@solongate/proxy 0.43.0 → 0.45.0

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.
@@ -0,0 +1,198 @@
1
+ // src/global-install.ts
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
3
+ import { resolve, join, dirname } from "path";
4
+ import { homedir } from "os";
5
+ import { fileURLToPath } from "url";
6
+ import { createInterface } from "readline";
7
+ import { execFileSync } from "child_process";
8
+ var __dirname = dirname(fileURLToPath(import.meta.url));
9
+ var HOOKS_DIR = resolve(__dirname, "..", "hooks");
10
+ function lockFile(file) {
11
+ if (!existsSync(file)) return;
12
+ try {
13
+ if (process.platform === "win32") {
14
+ try {
15
+ execFileSync("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE)"], { stdio: "ignore" });
16
+ } catch {
17
+ }
18
+ try {
19
+ execFileSync("attrib", ["+R", file], { stdio: "ignore" });
20
+ } catch {
21
+ }
22
+ } else if (process.platform === "darwin") {
23
+ try {
24
+ execFileSync("chflags", ["uchg", file], { stdio: "ignore" });
25
+ } catch {
26
+ }
27
+ } else {
28
+ try {
29
+ execFileSync("chattr", ["+i", file], { stdio: "ignore" });
30
+ } catch {
31
+ }
32
+ }
33
+ } catch {
34
+ }
35
+ }
36
+ function unlockFile(file) {
37
+ if (!existsSync(file)) return;
38
+ try {
39
+ if (process.platform === "win32") {
40
+ try {
41
+ execFileSync("icacls", [file, "/remove:d", "*S-1-1-0"], { stdio: "ignore" });
42
+ } catch {
43
+ }
44
+ try {
45
+ execFileSync("icacls", [file, "/reset"], { stdio: "ignore" });
46
+ } catch {
47
+ }
48
+ try {
49
+ execFileSync("attrib", ["-R", file], { stdio: "ignore" });
50
+ } catch {
51
+ }
52
+ } else if (process.platform === "darwin") {
53
+ try {
54
+ execFileSync("chflags", ["nouchg", file], { stdio: "ignore" });
55
+ } catch {
56
+ }
57
+ } else {
58
+ try {
59
+ execFileSync("chattr", ["-i", file], { stdio: "ignore" });
60
+ } catch {
61
+ }
62
+ }
63
+ } catch {
64
+ }
65
+ }
66
+ function protectedTargets() {
67
+ const p = globalPaths();
68
+ return [
69
+ join(p.hooksDir, "guard.mjs"),
70
+ join(p.hooksDir, "audit.mjs"),
71
+ join(p.hooksDir, "stop.mjs"),
72
+ p.configPath,
73
+ p.settingsPath
74
+ ];
75
+ }
76
+ function lockProtected() {
77
+ for (const f of protectedTargets()) lockFile(f);
78
+ }
79
+ function unlockProtected() {
80
+ for (const f of protectedTargets()) unlockFile(f);
81
+ }
82
+ function globalPaths() {
83
+ const home = homedir();
84
+ const sgDir = join(home, ".solongate");
85
+ const hooksDir = join(sgDir, "hooks");
86
+ const claudeDir = join(home, ".claude");
87
+ return {
88
+ home,
89
+ sgDir,
90
+ hooksDir,
91
+ claudeDir,
92
+ settingsPath: join(claudeDir, "settings.json"),
93
+ backupPath: join(claudeDir, "settings.solongate.bak"),
94
+ configPath: join(sgDir, "cloud-guard.json")
95
+ };
96
+ }
97
+ function readHook(filename) {
98
+ return readFileSync(join(HOOKS_DIR, filename), "utf-8");
99
+ }
100
+ function readGuard() {
101
+ const bundled = join(HOOKS_DIR, "guard.bundled.mjs");
102
+ return existsSync(bundled) ? readFileSync(bundled, "utf-8") : readHook("guard.mjs");
103
+ }
104
+ function ask(question) {
105
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
106
+ return new Promise((res) => rl.question(question, (a) => {
107
+ rl.close();
108
+ res(a.trim());
109
+ }));
110
+ }
111
+ function runGlobalRestore() {
112
+ const p = globalPaths();
113
+ unlockProtected();
114
+ if (existsSync(p.backupPath)) {
115
+ writeFileSync(p.settingsPath, readFileSync(p.backupPath, "utf-8"));
116
+ console.log(` Restored ${p.settingsPath} from backup.`);
117
+ } else if (existsSync(p.settingsPath)) {
118
+ try {
119
+ const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
120
+ delete s.hooks;
121
+ writeFileSync(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
122
+ console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
123
+ } catch {
124
+ }
125
+ } else {
126
+ console.log(" Nothing to restore \u2014 no global Claude Code settings found.");
127
+ }
128
+ console.log(" Global SolonGate enforcement uninstalled. Restart Claude Code.");
129
+ }
130
+ async function runGlobalInstall(opts = {}) {
131
+ const p = globalPaths();
132
+ let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
133
+ if (!apiKey || apiKey === "sg_live_your_key_here") {
134
+ try {
135
+ const cfg = JSON.parse(readFileSync(p.configPath, "utf-8"));
136
+ if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
137
+ } catch {
138
+ }
139
+ }
140
+ if (!apiKey || apiKey === "sg_live_your_key_here") {
141
+ apiKey = await ask(" Enter your SolonGate API key (sg_live_\u2026 from https://dashboard.solongate.com): ");
142
+ }
143
+ if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
144
+ console.log(" Invalid API key. Must start with sg_live_ or sg_test_");
145
+ process.exit(1);
146
+ }
147
+ const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
148
+ mkdirSync(p.hooksDir, { recursive: true });
149
+ mkdirSync(p.claudeDir, { recursive: true });
150
+ unlockProtected();
151
+ writeFileSync(join(p.hooksDir, "guard.mjs"), readGuard());
152
+ writeFileSync(join(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
153
+ writeFileSync(join(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
154
+ console.log(` Installed hooks \u2192 ${p.hooksDir}`);
155
+ writeFileSync(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
156
+ console.log(` Wrote ${p.configPath}`);
157
+ let existing = {};
158
+ if (existsSync(p.settingsPath)) {
159
+ const raw = readFileSync(p.settingsPath, "utf-8");
160
+ if (!existsSync(p.backupPath)) {
161
+ writeFileSync(p.backupPath, raw);
162
+ console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
163
+ }
164
+ try {
165
+ existing = JSON.parse(raw);
166
+ } catch {
167
+ existing = {};
168
+ }
169
+ }
170
+ const guardAbs = join(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
171
+ const auditAbs = join(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
172
+ const stopAbs = join(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
173
+ const merged = {
174
+ ...existing,
175
+ hooks: {
176
+ PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: `node "${guardAbs}" claude-code "Claude Code"` }] }],
177
+ PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: `node "${auditAbs}" claude-code "Claude Code"` }] }],
178
+ Stop: [{ matcher: "", hooks: [{ type: "command", command: `node "${stopAbs}" claude-code "Claude Code"` }] }]
179
+ }
180
+ };
181
+ writeFileSync(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
182
+ console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
183
+ if (process.env["SOLONGATE_OS_LOCK"] === "1") {
184
+ lockProtected();
185
+ console.log(" Locked protection files (OS-level read-only/immutable).");
186
+ }
187
+ }
188
+ async function installGlobalWithKey(apiKey, apiUrl) {
189
+ await runGlobalInstall({ apiKey, apiUrl });
190
+ }
191
+ export {
192
+ globalPaths,
193
+ installGlobalWithKey,
194
+ lockProtected,
195
+ runGlobalInstall,
196
+ runGlobalRestore,
197
+ unlockProtected
198
+ };