bazilion 0.10.0 → 0.12.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.
Files changed (80) hide show
  1. package/dist/cli.js +1666 -266
  2. package/dist/cli.js.map +1 -1
  3. package/dist/daemon.js +4303 -522
  4. package/dist/daemon.js.map +1 -1
  5. package/dist/migrations/0001_init.sql +101 -0
  6. package/dist/web/client/assets/AgentTabs-R6gSjIj3.js +1 -0
  7. package/dist/web/client/assets/{ProductionTeamPolicyEditor-CiQ-VVDo.js → ProductionTeamPolicyEditor-CSL3W7XM.js} +2 -2
  8. package/dist/web/client/assets/{ProfileCommunicationEditor-D84F5yfr.js → ProfileCommunicationEditor-CwU0QkmE.js} +1 -1
  9. package/dist/web/client/assets/{TeamTabs-Btoe_FAE.js → TeamTabs-C1Hakkff.js} +1 -1
  10. package/dist/web/client/assets/{TemplatesTabs-CAYY03F5.js → TemplatesTabs-klEydJ8-.js} +1 -1
  11. package/dist/web/client/assets/{_id-BqU2BodK.js → _id-BOC7ioJu.js} +1 -1
  12. package/dist/web/client/assets/{_id-CKtoio3t.js → _id-CnVq-rED.js} +1 -1
  13. package/dist/web/client/assets/{_id-BeauECmk.js → _id-e4u7dmBb.js} +1 -1
  14. package/dist/web/client/assets/{_id-Q98Of6k9.js → _id-wkjjWLz0.js} +1 -1
  15. package/dist/web/client/assets/{activity-DXN_N85w.js → activity-uqhwzmiy.js} +1 -1
  16. package/dist/web/client/assets/{agents-C5Xsdge1.js → agents-B2usE-L_.js} +1 -1
  17. package/dist/web/client/assets/{agents-Cgxo4H5G.js → agents-FaQtkAWs.js} +1 -1
  18. package/dist/web/client/assets/{approvals-BnG7fbw-.js → approvals-DqLE6ttw.js} +1 -1
  19. package/dist/web/client/assets/attention-CkGozYnJ.js +1 -0
  20. package/dist/web/client/assets/attention-Gavt2yEp.js +1 -0
  21. package/dist/web/client/assets/chevron-down-CJk0zGNT.js +1 -0
  22. package/dist/web/client/assets/{config-xw4NyrXF.js → config-xrXoDEWM.js} +1 -1
  23. package/dist/web/client/assets/{context-D2V1Oi4z.js → context-ZWqx3uiC.js} +1 -1
  24. package/dist/web/client/assets/inbox-BoIvl9ZF.js +1 -0
  25. package/dist/web/client/assets/{index-VdURSoM4.js → index-nb2MV8X1.js} +3 -3
  26. package/dist/web/client/assets/learning-6_oYn1l7.js +1 -0
  27. package/dist/web/client/assets/{login-CvptUoMR.js → login-DM1ytxQL.js} +1 -1
  28. package/dist/web/client/assets/{mcp-BDFM_sW-.js → mcp-CetZbyUh.js} +1 -1
  29. package/dist/web/client/assets/{members-BJvph_TR.js → members-DdqWtwZd.js} +1 -1
  30. package/dist/web/client/assets/{memory-DRytqZCD.js → memory-FNrxFHmC.js} +1 -1
  31. package/dist/web/client/assets/plus-C3GroITC.js +1 -0
  32. package/dist/web/client/assets/{policy-CGP50OtQ.js → policy-BKPejYRI.js} +1 -1
  33. package/dist/web/client/assets/{routes-DJnWYvC9.js → routes-CH1ZTTo-.js} +1 -1
  34. package/dist/web/client/assets/{services-DHheiv7L.js → services-zk9SqN06.js} +1 -1
  35. package/dist/web/client/assets/{skills-DmsitluU.js → skills-tXDpomHW.js} +1 -1
  36. package/dist/web/client/assets/styles-DMltY3su.css +2 -0
  37. package/dist/web/client/assets/{teams-BdC4gthq.js → teams-BZYjekBN.js} +1 -1
  38. package/dist/web/client/assets/{teams-CbxSaorm.js → teams-D1c8zlxs.js} +1 -1
  39. package/dist/web/client/assets/telegram-DCe1CWMt.js +1 -0
  40. package/dist/web/client/assets/{tokens-CjRplDrJ.js → tokens-DAPjRS0N.js} +1 -1
  41. package/dist/web/client/assets/triggers-D8R-5guA.js +1 -0
  42. package/dist/web/client/assets/{welcome-wBfX9dAd.js → welcome-CuK0shDJ.js} +1 -1
  43. package/dist/web/client/assets/wire-constants-BhVKOCQa.js +74 -0
  44. package/dist/web/server/assets/{AgentTabs-DoMDJBXH.js → AgentTabs-aXdsiiMp.js} +5 -0
  45. package/dist/web/server/assets/{ChatPane-eUY_dga3.js → ChatPane-Ba5wTsVk.js} +325 -36
  46. package/dist/web/server/assets/{_id-ClN9hY2x.js → _id-BHYLL8M-.js} +3 -3
  47. package/dist/web/server/assets/{_id-DkYodSkG.js → _id-FcHdSocl.js} +1 -1
  48. package/dist/web/server/assets/{_tanstack-start-manifest_v-D09Xu_6u.js → _tanstack-start-manifest_v-fLsYERQ-.js} +77 -54
  49. package/dist/web/server/assets/attention-BW_6ddkj.js +11 -0
  50. package/dist/web/server/assets/attention-BxkTf337.js +14 -0
  51. package/dist/web/server/assets/attention-CM-Ks1N9.js +19 -0
  52. package/dist/web/server/assets/attention-w21GQIGH.js +264 -0
  53. package/dist/web/server/assets/{inbox-CUIyf9MT.js → inbox-BLUzA5sQ.js} +40 -3
  54. package/dist/web/server/assets/{inbox-B3-TRsNI.js → inbox-CzlKT9Wg.js} +5 -3
  55. package/dist/web/server/assets/{inbox-BardsGyy.js → inbox-ZvDNAjaM.js} +1 -1
  56. package/dist/web/server/assets/learning-8zav4pIT.js +33 -0
  57. package/dist/web/server/assets/learning-B7PBkvER.js +16 -0
  58. package/dist/web/server/assets/learning-Cq7K2heb.js +312 -0
  59. package/dist/web/server/assets/{router-DIdp8ZNt.js → router-CQuj7553.js} +81 -48
  60. package/dist/web/server/assets/{routes-Jb0zjKav.js → routes-B6cMLIPk.js} +3 -3
  61. package/dist/web/server/assets/{routes-CXz4hTAr.js → routes-BrA-U_Hm.js} +1 -1
  62. package/dist/web/server/assets/{routes-lIclH9Br.js → routes-C19CMLZt.js} +1 -1
  63. package/dist/web/server/assets/{telegram-7ub--6xz.js → telegram-CBF8SJzB.js} +1 -24
  64. package/dist/web/server/assets/{telegram-Bazhmpr4.js → telegram-XwyKSEtj.js} +1 -1
  65. package/dist/web/server/assets/{triggers-Ca57_qym.js → triggers-BZPl5CHs.js} +1 -1
  66. package/dist/web/server/assets/{triggers-BMpHsGIr.js → triggers-CSNNzRIi.js} +28 -4
  67. package/dist/web/server/assets/{triggers-C_0y6QV9.js → triggers-D79NoSXk.js} +3 -2
  68. package/dist/web/server/server.js +13 -5
  69. package/dist/worker.js +1752 -148
  70. package/dist/worker.js.map +1 -1
  71. package/package.json +4 -3
  72. package/dist/web/client/assets/AgentTabs-B3hq5a3e.js +0 -1
  73. package/dist/web/client/assets/chevron-down-BhcBspw3.js +0 -1
  74. package/dist/web/client/assets/inbox-DK6hWCMp.js +0 -1
  75. package/dist/web/client/assets/plus-C9ee3SIO.js +0 -1
  76. package/dist/web/client/assets/styles-B5eQAwLP.css +0 -2
  77. package/dist/web/client/assets/telegram-D61G_alO.js +0 -1
  78. package/dist/web/client/assets/triggers-MLzTm_DM.js +0 -1
  79. package/dist/web/client/assets/wire-constants-BxfyUMXB.js +0 -74
  80. package/dist/web/server/assets/{Sidebar-O_CH1-JD.js → Sidebar-DsRVCszF.js} +1 -1
package/dist/cli.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { defineCommand as defineCommand25, runCommand, showUsage } from "citty";
4
+ import { defineCommand as defineCommand26, runCommand, showUsage } from "citty";
5
5
 
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "bazilion",
9
- version: "0.10.0",
9
+ version: "0.12.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: {
@@ -68,6 +68,7 @@ var package_default = {
68
68
  "qrcode-terminal": "^0.12.0",
69
69
  react: "^19.2.7",
70
70
  "react-dom": "^19.2.7",
71
+ tar: "^7.5.16",
71
72
  typebox: "^1.3.0",
72
73
  undici: "^8.5.0",
73
74
  yaml: "^2.9.0"
@@ -372,12 +373,12 @@ var listCmd = defineCommand({
372
373
  async run({ args }) {
373
374
  const client = createClient2();
374
375
  const q = args.all ? "?includeArchived=true" : "";
375
- const list3 = await client.get(`/api/agents${q}`);
376
- if (list3.length === 0) {
376
+ const list5 = await client.get(`/api/agents${q}`);
377
+ if (list5.length === 0) {
377
378
  console.log("(no agents)");
378
379
  return;
379
380
  }
380
- const rows = list3.map(
381
+ const rows = list5.map(
381
382
  (a) => args.long ? [a.id, a.status, a.name, a.profileId] : (
382
383
  // Short UUID prefix — all CLI commands accept 4+ char prefixes now.
383
384
  [a.id.slice(0, 8), a.status, a.name]
@@ -448,6 +449,74 @@ var deleteCmd = defineCommand({
448
449
  console.log(`deleted agent ${args.id}`);
449
450
  }
450
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
+ }
451
520
  function printEvent(e, state) {
452
521
  switch (e.type) {
453
522
  case "assistant_delta":
@@ -494,16 +563,32 @@ function printEvent(e, state) {
494
563
  break;
495
564
  }
496
565
  }
497
- async function streamTurn(client, agentId, message, attachments) {
566
+ async function streamTurn(client, agentId, message, attachments, options = { bashApprovalMode: "auto_deny" }) {
498
567
  const state = { inDeltaStream: false };
499
- for await (const frame of client.stream("POST", `/api/agents/${agentId}/chat`, {
500
- message,
501
- ...attachments && attachments.length > 0 ? { attachments } : {}
502
- })) {
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
+ )) {
503
574
  if (frame.kind === "event") {
504
- if (frame.event.type !== "user_message") printEvent(frame.event, state);
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
+ }
505
590
  } else if (frame.kind === "fatal") {
506
- if (state.inDeltaStream) process.stdout.write("\n");
591
+ closeDeltaLine(state);
507
592
  throw new Error(frame.error);
508
593
  }
509
594
  }
@@ -528,34 +613,78 @@ var chatCmd = defineCommand({
528
613
  async run({ args }) {
529
614
  const client = createClient2();
530
615
  const resolved = await client.get(`/api/agents/${args.id}`);
616
+ const bashApprovalMode = bashApprovalModeForTty(stdin.isTTY, stdout.isTTY);
531
617
  const attachments = [
532
618
  ...loadImages(asPaths(args.image)),
533
619
  ...loadFiles(asPaths(args.file))
534
620
  ];
535
621
  if (args.message || attachments.length > 0) {
536
- await streamTurn(client, resolved.agent.id, args.message ?? "", attachments);
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
+ }
537
636
  return;
538
637
  }
539
638
  console.log(`chatting with ${resolved.agent.name} (${resolved.model})`);
540
639
  console.log("(type /exit to quit)");
541
640
  const rl = createInterface({ input: stdin, output: stdout });
542
- rl.setPrompt("> ");
543
- rl.prompt();
544
- for await (const line of rl) {
545
- const trimmed = line.trim();
546
- if (trimmed === "/exit" || trimmed === "/quit") break;
547
- if (!trimmed) {
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("> ");
548
667
  rl.prompt();
549
- continue;
550
- }
551
- try {
552
- await streamTurn(client, resolved.agent.id, trimmed);
553
- } catch (err) {
554
- console.error(`error: ${err.message}`);
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
+ }
555
684
  }
556
- rl.prompt();
685
+ } finally {
686
+ rl.close();
557
687
  }
558
- rl.close();
559
688
  }
560
689
  });
561
690
  var chatResetCmd = defineCommand({
@@ -567,8 +696,8 @@ var chatResetCmd = defineCommand({
567
696
  async run({ args }) {
568
697
  if (!args.force) {
569
698
  process.stdout.write(`reset all chat history for ${args.id}? [y/N] `);
570
- const answer = await new Promise((resolve3) => {
571
- process.stdin.once("data", (d) => resolve3(String(d).trim().toLowerCase()));
699
+ const answer = await new Promise((resolve4) => {
700
+ process.stdin.once("data", (d) => resolve4(String(d).trim().toLowerCase()));
572
701
  });
573
702
  if (answer !== "y" && answer !== "yes") {
574
703
  console.log("aborted");
@@ -716,8 +845,8 @@ var chatCompactCmd = defineCommand({
716
845
  process.stdout.write(
717
846
  `compact chat history for ${args.id}? head turns become a summary; tail is preserved. [y/N] `
718
847
  );
719
- const answer = await new Promise((resolve3) => {
720
- process.stdin.once("data", (d) => resolve3(String(d).trim().toLowerCase()));
848
+ const answer = await new Promise((resolve4) => {
849
+ process.stdin.once("data", (d) => resolve4(String(d).trim().toLowerCase()));
721
850
  });
722
851
  if (answer !== "y" && answer !== "yes") {
723
852
  console.log("aborted");
@@ -856,6 +985,159 @@ var cancelCmd = defineCommand({
856
985
  console.log(`cancelled active turn for ${args.id}`);
857
986
  }
858
987
  });
988
+ var reviewConfigCmd = defineCommand({
989
+ meta: { name: "review-config", description: "Inspect or configure reviewed learning" },
990
+ args: {
991
+ id: { type: "positional", required: true, description: "Agent id or prefix" },
992
+ enable: { type: "boolean", description: "Enable periodic reviews" },
993
+ disable: { type: "boolean", description: "Disable periodic reviews" },
994
+ every: { type: "string", description: "Successful user turns between reviews (1-100)" },
995
+ model: { type: "string", description: "Review model, or 'agent' to use the Agent model" },
996
+ reasoning: { type: "string", description: "Review reasoning level" }
997
+ },
998
+ async run({ args }) {
999
+ if (args.enable && args.disable) throw new Error("choose either --enable or --disable");
1000
+ const client = createClient2();
1001
+ const body = {};
1002
+ if (args.enable || args.disable) body.enabled = Boolean(args.enable);
1003
+ if (args.every !== void 0) {
1004
+ const value = Number(args.every);
1005
+ if (!Number.isInteger(value) || value < 1 || value > 100) {
1006
+ throw new Error("--every must be an integer from 1 to 100");
1007
+ }
1008
+ body.everyNTurns = value;
1009
+ }
1010
+ if (args.model !== void 0) body.model = args.model === "agent" ? null : args.model;
1011
+ if (args.reasoning !== void 0) {
1012
+ body.reasoningLevel = args.reasoning;
1013
+ }
1014
+ const hasChanges = Object.keys(body).length > 0;
1015
+ const config = hasChanges ? await client.patch(`/api/agents/${args.id}/review-config`, body) : await client.get(`/api/agents/${args.id}/review-config`);
1016
+ console.log(`review: ${config.enabled ? "enabled" : "disabled"}`);
1017
+ console.log(`cadence: every ${config.everyNTurns} successful user turns`);
1018
+ console.log(`model: ${config.model ?? "agent model"}`);
1019
+ console.log(`reasoning: ${config.reasoningLevel}`);
1020
+ console.log(`turns since last review: ${config.turnsSinceLast}`);
1021
+ }
1022
+ });
1023
+ var reviewCmd = defineCommand({
1024
+ meta: { name: "review", description: "Enqueue a reviewed-learning pass" },
1025
+ args: { id: { type: "positional", required: true, description: "Agent id or prefix" } },
1026
+ async run({ args }) {
1027
+ const response = await createClient2().post(
1028
+ `/api/agents/${args.id}/reviews`
1029
+ );
1030
+ console.log(`review ${response.review.id} ${response.review.status}`);
1031
+ }
1032
+ });
1033
+ var reviewsCmd = defineCommand({
1034
+ meta: { name: "reviews", description: "List reviewed-learning history" },
1035
+ args: {
1036
+ id: { type: "positional", required: true, description: "Agent id or prefix" },
1037
+ status: { type: "string", description: "pending|running|retrying|completed|failed|cancelled" }
1038
+ },
1039
+ async run({ args }) {
1040
+ const response = await createClient2().get(
1041
+ `/api/agents/${args.id}/reviews`
1042
+ );
1043
+ const reviews = args.status ? response.reviews.filter((review) => review.status === args.status) : response.reviews;
1044
+ if (reviews.length === 0) return console.log("(no reviews)");
1045
+ for (const line of columnize(
1046
+ reviews.map((review) => [
1047
+ review.id,
1048
+ review.status,
1049
+ review.trigger,
1050
+ `${review.proposalCount} proposal(s)`,
1051
+ new Date(review.createdAt).toISOString()
1052
+ ])
1053
+ )) {
1054
+ console.log(line);
1055
+ }
1056
+ }
1057
+ });
1058
+ var lessonsCmd = defineCommand({
1059
+ meta: { name: "lessons", description: "List reviewed lesson proposals" },
1060
+ args: {
1061
+ id: { type: "positional", required: true, description: "Agent id or prefix" },
1062
+ status: { type: "string", description: "pending|approved|rejected|revoked" }
1063
+ },
1064
+ async run({ args }) {
1065
+ const status = args.status;
1066
+ const query = status ? `?status=${encodeURIComponent(status)}` : "";
1067
+ const response = await createClient2().get(
1068
+ `/api/agents/${args.id}/lesson-proposals${query}`
1069
+ );
1070
+ if (response.proposals.length === 0) return console.log("(no lesson proposals)");
1071
+ for (const line of columnize(
1072
+ response.proposals.map((proposal) => [
1073
+ proposal.id,
1074
+ proposal.status,
1075
+ proposal.scope,
1076
+ proposal.text
1077
+ ])
1078
+ )) {
1079
+ console.log(line);
1080
+ }
1081
+ }
1082
+ });
1083
+ async function locateProposal(id) {
1084
+ const client = createClient2();
1085
+ const agents = await client.get("/api/agents?includeArchived=true");
1086
+ for (const agent of agents) {
1087
+ const response = await client.get(
1088
+ `/api/agents/${agent.id}/lesson-proposals`
1089
+ );
1090
+ const proposal = response.proposals.find((item) => item.id === id || item.id.startsWith(id));
1091
+ if (proposal) return { agent, proposal };
1092
+ }
1093
+ throw new Error(`lesson proposal not found: ${id}`);
1094
+ }
1095
+ function lessonDecisionCmd(decision2) {
1096
+ return defineCommand({
1097
+ meta: { name: decision2, description: `${decision2} a reviewed lesson proposal` },
1098
+ args: {
1099
+ id: { type: "positional", required: true, description: "Proposal id or prefix" },
1100
+ yes: { type: "boolean", description: `Confirm ${decision2}` }
1101
+ },
1102
+ async run({ args }) {
1103
+ if (!args.yes) throw new Error(`${decision2} requires --yes`);
1104
+ const { agent, proposal } = await locateProposal(args.id);
1105
+ const response = await createClient2().post(
1106
+ `/api/agents/${agent.id}/lesson-proposals/${proposal.id}/${decision2}`,
1107
+ { version: proposal.version }
1108
+ );
1109
+ console.log(`${response.proposal.status} ${response.proposal.id}`);
1110
+ }
1111
+ });
1112
+ }
1113
+ var lessonEditCmd = defineCommand({
1114
+ meta: { name: "edit", description: "Edit a pending reviewed lesson proposal" },
1115
+ args: {
1116
+ id: { type: "positional", required: true, description: "Proposal id or prefix" },
1117
+ text: { type: "string", description: "Replacement lesson text" },
1118
+ scope: { type: "string", description: "private|shared" }
1119
+ },
1120
+ async run({ args }) {
1121
+ if (args.text === void 0 && args.scope === void 0) {
1122
+ throw new Error("lesson edit requires --text or --scope");
1123
+ }
1124
+ const { agent, proposal } = await locateProposal(args.id);
1125
+ const response = await createClient2().patch(
1126
+ `/api/agents/${agent.id}/lesson-proposals/${proposal.id}`,
1127
+ { version: proposal.version, text: args.text, scope: args.scope }
1128
+ );
1129
+ console.log(`updated ${response.proposal.id} v${response.proposal.version}`);
1130
+ }
1131
+ });
1132
+ var lessonCmd = defineCommand({
1133
+ meta: { name: "lesson", description: "Edit or decide a reviewed lesson proposal" },
1134
+ subCommands: {
1135
+ edit: lessonEditCmd,
1136
+ approve: lessonDecisionCmd("approve"),
1137
+ reject: lessonDecisionCmd("reject"),
1138
+ revoke: lessonDecisionCmd("revoke")
1139
+ }
1140
+ });
859
1141
  var agentCommand = defineCommand({
860
1142
  meta: { name: "agent", description: "Manage agent instances" },
861
1143
  subCommands: {
@@ -872,6 +1154,11 @@ var agentCommand = defineCommand({
872
1154
  "chat-context": chatContextCmd,
873
1155
  "chat-compact": chatCompactCmd,
874
1156
  cancel: cancelCmd,
1157
+ "review-config": reviewConfigCmd,
1158
+ review: reviewCmd,
1159
+ reviews: reviewsCmd,
1160
+ lessons: lessonsCmd,
1161
+ lesson: lessonCmd,
875
1162
  skill: skillCmd,
876
1163
  move: moveCmd,
877
1164
  "session-head": sessionHeadCmd
@@ -965,15 +1252,101 @@ var approvalCommand = defineCommand2({
965
1252
  }
966
1253
  });
967
1254
 
1255
+ // src/commands/attention.ts
1256
+ import { defineCommand as defineCommand3 } from "citty";
1257
+ var list2 = defineCommand3({
1258
+ meta: { name: "list", description: "List operator attention items" },
1259
+ args: {
1260
+ state: { type: "string", default: "open" },
1261
+ kind: { type: "string" },
1262
+ json: { type: "boolean" }
1263
+ },
1264
+ async run({ args }) {
1265
+ const query = new URLSearchParams({ state: args.state, limit: "200" });
1266
+ if (args.kind) query.set("kind", args.kind);
1267
+ const body = await createClient2().get(`/api/attention?${query}`);
1268
+ if (args.json) return console.log(JSON.stringify(body, null, 2));
1269
+ if (!body.items.length) console.log("(nothing needs attention)");
1270
+ else
1271
+ for (const line of columnize([
1272
+ ["severity", "kind", "context", "when", "key"],
1273
+ ...body.items.map((item) => [
1274
+ item.severity,
1275
+ item.kind,
1276
+ context(item),
1277
+ new Date(item.occurredAt).toISOString(),
1278
+ item.key
1279
+ ])
1280
+ ]))
1281
+ console.log(line);
1282
+ for (const source of body.degraded)
1283
+ console.error(`warning: ${source.kind} unavailable: ${source.error}`);
1284
+ }
1285
+ });
1286
+ var summary = defineCommand3({
1287
+ meta: { name: "summary", description: "Summarize open attention items" },
1288
+ args: { json: { type: "boolean" } },
1289
+ async run({ args }) {
1290
+ const body = await createClient2().get("/api/attention/summary");
1291
+ if (args.json) console.log(JSON.stringify(body, null, 2));
1292
+ else
1293
+ console.log(
1294
+ `${body.openTotal} open \xB7 ${body.bySeverity.action_required} action required \xB7 ${body.bySeverity.error} errors \xB7 ${body.bySeverity.warning} warnings`
1295
+ );
1296
+ }
1297
+ });
1298
+ function mutation(name) {
1299
+ return defineCommand3({
1300
+ meta: { name, description: `${name} one informational attention item` },
1301
+ args: {
1302
+ key: { type: "positional", required: true },
1303
+ yes: { type: "boolean", description: "Required confirmation" },
1304
+ json: { type: "boolean" }
1305
+ },
1306
+ async run({ args }) {
1307
+ if (!args.yes) throw new Error(`refusing mutation: pass --yes to ${name}`);
1308
+ const path = `/api/attention/${encodeURIComponent(args.key)}/${name === "acknowledge" ? "acknowledge" : "acknowledgement"}`;
1309
+ const body = name === "acknowledge" ? await createClient2().post(path) : await createClient2().del(path);
1310
+ if (args.json) console.log(JSON.stringify(body, null, 2));
1311
+ else console.log(`${body.item.key}: ${body.item.acknowledgedAt ? "acknowledged" : "open"}`);
1312
+ }
1313
+ });
1314
+ }
1315
+ var acknowledgeAll = defineCommand3({
1316
+ meta: { name: "acknowledge-all", description: "Acknowledge all open informational items" },
1317
+ args: { yes: { type: "boolean" }, json: { type: "boolean" } },
1318
+ async run({ args }) {
1319
+ if (!args.yes) throw new Error("refusing mutation: pass --yes to acknowledge-all");
1320
+ const body = await createClient2().post(
1321
+ "/api/attention/acknowledge-all"
1322
+ );
1323
+ if (args.json) console.log(JSON.stringify(body, null, 2));
1324
+ else console.log(`acknowledged ${body.acknowledged} informational item(s)`);
1325
+ }
1326
+ });
1327
+ function context(item) {
1328
+ return [item.agentName, item.teamName].filter(Boolean).join(" / ") || "system";
1329
+ }
1330
+ var attentionCommand = defineCommand3({
1331
+ meta: { name: "attention", description: "Inspect the operator attention center" },
1332
+ subCommands: {
1333
+ list: list2,
1334
+ summary,
1335
+ acknowledge: mutation("acknowledge"),
1336
+ unacknowledge: mutation("unacknowledge"),
1337
+ "acknowledge-all": acknowledgeAll
1338
+ }
1339
+ });
1340
+
968
1341
  // src/commands/auth.ts
969
1342
  import { loginOpenAICodex } from "@earendil-works/pi-ai/oauth";
970
- import { defineCommand as defineCommand3 } from "citty";
1343
+ import { defineCommand as defineCommand4 } from "citty";
971
1344
  function formatExpiry(ms) {
972
1345
  if (!ms) return "(unknown)";
973
1346
  const d = new Date(ms);
974
1347
  return d.toISOString().replace(/\.\d+Z$/, "Z");
975
1348
  }
976
- var openaiLoginCmd = defineCommand3({
1349
+ var openaiLoginCmd = defineCommand4({
977
1350
  meta: {
978
1351
  name: "login",
979
1352
  description: "Connect your ChatGPT account via OAuth (browser sign-in)"
@@ -990,8 +1363,8 @@ var openaiLoginCmd = defineCommand3({
990
1363
  },
991
1364
  onPrompt: async (prompt) => {
992
1365
  process.stdout.write(`${prompt.message}: `);
993
- return new Promise((resolve3) => {
994
- process.stdin.once("data", (d) => resolve3(String(d).trim()));
1366
+ return new Promise((resolve4) => {
1367
+ process.stdin.once("data", (d) => resolve4(String(d).trim()));
995
1368
  });
996
1369
  },
997
1370
  onProgress: (msg) => {
@@ -1015,7 +1388,7 @@ var openaiLoginCmd = defineCommand3({
1015
1388
  console.log(" bazilion provider models-set openai-codex gpt-5.3-codex-spark");
1016
1389
  }
1017
1390
  });
1018
- var openaiLogoutCmd = defineCommand3({
1391
+ var openaiLogoutCmd = defineCommand4({
1019
1392
  meta: {
1020
1393
  name: "logout",
1021
1394
  description: "Forget the stored ChatGPT OAuth credentials"
@@ -1026,7 +1399,7 @@ var openaiLogoutCmd = defineCommand3({
1026
1399
  console.log("disconnected ChatGPT OAuth credentials");
1027
1400
  }
1028
1401
  });
1029
- var openaiStatusCmd = defineCommand3({
1402
+ var openaiStatusCmd = defineCommand4({
1030
1403
  meta: {
1031
1404
  name: "status",
1032
1405
  description: "Show ChatGPT OAuth connection status"
@@ -1043,7 +1416,7 @@ var openaiStatusCmd = defineCommand3({
1043
1416
  console.log(`access token expires: ${formatExpiry(status.expiresAt)}`);
1044
1417
  }
1045
1418
  });
1046
- var openaiCmd = defineCommand3({
1419
+ var openaiCmd = defineCommand4({
1047
1420
  meta: {
1048
1421
  name: "openai",
1049
1422
  description: "Manage ChatGPT (OAuth) authentication"
@@ -1054,7 +1427,7 @@ var openaiCmd = defineCommand3({
1054
1427
  status: openaiStatusCmd
1055
1428
  }
1056
1429
  });
1057
- var authCommand = defineCommand3({
1430
+ var authCommand = defineCommand4({
1058
1431
  meta: {
1059
1432
  name: "auth",
1060
1433
  description: "OAuth-based provider authentication (ChatGPT account, etc.)"
@@ -1065,13 +1438,448 @@ var authCommand = defineCommand3({
1065
1438
  });
1066
1439
 
1067
1440
  // src/commands/backup.ts
1068
- import { spawn } from "child_process";
1069
- import { createWriteStream, existsSync as existsSync2, mkdirSync, readdirSync, rmSync } from "fs";
1070
- import { resolve } from "path";
1441
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
1442
+ import {
1443
+ chmodSync as chmodSync2,
1444
+ closeSync as closeSync2,
1445
+ copyFileSync,
1446
+ createWriteStream,
1447
+ existsSync as existsSync2,
1448
+ fsyncSync as fsyncSync2,
1449
+ lstatSync as lstatSync2,
1450
+ mkdirSync as mkdirSync2,
1451
+ mkdtempSync,
1452
+ openSync as openSync2,
1453
+ readdirSync,
1454
+ readFileSync as readFileSync4,
1455
+ renameSync as renameSync2,
1456
+ rmSync as rmSync2
1457
+ } from "fs";
1458
+ import { tmpdir } from "os";
1459
+ import { basename as basename3, dirname as dirname2, isAbsolute, parse, posix, relative, resolve as resolve2 } from "path";
1460
+ import { DatabaseSync } from "node:sqlite";
1071
1461
  import { Readable } from "stream";
1072
1462
  import { pipeline } from "stream/promises";
1073
- import { defineCommand as defineCommand4 } from "citty";
1074
- var createCmd = defineCommand4({
1463
+ import { defineCommand as defineCommand5 } from "citty";
1464
+ import { extract, list as list3 } from "tar";
1465
+
1466
+ // src/backup-schema.ts
1467
+ import { createHash } from "crypto";
1468
+ var CANONICAL_MIGRATION = "0001_init";
1469
+ var CANONICAL_SCHEMA_HASH = "7c0867f97bd6a7d9c8a89f43f6ae7e5259c3976d40cd81a2061c43c30b7590a1";
1470
+ var CANONICAL_OBJECTS = [
1471
+ ["index", "agent_triggers_agent"],
1472
+ ["index", "agent_triggers_enabled"],
1473
+ ["index", "agent_loop_break_events_agent_time"],
1474
+ ["index", "agent_loop_break_events_team_time"],
1475
+ ["index", "agent_lesson_proposals_agent_status_time"],
1476
+ ["index", "agent_lesson_proposals_review"],
1477
+ ["index", "agent_reviews_agent_time"],
1478
+ ["index", "agent_reviews_claimable"],
1479
+ ["index", "agent_reviews_one_open_per_agent"],
1480
+ ["index", "communication_approval_events_attempt"],
1481
+ ["index", "communication_approvals_queue"],
1482
+ ["index", "communication_approvals_teams"],
1483
+ ["index", "idx_agents_telegram_topic_id"],
1484
+ ["index", "idx_provider_models_provider"],
1485
+ ["index", "messages_policy_delivery_queue"],
1486
+ ["index", "messages_causal_chain"],
1487
+ ["index", "messages_to_unread"],
1488
+ ["index", "team_policy_baseline_owner"],
1489
+ ["index", "team_policy_blocks_team_time"],
1490
+ ["index", "team_template_active_position"],
1491
+ ["index", "trigger_dispatches_claimable"],
1492
+ ["index", "trigger_dispatches_trigger_time"],
1493
+ ["index", "web_tokens_active"],
1494
+ ["table", "agent_skills"],
1495
+ ["table", "agent_loop_break_events"],
1496
+ ["table", "agent_lesson_proposals"],
1497
+ ["table", "agent_reviews"],
1498
+ ["table", "agent_triggers"],
1499
+ ["table", "agents"],
1500
+ ["table", "attention_acknowledgements"],
1501
+ ["table", "communication_approval_events"],
1502
+ ["table", "communication_approval_message_grants"],
1503
+ ["table", "communication_approvals"],
1504
+ ["table", "config"],
1505
+ ["table", "mcp_servers"],
1506
+ ["table", "messages"],
1507
+ ["table", "profile_communication_defaults"],
1508
+ ["table", "profile_default_skills"],
1509
+ ["table", "profiles"],
1510
+ ["table", "provider_models"],
1511
+ ["table", "provider_state"],
1512
+ ["table", "schema_migrations"],
1513
+ ["table", "secrets"],
1514
+ ["table", "skill_meta"],
1515
+ ["table", "source_slot_bindings"],
1516
+ ["table", "team_agent_state"],
1517
+ ["table", "team_policies"],
1518
+ ["table", "team_policy_block_events"],
1519
+ ["table", "team_policy_edges"],
1520
+ ["table", "team_template_edges"],
1521
+ ["table", "team_template_revision_edges"],
1522
+ ["table", "team_template_revision_slots"],
1523
+ ["table", "team_template_revisions"],
1524
+ ["table", "team_template_slots"],
1525
+ ["table", "team_templates"],
1526
+ ["table", "teams"],
1527
+ ["table", "telegram_allowed_users"],
1528
+ ["table", "template_instantiations"],
1529
+ ["table", "trigger_dispatches"],
1530
+ ["table", "web_tokens"],
1531
+ ["trigger", "create_team_policy"],
1532
+ ["trigger", "prevent_detached_team_policy_delete"],
1533
+ ["trigger", "validate_source_binding_insert"],
1534
+ ["trigger", "validate_team_agent_state_insert"],
1535
+ ["trigger", "validate_team_policy_baseline_update"],
1536
+ ["trigger", "validate_team_policy_edge_insert"],
1537
+ ["trigger", "validate_team_template_edge_insert"]
1538
+ ];
1539
+ function normalizeSql(sql) {
1540
+ return sql.replace(/\s+/g, " ").trim();
1541
+ }
1542
+ function assertCanonicalBackupSchema(db) {
1543
+ const migrations = db.prepare("SELECT version FROM schema_migrations ORDER BY version").all();
1544
+ if (migrations.length !== 1 || migrations[0]?.version !== CANONICAL_MIGRATION) {
1545
+ throw new Error(
1546
+ `schema_migrations must contain exactly ${CANONICAL_MIGRATION}; found ` + (migrations.map((row) => row.version).join(", ") || "none")
1547
+ );
1548
+ }
1549
+ const rows = db.prepare(
1550
+ `SELECT type, name, tbl_name, sql
1551
+ FROM sqlite_schema
1552
+ WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%'`
1553
+ ).all();
1554
+ const byKey = new Map(rows.map((row) => [`${row.type}\0${row.name}`, row]));
1555
+ const canonicalKeys = new Set(CANONICAL_OBJECTS.map(([type, name]) => `${type}\0${name}`));
1556
+ for (const row of rows) {
1557
+ if (!canonicalKeys.has(`${row.type}\0${row.name}`)) {
1558
+ throw new Error(`unexpected schema ${row.type} is not canonical: ${row.name}`);
1559
+ }
1560
+ }
1561
+ const canonicalRows = [];
1562
+ for (const [type, name] of CANONICAL_OBJECTS) {
1563
+ const row = byKey.get(`${type}\0${name}`);
1564
+ if (!row) throw new Error(`required canonical schema ${type} is missing: ${name}`);
1565
+ canonicalRows.push(row);
1566
+ }
1567
+ const payload = canonicalRows.map((row) => [row.type, row.name, row.tbl_name, normalizeSql(row.sql)].join("\0")).join("\n");
1568
+ const actualHash = createHash("sha256").update(payload).digest("hex");
1569
+ if (actualHash !== CANONICAL_SCHEMA_HASH) {
1570
+ throw new Error(
1571
+ "canonical schema fingerprint does not match this Bazilion release; restore a backup created from the current clean-install schema"
1572
+ );
1573
+ }
1574
+ }
1575
+
1576
+ // src/daemon-liveness.ts
1577
+ import { createHash as createHash2, randomUUID } from "crypto";
1578
+ import {
1579
+ chmodSync,
1580
+ closeSync,
1581
+ fstatSync,
1582
+ fsyncSync,
1583
+ ftruncateSync,
1584
+ futimesSync,
1585
+ lstatSync,
1586
+ mkdirSync,
1587
+ openSync,
1588
+ readFileSync as readFileSync3,
1589
+ realpathSync,
1590
+ renameSync,
1591
+ rmSync,
1592
+ writeSync
1593
+ } from "fs";
1594
+ import { basename as basename2, dirname, join as join2, resolve } from "path";
1595
+ var DAEMON_LIVENESS_FILENAME = "daemon-runtime.json";
1596
+ var STALE_AFTER_MS = 6e4;
1597
+ var PROBE_TIMEOUT_MS = 750;
1598
+ function canonicalHomeIdentity(home) {
1599
+ const absolute = resolve(home);
1600
+ try {
1601
+ return realpathSync.native(absolute);
1602
+ } catch (error) {
1603
+ if (errorCode(error) !== "ENOENT") throw error;
1604
+ const parent = dirname(absolute);
1605
+ if (parent === absolute) throw error;
1606
+ return join2(canonicalHomeIdentity(parent), basename2(absolute));
1607
+ }
1608
+ }
1609
+ function daemonLivenessPath(home) {
1610
+ const canonicalHome = canonicalHomeIdentity(home);
1611
+ const identity = createHash2("sha256").update(canonicalHome).digest("hex").slice(0, 24);
1612
+ return join2(dirname(canonicalHome), `.bazilion-runtime-${identity}.json`);
1613
+ }
1614
+ function errorCode(error) {
1615
+ return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
1616
+ }
1617
+ function parseRecord(raw) {
1618
+ try {
1619
+ const value = JSON.parse(raw);
1620
+ if (value.version !== 1 || typeof value.instanceId !== "string" || !value.instanceId || !Number.isSafeInteger(value.pid) || (value.pid ?? 0) <= 0 || !["daemon", "restore"].includes(value.owner ?? "") || ![
1621
+ "starting",
1622
+ "listening",
1623
+ "restoring",
1624
+ "swapping",
1625
+ "installed",
1626
+ "recovery-required"
1627
+ ].includes(value.state ?? "") || !Number.isFinite(value.startedAt)) {
1628
+ return null;
1629
+ }
1630
+ if (value.owner === "daemon" && !["starting", "listening"].includes(value.state ?? "") || value.owner === "restore" && !["restoring", "swapping", "installed", "recovery-required"].includes(value.state ?? "")) {
1631
+ return null;
1632
+ }
1633
+ if (value.owner === "restore" && ["swapping", "recovery-required"].includes(value.state ?? "") && (typeof value.recoveryPath !== "string" || !value.recoveryPath || typeof value.hadPrevious !== "boolean")) {
1634
+ return null;
1635
+ }
1636
+ 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)) {
1637
+ return null;
1638
+ }
1639
+ return value;
1640
+ } catch {
1641
+ return null;
1642
+ }
1643
+ }
1644
+ function readRecord(path) {
1645
+ try {
1646
+ const stat = lstatSync(path);
1647
+ const raw = stat.isFile() ? readFileSync3(path, "utf8") : "";
1648
+ return { raw, stat, record: stat.isFile() ? parseRecord(raw) : null };
1649
+ } catch (error) {
1650
+ if (errorCode(error) === "ENOENT") return null;
1651
+ throw error;
1652
+ }
1653
+ }
1654
+ function pidIsAlive(pid) {
1655
+ try {
1656
+ process.kill(pid, 0);
1657
+ return true;
1658
+ } catch (error) {
1659
+ return errorCode(error) !== "ESRCH";
1660
+ }
1661
+ }
1662
+ function endpointFor(record) {
1663
+ if (record.state !== "listening" || !record.host || !record.port) return null;
1664
+ const normalized = record.host.toLowerCase();
1665
+ if (normalized === "0.0.0.0") return `http://127.0.0.1:${record.port}`;
1666
+ if (normalized === "::" || normalized === "0:0:0:0:0:0:0:0") {
1667
+ return `http://[::1]:${record.port}`;
1668
+ }
1669
+ if (normalized === "localhost" || normalized === "127.0.0.1") {
1670
+ return `http://${normalized}:${record.port}`;
1671
+ }
1672
+ if (normalized === "::1" || normalized === "[::1]") {
1673
+ return `http://[::1]:${record.port}`;
1674
+ }
1675
+ return null;
1676
+ }
1677
+ async function endpointHasOwner(endpoint3, instanceId) {
1678
+ const controller = new AbortController();
1679
+ const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
1680
+ try {
1681
+ const response = await fetch(new URL("/api/health", endpoint3), {
1682
+ signal: controller.signal
1683
+ });
1684
+ return response.headers.get("x-bazilion-daemon-instance") === instanceId;
1685
+ } catch {
1686
+ return false;
1687
+ } finally {
1688
+ clearTimeout(timer);
1689
+ }
1690
+ }
1691
+ function sameFile(left, right) {
1692
+ return left.dev === right.dev && left.ino === right.ino;
1693
+ }
1694
+ function writeRecord(fd, record) {
1695
+ const body = `${JSON.stringify(record, null, 2)}
1696
+ `;
1697
+ ftruncateSync(fd, 0);
1698
+ writeSync(fd, body, 0, "utf8");
1699
+ fsyncSync(fd);
1700
+ }
1701
+ function fsyncParentDirectory(path) {
1702
+ let directoryFd = null;
1703
+ try {
1704
+ directoryFd = openSync(dirname(path), "r");
1705
+ fsyncSync(directoryFd);
1706
+ } catch (error) {
1707
+ if (process.platform !== "win32") throw error;
1708
+ } finally {
1709
+ if (directoryFd !== null) closeSync(directoryFd);
1710
+ }
1711
+ }
1712
+ function reclaimRecord(path, existing) {
1713
+ const identity = createHash2("sha256").update(existing.record?.instanceId ?? existing.raw).update(`\0${existing.stat.dev}\0${existing.stat.ino}`).digest("hex").slice(0, 24);
1714
+ const claim = `${path}.reclaim-${identity}`;
1715
+ try {
1716
+ mkdirSync(claim, { mode: 448 });
1717
+ } catch (error) {
1718
+ if (errorCode(error) !== "EEXIST") throw error;
1719
+ return null;
1720
+ }
1721
+ const current = readRecord(path);
1722
+ if (!current || !sameFile(current.stat, existing.stat) || current.raw !== existing.raw) {
1723
+ return null;
1724
+ }
1725
+ try {
1726
+ renameSync(path, join2(claim, "stale-record.json"));
1727
+ return claim;
1728
+ } catch (error) {
1729
+ if (errorCode(error) === "ENOENT") return null;
1730
+ throw error;
1731
+ }
1732
+ }
1733
+ async function acquireHomeRestoreLock(home) {
1734
+ const targetHome = resolve(home);
1735
+ mkdirSync(dirname(targetHome), { recursive: true });
1736
+ const path = daemonLivenessPath(targetHome);
1737
+ mkdirSync(dirname(path), { recursive: true });
1738
+ const instanceId = randomUUID();
1739
+ let fd = null;
1740
+ let reclaimClaim = null;
1741
+ for (let attempt = 0; attempt < 8 && fd === null; attempt++) {
1742
+ try {
1743
+ fd = openSync(path, "wx", 384);
1744
+ break;
1745
+ } catch (error) {
1746
+ if (errorCode(error) !== "EEXIST") throw error;
1747
+ }
1748
+ const existing = readRecord(path);
1749
+ if (!existing) continue;
1750
+ const endpoint3 = existing.record ? endpointFor(existing.record) : null;
1751
+ if (endpoint3 && existing.record && await endpointHasOwner(endpoint3, existing.record.instanceId)) {
1752
+ throw new Error(
1753
+ `bazilion daemon is running at ${endpoint3} for ${targetHome} \u2014 stop it before restoring`
1754
+ );
1755
+ }
1756
+ if (!existing.record) {
1757
+ throw new Error(
1758
+ `the daemon ownership record for ${targetHome} is invalid; verify no daemon is running before removing it manually`
1759
+ );
1760
+ }
1761
+ const livePid = pidIsAlive(existing.record.pid);
1762
+ if (existing.record.owner === "restore" && ["swapping", "recovery-required"].includes(existing.record.state)) {
1763
+ if (livePid) {
1764
+ throw new Error(
1765
+ `bazilion restore process ${existing.record.pid} is ${existing.record.state} ${targetHome} \u2014 wait for it to finish`
1766
+ );
1767
+ }
1768
+ const phase = existing.record.state.replace("-", " ");
1769
+ throw new Error(
1770
+ `Bazilion restore ${phase} for ${targetHome}; recovery data is at ${existing.record.recoveryPath}. Recover the previous home, then remove ${path} manually.`
1771
+ );
1772
+ }
1773
+ if (livePid) {
1774
+ const ageMs = Math.max(0, Date.now() - existing.stat.mtimeMs);
1775
+ const staleDetail = ageMs > STALE_AFTER_MS ? " (its heartbeat is stale)" : "";
1776
+ const owner = existing.record.owner === "daemon" ? "daemon" : "restore";
1777
+ throw new Error(
1778
+ `bazilion ${owner} process ${existing.record.pid} owns ${targetHome}${staleDetail} \u2014 stop it before restoring`
1779
+ );
1780
+ }
1781
+ const claim = reclaimRecord(path, existing);
1782
+ if (claim) {
1783
+ reclaimClaim = claim;
1784
+ console.warn(`removed stale Bazilion daemon ownership record for ${targetHome}`);
1785
+ }
1786
+ }
1787
+ if (fd === null) {
1788
+ throw new Error(
1789
+ `could not safely acquire exclusive restore ownership for ${targetHome}; try again`
1790
+ );
1791
+ }
1792
+ const ownedStat = fstatSync(fd);
1793
+ const record = {
1794
+ version: 1,
1795
+ instanceId,
1796
+ pid: process.pid,
1797
+ owner: "restore",
1798
+ state: "restoring",
1799
+ startedAt: Date.now()
1800
+ };
1801
+ try {
1802
+ writeRecord(fd, record);
1803
+ fsyncParentDirectory(path);
1804
+ if (reclaimClaim) rmSync(reclaimClaim, { recursive: true, force: true });
1805
+ try {
1806
+ chmodSync(path, 384);
1807
+ } catch {
1808
+ }
1809
+ } catch (error) {
1810
+ closeSync(fd);
1811
+ fd = null;
1812
+ try {
1813
+ if (sameFile(lstatSync(path), ownedStat)) rmSync(path, { force: true });
1814
+ } catch {
1815
+ }
1816
+ throw error;
1817
+ }
1818
+ let released = false;
1819
+ const stillOwnsPath = () => {
1820
+ try {
1821
+ return sameFile(lstatSync(path), ownedStat);
1822
+ } catch {
1823
+ return false;
1824
+ }
1825
+ };
1826
+ const heartbeat = setInterval(() => {
1827
+ if (released) return;
1828
+ if (!stillOwnsPath()) {
1829
+ console.error("restore ownership was lost; exiting to protect the Bazilion database");
1830
+ process.exit(1);
1831
+ }
1832
+ try {
1833
+ const now = /* @__PURE__ */ new Date();
1834
+ futimesSync(fd, now, now);
1835
+ } catch (error) {
1836
+ console.error(
1837
+ "restore ownership heartbeat failed:",
1838
+ error instanceof Error ? error.message : error
1839
+ );
1840
+ process.exit(1);
1841
+ }
1842
+ }, 2e3);
1843
+ heartbeat.unref();
1844
+ return {
1845
+ path,
1846
+ markSwapping(recoveryPath, hadPrevious) {
1847
+ if (released || !stillOwnsPath()) throw new Error("restore ownership was lost");
1848
+ record.state = "swapping";
1849
+ record.recoveryPath = resolve(recoveryPath);
1850
+ record.hadPrevious = hadPrevious;
1851
+ writeRecord(fd, record);
1852
+ fsyncParentDirectory(path);
1853
+ },
1854
+ markInstalled() {
1855
+ if (released || !stillOwnsPath()) throw new Error("restore ownership was lost");
1856
+ record.state = "installed";
1857
+ delete record.recoveryPath;
1858
+ delete record.hadPrevious;
1859
+ writeRecord(fd, record);
1860
+ },
1861
+ markRecoveryRequired(recoveryPath, hadPrevious) {
1862
+ if (released || !stillOwnsPath()) throw new Error("restore ownership was lost");
1863
+ record.state = "recovery-required";
1864
+ record.recoveryPath = resolve(recoveryPath);
1865
+ record.hadPrevious = hadPrevious;
1866
+ writeRecord(fd, record);
1867
+ },
1868
+ release() {
1869
+ if (released) return;
1870
+ released = true;
1871
+ clearInterval(heartbeat);
1872
+ const ownsPath = stillOwnsPath();
1873
+ const retainForRecovery = ["swapping", "recovery-required"].includes(record.state);
1874
+ closeSync(fd);
1875
+ fd = null;
1876
+ if (ownsPath && !retainForRecovery) rmSync(path, { force: true });
1877
+ }
1878
+ };
1879
+ }
1880
+
1881
+ // src/commands/backup.ts
1882
+ var createCmd = defineCommand5({
1075
1883
  meta: { name: "create", description: "Download a tar.gz backup of ~/.bazilion from the server" },
1076
1884
  args: {
1077
1885
  output: {
@@ -1083,7 +1891,14 @@ var createCmd = defineCommand4({
1083
1891
  async run({ args }) {
1084
1892
  const cfg = loadClientConfig();
1085
1893
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1086
- const outAbs = resolve(args.output ?? `bazilion-backup-${date}.tar.gz`);
1894
+ const outAbs = resolve2(args.output ?? `bazilion-backup-${date}.tar.gz`);
1895
+ const configuredHome = resolve2(resolveCliPaths().home);
1896
+ const outputFromHome = relative(configuredHome, outAbs);
1897
+ if (!outputFromHome || !outputFromHome.startsWith("..") && !isAbsolute(outputFromHome)) {
1898
+ throw new Error(
1899
+ `backup output must be outside BAZILION_HOME (${configuredHome}) to avoid nesting backups`
1900
+ );
1901
+ }
1087
1902
  console.log(`downloading backup \u2192 ${outAbs}`);
1088
1903
  const res = await fetch(`${cfg.serverUrl}/api/backup`, {
1089
1904
  headers: { authorization: `Bearer ${cfg.token}`, origin: cfg.serverUrl }
@@ -1097,22 +1912,463 @@ var createCmd = defineCommand4({
1097
1912
  }
1098
1913
  throw new Error(`backup failed: ${err}`);
1099
1914
  }
1100
- await pipeline(Readable.fromWeb(res.body), createWriteStream(outAbs));
1915
+ const downloadDir = mkdtempSync(resolve2(tmpdir(), "bazilion-download-"));
1916
+ const partial = resolve2(downloadDir, "backup.tar.gz");
1917
+ try {
1918
+ await pipeline(
1919
+ Readable.fromWeb(res.body),
1920
+ createWriteStream(partial, { flags: "wx", mode: 384 })
1921
+ );
1922
+ await validateArchive(partial);
1923
+ try {
1924
+ renameSync2(partial, outAbs);
1925
+ } catch (error) {
1926
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "EXDEV") throw error;
1927
+ const installPartial = resolve2(
1928
+ dirname2(outAbs),
1929
+ `.${basename3(outAbs)}.${randomUUID2()}.installing`
1930
+ );
1931
+ try {
1932
+ copyFileSync(partial, installPartial);
1933
+ chmodSync2(installPartial, 384);
1934
+ renameSync2(installPartial, outAbs);
1935
+ } finally {
1936
+ rmSync2(installPartial, { force: true });
1937
+ }
1938
+ }
1939
+ } finally {
1940
+ rmSync2(downloadDir, { recursive: true, force: true });
1941
+ }
1101
1942
  console.log("done");
1102
1943
  }
1103
1944
  });
1104
- async function probeServerRunning(port) {
1945
+ async function probeServerRunning(serverUrl) {
1105
1946
  try {
1106
1947
  const ctrl = new AbortController();
1107
1948
  const timer = setTimeout(() => ctrl.abort(), 500);
1108
- const res = await fetch(`http://127.0.0.1:${port}/login`, { signal: ctrl.signal });
1109
- clearTimeout(timer);
1110
- return res.status >= 200 && res.status < 600;
1949
+ try {
1950
+ const res = await fetch(new URL("/api/health", serverUrl), { signal: ctrl.signal });
1951
+ return res.status >= 200 && res.status < 600;
1952
+ } finally {
1953
+ clearTimeout(timer);
1954
+ }
1111
1955
  } catch {
1112
1956
  return false;
1113
1957
  }
1114
1958
  }
1115
- var restoreCmd = defineCommand4({
1959
+ function loopbackServerUrl(raw) {
1960
+ if (!raw) return null;
1961
+ try {
1962
+ const url = new URL(raw);
1963
+ if (!["http:", "https:"].includes(url.protocol)) return null;
1964
+ const hostname = url.hostname.toLowerCase();
1965
+ if (!["127.0.0.1", "localhost", "[::1]", "::1"].includes(hostname)) return null;
1966
+ return url.origin;
1967
+ } catch {
1968
+ return null;
1969
+ }
1970
+ }
1971
+ function configuredLoopbackUrls() {
1972
+ const urls = /* @__PURE__ */ new Set(["http://127.0.0.1:4321"]);
1973
+ const envUrl = loopbackServerUrl(process.env.BAZILION_SERVER);
1974
+ if (envUrl) urls.add(envUrl);
1975
+ try {
1976
+ const clientUrl = loopbackServerUrl(loadClientConfig().serverUrl);
1977
+ if (clientUrl) urls.add(clientUrl);
1978
+ } catch {
1979
+ }
1980
+ return [...urls];
1981
+ }
1982
+ var EXTRACTABLE_ENTRY_TYPES = /* @__PURE__ */ new Set([
1983
+ "File",
1984
+ "OldFile",
1985
+ "Directory",
1986
+ "SymbolicLink"
1987
+ ]);
1988
+ function normalizeArchivePath(rawPath) {
1989
+ if (!rawPath || rawPath.includes("\0")) throw new Error("backup contains an invalid empty path");
1990
+ if (rawPath.includes("\\")) {
1991
+ throw new Error(`backup contains a non-portable path: ${JSON.stringify(rawPath)}`);
1992
+ }
1993
+ if (rawPath.startsWith("/") || /^[A-Za-z]:/.test(rawPath)) {
1994
+ throw new Error(`backup contains an absolute path: ${JSON.stringify(rawPath)}`);
1995
+ }
1996
+ const segments = [];
1997
+ for (const segment of rawPath.split("/")) {
1998
+ if (!segment || segment === ".") continue;
1999
+ if (segment === "..") {
2000
+ throw new Error(`backup path escapes the restore directory: ${JSON.stringify(rawPath)}`);
2001
+ }
2002
+ segments.push(segment);
2003
+ }
2004
+ return segments.join("/");
2005
+ }
2006
+ function validateArchiveSymbolicLink(path, rawTarget) {
2007
+ if (!rawTarget || rawTarget.includes("\0")) {
2008
+ throw new Error(`backup contains an invalid symbolic link target: ${path}`);
2009
+ }
2010
+ const segments = path.split("/");
2011
+ const isTeamSlot = segments.length === 2 && segments[0] === "teams" && /^[a-z0-9][a-z0-9-]*$/.test(segments[1] ?? "");
2012
+ const portableTarget = rawTarget.replaceAll("\\", "/");
2013
+ const absoluteOnAnyPlatform = isAbsolute(rawTarget) || posix.isAbsolute(portableTarget) || /^[A-Za-z]:\//.test(portableTarget);
2014
+ if (absoluteOnAnyPlatform) {
2015
+ if (!isTeamSlot) {
2016
+ throw new Error(`backup absolute symbolic links are only allowed for Team slots: ${path}`);
2017
+ }
2018
+ if (!isAbsolute(rawTarget)) {
2019
+ throw new Error(`backup contains an invalid Team link target: ${path}`);
2020
+ }
2021
+ return;
2022
+ }
2023
+ if (isTeamSlot) {
2024
+ throw new Error(`backup contains an invalid Team link target: ${path}`);
2025
+ }
2026
+ if (/^[A-Za-z]:/.test(portableTarget)) {
2027
+ throw new Error(`backup contains a non-portable symbolic link target: ${path}`);
2028
+ }
2029
+ const target = posix.normalize(posix.join(posix.dirname(path), portableTarget));
2030
+ if (target === ".." || target.startsWith("../") || posix.isAbsolute(target)) {
2031
+ throw new Error(`backup symbolic link target escapes the restore directory: ${path}`);
2032
+ }
2033
+ }
2034
+ async function validateArchive(file) {
2035
+ const entries = [];
2036
+ await list3({
2037
+ file,
2038
+ strict: true,
2039
+ onentry(entry2) {
2040
+ if (entry2.meta) return;
2041
+ const path = normalizeArchivePath(entry2.path);
2042
+ if (!path) return;
2043
+ if (!EXTRACTABLE_ENTRY_TYPES.has(entry2.type)) {
2044
+ throw new Error(`backup contains unsupported ${entry2.type} entry: ${path}`);
2045
+ }
2046
+ if (entry2.type === "SymbolicLink") {
2047
+ validateArchiveSymbolicLink(path, entry2.linkpath);
2048
+ }
2049
+ entries.push({ path, type: entry2.type, linkpath: entry2.linkpath });
2050
+ }
2051
+ });
2052
+ const byPath = /* @__PURE__ */ new Map();
2053
+ for (const entry2 of entries) {
2054
+ if (byPath.has(entry2.path)) throw new Error(`backup contains a duplicate path: ${entry2.path}`);
2055
+ byPath.set(entry2.path, entry2);
2056
+ }
2057
+ const symlinks = new Set(
2058
+ entries.filter((entry2) => entry2.type === "SymbolicLink").map((entry2) => entry2.path)
2059
+ );
2060
+ for (const entry2 of entries) {
2061
+ const segments = entry2.path.split("/");
2062
+ for (let i = 1; i < segments.length; i++) {
2063
+ const ancestor = segments.slice(0, i).join("/");
2064
+ if (symlinks.has(ancestor)) {
2065
+ throw new Error(`backup entry traverses symbolic link ${ancestor}: ${entry2.path}`);
2066
+ }
2067
+ const ancestorEntry = byPath.get(ancestor);
2068
+ if (ancestorEntry && ancestorEntry.type !== "Directory") {
2069
+ throw new Error(`backup entry traverses non-directory ${ancestor}: ${entry2.path}`);
2070
+ }
2071
+ }
2072
+ }
2073
+ for (const required of ["bazilion.db", "auth.json"]) {
2074
+ const entry2 = byPath.get(required);
2075
+ if (!entry2 || entry2.type !== "File" && entry2.type !== "OldFile") {
2076
+ throw new Error(`backup is missing required regular file: ${required}`);
2077
+ }
2078
+ }
2079
+ for (const transient of [
2080
+ "bazilion.db-wal",
2081
+ "bazilion.db-shm",
2082
+ "bazilion.db-journal",
2083
+ DAEMON_LIVENESS_FILENAME
2084
+ ]) {
2085
+ if (byPath.has(transient)) {
2086
+ throw new Error(`backup contains unsafe transient SQLite file: ${transient}`);
2087
+ }
2088
+ }
2089
+ return entries;
2090
+ }
2091
+ function validateAuthFile(path) {
2092
+ let parsed;
2093
+ try {
2094
+ parsed = JSON.parse(readFileSync4(path, "utf8"));
2095
+ } catch (error) {
2096
+ const detail = error instanceof Error ? error.message : String(error);
2097
+ throw new Error(`backup auth.json is invalid: ${detail}`);
2098
+ }
2099
+ if (typeof parsed !== "object" || parsed === null || !("token" in parsed) || typeof parsed.token !== "string" || !parsed.token.trim()) {
2100
+ throw new Error("backup auth.json is invalid: token is missing");
2101
+ }
2102
+ return parsed.token;
2103
+ }
2104
+ function validateDatabase(path, authToken) {
2105
+ let db = null;
2106
+ try {
2107
+ db = new DatabaseSync(path, { readOnly: true });
2108
+ const integrity = db.prepare("PRAGMA integrity_check").all();
2109
+ if (integrity.length !== 1 || integrity[0]?.integrity_check !== "ok") {
2110
+ const detail = integrity.map((row) => row.integrity_check).join("; ") || "no result";
2111
+ throw new Error(`integrity_check failed: ${detail}`);
2112
+ }
2113
+ const foreignKeyErrors = db.prepare("PRAGMA foreign_key_check").all();
2114
+ if (foreignKeyErrors.length > 0) {
2115
+ throw new Error(`foreign_key_check failed with ${foreignKeyErrors.length} violation(s)`);
2116
+ }
2117
+ assertCanonicalBackupSchema(db);
2118
+ const tokenHash = createHash3("sha256").update(authToken).digest("hex");
2119
+ const activeBootstrap = db.prepare(
2120
+ `SELECT 1 FROM web_tokens
2121
+ WHERE token_hash = ? AND label = 'bootstrap' AND revoked_at IS NULL
2122
+ LIMIT 1`
2123
+ ).get(tokenHash);
2124
+ if (!activeBootstrap) {
2125
+ throw new Error("auth.json token does not match an active web_tokens row");
2126
+ }
2127
+ } catch (error) {
2128
+ if (error instanceof Error && error.message.startsWith("backup database is invalid:")) {
2129
+ throw error;
2130
+ }
2131
+ const detail = error instanceof Error ? error.message : String(error);
2132
+ throw new Error(`backup database is invalid: ${detail}`);
2133
+ } finally {
2134
+ db?.close();
2135
+ }
2136
+ }
2137
+ var SLUG_ID = /^[a-z0-9][a-z0-9-]*$/;
2138
+ 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}$/;
2139
+ function validateEntityId(id, pattern, entity) {
2140
+ if (typeof id !== "string" || !pattern.test(id)) {
2141
+ throw new Error(`${entity} has an invalid canonical id: ${JSON.stringify(id)}`);
2142
+ }
2143
+ return id;
2144
+ }
2145
+ function containedChildDirectory(root, id, rowLabel) {
2146
+ const child = resolve2(root, id);
2147
+ const fromRoot = relative(root, child);
2148
+ if (!fromRoot || fromRoot.startsWith("..") || isAbsolute(fromRoot)) {
2149
+ throw new Error(`${rowLabel} directory escapes its canonical root: ${JSON.stringify(id)}`);
2150
+ }
2151
+ return child;
2152
+ }
2153
+ function requireExtractedDirectory(path, rowLabel) {
2154
+ try {
2155
+ if (lstatSync2(path).isDirectory()) return;
2156
+ } catch {
2157
+ }
2158
+ throw new Error(`${rowLabel} is missing its canonical directory in the backup`);
2159
+ }
2160
+ function requireExtractedTeamSlot(path, rowLabel) {
2161
+ try {
2162
+ const entry2 = lstatSync2(path);
2163
+ if (entry2.isDirectory() || entry2.isSymbolicLink()) return;
2164
+ } catch {
2165
+ }
2166
+ throw new Error(`${rowLabel} is missing its canonical Team slot in the backup`);
2167
+ }
2168
+ function rebaseRestoredHomeDirectories(database, payload, targetHome) {
2169
+ let db = null;
2170
+ try {
2171
+ db = new DatabaseSync(database);
2172
+ db.exec("PRAGMA foreign_keys = ON");
2173
+ const profiles = db.prepare("SELECT id FROM profiles ORDER BY id").all();
2174
+ const agents = db.prepare("SELECT id FROM agents ORDER BY id").all();
2175
+ const teams = db.prepare("SELECT id FROM teams ORDER BY id").all();
2176
+ const rewrites = [];
2177
+ for (const [table, rows, idPattern] of [
2178
+ ["profiles", profiles, SLUG_ID],
2179
+ ["agents", agents, AGENT_ID]
2180
+ ]) {
2181
+ const collection = table;
2182
+ const stagedRoot = resolve2(payload, collection);
2183
+ const targetRoot = resolve2(targetHome, collection);
2184
+ for (const row of rows) {
2185
+ const id = validateEntityId(row.id, idPattern, `${table} row`);
2186
+ const rowLabel = `${table} row ${JSON.stringify(id)}`;
2187
+ const stagedDir = containedChildDirectory(stagedRoot, id, rowLabel);
2188
+ requireExtractedDirectory(stagedDir, rowLabel);
2189
+ rewrites.push({
2190
+ table,
2191
+ id,
2192
+ dir: containedChildDirectory(targetRoot, id, rowLabel)
2193
+ });
2194
+ }
2195
+ }
2196
+ const stagedTeamsRoot = resolve2(payload, "teams");
2197
+ for (const row of teams) {
2198
+ const id = validateEntityId(row.id, SLUG_ID, "teams row");
2199
+ const rowLabel = `teams row ${JSON.stringify(id)}`;
2200
+ requireExtractedTeamSlot(containedChildDirectory(stagedTeamsRoot, id, rowLabel), rowLabel);
2201
+ }
2202
+ db.exec("PRAGMA journal_mode = DELETE");
2203
+ db.exec("PRAGMA synchronous = FULL");
2204
+ let transactionOpen = false;
2205
+ try {
2206
+ db.exec("BEGIN IMMEDIATE");
2207
+ transactionOpen = true;
2208
+ const updateProfile = db.prepare("UPDATE profiles SET dir = ? WHERE id = ?");
2209
+ const updateAgent = db.prepare("UPDATE agents SET dir = ? WHERE id = ?");
2210
+ for (const rewrite of rewrites) {
2211
+ const result = rewrite.table === "profiles" ? updateProfile.run(rewrite.dir, rewrite.id) : updateAgent.run(rewrite.dir, rewrite.id);
2212
+ if (result.changes !== 1) {
2213
+ throw new Error(`could not rebase ${rewrite.table} row ${JSON.stringify(rewrite.id)}`);
2214
+ }
2215
+ }
2216
+ const readProfile = db.prepare("SELECT dir FROM profiles WHERE id = ?");
2217
+ const readAgent = db.prepare("SELECT dir FROM agents WHERE id = ?");
2218
+ for (const rewrite of rewrites) {
2219
+ const row = rewrite.table === "profiles" ? readProfile.get(rewrite.id) : readAgent.get(rewrite.id);
2220
+ if (row?.dir !== rewrite.dir) {
2221
+ throw new Error(
2222
+ `could not verify rebased ${rewrite.table} row ${JSON.stringify(rewrite.id)}`
2223
+ );
2224
+ }
2225
+ }
2226
+ db.exec("COMMIT");
2227
+ transactionOpen = false;
2228
+ } catch (error) {
2229
+ if (transactionOpen) db.exec("ROLLBACK");
2230
+ throw error;
2231
+ }
2232
+ } catch (error) {
2233
+ const detail = error instanceof Error ? error.message : String(error);
2234
+ throw new Error(`backup database is invalid: ${detail}`);
2235
+ } finally {
2236
+ db?.close();
2237
+ }
2238
+ for (const suffix of ["-wal", "-shm", "-journal"]) {
2239
+ if (existsSync2(`${database}${suffix}`)) {
2240
+ throw new Error(`backup database is invalid: path rebasing left transient ${suffix} state`);
2241
+ }
2242
+ }
2243
+ }
2244
+ function removeRebuildableQmdIndexes(payload) {
2245
+ const teamsDir = resolve2(payload, "teams");
2246
+ if (!existsSync2(teamsDir) || !lstatSync2(teamsDir).isDirectory()) return;
2247
+ for (const team of readdirSync(teamsDir, { withFileTypes: true })) {
2248
+ if (!team.isDirectory() || team.isSymbolicLink()) continue;
2249
+ const memoryDir = resolve2(teamsDir, team.name, "memory");
2250
+ if (!existsSync2(memoryDir) || !lstatSync2(memoryDir).isDirectory()) continue;
2251
+ for (const entry2 of readdirSync(memoryDir, { withFileTypes: true })) {
2252
+ if (entry2.isFile() && entry2.name.startsWith(".qmd-index.sqlite")) {
2253
+ rmSync2(resolve2(memoryDir, entry2.name), { force: true });
2254
+ }
2255
+ }
2256
+ }
2257
+ }
2258
+ function installValidatedPayload(payload, targetHome, staging, lifecycle, operations = { rename: renameSync2 }) {
2259
+ const previous = resolve2(staging, "previous-home");
2260
+ const hadPrevious = existsSync2(targetHome);
2261
+ const targetParent = dirname2(targetHome);
2262
+ const syncDirectory = operations.fsyncDirectory ?? fsyncDirectory;
2263
+ const recoveryError = (message, recoveryPath, cause) => {
2264
+ let markerError;
2265
+ try {
2266
+ lifecycle?.markRecoveryRequired(recoveryPath, hadPrevious);
2267
+ } catch (error) {
2268
+ markerError = error;
2269
+ }
2270
+ const causeDetail = cause instanceof Error ? cause.message : String(cause);
2271
+ const markerDetail = markerError ? `; recovery marker update failed: ${markerError instanceof Error ? markerError.message : markerError}` : "";
2272
+ return new RestoreRecoveryRequiredError(
2273
+ `${message} (${causeDetail}); recovery data is at ${recoveryPath}${markerDetail}`,
2274
+ recoveryPath
2275
+ );
2276
+ };
2277
+ lifecycle?.markSwapping(previous, hadPrevious);
2278
+ if (hadPrevious) {
2279
+ try {
2280
+ operations.rename(targetHome, previous);
2281
+ } catch (moveError) {
2282
+ lifecycle?.markInstalled();
2283
+ throw moveError;
2284
+ }
2285
+ try {
2286
+ syncDirectory(targetParent);
2287
+ operations.afterPreviousMoved?.();
2288
+ } catch (postMoveError) {
2289
+ throw recoveryError(
2290
+ "the previous home moved but the swap could not continue safely",
2291
+ previous,
2292
+ postMoveError
2293
+ );
2294
+ }
2295
+ }
2296
+ try {
2297
+ operations.rename(payload, targetHome);
2298
+ } catch (installError) {
2299
+ if (hadPrevious) {
2300
+ try {
2301
+ operations.rename(previous, targetHome);
2302
+ } catch (rollbackError) {
2303
+ const installDetail = installError instanceof Error ? installError.message : String(installError);
2304
+ throw recoveryError(
2305
+ `restore install failed (${installDetail}) and rollback failed`,
2306
+ previous,
2307
+ rollbackError
2308
+ );
2309
+ }
2310
+ try {
2311
+ syncDirectory(targetParent);
2312
+ } catch (rollbackSyncError) {
2313
+ throw recoveryError(
2314
+ "the previous home was restored but its directory entry could not be made durable",
2315
+ targetHome,
2316
+ rollbackSyncError
2317
+ );
2318
+ }
2319
+ try {
2320
+ lifecycle?.markInstalled();
2321
+ } catch (phaseError) {
2322
+ throw recoveryError(
2323
+ "the previous home was restored but could not be marked safe",
2324
+ targetHome,
2325
+ phaseError
2326
+ );
2327
+ }
2328
+ } else {
2329
+ lifecycle?.markInstalled();
2330
+ }
2331
+ throw installError;
2332
+ }
2333
+ try {
2334
+ syncDirectory(targetParent);
2335
+ } catch (installSyncError) {
2336
+ throw recoveryError(
2337
+ "the restored home was installed but its directory entry could not be made durable",
2338
+ hadPrevious ? previous : targetHome,
2339
+ installSyncError
2340
+ );
2341
+ }
2342
+ try {
2343
+ lifecycle?.markInstalled();
2344
+ } catch (phaseError) {
2345
+ throw recoveryError(
2346
+ "the restored home was installed but could not be marked safe",
2347
+ hadPrevious ? previous : targetHome,
2348
+ phaseError
2349
+ );
2350
+ }
2351
+ }
2352
+ var RestoreRecoveryRequiredError = class extends Error {
2353
+ recoveryPath;
2354
+ constructor(message, recoveryPath) {
2355
+ super(message);
2356
+ this.name = "RestoreRecoveryRequiredError";
2357
+ this.recoveryPath = recoveryPath;
2358
+ }
2359
+ };
2360
+ function fsyncDirectory(path) {
2361
+ let fd = null;
2362
+ try {
2363
+ fd = openSync2(path, "r");
2364
+ fsyncSync2(fd);
2365
+ } catch (error) {
2366
+ if (process.platform !== "win32") throw error;
2367
+ } finally {
2368
+ if (fd !== null) closeSync2(fd);
2369
+ }
2370
+ }
2371
+ var restoreCmd = defineCommand5({
1116
2372
  meta: {
1117
2373
  name: "restore",
1118
2374
  description: "Extract a backup tar.gz into ~/.bazilion (offline; server must be stopped)"
@@ -1130,42 +2386,96 @@ var restoreCmd = defineCommand4({
1130
2386
  }
1131
2387
  },
1132
2388
  async run({ args }) {
1133
- const file = resolve(args.file);
2389
+ const file = resolve2(args.file);
1134
2390
  if (!existsSync2(file)) throw new Error(`backup file not found: ${file}`);
1135
2391
  const paths = resolveCliPaths(args.home);
1136
- const targetHome = paths.home;
1137
- const defaultHome = !args.home && !process.env.BAZILION_HOME;
1138
- if (defaultHome && !args.force && await probeServerRunning(4321)) {
1139
- throw new Error(
1140
- "bazilion server appears to be running on :4321 \u2014 stop it first (DB corruption risk), or pass --force to override."
1141
- );
2392
+ const targetHome = resolve2(paths.home);
2393
+ if (targetHome === parse(targetHome).root) {
2394
+ throw new Error(`refusing to restore over filesystem root: ${targetHome}`);
1142
2395
  }
1143
- if (existsSync2(targetHome)) {
1144
- const entries = readdirSync(targetHome);
1145
- if (entries.length > 0) {
1146
- if (!args.force) {
2396
+ const targetParent = dirname2(targetHome);
2397
+ mkdirSync2(targetParent, { recursive: true });
2398
+ const restoreLock = await acquireHomeRestoreLock(targetHome);
2399
+ try {
2400
+ const targetsConfiguredHome = targetHome === resolve2(resolveCliPaths().home);
2401
+ if (targetsConfiguredHome) {
2402
+ for (const serverUrl of configuredLoopbackUrls()) {
2403
+ if (await probeServerRunning(serverUrl)) {
2404
+ throw new Error(
2405
+ `bazilion server appears to be running at ${serverUrl} \u2014 stop it before restoring.`
2406
+ );
2407
+ }
2408
+ }
2409
+ }
2410
+ if (existsSync2(targetHome)) {
2411
+ if (!lstatSync2(targetHome).isDirectory()) {
2412
+ throw new Error(`${targetHome} exists and is not a directory`);
2413
+ }
2414
+ const entries = readdirSync(targetHome);
2415
+ if (entries.length > 0 && !args.force) {
1147
2416
  throw new Error(
1148
2417
  `${targetHome} is not empty. Pass --force to overwrite (destroys existing data).`
1149
2418
  );
1150
2419
  }
1151
- rmSync(targetHome, { recursive: true, force: true });
1152
2420
  }
2421
+ const staging = mkdtempSync(resolve2(targetParent, ".bazilion-restore-"));
2422
+ const stagedArchive = resolve2(staging, "backup.tar.gz");
2423
+ const payload = resolve2(staging, "payload");
2424
+ let preserveStaging = false;
2425
+ try {
2426
+ copyFileSync(file, stagedArchive);
2427
+ await validateArchive(stagedArchive);
2428
+ mkdirSync2(payload);
2429
+ await extract({
2430
+ file: stagedArchive,
2431
+ cwd: payload,
2432
+ strict: true,
2433
+ preserveOwner: false,
2434
+ // The private copy has already passed complete entry-path and link
2435
+ // validation above. Preserve paths here so an intentionally absolute
2436
+ // external linked-Team target is not stripped into a different,
2437
+ // payload-relative path by npm-tar.
2438
+ preservePaths: true
2439
+ });
2440
+ const database = resolve2(payload, "bazilion.db");
2441
+ const authFile = resolve2(payload, "auth.json");
2442
+ if (!lstatSync2(database).isFile() || !lstatSync2(authFile).isFile()) {
2443
+ throw new Error("backup required files did not extract as regular files");
2444
+ }
2445
+ const authToken = validateAuthFile(authFile);
2446
+ validateDatabase(database, authToken);
2447
+ rebaseRestoredHomeDirectories(database, payload, targetHome);
2448
+ validateDatabase(database, authToken);
2449
+ removeRebuildableQmdIndexes(payload);
2450
+ console.log(`installing validated backup ${file} \u2192 ${targetHome}`);
2451
+ try {
2452
+ installValidatedPayload(payload, targetHome, staging, restoreLock);
2453
+ } catch (error) {
2454
+ if (error instanceof RestoreRecoveryRequiredError) {
2455
+ preserveStaging = true;
2456
+ }
2457
+ throw error;
2458
+ }
2459
+ } finally {
2460
+ if (!preserveStaging) {
2461
+ try {
2462
+ rmSync2(staging, { recursive: true, force: true });
2463
+ } catch (error) {
2464
+ const detail = error instanceof Error ? error.message : String(error);
2465
+ console.warn(
2466
+ `warning: could not remove restore staging directory ${staging}: ${detail}`
2467
+ );
2468
+ }
2469
+ }
2470
+ }
2471
+ } finally {
2472
+ restoreLock.release();
1153
2473
  }
1154
- mkdirSync(targetHome, { recursive: true });
1155
- console.log(`extracting ${file} \u2192 ${targetHome}`);
1156
- await new Promise((res, rej) => {
1157
- const proc = spawn("tar", ["-xzf", file, "-C", targetHome], { stdio: "inherit" });
1158
- proc.on("error", rej);
1159
- proc.on("exit", (code) => {
1160
- if (code === 0) res();
1161
- else rej(new Error(`tar exited with code ${code}`));
1162
- });
1163
- });
1164
2474
  console.log(`restored to ${targetHome}`);
1165
2475
  console.log("start the server with: bazilion serve (migrations apply automatically)");
1166
2476
  }
1167
2477
  });
1168
- var backupCommand = defineCommand4({
2478
+ var backupCommand = defineCommand5({
1169
2479
  meta: { name: "backup", description: "Create or restore a ~/.bazilion tar.gz backup" },
1170
2480
  subCommands: {
1171
2481
  create: createCmd,
@@ -1179,7 +2489,7 @@ var backupCommand = defineCommand4({
1179
2489
  });
1180
2490
 
1181
2491
  // src/commands/completion.ts
1182
- import { defineCommand as defineCommand5 } from "citty";
2492
+ import { defineCommand as defineCommand6 } from "citty";
1183
2493
  async function walk(cmd, path, out) {
1184
2494
  const subsRaw = cmd.subCommands ?? {};
1185
2495
  const subNames = Object.keys(subsRaw);
@@ -1284,7 +2594,7 @@ function fishScript(flat) {
1284
2594
  `;
1285
2595
  }
1286
2596
  function completionCommand(main2) {
1287
- return defineCommand5({
2597
+ return defineCommand6({
1288
2598
  meta: {
1289
2599
  name: "completion",
1290
2600
  description: "Print a shell completion script (bash, zsh, or fish)"
@@ -1309,13 +2619,13 @@ function completionCommand(main2) {
1309
2619
  }
1310
2620
 
1311
2621
  // src/commands/config.ts
1312
- import { defineCommand as defineCommand6 } from "citty";
2622
+ import { defineCommand as defineCommand7 } from "citty";
1313
2623
  function displayValue(f) {
1314
2624
  if (!f.set) return "(unset)";
1315
2625
  if (f.kind === "secret") return f.preview ?? "(set)";
1316
2626
  return f.value ?? "(set)";
1317
2627
  }
1318
- var listCmd2 = defineCommand6({
2628
+ var listCmd2 = defineCommand7({
1319
2629
  meta: {
1320
2630
  name: "list",
1321
2631
  description: "List every configurable field (credentials + URLs) across all services"
@@ -1338,7 +2648,7 @@ var listCmd2 = defineCommand6({
1338
2648
  for (const line of columnize(rows)) console.log(line);
1339
2649
  }
1340
2650
  });
1341
- var setCmd = defineCommand6({
2651
+ var setCmd = defineCommand7({
1342
2652
  meta: {
1343
2653
  name: "set",
1344
2654
  description: "Set a configurable field (auto-routes to secrets or plaintext store)"
@@ -1365,7 +2675,7 @@ var setCmd = defineCommand6({
1365
2675
  console.log(`${state.envVar} (${state.kind}): ${displayValue(state)}`);
1366
2676
  }
1367
2677
  });
1368
- var rmCmd = defineCommand6({
2678
+ var rmCmd = defineCommand7({
1369
2679
  meta: { name: "rm", description: "Remove a configurable field value" },
1370
2680
  args: {
1371
2681
  envVar: { type: "positional", required: true, description: "Env var name to clear" }
@@ -1376,7 +2686,7 @@ var rmCmd = defineCommand6({
1376
2686
  console.log(`removed ${args.envVar}`);
1377
2687
  }
1378
2688
  });
1379
- var configCommand = defineCommand6({
2689
+ var configCommand = defineCommand7({
1380
2690
  meta: {
1381
2691
  name: "config",
1382
2692
  description: "Manage service configuration \u2014 credentials (encrypted) and URLs/IDs (plaintext), unified under one CLI"
@@ -1389,19 +2699,19 @@ var configCommand = defineCommand6({
1389
2699
  });
1390
2700
 
1391
2701
  // src/commands/dashboard.ts
1392
- import { spawn as spawn2 } from "child_process";
2702
+ import { spawn } from "child_process";
1393
2703
  import { existsSync as existsSync3 } from "fs";
1394
2704
  import { connect } from "net";
1395
- import { join as join2 } from "path";
1396
- import { defineCommand as defineCommand7 } from "citty";
1397
- var bundledDaemonEntry = join2(import.meta.dirname, "daemon.js");
1398
- var sourceDaemonEntry = join2(import.meta.dirname, "..", "..", "..", "daemon", "src", "index.ts");
2705
+ import { join as join3 } from "path";
2706
+ import { defineCommand as defineCommand8 } from "citty";
2707
+ var bundledDaemonEntry = join3(import.meta.dirname, "daemon.js");
2708
+ var sourceDaemonEntry = join3(import.meta.dirname, "..", "..", "..", "daemon", "src", "index.ts");
1399
2709
  var daemonEntry = existsSync3(bundledDaemonEntry) ? bundledDaemonEntry : sourceDaemonEntry;
1400
- var bundledWebServerEntry = join2(import.meta.dirname, "web-server.js");
1401
- var sourceWebServerEntry = join2(import.meta.dirname, "..", "web-server.ts");
2710
+ var bundledWebServerEntry = join3(import.meta.dirname, "web-server.js");
2711
+ var sourceWebServerEntry = join3(import.meta.dirname, "..", "web-server.ts");
1402
2712
  var webServerEntry = existsSync3(bundledWebServerEntry) ? bundledWebServerEntry : sourceWebServerEntry;
1403
- var bundledWebDist = join2(import.meta.dirname, "web");
1404
- var sourceWebDist = join2(import.meta.dirname, "..", "..", "..", "web", "dist");
2713
+ var bundledWebDist = join3(import.meta.dirname, "web");
2714
+ var sourceWebDist = join3(import.meta.dirname, "..", "..", "..", "web", "dist");
1405
2715
  var webDist = existsSync3(bundledWebDist) ? bundledWebDist : sourceWebDist;
1406
2716
  function nodeArgs(entry2) {
1407
2717
  return entry2.endsWith(".ts") ? ["--import", "tsx/esm", entry2] : [entry2];
@@ -1420,12 +2730,12 @@ function probeHost(host) {
1420
2730
  return host;
1421
2731
  }
1422
2732
  function isTcpPortOpen(host, port) {
1423
- return new Promise((resolve3) => {
2733
+ return new Promise((resolve4) => {
1424
2734
  const socket = connect({ host: probeHost(host), port: Number(port) });
1425
2735
  const done = (open) => {
1426
2736
  socket.removeAllListeners();
1427
2737
  socket.destroy();
1428
- resolve3(open);
2738
+ resolve4(open);
1429
2739
  };
1430
2740
  socket.setTimeout(600);
1431
2741
  socket.once("connect", () => done(true));
@@ -1475,19 +2785,19 @@ function openBrowser(url) {
1475
2785
  const platform = process.platform;
1476
2786
  const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : process.env.BROWSER || "xdg-open";
1477
2787
  const args = platform === "win32" ? ["/c", "start", "", url] : [url];
1478
- const proc = spawn2(cmd, args, { detached: true, stdio: "ignore" });
2788
+ const proc = spawn(cmd, args, { detached: true, stdio: "ignore" });
1479
2789
  proc.unref();
1480
2790
  }
1481
2791
  function spawnProcess(label, entry2, env) {
1482
2792
  if (!existsSync3(entry2)) {
1483
2793
  throw new Error(`${label} entry not found at ${entry2}`);
1484
2794
  }
1485
- return spawn2(process.execPath, nodeArgs(entry2), {
2795
+ return spawn(process.execPath, nodeArgs(entry2), {
1486
2796
  env,
1487
2797
  stdio: "inherit"
1488
2798
  });
1489
2799
  }
1490
- var dashboardCommand = defineCommand7({
2800
+ var dashboardCommand = defineCommand8({
1491
2801
  meta: {
1492
2802
  name: "dashboard",
1493
2803
  description: "Start the bazilion daemon and bundled web UI"
@@ -1564,7 +2874,7 @@ var dashboardCommand = defineCommand7({
1564
2874
  `web UI port ${webPort} is already in use. Re-run with --port <free-port>.`
1565
2875
  );
1566
2876
  }
1567
- if (!existsSync3(join2(webDist, "server", "server.js"))) {
2877
+ if (!existsSync3(join3(webDist, "server", "server.js"))) {
1568
2878
  throw new Error(
1569
2879
  `bazilion web UI bundle not found at ${webDist}. Run "pnpm --filter @bazilion/web build" from a source checkout, or reinstall the published package.`
1570
2880
  );
@@ -1591,13 +2901,13 @@ var dashboardCommand = defineCommand7({
1591
2901
  if (children.length === 0) return;
1592
2902
  process.on("SIGINT", terminateChildren);
1593
2903
  process.on("SIGTERM", terminateChildren);
1594
- await new Promise((resolve3) => {
2904
+ await new Promise((resolve4) => {
1595
2905
  let remaining = children.length;
1596
2906
  for (const child of children) {
1597
2907
  child.on("close", (code) => {
1598
2908
  remaining--;
1599
2909
  if (!signalled && code && code !== 0) process.exitCode = code;
1600
- if (remaining === 0) resolve3();
2910
+ if (remaining === 0) resolve4();
1601
2911
  });
1602
2912
  }
1603
2913
  });
@@ -1606,8 +2916,8 @@ var dashboardCommand = defineCommand7({
1606
2916
  });
1607
2917
 
1608
2918
  // src/commands/doctor.ts
1609
- import { defineCommand as defineCommand8 } from "citty";
1610
- var doctorCommand = defineCommand8({
2919
+ import { defineCommand as defineCommand9 } from "citty";
2920
+ var doctorCommand = defineCommand9({
1611
2921
  meta: { name: "doctor", description: "Diagnose your bazilion install" },
1612
2922
  async run() {
1613
2923
  const client = createClient2();
@@ -1688,6 +2998,36 @@ var doctorCommand = defineCommand8({
1688
2998
  ` ${r.scheduler.enabled ? "\u2713" : "-"} scheduler${r.scheduler.enabled ? ` (tick ${r.scheduler.tickMs}ms)` : " (BAZILION_SCHEDULER=off)"}`
1689
2999
  );
1690
3000
  console.log();
3001
+ console.log("agent shell security");
3002
+ if (!r.shellSecurity.ok) {
3003
+ check("valid shell-security configuration", false, r.shellSecurity.error);
3004
+ } else {
3005
+ if (r.shellSecurity.approvalMode === "dangerous") {
3006
+ console.log(" - dangerous-command approval enabled");
3007
+ console.log(
3008
+ " web and TTY CLI turns can allow once; background and non-TTY turns auto-deny"
3009
+ );
3010
+ } else {
3011
+ console.log(" - dangerous-command approval off");
3012
+ console.log(" set BAZILION_BASH_APPROVAL=dangerous to opt in");
3013
+ }
3014
+ if (r.shellSecurity.sandboxMode === "docker") {
3015
+ console.log(` - Docker sandbox configured (${r.shellSecurity.sandboxImage})`);
3016
+ console.log(
3017
+ " workspace-only writable mount \xB7 bounded inputs/skills/memory read-only \xB7 network disabled"
3018
+ );
3019
+ console.log(" host coding tools hidden");
3020
+ console.log(
3021
+ " policy syntax is valid; Docker and image availability are checked on execution"
3022
+ );
3023
+ console.log(" commands fail closed if either is unavailable");
3024
+ } else {
3025
+ console.log(" - sandbox off (agent shell and coding tools run on the host)");
3026
+ console.log(" approval is a host-execution tripwire, not a filesystem sandbox");
3027
+ console.log(" set BAZILION_BASH_SANDBOX=docker to opt in");
3028
+ }
3029
+ }
3030
+ console.log();
1691
3031
  console.log("operational counts");
1692
3032
  console.log(` ${r.triggers.active} active trigger(s), ${r.triggers.disabled} disabled`);
1693
3033
  console.log(` ${r.tokens.active} active web token(s)`);
@@ -1719,7 +3059,7 @@ var doctorCommand = defineCommand8({
1719
3059
  });
1720
3060
 
1721
3061
  // src/commands/inbox.ts
1722
- import { defineCommand as defineCommand9 } from "citty";
3062
+ import { defineCommand as defineCommand10 } from "citty";
1723
3063
  function decodeText(payload) {
1724
3064
  try {
1725
3065
  const obj = JSON.parse(payload);
@@ -1735,7 +3075,7 @@ function messageRow(m) {
1735
3075
  const preview = text2.length > 60 ? `${text2.slice(0, 60)}\u2026` : text2;
1736
3076
  return [m.id, state, when, `from:${m.fromAgentId.slice(0, 8)}`, `"${preview}"`];
1737
3077
  }
1738
- var listCmd3 = defineCommand9({
3078
+ var listCmd3 = defineCommand10({
1739
3079
  meta: { name: "list", description: "List messages in an agent inbox" },
1740
3080
  args: {
1741
3081
  agent: { type: "positional", required: true, description: "Recipient agent id" },
@@ -1754,7 +3094,7 @@ var listCmd3 = defineCommand9({
1754
3094
  for (const line of columnize(messages.map(messageRow))) console.log(line);
1755
3095
  }
1756
3096
  });
1757
- var showCmd2 = defineCommand9({
3097
+ var showCmd2 = defineCommand10({
1758
3098
  meta: { name: "show", description: "Show a single message in full" },
1759
3099
  args: {
1760
3100
  id: { type: "positional", required: true, description: "Message id" }
@@ -1774,7 +3114,7 @@ var showCmd2 = defineCommand9({
1774
3114
  console.log(decodeText(m.payload));
1775
3115
  }
1776
3116
  });
1777
- var readCmd = defineCommand9({
3117
+ var readCmd = defineCommand10({
1778
3118
  meta: { name: "read", description: "Mark a message as read" },
1779
3119
  args: {
1780
3120
  id: { type: "positional", required: true, description: "Message id" }
@@ -1785,18 +3125,47 @@ var readCmd = defineCommand9({
1785
3125
  console.log(`marked read: ${args.id}`);
1786
3126
  }
1787
3127
  });
1788
- var inboxCommand = defineCommand9({
3128
+ var loopBreaksCmd = defineCommand10({
3129
+ meta: { name: "loop-breaks", description: "List stopped agent-message chains" },
3130
+ args: {
3131
+ agent: { type: "positional", required: true, description: "Agent id" },
3132
+ limit: { type: "string", description: "Maximum events to show (default 25)" }
3133
+ },
3134
+ async run({ args }) {
3135
+ const client = createClient2();
3136
+ const limit = args.limit ? `?limit=${encodeURIComponent(args.limit)}` : "";
3137
+ const { events } = await client.get(
3138
+ `/api/agents/${args.agent}/loop-breaks${limit}`
3139
+ );
3140
+ if (events.length === 0) {
3141
+ console.log("(no agent-message chains stopped)");
3142
+ return;
3143
+ }
3144
+ for (const line of columnize(
3145
+ events.map((event) => [
3146
+ new Date(event.createdAt).toISOString(),
3147
+ `chain:${event.causalChainId.slice(0, 8)}`,
3148
+ `${event.fromAgentId.slice(0, 8)}\u2192${event.toAgentId.slice(0, 8)}`,
3149
+ `hop:${event.attemptedHop}/${event.maxHops}`,
3150
+ event.reason
3151
+ ])
3152
+ ))
3153
+ console.log(line);
3154
+ }
3155
+ });
3156
+ var inboxCommand = defineCommand10({
1789
3157
  meta: { name: "inbox", description: "Inspect agent inboxes" },
1790
3158
  subCommands: {
1791
3159
  list: listCmd3,
1792
3160
  show: showCmd2,
1793
- read: readCmd
3161
+ read: readCmd,
3162
+ "loop-breaks": loopBreaksCmd
1794
3163
  }
1795
3164
  });
1796
3165
 
1797
3166
  // src/commands/login.ts
1798
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1799
- import { defineCommand as defineCommand10 } from "citty";
3167
+ import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
3168
+ import { defineCommand as defineCommand11 } from "citty";
1800
3169
  function normalizeServer(raw) {
1801
3170
  let url;
1802
3171
  try {
@@ -1816,7 +3185,7 @@ async function verifyToken(server, token) {
1816
3185
  if (res.status === 401) throw new Error("server rejected token (401)");
1817
3186
  if (!res.ok) throw new Error(`server returned ${res.status} from /api/health`);
1818
3187
  }
1819
- var loginCommand = defineCommand10({
3188
+ var loginCommand = defineCommand11({
1820
3189
  meta: {
1821
3190
  name: "login",
1822
3191
  description: "Save a remote bazilion server + token pair to auth.json"
@@ -1834,7 +3203,7 @@ var loginCommand = defineCommand10({
1834
3203
  `${paths.authFile} not found. Start the daemon with "bazilion serve" first \u2014 login stores the remote there.`
1835
3204
  );
1836
3205
  }
1837
- const auth = JSON.parse(readFileSync3(paths.authFile, "utf8"));
3206
+ const auth = JSON.parse(readFileSync5(paths.authFile, "utf8"));
1838
3207
  if (args.clear) {
1839
3208
  if (!auth.remote) {
1840
3209
  console.log("no remote stored \u2014 nothing to clear");
@@ -1864,7 +3233,7 @@ var loginCommand = defineCommand10({
1864
3233
  });
1865
3234
 
1866
3235
  // src/commands/mcp.ts
1867
- import { defineCommand as defineCommand11 } from "citty";
3236
+ import { defineCommand as defineCommand12 } from "citty";
1868
3237
  function serverRow(s) {
1869
3238
  const where = s.transport === "stdio" ? `${s.command} ${s.args.join(" ")}`.trim() : s.url ?? "";
1870
3239
  return [
@@ -1876,7 +3245,7 @@ function serverRow(s) {
1876
3245
  where
1877
3246
  ];
1878
3247
  }
1879
- var addCmd = defineCommand11({
3248
+ var addCmd = defineCommand12({
1880
3249
  meta: { name: "add", description: "Register an MCP server" },
1881
3250
  args: {
1882
3251
  name: { type: "positional", required: true, description: "Unique name ([a-zA-Z0-9_])" },
@@ -1903,7 +3272,7 @@ var addCmd = defineCommand11({
1903
3272
  console.log(`${server.id} ${server.name} ${server.transport}`);
1904
3273
  }
1905
3274
  });
1906
- var listCmd4 = defineCommand11({
3275
+ var listCmd4 = defineCommand12({
1907
3276
  meta: { name: "list", description: "List MCP servers" },
1908
3277
  async run() {
1909
3278
  const client = createClient2();
@@ -1915,7 +3284,7 @@ var listCmd4 = defineCommand11({
1915
3284
  for (const line of columnize(servers.map(serverRow))) console.log(line);
1916
3285
  }
1917
3286
  });
1918
- var showCmd3 = defineCommand11({
3287
+ var showCmd3 = defineCommand12({
1919
3288
  meta: { name: "show", description: "Show one MCP server" },
1920
3289
  args: { id: { type: "positional", required: true } },
1921
3290
  async run({ args }) {
@@ -1924,7 +3293,7 @@ var showCmd3 = defineCommand11({
1924
3293
  console.log(JSON.stringify(server, null, 2));
1925
3294
  }
1926
3295
  });
1927
- var rmCmd2 = defineCommand11({
3296
+ var rmCmd2 = defineCommand12({
1928
3297
  meta: { name: "rm", description: "Delete an MCP server" },
1929
3298
  args: { id: { type: "positional", required: true } },
1930
3299
  async run({ args }) {
@@ -1933,7 +3302,7 @@ var rmCmd2 = defineCommand11({
1933
3302
  console.log(`removed MCP server ${args.id}`);
1934
3303
  }
1935
3304
  });
1936
- var enableCmd = defineCommand11({
3305
+ var enableCmd = defineCommand12({
1937
3306
  meta: { name: "enable", description: "Enable an MCP server" },
1938
3307
  args: { id: { type: "positional", required: true } },
1939
3308
  async run({ args }) {
@@ -1942,7 +3311,7 @@ var enableCmd = defineCommand11({
1942
3311
  console.log(`enabled MCP server ${args.id}`);
1943
3312
  }
1944
3313
  });
1945
- var disableCmd = defineCommand11({
3314
+ var disableCmd = defineCommand12({
1946
3315
  meta: { name: "disable", description: "Disable an MCP server" },
1947
3316
  args: { id: { type: "positional", required: true } },
1948
3317
  async run({ args }) {
@@ -1951,7 +3320,7 @@ var disableCmd = defineCommand11({
1951
3320
  console.log(`disabled MCP server ${args.id}`);
1952
3321
  }
1953
3322
  });
1954
- var testCmd = defineCommand11({
3323
+ var testCmd = defineCommand12({
1955
3324
  meta: { name: "test", description: "Connect to a server and list its tools" },
1956
3325
  args: { id: { type: "positional", required: true } },
1957
3326
  async run({ args }) {
@@ -1969,7 +3338,7 @@ var testCmd = defineCommand11({
1969
3338
  for (const t of tools) console.log(` ${t.name} ${t.description}`);
1970
3339
  }
1971
3340
  });
1972
- var mcpCommand = defineCommand11({
3341
+ var mcpCommand = defineCommand12({
1973
3342
  meta: { name: "mcp", description: "Manage MCP servers" },
1974
3343
  subCommands: {
1975
3344
  add: addCmd,
@@ -1983,11 +3352,11 @@ var mcpCommand = defineCommand11({
1983
3352
  });
1984
3353
 
1985
3354
  // src/commands/memory.ts
1986
- import { defineCommand as defineCommand12 } from "citty";
3355
+ import { defineCommand as defineCommand13 } from "citty";
1987
3356
  function encodeKey(key) {
1988
3357
  return key.split("/").map(encodeURIComponent).join("/");
1989
3358
  }
1990
- var writeCmd = defineCommand12({
3359
+ var writeCmd = defineCommand13({
1991
3360
  meta: { name: "write", description: "Write an entry into a team's shared memory" },
1992
3361
  args: {
1993
3362
  team: { type: "positional", required: true, description: "Team slug" },
@@ -2003,7 +3372,7 @@ var writeCmd = defineCommand12({
2003
3372
  console.log(`wrote ${entry2.key} (${entry2.content.length} bytes)`);
2004
3373
  }
2005
3374
  });
2006
- var readCmd2 = defineCommand12({
3375
+ var readCmd2 = defineCommand13({
2007
3376
  meta: { name: "read", description: "Read an entry from a team's shared memory" },
2008
3377
  args: {
2009
3378
  team: { type: "positional", required: true, description: "Team slug" },
@@ -2017,7 +3386,7 @@ var readCmd2 = defineCommand12({
2017
3386
  console.log(entry2.content);
2018
3387
  }
2019
3388
  });
2020
- var searchCmd = defineCommand12({
3389
+ var searchCmd = defineCommand13({
2021
3390
  meta: { name: "search", description: "Search a team's shared memory" },
2022
3391
  args: {
2023
3392
  team: { type: "positional", required: true, description: "Team slug" },
@@ -2038,7 +3407,7 @@ var searchCmd = defineCommand12({
2038
3407
  }
2039
3408
  }
2040
3409
  });
2041
- var listCmd5 = defineCommand12({
3410
+ var listCmd5 = defineCommand13({
2042
3411
  meta: { name: "list", description: "List all entries in a team's shared memory" },
2043
3412
  args: {
2044
3413
  team: { type: "positional", required: true, description: "Team slug" }
@@ -2055,7 +3424,7 @@ var listCmd5 = defineCommand12({
2055
3424
  }
2056
3425
  }
2057
3426
  });
2058
- var rmCmd3 = defineCommand12({
3427
+ var rmCmd3 = defineCommand13({
2059
3428
  meta: { name: "rm", description: "Remove an entry from a team's shared memory" },
2060
3429
  args: {
2061
3430
  team: { type: "positional", required: true, description: "Team slug" },
@@ -2067,7 +3436,7 @@ var rmCmd3 = defineCommand12({
2067
3436
  console.log(`removed ${args.key}`);
2068
3437
  }
2069
3438
  });
2070
- var memoryCommand = defineCommand12({
3439
+ var memoryCommand = defineCommand13({
2071
3440
  meta: {
2072
3441
  name: "memory",
2073
3442
  description: "Manage a team's shared memory (BM25-indexed markdown notes)"
@@ -2082,11 +3451,11 @@ var memoryCommand = defineCommand12({
2082
3451
  });
2083
3452
 
2084
3453
  // src/commands/profile.ts
2085
- import { spawn as spawn3 } from "child_process";
3454
+ import { spawn as spawn2 } from "child_process";
2086
3455
  import { randomBytes } from "crypto";
2087
- import { readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
2088
- import { tmpdir } from "os";
2089
- import { join as join3 } from "path";
3456
+ import { readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
3457
+ import { tmpdir as tmpdir2 } from "os";
3458
+ import { join as join4 } from "path";
2090
3459
 
2091
3460
  // ../../packages/api-types/src/index.ts
2092
3461
  var PROFILE_FILES = [
@@ -2099,7 +3468,7 @@ var PROFILE_FILES = [
2099
3468
  ];
2100
3469
 
2101
3470
  // src/commands/profile.ts
2102
- import { defineCommand as defineCommand13 } from "citty";
3471
+ import { defineCommand as defineCommand14 } from "citty";
2103
3472
  function splitCsv(v) {
2104
3473
  if (!v) return void 0;
2105
3474
  const out = v.split(",").map((s) => s.trim()).filter(Boolean);
@@ -2110,7 +3479,7 @@ function parseSkillsMode(v) {
2110
3479
  if (v === "all" || v === "selected") return v;
2111
3480
  throw new Error(`--skills-mode must be 'all' or 'selected', got '${v}'`);
2112
3481
  }
2113
- var createCmd2 = defineCommand13({
3482
+ var createCmd2 = defineCommand14({
2114
3483
  meta: { name: "create", description: "Create a new profile" },
2115
3484
  args: {
2116
3485
  id: { type: "positional", required: true, description: "Profile slug" },
@@ -2155,7 +3524,7 @@ var createCmd2 = defineCommand13({
2155
3524
  },
2156
3525
  async run({ args }) {
2157
3526
  const skillsMode = parseSkillsMode(args["skills-mode"]);
2158
- const readTemplate = (path) => path ? readFileSync4(path, "utf8") : void 0;
3527
+ const readTemplate = (path) => path ? readFileSync6(path, "utf8") : void 0;
2159
3528
  const body = {
2160
3529
  id: args.id,
2161
3530
  name: args.name,
@@ -2173,20 +3542,20 @@ var createCmd2 = defineCommand13({
2173
3542
  console.log(`created profile ${profile.id} at ${profile.dir}`);
2174
3543
  }
2175
3544
  });
2176
- var listCmd6 = defineCommand13({
3545
+ var listCmd6 = defineCommand14({
2177
3546
  meta: { name: "list", description: "List profiles" },
2178
3547
  async run() {
2179
3548
  const client = createClient2();
2180
- const list3 = await client.get("/api/profiles");
2181
- if (list3.length === 0) {
3549
+ const list5 = await client.get("/api/profiles");
3550
+ if (list5.length === 0) {
2182
3551
  console.log("(no profiles)");
2183
3552
  return;
2184
3553
  }
2185
- const rows = list3.map((p) => [p.id, p.defaultModel, p.name]);
3554
+ const rows = list5.map((p) => [p.id, p.defaultModel, p.name]);
2186
3555
  for (const line of columnize(rows)) console.log(line);
2187
3556
  }
2188
3557
  });
2189
- var showCmd4 = defineCommand13({
3558
+ var showCmd4 = defineCommand14({
2190
3559
  meta: { name: "show", description: "Show profile details" },
2191
3560
  args: {
2192
3561
  id: { type: "positional", required: true }
@@ -2211,7 +3580,7 @@ var showCmd4 = defineCommand13({
2211
3580
  }
2212
3581
  }
2213
3582
  });
2214
- var editCmd2 = defineCommand13({
3583
+ var editCmd2 = defineCommand14({
2215
3584
  meta: { name: "edit", description: "Open a profile file in $EDITOR" },
2216
3585
  args: {
2217
3586
  id: { type: "positional", required: true },
@@ -2230,17 +3599,17 @@ var editCmd2 = defineCommand13({
2230
3599
  const { content } = await client.get(
2231
3600
  `/api/profiles/${args.id}/files/${file}`
2232
3601
  );
2233
- const tmpPath = join3(tmpdir(), `bazilion-${args.id}-${file}-${randomBytes(4).toString("hex")}`);
3602
+ const tmpPath = join4(tmpdir2(), `bazilion-${args.id}-${file}-${randomBytes(4).toString("hex")}`);
2234
3603
  writeFileSync3(tmpPath, content);
2235
3604
  try {
2236
3605
  const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
2237
- const code = await new Promise((resolve3, reject) => {
2238
- const child = spawn3(editor, [tmpPath], { stdio: "inherit" });
3606
+ const code = await new Promise((resolve4, reject) => {
3607
+ const child = spawn2(editor, [tmpPath], { stdio: "inherit" });
2239
3608
  child.on("error", reject);
2240
- child.on("close", (c) => resolve3(c ?? 0));
3609
+ child.on("close", (c) => resolve4(c ?? 0));
2241
3610
  });
2242
3611
  if (code !== 0) throw new Error(`editor exited with code ${code}`);
2243
- const updated = readFileSync4(tmpPath, "utf8");
3612
+ const updated = readFileSync6(tmpPath, "utf8");
2244
3613
  if (updated === content) {
2245
3614
  console.log("(no changes)");
2246
3615
  return;
@@ -2250,13 +3619,13 @@ var editCmd2 = defineCommand13({
2250
3619
  console.log(`saved ${file}`);
2251
3620
  } finally {
2252
3621
  try {
2253
- rmSync2(tmpPath, { force: true });
3622
+ rmSync3(tmpPath, { force: true });
2254
3623
  } catch {
2255
3624
  }
2256
3625
  }
2257
3626
  }
2258
3627
  });
2259
- var updateCmd = defineCommand13({
3628
+ var updateCmd = defineCommand14({
2260
3629
  meta: {
2261
3630
  name: "update",
2262
3631
  description: "Update profile settings (name, model, skills-mode, skills)"
@@ -2290,7 +3659,7 @@ var updateCmd = defineCommand13({
2290
3659
  console.log(`updated profile ${profile.id}`);
2291
3660
  }
2292
3661
  });
2293
- var deleteCmd2 = defineCommand13({
3662
+ var deleteCmd2 = defineCommand14({
2294
3663
  meta: { name: "delete", description: "Permanently delete a profile and its files" },
2295
3664
  args: {
2296
3665
  id: { type: "positional", required: true }
@@ -2301,7 +3670,7 @@ var deleteCmd2 = defineCommand13({
2301
3670
  console.log(`deleted profile ${args.id}`);
2302
3671
  }
2303
3672
  });
2304
- var profileCommand = defineCommand13({
3673
+ var profileCommand = defineCommand14({
2305
3674
  meta: { name: "profile", description: "Manage profiles" },
2306
3675
  subCommands: {
2307
3676
  create: createCmd2,
@@ -2314,8 +3683,8 @@ var profileCommand = defineCommand13({
2314
3683
  });
2315
3684
 
2316
3685
  // src/commands/provider.ts
2317
- import { defineCommand as defineCommand14 } from "citty";
2318
- var listCmd7 = defineCommand14({
3686
+ import { defineCommand as defineCommand15 } from "citty";
3687
+ var listCmd7 = defineCommand15({
2319
3688
  meta: {
2320
3689
  name: "list",
2321
3690
  description: "List all providers with enabled/disabled state and curated model counts"
@@ -2341,7 +3710,7 @@ var listCmd7 = defineCommand14({
2341
3710
  for (const line of columnize(rows)) console.log(line);
2342
3711
  }
2343
3712
  });
2344
- var modelsCmd = defineCommand14({
3713
+ var modelsCmd = defineCommand15({
2345
3714
  meta: {
2346
3715
  name: "models",
2347
3716
  description: "Show curated + catalog + live models for one provider"
@@ -2373,7 +3742,7 @@ var modelsCmd = defineCommand14({
2373
3742
  }
2374
3743
  }
2375
3744
  });
2376
- var modelsSetCmd = defineCommand14({
3745
+ var modelsSetCmd = defineCommand15({
2377
3746
  meta: {
2378
3747
  name: "models-set",
2379
3748
  description: "Replace the curated model list for a provider (comma-separated)"
@@ -2398,7 +3767,7 @@ var modelsSetCmd = defineCommand14({
2398
3767
  for (const m of saved.models) console.log(` ${m}`);
2399
3768
  }
2400
3769
  });
2401
- var enableCmd2 = defineCommand14({
3770
+ var enableCmd2 = defineCommand15({
2402
3771
  meta: { name: "enable", description: "Toggle a provider on so agents can use it" },
2403
3772
  args: {
2404
3773
  name: { type: "positional", required: true, description: "Provider id (e.g. anthropic)" }
@@ -2412,7 +3781,7 @@ var enableCmd2 = defineCommand14({
2412
3781
  console.log(`${res.name}: enabled`);
2413
3782
  }
2414
3783
  });
2415
- var disableCmd2 = defineCommand14({
3784
+ var disableCmd2 = defineCommand15({
2416
3785
  meta: {
2417
3786
  name: "disable",
2418
3787
  description: "Toggle a provider off \u2014 agents will refuse its model strings"
@@ -2429,7 +3798,7 @@ var disableCmd2 = defineCommand14({
2429
3798
  console.log(`${res.name}: disabled`);
2430
3799
  }
2431
3800
  });
2432
- var testCmd2 = defineCommand14({
3801
+ var testCmd2 = defineCommand15({
2433
3802
  meta: { name: "test", description: "Send a single chat to a model and print the reply" },
2434
3803
  args: {
2435
3804
  model: {
@@ -2450,7 +3819,7 @@ var testCmd2 = defineCommand14({
2450
3819
  }
2451
3820
  }
2452
3821
  });
2453
- var providerCommand = defineCommand14({
3822
+ var providerCommand = defineCommand15({
2454
3823
  meta: { name: "provider", description: "Manage and test LLM providers" },
2455
3824
  subCommands: {
2456
3825
  list: listCmd7,
@@ -2463,8 +3832,8 @@ var providerCommand = defineCommand14({
2463
3832
  });
2464
3833
 
2465
3834
  // src/commands/send.ts
2466
- import { defineCommand as defineCommand15 } from "citty";
2467
- var sendCommand = defineCommand15({
3835
+ import { defineCommand as defineCommand16 } from "citty";
3836
+ var sendCommand = defineCommand16({
2468
3837
  meta: {
2469
3838
  name: "send",
2470
3839
  description: "Send a message from one agent to another"
@@ -2495,14 +3864,14 @@ var sendCommand = defineCommand15({
2495
3864
  });
2496
3865
 
2497
3866
  // src/commands/serve.ts
2498
- import { spawn as spawn4 } from "child_process";
3867
+ import { spawn as spawn3 } from "child_process";
2499
3868
  import { existsSync as existsSync5 } from "fs";
2500
- import { join as join4 } from "path";
2501
- import { defineCommand as defineCommand16 } from "citty";
2502
- var bundledDaemonEntry2 = join4(import.meta.dirname, "daemon.js");
2503
- var sourceDaemonEntry2 = join4(import.meta.dirname, "..", "..", "..", "daemon", "src", "index.ts");
3869
+ import { join as join5 } from "path";
3870
+ import { defineCommand as defineCommand17 } from "citty";
3871
+ var bundledDaemonEntry2 = join5(import.meta.dirname, "daemon.js");
3872
+ var sourceDaemonEntry2 = join5(import.meta.dirname, "..", "..", "..", "daemon", "src", "index.ts");
2504
3873
  var daemonEntry2 = existsSync5(bundledDaemonEntry2) ? bundledDaemonEntry2 : sourceDaemonEntry2;
2505
- var serveCommand = defineCommand16({
3874
+ var serveCommand = defineCommand17({
2506
3875
  meta: {
2507
3876
  name: "serve",
2508
3877
  description: "Start the bazilion daemon (HTTP API)"
@@ -2535,7 +3904,7 @@ var serveCommand = defineCommand16({
2535
3904
  console.log(`starting bazilion daemon at http://${host}:${port}`);
2536
3905
  console.log("(web UI runs separately: cd apps/web && pnpm dev)");
2537
3906
  const nodeArgs2 = daemonEntry2.endsWith(".ts") ? ["--import", "tsx/esm", daemonEntry2] : [daemonEntry2];
2538
- const proc = spawn4("node", nodeArgs2, {
3907
+ const proc = spawn3("node", nodeArgs2, {
2539
3908
  env,
2540
3909
  stdio: "inherit"
2541
3910
  });
@@ -2551,17 +3920,17 @@ var serveCommand = defineCommand16({
2551
3920
  signalled = true;
2552
3921
  });
2553
3922
  process.on("SIGTERM", onSigterm);
2554
- await new Promise((resolve3, reject) => {
3923
+ await new Promise((resolve4, reject) => {
2555
3924
  proc.on("error", (err) => reject(err));
2556
3925
  proc.on("close", (code) => {
2557
3926
  if (signalled) {
2558
3927
  process.exitCode = 0;
2559
- resolve3();
3928
+ resolve4();
2560
3929
  } else if (code === 0 || code === null) {
2561
- resolve3();
3930
+ resolve4();
2562
3931
  } else {
2563
3932
  process.exitCode = code;
2564
- resolve3();
3933
+ resolve4();
2565
3934
  }
2566
3935
  });
2567
3936
  });
@@ -2569,16 +3938,16 @@ var serveCommand = defineCommand16({
2569
3938
  });
2570
3939
 
2571
3940
  // src/commands/skill.ts
2572
- import { existsSync as existsSync6, readFileSync as readFileSync5, statSync } from "fs";
2573
- import { basename as basename2, resolve as resolve2 } from "path";
2574
- import { defineCommand as defineCommand17 } from "citty";
3941
+ import { existsSync as existsSync6, readFileSync as readFileSync7, statSync } from "fs";
3942
+ import { basename as basename4, resolve as resolve3 } from "path";
3943
+ import { defineCommand as defineCommand18 } from "citty";
2575
3944
  function printFindings(findings, prefix = " ") {
2576
3945
  for (const f of findings) {
2577
3946
  const line = f.line ? ` line ${f.line}` : "";
2578
3947
  console.log(`${prefix}${f.severity}: ${f.code}${line} - ${f.message}`);
2579
3948
  }
2580
3949
  }
2581
- var listCmd8 = defineCommand17({
3950
+ var listCmd8 = defineCommand18({
2582
3951
  meta: { name: "list", description: "List installed skills (or those attached to an agent)" },
2583
3952
  args: {
2584
3953
  agent: { type: "string", description: "Filter to skills attached to this agent" }
@@ -2619,7 +3988,7 @@ var listCmd8 = defineCommand17({
2619
3988
  }
2620
3989
  }
2621
3990
  });
2622
- var importCmd = defineCommand17({
3991
+ var importCmd = defineCommand18({
2623
3992
  meta: {
2624
3993
  name: "import",
2625
3994
  description: "Import skills from openclaw, a directory, or a local .zip archive"
@@ -2634,13 +4003,13 @@ var importCmd = defineCommand17({
2634
4003
  },
2635
4004
  async run({ args }) {
2636
4005
  const client = createClient2();
2637
- const absFrom = resolve2(args.from);
4006
+ const absFrom = resolve3(args.from);
2638
4007
  const isLocalZip = args.from.toLowerCase().endsWith(".zip") && existsSync6(absFrom) && statSync(absFrom).isFile();
2639
4008
  let result;
2640
4009
  try {
2641
4010
  if (isLocalZip) {
2642
- const bytes = readFileSync5(absFrom);
2643
- const file = new File([bytes], basename2(absFrom), { type: "application/zip" });
4011
+ const bytes = readFileSync7(absFrom);
4012
+ const file = new File([bytes], basename4(absFrom), { type: "application/zip" });
2644
4013
  const fd = new FormData();
2645
4014
  fd.set("file", file);
2646
4015
  if (args.force) fd.set("force", "true");
@@ -2688,7 +4057,7 @@ var importCmd = defineCommand17({
2688
4057
  }
2689
4058
  }
2690
4059
  });
2691
- var rmCmd4 = defineCommand17({
4060
+ var rmCmd4 = defineCommand18({
2692
4061
  meta: { name: "rm", description: "Remove an installed skill" },
2693
4062
  args: {
2694
4063
  name: { type: "positional", required: true }
@@ -2699,7 +4068,7 @@ var rmCmd4 = defineCommand17({
2699
4068
  console.log(`removed skill ${args.name}`);
2700
4069
  }
2701
4070
  });
2702
- var skillCommand = defineCommand17({
4071
+ var skillCommand = defineCommand18({
2703
4072
  meta: { name: "skill", description: "Manage the skill library" },
2704
4073
  subCommands: {
2705
4074
  list: listCmd8,
@@ -2709,13 +4078,13 @@ var skillCommand = defineCommand17({
2709
4078
  });
2710
4079
 
2711
4080
  // src/commands/team.ts
2712
- import { readFileSync as readFileSync7 } from "fs";
4081
+ import { readFileSync as readFileSync9 } from "fs";
2713
4082
  import { stdin as stdin2 } from "process";
2714
- import { defineCommand as defineCommand19 } from "citty";
4083
+ import { defineCommand as defineCommand20 } from "citty";
2715
4084
 
2716
4085
  // src/commands/team-policy.ts
2717
- import { readFileSync as readFileSync6 } from "fs";
2718
- import { defineCommand as defineCommand18 } from "citty";
4086
+ import { readFileSync as readFileSync8 } from "fs";
4087
+ import { defineCommand as defineCommand19 } from "citty";
2719
4088
 
2720
4089
  // src/team-interchange.ts
2721
4090
  var TEAM_POLICY_INTERCHANGE_VERSION = 1;
@@ -2931,7 +4300,7 @@ function edgeDiff(before, after) {
2931
4300
  // src/commands/team-policy.ts
2932
4301
  function readJson(path) {
2933
4302
  try {
2934
- return JSON.parse(readFileSync6(path, "utf8"));
4303
+ return JSON.parse(readFileSync8(path, "utf8"));
2935
4304
  } catch (error) {
2936
4305
  throw new Error(`invalid JSON: ${error.message}`);
2937
4306
  }
@@ -2963,7 +4332,7 @@ function printPolicy(detail) {
2963
4332
  console.log(line);
2964
4333
  }
2965
4334
  }
2966
- var show2 = defineCommand18({
4335
+ var show2 = defineCommand19({
2967
4336
  meta: { name: "show", description: "Show the one effective live policy for a Team" },
2968
4337
  args: {
2969
4338
  team: { type: "positional", required: true },
@@ -2977,7 +4346,7 @@ var show2 = defineCommand18({
2977
4346
  else printPolicy(detail);
2978
4347
  }
2979
4348
  });
2980
- var exportCommand = defineCommand18({
4349
+ var exportCommand = defineCommand19({
2981
4350
  meta: { name: "export", description: "Export a revisioned Team policy document" },
2982
4351
  args: { team: { type: "positional", required: true } },
2983
4352
  async run({ args }) {
@@ -3001,7 +4370,7 @@ function printEdgeDiff(current, edges) {
3001
4370
  for (const value of diff2.added) console.log(` + ${value}`);
3002
4371
  for (const value of diff2.removed) console.log(` - ${value}`);
3003
4372
  }
3004
- var importCommand = defineCommand18({
4373
+ var importCommand = defineCommand19({
3005
4374
  meta: {
3006
4375
  name: "import",
3007
4376
  description: "Validate, diff, and explicitly replace a Team policy"
@@ -3056,7 +4425,7 @@ var importCommand = defineCommand18({
3056
4425
  console.log(`updated Team ${args.team} policy to revision ${updated.teamPolicy.revision}`);
3057
4426
  }
3058
4427
  });
3059
- var diff = defineCommand18({
4428
+ var diff = defineCommand19({
3060
4429
  meta: { name: "diff", description: "Compare live policy with its retained baseline source" },
3061
4430
  args: {
3062
4431
  team: { type: "positional", required: true },
@@ -3080,7 +4449,7 @@ var diff = defineCommand18({
3080
4449
  );
3081
4450
  }
3082
4451
  });
3083
- var evaluate = defineCommand18({
4452
+ var evaluate = defineCommand19({
3084
4453
  meta: {
3085
4454
  name: "evaluate",
3086
4455
  description: "Evaluate a communication path without sending or recording a block"
@@ -3111,7 +4480,7 @@ var evaluate = defineCommand18({
3111
4480
  );
3112
4481
  }
3113
4482
  });
3114
- var blocks = defineCommand18({
4483
+ var blocks = defineCommand19({
3115
4484
  meta: { name: "blocks", description: "List paginated durable communication denials" },
3116
4485
  args: {
3117
4486
  team: { type: "positional", required: true },
@@ -3160,7 +4529,7 @@ var blocks = defineCommand18({
3160
4529
  if (page.nextCursor) console.log(`next cursor: ${page.nextCursor}`);
3161
4530
  }
3162
4531
  });
3163
- var teamPolicyCommand = defineCommand18({
4532
+ var teamPolicyCommand = defineCommand19({
3164
4533
  meta: { name: "policy", description: "Inspect and manage the Team-owned live policy" },
3165
4534
  subCommands: {
3166
4535
  show: show2,
@@ -3173,7 +4542,7 @@ var teamPolicyCommand = defineCommand18({
3173
4542
  });
3174
4543
 
3175
4544
  // src/commands/team.ts
3176
- var addCmd2 = defineCommand19({
4545
+ var addCmd2 = defineCommand20({
3177
4546
  meta: {
3178
4547
  name: "add",
3179
4548
  description: "Register a team at ~/.bazilion/teams/<slug>/ (use --link to point at an existing tree)"
@@ -3197,20 +4566,20 @@ var addCmd2 = defineCommand19({
3197
4566
  console.log(`registered team ${g.id} at ${g.path}`);
3198
4567
  }
3199
4568
  });
3200
- var listCmd9 = defineCommand19({
4569
+ var listCmd9 = defineCommand20({
3201
4570
  meta: { name: "list", description: "List teams" },
3202
4571
  async run() {
3203
4572
  const client = createClient2();
3204
- const list3 = await client.get("/api/teams");
3205
- if (list3.length === 0) {
4573
+ const list5 = await client.get("/api/teams");
4574
+ if (list5.length === 0) {
3206
4575
  console.log("(no teams)");
3207
4576
  return;
3208
4577
  }
3209
- const rows = list3.map((g) => [g.id, g.path]);
4578
+ const rows = list5.map((g) => [g.id, g.path]);
3210
4579
  for (const line of columnize(rows)) console.log(line);
3211
4580
  }
3212
4581
  });
3213
- var rmCmd5 = defineCommand19({
4582
+ var rmCmd5 = defineCommand20({
3214
4583
  meta: { name: "rm", description: "Remove a team registration" },
3215
4584
  args: {
3216
4585
  id: { type: "positional", required: true }
@@ -3231,7 +4600,7 @@ async function readStdin() {
3231
4600
  for await (const chunk of stdin2) chunks.push(chunk);
3232
4601
  return Buffer.concat(chunks).toString("utf8");
3233
4602
  }
3234
- var userMdShowCmd = defineCommand19({
4603
+ var userMdShowCmd = defineCommand20({
3235
4604
  meta: { name: "show", description: "Print the team's USER.md" },
3236
4605
  args: { id: { type: "positional", required: true } },
3237
4606
  async run({ args }) {
@@ -3241,7 +4610,7 @@ var userMdShowCmd = defineCommand19({
3241
4610
  if (g.userMd && !g.userMd.endsWith("\n")) process.stdout.write("\n");
3242
4611
  }
3243
4612
  });
3244
- var userMdSetCmd = defineCommand19({
4613
+ var userMdSetCmd = defineCommand20({
3245
4614
  meta: {
3246
4615
  name: "set",
3247
4616
  description: "Replace the team's USER.md (from --file, --from-stdin, or inline text)"
@@ -3255,7 +4624,7 @@ var userMdSetCmd = defineCommand19({
3255
4624
  async run({ args }) {
3256
4625
  let content;
3257
4626
  if (args["from-stdin"]) content = await readStdin();
3258
- else if (args.file) content = readFileSync7(args.file, "utf8");
4627
+ else if (args.file) content = readFileSync9(args.file, "utf8");
3259
4628
  else if (args.text !== void 0) content = args.text;
3260
4629
  else {
3261
4630
  console.error("team user-md set: provide --file, --from-stdin, or --text");
@@ -3267,7 +4636,7 @@ var userMdSetCmd = defineCommand19({
3267
4636
  console.log(`updated USER.md for team ${args.id} (${content.length} bytes)`);
3268
4637
  }
3269
4638
  });
3270
- var userMdClearCmd = defineCommand19({
4639
+ var userMdClearCmd = defineCommand20({
3271
4640
  meta: { name: "clear", description: "Clear the team's USER.md to empty" },
3272
4641
  args: { id: { type: "positional", required: true } },
3273
4642
  async run({ args }) {
@@ -3277,7 +4646,7 @@ var userMdClearCmd = defineCommand19({
3277
4646
  console.log(`cleared USER.md for team ${args.id}`);
3278
4647
  }
3279
4648
  });
3280
- var userMdCmd = defineCommand19({
4649
+ var userMdCmd = defineCommand20({
3281
4650
  meta: { name: "user-md", description: "View or edit a team's USER.md" },
3282
4651
  subCommands: {
3283
4652
  show: userMdShowCmd,
@@ -3285,7 +4654,7 @@ var userMdCmd = defineCommand19({
3285
4654
  clear: userMdClearCmd
3286
4655
  }
3287
4656
  });
3288
- var topicFormatShowCmd = defineCommand19({
4657
+ var topicFormatShowCmd = defineCommand20({
3289
4658
  meta: { name: "show", description: "Print the team's Telegram topic-name template" },
3290
4659
  args: { id: { type: "positional", required: true } },
3291
4660
  async run({ args }) {
@@ -3294,7 +4663,7 @@ var topicFormatShowCmd = defineCommand19({
3294
4663
  console.log(g.telegramTopicNameFormat ?? "(default naming)");
3295
4664
  }
3296
4665
  });
3297
- var topicFormatSetCmd = defineCommand19({
4666
+ var topicFormatSetCmd = defineCommand20({
3298
4667
  meta: {
3299
4668
  name: "set",
3300
4669
  description: "Set the topic-name template. Tokens: {agent.name} {team.name} {team.slug} (must include {agent.name})"
@@ -3314,7 +4683,7 @@ var topicFormatSetCmd = defineCommand19({
3314
4683
  console.log(`set topic-name template for team ${args.id}: ${g.telegramTopicNameFormat}`);
3315
4684
  }
3316
4685
  });
3317
- var topicFormatClearCmd = defineCommand19({
4686
+ var topicFormatClearCmd = defineCommand20({
3318
4687
  meta: { name: "clear", description: "Clear the template (revert to built-in naming)" },
3319
4688
  args: { id: { type: "positional", required: true } },
3320
4689
  async run({ args }) {
@@ -3324,7 +4693,7 @@ var topicFormatClearCmd = defineCommand19({
3324
4693
  console.log(`cleared topic-name template for team ${args.id}`);
3325
4694
  }
3326
4695
  });
3327
- var topicFormatCmd = defineCommand19({
4696
+ var topicFormatCmd = defineCommand20({
3328
4697
  meta: {
3329
4698
  name: "topic-format",
3330
4699
  description: "View or edit a team's Telegram forum-topic name template"
@@ -3335,7 +4704,7 @@ var topicFormatCmd = defineCommand19({
3335
4704
  clear: topicFormatClearCmd
3336
4705
  }
3337
4706
  });
3338
- var teamCommand = defineCommand19({
4707
+ var teamCommand = defineCommand20({
3339
4708
  meta: { name: "team", description: "Manage teams (collaboration contexts)" },
3340
4709
  subCommands: {
3341
4710
  add: addCmd2,
@@ -3348,11 +4717,11 @@ var teamCommand = defineCommand19({
3348
4717
  });
3349
4718
 
3350
4719
  // src/commands/team-template.ts
3351
- import { readFileSync as readFileSync8 } from "fs";
3352
- import { defineCommand as defineCommand20 } from "citty";
4720
+ import { readFileSync as readFileSync10 } from "fs";
4721
+ import { defineCommand as defineCommand21 } from "citty";
3353
4722
  function jsonFile(path) {
3354
4723
  try {
3355
- return JSON.parse(readFileSync8(path, "utf8"));
4724
+ return JSON.parse(readFileSync10(path, "utf8"));
3356
4725
  } catch (error) {
3357
4726
  throw new Error(`invalid JSON: ${error.message}`);
3358
4727
  }
@@ -3377,7 +4746,7 @@ function printTeam(detail) {
3377
4746
  console.log(line);
3378
4747
  }
3379
4748
  }
3380
- var list2 = defineCommand20({
4749
+ var list4 = defineCommand21({
3381
4750
  meta: { name: "list", description: "List canonical Team templates" },
3382
4751
  args: { json: { type: "boolean", description: "Emit stable JSON" } },
3383
4752
  async run({ args }) {
@@ -3391,7 +4760,7 @@ var list2 = defineCommand20({
3391
4760
  console.log(line);
3392
4761
  }
3393
4762
  });
3394
- var show3 = defineCommand20({
4763
+ var show3 = defineCommand21({
3395
4764
  meta: { name: "show", description: "Show a canonical Team template" },
3396
4765
  args: {
3397
4766
  id: { type: "positional", required: true },
@@ -3405,7 +4774,7 @@ var show3 = defineCommand20({
3405
4774
  else printTeam(detail);
3406
4775
  }
3407
4776
  });
3408
- var exportCommand2 = defineCommand20({
4777
+ var exportCommand2 = defineCommand21({
3409
4778
  meta: {
3410
4779
  name: "export",
3411
4780
  description: "Export a portable versioned Team-template document to stdout"
@@ -3445,7 +4814,7 @@ function printImportDiff(current, document) {
3445
4814
  for (const value of diff2.added) console.log(` + ${value}`);
3446
4815
  for (const value of diff2.removed) console.log(` - ${value}`);
3447
4816
  }
3448
- var importCommand2 = defineCommand20({
4817
+ var importCommand2 = defineCommand21({
3449
4818
  meta: {
3450
4819
  name: "import",
3451
4820
  description: "Validate, diff, and explicitly apply a portable Team-template document"
@@ -3526,17 +4895,17 @@ var importCommand2 = defineCommand20({
3526
4895
  );
3527
4896
  }
3528
4897
  });
3529
- var teamTemplateCommand = defineCommand20({
4898
+ var teamTemplateCommand = defineCommand21({
3530
4899
  meta: {
3531
4900
  name: "team-template",
3532
4901
  description: "Inspect and exchange canonical Team templates"
3533
4902
  },
3534
- subCommands: { list: list2, show: show3, export: exportCommand2, import: importCommand2 }
4903
+ subCommands: { list: list4, show: show3, export: exportCommand2, import: importCommand2 }
3535
4904
  });
3536
4905
 
3537
4906
  // src/commands/telegram.ts
3538
- import { defineCommand as defineCommand21 } from "citty";
3539
- var setCmd2 = defineCommand21({
4907
+ import { defineCommand as defineCommand22 } from "citty";
4908
+ var setCmd2 = defineCommand22({
3540
4909
  meta: {
3541
4910
  name: "set",
3542
4911
  description: "Save bot token + supergroup chat ID (paired write)"
@@ -3554,7 +4923,7 @@ var setCmd2 = defineCommand21({
3554
4923
  console.log(`saved \xB7 token ${state.botTokenPreview} \xB7 chat ${state.chatId}`);
3555
4924
  }
3556
4925
  });
3557
- var clearCmd = defineCommand21({
4926
+ var clearCmd = defineCommand22({
3558
4927
  meta: {
3559
4928
  name: "clear",
3560
4929
  description: "Remove stored bot token + chat ID"
@@ -3565,7 +4934,7 @@ var clearCmd = defineCommand21({
3565
4934
  console.log("cleared");
3566
4935
  }
3567
4936
  });
3568
- var showCmd5 = defineCommand21({
4937
+ var showCmd5 = defineCommand22({
3569
4938
  meta: {
3570
4939
  name: "show",
3571
4940
  description: "Show what credentials are stored (token is masked)"
@@ -3586,7 +4955,7 @@ var showCmd5 = defineCommand21({
3586
4955
  }
3587
4956
  }
3588
4957
  });
3589
- var reconnectCmd = defineCommand21({
4958
+ var reconnectCmd = defineCommand22({
3590
4959
  meta: {
3591
4960
  name: "reconnect",
3592
4961
  description: "Apply a pending supergroup chat-id migration + re-activate the bot"
@@ -3597,7 +4966,7 @@ var reconnectCmd = defineCommand21({
3597
4966
  console.log(`reconnected \xB7 chat ${state.chatId}`);
3598
4967
  }
3599
4968
  });
3600
- var configCmd = defineCommand21({
4969
+ var configCmd = defineCommand22({
3601
4970
  meta: {
3602
4971
  name: "config",
3603
4972
  description: "Manage Telegram credentials (bot token + chat ID)"
@@ -3608,7 +4977,7 @@ var configCmd = defineCommand21({
3608
4977
  show: showCmd5
3609
4978
  }
3610
4979
  });
3611
- var healthCmd = defineCommand21({
4980
+ var healthCmd = defineCommand22({
3612
4981
  meta: {
3613
4982
  name: "health",
3614
4983
  description: "Run the four-step preflight against the Telegram Bot API"
@@ -3642,7 +5011,7 @@ var healthCmd = defineCommand21({
3642
5011
  if (!allOk) process.exitCode = 1;
3643
5012
  }
3644
5013
  });
3645
- var botStatusCmd = defineCommand21({
5014
+ var botStatusCmd = defineCommand22({
3646
5015
  meta: {
3647
5016
  name: "status",
3648
5017
  description: "Show polling state of the live bot"
@@ -3667,7 +5036,7 @@ var botStatusCmd = defineCommand21({
3667
5036
  if (!p.running) process.exitCode = 1;
3668
5037
  }
3669
5038
  });
3670
- var botRestartCmd = defineCommand21({
5039
+ var botRestartCmd = defineCommand22({
3671
5040
  meta: {
3672
5041
  name: "restart",
3673
5042
  description: "Force-restart the bot (tears down + brings up from current creds)"
@@ -3678,7 +5047,7 @@ var botRestartCmd = defineCommand21({
3678
5047
  console.log("restart requested");
3679
5048
  }
3680
5049
  });
3681
- var botCmd = defineCommand21({
5050
+ var botCmd = defineCommand22({
3682
5051
  meta: {
3683
5052
  name: "bot",
3684
5053
  description: "Inspect / control the live polling bot"
@@ -3688,7 +5057,7 @@ var botCmd = defineCommand21({
3688
5057
  restart: botRestartCmd
3689
5058
  }
3690
5059
  });
3691
- var bindCmd = defineCommand21({
5060
+ var bindCmd = defineCommand22({
3692
5061
  meta: {
3693
5062
  name: "bind",
3694
5063
  description: "Create a Telegram topic for an agent (manual fallback for /talk)"
@@ -3706,7 +5075,7 @@ var bindCmd = defineCommand21({
3706
5075
  console.log(` ${result.deepLink}`);
3707
5076
  }
3708
5077
  });
3709
- var unbindCmd = defineCommand21({
5078
+ var unbindCmd = defineCommand22({
3710
5079
  meta: {
3711
5080
  name: "unbind",
3712
5081
  description: "Clear an agent's Telegram topic binding (topic stays in Telegram as orphan)"
@@ -3720,7 +5089,7 @@ var unbindCmd = defineCommand21({
3720
5089
  console.log(`unbound agent ${args.agent}`);
3721
5090
  }
3722
5091
  });
3723
- var listBindingsCmd = defineCommand21({
5092
+ var listBindingsCmd = defineCommand22({
3724
5093
  meta: {
3725
5094
  name: "list",
3726
5095
  description: "List agents and their Telegram topic bindings"
@@ -3748,7 +5117,7 @@ var listBindingsCmd = defineCommand21({
3748
5117
  }
3749
5118
  }
3750
5119
  });
3751
- var allowCmd = defineCommand21({
5120
+ var allowCmd = defineCommand22({
3752
5121
  meta: { name: "allow", description: "Add a Telegram user id to the allowlist" },
3753
5122
  args: {
3754
5123
  userId: { type: "positional", required: true, description: "Numeric Telegram user id" },
@@ -3765,7 +5134,7 @@ var allowCmd = defineCommand21({
3765
5134
  console.log(`allowed ${u.userId} (${u.role})`);
3766
5135
  }
3767
5136
  });
3768
- var denyCmd = defineCommand21({
5137
+ var denyCmd = defineCommand22({
3769
5138
  meta: { name: "deny", description: "Remove a Telegram user id from the allowlist" },
3770
5139
  args: { userId: { type: "positional", required: true } },
3771
5140
  async run({ args }) {
@@ -3774,7 +5143,7 @@ var denyCmd = defineCommand21({
3774
5143
  console.log(`removed ${args.userId}`);
3775
5144
  }
3776
5145
  });
3777
- var allowedCmd = defineCommand21({
5146
+ var allowedCmd = defineCommand22({
3778
5147
  meta: { name: "allowed", description: "List allowlisted Telegram users" },
3779
5148
  async run() {
3780
5149
  const client = createClient2();
@@ -3789,7 +5158,7 @@ var allowedCmd = defineCommand21({
3789
5158
  }
3790
5159
  }
3791
5160
  });
3792
- var telegramCommand = defineCommand21({
5161
+ var telegramCommand = defineCommand22({
3793
5162
  meta: {
3794
5163
  name: "telegram",
3795
5164
  description: "Telegram integration: credentials, health, bot lifecycle, bindings, access"
@@ -3810,7 +5179,7 @@ var telegramCommand = defineCommand21({
3810
5179
 
3811
5180
  // src/commands/token.ts
3812
5181
  import { networkInterfaces } from "os";
3813
- import { defineCommand as defineCommand22 } from "citty";
5182
+ import { defineCommand as defineCommand23 } from "citty";
3814
5183
  import qrcode from "qrcode-terminal";
3815
5184
  function tokenRow(t) {
3816
5185
  const last = t.lastUsedAt ? new Date(t.lastUsedAt).toISOString() : "(never)";
@@ -3844,7 +5213,7 @@ function resolveQrServer(override) {
3844
5213
  if (detected.warning) console.warn(`\u26A0 ${detected.warning}`);
3845
5214
  return detected.origin;
3846
5215
  }
3847
- var createCmd3 = defineCommand22({
5216
+ var createCmd3 = defineCommand23({
3848
5217
  meta: { name: "create", description: "Mint a new web token (shown once)" },
3849
5218
  args: {
3850
5219
  label: { type: "positional", required: true, description: "Human-readable label" },
@@ -3875,7 +5244,7 @@ var createCmd3 = defineCommand22({
3875
5244
  qrcode.generate(pairUrl, { small: true }, (qr) => console.log(qr));
3876
5245
  }
3877
5246
  });
3878
- var listCmd10 = defineCommand22({
5247
+ var listCmd10 = defineCommand23({
3879
5248
  meta: { name: "list", description: "List web tokens" },
3880
5249
  args: {
3881
5250
  all: { type: "boolean", description: "Include revoked tokens" }
@@ -3891,7 +5260,7 @@ var listCmd10 = defineCommand22({
3891
5260
  for (const line of columnize(tokens.map(tokenRow))) console.log(line);
3892
5261
  }
3893
5262
  });
3894
- var showLocalCmd = defineCommand22({
5263
+ var showLocalCmd = defineCommand23({
3895
5264
  meta: {
3896
5265
  name: "show-local",
3897
5266
  description: "Print the bootstrap web token stored in ~/.bazilion/auth.json"
@@ -3901,7 +5270,7 @@ var showLocalCmd = defineCommand22({
3901
5270
  console.log(readAuthFile(paths.authFile).token);
3902
5271
  }
3903
5272
  });
3904
- var revokeCmd = defineCommand22({
5273
+ var revokeCmd = defineCommand23({
3905
5274
  meta: { name: "revoke", description: "Revoke a web token" },
3906
5275
  args: {
3907
5276
  id: { type: "positional", required: true }
@@ -3912,7 +5281,7 @@ var revokeCmd = defineCommand22({
3912
5281
  console.log(`revoked token ${args.id}`);
3913
5282
  }
3914
5283
  });
3915
- var tokenCommand = defineCommand22({
5284
+ var tokenCommand = defineCommand23({
3916
5285
  meta: { name: "token", description: "Manage web tokens for API/CLI clients" },
3917
5286
  subCommands: {
3918
5287
  create: createCmd3,
@@ -3923,8 +5292,8 @@ var tokenCommand = defineCommand22({
3923
5292
  });
3924
5293
 
3925
5294
  // src/commands/trigger.ts
3926
- import { defineCommand as defineCommand23 } from "citty";
3927
- var addCmd3 = defineCommand23({
5295
+ import { defineCommand as defineCommand24 } from "citty";
5296
+ var addCmd3 = defineCommand24({
3928
5297
  meta: { name: "add", description: "Add an interval / cron trigger to an agent" },
3929
5298
  args: {
3930
5299
  agent: { type: "positional", required: true },
@@ -3972,7 +5341,7 @@ function triggerRow(t) {
3972
5341
  const msgPreview = t.message.length > 60 ? `${t.message.slice(0, 60)}\u2026` : t.message;
3973
5342
  return [t.id, state, spec, `last: ${last}`, `"${msgPreview}"`];
3974
5343
  }
3975
- var listCmd11 = defineCommand23({
5344
+ var listCmd11 = defineCommand24({
3976
5345
  meta: { name: "list", description: "List triggers for an agent" },
3977
5346
  args: {
3978
5347
  agent: { type: "positional", required: true }
@@ -3989,7 +5358,7 @@ var listCmd11 = defineCommand23({
3989
5358
  for (const line of columnize(triggers.map(triggerRow))) console.log(line);
3990
5359
  }
3991
5360
  });
3992
- var rmCmd6 = defineCommand23({
5361
+ var rmCmd6 = defineCommand24({
3993
5362
  meta: { name: "rm", description: "Delete a trigger" },
3994
5363
  args: {
3995
5364
  id: { type: "positional", required: true }
@@ -4000,7 +5369,7 @@ var rmCmd6 = defineCommand23({
4000
5369
  console.log(`removed trigger ${args.id}`);
4001
5370
  }
4002
5371
  });
4003
- var enableCmd3 = defineCommand23({
5372
+ var enableCmd3 = defineCommand24({
4004
5373
  meta: { name: "enable", description: "Enable a trigger" },
4005
5374
  args: {
4006
5375
  id: { type: "positional", required: true }
@@ -4012,7 +5381,7 @@ var enableCmd3 = defineCommand23({
4012
5381
  console.log(`enabled trigger ${args.id}`);
4013
5382
  }
4014
5383
  });
4015
- var disableCmd3 = defineCommand23({
5384
+ var disableCmd3 = defineCommand24({
4016
5385
  meta: { name: "disable", description: "Disable a trigger" },
4017
5386
  args: {
4018
5387
  id: { type: "positional", required: true }
@@ -4024,21 +5393,50 @@ var disableCmd3 = defineCommand23({
4024
5393
  console.log(`disabled trigger ${args.id}`);
4025
5394
  }
4026
5395
  });
4027
- var triggerCommand = defineCommand23({
5396
+ var historyCmd = defineCommand24({
5397
+ meta: { name: "history", description: "Show recent dispatches for a trigger" },
5398
+ args: {
5399
+ id: { type: "positional", required: true },
5400
+ limit: { type: "string", description: "Maximum rows (default 20)" }
5401
+ },
5402
+ async run({ args }) {
5403
+ const limit = args.limit ? Number(args.limit) : 20;
5404
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
5405
+ throw new Error("--limit must be an integer from 1 to 100");
5406
+ }
5407
+ const client = createClient2();
5408
+ const { dispatches } = await client.get(
5409
+ `/api/triggers/${args.id}/dispatches?limit=${limit}`
5410
+ );
5411
+ if (dispatches.length === 0) {
5412
+ console.log("(no dispatches)");
5413
+ return;
5414
+ }
5415
+ const rows = dispatches.map((dispatch) => [
5416
+ new Date(dispatch.scheduledAt).toISOString(),
5417
+ dispatch.status,
5418
+ `attempts: ${dispatch.attemptCount}`,
5419
+ dispatch.lastError ?? ""
5420
+ ]);
5421
+ for (const line of columnize(rows)) console.log(line);
5422
+ }
5423
+ });
5424
+ var triggerCommand = defineCommand24({
4028
5425
  meta: { name: "trigger", description: "Manage scheduled agent triggers" },
4029
5426
  subCommands: {
4030
5427
  add: addCmd3,
4031
5428
  list: listCmd11,
4032
5429
  rm: rmCmd6,
4033
5430
  enable: enableCmd3,
4034
- disable: disableCmd3
5431
+ disable: disableCmd3,
5432
+ history: historyCmd
4035
5433
  }
4036
5434
  });
4037
5435
 
4038
5436
  // src/commands/uninstall.ts
4039
- import { existsSync as existsSync7, readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
4040
- import { join as join5 } from "path";
4041
- import { defineCommand as defineCommand24 } from "citty";
5437
+ import { existsSync as existsSync7, readdirSync as readdirSync2, rmSync as rmSync4 } from "fs";
5438
+ import { join as join6 } from "path";
5439
+ import { defineCommand as defineCommand25 } from "citty";
4042
5440
  function makeLineReader() {
4043
5441
  let buffer = "";
4044
5442
  let ended = false;
@@ -4048,8 +5446,8 @@ function makeLineReader() {
4048
5446
  const nlIdx = buffer.indexOf("\n");
4049
5447
  if (nlIdx === -1) {
4050
5448
  if (ended) {
4051
- const resolve3 = queue.shift();
4052
- resolve3?.(buffer);
5449
+ const resolve4 = queue.shift();
5450
+ resolve4?.(buffer);
4053
5451
  buffer = "";
4054
5452
  } else {
4055
5453
  return;
@@ -4057,8 +5455,8 @@ function makeLineReader() {
4057
5455
  } else {
4058
5456
  const line = buffer.slice(0, nlIdx);
4059
5457
  buffer = buffer.slice(nlIdx + 1);
4060
- const resolve3 = queue.shift();
4061
- resolve3?.(line);
5458
+ const resolve4 = queue.shift();
5459
+ resolve4?.(line);
4062
5460
  }
4063
5461
  }
4064
5462
  };
@@ -4071,8 +5469,8 @@ function makeLineReader() {
4071
5469
  ended = true;
4072
5470
  tryDeliver();
4073
5471
  });
4074
- return () => new Promise((resolve3) => {
4075
- queue.push(resolve3);
5472
+ return () => new Promise((resolve4) => {
5473
+ queue.push(resolve4);
4076
5474
  tryDeliver();
4077
5475
  });
4078
5476
  }
@@ -4083,10 +5481,10 @@ async function askYes(readLine, prompt) {
4083
5481
  }
4084
5482
  function removePath(p) {
4085
5483
  if (!existsSync7(p)) return false;
4086
- rmSync3(p, { recursive: true, force: true });
5484
+ rmSync4(p, { recursive: true, force: true });
4087
5485
  return true;
4088
5486
  }
4089
- var uninstallCommand = defineCommand24({
5487
+ var uninstallCommand = defineCommand25({
4090
5488
  meta: {
4091
5489
  name: "uninstall",
4092
5490
  description: "Wipe bazilion state from ~/.bazilion (or BAZILION_HOME)"
@@ -4104,12 +5502,12 @@ var uninstallCommand = defineCommand24({
4104
5502
  },
4105
5503
  async run({ args }) {
4106
5504
  const paths = resolveCliPaths(args.home);
4107
- const dbFile = join5(paths.home, "bazilion.db");
4108
- const profilesDir = join5(paths.home, "profiles");
4109
- const agentsDir = join5(paths.home, "agents");
4110
- const teamsDir = join5(paths.home, "teams");
4111
- const skillsDir = join5(paths.home, "skills");
4112
- const logsDir = join5(paths.home, "logs");
5505
+ const dbFile = join6(paths.home, "bazilion.db");
5506
+ const profilesDir = join6(paths.home, "profiles");
5507
+ const agentsDir = join6(paths.home, "agents");
5508
+ const teamsDir = join6(paths.home, "teams");
5509
+ const skillsDir = join6(paths.home, "skills");
5510
+ const logsDir = join6(paths.home, "logs");
4113
5511
  if (!existsSync7(paths.home)) {
4114
5512
  console.log(`nothing to remove: ${paths.home} does not exist`);
4115
5513
  return;
@@ -4142,7 +5540,7 @@ var uninstallCommand = defineCommand24({
4142
5540
  if (removePath(p)) console.log(`removed ${p}`);
4143
5541
  }
4144
5542
  if (readdirSync2(paths.home).length === 0) {
4145
- rmSync3(paths.home, { recursive: true, force: true });
5543
+ rmSync4(paths.home, { recursive: true, force: true });
4146
5544
  console.log(`removed ${paths.home}`);
4147
5545
  } else {
4148
5546
  console.log(`${paths.home} still has unmanaged files; left in place`);
@@ -4157,7 +5555,7 @@ var uninstallCommand = defineCommand24({
4157
5555
 
4158
5556
  // src/index.ts
4159
5557
  var VERSION = package_default.version;
4160
- var main = defineCommand25({
5558
+ var main = defineCommand26({
4161
5559
  meta: {
4162
5560
  name: "bazilion",
4163
5561
  version: VERSION,
@@ -4170,6 +5568,7 @@ var main = defineCommand25({
4170
5568
  "team-template": teamTemplateCommand,
4171
5569
  agent: agentCommand,
4172
5570
  approval: approvalCommand,
5571
+ attention: attentionCommand,
4173
5572
  skill: skillCommand,
4174
5573
  memory: memoryCommand,
4175
5574
  mcp: mcpCommand,
@@ -4265,6 +5664,7 @@ function printTopLevelHelp() {
4265
5664
  title: "agents",
4266
5665
  items: [
4267
5666
  ["approval", "Review and decide communication approval attempts"],
5667
+ ["attention", "See everything that needs operator attention"],
4268
5668
  ["agent", "Spawn, list, chat, archive agents"],
4269
5669
  ["memory", "Read/write a team's shared memory"],
4270
5670
  ["send", "Send a message from one agent to another"],