@solongate/proxy 0.83.56 → 0.83.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/index.js +154 -71
- 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 +506 -251
- package/dist/tui/index.js +417 -166
- package/hooks/opencode-plugin.mjs +21 -1
- package/package.json +7 -7
package/dist/tui/index.js
CHANGED
|
@@ -8,38 +8,230 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
// src/
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
// src/hook-launcher.ts
|
|
12
|
+
function launcherScript(pinnedNode) {
|
|
13
|
+
const pinned = pinnedNode.replace(/'/g, `'\\''`);
|
|
14
|
+
return `#!/bin/sh
|
|
15
|
+
# SolonGate hook launcher \u2014 generated by \`solongate init --global\` / \`repair\`.
|
|
16
|
+
#
|
|
17
|
+
# Finds a working node and execs the hook with it. Do not edit: a reinstall
|
|
18
|
+
# overwrites this file, and on macOS it is chflags-locked besides.
|
|
19
|
+
#
|
|
20
|
+
# The reason it exists rather than the hook naming a node directly: an absolute
|
|
21
|
+
# node path recorded at install time is a Homebrew Cellar path or an nvm version
|
|
22
|
+
# directory, and both are deleted by a routine upgrade. The hook then could not
|
|
23
|
+
# start, nothing was enforced, nothing was logged, and every check still said
|
|
24
|
+
# the guard was registered \u2014 because it was.
|
|
25
|
+
|
|
26
|
+
set -u
|
|
27
|
+
|
|
28
|
+
script="\${1:-}"
|
|
29
|
+
[ -n "$script" ] || { echo "solongate: launcher called with no hook script" >&2; exit 1; }
|
|
30
|
+
shift
|
|
31
|
+
|
|
32
|
+
hook=\${script##*/}
|
|
33
|
+
home=\${HOME:-~}
|
|
34
|
+
beatdir="$home/.solongate/${BEAT_DIR}"
|
|
35
|
+
|
|
36
|
+
# The beat. Written BEFORE node is resolved, because its whole job is to record
|
|
37
|
+
# that the client invoked us \u2014 which is true even when everything after this
|
|
38
|
+
# line fails. The file's modification time is the timestamp; nothing is forked
|
|
39
|
+
# to produce one, and the directory test is a builtin.
|
|
40
|
+
#
|
|
41
|
+
# The braces matter. A redirect into a missing directory is reported by the
|
|
42
|
+
# SHELL, before the command runs, so a \`2>/dev/null\` on the printf alone does
|
|
43
|
+
# not suppress it \u2014 it lands on the hook's stderr, and Claude Code shows a
|
|
44
|
+
# hook's stderr to the person using it. Redirecting the group catches both.
|
|
45
|
+
beat() {
|
|
46
|
+
[ -d "$beatdir" ] || mkdir -p "$beatdir" 2>/dev/null || return 0
|
|
47
|
+
{ printf '%s\\n' "$1" > "$beatdir/$hook"; } 2>/dev/null || true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try() {
|
|
51
|
+
[ -n "\${1:-}" ] && [ -x "$1" ]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
resolve_node() {
|
|
55
|
+
# Told explicitly.
|
|
56
|
+
if try "\${SOLONGATE_NODE:-}"; then echo "$SOLONGATE_NODE"; return 0; fi
|
|
57
|
+
# The node this was installed with.
|
|
58
|
+
if try '${pinned}'; then echo '${pinned}'; return 0; fi
|
|
59
|
+
# PATH, when the client gave us one worth having.
|
|
60
|
+
p=$(command -v node 2>/dev/null) || p=
|
|
61
|
+
if try "$p"; then echo "$p"; return 0; fi
|
|
62
|
+
# The fixed locations.
|
|
63
|
+
for c in ${NODE_CANDIDATES.map((c2) => `"${c2}"`).join(" ")}; do
|
|
64
|
+
for g in $c; do
|
|
65
|
+
if try "$g"; then echo "$g"; return 0; fi
|
|
66
|
+
done
|
|
67
|
+
done
|
|
68
|
+
# The version managers.
|
|
69
|
+
for c in ${NODE_GLOBS.join(" ")}; do
|
|
70
|
+
for g in $c; do
|
|
71
|
+
if try "$g"; then echo "$g"; return 0; fi
|
|
72
|
+
done
|
|
73
|
+
done
|
|
74
|
+
return 1
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
node_bin=$(resolve_node) || node_bin=
|
|
78
|
+
|
|
79
|
+
# --sg-doctor: report what would be used and leave. This is what \`solongate
|
|
80
|
+
# doctor\` runs, so the health check exercises the REAL resolution rather than a
|
|
81
|
+
# copy of it that can drift.
|
|
82
|
+
if [ "$script" = "--sg-doctor" ] || [ "\${1:-}" = "--sg-doctor" ]; then
|
|
83
|
+
[ -n "$node_bin" ] && { echo "$node_bin"; exit 0; }
|
|
84
|
+
echo "no node found" >&2
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [ -z "$node_bin" ]; then
|
|
89
|
+
beat "no-node"
|
|
90
|
+
# Nothing can be enforced without node, and which way to fail is not a
|
|
91
|
+
# judgement call: the guard is fail-closed, so it refuses the call and says
|
|
92
|
+
# why. Everything else here only records what already happened, and refusing
|
|
93
|
+
# a tool call because the log could not be written would be a worse product
|
|
94
|
+
# than a gap in the log.
|
|
95
|
+
echo "solongate: no node runtime found, so $hook did not run." >&2
|
|
96
|
+
echo "solongate: run \\\`solongate repair\\\` in a terminal, or set SOLONGATE_NODE to your node binary." >&2
|
|
97
|
+
case "$hook" in
|
|
98
|
+
guard.mjs) echo "solongate: this tool call is REFUSED \u2014 the guard is fail-closed." >&2; exit 2 ;;
|
|
99
|
+
*) exit 0 ;;
|
|
100
|
+
esac
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
beat "$node_bin"
|
|
104
|
+
exec "$node_bin" "$script" "$@"
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
var NODE_CANDIDATES, NODE_GLOBS, LAUNCHER_NAME, BEAT_DIR;
|
|
108
|
+
var init_hook_launcher = __esm({
|
|
109
|
+
"src/hook-launcher.ts"() {
|
|
110
|
+
"use strict";
|
|
111
|
+
NODE_CANDIDATES = [
|
|
112
|
+
// Homebrew, by the STABLE symlink rather than the Cellar path behind it —
|
|
113
|
+
// the exact distinction this file exists for. Apple Silicon then Intel.
|
|
114
|
+
"/opt/homebrew/bin/node",
|
|
115
|
+
"/usr/local/bin/node",
|
|
116
|
+
// Homebrew's keg-only layout, both prefixes.
|
|
117
|
+
"/opt/homebrew/opt/node/bin/node",
|
|
118
|
+
"/usr/local/opt/node/bin/node",
|
|
119
|
+
// Distro and hand-built.
|
|
120
|
+
"/usr/bin/node",
|
|
121
|
+
"/usr/local/n/versions/node/*/bin/node",
|
|
122
|
+
"/snap/bin/node"
|
|
123
|
+
];
|
|
124
|
+
NODE_GLOBS = [
|
|
125
|
+
// nvm. NVM_DIR is exported by its own shell hook, which a non-interactive
|
|
126
|
+
// hook environment does not run, so the default location is tried too.
|
|
127
|
+
'"${NVM_DIR:-}"/versions/node/*/bin/node',
|
|
128
|
+
'"$home"/.nvm/versions/node/*/bin/node',
|
|
129
|
+
// fnm, both the XDG location and the macOS Application Support one.
|
|
130
|
+
'"$home"/.local/share/fnm/node-versions/*/installation/bin/node',
|
|
131
|
+
'"$home"/Library/Application Support/fnm/node-versions/*/installation/bin/node',
|
|
132
|
+
// Volta.
|
|
133
|
+
'"$home"/.volta/tools/image/node/*/bin/node',
|
|
134
|
+
// asdf, old layout and the current plugin one.
|
|
135
|
+
'"$home"/.asdf/installs/nodejs/*/bin/node',
|
|
136
|
+
'"$home"/.asdf/installs/node/*/bin/node'
|
|
137
|
+
];
|
|
138
|
+
LAUNCHER_NAME = "sg-run.sh";
|
|
139
|
+
BEAT_DIR = ".beat";
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// src/hook-health.ts
|
|
144
|
+
import { execFileSync } from "child_process";
|
|
145
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
146
|
+
import { join as join3 } from "path";
|
|
14
147
|
import { homedir as homedir3 } from "os";
|
|
148
|
+
function hookCanStart() {
|
|
149
|
+
const launcher = join3(hooksDir(), LAUNCHER_NAME);
|
|
150
|
+
if (process.platform === "win32") {
|
|
151
|
+
const ok = existsSync2(process.execPath);
|
|
152
|
+
return { ok, node: process.execPath, detail: ok ? process.execPath : `${process.execPath} is gone` };
|
|
153
|
+
}
|
|
154
|
+
if (!existsSync2(launcher)) {
|
|
155
|
+
return { ok: false, detail: "hook launcher missing - run `solongate repair`" };
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const out = execFileSync("/bin/sh", [launcher, "--sg-doctor"], {
|
|
159
|
+
encoding: "utf-8",
|
|
160
|
+
timeout: 5e3,
|
|
161
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
162
|
+
}).trim();
|
|
163
|
+
if (!out) return { ok: false, detail: "launcher found no node runtime - set SOLONGATE_NODE or install node" };
|
|
164
|
+
return { ok: true, node: out, detail: out };
|
|
165
|
+
} catch (e) {
|
|
166
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
167
|
+
return { ok: false, detail: `launcher will not run: ${msg.split("\n")[0]}` };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function hookBeats() {
|
|
171
|
+
const dir = join3(sgDir(), BEAT_DIR);
|
|
172
|
+
const out = [];
|
|
173
|
+
for (const hook of ["guard.mjs", "audit.mjs", "conversation.mjs", "stop.mjs"]) {
|
|
174
|
+
const f = join3(dir, hook);
|
|
175
|
+
try {
|
|
176
|
+
const st = statSync2(f);
|
|
177
|
+
out.push({ hook, at: st.mtime, node: readFileSync3(f, "utf-8").trim() });
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return out.sort((a, b) => b.at.getTime() - a.at.getTime());
|
|
182
|
+
}
|
|
183
|
+
function guardBeat() {
|
|
184
|
+
return hookBeats().find((b) => b.hook === "guard.mjs") ?? null;
|
|
185
|
+
}
|
|
186
|
+
function agoLabel(d) {
|
|
187
|
+
const s = Math.max(0, Math.round((Date.now() - d.getTime()) / 1e3));
|
|
188
|
+
if (s < 60) return s <= 3 ? "just now" : `${s}s ago`;
|
|
189
|
+
if (s < 3600) return `${Math.round(s / 60)}m ago`;
|
|
190
|
+
if (s < 86400) return `${Math.round(s / 3600)}h ago`;
|
|
191
|
+
return `${Math.round(s / 86400)}d ago`;
|
|
192
|
+
}
|
|
193
|
+
var sgDir, hooksDir;
|
|
194
|
+
var init_hook_health = __esm({
|
|
195
|
+
"src/hook-health.ts"() {
|
|
196
|
+
"use strict";
|
|
197
|
+
init_hook_launcher();
|
|
198
|
+
sgDir = () => join3(homedir3(), ".solongate");
|
|
199
|
+
hooksDir = () => join3(sgDir(), "hooks");
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// src/global-install.ts
|
|
204
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync, rmSync, rmdirSync, readdirSync as readdirSync2, statSync as statSync3, chmodSync, copyFileSync, renameSync } from "fs";
|
|
205
|
+
import { resolve, join as join4, dirname, basename } from "path";
|
|
206
|
+
import { homedir as homedir4 } from "os";
|
|
15
207
|
import { createRequire } from "module";
|
|
16
208
|
import { fileURLToPath } from "url";
|
|
17
209
|
import { createInterface } from "readline";
|
|
18
|
-
import { execFileSync, spawn } from "child_process";
|
|
210
|
+
import { execFileSync as execFileSync2, spawn } from "child_process";
|
|
19
211
|
function lockFile(file) {
|
|
20
|
-
if (!
|
|
212
|
+
if (!existsSync3(file)) return;
|
|
21
213
|
try {
|
|
22
214
|
if (process.platform === "win32") {
|
|
23
215
|
try {
|
|
24
|
-
|
|
216
|
+
execFileSync2("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE,WDAC,WO)"], { stdio: "ignore" });
|
|
25
217
|
} catch {
|
|
26
218
|
}
|
|
27
219
|
try {
|
|
28
|
-
|
|
220
|
+
execFileSync2("icacls", [file, "/grant", "*S-1-3-4:(RX)"], { stdio: "ignore" });
|
|
29
221
|
} catch {
|
|
30
222
|
}
|
|
31
223
|
try {
|
|
32
|
-
|
|
224
|
+
execFileSync2("attrib", ["+R", file], { stdio: "ignore" });
|
|
33
225
|
} catch {
|
|
34
226
|
}
|
|
35
227
|
} else if (process.platform === "darwin") {
|
|
36
228
|
try {
|
|
37
|
-
|
|
229
|
+
execFileSync2("chflags", ["uchg", file], { stdio: "ignore" });
|
|
38
230
|
} catch {
|
|
39
231
|
}
|
|
40
232
|
} else {
|
|
41
233
|
try {
|
|
42
|
-
|
|
234
|
+
execFileSync2("chattr", ["+i", file], { stdio: "ignore" });
|
|
43
235
|
} catch {
|
|
44
236
|
}
|
|
45
237
|
try {
|
|
@@ -51,33 +243,33 @@ function lockFile(file) {
|
|
|
51
243
|
}
|
|
52
244
|
}
|
|
53
245
|
function unlockFile(file) {
|
|
54
|
-
if (!
|
|
246
|
+
if (!existsSync3(file)) return;
|
|
55
247
|
try {
|
|
56
248
|
if (process.platform === "win32") {
|
|
57
249
|
try {
|
|
58
|
-
|
|
250
|
+
execFileSync2("icacls", [file, "/remove:g", "*S-1-3-4"], { stdio: "ignore" });
|
|
59
251
|
} catch {
|
|
60
252
|
}
|
|
61
253
|
try {
|
|
62
|
-
|
|
254
|
+
execFileSync2("icacls", [file, "/remove:d", "*S-1-1-0"], { stdio: "ignore" });
|
|
63
255
|
} catch {
|
|
64
256
|
}
|
|
65
257
|
try {
|
|
66
|
-
|
|
258
|
+
execFileSync2("icacls", [file, "/reset"], { stdio: "ignore" });
|
|
67
259
|
} catch {
|
|
68
260
|
}
|
|
69
261
|
try {
|
|
70
|
-
|
|
262
|
+
execFileSync2("attrib", ["-R", file], { stdio: "ignore" });
|
|
71
263
|
} catch {
|
|
72
264
|
}
|
|
73
265
|
} else if (process.platform === "darwin") {
|
|
74
266
|
try {
|
|
75
|
-
|
|
267
|
+
execFileSync2("chflags", ["nouchg", file], { stdio: "ignore" });
|
|
76
268
|
} catch {
|
|
77
269
|
}
|
|
78
270
|
} else {
|
|
79
271
|
try {
|
|
80
|
-
|
|
272
|
+
execFileSync2("chattr", ["-i", file], { stdio: "ignore" });
|
|
81
273
|
} catch {
|
|
82
274
|
}
|
|
83
275
|
try {
|
|
@@ -91,14 +283,19 @@ function unlockFile(file) {
|
|
|
91
283
|
function protectedTargets() {
|
|
92
284
|
const p = globalPaths();
|
|
93
285
|
return [
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
286
|
+
join4(p.hooksDir, "guard.mjs"),
|
|
287
|
+
join4(p.hooksDir, "audit.mjs"),
|
|
288
|
+
join4(p.hooksDir, "stop.mjs"),
|
|
289
|
+
join4(p.hooksDir, "shield.mjs"),
|
|
98
290
|
// The conversation record is locked with the rest. A guest who could edit
|
|
99
291
|
// it could decide what their host sees them say, which is the same class of
|
|
100
292
|
// problem as editing the guard.
|
|
101
|
-
|
|
293
|
+
join4(p.hooksDir, "conversation.mjs"),
|
|
294
|
+
// The launcher is the enforcement path now: every hook command in every
|
|
295
|
+
// client config runs THROUGH it. A program that could rewrite it could
|
|
296
|
+
// point every hook at /bin/true and disarm the guard without touching a
|
|
297
|
+
// single file that used to be locked.
|
|
298
|
+
join4(p.hooksDir, LAUNCHER_NAME),
|
|
102
299
|
p.configPath,
|
|
103
300
|
p.settingsPath,
|
|
104
301
|
p.antigravityHooksPath,
|
|
@@ -132,52 +329,52 @@ function writeProtectedFile(file, contents) {
|
|
|
132
329
|
}
|
|
133
330
|
}
|
|
134
331
|
function globalPaths() {
|
|
135
|
-
const home =
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const claudeDir =
|
|
139
|
-
const antigravityDir =
|
|
140
|
-
const codexDir = process.env["CODEX_HOME"] ? resolve(process.env["CODEX_HOME"]) :
|
|
141
|
-
const opencodeDir =
|
|
142
|
-
const binDir =
|
|
332
|
+
const home = homedir4();
|
|
333
|
+
const sgDir2 = join4(home, ".solongate");
|
|
334
|
+
const hooksDir2 = join4(sgDir2, "hooks");
|
|
335
|
+
const claudeDir = join4(home, ".claude");
|
|
336
|
+
const antigravityDir = join4(home, ".gemini", "config");
|
|
337
|
+
const codexDir = process.env["CODEX_HOME"] ? resolve(process.env["CODEX_HOME"]) : join4(home, ".codex");
|
|
338
|
+
const opencodeDir = join4(process.env["XDG_CONFIG_HOME"] ? resolve(process.env["XDG_CONFIG_HOME"]) : join4(home, ".config"), "opencode");
|
|
339
|
+
const binDir = join4(sgDir2, "bin");
|
|
143
340
|
return {
|
|
144
341
|
home,
|
|
145
|
-
sgDir,
|
|
146
|
-
hooksDir,
|
|
342
|
+
sgDir: sgDir2,
|
|
343
|
+
hooksDir: hooksDir2,
|
|
147
344
|
binDir,
|
|
148
345
|
claudeDir,
|
|
149
346
|
antigravityDir,
|
|
150
347
|
codexDir,
|
|
151
348
|
opencodeDir,
|
|
152
|
-
settingsPath:
|
|
153
|
-
backupPath:
|
|
154
|
-
configPath:
|
|
349
|
+
settingsPath: join4(claudeDir, "settings.json"),
|
|
350
|
+
backupPath: join4(claudeDir, "settings.solongate.bak"),
|
|
351
|
+
configPath: join4(sgDir2, "cloud-guard.json"),
|
|
155
352
|
// Antigravity reads global hooks from ~/.gemini/config/hooks.json. Only the
|
|
156
353
|
// guard is registered there (PreToolUse); Antigravity's ALLOW-path audit is
|
|
157
354
|
// covered by the passive session-log collector, not a hook.
|
|
158
|
-
antigravityHooksPath:
|
|
159
|
-
antigravityBackupPath:
|
|
355
|
+
antigravityHooksPath: join4(antigravityDir, "hooks.json"),
|
|
356
|
+
antigravityBackupPath: join4(antigravityDir, "hooks.solongate.bak"),
|
|
160
357
|
// Codex reads user-level hooks from ~/.codex/hooks.json (or a [hooks] table
|
|
161
358
|
// in ~/.codex/config.toml — we use the JSON file so we never have to rewrite
|
|
162
359
|
// the user's TOML, which also holds the hook TRUST state Codex manages).
|
|
163
|
-
codexHooksPath:
|
|
164
|
-
codexBackupPath:
|
|
165
|
-
codexConfigPath:
|
|
360
|
+
codexHooksPath: join4(codexDir, "hooks.json"),
|
|
361
|
+
codexBackupPath: join4(codexDir, "hooks.solongate.bak"),
|
|
362
|
+
codexConfigPath: join4(codexDir, "config.toml"),
|
|
166
363
|
// OpenCode scans its plugin folder at startup and loads every module in it.
|
|
167
364
|
// Measured on 1.18.10: BOTH `plugin/` and `plugins/` are scanned, so the
|
|
168
365
|
// docs and the field reports are each half right. We write the documented
|
|
169
366
|
// one. There is nothing to register anywhere — dropping the file IS the
|
|
170
367
|
// installation, which also means deleting the file IS the uninstall.
|
|
171
|
-
opencodePluginDir:
|
|
172
|
-
opencodePluginPath:
|
|
368
|
+
opencodePluginDir: join4(opencodeDir, "plugins"),
|
|
369
|
+
opencodePluginPath: join4(opencodeDir, "plugins", "solongate.js")
|
|
173
370
|
};
|
|
174
371
|
}
|
|
175
372
|
function readHook(filename) {
|
|
176
|
-
return
|
|
373
|
+
return readFileSync4(join4(HOOKS_DIR, filename), "utf-8");
|
|
177
374
|
}
|
|
178
375
|
function firstAccountCredential() {
|
|
179
376
|
try {
|
|
180
|
-
const raw = JSON.parse(
|
|
377
|
+
const raw = JSON.parse(readFileSync4(join4(homedir4(), ".solongate", "accounts.json"), "utf-8"));
|
|
181
378
|
if (Array.isArray(raw)) {
|
|
182
379
|
const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
|
|
183
380
|
if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
|
|
@@ -187,8 +384,8 @@ function firstAccountCredential() {
|
|
|
187
384
|
return {};
|
|
188
385
|
}
|
|
189
386
|
function readGuard() {
|
|
190
|
-
const bundled =
|
|
191
|
-
return
|
|
387
|
+
const bundled = join4(HOOKS_DIR, "guard.bundled.mjs");
|
|
388
|
+
return existsSync3(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
|
|
192
389
|
}
|
|
193
390
|
function installGoBinaries(binDir) {
|
|
194
391
|
const os_ = process.platform === "win32" ? "win32" : process.platform;
|
|
@@ -208,10 +405,10 @@ function installGoBinaries(binDir) {
|
|
|
208
405
|
return placed;
|
|
209
406
|
}
|
|
210
407
|
for (const name of ["solongate-guard", "solongate"]) {
|
|
211
|
-
const from =
|
|
212
|
-
const to =
|
|
408
|
+
const from = join4(pkgDir, name + suffix);
|
|
409
|
+
const to = join4(binDir, name + suffix);
|
|
213
410
|
try {
|
|
214
|
-
if (!
|
|
411
|
+
if (!existsSync3(from)) continue;
|
|
215
412
|
const tmp = to + ".new";
|
|
216
413
|
copyFileSync(from, tmp);
|
|
217
414
|
chmodSync(tmp, 493);
|
|
@@ -223,16 +420,14 @@ function installGoBinaries(binDir) {
|
|
|
223
420
|
return placed;
|
|
224
421
|
}
|
|
225
422
|
function antigravityHookCommand(guardAbs) {
|
|
226
|
-
|
|
227
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
228
|
-
return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" antigravity "Antigravity"`;
|
|
423
|
+
return hookCommandFor(dirname(guardAbs), basename(guardAbs), "antigravity", "Antigravity");
|
|
229
424
|
}
|
|
230
425
|
function installAntigravityGuard(p, guardAbs) {
|
|
231
426
|
mkdirSync(p.antigravityDir, { recursive: true });
|
|
232
427
|
let existing = {};
|
|
233
|
-
if (
|
|
234
|
-
const raw =
|
|
235
|
-
if (!
|
|
428
|
+
if (existsSync3(p.antigravityHooksPath)) {
|
|
429
|
+
const raw = readFileSync4(p.antigravityHooksPath, "utf-8");
|
|
430
|
+
if (!existsSync3(p.antigravityBackupPath)) writeFileSync2(p.antigravityBackupPath, raw);
|
|
236
431
|
try {
|
|
237
432
|
existing = JSON.parse(raw);
|
|
238
433
|
} catch {
|
|
@@ -248,9 +443,9 @@ function installAntigravityGuard(p, guardAbs) {
|
|
|
248
443
|
writeFileSync2(p.antigravityHooksPath, JSON.stringify(merged, null, 2) + "\n");
|
|
249
444
|
}
|
|
250
445
|
function removeAntigravityGuard(p) {
|
|
251
|
-
if (!
|
|
446
|
+
if (!existsSync3(p.antigravityHooksPath)) return;
|
|
252
447
|
try {
|
|
253
|
-
const s = JSON.parse(
|
|
448
|
+
const s = JSON.parse(readFileSync4(p.antigravityHooksPath, "utf-8"));
|
|
254
449
|
if (!(ANTIGRAVITY_GROUP in s)) return;
|
|
255
450
|
delete s[ANTIGRAVITY_GROUP];
|
|
256
451
|
writeFileSync2(p.antigravityHooksPath, JSON.stringify(s, null, 2) + "\n");
|
|
@@ -258,8 +453,10 @@ function removeAntigravityGuard(p) {
|
|
|
258
453
|
}
|
|
259
454
|
}
|
|
260
455
|
function codexHookCommand(scriptAbs) {
|
|
261
|
-
|
|
262
|
-
|
|
456
|
+
if (process.platform === "win32") {
|
|
457
|
+
return `"${process.execPath.replace(/\\/g, "/")}" "${scriptAbs.replace(/\\/g, "/")}" codex "Codex"`;
|
|
458
|
+
}
|
|
459
|
+
return hookCommandFor(dirname(scriptAbs), basename(scriptAbs), "codex", "Codex");
|
|
263
460
|
}
|
|
264
461
|
function isOurCodexGroup(group) {
|
|
265
462
|
const hooks = Array.isArray(group?.hooks) ? group.hooks : [];
|
|
@@ -267,10 +464,10 @@ function isOurCodexGroup(group) {
|
|
|
267
464
|
}
|
|
268
465
|
function readCodexHooksFile(path) {
|
|
269
466
|
const out = { hooks: {}, rest: {} };
|
|
270
|
-
if (!
|
|
467
|
+
if (!existsSync3(path)) return out;
|
|
271
468
|
let raw;
|
|
272
469
|
try {
|
|
273
|
-
raw = JSON.parse(
|
|
470
|
+
raw = JSON.parse(readFileSync4(path, "utf-8"));
|
|
274
471
|
} catch {
|
|
275
472
|
return out;
|
|
276
473
|
}
|
|
@@ -315,10 +512,10 @@ function writeCodexHooksFile(path, file) {
|
|
|
315
512
|
body["hooks"] = hooks;
|
|
316
513
|
writeFileSync2(path, JSON.stringify(body, null, 2) + "\n");
|
|
317
514
|
}
|
|
318
|
-
function installCodexGuard(p,
|
|
515
|
+
function installCodexGuard(p, hooksDir2) {
|
|
319
516
|
mkdirSync(p.codexDir, { recursive: true });
|
|
320
|
-
if (
|
|
321
|
-
writeFileSync2(p.codexBackupPath,
|
|
517
|
+
if (existsSync3(p.codexHooksPath) && !existsSync3(p.codexBackupPath)) {
|
|
518
|
+
writeFileSync2(p.codexBackupPath, readFileSync4(p.codexHooksPath, "utf-8"));
|
|
322
519
|
}
|
|
323
520
|
const file = readCodexHooksFile(p.codexHooksPath);
|
|
324
521
|
const script = {
|
|
@@ -342,7 +539,7 @@ function installCodexGuard(p, hooksDir) {
|
|
|
342
539
|
...ev === "Stop" ? {} : { matcher: "*" },
|
|
343
540
|
hooks: [{
|
|
344
541
|
type: "command",
|
|
345
|
-
command: codexHookCommand(
|
|
542
|
+
command: codexHookCommand(join4(hooksDir2, script[ev]).replace(/\\/g, "/")),
|
|
346
543
|
timeout: CODEX_TIMEOUT_SEC,
|
|
347
544
|
statusMessage: status[ev]
|
|
348
545
|
}]
|
|
@@ -352,7 +549,7 @@ function installCodexGuard(p, hooksDir) {
|
|
|
352
549
|
writeCodexHooksFile(p.codexHooksPath, file);
|
|
353
550
|
}
|
|
354
551
|
function removeCodexGuard(p) {
|
|
355
|
-
if (!
|
|
552
|
+
if (!existsSync3(p.codexHooksPath)) return;
|
|
356
553
|
try {
|
|
357
554
|
const file = readCodexHooksFile(p.codexHooksPath);
|
|
358
555
|
let changed = false;
|
|
@@ -378,7 +575,7 @@ function isCodexGuardInstalled() {
|
|
|
378
575
|
}
|
|
379
576
|
function codexDetected() {
|
|
380
577
|
try {
|
|
381
|
-
return
|
|
578
|
+
return existsSync3(globalPaths().codexDir);
|
|
382
579
|
} catch {
|
|
383
580
|
return false;
|
|
384
581
|
}
|
|
@@ -388,7 +585,7 @@ function codexHooksStatus() {
|
|
|
388
585
|
const registered = isCodexGuardInstalled();
|
|
389
586
|
let trusted = false, disabled = false;
|
|
390
587
|
try {
|
|
391
|
-
const toml =
|
|
588
|
+
const toml = readFileSync4(p.codexConfigPath, "utf-8");
|
|
392
589
|
trusted = /trusted_hash\s*=/.test(toml);
|
|
393
590
|
disabled = /^\s*hooks\s*=\s*false\s*$/m.test(toml);
|
|
394
591
|
} catch {
|
|
@@ -407,10 +604,10 @@ function removeOpencodeGuard(p) {
|
|
|
407
604
|
} catch {
|
|
408
605
|
}
|
|
409
606
|
}
|
|
410
|
-
function warmPolicyCache(
|
|
607
|
+
function warmPolicyCache(hooksDir2, agents) {
|
|
411
608
|
for (const agent of agents) {
|
|
412
609
|
try {
|
|
413
|
-
const child = spawn(process.execPath, [
|
|
610
|
+
const child = spawn(process.execPath, [join4(hooksDir2, "guard.mjs"), agent, "--sg-refresh-policy"], {
|
|
414
611
|
detached: true,
|
|
415
612
|
stdio: "ignore",
|
|
416
613
|
windowsHide: true
|
|
@@ -424,23 +621,23 @@ function warmPolicyCache(hooksDir, agents) {
|
|
|
424
621
|
}
|
|
425
622
|
function isOpencodeGuardInstalled() {
|
|
426
623
|
try {
|
|
427
|
-
return
|
|
624
|
+
return readFileSync4(globalPaths().opencodePluginPath, "utf-8").includes("tool.execute.before");
|
|
428
625
|
} catch {
|
|
429
626
|
return false;
|
|
430
627
|
}
|
|
431
628
|
}
|
|
432
629
|
function opencodeDetected() {
|
|
433
630
|
try {
|
|
434
|
-
return
|
|
631
|
+
return existsSync3(globalPaths().opencodeDir);
|
|
435
632
|
} catch {
|
|
436
633
|
return false;
|
|
437
634
|
}
|
|
438
635
|
}
|
|
439
|
-
function sweepStrayScratchDirs(root =
|
|
636
|
+
function sweepStrayScratchDirs(root = homedir4(), maxDepth = 6, budget = 4e4) {
|
|
440
637
|
let removed = 0;
|
|
441
638
|
let visited = 0;
|
|
442
639
|
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "build"]);
|
|
443
|
-
const store =
|
|
640
|
+
const store = join4(homedir4(), ".solongate");
|
|
444
641
|
const walk = (dir, depth) => {
|
|
445
642
|
if (depth > maxDepth || visited++ > budget) return;
|
|
446
643
|
let entries;
|
|
@@ -451,11 +648,11 @@ function sweepStrayScratchDirs(root = homedir3(), maxDepth = 6, budget = 4e4) {
|
|
|
451
648
|
}
|
|
452
649
|
for (const name of entries) {
|
|
453
650
|
if (skip.has(name)) continue;
|
|
454
|
-
const full =
|
|
651
|
+
const full = join4(dir, name);
|
|
455
652
|
if (full === store) continue;
|
|
456
653
|
let isDir = false;
|
|
457
654
|
try {
|
|
458
|
-
isDir =
|
|
655
|
+
isDir = statSync3(full).isDirectory();
|
|
459
656
|
} catch {
|
|
460
657
|
continue;
|
|
461
658
|
}
|
|
@@ -466,7 +663,7 @@ function sweepStrayScratchDirs(root = homedir3(), maxDepth = 6, budget = 4e4) {
|
|
|
466
663
|
for (const f of readdirSync2(full)) {
|
|
467
664
|
if (SCRATCH_FILES.has(f)) {
|
|
468
665
|
try {
|
|
469
|
-
rmSync(
|
|
666
|
+
rmSync(join4(full, f), { force: true });
|
|
470
667
|
} catch {
|
|
471
668
|
left.push(f);
|
|
472
669
|
}
|
|
@@ -488,12 +685,17 @@ function sweepStrayScratchDirs(root = homedir3(), maxDepth = 6, budget = 4e4) {
|
|
|
488
685
|
}
|
|
489
686
|
function repairQuiet() {
|
|
490
687
|
const p = globalPaths();
|
|
491
|
-
const has = (f) =>
|
|
492
|
-
const guardFile =
|
|
688
|
+
const has = (f) => existsSync3(f);
|
|
689
|
+
const guardFile = join4(p.hooksDir, "guard.mjs");
|
|
493
690
|
const line = (label, ok, yes, no) => ({ label, ok, detail: ok ? yes : no });
|
|
691
|
+
const runtime = () => {
|
|
692
|
+
const r2 = hookCanStart();
|
|
693
|
+
return { label: "hook runtime", ok: r2.ok, detail: r2.ok ? `node ${r2.detail}` : r2.detail };
|
|
694
|
+
};
|
|
494
695
|
const before = [
|
|
495
696
|
line("guard hook file", has(guardFile), "present", "MISSING"),
|
|
496
697
|
line("cloud credential", has(p.configPath), "present", "MISSING"),
|
|
698
|
+
runtime(),
|
|
497
699
|
line("Claude hooks", isGuardInstalled(), "guard registered", "guard NOT registered"),
|
|
498
700
|
line("Antigravity hooks", has(p.antigravityHooksPath), "guard registered", "guard NOT registered"),
|
|
499
701
|
line("Codex hooks", isCodexGuardInstalled(), "guard registered", "guard NOT registered"),
|
|
@@ -503,6 +705,7 @@ function repairQuiet() {
|
|
|
503
705
|
if (!r.ok) return { ok: false, message: r.message, before, after: [], notes: [] };
|
|
504
706
|
const after = [
|
|
505
707
|
{ label: "guard hook file", ok: true, detail: `present (v${installedGuardVersion() ?? "?"})` },
|
|
708
|
+
runtime(),
|
|
506
709
|
line("Claude hooks", isGuardInstalled(), "guard registered", "NOT registered"),
|
|
507
710
|
line("Antigravity hooks", has(p.antigravityHooksPath), "guard registered", "NOT registered"),
|
|
508
711
|
line("Codex hooks", isCodexGuardInstalled(), "guard registered", "NOT registered"),
|
|
@@ -529,7 +732,7 @@ function installGlobalQuiet() {
|
|
|
529
732
|
let apiKey = process.env["SOLONGATE_API_KEY"] || "";
|
|
530
733
|
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
531
734
|
try {
|
|
532
|
-
const cfg = JSON.parse(
|
|
735
|
+
const cfg = JSON.parse(readFileSync4(p.configPath, "utf-8"));
|
|
533
736
|
if (cfg && typeof cfg.apiKey === "string") apiKey = apiKey || cfg.apiKey;
|
|
534
737
|
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
535
738
|
} catch {
|
|
@@ -545,26 +748,26 @@ function installGlobalQuiet() {
|
|
|
545
748
|
mkdirSync(p.hooksDir, { recursive: true });
|
|
546
749
|
mkdirSync(p.claudeDir, { recursive: true });
|
|
547
750
|
unlockProtected();
|
|
548
|
-
writeFileSync2(
|
|
751
|
+
writeFileSync2(join4(p.hooksDir, "guard.mjs"), readGuard());
|
|
549
752
|
installGoBinaries(p.binDir);
|
|
550
|
-
writeFileSync2(
|
|
551
|
-
writeFileSync2(
|
|
552
|
-
writeFileSync2(
|
|
553
|
-
writeFileSync2(
|
|
753
|
+
writeFileSync2(join4(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
754
|
+
writeFileSync2(join4(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
755
|
+
writeFileSync2(join4(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
756
|
+
writeFileSync2(join4(p.hooksDir, "conversation.mjs"), readHook("conversation.mjs"));
|
|
757
|
+
writeLauncher(p.hooksDir);
|
|
758
|
+
writeLauncher(p.hooksDir);
|
|
554
759
|
writeFileSync2(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
555
760
|
let existing = {};
|
|
556
|
-
if (
|
|
557
|
-
const raw =
|
|
558
|
-
if (!
|
|
761
|
+
if (existsSync3(p.settingsPath)) {
|
|
762
|
+
const raw = readFileSync4(p.settingsPath, "utf-8");
|
|
763
|
+
if (!existsSync3(p.backupPath)) writeFileSync2(p.backupPath, raw);
|
|
559
764
|
try {
|
|
560
765
|
existing = JSON.parse(raw);
|
|
561
766
|
} catch {
|
|
562
767
|
existing = {};
|
|
563
768
|
}
|
|
564
769
|
}
|
|
565
|
-
const
|
|
566
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
567
|
-
const hookCmd = (script) => `${call}"${nodeBin}" "${join3(p.hooksDir, script).replace(/\\/g, "/")}" claude-code "Claude Code"`;
|
|
770
|
+
const hookCmd = (script) => hookCommandFor(p.hooksDir, script);
|
|
568
771
|
const merged = {
|
|
569
772
|
...existing,
|
|
570
773
|
hooks: {
|
|
@@ -591,7 +794,7 @@ function installGlobalQuiet() {
|
|
|
591
794
|
};
|
|
592
795
|
writeFileSync2(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
593
796
|
try {
|
|
594
|
-
installAntigravityGuard(p,
|
|
797
|
+
installAntigravityGuard(p, join4(p.hooksDir, "guard.mjs").replace(/\\/g, "/"));
|
|
595
798
|
} catch {
|
|
596
799
|
}
|
|
597
800
|
try {
|
|
@@ -612,7 +815,7 @@ function installGlobalQuiet() {
|
|
|
612
815
|
function installedGuardVersion() {
|
|
613
816
|
try {
|
|
614
817
|
const p = globalPaths();
|
|
615
|
-
const s =
|
|
818
|
+
const s = readFileSync4(join4(p.hooksDir, "guard.mjs"), "utf-8");
|
|
616
819
|
const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
|
|
617
820
|
return m ? parseInt(m[1], 10) : null;
|
|
618
821
|
} catch {
|
|
@@ -622,16 +825,35 @@ function installedGuardVersion() {
|
|
|
622
825
|
function guardHookOutdated() {
|
|
623
826
|
try {
|
|
624
827
|
const p = globalPaths();
|
|
625
|
-
return
|
|
828
|
+
return readFileSync4(join4(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
|
|
626
829
|
} catch {
|
|
627
830
|
return false;
|
|
628
831
|
}
|
|
629
832
|
}
|
|
833
|
+
function hookCommandFor(hooksDir2, script, client = "claude-code", label = "Claude Code") {
|
|
834
|
+
const target = join4(hooksDir2, script).replace(/\\/g, "/");
|
|
835
|
+
if (process.platform === "win32") {
|
|
836
|
+
return `& "${process.execPath.replace(/\\/g, "/")}" "${target}" ${client} "${label}"`;
|
|
837
|
+
}
|
|
838
|
+
const launcher = join4(hooksDir2, LAUNCHER_NAME).replace(/\\/g, "/");
|
|
839
|
+
return `/bin/sh "${launcher}" "${target}" ${client} "${label}"`;
|
|
840
|
+
}
|
|
841
|
+
function writeLauncher(hooksDir2) {
|
|
842
|
+
writeFileSync2(join4(hooksDir2, LAUNCHER_NAME), launcherScript(process.execPath));
|
|
843
|
+
try {
|
|
844
|
+
chmodSync(join4(hooksDir2, LAUNCHER_NAME), 493);
|
|
845
|
+
} catch {
|
|
846
|
+
}
|
|
847
|
+
try {
|
|
848
|
+
mkdirSync(join4(hooksDir2, "..", BEAT_DIR), { recursive: true });
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
}
|
|
630
852
|
function isGuardInstalled() {
|
|
631
853
|
try {
|
|
632
854
|
const p = globalPaths();
|
|
633
|
-
if (!
|
|
634
|
-
const s = JSON.parse(
|
|
855
|
+
if (!existsSync3(p.settingsPath)) return false;
|
|
856
|
+
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
635
857
|
return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
|
|
636
858
|
} catch {
|
|
637
859
|
return false;
|
|
@@ -654,8 +876,8 @@ function uninstallGlobalQuiet() {
|
|
|
654
876
|
removeOpencodeGuard(p);
|
|
655
877
|
} catch {
|
|
656
878
|
}
|
|
657
|
-
if (!
|
|
658
|
-
const s = JSON.parse(
|
|
879
|
+
if (!existsSync3(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
|
|
880
|
+
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
659
881
|
delete s.hooks;
|
|
660
882
|
writeFileSync2(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
661
883
|
return { ok: true, message: "guard removed (open a new session)" };
|
|
@@ -669,17 +891,17 @@ function escapeRe(s) {
|
|
|
669
891
|
function shimTargets() {
|
|
670
892
|
if (process.platform === "win32") {
|
|
671
893
|
try {
|
|
672
|
-
const prof =
|
|
894
|
+
const prof = execFileSync2("powershell", ["-NoProfile", "-Command", "$PROFILE.CurrentUserAllHosts"], { encoding: "utf-8" }).trim();
|
|
673
895
|
return prof ? [prof] : [];
|
|
674
896
|
} catch {
|
|
675
897
|
return [];
|
|
676
898
|
}
|
|
677
899
|
}
|
|
678
|
-
return [".bashrc", ".zshrc", ".profile"].map((f) =>
|
|
900
|
+
return [".bashrc", ".zshrc", ".profile"].map((f) => join4(homedir4(), f)).filter((f) => existsSync3(f));
|
|
679
901
|
}
|
|
680
902
|
function writeShimBlock(file, block2) {
|
|
681
903
|
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
682
|
-
let content =
|
|
904
|
+
let content = existsSync3(file) ? readFileSync4(file, "utf-8") : "";
|
|
683
905
|
content = content.replace(re, "");
|
|
684
906
|
if (block2) {
|
|
685
907
|
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
@@ -700,6 +922,10 @@ var __dirname, HOOKS_DIR, ANTIGRAVITY_GROUP, CODEX_EVENTS, CODEX_TIMEOUT_SEC, SC
|
|
|
700
922
|
var init_global_install = __esm({
|
|
701
923
|
"src/global-install.ts"() {
|
|
702
924
|
"use strict";
|
|
925
|
+
init_hook_launcher();
|
|
926
|
+
init_hook_launcher();
|
|
927
|
+
init_hook_health();
|
|
928
|
+
init_hook_health();
|
|
703
929
|
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
704
930
|
HOOKS_DIR = resolve(__dirname, "..", "hooks");
|
|
705
931
|
ANTIGRAVITY_GROUP = "solongate-guard";
|
|
@@ -713,8 +939,8 @@ var init_global_install = __esm({
|
|
|
713
939
|
|
|
714
940
|
// src/tui/index.tsx
|
|
715
941
|
import { appendFileSync, mkdirSync as mkdirSync7 } from "fs";
|
|
716
|
-
import { homedir as
|
|
717
|
-
import { join as
|
|
942
|
+
import { homedir as homedir11 } from "os";
|
|
943
|
+
import { join as join11 } from "path";
|
|
718
944
|
import { render } from "ink";
|
|
719
945
|
|
|
720
946
|
// src/tui/App.tsx
|
|
@@ -915,8 +1141,8 @@ function KeyHints({ hints }) {
|
|
|
915
1141
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
916
1142
|
import TextInput from "ink-text-input";
|
|
917
1143
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
918
|
-
import { homedir as
|
|
919
|
-
import { join as
|
|
1144
|
+
import { homedir as homedir6 } from "os";
|
|
1145
|
+
import { join as join6, resolve as resolve3 } from "path";
|
|
920
1146
|
|
|
921
1147
|
// src/tui/local-log.ts
|
|
922
1148
|
import { closeSync, existsSync, openSync, readdirSync, readFileSync as readFileSync2, readSync, statSync, writeFileSync } from "fs";
|
|
@@ -948,9 +1174,9 @@ function localLogsSetting() {
|
|
|
948
1174
|
return off;
|
|
949
1175
|
}
|
|
950
1176
|
function policyCachesNewestFirst() {
|
|
951
|
-
const
|
|
952
|
-
return readdirSync(
|
|
953
|
-
const p = join2(
|
|
1177
|
+
const sgDir2 = join2(homedir2(), ".solongate");
|
|
1178
|
+
return readdirSync(sgDir2).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
|
|
1179
|
+
const p = join2(sgDir2, f);
|
|
954
1180
|
let mtime = 0;
|
|
955
1181
|
try {
|
|
956
1182
|
mtime = statSync(p).mtimeMs;
|
|
@@ -961,9 +1187,9 @@ function policyCachesNewestFirst() {
|
|
|
961
1187
|
}
|
|
962
1188
|
function localLogFile() {
|
|
963
1189
|
try {
|
|
964
|
-
const
|
|
965
|
-
const caches = readdirSync(
|
|
966
|
-
const p = join2(
|
|
1190
|
+
const sgDir2 = join2(homedir2(), ".solongate");
|
|
1191
|
+
const caches = readdirSync(sgDir2).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
|
|
1192
|
+
const p = join2(sgDir2, f);
|
|
967
1193
|
let mtime = 0;
|
|
968
1194
|
try {
|
|
969
1195
|
mtime = statSync(p).mtimeMs;
|
|
@@ -1074,15 +1300,15 @@ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2
|
|
|
1074
1300
|
|
|
1075
1301
|
// src/api-client/client.ts
|
|
1076
1302
|
init_global_install();
|
|
1077
|
-
import { readFileSync as
|
|
1078
|
-
import { resolve as resolve2, join as
|
|
1079
|
-
import { homedir as
|
|
1303
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
1304
|
+
import { resolve as resolve2, join as join5 } from "path";
|
|
1305
|
+
import { homedir as homedir5 } from "os";
|
|
1080
1306
|
var DEFAULT_API_URL = "https://api.solongate.com";
|
|
1081
|
-
var accountsFile = () =>
|
|
1307
|
+
var accountsFile = () => join5(homedir5(), ".solongate", "accounts.json");
|
|
1082
1308
|
function listAccounts() {
|
|
1083
1309
|
let list5 = [];
|
|
1084
1310
|
try {
|
|
1085
|
-
const raw = JSON.parse(
|
|
1311
|
+
const raw = JSON.parse(readFileSync5(accountsFile(), "utf-8"));
|
|
1086
1312
|
if (Array.isArray(raw)) list5 = raw.filter((a) => a && typeof a.apiKey === "string");
|
|
1087
1313
|
} catch {
|
|
1088
1314
|
}
|
|
@@ -1096,7 +1322,7 @@ function saveAccount(acc) {
|
|
|
1096
1322
|
try {
|
|
1097
1323
|
const list5 = (() => {
|
|
1098
1324
|
try {
|
|
1099
|
-
const raw = JSON.parse(
|
|
1325
|
+
const raw = JSON.parse(readFileSync5(accountsFile(), "utf-8"));
|
|
1100
1326
|
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
1101
1327
|
} catch {
|
|
1102
1328
|
return [];
|
|
@@ -1104,7 +1330,7 @@ function saveAccount(acc) {
|
|
|
1104
1330
|
})();
|
|
1105
1331
|
const next = list5.filter((a) => a.apiKey !== acc.apiKey);
|
|
1106
1332
|
next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
|
|
1107
|
-
mkdirSync2(
|
|
1333
|
+
mkdirSync2(join5(homedir5(), ".solongate"), { recursive: true });
|
|
1108
1334
|
writeFileSync3(accountsFile(), JSON.stringify(next, null, 2));
|
|
1109
1335
|
} catch {
|
|
1110
1336
|
}
|
|
@@ -1113,7 +1339,7 @@ function removeAccount(apiKey) {
|
|
|
1113
1339
|
try {
|
|
1114
1340
|
const list5 = (() => {
|
|
1115
1341
|
try {
|
|
1116
|
-
const raw = JSON.parse(
|
|
1342
|
+
const raw = JSON.parse(readFileSync5(accountsFile(), "utf-8"));
|
|
1117
1343
|
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
1118
1344
|
} catch {
|
|
1119
1345
|
return [];
|
|
@@ -1136,12 +1362,12 @@ function isActiveAccount(apiKey) {
|
|
|
1136
1362
|
}
|
|
1137
1363
|
function setActiveAccount(creds) {
|
|
1138
1364
|
try {
|
|
1139
|
-
const dir =
|
|
1365
|
+
const dir = join5(homedir5(), ".solongate");
|
|
1140
1366
|
mkdirSync2(dir, { recursive: true });
|
|
1141
|
-
const p =
|
|
1367
|
+
const p = join5(dir, ["cloud", "guard.json"].join("-"));
|
|
1142
1368
|
let existing = {};
|
|
1143
1369
|
try {
|
|
1144
|
-
existing = JSON.parse(
|
|
1370
|
+
existing = JSON.parse(readFileSync5(p, "utf-8"));
|
|
1145
1371
|
} catch {
|
|
1146
1372
|
}
|
|
1147
1373
|
if (!writeProtectedFile(p, JSON.stringify({ ...existing, apiKey: creds.apiKey, apiUrl: creds.apiUrl }, null, 2))) return false;
|
|
@@ -1153,14 +1379,14 @@ function setActiveAccount(creds) {
|
|
|
1153
1379
|
}
|
|
1154
1380
|
function clearActiveCredential() {
|
|
1155
1381
|
try {
|
|
1156
|
-
const p =
|
|
1157
|
-
if (!
|
|
1382
|
+
const p = join5(homedir5(), ".solongate", ["cloud", "guard.json"].join("-"));
|
|
1383
|
+
if (!existsSync4(p)) {
|
|
1158
1384
|
cached2 = null;
|
|
1159
1385
|
return true;
|
|
1160
1386
|
}
|
|
1161
1387
|
let existing = {};
|
|
1162
1388
|
try {
|
|
1163
|
-
existing = JSON.parse(
|
|
1389
|
+
existing = JSON.parse(readFileSync5(p, "utf-8"));
|
|
1164
1390
|
} catch {
|
|
1165
1391
|
}
|
|
1166
1392
|
delete existing.apiKey;
|
|
@@ -1191,9 +1417,9 @@ var NotAuthenticatedError = class extends Error {
|
|
|
1191
1417
|
};
|
|
1192
1418
|
function loginCredentialFile() {
|
|
1193
1419
|
try {
|
|
1194
|
-
const p =
|
|
1195
|
-
if (!
|
|
1196
|
-
const c2 = JSON.parse(
|
|
1420
|
+
const p = join5(homedir5(), ".solongate", "cloud-guard.json");
|
|
1421
|
+
if (!existsSync4(p)) return {};
|
|
1422
|
+
const c2 = JSON.parse(readFileSync5(p, "utf-8"));
|
|
1197
1423
|
return c2 && typeof c2 === "object" ? c2 : {};
|
|
1198
1424
|
} catch {
|
|
1199
1425
|
return {};
|
|
@@ -1202,8 +1428,8 @@ function loginCredentialFile() {
|
|
|
1202
1428
|
function dotenvApiKey() {
|
|
1203
1429
|
try {
|
|
1204
1430
|
const envPath = resolve2(".env");
|
|
1205
|
-
if (!
|
|
1206
|
-
for (const line of
|
|
1431
|
+
if (!existsSync4(envPath)) return void 0;
|
|
1432
|
+
for (const line of readFileSync5(envPath, "utf-8").split("\n")) {
|
|
1207
1433
|
const trimmed = line.trim();
|
|
1208
1434
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1209
1435
|
const eq = trimmed.indexOf("=");
|
|
@@ -1687,7 +1913,7 @@ var projectKey = (dir) => {
|
|
|
1687
1913
|
}
|
|
1688
1914
|
return h.toString(16);
|
|
1689
1915
|
};
|
|
1690
|
-
var RING =
|
|
1916
|
+
var RING = join6(homedir6(), ".solongate", "projects", projectKey(resolve3(process.cwd())), ".eval-ring.jsonl");
|
|
1691
1917
|
var fmtUp = (ms) => {
|
|
1692
1918
|
const s = Math.floor(ms / 1e3);
|
|
1693
1919
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -2294,9 +2520,9 @@ function LivePanel({ active: active2 }) {
|
|
|
2294
2520
|
else if (input === "x") toggleSignal("dlp");
|
|
2295
2521
|
else if (input === "r") toggleSignal("ratelimit");
|
|
2296
2522
|
else if (input === "e") {
|
|
2297
|
-
const file =
|
|
2523
|
+
const file = join6(homedir6(), ".solongate", "live-export.jsonl");
|
|
2298
2524
|
try {
|
|
2299
|
-
mkdirSync3(
|
|
2525
|
+
mkdirSync3(join6(homedir6(), ".solongate"), { recursive: true });
|
|
2300
2526
|
writeFileSync4(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
2301
2527
|
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
2302
2528
|
} catch (err2) {
|
|
@@ -3896,8 +4122,8 @@ function DlpPanel({ focused }) {
|
|
|
3896
4122
|
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
3897
4123
|
import TextInput5 from "ink-text-input";
|
|
3898
4124
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
3899
|
-
import { homedir as
|
|
3900
|
-
import { join as
|
|
4125
|
+
import { homedir as homedir7 } from "os";
|
|
4126
|
+
import { join as join7 } from "path";
|
|
3901
4127
|
import { useState as useState7 } from "react";
|
|
3902
4128
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3903
4129
|
var DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
@@ -4104,8 +4330,8 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
4104
4330
|
const doExport = (kind) => {
|
|
4105
4331
|
setMsg({ text: "exporting\u2026", level: "ok" });
|
|
4106
4332
|
const run = async () => {
|
|
4107
|
-
const dir =
|
|
4108
|
-
const file =
|
|
4333
|
+
const dir = join7(homedir7(), ".solongate");
|
|
4334
|
+
const file = join7(dir, `audit-export-${source}.jsonl`);
|
|
4109
4335
|
let rows2;
|
|
4110
4336
|
if (kind === "page") rows2 = pageRows;
|
|
4111
4337
|
else if (source === "cloud") {
|
|
@@ -4479,10 +4705,11 @@ import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } fro
|
|
|
4479
4705
|
init_global_install();
|
|
4480
4706
|
|
|
4481
4707
|
// src/commands/doctor.ts
|
|
4482
|
-
import { existsSync as
|
|
4483
|
-
import { homedir as
|
|
4484
|
-
import { join as
|
|
4708
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
4709
|
+
import { homedir as homedir8 } from "os";
|
|
4710
|
+
import { join as join8 } from "path";
|
|
4485
4711
|
init_global_install();
|
|
4712
|
+
init_hook_health();
|
|
4486
4713
|
async function collectChecks() {
|
|
4487
4714
|
const checks = [];
|
|
4488
4715
|
if (!isAuthenticated()) {
|
|
@@ -4530,8 +4757,32 @@ async function collectChecks() {
|
|
|
4530
4757
|
ok: claudeReg,
|
|
4531
4758
|
detail: claudeReg ? "guard registered" : "guard NOT registered - run `solongate repair`"
|
|
4532
4759
|
});
|
|
4533
|
-
if (
|
|
4534
|
-
const
|
|
4760
|
+
if (claudeReg) {
|
|
4761
|
+
const start = hookCanStart();
|
|
4762
|
+
checks.push({
|
|
4763
|
+
name: "hook runtime",
|
|
4764
|
+
ok: start.ok,
|
|
4765
|
+
detail: start.ok ? `node ${start.detail}` : `${start.detail} - nothing is being enforced or logged`
|
|
4766
|
+
});
|
|
4767
|
+
const beat = guardBeat();
|
|
4768
|
+
if (!beat) {
|
|
4769
|
+
checks.push({
|
|
4770
|
+
name: "guard fired",
|
|
4771
|
+
ok: "warn",
|
|
4772
|
+
detail: "never - open your agent and run one tool call, then check again"
|
|
4773
|
+
});
|
|
4774
|
+
} else if (beat.node === "no-node") {
|
|
4775
|
+
checks.push({
|
|
4776
|
+
name: "guard fired",
|
|
4777
|
+
ok: false,
|
|
4778
|
+
detail: `${agoLabel(beat.at)}, but found no node to run with - run \`solongate repair\``
|
|
4779
|
+
});
|
|
4780
|
+
} else {
|
|
4781
|
+
checks.push({ name: "guard fired", ok: true, detail: `${agoLabel(beat.at)} \xB7 ${beat.node}` });
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
if (existsSync5(globalPaths().antigravityDir)) {
|
|
4785
|
+
const reg = existsSync5(globalPaths().antigravityHooksPath);
|
|
4535
4786
|
checks.push({
|
|
4536
4787
|
name: "Antigravity hooks",
|
|
4537
4788
|
ok: reg,
|
|
@@ -4559,7 +4810,7 @@ async function collectChecks() {
|
|
|
4559
4810
|
});
|
|
4560
4811
|
}
|
|
4561
4812
|
try {
|
|
4562
|
-
const raw =
|
|
4813
|
+
const raw = readFileSync6(join8(homedir8(), ".solongate", ".key-rejected.json"), "utf-8");
|
|
4563
4814
|
const m = JSON.parse(raw);
|
|
4564
4815
|
const ageMin = m.ts ? Math.round((Date.now() - m.ts) / 6e4) : null;
|
|
4565
4816
|
checks.push({
|
|
@@ -4570,8 +4821,8 @@ async function collectChecks() {
|
|
|
4570
4821
|
} catch {
|
|
4571
4822
|
}
|
|
4572
4823
|
const LOCAL_LOG2 = localLogFile();
|
|
4573
|
-
if (
|
|
4574
|
-
const st =
|
|
4824
|
+
if (existsSync5(LOCAL_LOG2)) {
|
|
4825
|
+
const st = statSync4(LOCAL_LOG2);
|
|
4575
4826
|
const ageMin = (Date.now() - st.mtimeMs) / 6e4;
|
|
4576
4827
|
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"}` });
|
|
4577
4828
|
} else {
|
|
@@ -4582,17 +4833,17 @@ async function collectChecks() {
|
|
|
4582
4833
|
|
|
4583
4834
|
// src/logs-server-daemon.ts
|
|
4584
4835
|
import { spawn as spawn4 } from "child_process";
|
|
4585
|
-
import { mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as
|
|
4586
|
-
import { homedir as
|
|
4587
|
-
import { dirname as dirname2, join as
|
|
4836
|
+
import { mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
4837
|
+
import { homedir as homedir9 } from "os";
|
|
4838
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
4588
4839
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4589
|
-
var DIR =
|
|
4590
|
-
var STATE_FILE =
|
|
4591
|
-
var LOG_FILE =
|
|
4840
|
+
var DIR = join9(homedir9(), ".solongate");
|
|
4841
|
+
var STATE_FILE = join9(DIR, ".logs-server.json");
|
|
4842
|
+
var LOG_FILE = join9(DIR, "logs-server.log");
|
|
4592
4843
|
var LOGS_SERVER_PORT = 8788;
|
|
4593
4844
|
function readState() {
|
|
4594
4845
|
try {
|
|
4595
|
-
const s = JSON.parse(
|
|
4846
|
+
const s = JSON.parse(readFileSync7(STATE_FILE, "utf-8"));
|
|
4596
4847
|
return s && typeof s === "object" ? s : {};
|
|
4597
4848
|
} catch {
|
|
4598
4849
|
return {};
|
|
@@ -4628,7 +4879,7 @@ function startLogsServerDaemon() {
|
|
|
4628
4879
|
try {
|
|
4629
4880
|
mkdirSync5(DIR, { recursive: true });
|
|
4630
4881
|
const log = openSync2(LOG_FILE, "a");
|
|
4631
|
-
const cli =
|
|
4882
|
+
const cli = join9(dirname2(fileURLToPath2(import.meta.url)), "index.js");
|
|
4632
4883
|
const p = spawn4(process.execPath, [cli, "logs-server"], {
|
|
4633
4884
|
detached: true,
|
|
4634
4885
|
stdio: ["ignore", log, log],
|
|
@@ -4659,22 +4910,22 @@ function stopLogsServerDaemon() {
|
|
|
4659
4910
|
}
|
|
4660
4911
|
|
|
4661
4912
|
// src/self-update.ts
|
|
4662
|
-
import { execFile, execFileSync as
|
|
4663
|
-
import { access, mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as
|
|
4664
|
-
import { homedir as
|
|
4665
|
-
import { dirname as dirname3, join as
|
|
4913
|
+
import { execFile, execFileSync as execFileSync3, spawn as spawn5 } from "child_process";
|
|
4914
|
+
import { access, mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync7, constants as FS } from "fs";
|
|
4915
|
+
import { homedir as homedir10 } from "os";
|
|
4916
|
+
import { dirname as dirname3, join as join10, sep } from "path";
|
|
4666
4917
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4667
4918
|
var PKG = "@solongate/proxy";
|
|
4668
4919
|
var CHECK_EVERY_MS = 30 * 60 * 1e3;
|
|
4669
4920
|
var ATTEMPT_EVERY_MS = 6 * 60 * 60 * 1e3;
|
|
4670
|
-
var STATE_FILE2 =
|
|
4671
|
-
var LOG_FILE2 =
|
|
4672
|
-
var LOCK_FILE =
|
|
4921
|
+
var STATE_FILE2 = join10(homedir10(), ".solongate", ".self-update.json");
|
|
4922
|
+
var LOG_FILE2 = join10(homedir10(), ".solongate", "self-update.log");
|
|
4923
|
+
var LOCK_FILE = join10(homedir10(), ".solongate", ".update-install.lock");
|
|
4673
4924
|
var LOCK_STALE_MS = 3 * 6e4;
|
|
4674
4925
|
var NEEDS_ADMIN_RE = /\bEACCES\b|\bEPERM\b|permission denied|operation not permitted/i;
|
|
4675
4926
|
function readState2() {
|
|
4676
4927
|
try {
|
|
4677
|
-
const s = JSON.parse(
|
|
4928
|
+
const s = JSON.parse(readFileSync8(STATE_FILE2, "utf-8"));
|
|
4678
4929
|
return s && typeof s === "object" ? s : {};
|
|
4679
4930
|
} catch {
|
|
4680
4931
|
return {};
|
|
@@ -4682,7 +4933,7 @@ function readState2() {
|
|
|
4682
4933
|
}
|
|
4683
4934
|
function writeState2(s) {
|
|
4684
4935
|
try {
|
|
4685
|
-
mkdirSync6(
|
|
4936
|
+
mkdirSync6(join10(homedir10(), ".solongate"), { recursive: true });
|
|
4686
4937
|
writeFileSync7(STATE_FILE2, JSON.stringify(s));
|
|
4687
4938
|
} catch {
|
|
4688
4939
|
}
|
|
@@ -4703,7 +4954,7 @@ function setAutoUpdate(on) {
|
|
|
4703
4954
|
}
|
|
4704
4955
|
function currentVersion() {
|
|
4705
4956
|
try {
|
|
4706
|
-
const pkg = JSON.parse(
|
|
4957
|
+
const pkg = JSON.parse(readFileSync8(join10(dirname3(fileURLToPath3(import.meta.url)), "..", "package.json"), "utf-8"));
|
|
4707
4958
|
return pkg.version ?? "0.0.0";
|
|
4708
4959
|
} catch {
|
|
4709
4960
|
return "0.0.0";
|
|
@@ -4740,7 +4991,7 @@ async function fetchLatest() {
|
|
|
4740
4991
|
function runGlobalInstall(version) {
|
|
4741
4992
|
return new Promise((resolve4) => {
|
|
4742
4993
|
try {
|
|
4743
|
-
mkdirSync6(
|
|
4994
|
+
mkdirSync6(join10(homedir10(), ".solongate"), { recursive: true });
|
|
4744
4995
|
execFile(
|
|
4745
4996
|
"npm",
|
|
4746
4997
|
["install", "-g", `${PKG}@${version}`],
|
|
@@ -5904,11 +6155,11 @@ async function launchTui() {
|
|
|
5904
6155
|
return;
|
|
5905
6156
|
}
|
|
5906
6157
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
5907
|
-
const debugLog =
|
|
6158
|
+
const debugLog = join11(homedir11(), ".solongate", "dataroom-debug.log");
|
|
5908
6159
|
const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
|
|
5909
6160
|
const toFile = (level) => (...args) => {
|
|
5910
6161
|
try {
|
|
5911
|
-
mkdirSync7(
|
|
6162
|
+
mkdirSync7(join11(homedir11(), ".solongate"), { recursive: true });
|
|
5912
6163
|
appendFileSync(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
5913
6164
|
`);
|
|
5914
6165
|
} catch {
|