@solongate/proxy 0.81.79 → 0.81.81
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/global-install.d.ts +14 -0
- package/dist/global-install.js +49 -0
- package/dist/index.js +111 -54
- package/dist/tui/index.js +105 -46
- package/package.json +1 -1
package/dist/global-install.d.ts
CHANGED
|
@@ -11,6 +11,20 @@ export declare function globalPaths(): {
|
|
|
11
11
|
};
|
|
12
12
|
export declare function clearGuardUpdateCheck(): boolean;
|
|
13
13
|
export declare function runGlobalRestore(): void;
|
|
14
|
+
/**
|
|
15
|
+
* TUI-safe (re)install — the dataroom's one-key "install / update guard" action.
|
|
16
|
+
* Writes the hook files THIS CLI ships (readGuard = the current package's bundle,
|
|
17
|
+
* so a self-updated dataroom installs the newest hooks DIRECTLY — no waiting for a
|
|
18
|
+
* tool call) and registers them in the global Claude settings, reusing the key
|
|
19
|
+
* already stored in cloud-guard.json. No prompt, no stdout, no process.exit — safe
|
|
20
|
+
* to call from Ink. Returns a status; asks the user to log in when no key exists.
|
|
21
|
+
* The auto-shield shell shim is left to `solongate` login (it can print), so this
|
|
22
|
+
* touches only the guard/audit/stop hooks — the primary protection.
|
|
23
|
+
*/
|
|
24
|
+
export declare function installGlobalQuiet(): {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
14
28
|
/**
|
|
15
29
|
* Are the SolonGate guard hooks currently installed in the global Claude settings
|
|
16
30
|
* on THIS device? Reflects a local install/remove IMMEDIATELY — unlike the cloud
|
package/dist/global-install.js
CHANGED
|
@@ -137,6 +137,54 @@ function runGlobalRestore() {
|
|
|
137
137
|
}
|
|
138
138
|
console.log(" Global SolonGate enforcement uninstalled. Restart Claude Code.");
|
|
139
139
|
}
|
|
140
|
+
function installGlobalQuiet() {
|
|
141
|
+
try {
|
|
142
|
+
const p = globalPaths();
|
|
143
|
+
let apiKey = process.env["SOLONGATE_API_KEY"] || "";
|
|
144
|
+
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
145
|
+
try {
|
|
146
|
+
const cfg = JSON.parse(readFileSync(p.configPath, "utf-8"));
|
|
147
|
+
if (cfg && typeof cfg.apiKey === "string") apiKey = apiKey || cfg.apiKey;
|
|
148
|
+
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
|
|
152
|
+
mkdirSync(p.hooksDir, { recursive: true });
|
|
153
|
+
mkdirSync(p.claudeDir, { recursive: true });
|
|
154
|
+
unlockProtected();
|
|
155
|
+
writeFileSync(join(p.hooksDir, "guard.mjs"), readGuard());
|
|
156
|
+
writeFileSync(join(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
157
|
+
writeFileSync(join(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
158
|
+
writeFileSync(join(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
159
|
+
writeFileSync(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
160
|
+
let existing = {};
|
|
161
|
+
if (existsSync(p.settingsPath)) {
|
|
162
|
+
const raw = readFileSync(p.settingsPath, "utf-8");
|
|
163
|
+
if (!existsSync(p.backupPath)) writeFileSync(p.backupPath, raw);
|
|
164
|
+
try {
|
|
165
|
+
existing = JSON.parse(raw);
|
|
166
|
+
} catch {
|
|
167
|
+
existing = {};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
171
|
+
const call = process.platform === "win32" ? "& " : "";
|
|
172
|
+
const hookCmd = (script) => `${call}"${nodeBin}" "${join(p.hooksDir, script).replace(/\\/g, "/")}" claude-code "Claude Code"`;
|
|
173
|
+
const merged = {
|
|
174
|
+
...existing,
|
|
175
|
+
hooks: {
|
|
176
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("guard.mjs") }] }],
|
|
177
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("audit.mjs") }] }],
|
|
178
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("stop.mjs") }] }]
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
writeFileSync(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
182
|
+
if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
|
|
183
|
+
return { ok: true, message: "guard installed \u2014 active in NEW Claude Code sessions (open a new one)" };
|
|
184
|
+
} catch (e) {
|
|
185
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
140
188
|
function isGuardInstalled() {
|
|
141
189
|
try {
|
|
142
190
|
const p = globalPaths();
|
|
@@ -309,6 +357,7 @@ export {
|
|
|
309
357
|
clearGuardUpdateCheck,
|
|
310
358
|
globalPaths,
|
|
311
359
|
installClaudeShim,
|
|
360
|
+
installGlobalQuiet,
|
|
312
361
|
installGlobalWithKey,
|
|
313
362
|
isGuardInstalled,
|
|
314
363
|
lockProtected,
|
package/dist/index.js
CHANGED
|
@@ -6562,7 +6562,7 @@ __export(self_update_exports, {
|
|
|
6562
6562
|
tuiUpdateFlow: () => tuiUpdateFlow
|
|
6563
6563
|
});
|
|
6564
6564
|
import { execFile, spawn } from "child_process";
|
|
6565
|
-
import { mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync4,
|
|
6565
|
+
import { mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
6566
6566
|
import { homedir as homedir2 } from "os";
|
|
6567
6567
|
import { dirname, join as join4 } from "path";
|
|
6568
6568
|
import { fileURLToPath } from "url";
|
|
@@ -6614,28 +6614,6 @@ async function fetchLatest() {
|
|
|
6614
6614
|
return null;
|
|
6615
6615
|
}
|
|
6616
6616
|
}
|
|
6617
|
-
function npmGlobalRoot() {
|
|
6618
|
-
return new Promise((resolve10) => {
|
|
6619
|
-
try {
|
|
6620
|
-
execFile("npm", ["root", "-g"], { timeout: 5e3, windowsHide: true, shell: process.platform === "win32" }, (err2, stdout) => {
|
|
6621
|
-
resolve10(err2 ? null : stdout.trim() || null);
|
|
6622
|
-
});
|
|
6623
|
-
} catch {
|
|
6624
|
-
resolve10(null);
|
|
6625
|
-
}
|
|
6626
|
-
});
|
|
6627
|
-
}
|
|
6628
|
-
async function isGlobalInstall() {
|
|
6629
|
-
try {
|
|
6630
|
-
const script = realpathSync(process.argv[1] ?? "");
|
|
6631
|
-
if (!script) return false;
|
|
6632
|
-
const root = await npmGlobalRoot();
|
|
6633
|
-
if (!root) return false;
|
|
6634
|
-
return script.startsWith(realpathSync(root));
|
|
6635
|
-
} catch {
|
|
6636
|
-
return false;
|
|
6637
|
-
}
|
|
6638
|
-
}
|
|
6639
6617
|
function spawnGlobalInstall(version) {
|
|
6640
6618
|
try {
|
|
6641
6619
|
mkdirSync3(join4(homedir2(), ".solongate"), { recursive: true });
|
|
@@ -6687,7 +6665,7 @@ async function tuiUpdateFlow(onStatus) {
|
|
|
6687
6665
|
if (!latest || !newerThan(latest, current)) return;
|
|
6688
6666
|
const attempts = readState().attempts ?? {};
|
|
6689
6667
|
const canRetry = Date.now() - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS;
|
|
6690
|
-
if (
|
|
6668
|
+
if (canRetry) {
|
|
6691
6669
|
writeState({ ...readState(), attempts: { [latest]: Date.now() } });
|
|
6692
6670
|
onStatus({ kind: "updating", version: latest });
|
|
6693
6671
|
if (await runGlobalInstall(latest)) {
|
|
@@ -6713,16 +6691,14 @@ function maybeSelfUpdate(notify = (l) => process.stderr.write(l + "\n")) {
|
|
|
6713
6691
|
}
|
|
6714
6692
|
if (!latest || !newerThan(latest, current)) return;
|
|
6715
6693
|
const attempts = readState().attempts ?? {};
|
|
6716
|
-
if (
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
notify(`${c.dim}\u2191 solongate v${latest} available (running v${current}) \u2014 updating in the background, next run uses it${c.reset}`);
|
|
6721
|
-
return;
|
|
6722
|
-
}
|
|
6723
|
-
} else {
|
|
6694
|
+
if (now - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS) {
|
|
6695
|
+
writeState({ ...readState(), attempts: { [latest]: now } });
|
|
6696
|
+
if (spawnGlobalInstall(latest)) {
|
|
6697
|
+
notify(`${c.dim}\u2191 solongate v${latest} available (running v${current}) \u2014 updating in the background, next run uses it${c.reset}`);
|
|
6724
6698
|
return;
|
|
6725
6699
|
}
|
|
6700
|
+
} else {
|
|
6701
|
+
return;
|
|
6726
6702
|
}
|
|
6727
6703
|
notify(`${c.dim}\u2191 solongate v${latest} available (running v${current}) \u2014 update: ${c.reset}${c.cyan}npm i -g ${PKG}@latest${c.reset}`);
|
|
6728
6704
|
} catch {
|
|
@@ -10115,6 +10091,32 @@ import { homedir as homedir9 } from "os";
|
|
|
10115
10091
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10116
10092
|
import { createInterface } from "readline";
|
|
10117
10093
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
10094
|
+
function lockFile(file) {
|
|
10095
|
+
if (!existsSync4(file)) return;
|
|
10096
|
+
try {
|
|
10097
|
+
if (process.platform === "win32") {
|
|
10098
|
+
try {
|
|
10099
|
+
execFileSync2("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE)"], { stdio: "ignore" });
|
|
10100
|
+
} catch {
|
|
10101
|
+
}
|
|
10102
|
+
try {
|
|
10103
|
+
execFileSync2("attrib", ["+R", file], { stdio: "ignore" });
|
|
10104
|
+
} catch {
|
|
10105
|
+
}
|
|
10106
|
+
} else if (process.platform === "darwin") {
|
|
10107
|
+
try {
|
|
10108
|
+
execFileSync2("chflags", ["uchg", file], { stdio: "ignore" });
|
|
10109
|
+
} catch {
|
|
10110
|
+
}
|
|
10111
|
+
} else {
|
|
10112
|
+
try {
|
|
10113
|
+
execFileSync2("chattr", ["+i", file], { stdio: "ignore" });
|
|
10114
|
+
} catch {
|
|
10115
|
+
}
|
|
10116
|
+
}
|
|
10117
|
+
} catch {
|
|
10118
|
+
}
|
|
10119
|
+
}
|
|
10118
10120
|
function unlockFile(file) {
|
|
10119
10121
|
if (!existsSync4(file)) return;
|
|
10120
10122
|
try {
|
|
@@ -10156,6 +10158,9 @@ function protectedTargets() {
|
|
|
10156
10158
|
p.settingsPath
|
|
10157
10159
|
];
|
|
10158
10160
|
}
|
|
10161
|
+
function lockProtected() {
|
|
10162
|
+
for (const f of protectedTargets()) lockFile(f);
|
|
10163
|
+
}
|
|
10159
10164
|
function unlockProtected() {
|
|
10160
10165
|
for (const f of protectedTargets()) unlockFile(f);
|
|
10161
10166
|
}
|
|
@@ -10174,12 +10179,59 @@ function globalPaths() {
|
|
|
10174
10179
|
configPath: join11(sgDir, "cloud-guard.json")
|
|
10175
10180
|
};
|
|
10176
10181
|
}
|
|
10177
|
-
function
|
|
10182
|
+
function readHook(filename) {
|
|
10183
|
+
return readFileSync9(join11(HOOKS_DIR, filename), "utf-8");
|
|
10184
|
+
}
|
|
10185
|
+
function readGuard() {
|
|
10186
|
+
const bundled = join11(HOOKS_DIR, "guard.bundled.mjs");
|
|
10187
|
+
return existsSync4(bundled) ? readFileSync9(bundled, "utf-8") : readHook("guard.mjs");
|
|
10188
|
+
}
|
|
10189
|
+
function installGlobalQuiet() {
|
|
10178
10190
|
try {
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10191
|
+
const p = globalPaths();
|
|
10192
|
+
let apiKey = process.env["SOLONGATE_API_KEY"] || "";
|
|
10193
|
+
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
10194
|
+
try {
|
|
10195
|
+
const cfg = JSON.parse(readFileSync9(p.configPath, "utf-8"));
|
|
10196
|
+
if (cfg && typeof cfg.apiKey === "string") apiKey = apiKey || cfg.apiKey;
|
|
10197
|
+
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
10198
|
+
} catch {
|
|
10199
|
+
}
|
|
10200
|
+
if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
|
|
10201
|
+
mkdirSync8(p.hooksDir, { recursive: true });
|
|
10202
|
+
mkdirSync8(p.claudeDir, { recursive: true });
|
|
10203
|
+
unlockProtected();
|
|
10204
|
+
writeFileSync9(join11(p.hooksDir, "guard.mjs"), readGuard());
|
|
10205
|
+
writeFileSync9(join11(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
10206
|
+
writeFileSync9(join11(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
10207
|
+
writeFileSync9(join11(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
10208
|
+
writeFileSync9(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
10209
|
+
let existing = {};
|
|
10210
|
+
if (existsSync4(p.settingsPath)) {
|
|
10211
|
+
const raw = readFileSync9(p.settingsPath, "utf-8");
|
|
10212
|
+
if (!existsSync4(p.backupPath)) writeFileSync9(p.backupPath, raw);
|
|
10213
|
+
try {
|
|
10214
|
+
existing = JSON.parse(raw);
|
|
10215
|
+
} catch {
|
|
10216
|
+
existing = {};
|
|
10217
|
+
}
|
|
10218
|
+
}
|
|
10219
|
+
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
10220
|
+
const call = process.platform === "win32" ? "& " : "";
|
|
10221
|
+
const hookCmd = (script) => `${call}"${nodeBin}" "${join11(p.hooksDir, script).replace(/\\/g, "/")}" claude-code "Claude Code"`;
|
|
10222
|
+
const merged = {
|
|
10223
|
+
...existing,
|
|
10224
|
+
hooks: {
|
|
10225
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("guard.mjs") }] }],
|
|
10226
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("audit.mjs") }] }],
|
|
10227
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("stop.mjs") }] }]
|
|
10228
|
+
}
|
|
10229
|
+
};
|
|
10230
|
+
writeFileSync9(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
10231
|
+
if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
|
|
10232
|
+
return { ok: true, message: "guard installed \u2014 active in NEW Claude Code sessions (open a new one)" };
|
|
10233
|
+
} catch (e) {
|
|
10234
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
10183
10235
|
}
|
|
10184
10236
|
}
|
|
10185
10237
|
function isGuardInstalled() {
|
|
@@ -10303,6 +10355,19 @@ function SettingsPanel({
|
|
|
10303
10355
|
const local = localQ.data;
|
|
10304
10356
|
const guard = guardQ.data;
|
|
10305
10357
|
const selfProt = selfQ.data;
|
|
10358
|
+
const autoInstalledRef = useRef3(false);
|
|
10359
|
+
useEffect6(() => {
|
|
10360
|
+
if (autoInstalledRef.current) return;
|
|
10361
|
+
if (guardHere && guard && guard.installed != null && !guard.up_to_date) {
|
|
10362
|
+
autoInstalledRef.current = true;
|
|
10363
|
+
const res = installGlobalQuiet();
|
|
10364
|
+
if (res.ok) {
|
|
10365
|
+
setGuardHere(isGuardInstalled());
|
|
10366
|
+
setMsg({ text: `\u2713 guard auto-updated \u2192 v${guard.latest} hooks written \xB7 open a new Claude Code session to apply`, level: "ok" });
|
|
10367
|
+
guardQ.reload();
|
|
10368
|
+
}
|
|
10369
|
+
}
|
|
10370
|
+
}, [guardHere, guard]);
|
|
10306
10371
|
const [hidden, setHidden] = useState7(/* @__PURE__ */ new Set());
|
|
10307
10372
|
const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
|
|
10308
10373
|
const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
|
|
@@ -10428,19 +10493,10 @@ function SettingsPanel({
|
|
|
10428
10493
|
} else if (r.kind === "acct-add") {
|
|
10429
10494
|
beginLogin();
|
|
10430
10495
|
} else if (r.kind === "guard") {
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
if (!guard) return;
|
|
10436
|
-
if (guard.up_to_date) {
|
|
10437
|
-
setMsg({ text: `guard already on the latest hook (v${guard.latest})`, level: "ok" });
|
|
10438
|
-
return;
|
|
10439
|
-
}
|
|
10440
|
-
const armed = clearGuardUpdateCheck();
|
|
10441
|
-
setMsg(
|
|
10442
|
-
armed ? { text: `\u2713 update armed. now make your agent RUN any command (e.g. "echo ok"), not just chat; the guard installs v${guard.latest} on the next executed command`, level: "ok" } : { text: "\u2717 could not arm the update (check ~/.solongate permissions)", level: "bad" }
|
|
10443
|
-
);
|
|
10496
|
+
const res = installGlobalQuiet();
|
|
10497
|
+
setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
|
|
10498
|
+
setGuardHere(isGuardInstalled());
|
|
10499
|
+
guardQ.reload();
|
|
10444
10500
|
} else if (r.kind === "self") {
|
|
10445
10501
|
if (!selfProt) return;
|
|
10446
10502
|
run12(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
|
|
@@ -10712,12 +10768,13 @@ function SettingsPanel({
|
|
|
10712
10768
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
|
|
10713
10769
|
!guardHere ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
10714
10770
|
/* @__PURE__ */ jsx7(Text7, { color: theme.bad, bold: true, children: "removed" }),
|
|
10715
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014
|
|
10771
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014 new Claude Code sessions are unguarded \xB7 " }),
|
|
10772
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, children: "enter to install" })
|
|
10716
10773
|
] }) : guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
10717
10774
|
/* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
|
|
10718
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available \xB7 enter update` }),
|
|
10775
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest) \xB7 enter reinstall" : ` \u2192 v${guard.latest} available \xB7 enter update` }),
|
|
10719
10776
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 ${guard.device_count} device${guard.device_count === 1 ? "" : "s"}${guard.outdated_count ? ` \xB7 ${guard.outdated_count} outdated` : ""} \xB7 d remove` })
|
|
10720
|
-
] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "
|
|
10777
|
+
] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "installed \xB7 enter reinstall/update \xB7 d remove" })
|
|
10721
10778
|
] });
|
|
10722
10779
|
case "self":
|
|
10723
10780
|
return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
@@ -10781,7 +10838,7 @@ function SettingsPanel({
|
|
|
10781
10838
|
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
10782
10839
|
const SECTION_DESC = {
|
|
10783
10840
|
ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
|
|
10784
|
-
PROTECTION: "guard hook: enter update \xB7 d remove \xB7 + self-protection
|
|
10841
|
+
PROTECTION: "guard hook: enter install/update (writes the files) \xB7 d remove \xB7 + self-protection",
|
|
10785
10842
|
"LOCAL LOGS": "mirror every decision to a file + dashboard link",
|
|
10786
10843
|
WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
|
|
10787
10844
|
ALERTS: `one email + one telegram (${alerts.length}) \xB7 enter edits \xB7 m on/off`
|
package/dist/tui/index.js
CHANGED
|
@@ -3148,6 +3148,32 @@ import { createInterface } from "readline";
|
|
|
3148
3148
|
import { execFileSync } from "child_process";
|
|
3149
3149
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3150
3150
|
var HOOKS_DIR = resolve2(__dirname, "..", "hooks");
|
|
3151
|
+
function lockFile(file) {
|
|
3152
|
+
if (!existsSync2(file)) return;
|
|
3153
|
+
try {
|
|
3154
|
+
if (process.platform === "win32") {
|
|
3155
|
+
try {
|
|
3156
|
+
execFileSync("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE)"], { stdio: "ignore" });
|
|
3157
|
+
} catch {
|
|
3158
|
+
}
|
|
3159
|
+
try {
|
|
3160
|
+
execFileSync("attrib", ["+R", file], { stdio: "ignore" });
|
|
3161
|
+
} catch {
|
|
3162
|
+
}
|
|
3163
|
+
} else if (process.platform === "darwin") {
|
|
3164
|
+
try {
|
|
3165
|
+
execFileSync("chflags", ["uchg", file], { stdio: "ignore" });
|
|
3166
|
+
} catch {
|
|
3167
|
+
}
|
|
3168
|
+
} else {
|
|
3169
|
+
try {
|
|
3170
|
+
execFileSync("chattr", ["+i", file], { stdio: "ignore" });
|
|
3171
|
+
} catch {
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
} catch {
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3151
3177
|
function unlockFile(file) {
|
|
3152
3178
|
if (!existsSync2(file)) return;
|
|
3153
3179
|
try {
|
|
@@ -3189,6 +3215,9 @@ function protectedTargets() {
|
|
|
3189
3215
|
p.settingsPath
|
|
3190
3216
|
];
|
|
3191
3217
|
}
|
|
3218
|
+
function lockProtected() {
|
|
3219
|
+
for (const f of protectedTargets()) lockFile(f);
|
|
3220
|
+
}
|
|
3192
3221
|
function unlockProtected() {
|
|
3193
3222
|
for (const f of protectedTargets()) unlockFile(f);
|
|
3194
3223
|
}
|
|
@@ -3207,12 +3236,59 @@ function globalPaths() {
|
|
|
3207
3236
|
configPath: join6(sgDir, "cloud-guard.json")
|
|
3208
3237
|
};
|
|
3209
3238
|
}
|
|
3210
|
-
function
|
|
3239
|
+
function readHook(filename) {
|
|
3240
|
+
return readFileSync4(join6(HOOKS_DIR, filename), "utf-8");
|
|
3241
|
+
}
|
|
3242
|
+
function readGuard() {
|
|
3243
|
+
const bundled = join6(HOOKS_DIR, "guard.bundled.mjs");
|
|
3244
|
+
return existsSync2(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
|
|
3245
|
+
}
|
|
3246
|
+
function installGlobalQuiet() {
|
|
3211
3247
|
try {
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3248
|
+
const p = globalPaths();
|
|
3249
|
+
let apiKey = process.env["SOLONGATE_API_KEY"] || "";
|
|
3250
|
+
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
3251
|
+
try {
|
|
3252
|
+
const cfg = JSON.parse(readFileSync4(p.configPath, "utf-8"));
|
|
3253
|
+
if (cfg && typeof cfg.apiKey === "string") apiKey = apiKey || cfg.apiKey;
|
|
3254
|
+
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
3255
|
+
} catch {
|
|
3256
|
+
}
|
|
3257
|
+
if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
|
|
3258
|
+
mkdirSync4(p.hooksDir, { recursive: true });
|
|
3259
|
+
mkdirSync4(p.claudeDir, { recursive: true });
|
|
3260
|
+
unlockProtected();
|
|
3261
|
+
writeFileSync5(join6(p.hooksDir, "guard.mjs"), readGuard());
|
|
3262
|
+
writeFileSync5(join6(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
3263
|
+
writeFileSync5(join6(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
3264
|
+
writeFileSync5(join6(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
3265
|
+
writeFileSync5(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
3266
|
+
let existing = {};
|
|
3267
|
+
if (existsSync2(p.settingsPath)) {
|
|
3268
|
+
const raw = readFileSync4(p.settingsPath, "utf-8");
|
|
3269
|
+
if (!existsSync2(p.backupPath)) writeFileSync5(p.backupPath, raw);
|
|
3270
|
+
try {
|
|
3271
|
+
existing = JSON.parse(raw);
|
|
3272
|
+
} catch {
|
|
3273
|
+
existing = {};
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
3277
|
+
const call = process.platform === "win32" ? "& " : "";
|
|
3278
|
+
const hookCmd = (script) => `${call}"${nodeBin}" "${join6(p.hooksDir, script).replace(/\\/g, "/")}" claude-code "Claude Code"`;
|
|
3279
|
+
const merged = {
|
|
3280
|
+
...existing,
|
|
3281
|
+
hooks: {
|
|
3282
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("guard.mjs") }] }],
|
|
3283
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("audit.mjs") }] }],
|
|
3284
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("stop.mjs") }] }]
|
|
3285
|
+
}
|
|
3286
|
+
};
|
|
3287
|
+
writeFileSync5(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3288
|
+
if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
|
|
3289
|
+
return { ok: true, message: "guard installed \u2014 active in NEW Claude Code sessions (open a new one)" };
|
|
3290
|
+
} catch (e) {
|
|
3291
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
3216
3292
|
}
|
|
3217
3293
|
}
|
|
3218
3294
|
function isGuardInstalled() {
|
|
@@ -3416,6 +3492,19 @@ function SettingsPanel({
|
|
|
3416
3492
|
const local = localQ.data;
|
|
3417
3493
|
const guard = guardQ.data;
|
|
3418
3494
|
const selfProt = selfQ.data;
|
|
3495
|
+
const autoInstalledRef = useRef3(false);
|
|
3496
|
+
useEffect6(() => {
|
|
3497
|
+
if (autoInstalledRef.current) return;
|
|
3498
|
+
if (guardHere && guard && guard.installed != null && !guard.up_to_date) {
|
|
3499
|
+
autoInstalledRef.current = true;
|
|
3500
|
+
const res = installGlobalQuiet();
|
|
3501
|
+
if (res.ok) {
|
|
3502
|
+
setGuardHere(isGuardInstalled());
|
|
3503
|
+
setMsg({ text: `\u2713 guard auto-updated \u2192 v${guard.latest} hooks written \xB7 open a new Claude Code session to apply`, level: "ok" });
|
|
3504
|
+
guardQ.reload();
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
}, [guardHere, guard]);
|
|
3419
3508
|
const [hidden, setHidden] = useState7(/* @__PURE__ */ new Set());
|
|
3420
3509
|
const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
|
|
3421
3510
|
const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
|
|
@@ -3541,19 +3630,10 @@ function SettingsPanel({
|
|
|
3541
3630
|
} else if (r.kind === "acct-add") {
|
|
3542
3631
|
beginLogin();
|
|
3543
3632
|
} else if (r.kind === "guard") {
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
if (!guard) return;
|
|
3549
|
-
if (guard.up_to_date) {
|
|
3550
|
-
setMsg({ text: `guard already on the latest hook (v${guard.latest})`, level: "ok" });
|
|
3551
|
-
return;
|
|
3552
|
-
}
|
|
3553
|
-
const armed = clearGuardUpdateCheck();
|
|
3554
|
-
setMsg(
|
|
3555
|
-
armed ? { text: `\u2713 update armed. now make your agent RUN any command (e.g. "echo ok"), not just chat; the guard installs v${guard.latest} on the next executed command`, level: "ok" } : { text: "\u2717 could not arm the update (check ~/.solongate permissions)", level: "bad" }
|
|
3556
|
-
);
|
|
3633
|
+
const res = installGlobalQuiet();
|
|
3634
|
+
setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
|
|
3635
|
+
setGuardHere(isGuardInstalled());
|
|
3636
|
+
guardQ.reload();
|
|
3557
3637
|
} else if (r.kind === "self") {
|
|
3558
3638
|
if (!selfProt) return;
|
|
3559
3639
|
run(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
|
|
@@ -3825,12 +3905,13 @@ function SettingsPanel({
|
|
|
3825
3905
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
|
|
3826
3906
|
!guardHere ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3827
3907
|
/* @__PURE__ */ jsx7(Text7, { color: theme.bad, bold: true, children: "removed" }),
|
|
3828
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014
|
|
3908
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014 new Claude Code sessions are unguarded \xB7 " }),
|
|
3909
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, children: "enter to install" })
|
|
3829
3910
|
] }) : guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3830
3911
|
/* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
|
|
3831
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available \xB7 enter update` }),
|
|
3912
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest) \xB7 enter reinstall" : ` \u2192 v${guard.latest} available \xB7 enter update` }),
|
|
3832
3913
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 ${guard.device_count} device${guard.device_count === 1 ? "" : "s"}${guard.outdated_count ? ` \xB7 ${guard.outdated_count} outdated` : ""} \xB7 d remove` })
|
|
3833
|
-
] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "
|
|
3914
|
+
] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "installed \xB7 enter reinstall/update \xB7 d remove" })
|
|
3834
3915
|
] });
|
|
3835
3916
|
case "self":
|
|
3836
3917
|
return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
@@ -3894,7 +3975,7 @@ function SettingsPanel({
|
|
|
3894
3975
|
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
3895
3976
|
const SECTION_DESC = {
|
|
3896
3977
|
ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
|
|
3897
|
-
PROTECTION: "guard hook: enter update \xB7 d remove \xB7 + self-protection
|
|
3978
|
+
PROTECTION: "guard hook: enter install/update (writes the files) \xB7 d remove \xB7 + self-protection",
|
|
3898
3979
|
"LOCAL LOGS": "mirror every decision to a file + dashboard link",
|
|
3899
3980
|
WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
|
|
3900
3981
|
ALERTS: `one email + one telegram (${alerts.length}) \xB7 enter edits \xB7 m on/off`
|
|
@@ -3952,7 +4033,7 @@ function SettingsPanel({
|
|
|
3952
4033
|
|
|
3953
4034
|
// src/self-update.ts
|
|
3954
4035
|
import { execFile, spawn as spawn4 } from "child_process";
|
|
3955
|
-
import { mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync6,
|
|
4036
|
+
import { mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
3956
4037
|
import { homedir as homedir8 } from "os";
|
|
3957
4038
|
import { dirname as dirname3, join as join8 } from "path";
|
|
3958
4039
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -4009,28 +4090,6 @@ async function fetchLatest() {
|
|
|
4009
4090
|
return null;
|
|
4010
4091
|
}
|
|
4011
4092
|
}
|
|
4012
|
-
function npmGlobalRoot() {
|
|
4013
|
-
return new Promise((resolve3) => {
|
|
4014
|
-
try {
|
|
4015
|
-
execFile("npm", ["root", "-g"], { timeout: 5e3, windowsHide: true, shell: process.platform === "win32" }, (err, stdout) => {
|
|
4016
|
-
resolve3(err ? null : stdout.trim() || null);
|
|
4017
|
-
});
|
|
4018
|
-
} catch {
|
|
4019
|
-
resolve3(null);
|
|
4020
|
-
}
|
|
4021
|
-
});
|
|
4022
|
-
}
|
|
4023
|
-
async function isGlobalInstall() {
|
|
4024
|
-
try {
|
|
4025
|
-
const script = realpathSync(process.argv[1] ?? "");
|
|
4026
|
-
if (!script) return false;
|
|
4027
|
-
const root = await npmGlobalRoot();
|
|
4028
|
-
if (!root) return false;
|
|
4029
|
-
return script.startsWith(realpathSync(root));
|
|
4030
|
-
} catch {
|
|
4031
|
-
return false;
|
|
4032
|
-
}
|
|
4033
|
-
}
|
|
4034
4093
|
function runGlobalInstall(version) {
|
|
4035
4094
|
return new Promise((resolve3) => {
|
|
4036
4095
|
try {
|
|
@@ -4064,7 +4123,7 @@ async function tuiUpdateFlow(onStatus) {
|
|
|
4064
4123
|
if (!latest || !newerThan(latest, current)) return;
|
|
4065
4124
|
const attempts = readState2().attempts ?? {};
|
|
4066
4125
|
const canRetry = Date.now() - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS;
|
|
4067
|
-
if (
|
|
4126
|
+
if (canRetry) {
|
|
4068
4127
|
writeState2({ ...readState2(), attempts: { [latest]: Date.now() } });
|
|
4069
4128
|
onStatus({ kind: "updating", version: latest });
|
|
4070
4129
|
if (await runGlobalInstall(latest)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.81.
|
|
3
|
+
"version": "0.81.81",
|
|
4
4
|
"description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|