@solongate/proxy 0.83.56 → 0.83.58
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.
- package/dist/commands/index.js +154 -71
- package/dist/global-install.d.ts +28 -0
- package/dist/global-install.js +349 -130
- package/dist/hook-health.d.ts +26 -0
- package/dist/hook-launcher.d.ts +57 -0
- package/dist/index.js +511 -252
- package/dist/tui/index.js +422 -167
- package/hooks/conversation.mjs +85 -30
- package/hooks/opencode-plugin.mjs +94 -1
- package/package.json +7 -7
package/dist/commands/index.js
CHANGED
|
@@ -5,59 +5,118 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/api-client/client.ts
|
|
8
|
-
import { readFileSync as
|
|
9
|
-
import { resolve as resolve2, join as
|
|
10
|
-
import { homedir as
|
|
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 =
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const claudeDir =
|
|
27
|
-
const antigravityDir =
|
|
28
|
-
const codexDir = process.env["CODEX_HOME"] ? resolve(process.env["CODEX_HOME"]) :
|
|
29
|
-
const opencodeDir =
|
|
30
|
-
const binDir =
|
|
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:
|
|
41
|
-
backupPath:
|
|
42
|
-
configPath:
|
|
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:
|
|
47
|
-
antigravityBackupPath:
|
|
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:
|
|
52
|
-
codexBackupPath:
|
|
53
|
-
codexConfigPath:
|
|
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:
|
|
60
|
-
opencodePluginPath:
|
|
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 (!
|
|
128
|
+
if (!existsSync2(path)) return out2;
|
|
70
129
|
let raw;
|
|
71
130
|
try {
|
|
72
|
-
raw = JSON.parse(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 (!
|
|
165
|
-
const s = JSON.parse(
|
|
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 =
|
|
194
|
-
if (!
|
|
195
|
-
const c2 = JSON.parse(
|
|
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 (!
|
|
205
|
-
for (const line of
|
|
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
|
|
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(
|
|
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
|
|
1328
|
-
import { homedir as
|
|
1329
|
-
import { join as
|
|
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
|
|
1333
|
-
import { homedir as
|
|
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
|
|
1336
|
-
var DEFAULT_LOCAL_LOG =
|
|
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
|
|
1340
|
-
const caches = readdirSync2(
|
|
1341
|
-
const p =
|
|
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 =
|
|
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(
|
|
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 =
|
|
1358
|
-
if (
|
|
1359
|
-
return
|
|
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 (
|
|
1418
|
-
const
|
|
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 =
|
|
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 (
|
|
1458
|
-
const st =
|
|
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
|
|
1489
|
-
import { homedir as
|
|
1490
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 || !
|
|
1718
|
+
if (cloudOnly || !existsSync6(LOCAL_LOG2)) return;
|
|
1636
1719
|
const rows = [];
|
|
1637
1720
|
for (const line of tailLocal(LOCAL_LOG2)) {
|
|
1638
1721
|
try {
|
package/dist/global-install.d.ts
CHANGED
|
@@ -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
|