bazilion 0.9.0 → 0.11.0
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/cli.js +1257 -150
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +3049 -471
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0001_init.sql +20 -0
- package/dist/web/client/assets/AgentTabs-B3hq5a3e.js +1 -0
- package/dist/web/client/assets/ConfigPage-BCwfgyjD.js +1 -0
- package/dist/web/client/assets/FieldRow-avAFLgYW.js +1 -0
- package/dist/web/client/assets/Page-CwNuDhWz.js +1 -0
- package/dist/web/client/assets/ProductionTeamPolicyEditor-B6dbpuYy.js +7 -0
- package/dist/web/client/assets/{ProfileCommunicationEditor-D1OZ8WE5.js → ProfileCommunicationEditor-Bf8UvmZT.js} +1 -1
- package/dist/web/client/assets/SectionTabs-DZzy01az.js +1 -0
- package/dist/web/client/assets/TeamTabs-sQBHChtd.js +1 -0
- package/dist/web/client/assets/TemplatesTabs-DPsuwsWB.js +1 -0
- package/dist/web/client/assets/_id-C9rkCclz.js +1 -0
- package/dist/web/client/assets/_id-D65IzC8H.js +1 -0
- package/dist/web/client/assets/_id-DE4IY5bL.js +1 -0
- package/dist/web/client/assets/_id-DYASsGQ8.js +1 -0
- package/dist/web/client/assets/activity-C7o6-xHg.js +1 -0
- package/dist/web/client/assets/agents-UroO4d25.js +1 -0
- package/dist/web/client/assets/agents-h8yWXQ2H.js +1 -0
- package/dist/web/client/assets/approvals-DKhF0YLC.js +1 -0
- package/dist/web/client/assets/chevron-down-BK6NPMMP.js +1 -0
- package/dist/web/client/assets/config-q8JDresK.js +3 -0
- package/dist/web/client/assets/context-CBucAoi6.js +1 -0
- package/dist/web/client/assets/inbox-BwoRikj1.js +1 -0
- package/dist/web/client/assets/index-DUXAevpZ.js +12 -0
- package/dist/web/client/assets/login-CNAV9Wer.js +1 -0
- package/dist/web/client/assets/mcp-BKdEFwHO.js +1 -0
- package/dist/web/client/assets/members-BDqZQh26.js +1 -0
- package/dist/web/client/assets/memory-DVdEVr6L.js +1 -0
- package/dist/web/client/assets/plus-b4yFXP0O.js +1 -0
- package/dist/web/client/assets/policy-DdDctqvg.js +1 -0
- package/dist/web/client/assets/preload-helper-mNuBjNXU.js +1 -0
- package/dist/web/client/assets/react-3W8R1pOO.js +1 -0
- package/dist/web/client/assets/routes-BEfALSZv.js +1 -0
- package/dist/web/client/assets/services-DZP2ybzb.js +1 -0
- package/dist/web/client/assets/skills-DoeIjITs.js +1 -0
- package/dist/web/client/assets/styles-BYM0UGdF.css +2 -0
- package/dist/web/client/assets/team-policy-D0jpnn2b.js +1 -0
- package/dist/web/client/assets/teams-DlHRnrQw.js +1 -0
- package/dist/web/client/assets/teams-W3j-UoJ9.js +1 -0
- package/dist/web/client/assets/telegram-2OdeGv4E.js +1 -0
- package/dist/web/client/assets/tokens-MuBfmgue.js +1 -0
- package/dist/web/client/assets/triggers-CX7RMQFQ.js +1 -0
- package/dist/web/client/assets/welcome-CvNr5IMi.js +1 -0
- package/dist/web/client/assets/wire-constants-BhVKOCQa.js +74 -0
- package/dist/web/server/assets/{AgentTabs-BOjTN7QF.js → AgentTabs-DoMDJBXH.js} +10 -7
- package/dist/web/server/assets/{ChatPane-Dh3qxzdb.js → ChatPane-Ba5wTsVk.js} +340 -44
- package/dist/web/server/assets/ConfigPage-BKlh5vOU.js +68 -0
- package/dist/web/server/assets/{FieldRow-DwyqycWI.js → FieldRow-B4eTegH9.js} +5 -5
- package/dist/web/server/assets/Page-LENulSXY.js +111 -0
- package/dist/web/server/assets/{ProductionTeamPolicyEditor-Bke5doyl.js → ProductionTeamPolicyEditor-Y4mzFFvR.js} +4 -4
- package/dist/web/server/assets/SectionTabs-BJTPr5nU.js +8 -0
- package/dist/web/server/assets/{Sidebar-CGmtCnYU.js → Sidebar-DsRVCszF.js} +229 -118
- package/dist/web/server/assets/{TeamTabs-BPxDBfqy.js → TeamTabs-zZzbP1t_.js} +9 -8
- package/dist/web/server/assets/{TemplatesTabs-zf4YhStd.js → TemplatesTabs-CcRxZhJe.js} +5 -4
- package/dist/web/server/assets/{_id-fpFQ9_Ya.js → _id-BhOB8Umi.js} +1 -1
- package/dist/web/server/assets/{_id-CCHA0gdR.js → _id-C3HY4lWg.js} +7 -10
- package/dist/web/server/assets/{_id-B6g9wmPR.js → _id-Cg7x6q3g.js} +22 -28
- package/dist/web/server/assets/{_id-B04VaQlh.js → _id-CwyGjJeP.js} +2 -2
- package/dist/web/server/assets/{_id-_nFwi9gD.js → _id-D27T85mU.js} +1 -1
- package/dist/web/server/assets/{_id-5lLzLwcv.js → _id-D3Ap7zZA.js} +142 -144
- package/dist/web/server/assets/{_id-ButXHnRH.js → _id-DinTp_7X.js} +2 -2
- package/dist/web/server/assets/{_id-DdC9wpNN.js → _id-Dj1yZ3zJ.js} +2 -2
- package/dist/web/server/assets/{_id-DkFgCIb-.js → _id-DqON-59C.js} +2 -2
- package/dist/web/server/assets/{_id-CUWgTqMS.js → _id-IzadFZV9.js} +1 -1
- package/dist/web/server/assets/{_id-C4AOhstq.js → _id-LrvXU4Nr.js} +6 -5
- package/dist/web/server/assets/{_id-DJkzwHjM.js → _id-tB47Vsfp.js} +1 -1
- package/dist/web/server/assets/{_tanstack-start-manifest_v-Div-TukY.js → _tanstack-start-manifest_v-BXauUXg6.js} +96 -51
- package/dist/web/server/assets/{activity-C2ZKg1Q9.js → activity-BCI4mfNS.js} +28 -30
- package/dist/web/server/assets/{activity-DedraoFj.js → activity-D9SMqBhy.js} +2 -2
- package/dist/web/server/assets/{activity-GPx8NCeB.js → activity-DohitgwW.js} +1 -1
- package/dist/web/server/assets/{agents-BR2qXlXF.js → agents-BPKOshR5.js} +1 -1
- package/dist/web/server/assets/{agents-LHowEI6S.js → agents-BY74v7fM.js} +1 -1
- package/dist/web/server/assets/{agents-B8kinIPW.js → agents-BvttypqB.js} +2 -2
- package/dist/web/server/assets/{agents-Bp1nsVs2.js → agents-C9xlWK0Z.js} +2 -2
- package/dist/web/server/assets/agents-CgZMHj80.js +265 -0
- package/dist/web/server/assets/{agents-CUQmWhTp.js → agents-DZuFEj0D.js} +59 -77
- package/dist/web/server/assets/{approvals-VPc0YpLN.js → approvals-BGU-XGIO.js} +1 -1
- package/dist/web/server/assets/{approvals-Dw2LJoSA.js → approvals-CWcFHlor.js} +2 -2
- package/dist/web/server/assets/{approvals-DHgARzd8.js → approvals-DCaVw1Mz.js} +116 -78
- package/dist/web/server/assets/{auth-l-Vf8wlu.js → auth-byfSx6Ja.js} +1 -1
- package/dist/web/server/assets/{config-CHaCv89D.js → config-BDCJdtj2.js} +1 -1
- package/dist/web/server/assets/{config-BvKctDOX.js → config-D2LAsud2.js} +28 -49
- package/dist/web/server/assets/{config-QvHaxGlP.js → config-TeIoyYYc.js} +2 -2
- package/dist/web/server/assets/{context-5lrf2ame.js → context-B-XDf6Ym.js} +1 -1
- package/dist/web/server/assets/{context-D3Egm-vY.js → context-BXGo5Vfi.js} +2 -2
- package/dist/web/server/assets/context-DrkrIWun.js +47 -0
- package/dist/web/server/assets/{createServerRpc-CeKECg65.js → createServerRpc-pU-IQV2Y.js} +1 -1
- package/dist/web/server/assets/{createSsrRpc-ZBGxhl8t.js → createSsrRpc-DFsIAfFd.js} +1 -1
- package/dist/web/server/assets/{empty-plugin-adapters-DKUlnwwl.js → empty-plugin-adapters-Cq4--YW-.js} +1 -1
- package/dist/web/server/assets/{inbox-CkiSvD14.js → inbox-B3-TRsNI.js} +1 -1
- package/dist/web/server/assets/{inbox-Bxb3W04M.js → inbox-BardsGyy.js} +2 -2
- package/dist/web/server/assets/inbox-CUIyf9MT.js +131 -0
- package/dist/web/server/assets/login-Bx5LEyGP.js +118 -0
- package/dist/web/server/assets/login-CmWvxfTK.js +136 -0
- package/dist/web/server/assets/{mcp-BsFKEoXt.js → mcp-2pwT9HU8.js} +1 -1
- package/dist/web/server/assets/{mcp-BU_kKENi.js → mcp-BJjLIdP0.js} +105 -112
- package/dist/web/server/assets/{mcp-CKqwG4mt.js → mcp-DIWTogHM.js} +2 -2
- package/dist/web/server/assets/members-CCv17cjQ.js +46 -0
- package/dist/web/server/assets/{members-DlGBKZNl.js → members-CW5aBe81.js} +1 -1
- package/dist/web/server/assets/{members-ksELRsyk.js → members-VdPwD6CE.js} +2 -2
- package/dist/web/server/assets/{memory-CO0jKGzJ.js → memory-Dngc13Nw.js} +1 -1
- package/dist/web/server/assets/memory-a8Kz57Kh.js +287 -0
- package/dist/web/server/assets/{memory-B5moRnnp.js → memory-pZ4pZT7p.js} +2 -2
- package/dist/web/server/assets/{policy-Dxs78_GO.js → policy-Bb-vEgqg.js} +1 -1
- package/dist/web/server/assets/{policy-Dr55y-Mh.js → policy-BcGELcc9.js} +16 -22
- package/dist/web/server/assets/{policy-DNcGZLOB.js → policy-C30AeFIb.js} +2 -2
- package/dist/web/server/assets/{router-_izWQUeG.js → router-C5_NjTsP.js} +100 -111
- package/dist/web/server/assets/{routes-BSSKp3Rv.js → routes-B6cMLIPk.js} +16 -13
- package/dist/web/server/assets/{routes-BrTOoeth.js → routes-BrA-U_Hm.js} +2 -2
- package/dist/web/server/assets/{routes-DOj6ljcm.js → routes-C19CMLZt.js} +2 -2
- package/dist/web/server/assets/services-B9yBWDX0.js +79 -0
- package/dist/web/server/assets/{services-Pv_V88VA.js → services-C4szdhPx.js} +2 -2
- package/dist/web/server/assets/{services-DhRiNeq0.js → services-wG8I06oS.js} +1 -1
- package/dist/web/server/assets/skills-BZdqiDig.js +358 -0
- package/dist/web/server/assets/{skills-l2cyQ3zS.js → skills-C-UgkuCW.js} +2 -2
- package/dist/web/server/assets/{skills-CFIWVg32.js → skills-a9VSWHWq.js} +1 -1
- package/dist/web/server/assets/{start-DlHq0RaQ.js → start-WH8WzTL_.js} +1 -1
- package/dist/web/server/assets/{team-policy-Csvc5EDd.js → team-policy-vPG01onj.js} +3 -3
- package/dist/web/server/assets/{teams-B9O-WDH5.js → teams-CpQ0YgWf.js} +38 -34
- package/dist/web/server/assets/{teams-35Oprf6e.js → teams-DEpQR2QA.js} +2 -2
- package/dist/web/server/assets/{teams-DFkN7ic7.js → teams-DqfrEoH6.js} +1 -1
- package/dist/web/server/assets/{teams-B5IF65p2.js → teams-MjqgsmC3.js} +1 -1
- package/dist/web/server/assets/{teams-DH1CvWmT.js → teams-OIkHuxy1.js} +2 -2
- package/dist/web/server/assets/teams-jBtvnk59.js +193 -0
- package/dist/web/server/assets/{telegram-C9sQFwWj.js → telegram-CBF8SJzB.js} +36 -71
- package/dist/web/server/assets/{telegram-BdaUIjvs.js → telegram-DNgCWebD.js} +1 -1
- package/dist/web/server/assets/{telegram-mTnJCOmn.js → telegram-XwyKSEtj.js} +2 -2
- package/dist/web/server/assets/{tokens-DdpnfV_Y.js → tokens-BnwFbV7T.js} +2 -2
- package/dist/web/server/assets/{tokens-BCz3PSoT.js → tokens-DPDMSN5O.js} +1 -1
- package/dist/web/server/assets/tokens-yyTSfBSN.js +186 -0
- package/dist/web/server/assets/{triggers-BcTTwMfy.js → triggers-CBl4yMyw.js} +2 -2
- package/dist/web/server/assets/{triggers-BcH_6PW1.js → triggers-D79NoSXk.js} +4 -3
- package/dist/web/server/assets/{triggers-BzJG1nN5.js → triggers-_3rdhwSN.js} +31 -6
- package/dist/web/server/assets/{welcome-CDGE2Sdk.js → welcome-IYHGurWx.js} +21 -13
- package/dist/web/server/server.js +82 -73
- package/dist/worker.js +1614 -153
- package/dist/worker.js.map +1 -1
- package/package.json +5 -3
- package/dist/web/client/assets/AgentTabs-DioGqUVR.js +0 -1
- package/dist/web/client/assets/ConfigTabs-pY-V0Rie.js +0 -1
- package/dist/web/client/assets/FieldRow-DmwAFfbF.js +0 -1
- package/dist/web/client/assets/ProductionTeamPolicyEditor-1a5jQqUB.js +0 -7
- package/dist/web/client/assets/TeamTabs-BViZ4sti.js +0 -1
- package/dist/web/client/assets/TemplatesTabs-DVcY9pMD.js +0 -1
- package/dist/web/client/assets/_id-8P7brhGy.js +0 -1
- package/dist/web/client/assets/_id-C3o3MHq8.js +0 -1
- package/dist/web/client/assets/_id-DGRrSF1e.js +0 -1
- package/dist/web/client/assets/_id-KnJe-aW9.js +0 -1
- package/dist/web/client/assets/activity-WWvJSo4t.js +0 -1
- package/dist/web/client/assets/agents-C32T1voV.js +0 -1
- package/dist/web/client/assets/agents-nH4m4gZ1.js +0 -1
- package/dist/web/client/assets/approvals-l8jIZjqz.js +0 -1
- package/dist/web/client/assets/config-C7wvi1nR.js +0 -3
- package/dist/web/client/assets/context-BEGWE3RC.js +0 -1
- package/dist/web/client/assets/inbox--4aBaAgG.js +0 -1
- package/dist/web/client/assets/index-BCXoh1c8.js +0 -12
- package/dist/web/client/assets/login-Bk2wwIHQ.js +0 -1
- package/dist/web/client/assets/mcp-vM7xgh9e.js +0 -1
- package/dist/web/client/assets/members-cPKfnrSL.js +0 -1
- package/dist/web/client/assets/memory-BOcsaemD.js +0 -1
- package/dist/web/client/assets/policy-BGLCJYvS.js +0 -1
- package/dist/web/client/assets/preload-helper-eOOCz1Fu.js +0 -1
- package/dist/web/client/assets/routes-DnrP3dwv.js +0 -1
- package/dist/web/client/assets/services-C2_O3ifw.js +0 -1
- package/dist/web/client/assets/skills-B__fLZjx.js +0 -1
- package/dist/web/client/assets/styles-Dn24-ZvP.css +0 -2
- package/dist/web/client/assets/team-policy-BdX340m4.js +0 -1
- package/dist/web/client/assets/teams-DZMB4_iw.js +0 -1
- package/dist/web/client/assets/teams-rpaYKNIb.js +0 -1
- package/dist/web/client/assets/telegram-B1O4Ko0V.js +0 -1
- package/dist/web/client/assets/tokens-D7FH9VgA.js +0 -1
- package/dist/web/client/assets/triggers-3PLy2_Fo.js +0 -1
- package/dist/web/client/assets/welcome-B-QQobzq.js +0 -1
- package/dist/web/client/assets/wire-constants-BY9Q9Z7d.js +0 -74
- package/dist/web/server/assets/ConfigTabs-CyuxzesF.js +0 -41
- package/dist/web/server/assets/agents-DU9Hqs58.js +0 -249
- package/dist/web/server/assets/context-CEr4-xiA.js +0 -49
- package/dist/web/server/assets/inbox-DU27iHv_.js +0 -133
- package/dist/web/server/assets/login-CNznncgA.js +0 -58
- package/dist/web/server/assets/login-CQybKk7Y.js +0 -74
- package/dist/web/server/assets/members-40DxRdVj.js +0 -48
- package/dist/web/server/assets/memory-CiTl4cN9.js +0 -289
- package/dist/web/server/assets/services-CHe4ihJk.js +0 -94
- package/dist/web/server/assets/skills-D89ND2kR.js +0 -321
- package/dist/web/server/assets/teams-zcm9XaQU.js +0 -174
- package/dist/web/server/assets/tokens-CkKqrtk-.js +0 -193
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { defineCommand as defineCommand25, runCommand, showUsage } from "citty";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "bazilion",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.11.0",
|
|
10
10
|
description: "Multi-agent runtime CLI \u2014 spawn LLM agents, manage profiles/teams/skills, and run the local daemon.",
|
|
11
11
|
license: "MIT",
|
|
12
12
|
repository: {
|
|
@@ -54,6 +54,7 @@ var package_default = {
|
|
|
54
54
|
"@earendil-works/pi-coding-agent": "^0.80.6",
|
|
55
55
|
"@hono/node-server": "^2.0.6",
|
|
56
56
|
"@mozilla/readability": "^0.6.0",
|
|
57
|
+
"@playwright/browser-chromium": "^1.61.0",
|
|
57
58
|
"@tanstack/react-router": "1.170.16",
|
|
58
59
|
"@tobilu/qmd": "^2.5.3",
|
|
59
60
|
"adm-zip": "^0.5.17",
|
|
@@ -67,6 +68,7 @@ var package_default = {
|
|
|
67
68
|
"qrcode-terminal": "^0.12.0",
|
|
68
69
|
react: "^19.2.7",
|
|
69
70
|
"react-dom": "^19.2.7",
|
|
71
|
+
tar: "^7.5.16",
|
|
70
72
|
typebox: "^1.3.0",
|
|
71
73
|
undici: "^8.5.0",
|
|
72
74
|
yaml: "^2.9.0"
|
|
@@ -304,7 +306,7 @@ var spawnCmd = defineCommand({
|
|
|
304
306
|
reasoningLevel: args.reasoning,
|
|
305
307
|
teamId,
|
|
306
308
|
teamExpectedRevision: policy.teamPolicy.revision,
|
|
307
|
-
placement: "
|
|
309
|
+
placement: "profile_defaults"
|
|
308
310
|
};
|
|
309
311
|
const response = await client.post("/api/agents", body);
|
|
310
312
|
const agent = response.agent;
|
|
@@ -371,12 +373,12 @@ var listCmd = defineCommand({
|
|
|
371
373
|
async run({ args }) {
|
|
372
374
|
const client = createClient2();
|
|
373
375
|
const q = args.all ? "?includeArchived=true" : "";
|
|
374
|
-
const
|
|
375
|
-
if (
|
|
376
|
+
const list4 = await client.get(`/api/agents${q}`);
|
|
377
|
+
if (list4.length === 0) {
|
|
376
378
|
console.log("(no agents)");
|
|
377
379
|
return;
|
|
378
380
|
}
|
|
379
|
-
const rows =
|
|
381
|
+
const rows = list4.map(
|
|
380
382
|
(a) => args.long ? [a.id, a.status, a.name, a.profileId] : (
|
|
381
383
|
// Short UUID prefix — all CLI commands accept 4+ char prefixes now.
|
|
382
384
|
[a.id.slice(0, 8), a.status, a.name]
|
|
@@ -447,6 +449,74 @@ var deleteCmd = defineCommand({
|
|
|
447
449
|
console.log(`deleted agent ${args.id}`);
|
|
448
450
|
}
|
|
449
451
|
});
|
|
452
|
+
function bashApprovalModeForTty(inputIsTty, outputIsTty) {
|
|
453
|
+
return inputIsTty && outputIsTty ? "interactive" : "auto_deny";
|
|
454
|
+
}
|
|
455
|
+
function buildChatRequest(message, attachments, bashApprovalMode) {
|
|
456
|
+
return {
|
|
457
|
+
message,
|
|
458
|
+
bashApprovalMode,
|
|
459
|
+
...attachments && attachments.length > 0 ? { attachments } : {}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function terminalSafe(value) {
|
|
463
|
+
let safe = "";
|
|
464
|
+
for (const character of value) {
|
|
465
|
+
const code = character.charCodeAt(0);
|
|
466
|
+
const isControl = code <= 9 || code >= 11 && code <= 31 || code >= 127 && code <= 159;
|
|
467
|
+
safe += isControl ? `\\x${code.toString(16).padStart(2, "0")}` : character;
|
|
468
|
+
}
|
|
469
|
+
return safe;
|
|
470
|
+
}
|
|
471
|
+
async function promptForCommandApproval(approval, prompt) {
|
|
472
|
+
prompt.write(" [shell command approval required]");
|
|
473
|
+
prompt.write(" command:");
|
|
474
|
+
for (const line of approval.command.split("\n")) prompt.write(` ${terminalSafe(line)}`);
|
|
475
|
+
prompt.write(" risks:");
|
|
476
|
+
for (const risk of approval.risks) {
|
|
477
|
+
prompt.write(
|
|
478
|
+
` - ${terminalSafe(risk.code)} (${terminalSafe(risk.severity)}): ${terminalSafe(risk.message)}`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const answer = (await prompt.question(" Allow this command once? [y/N] ")).trim().toLowerCase();
|
|
483
|
+
return answer === "y" || answer === "yes" ? "allow" : "deny";
|
|
484
|
+
} catch {
|
|
485
|
+
prompt.write(" [approval input unavailable; denying]");
|
|
486
|
+
return "deny";
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async function respondToCommandApproval(client, approval, prompt) {
|
|
490
|
+
const decision2 = prompt ? await promptForCommandApproval(approval, prompt) : "deny";
|
|
491
|
+
await client.post(`/api/shell-approvals/${encodeURIComponent(approval.id)}`, { decision: decision2 });
|
|
492
|
+
return decision2;
|
|
493
|
+
}
|
|
494
|
+
function closeDeltaLine(state) {
|
|
495
|
+
if (!state.inDeltaStream) return;
|
|
496
|
+
process.stdout.write("\n");
|
|
497
|
+
state.inDeltaStream = false;
|
|
498
|
+
}
|
|
499
|
+
function printCommandApprovalStatus(approval) {
|
|
500
|
+
switch (approval.status) {
|
|
501
|
+
case "allowed":
|
|
502
|
+
console.log(" [shell command allowed]");
|
|
503
|
+
break;
|
|
504
|
+
case "denied":
|
|
505
|
+
console.log(" [shell command denied]");
|
|
506
|
+
break;
|
|
507
|
+
case "auto_denied":
|
|
508
|
+
console.log(" [shell command auto-denied: non-interactive turn]");
|
|
509
|
+
break;
|
|
510
|
+
case "expired":
|
|
511
|
+
console.log(" [shell command approval expired]");
|
|
512
|
+
break;
|
|
513
|
+
case "cancelled":
|
|
514
|
+
console.log(" [shell command approval cancelled]");
|
|
515
|
+
break;
|
|
516
|
+
case "pending":
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
450
520
|
function printEvent(e, state) {
|
|
451
521
|
switch (e.type) {
|
|
452
522
|
case "assistant_delta":
|
|
@@ -493,16 +563,32 @@ function printEvent(e, state) {
|
|
|
493
563
|
break;
|
|
494
564
|
}
|
|
495
565
|
}
|
|
496
|
-
async function streamTurn(client, agentId, message, attachments) {
|
|
566
|
+
async function streamTurn(client, agentId, message, attachments, options = { bashApprovalMode: "auto_deny" }) {
|
|
497
567
|
const state = { inDeltaStream: false };
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
568
|
+
const respondedApprovalIds = /* @__PURE__ */ new Set();
|
|
569
|
+
for await (const frame of client.stream(
|
|
570
|
+
"POST",
|
|
571
|
+
`/api/agents/${agentId}/chat`,
|
|
572
|
+
buildChatRequest(message, attachments, options.bashApprovalMode)
|
|
573
|
+
)) {
|
|
502
574
|
if (frame.kind === "event") {
|
|
503
|
-
|
|
575
|
+
const event = frame.event;
|
|
576
|
+
if (event.type === "command_approval") {
|
|
577
|
+
closeDeltaLine(state);
|
|
578
|
+
const approval = event.approval;
|
|
579
|
+
if (approval.status === "pending") {
|
|
580
|
+
if (!respondedApprovalIds.has(approval.id)) {
|
|
581
|
+
respondedApprovalIds.add(approval.id);
|
|
582
|
+
await respondToCommandApproval(client, approval, options.approvalPrompt);
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
printCommandApprovalStatus(approval);
|
|
586
|
+
}
|
|
587
|
+
} else if (event.type !== "user_message") {
|
|
588
|
+
printEvent(event, state);
|
|
589
|
+
}
|
|
504
590
|
} else if (frame.kind === "fatal") {
|
|
505
|
-
|
|
591
|
+
closeDeltaLine(state);
|
|
506
592
|
throw new Error(frame.error);
|
|
507
593
|
}
|
|
508
594
|
}
|
|
@@ -527,34 +613,78 @@ var chatCmd = defineCommand({
|
|
|
527
613
|
async run({ args }) {
|
|
528
614
|
const client = createClient2();
|
|
529
615
|
const resolved = await client.get(`/api/agents/${args.id}`);
|
|
616
|
+
const bashApprovalMode = bashApprovalModeForTty(stdin.isTTY, stdout.isTTY);
|
|
530
617
|
const attachments = [
|
|
531
618
|
...loadImages(asPaths(args.image)),
|
|
532
619
|
...loadFiles(asPaths(args.file))
|
|
533
620
|
];
|
|
534
621
|
if (args.message || attachments.length > 0) {
|
|
535
|
-
|
|
622
|
+
const rl2 = bashApprovalMode === "interactive" ? createInterface({ input: stdin, output: stdout }) : null;
|
|
623
|
+
try {
|
|
624
|
+
await streamTurn(client, resolved.agent.id, args.message ?? "", attachments, {
|
|
625
|
+
bashApprovalMode,
|
|
626
|
+
...rl2 ? {
|
|
627
|
+
approvalPrompt: {
|
|
628
|
+
question: (question) => rl2.question(question),
|
|
629
|
+
write: (line) => console.log(line)
|
|
630
|
+
}
|
|
631
|
+
} : {}
|
|
632
|
+
});
|
|
633
|
+
} finally {
|
|
634
|
+
rl2?.close();
|
|
635
|
+
}
|
|
536
636
|
return;
|
|
537
637
|
}
|
|
538
638
|
console.log(`chatting with ${resolved.agent.name} (${resolved.model})`);
|
|
539
639
|
console.log("(type /exit to quit)");
|
|
540
640
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
641
|
+
try {
|
|
642
|
+
if (bashApprovalMode === "interactive") {
|
|
643
|
+
while (true) {
|
|
644
|
+
let line;
|
|
645
|
+
try {
|
|
646
|
+
line = await rl.question("> ");
|
|
647
|
+
} catch {
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
const trimmed = line.trim();
|
|
651
|
+
if (trimmed === "/exit" || trimmed === "/quit") break;
|
|
652
|
+
if (!trimmed) continue;
|
|
653
|
+
try {
|
|
654
|
+
await streamTurn(client, resolved.agent.id, trimmed, void 0, {
|
|
655
|
+
bashApprovalMode,
|
|
656
|
+
approvalPrompt: {
|
|
657
|
+
question: (question) => rl.question(question),
|
|
658
|
+
write: (output) => console.log(output)
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
} catch (err) {
|
|
662
|
+
console.error(`error: ${err.message}`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
rl.setPrompt("> ");
|
|
547
667
|
rl.prompt();
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
668
|
+
for await (const line of rl) {
|
|
669
|
+
const trimmed = line.trim();
|
|
670
|
+
if (trimmed === "/exit" || trimmed === "/quit") break;
|
|
671
|
+
if (!trimmed) {
|
|
672
|
+
rl.prompt();
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
await streamTurn(client, resolved.agent.id, trimmed, void 0, {
|
|
677
|
+
bashApprovalMode
|
|
678
|
+
});
|
|
679
|
+
} catch (err) {
|
|
680
|
+
console.error(`error: ${err.message}`);
|
|
681
|
+
}
|
|
682
|
+
rl.prompt();
|
|
683
|
+
}
|
|
554
684
|
}
|
|
555
|
-
|
|
685
|
+
} finally {
|
|
686
|
+
rl.close();
|
|
556
687
|
}
|
|
557
|
-
rl.close();
|
|
558
688
|
}
|
|
559
689
|
});
|
|
560
690
|
var chatResetCmd = defineCommand({
|
|
@@ -566,8 +696,8 @@ var chatResetCmd = defineCommand({
|
|
|
566
696
|
async run({ args }) {
|
|
567
697
|
if (!args.force) {
|
|
568
698
|
process.stdout.write(`reset all chat history for ${args.id}? [y/N] `);
|
|
569
|
-
const answer = await new Promise((
|
|
570
|
-
process.stdin.once("data", (d) =>
|
|
699
|
+
const answer = await new Promise((resolve4) => {
|
|
700
|
+
process.stdin.once("data", (d) => resolve4(String(d).trim().toLowerCase()));
|
|
571
701
|
});
|
|
572
702
|
if (answer !== "y" && answer !== "yes") {
|
|
573
703
|
console.log("aborted");
|
|
@@ -715,8 +845,8 @@ var chatCompactCmd = defineCommand({
|
|
|
715
845
|
process.stdout.write(
|
|
716
846
|
`compact chat history for ${args.id}? head turns become a summary; tail is preserved. [y/N] `
|
|
717
847
|
);
|
|
718
|
-
const answer = await new Promise((
|
|
719
|
-
process.stdin.once("data", (d) =>
|
|
848
|
+
const answer = await new Promise((resolve4) => {
|
|
849
|
+
process.stdin.once("data", (d) => resolve4(String(d).trim().toLowerCase()));
|
|
720
850
|
});
|
|
721
851
|
if (answer !== "y" && answer !== "yes") {
|
|
722
852
|
console.log("aborted");
|
|
@@ -819,7 +949,7 @@ var moveCmd = defineCommand({
|
|
|
819
949
|
teamId: args.team,
|
|
820
950
|
sourceExpectedRevision: source.teamPolicy.revision,
|
|
821
951
|
destinationExpectedRevision: destination.teamPolicy.revision,
|
|
822
|
-
placement: "
|
|
952
|
+
placement: "profile_defaults"
|
|
823
953
|
};
|
|
824
954
|
await client.patch(`/api/agents/${args.agent}/team`, body);
|
|
825
955
|
console.log(`moved ${args.agent} to team ${args.team}`);
|
|
@@ -989,8 +1119,8 @@ var openaiLoginCmd = defineCommand3({
|
|
|
989
1119
|
},
|
|
990
1120
|
onPrompt: async (prompt) => {
|
|
991
1121
|
process.stdout.write(`${prompt.message}: `);
|
|
992
|
-
return new Promise((
|
|
993
|
-
process.stdin.once("data", (d) =>
|
|
1122
|
+
return new Promise((resolve4) => {
|
|
1123
|
+
process.stdin.once("data", (d) => resolve4(String(d).trim()));
|
|
994
1124
|
});
|
|
995
1125
|
},
|
|
996
1126
|
onProgress: (msg) => {
|
|
@@ -1064,12 +1194,435 @@ var authCommand = defineCommand3({
|
|
|
1064
1194
|
});
|
|
1065
1195
|
|
|
1066
1196
|
// src/commands/backup.ts
|
|
1067
|
-
import {
|
|
1068
|
-
import {
|
|
1069
|
-
|
|
1197
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
1198
|
+
import {
|
|
1199
|
+
chmodSync as chmodSync2,
|
|
1200
|
+
closeSync as closeSync2,
|
|
1201
|
+
copyFileSync,
|
|
1202
|
+
createWriteStream,
|
|
1203
|
+
existsSync as existsSync2,
|
|
1204
|
+
fsyncSync as fsyncSync2,
|
|
1205
|
+
lstatSync as lstatSync2,
|
|
1206
|
+
mkdirSync as mkdirSync2,
|
|
1207
|
+
mkdtempSync,
|
|
1208
|
+
openSync as openSync2,
|
|
1209
|
+
readdirSync,
|
|
1210
|
+
readFileSync as readFileSync4,
|
|
1211
|
+
renameSync as renameSync2,
|
|
1212
|
+
rmSync as rmSync2
|
|
1213
|
+
} from "fs";
|
|
1214
|
+
import { tmpdir } from "os";
|
|
1215
|
+
import { basename as basename3, dirname as dirname2, isAbsolute, parse, posix, relative, resolve as resolve2 } from "path";
|
|
1216
|
+
import { DatabaseSync } from "node:sqlite";
|
|
1070
1217
|
import { Readable } from "stream";
|
|
1071
1218
|
import { pipeline } from "stream/promises";
|
|
1072
1219
|
import { defineCommand as defineCommand4 } from "citty";
|
|
1220
|
+
import { extract, list as list2 } from "tar";
|
|
1221
|
+
|
|
1222
|
+
// src/backup-schema.ts
|
|
1223
|
+
import { createHash } from "crypto";
|
|
1224
|
+
var CANONICAL_MIGRATION = "0001_init";
|
|
1225
|
+
var CANONICAL_SCHEMA_HASH = "f2da105d21bdea7bedff3ba601243c22d0a7b2d023b954c714940642152b227f";
|
|
1226
|
+
var CANONICAL_OBJECTS = [
|
|
1227
|
+
["index", "agent_triggers_agent"],
|
|
1228
|
+
["index", "agent_triggers_enabled"],
|
|
1229
|
+
["index", "communication_approval_events_attempt"],
|
|
1230
|
+
["index", "communication_approvals_queue"],
|
|
1231
|
+
["index", "communication_approvals_teams"],
|
|
1232
|
+
["index", "idx_agents_telegram_topic_id"],
|
|
1233
|
+
["index", "idx_provider_models_provider"],
|
|
1234
|
+
["index", "messages_policy_delivery_queue"],
|
|
1235
|
+
["index", "messages_to_unread"],
|
|
1236
|
+
["index", "team_policy_baseline_owner"],
|
|
1237
|
+
["index", "team_policy_blocks_team_time"],
|
|
1238
|
+
["index", "team_template_active_position"],
|
|
1239
|
+
["index", "trigger_dispatches_claimable"],
|
|
1240
|
+
["index", "trigger_dispatches_trigger_time"],
|
|
1241
|
+
["index", "web_tokens_active"],
|
|
1242
|
+
["table", "agent_skills"],
|
|
1243
|
+
["table", "agent_triggers"],
|
|
1244
|
+
["table", "agents"],
|
|
1245
|
+
["table", "communication_approval_events"],
|
|
1246
|
+
["table", "communication_approval_message_grants"],
|
|
1247
|
+
["table", "communication_approvals"],
|
|
1248
|
+
["table", "config"],
|
|
1249
|
+
["table", "mcp_servers"],
|
|
1250
|
+
["table", "messages"],
|
|
1251
|
+
["table", "profile_communication_defaults"],
|
|
1252
|
+
["table", "profile_default_skills"],
|
|
1253
|
+
["table", "profiles"],
|
|
1254
|
+
["table", "provider_models"],
|
|
1255
|
+
["table", "provider_state"],
|
|
1256
|
+
["table", "schema_migrations"],
|
|
1257
|
+
["table", "secrets"],
|
|
1258
|
+
["table", "skill_meta"],
|
|
1259
|
+
["table", "source_slot_bindings"],
|
|
1260
|
+
["table", "team_agent_state"],
|
|
1261
|
+
["table", "team_policies"],
|
|
1262
|
+
["table", "team_policy_block_events"],
|
|
1263
|
+
["table", "team_policy_edges"],
|
|
1264
|
+
["table", "team_template_edges"],
|
|
1265
|
+
["table", "team_template_revision_edges"],
|
|
1266
|
+
["table", "team_template_revision_slots"],
|
|
1267
|
+
["table", "team_template_revisions"],
|
|
1268
|
+
["table", "team_template_slots"],
|
|
1269
|
+
["table", "team_templates"],
|
|
1270
|
+
["table", "teams"],
|
|
1271
|
+
["table", "telegram_allowed_users"],
|
|
1272
|
+
["table", "template_instantiations"],
|
|
1273
|
+
["table", "trigger_dispatches"],
|
|
1274
|
+
["table", "web_tokens"],
|
|
1275
|
+
["trigger", "create_team_policy"],
|
|
1276
|
+
["trigger", "prevent_detached_team_policy_delete"],
|
|
1277
|
+
["trigger", "validate_source_binding_insert"],
|
|
1278
|
+
["trigger", "validate_team_agent_state_insert"],
|
|
1279
|
+
["trigger", "validate_team_policy_baseline_update"],
|
|
1280
|
+
["trigger", "validate_team_policy_edge_insert"],
|
|
1281
|
+
["trigger", "validate_team_template_edge_insert"]
|
|
1282
|
+
];
|
|
1283
|
+
function normalizeSql(sql) {
|
|
1284
|
+
return sql.replace(/\s+/g, " ").trim();
|
|
1285
|
+
}
|
|
1286
|
+
function assertCanonicalBackupSchema(db) {
|
|
1287
|
+
const migrations = db.prepare("SELECT version FROM schema_migrations ORDER BY version").all();
|
|
1288
|
+
if (migrations.length !== 1 || migrations[0]?.version !== CANONICAL_MIGRATION) {
|
|
1289
|
+
throw new Error(
|
|
1290
|
+
`schema_migrations must contain exactly ${CANONICAL_MIGRATION}; found ` + (migrations.map((row) => row.version).join(", ") || "none")
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
const rows = db.prepare(
|
|
1294
|
+
`SELECT type, name, tbl_name, sql
|
|
1295
|
+
FROM sqlite_schema
|
|
1296
|
+
WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%'`
|
|
1297
|
+
).all();
|
|
1298
|
+
const byKey = new Map(rows.map((row) => [`${row.type}\0${row.name}`, row]));
|
|
1299
|
+
const canonicalKeys = new Set(CANONICAL_OBJECTS.map(([type, name]) => `${type}\0${name}`));
|
|
1300
|
+
for (const row of rows) {
|
|
1301
|
+
if (!canonicalKeys.has(`${row.type}\0${row.name}`)) {
|
|
1302
|
+
throw new Error(`unexpected schema ${row.type} is not canonical: ${row.name}`);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
const canonicalRows = [];
|
|
1306
|
+
for (const [type, name] of CANONICAL_OBJECTS) {
|
|
1307
|
+
const row = byKey.get(`${type}\0${name}`);
|
|
1308
|
+
if (!row) throw new Error(`required canonical schema ${type} is missing: ${name}`);
|
|
1309
|
+
canonicalRows.push(row);
|
|
1310
|
+
}
|
|
1311
|
+
const payload = canonicalRows.map((row) => [row.type, row.name, row.tbl_name, normalizeSql(row.sql)].join("\0")).join("\n");
|
|
1312
|
+
const actualHash = createHash("sha256").update(payload).digest("hex");
|
|
1313
|
+
if (actualHash !== CANONICAL_SCHEMA_HASH) {
|
|
1314
|
+
throw new Error(
|
|
1315
|
+
"canonical schema fingerprint does not match this Bazilion release; restore a backup created from the current clean-install schema"
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// src/daemon-liveness.ts
|
|
1321
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
1322
|
+
import {
|
|
1323
|
+
chmodSync,
|
|
1324
|
+
closeSync,
|
|
1325
|
+
fstatSync,
|
|
1326
|
+
fsyncSync,
|
|
1327
|
+
ftruncateSync,
|
|
1328
|
+
futimesSync,
|
|
1329
|
+
lstatSync,
|
|
1330
|
+
mkdirSync,
|
|
1331
|
+
openSync,
|
|
1332
|
+
readFileSync as readFileSync3,
|
|
1333
|
+
realpathSync,
|
|
1334
|
+
renameSync,
|
|
1335
|
+
rmSync,
|
|
1336
|
+
writeSync
|
|
1337
|
+
} from "fs";
|
|
1338
|
+
import { basename as basename2, dirname, join as join2, resolve } from "path";
|
|
1339
|
+
var DAEMON_LIVENESS_FILENAME = "daemon-runtime.json";
|
|
1340
|
+
var STALE_AFTER_MS = 6e4;
|
|
1341
|
+
var PROBE_TIMEOUT_MS = 750;
|
|
1342
|
+
function canonicalHomeIdentity(home) {
|
|
1343
|
+
const absolute = resolve(home);
|
|
1344
|
+
try {
|
|
1345
|
+
return realpathSync.native(absolute);
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
1348
|
+
const parent = dirname(absolute);
|
|
1349
|
+
if (parent === absolute) throw error;
|
|
1350
|
+
return join2(canonicalHomeIdentity(parent), basename2(absolute));
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
function daemonLivenessPath(home) {
|
|
1354
|
+
const canonicalHome = canonicalHomeIdentity(home);
|
|
1355
|
+
const identity = createHash2("sha256").update(canonicalHome).digest("hex").slice(0, 24);
|
|
1356
|
+
return join2(dirname(canonicalHome), `.bazilion-runtime-${identity}.json`);
|
|
1357
|
+
}
|
|
1358
|
+
function errorCode(error) {
|
|
1359
|
+
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1360
|
+
}
|
|
1361
|
+
function parseRecord(raw) {
|
|
1362
|
+
try {
|
|
1363
|
+
const value = JSON.parse(raw);
|
|
1364
|
+
if (value.version !== 1 || typeof value.instanceId !== "string" || !value.instanceId || !Number.isSafeInteger(value.pid) || (value.pid ?? 0) <= 0 || !["daemon", "restore"].includes(value.owner ?? "") || ![
|
|
1365
|
+
"starting",
|
|
1366
|
+
"listening",
|
|
1367
|
+
"restoring",
|
|
1368
|
+
"swapping",
|
|
1369
|
+
"installed",
|
|
1370
|
+
"recovery-required"
|
|
1371
|
+
].includes(value.state ?? "") || !Number.isFinite(value.startedAt)) {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
if (value.owner === "daemon" && !["starting", "listening"].includes(value.state ?? "") || value.owner === "restore" && !["restoring", "swapping", "installed", "recovery-required"].includes(value.state ?? "")) {
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
if (value.owner === "restore" && ["swapping", "recovery-required"].includes(value.state ?? "") && (typeof value.recoveryPath !== "string" || !value.recoveryPath || typeof value.hadPrevious !== "boolean")) {
|
|
1378
|
+
return null;
|
|
1379
|
+
}
|
|
1380
|
+
if (value.owner === "daemon" && value.state === "listening" && (typeof value.host !== "string" || !value.host || !Number.isSafeInteger(value.port) || (value.port ?? 0) < 1 || (value.port ?? 0) > 65535)) {
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
return value;
|
|
1384
|
+
} catch {
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function readRecord(path) {
|
|
1389
|
+
try {
|
|
1390
|
+
const stat = lstatSync(path);
|
|
1391
|
+
const raw = stat.isFile() ? readFileSync3(path, "utf8") : "";
|
|
1392
|
+
return { raw, stat, record: stat.isFile() ? parseRecord(raw) : null };
|
|
1393
|
+
} catch (error) {
|
|
1394
|
+
if (errorCode(error) === "ENOENT") return null;
|
|
1395
|
+
throw error;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
function pidIsAlive(pid) {
|
|
1399
|
+
try {
|
|
1400
|
+
process.kill(pid, 0);
|
|
1401
|
+
return true;
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
return errorCode(error) !== "ESRCH";
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
function endpointFor(record) {
|
|
1407
|
+
if (record.state !== "listening" || !record.host || !record.port) return null;
|
|
1408
|
+
const normalized = record.host.toLowerCase();
|
|
1409
|
+
if (normalized === "0.0.0.0") return `http://127.0.0.1:${record.port}`;
|
|
1410
|
+
if (normalized === "::" || normalized === "0:0:0:0:0:0:0:0") {
|
|
1411
|
+
return `http://[::1]:${record.port}`;
|
|
1412
|
+
}
|
|
1413
|
+
if (normalized === "localhost" || normalized === "127.0.0.1") {
|
|
1414
|
+
return `http://${normalized}:${record.port}`;
|
|
1415
|
+
}
|
|
1416
|
+
if (normalized === "::1" || normalized === "[::1]") {
|
|
1417
|
+
return `http://[::1]:${record.port}`;
|
|
1418
|
+
}
|
|
1419
|
+
return null;
|
|
1420
|
+
}
|
|
1421
|
+
async function endpointHasOwner(endpoint3, instanceId) {
|
|
1422
|
+
const controller = new AbortController();
|
|
1423
|
+
const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
|
|
1424
|
+
try {
|
|
1425
|
+
const response = await fetch(new URL("/api/health", endpoint3), {
|
|
1426
|
+
signal: controller.signal
|
|
1427
|
+
});
|
|
1428
|
+
return response.headers.get("x-bazilion-daemon-instance") === instanceId;
|
|
1429
|
+
} catch {
|
|
1430
|
+
return false;
|
|
1431
|
+
} finally {
|
|
1432
|
+
clearTimeout(timer);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
function sameFile(left, right) {
|
|
1436
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
1437
|
+
}
|
|
1438
|
+
function writeRecord(fd, record) {
|
|
1439
|
+
const body = `${JSON.stringify(record, null, 2)}
|
|
1440
|
+
`;
|
|
1441
|
+
ftruncateSync(fd, 0);
|
|
1442
|
+
writeSync(fd, body, 0, "utf8");
|
|
1443
|
+
fsyncSync(fd);
|
|
1444
|
+
}
|
|
1445
|
+
function fsyncParentDirectory(path) {
|
|
1446
|
+
let directoryFd = null;
|
|
1447
|
+
try {
|
|
1448
|
+
directoryFd = openSync(dirname(path), "r");
|
|
1449
|
+
fsyncSync(directoryFd);
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
if (process.platform !== "win32") throw error;
|
|
1452
|
+
} finally {
|
|
1453
|
+
if (directoryFd !== null) closeSync(directoryFd);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
function reclaimRecord(path, existing) {
|
|
1457
|
+
const identity = createHash2("sha256").update(existing.record?.instanceId ?? existing.raw).update(`\0${existing.stat.dev}\0${existing.stat.ino}`).digest("hex").slice(0, 24);
|
|
1458
|
+
const claim = `${path}.reclaim-${identity}`;
|
|
1459
|
+
try {
|
|
1460
|
+
mkdirSync(claim, { mode: 448 });
|
|
1461
|
+
} catch (error) {
|
|
1462
|
+
if (errorCode(error) !== "EEXIST") throw error;
|
|
1463
|
+
return null;
|
|
1464
|
+
}
|
|
1465
|
+
const current = readRecord(path);
|
|
1466
|
+
if (!current || !sameFile(current.stat, existing.stat) || current.raw !== existing.raw) {
|
|
1467
|
+
return null;
|
|
1468
|
+
}
|
|
1469
|
+
try {
|
|
1470
|
+
renameSync(path, join2(claim, "stale-record.json"));
|
|
1471
|
+
return claim;
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
if (errorCode(error) === "ENOENT") return null;
|
|
1474
|
+
throw error;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
async function acquireHomeRestoreLock(home) {
|
|
1478
|
+
const targetHome = resolve(home);
|
|
1479
|
+
mkdirSync(dirname(targetHome), { recursive: true });
|
|
1480
|
+
const path = daemonLivenessPath(targetHome);
|
|
1481
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1482
|
+
const instanceId = randomUUID();
|
|
1483
|
+
let fd = null;
|
|
1484
|
+
let reclaimClaim = null;
|
|
1485
|
+
for (let attempt = 0; attempt < 8 && fd === null; attempt++) {
|
|
1486
|
+
try {
|
|
1487
|
+
fd = openSync(path, "wx", 384);
|
|
1488
|
+
break;
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
if (errorCode(error) !== "EEXIST") throw error;
|
|
1491
|
+
}
|
|
1492
|
+
const existing = readRecord(path);
|
|
1493
|
+
if (!existing) continue;
|
|
1494
|
+
const endpoint3 = existing.record ? endpointFor(existing.record) : null;
|
|
1495
|
+
if (endpoint3 && existing.record && await endpointHasOwner(endpoint3, existing.record.instanceId)) {
|
|
1496
|
+
throw new Error(
|
|
1497
|
+
`bazilion daemon is running at ${endpoint3} for ${targetHome} \u2014 stop it before restoring`
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
if (!existing.record) {
|
|
1501
|
+
throw new Error(
|
|
1502
|
+
`the daemon ownership record for ${targetHome} is invalid; verify no daemon is running before removing it manually`
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
const livePid = pidIsAlive(existing.record.pid);
|
|
1506
|
+
if (existing.record.owner === "restore" && ["swapping", "recovery-required"].includes(existing.record.state)) {
|
|
1507
|
+
if (livePid) {
|
|
1508
|
+
throw new Error(
|
|
1509
|
+
`bazilion restore process ${existing.record.pid} is ${existing.record.state} ${targetHome} \u2014 wait for it to finish`
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
const phase = existing.record.state.replace("-", " ");
|
|
1513
|
+
throw new Error(
|
|
1514
|
+
`Bazilion restore ${phase} for ${targetHome}; recovery data is at ${existing.record.recoveryPath}. Recover the previous home, then remove ${path} manually.`
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
if (livePid) {
|
|
1518
|
+
const ageMs = Math.max(0, Date.now() - existing.stat.mtimeMs);
|
|
1519
|
+
const staleDetail = ageMs > STALE_AFTER_MS ? " (its heartbeat is stale)" : "";
|
|
1520
|
+
const owner = existing.record.owner === "daemon" ? "daemon" : "restore";
|
|
1521
|
+
throw new Error(
|
|
1522
|
+
`bazilion ${owner} process ${existing.record.pid} owns ${targetHome}${staleDetail} \u2014 stop it before restoring`
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
const claim = reclaimRecord(path, existing);
|
|
1526
|
+
if (claim) {
|
|
1527
|
+
reclaimClaim = claim;
|
|
1528
|
+
console.warn(`removed stale Bazilion daemon ownership record for ${targetHome}`);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (fd === null) {
|
|
1532
|
+
throw new Error(
|
|
1533
|
+
`could not safely acquire exclusive restore ownership for ${targetHome}; try again`
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
const ownedStat = fstatSync(fd);
|
|
1537
|
+
const record = {
|
|
1538
|
+
version: 1,
|
|
1539
|
+
instanceId,
|
|
1540
|
+
pid: process.pid,
|
|
1541
|
+
owner: "restore",
|
|
1542
|
+
state: "restoring",
|
|
1543
|
+
startedAt: Date.now()
|
|
1544
|
+
};
|
|
1545
|
+
try {
|
|
1546
|
+
writeRecord(fd, record);
|
|
1547
|
+
fsyncParentDirectory(path);
|
|
1548
|
+
if (reclaimClaim) rmSync(reclaimClaim, { recursive: true, force: true });
|
|
1549
|
+
try {
|
|
1550
|
+
chmodSync(path, 384);
|
|
1551
|
+
} catch {
|
|
1552
|
+
}
|
|
1553
|
+
} catch (error) {
|
|
1554
|
+
closeSync(fd);
|
|
1555
|
+
fd = null;
|
|
1556
|
+
try {
|
|
1557
|
+
if (sameFile(lstatSync(path), ownedStat)) rmSync(path, { force: true });
|
|
1558
|
+
} catch {
|
|
1559
|
+
}
|
|
1560
|
+
throw error;
|
|
1561
|
+
}
|
|
1562
|
+
let released = false;
|
|
1563
|
+
const stillOwnsPath = () => {
|
|
1564
|
+
try {
|
|
1565
|
+
return sameFile(lstatSync(path), ownedStat);
|
|
1566
|
+
} catch {
|
|
1567
|
+
return false;
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
const heartbeat = setInterval(() => {
|
|
1571
|
+
if (released) return;
|
|
1572
|
+
if (!stillOwnsPath()) {
|
|
1573
|
+
console.error("restore ownership was lost; exiting to protect the Bazilion database");
|
|
1574
|
+
process.exit(1);
|
|
1575
|
+
}
|
|
1576
|
+
try {
|
|
1577
|
+
const now = /* @__PURE__ */ new Date();
|
|
1578
|
+
futimesSync(fd, now, now);
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
console.error(
|
|
1581
|
+
"restore ownership heartbeat failed:",
|
|
1582
|
+
error instanceof Error ? error.message : error
|
|
1583
|
+
);
|
|
1584
|
+
process.exit(1);
|
|
1585
|
+
}
|
|
1586
|
+
}, 2e3);
|
|
1587
|
+
heartbeat.unref();
|
|
1588
|
+
return {
|
|
1589
|
+
path,
|
|
1590
|
+
markSwapping(recoveryPath, hadPrevious) {
|
|
1591
|
+
if (released || !stillOwnsPath()) throw new Error("restore ownership was lost");
|
|
1592
|
+
record.state = "swapping";
|
|
1593
|
+
record.recoveryPath = resolve(recoveryPath);
|
|
1594
|
+
record.hadPrevious = hadPrevious;
|
|
1595
|
+
writeRecord(fd, record);
|
|
1596
|
+
fsyncParentDirectory(path);
|
|
1597
|
+
},
|
|
1598
|
+
markInstalled() {
|
|
1599
|
+
if (released || !stillOwnsPath()) throw new Error("restore ownership was lost");
|
|
1600
|
+
record.state = "installed";
|
|
1601
|
+
delete record.recoveryPath;
|
|
1602
|
+
delete record.hadPrevious;
|
|
1603
|
+
writeRecord(fd, record);
|
|
1604
|
+
},
|
|
1605
|
+
markRecoveryRequired(recoveryPath, hadPrevious) {
|
|
1606
|
+
if (released || !stillOwnsPath()) throw new Error("restore ownership was lost");
|
|
1607
|
+
record.state = "recovery-required";
|
|
1608
|
+
record.recoveryPath = resolve(recoveryPath);
|
|
1609
|
+
record.hadPrevious = hadPrevious;
|
|
1610
|
+
writeRecord(fd, record);
|
|
1611
|
+
},
|
|
1612
|
+
release() {
|
|
1613
|
+
if (released) return;
|
|
1614
|
+
released = true;
|
|
1615
|
+
clearInterval(heartbeat);
|
|
1616
|
+
const ownsPath = stillOwnsPath();
|
|
1617
|
+
const retainForRecovery = ["swapping", "recovery-required"].includes(record.state);
|
|
1618
|
+
closeSync(fd);
|
|
1619
|
+
fd = null;
|
|
1620
|
+
if (ownsPath && !retainForRecovery) rmSync(path, { force: true });
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// src/commands/backup.ts
|
|
1073
1626
|
var createCmd = defineCommand4({
|
|
1074
1627
|
meta: { name: "create", description: "Download a tar.gz backup of ~/.bazilion from the server" },
|
|
1075
1628
|
args: {
|
|
@@ -1082,7 +1635,14 @@ var createCmd = defineCommand4({
|
|
|
1082
1635
|
async run({ args }) {
|
|
1083
1636
|
const cfg = loadClientConfig();
|
|
1084
1637
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1085
|
-
const outAbs =
|
|
1638
|
+
const outAbs = resolve2(args.output ?? `bazilion-backup-${date}.tar.gz`);
|
|
1639
|
+
const configuredHome = resolve2(resolveCliPaths().home);
|
|
1640
|
+
const outputFromHome = relative(configuredHome, outAbs);
|
|
1641
|
+
if (!outputFromHome || !outputFromHome.startsWith("..") && !isAbsolute(outputFromHome)) {
|
|
1642
|
+
throw new Error(
|
|
1643
|
+
`backup output must be outside BAZILION_HOME (${configuredHome}) to avoid nesting backups`
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1086
1646
|
console.log(`downloading backup \u2192 ${outAbs}`);
|
|
1087
1647
|
const res = await fetch(`${cfg.serverUrl}/api/backup`, {
|
|
1088
1648
|
headers: { authorization: `Bearer ${cfg.token}`, origin: cfg.serverUrl }
|
|
@@ -1096,21 +1656,462 @@ var createCmd = defineCommand4({
|
|
|
1096
1656
|
}
|
|
1097
1657
|
throw new Error(`backup failed: ${err}`);
|
|
1098
1658
|
}
|
|
1099
|
-
|
|
1659
|
+
const downloadDir = mkdtempSync(resolve2(tmpdir(), "bazilion-download-"));
|
|
1660
|
+
const partial = resolve2(downloadDir, "backup.tar.gz");
|
|
1661
|
+
try {
|
|
1662
|
+
await pipeline(
|
|
1663
|
+
Readable.fromWeb(res.body),
|
|
1664
|
+
createWriteStream(partial, { flags: "wx", mode: 384 })
|
|
1665
|
+
);
|
|
1666
|
+
await validateArchive(partial);
|
|
1667
|
+
try {
|
|
1668
|
+
renameSync2(partial, outAbs);
|
|
1669
|
+
} catch (error) {
|
|
1670
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "EXDEV") throw error;
|
|
1671
|
+
const installPartial = resolve2(
|
|
1672
|
+
dirname2(outAbs),
|
|
1673
|
+
`.${basename3(outAbs)}.${randomUUID2()}.installing`
|
|
1674
|
+
);
|
|
1675
|
+
try {
|
|
1676
|
+
copyFileSync(partial, installPartial);
|
|
1677
|
+
chmodSync2(installPartial, 384);
|
|
1678
|
+
renameSync2(installPartial, outAbs);
|
|
1679
|
+
} finally {
|
|
1680
|
+
rmSync2(installPartial, { force: true });
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
} finally {
|
|
1684
|
+
rmSync2(downloadDir, { recursive: true, force: true });
|
|
1685
|
+
}
|
|
1100
1686
|
console.log("done");
|
|
1101
1687
|
}
|
|
1102
1688
|
});
|
|
1103
|
-
async function probeServerRunning(
|
|
1689
|
+
async function probeServerRunning(serverUrl) {
|
|
1104
1690
|
try {
|
|
1105
1691
|
const ctrl = new AbortController();
|
|
1106
1692
|
const timer = setTimeout(() => ctrl.abort(), 500);
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1693
|
+
try {
|
|
1694
|
+
const res = await fetch(new URL("/api/health", serverUrl), { signal: ctrl.signal });
|
|
1695
|
+
return res.status >= 200 && res.status < 600;
|
|
1696
|
+
} finally {
|
|
1697
|
+
clearTimeout(timer);
|
|
1698
|
+
}
|
|
1110
1699
|
} catch {
|
|
1111
1700
|
return false;
|
|
1112
1701
|
}
|
|
1113
1702
|
}
|
|
1703
|
+
function loopbackServerUrl(raw) {
|
|
1704
|
+
if (!raw) return null;
|
|
1705
|
+
try {
|
|
1706
|
+
const url = new URL(raw);
|
|
1707
|
+
if (!["http:", "https:"].includes(url.protocol)) return null;
|
|
1708
|
+
const hostname = url.hostname.toLowerCase();
|
|
1709
|
+
if (!["127.0.0.1", "localhost", "[::1]", "::1"].includes(hostname)) return null;
|
|
1710
|
+
return url.origin;
|
|
1711
|
+
} catch {
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
function configuredLoopbackUrls() {
|
|
1716
|
+
const urls = /* @__PURE__ */ new Set(["http://127.0.0.1:4321"]);
|
|
1717
|
+
const envUrl = loopbackServerUrl(process.env.BAZILION_SERVER);
|
|
1718
|
+
if (envUrl) urls.add(envUrl);
|
|
1719
|
+
try {
|
|
1720
|
+
const clientUrl = loopbackServerUrl(loadClientConfig().serverUrl);
|
|
1721
|
+
if (clientUrl) urls.add(clientUrl);
|
|
1722
|
+
} catch {
|
|
1723
|
+
}
|
|
1724
|
+
return [...urls];
|
|
1725
|
+
}
|
|
1726
|
+
var EXTRACTABLE_ENTRY_TYPES = /* @__PURE__ */ new Set([
|
|
1727
|
+
"File",
|
|
1728
|
+
"OldFile",
|
|
1729
|
+
"Directory",
|
|
1730
|
+
"SymbolicLink"
|
|
1731
|
+
]);
|
|
1732
|
+
function normalizeArchivePath(rawPath) {
|
|
1733
|
+
if (!rawPath || rawPath.includes("\0")) throw new Error("backup contains an invalid empty path");
|
|
1734
|
+
if (rawPath.includes("\\")) {
|
|
1735
|
+
throw new Error(`backup contains a non-portable path: ${JSON.stringify(rawPath)}`);
|
|
1736
|
+
}
|
|
1737
|
+
if (rawPath.startsWith("/") || /^[A-Za-z]:/.test(rawPath)) {
|
|
1738
|
+
throw new Error(`backup contains an absolute path: ${JSON.stringify(rawPath)}`);
|
|
1739
|
+
}
|
|
1740
|
+
const segments = [];
|
|
1741
|
+
for (const segment of rawPath.split("/")) {
|
|
1742
|
+
if (!segment || segment === ".") continue;
|
|
1743
|
+
if (segment === "..") {
|
|
1744
|
+
throw new Error(`backup path escapes the restore directory: ${JSON.stringify(rawPath)}`);
|
|
1745
|
+
}
|
|
1746
|
+
segments.push(segment);
|
|
1747
|
+
}
|
|
1748
|
+
return segments.join("/");
|
|
1749
|
+
}
|
|
1750
|
+
function validateArchiveSymbolicLink(path, rawTarget) {
|
|
1751
|
+
if (!rawTarget || rawTarget.includes("\0")) {
|
|
1752
|
+
throw new Error(`backup contains an invalid symbolic link target: ${path}`);
|
|
1753
|
+
}
|
|
1754
|
+
const segments = path.split("/");
|
|
1755
|
+
const isTeamSlot = segments.length === 2 && segments[0] === "teams" && /^[a-z0-9][a-z0-9-]*$/.test(segments[1] ?? "");
|
|
1756
|
+
const portableTarget = rawTarget.replaceAll("\\", "/");
|
|
1757
|
+
const absoluteOnAnyPlatform = isAbsolute(rawTarget) || posix.isAbsolute(portableTarget) || /^[A-Za-z]:\//.test(portableTarget);
|
|
1758
|
+
if (absoluteOnAnyPlatform) {
|
|
1759
|
+
if (!isTeamSlot) {
|
|
1760
|
+
throw new Error(`backup absolute symbolic links are only allowed for Team slots: ${path}`);
|
|
1761
|
+
}
|
|
1762
|
+
if (!isAbsolute(rawTarget)) {
|
|
1763
|
+
throw new Error(`backup contains an invalid Team link target: ${path}`);
|
|
1764
|
+
}
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
if (isTeamSlot) {
|
|
1768
|
+
throw new Error(`backup contains an invalid Team link target: ${path}`);
|
|
1769
|
+
}
|
|
1770
|
+
if (/^[A-Za-z]:/.test(portableTarget)) {
|
|
1771
|
+
throw new Error(`backup contains a non-portable symbolic link target: ${path}`);
|
|
1772
|
+
}
|
|
1773
|
+
const target = posix.normalize(posix.join(posix.dirname(path), portableTarget));
|
|
1774
|
+
if (target === ".." || target.startsWith("../") || posix.isAbsolute(target)) {
|
|
1775
|
+
throw new Error(`backup symbolic link target escapes the restore directory: ${path}`);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
async function validateArchive(file) {
|
|
1779
|
+
const entries = [];
|
|
1780
|
+
await list2({
|
|
1781
|
+
file,
|
|
1782
|
+
strict: true,
|
|
1783
|
+
onentry(entry2) {
|
|
1784
|
+
if (entry2.meta) return;
|
|
1785
|
+
const path = normalizeArchivePath(entry2.path);
|
|
1786
|
+
if (!path) return;
|
|
1787
|
+
if (!EXTRACTABLE_ENTRY_TYPES.has(entry2.type)) {
|
|
1788
|
+
throw new Error(`backup contains unsupported ${entry2.type} entry: ${path}`);
|
|
1789
|
+
}
|
|
1790
|
+
if (entry2.type === "SymbolicLink") {
|
|
1791
|
+
validateArchiveSymbolicLink(path, entry2.linkpath);
|
|
1792
|
+
}
|
|
1793
|
+
entries.push({ path, type: entry2.type, linkpath: entry2.linkpath });
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
1797
|
+
for (const entry2 of entries) {
|
|
1798
|
+
if (byPath.has(entry2.path)) throw new Error(`backup contains a duplicate path: ${entry2.path}`);
|
|
1799
|
+
byPath.set(entry2.path, entry2);
|
|
1800
|
+
}
|
|
1801
|
+
const symlinks = new Set(
|
|
1802
|
+
entries.filter((entry2) => entry2.type === "SymbolicLink").map((entry2) => entry2.path)
|
|
1803
|
+
);
|
|
1804
|
+
for (const entry2 of entries) {
|
|
1805
|
+
const segments = entry2.path.split("/");
|
|
1806
|
+
for (let i = 1; i < segments.length; i++) {
|
|
1807
|
+
const ancestor = segments.slice(0, i).join("/");
|
|
1808
|
+
if (symlinks.has(ancestor)) {
|
|
1809
|
+
throw new Error(`backup entry traverses symbolic link ${ancestor}: ${entry2.path}`);
|
|
1810
|
+
}
|
|
1811
|
+
const ancestorEntry = byPath.get(ancestor);
|
|
1812
|
+
if (ancestorEntry && ancestorEntry.type !== "Directory") {
|
|
1813
|
+
throw new Error(`backup entry traverses non-directory ${ancestor}: ${entry2.path}`);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
for (const required of ["bazilion.db", "auth.json"]) {
|
|
1818
|
+
const entry2 = byPath.get(required);
|
|
1819
|
+
if (!entry2 || entry2.type !== "File" && entry2.type !== "OldFile") {
|
|
1820
|
+
throw new Error(`backup is missing required regular file: ${required}`);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
for (const transient of [
|
|
1824
|
+
"bazilion.db-wal",
|
|
1825
|
+
"bazilion.db-shm",
|
|
1826
|
+
"bazilion.db-journal",
|
|
1827
|
+
DAEMON_LIVENESS_FILENAME
|
|
1828
|
+
]) {
|
|
1829
|
+
if (byPath.has(transient)) {
|
|
1830
|
+
throw new Error(`backup contains unsafe transient SQLite file: ${transient}`);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
return entries;
|
|
1834
|
+
}
|
|
1835
|
+
function validateAuthFile(path) {
|
|
1836
|
+
let parsed;
|
|
1837
|
+
try {
|
|
1838
|
+
parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
1839
|
+
} catch (error) {
|
|
1840
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1841
|
+
throw new Error(`backup auth.json is invalid: ${detail}`);
|
|
1842
|
+
}
|
|
1843
|
+
if (typeof parsed !== "object" || parsed === null || !("token" in parsed) || typeof parsed.token !== "string" || !parsed.token.trim()) {
|
|
1844
|
+
throw new Error("backup auth.json is invalid: token is missing");
|
|
1845
|
+
}
|
|
1846
|
+
return parsed.token;
|
|
1847
|
+
}
|
|
1848
|
+
function validateDatabase(path, authToken) {
|
|
1849
|
+
let db = null;
|
|
1850
|
+
try {
|
|
1851
|
+
db = new DatabaseSync(path, { readOnly: true });
|
|
1852
|
+
const integrity = db.prepare("PRAGMA integrity_check").all();
|
|
1853
|
+
if (integrity.length !== 1 || integrity[0]?.integrity_check !== "ok") {
|
|
1854
|
+
const detail = integrity.map((row) => row.integrity_check).join("; ") || "no result";
|
|
1855
|
+
throw new Error(`integrity_check failed: ${detail}`);
|
|
1856
|
+
}
|
|
1857
|
+
const foreignKeyErrors = db.prepare("PRAGMA foreign_key_check").all();
|
|
1858
|
+
if (foreignKeyErrors.length > 0) {
|
|
1859
|
+
throw new Error(`foreign_key_check failed with ${foreignKeyErrors.length} violation(s)`);
|
|
1860
|
+
}
|
|
1861
|
+
assertCanonicalBackupSchema(db);
|
|
1862
|
+
const tokenHash = createHash3("sha256").update(authToken).digest("hex");
|
|
1863
|
+
const activeBootstrap = db.prepare(
|
|
1864
|
+
`SELECT 1 FROM web_tokens
|
|
1865
|
+
WHERE token_hash = ? AND label = 'bootstrap' AND revoked_at IS NULL
|
|
1866
|
+
LIMIT 1`
|
|
1867
|
+
).get(tokenHash);
|
|
1868
|
+
if (!activeBootstrap) {
|
|
1869
|
+
throw new Error("auth.json token does not match an active web_tokens row");
|
|
1870
|
+
}
|
|
1871
|
+
} catch (error) {
|
|
1872
|
+
if (error instanceof Error && error.message.startsWith("backup database is invalid:")) {
|
|
1873
|
+
throw error;
|
|
1874
|
+
}
|
|
1875
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1876
|
+
throw new Error(`backup database is invalid: ${detail}`);
|
|
1877
|
+
} finally {
|
|
1878
|
+
db?.close();
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
var SLUG_ID = /^[a-z0-9][a-z0-9-]*$/;
|
|
1882
|
+
var AGENT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
1883
|
+
function validateEntityId(id, pattern, entity) {
|
|
1884
|
+
if (typeof id !== "string" || !pattern.test(id)) {
|
|
1885
|
+
throw new Error(`${entity} has an invalid canonical id: ${JSON.stringify(id)}`);
|
|
1886
|
+
}
|
|
1887
|
+
return id;
|
|
1888
|
+
}
|
|
1889
|
+
function containedChildDirectory(root, id, rowLabel) {
|
|
1890
|
+
const child = resolve2(root, id);
|
|
1891
|
+
const fromRoot = relative(root, child);
|
|
1892
|
+
if (!fromRoot || fromRoot.startsWith("..") || isAbsolute(fromRoot)) {
|
|
1893
|
+
throw new Error(`${rowLabel} directory escapes its canonical root: ${JSON.stringify(id)}`);
|
|
1894
|
+
}
|
|
1895
|
+
return child;
|
|
1896
|
+
}
|
|
1897
|
+
function requireExtractedDirectory(path, rowLabel) {
|
|
1898
|
+
try {
|
|
1899
|
+
if (lstatSync2(path).isDirectory()) return;
|
|
1900
|
+
} catch {
|
|
1901
|
+
}
|
|
1902
|
+
throw new Error(`${rowLabel} is missing its canonical directory in the backup`);
|
|
1903
|
+
}
|
|
1904
|
+
function requireExtractedTeamSlot(path, rowLabel) {
|
|
1905
|
+
try {
|
|
1906
|
+
const entry2 = lstatSync2(path);
|
|
1907
|
+
if (entry2.isDirectory() || entry2.isSymbolicLink()) return;
|
|
1908
|
+
} catch {
|
|
1909
|
+
}
|
|
1910
|
+
throw new Error(`${rowLabel} is missing its canonical Team slot in the backup`);
|
|
1911
|
+
}
|
|
1912
|
+
function rebaseRestoredHomeDirectories(database, payload, targetHome) {
|
|
1913
|
+
let db = null;
|
|
1914
|
+
try {
|
|
1915
|
+
db = new DatabaseSync(database);
|
|
1916
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
1917
|
+
const profiles = db.prepare("SELECT id FROM profiles ORDER BY id").all();
|
|
1918
|
+
const agents = db.prepare("SELECT id FROM agents ORDER BY id").all();
|
|
1919
|
+
const teams = db.prepare("SELECT id FROM teams ORDER BY id").all();
|
|
1920
|
+
const rewrites = [];
|
|
1921
|
+
for (const [table, rows, idPattern] of [
|
|
1922
|
+
["profiles", profiles, SLUG_ID],
|
|
1923
|
+
["agents", agents, AGENT_ID]
|
|
1924
|
+
]) {
|
|
1925
|
+
const collection = table;
|
|
1926
|
+
const stagedRoot = resolve2(payload, collection);
|
|
1927
|
+
const targetRoot = resolve2(targetHome, collection);
|
|
1928
|
+
for (const row of rows) {
|
|
1929
|
+
const id = validateEntityId(row.id, idPattern, `${table} row`);
|
|
1930
|
+
const rowLabel = `${table} row ${JSON.stringify(id)}`;
|
|
1931
|
+
const stagedDir = containedChildDirectory(stagedRoot, id, rowLabel);
|
|
1932
|
+
requireExtractedDirectory(stagedDir, rowLabel);
|
|
1933
|
+
rewrites.push({
|
|
1934
|
+
table,
|
|
1935
|
+
id,
|
|
1936
|
+
dir: containedChildDirectory(targetRoot, id, rowLabel)
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
const stagedTeamsRoot = resolve2(payload, "teams");
|
|
1941
|
+
for (const row of teams) {
|
|
1942
|
+
const id = validateEntityId(row.id, SLUG_ID, "teams row");
|
|
1943
|
+
const rowLabel = `teams row ${JSON.stringify(id)}`;
|
|
1944
|
+
requireExtractedTeamSlot(containedChildDirectory(stagedTeamsRoot, id, rowLabel), rowLabel);
|
|
1945
|
+
}
|
|
1946
|
+
db.exec("PRAGMA journal_mode = DELETE");
|
|
1947
|
+
db.exec("PRAGMA synchronous = FULL");
|
|
1948
|
+
let transactionOpen = false;
|
|
1949
|
+
try {
|
|
1950
|
+
db.exec("BEGIN IMMEDIATE");
|
|
1951
|
+
transactionOpen = true;
|
|
1952
|
+
const updateProfile = db.prepare("UPDATE profiles SET dir = ? WHERE id = ?");
|
|
1953
|
+
const updateAgent = db.prepare("UPDATE agents SET dir = ? WHERE id = ?");
|
|
1954
|
+
for (const rewrite of rewrites) {
|
|
1955
|
+
const result = rewrite.table === "profiles" ? updateProfile.run(rewrite.dir, rewrite.id) : updateAgent.run(rewrite.dir, rewrite.id);
|
|
1956
|
+
if (result.changes !== 1) {
|
|
1957
|
+
throw new Error(`could not rebase ${rewrite.table} row ${JSON.stringify(rewrite.id)}`);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
const readProfile = db.prepare("SELECT dir FROM profiles WHERE id = ?");
|
|
1961
|
+
const readAgent = db.prepare("SELECT dir FROM agents WHERE id = ?");
|
|
1962
|
+
for (const rewrite of rewrites) {
|
|
1963
|
+
const row = rewrite.table === "profiles" ? readProfile.get(rewrite.id) : readAgent.get(rewrite.id);
|
|
1964
|
+
if (row?.dir !== rewrite.dir) {
|
|
1965
|
+
throw new Error(
|
|
1966
|
+
`could not verify rebased ${rewrite.table} row ${JSON.stringify(rewrite.id)}`
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
db.exec("COMMIT");
|
|
1971
|
+
transactionOpen = false;
|
|
1972
|
+
} catch (error) {
|
|
1973
|
+
if (transactionOpen) db.exec("ROLLBACK");
|
|
1974
|
+
throw error;
|
|
1975
|
+
}
|
|
1976
|
+
} catch (error) {
|
|
1977
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1978
|
+
throw new Error(`backup database is invalid: ${detail}`);
|
|
1979
|
+
} finally {
|
|
1980
|
+
db?.close();
|
|
1981
|
+
}
|
|
1982
|
+
for (const suffix of ["-wal", "-shm", "-journal"]) {
|
|
1983
|
+
if (existsSync2(`${database}${suffix}`)) {
|
|
1984
|
+
throw new Error(`backup database is invalid: path rebasing left transient ${suffix} state`);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
function removeRebuildableQmdIndexes(payload) {
|
|
1989
|
+
const teamsDir = resolve2(payload, "teams");
|
|
1990
|
+
if (!existsSync2(teamsDir) || !lstatSync2(teamsDir).isDirectory()) return;
|
|
1991
|
+
for (const team of readdirSync(teamsDir, { withFileTypes: true })) {
|
|
1992
|
+
if (!team.isDirectory() || team.isSymbolicLink()) continue;
|
|
1993
|
+
const memoryDir = resolve2(teamsDir, team.name, "memory");
|
|
1994
|
+
if (!existsSync2(memoryDir) || !lstatSync2(memoryDir).isDirectory()) continue;
|
|
1995
|
+
for (const entry2 of readdirSync(memoryDir, { withFileTypes: true })) {
|
|
1996
|
+
if (entry2.isFile() && entry2.name.startsWith(".qmd-index.sqlite")) {
|
|
1997
|
+
rmSync2(resolve2(memoryDir, entry2.name), { force: true });
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
function installValidatedPayload(payload, targetHome, staging, lifecycle, operations = { rename: renameSync2 }) {
|
|
2003
|
+
const previous = resolve2(staging, "previous-home");
|
|
2004
|
+
const hadPrevious = existsSync2(targetHome);
|
|
2005
|
+
const targetParent = dirname2(targetHome);
|
|
2006
|
+
const syncDirectory = operations.fsyncDirectory ?? fsyncDirectory;
|
|
2007
|
+
const recoveryError = (message, recoveryPath, cause) => {
|
|
2008
|
+
let markerError;
|
|
2009
|
+
try {
|
|
2010
|
+
lifecycle?.markRecoveryRequired(recoveryPath, hadPrevious);
|
|
2011
|
+
} catch (error) {
|
|
2012
|
+
markerError = error;
|
|
2013
|
+
}
|
|
2014
|
+
const causeDetail = cause instanceof Error ? cause.message : String(cause);
|
|
2015
|
+
const markerDetail = markerError ? `; recovery marker update failed: ${markerError instanceof Error ? markerError.message : markerError}` : "";
|
|
2016
|
+
return new RestoreRecoveryRequiredError(
|
|
2017
|
+
`${message} (${causeDetail}); recovery data is at ${recoveryPath}${markerDetail}`,
|
|
2018
|
+
recoveryPath
|
|
2019
|
+
);
|
|
2020
|
+
};
|
|
2021
|
+
lifecycle?.markSwapping(previous, hadPrevious);
|
|
2022
|
+
if (hadPrevious) {
|
|
2023
|
+
try {
|
|
2024
|
+
operations.rename(targetHome, previous);
|
|
2025
|
+
} catch (moveError) {
|
|
2026
|
+
lifecycle?.markInstalled();
|
|
2027
|
+
throw moveError;
|
|
2028
|
+
}
|
|
2029
|
+
try {
|
|
2030
|
+
syncDirectory(targetParent);
|
|
2031
|
+
operations.afterPreviousMoved?.();
|
|
2032
|
+
} catch (postMoveError) {
|
|
2033
|
+
throw recoveryError(
|
|
2034
|
+
"the previous home moved but the swap could not continue safely",
|
|
2035
|
+
previous,
|
|
2036
|
+
postMoveError
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
try {
|
|
2041
|
+
operations.rename(payload, targetHome);
|
|
2042
|
+
} catch (installError) {
|
|
2043
|
+
if (hadPrevious) {
|
|
2044
|
+
try {
|
|
2045
|
+
operations.rename(previous, targetHome);
|
|
2046
|
+
} catch (rollbackError) {
|
|
2047
|
+
const installDetail = installError instanceof Error ? installError.message : String(installError);
|
|
2048
|
+
throw recoveryError(
|
|
2049
|
+
`restore install failed (${installDetail}) and rollback failed`,
|
|
2050
|
+
previous,
|
|
2051
|
+
rollbackError
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
try {
|
|
2055
|
+
syncDirectory(targetParent);
|
|
2056
|
+
} catch (rollbackSyncError) {
|
|
2057
|
+
throw recoveryError(
|
|
2058
|
+
"the previous home was restored but its directory entry could not be made durable",
|
|
2059
|
+
targetHome,
|
|
2060
|
+
rollbackSyncError
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
try {
|
|
2064
|
+
lifecycle?.markInstalled();
|
|
2065
|
+
} catch (phaseError) {
|
|
2066
|
+
throw recoveryError(
|
|
2067
|
+
"the previous home was restored but could not be marked safe",
|
|
2068
|
+
targetHome,
|
|
2069
|
+
phaseError
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
} else {
|
|
2073
|
+
lifecycle?.markInstalled();
|
|
2074
|
+
}
|
|
2075
|
+
throw installError;
|
|
2076
|
+
}
|
|
2077
|
+
try {
|
|
2078
|
+
syncDirectory(targetParent);
|
|
2079
|
+
} catch (installSyncError) {
|
|
2080
|
+
throw recoveryError(
|
|
2081
|
+
"the restored home was installed but its directory entry could not be made durable",
|
|
2082
|
+
hadPrevious ? previous : targetHome,
|
|
2083
|
+
installSyncError
|
|
2084
|
+
);
|
|
2085
|
+
}
|
|
2086
|
+
try {
|
|
2087
|
+
lifecycle?.markInstalled();
|
|
2088
|
+
} catch (phaseError) {
|
|
2089
|
+
throw recoveryError(
|
|
2090
|
+
"the restored home was installed but could not be marked safe",
|
|
2091
|
+
hadPrevious ? previous : targetHome,
|
|
2092
|
+
phaseError
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
var RestoreRecoveryRequiredError = class extends Error {
|
|
2097
|
+
recoveryPath;
|
|
2098
|
+
constructor(message, recoveryPath) {
|
|
2099
|
+
super(message);
|
|
2100
|
+
this.name = "RestoreRecoveryRequiredError";
|
|
2101
|
+
this.recoveryPath = recoveryPath;
|
|
2102
|
+
}
|
|
2103
|
+
};
|
|
2104
|
+
function fsyncDirectory(path) {
|
|
2105
|
+
let fd = null;
|
|
2106
|
+
try {
|
|
2107
|
+
fd = openSync2(path, "r");
|
|
2108
|
+
fsyncSync2(fd);
|
|
2109
|
+
} catch (error) {
|
|
2110
|
+
if (process.platform !== "win32") throw error;
|
|
2111
|
+
} finally {
|
|
2112
|
+
if (fd !== null) closeSync2(fd);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
1114
2115
|
var restoreCmd = defineCommand4({
|
|
1115
2116
|
meta: {
|
|
1116
2117
|
name: "restore",
|
|
@@ -1129,37 +2130,91 @@ var restoreCmd = defineCommand4({
|
|
|
1129
2130
|
}
|
|
1130
2131
|
},
|
|
1131
2132
|
async run({ args }) {
|
|
1132
|
-
const file =
|
|
2133
|
+
const file = resolve2(args.file);
|
|
1133
2134
|
if (!existsSync2(file)) throw new Error(`backup file not found: ${file}`);
|
|
1134
2135
|
const paths = resolveCliPaths(args.home);
|
|
1135
|
-
const targetHome = paths.home;
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
throw new Error(
|
|
1139
|
-
"bazilion server appears to be running on :4321 \u2014 stop it first (DB corruption risk), or pass --force to override."
|
|
1140
|
-
);
|
|
2136
|
+
const targetHome = resolve2(paths.home);
|
|
2137
|
+
if (targetHome === parse(targetHome).root) {
|
|
2138
|
+
throw new Error(`refusing to restore over filesystem root: ${targetHome}`);
|
|
1141
2139
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
2140
|
+
const targetParent = dirname2(targetHome);
|
|
2141
|
+
mkdirSync2(targetParent, { recursive: true });
|
|
2142
|
+
const restoreLock = await acquireHomeRestoreLock(targetHome);
|
|
2143
|
+
try {
|
|
2144
|
+
const targetsConfiguredHome = targetHome === resolve2(resolveCliPaths().home);
|
|
2145
|
+
if (targetsConfiguredHome) {
|
|
2146
|
+
for (const serverUrl of configuredLoopbackUrls()) {
|
|
2147
|
+
if (await probeServerRunning(serverUrl)) {
|
|
2148
|
+
throw new Error(
|
|
2149
|
+
`bazilion server appears to be running at ${serverUrl} \u2014 stop it before restoring.`
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
if (existsSync2(targetHome)) {
|
|
2155
|
+
if (!lstatSync2(targetHome).isDirectory()) {
|
|
2156
|
+
throw new Error(`${targetHome} exists and is not a directory`);
|
|
2157
|
+
}
|
|
2158
|
+
const entries = readdirSync(targetHome);
|
|
2159
|
+
if (entries.length > 0 && !args.force) {
|
|
1146
2160
|
throw new Error(
|
|
1147
2161
|
`${targetHome} is not empty. Pass --force to overwrite (destroys existing data).`
|
|
1148
2162
|
);
|
|
1149
2163
|
}
|
|
1150
|
-
rmSync(targetHome, { recursive: true, force: true });
|
|
1151
2164
|
}
|
|
2165
|
+
const staging = mkdtempSync(resolve2(targetParent, ".bazilion-restore-"));
|
|
2166
|
+
const stagedArchive = resolve2(staging, "backup.tar.gz");
|
|
2167
|
+
const payload = resolve2(staging, "payload");
|
|
2168
|
+
let preserveStaging = false;
|
|
2169
|
+
try {
|
|
2170
|
+
copyFileSync(file, stagedArchive);
|
|
2171
|
+
await validateArchive(stagedArchive);
|
|
2172
|
+
mkdirSync2(payload);
|
|
2173
|
+
await extract({
|
|
2174
|
+
file: stagedArchive,
|
|
2175
|
+
cwd: payload,
|
|
2176
|
+
strict: true,
|
|
2177
|
+
preserveOwner: false,
|
|
2178
|
+
// The private copy has already passed complete entry-path and link
|
|
2179
|
+
// validation above. Preserve paths here so an intentionally absolute
|
|
2180
|
+
// external linked-Team target is not stripped into a different,
|
|
2181
|
+
// payload-relative path by npm-tar.
|
|
2182
|
+
preservePaths: true
|
|
2183
|
+
});
|
|
2184
|
+
const database = resolve2(payload, "bazilion.db");
|
|
2185
|
+
const authFile = resolve2(payload, "auth.json");
|
|
2186
|
+
if (!lstatSync2(database).isFile() || !lstatSync2(authFile).isFile()) {
|
|
2187
|
+
throw new Error("backup required files did not extract as regular files");
|
|
2188
|
+
}
|
|
2189
|
+
const authToken = validateAuthFile(authFile);
|
|
2190
|
+
validateDatabase(database, authToken);
|
|
2191
|
+
rebaseRestoredHomeDirectories(database, payload, targetHome);
|
|
2192
|
+
validateDatabase(database, authToken);
|
|
2193
|
+
removeRebuildableQmdIndexes(payload);
|
|
2194
|
+
console.log(`installing validated backup ${file} \u2192 ${targetHome}`);
|
|
2195
|
+
try {
|
|
2196
|
+
installValidatedPayload(payload, targetHome, staging, restoreLock);
|
|
2197
|
+
} catch (error) {
|
|
2198
|
+
if (error instanceof RestoreRecoveryRequiredError) {
|
|
2199
|
+
preserveStaging = true;
|
|
2200
|
+
}
|
|
2201
|
+
throw error;
|
|
2202
|
+
}
|
|
2203
|
+
} finally {
|
|
2204
|
+
if (!preserveStaging) {
|
|
2205
|
+
try {
|
|
2206
|
+
rmSync2(staging, { recursive: true, force: true });
|
|
2207
|
+
} catch (error) {
|
|
2208
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2209
|
+
console.warn(
|
|
2210
|
+
`warning: could not remove restore staging directory ${staging}: ${detail}`
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
} finally {
|
|
2216
|
+
restoreLock.release();
|
|
1152
2217
|
}
|
|
1153
|
-
mkdirSync(targetHome, { recursive: true });
|
|
1154
|
-
console.log(`extracting ${file} \u2192 ${targetHome}`);
|
|
1155
|
-
await new Promise((res, rej) => {
|
|
1156
|
-
const proc = spawn("tar", ["-xzf", file, "-C", targetHome], { stdio: "inherit" });
|
|
1157
|
-
proc.on("error", rej);
|
|
1158
|
-
proc.on("exit", (code) => {
|
|
1159
|
-
if (code === 0) res();
|
|
1160
|
-
else rej(new Error(`tar exited with code ${code}`));
|
|
1161
|
-
});
|
|
1162
|
-
});
|
|
1163
2218
|
console.log(`restored to ${targetHome}`);
|
|
1164
2219
|
console.log("start the server with: bazilion serve (migrations apply automatically)");
|
|
1165
2220
|
}
|
|
@@ -1388,19 +2443,19 @@ var configCommand = defineCommand6({
|
|
|
1388
2443
|
});
|
|
1389
2444
|
|
|
1390
2445
|
// src/commands/dashboard.ts
|
|
1391
|
-
import { spawn
|
|
2446
|
+
import { spawn } from "child_process";
|
|
1392
2447
|
import { existsSync as existsSync3 } from "fs";
|
|
1393
2448
|
import { connect } from "net";
|
|
1394
|
-
import { join as
|
|
2449
|
+
import { join as join3 } from "path";
|
|
1395
2450
|
import { defineCommand as defineCommand7 } from "citty";
|
|
1396
|
-
var bundledDaemonEntry =
|
|
1397
|
-
var sourceDaemonEntry =
|
|
2451
|
+
var bundledDaemonEntry = join3(import.meta.dirname, "daemon.js");
|
|
2452
|
+
var sourceDaemonEntry = join3(import.meta.dirname, "..", "..", "..", "daemon", "src", "index.ts");
|
|
1398
2453
|
var daemonEntry = existsSync3(bundledDaemonEntry) ? bundledDaemonEntry : sourceDaemonEntry;
|
|
1399
|
-
var bundledWebServerEntry =
|
|
1400
|
-
var sourceWebServerEntry =
|
|
2454
|
+
var bundledWebServerEntry = join3(import.meta.dirname, "web-server.js");
|
|
2455
|
+
var sourceWebServerEntry = join3(import.meta.dirname, "..", "web-server.ts");
|
|
1401
2456
|
var webServerEntry = existsSync3(bundledWebServerEntry) ? bundledWebServerEntry : sourceWebServerEntry;
|
|
1402
|
-
var bundledWebDist =
|
|
1403
|
-
var sourceWebDist =
|
|
2457
|
+
var bundledWebDist = join3(import.meta.dirname, "web");
|
|
2458
|
+
var sourceWebDist = join3(import.meta.dirname, "..", "..", "..", "web", "dist");
|
|
1404
2459
|
var webDist = existsSync3(bundledWebDist) ? bundledWebDist : sourceWebDist;
|
|
1405
2460
|
function nodeArgs(entry2) {
|
|
1406
2461
|
return entry2.endsWith(".ts") ? ["--import", "tsx/esm", entry2] : [entry2];
|
|
@@ -1419,12 +2474,12 @@ function probeHost(host) {
|
|
|
1419
2474
|
return host;
|
|
1420
2475
|
}
|
|
1421
2476
|
function isTcpPortOpen(host, port) {
|
|
1422
|
-
return new Promise((
|
|
2477
|
+
return new Promise((resolve4) => {
|
|
1423
2478
|
const socket = connect({ host: probeHost(host), port: Number(port) });
|
|
1424
2479
|
const done = (open) => {
|
|
1425
2480
|
socket.removeAllListeners();
|
|
1426
2481
|
socket.destroy();
|
|
1427
|
-
|
|
2482
|
+
resolve4(open);
|
|
1428
2483
|
};
|
|
1429
2484
|
socket.setTimeout(600);
|
|
1430
2485
|
socket.once("connect", () => done(true));
|
|
@@ -1474,14 +2529,14 @@ function openBrowser(url) {
|
|
|
1474
2529
|
const platform = process.platform;
|
|
1475
2530
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : process.env.BROWSER || "xdg-open";
|
|
1476
2531
|
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
1477
|
-
const proc =
|
|
2532
|
+
const proc = spawn(cmd, args, { detached: true, stdio: "ignore" });
|
|
1478
2533
|
proc.unref();
|
|
1479
2534
|
}
|
|
1480
2535
|
function spawnProcess(label, entry2, env) {
|
|
1481
2536
|
if (!existsSync3(entry2)) {
|
|
1482
2537
|
throw new Error(`${label} entry not found at ${entry2}`);
|
|
1483
2538
|
}
|
|
1484
|
-
return
|
|
2539
|
+
return spawn(process.execPath, nodeArgs(entry2), {
|
|
1485
2540
|
env,
|
|
1486
2541
|
stdio: "inherit"
|
|
1487
2542
|
});
|
|
@@ -1563,7 +2618,7 @@ var dashboardCommand = defineCommand7({
|
|
|
1563
2618
|
`web UI port ${webPort} is already in use. Re-run with --port <free-port>.`
|
|
1564
2619
|
);
|
|
1565
2620
|
}
|
|
1566
|
-
if (!existsSync3(
|
|
2621
|
+
if (!existsSync3(join3(webDist, "server", "server.js"))) {
|
|
1567
2622
|
throw new Error(
|
|
1568
2623
|
`bazilion web UI bundle not found at ${webDist}. Run "pnpm --filter @bazilion/web build" from a source checkout, or reinstall the published package.`
|
|
1569
2624
|
);
|
|
@@ -1590,13 +2645,13 @@ var dashboardCommand = defineCommand7({
|
|
|
1590
2645
|
if (children.length === 0) return;
|
|
1591
2646
|
process.on("SIGINT", terminateChildren);
|
|
1592
2647
|
process.on("SIGTERM", terminateChildren);
|
|
1593
|
-
await new Promise((
|
|
2648
|
+
await new Promise((resolve4) => {
|
|
1594
2649
|
let remaining = children.length;
|
|
1595
2650
|
for (const child of children) {
|
|
1596
2651
|
child.on("close", (code) => {
|
|
1597
2652
|
remaining--;
|
|
1598
2653
|
if (!signalled && code && code !== 0) process.exitCode = code;
|
|
1599
|
-
if (remaining === 0)
|
|
2654
|
+
if (remaining === 0) resolve4();
|
|
1600
2655
|
});
|
|
1601
2656
|
}
|
|
1602
2657
|
});
|
|
@@ -1687,6 +2742,36 @@ var doctorCommand = defineCommand8({
|
|
|
1687
2742
|
` ${r.scheduler.enabled ? "\u2713" : "-"} scheduler${r.scheduler.enabled ? ` (tick ${r.scheduler.tickMs}ms)` : " (BAZILION_SCHEDULER=off)"}`
|
|
1688
2743
|
);
|
|
1689
2744
|
console.log();
|
|
2745
|
+
console.log("agent shell security");
|
|
2746
|
+
if (!r.shellSecurity.ok) {
|
|
2747
|
+
check("valid shell-security configuration", false, r.shellSecurity.error);
|
|
2748
|
+
} else {
|
|
2749
|
+
if (r.shellSecurity.approvalMode === "dangerous") {
|
|
2750
|
+
console.log(" - dangerous-command approval enabled");
|
|
2751
|
+
console.log(
|
|
2752
|
+
" web and TTY CLI turns can allow once; background and non-TTY turns auto-deny"
|
|
2753
|
+
);
|
|
2754
|
+
} else {
|
|
2755
|
+
console.log(" - dangerous-command approval off");
|
|
2756
|
+
console.log(" set BAZILION_BASH_APPROVAL=dangerous to opt in");
|
|
2757
|
+
}
|
|
2758
|
+
if (r.shellSecurity.sandboxMode === "docker") {
|
|
2759
|
+
console.log(` - Docker sandbox configured (${r.shellSecurity.sandboxImage})`);
|
|
2760
|
+
console.log(
|
|
2761
|
+
" workspace-only writable mount \xB7 bounded inputs/skills/memory read-only \xB7 network disabled"
|
|
2762
|
+
);
|
|
2763
|
+
console.log(" host coding tools hidden");
|
|
2764
|
+
console.log(
|
|
2765
|
+
" policy syntax is valid; Docker and image availability are checked on execution"
|
|
2766
|
+
);
|
|
2767
|
+
console.log(" commands fail closed if either is unavailable");
|
|
2768
|
+
} else {
|
|
2769
|
+
console.log(" - sandbox off (agent shell and coding tools run on the host)");
|
|
2770
|
+
console.log(" approval is a host-execution tripwire, not a filesystem sandbox");
|
|
2771
|
+
console.log(" set BAZILION_BASH_SANDBOX=docker to opt in");
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
console.log();
|
|
1690
2775
|
console.log("operational counts");
|
|
1691
2776
|
console.log(` ${r.triggers.active} active trigger(s), ${r.triggers.disabled} disabled`);
|
|
1692
2777
|
console.log(` ${r.tokens.active} active web token(s)`);
|
|
@@ -1794,7 +2879,7 @@ var inboxCommand = defineCommand9({
|
|
|
1794
2879
|
});
|
|
1795
2880
|
|
|
1796
2881
|
// src/commands/login.ts
|
|
1797
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2882
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
1798
2883
|
import { defineCommand as defineCommand10 } from "citty";
|
|
1799
2884
|
function normalizeServer(raw) {
|
|
1800
2885
|
let url;
|
|
@@ -1833,7 +2918,7 @@ var loginCommand = defineCommand10({
|
|
|
1833
2918
|
`${paths.authFile} not found. Start the daemon with "bazilion serve" first \u2014 login stores the remote there.`
|
|
1834
2919
|
);
|
|
1835
2920
|
}
|
|
1836
|
-
const auth = JSON.parse(
|
|
2921
|
+
const auth = JSON.parse(readFileSync5(paths.authFile, "utf8"));
|
|
1837
2922
|
if (args.clear) {
|
|
1838
2923
|
if (!auth.remote) {
|
|
1839
2924
|
console.log("no remote stored \u2014 nothing to clear");
|
|
@@ -2081,11 +3166,11 @@ var memoryCommand = defineCommand12({
|
|
|
2081
3166
|
});
|
|
2082
3167
|
|
|
2083
3168
|
// src/commands/profile.ts
|
|
2084
|
-
import { spawn as
|
|
3169
|
+
import { spawn as spawn2 } from "child_process";
|
|
2085
3170
|
import { randomBytes } from "crypto";
|
|
2086
|
-
import { readFileSync as
|
|
2087
|
-
import { tmpdir } from "os";
|
|
2088
|
-
import { join as
|
|
3171
|
+
import { readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3172
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3173
|
+
import { join as join4 } from "path";
|
|
2089
3174
|
|
|
2090
3175
|
// ../../packages/api-types/src/index.ts
|
|
2091
3176
|
var PROFILE_FILES = [
|
|
@@ -2094,8 +3179,7 @@ var PROFILE_FILES = [
|
|
|
2094
3179
|
"IDENTITY.md",
|
|
2095
3180
|
"BOOTSTRAP.md",
|
|
2096
3181
|
"AGENTS.md",
|
|
2097
|
-
"TOOLS.md"
|
|
2098
|
-
"HEARTBEAT.md"
|
|
3182
|
+
"TOOLS.md"
|
|
2099
3183
|
];
|
|
2100
3184
|
|
|
2101
3185
|
// src/commands/profile.ts
|
|
@@ -2151,15 +3235,11 @@ var createCmd2 = defineCommand13({
|
|
|
2151
3235
|
"tools-file": {
|
|
2152
3236
|
type: "string",
|
|
2153
3237
|
description: "Path to TOOLS.md content (optional; omit to skip)"
|
|
2154
|
-
},
|
|
2155
|
-
"heartbeat-file": {
|
|
2156
|
-
type: "string",
|
|
2157
|
-
description: "Path to HEARTBEAT.md content (optional; omit to skip)"
|
|
2158
3238
|
}
|
|
2159
3239
|
},
|
|
2160
3240
|
async run({ args }) {
|
|
2161
3241
|
const skillsMode = parseSkillsMode(args["skills-mode"]);
|
|
2162
|
-
const readTemplate = (path) => path ?
|
|
3242
|
+
const readTemplate = (path) => path ? readFileSync6(path, "utf8") : void 0;
|
|
2163
3243
|
const body = {
|
|
2164
3244
|
id: args.id,
|
|
2165
3245
|
name: args.name,
|
|
@@ -2170,8 +3250,7 @@ var createCmd2 = defineCommand13({
|
|
|
2170
3250
|
identity: readTemplate(args["identity-file"]),
|
|
2171
3251
|
bootstrap: args["skip-bootstrap"] ? null : readTemplate(args["bootstrap-file"]) ?? void 0,
|
|
2172
3252
|
agents: readTemplate(args["agents-file"]),
|
|
2173
|
-
tools: readTemplate(args["tools-file"])
|
|
2174
|
-
heartbeat: readTemplate(args["heartbeat-file"])
|
|
3253
|
+
tools: readTemplate(args["tools-file"])
|
|
2175
3254
|
};
|
|
2176
3255
|
const client = createClient2();
|
|
2177
3256
|
const profile = await client.post("/api/profiles", body);
|
|
@@ -2182,12 +3261,12 @@ var listCmd6 = defineCommand13({
|
|
|
2182
3261
|
meta: { name: "list", description: "List profiles" },
|
|
2183
3262
|
async run() {
|
|
2184
3263
|
const client = createClient2();
|
|
2185
|
-
const
|
|
2186
|
-
if (
|
|
3264
|
+
const list4 = await client.get("/api/profiles");
|
|
3265
|
+
if (list4.length === 0) {
|
|
2187
3266
|
console.log("(no profiles)");
|
|
2188
3267
|
return;
|
|
2189
3268
|
}
|
|
2190
|
-
const rows =
|
|
3269
|
+
const rows = list4.map((p) => [p.id, p.defaultModel, p.name]);
|
|
2191
3270
|
for (const line of columnize(rows)) console.log(line);
|
|
2192
3271
|
}
|
|
2193
3272
|
});
|
|
@@ -2210,7 +3289,6 @@ var showCmd4 = defineCommand13({
|
|
|
2210
3289
|
console.log(`bootstrap: ${loaded.files.bootstrap !== null ? "yes" : "no"}`);
|
|
2211
3290
|
console.log(`agents: ${loaded.files.agents !== null ? "yes" : "no"}`);
|
|
2212
3291
|
console.log(`tools: ${loaded.files.tools !== null ? "yes" : "no"}`);
|
|
2213
|
-
console.log(`heartbeat: ${loaded.files.heartbeat !== null ? "yes" : "no"}`);
|
|
2214
3292
|
if (loaded.identity) {
|
|
2215
3293
|
const fields = Object.entries(loaded.identity).filter(([, v]) => typeof v === "string" && v.length > 0).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
2216
3294
|
if (fields) console.log(`identity: ${fields}`);
|
|
@@ -2236,17 +3314,17 @@ var editCmd2 = defineCommand13({
|
|
|
2236
3314
|
const { content } = await client.get(
|
|
2237
3315
|
`/api/profiles/${args.id}/files/${file}`
|
|
2238
3316
|
);
|
|
2239
|
-
const tmpPath =
|
|
3317
|
+
const tmpPath = join4(tmpdir2(), `bazilion-${args.id}-${file}-${randomBytes(4).toString("hex")}`);
|
|
2240
3318
|
writeFileSync3(tmpPath, content);
|
|
2241
3319
|
try {
|
|
2242
3320
|
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
2243
|
-
const code = await new Promise((
|
|
2244
|
-
const child =
|
|
3321
|
+
const code = await new Promise((resolve4, reject) => {
|
|
3322
|
+
const child = spawn2(editor, [tmpPath], { stdio: "inherit" });
|
|
2245
3323
|
child.on("error", reject);
|
|
2246
|
-
child.on("close", (c) =>
|
|
3324
|
+
child.on("close", (c) => resolve4(c ?? 0));
|
|
2247
3325
|
});
|
|
2248
3326
|
if (code !== 0) throw new Error(`editor exited with code ${code}`);
|
|
2249
|
-
const updated =
|
|
3327
|
+
const updated = readFileSync6(tmpPath, "utf8");
|
|
2250
3328
|
if (updated === content) {
|
|
2251
3329
|
console.log("(no changes)");
|
|
2252
3330
|
return;
|
|
@@ -2256,7 +3334,7 @@ var editCmd2 = defineCommand13({
|
|
|
2256
3334
|
console.log(`saved ${file}`);
|
|
2257
3335
|
} finally {
|
|
2258
3336
|
try {
|
|
2259
|
-
|
|
3337
|
+
rmSync3(tmpPath, { force: true });
|
|
2260
3338
|
} catch {
|
|
2261
3339
|
}
|
|
2262
3340
|
}
|
|
@@ -2501,12 +3579,12 @@ var sendCommand = defineCommand15({
|
|
|
2501
3579
|
});
|
|
2502
3580
|
|
|
2503
3581
|
// src/commands/serve.ts
|
|
2504
|
-
import { spawn as
|
|
3582
|
+
import { spawn as spawn3 } from "child_process";
|
|
2505
3583
|
import { existsSync as existsSync5 } from "fs";
|
|
2506
|
-
import { join as
|
|
3584
|
+
import { join as join5 } from "path";
|
|
2507
3585
|
import { defineCommand as defineCommand16 } from "citty";
|
|
2508
|
-
var bundledDaemonEntry2 =
|
|
2509
|
-
var sourceDaemonEntry2 =
|
|
3586
|
+
var bundledDaemonEntry2 = join5(import.meta.dirname, "daemon.js");
|
|
3587
|
+
var sourceDaemonEntry2 = join5(import.meta.dirname, "..", "..", "..", "daemon", "src", "index.ts");
|
|
2510
3588
|
var daemonEntry2 = existsSync5(bundledDaemonEntry2) ? bundledDaemonEntry2 : sourceDaemonEntry2;
|
|
2511
3589
|
var serveCommand = defineCommand16({
|
|
2512
3590
|
meta: {
|
|
@@ -2541,7 +3619,7 @@ var serveCommand = defineCommand16({
|
|
|
2541
3619
|
console.log(`starting bazilion daemon at http://${host}:${port}`);
|
|
2542
3620
|
console.log("(web UI runs separately: cd apps/web && pnpm dev)");
|
|
2543
3621
|
const nodeArgs2 = daemonEntry2.endsWith(".ts") ? ["--import", "tsx/esm", daemonEntry2] : [daemonEntry2];
|
|
2544
|
-
const proc =
|
|
3622
|
+
const proc = spawn3("node", nodeArgs2, {
|
|
2545
3623
|
env,
|
|
2546
3624
|
stdio: "inherit"
|
|
2547
3625
|
});
|
|
@@ -2557,17 +3635,17 @@ var serveCommand = defineCommand16({
|
|
|
2557
3635
|
signalled = true;
|
|
2558
3636
|
});
|
|
2559
3637
|
process.on("SIGTERM", onSigterm);
|
|
2560
|
-
await new Promise((
|
|
3638
|
+
await new Promise((resolve4, reject) => {
|
|
2561
3639
|
proc.on("error", (err) => reject(err));
|
|
2562
3640
|
proc.on("close", (code) => {
|
|
2563
3641
|
if (signalled) {
|
|
2564
3642
|
process.exitCode = 0;
|
|
2565
|
-
|
|
3643
|
+
resolve4();
|
|
2566
3644
|
} else if (code === 0 || code === null) {
|
|
2567
|
-
|
|
3645
|
+
resolve4();
|
|
2568
3646
|
} else {
|
|
2569
3647
|
process.exitCode = code;
|
|
2570
|
-
|
|
3648
|
+
resolve4();
|
|
2571
3649
|
}
|
|
2572
3650
|
});
|
|
2573
3651
|
});
|
|
@@ -2575,8 +3653,8 @@ var serveCommand = defineCommand16({
|
|
|
2575
3653
|
});
|
|
2576
3654
|
|
|
2577
3655
|
// src/commands/skill.ts
|
|
2578
|
-
import { existsSync as existsSync6, readFileSync as
|
|
2579
|
-
import { basename as
|
|
3656
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, statSync } from "fs";
|
|
3657
|
+
import { basename as basename4, resolve as resolve3 } from "path";
|
|
2580
3658
|
import { defineCommand as defineCommand17 } from "citty";
|
|
2581
3659
|
function printFindings(findings, prefix = " ") {
|
|
2582
3660
|
for (const f of findings) {
|
|
@@ -2640,13 +3718,13 @@ var importCmd = defineCommand17({
|
|
|
2640
3718
|
},
|
|
2641
3719
|
async run({ args }) {
|
|
2642
3720
|
const client = createClient2();
|
|
2643
|
-
const absFrom =
|
|
3721
|
+
const absFrom = resolve3(args.from);
|
|
2644
3722
|
const isLocalZip = args.from.toLowerCase().endsWith(".zip") && existsSync6(absFrom) && statSync(absFrom).isFile();
|
|
2645
3723
|
let result;
|
|
2646
3724
|
try {
|
|
2647
3725
|
if (isLocalZip) {
|
|
2648
|
-
const bytes =
|
|
2649
|
-
const file = new File([bytes],
|
|
3726
|
+
const bytes = readFileSync7(absFrom);
|
|
3727
|
+
const file = new File([bytes], basename4(absFrom), { type: "application/zip" });
|
|
2650
3728
|
const fd = new FormData();
|
|
2651
3729
|
fd.set("file", file);
|
|
2652
3730
|
if (args.force) fd.set("force", "true");
|
|
@@ -2715,12 +3793,12 @@ var skillCommand = defineCommand17({
|
|
|
2715
3793
|
});
|
|
2716
3794
|
|
|
2717
3795
|
// src/commands/team.ts
|
|
2718
|
-
import { readFileSync as
|
|
3796
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
2719
3797
|
import { stdin as stdin2 } from "process";
|
|
2720
3798
|
import { defineCommand as defineCommand19 } from "citty";
|
|
2721
3799
|
|
|
2722
3800
|
// src/commands/team-policy.ts
|
|
2723
|
-
import { readFileSync as
|
|
3801
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
2724
3802
|
import { defineCommand as defineCommand18 } from "citty";
|
|
2725
3803
|
|
|
2726
3804
|
// src/team-interchange.ts
|
|
@@ -2937,7 +4015,7 @@ function edgeDiff(before, after) {
|
|
|
2937
4015
|
// src/commands/team-policy.ts
|
|
2938
4016
|
function readJson(path) {
|
|
2939
4017
|
try {
|
|
2940
|
-
return JSON.parse(
|
|
4018
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
2941
4019
|
} catch (error) {
|
|
2942
4020
|
throw new Error(`invalid JSON: ${error.message}`);
|
|
2943
4021
|
}
|
|
@@ -3207,12 +4285,12 @@ var listCmd9 = defineCommand19({
|
|
|
3207
4285
|
meta: { name: "list", description: "List teams" },
|
|
3208
4286
|
async run() {
|
|
3209
4287
|
const client = createClient2();
|
|
3210
|
-
const
|
|
3211
|
-
if (
|
|
4288
|
+
const list4 = await client.get("/api/teams");
|
|
4289
|
+
if (list4.length === 0) {
|
|
3212
4290
|
console.log("(no teams)");
|
|
3213
4291
|
return;
|
|
3214
4292
|
}
|
|
3215
|
-
const rows =
|
|
4293
|
+
const rows = list4.map((g) => [g.id, g.path]);
|
|
3216
4294
|
for (const line of columnize(rows)) console.log(line);
|
|
3217
4295
|
}
|
|
3218
4296
|
});
|
|
@@ -3261,7 +4339,7 @@ var userMdSetCmd = defineCommand19({
|
|
|
3261
4339
|
async run({ args }) {
|
|
3262
4340
|
let content;
|
|
3263
4341
|
if (args["from-stdin"]) content = await readStdin();
|
|
3264
|
-
else if (args.file) content =
|
|
4342
|
+
else if (args.file) content = readFileSync9(args.file, "utf8");
|
|
3265
4343
|
else if (args.text !== void 0) content = args.text;
|
|
3266
4344
|
else {
|
|
3267
4345
|
console.error("team user-md set: provide --file, --from-stdin, or --text");
|
|
@@ -3354,11 +4432,11 @@ var teamCommand = defineCommand19({
|
|
|
3354
4432
|
});
|
|
3355
4433
|
|
|
3356
4434
|
// src/commands/team-template.ts
|
|
3357
|
-
import { readFileSync as
|
|
4435
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
3358
4436
|
import { defineCommand as defineCommand20 } from "citty";
|
|
3359
4437
|
function jsonFile(path) {
|
|
3360
4438
|
try {
|
|
3361
|
-
return JSON.parse(
|
|
4439
|
+
return JSON.parse(readFileSync10(path, "utf8"));
|
|
3362
4440
|
} catch (error) {
|
|
3363
4441
|
throw new Error(`invalid JSON: ${error.message}`);
|
|
3364
4442
|
}
|
|
@@ -3383,7 +4461,7 @@ function printTeam(detail) {
|
|
|
3383
4461
|
console.log(line);
|
|
3384
4462
|
}
|
|
3385
4463
|
}
|
|
3386
|
-
var
|
|
4464
|
+
var list3 = defineCommand20({
|
|
3387
4465
|
meta: { name: "list", description: "List canonical Team templates" },
|
|
3388
4466
|
args: { json: { type: "boolean", description: "Emit stable JSON" } },
|
|
3389
4467
|
async run({ args }) {
|
|
@@ -3537,7 +4615,7 @@ var teamTemplateCommand = defineCommand20({
|
|
|
3537
4615
|
name: "team-template",
|
|
3538
4616
|
description: "Inspect and exchange canonical Team templates"
|
|
3539
4617
|
},
|
|
3540
|
-
subCommands: { list:
|
|
4618
|
+
subCommands: { list: list3, show: show3, export: exportCommand2, import: importCommand2 }
|
|
3541
4619
|
});
|
|
3542
4620
|
|
|
3543
4621
|
// src/commands/telegram.ts
|
|
@@ -3931,7 +5009,7 @@ var tokenCommand = defineCommand22({
|
|
|
3931
5009
|
// src/commands/trigger.ts
|
|
3932
5010
|
import { defineCommand as defineCommand23 } from "citty";
|
|
3933
5011
|
var addCmd3 = defineCommand23({
|
|
3934
|
-
meta: { name: "add", description: "Add
|
|
5012
|
+
meta: { name: "add", description: "Add an interval / cron trigger to an agent" },
|
|
3935
5013
|
args: {
|
|
3936
5014
|
agent: { type: "positional", required: true },
|
|
3937
5015
|
every: {
|
|
@@ -4030,20 +5108,49 @@ var disableCmd3 = defineCommand23({
|
|
|
4030
5108
|
console.log(`disabled trigger ${args.id}`);
|
|
4031
5109
|
}
|
|
4032
5110
|
});
|
|
5111
|
+
var historyCmd = defineCommand23({
|
|
5112
|
+
meta: { name: "history", description: "Show recent dispatches for a trigger" },
|
|
5113
|
+
args: {
|
|
5114
|
+
id: { type: "positional", required: true },
|
|
5115
|
+
limit: { type: "string", description: "Maximum rows (default 20)" }
|
|
5116
|
+
},
|
|
5117
|
+
async run({ args }) {
|
|
5118
|
+
const limit = args.limit ? Number(args.limit) : 20;
|
|
5119
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
|
|
5120
|
+
throw new Error("--limit must be an integer from 1 to 100");
|
|
5121
|
+
}
|
|
5122
|
+
const client = createClient2();
|
|
5123
|
+
const { dispatches } = await client.get(
|
|
5124
|
+
`/api/triggers/${args.id}/dispatches?limit=${limit}`
|
|
5125
|
+
);
|
|
5126
|
+
if (dispatches.length === 0) {
|
|
5127
|
+
console.log("(no dispatches)");
|
|
5128
|
+
return;
|
|
5129
|
+
}
|
|
5130
|
+
const rows = dispatches.map((dispatch) => [
|
|
5131
|
+
new Date(dispatch.scheduledAt).toISOString(),
|
|
5132
|
+
dispatch.status,
|
|
5133
|
+
`attempts: ${dispatch.attemptCount}`,
|
|
5134
|
+
dispatch.lastError ?? ""
|
|
5135
|
+
]);
|
|
5136
|
+
for (const line of columnize(rows)) console.log(line);
|
|
5137
|
+
}
|
|
5138
|
+
});
|
|
4033
5139
|
var triggerCommand = defineCommand23({
|
|
4034
|
-
meta: { name: "trigger", description: "Manage agent
|
|
5140
|
+
meta: { name: "trigger", description: "Manage scheduled agent triggers" },
|
|
4035
5141
|
subCommands: {
|
|
4036
5142
|
add: addCmd3,
|
|
4037
5143
|
list: listCmd11,
|
|
4038
5144
|
rm: rmCmd6,
|
|
4039
5145
|
enable: enableCmd3,
|
|
4040
|
-
disable: disableCmd3
|
|
5146
|
+
disable: disableCmd3,
|
|
5147
|
+
history: historyCmd
|
|
4041
5148
|
}
|
|
4042
5149
|
});
|
|
4043
5150
|
|
|
4044
5151
|
// src/commands/uninstall.ts
|
|
4045
|
-
import { existsSync as existsSync7, readdirSync as readdirSync2, rmSync as
|
|
4046
|
-
import { join as
|
|
5152
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2, rmSync as rmSync4 } from "fs";
|
|
5153
|
+
import { join as join6 } from "path";
|
|
4047
5154
|
import { defineCommand as defineCommand24 } from "citty";
|
|
4048
5155
|
function makeLineReader() {
|
|
4049
5156
|
let buffer = "";
|
|
@@ -4054,8 +5161,8 @@ function makeLineReader() {
|
|
|
4054
5161
|
const nlIdx = buffer.indexOf("\n");
|
|
4055
5162
|
if (nlIdx === -1) {
|
|
4056
5163
|
if (ended) {
|
|
4057
|
-
const
|
|
4058
|
-
|
|
5164
|
+
const resolve4 = queue.shift();
|
|
5165
|
+
resolve4?.(buffer);
|
|
4059
5166
|
buffer = "";
|
|
4060
5167
|
} else {
|
|
4061
5168
|
return;
|
|
@@ -4063,8 +5170,8 @@ function makeLineReader() {
|
|
|
4063
5170
|
} else {
|
|
4064
5171
|
const line = buffer.slice(0, nlIdx);
|
|
4065
5172
|
buffer = buffer.slice(nlIdx + 1);
|
|
4066
|
-
const
|
|
4067
|
-
|
|
5173
|
+
const resolve4 = queue.shift();
|
|
5174
|
+
resolve4?.(line);
|
|
4068
5175
|
}
|
|
4069
5176
|
}
|
|
4070
5177
|
};
|
|
@@ -4077,8 +5184,8 @@ function makeLineReader() {
|
|
|
4077
5184
|
ended = true;
|
|
4078
5185
|
tryDeliver();
|
|
4079
5186
|
});
|
|
4080
|
-
return () => new Promise((
|
|
4081
|
-
queue.push(
|
|
5187
|
+
return () => new Promise((resolve4) => {
|
|
5188
|
+
queue.push(resolve4);
|
|
4082
5189
|
tryDeliver();
|
|
4083
5190
|
});
|
|
4084
5191
|
}
|
|
@@ -4089,7 +5196,7 @@ async function askYes(readLine, prompt) {
|
|
|
4089
5196
|
}
|
|
4090
5197
|
function removePath(p) {
|
|
4091
5198
|
if (!existsSync7(p)) return false;
|
|
4092
|
-
|
|
5199
|
+
rmSync4(p, { recursive: true, force: true });
|
|
4093
5200
|
return true;
|
|
4094
5201
|
}
|
|
4095
5202
|
var uninstallCommand = defineCommand24({
|
|
@@ -4110,12 +5217,12 @@ var uninstallCommand = defineCommand24({
|
|
|
4110
5217
|
},
|
|
4111
5218
|
async run({ args }) {
|
|
4112
5219
|
const paths = resolveCliPaths(args.home);
|
|
4113
|
-
const dbFile =
|
|
4114
|
-
const profilesDir =
|
|
4115
|
-
const agentsDir =
|
|
4116
|
-
const teamsDir =
|
|
4117
|
-
const skillsDir =
|
|
4118
|
-
const logsDir =
|
|
5220
|
+
const dbFile = join6(paths.home, "bazilion.db");
|
|
5221
|
+
const profilesDir = join6(paths.home, "profiles");
|
|
5222
|
+
const agentsDir = join6(paths.home, "agents");
|
|
5223
|
+
const teamsDir = join6(paths.home, "teams");
|
|
5224
|
+
const skillsDir = join6(paths.home, "skills");
|
|
5225
|
+
const logsDir = join6(paths.home, "logs");
|
|
4119
5226
|
if (!existsSync7(paths.home)) {
|
|
4120
5227
|
console.log(`nothing to remove: ${paths.home} does not exist`);
|
|
4121
5228
|
return;
|
|
@@ -4148,7 +5255,7 @@ var uninstallCommand = defineCommand24({
|
|
|
4148
5255
|
if (removePath(p)) console.log(`removed ${p}`);
|
|
4149
5256
|
}
|
|
4150
5257
|
if (readdirSync2(paths.home).length === 0) {
|
|
4151
|
-
|
|
5258
|
+
rmSync4(paths.home, { recursive: true, force: true });
|
|
4152
5259
|
console.log(`removed ${paths.home}`);
|
|
4153
5260
|
} else {
|
|
4154
5261
|
console.log(`${paths.home} still has unmanaged files; left in place`);
|
|
@@ -4275,7 +5382,7 @@ function printTopLevelHelp() {
|
|
|
4275
5382
|
["memory", "Read/write a team's shared memory"],
|
|
4276
5383
|
["send", "Send a message from one agent to another"],
|
|
4277
5384
|
["inbox", "Inspect agent inboxes"],
|
|
4278
|
-
["trigger", "Manage agent
|
|
5385
|
+
["trigger", "Manage scheduled agent triggers"]
|
|
4279
5386
|
]
|
|
4280
5387
|
},
|
|
4281
5388
|
{
|