projmux 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1367 @@
1
+ # CLI Task Guide
2
+
3
+ The hand-maintained half of the CLI documentation: what each command is for,
4
+ which flags change its behavior, and the contracts a route listing cannot
5
+ express.
6
+
7
+ The route inventory itself is not here. [CLI Reference](cli.md) is generated
8
+ from the command manifest the binary renders its own help from, so it is the
9
+ authority on which routes, sub-routes, output modes, and field projections
10
+ exist in a given build. This guide is the authority on everything a one-line
11
+ summary leaves out. When the two disagree about whether a route exists, the
12
+ generated page is right and this one is stale.
13
+
14
+ Run `projmux help` for the live top-level list, or `projmux <cmd> --help` /
15
+ `projmux <cmd> help` for the per-command usage string. See
16
+ [Help boundary](#help-boundary) for the exit-code and stream contract shared by
17
+ every `--help` invocation.
18
+
19
+ Exit codes:
20
+
21
+ - `0` — success.
22
+ - `1` — runtime failure.
23
+ - `2` — usage error (unknown flag, bad enum, missing required flag) or a
24
+ deterministic semantic exit (e.g. `focus` cannot resolve the target).
25
+
26
+ ## Help boundary
27
+
28
+ Help is handled once, at the root, from the shared command manifest rather than
29
+ by each leaf parser. Matching is on the flag **name** — the part after the
30
+ leading dashes and before any `=` — so every spelling the Go `flag` package
31
+ treats as help goes through this one boundary: `--help`, `-help`, `--h`, `-h`,
32
+ and any `=value` form of those (`--help=true`, `--help=false`, ...). `flag`
33
+ returns its help result for all of them regardless of the value, so aligning
34
+ with it keeps one notion of help; the boundary never interprets a flag value:
35
+
36
+ - Every help invocation exits `0`, writes to **stdout** only, records no
37
+ operational error, and performs no tmux, runtime, or lifecycle-migration
38
+ access. This includes nested routes (`projmux config edit --help`) and the
39
+ hidden internal namespace (`projmux internal popup-wait-key --help`).
40
+ - Help resolves to the deepest documented route and shows its summary, usage
41
+ synopsis, sub-routes, route-local output projections, and the canonical route
42
+ spelling it will move to. The synopsis names the route's flags (for example
43
+ `projmux setup terminal [terminal] [--apply] [--config <path>]
44
+ [--allow-symlink]`); the per-flag prose descriptions come from the command's
45
+ own parser and are not reproduced here.
46
+ - A help flag after the first bare `--` is payload, not help:
47
+ `projmux create agent -- --help` forwards `--help` to the launched process
48
+ unchanged.
49
+ - An unknown command keeps its `unknown command: <token>` error and exit `1`
50
+ even with `--help`, and `projmux help` / bare `projmux` keep printing the
51
+ top-level list. A bare `help` word nested under a command
52
+ (`projmux pin help`) still reaches that command's own handler.
53
+ - Because a help invocation runs no handler, it is never recorded as a state
54
+ change in the operations journal, at any depth — `projmux agent topic set --help`
55
+ logs neither an error nor a state-changing success.
56
+
57
+ ## Command inventory
58
+
59
+ The full route list -- every public top-level command, its sub-routes, its
60
+ usage synopsis, its shared `-o` output modes, and its route-local field
61
+ projections -- lives in the generated [CLI Reference](cli.md). That page is
62
+ rendered from the same command manifest the binary renders `projmux help`
63
+ from and is verified against it on every `make test`, so it cannot drift.
64
+ Nothing in this guide restates it.
65
+
66
+ ## Resource selectors and the active target
67
+
68
+ The resource routes (`get`, `describe`, `create`, `rename`, `rebind`, `delete`,
69
+ `agent resume`) address stored resources through one shared selector grammar.
70
+
71
+ The grammar in one paragraph: a value is either `uid:<uid>` or a bare
72
+ `metadata.name`. There is no bare-uid form, values are never split on commas,
73
+ and a `displayName`, a `spec.root` path, or a raw tmux `%N`/`@N`/`$N` handle
74
+ never resolves anything. `--project` occurs at most once and fixes the scope;
75
+ `--window` and `--pane` repeat and union in argv order; `--selector key=value`
76
+ repeats and ANDs. A singular route also accepts the target as a positional
77
+ `<ref>`, which may appear before or after the flags. How many resolved targets
78
+ each `<verb, kind>` pair accepts is a declared matrix, not a per-route rule, and
79
+ a violation is exit `2` with a candidate listing bounded to five rows.
80
+
81
+ ### Empty selector: the active tmux target
82
+
83
+ Inside tmux, an invocation that carries **no selector at all** addresses the
84
+ resource you are looking at instead of the whole registry:
85
+
86
+ ```
87
+ projmux describe pane # the active pane
88
+ projmux describe window # the window the active pane is in
89
+ projmux describe project # the Project that owns that window
90
+ projmux describe agent # the Agent that owns the active pane
91
+ projmux rename pane --name build # renames the active pane
92
+ projmux rebind project --root /new/path
93
+ projmux get pane -o uid
94
+ ```
95
+
96
+ The contract:
97
+
98
+ - **Omission is the only spelling.** There is no `--pane current` and no
99
+ `--pane active`. Those are legal `metadata.name` values today — `rename pane
100
+ --name current` succeeds — so a sentinel token would silently shadow a real
101
+ resource.
102
+ - **"No selector at all" means exactly that.** Any positional `<ref>`, any
103
+ `--project`/`--window`/`--pane`, or any `--selector` label keeps the
104
+ pre-existing behavior unchanged. The fallback is never blended into a
105
+ partially specified selector.
106
+ - **Only the singular routes.** The plural reads (`get projects|windows|panes|
107
+ agents`) stay 0..N inventories over their whole scope. `delete` and `create`
108
+ are unchanged: `create`'s omitted `--pane` selects a split anchor inside an
109
+ already-chosen Window, which is not the same question.
110
+ - **Inside tmux is decided by `$TMUX_PANE` plus `$TMUX`**, not by whether a tmux
111
+ server answers. A bare `display-message` from outside a client still succeeds
112
+ and answers for the most-recently-used session; projmux never uses that.
113
+ Outside tmux the empty-selector invocations keep their previous
114
+ `matched N ..., want exactly one` error and exit `2`.
115
+ - **No persistent scope.** There is no `set-context` equivalent and no stored
116
+ "current pane": the target is read from tmux focus on every invocation, so
117
+ there is nothing to go stale and nothing to invalidate.
118
+ - **`projmux describe pane` with no selector is the preview.** Because every
119
+ verb in this family resolves through the same seam, whatever `describe pane`
120
+ shows is exactly what `rename pane --name X` will act on;
121
+ `projmux get pane -o uid` (or `-o name`) gives the same answer as a scalar.
122
+
123
+ Resolution reads only two tmux options — `@projmux_pane_uid` on the active pane
124
+ and `@projmux_window_uid` on its window — and derives every ancestor from
125
+ registry `ownerRef`. The session-scoped `@projmux_project_uid` is deliberately
126
+ not consulted.
127
+
128
+ ### Rename and rebind live convergence
129
+
130
+ `rename project|window|pane` commits the selected Registry `metadata.name` and
131
+ then updates only its exact UID-bound live transport field:
132
+
133
+ - Project: `@projmux_project_name` (never the tmux session name)
134
+ - Window: `@projmux_window_name` (never `metadata.displayName` or tmux
135
+ `window_name`)
136
+ - Pane: `@projmux_pane_label` (never raw `pane_title`)
137
+
138
+ `rename agent` changes only the Agent's stable Window-scoped `metadata.name`.
139
+ It does not change the Agent topic, provider, lifecycle state, or managed Pane
140
+ name/title. `rebind project` preserves uid and session name, moves no files,
141
+ and updates only `spec.root` plus the exact session's
142
+ `@projmux_project_path` anchor.
143
+
144
+ The Registry commit is authoritative. Inside tmux, immediate convergence uses
145
+ only the inherited absolute socket path (`tmux -S`) and stable `$N`/`@N`/`%N`
146
+ handles. Outside tmux the command probes no default server and remains
147
+ Registry-only. If the exact UID is not present on that transport, or its
148
+ inventory cannot establish that it is live, the command succeeds with offline
149
+ drift that a later explicit-socket `projmux reconcile resources` can converge.
150
+ If an exact live target is found but its option write fails, or more than one
151
+ live object claims the UID, the command exits nonzero, states that Registry data
152
+ already committed, and names the same public reconcile route as the retry. A
153
+ valid unique Project UID wins over the old path after rebind; unknown and
154
+ duplicate UID claims remain fail-closed.
155
+
156
+ ### When the active target is not a Projmux resource
157
+
158
+ A pane created outside the registry-backed routes carries no
159
+ `@projmux_pane_uid`, so "the active pane is not a Pane resource" is an ordinary
160
+ situation rather than a corner case. When that happens the command **refuses**:
161
+
162
+ ```
163
+ $ projmux describe pane
164
+ resolve pane: no selector was given and the active tmux pane %46 carries no
165
+ @projmux_pane_uid; nothing was selected, so pass an explicit resource reference
166
+ or --selector
167
+ ```
168
+
169
+ It exits `2`, writes nothing to stdout, selects no other resource, and creates
170
+ and mutates nothing. The message is deliberately *not* the
171
+ `matched N ..., want exactly one` ambiguity error: that wording plus its
172
+ candidate listing would read as ordinary ambiguity and hide the real cause. The
173
+ variants name what was inspected — a missing `@projmux_window_uid`, a mirrored
174
+ uid the registry does not hold, or an active pane that is a shell Pane with no
175
+ owning Agent.
176
+
177
+ ### `get pane --current` is a different route
178
+
179
+ `projmux get pane --current -o cwd` is unrelated to the fallback and is not
180
+ deprecated by it. It never reads the resource registry: it prints the live tmux
181
+ `#{pane_current_path}` of the focused pane as a bare path scalar, and `cwd` is
182
+ the only projection it accepts. `projmux get pane` with no selector resolves a
183
+ registry **Pane resource** and renders the shared resource projection. Different
184
+ source, different output, different failure surface — both stay.
185
+
186
+ ## reconcile resources
187
+
188
+ ```text
189
+ projmux reconcile resources [--dry-run] [--socket <name> | --socket-path <absolute>] [-o json]
190
+ ```
191
+
192
+ `reconcile resources` is the explicit repair boundary for drift between the
193
+ authoritative resource Registry and one exact tmux server. `--dry-run` and the
194
+ default execute mode use the same deterministic plan. The plan classifies
195
+ missing, stale, foreign, and orphan state; separates Registry changes from tmux
196
+ mirror writes; and reports the target plus changed, no-op, and failed counts.
197
+ `-o json` emits the same item keys, order, outcomes, remaining drift, completed
198
+ stages, and retry command as structured data.
199
+
200
+ Socket selection is fail closed:
201
+
202
+ - `--socket <name>` means exactly `tmux -L <name>`.
203
+ - `--socket-path <absolute>` means exactly `tmux -S <absolute>`.
204
+ - The two flags are mutually exclusive.
205
+ - With neither flag, an invocation inside tmux inherits only the absolute
206
+ socket path from `$TMUX` and uses `-S`.
207
+ - With neither flag outside tmux, the command is a usage error before the
208
+ Registry, filesystem, or tmux is mutated. There is no default-socket guess or
209
+ fallback to another server.
210
+
211
+ Dry-run performs only reads: Registry bytes, tmux options/window names, and
212
+ other filesystem state stay unchanged. Execute rebuilds the plan from current
213
+ state while holding the Registry lock, commits Registry authority first, then
214
+ prevalidates every exact live UID target before replaying the planned mirror
215
+ writes. A repeat against converged state is a no-op. Foreign or ambiguous live
216
+ UIDs are reported and refused rather than merged or replaced; unrelated
217
+ resources and sockets are not touched.
218
+
219
+ If Registry commit or a live mirror write fails, the result identifies the
220
+ completed stages, replans the remaining drift, and prints an exact
221
+ `projmux reconcile resources ...` retry. Registry commit failure publishes no
222
+ planned UID into tmux; a later tmux failure leaves the already durable Registry
223
+ identity authoritative and retryable.
224
+
225
+ `doctor`, `get`, and `describe` remain read-only. `doctor` reports general
226
+ runtime/integration health; `reconcile resources` is the opt-in repair command.
227
+ It does not run `config apply`, reload unrelated configuration, reconstruct a
228
+ missing Registry, heuristically merge identities, or turn a read verb into a
229
+ write transaction.
230
+
231
+ ## Internal plumbing (`projmux internal ...`)
232
+
233
+ `internal` is a hidden namespace for the routes generated tmux config, tmux
234
+ hooks, popup payloads, and provider hooks invoke. Nothing under it is meant to
235
+ be typed at a prompt, so it is absent from `projmux help` and from the
236
+ generated [CLI Reference](cli.md). It is listed here because operators still
237
+ have to recognize these spellings when they show up in a generated config, a
238
+ hook payload, or a `make install` log.
239
+
240
+ | Route | Purpose |
241
+ | --- | --- |
242
+ | `internal tmux` | Generated config render/install/apply, popup entry helpers, pane rebalance/rename, snapshot autosave. |
243
+ | `internal status` | Status bar segment renderers (`git`, `project`, `usage`, `notify`, `resources`). |
244
+ | `internal statusbar` | Status bar click and shortcut dispatch (`click`, `usage-refresh`). |
245
+ | `internal preview` | Persisted preview cursor (`cycle-pane`, `cycle-window`, `select`). |
246
+ | `internal session-popup` | Session popup preview/open and popup cursor movement. |
247
+ | `internal agent-hook` | Provider hook ingest (`ingest`) and the Agent pane title watcher (`watch-title`). |
248
+ | `internal focus` | Machine focus ingress used by statusbar and notification-sidebar actions. |
249
+ | `internal key-broker` | Darwin physical key transport. |
250
+ | `internal popup-wait-key` | Single-key reader that closes a display-only popup. |
251
+
252
+ The old pre-namespace top-level aliases have been removed. Invoking `status`,
253
+ `statusbar`, `preview`, `session-popup`, `tmux`, `key-broker`, or
254
+ `popup-wait-key` as the first token is an unknown command (exit 1). Generated
255
+ payloads use the `internal` routes above.
256
+
257
+ The tmux plumbing has public equivalents where an operator needs them:
258
+
259
+ | internal spelling | public spelling |
260
+ | --- | --- |
261
+ | `projmux internal tmux print-config` | `projmux config render standalone` |
262
+ | `projmux internal tmux print-app-config` | `projmux config render app` |
263
+ | `projmux internal tmux apply` | `projmux config apply` |
264
+ | `projmux internal tmux install` | *(none — installer plumbing)* |
265
+ | `projmux internal tmux install-app` | *(none — installer plumbing)* |
266
+
267
+ The public spellings forward to the same internal handlers. `make install`
268
+ calls `projmux config apply`.
269
+
270
+ The complete compatibility-to-canonical removal record, including intentional
271
+ error differences, is maintained in the [Legacy CLI Retirement Ledger](legacy-cli-retirement.md).
272
+
273
+ `install` and `install-app` deliberately have no public spelling. They write and
274
+ wire up config files as part of the install pipeline rather than answering an
275
+ operator's question, so they stay reachable only as `projmux internal tmux ...`.
276
+
277
+ ## switch
278
+
279
+ ```
280
+ projmux switch [--ui=popup|sidebar]
281
+ projmux switch open <path>
282
+ projmux switch toggle-tag | toggle-pin | kill | settings | preview
283
+ projmux switch cycle-pane | cycle-window | sidebar-focus
284
+ ```
285
+
286
+ Project picker. With no positional argument, opens the configured picker popup
287
+ or sidebar (depending on entry helper). `switch open <path>` jumps directly. The
288
+ sub-verbs are entry hooks invoked by tmux keybindings (e.g.
289
+ `sidebar-focus` is wired to the sidebar's focus binding so navigation keeps
290
+ the active session in sync).
291
+
292
+ Settings > Labs remains available for experimental settings, but picker
293
+ selection/source rows have been retired. The native picker is always used, and
294
+ there is no picker selection configuration or migration behavior.
295
+
296
+ ## setup
297
+
298
+ ```
299
+ projmux setup [--timeout DURATION] [--non-interactive]
300
+ ```
301
+
302
+ Probes the guaranteed launch defaults (`Alt-1..5`) plus transport candidates
303
+ such as `Ctrl-N`, `Ctrl-Shift-{R,L,M}`, `Ctrl-M`, and
304
+ `Alt-Shift-{Left,Right}`. Reports whether each key arrived as the expected
305
+ plain bytes, an unsupported legacy/app-specific sequence, an unknown sequence,
306
+ or a timeout. `--non-interactive` skips the TTY probe and prints the expected
307
+ key map. Default `--timeout` is `5s`. Run it outside tmux after trying
308
+ `projmux shell`; `Alt-1..5` are the only guaranteed zero-config defaults.
309
+ Settings > Keybindings edits in-app action aliases; terminal delivery
310
+ diagnostics stay in this CLI flow.
311
+
312
+ ## setup terminal
313
+
314
+ ```
315
+ projmux setup terminal [terminal] [--apply] [--config <path>]
316
+ [--allow-symlink]
317
+ ```
318
+
319
+ Previews or applies terminal-specific key delivery mappings for supported
320
+ terminals when `projmux setup` reports swallowed shortcuts. When `terminal` is
321
+ omitted, autodetects from `$TERM_PROGRAM`/`$TERMINAL_EMULATOR`.
322
+ Known terminals: `ghostty`, `windows-terminal`. Default is a read-only
323
+ preview; pass `--apply` to write (timestamped `.bak.<timestamp>` is created). Refuses to
324
+ write through a symlink unless `--allow-symlink` is passed (dotfiles repos).
325
+ `--config <path>` overrides the candidate list when the adapter has more
326
+ than one default location (Ghostty `config` vs `config.ghostty`). If setup
327
+ shows every key arriving, skip terminal remediation.
328
+
329
+ ## doctor
330
+
331
+ ```
332
+ projmux doctor [--json] [--section deps|runtime|integrations|session-state|logs] [--verbose]
333
+ ```
334
+
335
+ Runs read-only diagnostics, including a dependency check for `tmux ≥ 3.4`,
336
+ `git`, and `stty` (POSIX only), then reports read-only AI notify integration diagnostics
337
+ for Codex hooks, Claude Code hooks, Antigravity hooks/statusline, and the tmux bell
338
+ fallback. AI notify integration statuses are `installed`, `missing`, or
339
+ `conflict`; missing or conflicting integrations are informational and do not
340
+ make doctor fail. It also reports read-only Session State resume metadata
341
+ diagnostics for saved agent panes, including `available`, `stale`, or
342
+ `unavailable` status plus confidence, source, updated-at, and the affected
343
+ snapshot/window/pane.
344
+
345
+ Session State preview and doctor report the resume source captured on the pane.
346
+ Live `hook`/`session-id` metadata is high confidence; DB-validated Antigravity
347
+ `antigravity-last-conversation` and `antigravity-conversation-metadata` picker
348
+ sources are medium confidence; legacy `antigravity-history` is low confidence.
349
+ Disk discovery never lowers or overwrites an already captured live source.
350
+
351
+ The default text report shows per-section summaries plus failing or warning
352
+ items. `--verbose` adds successful checks and complete typed detail, including
353
+ versions, paths, confidence/source metadata, and displayed remediation.
354
+ `--section` projects the same inventory used by text and JSON: `deps` selects
355
+ dependencies, `integrations` selects AI notify integrations, and
356
+ `session-state` selects resume metadata plus retention guidance. `runtime`
357
+ selects the fixed `tmux` backend, an actual one-second read-only probe of the
358
+ app socket, and generated-versus-live config digest state. `logs` selects the
359
+ state/log/journal presence, private permissions and metadata-only writability
360
+ checks plus a bounded aggregate of recent safe operational error codes. These
361
+ sections expose only closed status codes and counts: no path, socket name,
362
+ routing identity, message, config content, or argv is rendered.
363
+ The recent-error count is the size of the newest 20-record window, not a
364
+ lifetime total; `logs.recent-errors.bounded` means older errors were omitted.
365
+ The probe captures at most 4 KiB, generated config inspection reads at most
366
+ 1 MiB, and the journal seam reads at most 5 MiB.
367
+ Symlinks and non-regular inputs are rejected without following or blocking on
368
+ them. On Windows, POSIX mode bits cannot establish ACL privacy, so otherwise
369
+ valid paths report the closed `privacy-unverified` warning rather than a false
370
+ private/insecure classification, followed by a separate metadata-only `ready`
371
+ or `not-writable` finding; Doctor never changes ACLs.
372
+
373
+ JSON reports have integer `schema_version: 2`. An unfiltered report retains the
374
+ existing typed `dependencies`, `ai_notify_integrations`,
375
+ `session_state_resume`, and `session_state_prune` detail and adds ordered
376
+ `runtime` and `logs` finding arrays. Every finding has closed `severity`,
377
+ stable `code`, and closed `remediation`; bounded aggregates may add `count`
378
+ and `safe_codes`. A filtered report contains only the selected typed field(s).
379
+ `--verbose` is accepted with `--json` but does not change JSON fields or values.
380
+
381
+ Default and verbose text exit non-zero only when their projected dependency
382
+ inventory contains a required missing or stale dependency. A non-`deps`
383
+ section exits `0`. JSON preserves its successful exit after emitting a report,
384
+ even when required dependencies are missing or stale.
385
+
386
+ Doctor is read-only for every flag combination. It never creates or repairs
387
+ state/log paths, installs packages, runs displayed remediation, changes
388
+ terminal or tmux state, generates/applies config, migrates files, or writes
389
+ operational-log outcomes. Missing, malformed, or permission-denied journals
390
+ degrade to typed log findings without failing the command. The removed `--install-missing`,
391
+ `--include-optional`, and Doctor `--dry-run` mutation flags fail as unknown
392
+ usage with an exact instruction to remove the flag and run displayed
393
+ remediation explicitly outside Doctor; they are never ignored. Doctor does not
394
+ diagnose terminal key delivery; use `projmux setup` for that.
395
+
396
+ JSON migration: consumers must switch on `schema_version` before decoding.
397
+ Version 2 changes the previously empty/reserved `runtime` and `logs` arrays to
398
+ the typed finding shape above; field meanings inside the version 1 dependency,
399
+ integration, and Session State inventories are unchanged. Consumers that only
400
+ understand version 1 must reject version 2 rather than decoding the new arrays
401
+ as the old empty placeholder shape.
402
+
403
+ `Settings > Notifications > Delivery sources` shows active Codex, Claude, and
404
+ Antigravity hooks plus tmux statuses, conflicts, config paths, and
405
+ copyable AI integration commands where available. Its summary/detail also shows
406
+ whether `PROJMUX_NOTIFY_HOOK` overrides the built-in desktop sender. Settings
407
+ does not install or remove external Codex, Claude, Antigravity, or tmux notify
408
+ wiring. Pending in-app queue rows remain owned by the statusbar/sidebar rather
409
+ than a standalone Settings row.
410
+
411
+ ## diagnostics
412
+
413
+ ```
414
+ projmux diagnostics log [--tail N] [--json]
415
+ [--level info|error] [--component NAME] [--path]
416
+ projmux diagnostics report [--output <path>]
417
+ ```
418
+
419
+ Reads the local operational event journal through the same tolerant JSONL
420
+ reader used by every output mode. The default text view shows the newest 50
421
+ valid records. `--tail N` changes that bound, `--json` emits the selected
422
+ records as JSONL, and `--level` / `--component` filter before tailing. `--path`
423
+ prints the resolved path without creating or reading the log.
424
+
425
+ Successful state-changing top-level commands produce one `info` outcome, and
426
+ every top-level command error produces one `error` outcome. Successful
427
+ high-frequency/read-only commands such as `internal status`, `internal
428
+ agent-hook ingest`, `attention arm`/`clear`/`window`, `internal tmux
429
+ autosave-session-state`, `window record`, and
430
+ successful `diagnostics log` views do not produce an event. Errors from those
431
+ automatic hook/poll paths still produce one safe `error` outcome. Successful
432
+ direct command help and explicit `--dry-run` preview modes also remain
433
+ read-only and do not produce an event. Journal failures are a best-effort side
434
+ channel and never change command output or exit status. See
435
+ [operational-diagnostics.md](operational-diagnostics.md) for the file,
436
+ retention, concurrency, and privacy contracts.
437
+
438
+ `internal agent-hook watch-title` emits a bounded common lifecycle: one start, one terminal
439
+ pane-gone/hook-active stop, and at most one copy of each closed watcher failure
440
+ tuple per process. Normal polling iterations emit nothing. AI hook ingest emits
441
+ common events only for malformed/read/oversized payloads, unmatched or invalid
442
+ targets, unsupported event classification, and route failures. Provider event
443
+ names, payloads, prompt/tool/transcript values, notification text, pane
444
+ metadata, paths, UUIDs, and conversation/session IDs are never stored. Normal
445
+ state/notify/quiet/dedupe hook results stay zero-volume in the common AI family;
446
+ the existing notify transition remains the owner when notification behavior
447
+ occurs.
448
+
449
+ Session create/attach/switch/kill and `config apply` use correlated
450
+ `lifecycle.start`/`lifecycle.outcome` records instead of a duplicate generic
451
+ top-level outcome. The text and JSONL views expose only the closed safe
452
+ `operation` and optional `code` enums; session names, socket paths, tmux
453
+ targets, subprocess argv, and generated configuration are never recorded.
454
+
455
+ `diagnostics report` is the explicit consent boundary for creating one local
456
+ private `tar.gz` support archive. The invocation first prints a redacted
457
+ destination label, the complete included/omitted entry list, stable omission reasons, report
458
+ schema, and redaction mode; the first parent/temp/archive write happens only
459
+ after that preview is successfully written. `--output` selects the local
460
+ destination. Without it, the command uses a timestamped archive in the current
461
+ directory. Existing destinations are never replaced.
462
+
463
+ The archive contains `manifest.json`, safe projmux version/platform/backend
464
+ metadata, a redacted projection of Doctor JSON schema version 2,
465
+ config presence states (never values), up to 50 recent errors from the existing
466
+ bounded operations reader, and count-only AI ingest diagnostics. Paths,
467
+ session/window/pane/thread/routing identifiers, run IDs, tool/version output,
468
+ commands, guidance, reasons, and other free text are field-scoped hashes unless they
469
+ match a closed diagnostic enum/static-name allowlist. Raw config/environment
470
+ values, argv/stdin, prompts, notification text, pane output, transcripts, and
471
+ hook payloads are never collected. Missing, corrupt, or unreadable sources are
472
+ recorded as stable manifest omissions. Report collection does not repair source
473
+ permissions, migrate hooks, append an operational outcome, contact a network,
474
+ upload, create an issue, or run in the background; only the explicitly selected
475
+ output parent/temp/archive can be written.
476
+
477
+ The redacted Doctor projection keeps `schema_version`, closed runtime/log
478
+ finding enums, and structural/count
479
+ numbers as numbers. Numeric routing fields such as `window_index` and
480
+ `pane_index` become field-scoped hash strings under `default-hash-v1`; consumers
481
+ must treat this support projection as redacted evidence rather than decoding it
482
+ back into the unredacted Doctor Go types.
483
+
484
+ ## focus
485
+
486
+ ```
487
+ projmux focus project <ref> [--socket <path>] [--client <tty>] [--json]
488
+ projmux focus window <ref> --project <ref> [--socket <path>] [--client <tty>] [--json]
489
+ projmux focus pane <ref> --project <ref> --window <ref> [--socket <path>] [--json]
490
+ ```
491
+
492
+ Moves one attached client to an exact live Project, Window, or Pane. It never
493
+ materializes an offline resource or force-detaches another client. `--socket`
494
+ is explicit; when omitted, it is derived from `$TMUX`.
495
+
496
+ Generated statusbar and notification-sidebar payloads use the hidden
497
+ `projmux internal focus` spelling. The old public `focus --target|--uri`
498
+ machine argv has been removed.
499
+
500
+ `projmux focus` stops at the tmux layer. It never asks the host terminal
501
+ window to come forward, in any Desktop notification mode.
502
+
503
+ `--client` is a preferred origin tmux client. In-app consumers such as the
504
+ status bar and notify sidebar pass the clicked client so focus redirects that
505
+ display first. If that client is gone, focus falls back to an attached client
506
+ already viewing the target session, then the stable first attached client.
507
+ Toast clicks do not pass `--client`.
508
+
509
+ The removed public `--uri`/`--target` ingress is not accepted here. Generated
510
+ machine payloads use `projmux internal focus`; human callers select an exact
511
+ Project, Window, or Pane with the public kind routes above.
512
+
513
+ Exit codes:
514
+
515
+ - `0` — focused (or the notify-only fallback fired).
516
+ - `2` — target session/window/pane could not be resolved
517
+ (`focusExitNotResolved`).
518
+
519
+ `--source`/`--kind` are telemetry labels logged when
520
+ `PROJMUX_FOCUS_DEBUG` is set. `--json` prints a single-line JSON payload
521
+ with `{ok, fallback, target, socket, resolved_session, client, dispatch,
522
+ session_state, window_state, pane_state, reason, note}`. Callers can
523
+ distinguish unresolved sessions (`reason=session-unresolved`, exit 2),
524
+ session rename/prefix fallback (`session_state=fallback`), window index
525
+ fallback (`window_state=index-fallback-session`), pane index fallback
526
+ (`pane_state=index-fallback-window`), and explicit id failures
527
+ (`window-id-unresolved` / `pane-id-unresolved`, exit 2).
528
+
529
+ ## notification queue
530
+
531
+ Pending AI notification queue. `attention` is live tmux pane state; the queue is
532
+ the explicit-ack pending notification source of truth used by the status-bar
533
+ notify segment and notify sidebar. It is not the source of truth for all live pane attention. See
534
+ [notify-queue.md](notify-queue.md) for the full data model.
535
+
536
+ Use only the canonical queue spellings:
537
+
538
+ ```
539
+ projmux create notification --text <s> --target <SESSION[:WINDOW[.PANE]]> ...
540
+ projmux get notifications [--live] [--json] [--limit N] ...
541
+ projmux notification ack <id> | --all
542
+ projmux notification reconcile [--json]
543
+ ```
544
+
545
+ - `create notification` — append (or refresh, with `--id`) one entry. `--ttl` defaults to
546
+ `600` seconds as freshness metadata; expiration does not remove rows from
547
+ `get notifications` and is considered only by reconcile together with a gone
548
+ target. Reconcile also retains only the newest 256 rows. `--text` is hard-capped to 80 runes (longer text is
549
+ truncated server-side). After a successful queue write, projmux sends a
550
+ best-effort refresh event to open native notify sidebars and fires
551
+ declarative `[hooks.send-noti]` asynchronously if configured. Event delivery
552
+ failure does not fail the queue write; reopening the sidebar still shows the
553
+ latest queue. The hook gets a JSON payload on stdin plus
554
+ `PROJMUX_NOTIFY_*` env vars, and it does not replace the normal desktop
555
+ notification path.
556
+ - `get notifications` — newest-first pending queue table `ID AGE SEV SRC TARGET TEXT`
557
+ (or JSON). `--severity` and `--source` are repeatable filters.
558
+ `--live` adds a non-mutating explanation table (or JSON report) that
559
+ compares queued entries with live pane attention. It calls out manual
560
+ reply badges that do not queue because no AI agent is attached, live AI
561
+ reply panes missing a queue entry, matched AI reply entries, inactive
562
+ (`queue-stale`) queue entries whose live pane EXISTS but no longer matches
563
+ reply+agent state, and gone (`queue-gone`) entries whose pane is absent from
564
+ the real tmux live pane inventory (or which have no routable target).
565
+ `--ui=sidebar` opens the compact interactive notify list where Enter
566
+ focuses and acks live or inactive-routable targets, cleans gone/unroutable
567
+ targets without focusing, `a` acks the selected row, `x` clears non-critical
568
+ rows, and `Ctrl-X` clears all; opening or navigating the sidebar does not ack.
569
+ While open, the native
570
+ sidebar refreshes its row list on successful queue-write events without an
571
+ Alt-2 close/reopen toggle, using the same deferred refresh path as `a` and
572
+ `x`. The sidebar is a pane/session-grouped inbox whose collapsed rows are
573
+ fixed three-line cards: project/session, agent/provider, and newest age on
574
+ line 1; topic/pane-title/task context plus severity/live-state metadata on
575
+ line 2; and the latest notification preview on line 3. Hidden queue ids
576
+ remain action values, but the sidebar has no search input. `--client` is used
577
+ by tmux popup launchers to keep row-select focus on the clicked client.
578
+ - `ack <id>` removes one entry; `--all` flushes the queue.
579
+ - `reconcile` — walks `tmux list-panes -a` and back-fills entries for
580
+ panes whose attention state is `reply` AND whose AI agent option is
581
+ set, reporting stale `ai:` entries that no longer match a live pane without
582
+ acking them.
583
+ Soft-fails (no error, populated `errors` field in the summary) when
584
+ tmux is not running. Use this as the recovery path when the queue and
585
+ live pane state drift.
586
+
587
+ ## agent usage
588
+
589
+ Authoritative AI account usage. See [usage-tracking.md](usage-tracking.md)
590
+ for adapter detail.
591
+
592
+ ```
593
+ projmux agent usage [--model codex|claude|antigravity|all] [--window 5h|weekly|context|quota|all]
594
+ [--json] [--force|-f]
595
+ ```
596
+
597
+ Renders a tab-aligned `MODEL WINDOW PCT RESETS_AT RESET_IN STALE` table; appends a
598
+ backoff note when an adapter is in 429 cooldown. `--force` clears any
599
+ active backoff and bypasses the per-adapter throttle floor (Claude `5m`,
600
+ Codex shares the global `30s`). `--json` emits the snapshot array; when
601
+ backoff is active the wrapper `{snapshots, backoff}` object is emitted
602
+ instead.
603
+
604
+ Claude keeps the canonical aggregate `5h` and `weekly` rows and projects each
605
+ valid typed upstream `limits[]` entry as a named `quota/<exact group>` row.
606
+ Model-scoped rows append the exact upstream model display identity in text
607
+ output; terminal controls are escaped and the visible label is bounded without
608
+ normalizing the stored identity. JSON preserves the typed named-quota metadata,
609
+ including nullable scope/model ID/surface, reset, `updated_at`, and `stale`.
610
+ These percent-only rows never synthesize token counts. A malformed limits block
611
+ fails that adapter refresh so the prior complete Claude slice remains visible;
612
+ a valid aggregate-only response replaces and removes obsolete named rows.
613
+
614
+ Antigravity emits official account rows labelled
615
+ `quota/<upstream bucket ID>`. Conversation-local context remains private
616
+ hook/notify diagnostic metadata and legacy cached context rows are suppressed
617
+ from text and JSON output. `--window quota` selects named account rows; opaque
618
+ IDs such as `weekly` are not aliases for the fixed `weekly` window. Used
619
+ percent is `100 * (1 - remaining_fraction)`. `--window context` remains
620
+ accepted for compatibility and returns no Usage rows.
621
+ `reset_time` and optional `reset_in_seconds` are preserved independently,
622
+ including the distinction between absent and explicit zero. Invalid,
623
+ disabled, missing, or empty quota data degrades without reinterpreting the
624
+ private conversation context diagnostic.
625
+
626
+ ## resources
627
+
628
+ ```
629
+ projmux resources
630
+ ```
631
+
632
+ Opens the native, read-only Resource Inspector. It samples only while this
633
+ interactive process is alive, paints `warming` immediately, then refreshes at
634
+ a non-overlapping two-second cadence. Right or Enter drills Project → Window →
635
+ Pane → Pane detail; Left returns (and is a no-op at the root), while Esc closes
636
+ the popup at every depth. Search matches the current scope's display name and
637
+ stable tmux id. Tab cycles CPU, Memory, and Name sorting; Ctrl-R requests an
638
+ immediate refresh. A plain `r` remains search input.
639
+
640
+ CPU list values are host-capacity share; pane detail also shows
641
+ core-equivalent CPU. Project and window rows count panes, while pane rows count
642
+ the attributed processes. Pane rows and detail share the resolved pane identity
643
+ and label the tmux current command, PID/SID, pane id, and TTY separately. Memory
644
+ is explicitly an RSS sum (shared pages can be counted more than once) plus its
645
+ host ratio. `No project match`, `Multiple project matches`, and non-drillable
646
+ `Other / unattributed` remain explicit. The first two are display labels over
647
+ stable internal attribution keys. Warming, partial, unavailable, unknown, and
648
+ overage states also remain explicit. No process
649
+ command list, mutation, history, graph, daemon, persistence, or Session State
650
+ telemetry is created. Linux/tmux provides attribution; unsupported platforms
651
+ show an unavailable reason rather than zero metrics.
652
+
653
+ Only Resource Inspector anomalies (`unavailable`, `partial`, `stale`, closed
654
+ collection-stage errors, and scan-budget exhaustion) enter the private bounded
655
+ operations journal. Identical persistent anomalies are transition-coalesced;
656
+ healthy periodic samples and refreshes emit zero records. These records contain
657
+ no CPU/RSS values, PID/process detail, project/tmux identity, path/title/command,
658
+ or arbitrary error/status text. `diagnostics report` includes only error-level
659
+ closed resource outcomes; local-only partial/stale info rows are omitted.
660
+
661
+ This is distinct from `projmux internal status resources`, which remains the short
662
+ host-only statusbar renderer.
663
+
664
+ ## internal status
665
+
666
+ Per-segment status-bar renderers. All are silent on failure — the
667
+ tmux status interval polls them and must never produce a stack trace.
668
+
669
+ ```
670
+ projmux internal status git [path]
671
+ projmux internal status usage [--max-width N] [--force|-f]
672
+ projmux internal status notify [--max-width N]
673
+ projmux internal status resources
674
+ ```
675
+
676
+ - `git` — `#[bold,fg=colour16,bg=colour45] <branch> <state> #[default]` for
677
+ the pane's `pane_current_path` (or the supplied path). Empty when not in a
678
+ repo. `<state>` is omitted when clean, otherwise it may include `*` for
679
+ local changes, `+N` staged entries, and `↑N`/`↓N` ahead/behind counts, with
680
+ compact per-token colors in tmux output.
681
+ - `usage` — HUD-style provider blocks containing only official `5h` and
682
+ `weekly` windows. Antigravity's exact `quota/gemini-weekly` snapshot is
683
+ projected as `weekly` without changing its cached identity; other named
684
+ quotas and context never consume status width. Claude typed `limits[]`
685
+ named/model rows are likewise excluded, so only its aggregate `5h` and
686
+ `weekly` rows reach the status line. Narrow tiers keep one primary window per
687
+ provider (`5h`, otherwise `weekly`) before hard truncation.
688
+ Triggers an opportunistic, throttled refresh (per-adapter
689
+ throttle, `30s` floor) so a stale cache self-heals.
690
+ - `notify` — newest-first HUD block with project, state, optional agent, text,
691
+ age, and `+<extras>`. Window/pane ids remain routable metadata but are not
692
+ displayed in the compact HUD. Degrades through width tiers; default
693
+ `--max-width` is `200` runes.
694
+ - `resources` — macOS/Linux/WSL aggregate `CPU N% MEM N%`. Linux uses
695
+ `/proc/stat` and `/proc/meminfo`; macOS uses native Mach host statistics.
696
+ CPU needs two invocations to establish a delta and renders `--` for the first
697
+ sample. CPU independently warns at 70–89% and becomes bold critical at 90% or
698
+ above; memory independently warns at 75–89% and becomes bold critical at 90%
699
+ or above. Normal and unavailable values use the secondary status-text role.
700
+ These values are host-scoped rather than pane/window/project/session
701
+ attribution. WSL reports the Linux guest/VM view. Unsupported platforms and
702
+ unreadable system metrics produce no error output.
703
+
704
+ ## internal statusbar
705
+
706
+ ```
707
+ projmux internal statusbar click <range-id> [--socket <s>] [--mouse-window <id>]
708
+ [--client <tty>] [--mouse-x N] [--mouse-y N]
709
+ projmux internal statusbar usage-refresh
710
+ ```
711
+
712
+ Click/keyboard dispatcher for the two-line status bar. Implemented range ids:
713
+ `session pwd git resources usage notify settings`. The bare `window` /
714
+ `window|<idx>` token (tmux's built-in window-list range) and the empty
715
+ range fall through to `select-window -t @<mouse_window>` so the native
716
+ click-to-switch tab affordance is preserved on row 1. Unknown range ids are
717
+ non-specialized placeholders and no-op. `session` opens the existing-session
718
+ popup; `pwd` shows the current pane path in a native-framed display-only
719
+ popup; `git` opens the project switcher popup;
720
+ `settings` toggles the settings popup for the tmux client; `usage` opens the
721
+ detailed cached account-usage popup. Legacy context rows are suppressed and
722
+ named quotas retain exact identity/reset/freshness values. Claude model-scoped
723
+ rows distinguish the exact group and model display identity with bounded,
724
+ terminal-safe labels; JSON retains their full typed metadata. `USED`, `LIMIT`,
725
+ and `LEFT` appear together only when at least one displayed row has real
726
+ absolute counts; percent-only datasets omit those columns rather than
727
+ synthesizing counts.
728
+ `notify` focuses and acks the newest actionable queue target. The internal `usage-refresh` shortcut entry point
729
+ runs the same throttled, per-adapter collection policy as `internal status usage` and
730
+ then reopens the display-only usage popup from cache.
731
+ `MouseDown1Status` errors are
732
+ swallowed and surfaced as `display-message` toasts so a transient
733
+ failure does not raise a tmux error popup. See [statusbar.md](statusbar.md).
734
+
735
+ ## attention
736
+
737
+ ```
738
+ projmux attention toggle [pane]
739
+ projmux attention clear [pane]
740
+ projmux attention arm [pane]
741
+ projmux attention list [--json] [--all]
742
+ projmux attention window [window]
743
+ ```
744
+
745
+ Toggles the `✳` pane title prefix and the `@projmux_attention_state` pane
746
+ option. `toggle` flips between cleared and `reply`; `clear` always
747
+ clears; `arm` sets a pre-reply armed state used by the AI flow. The
748
+ producer side pushes the matching entry into the notify queue when the pane
749
+ has an associated AI agent option; clearing attention does not ack the queue
750
+ row (manual toggles on shell panes do not push). `list` reads `tmux list-panes -a` and shows live pane
751
+ attention state without reading or mutating the notify queue; by default
752
+ it shows panes with an attention option or title marker, and `--all`
753
+ includes every pane. `window` renders the status-bar window badge for the
754
+ supplied window.
755
+
756
+ ## Agent creation and hook ingress
757
+
758
+ ```
759
+ projmux create agent --provider <claude|codex|antigravity> [--placement right|down] ...
760
+ projmux create pane [--placement right|down] ...
761
+ projmux config edit [--get|--set <mode>]
762
+ projmux agent status set <thinking|waiting|idle> [pane]
763
+ projmux agent topic ...
764
+ projmux internal agent-hook watch-title [pane]
765
+ projmux internal agent-hook ingest codex-hook < payload.json
766
+ projmux internal agent-hook ingest claude-hook < payload.json
767
+ projmux internal agent-hook ingest antigravity-hook [--event <PreInvocation|PostInvocation|PostToolUse|Stop|Statusline>] < payload.json
768
+ projmux internal agent-hook ingest bell --pane <pane_id>
769
+ projmux diagnostics agent-hook [--tail N] [--json] [--path]
770
+ projmux agent integrate codex [--dry-run] [--remove]
771
+ projmux agent integrate claude [--dry-run] [--remove]
772
+ projmux agent integrate antigravity [--dry-run] [--remove]
773
+ projmux agent integrate tmux-bell [--dry-run] [--remove]
774
+ ```
775
+
776
+ These routes manage the Agent lifecycle and the per-pane state machine that drives
777
+ the `attention` badge, the `notify` queue producer, and the desktop
778
+ notifier. `status set waiting` is the trigger that flips a pane to the
779
+ reply-ready state — that transition pushes an `ai:<session>:<pane>`
780
+ entry into the notify queue.
781
+ Use `projmux diagnostics agent-hook` for the bounded ingest journal.
782
+ The durable semantic status badge is stored separately in
783
+ `@projmux_ai_badge_kind` as `approval_required`, `input_required`,
784
+ `response_complete`, `in_progress`, or unset. The live status surfaces color
785
+ those values with action-required amber-orange, success green, and progress
786
+ yellow respectively. That palette is independent from notify queue
787
+ `--severity` and from OS desktop notification urgency; a critical approval row
788
+ can still render a non-red action-required status badge.
789
+
790
+ `create agent --provider ...` selects a provider without changing the saved
791
+ default. Concrete provider invocations create a new managed
792
+ agent pane every time; existing managed AI panes in the same project/session are
793
+ not selected or reused.
794
+ The provider picker and plain Pane creation remain available through the
795
+ canonical create workflow. Arguments after `--` are extra arguments appended to
796
+ the resolved `claude`, `codex`, or `agy` executable inside the managed wrapper;
797
+ projmux still sets the context directory, tmux title, AI pane metadata, title
798
+ watcher, and split layout.
799
+
800
+ Automation callers can add `--print-pane-id` to an explicit direct
801
+ `--agent claude|codex|antigravity|shell` launch. On success, stdout contains
802
+ exactly the new `%N` pane id followed by one newline. The value comes directly
803
+ from tmux's existing
804
+ `split-window -P -F '#{pane_id}'` result. If tmux returns no valid pane id, the
805
+ command fails non-zero with tmux-specific guidance and writes no
806
+ success value. Without `--print-pane-id`, successful split invocations keep the
807
+ existing empty-stdout behavior.
808
+
809
+ `--print-pane-id` is not available for the saved default mode or for
810
+ `--agent selective|resume`, because those paths may open a picker and launch
811
+ only after a later user selection. Those combinations fail before opening a
812
+ picker or creating a pane. Arguments after `--` keep their existing argv-tail
813
+ meaning when the flag is used with a concrete AI agent.
814
+ The resume picker lists the newest deduplicated Claude, Codex, and Antigravity
815
+ resume sessions for the current project, with `[+ New Session]` pinned first.
816
+ If there are no resume sessions it goes straight to the existing selective
817
+ picker. Selecting a row directly starts `claude --resume <id>`,
818
+ `codex resume <id>`, or `agy --conversation <uuid>`.
819
+
820
+ Live Antigravity hook/session-state resume metadata remains a separate,
821
+ high-confidence lane; it is not enumerated from disk by the picker. Within the
822
+ picker's disk discovery, source order is the workspace-to-latest-UUID mapping
823
+ in `cache/last_conversations.json`, workspace-bearing summarized rows in
824
+ `cache/conversation_metadata.json`, then legacy `history.jsonl`. The two cache
825
+ sources require a normalized UUID and an exact regular
826
+ `conversations/<uuid>.db`; only DB existence and mtime are read. SQLite content,
827
+ prompt/transcript text, sidecars, symlinks, and arbitrary paths are never used.
828
+ The cache is only the history floor exposed by upstream v1.1.12, not a complete
829
+ conversation history. Cache rows have medium confidence and blank turns;
830
+ `last_conversations` uses a short UUID title, while metadata uses only a safe
831
+ summary. Legacy history is low confidence. Missing/malformed cache, stale
832
+ missing-DB mappings, workspace-less metadata, and unknown fields degrade
833
+ without failing Claude/Codex or legacy discovery.
834
+ Settings > AI Settings > Enabled agents controls Claude/Codex/Antigravity launch
835
+ visibility. Disabled agents are hidden from the selective picker and from the
836
+ default-mode picker. A saved default that later becomes disabled fails clearly
837
+ instead of falling back to another agent. Direct
838
+ `--agent claude|codex|antigravity` launches also fail when disabled, including
839
+ shortcuts that call the same command. For a deliberate one-shot direct CLI
840
+ launch, pass `--force-agent`; picker and saved default paths do not use this
841
+ override. If all AI agents are disabled, the selective picker still offers the
842
+ plain `shell` split and shows guidance to re-enable Claude/Codex/Antigravity.
843
+ For user-level skill, slash-command, editor, or launcher registrations that
844
+ call this contract, see [AI Agent Shortcuts](ai-agent-shortcuts.md).
845
+
846
+ `ingest codex-hook` is the hook-facing entrypoint for Codex hooks-engine JSON.
847
+ It reads one JSON payload from stdin and handles the default Codex hook catalog
848
+ `PreToolUse`, `PermissionRequest`, `PostToolUse`, `PreCompact`, `PostCompact`,
849
+ `SessionStart`, `UserPromptSubmit`, and `Stop` as exposed by Codex CLI 0.130.0.
850
+ It accepts the common Codex hook fields `hook_event_name`/`event_name`,
851
+ `thread_id`, `session_id`, `turn_id`, `cwd`, `transcript_path`, `model`,
852
+ `tool_name`, nested `tool.name`, `tool_input`, and `input`. `UserPromptSubmit`
853
+ marks the matched pane hook-active and moves it to thinking/busy without
854
+ pushing a queue entry. `Stop` pushes an info completion row. `PermissionRequest`
855
+ pushes a critical approval row with the tool name and concise action summary.
856
+ The other Codex events are quiet: they mark the pane hook-active and write
857
+ ingest diagnostics, but do not push notify queue entries.
858
+ For event names without a specialized notify/state handler, ingest falls back
859
+ to quiet/log-only handling. A local catalog entry with `"action": "quiet"`
860
+ therefore lets newly discovered events be installed and observed without
861
+ creating notification noise; `"notify"` and `"state"` still require a built-in
862
+ handler before they can change pane state or push queue rows.
863
+ Runtime action overrides from
864
+ `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/ai-hook-actions.json` take
865
+ precedence over catalog `action` during ingest, including known events such as
866
+ `Stop` and `PermissionRequest`. These overrides are managed by
867
+ `Settings > Notifications > Agent event behavior` and do not change the catalog
868
+ `install` field used by `projmux agent integrate codex`. A runtime `notify`
869
+ override for a known Codex event without a specialized handler, such as
870
+ `PreToolUse` or `PostToolUse`, pushes a short generic in-app row like
871
+ `PreToolUse · Bash` with agent/category metadata. Generic rows are
872
+ queue/sidebar/statusbar only and
873
+ do not dispatch OS desktop notifications, `PROJMUX_NOTIFY_HOOK`, or
874
+ `[hooks.send-noti]`.
875
+
876
+ `ingest claude-hook` is the hook-facing entrypoint for Claude Code hooks. It
877
+ reads one JSON payload from stdin and handles the default Claude Code 2.1.140
878
+ hook catalog: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`,
879
+ `PostToolBatch`, `PermissionDenied`, `Notification`, `UserPromptSubmit`,
880
+ `UserPromptExpansion`, `SessionStart`, `Stop`, `StopFailure`,
881
+ `SubagentStart`, `SubagentStop`, `PreCompact`, `PostCompact`, `SessionEnd`,
882
+ `PermissionRequest`, `Setup`, `TeammateIdle`, `TaskCreated`,
883
+ `TaskCompleted`, `Elicitation`, `ElicitationResult`, `ConfigChange`,
884
+ `InstructionsLoaded`, `WorktreeCreate`, `WorktreeRemove`, `CwdChanged`, and
885
+ `FileChanged`. It uses the same pane matching order as Codex ingest
886
+ (`$TMUX_PANE`, payload `cwd`, then cached session id), marks matched panes
887
+ hook-active, and writes metadata-bearing notify queue entries only for
888
+ reply-ready, input-ready, approval-required, error, and teammate-idle events.
889
+ `SubagentStop` and the other lifecycle/tool events are quiet: they mark the
890
+ pane hook-active and write ingest diagnostics, but do not push notify queue
891
+ entries. `UserPromptSubmit` only moves the pane to thinking/busy and does not
892
+ push a queue entry.
893
+ Unknown Claude events also fall back to quiet/log-only handling after pane
894
+ matching. Catalog `action` is honored for quiet fallback events; notify/state
895
+ actions need built-in handlers for event-specific body text and state changes.
896
+ Runtime action overrides from
897
+ `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/ai-hook-actions.json` take
898
+ precedence over catalog `action` for known Claude events too; for example a
899
+ noisy notify event can be made state-only or quiet without changing installed
900
+ Claude hook commands.
901
+
902
+ `ingest antigravity-hook` is the hook/statusline entrypoint for
903
+ Antigravity CLI `agy` payloads. Official v1.1.12 hook commands must pass their
904
+ event identity explicitly, for example
905
+ `projmux internal agent-hook ingest antigravity-hook --event Stop`; the official stdin payload
906
+ does not carry an event field. The explicit selector is authoritative, while
907
+ payload `eventName` and its legacy aliases remain fallback inputs for existing
908
+ manual wiring. `projmux agent integrate antigravity` manages exactly the named
909
+ `projmux` entry in `~/.gemini/config/hooks.json` and, separately, exactly the
910
+ `statusLine` member in `~/.gemini/antigravity-cli/settings.json`. The managed
911
+ statusline uses the official v1.1.12 `{type:"command", enabled:true,
912
+ stack_with_default:true}` shape and an absolute direct ingest command whose
913
+ stdout is empty, so the built-in statusline remains visible. It preserves every
914
+ other named entry and unknown JSON value, resolves the running projmux
915
+ executable to a stable absolute path, and supports `--dry-run` and `--remove`.
916
+ An existing unmanaged custom `statusLine` is an actionable conflict and is
917
+ never chained, wrapped, or rewritten. An existing
918
+ unmanaged `projmux` entry, another Antigravity projmux ingest command, malformed
919
+ JSON, symlinks, and read/write permission failures are reported without
920
+ rewriting the file. Doctor and Settings also report a managed entry as `stale`
921
+ when its absolute executable, event/schema wiring, or stdout fallback differs
922
+ from the current plan; the displayed install command refreshes it.
923
+
924
+ The embedded v1.1.12 catalog contains the five official events `PreToolUse`,
925
+ `PostToolUse`, `PreInvocation`, `PostInvocation`, and `Stop`. The managed entry
926
+ installs `PreInvocation`, `PostInvocation`, `PostToolUse`, and `Stop`, each with
927
+ an explicit `--event`; `PreToolUse` remains disabled because its response can
928
+ change permission policy. `Statusline` remains an explicit statusline selector
929
+ outside that official hook catalog.
930
+
931
+ `PreInvocation` moves the matched pane to thinking/busy without notifying.
932
+ `PostInvocation` and `PostToolUse` remain quiet bookkeeping paths, with tool
933
+ errors retained in ingest diagnostics. `Stop` keeps the completion/error notify
934
+ classification. Hook stdout is `{}` for the three non-Stop managed events and
935
+ `{"decision":"stop"}` for Stop, including a shell fallback if ingest fails, so
936
+ the hook cannot force continuation or synthesize a permission decision.
937
+ Official statusline `agent_state` values `thinking`, `working`, and `tool_use`
938
+ map to thinking/busy unless the pane already holds a terminal completion or
939
+ approval state; this prevents a late statusline refresh from regressing `Stop`.
940
+ A new `PreInvocation` resets the pane to thinking for the next generation.
941
+ `idle` is quiet and does not clear an existing completion or approval state.
942
+ `tool_confirmation_pending=true` produces a stable-ID,
943
+ deduped approval-required row; false never produces a notification.
944
+
945
+ The managed JSON is the install source of truth. The command
946
+ `agy -p '/hooks' --output-format json` is a read-only runtime diagnostic for
947
+ confirming loaded event names and sources; projmux never uses `/hooks` output
948
+ to rewrite `hooks.json`.
949
+
950
+ `workspacePaths` uses the first non-empty path as a cwd matching candidate;
951
+ an absent or empty array does not invent a cwd. Inherited `$TMUX_PANE` remains
952
+ the first pane attribution source. A Stop with non-empty `error`, explicit
953
+ `ERROR`, or a `MAX_STEPS_EXCEEDED` family reason pushes a critical error row.
954
+ `NO_TOOL_CALL`, `MODEL_STOP`, and known normal reasons push an info completion;
955
+ unknown reasons remain info completions with diagnostic metadata rather than
956
+ being promoted to critical. Official camelCase fields retained by the parser
957
+ also include `artifactDirectoryPath`, `modelName`, `invocationNum`,
958
+ `initialNumSteps`, `toolCall`, `stepIdx`, `executionNum`, and `fullyIdle`.
959
+ Antigravity notify metadata uses `agent=antigravity`. Phase 3 session-state
960
+ restore is included: Antigravity ingest stores `conversationId` as pane thread
961
+ metadata for matching and as session-state resume metadata. Restore uses
962
+ `agy --conversation <uuid>` when that id is present and UUID-shaped; otherwise
963
+ session-state preview/doctor render `resume unavailable`. Structured statusline
964
+ `context_window.used_percentage` is persisted with its conversation id as
965
+ private hook/notify diagnostic metadata and is not surfaced as account usage.
966
+ The official `quota` map is persisted independently and surfaces each valid entry as
967
+ `quota/<exact bucket ID>` with independently retained absolute and relative
968
+ reset values. Bucket IDs are never mapped to `5h`/`weekly`, and account quota
969
+ is never inferred from the conversation-local gauge.
970
+ The earlier string percentage form remains a compatibility fallback.
971
+ Transcript contents are not read.
972
+
973
+ The canonical `internal agent-hook ingest bell --pane <pane_id>` route is the
974
+ narrow tmux-bell fallback ingest path.
975
+ It does not require the pane to be AI-managed. Projmux resolves session,
976
+ window, pane, title, command, and socket metadata from tmux, pushes an info
977
+ queue row such as `bell · <pane title>`, and suppresses repeat bell rows from
978
+ the same pane for 5 seconds.
979
+
980
+ `diagnostics agent-hook` prints recent ingest diagnostics from
981
+ `$XDG_STATE_HOME/projmux/ai-ingest.log`, or `~/.local/state/projmux/ai-ingest.log`
982
+ when `XDG_STATE_HOME` is unset. Ingest paths append compact JSONL records for
983
+ parse errors, unsupported events, missing pane matches, deduped bells,
984
+ state-only transitions, quiet high-volume events, and notify pushes. Raw hook
985
+ payloads are not stored. The log is capped at 1 MiB and trimmed to the most
986
+ recent roughly 512 KiB when it grows past the cap. Use `--json` for raw JSONL
987
+ and `--path` to print the resolved file path.
988
+
989
+ The bounded log and its 1 MiB/roughly 512 KiB retention remain available to the
990
+ canonical diagnostics consumer and support-report count summary. The common
991
+ operations journal carries only the safe anomalous classification and watcher
992
+ lifecycle described above; both diagnostic stores continue to run in parallel.
993
+ See [legacy-diagnostics-inventory.md](legacy-diagnostics-inventory.md).
994
+
995
+ For `Stop`, projmux reads `transcript_path` when present and extracts the last
996
+ assistant text from the transcript tail; if that is unavailable, it falls back
997
+ to a generic Claude completion row. `PermissionRequest` rows expose the tool
998
+ name plus a concise input summary, preferring Bash commands, file paths, and
999
+ URLs when those fields exist.
1000
+
1001
+ Hook row text is intentionally compact: agent label, event category, then the
1002
+ best available summary. Structured details remain in queue metadata and are
1003
+ available from `get notifications --json`; the sidebar does not add a separate
1004
+ metadata detail view.
1005
+
1006
+ The extra Claude events accept conservative field aliases while Claude's event
1007
+ schemas settle. `StopFailure` reads `error_type`/`errorType`/`failure_type` and
1008
+ `error_message`/`errorMessage`/`message`/`reason`, plus nested
1009
+ `error.type|name|code` and `error.message|text|reason`. `SubagentStop` reads
1010
+ `subagent_type`/`subagentType`/`agent_type`, `subagent_id`/`subagentId`, and
1011
+ nested `subagent.type|name|kind|id`. `TeammateIdle` reads
1012
+ `teammate_name`/`teammateName`, `teammate_id`/`teammateId`,
1013
+ `teammate_context`/`teammateContext`/`context`/`reason`/`message`, and nested
1014
+ `teammate.name|id|context|status|reason|message`.
1015
+
1016
+ Claude Code hook ingest is available through `ingest claude-hook`, but
1017
+ `integrate claude` is the opt-in user-level wiring command for
1018
+ `~/.claude/settings.json`. It installs command hooks for every event whose
1019
+ effective Claude hook catalog entry has `"install": true`. The embedded default
1020
+ catalog is based on Claude Code 2.1.140 and lives at
1021
+ `internal/app/ai_hook_catalogs/claude.json`; a local override may be placed at
1022
+ `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/ai-hooks.d/claude.json` to disable
1023
+ or add events before projmux itself is released:
1024
+
1025
+ ```json
1026
+ {
1027
+ "provider": "claude",
1028
+ "events": [
1029
+ { "name": "Stop", "install": false, "action": "notify" },
1030
+ { "name": "FutureEvent", "install": true, "action": "quiet" }
1031
+ ]
1032
+ }
1033
+ ```
1034
+
1035
+ The embedded Claude catalog contains all 29 Claude Code 2.1.140 events:
1036
+ `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PostToolBatch`,
1037
+ `PermissionDenied`, `Notification`, `UserPromptSubmit`,
1038
+ `UserPromptExpansion`, `SessionStart`, `Stop`, `StopFailure`,
1039
+ `SubagentStart`, `SubagentStop`, `PreCompact`, `PostCompact`, `SessionEnd`,
1040
+ `PermissionRequest`, `Setup`, `TeammateIdle`, `TaskCreated`,
1041
+ `TaskCompleted`, `Elicitation`, `ElicitationResult`, `ConfigChange`,
1042
+ `InstructionsLoaded`, `WorktreeCreate`, `WorktreeRemove`, `CwdChanged`, and
1043
+ `FileChanged`. `SubagentStop` remains quiet/log-only.
1044
+
1045
+ The managed command receives Claude's hook JSON on stdin, keeps stdout/stderr
1046
+ quiet, and exits successfully even if ingest fails so it does not block Claude
1047
+ Code behavior. `--dry-run` previews the JSON update, and `--remove` deletes
1048
+ only commands carrying the projmux marker. Removal and unmanaged conflict
1049
+ detection scan every `hooks` event in the settings file rather than trusting the
1050
+ current catalog, so stale managed events from older catalogs are still removed.
1051
+ Existing unrelated Claude settings and hooks are preserved. If any event already
1052
+ contains an unmanaged `projmux internal agent-hook ingest claude-hook` command, projmux refuses
1053
+ to install over it and leaves the settings file untouched.
1054
+
1055
+ `projmux agent integrate codex` manages a hooks-engine
1056
+ block in `~/.codex/config.toml`. It enables `[features] hooks = true`,
1057
+ merging into an existing `[features]` table when present, and installs broad
1058
+ command hooks for every event whose effective Codex hook catalog entry has
1059
+ `"install": true`. The embedded default catalog is based on Codex CLI 0.130.0
1060
+ and lives at `internal/app/ai_hook_catalogs/codex.json`; a local override may
1061
+ be placed at `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/ai-hooks.d/codex.json`:
1062
+
1063
+ ```json
1064
+ {
1065
+ "provider": "codex",
1066
+ "events": [
1067
+ { "name": "Stop", "install": false, "action": "notify" },
1068
+ { "name": "FutureEvent", "install": true, "action": "quiet" }
1069
+ ]
1070
+ }
1071
+ ```
1072
+
1073
+ The embedded Codex catalog contains the 8 Codex CLI 0.130.0 events
1074
+ `PreToolUse`, `PermissionRequest`, `PostToolUse`, `PreCompact`,
1075
+ `PostCompact`, `SessionStart`, `UserPromptSubmit`, and `Stop`.
1076
+
1077
+ ```toml
1078
+ [features]
1079
+ hooks = true
1080
+
1081
+ [[hooks.PreToolUse]]
1082
+ matcher = "*"
1083
+ [[hooks.PreToolUse.hooks]]
1084
+ type = "command"
1085
+ command = "projmux internal agent-hook ingest codex-hook >/dev/null 2>&1 || true"
1086
+
1087
+ [[hooks.PermissionRequest]]
1088
+ matcher = "*"
1089
+ [[hooks.PermissionRequest.hooks]]
1090
+ type = "command"
1091
+ command = "projmux internal agent-hook ingest codex-hook >/dev/null 2>&1 || true"
1092
+
1093
+ [[hooks.PostToolUse]]
1094
+ matcher = "*"
1095
+ [[hooks.PostToolUse.hooks]]
1096
+ type = "command"
1097
+ command = "projmux internal agent-hook ingest codex-hook >/dev/null 2>&1 || true"
1098
+
1099
+ [[hooks.PreCompact]]
1100
+ matcher = "*"
1101
+ [[hooks.PreCompact.hooks]]
1102
+ type = "command"
1103
+ command = "projmux internal agent-hook ingest codex-hook >/dev/null 2>&1 || true"
1104
+
1105
+ [[hooks.PostCompact]]
1106
+ matcher = "*"
1107
+ [[hooks.PostCompact.hooks]]
1108
+ type = "command"
1109
+ command = "projmux internal agent-hook ingest codex-hook >/dev/null 2>&1 || true"
1110
+
1111
+ [[hooks.SessionStart]]
1112
+ matcher = "*"
1113
+ [[hooks.SessionStart.hooks]]
1114
+ type = "command"
1115
+ command = "projmux internal agent-hook ingest codex-hook >/dev/null 2>&1 || true"
1116
+
1117
+ [[hooks.UserPromptSubmit]]
1118
+ matcher = "*"
1119
+ [[hooks.UserPromptSubmit.hooks]]
1120
+ type = "command"
1121
+ command = "projmux internal agent-hook ingest codex-hook >/dev/null 2>&1 || true"
1122
+
1123
+ [[hooks.Stop]]
1124
+ matcher = "*"
1125
+ [[hooks.Stop.hooks]]
1126
+ type = "command"
1127
+ command = "projmux internal agent-hook ingest codex-hook >/dev/null 2>&1 || true"
1128
+ ```
1129
+
1130
+ Codex hooks are the default integration mode. The hooks install is idempotent,
1131
+ preserves unrelated Codex config and unmanaged hook entries, and refuses to
1132
+ install over unmanaged projmux Codex hook commands it cannot safely own.
1133
+ `--remove` removes projmux-managed Codex hooks wiring. `--dry-run` prints the
1134
+ planned change without writing. Codex may still require reviewing or trusting
1135
+ the hook through its `/hooks` flow before commands run.
1136
+
1137
+ `integrate tmux-bell` is opt-in server-level tmux wiring for arbitrary tools
1138
+ that emit BEL or OSC 9. It applies `allow-passthrough on`, `monitor-bell on`,
1139
+ `bell-action other`, and appends a marked `alert-bell` hook that invokes
1140
+ `projmux internal agent-hook ingest bell --pane "#{pane_id}"`. `--dry-run` prints the tmux
1141
+ commands. tmux `alert-bell` is a window alert and does not expose
1142
+ `#{hook_pane}` on tmux 3.4, so `#{pane_id}` is the best available pane context.
1143
+ `--remove` unsets only hook entries carrying the projmux marker and leaves
1144
+ user-owned `alert-bell` hooks alone.
1145
+
1146
+ ## internal tmux
1147
+
1148
+ ```
1149
+ projmux internal tmux popup-toggle [--client <key>] <mode>
1150
+ projmux internal tmux popup-switch
1151
+ projmux internal tmux popup-sessions
1152
+ projmux internal tmux popup-preview <session>
1153
+ projmux internal tmux rebalance-panes
1154
+ projmux internal tmux rename-pane <pane> <label>
1155
+ projmux internal tmux print-config [--bin <path>]
1156
+ projmux internal tmux print-app-config [--bin <path>]
1157
+ projmux internal tmux install [--bin <path>] [--config <path>] [--include <path>]
1158
+ projmux internal tmux install-app [--bin <path>] [--config <path>]
1159
+ projmux internal tmux apply
1160
+ ```
1161
+
1162
+ Helpers tmux's keybindings and the install pipeline call into. Modes
1163
+ accepted by `popup-toggle` mirror the historical sessionizer surface:
1164
+ `session-popup`, `sessionizer`, `sessionizer-sidebar`,
1165
+ `notify-sidebar`, `recent-windows`, `resource-inspector`, `ai-split-picker-right`,
1166
+ `ai-split-picker-down`, `ai-split-resume-right`, `ai-split-resume-down`,
1167
+ `ai-split-settings`.
1168
+ `rename-pane` sets only the pane-scoped user label
1169
+ `@projmux_pane_label`; an empty label clears the option. It does not change the
1170
+ raw tmux pane title, AI topic, or AI topic manual-ownership flag. The canonical
1171
+ keybinding action id is `rename-pane-label`. The retired `rename-pane-topic`
1172
+ keymap action is no longer accepted: replace a stale
1173
+ `[bindings.rename-pane-topic]` table with `[bindings.rename-pane-label]`.
1174
+ The `projmux agent topic set/clear` commands keep
1175
+ AI topic ownership separate from the user pane label and raw pane title.
1176
+ `apply` regenerates the app tmux config and reloads the live `-L projmux`
1177
+ server without restarting it. `make install` and `projmux update apply` invoke it
1178
+ after replacing the binary. Settings > Keybindings normally runs the same
1179
+ save/config/reload flow automatically; use `projmux config apply` (or its
1180
+ hidden equivalent `projmux internal tmux apply`) as the CLI recovery or sync path after
1181
+ hand-editing `keymap.toml`, after saving Settings outside tmux, or after
1182
+ resolving a reported config-generation or live-reload failure. Reload also
1183
+ removes the known retired no-prefix `C-t` pane-label binding from older live
1184
+ servers before installing current bindings. If the current keymap assigns `C-t`
1185
+ to another action, that current action is bound after cleanup and remains the
1186
+ owner.
1187
+
1188
+ ## config
1189
+
1190
+ ```
1191
+ projmux config edit [--get|--set <mode>]
1192
+ projmux config render standalone [--bin <path>]
1193
+ projmux config render app [--bin <path>]
1194
+ projmux config apply [--bin <path>] [--config <path>] [--socket <name>]
1195
+ ```
1196
+
1197
+ The public configuration domain. Every route here is a parity alias over the
1198
+ handler that already owned the behavior — identical stdout, stderr, exit code,
1199
+ and side effects. `edit` forwards to the existing AI split-mode settings
1200
+ picker and preserves its `--get`/`--set` forms; `render` and `apply` forward to
1201
+ the existing `tmux` handler. The general `settings` UI remains a separate
1202
+ shortcut and is not renamed by this route.
1203
+
1204
+ `render` takes the artifact as a positional token because projmux generates two
1205
+ different tmux configs, not two views of one:
1206
+
1207
+ - `standalone` is the snippet you source from your own `~/.tmux.conf`. It prints
1208
+ to stdout and writes nothing. Equivalent to `projmux internal tmux print-config`.
1209
+ - `app` is the config the app-owned `-L projmux` server runs from. It carries the
1210
+ default shell and the app-only bindings on top of the standalone content, and
1211
+ it also only prints. Equivalent to `projmux internal tmux print-app-config`.
1212
+
1213
+ A bare `projmux config render` is a usage error (exit 2) listing the two
1214
+ artifacts. There is deliberately no default: silently choosing one would leave
1215
+ the other with no obvious public spelling, which is the gap this route exists to
1216
+ close.
1217
+
1218
+ `apply` takes no artifact. It writes the app tmux config and reloads the live
1219
+ `-L projmux` server, which is one operation over one file. Equivalent to
1220
+ `projmux internal tmux apply`.
1221
+
1222
+ Writing the standalone snippet and wiring the `source-file` line into your
1223
+ `~/.tmux.conf` is `projmux internal tmux install`, and writing the app config without
1224
+ reloading is `projmux internal tmux install-app`. Both are install-pipeline plumbing and
1225
+ have no public spelling; see
1226
+ [Internal plumbing](#internal-plumbing-projmux-internal-).
1227
+
1228
+ ## update
1229
+
1230
+ ```
1231
+ projmux update status [--json]
1232
+ projmux update check [--json]
1233
+ projmux update apply [--dry-run] [--no-apply]
1234
+ ```
1235
+
1236
+ Installer-aware update status foundation. `status` is read-only: it
1237
+ prints the current version, cached latest GitHub Release tag when present,
1238
+ cache freshness (`fresh`, `stale`, or `unknown`), update state, detected
1239
+ installer source, and cache path. It never reaches the network, so it is
1240
+ safe for interactive use and shell startup paths.
1241
+
1242
+ `check` fetches the latest GitHub Release metadata for
1243
+ `crevissepartners/projmux`, atomically writes
1244
+ `${XDG_CACHE_HOME:-~/.cache}/projmux/update.json`, then prints the concise
1245
+ latest/update/cache result. `--json` emits the same machine-readable
1246
+ status shape for both subcommands.
1247
+
1248
+ `projmux shell` reads the same cache before opening the isolated tmux app.
1249
+ When the cache is missing or stale, shell startup attempts a bounded
1250
+ best-effort refresh, then continues even if the network check fails. When a
1251
+ fresh cached update is available, the shell welcome uses Enter=Continue,
1252
+ `u`=Upgrade, and `s`=Skip until next. Upgrade invokes only
1253
+ `projmux update apply`; Skip until next writes the latest release tag to
1254
+ `${XDG_CACHE_HOME:-~/.cache}/projmux/update-skip.json` and suppresses that tag
1255
+ until a newer latest release appears.
1256
+
1257
+ Installer detection honors an explicit
1258
+ `PROJMUX_INSTALLER=npm|go|github-release|source` first. When it is unset,
1259
+ detection falls back to inspecting the running binary: an npm install tree
1260
+ (`node_modules/@projmux/...`) is reported as `npm`, a `go install …@latest`
1261
+ binary in `$GOBIN`/`$GOPATH/bin`/`~/go/bin` as `go`, and a local `go build`
1262
+ (`make install`) — which reports a `(devel)` main-module version — as `source`.
1263
+ `github-release` installs are indistinguishable from a hand-placed binary and
1264
+ still require an explicit `PROJMUX_INSTALLER=github-release`. Anything else is
1265
+ reported as `unknown` with guidance.
1266
+ `apply` is installer-aware and only runs after explicit user selection.
1267
+ For npm installs, it runs `npm install -g projmux@latest` (which reliably
1268
+ crosses minor/major versions where `npm update -g` does not, and re-resolves
1269
+ the per-platform optional dependency) and then runs the new binary's
1270
+ `projmux config apply`. With `--no-apply`, that convergence step uses
1271
+ `--no-reload`: it still migrates marker-owned files and writes generated
1272
+ configuration without accessing live tmux. For Go installs, it uses the existing atomic
1273
+ replacement implementation. For `github-release` installs, it downloads the latest
1274
+ matching `projmux_<version>_<goos>_<goarch>.tar.gz` release asset, extracts the
1275
+ binary, atomically replaces the current executable, then performs the same
1276
+ apply/`--no-reload` convergence. `source` installs report an
1277
+ actionable error to update the checkout with `git pull --ff-only && make install`.
1278
+
1279
+ ## welcome
1280
+
1281
+ ```
1282
+ projmux welcome [--popup [--force]]
1283
+ ```
1284
+
1285
+ Prints the shell-entry release prompt and bootstrap reminder without starting
1286
+ tmux. This is useful when you want to revisit the welcome view at any time.
1287
+
1288
+ `--popup` is the tmux attach-helper form. It shows the popup only when a
1289
+ pending attach welcome marker exists. `--popup --force` opens the popup without
1290
+ consulting pending or skip state. Passing positional arguments prints usage and
1291
+ returns a usage error.
1292
+
1293
+ ## runtime / pin / prune / internal helpers
1294
+
1295
+ The live tmux inventory is under `runtime`: `runtime sessions`, `runtime
1296
+ attach`, `runtime stop`, `runtime tag`, and `runtime prune`. Project pins use
1297
+ `pin project list|add|remove|toggle|clear`. Resource retention uses `prune
1298
+ project|snapshot`, while explicit snapshot deletion uses `delete snapshot`.
1299
+
1300
+ Popup-marker, preview, status, and tmux configuration plumbing is hidden under
1301
+ `internal session-popup`, `internal preview`, `internal status`, `internal
1302
+ statusbar`, and `internal tmux`. Generated configuration is their producer;
1303
+ human configuration work should prefer `config render` and `config apply`.
1304
+
1305
+ ## shell / attach / settings / quit
1306
+
1307
+ - `shell` — boot the isolated `-L projmux` tmux server with the
1308
+ generated config. The generated app config uses absolute `$SHELL` as the
1309
+ tmux default shell when set, otherwise `/bin/sh`. `shell` starts or attaches
1310
+ the app session directly after resolving the target app session name and
1311
+ startup directory. Alt-1 sidebar project open defaults to `Empty session`; the
1312
+ Session State `Sidebar startup picker` opt-in shows `Latest snapshot`, `Named
1313
+ snapshot`, and `Empty session` before creating a closed project session.
1314
+ `Latest snapshot` is auto-saved; named snapshots are fixed until the user
1315
+ saves or replaces them.
1316
+ - `quit` — open an action picker with `Quit projmux` and `Cancel`. Selecting
1317
+ `Quit projmux` terminates only a `tmux -L projmux` runtime whose global
1318
+ `@projmux_app` option is set by the generated app config. Missing servers,
1319
+ default tmux servers, embedded tmux servers, and other tmux runtimes without
1320
+ that marker are no-ops. Non-interactive callers must pass `--yes` or
1321
+ `--force`; the default command always goes through the action picker.
1322
+ - `attach project <ref>` — enter a Project runtime from outside tmux.
1323
+ Automatic live-runtime attachment is `runtime attach`.
1324
+ - `settings` — interactive configuration UI for the project picker, AI
1325
+ splits, Notifications, Appearance mode, Project Root management, the
1326
+ switcher's saved workdirs list, Labs (experimental), Settings > Keybindings,
1327
+ and About/Update status. The keybinding flow is a single
1328
+ `Settings > Keybindings` action list with simplified action details for
1329
+ aliases and reset. Key save/reset automatically writes the key list,
1330
+ regenerates the app config, and reloads the running tmux session when
1331
+ possible; skipped or failed stages show `projmux config apply` as the recovery
1332
+ or sync command. Terminal diagnostics and terminal mapping application stay
1333
+ in the `projmux shell` -> `projmux setup` -> `projmux setup terminal`
1334
+ remediation path.
1335
+ The compact About section contains Version, Source, real update
1336
+ status/actions, Welcome, and Quit. It does not duplicate static setup or
1337
+ diagnostics guides: use `projmux setup` for key-delivery diagnosis,
1338
+ `projmux setup terminal` for supported terminal remediation, and the
1339
+ read-only `projmux doctor` report for dependency/runtime diagnostics. In Project
1340
+ Picker, `Project Root` manages the saved
1341
+ primary root (`~/.config/projmux/projdir`) and displays whether the effective
1342
+ value comes from `PROJMUX_PROJDIR`, tmux `@projmux_projdir`, saved config, or
1343
+ no configured source. When no source is configured, the direct-set prompt
1344
+ starts with `$HOME` as an editable fallback, but `$HOME` is not used as the
1345
+ effective root unless saved. `Workdirs` remains separate: those entries are
1346
+ additional search roots, not the primary root. Appearance stores per-surface
1347
+ path/git/notify icon decoration as `off` (default), `symbol`, or `emoji` and
1348
+ updates the matching live tmux option when available. Labs remains
1349
+ available for experimental settings. The About section reads the cached
1350
+ update status without network access;
1351
+ selecting Check Updates runs `projmux update check`, Update Now runs
1352
+ `projmux update apply`, and Welcome opens a Settings-native viewer
1353
+ independent of shell skip state. `Settings > About > Quit projmux` routes
1354
+ through the same `projmux quit` action picker. Settings mutations surface
1355
+ their handled success/failure as a transient passive row inside the native
1356
+ popup; selecting the next action clears or replaces that row.
1357
+
1358
+ ## See also
1359
+
1360
+ - [cli.md](cli.md) — the generated CLI reference: the authoritative route,
1361
+ sub-route, output-mode, and field-projection inventory for this build.
1362
+ - [npm-distribution.md](npm-distribution.md) — npm binary package layout.
1363
+ - [statusbar.md](statusbar.md) — two-line layout and click range catalogue.
1364
+ - [notify-queue.md](notify-queue.md) — queue file format and lifecycle.
1365
+ - [usage-tracking.md](usage-tracking.md) — adapter HTTP/file behaviour.
1366
+ - [keybindings.md](keybindings.md) — terminal key delivery and aliases.
1367
+ - [hooks.md](hooks.md) — lifecycle hooks, startup commands, and `send-noti` payload contract.