@solongate/proxy 0.82.14 → 0.82.15
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 +1 -1
- package/dist/index.js +9 -7
- 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
|
@@ -323,7 +323,7 @@ function writeShimBlock(file, block) {
|
|
|
323
323
|
function installClaudeShim(shieldPath) {
|
|
324
324
|
const real = resolveRealClaude();
|
|
325
325
|
if (!real) {
|
|
326
|
-
console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-
|
|
326
|
+
console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-open `solongate` \u2192 Settings \u2192 guard.)");
|
|
327
327
|
return;
|
|
328
328
|
}
|
|
329
329
|
const node = process.execPath.replace(/\\/g, "/");
|
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;
|
|
@@ -12012,7 +12012,7 @@ async function run6(argv) {
|
|
|
12012
12012
|
const json = flagBool(flags, "json");
|
|
12013
12013
|
const checks = [];
|
|
12014
12014
|
if (!isAuthenticated()) {
|
|
12015
|
-
checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate
|
|
12015
|
+
checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate`, add your account in the Accounts panel" });
|
|
12016
12016
|
} else {
|
|
12017
12017
|
const { apiUrl } = resolveCredentials();
|
|
12018
12018
|
checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
|
|
@@ -12032,7 +12032,7 @@ async function run6(argv) {
|
|
|
12032
12032
|
}
|
|
12033
12033
|
try {
|
|
12034
12034
|
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
|
|
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 update from \`solongate\` \u2192 Settings \u2192 guard` });
|
|
12036
12036
|
} catch {
|
|
12037
12037
|
}
|
|
12038
12038
|
}
|
|
@@ -16968,7 +16968,7 @@ ${msg.content.text}`;
|
|
|
16968
16968
|
|
|
16969
16969
|
// src/index.ts
|
|
16970
16970
|
init_cli_utils();
|
|
16971
|
-
var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["
|
|
16971
|
+
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
16972
|
var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "");
|
|
16973
16973
|
if (!IS_HUMAN_CLI) {
|
|
16974
16974
|
console.log = (...args) => {
|
|
@@ -17034,8 +17034,10 @@ async function main4() {
|
|
|
17034
17034
|
process.exit(code);
|
|
17035
17035
|
}
|
|
17036
17036
|
if (subcommand === "login") {
|
|
17037
|
-
|
|
17038
|
-
|
|
17037
|
+
console.log("");
|
|
17038
|
+
console.log(` ${c.dim}\`solongate login\` was removed.${c.reset} Run ${c.cyan}solongate${c.reset} and add your`);
|
|
17039
|
+
console.log(` account from the Accounts panel \u2014 the guard installs and updates from there.`);
|
|
17040
|
+
console.log("");
|
|
17039
17041
|
return;
|
|
17040
17042
|
}
|
|
17041
17043
|
if (subcommand === "shield") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.82.
|
|
3
|
+
"version": "0.82.15",
|
|
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
|
-
});
|