@hwip/cli 0.0.1 → 0.1.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.
Files changed (245) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +11 -0
  3. package/README.md +1979 -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 +292 -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 +35 -0
  39. package/dist/blocked-prompt-seal.js +89 -0
  40. package/dist/blocked-prompt.d.ts +57 -0
  41. package/dist/blocked-prompt.js +181 -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 +272 -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 +5680 -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,292 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tmuxArgs = exports.socketKey = exports.invalidateTmuxSocketCache = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const fs_1 = require("fs");
6
+ const os_1 = require("os");
7
+ const listener_log_js_1 = require("./listener-log.js");
8
+ // ─── tmux socket discovery ──────────────────────────────────────────
9
+ /**
10
+ * macOS sets a per-user `TMPDIR` like `/var/folders/xz/.../T/`, and tmux
11
+ * (started from a regular shell there) lays its socket at
12
+ * `<TMPDIR>/tmux-<uid>/default`. When the listener is spawned from a
13
+ * shell with a different TMPDIR — or no TMPDIR at all (cron, launchd,
14
+ * IDE-managed terminals) — tmux defaults to `/tmp/tmux-<uid>/default`
15
+ * and the user's real server is invisible. We probe a small list of
16
+ * common locations and use `-S <path>` for every subsequent tmux call
17
+ * once a live server is found.
18
+ *
19
+ * Caching is one-way: a successful discovery sticks for the process
20
+ * lifetime, but failure does NOT — we re-probe every cycle so the
21
+ * listener picks up a tmux server that gets started AFTER the listener
22
+ * itself (very common: the user opens `hwip cc` after starting the
23
+ * daemon). If tmux dies and respawns under a different path the user
24
+ * has to restart the listener (rare).
25
+ */
26
+ let cachedSocketPath = null;
27
+ /** True once we've confirmed a working socket. `cachedSocketPath` of
28
+ * `null` is ambiguous on its own — it can mean either "use default
29
+ * (we verified it works)" or "we haven't checked yet". This flag
30
+ * removes the ambiguity so we don't re-probe every collectSessions
31
+ * cycle (which was spamming the log with "tmux: default socket OK"). */
32
+ let cacheValid = false;
33
+ /**
34
+ * Mark the cached socket as no longer trustworthy — call this when a
35
+ * tmux command fails against the cached path. The next findTmuxSocket()
36
+ * will redo full discovery (default probe → /var/folders walk → lsof
37
+ * fallback) instead of returning a stale answer. Without this the
38
+ * listener wedged at "reported 0 session(s)" forever after a tmux
39
+ * server restart, even when a new server was live and discoverable.
40
+ */
41
+ const invalidateTmuxSocketCache = () => {
42
+ cacheValid = false;
43
+ cachedSocketPath = null;
44
+ };
45
+ exports.invalidateTmuxSocketCache = invalidateTmuxSocketCache;
46
+ const probeTmuxSocketDetail = (socketPath) => {
47
+ const args = socketPath ? ['-S', socketPath, 'list-sessions'] : ['list-sessions'];
48
+ const r = (0, child_process_1.spawnSync)('tmux', args, {
49
+ encoding: 'utf-8',
50
+ stdio: ['ignore', 'pipe', 'pipe'],
51
+ });
52
+ if (r.status === 0)
53
+ return { ok: true };
54
+ const err = (r.stderr ?? '').trim();
55
+ return { ok: false, stderr: err || undefined };
56
+ };
57
+ const probeTmuxSocket = (socketPath) => probeTmuxSocketDetail(socketPath).ok;
58
+ /**
59
+ * List every socket file inside a `tmux-<uid>/` directory. tmux's
60
+ * default socket name is `default`, but users can change it with
61
+ * `tmux -L <name>` or via .tmux.conf — so we probe every file we find,
62
+ * not just `default`. Returns absolute socket paths.
63
+ */
64
+ const listSocketsIn = (dir) => {
65
+ if (!(0, fs_1.existsSync)(dir))
66
+ return [];
67
+ try {
68
+ return (0, fs_1.readdirSync)(dir).map((name) => `${dir}/${name}`);
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ };
74
+ /**
75
+ * One name per socket file: the path with its symlinks resolved, or the path
76
+ * as given when nothing is there to resolve. macOS reaches the same directory
77
+ * as `/tmp/…` and `/private/tmp/…`, and as `/var/folders/…` and
78
+ * `/private/var/folders/…`; without this each dead socket there is probed
79
+ * once per spelling.
80
+ */
81
+ const socketKey = (path) => {
82
+ try {
83
+ return (0, fs_1.realpathSync)(path);
84
+ }
85
+ catch {
86
+ return path;
87
+ }
88
+ };
89
+ exports.socketKey = socketKey;
90
+ /**
91
+ * Final-fallback socket discovery: find tmux server processes via `ps`
92
+ * and ask `lsof` what unix socket each is bound to. Handles the cases
93
+ * filesystem walking can't:
94
+ * - macOS auto-cleanup deleted the socket file while the server kept
95
+ * running (the most likely cause of "no server running" errors when
96
+ * a tmux session is clearly alive in another iTerm/Warp pane)
97
+ * - The user runs tmux with `-L <name>` or `-S <unusual-path>` that we
98
+ * never thought to enumerate
99
+ *
100
+ * `lsof` on macOS may report sockets as `(deleted)`. Even then, if the
101
+ * server still has the inode open we can still tmux-attach by recreating
102
+ * the path — but for now we only return paths that still exist on disk
103
+ * so tmux's connect logic isn't confused. If the path is gone, the user
104
+ * has to `tmux kill-server` + restart anyway.
105
+ *
106
+ * Returns the first path `isLive` accepts, and stops there: every tmux
107
+ * process — each `tmux new -A` client, not only the server — costs one
108
+ * `lsof`, seconds apiece on a loaded daemon.
109
+ */
110
+ const findTmuxViaProcess = (isLive) => {
111
+ const username = (0, os_1.userInfo)().username;
112
+ const ps = (0, child_process_1.spawnSync)('ps', ['-A', '-o', 'pid=,user=,command='], {
113
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],
114
+ });
115
+ if (ps.status !== 0)
116
+ return undefined;
117
+ const tmuxPids = [];
118
+ for (const line of (ps.stdout ?? '').split('\n')) {
119
+ const m = line.match(/^\s*(\d+)\s+(\S+)\s+(.+)$/);
120
+ if (!m)
121
+ continue;
122
+ const [, pid, user, cmd] = m;
123
+ if (user !== username)
124
+ continue;
125
+ // Server processes show up as `tmux: server` (with the colon) on
126
+ // some versions; client/wrapper invocations show up as `tmux new`
127
+ // / `tmux attach` etc. lsof works on either.
128
+ if (!/(^|[^\w-])tmux($|[:\s])/.test(cmd))
129
+ continue;
130
+ tmuxPids.push(pid);
131
+ }
132
+ for (const pid of tmuxPids) {
133
+ const lsof = (0, child_process_1.spawnSync)('lsof', ['-p', pid, '-Fn'], {
134
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],
135
+ });
136
+ if (lsof.status !== 0)
137
+ continue;
138
+ // `-Fn` prints names prefixed with `n`; one per line. Filter for
139
+ // tmux-shaped socket paths.
140
+ for (const lline of (lsof.stdout ?? '').split('\n')) {
141
+ if (!lline.startsWith('n'))
142
+ continue;
143
+ const path = lline.slice(1);
144
+ if (!/\/tmux-\d+\//.test(path))
145
+ continue;
146
+ if (path.includes('(deleted)'))
147
+ continue;
148
+ if (isLive(path))
149
+ return path;
150
+ }
151
+ }
152
+ return undefined;
153
+ };
154
+ /** Walk `/var/folders` for user-owned `tmux-<uid>/*` socket files. Each
155
+ * subdir is wrapped in its own try/catch — entries that belong to other
156
+ * users (or that we otherwise can't read) must skip cleanly, not abort
157
+ * the whole walk. */
158
+ const walkVarFolders = (uid) => {
159
+ const found = [];
160
+ const root = '/var/folders';
161
+ if (!(0, fs_1.existsSync)(root))
162
+ return found;
163
+ let topEntries;
164
+ try {
165
+ topEntries = (0, fs_1.readdirSync)(root);
166
+ }
167
+ catch {
168
+ return found;
169
+ }
170
+ for (const a of topEntries) {
171
+ const aPath = `${root}/${a}`;
172
+ let subEntries;
173
+ try {
174
+ subEntries = (0, fs_1.readdirSync)(aPath);
175
+ }
176
+ catch {
177
+ continue;
178
+ }
179
+ for (const b of subEntries) {
180
+ found.push(...listSocketsIn(`${aPath}/${b}/T/tmux-${uid}`));
181
+ }
182
+ }
183
+ return found;
184
+ };
185
+ /**
186
+ * Track whether the "no server anywhere" diagnostic was already logged
187
+ * this run. We want the user to see the path list *once* on first
188
+ * failure, then go quiet until we either find a server or notice a new
189
+ * candidate file appearing — otherwise every 30-s cycle would spam the
190
+ * full probe report.
191
+ */
192
+ let warnedNoServer = false;
193
+ const findTmuxSocket = () => {
194
+ // Successful discovery sticks. Failure does NOT — we want to pick
195
+ // up a tmux server that the user launches *after* `hwip listener`.
196
+ if (cacheValid)
197
+ return cachedSocketPath;
198
+ // Explicit override — for users with `tmux -L <name>` setups or
199
+ // unusual socket locations. Skip discovery entirely if set and
200
+ // probeable.
201
+ const override = process.env.HWIP_TMUX_SOCKET;
202
+ if (override) {
203
+ if (probeTmuxSocket(override)) {
204
+ cachedSocketPath = override;
205
+ cacheValid = true;
206
+ (0, listener_log_js_1.log)(`tmux socket → ${override} (from HWIP_TMUX_SOCKET)`);
207
+ warnedNoServer = false;
208
+ return override;
209
+ }
210
+ // Fall through to standard discovery if override fails — better
211
+ // than failing silently. We re-log this every cycle (no
212
+ // `warnedNoServer`) because it's a user-supplied setting we want
213
+ // to keep nagging about.
214
+ (0, listener_log_js_1.log)(`tmux: HWIP_TMUX_SOCKET=${override} probe failed, falling back to auto-discovery`);
215
+ }
216
+ // Default first — succeeds when the shell that launched us shares
217
+ // tmux's view, and costs one tmux call. It must run before the scans
218
+ // below: `findTmuxViaProcess` runs up to one `lsof` per tmux process (every
219
+ // `tmux new -A` client, not just the server), which took 11–19s+ with 14
220
+ // sessions on a loaded daemon — longer than a fresh inventory worker's
221
+ // whole 20s budget.
222
+ if (probeTmuxSocket(null)) {
223
+ cachedSocketPath = null; // null means "use default"
224
+ cacheValid = true;
225
+ if (!warnedNoServer)
226
+ (0, listener_log_js_1.log)('tmux: default socket OK');
227
+ warnedNoServer = false;
228
+ return null;
229
+ }
230
+ const uid = (0, os_1.userInfo)().uid;
231
+ const probed = new Set();
232
+ const isLive = (path) => {
233
+ const key = (0, exports.socketKey)(path);
234
+ if (probed.has(key))
235
+ return false;
236
+ probed.add(key);
237
+ return (0, fs_1.existsSync)(path) && probeTmuxSocket(path);
238
+ };
239
+ // Every socket file in any `tmux-<uid>/` dir next — `tmux -L <name>`
240
+ // puts its socket beside `default`, so one probe per file finds it.
241
+ const envDir = process.env.TMUX_TMPDIR || process.env.TMPDIR;
242
+ const files = [
243
+ ...(envDir ? listSocketsIn(`${envDir.replace(/\/+$/, '')}/tmux-${uid}`) : []),
244
+ ...walkVarFolders(uid),
245
+ ...listSocketsIn(`/tmp/tmux-${uid}`),
246
+ ...listSocketsIn(`/private/tmp/tmux-${uid}`),
247
+ ];
248
+ // Process-based discovery last — it is the only path that finds a `-S`
249
+ // path the walks never look in, but it is also the one that costs seconds.
250
+ const live = files.find(isLive) ?? findTmuxViaProcess(isLive);
251
+ if (live) {
252
+ cachedSocketPath = live;
253
+ cacheValid = true;
254
+ (0, listener_log_js_1.log)(`tmux socket → ${live}`);
255
+ warnedNoServer = false;
256
+ return live;
257
+ }
258
+ const unique = [...probed];
259
+ // No live tmux yet. Log the full probe report once, then stay quiet
260
+ // until something works — otherwise the user gets a 4-line dump
261
+ // every 30 seconds while they're still bringing tmux up. Include the
262
+ // tmux binary identification + stderr for failed probes so the user
263
+ // can spot version mismatches (homebrew on /usr/local vs /opt/homebrew)
264
+ // or stale socket files.
265
+ if (!warnedNoServer) {
266
+ const which = (0, child_process_1.spawnSync)('which', ['tmux'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
267
+ const tmuxPath = (which.stdout ?? '').trim() || '(not on PATH)';
268
+ const ver = (0, child_process_1.spawnSync)('tmux', ['-V'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
269
+ const tmuxVer = (ver.stdout ?? '').trim() || '?';
270
+ (0, listener_log_js_1.log)(`tmux: no live server yet — using ${tmuxPath} (${tmuxVer})`);
271
+ (0, listener_log_js_1.log)(`tmux: probed ${unique.length} candidate(s):`);
272
+ for (const path of unique) {
273
+ if (!(0, fs_1.existsSync)(path)) {
274
+ (0, listener_log_js_1.log)(` - ${path} (no socket file)`);
275
+ continue;
276
+ }
277
+ const detail = probeTmuxSocketDetail(path);
278
+ (0, listener_log_js_1.log)(` ✗ ${path} (${detail.stderr ?? 'probe failed without stderr'})`);
279
+ }
280
+ (0, listener_log_js_1.log)(`tmux: will retry each cycle. If your tmux uses a custom socket,`);
281
+ (0, listener_log_js_1.log)(` run \`tmux info | head -1\` in the same shell as 'hwip cc'`);
282
+ (0, listener_log_js_1.log)(` and pass it via: HWIP_TMUX_SOCKET=<path> hwip listener`);
283
+ warnedNoServer = true;
284
+ }
285
+ return null;
286
+ };
287
+ /** Prepend `-S <socket>` when we've discovered a non-default tmux server. */
288
+ const tmuxArgs = (args) => {
289
+ const sock = findTmuxSocket();
290
+ return sock ? ['-S', sock, ...args] : args;
291
+ };
292
+ exports.tmuxArgs = tmuxArgs;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The agent's own to-do list, folded out of Claude Code's task tools.
3
+ *
4
+ * Claude Code does not write the list down whole: TaskCreate adds one task,
5
+ * TaskUpdate moves one, TaskList reads them back. What the timeline wants is the
6
+ * list as it stands, so this folds those steps into it.
7
+ *
8
+ * Only the *results* are read — the user entry's top-level `toolUseResult` —
9
+ * because they carry everything at once: a created task's id comes back there and
10
+ * nowhere in the call (measured 2026-10-09, one transcript: TaskCreate input is
11
+ * `{subject, description, activeForm}`, its result `{task: {id, subject}}`;
12
+ * TaskUpdate's result `{taskId, statusChange: {from, to}}`; TaskList's
13
+ * `{tasks: [{id, subject, status}]}`). Folding results alone means a call and its
14
+ * result landing in different reads never have to be matched up.
15
+ */
16
+ export type TodoStatus = 'pending' | 'in_progress' | 'completed';
17
+ export interface TodoItem {
18
+ readonly id: string;
19
+ readonly text: string;
20
+ readonly status: TodoStatus;
21
+ }
22
+ export interface TodoList {
23
+ readonly items: readonly TodoItem[];
24
+ }
25
+ /**
26
+ * The list after one transcript entry. Returns `list` itself — the same
27
+ * reference — when the entry is not a task result, so a caller tells "changed"
28
+ * from "not" by identity.
29
+ */
30
+ export declare const applyTodoEntry: (list: TodoList | undefined, entry: Record<string, unknown>) => TodoList | undefined;
31
+ //# sourceMappingURL=todo-fold.d.ts.map
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ /**
3
+ * The agent's own to-do list, folded out of Claude Code's task tools.
4
+ *
5
+ * Claude Code does not write the list down whole: TaskCreate adds one task,
6
+ * TaskUpdate moves one, TaskList reads them back. What the timeline wants is the
7
+ * list as it stands, so this folds those steps into it.
8
+ *
9
+ * Only the *results* are read — the user entry's top-level `toolUseResult` —
10
+ * because they carry everything at once: a created task's id comes back there and
11
+ * nowhere in the call (measured 2026-10-09, one transcript: TaskCreate input is
12
+ * `{subject, description, activeForm}`, its result `{task: {id, subject}}`;
13
+ * TaskUpdate's result `{taskId, statusChange: {from, to}}`; TaskList's
14
+ * `{tasks: [{id, subject, status}]}`). Folding results alone means a call and its
15
+ * result landing in different reads never have to be matched up.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.applyTodoEntry = void 0;
19
+ const isTodoStatus = (value) => value === 'pending' || value === 'in_progress' || value === 'completed';
20
+ // `record`, `nonBlank` and `listed` take `unknown` on purpose: they are the parse
21
+ // boundary, reading the vendor's JSON field by field.
22
+ const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : undefined; // narrowed by the object/array check
23
+ const nonBlank = (value) => typeof value === 'string' && value.trim() ? value.trim() : undefined;
24
+ const allDone = (list) => list.items.length > 0 && list.items.every((item) => item.status === 'completed');
25
+ /** TaskCreate: one more task, pending. A finished list is replaced, not added to. */
26
+ const created = (list, task) => {
27
+ const id = nonBlank(task.id);
28
+ const text = nonBlank(task.subject);
29
+ if (!id || !text)
30
+ return list;
31
+ const item = { id, text, status: 'pending' };
32
+ // Every task done and a new one arrives: that is the next list starting.
33
+ if (!list || allDone(list))
34
+ return { items: [item] };
35
+ if (list.items.some((existing) => existing.id === id))
36
+ return list;
37
+ return { items: [...list.items, item] };
38
+ };
39
+ /** TaskUpdate: one task moved. `to` undefined = a status outside the three, which takes it off the list. */
40
+ const updated = (list, taskId, to) => {
41
+ // A task this list never saw was made before the read began: nothing to move.
42
+ const task = list?.items.find((item) => item.id === taskId);
43
+ // Already there (or never seen): the same list, so no `todo` goes out again.
44
+ if (!list || !task || task.status === to)
45
+ return list;
46
+ if (!to)
47
+ return { items: list.items.filter((item) => item.id !== taskId) };
48
+ return { items: list.items.map((item) => (item.id === taskId && item.status !== to ? { ...item, status: to } : item)) };
49
+ };
50
+ /**
51
+ * TaskList: the tasks it named are refreshed and any it adds are taken, but a
52
+ * task it leaves out stays. The one TaskList measured returned only pending and
53
+ * in-progress tasks, so treating its answer as the whole list would drop every
54
+ * finished one and turn 7/7 into 0/4.
55
+ */
56
+ const listed = (list, tasks) => {
57
+ const named = [];
58
+ for (const raw of tasks) {
59
+ const task = record(raw);
60
+ const id = nonBlank(task?.id);
61
+ const text = nonBlank(task?.subject);
62
+ if (!id || !text || !isTodoStatus(task?.status))
63
+ return list;
64
+ named.push({ id, text, status: task.status });
65
+ }
66
+ if (!named.length)
67
+ return list;
68
+ const byId = new Map(named.map((item) => [item.id, item]));
69
+ const kept = (list?.items ?? []).map((item) => byId.get(item.id) ?? item);
70
+ const known = new Set(kept.map((item) => item.id));
71
+ const items = [...kept, ...named.filter((item) => !known.has(item.id))];
72
+ const same = list && items.length === list.items.length && items.every((item, i) => sameItem(item, list.items[i]));
73
+ return same ? list : { items };
74
+ };
75
+ const sameItem = (a, b) => a.id === b.id && a.text === b.text && a.status === b.status;
76
+ /**
77
+ * The list after one transcript entry. Returns `list` itself — the same
78
+ * reference — when the entry is not a task result, so a caller tells "changed"
79
+ * from "not" by identity.
80
+ */
81
+ const applyTodoEntry = (list, entry) => {
82
+ if (entry.type !== 'user')
83
+ return list;
84
+ const result = record(entry.toolUseResult);
85
+ if (!result)
86
+ return list;
87
+ const task = record(result.task);
88
+ // A task that says its status (TaskGet's answer, unmeasured) is a read, not a create:
89
+ // folded like a one-task TaskList, so it cannot restart a finished list.
90
+ if (task && 'status' in task)
91
+ return listed(list, [task]);
92
+ if (task)
93
+ return created(list, task);
94
+ const taskId = nonBlank(result.taskId);
95
+ const change = record(result.statusChange);
96
+ if (taskId && change)
97
+ return updated(list, taskId, isTodoStatus(change.to) ? change.to : undefined);
98
+ if (Array.isArray(result.tasks))
99
+ return listed(list, result.tasks);
100
+ return list;
101
+ };
102
+ exports.applyTodoEntry = applyTodoEntry;