@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
package/dist/gate.js ADDED
@@ -0,0 +1,336 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.autoPushMode = exports.readPushMode = exports.NONREADONLY_COUNT_FLAG = exports.TOOL_COUNT_FLAG = exports.PUSHMODE_DEFAULT_FLAG = exports.PUSHMODE_DEFAULT = exports.isMuted = exports.clearRemoteActive = exports.touchRemoteActive = exports.isRemoteActive = exports.remoteStatePath = exports.REMOTE_TTL_SEC = exports.remoteDigest = exports.remoteMarkerPath = exports.projectHash = exports.posixCksum = exports.stateDir = exports.decidePush = exports.normalizePushMode = exports.normalizeMarker = exports.GATE_DEFAULTS = void 0;
4
+ /**
5
+ * Push-gate decision — the portable half of the Hwip Stop-hook logic.
6
+ *
7
+ * `decidePush` is the TS twin of `plugin/hooks/gate.sh` (hwipit/plugin).
8
+ * Both implementations are locked to the same semantics by the shared
9
+ * vector file `src/fixtures/gate-vectors.json` (vendored from the plugin
10
+ * repo's canonical copy via `npm run sync:plugin`): the bash side and this
11
+ * side run the exact same cases in their CIs, so a semantic change to one
12
+ * that isn't mirrored in the other fails a build. Edit them together.
13
+ *
14
+ * Ordering is contractual (encoded as named vectors):
15
+ * 1. alreadyAsked wins over EVERYTHING — even loud (dedup beats the dial).
16
+ * 2. priority is high iff marker === 'high', decided BEFORE the mode
17
+ * switch, so quiet+high and loud+high both push at high priority.
18
+ * 3. quiet → a high marker or an away user pushes; loud → always push; normal →
19
+ * marker overrides the heuristic (skip → silent, push/high → push),
20
+ * no marker → push iff toolCount ≥ 2 AND nonReadonlyCount > 0
21
+ * (the B1 read-only floor).
22
+ */
23
+ const crypto_1 = require("crypto");
24
+ const fs_1 = require("fs");
25
+ const os_1 = require("os");
26
+ const path_1 = require("path");
27
+ const private_files_js_1 = require("./private-files.js");
28
+ /**
29
+ * "Assume real work" defaults for hooks that can't supply turn facts
30
+ * (most non-Claude agents pass no counts): in normal mode the push still
31
+ * fires — preserving the historical always-push behavior of the dumb
32
+ * hooks — while quiet/loud now work everywhere.
33
+ *
34
+ * These defaults cannot rescue a quiet dial: quiet only lets a `high` marker
35
+ * or an away user (presence.ts) through, and a hook with no turn facts has no
36
+ * marker either. That is why the installed templates pass `--pushmode-default
37
+ * normal` (see templates.ts) — for them quiet is not a lower volume, it is
38
+ * silence while the user is at the terminal.
39
+ */
40
+ exports.GATE_DEFAULTS = {
41
+ toolCount: 2,
42
+ nonReadonlyCount: 1,
43
+ alreadyAsked: false,
44
+ };
45
+ const normalizeMarker = (raw) => raw === 'skip' || raw === 'push' || raw === 'high' ? raw : 'none';
46
+ exports.normalizeMarker = normalizeMarker;
47
+ const normalizePushMode = (raw) => raw === 'quiet' || raw === 'loud' ? raw : 'normal';
48
+ exports.normalizePushMode = normalizePushMode;
49
+ const decidePush = (input) => {
50
+ if (input.alreadyAsked)
51
+ return { push: false, priority: 'normal' };
52
+ const priority = input.marker === 'high' ? 'high' : 'normal';
53
+ if (input.pushMode === 'quiet')
54
+ return { push: input.marker === 'high' || input.away === true, priority };
55
+ if (input.pushMode === 'loud')
56
+ return { push: true, priority };
57
+ if (input.marker === 'skip')
58
+ return { push: false, priority };
59
+ if (input.marker === 'push' || input.marker === 'high')
60
+ return { push: true, priority };
61
+ return { push: input.toolCount >= 2 && input.nonReadonlyCount > 0, priority };
62
+ };
63
+ exports.decidePush = decidePush;
64
+ // ── Per-project gate state (mute + push-mode dial) ───────────────
65
+ //
66
+ // State lives under ${XDG_STATE_HOME:-~/.local/state}/hwip — a per-user
67
+ // directory. It used to live at predictable names in world-writable /tmp,
68
+ // where any local user could pre-create a victim's mute file (and sticky
69
+ // /tmp makes that file un-deletable by the victim). Legacy /tmp files are
70
+ // still honored during the migration window, but only when owned by the
71
+ // current user, which neutralizes planted files.
72
+ //
73
+ // The plugin's bash hooks key these files off `cksum` of the project dir
74
+ // (`printf '%s' "$dir" | cksum`). `posixCksum` is that CRC computed in
75
+ // process — a notify used to spawn `cksum` up to ~8 times — and the vector
76
+ // test pins it to the real `cksum`, so every already-written file keeps its name.
77
+ const stateDir = () => (0, path_1.join)(process.env.XDG_STATE_HOME || (0, path_1.join)((0, os_1.homedir)(), '.local', 'state'), 'hwip');
78
+ exports.stateDir = stateDir;
79
+ const ownedByCurrentUser = (path) => {
80
+ try {
81
+ const st = (0, fs_1.statSync)(path);
82
+ return typeof process.getuid !== 'function' || st.uid === process.getuid();
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ };
88
+ /**
89
+ * Resolve a state file: current location first, then user-owned legacy /tmp,
90
+ * then — for `pushmode` only — the machine-wide default written by
91
+ * `/hwip-mode quiet --global`. Mirrors plugin/hooks/gate.sh hwip_state_present,
92
+ * including the deliberate absence of a global mute (see the comment there).
93
+ */
94
+ const findStateFile = (kind, hash) => {
95
+ const current = (0, path_1.join)((0, exports.stateDir)(), `${kind}-${hash}`);
96
+ if ((0, fs_1.existsSync)(current))
97
+ return current;
98
+ const legacy = `/tmp/hwip-${kind}-${hash}`;
99
+ if (ownedByCurrentUser(legacy))
100
+ return legacy;
101
+ if (kind !== 'pushmode')
102
+ return null;
103
+ const globalDefault = (0, path_1.join)((0, exports.stateDir)(), 'pushmode-default');
104
+ return (0, fs_1.existsSync)(globalDefault) ? globalDefault : null;
105
+ };
106
+ /** CRC table for POSIX `cksum`: polynomial 0x04C11DB7, most significant bit first. */
107
+ const CKSUM_TABLE = (() => {
108
+ const table = new Uint32Array(256);
109
+ for (let i = 0; i < 256; i++) {
110
+ let c = i << 24;
111
+ for (let k = 0; k < 8; k++)
112
+ c = c & 0x80000000 ? (c << 1) ^ 0x04c11db7 : c << 1;
113
+ table[i] = c >>> 0;
114
+ }
115
+ return table;
116
+ })();
117
+ /**
118
+ * The first field of POSIX `cksum` over `data`: the CRC of the bytes, then of
119
+ * the length (low byte first, no trailing zero bytes), complemented.
120
+ */
121
+ const posixCksum = (data) => {
122
+ let crc = 0;
123
+ const step = (byte) => {
124
+ crc = ((crc << 8) ^ CKSUM_TABLE[((crc >>> 24) ^ byte) & 0xff]) >>> 0;
125
+ };
126
+ for (const byte of data)
127
+ step(byte);
128
+ for (let n = data.length; n > 0; n = Math.floor(n / 256))
129
+ step(n & 0xff);
130
+ return ~crc >>> 0;
131
+ };
132
+ exports.posixCksum = posixCksum;
133
+ /** `cksum` of the project dir as the plugin computes it. */
134
+ const projectHash = (dir) => String((0, exports.posixCksum)(Buffer.from(dir, 'utf-8')));
135
+ exports.projectHash = projectHash;
136
+ // ── Remote-origin marker (ADR-0002) ──────────────────────────────
137
+ //
138
+ // The listener records every phone→pane text injection as a one-shot
139
+ // marker file; a prompt-submit hook (Claude Code plugin's hwip-remote.sh,
140
+ // or `hwip remote-hook` for Gemini/Codex) consumes it on an exact-text
141
+ // match and flags the prompt as remote-originated. Writer and TS reader
142
+ // share these two helpers so their bytes can never diverge; the bash
143
+ // reader is held to the same semantics by the plugin's test suite.
144
+ /** Marker path for a project hash: `<stateDir>/remote-<cksum(projectDir)>`. */
145
+ const remoteMarkerPath = (hash) => (0, path_1.join)((0, exports.stateDir)(), `remote-${hash}`);
146
+ exports.remoteMarkerPath = remoteMarkerPath;
147
+ /**
148
+ * sha256 over the ASCII-only-trimmed text — recorded at inject time,
149
+ * recomputed at prompt submit. Trim strips ONLY ' \t\r\n\f\v', NOT
150
+ * String.trim(): trim() also eats Unicode whitespace (U+00A0 etc.) that
151
+ * the bash reader keeps, and both sides must hash identical bytes.
152
+ */
153
+ const remoteDigest = (text) => (0, crypto_1.createHash)('sha256')
154
+ .update(text.replace(/^[ \t\r\n\f\v]+|[ \t\r\n\f\v]+$/g, ''))
155
+ .digest('hex');
156
+ exports.remoteDigest = remoteDigest;
157
+ // ── Sticky REMOTE state ──────────────────────────────────────────
158
+ //
159
+ // The marker above is a ONE-SHOT entry signal, consumed the moment a prompt
160
+ // matches it. REMOTE outlives that turn — the user can answer from the phone
161
+ // and then type at the terminal — so the mode itself lives in
162
+ // `remote-active-<hash>`, holding the epoch second it was last confirmed.
163
+ // Keeping it in a file is also what lets it survive context compaction.
164
+ // Bash twin: plugin/hooks/gate.sh hwip_remote_active / hwip_remote_touch.
165
+ //
166
+ // Deliberately not routed through findStateFile. That helper also honors a
167
+ // legacy /tmp copy so files written by older versions keep working; this kind
168
+ // has never had a /tmp writer, so the branch could only ever match something
169
+ // stale — and since the refresh always writes the XDG path, such a file would
170
+ // never expire.
171
+ /**
172
+ * How long REMOTE stays live without a refresh. Generous on purpose: the state
173
+ * is refreshed on every phone prompt and every answered `hwip_ask`, so it only
174
+ * has to outlive a working session, never an idle user. It is the backstop and
175
+ * not the usual exit — a terminal-typed prompt, a Done-like answer and the
176
+ * model's `<!-- hwip: exit -->` all delete the file outright. What none of them
177
+ * covers is a session that simply died: there is no SessionEnd hook, so without
178
+ * a TTL a crash or Ctrl-C would latch REMOTE forever.
179
+ */
180
+ exports.REMOTE_TTL_SEC = 14400;
181
+ /** State path for a project hash: `<stateDir>/remote-active-<cksum(dir)>`. */
182
+ const remoteStatePath = (hash) => (0, path_1.join)((0, exports.stateDir)(), `remote-active-${hash}`);
183
+ exports.remoteStatePath = remoteStatePath;
184
+ /**
185
+ * True while REMOTE is live for this project. A time ahead of the clock counts
186
+ * as now (and is rewritten as now). State that is expired or
187
+ * unparseable is deleted on sight — the same housekeeping a stale entry
188
+ * marker gets, since state that can never flag again is dead weight.
189
+ */
190
+ const isRemoteActive = (dir, now = Date.now) => {
191
+ const hash = (0, exports.projectHash)(dir);
192
+ const file = (0, exports.remoteStatePath)(hash);
193
+ let raw;
194
+ try {
195
+ // The first line, without the spaces around it: what `read -r ts` hands the bash twin.
196
+ raw = ((0, fs_1.readFileSync)(file, 'utf-8').split('\n')[0] ?? '').replace(/^[ \t]+|[ \t]+$/g, '');
197
+ }
198
+ catch {
199
+ return false;
200
+ }
201
+ // A plain number of at most 12 digits, matching the bash twin (`hwip_age_sec`): digits only
202
+ // (Number() alone reads '1e10' as ten billion, so a file bash would sweep would live on here as a
203
+ // far-future timestamp), and nothing longer is a time (year 33658). A leading 0 is decimal.
204
+ const ageSec = /^\d{1,12}$/.test(raw) ? Math.floor(now() / 1000) - Number(raw) : null;
205
+ const sweep = () => {
206
+ try {
207
+ (0, fs_1.unlinkSync)(file);
208
+ }
209
+ catch {
210
+ /* best-effort housekeeping */
211
+ }
212
+ };
213
+ if (ageSec === null || ageSec > exports.REMOTE_TTL_SEC) {
214
+ sweep();
215
+ return false;
216
+ }
217
+ if (ageSec < 0) {
218
+ // A time ahead of the clock (it was set back, or the file is damaged) reads as now, and now is written
219
+ // back: left alone the file would stay live until the clock caught up with it, however far ahead it is.
220
+ // If now cannot be written (a read-only state folder) nothing would ever end it: it is not REMOTE.
221
+ if (!writeRemoteNow(dir, now)) {
222
+ sweep();
223
+ return false;
224
+ }
225
+ }
226
+ return true;
227
+ };
228
+ exports.isRemoteActive = isRemoteActive;
229
+ /**
230
+ * Enter REMOTE, or push its expiry back. Best-effort by contract: the prompt
231
+ * hooks that call this must never fail a prompt over state IO.
232
+ */
233
+ const touchRemoteActive = (dir, now = Date.now) => {
234
+ writeRemoteNow(dir, now);
235
+ };
236
+ exports.touchRemoteActive = touchRemoteActive;
237
+ /** The state file holds now; false when it could not be written. */
238
+ const writeRemoteNow = (dir, now) => {
239
+ const hash = (0, exports.projectHash)(dir);
240
+ try {
241
+ (0, private_files_js_1.mkdirHandedBack)((0, exports.stateDir)());
242
+ // Before the write, so a file root makes is the sudo user's from the start (and the write follows it).
243
+ (0, private_files_js_1.createdHandedBack)((0, exports.remoteStatePath)(hash));
244
+ (0, fs_1.writeFileSync)((0, exports.remoteStatePath)(hash), `${Math.floor(now() / 1000)}\n`);
245
+ return true;
246
+ }
247
+ catch {
248
+ /* a hook must never fail on state IO */
249
+ return false;
250
+ }
251
+ };
252
+ /**
253
+ * Leave REMOTE. Best-effort like its twin: an absent file is success, and the
254
+ * caller is a hook with its own job to finish. Bash twin: gate.sh
255
+ * hwip_remote_clear.
256
+ */
257
+ const clearRemoteActive = (dir) => {
258
+ const hash = (0, exports.projectHash)(dir);
259
+ try {
260
+ (0, fs_1.unlinkSync)((0, exports.remoteStatePath)(hash));
261
+ }
262
+ catch {
263
+ /* already gone, or unwritable — the prompt still goes through */
264
+ }
265
+ };
266
+ exports.clearRemoteActive = clearRemoteActive;
267
+ /** True when the user ran /hwip-mode mute for this project. */
268
+ const isMuted = (dir) => {
269
+ const hash = (0, exports.projectHash)(dir);
270
+ return findStateFile('muted', hash) !== null;
271
+ };
272
+ exports.isMuted = isMuted;
273
+ /**
274
+ * Push mode for an install that has never set a dial. The twin is
275
+ * plugin/hooks/gate.sh's missing-5th-argument default; the shared vectors
276
+ * never reach either one (every vector passes pushMode explicitly), so both
277
+ * sides pin it in their own tests.
278
+ */
279
+ exports.PUSHMODE_DEFAULT = 'quiet';
280
+ /**
281
+ * `notify` flag naming the push mode to assume when the project has no dial.
282
+ * Written by templates.ts into every hook-driven agent's completion hook and
283
+ * read back in cli.ts — shared so the two can never drift apart.
284
+ */
285
+ exports.PUSHMODE_DEFAULT_FLAG = 'pushmode-default';
286
+ /**
287
+ * `notify` flags carrying the turn's tool counts. Only the drop-in artifacts
288
+ * (pi extension, opencode plugin) can supply them — they are the only installed
289
+ * hooks that see per-tool events. Shared for the same reason as the flag above:
290
+ * templates.ts writes them, cli.ts reads them, and a rename on one side alone
291
+ * would send `gateCount` back to GATE_DEFAULTS with nothing to show for it.
292
+ */
293
+ exports.TOOL_COUNT_FLAG = 'tools';
294
+ exports.NONREADONLY_COUNT_FLAG = 'nonreadonly';
295
+ /**
296
+ * The user's session push-mode dial (/hwip-mode quiet | /hwip-mode loud | /hwip-mode normal).
297
+ *
298
+ * Three failure shapes, three answers — "no dial" is the only one that gets
299
+ * the quiet default:
300
+ * - no dial file → `fallback` (PUSHMODE_DEFAULT unless the caller
301
+ * overrides it with --pushmode-default)
302
+ * - unusable dial file → normal. An unreadable
303
+ * file, or a garbled/empty value is a broken
304
+ * setting, and resolving breakage to silence
305
+ * leaves the user with no symptom to debug. The
306
+ * point of the new default is quiet, not hidden
307
+ * errors.
308
+ * - readable dial file → whatever it says.
309
+ */
310
+ const readPushMode = (dir, fallback = exports.PUSHMODE_DEFAULT) => {
311
+ const hash = (0, exports.projectHash)(dir);
312
+ const file = findStateFile('pushmode', hash);
313
+ if (!file)
314
+ return fallback;
315
+ try {
316
+ return (0, exports.normalizePushMode)((0, fs_1.readFileSync)(file, 'utf-8').replace(/\s+/g, ''));
317
+ }
318
+ catch {
319
+ return 'normal';
320
+ }
321
+ };
322
+ exports.readPushMode = readPushMode;
323
+ /**
324
+ * Push mode for a `--auto` notify: the user's dial if they set one, otherwise
325
+ * the mode named by `--pushmode-default`, otherwise the built-in quiet.
326
+ *
327
+ * The dial outranks the flag deliberately. The flag exists so an agent whose
328
+ * hook cannot participate in the heuristic still pushes out of the box; if it
329
+ * outranked the dial, `/hwip-mode quiet` would silently do nothing for that agent.
330
+ *
331
+ * A flag value that isn't one of the three modes resolves to `normal`, not to
332
+ * the quiet default — same rule as a garbled dial file. A caller that passes
333
+ * nonsense has a bug, and answering a bug with silence hides it.
334
+ */
335
+ const autoPushMode = (dir, flag) => (0, exports.readPushMode)(dir, typeof flag === 'string' ? (0, exports.normalizePushMode)(flag) : exports.PUSHMODE_DEFAULT);
336
+ exports.autoPushMode = autoPushMode;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Gemini CLI's transcript format, projected into the events the timeline draws.
3
+ *
4
+ * Gemini's vocabulary belongs to this file alone — the formats share none, so
5
+ * nothing here is borrowed from the Claude, pi or Codex projectors beyond the
6
+ * `transcript-tail` helpers that are about `TurnEvent` itself.
7
+ *
8
+ * Shape measured 2026-10-08 against `~/.gemini/tmp/<project>/chats/session-*.jsonl`
9
+ * (8 sessions, structure only — no conversation text was opened) and the writer
10
+ * that produces it: `@google/gemini-cli` 0.46.0, `bundle/chunk-6ZHP2EJW.js`
11
+ * (line types at 281975-281983, `pushMessage`/`$set` handling at 282072-282074,
12
+ * append at 282289-282291). The version that wrote each of the 8 files is not
13
+ * knowable; 0.46.0's reader and writer agree on the shape below.
14
+ *
15
+ * Three kinds of line, one per `JSON.parse`:
16
+ *
17
+ * - a header `{sessionId, projectHash, startTime, …}` — no `id`;
18
+ * - `{ $set: {…} }` — a patch to the session record;
19
+ * - a message `{id, timestamp, type, content, …}`.
20
+ *
21
+ * The writer also emits `{ $rewindTo: id }` (282532) when the user rewinds. It
22
+ * has no `id`, so it is skipped with the header, and the messages it rewinds
23
+ * past still project: the timeline records what was said, not what the session
24
+ * later forgot.
25
+ *
26
+ * Nothing here refuses to read another version: an unknown record type is
27
+ * named through `onUnknown` and skipped, so the timeline is thinner by one row
28
+ * rather than stopped.
29
+ */
30
+ import { type TranscriptProjector } from './transcript-tail.js';
31
+ export declare const projectGeminiEntries: TranscriptProjector;
32
+ //# sourceMappingURL=gemini-transcript.d.ts.map
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ /**
3
+ * Gemini CLI's transcript format, projected into the events the timeline draws.
4
+ *
5
+ * Gemini's vocabulary belongs to this file alone — the formats share none, so
6
+ * nothing here is borrowed from the Claude, pi or Codex projectors beyond the
7
+ * `transcript-tail` helpers that are about `TurnEvent` itself.
8
+ *
9
+ * Shape measured 2026-10-08 against `~/.gemini/tmp/<project>/chats/session-*.jsonl`
10
+ * (8 sessions, structure only — no conversation text was opened) and the writer
11
+ * that produces it: `@google/gemini-cli` 0.46.0, `bundle/chunk-6ZHP2EJW.js`
12
+ * (line types at 281975-281983, `pushMessage`/`$set` handling at 282072-282074,
13
+ * append at 282289-282291). The version that wrote each of the 8 files is not
14
+ * knowable; 0.46.0's reader and writer agree on the shape below.
15
+ *
16
+ * Three kinds of line, one per `JSON.parse`:
17
+ *
18
+ * - a header `{sessionId, projectHash, startTime, …}` — no `id`;
19
+ * - `{ $set: {…} }` — a patch to the session record;
20
+ * - a message `{id, timestamp, type, content, …}`.
21
+ *
22
+ * The writer also emits `{ $rewindTo: id }` (282532) when the user rewinds. It
23
+ * has no `id`, so it is skipped with the header, and the messages it rewinds
24
+ * past still project: the timeline records what was said, not what the session
25
+ * later forgot.
26
+ *
27
+ * Nothing here refuses to read another version: an unknown record type is
28
+ * named through `onUnknown` and skipped, so the timeline is thinner by one row
29
+ * rather than stopped.
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.projectGeminiEntries = void 0;
33
+ const transcript_tail_js_1 = require("./transcript-tail.js");
34
+ /**
35
+ * Which tool argument reads as "what this call is about", first match wins.
36
+ *
37
+ * The names are Gemini's built-in tool parameters, read out of the bundle
38
+ * (`PARAM_*` constants, 276613-276684) rather than guessed. `description` and
39
+ * `title` lead because they are the sentence the model wrote for a person
40
+ * (`run_shell_command` carries one); the rest are the target of the file and
41
+ * search tools, in the order of how directly they name a thing.
42
+ *
43
+ * `command` is absent for the reason it is absent from every other list — a
44
+ * command line is the argument most likely to carry a secret. `prompt`
45
+ * (`web_fetch`, which embeds URLs) and `instruction` (`replace`) are absent for
46
+ * the same reason of not knowing what a person pasted into them.
47
+ */
48
+ const GEMINI_TARGET_KEYS = ['description', 'title', 'file_path', 'dir_path', 'path', 'pattern', 'query', 'name'];
49
+ /** Text out of a message's `content`: a string, or parts of which only `text` is prose. */
50
+ const textOf = (content) => {
51
+ if (typeof content === 'string')
52
+ return content;
53
+ if (!Array.isArray(content))
54
+ return '';
55
+ let text = '';
56
+ for (const part of content) {
57
+ if (part && typeof part === 'object' && typeof part.text === 'string') {
58
+ text += part.text;
59
+ }
60
+ }
61
+ return text;
62
+ };
63
+ /**
64
+ * What the person typed, out of a `user` message.
65
+ *
66
+ * `content` is what went to the model, and for a prompt with `@path` references
67
+ * that includes the referenced files' bodies after a
68
+ * `--- Content from referenced files ---` line (`REFERENCE_CONTENT_START`,
69
+ * `bundle:250454`). The message may also carry `displayContent`, the version the
70
+ * UI shows (`bundle:330220-330232`: recorded only when it differs from
71
+ * `content`). Either way the file bodies must not reach the phone as a prompt:
72
+ * prefer `displayContent`, and cut `content` at the marker when it is all there
73
+ * is. Read from the vendor source; no message on this machine carried either.
74
+ */
75
+ const REFERENCE_CONTENT_MARKER = '--- Content from referenced files ---';
76
+ const typedText = (entry) => {
77
+ const shown = textOf(entry.displayContent);
78
+ const text = shown.trim() ? shown : textOf(entry.content);
79
+ const cut = text.indexOf(REFERENCE_CONTENT_MARKER);
80
+ return cut === -1 ? text : text.slice(0, cut);
81
+ };
82
+ /** `toolCalls[].status` values that mean the call is over; the rest are still in flight. */
83
+ const SETTLED = new Set(['success', 'error', 'cancelled']);
84
+ const projectGeminiEntries = (lines, opts = {}) => {
85
+ const events = [];
86
+ for (const raw of lines) {
87
+ let entry;
88
+ try {
89
+ const parsed = JSON.parse(raw);
90
+ if (!parsed || typeof parsed !== 'object')
91
+ continue;
92
+ entry = parsed;
93
+ }
94
+ catch {
95
+ continue;
96
+ }
97
+ // `$set.messages` replaces the whole message list, so reading it would
98
+ // repeat everything already written above it; and the one a session
99
+ // opens with is the context Gemini injects, not anything a person said.
100
+ // The other keys (`lastUpdated`, `summary`, …) are bookkeeping.
101
+ if ('$set' in entry)
102
+ continue;
103
+ // The header is the only line without an `id`.
104
+ if (typeof entry.id !== 'string')
105
+ continue;
106
+ const at = typeof entry.timestamp === 'string' ? entry.timestamp : undefined;
107
+ const stamp = at ? { at } : {};
108
+ if (entry.type === 'user') {
109
+ // A `user` message is either typed text or the answers to tool
110
+ // calls (`functionResponse` parts, no `text`) — the latter joins to
111
+ // nothing and is skipped. No wrapper filter on the text: of the
112
+ // typed messages measured, none opened with `<`, and a guess at an
113
+ // injection wrapper nobody has seen deletes real sentences.
114
+ const text = typedText(entry).trim();
115
+ if (text)
116
+ events.push({ kind: 'prompt', text: (0, transcript_tail_js_1.clamp)(text, transcript_tail_js_1.MAX_EVENT_TEXT_CHARS), ...stamp });
117
+ continue;
118
+ }
119
+ if (entry.type === 'info' || entry.type === 'error')
120
+ continue;
121
+ if (entry.type !== 'gemini') {
122
+ opts.onUnknown?.(`gemini record type ${typeof entry.type === 'string' ? entry.type : '(none)'}`);
123
+ continue;
124
+ }
125
+ const tokens = entry.tokens;
126
+ if (tokens && typeof tokens === 'object') {
127
+ const t = tokens;
128
+ const thinking = Array.isArray(entry.thoughts) ? entry.thoughts.length : 0;
129
+ events.push({
130
+ kind: 'msg',
131
+ mid: (0, transcript_tail_js_1.clamp)(entry.id),
132
+ ...(typeof entry.model === 'string' ? { model: (0, transcript_tail_js_1.clamp)(entry.model) } : {}),
133
+ out: (0, transcript_tail_js_1.finiteNumber)(t.output),
134
+ // `input` is the prompt token count and already includes the
135
+ // cached share (measured: cached <= input in 9 of 9), so adding
136
+ // `cached` would count it twice.
137
+ ctx: (0, transcript_tail_js_1.finiteNumber)(t.input),
138
+ ...(thinking ? { thinking } : {}),
139
+ ...stamp,
140
+ });
141
+ }
142
+ // Gemini rewrites a whole message as a new line whenever it changes, so
143
+ // a reply that called tools is on disk twice: first with its `content`,
144
+ // then again with the same `content` and `toolCalls`. The prose already
145
+ // went out with the first line, so a line with `toolCalls` sends none.
146
+ // That loses text only for a reply first written together with its
147
+ // `toolCalls`, which the writer does not do: a message it creates for a
148
+ // tool call starts with `content: ""` (`recordToolCalls`, 282401-282451).
149
+ // The opposite miss is accepted: `recordMessageTokens` (282374) rewrites
150
+ // a reply once its `tokens` arrive late, and that second line repeats
151
+ // the same text (measured 0 of 12) — a doubled row beats a lost one.
152
+ const toolCalls = Array.isArray(entry.toolCalls) ? entry.toolCalls : undefined;
153
+ if (!toolCalls) {
154
+ const text = textOf(entry.content);
155
+ if (text.trim())
156
+ events.push({ kind: 'text', text: (0, transcript_tail_js_1.clamp)(text, transcript_tail_js_1.MAX_EVENT_TEXT_CHARS), ...stamp });
157
+ continue;
158
+ }
159
+ for (const item of toolCalls) {
160
+ if (!item || typeof item !== 'object')
161
+ continue;
162
+ const call = item;
163
+ if (typeof call.id !== 'string' || typeof call.name !== 'string')
164
+ continue;
165
+ const target = (0, transcript_tail_js_1.oneLine)((0, transcript_tail_js_1.targetFrom)(call.args, GEMINI_TARGET_KEYS));
166
+ events.push({
167
+ kind: 'tool',
168
+ id: (0, transcript_tail_js_1.clamp)(call.id),
169
+ name: (0, transcript_tail_js_1.clamp)(call.name),
170
+ ...(target ? { target } : {}),
171
+ ...stamp,
172
+ });
173
+ // Settled calls carry their outcome on the same line as the call.
174
+ // Only the line count of the display text leaves, never the text.
175
+ if (typeof call.status === 'string' && SETTLED.has(call.status)) {
176
+ const resultLines = typeof call.resultDisplay === 'string' ? (0, transcript_tail_js_1.resultLinesOf)(call.resultDisplay) : 0;
177
+ events.push({
178
+ kind: 'tool_result',
179
+ id: (0, transcript_tail_js_1.clamp)(call.id),
180
+ ok: call.status === 'success',
181
+ ...(resultLines ? { lines: resultLines } : {}),
182
+ ...stamp,
183
+ });
184
+ }
185
+ }
186
+ }
187
+ return (0, transcript_tail_js_1.sliceSinceLastPrompt)(events, opts);
188
+ };
189
+ exports.projectGeminiEntries = projectGeminiEntries;
@@ -0,0 +1,98 @@
1
+ import type http from 'node:http';
2
+ /**
3
+ * Did `fetch` reject because the answer was a redirect (every call that carries
4
+ * the API key passes `redirect: 'error'`)? The reason sits in the error's cause
5
+ * chain, one or two levels down depending on who wrapped it.
6
+ */
7
+ export declare const isRedirectRefusal: (err: unknown) => boolean;
8
+ /**
9
+ * Did `fetch` reject before the request could be sent? Any other rejection — a socket reset, a body
10
+ * that stops halfway — is also a `TypeError`, and may be of a request the server took. The code sits
11
+ * in the cause chain, as for a refused redirect. The shapes are Node 24.21's (undici 7.29), read
12
+ * 2026-10-14: `{ code, syscall }` on the cause, and an `AggregateError` of them for a host with two addresses.
13
+ */
14
+ export declare const neverConnected: (err: unknown) => boolean;
15
+ /** Did `fetch` reject because the address is not one? It will say so again however often it is asked. */
16
+ export declare const isInvalidUrl: (err: unknown) => boolean;
17
+ /**
18
+ * Is this failure a permanent configuration problem (bad base URL, unsendable API key) rather than a
19
+ * network blip? Such errors say so in fixed words — `apiFetch` throws them before any `fetch`, and
20
+ * `resolveBaseUrl` throws them before any request — so swallowing them as "could not fetch" or
21
+ * retrying them with backoff only repeats them. Matched by message, as the throwers sanitize it — so
22
+ * never on an error that carries an HTTP status (`HttpError`, a `HwipError` from a reply): its message
23
+ * is the server's own text, and a 5xx that happens to say "is unknown" is still worth another try.
24
+ */
25
+ export declare const isConfigFailure: (err: unknown) => boolean;
26
+ /**
27
+ * Did `fetch` reject for a header value it cannot send? It does so before it opens a connection, and the
28
+ * same way however often it is asked. Two shapes (Node 24.21, read 2026-10-14 — held to a real `fetch`
29
+ * in http-failure.test.ts): a `TypeError` with no cause that says which value — whole for a line break
30
+ * or a NUL, by place for a character past U+00FF — and, for any other control character, undici's
31
+ * `UND_ERR_INVALID_ARG` ("invalid X-API-Key header") under "fetch failed". Looked for down the cause
32
+ * chain, as the callers hand on what `fetch` threw inside an error in their own words.
33
+ */
34
+ export declare const isUnsendableHeader: (err: unknown) => boolean;
35
+ /** "could not reach api.hwip.app (ENOTFOUND)" from a rejected `fetch`. */
36
+ export declare const networkFailureMessage: (err: unknown, url: string) => string;
37
+ /** The body as JSON when it is JSON, else null. A read that fails (an abort
38
+ * mid-body) still throws: that is a timeout or a dropped connection, not a
39
+ * page that is not ours. */
40
+ export declare const readJsonBody: (response: Response) => Promise<unknown>;
41
+ export declare const httpFailureMessage: (status: number, body: unknown) => string;
42
+ /** What to do about a rejected key, appended to its message. */
43
+ export declare const AUTH_HINT = "run `hwip login`, or check --key / HWIP_API_KEY";
44
+ /**
45
+ * The proxy variables, read the way undici's `EnvHttpProxyAgent` reads them
46
+ * (6.29): lower case before upper case, an https target falls back to the http
47
+ * proxy, and `NO_PROXY` is a list of hosts, `.suffix`es and `host:port`s. The
48
+ * listener's socket is not `fetch` and never reaches its dispatcher, so it asks
49
+ * the same question here and the two cannot disagree about what goes direct.
50
+ */
51
+ export interface ProxyEnv {
52
+ http_proxy?: string;
53
+ HTTP_PROXY?: string;
54
+ https_proxy?: string;
55
+ HTTPS_PROXY?: string;
56
+ no_proxy?: string;
57
+ NO_PROXY?: string;
58
+ }
59
+ /** The proxy a request to `rawUrl` goes through, or null for a direct connection. `ws` / `wss` follow `http` / `https`. */
60
+ export declare const proxyForUrl: (rawUrl: string, env?: ProxyEnv) => string | null;
61
+ /** The proxy settings `~/.hwip/config.json` can hold: what a launchd service, which never sees the shell's variables, reads instead. */
62
+ export interface ProxyConfig {
63
+ httpProxy?: unknown;
64
+ httpsProxy?: unknown;
65
+ noProxy?: unknown;
66
+ }
67
+ /**
68
+ * Route `fetch` and the listener's socket through HTTPS_PROXY / HTTP_PROXY,
69
+ * honouring NO_PROXY, when the variables are set; a no-op otherwise.
70
+ *
71
+ * `config` (`httpProxy`, `httpsProxy`, `noProxy` of ~/.hwip/config.json) fills
72
+ * in what the environment leaves unset, one setting at a time: a launchd
73
+ * service starts with its own environment and the plist must not hold a proxy
74
+ * password, so the file is where it finds them. The environment wins when it
75
+ * sets a name at all, empty included.
76
+ *
77
+ * Not through Node's own `http.setGlobalProxyFromEnv`: it exists only from 24.14
78
+ * and 25.4, and the CLI runs on older Nodes. `fetch` gets undici's
79
+ * `EnvHttpProxyAgent` as the global dispatcher (undici 6, Node >= 18.17); `ws`
80
+ * opens its own connection, so it gets a CONNECT-tunnelling agent from
81
+ * `https-proxy-agent` (`webSocketProxyOptions`). Both libraries load only here,
82
+ * so a run with no proxy pays nothing for them.
83
+ *
84
+ * A proxy that cannot be set up is an error, not a direct connection: going
85
+ * around a proxy someone configured is the wrong way to fail. The error names
86
+ * the setting and never quotes it: the value may carry the proxy's password.
87
+ */
88
+ export declare const useProxyFromEnv: (env?: ProxyEnv, config?: ProxyConfig) => Promise<void>;
89
+ /**
90
+ * Options for the listener's `new WebSocket`: `ws` opens its own connection
91
+ * and never reaches fetch's dispatcher, so with a proxy set it is handed an
92
+ * agent that tunnels through it (CONNECT, for `ws:` as well as `wss:`) — unless
93
+ * NO_PROXY says that host goes direct.
94
+ */
95
+ export declare const webSocketProxyOptions: (url: string) => {
96
+ agent?: http.Agent;
97
+ };
98
+ //# sourceMappingURL=http-failure.d.ts.map