projmux 0.5.2 → 0.6.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.
- package/docs/agent-workflow.md +17 -5
- package/docs/cli.md +281 -17
- package/docs/configuration.md +168 -25
- package/docs/hooks.md +476 -64
- package/docs/install.md +6 -0
- package/docs/keybindings.md +22 -39
- package/docs/notify-os-focus-poc.md +84 -6
- package/docs/notify-queue.md +37 -5
- package/docs/session-restore.md +78 -0
- package/docs/settings-ia.md +54 -0
- package/docs/statusbar.md +13 -4
- package/package.json +5 -5
- package/docs/roadmap.md +0 -109
package/docs/hooks.md
CHANGED
|
@@ -15,8 +15,8 @@ projmux-owned internal tmux hooks such as `pane-focus-in`, `pane-focus-out`,
|
|
|
15
15
|
| --- | --- | --- | --- |
|
|
16
16
|
| `pre-create` | Before projmux creates a missing persistent or ephemeral session | Non-zero exit, exec error, or timeout aborts creation | Logged with `[pre-create] ` |
|
|
17
17
|
| `post-create` | After projmux creates a brand-new persistent or ephemeral session | Logged and ignored; creation continues | Logged with `[post-create] ` |
|
|
18
|
-
| `pane-startup` | After `post-create`, once the initial pane of a brand-new session reaches a shell prompt | Logged and ignored; empty output is no-op | Captured as the command to send into the pane |
|
|
19
18
|
| `post-attach` | After projmux switches the current tmux client to an existing session/target from inside tmux | Logged and ignored | Logged with `[post-attach] ` |
|
|
19
|
+
| `send-noti` | After `projmux notify push` (or the in-process AI notify producer) successfully writes a queue entry | Fired asynchronously and best-effort; queue write and desktop notifications continue even if the hook fails or times out | Receives JSON on stdin; stdout/stderr are logged with `[send-noti] ` |
|
|
20
20
|
|
|
21
21
|
Deferred Phase A candidates remain future work until their behavior can be
|
|
22
22
|
specified without exposing projmux's internal tmux hook machinery: pane exit,
|
|
@@ -24,47 +24,24 @@ window create/rename, and focus-change hook events.
|
|
|
24
24
|
|
|
25
25
|
## Where Hooks Live
|
|
26
26
|
|
|
27
|
-
Global hooks live
|
|
27
|
+
Global hooks live in the XDG config file:
|
|
28
28
|
|
|
29
29
|
```text
|
|
30
|
-
${XDG_CONFIG_HOME:-$HOME/.config}/projmux/
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
For example:
|
|
34
|
-
|
|
35
|
-
```text
|
|
36
|
-
${XDG_CONFIG_HOME:-$HOME/.config}/projmux/hooks/post-create
|
|
37
|
-
${XDG_CONFIG_HOME:-$HOME/.config}/projmux/hooks/pane-startup
|
|
30
|
+
${XDG_CONFIG_HOME:-$HOME/.config}/projmux/config.toml
|
|
38
31
|
```
|
|
39
32
|
|
|
40
33
|
Project-local hooks are discovered from the lifecycle context's `PROJMUX_CWD`:
|
|
41
34
|
|
|
42
35
|
```text
|
|
43
|
-
<repo>/.projmux/<event>
|
|
44
|
-
<repo>/.projmux/hooks/<event>
|
|
45
36
|
<repo>/.projmux/config.toml
|
|
46
37
|
```
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<repo>/.projmux/pane-startup
|
|
52
|
-
<repo>/.projmux/hooks/pane-startup
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Each hook file must exist, be a regular file or symlink (not a directory), and
|
|
56
|
-
have the owner-execute bit set. Anything else is silently skipped.
|
|
57
|
-
|
|
58
|
-
For each event, projmux runs at most one project-local file: first
|
|
59
|
-
`.projmux/<event>` if executable, otherwise `.projmux/hooks/<event>` if
|
|
60
|
-
executable. Discovery does not walk parent directories and does not run hooks
|
|
61
|
-
from status, preview, or picker hot paths.
|
|
39
|
+
File-form hooks from the historical `.projmux/<event>` and
|
|
40
|
+
`.projmux/hooks/<event>` layouts are no longer executed. Use declarative
|
|
41
|
+
`[hooks.<event>] run` entries instead.
|
|
62
42
|
|
|
63
43
|
If both a global hook and a project-local hook exist for an event, projmux runs
|
|
64
|
-
the global hook first, then the project-local hook
|
|
65
|
-
declarative config command from `.projmux/config.toml`. For `pane-startup`, the
|
|
66
|
-
last non-empty trimmed stdout wins. Config `pane-startup` commands therefore
|
|
67
|
-
override file hooks deterministically.
|
|
44
|
+
the global hook first, then the project-local hook.
|
|
68
45
|
|
|
69
46
|
## Project Config
|
|
70
47
|
|
|
@@ -81,12 +58,12 @@ run = "echo checking"
|
|
|
81
58
|
[hooks.post-create]
|
|
82
59
|
run = "echo created $PROJMUX_SESSION"
|
|
83
60
|
|
|
84
|
-
[hooks.pane-startup]
|
|
85
|
-
run = "echo make test"
|
|
86
|
-
|
|
87
61
|
[hooks.post-attach]
|
|
88
62
|
run = "echo attached"
|
|
89
63
|
|
|
64
|
+
[hooks.send-noti]
|
|
65
|
+
run = "jq -r '.message' | xargs -I{} send-slack \"{}\""
|
|
66
|
+
|
|
90
67
|
[env]
|
|
91
68
|
FOO = "bar"
|
|
92
69
|
|
|
@@ -97,18 +74,22 @@ namespace = "tools"
|
|
|
97
74
|
|
|
98
75
|
Only quoted string values are supported. Unknown sections or keys make the
|
|
99
76
|
config file invalid for this run. Supported hook events are the public lifecycle
|
|
100
|
-
events listed above: `pre-create`, `post-create`, `
|
|
101
|
-
`
|
|
102
|
-
Phase C secret interpolation is not
|
|
77
|
+
events listed above: `pre-create`, `post-create`, `post-attach`, and
|
|
78
|
+
`send-noti`. Internal tmux hook names such as
|
|
79
|
+
`after-select-pane` are rejected. Phase C secret interpolation is not
|
|
80
|
+
implemented; values are used literally.
|
|
103
81
|
|
|
104
82
|
`[hooks.<event>] run` executes through `sh -c` with the same timeout, logging,
|
|
105
|
-
environment, and failure model
|
|
106
|
-
|
|
83
|
+
environment, and failure model for all hook events.
|
|
84
|
+
|
|
85
|
+
`[hooks.send-noti] run` is the declarative forward path for queue-backed
|
|
86
|
+
notifications. It does not replace desktop notifications or the in-app queue;
|
|
87
|
+
it runs in parallel after the queue write succeeds so users can fan out to
|
|
88
|
+
Slack, webhooks, or custom scripts without changing the built-in notification
|
|
89
|
+
path.
|
|
107
90
|
|
|
108
91
|
`[startup] run` is a direct shorthand for a startup pane command. It is not
|
|
109
92
|
executed as shell by the hook runner; the string itself is sent to the new pane.
|
|
110
|
-
When both `[hooks.pane-startup] run` and `[startup] run` are present,
|
|
111
|
-
`[startup] run` wins because it is applied last.
|
|
112
93
|
|
|
113
94
|
`[env]` values are added to hook process environments and to newly-created tmux
|
|
114
95
|
session environments. For new sessions, projmux passes them to
|
|
@@ -128,17 +109,17 @@ values.
|
|
|
128
109
|
|
|
129
110
|
Global hooks under `$XDG_CONFIG_HOME` are prompt-free.
|
|
130
111
|
|
|
131
|
-
Project-local
|
|
132
|
-
|
|
133
|
-
|
|
112
|
+
Project-local `.projmux/config.toml` files are gated by trust-on-first-use
|
|
113
|
+
before projmux runs hooks or applies startup/session environment settings.
|
|
114
|
+
Approving "always"
|
|
134
115
|
records the file content hash in:
|
|
135
116
|
|
|
136
117
|
```text
|
|
137
118
|
${XDG_STATE_HOME:-$HOME/.local/state}/projmux/trusted-projects.json
|
|
138
119
|
```
|
|
139
120
|
|
|
140
|
-
The trust key is the absolute repository path and
|
|
141
|
-
|
|
121
|
+
The trust key is the absolute repository path and the config path is stored
|
|
122
|
+
relative to that repository. Each project entry has a
|
|
142
123
|
`trusted_at` timestamp and a `files` map of relative paths to SHA-256 hashes.
|
|
143
124
|
When file content changes, projmux asks again and shows the old and new SHA-256
|
|
144
125
|
hashes. In non-interactive contexts such as tmux run-shell or CI, untrusted or
|
|
@@ -148,27 +129,462 @@ Set `PROJMUX_PROJECT_HOOKS=off` to disable project-local hook discovery
|
|
|
148
129
|
entirely. Project-local hooks can also be disabled from `projmux settings`
|
|
149
130
|
under Labs. The global hook still runs either way.
|
|
150
131
|
|
|
151
|
-
##
|
|
132
|
+
## Startup Commands
|
|
152
133
|
|
|
153
|
-
`
|
|
134
|
+
`[startup] run` applies only to the initial pane created with a new
|
|
154
135
|
projmux-managed session. It runs after `post-create` so `post-create` can seed
|
|
155
136
|
session-level tmux environment before the startup command is sent. It does not
|
|
156
137
|
run when projmux attaches to an existing session or target.
|
|
157
138
|
|
|
158
|
-
Before
|
|
159
|
-
new pane and waits until it reports a shell command. This avoids fixed
|
|
160
|
-
the primary readiness mechanism.
|
|
139
|
+
Before sending the startup command, projmux polls tmux `pane_current_command`
|
|
140
|
+
for the new pane and waits until it reports a shell command. This avoids fixed
|
|
141
|
+
sleeps as the primary readiness mechanism.
|
|
161
142
|
|
|
162
|
-
The
|
|
143
|
+
The configured string is sent into the new pane:
|
|
163
144
|
|
|
164
145
|
```text
|
|
165
146
|
tmux send-keys -t <pane-id> <command> Enter
|
|
166
147
|
```
|
|
167
148
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
149
|
+
An empty `[startup] run` is a no-op.
|
|
150
|
+
|
|
151
|
+
## Send Noti
|
|
152
|
+
|
|
153
|
+
`send-noti` fires only after the notify queue write succeeds. The queue entry is
|
|
154
|
+
already durable before the hook starts, so a failing or slow hook cannot drop
|
|
155
|
+
the notification or block the normal desktop notification flow.
|
|
156
|
+
|
|
157
|
+
The hook runs asynchronously with the same default timeout (`5s`) as other
|
|
158
|
+
hooks. projmux does not wait for completion before returning from
|
|
159
|
+
`projmux notify push`.
|
|
160
|
+
|
|
161
|
+
stdin receives one JSON object:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"event": "send-noti",
|
|
166
|
+
"id": "ai:main:%9",
|
|
167
|
+
"type": "ai-reply-ready",
|
|
168
|
+
"agent": "claude",
|
|
169
|
+
"topic": "worker loop",
|
|
170
|
+
"pane": "%9",
|
|
171
|
+
"session": "main",
|
|
172
|
+
"message": "claude: reply ready · worker loop",
|
|
173
|
+
"metadata": {
|
|
174
|
+
"agent": "claude"
|
|
175
|
+
},
|
|
176
|
+
"created_at": "2026-05-12T02:03:04Z"
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The hook environment also includes:
|
|
181
|
+
|
|
182
|
+
| Variable | Description |
|
|
183
|
+
| --- | --- |
|
|
184
|
+
| `PROJMUX_NOTIFY_ID` | Queue entry id |
|
|
185
|
+
| `PROJMUX_NOTIFY_TYPE` | Notification kind (`ai-reply-ready`, `external`, etc.) |
|
|
186
|
+
| `PROJMUX_NOTIFY_AGENT` | AI agent label when known |
|
|
187
|
+
| `PROJMUX_NOTIFY_TOPIC` | AI topic when known |
|
|
188
|
+
| `PROJMUX_NOTIFY_PANE` | Target pane id when known |
|
|
189
|
+
| `PROJMUX_NOTIFY_SESSION` | Target tmux session |
|
|
190
|
+
| `PROJMUX_NOTIFY_MESSAGE` | Human-readable message text |
|
|
191
|
+
| `PROJMUX_NOTIFY_HOOK_DEPTH` | Recursion guard; values `>= 1` suppress nested `send-noti` dispatch |
|
|
192
|
+
|
|
193
|
+
If a `send-noti` hook itself calls `projmux notify push`, projmux sees
|
|
194
|
+
`PROJMUX_NOTIFY_HOOK_DEPTH=1` in the child environment and skips another
|
|
195
|
+
`send-noti` hook fire. The queue write itself still succeeds.
|
|
196
|
+
|
|
197
|
+
`Settings > Notifications > Delivery sources` surfaces the active Codex hooks,
|
|
198
|
+
Claude, and tmux AI notify diagnostics: status, conflicts, config paths, and
|
|
199
|
+
copyable CLI install/remove/dry-run commands. It does not install or remove
|
|
200
|
+
external Codex, Claude, or tmux settings.
|
|
201
|
+
|
|
202
|
+
## Codex Hooks Engine
|
|
203
|
+
|
|
204
|
+
`projmux doctor` reports Codex hooks-engine wiring separately from legacy
|
|
205
|
+
notify, including unmanaged hooks/conflict details and the relevant
|
|
206
|
+
`projmux ai integrate codex` commands.
|
|
207
|
+
|
|
208
|
+
`projmux ai ingest codex-hook` is the conservative core ingest path for Codex
|
|
209
|
+
hooks-engine events. It reads a single JSON payload from stdin. The embedded
|
|
210
|
+
default install catalog is based on Codex CLI 0.130.0:
|
|
211
|
+
|
|
212
|
+
| Event | Behavior |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| `PreToolUse` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
215
|
+
| `UserPromptSubmit` | marks the matched pane hook-active and sets AI state to thinking/busy; no notify queue entry is pushed |
|
|
216
|
+
| `PermissionRequest` | pushes a critical approval row with the tool name and a concise tool/action summary |
|
|
217
|
+
| `PostToolUse` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
218
|
+
| `PreCompact` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
219
|
+
| `PostCompact` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
220
|
+
| `SessionStart` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
221
|
+
| `Stop` | pushes an info Codex completion row |
|
|
222
|
+
|
|
223
|
+
Codex hook payload parsing accepts the common fields
|
|
224
|
+
`hook_event_name`/`event_name`, `thread_id`, `session_id`, `turn_id`, `cwd`,
|
|
225
|
+
`transcript_path`, `model`, `tool_name`, nested `tool.name`, `tool_input`, and
|
|
226
|
+
`input`. For pane matching, Codex hook ingest uses `thread_id` when available
|
|
227
|
+
and falls back to treating `session_id` as the thread identity so existing
|
|
228
|
+
`matchAIPane` matching can reuse cached pane metadata.
|
|
229
|
+
|
|
230
|
+
`projmux ai integrate codex` manages a separate
|
|
231
|
+
`~/.codex/config.toml` marker block for the hooks engine. If a `[features]`
|
|
232
|
+
table already exists, projmux merges `hooks = true` into that table instead of
|
|
233
|
+
creating a duplicate table. Older projmux-managed `codex_hooks = true` entries
|
|
234
|
+
are migrated to `hooks = true` to avoid Codex's deprecation warning:
|
|
235
|
+
|
|
236
|
+
```toml
|
|
237
|
+
[features]
|
|
238
|
+
hooks = true
|
|
239
|
+
|
|
240
|
+
[[hooks.PreToolUse]]
|
|
241
|
+
matcher = "*"
|
|
242
|
+
[[hooks.PreToolUse.hooks]]
|
|
243
|
+
type = "command"
|
|
244
|
+
command = "projmux ai ingest codex-hook >/dev/null 2>&1 || true"
|
|
245
|
+
|
|
246
|
+
[[hooks.PermissionRequest]]
|
|
247
|
+
matcher = "*"
|
|
248
|
+
[[hooks.PermissionRequest.hooks]]
|
|
249
|
+
type = "command"
|
|
250
|
+
command = "projmux ai ingest codex-hook >/dev/null 2>&1 || true"
|
|
251
|
+
|
|
252
|
+
[[hooks.PostToolUse]]
|
|
253
|
+
matcher = "*"
|
|
254
|
+
[[hooks.PostToolUse.hooks]]
|
|
255
|
+
type = "command"
|
|
256
|
+
command = "projmux ai ingest codex-hook >/dev/null 2>&1 || true"
|
|
257
|
+
|
|
258
|
+
[[hooks.PreCompact]]
|
|
259
|
+
matcher = "*"
|
|
260
|
+
[[hooks.PreCompact.hooks]]
|
|
261
|
+
type = "command"
|
|
262
|
+
command = "projmux ai ingest codex-hook >/dev/null 2>&1 || true"
|
|
263
|
+
|
|
264
|
+
[[hooks.PostCompact]]
|
|
265
|
+
matcher = "*"
|
|
266
|
+
[[hooks.PostCompact.hooks]]
|
|
267
|
+
type = "command"
|
|
268
|
+
command = "projmux ai ingest codex-hook >/dev/null 2>&1 || true"
|
|
269
|
+
|
|
270
|
+
[[hooks.SessionStart]]
|
|
271
|
+
matcher = "*"
|
|
272
|
+
[[hooks.SessionStart.hooks]]
|
|
273
|
+
type = "command"
|
|
274
|
+
command = "projmux ai ingest codex-hook >/dev/null 2>&1 || true"
|
|
275
|
+
|
|
276
|
+
[[hooks.UserPromptSubmit]]
|
|
277
|
+
matcher = "*"
|
|
278
|
+
[[hooks.UserPromptSubmit.hooks]]
|
|
279
|
+
type = "command"
|
|
280
|
+
command = "projmux ai ingest codex-hook >/dev/null 2>&1 || true"
|
|
281
|
+
|
|
282
|
+
[[hooks.Stop]]
|
|
283
|
+
matcher = "*"
|
|
284
|
+
[[hooks.Stop.hooks]]
|
|
285
|
+
type = "command"
|
|
286
|
+
command = "projmux ai ingest codex-hook >/dev/null 2>&1 || true"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Repeated installs are idempotent and preserve unrelated Codex config, including
|
|
290
|
+
unmanaged hook entries for the same events. If projmux sees an unmanaged
|
|
291
|
+
`projmux ai ingest codex-hook` command, it refuses to install over it rather
|
|
292
|
+
than guessing ownership. `--dry-run` previews the TOML update. `--remove`
|
|
293
|
+
removes projmux-managed Codex hooks wiring.
|
|
294
|
+
|
|
295
|
+
The Codex hooks install list is catalog-driven. Projmux ships an embedded
|
|
296
|
+
default catalog at `internal/app/ai_hook_catalogs/codex.json`, and merges an
|
|
297
|
+
optional local override from:
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
${XDG_CONFIG_HOME:-$HOME/.config}/projmux/ai-hooks.d/codex.json
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Override events replace matching embedded events by name and append new names,
|
|
304
|
+
so a local file can disable a stale event with `"install": false` or add a new
|
|
305
|
+
Codex event before projmux itself is released. Supported `action` values are
|
|
306
|
+
`notify`, `state`, and `quiet`; install uses only events whose effective
|
|
307
|
+
`install` value is true. Removal remains marker-block based rather than catalog
|
|
308
|
+
based, so older projmux-managed Codex hook blocks are still removed after the
|
|
309
|
+
catalog changes.
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
{
|
|
313
|
+
"provider": "codex",
|
|
314
|
+
"events": [
|
|
315
|
+
{ "name": "Stop", "install": false, "action": "notify" },
|
|
316
|
+
{ "name": "FutureEvent", "install": true, "action": "quiet" }
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Codex ingest has built-in notify/state handlers for the known reply-ready,
|
|
322
|
+
approval, and prompt-submit events. Events without a specialized handler,
|
|
323
|
+
including override-added events, are quiet/log-only after pane matching; a
|
|
324
|
+
catalog `"action": "quiet"` makes that quiet fallback explicit. Catalog
|
|
325
|
+
`"notify"` and `"state"` entries still need a built-in handler before they can
|
|
326
|
+
push queue rows or change pane state.
|
|
327
|
+
|
|
328
|
+
Codex may require reviewing or trusting hooks through its `/hooks` flow before
|
|
329
|
+
commands run. Projmux only writes the managed config block; it does not attempt
|
|
330
|
+
to auto-trust hooks.
|
|
331
|
+
|
|
332
|
+
## Tmux Bell Fallback
|
|
333
|
+
|
|
334
|
+
`projmux doctor` reports whether the current tmux server has a
|
|
335
|
+
projmux-managed bell hook installed. The diagnostic is read-only; it only
|
|
336
|
+
inspects current `alert-bell` hooks and prints the matching integrate commands.
|
|
337
|
+
|
|
338
|
+
`projmux ai integrate tmux-bell` is the opt-in fallback for AI CLIs that do
|
|
339
|
+
not expose structured hooks but do emit BEL or OSC 9. It mutates the current
|
|
340
|
+
tmux server only; it does not edit tmux config files. Install applies these
|
|
341
|
+
server settings and appends a marked `alert-bell` hook:
|
|
342
|
+
|
|
343
|
+
```text
|
|
344
|
+
tmux set-option -g allow-passthrough on
|
|
345
|
+
tmux set-option -g monitor-bell on
|
|
346
|
+
tmux set-option -g bell-action other
|
|
347
|
+
tmux set-hook -ag alert-bell run-shell -b 'projmux ai ingest bell --pane "#{pane_id}" >/dev/null 2>&1 || true # projmux-managed:tmux-bell:v1'
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The hook calls `projmux ai ingest bell --pane <pane_id>`. Bell ingest resolves
|
|
351
|
+
the target pane through tmux and pushes an info notify queue row such as
|
|
352
|
+
`bell · Claude CLI`, with metadata including `agent=bell`, `event=bell`,
|
|
353
|
+
session/window/pane, pane title, command, and socket path when available. The
|
|
354
|
+
pane does not need `@projmux_ai_agent` or any other AI-managed option because
|
|
355
|
+
this path is for unknown tools. tmux `alert-bell` is a window alert; tmux 3.4
|
|
356
|
+
does not expose `#{hook_pane}` there, so projmux uses `#{pane_id}` as the best
|
|
357
|
+
available pane context.
|
|
358
|
+
|
|
359
|
+
Repeated bells from the same pane are suppressed for 5 seconds using a
|
|
360
|
+
pane-local tmux timestamp option. The notify row also uses a stable
|
|
361
|
+
`ai:bell:<session>:<pane>` id, so later non-suppressed bells refresh the same
|
|
362
|
+
queue entry rather than creating unbounded duplicates.
|
|
363
|
+
|
|
364
|
+
`--dry-run` prints the tmux commands without applying them. `--remove` reads
|
|
365
|
+
the current `alert-bell` hooks and unsets only entries carrying
|
|
366
|
+
`projmux-managed:tmux-bell:v1`, preserving unmanaged user hooks.
|
|
367
|
+
|
|
368
|
+
## Claude Code Hook Ingest
|
|
369
|
+
|
|
370
|
+
`projmux doctor` reports Claude Code hook wiring in `~/.claude/settings.json`,
|
|
371
|
+
including unmanaged projmux ingest command conflicts and the relevant
|
|
372
|
+
`projmux ai integrate claude` commands.
|
|
373
|
+
|
|
374
|
+
`projmux ai ingest claude-hook` is the conservative core ingest path for
|
|
375
|
+
Claude Code hooks. It reads a single JSON payload from stdin. The embedded
|
|
376
|
+
default install catalog is based on Claude Code 2.1.140 and represents the
|
|
377
|
+
29 hook events visible in that version:
|
|
378
|
+
|
|
379
|
+
| Event | Behavior |
|
|
380
|
+
| --- | --- |
|
|
381
|
+
| `PreToolUse` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
382
|
+
| `PostToolUse` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
383
|
+
| `PostToolUseFailure` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
384
|
+
| `PostToolBatch` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
385
|
+
| `PermissionDenied` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
386
|
+
| `Notification` | pushes a Claude notify row for response-ready, approval-required, or input-ready based on `notification_type` |
|
|
387
|
+
| `UserPromptSubmit` | marks the matched pane hook-active and sets AI state to thinking/busy; no notify queue entry is pushed |
|
|
388
|
+
| `UserPromptExpansion` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
389
|
+
| `SessionStart` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
390
|
+
| `Stop` | pushes a Claude completion row, using the last assistant transcript text when `transcript_path` is readable |
|
|
391
|
+
| `StopFailure` | pushes a critical Claude error row with error type/message metadata when present |
|
|
392
|
+
| `SubagentStart` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
393
|
+
| `SubagentStop` | marks the pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
394
|
+
| `PreCompact` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
395
|
+
| `PostCompact` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
396
|
+
| `SessionEnd` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
397
|
+
| `PermissionRequest` | pushes a critical approval row with the tool name and a concise tool input summary |
|
|
398
|
+
| `Setup` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
399
|
+
| `TeammateIdle` | pushes an info Claude teammate waiting row with teammate context metadata when present |
|
|
400
|
+
| `TaskCreated` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
401
|
+
| `TaskCompleted` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
402
|
+
| `Elicitation` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
403
|
+
| `ElicitationResult` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
404
|
+
| `ConfigChange` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
405
|
+
| `InstructionsLoaded` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
406
|
+
| `WorktreeCreate` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
407
|
+
| `WorktreeRemove` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
408
|
+
| `CwdChanged` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
409
|
+
| `FileChanged` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
410
|
+
|
|
411
|
+
Hook-generated queue rows use the same compact body catalog: agent label,
|
|
412
|
+
event category, then the best available summary (Codex assistant text, Claude
|
|
413
|
+
tool/action summary, transcript summary, error, or teammate labels). Structured
|
|
414
|
+
payload details remain in `metadata`, which is passed through the `send-noti`
|
|
415
|
+
JSON payload and `notify list --json`; the sidebar stays compact and does not
|
|
416
|
+
expand a separate metadata detail view. `SubagentStop` remains parsed for
|
|
417
|
+
diagnostics but is intentionally excluded from hook notifications because it can
|
|
418
|
+
fire at high volume.
|
|
419
|
+
|
|
420
|
+
Pane matching follows the shared AI ingest order: inherited `$TMUX_PANE`, then
|
|
421
|
+
payload `cwd`, then cached session id pane options. A matched pane is marked
|
|
422
|
+
with `@projmux_ai_hook_active=1`, so `projmux ai watch-title` skips the pane
|
|
423
|
+
and hook payloads become the primary signal.
|
|
424
|
+
|
|
425
|
+
The Claude hook payload is intentionally accepted directly at the ingest
|
|
426
|
+
boundary. Core identity fields accept `hook_event_name`/`event_name`,
|
|
427
|
+
`session_id`/`session-id`, `cwd`/`workspace`/`project_dir`, and nested
|
|
428
|
+
`workspace.cwd|path`. Extra-event fields are intentionally tolerant while the
|
|
429
|
+
upstream schemas settle:
|
|
430
|
+
|
|
431
|
+
| Event | Accepted fields |
|
|
432
|
+
| --- | --- |
|
|
433
|
+
| `StopFailure` | `error_type`, `errorType`, `failure_type`, `failureType`; `error_message`, `errorMessage`, `message`, `reason`; nested `error.type`, `error.name`, `error.code`, `error.message`, `error.text`, `error.reason` |
|
|
434
|
+
| `SubagentStop` | `subagent_type`, `subagentType`, `agent_type`, `agentType`; `subagent_id`, `subagentId`, `agent_id`, `agentId`; nested `subagent.type`, `subagent.name`, `subagent.kind`, `subagent.id`, `subagent.subagent_id`, `subagent.agent_id` |
|
|
435
|
+
| `TeammateIdle` | `teammate_name`, `teammateName`, `teammate`; `teammate_id`, `teammateId`; `teammate_context`, `teammateContext`, `context`, `reason`, `message`; nested `teammate.name`, `teammate.type`, `teammate.kind`, `teammate.id`, `teammate.teammate_id`, `teammate.context`, `teammate.status`, `teammate.reason`, `teammate.message` |
|
|
436
|
+
|
|
437
|
+
`projmux ai integrate claude` manages user-level Claude Code hook settings in
|
|
438
|
+
`~/.claude/settings.json`. Claude Code hooks are configured under the top-level
|
|
439
|
+
`hooks` object, with each event containing matcher entries and each matcher
|
|
440
|
+
entry containing a `hooks` array. Projmux omits `matcher`, which Claude Code
|
|
441
|
+
treats as "match all" for the event; this keeps `Notification` notification
|
|
442
|
+
types and `PermissionRequest` tool names broad while this integration remains
|
|
443
|
+
an observability hook:
|
|
444
|
+
|
|
445
|
+
```json
|
|
446
|
+
{
|
|
447
|
+
"hooks": {
|
|
448
|
+
"Notification": [
|
|
449
|
+
{
|
|
450
|
+
"hooks": [
|
|
451
|
+
{
|
|
452
|
+
"type": "command",
|
|
453
|
+
"command": "projmux ai ingest claude-hook >/dev/null 2>&1 || true # projmux-managed:claude-hook:v1"
|
|
454
|
+
}
|
|
455
|
+
]
|
|
456
|
+
}
|
|
457
|
+
],
|
|
458
|
+
"Stop": [
|
|
459
|
+
{
|
|
460
|
+
"hooks": [
|
|
461
|
+
{
|
|
462
|
+
"type": "command",
|
|
463
|
+
"command": "projmux ai ingest claude-hook >/dev/null 2>&1 || true # projmux-managed:claude-hook:v1"
|
|
464
|
+
}
|
|
465
|
+
]
|
|
466
|
+
}
|
|
467
|
+
],
|
|
468
|
+
"UserPromptSubmit": [
|
|
469
|
+
{
|
|
470
|
+
"hooks": [
|
|
471
|
+
{
|
|
472
|
+
"type": "command",
|
|
473
|
+
"command": "projmux ai ingest claude-hook >/dev/null 2>&1 || true # projmux-managed:claude-hook:v1"
|
|
474
|
+
}
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
],
|
|
478
|
+
"PermissionRequest": [
|
|
479
|
+
{
|
|
480
|
+
"hooks": [
|
|
481
|
+
{
|
|
482
|
+
"type": "command",
|
|
483
|
+
"command": "projmux ai ingest claude-hook >/dev/null 2>&1 || true # projmux-managed:claude-hook:v1"
|
|
484
|
+
}
|
|
485
|
+
]
|
|
486
|
+
}
|
|
487
|
+
],
|
|
488
|
+
"StopFailure": [
|
|
489
|
+
{
|
|
490
|
+
"hooks": [
|
|
491
|
+
{
|
|
492
|
+
"type": "command",
|
|
493
|
+
"command": "projmux ai ingest claude-hook >/dev/null 2>&1 || true # projmux-managed:claude-hook:v1"
|
|
494
|
+
}
|
|
495
|
+
]
|
|
496
|
+
}
|
|
497
|
+
],
|
|
498
|
+
"SubagentStop": [
|
|
499
|
+
{
|
|
500
|
+
"hooks": [
|
|
501
|
+
{
|
|
502
|
+
"type": "command",
|
|
503
|
+
"command": "projmux ai ingest claude-hook >/dev/null 2>&1 || true # projmux-managed:claude-hook:v1"
|
|
504
|
+
}
|
|
505
|
+
]
|
|
506
|
+
}
|
|
507
|
+
],
|
|
508
|
+
"TeammateIdle": [
|
|
509
|
+
{
|
|
510
|
+
"hooks": [
|
|
511
|
+
{
|
|
512
|
+
"type": "command",
|
|
513
|
+
"command": "projmux ai ingest claude-hook >/dev/null 2>&1 || true # projmux-managed:claude-hook:v1"
|
|
514
|
+
}
|
|
515
|
+
]
|
|
516
|
+
}
|
|
517
|
+
]
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
The marker lives inside the command string because JSON settings cannot carry
|
|
523
|
+
comments. Repeated installs are idempotent: projmux removes its marked commands
|
|
524
|
+
and re-adds the current managed command while preserving unrelated settings and
|
|
525
|
+
hooks. `--remove` deletes only marked commands, and `--dry-run` previews the
|
|
526
|
+
JSON without writing. Removal and unmanaged conflict detection scan every event
|
|
527
|
+
under the settings `hooks` object, not just the current catalog. If any event
|
|
528
|
+
already has an unmanaged command that invokes `projmux ai ingest claude-hook`,
|
|
529
|
+
projmux refuses to install over it because it cannot tell whether the command
|
|
530
|
+
is user-owned or stale projmux wiring.
|
|
531
|
+
|
|
532
|
+
The Claude hooks install list is catalog-driven. Projmux ships an embedded
|
|
533
|
+
default catalog at `internal/app/ai_hook_catalogs/claude.json`, and merges an
|
|
534
|
+
optional local override from:
|
|
535
|
+
|
|
536
|
+
```text
|
|
537
|
+
${XDG_CONFIG_HOME:-$HOME/.config}/projmux/ai-hooks.d/claude.json
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Override events replace matching embedded events by name and append new names,
|
|
541
|
+
so a local file can disable a stale event with `"install": false` or add a new
|
|
542
|
+
Claude Code event before projmux itself is released. Supported `action` values
|
|
543
|
+
are `notify`, `state`, and `quiet`; install uses only events whose effective
|
|
544
|
+
`install` value is true. Removal and unmanaged conflict detection scan every
|
|
545
|
+
event under `~/.claude/settings.json` for the projmux command marker rather
|
|
546
|
+
than trusting the current catalog, so stale managed events from older catalogs
|
|
547
|
+
are still removed safely. `SubagentStop` is wired for hook-active pane marking
|
|
548
|
+
and ingest debugging only.
|
|
549
|
+
|
|
550
|
+
```json
|
|
551
|
+
{
|
|
552
|
+
"provider": "claude",
|
|
553
|
+
"events": [
|
|
554
|
+
{ "name": "Stop", "install": false, "action": "notify" },
|
|
555
|
+
{ "name": "FutureEvent", "install": true, "action": "quiet" }
|
|
556
|
+
]
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Claude ingest has built-in notify/state handlers for the known completion,
|
|
561
|
+
notification, approval, prompt-submit, error, and teammate-idle events. Events
|
|
562
|
+
without a specialized handler, including unknown future events, are
|
|
563
|
+
quiet/log-only after pane matching. Catalog `"action": "quiet"` is used for
|
|
564
|
+
that fallback; catalog `"notify"` and `"state"` entries still need built-in
|
|
565
|
+
handler code for event-specific queue rows or state transitions.
|
|
566
|
+
|
|
567
|
+
## Ingest Debug Log
|
|
568
|
+
|
|
569
|
+
Every `projmux ai ingest ...` path appends compact JSONL diagnostics to
|
|
570
|
+
`$XDG_STATE_HOME/projmux/ai-ingest.log`, or
|
|
571
|
+
`~/.local/state/projmux/ai-ingest.log` when `XDG_STATE_HOME` is unset. The log
|
|
572
|
+
records source, event, result, pane, match identifiers, and a short reason for
|
|
573
|
+
parse errors, unsupported events, missing pane matches, deduped bells,
|
|
574
|
+
state-only transitions, quiet high-volume events, and notify pushes. Raw hook
|
|
575
|
+
payloads are not stored.
|
|
576
|
+
|
|
577
|
+
Use `projmux ai ingest log` to inspect recent entries:
|
|
578
|
+
|
|
579
|
+
```text
|
|
580
|
+
projmux ai ingest log --tail 20
|
|
581
|
+
projmux ai ingest log --json --tail 20
|
|
582
|
+
projmux ai ingest log --path
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
The file is capped at 1 MiB. When an append grows it past the cap, projmux
|
|
586
|
+
keeps the most recent roughly 512 KiB and trims from the next JSONL boundary so
|
|
587
|
+
the remaining file still starts at a whole entry.
|
|
172
588
|
|
|
173
589
|
## Pre Create Abort
|
|
174
590
|
|
|
@@ -207,15 +623,11 @@ Hooks inherit projmux's environment, plus:
|
|
|
207
623
|
echo "session=$PROJMUX_SESSION cwd=$PROJMUX_CWD kind=$PROJMUX_SESSION_KIND"
|
|
208
624
|
```
|
|
209
625
|
|
|
210
|
-
### Project
|
|
626
|
+
### Project Startup Command
|
|
211
627
|
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
#!/usr/bin/env bash
|
|
216
|
-
echo "git status --short"
|
|
217
|
-
EOF
|
|
218
|
-
chmod +x .projmux/pane-startup
|
|
628
|
+
```toml
|
|
629
|
+
[startup]
|
|
630
|
+
run = "git status --short"
|
|
219
631
|
```
|
|
220
632
|
|
|
221
633
|
### Per Session GH_TOKEN By Repo
|
|
@@ -257,4 +669,4 @@ it does not retroactively change the current shell. Open new panes via tmux
|
|
|
257
669
|
projmux logs once and moves on.
|
|
258
670
|
- **Lines appear with `[post-create] `, `[pre-create] `, or `[post-attach] `
|
|
259
671
|
prefixes.** Expected; hook stdout/stderr are multiplexed into projmux's
|
|
260
|
-
stderr stream.
|
|
672
|
+
stderr stream.
|
package/docs/install.md
CHANGED
|
@@ -33,6 +33,12 @@ when available. If an installer-supported update is available, pressing Enter
|
|
|
33
33
|
at the inline prompt runs `projmux update apply`; answering `n` prints that
|
|
34
34
|
manual command and continues into the shell.
|
|
35
35
|
|
|
36
|
+
After the shell tmux client attaches, projmux consumes the pending welcome
|
|
37
|
+
marker once and shows the same guide in a dismissable popup. If you want to
|
|
38
|
+
revisit that guide later, run `projmux welcome`. Set `PROJMUX_WELCOME=off`
|
|
39
|
+
before launching `projmux shell` to suppress the automatic attach popup without
|
|
40
|
+
disabling the manual command.
|
|
41
|
+
|
|
36
42
|
## Runtime Tools
|
|
37
43
|
|
|
38
44
|
Normal use needs:
|