@solongate/proxy 0.83.55 → 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.
- package/dist/api-client/audit.d.ts +0 -13
- package/dist/commands/index.js +154 -79
- package/dist/global-install.d.ts +28 -0
- package/dist/global-install.js +344 -129
- package/dist/hook-health.d.ts +26 -0
- package/dist/hook-launcher.d.ts +57 -0
- package/dist/index.js +507 -329
- package/dist/tui/index.js +418 -244
- package/dist/tui/local-log.d.ts +0 -2
- package/hooks/opencode-plugin.mjs +21 -1
- package/package.json +7 -7
|
@@ -12,19 +12,6 @@ export interface AuditQuery {
|
|
|
12
12
|
signal?: 'dlp' | 'ratelimit';
|
|
13
13
|
}
|
|
14
14
|
export declare function list(query?: AuditQuery): Promise<AuditList>;
|
|
15
|
-
export declare function remove(ids: string[]): Promise<{
|
|
16
|
-
deleted: {
|
|
17
|
-
logs: number;
|
|
18
|
-
agents: number;
|
|
19
|
-
};
|
|
20
|
-
}>;
|
|
21
|
-
/** Deletes EVERY audit log of the project (agents/sessions are kept). */
|
|
22
|
-
export declare function removeAll(): Promise<{
|
|
23
|
-
deleted: {
|
|
24
|
-
logs: number;
|
|
25
|
-
agents: number;
|
|
26
|
-
};
|
|
27
|
-
}>;
|
|
28
15
|
export declare function whitelist(id: string, scope?: 'exact' | 'tool'): Promise<{
|
|
29
16
|
ok: true;
|
|
30
17
|
deduped: boolean;
|
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("=");
|
|
@@ -480,19 +539,11 @@ var audit_exports = {};
|
|
|
480
539
|
__export(audit_exports, {
|
|
481
540
|
block: () => block,
|
|
482
541
|
list: () => list2,
|
|
483
|
-
remove: () => remove2,
|
|
484
|
-
removeAll: () => removeAll,
|
|
485
542
|
whitelist: () => whitelist
|
|
486
543
|
});
|
|
487
544
|
function list2(query = {}) {
|
|
488
545
|
return request("GET", "/audit-logs", { query });
|
|
489
546
|
}
|
|
490
|
-
function remove2(ids) {
|
|
491
|
-
return request("DELETE", "/audit-logs", { body: { ids } });
|
|
492
|
-
}
|
|
493
|
-
function removeAll() {
|
|
494
|
-
return request("DELETE", "/audit-logs", { body: { scope: "logs" } });
|
|
495
|
-
}
|
|
496
547
|
function whitelist(id, scope = "exact") {
|
|
497
548
|
return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
|
|
498
549
|
}
|
|
@@ -645,7 +696,7 @@ function sparkline(values) {
|
|
|
645
696
|
}
|
|
646
697
|
|
|
647
698
|
// src/commands/policy.ts
|
|
648
|
-
import { readFileSync as
|
|
699
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
649
700
|
|
|
650
701
|
// src/commands/args.ts
|
|
651
702
|
function parse(argv) {
|
|
@@ -910,7 +961,7 @@ function printRules(rules) {
|
|
|
910
961
|
}
|
|
911
962
|
async function resolveRules(target) {
|
|
912
963
|
if (target.endsWith(".json")) {
|
|
913
|
-
const parsed = JSON.parse(
|
|
964
|
+
const parsed = JSON.parse(readFileSync4(target, "utf-8"));
|
|
914
965
|
return parsed.rules ?? [];
|
|
915
966
|
}
|
|
916
967
|
const p = await api.policies.get(target);
|
|
@@ -1332,39 +1383,39 @@ async function runAgent(argv) {
|
|
|
1332
1383
|
}
|
|
1333
1384
|
|
|
1334
1385
|
// src/commands/doctor.ts
|
|
1335
|
-
import { existsSync as
|
|
1336
|
-
import { homedir as
|
|
1337
|
-
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";
|
|
1338
1389
|
|
|
1339
1390
|
// src/tui/local-log.ts
|
|
1340
|
-
import { closeSync, existsSync as
|
|
1341
|
-
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";
|
|
1342
1393
|
import { createHash } from "crypto";
|
|
1343
|
-
import { isAbsolute, join as
|
|
1344
|
-
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");
|
|
1345
1396
|
function localLogFile() {
|
|
1346
1397
|
try {
|
|
1347
|
-
const
|
|
1348
|
-
const caches = readdirSync2(
|
|
1349
|
-
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);
|
|
1350
1401
|
let mtime = 0;
|
|
1351
1402
|
try {
|
|
1352
|
-
mtime =
|
|
1403
|
+
mtime = statSync3(p).mtimeMs;
|
|
1353
1404
|
} catch {
|
|
1354
1405
|
}
|
|
1355
1406
|
return { p, mtime };
|
|
1356
1407
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
1357
1408
|
for (const { p } of caches) {
|
|
1358
1409
|
try {
|
|
1359
|
-
const c2 = JSON.parse(
|
|
1410
|
+
const c2 = JSON.parse(readFileSync5(p, "utf-8"));
|
|
1360
1411
|
const l = c2?.security?.localLogs;
|
|
1361
1412
|
if (!l || !l.enabled || typeof l.path !== "string" || !l.path.trim()) continue;
|
|
1362
1413
|
const dir = l.path.trim().replace(/[\\/]+$/, "");
|
|
1363
1414
|
if (!dir) continue;
|
|
1364
1415
|
if (!isAbsolute(dir)) return DEFAULT_LOCAL_LOG;
|
|
1365
|
-
const file =
|
|
1366
|
-
if (
|
|
1367
|
-
return
|
|
1416
|
+
const file = join4(dir, "solongate-audit.jsonl");
|
|
1417
|
+
if (existsSync4(file)) return file;
|
|
1418
|
+
return existsSync4(dir) ? file : DEFAULT_LOCAL_LOG;
|
|
1368
1419
|
} catch {
|
|
1369
1420
|
}
|
|
1370
1421
|
}
|
|
@@ -1422,8 +1473,32 @@ async function collectChecks() {
|
|
|
1422
1473
|
ok: claudeReg,
|
|
1423
1474
|
detail: claudeReg ? "guard registered" : "guard NOT registered - run `solongate repair`"
|
|
1424
1475
|
});
|
|
1425
|
-
if (
|
|
1426
|
-
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);
|
|
1427
1502
|
checks.push({
|
|
1428
1503
|
name: "Antigravity hooks",
|
|
1429
1504
|
ok: reg,
|
|
@@ -1451,7 +1526,7 @@ async function collectChecks() {
|
|
|
1451
1526
|
});
|
|
1452
1527
|
}
|
|
1453
1528
|
try {
|
|
1454
|
-
const raw =
|
|
1529
|
+
const raw = readFileSync6(join5(homedir5(), ".solongate", ".key-rejected.json"), "utf-8");
|
|
1455
1530
|
const m = JSON.parse(raw);
|
|
1456
1531
|
const ageMin = m.ts ? Math.round((Date.now() - m.ts) / 6e4) : null;
|
|
1457
1532
|
checks.push({
|
|
@@ -1462,8 +1537,8 @@ async function collectChecks() {
|
|
|
1462
1537
|
} catch {
|
|
1463
1538
|
}
|
|
1464
1539
|
const LOCAL_LOG2 = localLogFile();
|
|
1465
|
-
if (
|
|
1466
|
-
const st =
|
|
1540
|
+
if (existsSync5(LOCAL_LOG2)) {
|
|
1541
|
+
const st = statSync4(LOCAL_LOG2);
|
|
1467
1542
|
const ageMin = (Date.now() - st.mtimeMs) / 6e4;
|
|
1468
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"}` });
|
|
1469
1544
|
} else {
|
|
@@ -1493,16 +1568,16 @@ async function run7(argv) {
|
|
|
1493
1568
|
}
|
|
1494
1569
|
|
|
1495
1570
|
// src/commands/trace.ts
|
|
1496
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
1497
|
-
import { homedir as
|
|
1498
|
-
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";
|
|
1499
1574
|
var USAGE7 = usage("solongate trace", "what the guard saw here", [
|
|
1500
1575
|
["trace", "the last evaluations in this directory"],
|
|
1501
1576
|
["trace --limit N", "how many to show (default 20)"],
|
|
1502
1577
|
["trace --json", "the raw records"]
|
|
1503
1578
|
]);
|
|
1504
1579
|
function readAllRings() {
|
|
1505
|
-
const root =
|
|
1580
|
+
const root = join6(homedir6(), ".solongate", "projects");
|
|
1506
1581
|
let dirs;
|
|
1507
1582
|
try {
|
|
1508
1583
|
dirs = readdirSync3(root);
|
|
@@ -1513,7 +1588,7 @@ function readAllRings() {
|
|
|
1513
1588
|
for (const d of dirs) {
|
|
1514
1589
|
let text;
|
|
1515
1590
|
try {
|
|
1516
|
-
text =
|
|
1591
|
+
text = readFileSync7(join6(root, d, ".eval-ring.jsonl"), "utf-8");
|
|
1517
1592
|
} catch {
|
|
1518
1593
|
continue;
|
|
1519
1594
|
}
|
|
@@ -1593,12 +1668,12 @@ async function run8(argv) {
|
|
|
1593
1668
|
}
|
|
1594
1669
|
|
|
1595
1670
|
// src/commands/watch.ts
|
|
1596
|
-
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";
|
|
1597
1672
|
var trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
1598
1673
|
var time = (ms) => new Date(ms).toTimeString().slice(0, 8);
|
|
1599
1674
|
function tailLocal(file, maxBytes = 131072) {
|
|
1600
1675
|
try {
|
|
1601
|
-
const size =
|
|
1676
|
+
const size = statSync5(file).size;
|
|
1602
1677
|
const start = Math.max(0, size - maxBytes);
|
|
1603
1678
|
const fd = openSync2(file, "r");
|
|
1604
1679
|
const buf = Buffer.alloc(size - start);
|
|
@@ -1640,7 +1715,7 @@ async function run9(argv) {
|
|
|
1640
1715
|
};
|
|
1641
1716
|
const pollLocal = () => {
|
|
1642
1717
|
const LOCAL_LOG2 = localLogFile();
|
|
1643
|
-
if (cloudOnly || !
|
|
1718
|
+
if (cloudOnly || !existsSync6(LOCAL_LOG2)) return;
|
|
1644
1719
|
const rows = [];
|
|
1645
1720
|
for (const line of tailLocal(LOCAL_LOG2)) {
|
|
1646
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
|