@solongate/proxy 0.82.14 → 0.82.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client/client.d.ts +1 -1
- package/dist/commands/index.js +2 -2
- package/dist/global-install.js +23 -1
- package/dist/index.js +27 -7
- package/dist/tui/index.js +18 -0
- package/package.json +1 -1
- package/dist/login.d.ts +0 -2
- package/dist/login.js +0 -466
|
@@ -44,7 +44,7 @@ export declare class ApiError extends Error {
|
|
|
44
44
|
readonly code: string;
|
|
45
45
|
constructor(status: number, code: string, message: string);
|
|
46
46
|
}
|
|
47
|
-
/** Raised when no API key can be found — tells the user to run `solongate
|
|
47
|
+
/** Raised when no API key can be found — tells the user to run `solongate` and pair. */
|
|
48
48
|
export declare class NotAuthenticatedError extends Error {
|
|
49
49
|
constructor();
|
|
50
50
|
}
|
package/dist/commands/index.js
CHANGED
|
@@ -1039,7 +1039,7 @@ async function run6(argv) {
|
|
|
1039
1039
|
const json = flagBool(flags, "json");
|
|
1040
1040
|
const checks = [];
|
|
1041
1041
|
if (!isAuthenticated()) {
|
|
1042
|
-
checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate
|
|
1042
|
+
checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate`, add your account in the Accounts panel" });
|
|
1043
1043
|
} else {
|
|
1044
1044
|
const { apiUrl } = resolveCredentials();
|
|
1045
1045
|
checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
|
|
@@ -1059,7 +1059,7 @@ async function run6(argv) {
|
|
|
1059
1059
|
}
|
|
1060
1060
|
try {
|
|
1061
1061
|
const g = await api.settings.getGuardStatus();
|
|
1062
|
-
checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7
|
|
1062
|
+
checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7 update from \`solongate\` \u2192 Settings \u2192 guard` });
|
|
1063
1063
|
} catch {
|
|
1064
1064
|
}
|
|
1065
1065
|
}
|
package/dist/global-install.js
CHANGED
|
@@ -114,6 +114,17 @@ function clearGuardUpdateCheck() {
|
|
|
114
114
|
function readHook(filename) {
|
|
115
115
|
return readFileSync(join(HOOKS_DIR, filename), "utf-8");
|
|
116
116
|
}
|
|
117
|
+
function firstAccountCredential() {
|
|
118
|
+
try {
|
|
119
|
+
const raw = JSON.parse(readFileSync(join(homedir(), ".solongate", "accounts.json"), "utf-8"));
|
|
120
|
+
if (Array.isArray(raw)) {
|
|
121
|
+
const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
|
|
122
|
+
if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
117
128
|
function readGuard() {
|
|
118
129
|
const bundled = join(HOOKS_DIR, "guard.bundled.mjs");
|
|
119
130
|
return existsSync(bundled) ? readFileSync(bundled, "utf-8") : readHook("guard.mjs");
|
|
@@ -193,6 +204,13 @@ function installGlobalQuiet() {
|
|
|
193
204
|
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
194
205
|
} catch {
|
|
195
206
|
}
|
|
207
|
+
if (!apiKey) {
|
|
208
|
+
const acc = firstAccountCredential();
|
|
209
|
+
if (acc.apiKey) {
|
|
210
|
+
apiKey = acc.apiKey;
|
|
211
|
+
if (acc.apiUrl) apiUrl = acc.apiUrl;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
196
214
|
if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
|
|
197
215
|
mkdirSync(p.hooksDir, { recursive: true });
|
|
198
216
|
mkdirSync(p.claudeDir, { recursive: true });
|
|
@@ -323,7 +341,7 @@ function writeShimBlock(file, block) {
|
|
|
323
341
|
function installClaudeShim(shieldPath) {
|
|
324
342
|
const real = resolveRealClaude();
|
|
325
343
|
if (!real) {
|
|
326
|
-
console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-
|
|
344
|
+
console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-open `solongate` \u2192 Settings \u2192 guard.)");
|
|
327
345
|
return;
|
|
328
346
|
}
|
|
329
347
|
const node = process.execPath.replace(/\\/g, "/");
|
|
@@ -361,6 +379,10 @@ async function runGlobalInstall(opts = {}) {
|
|
|
361
379
|
} catch {
|
|
362
380
|
}
|
|
363
381
|
}
|
|
382
|
+
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
383
|
+
const acc = firstAccountCredential();
|
|
384
|
+
if (acc.apiKey) apiKey = acc.apiKey;
|
|
385
|
+
}
|
|
364
386
|
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
365
387
|
apiKey = await ask(" Enter your SolonGate API key (sg_live_\u2026 from https://dashboard.solongate.com): ");
|
|
366
388
|
}
|
package/dist/index.js
CHANGED
|
@@ -271,7 +271,7 @@ function parseArgs(argv) {
|
|
|
271
271
|
}
|
|
272
272
|
if (!apiKey) {
|
|
273
273
|
throw new Error(
|
|
274
|
-
"Not logged in. Run this once to get started:\n\n solongate
|
|
274
|
+
"Not logged in. Run this once to get started:\n\n solongate\n\n then add your account from the Accounts panel.\n"
|
|
275
275
|
);
|
|
276
276
|
}
|
|
277
277
|
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
@@ -327,7 +327,7 @@ function parseArgs(argv) {
|
|
|
327
327
|
}
|
|
328
328
|
if (upstreamArgs.length === 0) {
|
|
329
329
|
throw new Error(
|
|
330
|
-
"No upstream server command provided.\n\nIf you just want to get started, run:\n solongate
|
|
330
|
+
"No upstream server command provided.\n\nIf you just want to get started, run:\n solongate\n"
|
|
331
331
|
);
|
|
332
332
|
}
|
|
333
333
|
const [command, ...commandArgs] = upstreamArgs;
|
|
@@ -10245,6 +10245,17 @@ function globalPaths() {
|
|
|
10245
10245
|
function readHook(filename) {
|
|
10246
10246
|
return readFileSync9(join11(HOOKS_DIR, filename), "utf-8");
|
|
10247
10247
|
}
|
|
10248
|
+
function firstAccountCredential() {
|
|
10249
|
+
try {
|
|
10250
|
+
const raw = JSON.parse(readFileSync9(join11(homedir9(), ".solongate", "accounts.json"), "utf-8"));
|
|
10251
|
+
if (Array.isArray(raw)) {
|
|
10252
|
+
const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
|
|
10253
|
+
if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
|
|
10254
|
+
}
|
|
10255
|
+
} catch {
|
|
10256
|
+
}
|
|
10257
|
+
return {};
|
|
10258
|
+
}
|
|
10248
10259
|
function readGuard() {
|
|
10249
10260
|
const bundled = join11(HOOKS_DIR, "guard.bundled.mjs");
|
|
10250
10261
|
return existsSync4(bundled) ? readFileSync9(bundled, "utf-8") : readHook("guard.mjs");
|
|
@@ -10295,6 +10306,13 @@ function installGlobalQuiet() {
|
|
|
10295
10306
|
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
10296
10307
|
} catch {
|
|
10297
10308
|
}
|
|
10309
|
+
if (!apiKey) {
|
|
10310
|
+
const acc = firstAccountCredential();
|
|
10311
|
+
if (acc.apiKey) {
|
|
10312
|
+
apiKey = acc.apiKey;
|
|
10313
|
+
if (acc.apiUrl) apiUrl = acc.apiUrl;
|
|
10314
|
+
}
|
|
10315
|
+
}
|
|
10298
10316
|
if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
|
|
10299
10317
|
mkdirSync8(p.hooksDir, { recursive: true });
|
|
10300
10318
|
mkdirSync8(p.claudeDir, { recursive: true });
|
|
@@ -12012,7 +12030,7 @@ async function run6(argv) {
|
|
|
12012
12030
|
const json = flagBool(flags, "json");
|
|
12013
12031
|
const checks = [];
|
|
12014
12032
|
if (!isAuthenticated()) {
|
|
12015
|
-
checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate
|
|
12033
|
+
checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate`, add your account in the Accounts panel" });
|
|
12016
12034
|
} else {
|
|
12017
12035
|
const { apiUrl } = resolveCredentials();
|
|
12018
12036
|
checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
|
|
@@ -12032,7 +12050,7 @@ async function run6(argv) {
|
|
|
12032
12050
|
}
|
|
12033
12051
|
try {
|
|
12034
12052
|
const g = await api.settings.getGuardStatus();
|
|
12035
|
-
checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7
|
|
12053
|
+
checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7 update from \`solongate\` \u2192 Settings \u2192 guard` });
|
|
12036
12054
|
} catch {
|
|
12037
12055
|
}
|
|
12038
12056
|
}
|
|
@@ -16968,7 +16986,7 @@ ${msg.content.text}`;
|
|
|
16968
16986
|
|
|
16969
16987
|
// src/index.ts
|
|
16970
16988
|
init_cli_utils();
|
|
16971
|
-
var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["
|
|
16989
|
+
var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["shield", "create", "inject", "pull", "push", "list", "ls", "logs-server", "local-logs", "policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent", "doctor", "watch", "keys", "mcp", "alerts", "webhooks", "dataroom"]);
|
|
16972
16990
|
var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "");
|
|
16973
16991
|
if (!IS_HUMAN_CLI) {
|
|
16974
16992
|
console.log = (...args) => {
|
|
@@ -17034,8 +17052,10 @@ async function main4() {
|
|
|
17034
17052
|
process.exit(code);
|
|
17035
17053
|
}
|
|
17036
17054
|
if (subcommand === "login") {
|
|
17037
|
-
|
|
17038
|
-
|
|
17055
|
+
console.log("");
|
|
17056
|
+
console.log(` ${c.dim}\`solongate login\` was removed.${c.reset} Run ${c.cyan}solongate${c.reset} and add your`);
|
|
17057
|
+
console.log(` account from the Accounts panel \u2014 the guard installs and updates from there.`);
|
|
17058
|
+
console.log("");
|
|
17039
17059
|
return;
|
|
17040
17060
|
}
|
|
17041
17061
|
if (subcommand === "shield") {
|
package/dist/tui/index.js
CHANGED
|
@@ -3303,6 +3303,17 @@ function globalPaths() {
|
|
|
3303
3303
|
function readHook(filename) {
|
|
3304
3304
|
return readFileSync4(join6(HOOKS_DIR, filename), "utf-8");
|
|
3305
3305
|
}
|
|
3306
|
+
function firstAccountCredential() {
|
|
3307
|
+
try {
|
|
3308
|
+
const raw = JSON.parse(readFileSync4(join6(homedir6(), ".solongate", "accounts.json"), "utf-8"));
|
|
3309
|
+
if (Array.isArray(raw)) {
|
|
3310
|
+
const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
|
|
3311
|
+
if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
|
|
3312
|
+
}
|
|
3313
|
+
} catch {
|
|
3314
|
+
}
|
|
3315
|
+
return {};
|
|
3316
|
+
}
|
|
3306
3317
|
function readGuard() {
|
|
3307
3318
|
const bundled = join6(HOOKS_DIR, "guard.bundled.mjs");
|
|
3308
3319
|
return existsSync2(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
|
|
@@ -3354,6 +3365,13 @@ function installGlobalQuiet() {
|
|
|
3354
3365
|
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
3355
3366
|
} catch {
|
|
3356
3367
|
}
|
|
3368
|
+
if (!apiKey) {
|
|
3369
|
+
const acc = firstAccountCredential();
|
|
3370
|
+
if (acc.apiKey) {
|
|
3371
|
+
apiKey = acc.apiKey;
|
|
3372
|
+
if (acc.apiUrl) apiUrl = acc.apiUrl;
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3357
3375
|
if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
|
|
3358
3376
|
mkdirSync4(p.hooksDir, { recursive: true });
|
|
3359
3377
|
mkdirSync4(p.claudeDir, { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.82.
|
|
3
|
+
"version": "0.82.16",
|
|
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": {
|
package/dist/login.d.ts
DELETED
package/dist/login.js
DELETED
|
@@ -1,466 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/login.ts
|
|
4
|
-
import { spawn } from "child_process";
|
|
5
|
-
|
|
6
|
-
// src/cli-utils.ts
|
|
7
|
-
var c = {
|
|
8
|
-
reset: "\x1B[0m",
|
|
9
|
-
bold: "\x1B[1m",
|
|
10
|
-
dim: "\x1B[2m",
|
|
11
|
-
italic: "\x1B[3m",
|
|
12
|
-
white: "\x1B[97m",
|
|
13
|
-
gray: "\x1B[90m",
|
|
14
|
-
blue1: "\x1B[38;2;20;50;160m",
|
|
15
|
-
blue2: "\x1B[38;2;40;80;190m",
|
|
16
|
-
blue3: "\x1B[38;2;60;110;215m",
|
|
17
|
-
blue4: "\x1B[38;2;90;140;230m",
|
|
18
|
-
blue5: "\x1B[38;2;130;170;240m",
|
|
19
|
-
blue6: "\x1B[38;2;170;200;250m",
|
|
20
|
-
green: "\x1B[38;2;80;200;120m",
|
|
21
|
-
red: "\x1B[38;2;220;80;80m",
|
|
22
|
-
cyan: "\x1B[38;2;100;200;220m",
|
|
23
|
-
yellow: "\x1B[38;2;220;200;80m",
|
|
24
|
-
bgBlue: "\x1B[48;2;20;50;160m"
|
|
25
|
-
};
|
|
26
|
-
var BANNER_COLORS = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
|
|
27
|
-
|
|
28
|
-
// src/global-install.ts
|
|
29
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "fs";
|
|
30
|
-
import { resolve, join, dirname } from "path";
|
|
31
|
-
import { homedir } from "os";
|
|
32
|
-
import { fileURLToPath } from "url";
|
|
33
|
-
import { createInterface } from "readline";
|
|
34
|
-
import { execFileSync } from "child_process";
|
|
35
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
|
-
var HOOKS_DIR = resolve(__dirname, "..", "hooks");
|
|
37
|
-
function lockFile(file) {
|
|
38
|
-
if (!existsSync(file)) return;
|
|
39
|
-
try {
|
|
40
|
-
if (process.platform === "win32") {
|
|
41
|
-
try {
|
|
42
|
-
execFileSync("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE)"], { stdio: "ignore" });
|
|
43
|
-
} catch {
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
execFileSync("attrib", ["+R", file], { stdio: "ignore" });
|
|
47
|
-
} catch {
|
|
48
|
-
}
|
|
49
|
-
} else if (process.platform === "darwin") {
|
|
50
|
-
try {
|
|
51
|
-
execFileSync("chflags", ["uchg", file], { stdio: "ignore" });
|
|
52
|
-
} catch {
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
try {
|
|
56
|
-
execFileSync("chattr", ["+i", file], { stdio: "ignore" });
|
|
57
|
-
} catch {
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
} catch {
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function unlockFile(file) {
|
|
64
|
-
if (!existsSync(file)) return;
|
|
65
|
-
try {
|
|
66
|
-
if (process.platform === "win32") {
|
|
67
|
-
try {
|
|
68
|
-
execFileSync("icacls", [file, "/remove:d", "*S-1-1-0"], { stdio: "ignore" });
|
|
69
|
-
} catch {
|
|
70
|
-
}
|
|
71
|
-
try {
|
|
72
|
-
execFileSync("icacls", [file, "/reset"], { stdio: "ignore" });
|
|
73
|
-
} catch {
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
execFileSync("attrib", ["-R", file], { stdio: "ignore" });
|
|
77
|
-
} catch {
|
|
78
|
-
}
|
|
79
|
-
} else if (process.platform === "darwin") {
|
|
80
|
-
try {
|
|
81
|
-
execFileSync("chflags", ["nouchg", file], { stdio: "ignore" });
|
|
82
|
-
} catch {
|
|
83
|
-
}
|
|
84
|
-
} else {
|
|
85
|
-
try {
|
|
86
|
-
execFileSync("chattr", ["-i", file], { stdio: "ignore" });
|
|
87
|
-
} catch {
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
} catch {
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function protectedTargets() {
|
|
94
|
-
const p = globalPaths();
|
|
95
|
-
return [
|
|
96
|
-
join(p.hooksDir, "guard.mjs"),
|
|
97
|
-
join(p.hooksDir, "audit.mjs"),
|
|
98
|
-
join(p.hooksDir, "stop.mjs"),
|
|
99
|
-
join(p.hooksDir, "shield.mjs"),
|
|
100
|
-
p.configPath,
|
|
101
|
-
p.settingsPath,
|
|
102
|
-
p.antigravityHooksPath
|
|
103
|
-
];
|
|
104
|
-
}
|
|
105
|
-
function lockProtected() {
|
|
106
|
-
for (const f of protectedTargets()) lockFile(f);
|
|
107
|
-
}
|
|
108
|
-
function unlockProtected() {
|
|
109
|
-
for (const f of protectedTargets()) unlockFile(f);
|
|
110
|
-
}
|
|
111
|
-
function globalPaths() {
|
|
112
|
-
const home = homedir();
|
|
113
|
-
const sgDir = join(home, ".solongate");
|
|
114
|
-
const hooksDir = join(sgDir, "hooks");
|
|
115
|
-
const claudeDir = join(home, ".claude");
|
|
116
|
-
const antigravityDir = join(home, ".gemini", "config");
|
|
117
|
-
return {
|
|
118
|
-
home,
|
|
119
|
-
sgDir,
|
|
120
|
-
hooksDir,
|
|
121
|
-
claudeDir,
|
|
122
|
-
antigravityDir,
|
|
123
|
-
settingsPath: join(claudeDir, "settings.json"),
|
|
124
|
-
backupPath: join(claudeDir, "settings.solongate.bak"),
|
|
125
|
-
configPath: join(sgDir, "cloud-guard.json"),
|
|
126
|
-
// Antigravity reads global hooks from ~/.gemini/config/hooks.json. Only the
|
|
127
|
-
// guard is registered there (PreToolUse); Antigravity's ALLOW-path audit is
|
|
128
|
-
// covered by the passive session-log collector, not a hook.
|
|
129
|
-
antigravityHooksPath: join(antigravityDir, "hooks.json"),
|
|
130
|
-
antigravityBackupPath: join(antigravityDir, "hooks.solongate.bak")
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
function readHook(filename) {
|
|
134
|
-
return readFileSync(join(HOOKS_DIR, filename), "utf-8");
|
|
135
|
-
}
|
|
136
|
-
function readGuard() {
|
|
137
|
-
const bundled = join(HOOKS_DIR, "guard.bundled.mjs");
|
|
138
|
-
return existsSync(bundled) ? readFileSync(bundled, "utf-8") : readHook("guard.mjs");
|
|
139
|
-
}
|
|
140
|
-
var ANTIGRAVITY_GROUP = "solongate-guard";
|
|
141
|
-
function antigravityHookCommand(guardAbs) {
|
|
142
|
-
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
143
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
144
|
-
return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" antigravity "Antigravity"`;
|
|
145
|
-
}
|
|
146
|
-
function installAntigravityGuard(p, guardAbs) {
|
|
147
|
-
mkdirSync(p.antigravityDir, { recursive: true });
|
|
148
|
-
let existing = {};
|
|
149
|
-
if (existsSync(p.antigravityHooksPath)) {
|
|
150
|
-
const raw = readFileSync(p.antigravityHooksPath, "utf-8");
|
|
151
|
-
if (!existsSync(p.antigravityBackupPath)) writeFileSync(p.antigravityBackupPath, raw);
|
|
152
|
-
try {
|
|
153
|
-
existing = JSON.parse(raw);
|
|
154
|
-
} catch {
|
|
155
|
-
existing = {};
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const merged = {
|
|
159
|
-
...existing,
|
|
160
|
-
[ANTIGRAVITY_GROUP]: {
|
|
161
|
-
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: antigravityHookCommand(guardAbs) }] }]
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
writeFileSync(p.antigravityHooksPath, JSON.stringify(merged, null, 2) + "\n");
|
|
165
|
-
}
|
|
166
|
-
function ask(question) {
|
|
167
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
168
|
-
return new Promise((res) => rl.question(question, (a) => {
|
|
169
|
-
rl.close();
|
|
170
|
-
res(a.trim());
|
|
171
|
-
}));
|
|
172
|
-
}
|
|
173
|
-
var SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
|
|
174
|
-
var SHIM_END = "# <<< SolonGate shield <<<";
|
|
175
|
-
function escapeRe(s) {
|
|
176
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
177
|
-
}
|
|
178
|
-
function resolveRealClaude() {
|
|
179
|
-
try {
|
|
180
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
181
|
-
const out = execFileSync(finder, ["claude"], { encoding: "utf-8" }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
182
|
-
if (process.platform === "win32") {
|
|
183
|
-
const low = (s) => s.toLowerCase();
|
|
184
|
-
return out.find((l) => low(l).endsWith(".cmd")) || out.find((l) => low(l).endsWith(".exe")) || out.find((l) => low(l).endsWith(".bat")) || out[0] || null;
|
|
185
|
-
}
|
|
186
|
-
return out[0] || null;
|
|
187
|
-
} catch {
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
function shimTargets() {
|
|
192
|
-
if (process.platform === "win32") {
|
|
193
|
-
try {
|
|
194
|
-
const prof = execFileSync("powershell", ["-NoProfile", "-Command", "$PROFILE.CurrentUserAllHosts"], { encoding: "utf-8" }).trim();
|
|
195
|
-
return prof ? [prof] : [];
|
|
196
|
-
} catch {
|
|
197
|
-
return [];
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return [".bashrc", ".zshrc", ".profile"].map((f) => join(homedir(), f)).filter((f) => existsSync(f));
|
|
201
|
-
}
|
|
202
|
-
function writeShimBlock(file, block) {
|
|
203
|
-
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
204
|
-
let content = existsSync(file) ? readFileSync(file, "utf-8") : "";
|
|
205
|
-
content = content.replace(re, "");
|
|
206
|
-
if (block) {
|
|
207
|
-
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
208
|
-
content += block + "\n";
|
|
209
|
-
}
|
|
210
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
211
|
-
writeFileSync(file, content);
|
|
212
|
-
}
|
|
213
|
-
function installClaudeShim(shieldPath) {
|
|
214
|
-
const real = resolveRealClaude();
|
|
215
|
-
if (!real) {
|
|
216
|
-
console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-run `login`.)");
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
const node = process.execPath.replace(/\\/g, "/");
|
|
220
|
-
const shield = shieldPath.replace(/\\/g, "/");
|
|
221
|
-
const win = process.platform === "win32";
|
|
222
|
-
const block = win ? `${SHIM_BEGIN}
|
|
223
|
-
function claude { & "${node}" "${shield}" -- "${real}" @args }
|
|
224
|
-
${SHIM_END}` : `${SHIM_BEGIN}
|
|
225
|
-
claude() { "${node}" "${shield}" -- "${real}" "$@"; }
|
|
226
|
-
${SHIM_END}`;
|
|
227
|
-
const targets = shimTargets();
|
|
228
|
-
if (targets.length === 0) return;
|
|
229
|
-
for (const file of targets) {
|
|
230
|
-
try {
|
|
231
|
-
writeShimBlock(file, block);
|
|
232
|
-
} catch {
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
async function runGlobalInstall(opts = {}) {
|
|
237
|
-
const p = globalPaths();
|
|
238
|
-
let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
|
|
239
|
-
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
240
|
-
try {
|
|
241
|
-
const cfg = JSON.parse(readFileSync(p.configPath, "utf-8"));
|
|
242
|
-
if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
|
|
243
|
-
} catch {
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
247
|
-
apiKey = await ask(" Enter your SolonGate API key (sg_live_\u2026 from https://dashboard.solongate.com): ");
|
|
248
|
-
}
|
|
249
|
-
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
250
|
-
console.log(" Invalid API key. Must start with sg_live_ or sg_test_");
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
254
|
-
mkdirSync(p.hooksDir, { recursive: true });
|
|
255
|
-
mkdirSync(p.claudeDir, { recursive: true });
|
|
256
|
-
unlockProtected();
|
|
257
|
-
writeFileSync(join(p.hooksDir, "guard.mjs"), readGuard());
|
|
258
|
-
writeFileSync(join(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
259
|
-
writeFileSync(join(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
260
|
-
writeFileSync(join(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
261
|
-
console.log(` Installed hooks \u2192 ${p.hooksDir}`);
|
|
262
|
-
installClaudeShim(join(p.hooksDir, "shield.mjs"));
|
|
263
|
-
writeFileSync(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
264
|
-
console.log(` Wrote ${p.configPath}`);
|
|
265
|
-
let existing = {};
|
|
266
|
-
if (existsSync(p.settingsPath)) {
|
|
267
|
-
const raw = readFileSync(p.settingsPath, "utf-8");
|
|
268
|
-
if (!existsSync(p.backupPath)) {
|
|
269
|
-
writeFileSync(p.backupPath, raw);
|
|
270
|
-
console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
|
|
271
|
-
}
|
|
272
|
-
try {
|
|
273
|
-
existing = JSON.parse(raw);
|
|
274
|
-
} catch {
|
|
275
|
-
existing = {};
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
const guardAbs = join(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
|
|
279
|
-
const auditAbs = join(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
|
|
280
|
-
const stopAbs = join(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
|
|
281
|
-
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
282
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
283
|
-
const hookCmd = (script) => `${call}"${nodeBin}" "${script}" claude-code "Claude Code"`;
|
|
284
|
-
const merged = {
|
|
285
|
-
...existing,
|
|
286
|
-
hooks: {
|
|
287
|
-
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(guardAbs) }] }],
|
|
288
|
-
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(auditAbs) }] }],
|
|
289
|
-
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(stopAbs) }] }]
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
writeFileSync(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
293
|
-
console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
|
|
294
|
-
try {
|
|
295
|
-
installAntigravityGuard(p, guardAbs);
|
|
296
|
-
console.log(` Registered Antigravity CLI guard \u2192 ${p.antigravityHooksPath}`);
|
|
297
|
-
} catch {
|
|
298
|
-
}
|
|
299
|
-
if (process.env["SOLONGATE_OS_LOCK"] === "1") {
|
|
300
|
-
lockProtected();
|
|
301
|
-
console.log(" Locked protection files (OS-level read-only/immutable).");
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// src/api-client/client.ts
|
|
306
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
|
|
307
|
-
import { resolve as resolve2, join as join2 } from "path";
|
|
308
|
-
import { homedir as homedir2 } from "os";
|
|
309
|
-
var accountsFile = () => join2(homedir2(), ".solongate", "accounts.json");
|
|
310
|
-
function saveAccount(acc) {
|
|
311
|
-
try {
|
|
312
|
-
const list = (() => {
|
|
313
|
-
try {
|
|
314
|
-
const raw = JSON.parse(readFileSync2(accountsFile(), "utf-8"));
|
|
315
|
-
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
316
|
-
} catch {
|
|
317
|
-
return [];
|
|
318
|
-
}
|
|
319
|
-
})();
|
|
320
|
-
const next = list.filter((a) => a.apiKey !== acc.apiKey);
|
|
321
|
-
next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
|
|
322
|
-
mkdirSync2(join2(homedir2(), ".solongate"), { recursive: true });
|
|
323
|
-
writeFileSync2(accountsFile(), JSON.stringify(next, null, 2));
|
|
324
|
-
} catch {
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// src/login.ts
|
|
329
|
-
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
330
|
-
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
331
|
-
function startSpinner(text) {
|
|
332
|
-
if (!process.stderr.isTTY) {
|
|
333
|
-
process.stderr.write(` ${text}
|
|
334
|
-
`);
|
|
335
|
-
return (finalLine) => {
|
|
336
|
-
if (finalLine) process.stderr.write(finalLine + "\n");
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
let i = 0;
|
|
340
|
-
const draw = () => {
|
|
341
|
-
const frame = SPINNER_FRAMES[i = (i + 1) % SPINNER_FRAMES.length];
|
|
342
|
-
process.stderr.write(`\r ${c.cyan}${frame}${c.reset} ${c.dim}${text}${c.reset}\x1B[K`);
|
|
343
|
-
};
|
|
344
|
-
draw();
|
|
345
|
-
const timer = setInterval(draw, 80);
|
|
346
|
-
return (finalLine) => {
|
|
347
|
-
clearInterval(timer);
|
|
348
|
-
process.stderr.write("\r\x1B[K");
|
|
349
|
-
if (finalLine) process.stderr.write(finalLine + "\n");
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
function parseArgs(argv) {
|
|
353
|
-
const args = argv.slice(3);
|
|
354
|
-
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
355
|
-
let install = true;
|
|
356
|
-
for (let i = 0; i < args.length; i++) {
|
|
357
|
-
if (args[i] === "--api-url") apiUrl = args[++i] || apiUrl;
|
|
358
|
-
else if (args[i] === "--no-install") install = false;
|
|
359
|
-
}
|
|
360
|
-
return { apiUrl, install };
|
|
361
|
-
}
|
|
362
|
-
function openBrowser(url) {
|
|
363
|
-
try {
|
|
364
|
-
const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
365
|
-
const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
|
|
366
|
-
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
367
|
-
child.on("error", () => {
|
|
368
|
-
});
|
|
369
|
-
child.unref();
|
|
370
|
-
} catch {
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
async function main() {
|
|
374
|
-
const { apiUrl, install } = parseArgs(process.argv);
|
|
375
|
-
console.log("");
|
|
376
|
-
console.log(` ${c.bold}${c.blue4}SolonGate${c.reset} ${c.dim}\u2014 Login${c.reset}`);
|
|
377
|
-
console.log("");
|
|
378
|
-
let start;
|
|
379
|
-
try {
|
|
380
|
-
const res = await fetch(`${apiUrl}/api/v1/auth/device/start`, { method: "POST" });
|
|
381
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
382
|
-
start = await res.json();
|
|
383
|
-
} catch (e) {
|
|
384
|
-
console.log(` Could not reach SolonGate (${apiUrl}).`);
|
|
385
|
-
console.log(` ${e instanceof Error ? e.message : String(e)}`);
|
|
386
|
-
process.exit(1);
|
|
387
|
-
}
|
|
388
|
-
const { device_code, user_code, verification_uri_complete, verification_uri, interval, expires_in } = start;
|
|
389
|
-
const verifyUrl = verification_uri_complete || verification_uri;
|
|
390
|
-
console.log(` ${c.blue5}\u2192${c.reset} Opening your browser to authorize this device\u2026`);
|
|
391
|
-
console.log("");
|
|
392
|
-
console.log(` ${c.dim}If it doesn't open, visit this link:${c.reset}`);
|
|
393
|
-
console.log(` ${c.cyan}${verifyUrl}${c.reset}`);
|
|
394
|
-
console.log("");
|
|
395
|
-
void user_code;
|
|
396
|
-
openBrowser(verifyUrl);
|
|
397
|
-
const pollMs = Math.max(2, Number(interval) || 3) * 1e3;
|
|
398
|
-
const deadline = Date.now() + (Number(expires_in) || 600) * 1e3;
|
|
399
|
-
let apiKey = "";
|
|
400
|
-
const stopSpinner = startSpinner("Waiting for authorization\u2026");
|
|
401
|
-
while (Date.now() < deadline) {
|
|
402
|
-
await sleep(pollMs);
|
|
403
|
-
try {
|
|
404
|
-
const res = await fetch(`${apiUrl}/api/v1/auth/device/poll`, {
|
|
405
|
-
method: "POST",
|
|
406
|
-
headers: { "Content-Type": "application/json" },
|
|
407
|
-
body: JSON.stringify({ device_code })
|
|
408
|
-
});
|
|
409
|
-
const data = await res.json().catch(() => ({}));
|
|
410
|
-
if (data?.status === "approved" && data?.api_key) {
|
|
411
|
-
apiKey = data.api_key;
|
|
412
|
-
stopSpinner();
|
|
413
|
-
console.log("");
|
|
414
|
-
const who = data?.user?.name || data?.user?.email;
|
|
415
|
-
const mail = data?.user?.email;
|
|
416
|
-
if (who) {
|
|
417
|
-
const suffix = mail && mail !== who ? ` ${c.dim}(${mail})${c.reset}` : "";
|
|
418
|
-
console.log(` ${c.green}\u2713${c.reset} ${c.bold}Logged in as ${c.white}${who}${c.reset}${suffix}`);
|
|
419
|
-
} else {
|
|
420
|
-
console.log(` ${c.green}\u2713${c.reset} ${c.bold}Logged in${c.reset}`);
|
|
421
|
-
}
|
|
422
|
-
if (data?.project?.name) {
|
|
423
|
-
console.log(` ${c.dim}Project:${c.reset} ${c.cyan}${data.project.name}${c.reset}`);
|
|
424
|
-
}
|
|
425
|
-
saveAccount({ apiKey, apiUrl, project: data?.project?.name, user: data?.user?.name || data?.user?.email, email: data?.user?.email });
|
|
426
|
-
break;
|
|
427
|
-
}
|
|
428
|
-
if (data?.status === "expired" || data?.status === "not_found") {
|
|
429
|
-
stopSpinner(` ${c.red}\u2717${c.reset} Code expired. Run \`login\` again.`);
|
|
430
|
-
process.exit(1);
|
|
431
|
-
}
|
|
432
|
-
} catch {
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
if (!apiKey) {
|
|
436
|
-
stopSpinner(` ${c.red}\u2717${c.reset} Timed out waiting for authorization. Run \`login\` again.`);
|
|
437
|
-
process.exit(1);
|
|
438
|
-
}
|
|
439
|
-
if (!install) {
|
|
440
|
-
console.log("");
|
|
441
|
-
console.log(" Logged in. Re-run `login` to enable system-wide enforcement.");
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
console.log("");
|
|
445
|
-
await runGlobalInstall({ apiKey, apiUrl });
|
|
446
|
-
console.log("");
|
|
447
|
-
console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
448
|
-
console.log(" \u2502 You are logged in and protected. \u2502");
|
|
449
|
-
console.log(" \u2502 Every Claude Code session on this machine is \u2502");
|
|
450
|
-
console.log(" \u2502 now guarded by your cloud policy (OPA WASM). \u2502");
|
|
451
|
-
console.log(" \u2502 \u2502");
|
|
452
|
-
console.log(" \u2502 Manage policy: https://dashboard.solongate.com \u2502");
|
|
453
|
-
console.log(" \u2502 Restart Claude Code to apply. \u2502");
|
|
454
|
-
console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
455
|
-
console.log("");
|
|
456
|
-
console.log(` ${c.bold}${c.yellow}! Already-open terminals are NOT protected yet.${c.reset}`);
|
|
457
|
-
console.log(` ${c.dim} Hooks load only when a session starts, so any terminal`);
|
|
458
|
-
console.log(` (and the Claude Code running in it) that was already open`);
|
|
459
|
-
console.log(` won't be caught. Open a NEW terminal and start Claude Code`);
|
|
460
|
-
console.log(` there for the guard + audit logging to take effect.${c.reset}`);
|
|
461
|
-
console.log("");
|
|
462
|
-
}
|
|
463
|
-
main().catch((err) => {
|
|
464
|
-
console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
465
|
-
process.exit(1);
|
|
466
|
-
});
|