xaq-cli 0.1.0-nightly.87 → 0.1.0-nightly.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/{admin-CoriRb5Z.js → admin-B0qZYQ8g.js} +4 -4
- package/dist/{admin-CoriRb5Z.js.map → admin-B0qZYQ8g.js.map} +1 -1
- package/dist/{admin-BzWIG9E5.js → admin-BTERuaPM.js} +4 -4
- package/dist/{admin-BzWIG9E5.js.map → admin-BTERuaPM.js.map} +1 -1
- package/dist/{admin-CFldldag.js → admin-DbJXEo2d.js} +1 -1
- package/dist/agent-B8a-Ivyd.js +2 -0
- package/dist/agent-mYT2noTp.js +52 -0
- package/dist/agent-mYT2noTp.js.map +1 -0
- package/dist/{api-D_RsOpvQ.js → api-_Frr5NsJ.js} +2 -2
- package/dist/{api-D_RsOpvQ.js.map → api-_Frr5NsJ.js.map} +1 -1
- package/dist/attachment-parser.js +78104 -0
- package/dist/{auth-B8_i8XFz.js → auth-CcvstbI5.js} +1 -1
- package/dist/{auth-BLYwD_zT.js → auth-D4QQM-EQ.js} +2 -2
- package/dist/{auth-BLYwD_zT.js.map → auth-D4QQM-EQ.js.map} +1 -1
- package/dist/{cli-Vc6EdaKf.js → cli-B8cuKWiS.js} +4 -4
- package/dist/{cli-Vc6EdaKf.js.map → cli-B8cuKWiS.js.map} +1 -1
- package/dist/cli-DozpH5Tw.js +126 -0
- package/dist/cli-DozpH5Tw.js.map +1 -0
- package/dist/cli.js +19 -17
- package/dist/cli.js.map +1 -1
- package/dist/{commands-k14buGhD.js → commands-Bb3Jp0f_.js} +15 -15
- package/dist/{commands-k14buGhD.js.map → commands-Bb3Jp0f_.js.map} +1 -1
- package/dist/{commands-DIzX5CG3.js → commands-vEfok6bQ.js} +11 -11
- package/dist/{commands-DIzX5CG3.js.map → commands-vEfok6bQ.js.map} +1 -1
- package/dist/{config-CHNizKd3.js → config-CAqDP8xi.js} +1 -1
- package/dist/{config-CScDGXOW.js → config-DzPEJM-P.js} +2 -2
- package/dist/{config-CScDGXOW.js.map → config-DzPEJM-P.js.map} +1 -1
- package/dist/{connect-CFhUTklP.js → connect-DzW9840r.js} +4 -4
- package/dist/{connect-CFhUTklP.js.map → connect-DzW9840r.js.map} +1 -1
- package/dist/{content-C1lKMxFS.js → content-Di_U9ZZ6.js} +5 -3
- package/dist/content-Di_U9ZZ6.js.map +1 -0
- package/dist/content-worker.js +1 -1
- package/dist/{hermes-DTqHu0Hd.js → hermes-Ccdi1g6M.js} +2 -2
- package/dist/{hermes-DTqHu0Hd.js.map → hermes-Ccdi1g6M.js.map} +1 -1
- package/dist/hermes-neOJoelw.js +3 -0
- package/dist/ids-DtsPJB4V.js +16 -0
- package/dist/ids-DtsPJB4V.js.map +1 -0
- package/dist/{import-BG7UOCpl.js → import-B-xgD799.js} +2 -2
- package/dist/{import-BG7UOCpl.js.map → import-B-xgD799.js.map} +1 -1
- package/dist/instance-BsILpEQ7.js +2 -0
- package/dist/{instance-B6H0XH7S.js → instance-DoBa4kgq.js} +2 -2
- package/dist/{instance-B6H0XH7S.js.map → instance-DoBa4kgq.js.map} +1 -1
- package/dist/{listen-DZPX71t5.js → listen-B6u7Gt6X.js} +2 -2
- package/dist/{listen-DZPX71t5.js.map → listen-B6u7Gt6X.js.map} +1 -1
- package/dist/listen-BgNbGAri.js +2 -0
- package/dist/{manage-CoA2l484.js → manage-C6pUOFQK.js} +1 -1
- package/dist/{manage-BVssYjLB.js → manage-EK_UQB4B.js} +3 -2
- package/dist/{manage-BVssYjLB.js.map → manage-EK_UQB4B.js.map} +1 -1
- package/dist/own-dirs-3-IxMhGv.js +97 -0
- package/dist/own-dirs-3-IxMhGv.js.map +1 -0
- package/dist/report-BLbtzUlC.js +44 -0
- package/dist/report-BLbtzUlC.js.map +1 -0
- package/dist/report-D2D_6E5O.js +2 -0
- package/dist/run-Naf4FpSh.js +1225 -0
- package/dist/run-Naf4FpSh.js.map +1 -0
- package/dist/{server-DrcmvN-U.js → server-PBZlvr8o.js} +415 -4661
- package/dist/server-PBZlvr8o.js.map +1 -0
- package/dist/{service-DrpXdB3s.js → service-B4g01DFf.js} +6 -4
- package/dist/service-B4g01DFf.js.map +1 -0
- package/dist/{service-BxVaIXjG.js → service-CCbVYqVp.js} +1 -1
- package/dist/{service-BhPcxIL4.js → service-CY2d9kj-.js} +6 -4
- package/dist/service-CY2d9kj-.js.map +1 -0
- package/dist/{service-B81gCdCD.js → service-D-verWHJ.js} +1 -1
- package/dist/{settings-B8Kmkrfa.js → settings-C0rWKyEE.js} +8 -94
- package/dist/settings-C0rWKyEE.js.map +1 -0
- package/dist/settings-Dlhgb3bB.js +2 -0
- package/dist/{settings-cli-LG9y-7Wn.js → settings-cli-DfWc-yWJ.js} +4 -4
- package/dist/{settings-cli-LG9y-7Wn.js.map → settings-cli-DfWc-yWJ.js.map} +1 -1
- package/dist/{setup-token-DBqR_h01.js → setup-token-D1urJ8_0.js} +3 -14
- package/dist/setup-token-D1urJ8_0.js.map +1 -0
- package/dist/skill/SKILL.md +9 -2
- package/dist/smoke-C2Zm2jbn.js +5637 -0
- package/dist/smoke-C2Zm2jbn.js.map +1 -0
- package/dist/ssrf-BlVpGZ7C.js +181 -0
- package/dist/ssrf-BlVpGZ7C.js.map +1 -0
- package/dist/types-BhJDbSzE.js.map +1 -1
- package/dist/{user-server-CpMWMbVi.js → user-server-BgZT7jeQ.js} +4 -178
- package/dist/user-server-BgZT7jeQ.js.map +1 -0
- package/dist/web/assets/{_agentId-BV0046a8.js → _agentId-BTI6pLh-.js} +1 -1
- package/dist/web/assets/{_app-DRQqYPIc.js → _app-BMzOnZRV.js} +1 -1
- package/dist/web/assets/{_app-DQ639hfI.js → _app-DxD9OwFo.js} +1 -1
- package/dist/web/assets/{_connectionId-Bk7TMj62.js → _connectionId-CFRgtHO4.js} +1 -1
- package/dist/web/assets/{_webhookId-BHusUB_Q.js → _webhookId-CG2q1ZJ-.js} +1 -1
- package/dist/web/assets/{account-status-C_2hcU83.js → account-status-DzUdq75O.js} +1 -1
- package/dist/web/assets/{accounts-DzCEfG2-.js → accounts-CCGwcLyC.js} +1 -1
- package/dist/web/assets/{accounts-list-CckJQ8OI.js → accounts-list-4Vb6vg4U.js} +1 -1
- package/dist/web/assets/{add-connection-SeOmoVPN.js → add-connection-CCaBwv__.js} +1 -1
- package/dist/web/assets/{agent-create-form-BFD0Ea4y.js → agent-create-form-DbXoiv3_.js} +1 -1
- package/dist/web/assets/{agents-Cu1TsvEB.js → agents-D5disEvC.js} +1 -1
- package/dist/web/assets/{approvals-HpDC2C1w.js → approvals-CoNYkEx1.js} +1 -1
- package/dist/web/assets/{approve._id-BbI_I05e.js → approve._id-Bxj7fg3h.js} +1 -1
- package/dist/web/assets/{attention-Bkszvctt.js → attention-TE46PooV.js} +1 -1
- package/dist/web/assets/{audit-Cziwv03n.js → audit-37Pl7yM1.js} +1 -1
- package/dist/web/assets/{audit-bits-DF3lGxOP.js → audit-bits-C7bLorEx.js} +1 -1
- package/dist/web/assets/{calendar-notice-BucxCgAz.js → calendar-notice-CM8qP1gh.js} +1 -1
- package/dist/web/assets/{calendar-setup-D6dumnCQ.js → calendar-setup-CILVc1h6.js} +1 -1
- package/dist/web/assets/{chips-input-BJfPYugG.js → chips-input-D8tfyFP6.js} +1 -1
- package/dist/web/assets/{choice-cards-CXY55aZb.js → choice-cards-BbDUoEVk.js} +1 -1
- package/dist/web/assets/{command-KbX3WaHZ.js → command-B1OAqt6J.js} +1 -1
- package/dist/web/assets/{common-D2X8MC2e.js → common-Cg2GITTf.js} +1 -1
- package/dist/web/assets/{connect-agent-guide-BQ4ZkgDb.js → connect-agent-guide-D2n-c-k8.js} +1 -1
- package/dist/web/assets/{connect-host-YIPBDhTt.js → connect-host-CRIG3MhP.js} +1 -1
- package/dist/web/assets/{connection-row-CE5698q5.js → connection-row-C_Rxt4Wl.js} +1 -1
- package/dist/web/assets/{connections-CXqUQYsk.js → connections-C8CuYJp0.js} +1 -1
- package/dist/web/assets/{data-table-DAoPHXft.js → data-table-elJCZBgn.js} +1 -1
- package/dist/web/assets/{events-BP1qCweh.js → events-BVP619Ik.js} +1 -1
- package/dist/web/assets/{extensions-Vdye6ZC6.js → extensions-Cb4cjpzE.js} +1 -1
- package/dist/web/assets/{get-started-Payy9AFB.js → get-started-CYyUMc2t.js} +1 -1
- package/dist/web/assets/{index-CHL5gD-z.js → index-CDTzYswr.js} +2 -2
- package/dist/web/assets/{label-multi-select-B3ZA79r5.js → label-multi-select-DgHZO8Ar.js} +1 -1
- package/dist/web/assets/{labels-B-u2BbHS.js → labels-B3bD1rSv.js} +1 -1
- package/dist/web/assets/{last-call-sEO4E_uk.js → last-call-BwglTqMX.js} +1 -1
- package/dist/web/assets/{login-Brzv4TjL.js → login-Dw8oV8nS.js} +1 -1
- package/dist/web/assets/{mcp-call-card-CI1yfFn6.js → mcp-call-card-CnN5zh_N.js} +1 -1
- package/dist/web/assets/{mutations-DuF70m_K.js → mutations-BXRo_3JQ.js} +1 -1
- package/dist/web/assets/{pairing-lJF6B3uv.js → pairing-D2ciE04r.js} +1 -1
- package/dist/web/assets/{public-url-WjOtOVxE.js → public-url-DFt0P5Kh.js} +1 -1
- package/dist/web/assets/{select-DccuxwQ_.js → select-D_d4lpe_.js} +1 -1
- package/dist/web/assets/settings-B56BRCQH.js +1 -0
- package/dist/web/assets/{setup-Hc61HNT1.js → setup-BuxNH7Bv.js} +1 -1
- package/dist/web/assets/{sheet-eR5CQfI3.js → sheet-C6hzYqBi.js} +1 -1
- package/dist/web/assets/{step-url-brgRsPjf.js → step-url-D_jQJ0Zb.js} +1 -1
- package/dist/web/assets/{test-email-button-CukkGYzv.js → test-email-button-D7vnDUsO.js} +1 -1
- package/dist/web/assets/{toggle-group-BjeBt78C.js → toggle-group-CLnRqcHd.js} +1 -1
- package/dist/web/assets/{use-delivery-speed-CAMuL19d.js → use-delivery-speed-oyD1SeCk.js} +1 -1
- package/dist/web/assets/{webhook-bits-BgnpGCB3.js → webhook-bits-BrAD7AuK.js} +1 -1
- package/dist/web/assets/{webhook-sheet-Cy89A5GZ.js → webhook-sheet-Cgw06UIG.js} +1 -1
- package/dist/web/assets/{webhooks-DUkoZnZg.js → webhooks-BIj6_JK3.js} +1 -1
- package/dist/web/assets/{x-B2T6mLki.js → x-BG2jbslr.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +4 -1
- package/dist/content-C1lKMxFS.js.map +0 -1
- package/dist/hermes-DNz1xdTU.js +0 -3
- package/dist/instance-DWYd1KYi.js +0 -2
- package/dist/listen-Bwh1Iz0W.js +0 -2
- package/dist/server-DrcmvN-U.js.map +0 -1
- package/dist/service-BhPcxIL4.js.map +0 -1
- package/dist/service-DrpXdB3s.js.map +0 -1
- package/dist/settings-B8Kmkrfa.js.map +0 -1
- package/dist/settings-DTa_PN1y.js +0 -2
- package/dist/setup-token-DBqR_h01.js.map +0 -1
- package/dist/user-server-CpMWMbVi.js.map +0 -1
- package/dist/web/assets/settings-CTCUD9Mg.js +0 -1
package/README.md
CHANGED
|
@@ -45,6 +45,25 @@ limits its files, child processes and workers, not its network. Details: [Extens
|
|
|
45
45
|
|
|
46
46
|

|
|
47
47
|
|
|
48
|
+
## Live check
|
|
49
|
+
|
|
50
|
+
There is no test account, so `smoke google-workspace` checks Gmail attachments and the Google Workspace extension
|
|
51
|
+
on the real one, end to end. Run it on xaq's machine while xaq is running:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
npx xaq-cli smoke google-workspace --scratch
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
It prints its plan and asks first. It starts by removing what earlier runs left behind, and lists anything it can't
|
|
58
|
+
remove for you to check. Existing files and mail are only read; it prints their names and the email's subject,
|
|
59
|
+
never their content, and it never fetches an attachment that would be saved to disk. Every write goes into one new Drive folder, `xaq-smoke-<UTC timestamp>`, and the only
|
|
60
|
+
mail is one draft to the account itself, which is never sent. A temporary agent makes the calls through `/mcp`, so
|
|
61
|
+
risky ones go through approval, and the command approves its own. At the end the draft is deleted, the folder goes
|
|
62
|
+
to the Drive trash, and the agent and the temporary settings are removed, also after a failure, Ctrl-C, SIGTERM or
|
|
63
|
+
SIGHUP. You get a
|
|
64
|
+
PASS/FAIL table and a "Left behind" list, which should say nothing. Details:
|
|
65
|
+
[Live check](docs/reference.md#live-check-smoke-google-workspace).
|
|
66
|
+
|
|
48
67
|
<!-- rename-xaq: keep-start -->
|
|
49
68
|
## Upgrading from agentinbox
|
|
50
69
|
|
|
@@ -5,9 +5,9 @@ import { c as profilePath, i as deadPath, o as logPath, p as validProfileName }
|
|
|
5
5
|
import { i as saveProfile, n as loadProfile } from "./profile-B8LfeVb-.js";
|
|
6
6
|
import { b as TransportError, f as isHermesRoute, x as checkTransport } from "./recipes-D4dn2FgR.js";
|
|
7
7
|
import { i as safeText, r as formatDuration } from "./ui-B8Sd4Y7Y.js";
|
|
8
|
-
import { D as ApiError, N as getSelf, b as forwardEvent, g as resolveListen, h as forwardSecret, m as describeFilter, o as routeWarning, u as DEFAULT_LISTEN_TYPES } from "./hermes-
|
|
9
|
-
import { c as readDead, d as runCommand, f as Dispatcher, i as outputLine, l as readStatus, m as hasAccess, p as describeGrants, s as pidAlive, u as writeDead } from "./listen-
|
|
10
|
-
import { r as installedServiceLabel } from "./service-
|
|
8
|
+
import { D as ApiError, N as getSelf, b as forwardEvent, g as resolveListen, h as forwardSecret, m as describeFilter, o as routeWarning, u as DEFAULT_LISTEN_TYPES } from "./hermes-Ccdi1g6M.js";
|
|
9
|
+
import { c as readDead, d as runCommand, f as Dispatcher, i as outputLine, l as readStatus, m as hasAccess, p as describeGrants, s as pidAlive, u as writeDead } from "./listen-B6u7Gt6X.js";
|
|
10
|
+
import { r as installedServiceLabel } from "./service-B4g01DFf.js";
|
|
11
11
|
import pc from "picocolors";
|
|
12
12
|
//#region server/listen/admin.ts
|
|
13
13
|
const out = (s = "") => process.stdout.write(`${s}\n`);
|
|
@@ -216,4 +216,4 @@ async function replayDead(flags, opts = {}) {
|
|
|
216
216
|
//#endregion
|
|
217
217
|
export { clearDead, printDead, printSecret, printStatus, replayDead };
|
|
218
218
|
|
|
219
|
-
//# sourceMappingURL=admin-
|
|
219
|
+
//# sourceMappingURL=admin-B0qZYQ8g.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-CoriRb5Z.js","names":[],"sources":["../server/listen/admin.ts"],"sourcesContent":["import pc from \"picocolors\";\nimport { ApiError, getSelf } from \"./api.ts\";\nimport { describeGrants, hasAccess } from \"./grants.ts\";\nimport { Dispatcher, type Job, type Target } from \"./dispatcher.ts\";\nimport { runCommand } from \"./exec.ts\";\nimport { ensurePrivateDir } from \"./fsutil.ts\";\nimport { forwardEvent } from \"./forward.ts\";\nimport { routeWarning } from \"./hermes.ts\";\nimport { describeFilter, type ListenFlags, MAX_BATCH, resolveListen } from \"./options.ts\";\nimport { outputLine } from \"./listen.ts\";\nimport { isHermesRoute } from \"./recipes.ts\";\nimport { DEFAULT_PROFILE, deadPath, logPath, profilePath, validProfileName } from \"./paths.ts\";\nimport { loadProfile, saveProfile } from \"./profile.ts\";\nimport { DEFAULT_LISTEN_TYPES, forwardSecret } from \"./options.ts\";\nimport { installedServiceLabel } from \"./service.ts\";\nimport { type DeadEntry, pidAlive, readDead, readStatus, writeDead } from \"./state.ts\";\nimport { checkTransport, TransportError } from \"./transport.ts\";\nimport { formatDuration, safeText } from \"./ui.ts\";\nimport { NPX } from \"./version.ts\";\n\nconst out = (s = \"\") => process.stdout.write(`${s}\\n`);\nconst flagFor = (profile: string) => (profile === DEFAULT_PROFILE ? \"\" : ` --profile ${profile}`);\nconst ago = (iso: string | null | undefined) => (iso ? `${formatDuration(Date.now() - Date.parse(iso))} ago` : \"never\");\n\n// ───────────── listen secret ─────────────\n\n/** Prints the forward-mode signing secret (generating and saving it on first use). */\nexport function printSecret(profileName: string, opts: { rotate?: boolean }): void {\n const name = validProfileName(profileName);\n const profile = loadProfile(name);\n let { secret, created } = forwardSecret(profile);\n if (opts.rotate) {\n secret = forwardSecret({ ...profile, forward: undefined, forwardSecret: undefined }).secret;\n created = true;\n }\n if (created) {\n if (profile.forward) profile.forward = { ...profile.forward, secret };\n else profile.forwardSecret = secret;\n saveProfile(name, profile);\n process.stderr.write(pc.gray(`${opts.rotate ? \"Rotated\" : \"Generated\"} the forward signing secret for profile \"${name}\"${opts.rotate ? \"; restart the listener\" : \"\"}.\\n`));\n }\n // The secret alone on stdout, so `export XAQ_WEBHOOK_SECRET=$(xaq listen secret)` works.\n out(secret);\n}\n\n// ───────────── listen status ─────────────\n\nexport async function printStatus(profileName: string): Promise<void> {\n const name = validProfileName(profileName);\n const profile = loadProfile(name);\n const st = readStatus(name);\n const alive = st ? pidAlive(st.pid) && !st.stoppedAt : false;\n const dead = readDead(name);\n\n out(`${pc.bold(\"Profile\")} ${name} ${pc.gray(profilePath(name))}`);\n out(`${pc.bold(\"Agent\")} ${safeText(profile.agentName)} ${pc.gray(`(${profile.agentId})`)}`);\n out(`${pc.bold(\"Daemon\")} ${profile.url}`);\n out(`${pc.bold(\"Consumer\")} ${st?.consumer ?? profile.consumer}`);\n // What the last run used (flags included), else what the profile has saved.\n const exec = st && st.exec !== undefined ? st.exec : profile.exec;\n const forward = st && st.forward !== undefined ? st.forward : profile.forward?.url;\n const targets = [exec ? `exec ${safeText(exec, 120)}` : null, forward ? `forward ${forward}` : null].filter(Boolean);\n out(`${pc.bold(\"Runs\")} ${targets.length ? targets.join(\" · \") : pc.gray(\"nothing saved (pass --exec/--forward with --save)\")}`);\n out(\n `${pc.bold(\"Events\")} ${\n profile.types?.length\n ? profile.types.join(\", \")\n : `${DEFAULT_LISTEN_TYPES.join(\", \")} ${pc.gray(\"(default types; add --types all --save for every event)\")}`\n }`,\n );\n // The running listener's filters (flags included), else the profile's.\n let saved: ReturnType<typeof resolveListen>[\"config\"] | null = null;\n try {\n saved = resolveListen(name, profile, {}).config;\n } catch {\n // An invalid saved setting: `listen` itself reports it.\n }\n out(`${pc.bold(\"Filters\")} ${(alive && st?.filters) || (saved ? describeFilter(saved) : \"?\")}`);\n const batchSeconds = alive && st && st.batchSeconds !== undefined ? st.batchSeconds : (saved?.batchSeconds ?? 0);\n if (batchSeconds) out(`${pc.bold(\"Batch\")} ${formatDuration(batchSeconds * 1000)} window, up to ${MAX_BATCH} events per --exec run`);\n const route = forward && isHermesRoute(forward) ? routeWarning(name, forward) : null;\n if (route?.level === \"warn\") out(`${pc.bold(\"Route\")} ${pc.yellow(route.message)}`);\n out(`${pc.bold(\"Output\")} ${outputLine(name, { exec: exec ?? null, forward: forward ? { url: forward } : null }, logPath(name))}`);\n\n let svc = \"not installed\";\n if (process.platform === \"linux\" || process.platform === \"darwin\") {\n const installed = installedServiceLabel(name);\n if (installed) svc = `installed (${installed}; details: ${NPX} listen service status${flagFor(name)})`;\n }\n out(`${pc.bold(\"Service\")} ${svc}`);\n\n try {\n // Never send the key over a connection `listen` itself would refuse.\n await checkTransport(profile.url, { insecure: profile.insecure });\n const self = await getSelf(profile.url, profile.apiKey, 5000);\n out(`${pc.bold(\"Key\")} ${pc.green(\"valid\")}`);\n // The access the console actually granted, in plain words (what the agent should report to its human).\n const lines = describeGrants(self.grants, self.connections);\n out(`${pc.bold(\"Access\")} ${hasAccess(self) ? lines[0] : pc.yellow(lines[0]!)}`);\n for (const l of lines.slice(1)) out(` ${l}`);\n } catch (err) {\n const revoked = err instanceof ApiError && (err.status === 401 || err.status === 403);\n const why = err instanceof ApiError && err.status === 0 ? (err.cause as string) : err instanceof Error ? err.message.split(\"\\n\")[0] : String(err);\n if (err instanceof TransportError) out(`${pc.bold(\"Key\")} ${pc.red(`not checked: ${why}`)}`);\n else out(`${pc.bold(\"Key\")} ${revoked ? pc.red(`rejected: ${(err as Error).message}`) : pc.yellow(`not checked (${why})`)}`);\n }\n\n if (!st) {\n out(`${pc.bold(\"Listener\")} ${pc.gray(\"has not run yet\")}`);\n } else {\n const state = !alive\n ? pc.gray(`not running${st.stoppedAt ? ` (stopped ${ago(st.stoppedAt)}${st.exitReason ? `: ${safeText(st.exitReason, 160)}` : \"\"})` : \" (exited unexpectedly)\"}`)\n : st.connected\n ? pc.green(`running (pid ${st.pid}), connected ${ago(st.connectedSince)}`)\n : pc.yellow(`running (pid ${st.pid}), disconnected${st.lastDisconnect ? ` ${ago(st.lastDisconnect.at)}: ${safeText(st.lastDisconnect.reason, 160)}` : \"\"}`);\n out(`${pc.bold(\"Listener\")} ${state}`);\n out(\n `${pc.bold(\"Cursor\")} acked #${st.ackedSeq} · received #${st.receivedSeq}${alive && st.running ? ` · ${st.running} in progress` : \"\"}${st.skipped ? ` · ${st.skipped} skipped by filters` : \"\"} · last event ${ago(st.lastEventAt)}`,\n );\n if (st.recent.length) {\n out(pc.bold(\"Recent\"));\n for (const r of st.recent.slice(0, 8)) {\n const icon = r.outcome === \"succeeded\" ? pc.green(\"✓\") : r.outcome === \"dead\" ? pc.red(\"✖\") : r.outcome === \"retrying\" ? pc.yellow(\"↻\") : pc.gray(\"·\");\n out(` ${icon} #${r.seq} ${r.type} ${pc.gray(`${ago(r.at)}${r.attempts > 1 ? `, ${r.attempts} attempts` : \"\"}${r.error && r.outcome !== \"succeeded\" ? `, ${safeText(r.error, 100)}` : \"\"}`)}`);\n }\n }\n }\n out(`${pc.bold(\"Dead\")} ${dead.length ? pc.red(`${dead.length} event${dead.length > 1 ? \"s\" : \"\"}`) + pc.gray(` (inspect: ${NPX} listen dead${flagFor(name)})`) : \"0\"}`);\n out(`${pc.bold(\"Log\")} ${logPath(name)}`);\n}\n\n// ───────────── listen dead ─────────────\n\nexport function printDead(profileName: string, opts: { json?: boolean }): void {\n const name = validProfileName(profileName);\n const entries = readDead(name);\n if (opts.json) {\n for (const e of entries) out(JSON.stringify(e));\n return;\n }\n if (!entries.length) {\n out(`No dead events for profile \"${name}\".`);\n return;\n }\n for (const e of entries) {\n const m = e.payload.data.message;\n out(`${pc.red(\"✖\")} #${e.seq} ${e.type} ${pc.gray(e.id)} ${pc.gray(`died ${ago(e.deadAt)} after ${e.attempts} attempts`)}`);\n if (m) out(` from ${safeText(m.from?.email ?? \"?\", 80)} \"${safeText(m.subject, 80)}\"`);\n out(pc.gray(` ${safeText(e.error, 300)}`));\n }\n out();\n out(pc.gray(`${entries.length} dead event${entries.length > 1 ? \"s\" : \"\"} in ${deadPath(name)}.`));\n out(pc.gray(`Replay with: ${NPX} listen dead --replay${flagFor(name)} (one: --id <evt_…>; drop all: --clear)`));\n out(pc.gray(`Replay uses the profile's saved --exec/--forward; if you ran them as flags only, pass them again, e.g. --replay --exec '<command>'.`));\n}\n\nexport function clearDead(profileName: string, id?: string): void {\n const name = validProfileName(profileName);\n const entries = readDead(name);\n const keep = id ? entries.filter((e) => e.id !== id) : [];\n writeDead(name, keep);\n out(`Removed ${entries.length - keep.length} dead event${entries.length - keep.length === 1 ? \"\" : \"s\"}.`);\n}\n\n/**\n * Runs dead events again through the profile's targets (or the ones passed as flags). Successes are\n * removed from dead.jsonl; failures stay. Returns how many still failed.\n */\nexport async function replayDead(flags: ListenFlags & { id?: string }, opts: { retryDelayMs?: (n: number) => number } = {}): Promise<number> {\n const name = validProfileName(flags.profile ?? DEFAULT_PROFILE);\n const profile = loadProfile(name);\n const { config } = resolveListen(name, profile, { ...flags, save: false });\n const all = readDead(name);\n const chosen = all.filter((e) => !flags.id || e.id === flags.id);\n if (!chosen.length) {\n out(flags.id ? `No dead event with id ${flags.id}.` : `No dead events for profile \"${name}\".`);\n return 0;\n }\n const available: Target[] = [];\n if (config.exec) available.push(\"exec\");\n if (config.forward) available.push(\"forward\");\n if (!available.length) throw new Error(`Nothing to replay to: pass --recipe <tool> (claude-code, codex, cursor, hermes), --exec '<command>' and/or --forward <url> (or save them in the profile with \\`${NPX} listen --save …\\`).`);\n if (config.exec && process.platform === \"win32\") throw new Error(\"--exec isn't supported on Windows; replay with --forward instead.\");\n\n const timeoutMs = config.timeoutSeconds * 1000;\n if (config.cwdIsDefault) ensurePrivateDir(config.cwd);\n const failed = new Map<string, DeadEntry>();\n const bySeq = new Map(chosen.map((e) => [e.seq, e]));\n const dispatcher = new Dispatcher({\n targets: available,\n concurrency: config.concurrency,\n maxAttempts: config.maxAttempts,\n retryDelayMs: opts.retryDelayMs,\n run: async (job: Job, signal) => {\n const errors: string[] = [];\n if (job.remaining.has(\"exec\") && config.exec) {\n const r = await runCommand(config.exec, job.payload, { timeoutMs, signal, cwd: config.cwd });\n if (r.ok) job.remaining.delete(\"exec\");\n else errors.push(`command: ${r.error}`);\n }\n if (job.remaining.has(\"forward\") && config.forward) {\n const r = await forwardEvent(config.forward.url, config.forward.secret, job.payload, { timeoutMs: Math.min(timeoutMs, 30_000), signal, wrap: isHermesRoute(config.forward.url) });\n if (r.ok) job.remaining.delete(\"forward\");\n else errors.push(`forward: ${r.error}`);\n }\n return errors;\n },\n onAdvance: () => {},\n onDead: (job) => {\n const e = bySeq.get(job.seq)!;\n failed.set(e.id, { ...e, deadAt: new Date().toISOString(), attempts: e.attempts + job.attempts, error: job.lastError ?? \"failed\" });\n out(`${pc.red(\"✖\")} #${job.seq} still failing: ${safeText(job.lastError ?? \"failed\", 200)}`);\n },\n onFinished: (job) => {\n if (job.outcome === \"succeeded\") out(`${pc.green(\"✓\")} #${job.seq} ${job.payload.type} replayed`);\n },\n });\n out(`Replaying ${chosen.length} dead event${chosen.length > 1 ? \"s\" : \"\"} (up to ${config.maxAttempts} attempts each)…`);\n // Only the targets that never succeeded (a forward that went through isn't POSTed again).\n for (const e of [...chosen].sort((a, b) => a.seq - b.seq)) {\n const targets = e.targets?.length ? e.targets.filter((t) => available.includes(t)) : available;\n if (!targets.length) {\n out(`${pc.yellow(\"!\")} #${e.seq} needs ${e.targets.join(\" and \")}, which isn't configured now; pass --${e.targets[0]} to replay it.`);\n failed.set(e.id, e);\n continue;\n }\n dispatcher.push(e.payload, { targets });\n }\n await dispatcher.idle();\n\n const replayed = new Set(chosen.map((e) => e.id));\n // Re-read so entries added by a running listener meanwhile are kept.\n const current = readDead(name);\n writeDead(name, [...current.filter((e) => !replayed.has(e.id)), ...failed.values()]);\n out(`${chosen.length - failed.size} replayed, ${failed.size} still dead.`);\n return failed.size;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"admin-B0qZYQ8g.js","names":[],"sources":["../server/listen/admin.ts"],"sourcesContent":["import pc from \"picocolors\";\nimport { ApiError, getSelf } from \"./api.ts\";\nimport { describeGrants, hasAccess } from \"./grants.ts\";\nimport { Dispatcher, type Job, type Target } from \"./dispatcher.ts\";\nimport { runCommand } from \"./exec.ts\";\nimport { ensurePrivateDir } from \"./fsutil.ts\";\nimport { forwardEvent } from \"./forward.ts\";\nimport { routeWarning } from \"./hermes.ts\";\nimport { describeFilter, type ListenFlags, MAX_BATCH, resolveListen } from \"./options.ts\";\nimport { outputLine } from \"./listen.ts\";\nimport { isHermesRoute } from \"./recipes.ts\";\nimport { DEFAULT_PROFILE, deadPath, logPath, profilePath, validProfileName } from \"./paths.ts\";\nimport { loadProfile, saveProfile } from \"./profile.ts\";\nimport { DEFAULT_LISTEN_TYPES, forwardSecret } from \"./options.ts\";\nimport { installedServiceLabel } from \"./service.ts\";\nimport { type DeadEntry, pidAlive, readDead, readStatus, writeDead } from \"./state.ts\";\nimport { checkTransport, TransportError } from \"./transport.ts\";\nimport { formatDuration, safeText } from \"./ui.ts\";\nimport { NPX } from \"./version.ts\";\n\nconst out = (s = \"\") => process.stdout.write(`${s}\\n`);\nconst flagFor = (profile: string) => (profile === DEFAULT_PROFILE ? \"\" : ` --profile ${profile}`);\nconst ago = (iso: string | null | undefined) => (iso ? `${formatDuration(Date.now() - Date.parse(iso))} ago` : \"never\");\n\n// ───────────── listen secret ─────────────\n\n/** Prints the forward-mode signing secret (generating and saving it on first use). */\nexport function printSecret(profileName: string, opts: { rotate?: boolean }): void {\n const name = validProfileName(profileName);\n const profile = loadProfile(name);\n let { secret, created } = forwardSecret(profile);\n if (opts.rotate) {\n secret = forwardSecret({ ...profile, forward: undefined, forwardSecret: undefined }).secret;\n created = true;\n }\n if (created) {\n if (profile.forward) profile.forward = { ...profile.forward, secret };\n else profile.forwardSecret = secret;\n saveProfile(name, profile);\n process.stderr.write(pc.gray(`${opts.rotate ? \"Rotated\" : \"Generated\"} the forward signing secret for profile \"${name}\"${opts.rotate ? \"; restart the listener\" : \"\"}.\\n`));\n }\n // The secret alone on stdout, so `export XAQ_WEBHOOK_SECRET=$(xaq listen secret)` works.\n out(secret);\n}\n\n// ───────────── listen status ─────────────\n\nexport async function printStatus(profileName: string): Promise<void> {\n const name = validProfileName(profileName);\n const profile = loadProfile(name);\n const st = readStatus(name);\n const alive = st ? pidAlive(st.pid) && !st.stoppedAt : false;\n const dead = readDead(name);\n\n out(`${pc.bold(\"Profile\")} ${name} ${pc.gray(profilePath(name))}`);\n out(`${pc.bold(\"Agent\")} ${safeText(profile.agentName)} ${pc.gray(`(${profile.agentId})`)}`);\n out(`${pc.bold(\"Daemon\")} ${profile.url}`);\n out(`${pc.bold(\"Consumer\")} ${st?.consumer ?? profile.consumer}`);\n // What the last run used (flags included), else what the profile has saved.\n const exec = st && st.exec !== undefined ? st.exec : profile.exec;\n const forward = st && st.forward !== undefined ? st.forward : profile.forward?.url;\n const targets = [exec ? `exec ${safeText(exec, 120)}` : null, forward ? `forward ${forward}` : null].filter(Boolean);\n out(`${pc.bold(\"Runs\")} ${targets.length ? targets.join(\" · \") : pc.gray(\"nothing saved (pass --exec/--forward with --save)\")}`);\n out(\n `${pc.bold(\"Events\")} ${\n profile.types?.length\n ? profile.types.join(\", \")\n : `${DEFAULT_LISTEN_TYPES.join(\", \")} ${pc.gray(\"(default types; add --types all --save for every event)\")}`\n }`,\n );\n // The running listener's filters (flags included), else the profile's.\n let saved: ReturnType<typeof resolveListen>[\"config\"] | null = null;\n try {\n saved = resolveListen(name, profile, {}).config;\n } catch {\n // An invalid saved setting: `listen` itself reports it.\n }\n out(`${pc.bold(\"Filters\")} ${(alive && st?.filters) || (saved ? describeFilter(saved) : \"?\")}`);\n const batchSeconds = alive && st && st.batchSeconds !== undefined ? st.batchSeconds : (saved?.batchSeconds ?? 0);\n if (batchSeconds) out(`${pc.bold(\"Batch\")} ${formatDuration(batchSeconds * 1000)} window, up to ${MAX_BATCH} events per --exec run`);\n const route = forward && isHermesRoute(forward) ? routeWarning(name, forward) : null;\n if (route?.level === \"warn\") out(`${pc.bold(\"Route\")} ${pc.yellow(route.message)}`);\n out(`${pc.bold(\"Output\")} ${outputLine(name, { exec: exec ?? null, forward: forward ? { url: forward } : null }, logPath(name))}`);\n\n let svc = \"not installed\";\n if (process.platform === \"linux\" || process.platform === \"darwin\") {\n const installed = installedServiceLabel(name);\n if (installed) svc = `installed (${installed}; details: ${NPX} listen service status${flagFor(name)})`;\n }\n out(`${pc.bold(\"Service\")} ${svc}`);\n\n try {\n // Never send the key over a connection `listen` itself would refuse.\n await checkTransport(profile.url, { insecure: profile.insecure });\n const self = await getSelf(profile.url, profile.apiKey, 5000);\n out(`${pc.bold(\"Key\")} ${pc.green(\"valid\")}`);\n // The access the console actually granted, in plain words (what the agent should report to its human).\n const lines = describeGrants(self.grants, self.connections);\n out(`${pc.bold(\"Access\")} ${hasAccess(self) ? lines[0] : pc.yellow(lines[0]!)}`);\n for (const l of lines.slice(1)) out(` ${l}`);\n } catch (err) {\n const revoked = err instanceof ApiError && (err.status === 401 || err.status === 403);\n const why = err instanceof ApiError && err.status === 0 ? (err.cause as string) : err instanceof Error ? err.message.split(\"\\n\")[0] : String(err);\n if (err instanceof TransportError) out(`${pc.bold(\"Key\")} ${pc.red(`not checked: ${why}`)}`);\n else out(`${pc.bold(\"Key\")} ${revoked ? pc.red(`rejected: ${(err as Error).message}`) : pc.yellow(`not checked (${why})`)}`);\n }\n\n if (!st) {\n out(`${pc.bold(\"Listener\")} ${pc.gray(\"has not run yet\")}`);\n } else {\n const state = !alive\n ? pc.gray(`not running${st.stoppedAt ? ` (stopped ${ago(st.stoppedAt)}${st.exitReason ? `: ${safeText(st.exitReason, 160)}` : \"\"})` : \" (exited unexpectedly)\"}`)\n : st.connected\n ? pc.green(`running (pid ${st.pid}), connected ${ago(st.connectedSince)}`)\n : pc.yellow(`running (pid ${st.pid}), disconnected${st.lastDisconnect ? ` ${ago(st.lastDisconnect.at)}: ${safeText(st.lastDisconnect.reason, 160)}` : \"\"}`);\n out(`${pc.bold(\"Listener\")} ${state}`);\n out(\n `${pc.bold(\"Cursor\")} acked #${st.ackedSeq} · received #${st.receivedSeq}${alive && st.running ? ` · ${st.running} in progress` : \"\"}${st.skipped ? ` · ${st.skipped} skipped by filters` : \"\"} · last event ${ago(st.lastEventAt)}`,\n );\n if (st.recent.length) {\n out(pc.bold(\"Recent\"));\n for (const r of st.recent.slice(0, 8)) {\n const icon = r.outcome === \"succeeded\" ? pc.green(\"✓\") : r.outcome === \"dead\" ? pc.red(\"✖\") : r.outcome === \"retrying\" ? pc.yellow(\"↻\") : pc.gray(\"·\");\n out(` ${icon} #${r.seq} ${r.type} ${pc.gray(`${ago(r.at)}${r.attempts > 1 ? `, ${r.attempts} attempts` : \"\"}${r.error && r.outcome !== \"succeeded\" ? `, ${safeText(r.error, 100)}` : \"\"}`)}`);\n }\n }\n }\n out(`${pc.bold(\"Dead\")} ${dead.length ? pc.red(`${dead.length} event${dead.length > 1 ? \"s\" : \"\"}`) + pc.gray(` (inspect: ${NPX} listen dead${flagFor(name)})`) : \"0\"}`);\n out(`${pc.bold(\"Log\")} ${logPath(name)}`);\n}\n\n// ───────────── listen dead ─────────────\n\nexport function printDead(profileName: string, opts: { json?: boolean }): void {\n const name = validProfileName(profileName);\n const entries = readDead(name);\n if (opts.json) {\n for (const e of entries) out(JSON.stringify(e));\n return;\n }\n if (!entries.length) {\n out(`No dead events for profile \"${name}\".`);\n return;\n }\n for (const e of entries) {\n const m = e.payload.data.message;\n out(`${pc.red(\"✖\")} #${e.seq} ${e.type} ${pc.gray(e.id)} ${pc.gray(`died ${ago(e.deadAt)} after ${e.attempts} attempts`)}`);\n if (m) out(` from ${safeText(m.from?.email ?? \"?\", 80)} \"${safeText(m.subject, 80)}\"`);\n out(pc.gray(` ${safeText(e.error, 300)}`));\n }\n out();\n out(pc.gray(`${entries.length} dead event${entries.length > 1 ? \"s\" : \"\"} in ${deadPath(name)}.`));\n out(pc.gray(`Replay with: ${NPX} listen dead --replay${flagFor(name)} (one: --id <evt_…>; drop all: --clear)`));\n out(pc.gray(`Replay uses the profile's saved --exec/--forward; if you ran them as flags only, pass them again, e.g. --replay --exec '<command>'.`));\n}\n\nexport function clearDead(profileName: string, id?: string): void {\n const name = validProfileName(profileName);\n const entries = readDead(name);\n const keep = id ? entries.filter((e) => e.id !== id) : [];\n writeDead(name, keep);\n out(`Removed ${entries.length - keep.length} dead event${entries.length - keep.length === 1 ? \"\" : \"s\"}.`);\n}\n\n/**\n * Runs dead events again through the profile's targets (or the ones passed as flags). Successes are\n * removed from dead.jsonl; failures stay. Returns how many still failed.\n */\nexport async function replayDead(flags: ListenFlags & { id?: string }, opts: { retryDelayMs?: (n: number) => number } = {}): Promise<number> {\n const name = validProfileName(flags.profile ?? DEFAULT_PROFILE);\n const profile = loadProfile(name);\n const { config } = resolveListen(name, profile, { ...flags, save: false });\n const all = readDead(name);\n const chosen = all.filter((e) => !flags.id || e.id === flags.id);\n if (!chosen.length) {\n out(flags.id ? `No dead event with id ${flags.id}.` : `No dead events for profile \"${name}\".`);\n return 0;\n }\n const available: Target[] = [];\n if (config.exec) available.push(\"exec\");\n if (config.forward) available.push(\"forward\");\n if (!available.length) throw new Error(`Nothing to replay to: pass --recipe <tool> (claude-code, codex, cursor, hermes), --exec '<command>' and/or --forward <url> (or save them in the profile with \\`${NPX} listen --save …\\`).`);\n if (config.exec && process.platform === \"win32\") throw new Error(\"--exec isn't supported on Windows; replay with --forward instead.\");\n\n const timeoutMs = config.timeoutSeconds * 1000;\n if (config.cwdIsDefault) ensurePrivateDir(config.cwd);\n const failed = new Map<string, DeadEntry>();\n const bySeq = new Map(chosen.map((e) => [e.seq, e]));\n const dispatcher = new Dispatcher({\n targets: available,\n concurrency: config.concurrency,\n maxAttempts: config.maxAttempts,\n retryDelayMs: opts.retryDelayMs,\n run: async (job: Job, signal) => {\n const errors: string[] = [];\n if (job.remaining.has(\"exec\") && config.exec) {\n const r = await runCommand(config.exec, job.payload, { timeoutMs, signal, cwd: config.cwd });\n if (r.ok) job.remaining.delete(\"exec\");\n else errors.push(`command: ${r.error}`);\n }\n if (job.remaining.has(\"forward\") && config.forward) {\n const r = await forwardEvent(config.forward.url, config.forward.secret, job.payload, { timeoutMs: Math.min(timeoutMs, 30_000), signal, wrap: isHermesRoute(config.forward.url) });\n if (r.ok) job.remaining.delete(\"forward\");\n else errors.push(`forward: ${r.error}`);\n }\n return errors;\n },\n onAdvance: () => {},\n onDead: (job) => {\n const e = bySeq.get(job.seq)!;\n failed.set(e.id, { ...e, deadAt: new Date().toISOString(), attempts: e.attempts + job.attempts, error: job.lastError ?? \"failed\" });\n out(`${pc.red(\"✖\")} #${job.seq} still failing: ${safeText(job.lastError ?? \"failed\", 200)}`);\n },\n onFinished: (job) => {\n if (job.outcome === \"succeeded\") out(`${pc.green(\"✓\")} #${job.seq} ${job.payload.type} replayed`);\n },\n });\n out(`Replaying ${chosen.length} dead event${chosen.length > 1 ? \"s\" : \"\"} (up to ${config.maxAttempts} attempts each)…`);\n // Only the targets that never succeeded (a forward that went through isn't POSTed again).\n for (const e of [...chosen].sort((a, b) => a.seq - b.seq)) {\n const targets = e.targets?.length ? e.targets.filter((t) => available.includes(t)) : available;\n if (!targets.length) {\n out(`${pc.yellow(\"!\")} #${e.seq} needs ${e.targets.join(\" and \")}, which isn't configured now; pass --${e.targets[0]} to replay it.`);\n failed.set(e.id, e);\n continue;\n }\n dispatcher.push(e.payload, { targets });\n }\n await dispatcher.idle();\n\n const replayed = new Set(chosen.map((e) => e.id));\n // Re-read so entries added by a running listener meanwhile are kept.\n const current = readDead(name);\n writeDead(name, [...current.filter((e) => !replayed.has(e.id)), ...failed.values()]);\n out(`${chosen.length - failed.size} replayed, ${failed.size} still dead.`);\n return failed.size;\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,MAAM,OAAO,IAAI,OAAO,QAAQ,OAAO,MAAM,GAAG,EAAE,GAAG;AACrD,MAAM,WAAW,YAAqB,YAAA,YAA8B,KAAK,cAAc;AACvF,MAAM,OAAO,QAAoC,MAAM,GAAG,eAAe,KAAK,IAAI,IAAI,KAAK,MAAM,GAAG,CAAC,EAAE,QAAQ;;AAK/G,SAAgB,YAAY,aAAqB,MAAkC;CACjF,MAAM,OAAO,iBAAiB,WAAW;CACzC,MAAM,UAAU,YAAY,IAAI;CAChC,IAAI,EAAE,QAAQ,YAAY,cAAc,OAAO;CAC/C,IAAI,KAAK,QAAQ;EACf,SAAS,cAAc;GAAE,GAAG;GAAS,SAAS,KAAA;GAAW,eAAe,KAAA;EAAU,CAAC,CAAC,CAAC;EACrF,UAAU;CACZ;CACA,IAAI,SAAS;EACX,IAAI,QAAQ,SAAS,QAAQ,UAAU;GAAE,GAAG,QAAQ;GAAS;EAAO;OAC/D,QAAQ,gBAAgB;EAC7B,YAAY,MAAM,OAAO;EACzB,QAAQ,OAAO,MAAM,GAAG,KAAK,GAAG,KAAK,SAAS,YAAY,YAAY,2CAA2C,KAAK,GAAG,KAAK,SAAS,2BAA2B,GAAG,IAAI,CAAC;CAC5K;CAEA,IAAI,MAAM;AACZ;AAIA,eAAsB,YAAY,aAAoC;CACpE,MAAM,OAAO,iBAAiB,WAAW;CACzC,MAAM,UAAU,YAAY,IAAI;CAChC,MAAM,KAAK,WAAW,IAAI;CAC1B,MAAM,QAAQ,KAAK,SAAS,GAAG,GAAG,KAAK,CAAC,GAAG,YAAY;CACvD,MAAM,OAAO,SAAS,IAAI;CAE1B,IAAI,GAAG,GAAG,KAAK,SAAS,EAAE,KAAK,KAAK,IAAI,GAAG,KAAK,YAAY,IAAI,CAAC,GAAG;CACpE,IAAI,GAAG,GAAG,KAAK,OAAO,EAAE,OAAO,SAAS,QAAQ,SAAS,EAAE,GAAG,GAAG,KAAK,IAAI,QAAQ,QAAQ,EAAE,GAAG;CAC/F,IAAI,GAAG,GAAG,KAAK,QAAQ,EAAE,MAAM,QAAQ,KAAK;CAC5C,IAAI,GAAG,GAAG,KAAK,UAAU,EAAE,IAAI,IAAI,YAAY,QAAQ,UAAU;CAEjE,MAAM,OAAO,MAAM,GAAG,SAAS,KAAA,IAAY,GAAG,OAAO,QAAQ;CAC7D,MAAM,UAAU,MAAM,GAAG,YAAY,KAAA,IAAY,GAAG,UAAU,QAAQ,SAAS;CAC/E,MAAM,UAAU,CAAC,OAAO,QAAQ,SAAS,MAAM,GAAG,MAAM,MAAM,UAAU,WAAW,YAAY,IAAI,CAAC,CAAC,OAAO,OAAO;CACnH,IAAI,GAAG,GAAG,KAAK,MAAM,EAAE,QAAQ,QAAQ,SAAS,QAAQ,KAAK,KAAK,IAAI,GAAG,KAAK,mDAAmD,GAAG;CACpI,IACE,GAAG,GAAG,KAAK,QAAQ,EAAE,MACnB,QAAQ,OAAO,SACX,QAAQ,MAAM,KAAK,IAAI,IACvB,GAAG,qBAAqB,KAAK,IAAI,EAAE,GAAG,GAAG,KAAK,yDAAyD,KAE/G;CAEA,IAAI,QAA2D;CAC/D,IAAI;EACF,QAAQ,cAAc,MAAM,SAAS,CAAC,CAAC,CAAC,CAAC;CAC3C,QAAQ,CAER;CACA,IAAI,GAAG,GAAG,KAAK,SAAS,EAAE,KAAM,SAAS,IAAI,YAAa,QAAQ,eAAe,KAAK,IAAI,MAAM;CAChG,MAAM,eAAe,SAAS,MAAM,GAAG,iBAAiB,KAAA,IAAY,GAAG,eAAgB,OAAO,gBAAgB;CAC9G,IAAI,cAAc,IAAI,GAAG,GAAG,KAAK,OAAO,EAAE,OAAO,eAAe,eAAe,GAAI,EAAE,wCAAkD;CACvI,MAAM,QAAQ,WAAW,cAAc,OAAO,IAAI,aAAa,MAAM,OAAO,IAAI;CAChF,IAAI,OAAO,UAAU,QAAQ,IAAI,GAAG,GAAG,KAAK,OAAO,EAAE,OAAO,GAAG,OAAO,MAAM,OAAO,GAAG;CACtF,IAAI,GAAG,GAAG,KAAK,QAAQ,EAAE,MAAM,WAAW,MAAM;EAAE,MAAM,QAAQ;EAAM,SAAS,UAAU,EAAE,KAAK,QAAQ,IAAI;CAAK,GAAG,QAAQ,IAAI,CAAC,GAAG;CAEpI,IAAI,MAAM;CACV,IAAI,QAAQ,aAAa,WAAW,QAAQ,aAAa,UAAU;EACjE,MAAM,YAAY,sBAAsB,IAAI;EAC5C,IAAI,WAAW,MAAM,cAAc,UAAU,aAAa,IAAI,wBAAwB,QAAQ,IAAI,EAAE;CACtG;CACA,IAAI,GAAG,GAAG,KAAK,SAAS,EAAE,KAAK,KAAK;CAEpC,IAAI;EAEF,MAAM,eAAe,QAAQ,KAAK,EAAE,UAAU,QAAQ,SAAS,CAAC;EAChE,MAAM,OAAO,MAAM,QAAQ,QAAQ,KAAK,QAAQ,QAAQ,GAAI;EAC5D,IAAI,GAAG,GAAG,KAAK,KAAK,EAAE,SAAS,GAAG,MAAM,OAAO,GAAG;EAElD,MAAM,QAAQ,eAAe,KAAK,QAAQ,KAAK,WAAW;EAC1D,IAAI,GAAG,GAAG,KAAK,QAAQ,EAAE,MAAM,UAAU,IAAI,IAAI,MAAM,KAAK,GAAG,OAAO,MAAM,EAAG,GAAG;EAClF,KAAK,MAAM,KAAK,MAAM,MAAM,CAAC,GAAG,IAAI,aAAa,GAAG;CACtD,SAAS,KAAK;EACZ,MAAM,UAAU,eAAe,aAAa,IAAI,WAAW,OAAO,IAAI,WAAW;EACjF,MAAM,MAAM,eAAe,YAAY,IAAI,WAAW,IAAK,IAAI,QAAmB,eAAe,QAAQ,IAAI,QAAQ,MAAM,IAAI,CAAC,CAAC,KAAK,OAAO,GAAG;EAChJ,IAAI,eAAe,gBAAgB,IAAI,GAAG,GAAG,KAAK,KAAK,EAAE,SAAS,GAAG,IAAI,gBAAgB,KAAK,GAAG;OAC5F,IAAI,GAAG,GAAG,KAAK,KAAK,EAAE,SAAS,UAAU,GAAG,IAAI,aAAc,IAAc,SAAS,IAAI,GAAG,OAAO,gBAAgB,IAAI,EAAE,GAAG;CACnI;CAEA,IAAI,CAAC,IACH,IAAI,GAAG,GAAG,KAAK,UAAU,EAAE,IAAI,GAAG,KAAK,iBAAiB,GAAG;MACtD;EACL,MAAM,QAAQ,CAAC,QACX,GAAG,KAAK,cAAc,GAAG,YAAY,aAAa,IAAI,GAAG,SAAS,IAAI,GAAG,aAAa,KAAK,SAAS,GAAG,YAAY,GAAG,MAAM,GAAG,KAAK,0BAA0B,IAC9J,GAAG,YACD,GAAG,MAAM,gBAAgB,GAAG,IAAI,eAAe,IAAI,GAAG,cAAc,GAAG,IACvE,GAAG,OAAO,gBAAgB,GAAG,IAAI,iBAAiB,GAAG,iBAAiB,IAAI,IAAI,GAAG,eAAe,EAAE,EAAE,IAAI,SAAS,GAAG,eAAe,QAAQ,GAAG,MAAM,IAAI;EAC9J,IAAI,GAAG,GAAG,KAAK,UAAU,EAAE,IAAI,OAAO;EACtC,IACE,GAAG,GAAG,KAAK,QAAQ,EAAE,aAAa,GAAG,SAAS,eAAe,GAAG,cAAc,SAAS,GAAG,UAAU,MAAM,GAAG,QAAQ,gBAAgB,KAAK,GAAG,UAAU,MAAM,GAAG,QAAQ,uBAAuB,GAAG,gBAAgB,IAAI,GAAG,WAAW,GACtO;EACA,IAAI,GAAG,OAAO,QAAQ;GACpB,IAAI,GAAG,KAAK,QAAQ,CAAC;GACrB,KAAK,MAAM,KAAK,GAAG,OAAO,MAAM,GAAG,CAAC,GAAG;IACrC,MAAM,OAAO,EAAE,YAAY,cAAc,GAAG,MAAM,GAAG,IAAI,EAAE,YAAY,SAAS,GAAG,IAAI,GAAG,IAAI,EAAE,YAAY,aAAa,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG;IACrJ,IAAI,KAAK,KAAK,IAAI,EAAE,IAAI,GAAG,EAAE,KAAK,GAAG,GAAG,KAAK,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,SAAS,aAAa,KAAK,EAAE,SAAS,EAAE,YAAY,cAAc,KAAK,SAAS,EAAE,OAAO,GAAG,MAAM,IAAI,GAAG;GAC/L;EACF;CACF;CACA,IAAI,GAAG,GAAG,KAAK,MAAM,EAAE,QAAQ,KAAK,SAAS,GAAG,IAAI,GAAG,KAAK,OAAO,QAAQ,KAAK,SAAS,IAAI,MAAM,IAAI,IAAI,GAAG,KAAK,cAAc,IAAI,cAAc,QAAQ,IAAI,EAAE,EAAE,IAAI,KAAK;CAC5K,IAAI,GAAG,GAAG,KAAK,KAAK,EAAE,SAAS,QAAQ,IAAI,GAAG;AAChD;AAIA,SAAgB,UAAU,aAAqB,MAAgC;CAC7E,MAAM,OAAO,iBAAiB,WAAW;CACzC,MAAM,UAAU,SAAS,IAAI;CAC7B,IAAI,KAAK,MAAM;EACb,KAAK,MAAM,KAAK,SAAS,IAAI,KAAK,UAAU,CAAC,CAAC;EAC9C;CACF;CACA,IAAI,CAAC,QAAQ,QAAQ;EACnB,IAAI,+BAA+B,KAAK,GAAG;EAC3C;CACF;CACA,KAAK,MAAM,KAAK,SAAS;EACvB,MAAM,IAAI,EAAE,QAAQ,KAAK;EACzB,IAAI,GAAG,GAAG,IAAI,GAAG,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,KAAK,GAAG,GAAG,KAAK,EAAE,EAAE,EAAE,IAAI,GAAG,KAAK,QAAQ,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,UAAU,GAAG;EAC3H,IAAI,GAAG,IAAI,YAAY,SAAS,EAAE,MAAM,SAAS,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE;EACzF,IAAI,GAAG,KAAK,OAAO,SAAS,EAAE,OAAO,GAAG,GAAG,CAAC;CAC9C;CACA,IAAI;CACJ,IAAI,GAAG,KAAK,GAAG,QAAQ,OAAO,aAAa,QAAQ,SAAS,IAAI,MAAM,GAAG,MAAM,SAAS,IAAI,EAAE,EAAE,CAAC;CACjG,IAAI,GAAG,KAAK,gBAAgB,IAAI,uBAAuB,QAAQ,IAAI,EAAE,0CAA0C,CAAC;CAChH,IAAI,GAAG,KAAK,qIAAqI,CAAC;AACpJ;AAEA,SAAgB,UAAU,aAAqB,IAAmB;CAChE,MAAM,OAAO,iBAAiB,WAAW;CACzC,MAAM,UAAU,SAAS,IAAI;CAC7B,MAAM,OAAO,KAAK,QAAQ,QAAQ,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;CACxD,UAAU,MAAM,IAAI;CACpB,IAAI,WAAW,QAAQ,SAAS,KAAK,OAAO,aAAa,QAAQ,SAAS,KAAK,WAAW,IAAI,KAAK,IAAI,EAAE;AAC3G;;;;;AAMA,eAAsB,WAAW,OAAsC,OAAiD,CAAC,GAAoB;CAC3I,MAAM,OAAO,iBAAiB,MAAM,WAAA,SAA0B;CAC9D,MAAM,UAAU,YAAY,IAAI;CAChC,MAAM,EAAE,WAAW,cAAc,MAAM,SAAS;EAAE,GAAG;EAAO,MAAM;CAAM,CAAC;CAEzE,MAAM,SADM,SAAS,IACJ,CAAC,CAAC,QAAQ,MAAM,CAAC,MAAM,MAAM,EAAE,OAAO,MAAM,EAAE;CAC/D,IAAI,CAAC,OAAO,QAAQ;EAClB,IAAI,MAAM,KAAK,yBAAyB,MAAM,GAAG,KAAK,+BAA+B,KAAK,GAAG;EAC7F,OAAO;CACT;CACA,MAAM,YAAsB,CAAC;CAC7B,IAAI,OAAO,MAAM,UAAU,KAAK,MAAM;CACtC,IAAI,OAAO,SAAS,UAAU,KAAK,SAAS;CAC5C,IAAI,CAAC,UAAU,QAAQ,MAAM,IAAI,MAAM,kKAAkK,IAAI,qBAAqB;CAClO,IAAI,OAAO,QAAQ,QAAQ,aAAa,SAAS,MAAM,IAAI,MAAM,mEAAmE;CAEpI,MAAM,YAAY,OAAO,iBAAiB;CAC1C,IAAI,OAAO,cAAc,iBAAiB,OAAO,GAAG;CACpD,MAAM,yBAAS,IAAI,IAAuB;CAC1C,MAAM,QAAQ,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;CACnD,MAAM,aAAa,IAAI,WAAW;EAChC,SAAS;EACT,aAAa,OAAO;EACpB,aAAa,OAAO;EACpB,cAAc,KAAK;EACnB,KAAK,OAAO,KAAU,WAAW;GAC/B,MAAM,SAAmB,CAAC;GAC1B,IAAI,IAAI,UAAU,IAAI,MAAM,KAAK,OAAO,MAAM;IAC5C,MAAM,IAAI,MAAM,WAAW,OAAO,MAAM,IAAI,SAAS;KAAE;KAAW;KAAQ,KAAK,OAAO;IAAI,CAAC;IAC3F,IAAI,EAAE,IAAI,IAAI,UAAU,OAAO,MAAM;SAChC,OAAO,KAAK,YAAY,EAAE,OAAO;GACxC;GACA,IAAI,IAAI,UAAU,IAAI,SAAS,KAAK,OAAO,SAAS;IAClD,MAAM,IAAI,MAAM,aAAa,OAAO,QAAQ,KAAK,OAAO,QAAQ,QAAQ,IAAI,SAAS;KAAE,WAAW,KAAK,IAAI,WAAW,GAAM;KAAG;KAAQ,MAAM,cAAc,OAAO,QAAQ,GAAG;IAAE,CAAC;IAChL,IAAI,EAAE,IAAI,IAAI,UAAU,OAAO,SAAS;SACnC,OAAO,KAAK,YAAY,EAAE,OAAO;GACxC;GACA,OAAO;EACT;EACA,iBAAiB,CAAC;EAClB,SAAS,QAAQ;GACf,MAAM,IAAI,MAAM,IAAI,IAAI,GAAG;GAC3B,OAAO,IAAI,EAAE,IAAI;IAAE,GAAG;IAAG,yBAAQ,IAAI,KAAK,EAAA,CAAE,YAAY;IAAG,UAAU,EAAE,WAAW,IAAI;IAAU,OAAO,IAAI,aAAa;GAAS,CAAC;GAClI,IAAI,GAAG,GAAG,IAAI,GAAG,EAAE,IAAI,IAAI,IAAI,kBAAkB,SAAS,IAAI,aAAa,UAAU,GAAG,GAAG;EAC7F;EACA,aAAa,QAAQ;GACnB,IAAI,IAAI,YAAY,aAAa,IAAI,GAAG,GAAG,MAAM,GAAG,EAAE,IAAI,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK,UAAU;EAClG;CACF,CAAC;CACD,IAAI,aAAa,OAAO,OAAO,aAAa,OAAO,SAAS,IAAI,MAAM,GAAG,UAAU,OAAO,YAAY,iBAAiB;CAEvH,KAAK,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG;EACzD,MAAM,UAAU,EAAE,SAAS,SAAS,EAAE,QAAQ,QAAQ,MAAM,UAAU,SAAS,CAAC,CAAC,IAAI;EACrF,IAAI,CAAC,QAAQ,QAAQ;GACnB,IAAI,GAAG,GAAG,OAAO,GAAG,EAAE,IAAI,EAAE,IAAI,SAAS,EAAE,QAAQ,KAAK,OAAO,EAAE,uCAAuC,EAAE,QAAQ,GAAG,eAAe;GACpI,OAAO,IAAI,EAAE,IAAI,CAAC;GAClB;EACF;EACA,WAAW,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC;CACxC;CACA,MAAM,WAAW,KAAK;CAEtB,MAAM,WAAW,IAAI,IAAI,OAAO,KAAK,MAAM,EAAE,EAAE,CAAC;CAEhD,MAAM,UAAU,SAAS,IAAI;CAC7B,UAAU,MAAM,CAAC,GAAG,QAAQ,QAAQ,MAAM,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,OAAO,OAAO,CAAC,CAAC;CACnF,IAAI,GAAG,OAAO,SAAS,OAAO,KAAK,aAAa,OAAO,KAAK,aAAa;CACzE,OAAO,OAAO;AAChB"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { i as sha256 } from "./crypto-s8x6up-l.js";
|
|
3
|
-
import { r as SESSION_COOKIE, t as CSRF_HEADER, y as SESSION_MAX_AGE_MS } from "./auth-
|
|
4
|
-
import { n as dataFiles } from "./config-
|
|
5
|
-
import { c as runningInstance } from "./instance-
|
|
3
|
+
import { r as SESSION_COOKIE, t as CSRF_HEADER, y as SESSION_MAX_AGE_MS } from "./auth-D4QQM-EQ.js";
|
|
4
|
+
import { n as dataFiles } from "./config-DzPEJM-P.js";
|
|
5
|
+
import { c as runningInstance } from "./instance-DoBa4kgq.js";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { DatabaseSync } from "node:sqlite";
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
@@ -159,4 +159,4 @@ function openAdminChannel(opts) {
|
|
|
159
159
|
//#endregion
|
|
160
160
|
export { createCliSession as a, CLI_SESSION_TTL_MS as i, CLEANUP_SIGNALS as n, daemonOrigin as o, CLI_SESSION_LABEL as r, openAdminChannel as s, AdminApiError as t };
|
|
161
161
|
|
|
162
|
-
//# sourceMappingURL=admin-
|
|
162
|
+
//# sourceMappingURL=admin-BTERuaPM.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-BzWIG9E5.js","names":[],"sources":["../server/connections-cli/admin.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { SESSION_COOKIE, CSRF_HEADER } from \"../auth/index.ts\";\nimport { SESSION_MAX_AGE_MS } from \"../auth/sessions.ts\";\nimport { dataFiles } from \"../config.ts\";\nimport { sha256 } from \"../crypto.ts\";\nimport { type InstanceInfo, runningInstance } from \"../instance.ts\";\n\n/**\n * The CLI's admin channel to the daemon on this machine (`connections …`, `import`), without the console password.\n *\n * Whoever can write the data directory already holds master.key and the database (`reset-password` relies on the\n * same thing), so the CLI proves it is the admin by writing there: it inserts a one-time admin session (only\n * sha256(token) is stored, like console sessions) that expires after 15 minutes, uses it as the session cookie\n * against the daemon's own /api on loopback, and deletes it when the command ends. Nothing new listens, no\n * token file sits on disk, and the daemon's admin auth (requireAdmin + the CSRF header) is unchanged: every API\n * call is checked, audited and rate-limited exactly like the console's.\n *\n * Console sessions slide forward on use up to SESSION_MAX_AGE_MS after created_at. The CLI's row is written with\n * created_at = expiry − SESSION_MAX_AGE_MS, so that hard limit is the 15-minute expiry and the row can never be\n * extended (see docs/FOLLOWUPS.md: a session kind column would say this directly).\n */\n\nexport const CLI_SESSION_TTL_MS = 15 * 60_000;\nexport const CLI_SESSION_LABEL = \"xaq CLI (local admin, one-time)\";\n\nexport class AdminApiError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n readonly hint?: string,\n /** The parsed response body (some routes answer an error with their own shape, e.g. /finish). */\n readonly body?: unknown,\n ) {\n super(message);\n this.name = \"AdminApiError\";\n }\n}\n\nexport interface AdminChannel {\n /** The daemon's loopback origin, e.g. http://127.0.0.1:8425. */\n readonly baseUrl: string;\n request<T>(method: string, path: string, body?: unknown): Promise<T>;\n /**\n * A fingerprint of the connection's stored credential (issued_at plus its randomised ciphertext), read from the\n * database: it changes on every saved sign-in, so a Reconnect's completion is seen even on an active connection.\n * null when nothing is stored (or it can't be read). Never the credential itself.\n */\n credentialStamp(connectionId: string): string | null;\n /** Deletes the one-time session. Idempotent. */\n close(): void;\n}\n\n/** Signals that end a CLI command: the one-time session is deleted on each (and on a normal exit). */\nexport const CLEANUP_SIGNALS = [\"SIGINT\", \"SIGTERM\", \"SIGHUP\"] as const;\n\nexport interface OpenAdminOptions {\n dataDir: string;\n /** Tests: the running instance, instead of reading the lock file. */\n instance?: InstanceInfo | null;\n /** Tests: the daemon origin, instead of deriving it from the instance. */\n baseUrl?: string;\n fetch?: typeof fetch;\n clock?: () => number;\n}\n\n/** Where the CLI reaches the daemon: its bind address, or loopback when it listens on every interface. */\nexport function daemonOrigin(instance: Pick<InstanceInfo, \"host\" | \"port\">): string {\n const host = instance.host;\n const any = !host || host === \"0.0.0.0\" || host === \"::\" || host === \"[::]\";\n const h = any ? \"127.0.0.1\" : host.replace(/^\\[|\\]$/g, \"\");\n return `http://${h.includes(\":\") ? `[${h}]` : h}:${instance.port}`;\n}\n\n/** Inserts the one-time admin session; returns the token. */\nexport function createCliSession(dbPath: string, clock: () => number = Date.now): { token: string; remove: () => void } {\n const token = randomBytes(32).toString(\"base64url\");\n const hash = sha256(token);\n const expires = clock() + CLI_SESSION_TTL_MS;\n const db = new DatabaseSync(dbPath);\n try {\n db.exec(\"PRAGMA busy_timeout = 5000\");\n db.prepare(\"INSERT INTO admin_sessions (token_hash, created_at, expires_at, ip, user_agent) VALUES (?, ?, ?, ?, ?)\").run(\n hash,\n new Date(expires - SESSION_MAX_AGE_MS).toISOString(),\n new Date(expires).toISOString(),\n \"local-cli\",\n CLI_SESSION_LABEL,\n );\n } finally {\n db.close();\n }\n let removed = false;\n return {\n token,\n remove() {\n if (removed) return;\n removed = true;\n try {\n const d = new DatabaseSync(dbPath);\n try {\n d.exec(\"PRAGMA busy_timeout = 5000\");\n d.prepare(\"DELETE FROM admin_sessions WHERE token_hash = ?\").run(hash);\n } finally {\n d.close();\n }\n } catch {\n // It expires on its own within 15 minutes.\n }\n },\n };\n}\n\nexport function openAdminChannel(opts: OpenAdminOptions): AdminChannel {\n // xaq.db, or agentinbox.db in a data directory from before the rename (rename-xaq: keep).\n const dbPath = dataFiles(opts.dataDir).db;\n if (!existsSync(dbPath)) {\n throw new Error(\n `No xaq data in ${opts.dataDir}. Run this on the machine xaq runs on, as the user it runs as ` +\n \"(with sudo for a system service), or pass its data directory with --data.\",\n );\n }\n const instance = opts.instance === undefined ? runningInstance(opts.dataDir) : opts.instance;\n if (!instance && !opts.baseUrl) throw new Error(`xaq isn't running with the data directory ${opts.dataDir}. Start it first (or pass --data).`);\n const baseUrl = opts.baseUrl ?? daemonOrigin(instance!);\n let session: ReturnType<typeof createCliSession>;\n try {\n session = createCliSession(dbPath, opts.clock);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n throw new Error(\n `Can't open ${dbPath}${code ? ` (${code})` : \"\"}: ${(err as Error).message}. Run this as the user xaq runs as (with sudo for a system service).`,\n );\n }\n const onExit = () => session.remove();\n process.once(\"exit\", onExit);\n const doFetch = opts.fetch ?? fetch;\n const send = (method: string, path: string, body: unknown) => {\n const headers: Record<string, string> = { cookie: `${SESSION_COOKIE}=${session.token}`, [CSRF_HEADER]: \"1\", accept: \"application/json\" };\n if (body !== undefined) headers[\"content-type\"] = \"application/json\";\n return doFetch(`${baseUrl}${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(120_000) });\n };\n return {\n baseUrl,\n async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n let res: Response;\n try {\n res = await send(method, path, body);\n // The session's 15 minutes ran out (a slow sign-in): mint a fresh one and retry once.\n if (res.status === 401) {\n await res.body?.cancel().catch(() => {});\n session.remove();\n session = createCliSession(dbPath, opts.clock);\n res = await send(method, path, body);\n }\n } catch (err) {\n throw new Error(`Couldn't reach xaq at ${baseUrl}: ${(err as Error).message}`);\n }\n if (res.status === 204) return undefined as T;\n const text = await res.text();\n let data: unknown = null;\n try {\n data = text ? JSON.parse(text) : null;\n } catch {\n // Not JSON: reported below.\n }\n if (!res.ok) {\n const e = (data as { error?: { code?: string; message?: string; hint?: string } } | null)?.error;\n throw new AdminApiError(res.status, e?.code ?? `http_${res.status}`, e?.message ?? `HTTP ${res.status} from ${path}`, e?.hint, data);\n }\n return data as T;\n },\n credentialStamp(connectionId: string): string | null {\n try {\n const db = new DatabaseSync(dbPath, { readOnly: true });\n try {\n db.exec(\"PRAGMA busy_timeout = 5000\");\n const r = db.prepare(\"SELECT issued_at, access_token_enc, header_value_enc FROM connection_credentials WHERE connection_id = ?\").get(connectionId) as\n | { issued_at: string | null; access_token_enc: string | null; header_value_enc: string | null }\n | undefined;\n return r ? sha256(`${r.issued_at ?? \"\"}|${r.access_token_enc ?? \"\"}|${r.header_value_enc ?? \"\"}`) : null;\n } finally {\n db.close();\n }\n } catch {\n return null;\n }\n },\n close() {\n process.removeListener(\"exit\", onExit);\n session.remove();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"admin-BTERuaPM.js","names":[],"sources":["../server/connections-cli/admin.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { SESSION_COOKIE, CSRF_HEADER } from \"../auth/index.ts\";\nimport { SESSION_MAX_AGE_MS } from \"../auth/sessions.ts\";\nimport { dataFiles } from \"../config.ts\";\nimport { sha256 } from \"../crypto.ts\";\nimport { type InstanceInfo, runningInstance } from \"../instance.ts\";\n\n/**\n * The CLI's admin channel to the daemon on this machine (`connections …`, `import`), without the console password.\n *\n * Whoever can write the data directory already holds master.key and the database (`reset-password` relies on the\n * same thing), so the CLI proves it is the admin by writing there: it inserts a one-time admin session (only\n * sha256(token) is stored, like console sessions) that expires after 15 minutes, uses it as the session cookie\n * against the daemon's own /api on loopback, and deletes it when the command ends. Nothing new listens, no\n * token file sits on disk, and the daemon's admin auth (requireAdmin + the CSRF header) is unchanged: every API\n * call is checked, audited and rate-limited exactly like the console's.\n *\n * Console sessions slide forward on use up to SESSION_MAX_AGE_MS after created_at. The CLI's row is written with\n * created_at = expiry − SESSION_MAX_AGE_MS, so that hard limit is the 15-minute expiry and the row can never be\n * extended (see docs/FOLLOWUPS.md: a session kind column would say this directly).\n */\n\nexport const CLI_SESSION_TTL_MS = 15 * 60_000;\nexport const CLI_SESSION_LABEL = \"xaq CLI (local admin, one-time)\";\n\nexport class AdminApiError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n readonly hint?: string,\n /** The parsed response body (some routes answer an error with their own shape, e.g. /finish). */\n readonly body?: unknown,\n ) {\n super(message);\n this.name = \"AdminApiError\";\n }\n}\n\nexport interface AdminChannel {\n /** The daemon's loopback origin, e.g. http://127.0.0.1:8425. */\n readonly baseUrl: string;\n request<T>(method: string, path: string, body?: unknown): Promise<T>;\n /**\n * A fingerprint of the connection's stored credential (issued_at plus its randomised ciphertext), read from the\n * database: it changes on every saved sign-in, so a Reconnect's completion is seen even on an active connection.\n * null when nothing is stored (or it can't be read). Never the credential itself.\n */\n credentialStamp(connectionId: string): string | null;\n /** Deletes the one-time session. Idempotent. */\n close(): void;\n}\n\n/** Signals that end a CLI command: the one-time session is deleted on each (and on a normal exit). */\nexport const CLEANUP_SIGNALS = [\"SIGINT\", \"SIGTERM\", \"SIGHUP\"] as const;\n\nexport interface OpenAdminOptions {\n dataDir: string;\n /** Tests: the running instance, instead of reading the lock file. */\n instance?: InstanceInfo | null;\n /** Tests: the daemon origin, instead of deriving it from the instance. */\n baseUrl?: string;\n fetch?: typeof fetch;\n clock?: () => number;\n}\n\n/** Where the CLI reaches the daemon: its bind address, or loopback when it listens on every interface. */\nexport function daemonOrigin(instance: Pick<InstanceInfo, \"host\" | \"port\">): string {\n const host = instance.host;\n const any = !host || host === \"0.0.0.0\" || host === \"::\" || host === \"[::]\";\n const h = any ? \"127.0.0.1\" : host.replace(/^\\[|\\]$/g, \"\");\n return `http://${h.includes(\":\") ? `[${h}]` : h}:${instance.port}`;\n}\n\n/** Inserts the one-time admin session; returns the token. */\nexport function createCliSession(dbPath: string, clock: () => number = Date.now): { token: string; remove: () => void } {\n const token = randomBytes(32).toString(\"base64url\");\n const hash = sha256(token);\n const expires = clock() + CLI_SESSION_TTL_MS;\n const db = new DatabaseSync(dbPath);\n try {\n db.exec(\"PRAGMA busy_timeout = 5000\");\n db.prepare(\"INSERT INTO admin_sessions (token_hash, created_at, expires_at, ip, user_agent) VALUES (?, ?, ?, ?, ?)\").run(\n hash,\n new Date(expires - SESSION_MAX_AGE_MS).toISOString(),\n new Date(expires).toISOString(),\n \"local-cli\",\n CLI_SESSION_LABEL,\n );\n } finally {\n db.close();\n }\n let removed = false;\n return {\n token,\n remove() {\n if (removed) return;\n removed = true;\n try {\n const d = new DatabaseSync(dbPath);\n try {\n d.exec(\"PRAGMA busy_timeout = 5000\");\n d.prepare(\"DELETE FROM admin_sessions WHERE token_hash = ?\").run(hash);\n } finally {\n d.close();\n }\n } catch {\n // It expires on its own within 15 minutes.\n }\n },\n };\n}\n\nexport function openAdminChannel(opts: OpenAdminOptions): AdminChannel {\n // xaq.db, or agentinbox.db in a data directory from before the rename (rename-xaq: keep).\n const dbPath = dataFiles(opts.dataDir).db;\n if (!existsSync(dbPath)) {\n throw new Error(\n `No xaq data in ${opts.dataDir}. Run this on the machine xaq runs on, as the user it runs as ` +\n \"(with sudo for a system service), or pass its data directory with --data.\",\n );\n }\n const instance = opts.instance === undefined ? runningInstance(opts.dataDir) : opts.instance;\n if (!instance && !opts.baseUrl) throw new Error(`xaq isn't running with the data directory ${opts.dataDir}. Start it first (or pass --data).`);\n const baseUrl = opts.baseUrl ?? daemonOrigin(instance!);\n let session: ReturnType<typeof createCliSession>;\n try {\n session = createCliSession(dbPath, opts.clock);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n throw new Error(\n `Can't open ${dbPath}${code ? ` (${code})` : \"\"}: ${(err as Error).message}. Run this as the user xaq runs as (with sudo for a system service).`,\n );\n }\n const onExit = () => session.remove();\n process.once(\"exit\", onExit);\n const doFetch = opts.fetch ?? fetch;\n const send = (method: string, path: string, body: unknown) => {\n const headers: Record<string, string> = { cookie: `${SESSION_COOKIE}=${session.token}`, [CSRF_HEADER]: \"1\", accept: \"application/json\" };\n if (body !== undefined) headers[\"content-type\"] = \"application/json\";\n return doFetch(`${baseUrl}${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(120_000) });\n };\n return {\n baseUrl,\n async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n let res: Response;\n try {\n res = await send(method, path, body);\n // The session's 15 minutes ran out (a slow sign-in): mint a fresh one and retry once.\n if (res.status === 401) {\n await res.body?.cancel().catch(() => {});\n session.remove();\n session = createCliSession(dbPath, opts.clock);\n res = await send(method, path, body);\n }\n } catch (err) {\n throw new Error(`Couldn't reach xaq at ${baseUrl}: ${(err as Error).message}`);\n }\n if (res.status === 204) return undefined as T;\n const text = await res.text();\n let data: unknown = null;\n try {\n data = text ? JSON.parse(text) : null;\n } catch {\n // Not JSON: reported below.\n }\n if (!res.ok) {\n const e = (data as { error?: { code?: string; message?: string; hint?: string } } | null)?.error;\n throw new AdminApiError(res.status, e?.code ?? `http_${res.status}`, e?.message ?? `HTTP ${res.status} from ${path}`, e?.hint, data);\n }\n return data as T;\n },\n credentialStamp(connectionId: string): string | null {\n try {\n const db = new DatabaseSync(dbPath, { readOnly: true });\n try {\n db.exec(\"PRAGMA busy_timeout = 5000\");\n const r = db.prepare(\"SELECT issued_at, access_token_enc, header_value_enc FROM connection_credentials WHERE connection_id = ?\").get(connectionId) as\n | { issued_at: string | null; access_token_enc: string | null; header_value_enc: string | null }\n | undefined;\n return r ? sha256(`${r.issued_at ?? \"\"}|${r.access_token_enc ?? \"\"}|${r.header_value_enc ?? \"\"}`) : null;\n } finally {\n db.close();\n }\n } catch {\n return null;\n }\n },\n close() {\n process.removeListener(\"exit\", onExit);\n session.remove();\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { n as CLEANUP_SIGNALS, o as daemonOrigin, s as openAdminChannel } from "./admin-
|
|
2
|
+
export { n as CLEANUP_SIGNALS, o as daemonOrigin, s as openAdminChannel } from "./admin-BTERuaPM.js";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
|
+
//#region server/smoke/agent.ts
|
|
5
|
+
/** The broker wraps upstream text as `<tool_output source=… nonce=… untrusted>…</tool_output>`; the run wants the inside. */
|
|
6
|
+
function unwrapToolOutput(text) {
|
|
7
|
+
return text.replace(/<tool_output [^>]*>\n?/g, "").replace(/\n?<\/tool_output>/g, "");
|
|
8
|
+
}
|
|
9
|
+
function toOutcome(raw) {
|
|
10
|
+
const content = Array.isArray(raw.content) ? raw.content : [];
|
|
11
|
+
const text = unwrapToolOutput(content.map((c) => c.type === "text" ? c.text ?? "" : "").filter(Boolean).join("\n"));
|
|
12
|
+
const s = raw.structuredContent;
|
|
13
|
+
return {
|
|
14
|
+
isError: raw.isError === true,
|
|
15
|
+
text,
|
|
16
|
+
structured: s && typeof s === "object" && !Array.isArray(s) ? s : null,
|
|
17
|
+
content
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function connectAgentMcp(baseUrl, apiKey, opts = {}) {
|
|
21
|
+
const client = new Client({
|
|
22
|
+
name: "xaq-smoke",
|
|
23
|
+
version: opts.version ?? "1"
|
|
24
|
+
});
|
|
25
|
+
await client.connect(new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`), {
|
|
26
|
+
requestInit: { headers: { Authorization: `Bearer ${apiKey}` } },
|
|
27
|
+
...opts.fetch ? { fetch: opts.fetch } : {}
|
|
28
|
+
}));
|
|
29
|
+
return {
|
|
30
|
+
async listTools() {
|
|
31
|
+
const names = [];
|
|
32
|
+
let cursor;
|
|
33
|
+
do {
|
|
34
|
+
const page = await client.listTools(cursor ? { cursor } : void 0);
|
|
35
|
+
names.push(...page.tools.map((t) => t.name));
|
|
36
|
+
cursor = page.nextCursor;
|
|
37
|
+
} while (cursor);
|
|
38
|
+
return names;
|
|
39
|
+
},
|
|
40
|
+
async call(name, args) {
|
|
41
|
+
return toOutcome(await client.callTool({
|
|
42
|
+
name,
|
|
43
|
+
arguments: args
|
|
44
|
+
}, void 0, { timeout: 18e4 }));
|
|
45
|
+
},
|
|
46
|
+
close: () => client.close()
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { toOutcome as n, unwrapToolOutput as r, connectAgentMcp as t };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=agent-mYT2noTp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-mYT2noTp.js","names":[],"sources":["../server/smoke/agent.ts"],"sourcesContent":["// The smoke run's agent side: an MCP client on the daemon's /mcp with the temporary agent's key, exactly as Hermes\n// connects (the SDK client over Streamable HTTP, `Authorization: Bearer <key>`). The key lives in this object only.\n\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\n\n/** One tool call's answer, as the run reads it. */\nexport interface CallOutcome {\n isError: boolean;\n /** Every text block joined, the broker's untrusted-output wrapper removed. Never printed. */\n text: string;\n structured: Record<string, unknown> | null;\n /** The raw content blocks (get_attachment: text, image or resource). */\n content: { type: string; text?: string; data?: string; mimeType?: string; resource?: { blob?: string; text?: string; mimeType?: string } }[];\n}\n\nexport interface AgentMcp {\n listTools(): Promise<string[]>;\n call(name: string, args: Record<string, unknown>): Promise<CallOutcome>;\n close(): Promise<void>;\n}\n\n/** The broker wraps upstream text as `<tool_output source=… nonce=… untrusted>…</tool_output>`; the run wants the inside. */\nexport function unwrapToolOutput(text: string): string {\n return text.replace(/<tool_output [^>]*>\\n?/g, \"\").replace(/\\n?<\\/tool_output>/g, \"\");\n}\n\nexport function toOutcome(raw: { isError?: unknown; content?: unknown; structuredContent?: unknown }): CallOutcome {\n const content = (Array.isArray(raw.content) ? raw.content : []) as CallOutcome[\"content\"];\n const text = unwrapToolOutput(\n content\n .map((c) => (c.type === \"text\" ? (c.text ?? \"\") : \"\"))\n .filter(Boolean)\n .join(\"\\n\"),\n );\n const s = raw.structuredContent;\n return { isError: raw.isError === true, text, structured: s && typeof s === \"object\" && !Array.isArray(s) ? (s as Record<string, unknown>) : null, content };\n}\n\nexport async function connectAgentMcp(baseUrl: string, apiKey: string, opts: { fetch?: typeof fetch; version?: string } = {}): Promise<AgentMcp> {\n const client = new Client({ name: \"xaq-smoke\", version: opts.version ?? \"1\" });\n await client.connect(\n new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`), {\n requestInit: { headers: { Authorization: `Bearer ${apiKey}` } },\n ...(opts.fetch ? { fetch: opts.fetch } : {}),\n }),\n );\n return {\n async listTools() {\n const names: string[] = [];\n let cursor: string | undefined;\n do {\n const page = await client.listTools(cursor ? { cursor } : undefined);\n names.push(...page.tools.map((t) => t.name));\n cursor = page.nextCursor;\n } while (cursor);\n return names;\n },\n async call(name, args) {\n return toOutcome((await client.callTool({ name, arguments: args }, undefined, { timeout: 180_000 })) as Parameters<typeof toOutcome>[0]);\n },\n close: () => client.close(),\n };\n}\n"],"mappings":";;;;;AAuBA,SAAgB,iBAAiB,MAAsB;CACrD,OAAO,KAAK,QAAQ,2BAA2B,EAAE,CAAC,CAAC,QAAQ,uBAAuB,EAAE;AACtF;AAEA,SAAgB,UAAU,KAAyF;CACjH,MAAM,UAAW,MAAM,QAAQ,IAAI,OAAO,IAAI,IAAI,UAAU,CAAC;CAC7D,MAAM,OAAO,iBACX,QACG,KAAK,MAAO,EAAE,SAAS,SAAU,EAAE,QAAQ,KAAM,EAAG,CAAC,CACrD,OAAO,OAAO,CAAC,CACf,KAAK,IAAI,CACd;CACA,MAAM,IAAI,IAAI;CACd,OAAO;EAAE,SAAS,IAAI,YAAY;EAAM;EAAM,YAAY,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,IAAK,IAAgC;EAAM;CAAQ;AAC7J;AAEA,eAAsB,gBAAgB,SAAiB,QAAgB,OAAmD,CAAC,GAAsB;CAC/I,MAAM,SAAS,IAAI,OAAO;EAAE,MAAM;EAAa,SAAS,KAAK,WAAW;CAAI,CAAC;CAC7E,MAAM,OAAO,QACX,IAAI,8BAA8B,IAAI,IAAI,GAAG,QAAQ,KAAK,GAAG;EAC3D,aAAa,EAAE,SAAS,EAAE,eAAe,UAAU,SAAS,EAAE;EAC9D,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;CAC5C,CAAC,CACH;CACA,OAAO;EACL,MAAM,YAAY;GAChB,MAAM,QAAkB,CAAC;GACzB,IAAI;GACJ,GAAG;IACD,MAAM,OAAO,MAAM,OAAO,UAAU,SAAS,EAAE,OAAO,IAAI,KAAA,CAAS;IACnE,MAAM,KAAK,GAAG,KAAK,MAAM,KAAK,MAAM,EAAE,IAAI,CAAC;IAC3C,SAAS,KAAK;GAChB,SAAS;GACT,OAAO;EACT;EACA,MAAM,KAAK,MAAM,MAAM;GACrB,OAAO,UAAW,MAAM,OAAO,SAAS;IAAE;IAAM,WAAW;GAAK,GAAG,KAAA,GAAW,EAAE,SAAS,KAAQ,CAAC,CAAqC;EACzI;EACA,aAAa,OAAO,MAAM;CAC5B;AACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as AdminApiError } from "./admin-
|
|
2
|
+
import { t as AdminApiError } from "./admin-BTERuaPM.js";
|
|
3
3
|
//#region server/connections-cli/api.ts
|
|
4
4
|
/** The routes aren't there: an older daemon, or brokerUpstreams is off. */
|
|
5
5
|
var BrokerOffError = class extends Error {
|
|
@@ -94,4 +94,4 @@ var ConnectionsApi = class {
|
|
|
94
94
|
//#endregion
|
|
95
95
|
export { ConnectionsApi };
|
|
96
96
|
|
|
97
|
-
//# sourceMappingURL=api-
|
|
97
|
+
//# sourceMappingURL=api-_Frr5NsJ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-
|
|
1
|
+
{"version":3,"file":"api-_Frr5NsJ.js","names":[],"sources":["../server/connections-cli/api.ts"],"sourcesContent":["import type {\n AccessLevel,\n Agent,\n ConnectionGrant,\n ConnectionProbe,\n ConnectionView,\n CreateConnectionInput,\n GoogleApiCheckView,\n PresetView,\n StartConnectInput,\n StartConnectResult,\n} from \"../../shared/types.ts\";\nimport { AdminApiError, type AdminChannel } from \"./admin.ts\";\n\n/**\n * The admin connections API (server/api/connections.ts, DTOs in shared/types.ts) as the CLI uses it. While the\n * `brokerUpstreams` setting is off every /api/connections route answers 404; the CLI says so in plain words.\n */\n\nexport type { ConnectionProbe, ConnectionView, PresetView, StartConnectResult };\n\n/** The routes aren't there: an older daemon, or brokerUpstreams is off. */\nexport class BrokerOffError extends Error {\n constructor() {\n super(\n \"This xaq doesn't have connections turned on yet (the brokerUpstreams setting), or is older than this CLI. \" +\n \"Update it, turn connections on in the console's Settings, then run this again.\",\n );\n this.name = \"BrokerOffError\";\n }\n}\n\nconst enc = encodeURIComponent;\n\nexport class ConnectionsApi {\n constructor(readonly channel: AdminChannel) {}\n\n private async call<T>(method: string, path: string, body?: unknown): Promise<T> {\n try {\n return await this.channel.request<T>(method, path, body);\n } catch (err) {\n if (err instanceof AdminApiError && err.status === 404 && err.code === \"not_found\" && path.startsWith(\"/api/connections\")) throw new BrokerOffError();\n throw err;\n }\n }\n\n list(): Promise<ConnectionView[]> {\n return this.call(\"GET\", \"/api/connections\");\n }\n presets(): Promise<PresetView[]> {\n return this.call(\"GET\", \"/api/connections/presets\");\n }\n /** With `connectionId`, the probe uses that connection's private-target allow (when it covers the URL). */\n probe(url: string, connectionId?: string): Promise<ConnectionProbe> {\n return this.call(\"POST\", \"/api/connections/probe\", connectionId ? { url, connectionId } : { url });\n }\n create(input: CreateConnectionInput): Promise<ConnectionView> {\n return this.call(\"POST\", \"/api/connections\", input);\n }\n get(id: string): Promise<ConnectionView> {\n return this.call(\"GET\", `/api/connections/${enc(id)}`);\n }\n /** A Google extension's \"Check again\": is each of its Google APIs on in the project, checked as `account`? */\n googleApis(presetId: string, account: string): Promise<GoogleApiCheckView[]> {\n return this.call(\"POST\", `/api/connections/presets/${enc(presetId)}/google-apis`, { account });\n }\n /** Stores a static header (and connects), or starts OAuth and returns the authorize URL. */\n connect(id: string, input: StartConnectInput = {}): Promise<StartConnectResult> {\n return this.call(\"POST\", `/api/connections/${enc(id)}/connect`, input);\n }\n /** The admin-only private-target allow, pinned server-side to the URL's exact scheme://host:port. */\n allowPrivateTarget(id: string, allow: boolean): Promise<ConnectionView> {\n return this.call(\"PUT\", `/api/connections/${enc(id)}/private-target`, { allow });\n }\n /** The kill switch: disabled at once, pending calls cancelled, credentials deleted. */\n kill(id: string): Promise<void> {\n return this.call(\"POST\", `/api/connections/${enc(id)}/kill`, {});\n }\n /** PATCH: a new slug renames the exposed tools (`<slug>__<tool>`); approval rules carry over. */\n update(id: string, patch: { slug?: string; name?: string }): Promise<ConnectionView> {\n return this.call(\"PATCH\", `/api/connections/${enc(id)}`, patch);\n }\n remove(id: string): Promise<void> {\n return this.call(\"DELETE\", `/api/connections/${enc(id)}`);\n }\n async bySlug(slug: string): Promise<ConnectionView> {\n const all = await this.list();\n const found = all.find((c) => c.slug === slug);\n if (!found) throw new Error(`No connection \"${slug}\". ${all.length ? `There are: ${all.map((c) => c.slug).join(\", \")}.` : \"There are none yet.\"}`);\n return found;\n }\n grants(id: string): Promise<ConnectionGrant[]> {\n return this.call(\"GET\", `/api/connections/${enc(id)}/grants`);\n }\n /** The full list: agents not in it lose access. */\n setGrants(id: string, grants: { agentId: string; level: AccessLevel }[]): Promise<ConnectionGrant[]> {\n return this.call(\"PUT\", `/api/connections/${enc(id)}/grants`, { grants });\n }\n agents(): Promise<Agent[]> {\n return this.channel.request(\"GET\", \"/api/agents\");\n }\n\n /** Paste mode: the address the browser landed on after approving (the daemon checks state and issuer). */\n async finish(url: string): Promise<StartConnectResult> {\n try {\n return await this.call<StartConnectResult>(\"POST\", \"/api/connections/finish\", { url });\n } catch (err) {\n // A refused sign-in is a 400 carrying a StartConnectResult, not the usual error envelope.\n const body = err instanceof AdminApiError ? (err.body as StartConnectResult | null) : null;\n if (body?.status === \"error\") return body;\n throw err;\n }\n }\n}\n"],"mappings":";;;;AAsBA,IAAa,iBAAb,cAAoC,MAAM;CACxC,cAAc;EACZ,MACE,0LAEF;EACA,KAAK,OAAO;CACd;AACF;AAEA,MAAM,MAAM;AAEZ,IAAa,iBAAb,MAA4B;CACL;CAArB,YAAY,SAAgC;EAAvB,KAAA,UAAA;CAAwB;CAE7C,MAAc,KAAQ,QAAgB,MAAc,MAA4B;EAC9E,IAAI;GACF,OAAO,MAAM,KAAK,QAAQ,QAAW,QAAQ,MAAM,IAAI;EACzD,SAAS,KAAK;GACZ,IAAI,eAAe,iBAAiB,IAAI,WAAW,OAAO,IAAI,SAAS,eAAe,KAAK,WAAW,kBAAkB,GAAG,MAAM,IAAI,eAAe;GACpJ,MAAM;EACR;CACF;CAEA,OAAkC;EAChC,OAAO,KAAK,KAAK,OAAO,kBAAkB;CAC5C;CACA,UAAiC;EAC/B,OAAO,KAAK,KAAK,OAAO,0BAA0B;CACpD;;CAEA,MAAM,KAAa,cAAiD;EAClE,OAAO,KAAK,KAAK,QAAQ,0BAA0B,eAAe;GAAE;GAAK;EAAa,IAAI,EAAE,IAAI,CAAC;CACnG;CACA,OAAO,OAAuD;EAC5D,OAAO,KAAK,KAAK,QAAQ,oBAAoB,KAAK;CACpD;CACA,IAAI,IAAqC;EACvC,OAAO,KAAK,KAAK,OAAO,oBAAoB,IAAI,EAAE,GAAG;CACvD;;CAEA,WAAW,UAAkB,SAAgD;EAC3E,OAAO,KAAK,KAAK,QAAQ,4BAA4B,IAAI,QAAQ,EAAE,eAAe,EAAE,QAAQ,CAAC;CAC/F;;CAEA,QAAQ,IAAY,QAA2B,CAAC,GAAgC;EAC9E,OAAO,KAAK,KAAK,QAAQ,oBAAoB,IAAI,EAAE,EAAE,WAAW,KAAK;CACvE;;CAEA,mBAAmB,IAAY,OAAyC;EACtE,OAAO,KAAK,KAAK,OAAO,oBAAoB,IAAI,EAAE,EAAE,kBAAkB,EAAE,MAAM,CAAC;CACjF;;CAEA,KAAK,IAA2B;EAC9B,OAAO,KAAK,KAAK,QAAQ,oBAAoB,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;CACjE;;CAEA,OAAO,IAAY,OAAkE;EACnF,OAAO,KAAK,KAAK,SAAS,oBAAoB,IAAI,EAAE,KAAK,KAAK;CAChE;CACA,OAAO,IAA2B;EAChC,OAAO,KAAK,KAAK,UAAU,oBAAoB,IAAI,EAAE,GAAG;CAC1D;CACA,MAAM,OAAO,MAAuC;EAClD,MAAM,MAAM,MAAM,KAAK,KAAK;EAC5B,MAAM,QAAQ,IAAI,MAAM,MAAM,EAAE,SAAS,IAAI;EAC7C,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,kBAAkB,KAAK,KAAK,IAAI,SAAS,cAAc,IAAI,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,KAAK,uBAAuB;EACjJ,OAAO;CACT;CACA,OAAO,IAAwC;EAC7C,OAAO,KAAK,KAAK,OAAO,oBAAoB,IAAI,EAAE,EAAE,QAAQ;CAC9D;;CAEA,UAAU,IAAY,QAA+E;EACnG,OAAO,KAAK,KAAK,OAAO,oBAAoB,IAAI,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC;CAC1E;CACA,SAA2B;EACzB,OAAO,KAAK,QAAQ,QAAQ,OAAO,aAAa;CAClD;;CAGA,MAAM,OAAO,KAA0C;EACrD,IAAI;GACF,OAAO,MAAM,KAAK,KAAyB,QAAQ,2BAA2B,EAAE,IAAI,CAAC;EACvF,SAAS,KAAK;GAEZ,MAAM,OAAO,eAAe,gBAAiB,IAAI,OAAqC;GACtF,IAAI,MAAM,WAAW,SAAS,OAAO;GACrC,MAAM;EACR;CACF;AACF"}
|