@solongate/proxy 0.81.80 → 0.81.82
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 +22 -0
- package/dist/global-install.js +74 -12
- package/dist/index.js +135 -37
- package/dist/tui/index.js +135 -37
- package/package.json +1 -1
package/dist/global-install.d.ts
CHANGED
|
@@ -11,6 +11,28 @@ 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
|
+
};
|
|
28
|
+
/** HOOK_VERSION of the guard hook currently installed on THIS device (read from
|
|
29
|
+
* the file), or null when not installed. The LOCAL truth — independent of the
|
|
30
|
+
* cloud guard-status (which only knows what the device last REPORTED). */
|
|
31
|
+
export declare function installedGuardVersion(): number | null;
|
|
32
|
+
/** True when the installed guard hook DIFFERS from the one this CLI ships — i.e.
|
|
33
|
+
* a newer hook is available locally (the CLI self-updated). Cloud-independent,
|
|
34
|
+
* so the dataroom can offer/apply an update even when the API is behind. */
|
|
35
|
+
export declare function guardHookOutdated(): boolean;
|
|
14
36
|
/**
|
|
15
37
|
* Are the SolonGate guard hooks currently installed in the global Claude settings
|
|
16
38
|
* on THIS device? Reflects a local install/remove IMMEDIATELY — unlike the cloud
|
package/dist/global-install.js
CHANGED
|
@@ -137,6 +137,72 @@ 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 (open a new session)" };
|
|
184
|
+
} catch (e) {
|
|
185
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function installedGuardVersion() {
|
|
189
|
+
try {
|
|
190
|
+
const p = globalPaths();
|
|
191
|
+
const s = readFileSync(join(p.hooksDir, "guard.mjs"), "utf-8");
|
|
192
|
+
const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
|
|
193
|
+
return m ? parseInt(m[1], 10) : null;
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function guardHookOutdated() {
|
|
199
|
+
try {
|
|
200
|
+
const p = globalPaths();
|
|
201
|
+
return readFileSync(join(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
|
|
202
|
+
} catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
140
206
|
function isGuardInstalled() {
|
|
141
207
|
try {
|
|
142
208
|
const p = globalPaths();
|
|
@@ -152,18 +218,11 @@ function uninstallGlobalQuiet() {
|
|
|
152
218
|
const p = globalPaths();
|
|
153
219
|
unlockProtected();
|
|
154
220
|
removeClaudeShim();
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
if (existsSync(p.settingsPath)) {
|
|
161
|
-
const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
|
|
162
|
-
delete s.hooks;
|
|
163
|
-
writeFileSync(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
164
|
-
return { ok: true, message: `guard removed \u2014 hooks cleared \xB7 ${applies}` };
|
|
165
|
-
}
|
|
166
|
-
return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
|
|
221
|
+
if (!existsSync(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
|
|
222
|
+
const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
|
|
223
|
+
delete s.hooks;
|
|
224
|
+
writeFileSync(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
225
|
+
return { ok: true, message: "guard removed (open a new session)" };
|
|
167
226
|
} catch (e) {
|
|
168
227
|
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
169
228
|
}
|
|
@@ -308,8 +367,11 @@ async function installGlobalWithKey(apiKey, apiUrl) {
|
|
|
308
367
|
export {
|
|
309
368
|
clearGuardUpdateCheck,
|
|
310
369
|
globalPaths,
|
|
370
|
+
guardHookOutdated,
|
|
311
371
|
installClaudeShim,
|
|
372
|
+
installGlobalQuiet,
|
|
312
373
|
installGlobalWithKey,
|
|
374
|
+
installedGuardVersion,
|
|
313
375
|
isGuardInstalled,
|
|
314
376
|
lockProtected,
|
|
315
377
|
removeClaudeShim,
|
package/dist/index.js
CHANGED
|
@@ -10091,6 +10091,32 @@ import { homedir as homedir9 } from "os";
|
|
|
10091
10091
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10092
10092
|
import { createInterface } from "readline";
|
|
10093
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
|
+
}
|
|
10094
10120
|
function unlockFile(file) {
|
|
10095
10121
|
if (!existsSync4(file)) return;
|
|
10096
10122
|
try {
|
|
@@ -10132,6 +10158,9 @@ function protectedTargets() {
|
|
|
10132
10158
|
p.settingsPath
|
|
10133
10159
|
];
|
|
10134
10160
|
}
|
|
10161
|
+
function lockProtected() {
|
|
10162
|
+
for (const f of protectedTargets()) lockFile(f);
|
|
10163
|
+
}
|
|
10135
10164
|
function unlockProtected() {
|
|
10136
10165
|
for (const f of protectedTargets()) unlockFile(f);
|
|
10137
10166
|
}
|
|
@@ -10150,10 +10179,75 @@ function globalPaths() {
|
|
|
10150
10179
|
configPath: join11(sgDir, "cloud-guard.json")
|
|
10151
10180
|
};
|
|
10152
10181
|
}
|
|
10153
|
-
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() {
|
|
10154
10190
|
try {
|
|
10155
|
-
|
|
10156
|
-
|
|
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 (open a new session)" };
|
|
10233
|
+
} catch (e) {
|
|
10234
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
10235
|
+
}
|
|
10236
|
+
}
|
|
10237
|
+
function installedGuardVersion() {
|
|
10238
|
+
try {
|
|
10239
|
+
const p = globalPaths();
|
|
10240
|
+
const s = readFileSync9(join11(p.hooksDir, "guard.mjs"), "utf-8");
|
|
10241
|
+
const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
|
|
10242
|
+
return m ? parseInt(m[1], 10) : null;
|
|
10243
|
+
} catch {
|
|
10244
|
+
return null;
|
|
10245
|
+
}
|
|
10246
|
+
}
|
|
10247
|
+
function guardHookOutdated() {
|
|
10248
|
+
try {
|
|
10249
|
+
const p = globalPaths();
|
|
10250
|
+
return readFileSync9(join11(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
|
|
10157
10251
|
} catch {
|
|
10158
10252
|
return false;
|
|
10159
10253
|
}
|
|
@@ -10173,18 +10267,11 @@ function uninstallGlobalQuiet() {
|
|
|
10173
10267
|
const p = globalPaths();
|
|
10174
10268
|
unlockProtected();
|
|
10175
10269
|
removeClaudeShim();
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
}
|
|
10181
|
-
if (existsSync4(p.settingsPath)) {
|
|
10182
|
-
const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
|
|
10183
|
-
delete s.hooks;
|
|
10184
|
-
writeFileSync9(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
10185
|
-
return { ok: true, message: `guard removed \u2014 hooks cleared \xB7 ${applies}` };
|
|
10186
|
-
}
|
|
10187
|
-
return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
|
|
10270
|
+
if (!existsSync4(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
|
|
10271
|
+
const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
|
|
10272
|
+
delete s.hooks;
|
|
10273
|
+
writeFileSync9(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
10274
|
+
return { ok: true, message: "guard removed (open a new session)" };
|
|
10188
10275
|
} catch (e) {
|
|
10189
10276
|
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
10190
10277
|
}
|
|
@@ -10260,6 +10347,13 @@ function SettingsPanel({
|
|
|
10260
10347
|
const abort = useRef3(false);
|
|
10261
10348
|
const refreshAccounts = () => setAccounts(listAccounts());
|
|
10262
10349
|
const [guardHere, setGuardHere] = useState7(() => isGuardInstalled());
|
|
10350
|
+
const [guardVer, setGuardVer] = useState7(() => installedGuardVersion());
|
|
10351
|
+
const [guardOld, setGuardOld] = useState7(() => guardHookOutdated());
|
|
10352
|
+
const refreshGuard = () => {
|
|
10353
|
+
setGuardHere(isGuardInstalled());
|
|
10354
|
+
setGuardVer(installedGuardVersion());
|
|
10355
|
+
setGuardOld(guardHookOutdated());
|
|
10356
|
+
};
|
|
10263
10357
|
const locked = accounts.length === 0;
|
|
10264
10358
|
const [srv, setSrv] = useState7(() => logsServerStatus());
|
|
10265
10359
|
useEffect6(() => {
|
|
@@ -10277,8 +10371,20 @@ function SettingsPanel({
|
|
|
10277
10371
|
const guardQ = useLoader(() => listAccounts().length ? api.settings.getGuardStatus() : Promise.resolve(null));
|
|
10278
10372
|
const selfQ = useLoader(() => listAccounts().length ? api.settings.getSelfProtection() : Promise.resolve(null));
|
|
10279
10373
|
const local = localQ.data;
|
|
10280
|
-
const guard = guardQ.data;
|
|
10281
10374
|
const selfProt = selfQ.data;
|
|
10375
|
+
const autoInstalledRef = useRef3(false);
|
|
10376
|
+
useEffect6(() => {
|
|
10377
|
+
if (autoInstalledRef.current) return;
|
|
10378
|
+
if (guardHere && guardOld) {
|
|
10379
|
+
autoInstalledRef.current = true;
|
|
10380
|
+
const res = installGlobalQuiet();
|
|
10381
|
+
if (res.ok) {
|
|
10382
|
+
refreshGuard();
|
|
10383
|
+
setMsg({ text: `\u2713 guard updated \u2192 v${installedGuardVersion() ?? "?"} (open a new session)`, level: "ok" });
|
|
10384
|
+
guardQ.reload();
|
|
10385
|
+
}
|
|
10386
|
+
}
|
|
10387
|
+
}, [guardHere, guardOld]);
|
|
10282
10388
|
const [hidden, setHidden] = useState7(/* @__PURE__ */ new Set());
|
|
10283
10389
|
const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
|
|
10284
10390
|
const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
|
|
@@ -10404,19 +10510,10 @@ function SettingsPanel({
|
|
|
10404
10510
|
} else if (r.kind === "acct-add") {
|
|
10405
10511
|
beginLogin();
|
|
10406
10512
|
} else if (r.kind === "guard") {
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
if (!guard) return;
|
|
10412
|
-
if (guard.up_to_date) {
|
|
10413
|
-
setMsg({ text: `guard already on the latest hook (v${guard.latest})`, level: "ok" });
|
|
10414
|
-
return;
|
|
10415
|
-
}
|
|
10416
|
-
const armed = clearGuardUpdateCheck();
|
|
10417
|
-
setMsg(
|
|
10418
|
-
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" }
|
|
10419
|
-
);
|
|
10513
|
+
const res = installGlobalQuiet();
|
|
10514
|
+
setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
|
|
10515
|
+
refreshGuard();
|
|
10516
|
+
guardQ.reload();
|
|
10420
10517
|
} else if (r.kind === "self") {
|
|
10421
10518
|
if (!selfProt) return;
|
|
10422
10519
|
run12(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
|
|
@@ -10579,7 +10676,7 @@ function SettingsPanel({
|
|
|
10579
10676
|
setConfirmDel(null);
|
|
10580
10677
|
const res = uninstallGlobalQuiet();
|
|
10581
10678
|
setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
|
|
10582
|
-
|
|
10679
|
+
refreshGuard();
|
|
10583
10680
|
guardQ.reload();
|
|
10584
10681
|
} else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
|
|
10585
10682
|
const k = keyOf(cur);
|
|
@@ -10688,12 +10785,13 @@ function SettingsPanel({
|
|
|
10688
10785
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
|
|
10689
10786
|
!guardHere ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
10690
10787
|
/* @__PURE__ */ jsx7(Text7, { color: theme.bad, bold: true, children: "removed" }),
|
|
10691
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.
|
|
10695
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children:
|
|
10696
|
-
|
|
10788
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
|
|
10789
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, children: "enter to install" })
|
|
10790
|
+
] }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
10791
|
+
/* @__PURE__ */ jsx7(Text7, { color: guardOld ? theme.warn : theme.ok, children: `v${guardVer ?? "?"}` }),
|
|
10792
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guardOld ? " \xB7 update available \xB7 enter update" : " (latest) \xB7 enter reinstall" }),
|
|
10793
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 d remove" })
|
|
10794
|
+
] })
|
|
10697
10795
|
] });
|
|
10698
10796
|
case "self":
|
|
10699
10797
|
return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
@@ -10757,7 +10855,7 @@ function SettingsPanel({
|
|
|
10757
10855
|
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";
|
|
10758
10856
|
const SECTION_DESC = {
|
|
10759
10857
|
ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
|
|
10760
|
-
PROTECTION: "guard hook: enter update \xB7 d remove \xB7 + self-protection
|
|
10858
|
+
PROTECTION: "guard hook: enter install/update (writes the files) \xB7 d remove \xB7 + self-protection",
|
|
10761
10859
|
"LOCAL LOGS": "mirror every decision to a file + dashboard link",
|
|
10762
10860
|
WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
|
|
10763
10861
|
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,10 +3236,75 @@ 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
|
-
|
|
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 (open a new session)" };
|
|
3290
|
+
} catch (e) {
|
|
3291
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
function installedGuardVersion() {
|
|
3295
|
+
try {
|
|
3296
|
+
const p = globalPaths();
|
|
3297
|
+
const s = readFileSync4(join6(p.hooksDir, "guard.mjs"), "utf-8");
|
|
3298
|
+
const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
|
|
3299
|
+
return m ? parseInt(m[1], 10) : null;
|
|
3300
|
+
} catch {
|
|
3301
|
+
return null;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
function guardHookOutdated() {
|
|
3305
|
+
try {
|
|
3306
|
+
const p = globalPaths();
|
|
3307
|
+
return readFileSync4(join6(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
|
|
3214
3308
|
} catch {
|
|
3215
3309
|
return false;
|
|
3216
3310
|
}
|
|
@@ -3230,18 +3324,11 @@ function uninstallGlobalQuiet() {
|
|
|
3230
3324
|
const p = globalPaths();
|
|
3231
3325
|
unlockProtected();
|
|
3232
3326
|
removeClaudeShim();
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
}
|
|
3238
|
-
if (existsSync2(p.settingsPath)) {
|
|
3239
|
-
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
3240
|
-
delete s.hooks;
|
|
3241
|
-
writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
3242
|
-
return { ok: true, message: `guard removed \u2014 hooks cleared \xB7 ${applies}` };
|
|
3243
|
-
}
|
|
3244
|
-
return { ok: true, message: "nothing to remove \u2014 no global Claude Code settings found" };
|
|
3327
|
+
if (!existsSync2(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
|
|
3328
|
+
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
3329
|
+
delete s.hooks;
|
|
3330
|
+
writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
3331
|
+
return { ok: true, message: "guard removed (open a new session)" };
|
|
3245
3332
|
} catch (e) {
|
|
3246
3333
|
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
3247
3334
|
}
|
|
@@ -3397,6 +3484,13 @@ function SettingsPanel({
|
|
|
3397
3484
|
const abort = useRef3(false);
|
|
3398
3485
|
const refreshAccounts = () => setAccounts(listAccounts());
|
|
3399
3486
|
const [guardHere, setGuardHere] = useState7(() => isGuardInstalled());
|
|
3487
|
+
const [guardVer, setGuardVer] = useState7(() => installedGuardVersion());
|
|
3488
|
+
const [guardOld, setGuardOld] = useState7(() => guardHookOutdated());
|
|
3489
|
+
const refreshGuard = () => {
|
|
3490
|
+
setGuardHere(isGuardInstalled());
|
|
3491
|
+
setGuardVer(installedGuardVersion());
|
|
3492
|
+
setGuardOld(guardHookOutdated());
|
|
3493
|
+
};
|
|
3400
3494
|
const locked = accounts.length === 0;
|
|
3401
3495
|
const [srv, setSrv] = useState7(() => logsServerStatus());
|
|
3402
3496
|
useEffect6(() => {
|
|
@@ -3414,8 +3508,20 @@ function SettingsPanel({
|
|
|
3414
3508
|
const guardQ = useLoader(() => listAccounts().length ? api.settings.getGuardStatus() : Promise.resolve(null));
|
|
3415
3509
|
const selfQ = useLoader(() => listAccounts().length ? api.settings.getSelfProtection() : Promise.resolve(null));
|
|
3416
3510
|
const local = localQ.data;
|
|
3417
|
-
const guard = guardQ.data;
|
|
3418
3511
|
const selfProt = selfQ.data;
|
|
3512
|
+
const autoInstalledRef = useRef3(false);
|
|
3513
|
+
useEffect6(() => {
|
|
3514
|
+
if (autoInstalledRef.current) return;
|
|
3515
|
+
if (guardHere && guardOld) {
|
|
3516
|
+
autoInstalledRef.current = true;
|
|
3517
|
+
const res = installGlobalQuiet();
|
|
3518
|
+
if (res.ok) {
|
|
3519
|
+
refreshGuard();
|
|
3520
|
+
setMsg({ text: `\u2713 guard updated \u2192 v${installedGuardVersion() ?? "?"} (open a new session)`, level: "ok" });
|
|
3521
|
+
guardQ.reload();
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
}, [guardHere, guardOld]);
|
|
3419
3525
|
const [hidden, setHidden] = useState7(/* @__PURE__ */ new Set());
|
|
3420
3526
|
const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
|
|
3421
3527
|
const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
|
|
@@ -3541,19 +3647,10 @@ function SettingsPanel({
|
|
|
3541
3647
|
} else if (r.kind === "acct-add") {
|
|
3542
3648
|
beginLogin();
|
|
3543
3649
|
} 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
|
-
);
|
|
3650
|
+
const res = installGlobalQuiet();
|
|
3651
|
+
setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
|
|
3652
|
+
refreshGuard();
|
|
3653
|
+
guardQ.reload();
|
|
3557
3654
|
} else if (r.kind === "self") {
|
|
3558
3655
|
if (!selfProt) return;
|
|
3559
3656
|
run(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
|
|
@@ -3716,7 +3813,7 @@ function SettingsPanel({
|
|
|
3716
3813
|
setConfirmDel(null);
|
|
3717
3814
|
const res = uninstallGlobalQuiet();
|
|
3718
3815
|
setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
|
|
3719
|
-
|
|
3816
|
+
refreshGuard();
|
|
3720
3817
|
guardQ.reload();
|
|
3721
3818
|
} else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
|
|
3722
3819
|
const k = keyOf(cur);
|
|
@@ -3825,12 +3922,13 @@ function SettingsPanel({
|
|
|
3825
3922
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
|
|
3826
3923
|
!guardHere ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3827
3924
|
/* @__PURE__ */ jsx7(Text7, { color: theme.bad, bold: true, children: "removed" }),
|
|
3828
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.
|
|
3832
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children:
|
|
3833
|
-
|
|
3925
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
|
|
3926
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, children: "enter to install" })
|
|
3927
|
+
] }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3928
|
+
/* @__PURE__ */ jsx7(Text7, { color: guardOld ? theme.warn : theme.ok, children: `v${guardVer ?? "?"}` }),
|
|
3929
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guardOld ? " \xB7 update available \xB7 enter update" : " (latest) \xB7 enter reinstall" }),
|
|
3930
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 d remove" })
|
|
3931
|
+
] })
|
|
3834
3932
|
] });
|
|
3835
3933
|
case "self":
|
|
3836
3934
|
return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
@@ -3894,7 +3992,7 @@ function SettingsPanel({
|
|
|
3894
3992
|
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
3993
|
const SECTION_DESC = {
|
|
3896
3994
|
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
|
|
3995
|
+
PROTECTION: "guard hook: enter install/update (writes the files) \xB7 d remove \xB7 + self-protection",
|
|
3898
3996
|
"LOCAL LOGS": "mirror every decision to a file + dashboard link",
|
|
3899
3997
|
WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
|
|
3900
3998
|
ALERTS: `one email + one telegram (${alerts.length}) \xB7 enter edits \xB7 m on/off`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.81.
|
|
3
|
+
"version": "0.81.82",
|
|
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": {
|