@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
package/dist/args.d.ts ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * What each flag the CLI knows does with the word after it. A `value` flag takes
3
+ * that word whatever it looks like: the value can be text nobody typed (a command
4
+ * to approve, the model's last message), and one that starts with `--base-url=…`
5
+ * must not be read as the CLI's own flag. A `boolean` takes a following word only
6
+ * as the old parser did (a word that does not look like a flag); `optional` is a
7
+ * flag whose value may be left out (`trust --reset [device]`). A flag missing here
8
+ * is read the old way, and the test over COMMAND_FLAGS keeps a new one from
9
+ * missing.
10
+ */
11
+ export declare const FLAG_KINDS: Readonly<Record<string, 'value' | 'boolean' | 'optional'>>;
12
+ /** A yes/no flag was given a value that is neither (`--dry-run=maybe`). */
13
+ export declare class FlagValueError extends Error {
14
+ readonly flag: string;
15
+ readonly value: string;
16
+ constructor(flag: string, value: string);
17
+ }
18
+ /**
19
+ * Is the yes/no flag `name` on? The one reading every consumer of such a flag shares: not given is
20
+ * off; given bare (`--dry-run`), `=true`, `=1`, `=yes` is on; `=false`, `=0`, `=no` is off; any
21
+ * other value stops with `FlagValueError`, because a flag read as "not given" is how
22
+ * `--dry-run=true` removed what it was asked to preview and `--no-turn-history=1` left the
23
+ * upload on. The boolean parser also takes the word after a flag as its value (`--dry-run yes`),
24
+ * and that word goes through the same reading. Words are matched without case.
25
+ * `lenient` reads a value that is neither as off instead of stopping, for the few places
26
+ * that must answer whatever the flags hold (the usage gate, an error report).
27
+ */
28
+ export declare const flagOn: (args: Record<string, string | boolean>, name: string, lenient?: boolean) => boolean;
29
+ export declare const parseArgs: (argv: string[]) => Record<string, string | boolean>;
30
+ /**
31
+ * The CLI's flags, and for `send` and `agent send` the words after it. They take everything
32
+ * after it as the target and the message, verbatim: its flags come from the
33
+ * part before it only, so a `--base-url` word in a message never redirects the
34
+ * API key and a `--version` never swallows the send. The one exception is a
35
+ * help flag where the target goes. The `send` is the positional word the flag
36
+ * parser itself reads as the command (`send`) or as the one right after `agent`
37
+ * — never a word that merely looks like it: a flag value can be spelled
38
+ * `send`, and a message can hold `agent send`.
39
+ */
40
+ export declare const parseCliArgv: (argv: string[]) => {
41
+ args: Record<string, string | boolean>;
42
+ sendArgv: string[];
43
+ };
44
+ /**
45
+ * Print usage instead of running anything. A help flag used to run the
46
+ * command itself, so `notify --help` sent a push and `uninstall --help`
47
+ * uninstalled. An agent subcommand (`hwip cc`) passes the flags after it to
48
+ * the agent untouched, so `hwip cc --help` is the agent's help — but a help
49
+ * flag before it is hwip's own.
50
+ */
51
+ export declare const asksForUsage: (argv: string[], args: Record<string, string | boolean>, isAgentCommand: boolean) => boolean;
52
+ /** Flags every command accepts: `createHook` and the usage gate read them. */
53
+ export declare const GLOBAL_FLAGS: string[];
54
+ /**
55
+ * The flags each command reads, besides the global ones. Commands missing here
56
+ * (agent subcommands, `sh`, `mcp`) are not checked: an agent subcommand hands
57
+ * the words after it to the agent, and `send`'s message is never parsed.
58
+ */
59
+ export declare const COMMAND_FLAGS: Readonly<Record<string, readonly string[]>>;
60
+ /**
61
+ * Flags the command would ignore, in the order typed. A typo (`--titel`) used
62
+ * to vanish silently, and the push went out with the default title.
63
+ */
64
+ export declare const unknownFlags: (args: Record<string, string | boolean>) => string[];
65
+ /**
66
+ * The first yes/no flag of a command that holds a value that is neither yes nor no, as the
67
+ * words to print, or null. Checked before the command runs: its handlers read the flags with
68
+ * `flagOn`, which stops on the same values, and by then work may be under way. A command that
69
+ * hands its words to an agent (or is not in `COMMAND_FLAGS`) is not checked, and neither is
70
+ * `trust --yes`, whose value is the device it names. `--allow-host` is checked here too (C132): a list that is
71
+ * no list of host names stops `login` and `install` before an agent picker or a browser, not after.
72
+ */
73
+ export declare const badFlagValue: (args: Record<string, string | boolean>) => string | null;
74
+ //# sourceMappingURL=args.d.ts.map
package/dist/args.js ADDED
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ // ── Arg Parser ──────────────────────────────────────────────────
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.badFlagValue = exports.unknownFlags = exports.COMMAND_FLAGS = exports.GLOBAL_FLAGS = exports.asksForUsage = exports.parseCliArgv = exports.parseArgs = exports.flagOn = exports.FlagValueError = exports.FLAG_KINDS = void 0;
5
+ const login_address_js_1 = require("./login-address.js");
6
+ /**
7
+ * A word that starts a flag: `--name` or `--name=value`. A dash run like
8
+ * `--- done` is a value, so `--title "--- done"` keeps its title.
9
+ */
10
+ const isFlagWord = (word) => /^--[^-=]/.test(word);
11
+ /**
12
+ * What each flag the CLI knows does with the word after it. A `value` flag takes
13
+ * that word whatever it looks like: the value can be text nobody typed (a command
14
+ * to approve, the model's last message), and one that starts with `--base-url=…`
15
+ * must not be read as the CLI's own flag. A `boolean` takes a following word only
16
+ * as the old parser did (a word that does not look like a flag); `optional` is a
17
+ * flag whose value may be left out (`trust --reset [device]`). A flag missing here
18
+ * is read the old way, and the test over COMMAND_FLAGS keeps a new one from
19
+ * missing.
20
+ */
21
+ exports.FLAG_KINDS = {
22
+ key: 'value', 'api-key': 'value', 'base-url': 'value', 'ws-url': 'value', 'web-url': 'value',
23
+ 'allow-host': 'value',
24
+ title: 'value', body: 'value', url: 'value', type: 'value', priority: 'value', device: 'value', session: 'value',
25
+ marker: 'value', tools: 'value', nonreadonly: 'value', 'pushmode-default': 'value', actions: 'value',
26
+ timeout: 'value', hook: 'value', only: 'value', limit: 'value', channel: 'value', turns: 'value',
27
+ at: 'value', days: 'value', every: 'value', prompt: 'value', label: 'value', wt: 'value', 'max-runtime': 'value',
28
+ json: 'boolean', version: 'boolean', help: 'boolean', relogin: 'boolean', service: 'boolean', 'no-service': 'boolean',
29
+ 'dry-run': 'boolean', purge: 'boolean', yes: 'boolean', ping: 'boolean', auto: 'boolean', clear: 'boolean',
30
+ all: 'boolean', ended: 'boolean', stop: 'boolean', restart: 'boolean', 'no-keep-awake': 'boolean',
31
+ 'no-turn-history': 'boolean', 'install-service': 'boolean', 'uninstall-service': 'boolean',
32
+ 'service-status': 'boolean', 'accepts-exit': 'boolean', global: 'boolean',
33
+ reset: 'optional',
34
+ };
35
+ /** A yes/no flag was given a value that is neither (`--dry-run=maybe`). */
36
+ class FlagValueError extends Error {
37
+ flag;
38
+ value;
39
+ constructor(flag, value) {
40
+ super(`--${flag} takes true or false (or nothing, which is true), not "${value}"`);
41
+ this.flag = flag;
42
+ this.value = value;
43
+ this.name = 'FlagValueError';
44
+ }
45
+ }
46
+ exports.FlagValueError = FlagValueError;
47
+ const ON_WORDS = new Set(['true', '1', 'yes']);
48
+ const OFF_WORDS = new Set(['false', '0', 'no']);
49
+ /**
50
+ * Is the yes/no flag `name` on? The one reading every consumer of such a flag shares: not given is
51
+ * off; given bare (`--dry-run`), `=true`, `=1`, `=yes` is on; `=false`, `=0`, `=no` is off; any
52
+ * other value stops with `FlagValueError`, because a flag read as "not given" is how
53
+ * `--dry-run=true` removed what it was asked to preview and `--no-turn-history=1` left the
54
+ * upload on. The boolean parser also takes the word after a flag as its value (`--dry-run yes`),
55
+ * and that word goes through the same reading. Words are matched without case.
56
+ * `lenient` reads a value that is neither as off instead of stopping, for the few places
57
+ * that must answer whatever the flags hold (the usage gate, an error report).
58
+ */
59
+ const flagOn = (args, name, lenient = false) => {
60
+ const value = args[name];
61
+ if (value === undefined || value === false)
62
+ return false;
63
+ if (value === true)
64
+ return true;
65
+ const word = value.trim().toLowerCase();
66
+ if (ON_WORDS.has(word))
67
+ return true;
68
+ if (OFF_WORDS.has(word))
69
+ return false;
70
+ if (lenient)
71
+ return false;
72
+ throw new FlagValueError(name, value);
73
+ };
74
+ exports.flagOn = flagOn;
75
+ /** The yes/no flags, from `FLAG_KINDS`. `trust --yes <device>` reads its own value and is not among those checked. */
76
+ const YES_NO_FLAGS = Object.keys(exports.FLAG_KINDS).filter((key) => exports.FLAG_KINDS[key] === 'boolean');
77
+ /** The flags and positional words of argv, and where in argv each positional word stands. */
78
+ const scanArgs = (argv) => {
79
+ const result = {};
80
+ const positional = [];
81
+ const positionalAt = [];
82
+ const args = argv.slice(2);
83
+ for (let i = 0; i < args.length; i++) {
84
+ // `-h` is `--help` before anything reads the words, so it is never a
85
+ // positional and never a flag's value.
86
+ const arg = args[i] === '-h' ? '--help' : args[i];
87
+ // `--` ends the flags: every word after it is a positional, as typed.
88
+ if (arg === '--') {
89
+ for (let j = i + 1; j < args.length; j++) {
90
+ positional.push(args[j]);
91
+ positionalAt.push(j + 2);
92
+ }
93
+ break;
94
+ }
95
+ if (!isFlagWord(arg)) {
96
+ positional.push(arg);
97
+ positionalAt.push(i + 2);
98
+ continue;
99
+ }
100
+ const eq = arg.indexOf('=');
101
+ if (eq > 0) {
102
+ result[arg.slice(2, eq)] = arg.slice(eq + 1);
103
+ continue;
104
+ }
105
+ const key = arg.slice(2);
106
+ const next = args[i + 1];
107
+ // `--help` never takes a value: `notify --help foo` and
108
+ // `uninstall --dry-run -h` both ask for help instead of running.
109
+ // A `value` flag takes a flag-looking word too; `-h`, `--help` and `--` stay what they are.
110
+ const takesAnyWord = Object.hasOwn(exports.FLAG_KINDS, key) && exports.FLAG_KINDS[key] === 'value' && next !== '--help';
111
+ if (key === 'help' || !next || next === '-h' || next === '--' || (isFlagWord(next) && !takesAnyWord)) {
112
+ result[key] = true;
113
+ }
114
+ else {
115
+ result[key] = next;
116
+ i++;
117
+ }
118
+ }
119
+ result._command = positional[0] ?? '';
120
+ result._arg1 = positional[1] ?? '';
121
+ result._arg2 = positional[2] ?? '';
122
+ return { result, positional, positionalAt };
123
+ };
124
+ const parseArgs = (argv) => scanArgs(argv).result;
125
+ exports.parseArgs = parseArgs;
126
+ /**
127
+ * The CLI's flags, and for `send` and `agent send` the words after it. They take everything
128
+ * after it as the target and the message, verbatim: its flags come from the
129
+ * part before it only, so a `--base-url` word in a message never redirects the
130
+ * API key and a `--version` never swallows the send. The one exception is a
131
+ * help flag where the target goes. The `send` is the positional word the flag
132
+ * parser itself reads as the command (`send`) or as the one right after `agent`
133
+ * — never a word that merely looks like it: a flag value can be spelled
134
+ * `send`, and a message can hold `agent send`.
135
+ */
136
+ const parseCliArgv = (argv) => {
137
+ const { result: whole, positional, positionalAt } = scanArgs(argv);
138
+ const at = positional[0] === 'send' ? positionalAt[0] : positional[0] === 'agent' && positional[1] === 'send' ? positionalAt[1] : -1;
139
+ if (at < 0)
140
+ return { args: whole, sendArgv: [] };
141
+ const args = (0, exports.parseArgs)(argv.slice(0, at + 1));
142
+ const sendArgv = argv.slice(at + 1);
143
+ // Where the target goes, a help flag can't be a target: `send --help` is a
144
+ // help request, not a message to a device named "--help".
145
+ if (sendArgv[0] === '-h' || sendArgv[0] === '--help')
146
+ args.help = true;
147
+ return { args, sendArgv };
148
+ };
149
+ exports.parseCliArgv = parseCliArgv;
150
+ const isHelpWord = (word) => word === '-h' || word === '--help';
151
+ /**
152
+ * Print usage instead of running anything. A help flag used to run the
153
+ * command itself, so `notify --help` sent a push and `uninstall --help`
154
+ * uninstalled. An agent subcommand (`hwip cc`) passes the flags after it to
155
+ * the agent untouched, so `hwip cc --help` is the agent's help — but a help
156
+ * flag before it is hwip's own.
157
+ */
158
+ const asksForUsage = (argv, args, isAgentCommand) => {
159
+ const command = args._command;
160
+ if (!command || command === 'help')
161
+ return true;
162
+ if (!(0, exports.flagOn)(args, 'help', true))
163
+ return false;
164
+ if (!isAgentCommand)
165
+ return true;
166
+ const at = argv.indexOf(String(command), 2);
167
+ return argv.slice(2, at).some(isHelpWord);
168
+ };
169
+ exports.asksForUsage = asksForUsage;
170
+ /** Flags every command accepts: `createHook` and the usage gate read them. */
171
+ exports.GLOBAL_FLAGS = ['key', 'base-url', 'json', 'version', 'help'];
172
+ /**
173
+ * The flags each command reads, besides the global ones. Commands missing here
174
+ * (agent subcommands, `sh`, `mcp`) are not checked: an agent subcommand hands
175
+ * the words after it to the agent, and `send`'s message is never parsed.
176
+ */
177
+ exports.COMMAND_FLAGS = {
178
+ install: ['hook', 'only', 'relogin', 'service', 'no-service', 'timeout', 'web-url', 'allow-host'],
179
+ login: ['timeout', 'web-url', 'allow-host'],
180
+ uninstall: ['dry-run', 'purge', 'yes'],
181
+ verify: ['ping', 'ws-url'],
182
+ 'check-update': [],
183
+ notify: [
184
+ 'title', 'body', 'url', 'type', 'priority', 'device', 'session',
185
+ 'auto', 'marker', 'pushmode-default', 'tools', 'nonreadonly', 'channel',
186
+ ],
187
+ channel: [],
188
+ ask: ['title', 'body', 'actions', 'timeout', 'hook', 'accepts-exit', 'api-key'],
189
+ 'ask-context': ['global'],
190
+ 'file-view': ['global'],
191
+ agent: ['json', 'turns'],
192
+ send: [],
193
+ list: ['limit', 'type'],
194
+ dismiss: ['all'],
195
+ rename: ['clear', 'device', 'session'],
196
+ forget: ['ended'],
197
+ worktree: ['dry-run'],
198
+ test: [],
199
+ trust: ['reset', 'yes'],
200
+ schedule: ['at', 'days', 'every', 'prompt', 'label', 'wt', 'max-runtime'],
201
+ listener: [
202
+ 'ws-url', 'stop', 'restart', 'no-keep-awake', 'no-turn-history',
203
+ 'install-service', 'uninstall-service', 'service-status',
204
+ ],
205
+ 'remote-hook': [],
206
+ };
207
+ /**
208
+ * Flags the command would ignore, in the order typed. A typo (`--titel`) used
209
+ * to vanish silently, and the push went out with the default title.
210
+ */
211
+ const unknownFlags = (args) => {
212
+ const command = String(args._command);
213
+ const table = command === 'setup' ? 'install' : command;
214
+ // Own keys only: `hwip constructor` must not find Object's.
215
+ if (!Object.hasOwn(exports.COMMAND_FLAGS, table))
216
+ return [];
217
+ const known = exports.COMMAND_FLAGS[table];
218
+ return Object.keys(args).filter((key) => !key.startsWith('_') && !exports.GLOBAL_FLAGS.includes(key) && !known.includes(key));
219
+ };
220
+ exports.unknownFlags = unknownFlags;
221
+ /**
222
+ * The first yes/no flag of a command that holds a value that is neither yes nor no, as the
223
+ * words to print, or null. Checked before the command runs: its handlers read the flags with
224
+ * `flagOn`, which stops on the same values, and by then work may be under way. A command that
225
+ * hands its words to an agent (or is not in `COMMAND_FLAGS`) is not checked, and neither is
226
+ * `trust --yes`, whose value is the device it names. `--allow-host` is checked here too (C132): a list that is
227
+ * no list of host names stops `login` and `install` before an agent picker or a browser, not after.
228
+ */
229
+ const badFlagValue = (args) => {
230
+ const command = String(args._command);
231
+ const table = command === 'setup' ? 'install' : command;
232
+ if (!Object.hasOwn(exports.COMMAND_FLAGS, table))
233
+ return null;
234
+ const read = [...exports.GLOBAL_FLAGS, ...exports.COMMAND_FLAGS[table]];
235
+ for (const name of YES_NO_FLAGS) {
236
+ if (!read.includes(name) || (table === 'trust' && name === 'yes'))
237
+ continue;
238
+ try {
239
+ (0, exports.flagOn)(args, name);
240
+ }
241
+ catch (err) {
242
+ if (err instanceof FlagValueError)
243
+ return err.message;
244
+ throw err;
245
+ }
246
+ }
247
+ if (read.includes('allow-host')) {
248
+ const parsed = (0, login_address_js_1.parseAllowHosts)(args['allow-host']);
249
+ if ('error' in parsed)
250
+ return parsed.error;
251
+ }
252
+ return null;
253
+ };
254
+ exports.badFlagValue = badFlagValue;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The attachment a sealed question carries (F3): the original of its context — the masked diff and screen of
3
+ * `ask-context.ts` — sealed for the devices the question is sealed for.
4
+ *
5
+ * What the server sees of it is the same for every sealed question: one attachment, named `file`, of type
6
+ * `application/octet-stream`, 64 KiB long. A question with nothing to carry (the switch is off, no change and
7
+ * no screen, git did not answer) gets the same attachment with nothing in it, so neither whether a question has
8
+ * context nor how much the session changed shows. The real name, type and length are in the sealed body.
9
+ */
10
+ import { type AskContext } from './ask-context.js';
11
+ import { type AskContextOffBy } from './ask-context-switch.js';
12
+ import type { AskAttachment } from './ask-seal.js';
13
+ import { encryptFileForDevices, type DeviceRecipient, type SealedAskContext } from './crypto.js';
14
+ /** Every attachment is this long before it is sealed: the longest original, so none needs more. */
15
+ export declare const ASK_ATTACHMENT_BYTES: number;
16
+ /** With less of the question's window left than this, nothing is gathered: git and tmux are asked synchronously and may take most of it. */
17
+ export declare const ASK_CONTEXT_MIN_WINDOW_MS = 20000;
18
+ /** What attaching leaves of the question's window, for the question itself to be sent and answered. */
19
+ export declare const ASK_ATTACH_RESERVE_MS = 10000;
20
+ /**
21
+ * The original as JSON (`{ diff, screen }`, UTF-8) followed by zero bytes up to `ASK_ATTACHMENT_BYTES`; `size`
22
+ * is where the JSON ends. No context, or an original that does not fit, is all zeros and `size` 0 — and no summary.
23
+ */
24
+ export declare const padAskOriginal: (ctx: AskContext | null) => {
25
+ bytes: Buffer;
26
+ size: number;
27
+ context?: SealedAskContext;
28
+ };
29
+ export interface AttachDeps {
30
+ /** The context to carry, or `null`; a throw counts as `null`. */
31
+ context: () => Promise<AskContext | null>;
32
+ encrypt: typeof encryptFileForDevices;
33
+ /** Stores the sealed bytes and returns their `fileKey`. */
34
+ upload: (sealed: Buffer) => Promise<string>;
35
+ }
36
+ /** Seals and uploads the attachment for `recipients`. Throws when sealing or the upload fails. */
37
+ export declare const attachAskContext: (recipients: DeviceRecipient[], deps: AttachDeps) => Promise<AskAttachment>;
38
+ /**
39
+ * `attachAskContext`, given up (it rejects, and the question goes without it) once it would eat into the last
40
+ * `ASK_ATTACH_RESERVE_MS` of a window of `windowMs`: storing the attachment must never hold the question back.
41
+ * The race cannot cut the synchronous git and tmux reads short; `ASK_CONTEXT_MIN_WINDOW_MS` bounds those.
42
+ */
43
+ export declare const attachWithinWindow: (recipients: DeviceRecipient[], deps: AttachDeps, windowMs: number) => Promise<AskAttachment>;
44
+ export interface CollectDeps {
45
+ offBy: (projectDir: string, env: NodeJS.ProcessEnv) => AskContextOffBy | null;
46
+ /** The screen of a tmux pane, by pane id. */
47
+ capture: (pane: string) => Promise<string | null>;
48
+ gather: (cwd: string, screen: string | null) => AskContext | null;
49
+ }
50
+ /**
51
+ * The context of the session asking from `cwd`: its changes, and the screen of the pane this process runs in
52
+ * (`TMUX_PANE`; none outside tmux). `off` says why the switch kept it from being read. `windowMs` is what is
53
+ * left of the question's window.
54
+ */
55
+ export declare const collectAskContext: (cwd: string, env: NodeJS.ProcessEnv, deps?: CollectDeps, windowMs?: number) => Promise<{
56
+ context: AskContext | null;
57
+ off?: AskContextOffBy;
58
+ }>;
59
+ /** Stores sealed attachment bytes with the account and returns their `fileKey`. Throws on any failure. */
60
+ export declare const uploadAskAttachment: (sealed: Buffer, account: {
61
+ apiKey: string;
62
+ baseUrl: string;
63
+ }, fetchFn?: typeof fetch) => Promise<string>;
64
+ /**
65
+ * `AskSealDeps.attach` for real use: this directory's context, sealed with this machine's keys, stored with the
66
+ * account, within what is left before `deadline`. What goes wrong is said on stderr (stdout is the answer's): a
67
+ * context that could not be gathered goes as the empty attachment, one that could not be stored not at all.
68
+ */
69
+ export declare const liveAttach: (account: {
70
+ apiKey: string;
71
+ baseUrl: string;
72
+ }, deadline: number, warn: (line: string) => void) => (recipients: DeviceRecipient[]) => Promise<AskAttachment>;
73
+ //# sourceMappingURL=ask-context-attach.d.ts.map
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ /**
3
+ * The attachment a sealed question carries (F3): the original of its context — the masked diff and screen of
4
+ * `ask-context.ts` — sealed for the devices the question is sealed for.
5
+ *
6
+ * What the server sees of it is the same for every sealed question: one attachment, named `file`, of type
7
+ * `application/octet-stream`, 64 KiB long. A question with nothing to carry (the switch is off, no change and
8
+ * no screen, git did not answer) gets the same attachment with nothing in it, so neither whether a question has
9
+ * context nor how much the session changed shows. The real name, type and length are in the sealed body.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.liveAttach = exports.uploadAskAttachment = exports.collectAskContext = exports.attachWithinWindow = exports.attachAskContext = exports.padAskOriginal = exports.ASK_ATTACH_RESERVE_MS = exports.ASK_CONTEXT_MIN_WINDOW_MS = exports.ASK_ATTACHMENT_BYTES = void 0;
13
+ const api_js_1 = require("./api.js");
14
+ const ask_context_js_1 = require("./ask-context.js");
15
+ const ask_context_screen_js_1 = require("./ask-context-screen.js");
16
+ const ask_context_switch_js_1 = require("./ask-context-switch.js");
17
+ const crypto_js_1 = require("./crypto.js");
18
+ /** Every attachment is this long before it is sealed: the longest original, so none needs more. */
19
+ exports.ASK_ATTACHMENT_BYTES = ask_context_js_1.ASK_CONTEXT_MAX_BYTES;
20
+ /** What the server is told the attachment is. */
21
+ const WIRE_NAME = 'file';
22
+ const WIRE_TYPE = 'application/octet-stream';
23
+ /** What the sealed body says it is; a reader finds the original by this type. */
24
+ const SEALED_NAME = 'ask-context.json';
25
+ const SEALED_TYPE = 'application/vnd.hwip.ask-context+json';
26
+ /** With less of the question's window left than this, nothing is gathered: git and tmux are asked synchronously and may take most of it. */
27
+ exports.ASK_CONTEXT_MIN_WINDOW_MS = 20_000;
28
+ /** What attaching leaves of the question's window, for the question itself to be sent and answered. */
29
+ exports.ASK_ATTACH_RESERVE_MS = 10_000;
30
+ /** A window this short is a choice (a quick gate), not a failure: it is not warned about. */
31
+ const NO_ROOM = 'too little of the window left to attach the context';
32
+ const UPLOAD_TIMEOUT_MS = 10_000;
33
+ /**
34
+ * The original as JSON (`{ diff, screen }`, UTF-8) followed by zero bytes up to `ASK_ATTACHMENT_BYTES`; `size`
35
+ * is where the JSON ends. No context, or an original that does not fit, is all zeros and `size` 0 — and no summary.
36
+ */
37
+ const padAskOriginal = (ctx) => {
38
+ const bytes = Buffer.alloc(exports.ASK_ATTACHMENT_BYTES);
39
+ if (!ctx)
40
+ return { bytes, size: 0 };
41
+ const text = Buffer.from(JSON.stringify(ctx.original), 'utf-8');
42
+ if (text.length > exports.ASK_ATTACHMENT_BYTES)
43
+ return { bytes, size: 0 };
44
+ text.copy(bytes);
45
+ return { bytes, size: text.length, context: ctx.summary };
46
+ };
47
+ exports.padAskOriginal = padAskOriginal;
48
+ /** Seals and uploads the attachment for `recipients`. Throws when sealing or the upload fails. */
49
+ const attachAskContext = async (recipients, deps) => {
50
+ // No context and a context that failed look the same to the server on purpose; `liveAttach` says which on stderr.
51
+ const padded = (0, exports.padAskOriginal)(await deps.context().catch(() => null));
52
+ const sealed = await deps.encrypt(padded.bytes, recipients);
53
+ const fileKey = await deps.upload(sealed.ciphertext);
54
+ return {
55
+ ...(padded.context ? { context: padded.context } : {}),
56
+ meta: { iv: sealed.iv, name: SEALED_NAME, type: SEALED_TYPE, size: padded.size },
57
+ file: { fileKey, fileName: WIRE_NAME, fileType: WIRE_TYPE, fileSize: exports.ASK_ATTACHMENT_BYTES, iv: sealed.iv, deviceKeyMap: sealed.deviceKeyMap },
58
+ };
59
+ };
60
+ exports.attachAskContext = attachAskContext;
61
+ /**
62
+ * `attachAskContext`, given up (it rejects, and the question goes without it) once it would eat into the last
63
+ * `ASK_ATTACH_RESERVE_MS` of a window of `windowMs`: storing the attachment must never hold the question back.
64
+ * The race cannot cut the synchronous git and tmux reads short; `ASK_CONTEXT_MIN_WINDOW_MS` bounds those.
65
+ */
66
+ const attachWithinWindow = (recipients, deps, windowMs) => {
67
+ const room = windowMs - exports.ASK_ATTACH_RESERVE_MS;
68
+ if (room <= 0)
69
+ return Promise.reject(new Error(NO_ROOM));
70
+ let timer;
71
+ const late = new Promise((_, reject) => {
72
+ timer = setTimeout(() => reject(new Error(`attaching the context took over ${room} ms`)), room);
73
+ });
74
+ return Promise.race([(0, exports.attachAskContext)(recipients, deps), late]).finally(() => clearTimeout(timer));
75
+ };
76
+ exports.attachWithinWindow = attachWithinWindow;
77
+ const LIVE_COLLECT = {
78
+ offBy: (dir, env) => (0, ask_context_switch_js_1.askContextOffBy)((0, ask_context_switch_js_1.askContextProjectDir)(dir), env),
79
+ // The id is the target as it is: a session name would be matched by prefix and could name another session's pane.
80
+ capture: async (pane) => (0, ask_context_screen_js_1.captureAskScreen)(pane, { ...(await (0, ask_context_screen_js_1.liveScreenDeps)()), targetOf: (id) => id }),
81
+ gather: ask_context_js_1.gatherAskContext,
82
+ };
83
+ /** A tmux pane id, as tmux sets it in `TMUX_PANE` for what runs in the pane. */
84
+ const PANE_ID = /^%\d+$/;
85
+ /**
86
+ * The context of the session asking from `cwd`: its changes, and the screen of the pane this process runs in
87
+ * (`TMUX_PANE`; none outside tmux). `off` says why the switch kept it from being read. `windowMs` is what is
88
+ * left of the question's window.
89
+ */
90
+ const collectAskContext = async (cwd, env, deps = LIVE_COLLECT, windowMs = Infinity) => {
91
+ const off = deps.offBy(cwd, env);
92
+ if (off !== null)
93
+ return { context: null, off };
94
+ if (windowMs < exports.ASK_CONTEXT_MIN_WINDOW_MS)
95
+ return { context: null };
96
+ const pane = env.TMUX_PANE;
97
+ const screen = pane !== undefined && PANE_ID.test(pane) ? await deps.capture(pane) : null;
98
+ return { context: deps.gather(cwd, screen) };
99
+ };
100
+ exports.collectAskContext = collectAskContext;
101
+ /** Stores sealed attachment bytes with the account and returns their `fileKey`. Throws on any failure. */
102
+ const uploadAskAttachment = async (sealed, account, fetchFn = fetch) => {
103
+ const res = await (0, api_js_1.apiFetch)(fetchFn, (0, api_js_1.apiUrl)(account.baseUrl, '/files/upload-request'), account.apiKey, UPLOAD_TIMEOUT_MS, {
104
+ method: 'POST',
105
+ body: JSON.stringify({ fileName: WIRE_NAME, fileType: WIRE_TYPE, fileSize: sealed.length }),
106
+ });
107
+ if (!res.ok)
108
+ throw new Error(`upload-request ${res.status}`);
109
+ const json = (await res.json());
110
+ const { uploadUrl, fileKey } = json.data ?? {};
111
+ if (typeof uploadUrl !== 'string' || typeof fileKey !== 'string')
112
+ throw new Error('upload-request: unexpected response');
113
+ // The address is signed for this content type and authenticates itself: no API key goes to the store.
114
+ const put = await fetchFn(uploadUrl, {
115
+ method: 'PUT',
116
+ headers: { 'Content-Type': WIRE_TYPE },
117
+ body: new Uint8Array(sealed),
118
+ signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS),
119
+ redirect: 'error',
120
+ });
121
+ if (!put.ok)
122
+ throw new Error(`upload ${put.status}`);
123
+ return fileKey;
124
+ };
125
+ exports.uploadAskAttachment = uploadAskAttachment;
126
+ /**
127
+ * `AskSealDeps.attach` for real use: this directory's context, sealed with this machine's keys, stored with the
128
+ * account, within what is left before `deadline`. What goes wrong is said on stderr (stdout is the answer's): a
129
+ * context that could not be gathered goes as the empty attachment, one that could not be stored not at all.
130
+ */
131
+ const liveAttach = (account, deadline, warn) => (recipients) => {
132
+ const windowMs = deadline - Date.now();
133
+ return (0, exports.attachWithinWindow)(recipients, {
134
+ context: async () => {
135
+ try {
136
+ return (await (0, exports.collectAskContext)(process.cwd(), process.env, LIVE_COLLECT, windowMs)).context;
137
+ }
138
+ catch (err) {
139
+ warn(`the context could not be gathered (${err.message}); the question carries none`);
140
+ return null;
141
+ }
142
+ },
143
+ encrypt: crypto_js_1.encryptFileForDevices,
144
+ upload: (sealed) => (0, exports.uploadAskAttachment)(sealed, account),
145
+ }, windowMs).catch((err) => {
146
+ if (err.message !== NO_ROOM)
147
+ warn(`the context was not attached (${err.message}); the question goes without it`);
148
+ throw err;
149
+ });
150
+ };
151
+ exports.liveAttach = liveAttach;
@@ -0,0 +1,21 @@
1
+ export interface ScreenDeps {
2
+ /** The tmux target for a session name; `null` when the name resolves to no pane. */
3
+ targetOf: (session: string) => string | null;
4
+ /** Runs tmux with these arguments (the socket flag is the caller's). */
5
+ tmux: (args: string[]) => {
6
+ status: number | null;
7
+ stdout: string;
8
+ };
9
+ }
10
+ /**
11
+ * The last `ASK_CONTEXT_SCREEN_WINDOW_LINES` lines of the session's pane (history included), or `null`. `-J` joins
12
+ * a line the pane wrapped: masking works line by line, and a token wider than the pane would otherwise be cut into
13
+ * rows, each too short to be read as a token, with its tail left in the clear.
14
+ */
15
+ export declare const captureAskScreen: (session: string, deps: ScreenDeps) => string | null;
16
+ /**
17
+ * The real tmux. The listener's log goes quiet first: its tmux socket search prints to stdout, and a
18
+ * `hwip ask` answer owns stdout (the same step `liveLocalAgentDeps` takes).
19
+ */
20
+ export declare const liveScreenDeps: () => Promise<ScreenDeps>;
21
+ //# sourceMappingURL=ask-context-screen.d.ts.map
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.liveScreenDeps = exports.captureAskScreen = void 0;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const ask_context_js_1 = require("./ask-context.js");
6
+ /**
7
+ * The terminal screen of a session as plain text, for `gatherAskContext`'s `screen` argument. It is the
8
+ * listener's own `capture-pane` read (tmux `capture-pane -p`, no escapes) as a small function of its own, so
9
+ * `listener.ts` stays as it is. Nothing is masked here — `composeAskContext` does that, once, over what this
10
+ * returns. Nothing is sent, and a pane that cannot be read is `null`, never an error.
11
+ */
12
+ /** The capture that does not answer within this is given up on; a hung server must not hold up a question. It does not cover the socket search `tmuxArgs` does first (the listener's own, with its own probes). */
13
+ const CAPTURE_TIMEOUT_MS = 3_000;
14
+ /** Longest capture that is read; a pane wider or taller than this is not a screen worth sending whole. */
15
+ const CAPTURE_MAX_BYTES = 1024 * 1024;
16
+ /**
17
+ * The last `ASK_CONTEXT_SCREEN_WINDOW_LINES` lines of the session's pane (history included), or `null`. `-J` joins
18
+ * a line the pane wrapped: masking works line by line, and a token wider than the pane would otherwise be cut into
19
+ * rows, each too short to be read as a token, with its tail left in the clear.
20
+ */
21
+ const captureAskScreen = (session, deps) => {
22
+ const target = deps.targetOf(session);
23
+ if (target === null)
24
+ return null;
25
+ const run = deps.tmux(['capture-pane', '-p', '-J', '-t', target, '-S', `-${ask_context_js_1.ASK_CONTEXT_SCREEN_WINDOW_LINES}`]);
26
+ return run.status === 0 ? run.stdout : null;
27
+ };
28
+ exports.captureAskScreen = captureAskScreen;
29
+ /**
30
+ * The real tmux. The listener's log goes quiet first: its tmux socket search prints to stdout, and a
31
+ * `hwip ask` answer owns stdout (the same step `liveLocalAgentDeps` takes).
32
+ */
33
+ const liveScreenDeps = async () => {
34
+ const { silenceListenerLog, tmuxArgs, tmuxTargetFor } = await import('./listener.js');
35
+ silenceListenerLog();
36
+ return {
37
+ targetOf: tmuxTargetFor,
38
+ tmux: (args) => {
39
+ const r = (0, node_child_process_1.spawnSync)('tmux', tmuxArgs(args), {
40
+ encoding: 'utf-8',
41
+ stdio: ['ignore', 'pipe', 'ignore'],
42
+ timeout: CAPTURE_TIMEOUT_MS,
43
+ maxBuffer: CAPTURE_MAX_BYTES,
44
+ });
45
+ return { status: r.error ? null : r.status, stdout: r.stdout ?? '' };
46
+ },
47
+ };
48
+ };
49
+ exports.liveScreenDeps = liveScreenDeps;
@@ -0,0 +1,31 @@
1
+ import { type SwitchOffBy } from './project-switch.js';
2
+ /**
3
+ * The switch for the context a sealed question may carry (`ask-context.ts`): one state file per project and one
4
+ * for every project. Both are local files in the state directory (`gate.ts`'s, beside `muted-<hash>`) — the
5
+ * switch never reaches the server or the wire. No file means on: the context is automatic for an account with
6
+ * E2E on (the user's decision), and this is how a project or the whole machine opts out.
7
+ */
8
+ export type AskContextOffBy = SwitchOffBy;
9
+ /** The project a question belongs to: the repository root, else the directory itself, so a question asked from a subfolder reads the same switch. */
10
+ export declare const askContextProjectDir: (cwd: string, repoRoot?: (cwd: string) => string | null) => string;
11
+ /** Why the context is off for this project, or `null` when it is on. `HWIP_ASK_CONTEXT=0` turns it off for one run. */
12
+ export declare const askContextOffBy: (dir: string, env?: NodeJS.ProcessEnv) => SwitchOffBy | null;
13
+ export declare const askContextEnabled: (dir: string, env?: NodeJS.ProcessEnv) => boolean;
14
+ /** Turn the context on or off for this project, or for every project. Throws when the state file cannot be written or removed. */
15
+ export declare const setAskContext: (dir: string, on: boolean, scope: import("./project-switch.js").SwitchScope) => void;
16
+ export declare const describeAskContext: (offBy: AskContextOffBy | null) => string;
17
+ export interface AskContextCommandDeps {
18
+ cwd: () => string;
19
+ out: (line: string) => void;
20
+ err: (line: string) => void;
21
+ env: NodeJS.ProcessEnv;
22
+ repoRoot: (cwd: string) => string | null;
23
+ }
24
+ export declare const runAskContext: (sub: string | undefined, global: boolean, deps: AskContextCommandDeps) => number;
25
+ /**
26
+ * `show` prints, as one JSON line, what a question asked from this directory would carry: `{ context, off? }`
27
+ * (`collectAskContext`). It reads the switch itself, so a caller that only seals (mcp-server's `hwip_ask`) needs
28
+ * no switch logic of its own. Nothing is sent.
29
+ */
30
+ export declare const handleAskContext: (args: Record<string, string | boolean>) => Promise<number>;
31
+ //# sourceMappingURL=ask-context-switch.d.ts.map