@solongate/proxy 0.83.56 → 0.83.57

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.
@@ -5,59 +5,118 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/api-client/client.ts
8
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
9
- import { resolve as resolve2, join as join2 } from "path";
10
- import { homedir as homedir2 } from "os";
8
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
9
+ import { resolve as resolve2, join as join3 } from "path";
10
+ import { homedir as homedir3 } from "os";
11
11
 
12
12
  // src/global-install.ts
13
- import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, rmdirSync, readdirSync, statSync, chmodSync, copyFileSync, renameSync } from "fs";
14
- import { resolve, join, dirname } from "path";
15
- import { homedir } from "os";
13
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync, rmSync, rmdirSync, readdirSync, statSync as statSync2, chmodSync, copyFileSync, renameSync } from "fs";
14
+ import { resolve, join as join2, dirname, basename } from "path";
15
+ import { homedir as homedir2 } from "os";
16
16
  import { createRequire } from "module";
17
17
  import { fileURLToPath } from "url";
18
18
  import { createInterface } from "readline";
19
- import { execFileSync, spawn } from "child_process";
19
+ import { execFileSync as execFileSync2, spawn } from "child_process";
20
+
21
+ // src/hook-launcher.ts
22
+ var LAUNCHER_NAME = "sg-run.sh";
23
+ var BEAT_DIR = ".beat";
24
+
25
+ // src/hook-health.ts
26
+ import { execFileSync } from "child_process";
27
+ import { existsSync, readFileSync, statSync } from "fs";
28
+ import { join } from "path";
29
+ import { homedir } from "os";
30
+ var sgDir = () => join(homedir(), ".solongate");
31
+ var hooksDir = () => join(sgDir(), "hooks");
32
+ function hookCanStart() {
33
+ const launcher = join(hooksDir(), LAUNCHER_NAME);
34
+ if (process.platform === "win32") {
35
+ const ok = existsSync(process.execPath);
36
+ return { ok, node: process.execPath, detail: ok ? process.execPath : `${process.execPath} is gone` };
37
+ }
38
+ if (!existsSync(launcher)) {
39
+ return { ok: false, detail: "hook launcher missing - run `solongate repair`" };
40
+ }
41
+ try {
42
+ const out2 = execFileSync("/bin/sh", [launcher, "--sg-doctor"], {
43
+ encoding: "utf-8",
44
+ timeout: 5e3,
45
+ stdio: ["ignore", "pipe", "pipe"]
46
+ }).trim();
47
+ if (!out2) return { ok: false, detail: "launcher found no node runtime - set SOLONGATE_NODE or install node" };
48
+ return { ok: true, node: out2, detail: out2 };
49
+ } catch (e) {
50
+ const msg = e instanceof Error ? e.message : String(e);
51
+ return { ok: false, detail: `launcher will not run: ${msg.split("\n")[0]}` };
52
+ }
53
+ }
54
+ function hookBeats() {
55
+ const dir = join(sgDir(), BEAT_DIR);
56
+ const out2 = [];
57
+ for (const hook of ["guard.mjs", "audit.mjs", "conversation.mjs", "stop.mjs"]) {
58
+ const f = join(dir, hook);
59
+ try {
60
+ const st = statSync(f);
61
+ out2.push({ hook, at: st.mtime, node: readFileSync(f, "utf-8").trim() });
62
+ } catch {
63
+ }
64
+ }
65
+ return out2.sort((a, b) => b.at.getTime() - a.at.getTime());
66
+ }
67
+ function guardBeat() {
68
+ return hookBeats().find((b) => b.hook === "guard.mjs") ?? null;
69
+ }
70
+ function agoLabel(d) {
71
+ const s = Math.max(0, Math.round((Date.now() - d.getTime()) / 1e3));
72
+ if (s < 60) return s <= 3 ? "just now" : `${s}s ago`;
73
+ if (s < 3600) return `${Math.round(s / 60)}m ago`;
74
+ if (s < 86400) return `${Math.round(s / 3600)}h ago`;
75
+ return `${Math.round(s / 86400)}d ago`;
76
+ }
77
+
78
+ // src/global-install.ts
20
79
  var __dirname = dirname(fileURLToPath(import.meta.url));
21
80
  var HOOKS_DIR = resolve(__dirname, "..", "hooks");
22
81
  function globalPaths() {
23
- const home = homedir();
24
- const sgDir = join(home, ".solongate");
25
- const hooksDir = join(sgDir, "hooks");
26
- const claudeDir = join(home, ".claude");
27
- const antigravityDir = join(home, ".gemini", "config");
28
- const codexDir = process.env["CODEX_HOME"] ? resolve(process.env["CODEX_HOME"]) : join(home, ".codex");
29
- const opencodeDir = join(process.env["XDG_CONFIG_HOME"] ? resolve(process.env["XDG_CONFIG_HOME"]) : join(home, ".config"), "opencode");
30
- const binDir = join(sgDir, "bin");
82
+ const home = homedir2();
83
+ const sgDir2 = join2(home, ".solongate");
84
+ const hooksDir2 = join2(sgDir2, "hooks");
85
+ const claudeDir = join2(home, ".claude");
86
+ const antigravityDir = join2(home, ".gemini", "config");
87
+ const codexDir = process.env["CODEX_HOME"] ? resolve(process.env["CODEX_HOME"]) : join2(home, ".codex");
88
+ const opencodeDir = join2(process.env["XDG_CONFIG_HOME"] ? resolve(process.env["XDG_CONFIG_HOME"]) : join2(home, ".config"), "opencode");
89
+ const binDir = join2(sgDir2, "bin");
31
90
  return {
32
91
  home,
33
- sgDir,
34
- hooksDir,
92
+ sgDir: sgDir2,
93
+ hooksDir: hooksDir2,
35
94
  binDir,
36
95
  claudeDir,
37
96
  antigravityDir,
38
97
  codexDir,
39
98
  opencodeDir,
40
- settingsPath: join(claudeDir, "settings.json"),
41
- backupPath: join(claudeDir, "settings.solongate.bak"),
42
- configPath: join(sgDir, "cloud-guard.json"),
99
+ settingsPath: join2(claudeDir, "settings.json"),
100
+ backupPath: join2(claudeDir, "settings.solongate.bak"),
101
+ configPath: join2(sgDir2, "cloud-guard.json"),
43
102
  // Antigravity reads global hooks from ~/.gemini/config/hooks.json. Only the
44
103
  // guard is registered there (PreToolUse); Antigravity's ALLOW-path audit is
45
104
  // covered by the passive session-log collector, not a hook.
46
- antigravityHooksPath: join(antigravityDir, "hooks.json"),
47
- antigravityBackupPath: join(antigravityDir, "hooks.solongate.bak"),
105
+ antigravityHooksPath: join2(antigravityDir, "hooks.json"),
106
+ antigravityBackupPath: join2(antigravityDir, "hooks.solongate.bak"),
48
107
  // Codex reads user-level hooks from ~/.codex/hooks.json (or a [hooks] table
49
108
  // in ~/.codex/config.toml — we use the JSON file so we never have to rewrite
50
109
  // the user's TOML, which also holds the hook TRUST state Codex manages).
51
- codexHooksPath: join(codexDir, "hooks.json"),
52
- codexBackupPath: join(codexDir, "hooks.solongate.bak"),
53
- codexConfigPath: join(codexDir, "config.toml"),
110
+ codexHooksPath: join2(codexDir, "hooks.json"),
111
+ codexBackupPath: join2(codexDir, "hooks.solongate.bak"),
112
+ codexConfigPath: join2(codexDir, "config.toml"),
54
113
  // OpenCode scans its plugin folder at startup and loads every module in it.
55
114
  // Measured on 1.18.10: BOTH `plugin/` and `plugins/` are scanned, so the
56
115
  // docs and the field reports are each half right. We write the documented
57
116
  // one. There is nothing to register anywhere — dropping the file IS the
58
117
  // installation, which also means deleting the file IS the uninstall.
59
- opencodePluginDir: join(opencodeDir, "plugins"),
60
- opencodePluginPath: join(opencodeDir, "plugins", "solongate.js")
118
+ opencodePluginDir: join2(opencodeDir, "plugins"),
119
+ opencodePluginPath: join2(opencodeDir, "plugins", "solongate.js")
61
120
  };
62
121
  }
63
122
  function isOurCodexGroup(group) {
@@ -66,10 +125,10 @@ function isOurCodexGroup(group) {
66
125
  }
67
126
  function readCodexHooksFile(path) {
68
127
  const out2 = { hooks: {}, rest: {} };
69
- if (!existsSync(path)) return out2;
128
+ if (!existsSync2(path)) return out2;
70
129
  let raw;
71
130
  try {
72
- raw = JSON.parse(readFileSync(path, "utf-8"));
131
+ raw = JSON.parse(readFileSync2(path, "utf-8"));
73
132
  } catch {
74
133
  return out2;
75
134
  }
@@ -117,7 +176,7 @@ function isCodexGuardInstalled() {
117
176
  }
118
177
  function codexDetected() {
119
178
  try {
120
- return existsSync(globalPaths().codexDir);
179
+ return existsSync2(globalPaths().codexDir);
121
180
  } catch {
122
181
  return false;
123
182
  }
@@ -127,7 +186,7 @@ function codexHooksStatus() {
127
186
  const registered = isCodexGuardInstalled();
128
187
  let trusted = false, disabled = false;
129
188
  try {
130
- const toml = readFileSync(p.codexConfigPath, "utf-8");
189
+ const toml = readFileSync2(p.codexConfigPath, "utf-8");
131
190
  trusted = /trusted_hash\s*=/.test(toml);
132
191
  disabled = /^\s*hooks\s*=\s*false\s*$/m.test(toml);
133
192
  } catch {
@@ -136,14 +195,14 @@ function codexHooksStatus() {
136
195
  }
137
196
  function isOpencodeGuardInstalled() {
138
197
  try {
139
- return readFileSync(globalPaths().opencodePluginPath, "utf-8").includes("tool.execute.before");
198
+ return readFileSync2(globalPaths().opencodePluginPath, "utf-8").includes("tool.execute.before");
140
199
  } catch {
141
200
  return false;
142
201
  }
143
202
  }
144
203
  function opencodeDetected() {
145
204
  try {
146
- return existsSync(globalPaths().opencodeDir);
205
+ return existsSync2(globalPaths().opencodeDir);
147
206
  } catch {
148
207
  return false;
149
208
  }
@@ -151,7 +210,7 @@ function opencodeDetected() {
151
210
  function installedGuardVersion() {
152
211
  try {
153
212
  const p = globalPaths();
154
- const s = readFileSync(join(p.hooksDir, "guard.mjs"), "utf-8");
213
+ const s = readFileSync2(join2(p.hooksDir, "guard.mjs"), "utf-8");
155
214
  const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
156
215
  return m ? parseInt(m[1], 10) : null;
157
216
  } catch {
@@ -161,8 +220,8 @@ function installedGuardVersion() {
161
220
  function isGuardInstalled() {
162
221
  try {
163
222
  const p = globalPaths();
164
- if (!existsSync(p.settingsPath)) return false;
165
- const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
223
+ if (!existsSync2(p.settingsPath)) return false;
224
+ const s = JSON.parse(readFileSync2(p.settingsPath, "utf-8"));
166
225
  return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
167
226
  } catch {
168
227
  return false;
@@ -190,9 +249,9 @@ var NotAuthenticatedError = class extends Error {
190
249
  };
191
250
  function loginCredentialFile() {
192
251
  try {
193
- const p = join2(homedir2(), ".solongate", "cloud-guard.json");
194
- if (!existsSync2(p)) return {};
195
- const c2 = JSON.parse(readFileSync2(p, "utf-8"));
252
+ const p = join3(homedir3(), ".solongate", "cloud-guard.json");
253
+ if (!existsSync3(p)) return {};
254
+ const c2 = JSON.parse(readFileSync3(p, "utf-8"));
196
255
  return c2 && typeof c2 === "object" ? c2 : {};
197
256
  } catch {
198
257
  return {};
@@ -201,8 +260,8 @@ function loginCredentialFile() {
201
260
  function dotenvApiKey() {
202
261
  try {
203
262
  const envPath = resolve2(".env");
204
- if (!existsSync2(envPath)) return void 0;
205
- for (const line of readFileSync2(envPath, "utf-8").split("\n")) {
263
+ if (!existsSync3(envPath)) return void 0;
264
+ for (const line of readFileSync3(envPath, "utf-8").split("\n")) {
206
265
  const trimmed = line.trim();
207
266
  if (!trimmed || trimmed.startsWith("#")) continue;
208
267
  const eq = trimmed.indexOf("=");
@@ -637,7 +696,7 @@ function sparkline(values) {
637
696
  }
638
697
 
639
698
  // src/commands/policy.ts
640
- import { readFileSync as readFileSync3 } from "fs";
699
+ import { readFileSync as readFileSync4 } from "fs";
641
700
 
642
701
  // src/commands/args.ts
643
702
  function parse(argv) {
@@ -902,7 +961,7 @@ function printRules(rules) {
902
961
  }
903
962
  async function resolveRules(target) {
904
963
  if (target.endsWith(".json")) {
905
- const parsed = JSON.parse(readFileSync3(target, "utf-8"));
964
+ const parsed = JSON.parse(readFileSync4(target, "utf-8"));
906
965
  return parsed.rules ?? [];
907
966
  }
908
967
  const p = await api.policies.get(target);
@@ -1324,39 +1383,39 @@ async function runAgent(argv) {
1324
1383
  }
1325
1384
 
1326
1385
  // src/commands/doctor.ts
1327
- import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
1328
- import { homedir as homedir4 } from "os";
1329
- import { join as join4 } from "path";
1386
+ import { existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
1387
+ import { homedir as homedir5 } from "os";
1388
+ import { join as join5 } from "path";
1330
1389
 
1331
1390
  // src/tui/local-log.ts
1332
- import { closeSync, existsSync as existsSync3, openSync, readdirSync as readdirSync2, readFileSync as readFileSync4, readSync, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
1333
- import { homedir as homedir3 } from "os";
1391
+ import { closeSync, existsSync as existsSync4, openSync, readdirSync as readdirSync2, readFileSync as readFileSync5, readSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
1392
+ import { homedir as homedir4 } from "os";
1334
1393
  import { createHash } from "crypto";
1335
- import { isAbsolute, join as join3 } from "path";
1336
- var DEFAULT_LOCAL_LOG = join3(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
1394
+ import { isAbsolute, join as join4 } from "path";
1395
+ var DEFAULT_LOCAL_LOG = join4(homedir4(), ".solongate", "local-logs", "solongate-audit.jsonl");
1337
1396
  function localLogFile() {
1338
1397
  try {
1339
- const sgDir = join3(homedir3(), ".solongate");
1340
- const caches = readdirSync2(sgDir).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
1341
- const p = join3(sgDir, f);
1398
+ const sgDir2 = join4(homedir4(), ".solongate");
1399
+ const caches = readdirSync2(sgDir2).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
1400
+ const p = join4(sgDir2, f);
1342
1401
  let mtime = 0;
1343
1402
  try {
1344
- mtime = statSync2(p).mtimeMs;
1403
+ mtime = statSync3(p).mtimeMs;
1345
1404
  } catch {
1346
1405
  }
1347
1406
  return { p, mtime };
1348
1407
  }).sort((a, b) => b.mtime - a.mtime);
1349
1408
  for (const { p } of caches) {
1350
1409
  try {
1351
- const c2 = JSON.parse(readFileSync4(p, "utf-8"));
1410
+ const c2 = JSON.parse(readFileSync5(p, "utf-8"));
1352
1411
  const l = c2?.security?.localLogs;
1353
1412
  if (!l || !l.enabled || typeof l.path !== "string" || !l.path.trim()) continue;
1354
1413
  const dir = l.path.trim().replace(/[\\/]+$/, "");
1355
1414
  if (!dir) continue;
1356
1415
  if (!isAbsolute(dir)) return DEFAULT_LOCAL_LOG;
1357
- const file = join3(dir, "solongate-audit.jsonl");
1358
- if (existsSync3(file)) return file;
1359
- return existsSync3(dir) ? file : DEFAULT_LOCAL_LOG;
1416
+ const file = join4(dir, "solongate-audit.jsonl");
1417
+ if (existsSync4(file)) return file;
1418
+ return existsSync4(dir) ? file : DEFAULT_LOCAL_LOG;
1360
1419
  } catch {
1361
1420
  }
1362
1421
  }
@@ -1414,8 +1473,32 @@ async function collectChecks() {
1414
1473
  ok: claudeReg,
1415
1474
  detail: claudeReg ? "guard registered" : "guard NOT registered - run `solongate repair`"
1416
1475
  });
1417
- if (existsSync4(globalPaths().antigravityDir)) {
1418
- const reg = existsSync4(globalPaths().antigravityHooksPath);
1476
+ if (claudeReg) {
1477
+ const start = hookCanStart();
1478
+ checks.push({
1479
+ name: "hook runtime",
1480
+ ok: start.ok,
1481
+ detail: start.ok ? `node ${start.detail}` : `${start.detail} - nothing is being enforced or logged`
1482
+ });
1483
+ const beat = guardBeat();
1484
+ if (!beat) {
1485
+ checks.push({
1486
+ name: "guard fired",
1487
+ ok: "warn",
1488
+ detail: "never - open your agent and run one tool call, then check again"
1489
+ });
1490
+ } else if (beat.node === "no-node") {
1491
+ checks.push({
1492
+ name: "guard fired",
1493
+ ok: false,
1494
+ detail: `${agoLabel(beat.at)}, but found no node to run with - run \`solongate repair\``
1495
+ });
1496
+ } else {
1497
+ checks.push({ name: "guard fired", ok: true, detail: `${agoLabel(beat.at)} \xB7 ${beat.node}` });
1498
+ }
1499
+ }
1500
+ if (existsSync5(globalPaths().antigravityDir)) {
1501
+ const reg = existsSync5(globalPaths().antigravityHooksPath);
1419
1502
  checks.push({
1420
1503
  name: "Antigravity hooks",
1421
1504
  ok: reg,
@@ -1443,7 +1526,7 @@ async function collectChecks() {
1443
1526
  });
1444
1527
  }
1445
1528
  try {
1446
- const raw = readFileSync5(join4(homedir4(), ".solongate", ".key-rejected.json"), "utf-8");
1529
+ const raw = readFileSync6(join5(homedir5(), ".solongate", ".key-rejected.json"), "utf-8");
1447
1530
  const m = JSON.parse(raw);
1448
1531
  const ageMin = m.ts ? Math.round((Date.now() - m.ts) / 6e4) : null;
1449
1532
  checks.push({
@@ -1454,8 +1537,8 @@ async function collectChecks() {
1454
1537
  } catch {
1455
1538
  }
1456
1539
  const LOCAL_LOG2 = localLogFile();
1457
- if (existsSync4(LOCAL_LOG2)) {
1458
- const st = statSync3(LOCAL_LOG2);
1540
+ if (existsSync5(LOCAL_LOG2)) {
1541
+ const st = statSync4(LOCAL_LOG2);
1459
1542
  const ageMin = (Date.now() - st.mtimeMs) / 6e4;
1460
1543
  checks.push({ name: "local logs", ok: true, detail: `on \xB7 ${(st.size / 1024).toFixed(0)}KB \xB7 last write ${ageMin < 1 ? "just now" : Math.round(ageMin) + "m ago"}` });
1461
1544
  } else {
@@ -1485,16 +1568,16 @@ async function run7(argv) {
1485
1568
  }
1486
1569
 
1487
1570
  // src/commands/trace.ts
1488
- import { readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
1489
- import { homedir as homedir5 } from "os";
1490
- import { join as join5, resolve as resolve3 } from "path";
1571
+ import { readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
1572
+ import { homedir as homedir6 } from "os";
1573
+ import { join as join6, resolve as resolve3 } from "path";
1491
1574
  var USAGE7 = usage("solongate trace", "what the guard saw here", [
1492
1575
  ["trace", "the last evaluations in this directory"],
1493
1576
  ["trace --limit N", "how many to show (default 20)"],
1494
1577
  ["trace --json", "the raw records"]
1495
1578
  ]);
1496
1579
  function readAllRings() {
1497
- const root = join5(homedir5(), ".solongate", "projects");
1580
+ const root = join6(homedir6(), ".solongate", "projects");
1498
1581
  let dirs;
1499
1582
  try {
1500
1583
  dirs = readdirSync3(root);
@@ -1505,7 +1588,7 @@ function readAllRings() {
1505
1588
  for (const d of dirs) {
1506
1589
  let text;
1507
1590
  try {
1508
- text = readFileSync6(join5(root, d, ".eval-ring.jsonl"), "utf-8");
1591
+ text = readFileSync7(join6(root, d, ".eval-ring.jsonl"), "utf-8");
1509
1592
  } catch {
1510
1593
  continue;
1511
1594
  }
@@ -1585,12 +1668,12 @@ async function run8(argv) {
1585
1668
  }
1586
1669
 
1587
1670
  // src/commands/watch.ts
1588
- import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readSync as readSync2, statSync as statSync4 } from "fs";
1671
+ import { closeSync as closeSync2, existsSync as existsSync6, openSync as openSync2, readSync as readSync2, statSync as statSync5 } from "fs";
1589
1672
  var trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
1590
1673
  var time = (ms) => new Date(ms).toTimeString().slice(0, 8);
1591
1674
  function tailLocal(file, maxBytes = 131072) {
1592
1675
  try {
1593
- const size = statSync4(file).size;
1676
+ const size = statSync5(file).size;
1594
1677
  const start = Math.max(0, size - maxBytes);
1595
1678
  const fd = openSync2(file, "r");
1596
1679
  const buf = Buffer.alloc(size - start);
@@ -1632,7 +1715,7 @@ async function run9(argv) {
1632
1715
  };
1633
1716
  const pollLocal = () => {
1634
1717
  const LOCAL_LOG2 = localLogFile();
1635
- if (cloudOnly || !existsSync5(LOCAL_LOG2)) return;
1718
+ if (cloudOnly || !existsSync6(LOCAL_LOG2)) return;
1636
1719
  const rows = [];
1637
1720
  for (const line of tailLocal(LOCAL_LOG2)) {
1638
1721
  try {
@@ -1,3 +1,5 @@
1
+ export { BEAT_DIR, LAUNCHER_NAME, launcherScript } from './hook-launcher.js';
2
+ export { agoLabel, guardBeat, hookBeats, hookCanStart } from './hook-health.js';
1
3
  export declare function lockProtected(): void;
2
4
  export declare function unlockProtected(): void;
3
5
  /**
@@ -112,6 +114,32 @@ export declare function guardHookOutdated(): boolean;
112
114
  * guard-status (which shows the version this device last reported and lingers for
113
115
  * ~14 days), so the dataroom can show "removed" the instant you remove it.
114
116
  */
117
+ /**
118
+ * The command string a client config gets for one hook.
119
+ *
120
+ * POSIX goes through the launcher, which resolves node at run time. See
121
+ * hook-launcher.ts for why an absolute node path recorded at install time is
122
+ * not survivable on a Mac.
123
+ *
124
+ * Windows keeps naming node directly. Its node lives at a fixed location under
125
+ * Program Files rather than in a versioned directory a package manager deletes,
126
+ * there is no /bin/sh to launch from, and the `&` prefix is already required
127
+ * because Claude Code runs hook commands through PowerShell — which reads a
128
+ * line starting with a quoted path as a string literal rather than a command,
129
+ * fails to parse it, and runs nothing.
130
+ *
131
+ * `/bin/sh <script>` rather than executing the launcher directly, so the file
132
+ * never needs its executable bit. npm does not preserve the mode of files
133
+ * outside `bin`, and a launcher that unpacks as 0644 would fail to start on
134
+ * every install.
135
+ */
136
+ export declare function hookCommandFor(hooksDir: string, script: string, client?: string, label?: string): string;
137
+ /**
138
+ * Write the launcher. Called by every install path before the configs that
139
+ * point at it, because a config naming a launcher that is not there is the
140
+ * failure this whole file exists to end.
141
+ */
142
+ export declare function writeLauncher(hooksDir: string): void;
115
143
  export declare function isGuardInstalled(): boolean;
116
144
  /**
117
145
  * TUI-safe uninstall (dataroom Settings → guard → remove). Same effect as