bazilion 0.14.0 → 0.14.1

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 (51) hide show
  1. package/README.md +16 -5
  2. package/dist/cli.js +285 -86
  3. package/dist/cli.js.map +1 -1
  4. package/dist/daemon.js +514 -76
  5. package/dist/daemon.js.map +1 -1
  6. package/dist/web/client/assets/{ConfirmDialog-CyL9A20V.js → ConfirmDialog-BAiyYC8w.js} +1 -1
  7. package/dist/web/client/assets/{FieldRow-D6REbYUa.js → FieldRow-DsWhqiIQ.js} +1 -1
  8. package/dist/web/client/assets/{ProductionTeamPolicyEditor-DBKnOezm.js → ProductionTeamPolicyEditor-CHBKZMpY.js} +1 -1
  9. package/dist/web/client/assets/{ProfileCommunicationEditor-4RK2laqO.js → ProfileCommunicationEditor-DZ8yTQvV.js} +1 -1
  10. package/dist/web/client/assets/{TeamTabs-CdtNEqt4.js → TeamTabs-BfC1pfQe.js} +1 -1
  11. package/dist/web/client/assets/{TemplatesTabs-CZyss2Y0.js → TemplatesTabs-BwsLjN0Q.js} +1 -1
  12. package/dist/web/client/assets/{UnsavedChangesGuard-CEoK-ZcN.js → UnsavedChangesGuard-C7U9rsPw.js} +1 -1
  13. package/dist/web/client/assets/{_id-DIqtdQij.js → _id-CG9YM1tY.js} +1 -1
  14. package/dist/web/client/assets/{_id-f_r5M0p3.js → _id-DfzTSFBA.js} +1 -1
  15. package/dist/web/client/assets/{_id-4ZS-w_Aj.js → _id-DtHL-U-0.js} +1 -1
  16. package/dist/web/client/assets/{_id-DbcC2E7F.js → _id-J0kM-98d.js} +1 -1
  17. package/dist/web/client/assets/{activity-CB9Ufth-.js → activity-CBQGMwWo.js} +1 -1
  18. package/dist/web/client/assets/{agents-D-JGXVg9.js → agents-BWGBjF2Y.js} +1 -1
  19. package/dist/web/client/assets/{agents-DS2XafoA.js → agents-DTY5RXgO.js} +1 -1
  20. package/dist/web/client/assets/{approvals-Cke_mrJv.js → approvals-DwMk9S4Z.js} +1 -1
  21. package/dist/web/client/assets/{attention-PYwRM2wC.js → attention-7qYFtwOW.js} +1 -1
  22. package/dist/web/client/assets/chevron-right-DK703skW.js +1 -0
  23. package/dist/web/client/assets/{config-DF9Gqmfa.js → config-D59MT3Y5.js} +2 -2
  24. package/dist/web/client/assets/{inbox-DC_HdOfI.js → inbox-CpKuNfOj.js} +1 -1
  25. package/dist/web/client/assets/{index-Cp6UHWr2.js → index-BVfpuiby.js} +2 -2
  26. package/dist/web/client/assets/{learning-BfrQ4YZY.js → learning-631_q6a9.js} +1 -1
  27. package/dist/web/client/assets/{login-D4n3AcBR.js → login-JjfTCGIb.js} +1 -1
  28. package/dist/web/client/assets/{mcp-CnjQc_JJ.js → mcp-DHgUOUPG.js} +1 -1
  29. package/dist/web/client/assets/{members-wxsVTJdW.js → members-CLCUdo-h.js} +1 -1
  30. package/dist/web/client/assets/{memory-Du60O3pE.js → memory-paAiM6ix.js} +1 -1
  31. package/dist/web/client/assets/{policy-BOfnkJsL.js → policy-Fy_Wnq6p.js} +1 -1
  32. package/dist/web/client/assets/{routes-B9CuhzUW.js → routes-DciiC6QA.js} +1 -1
  33. package/dist/web/client/assets/{services-DyvqJqM2.js → services-i_RmJYAO.js} +1 -1
  34. package/dist/web/client/assets/{skills-DQo7ioW7.js → skills-BX42OGpp.js} +1 -1
  35. package/dist/web/client/assets/{teams-DPggy97H.js → teams-4ySkP31k.js} +1 -1
  36. package/dist/web/client/assets/{teams-DdawcfNR.js → teams-D6nIPyC0.js} +1 -1
  37. package/dist/web/client/assets/{telegram-DcTeDpP5.js → telegram-CphoIRWP.js} +1 -1
  38. package/dist/web/client/assets/{tokens-CIEKwZXc.js → tokens-2j3NQudt.js} +1 -1
  39. package/dist/web/client/assets/{triggers-Bc2RrUPm.js → triggers-sDA075Nu.js} +1 -1
  40. package/dist/web/client/assets/{welcome-DlhNXUIa.js → welcome-Do427w4M.js} +1 -1
  41. package/dist/web/client/assets/{wire-constants-BzdHSc6A.js → wire-constants-B7b2CQp1.js} +1 -1
  42. package/dist/web/client/assets/{x-DAXGIe_Q.js → x-CbAF-3_T.js} +1 -1
  43. package/dist/web/server/assets/{_tanstack-start-manifest_v-C-OiOZeM.js → _tanstack-start-manifest_v-DR7wyKcH.js} +75 -75
  44. package/dist/web/server/assets/{config-BlMkHStx.js → config-DQ8n0AwF.js} +1 -1
  45. package/dist/web/server/assets/{config-ExacOzHX.js → config-XSTLuRaT.js} +31 -9
  46. package/dist/web/server/assets/{router-pjH2t8Rl.js → router-M0lutIDA.js} +3 -2
  47. package/dist/web/server/server.js +2 -2
  48. package/dist/worker.js +64 -19
  49. package/dist/worker.js.map +1 -1
  50. package/package.json +3 -3
  51. package/dist/web/client/assets/chevron-right-BG4OeFRQ.js +0 -1
package/README.md CHANGED
@@ -107,8 +107,12 @@ cd apps/web && pnpm dev # http://127.0.0.1:4322
107
107
  Use your ChatGPT account instead of an API key to access `gpt-5.x` / `gpt-5.x-codex` models:
108
108
 
109
109
  ```sh
110
- bazilion auth openai login # browser flow on localhost:1455
111
- bazilion auth openai status # check connection / token expiry
110
+ bazilion auth openai login # browser flow on localhost:1455
111
+ bazilion auth openai login --device-code # headless/remote or callback-port fallback
112
+ bazilion auth openai status # check connection / token expiry
113
+
114
+ # Source checkout: run from the repository root.
115
+ pnpm tsx apps/cli/src/index.ts auth openai login --device-code
112
116
  ```
113
117
 
114
118
  After connecting, enable `openai-codex` and curate at least one model, for example
@@ -125,11 +129,18 @@ Credentials are stored AES-256-GCM-encrypted in the daemon's `secrets` table.
125
129
  ## Uninstall
126
130
 
127
131
  ```sh
128
- bazilion uninstall # interactive: data wipe, then optional full wipe
129
- bazilion uninstall --yes # data tier only (DB + profiles/agents/teams)
130
- bazilion uninstall --yes --all # also remove auth.json, logs/, skills/
132
+ bazilion uninstall # interactive: reset, then optional full wipe
133
+ bazilion uninstall --yes # reset DB + auth.json + profiles/agents/teams
134
+ bazilion uninstall --yes --all # also remove logs/ and skills/
131
135
  ```
132
136
 
137
+ Both tiers remove the DB and `auth.json` together so the next `bazilion serve` creates a matching
138
+ bootstrap-token pair. They also remove the legacy `groups/`, `config.json`, and `secrets.enc`
139
+ paths. Team and legacy Group symlink slots are unlinked without touching their external targets. A
140
+ full wipe removes the Bazilion home when only managed paths remain; unmanaged files keep the home
141
+ in place. A symlinked `BAZILION_HOME` root remains as an empty slot so interrupted cleanup can be
142
+ safely resumed through the same canonical identity.
143
+
133
144
  ## Documentation
134
145
 
135
146
  - Full README: <https://github.com/rullopat/bazilion#readme>
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { defineCommand as defineCommand28, runCommand, showUsage } from "citty";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "bazilion",
9
- version: "0.14.0",
9
+ version: "0.14.1",
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: {
@@ -699,8 +699,8 @@ var chatResetCmd = defineCommand({
699
699
  async run({ args }) {
700
700
  if (!args.force) {
701
701
  process.stdout.write(`reset all chat history for ${args.id}? [y/N] `);
702
- const answer = await new Promise((resolve4) => {
703
- process.stdin.once("data", (d) => resolve4(String(d).trim().toLowerCase()));
702
+ const answer = await new Promise((resolve5) => {
703
+ process.stdin.once("data", (d) => resolve5(String(d).trim().toLowerCase()));
704
704
  });
705
705
  if (answer !== "y" && answer !== "yes") {
706
706
  console.log("aborted");
@@ -848,8 +848,8 @@ var chatCompactCmd = defineCommand({
848
848
  process.stdout.write(
849
849
  `compact chat history for ${args.id}? head turns become a summary; tail is preserved. [y/N] `
850
850
  );
851
- const answer = await new Promise((resolve4) => {
852
- process.stdin.once("data", (d) => resolve4(String(d).trim().toLowerCase()));
851
+ const answer = await new Promise((resolve5) => {
852
+ process.stdin.once("data", (d) => resolve5(String(d).trim().toLowerCase()));
853
853
  });
854
854
  if (answer !== "y" && answer !== "yes") {
855
855
  console.log("aborted");
@@ -1344,6 +1344,91 @@ var attentionCommand = defineCommand3({
1344
1344
  // src/commands/auth.ts
1345
1345
  import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
1346
1346
  import { defineCommand as defineCommand4 } from "citty";
1347
+ var CLI_OPENAI_LOGIN_TIMEOUT_MS = 10 * 6e4;
1348
+ function createCliOpenAILoginLifetime(timeoutMs = CLI_OPENAI_LOGIN_TIMEOUT_MS) {
1349
+ const controller = new AbortController();
1350
+ const timeout = setTimeout(() => {
1351
+ controller.abort(
1352
+ new DOMException(
1353
+ "OpenAI login timed out after 10 minutes. Try again with `bazilion auth openai login --device-code`.",
1354
+ "TimeoutError"
1355
+ )
1356
+ );
1357
+ }, timeoutMs);
1358
+ return {
1359
+ signal: controller.signal,
1360
+ dispose() {
1361
+ clearTimeout(timeout);
1362
+ }
1363
+ };
1364
+ }
1365
+ function promptAbortError(signal) {
1366
+ return signal.reason instanceof Error ? signal.reason : new DOMException("OAuth prompt cancelled", "AbortError");
1367
+ }
1368
+ function answerCliOpenAIPrompt(prompt, input = process.stdin, output = process.stdout, loginMethod = "browser", loginSignal) {
1369
+ if (prompt.type === "select") {
1370
+ const option = prompt.options.find((candidate) => candidate.id === loginMethod);
1371
+ return option ? Promise.resolve(option.id) : Promise.reject(
1372
+ new Error(
1373
+ `Pi openai-codex provider does not support ${loginMethod === "browser" ? "browser" : "device-code"} login`
1374
+ )
1375
+ );
1376
+ }
1377
+ output.write(`${prompt.message}: `);
1378
+ return new Promise((resolve5, reject) => {
1379
+ let settled = false;
1380
+ const signal = prompt.signal;
1381
+ let readingInput = true;
1382
+ const cleanupInput = () => {
1383
+ if (!readingInput) return;
1384
+ readingInput = false;
1385
+ input.removeListener("data", onData);
1386
+ input.removeListener("end", onInputUnavailable);
1387
+ input.removeListener("error", onInputUnavailable);
1388
+ input.pause();
1389
+ };
1390
+ const cleanup = () => {
1391
+ cleanupInput();
1392
+ signal?.removeEventListener("abort", onAbort);
1393
+ loginSignal?.removeEventListener("abort", onLoginAbort);
1394
+ };
1395
+ const onData = (data) => {
1396
+ if (settled) return;
1397
+ settled = true;
1398
+ cleanup();
1399
+ resolve5(String(data).trim());
1400
+ };
1401
+ const onAbort = () => {
1402
+ if (settled || !signal) return;
1403
+ settled = true;
1404
+ cleanup();
1405
+ reject(promptAbortError(signal));
1406
+ };
1407
+ const onLoginAbort = () => {
1408
+ if (settled || !loginSignal) return;
1409
+ settled = true;
1410
+ cleanup();
1411
+ reject(promptAbortError(loginSignal));
1412
+ };
1413
+ const onInputUnavailable = () => {
1414
+ if (settled) return;
1415
+ cleanupInput();
1416
+ };
1417
+ if (signal?.aborted) {
1418
+ onAbort();
1419
+ return;
1420
+ }
1421
+ if (loginSignal?.aborted) {
1422
+ onLoginAbort();
1423
+ return;
1424
+ }
1425
+ input.once("data", onData);
1426
+ input.once("end", onInputUnavailable);
1427
+ input.once("error", onInputUnavailable);
1428
+ signal?.addEventListener("abort", onAbort, { once: true });
1429
+ loginSignal?.addEventListener("abort", onLoginAbort, { once: true });
1430
+ });
1431
+ }
1347
1432
  function formatExpiry(ms) {
1348
1433
  if (!ms) return "(unknown)";
1349
1434
  const d = new Date(ms);
@@ -1352,36 +1437,53 @@ function formatExpiry(ms) {
1352
1437
  var openaiLoginCmd = defineCommand4({
1353
1438
  meta: {
1354
1439
  name: "login",
1355
- description: "Connect your ChatGPT account via OAuth (browser sign-in)"
1440
+ description: "Connect your ChatGPT account via OAuth"
1356
1441
  },
1357
- async run() {
1442
+ args: {
1443
+ "device-code": {
1444
+ type: "boolean",
1445
+ description: "Use headless device-code login instead of the localhost browser callback"
1446
+ }
1447
+ },
1448
+ async run({ args }) {
1358
1449
  const client = createClient2();
1359
- console.log("opening your browser for OpenAI sign-in...");
1360
- console.log("(if it doesn't open automatically, paste the URL shown below)");
1450
+ const loginMethod = args["device-code"] ? "device_code" : "browser";
1451
+ if (loginMethod === "device_code") {
1452
+ console.log("starting OpenAI device-code sign-in...");
1453
+ } else {
1454
+ console.log("opening your browser for OpenAI sign-in...");
1455
+ console.log("(if it doesn't open automatically, paste the URL shown below)");
1456
+ }
1361
1457
  const oauth = openaiCodexProvider().auth.oauth;
1362
1458
  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
- }
1376
- },
1377
- prompt: async (prompt) => {
1378
- if (prompt.type === "select") return prompt.options[0]?.id ?? "";
1379
- process.stdout.write(`${prompt.message}: `);
1380
- return new Promise((resolve4) => {
1381
- process.stdin.once("data", (d) => resolve4(String(d).trim()));
1459
+ const lifetime = createCliOpenAILoginLifetime();
1460
+ const creds = await (async () => {
1461
+ try {
1462
+ return await oauth.login({
1463
+ signal: lifetime.signal,
1464
+ notify: (event) => {
1465
+ if (event.type === "auth_url") {
1466
+ console.log("");
1467
+ console.log(` \u2192 ${event.url}`);
1468
+ console.log("");
1469
+ } else if (event.type === "progress" || event.type === "info") {
1470
+ console.log(` ${event.message}`);
1471
+ } else if (event.type === "device_code") {
1472
+ console.log(` open ${event.verificationUri} and enter ${event.userCode}`);
1473
+ }
1474
+ },
1475
+ prompt: (prompt) => answerCliOpenAIPrompt(
1476
+ prompt,
1477
+ process.stdin,
1478
+ process.stdout,
1479
+ loginMethod,
1480
+ lifetime.signal
1481
+ )
1382
1482
  });
1483
+ } finally {
1484
+ lifetime.dispose();
1383
1485
  }
1384
- });
1486
+ })();
1385
1487
  const status = await client.put("/api/auth/openai", {
1386
1488
  refresh: creds.refresh,
1387
1489
  access: creds.access,
@@ -1648,13 +1750,16 @@ function parseRecord(raw) {
1648
1750
  "starting",
1649
1751
  "listening",
1650
1752
  "restoring",
1753
+ "uninstalling",
1651
1754
  "swapping",
1652
1755
  "installed",
1653
1756
  "recovery-required"
1654
1757
  ].includes(value.state ?? "") || !Number.isFinite(value.startedAt)) {
1655
1758
  return null;
1656
1759
  }
1657
- if (value.owner === "daemon" && !["starting", "listening"].includes(value.state ?? "") || value.owner === "restore" && !["restoring", "swapping", "installed", "recovery-required"].includes(value.state ?? "")) {
1760
+ if (value.owner === "daemon" && !["starting", "listening"].includes(value.state ?? "") || value.owner === "restore" && !["restoring", "uninstalling", "swapping", "installed", "recovery-required"].includes(
1761
+ value.state ?? ""
1762
+ )) {
1658
1763
  return null;
1659
1764
  }
1660
1765
  if (value.owner === "restore" && ["swapping", "recovery-required"].includes(value.state ?? "") && (typeof value.recoveryPath !== "string" || !value.recoveryPath || typeof value.hadPrevious !== "boolean")) {
@@ -1757,7 +1862,7 @@ function reclaimRecord(path, existing) {
1757
1862
  throw error;
1758
1863
  }
1759
1864
  }
1760
- async function acquireHomeRestoreLock(home) {
1865
+ async function acquireHomeRestoreLock(home, operation = "restoring") {
1761
1866
  const targetHome = resolve(home);
1762
1867
  mkdirSync(dirname(targetHome), { recursive: true });
1763
1868
  const path = daemonLivenessPath(targetHome);
@@ -1777,7 +1882,7 @@ async function acquireHomeRestoreLock(home) {
1777
1882
  const endpoint3 = existing.record ? endpointFor(existing.record) : null;
1778
1883
  if (endpoint3 && existing.record && await endpointHasOwner(endpoint3, existing.record.instanceId)) {
1779
1884
  throw new Error(
1780
- `bazilion daemon is running at ${endpoint3} for ${targetHome} \u2014 stop it before restoring`
1885
+ `bazilion daemon is running at ${endpoint3} for ${targetHome} \u2014 stop it before ${operation}`
1781
1886
  );
1782
1887
  }
1783
1888
  if (!existing.record) {
@@ -1786,6 +1891,19 @@ async function acquireHomeRestoreLock(home) {
1786
1891
  );
1787
1892
  }
1788
1893
  const livePid = pidIsAlive(existing.record.pid);
1894
+ if (existing.record.owner === "restore" && existing.record.state === "uninstalling") {
1895
+ if (livePid) {
1896
+ throw new Error(
1897
+ `bazilion uninstall process ${existing.record.pid} owns ${targetHome} \u2014 wait for it to finish before ${operation}`
1898
+ );
1899
+ }
1900
+ if (operation !== "uninstalling") {
1901
+ throw new Error(
1902
+ `a previous Bazilion uninstall was interrupted for ${targetHome}. Re-run the same \`bazilion uninstall\` command (including its \`--all\` choice) with the same BAZILION_HOME (or --home) to finish it before ${operation}.`
1903
+ );
1904
+ }
1905
+ console.warn(`resuming interrupted Bazilion uninstall for ${targetHome}`);
1906
+ }
1789
1907
  if (existing.record.owner === "restore" && ["swapping", "recovery-required"].includes(existing.record.state)) {
1790
1908
  if (livePid) {
1791
1909
  throw new Error(
@@ -1802,7 +1920,7 @@ async function acquireHomeRestoreLock(home) {
1802
1920
  const staleDetail = ageMs > STALE_AFTER_MS ? " (its heartbeat is stale)" : "";
1803
1921
  const owner = existing.record.owner === "daemon" ? "daemon" : "restore";
1804
1922
  throw new Error(
1805
- `bazilion ${owner} process ${existing.record.pid} owns ${targetHome}${staleDetail} \u2014 stop it before restoring`
1923
+ `bazilion ${owner} process ${existing.record.pid} owns ${targetHome}${staleDetail} \u2014 stop it before ${operation}`
1806
1924
  );
1807
1925
  }
1808
1926
  const claim = reclaimRecord(path, existing);
@@ -1813,7 +1931,7 @@ async function acquireHomeRestoreLock(home) {
1813
1931
  }
1814
1932
  if (fd === null) {
1815
1933
  throw new Error(
1816
- `could not safely acquire exclusive restore ownership for ${targetHome}; try again`
1934
+ `could not safely acquire exclusive ownership for ${operation} ${targetHome}; try again`
1817
1935
  );
1818
1936
  }
1819
1937
  const ownedStat = fstatSync(fd);
@@ -1822,7 +1940,7 @@ async function acquireHomeRestoreLock(home) {
1822
1940
  instanceId,
1823
1941
  pid: process.pid,
1824
1942
  owner: "restore",
1825
- state: "restoring",
1943
+ state: operation,
1826
1944
  startedAt: Date.now()
1827
1945
  };
1828
1946
  try {
@@ -1843,6 +1961,7 @@ async function acquireHomeRestoreLock(home) {
1843
1961
  throw error;
1844
1962
  }
1845
1963
  let released = false;
1964
+ let uninstallComplete = false;
1846
1965
  const stillOwnsPath = () => {
1847
1966
  try {
1848
1967
  return sameFile(lstatSync(path), ownedStat);
@@ -1853,7 +1972,7 @@ async function acquireHomeRestoreLock(home) {
1853
1972
  const heartbeat = setInterval(() => {
1854
1973
  if (released) return;
1855
1974
  if (!stillOwnsPath()) {
1856
- console.error("restore ownership was lost; exiting to protect the Bazilion database");
1975
+ console.error(`${operation} ownership was lost; exiting to protect the Bazilion database`);
1857
1976
  process.exit(1);
1858
1977
  }
1859
1978
  try {
@@ -1861,7 +1980,7 @@ async function acquireHomeRestoreLock(home) {
1861
1980
  futimesSync(fd, now, now);
1862
1981
  } catch (error) {
1863
1982
  console.error(
1864
- "restore ownership heartbeat failed:",
1983
+ `${operation} ownership heartbeat failed:`,
1865
1984
  error instanceof Error ? error.message : error
1866
1985
  );
1867
1986
  process.exit(1);
@@ -1892,12 +2011,19 @@ async function acquireHomeRestoreLock(home) {
1892
2011
  record.hadPrevious = hadPrevious;
1893
2012
  writeRecord(fd, record);
1894
2013
  },
2014
+ markUninstallComplete() {
2015
+ if (released || !stillOwnsPath()) throw new Error("uninstall ownership was lost");
2016
+ if (record.state !== "uninstalling") {
2017
+ throw new Error("cannot complete uninstall from a non-uninstall ownership state");
2018
+ }
2019
+ uninstallComplete = true;
2020
+ },
1895
2021
  release() {
1896
2022
  if (released) return;
1897
2023
  released = true;
1898
2024
  clearInterval(heartbeat);
1899
2025
  const ownsPath = stillOwnsPath();
1900
- const retainForRecovery = ["swapping", "recovery-required"].includes(record.state);
2026
+ const retainForRecovery = ["swapping", "recovery-required"].includes(record.state) || record.state === "uninstalling" && !uninstallComplete;
1901
2027
  closeSync(fd);
1902
2028
  fd = null;
1903
2029
  if (ownsPath && !retainForRecovery) rmSync(path, { force: true });
@@ -3073,12 +3199,12 @@ function probeHost(host) {
3073
3199
  return host;
3074
3200
  }
3075
3201
  function isTcpPortOpen(host, port) {
3076
- return new Promise((resolve4) => {
3202
+ return new Promise((resolve5) => {
3077
3203
  const socket = connect({ host: probeHost(host), port: Number(port) });
3078
3204
  const done = (open) => {
3079
3205
  socket.removeAllListeners();
3080
3206
  socket.destroy();
3081
- resolve4(open);
3207
+ resolve5(open);
3082
3208
  };
3083
3209
  socket.setTimeout(600);
3084
3210
  socket.once("connect", () => done(true));
@@ -3219,7 +3345,7 @@ var dashboardCommand = defineCommand8({
3219
3345
  }
3220
3346
  if (!existsSync3(join3(webDist, "server", "server.js"))) {
3221
3347
  throw new Error(
3222
- `bazilion web UI bundle not found at ${webDist}. Run "pnpm --filter @bazilion/web build" from a source checkout, or reinstall the published package.`
3348
+ `bazilion web UI bundle not found at ${webDist}. Run "pnpm --filter @bazilion/web build" from the repository root of a source checkout, or reinstall the published package.`
3223
3349
  );
3224
3350
  }
3225
3351
  console.log(`starting bazilion web UI at ${webUrl}`);
@@ -3244,13 +3370,13 @@ var dashboardCommand = defineCommand8({
3244
3370
  if (children.length === 0) return;
3245
3371
  process.on("SIGINT", terminateChildren);
3246
3372
  process.on("SIGTERM", terminateChildren);
3247
- await new Promise((resolve4) => {
3373
+ await new Promise((resolve5) => {
3248
3374
  let remaining = children.length;
3249
3375
  for (const child of children) {
3250
3376
  child.on("close", (code) => {
3251
3377
  remaining--;
3252
3378
  if (!signalled && code && code !== 0) process.exitCode = code;
3253
- if (remaining === 0) resolve4();
3379
+ if (remaining === 0) resolve5();
3254
3380
  });
3255
3381
  }
3256
3382
  });
@@ -4065,10 +4191,10 @@ var editCmd2 = defineCommand15({
4065
4191
  writeFileSync4(tmpPath, content);
4066
4192
  try {
4067
4193
  const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
4068
- const code = await new Promise((resolve4, reject) => {
4194
+ const code = await new Promise((resolve5, reject) => {
4069
4195
  const child = spawn2(editor, [tmpPath], { stdio: "inherit" });
4070
4196
  child.on("error", reject);
4071
- child.on("close", (c) => resolve4(c ?? 0));
4197
+ child.on("close", (c) => resolve5(c ?? 0));
4072
4198
  });
4073
4199
  if (code !== 0) throw new Error(`editor exited with code ${code}`);
4074
4200
  const updated = readFileSync6(tmpPath, "utf8");
@@ -4382,17 +4508,17 @@ var serveCommand = defineCommand18({
4382
4508
  signalled = true;
4383
4509
  });
4384
4510
  process.on("SIGTERM", onSigterm);
4385
- await new Promise((resolve4, reject) => {
4511
+ await new Promise((resolve5, reject) => {
4386
4512
  proc.on("error", (err) => reject(err));
4387
4513
  proc.on("close", (code) => {
4388
4514
  if (signalled) {
4389
4515
  process.exitCode = 0;
4390
- resolve4();
4516
+ resolve5();
4391
4517
  } else if (code === 0 || code === null) {
4392
- resolve4();
4518
+ resolve5();
4393
4519
  } else {
4394
4520
  process.exitCode = code;
4395
- resolve4();
4521
+ resolve5();
4396
4522
  }
4397
4523
  });
4398
4524
  });
@@ -5993,8 +6119,9 @@ var triggerCommand = defineCommand26({
5993
6119
  });
5994
6120
 
5995
6121
  // src/commands/uninstall.ts
5996
- import { existsSync as existsSync7, readdirSync as readdirSync2, rmSync as rmSync4 } from "fs";
5997
- import { join as join6 } from "path";
6122
+ import { existsSync as existsSync7, lstatSync as lstatSync3, readdirSync as readdirSync2, realpathSync as realpathSync2, rmSync as rmSync4 } from "fs";
6123
+ import { homedir as homedir2 } from "os";
6124
+ import { isAbsolute as isAbsolute2, join as join6, parse as parse2, relative as relative2, resolve as resolve4, sep } from "path";
5998
6125
  import { defineCommand as defineCommand27 } from "citty";
5999
6126
  function makeLineReader() {
6000
6127
  let buffer = "";
@@ -6005,8 +6132,8 @@ function makeLineReader() {
6005
6132
  const nlIdx = buffer.indexOf("\n");
6006
6133
  if (nlIdx === -1) {
6007
6134
  if (ended) {
6008
- const resolve4 = queue.shift();
6009
- resolve4?.(buffer);
6135
+ const resolve5 = queue.shift();
6136
+ resolve5?.(buffer);
6010
6137
  buffer = "";
6011
6138
  } else {
6012
6139
  return;
@@ -6014,8 +6141,8 @@ function makeLineReader() {
6014
6141
  } else {
6015
6142
  const line = buffer.slice(0, nlIdx);
6016
6143
  buffer = buffer.slice(nlIdx + 1);
6017
- const resolve4 = queue.shift();
6018
- resolve4?.(line);
6144
+ const resolve5 = queue.shift();
6145
+ resolve5?.(line);
6019
6146
  }
6020
6147
  }
6021
6148
  };
@@ -6028,8 +6155,8 @@ function makeLineReader() {
6028
6155
  ended = true;
6029
6156
  tryDeliver();
6030
6157
  });
6031
- return () => new Promise((resolve4) => {
6032
- queue.push(resolve4);
6158
+ return () => new Promise((resolve5) => {
6159
+ queue.push(resolve5);
6033
6160
  tryDeliver();
6034
6161
  });
6035
6162
  }
@@ -6039,10 +6166,49 @@ async function askYes(readLine, prompt) {
6039
6166
  return answer === "y" || answer === "yes";
6040
6167
  }
6041
6168
  function removePath(p) {
6042
- if (!existsSync7(p)) return false;
6169
+ try {
6170
+ lstatSync3(p);
6171
+ } catch (error) {
6172
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
6173
+ throw error;
6174
+ }
6043
6175
  rmSync4(p, { recursive: true, force: true });
6044
6176
  return true;
6045
6177
  }
6178
+ function canonicalExistingPath(path) {
6179
+ try {
6180
+ return realpathSync2.native(path);
6181
+ } catch (error) {
6182
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
6183
+ return resolve4(path);
6184
+ }
6185
+ }
6186
+ function containsPath(container, candidate) {
6187
+ const nested = relative2(container, candidate);
6188
+ return nested === "" || nested !== ".." && !nested.startsWith(`..${sep}`) && !isAbsolute2(nested);
6189
+ }
6190
+ function assertSafeUninstallHome(home) {
6191
+ const target = resolve4(home);
6192
+ const canonical = canonicalExistingPath(target);
6193
+ const userHome = canonicalExistingPath(homedir2());
6194
+ const workingDirectory = canonicalExistingPath(process.cwd());
6195
+ const exactForbidden = /* @__PURE__ */ new Map([
6196
+ [parse2(canonical).root, "filesystem root"],
6197
+ [userHome, "user home directory"],
6198
+ [workingDirectory, "current working directory"]
6199
+ ]);
6200
+ const reason = exactForbidden.get(canonical);
6201
+ if (reason) throw new Error(`refusing to uninstall from ${reason}: ${target}`);
6202
+ if (containsPath(canonical, userHome)) {
6203
+ throw new Error(`refusing to uninstall from an ancestor of the user home directory: ${target}`);
6204
+ }
6205
+ if (containsPath(canonical, workingDirectory)) {
6206
+ throw new Error(
6207
+ `refusing to uninstall from an ancestor of the current working directory: ${target}`
6208
+ );
6209
+ }
6210
+ return target;
6211
+ }
6046
6212
  var uninstallCommand = defineCommand27({
6047
6213
  meta: {
6048
6214
  name: "uninstall",
@@ -6052,33 +6218,56 @@ var uninstallCommand = defineCommand27({
6052
6218
  home: { type: "string", description: "Override BAZILION_HOME" },
6053
6219
  yes: {
6054
6220
  type: "boolean",
6055
- description: "Skip confirmations (data tier only unless --all is set)"
6221
+ description: "Skip confirmations (reset tier only unless --all is set)"
6056
6222
  },
6057
6223
  all: {
6058
6224
  type: "boolean",
6059
- description: "Also remove configs, logs, and skills (full wipe)"
6225
+ description: "Also remove logs and skills (full wipe)"
6060
6226
  }
6061
6227
  },
6062
6228
  async run({ args }) {
6063
6229
  const paths = resolveCliPaths(args.home);
6064
- const dbFile = join6(paths.home, "bazilion.db");
6065
- const profilesDir = join6(paths.home, "profiles");
6066
- const agentsDir = join6(paths.home, "agents");
6067
- const teamsDir = join6(paths.home, "teams");
6068
- const skillsDir = join6(paths.home, "skills");
6069
- const logsDir = join6(paths.home, "logs");
6070
- if (!existsSync7(paths.home)) {
6071
- console.log(`nothing to remove: ${paths.home} does not exist`);
6230
+ const targetHome = assertSafeUninstallHome(paths.home);
6231
+ const dbFile = join6(targetHome, "bazilion.db");
6232
+ const profilesDir = join6(targetHome, "profiles");
6233
+ const agentsDir = join6(targetHome, "agents");
6234
+ const teamsDir = join6(targetHome, "teams");
6235
+ const legacyGroupsDir = join6(targetHome, "groups");
6236
+ const skillsDir = join6(targetHome, "skills");
6237
+ const logsDir = join6(targetHome, "logs");
6238
+ const legacyConfigFile = join6(targetHome, "config.json");
6239
+ const legacySecretsFile = join6(targetHome, "secrets.enc");
6240
+ if (!existsSync7(targetHome)) {
6241
+ const uninstallLock2 = await acquireHomeRestoreLock(targetHome, "uninstalling");
6242
+ try {
6243
+ console.log(`nothing to remove: ${targetHome} does not exist`);
6244
+ uninstallLock2.markUninstallComplete();
6245
+ } finally {
6246
+ uninstallLock2.release();
6247
+ }
6072
6248
  return;
6073
6249
  }
6074
- console.log(`about to uninstall bazilion at ${paths.home}`);
6250
+ const homeIsSymlink = lstatSync3(targetHome).isSymbolicLink();
6251
+ console.log(`about to uninstall bazilion at ${targetHome}`);
6075
6252
  console.log("");
6076
- const dataTargets = [dbFile, `${dbFile}-wal`, `${dbFile}-shm`, profilesDir, agentsDir, teamsDir];
6253
+ const resetTargets = [
6254
+ dbFile,
6255
+ `${dbFile}-wal`,
6256
+ `${dbFile}-shm`,
6257
+ `${dbFile}-journal`,
6258
+ join6(targetHome, "auth.json"),
6259
+ legacyConfigFile,
6260
+ legacySecretsFile,
6261
+ profilesDir,
6262
+ agentsDir,
6263
+ teamsDir,
6264
+ legacyGroupsDir
6265
+ ];
6077
6266
  const needsPrompt = !args.yes;
6078
6267
  const readLine = needsPrompt ? makeLineReader() : null;
6079
6268
  const wipeData = args.yes || await askYes(
6080
6269
  readLine,
6081
- "remove DB + agent / profile / workspace data?"
6270
+ "remove DB + auth/config + agent / profile / team data?"
6082
6271
  );
6083
6272
  if (!wipeData) {
6084
6273
  console.log("aborted");
@@ -6087,27 +6276,37 @@ var uninstallCommand = defineCommand27({
6087
6276
  }
6088
6277
  const wipeAll = args.all || (args.yes ? false : await askYes(
6089
6278
  readLine,
6090
- "also remove configs, logs, and skills? (full wipe of ~/.bazilion)"
6279
+ `also remove logs and skills? (full wipe of ${targetHome})`
6091
6280
  ));
6092
6281
  process.stdin.pause();
6093
- for (const p of dataTargets) {
6094
- if (removePath(p)) console.log(`removed ${p}`);
6095
- }
6096
- if (wipeAll) {
6097
- const configTargets = [paths.authFile, logsDir, skillsDir];
6098
- for (const p of configTargets) {
6282
+ const uninstallLock = await acquireHomeRestoreLock(targetHome, "uninstalling");
6283
+ try {
6284
+ for (const p of resetTargets) {
6099
6285
  if (removePath(p)) console.log(`removed ${p}`);
6100
6286
  }
6101
- if (readdirSync2(paths.home).length === 0) {
6102
- rmSync4(paths.home, { recursive: true, force: true });
6103
- console.log(`removed ${paths.home}`);
6287
+ if (wipeAll) {
6288
+ const fullWipeTargets = [logsDir, skillsDir];
6289
+ for (const p of fullWipeTargets) {
6290
+ if (removePath(p)) console.log(`removed ${p}`);
6291
+ }
6292
+ if (readdirSync2(targetHome).length === 0 && !homeIsSymlink) {
6293
+ rmSync4(targetHome, { recursive: true, force: true });
6294
+ console.log(`removed ${targetHome}`);
6295
+ } else if (homeIsSymlink && readdirSync2(targetHome).length === 0) {
6296
+ console.log(`kept ${targetHome}: symlinked BAZILION_HOME root is now empty`);
6297
+ } else {
6298
+ console.log(`${targetHome} still has unmanaged files; left in place`);
6299
+ }
6104
6300
  } else {
6105
- console.log(`${paths.home} still has unmanaged files; left in place`);
6301
+ console.log("");
6302
+ console.log("kept: logs/, skills/");
6303
+ console.log(
6304
+ `re-run 'bazilion serve' to create a fresh DB and matching auth.json when you're ready.`
6305
+ );
6106
6306
  }
6107
- } else {
6108
- console.log("");
6109
- console.log("kept: auth.json, logs/, skills/");
6110
- console.log(`re-run 'bazilion serve' to recreate the DB when you're ready.`);
6307
+ uninstallLock.markUninstallComplete();
6308
+ } finally {
6309
+ uninstallLock.release();
6111
6310
  }
6112
6311
  }
6113
6312
  });