@solongate/proxy 0.83.56 → 0.83.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/index.js +154 -71
- package/dist/global-install.d.ts +28 -0
- package/dist/global-install.js +349 -130
- package/dist/hook-health.d.ts +26 -0
- package/dist/hook-launcher.d.ts +57 -0
- package/dist/index.js +511 -252
- package/dist/tui/index.js +422 -167
- package/hooks/conversation.mjs +85 -30
- package/hooks/opencode-plugin.mjs +94 -1
- package/package.json +7 -7
package/dist/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,17 @@ function installGoBinaries(binDir) {
|
|
|
223
420
|
return placed;
|
|
224
421
|
}
|
|
225
422
|
function antigravityHookCommand(guardAbs) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
423
|
+
return hookCommandFor(dirname(guardAbs), basename(guardAbs), "antigravity", "Antigravity");
|
|
424
|
+
}
|
|
425
|
+
function antigravityConversationCommand(guardAbs) {
|
|
426
|
+
return hookCommandFor(dirname(guardAbs), "conversation.mjs", "antigravity", "Antigravity");
|
|
229
427
|
}
|
|
230
428
|
function installAntigravityGuard(p, guardAbs) {
|
|
231
429
|
mkdirSync(p.antigravityDir, { recursive: true });
|
|
232
430
|
let existing = {};
|
|
233
|
-
if (
|
|
234
|
-
const raw =
|
|
235
|
-
if (!
|
|
431
|
+
if (existsSync3(p.antigravityHooksPath)) {
|
|
432
|
+
const raw = readFileSync4(p.antigravityHooksPath, "utf-8");
|
|
433
|
+
if (!existsSync3(p.antigravityBackupPath)) writeFileSync2(p.antigravityBackupPath, raw);
|
|
236
434
|
try {
|
|
237
435
|
existing = JSON.parse(raw);
|
|
238
436
|
} catch {
|
|
@@ -242,15 +440,16 @@ function installAntigravityGuard(p, guardAbs) {
|
|
|
242
440
|
const merged = {
|
|
243
441
|
...existing,
|
|
244
442
|
[ANTIGRAVITY_GROUP]: {
|
|
245
|
-
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: antigravityHookCommand(guardAbs) }] }]
|
|
443
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: antigravityHookCommand(guardAbs) }] }],
|
|
444
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: antigravityConversationCommand(guardAbs) }] }]
|
|
246
445
|
}
|
|
247
446
|
};
|
|
248
447
|
writeFileSync2(p.antigravityHooksPath, JSON.stringify(merged, null, 2) + "\n");
|
|
249
448
|
}
|
|
250
449
|
function removeAntigravityGuard(p) {
|
|
251
|
-
if (!
|
|
450
|
+
if (!existsSync3(p.antigravityHooksPath)) return;
|
|
252
451
|
try {
|
|
253
|
-
const s = JSON.parse(
|
|
452
|
+
const s = JSON.parse(readFileSync4(p.antigravityHooksPath, "utf-8"));
|
|
254
453
|
if (!(ANTIGRAVITY_GROUP in s)) return;
|
|
255
454
|
delete s[ANTIGRAVITY_GROUP];
|
|
256
455
|
writeFileSync2(p.antigravityHooksPath, JSON.stringify(s, null, 2) + "\n");
|
|
@@ -258,8 +457,10 @@ function removeAntigravityGuard(p) {
|
|
|
258
457
|
}
|
|
259
458
|
}
|
|
260
459
|
function codexHookCommand(scriptAbs) {
|
|
261
|
-
|
|
262
|
-
|
|
460
|
+
if (process.platform === "win32") {
|
|
461
|
+
return `"${process.execPath.replace(/\\/g, "/")}" "${scriptAbs.replace(/\\/g, "/")}" codex "Codex"`;
|
|
462
|
+
}
|
|
463
|
+
return hookCommandFor(dirname(scriptAbs), basename(scriptAbs), "codex", "Codex");
|
|
263
464
|
}
|
|
264
465
|
function isOurCodexGroup(group) {
|
|
265
466
|
const hooks = Array.isArray(group?.hooks) ? group.hooks : [];
|
|
@@ -267,10 +468,10 @@ function isOurCodexGroup(group) {
|
|
|
267
468
|
}
|
|
268
469
|
function readCodexHooksFile(path) {
|
|
269
470
|
const out = { hooks: {}, rest: {} };
|
|
270
|
-
if (!
|
|
471
|
+
if (!existsSync3(path)) return out;
|
|
271
472
|
let raw;
|
|
272
473
|
try {
|
|
273
|
-
raw = JSON.parse(
|
|
474
|
+
raw = JSON.parse(readFileSync4(path, "utf-8"));
|
|
274
475
|
} catch {
|
|
275
476
|
return out;
|
|
276
477
|
}
|
|
@@ -315,10 +516,10 @@ function writeCodexHooksFile(path, file) {
|
|
|
315
516
|
body["hooks"] = hooks;
|
|
316
517
|
writeFileSync2(path, JSON.stringify(body, null, 2) + "\n");
|
|
317
518
|
}
|
|
318
|
-
function installCodexGuard(p,
|
|
519
|
+
function installCodexGuard(p, hooksDir2) {
|
|
319
520
|
mkdirSync(p.codexDir, { recursive: true });
|
|
320
|
-
if (
|
|
321
|
-
writeFileSync2(p.codexBackupPath,
|
|
521
|
+
if (existsSync3(p.codexHooksPath) && !existsSync3(p.codexBackupPath)) {
|
|
522
|
+
writeFileSync2(p.codexBackupPath, readFileSync4(p.codexHooksPath, "utf-8"));
|
|
322
523
|
}
|
|
323
524
|
const file = readCodexHooksFile(p.codexHooksPath);
|
|
324
525
|
const script = {
|
|
@@ -342,7 +543,7 @@ function installCodexGuard(p, hooksDir) {
|
|
|
342
543
|
...ev === "Stop" ? {} : { matcher: "*" },
|
|
343
544
|
hooks: [{
|
|
344
545
|
type: "command",
|
|
345
|
-
command: codexHookCommand(
|
|
546
|
+
command: codexHookCommand(join4(hooksDir2, script[ev]).replace(/\\/g, "/")),
|
|
346
547
|
timeout: CODEX_TIMEOUT_SEC,
|
|
347
548
|
statusMessage: status[ev]
|
|
348
549
|
}]
|
|
@@ -352,7 +553,7 @@ function installCodexGuard(p, hooksDir) {
|
|
|
352
553
|
writeCodexHooksFile(p.codexHooksPath, file);
|
|
353
554
|
}
|
|
354
555
|
function removeCodexGuard(p) {
|
|
355
|
-
if (!
|
|
556
|
+
if (!existsSync3(p.codexHooksPath)) return;
|
|
356
557
|
try {
|
|
357
558
|
const file = readCodexHooksFile(p.codexHooksPath);
|
|
358
559
|
let changed = false;
|
|
@@ -378,7 +579,7 @@ function isCodexGuardInstalled() {
|
|
|
378
579
|
}
|
|
379
580
|
function codexDetected() {
|
|
380
581
|
try {
|
|
381
|
-
return
|
|
582
|
+
return existsSync3(globalPaths().codexDir);
|
|
382
583
|
} catch {
|
|
383
584
|
return false;
|
|
384
585
|
}
|
|
@@ -388,7 +589,7 @@ function codexHooksStatus() {
|
|
|
388
589
|
const registered = isCodexGuardInstalled();
|
|
389
590
|
let trusted = false, disabled = false;
|
|
390
591
|
try {
|
|
391
|
-
const toml =
|
|
592
|
+
const toml = readFileSync4(p.codexConfigPath, "utf-8");
|
|
392
593
|
trusted = /trusted_hash\s*=/.test(toml);
|
|
393
594
|
disabled = /^\s*hooks\s*=\s*false\s*$/m.test(toml);
|
|
394
595
|
} catch {
|
|
@@ -407,10 +608,10 @@ function removeOpencodeGuard(p) {
|
|
|
407
608
|
} catch {
|
|
408
609
|
}
|
|
409
610
|
}
|
|
410
|
-
function warmPolicyCache(
|
|
611
|
+
function warmPolicyCache(hooksDir2, agents) {
|
|
411
612
|
for (const agent of agents) {
|
|
412
613
|
try {
|
|
413
|
-
const child = spawn(process.execPath, [
|
|
614
|
+
const child = spawn(process.execPath, [join4(hooksDir2, "guard.mjs"), agent, "--sg-refresh-policy"], {
|
|
414
615
|
detached: true,
|
|
415
616
|
stdio: "ignore",
|
|
416
617
|
windowsHide: true
|
|
@@ -424,23 +625,23 @@ function warmPolicyCache(hooksDir, agents) {
|
|
|
424
625
|
}
|
|
425
626
|
function isOpencodeGuardInstalled() {
|
|
426
627
|
try {
|
|
427
|
-
return
|
|
628
|
+
return readFileSync4(globalPaths().opencodePluginPath, "utf-8").includes("tool.execute.before");
|
|
428
629
|
} catch {
|
|
429
630
|
return false;
|
|
430
631
|
}
|
|
431
632
|
}
|
|
432
633
|
function opencodeDetected() {
|
|
433
634
|
try {
|
|
434
|
-
return
|
|
635
|
+
return existsSync3(globalPaths().opencodeDir);
|
|
435
636
|
} catch {
|
|
436
637
|
return false;
|
|
437
638
|
}
|
|
438
639
|
}
|
|
439
|
-
function sweepStrayScratchDirs(root =
|
|
640
|
+
function sweepStrayScratchDirs(root = homedir4(), maxDepth = 6, budget = 4e4) {
|
|
440
641
|
let removed = 0;
|
|
441
642
|
let visited = 0;
|
|
442
643
|
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "build"]);
|
|
443
|
-
const store =
|
|
644
|
+
const store = join4(homedir4(), ".solongate");
|
|
444
645
|
const walk = (dir, depth) => {
|
|
445
646
|
if (depth > maxDepth || visited++ > budget) return;
|
|
446
647
|
let entries;
|
|
@@ -451,11 +652,11 @@ function sweepStrayScratchDirs(root = homedir3(), maxDepth = 6, budget = 4e4) {
|
|
|
451
652
|
}
|
|
452
653
|
for (const name of entries) {
|
|
453
654
|
if (skip.has(name)) continue;
|
|
454
|
-
const full =
|
|
655
|
+
const full = join4(dir, name);
|
|
455
656
|
if (full === store) continue;
|
|
456
657
|
let isDir = false;
|
|
457
658
|
try {
|
|
458
|
-
isDir =
|
|
659
|
+
isDir = statSync3(full).isDirectory();
|
|
459
660
|
} catch {
|
|
460
661
|
continue;
|
|
461
662
|
}
|
|
@@ -466,7 +667,7 @@ function sweepStrayScratchDirs(root = homedir3(), maxDepth = 6, budget = 4e4) {
|
|
|
466
667
|
for (const f of readdirSync2(full)) {
|
|
467
668
|
if (SCRATCH_FILES.has(f)) {
|
|
468
669
|
try {
|
|
469
|
-
rmSync(
|
|
670
|
+
rmSync(join4(full, f), { force: true });
|
|
470
671
|
} catch {
|
|
471
672
|
left.push(f);
|
|
472
673
|
}
|
|
@@ -488,12 +689,17 @@ function sweepStrayScratchDirs(root = homedir3(), maxDepth = 6, budget = 4e4) {
|
|
|
488
689
|
}
|
|
489
690
|
function repairQuiet() {
|
|
490
691
|
const p = globalPaths();
|
|
491
|
-
const has = (f) =>
|
|
492
|
-
const guardFile =
|
|
692
|
+
const has = (f) => existsSync3(f);
|
|
693
|
+
const guardFile = join4(p.hooksDir, "guard.mjs");
|
|
493
694
|
const line = (label, ok, yes, no) => ({ label, ok, detail: ok ? yes : no });
|
|
695
|
+
const runtime = () => {
|
|
696
|
+
const r2 = hookCanStart();
|
|
697
|
+
return { label: "hook runtime", ok: r2.ok, detail: r2.ok ? `node ${r2.detail}` : r2.detail };
|
|
698
|
+
};
|
|
494
699
|
const before = [
|
|
495
700
|
line("guard hook file", has(guardFile), "present", "MISSING"),
|
|
496
701
|
line("cloud credential", has(p.configPath), "present", "MISSING"),
|
|
702
|
+
runtime(),
|
|
497
703
|
line("Claude hooks", isGuardInstalled(), "guard registered", "guard NOT registered"),
|
|
498
704
|
line("Antigravity hooks", has(p.antigravityHooksPath), "guard registered", "guard NOT registered"),
|
|
499
705
|
line("Codex hooks", isCodexGuardInstalled(), "guard registered", "guard NOT registered"),
|
|
@@ -503,6 +709,7 @@ function repairQuiet() {
|
|
|
503
709
|
if (!r.ok) return { ok: false, message: r.message, before, after: [], notes: [] };
|
|
504
710
|
const after = [
|
|
505
711
|
{ label: "guard hook file", ok: true, detail: `present (v${installedGuardVersion() ?? "?"})` },
|
|
712
|
+
runtime(),
|
|
506
713
|
line("Claude hooks", isGuardInstalled(), "guard registered", "NOT registered"),
|
|
507
714
|
line("Antigravity hooks", has(p.antigravityHooksPath), "guard registered", "NOT registered"),
|
|
508
715
|
line("Codex hooks", isCodexGuardInstalled(), "guard registered", "NOT registered"),
|
|
@@ -529,7 +736,7 @@ function installGlobalQuiet() {
|
|
|
529
736
|
let apiKey = process.env["SOLONGATE_API_KEY"] || "";
|
|
530
737
|
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
531
738
|
try {
|
|
532
|
-
const cfg = JSON.parse(
|
|
739
|
+
const cfg = JSON.parse(readFileSync4(p.configPath, "utf-8"));
|
|
533
740
|
if (cfg && typeof cfg.apiKey === "string") apiKey = apiKey || cfg.apiKey;
|
|
534
741
|
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
535
742
|
} catch {
|
|
@@ -545,26 +752,26 @@ function installGlobalQuiet() {
|
|
|
545
752
|
mkdirSync(p.hooksDir, { recursive: true });
|
|
546
753
|
mkdirSync(p.claudeDir, { recursive: true });
|
|
547
754
|
unlockProtected();
|
|
548
|
-
writeFileSync2(
|
|
755
|
+
writeFileSync2(join4(p.hooksDir, "guard.mjs"), readGuard());
|
|
549
756
|
installGoBinaries(p.binDir);
|
|
550
|
-
writeFileSync2(
|
|
551
|
-
writeFileSync2(
|
|
552
|
-
writeFileSync2(
|
|
553
|
-
writeFileSync2(
|
|
757
|
+
writeFileSync2(join4(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
758
|
+
writeFileSync2(join4(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
759
|
+
writeFileSync2(join4(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
760
|
+
writeFileSync2(join4(p.hooksDir, "conversation.mjs"), readHook("conversation.mjs"));
|
|
761
|
+
writeLauncher(p.hooksDir);
|
|
762
|
+
writeLauncher(p.hooksDir);
|
|
554
763
|
writeFileSync2(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
555
764
|
let existing = {};
|
|
556
|
-
if (
|
|
557
|
-
const raw =
|
|
558
|
-
if (!
|
|
765
|
+
if (existsSync3(p.settingsPath)) {
|
|
766
|
+
const raw = readFileSync4(p.settingsPath, "utf-8");
|
|
767
|
+
if (!existsSync3(p.backupPath)) writeFileSync2(p.backupPath, raw);
|
|
559
768
|
try {
|
|
560
769
|
existing = JSON.parse(raw);
|
|
561
770
|
} catch {
|
|
562
771
|
existing = {};
|
|
563
772
|
}
|
|
564
773
|
}
|
|
565
|
-
const
|
|
566
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
567
|
-
const hookCmd = (script) => `${call}"${nodeBin}" "${join3(p.hooksDir, script).replace(/\\/g, "/")}" claude-code "Claude Code"`;
|
|
774
|
+
const hookCmd = (script) => hookCommandFor(p.hooksDir, script);
|
|
568
775
|
const merged = {
|
|
569
776
|
...existing,
|
|
570
777
|
hooks: {
|
|
@@ -591,7 +798,7 @@ function installGlobalQuiet() {
|
|
|
591
798
|
};
|
|
592
799
|
writeFileSync2(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
593
800
|
try {
|
|
594
|
-
installAntigravityGuard(p,
|
|
801
|
+
installAntigravityGuard(p, join4(p.hooksDir, "guard.mjs").replace(/\\/g, "/"));
|
|
595
802
|
} catch {
|
|
596
803
|
}
|
|
597
804
|
try {
|
|
@@ -612,7 +819,7 @@ function installGlobalQuiet() {
|
|
|
612
819
|
function installedGuardVersion() {
|
|
613
820
|
try {
|
|
614
821
|
const p = globalPaths();
|
|
615
|
-
const s =
|
|
822
|
+
const s = readFileSync4(join4(p.hooksDir, "guard.mjs"), "utf-8");
|
|
616
823
|
const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
|
|
617
824
|
return m ? parseInt(m[1], 10) : null;
|
|
618
825
|
} catch {
|
|
@@ -622,16 +829,35 @@ function installedGuardVersion() {
|
|
|
622
829
|
function guardHookOutdated() {
|
|
623
830
|
try {
|
|
624
831
|
const p = globalPaths();
|
|
625
|
-
return
|
|
832
|
+
return readFileSync4(join4(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
|
|
626
833
|
} catch {
|
|
627
834
|
return false;
|
|
628
835
|
}
|
|
629
836
|
}
|
|
837
|
+
function hookCommandFor(hooksDir2, script, client = "claude-code", label = "Claude Code") {
|
|
838
|
+
const target = join4(hooksDir2, script).replace(/\\/g, "/");
|
|
839
|
+
if (process.platform === "win32") {
|
|
840
|
+
return `& "${process.execPath.replace(/\\/g, "/")}" "${target}" ${client} "${label}"`;
|
|
841
|
+
}
|
|
842
|
+
const launcher = join4(hooksDir2, LAUNCHER_NAME).replace(/\\/g, "/");
|
|
843
|
+
return `/bin/sh "${launcher}" "${target}" ${client} "${label}"`;
|
|
844
|
+
}
|
|
845
|
+
function writeLauncher(hooksDir2) {
|
|
846
|
+
writeFileSync2(join4(hooksDir2, LAUNCHER_NAME), launcherScript(process.execPath));
|
|
847
|
+
try {
|
|
848
|
+
chmodSync(join4(hooksDir2, LAUNCHER_NAME), 493);
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
try {
|
|
852
|
+
mkdirSync(join4(hooksDir2, "..", BEAT_DIR), { recursive: true });
|
|
853
|
+
} catch {
|
|
854
|
+
}
|
|
855
|
+
}
|
|
630
856
|
function isGuardInstalled() {
|
|
631
857
|
try {
|
|
632
858
|
const p = globalPaths();
|
|
633
|
-
if (!
|
|
634
|
-
const s = JSON.parse(
|
|
859
|
+
if (!existsSync3(p.settingsPath)) return false;
|
|
860
|
+
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
635
861
|
return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
|
|
636
862
|
} catch {
|
|
637
863
|
return false;
|
|
@@ -654,8 +880,8 @@ function uninstallGlobalQuiet() {
|
|
|
654
880
|
removeOpencodeGuard(p);
|
|
655
881
|
} catch {
|
|
656
882
|
}
|
|
657
|
-
if (!
|
|
658
|
-
const s = JSON.parse(
|
|
883
|
+
if (!existsSync3(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
|
|
884
|
+
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
659
885
|
delete s.hooks;
|
|
660
886
|
writeFileSync2(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
661
887
|
return { ok: true, message: "guard removed (open a new session)" };
|
|
@@ -669,17 +895,17 @@ function escapeRe(s) {
|
|
|
669
895
|
function shimTargets() {
|
|
670
896
|
if (process.platform === "win32") {
|
|
671
897
|
try {
|
|
672
|
-
const prof =
|
|
898
|
+
const prof = execFileSync2("powershell", ["-NoProfile", "-Command", "$PROFILE.CurrentUserAllHosts"], { encoding: "utf-8" }).trim();
|
|
673
899
|
return prof ? [prof] : [];
|
|
674
900
|
} catch {
|
|
675
901
|
return [];
|
|
676
902
|
}
|
|
677
903
|
}
|
|
678
|
-
return [".bashrc", ".zshrc", ".profile"].map((f) =>
|
|
904
|
+
return [".bashrc", ".zshrc", ".profile"].map((f) => join4(homedir4(), f)).filter((f) => existsSync3(f));
|
|
679
905
|
}
|
|
680
906
|
function writeShimBlock(file, block2) {
|
|
681
907
|
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
682
|
-
let content =
|
|
908
|
+
let content = existsSync3(file) ? readFileSync4(file, "utf-8") : "";
|
|
683
909
|
content = content.replace(re, "");
|
|
684
910
|
if (block2) {
|
|
685
911
|
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
@@ -700,6 +926,10 @@ var __dirname, HOOKS_DIR, ANTIGRAVITY_GROUP, CODEX_EVENTS, CODEX_TIMEOUT_SEC, SC
|
|
|
700
926
|
var init_global_install = __esm({
|
|
701
927
|
"src/global-install.ts"() {
|
|
702
928
|
"use strict";
|
|
929
|
+
init_hook_launcher();
|
|
930
|
+
init_hook_launcher();
|
|
931
|
+
init_hook_health();
|
|
932
|
+
init_hook_health();
|
|
703
933
|
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
704
934
|
HOOKS_DIR = resolve(__dirname, "..", "hooks");
|
|
705
935
|
ANTIGRAVITY_GROUP = "solongate-guard";
|
|
@@ -713,8 +943,8 @@ var init_global_install = __esm({
|
|
|
713
943
|
|
|
714
944
|
// src/tui/index.tsx
|
|
715
945
|
import { appendFileSync, mkdirSync as mkdirSync7 } from "fs";
|
|
716
|
-
import { homedir as
|
|
717
|
-
import { join as
|
|
946
|
+
import { homedir as homedir11 } from "os";
|
|
947
|
+
import { join as join11 } from "path";
|
|
718
948
|
import { render } from "ink";
|
|
719
949
|
|
|
720
950
|
// src/tui/App.tsx
|
|
@@ -915,8 +1145,8 @@ function KeyHints({ hints }) {
|
|
|
915
1145
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
916
1146
|
import TextInput from "ink-text-input";
|
|
917
1147
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
918
|
-
import { homedir as
|
|
919
|
-
import { join as
|
|
1148
|
+
import { homedir as homedir6 } from "os";
|
|
1149
|
+
import { join as join6, resolve as resolve3 } from "path";
|
|
920
1150
|
|
|
921
1151
|
// src/tui/local-log.ts
|
|
922
1152
|
import { closeSync, existsSync, openSync, readdirSync, readFileSync as readFileSync2, readSync, statSync, writeFileSync } from "fs";
|
|
@@ -948,9 +1178,9 @@ function localLogsSetting() {
|
|
|
948
1178
|
return off;
|
|
949
1179
|
}
|
|
950
1180
|
function policyCachesNewestFirst() {
|
|
951
|
-
const
|
|
952
|
-
return readdirSync(
|
|
953
|
-
const p = join2(
|
|
1181
|
+
const sgDir2 = join2(homedir2(), ".solongate");
|
|
1182
|
+
return readdirSync(sgDir2).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
|
|
1183
|
+
const p = join2(sgDir2, f);
|
|
954
1184
|
let mtime = 0;
|
|
955
1185
|
try {
|
|
956
1186
|
mtime = statSync(p).mtimeMs;
|
|
@@ -961,9 +1191,9 @@ function policyCachesNewestFirst() {
|
|
|
961
1191
|
}
|
|
962
1192
|
function localLogFile() {
|
|
963
1193
|
try {
|
|
964
|
-
const
|
|
965
|
-
const caches = readdirSync(
|
|
966
|
-
const p = join2(
|
|
1194
|
+
const sgDir2 = join2(homedir2(), ".solongate");
|
|
1195
|
+
const caches = readdirSync(sgDir2).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
|
|
1196
|
+
const p = join2(sgDir2, f);
|
|
967
1197
|
let mtime = 0;
|
|
968
1198
|
try {
|
|
969
1199
|
mtime = statSync(p).mtimeMs;
|
|
@@ -1074,15 +1304,15 @@ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2
|
|
|
1074
1304
|
|
|
1075
1305
|
// src/api-client/client.ts
|
|
1076
1306
|
init_global_install();
|
|
1077
|
-
import { readFileSync as
|
|
1078
|
-
import { resolve as resolve2, join as
|
|
1079
|
-
import { homedir as
|
|
1307
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
1308
|
+
import { resolve as resolve2, join as join5 } from "path";
|
|
1309
|
+
import { homedir as homedir5 } from "os";
|
|
1080
1310
|
var DEFAULT_API_URL = "https://api.solongate.com";
|
|
1081
|
-
var accountsFile = () =>
|
|
1311
|
+
var accountsFile = () => join5(homedir5(), ".solongate", "accounts.json");
|
|
1082
1312
|
function listAccounts() {
|
|
1083
1313
|
let list5 = [];
|
|
1084
1314
|
try {
|
|
1085
|
-
const raw = JSON.parse(
|
|
1315
|
+
const raw = JSON.parse(readFileSync5(accountsFile(), "utf-8"));
|
|
1086
1316
|
if (Array.isArray(raw)) list5 = raw.filter((a) => a && typeof a.apiKey === "string");
|
|
1087
1317
|
} catch {
|
|
1088
1318
|
}
|
|
@@ -1096,7 +1326,7 @@ function saveAccount(acc) {
|
|
|
1096
1326
|
try {
|
|
1097
1327
|
const list5 = (() => {
|
|
1098
1328
|
try {
|
|
1099
|
-
const raw = JSON.parse(
|
|
1329
|
+
const raw = JSON.parse(readFileSync5(accountsFile(), "utf-8"));
|
|
1100
1330
|
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
1101
1331
|
} catch {
|
|
1102
1332
|
return [];
|
|
@@ -1104,7 +1334,7 @@ function saveAccount(acc) {
|
|
|
1104
1334
|
})();
|
|
1105
1335
|
const next = list5.filter((a) => a.apiKey !== acc.apiKey);
|
|
1106
1336
|
next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
|
|
1107
|
-
mkdirSync2(
|
|
1337
|
+
mkdirSync2(join5(homedir5(), ".solongate"), { recursive: true });
|
|
1108
1338
|
writeFileSync3(accountsFile(), JSON.stringify(next, null, 2));
|
|
1109
1339
|
} catch {
|
|
1110
1340
|
}
|
|
@@ -1113,7 +1343,7 @@ function removeAccount(apiKey) {
|
|
|
1113
1343
|
try {
|
|
1114
1344
|
const list5 = (() => {
|
|
1115
1345
|
try {
|
|
1116
|
-
const raw = JSON.parse(
|
|
1346
|
+
const raw = JSON.parse(readFileSync5(accountsFile(), "utf-8"));
|
|
1117
1347
|
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
1118
1348
|
} catch {
|
|
1119
1349
|
return [];
|
|
@@ -1136,12 +1366,12 @@ function isActiveAccount(apiKey) {
|
|
|
1136
1366
|
}
|
|
1137
1367
|
function setActiveAccount(creds) {
|
|
1138
1368
|
try {
|
|
1139
|
-
const dir =
|
|
1369
|
+
const dir = join5(homedir5(), ".solongate");
|
|
1140
1370
|
mkdirSync2(dir, { recursive: true });
|
|
1141
|
-
const p =
|
|
1371
|
+
const p = join5(dir, ["cloud", "guard.json"].join("-"));
|
|
1142
1372
|
let existing = {};
|
|
1143
1373
|
try {
|
|
1144
|
-
existing = JSON.parse(
|
|
1374
|
+
existing = JSON.parse(readFileSync5(p, "utf-8"));
|
|
1145
1375
|
} catch {
|
|
1146
1376
|
}
|
|
1147
1377
|
if (!writeProtectedFile(p, JSON.stringify({ ...existing, apiKey: creds.apiKey, apiUrl: creds.apiUrl }, null, 2))) return false;
|
|
@@ -1153,14 +1383,14 @@ function setActiveAccount(creds) {
|
|
|
1153
1383
|
}
|
|
1154
1384
|
function clearActiveCredential() {
|
|
1155
1385
|
try {
|
|
1156
|
-
const p =
|
|
1157
|
-
if (!
|
|
1386
|
+
const p = join5(homedir5(), ".solongate", ["cloud", "guard.json"].join("-"));
|
|
1387
|
+
if (!existsSync4(p)) {
|
|
1158
1388
|
cached2 = null;
|
|
1159
1389
|
return true;
|
|
1160
1390
|
}
|
|
1161
1391
|
let existing = {};
|
|
1162
1392
|
try {
|
|
1163
|
-
existing = JSON.parse(
|
|
1393
|
+
existing = JSON.parse(readFileSync5(p, "utf-8"));
|
|
1164
1394
|
} catch {
|
|
1165
1395
|
}
|
|
1166
1396
|
delete existing.apiKey;
|
|
@@ -1191,9 +1421,9 @@ var NotAuthenticatedError = class extends Error {
|
|
|
1191
1421
|
};
|
|
1192
1422
|
function loginCredentialFile() {
|
|
1193
1423
|
try {
|
|
1194
|
-
const p =
|
|
1195
|
-
if (!
|
|
1196
|
-
const c2 = JSON.parse(
|
|
1424
|
+
const p = join5(homedir5(), ".solongate", "cloud-guard.json");
|
|
1425
|
+
if (!existsSync4(p)) return {};
|
|
1426
|
+
const c2 = JSON.parse(readFileSync5(p, "utf-8"));
|
|
1197
1427
|
return c2 && typeof c2 === "object" ? c2 : {};
|
|
1198
1428
|
} catch {
|
|
1199
1429
|
return {};
|
|
@@ -1202,8 +1432,8 @@ function loginCredentialFile() {
|
|
|
1202
1432
|
function dotenvApiKey() {
|
|
1203
1433
|
try {
|
|
1204
1434
|
const envPath = resolve2(".env");
|
|
1205
|
-
if (!
|
|
1206
|
-
for (const line of
|
|
1435
|
+
if (!existsSync4(envPath)) return void 0;
|
|
1436
|
+
for (const line of readFileSync5(envPath, "utf-8").split("\n")) {
|
|
1207
1437
|
const trimmed = line.trim();
|
|
1208
1438
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1209
1439
|
const eq = trimmed.indexOf("=");
|
|
@@ -1687,7 +1917,7 @@ var projectKey = (dir) => {
|
|
|
1687
1917
|
}
|
|
1688
1918
|
return h.toString(16);
|
|
1689
1919
|
};
|
|
1690
|
-
var RING =
|
|
1920
|
+
var RING = join6(homedir6(), ".solongate", "projects", projectKey(resolve3(process.cwd())), ".eval-ring.jsonl");
|
|
1691
1921
|
var fmtUp = (ms) => {
|
|
1692
1922
|
const s = Math.floor(ms / 1e3);
|
|
1693
1923
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -2294,9 +2524,9 @@ function LivePanel({ active: active2 }) {
|
|
|
2294
2524
|
else if (input === "x") toggleSignal("dlp");
|
|
2295
2525
|
else if (input === "r") toggleSignal("ratelimit");
|
|
2296
2526
|
else if (input === "e") {
|
|
2297
|
-
const file =
|
|
2527
|
+
const file = join6(homedir6(), ".solongate", "live-export.jsonl");
|
|
2298
2528
|
try {
|
|
2299
|
-
mkdirSync3(
|
|
2529
|
+
mkdirSync3(join6(homedir6(), ".solongate"), { recursive: true });
|
|
2300
2530
|
writeFileSync4(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
2301
2531
|
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
2302
2532
|
} catch (err2) {
|
|
@@ -3896,8 +4126,8 @@ function DlpPanel({ focused }) {
|
|
|
3896
4126
|
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
3897
4127
|
import TextInput5 from "ink-text-input";
|
|
3898
4128
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
3899
|
-
import { homedir as
|
|
3900
|
-
import { join as
|
|
4129
|
+
import { homedir as homedir7 } from "os";
|
|
4130
|
+
import { join as join7 } from "path";
|
|
3901
4131
|
import { useState as useState7 } from "react";
|
|
3902
4132
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3903
4133
|
var DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
@@ -4104,8 +4334,8 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
4104
4334
|
const doExport = (kind) => {
|
|
4105
4335
|
setMsg({ text: "exporting\u2026", level: "ok" });
|
|
4106
4336
|
const run = async () => {
|
|
4107
|
-
const dir =
|
|
4108
|
-
const file =
|
|
4337
|
+
const dir = join7(homedir7(), ".solongate");
|
|
4338
|
+
const file = join7(dir, `audit-export-${source}.jsonl`);
|
|
4109
4339
|
let rows2;
|
|
4110
4340
|
if (kind === "page") rows2 = pageRows;
|
|
4111
4341
|
else if (source === "cloud") {
|
|
@@ -4479,10 +4709,11 @@ import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } fro
|
|
|
4479
4709
|
init_global_install();
|
|
4480
4710
|
|
|
4481
4711
|
// src/commands/doctor.ts
|
|
4482
|
-
import { existsSync as
|
|
4483
|
-
import { homedir as
|
|
4484
|
-
import { join as
|
|
4712
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
4713
|
+
import { homedir as homedir8 } from "os";
|
|
4714
|
+
import { join as join8 } from "path";
|
|
4485
4715
|
init_global_install();
|
|
4716
|
+
init_hook_health();
|
|
4486
4717
|
async function collectChecks() {
|
|
4487
4718
|
const checks = [];
|
|
4488
4719
|
if (!isAuthenticated()) {
|
|
@@ -4530,8 +4761,32 @@ async function collectChecks() {
|
|
|
4530
4761
|
ok: claudeReg,
|
|
4531
4762
|
detail: claudeReg ? "guard registered" : "guard NOT registered - run `solongate repair`"
|
|
4532
4763
|
});
|
|
4533
|
-
if (
|
|
4534
|
-
const
|
|
4764
|
+
if (claudeReg) {
|
|
4765
|
+
const start = hookCanStart();
|
|
4766
|
+
checks.push({
|
|
4767
|
+
name: "hook runtime",
|
|
4768
|
+
ok: start.ok,
|
|
4769
|
+
detail: start.ok ? `node ${start.detail}` : `${start.detail} - nothing is being enforced or logged`
|
|
4770
|
+
});
|
|
4771
|
+
const beat = guardBeat();
|
|
4772
|
+
if (!beat) {
|
|
4773
|
+
checks.push({
|
|
4774
|
+
name: "guard fired",
|
|
4775
|
+
ok: "warn",
|
|
4776
|
+
detail: "never - open your agent and run one tool call, then check again"
|
|
4777
|
+
});
|
|
4778
|
+
} else if (beat.node === "no-node") {
|
|
4779
|
+
checks.push({
|
|
4780
|
+
name: "guard fired",
|
|
4781
|
+
ok: false,
|
|
4782
|
+
detail: `${agoLabel(beat.at)}, but found no node to run with - run \`solongate repair\``
|
|
4783
|
+
});
|
|
4784
|
+
} else {
|
|
4785
|
+
checks.push({ name: "guard fired", ok: true, detail: `${agoLabel(beat.at)} \xB7 ${beat.node}` });
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
if (existsSync5(globalPaths().antigravityDir)) {
|
|
4789
|
+
const reg = existsSync5(globalPaths().antigravityHooksPath);
|
|
4535
4790
|
checks.push({
|
|
4536
4791
|
name: "Antigravity hooks",
|
|
4537
4792
|
ok: reg,
|
|
@@ -4559,7 +4814,7 @@ async function collectChecks() {
|
|
|
4559
4814
|
});
|
|
4560
4815
|
}
|
|
4561
4816
|
try {
|
|
4562
|
-
const raw =
|
|
4817
|
+
const raw = readFileSync6(join8(homedir8(), ".solongate", ".key-rejected.json"), "utf-8");
|
|
4563
4818
|
const m = JSON.parse(raw);
|
|
4564
4819
|
const ageMin = m.ts ? Math.round((Date.now() - m.ts) / 6e4) : null;
|
|
4565
4820
|
checks.push({
|
|
@@ -4570,8 +4825,8 @@ async function collectChecks() {
|
|
|
4570
4825
|
} catch {
|
|
4571
4826
|
}
|
|
4572
4827
|
const LOCAL_LOG2 = localLogFile();
|
|
4573
|
-
if (
|
|
4574
|
-
const st =
|
|
4828
|
+
if (existsSync5(LOCAL_LOG2)) {
|
|
4829
|
+
const st = statSync4(LOCAL_LOG2);
|
|
4575
4830
|
const ageMin = (Date.now() - st.mtimeMs) / 6e4;
|
|
4576
4831
|
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
4832
|
} else {
|
|
@@ -4582,17 +4837,17 @@ async function collectChecks() {
|
|
|
4582
4837
|
|
|
4583
4838
|
// src/logs-server-daemon.ts
|
|
4584
4839
|
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
|
|
4840
|
+
import { mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
4841
|
+
import { homedir as homedir9 } from "os";
|
|
4842
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
4588
4843
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4589
|
-
var DIR =
|
|
4590
|
-
var STATE_FILE =
|
|
4591
|
-
var LOG_FILE =
|
|
4844
|
+
var DIR = join9(homedir9(), ".solongate");
|
|
4845
|
+
var STATE_FILE = join9(DIR, ".logs-server.json");
|
|
4846
|
+
var LOG_FILE = join9(DIR, "logs-server.log");
|
|
4592
4847
|
var LOGS_SERVER_PORT = 8788;
|
|
4593
4848
|
function readState() {
|
|
4594
4849
|
try {
|
|
4595
|
-
const s = JSON.parse(
|
|
4850
|
+
const s = JSON.parse(readFileSync7(STATE_FILE, "utf-8"));
|
|
4596
4851
|
return s && typeof s === "object" ? s : {};
|
|
4597
4852
|
} catch {
|
|
4598
4853
|
return {};
|
|
@@ -4628,7 +4883,7 @@ function startLogsServerDaemon() {
|
|
|
4628
4883
|
try {
|
|
4629
4884
|
mkdirSync5(DIR, { recursive: true });
|
|
4630
4885
|
const log = openSync2(LOG_FILE, "a");
|
|
4631
|
-
const cli =
|
|
4886
|
+
const cli = join9(dirname2(fileURLToPath2(import.meta.url)), "index.js");
|
|
4632
4887
|
const p = spawn4(process.execPath, [cli, "logs-server"], {
|
|
4633
4888
|
detached: true,
|
|
4634
4889
|
stdio: ["ignore", log, log],
|
|
@@ -4659,22 +4914,22 @@ function stopLogsServerDaemon() {
|
|
|
4659
4914
|
}
|
|
4660
4915
|
|
|
4661
4916
|
// 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
|
|
4917
|
+
import { execFile, execFileSync as execFileSync3, spawn as spawn5 } from "child_process";
|
|
4918
|
+
import { access, mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync7, constants as FS } from "fs";
|
|
4919
|
+
import { homedir as homedir10 } from "os";
|
|
4920
|
+
import { dirname as dirname3, join as join10, sep } from "path";
|
|
4666
4921
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4667
4922
|
var PKG = "@solongate/proxy";
|
|
4668
4923
|
var CHECK_EVERY_MS = 30 * 60 * 1e3;
|
|
4669
4924
|
var ATTEMPT_EVERY_MS = 6 * 60 * 60 * 1e3;
|
|
4670
|
-
var STATE_FILE2 =
|
|
4671
|
-
var LOG_FILE2 =
|
|
4672
|
-
var LOCK_FILE =
|
|
4925
|
+
var STATE_FILE2 = join10(homedir10(), ".solongate", ".self-update.json");
|
|
4926
|
+
var LOG_FILE2 = join10(homedir10(), ".solongate", "self-update.log");
|
|
4927
|
+
var LOCK_FILE = join10(homedir10(), ".solongate", ".update-install.lock");
|
|
4673
4928
|
var LOCK_STALE_MS = 3 * 6e4;
|
|
4674
4929
|
var NEEDS_ADMIN_RE = /\bEACCES\b|\bEPERM\b|permission denied|operation not permitted/i;
|
|
4675
4930
|
function readState2() {
|
|
4676
4931
|
try {
|
|
4677
|
-
const s = JSON.parse(
|
|
4932
|
+
const s = JSON.parse(readFileSync8(STATE_FILE2, "utf-8"));
|
|
4678
4933
|
return s && typeof s === "object" ? s : {};
|
|
4679
4934
|
} catch {
|
|
4680
4935
|
return {};
|
|
@@ -4682,7 +4937,7 @@ function readState2() {
|
|
|
4682
4937
|
}
|
|
4683
4938
|
function writeState2(s) {
|
|
4684
4939
|
try {
|
|
4685
|
-
mkdirSync6(
|
|
4940
|
+
mkdirSync6(join10(homedir10(), ".solongate"), { recursive: true });
|
|
4686
4941
|
writeFileSync7(STATE_FILE2, JSON.stringify(s));
|
|
4687
4942
|
} catch {
|
|
4688
4943
|
}
|
|
@@ -4703,7 +4958,7 @@ function setAutoUpdate(on) {
|
|
|
4703
4958
|
}
|
|
4704
4959
|
function currentVersion() {
|
|
4705
4960
|
try {
|
|
4706
|
-
const pkg = JSON.parse(
|
|
4961
|
+
const pkg = JSON.parse(readFileSync8(join10(dirname3(fileURLToPath3(import.meta.url)), "..", "package.json"), "utf-8"));
|
|
4707
4962
|
return pkg.version ?? "0.0.0";
|
|
4708
4963
|
} catch {
|
|
4709
4964
|
return "0.0.0";
|
|
@@ -4740,7 +4995,7 @@ async function fetchLatest() {
|
|
|
4740
4995
|
function runGlobalInstall(version) {
|
|
4741
4996
|
return new Promise((resolve4) => {
|
|
4742
4997
|
try {
|
|
4743
|
-
mkdirSync6(
|
|
4998
|
+
mkdirSync6(join10(homedir10(), ".solongate"), { recursive: true });
|
|
4744
4999
|
execFile(
|
|
4745
5000
|
"npm",
|
|
4746
5001
|
["install", "-g", `${PKG}@${version}`],
|
|
@@ -5904,11 +6159,11 @@ async function launchTui() {
|
|
|
5904
6159
|
return;
|
|
5905
6160
|
}
|
|
5906
6161
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
5907
|
-
const debugLog =
|
|
6162
|
+
const debugLog = join11(homedir11(), ".solongate", "dataroom-debug.log");
|
|
5908
6163
|
const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
|
|
5909
6164
|
const toFile = (level) => (...args) => {
|
|
5910
6165
|
try {
|
|
5911
|
-
mkdirSync7(
|
|
6166
|
+
mkdirSync7(join11(homedir11(), ".solongate"), { recursive: true });
|
|
5912
6167
|
appendFileSync(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
5913
6168
|
`);
|
|
5914
6169
|
} catch {
|