@solongate/proxy 0.81.96 → 0.81.98

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/README.md CHANGED
@@ -1,72 +1,74 @@
1
- # @solongate/proxy
1
+ <div align="center">
2
+ <img src="https://solongate.com/hero.png" alt="SolonGate. For AI agents to execute tools safely. Make agents secure again." width="900" />
3
+ </div>
2
4
 
3
- > **Don't `npm i` this.** There's nothing to import. Run the two commands below to pair your machine:
4
- > ```bash
5
- > npm i -g @solongate/proxy
6
- > solongate
7
- > ```
5
+ <a href="https://github.com/Solongate"><img alt="GitHub" src="https://img.shields.io/badge/GitHub-111111?style=flat-square&logo=github&logoColor=white" height="24" /></a>
6
+ <a href="https://www.linkedin.com/company/solongate/"><img alt="LinkedIn" src="https://img.shields.io/badge/LinkedIn-111111?style=flat-square&logo=linkedin&logoColor=white" height="24" /></a>
8
7
 
9
- **Security for AI agents.** SolonGate checks every action an AI agent takes. Each tool call (shell command, file read or write, network request) is allowed, blocked, or logged before it runs, based on a policy you control. No code changes.
8
+ <p align="center">
9
+ <a href="https://nodejs.org"><img alt="Node.js" src="https://img.shields.io/node/v/@solongate/proxy?style=flat-square&label=Node.js&labelColor=111111&color=eeeeee&logo=nodedotjs&logoColor=white" /></a>
10
+ <a href="https://www.npmjs.com/package/@solongate/proxy"><img alt="npm" src="https://img.shields.io/npm/v/@solongate/proxy?style=flat-square&color=eeeeee&labelColor=111111&logo=npm&logoColor=white" /></a>
11
+ </p>
10
12
 
11
- ```
12
- AI agent ──(tool call)──> SolonGate guard ──> Tool runs
13
-
14
- [ policy check ]
15
- [ allow / block / log ]
16
- ```
13
+ SolonGate is the guardrail for AI agents. It checks every action an agent takes (each shell command, file read or write, and network request) and allows, blocks, or logs it before it runs, based on a policy you control. No code changes.
17
14
 
18
- SolonGate is fully managed. There is nothing to host: pair your machine with one command, then manage policies and review logs at [dashboard.solongate.com](https://dashboard.solongate.com).
15
+ **[solongate.com](https://solongate.com)** | [Documentation](https://solongate.com/docs) | [Dashboard](https://dashboard.solongate.com)
19
16
 
20
- ## Quick start
17
+ <div align="center">
18
+ <img src="https://solongate.com/flow.gif" alt="A tool call from an AI agent passes through SolonGate, which runs a policy check, DLP scan, and rate limit, then allows, blocks, or logs it before the tool runs" width="900" />
19
+ </div>
21
20
 
22
- **You need:** a free [SolonGate account](https://auth.solongate.com), Node.js 18+ on the machine you want to protect, and an AI tool that makes tool calls (Claude Code; Gemini CLI is also supported).
21
+ ## Get started
23
22
 
24
- Pair the machine and turn on the guard. There are no API keys to copy. First fetch SolonGate, then log in:
23
+ > **Don't `npm i` this.** There's nothing to import. Run the two commands below to pair your machine.
25
24
 
26
- ```bash
25
+ You need a free [SolonGate account](https://auth.solongate.com) and Node.js 20+ on the machine you want to protect.
26
+
27
+ ```sh
27
28
  npm i -g @solongate/proxy
28
29
  solongate
29
30
  ```
30
31
 
31
- This opens your browser to authorize the device. Approve it from your signed-in dashboard and SolonGate installs a global guard hook that intercepts every tool call from every AI session on the machine and checks it against your active policy.
32
+ `solongate` opens your browser to authorize the device. Approve it and SolonGate installs a global guard hook that checks every tool call from every AI session on the machine against your active policy. No API keys to copy.
32
33
 
33
34
  > Start a **new** terminal session afterwards. Hooks load when a session starts, so already-open terminals aren't guarded yet.
34
35
 
35
- ## Write a policy
36
+ ## Guarding a live Claude Code session
37
+
38
+ Drops in front of any MCP-speaking agent over stdio, SSE, or HTTP. Every tool call is checked in real time, and each allow or deny decision streams to your dashboard with the full arguments attached.
36
39
 
37
- Open **Policies** in the dashboard and create one. A policy is a set of rules. Each rule targets a tool plus a constraint (path, command, filename, or URL) and is either ALLOW or DENY. Two modes:
40
+ <img src="https://solongate.com/claudecode.gif" alt="SolonGate guarding a live Claude Code session" width="720" />
38
41
 
39
- - **Denylist:** everything is allowed except what you block (for example, deny any file named `*.env`, or commands matching `*rm -rf*`).
40
- - **Whitelist:** nothing is allowed except what you permit.
42
+ ## Command center for every decision area
41
43
 
42
- Click **Activate** to make a policy the one the guard enforces. It takes effect on the next tool call.
44
+ Live analytics by tool, agent, and policy. Version your policies with cloud sync and one-click rollback. Every decision lands in a tamper-evident, searchable audit trail you can export as CSV.
45
+
46
+ <img src="https://solongate.com/solongatedashboard.gif" alt="SolonGate dashboard: analytics, policies, and audit trail" width="720" />
43
47
 
44
48
  ## What SolonGate can enforce
45
49
 
46
50
  - **Policy rules:** allow or block tool calls by path, command, filename, or URL.
47
- - **Ghost paths:** make chosen files and folders invisible to the agent. They don't show up in listings and can't be read.
48
- - **Data loss prevention (DLP):** if a call contains a secret (API key, token, private key), block it, or hide the secret in the output the model sees.
51
+ - **Ghost paths:** make chosen files and folders invisible to the agent, unlistable and unreadable.
52
+ - **Data loss prevention (DLP):** when a call carries a secret (API key, token, private key), block it or hide it from the model.
49
53
  - **Rate limiting:** cap how many tool calls an agent can make per minute, hour, or day.
50
54
 
51
- ## Review the audit log
52
-
53
- Every decision shows up under **Audit**: what was allowed, what was blocked, and which rule matched. Each agent run is also grouped as a **session** with its own timeline. When a legitimate action is blocked, open the entry and click **Whitelist this**: SolonGate adds a narrow ALLOW exception for exactly that action. Revoke any exception from **Granted exceptions**.
54
-
55
- ## Keep logs on your own machine
56
-
57
- Prefer to keep everything local? In **Settings**, turn on **Local log storage** and pick a folder. Every decision is written there as a JSON line, and nothing is sent to SolonGate cloud.
58
-
59
- ## Get alerted
60
-
61
- Set a threshold in **Settings** and SolonGate notifies you by Telegram, email, or webhook when blocks spike.
62
-
63
- ## Why SolonGate?
64
-
65
- AI agents get direct access to your system: shell, file system, databases, network. SolonGate sits between the agent and the action, enforcing your policy on every tool call before it runs, and recording every decision for audit.
66
-
67
- - **Docs:** [solongate.com/docs](https://solongate.com/docs)
68
- - **Dashboard:** [dashboard.solongate.com](https://dashboard.solongate.com)
69
-
70
- ## License
71
-
72
- MIT
55
+ Manage everything from the **Policies**, **Audit**, and **Settings** pages in the [dashboard](https://dashboard.solongate.com). When a legitimate action is blocked, click **Whitelist this** to add a narrow exception. Keep logs local instead of in the cloud, and get alerted by Telegram, email, or webhook when blocks spike.
56
+
57
+ ## Integrations
58
+
59
+ **Compatible with every tool.** SolonGate guards any MCP-speaking agent out of the box. Missing yours? [Reach out](https://solongate.com/contact) and we will consider it.
60
+
61
+ <table>
62
+ <tr>
63
+ <td align="center" width="140"><a href="https://claude.com/claude-code"><img src="https://solongate.com/platforms/claude.png" width="44" height="44" alt="Claude Code" /><br/>Claude Code</a></td>
64
+ <td align="center" width="140"><a href="https://github.com/google-gemini/gemini-cli"><img src="https://solongate.com/platforms/gemini.png" width="44" height="44" alt="Gemini CLI" /><br/>Gemini CLI</a></td>
65
+ <td align="center" width="140"><a href="https://openclaw.ai"><img src="https://solongate.com/platforms/openclaw.png" width="44" height="44" alt="OpenClaw" /><br/>OpenClaw<br/><sub>soon</sub></a></td>
66
+ <td align="center" width="140"><a href="https://openai.com/codex"><img src="https://solongate.com/platforms/codex.png" width="44" height="44" alt="Codex" /><br/>Codex<br/><sub>soon</sub></a></td>
67
+ </tr>
68
+ <tr>
69
+ <td align="center" width="140"><a href="https://hermes.nousresearch.com"><img src="https://solongate.com/platforms/hermes.png" width="44" height="44" alt="Hermes" /><br/>Hermes<br/><sub>soon</sub></a></td>
70
+ <td align="center" width="140"><a href="https://opencode.ai"><img src="https://solongate.com/platforms/opencode.png" width="44" height="44" alt="OpenCode" /><br/>OpenCode<br/><sub>soon</sub></a></td>
71
+ <td align="center" width="140"><a href="https://kilocode.ai"><img src="https://solongate.com/platforms/kilo.png" width="44" height="44" alt="Kilo" /><br/>Kilo<br/><sub>soon</sub></a></td>
72
+ <td align="center" width="140"><a href="https://solongate.com/contact"><b>+</b><br/>Your tool</a></td>
73
+ </tr>
74
+ </table>
package/dist/index.js CHANGED
@@ -9111,9 +9111,8 @@ function RateLimitPanel({ focused }) {
9111
9111
  const insightsQ = useLoader(() => api.stats.securityInsights(7));
9112
9112
  const [draft, setDraft] = useState4(null);
9113
9113
  const [dirty, setDirty] = useState4(false);
9114
- const [fi, setFi] = useState4(0);
9114
+ const [sel, setSel] = useState4(0);
9115
9115
  const [status, setStatus] = useState4(null);
9116
- const [burstOff, setBurstOff] = useState4(0);
9117
9116
  useEffect4(() => {
9118
9117
  if (layersQ.data && !dirty) setDraft({ ...layersQ.data.layers.rateLimit });
9119
9118
  }, [layersQ.data]);
@@ -9125,7 +9124,7 @@ function RateLimitPanel({ focused }) {
9125
9124
  insightsQ.reloadQuiet();
9126
9125
  }, 1e4);
9127
9126
  const adjust = (dir, step) => {
9128
- if (!draft) return;
9127
+ if (!draft || fi < 0) return;
9129
9128
  const field = FIELDS2[fi];
9130
9129
  if (field === "mode") setDraft({ ...draft, mode: MODES[(MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length] });
9131
9130
  else setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
@@ -9156,14 +9155,19 @@ function RateLimitPanel({ focused }) {
9156
9155
  const fixed = 1 + 4 + (showExtras ? (hasLoad ? 1 : 0) + 5 : 0) + 1 + 1 + 1 + 1;
9157
9156
  const burstBudget = Math.max(1, rows - fixed);
9158
9157
  const maxOff = Math.max(0, anomalies2.length - burstBudget);
9159
- const off = Math.min(burstOff, maxOff);
9158
+ const total = 4 + anomalies2.length;
9159
+ const selC = Math.min(sel, Math.max(0, total - 1));
9160
+ const onField = selC < 4;
9161
+ const fi = onField ? selC : -1;
9162
+ const bcur = onField ? -1 : selC - 4;
9163
+ const off = bcur < 0 ? 0 : Math.min(Math.max(0, bcur - Math.floor(burstBudget / 2)), maxOff);
9160
9164
  useInput3(
9161
9165
  (input, key) => {
9162
9166
  const step = key.shift ? 10 : 1;
9163
- if (key.pageDown) setBurstOff((o) => Math.min(o + burstBudget, maxOff));
9164
- else if (key.pageUp) setBurstOff((o) => Math.max(0, o - burstBudget));
9165
- else if (key.upArrow) setFi((n) => (n - 1 + FIELDS2.length) % FIELDS2.length);
9166
- else if (key.downArrow) setFi((n) => (n + 1) % FIELDS2.length);
9167
+ if (key.upArrow) setSel((n) => Math.max(0, n - 1));
9168
+ else if (key.downArrow) setSel((n) => Math.min(total - 1, n + 1));
9169
+ else if (key.pageDown) setSel((n) => Math.min(total - 1, n + burstBudget));
9170
+ else if (key.pageUp) setSel((n) => Math.max(0, n - burstBudget));
9167
9171
  else if (key.leftArrow) adjust(-1, step);
9168
9172
  else if (key.rightArrow) adjust(1, step);
9169
9173
  else if (input === "s") void save();
@@ -9184,7 +9188,7 @@ function RateLimitPanel({ focused }) {
9184
9188
  const shown = anomalies2.slice(off, off + burstBudget);
9185
9189
  const bw = Math.max(10, cols - 30);
9186
9190
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", height: rows, overflow: "hidden", children: [
9187
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save \xB7 ^R refresh${maxOff > 0 ? " \xB7 PgUp/PgDn bursts" : ""}` : "press \u2192 to edit" }),
9191
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 move (fields + bursts) \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save \xB7 ^R refresh" : "press \u2192 to edit" }),
9188
9192
  /* @__PURE__ */ jsxs4(FieldRow, { label: "Mode", active: focused && fi === 0, children: [
9189
9193
  /* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), bold: true, children: draft.mode }),
9190
9194
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: draft.mode === "off" ? " no limit" : draft.mode === "detect" ? " flag bursts, never block" : " DENY calls over the limit" })
@@ -9220,13 +9224,16 @@ function RateLimitPanel({ focused }) {
9220
9224
  ] }) : /* @__PURE__ */ jsx4(
9221
9225
  Table,
9222
9226
  {
9223
- columns: [{ header: "WHEN", width: 13 }, { header: "RESULT", width: 9 }, { header: "CALLS", width: 7 }, { header: "AGENT", width: Math.max(8, bw - 29) }],
9224
- rows: shown.map((a) => [
9225
- { value: when(new Date(a.minute).getTime()), dim: true },
9226
- { value: a.blocked ? "Blocked" : "Bypassed", color: a.blocked ? theme.bad : theme.warn },
9227
- { value: `${a.count}/${a.limit}`, color: theme.warn },
9228
- { value: truncate2(a.agent, Math.max(8, bw - 29)), dim: true }
9229
- ])
9227
+ columns: [{ header: "WHEN", width: 15 }, { header: "RESULT", width: 9 }, { header: "CALLS", width: 7 }, { header: "AGENT", width: Math.max(8, bw - 31) }],
9228
+ rows: shown.map((a, i) => {
9229
+ const isSel = off + i === bcur;
9230
+ return [
9231
+ { value: (isSel ? "\u25B8 " : " ") + when(new Date(a.minute).getTime()), color: isSel ? theme.accentBright : void 0, dim: !isSel },
9232
+ { value: a.blocked ? "Blocked" : "Bypassed", color: a.blocked ? theme.bad : theme.warn },
9233
+ { value: `${a.count}/${a.limit}`, color: theme.warn },
9234
+ { value: truncate2(a.agent, Math.max(8, bw - 31)), dim: !isSel }
9235
+ ];
9236
+ })
9230
9237
  }
9231
9238
  )
9232
9239
  ] }),
package/dist/tui/index.js CHANGED
@@ -2199,9 +2199,8 @@ function RateLimitPanel({ focused }) {
2199
2199
  const insightsQ = useLoader(() => api.stats.securityInsights(7));
2200
2200
  const [draft, setDraft] = useState4(null);
2201
2201
  const [dirty, setDirty] = useState4(false);
2202
- const [fi, setFi] = useState4(0);
2202
+ const [sel, setSel] = useState4(0);
2203
2203
  const [status, setStatus] = useState4(null);
2204
- const [burstOff, setBurstOff] = useState4(0);
2205
2204
  useEffect4(() => {
2206
2205
  if (layersQ.data && !dirty) setDraft({ ...layersQ.data.layers.rateLimit });
2207
2206
  }, [layersQ.data]);
@@ -2213,7 +2212,7 @@ function RateLimitPanel({ focused }) {
2213
2212
  insightsQ.reloadQuiet();
2214
2213
  }, 1e4);
2215
2214
  const adjust = (dir, step) => {
2216
- if (!draft) return;
2215
+ if (!draft || fi < 0) return;
2217
2216
  const field = FIELDS2[fi];
2218
2217
  if (field === "mode") setDraft({ ...draft, mode: MODES[(MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length] });
2219
2218
  else setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
@@ -2244,14 +2243,19 @@ function RateLimitPanel({ focused }) {
2244
2243
  const fixed = 1 + 4 + (showExtras ? (hasLoad ? 1 : 0) + 5 : 0) + 1 + 1 + 1 + 1;
2245
2244
  const burstBudget = Math.max(1, rows - fixed);
2246
2245
  const maxOff = Math.max(0, anomalies2.length - burstBudget);
2247
- const off = Math.min(burstOff, maxOff);
2246
+ const total = 4 + anomalies2.length;
2247
+ const selC = Math.min(sel, Math.max(0, total - 1));
2248
+ const onField = selC < 4;
2249
+ const fi = onField ? selC : -1;
2250
+ const bcur = onField ? -1 : selC - 4;
2251
+ const off = bcur < 0 ? 0 : Math.min(Math.max(0, bcur - Math.floor(burstBudget / 2)), maxOff);
2248
2252
  useInput3(
2249
2253
  (input, key) => {
2250
2254
  const step = key.shift ? 10 : 1;
2251
- if (key.pageDown) setBurstOff((o) => Math.min(o + burstBudget, maxOff));
2252
- else if (key.pageUp) setBurstOff((o) => Math.max(0, o - burstBudget));
2253
- else if (key.upArrow) setFi((n) => (n - 1 + FIELDS2.length) % FIELDS2.length);
2254
- else if (key.downArrow) setFi((n) => (n + 1) % FIELDS2.length);
2255
+ if (key.upArrow) setSel((n) => Math.max(0, n - 1));
2256
+ else if (key.downArrow) setSel((n) => Math.min(total - 1, n + 1));
2257
+ else if (key.pageDown) setSel((n) => Math.min(total - 1, n + burstBudget));
2258
+ else if (key.pageUp) setSel((n) => Math.max(0, n - burstBudget));
2255
2259
  else if (key.leftArrow) adjust(-1, step);
2256
2260
  else if (key.rightArrow) adjust(1, step);
2257
2261
  else if (input === "s") void save();
@@ -2272,7 +2276,7 @@ function RateLimitPanel({ focused }) {
2272
2276
  const shown = anomalies2.slice(off, off + burstBudget);
2273
2277
  const bw = Math.max(10, cols - 30);
2274
2278
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", height: rows, overflow: "hidden", children: [
2275
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save \xB7 ^R refresh${maxOff > 0 ? " \xB7 PgUp/PgDn bursts" : ""}` : "press \u2192 to edit" }),
2279
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 move (fields + bursts) \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save \xB7 ^R refresh" : "press \u2192 to edit" }),
2276
2280
  /* @__PURE__ */ jsxs4(FieldRow, { label: "Mode", active: focused && fi === 0, children: [
2277
2281
  /* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), bold: true, children: draft.mode }),
2278
2282
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: draft.mode === "off" ? " no limit" : draft.mode === "detect" ? " flag bursts, never block" : " DENY calls over the limit" })
@@ -2308,13 +2312,16 @@ function RateLimitPanel({ focused }) {
2308
2312
  ] }) : /* @__PURE__ */ jsx4(
2309
2313
  Table,
2310
2314
  {
2311
- columns: [{ header: "WHEN", width: 13 }, { header: "RESULT", width: 9 }, { header: "CALLS", width: 7 }, { header: "AGENT", width: Math.max(8, bw - 29) }],
2312
- rows: shown.map((a) => [
2313
- { value: when(new Date(a.minute).getTime()), dim: true },
2314
- { value: a.blocked ? "Blocked" : "Bypassed", color: a.blocked ? theme.bad : theme.warn },
2315
- { value: `${a.count}/${a.limit}`, color: theme.warn },
2316
- { value: truncate(a.agent, Math.max(8, bw - 29)), dim: true }
2317
- ])
2315
+ columns: [{ header: "WHEN", width: 15 }, { header: "RESULT", width: 9 }, { header: "CALLS", width: 7 }, { header: "AGENT", width: Math.max(8, bw - 31) }],
2316
+ rows: shown.map((a, i) => {
2317
+ const isSel = off + i === bcur;
2318
+ return [
2319
+ { value: (isSel ? "\u25B8 " : " ") + when(new Date(a.minute).getTime()), color: isSel ? theme.accentBright : void 0, dim: !isSel },
2320
+ { value: a.blocked ? "Blocked" : "Bypassed", color: a.blocked ? theme.bad : theme.warn },
2321
+ { value: `${a.count}/${a.limit}`, color: theme.warn },
2322
+ { value: truncate(a.agent, Math.max(8, bw - 31)), dim: !isSel }
2323
+ ];
2324
+ })
2318
2325
  }
2319
2326
  )
2320
2327
  ] }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.96",
3
+ "version": "0.81.98",
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": {
@@ -56,7 +56,7 @@
56
56
  "url": "https://github.com/solongate/solongate"
57
57
  },
58
58
  "engines": {
59
- "node": ">=18.0.0"
59
+ "node": ">=20.0.0"
60
60
  },
61
61
  "dependencies": {
62
62
  "@modelcontextprotocol/sdk": "^1.26.0",