@hwip/cli 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +11 -0
  3. package/README.md +1979 -0
  4. package/dist/agent-list.d.ts +76 -0
  5. package/dist/agent-list.js +102 -0
  6. package/dist/agent-read.d.ts +105 -0
  7. package/dist/agent-read.js +309 -0
  8. package/dist/agent-rules-fetch.d.ts +51 -0
  9. package/dist/agent-rules-fetch.js +275 -0
  10. package/dist/agent-rules.default.d.ts +30 -0
  11. package/dist/agent-rules.default.js +292 -0
  12. package/dist/agent-session.d.ts +23 -0
  13. package/dist/agent-session.js +44 -0
  14. package/dist/agent-state.d.ts +84 -0
  15. package/dist/agent-state.js +188 -0
  16. package/dist/agent-target.d.ts +60 -0
  17. package/dist/agent-target.js +81 -0
  18. package/dist/agents.d.ts +31 -0
  19. package/dist/agents.js +75 -0
  20. package/dist/api.d.ts +28 -0
  21. package/dist/api.js +76 -0
  22. package/dist/args.d.ts +74 -0
  23. package/dist/args.js +254 -0
  24. package/dist/ask-context-attach.d.ts +73 -0
  25. package/dist/ask-context-attach.js +151 -0
  26. package/dist/ask-context-screen.d.ts +21 -0
  27. package/dist/ask-context-screen.js +49 -0
  28. package/dist/ask-context-switch.d.ts +31 -0
  29. package/dist/ask-context-switch.js +74 -0
  30. package/dist/ask-context.d.ts +84 -0
  31. package/dist/ask-context.js +172 -0
  32. package/dist/ask-pending.d.ts +47 -0
  33. package/dist/ask-pending.js +221 -0
  34. package/dist/ask-seal.d.ts +120 -0
  35. package/dist/ask-seal.js +179 -0
  36. package/dist/ask.d.ts +107 -0
  37. package/dist/ask.js +551 -0
  38. package/dist/blocked-prompt-seal.d.ts +35 -0
  39. package/dist/blocked-prompt-seal.js +89 -0
  40. package/dist/blocked-prompt.d.ts +57 -0
  41. package/dist/blocked-prompt.js +181 -0
  42. package/dist/channel.d.ts +67 -0
  43. package/dist/channel.js +136 -0
  44. package/dist/check-update.d.ts +28 -0
  45. package/dist/check-update.js +153 -0
  46. package/dist/cli.d.ts +3 -0
  47. package/dist/cli.js +813 -0
  48. package/dist/codex-config.d.ts +24 -0
  49. package/dist/codex-config.js +169 -0
  50. package/dist/codex-transcript.d.ts +59 -0
  51. package/dist/codex-transcript.js +373 -0
  52. package/dist/command-refusal.d.ts +56 -0
  53. package/dist/command-refusal.js +127 -0
  54. package/dist/command-scan.d.ts +46 -0
  55. package/dist/command-scan.js +154 -0
  56. package/dist/command-seal.d.ts +57 -0
  57. package/dist/command-seal.js +88 -0
  58. package/dist/command-seq.d.ts +30 -0
  59. package/dist/command-seq.js +150 -0
  60. package/dist/config.d.ts +156 -0
  61. package/dist/config.js +392 -0
  62. package/dist/core-sections.d.ts +18 -0
  63. package/dist/core-sections.js +28 -0
  64. package/dist/crypto.d.ts +278 -0
  65. package/dist/crypto.js +698 -0
  66. package/dist/desktop-notify.d.ts +33 -0
  67. package/dist/desktop-notify.js +49 -0
  68. package/dist/device-directory.d.ts +36 -0
  69. package/dist/device-directory.js +97 -0
  70. package/dist/device-presence.d.ts +17 -0
  71. package/dist/device-presence.js +62 -0
  72. package/dist/downloads.d.ts +29 -0
  73. package/dist/downloads.js +106 -0
  74. package/dist/errors.d.ts +12 -0
  75. package/dist/errors.js +28 -0
  76. package/dist/file-crypto.d.ts +94 -0
  77. package/dist/file-crypto.js +272 -0
  78. package/dist/file-view-budget.d.ts +15 -0
  79. package/dist/file-view-budget.js +34 -0
  80. package/dist/file-view-switch.d.ts +28 -0
  81. package/dist/file-view-switch.js +122 -0
  82. package/dist/file-view.d.ts +108 -0
  83. package/dist/file-view.js +327 -0
  84. package/dist/gate.d.ts +141 -0
  85. package/dist/gate.js +336 -0
  86. package/dist/gemini-transcript.d.ts +32 -0
  87. package/dist/gemini-transcript.js +189 -0
  88. package/dist/http-failure.d.ts +98 -0
  89. package/dist/http-failure.js +349 -0
  90. package/dist/hwip-core.generated.d.ts +7 -0
  91. package/dist/hwip-core.generated.js +14 -0
  92. package/dist/hwip-hook.d.ts +77 -0
  93. package/dist/hwip-hook.js +340 -0
  94. package/dist/index.d.ts +7 -0
  95. package/dist/index.js +14 -0
  96. package/dist/input-sequencer.d.ts +65 -0
  97. package/dist/input-sequencer.js +142 -0
  98. package/dist/insecure-url.d.ts +16 -0
  99. package/dist/insecure-url.js +76 -0
  100. package/dist/installer.d.ts +96 -0
  101. package/dist/installer.js +850 -0
  102. package/dist/inventory-offload.d.ts +49 -0
  103. package/dist/inventory-offload.js +209 -0
  104. package/dist/inventory-worker.d.ts +2 -0
  105. package/dist/inventory-worker.js +27 -0
  106. package/dist/jsonc-doc.d.ts +106 -0
  107. package/dist/jsonc-doc.js +354 -0
  108. package/dist/keep-awake.d.ts +32 -0
  109. package/dist/keep-awake.js +46 -0
  110. package/dist/key-changes.d.ts +48 -0
  111. package/dist/key-changes.js +140 -0
  112. package/dist/lan-auth.d.ts +140 -0
  113. package/dist/lan-auth.js +217 -0
  114. package/dist/lan-endpoint.d.ts +41 -0
  115. package/dist/lan-endpoint.js +115 -0
  116. package/dist/lan-receiver.d.ts +122 -0
  117. package/dist/lan-receiver.js +510 -0
  118. package/dist/lan-sender.d.ts +76 -0
  119. package/dist/lan-sender.js +184 -0
  120. package/dist/lan-transfer.d.ts +36 -0
  121. package/dist/lan-transfer.js +54 -0
  122. package/dist/listener-device-id.d.ts +20 -0
  123. package/dist/listener-device-id.js +133 -0
  124. package/dist/listener-log.d.ts +4 -0
  125. package/dist/listener-log.js +23 -0
  126. package/dist/listener-process.d.ts +200 -0
  127. package/dist/listener-process.js +470 -0
  128. package/dist/listener-service.d.ts +225 -0
  129. package/dist/listener-service.js +682 -0
  130. package/dist/listener-worktree-cleanup.d.ts +27 -0
  131. package/dist/listener-worktree-cleanup.js +62 -0
  132. package/dist/listener.d.ts +1325 -0
  133. package/dist/listener.js +5680 -0
  134. package/dist/login-address.d.ts +25 -0
  135. package/dist/login-address.js +97 -0
  136. package/dist/login-page.d.ts +29 -0
  137. package/dist/login-page.js +93 -0
  138. package/dist/login.d.ts +98 -0
  139. package/dist/login.js +388 -0
  140. package/dist/mcp-command.d.ts +45 -0
  141. package/dist/mcp-command.js +50 -0
  142. package/dist/mcp.d.ts +19 -0
  143. package/dist/mcp.js +29 -0
  144. package/dist/node-support.d.ts +19 -0
  145. package/dist/node-support.js +38 -0
  146. package/dist/notify-failure.d.ts +57 -0
  147. package/dist/notify-failure.js +150 -0
  148. package/dist/opencode-transcript.d.ts +139 -0
  149. package/dist/opencode-transcript.js +390 -0
  150. package/dist/optional-features.d.ts +6 -0
  151. package/dist/optional-features.js +19 -0
  152. package/dist/pane-fit.d.ts +40 -0
  153. package/dist/pane-fit.js +119 -0
  154. package/dist/peer-key.d.ts +33 -0
  155. package/dist/peer-key.js +34 -0
  156. package/dist/pi-transcript.d.ts +26 -0
  157. package/dist/pi-transcript.js +255 -0
  158. package/dist/presence.d.ts +14 -0
  159. package/dist/presence.js +124 -0
  160. package/dist/private-files.d.ts +51 -0
  161. package/dist/private-files.js +218 -0
  162. package/dist/project-switch.d.ts +19 -0
  163. package/dist/project-switch.js +60 -0
  164. package/dist/redact.d.ts +10 -0
  165. package/dist/redact.js +21 -0
  166. package/dist/remote-agents.d.ts +410 -0
  167. package/dist/remote-agents.js +1218 -0
  168. package/dist/remote-hook.d.ts +12 -0
  169. package/dist/remote-hook.js +196 -0
  170. package/dist/replace-file.d.ts +50 -0
  171. package/dist/replace-file.js +328 -0
  172. package/dist/schedule-command.d.ts +35 -0
  173. package/dist/schedule-command.js +260 -0
  174. package/dist/schedule-launch.d.ts +15 -0
  175. package/dist/schedule-launch.js +24 -0
  176. package/dist/schedule-ready.d.ts +44 -0
  177. package/dist/schedule-ready.js +71 -0
  178. package/dist/schedule-remote.d.ts +77 -0
  179. package/dist/schedule-remote.js +167 -0
  180. package/dist/schedule-runner.d.ts +89 -0
  181. package/dist/schedule-runner.js +452 -0
  182. package/dist/schedule.d.ts +149 -0
  183. package/dist/schedule.js +428 -0
  184. package/dist/secret-mask.d.ts +29 -0
  185. package/dist/secret-mask.js +245 -0
  186. package/dist/self-register.d.ts +45 -0
  187. package/dist/self-register.js +67 -0
  188. package/dist/send.d.ts +56 -0
  189. package/dist/send.js +104 -0
  190. package/dist/session-diff.d.ts +161 -0
  191. package/dist/session-diff.js +445 -0
  192. package/dist/session-registry.d.ts +143 -0
  193. package/dist/session-registry.js +216 -0
  194. package/dist/sessions-report.d.ts +59 -0
  195. package/dist/sessions-report.js +99 -0
  196. package/dist/shell-audit.d.ts +34 -0
  197. package/dist/shell-audit.js +47 -0
  198. package/dist/subagent-transcripts.d.ts +167 -0
  199. package/dist/subagent-transcripts.js +437 -0
  200. package/dist/subscriber-trust.d.ts +128 -0
  201. package/dist/subscriber-trust.js +397 -0
  202. package/dist/templates.d.ts +105 -0
  203. package/dist/templates.js +871 -0
  204. package/dist/tmux-install.d.ts +44 -0
  205. package/dist/tmux-install.js +65 -0
  206. package/dist/tmux-liveness.d.ts +45 -0
  207. package/dist/tmux-liveness.js +71 -0
  208. package/dist/tmux-pane.d.ts +55 -0
  209. package/dist/tmux-pane.js +126 -0
  210. package/dist/tmux-socket.d.ts +20 -0
  211. package/dist/tmux-socket.js +292 -0
  212. package/dist/todo-fold.d.ts +31 -0
  213. package/dist/todo-fold.js +102 -0
  214. package/dist/transcript-tail.d.ts +312 -0
  215. package/dist/transcript-tail.js +718 -0
  216. package/dist/trust-command.d.ts +49 -0
  217. package/dist/trust-command.js +200 -0
  218. package/dist/trust-lock.d.ts +26 -0
  219. package/dist/trust-lock.js +94 -0
  220. package/dist/trust-pins.d.ts +37 -0
  221. package/dist/trust-pins.js +82 -0
  222. package/dist/turn-recorder-wire.d.ts +70 -0
  223. package/dist/turn-recorder-wire.js +165 -0
  224. package/dist/turn-recorder.d.ts +190 -0
  225. package/dist/turn-recorder.js +695 -0
  226. package/dist/turn-ring.d.ts +93 -0
  227. package/dist/turn-ring.js +418 -0
  228. package/dist/turn-watch.d.ts +177 -0
  229. package/dist/turn-watch.js +630 -0
  230. package/dist/types.d.ts +63 -0
  231. package/dist/types.js +2 -0
  232. package/dist/uninstall.d.ts +55 -0
  233. package/dist/uninstall.js +507 -0
  234. package/dist/usage.d.ts +17 -0
  235. package/dist/usage.js +421 -0
  236. package/dist/verify.d.ts +47 -0
  237. package/dist/verify.js +396 -0
  238. package/dist/worktree.d.ts +144 -0
  239. package/dist/worktree.js +310 -0
  240. package/dist/wrapper.d.ts +254 -0
  241. package/dist/wrapper.js +704 -0
  242. package/dist/ws-url.d.ts +109 -0
  243. package/dist/ws-url.js +202 -0
  244. package/package.json +78 -10
  245. package/bin/hwip.js +0 -3
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensurePrivateFile = exports.ensurePrivateDir = exports.mkdirExclusiveHandedBack = exports.createdHandedBack = exports.mkdirHandedBack = exports.handBackToSudoUser = exports.sudoOwner = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_os_1 = require("node:os");
6
+ const node_path_1 = require("node:path");
7
+ /**
8
+ * Where the API key lives (`~/.hwip`) is for its owner alone. `mkdir` and
9
+ * `open` apply their mode only on creation, so an older install — or a login
10
+ * under a loose umask — left the folder and the listener log readable by every
11
+ * local user. These make the mode what it should be whether the path is new or
12
+ * not. Best effort: a path owned by someone else cannot be changed here, and
13
+ * Windows has no such modes.
14
+ */
15
+ const SUDO_ID = /^[1-9]\d{0,9}$/;
16
+ /**
17
+ * Who a file hwip creates under the home folder belongs to when root runs it by `sudo` with HOME
18
+ * kept (macOS does): the person who typed `sudo`, so the next plain `hwip` can read what this
19
+ * one wrote. Null unless every sign agrees: root, `SUDO_UID`/`SUDO_GID` set to real ids, the path
20
+ * under the home folder, and the home folder owned by that same `SUDO_UID`. The last one is what
21
+ * stops a root whose HOME is `/root` (sudo resets it there on Linux) from handing root's folder
22
+ * to whoever ran `sudo`.
23
+ */
24
+ const sudoOwner = (path) => {
25
+ if (process.getuid?.() !== 0)
26
+ return null;
27
+ const { SUDO_UID, SUDO_GID } = process.env;
28
+ if (!SUDO_UID || !SUDO_GID || !SUDO_ID.test(SUDO_UID) || !SUDO_ID.test(SUDO_GID))
29
+ return null;
30
+ try {
31
+ const home = (0, node_os_1.homedir)();
32
+ let realHome = home;
33
+ try {
34
+ realHome = (0, node_fs_1.realpathSync)(home);
35
+ }
36
+ catch {
37
+ /* lexical home is all there is */
38
+ }
39
+ let realDir = (0, node_path_1.dirname)(path);
40
+ try {
41
+ realDir = (0, node_fs_1.realpathSync)(realDir);
42
+ }
43
+ catch {
44
+ /* not there yet: lexical parent is all there is */
45
+ }
46
+ if (!(realDir === realHome || realDir.startsWith(realHome + node_path_1.sep)))
47
+ return null;
48
+ if ((0, node_fs_1.statSync)(home).uid !== Number(SUDO_UID))
49
+ return null;
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ return { uid: Number(SUDO_UID), gid: Number(SUDO_GID) };
55
+ };
56
+ exports.sudoOwner = sudoOwner;
57
+ /**
58
+ * Give a path root just created to the person who ran `sudo` (see `sudoOwner`). Best effort. The path
59
+ * itself, never what a link points at: whoever owns the folder can put a link where the new file was,
60
+ * and root following it would hand them a file of anyone's.
61
+ */
62
+ const handBackToSudoUser = (path) => {
63
+ const owner = (0, exports.sudoOwner)(path);
64
+ if (!owner)
65
+ return;
66
+ try {
67
+ (0, node_fs_1.lchownSync)(path, owner.uid, owner.gid);
68
+ }
69
+ catch { /* not ours to change */ }
70
+ };
71
+ exports.handBackToSudoUser = handBackToSudoUser;
72
+ /**
73
+ * `mkdir -p`: every folder it makes, not only the last, goes to the person who ran `sudo` when root
74
+ * makes it — a root-owned `~/.hwip` is one the next plain `hwip` cannot write in.
75
+ */
76
+ const mkdirHandedBack = (dir, mode) => {
77
+ const made = [];
78
+ for (let at = dir; !(0, node_fs_1.existsSync)(at) && (0, node_path_1.dirname)(at) !== at; at = (0, node_path_1.dirname)(at))
79
+ made.push(at);
80
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true, ...(mode === undefined ? {} : { mode }) });
81
+ for (const path of made)
82
+ (0, exports.handBackToSudoUser)(path);
83
+ };
84
+ exports.mkdirHandedBack = mkdirHandedBack;
85
+ /**
86
+ * Create `path` without following a link at its end, handing the new file back
87
+ * when root made it (fd, never by name: a hardlink put there before shares the
88
+ * inode, and `lchown` by name would hand that inode over too). EEXIST (taken
89
+ * before, by anyone) and ELOOP (a link at the end) create nothing and hand
90
+ * nothing back — fail closed.
91
+ */
92
+ const createExclusiveHandedBack = (path, mode) => {
93
+ const { O_CREAT, O_EXCL, O_WRONLY, O_NOFOLLOW } = node_fs_1.constants;
94
+ if (O_EXCL === undefined || O_NOFOLLOW === undefined)
95
+ return 'not-created';
96
+ let fd;
97
+ try {
98
+ fd = (0, node_fs_1.openSync)(path, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW, mode);
99
+ }
100
+ catch {
101
+ return 'not-created';
102
+ }
103
+ try {
104
+ const owner = (0, exports.sudoOwner)(path);
105
+ if (owner) {
106
+ try {
107
+ (0, node_fs_1.fchownSync)(fd, owner.uid, owner.gid);
108
+ }
109
+ catch { /* not ours to change */ }
110
+ }
111
+ }
112
+ finally {
113
+ (0, node_fs_1.closeSync)(fd);
114
+ }
115
+ return 'created';
116
+ };
117
+ /**
118
+ * Make sure `path` exists before a plain write fills it, handing it back when root made it. For a
119
+ * caller whose write line is better left alone (it sits next to code others are changing); the
120
+ * write that follows keeps the owner, since it opens the same file.
121
+ */
122
+ const createdHandedBack = (path) => {
123
+ if ((0, node_fs_1.existsSync)(path))
124
+ return;
125
+ createExclusiveHandedBack(path, 0o600);
126
+ };
127
+ exports.createdHandedBack = createdHandedBack;
128
+ /** `mkdir` without `-p`: fails with EEXIST when it is there, so the caller can tell who made it first. */
129
+ const mkdirExclusiveHandedBack = (dir) => {
130
+ (0, node_fs_1.mkdirSync)(dir, { recursive: false });
131
+ (0, exports.handBackToSudoUser)(dir);
132
+ };
133
+ exports.mkdirExclusiveHandedBack = mkdirExclusiveHandedBack;
134
+ /**
135
+ * The directory exists and only its owner can enter it (0700). A symlink is left
136
+ * as it is: it points at a folder that may be shared on purpose (dotfiles), and
137
+ * what it points at is not ours to lock.
138
+ *
139
+ * Opened without following a link and changed through what was opened, so the folder looked at is the
140
+ * one changed: by name, a link put there after the look got its target locked instead. Where the
141
+ * platform has no such open (Windows, which has no such modes either), by name as before.
142
+ *
143
+ * A folder its owner cannot read (0377) cannot be opened, yet others may still write into it, so that one
144
+ * too is changed by name — when it is a folder of the caller's own, and unless root runs this (by either of
145
+ * its ids): root is who could lock a file of someone else's through a link put there after the look. A person
146
+ * can only lock their own.
147
+ */
148
+ const ensurePrivateDir = (dir) => {
149
+ (0, exports.mkdirHandedBack)(dir, 0o700);
150
+ try {
151
+ const { O_RDONLY, O_DIRECTORY, O_NOFOLLOW } = node_fs_1.constants;
152
+ if (O_DIRECTORY === undefined || O_NOFOLLOW === undefined) {
153
+ if (!(0, node_fs_1.lstatSync)(dir).isSymbolicLink())
154
+ (0, node_fs_1.chmodSync)(dir, 0o700);
155
+ return;
156
+ }
157
+ // A link here fails the open (ENOTDIR on macOS, seen 2026-10-14), which is the "left as it is" above.
158
+ let fd;
159
+ try {
160
+ fd = (0, node_fs_1.openSync)(dir, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
161
+ }
162
+ catch (err) {
163
+ const unreadable = err instanceof Error && 'code' in err && err.code === 'EACCES';
164
+ const asRoot = process.geteuid?.() === 0 || process.getuid?.() === 0;
165
+ if (!unreadable || asRoot)
166
+ throw err;
167
+ const seen = (0, node_fs_1.lstatSync)(dir);
168
+ const ownFolder = seen.isDirectory() && seen.uid === process.geteuid?.();
169
+ if (ownFolder)
170
+ (0, node_fs_1.chmodSync)(dir, 0o700);
171
+ return;
172
+ }
173
+ try {
174
+ (0, node_fs_1.fchmodSync)(fd, 0o700);
175
+ }
176
+ finally {
177
+ (0, node_fs_1.closeSync)(fd);
178
+ }
179
+ }
180
+ catch { /* not ours to change */ }
181
+ };
182
+ exports.ensurePrivateDir = ensurePrivateDir;
183
+ /** The file exists and only its owner can read it (0600); its content is untouched. A link at the
184
+ * end is left as it is: creating its target and then fixing only the link would leave a file of
185
+ * someone's behind (C149 ②). An existing file is changed through an fd for the same reason. */
186
+ const ensurePrivateFile = (path) => {
187
+ const existed = (0, node_fs_1.existsSync)(path);
188
+ if (!existed) {
189
+ if (createExclusiveHandedBack(path, 0o600) === 'created')
190
+ return;
191
+ if ((0, node_fs_1.lstatSync)(path, { throwIfNoEntry: false })?.isSymbolicLink())
192
+ return;
193
+ }
194
+ else if ((0, node_fs_1.lstatSync)(path, { throwIfNoEntry: false })?.isSymbolicLink()) {
195
+ return;
196
+ }
197
+ const { O_WRONLY, O_NOFOLLOW } = node_fs_1.constants;
198
+ if (O_NOFOLLOW === undefined) {
199
+ // No link-safe primitives on this platform: the legacy by-name path stays.
200
+ try {
201
+ (0, node_fs_1.closeSync)((0, node_fs_1.openSync)(path, 'a', 0o600));
202
+ (0, node_fs_1.chmodSync)(path, 0o600);
203
+ }
204
+ catch { /* not ours to change */ }
205
+ return;
206
+ }
207
+ try {
208
+ const fd = (0, node_fs_1.openSync)(path, O_WRONLY | O_NOFOLLOW);
209
+ try {
210
+ (0, node_fs_1.fchmodSync)(fd, 0o600);
211
+ }
212
+ finally {
213
+ (0, node_fs_1.closeSync)(fd);
214
+ }
215
+ }
216
+ catch { /* link swapped in after the look, or not ours to change */ }
217
+ };
218
+ exports.ensurePrivateFile = ensurePrivateFile;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A per-project on/off switch kept as local state files — one per project and one for every project — in the state
3
+ * directory (`gate.ts`'s, beside `muted-<hash>`). `ask-context` and `file-view` share it, so the rule that matters
4
+ * ("a switch file that cannot be read counts as off") is written once. A switch never reaches the server or the wire.
5
+ * No file means on.
6
+ */
7
+ export type SwitchOffBy = 'env' | 'project' | 'global' | 'unreadable';
8
+ export type SwitchScope = 'project' | 'global';
9
+ /** The project a command or request belongs to: the repository root, else the directory itself, so a subfolder reads the same switch. */
10
+ export declare const projectDirOf: (cwd: string, repoRoot?: (cwd: string) => string | null) => string;
11
+ export interface ProjectSwitch {
12
+ /** Why the feature is off for this project, or `null` when it is on. The env variable turns it off for one run. */
13
+ offBy: (dir: string, env?: NodeJS.ProcessEnv) => SwitchOffBy | null;
14
+ /** Turn it on or off for this project, or for every project. Throws when the state file cannot be written or removed. */
15
+ set: (dir: string, on: boolean, scope: SwitchScope) => void;
16
+ }
17
+ /** The switch named `<name>` (state files `<name>-off-<project hash>` and `<name>-off-default`), also turned off by `envVar` for one run. */
18
+ export declare const projectSwitch: (name: string, envVar: string) => ProjectSwitch;
19
+ //# sourceMappingURL=project-switch.d.ts.map
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.projectSwitch = exports.projectDirOf = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const gate_js_1 = require("./gate.js");
7
+ const private_files_js_1 = require("./private-files.js");
8
+ const session_diff_js_1 = require("./session-diff.js");
9
+ const OFF_WORDS = new Set(['0', 'false', 'off', 'no']);
10
+ /** The project a command or request belongs to: the repository root, else the directory itself, so a subfolder reads the same switch. */
11
+ const projectDirOf = (cwd, repoRoot = session_diff_js_1.repoRootOf) => repoRoot(cwd) ?? cwd;
12
+ exports.projectDirOf = projectDirOf;
13
+ /**
14
+ * Whether a state file is there. A file that cannot be told apart from absent (permissions, a broken state
15
+ * directory) counts as there: a switch someone turned off must not turn itself back on.
16
+ */
17
+ const stateOf = (path) => {
18
+ try {
19
+ (0, node_fs_1.lstatSync)(path);
20
+ return 'present';
21
+ }
22
+ catch (err) {
23
+ return err.code === 'ENOENT' ? 'absent' : 'unreadable';
24
+ }
25
+ };
26
+ /** The switch named `<name>` (state files `<name>-off-<project hash>` and `<name>-off-default`), also turned off by `envVar` for one run. */
27
+ const projectSwitch = (name, envVar) => {
28
+ const offFile = (scope, dir) => (0, node_path_1.join)((0, gate_js_1.stateDir)(), `${name}-off-${scope === 'global' ? 'default' : (0, gate_js_1.projectHash)(dir)}`);
29
+ return {
30
+ offBy: (dir, env = process.env) => {
31
+ if (OFF_WORDS.has((env[envVar] ?? '').trim().toLowerCase()))
32
+ return 'env';
33
+ for (const scope of ['project', 'global']) {
34
+ const state = stateOf(offFile(scope, dir));
35
+ if (state === 'present')
36
+ return scope;
37
+ if (state === 'unreadable')
38
+ return 'unreadable';
39
+ }
40
+ return null;
41
+ },
42
+ set: (dir, on, scope) => {
43
+ const file = offFile(scope, dir);
44
+ if (on) {
45
+ try {
46
+ (0, node_fs_1.unlinkSync)(file);
47
+ }
48
+ catch (err) {
49
+ if (err.code !== 'ENOENT')
50
+ throw err;
51
+ }
52
+ return;
53
+ }
54
+ (0, private_files_js_1.mkdirHandedBack)((0, gate_js_1.stateDir)());
55
+ (0, private_files_js_1.createdHandedBack)(file);
56
+ (0, node_fs_1.writeFileSync)(file, 'off\n');
57
+ },
58
+ };
59
+ };
60
+ exports.projectSwitch = projectSwitch;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Text that may go to stderr, a log file or a crash line is cleaned of the API
3
+ * key first. The listener's socket URL carries it (`?apiKey=…`), and an error
4
+ * that quotes the URL — `Invalid URL: ws.hwip.app?apiKey=ak_…` — used to land,
5
+ * key and all, in `~/.hwip/listener.log`.
6
+ */
7
+ export declare const redactSecrets: (text: string) => string;
8
+ /** A key for a line that shows which one is in use: the prefix and no more (`ak_…`), never enough to guess from. */
9
+ export declare const maskKey: (key: string) => string;
10
+ //# sourceMappingURL=redact.d.ts.map
package/dist/redact.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.maskKey = exports.redactSecrets = void 0;
4
+ /**
5
+ * Text that may go to stderr, a log file or a crash line is cleaned of the API
6
+ * key first. The listener's socket URL carries it (`?apiKey=…`), and an error
7
+ * that quotes the URL — `Invalid URL: ws.hwip.app?apiKey=ak_…` — used to land,
8
+ * key and all, in `~/.hwip/listener.log`.
9
+ */
10
+ const redactSecrets = (text) => text
11
+ // `https://user:password@host` — a base URL with credentials in it, quoted by a fetch error. Up to the last
12
+ // `@` before the host, since a password can have one of its own; a quote, a comma or a space ends the
13
+ // address, so one after another in unrelated text is not run together.
14
+ .replace(/(\b[a-z][a-z0-9+.-]{0,31}:\/\/)[^\s/?#"'<>,]*@/gi, '$1***@')
15
+ .replace(/([?&]apiKey=)[^&\s"'<>]*/gi, '$1***')
16
+ .replace(/("apiKey"\s*:\s*")[^"]*"/gi, '$1***"')
17
+ .replace(/\bak_[A-Za-z0-9_.~-]{4,}/g, 'ak_***');
18
+ exports.redactSecrets = redactSecrets;
19
+ /** A key for a line that shows which one is in use: the prefix and no more (`ak_…`), never enough to guess from. */
20
+ const maskKey = (key) => `${key.length > 4 ? key.slice(0, 3) : ''}…`;
21
+ exports.maskKey = maskKey;