@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
package/dist/index.js
CHANGED
|
@@ -6187,14 +6187,213 @@ var init_cli_utils = __esm({
|
|
|
6187
6187
|
}
|
|
6188
6188
|
});
|
|
6189
6189
|
|
|
6190
|
+
// src/hook-launcher.ts
|
|
6191
|
+
function launcherScript(pinnedNode) {
|
|
6192
|
+
const pinned = pinnedNode.replace(/'/g, `'\\''`);
|
|
6193
|
+
return `#!/bin/sh
|
|
6194
|
+
# SolonGate hook launcher \u2014 generated by \`solongate init --global\` / \`repair\`.
|
|
6195
|
+
#
|
|
6196
|
+
# Finds a working node and execs the hook with it. Do not edit: a reinstall
|
|
6197
|
+
# overwrites this file, and on macOS it is chflags-locked besides.
|
|
6198
|
+
#
|
|
6199
|
+
# The reason it exists rather than the hook naming a node directly: an absolute
|
|
6200
|
+
# node path recorded at install time is a Homebrew Cellar path or an nvm version
|
|
6201
|
+
# directory, and both are deleted by a routine upgrade. The hook then could not
|
|
6202
|
+
# start, nothing was enforced, nothing was logged, and every check still said
|
|
6203
|
+
# the guard was registered \u2014 because it was.
|
|
6204
|
+
|
|
6205
|
+
set -u
|
|
6206
|
+
|
|
6207
|
+
script="\${1:-}"
|
|
6208
|
+
[ -n "$script" ] || { echo "solongate: launcher called with no hook script" >&2; exit 1; }
|
|
6209
|
+
shift
|
|
6210
|
+
|
|
6211
|
+
hook=\${script##*/}
|
|
6212
|
+
home=\${HOME:-~}
|
|
6213
|
+
beatdir="$home/.solongate/${BEAT_DIR}"
|
|
6214
|
+
|
|
6215
|
+
# The beat. Written BEFORE node is resolved, because its whole job is to record
|
|
6216
|
+
# that the client invoked us \u2014 which is true even when everything after this
|
|
6217
|
+
# line fails. The file's modification time is the timestamp; nothing is forked
|
|
6218
|
+
# to produce one, and the directory test is a builtin.
|
|
6219
|
+
#
|
|
6220
|
+
# The braces matter. A redirect into a missing directory is reported by the
|
|
6221
|
+
# SHELL, before the command runs, so a \`2>/dev/null\` on the printf alone does
|
|
6222
|
+
# not suppress it \u2014 it lands on the hook's stderr, and Claude Code shows a
|
|
6223
|
+
# hook's stderr to the person using it. Redirecting the group catches both.
|
|
6224
|
+
beat() {
|
|
6225
|
+
[ -d "$beatdir" ] || mkdir -p "$beatdir" 2>/dev/null || return 0
|
|
6226
|
+
{ printf '%s\\n' "$1" > "$beatdir/$hook"; } 2>/dev/null || true
|
|
6227
|
+
}
|
|
6228
|
+
|
|
6229
|
+
try() {
|
|
6230
|
+
[ -n "\${1:-}" ] && [ -x "$1" ]
|
|
6231
|
+
}
|
|
6232
|
+
|
|
6233
|
+
resolve_node() {
|
|
6234
|
+
# Told explicitly.
|
|
6235
|
+
if try "\${SOLONGATE_NODE:-}"; then echo "$SOLONGATE_NODE"; return 0; fi
|
|
6236
|
+
# The node this was installed with.
|
|
6237
|
+
if try '${pinned}'; then echo '${pinned}'; return 0; fi
|
|
6238
|
+
# PATH, when the client gave us one worth having.
|
|
6239
|
+
p=$(command -v node 2>/dev/null) || p=
|
|
6240
|
+
if try "$p"; then echo "$p"; return 0; fi
|
|
6241
|
+
# The fixed locations.
|
|
6242
|
+
for c in ${NODE_CANDIDATES.map((c2) => `"${c2}"`).join(" ")}; do
|
|
6243
|
+
for g in $c; do
|
|
6244
|
+
if try "$g"; then echo "$g"; return 0; fi
|
|
6245
|
+
done
|
|
6246
|
+
done
|
|
6247
|
+
# The version managers.
|
|
6248
|
+
for c in ${NODE_GLOBS.join(" ")}; do
|
|
6249
|
+
for g in $c; do
|
|
6250
|
+
if try "$g"; then echo "$g"; return 0; fi
|
|
6251
|
+
done
|
|
6252
|
+
done
|
|
6253
|
+
return 1
|
|
6254
|
+
}
|
|
6255
|
+
|
|
6256
|
+
node_bin=$(resolve_node) || node_bin=
|
|
6257
|
+
|
|
6258
|
+
# --sg-doctor: report what would be used and leave. This is what \`solongate
|
|
6259
|
+
# doctor\` runs, so the health check exercises the REAL resolution rather than a
|
|
6260
|
+
# copy of it that can drift.
|
|
6261
|
+
if [ "$script" = "--sg-doctor" ] || [ "\${1:-}" = "--sg-doctor" ]; then
|
|
6262
|
+
[ -n "$node_bin" ] && { echo "$node_bin"; exit 0; }
|
|
6263
|
+
echo "no node found" >&2
|
|
6264
|
+
exit 1
|
|
6265
|
+
fi
|
|
6266
|
+
|
|
6267
|
+
if [ -z "$node_bin" ]; then
|
|
6268
|
+
beat "no-node"
|
|
6269
|
+
# Nothing can be enforced without node, and which way to fail is not a
|
|
6270
|
+
# judgement call: the guard is fail-closed, so it refuses the call and says
|
|
6271
|
+
# why. Everything else here only records what already happened, and refusing
|
|
6272
|
+
# a tool call because the log could not be written would be a worse product
|
|
6273
|
+
# than a gap in the log.
|
|
6274
|
+
echo "solongate: no node runtime found, so $hook did not run." >&2
|
|
6275
|
+
echo "solongate: run \\\`solongate repair\\\` in a terminal, or set SOLONGATE_NODE to your node binary." >&2
|
|
6276
|
+
case "$hook" in
|
|
6277
|
+
guard.mjs) echo "solongate: this tool call is REFUSED \u2014 the guard is fail-closed." >&2; exit 2 ;;
|
|
6278
|
+
*) exit 0 ;;
|
|
6279
|
+
esac
|
|
6280
|
+
fi
|
|
6281
|
+
|
|
6282
|
+
beat "$node_bin"
|
|
6283
|
+
exec "$node_bin" "$script" "$@"
|
|
6284
|
+
`;
|
|
6285
|
+
}
|
|
6286
|
+
var NODE_CANDIDATES, NODE_GLOBS, LAUNCHER_NAME, BEAT_DIR;
|
|
6287
|
+
var init_hook_launcher = __esm({
|
|
6288
|
+
"src/hook-launcher.ts"() {
|
|
6289
|
+
"use strict";
|
|
6290
|
+
NODE_CANDIDATES = [
|
|
6291
|
+
// Homebrew, by the STABLE symlink rather than the Cellar path behind it —
|
|
6292
|
+
// the exact distinction this file exists for. Apple Silicon then Intel.
|
|
6293
|
+
"/opt/homebrew/bin/node",
|
|
6294
|
+
"/usr/local/bin/node",
|
|
6295
|
+
// Homebrew's keg-only layout, both prefixes.
|
|
6296
|
+
"/opt/homebrew/opt/node/bin/node",
|
|
6297
|
+
"/usr/local/opt/node/bin/node",
|
|
6298
|
+
// Distro and hand-built.
|
|
6299
|
+
"/usr/bin/node",
|
|
6300
|
+
"/usr/local/n/versions/node/*/bin/node",
|
|
6301
|
+
"/snap/bin/node"
|
|
6302
|
+
];
|
|
6303
|
+
NODE_GLOBS = [
|
|
6304
|
+
// nvm. NVM_DIR is exported by its own shell hook, which a non-interactive
|
|
6305
|
+
// hook environment does not run, so the default location is tried too.
|
|
6306
|
+
'"${NVM_DIR:-}"/versions/node/*/bin/node',
|
|
6307
|
+
'"$home"/.nvm/versions/node/*/bin/node',
|
|
6308
|
+
// fnm, both the XDG location and the macOS Application Support one.
|
|
6309
|
+
'"$home"/.local/share/fnm/node-versions/*/installation/bin/node',
|
|
6310
|
+
'"$home"/Library/Application Support/fnm/node-versions/*/installation/bin/node',
|
|
6311
|
+
// Volta.
|
|
6312
|
+
'"$home"/.volta/tools/image/node/*/bin/node',
|
|
6313
|
+
// asdf, old layout and the current plugin one.
|
|
6314
|
+
'"$home"/.asdf/installs/nodejs/*/bin/node',
|
|
6315
|
+
'"$home"/.asdf/installs/node/*/bin/node'
|
|
6316
|
+
];
|
|
6317
|
+
LAUNCHER_NAME = "sg-run.sh";
|
|
6318
|
+
BEAT_DIR = ".beat";
|
|
6319
|
+
}
|
|
6320
|
+
});
|
|
6321
|
+
|
|
6322
|
+
// src/hook-health.ts
|
|
6323
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
6324
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
|
|
6325
|
+
import { join as join4 } from "path";
|
|
6326
|
+
import { homedir as homedir2 } from "os";
|
|
6327
|
+
function hookCanStart() {
|
|
6328
|
+
const launcher = join4(hooksDir(), LAUNCHER_NAME);
|
|
6329
|
+
if (process.platform === "win32") {
|
|
6330
|
+
const ok = existsSync3(process.execPath);
|
|
6331
|
+
return { ok, node: process.execPath, detail: ok ? process.execPath : `${process.execPath} is gone` };
|
|
6332
|
+
}
|
|
6333
|
+
if (!existsSync3(launcher)) {
|
|
6334
|
+
return { ok: false, detail: "hook launcher missing - run `solongate repair`" };
|
|
6335
|
+
}
|
|
6336
|
+
try {
|
|
6337
|
+
const out2 = execFileSync2("/bin/sh", [launcher, "--sg-doctor"], {
|
|
6338
|
+
encoding: "utf-8",
|
|
6339
|
+
timeout: 5e3,
|
|
6340
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6341
|
+
}).trim();
|
|
6342
|
+
if (!out2) return { ok: false, detail: "launcher found no node runtime - set SOLONGATE_NODE or install node" };
|
|
6343
|
+
return { ok: true, node: out2, detail: out2 };
|
|
6344
|
+
} catch (e) {
|
|
6345
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
6346
|
+
return { ok: false, detail: `launcher will not run: ${msg.split("\n")[0]}` };
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
6349
|
+
function hookBeats() {
|
|
6350
|
+
const dir = join4(sgDir(), BEAT_DIR);
|
|
6351
|
+
const out2 = [];
|
|
6352
|
+
for (const hook of ["guard.mjs", "audit.mjs", "conversation.mjs", "stop.mjs"]) {
|
|
6353
|
+
const f = join4(dir, hook);
|
|
6354
|
+
try {
|
|
6355
|
+
const st = statSync(f);
|
|
6356
|
+
out2.push({ hook, at: st.mtime, node: readFileSync4(f, "utf-8").trim() });
|
|
6357
|
+
} catch {
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6360
|
+
return out2.sort((a, b) => b.at.getTime() - a.at.getTime());
|
|
6361
|
+
}
|
|
6362
|
+
function guardBeat() {
|
|
6363
|
+
return hookBeats().find((b) => b.hook === "guard.mjs") ?? null;
|
|
6364
|
+
}
|
|
6365
|
+
function agoLabel(d) {
|
|
6366
|
+
const s = Math.max(0, Math.round((Date.now() - d.getTime()) / 1e3));
|
|
6367
|
+
if (s < 60) return s <= 3 ? "just now" : `${s}s ago`;
|
|
6368
|
+
if (s < 3600) return `${Math.round(s / 60)}m ago`;
|
|
6369
|
+
if (s < 86400) return `${Math.round(s / 3600)}h ago`;
|
|
6370
|
+
return `${Math.round(s / 86400)}d ago`;
|
|
6371
|
+
}
|
|
6372
|
+
var sgDir, hooksDir;
|
|
6373
|
+
var init_hook_health = __esm({
|
|
6374
|
+
"src/hook-health.ts"() {
|
|
6375
|
+
"use strict";
|
|
6376
|
+
init_hook_launcher();
|
|
6377
|
+
sgDir = () => join4(homedir2(), ".solongate");
|
|
6378
|
+
hooksDir = () => join4(sgDir(), "hooks");
|
|
6379
|
+
}
|
|
6380
|
+
});
|
|
6381
|
+
|
|
6190
6382
|
// src/global-install.ts
|
|
6191
6383
|
var global_install_exports = {};
|
|
6192
6384
|
__export(global_install_exports, {
|
|
6385
|
+
BEAT_DIR: () => BEAT_DIR,
|
|
6386
|
+
LAUNCHER_NAME: () => LAUNCHER_NAME,
|
|
6387
|
+
agoLabel: () => agoLabel,
|
|
6193
6388
|
clearGuardUpdateCheck: () => clearGuardUpdateCheck,
|
|
6194
6389
|
codexDetected: () => codexDetected,
|
|
6195
6390
|
codexHooksStatus: () => codexHooksStatus,
|
|
6196
6391
|
globalPaths: () => globalPaths,
|
|
6392
|
+
guardBeat: () => guardBeat,
|
|
6197
6393
|
guardHookOutdated: () => guardHookOutdated,
|
|
6394
|
+
hookBeats: () => hookBeats,
|
|
6395
|
+
hookCanStart: () => hookCanStart,
|
|
6396
|
+
hookCommandFor: () => hookCommandFor,
|
|
6198
6397
|
installClaudeShim: () => installClaudeShim,
|
|
6199
6398
|
installGlobalQuiet: () => installGlobalQuiet,
|
|
6200
6399
|
installGlobalWithKey: () => installGlobalWithKey,
|
|
@@ -6202,6 +6401,7 @@ __export(global_install_exports, {
|
|
|
6202
6401
|
isCodexGuardInstalled: () => isCodexGuardInstalled,
|
|
6203
6402
|
isGuardInstalled: () => isGuardInstalled,
|
|
6204
6403
|
isOpencodeGuardInstalled: () => isOpencodeGuardInstalled,
|
|
6404
|
+
launcherScript: () => launcherScript,
|
|
6205
6405
|
lockProtected: () => lockProtected,
|
|
6206
6406
|
opencodeDetected: () => opencodeDetected,
|
|
6207
6407
|
removeClaudeShim: () => removeClaudeShim,
|
|
@@ -6212,39 +6412,40 @@ __export(global_install_exports, {
|
|
|
6212
6412
|
sweepStrayScratchDirs: () => sweepStrayScratchDirs,
|
|
6213
6413
|
uninstallGlobalQuiet: () => uninstallGlobalQuiet,
|
|
6214
6414
|
unlockProtected: () => unlockProtected,
|
|
6415
|
+
writeLauncher: () => writeLauncher,
|
|
6215
6416
|
writeProtectedFile: () => writeProtectedFile
|
|
6216
6417
|
});
|
|
6217
|
-
import { readFileSync as
|
|
6218
|
-
import { resolve as resolve3, join as
|
|
6219
|
-
import { homedir as
|
|
6418
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3, rmSync as rmSync2, rmdirSync, readdirSync, statSync as statSync2, chmodSync, copyFileSync, renameSync } from "fs";
|
|
6419
|
+
import { resolve as resolve3, join as join5, dirname, basename } from "path";
|
|
6420
|
+
import { homedir as homedir3 } from "os";
|
|
6220
6421
|
import { createRequire } from "module";
|
|
6221
6422
|
import { fileURLToPath } from "url";
|
|
6222
6423
|
import { createInterface } from "readline";
|
|
6223
|
-
import { execFileSync as
|
|
6424
|
+
import { execFileSync as execFileSync3, spawn } from "child_process";
|
|
6224
6425
|
function lockFile(file) {
|
|
6225
|
-
if (!
|
|
6426
|
+
if (!existsSync4(file)) return;
|
|
6226
6427
|
try {
|
|
6227
6428
|
if (process.platform === "win32") {
|
|
6228
6429
|
try {
|
|
6229
|
-
|
|
6430
|
+
execFileSync3("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE,WDAC,WO)"], { stdio: "ignore" });
|
|
6230
6431
|
} catch {
|
|
6231
6432
|
}
|
|
6232
6433
|
try {
|
|
6233
|
-
|
|
6434
|
+
execFileSync3("icacls", [file, "/grant", "*S-1-3-4:(RX)"], { stdio: "ignore" });
|
|
6234
6435
|
} catch {
|
|
6235
6436
|
}
|
|
6236
6437
|
try {
|
|
6237
|
-
|
|
6438
|
+
execFileSync3("attrib", ["+R", file], { stdio: "ignore" });
|
|
6238
6439
|
} catch {
|
|
6239
6440
|
}
|
|
6240
6441
|
} else if (process.platform === "darwin") {
|
|
6241
6442
|
try {
|
|
6242
|
-
|
|
6443
|
+
execFileSync3("chflags", ["uchg", file], { stdio: "ignore" });
|
|
6243
6444
|
} catch {
|
|
6244
6445
|
}
|
|
6245
6446
|
} else {
|
|
6246
6447
|
try {
|
|
6247
|
-
|
|
6448
|
+
execFileSync3("chattr", ["+i", file], { stdio: "ignore" });
|
|
6248
6449
|
} catch {
|
|
6249
6450
|
}
|
|
6250
6451
|
try {
|
|
@@ -6256,33 +6457,33 @@ function lockFile(file) {
|
|
|
6256
6457
|
}
|
|
6257
6458
|
}
|
|
6258
6459
|
function unlockFile(file) {
|
|
6259
|
-
if (!
|
|
6460
|
+
if (!existsSync4(file)) return;
|
|
6260
6461
|
try {
|
|
6261
6462
|
if (process.platform === "win32") {
|
|
6262
6463
|
try {
|
|
6263
|
-
|
|
6464
|
+
execFileSync3("icacls", [file, "/remove:g", "*S-1-3-4"], { stdio: "ignore" });
|
|
6264
6465
|
} catch {
|
|
6265
6466
|
}
|
|
6266
6467
|
try {
|
|
6267
|
-
|
|
6468
|
+
execFileSync3("icacls", [file, "/remove:d", "*S-1-1-0"], { stdio: "ignore" });
|
|
6268
6469
|
} catch {
|
|
6269
6470
|
}
|
|
6270
6471
|
try {
|
|
6271
|
-
|
|
6472
|
+
execFileSync3("icacls", [file, "/reset"], { stdio: "ignore" });
|
|
6272
6473
|
} catch {
|
|
6273
6474
|
}
|
|
6274
6475
|
try {
|
|
6275
|
-
|
|
6476
|
+
execFileSync3("attrib", ["-R", file], { stdio: "ignore" });
|
|
6276
6477
|
} catch {
|
|
6277
6478
|
}
|
|
6278
6479
|
} else if (process.platform === "darwin") {
|
|
6279
6480
|
try {
|
|
6280
|
-
|
|
6481
|
+
execFileSync3("chflags", ["nouchg", file], { stdio: "ignore" });
|
|
6281
6482
|
} catch {
|
|
6282
6483
|
}
|
|
6283
6484
|
} else {
|
|
6284
6485
|
try {
|
|
6285
|
-
|
|
6486
|
+
execFileSync3("chattr", ["-i", file], { stdio: "ignore" });
|
|
6286
6487
|
} catch {
|
|
6287
6488
|
}
|
|
6288
6489
|
try {
|
|
@@ -6296,14 +6497,19 @@ function unlockFile(file) {
|
|
|
6296
6497
|
function protectedTargets() {
|
|
6297
6498
|
const p = globalPaths();
|
|
6298
6499
|
return [
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6500
|
+
join5(p.hooksDir, "guard.mjs"),
|
|
6501
|
+
join5(p.hooksDir, "audit.mjs"),
|
|
6502
|
+
join5(p.hooksDir, "stop.mjs"),
|
|
6503
|
+
join5(p.hooksDir, "shield.mjs"),
|
|
6303
6504
|
// The conversation record is locked with the rest. A guest who could edit
|
|
6304
6505
|
// it could decide what their host sees them say, which is the same class of
|
|
6305
6506
|
// problem as editing the guard.
|
|
6306
|
-
|
|
6507
|
+
join5(p.hooksDir, "conversation.mjs"),
|
|
6508
|
+
// The launcher is the enforcement path now: every hook command in every
|
|
6509
|
+
// client config runs THROUGH it. A program that could rewrite it could
|
|
6510
|
+
// point every hook at /bin/true and disarm the guard without touching a
|
|
6511
|
+
// single file that used to be locked.
|
|
6512
|
+
join5(p.hooksDir, LAUNCHER_NAME),
|
|
6307
6513
|
p.configPath,
|
|
6308
6514
|
p.settingsPath,
|
|
6309
6515
|
p.antigravityHooksPath,
|
|
@@ -6337,60 +6543,60 @@ function writeProtectedFile(file, contents) {
|
|
|
6337
6543
|
}
|
|
6338
6544
|
}
|
|
6339
6545
|
function globalPaths() {
|
|
6340
|
-
const home =
|
|
6341
|
-
const
|
|
6342
|
-
const
|
|
6343
|
-
const claudeDir =
|
|
6344
|
-
const antigravityDir =
|
|
6345
|
-
const codexDir = process.env["CODEX_HOME"] ? resolve3(process.env["CODEX_HOME"]) :
|
|
6346
|
-
const opencodeDir =
|
|
6347
|
-
const binDir =
|
|
6546
|
+
const home = homedir3();
|
|
6547
|
+
const sgDir2 = join5(home, ".solongate");
|
|
6548
|
+
const hooksDir2 = join5(sgDir2, "hooks");
|
|
6549
|
+
const claudeDir = join5(home, ".claude");
|
|
6550
|
+
const antigravityDir = join5(home, ".gemini", "config");
|
|
6551
|
+
const codexDir = process.env["CODEX_HOME"] ? resolve3(process.env["CODEX_HOME"]) : join5(home, ".codex");
|
|
6552
|
+
const opencodeDir = join5(process.env["XDG_CONFIG_HOME"] ? resolve3(process.env["XDG_CONFIG_HOME"]) : join5(home, ".config"), "opencode");
|
|
6553
|
+
const binDir = join5(sgDir2, "bin");
|
|
6348
6554
|
return {
|
|
6349
6555
|
home,
|
|
6350
|
-
sgDir,
|
|
6351
|
-
hooksDir,
|
|
6556
|
+
sgDir: sgDir2,
|
|
6557
|
+
hooksDir: hooksDir2,
|
|
6352
6558
|
binDir,
|
|
6353
6559
|
claudeDir,
|
|
6354
6560
|
antigravityDir,
|
|
6355
6561
|
codexDir,
|
|
6356
6562
|
opencodeDir,
|
|
6357
|
-
settingsPath:
|
|
6358
|
-
backupPath:
|
|
6359
|
-
configPath:
|
|
6563
|
+
settingsPath: join5(claudeDir, "settings.json"),
|
|
6564
|
+
backupPath: join5(claudeDir, "settings.solongate.bak"),
|
|
6565
|
+
configPath: join5(sgDir2, "cloud-guard.json"),
|
|
6360
6566
|
// Antigravity reads global hooks from ~/.gemini/config/hooks.json. Only the
|
|
6361
6567
|
// guard is registered there (PreToolUse); Antigravity's ALLOW-path audit is
|
|
6362
6568
|
// covered by the passive session-log collector, not a hook.
|
|
6363
|
-
antigravityHooksPath:
|
|
6364
|
-
antigravityBackupPath:
|
|
6569
|
+
antigravityHooksPath: join5(antigravityDir, "hooks.json"),
|
|
6570
|
+
antigravityBackupPath: join5(antigravityDir, "hooks.solongate.bak"),
|
|
6365
6571
|
// Codex reads user-level hooks from ~/.codex/hooks.json (or a [hooks] table
|
|
6366
6572
|
// in ~/.codex/config.toml — we use the JSON file so we never have to rewrite
|
|
6367
6573
|
// the user's TOML, which also holds the hook TRUST state Codex manages).
|
|
6368
|
-
codexHooksPath:
|
|
6369
|
-
codexBackupPath:
|
|
6370
|
-
codexConfigPath:
|
|
6574
|
+
codexHooksPath: join5(codexDir, "hooks.json"),
|
|
6575
|
+
codexBackupPath: join5(codexDir, "hooks.solongate.bak"),
|
|
6576
|
+
codexConfigPath: join5(codexDir, "config.toml"),
|
|
6371
6577
|
// OpenCode scans its plugin folder at startup and loads every module in it.
|
|
6372
6578
|
// Measured on 1.18.10: BOTH `plugin/` and `plugins/` are scanned, so the
|
|
6373
6579
|
// docs and the field reports are each half right. We write the documented
|
|
6374
6580
|
// one. There is nothing to register anywhere — dropping the file IS the
|
|
6375
6581
|
// installation, which also means deleting the file IS the uninstall.
|
|
6376
|
-
opencodePluginDir:
|
|
6377
|
-
opencodePluginPath:
|
|
6582
|
+
opencodePluginDir: join5(opencodeDir, "plugins"),
|
|
6583
|
+
opencodePluginPath: join5(opencodeDir, "plugins", "solongate.js")
|
|
6378
6584
|
};
|
|
6379
6585
|
}
|
|
6380
6586
|
function clearGuardUpdateCheck() {
|
|
6381
6587
|
try {
|
|
6382
|
-
rmSync2(
|
|
6588
|
+
rmSync2(join5(globalPaths().sgDir, ".hook-update-check"), { force: true });
|
|
6383
6589
|
return true;
|
|
6384
6590
|
} catch {
|
|
6385
6591
|
return false;
|
|
6386
6592
|
}
|
|
6387
6593
|
}
|
|
6388
6594
|
function readHook(filename) {
|
|
6389
|
-
return
|
|
6595
|
+
return readFileSync5(join5(HOOKS_DIR, filename), "utf-8");
|
|
6390
6596
|
}
|
|
6391
6597
|
function firstAccountCredential() {
|
|
6392
6598
|
try {
|
|
6393
|
-
const raw = JSON.parse(
|
|
6599
|
+
const raw = JSON.parse(readFileSync5(join5(homedir3(), ".solongate", "accounts.json"), "utf-8"));
|
|
6394
6600
|
if (Array.isArray(raw)) {
|
|
6395
6601
|
const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
|
|
6396
6602
|
if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
|
|
@@ -6400,8 +6606,8 @@ function firstAccountCredential() {
|
|
|
6400
6606
|
return {};
|
|
6401
6607
|
}
|
|
6402
6608
|
function readGuard() {
|
|
6403
|
-
const bundled =
|
|
6404
|
-
return
|
|
6609
|
+
const bundled = join5(HOOKS_DIR, "guard.bundled.mjs");
|
|
6610
|
+
return existsSync4(bundled) ? readFileSync5(bundled, "utf-8") : readHook("guard.mjs");
|
|
6405
6611
|
}
|
|
6406
6612
|
function installGoBinaries(binDir) {
|
|
6407
6613
|
const os_ = process.platform === "win32" ? "win32" : process.platform;
|
|
@@ -6421,10 +6627,10 @@ function installGoBinaries(binDir) {
|
|
|
6421
6627
|
return placed;
|
|
6422
6628
|
}
|
|
6423
6629
|
for (const name of ["solongate-guard", "solongate"]) {
|
|
6424
|
-
const from =
|
|
6425
|
-
const to =
|
|
6630
|
+
const from = join5(pkgDir, name + suffix);
|
|
6631
|
+
const to = join5(binDir, name + suffix);
|
|
6426
6632
|
try {
|
|
6427
|
-
if (!
|
|
6633
|
+
if (!existsSync4(from)) continue;
|
|
6428
6634
|
const tmp = to + ".new";
|
|
6429
6635
|
copyFileSync(from, tmp);
|
|
6430
6636
|
chmodSync(tmp, 493);
|
|
@@ -6436,16 +6642,14 @@ function installGoBinaries(binDir) {
|
|
|
6436
6642
|
return placed;
|
|
6437
6643
|
}
|
|
6438
6644
|
function antigravityHookCommand(guardAbs) {
|
|
6439
|
-
|
|
6440
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
6441
|
-
return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" antigravity "Antigravity"`;
|
|
6645
|
+
return hookCommandFor(dirname(guardAbs), basename(guardAbs), "antigravity", "Antigravity");
|
|
6442
6646
|
}
|
|
6443
6647
|
function installAntigravityGuard(p, guardAbs) {
|
|
6444
6648
|
mkdirSync3(p.antigravityDir, { recursive: true });
|
|
6445
6649
|
let existing = {};
|
|
6446
|
-
if (
|
|
6447
|
-
const raw =
|
|
6448
|
-
if (!
|
|
6650
|
+
if (existsSync4(p.antigravityHooksPath)) {
|
|
6651
|
+
const raw = readFileSync5(p.antigravityHooksPath, "utf-8");
|
|
6652
|
+
if (!existsSync4(p.antigravityBackupPath)) writeFileSync3(p.antigravityBackupPath, raw);
|
|
6449
6653
|
try {
|
|
6450
6654
|
existing = JSON.parse(raw);
|
|
6451
6655
|
} catch {
|
|
@@ -6461,9 +6665,9 @@ function installAntigravityGuard(p, guardAbs) {
|
|
|
6461
6665
|
writeFileSync3(p.antigravityHooksPath, JSON.stringify(merged, null, 2) + "\n");
|
|
6462
6666
|
}
|
|
6463
6667
|
function removeAntigravityGuard(p) {
|
|
6464
|
-
if (!
|
|
6668
|
+
if (!existsSync4(p.antigravityHooksPath)) return;
|
|
6465
6669
|
try {
|
|
6466
|
-
const s = JSON.parse(
|
|
6670
|
+
const s = JSON.parse(readFileSync5(p.antigravityHooksPath, "utf-8"));
|
|
6467
6671
|
if (!(ANTIGRAVITY_GROUP in s)) return;
|
|
6468
6672
|
delete s[ANTIGRAVITY_GROUP];
|
|
6469
6673
|
writeFileSync3(p.antigravityHooksPath, JSON.stringify(s, null, 2) + "\n");
|
|
@@ -6471,8 +6675,10 @@ function removeAntigravityGuard(p) {
|
|
|
6471
6675
|
}
|
|
6472
6676
|
}
|
|
6473
6677
|
function codexHookCommand(scriptAbs) {
|
|
6474
|
-
|
|
6475
|
-
|
|
6678
|
+
if (process.platform === "win32") {
|
|
6679
|
+
return `"${process.execPath.replace(/\\/g, "/")}" "${scriptAbs.replace(/\\/g, "/")}" codex "Codex"`;
|
|
6680
|
+
}
|
|
6681
|
+
return hookCommandFor(dirname(scriptAbs), basename(scriptAbs), "codex", "Codex");
|
|
6476
6682
|
}
|
|
6477
6683
|
function isOurCodexGroup(group) {
|
|
6478
6684
|
const hooks = Array.isArray(group?.hooks) ? group.hooks : [];
|
|
@@ -6480,10 +6686,10 @@ function isOurCodexGroup(group) {
|
|
|
6480
6686
|
}
|
|
6481
6687
|
function readCodexHooksFile(path) {
|
|
6482
6688
|
const out2 = { hooks: {}, rest: {} };
|
|
6483
|
-
if (!
|
|
6689
|
+
if (!existsSync4(path)) return out2;
|
|
6484
6690
|
let raw;
|
|
6485
6691
|
try {
|
|
6486
|
-
raw = JSON.parse(
|
|
6692
|
+
raw = JSON.parse(readFileSync5(path, "utf-8"));
|
|
6487
6693
|
} catch {
|
|
6488
6694
|
return out2;
|
|
6489
6695
|
}
|
|
@@ -6528,10 +6734,10 @@ function writeCodexHooksFile(path, file) {
|
|
|
6528
6734
|
body["hooks"] = hooks;
|
|
6529
6735
|
writeFileSync3(path, JSON.stringify(body, null, 2) + "\n");
|
|
6530
6736
|
}
|
|
6531
|
-
function installCodexGuard(p,
|
|
6737
|
+
function installCodexGuard(p, hooksDir2) {
|
|
6532
6738
|
mkdirSync3(p.codexDir, { recursive: true });
|
|
6533
|
-
if (
|
|
6534
|
-
writeFileSync3(p.codexBackupPath,
|
|
6739
|
+
if (existsSync4(p.codexHooksPath) && !existsSync4(p.codexBackupPath)) {
|
|
6740
|
+
writeFileSync3(p.codexBackupPath, readFileSync5(p.codexHooksPath, "utf-8"));
|
|
6535
6741
|
}
|
|
6536
6742
|
const file = readCodexHooksFile(p.codexHooksPath);
|
|
6537
6743
|
const script = {
|
|
@@ -6555,7 +6761,7 @@ function installCodexGuard(p, hooksDir) {
|
|
|
6555
6761
|
...ev === "Stop" ? {} : { matcher: "*" },
|
|
6556
6762
|
hooks: [{
|
|
6557
6763
|
type: "command",
|
|
6558
|
-
command: codexHookCommand(
|
|
6764
|
+
command: codexHookCommand(join5(hooksDir2, script[ev]).replace(/\\/g, "/")),
|
|
6559
6765
|
timeout: CODEX_TIMEOUT_SEC,
|
|
6560
6766
|
statusMessage: status[ev]
|
|
6561
6767
|
}]
|
|
@@ -6565,7 +6771,7 @@ function installCodexGuard(p, hooksDir) {
|
|
|
6565
6771
|
writeCodexHooksFile(p.codexHooksPath, file);
|
|
6566
6772
|
}
|
|
6567
6773
|
function removeCodexGuard(p) {
|
|
6568
|
-
if (!
|
|
6774
|
+
if (!existsSync4(p.codexHooksPath)) return;
|
|
6569
6775
|
try {
|
|
6570
6776
|
const file = readCodexHooksFile(p.codexHooksPath);
|
|
6571
6777
|
let changed = false;
|
|
@@ -6591,7 +6797,7 @@ function isCodexGuardInstalled() {
|
|
|
6591
6797
|
}
|
|
6592
6798
|
function codexDetected() {
|
|
6593
6799
|
try {
|
|
6594
|
-
return
|
|
6800
|
+
return existsSync4(globalPaths().codexDir);
|
|
6595
6801
|
} catch {
|
|
6596
6802
|
return false;
|
|
6597
6803
|
}
|
|
@@ -6601,7 +6807,7 @@ function codexHooksStatus() {
|
|
|
6601
6807
|
const registered = isCodexGuardInstalled();
|
|
6602
6808
|
let trusted = false, disabled = false;
|
|
6603
6809
|
try {
|
|
6604
|
-
const toml =
|
|
6810
|
+
const toml = readFileSync5(p.codexConfigPath, "utf-8");
|
|
6605
6811
|
trusted = /trusted_hash\s*=/.test(toml);
|
|
6606
6812
|
disabled = /^\s*hooks\s*=\s*false\s*$/m.test(toml);
|
|
6607
6813
|
} catch {
|
|
@@ -6620,10 +6826,10 @@ function removeOpencodeGuard(p) {
|
|
|
6620
6826
|
} catch {
|
|
6621
6827
|
}
|
|
6622
6828
|
}
|
|
6623
|
-
function warmPolicyCache(
|
|
6829
|
+
function warmPolicyCache(hooksDir2, agents) {
|
|
6624
6830
|
for (const agent of agents) {
|
|
6625
6831
|
try {
|
|
6626
|
-
const child = spawn(process.execPath, [
|
|
6832
|
+
const child = spawn(process.execPath, [join5(hooksDir2, "guard.mjs"), agent, "--sg-refresh-policy"], {
|
|
6627
6833
|
detached: true,
|
|
6628
6834
|
stdio: "ignore",
|
|
6629
6835
|
windowsHide: true
|
|
@@ -6637,23 +6843,23 @@ function warmPolicyCache(hooksDir, agents) {
|
|
|
6637
6843
|
}
|
|
6638
6844
|
function isOpencodeGuardInstalled() {
|
|
6639
6845
|
try {
|
|
6640
|
-
return
|
|
6846
|
+
return readFileSync5(globalPaths().opencodePluginPath, "utf-8").includes("tool.execute.before");
|
|
6641
6847
|
} catch {
|
|
6642
6848
|
return false;
|
|
6643
6849
|
}
|
|
6644
6850
|
}
|
|
6645
6851
|
function opencodeDetected() {
|
|
6646
6852
|
try {
|
|
6647
|
-
return
|
|
6853
|
+
return existsSync4(globalPaths().opencodeDir);
|
|
6648
6854
|
} catch {
|
|
6649
6855
|
return false;
|
|
6650
6856
|
}
|
|
6651
6857
|
}
|
|
6652
|
-
function sweepStrayScratchDirs(root =
|
|
6858
|
+
function sweepStrayScratchDirs(root = homedir3(), maxDepth = 6, budget = 4e4) {
|
|
6653
6859
|
let removed = 0;
|
|
6654
6860
|
let visited = 0;
|
|
6655
6861
|
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "build"]);
|
|
6656
|
-
const store =
|
|
6862
|
+
const store = join5(homedir3(), ".solongate");
|
|
6657
6863
|
const walk = (dir, depth) => {
|
|
6658
6864
|
if (depth > maxDepth || visited++ > budget) return;
|
|
6659
6865
|
let entries;
|
|
@@ -6664,11 +6870,11 @@ function sweepStrayScratchDirs(root = homedir2(), maxDepth = 6, budget = 4e4) {
|
|
|
6664
6870
|
}
|
|
6665
6871
|
for (const name of entries) {
|
|
6666
6872
|
if (skip.has(name)) continue;
|
|
6667
|
-
const full =
|
|
6873
|
+
const full = join5(dir, name);
|
|
6668
6874
|
if (full === store) continue;
|
|
6669
6875
|
let isDir = false;
|
|
6670
6876
|
try {
|
|
6671
|
-
isDir =
|
|
6877
|
+
isDir = statSync2(full).isDirectory();
|
|
6672
6878
|
} catch {
|
|
6673
6879
|
continue;
|
|
6674
6880
|
}
|
|
@@ -6679,7 +6885,7 @@ function sweepStrayScratchDirs(root = homedir2(), maxDepth = 6, budget = 4e4) {
|
|
|
6679
6885
|
for (const f of readdirSync(full)) {
|
|
6680
6886
|
if (SCRATCH_FILES.has(f)) {
|
|
6681
6887
|
try {
|
|
6682
|
-
rmSync2(
|
|
6888
|
+
rmSync2(join5(full, f), { force: true });
|
|
6683
6889
|
} catch {
|
|
6684
6890
|
left.push(f);
|
|
6685
6891
|
}
|
|
@@ -6719,12 +6925,12 @@ function runGlobalRestore() {
|
|
|
6719
6925
|
removeOpencodeGuard(p);
|
|
6720
6926
|
} catch {
|
|
6721
6927
|
}
|
|
6722
|
-
if (
|
|
6723
|
-
writeFileSync3(p.settingsPath,
|
|
6928
|
+
if (existsSync4(p.backupPath)) {
|
|
6929
|
+
writeFileSync3(p.settingsPath, readFileSync5(p.backupPath, "utf-8"));
|
|
6724
6930
|
console.log(` Restored ${p.settingsPath} from backup.`);
|
|
6725
|
-
} else if (
|
|
6931
|
+
} else if (existsSync4(p.settingsPath)) {
|
|
6726
6932
|
try {
|
|
6727
|
-
const s = JSON.parse(
|
|
6933
|
+
const s = JSON.parse(readFileSync5(p.settingsPath, "utf-8"));
|
|
6728
6934
|
delete s.hooks;
|
|
6729
6935
|
writeFileSync3(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
6730
6936
|
console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
|
|
@@ -6737,12 +6943,17 @@ function runGlobalRestore() {
|
|
|
6737
6943
|
}
|
|
6738
6944
|
function repairQuiet() {
|
|
6739
6945
|
const p = globalPaths();
|
|
6740
|
-
const has = (f) =>
|
|
6741
|
-
const guardFile =
|
|
6946
|
+
const has = (f) => existsSync4(f);
|
|
6947
|
+
const guardFile = join5(p.hooksDir, "guard.mjs");
|
|
6742
6948
|
const line = (label, ok, yes, no) => ({ label, ok, detail: ok ? yes : no });
|
|
6949
|
+
const runtime = () => {
|
|
6950
|
+
const r2 = hookCanStart();
|
|
6951
|
+
return { label: "hook runtime", ok: r2.ok, detail: r2.ok ? `node ${r2.detail}` : r2.detail };
|
|
6952
|
+
};
|
|
6743
6953
|
const before = [
|
|
6744
6954
|
line("guard hook file", has(guardFile), "present", "MISSING"),
|
|
6745
6955
|
line("cloud credential", has(p.configPath), "present", "MISSING"),
|
|
6956
|
+
runtime(),
|
|
6746
6957
|
line("Claude hooks", isGuardInstalled(), "guard registered", "guard NOT registered"),
|
|
6747
6958
|
line("Antigravity hooks", has(p.antigravityHooksPath), "guard registered", "guard NOT registered"),
|
|
6748
6959
|
line("Codex hooks", isCodexGuardInstalled(), "guard registered", "guard NOT registered"),
|
|
@@ -6752,6 +6963,7 @@ function repairQuiet() {
|
|
|
6752
6963
|
if (!r.ok) return { ok: false, message: r.message, before, after: [], notes: [] };
|
|
6753
6964
|
const after = [
|
|
6754
6965
|
{ label: "guard hook file", ok: true, detail: `present (v${installedGuardVersion() ?? "?"})` },
|
|
6966
|
+
runtime(),
|
|
6755
6967
|
line("Claude hooks", isGuardInstalled(), "guard registered", "NOT registered"),
|
|
6756
6968
|
line("Antigravity hooks", has(p.antigravityHooksPath), "guard registered", "NOT registered"),
|
|
6757
6969
|
line("Codex hooks", isCodexGuardInstalled(), "guard registered", "NOT registered"),
|
|
@@ -6812,7 +7024,7 @@ function installGlobalQuiet() {
|
|
|
6812
7024
|
let apiKey = process.env["SOLONGATE_API_KEY"] || "";
|
|
6813
7025
|
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
6814
7026
|
try {
|
|
6815
|
-
const cfg = JSON.parse(
|
|
7027
|
+
const cfg = JSON.parse(readFileSync5(p.configPath, "utf-8"));
|
|
6816
7028
|
if (cfg && typeof cfg.apiKey === "string") apiKey = apiKey || cfg.apiKey;
|
|
6817
7029
|
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
6818
7030
|
} catch {
|
|
@@ -6828,26 +7040,26 @@ function installGlobalQuiet() {
|
|
|
6828
7040
|
mkdirSync3(p.hooksDir, { recursive: true });
|
|
6829
7041
|
mkdirSync3(p.claudeDir, { recursive: true });
|
|
6830
7042
|
unlockProtected();
|
|
6831
|
-
writeFileSync3(
|
|
7043
|
+
writeFileSync3(join5(p.hooksDir, "guard.mjs"), readGuard());
|
|
6832
7044
|
installGoBinaries(p.binDir);
|
|
6833
|
-
writeFileSync3(
|
|
6834
|
-
writeFileSync3(
|
|
6835
|
-
writeFileSync3(
|
|
6836
|
-
writeFileSync3(
|
|
7045
|
+
writeFileSync3(join5(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
7046
|
+
writeFileSync3(join5(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
7047
|
+
writeFileSync3(join5(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
7048
|
+
writeFileSync3(join5(p.hooksDir, "conversation.mjs"), readHook("conversation.mjs"));
|
|
7049
|
+
writeLauncher(p.hooksDir);
|
|
7050
|
+
writeLauncher(p.hooksDir);
|
|
6837
7051
|
writeFileSync3(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
6838
7052
|
let existing = {};
|
|
6839
|
-
if (
|
|
6840
|
-
const raw =
|
|
6841
|
-
if (!
|
|
7053
|
+
if (existsSync4(p.settingsPath)) {
|
|
7054
|
+
const raw = readFileSync5(p.settingsPath, "utf-8");
|
|
7055
|
+
if (!existsSync4(p.backupPath)) writeFileSync3(p.backupPath, raw);
|
|
6842
7056
|
try {
|
|
6843
7057
|
existing = JSON.parse(raw);
|
|
6844
7058
|
} catch {
|
|
6845
7059
|
existing = {};
|
|
6846
7060
|
}
|
|
6847
7061
|
}
|
|
6848
|
-
const
|
|
6849
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
6850
|
-
const hookCmd = (script) => `${call}"${nodeBin}" "${join4(p.hooksDir, script).replace(/\\/g, "/")}" claude-code "Claude Code"`;
|
|
7062
|
+
const hookCmd = (script) => hookCommandFor(p.hooksDir, script);
|
|
6851
7063
|
const merged = {
|
|
6852
7064
|
...existing,
|
|
6853
7065
|
hooks: {
|
|
@@ -6874,7 +7086,7 @@ function installGlobalQuiet() {
|
|
|
6874
7086
|
};
|
|
6875
7087
|
writeFileSync3(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
6876
7088
|
try {
|
|
6877
|
-
installAntigravityGuard(p,
|
|
7089
|
+
installAntigravityGuard(p, join5(p.hooksDir, "guard.mjs").replace(/\\/g, "/"));
|
|
6878
7090
|
} catch {
|
|
6879
7091
|
}
|
|
6880
7092
|
try {
|
|
@@ -6895,7 +7107,7 @@ function installGlobalQuiet() {
|
|
|
6895
7107
|
function installedGuardVersion() {
|
|
6896
7108
|
try {
|
|
6897
7109
|
const p = globalPaths();
|
|
6898
|
-
const s =
|
|
7110
|
+
const s = readFileSync5(join5(p.hooksDir, "guard.mjs"), "utf-8");
|
|
6899
7111
|
const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
|
|
6900
7112
|
return m ? parseInt(m[1], 10) : null;
|
|
6901
7113
|
} catch {
|
|
@@ -6905,16 +7117,35 @@ function installedGuardVersion() {
|
|
|
6905
7117
|
function guardHookOutdated() {
|
|
6906
7118
|
try {
|
|
6907
7119
|
const p = globalPaths();
|
|
6908
|
-
return
|
|
7120
|
+
return readFileSync5(join5(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
|
|
6909
7121
|
} catch {
|
|
6910
7122
|
return false;
|
|
6911
7123
|
}
|
|
6912
7124
|
}
|
|
7125
|
+
function hookCommandFor(hooksDir2, script, client = "claude-code", label = "Claude Code") {
|
|
7126
|
+
const target = join5(hooksDir2, script).replace(/\\/g, "/");
|
|
7127
|
+
if (process.platform === "win32") {
|
|
7128
|
+
return `& "${process.execPath.replace(/\\/g, "/")}" "${target}" ${client} "${label}"`;
|
|
7129
|
+
}
|
|
7130
|
+
const launcher = join5(hooksDir2, LAUNCHER_NAME).replace(/\\/g, "/");
|
|
7131
|
+
return `/bin/sh "${launcher}" "${target}" ${client} "${label}"`;
|
|
7132
|
+
}
|
|
7133
|
+
function writeLauncher(hooksDir2) {
|
|
7134
|
+
writeFileSync3(join5(hooksDir2, LAUNCHER_NAME), launcherScript(process.execPath));
|
|
7135
|
+
try {
|
|
7136
|
+
chmodSync(join5(hooksDir2, LAUNCHER_NAME), 493);
|
|
7137
|
+
} catch {
|
|
7138
|
+
}
|
|
7139
|
+
try {
|
|
7140
|
+
mkdirSync3(join5(hooksDir2, "..", BEAT_DIR), { recursive: true });
|
|
7141
|
+
} catch {
|
|
7142
|
+
}
|
|
7143
|
+
}
|
|
6913
7144
|
function isGuardInstalled() {
|
|
6914
7145
|
try {
|
|
6915
7146
|
const p = globalPaths();
|
|
6916
|
-
if (!
|
|
6917
|
-
const s = JSON.parse(
|
|
7147
|
+
if (!existsSync4(p.settingsPath)) return false;
|
|
7148
|
+
const s = JSON.parse(readFileSync5(p.settingsPath, "utf-8"));
|
|
6918
7149
|
return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
|
|
6919
7150
|
} catch {
|
|
6920
7151
|
return false;
|
|
@@ -6937,8 +7168,8 @@ function uninstallGlobalQuiet() {
|
|
|
6937
7168
|
removeOpencodeGuard(p);
|
|
6938
7169
|
} catch {
|
|
6939
7170
|
}
|
|
6940
|
-
if (!
|
|
6941
|
-
const s = JSON.parse(
|
|
7171
|
+
if (!existsSync4(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
|
|
7172
|
+
const s = JSON.parse(readFileSync5(p.settingsPath, "utf-8"));
|
|
6942
7173
|
delete s.hooks;
|
|
6943
7174
|
writeFileSync3(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
6944
7175
|
return { ok: true, message: "guard removed (open a new session)" };
|
|
@@ -6952,7 +7183,7 @@ function escapeRe(s) {
|
|
|
6952
7183
|
function resolveRealClaude() {
|
|
6953
7184
|
try {
|
|
6954
7185
|
const finder = process.platform === "win32" ? "where" : "which";
|
|
6955
|
-
const out2 =
|
|
7186
|
+
const out2 = execFileSync3(finder, ["claude"], { encoding: "utf-8" }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
6956
7187
|
if (process.platform === "win32") {
|
|
6957
7188
|
const low = (s) => s.toLowerCase();
|
|
6958
7189
|
return out2.find((l) => low(l).endsWith(".cmd")) || out2.find((l) => low(l).endsWith(".exe")) || out2.find((l) => low(l).endsWith(".bat")) || out2[0] || null;
|
|
@@ -6965,17 +7196,17 @@ function resolveRealClaude() {
|
|
|
6965
7196
|
function shimTargets() {
|
|
6966
7197
|
if (process.platform === "win32") {
|
|
6967
7198
|
try {
|
|
6968
|
-
const prof =
|
|
7199
|
+
const prof = execFileSync3("powershell", ["-NoProfile", "-Command", "$PROFILE.CurrentUserAllHosts"], { encoding: "utf-8" }).trim();
|
|
6969
7200
|
return prof ? [prof] : [];
|
|
6970
7201
|
} catch {
|
|
6971
7202
|
return [];
|
|
6972
7203
|
}
|
|
6973
7204
|
}
|
|
6974
|
-
return [".bashrc", ".zshrc", ".profile"].map((f) =>
|
|
7205
|
+
return [".bashrc", ".zshrc", ".profile"].map((f) => join5(homedir3(), f)).filter((f) => existsSync4(f));
|
|
6975
7206
|
}
|
|
6976
7207
|
function writeShimBlock(file, block2) {
|
|
6977
7208
|
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
6978
|
-
let content =
|
|
7209
|
+
let content = existsSync4(file) ? readFileSync5(file, "utf-8") : "";
|
|
6979
7210
|
content = content.replace(re, "");
|
|
6980
7211
|
if (block2) {
|
|
6981
7212
|
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
@@ -7020,7 +7251,7 @@ async function runGlobalInstall(opts = {}) {
|
|
|
7020
7251
|
let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
|
|
7021
7252
|
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
7022
7253
|
try {
|
|
7023
|
-
const cfg = JSON.parse(
|
|
7254
|
+
const cfg = JSON.parse(readFileSync5(p.configPath, "utf-8"));
|
|
7024
7255
|
if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
|
|
7025
7256
|
} catch {
|
|
7026
7257
|
}
|
|
@@ -7040,19 +7271,19 @@ async function runGlobalInstall(opts = {}) {
|
|
|
7040
7271
|
mkdirSync3(p.hooksDir, { recursive: true });
|
|
7041
7272
|
mkdirSync3(p.claudeDir, { recursive: true });
|
|
7042
7273
|
unlockProtected();
|
|
7043
|
-
writeFileSync3(
|
|
7044
|
-
writeFileSync3(
|
|
7045
|
-
writeFileSync3(
|
|
7046
|
-
writeFileSync3(
|
|
7047
|
-
writeFileSync3(
|
|
7274
|
+
writeFileSync3(join5(p.hooksDir, "guard.mjs"), readGuard());
|
|
7275
|
+
writeFileSync3(join5(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
7276
|
+
writeFileSync3(join5(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
7277
|
+
writeFileSync3(join5(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
7278
|
+
writeFileSync3(join5(p.hooksDir, "conversation.mjs"), readHook("conversation.mjs"));
|
|
7048
7279
|
console.log(` Installed hooks \u2192 ${p.hooksDir}`);
|
|
7049
|
-
installClaudeShim(
|
|
7280
|
+
installClaudeShim(join5(p.hooksDir, "shield.mjs"));
|
|
7050
7281
|
writeFileSync3(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
7051
7282
|
console.log(` Wrote ${p.configPath}`);
|
|
7052
7283
|
let existing = {};
|
|
7053
|
-
if (
|
|
7054
|
-
const raw =
|
|
7055
|
-
if (!
|
|
7284
|
+
if (existsSync4(p.settingsPath)) {
|
|
7285
|
+
const raw = readFileSync5(p.settingsPath, "utf-8");
|
|
7286
|
+
if (!existsSync4(p.backupPath)) {
|
|
7056
7287
|
writeFileSync3(p.backupPath, raw);
|
|
7057
7288
|
console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
|
|
7058
7289
|
}
|
|
@@ -7062,22 +7293,17 @@ async function runGlobalInstall(opts = {}) {
|
|
|
7062
7293
|
existing = {};
|
|
7063
7294
|
}
|
|
7064
7295
|
}
|
|
7065
|
-
const guardAbs =
|
|
7066
|
-
const
|
|
7067
|
-
const stopAbs = join4(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
|
|
7068
|
-
const convAbs = join4(p.hooksDir, "conversation.mjs").replace(/\\/g, "/");
|
|
7069
|
-
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
7070
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
7071
|
-
const hookCmd = (script) => `${call}"${nodeBin}" "${script}" claude-code "Claude Code"`;
|
|
7296
|
+
const guardAbs = join5(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
|
|
7297
|
+
const hookCmd = (script) => hookCommandFor(p.hooksDir, script);
|
|
7072
7298
|
const merged = {
|
|
7073
7299
|
...existing,
|
|
7074
7300
|
hooks: {
|
|
7075
|
-
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(
|
|
7076
|
-
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(
|
|
7077
|
-
UserPromptSubmit: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(
|
|
7301
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("guard.mjs") }] }],
|
|
7302
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("audit.mjs") }] }],
|
|
7303
|
+
UserPromptSubmit: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("conversation.mjs") }] }],
|
|
7078
7304
|
Stop: [
|
|
7079
|
-
{ matcher: "", hooks: [{ type: "command", command: hookCmd(
|
|
7080
|
-
{ matcher: "", hooks: [{ type: "command", command: hookCmd(
|
|
7305
|
+
{ matcher: "", hooks: [{ type: "command", command: hookCmd("stop.mjs") }] },
|
|
7306
|
+
{ matcher: "", hooks: [{ type: "command", command: hookCmd("conversation.mjs") }] }
|
|
7081
7307
|
]
|
|
7082
7308
|
}
|
|
7083
7309
|
};
|
|
@@ -7122,6 +7348,10 @@ var __dirname, HOOKS_DIR, ANTIGRAVITY_GROUP, CODEX_EVENTS, CODEX_TIMEOUT_SEC, SC
|
|
|
7122
7348
|
var init_global_install = __esm({
|
|
7123
7349
|
"src/global-install.ts"() {
|
|
7124
7350
|
"use strict";
|
|
7351
|
+
init_hook_launcher();
|
|
7352
|
+
init_hook_launcher();
|
|
7353
|
+
init_hook_health();
|
|
7354
|
+
init_hook_health();
|
|
7125
7355
|
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
7126
7356
|
HOOKS_DIR = resolve3(__dirname, "..", "hooks");
|
|
7127
7357
|
ANTIGRAVITY_GROUP = "solongate-guard";
|
|
@@ -7152,14 +7382,14 @@ __export(self_update_exports, {
|
|
|
7152
7382
|
tuiUpdateFlow: () => tuiUpdateFlow,
|
|
7153
7383
|
updateNow: () => updateNow
|
|
7154
7384
|
});
|
|
7155
|
-
import { execFile, execFileSync as
|
|
7156
|
-
import { access, mkdirSync as mkdirSync4, openSync, readFileSync as
|
|
7157
|
-
import { homedir as
|
|
7158
|
-
import { dirname as dirname2, join as
|
|
7385
|
+
import { execFile, execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
|
|
7386
|
+
import { access, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync6, writeFileSync as writeFileSync4, constants as FS } from "fs";
|
|
7387
|
+
import { homedir as homedir4 } from "os";
|
|
7388
|
+
import { dirname as dirname2, join as join6, sep } from "path";
|
|
7159
7389
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7160
7390
|
function lockHeldAt() {
|
|
7161
7391
|
try {
|
|
7162
|
-
const ts = Number(
|
|
7392
|
+
const ts = Number(readFileSync6(LOCK_FILE, "utf-8").trim().split(/\s+/)[1] ?? 0);
|
|
7163
7393
|
if (!Number.isFinite(ts) || Date.now() - ts >= LOCK_STALE_MS) return null;
|
|
7164
7394
|
return ts;
|
|
7165
7395
|
} catch {
|
|
@@ -7168,7 +7398,7 @@ function lockHeldAt() {
|
|
|
7168
7398
|
}
|
|
7169
7399
|
function takeInstallLock() {
|
|
7170
7400
|
try {
|
|
7171
|
-
mkdirSync4(
|
|
7401
|
+
mkdirSync4(join6(homedir4(), ".solongate"), { recursive: true });
|
|
7172
7402
|
writeFileSync4(LOCK_FILE, `${process.pid} ${Date.now()}
|
|
7173
7403
|
`);
|
|
7174
7404
|
} catch {
|
|
@@ -7183,7 +7413,7 @@ function releaseInstallLock() {
|
|
|
7183
7413
|
}
|
|
7184
7414
|
function readState() {
|
|
7185
7415
|
try {
|
|
7186
|
-
const s = JSON.parse(
|
|
7416
|
+
const s = JSON.parse(readFileSync6(STATE_FILE, "utf-8"));
|
|
7187
7417
|
return s && typeof s === "object" ? s : {};
|
|
7188
7418
|
} catch {
|
|
7189
7419
|
return {};
|
|
@@ -7191,7 +7421,7 @@ function readState() {
|
|
|
7191
7421
|
}
|
|
7192
7422
|
function writeState(s) {
|
|
7193
7423
|
try {
|
|
7194
|
-
mkdirSync4(
|
|
7424
|
+
mkdirSync4(join6(homedir4(), ".solongate"), { recursive: true });
|
|
7195
7425
|
writeFileSync4(STATE_FILE, JSON.stringify(s));
|
|
7196
7426
|
} catch {
|
|
7197
7427
|
}
|
|
@@ -7212,7 +7442,7 @@ function setAutoUpdate(on) {
|
|
|
7212
7442
|
}
|
|
7213
7443
|
function currentVersion() {
|
|
7214
7444
|
try {
|
|
7215
|
-
const pkg = JSON.parse(
|
|
7445
|
+
const pkg = JSON.parse(readFileSync6(join6(dirname2(fileURLToPath2(import.meta.url)), "..", "package.json"), "utf-8"));
|
|
7216
7446
|
return pkg.version ?? "0.0.0";
|
|
7217
7447
|
} catch {
|
|
7218
7448
|
return "0.0.0";
|
|
@@ -7249,7 +7479,7 @@ async function fetchLatest() {
|
|
|
7249
7479
|
function spawnGlobalInstall(version) {
|
|
7250
7480
|
if (lockHeldAt() !== null) return false;
|
|
7251
7481
|
try {
|
|
7252
|
-
mkdirSync4(
|
|
7482
|
+
mkdirSync4(join6(homedir4(), ".solongate"), { recursive: true });
|
|
7253
7483
|
takeInstallLock();
|
|
7254
7484
|
const log3 = openSync(LOG_FILE, "a");
|
|
7255
7485
|
const p = spawn2("npm", ["install", "-g", `${PKG}@${version}`], {
|
|
@@ -7320,7 +7550,7 @@ async function runUpdateCommand() {
|
|
|
7320
7550
|
}
|
|
7321
7551
|
try {
|
|
7322
7552
|
if (newerThan(latest, cur)) {
|
|
7323
|
-
|
|
7553
|
+
execFileSync4("solongate", ["repair"], {
|
|
7324
7554
|
env: { ...process.env, SOLONGATE_INTERNAL: "1" },
|
|
7325
7555
|
stdio: "inherit",
|
|
7326
7556
|
shell: process.platform === "win32"
|
|
@@ -7337,7 +7567,7 @@ async function runUpdateCommand() {
|
|
|
7337
7567
|
function runGlobalInstall2(version) {
|
|
7338
7568
|
return new Promise((resolve8) => {
|
|
7339
7569
|
try {
|
|
7340
|
-
mkdirSync4(
|
|
7570
|
+
mkdirSync4(join6(homedir4(), ".solongate"), { recursive: true });
|
|
7341
7571
|
execFile(
|
|
7342
7572
|
"npm",
|
|
7343
7573
|
["install", "-g", `${PKG}@${version}`],
|
|
@@ -7530,9 +7760,9 @@ var init_self_update = __esm({
|
|
|
7530
7760
|
PKG = "@solongate/proxy";
|
|
7531
7761
|
CHECK_EVERY_MS = 30 * 60 * 1e3;
|
|
7532
7762
|
ATTEMPT_EVERY_MS = 6 * 60 * 60 * 1e3;
|
|
7533
|
-
STATE_FILE =
|
|
7534
|
-
LOG_FILE =
|
|
7535
|
-
LOCK_FILE =
|
|
7763
|
+
STATE_FILE = join6(homedir4(), ".solongate", ".self-update.json");
|
|
7764
|
+
LOG_FILE = join6(homedir4(), ".solongate", "self-update.log");
|
|
7765
|
+
LOCK_FILE = join6(homedir4(), ".solongate", ".update-install.lock");
|
|
7536
7766
|
LOCK_STALE_MS = 3 * 6e4;
|
|
7537
7767
|
LOCK_WAIT_MS = 9e4;
|
|
7538
7768
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -7552,13 +7782,13 @@ __export(logs_server_daemon_exports, {
|
|
|
7552
7782
|
stopLogsServerDaemon: () => stopLogsServerDaemon
|
|
7553
7783
|
});
|
|
7554
7784
|
import { spawn as spawn3 } from "child_process";
|
|
7555
|
-
import { mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as
|
|
7556
|
-
import { homedir as
|
|
7557
|
-
import { dirname as dirname3, join as
|
|
7785
|
+
import { mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
7786
|
+
import { homedir as homedir5 } from "os";
|
|
7787
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
7558
7788
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7559
7789
|
function readState2() {
|
|
7560
7790
|
try {
|
|
7561
|
-
const s = JSON.parse(
|
|
7791
|
+
const s = JSON.parse(readFileSync7(STATE_FILE2, "utf-8"));
|
|
7562
7792
|
return s && typeof s === "object" ? s : {};
|
|
7563
7793
|
} catch {
|
|
7564
7794
|
return {};
|
|
@@ -7597,7 +7827,7 @@ function startLogsServerDaemon() {
|
|
|
7597
7827
|
try {
|
|
7598
7828
|
mkdirSync5(DIR, { recursive: true });
|
|
7599
7829
|
const log3 = openSync2(LOG_FILE2, "a");
|
|
7600
|
-
const cli =
|
|
7830
|
+
const cli = join7(dirname3(fileURLToPath3(import.meta.url)), "index.js");
|
|
7601
7831
|
const p = spawn3(process.execPath, [cli, "logs-server"], {
|
|
7602
7832
|
detached: true,
|
|
7603
7833
|
stdio: ["ignore", log3, log3],
|
|
@@ -7637,22 +7867,22 @@ var DIR, STATE_FILE2, LOG_FILE2, LOGS_SERVER_PORT;
|
|
|
7637
7867
|
var init_logs_server_daemon = __esm({
|
|
7638
7868
|
"src/logs-server-daemon.ts"() {
|
|
7639
7869
|
"use strict";
|
|
7640
|
-
DIR =
|
|
7641
|
-
STATE_FILE2 =
|
|
7642
|
-
LOG_FILE2 =
|
|
7870
|
+
DIR = join7(homedir5(), ".solongate");
|
|
7871
|
+
STATE_FILE2 = join7(DIR, ".logs-server.json");
|
|
7872
|
+
LOG_FILE2 = join7(DIR, "logs-server.log");
|
|
7643
7873
|
LOGS_SERVER_PORT = 8788;
|
|
7644
7874
|
}
|
|
7645
7875
|
});
|
|
7646
7876
|
|
|
7647
7877
|
// src/tui/config.ts
|
|
7648
|
-
import { readFileSync as
|
|
7649
|
-
import { homedir as
|
|
7650
|
-
import { join as
|
|
7878
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
7879
|
+
import { homedir as homedir6 } from "os";
|
|
7880
|
+
import { join as join8 } from "path";
|
|
7651
7881
|
function loadConfig() {
|
|
7652
7882
|
if (cached) return cached;
|
|
7653
7883
|
const defaults = { notifications: true };
|
|
7654
7884
|
try {
|
|
7655
|
-
const raw =
|
|
7885
|
+
const raw = readFileSync8(join8(homedir6(), ".solongate", "tui-config.json"), "utf-8");
|
|
7656
7886
|
const j = JSON.parse(raw);
|
|
7657
7887
|
cached = {
|
|
7658
7888
|
notifications: j.notifications !== false,
|
|
@@ -7823,16 +8053,16 @@ var init_components = __esm({
|
|
|
7823
8053
|
});
|
|
7824
8054
|
|
|
7825
8055
|
// src/tui/local-log.ts
|
|
7826
|
-
import { closeSync, existsSync as
|
|
7827
|
-
import { homedir as
|
|
8056
|
+
import { closeSync, existsSync as existsSync5, openSync as openSync3, readdirSync as readdirSync2, readFileSync as readFileSync9, readSync, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
8057
|
+
import { homedir as homedir7 } from "os";
|
|
7828
8058
|
import { createHash as createHash2 } from "crypto";
|
|
7829
|
-
import { isAbsolute, join as
|
|
8059
|
+
import { isAbsolute, join as join9 } from "path";
|
|
7830
8060
|
function localLogsSetting() {
|
|
7831
8061
|
const off = { enabled: false, configuredPath: null, usableHere: true, file: DEFAULT_LOCAL_LOG };
|
|
7832
8062
|
try {
|
|
7833
8063
|
for (const cache of policyCachesNewestFirst()) {
|
|
7834
8064
|
try {
|
|
7835
|
-
const c2 = JSON.parse(
|
|
8065
|
+
const c2 = JSON.parse(readFileSync9(cache, "utf-8"));
|
|
7836
8066
|
const l = c2?.security?.localLogs;
|
|
7837
8067
|
if (!l || typeof l.enabled !== "boolean") continue;
|
|
7838
8068
|
if (!l.enabled) return { ...off, configuredPath: typeof l.path === "string" ? l.path.trim() || null : null };
|
|
@@ -7840,8 +8070,8 @@ function localLogsSetting() {
|
|
|
7840
8070
|
const dir = raw.replace(/[\\/]+$/, "");
|
|
7841
8071
|
if (!dir) return { enabled: true, configuredPath: null, usableHere: true, file: DEFAULT_LOCAL_LOG };
|
|
7842
8072
|
if (!isAbsolute(dir)) return { enabled: true, configuredPath: raw, usableHere: false, file: DEFAULT_LOCAL_LOG };
|
|
7843
|
-
const file =
|
|
7844
|
-
const usable =
|
|
8073
|
+
const file = join9(dir, "solongate-audit.jsonl");
|
|
8074
|
+
const usable = existsSync5(file) || existsSync5(dir);
|
|
7845
8075
|
return { enabled: true, configuredPath: raw, usableHere: usable, file: usable ? file : DEFAULT_LOCAL_LOG };
|
|
7846
8076
|
} catch {
|
|
7847
8077
|
}
|
|
@@ -7851,12 +8081,12 @@ function localLogsSetting() {
|
|
|
7851
8081
|
return off;
|
|
7852
8082
|
}
|
|
7853
8083
|
function policyCachesNewestFirst() {
|
|
7854
|
-
const
|
|
7855
|
-
return readdirSync2(
|
|
7856
|
-
const p =
|
|
8084
|
+
const sgDir2 = join9(homedir7(), ".solongate");
|
|
8085
|
+
return readdirSync2(sgDir2).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
|
|
8086
|
+
const p = join9(sgDir2, f);
|
|
7857
8087
|
let mtime = 0;
|
|
7858
8088
|
try {
|
|
7859
|
-
mtime =
|
|
8089
|
+
mtime = statSync3(p).mtimeMs;
|
|
7860
8090
|
} catch {
|
|
7861
8091
|
}
|
|
7862
8092
|
return { p, mtime };
|
|
@@ -7864,27 +8094,27 @@ function policyCachesNewestFirst() {
|
|
|
7864
8094
|
}
|
|
7865
8095
|
function localLogFile() {
|
|
7866
8096
|
try {
|
|
7867
|
-
const
|
|
7868
|
-
const caches = readdirSync2(
|
|
7869
|
-
const p =
|
|
8097
|
+
const sgDir2 = join9(homedir7(), ".solongate");
|
|
8098
|
+
const caches = readdirSync2(sgDir2).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
|
|
8099
|
+
const p = join9(sgDir2, f);
|
|
7870
8100
|
let mtime = 0;
|
|
7871
8101
|
try {
|
|
7872
|
-
mtime =
|
|
8102
|
+
mtime = statSync3(p).mtimeMs;
|
|
7873
8103
|
} catch {
|
|
7874
8104
|
}
|
|
7875
8105
|
return { p, mtime };
|
|
7876
8106
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
7877
8107
|
for (const { p } of caches) {
|
|
7878
8108
|
try {
|
|
7879
|
-
const c2 = JSON.parse(
|
|
8109
|
+
const c2 = JSON.parse(readFileSync9(p, "utf-8"));
|
|
7880
8110
|
const l = c2?.security?.localLogs;
|
|
7881
8111
|
if (!l || !l.enabled || typeof l.path !== "string" || !l.path.trim()) continue;
|
|
7882
8112
|
const dir = l.path.trim().replace(/[\\/]+$/, "");
|
|
7883
8113
|
if (!dir) continue;
|
|
7884
8114
|
if (!isAbsolute(dir)) return DEFAULT_LOCAL_LOG;
|
|
7885
|
-
const file =
|
|
7886
|
-
if (
|
|
7887
|
-
return
|
|
8115
|
+
const file = join9(dir, "solongate-audit.jsonl");
|
|
8116
|
+
if (existsSync5(file)) return file;
|
|
8117
|
+
return existsSync5(dir) ? file : DEFAULT_LOCAL_LOG;
|
|
7888
8118
|
} catch {
|
|
7889
8119
|
}
|
|
7890
8120
|
}
|
|
@@ -7894,11 +8124,11 @@ function localLogFile() {
|
|
|
7894
8124
|
}
|
|
7895
8125
|
function ensureLocalLogOwner(activeApiKey) {
|
|
7896
8126
|
try {
|
|
7897
|
-
const marker =
|
|
8127
|
+
const marker = join9(homedir7(), ".solongate", "local-logs", ".owner");
|
|
7898
8128
|
const want = activeApiKey ? createHash2("sha256").update(activeApiKey).digest("hex").slice(0, 16) : "";
|
|
7899
8129
|
let have = "";
|
|
7900
8130
|
try {
|
|
7901
|
-
have =
|
|
8131
|
+
have = readFileSync9(marker, "utf-8").trim();
|
|
7902
8132
|
} catch {
|
|
7903
8133
|
}
|
|
7904
8134
|
if (have === want) return false;
|
|
@@ -7913,7 +8143,7 @@ function ensureLocalLogOwner(activeApiKey) {
|
|
|
7913
8143
|
}
|
|
7914
8144
|
function tailLines(file, maxBytes = 131072) {
|
|
7915
8145
|
try {
|
|
7916
|
-
const size =
|
|
8146
|
+
const size = statSync3(file).size;
|
|
7917
8147
|
const start = Math.max(0, size - maxBytes);
|
|
7918
8148
|
const fd = openSync3(file, "r");
|
|
7919
8149
|
const buf = Buffer.alloc(size - start);
|
|
@@ -7926,35 +8156,10 @@ function tailLines(file, maxBytes = 131072) {
|
|
|
7926
8156
|
return [];
|
|
7927
8157
|
}
|
|
7928
8158
|
}
|
|
7929
|
-
function deleteLocalEntry(at, tool, session) {
|
|
7930
|
-
try {
|
|
7931
|
-
const file = localLogFile();
|
|
7932
|
-
const lines = readFileSync8(file, "utf-8").split("\n");
|
|
7933
|
-
let removed = 0;
|
|
7934
|
-
const kept = lines.filter((line) => {
|
|
7935
|
-
if (!line.trim()) return false;
|
|
7936
|
-
if (removed) return true;
|
|
7937
|
-
try {
|
|
7938
|
-
const j = JSON.parse(line);
|
|
7939
|
-
const hit = Date.parse(j.ts ?? "") === at && (j.tool ?? "?") === tool && (!session || j.session_id === session);
|
|
7940
|
-
if (hit) {
|
|
7941
|
-
removed++;
|
|
7942
|
-
return false;
|
|
7943
|
-
}
|
|
7944
|
-
} catch {
|
|
7945
|
-
}
|
|
7946
|
-
return true;
|
|
7947
|
-
});
|
|
7948
|
-
if (removed) writeFileSync6(file, kept.length ? kept.join("\n") + "\n" : "");
|
|
7949
|
-
return removed;
|
|
7950
|
-
} catch {
|
|
7951
|
-
return 0;
|
|
7952
|
-
}
|
|
7953
|
-
}
|
|
7954
8159
|
function clearLocalLog() {
|
|
7955
8160
|
try {
|
|
7956
8161
|
const file = localLogFile();
|
|
7957
|
-
const n =
|
|
8162
|
+
const n = readFileSync9(file, "utf-8").split("\n").filter(Boolean).length;
|
|
7958
8163
|
writeFileSync6(file, "");
|
|
7959
8164
|
return n;
|
|
7960
8165
|
} catch {
|
|
@@ -7972,8 +8177,8 @@ function reasonSignals(reason) {
|
|
|
7972
8177
|
}
|
|
7973
8178
|
function ownMark() {
|
|
7974
8179
|
try {
|
|
7975
|
-
const p =
|
|
7976
|
-
const c2 = JSON.parse(
|
|
8180
|
+
const p = join9(homedir7(), ".solongate", "cloud-guard.json");
|
|
8181
|
+
const c2 = JSON.parse(readFileSync9(p, "utf-8"));
|
|
7977
8182
|
return c2?.apiKey ? createHash2("sha256").update(c2.apiKey).digest("hex").slice(0, 16) : "";
|
|
7978
8183
|
} catch {
|
|
7979
8184
|
return "";
|
|
@@ -7997,7 +8202,7 @@ var DEFAULT_LOCAL_LOG, LOCAL_LOG, DLP_REASON, RL_REASON;
|
|
|
7997
8202
|
var init_local_log = __esm({
|
|
7998
8203
|
"src/tui/local-log.ts"() {
|
|
7999
8204
|
"use strict";
|
|
8000
|
-
DEFAULT_LOCAL_LOG =
|
|
8205
|
+
DEFAULT_LOCAL_LOG = join9(homedir7(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
8001
8206
|
LOCAL_LOG = localLogFile();
|
|
8002
8207
|
DLP_REASON = /security layer \(dlp\)/i;
|
|
8003
8208
|
RL_REASON = /security layer \(rate limit\)|rate[- ]?limit(?:ed)?\b.*exceed|exceeded \d+ calls/i;
|
|
@@ -8005,13 +8210,13 @@ var init_local_log = __esm({
|
|
|
8005
8210
|
});
|
|
8006
8211
|
|
|
8007
8212
|
// src/api-client/client.ts
|
|
8008
|
-
import { readFileSync as
|
|
8009
|
-
import { resolve as resolve4, join as
|
|
8010
|
-
import { homedir as
|
|
8213
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync6 } from "fs";
|
|
8214
|
+
import { resolve as resolve4, join as join10 } from "path";
|
|
8215
|
+
import { homedir as homedir8 } from "os";
|
|
8011
8216
|
function listAccounts() {
|
|
8012
8217
|
let list5 = [];
|
|
8013
8218
|
try {
|
|
8014
|
-
const raw = JSON.parse(
|
|
8219
|
+
const raw = JSON.parse(readFileSync10(accountsFile(), "utf-8"));
|
|
8015
8220
|
if (Array.isArray(raw)) list5 = raw.filter((a) => a && typeof a.apiKey === "string");
|
|
8016
8221
|
} catch {
|
|
8017
8222
|
}
|
|
@@ -8025,7 +8230,7 @@ function saveAccount(acc) {
|
|
|
8025
8230
|
try {
|
|
8026
8231
|
const list5 = (() => {
|
|
8027
8232
|
try {
|
|
8028
|
-
const raw = JSON.parse(
|
|
8233
|
+
const raw = JSON.parse(readFileSync10(accountsFile(), "utf-8"));
|
|
8029
8234
|
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
8030
8235
|
} catch {
|
|
8031
8236
|
return [];
|
|
@@ -8033,7 +8238,7 @@ function saveAccount(acc) {
|
|
|
8033
8238
|
})();
|
|
8034
8239
|
const next = list5.filter((a) => a.apiKey !== acc.apiKey);
|
|
8035
8240
|
next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
|
|
8036
|
-
mkdirSync6(
|
|
8241
|
+
mkdirSync6(join10(homedir8(), ".solongate"), { recursive: true });
|
|
8037
8242
|
writeFileSync7(accountsFile(), JSON.stringify(next, null, 2));
|
|
8038
8243
|
} catch {
|
|
8039
8244
|
}
|
|
@@ -8042,7 +8247,7 @@ function removeAccount(apiKey) {
|
|
|
8042
8247
|
try {
|
|
8043
8248
|
const list5 = (() => {
|
|
8044
8249
|
try {
|
|
8045
|
-
const raw = JSON.parse(
|
|
8250
|
+
const raw = JSON.parse(readFileSync10(accountsFile(), "utf-8"));
|
|
8046
8251
|
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
8047
8252
|
} catch {
|
|
8048
8253
|
return [];
|
|
@@ -8064,12 +8269,12 @@ function isActiveAccount(apiKey) {
|
|
|
8064
8269
|
}
|
|
8065
8270
|
function setActiveAccount(creds) {
|
|
8066
8271
|
try {
|
|
8067
|
-
const dir =
|
|
8272
|
+
const dir = join10(homedir8(), ".solongate");
|
|
8068
8273
|
mkdirSync6(dir, { recursive: true });
|
|
8069
|
-
const p =
|
|
8274
|
+
const p = join10(dir, ["cloud", "guard.json"].join("-"));
|
|
8070
8275
|
let existing = {};
|
|
8071
8276
|
try {
|
|
8072
|
-
existing = JSON.parse(
|
|
8277
|
+
existing = JSON.parse(readFileSync10(p, "utf-8"));
|
|
8073
8278
|
} catch {
|
|
8074
8279
|
}
|
|
8075
8280
|
if (!writeProtectedFile(p, JSON.stringify({ ...existing, apiKey: creds.apiKey, apiUrl: creds.apiUrl }, null, 2))) return false;
|
|
@@ -8081,14 +8286,14 @@ function setActiveAccount(creds) {
|
|
|
8081
8286
|
}
|
|
8082
8287
|
function clearActiveCredential() {
|
|
8083
8288
|
try {
|
|
8084
|
-
const p =
|
|
8085
|
-
if (!
|
|
8289
|
+
const p = join10(homedir8(), ".solongate", ["cloud", "guard.json"].join("-"));
|
|
8290
|
+
if (!existsSync6(p)) {
|
|
8086
8291
|
cached2 = null;
|
|
8087
8292
|
return true;
|
|
8088
8293
|
}
|
|
8089
8294
|
let existing = {};
|
|
8090
8295
|
try {
|
|
8091
|
-
existing = JSON.parse(
|
|
8296
|
+
existing = JSON.parse(readFileSync10(p, "utf-8"));
|
|
8092
8297
|
} catch {
|
|
8093
8298
|
}
|
|
8094
8299
|
delete existing.apiKey;
|
|
@@ -8103,9 +8308,9 @@ function clearActiveCredential() {
|
|
|
8103
8308
|
}
|
|
8104
8309
|
function loginCredentialFile() {
|
|
8105
8310
|
try {
|
|
8106
|
-
const p =
|
|
8107
|
-
if (!
|
|
8108
|
-
const c2 = JSON.parse(
|
|
8311
|
+
const p = join10(homedir8(), ".solongate", "cloud-guard.json");
|
|
8312
|
+
if (!existsSync6(p)) return {};
|
|
8313
|
+
const c2 = JSON.parse(readFileSync10(p, "utf-8"));
|
|
8109
8314
|
return c2 && typeof c2 === "object" ? c2 : {};
|
|
8110
8315
|
} catch {
|
|
8111
8316
|
return {};
|
|
@@ -8114,8 +8319,8 @@ function loginCredentialFile() {
|
|
|
8114
8319
|
function dotenvApiKey() {
|
|
8115
8320
|
try {
|
|
8116
8321
|
const envPath = resolve4(".env");
|
|
8117
|
-
if (!
|
|
8118
|
-
for (const line of
|
|
8322
|
+
if (!existsSync6(envPath)) return void 0;
|
|
8323
|
+
for (const line of readFileSync10(envPath, "utf-8").split("\n")) {
|
|
8119
8324
|
const trimmed = line.trim();
|
|
8120
8325
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
8121
8326
|
const eq = trimmed.indexOf("=");
|
|
@@ -8219,7 +8424,7 @@ var init_client = __esm({
|
|
|
8219
8424
|
"use strict";
|
|
8220
8425
|
init_global_install();
|
|
8221
8426
|
DEFAULT_API_URL2 = "https://api.solongate.com";
|
|
8222
|
-
accountsFile = () =>
|
|
8427
|
+
accountsFile = () => join10(homedir8(), ".solongate", "accounts.json");
|
|
8223
8428
|
viewOverride = null;
|
|
8224
8429
|
ApiError = class extends Error {
|
|
8225
8430
|
status;
|
|
@@ -8495,19 +8700,11 @@ var audit_exports = {};
|
|
|
8495
8700
|
__export(audit_exports, {
|
|
8496
8701
|
block: () => block,
|
|
8497
8702
|
list: () => list2,
|
|
8498
|
-
remove: () => remove2,
|
|
8499
|
-
removeAll: () => removeAll,
|
|
8500
8703
|
whitelist: () => whitelist
|
|
8501
8704
|
});
|
|
8502
8705
|
function list2(query = {}) {
|
|
8503
8706
|
return request("GET", "/audit-logs", { query });
|
|
8504
8707
|
}
|
|
8505
|
-
function remove2(ids) {
|
|
8506
|
-
return request("DELETE", "/audit-logs", { body: { ids } });
|
|
8507
|
-
}
|
|
8508
|
-
function removeAll() {
|
|
8509
|
-
return request("DELETE", "/audit-logs", { body: { scope: "logs" } });
|
|
8510
|
-
}
|
|
8511
8708
|
function whitelist(id, scope = "exact") {
|
|
8512
8709
|
return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
|
|
8513
8710
|
}
|
|
@@ -8711,8 +8908,8 @@ var init_hooks = __esm({
|
|
|
8711
8908
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
8712
8909
|
import TextInput from "ink-text-input";
|
|
8713
8910
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
8714
|
-
import { homedir as
|
|
8715
|
-
import { join as
|
|
8911
|
+
import { homedir as homedir9 } from "os";
|
|
8912
|
+
import { join as join11, resolve as resolve5 } from "path";
|
|
8716
8913
|
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
8717
8914
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
8718
8915
|
function extractTarget(detail) {
|
|
@@ -9269,9 +9466,9 @@ function LivePanel({ active: active2 }) {
|
|
|
9269
9466
|
else if (input === "x") toggleSignal("dlp");
|
|
9270
9467
|
else if (input === "r") toggleSignal("ratelimit");
|
|
9271
9468
|
else if (input === "e") {
|
|
9272
|
-
const file =
|
|
9469
|
+
const file = join11(homedir9(), ".solongate", "live-export.jsonl");
|
|
9273
9470
|
try {
|
|
9274
|
-
mkdirSync7(
|
|
9471
|
+
mkdirSync7(join11(homedir9(), ".solongate"), { recursive: true });
|
|
9275
9472
|
writeFileSync8(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
9276
9473
|
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
9277
9474
|
} catch (err2) {
|
|
@@ -9709,7 +9906,7 @@ var init_Live = __esm({
|
|
|
9709
9906
|
}
|
|
9710
9907
|
return h.toString(16);
|
|
9711
9908
|
};
|
|
9712
|
-
RING =
|
|
9909
|
+
RING = join11(homedir9(), ".solongate", "projects", projectKey(resolve5(process.cwd())), ".eval-ring.jsonl");
|
|
9713
9910
|
fmtUp = (ms) => {
|
|
9714
9911
|
const s = Math.floor(ms / 1e3);
|
|
9715
9912
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -10995,8 +11192,8 @@ var init_Dlp = __esm({
|
|
|
10995
11192
|
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
10996
11193
|
import TextInput5 from "ink-text-input";
|
|
10997
11194
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
10998
|
-
import { homedir as
|
|
10999
|
-
import { join as
|
|
11195
|
+
import { homedir as homedir10 } from "os";
|
|
11196
|
+
import { join as join12 } from "path";
|
|
11000
11197
|
import { useState as useState7 } from "react";
|
|
11001
11198
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
11002
11199
|
function loadLocalRows() {
|
|
@@ -11038,7 +11235,6 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
11038
11235
|
const [si, setSi] = useState7(0);
|
|
11039
11236
|
const [sessSearch, setSessSearch] = useState7("");
|
|
11040
11237
|
const [sessSel, setSessSel] = useState7(0);
|
|
11041
|
-
const [confirm, setConfirm] = useState7(null);
|
|
11042
11238
|
const [msg, setMsg] = useState7(null);
|
|
11043
11239
|
const [showHelp, setShowHelp] = useState7(false);
|
|
11044
11240
|
const [frozen, setFrozen] = useState7(false);
|
|
@@ -11120,34 +11316,11 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
11120
11316
|
const currentSess = sessionsFiltered[Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1))];
|
|
11121
11317
|
const logsLoading = (source === "cloud" ? cloudQ.loading : localQ.loading) && !editing;
|
|
11122
11318
|
const sessLoading = source === "cloud" ? agentsQ.loading : localQ.loading;
|
|
11123
|
-
const doDelete = (kind) => {
|
|
11124
|
-
setMsg({ text: "deleting\u2026", level: "ok" });
|
|
11125
|
-
const run12 = async () => {
|
|
11126
|
-
if (source === "cloud") {
|
|
11127
|
-
if (kind === "one") {
|
|
11128
|
-
if (!current) throw new Error("nothing selected");
|
|
11129
|
-
await api.audit.remove([current.id]);
|
|
11130
|
-
} else {
|
|
11131
|
-
await api.audit.removeAll();
|
|
11132
|
-
}
|
|
11133
|
-
cloudQ.reload();
|
|
11134
|
-
statsQ.reloadQuiet();
|
|
11135
|
-
} else {
|
|
11136
|
-
const n = kind === "one" && current ? deleteLocalEntry(current.at, current.tool, current.session) : kind === "all" ? clearLocalLog() : 0;
|
|
11137
|
-
if (!n) throw new Error("entry not found in the local file");
|
|
11138
|
-
localQ.reload();
|
|
11139
|
-
}
|
|
11140
|
-
};
|
|
11141
|
-
run12().then(() => {
|
|
11142
|
-
setMsg({ text: kind === "one" ? "\u2713 entry deleted" : `\u2713 ALL ${source} logs deleted`, level: "ok" });
|
|
11143
|
-
toTop();
|
|
11144
|
-
}).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
|
|
11145
|
-
};
|
|
11146
11319
|
const doExport = (kind) => {
|
|
11147
11320
|
setMsg({ text: "exporting\u2026", level: "ok" });
|
|
11148
11321
|
const run12 = async () => {
|
|
11149
|
-
const dir =
|
|
11150
|
-
const file =
|
|
11322
|
+
const dir = join12(homedir10(), ".solongate");
|
|
11323
|
+
const file = join12(dir, `audit-export-${source}.jsonl`);
|
|
11151
11324
|
let rows2;
|
|
11152
11325
|
if (kind === "page") rows2 = pageRows;
|
|
11153
11326
|
else if (source === "cloud") {
|
|
@@ -11193,8 +11366,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
11193
11366
|
return;
|
|
11194
11367
|
}
|
|
11195
11368
|
if (view === "logs" ? logsLoading : sessLoading) return;
|
|
11196
|
-
if (
|
|
11197
|
-
setConfirm(null);
|
|
11369
|
+
if (msg) {
|
|
11198
11370
|
setMsg(null);
|
|
11199
11371
|
}
|
|
11200
11372
|
if (input === "s") {
|
|
@@ -11258,23 +11430,6 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
11258
11430
|
setGi((n) => (n + 1) % SIGNALS.length);
|
|
11259
11431
|
setPage(0);
|
|
11260
11432
|
toTop();
|
|
11261
|
-
} else if (input === "x") {
|
|
11262
|
-
if (!current) return;
|
|
11263
|
-
if (confirm?.kind !== "one" || confirm.key !== current.id) {
|
|
11264
|
-
setConfirm({ kind: "one", key: current.id });
|
|
11265
|
-
setMsg({ text: `x = delete ONLY the selected entry: ${current.decision} ${current.tool} (${ago(current.at)} ago) \u2014 press x again`, level: "bad" });
|
|
11266
|
-
return;
|
|
11267
|
-
}
|
|
11268
|
-
setConfirm(null);
|
|
11269
|
-
doDelete("one");
|
|
11270
|
-
} else if (input === "X") {
|
|
11271
|
-
if (confirm?.kind !== "all") {
|
|
11272
|
-
setConfirm({ kind: "all", key: "all" });
|
|
11273
|
-
setMsg({ text: `\u26A0 X = delete ALL ${source} logs \u2014 every one of the ${total} matched entries! press X again`, level: "bad" });
|
|
11274
|
-
return;
|
|
11275
|
-
}
|
|
11276
|
-
setConfirm(null);
|
|
11277
|
-
doDelete("all");
|
|
11278
11433
|
} else if (input === "e") doExport("page");
|
|
11279
11434
|
else if (input === "E") doExport("all");
|
|
11280
11435
|
else if (input === "t") setEditing("tool");
|
|
@@ -11588,8 +11743,6 @@ var init_Audit = __esm({
|
|
|
11588
11743
|
["g", "signal filter: all \u2192 dlp \u2192 ratelimit"],
|
|
11589
11744
|
["t / n", "tool / agent filter (type, enter done)"],
|
|
11590
11745
|
["/", "free-text search"],
|
|
11591
|
-
["x", "delete ONLY the selected entry (press x twice)"],
|
|
11592
|
-
["X", "delete ALL matched logs of the source (press X twice)"],
|
|
11593
11746
|
["e", "export this page \u2192 ~/.solongate/audit-export-<src>.jsonl"],
|
|
11594
11747
|
["E", "export ALL matched rows (cloud: up to 10k)"],
|
|
11595
11748
|
["c", "clear every filter (incl. session)"]
|
|
@@ -11751,9 +11904,9 @@ var init_args = __esm({
|
|
|
11751
11904
|
});
|
|
11752
11905
|
|
|
11753
11906
|
// src/commands/doctor.ts
|
|
11754
|
-
import { existsSync as
|
|
11755
|
-
import { homedir as
|
|
11756
|
-
import { join as
|
|
11907
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
|
|
11908
|
+
import { homedir as homedir11 } from "os";
|
|
11909
|
+
import { join as join13 } from "path";
|
|
11757
11910
|
async function collectChecks() {
|
|
11758
11911
|
const checks = [];
|
|
11759
11912
|
if (!isAuthenticated()) {
|
|
@@ -11801,8 +11954,32 @@ async function collectChecks() {
|
|
|
11801
11954
|
ok: claudeReg,
|
|
11802
11955
|
detail: claudeReg ? "guard registered" : "guard NOT registered - run `solongate repair`"
|
|
11803
11956
|
});
|
|
11804
|
-
if (
|
|
11805
|
-
const
|
|
11957
|
+
if (claudeReg) {
|
|
11958
|
+
const start = hookCanStart();
|
|
11959
|
+
checks.push({
|
|
11960
|
+
name: "hook runtime",
|
|
11961
|
+
ok: start.ok,
|
|
11962
|
+
detail: start.ok ? `node ${start.detail}` : `${start.detail} - nothing is being enforced or logged`
|
|
11963
|
+
});
|
|
11964
|
+
const beat = guardBeat();
|
|
11965
|
+
if (!beat) {
|
|
11966
|
+
checks.push({
|
|
11967
|
+
name: "guard fired",
|
|
11968
|
+
ok: "warn",
|
|
11969
|
+
detail: "never - open your agent and run one tool call, then check again"
|
|
11970
|
+
});
|
|
11971
|
+
} else if (beat.node === "no-node") {
|
|
11972
|
+
checks.push({
|
|
11973
|
+
name: "guard fired",
|
|
11974
|
+
ok: false,
|
|
11975
|
+
detail: `${agoLabel(beat.at)}, but found no node to run with - run \`solongate repair\``
|
|
11976
|
+
});
|
|
11977
|
+
} else {
|
|
11978
|
+
checks.push({ name: "guard fired", ok: true, detail: `${agoLabel(beat.at)} \xB7 ${beat.node}` });
|
|
11979
|
+
}
|
|
11980
|
+
}
|
|
11981
|
+
if (existsSync7(globalPaths().antigravityDir)) {
|
|
11982
|
+
const reg = existsSync7(globalPaths().antigravityHooksPath);
|
|
11806
11983
|
checks.push({
|
|
11807
11984
|
name: "Antigravity hooks",
|
|
11808
11985
|
ok: reg,
|
|
@@ -11830,7 +12007,7 @@ async function collectChecks() {
|
|
|
11830
12007
|
});
|
|
11831
12008
|
}
|
|
11832
12009
|
try {
|
|
11833
|
-
const raw =
|
|
12010
|
+
const raw = readFileSync11(join13(homedir11(), ".solongate", ".key-rejected.json"), "utf-8");
|
|
11834
12011
|
const m = JSON.parse(raw);
|
|
11835
12012
|
const ageMin = m.ts ? Math.round((Date.now() - m.ts) / 6e4) : null;
|
|
11836
12013
|
checks.push({
|
|
@@ -11841,8 +12018,8 @@ async function collectChecks() {
|
|
|
11841
12018
|
} catch {
|
|
11842
12019
|
}
|
|
11843
12020
|
const LOCAL_LOG2 = localLogFile();
|
|
11844
|
-
if (
|
|
11845
|
-
const st =
|
|
12021
|
+
if (existsSync7(LOCAL_LOG2)) {
|
|
12022
|
+
const st = statSync4(LOCAL_LOG2);
|
|
11846
12023
|
const ageMin = (Date.now() - st.mtimeMs) / 6e4;
|
|
11847
12024
|
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"}` });
|
|
11848
12025
|
} else {
|
|
@@ -11875,6 +12052,7 @@ var init_doctor = __esm({
|
|
|
11875
12052
|
"use strict";
|
|
11876
12053
|
init_api_client();
|
|
11877
12054
|
init_global_install();
|
|
12055
|
+
init_hook_health();
|
|
11878
12056
|
init_local_log();
|
|
11879
12057
|
init_format();
|
|
11880
12058
|
init_args();
|
|
@@ -12982,8 +13160,8 @@ __export(tui_exports, {
|
|
|
12982
13160
|
launchTui: () => launchTui
|
|
12983
13161
|
});
|
|
12984
13162
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9 } from "fs";
|
|
12985
|
-
import { homedir as
|
|
12986
|
-
import { join as
|
|
13163
|
+
import { homedir as homedir12 } from "os";
|
|
13164
|
+
import { join as join14 } from "path";
|
|
12987
13165
|
import { render } from "ink";
|
|
12988
13166
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
12989
13167
|
async function launchTui() {
|
|
@@ -12994,11 +13172,11 @@ async function launchTui() {
|
|
|
12994
13172
|
return;
|
|
12995
13173
|
}
|
|
12996
13174
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
12997
|
-
const debugLog =
|
|
13175
|
+
const debugLog = join14(homedir12(), ".solongate", "dataroom-debug.log");
|
|
12998
13176
|
const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
|
|
12999
13177
|
const toFile = (level) => (...args) => {
|
|
13000
13178
|
try {
|
|
13001
|
-
mkdirSync9(
|
|
13179
|
+
mkdirSync9(join14(homedir12(), ".solongate"), { recursive: true });
|
|
13002
13180
|
appendFileSync2(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
13003
13181
|
`);
|
|
13004
13182
|
} catch {
|
|
@@ -13025,7 +13203,7 @@ var init_tui = __esm({
|
|
|
13025
13203
|
});
|
|
13026
13204
|
|
|
13027
13205
|
// src/commands/policy.ts
|
|
13028
|
-
import { readFileSync as
|
|
13206
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
13029
13207
|
async function run2(argv) {
|
|
13030
13208
|
const { positionals, flags } = parse(argv);
|
|
13031
13209
|
const sub = positionals[0];
|
|
@@ -13234,7 +13412,7 @@ function printRules(rules) {
|
|
|
13234
13412
|
}
|
|
13235
13413
|
async function resolveRules(target) {
|
|
13236
13414
|
if (target.endsWith(".json")) {
|
|
13237
|
-
const parsed = JSON.parse(
|
|
13415
|
+
const parsed = JSON.parse(readFileSync12(target, "utf-8"));
|
|
13238
13416
|
return parsed.rules ?? [];
|
|
13239
13417
|
}
|
|
13240
13418
|
const p = await api.policies.get(target);
|
|
@@ -13733,11 +13911,11 @@ var init_agents2 = __esm({
|
|
|
13733
13911
|
});
|
|
13734
13912
|
|
|
13735
13913
|
// src/commands/trace.ts
|
|
13736
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
13737
|
-
import { homedir as
|
|
13738
|
-
import { join as
|
|
13914
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync13 } from "fs";
|
|
13915
|
+
import { homedir as homedir13 } from "os";
|
|
13916
|
+
import { join as join15, resolve as resolve6 } from "path";
|
|
13739
13917
|
function readAllRings() {
|
|
13740
|
-
const root =
|
|
13918
|
+
const root = join15(homedir13(), ".solongate", "projects");
|
|
13741
13919
|
let dirs;
|
|
13742
13920
|
try {
|
|
13743
13921
|
dirs = readdirSync3(root);
|
|
@@ -13748,7 +13926,7 @@ function readAllRings() {
|
|
|
13748
13926
|
for (const d of dirs) {
|
|
13749
13927
|
let text;
|
|
13750
13928
|
try {
|
|
13751
|
-
text =
|
|
13929
|
+
text = readFileSync13(join15(root, d, ".eval-ring.jsonl"), "utf-8");
|
|
13752
13930
|
} catch {
|
|
13753
13931
|
continue;
|
|
13754
13932
|
}
|
|
@@ -13841,10 +14019,10 @@ var init_trace = __esm({
|
|
|
13841
14019
|
});
|
|
13842
14020
|
|
|
13843
14021
|
// src/commands/watch.ts
|
|
13844
|
-
import { closeSync as closeSync2, existsSync as
|
|
14022
|
+
import { closeSync as closeSync2, existsSync as existsSync8, openSync as openSync4, readSync as readSync2, statSync as statSync5 } from "fs";
|
|
13845
14023
|
function tailLocal(file, maxBytes = 131072) {
|
|
13846
14024
|
try {
|
|
13847
|
-
const size =
|
|
14025
|
+
const size = statSync5(file).size;
|
|
13848
14026
|
const start = Math.max(0, size - maxBytes);
|
|
13849
14027
|
const fd = openSync4(file, "r");
|
|
13850
14028
|
const buf = Buffer.alloc(size - start);
|
|
@@ -13886,7 +14064,7 @@ async function run9(argv) {
|
|
|
13886
14064
|
};
|
|
13887
14065
|
const pollLocal = () => {
|
|
13888
14066
|
const LOCAL_LOG2 = localLogFile();
|
|
13889
|
-
if (cloudOnly || !
|
|
14067
|
+
if (cloudOnly || !existsSync8(LOCAL_LOG2)) return;
|
|
13890
14068
|
const rows = [];
|
|
13891
14069
|
for (const line of tailLocal(LOCAL_LOG2)) {
|
|
13892
14070
|
try {
|
|
@@ -14158,9 +14336,9 @@ __export(logs_server_exports, {
|
|
|
14158
14336
|
runLogsServer: () => runLogsServer
|
|
14159
14337
|
});
|
|
14160
14338
|
import { createServer } from "http";
|
|
14161
|
-
import { readFileSync as
|
|
14162
|
-
import { resolve as resolve7, join as
|
|
14163
|
-
import { homedir as
|
|
14339
|
+
import { readFileSync as readFileSync14, statSync as statSync6 } from "fs";
|
|
14340
|
+
import { resolve as resolve7, join as join16, isAbsolute as isAbsolute2 } from "path";
|
|
14341
|
+
import { homedir as homedir14 } from "os";
|
|
14164
14342
|
import { readdirSync as readdirSync4 } from "fs";
|
|
14165
14343
|
function allowedOrigins() {
|
|
14166
14344
|
const base = [
|
|
@@ -14177,15 +14355,15 @@ function resolveLocalLogDir(rawPath) {
|
|
|
14177
14355
|
const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
|
|
14178
14356
|
if (!dir) return null;
|
|
14179
14357
|
if (isAbsolute2(dir)) return dir;
|
|
14180
|
-
return resolve7(
|
|
14358
|
+
return resolve7(homedir14(), ".solongate", "local-logs");
|
|
14181
14359
|
}
|
|
14182
14360
|
async function findLogDir() {
|
|
14183
|
-
const base = resolve7(
|
|
14361
|
+
const base = resolve7(homedir14(), ".solongate");
|
|
14184
14362
|
try {
|
|
14185
14363
|
const files = readdirSync4(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
|
|
14186
14364
|
for (const f of files) {
|
|
14187
14365
|
try {
|
|
14188
|
-
const c2 = JSON.parse(
|
|
14366
|
+
const c2 = JSON.parse(readFileSync14(join16(base, f), "utf-8"));
|
|
14189
14367
|
const p = c2?.security?.localLogs?.path;
|
|
14190
14368
|
if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
|
|
14191
14369
|
} catch {
|
|
@@ -14194,7 +14372,7 @@ async function findLogDir() {
|
|
|
14194
14372
|
} catch {
|
|
14195
14373
|
}
|
|
14196
14374
|
try {
|
|
14197
|
-
const cfgRaw =
|
|
14375
|
+
const cfgRaw = readFileSync14(join16(base, "cloud-guard.json"), "utf-8");
|
|
14198
14376
|
const { apiKey, apiUrl } = JSON.parse(cfgRaw);
|
|
14199
14377
|
if (apiKey) {
|
|
14200
14378
|
const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
|
|
@@ -14223,9 +14401,9 @@ function setCors(req, res) {
|
|
|
14223
14401
|
}
|
|
14224
14402
|
function fileInfo(dir) {
|
|
14225
14403
|
if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
|
|
14226
|
-
const file =
|
|
14404
|
+
const file = join16(dir, LOG_FILENAME);
|
|
14227
14405
|
try {
|
|
14228
|
-
const st =
|
|
14406
|
+
const st = statSync6(file);
|
|
14229
14407
|
return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
|
|
14230
14408
|
} catch {
|
|
14231
14409
|
return { file, exists: false, size: 0, mtimeMs: 0 };
|
|
@@ -14306,7 +14484,7 @@ async function runLogsServer() {
|
|
|
14306
14484
|
return;
|
|
14307
14485
|
}
|
|
14308
14486
|
try {
|
|
14309
|
-
const text =
|
|
14487
|
+
const text = readFileSync14(info.file, "utf-8");
|
|
14310
14488
|
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Last-Modified": lastMod, "X-Solongate-Exists": "1" });
|
|
14311
14489
|
res.end(text);
|
|
14312
14490
|
} catch {
|
|
@@ -14359,9 +14537,9 @@ var init_logs_server = __esm({
|
|
|
14359
14537
|
});
|
|
14360
14538
|
|
|
14361
14539
|
// src/index.ts
|
|
14362
|
-
import { readFileSync as
|
|
14540
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
14363
14541
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14364
|
-
import { dirname as dirname4, join as
|
|
14542
|
+
import { dirname as dirname4, join as join17 } from "path";
|
|
14365
14543
|
|
|
14366
14544
|
// src/config.ts
|
|
14367
14545
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -15474,9 +15652,9 @@ function extractFilenames(args) {
|
|
|
15474
15652
|
if (isUrl) return;
|
|
15475
15653
|
if (trimmed.includes("/") || trimmed.includes("\\")) {
|
|
15476
15654
|
const parts = trimmed.split(/[/\\]/);
|
|
15477
|
-
const
|
|
15478
|
-
if (
|
|
15479
|
-
addFilename(
|
|
15655
|
+
const basename2 = parts[parts.length - 1];
|
|
15656
|
+
if (basename2 && looksLikeFilename(basename2)) {
|
|
15657
|
+
addFilename(basename2);
|
|
15480
15658
|
}
|
|
15481
15659
|
}
|
|
15482
15660
|
const lower = trimmed.toLowerCase();
|
|
@@ -15494,9 +15672,9 @@ function extractFilenames(args) {
|
|
|
15494
15672
|
}
|
|
15495
15673
|
if (word.includes("/") || word.includes("\\")) {
|
|
15496
15674
|
const parts = word.split(/[/\\]/);
|
|
15497
|
-
const
|
|
15498
|
-
if (
|
|
15499
|
-
addFilename(
|
|
15675
|
+
const basename2 = parts[parts.length - 1];
|
|
15676
|
+
if (basename2 && looksLikeFilename(basename2)) {
|
|
15677
|
+
addFilename(basename2);
|
|
15500
15678
|
}
|
|
15501
15679
|
}
|
|
15502
15680
|
if (word.includes("*") || word.includes("?")) {
|
|
@@ -17756,8 +17934,8 @@ if (!IS_HUMAN_CLI) {
|
|
|
17756
17934
|
}
|
|
17757
17935
|
var PKG_VERSION = (() => {
|
|
17758
17936
|
try {
|
|
17759
|
-
const p =
|
|
17760
|
-
return JSON.parse(
|
|
17937
|
+
const p = join17(dirname4(fileURLToPath4(import.meta.url)), "..", "package.json");
|
|
17938
|
+
return JSON.parse(readFileSync15(p, "utf-8")).version || "unknown";
|
|
17761
17939
|
} catch {
|
|
17762
17940
|
return "unknown";
|
|
17763
17941
|
}
|