@solongate/proxy 0.82.41 → 0.82.43
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/commands/index.js +18 -0
- package/dist/index.js +1509 -1291
- package/dist/self-update.d.ts +7 -0
- package/dist/tui/index.js +930 -919
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6187,907 +6187,1406 @@ var init_cli_utils = __esm({
|
|
|
6187
6187
|
}
|
|
6188
6188
|
});
|
|
6189
6189
|
|
|
6190
|
-
// src/
|
|
6191
|
-
var
|
|
6192
|
-
__export(
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6190
|
+
// src/global-install.ts
|
|
6191
|
+
var global_install_exports = {};
|
|
6192
|
+
__export(global_install_exports, {
|
|
6193
|
+
clearGuardUpdateCheck: () => clearGuardUpdateCheck,
|
|
6194
|
+
globalPaths: () => globalPaths,
|
|
6195
|
+
guardHookOutdated: () => guardHookOutdated,
|
|
6196
|
+
installClaudeShim: () => installClaudeShim,
|
|
6197
|
+
installGlobalQuiet: () => installGlobalQuiet,
|
|
6198
|
+
installGlobalWithKey: () => installGlobalWithKey,
|
|
6199
|
+
installedGuardVersion: () => installedGuardVersion,
|
|
6200
|
+
isGuardInstalled: () => isGuardInstalled,
|
|
6201
|
+
lockProtected: () => lockProtected,
|
|
6202
|
+
removeClaudeShim: () => removeClaudeShim,
|
|
6203
|
+
runGlobalInstall: () => runGlobalInstall,
|
|
6204
|
+
runGlobalRestore: () => runGlobalRestore,
|
|
6205
|
+
uninstallGlobalQuiet: () => uninstallGlobalQuiet,
|
|
6206
|
+
unlockProtected: () => unlockProtected
|
|
6198
6207
|
});
|
|
6199
|
-
import {
|
|
6200
|
-
import {
|
|
6208
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, rmSync as rmSync2 } from "fs";
|
|
6209
|
+
import { resolve as resolve3, join as join4, dirname } from "path";
|
|
6201
6210
|
import { homedir as homedir2 } from "os";
|
|
6202
|
-
import { dirname, join as join4 } from "path";
|
|
6203
6211
|
import { fileURLToPath } from "url";
|
|
6204
|
-
|
|
6212
|
+
import { createInterface } from "readline";
|
|
6213
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
6214
|
+
function lockFile(file) {
|
|
6215
|
+
if (!existsSync3(file)) return;
|
|
6205
6216
|
try {
|
|
6206
|
-
|
|
6207
|
-
|
|
6217
|
+
if (process.platform === "win32") {
|
|
6218
|
+
try {
|
|
6219
|
+
execFileSync2("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE)"], { stdio: "ignore" });
|
|
6220
|
+
} catch {
|
|
6221
|
+
}
|
|
6222
|
+
try {
|
|
6223
|
+
execFileSync2("attrib", ["+R", file], { stdio: "ignore" });
|
|
6224
|
+
} catch {
|
|
6225
|
+
}
|
|
6226
|
+
} else if (process.platform === "darwin") {
|
|
6227
|
+
try {
|
|
6228
|
+
execFileSync2("chflags", ["uchg", file], { stdio: "ignore" });
|
|
6229
|
+
} catch {
|
|
6230
|
+
}
|
|
6231
|
+
} else {
|
|
6232
|
+
try {
|
|
6233
|
+
execFileSync2("chattr", ["+i", file], { stdio: "ignore" });
|
|
6234
|
+
} catch {
|
|
6235
|
+
}
|
|
6236
|
+
}
|
|
6208
6237
|
} catch {
|
|
6209
|
-
return {};
|
|
6210
6238
|
}
|
|
6211
6239
|
}
|
|
6212
|
-
function
|
|
6240
|
+
function unlockFile(file) {
|
|
6241
|
+
if (!existsSync3(file)) return;
|
|
6213
6242
|
try {
|
|
6214
|
-
|
|
6215
|
-
|
|
6243
|
+
if (process.platform === "win32") {
|
|
6244
|
+
try {
|
|
6245
|
+
execFileSync2("icacls", [file, "/remove:d", "*S-1-1-0"], { stdio: "ignore" });
|
|
6246
|
+
} catch {
|
|
6247
|
+
}
|
|
6248
|
+
try {
|
|
6249
|
+
execFileSync2("icacls", [file, "/reset"], { stdio: "ignore" });
|
|
6250
|
+
} catch {
|
|
6251
|
+
}
|
|
6252
|
+
try {
|
|
6253
|
+
execFileSync2("attrib", ["-R", file], { stdio: "ignore" });
|
|
6254
|
+
} catch {
|
|
6255
|
+
}
|
|
6256
|
+
} else if (process.platform === "darwin") {
|
|
6257
|
+
try {
|
|
6258
|
+
execFileSync2("chflags", ["nouchg", file], { stdio: "ignore" });
|
|
6259
|
+
} catch {
|
|
6260
|
+
}
|
|
6261
|
+
} else {
|
|
6262
|
+
try {
|
|
6263
|
+
execFileSync2("chattr", ["-i", file], { stdio: "ignore" });
|
|
6264
|
+
} catch {
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6216
6267
|
} catch {
|
|
6217
6268
|
}
|
|
6218
6269
|
}
|
|
6219
|
-
function
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6270
|
+
function protectedTargets() {
|
|
6271
|
+
const p = globalPaths();
|
|
6272
|
+
return [
|
|
6273
|
+
join4(p.hooksDir, "guard.mjs"),
|
|
6274
|
+
join4(p.hooksDir, "audit.mjs"),
|
|
6275
|
+
join4(p.hooksDir, "stop.mjs"),
|
|
6276
|
+
join4(p.hooksDir, "shield.mjs"),
|
|
6277
|
+
p.configPath,
|
|
6278
|
+
p.settingsPath,
|
|
6279
|
+
p.antigravityHooksPath
|
|
6280
|
+
];
|
|
6226
6281
|
}
|
|
6227
|
-
function
|
|
6228
|
-
const
|
|
6229
|
-
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
6230
|
-
for (let i = 0; i < 3; i++) {
|
|
6231
|
-
if ((pb[i] ?? 0) > (pa[i] ?? 0)) return true;
|
|
6232
|
-
if ((pb[i] ?? 0) < (pa[i] ?? 0)) return false;
|
|
6233
|
-
}
|
|
6234
|
-
return false;
|
|
6282
|
+
function lockProtected() {
|
|
6283
|
+
for (const f of protectedTargets()) lockFile(f);
|
|
6235
6284
|
}
|
|
6236
|
-
|
|
6237
|
-
|
|
6285
|
+
function unlockProtected() {
|
|
6286
|
+
for (const f of protectedTargets()) unlockFile(f);
|
|
6238
6287
|
}
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6288
|
+
function globalPaths() {
|
|
6289
|
+
const home = homedir2();
|
|
6290
|
+
const sgDir = join4(home, ".solongate");
|
|
6291
|
+
const hooksDir = join4(sgDir, "hooks");
|
|
6292
|
+
const claudeDir = join4(home, ".claude");
|
|
6293
|
+
const antigravityDir = join4(home, ".gemini", "config");
|
|
6294
|
+
return {
|
|
6295
|
+
home,
|
|
6296
|
+
sgDir,
|
|
6297
|
+
hooksDir,
|
|
6298
|
+
claudeDir,
|
|
6299
|
+
antigravityDir,
|
|
6300
|
+
settingsPath: join4(claudeDir, "settings.json"),
|
|
6301
|
+
backupPath: join4(claudeDir, "settings.solongate.bak"),
|
|
6302
|
+
configPath: join4(sgDir, "cloud-guard.json"),
|
|
6303
|
+
// Antigravity reads global hooks from ~/.gemini/config/hooks.json. Only the
|
|
6304
|
+
// guard is registered there (PreToolUse); Antigravity's ALLOW-path audit is
|
|
6305
|
+
// covered by the passive session-log collector, not a hook.
|
|
6306
|
+
antigravityHooksPath: join4(antigravityDir, "hooks.json"),
|
|
6307
|
+
antigravityBackupPath: join4(antigravityDir, "hooks.solongate.bak")
|
|
6308
|
+
};
|
|
6254
6309
|
}
|
|
6255
|
-
function
|
|
6310
|
+
function clearGuardUpdateCheck() {
|
|
6256
6311
|
try {
|
|
6257
|
-
|
|
6258
|
-
const log3 = openSync(LOG_FILE, "a");
|
|
6259
|
-
const p = spawn("npm", ["install", "-g", `${PKG}@${version}`], {
|
|
6260
|
-
stdio: ["ignore", log3, log3],
|
|
6261
|
-
detached: true,
|
|
6262
|
-
windowsHide: true,
|
|
6263
|
-
shell: process.platform === "win32"
|
|
6264
|
-
});
|
|
6265
|
-
p.on("error", () => {
|
|
6266
|
-
});
|
|
6267
|
-
p.unref();
|
|
6312
|
+
rmSync2(join4(globalPaths().sgDir, ".hook-update-check"), { force: true });
|
|
6268
6313
|
return true;
|
|
6269
6314
|
} catch {
|
|
6270
6315
|
return false;
|
|
6271
6316
|
}
|
|
6272
6317
|
}
|
|
6273
|
-
function
|
|
6274
|
-
return
|
|
6275
|
-
try {
|
|
6276
|
-
mkdirSync3(join4(homedir2(), ".solongate"), { recursive: true });
|
|
6277
|
-
execFile(
|
|
6278
|
-
"npm",
|
|
6279
|
-
["install", "-g", `${PKG}@${version}`],
|
|
6280
|
-
{ timeout: 3e5, windowsHide: true, shell: process.platform === "win32" },
|
|
6281
|
-
(err2, stdout, stderr) => {
|
|
6282
|
-
try {
|
|
6283
|
-
writeFileSync3(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} install ${version}: ${err2 ? "FAILED" : "ok"}
|
|
6284
|
-
${stdout}
|
|
6285
|
-
${stderr}
|
|
6286
|
-
`, { flag: "a" });
|
|
6287
|
-
} catch {
|
|
6288
|
-
}
|
|
6289
|
-
resolve6(!err2);
|
|
6290
|
-
}
|
|
6291
|
-
);
|
|
6292
|
-
} catch {
|
|
6293
|
-
resolve6(false);
|
|
6294
|
-
}
|
|
6295
|
-
});
|
|
6318
|
+
function readHook(filename) {
|
|
6319
|
+
return readFileSync4(join4(HOOKS_DIR, filename), "utf-8");
|
|
6296
6320
|
}
|
|
6297
|
-
|
|
6321
|
+
function firstAccountCredential() {
|
|
6298
6322
|
try {
|
|
6299
|
-
const
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
if (!latest || !newerThan(latest, current)) return;
|
|
6304
|
-
if (readState().installed === latest) {
|
|
6305
|
-
onStatus({ kind: "updated", version: latest });
|
|
6306
|
-
return;
|
|
6307
|
-
}
|
|
6308
|
-
onStatus({ kind: "updating", version: latest });
|
|
6309
|
-
if (await runGlobalInstall(latest)) {
|
|
6310
|
-
writeState({ ...readState(), installed: latest });
|
|
6311
|
-
onStatus({ kind: "updated", version: latest });
|
|
6323
|
+
const raw = JSON.parse(readFileSync4(join4(homedir2(), ".solongate", "accounts.json"), "utf-8"));
|
|
6324
|
+
if (Array.isArray(raw)) {
|
|
6325
|
+
const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
|
|
6326
|
+
if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
|
|
6312
6327
|
}
|
|
6313
6328
|
} catch {
|
|
6314
6329
|
}
|
|
6330
|
+
return {};
|
|
6315
6331
|
}
|
|
6316
|
-
function
|
|
6317
|
-
|
|
6332
|
+
function readGuard() {
|
|
6333
|
+
const bundled = join4(HOOKS_DIR, "guard.bundled.mjs");
|
|
6334
|
+
return existsSync3(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
|
|
6335
|
+
}
|
|
6336
|
+
function antigravityHookCommand(guardAbs) {
|
|
6337
|
+
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
6338
|
+
const call = process.platform === "win32" ? "& " : "";
|
|
6339
|
+
return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" antigravity "Antigravity"`;
|
|
6340
|
+
}
|
|
6341
|
+
function installAntigravityGuard(p, guardAbs) {
|
|
6342
|
+
mkdirSync3(p.antigravityDir, { recursive: true });
|
|
6343
|
+
let existing = {};
|
|
6344
|
+
if (existsSync3(p.antigravityHooksPath)) {
|
|
6345
|
+
const raw = readFileSync4(p.antigravityHooksPath, "utf-8");
|
|
6346
|
+
if (!existsSync3(p.antigravityBackupPath)) writeFileSync3(p.antigravityBackupPath, raw);
|
|
6318
6347
|
try {
|
|
6319
|
-
|
|
6320
|
-
const now = Date.now();
|
|
6321
|
-
const current = currentVersion();
|
|
6322
|
-
let latest = state.latestSeen ?? null;
|
|
6323
|
-
if (now - (state.lastCheckAt ?? 0) >= CHECK_EVERY_MS) {
|
|
6324
|
-
latest = await fetchLatest();
|
|
6325
|
-
if (latest) writeState({ ...state, lastCheckAt: now, latestSeen: latest });
|
|
6326
|
-
else writeState({ ...state, lastCheckAt: now });
|
|
6327
|
-
}
|
|
6328
|
-
if (!latest || !newerThan(latest, current)) return;
|
|
6329
|
-
const attempts = readState().attempts ?? {};
|
|
6330
|
-
if (now - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS) {
|
|
6331
|
-
writeState({ ...readState(), attempts: { [latest]: now } });
|
|
6332
|
-
if (spawnGlobalInstall(latest)) {
|
|
6333
|
-
notify(`${c.dim}\u2191 solongate v${latest} available (running v${current}) \u2014 updating in the background, next run uses it${c.reset}`);
|
|
6334
|
-
}
|
|
6335
|
-
}
|
|
6348
|
+
existing = JSON.parse(raw);
|
|
6336
6349
|
} catch {
|
|
6350
|
+
existing = {};
|
|
6337
6351
|
}
|
|
6338
|
-
})();
|
|
6339
|
-
}
|
|
6340
|
-
var PKG, CHECK_EVERY_MS, ATTEMPT_EVERY_MS, STATE_FILE, LOG_FILE;
|
|
6341
|
-
var init_self_update = __esm({
|
|
6342
|
-
"src/self-update.ts"() {
|
|
6343
|
-
"use strict";
|
|
6344
|
-
init_cli_utils();
|
|
6345
|
-
PKG = "@solongate/proxy";
|
|
6346
|
-
CHECK_EVERY_MS = 30 * 60 * 1e3;
|
|
6347
|
-
ATTEMPT_EVERY_MS = 6 * 60 * 60 * 1e3;
|
|
6348
|
-
STATE_FILE = join4(homedir2(), ".solongate", ".self-update.json");
|
|
6349
|
-
LOG_FILE = join4(homedir2(), ".solongate", "self-update.log");
|
|
6350
|
-
}
|
|
6351
|
-
});
|
|
6352
|
-
|
|
6353
|
-
// src/logs-server-daemon.ts
|
|
6354
|
-
var logs_server_daemon_exports = {};
|
|
6355
|
-
__export(logs_server_daemon_exports, {
|
|
6356
|
-
LOGS_SERVER_PORT: () => LOGS_SERVER_PORT,
|
|
6357
|
-
ensureLogsServerDaemon: () => ensureLogsServerDaemon,
|
|
6358
|
-
logsServerStatus: () => logsServerStatus,
|
|
6359
|
-
recordLogsServerStarted: () => recordLogsServerStarted,
|
|
6360
|
-
startLogsServerDaemon: () => startLogsServerDaemon,
|
|
6361
|
-
stopLogsServerDaemon: () => stopLogsServerDaemon
|
|
6362
|
-
});
|
|
6363
|
-
import { spawn as spawn2 } from "child_process";
|
|
6364
|
-
import { mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
6365
|
-
import { homedir as homedir3 } from "os";
|
|
6366
|
-
import { dirname as dirname2, join as join5 } from "path";
|
|
6367
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6368
|
-
function readState2() {
|
|
6369
|
-
try {
|
|
6370
|
-
const s = JSON.parse(readFileSync5(STATE_FILE2, "utf-8"));
|
|
6371
|
-
return s && typeof s === "object" ? s : {};
|
|
6372
|
-
} catch {
|
|
6373
|
-
return {};
|
|
6374
6352
|
}
|
|
6353
|
+
const merged = {
|
|
6354
|
+
...existing,
|
|
6355
|
+
[ANTIGRAVITY_GROUP]: {
|
|
6356
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: antigravityHookCommand(guardAbs) }] }]
|
|
6357
|
+
}
|
|
6358
|
+
};
|
|
6359
|
+
writeFileSync3(p.antigravityHooksPath, JSON.stringify(merged, null, 2) + "\n");
|
|
6375
6360
|
}
|
|
6376
|
-
function
|
|
6361
|
+
function removeAntigravityGuard(p) {
|
|
6362
|
+
if (!existsSync3(p.antigravityHooksPath)) return;
|
|
6377
6363
|
try {
|
|
6378
|
-
|
|
6379
|
-
|
|
6364
|
+
const s = JSON.parse(readFileSync4(p.antigravityHooksPath, "utf-8"));
|
|
6365
|
+
if (!(ANTIGRAVITY_GROUP in s)) return;
|
|
6366
|
+
delete s[ANTIGRAVITY_GROUP];
|
|
6367
|
+
writeFileSync3(p.antigravityHooksPath, JSON.stringify(s, null, 2) + "\n");
|
|
6380
6368
|
} catch {
|
|
6381
6369
|
}
|
|
6382
6370
|
}
|
|
6383
|
-
function
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
}
|
|
6389
|
-
return e.code === "EPERM";
|
|
6390
|
-
}
|
|
6371
|
+
function ask(question) {
|
|
6372
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
6373
|
+
return new Promise((res) => rl.question(question, (a) => {
|
|
6374
|
+
rl.close();
|
|
6375
|
+
res(a.trim());
|
|
6376
|
+
}));
|
|
6391
6377
|
}
|
|
6392
|
-
function
|
|
6393
|
-
const
|
|
6394
|
-
|
|
6395
|
-
|
|
6378
|
+
function runGlobalRestore() {
|
|
6379
|
+
const p = globalPaths();
|
|
6380
|
+
unlockProtected();
|
|
6381
|
+
removeClaudeShim();
|
|
6382
|
+
removeAntigravityGuard(p);
|
|
6383
|
+
if (existsSync3(p.backupPath)) {
|
|
6384
|
+
writeFileSync3(p.settingsPath, readFileSync4(p.backupPath, "utf-8"));
|
|
6385
|
+
console.log(` Restored ${p.settingsPath} from backup.`);
|
|
6386
|
+
} else if (existsSync3(p.settingsPath)) {
|
|
6387
|
+
try {
|
|
6388
|
+
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
6389
|
+
delete s.hooks;
|
|
6390
|
+
writeFileSync3(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
6391
|
+
console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
|
|
6392
|
+
} catch {
|
|
6393
|
+
}
|
|
6394
|
+
} else {
|
|
6395
|
+
console.log(" Nothing to restore \u2014 no global Claude Code settings found.");
|
|
6396
|
+
}
|
|
6397
|
+
console.log(" Global SolonGate enforcement uninstalled. Restart Claude Code.");
|
|
6396
6398
|
}
|
|
6397
|
-
function
|
|
6398
|
-
|
|
6399
|
+
function installGlobalQuiet() {
|
|
6400
|
+
try {
|
|
6401
|
+
const p = globalPaths();
|
|
6402
|
+
let apiKey = process.env["SOLONGATE_API_KEY"] || "";
|
|
6403
|
+
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
6404
|
+
try {
|
|
6405
|
+
const cfg = JSON.parse(readFileSync4(p.configPath, "utf-8"));
|
|
6406
|
+
if (cfg && typeof cfg.apiKey === "string") apiKey = apiKey || cfg.apiKey;
|
|
6407
|
+
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
6408
|
+
} catch {
|
|
6409
|
+
}
|
|
6410
|
+
if (!apiKey) {
|
|
6411
|
+
const acc = firstAccountCredential();
|
|
6412
|
+
if (acc.apiKey) {
|
|
6413
|
+
apiKey = acc.apiKey;
|
|
6414
|
+
if (acc.apiUrl) apiUrl = acc.apiUrl;
|
|
6415
|
+
}
|
|
6416
|
+
}
|
|
6417
|
+
if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
|
|
6418
|
+
mkdirSync3(p.hooksDir, { recursive: true });
|
|
6419
|
+
mkdirSync3(p.claudeDir, { recursive: true });
|
|
6420
|
+
unlockProtected();
|
|
6421
|
+
writeFileSync3(join4(p.hooksDir, "guard.mjs"), readGuard());
|
|
6422
|
+
writeFileSync3(join4(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
6423
|
+
writeFileSync3(join4(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
6424
|
+
writeFileSync3(join4(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
6425
|
+
writeFileSync3(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
6426
|
+
let existing = {};
|
|
6427
|
+
if (existsSync3(p.settingsPath)) {
|
|
6428
|
+
const raw = readFileSync4(p.settingsPath, "utf-8");
|
|
6429
|
+
if (!existsSync3(p.backupPath)) writeFileSync3(p.backupPath, raw);
|
|
6430
|
+
try {
|
|
6431
|
+
existing = JSON.parse(raw);
|
|
6432
|
+
} catch {
|
|
6433
|
+
existing = {};
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
6437
|
+
const call = process.platform === "win32" ? "& " : "";
|
|
6438
|
+
const hookCmd = (script) => `${call}"${nodeBin}" "${join4(p.hooksDir, script).replace(/\\/g, "/")}" claude-code "Claude Code"`;
|
|
6439
|
+
const merged = {
|
|
6440
|
+
...existing,
|
|
6441
|
+
hooks: {
|
|
6442
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("guard.mjs") }] }],
|
|
6443
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("audit.mjs") }] }],
|
|
6444
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("stop.mjs") }] }]
|
|
6445
|
+
}
|
|
6446
|
+
};
|
|
6447
|
+
writeFileSync3(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
6448
|
+
try {
|
|
6449
|
+
installAntigravityGuard(p, join4(p.hooksDir, "guard.mjs").replace(/\\/g, "/"));
|
|
6450
|
+
} catch {
|
|
6451
|
+
}
|
|
6452
|
+
if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
|
|
6453
|
+
return { ok: true, message: "guard installed (open a new session)" };
|
|
6454
|
+
} catch (e) {
|
|
6455
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
6456
|
+
}
|
|
6399
6457
|
}
|
|
6400
|
-
function
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6458
|
+
function installedGuardVersion() {
|
|
6459
|
+
try {
|
|
6460
|
+
const p = globalPaths();
|
|
6461
|
+
const s = readFileSync4(join4(p.hooksDir, "guard.mjs"), "utf-8");
|
|
6462
|
+
const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
|
|
6463
|
+
return m ? parseInt(m[1], 10) : null;
|
|
6464
|
+
} catch {
|
|
6465
|
+
return null;
|
|
6405
6466
|
}
|
|
6467
|
+
}
|
|
6468
|
+
function guardHookOutdated() {
|
|
6406
6469
|
try {
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
const cli = join5(dirname2(fileURLToPath2(import.meta.url)), "index.js");
|
|
6410
|
-
const p = spawn2(process.execPath, [cli, "logs-server"], {
|
|
6411
|
-
detached: true,
|
|
6412
|
-
stdio: ["ignore", log3, log3],
|
|
6413
|
-
windowsHide: true
|
|
6414
|
-
});
|
|
6415
|
-
p.on("error", () => {
|
|
6416
|
-
});
|
|
6417
|
-
p.unref();
|
|
6418
|
-
writeState2({ desired: "on", pid: p.pid, port: LOGS_SERVER_PORT, startedAt: Date.now() });
|
|
6419
|
-
return { desired: "on", running: true, pid: p.pid, port: LOGS_SERVER_PORT };
|
|
6470
|
+
const p = globalPaths();
|
|
6471
|
+
return readFileSync4(join4(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
|
|
6420
6472
|
} catch {
|
|
6421
|
-
return
|
|
6473
|
+
return false;
|
|
6422
6474
|
}
|
|
6423
6475
|
}
|
|
6424
|
-
function
|
|
6425
|
-
|
|
6426
|
-
|
|
6476
|
+
function isGuardInstalled() {
|
|
6477
|
+
try {
|
|
6478
|
+
const p = globalPaths();
|
|
6479
|
+
if (!existsSync3(p.settingsPath)) return false;
|
|
6480
|
+
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
6481
|
+
return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
|
|
6482
|
+
} catch {
|
|
6483
|
+
return false;
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
function uninstallGlobalQuiet() {
|
|
6487
|
+
try {
|
|
6488
|
+
const p = globalPaths();
|
|
6489
|
+
unlockProtected();
|
|
6490
|
+
removeClaudeShim();
|
|
6427
6491
|
try {
|
|
6428
|
-
|
|
6492
|
+
removeAntigravityGuard(p);
|
|
6429
6493
|
} catch {
|
|
6430
6494
|
}
|
|
6495
|
+
if (!existsSync3(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
|
|
6496
|
+
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
6497
|
+
delete s.hooks;
|
|
6498
|
+
writeFileSync3(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
6499
|
+
return { ok: true, message: "guard removed (open a new session)" };
|
|
6500
|
+
} catch (e) {
|
|
6501
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
6431
6502
|
}
|
|
6432
|
-
writeState2({ desired: "off", port: s.port });
|
|
6433
|
-
return { desired: "off", running: false, port: s.port || LOGS_SERVER_PORT };
|
|
6434
6503
|
}
|
|
6435
|
-
function
|
|
6504
|
+
function escapeRe(s) {
|
|
6505
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6506
|
+
}
|
|
6507
|
+
function resolveRealClaude() {
|
|
6436
6508
|
try {
|
|
6437
|
-
const
|
|
6438
|
-
|
|
6509
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
6510
|
+
const out2 = execFileSync2(finder, ["claude"], { encoding: "utf-8" }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
6511
|
+
if (process.platform === "win32") {
|
|
6512
|
+
const low = (s) => s.toLowerCase();
|
|
6513
|
+
return out2.find((l) => low(l).endsWith(".cmd")) || out2.find((l) => low(l).endsWith(".exe")) || out2.find((l) => low(l).endsWith(".bat")) || out2[0] || null;
|
|
6514
|
+
}
|
|
6515
|
+
return out2[0] || null;
|
|
6439
6516
|
} catch {
|
|
6517
|
+
return null;
|
|
6440
6518
|
}
|
|
6441
6519
|
}
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6520
|
+
function shimTargets() {
|
|
6521
|
+
if (process.platform === "win32") {
|
|
6522
|
+
try {
|
|
6523
|
+
const prof = execFileSync2("powershell", ["-NoProfile", "-Command", "$PROFILE.CurrentUserAllHosts"], { encoding: "utf-8" }).trim();
|
|
6524
|
+
return prof ? [prof] : [];
|
|
6525
|
+
} catch {
|
|
6526
|
+
return [];
|
|
6527
|
+
}
|
|
6450
6528
|
}
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6529
|
+
return [".bashrc", ".zshrc", ".profile"].map((f) => join4(homedir2(), f)).filter((f) => existsSync3(f));
|
|
6530
|
+
}
|
|
6531
|
+
function writeShimBlock(file, block2) {
|
|
6532
|
+
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
6533
|
+
let content = existsSync3(file) ? readFileSync4(file, "utf-8") : "";
|
|
6534
|
+
content = content.replace(re, "");
|
|
6535
|
+
if (block2) {
|
|
6536
|
+
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
6537
|
+
content += block2 + "\n";
|
|
6538
|
+
}
|
|
6539
|
+
mkdirSync3(dirname(file), { recursive: true });
|
|
6540
|
+
writeFileSync3(file, content);
|
|
6541
|
+
}
|
|
6542
|
+
function installClaudeShim(shieldPath) {
|
|
6543
|
+
const real = resolveRealClaude();
|
|
6544
|
+
if (!real) {
|
|
6545
|
+
console.log(" (Claude Code not found on PATH \u2014 skipped auto-shield. Install it, then re-open `solongate` \u2192 Settings \u2192 guard.)");
|
|
6546
|
+
return;
|
|
6547
|
+
}
|
|
6548
|
+
const node = process.execPath.replace(/\\/g, "/");
|
|
6549
|
+
const shield = shieldPath.replace(/\\/g, "/");
|
|
6550
|
+
const win = process.platform === "win32";
|
|
6551
|
+
const block2 = win ? `${SHIM_BEGIN}
|
|
6552
|
+
function claude { & "${node}" "${shield}" -- "${real}" @args }
|
|
6553
|
+
${SHIM_END}` : `${SHIM_BEGIN}
|
|
6554
|
+
claude() { "${node}" "${shield}" -- "${real}" "$@"; }
|
|
6555
|
+
${SHIM_END}`;
|
|
6556
|
+
const targets = shimTargets();
|
|
6557
|
+
if (targets.length === 0) return;
|
|
6558
|
+
for (const file of targets) {
|
|
6559
|
+
try {
|
|
6560
|
+
writeShimBlock(file, block2);
|
|
6561
|
+
} catch {
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
function removeClaudeShim() {
|
|
6566
|
+
for (const file of shimTargets()) {
|
|
6567
|
+
try {
|
|
6568
|
+
writeShimBlock(file, null);
|
|
6569
|
+
} catch {
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
async function runGlobalInstall(opts = {}) {
|
|
6574
|
+
const p = globalPaths();
|
|
6575
|
+
let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
|
|
6576
|
+
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
6577
|
+
try {
|
|
6578
|
+
const cfg = JSON.parse(readFileSync4(p.configPath, "utf-8"));
|
|
6579
|
+
if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
|
|
6580
|
+
} catch {
|
|
6581
|
+
}
|
|
6582
|
+
}
|
|
6583
|
+
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
6584
|
+
const acc = firstAccountCredential();
|
|
6585
|
+
if (acc.apiKey) apiKey = acc.apiKey;
|
|
6586
|
+
}
|
|
6587
|
+
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
6588
|
+
apiKey = await ask(" Enter your SolonGate API key (sg_live_\u2026 from https://dashboard.solongate.com): ");
|
|
6589
|
+
}
|
|
6590
|
+
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
6591
|
+
console.log(" Invalid API key. Must start with sg_live_ or sg_test_");
|
|
6592
|
+
process.exit(1);
|
|
6593
|
+
}
|
|
6594
|
+
const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
6595
|
+
mkdirSync3(p.hooksDir, { recursive: true });
|
|
6596
|
+
mkdirSync3(p.claudeDir, { recursive: true });
|
|
6597
|
+
unlockProtected();
|
|
6598
|
+
writeFileSync3(join4(p.hooksDir, "guard.mjs"), readGuard());
|
|
6599
|
+
writeFileSync3(join4(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
6600
|
+
writeFileSync3(join4(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
6601
|
+
writeFileSync3(join4(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
6602
|
+
console.log(` Installed hooks \u2192 ${p.hooksDir}`);
|
|
6603
|
+
installClaudeShim(join4(p.hooksDir, "shield.mjs"));
|
|
6604
|
+
writeFileSync3(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
6605
|
+
console.log(` Wrote ${p.configPath}`);
|
|
6606
|
+
let existing = {};
|
|
6607
|
+
if (existsSync3(p.settingsPath)) {
|
|
6608
|
+
const raw = readFileSync4(p.settingsPath, "utf-8");
|
|
6609
|
+
if (!existsSync3(p.backupPath)) {
|
|
6610
|
+
writeFileSync3(p.backupPath, raw);
|
|
6611
|
+
console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
|
|
6612
|
+
}
|
|
6613
|
+
try {
|
|
6614
|
+
existing = JSON.parse(raw);
|
|
6615
|
+
} catch {
|
|
6616
|
+
existing = {};
|
|
6617
|
+
}
|
|
6618
|
+
}
|
|
6619
|
+
const guardAbs = join4(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
|
|
6620
|
+
const auditAbs = join4(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
|
|
6621
|
+
const stopAbs = join4(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
|
|
6622
|
+
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
6623
|
+
const call = process.platform === "win32" ? "& " : "";
|
|
6624
|
+
const hookCmd = (script) => `${call}"${nodeBin}" "${script}" claude-code "Claude Code"`;
|
|
6625
|
+
const merged = {
|
|
6626
|
+
...existing,
|
|
6627
|
+
hooks: {
|
|
6628
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(guardAbs) }] }],
|
|
6629
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(auditAbs) }] }],
|
|
6630
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(stopAbs) }] }]
|
|
6631
|
+
}
|
|
6632
|
+
};
|
|
6633
|
+
writeFileSync3(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
6634
|
+
console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
|
|
6460
6635
|
try {
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
cached = {
|
|
6464
|
-
notifications: j.notifications !== false,
|
|
6465
|
-
accent: typeof j.accent === "string" ? j.accent : void 0,
|
|
6466
|
-
pollMs: typeof j.pollMs === "number" ? j.pollMs : void 0
|
|
6467
|
-
};
|
|
6636
|
+
installAntigravityGuard(p, guardAbs);
|
|
6637
|
+
console.log(` Registered Antigravity CLI guard \u2192 ${p.antigravityHooksPath}`);
|
|
6468
6638
|
} catch {
|
|
6469
|
-
cached = defaults;
|
|
6470
6639
|
}
|
|
6471
|
-
|
|
6640
|
+
if (process.env["SOLONGATE_OS_LOCK"] === "1") {
|
|
6641
|
+
lockProtected();
|
|
6642
|
+
console.log(" Locked protection files (OS-level read-only/immutable).");
|
|
6643
|
+
}
|
|
6472
6644
|
}
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6645
|
+
async function installGlobalWithKey(apiKey, apiUrl) {
|
|
6646
|
+
await runGlobalInstall({ apiKey, apiUrl });
|
|
6647
|
+
}
|
|
6648
|
+
var __dirname, HOOKS_DIR, ANTIGRAVITY_GROUP, SHIM_BEGIN, SHIM_END;
|
|
6649
|
+
var init_global_install = __esm({
|
|
6650
|
+
"src/global-install.ts"() {
|
|
6476
6651
|
"use strict";
|
|
6477
|
-
|
|
6652
|
+
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
6653
|
+
HOOKS_DIR = resolve3(__dirname, "..", "hooks");
|
|
6654
|
+
ANTIGRAVITY_GROUP = "solongate-guard";
|
|
6655
|
+
SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
|
|
6656
|
+
SHIM_END = "# <<< SolonGate shield <<<";
|
|
6478
6657
|
}
|
|
6479
6658
|
});
|
|
6480
6659
|
|
|
6481
|
-
// src/
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6660
|
+
// src/self-update.ts
|
|
6661
|
+
var self_update_exports = {};
|
|
6662
|
+
__export(self_update_exports, {
|
|
6663
|
+
currentVersion: () => currentVersion,
|
|
6664
|
+
latestVersion: () => latestVersion,
|
|
6665
|
+
maybeSelfUpdate: () => maybeSelfUpdate,
|
|
6666
|
+
newerThan: () => newerThan,
|
|
6667
|
+
runUpdateCommand: () => runUpdateCommand,
|
|
6668
|
+
tuiUpdateFlow: () => tuiUpdateFlow
|
|
6669
|
+
});
|
|
6670
|
+
import { execFile, spawn } from "child_process";
|
|
6671
|
+
import { mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
6672
|
+
import { homedir as homedir3 } from "os";
|
|
6673
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
6674
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6675
|
+
function readState() {
|
|
6676
|
+
try {
|
|
6677
|
+
const s = JSON.parse(readFileSync5(STATE_FILE, "utf-8"));
|
|
6678
|
+
return s && typeof s === "object" ? s : {};
|
|
6679
|
+
} catch {
|
|
6680
|
+
return {};
|
|
6681
|
+
}
|
|
6487
6682
|
}
|
|
6488
|
-
function
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6683
|
+
function writeState(s) {
|
|
6684
|
+
try {
|
|
6685
|
+
mkdirSync4(join5(homedir3(), ".solongate"), { recursive: true });
|
|
6686
|
+
writeFileSync4(STATE_FILE, JSON.stringify(s));
|
|
6687
|
+
} catch {
|
|
6688
|
+
}
|
|
6492
6689
|
}
|
|
6493
|
-
function
|
|
6690
|
+
function currentVersion() {
|
|
6494
6691
|
try {
|
|
6495
|
-
|
|
6692
|
+
const pkg = JSON.parse(readFileSync5(join5(dirname2(fileURLToPath2(import.meta.url)), "..", "package.json"), "utf-8"));
|
|
6693
|
+
return pkg.version ?? "0.0.0";
|
|
6496
6694
|
} catch {
|
|
6497
|
-
return
|
|
6695
|
+
return "0.0.0";
|
|
6498
6696
|
}
|
|
6499
6697
|
}
|
|
6500
|
-
function
|
|
6501
|
-
const
|
|
6502
|
-
const
|
|
6503
|
-
for (
|
|
6504
|
-
if (
|
|
6505
|
-
|
|
6506
|
-
continue;
|
|
6507
|
-
}
|
|
6508
|
-
for (let i = 0; i < raw.length; i += w) out2.push(raw.slice(i, i + w));
|
|
6698
|
+
function newerThan(b, a) {
|
|
6699
|
+
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
6700
|
+
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
6701
|
+
for (let i = 0; i < 3; i++) {
|
|
6702
|
+
if ((pb[i] ?? 0) > (pa[i] ?? 0)) return true;
|
|
6703
|
+
if ((pb[i] ?? 0) < (pa[i] ?? 0)) return false;
|
|
6509
6704
|
}
|
|
6510
|
-
return
|
|
6705
|
+
return false;
|
|
6511
6706
|
}
|
|
6512
|
-
function
|
|
6513
|
-
|
|
6514
|
-
return s.length <= n ? s : s.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
6707
|
+
async function latestVersion() {
|
|
6708
|
+
return fetchLatest();
|
|
6515
6709
|
}
|
|
6516
|
-
function
|
|
6517
|
-
const t = typeof ts === "number" ? ts : Date.parse(ts);
|
|
6518
|
-
if (Number.isNaN(t)) return String(ts ?? "");
|
|
6519
|
-
const s = Math.max(0, (Date.now() - t) / 1e3);
|
|
6520
|
-
if (s < 60) return `${Math.floor(s)}s`;
|
|
6521
|
-
if (s < 3600) return `${Math.floor(s / 60)}m`;
|
|
6522
|
-
if (s < 86400) return `${Math.floor(s / 3600)}h`;
|
|
6523
|
-
return `${Math.floor(s / 86400)}d`;
|
|
6524
|
-
}
|
|
6525
|
-
var accent, theme;
|
|
6526
|
-
var init_theme = __esm({
|
|
6527
|
-
"src/tui/theme.ts"() {
|
|
6528
|
-
"use strict";
|
|
6529
|
-
init_config();
|
|
6530
|
-
accent = loadConfig().accent;
|
|
6531
|
-
theme = {
|
|
6532
|
-
accent: accent || "white",
|
|
6533
|
-
accentBright: accent || "white",
|
|
6534
|
-
ok: "green",
|
|
6535
|
-
warn: "yellow",
|
|
6536
|
-
bad: "red",
|
|
6537
|
-
dim: "gray"
|
|
6538
|
-
};
|
|
6539
|
-
}
|
|
6540
|
-
});
|
|
6541
|
-
|
|
6542
|
-
// src/tui/components.tsx
|
|
6543
|
-
import { Box, Text } from "ink";
|
|
6544
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6545
|
-
function PaneTitle({ label, extra, width: width2 }) {
|
|
6546
|
-
const tail = extra ? ` ${extra} ` : " ";
|
|
6547
|
-
const used = 2 + 1 + label.length + tail.length;
|
|
6548
|
-
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
|
|
6549
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: "\u2500 " }),
|
|
6550
|
-
/* @__PURE__ */ jsxs(Text, { color: theme.accentBright, bold: true, children: [
|
|
6551
|
-
"\u258E",
|
|
6552
|
-
label
|
|
6553
|
-
] }),
|
|
6554
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
|
|
6555
|
-
] });
|
|
6556
|
-
}
|
|
6557
|
-
function StreamLine({ e, loc, selected, date }) {
|
|
6558
|
-
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
|
|
6559
|
-
/* @__PURE__ */ jsx(Text, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
|
|
6560
|
-
/* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
6561
|
-
"[",
|
|
6562
|
-
date ? ymd(e.at) + " " : "",
|
|
6563
|
-
hhmmss(e.at),
|
|
6564
|
-
" "
|
|
6565
|
-
] }),
|
|
6566
|
-
/* @__PURE__ */ jsx(Text, { color: loc ? theme.ok : "white", children: loc ? "LOC" : "CLD" }),
|
|
6567
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: "] " }),
|
|
6568
|
-
/* @__PURE__ */ jsx(Text, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
|
|
6569
|
-
/* @__PURE__ */ jsx(Text, { color: theme.accent, children: truncate2(e.tool, 12).padEnd(13) }),
|
|
6570
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: (e.permission ?? "").padEnd(5) }),
|
|
6571
|
-
/* @__PURE__ */ jsx(Text, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
|
|
6572
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncate2(e.agent ?? "-", 11).padEnd(12) }),
|
|
6573
|
-
/* @__PURE__ */ jsx(Text, { color: e.dlp ? theme.bad : theme.dim, bold: e.dlp, children: ("dlp:" + (e.dlp ? "yes" : "no")).padEnd(8) }),
|
|
6574
|
-
/* @__PURE__ */ jsx(Text, { color: e.burst ? theme.warn : theme.dim, bold: e.burst, children: ("rl:" + (e.burst ? "yes" : "no")).padEnd(7) }),
|
|
6575
|
-
e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx(Text, { color: theme.bad, children: truncate2(e.rule, 14) + " " }) : null,
|
|
6576
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: e.detail })
|
|
6577
|
-
] });
|
|
6578
|
-
}
|
|
6579
|
-
function DataView({
|
|
6580
|
-
loading,
|
|
6581
|
-
error,
|
|
6582
|
-
empty,
|
|
6583
|
-
emptyText,
|
|
6584
|
-
children
|
|
6585
|
-
}) {
|
|
6586
|
-
if (error) return /* @__PURE__ */ jsxs(Text, { color: theme.bad, children: [
|
|
6587
|
-
"\u2717 ",
|
|
6588
|
-
error
|
|
6589
|
-
] });
|
|
6590
|
-
if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u27F3 loading\u2026" });
|
|
6591
|
-
if (empty) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: emptyText ?? "No data." });
|
|
6592
|
-
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
6593
|
-
}
|
|
6594
|
-
function Table({ columns, rows }) {
|
|
6595
|
-
const fit = (s, w) => {
|
|
6596
|
-
const v = s ?? "";
|
|
6597
|
-
if (v.length > w) return v.slice(0, Math.max(0, w - 1)) + "\u2026";
|
|
6598
|
-
return v.padEnd(w);
|
|
6599
|
-
};
|
|
6600
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
6601
|
-
/* @__PURE__ */ jsx(Box, { children: columns.map((c2, i) => /* @__PURE__ */ jsx(Text, { color: theme.dim, children: fit(c2.header, c2.width) + " " }, i)) }),
|
|
6602
|
-
rows.map((row, ri) => /* @__PURE__ */ jsx(Box, { children: row.map((cell, ci) => /* @__PURE__ */ jsx(Text, { color: cell.color, dimColor: cell.dim, bold: cell.bold, children: fit(cell.value, columns[ci]?.width ?? 10) + " " }, ci)) }, ri))
|
|
6603
|
-
] });
|
|
6604
|
-
}
|
|
6605
|
-
function KeyHints({ hints }) {
|
|
6606
|
-
return /* @__PURE__ */ jsx(Box, { children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
6607
|
-
/* @__PURE__ */ jsx(Text, { color: theme.accent, children: key }),
|
|
6608
|
-
" ",
|
|
6609
|
-
label,
|
|
6610
|
-
i < hints.length - 1 ? " " : ""
|
|
6611
|
-
] }, i)) });
|
|
6612
|
-
}
|
|
6613
|
-
var hhmmss, ymd;
|
|
6614
|
-
var init_components = __esm({
|
|
6615
|
-
"src/tui/components.tsx"() {
|
|
6616
|
-
"use strict";
|
|
6617
|
-
init_theme();
|
|
6618
|
-
hhmmss = (ts) => {
|
|
6619
|
-
const d = new Date(ts);
|
|
6620
|
-
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
|
6621
|
-
};
|
|
6622
|
-
ymd = (ts) => {
|
|
6623
|
-
const d = new Date(ts);
|
|
6624
|
-
if (Number.isNaN(d.getTime())) return "----------";
|
|
6625
|
-
const p = (n) => String(n).padStart(2, "0");
|
|
6626
|
-
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
6627
|
-
};
|
|
6628
|
-
}
|
|
6629
|
-
});
|
|
6630
|
-
|
|
6631
|
-
// src/tui/local-log.ts
|
|
6632
|
-
import { closeSync, openSync as openSync3, readFileSync as readFileSync7, readSync, statSync, writeFileSync as writeFileSync5 } from "fs";
|
|
6633
|
-
import { homedir as homedir5 } from "os";
|
|
6634
|
-
import { join as join7 } from "path";
|
|
6635
|
-
function tailLines(file, maxBytes = 131072) {
|
|
6710
|
+
async function fetchLatest() {
|
|
6636
6711
|
try {
|
|
6637
|
-
const
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
const
|
|
6644
|
-
if (
|
|
6645
|
-
return
|
|
6712
|
+
const res = await fetch(`https://registry.npmjs.org/${PKG}`, {
|
|
6713
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
6714
|
+
signal: AbortSignal.timeout(3e3)
|
|
6715
|
+
});
|
|
6716
|
+
if (!res.ok) return null;
|
|
6717
|
+
const j = await res.json();
|
|
6718
|
+
const latest = j["dist-tags"]?.latest;
|
|
6719
|
+
if (typeof latest !== "string") return null;
|
|
6720
|
+
if (!j.versions || !(latest in j.versions)) return null;
|
|
6721
|
+
return latest;
|
|
6646
6722
|
} catch {
|
|
6647
|
-
return
|
|
6723
|
+
return null;
|
|
6648
6724
|
}
|
|
6649
6725
|
}
|
|
6650
|
-
function
|
|
6726
|
+
function spawnGlobalInstall(version) {
|
|
6651
6727
|
try {
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
const
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
const hit = Date.parse(j.ts ?? "") === at && (j.tool ?? "?") === tool && (!session || j.session_id === session);
|
|
6660
|
-
if (hit) {
|
|
6661
|
-
removed++;
|
|
6662
|
-
return false;
|
|
6663
|
-
}
|
|
6664
|
-
} catch {
|
|
6665
|
-
}
|
|
6666
|
-
return true;
|
|
6728
|
+
mkdirSync4(join5(homedir3(), ".solongate"), { recursive: true });
|
|
6729
|
+
const log3 = openSync(LOG_FILE, "a");
|
|
6730
|
+
const p = spawn("npm", ["install", "-g", `${PKG}@${version}`], {
|
|
6731
|
+
stdio: ["ignore", log3, log3],
|
|
6732
|
+
detached: true,
|
|
6733
|
+
windowsHide: true,
|
|
6734
|
+
shell: process.platform === "win32"
|
|
6667
6735
|
});
|
|
6668
|
-
|
|
6669
|
-
|
|
6736
|
+
p.on("error", () => {
|
|
6737
|
+
});
|
|
6738
|
+
p.unref();
|
|
6739
|
+
return true;
|
|
6670
6740
|
} catch {
|
|
6671
|
-
return
|
|
6741
|
+
return false;
|
|
6672
6742
|
}
|
|
6673
6743
|
}
|
|
6674
|
-
function
|
|
6744
|
+
async function runUpdateCommand() {
|
|
6745
|
+
const out2 = (s) => void process.stderr.write(s + "\n");
|
|
6746
|
+
const cur = currentVersion();
|
|
6747
|
+
out2(` current ${cur}`);
|
|
6748
|
+
const latest = await latestVersion();
|
|
6749
|
+
if (!latest) {
|
|
6750
|
+
out2(" could not reach the npm registry. Try again, or: npm i -g @solongate/proxy@latest");
|
|
6751
|
+
return 1;
|
|
6752
|
+
}
|
|
6753
|
+
if (newerThan(latest, cur)) {
|
|
6754
|
+
out2(` updating ${cur} -> ${latest} ...`);
|
|
6755
|
+
const ok = await runGlobalInstall2(latest);
|
|
6756
|
+
if (!ok) {
|
|
6757
|
+
out2(" update failed. Try: npm i -g @solongate/proxy@latest");
|
|
6758
|
+
return 1;
|
|
6759
|
+
}
|
|
6760
|
+
out2(` \u2713 updated to ${latest} (open a new session to use it)`);
|
|
6761
|
+
} else {
|
|
6762
|
+
out2(` \u2713 already up to date`);
|
|
6763
|
+
}
|
|
6675
6764
|
try {
|
|
6676
|
-
const
|
|
6677
|
-
|
|
6678
|
-
|
|
6765
|
+
const { installGlobalQuiet: installGlobalQuiet2, installedGuardVersion: installedGuardVersion2 } = await Promise.resolve().then(() => (init_global_install(), global_install_exports));
|
|
6766
|
+
const r = installGlobalQuiet2();
|
|
6767
|
+
out2(r.ok ? ` \u2713 guard hooks refreshed (v${installedGuardVersion2() ?? "?"})` : ` guard: ${r.message}`);
|
|
6679
6768
|
} catch {
|
|
6680
|
-
return 0;
|
|
6681
|
-
}
|
|
6682
|
-
}
|
|
6683
|
-
function reasonSignals(reason) {
|
|
6684
|
-
const r = reason || "";
|
|
6685
|
-
const dlp = [];
|
|
6686
|
-
if (DLP_REASON.test(r)) {
|
|
6687
|
-
const m = r.match(/contain(?:s)? (?:a |an )?(.+?)(?:\.|$)/i);
|
|
6688
|
-
dlp.push(m ? m[1].trim() : "DLP");
|
|
6689
6769
|
}
|
|
6690
|
-
return
|
|
6770
|
+
return 0;
|
|
6691
6771
|
}
|
|
6692
|
-
function
|
|
6693
|
-
|
|
6694
|
-
for (const line of lines) {
|
|
6772
|
+
function runGlobalInstall2(version) {
|
|
6773
|
+
return new Promise((resolve6) => {
|
|
6695
6774
|
try {
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6775
|
+
mkdirSync4(join5(homedir3(), ".solongate"), { recursive: true });
|
|
6776
|
+
execFile(
|
|
6777
|
+
"npm",
|
|
6778
|
+
["install", "-g", `${PKG}@${version}`],
|
|
6779
|
+
{ timeout: 3e5, windowsHide: true, shell: process.platform === "win32" },
|
|
6780
|
+
(err2, stdout, stderr) => {
|
|
6781
|
+
try {
|
|
6782
|
+
writeFileSync4(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} install ${version}: ${err2 ? "FAILED" : "ok"}
|
|
6783
|
+
${stdout}
|
|
6784
|
+
${stderr}
|
|
6785
|
+
`, { flag: "a" });
|
|
6786
|
+
} catch {
|
|
6787
|
+
}
|
|
6788
|
+
resolve6(!err2);
|
|
6789
|
+
}
|
|
6790
|
+
);
|
|
6699
6791
|
} catch {
|
|
6792
|
+
resolve6(false);
|
|
6700
6793
|
}
|
|
6701
|
-
}
|
|
6702
|
-
return out2;
|
|
6794
|
+
});
|
|
6703
6795
|
}
|
|
6704
|
-
|
|
6705
|
-
var init_local_log = __esm({
|
|
6706
|
-
"src/tui/local-log.ts"() {
|
|
6707
|
-
"use strict";
|
|
6708
|
-
LOCAL_LOG = join7(homedir5(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
6709
|
-
DLP_REASON = /security layer \(dlp\)/i;
|
|
6710
|
-
RL_REASON = /security layer \(rate limit\)|rate[- ]?limit(?:ed)?\b.*exceed|exceeded \d+ calls/i;
|
|
6711
|
-
}
|
|
6712
|
-
});
|
|
6713
|
-
|
|
6714
|
-
// src/api-client/client.ts
|
|
6715
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync3 } from "fs";
|
|
6716
|
-
import { resolve as resolve3, join as join8 } from "path";
|
|
6717
|
-
import { homedir as homedir6 } from "os";
|
|
6718
|
-
function listAccounts() {
|
|
6719
|
-
let list5 = [];
|
|
6796
|
+
async function tuiUpdateFlow(onStatus) {
|
|
6720
6797
|
try {
|
|
6721
|
-
const
|
|
6722
|
-
|
|
6798
|
+
const current = currentVersion();
|
|
6799
|
+
const latest = await fetchLatest();
|
|
6800
|
+
const state = readState();
|
|
6801
|
+
if (latest) writeState({ ...state, lastCheckAt: Date.now(), latestSeen: latest });
|
|
6802
|
+
if (!latest || !newerThan(latest, current)) return;
|
|
6803
|
+
if (readState().installed === latest) {
|
|
6804
|
+
onStatus({ kind: "updated", version: latest });
|
|
6805
|
+
return;
|
|
6806
|
+
}
|
|
6807
|
+
onStatus({ kind: "updating", version: latest });
|
|
6808
|
+
if (await runGlobalInstall2(latest)) {
|
|
6809
|
+
writeState({ ...readState(), installed: latest });
|
|
6810
|
+
onStatus({ kind: "updated", version: latest });
|
|
6811
|
+
}
|
|
6723
6812
|
} catch {
|
|
6724
6813
|
}
|
|
6725
|
-
const active2 = loginCredentialFile();
|
|
6726
|
-
if (active2.apiKey && !list5.some((a) => a.apiKey === active2.apiKey)) {
|
|
6727
|
-
list5.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL2 });
|
|
6728
|
-
}
|
|
6729
|
-
return list5;
|
|
6730
6814
|
}
|
|
6731
|
-
function
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6815
|
+
function maybeSelfUpdate(notify = (l) => process.stderr.write(l + "\n")) {
|
|
6816
|
+
void (async () => {
|
|
6817
|
+
try {
|
|
6818
|
+
const state = readState();
|
|
6819
|
+
const now = Date.now();
|
|
6820
|
+
const current = currentVersion();
|
|
6821
|
+
let latest = state.latestSeen ?? null;
|
|
6822
|
+
if (now - (state.lastCheckAt ?? 0) >= CHECK_EVERY_MS) {
|
|
6823
|
+
latest = await fetchLatest();
|
|
6824
|
+
if (latest) writeState({ ...state, lastCheckAt: now, latestSeen: latest });
|
|
6825
|
+
else writeState({ ...state, lastCheckAt: now });
|
|
6826
|
+
}
|
|
6827
|
+
if (!latest || !newerThan(latest, current)) return;
|
|
6828
|
+
const attempts = readState().attempts ?? {};
|
|
6829
|
+
if (now - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS) {
|
|
6830
|
+
writeState({ ...readState(), attempts: { [latest]: now } });
|
|
6831
|
+
if (spawnGlobalInstall(latest)) {
|
|
6832
|
+
notify(`${c.dim}\u2191 solongate v${latest} available (running v${current}) \u2014 updating in the background, next run uses it${c.reset}`);
|
|
6833
|
+
}
|
|
6834
|
+
}
|
|
6835
|
+
} catch {
|
|
6836
|
+
}
|
|
6837
|
+
})();
|
|
6838
|
+
}
|
|
6839
|
+
var PKG, CHECK_EVERY_MS, ATTEMPT_EVERY_MS, STATE_FILE, LOG_FILE;
|
|
6840
|
+
var init_self_update = __esm({
|
|
6841
|
+
"src/self-update.ts"() {
|
|
6842
|
+
"use strict";
|
|
6843
|
+
init_cli_utils();
|
|
6844
|
+
PKG = "@solongate/proxy";
|
|
6845
|
+
CHECK_EVERY_MS = 30 * 60 * 1e3;
|
|
6846
|
+
ATTEMPT_EVERY_MS = 6 * 60 * 60 * 1e3;
|
|
6847
|
+
STATE_FILE = join5(homedir3(), ".solongate", ".self-update.json");
|
|
6848
|
+
LOG_FILE = join5(homedir3(), ".solongate", "self-update.log");
|
|
6849
|
+
}
|
|
6850
|
+
});
|
|
6851
|
+
|
|
6852
|
+
// src/logs-server-daemon.ts
|
|
6853
|
+
var logs_server_daemon_exports = {};
|
|
6854
|
+
__export(logs_server_daemon_exports, {
|
|
6855
|
+
LOGS_SERVER_PORT: () => LOGS_SERVER_PORT,
|
|
6856
|
+
ensureLogsServerDaemon: () => ensureLogsServerDaemon,
|
|
6857
|
+
logsServerStatus: () => logsServerStatus,
|
|
6858
|
+
recordLogsServerStarted: () => recordLogsServerStarted,
|
|
6859
|
+
startLogsServerDaemon: () => startLogsServerDaemon,
|
|
6860
|
+
stopLogsServerDaemon: () => stopLogsServerDaemon
|
|
6861
|
+
});
|
|
6862
|
+
import { spawn as spawn2 } from "child_process";
|
|
6863
|
+
import { mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
6864
|
+
import { homedir as homedir4 } from "os";
|
|
6865
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
6866
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6867
|
+
function readState2() {
|
|
6868
|
+
try {
|
|
6869
|
+
const s = JSON.parse(readFileSync6(STATE_FILE2, "utf-8"));
|
|
6870
|
+
return s && typeof s === "object" ? s : {};
|
|
6745
6871
|
} catch {
|
|
6872
|
+
return {};
|
|
6746
6873
|
}
|
|
6747
6874
|
}
|
|
6748
|
-
function
|
|
6875
|
+
function writeState2(s) {
|
|
6749
6876
|
try {
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
const raw = JSON.parse(readFileSync8(accountsFile(), "utf-8"));
|
|
6753
|
-
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
6754
|
-
} catch {
|
|
6755
|
-
return [];
|
|
6756
|
-
}
|
|
6757
|
-
})();
|
|
6758
|
-
writeFileSync6(accountsFile(), JSON.stringify(list5.filter((a) => a.apiKey !== apiKey), null, 2));
|
|
6877
|
+
mkdirSync5(DIR, { recursive: true });
|
|
6878
|
+
writeFileSync5(STATE_FILE2, JSON.stringify(s));
|
|
6759
6879
|
} catch {
|
|
6760
6880
|
}
|
|
6761
6881
|
}
|
|
6762
|
-
function
|
|
6763
|
-
|
|
6764
|
-
|
|
6882
|
+
function pidAlive(pid) {
|
|
6883
|
+
if (!pid || pid <= 0) return false;
|
|
6884
|
+
try {
|
|
6885
|
+
process.kill(pid, 0);
|
|
6886
|
+
return true;
|
|
6887
|
+
} catch (e) {
|
|
6888
|
+
return e.code === "EPERM";
|
|
6889
|
+
}
|
|
6765
6890
|
}
|
|
6766
|
-
function
|
|
6767
|
-
|
|
6891
|
+
function logsServerStatus() {
|
|
6892
|
+
const s = readState2();
|
|
6893
|
+
const running = pidAlive(s.pid);
|
|
6894
|
+
return { desired: s.desired === "on" ? "on" : "off", running, pid: running ? s.pid : void 0, port: s.port || LOGS_SERVER_PORT };
|
|
6768
6895
|
}
|
|
6769
|
-
function
|
|
6896
|
+
function recordLogsServerStarted(port) {
|
|
6897
|
+
writeState2({ desired: "on", pid: process.pid, port, startedAt: Date.now() });
|
|
6898
|
+
}
|
|
6899
|
+
function startLogsServerDaemon() {
|
|
6900
|
+
const cur = logsServerStatus();
|
|
6901
|
+
if (cur.running) {
|
|
6902
|
+
writeState2({ ...readState2(), desired: "on" });
|
|
6903
|
+
return { ...cur, desired: "on" };
|
|
6904
|
+
}
|
|
6770
6905
|
try {
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
const
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
}
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6906
|
+
mkdirSync5(DIR, { recursive: true });
|
|
6907
|
+
const log3 = openSync2(LOG_FILE2, "a");
|
|
6908
|
+
const cli = join6(dirname3(fileURLToPath3(import.meta.url)), "index.js");
|
|
6909
|
+
const p = spawn2(process.execPath, [cli, "logs-server"], {
|
|
6910
|
+
detached: true,
|
|
6911
|
+
stdio: ["ignore", log3, log3],
|
|
6912
|
+
windowsHide: true
|
|
6913
|
+
});
|
|
6914
|
+
p.on("error", () => {
|
|
6915
|
+
});
|
|
6916
|
+
p.unref();
|
|
6917
|
+
writeState2({ desired: "on", pid: p.pid, port: LOGS_SERVER_PORT, startedAt: Date.now() });
|
|
6918
|
+
return { desired: "on", running: true, pid: p.pid, port: LOGS_SERVER_PORT };
|
|
6782
6919
|
} catch {
|
|
6783
|
-
return false;
|
|
6920
|
+
return { ...cur, desired: "on", running: false };
|
|
6784
6921
|
}
|
|
6785
6922
|
}
|
|
6786
|
-
function
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
if (!existsSync3(p)) {
|
|
6790
|
-
cached2 = null;
|
|
6791
|
-
return true;
|
|
6792
|
-
}
|
|
6793
|
-
let existing = {};
|
|
6923
|
+
function stopLogsServerDaemon() {
|
|
6924
|
+
const s = readState2();
|
|
6925
|
+
if (pidAlive(s.pid)) {
|
|
6794
6926
|
try {
|
|
6795
|
-
|
|
6927
|
+
process.kill(s.pid);
|
|
6796
6928
|
} catch {
|
|
6797
6929
|
}
|
|
6798
|
-
delete existing.apiKey;
|
|
6799
|
-
delete existing.apiUrl;
|
|
6800
|
-
writeFileSync6(p, JSON.stringify(existing, null, 2));
|
|
6801
|
-
cached2 = null;
|
|
6802
|
-
viewOverride = null;
|
|
6803
|
-
return true;
|
|
6804
|
-
} catch {
|
|
6805
|
-
return false;
|
|
6806
6930
|
}
|
|
6931
|
+
writeState2({ desired: "off", port: s.port });
|
|
6932
|
+
return { desired: "off", running: false, port: s.port || LOGS_SERVER_PORT };
|
|
6807
6933
|
}
|
|
6808
|
-
function
|
|
6934
|
+
function ensureLogsServerDaemon() {
|
|
6809
6935
|
try {
|
|
6810
|
-
const
|
|
6811
|
-
if (!
|
|
6812
|
-
const c2 = JSON.parse(readFileSync8(p, "utf-8"));
|
|
6813
|
-
return c2 && typeof c2 === "object" ? c2 : {};
|
|
6936
|
+
const s = logsServerStatus();
|
|
6937
|
+
if (s.desired === "on" && !s.running) startLogsServerDaemon();
|
|
6814
6938
|
} catch {
|
|
6815
|
-
return {};
|
|
6816
6939
|
}
|
|
6817
6940
|
}
|
|
6818
|
-
|
|
6941
|
+
var DIR, STATE_FILE2, LOG_FILE2, LOGS_SERVER_PORT;
|
|
6942
|
+
var init_logs_server_daemon = __esm({
|
|
6943
|
+
"src/logs-server-daemon.ts"() {
|
|
6944
|
+
"use strict";
|
|
6945
|
+
DIR = join6(homedir4(), ".solongate");
|
|
6946
|
+
STATE_FILE2 = join6(DIR, ".logs-server.json");
|
|
6947
|
+
LOG_FILE2 = join6(DIR, "logs-server.log");
|
|
6948
|
+
LOGS_SERVER_PORT = 8788;
|
|
6949
|
+
}
|
|
6950
|
+
});
|
|
6951
|
+
|
|
6952
|
+
// src/tui/config.ts
|
|
6953
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
6954
|
+
import { homedir as homedir5 } from "os";
|
|
6955
|
+
import { join as join7 } from "path";
|
|
6956
|
+
function loadConfig() {
|
|
6957
|
+
if (cached) return cached;
|
|
6958
|
+
const defaults = { notifications: true };
|
|
6819
6959
|
try {
|
|
6820
|
-
const
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
const key = trimmed.slice(0, eq).trim();
|
|
6828
|
-
if (key !== "SOLONGATE_API_KEY") continue;
|
|
6829
|
-
return trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
6830
|
-
}
|
|
6960
|
+
const raw = readFileSync7(join7(homedir5(), ".solongate", "tui-config.json"), "utf-8");
|
|
6961
|
+
const j = JSON.parse(raw);
|
|
6962
|
+
cached = {
|
|
6963
|
+
notifications: j.notifications !== false,
|
|
6964
|
+
accent: typeof j.accent === "string" ? j.accent : void 0,
|
|
6965
|
+
pollMs: typeof j.pollMs === "number" ? j.pollMs : void 0
|
|
6966
|
+
};
|
|
6831
6967
|
} catch {
|
|
6968
|
+
cached = defaults;
|
|
6832
6969
|
}
|
|
6833
|
-
return
|
|
6970
|
+
return cached;
|
|
6834
6971
|
}
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
return false;
|
|
6972
|
+
var cached;
|
|
6973
|
+
var init_config = __esm({
|
|
6974
|
+
"src/tui/config.ts"() {
|
|
6975
|
+
"use strict";
|
|
6976
|
+
cached = null;
|
|
6841
6977
|
}
|
|
6978
|
+
});
|
|
6979
|
+
|
|
6980
|
+
// src/tui/theme.ts
|
|
6981
|
+
function decisionColor(decision) {
|
|
6982
|
+
const d = (decision || "").toUpperCase();
|
|
6983
|
+
if (d === "ALLOW") return theme.ok;
|
|
6984
|
+
if (d === "DENY" || d === "DENIED") return theme.bad;
|
|
6985
|
+
return theme.dim;
|
|
6842
6986
|
}
|
|
6843
|
-
function
|
|
6844
|
-
if (
|
|
6845
|
-
if (
|
|
6846
|
-
|
|
6847
|
-
const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
|
|
6848
|
-
if (!apiKey) throw new NotAuthenticatedError();
|
|
6849
|
-
const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL2;
|
|
6850
|
-
const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
|
|
6851
|
-
if (!apiUrlOverride) cached2 = creds;
|
|
6852
|
-
return creds;
|
|
6987
|
+
function modeColor(m) {
|
|
6988
|
+
if (m === "block" || m === "active" || m === "on") return theme.ok;
|
|
6989
|
+
if (m === "detect" || m === "idle") return theme.warn;
|
|
6990
|
+
return theme.dim;
|
|
6853
6991
|
}
|
|
6854
|
-
function
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
url.searchParams.set(k, String(v));
|
|
6860
|
-
}
|
|
6992
|
+
function prettyJson(s) {
|
|
6993
|
+
try {
|
|
6994
|
+
return JSON.stringify(JSON.parse(s), null, 2);
|
|
6995
|
+
} catch {
|
|
6996
|
+
return s;
|
|
6861
6997
|
}
|
|
6862
|
-
return url.toString();
|
|
6863
6998
|
}
|
|
6864
|
-
|
|
6865
|
-
const
|
|
6866
|
-
const
|
|
6867
|
-
const
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
if (opts.body !== void 0) {
|
|
6872
|
-
headers["Content-Type"] = "application/json";
|
|
6873
|
-
bodyInit = JSON.stringify(opts.body);
|
|
6874
|
-
}
|
|
6875
|
-
const attempt = () => fetch(url, {
|
|
6876
|
-
method,
|
|
6877
|
-
headers: { ...headers, Connection: "close" },
|
|
6878
|
-
body: bodyInit,
|
|
6879
|
-
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
6880
|
-
});
|
|
6881
|
-
const maxTries = method === "GET" ? 3 : 1;
|
|
6882
|
-
let res;
|
|
6883
|
-
let lastErr;
|
|
6884
|
-
for (let i = 0; i < maxTries; i++) {
|
|
6885
|
-
try {
|
|
6886
|
-
res = await attempt();
|
|
6887
|
-
break;
|
|
6888
|
-
} catch (err2) {
|
|
6889
|
-
lastErr = err2;
|
|
6890
|
-
if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
|
|
6999
|
+
function wrapLines(s, width2) {
|
|
7000
|
+
const w = Math.max(8, width2);
|
|
7001
|
+
const out2 = [];
|
|
7002
|
+
for (const raw of (s ?? "").split("\n")) {
|
|
7003
|
+
if (raw.length <= w) {
|
|
7004
|
+
out2.push(raw);
|
|
7005
|
+
continue;
|
|
6891
7006
|
}
|
|
7007
|
+
for (let i = 0; i < raw.length; i += w) out2.push(raw.slice(i, i + w));
|
|
6892
7008
|
}
|
|
6893
|
-
|
|
6894
|
-
const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
6895
|
-
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
6896
|
-
}
|
|
6897
|
-
const text = await res.text().catch(() => "");
|
|
6898
|
-
let json = void 0;
|
|
6899
|
-
if (text) {
|
|
6900
|
-
try {
|
|
6901
|
-
json = JSON.parse(text);
|
|
6902
|
-
} catch {
|
|
6903
|
-
}
|
|
6904
|
-
}
|
|
6905
|
-
if (!res.ok) {
|
|
6906
|
-
const envelope = json?.error;
|
|
6907
|
-
if (envelope && typeof envelope === "object") {
|
|
6908
|
-
throw new ApiError(res.status, envelope.code || "ERROR", envelope.message || res.statusText);
|
|
6909
|
-
}
|
|
6910
|
-
if (typeof envelope === "string") {
|
|
6911
|
-
throw new ApiError(res.status, "ERROR", envelope);
|
|
6912
|
-
}
|
|
6913
|
-
if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate` and log in from the Accounts panel.");
|
|
6914
|
-
if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
|
|
6915
|
-
if (res.status >= 500) throw new ApiError(res.status, "SERVER_ERROR", text || "SolonGate API had a problem (server error). Please try again in a moment.");
|
|
6916
|
-
throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
|
|
6917
|
-
}
|
|
6918
|
-
return json;
|
|
7009
|
+
return out2;
|
|
6919
7010
|
}
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
7011
|
+
function truncate2(s, n) {
|
|
7012
|
+
if (!s) return "";
|
|
7013
|
+
return s.length <= n ? s : s.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
7014
|
+
}
|
|
7015
|
+
function ago(ts) {
|
|
7016
|
+
const t = typeof ts === "number" ? ts : Date.parse(ts);
|
|
7017
|
+
if (Number.isNaN(t)) return String(ts ?? "");
|
|
7018
|
+
const s = Math.max(0, (Date.now() - t) / 1e3);
|
|
7019
|
+
if (s < 60) return `${Math.floor(s)}s`;
|
|
7020
|
+
if (s < 3600) return `${Math.floor(s / 60)}m`;
|
|
7021
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h`;
|
|
7022
|
+
return `${Math.floor(s / 86400)}d`;
|
|
7023
|
+
}
|
|
7024
|
+
var accent, theme;
|
|
7025
|
+
var init_theme = __esm({
|
|
7026
|
+
"src/tui/theme.ts"() {
|
|
6923
7027
|
"use strict";
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
this.status = status;
|
|
6934
|
-
this.code = code;
|
|
6935
|
-
}
|
|
6936
|
-
};
|
|
6937
|
-
NotAuthenticatedError = class extends Error {
|
|
6938
|
-
constructor() {
|
|
6939
|
-
super("Not logged in. Run `solongate` and log in from the Accounts panel.");
|
|
6940
|
-
this.name = "NotAuthenticatedError";
|
|
6941
|
-
}
|
|
7028
|
+
init_config();
|
|
7029
|
+
accent = loadConfig().accent;
|
|
7030
|
+
theme = {
|
|
7031
|
+
accent: accent || "white",
|
|
7032
|
+
accentBright: accent || "white",
|
|
7033
|
+
ok: "green",
|
|
7034
|
+
warn: "yellow",
|
|
7035
|
+
bad: "red",
|
|
7036
|
+
dim: "gray"
|
|
6942
7037
|
};
|
|
6943
|
-
cached2 = null;
|
|
6944
7038
|
}
|
|
6945
7039
|
});
|
|
6946
7040
|
|
|
6947
|
-
// src/
|
|
6948
|
-
|
|
6949
|
-
|
|
7041
|
+
// src/tui/components.tsx
|
|
7042
|
+
import { Box, Text } from "ink";
|
|
7043
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7044
|
+
function PaneTitle({ label, extra, width: width2 }) {
|
|
7045
|
+
const tail = extra ? ` ${extra} ` : " ";
|
|
7046
|
+
const used = 2 + 1 + label.length + tail.length;
|
|
7047
|
+
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
|
|
7048
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: "\u2500 " }),
|
|
7049
|
+
/* @__PURE__ */ jsxs(Text, { color: theme.accentBright, bold: true, children: [
|
|
7050
|
+
"\u258E",
|
|
7051
|
+
label
|
|
7052
|
+
] }),
|
|
7053
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
|
|
7054
|
+
] });
|
|
7055
|
+
}
|
|
7056
|
+
function StreamLine({ e, loc, selected, date }) {
|
|
7057
|
+
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
|
|
7058
|
+
/* @__PURE__ */ jsx(Text, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
|
|
7059
|
+
/* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
7060
|
+
"[",
|
|
7061
|
+
date ? ymd(e.at) + " " : "",
|
|
7062
|
+
hhmmss(e.at),
|
|
7063
|
+
" "
|
|
7064
|
+
] }),
|
|
7065
|
+
/* @__PURE__ */ jsx(Text, { color: loc ? theme.ok : "white", children: loc ? "LOC" : "CLD" }),
|
|
7066
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: "] " }),
|
|
7067
|
+
/* @__PURE__ */ jsx(Text, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
|
|
7068
|
+
/* @__PURE__ */ jsx(Text, { color: theme.accent, children: truncate2(e.tool, 12).padEnd(13) }),
|
|
7069
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: (e.permission ?? "").padEnd(5) }),
|
|
7070
|
+
/* @__PURE__ */ jsx(Text, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
|
|
7071
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncate2(e.agent ?? "-", 11).padEnd(12) }),
|
|
7072
|
+
/* @__PURE__ */ jsx(Text, { color: e.dlp ? theme.bad : theme.dim, bold: e.dlp, children: ("dlp:" + (e.dlp ? "yes" : "no")).padEnd(8) }),
|
|
7073
|
+
/* @__PURE__ */ jsx(Text, { color: e.burst ? theme.warn : theme.dim, bold: e.burst, children: ("rl:" + (e.burst ? "yes" : "no")).padEnd(7) }),
|
|
7074
|
+
e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx(Text, { color: theme.bad, children: truncate2(e.rule, 14) + " " }) : null,
|
|
7075
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: e.detail })
|
|
7076
|
+
] });
|
|
7077
|
+
}
|
|
7078
|
+
function DataView({
|
|
7079
|
+
loading,
|
|
7080
|
+
error,
|
|
7081
|
+
empty,
|
|
7082
|
+
emptyText,
|
|
7083
|
+
children
|
|
7084
|
+
}) {
|
|
7085
|
+
if (error) return /* @__PURE__ */ jsxs(Text, { color: theme.bad, children: [
|
|
7086
|
+
"\u2717 ",
|
|
7087
|
+
error
|
|
7088
|
+
] });
|
|
7089
|
+
if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u27F3 loading\u2026" });
|
|
7090
|
+
if (empty) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: emptyText ?? "No data." });
|
|
7091
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
7092
|
+
}
|
|
7093
|
+
function Table({ columns, rows }) {
|
|
7094
|
+
const fit = (s, w) => {
|
|
7095
|
+
const v = s ?? "";
|
|
7096
|
+
if (v.length > w) return v.slice(0, Math.max(0, w - 1)) + "\u2026";
|
|
7097
|
+
return v.padEnd(w);
|
|
7098
|
+
};
|
|
7099
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
7100
|
+
/* @__PURE__ */ jsx(Box, { children: columns.map((c2, i) => /* @__PURE__ */ jsx(Text, { color: theme.dim, children: fit(c2.header, c2.width) + " " }, i)) }),
|
|
7101
|
+
rows.map((row, ri) => /* @__PURE__ */ jsx(Box, { children: row.map((cell, ci) => /* @__PURE__ */ jsx(Text, { color: cell.color, dimColor: cell.dim, bold: cell.bold, children: fit(cell.value, columns[ci]?.width ?? 10) + " " }, ci)) }, ri))
|
|
7102
|
+
] });
|
|
7103
|
+
}
|
|
7104
|
+
function KeyHints({ hints }) {
|
|
7105
|
+
return /* @__PURE__ */ jsx(Box, { children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
7106
|
+
/* @__PURE__ */ jsx(Text, { color: theme.accent, children: key }),
|
|
7107
|
+
" ",
|
|
7108
|
+
label,
|
|
7109
|
+
i < hints.length - 1 ? " " : ""
|
|
7110
|
+
] }, i)) });
|
|
7111
|
+
}
|
|
7112
|
+
var hhmmss, ymd;
|
|
7113
|
+
var init_components = __esm({
|
|
7114
|
+
"src/tui/components.tsx"() {
|
|
6950
7115
|
"use strict";
|
|
7116
|
+
init_theme();
|
|
7117
|
+
hhmmss = (ts) => {
|
|
7118
|
+
const d = new Date(ts);
|
|
7119
|
+
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
|
7120
|
+
};
|
|
7121
|
+
ymd = (ts) => {
|
|
7122
|
+
const d = new Date(ts);
|
|
7123
|
+
if (Number.isNaN(d.getTime())) return "----------";
|
|
7124
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
7125
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
7126
|
+
};
|
|
6951
7127
|
}
|
|
6952
7128
|
});
|
|
6953
7129
|
|
|
6954
|
-
// src/
|
|
6955
|
-
import {
|
|
6956
|
-
|
|
7130
|
+
// src/tui/local-log.ts
|
|
7131
|
+
import { closeSync, openSync as openSync3, readFileSync as readFileSync8, readSync, statSync, writeFileSync as writeFileSync6 } from "fs";
|
|
7132
|
+
import { homedir as homedir6 } from "os";
|
|
7133
|
+
import { join as join8 } from "path";
|
|
7134
|
+
function tailLines(file, maxBytes = 131072) {
|
|
6957
7135
|
try {
|
|
6958
|
-
const
|
|
6959
|
-
const
|
|
6960
|
-
const
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
7136
|
+
const size = statSync(file).size;
|
|
7137
|
+
const start = Math.max(0, size - maxBytes);
|
|
7138
|
+
const fd = openSync3(file, "r");
|
|
7139
|
+
const buf = Buffer.alloc(size - start);
|
|
7140
|
+
readSync(fd, buf, 0, buf.length, start);
|
|
7141
|
+
closeSync(fd);
|
|
7142
|
+
const lines = buf.toString("utf-8").split("\n").filter(Boolean);
|
|
7143
|
+
if (start > 0) lines.shift();
|
|
7144
|
+
return lines;
|
|
6964
7145
|
} catch {
|
|
7146
|
+
return [];
|
|
6965
7147
|
}
|
|
6966
7148
|
}
|
|
6967
|
-
|
|
6968
|
-
const res = await fetch(`${apiUrl}/api/v1/auth/device/start`, { method: "POST" });
|
|
6969
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
6970
|
-
const j = await res.json();
|
|
6971
|
-
return {
|
|
6972
|
-
deviceCode: j.device_code,
|
|
6973
|
-
verifyUrl: j.verification_uri_complete || j.verification_uri || "",
|
|
6974
|
-
intervalMs: Math.max(2, Number(j.interval) || 3) * 1e3,
|
|
6975
|
-
expiresAt: Date.now() + (Number(j.expires_in) || 600) * 1e3
|
|
6976
|
-
};
|
|
6977
|
-
}
|
|
6978
|
-
async function pollDeviceLogin(apiUrl, deviceCode) {
|
|
7149
|
+
function deleteLocalEntry(at, tool, session) {
|
|
6979
7150
|
try {
|
|
6980
|
-
const
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
7151
|
+
const lines = readFileSync8(LOCAL_LOG, "utf-8").split("\n");
|
|
7152
|
+
let removed = 0;
|
|
7153
|
+
const kept = lines.filter((line) => {
|
|
7154
|
+
if (!line.trim()) return false;
|
|
7155
|
+
if (removed) return true;
|
|
7156
|
+
try {
|
|
7157
|
+
const j = JSON.parse(line);
|
|
7158
|
+
const hit = Date.parse(j.ts ?? "") === at && (j.tool ?? "?") === tool && (!session || j.session_id === session);
|
|
7159
|
+
if (hit) {
|
|
7160
|
+
removed++;
|
|
7161
|
+
return false;
|
|
7162
|
+
}
|
|
7163
|
+
} catch {
|
|
7164
|
+
}
|
|
7165
|
+
return true;
|
|
6984
7166
|
});
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
return { status: "approved", apiKey: j.api_key, project: j.project?.name, user: j.user?.name || j.user?.email, email: j.user?.email };
|
|
6988
|
-
}
|
|
6989
|
-
if (j.status === "expired" || j.status === "not_found") return { status: j.status };
|
|
6990
|
-
return { status: "pending" };
|
|
7167
|
+
if (removed) writeFileSync6(LOCAL_LOG, kept.length ? kept.join("\n") + "\n" : "");
|
|
7168
|
+
return removed;
|
|
6991
7169
|
} catch {
|
|
6992
|
-
return
|
|
7170
|
+
return 0;
|
|
6993
7171
|
}
|
|
6994
7172
|
}
|
|
6995
|
-
|
|
6996
|
-
|
|
7173
|
+
function clearLocalLog() {
|
|
7174
|
+
try {
|
|
7175
|
+
const n = readFileSync8(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
|
|
7176
|
+
writeFileSync6(LOCAL_LOG, "");
|
|
7177
|
+
return n;
|
|
7178
|
+
} catch {
|
|
7179
|
+
return 0;
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7182
|
+
function reasonSignals(reason) {
|
|
7183
|
+
const r = reason || "";
|
|
7184
|
+
const dlp = [];
|
|
7185
|
+
if (DLP_REASON.test(r)) {
|
|
7186
|
+
const m = r.match(/contain(?:s)? (?:a |an )?(.+?)(?:\.|$)/i);
|
|
7187
|
+
dlp.push(m ? m[1].trim() : "DLP");
|
|
7188
|
+
}
|
|
7189
|
+
return { dlp, burst: RL_REASON.test(r) };
|
|
7190
|
+
}
|
|
7191
|
+
function parseLocalLines(lines) {
|
|
7192
|
+
const out2 = [];
|
|
7193
|
+
for (const line of lines) {
|
|
7194
|
+
try {
|
|
7195
|
+
const j = JSON.parse(line);
|
|
7196
|
+
const at = Date.parse(j.ts ?? "");
|
|
7197
|
+
if (Number.isFinite(at)) out2.push({ ...j, at });
|
|
7198
|
+
} catch {
|
|
7199
|
+
}
|
|
7200
|
+
}
|
|
7201
|
+
return out2;
|
|
7202
|
+
}
|
|
7203
|
+
var LOCAL_LOG, DLP_REASON, RL_REASON;
|
|
7204
|
+
var init_local_log = __esm({
|
|
7205
|
+
"src/tui/local-log.ts"() {
|
|
6997
7206
|
"use strict";
|
|
6998
|
-
|
|
7207
|
+
LOCAL_LOG = join8(homedir6(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
7208
|
+
DLP_REASON = /security layer \(dlp\)/i;
|
|
7209
|
+
RL_REASON = /security layer \(rate limit\)|rate[- ]?limit(?:ed)?\b.*exceed|exceeded \d+ calls/i;
|
|
6999
7210
|
}
|
|
7000
7211
|
});
|
|
7001
7212
|
|
|
7002
|
-
// src/api-client/
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
"use strict";
|
|
7013
|
-
init_client();
|
|
7213
|
+
// src/api-client/client.ts
|
|
7214
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync4 } from "fs";
|
|
7215
|
+
import { resolve as resolve4, join as join9 } from "path";
|
|
7216
|
+
import { homedir as homedir7 } from "os";
|
|
7217
|
+
function listAccounts() {
|
|
7218
|
+
let list5 = [];
|
|
7219
|
+
try {
|
|
7220
|
+
const raw = JSON.parse(readFileSync9(accountsFile(), "utf-8"));
|
|
7221
|
+
if (Array.isArray(raw)) list5 = raw.filter((a) => a && typeof a.apiKey === "string");
|
|
7222
|
+
} catch {
|
|
7014
7223
|
}
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
active: () => active,
|
|
7021
|
-
addRule: () => addRule,
|
|
7022
|
-
backtest: () => backtest,
|
|
7023
|
-
create: () => create,
|
|
7024
|
-
dryRun: () => dryRun,
|
|
7025
|
-
get: () => get,
|
|
7026
|
-
list: () => list,
|
|
7027
|
-
remove: () => remove,
|
|
7028
|
-
revokeRule: () => revokeRule,
|
|
7029
|
-
rollback: () => rollback,
|
|
7030
|
-
setActive: () => setActive,
|
|
7031
|
-
update: () => update,
|
|
7032
|
-
versions: () => versions
|
|
7033
|
-
});
|
|
7034
|
-
function list() {
|
|
7035
|
-
return request("GET", "/policies");
|
|
7224
|
+
const active2 = loginCredentialFile();
|
|
7225
|
+
if (active2.apiKey && !list5.some((a) => a.apiKey === active2.apiKey)) {
|
|
7226
|
+
list5.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL2 });
|
|
7227
|
+
}
|
|
7228
|
+
return list5;
|
|
7036
7229
|
}
|
|
7037
|
-
function
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7230
|
+
function saveAccount(acc) {
|
|
7231
|
+
try {
|
|
7232
|
+
const list5 = (() => {
|
|
7233
|
+
try {
|
|
7234
|
+
const raw = JSON.parse(readFileSync9(accountsFile(), "utf-8"));
|
|
7235
|
+
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
7236
|
+
} catch {
|
|
7237
|
+
return [];
|
|
7238
|
+
}
|
|
7239
|
+
})();
|
|
7240
|
+
const next = list5.filter((a) => a.apiKey !== acc.apiKey);
|
|
7241
|
+
next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
|
|
7242
|
+
mkdirSync6(join9(homedir7(), ".solongate"), { recursive: true });
|
|
7243
|
+
writeFileSync7(accountsFile(), JSON.stringify(next, null, 2));
|
|
7244
|
+
} catch {
|
|
7245
|
+
}
|
|
7041
7246
|
}
|
|
7042
|
-
function
|
|
7043
|
-
|
|
7247
|
+
function removeAccount(apiKey) {
|
|
7248
|
+
try {
|
|
7249
|
+
const list5 = (() => {
|
|
7250
|
+
try {
|
|
7251
|
+
const raw = JSON.parse(readFileSync9(accountsFile(), "utf-8"));
|
|
7252
|
+
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
7253
|
+
} catch {
|
|
7254
|
+
return [];
|
|
7255
|
+
}
|
|
7256
|
+
})();
|
|
7257
|
+
writeFileSync7(accountsFile(), JSON.stringify(list5.filter((a) => a.apiKey !== apiKey), null, 2));
|
|
7258
|
+
} catch {
|
|
7259
|
+
}
|
|
7044
7260
|
}
|
|
7045
|
-
function
|
|
7046
|
-
|
|
7261
|
+
function setViewCredentials(creds) {
|
|
7262
|
+
viewOverride = creds;
|
|
7263
|
+
cached2 = null;
|
|
7047
7264
|
}
|
|
7048
|
-
function
|
|
7049
|
-
return
|
|
7265
|
+
function isActiveAccount(apiKey) {
|
|
7266
|
+
return loginCredentialFile().apiKey === apiKey;
|
|
7050
7267
|
}
|
|
7051
|
-
function
|
|
7052
|
-
|
|
7268
|
+
function setActiveAccount(creds) {
|
|
7269
|
+
try {
|
|
7270
|
+
const dir = join9(homedir7(), ".solongate");
|
|
7271
|
+
mkdirSync6(dir, { recursive: true });
|
|
7272
|
+
const p = join9(dir, ["cloud", "guard.json"].join("-"));
|
|
7273
|
+
let existing = {};
|
|
7274
|
+
try {
|
|
7275
|
+
existing = JSON.parse(readFileSync9(p, "utf-8"));
|
|
7276
|
+
} catch {
|
|
7277
|
+
}
|
|
7278
|
+
writeFileSync7(p, JSON.stringify({ ...existing, apiKey: creds.apiKey, apiUrl: creds.apiUrl }, null, 2));
|
|
7279
|
+
cached2 = null;
|
|
7280
|
+
return true;
|
|
7281
|
+
} catch {
|
|
7282
|
+
return false;
|
|
7283
|
+
}
|
|
7053
7284
|
}
|
|
7054
|
-
function
|
|
7055
|
-
|
|
7285
|
+
function clearActiveCredential() {
|
|
7286
|
+
try {
|
|
7287
|
+
const p = join9(homedir7(), ".solongate", ["cloud", "guard.json"].join("-"));
|
|
7288
|
+
if (!existsSync4(p)) {
|
|
7289
|
+
cached2 = null;
|
|
7290
|
+
return true;
|
|
7291
|
+
}
|
|
7292
|
+
let existing = {};
|
|
7293
|
+
try {
|
|
7294
|
+
existing = JSON.parse(readFileSync9(p, "utf-8"));
|
|
7295
|
+
} catch {
|
|
7296
|
+
}
|
|
7297
|
+
delete existing.apiKey;
|
|
7298
|
+
delete existing.apiUrl;
|
|
7299
|
+
writeFileSync7(p, JSON.stringify(existing, null, 2));
|
|
7300
|
+
cached2 = null;
|
|
7301
|
+
viewOverride = null;
|
|
7302
|
+
return true;
|
|
7303
|
+
} catch {
|
|
7304
|
+
return false;
|
|
7305
|
+
}
|
|
7056
7306
|
}
|
|
7057
|
-
function
|
|
7058
|
-
|
|
7307
|
+
function loginCredentialFile() {
|
|
7308
|
+
try {
|
|
7309
|
+
const p = join9(homedir7(), ".solongate", "cloud-guard.json");
|
|
7310
|
+
if (!existsSync4(p)) return {};
|
|
7311
|
+
const c2 = JSON.parse(readFileSync9(p, "utf-8"));
|
|
7312
|
+
return c2 && typeof c2 === "object" ? c2 : {};
|
|
7313
|
+
} catch {
|
|
7314
|
+
return {};
|
|
7315
|
+
}
|
|
7059
7316
|
}
|
|
7060
|
-
function
|
|
7061
|
-
|
|
7317
|
+
function dotenvApiKey() {
|
|
7318
|
+
try {
|
|
7319
|
+
const envPath = resolve4(".env");
|
|
7320
|
+
if (!existsSync4(envPath)) return void 0;
|
|
7321
|
+
for (const line of readFileSync9(envPath, "utf-8").split("\n")) {
|
|
7322
|
+
const trimmed = line.trim();
|
|
7323
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
7324
|
+
const eq = trimmed.indexOf("=");
|
|
7325
|
+
if (eq === -1) continue;
|
|
7326
|
+
const key = trimmed.slice(0, eq).trim();
|
|
7327
|
+
if (key !== "SOLONGATE_API_KEY") continue;
|
|
7328
|
+
return trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
7329
|
+
}
|
|
7330
|
+
} catch {
|
|
7331
|
+
}
|
|
7332
|
+
return void 0;
|
|
7062
7333
|
}
|
|
7063
|
-
function
|
|
7064
|
-
|
|
7334
|
+
function isAuthenticated() {
|
|
7335
|
+
try {
|
|
7336
|
+
resolveCredentials();
|
|
7337
|
+
return true;
|
|
7338
|
+
} catch {
|
|
7339
|
+
return false;
|
|
7340
|
+
}
|
|
7065
7341
|
}
|
|
7066
|
-
function
|
|
7067
|
-
|
|
7342
|
+
function resolveCredentials(apiUrlOverride) {
|
|
7343
|
+
if (viewOverride && !apiUrlOverride) return viewOverride;
|
|
7344
|
+
if (cached2 && !apiUrlOverride) return cached2;
|
|
7345
|
+
const file = loginCredentialFile();
|
|
7346
|
+
const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
|
|
7347
|
+
if (!apiKey) throw new NotAuthenticatedError();
|
|
7348
|
+
const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL2;
|
|
7349
|
+
const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
|
|
7350
|
+
if (!apiUrlOverride) cached2 = creds;
|
|
7351
|
+
return creds;
|
|
7068
7352
|
}
|
|
7069
|
-
function
|
|
7070
|
-
|
|
7353
|
+
function buildUrl(base, path, query) {
|
|
7354
|
+
const url = new URL(`${base}/api/v1${path}`);
|
|
7355
|
+
if (query) {
|
|
7356
|
+
for (const [k, v] of Object.entries(query)) {
|
|
7357
|
+
if (v === void 0) continue;
|
|
7358
|
+
url.searchParams.set(k, String(v));
|
|
7359
|
+
}
|
|
7360
|
+
}
|
|
7361
|
+
return url.toString();
|
|
7071
7362
|
}
|
|
7072
|
-
function
|
|
7073
|
-
|
|
7363
|
+
async function request(method, path, opts = {}) {
|
|
7364
|
+
const creds = resolveCredentials(opts.apiUrl);
|
|
7365
|
+
const url = buildUrl(creds.apiUrl, path, opts.query);
|
|
7366
|
+
const headers = {
|
|
7367
|
+
Authorization: `Bearer ${creds.apiKey}`
|
|
7368
|
+
};
|
|
7369
|
+
let bodyInit;
|
|
7370
|
+
if (opts.body !== void 0) {
|
|
7371
|
+
headers["Content-Type"] = "application/json";
|
|
7372
|
+
bodyInit = JSON.stringify(opts.body);
|
|
7373
|
+
}
|
|
7374
|
+
const attempt = () => fetch(url, {
|
|
7375
|
+
method,
|
|
7376
|
+
headers: { ...headers, Connection: "close" },
|
|
7377
|
+
body: bodyInit,
|
|
7378
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
7379
|
+
});
|
|
7380
|
+
const maxTries = method === "GET" ? 3 : 1;
|
|
7381
|
+
let res;
|
|
7382
|
+
let lastErr;
|
|
7383
|
+
for (let i = 0; i < maxTries; i++) {
|
|
7384
|
+
try {
|
|
7385
|
+
res = await attempt();
|
|
7386
|
+
break;
|
|
7387
|
+
} catch (err2) {
|
|
7388
|
+
lastErr = err2;
|
|
7389
|
+
if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
|
|
7390
|
+
}
|
|
7391
|
+
}
|
|
7392
|
+
if (!res) {
|
|
7393
|
+
const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
7394
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
7395
|
+
}
|
|
7396
|
+
const text = await res.text().catch(() => "");
|
|
7397
|
+
let json = void 0;
|
|
7398
|
+
if (text) {
|
|
7399
|
+
try {
|
|
7400
|
+
json = JSON.parse(text);
|
|
7401
|
+
} catch {
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
if (!res.ok) {
|
|
7405
|
+
const envelope = json?.error;
|
|
7406
|
+
if (envelope && typeof envelope === "object") {
|
|
7407
|
+
throw new ApiError(res.status, envelope.code || "ERROR", envelope.message || res.statusText);
|
|
7408
|
+
}
|
|
7409
|
+
if (typeof envelope === "string") {
|
|
7410
|
+
throw new ApiError(res.status, "ERROR", envelope);
|
|
7411
|
+
}
|
|
7412
|
+
if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate` and log in from the Accounts panel.");
|
|
7413
|
+
if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
|
|
7414
|
+
if (res.status >= 500) throw new ApiError(res.status, "SERVER_ERROR", text || "SolonGate API had a problem (server error). Please try again in a moment.");
|
|
7415
|
+
throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
|
|
7416
|
+
}
|
|
7417
|
+
return json;
|
|
7074
7418
|
}
|
|
7075
|
-
var
|
|
7076
|
-
|
|
7419
|
+
var DEFAULT_API_URL2, accountsFile, viewOverride, ApiError, NotAuthenticatedError, cached2;
|
|
7420
|
+
var init_client = __esm({
|
|
7421
|
+
"src/api-client/client.ts"() {
|
|
7077
7422
|
"use strict";
|
|
7078
|
-
|
|
7423
|
+
DEFAULT_API_URL2 = "https://api.solongate.com";
|
|
7424
|
+
accountsFile = () => join9(homedir7(), ".solongate", "accounts.json");
|
|
7425
|
+
viewOverride = null;
|
|
7426
|
+
ApiError = class extends Error {
|
|
7427
|
+
status;
|
|
7428
|
+
code;
|
|
7429
|
+
constructor(status, code, message) {
|
|
7430
|
+
super(message);
|
|
7431
|
+
this.name = "ApiError";
|
|
7432
|
+
this.status = status;
|
|
7433
|
+
this.code = code;
|
|
7434
|
+
}
|
|
7435
|
+
};
|
|
7436
|
+
NotAuthenticatedError = class extends Error {
|
|
7437
|
+
constructor() {
|
|
7438
|
+
super("Not logged in. Run `solongate` and log in from the Accounts panel.");
|
|
7439
|
+
this.name = "NotAuthenticatedError";
|
|
7440
|
+
}
|
|
7441
|
+
};
|
|
7442
|
+
cached2 = null;
|
|
7079
7443
|
}
|
|
7080
7444
|
});
|
|
7081
7445
|
|
|
7082
|
-
// src/api-client/
|
|
7083
|
-
var
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7446
|
+
// src/api-client/types.ts
|
|
7447
|
+
var init_types = __esm({
|
|
7448
|
+
"src/api-client/types.ts"() {
|
|
7449
|
+
"use strict";
|
|
7450
|
+
}
|
|
7451
|
+
});
|
|
7452
|
+
|
|
7453
|
+
// src/api-client/device-login.ts
|
|
7454
|
+
import { spawn as spawn3 } from "child_process";
|
|
7455
|
+
function openBrowser(url) {
|
|
7456
|
+
try {
|
|
7457
|
+
const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
7458
|
+
const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
|
|
7459
|
+
const child = spawn3(cmd, args, { stdio: "ignore", detached: true });
|
|
7460
|
+
child.on("error", () => {
|
|
7461
|
+
});
|
|
7462
|
+
child.unref();
|
|
7463
|
+
} catch {
|
|
7464
|
+
}
|
|
7465
|
+
}
|
|
7466
|
+
async function startDeviceLogin(apiUrl = DEFAULT_API_URL2) {
|
|
7467
|
+
const res = await fetch(`${apiUrl}/api/v1/auth/device/start`, { method: "POST" });
|
|
7468
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
7469
|
+
const j = await res.json();
|
|
7470
|
+
return {
|
|
7471
|
+
deviceCode: j.device_code,
|
|
7472
|
+
verifyUrl: j.verification_uri_complete || j.verification_uri || "",
|
|
7473
|
+
intervalMs: Math.max(2, Number(j.interval) || 3) * 1e3,
|
|
7474
|
+
expiresAt: Date.now() + (Number(j.expires_in) || 600) * 1e3
|
|
7475
|
+
};
|
|
7476
|
+
}
|
|
7477
|
+
async function pollDeviceLogin(apiUrl, deviceCode) {
|
|
7478
|
+
try {
|
|
7479
|
+
const res = await fetch(`${apiUrl}/api/v1/auth/device/poll`, {
|
|
7480
|
+
method: "POST",
|
|
7481
|
+
headers: { "Content-Type": "application/json" },
|
|
7482
|
+
body: JSON.stringify({ device_code: deviceCode })
|
|
7483
|
+
});
|
|
7484
|
+
const j = await res.json().catch(() => ({}));
|
|
7485
|
+
if (j.status === "approved" && j.api_key) {
|
|
7486
|
+
return { status: "approved", apiKey: j.api_key, project: j.project?.name, user: j.user?.name || j.user?.email, email: j.user?.email };
|
|
7487
|
+
}
|
|
7488
|
+
if (j.status === "expired" || j.status === "not_found") return { status: j.status };
|
|
7489
|
+
return { status: "pending" };
|
|
7490
|
+
} catch {
|
|
7491
|
+
return { status: "pending" };
|
|
7492
|
+
}
|
|
7493
|
+
}
|
|
7494
|
+
var init_device_login = __esm({
|
|
7495
|
+
"src/api-client/device-login.ts"() {
|
|
7496
|
+
"use strict";
|
|
7497
|
+
init_client();
|
|
7498
|
+
}
|
|
7499
|
+
});
|
|
7500
|
+
|
|
7501
|
+
// src/api-client/auth.ts
|
|
7502
|
+
var auth_exports = {};
|
|
7503
|
+
__export(auth_exports, {
|
|
7504
|
+
me: () => me
|
|
7505
|
+
});
|
|
7506
|
+
function me() {
|
|
7507
|
+
return request("GET", "/auth/me");
|
|
7508
|
+
}
|
|
7509
|
+
var init_auth = __esm({
|
|
7510
|
+
"src/api-client/auth.ts"() {
|
|
7511
|
+
"use strict";
|
|
7512
|
+
init_client();
|
|
7513
|
+
}
|
|
7514
|
+
});
|
|
7515
|
+
|
|
7516
|
+
// src/api-client/policies.ts
|
|
7517
|
+
var policies_exports = {};
|
|
7518
|
+
__export(policies_exports, {
|
|
7519
|
+
active: () => active,
|
|
7520
|
+
addRule: () => addRule,
|
|
7521
|
+
backtest: () => backtest,
|
|
7522
|
+
create: () => create,
|
|
7523
|
+
dryRun: () => dryRun,
|
|
7524
|
+
get: () => get,
|
|
7525
|
+
list: () => list,
|
|
7526
|
+
remove: () => remove,
|
|
7527
|
+
revokeRule: () => revokeRule,
|
|
7528
|
+
rollback: () => rollback,
|
|
7529
|
+
setActive: () => setActive,
|
|
7530
|
+
update: () => update,
|
|
7531
|
+
versions: () => versions
|
|
7532
|
+
});
|
|
7533
|
+
function list() {
|
|
7534
|
+
return request("GET", "/policies");
|
|
7535
|
+
}
|
|
7536
|
+
function get(id, version) {
|
|
7537
|
+
return request("GET", `/policies/${encodeURIComponent(id)}`, {
|
|
7538
|
+
query: version !== void 0 ? { version } : void 0
|
|
7539
|
+
});
|
|
7540
|
+
}
|
|
7541
|
+
function create(policy) {
|
|
7542
|
+
return request("POST", "/policies", { body: policy });
|
|
7543
|
+
}
|
|
7544
|
+
function update(id, policy) {
|
|
7545
|
+
return request("PUT", `/policies/${encodeURIComponent(id)}`, { body: policy });
|
|
7546
|
+
}
|
|
7547
|
+
function remove(id) {
|
|
7548
|
+
return request("DELETE", `/policies/${encodeURIComponent(id)}`);
|
|
7549
|
+
}
|
|
7550
|
+
function addRule(id, spec) {
|
|
7551
|
+
return request("POST", `/policies/${encodeURIComponent(id)}/rules`, { body: spec });
|
|
7552
|
+
}
|
|
7553
|
+
function revokeRule(id, ruleId) {
|
|
7554
|
+
return request("DELETE", `/policies/${encodeURIComponent(id)}/rules/${encodeURIComponent(ruleId)}`);
|
|
7555
|
+
}
|
|
7556
|
+
function versions(id, opts = {}) {
|
|
7557
|
+
return request("GET", `/policies/${encodeURIComponent(id)}/versions`, { query: opts });
|
|
7558
|
+
}
|
|
7559
|
+
function rollback(id, version) {
|
|
7560
|
+
return request("POST", `/policies/${encodeURIComponent(id)}/rollback`, { body: { version } });
|
|
7561
|
+
}
|
|
7562
|
+
function active(agentId) {
|
|
7563
|
+
return request("GET", "/policies/active", { query: agentId ? { agent_id: agentId } : void 0 });
|
|
7564
|
+
}
|
|
7565
|
+
function setActive(policyId) {
|
|
7566
|
+
return request("POST", "/policies/active", { body: { policyId: policyId ?? "" } });
|
|
7567
|
+
}
|
|
7568
|
+
function dryRun(body) {
|
|
7569
|
+
return request("POST", "/policies/dry-run", { body });
|
|
7570
|
+
}
|
|
7571
|
+
function backtest(body) {
|
|
7572
|
+
return request("POST", "/policies/backtest", { body });
|
|
7573
|
+
}
|
|
7574
|
+
var init_policies = __esm({
|
|
7575
|
+
"src/api-client/policies.ts"() {
|
|
7576
|
+
"use strict";
|
|
7577
|
+
init_client();
|
|
7578
|
+
}
|
|
7579
|
+
});
|
|
7580
|
+
|
|
7581
|
+
// src/api-client/settings.ts
|
|
7582
|
+
var settings_exports = {};
|
|
7583
|
+
__export(settings_exports, {
|
|
7584
|
+
clearRateLimitHistory: () => clearRateLimitHistory,
|
|
7585
|
+
createAlert: () => createAlert,
|
|
7586
|
+
createWebhook: () => createWebhook,
|
|
7587
|
+
deleteAlert: () => deleteAlert,
|
|
7588
|
+
deleteWebhook: () => deleteWebhook,
|
|
7589
|
+
getAlerts: () => getAlerts,
|
|
7091
7590
|
getGuardStatus: () => getGuardStatus,
|
|
7092
7591
|
getLocalLogs: () => getLocalLogs,
|
|
7093
7592
|
getRateLimitHistory: () => getRateLimitHistory,
|
|
@@ -7413,9 +7912,9 @@ var init_hooks = __esm({
|
|
|
7413
7912
|
// src/tui/panels/Live.tsx
|
|
7414
7913
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
7415
7914
|
import TextInput from "ink-text-input";
|
|
7416
|
-
import { mkdirSync as
|
|
7417
|
-
import { homedir as
|
|
7418
|
-
import { join as
|
|
7915
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
7916
|
+
import { homedir as homedir8 } from "os";
|
|
7917
|
+
import { join as join10 } from "path";
|
|
7419
7918
|
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
7420
7919
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
7421
7920
|
function extractTarget(detail) {
|
|
@@ -7973,10 +8472,10 @@ function LivePanel({ active: active2 }) {
|
|
|
7973
8472
|
else if (input === "x") toggleSignal("dlp");
|
|
7974
8473
|
else if (input === "r") toggleSignal("ratelimit");
|
|
7975
8474
|
else if (input === "e") {
|
|
7976
|
-
const file =
|
|
8475
|
+
const file = join10(homedir8(), ".solongate", "live-export.jsonl");
|
|
7977
8476
|
try {
|
|
7978
|
-
|
|
7979
|
-
|
|
8477
|
+
mkdirSync7(join10(homedir8(), ".solongate"), { recursive: true });
|
|
8478
|
+
writeFileSync8(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
7980
8479
|
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
7981
8480
|
} catch (err2) {
|
|
7982
8481
|
setActionMsg({ text: "\u2717 export failed: " + (err2 instanceof Error ? err2.message : String(err2)), level: "bad", until: Date.now() + 6e3 });
|
|
@@ -8387,7 +8886,7 @@ var init_Live = __esm({
|
|
|
8387
8886
|
SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
8388
8887
|
BG = "#12234f";
|
|
8389
8888
|
DIM_FLOOR = "#233457";
|
|
8390
|
-
RING =
|
|
8889
|
+
RING = join10(process.cwd(), ".solongate", ".eval-ring.jsonl");
|
|
8391
8890
|
fmtUp = (ms) => {
|
|
8392
8891
|
const s = Math.floor(ms / 1e3);
|
|
8393
8892
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -9390,9 +9889,9 @@ var init_Dlp = __esm({
|
|
|
9390
9889
|
// src/tui/panels/Audit.tsx
|
|
9391
9890
|
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
9392
9891
|
import TextInput5 from "ink-text-input";
|
|
9393
|
-
import { mkdirSync as
|
|
9394
|
-
import { homedir as
|
|
9395
|
-
import { join as
|
|
9892
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
9893
|
+
import { homedir as homedir9 } from "os";
|
|
9894
|
+
import { join as join11 } from "path";
|
|
9396
9895
|
import { useState as useState7 } from "react";
|
|
9397
9896
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
9398
9897
|
function loadLocalRows() {
|
|
@@ -9542,16 +10041,16 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
9542
10041
|
const doExport = (kind) => {
|
|
9543
10042
|
setMsg({ text: "exporting\u2026", level: "ok" });
|
|
9544
10043
|
const run10 = async () => {
|
|
9545
|
-
const dir =
|
|
9546
|
-
const file =
|
|
10044
|
+
const dir = join11(homedir9(), ".solongate");
|
|
10045
|
+
const file = join11(dir, `audit-export-${source}.jsonl`);
|
|
9547
10046
|
let rows2;
|
|
9548
10047
|
if (kind === "page") rows2 = pageRows;
|
|
9549
10048
|
else if (source === "cloud") {
|
|
9550
10049
|
const r = await api.audit.list({ ...query, limit: 1e4, offset: 0 });
|
|
9551
10050
|
rows2 = r.entries.map(cloudRow).sort((a, b) => b.at - a.at);
|
|
9552
10051
|
} else rows2 = localFiltered;
|
|
9553
|
-
|
|
9554
|
-
|
|
10052
|
+
mkdirSync8(dir, { recursive: true });
|
|
10053
|
+
writeFileSync9(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
|
|
9555
10054
|
return { n: rows2.length, file };
|
|
9556
10055
|
};
|
|
9557
10056
|
run10().then(({ n, file }) => setMsg({ text: `\u2713 exported ${n} rows \u2192 ${file}`, level: "ok" })).catch((e) => setMsg({ text: "\u2717 export failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
|
|
@@ -9780,551 +10279,245 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
9780
10279
|
/* @__PURE__ */ jsx7(Text7, { children: e.trust || "\u2014" }),
|
|
9781
10280
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 eval " }),
|
|
9782
10281
|
/* @__PURE__ */ jsx7(Text7, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : void 0, children: e.evalMs != null ? `${e.evalMs}ms` : "\u2014" }),
|
|
9783
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 agent " }),
|
|
9784
|
-
/* @__PURE__ */ jsx7(Text7, { children: e.agent ?? "\u2014" }),
|
|
9785
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502" })
|
|
9786
|
-
] }),
|
|
9787
|
-
/* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
9788
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2502 session " }),
|
|
9789
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: e.session ?? "\u2014" }),
|
|
9790
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 rule " }),
|
|
9791
|
-
/* @__PURE__ */ jsx7(Text7, { color: e.rule && e.decision !== "ALLOW" ? theme.bad : theme.dim, children: e.rule ?? "\u2014" }),
|
|
9792
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502" })
|
|
9793
|
-
] }),
|
|
9794
|
-
/* @__PURE__ */ jsx7(PaneTitle, { label: "FULL CONTENT", extra: `${contentLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""} \xB7 space copy \xB7 \u2190 back`, width: innerW }),
|
|
9795
|
-
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: l || " " }, off + i)) }),
|
|
9796
|
-
/* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
9797
|
-
/* @__PURE__ */ jsx7(Text7, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
|
|
9798
|
-
/* @__PURE__ */ jsx7(Text7, { backgroundColor: "#0b1530", color: "white", children: ` ${e.id} ` }),
|
|
9799
|
-
/* @__PURE__ */ jsx7(Text7, { backgroundColor: BG2, color: "white", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
|
|
9800
|
-
] })
|
|
9801
|
-
] });
|
|
9802
|
-
}
|
|
9803
|
-
const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
9804
|
-
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
9805
|
-
label,
|
|
9806
|
-
":"
|
|
9807
|
-
] }),
|
|
9808
|
-
/* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
|
|
9809
|
-
/* @__PURE__ */ jsx7(Text7, { children: " " })
|
|
9810
|
-
] });
|
|
9811
|
-
if (view === "sessions") {
|
|
9812
|
-
const headerRows2 = 5 + (editing ? 1 : 0);
|
|
9813
|
-
const listRows2 = Math.max(4, rows - headerRows2);
|
|
9814
|
-
const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
|
|
9815
|
-
const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
|
|
9816
|
-
const win = sessionsFiltered.slice(start2, start2 + listRows2);
|
|
9817
|
-
return /* @__PURE__ */ jsx7(DataView, { loading: sessLoading && !frozen, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
9818
|
-
strip,
|
|
9819
|
-
copyBanner,
|
|
9820
|
-
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
9821
|
-
srcChip,
|
|
9822
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
|
|
9823
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 logs (v) " }),
|
|
9824
|
-
chip("status", SESS_STATUS[si] ?? "all", si !== 0),
|
|
9825
|
-
chip("search", sessSearch || "\xB7", !!sessSearch)
|
|
9826
|
-
] }),
|
|
9827
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter \u2192 session logs \xB7 v logs \xB7 ^R refresh \xB7 ? all keys" : "press \u2192 to browse" }),
|
|
9828
|
-
editing === "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
9829
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "search: " }),
|
|
9830
|
-
/* @__PURE__ */ jsx7(
|
|
9831
|
-
TextInput5,
|
|
9832
|
-
{
|
|
9833
|
-
value: sessSearch,
|
|
9834
|
-
onChange: (v) => {
|
|
9835
|
-
setSessSearch(v);
|
|
9836
|
-
setSessSel(0);
|
|
9837
|
-
},
|
|
9838
|
-
onSubmit: () => setEditing(null)
|
|
9839
|
-
}
|
|
9840
|
-
)
|
|
9841
|
-
] }) : null,
|
|
9842
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: `${sessionsFiltered.length} sessions \xB7 ${selC + 1}/${sessionsFiltered.length}${start2 ? ` \xB7 \u25B2${start2}` : ""}${start2 + listRows2 < sessionsFiltered.length ? ` \xB7 \u25BC${sessionsFiltered.length - start2 - listRows2}` : ""}` }),
|
|
9843
|
-
/* @__PURE__ */ jsx7(
|
|
9844
|
-
Table,
|
|
9845
|
-
{
|
|
9846
|
-
columns: [
|
|
9847
|
-
{ header: "", width: 2 },
|
|
9848
|
-
{ header: "STATUS", width: 8 },
|
|
9849
|
-
{ header: "AGENT", width: 18 },
|
|
9850
|
-
{ header: "SESSION", width: 10 },
|
|
9851
|
-
{ header: "CALLS", width: 6 },
|
|
9852
|
-
{ header: "DENY", width: 5 },
|
|
9853
|
-
{ header: "DLP", width: 4 },
|
|
9854
|
-
{ header: "TRUST", width: 5 },
|
|
9855
|
-
{ header: "LAST", width: 6 }
|
|
9856
|
-
],
|
|
9857
|
-
rows: win.map((r, i) => {
|
|
9858
|
-
const st = STATUS_DOT[r.status];
|
|
9859
|
-
const isSel = start2 + i === selC && focused;
|
|
9860
|
-
return [
|
|
9861
|
-
{ value: isSel ? "\u25B8" : "", color: theme.accentBright },
|
|
9862
|
-
{ value: `${st.ch} ${r.status}`, color: st.color },
|
|
9863
|
-
{ value: truncate2(r.agent, 18), color: theme.accent, bold: isSel },
|
|
9864
|
-
{ value: r.id.slice(0, 8), dim: true },
|
|
9865
|
-
{ value: String(r.calls) },
|
|
9866
|
-
{ value: String(r.denies), color: r.denies ? theme.bad : void 0, dim: !r.denies },
|
|
9867
|
-
{ value: String(r.dlp), color: r.dlp ? theme.bad : void 0, dim: !r.dlp },
|
|
9868
|
-
{ value: r.trust != null ? String(r.trust) : "\u2014", dim: true },
|
|
9869
|
-
{ value: ago(r.lastAt), dim: true }
|
|
9870
|
-
];
|
|
9871
|
-
})
|
|
9872
|
-
}
|
|
9873
|
-
),
|
|
9874
|
-
sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
9875
|
-
"(no sessions",
|
|
9876
|
-
source === "local" ? " in the local file" : "",
|
|
9877
|
-
")"
|
|
9878
|
-
] }) : null
|
|
9879
|
-
] }) });
|
|
9880
|
-
}
|
|
9881
|
-
const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0) + (msg ? 1 : 0) + (frozen ? 1 : 0);
|
|
9882
|
-
const listRows = Math.max(4, rows - headerRows);
|
|
9883
|
-
const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
|
|
9884
|
-
const maxStart = Math.max(0, pageRows.length - listRows);
|
|
9885
|
-
const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
|
|
9886
|
-
const windowed = pageRows.slice(start, start + listRows);
|
|
9887
|
-
return /* @__PURE__ */ jsx7(DataView, { loading: logsLoading && !frozen, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
9888
|
-
strip,
|
|
9889
|
-
copyBanner,
|
|
9890
|
-
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
9891
|
-
srcChip,
|
|
9892
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, children: "LOGS" }),
|
|
9893
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 sessions (v) " }),
|
|
9894
|
-
chip("dec", DECISIONS[di] ?? "all", di !== 0),
|
|
9895
|
-
chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
|
|
9896
|
-
chip("tool", tool || "\xB7", !!tool),
|
|
9897
|
-
chip("agent", agent || "\xB7", !!agent),
|
|
9898
|
-
chip("search", search || "\xB7", !!search),
|
|
9899
|
-
sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
|
|
9900
|
-
] }),
|
|
9901
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 v sessions \xB7 ^R refresh \xB7 ? all keys" : "press \u2192 to browse" }),
|
|
9902
|
-
msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : null,
|
|
9903
|
-
editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
9904
|
-
/* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
|
|
9905
|
-
editing,
|
|
9906
|
-
": "
|
|
9907
|
-
] }),
|
|
9908
|
-
/* @__PURE__ */ jsx7(
|
|
9909
|
-
TextInput5,
|
|
9910
|
-
{
|
|
9911
|
-
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
9912
|
-
onChange: (v) => {
|
|
9913
|
-
(editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
|
|
9914
|
-
setPage(0);
|
|
9915
|
-
toTop();
|
|
9916
|
-
},
|
|
9917
|
-
onSubmit: () => setEditing(null)
|
|
9918
|
-
}
|
|
9919
|
-
)
|
|
9920
|
-
] }) : null,
|
|
9921
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: `${total} matched \xB7 page ${Math.min(page + 1, pages)}/${pages} \xB7 ${PAGE}/page \xB7 ${pageRows.length ? selClamped + 1 : 0}/${pageRows.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < pageRows.length ? ` \xB7 \u25BC${pageRows.length - start - listRows} older` : ""}` }),
|
|
9922
|
-
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx7(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused, date: true }, e.id)) }),
|
|
9923
|
-
pageRows.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
9924
|
-
"(no entries",
|
|
9925
|
-
source === "local" ? " in the local file" : "",
|
|
9926
|
-
" \u2014 c clears filters)"
|
|
9927
|
-
] }) : null
|
|
9928
|
-
] }) });
|
|
9929
|
-
}
|
|
9930
|
-
var DECISIONS, SIGNALS, SESS_STATUS, PAGE, LOCAL_MAX_BYTES, BG2, toStream, cloudRow, AUDIT_HELP, sessStatus2, STATUS_DOT;
|
|
9931
|
-
var init_Audit = __esm({
|
|
9932
|
-
"src/tui/panels/Audit.tsx"() {
|
|
9933
|
-
"use strict";
|
|
9934
|
-
init_api_client();
|
|
9935
|
-
init_components();
|
|
9936
|
-
init_hooks();
|
|
9937
|
-
init_local_log();
|
|
9938
|
-
init_theme();
|
|
9939
|
-
DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
9940
|
-
SIGNALS = [void 0, "dlp", "ratelimit"];
|
|
9941
|
-
SESS_STATUS = [void 0, "active", "idle", "ended"];
|
|
9942
|
-
PAGE = 500;
|
|
9943
|
-
LOCAL_MAX_BYTES = 16 * 1024 * 1024;
|
|
9944
|
-
BG2 = "#12234f";
|
|
9945
|
-
toStream = (e) => ({
|
|
9946
|
-
at: e.at,
|
|
9947
|
-
tool: e.tool,
|
|
9948
|
-
decision: e.decision,
|
|
9949
|
-
permission: (e.permission ?? "").slice(0, 4),
|
|
9950
|
-
detail: (e.args ?? e.reason ?? "").replace(/\s+/g, " "),
|
|
9951
|
-
dlp: e.dlp.length > 0,
|
|
9952
|
-
burst: e.burst,
|
|
9953
|
-
agent: e.agent,
|
|
9954
|
-
evalMs: e.evalMs,
|
|
9955
|
-
rule: e.rule
|
|
9956
|
-
});
|
|
9957
|
-
cloudRow = (e) => {
|
|
9958
|
-
const rs = reasonSignals(e.reason);
|
|
9959
|
-
return {
|
|
9960
|
-
id: e.id,
|
|
9961
|
-
at: Date.parse(e.created_at),
|
|
9962
|
-
tool: e.tool_name,
|
|
9963
|
-
decision: e.decision,
|
|
9964
|
-
permission: e.permission,
|
|
9965
|
-
trust: e.trust_level,
|
|
9966
|
-
agent: e.agent_name,
|
|
9967
|
-
session: e.session_id,
|
|
9968
|
-
reason: e.reason,
|
|
9969
|
-
rule: e.matched_rule_id,
|
|
9970
|
-
evalMs: e.evaluation_time_ms,
|
|
9971
|
-
dlp: e.dlp_matches?.length ? e.dlp_matches : rs.dlp,
|
|
9972
|
-
burst: !!e.rate_limit_burst || rs.burst,
|
|
9973
|
-
args: e.arguments_summary ? JSON.stringify(e.arguments_summary) : null
|
|
9974
|
-
};
|
|
9975
|
-
};
|
|
9976
|
-
AUDIT_HELP = [
|
|
9977
|
-
[
|
|
9978
|
-
"Logs",
|
|
9979
|
-
[
|
|
9980
|
-
["\u2191\u2193 / PgUp PgDn", "select a row (window follows)"],
|
|
9981
|
-
["enter", "open the FULL entry (reason + arguments)"],
|
|
9982
|
-
["\u2190 \u2192", "previous / next page (500 per page, jumps to top)"],
|
|
9983
|
-
["f", "decision filter: all \u2192 DENY \u2192 ALLOW"],
|
|
9984
|
-
["g", "signal filter: all \u2192 dlp \u2192 ratelimit"],
|
|
9985
|
-
["t / n", "tool / agent filter (type, enter done)"],
|
|
9986
|
-
["/", "free-text search"],
|
|
9987
|
-
["x", "delete ONLY the selected entry (press x twice)"],
|
|
9988
|
-
["X", "delete ALL matched logs of the source (press X twice)"],
|
|
9989
|
-
["e", "export this page \u2192 ~/.solongate/audit-export-<src>.jsonl"],
|
|
9990
|
-
["E", "export ALL matched rows (cloud: up to 10k)"],
|
|
9991
|
-
["c", "clear every filter (incl. session)"]
|
|
9992
|
-
]
|
|
9993
|
-
],
|
|
9994
|
-
[
|
|
9995
|
-
"Sessions",
|
|
9996
|
-
[
|
|
9997
|
-
["\u2191\u2193", "select a session"],
|
|
9998
|
-
["enter", "open that session's logs"],
|
|
9999
|
-
["f", "status filter: all \u2192 active \u2192 idle \u2192 ended"],
|
|
10000
|
-
["/", "search agent / session id"],
|
|
10001
|
-
["c", "clear session filters"]
|
|
10002
|
-
]
|
|
10003
|
-
],
|
|
10004
|
-
[
|
|
10005
|
-
"Anywhere in Audit",
|
|
10006
|
-
[
|
|
10007
|
-
["v", "switch logs \u2194 sessions"],
|
|
10008
|
-
["s", "switch source cloud \u2194 local file"],
|
|
10009
|
-
["space", "copy mode: freeze screen for mouse selection"],
|
|
10010
|
-
["?", "this help \xB7 any key closes"],
|
|
10011
|
-
["esc", "back to the menu"]
|
|
10012
|
-
]
|
|
10013
|
-
],
|
|
10014
|
-
["Entry (full content)", [["\u2191\u2193 / PgUp PgDn", "scroll the reason + arguments"], ["space", "copy mode (freeze, then select)"], ["\u2190 / esc", "back to the list"]]]
|
|
10015
|
-
];
|
|
10016
|
-
sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
|
|
10017
|
-
STATUS_DOT = {
|
|
10018
|
-
active: { ch: "\u25CF", color: theme.ok },
|
|
10019
|
-
idle: { ch: "\u25D0", color: theme.warn },
|
|
10020
|
-
ended: { ch: "\u25CB", color: theme.dim }
|
|
10021
|
-
};
|
|
10022
|
-
}
|
|
10023
|
-
});
|
|
10024
|
-
|
|
10025
|
-
// src/global-install.ts
|
|
10026
|
-
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync4, mkdirSync as mkdirSync8, rmSync as rmSync2 } from "fs";
|
|
10027
|
-
import { resolve as resolve4, join as join11, dirname as dirname3 } from "path";
|
|
10028
|
-
import { homedir as homedir9 } from "os";
|
|
10029
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10030
|
-
import { createInterface } from "readline";
|
|
10031
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
10032
|
-
function lockFile(file) {
|
|
10033
|
-
if (!existsSync4(file)) return;
|
|
10034
|
-
try {
|
|
10035
|
-
if (process.platform === "win32") {
|
|
10036
|
-
try {
|
|
10037
|
-
execFileSync2("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE)"], { stdio: "ignore" });
|
|
10038
|
-
} catch {
|
|
10039
|
-
}
|
|
10040
|
-
try {
|
|
10041
|
-
execFileSync2("attrib", ["+R", file], { stdio: "ignore" });
|
|
10042
|
-
} catch {
|
|
10043
|
-
}
|
|
10044
|
-
} else if (process.platform === "darwin") {
|
|
10045
|
-
try {
|
|
10046
|
-
execFileSync2("chflags", ["uchg", file], { stdio: "ignore" });
|
|
10047
|
-
} catch {
|
|
10048
|
-
}
|
|
10049
|
-
} else {
|
|
10050
|
-
try {
|
|
10051
|
-
execFileSync2("chattr", ["+i", file], { stdio: "ignore" });
|
|
10052
|
-
} catch {
|
|
10053
|
-
}
|
|
10054
|
-
}
|
|
10055
|
-
} catch {
|
|
10056
|
-
}
|
|
10057
|
-
}
|
|
10058
|
-
function unlockFile(file) {
|
|
10059
|
-
if (!existsSync4(file)) return;
|
|
10060
|
-
try {
|
|
10061
|
-
if (process.platform === "win32") {
|
|
10062
|
-
try {
|
|
10063
|
-
execFileSync2("icacls", [file, "/remove:d", "*S-1-1-0"], { stdio: "ignore" });
|
|
10064
|
-
} catch {
|
|
10065
|
-
}
|
|
10066
|
-
try {
|
|
10067
|
-
execFileSync2("icacls", [file, "/reset"], { stdio: "ignore" });
|
|
10068
|
-
} catch {
|
|
10069
|
-
}
|
|
10070
|
-
try {
|
|
10071
|
-
execFileSync2("attrib", ["-R", file], { stdio: "ignore" });
|
|
10072
|
-
} catch {
|
|
10073
|
-
}
|
|
10074
|
-
} else if (process.platform === "darwin") {
|
|
10075
|
-
try {
|
|
10076
|
-
execFileSync2("chflags", ["nouchg", file], { stdio: "ignore" });
|
|
10077
|
-
} catch {
|
|
10078
|
-
}
|
|
10079
|
-
} else {
|
|
10080
|
-
try {
|
|
10081
|
-
execFileSync2("chattr", ["-i", file], { stdio: "ignore" });
|
|
10082
|
-
} catch {
|
|
10083
|
-
}
|
|
10084
|
-
}
|
|
10085
|
-
} catch {
|
|
10086
|
-
}
|
|
10087
|
-
}
|
|
10088
|
-
function protectedTargets() {
|
|
10089
|
-
const p = globalPaths();
|
|
10090
|
-
return [
|
|
10091
|
-
join11(p.hooksDir, "guard.mjs"),
|
|
10092
|
-
join11(p.hooksDir, "audit.mjs"),
|
|
10093
|
-
join11(p.hooksDir, "stop.mjs"),
|
|
10094
|
-
join11(p.hooksDir, "shield.mjs"),
|
|
10095
|
-
p.configPath,
|
|
10096
|
-
p.settingsPath,
|
|
10097
|
-
p.antigravityHooksPath
|
|
10098
|
-
];
|
|
10099
|
-
}
|
|
10100
|
-
function lockProtected() {
|
|
10101
|
-
for (const f of protectedTargets()) lockFile(f);
|
|
10102
|
-
}
|
|
10103
|
-
function unlockProtected() {
|
|
10104
|
-
for (const f of protectedTargets()) unlockFile(f);
|
|
10105
|
-
}
|
|
10106
|
-
function globalPaths() {
|
|
10107
|
-
const home = homedir9();
|
|
10108
|
-
const sgDir = join11(home, ".solongate");
|
|
10109
|
-
const hooksDir = join11(sgDir, "hooks");
|
|
10110
|
-
const claudeDir = join11(home, ".claude");
|
|
10111
|
-
const antigravityDir = join11(home, ".gemini", "config");
|
|
10112
|
-
return {
|
|
10113
|
-
home,
|
|
10114
|
-
sgDir,
|
|
10115
|
-
hooksDir,
|
|
10116
|
-
claudeDir,
|
|
10117
|
-
antigravityDir,
|
|
10118
|
-
settingsPath: join11(claudeDir, "settings.json"),
|
|
10119
|
-
backupPath: join11(claudeDir, "settings.solongate.bak"),
|
|
10120
|
-
configPath: join11(sgDir, "cloud-guard.json"),
|
|
10121
|
-
// Antigravity reads global hooks from ~/.gemini/config/hooks.json. Only the
|
|
10122
|
-
// guard is registered there (PreToolUse); Antigravity's ALLOW-path audit is
|
|
10123
|
-
// covered by the passive session-log collector, not a hook.
|
|
10124
|
-
antigravityHooksPath: join11(antigravityDir, "hooks.json"),
|
|
10125
|
-
antigravityBackupPath: join11(antigravityDir, "hooks.solongate.bak")
|
|
10126
|
-
};
|
|
10127
|
-
}
|
|
10128
|
-
function readHook(filename) {
|
|
10129
|
-
return readFileSync9(join11(HOOKS_DIR, filename), "utf-8");
|
|
10130
|
-
}
|
|
10131
|
-
function firstAccountCredential() {
|
|
10132
|
-
try {
|
|
10133
|
-
const raw = JSON.parse(readFileSync9(join11(homedir9(), ".solongate", "accounts.json"), "utf-8"));
|
|
10134
|
-
if (Array.isArray(raw)) {
|
|
10135
|
-
const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
|
|
10136
|
-
if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
|
|
10137
|
-
}
|
|
10138
|
-
} catch {
|
|
10139
|
-
}
|
|
10140
|
-
return {};
|
|
10141
|
-
}
|
|
10142
|
-
function readGuard() {
|
|
10143
|
-
const bundled = join11(HOOKS_DIR, "guard.bundled.mjs");
|
|
10144
|
-
return existsSync4(bundled) ? readFileSync9(bundled, "utf-8") : readHook("guard.mjs");
|
|
10145
|
-
}
|
|
10146
|
-
function antigravityHookCommand(guardAbs) {
|
|
10147
|
-
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
10148
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
10149
|
-
return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" antigravity "Antigravity"`;
|
|
10150
|
-
}
|
|
10151
|
-
function installAntigravityGuard(p, guardAbs) {
|
|
10152
|
-
mkdirSync8(p.antigravityDir, { recursive: true });
|
|
10153
|
-
let existing = {};
|
|
10154
|
-
if (existsSync4(p.antigravityHooksPath)) {
|
|
10155
|
-
const raw = readFileSync9(p.antigravityHooksPath, "utf-8");
|
|
10156
|
-
if (!existsSync4(p.antigravityBackupPath)) writeFileSync9(p.antigravityBackupPath, raw);
|
|
10157
|
-
try {
|
|
10158
|
-
existing = JSON.parse(raw);
|
|
10159
|
-
} catch {
|
|
10160
|
-
existing = {};
|
|
10161
|
-
}
|
|
10162
|
-
}
|
|
10163
|
-
const merged = {
|
|
10164
|
-
...existing,
|
|
10165
|
-
[ANTIGRAVITY_GROUP]: {
|
|
10166
|
-
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: antigravityHookCommand(guardAbs) }] }]
|
|
10167
|
-
}
|
|
10168
|
-
};
|
|
10169
|
-
writeFileSync9(p.antigravityHooksPath, JSON.stringify(merged, null, 2) + "\n");
|
|
10170
|
-
}
|
|
10171
|
-
function removeAntigravityGuard(p) {
|
|
10172
|
-
if (!existsSync4(p.antigravityHooksPath)) return;
|
|
10173
|
-
try {
|
|
10174
|
-
const s = JSON.parse(readFileSync9(p.antigravityHooksPath, "utf-8"));
|
|
10175
|
-
if (!(ANTIGRAVITY_GROUP in s)) return;
|
|
10176
|
-
delete s[ANTIGRAVITY_GROUP];
|
|
10177
|
-
writeFileSync9(p.antigravityHooksPath, JSON.stringify(s, null, 2) + "\n");
|
|
10178
|
-
} catch {
|
|
10179
|
-
}
|
|
10180
|
-
}
|
|
10181
|
-
function installGlobalQuiet() {
|
|
10182
|
-
try {
|
|
10183
|
-
const p = globalPaths();
|
|
10184
|
-
let apiKey = process.env["SOLONGATE_API_KEY"] || "";
|
|
10185
|
-
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
10186
|
-
try {
|
|
10187
|
-
const cfg = JSON.parse(readFileSync9(p.configPath, "utf-8"));
|
|
10188
|
-
if (cfg && typeof cfg.apiKey === "string") apiKey = apiKey || cfg.apiKey;
|
|
10189
|
-
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
10190
|
-
} catch {
|
|
10191
|
-
}
|
|
10192
|
-
if (!apiKey) {
|
|
10193
|
-
const acc = firstAccountCredential();
|
|
10194
|
-
if (acc.apiKey) {
|
|
10195
|
-
apiKey = acc.apiKey;
|
|
10196
|
-
if (acc.apiUrl) apiUrl = acc.apiUrl;
|
|
10197
|
-
}
|
|
10198
|
-
}
|
|
10199
|
-
if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
|
|
10200
|
-
mkdirSync8(p.hooksDir, { recursive: true });
|
|
10201
|
-
mkdirSync8(p.claudeDir, { recursive: true });
|
|
10202
|
-
unlockProtected();
|
|
10203
|
-
writeFileSync9(join11(p.hooksDir, "guard.mjs"), readGuard());
|
|
10204
|
-
writeFileSync9(join11(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
10205
|
-
writeFileSync9(join11(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
10206
|
-
writeFileSync9(join11(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
10207
|
-
writeFileSync9(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
10208
|
-
let existing = {};
|
|
10209
|
-
if (existsSync4(p.settingsPath)) {
|
|
10210
|
-
const raw = readFileSync9(p.settingsPath, "utf-8");
|
|
10211
|
-
if (!existsSync4(p.backupPath)) writeFileSync9(p.backupPath, raw);
|
|
10212
|
-
try {
|
|
10213
|
-
existing = JSON.parse(raw);
|
|
10214
|
-
} catch {
|
|
10215
|
-
existing = {};
|
|
10216
|
-
}
|
|
10217
|
-
}
|
|
10218
|
-
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
10219
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
10220
|
-
const hookCmd = (script) => `${call}"${nodeBin}" "${join11(p.hooksDir, script).replace(/\\/g, "/")}" claude-code "Claude Code"`;
|
|
10221
|
-
const merged = {
|
|
10222
|
-
...existing,
|
|
10223
|
-
hooks: {
|
|
10224
|
-
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("guard.mjs") }] }],
|
|
10225
|
-
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("audit.mjs") }] }],
|
|
10226
|
-
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("stop.mjs") }] }]
|
|
10227
|
-
}
|
|
10228
|
-
};
|
|
10229
|
-
writeFileSync9(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
10230
|
-
try {
|
|
10231
|
-
installAntigravityGuard(p, join11(p.hooksDir, "guard.mjs").replace(/\\/g, "/"));
|
|
10232
|
-
} catch {
|
|
10233
|
-
}
|
|
10234
|
-
if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
|
|
10235
|
-
return { ok: true, message: "guard installed (open a new session)" };
|
|
10236
|
-
} catch (e) {
|
|
10237
|
-
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
10238
|
-
}
|
|
10239
|
-
}
|
|
10240
|
-
function installedGuardVersion() {
|
|
10241
|
-
try {
|
|
10242
|
-
const p = globalPaths();
|
|
10243
|
-
const s = readFileSync9(join11(p.hooksDir, "guard.mjs"), "utf-8");
|
|
10244
|
-
const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
|
|
10245
|
-
return m ? parseInt(m[1], 10) : null;
|
|
10246
|
-
} catch {
|
|
10247
|
-
return null;
|
|
10248
|
-
}
|
|
10249
|
-
}
|
|
10250
|
-
function guardHookOutdated() {
|
|
10251
|
-
try {
|
|
10252
|
-
const p = globalPaths();
|
|
10253
|
-
return readFileSync9(join11(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
|
|
10254
|
-
} catch {
|
|
10255
|
-
return false;
|
|
10256
|
-
}
|
|
10257
|
-
}
|
|
10258
|
-
function isGuardInstalled() {
|
|
10259
|
-
try {
|
|
10260
|
-
const p = globalPaths();
|
|
10261
|
-
if (!existsSync4(p.settingsPath)) return false;
|
|
10262
|
-
const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
|
|
10263
|
-
return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
|
|
10264
|
-
} catch {
|
|
10265
|
-
return false;
|
|
10266
|
-
}
|
|
10267
|
-
}
|
|
10268
|
-
function uninstallGlobalQuiet() {
|
|
10269
|
-
try {
|
|
10270
|
-
const p = globalPaths();
|
|
10271
|
-
unlockProtected();
|
|
10272
|
-
removeClaudeShim();
|
|
10273
|
-
try {
|
|
10274
|
-
removeAntigravityGuard(p);
|
|
10275
|
-
} catch {
|
|
10276
|
-
}
|
|
10277
|
-
if (!existsSync4(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
|
|
10278
|
-
const s = JSON.parse(readFileSync9(p.settingsPath, "utf-8"));
|
|
10279
|
-
delete s.hooks;
|
|
10280
|
-
writeFileSync9(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
10281
|
-
return { ok: true, message: "guard removed (open a new session)" };
|
|
10282
|
-
} catch (e) {
|
|
10283
|
-
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
10284
|
-
}
|
|
10285
|
-
}
|
|
10286
|
-
function escapeRe(s) {
|
|
10287
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10288
|
-
}
|
|
10289
|
-
function shimTargets() {
|
|
10290
|
-
if (process.platform === "win32") {
|
|
10291
|
-
try {
|
|
10292
|
-
const prof = execFileSync2("powershell", ["-NoProfile", "-Command", "$PROFILE.CurrentUserAllHosts"], { encoding: "utf-8" }).trim();
|
|
10293
|
-
return prof ? [prof] : [];
|
|
10294
|
-
} catch {
|
|
10295
|
-
return [];
|
|
10296
|
-
}
|
|
10297
|
-
}
|
|
10298
|
-
return [".bashrc", ".zshrc", ".profile"].map((f) => join11(homedir9(), f)).filter((f) => existsSync4(f));
|
|
10299
|
-
}
|
|
10300
|
-
function writeShimBlock(file, block2) {
|
|
10301
|
-
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
10302
|
-
let content = existsSync4(file) ? readFileSync9(file, "utf-8") : "";
|
|
10303
|
-
content = content.replace(re, "");
|
|
10304
|
-
if (block2) {
|
|
10305
|
-
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
10306
|
-
content += block2 + "\n";
|
|
10282
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 agent " }),
|
|
10283
|
+
/* @__PURE__ */ jsx7(Text7, { children: e.agent ?? "\u2014" }),
|
|
10284
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502" })
|
|
10285
|
+
] }),
|
|
10286
|
+
/* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
10287
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2502 session " }),
|
|
10288
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: e.session ?? "\u2014" }),
|
|
10289
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 rule " }),
|
|
10290
|
+
/* @__PURE__ */ jsx7(Text7, { color: e.rule && e.decision !== "ALLOW" ? theme.bad : theme.dim, children: e.rule ?? "\u2014" }),
|
|
10291
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502" })
|
|
10292
|
+
] }),
|
|
10293
|
+
/* @__PURE__ */ jsx7(PaneTitle, { label: "FULL CONTENT", extra: `${contentLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""} \xB7 space copy \xB7 \u2190 back`, width: innerW }),
|
|
10294
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: l || " " }, off + i)) }),
|
|
10295
|
+
/* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
10296
|
+
/* @__PURE__ */ jsx7(Text7, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
|
|
10297
|
+
/* @__PURE__ */ jsx7(Text7, { backgroundColor: "#0b1530", color: "white", children: ` ${e.id} ` }),
|
|
10298
|
+
/* @__PURE__ */ jsx7(Text7, { backgroundColor: BG2, color: "white", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
|
|
10299
|
+
] })
|
|
10300
|
+
] });
|
|
10307
10301
|
}
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10302
|
+
const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
10303
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
10304
|
+
label,
|
|
10305
|
+
":"
|
|
10306
|
+
] }),
|
|
10307
|
+
/* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
|
|
10308
|
+
/* @__PURE__ */ jsx7(Text7, { children: " " })
|
|
10309
|
+
] });
|
|
10310
|
+
if (view === "sessions") {
|
|
10311
|
+
const headerRows2 = 5 + (editing ? 1 : 0);
|
|
10312
|
+
const listRows2 = Math.max(4, rows - headerRows2);
|
|
10313
|
+
const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
|
|
10314
|
+
const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
|
|
10315
|
+
const win = sessionsFiltered.slice(start2, start2 + listRows2);
|
|
10316
|
+
return /* @__PURE__ */ jsx7(DataView, { loading: sessLoading && !frozen, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
10317
|
+
strip,
|
|
10318
|
+
copyBanner,
|
|
10319
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
10320
|
+
srcChip,
|
|
10321
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
|
|
10322
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 logs (v) " }),
|
|
10323
|
+
chip("status", SESS_STATUS[si] ?? "all", si !== 0),
|
|
10324
|
+
chip("search", sessSearch || "\xB7", !!sessSearch)
|
|
10325
|
+
] }),
|
|
10326
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter \u2192 session logs \xB7 v logs \xB7 ^R refresh \xB7 ? all keys" : "press \u2192 to browse" }),
|
|
10327
|
+
editing === "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
10328
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "search: " }),
|
|
10329
|
+
/* @__PURE__ */ jsx7(
|
|
10330
|
+
TextInput5,
|
|
10331
|
+
{
|
|
10332
|
+
value: sessSearch,
|
|
10333
|
+
onChange: (v) => {
|
|
10334
|
+
setSessSearch(v);
|
|
10335
|
+
setSessSel(0);
|
|
10336
|
+
},
|
|
10337
|
+
onSubmit: () => setEditing(null)
|
|
10338
|
+
}
|
|
10339
|
+
)
|
|
10340
|
+
] }) : null,
|
|
10341
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: `${sessionsFiltered.length} sessions \xB7 ${selC + 1}/${sessionsFiltered.length}${start2 ? ` \xB7 \u25B2${start2}` : ""}${start2 + listRows2 < sessionsFiltered.length ? ` \xB7 \u25BC${sessionsFiltered.length - start2 - listRows2}` : ""}` }),
|
|
10342
|
+
/* @__PURE__ */ jsx7(
|
|
10343
|
+
Table,
|
|
10344
|
+
{
|
|
10345
|
+
columns: [
|
|
10346
|
+
{ header: "", width: 2 },
|
|
10347
|
+
{ header: "STATUS", width: 8 },
|
|
10348
|
+
{ header: "AGENT", width: 18 },
|
|
10349
|
+
{ header: "SESSION", width: 10 },
|
|
10350
|
+
{ header: "CALLS", width: 6 },
|
|
10351
|
+
{ header: "DENY", width: 5 },
|
|
10352
|
+
{ header: "DLP", width: 4 },
|
|
10353
|
+
{ header: "TRUST", width: 5 },
|
|
10354
|
+
{ header: "LAST", width: 6 }
|
|
10355
|
+
],
|
|
10356
|
+
rows: win.map((r, i) => {
|
|
10357
|
+
const st = STATUS_DOT[r.status];
|
|
10358
|
+
const isSel = start2 + i === selC && focused;
|
|
10359
|
+
return [
|
|
10360
|
+
{ value: isSel ? "\u25B8" : "", color: theme.accentBright },
|
|
10361
|
+
{ value: `${st.ch} ${r.status}`, color: st.color },
|
|
10362
|
+
{ value: truncate2(r.agent, 18), color: theme.accent, bold: isSel },
|
|
10363
|
+
{ value: r.id.slice(0, 8), dim: true },
|
|
10364
|
+
{ value: String(r.calls) },
|
|
10365
|
+
{ value: String(r.denies), color: r.denies ? theme.bad : void 0, dim: !r.denies },
|
|
10366
|
+
{ value: String(r.dlp), color: r.dlp ? theme.bad : void 0, dim: !r.dlp },
|
|
10367
|
+
{ value: r.trust != null ? String(r.trust) : "\u2014", dim: true },
|
|
10368
|
+
{ value: ago(r.lastAt), dim: true }
|
|
10369
|
+
];
|
|
10370
|
+
})
|
|
10371
|
+
}
|
|
10372
|
+
),
|
|
10373
|
+
sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
10374
|
+
"(no sessions",
|
|
10375
|
+
source === "local" ? " in the local file" : "",
|
|
10376
|
+
")"
|
|
10377
|
+
] }) : null
|
|
10378
|
+
] }) });
|
|
10317
10379
|
}
|
|
10380
|
+
const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0) + (msg ? 1 : 0) + (frozen ? 1 : 0);
|
|
10381
|
+
const listRows = Math.max(4, rows - headerRows);
|
|
10382
|
+
const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
|
|
10383
|
+
const maxStart = Math.max(0, pageRows.length - listRows);
|
|
10384
|
+
const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
|
|
10385
|
+
const windowed = pageRows.slice(start, start + listRows);
|
|
10386
|
+
return /* @__PURE__ */ jsx7(DataView, { loading: logsLoading && !frozen, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
10387
|
+
strip,
|
|
10388
|
+
copyBanner,
|
|
10389
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
10390
|
+
srcChip,
|
|
10391
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, children: "LOGS" }),
|
|
10392
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 sessions (v) " }),
|
|
10393
|
+
chip("dec", DECISIONS[di] ?? "all", di !== 0),
|
|
10394
|
+
chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
|
|
10395
|
+
chip("tool", tool || "\xB7", !!tool),
|
|
10396
|
+
chip("agent", agent || "\xB7", !!agent),
|
|
10397
|
+
chip("search", search || "\xB7", !!search),
|
|
10398
|
+
sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
|
|
10399
|
+
] }),
|
|
10400
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 v sessions \xB7 ^R refresh \xB7 ? all keys" : "press \u2192 to browse" }),
|
|
10401
|
+
msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : null,
|
|
10402
|
+
editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
10403
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
|
|
10404
|
+
editing,
|
|
10405
|
+
": "
|
|
10406
|
+
] }),
|
|
10407
|
+
/* @__PURE__ */ jsx7(
|
|
10408
|
+
TextInput5,
|
|
10409
|
+
{
|
|
10410
|
+
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
10411
|
+
onChange: (v) => {
|
|
10412
|
+
(editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
|
|
10413
|
+
setPage(0);
|
|
10414
|
+
toTop();
|
|
10415
|
+
},
|
|
10416
|
+
onSubmit: () => setEditing(null)
|
|
10417
|
+
}
|
|
10418
|
+
)
|
|
10419
|
+
] }) : null,
|
|
10420
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: `${total} matched \xB7 page ${Math.min(page + 1, pages)}/${pages} \xB7 ${PAGE}/page \xB7 ${pageRows.length ? selClamped + 1 : 0}/${pageRows.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < pageRows.length ? ` \xB7 \u25BC${pageRows.length - start - listRows} older` : ""}` }),
|
|
10421
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx7(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused, date: true }, e.id)) }),
|
|
10422
|
+
pageRows.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
10423
|
+
"(no entries",
|
|
10424
|
+
source === "local" ? " in the local file" : "",
|
|
10425
|
+
" \u2014 c clears filters)"
|
|
10426
|
+
] }) : null
|
|
10427
|
+
] }) });
|
|
10318
10428
|
}
|
|
10319
|
-
var
|
|
10320
|
-
var
|
|
10321
|
-
"src/
|
|
10429
|
+
var DECISIONS, SIGNALS, SESS_STATUS, PAGE, LOCAL_MAX_BYTES, BG2, toStream, cloudRow, AUDIT_HELP, sessStatus2, STATUS_DOT;
|
|
10430
|
+
var init_Audit = __esm({
|
|
10431
|
+
"src/tui/panels/Audit.tsx"() {
|
|
10322
10432
|
"use strict";
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10433
|
+
init_api_client();
|
|
10434
|
+
init_components();
|
|
10435
|
+
init_hooks();
|
|
10436
|
+
init_local_log();
|
|
10437
|
+
init_theme();
|
|
10438
|
+
DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
10439
|
+
SIGNALS = [void 0, "dlp", "ratelimit"];
|
|
10440
|
+
SESS_STATUS = [void 0, "active", "idle", "ended"];
|
|
10441
|
+
PAGE = 500;
|
|
10442
|
+
LOCAL_MAX_BYTES = 16 * 1024 * 1024;
|
|
10443
|
+
BG2 = "#12234f";
|
|
10444
|
+
toStream = (e) => ({
|
|
10445
|
+
at: e.at,
|
|
10446
|
+
tool: e.tool,
|
|
10447
|
+
decision: e.decision,
|
|
10448
|
+
permission: (e.permission ?? "").slice(0, 4),
|
|
10449
|
+
detail: (e.args ?? e.reason ?? "").replace(/\s+/g, " "),
|
|
10450
|
+
dlp: e.dlp.length > 0,
|
|
10451
|
+
burst: e.burst,
|
|
10452
|
+
agent: e.agent,
|
|
10453
|
+
evalMs: e.evalMs,
|
|
10454
|
+
rule: e.rule
|
|
10455
|
+
});
|
|
10456
|
+
cloudRow = (e) => {
|
|
10457
|
+
const rs = reasonSignals(e.reason);
|
|
10458
|
+
return {
|
|
10459
|
+
id: e.id,
|
|
10460
|
+
at: Date.parse(e.created_at),
|
|
10461
|
+
tool: e.tool_name,
|
|
10462
|
+
decision: e.decision,
|
|
10463
|
+
permission: e.permission,
|
|
10464
|
+
trust: e.trust_level,
|
|
10465
|
+
agent: e.agent_name,
|
|
10466
|
+
session: e.session_id,
|
|
10467
|
+
reason: e.reason,
|
|
10468
|
+
rule: e.matched_rule_id,
|
|
10469
|
+
evalMs: e.evaluation_time_ms,
|
|
10470
|
+
dlp: e.dlp_matches?.length ? e.dlp_matches : rs.dlp,
|
|
10471
|
+
burst: !!e.rate_limit_burst || rs.burst,
|
|
10472
|
+
args: e.arguments_summary ? JSON.stringify(e.arguments_summary) : null
|
|
10473
|
+
};
|
|
10474
|
+
};
|
|
10475
|
+
AUDIT_HELP = [
|
|
10476
|
+
[
|
|
10477
|
+
"Logs",
|
|
10478
|
+
[
|
|
10479
|
+
["\u2191\u2193 / PgUp PgDn", "select a row (window follows)"],
|
|
10480
|
+
["enter", "open the FULL entry (reason + arguments)"],
|
|
10481
|
+
["\u2190 \u2192", "previous / next page (500 per page, jumps to top)"],
|
|
10482
|
+
["f", "decision filter: all \u2192 DENY \u2192 ALLOW"],
|
|
10483
|
+
["g", "signal filter: all \u2192 dlp \u2192 ratelimit"],
|
|
10484
|
+
["t / n", "tool / agent filter (type, enter done)"],
|
|
10485
|
+
["/", "free-text search"],
|
|
10486
|
+
["x", "delete ONLY the selected entry (press x twice)"],
|
|
10487
|
+
["X", "delete ALL matched logs of the source (press X twice)"],
|
|
10488
|
+
["e", "export this page \u2192 ~/.solongate/audit-export-<src>.jsonl"],
|
|
10489
|
+
["E", "export ALL matched rows (cloud: up to 10k)"],
|
|
10490
|
+
["c", "clear every filter (incl. session)"]
|
|
10491
|
+
]
|
|
10492
|
+
],
|
|
10493
|
+
[
|
|
10494
|
+
"Sessions",
|
|
10495
|
+
[
|
|
10496
|
+
["\u2191\u2193", "select a session"],
|
|
10497
|
+
["enter", "open that session's logs"],
|
|
10498
|
+
["f", "status filter: all \u2192 active \u2192 idle \u2192 ended"],
|
|
10499
|
+
["/", "search agent / session id"],
|
|
10500
|
+
["c", "clear session filters"]
|
|
10501
|
+
]
|
|
10502
|
+
],
|
|
10503
|
+
[
|
|
10504
|
+
"Anywhere in Audit",
|
|
10505
|
+
[
|
|
10506
|
+
["v", "switch logs \u2194 sessions"],
|
|
10507
|
+
["s", "switch source cloud \u2194 local file"],
|
|
10508
|
+
["space", "copy mode: freeze screen for mouse selection"],
|
|
10509
|
+
["?", "this help \xB7 any key closes"],
|
|
10510
|
+
["esc", "back to the menu"]
|
|
10511
|
+
]
|
|
10512
|
+
],
|
|
10513
|
+
["Entry (full content)", [["\u2191\u2193 / PgUp PgDn", "scroll the reason + arguments"], ["space", "copy mode (freeze, then select)"], ["\u2190 / esc", "back to the list"]]]
|
|
10514
|
+
];
|
|
10515
|
+
sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
|
|
10516
|
+
STATUS_DOT = {
|
|
10517
|
+
active: { ch: "\u25CF", color: theme.ok },
|
|
10518
|
+
idle: { ch: "\u25D0", color: theme.warn },
|
|
10519
|
+
ended: { ch: "\u25CB", color: theme.dim }
|
|
10520
|
+
};
|
|
10328
10521
|
}
|
|
10329
10522
|
});
|
|
10330
10523
|
|
|
@@ -11394,6 +11587,22 @@ async function run(argv) {
|
|
|
11394
11587
|
printRules(p.rules);
|
|
11395
11588
|
return 0;
|
|
11396
11589
|
}
|
|
11590
|
+
case "create": {
|
|
11591
|
+
const name = positionals.slice(1).join(" ").trim();
|
|
11592
|
+
if (!name) return err(" Usage: policy create <name>"), 1;
|
|
11593
|
+
const res = await api.policies.create({ id: `policy-${Date.now()}`, name, rules: [], mode: "denylist" });
|
|
11594
|
+
if (json) return printJson(res), 0;
|
|
11595
|
+
err(green(` \u2713 Created "${name}"`) + dim(` (${res.id})`));
|
|
11596
|
+
return 0;
|
|
11597
|
+
}
|
|
11598
|
+
case "delete": {
|
|
11599
|
+
const id = positionals[1];
|
|
11600
|
+
if (!id) return err(" Usage: policy delete <id>"), 1;
|
|
11601
|
+
const res = await api.policies.remove(id);
|
|
11602
|
+
if (json) return printJson(res), 0;
|
|
11603
|
+
err(green(` \u2713 Deleted ${res.policy_id}`));
|
|
11604
|
+
return 0;
|
|
11605
|
+
}
|
|
11397
11606
|
case "allow":
|
|
11398
11607
|
case "deny": {
|
|
11399
11608
|
const effect = sub === "allow" ? "ALLOW" : "DENY";
|
|
@@ -11508,6 +11717,8 @@ var init_policy = __esm({
|
|
|
11508
11717
|
init_format();
|
|
11509
11718
|
USAGE = usage("solongate policy", "manage cloud policies", [
|
|
11510
11719
|
["policy list", "list all policies"],
|
|
11720
|
+
["policy create <name>", "create a new empty policy"],
|
|
11721
|
+
["policy delete <id>", "delete a policy"],
|
|
11511
11722
|
["policy show <id>", "show one policy (rules, mode)"],
|
|
11512
11723
|
["policy allow <id> [--command|--path|--url <val>]", "append an ALLOW rule"],
|
|
11513
11724
|
["policy deny <id> [--command|--path|--url <val>]", "append a DENY rule"],
|
|
@@ -15844,7 +16055,7 @@ ${msg.content.text}`;
|
|
|
15844
16055
|
|
|
15845
16056
|
// src/index.ts
|
|
15846
16057
|
init_cli_utils();
|
|
15847
|
-
var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["logs-server", "local-logs", "policy", "ratelimit", "dlp", "stats", "audit", "sessions", "session", "doctor", "watch", "alerts", "webhooks", "dataroom"]);
|
|
16058
|
+
var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["update", "logs-server", "local-logs", "policy", "ratelimit", "dlp", "stats", "audit", "sessions", "session", "doctor", "watch", "alerts", "webhooks", "dataroom"]);
|
|
15848
16059
|
var CLI_INFO_ARGS = /* @__PURE__ */ new Set(["login", "help", "--help", "-h", "--version", "-v", "version"]);
|
|
15849
16060
|
var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "") || CLI_INFO_ARGS.has(process.argv[2] ?? "");
|
|
15850
16061
|
if (!IS_HUMAN_CLI) {
|
|
@@ -15905,6 +16116,7 @@ function printHelp() {
|
|
|
15905
16116
|
console.log(` ${c.dim}Usage:${c.reset} ${c.cyan}solongate${c.reset} ${c.dim}[command]${c.reset} ${c.dim}(no command opens the dataroom: all of this in a terminal UI)${c.reset}`);
|
|
15906
16117
|
head("Setup & status");
|
|
15907
16118
|
cmd("solongate", "open the dataroom UI (login, policies, audit, settings)");
|
|
16119
|
+
cmd("update", "update SolonGate to the newest version and refresh the guard");
|
|
15908
16120
|
cmd("doctor", "health check: login, policy, guard, local logs");
|
|
15909
16121
|
cmd("doctor --json", "the same health check as machine-readable JSON");
|
|
15910
16122
|
cmd("logs-server start", "start the local audit-log service for the dashboard (background)");
|
|
@@ -15912,6 +16124,8 @@ function printHelp() {
|
|
|
15912
16124
|
cmd("logs-server status", "show the local audit-log service status");
|
|
15913
16125
|
head("Policies");
|
|
15914
16126
|
cmd("policy list", "list all policies");
|
|
16127
|
+
cmd("policy create <name>", "create a new empty policy");
|
|
16128
|
+
cmd("policy delete <id>", "delete a policy");
|
|
15915
16129
|
cmd("policy show <id>", "show one policy (rules, mode)");
|
|
15916
16130
|
cmd("policy allow <id> [--command|--path|--url <val>]", "add an ALLOW rule");
|
|
15917
16131
|
cmd("policy deny <id> [--command|--path|--url <val>]", "add a DENY rule");
|
|
@@ -15998,6 +16212,10 @@ async function main() {
|
|
|
15998
16212
|
console.log("");
|
|
15999
16213
|
return;
|
|
16000
16214
|
}
|
|
16215
|
+
if (subcommand === "update") {
|
|
16216
|
+
const { runUpdateCommand: runUpdateCommand2 } = await Promise.resolve().then(() => (init_self_update(), self_update_exports));
|
|
16217
|
+
process.exit(await runUpdateCommand2());
|
|
16218
|
+
}
|
|
16001
16219
|
if (subcommand === "logs-server" || subcommand === "local-logs") {
|
|
16002
16220
|
const { runLogsServer: runLogsServer2 } = await Promise.resolve().then(() => (init_logs_server(), logs_server_exports));
|
|
16003
16221
|
await runLogsServer2();
|