pty tag-multi: multi-session tag operations + harden bulk pty tag
New sibling command pty tag-multi for multi-session work. Single-
session pty tag stays as-is; pty tag-multi lives next to it so a
fingerslip cant accidentally rewrite the wrong scope.
Three mutually-exclusive selectors:
<name>... explicit list (resolved up-front; if any
name is unresolvable the command aborts
before any write — no partial application)
--filter-tag k=v sessions matching tag(s); repeatable AND
--all every session
No ops = read mode (per-session tag dump, text or --json returning
{ name → tags }). Any ops (k=v / --rm k) flips to write mode. --all
writes are destructive across the whole session dir, so they require
--yes / -y; without it the command prints the matched count and
exits non-zero, no writes performed. Empty match (filter or --all
over an empty dir) is exit 0 with "0 sessions matched."
Each successful write goes through the existing atomic updateTags
+ tags_change event flow per-session; per-session no-ops emit
nothing. Selector resolution is upfront for explicit lists, lazy
for filter / --all (just listSessions).
Also hardens the existing single-session pty tag while we're here:
- rejects empty key in `=value`
- rejects --rm "" (empty key for removal)
- clearer error for --rm at end of argv with no key
- tightened parsing comments
Tests:
tests/tag-multi.test.ts (38 tests) — read explicit list, read
selectors (--filter-tag single/repeated/empty, --all on empty
dir), write explicit list (events fire per session, no-op on
one peer doesnt suppress the others, upfront-unresolvable
aborts), write selectors (--filter-tag, --all without --yes
rejected, --all --yes applied, -y short form), selector mutex
(--all+filter, --all+names, filter+names, no selector, ops
without selector), ops parsing errors (empty key, --rm at end,
--rm "", --filter-tag without value, --filter-tag without =,
multi-= splits on first =, set+rm same key wins for rm to
match pty tag).
tests/tag-bulk.test.ts (29 tests) — pin the existing pty tag
contract: bulk set, bulk rm, combined set+rm, position
independence, empty key/value edges, atomic events (one
tags_change per call), no-op detection, partial no-op + real
change, displayName resolution.
CHANGELOG and README reflect both.