@bivy/bivy 0.20.1-staging.10 → 0.20.1-staging.3

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 (161) hide show
  1. package/bin/bivy.mjs +18 -300
  2. package/bin/cli-commands.mjs +2 -7
  3. package/bin/guides/talk-to-the-user.md +3 -19
  4. package/bin/install-kind.mjs +0 -20
  5. package/dist/agent-instructions.js +3 -8
  6. package/dist/apps/listeners.js +3 -31
  7. package/dist/apps/service.js +1 -8
  8. package/dist/apps/styles.css +2 -12
  9. package/dist/apps/tokens.css +0 -40
  10. package/dist/auth.js +4 -31
  11. package/dist/controllers/app-commands.js +2 -7
  12. package/dist/node-update.js +0 -16
  13. package/dist/protocol/client-command-routes.js +0 -2
  14. package/dist/protocol/client-command-schemas.js +1 -3
  15. package/dist/remote/index.js +0 -2
  16. package/dist/server.js +18 -165
  17. package/dist/session/run-terminal.js +2 -6
  18. package/dist/session/session-namer.js +2 -8
  19. package/dist/session/session-tokens.js +1 -1
  20. package/dist/session/suggestions.js +1 -5
  21. package/package.json +1 -1
  22. package/bin/access-view.mjs +0 -43
  23. package/bin/tailscale.mjs +0 -70
  24. package/bin/tui/app.mjs +0 -189
  25. package/bin/tui/model.mjs +0 -112
  26. package/bin/tui/render.mjs +0 -164
  27. package/bin/tui/text.mjs +0 -72
  28. package/bin/tui/transcript.mjs +0 -54
  29. package/dist/access.js +0 -72
  30. package/dist/machine-theme.js +0 -118
  31. package/dist/pair-codes.js +0 -41
  32. package/dist/remote/direct.js +0 -24
  33. package/dist/remote/tailnet.js +0 -44
  34. package/dist/web-app.js +0 -55
  35. package/web/apple-touch-icon.png +0 -0
  36. package/web/assets/AutomationsView-CjTCAznJ.js +0 -51
  37. package/web/assets/ChangesCard-CBV_oCpv.js +0 -4
  38. package/web/assets/LibraryView-tZ-SLR74.js +0 -2
  39. package/web/assets/ProviderConnect-di0nwwfS.js +0 -1
  40. package/web/assets/ReadinessChecklist-Bbgq2GQ8.js +0 -1
  41. package/web/assets/Settings-wr27-Utd.js +0 -10
  42. package/web/assets/Sheet-DyGDs8Ap.js +0 -2
  43. package/web/assets/StatusDot-dpbztK53.js +0 -1
  44. package/web/assets/Terminal-B1njb2Cr.css +0 -1
  45. package/web/assets/Terminal-BnewuT2I.js +0 -96
  46. package/web/assets/VoiceSettings-gkjVXlwh.js +0 -1
  47. package/web/assets/abnfDiagram-VCTEODGH-xPExYTbq.js +0 -1
  48. package/web/assets/arc-Pry-7G1_.js +0 -1
  49. package/web/assets/architecture-7GRP2DOG-C2VA8oJp.js +0 -1
  50. package/web/assets/architectureDiagram-5GKGNRK7-DY9aKTXO.js +0 -36
  51. package/web/assets/array-BifhSqXX.js +0 -1
  52. package/web/assets/blockDiagram-I7D4REHJ-oAtHEG_S.js +0 -129
  53. package/web/assets/c4Diagram-7LVT6UL2-DAW5aIn-.js +0 -38
  54. package/web/assets/channel-DfQdWzMf.js +0 -1
  55. package/web/assets/chunk-2Q5K7J3B-C1jixKkw.js +0 -1
  56. package/web/assets/chunk-4HAMMTFA-k55Imkrb.js +0 -62
  57. package/web/assets/chunk-5VM5RSS4-ZNzvKenW.js +0 -15
  58. package/web/assets/chunk-75Z2AOVW-9BoEka2V.js +0 -2
  59. package/web/assets/chunk-DU6HZSFF-v3xjhwis.js +0 -127
  60. package/web/assets/chunk-F27PBJKO-ukCww9iX.js +0 -1
  61. package/web/assets/chunk-FOHPRMQF-DzxwRta7.js +0 -161
  62. package/web/assets/chunk-GMAD6QVW-eEDdT117.js +0 -72
  63. package/web/assets/chunk-GVQU2GXP-BJ7btc9s.js +0 -1
  64. package/web/assets/chunk-IMKFNOWR-Cmcy1775.js +0 -231
  65. package/web/assets/chunk-JWPE2WC7-DVXcaiue.js +0 -1
  66. package/web/assets/chunk-L3NEJ4N5-BjMoq7Ow.js +0 -1
  67. package/web/assets/chunk-OSK3NFVY-03uwrVwP.js +0 -10
  68. package/web/assets/chunk-P2QGCYS3-C3poXv15.js +0 -1
  69. package/web/assets/chunk-POPQ4Y6H-DmAcZLrU.js +0 -1
  70. package/web/assets/chunk-PWAF6VOD-Bi69-MYG.js +0 -1
  71. package/web/assets/chunk-SHT3W25Y-DBVBaWqA.js +0 -168
  72. package/web/assets/chunk-SVP7TREG-DA4UOsHJ.js +0 -88
  73. package/web/assets/chunk-TICWLB2K-CcUmJxSS.js +0 -206
  74. package/web/assets/chunk-XXDRQBXY-DqAYeljN.js +0 -1
  75. package/web/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +0 -1
  76. package/web/assets/classDiagram-ZZMXUADV-DDfEWWro.js +0 -1
  77. package/web/assets/classDiagram-v2-VYDZK3BY-DDfEWWro.js +0 -1
  78. package/web/assets/common-DvIV9UK2.js +0 -5
  79. package/web/assets/cose-bilkent-JH36ORCC-BIqBF2fY.js +0 -1
  80. package/web/assets/cynefin-OW5HDTMX-B-p1iHVa.js +0 -1
  81. package/web/assets/cynefinDiagram-5FMLGOSQ-D3t5rjlb.js +0 -62
  82. package/web/assets/cytoscape.esm-NgwPT4ez.js +0 -321
  83. package/web/assets/dagre-C-rnJpZF.js +0 -1
  84. package/web/assets/dagre-GXQ25YYZ-CAVvMffj.js +0 -4
  85. package/web/assets/defaultLocale-BFoDCU3G.js +0 -1
  86. package/web/assets/diagram-S7CK7UJ4-5avxKhVt.js +0 -30
  87. package/web/assets/diagram-UQ7AKVKN-Dre36bGM.js +0 -41
  88. package/web/assets/diagram-VSXAHHWV-DiIGVyBf.js +0 -3
  89. package/web/assets/diagram-VX7I27RA-J24keGuv.js +0 -24
  90. package/web/assets/diagram-Z3DM3KII-CO6mpfCn.js +0 -24
  91. package/web/assets/dist-v5Q1xZ2K.js +0 -1
  92. package/web/assets/ebnfDiagram-PWID7BFC-Cj8nHw-p.js +0 -1
  93. package/web/assets/erDiagram-RLTQ6QDP-DCnatJWn.js +0 -99
  94. package/web/assets/eventmodeling-NTZA5JFV-CUmkoHPv.js +0 -1
  95. package/web/assets/flowDiagram-HODETNUW-CAMIZvHT.js +0 -1
  96. package/web/assets/ganttDiagram-EL5Y4UJY-CnoyY5yJ.js +0 -292
  97. package/web/assets/gitGraph-4MIJSDKK-5QxKBdhV.js +0 -1
  98. package/web/assets/gitGraphDiagram-WWUBYQGX-m72whYP5.js +0 -106
  99. package/web/assets/graphlib-DS17s2tU.js +0 -1
  100. package/web/assets/index-D36pVYmX.css +0 -1
  101. package/web/assets/index-DiYWjsXW.js +0 -3
  102. package/web/assets/info-A6RAGUB7-D0yTLuOD.js +0 -1
  103. package/web/assets/infoDiagram-27XIBGKW-DhMqaJK2.js +0 -2
  104. package/web/assets/init-C-OQMol4.js +0 -1
  105. package/web/assets/ishikawaDiagram-5VMMS53U-D43D69Lw.js +0 -70
  106. package/web/assets/journeyDiagram-3NMN7TZE-BGvdefN8.js +0 -139
  107. package/web/assets/jsx-runtime-NZYk81nU.js +0 -1
  108. package/web/assets/kanban-definition-UXKFOSKX-DTLCjXTM.js +0 -89
  109. package/web/assets/katex-ZlcWpGUi.js +0 -257
  110. package/web/assets/line-D3e4bvOi.js +0 -1
  111. package/web/assets/linear-SzVmGSw4.js +0 -1
  112. package/web/assets/mermaid-parser.core-hF3j4BfN.js +0 -7
  113. package/web/assets/mermaid.core-Dt4GvTcF.js +0 -44
  114. package/web/assets/mindmap-definition-YA3MSWOX-kGjt1kFy.js +0 -96
  115. package/web/assets/mount-BjlUftih.css +0 -1
  116. package/web/assets/mount-DFFc-s3e.js +0 -27
  117. package/web/assets/ordinal-BDEzSJ7C.js +0 -1
  118. package/web/assets/packet-AYTQ26CC-uWunEOCP.js +0 -1
  119. package/web/assets/path-fybaL0A-.js +0 -1
  120. package/web/assets/pegDiagram-XKGWAZYB-DHUvSUTF.js +0 -1
  121. package/web/assets/pie-WAS4IAKB-CDjHTH5e.js +0 -1
  122. package/web/assets/pieDiagram-E7YTZNPT-BXmHg_oo.js +0 -39
  123. package/web/assets/quadrantDiagram-AXDQQJYC-wrGDXaUj.js +0 -7
  124. package/web/assets/radar-RG4KPBEZ-iriLOEHZ.js +0 -1
  125. package/web/assets/railroad-74A4TZTK-Bji_D229.js +0 -1
  126. package/web/assets/railroad-abnf-HS5TGJTU-C2rjIz3_.js +0 -1
  127. package/web/assets/railroad-ebnf-LZEXJU2U-D1IwbAAk.js +0 -1
  128. package/web/assets/railroad-peg-WCYAUIDC-D6AkTvV0.js +0 -1
  129. package/web/assets/railroadDiagram-O6MQD6OU-DaMvYvYd.js +0 -1
  130. package/web/assets/requirementDiagram-BXWQKSXE-CSk6pyHt.js +0 -84
  131. package/web/assets/rolldown-runtime-hePW80VL.js +0 -1
  132. package/web/assets/rough.esm-Dy-Kn_BL.js +0 -1
  133. package/web/assets/sankeyDiagram-P5KCCOFB-CDYh3o0x.js +0 -40
  134. package/web/assets/sequenceDiagram-WJ2MYXX4-Cebdnnr3.js +0 -162
  135. package/web/assets/settingsRoute-D0Hlh8NA.js +0 -1
  136. package/web/assets/sizeCapture-INFHLROL-3uHXrFqA.js +0 -1
  137. package/web/assets/src-B6xuSHsQ.js +0 -1
  138. package/web/assets/stateDiagram-D77RDMKH-B9UCBm56.js +0 -1
  139. package/web/assets/stateDiagram-v2-MP3YSRHH-Btx-OjtR.js +0 -1
  140. package/web/assets/swimlanes-42K2YHIH-zH5kmbnV.js +0 -1
  141. package/web/assets/swimlanesDiagram-VR7AAH4N-CZInbEeM.js +0 -8
  142. package/web/assets/timeline-definition-24CTP7MA-D2tCs5Ws.js +0 -120
  143. package/web/assets/treeView-Q6P3EWNA-BCo4q2Xs.js +0 -1
  144. package/web/assets/treemap-WGGIJYW6-C2Xuxw-p.js +0 -1
  145. package/web/assets/useStore-DECYZPZP.js +0 -27
  146. package/web/assets/vennDiagram-4TSXK5OY-CMD5TuHM.js +0 -34
  147. package/web/assets/wardley-WFR3VGLG-0MDv_diU.js +0 -1
  148. package/web/assets/wardleyDiagram-VM6X3IG4-BOQSNZzn.js +0 -78
  149. package/web/assets/workbox-window.prod.es5-Bd17z0YL.js +0 -2
  150. package/web/assets/xychartDiagram-S5SC5T6Z-B79lzllh.js +0 -7
  151. package/web/fonts/OFL.txt +0 -90
  152. package/web/fonts/newsreader-italic.woff2 +0 -0
  153. package/web/fonts/newsreader-roman.woff2 +0 -0
  154. package/web/icon-192.png +0 -0
  155. package/web/icon-512.png +0 -0
  156. package/web/icon.svg +0 -16
  157. package/web/index.html +0 -36
  158. package/web/manifest.webmanifest +0 -1
  159. package/web/runtime-config.js +0 -3
  160. package/web/sw.js +0 -1
  161. package/web/tent-logo.png +0 -0
package/bin/bivy.mjs CHANGED
@@ -40,10 +40,8 @@ import { nativeResumeRef, resolveSessionsLimit, truncateSavedSessions } from "./
40
40
  import { renderManagedBlock, upsertManagedBlock, removeManagedBlock, rcFileForShell } from "./shim-path.mjs";
41
41
  import { removeInstallAndState } from "./uninstall-paths.mjs";
42
42
  import { findAvailablePort, reconcilePort } from "./port-picker.mjs";
43
- import { accessLine, accessScreen } from "./access-view.mjs";
44
- import { parseTailscaleStatus, serveConflict, serveFailureHint, serveOffArgs, serveOnArgs, serveState } from "./tailscale.mjs";
45
43
  import { resolveAttachSessionId } from "./attach-session-id.mjs";
46
- import { detectInstallKind as classifyInstallKind, managedInstall, npmGlobalPrefix } from "./install-kind.mjs";
44
+ import { detectInstallKind as classifyInstallKind, npmGlobalPrefix } from "./install-kind.mjs";
47
45
  import { hasConfiguredService as configuredServiceExists } from "./service-state.mjs";
48
46
  import { COMMANDS, EXIT, cliError, completionWords, describeCli, describeCommand, describeTools, renderHelp, resolveCommand, subcommandTable, suggestCommands, toolArgv, wantsJson } from "./cli-commands.mjs";
49
47
 
@@ -78,7 +76,6 @@ process.env.BIVY_DATA_DIR = appDir;
78
76
  // - "npx" ephemeral `npx bivy` run (repoRoot under an npm _npx cache)
79
77
  // - "npm-global" `npm i -g @bivy/bivy` (repoRoot is below node_modules/@bivy)
80
78
  // - "packaged" install.sh tarball tree (user-owned, self-preserving)
81
- // - "managed" a system package (the AUR's `bivy`), updated by its manager
82
79
  function detectInstallKind() {
83
80
  return classifyInstallKind(repoRoot);
84
81
  }
@@ -2135,31 +2132,6 @@ async function fetchJson(baseUrl, pathName, token) {
2135
2132
  // --json machine-readable list, no prompt
2136
2133
  // --limit/-n N cap how many saved sessions to show (default: unlimited — all of them)
2137
2134
  // <n> | <id> select non-interactively (index in the list, or a session id)
2138
- // `bivy tui` — every session on this machine and your direct nodes in one
2139
- // terminal view: live transcripts, approvals, messages to the agent.
2140
- async function cmdTui(args = []) {
2141
- if (args.includes("-h") || args.includes("--help")) {
2142
- console.log("Usage: bivy tui\n\nA terminal view of the sessions on this machine and on the nodes you added with 'bivy nodes add': live transcripts, approvals and messages to the agent, all from the keyboard. Press ? inside for keys.");
2143
- return;
2144
- }
2145
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
2146
- console.error(c.red("bivy tui needs an interactive terminal."));
2147
- process.exitCode = EXIT.usage;
2148
- return;
2149
- }
2150
- const config = loadConfig();
2151
- if (!(await ensureNodeRunning(config))) {
2152
- console.error(c.red(`Could not start the Bivy node at ${url(config)}.`));
2153
- process.exitCode = EXIT.unavailable;
2154
- return;
2155
- }
2156
- const token = await localDeviceToken(config);
2157
- const info = await fetchJson(url(config), "/api/node/info", token).catch(() => ({}));
2158
- const direct = Object.entries(loadNodes().nodes).map(([name, node]) => ({ name, base: node.url, token: node.token }));
2159
- const { runTui } = await import("./tui/app.mjs");
2160
- await runTui({ machines: [{ name: info?.name || "this machine", base: url(config), token }, ...direct] });
2161
- }
2162
-
2163
2135
  async function cmdSessions(args = [], opts = {}) {
2164
2136
  if (args.includes("-h") || args.includes("--help")) {
2165
2137
  console.log(
@@ -2549,18 +2521,18 @@ async function cmdContext(args = []) {
2549
2521
  console.log(`\nEverything else: ${c.cyan("bivy help")} (or ${c.cyan("bivy help --json")}).`);
2550
2522
  }
2551
2523
 
2552
- // `bivy suggest "<task>" [--title "…"] [--run here|subagents|new] [--session <id>]`
2553
- // — propose a task the user can start in one tap, in this session or beside it. For an agent
2524
+ // `bivy suggest "<task>" [--title "…"] [--session <id>]` — propose a task the
2525
+ // user can start in one tap, beside this session or in it. For an agent
2554
2526
  // offering next steps: write the task as a complete instruction.
2555
2527
  async function cmdSuggest(args = []) {
2556
- const usage = 'Usage: bivy suggest "<task>" [--title "short label"] [--run here|subagents|new] [--session <id>] [--json]';
2528
+ const usage = 'Usage: bivy suggest "<task>" [--title "short label"] [--session <id>] [--json]';
2557
2529
  if (args.includes("-h") || args.includes("--help")) {
2558
- console.log(`${usage}\n\nPost a task the user can start in one tap: in this session, through your sub-agents, or in a parallel session that works in its own copy of the project. Write it as a complete instruction, with paths relative to the project root.\n\n--run says where you recommend running it, which becomes the card's main button: here (builds on this conversation), subagents (independent tasks you can split across your own sub-agents; only if you have them), or new (bigger independent work the user will want to follow in its own session). Without it, one card recommends here and several recommend new.\n\n--json prints {"ok","id"}.`);
2530
+ console.log(`${usage}\n\nPost a task the user can start in one tap, in this session or in a parallel one that works in its own copy of the project. Write it as a complete instruction, with paths relative to the project root. --json prints {"ok","id"}.`);
2559
2531
  return;
2560
2532
  }
2561
2533
  const json = wantsJson(args);
2562
2534
  const fail = (error) => cliError(error, { json, paint: c.red });
2563
- const flagsWithValue = new Set(["--session", "--title", "--run"]);
2535
+ const flagsWithValue = new Set(["--session", "--title"]);
2564
2536
  const flag = (name) => {
2565
2537
  const i = args.indexOf(name);
2566
2538
  return i >= 0 && i + 1 < args.length ? args[i + 1] : undefined;
@@ -2568,12 +2540,10 @@ async function cmdSuggest(args = []) {
2568
2540
  const text = args.filter((a, i) => !a.startsWith("-") && !(i > 0 && flagsWithValue.has(args[i - 1]))).join(" ").trim();
2569
2541
  const sessionId = resolveAttachSessionId({ sessionFlag: flag("--session"), env: process.env });
2570
2542
  if (!text) return fail({ code: "usage", message: usage, exit: EXIT.usage });
2571
- const run = flag("--run");
2572
- if (run !== undefined && !["here", "subagents", "new"].includes(run)) return fail({ code: "usage", message: `--run is here, subagents or new (got "${run}").`, exit: EXIT.usage });
2573
2543
  if (!sessionId) return fail({ code: "no_session", message: "No session id.", hint: "Run inside an agent session ($BIVY_SESSION_ID) or pass --session <id>.", next: "bivy sessions --json", exit: EXIT.usage });
2574
2544
  const config = loadConfig();
2575
2545
  if (!(await ensureNodeRunning(config))) return fail({ code: "node_unreachable", message: `Could not reach the Bivy node at ${url(config)}.`, next: "bivy status", exit: EXIT.unavailable });
2576
- const res = await sessionPost(config, sessionId, "suggest", { text, title: flag("--title"), run }).catch((error) => error);
2546
+ const res = await sessionPost(config, sessionId, "suggest", { text, title: flag("--title") }).catch((error) => error);
2577
2547
  if (res instanceof Error) return fail({ code: "node_unreachable", message: `Could not reach the Bivy node: ${res.message}`, next: "bivy status", exit: EXIT.unavailable });
2578
2548
  const body = await res.json().catch(() => ({}));
2579
2549
  if (!res.ok) return fail(sessionHttpError("Suggest", res.status, body));
@@ -2581,30 +2551,6 @@ async function cmdSuggest(args = []) {
2581
2551
  console.log(c.green("Suggested in the chat. The user can start it in one tap."));
2582
2552
  }
2583
2553
 
2584
- // `bivy title "<title>" [--session <id>]` — rename the session the agent runs in.
2585
- async function cmdTitle(args = []) {
2586
- const usage = 'Usage: bivy title "<title>" [--session <id>] [--json]';
2587
- if (args.includes("-h") || args.includes("--help")) {
2588
- console.log(`${usage}\n\nRename this session in the user's session list. Use a short title that says what the work is, when the first message made a poor title or the work changed direction. --json prints {"ok","title"}.`);
2589
- return;
2590
- }
2591
- const json = wantsJson(args);
2592
- const fail = (error) => cliError(error, { json, paint: c.red });
2593
- const sessionFlag = args.indexOf("--session");
2594
- const title = args.filter((a, i) => !a.startsWith("-") && !(sessionFlag >= 0 && i === sessionFlag + 1)).join(" ").trim();
2595
- const sessionId = resolveAttachSessionId({ sessionFlag: sessionFlag >= 0 ? args[sessionFlag + 1] : undefined, env: process.env });
2596
- if (!title) return fail({ code: "usage", message: usage, exit: EXIT.usage });
2597
- if (!sessionId) return fail({ code: "no_session", message: "No session id.", hint: "Run inside an agent session ($BIVY_SESSION_ID) or pass --session <id>.", next: "bivy sessions --json", exit: EXIT.usage });
2598
- const config = loadConfig();
2599
- if (!(await ensureNodeRunning(config))) return fail({ code: "node_unreachable", message: `Could not reach the Bivy node at ${url(config)}.`, next: "bivy status", exit: EXIT.unavailable });
2600
- const res = await sessionPost(config, sessionId, "title", { title }).catch((error) => error);
2601
- if (res instanceof Error) return fail({ code: "node_unreachable", message: `Could not reach the Bivy node: ${res.message}`, next: "bivy status", exit: EXIT.unavailable });
2602
- const body = await res.json().catch(() => ({}));
2603
- if (!res.ok) return fail(sessionHttpError("Title", res.status, body));
2604
- if (json) { console.log(JSON.stringify(body)); return; }
2605
- console.log(c.green(`Renamed this session to "${body.title}".`));
2606
- }
2607
-
2608
2554
  // Flags shared by the session commands: `--name value` pairs and bare switches,
2609
2555
  // with every other word collected as positional text.
2610
2556
  function parseSessionArgs(args, valueFlags) {
@@ -4528,8 +4474,6 @@ async function cmdTokenSetup() {
4528
4474
  }
4529
4475
 
4530
4476
  async function cmdSetup(args = []) {
4531
- // Set by the access question; `bivy tailscale` runs once the node is up.
4532
- let wantTailscale = false;
4533
4477
  if (args.includes("-h") || args.includes("--help")) {
4534
4478
  console.log("Usage: bivy setup\n\nFirst-run wizard: agent choice, model login, remote access + sign-in, and background service. Workspace and port get safe defaults. Re-run later to change the default agent or remote access.");
4535
4479
  return;
@@ -4637,22 +4581,18 @@ async function cmdSetup(args = []) {
4637
4581
  // (BIVY_CONTROL_PLANE_URL / BIVY_RELAY_URL then pre-fill the URL prompts below).
4638
4582
  const nodeClaimCode = process.env.BIVY_NODE_CLAIM_CODE?.trim();
4639
4583
  const selfHostEnv = !nodeClaimCode && Boolean((process.env.BIVY_CONTROL_PLANE_URL || "").trim() || (process.env.BIVY_RELAY_URL || "").trim());
4640
- // Tailscale is offered only where it's installed; it needs no account.
4641
- const tailscaleInstalled = runTailscale(["version"]) !== null;
4642
4584
  const syncChoice = nodeClaimCode ? "h" : await askChoice(
4643
- "How will you reach this machine?",
4585
+ "Remote access",
4644
4586
  [
4645
- { key: "h", label: "Bivy hosted (recommended — sign in with GitHub or email; every machine, from anywhere)" },
4646
- ...(tailscaleInstalled ? [{ key: "t", label: "Tailscale (your devices over your tailnet; no account, nothing in between)" }] : []),
4647
- { key: "s", label: "your own server (a Bivy server you run)" },
4648
- { key: "l", label: "this machine only for now (add more any time with 'bivy access')" },
4587
+ { key: "h", label: "hosted (recommended — sign in with GitHub or email; nothing caps your local usage)" },
4588
+ { key: "s", label: "self-hosted (your own control plane + relay)" },
4589
+ { key: "l", label: "local only for now (terminal CLI only; run 'bivy relay:setup' later)" },
4649
4590
  ],
4650
4591
  selfHostEnv ? "s" : "h",
4651
4592
  );
4652
- wantTailscale = syncChoice === "t";
4653
4593
  const relayArgs = [];
4654
- if (syncChoice === "l" || syncChoice === "t") {
4655
- console.log(c.dim(`\n${syncChoice === "t" ? "Tailscale is turned on once the node is running." : "Skipping remote access."} 'bivy access' shows every option later.`));
4594
+ if (syncChoice === "l") {
4595
+ console.log(c.dim("\nSkipping remote access. Enable it any time with 'bivy relay:setup'."));
4656
4596
  }
4657
4597
  if (syncChoice === "s") {
4658
4598
  const endpoints = await getHostedEndpoints();
@@ -4662,7 +4602,7 @@ async function cmdSetup(args = []) {
4662
4602
  if (relayWs.trim()) relayArgs.push("--relay", relayWs.trim());
4663
4603
  }
4664
4604
 
4665
- if (syncChoice !== "l" && syncChoice !== "t") {
4605
+ if (syncChoice !== "l") {
4666
4606
  if (!nodeClaimCode) {
4667
4607
  const loginChoice = await askChoice(
4668
4608
  "Remote login",
@@ -4711,7 +4651,7 @@ async function cmdSetup(args = []) {
4711
4651
  setupSession = consumeSetupSession();
4712
4652
  }
4713
4653
  } else {
4714
- console.log(c.dim("\nRemote access already configured. 'bivy access' shows it and the other options."));
4654
+ console.log(c.dim("\nRemote access already configured. Re-run 'bivy relay:setup' to change sync or sign-in."));
4715
4655
  }
4716
4656
 
4717
4657
  // GitHub App — connect it from the web app (Settings → GitHub App) or with
@@ -4750,19 +4690,6 @@ async function cmdSetup(args = []) {
4750
4690
  }
4751
4691
  }
4752
4692
 
4753
- // The terminal path to the same loop: the agent's own command starts a Bivy
4754
- // session, so work begun in a terminal shows up in the app and reaches the
4755
- // phone. Offered after sign-in, which runs the real agent.
4756
- const shimAgent = setupAgent?.command;
4757
- if (agentReady && shimAgent && AGENT_INTEGRATIONS.has(shimAgent) && !loadShims().shims[shimAgent]) {
4758
- console.log(`\nWhen you type '${shimAgent}' in a terminal, Bivy can run it as a session: it shows up in the app, and your phone hears when it finishes or needs you.`);
4759
- if (await askYesNo(`Start a Bivy session whenever you run '${shimAgent}'?`, true)) {
4760
- rl.pause();
4761
- await run(nodeBin, [selfScript, "shim", "install", shimAgent], { env: process.env });
4762
- rl.resume();
4763
- }
4764
- }
4765
-
4766
4693
  // Materialize canonical typed config before starting/restarting the daemon,
4767
4694
  // so this setup run's workspace/port/agent choices are effective immediately.
4768
4695
  // Existing config keeps every advanced field.
@@ -4804,7 +4731,6 @@ async function cmdSetup(args = []) {
4804
4731
  ? Boolean(liveReadiness?.credential?.ok ?? hasModelConfig(finalConfig))
4805
4732
  : agentAuthReady;
4806
4733
  const repoReady = Boolean(liveReadiness?.repository?.ok);
4807
- if (wantTailscale) await cmdTailscale(["on"]).catch((error) => console.log(c.yellow(`Tailscale setup didn't finish: ${error?.message || error}. Run 'bivy access tailscale' to retry.`)));
4808
4734
  console.log(c.bold(c.green("\n ✓ Node running. Check first-task readiness below.\n")));
4809
4735
  console.log(` ${c.green("✓")} node reachable at ${url(finalConfig)}`);
4810
4736
  console.log(` ${agentReady ? c.green("✓") : c.yellow("!")} runtime ${agentReady ? `${setupAgent?.label || "Pi"} available` : "not installed — run 'bivy agents:install'"}`);
@@ -4813,7 +4739,7 @@ async function cmdSetup(args = []) {
4813
4739
  const ghReady = githubConnected(finalConfig);
4814
4740
  console.log(` ${ghReady ? c.green("✓") : c.dim("○")} GitHub ${ghReady ? "connected — your repos will list in the app" : c.dim("optional — connect later in the app under Settings → GitHub App")}`);
4815
4741
  console.log(` ${agentReady && modelReady && repoReady ? c.green("✓") : c.yellow("!")} first task ${agentReady && modelReady && repoReady ? "ready to try" : "blocked by the stage above"}`);
4816
- console.log(` ${fs.existsSync(relayConfigPath) || fs.existsSync(tailscaleConfigPath) ? c.green("✓") : c.dim("○")} access ${fs.existsSync(relayConfigPath) || fs.existsSync(tailscaleConfigPath) ? "set up — 'bivy access' shows what it gives you" : "this machine only — 'bivy access' shows the options"}\n`);
4742
+ console.log(` ${fs.existsSync(relayConfigPath) ? c.green("✓") : c.yellow("!")} remote ${fs.existsSync(relayConfigPath) ? "configured" : "not configured — run 'bivy relay:setup'"}\n`);
4817
4743
  // Get the user into the product immediately; terminal commands are the
4818
4744
  // fallback/next-step checklist after the remote app has been opened or linked.
4819
4745
  await finishSetupRemote(finalConfig, setupSession);
@@ -5000,10 +4926,8 @@ async function cmdStatus(args = []) {
5000
4926
  const relay = loadRelayConfig();
5001
4927
  const relaySt = status?.relay;
5002
4928
  const relayConfigured = Boolean(relaySt?.configured || relay);
5003
- const access = reachable ? await fetchAccess(config).catch(() => null) : null;
5004
- if (access) console.log(` access: ${accessLine(access)}`);
5005
4929
  if (!relayConfigured) {
5006
- if (!access) console.log(` remote: ${c.dim("this machine only")} ${c.dim("('bivy access' to see the options)")}`);
4930
+ console.log(` remote: ${c.dim("local only")} ${c.dim("('bivy relay:setup' to enable remote access)")}`);
5007
4931
  } else {
5008
4932
  // The live link state is only knowable when the node is running; if it's
5009
4933
  // down we can say it's configured but not whether it's currently connected.
@@ -5198,7 +5122,7 @@ async function cmdDoctor(args = []) {
5198
5122
  const relayApp = status?.relay?.controlPlaneUrl;
5199
5123
  const relayErr = status?.relay?.lastError;
5200
5124
  const relayLine = !relayConfigured
5201
- ? c.dim("this machine only — 'bivy access' to see the options")
5125
+ ? c.dim("local only — 'bivy relay:setup' to enable")
5202
5126
  : relayConnected
5203
5127
  ? c.green("relay connected") + (relayApp ? c.dim(` ${relayApp}`) : "")
5204
5128
  : c.yellow("configured, not connected") + (relayErr ? c.dim(` (${relayErr})`) : "");
@@ -5363,13 +5287,6 @@ async function cmdUpdate(args = []) {
5363
5287
  console.log("Usage: bivy update [--force|--no-wait] [--staging|--stable|--channel <name>]\n\nUpdate Bivy + install deps + restart service. Stays on the release channel recorded at install time (default 'latest'); --staging/--stable/--channel switch channels and are remembered for future updates. Waits for active sessions to finish a turn first; --force/--no-wait skips the wait. See 'bivy update:log' for the last run's output.");
5364
5288
  return;
5365
5289
  }
5366
- const managed = managedInstall(repoRoot);
5367
- if (managed) {
5368
- console.log(`Bivy is installed by ${managed.manager} here, so it updates through it${managed.update ? `: ${c.cyan(managed.update)}` : "."}`);
5369
- console.log(c.dim("Then run 'bivy restart' to pick up the new version."));
5370
- process.exitCode = EXIT.failed;
5371
- return;
5372
- }
5373
5290
  // Inside a Bivy web/PWA terminal the shell is a child of the node's own
5374
5291
  // process, so `restartService()` — the final step of an update — tears down
5375
5292
  // this very terminal. Run inline and you lose all output the moment it lands.
@@ -5754,193 +5671,6 @@ async function cmdLinkPhone(args = []) {
5754
5671
  }
5755
5672
  }
5756
5673
 
5757
- const tailscaleConfigPath = path.join(appDir, "tailscale.json");
5758
- const LOCAL_TOKEN_NAMES = ["Bivy CLI", "Local Remote UI"];
5759
-
5760
- function runTailscale(args) {
5761
- const res = spawnSync("tailscale", args, { encoding: "utf8" });
5762
- if (res.error?.code === "ENOENT") return null;
5763
- return { ok: res.status === 0, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
5764
- }
5765
-
5766
- async function printPairLink(config) {
5767
- const data = await localApi(config, "/api/auth/pair-codes", { method: "POST", body: "{}" });
5768
- if (!data?.url) throw new Error("The node did not return a pairing link.");
5769
- const qr = terminalQr(data.url);
5770
- if (qr) console.log(qr);
5771
- console.log(`\nOpen on your phone (single use, 10 minutes):\n${c.cyan(data.url)}\n`);
5772
- }
5773
-
5774
- // `bivy tailscale` — reach this machine from anywhere on your tailnet with no
5775
- // control plane or relay: `tailscale serve` puts HTTPS on the machine's ts.net
5776
- // name and forwards it to the node's direct listener, which serves the web app.
5777
- async function cmdTailscale(args = []) {
5778
- const [verb = "on"] = args;
5779
- if (args.includes("-h") || args.includes("--help") || !["on", "pair", "status", "devices", "revoke", "off"].includes(verb)) {
5780
- console.log(`Usage: bivy tailscale [on|pair|status|devices|revoke <id>|off]
5781
-
5782
- on Serve this machine's Bivy on https://<machine>.<tailnet>.ts.net (tailnet only) and show a pairing link
5783
- pair Show a new one-time pairing link for another device
5784
- status Show the Tailscale address and whether it is served
5785
- devices List the devices holding a token for this node
5786
- revoke <id> Revoke a device's token and disconnect it
5787
- off Stop serving Bivy over Tailscale`);
5788
- return;
5789
- }
5790
- const config = loadConfig();
5791
- if (!(await isReachable(config))) {
5792
- console.log(c.yellow(`The node is not reachable at ${url(config)}. Start it first: ${c.cyan(config.service ? "bivy restart" : "bivy start")}`));
5793
- process.exitCode = EXIT.unavailable;
5794
- return;
5795
- }
5796
- const saved = (() => { try { return JSON.parse(fs.readFileSync(tailscaleConfigPath, "utf8")); } catch { return null; } })();
5797
-
5798
- if (verb === "pair") {
5799
- if (!saved) { console.log(`Tailscale access is off. Turn it on with ${c.cyan("bivy tailscale")}.`); process.exitCode = EXIT.failed; return; }
5800
- await printPairLink(config);
5801
- return;
5802
- }
5803
- if (verb === "devices") {
5804
- // The CLI and loopback web client mint their own tokens on this machine; list them as a count.
5805
- const all = (await localApi(config, "/api/auth/devices")) ?? [];
5806
- const devices = all.filter((device) => !LOCAL_TOKEN_NAMES.includes(device.name));
5807
- for (const device of devices) console.log(`${c.cyan(device.id)} ${device.name}${device.createdAt ? c.dim(` paired ${device.createdAt}`) : ""}`);
5808
- if (!devices.length) console.log("No paired devices.");
5809
- if (all.length > devices.length) console.log(c.dim(`(+${all.length - devices.length} tokens used on this machine by the CLI)`));
5810
- return;
5811
- }
5812
- if (verb === "revoke") {
5813
- const id = args[1];
5814
- if (!id) { console.log("Usage: bivy tailscale revoke <id> (ids from 'bivy tailscale devices')"); process.exitCode = EXIT.usage; return; }
5815
- const res = await fetch(`${url(config)}/api/auth/devices/${encodeURIComponent(id)}`, { method: "DELETE", headers: { authorization: `Bearer ${await localDeviceToken(config)}` } });
5816
- if (!res.ok) { console.log(c.yellow(`No device ${id}.`)); process.exitCode = EXIT.notFound; return; }
5817
- console.log(`${c.green("✓")} Revoked ${id}.`);
5818
- return;
5819
- }
5820
- if (verb === "status") {
5821
- console.log(saved ? `${c.green("✓")} Served on ${c.cyan(`https://${saved.hostname}`)} (tailnet only) → 127.0.0.1:${saved.port}` : `Tailscale access is off. Turn it on with ${c.cyan("bivy tailscale")}.`);
5822
- return;
5823
- }
5824
- if (verb === "off") {
5825
- if (saved) {
5826
- const state = serveState(runTailscale(["serve", "status", "--json"])?.stdout, saved.hostname);
5827
- if (state.target === `http://127.0.0.1:${saved.port}`) runTailscale(serveOffArgs());
5828
- }
5829
- fs.rmSync(tailscaleConfigPath, { force: true });
5830
- await localApi(config, "/api/direct/reload", { method: "POST", body: "{}" });
5831
- console.log(`${c.green("✓")} Bivy is no longer served over Tailscale. Paired devices keep their tokens; see them with 'bivy tailscale devices' and revoke with 'bivy tailscale revoke <id>'.`);
5832
- return;
5833
- }
5834
-
5835
- const status = runTailscale(["status", "--json"]);
5836
- if (!status) {
5837
- console.log(`${c.yellow("Tailscale isn't installed.")} Install it from ${c.cyan("https://tailscale.com/download")} (Arch: ${c.cyan("sudo pacman -S tailscale && sudo systemctl enable --now tailscaled")}), sign in with ${c.cyan("sudo tailscale up")}, then run ${c.cyan("bivy tailscale")} again.`);
5838
- process.exitCode = EXIT.unavailable;
5839
- return;
5840
- }
5841
- const { running, hostname, owner } = parseTailscaleStatus(status.stdout);
5842
- if (!running || !hostname) {
5843
- console.log(`${c.yellow("Tailscale isn't connected.")} Sign in with ${c.cyan("sudo tailscale up")}, then run ${c.cyan("bivy tailscale")} again.`);
5844
- process.exitCode = EXIT.unavailable;
5845
- return;
5846
- }
5847
- const port = saved?.port ?? await findAvailablePort(config.port + 2, "127.0.0.1");
5848
- const conflict = serveConflict(serveState(runTailscale(["serve", "status", "--json"])?.stdout, hostname), port);
5849
- if (conflict) {
5850
- console.log(c.yellow(conflict));
5851
- process.exitCode = EXIT.conflict;
5852
- return;
5853
- }
5854
- fs.writeFileSync(tailscaleConfigPath, `${JSON.stringify({ port, hostname, ...(owner ? { owner } : {}) }, null, 2)}\n`, { mode: 0o600 });
5855
- const reload = await localApi(config, "/api/direct/reload", { method: "POST", body: "{}" });
5856
- const served = runTailscale(serveOnArgs(port));
5857
- if (!served?.ok) {
5858
- fs.rmSync(tailscaleConfigPath, { force: true });
5859
- await localApi(config, "/api/direct/reload", { method: "POST", body: "{}" }).catch(() => {});
5860
- console.error(c.red(`tailscale serve failed: ${(served?.stderr || served?.stdout || "").trim()}`));
5861
- const hint = serveFailureHint(served?.stderr);
5862
- if (hint) console.log(hint);
5863
- process.exitCode = EXIT.failed;
5864
- return;
5865
- }
5866
- console.log(c.bold(c.green(`\n Bivy is on https://${hostname}\n`)));
5867
- console.log(c.dim(owner
5868
- ? ` Only devices on your tailnet can reach it. Yours (signed in as ${owner}) are let in without pairing;\n anyone else's pairs once with a link from 'bivy tailscale pair'.`
5869
- : " Only devices on your tailnet can reach it. This machine is tagged, so each device pairs once with a link from 'bivy tailscale pair'."));
5870
- if (!reload?.webApp) console.log(c.yellow(" This install has no built web app, so the address serves the API only. Run 'pnpm run build:web' in a source checkout."));
5871
- console.log("");
5872
- if (owner) {
5873
- console.log(`Open ${c.cyan(`https://${hostname}`)} on any of your devices.\n`);
5874
- return;
5875
- }
5876
- await printPairLink(config);
5877
- }
5878
-
5879
- async function fetchAccess(config) {
5880
- return localApi(config, "/api/access/get", { method: "POST", body: "{}" });
5881
- }
5882
-
5883
- // `bivy access` — how this machine can be reached (this machine only,
5884
- // Tailscale, Bivy hosted, your own server), what that gives you, and the next
5885
- // step. The node owns the table (src/access.ts); this only renders and routes.
5886
- async function cmdAccess(args = []) {
5887
- const [verb, target] = args.filter((a) => !a.startsWith("-"));
5888
- if (args.includes("-h") || args.includes("--help") || (verb && !["local", "tailscale", "hosted", "server"].includes(verb))) {
5889
- console.log(`Usage: bivy access [--json]
5890
- bivy access tailscale reach this machine from your devices over Tailscale
5891
- bivy access hosted sign in to Bivy hosted: every machine, from anywhere
5892
- bivy access server <url> [--relay <wss-url>] the same, through your own server
5893
- bivy access local turn remote access off
5894
-
5895
- Setups stack: Tailscale can stay on alongside hosted or your own server.`);
5896
- if (verb && !["local", "tailscale", "hosted", "server"].includes(verb)) process.exitCode = EXIT.usage;
5897
- return;
5898
- }
5899
- const config = loadConfig();
5900
- if (!(await ensureNodeRunning(config))) {
5901
- console.error(c.red(`Could not start the Bivy node at ${url(config)}.`));
5902
- process.exitCode = EXIT.unavailable;
5903
- return;
5904
- }
5905
- const report = await fetchAccess(config);
5906
-
5907
- if (!verb) {
5908
- if (wantsJson(args)) { console.log(JSON.stringify(report, null, 2)); return; }
5909
- console.log(`\n${accessScreen(report, c).join("\n")}\n`);
5910
- return;
5911
- }
5912
- if (verb === "tailscale") return cmdTailscale(["on"]);
5913
- if (verb === "hosted" || verb === "server") {
5914
- const linked = report.setups.find((s) => s.active && (s.id === "hosted" || s.id === "server"));
5915
- if (linked && linked.id === verb && verb === "hosted") {
5916
- console.log(`Already on ${linked.label}${report.controlPlaneUrl ? ` (${report.controlPlaneUrl})` : ""}.`);
5917
- return;
5918
- }
5919
- if (linked) console.log(c.yellow(`This machine is on ${linked.label}${report.controlPlaneUrl ? ` (${report.controlPlaneUrl})` : ""}. Switching keeps everything on this machine; your devices sign in again at the new address.`));
5920
- if (verb === "hosted") return cmdRelaySetup([]);
5921
- if (!target) {
5922
- console.log(`Usage: bivy access server <url> [--relay <wss-url>]\n\nThe address of your Bivy server, e.g. https://bivy.example.com. Set one up with docs/self-host-quickstart.md.`);
5923
- process.exitCode = EXIT.usage;
5924
- return;
5925
- }
5926
- let appUrl;
5927
- try { appUrl = new URL(/^https?:\/\//.test(target) ? target : `https://${target}`); } catch { appUrl = null; }
5928
- if (!appUrl) { console.error(c.red(`Not a URL: ${target}`)); process.exitCode = EXIT.usage; return; }
5929
- // Bivy's server installs serve the relay at /relay on the app's own domain.
5930
- const relayUrl = argValue(args, "relay") || `${appUrl.protocol === "http:" ? "ws" : "wss"}://${appUrl.host}/relay`;
5931
- return cmdRelaySetup(["--control-plane", appUrl.origin, "--relay", relayUrl]);
5932
- }
5933
- // local: turn off every remote setup that is on.
5934
- const on = report.setups.filter((s) => s.active && s.id !== "local");
5935
- if (!on.length) { console.log("Already this machine only."); return; }
5936
- const rl = createPrompter();
5937
- const yes = await rl.askYesNo(`Turn off ${on.map((s) => s.label).join(" and ")}? Sessions and credentials on this machine stay.`, false);
5938
- rl.close();
5939
- if (!yes) return;
5940
- if (on.some((s) => s.id === "tailscale")) await cmdTailscale(["off"]);
5941
- if (on.some((s) => s.id === "hosted" || s.id === "server")) await cmdAccountLogout([]);
5942
- }
5943
-
5944
5674
  function printUninstallHelp() {
5945
5675
  console.log(`
5946
5676
  ${c.bold("bivy uninstall")} — remove Bivy and all its data from this machine
@@ -6355,9 +6085,6 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
6355
6085
  case "suggest":
6356
6086
  await cmdSuggest(args);
6357
6087
  break;
6358
- case "title":
6359
- await cmdTitle(args);
6360
- break;
6361
6088
  case "notify":
6362
6089
  await cmdNotify(args);
6363
6090
  break;
@@ -6541,15 +6268,6 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
6541
6268
  case "link":
6542
6269
  await cmdLinkPhone(args);
6543
6270
  break;
6544
- case "tui":
6545
- await cmdTui(args);
6546
- break;
6547
- case "tailscale":
6548
- await cmdTailscale(args);
6549
- break;
6550
- case "access":
6551
- await cmdAccess(args);
6552
- break;
6553
6271
  case "relay:setup":
6554
6272
  await cmdRelaySetup(args);
6555
6273
  break;
@@ -48,10 +48,8 @@ export const COMMANDS = [
48
48
  tools: [{ name: "notify_user", description: "Send the user a message: a card in the chat, and a push to their phone when nobody has the app open (the push names the session, not the text). Use it when long work finishes, you are blocked, or something needs their attention. At most one push a minute per session.", input: { message: { type: "string", description: "What to tell the user.", required: true }, urgent: { type: "boolean", description: "Push even while the user has the app open." } }, argv: ["notify", { arg: "message" }, { when: "urgent", flag: "--urgent" }] }] },
49
49
  { name: "ask", group: "session", scope: "session", json: true, usage: 'ask "<question>" [--option A --option B] [--async]', summary: "Ask the user a question and wait for the answer", subcommands: ["status", "wait"],
50
50
  tools: [{ name: "ask_user", description: "Ask the user a question in the chat and wait for their answer (their phone gets a push). Offer 2-8 options, or none for a free-text answer; they can always write their own. Returns {status, answer}: status is answered, dismissed (decide yourself) or expired.", input: { question: { type: "string", description: "The question.", required: true }, options: { type: "array", items: { type: "string" }, description: "Choices to pick from (2-8)." }, multiple: { type: "boolean", description: "Allow several choices." }, header: { type: "string", description: "A short label for the card." }, timeout_seconds: { type: "number", description: "How long to wait (default 600)." } }, argv: ["ask", { arg: "question" }, { flag: "--option", from: "options" }, { when: "multiple", flag: "--multi" }, { flag: "--header", from: "header" }, { flag: "--timeout", from: "timeout_seconds" }] }] },
51
- { name: "title", group: "session", scope: "session", json: true, usage: 'title "<title>"', summary: "Rename this session",
52
- tools: [{ name: "set_session_title", description: "Rename this session in the user's session list. Use a short title (under 60 characters) that says what the work is, when the first message made a poor title or the work changed direction.", input: { title: { type: "string", description: "The new title.", required: true } }, argv: ["title", { arg: "title" }] }] },
53
- { name: "suggest", group: "session", scope: "session", json: true, usage: 'suggest "<task>" [--title "label"] [--run here|subagents|new]', summary: "Propose a task the user can start in one tap",
54
- tools: [{ name: "suggest_task", description: "Propose a next step the user can start in one tap: in this session, through your sub-agents, or in a parallel session with its own copy of the project. Write the task as a complete instruction with paths relative to the project root. Post one per idea instead of listing them.", input: { task: { type: "string", description: "The complete instruction.", required: true }, title: { type: "string", description: "A short label for the card." }, run: { type: "string", enum: ["here", "subagents", "new"], description: "Where you recommend running it: here (builds on this conversation), subagents (independent tasks you can split across your own sub-agents; only if you have them), or new (bigger independent work in its own session). Defaults: here for one card, new for several." } }, argv: ["suggest", { arg: "task" }, { flag: "--title", from: "title" }, { flag: "--run", from: "run" }] }] },
51
+ { name: "suggest", group: "session", scope: "session", json: true, usage: 'suggest "<task>" [--title "label"]', summary: "Propose a task the user can start in one tap",
52
+ tools: [{ name: "suggest_task", description: "Propose a next step the user can start in one tap, in this session or in a parallel one with its own copy of the project. Write the task as a complete instruction with paths relative to the project root. Post one per idea instead of listing them.", input: { task: { type: "string", description: "The complete instruction.", required: true }, title: { type: "string", description: "A short label for the card." } }, argv: ["suggest", { arg: "task" }, { flag: "--title", from: "title" }] }] },
55
53
  { name: "app", group: "session", scope: "session", json: true, usage: "app <publish|shot|present|share|run|…>", summary: "Live previews: publish a web/terminal/desktop app, screenshot it, present it", subcommands: ["publish", "list", "remove", "shot", "present", "share", "notes", "run", "click", "type", "key", "scroll", "drag", "move", "menu"],
56
54
  tools: [
57
55
  { name: "app_publish", description: "Give the user a live preview of something with a UI, from an app manifest file (web server port, static build, terminal, or desktop app). Run `bivy app --help` for the manifest format.", input: { manifest_path: { type: "string", description: "Path to the manifest JSON.", required: true } }, argv: ["app", "publish", { arg: "manifest_path" }] },
@@ -65,7 +63,6 @@ export const COMMANDS = [
65
63
  { name: "run", group: "sessions", scope: "node", usage: "run <agent> [--chat] [--name …] [--model …] [--node …] [--workspace …]", summary: "Run an agent (native CLI/TUI) as a session the app can see" },
66
64
  { name: "exec", group: "sessions", scope: "node", json: true, usage: 'exec "<prompt>" [--agent …] [--session …]', summary: "One-shot headless session: prints the answer to stdout" },
67
65
  { name: "send", group: "sessions", scope: "node", json: true, usage: 'send <id> "<message>"', summary: "Send a prompt to an existing session and stream the reply" },
68
- { name: "tui", group: "sessions", scope: "node", usage: "tui", summary: "Sessions across your machines in the terminal: transcripts, approvals, messages" },
69
66
  { name: "sessions", aliases: ["ls"], group: "sessions", scope: "node", json: true, usage: "sessions [--json]", summary: "List recent sessions (live and saved) and resume one" },
70
67
  { name: "resume", group: "sessions", scope: "node", usage: "resume [n|id]", summary: "Resume a session directly (default: most recent)" },
71
68
  { name: "kill", group: "sessions", scope: "node", usage: "kill <id> [--delete]", summary: "Stop a session or terminal" },
@@ -106,8 +103,6 @@ export const COMMANDS = [
106
103
  { name: "token", group: "node", scope: "node", usage: "token", summary: "Print a device token for this node" },
107
104
  { name: "open", group: "node", scope: "node", usage: "open", summary: "Open the remote web app" },
108
105
  { name: "link", group: "node", scope: "node", usage: "link", summary: "Show a web app link as a QR code in the terminal" },
109
- { name: "access", group: "node", scope: "node", json: true, usage: "access [local|tailscale|hosted|server <url>]", summary: "How this machine can be reached, what that gives you, and the next step", subcommands: ["local", "tailscale", "hosted", "server"] },
110
- { name: "tailscale", group: "node", scope: "node", usage: "tailscale [on|pair|status|devices|revoke|off]", summary: "Reach this machine over Tailscale, with no server in between", subcommands: ["on", "pair", "status", "devices", "revoke", "off"] },
111
106
  { name: "relay:setup", group: "node", scope: "node", usage: "relay:setup", summary: "Enable secure remote web app access" },
112
107
  { name: "uninstall", group: "node", scope: "node", usage: "uninstall [--keep-sessions] [--dry-run]", summary: "Remove Bivy and all its data" },
113
108
 
@@ -28,25 +28,9 @@ Ask only when you can't reasonably decide yourself.
28
28
 
29
29
  bivy suggest "Add a GET /version endpoint that returns the package version." --title "Add /version"
30
30
 
31
- Each suggestion is a card the user can start in one tap: here, through your
32
- sub-agents, or in a parallel session with its own copy of the project. Write it as
33
- a complete instruction with paths relative to the project root. Post one per idea
34
- instead of a bulleted list.
35
-
36
- `--run` picks the card's main button; the others stay one tap away:
37
-
38
- | `--run` | When |
39
- |---|---|
40
- | `here` | It builds on this conversation, or it's small. Default for a single card. |
41
- | `subagents` | Independent tasks you can split across your own sub-agents and report back on. Only if you have sub-agents. |
42
- | `new` | Bigger independent work the user will want to follow, review or merge on its own. Default for several cards. |
43
-
44
- ## Name the session: title
45
-
46
- bivy title "Fix login redirect loop"
47
-
48
- The session list shows a title taken from the first message. When that doesn't
49
- say what the work is, or the work changes direction, rename it. Keep it short.
31
+ Each suggestion is a card the user can start in one tap, here or in a parallel
32
+ session with its own copy of the project. Write it as a complete instruction with
33
+ paths relative to the project root. Post one per idea instead of a bulleted list.
50
34
 
51
35
  ## Is anyone there?
52
36
 
@@ -3,29 +3,9 @@
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
 
6
- /** The file a system package (pacman, …) writes into the install it owns. */
7
- export const MANAGED_INSTALL_FILE = ".bivy-install.json";
8
-
9
- /**
10
- * `{ manager, update }` when a system package manager owns this install, so
11
- * Bivy must not replace its files; null otherwise.
12
- */
13
- export function managedInstall(repoRoot, readFileSync = fs.readFileSync) {
14
- try {
15
- const data = JSON.parse(readFileSync(path.join(repoRoot, MANAGED_INSTALL_FILE), "utf8"));
16
- if (typeof data?.manager === "string" && data.manager) {
17
- return { manager: data.manager, update: typeof data.update === "string" ? data.update : "" };
18
- }
19
- } catch {
20
- /* not managed */
21
- }
22
- return null;
23
- }
24
-
25
6
  /** Classify a Bivy package root without assuming unscoped npm package layout. */
26
7
  export function detectInstallKind(repoRoot, existsSync = fs.existsSync) {
27
8
  if (existsSync(path.join(repoRoot, ".git"))) return "git";
28
- if (existsSync(path.join(repoRoot, MANAGED_INSTALL_FILE))) return "managed";
29
9
 
30
10
  // npm installs an unscoped package at node_modules/name and a scoped package
31
11
  // at node_modules/@scope/name. Bivy uses the latter layout.
@@ -39,14 +39,9 @@ export const BIVY_AGENT_NOTE = [
39
39
  "can check your work; `bivy app present` tells the user a visible change is ready to look at. " +
40
40
  "`bivy app --help` has the details.",
41
41
  "- Proposing tasks the user could hand you (next steps, ideas, options): post each with " +
42
- '`bivy suggest "<complete instruction>" [--title "short label"] [--run here|subagents|new]` instead of only ' +
43
- "listing them. The user can start each in one tap, in this session, through your sub-agents, or in a parallel " +
44
- "one that works in its own copy of the project, so write it to stand on its own, with paths relative to the " +
45
- "project root. `--run` is the one you recommend: here when it builds on this conversation, subagents for " +
46
- "independent tasks you can split and supervise (only if you have sub-agents), new for bigger work worth its " +
47
- "own session.",
48
- '- If this session\'s title (taken from the first message) doesn\'t say what the work is, or the work changes ' +
49
- 'direction: `bivy title "<short title>"`.',
42
+ '`bivy suggest "<complete instruction>" [--title "short label"]` instead of only listing them. The user can ' +
43
+ "start each in one tap, in this session or in a parallel one that works in its own copy of the project, so " +
44
+ "write it to stand on its own, with paths relative to the project root.",
50
45
  "- When you finish long work, get blocked, or need the user to look at something: " +
51
46
  '`bivy notify "<message>"` (a chat card, plus a push to their phone when they are away). To ask and wait for ' +
52
47
  'an answer: `bivy ask "<question>" [--option A --option B]` prints their answer (or use your own ask-the-user tool).',