@solongate/proxy 0.82.10 → 0.82.11

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.
@@ -5,9 +5,12 @@ export declare function globalPaths(): {
5
5
  sgDir: string;
6
6
  hooksDir: string;
7
7
  claudeDir: string;
8
+ geminiDir: string;
8
9
  settingsPath: string;
9
10
  backupPath: string;
10
11
  configPath: string;
12
+ geminiSettingsPath: string;
13
+ geminiBackupPath: string;
11
14
  };
12
15
  export declare function clearGuardUpdateCheck(): boolean;
13
16
  export declare function runGlobalRestore(): void;
@@ -71,7 +71,8 @@ function protectedTargets() {
71
71
  join(p.hooksDir, "stop.mjs"),
72
72
  join(p.hooksDir, "shield.mjs"),
73
73
  p.configPath,
74
- p.settingsPath
74
+ p.settingsPath,
75
+ p.geminiSettingsPath
75
76
  ];
76
77
  }
77
78
  function lockProtected() {
@@ -85,14 +86,21 @@ function globalPaths() {
85
86
  const sgDir = join(home, ".solongate");
86
87
  const hooksDir = join(sgDir, "hooks");
87
88
  const claudeDir = join(home, ".claude");
89
+ const geminiDir = join(home, ".gemini");
88
90
  return {
89
91
  home,
90
92
  sgDir,
91
93
  hooksDir,
92
94
  claudeDir,
95
+ geminiDir,
93
96
  settingsPath: join(claudeDir, "settings.json"),
94
97
  backupPath: join(claudeDir, "settings.solongate.bak"),
95
- configPath: join(sgDir, "cloud-guard.json")
98
+ configPath: join(sgDir, "cloud-guard.json"),
99
+ // Gemini CLI reads hooks from ~/.gemini/settings.json (user-level). Only the
100
+ // guard is registered there (BeforeTool); Gemini's ALLOW-path audit is
101
+ // covered by the passive session-log collector (~/.gemini/tmp), not a hook.
102
+ geminiSettingsPath: join(geminiDir, "settings.json"),
103
+ geminiBackupPath: join(geminiDir, "settings.solongate.bak")
96
104
  };
97
105
  }
98
106
  function clearGuardUpdateCheck() {
@@ -110,6 +118,50 @@ function readGuard() {
110
118
  const bundled = join(HOOKS_DIR, "guard.bundled.mjs");
111
119
  return existsSync(bundled) ? readFileSync(bundled, "utf-8") : readHook("guard.mjs");
112
120
  }
121
+ function geminiHookCommand(guardAbs) {
122
+ const nodeBin = process.execPath.replace(/\\/g, "/");
123
+ const call = process.platform === "win32" ? "& " : "";
124
+ return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" gemini-cli "Gemini CLI"`;
125
+ }
126
+ function installGeminiGuard(p, guardAbs) {
127
+ mkdirSync(p.geminiDir, { recursive: true });
128
+ let existing = {};
129
+ if (existsSync(p.geminiSettingsPath)) {
130
+ const raw = readFileSync(p.geminiSettingsPath, "utf-8");
131
+ if (!existsSync(p.geminiBackupPath)) writeFileSync(p.geminiBackupPath, raw);
132
+ try {
133
+ existing = JSON.parse(raw);
134
+ } catch {
135
+ existing = {};
136
+ }
137
+ }
138
+ const existingHooks = existing.hooks && typeof existing.hooks === "object" ? existing.hooks : {};
139
+ const merged = {
140
+ ...existing,
141
+ hooks: {
142
+ ...existingHooks,
143
+ BeforeTool: [{ matcher: "", hooks: [{ type: "command", command: geminiHookCommand(guardAbs) }] }]
144
+ }
145
+ };
146
+ writeFileSync(p.geminiSettingsPath, JSON.stringify(merged, null, 2) + "\n");
147
+ }
148
+ function removeGeminiGuard(p) {
149
+ if (!existsSync(p.geminiSettingsPath)) return;
150
+ try {
151
+ const s = JSON.parse(readFileSync(p.geminiSettingsPath, "utf-8"));
152
+ const hooks = s.hooks && typeof s.hooks === "object" ? s.hooks : null;
153
+ if (!hooks || !Array.isArray(hooks.BeforeTool)) return;
154
+ const kept = hooks.BeforeTool.filter((entry) => {
155
+ const inner = Array.isArray(entry?.hooks) ? entry.hooks : [];
156
+ return !inner.some((h) => typeof h?.command === "string" && h.command.includes(".solongate"));
157
+ });
158
+ if (kept.length) hooks.BeforeTool = kept;
159
+ else delete hooks.BeforeTool;
160
+ if (Object.keys(hooks).length === 0) delete s.hooks;
161
+ writeFileSync(p.geminiSettingsPath, JSON.stringify(s, null, 2) + "\n");
162
+ } catch {
163
+ }
164
+ }
113
165
  function ask(question) {
114
166
  const rl = createInterface({ input: process.stdin, output: process.stderr });
115
167
  return new Promise((res) => rl.question(question, (a) => {
@@ -121,6 +173,7 @@ function runGlobalRestore() {
121
173
  const p = globalPaths();
122
174
  unlockProtected();
123
175
  removeClaudeShim();
176
+ removeGeminiGuard(p);
124
177
  if (existsSync(p.backupPath)) {
125
178
  writeFileSync(p.settingsPath, readFileSync(p.backupPath, "utf-8"));
126
179
  console.log(` Restored ${p.settingsPath} from backup.`);
@@ -179,6 +232,10 @@ function installGlobalQuiet() {
179
232
  }
180
233
  };
181
234
  writeFileSync(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
235
+ try {
236
+ installGeminiGuard(p, join(p.hooksDir, "guard.mjs").replace(/\\/g, "/"));
237
+ } catch {
238
+ }
182
239
  if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
183
240
  return { ok: true, message: "guard installed (open a new session)" };
184
241
  } catch (e) {
@@ -218,6 +275,10 @@ function uninstallGlobalQuiet() {
218
275
  const p = globalPaths();
219
276
  unlockProtected();
220
277
  removeClaudeShim();
278
+ try {
279
+ removeGeminiGuard(p);
280
+ } catch {
281
+ }
221
282
  if (!existsSync(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
222
283
  const s = JSON.parse(readFileSync(p.settingsPath, "utf-8"));
223
284
  delete s.hooks;
@@ -356,6 +417,11 @@ async function runGlobalInstall(opts = {}) {
356
417
  };
357
418
  writeFileSync(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
358
419
  console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
420
+ try {
421
+ installGeminiGuard(p, guardAbs);
422
+ console.log(` Registered Gemini CLI guard \u2192 ${p.geminiSettingsPath}`);
423
+ } catch {
424
+ }
359
425
  if (process.env["SOLONGATE_OS_LOCK"] === "1") {
360
426
  lockProtected();
361
427
  console.log(" Locked protection files (OS-level read-only/immutable).");
package/dist/index.js CHANGED
@@ -10210,7 +10210,8 @@ function protectedTargets() {
10210
10210
  join11(p.hooksDir, "stop.mjs"),
10211
10211
  join11(p.hooksDir, "shield.mjs"),
10212
10212
  p.configPath,
10213
- p.settingsPath
10213
+ p.settingsPath,
10214
+ p.geminiSettingsPath
10214
10215
  ];
10215
10216
  }
10216
10217
  function lockProtected() {
@@ -10224,14 +10225,21 @@ function globalPaths() {
10224
10225
  const sgDir = join11(home, ".solongate");
10225
10226
  const hooksDir = join11(sgDir, "hooks");
10226
10227
  const claudeDir = join11(home, ".claude");
10228
+ const geminiDir = join11(home, ".gemini");
10227
10229
  return {
10228
10230
  home,
10229
10231
  sgDir,
10230
10232
  hooksDir,
10231
10233
  claudeDir,
10234
+ geminiDir,
10232
10235
  settingsPath: join11(claudeDir, "settings.json"),
10233
10236
  backupPath: join11(claudeDir, "settings.solongate.bak"),
10234
- configPath: join11(sgDir, "cloud-guard.json")
10237
+ configPath: join11(sgDir, "cloud-guard.json"),
10238
+ // Gemini CLI reads hooks from ~/.gemini/settings.json (user-level). Only the
10239
+ // guard is registered there (BeforeTool); Gemini's ALLOW-path audit is
10240
+ // covered by the passive session-log collector (~/.gemini/tmp), not a hook.
10241
+ geminiSettingsPath: join11(geminiDir, "settings.json"),
10242
+ geminiBackupPath: join11(geminiDir, "settings.solongate.bak")
10235
10243
  };
10236
10244
  }
10237
10245
  function readHook(filename) {
@@ -10241,6 +10249,50 @@ function readGuard() {
10241
10249
  const bundled = join11(HOOKS_DIR, "guard.bundled.mjs");
10242
10250
  return existsSync4(bundled) ? readFileSync9(bundled, "utf-8") : readHook("guard.mjs");
10243
10251
  }
10252
+ function geminiHookCommand(guardAbs) {
10253
+ const nodeBin = process.execPath.replace(/\\/g, "/");
10254
+ const call = process.platform === "win32" ? "& " : "";
10255
+ return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" gemini-cli "Gemini CLI"`;
10256
+ }
10257
+ function installGeminiGuard(p, guardAbs) {
10258
+ mkdirSync8(p.geminiDir, { recursive: true });
10259
+ let existing = {};
10260
+ if (existsSync4(p.geminiSettingsPath)) {
10261
+ const raw = readFileSync9(p.geminiSettingsPath, "utf-8");
10262
+ if (!existsSync4(p.geminiBackupPath)) writeFileSync9(p.geminiBackupPath, raw);
10263
+ try {
10264
+ existing = JSON.parse(raw);
10265
+ } catch {
10266
+ existing = {};
10267
+ }
10268
+ }
10269
+ const existingHooks = existing.hooks && typeof existing.hooks === "object" ? existing.hooks : {};
10270
+ const merged = {
10271
+ ...existing,
10272
+ hooks: {
10273
+ ...existingHooks,
10274
+ BeforeTool: [{ matcher: "", hooks: [{ type: "command", command: geminiHookCommand(guardAbs) }] }]
10275
+ }
10276
+ };
10277
+ writeFileSync9(p.geminiSettingsPath, JSON.stringify(merged, null, 2) + "\n");
10278
+ }
10279
+ function removeGeminiGuard(p) {
10280
+ if (!existsSync4(p.geminiSettingsPath)) return;
10281
+ try {
10282
+ const s = JSON.parse(readFileSync9(p.geminiSettingsPath, "utf-8"));
10283
+ const hooks = s.hooks && typeof s.hooks === "object" ? s.hooks : null;
10284
+ if (!hooks || !Array.isArray(hooks.BeforeTool)) return;
10285
+ const kept = hooks.BeforeTool.filter((entry) => {
10286
+ const inner = Array.isArray(entry?.hooks) ? entry.hooks : [];
10287
+ return !inner.some((h) => typeof h?.command === "string" && h.command.includes(".solongate"));
10288
+ });
10289
+ if (kept.length) hooks.BeforeTool = kept;
10290
+ else delete hooks.BeforeTool;
10291
+ if (Object.keys(hooks).length === 0) delete s.hooks;
10292
+ writeFileSync9(p.geminiSettingsPath, JSON.stringify(s, null, 2) + "\n");
10293
+ } catch {
10294
+ }
10295
+ }
10244
10296
  function installGlobalQuiet() {
10245
10297
  try {
10246
10298
  const p = globalPaths();
@@ -10283,6 +10335,10 @@ function installGlobalQuiet() {
10283
10335
  }
10284
10336
  };
10285
10337
  writeFileSync9(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
10338
+ try {
10339
+ installGeminiGuard(p, join11(p.hooksDir, "guard.mjs").replace(/\\/g, "/"));
10340
+ } catch {
10341
+ }
10286
10342
  if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
10287
10343
  return { ok: true, message: "guard installed (open a new session)" };
10288
10344
  } catch (e) {
@@ -10322,6 +10378,10 @@ function uninstallGlobalQuiet() {
10322
10378
  const p = globalPaths();
10323
10379
  unlockProtected();
10324
10380
  removeClaudeShim();
10381
+ try {
10382
+ removeGeminiGuard(p);
10383
+ } catch {
10384
+ }
10325
10385
  if (!existsSync4(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
10326
10386
  const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
10327
10387
  delete s.hooks;
package/dist/login.js CHANGED
@@ -98,7 +98,8 @@ function protectedTargets() {
98
98
  join(p.hooksDir, "stop.mjs"),
99
99
  join(p.hooksDir, "shield.mjs"),
100
100
  p.configPath,
101
- p.settingsPath
101
+ p.settingsPath,
102
+ p.geminiSettingsPath
102
103
  ];
103
104
  }
104
105
  function lockProtected() {
@@ -112,14 +113,21 @@ function globalPaths() {
112
113
  const sgDir = join(home, ".solongate");
113
114
  const hooksDir = join(sgDir, "hooks");
114
115
  const claudeDir = join(home, ".claude");
116
+ const geminiDir = join(home, ".gemini");
115
117
  return {
116
118
  home,
117
119
  sgDir,
118
120
  hooksDir,
119
121
  claudeDir,
122
+ geminiDir,
120
123
  settingsPath: join(claudeDir, "settings.json"),
121
124
  backupPath: join(claudeDir, "settings.solongate.bak"),
122
- configPath: join(sgDir, "cloud-guard.json")
125
+ configPath: join(sgDir, "cloud-guard.json"),
126
+ // Gemini CLI reads hooks from ~/.gemini/settings.json (user-level). Only the
127
+ // guard is registered there (BeforeTool); Gemini's ALLOW-path audit is
128
+ // covered by the passive session-log collector (~/.gemini/tmp), not a hook.
129
+ geminiSettingsPath: join(geminiDir, "settings.json"),
130
+ geminiBackupPath: join(geminiDir, "settings.solongate.bak")
123
131
  };
124
132
  }
125
133
  function readHook(filename) {
@@ -129,6 +137,33 @@ function readGuard() {
129
137
  const bundled = join(HOOKS_DIR, "guard.bundled.mjs");
130
138
  return existsSync(bundled) ? readFileSync(bundled, "utf-8") : readHook("guard.mjs");
131
139
  }
140
+ function geminiHookCommand(guardAbs) {
141
+ const nodeBin = process.execPath.replace(/\\/g, "/");
142
+ const call = process.platform === "win32" ? "& " : "";
143
+ return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" gemini-cli "Gemini CLI"`;
144
+ }
145
+ function installGeminiGuard(p, guardAbs) {
146
+ mkdirSync(p.geminiDir, { recursive: true });
147
+ let existing = {};
148
+ if (existsSync(p.geminiSettingsPath)) {
149
+ const raw = readFileSync(p.geminiSettingsPath, "utf-8");
150
+ if (!existsSync(p.geminiBackupPath)) writeFileSync(p.geminiBackupPath, raw);
151
+ try {
152
+ existing = JSON.parse(raw);
153
+ } catch {
154
+ existing = {};
155
+ }
156
+ }
157
+ const existingHooks = existing.hooks && typeof existing.hooks === "object" ? existing.hooks : {};
158
+ const merged = {
159
+ ...existing,
160
+ hooks: {
161
+ ...existingHooks,
162
+ BeforeTool: [{ matcher: "", hooks: [{ type: "command", command: geminiHookCommand(guardAbs) }] }]
163
+ }
164
+ };
165
+ writeFileSync(p.geminiSettingsPath, JSON.stringify(merged, null, 2) + "\n");
166
+ }
132
167
  function ask(question) {
133
168
  const rl = createInterface({ input: process.stdin, output: process.stderr });
134
169
  return new Promise((res) => rl.question(question, (a) => {
@@ -257,6 +292,11 @@ async function runGlobalInstall(opts = {}) {
257
292
  };
258
293
  writeFileSync(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
259
294
  console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
295
+ try {
296
+ installGeminiGuard(p, guardAbs);
297
+ console.log(` Registered Gemini CLI guard \u2192 ${p.geminiSettingsPath}`);
298
+ } catch {
299
+ }
260
300
  if (process.env["SOLONGATE_OS_LOCK"] === "1") {
261
301
  lockProtected();
262
302
  console.log(" Locked protection files (OS-level read-only/immutable).");
package/dist/tui/index.js CHANGED
@@ -3268,7 +3268,8 @@ function protectedTargets() {
3268
3268
  join6(p.hooksDir, "stop.mjs"),
3269
3269
  join6(p.hooksDir, "shield.mjs"),
3270
3270
  p.configPath,
3271
- p.settingsPath
3271
+ p.settingsPath,
3272
+ p.geminiSettingsPath
3272
3273
  ];
3273
3274
  }
3274
3275
  function lockProtected() {
@@ -3282,14 +3283,21 @@ function globalPaths() {
3282
3283
  const sgDir = join6(home, ".solongate");
3283
3284
  const hooksDir = join6(sgDir, "hooks");
3284
3285
  const claudeDir = join6(home, ".claude");
3286
+ const geminiDir = join6(home, ".gemini");
3285
3287
  return {
3286
3288
  home,
3287
3289
  sgDir,
3288
3290
  hooksDir,
3289
3291
  claudeDir,
3292
+ geminiDir,
3290
3293
  settingsPath: join6(claudeDir, "settings.json"),
3291
3294
  backupPath: join6(claudeDir, "settings.solongate.bak"),
3292
- configPath: join6(sgDir, "cloud-guard.json")
3295
+ configPath: join6(sgDir, "cloud-guard.json"),
3296
+ // Gemini CLI reads hooks from ~/.gemini/settings.json (user-level). Only the
3297
+ // guard is registered there (BeforeTool); Gemini's ALLOW-path audit is
3298
+ // covered by the passive session-log collector (~/.gemini/tmp), not a hook.
3299
+ geminiSettingsPath: join6(geminiDir, "settings.json"),
3300
+ geminiBackupPath: join6(geminiDir, "settings.solongate.bak")
3293
3301
  };
3294
3302
  }
3295
3303
  function readHook(filename) {
@@ -3299,6 +3307,50 @@ function readGuard() {
3299
3307
  const bundled = join6(HOOKS_DIR, "guard.bundled.mjs");
3300
3308
  return existsSync2(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
3301
3309
  }
3310
+ function geminiHookCommand(guardAbs) {
3311
+ const nodeBin = process.execPath.replace(/\\/g, "/");
3312
+ const call = process.platform === "win32" ? "& " : "";
3313
+ return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" gemini-cli "Gemini CLI"`;
3314
+ }
3315
+ function installGeminiGuard(p, guardAbs) {
3316
+ mkdirSync4(p.geminiDir, { recursive: true });
3317
+ let existing = {};
3318
+ if (existsSync2(p.geminiSettingsPath)) {
3319
+ const raw = readFileSync4(p.geminiSettingsPath, "utf-8");
3320
+ if (!existsSync2(p.geminiBackupPath)) writeFileSync5(p.geminiBackupPath, raw);
3321
+ try {
3322
+ existing = JSON.parse(raw);
3323
+ } catch {
3324
+ existing = {};
3325
+ }
3326
+ }
3327
+ const existingHooks = existing.hooks && typeof existing.hooks === "object" ? existing.hooks : {};
3328
+ const merged = {
3329
+ ...existing,
3330
+ hooks: {
3331
+ ...existingHooks,
3332
+ BeforeTool: [{ matcher: "", hooks: [{ type: "command", command: geminiHookCommand(guardAbs) }] }]
3333
+ }
3334
+ };
3335
+ writeFileSync5(p.geminiSettingsPath, JSON.stringify(merged, null, 2) + "\n");
3336
+ }
3337
+ function removeGeminiGuard(p) {
3338
+ if (!existsSync2(p.geminiSettingsPath)) return;
3339
+ try {
3340
+ const s = JSON.parse(readFileSync4(p.geminiSettingsPath, "utf-8"));
3341
+ const hooks = s.hooks && typeof s.hooks === "object" ? s.hooks : null;
3342
+ if (!hooks || !Array.isArray(hooks.BeforeTool)) return;
3343
+ const kept = hooks.BeforeTool.filter((entry) => {
3344
+ const inner = Array.isArray(entry?.hooks) ? entry.hooks : [];
3345
+ return !inner.some((h) => typeof h?.command === "string" && h.command.includes(".solongate"));
3346
+ });
3347
+ if (kept.length) hooks.BeforeTool = kept;
3348
+ else delete hooks.BeforeTool;
3349
+ if (Object.keys(hooks).length === 0) delete s.hooks;
3350
+ writeFileSync5(p.geminiSettingsPath, JSON.stringify(s, null, 2) + "\n");
3351
+ } catch {
3352
+ }
3353
+ }
3302
3354
  function installGlobalQuiet() {
3303
3355
  try {
3304
3356
  const p = globalPaths();
@@ -3341,6 +3393,10 @@ function installGlobalQuiet() {
3341
3393
  }
3342
3394
  };
3343
3395
  writeFileSync5(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
3396
+ try {
3397
+ installGeminiGuard(p, join6(p.hooksDir, "guard.mjs").replace(/\\/g, "/"));
3398
+ } catch {
3399
+ }
3344
3400
  if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
3345
3401
  return { ok: true, message: "guard installed (open a new session)" };
3346
3402
  } catch (e) {
@@ -3380,6 +3436,10 @@ function uninstallGlobalQuiet() {
3380
3436
  const p = globalPaths();
3381
3437
  unlockProtected();
3382
3438
  removeClaudeShim();
3439
+ try {
3440
+ removeGeminiGuard(p);
3441
+ } catch {
3442
+ }
3383
3443
  if (!existsSync2(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
3384
3444
  const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
3385
3445
  delete s.hooks;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.82.10",
3
+ "version": "0.82.11",
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": {