@hwip/cli 0.0.1 → 0.1.1

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.
Files changed (245) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +11 -0
  3. package/README.md +1983 -0
  4. package/dist/agent-list.d.ts +76 -0
  5. package/dist/agent-list.js +102 -0
  6. package/dist/agent-read.d.ts +105 -0
  7. package/dist/agent-read.js +309 -0
  8. package/dist/agent-rules-fetch.d.ts +51 -0
  9. package/dist/agent-rules-fetch.js +275 -0
  10. package/dist/agent-rules.default.d.ts +30 -0
  11. package/dist/agent-rules.default.js +305 -0
  12. package/dist/agent-session.d.ts +23 -0
  13. package/dist/agent-session.js +44 -0
  14. package/dist/agent-state.d.ts +84 -0
  15. package/dist/agent-state.js +188 -0
  16. package/dist/agent-target.d.ts +60 -0
  17. package/dist/agent-target.js +81 -0
  18. package/dist/agents.d.ts +31 -0
  19. package/dist/agents.js +75 -0
  20. package/dist/api.d.ts +28 -0
  21. package/dist/api.js +76 -0
  22. package/dist/args.d.ts +74 -0
  23. package/dist/args.js +254 -0
  24. package/dist/ask-context-attach.d.ts +73 -0
  25. package/dist/ask-context-attach.js +151 -0
  26. package/dist/ask-context-screen.d.ts +21 -0
  27. package/dist/ask-context-screen.js +49 -0
  28. package/dist/ask-context-switch.d.ts +31 -0
  29. package/dist/ask-context-switch.js +74 -0
  30. package/dist/ask-context.d.ts +84 -0
  31. package/dist/ask-context.js +172 -0
  32. package/dist/ask-pending.d.ts +47 -0
  33. package/dist/ask-pending.js +221 -0
  34. package/dist/ask-seal.d.ts +120 -0
  35. package/dist/ask-seal.js +179 -0
  36. package/dist/ask.d.ts +107 -0
  37. package/dist/ask.js +551 -0
  38. package/dist/blocked-prompt-seal.d.ts +39 -0
  39. package/dist/blocked-prompt-seal.js +92 -0
  40. package/dist/blocked-prompt.d.ts +62 -0
  41. package/dist/blocked-prompt.js +518 -0
  42. package/dist/channel.d.ts +67 -0
  43. package/dist/channel.js +136 -0
  44. package/dist/check-update.d.ts +28 -0
  45. package/dist/check-update.js +153 -0
  46. package/dist/cli.d.ts +3 -0
  47. package/dist/cli.js +813 -0
  48. package/dist/codex-config.d.ts +24 -0
  49. package/dist/codex-config.js +169 -0
  50. package/dist/codex-transcript.d.ts +59 -0
  51. package/dist/codex-transcript.js +373 -0
  52. package/dist/command-refusal.d.ts +56 -0
  53. package/dist/command-refusal.js +127 -0
  54. package/dist/command-scan.d.ts +46 -0
  55. package/dist/command-scan.js +154 -0
  56. package/dist/command-seal.d.ts +57 -0
  57. package/dist/command-seal.js +88 -0
  58. package/dist/command-seq.d.ts +30 -0
  59. package/dist/command-seq.js +150 -0
  60. package/dist/config.d.ts +156 -0
  61. package/dist/config.js +392 -0
  62. package/dist/core-sections.d.ts +18 -0
  63. package/dist/core-sections.js +28 -0
  64. package/dist/crypto.d.ts +278 -0
  65. package/dist/crypto.js +698 -0
  66. package/dist/desktop-notify.d.ts +33 -0
  67. package/dist/desktop-notify.js +49 -0
  68. package/dist/device-directory.d.ts +36 -0
  69. package/dist/device-directory.js +97 -0
  70. package/dist/device-presence.d.ts +17 -0
  71. package/dist/device-presence.js +62 -0
  72. package/dist/downloads.d.ts +29 -0
  73. package/dist/downloads.js +106 -0
  74. package/dist/errors.d.ts +12 -0
  75. package/dist/errors.js +28 -0
  76. package/dist/file-crypto.d.ts +94 -0
  77. package/dist/file-crypto.js +278 -0
  78. package/dist/file-view-budget.d.ts +15 -0
  79. package/dist/file-view-budget.js +34 -0
  80. package/dist/file-view-switch.d.ts +28 -0
  81. package/dist/file-view-switch.js +122 -0
  82. package/dist/file-view.d.ts +108 -0
  83. package/dist/file-view.js +327 -0
  84. package/dist/gate.d.ts +141 -0
  85. package/dist/gate.js +336 -0
  86. package/dist/gemini-transcript.d.ts +32 -0
  87. package/dist/gemini-transcript.js +189 -0
  88. package/dist/http-failure.d.ts +98 -0
  89. package/dist/http-failure.js +349 -0
  90. package/dist/hwip-core.generated.d.ts +7 -0
  91. package/dist/hwip-core.generated.js +14 -0
  92. package/dist/hwip-hook.d.ts +77 -0
  93. package/dist/hwip-hook.js +340 -0
  94. package/dist/index.d.ts +7 -0
  95. package/dist/index.js +14 -0
  96. package/dist/input-sequencer.d.ts +65 -0
  97. package/dist/input-sequencer.js +142 -0
  98. package/dist/insecure-url.d.ts +16 -0
  99. package/dist/insecure-url.js +76 -0
  100. package/dist/installer.d.ts +96 -0
  101. package/dist/installer.js +850 -0
  102. package/dist/inventory-offload.d.ts +49 -0
  103. package/dist/inventory-offload.js +209 -0
  104. package/dist/inventory-worker.d.ts +2 -0
  105. package/dist/inventory-worker.js +27 -0
  106. package/dist/jsonc-doc.d.ts +106 -0
  107. package/dist/jsonc-doc.js +354 -0
  108. package/dist/keep-awake.d.ts +32 -0
  109. package/dist/keep-awake.js +46 -0
  110. package/dist/key-changes.d.ts +48 -0
  111. package/dist/key-changes.js +140 -0
  112. package/dist/lan-auth.d.ts +140 -0
  113. package/dist/lan-auth.js +217 -0
  114. package/dist/lan-endpoint.d.ts +41 -0
  115. package/dist/lan-endpoint.js +115 -0
  116. package/dist/lan-receiver.d.ts +122 -0
  117. package/dist/lan-receiver.js +510 -0
  118. package/dist/lan-sender.d.ts +76 -0
  119. package/dist/lan-sender.js +184 -0
  120. package/dist/lan-transfer.d.ts +36 -0
  121. package/dist/lan-transfer.js +54 -0
  122. package/dist/listener-device-id.d.ts +20 -0
  123. package/dist/listener-device-id.js +133 -0
  124. package/dist/listener-log.d.ts +4 -0
  125. package/dist/listener-log.js +23 -0
  126. package/dist/listener-process.d.ts +200 -0
  127. package/dist/listener-process.js +470 -0
  128. package/dist/listener-service.d.ts +225 -0
  129. package/dist/listener-service.js +682 -0
  130. package/dist/listener-worktree-cleanup.d.ts +27 -0
  131. package/dist/listener-worktree-cleanup.js +62 -0
  132. package/dist/listener.d.ts +1325 -0
  133. package/dist/listener.js +5681 -0
  134. package/dist/login-address.d.ts +25 -0
  135. package/dist/login-address.js +97 -0
  136. package/dist/login-page.d.ts +29 -0
  137. package/dist/login-page.js +93 -0
  138. package/dist/login.d.ts +98 -0
  139. package/dist/login.js +388 -0
  140. package/dist/mcp-command.d.ts +45 -0
  141. package/dist/mcp-command.js +50 -0
  142. package/dist/mcp.d.ts +19 -0
  143. package/dist/mcp.js +29 -0
  144. package/dist/node-support.d.ts +19 -0
  145. package/dist/node-support.js +38 -0
  146. package/dist/notify-failure.d.ts +57 -0
  147. package/dist/notify-failure.js +150 -0
  148. package/dist/opencode-transcript.d.ts +139 -0
  149. package/dist/opencode-transcript.js +390 -0
  150. package/dist/optional-features.d.ts +6 -0
  151. package/dist/optional-features.js +19 -0
  152. package/dist/pane-fit.d.ts +40 -0
  153. package/dist/pane-fit.js +119 -0
  154. package/dist/peer-key.d.ts +33 -0
  155. package/dist/peer-key.js +34 -0
  156. package/dist/pi-transcript.d.ts +26 -0
  157. package/dist/pi-transcript.js +255 -0
  158. package/dist/presence.d.ts +14 -0
  159. package/dist/presence.js +124 -0
  160. package/dist/private-files.d.ts +51 -0
  161. package/dist/private-files.js +218 -0
  162. package/dist/project-switch.d.ts +19 -0
  163. package/dist/project-switch.js +60 -0
  164. package/dist/redact.d.ts +10 -0
  165. package/dist/redact.js +21 -0
  166. package/dist/remote-agents.d.ts +410 -0
  167. package/dist/remote-agents.js +1218 -0
  168. package/dist/remote-hook.d.ts +12 -0
  169. package/dist/remote-hook.js +196 -0
  170. package/dist/replace-file.d.ts +50 -0
  171. package/dist/replace-file.js +328 -0
  172. package/dist/schedule-command.d.ts +35 -0
  173. package/dist/schedule-command.js +260 -0
  174. package/dist/schedule-launch.d.ts +15 -0
  175. package/dist/schedule-launch.js +24 -0
  176. package/dist/schedule-ready.d.ts +44 -0
  177. package/dist/schedule-ready.js +71 -0
  178. package/dist/schedule-remote.d.ts +77 -0
  179. package/dist/schedule-remote.js +167 -0
  180. package/dist/schedule-runner.d.ts +89 -0
  181. package/dist/schedule-runner.js +452 -0
  182. package/dist/schedule.d.ts +149 -0
  183. package/dist/schedule.js +428 -0
  184. package/dist/secret-mask.d.ts +29 -0
  185. package/dist/secret-mask.js +245 -0
  186. package/dist/self-register.d.ts +45 -0
  187. package/dist/self-register.js +67 -0
  188. package/dist/send.d.ts +56 -0
  189. package/dist/send.js +104 -0
  190. package/dist/session-diff.d.ts +161 -0
  191. package/dist/session-diff.js +445 -0
  192. package/dist/session-registry.d.ts +143 -0
  193. package/dist/session-registry.js +216 -0
  194. package/dist/sessions-report.d.ts +59 -0
  195. package/dist/sessions-report.js +99 -0
  196. package/dist/shell-audit.d.ts +34 -0
  197. package/dist/shell-audit.js +47 -0
  198. package/dist/subagent-transcripts.d.ts +167 -0
  199. package/dist/subagent-transcripts.js +437 -0
  200. package/dist/subscriber-trust.d.ts +128 -0
  201. package/dist/subscriber-trust.js +397 -0
  202. package/dist/templates.d.ts +105 -0
  203. package/dist/templates.js +871 -0
  204. package/dist/tmux-install.d.ts +44 -0
  205. package/dist/tmux-install.js +65 -0
  206. package/dist/tmux-liveness.d.ts +45 -0
  207. package/dist/tmux-liveness.js +71 -0
  208. package/dist/tmux-pane.d.ts +55 -0
  209. package/dist/tmux-pane.js +126 -0
  210. package/dist/tmux-socket.d.ts +20 -0
  211. package/dist/tmux-socket.js +292 -0
  212. package/dist/todo-fold.d.ts +31 -0
  213. package/dist/todo-fold.js +102 -0
  214. package/dist/transcript-tail.d.ts +312 -0
  215. package/dist/transcript-tail.js +718 -0
  216. package/dist/trust-command.d.ts +49 -0
  217. package/dist/trust-command.js +200 -0
  218. package/dist/trust-lock.d.ts +26 -0
  219. package/dist/trust-lock.js +94 -0
  220. package/dist/trust-pins.d.ts +37 -0
  221. package/dist/trust-pins.js +82 -0
  222. package/dist/turn-recorder-wire.d.ts +70 -0
  223. package/dist/turn-recorder-wire.js +165 -0
  224. package/dist/turn-recorder.d.ts +190 -0
  225. package/dist/turn-recorder.js +695 -0
  226. package/dist/turn-ring.d.ts +93 -0
  227. package/dist/turn-ring.js +418 -0
  228. package/dist/turn-watch.d.ts +177 -0
  229. package/dist/turn-watch.js +630 -0
  230. package/dist/types.d.ts +63 -0
  231. package/dist/types.js +2 -0
  232. package/dist/uninstall.d.ts +55 -0
  233. package/dist/uninstall.js +507 -0
  234. package/dist/usage.d.ts +17 -0
  235. package/dist/usage.js +421 -0
  236. package/dist/verify.d.ts +47 -0
  237. package/dist/verify.js +396 -0
  238. package/dist/worktree.d.ts +144 -0
  239. package/dist/worktree.js +310 -0
  240. package/dist/wrapper.d.ts +254 -0
  241. package/dist/wrapper.js +704 -0
  242. package/dist/ws-url.d.ts +109 -0
  243. package/dist/ws-url.js +202 -0
  244. package/package.json +78 -10
  245. package/bin/hwip.js +0 -3
@@ -0,0 +1,44 @@
1
+ /**
2
+ * tmux is not optional for `hwip cc` — every remote-agent subcommand runs the
3
+ * agent inside a named tmux session so the listener (and the phone picker) can
4
+ * reach it. Nothing in the install flow used to mention that, so a machine
5
+ * without tmux looked fully set up until the first `hwip cc` failed with an
6
+ * error from a program the user was never told they needed.
7
+ */
8
+ /** What this machine can do about a missing tmux. */
9
+ export type TmuxInstallPlan = {
10
+ readonly kind: 'present';
11
+ readonly path: string;
12
+ }
13
+ /** Safe to run unattended — no root, user-writable prefix. */
14
+ | {
15
+ readonly kind: 'command';
16
+ readonly run: readonly string[];
17
+ readonly label: string;
18
+ }
19
+ /** Needs root, or there is no package manager we recognise. Tell, don't do. */
20
+ | {
21
+ readonly kind: 'manual';
22
+ readonly hint: string;
23
+ };
24
+ export declare const planTmuxInstall: (deps: {
25
+ resolve: (cmd: string) => string | null;
26
+ platform: NodeJS.Platform;
27
+ }) => TmuxInstallPlan;
28
+ /**
29
+ * Act on the plan. Everything the outside world touches is injected, because
30
+ * the interesting branch — "offer, then run a package manager" — is otherwise
31
+ * only reachable through an interactive install that no test can drive.
32
+ */
33
+ export declare const ensureTmux: (deps: {
34
+ plan: TmuxInstallPlan;
35
+ nonInteractive: boolean;
36
+ askConfirm: (message: string) => Promise<boolean>;
37
+ run: (argv: readonly string[]) => void;
38
+ report: {
39
+ ok: (m: string) => void;
40
+ fail: (m: string) => void;
41
+ note: (m: string) => void;
42
+ };
43
+ }) => Promise<void>;
44
+ //# sourceMappingURL=tmux-install.d.ts.map
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * tmux is not optional for `hwip cc` — every remote-agent subcommand runs the
4
+ * agent inside a named tmux session so the listener (and the phone picker) can
5
+ * reach it. Nothing in the install flow used to mention that, so a machine
6
+ * without tmux looked fully set up until the first `hwip cc` failed with an
7
+ * error from a program the user was never told they needed.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.ensureTmux = exports.planTmuxInstall = void 0;
11
+ /** Package managers that need root, and the line to hand the user for each. */
12
+ const ROOT_PACKAGE_MANAGERS = [
13
+ { bin: 'apt-get', hint: 'sudo apt install tmux' },
14
+ { bin: 'dnf', hint: 'sudo dnf install tmux' },
15
+ { bin: 'pacman', hint: 'sudo pacman -S tmux' },
16
+ { bin: 'zypper', hint: 'sudo zypper install tmux' },
17
+ { bin: 'apk', hint: 'sudo apk add tmux' },
18
+ ];
19
+ const planTmuxInstall = (deps) => {
20
+ const path = deps.resolve('tmux');
21
+ if (path)
22
+ return { kind: 'present', path };
23
+ // tmux has no native Windows build. `hwip cc` is POSIX-only by way of tmux,
24
+ // so point at WSL rather than at a package that cannot be installed.
25
+ if (deps.platform === 'win32') {
26
+ return { kind: 'manual', hint: '`hwip cc` needs tmux, which has no Windows build — run it under WSL' };
27
+ }
28
+ if (deps.platform === 'darwin') {
29
+ return deps.resolve('brew')
30
+ ? { kind: 'command', run: ['brew', 'install', 'tmux'], label: 'brew install tmux' }
31
+ : { kind: 'manual', hint: 'install Homebrew (brew.sh), then: brew install tmux' };
32
+ }
33
+ const rootManager = ROOT_PACKAGE_MANAGERS.find((pm) => deps.resolve(pm.bin));
34
+ if (rootManager)
35
+ return { kind: 'manual', hint: rootManager.hint };
36
+ return { kind: 'manual', hint: 'install tmux with this system\'s package manager' };
37
+ };
38
+ exports.planTmuxInstall = planTmuxInstall;
39
+ /**
40
+ * Act on the plan. Everything the outside world touches is injected, because
41
+ * the interesting branch — "offer, then run a package manager" — is otherwise
42
+ * only reachable through an interactive install that no test can drive.
43
+ */
44
+ const ensureTmux = async (deps) => {
45
+ const { plan, report } = deps;
46
+ if (plan.kind === 'present')
47
+ return report.ok(`tmux found (${plan.path})`);
48
+ if (plan.kind === 'manual')
49
+ return report.fail(`tmux not found — \`hwip cc\` needs it. ${plan.hint}`);
50
+ if (deps.nonInteractive) {
51
+ return report.note(`tmux not found — \`hwip cc\` needs it. Run: ${plan.label}`);
52
+ }
53
+ const wanted = await deps.askConfirm(`tmux is missing and \`hwip cc\` needs it — run \`${plan.label}\`? (enter to confirm)`);
54
+ if (!wanted) {
55
+ return report.note(`tmux not installed — \`hwip cc\` will not work until you run: ${plan.label}`);
56
+ }
57
+ try {
58
+ deps.run(plan.run);
59
+ report.ok('tmux installed');
60
+ }
61
+ catch {
62
+ report.fail(`\`${plan.label}\` failed — run it by hand`);
63
+ }
64
+ };
65
+ exports.ensureTmux = ensureTmux;
@@ -0,0 +1,45 @@
1
+ import type { SessionLiveness } from './session-registry.js';
2
+ /**
3
+ * Every session name the tmux server currently holds, or null when there was no
4
+ * server to ask — the binary is missing, or nothing answers on the socket this
5
+ * process resolved.
6
+ *
7
+ * Null is not an empty list. `has-session` cannot tell the two apart: against a
8
+ * socket with nobody behind it it exits 1 — the same answer it gives for a name
9
+ * that ended (measured 2026-09-18). That matters because socket discovery falls
10
+ * back to the default socket, and a cron or launchd process that cannot see the
11
+ * user's server would otherwise call every live session an ended one.
12
+ */
13
+ export declare const liveSessionNames: () => string[] | null;
14
+ /**
15
+ * The three-state answer, for callers that destroy something on "ended".
16
+ *
17
+ * "tmux says no such session" and "no tmux answered" are not the same fact, and
18
+ * only one of them means the session is over. Collapsing them lets a missing
19
+ * binary or a socket nobody is behind read as "everything has ended" — which,
20
+ * on the delete path, is an irreversible deletion justified by a failure to ask.
21
+ *
22
+ * Asking for the whole list rather than `has-session -t =<name>` is what makes
23
+ * the distinction available at all, and the name comparison is then exact by
24
+ * construction — a tmux TARGET falls back to prefix and fnmatch matching, so
25
+ * with `hwip-hwipit` running an ended `hwip-hwip` used to answer "running".
26
+ */
27
+ export declare const sessionLiveness: (name: string) => SessionLiveness;
28
+ /**
29
+ * Whether this machine currently holds a session by this name. A subagent
30
+ * pane cannot be asked with has-session: tmux would parse `hwip-x.48` as
31
+ * session `hwip-x`, pane index 48 and answer for the WRONG thing (or exit 1
32
+ * for a missing index) — so subs are answered from the sweep's target map,
33
+ * which is exactly what "a pane we can address without ambiguity" means.
34
+ *
35
+ * An in-process subagent has no pane to address at all, so it is answered from
36
+ * the transcript map instead. Both maps are the same statement — the last sweep
37
+ * saw this thing — and the split is which of the two the sweep could record.
38
+ */
39
+ /** Exported for the same reason as `sessionLiveness` above: the subagent branch is
40
+ * the load-bearing one, and a test has to be able to reach it.
41
+ * Unknown means "not running" here, which is what its callers — resume,
42
+ * exit, stream targeting — already do with a session they cannot find. The
43
+ * paths that DELETE ask `sessionLiveness` instead and refuse on unknown. */
44
+ export declare const sessionExists: (name: string) => boolean;
45
+ //# sourceMappingURL=tmux-liveness.d.ts.map
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sessionExists = exports.sessionLiveness = exports.liveSessionNames = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const tmux_pane_js_1 = require("./tmux-pane.js");
6
+ const tmux_socket_js_1 = require("./tmux-socket.js");
7
+ /**
8
+ * Every session name the tmux server currently holds, or null when there was no
9
+ * server to ask — the binary is missing, or nothing answers on the socket this
10
+ * process resolved.
11
+ *
12
+ * Null is not an empty list. `has-session` cannot tell the two apart: against a
13
+ * socket with nobody behind it it exits 1 — the same answer it gives for a name
14
+ * that ended (measured 2026-09-18). That matters because socket discovery falls
15
+ * back to the default socket, and a cron or launchd process that cannot see the
16
+ * user's server would otherwise call every live session an ended one.
17
+ */
18
+ const liveSessionNames = () => {
19
+ const probe = (0, child_process_1.spawnSync)('tmux', (0, tmux_socket_js_1.tmuxArgs)(['list-sessions', '-F', '#{session_name}']), {
20
+ encoding: 'utf-8',
21
+ stdio: ['ignore', 'pipe', 'ignore'],
22
+ });
23
+ // A server with zero sessions does not exist — tmux exits with the last one
24
+ // — so a non-zero status here is always "no server answered", never "none".
25
+ if (probe.error || probe.status !== 0)
26
+ return null;
27
+ return (probe.stdout ?? '').split('\n').map((line) => line.trim()).filter(Boolean);
28
+ };
29
+ exports.liveSessionNames = liveSessionNames;
30
+ /**
31
+ * The three-state answer, for callers that destroy something on "ended".
32
+ *
33
+ * "tmux says no such session" and "no tmux answered" are not the same fact, and
34
+ * only one of them means the session is over. Collapsing them lets a missing
35
+ * binary or a socket nobody is behind read as "everything has ended" — which,
36
+ * on the delete path, is an irreversible deletion justified by a failure to ask.
37
+ *
38
+ * Asking for the whole list rather than `has-session -t =<name>` is what makes
39
+ * the distinction available at all, and the name comparison is then exact by
40
+ * construction — a tmux TARGET falls back to prefix and fnmatch matching, so
41
+ * with `hwip-hwipit` running an ended `hwip-hwip` used to answer "running".
42
+ */
43
+ const sessionLiveness = (name) => {
44
+ if ((0, tmux_pane_js_1.isSubagentSessionName)(name)) {
45
+ const known = tmux_pane_js_1.targetsBySession.has(name) || tmux_pane_js_1.subagentTranscriptsBySession.has(name);
46
+ return known ? 'running' : 'ended';
47
+ }
48
+ const names = (0, exports.liveSessionNames)();
49
+ if (names === null)
50
+ return 'unknown';
51
+ return names.includes(name) ? 'running' : 'ended';
52
+ };
53
+ exports.sessionLiveness = sessionLiveness;
54
+ /**
55
+ * Whether this machine currently holds a session by this name. A subagent
56
+ * pane cannot be asked with has-session: tmux would parse `hwip-x.48` as
57
+ * session `hwip-x`, pane index 48 and answer for the WRONG thing (or exit 1
58
+ * for a missing index) — so subs are answered from the sweep's target map,
59
+ * which is exactly what "a pane we can address without ambiguity" means.
60
+ *
61
+ * An in-process subagent has no pane to address at all, so it is answered from
62
+ * the transcript map instead. Both maps are the same statement — the last sweep
63
+ * saw this thing — and the split is which of the two the sweep could record.
64
+ */
65
+ /** Exported for the same reason as `sessionLiveness` above: the subagent branch is
66
+ * the load-bearing one, and a test has to be able to reach it.
67
+ * Unknown means "not running" here, which is what its callers — resume,
68
+ * exit, stream targeting — already do with a session they cannot find. The
69
+ * paths that DELETE ask `sessionLiveness` instead and refuse on unknown. */
70
+ const sessionExists = (name) => (0, exports.sessionLiveness)(name) === 'running';
71
+ exports.sessionExists = sessionExists;
@@ -0,0 +1,55 @@
1
+ import { type RegisteredRemoteAgent } from './remote-agents.js';
2
+ export interface PaneInfo {
3
+ currentCommand: string | null;
4
+ startCommand: string | null;
5
+ currentPath: string | null;
6
+ panePid: number | null;
7
+ }
8
+ export declare const FIELD_SEP = "\u241F";
9
+ /** Session name → pinned pane id, replaced wholesale by each inventory sweep. */
10
+ export declare let targetsBySession: Map<string, string>;
11
+ /**
12
+ * A subagent session name: `<session>.<pane id digits>` (`hwip-hwip.48`).
13
+ * tmux forbids `.` in session names, so the shape cannot collide; `#` would
14
+ * break the server's stream-sub keys and `:` is the web key separator.
15
+ */
16
+ export declare const isSubagentSessionName: (name: string) => boolean;
17
+ /** Store the latest sweep's name → pane-id map (see `CollectResult.targets`). */
18
+ export declare const recordTargets: (targets: Record<string, string> | null | undefined) => void;
19
+ /**
20
+ * Subagent name → the transcript it writes, for the ones that have no pane
21
+ * (`CollectResult.subagentTranscripts`). The addressing map above answers for
22
+ * pane subagents; this one answers for in-process ones, and it is deliberately
23
+ * NOT `targets`: a row in there is a pane every write path may address, and
24
+ * these rows have nothing to address.
25
+ */
26
+ export declare let subagentTranscriptsBySession: Map<string, string>;
27
+ export declare const recordSubagentTranscripts: (paths: Record<string, string> | null | undefined) => void;
28
+ /** The transcript of an in-process subagent, or null for anything else. */
29
+ export declare const subagentTranscriptFor: (name: string) => string | null;
30
+ /**
31
+ * The tmux target for a wire session name: its pinned pane id when the last
32
+ * sweep saw the session, else the name itself (a session younger than one
33
+ * sweep, or tmux unreachable at startup — today's resolution, active pane).
34
+ *
35
+ * A SUBAGENT name is the one exception: it resolves ONLY through the map, and
36
+ * an unknown one is null — because tmux parses `hwip-hwip.48` as session
37
+ * `hwip-hwip`, pane INDEX 48, and silently falls back to the active pane when
38
+ * that index is missing (measured: `display -t hwip-hwip.99` → main pane,
39
+ * exit 0). Every caller treats null as "gone": no spawn, refusal, or capture
40
+ * failure — never a write to whichever pane happens to be focused.
41
+ */
42
+ export declare const tmuxTargetFor: (name: string) => string | null;
43
+ export declare const readPaneInfo: (session: string) => PaneInfo;
44
+ /**
45
+ * Identify the agent from the tmux pane. Prefer `pane_start_command`
46
+ * because the foreground process is usually `node`/`python3` (the
47
+ * interpreter), which doesn't tell us *what* was launched. Fall back to
48
+ * `pane_current_command` when start_command is empty — tmux clears
49
+ * start_command in some re-attach cases, especially when a pre-existing
50
+ * session was joined via `tmux new -A` instead of being created fresh.
51
+ * That fallback is safe because only the literal binaries registered in
52
+ * remote-agents.ts are accepted as a match.
53
+ */
54
+ export declare const detectRemoteAgent: (info: PaneInfo) => RegisteredRemoteAgent | null;
55
+ //# sourceMappingURL=tmux-pane.d.ts.map
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectRemoteAgent = exports.readPaneInfo = exports.tmuxTargetFor = exports.subagentTranscriptFor = exports.recordSubagentTranscripts = exports.subagentTranscriptsBySession = exports.recordTargets = exports.isSubagentSessionName = exports.targetsBySession = exports.FIELD_SEP = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const path_1 = require("path");
6
+ const remote_agents_js_1 = require("./remote-agents.js");
7
+ const tmux_socket_js_1 = require("./tmux-socket.js");
8
+ // U+241F "Symbol for Unit Separator" — a *printable* Unicode glyph
9
+ // (3-byte UTF-8) that visually represents the C0 Unit Separator but is
10
+ // itself a normal character. Critical detail: tmux 3.5a's `-F` format
11
+ // escapes raw control bytes (0x00-0x1F) like `\037` for terminal safety,
12
+ // which broke an earlier `'\x1f'` separator — the byte we passed never
13
+ // arrived at the consumer end. A printable Unicode char passes through
14
+ // verbatim and won't appear in any real session name or filesystem path.
15
+ exports.FIELD_SEP = '␟';
16
+ // ─── Pane targets ───────────────────────────────────────────────────
17
+ // A bare `-t <session>` resolves to whichever pane has FOCUS, so the moment a
18
+ // second pane exists in a `hwip-*` session (pi-interactive-subagents splits
19
+ // one off for every subagent), capture and input silently follow the user's
20
+ // focus instead of the agent. The inventory sweep pins each session to its
21
+ // main pane id, and every `-t` call site goes through `tmuxTargetFor` so the
22
+ // pinning cannot be bypassed by one call site forgetting it.
23
+ /** Session name → pinned pane id, replaced wholesale by each inventory sweep. */
24
+ exports.targetsBySession = new Map();
25
+ /**
26
+ * A subagent session name: `<session>.<pane id digits>` (`hwip-hwip.48`).
27
+ * tmux forbids `.` in session names, so the shape cannot collide; `#` would
28
+ * break the server's stream-sub keys and `:` is the web key separator.
29
+ */
30
+ const isSubagentSessionName = (name) => /^hwip-.+\.\d+$/.test(name);
31
+ exports.isSubagentSessionName = isSubagentSessionName;
32
+ /** Store the latest sweep's name → pane-id map (see `CollectResult.targets`). */
33
+ const recordTargets = (targets) => {
34
+ exports.targetsBySession = new Map(Object.entries(targets ?? {}));
35
+ };
36
+ exports.recordTargets = recordTargets;
37
+ /**
38
+ * Subagent name → the transcript it writes, for the ones that have no pane
39
+ * (`CollectResult.subagentTranscripts`). The addressing map above answers for
40
+ * pane subagents; this one answers for in-process ones, and it is deliberately
41
+ * NOT `targets`: a row in there is a pane every write path may address, and
42
+ * these rows have nothing to address.
43
+ */
44
+ exports.subagentTranscriptsBySession = new Map();
45
+ const recordSubagentTranscripts = (paths) => {
46
+ exports.subagentTranscriptsBySession = new Map(Object.entries(paths ?? {}));
47
+ };
48
+ exports.recordSubagentTranscripts = recordSubagentTranscripts;
49
+ /** The transcript of an in-process subagent, or null for anything else. */
50
+ const subagentTranscriptFor = (name) => exports.subagentTranscriptsBySession.get(name) ?? null;
51
+ exports.subagentTranscriptFor = subagentTranscriptFor;
52
+ /**
53
+ * The tmux target for a wire session name: its pinned pane id when the last
54
+ * sweep saw the session, else the name itself (a session younger than one
55
+ * sweep, or tmux unreachable at startup — today's resolution, active pane).
56
+ *
57
+ * A SUBAGENT name is the one exception: it resolves ONLY through the map, and
58
+ * an unknown one is null — because tmux parses `hwip-hwip.48` as session
59
+ * `hwip-hwip`, pane INDEX 48, and silently falls back to the active pane when
60
+ * that index is missing (measured: `display -t hwip-hwip.99` → main pane,
61
+ * exit 0). Every caller treats null as "gone": no spawn, refusal, or capture
62
+ * failure — never a write to whichever pane happens to be focused.
63
+ */
64
+ const tmuxTargetFor = (name) => {
65
+ const hit = exports.targetsBySession.get(name);
66
+ if (hit !== undefined)
67
+ return hit;
68
+ return (0, exports.isSubagentSessionName)(name) ? null : name;
69
+ };
70
+ exports.tmuxTargetFor = tmuxTargetFor;
71
+ const readPaneInfo = (session) => {
72
+ const target = (0, exports.tmuxTargetFor)(session);
73
+ if (target === null)
74
+ return { currentCommand: null, startCommand: null, currentPath: null, panePid: null };
75
+ const r = (0, child_process_1.spawnSync)('tmux', (0, tmux_socket_js_1.tmuxArgs)(['display-message', '-p', '-t', target,
76
+ `#{pane_current_command}${exports.FIELD_SEP}#{pane_start_command}${exports.FIELD_SEP}#{pane_current_path}${exports.FIELD_SEP}#{pane_pid}`]), {
77
+ encoding: 'utf-8',
78
+ stdio: ['ignore', 'pipe', 'ignore'],
79
+ });
80
+ if (r.status !== 0)
81
+ return { currentCommand: null, startCommand: null, currentPath: null, panePid: null };
82
+ const parts = (r.stdout ?? '').trim().split(exports.FIELD_SEP);
83
+ if (parts.length !== 4)
84
+ return { currentCommand: null, startCommand: null, currentPath: null, panePid: null };
85
+ const [current, start, path, pid] = parts;
86
+ const parsedPid = Number(pid);
87
+ return {
88
+ currentCommand: current || null,
89
+ startCommand: start || null,
90
+ currentPath: path || null,
91
+ panePid: Number.isInteger(parsedPid) && parsedPid > 0 ? parsedPid : null,
92
+ };
93
+ };
94
+ exports.readPaneInfo = readPaneInfo;
95
+ /**
96
+ * Strip the surrounding quotes tmux uses when serialising commands
97
+ * with spaces. `tmux display-message -p '#{pane_start_command}'`
98
+ * outputs `"claude --resume xxx"` (literal double-quotes around the
99
+ * whole shell-command form the wrapper passed). Without unwrapping,
100
+ * the leading `"` made the basename check fail and the listener
101
+ * skipped the session as 'no agent in pane'.
102
+ */
103
+ const unwrapQuotes = (cmd) => {
104
+ const m = cmd.match(/^"(.+)"$/) || cmd.match(/^'(.+)'$/);
105
+ return m ? m[1] : cmd;
106
+ };
107
+ const firstTokenBasename = (cmd) => {
108
+ if (!cmd)
109
+ return '';
110
+ const stripped = unwrapQuotes(cmd.trim());
111
+ return (0, path_1.basename)(stripped.split(/\s+/)[0] || '');
112
+ };
113
+ /**
114
+ * Identify the agent from the tmux pane. Prefer `pane_start_command`
115
+ * because the foreground process is usually `node`/`python3` (the
116
+ * interpreter), which doesn't tell us *what* was launched. Fall back to
117
+ * `pane_current_command` when start_command is empty — tmux clears
118
+ * start_command in some re-attach cases, especially when a pre-existing
119
+ * session was joined via `tmux new -A` instead of being created fresh.
120
+ * That fallback is safe because only the literal binaries registered in
121
+ * remote-agents.ts are accepted as a match.
122
+ */
123
+ const detectRemoteAgent = (info) => (0, remote_agents_js_1.matchAgentByPaneCommand)(firstTokenBasename(info.startCommand))
124
+ ?? (0, remote_agents_js_1.matchAgentByPaneCommand)(firstTokenBasename(info.currentCommand))
125
+ ?? null;
126
+ exports.detectRemoteAgent = detectRemoteAgent;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Mark the cached socket as no longer trustworthy — call this when a
3
+ * tmux command fails against the cached path. The next findTmuxSocket()
4
+ * will redo full discovery (default probe → /var/folders walk → lsof
5
+ * fallback) instead of returning a stale answer. Without this the
6
+ * listener wedged at "reported 0 session(s)" forever after a tmux
7
+ * server restart, even when a new server was live and discoverable.
8
+ */
9
+ export declare const invalidateTmuxSocketCache: () => void;
10
+ /**
11
+ * One name per socket file: the path with its symlinks resolved, or the path
12
+ * as given when nothing is there to resolve. macOS reaches the same directory
13
+ * as `/tmp/…` and `/private/tmp/…`, and as `/var/folders/…` and
14
+ * `/private/var/folders/…`; without this each dead socket there is probed
15
+ * once per spelling.
16
+ */
17
+ export declare const socketKey: (path: string) => string;
18
+ /** Prepend `-S <socket>` when we've discovered a non-default tmux server. */
19
+ export declare const tmuxArgs: (args: string[]) => string[];
20
+ //# sourceMappingURL=tmux-socket.d.ts.map