@solongate/proxy 0.83.4 → 0.83.5
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/auth.d.ts +0 -8
- package/dist/api-client/client.d.ts +0 -14
- package/dist/commands/index.js +0 -33
- package/dist/index.js +37 -129
- package/dist/tui/index.js +19 -84
- package/hooks/guard.bundled.mjs +1 -44
- package/hooks/guard.mjs +1 -53
- package/package.json +1 -1
|
@@ -8,13 +8,5 @@ export interface Me {
|
|
|
8
8
|
name?: string;
|
|
9
9
|
slug?: string;
|
|
10
10
|
} | null;
|
|
11
|
-
/**
|
|
12
|
-
* Present only for a guest account, and carrying only whether the allowance is
|
|
13
|
-
* spent. The size of it and how much is left are the operator's numbers and
|
|
14
|
-
* are not sent here.
|
|
15
|
-
*/
|
|
16
|
-
guest?: {
|
|
17
|
-
exhausted?: boolean;
|
|
18
|
-
} | null;
|
|
19
11
|
}
|
|
20
12
|
export declare function me(): Promise<Me>;
|
|
@@ -18,16 +18,6 @@ export declare function saveAccount(acc: SavedAccount): void;
|
|
|
18
18
|
/** Remove a saved account from this device (does NOT revoke the cloud key). */
|
|
19
19
|
export declare function removeAccount(apiKey: string): void;
|
|
20
20
|
export declare function setViewCredentials(creds: Credentials | null): void;
|
|
21
|
-
/** The unroutable address a guest ("Skip for now") account is stored under. */
|
|
22
|
-
export declare const GUEST_EMAIL_SUFFIX = "@anonymous.solongate.invalid";
|
|
23
|
-
/**
|
|
24
|
-
* True when the account this device is enforcing with is a guest one.
|
|
25
|
-
*
|
|
26
|
-
* A guest has no dashboard rights beyond reading, and the CLI is the same
|
|
27
|
-
* account reached a different way, so the management commands answer the same
|
|
28
|
-
* way the dashboard does rather than half-working here.
|
|
29
|
-
*/
|
|
30
|
-
export declare function isGuestAccount(): boolean;
|
|
31
21
|
/** The key this device enforces with, or null when it is not paired. */
|
|
32
22
|
export declare function enforcingKey(): string | null;
|
|
33
23
|
/** True when the given key is the ACTIVE one the guard hooks use. */
|
|
@@ -56,10 +46,6 @@ export declare class ApiError extends Error {
|
|
|
56
46
|
readonly code: string;
|
|
57
47
|
constructor(status: number, code: string, message: string);
|
|
58
48
|
}
|
|
59
|
-
/** Raised when a guest account tries to change something. */
|
|
60
|
-
export declare class GuestReadOnlyError extends Error {
|
|
61
|
-
constructor();
|
|
62
|
-
}
|
|
63
49
|
/** Raised when no API key can be found — tells the user to run `solongate` and pair. */
|
|
64
50
|
export declare class NotAuthenticatedError extends Error {
|
|
65
51
|
constructor();
|
package/dist/commands/index.js
CHANGED
|
@@ -135,31 +135,7 @@ function installedGuardVersion() {
|
|
|
135
135
|
|
|
136
136
|
// src/api-client/client.ts
|
|
137
137
|
var DEFAULT_API_URL = "https://api.solongate.com";
|
|
138
|
-
var accountsFile = () => join2(homedir2(), ".solongate", "accounts.json");
|
|
139
|
-
function listAccounts() {
|
|
140
|
-
let list5 = [];
|
|
141
|
-
try {
|
|
142
|
-
const raw = JSON.parse(readFileSync2(accountsFile(), "utf-8"));
|
|
143
|
-
if (Array.isArray(raw)) list5 = raw.filter((a) => a && typeof a.apiKey === "string");
|
|
144
|
-
} catch {
|
|
145
|
-
}
|
|
146
|
-
const active2 = loginCredentialFile();
|
|
147
|
-
if (active2.apiKey && !list5.some((a) => a.apiKey === active2.apiKey)) {
|
|
148
|
-
list5.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL });
|
|
149
|
-
}
|
|
150
|
-
return list5;
|
|
151
|
-
}
|
|
152
138
|
var viewOverride = null;
|
|
153
|
-
var GUEST_EMAIL_SUFFIX = "@anonymous.solongate.invalid";
|
|
154
|
-
function isGuestAccount() {
|
|
155
|
-
try {
|
|
156
|
-
const active2 = loginCredentialFile().apiKey;
|
|
157
|
-
if (!active2) return false;
|
|
158
|
-
return listAccounts().some((a) => a.apiKey === active2 && (a.email ?? "").endsWith(GUEST_EMAIL_SUFFIX));
|
|
159
|
-
} catch {
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
139
|
var ApiError = class extends Error {
|
|
164
140
|
status;
|
|
165
141
|
code;
|
|
@@ -170,12 +146,6 @@ var ApiError = class extends Error {
|
|
|
170
146
|
this.code = code;
|
|
171
147
|
}
|
|
172
148
|
};
|
|
173
|
-
var GuestReadOnlyError = class extends Error {
|
|
174
|
-
constructor() {
|
|
175
|
-
super("Guest account: sign up at auth.solongate.com to change anything. Your project, its policy and everything recorded carry over.");
|
|
176
|
-
this.name = "GuestReadOnlyError";
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
149
|
var NotAuthenticatedError = class extends Error {
|
|
180
150
|
constructor() {
|
|
181
151
|
super("Not logged in. Run `solongate` and log in from the Accounts panel.");
|
|
@@ -240,9 +210,6 @@ function buildUrl(base, path, query) {
|
|
|
240
210
|
return url.toString();
|
|
241
211
|
}
|
|
242
212
|
async function request(method, path, opts = {}) {
|
|
243
|
-
if (method !== "GET" && isGuestAccount()) {
|
|
244
|
-
throw new GuestReadOnlyError();
|
|
245
|
-
}
|
|
246
213
|
const creds = resolveCredentials(opts.apiUrl);
|
|
247
214
|
const url = buildUrl(creds.apiUrl, path, opts.query);
|
|
248
215
|
const headers = {
|
package/dist/index.js
CHANGED
|
@@ -7716,26 +7716,6 @@ var init_local_log = __esm({
|
|
|
7716
7716
|
});
|
|
7717
7717
|
|
|
7718
7718
|
// src/api-client/client.ts
|
|
7719
|
-
var client_exports = {};
|
|
7720
|
-
__export(client_exports, {
|
|
7721
|
-
ApiError: () => ApiError,
|
|
7722
|
-
DEFAULT_API_URL: () => DEFAULT_API_URL2,
|
|
7723
|
-
GUEST_EMAIL_SUFFIX: () => GUEST_EMAIL_SUFFIX,
|
|
7724
|
-
GuestReadOnlyError: () => GuestReadOnlyError,
|
|
7725
|
-
NotAuthenticatedError: () => NotAuthenticatedError,
|
|
7726
|
-
clearActiveCredential: () => clearActiveCredential,
|
|
7727
|
-
enforcingKey: () => enforcingKey,
|
|
7728
|
-
isActiveAccount: () => isActiveAccount,
|
|
7729
|
-
isAuthenticated: () => isAuthenticated,
|
|
7730
|
-
isGuestAccount: () => isGuestAccount,
|
|
7731
|
-
listAccounts: () => listAccounts,
|
|
7732
|
-
removeAccount: () => removeAccount,
|
|
7733
|
-
request: () => request,
|
|
7734
|
-
resolveCredentials: () => resolveCredentials,
|
|
7735
|
-
saveAccount: () => saveAccount,
|
|
7736
|
-
setActiveAccount: () => setActiveAccount,
|
|
7737
|
-
setViewCredentials: () => setViewCredentials
|
|
7738
|
-
});
|
|
7739
7719
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync5 } from "fs";
|
|
7740
7720
|
import { resolve as resolve4, join as join9 } from "path";
|
|
7741
7721
|
import { homedir as homedir7 } from "os";
|
|
@@ -7787,15 +7767,6 @@ function setViewCredentials(creds) {
|
|
|
7787
7767
|
viewOverride = creds;
|
|
7788
7768
|
cached2 = null;
|
|
7789
7769
|
}
|
|
7790
|
-
function isGuestAccount() {
|
|
7791
|
-
try {
|
|
7792
|
-
const active2 = loginCredentialFile().apiKey;
|
|
7793
|
-
if (!active2) return false;
|
|
7794
|
-
return listAccounts().some((a) => a.apiKey === active2 && (a.email ?? "").endsWith(GUEST_EMAIL_SUFFIX));
|
|
7795
|
-
} catch {
|
|
7796
|
-
return false;
|
|
7797
|
-
}
|
|
7798
|
-
}
|
|
7799
7770
|
function enforcingKey() {
|
|
7800
7771
|
return loginCredentialFile().apiKey ?? null;
|
|
7801
7772
|
}
|
|
@@ -7898,9 +7869,6 @@ function buildUrl(base, path, query) {
|
|
|
7898
7869
|
return url.toString();
|
|
7899
7870
|
}
|
|
7900
7871
|
async function request(method, path, opts = {}) {
|
|
7901
|
-
if (method !== "GET" && isGuestAccount()) {
|
|
7902
|
-
throw new GuestReadOnlyError();
|
|
7903
|
-
}
|
|
7904
7872
|
const creds = resolveCredentials(opts.apiUrl);
|
|
7905
7873
|
const url = buildUrl(creds.apiUrl, path, opts.query);
|
|
7906
7874
|
const headers = {
|
|
@@ -7956,7 +7924,7 @@ async function request(method, path, opts = {}) {
|
|
|
7956
7924
|
}
|
|
7957
7925
|
return json;
|
|
7958
7926
|
}
|
|
7959
|
-
var DEFAULT_API_URL2, accountsFile, viewOverride,
|
|
7927
|
+
var DEFAULT_API_URL2, accountsFile, viewOverride, ApiError, NotAuthenticatedError, cached2;
|
|
7960
7928
|
var init_client = __esm({
|
|
7961
7929
|
"src/api-client/client.ts"() {
|
|
7962
7930
|
"use strict";
|
|
@@ -7964,7 +7932,6 @@ var init_client = __esm({
|
|
|
7964
7932
|
DEFAULT_API_URL2 = "https://api.solongate.com";
|
|
7965
7933
|
accountsFile = () => join9(homedir7(), ".solongate", "accounts.json");
|
|
7966
7934
|
viewOverride = null;
|
|
7967
|
-
GUEST_EMAIL_SUFFIX = "@anonymous.solongate.invalid";
|
|
7968
7935
|
ApiError = class extends Error {
|
|
7969
7936
|
status;
|
|
7970
7937
|
code;
|
|
@@ -7975,12 +7942,6 @@ var init_client = __esm({
|
|
|
7975
7942
|
this.code = code;
|
|
7976
7943
|
}
|
|
7977
7944
|
};
|
|
7978
|
-
GuestReadOnlyError = class extends Error {
|
|
7979
|
-
constructor() {
|
|
7980
|
-
super("Guest account: sign up at auth.solongate.com to change anything. Your project, its policy and everything recorded carry over.");
|
|
7981
|
-
this.name = "GuestReadOnlyError";
|
|
7982
|
-
}
|
|
7983
|
-
};
|
|
7984
7945
|
NotAuthenticatedError = class extends Error {
|
|
7985
7946
|
constructor() {
|
|
7986
7947
|
super("Not logged in. Run `solongate` and log in from the Accounts panel.");
|
|
@@ -9321,18 +9282,18 @@ function LivePanel({ active: active2 }) {
|
|
|
9321
9282
|
}
|
|
9322
9283
|
) : null,
|
|
9323
9284
|
activeAction ? /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", backgroundColor: activeAction.level === "bad" ? "#3d1220" : "#123d1f", color: activeAction.level === "bad" ? "#ff6b6b" : "#7bd88f", bold: true, children: ` ${truncate2(activeAction.text, innerW - 4)} ` }) : null,
|
|
9324
|
-
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
9325
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
9285
|
+
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, overflow: "hidden", children: [
|
|
9286
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, height: 1 + chartH, marginRight: 2, overflow: "hidden", children: [
|
|
9326
9287
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
9327
9288
|
/* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
|
|
9328
9289
|
] }),
|
|
9329
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
|
|
9290
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, height: 1 + chartH, overflow: "hidden", children: [
|
|
9330
9291
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `now ${latNow}ms \xB7 med ${latMed}ms \xB7 amber >${Math.round(latHotAt)}ms`, width: rightW }),
|
|
9331
9292
|
/* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "white", hotColor: "#ffb454" })
|
|
9332
9293
|
] })
|
|
9333
9294
|
] }),
|
|
9334
|
-
/* @__PURE__ */ jsxs2(Box2, { height: colH, children: [
|
|
9335
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
|
|
9295
|
+
/* @__PURE__ */ jsxs2(Box2, { height: colH, overflow: "hidden", children: [
|
|
9296
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, height: colH, marginRight: 2, overflow: "hidden", children: [
|
|
9336
9297
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", extra: "l = inspect", width: colW }),
|
|
9337
9298
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
9338
9299
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
|
|
@@ -9345,7 +9306,11 @@ function LivePanel({ active: active2 }) {
|
|
|
9345
9306
|
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
|
|
9346
9307
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${(dl?.patterns ?? []).length} builtin \xB7 ${(dl?.custom ?? []).length} custom` })
|
|
9347
9308
|
] }),
|
|
9348
|
-
dlpBars.length ? dlpBars.map((d2) => /* @__PURE__ */ jsx2(HBar, { label: truncate2(d2.pattern, 10), value: d2.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d2.pattern)) :
|
|
9309
|
+
dlpBars.length ? dlpBars.map((d2) => /* @__PURE__ */ jsx2(HBar, { label: truncate2(d2.pattern, 10), value: d2.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d2.pattern)) : (
|
|
9310
|
+
// 36 chars against a colW that is ~31 at cols=100: without truncate
|
|
9311
|
+
// this wrapped onto a second line and pushed the column over colH.
|
|
9312
|
+
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.dim, children: " no dlp hits in last 7 days" })
|
|
9313
|
+
),
|
|
9349
9314
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
9350
9315
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "GUARD".padEnd(10) }),
|
|
9351
9316
|
ring ? /* @__PURE__ */ jsxs2(Text2, { color: theme.ok, children: [
|
|
@@ -9354,7 +9319,7 @@ function LivePanel({ active: active2 }) {
|
|
|
9354
9319
|
] }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no local ring in cwd" })
|
|
9355
9320
|
] })
|
|
9356
9321
|
] }),
|
|
9357
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
|
|
9322
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, height: colH, marginRight: 2, overflow: "hidden", children: [
|
|
9358
9323
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
9359
9324
|
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
9360
9325
|
pickable.map((r, i) => {
|
|
@@ -9380,7 +9345,7 @@ function LivePanel({ active: active2 }) {
|
|
|
9380
9345
|
] }, r.id);
|
|
9381
9346
|
})
|
|
9382
9347
|
] }),
|
|
9383
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, overflow: "hidden", children: [
|
|
9348
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, height: colH, overflow: "hidden", children: [
|
|
9384
9349
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "system heartbeat", width: colW }),
|
|
9385
9350
|
log3.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
9386
9351
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
@@ -11587,26 +11552,7 @@ var init_doctor = __esm({
|
|
|
11587
11552
|
import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
|
|
11588
11553
|
import TextInput6 from "ink-text-input";
|
|
11589
11554
|
import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
|
|
11590
|
-
import { readFileSync as readFileSync11, readdirSync as readdirSync2 } from "fs";
|
|
11591
|
-
import { homedir as homedir11 } from "os";
|
|
11592
|
-
import { join as join13 } from "path";
|
|
11593
11555
|
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
11594
|
-
function guestAllowance() {
|
|
11595
|
-
try {
|
|
11596
|
-
const dir = join13(homedir11(), ".solongate");
|
|
11597
|
-
for (const f of readdirSync2(dir)) {
|
|
11598
|
-
if (!f.startsWith(".policy-cache-") || !f.endsWith(".json")) continue;
|
|
11599
|
-
try {
|
|
11600
|
-
const c2 = JSON.parse(readFileSync11(join13(dir, f), "utf-8"));
|
|
11601
|
-
const q = c2?.security?.guestQuota;
|
|
11602
|
-
if (q && typeof q.limit === "number") return { used: q.used ?? 0, limit: q.limit };
|
|
11603
|
-
} catch {
|
|
11604
|
-
}
|
|
11605
|
-
}
|
|
11606
|
-
} catch {
|
|
11607
|
-
}
|
|
11608
|
-
return null;
|
|
11609
|
-
}
|
|
11610
11556
|
function SettingsPanel({
|
|
11611
11557
|
active: active2,
|
|
11612
11558
|
focused,
|
|
@@ -11655,7 +11601,6 @@ function SettingsPanel({
|
|
|
11655
11601
|
const step = setInterval(() => setDiagStep((s) => Math.min(s + 1, total - 1)), 220);
|
|
11656
11602
|
return () => clearInterval(step);
|
|
11657
11603
|
}, [diagBusy]);
|
|
11658
|
-
void guestAllowance;
|
|
11659
11604
|
const ver = currentVersion();
|
|
11660
11605
|
useEffect7(() => {
|
|
11661
11606
|
let live2 = true;
|
|
@@ -12220,15 +12165,6 @@ function SettingsPanel({
|
|
|
12220
12165
|
cursor(r),
|
|
12221
12166
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
|
|
12222
12167
|
] });
|
|
12223
|
-
case "allowance": {
|
|
12224
|
-
const left = Math.max(0, r.limit - r.used);
|
|
12225
|
-
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
12226
|
-
cursor(r),
|
|
12227
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "allowance".padEnd(11) }),
|
|
12228
|
-
/* @__PURE__ */ jsx8(Text8, { color: left === 0 ? theme.bad : left <= 20 ? theme.warn : theme.ok, bold: true, children: `${r.used}/${r.limit} tool calls` }),
|
|
12229
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: left === 0 ? " used up \u2014 every call is denied \xB7 create an account to carry on" : ` ${left} left on this guest account \xB7 create an account for no limit` })
|
|
12230
|
-
] });
|
|
12231
|
-
}
|
|
12232
12168
|
case "guard":
|
|
12233
12169
|
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
12234
12170
|
cursor(r),
|
|
@@ -12334,7 +12270,7 @@ function SettingsPanel({
|
|
|
12334
12270
|
] });
|
|
12335
12271
|
}
|
|
12336
12272
|
};
|
|
12337
|
-
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add"
|
|
12273
|
+
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" || r.kind === "doctor" || r.kind === "repair" ? "PROTECTION" : r.kind === "cli-update" || r.kind === "auto-update" ? "UPDATES" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
12338
12274
|
const SECTION_DESC = {
|
|
12339
12275
|
ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
|
|
12340
12276
|
PROTECTION: "guard hook: enter install/update \xB7 d remove \xB7 self-protection \xB7 doctor + repair",
|
|
@@ -12528,23 +12464,7 @@ function App() {
|
|
|
12528
12464
|
const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
|
|
12529
12465
|
const cur = accounts[acctIdx];
|
|
12530
12466
|
const rawEmail = cur?.email ?? "";
|
|
12531
|
-
const
|
|
12532
|
-
const acctLabel2 = !cur ? "not logged in" : isGuestAccount2 ? "Anonymous" : rawEmail || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}`;
|
|
12533
|
-
const [guestSpent, setGuestSpent] = useState9(false);
|
|
12534
|
-
useEffect8(() => {
|
|
12535
|
-
if (!isGuestAccount2) {
|
|
12536
|
-
setGuestSpent(false);
|
|
12537
|
-
return;
|
|
12538
|
-
}
|
|
12539
|
-
let alive = true;
|
|
12540
|
-
api.auth.me().then((r) => {
|
|
12541
|
-
if (alive) setGuestSpent(r.guest?.exhausted === true);
|
|
12542
|
-
}).catch(() => {
|
|
12543
|
-
});
|
|
12544
|
-
return () => {
|
|
12545
|
-
alive = false;
|
|
12546
|
-
};
|
|
12547
|
-
}, [isGuestAccount2, cur?.apiKey]);
|
|
12467
|
+
const acctLabel2 = !cur ? "not logged in" : rawEmail || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}`;
|
|
12548
12468
|
useEffect8(() => {
|
|
12549
12469
|
if (!cur || cur.email) return;
|
|
12550
12470
|
let alive = true;
|
|
@@ -12630,12 +12550,7 @@ function App() {
|
|
|
12630
12550
|
/* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
12631
12551
|
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
|
|
12632
12552
|
/* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
|
|
12633
|
-
locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Settings to unlock the dataroom" }) :
|
|
12634
|
-
// Said on this line rather than on a new one: every panel budgets its
|
|
12635
|
-
// height against a fixed shell, so one more line up here silently clips
|
|
12636
|
-
// a row off whichever panel is open.
|
|
12637
|
-
/* @__PURE__ */ jsx9(Text9, { color: theme.bad, bold: true, children: " \xB7 allowance used up \xB7 every call denied \xB7 create an account to carry on" })
|
|
12638
|
-
) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Settings to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Settings to add another" }),
|
|
12553
|
+
locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Settings to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Settings to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Settings to add another" }),
|
|
12639
12554
|
update2.kind === "updating" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 updating to v${update2.version}\u2026` }) : update2.kind === "updated" ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, bold: true, children: ` \u2191 v${update2.version} installed \xB7 restart (q, then solongate) to apply` }) : update2.kind === "available" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} out \xB7 Settings \u2192 UPDATES` }) : update2.kind === "needs-admin" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} needs admin rights \xB7 Settings \u2192 UPDATES` }) : null
|
|
12640
12555
|
] }),
|
|
12641
12556
|
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
|
|
@@ -12713,8 +12628,8 @@ __export(tui_exports, {
|
|
|
12713
12628
|
launchTui: () => launchTui
|
|
12714
12629
|
});
|
|
12715
12630
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9 } from "fs";
|
|
12716
|
-
import { homedir as
|
|
12717
|
-
import { join as
|
|
12631
|
+
import { homedir as homedir11 } from "os";
|
|
12632
|
+
import { join as join13 } from "path";
|
|
12718
12633
|
import { render } from "ink";
|
|
12719
12634
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
12720
12635
|
async function launchTui() {
|
|
@@ -12725,11 +12640,11 @@ async function launchTui() {
|
|
|
12725
12640
|
return;
|
|
12726
12641
|
}
|
|
12727
12642
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
12728
|
-
const debugLog =
|
|
12643
|
+
const debugLog = join13(homedir11(), ".solongate", "dataroom-debug.log");
|
|
12729
12644
|
const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
|
|
12730
12645
|
const toFile = (level) => (...args) => {
|
|
12731
12646
|
try {
|
|
12732
|
-
mkdirSync9(
|
|
12647
|
+
mkdirSync9(join13(homedir11(), ".solongate"), { recursive: true });
|
|
12733
12648
|
appendFileSync2(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
12734
12649
|
`);
|
|
12735
12650
|
} catch {
|
|
@@ -12756,7 +12671,7 @@ var init_tui = __esm({
|
|
|
12756
12671
|
});
|
|
12757
12672
|
|
|
12758
12673
|
// src/commands/policy.ts
|
|
12759
|
-
import { readFileSync as
|
|
12674
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
12760
12675
|
async function run2(argv) {
|
|
12761
12676
|
const { positionals, flags } = parse(argv);
|
|
12762
12677
|
const sub = positionals[0];
|
|
@@ -12913,7 +12828,7 @@ function printRules(rules) {
|
|
|
12913
12828
|
}
|
|
12914
12829
|
async function resolveRules(target) {
|
|
12915
12830
|
if (target.endsWith(".json")) {
|
|
12916
|
-
const parsed = JSON.parse(
|
|
12831
|
+
const parsed = JSON.parse(readFileSync11(target, "utf-8"));
|
|
12917
12832
|
return parsed.rules ?? [];
|
|
12918
12833
|
}
|
|
12919
12834
|
const p = await api.policies.get(target);
|
|
@@ -13627,10 +13542,10 @@ __export(logs_server_exports, {
|
|
|
13627
13542
|
runLogsServer: () => runLogsServer
|
|
13628
13543
|
});
|
|
13629
13544
|
import { createServer } from "http";
|
|
13630
|
-
import { readFileSync as
|
|
13631
|
-
import { resolve as resolve5, join as
|
|
13632
|
-
import { homedir as
|
|
13633
|
-
import { readdirSync as
|
|
13545
|
+
import { readFileSync as readFileSync12, statSync as statSync4 } from "fs";
|
|
13546
|
+
import { resolve as resolve5, join as join14, isAbsolute as isAbsolute2 } from "path";
|
|
13547
|
+
import { homedir as homedir12 } from "os";
|
|
13548
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
13634
13549
|
function allowedOrigins() {
|
|
13635
13550
|
const base = [
|
|
13636
13551
|
"https://dashboard.solongate.com",
|
|
@@ -13646,15 +13561,15 @@ function resolveLocalLogDir(rawPath) {
|
|
|
13646
13561
|
const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
|
|
13647
13562
|
if (!dir) return null;
|
|
13648
13563
|
if (isAbsolute2(dir)) return dir;
|
|
13649
|
-
return resolve5(
|
|
13564
|
+
return resolve5(homedir12(), ".solongate", "local-logs");
|
|
13650
13565
|
}
|
|
13651
13566
|
async function findLogDir() {
|
|
13652
|
-
const base = resolve5(
|
|
13567
|
+
const base = resolve5(homedir12(), ".solongate");
|
|
13653
13568
|
try {
|
|
13654
|
-
const files =
|
|
13569
|
+
const files = readdirSync2(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
|
|
13655
13570
|
for (const f of files) {
|
|
13656
13571
|
try {
|
|
13657
|
-
const c2 = JSON.parse(
|
|
13572
|
+
const c2 = JSON.parse(readFileSync12(join14(base, f), "utf-8"));
|
|
13658
13573
|
const p = c2?.security?.localLogs?.path;
|
|
13659
13574
|
if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
|
|
13660
13575
|
} catch {
|
|
@@ -13663,7 +13578,7 @@ async function findLogDir() {
|
|
|
13663
13578
|
} catch {
|
|
13664
13579
|
}
|
|
13665
13580
|
try {
|
|
13666
|
-
const cfgRaw =
|
|
13581
|
+
const cfgRaw = readFileSync12(join14(base, "cloud-guard.json"), "utf-8");
|
|
13667
13582
|
const { apiKey, apiUrl } = JSON.parse(cfgRaw);
|
|
13668
13583
|
if (apiKey) {
|
|
13669
13584
|
const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
|
|
@@ -13692,7 +13607,7 @@ function setCors(req, res) {
|
|
|
13692
13607
|
}
|
|
13693
13608
|
function fileInfo(dir) {
|
|
13694
13609
|
if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
|
|
13695
|
-
const file =
|
|
13610
|
+
const file = join14(dir, LOG_FILENAME);
|
|
13696
13611
|
try {
|
|
13697
13612
|
const st = statSync4(file);
|
|
13698
13613
|
return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
|
|
@@ -13775,7 +13690,7 @@ async function runLogsServer() {
|
|
|
13775
13690
|
return;
|
|
13776
13691
|
}
|
|
13777
13692
|
try {
|
|
13778
|
-
const text =
|
|
13693
|
+
const text = readFileSync12(info.file, "utf-8");
|
|
13779
13694
|
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Last-Modified": lastMod, "X-Solongate-Exists": "1" });
|
|
13780
13695
|
res.end(text);
|
|
13781
13696
|
} catch {
|
|
@@ -13828,9 +13743,9 @@ var init_logs_server = __esm({
|
|
|
13828
13743
|
});
|
|
13829
13744
|
|
|
13830
13745
|
// src/index.ts
|
|
13831
|
-
import { readFileSync as
|
|
13746
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
13832
13747
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13833
|
-
import { dirname as dirname4, join as
|
|
13748
|
+
import { dirname as dirname4, join as join15 } from "path";
|
|
13834
13749
|
|
|
13835
13750
|
// src/config.ts
|
|
13836
13751
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -17225,8 +17140,8 @@ if (!IS_HUMAN_CLI) {
|
|
|
17225
17140
|
}
|
|
17226
17141
|
var PKG_VERSION = (() => {
|
|
17227
17142
|
try {
|
|
17228
|
-
const p =
|
|
17229
|
-
return JSON.parse(
|
|
17143
|
+
const p = join15(dirname4(fileURLToPath4(import.meta.url)), "..", "package.json");
|
|
17144
|
+
return JSON.parse(readFileSync13(p, "utf-8")).version || "unknown";
|
|
17230
17145
|
} catch {
|
|
17231
17146
|
return "unknown";
|
|
17232
17147
|
}
|
|
@@ -17397,13 +17312,6 @@ async function main() {
|
|
|
17397
17312
|
}
|
|
17398
17313
|
const MGMT_COMMANDS = /* @__PURE__ */ new Set(["policy", "ratelimit", "dlp", "stats", "audit", "sessions", "session", "doctor", "watch", "alerts", "webhooks"]);
|
|
17399
17314
|
if (MGMT_COMMANDS.has(subcommand ?? "")) {
|
|
17400
|
-
const { isGuestAccount: isGuestAccount2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
17401
|
-
if (subcommand !== "doctor" && isGuestAccount2()) {
|
|
17402
|
-
process.stderr.write(
|
|
17403
|
-
"\n This is a guest account, which can look but not change.\n Create an account to use the management commands:\n https://auth.solongate.com\n\n Your project, its policy and everything already recorded carry over,\n and this device keeps working without being paired again.\n\n"
|
|
17404
|
-
);
|
|
17405
|
-
process.exit(1);
|
|
17406
|
-
}
|
|
17407
17315
|
const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
|
|
17408
17316
|
const code = await runCommand2(subcommand, process.argv.slice(3));
|
|
17409
17317
|
process.exit(code);
|
package/dist/tui/index.js
CHANGED
|
@@ -530,8 +530,8 @@ var init_global_install = __esm({
|
|
|
530
530
|
|
|
531
531
|
// src/tui/index.tsx
|
|
532
532
|
import { appendFileSync, mkdirSync as mkdirSync7 } from "fs";
|
|
533
|
-
import { homedir as
|
|
534
|
-
import { join as
|
|
533
|
+
import { homedir as homedir10 } from "os";
|
|
534
|
+
import { join as join10 } from "path";
|
|
535
535
|
import { render } from "ink";
|
|
536
536
|
|
|
537
537
|
// src/tui/App.tsx
|
|
@@ -935,16 +935,6 @@ function setViewCredentials(creds) {
|
|
|
935
935
|
viewOverride = creds;
|
|
936
936
|
cached2 = null;
|
|
937
937
|
}
|
|
938
|
-
var GUEST_EMAIL_SUFFIX = "@anonymous.solongate.invalid";
|
|
939
|
-
function isGuestAccount() {
|
|
940
|
-
try {
|
|
941
|
-
const active2 = loginCredentialFile().apiKey;
|
|
942
|
-
if (!active2) return false;
|
|
943
|
-
return listAccounts().some((a) => a.apiKey === active2 && (a.email ?? "").endsWith(GUEST_EMAIL_SUFFIX));
|
|
944
|
-
} catch {
|
|
945
|
-
return false;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
938
|
function enforcingKey() {
|
|
949
939
|
return loginCredentialFile().apiKey ?? null;
|
|
950
940
|
}
|
|
@@ -1000,12 +990,6 @@ var ApiError = class extends Error {
|
|
|
1000
990
|
this.code = code;
|
|
1001
991
|
}
|
|
1002
992
|
};
|
|
1003
|
-
var GuestReadOnlyError = class extends Error {
|
|
1004
|
-
constructor() {
|
|
1005
|
-
super("Guest account: sign up at auth.solongate.com to change anything. Your project, its policy and everything recorded carry over.");
|
|
1006
|
-
this.name = "GuestReadOnlyError";
|
|
1007
|
-
}
|
|
1008
|
-
};
|
|
1009
993
|
var NotAuthenticatedError = class extends Error {
|
|
1010
994
|
constructor() {
|
|
1011
995
|
super("Not logged in. Run `solongate` and log in from the Accounts panel.");
|
|
@@ -1070,9 +1054,6 @@ function buildUrl(base, path, query) {
|
|
|
1070
1054
|
return url.toString();
|
|
1071
1055
|
}
|
|
1072
1056
|
async function request(method, path, opts = {}) {
|
|
1073
|
-
if (method !== "GET" && isGuestAccount()) {
|
|
1074
|
-
throw new GuestReadOnlyError();
|
|
1075
|
-
}
|
|
1076
1057
|
const creds = resolveCredentials(opts.apiUrl);
|
|
1077
1058
|
const url = buildUrl(creds.apiUrl, path, opts.query);
|
|
1078
1059
|
const headers = {
|
|
@@ -2422,18 +2403,18 @@ function LivePanel({ active: active2 }) {
|
|
|
2422
2403
|
}
|
|
2423
2404
|
) : null,
|
|
2424
2405
|
activeAction ? /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", backgroundColor: activeAction.level === "bad" ? "#3d1220" : "#123d1f", color: activeAction.level === "bad" ? "#ff6b6b" : "#7bd88f", bold: true, children: ` ${truncate(activeAction.text, innerW - 4)} ` }) : null,
|
|
2425
|
-
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
2426
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
2406
|
+
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, overflow: "hidden", children: [
|
|
2407
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, height: 1 + chartH, marginRight: 2, overflow: "hidden", children: [
|
|
2427
2408
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
2428
2409
|
/* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
|
|
2429
2410
|
] }),
|
|
2430
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
|
|
2411
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, height: 1 + chartH, overflow: "hidden", children: [
|
|
2431
2412
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `now ${latNow}ms \xB7 med ${latMed}ms \xB7 amber >${Math.round(latHotAt)}ms`, width: rightW }),
|
|
2432
2413
|
/* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "white", hotColor: "#ffb454" })
|
|
2433
2414
|
] })
|
|
2434
2415
|
] }),
|
|
2435
|
-
/* @__PURE__ */ jsxs2(Box2, { height: colH, children: [
|
|
2436
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
|
|
2416
|
+
/* @__PURE__ */ jsxs2(Box2, { height: colH, overflow: "hidden", children: [
|
|
2417
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, height: colH, marginRight: 2, overflow: "hidden", children: [
|
|
2437
2418
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", extra: "l = inspect", width: colW }),
|
|
2438
2419
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
2439
2420
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
|
|
@@ -2446,7 +2427,11 @@ function LivePanel({ active: active2 }) {
|
|
|
2446
2427
|
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
|
|
2447
2428
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${(dl?.patterns ?? []).length} builtin \xB7 ${(dl?.custom ?? []).length} custom` })
|
|
2448
2429
|
] }),
|
|
2449
|
-
dlpBars.length ? dlpBars.map((d2) => /* @__PURE__ */ jsx2(HBar, { label: truncate(d2.pattern, 10), value: d2.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d2.pattern)) :
|
|
2430
|
+
dlpBars.length ? dlpBars.map((d2) => /* @__PURE__ */ jsx2(HBar, { label: truncate(d2.pattern, 10), value: d2.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d2.pattern)) : (
|
|
2431
|
+
// 36 chars against a colW that is ~31 at cols=100: without truncate
|
|
2432
|
+
// this wrapped onto a second line and pushed the column over colH.
|
|
2433
|
+
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.dim, children: " no dlp hits in last 7 days" })
|
|
2434
|
+
),
|
|
2450
2435
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
2451
2436
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "GUARD".padEnd(10) }),
|
|
2452
2437
|
ring ? /* @__PURE__ */ jsxs2(Text2, { color: theme.ok, children: [
|
|
@@ -2455,7 +2440,7 @@ function LivePanel({ active: active2 }) {
|
|
|
2455
2440
|
] }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no local ring in cwd" })
|
|
2456
2441
|
] })
|
|
2457
2442
|
] }),
|
|
2458
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
|
|
2443
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, height: colH, marginRight: 2, overflow: "hidden", children: [
|
|
2459
2444
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
2460
2445
|
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
2461
2446
|
pickable.map((r, i) => {
|
|
@@ -2481,7 +2466,7 @@ function LivePanel({ active: active2 }) {
|
|
|
2481
2466
|
] }, r.id);
|
|
2482
2467
|
})
|
|
2483
2468
|
] }),
|
|
2484
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, overflow: "hidden", children: [
|
|
2469
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, height: colH, overflow: "hidden", children: [
|
|
2485
2470
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "system heartbeat", width: colW }),
|
|
2486
2471
|
log.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
2487
2472
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
@@ -4677,26 +4662,7 @@ async function tuiUpdateFlow(onStatus) {
|
|
|
4677
4662
|
}
|
|
4678
4663
|
|
|
4679
4664
|
// src/tui/panels/Settings.tsx
|
|
4680
|
-
import { readFileSync as readFileSync8, readdirSync as readdirSync2 } from "fs";
|
|
4681
|
-
import { homedir as homedir10 } from "os";
|
|
4682
|
-
import { join as join10 } from "path";
|
|
4683
4665
|
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
4684
|
-
function guestAllowance() {
|
|
4685
|
-
try {
|
|
4686
|
-
const dir = join10(homedir10(), ".solongate");
|
|
4687
|
-
for (const f of readdirSync2(dir)) {
|
|
4688
|
-
if (!f.startsWith(".policy-cache-") || !f.endsWith(".json")) continue;
|
|
4689
|
-
try {
|
|
4690
|
-
const c2 = JSON.parse(readFileSync8(join10(dir, f), "utf-8"));
|
|
4691
|
-
const q = c2?.security?.guestQuota;
|
|
4692
|
-
if (q && typeof q.limit === "number") return { used: q.used ?? 0, limit: q.limit };
|
|
4693
|
-
} catch {
|
|
4694
|
-
}
|
|
4695
|
-
}
|
|
4696
|
-
} catch {
|
|
4697
|
-
}
|
|
4698
|
-
return null;
|
|
4699
|
-
}
|
|
4700
4666
|
var EVENTS = ["denials", "allowed", "all"];
|
|
4701
4667
|
var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4702
4668
|
var SIGNALS2 = ["any", "deny", "dlp", "ratelimit"];
|
|
@@ -4761,7 +4727,6 @@ function SettingsPanel({
|
|
|
4761
4727
|
const step = setInterval(() => setDiagStep((s) => Math.min(s + 1, total - 1)), 220);
|
|
4762
4728
|
return () => clearInterval(step);
|
|
4763
4729
|
}, [diagBusy]);
|
|
4764
|
-
void guestAllowance;
|
|
4765
4730
|
const ver = currentVersion();
|
|
4766
4731
|
useEffect7(() => {
|
|
4767
4732
|
let live2 = true;
|
|
@@ -5326,15 +5291,6 @@ function SettingsPanel({
|
|
|
5326
5291
|
cursor(r),
|
|
5327
5292
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
|
|
5328
5293
|
] });
|
|
5329
|
-
case "allowance": {
|
|
5330
|
-
const left = Math.max(0, r.limit - r.used);
|
|
5331
|
-
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
5332
|
-
cursor(r),
|
|
5333
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "allowance".padEnd(11) }),
|
|
5334
|
-
/* @__PURE__ */ jsx8(Text8, { color: left === 0 ? theme.bad : left <= 20 ? theme.warn : theme.ok, bold: true, children: `${r.used}/${r.limit} tool calls` }),
|
|
5335
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: left === 0 ? " used up \u2014 every call is denied \xB7 create an account to carry on" : ` ${left} left on this guest account \xB7 create an account for no limit` })
|
|
5336
|
-
] });
|
|
5337
|
-
}
|
|
5338
5294
|
case "guard":
|
|
5339
5295
|
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
5340
5296
|
cursor(r),
|
|
@@ -5440,7 +5396,7 @@ function SettingsPanel({
|
|
|
5440
5396
|
] });
|
|
5441
5397
|
}
|
|
5442
5398
|
};
|
|
5443
|
-
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add"
|
|
5399
|
+
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" || r.kind === "doctor" || r.kind === "repair" ? "PROTECTION" : r.kind === "cli-update" || r.kind === "auto-update" ? "UPDATES" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
5444
5400
|
const SECTION_DESC = {
|
|
5445
5401
|
ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
|
|
5446
5402
|
PROTECTION: "guard hook: enter install/update \xB7 d remove \xB7 self-protection \xB7 doctor + repair",
|
|
@@ -5610,23 +5566,7 @@ function App() {
|
|
|
5610
5566
|
const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
|
|
5611
5567
|
const cur = accounts[acctIdx];
|
|
5612
5568
|
const rawEmail = cur?.email ?? "";
|
|
5613
|
-
const
|
|
5614
|
-
const acctLabel2 = !cur ? "not logged in" : isGuestAccount2 ? "Anonymous" : rawEmail || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}`;
|
|
5615
|
-
const [guestSpent, setGuestSpent] = useState9(false);
|
|
5616
|
-
useEffect8(() => {
|
|
5617
|
-
if (!isGuestAccount2) {
|
|
5618
|
-
setGuestSpent(false);
|
|
5619
|
-
return;
|
|
5620
|
-
}
|
|
5621
|
-
let alive = true;
|
|
5622
|
-
api.auth.me().then((r) => {
|
|
5623
|
-
if (alive) setGuestSpent(r.guest?.exhausted === true);
|
|
5624
|
-
}).catch(() => {
|
|
5625
|
-
});
|
|
5626
|
-
return () => {
|
|
5627
|
-
alive = false;
|
|
5628
|
-
};
|
|
5629
|
-
}, [isGuestAccount2, cur?.apiKey]);
|
|
5569
|
+
const acctLabel2 = !cur ? "not logged in" : rawEmail || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}`;
|
|
5630
5570
|
useEffect8(() => {
|
|
5631
5571
|
if (!cur || cur.email) return;
|
|
5632
5572
|
let alive = true;
|
|
@@ -5712,12 +5652,7 @@ function App() {
|
|
|
5712
5652
|
/* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
5713
5653
|
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
|
|
5714
5654
|
/* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
|
|
5715
|
-
locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Settings to unlock the dataroom" }) :
|
|
5716
|
-
// Said on this line rather than on a new one: every panel budgets its
|
|
5717
|
-
// height against a fixed shell, so one more line up here silently clips
|
|
5718
|
-
// a row off whichever panel is open.
|
|
5719
|
-
/* @__PURE__ */ jsx9(Text9, { color: theme.bad, bold: true, children: " \xB7 allowance used up \xB7 every call denied \xB7 create an account to carry on" })
|
|
5720
|
-
) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Settings to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Settings to add another" }),
|
|
5655
|
+
locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Settings to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Settings to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Settings to add another" }),
|
|
5721
5656
|
update2.kind === "updating" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 updating to v${update2.version}\u2026` }) : update2.kind === "updated" ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, bold: true, children: ` \u2191 v${update2.version} installed \xB7 restart (q, then solongate) to apply` }) : update2.kind === "available" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} out \xB7 Settings \u2192 UPDATES` }) : update2.kind === "needs-admin" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} needs admin rights \xB7 Settings \u2192 UPDATES` }) : null
|
|
5722
5657
|
] }),
|
|
5723
5658
|
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
|
|
@@ -5766,11 +5701,11 @@ async function launchTui() {
|
|
|
5766
5701
|
return;
|
|
5767
5702
|
}
|
|
5768
5703
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
5769
|
-
const debugLog =
|
|
5704
|
+
const debugLog = join10(homedir10(), ".solongate", "dataroom-debug.log");
|
|
5770
5705
|
const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
|
|
5771
5706
|
const toFile = (level) => (...args) => {
|
|
5772
5707
|
try {
|
|
5773
|
-
mkdirSync7(
|
|
5708
|
+
mkdirSync7(join10(homedir10(), ".solongate"), { recursive: true });
|
|
5774
5709
|
appendFileSync(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
5775
5710
|
`);
|
|
5776
5711
|
} catch {
|
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -6536,7 +6536,7 @@ import { resolve, join, dirname, isAbsolute } from "node:path";
|
|
|
6536
6536
|
import { homedir } from "node:os";
|
|
6537
6537
|
import { gunzipSync } from "node:zlib";
|
|
6538
6538
|
import { createHash } from "node:crypto";
|
|
6539
|
-
var HOOK_VERSION =
|
|
6539
|
+
var HOOK_VERSION = 71;
|
|
6540
6540
|
function localLogsOnly(security) {
|
|
6541
6541
|
if (security && typeof security === "object") {
|
|
6542
6542
|
const l = security.localLogs;
|
|
@@ -7951,53 +7951,10 @@ function rateLimitCheck(agentKey, limits) {
|
|
|
7951
7951
|
return null;
|
|
7952
7952
|
}
|
|
7953
7953
|
}
|
|
7954
|
-
function quotaTally(used) {
|
|
7955
|
-
const dir = resolve(homedir(), ".solongate");
|
|
7956
|
-
const markFile = join(dir, ".quota-mark");
|
|
7957
|
-
const tallyFile = join(dir, ".quota-tally");
|
|
7958
|
-
let n = 0;
|
|
7959
|
-
try {
|
|
7960
|
-
let mark = null;
|
|
7961
|
-
if (existsSync(markFile)) {
|
|
7962
|
-
try {
|
|
7963
|
-
mark = readFileSync(markFile, "utf-8").trim();
|
|
7964
|
-
} catch {
|
|
7965
|
-
}
|
|
7966
|
-
}
|
|
7967
|
-
if (mark !== String(used)) {
|
|
7968
|
-
writeFileSync(markFile, String(used));
|
|
7969
|
-
writeFileSync(tallyFile, "");
|
|
7970
|
-
} else if (existsSync(tallyFile)) {
|
|
7971
|
-
try {
|
|
7972
|
-
n = statSync(tallyFile).size;
|
|
7973
|
-
} catch {
|
|
7974
|
-
}
|
|
7975
|
-
}
|
|
7976
|
-
} catch {
|
|
7977
|
-
}
|
|
7978
|
-
return {
|
|
7979
|
-
n,
|
|
7980
|
-
add() {
|
|
7981
|
-
try {
|
|
7982
|
-
appendFileSync(tallyFile, ".");
|
|
7983
|
-
} catch {
|
|
7984
|
-
}
|
|
7985
|
-
}
|
|
7986
|
-
};
|
|
7987
|
-
}
|
|
7988
7954
|
function securityLayerCheck(toolName, args, cfg, agentKey) {
|
|
7989
7955
|
if (!cfg)
|
|
7990
7956
|
return null;
|
|
7991
7957
|
try {
|
|
7992
|
-
const q = cfg.guestQuota;
|
|
7993
|
-
if (q) {
|
|
7994
|
-
const tally = quotaTally(q.used ?? 0);
|
|
7995
|
-
const spent = (q.used ?? 0) + tally.n;
|
|
7996
|
-
if (q.exhausted || spent >= q.limit) {
|
|
7997
|
-
return `Guest allowance used up (${q.limit} tool calls). Create an account at https://auth.solongate.com to carry on. Your project, its policy and everything recorded carry over, and this device keeps working. To stop guarding this machine instead: solongate, Settings, guard, then press d.`;
|
|
7998
|
-
}
|
|
7999
|
-
tally.add();
|
|
8000
|
-
}
|
|
8001
7958
|
if (cfg.dlpBlock) {
|
|
8002
7959
|
const hit = dlpScan(args, cfg.dlpBlock);
|
|
8003
7960
|
if (hit)
|
package/hooks/guard.mjs
CHANGED
|
@@ -32,7 +32,7 @@ import { createHash } from 'node:crypto';
|
|
|
32
32
|
// the installed hook self-updates when the cloud version is higher (see
|
|
33
33
|
// maybeSelfUpdate). This is what makes guard fixes propagate without a manual
|
|
34
34
|
// reinstall — the same trust model as the OPA WASM this hook already runs.
|
|
35
|
-
const HOOK_VERSION =
|
|
35
|
+
const HOOK_VERSION = 71;
|
|
36
36
|
|
|
37
37
|
// True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
|
|
38
38
|
// nothing is sent to the cloud audit log.
|
|
@@ -1761,62 +1761,10 @@ function rateLimitCheck(agentKey, limits) {
|
|
|
1761
1761
|
}
|
|
1762
1762
|
}
|
|
1763
1763
|
|
|
1764
|
-
// The allowance figure the API sends is built from audit rows, and those are
|
|
1765
|
-
// written AFTER a call runs. Under a burst of parallel tool calls every hook
|
|
1766
|
-
// therefore reads the same pre-burst figure, and the allowance overshoots by
|
|
1767
|
-
// roughly a batch before the count catches up.
|
|
1768
|
-
//
|
|
1769
|
-
// The guard is the thing making the calls, so it can close that gap without
|
|
1770
|
-
// another round trip: it tallies what it has already let through since the
|
|
1771
|
-
// server's figure last moved, and adds it in. One byte is appended per call and
|
|
1772
|
-
// the file's LENGTH is the count, because many hooks run at once and a
|
|
1773
|
-
// read-modify-write would lose increments; an O_APPEND write of a single byte
|
|
1774
|
-
// does not interleave.
|
|
1775
|
-
function quotaTally(used) {
|
|
1776
|
-
const dir = resolve(homedir(), '.solongate');
|
|
1777
|
-
const markFile = join(dir, '.quota-mark');
|
|
1778
|
-
const tallyFile = join(dir, '.quota-tally');
|
|
1779
|
-
let n = 0;
|
|
1780
|
-
try {
|
|
1781
|
-
let mark = null;
|
|
1782
|
-
if (existsSync(markFile)) { try { mark = readFileSync(markFile, 'utf-8').trim(); } catch {} }
|
|
1783
|
-
// A figure that has moved means the audit rows caught up and absorbed the
|
|
1784
|
-
// local tally, so it starts again from there.
|
|
1785
|
-
if (mark !== String(used)) {
|
|
1786
|
-
writeFileSync(markFile, String(used));
|
|
1787
|
-
writeFileSync(tallyFile, '');
|
|
1788
|
-
} else if (existsSync(tallyFile)) {
|
|
1789
|
-
try { n = statSync(tallyFile).size; } catch {}
|
|
1790
|
-
}
|
|
1791
|
-
} catch { /* a missing tally only costs accuracy near the edge, never the gate */ }
|
|
1792
|
-
return {
|
|
1793
|
-
n,
|
|
1794
|
-
add() { try { appendFileSync(tallyFile, '.'); } catch {} },
|
|
1795
|
-
};
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
1764
|
// Runs all enabled enforcement layers; returns a deny reason or null (allow).
|
|
1799
1765
|
function securityLayerCheck(toolName, args, cfg, agentKey) {
|
|
1800
1766
|
if (!cfg) return null;
|
|
1801
1767
|
try {
|
|
1802
|
-
// Guest allowance. A guest account is a trial, so past its allowance every
|
|
1803
|
-
// call is denied without exception rather than quietly running unguarded:
|
|
1804
|
-
// an agent that believes it is protected and is not is worse off than one
|
|
1805
|
-
// that is stopped and told why. Checked first, so no other layer can let a
|
|
1806
|
-
// call through after the allowance is gone.
|
|
1807
|
-
const q = cfg.guestQuota;
|
|
1808
|
-
if (q) {
|
|
1809
|
-
// The server's figure plus what this device has run since, so a burst
|
|
1810
|
-
// cannot spend past the allowance while the audit rows are still in flight.
|
|
1811
|
-
const tally = quotaTally(q.used ?? 0);
|
|
1812
|
-
const spent = (q.used ?? 0) + tally.n;
|
|
1813
|
-
if (q.exhausted || spent >= q.limit) {
|
|
1814
|
-
return `Guest allowance used up (${q.limit} tool calls). Create an account at https://auth.solongate.com to carry on. Your project, its policy and everything recorded carry over, and this device keeps working. To stop guarding this machine instead: solongate, Settings, guard, then press d.`;
|
|
1815
|
-
}
|
|
1816
|
-
// Counted here rather than after the verdict, because a call the policy
|
|
1817
|
-
// goes on to deny is still recorded and still spends the allowance.
|
|
1818
|
-
tally.add();
|
|
1819
|
-
}
|
|
1820
1768
|
if (cfg.dlpBlock) {
|
|
1821
1769
|
const hit = dlpScan(args, cfg.dlpBlock);
|
|
1822
1770
|
if (hit) return 'Security layer (DLP): blocked - arguments contain a ' + hit +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.83.
|
|
3
|
+
"version": "0.83.5",
|
|
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": {
|