bazilion 0.12.1 → 0.13.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 (160) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +811 -250
  3. package/dist/cli.js.map +1 -1
  4. package/dist/daemon.js +4778 -1461
  5. package/dist/daemon.js.map +1 -1
  6. package/dist/migrations/0001_init.sql +21 -0
  7. package/dist/web/client/assets/{ProductionTeamPolicyEditor-CSL3W7XM.js → ProductionTeamPolicyEditor-DdpYInwV.js} +1 -1
  8. package/dist/web/client/assets/{ProfileCommunicationEditor-CwU0QkmE.js → ProfileCommunicationEditor-BcSf2Hao.js} +1 -1
  9. package/dist/web/client/assets/{TeamTabs-C1Hakkff.js → TeamTabs-Bk89JfN7.js} +1 -1
  10. package/dist/web/client/assets/{TemplatesTabs-klEydJ8-.js → TemplatesTabs-CyDVzrOl.js} +1 -1
  11. package/dist/web/client/assets/{_id-CnVq-rED.js → _id-CAJTTnee.js} +1 -1
  12. package/dist/web/client/assets/{_id-wkjjWLz0.js → _id-CN6jPaBu.js} +1 -1
  13. package/dist/web/client/assets/{_id-BOC7ioJu.js → _id-DGwJqqDc.js} +1 -1
  14. package/dist/web/client/assets/{_id-e4u7dmBb.js → _id-DWTMH7Eb.js} +1 -1
  15. package/dist/web/client/assets/{activity-uqhwzmiy.js → activity-DK_M0-bn.js} +1 -1
  16. package/dist/web/client/assets/{agents-B2usE-L_.js → agents-B-i5pASm.js} +1 -1
  17. package/dist/web/client/assets/{agents-FaQtkAWs.js → agents-n8U7qNxw.js} +1 -1
  18. package/dist/web/client/assets/{approvals-DqLE6ttw.js → approvals-CmVxCy0Z.js} +1 -1
  19. package/dist/web/client/assets/{attention-CkGozYnJ.js → attention-F0lUQCWE.js} +1 -1
  20. package/dist/web/client/assets/chevron-down-OndG7r1U.js +1 -0
  21. package/dist/web/client/assets/config-DuApZ9xS.js +3 -0
  22. package/dist/web/client/assets/{context-ZWqx3uiC.js → context-C1sMnW6K.js} +1 -1
  23. package/dist/web/client/assets/dm-sans-latin-400-normal-BwCSEQnW.woff +0 -0
  24. package/dist/web/client/assets/dm-sans-latin-400-normal-CW0RaeGs.woff2 +0 -0
  25. package/dist/web/client/assets/dm-sans-latin-500-normal-B9HHJjqV.woff2 +0 -0
  26. package/dist/web/client/assets/dm-sans-latin-500-normal-Dr3UlScf.woff +0 -0
  27. package/dist/web/client/assets/dm-sans-latin-600-normal-Aqo67rzb.woff2 +0 -0
  28. package/dist/web/client/assets/dm-sans-latin-600-normal-BmdmIIQ2.woff +0 -0
  29. package/dist/web/client/assets/dm-sans-latin-700-normal-CUSSCpQX.woff +0 -0
  30. package/dist/web/client/assets/dm-sans-latin-700-normal-DvUfVpUG.woff2 +0 -0
  31. package/dist/web/client/assets/dm-serif-display-latin-400-normal-C5_t9oOD.woff2 +0 -0
  32. package/dist/web/client/assets/dm-serif-display-latin-400-normal-_kVFYEpx.woff +0 -0
  33. package/dist/web/client/assets/{inbox-BoIvl9ZF.js → inbox-D2NypLee.js} +1 -1
  34. package/dist/web/client/assets/{index-nb2MV8X1.js → index-B3vdrEEg.js} +4 -4
  35. package/dist/web/client/assets/index-Cofc7zhl.css +1 -0
  36. package/dist/web/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  37. package/dist/web/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  38. package/dist/web/client/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  39. package/dist/web/client/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  40. package/dist/web/client/assets/{learning-6_oYn1l7.js → learning-zYDCfVxT.js} +1 -1
  41. package/dist/web/client/assets/{login-DM1ytxQL.js → login-B1Un51Zx.js} +1 -1
  42. package/dist/web/client/assets/{mcp-CetZbyUh.js → mcp-DSVmJBdv.js} +1 -1
  43. package/dist/web/client/assets/{members-DdqWtwZd.js → members-Pl7hXJcN.js} +1 -1
  44. package/dist/web/client/assets/{memory-FNrxFHmC.js → memory-Co3BEaAq.js} +1 -1
  45. package/dist/web/client/assets/plus-Dor-_wmL.js +1 -0
  46. package/dist/web/client/assets/{policy-BKPejYRI.js → policy-CHM95pMU.js} +1 -1
  47. package/dist/web/client/assets/{routes-CH1ZTTo-.js → routes-D29qjwG0.js} +1 -1
  48. package/dist/web/client/assets/{services-zk9SqN06.js → services-CCQ_6YWQ.js} +1 -1
  49. package/dist/web/client/assets/{skills-tXDpomHW.js → skills-3xAIvzvP.js} +1 -1
  50. package/dist/web/client/assets/styles-D6RPN7RK.css +2 -0
  51. package/dist/web/client/assets/{teams-BZYjekBN.js → teams-Dm_huGxn.js} +1 -1
  52. package/dist/web/client/assets/{teams-D1c8zlxs.js → teams-UGLyjHYj.js} +1 -1
  53. package/dist/web/client/assets/telegram-3KvrJVO6.js +1 -0
  54. package/dist/web/client/assets/tokens-DUSFmV5_.js +1 -0
  55. package/dist/web/client/assets/{triggers-D8R-5guA.js → triggers-DlQ2KF1h.js} +1 -1
  56. package/dist/web/client/assets/{welcome-CuK0shDJ.js → welcome-DkP8qzK1.js} +1 -1
  57. package/dist/web/server/assets/{_id-CwyGjJeP.js → _id-BENqNBfK.js} +4 -4
  58. package/dist/web/server/assets/{_id-D3Ap7zZA.js → _id-BFELaMIN.js} +1 -1
  59. package/dist/web/server/assets/{_id-LrvXU4Nr.js → _id-BQ2zoMiR.js} +1 -1
  60. package/dist/web/server/assets/{_id-D27T85mU.js → _id-BZWunvy7.js} +4 -4
  61. package/dist/web/server/assets/{_id-BHYLL8M-.js → _id-BreRHYV2.js} +1 -1
  62. package/dist/web/server/assets/{_id-C3HY4lWg.js → _id-CPIXIqyO.js} +1 -1
  63. package/dist/web/server/assets/{_id-FcHdSocl.js → _id-CvYqVJPU.js} +4 -4
  64. package/dist/web/server/assets/{_id-DinTp_7X.js → _id-DkrNqqTw.js} +3 -3
  65. package/dist/web/server/assets/{_id-IzadFZV9.js → _id-DvlDkk04.js} +4 -4
  66. package/dist/web/server/assets/{_id-BhOB8Umi.js → _id-DxCnEFpm.js} +4 -4
  67. package/dist/web/server/assets/{_id-tB47Vsfp.js → _id-HMy7jM3P.js} +3 -3
  68. package/dist/web/server/assets/{_id-Dj1yZ3zJ.js → _id-xTD_6ApX.js} +4 -4
  69. package/dist/web/server/assets/{_tanstack-start-manifest_v-fLsYERQ-.js → _tanstack-start-manifest_v-COkJjXt_.js} +50 -49
  70. package/dist/web/server/assets/{activity-DohitgwW.js → activity-9VP6QDTH.js} +3 -3
  71. package/dist/web/server/assets/{activity-BCI4mfNS.js → activity-B6N1T_5N.js} +1 -1
  72. package/dist/web/server/assets/{activity-D9SMqBhy.js → activity-BazZNfvf.js} +3 -3
  73. package/dist/web/server/assets/{agents-BY74v7fM.js → agents-BJAv919x.js} +3 -3
  74. package/dist/web/server/assets/{agents-C9xlWK0Z.js → agents-BpSeOA30.js} +4 -4
  75. package/dist/web/server/assets/{agents-BvttypqB.js → agents-DY630p6x.js} +3 -3
  76. package/dist/web/server/assets/{agents-DZuFEj0D.js → agents-DfoLnTyD.js} +1 -1
  77. package/dist/web/server/assets/{agents-BPKOshR5.js → agents-DjyQk94u.js} +4 -4
  78. package/dist/web/server/assets/{agents-CgZMHj80.js → agents-wSYNPuyK.js} +1 -1
  79. package/dist/web/server/assets/{approvals-DCaVw1Mz.js → approvals-BYajNOdE.js} +1 -1
  80. package/dist/web/server/assets/{approvals-BGU-XGIO.js → approvals-BYi84gwl.js} +3 -3
  81. package/dist/web/server/assets/{approvals-CWcFHlor.js → approvals-BkUGmYU9.js} +3 -3
  82. package/dist/web/server/assets/{attention-BxkTf337.js → attention-Bw_wiiqI.js} +3 -3
  83. package/dist/web/server/assets/{attention-CM-Ks1N9.js → attention-UhqPK_Ro.js} +3 -3
  84. package/dist/web/server/assets/{attention-w21GQIGH.js → attention-Z3bGuxOD.js} +1 -1
  85. package/dist/web/server/assets/{auth-byfSx6Ja.js → auth-7BHTH-KS.js} +5 -5
  86. package/dist/web/server/assets/{config-BDCJdtj2.js → config-BMiajTZP.js} +10 -5
  87. package/dist/web/server/assets/{config-D2LAsud2.js → config-DGzM28F_.js} +211 -27
  88. package/dist/web/server/assets/{config-TeIoyYYc.js → config-wYtIhkRm.js} +3 -3
  89. package/dist/web/server/assets/context-BkDDwbby.js +12 -0
  90. package/dist/web/server/assets/context-DMc0KyHh.js +12 -0
  91. package/dist/web/server/assets/{context-DrkrIWun.js → context-poimKI7w.js} +1 -1
  92. package/dist/web/server/assets/{createServerRpc-pU-IQV2Y.js → createServerRpc-B7Yr1K8i.js} +1 -1
  93. package/dist/web/server/assets/{createSsrRpc-DFsIAfFd.js → createSsrRpc-6LHNEsSK.js} +1 -1
  94. package/dist/web/server/assets/{daemon-client-2NRMzlTr.js → daemon-client-CI5NHwNC.js} +16 -4
  95. package/dist/web/server/assets/{inbox-ZvDNAjaM.js → inbox-0iiSg-Y1.js} +4 -4
  96. package/dist/web/server/assets/{inbox-BLUzA5sQ.js → inbox-DTTfSuGU.js} +1 -1
  97. package/dist/web/server/assets/{inbox-CzlKT9Wg.js → inbox-Dv55rGYq.js} +4 -4
  98. package/dist/web/server/assets/{learning-8zav4pIT.js → learning-C2upa1CE.js} +4 -4
  99. package/dist/web/server/assets/{learning-B7PBkvER.js → learning-DpyLNVe8.js} +4 -4
  100. package/dist/web/server/assets/{learning-Cq7K2heb.js → learning-fG8316uc.js} +1 -1
  101. package/dist/web/server/assets/{login-Bx5LEyGP.js → login-CrFVVUaE.js} +1 -1
  102. package/dist/web/server/assets/{login-CmWvxfTK.js → login-D_GDeTrB.js} +7 -7
  103. package/dist/web/server/assets/{mcp-DIWTogHM.js → mcp-BQtp2T87.js} +3 -3
  104. package/dist/web/server/assets/{mcp-2pwT9HU8.js → mcp-BvnvlVFd.js} +3 -3
  105. package/dist/web/server/assets/{mcp-BJjLIdP0.js → mcp-DlgXUcAS.js} +1 -1
  106. package/dist/web/server/assets/members-BP3UTqu1.js +12 -0
  107. package/dist/web/server/assets/{members-CCv17cjQ.js → members-CteHKPSF.js} +1 -1
  108. package/dist/web/server/assets/members-DXu2oAS_.js +12 -0
  109. package/dist/web/server/assets/{memory-Dngc13Nw.js → memory-BqV-Afy1.js} +4 -4
  110. package/dist/web/server/assets/{memory-a8Kz57Kh.js → memory-C6fX_FKI.js} +1 -1
  111. package/dist/web/server/assets/{memory-pZ4pZT7p.js → memory-Iq2M0-Yc.js} +4 -4
  112. package/dist/web/server/assets/{policy-Bb-vEgqg.js → policy-18fdTFO4.js} +3 -3
  113. package/dist/web/server/assets/{policy-BcGELcc9.js → policy-Bg6u-rTq.js} +1 -1
  114. package/dist/web/server/assets/{policy-C30AeFIb.js → policy-D7kj3Cy7.js} +3 -3
  115. package/dist/web/server/assets/public-origin-xV0lm0Ty.js +25 -0
  116. package/dist/web/server/assets/{router-CQuj7553.js → router-D-6TzLRZ.js} +168 -65
  117. package/dist/web/server/assets/{routes-BrA-U_Hm.js → routes-CNRzy79H.js} +4 -4
  118. package/dist/web/server/assets/{routes-B6cMLIPk.js → routes-CiPfEldd.js} +1 -1
  119. package/dist/web/server/assets/{routes-C19CMLZt.js → routes-D933ngZS.js} +4 -4
  120. package/dist/web/server/assets/{services-B9yBWDX0.js → services-DJRA1A0-.js} +1 -1
  121. package/dist/web/server/assets/{services-wG8I06oS.js → services-DozuJtzX.js} +3 -3
  122. package/dist/web/server/assets/{services-C4szdhPx.js → services-OB4LAr2A.js} +3 -3
  123. package/dist/web/server/assets/{skills-BZdqiDig.js → skills-Babb9jsQ.js} +1 -1
  124. package/dist/web/server/assets/{skills-C-UgkuCW.js → skills-D5DQL8bM.js} +3 -3
  125. package/dist/web/server/assets/{skills-a9VSWHWq.js → skills-DfGSJTCB.js} +3 -3
  126. package/dist/web/server/assets/start-uKQNK6if.js +70 -0
  127. package/dist/web/server/assets/{teams-CpQ0YgWf.js → teams-Bk6ZT0MD.js} +1 -1
  128. package/dist/web/server/assets/{teams-jBtvnk59.js → teams-Cc2Czynq.js} +1 -1
  129. package/dist/web/server/assets/{teams-DqfrEoH6.js → teams-DPnAX4wD.js} +4 -4
  130. package/dist/web/server/assets/{teams-DEpQR2QA.js → teams-DqC4wZxu.js} +3 -3
  131. package/dist/web/server/assets/{teams-OIkHuxy1.js → teams-Dz6ZEMRo.js} +3 -3
  132. package/dist/web/server/assets/{teams-MjqgsmC3.js → teams-be05ci17.js} +3 -3
  133. package/dist/web/server/assets/{telegram-DNgCWebD.js → telegram-1AWL6lFw.js} +3 -3
  134. package/dist/web/server/assets/{telegram-CBF8SJzB.js → telegram-DUbDIVGX.js} +122 -3
  135. package/dist/web/server/assets/{telegram-XwyKSEtj.js → telegram-J4T0XhwQ.js} +3 -3
  136. package/dist/web/server/assets/{tokens-yyTSfBSN.js → tokens-CJM_0NDa.js} +126 -22
  137. package/dist/web/server/assets/{tokens-BnwFbV7T.js → tokens-DO5BzNOn.js} +4 -4
  138. package/dist/web/server/assets/tokens-DjM8GsgS.js +19 -0
  139. package/dist/web/server/assets/{triggers-CSNNzRIi.js → triggers-B87fycBl.js} +1 -1
  140. package/dist/web/server/assets/{triggers-BZPl5CHs.js → triggers-BdBx-HwR.js} +4 -4
  141. package/dist/web/server/assets/{triggers-D79NoSXk.js → triggers-Nezfw-Bd.js} +4 -4
  142. package/dist/web/server/assets/{welcome-IYHGurWx.js → welcome-DDB11L5j.js} +2 -2
  143. package/dist/web/server/server.js +30 -30
  144. package/dist/web-server.js +4 -0
  145. package/dist/web-server.js.map +1 -1
  146. package/dist/worker.js +1982 -537
  147. package/dist/worker.js.map +1 -1
  148. package/package.json +7 -6
  149. package/dist/web/client/assets/chevron-down-CJk0zGNT.js +0 -1
  150. package/dist/web/client/assets/config-xrXoDEWM.js +0 -3
  151. package/dist/web/client/assets/plus-C3GroITC.js +0 -1
  152. package/dist/web/client/assets/styles-DMltY3su.css +0 -2
  153. package/dist/web/client/assets/telegram-DCe1CWMt.js +0 -1
  154. package/dist/web/client/assets/tokens-DAPjRS0N.js +0 -1
  155. package/dist/web/server/assets/context-B-XDf6Ym.js +0 -12
  156. package/dist/web/server/assets/context-BXGo5Vfi.js +0 -12
  157. package/dist/web/server/assets/members-CW5aBe81.js +0 -12
  158. package/dist/web/server/assets/members-VdPwD6CE.js +0 -12
  159. package/dist/web/server/assets/start-WH8WzTL_.js +0 -4
  160. package/dist/web/server/assets/tokens-DPDMSN5O.js +0 -12
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 defineCommand26, runCommand, showUsage } from "citty";
4
+ import { defineCommand as defineCommand28, runCommand, showUsage } from "citty";
5
5
 
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "bazilion",
9
- version: "0.12.1",
9
+ version: "0.13.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: {
@@ -49,15 +49,16 @@ var package_default = {
49
49
  access: "public"
50
50
  },
51
51
  dependencies: {
52
- "@earendil-works/pi-agent-core": "0.80.6",
53
- "@earendil-works/pi-ai": "0.80.6",
54
- "@earendil-works/pi-coding-agent": "0.80.6",
52
+ "@earendil-works/pi-agent-core": "0.84.3",
53
+ "@earendil-works/pi-ai": "0.84.3",
54
+ "@earendil-works/pi-coding-agent": "0.84.3",
55
55
  "@hono/node-server": "^2.0.6",
56
56
  "@mozilla/readability": "^0.6.0",
57
57
  "@playwright/browser-chromium": "^1.61.0",
58
58
  "@tanstack/react-router": "1.170.16",
59
59
  "@tobilu/qmd": "^2.5.3",
60
60
  "adm-zip": "^0.5.17",
61
+ "age-encryption": "0.3.0",
61
62
  citty: "^0.2.2",
62
63
  dompurify: "^3.4.11",
63
64
  grammy: "^1.44.0",
@@ -96,15 +97,17 @@ async function resolveToken(src) {
96
97
  }
97
98
  function createClient(cfg) {
98
99
  async function authHeaders() {
99
- const token = await resolveToken(cfg.token);
100
+ const token = cfg.token === null ? null : await resolveToken(cfg.token);
101
+ const extra = typeof cfg.headers === "function" ? await cfg.headers() : cfg.headers ?? {};
100
102
  return {
101
- authorization: `Bearer ${token}`,
103
+ ...token ? { authorization: `Bearer ${token}` } : {},
102
104
  // Astro 6 enabled `security.checkOrigin` by default for server mode:
103
105
  // any state-changing request without an `Origin` matching the server
104
106
  // host is rejected with 403. Browsers set this automatically; `fetch`
105
107
  // in Node / React Native does not. Stamping the server's own URL is
106
108
  // always valid for a legitimate client.
107
- origin: cfg.serverUrl
109
+ origin: cfg.serverUrl,
110
+ ...extra
108
111
  };
109
112
  }
110
113
  async function request(method, path, body) {
@@ -229,15 +232,15 @@ function columnize(rows, gap = " ") {
229
232
  if (rows.length === 0) return [];
230
233
  const colCount = rows[0]?.length ?? 0;
231
234
  const widths = new Array(colCount).fill(0);
232
- for (const row of rows) {
235
+ for (const row2 of rows) {
233
236
  for (let i = 0; i < colCount; i++) {
234
- const cell = row[i] ?? "";
237
+ const cell = row2[i] ?? "";
235
238
  const prev = widths[i] ?? 0;
236
239
  if (cell.length > prev) widths[i] = cell.length;
237
240
  }
238
241
  }
239
242
  return rows.map(
240
- (row) => row.map((cell, i) => {
243
+ (row2) => row2.map((cell, i) => {
241
244
  if (i === colCount - 1) return cell;
242
245
  return (cell ?? "").padEnd(widths[i] ?? 0);
243
246
  }).join(gap)
@@ -1339,7 +1342,7 @@ var attentionCommand = defineCommand3({
1339
1342
  });
1340
1343
 
1341
1344
  // src/commands/auth.ts
1342
- import { loginOpenAICodex } from "@earendil-works/pi-ai/oauth";
1345
+ import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
1343
1346
  import { defineCommand as defineCommand4 } from "citty";
1344
1347
  function formatExpiry(ms) {
1345
1348
  if (!ms) return "(unknown)";
@@ -1355,20 +1358,28 @@ var openaiLoginCmd = defineCommand4({
1355
1358
  const client = createClient2();
1356
1359
  console.log("opening your browser for OpenAI sign-in...");
1357
1360
  console.log("(if it doesn't open automatically, paste the URL shown below)");
1358
- const creds = await loginOpenAICodex({
1359
- onAuth: ({ url }) => {
1360
- console.log("");
1361
- console.log(` \u2192 ${url}`);
1362
- console.log("");
1361
+ const oauth = openaiCodexProvider().auth.oauth;
1362
+ if (!oauth) throw new Error("Pi openai-codex provider does not expose OAuth");
1363
+ const loginController = new AbortController();
1364
+ const creds = await oauth.login({
1365
+ signal: loginController.signal,
1366
+ notify: (event) => {
1367
+ if (event.type === "auth_url") {
1368
+ console.log("");
1369
+ console.log(` \u2192 ${event.url}`);
1370
+ console.log("");
1371
+ } else if (event.type === "progress" || event.type === "info") {
1372
+ console.log(` ${event.message}`);
1373
+ } else if (event.type === "device_code") {
1374
+ console.log(` open ${event.verificationUri} and enter ${event.userCode}`);
1375
+ }
1363
1376
  },
1364
- onPrompt: async (prompt) => {
1377
+ prompt: async (prompt) => {
1378
+ if (prompt.type === "select") return prompt.options[0]?.id ?? "";
1365
1379
  process.stdout.write(`${prompt.message}: `);
1366
1380
  return new Promise((resolve4) => {
1367
1381
  process.stdin.once("data", (d) => resolve4(String(d).trim()));
1368
1382
  });
1369
- },
1370
- onProgress: (msg) => {
1371
- console.log(` ${msg}`);
1372
1383
  }
1373
1384
  });
1374
1385
  const status = await client.put("/api/auth/openai", {
@@ -1438,11 +1449,20 @@ var authCommand = defineCommand4({
1438
1449
  });
1439
1450
 
1440
1451
  // src/commands/backup.ts
1441
- import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
1452
+ import {
1453
+ createCipheriv,
1454
+ createDecipheriv,
1455
+ createHash as createHash3,
1456
+ pbkdf2Sync,
1457
+ randomBytes,
1458
+ randomUUID as randomUUID2
1459
+ } from "crypto";
1442
1460
  import {
1443
1461
  chmodSync as chmodSync2,
1444
1462
  closeSync as closeSync2,
1445
1463
  copyFileSync,
1464
+ cpSync,
1465
+ createReadStream,
1446
1466
  createWriteStream,
1447
1467
  existsSync as existsSync2,
1448
1468
  fsyncSync as fsyncSync2,
@@ -1452,21 +1472,24 @@ import {
1452
1472
  openSync as openSync2,
1453
1473
  readdirSync,
1454
1474
  readFileSync as readFileSync4,
1475
+ readSync,
1455
1476
  renameSync as renameSync2,
1456
- rmSync as rmSync2
1477
+ rmSync as rmSync2,
1478
+ writeFileSync as writeFileSync2
1457
1479
  } from "fs";
1458
1480
  import { tmpdir } from "os";
1459
1481
  import { basename as basename3, dirname as dirname2, isAbsolute, parse, posix, relative, resolve as resolve2 } from "path";
1460
- import { DatabaseSync } from "node:sqlite";
1482
+ import { DatabaseSync, backup as sqliteBackup } from "node:sqlite";
1461
1483
  import { Readable } from "stream";
1462
1484
  import { pipeline } from "stream/promises";
1485
+ import { Decrypter, Encrypter } from "age-encryption";
1463
1486
  import { defineCommand as defineCommand5 } from "citty";
1464
1487
  import { extract, list as list3 } from "tar";
1465
1488
 
1466
1489
  // src/backup-schema.ts
1467
1490
  import { createHash } from "crypto";
1468
1491
  var CANONICAL_MIGRATION = "0001_init";
1469
- var CANONICAL_SCHEMA_HASH = "7c0867f97bd6a7d9c8a89f43f6ae7e5259c3976d40cd81a2061c43c30b7590a1";
1492
+ var CANONICAL_SCHEMA_HASH = "4bde209eb0bf9851bf1f01fc96263f165a1799c81e720ddea463169a0d6c0d51";
1470
1493
  var CANONICAL_OBJECTS = [
1471
1494
  ["index", "agent_triggers_agent"],
1472
1495
  ["index", "agent_triggers_enabled"],
@@ -1491,6 +1514,8 @@ var CANONICAL_OBJECTS = [
1491
1514
  ["index", "trigger_dispatches_claimable"],
1492
1515
  ["index", "trigger_dispatches_trigger_time"],
1493
1516
  ["index", "web_tokens_active"],
1517
+ ["index", "web_sessions_active"],
1518
+ ["index", "web_sessions_device"],
1494
1519
  ["table", "agent_skills"],
1495
1520
  ["table", "agent_loop_break_events"],
1496
1521
  ["table", "agent_lesson_proposals"],
@@ -1525,9 +1550,11 @@ var CANONICAL_OBJECTS = [
1525
1550
  ["table", "team_templates"],
1526
1551
  ["table", "teams"],
1527
1552
  ["table", "telegram_allowed_users"],
1553
+ ["table", "telegram_pairing_challenge"],
1528
1554
  ["table", "template_instantiations"],
1529
1555
  ["table", "trigger_dispatches"],
1530
1556
  ["table", "web_tokens"],
1557
+ ["table", "web_sessions"],
1531
1558
  ["trigger", "create_team_policy"],
1532
1559
  ["trigger", "prevent_detached_team_policy_delete"],
1533
1560
  ["trigger", "validate_source_binding_insert"],
@@ -1543,7 +1570,7 @@ function assertCanonicalBackupSchema(db) {
1543
1570
  const migrations = db.prepare("SELECT version FROM schema_migrations ORDER BY version").all();
1544
1571
  if (migrations.length !== 1 || migrations[0]?.version !== CANONICAL_MIGRATION) {
1545
1572
  throw new Error(
1546
- `schema_migrations must contain exactly ${CANONICAL_MIGRATION}; found ` + (migrations.map((row) => row.version).join(", ") || "none")
1573
+ `schema_migrations must contain exactly ${CANONICAL_MIGRATION}; found ` + (migrations.map((row2) => row2.version).join(", ") || "none")
1547
1574
  );
1548
1575
  }
1549
1576
  const rows = db.prepare(
@@ -1551,20 +1578,20 @@ function assertCanonicalBackupSchema(db) {
1551
1578
  FROM sqlite_schema
1552
1579
  WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%'`
1553
1580
  ).all();
1554
- const byKey = new Map(rows.map((row) => [`${row.type}\0${row.name}`, row]));
1581
+ const byKey = new Map(rows.map((row2) => [`${row2.type}\0${row2.name}`, row2]));
1555
1582
  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}`);
1583
+ for (const row2 of rows) {
1584
+ if (!canonicalKeys.has(`${row2.type}\0${row2.name}`)) {
1585
+ throw new Error(`unexpected schema ${row2.type} is not canonical: ${row2.name}`);
1559
1586
  }
1560
1587
  }
1561
1588
  const canonicalRows = [];
1562
1589
  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);
1590
+ const row2 = byKey.get(`${type}\0${name}`);
1591
+ if (!row2) throw new Error(`required canonical schema ${type} is missing: ${name}`);
1592
+ canonicalRows.push(row2);
1566
1593
  }
1567
- const payload = canonicalRows.map((row) => [row.type, row.name, row.tbl_name, normalizeSql(row.sql)].join("\0")).join("\n");
1594
+ const payload = canonicalRows.map((row2) => [row2.type, row2.name, row2.tbl_name, normalizeSql(row2.sql)].join("\0")).join("\n");
1568
1595
  const actualHash = createHash("sha256").update(payload).digest("hex");
1569
1596
  if (actualHash !== CANONICAL_SCHEMA_HASH) {
1570
1597
  throw new Error(
@@ -1880,18 +1907,34 @@ async function acquireHomeRestoreLock(home) {
1880
1907
 
1881
1908
  // src/commands/backup.ts
1882
1909
  var createCmd = defineCommand5({
1883
- meta: { name: "create", description: "Download a tar.gz backup of ~/.bazilion from the server" },
1910
+ meta: { name: "create", description: "Create a client-side encrypted backup of ~/.bazilion" },
1884
1911
  args: {
1885
1912
  output: {
1886
1913
  type: "positional",
1887
1914
  required: false,
1888
- description: "Output file path (default: ./bazilion-backup-YYYY-MM-DD.tar.gz)"
1915
+ description: "Output path (default: .tar.gz.age with --recipient, otherwise .tar.gz)"
1916
+ },
1917
+ recipient: {
1918
+ type: "string",
1919
+ description: "age recipient (age1\u2026 or age1pq1\u2026); encrypts before writing output"
1920
+ },
1921
+ plaintext: {
1922
+ type: "boolean",
1923
+ description: "Explicitly permit a sensitive unencrypted .tar.gz backup"
1889
1924
  }
1890
1925
  },
1891
1926
  async run({ args }) {
1927
+ if (args.recipient && args.plaintext) {
1928
+ throw new Error("--recipient and --plaintext are mutually exclusive");
1929
+ }
1930
+ if (!args.recipient && !args.plaintext) {
1931
+ throw new Error("choose --recipient <age1\u2026> for encryption or explicitly pass --plaintext");
1932
+ }
1892
1933
  const cfg = loadClientConfig();
1893
1934
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1894
- const outAbs = resolve2(args.output ?? `bazilion-backup-${date}.tar.gz`);
1935
+ const defaultSuffix = args.recipient ? ".tar.gz.age" : ".tar.gz";
1936
+ const outAbs = resolve2(args.output ?? `bazilion-backup-${date}${defaultSuffix}`);
1937
+ if (existsSync2(outAbs)) throw new Error(`refusing to overwrite existing backup: ${outAbs}`);
1895
1938
  const configuredHome = resolve2(resolveCliPaths().home);
1896
1939
  const outputFromHome = relative(configuredHome, outAbs);
1897
1940
  if (!outputFromHome || !outputFromHome.startsWith("..") && !isAbsolute(outputFromHome)) {
@@ -1899,7 +1942,10 @@ var createCmd = defineCommand5({
1899
1942
  `backup output must be outside BAZILION_HOME (${configuredHome}) to avoid nesting backups`
1900
1943
  );
1901
1944
  }
1902
- console.log(`downloading backup \u2192 ${outAbs}`);
1945
+ if (!args.recipient) {
1946
+ console.warn("warning: creating a plaintext backup containing all Bazilion credentials");
1947
+ }
1948
+ console.log(`${args.recipient ? "encrypting" : "downloading"} backup \u2192 ${outAbs}`);
1903
1949
  const res = await fetch(`${cfg.serverUrl}/api/backup`, {
1904
1950
  headers: { authorization: `Bearer ${cfg.token}`, origin: cfg.serverUrl }
1905
1951
  });
@@ -1915,11 +1961,25 @@ var createCmd = defineCommand5({
1915
1961
  const downloadDir = mkdtempSync(resolve2(tmpdir(), "bazilion-download-"));
1916
1962
  const partial = resolve2(downloadDir, "backup.tar.gz");
1917
1963
  try {
1918
- await pipeline(
1919
- Readable.fromWeb(res.body),
1920
- createWriteStream(partial, { flags: "wx", mode: 384 })
1921
- );
1922
- await validateArchive(partial);
1964
+ if (args.recipient) {
1965
+ const [encryptSource, validateSource] = res.body.tee();
1966
+ const encrypter = new Encrypter();
1967
+ encrypter.addRecipient(args.recipient);
1968
+ const encrypted = await encrypter.encrypt(encryptSource);
1969
+ await Promise.all([
1970
+ pipeline(
1971
+ Readable.fromWeb(encrypted),
1972
+ createWriteStream(partial, { flags: "wx", mode: 384 })
1973
+ ),
1974
+ validateArchiveReadable(Readable.fromWeb(validateSource))
1975
+ ]);
1976
+ } else {
1977
+ await pipeline(
1978
+ Readable.fromWeb(res.body),
1979
+ createWriteStream(partial, { flags: "wx", mode: 384 })
1980
+ );
1981
+ }
1982
+ if (!args.recipient) await validateArchive(partial);
1923
1983
  try {
1924
1984
  renameSync2(partial, outAbs);
1925
1985
  } catch (error) {
@@ -2049,6 +2109,27 @@ async function validateArchive(file) {
2049
2109
  entries.push({ path, type: entry2.type, linkpath: entry2.linkpath });
2050
2110
  }
2051
2111
  });
2112
+ return validateArchiveEntries(entries);
2113
+ }
2114
+ async function validateArchiveReadable(stream) {
2115
+ const entries = [];
2116
+ const parser = list3({
2117
+ strict: true,
2118
+ onentry(entry2) {
2119
+ if (entry2.meta) return;
2120
+ const path = normalizeArchivePath(entry2.path);
2121
+ if (!path) return;
2122
+ if (!EXTRACTABLE_ENTRY_TYPES.has(entry2.type)) {
2123
+ throw new Error(`backup contains unsupported ${entry2.type} entry: ${path}`);
2124
+ }
2125
+ if (entry2.type === "SymbolicLink") validateArchiveSymbolicLink(path, entry2.linkpath);
2126
+ entries.push({ path, type: entry2.type, linkpath: entry2.linkpath });
2127
+ }
2128
+ });
2129
+ await pipeline(stream, parser);
2130
+ return validateArchiveEntries(entries);
2131
+ }
2132
+ function validateArchiveEntries(entries) {
2052
2133
  const byPath = /* @__PURE__ */ new Map();
2053
2134
  for (const entry2 of entries) {
2054
2135
  if (byPath.has(entry2.path)) throw new Error(`backup contains a duplicate path: ${entry2.path}`);
@@ -2107,7 +2188,7 @@ function validateDatabase(path, authToken) {
2107
2188
  db = new DatabaseSync(path, { readOnly: true });
2108
2189
  const integrity = db.prepare("PRAGMA integrity_check").all();
2109
2190
  if (integrity.length !== 1 || integrity[0]?.integrity_check !== "ok") {
2110
- const detail = integrity.map((row) => row.integrity_check).join("; ") || "no result";
2191
+ const detail = integrity.map((row2) => row2.integrity_check).join("; ") || "no result";
2111
2192
  throw new Error(`integrity_check failed: ${detail}`);
2112
2193
  }
2113
2194
  const foreignKeyErrors = db.prepare("PRAGMA foreign_key_check").all();
@@ -2118,7 +2199,7 @@ function validateDatabase(path, authToken) {
2118
2199
  const tokenHash = createHash3("sha256").update(authToken).digest("hex");
2119
2200
  const activeBootstrap = db.prepare(
2120
2201
  `SELECT 1 FROM web_tokens
2121
- WHERE token_hash = ? AND label = 'bootstrap' AND revoked_at IS NULL
2202
+ WHERE token_hash = ? AND kind = 'bootstrap' AND revoked_at IS NULL
2122
2203
  LIMIT 1`
2123
2204
  ).get(tokenHash);
2124
2205
  if (!activeBootstrap) {
@@ -2181,8 +2262,8 @@ function rebaseRestoredHomeDirectories(database, payload, targetHome) {
2181
2262
  const collection = table;
2182
2263
  const stagedRoot = resolve2(payload, collection);
2183
2264
  const targetRoot = resolve2(targetHome, collection);
2184
- for (const row of rows) {
2185
- const id = validateEntityId(row.id, idPattern, `${table} row`);
2265
+ for (const row2 of rows) {
2266
+ const id = validateEntityId(row2.id, idPattern, `${table} row`);
2186
2267
  const rowLabel = `${table} row ${JSON.stringify(id)}`;
2187
2268
  const stagedDir = containedChildDirectory(stagedRoot, id, rowLabel);
2188
2269
  requireExtractedDirectory(stagedDir, rowLabel);
@@ -2194,8 +2275,8 @@ function rebaseRestoredHomeDirectories(database, payload, targetHome) {
2194
2275
  }
2195
2276
  }
2196
2277
  const stagedTeamsRoot = resolve2(payload, "teams");
2197
- for (const row of teams) {
2198
- const id = validateEntityId(row.id, SLUG_ID, "teams row");
2278
+ for (const row2 of teams) {
2279
+ const id = validateEntityId(row2.id, SLUG_ID, "teams row");
2199
2280
  const rowLabel = `teams row ${JSON.stringify(id)}`;
2200
2281
  requireExtractedTeamSlot(containedChildDirectory(stagedTeamsRoot, id, rowLabel), rowLabel);
2201
2282
  }
@@ -2216,8 +2297,8 @@ function rebaseRestoredHomeDirectories(database, payload, targetHome) {
2216
2297
  const readProfile = db.prepare("SELECT dir FROM profiles WHERE id = ?");
2217
2298
  const readAgent = db.prepare("SELECT dir FROM agents WHERE id = ?");
2218
2299
  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) {
2300
+ const row2 = rewrite.table === "profiles" ? readProfile.get(rewrite.id) : readAgent.get(rewrite.id);
2301
+ if (row2?.dir !== rewrite.dir) {
2221
2302
  throw new Error(
2222
2303
  `could not verify rebased ${rewrite.table} row ${JSON.stringify(rewrite.id)}`
2223
2304
  );
@@ -2255,6 +2336,14 @@ function removeRebuildableQmdIndexes(payload) {
2255
2336
  }
2256
2337
  }
2257
2338
  }
2339
+ function revokeRestoredBrowserSessions(database) {
2340
+ const db = new DatabaseSync(database);
2341
+ try {
2342
+ db.prepare("UPDATE web_sessions SET revoked_at = ? WHERE revoked_at IS NULL").run(Date.now());
2343
+ } finally {
2344
+ db.close();
2345
+ }
2346
+ }
2258
2347
  function installValidatedPayload(payload, targetHome, staging, lifecycle, operations = { rename: renameSync2 }) {
2259
2348
  const previous = resolve2(staging, "previous-home");
2260
2349
  const hadPrevious = existsSync2(targetHome);
@@ -2368,6 +2457,37 @@ function fsyncDirectory(path) {
2368
2457
  if (fd !== null) closeSync2(fd);
2369
2458
  }
2370
2459
  }
2460
+ function fsyncFile(path) {
2461
+ const fd = openSync2(path, "r");
2462
+ try {
2463
+ fsyncSync2(fd);
2464
+ } finally {
2465
+ closeSync2(fd);
2466
+ }
2467
+ }
2468
+ var AGE_HEADER = Buffer.from("age-encryption.org/v1\n", "ascii");
2469
+ function isAgeEncrypted(path) {
2470
+ const fd = openSync2(path, "r");
2471
+ try {
2472
+ const header = Buffer.alloc(AGE_HEADER.length);
2473
+ return readSync(fd, header, 0, header.length, 0) === header.length && header.equals(AGE_HEADER);
2474
+ } finally {
2475
+ closeSync2(fd);
2476
+ }
2477
+ }
2478
+ function readAgeIdentity(rawPath) {
2479
+ const path = resolve2(rawPath);
2480
+ const entry2 = lstatSync2(path);
2481
+ if (!entry2.isFile()) throw new Error(`age identity is not a regular file: ${path}`);
2482
+ if (process.platform !== "win32" && (entry2.mode & 63) !== 0) {
2483
+ throw new Error(`age identity must be owner-only (chmod 600): ${path}`);
2484
+ }
2485
+ const identities = readFileSync4(path, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
2486
+ if (identities.length !== 1 || !identities[0]?.startsWith("AGE-SECRET-KEY-")) {
2487
+ throw new Error("age identity file must contain exactly one supported AGE-SECRET-KEY entry");
2488
+ }
2489
+ return identities[0];
2490
+ }
2371
2491
  var restoreCmd = defineCommand5({
2372
2492
  meta: {
2373
2493
  name: "restore",
@@ -2383,6 +2503,10 @@ var restoreCmd = defineCommand5({
2383
2503
  force: {
2384
2504
  type: "boolean",
2385
2505
  description: "Overwrite existing non-empty home (destroys existing data)"
2506
+ },
2507
+ identity: {
2508
+ type: "string",
2509
+ description: "Path to an age identity file required for encrypted backups"
2386
2510
  }
2387
2511
  },
2388
2512
  async run({ args }) {
@@ -2423,7 +2547,36 @@ var restoreCmd = defineCommand5({
2423
2547
  const payload = resolve2(staging, "payload");
2424
2548
  let preserveStaging = false;
2425
2549
  try {
2426
- copyFileSync(file, stagedArchive);
2550
+ if (isAgeEncrypted(file)) {
2551
+ if (!args.identity) {
2552
+ throw new Error("encrypted backup requires --identity <owner-only age identity file>");
2553
+ }
2554
+ const identity = readAgeIdentity(args.identity);
2555
+ const decrypter = new Decrypter();
2556
+ decrypter.addIdentity(identity);
2557
+ let decrypted;
2558
+ try {
2559
+ decrypted = await decrypter.decrypt(
2560
+ Readable.toWeb(createReadStream(file))
2561
+ );
2562
+ } catch {
2563
+ throw new Error("backup decryption failed: wrong identity or invalid encrypted header");
2564
+ }
2565
+ try {
2566
+ await pipeline(
2567
+ Readable.fromWeb(decrypted),
2568
+ createWriteStream(stagedArchive, { flags: "wx", mode: 384 })
2569
+ );
2570
+ } catch {
2571
+ throw new Error("backup decryption failed: ciphertext is truncated or modified");
2572
+ }
2573
+ } else {
2574
+ if (args.identity) {
2575
+ throw new Error("encrypted-only restore requested, but the backup is plaintext");
2576
+ }
2577
+ copyFileSync(file, stagedArchive);
2578
+ chmodSync2(stagedArchive, 384);
2579
+ }
2427
2580
  await validateArchive(stagedArchive);
2428
2581
  mkdirSync2(payload);
2429
2582
  await extract({
@@ -2445,6 +2598,7 @@ var restoreCmd = defineCommand5({
2445
2598
  const authToken = validateAuthFile(authFile);
2446
2599
  validateDatabase(database, authToken);
2447
2600
  rebaseRestoredHomeDirectories(database, payload, targetHome);
2601
+ revokeRestoredBrowserSessions(database);
2448
2602
  validateDatabase(database, authToken);
2449
2603
  removeRebuildableQmdIndexes(payload);
2450
2604
  console.log(`installing validated backup ${file} \u2192 ${targetHome}`);
@@ -2475,11 +2629,200 @@ var restoreCmd = defineCommand5({
2475
2629
  console.log("start the server with: bazilion serve (migrations apply automatically)");
2476
2630
  }
2477
2631
  });
2632
+ function decryptSecretEnvelope(raw, password) {
2633
+ const envelope = JSON.parse(raw);
2634
+ const key = pbkdf2Sync(password, Buffer.from(envelope.salt, "hex"), 1e5, 32, "sha256");
2635
+ const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(envelope.iv, "hex"));
2636
+ decipher.setAuthTag(Buffer.from(envelope.tag, "hex"));
2637
+ return Buffer.concat([
2638
+ decipher.update(Buffer.from(envelope.data, "hex")),
2639
+ decipher.final()
2640
+ ]).toString("utf8");
2641
+ }
2642
+ function encryptSecretEnvelope(value, password) {
2643
+ const salt = randomBytes(16);
2644
+ const key = pbkdf2Sync(password, salt, 1e5, 32, "sha256");
2645
+ const iv = randomBytes(12);
2646
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
2647
+ const data = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
2648
+ return JSON.stringify({
2649
+ salt: salt.toString("hex"),
2650
+ iv: iv.toString("hex"),
2651
+ tag: cipher.getAuthTag().toString("hex"),
2652
+ data: data.toString("hex")
2653
+ });
2654
+ }
2655
+ function credentialInventory(home) {
2656
+ const authFile = resolve2(home, "auth.json");
2657
+ const database = resolve2(home, "bazilion.db");
2658
+ const token = validateAuthFile(authFile);
2659
+ validateDatabase(database, token);
2660
+ const db = new DatabaseSync(database, { readOnly: true });
2661
+ try {
2662
+ const activeTokens = db.prepare(
2663
+ "SELECT COUNT(*) AS count FROM web_tokens WHERE revoked_at IS NULL AND kind = 'device' AND expires_at > ?"
2664
+ ).get(Date.now());
2665
+ const secretKeys = db.prepare("SELECT key FROM secrets ORDER BY key").all().map((row2) => row2.key);
2666
+ return { bootstrap: true, activeTokens: activeTokens.count, secretKeys };
2667
+ } finally {
2668
+ db.close();
2669
+ }
2670
+ }
2671
+ var inventoryCmd = defineCommand5({
2672
+ meta: { name: "inventory", description: "List credential classes captured by a backup/home" },
2673
+ args: { home: { type: "string", description: "Override BAZILION_HOME" } },
2674
+ run({ args }) {
2675
+ const home = resolve2(resolveCliPaths(args.home).home);
2676
+ const inventory = credentialInventory(home);
2677
+ console.log("credential classes present (values are never shown):");
2678
+ console.log(` bootstrap token: ${inventory.bootstrap ? "present" : "missing"}`);
2679
+ console.log(` active device/web/mobile tokens: ${inventory.activeTokens}`);
2680
+ for (const key of inventory.secretKeys) console.log(` stored secret: ${key}`);
2681
+ }
2682
+ });
2683
+ var recoveryGuideCmd = defineCommand5({
2684
+ meta: { name: "recovery-guide", description: "Print the credential-exposure recovery checklist" },
2685
+ run() {
2686
+ console.log("Bazilion credential exposure recovery:");
2687
+ console.log("1. Stop the Bazilion daemon and preserve the suspected archive for investigation.");
2688
+ console.log(
2689
+ "2. Run `bazilion backup inventory`, then `bazilion backup rotate-bootstrap --yes`."
2690
+ );
2691
+ console.log("3. Regenerate the Telegram bot token with BotFather and save the replacement.");
2692
+ console.log(
2693
+ "4. Disconnect/revoke the affected OpenAI session or grant, then authenticate again."
2694
+ );
2695
+ console.log("5. Rotate every provider or MCP credential named by the inventory.");
2696
+ console.log("6. Create and verify a fresh recipient-encrypted backup after external rotation.");
2697
+ console.log(
2698
+ "Local bootstrap rotation does not revoke copied Telegram/OpenAI/provider credentials."
2699
+ );
2700
+ }
2701
+ });
2702
+ var rotateBootstrapCmd = defineCommand5({
2703
+ meta: {
2704
+ name: "rotate-bootstrap",
2705
+ description: "Offline crash-safe bootstrap rotation; revokes every other local token"
2706
+ },
2707
+ args: {
2708
+ home: { type: "string", description: "Override BAZILION_HOME" },
2709
+ yes: { type: "boolean", required: true, description: "Confirm token revocation and home swap" }
2710
+ },
2711
+ async run({ args }) {
2712
+ if (!args.yes) throw new Error("--yes is required");
2713
+ const paths = resolveCliPaths(args.home);
2714
+ const targetHome = resolve2(paths.home);
2715
+ if (targetHome === parse(targetHome).root) {
2716
+ throw new Error(`refusing to rotate filesystem root: ${targetHome}`);
2717
+ }
2718
+ const lock = await acquireHomeRestoreLock(targetHome);
2719
+ const parent = dirname2(targetHome);
2720
+ const staging = mkdtempSync(resolve2(parent, ".bazilion-rotate-"));
2721
+ const payload = resolve2(staging, "payload");
2722
+ let preserveStaging = false;
2723
+ try {
2724
+ for (const serverUrl of configuredLoopbackUrls()) {
2725
+ if (await probeServerRunning(serverUrl)) {
2726
+ throw new Error(`bazilion server appears to be running at ${serverUrl} \u2014 stop it first.`);
2727
+ }
2728
+ }
2729
+ const oldAuth = JSON.parse(readFileSync4(paths.authFile, "utf8"));
2730
+ if (typeof oldAuth.token !== "string" || !oldAuth.token) {
2731
+ throw new Error("auth.json is missing the bootstrap token");
2732
+ }
2733
+ validateDatabase(resolve2(targetHome, "bazilion.db"), oldAuth.token);
2734
+ cpSync(targetHome, payload, {
2735
+ recursive: true,
2736
+ dereference: false,
2737
+ verbatimSymlinks: true,
2738
+ preserveTimestamps: true
2739
+ });
2740
+ const stagedDb = resolve2(payload, "bazilion.db");
2741
+ rmSync2(stagedDb, { force: true });
2742
+ const sourceDb = new DatabaseSync(resolve2(targetHome, "bazilion.db"), { readOnly: true });
2743
+ try {
2744
+ await sqliteBackup(sourceDb, stagedDb);
2745
+ } finally {
2746
+ sourceDb.close();
2747
+ }
2748
+ for (const suffix of ["-wal", "-shm", "-journal"])
2749
+ rmSync2(`${stagedDb}${suffix}`, { force: true });
2750
+ const newToken = randomBytes(24).toString("hex");
2751
+ const db = new DatabaseSync(stagedDb);
2752
+ try {
2753
+ db.exec("PRAGMA foreign_keys = ON; BEGIN IMMEDIATE");
2754
+ try {
2755
+ const rows = db.prepare("SELECT key, envelope FROM secrets ORDER BY key").all();
2756
+ const updateSecret = db.prepare(
2757
+ "UPDATE secrets SET envelope = ?, updated_at = ? WHERE key = ?"
2758
+ );
2759
+ for (const row2 of rows) {
2760
+ let value;
2761
+ try {
2762
+ value = decryptSecretEnvelope(row2.envelope, oldAuth.token);
2763
+ } catch {
2764
+ throw new Error(`stored secret is unreadable and rotation was aborted: ${row2.key}`);
2765
+ }
2766
+ updateSecret.run(encryptSecretEnvelope(value, newToken), Date.now(), row2.key);
2767
+ }
2768
+ const oldHash = createHash3("sha256").update(oldAuth.token).digest("hex");
2769
+ const newHash = createHash3("sha256").update(newToken).digest("hex");
2770
+ const bootstrap = db.prepare(
2771
+ "UPDATE web_tokens SET token_hash = ?, last_used_at = NULL, revoked_at = NULL WHERE token_hash = ? AND kind = 'bootstrap' AND revoked_at IS NULL"
2772
+ ).run(newHash, oldHash);
2773
+ if (bootstrap.changes !== 1) throw new Error("active bootstrap token row was not unique");
2774
+ const revokedAt = Date.now();
2775
+ db.prepare(
2776
+ "UPDATE web_tokens SET revoked_at = ? WHERE kind != 'bootstrap' AND revoked_at IS NULL"
2777
+ ).run(revokedAt);
2778
+ db.prepare("UPDATE web_sessions SET revoked_at = ? WHERE revoked_at IS NULL").run(
2779
+ revokedAt
2780
+ );
2781
+ db.exec("COMMIT");
2782
+ } catch (error) {
2783
+ db.exec("ROLLBACK");
2784
+ throw error;
2785
+ }
2786
+ } finally {
2787
+ db.close();
2788
+ }
2789
+ const stagedAuth = resolve2(payload, "auth.json");
2790
+ const authPartial = resolve2(payload, `.auth.json.${randomUUID2()}.installing`);
2791
+ writeFileSync2(authPartial, `${JSON.stringify({ ...oldAuth, token: newToken }, null, 2)}
2792
+ `, {
2793
+ mode: 384,
2794
+ flag: "wx"
2795
+ });
2796
+ renameSync2(authPartial, stagedAuth);
2797
+ chmodSync2(stagedAuth, 384);
2798
+ validateDatabase(stagedDb, newToken);
2799
+ const verification = credentialInventory(payload);
2800
+ if (!verification.bootstrap) throw new Error("rotated credential inventory is invalid");
2801
+ fsyncFile(stagedDb);
2802
+ fsyncFile(stagedAuth);
2803
+ fsyncDirectory(payload);
2804
+ try {
2805
+ installValidatedPayload(payload, targetHome, staging, lock);
2806
+ } catch (error) {
2807
+ if (error instanceof RestoreRecoveryRequiredError) preserveStaging = true;
2808
+ throw error;
2809
+ }
2810
+ console.log("bootstrap token rotated; all non-bootstrap Bazilion tokens were revoked");
2811
+ console.log("external Telegram, OpenAI, provider, and MCP credentials were not revoked");
2812
+ } finally {
2813
+ if (!preserveStaging) rmSync2(staging, { recursive: true, force: true });
2814
+ lock.release();
2815
+ }
2816
+ }
2817
+ });
2478
2818
  var backupCommand = defineCommand5({
2479
- meta: { name: "backup", description: "Create or restore a ~/.bazilion tar.gz backup" },
2819
+ meta: { name: "backup", description: "Encrypted backup, restore, and credential recovery" },
2480
2820
  subCommands: {
2481
2821
  create: createCmd,
2482
- restore: restoreCmd
2822
+ restore: restoreCmd,
2823
+ inventory: inventoryCmd,
2824
+ "recovery-guide": recoveryGuideCmd,
2825
+ "rotate-bootstrap": rotateBootstrapCmd
2483
2826
  }
2484
2827
  // `bazilion backup` with no positional falls through to citty's subcommand
2485
2828
  // prompt ("No command specified"). The previous default (auto-download to
@@ -2921,7 +3264,7 @@ var doctorCommand = defineCommand9({
2921
3264
  meta: { name: "doctor", description: "Diagnose your bazilion install" },
2922
3265
  async run() {
2923
3266
  const client = createClient2();
2924
- const r = await client.get("/api/health");
3267
+ const r = await client.get("/api/health/details");
2925
3268
  function check(label, condition, hint) {
2926
3269
  const mark = condition ? "\u2713" : "\u2717";
2927
3270
  const tail = !condition && hint ? ` \u2014 ${hint}` : "";
@@ -2966,22 +3309,24 @@ var doctorCommand = defineCommand9({
2966
3309
  }
2967
3310
  }
2968
3311
  console.log(
2969
- ` \u2713 lmstudio: ${r.providers.lmstudio.baseURL}${r.providers.lmstudio.hasKey ? " (api key set)" : ""}`
3312
+ ` \u2713 lmstudio: ${r.providers.lmstudio.customEndpointConfigured ? "custom endpoint configured" : "default local endpoint"}${r.providers.lmstudio.keyConfigured ? " (API key configured)" : ""}`
3313
+ );
3314
+ console.log(
3315
+ ` \u2713 ollama: ${r.providers.ollama.customEndpointConfigured ? "custom endpoint configured" : "default local endpoint"}`
2970
3316
  );
2971
- console.log(` \u2713 ollama: ${r.providers.ollama.baseURL}`);
2972
3317
  console.log();
2973
3318
  console.log("web search (at least one needed for web_search tool)");
2974
- if (r.webSearch.bravePreview) {
2975
- console.log(` \u2713 Brave Search (BRAVE_API_KEY set, ${r.webSearch.bravePreview})`);
3319
+ if (r.webSearch.braveConfigured) {
3320
+ console.log(" \u2713 Brave Search configured");
2976
3321
  } else {
2977
3322
  console.log(" - Brave Search (set BRAVE_API_KEY \u2014 free at https://brave.com/search/api/)");
2978
3323
  }
2979
- if (r.webSearch.searxngUrl) {
2980
- console.log(` \u2713 SearXNG: ${r.webSearch.searxngUrl}`);
3324
+ if (r.webSearch.searxngConfigured) {
3325
+ console.log(" \u2713 SearXNG configured");
2981
3326
  } else {
2982
3327
  console.log(" - SearXNG (set SEARXNG_URL if you self-host one)");
2983
3328
  }
2984
- if (!r.webSearch.bravePreview && !r.webSearch.searxngUrl) {
3329
+ if (!r.webSearch.braveConfigured && !r.webSearch.searxngConfigured) {
2985
3330
  console.log(" \u26A0 no search backend \u2014 web_search will error until one is configured");
2986
3331
  }
2987
3332
  console.log();
@@ -2998,35 +3343,7 @@ var doctorCommand = defineCommand9({
2998
3343
  ` ${r.scheduler.enabled ? "\u2713" : "-"} scheduler${r.scheduler.enabled ? ` (tick ${r.scheduler.tickMs}ms)` : " (BAZILION_SCHEDULER=off)"}`
2999
3344
  );
3000
3345
  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
- }
3346
+ for (const line of executionSecurityDoctorLines(r)) console.log(line);
3030
3347
  console.log();
3031
3348
  console.log("operational counts");
3032
3349
  console.log(` ${r.triggers.active} active trigger(s), ${r.triggers.disabled} disabled`);
@@ -3035,7 +3352,7 @@ var doctorCommand = defineCommand9({
3035
3352
  const hasProfiles = (r.database?.ok ? r.database.profiles : 0) > 0;
3036
3353
  const hasAgents = (r.database?.ok ? r.database.totalAgents : 0) > 0;
3037
3354
  console.log();
3038
- if (!r.ok) {
3355
+ if (doctorHasIssues(r)) {
3039
3356
  console.log("issues found \u2717");
3040
3357
  process.exit(1);
3041
3358
  }
@@ -3057,9 +3374,150 @@ var doctorCommand = defineCommand9({
3057
3374
  }
3058
3375
  }
3059
3376
  });
3377
+ function executionSecurityDoctorLines(report) {
3378
+ const configured = report.executionSecurity.configuredOperatorHttp;
3379
+ const protectedTurns = report.executionSecurity.protectedUnattendedTurns;
3380
+ const lines = ["configured operator HTTP (legacy \xB7 unprotected)"];
3381
+ if (!report.shellSecurity.ok) {
3382
+ lines.push(checkLine("valid shell-security configuration", false, report.shellSecurity.error));
3383
+ } else {
3384
+ if (report.shellSecurity.approvalMode === "dangerous") {
3385
+ lines.push(
3386
+ " - dangerous-command approval enabled",
3387
+ " web and TTY CLI turns can allow once; non-interactive operator requests auto-deny"
3388
+ );
3389
+ } else {
3390
+ lines.push(
3391
+ " - dangerous-command approval off",
3392
+ " set BAZILION_BASH_APPROVAL=dangerous to opt in"
3393
+ );
3394
+ }
3395
+ if (report.shellSecurity.sandboxMode === "docker") {
3396
+ lines.push(
3397
+ ` - coding surface: Docker only (${report.shellSecurity.sandboxImage})`,
3398
+ " workspace-only writable mount \xB7 bounded inputs/skills/memory read-only \xB7 network disabled",
3399
+ " host coding tools hidden",
3400
+ " policy syntax is valid; Docker and image availability are checked on execution",
3401
+ " commands fail closed if either is unavailable"
3402
+ );
3403
+ } else {
3404
+ lines.push(
3405
+ " - coding surface: host tools",
3406
+ " approval is a host-execution tripwire, not a filesystem sandbox",
3407
+ " set BAZILION_BASH_SANDBOX=docker to opt in"
3408
+ );
3409
+ }
3410
+ }
3411
+ lines.push(` - browser: ${configured.browser}`, ` - MCP: ${configured.mcp}`, "");
3412
+ lines.push(
3413
+ "protected unattended turns baseline (Telegram, schedules, inbox, approvals)",
3414
+ checkLine("protected base runtime ready", protectedTurns.baseRuntimeReady),
3415
+ " - coding surface: Docker only",
3416
+ checkLine(
3417
+ `Docker ready (${protectedTurns.docker.image})`,
3418
+ protectedTurns.docker.ready,
3419
+ protectedTurns.docker.reason ?? void 0
3420
+ ),
3421
+ checkLine("OpenAI Codex enabled", protectedTurns.openaiCodex.enabled),
3422
+ checkLine("ChatGPT connected", protectedTurns.openaiCodex.connected),
3423
+ ` - OpenAI access: ${openAIAccessStatus(protectedTurns.openaiCodex)}`,
3424
+ checkLine("OpenAI Codex baseline eligible", protectedTurns.openaiCodex.baselineEligible),
3425
+ ` \u2713 browser: ${protectedTurns.browser}`,
3426
+ ` \u2713 MCP: ${protectedTurns.mcp}`,
3427
+ " - every turn separately validates its selected normal/review model, bound OAuth refresh, and mounts/paths"
3428
+ );
3429
+ if (protectedTurns.remediation) {
3430
+ lines.push(` remediation: ${protectedTurns.remediation}`);
3431
+ }
3432
+ return lines;
3433
+ }
3434
+ function doctorHasIssues(report) {
3435
+ return !report.ok || !report.protectedWorkBaselineReady;
3436
+ }
3437
+ function openAIAccessStatus(status) {
3438
+ if (status.accessCurrent) return "current";
3439
+ if (status.refreshOnNextTurn) return "refresh on next turn";
3440
+ return "unavailable";
3441
+ }
3442
+ function checkLine(label, condition, hint) {
3443
+ const mark = condition ? "\u2713" : "\u2717";
3444
+ const tail = !condition && hint ? ` \u2014 ${hint}` : "";
3445
+ return ` ${mark} ${label}${tail}`;
3446
+ }
3060
3447
 
3061
- // src/commands/inbox.ts
3448
+ // src/commands/gateway.ts
3449
+ import { execFile } from "child_process";
3450
+ import { promisify } from "util";
3062
3451
  import { defineCommand as defineCommand10 } from "citty";
3452
+ var execFileAsync = promisify(execFile);
3453
+ function exactHttpsOrigin(raw) {
3454
+ if (!raw) throw new Error("BAZILION_PUBLIC_ORIGIN is required");
3455
+ const url = new URL(raw);
3456
+ if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash || url.origin !== raw.replace(/\/$/, "")) {
3457
+ throw new Error("BAZILION_PUBLIC_ORIGIN must be an exact HTTPS origin");
3458
+ }
3459
+ return url;
3460
+ }
3461
+ function listenerIsLoopback(output, port) {
3462
+ const suffix = `:${port}`;
3463
+ const listeners = output.split("\n").filter((line) => line.trim()).filter((line) => line.split(/\s+/).some((field) => field.endsWith(suffix)));
3464
+ return listeners.length > 0 && listeners.every((line) => /(?:127\.0\.0\.1|\[?::1\]?):\d+/.test(line));
3465
+ }
3466
+ function hasFunnel(value) {
3467
+ if (Array.isArray(value)) return value.some(hasFunnel);
3468
+ if (!value || typeof value !== "object") return false;
3469
+ return Object.entries(value).some(
3470
+ ([key, nested]) => /funnel/i.test(key) && nested !== false && nested !== null || hasFunnel(nested)
3471
+ );
3472
+ }
3473
+ var preflightCmd = defineCommand10({
3474
+ meta: { name: "preflight", description: "Read-only private Tailscale gateway safety check" },
3475
+ async run() {
3476
+ const publicOrigin = exactHttpsOrigin(process.env.BAZILION_PUBLIC_ORIGIN);
3477
+ const daemonPort = Number(process.env.PORT ?? 4321);
3478
+ const webPort = Number(process.env.WEB_PORT ?? 4322);
3479
+ const { stdout: listeners } = await execFileAsync("ss", ["-ltnH"], { timeout: 5e3 });
3480
+ if (!listenerIsLoopback(listeners, daemonPort)) {
3481
+ throw new Error(`daemon port ${daemonPort} is not listening exclusively on loopback`);
3482
+ }
3483
+ if (!listenerIsLoopback(listeners, webPort)) {
3484
+ throw new Error(`web port ${webPort} is not listening exclusively on loopback`);
3485
+ }
3486
+ let serveStatus;
3487
+ try {
3488
+ const { stdout: stdout2 } = await execFileAsync("tailscale", ["serve", "status", "--json"], {
3489
+ timeout: 1e4,
3490
+ maxBuffer: 1024 * 1024
3491
+ });
3492
+ serveStatus = JSON.parse(stdout2);
3493
+ } catch {
3494
+ throw new Error("could not obtain unambiguous `tailscale serve status --json` evidence");
3495
+ }
3496
+ if (hasFunnel(serveStatus))
3497
+ throw new Error("Tailscale Funnel is enabled; private Serve is required");
3498
+ const serialized = JSON.stringify(serveStatus);
3499
+ if (!serialized.includes(`127.0.0.1:${webPort}`) && !serialized.includes(`localhost:${webPort}`)) {
3500
+ throw new Error(`Tailscale Serve does not clearly target the loopback web port ${webPort}`);
3501
+ }
3502
+ const health = await createClient2().get("/api/health/details");
3503
+ if (!health.protectedWorkBaselineReady) {
3504
+ throw new Error("BAZ-027 protected-turn baseline is not ready");
3505
+ }
3506
+ console.log("private gateway preflight passed");
3507
+ console.log(`public origin: ${publicOrigin.origin}`);
3508
+ console.log(`daemon: loopback:${daemonPort}`);
3509
+ console.log(`web: loopback:${webPort}`);
3510
+ console.log("Tailscale: private Serve target present; Funnel absent");
3511
+ console.log("hosted Agent turns: protected baseline ready");
3512
+ }
3513
+ });
3514
+ var gatewayCommand = defineCommand10({
3515
+ meta: { name: "gateway", description: "Inspect the private web gateway posture" },
3516
+ subCommands: { preflight: preflightCmd }
3517
+ });
3518
+
3519
+ // src/commands/inbox.ts
3520
+ import { defineCommand as defineCommand11 } from "citty";
3063
3521
  function decodeText(payload) {
3064
3522
  try {
3065
3523
  const obj = JSON.parse(payload);
@@ -3075,7 +3533,7 @@ function messageRow(m) {
3075
3533
  const preview = text2.length > 60 ? `${text2.slice(0, 60)}\u2026` : text2;
3076
3534
  return [m.id, state, when, `from:${m.fromAgentId.slice(0, 8)}`, `"${preview}"`];
3077
3535
  }
3078
- var listCmd3 = defineCommand10({
3536
+ var listCmd3 = defineCommand11({
3079
3537
  meta: { name: "list", description: "List messages in an agent inbox" },
3080
3538
  args: {
3081
3539
  agent: { type: "positional", required: true, description: "Recipient agent id" },
@@ -3094,7 +3552,7 @@ var listCmd3 = defineCommand10({
3094
3552
  for (const line of columnize(messages.map(messageRow))) console.log(line);
3095
3553
  }
3096
3554
  });
3097
- var showCmd2 = defineCommand10({
3555
+ var showCmd2 = defineCommand11({
3098
3556
  meta: { name: "show", description: "Show a single message in full" },
3099
3557
  args: {
3100
3558
  id: { type: "positional", required: true, description: "Message id" }
@@ -3114,7 +3572,7 @@ var showCmd2 = defineCommand10({
3114
3572
  console.log(decodeText(m.payload));
3115
3573
  }
3116
3574
  });
3117
- var readCmd = defineCommand10({
3575
+ var readCmd = defineCommand11({
3118
3576
  meta: { name: "read", description: "Mark a message as read" },
3119
3577
  args: {
3120
3578
  id: { type: "positional", required: true, description: "Message id" }
@@ -3125,7 +3583,7 @@ var readCmd = defineCommand10({
3125
3583
  console.log(`marked read: ${args.id}`);
3126
3584
  }
3127
3585
  });
3128
- var loopBreaksCmd = defineCommand10({
3586
+ var loopBreaksCmd = defineCommand11({
3129
3587
  meta: { name: "loop-breaks", description: "List stopped agent-message chains" },
3130
3588
  args: {
3131
3589
  agent: { type: "positional", required: true, description: "Agent id" },
@@ -3153,7 +3611,7 @@ var loopBreaksCmd = defineCommand10({
3153
3611
  console.log(line);
3154
3612
  }
3155
3613
  });
3156
- var inboxCommand = defineCommand10({
3614
+ var inboxCommand = defineCommand11({
3157
3615
  meta: { name: "inbox", description: "Inspect agent inboxes" },
3158
3616
  subCommands: {
3159
3617
  list: listCmd3,
@@ -3164,8 +3622,8 @@ var inboxCommand = defineCommand10({
3164
3622
  });
3165
3623
 
3166
3624
  // src/commands/login.ts
3167
- import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
3168
- import { defineCommand as defineCommand11 } from "citty";
3625
+ import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
3626
+ import { defineCommand as defineCommand12 } from "citty";
3169
3627
  function normalizeServer(raw) {
3170
3628
  let url;
3171
3629
  try {
@@ -3179,13 +3637,17 @@ function normalizeServer(raw) {
3179
3637
  return `${url.origin}${url.pathname.replace(/\/$/, "")}`;
3180
3638
  }
3181
3639
  async function verifyToken(server, token) {
3182
- const res = await fetch(`${server}/api/health`, {
3640
+ const res = await fetch(`${server}/api/auth/whoami`, {
3183
3641
  headers: { authorization: `Bearer ${token}`, origin: server }
3184
3642
  });
3185
3643
  if (res.status === 401) throw new Error("server rejected token (401)");
3186
- if (!res.ok) throw new Error(`server returned ${res.status} from /api/health`);
3644
+ if (!res.ok) throw new Error(`server returned ${res.status} from /api/auth/whoami`);
3645
+ const owner = await res.json();
3646
+ if (owner.publicOrigin && owner.publicOrigin !== new URL(server).origin) {
3647
+ throw new Error("server canonical private origin does not match --server");
3648
+ }
3187
3649
  }
3188
- var loginCommand = defineCommand11({
3650
+ var loginCommand = defineCommand12({
3189
3651
  meta: {
3190
3652
  name: "login",
3191
3653
  description: "Save a remote bazilion server + token pair to auth.json"
@@ -3193,7 +3655,7 @@ var loginCommand = defineCommand11({
3193
3655
  args: {
3194
3656
  server: { type: "string", description: "Remote server URL (http[s]://...)" },
3195
3657
  token: { type: "string", description: "Token issued by `bazilion token create`" },
3196
- skipVerify: { type: "boolean", description: "Don't probe /api/health before saving" },
3658
+ skipVerify: { type: "boolean", description: "Don't verify /api/auth/whoami before saving" },
3197
3659
  clear: { type: "boolean", description: "Remove the stored remote instead of setting one" }
3198
3660
  },
3199
3661
  async run({ args }) {
@@ -3210,7 +3672,7 @@ var loginCommand = defineCommand11({
3210
3672
  return;
3211
3673
  }
3212
3674
  delete auth.remote;
3213
- writeFileSync2(paths.authFile, `${JSON.stringify(auth, null, 2)}
3675
+ writeFileSync3(paths.authFile, `${JSON.stringify(auth, null, 2)}
3214
3676
  `, { mode: 384 });
3215
3677
  console.log("cleared stored remote; CLI will fall back to the local daemon");
3216
3678
  return;
@@ -3225,7 +3687,7 @@ var loginCommand = defineCommand11({
3225
3687
  await verifyToken(server, args.token);
3226
3688
  }
3227
3689
  auth.remote = { server, token: args.token };
3228
- writeFileSync2(paths.authFile, `${JSON.stringify(auth, null, 2)}
3690
+ writeFileSync3(paths.authFile, `${JSON.stringify(auth, null, 2)}
3229
3691
  `, { mode: 384 });
3230
3692
  console.log(`saved remote: ${server}`);
3231
3693
  console.log("CLI will now target this server unless BAZILION_SERVER is set in the env.");
@@ -3233,7 +3695,7 @@ var loginCommand = defineCommand11({
3233
3695
  });
3234
3696
 
3235
3697
  // src/commands/mcp.ts
3236
- import { defineCommand as defineCommand12 } from "citty";
3698
+ import { defineCommand as defineCommand13 } from "citty";
3237
3699
  function serverRow(s) {
3238
3700
  const where = s.transport === "stdio" ? `${s.command} ${s.args.join(" ")}`.trim() : s.url ?? "";
3239
3701
  return [
@@ -3245,7 +3707,7 @@ function serverRow(s) {
3245
3707
  where
3246
3708
  ];
3247
3709
  }
3248
- var addCmd = defineCommand12({
3710
+ var addCmd = defineCommand13({
3249
3711
  meta: { name: "add", description: "Register an MCP server" },
3250
3712
  args: {
3251
3713
  name: { type: "positional", required: true, description: "Unique name ([a-zA-Z0-9_])" },
@@ -3272,7 +3734,7 @@ var addCmd = defineCommand12({
3272
3734
  console.log(`${server.id} ${server.name} ${server.transport}`);
3273
3735
  }
3274
3736
  });
3275
- var listCmd4 = defineCommand12({
3737
+ var listCmd4 = defineCommand13({
3276
3738
  meta: { name: "list", description: "List MCP servers" },
3277
3739
  async run() {
3278
3740
  const client = createClient2();
@@ -3284,7 +3746,7 @@ var listCmd4 = defineCommand12({
3284
3746
  for (const line of columnize(servers.map(serverRow))) console.log(line);
3285
3747
  }
3286
3748
  });
3287
- var showCmd3 = defineCommand12({
3749
+ var showCmd3 = defineCommand13({
3288
3750
  meta: { name: "show", description: "Show one MCP server" },
3289
3751
  args: { id: { type: "positional", required: true } },
3290
3752
  async run({ args }) {
@@ -3293,7 +3755,7 @@ var showCmd3 = defineCommand12({
3293
3755
  console.log(JSON.stringify(server, null, 2));
3294
3756
  }
3295
3757
  });
3296
- var rmCmd2 = defineCommand12({
3758
+ var rmCmd2 = defineCommand13({
3297
3759
  meta: { name: "rm", description: "Delete an MCP server" },
3298
3760
  args: { id: { type: "positional", required: true } },
3299
3761
  async run({ args }) {
@@ -3302,7 +3764,7 @@ var rmCmd2 = defineCommand12({
3302
3764
  console.log(`removed MCP server ${args.id}`);
3303
3765
  }
3304
3766
  });
3305
- var enableCmd = defineCommand12({
3767
+ var enableCmd = defineCommand13({
3306
3768
  meta: { name: "enable", description: "Enable an MCP server" },
3307
3769
  args: { id: { type: "positional", required: true } },
3308
3770
  async run({ args }) {
@@ -3311,7 +3773,7 @@ var enableCmd = defineCommand12({
3311
3773
  console.log(`enabled MCP server ${args.id}`);
3312
3774
  }
3313
3775
  });
3314
- var disableCmd = defineCommand12({
3776
+ var disableCmd = defineCommand13({
3315
3777
  meta: { name: "disable", description: "Disable an MCP server" },
3316
3778
  args: { id: { type: "positional", required: true } },
3317
3779
  async run({ args }) {
@@ -3320,7 +3782,7 @@ var disableCmd = defineCommand12({
3320
3782
  console.log(`disabled MCP server ${args.id}`);
3321
3783
  }
3322
3784
  });
3323
- var testCmd = defineCommand12({
3785
+ var testCmd = defineCommand13({
3324
3786
  meta: { name: "test", description: "Connect to a server and list its tools" },
3325
3787
  args: { id: { type: "positional", required: true } },
3326
3788
  async run({ args }) {
@@ -3338,7 +3800,7 @@ var testCmd = defineCommand12({
3338
3800
  for (const t of tools) console.log(` ${t.name} ${t.description}`);
3339
3801
  }
3340
3802
  });
3341
- var mcpCommand = defineCommand12({
3803
+ var mcpCommand = defineCommand13({
3342
3804
  meta: { name: "mcp", description: "Manage MCP servers" },
3343
3805
  subCommands: {
3344
3806
  add: addCmd,
@@ -3352,11 +3814,11 @@ var mcpCommand = defineCommand12({
3352
3814
  });
3353
3815
 
3354
3816
  // src/commands/memory.ts
3355
- import { defineCommand as defineCommand13 } from "citty";
3817
+ import { defineCommand as defineCommand14 } from "citty";
3356
3818
  function encodeKey(key) {
3357
3819
  return key.split("/").map(encodeURIComponent).join("/");
3358
3820
  }
3359
- var writeCmd = defineCommand13({
3821
+ var writeCmd = defineCommand14({
3360
3822
  meta: { name: "write", description: "Write an entry into a team's shared memory" },
3361
3823
  args: {
3362
3824
  team: { type: "positional", required: true, description: "Team slug" },
@@ -3372,7 +3834,7 @@ var writeCmd = defineCommand13({
3372
3834
  console.log(`wrote ${entry2.key} (${entry2.content.length} bytes)`);
3373
3835
  }
3374
3836
  });
3375
- var readCmd2 = defineCommand13({
3837
+ var readCmd2 = defineCommand14({
3376
3838
  meta: { name: "read", description: "Read an entry from a team's shared memory" },
3377
3839
  args: {
3378
3840
  team: { type: "positional", required: true, description: "Team slug" },
@@ -3386,7 +3848,7 @@ var readCmd2 = defineCommand13({
3386
3848
  console.log(entry2.content);
3387
3849
  }
3388
3850
  });
3389
- var searchCmd = defineCommand13({
3851
+ var searchCmd = defineCommand14({
3390
3852
  meta: { name: "search", description: "Search a team's shared memory" },
3391
3853
  args: {
3392
3854
  team: { type: "positional", required: true, description: "Team slug" },
@@ -3407,7 +3869,7 @@ var searchCmd = defineCommand13({
3407
3869
  }
3408
3870
  }
3409
3871
  });
3410
- var listCmd5 = defineCommand13({
3872
+ var listCmd5 = defineCommand14({
3411
3873
  meta: { name: "list", description: "List all entries in a team's shared memory" },
3412
3874
  args: {
3413
3875
  team: { type: "positional", required: true, description: "Team slug" }
@@ -3424,7 +3886,7 @@ var listCmd5 = defineCommand13({
3424
3886
  }
3425
3887
  }
3426
3888
  });
3427
- var rmCmd3 = defineCommand13({
3889
+ var rmCmd3 = defineCommand14({
3428
3890
  meta: { name: "rm", description: "Remove an entry from a team's shared memory" },
3429
3891
  args: {
3430
3892
  team: { type: "positional", required: true, description: "Team slug" },
@@ -3436,7 +3898,7 @@ var rmCmd3 = defineCommand13({
3436
3898
  console.log(`removed ${args.key}`);
3437
3899
  }
3438
3900
  });
3439
- var memoryCommand = defineCommand13({
3901
+ var memoryCommand = defineCommand14({
3440
3902
  meta: {
3441
3903
  name: "memory",
3442
3904
  description: "Manage a team's shared memory (BM25-indexed markdown notes)"
@@ -3452,8 +3914,8 @@ var memoryCommand = defineCommand13({
3452
3914
 
3453
3915
  // src/commands/profile.ts
3454
3916
  import { spawn as spawn2 } from "child_process";
3455
- import { randomBytes } from "crypto";
3456
- import { readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
3917
+ import { randomBytes as randomBytes2 } from "crypto";
3918
+ import { readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
3457
3919
  import { tmpdir as tmpdir2 } from "os";
3458
3920
  import { join as join4 } from "path";
3459
3921
 
@@ -3468,7 +3930,7 @@ var PROFILE_FILES = [
3468
3930
  ];
3469
3931
 
3470
3932
  // src/commands/profile.ts
3471
- import { defineCommand as defineCommand14 } from "citty";
3933
+ import { defineCommand as defineCommand15 } from "citty";
3472
3934
  function splitCsv(v) {
3473
3935
  if (!v) return void 0;
3474
3936
  const out = v.split(",").map((s) => s.trim()).filter(Boolean);
@@ -3479,7 +3941,7 @@ function parseSkillsMode(v) {
3479
3941
  if (v === "all" || v === "selected") return v;
3480
3942
  throw new Error(`--skills-mode must be 'all' or 'selected', got '${v}'`);
3481
3943
  }
3482
- var createCmd2 = defineCommand14({
3944
+ var createCmd2 = defineCommand15({
3483
3945
  meta: { name: "create", description: "Create a new profile" },
3484
3946
  args: {
3485
3947
  id: { type: "positional", required: true, description: "Profile slug" },
@@ -3542,7 +4004,7 @@ var createCmd2 = defineCommand14({
3542
4004
  console.log(`created profile ${profile.id} at ${profile.dir}`);
3543
4005
  }
3544
4006
  });
3545
- var listCmd6 = defineCommand14({
4007
+ var listCmd6 = defineCommand15({
3546
4008
  meta: { name: "list", description: "List profiles" },
3547
4009
  async run() {
3548
4010
  const client = createClient2();
@@ -3555,7 +4017,7 @@ var listCmd6 = defineCommand14({
3555
4017
  for (const line of columnize(rows)) console.log(line);
3556
4018
  }
3557
4019
  });
3558
- var showCmd4 = defineCommand14({
4020
+ var showCmd4 = defineCommand15({
3559
4021
  meta: { name: "show", description: "Show profile details" },
3560
4022
  args: {
3561
4023
  id: { type: "positional", required: true }
@@ -3580,7 +4042,7 @@ var showCmd4 = defineCommand14({
3580
4042
  }
3581
4043
  }
3582
4044
  });
3583
- var editCmd2 = defineCommand14({
4045
+ var editCmd2 = defineCommand15({
3584
4046
  meta: { name: "edit", description: "Open a profile file in $EDITOR" },
3585
4047
  args: {
3586
4048
  id: { type: "positional", required: true },
@@ -3599,8 +4061,8 @@ var editCmd2 = defineCommand14({
3599
4061
  const { content } = await client.get(
3600
4062
  `/api/profiles/${args.id}/files/${file}`
3601
4063
  );
3602
- const tmpPath = join4(tmpdir2(), `bazilion-${args.id}-${file}-${randomBytes(4).toString("hex")}`);
3603
- writeFileSync3(tmpPath, content);
4064
+ const tmpPath = join4(tmpdir2(), `bazilion-${args.id}-${file}-${randomBytes2(4).toString("hex")}`);
4065
+ writeFileSync4(tmpPath, content);
3604
4066
  try {
3605
4067
  const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
3606
4068
  const code = await new Promise((resolve4, reject) => {
@@ -3625,7 +4087,7 @@ var editCmd2 = defineCommand14({
3625
4087
  }
3626
4088
  }
3627
4089
  });
3628
- var updateCmd = defineCommand14({
4090
+ var updateCmd = defineCommand15({
3629
4091
  meta: {
3630
4092
  name: "update",
3631
4093
  description: "Update profile settings (name, model, skills-mode, skills)"
@@ -3659,7 +4121,7 @@ var updateCmd = defineCommand14({
3659
4121
  console.log(`updated profile ${profile.id}`);
3660
4122
  }
3661
4123
  });
3662
- var deleteCmd2 = defineCommand14({
4124
+ var deleteCmd2 = defineCommand15({
3663
4125
  meta: { name: "delete", description: "Permanently delete a profile and its files" },
3664
4126
  args: {
3665
4127
  id: { type: "positional", required: true }
@@ -3670,7 +4132,7 @@ var deleteCmd2 = defineCommand14({
3670
4132
  console.log(`deleted profile ${args.id}`);
3671
4133
  }
3672
4134
  });
3673
- var profileCommand = defineCommand14({
4135
+ var profileCommand = defineCommand15({
3674
4136
  meta: { name: "profile", description: "Manage profiles" },
3675
4137
  subCommands: {
3676
4138
  create: createCmd2,
@@ -3683,8 +4145,8 @@ var profileCommand = defineCommand14({
3683
4145
  });
3684
4146
 
3685
4147
  // src/commands/provider.ts
3686
- import { defineCommand as defineCommand15 } from "citty";
3687
- var listCmd7 = defineCommand15({
4148
+ import { defineCommand as defineCommand16 } from "citty";
4149
+ var listCmd7 = defineCommand16({
3688
4150
  meta: {
3689
4151
  name: "list",
3690
4152
  description: "List all providers with enabled/disabled state and curated model counts"
@@ -3710,7 +4172,7 @@ var listCmd7 = defineCommand15({
3710
4172
  for (const line of columnize(rows)) console.log(line);
3711
4173
  }
3712
4174
  });
3713
- var modelsCmd = defineCommand15({
4175
+ var modelsCmd = defineCommand16({
3714
4176
  meta: {
3715
4177
  name: "models",
3716
4178
  description: "Show curated + catalog + live models for one provider"
@@ -3742,7 +4204,7 @@ var modelsCmd = defineCommand15({
3742
4204
  }
3743
4205
  }
3744
4206
  });
3745
- var modelsSetCmd = defineCommand15({
4207
+ var modelsSetCmd = defineCommand16({
3746
4208
  meta: {
3747
4209
  name: "models-set",
3748
4210
  description: "Replace the curated model list for a provider (comma-separated)"
@@ -3767,7 +4229,7 @@ var modelsSetCmd = defineCommand15({
3767
4229
  for (const m of saved.models) console.log(` ${m}`);
3768
4230
  }
3769
4231
  });
3770
- var enableCmd2 = defineCommand15({
4232
+ var enableCmd2 = defineCommand16({
3771
4233
  meta: { name: "enable", description: "Toggle a provider on so agents can use it" },
3772
4234
  args: {
3773
4235
  name: { type: "positional", required: true, description: "Provider id (e.g. anthropic)" }
@@ -3781,7 +4243,7 @@ var enableCmd2 = defineCommand15({
3781
4243
  console.log(`${res.name}: enabled`);
3782
4244
  }
3783
4245
  });
3784
- var disableCmd2 = defineCommand15({
4246
+ var disableCmd2 = defineCommand16({
3785
4247
  meta: {
3786
4248
  name: "disable",
3787
4249
  description: "Toggle a provider off \u2014 agents will refuse its model strings"
@@ -3798,7 +4260,7 @@ var disableCmd2 = defineCommand15({
3798
4260
  console.log(`${res.name}: disabled`);
3799
4261
  }
3800
4262
  });
3801
- var testCmd2 = defineCommand15({
4263
+ var testCmd2 = defineCommand16({
3802
4264
  meta: { name: "test", description: "Send a single chat to a model and print the reply" },
3803
4265
  args: {
3804
4266
  model: {
@@ -3819,7 +4281,7 @@ var testCmd2 = defineCommand15({
3819
4281
  }
3820
4282
  }
3821
4283
  });
3822
- var providerCommand = defineCommand15({
4284
+ var providerCommand = defineCommand16({
3823
4285
  meta: { name: "provider", description: "Manage and test LLM providers" },
3824
4286
  subCommands: {
3825
4287
  list: listCmd7,
@@ -3832,8 +4294,8 @@ var providerCommand = defineCommand15({
3832
4294
  });
3833
4295
 
3834
4296
  // src/commands/send.ts
3835
- import { defineCommand as defineCommand16 } from "citty";
3836
- var sendCommand = defineCommand16({
4297
+ import { defineCommand as defineCommand17 } from "citty";
4298
+ var sendCommand = defineCommand17({
3837
4299
  meta: {
3838
4300
  name: "send",
3839
4301
  description: "Send a message from one agent to another"
@@ -3867,11 +4329,11 @@ var sendCommand = defineCommand16({
3867
4329
  import { spawn as spawn3 } from "child_process";
3868
4330
  import { existsSync as existsSync5 } from "fs";
3869
4331
  import { join as join5 } from "path";
3870
- import { defineCommand as defineCommand17 } from "citty";
4332
+ import { defineCommand as defineCommand18 } from "citty";
3871
4333
  var bundledDaemonEntry2 = join5(import.meta.dirname, "daemon.js");
3872
4334
  var sourceDaemonEntry2 = join5(import.meta.dirname, "..", "..", "..", "daemon", "src", "index.ts");
3873
4335
  var daemonEntry2 = existsSync5(bundledDaemonEntry2) ? bundledDaemonEntry2 : sourceDaemonEntry2;
3874
- var serveCommand = defineCommand17({
4336
+ var serveCommand = defineCommand18({
3875
4337
  meta: {
3876
4338
  name: "serve",
3877
4339
  description: "Start the bazilion daemon (HTTP API)"
@@ -3937,17 +4399,54 @@ var serveCommand = defineCommand17({
3937
4399
  }
3938
4400
  });
3939
4401
 
4402
+ // src/commands/session.ts
4403
+ import { defineCommand as defineCommand19 } from "citty";
4404
+ function row(session) {
4405
+ const state = session.revokedAt ? "revoked" : session.absoluteExpiresAt <= Date.now() || session.idleExpiresAt <= Date.now() ? "expired" : "active";
4406
+ return [
4407
+ session.id,
4408
+ state,
4409
+ session.current ? "current" : "",
4410
+ session.deviceLabel,
4411
+ `idle: ${new Date(session.idleExpiresAt).toISOString()}`,
4412
+ `absolute: ${new Date(session.absoluteExpiresAt).toISOString()}`
4413
+ ];
4414
+ }
4415
+ var listCmd8 = defineCommand19({
4416
+ meta: { name: "list", description: "List browser sessions" },
4417
+ async run() {
4418
+ const { sessions } = await createClient2().get("/api/sessions");
4419
+ if (sessions.length === 0) {
4420
+ console.log("(no active browser sessions)");
4421
+ return;
4422
+ }
4423
+ for (const line of columnize(sessions.map(row))) console.log(line);
4424
+ }
4425
+ });
4426
+ var revokeCmd = defineCommand19({
4427
+ meta: { name: "revoke", description: "Revoke one browser session" },
4428
+ args: { id: { type: "positional", required: true } },
4429
+ async run({ args }) {
4430
+ await createClient2().del(`/api/sessions/${encodeURIComponent(args.id)}`);
4431
+ console.log(`revoked browser session ${args.id}`);
4432
+ }
4433
+ });
4434
+ var sessionCommand = defineCommand19({
4435
+ meta: { name: "session", description: "Manage browser sessions" },
4436
+ subCommands: { list: listCmd8, revoke: revokeCmd }
4437
+ });
4438
+
3940
4439
  // src/commands/skill.ts
3941
4440
  import { existsSync as existsSync6, readFileSync as readFileSync7, statSync } from "fs";
3942
4441
  import { basename as basename4, resolve as resolve3 } from "path";
3943
- import { defineCommand as defineCommand18 } from "citty";
4442
+ import { defineCommand as defineCommand20 } from "citty";
3944
4443
  function printFindings(findings, prefix = " ") {
3945
4444
  for (const f of findings) {
3946
4445
  const line = f.line ? ` line ${f.line}` : "";
3947
4446
  console.log(`${prefix}${f.severity}: ${f.code}${line} - ${f.message}`);
3948
4447
  }
3949
4448
  }
3950
- var listCmd8 = defineCommand18({
4449
+ var listCmd9 = defineCommand20({
3951
4450
  meta: { name: "list", description: "List installed skills (or those attached to an agent)" },
3952
4451
  args: {
3953
4452
  agent: { type: "string", description: "Filter to skills attached to this agent" }
@@ -3988,7 +4487,7 @@ var listCmd8 = defineCommand18({
3988
4487
  }
3989
4488
  }
3990
4489
  });
3991
- var importCmd = defineCommand18({
4490
+ var importCmd = defineCommand20({
3992
4491
  meta: {
3993
4492
  name: "import",
3994
4493
  description: "Import skills from openclaw, a directory, or a local .zip archive"
@@ -4057,7 +4556,7 @@ var importCmd = defineCommand18({
4057
4556
  }
4058
4557
  }
4059
4558
  });
4060
- var rmCmd4 = defineCommand18({
4559
+ var rmCmd4 = defineCommand20({
4061
4560
  meta: { name: "rm", description: "Remove an installed skill" },
4062
4561
  args: {
4063
4562
  name: { type: "positional", required: true }
@@ -4068,10 +4567,10 @@ var rmCmd4 = defineCommand18({
4068
4567
  console.log(`removed skill ${args.name}`);
4069
4568
  }
4070
4569
  });
4071
- var skillCommand = defineCommand18({
4570
+ var skillCommand = defineCommand20({
4072
4571
  meta: { name: "skill", description: "Manage the skill library" },
4073
4572
  subCommands: {
4074
- list: listCmd8,
4573
+ list: listCmd9,
4075
4574
  import: importCmd,
4076
4575
  rm: rmCmd4
4077
4576
  }
@@ -4080,11 +4579,11 @@ var skillCommand = defineCommand18({
4080
4579
  // src/commands/team.ts
4081
4580
  import { readFileSync as readFileSync9 } from "fs";
4082
4581
  import { stdin as stdin2 } from "process";
4083
- import { defineCommand as defineCommand20 } from "citty";
4582
+ import { defineCommand as defineCommand22 } from "citty";
4084
4583
 
4085
4584
  // src/commands/team-policy.ts
4086
4585
  import { readFileSync as readFileSync8 } from "fs";
4087
- import { defineCommand as defineCommand19 } from "citty";
4586
+ import { defineCommand as defineCommand21 } from "citty";
4088
4587
 
4089
4588
  // src/team-interchange.ts
4090
4589
  var TEAM_POLICY_INTERCHANGE_VERSION = 1;
@@ -4332,7 +4831,7 @@ function printPolicy(detail) {
4332
4831
  console.log(line);
4333
4832
  }
4334
4833
  }
4335
- var show2 = defineCommand19({
4834
+ var show2 = defineCommand21({
4336
4835
  meta: { name: "show", description: "Show the one effective live policy for a Team" },
4337
4836
  args: {
4338
4837
  team: { type: "positional", required: true },
@@ -4346,7 +4845,7 @@ var show2 = defineCommand19({
4346
4845
  else printPolicy(detail);
4347
4846
  }
4348
4847
  });
4349
- var exportCommand = defineCommand19({
4848
+ var exportCommand = defineCommand21({
4350
4849
  meta: { name: "export", description: "Export a revisioned Team policy document" },
4351
4850
  args: { team: { type: "positional", required: true } },
4352
4851
  async run({ args }) {
@@ -4370,7 +4869,7 @@ function printEdgeDiff(current, edges) {
4370
4869
  for (const value of diff2.added) console.log(` + ${value}`);
4371
4870
  for (const value of diff2.removed) console.log(` - ${value}`);
4372
4871
  }
4373
- var importCommand = defineCommand19({
4872
+ var importCommand = defineCommand21({
4374
4873
  meta: {
4375
4874
  name: "import",
4376
4875
  description: "Validate, diff, and explicitly replace a Team policy"
@@ -4425,7 +4924,7 @@ var importCommand = defineCommand19({
4425
4924
  console.log(`updated Team ${args.team} policy to revision ${updated.teamPolicy.revision}`);
4426
4925
  }
4427
4926
  });
4428
- var diff = defineCommand19({
4927
+ var diff = defineCommand21({
4429
4928
  meta: { name: "diff", description: "Compare live policy with its retained baseline source" },
4430
4929
  args: {
4431
4930
  team: { type: "positional", required: true },
@@ -4449,7 +4948,7 @@ var diff = defineCommand19({
4449
4948
  );
4450
4949
  }
4451
4950
  });
4452
- var evaluate = defineCommand19({
4951
+ var evaluate = defineCommand21({
4453
4952
  meta: {
4454
4953
  name: "evaluate",
4455
4954
  description: "Evaluate a communication path without sending or recording a block"
@@ -4480,7 +4979,7 @@ var evaluate = defineCommand19({
4480
4979
  );
4481
4980
  }
4482
4981
  });
4483
- var blocks = defineCommand19({
4982
+ var blocks = defineCommand21({
4484
4983
  meta: { name: "blocks", description: "List paginated durable communication denials" },
4485
4984
  args: {
4486
4985
  team: { type: "positional", required: true },
@@ -4529,7 +5028,7 @@ var blocks = defineCommand19({
4529
5028
  if (page.nextCursor) console.log(`next cursor: ${page.nextCursor}`);
4530
5029
  }
4531
5030
  });
4532
- var teamPolicyCommand = defineCommand19({
5031
+ var teamPolicyCommand = defineCommand21({
4533
5032
  meta: { name: "policy", description: "Inspect and manage the Team-owned live policy" },
4534
5033
  subCommands: {
4535
5034
  show: show2,
@@ -4542,7 +5041,7 @@ var teamPolicyCommand = defineCommand19({
4542
5041
  });
4543
5042
 
4544
5043
  // src/commands/team.ts
4545
- var addCmd2 = defineCommand20({
5044
+ var addCmd2 = defineCommand22({
4546
5045
  meta: {
4547
5046
  name: "add",
4548
5047
  description: "Register a team at ~/.bazilion/teams/<slug>/ (use --link to point at an existing tree)"
@@ -4566,7 +5065,7 @@ var addCmd2 = defineCommand20({
4566
5065
  console.log(`registered team ${g.id} at ${g.path}`);
4567
5066
  }
4568
5067
  });
4569
- var listCmd9 = defineCommand20({
5068
+ var listCmd10 = defineCommand22({
4570
5069
  meta: { name: "list", description: "List teams" },
4571
5070
  async run() {
4572
5071
  const client = createClient2();
@@ -4579,7 +5078,7 @@ var listCmd9 = defineCommand20({
4579
5078
  for (const line of columnize(rows)) console.log(line);
4580
5079
  }
4581
5080
  });
4582
- var rmCmd5 = defineCommand20({
5081
+ var rmCmd5 = defineCommand22({
4583
5082
  meta: { name: "rm", description: "Remove a team registration" },
4584
5083
  args: {
4585
5084
  id: { type: "positional", required: true }
@@ -4600,7 +5099,7 @@ async function readStdin() {
4600
5099
  for await (const chunk of stdin2) chunks.push(chunk);
4601
5100
  return Buffer.concat(chunks).toString("utf8");
4602
5101
  }
4603
- var userMdShowCmd = defineCommand20({
5102
+ var userMdShowCmd = defineCommand22({
4604
5103
  meta: { name: "show", description: "Print the team's USER.md" },
4605
5104
  args: { id: { type: "positional", required: true } },
4606
5105
  async run({ args }) {
@@ -4610,7 +5109,7 @@ var userMdShowCmd = defineCommand20({
4610
5109
  if (g.userMd && !g.userMd.endsWith("\n")) process.stdout.write("\n");
4611
5110
  }
4612
5111
  });
4613
- var userMdSetCmd = defineCommand20({
5112
+ var userMdSetCmd = defineCommand22({
4614
5113
  meta: {
4615
5114
  name: "set",
4616
5115
  description: "Replace the team's USER.md (from --file, --from-stdin, or inline text)"
@@ -4636,7 +5135,7 @@ var userMdSetCmd = defineCommand20({
4636
5135
  console.log(`updated USER.md for team ${args.id} (${content.length} bytes)`);
4637
5136
  }
4638
5137
  });
4639
- var userMdClearCmd = defineCommand20({
5138
+ var userMdClearCmd = defineCommand22({
4640
5139
  meta: { name: "clear", description: "Clear the team's USER.md to empty" },
4641
5140
  args: { id: { type: "positional", required: true } },
4642
5141
  async run({ args }) {
@@ -4646,7 +5145,7 @@ var userMdClearCmd = defineCommand20({
4646
5145
  console.log(`cleared USER.md for team ${args.id}`);
4647
5146
  }
4648
5147
  });
4649
- var userMdCmd = defineCommand20({
5148
+ var userMdCmd = defineCommand22({
4650
5149
  meta: { name: "user-md", description: "View or edit a team's USER.md" },
4651
5150
  subCommands: {
4652
5151
  show: userMdShowCmd,
@@ -4654,7 +5153,7 @@ var userMdCmd = defineCommand20({
4654
5153
  clear: userMdClearCmd
4655
5154
  }
4656
5155
  });
4657
- var topicFormatShowCmd = defineCommand20({
5156
+ var topicFormatShowCmd = defineCommand22({
4658
5157
  meta: { name: "show", description: "Print the team's Telegram topic-name template" },
4659
5158
  args: { id: { type: "positional", required: true } },
4660
5159
  async run({ args }) {
@@ -4663,7 +5162,7 @@ var topicFormatShowCmd = defineCommand20({
4663
5162
  console.log(g.telegramTopicNameFormat ?? "(default naming)");
4664
5163
  }
4665
5164
  });
4666
- var topicFormatSetCmd = defineCommand20({
5165
+ var topicFormatSetCmd = defineCommand22({
4667
5166
  meta: {
4668
5167
  name: "set",
4669
5168
  description: "Set the topic-name template. Tokens: {agent.name} {team.name} {team.slug} (must include {agent.name})"
@@ -4683,7 +5182,7 @@ var topicFormatSetCmd = defineCommand20({
4683
5182
  console.log(`set topic-name template for team ${args.id}: ${g.telegramTopicNameFormat}`);
4684
5183
  }
4685
5184
  });
4686
- var topicFormatClearCmd = defineCommand20({
5185
+ var topicFormatClearCmd = defineCommand22({
4687
5186
  meta: { name: "clear", description: "Clear the template (revert to built-in naming)" },
4688
5187
  args: { id: { type: "positional", required: true } },
4689
5188
  async run({ args }) {
@@ -4693,7 +5192,7 @@ var topicFormatClearCmd = defineCommand20({
4693
5192
  console.log(`cleared topic-name template for team ${args.id}`);
4694
5193
  }
4695
5194
  });
4696
- var topicFormatCmd = defineCommand20({
5195
+ var topicFormatCmd = defineCommand22({
4697
5196
  meta: {
4698
5197
  name: "topic-format",
4699
5198
  description: "View or edit a team's Telegram forum-topic name template"
@@ -4704,11 +5203,11 @@ var topicFormatCmd = defineCommand20({
4704
5203
  clear: topicFormatClearCmd
4705
5204
  }
4706
5205
  });
4707
- var teamCommand = defineCommand20({
5206
+ var teamCommand = defineCommand22({
4708
5207
  meta: { name: "team", description: "Manage teams (collaboration contexts)" },
4709
5208
  subCommands: {
4710
5209
  add: addCmd2,
4711
- list: listCmd9,
5210
+ list: listCmd10,
4712
5211
  rm: rmCmd5,
4713
5212
  "user-md": userMdCmd,
4714
5213
  "topic-format": topicFormatCmd,
@@ -4718,7 +5217,7 @@ var teamCommand = defineCommand20({
4718
5217
 
4719
5218
  // src/commands/team-template.ts
4720
5219
  import { readFileSync as readFileSync10 } from "fs";
4721
- import { defineCommand as defineCommand21 } from "citty";
5220
+ import { defineCommand as defineCommand23 } from "citty";
4722
5221
  function jsonFile(path) {
4723
5222
  try {
4724
5223
  return JSON.parse(readFileSync10(path, "utf8"));
@@ -4746,7 +5245,7 @@ function printTeam(detail) {
4746
5245
  console.log(line);
4747
5246
  }
4748
5247
  }
4749
- var list4 = defineCommand21({
5248
+ var list4 = defineCommand23({
4750
5249
  meta: { name: "list", description: "List canonical Team templates" },
4751
5250
  args: { json: { type: "boolean", description: "Emit stable JSON" } },
4752
5251
  async run({ args }) {
@@ -4755,12 +5254,12 @@ var list4 = defineCommand21({
4755
5254
  if (!rows.length) return console.log("(no Team templates)");
4756
5255
  for (const line of columnize([
4757
5256
  ["id", "revision", "slots", "name"],
4758
- ...rows.map((row) => [row.id, String(row.currentRevision), String(row.slotCount), row.name])
5257
+ ...rows.map((row2) => [row2.id, String(row2.currentRevision), String(row2.slotCount), row2.name])
4759
5258
  ]))
4760
5259
  console.log(line);
4761
5260
  }
4762
5261
  });
4763
- var show3 = defineCommand21({
5262
+ var show3 = defineCommand23({
4764
5263
  meta: { name: "show", description: "Show a canonical Team template" },
4765
5264
  args: {
4766
5265
  id: { type: "positional", required: true },
@@ -4774,7 +5273,7 @@ var show3 = defineCommand21({
4774
5273
  else printTeam(detail);
4775
5274
  }
4776
5275
  });
4777
- var exportCommand2 = defineCommand21({
5276
+ var exportCommand2 = defineCommand23({
4778
5277
  meta: {
4779
5278
  name: "export",
4780
5279
  description: "Export a portable versioned Team-template document to stdout"
@@ -4814,7 +5313,7 @@ function printImportDiff(current, document) {
4814
5313
  for (const value of diff2.added) console.log(` + ${value}`);
4815
5314
  for (const value of diff2.removed) console.log(` - ${value}`);
4816
5315
  }
4817
- var importCommand2 = defineCommand21({
5316
+ var importCommand2 = defineCommand23({
4818
5317
  meta: {
4819
5318
  name: "import",
4820
5319
  description: "Validate, diff, and explicitly apply a portable Team-template document"
@@ -4895,7 +5394,7 @@ var importCommand2 = defineCommand21({
4895
5394
  );
4896
5395
  }
4897
5396
  });
4898
- var teamTemplateCommand = defineCommand21({
5397
+ var teamTemplateCommand = defineCommand23({
4899
5398
  meta: {
4900
5399
  name: "team-template",
4901
5400
  description: "Inspect and exchange canonical Team templates"
@@ -4904,8 +5403,8 @@ var teamTemplateCommand = defineCommand21({
4904
5403
  });
4905
5404
 
4906
5405
  // src/commands/telegram.ts
4907
- import { defineCommand as defineCommand22 } from "citty";
4908
- var setCmd2 = defineCommand22({
5406
+ import { defineCommand as defineCommand24 } from "citty";
5407
+ var setCmd2 = defineCommand24({
4909
5408
  meta: {
4910
5409
  name: "set",
4911
5410
  description: "Save bot token + supergroup chat ID (paired write)"
@@ -4923,7 +5422,7 @@ var setCmd2 = defineCommand22({
4923
5422
  console.log(`saved \xB7 token ${state.botTokenPreview} \xB7 chat ${state.chatId}`);
4924
5423
  }
4925
5424
  });
4926
- var clearCmd = defineCommand22({
5425
+ var clearCmd = defineCommand24({
4927
5426
  meta: {
4928
5427
  name: "clear",
4929
5428
  description: "Remove stored bot token + chat ID"
@@ -4934,7 +5433,7 @@ var clearCmd = defineCommand22({
4934
5433
  console.log("cleared");
4935
5434
  }
4936
5435
  });
4937
- var showCmd5 = defineCommand22({
5436
+ var showCmd5 = defineCommand24({
4938
5437
  meta: {
4939
5438
  name: "show",
4940
5439
  description: "Show what credentials are stored (token is masked)"
@@ -4955,7 +5454,7 @@ var showCmd5 = defineCommand22({
4955
5454
  }
4956
5455
  }
4957
5456
  });
4958
- var reconnectCmd = defineCommand22({
5457
+ var reconnectCmd = defineCommand24({
4959
5458
  meta: {
4960
5459
  name: "reconnect",
4961
5460
  description: "Apply a pending supergroup chat-id migration + re-activate the bot"
@@ -4966,7 +5465,7 @@ var reconnectCmd = defineCommand22({
4966
5465
  console.log(`reconnected \xB7 chat ${state.chatId}`);
4967
5466
  }
4968
5467
  });
4969
- var configCmd = defineCommand22({
5468
+ var configCmd = defineCommand24({
4970
5469
  meta: {
4971
5470
  name: "config",
4972
5471
  description: "Manage Telegram credentials (bot token + chat ID)"
@@ -4977,7 +5476,7 @@ var configCmd = defineCommand22({
4977
5476
  show: showCmd5
4978
5477
  }
4979
5478
  });
4980
- var healthCmd = defineCommand22({
5479
+ var healthCmd = defineCommand24({
4981
5480
  meta: {
4982
5481
  name: "health",
4983
5482
  description: "Run the four-step preflight against the Telegram Bot API"
@@ -5007,11 +5506,16 @@ var healthCmd = defineCommand22({
5007
5506
  console.log(line(p.isForum, "forum topics enabled", String(p.isForum)));
5008
5507
  console.log(line(p.hasManageTopics, "can_manage_topics", String(p.hasManageTopics)));
5009
5508
  console.log(line(p.privacyModeOff, "Privacy Mode is OFF", String(p.privacyModeOff)));
5010
- const allOk = p.botUsername.length > 0 && p.chatTitle.length > 0 && p.isForum && p.hasManageTopics && p.privacyModeOff;
5509
+ console.log(line(p.chatIsPrivate, "supergroup is private", String(p.chatIsPrivate)));
5510
+ console.log(line(p.memberCount === 2, "one owner + bot", String(p.memberCount ?? "unknown")));
5511
+ if (p.ownerPresent !== null) {
5512
+ console.log(line(p.ownerPresent, "paired owner is present", String(p.ownerPresent)));
5513
+ }
5514
+ const allOk = p.botUsername.length > 0 && p.chatTitle.length > 0 && p.isForum && p.hasManageTopics && p.privacyModeOff && p.chatIsPrivate && p.memberCount === 2 && p.ownerPresent !== false;
5011
5515
  if (!allOk) process.exitCode = 1;
5012
5516
  }
5013
5517
  });
5014
- var botStatusCmd = defineCommand22({
5518
+ var botStatusCmd = defineCommand24({
5015
5519
  meta: {
5016
5520
  name: "status",
5017
5521
  description: "Show polling state of the live bot"
@@ -5036,7 +5540,7 @@ var botStatusCmd = defineCommand22({
5036
5540
  if (!p.running) process.exitCode = 1;
5037
5541
  }
5038
5542
  });
5039
- var botRestartCmd = defineCommand22({
5543
+ var botRestartCmd = defineCommand24({
5040
5544
  meta: {
5041
5545
  name: "restart",
5042
5546
  description: "Force-restart the bot (tears down + brings up from current creds)"
@@ -5047,7 +5551,7 @@ var botRestartCmd = defineCommand22({
5047
5551
  console.log("restart requested");
5048
5552
  }
5049
5553
  });
5050
- var botCmd = defineCommand22({
5554
+ var botCmd = defineCommand24({
5051
5555
  meta: {
5052
5556
  name: "bot",
5053
5557
  description: "Inspect / control the live polling bot"
@@ -5057,7 +5561,7 @@ var botCmd = defineCommand22({
5057
5561
  restart: botRestartCmd
5058
5562
  }
5059
5563
  });
5060
- var bindCmd = defineCommand22({
5564
+ var bindCmd = defineCommand24({
5061
5565
  meta: {
5062
5566
  name: "bind",
5063
5567
  description: "Create a Telegram topic for an agent (manual fallback for /talk)"
@@ -5075,7 +5579,7 @@ var bindCmd = defineCommand22({
5075
5579
  console.log(` ${result.deepLink}`);
5076
5580
  }
5077
5581
  });
5078
- var unbindCmd = defineCommand22({
5582
+ var unbindCmd = defineCommand24({
5079
5583
  meta: {
5080
5584
  name: "unbind",
5081
5585
  description: "Clear an agent's Telegram topic binding (topic stays in Telegram as orphan)"
@@ -5089,7 +5593,7 @@ var unbindCmd = defineCommand22({
5089
5593
  console.log(`unbound agent ${args.agent}`);
5090
5594
  }
5091
5595
  });
5092
- var listBindingsCmd = defineCommand22({
5596
+ var listBindingsCmd = defineCommand24({
5093
5597
  meta: {
5094
5598
  name: "list",
5095
5599
  description: "List agents and their Telegram topic bindings"
@@ -5117,24 +5621,23 @@ var listBindingsCmd = defineCommand22({
5117
5621
  }
5118
5622
  }
5119
5623
  });
5120
- var allowCmd = defineCommand22({
5624
+ var allowCmd = defineCommand24({
5121
5625
  meta: { name: "allow", description: "Add a Telegram user id to the allowlist" },
5122
5626
  args: {
5123
5627
  userId: { type: "positional", required: true, description: "Numeric Telegram user id" },
5124
- label: { type: "string", description: "Optional human label" },
5125
- owner: { type: "boolean", description: "Grant owner role (can manage the allowlist)" }
5628
+ label: { type: "string", description: "Optional human label" }
5126
5629
  },
5127
5630
  async run({ args }) {
5128
5631
  const client = createClient2();
5129
5632
  const u = await client.post("/api/config/telegram/acl", {
5130
5633
  userId: Number(args.userId),
5131
5634
  label: args.label ?? null,
5132
- role: args.owner ? "owner" : "member"
5635
+ role: "member"
5133
5636
  });
5134
5637
  console.log(`allowed ${u.userId} (${u.role})`);
5135
5638
  }
5136
5639
  });
5137
- var denyCmd = defineCommand22({
5640
+ var denyCmd = defineCommand24({
5138
5641
  meta: { name: "deny", description: "Remove a Telegram user id from the allowlist" },
5139
5642
  args: { userId: { type: "positional", required: true } },
5140
5643
  async run({ args }) {
@@ -5143,13 +5646,13 @@ var denyCmd = defineCommand22({
5143
5646
  console.log(`removed ${args.userId}`);
5144
5647
  }
5145
5648
  });
5146
- var allowedCmd = defineCommand22({
5649
+ var allowedCmd = defineCommand24({
5147
5650
  meta: { name: "allowed", description: "List allowlisted Telegram users" },
5148
5651
  async run() {
5149
5652
  const client = createClient2();
5150
5653
  const users = await client.get("/api/config/telegram/acl");
5151
5654
  if (users.length === 0) {
5152
- console.log("(allowlist empty \u2014 open; first user to message becomes owner)");
5655
+ console.log("(allowlist empty \u2014 unpaired and closed)");
5153
5656
  return;
5154
5657
  }
5155
5658
  for (const u of users) {
@@ -5158,7 +5661,56 @@ var allowedCmd = defineCommand22({
5158
5661
  }
5159
5662
  }
5160
5663
  });
5161
- var telegramCommand = defineCommand22({
5664
+ var pairingCreateCmd = defineCommand24({
5665
+ meta: { name: "create", description: "Generate a one-time Telegram owner pairing code" },
5666
+ async run() {
5667
+ const result = await createClient2().post(
5668
+ "/api/config/telegram/pairing/challenge"
5669
+ );
5670
+ console.log(`pairing code: ${result.code}`);
5671
+ console.log(`expires: ${new Date(result.expiresAt).toISOString()}`);
5672
+ console.log("send `/pair <code>` in the configured \u2699 bazilion service topic");
5673
+ }
5674
+ });
5675
+ var pairingStatusCmd = defineCommand24({
5676
+ meta: { name: "status", description: "Show Telegram owner pairing status" },
5677
+ async run() {
5678
+ const result = await createClient2().get("/api/config/telegram/pairing");
5679
+ console.log(`paired: ${result.paired}`);
5680
+ console.log(`owner: ${result.ownerUserId ?? "(none)"}`);
5681
+ console.log(`challenge active: ${result.challengeActive}`);
5682
+ if (result.challengeExpiresAt)
5683
+ console.log(`expires: ${new Date(result.challengeExpiresAt).toISOString()}`);
5684
+ }
5685
+ });
5686
+ var pairingCancelCmd = defineCommand24({
5687
+ meta: { name: "cancel", description: "Cancel the active Telegram pairing code" },
5688
+ async run() {
5689
+ await createClient2().del("/api/config/telegram/pairing/challenge");
5690
+ console.log("pairing challenge cancelled");
5691
+ }
5692
+ });
5693
+ var pairingResetCmd = defineCommand24({
5694
+ meta: { name: "reset", description: "Revoke the Telegram owner and require re-pairing" },
5695
+ args: {
5696
+ yes: { type: "boolean", description: "Confirm destructive owner reset", required: true }
5697
+ },
5698
+ async run({ args }) {
5699
+ if (!args.yes) throw new Error("--yes is required");
5700
+ await createClient2().post("/api/config/telegram/pairing/reset", { confirm: true });
5701
+ console.log("Telegram owner reset; ingress is closed until re-paired");
5702
+ }
5703
+ });
5704
+ var pairingCmd = defineCommand24({
5705
+ meta: { name: "pairing", description: "Manage single-owner Telegram pairing" },
5706
+ subCommands: {
5707
+ create: pairingCreateCmd,
5708
+ status: pairingStatusCmd,
5709
+ cancel: pairingCancelCmd,
5710
+ reset: pairingResetCmd
5711
+ }
5712
+ });
5713
+ var telegramCommand = defineCommand24({
5162
5714
  meta: {
5163
5715
  name: "telegram",
5164
5716
  description: "Telegram integration: credentials, health, bot lifecycle, bindings, access"
@@ -5173,47 +5725,42 @@ var telegramCommand = defineCommand22({
5173
5725
  list: listBindingsCmd,
5174
5726
  allow: allowCmd,
5175
5727
  deny: denyCmd,
5176
- allowed: allowedCmd
5728
+ allowed: allowedCmd,
5729
+ pairing: pairingCmd
5177
5730
  }
5178
5731
  });
5179
5732
 
5180
5733
  // src/commands/token.ts
5181
- import { networkInterfaces } from "os";
5182
- import { defineCommand as defineCommand23 } from "citty";
5734
+ import { defineCommand as defineCommand25 } from "citty";
5183
5735
  import qrcode from "qrcode-terminal";
5184
5736
  function tokenRow(t) {
5185
5737
  const last = t.lastUsedAt ? new Date(t.lastUsedAt).toISOString() : "(never)";
5186
- const state = t.revokedAt ? "revoked" : "active";
5187
- return [t.id, state, t.label, `last: ${last}`];
5188
- }
5189
- function detectLanOrigin(port) {
5190
- const candidates = [];
5191
- for (const ifs of Object.values(networkInterfaces())) {
5192
- for (const i of ifs ?? []) {
5193
- if (i.family === "IPv4" && !i.internal) candidates.push(i.address);
5194
- }
5195
- }
5196
- const [first, ...rest] = candidates;
5197
- if (!first) return null;
5198
- const origin = `http://${first}:${port}`;
5199
- return rest.length > 0 ? { origin, warning: `multiple LAN IPs found (${candidates.join(", ")}) \u2014 using ${first}` } : { origin };
5738
+ const expired = t.expiresAt !== null && t.expiresAt <= Date.now();
5739
+ const state = t.revokedAt ? "revoked" : expired ? "expired" : "active";
5740
+ const expires = t.expiresAt ? new Date(t.expiresAt).toISOString() : "(never)";
5741
+ return [t.id, t.kind, state, t.label, `expires: ${expires}`, `last: ${last}`];
5200
5742
  }
5201
5743
  function resolveQrServer(override) {
5202
- if (override) return override.replace(/\/$/, "");
5744
+ const raw = override ?? loadClientConfig().serverUrl;
5745
+ const url = new URL(raw);
5746
+ const loopback = ["127.0.0.1", "localhost", "[::1]", "::1"].includes(url.hostname);
5747
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
5748
+ throw new Error("pairing server must use HTTPS (HTTP is allowed only for loopback development)");
5749
+ }
5750
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
5751
+ throw new Error(
5752
+ "pairing server must be an exact origin without credentials, path, query, or fragment"
5753
+ );
5754
+ }
5203
5755
  const cfg = loadClientConfig();
5204
- const current = new URL(cfg.serverUrl);
5205
- const isLoopback = current.hostname === "127.0.0.1" || current.hostname === "localhost" || current.hostname === "::1";
5206
- if (!isLoopback) return cfg.serverUrl.replace(/\/$/, "");
5207
- const detected = detectLanOrigin(current.port || "4321");
5208
- if (!detected) {
5756
+ if (loopback && !override && new URL(cfg.serverUrl).hostname === url.hostname) {
5209
5757
  throw new Error(
5210
- "server URL is loopback-only and no LAN interface was found. Pass --server http://<host>:<port> so the mobile client knows where to connect."
5758
+ "server URL is loopback-only. Pass --server https://<private-tailnet-host> for mobile pairing."
5211
5759
  );
5212
5760
  }
5213
- if (detected.warning) console.warn(`\u26A0 ${detected.warning}`);
5214
- return detected.origin;
5761
+ return url.origin;
5215
5762
  }
5216
- var createCmd3 = defineCommand23({
5763
+ var createCmd3 = defineCommand25({
5217
5764
  meta: { name: "create", description: "Mint a new web token (shown once)" },
5218
5765
  args: {
5219
5766
  label: { type: "positional", required: true, description: "Human-readable label" },
@@ -5224,14 +5771,26 @@ var createCmd3 = defineCommand23({
5224
5771
  server: {
5225
5772
  type: "string",
5226
5773
  description: "Server URL to embed in the pairing QR (default: detect LAN IP; ignored without --qr)"
5774
+ },
5775
+ expiresDays: {
5776
+ type: "string",
5777
+ description: "Device lifetime in days (default 90, maximum 365)"
5227
5778
  }
5228
5779
  },
5229
5780
  async run({ args }) {
5230
5781
  const client = createClient2();
5231
- const body = { label: args.label };
5782
+ const expiresInDays = args.expiresDays === void 0 ? void 0 : Number(args.expiresDays);
5783
+ if (expiresInDays !== void 0 && !Number.isInteger(expiresInDays)) {
5784
+ throw new Error("--expires-days must be an integer");
5785
+ }
5786
+ const body = { label: args.label, expiresInDays };
5232
5787
  const res = await client.post("/api/tokens", body);
5233
5788
  console.log(`id: ${res.meta.id}`);
5234
5789
  console.log(`label: ${res.meta.label}`);
5790
+ console.log(`kind: ${res.meta.kind}`);
5791
+ console.log(
5792
+ `expires: ${res.meta.expiresAt ? new Date(res.meta.expiresAt).toISOString() : "(never)"}`
5793
+ );
5235
5794
  console.log(`token: ${res.token}`);
5236
5795
  console.log("");
5237
5796
  console.log("store the token now \u2014 it is not recoverable later.");
@@ -5244,7 +5803,7 @@ var createCmd3 = defineCommand23({
5244
5803
  qrcode.generate(pairUrl, { small: true }, (qr) => console.log(qr));
5245
5804
  }
5246
5805
  });
5247
- var listCmd10 = defineCommand23({
5806
+ var listCmd11 = defineCommand25({
5248
5807
  meta: { name: "list", description: "List web tokens" },
5249
5808
  args: {
5250
5809
  all: { type: "boolean", description: "Include revoked tokens" }
@@ -5260,7 +5819,7 @@ var listCmd10 = defineCommand23({
5260
5819
  for (const line of columnize(tokens.map(tokenRow))) console.log(line);
5261
5820
  }
5262
5821
  });
5263
- var showLocalCmd = defineCommand23({
5822
+ var showLocalCmd = defineCommand25({
5264
5823
  meta: {
5265
5824
  name: "show-local",
5266
5825
  description: "Print the bootstrap web token stored in ~/.bazilion/auth.json"
@@ -5270,7 +5829,7 @@ var showLocalCmd = defineCommand23({
5270
5829
  console.log(readAuthFile(paths.authFile).token);
5271
5830
  }
5272
5831
  });
5273
- var revokeCmd = defineCommand23({
5832
+ var revokeCmd2 = defineCommand25({
5274
5833
  meta: { name: "revoke", description: "Revoke a web token" },
5275
5834
  args: {
5276
5835
  id: { type: "positional", required: true }
@@ -5281,19 +5840,19 @@ var revokeCmd = defineCommand23({
5281
5840
  console.log(`revoked token ${args.id}`);
5282
5841
  }
5283
5842
  });
5284
- var tokenCommand = defineCommand23({
5843
+ var tokenCommand = defineCommand25({
5285
5844
  meta: { name: "token", description: "Manage web tokens for API/CLI clients" },
5286
5845
  subCommands: {
5287
5846
  create: createCmd3,
5288
- list: listCmd10,
5289
- revoke: revokeCmd,
5847
+ list: listCmd11,
5848
+ revoke: revokeCmd2,
5290
5849
  "show-local": showLocalCmd
5291
5850
  }
5292
5851
  });
5293
5852
 
5294
5853
  // src/commands/trigger.ts
5295
- import { defineCommand as defineCommand24 } from "citty";
5296
- var addCmd3 = defineCommand24({
5854
+ import { defineCommand as defineCommand26 } from "citty";
5855
+ var addCmd3 = defineCommand26({
5297
5856
  meta: { name: "add", description: "Add an interval / cron trigger to an agent" },
5298
5857
  args: {
5299
5858
  agent: { type: "positional", required: true },
@@ -5341,7 +5900,7 @@ function triggerRow(t) {
5341
5900
  const msgPreview = t.message.length > 60 ? `${t.message.slice(0, 60)}\u2026` : t.message;
5342
5901
  return [t.id, state, spec, `last: ${last}`, `"${msgPreview}"`];
5343
5902
  }
5344
- var listCmd11 = defineCommand24({
5903
+ var listCmd12 = defineCommand26({
5345
5904
  meta: { name: "list", description: "List triggers for an agent" },
5346
5905
  args: {
5347
5906
  agent: { type: "positional", required: true }
@@ -5358,7 +5917,7 @@ var listCmd11 = defineCommand24({
5358
5917
  for (const line of columnize(triggers.map(triggerRow))) console.log(line);
5359
5918
  }
5360
5919
  });
5361
- var rmCmd6 = defineCommand24({
5920
+ var rmCmd6 = defineCommand26({
5362
5921
  meta: { name: "rm", description: "Delete a trigger" },
5363
5922
  args: {
5364
5923
  id: { type: "positional", required: true }
@@ -5369,7 +5928,7 @@ var rmCmd6 = defineCommand24({
5369
5928
  console.log(`removed trigger ${args.id}`);
5370
5929
  }
5371
5930
  });
5372
- var enableCmd3 = defineCommand24({
5931
+ var enableCmd3 = defineCommand26({
5373
5932
  meta: { name: "enable", description: "Enable a trigger" },
5374
5933
  args: {
5375
5934
  id: { type: "positional", required: true }
@@ -5381,7 +5940,7 @@ var enableCmd3 = defineCommand24({
5381
5940
  console.log(`enabled trigger ${args.id}`);
5382
5941
  }
5383
5942
  });
5384
- var disableCmd3 = defineCommand24({
5943
+ var disableCmd3 = defineCommand26({
5385
5944
  meta: { name: "disable", description: "Disable a trigger" },
5386
5945
  args: {
5387
5946
  id: { type: "positional", required: true }
@@ -5393,7 +5952,7 @@ var disableCmd3 = defineCommand24({
5393
5952
  console.log(`disabled trigger ${args.id}`);
5394
5953
  }
5395
5954
  });
5396
- var historyCmd = defineCommand24({
5955
+ var historyCmd = defineCommand26({
5397
5956
  meta: { name: "history", description: "Show recent dispatches for a trigger" },
5398
5957
  args: {
5399
5958
  id: { type: "positional", required: true },
@@ -5421,11 +5980,11 @@ var historyCmd = defineCommand24({
5421
5980
  for (const line of columnize(rows)) console.log(line);
5422
5981
  }
5423
5982
  });
5424
- var triggerCommand = defineCommand24({
5983
+ var triggerCommand = defineCommand26({
5425
5984
  meta: { name: "trigger", description: "Manage scheduled agent triggers" },
5426
5985
  subCommands: {
5427
5986
  add: addCmd3,
5428
- list: listCmd11,
5987
+ list: listCmd12,
5429
5988
  rm: rmCmd6,
5430
5989
  enable: enableCmd3,
5431
5990
  disable: disableCmd3,
@@ -5436,7 +5995,7 @@ var triggerCommand = defineCommand24({
5436
5995
  // src/commands/uninstall.ts
5437
5996
  import { existsSync as existsSync7, readdirSync as readdirSync2, rmSync as rmSync4 } from "fs";
5438
5997
  import { join as join6 } from "path";
5439
- import { defineCommand as defineCommand25 } from "citty";
5998
+ import { defineCommand as defineCommand27 } from "citty";
5440
5999
  function makeLineReader() {
5441
6000
  let buffer = "";
5442
6001
  let ended = false;
@@ -5484,7 +6043,7 @@ function removePath(p) {
5484
6043
  rmSync4(p, { recursive: true, force: true });
5485
6044
  return true;
5486
6045
  }
5487
- var uninstallCommand = defineCommand25({
6046
+ var uninstallCommand = defineCommand27({
5488
6047
  meta: {
5489
6048
  name: "uninstall",
5490
6049
  description: "Wipe bazilion state from ~/.bazilion (or BAZILION_HOME)"
@@ -5555,7 +6114,7 @@ var uninstallCommand = defineCommand25({
5555
6114
 
5556
6115
  // src/index.ts
5557
6116
  var VERSION = package_default.version;
5558
- var main = defineCommand26({
6117
+ var main = defineCommand28({
5559
6118
  meta: {
5560
6119
  name: "bazilion",
5561
6120
  version: VERSION,
@@ -5574,11 +6133,13 @@ var main = defineCommand26({
5574
6133
  mcp: mcpCommand,
5575
6134
  provider: providerCommand,
5576
6135
  send: sendCommand,
6136
+ session: sessionCommand,
5577
6137
  inbox: inboxCommand,
5578
6138
  config: configCommand,
5579
6139
  serve: serveCommand,
5580
6140
  dashboard: dashboardCommand,
5581
6141
  doctor: doctorCommand,
6142
+ gateway: gatewayCommand,
5582
6143
  backup: backupCommand,
5583
6144
  trigger: triggerCommand,
5584
6145
  token: tokenCommand,
@@ -5678,7 +6239,7 @@ function printTopLevelHelp() {
5678
6239
  },
5679
6240
  {
5680
6241
  title: "ops",
5681
- items: [["backup", "Download a tar.gz backup of ~/.bazilion"]]
6242
+ items: [["backup", "Encrypted backup, restore, and credential recovery"]]
5682
6243
  },
5683
6244
  {
5684
6245
  title: "remote",