@bivy/bivy 0.10.1-staging.284 → 0.10.1-staging.286

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/bivy.mjs CHANGED
@@ -922,9 +922,10 @@ async function resolveRunSpec(agentId, extraArgs) {
922
922
  return { spec: { agent: id, label: agent.label, command, args: [...(agent.args ?? []), ...extraArgs] } };
923
923
  }
924
924
 
925
- // Pull bivy's own `--name`/`--model` flags (space or `=` form) out of the run
926
- // args so they aren't blindly forwarded. Only honored before a `--` separator,
927
- // past which everything is the raw command the user asked to run.
925
+ // Pull bivy's own run flags out of the args so they aren't blindly forwarded.
926
+ // Only honored before a `--` separator, past which everything is the raw command
927
+ // the user asked to run. `--chat` selects the governed app-session path instead
928
+ // of the native PTY path; `--no-open` keeps that path headless after creation.
928
929
  // Does a token look like a git remote (URL, scp-style, owner/repo, or a path)
929
930
  // rather than an agent id? Lets `--clone <remote>` disambiguate from bare
930
931
  // `--clone` (= current folder's repo) without a required `=`.
@@ -936,10 +937,14 @@ function looksLikeRemote(value) {
936
937
  function extractRunFlags(args) {
937
938
  const rest = [];
938
939
  let name, model, node, workspace;
940
+ let chat = false;
941
+ let noOpen = false;
939
942
  let clone; // undefined = no clone; true = current repo; string = explicit remote
940
943
  for (let i = 0; i < args.length; i++) {
941
944
  const a = args[i];
942
945
  if (a === "--") { rest.push(...args.slice(i)); break; }
946
+ if (a === "--chat") { chat = true; continue; }
947
+ if (a === "--no-open") { noOpen = true; continue; }
943
948
  if (a === "--name" && args[i + 1] !== undefined) { name = args[++i]; continue; }
944
949
  if (a.startsWith("--name=")) { name = a.slice("--name=".length); continue; }
945
950
  if (a === "--model" && args[i + 1] !== undefined) { model = args[++i]; continue; }
@@ -952,7 +957,7 @@ function extractRunFlags(args) {
952
957
  if (a === "--clone") { clone = looksLikeRemote(args[i + 1]) ? args[++i] : true; continue; }
953
958
  rest.push(a);
954
959
  }
955
- return { name: name?.trim() || undefined, model: model?.trim() || undefined, node: node?.trim() || undefined, workspace: workspace?.trim() || undefined, clone, rest };
960
+ return { name: name?.trim() || undefined, model: model?.trim() || undefined, node: node?.trim() || undefined, workspace: workspace?.trim() || undefined, chat, noOpen, clone, rest };
956
961
  }
957
962
 
958
963
  // A safe-ish workspace dir name from a remote or path (basename minus .git).
@@ -1730,7 +1735,7 @@ async function cmdExec(args = []) {
1730
1735
  function cmdCompletions(args = []) {
1731
1736
  const shell = (args[0] || "").toLowerCase();
1732
1737
  const commands = [
1733
- "run", "sessions", "ls", "resume", "promote", "rename", "nodes", "agent", "agents", "agents:install", "shim", "takeover", "token", "exec",
1738
+ "run", "runs", "sessions", "ls", "resume", "promote", "rename", "nodes", "agent", "agents", "agents:install", "shim", "takeover", "token", "exec",
1734
1739
  "send", "attach", "kill", "setup", "start", "stop", "restart", "status", "doctor", "diagnostics", "logs", "login",
1735
1740
  "update", "update:log", "audit", "automation", "config", "plugin", "open", "service", "secrets", "voice", "link", "relay:setup",
1736
1741
  "github:connect", "github:app-create", "github:app-connect", "github:app-sync", "prune", "uninstall", "help", "version",
@@ -1793,12 +1798,67 @@ complete -c bivy -n '__fish_seen_subcommand_from run' -a '${agents.join(" ")}'`)
1793
1798
  // flag on top of it.
1794
1799
  async function cmdRun(args = []) {
1795
1800
  if (!(await ensureDeps())) process.exit(1);
1796
- const { name, model, node, workspace, clone, rest } = extractRunFlags(args);
1801
+ const { name, model, node, workspace, chat, noOpen, clone, rest } = extractRunFlags(args);
1797
1802
  // Bare `bivy` (empty rest) resolves to the configured default agent; an
1798
1803
  // explicit `bivy run <agent>` keeps that agent verbatim.
1799
1804
  const [agentIdArg, ...extraArgs] = rest;
1800
1805
  const agentId = agentIdArg || resolveDefaultAgent();
1801
1806
 
1807
+ if (chat) {
1808
+ if (node) {
1809
+ console.error(c.red("--chat currently starts a governed session on this node; it can't be combined with --node."));
1810
+ process.exit(1);
1811
+ return;
1812
+ }
1813
+ if (agentId === "--" || extraArgs.length) {
1814
+ console.error(c.red("--chat starts a governed agent integration and does not accept native agent arguments or the raw '-- <command>' form."));
1815
+ process.exit(1);
1816
+ return;
1817
+ }
1818
+ let requestedWorkspace;
1819
+ try { requestedWorkspace = resolveWorkspaceDir({ clone, workspace }); }
1820
+ catch (error) { console.error(c.red(error?.message || String(error))); process.exit(1); return; }
1821
+ const config = loadConfig();
1822
+ // Preserve `bivy run`'s cwd contract: from inside a git checkout, a chat run
1823
+ // targets that checkout; elsewhere it uses the node's configured workspace.
1824
+ const sessionWorkspace = requestedWorkspace || defaultRunWorkspace(config);
1825
+ if (!(await ensureNodeRunning(config))) {
1826
+ console.error(c.red(`Could not start the Bivy node at ${url(config)}.`));
1827
+ process.exit(1);
1828
+ return;
1829
+ }
1830
+ try {
1831
+ const created = await localApi(config, "/api/session", {
1832
+ method: "POST",
1833
+ body: JSON.stringify({ agent: agentId, ...(model ? { model: { provider: "", id: model } } : {}), ...(sessionWorkspace ? { workspace: sessionWorkspace } : {}) }),
1834
+ });
1835
+ if (name) {
1836
+ await localApi(config, "/api/sessions/rename", {
1837
+ method: "POST",
1838
+ body: JSON.stringify({ sessionId: created.id, name }),
1839
+ });
1840
+ }
1841
+ const sessionPath = `/sessions/${encodeURIComponent(created.id)}`;
1842
+ const remote = await openRemoteApp({ open: !noOpen, remotePath: sessionPath });
1843
+ console.log(c.green(`Started chat session ${created.id} (${created.agentName || created.runtimeId || agentId}).`));
1844
+ if (remote && (noOpen || !canOpenBrowser())) console.log(`Open it in the Bivy app: ${c.cyan(remote.openUrl)}`);
1845
+ else if (!remote) {
1846
+ console.log(c.yellow("Remote app access is not configured on this node."));
1847
+ console.log(`Run ${c.cyan("bivy relay:setup")}, then open session ${c.cyan(created.id)} in the app.`);
1848
+ }
1849
+ return;
1850
+ } catch (error) {
1851
+ console.error(c.red(`Could not start chat session: ${error?.message || String(error)}`));
1852
+ process.exit(1);
1853
+ return;
1854
+ }
1855
+ }
1856
+ if (noOpen) {
1857
+ console.error(c.red("--no-open is only valid together with --chat."));
1858
+ process.exit(1);
1859
+ return;
1860
+ }
1861
+
1802
1862
  // A cloned/explicit workspace lives on THIS machine, so it only applies to the
1803
1863
  // local node. For a remote --node run the checkout would need to be made there.
1804
1864
  if (node && (clone !== undefined || workspace)) {
@@ -4518,7 +4578,8 @@ ${c.bold("bivy")} — Bivy node CLI
4518
4578
  ${c.cyan("bivy send <id>")} "..." Send a prompt to an existing session and stream the reply
4519
4579
  ${c.cyan("bivy kill <id>")} Stop a session/terminal (--delete also removes a saved session)
4520
4580
  ${c.cyan("bivy prune")} Delete old sessions/workspaces/worktrees (--keep N, --older-than 7d, --dry-run)
4521
- ${c.cyan("bivy exec")} "<prompt>" One-shot headless run: prints the answer to stdout (pipe-friendly)
4581
+ ${c.cyan("bivy exec")} "<prompt>" One-shot headless session: prints the answer to stdout (pipe-friendly)
4582
+ ${c.cyan("bivy runs start")} "<instructions>" Queue a one-off unattended Run with checks and a Receipt
4522
4583
  ${c.cyan("bivy automation")} list | trigger | init | validate | plan | test | apply
4523
4584
  ${c.cyan("bivy config")} init | validate | show | get | set | explain (typed node config)
4524
4585
  ${c.cyan("bivy plugin")} init | validate | doctor | test | install | list | remove
@@ -4573,9 +4634,10 @@ async function main() {
4573
4634
  // args (including its own --help) pass straight through to it — e.g.
4574
4635
  // 'bivy run claude --help' must show Claude's help, not bivy's.
4575
4636
  if (args[0] === "-h" || args[0] === "--help") {
4576
- console.log(`Usage: bivy run <agent> [--name <label>] [--model <model>] [--node <name>] [--clone [remote]] [--workspace <dir>] | -- <command>
4637
+ console.log(`Usage: bivy run <agent> [--chat [--no-open]] [--name <label>] [--model <model>] [--node <name>] [--clone [remote]] [--workspace <dir>] | -- <command>
4577
4638
 
4578
4639
  Run a native agent (real CLI/TUI) as a relay-visible session.
4640
+ Use --chat to start the governed session used by the app instead; --no-open prints its URL without launching a browser.
4579
4641
  Agents: ${[...AGENT_INTEGRATIONS.keys()].join(", ")}, or -- <command> for anything else.
4580
4642
  An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
4581
4643
  break;
@@ -4637,6 +4699,17 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
4637
4699
  case "completion":
4638
4700
  cmdCompletions(args);
4639
4701
  break;
4702
+ case "runs": {
4703
+ if (!(await ensureDeps())) process.exit(1);
4704
+ const [action, ...runArgs] = args;
4705
+ if (!action || ["-h", "--help", "help"].includes(action)) {
4706
+ console.log('Usage: bivy runs start "<instructions>" [--name <title>] [--repo <owner/name>] [--agent <id>] [--model <id>] [--approval <mode>] [--sandbox <tier>] [--max-attempts <n>] [--json]\n\nStart a one-off unattended Run. Unlike `bivy run`, this queues governed background work with checks, evidence, and a Receipt.');
4707
+ break;
4708
+ }
4709
+ if (action !== "start" && action !== "create") { console.error(c.red(`Unknown runs action: ${action}`)); process.exit(1); break; }
4710
+ process.exit(await run(nodeBin, [...nodeScriptArgs(automationEntry), "start-run", ...runArgs], { cwd: process.cwd(), env: process.env }));
4711
+ break;
4712
+ }
4640
4713
  case "automation":
4641
4714
  case "automations": {
4642
4715
  if (!(await ensureDeps())) process.exit(1);
@@ -30,6 +30,11 @@ Commands:
30
30
  test [path] --event <file> Simulate an event and explain the first match
31
31
  apply [path] [--prune] Encrypt instructions and reconcile the control plane
32
32
 
33
+ One-off Run flags (used by 'bivy runs start'):
34
+ --name <title> --repo <owner/name> --agent <id> --model <id>
35
+ --approval <mode> --sandbox <tier> --max-attempts <1-10> --json
36
+ Pass '-' as the instructions to read them from stdin.
37
+
33
38
  Default path: ${DEFAULT_AUTOMATION_CONFIG_PATH}
34
39
  List, trigger, and apply require an enrolled node ('bivy setup'). Instructions
35
40
  are encrypted on this machine before upload; the control plane receives
@@ -175,6 +180,57 @@ async function main() {
175
180
  }
176
181
  return;
177
182
  }
183
+ if (command === "start-run") {
184
+ if (args.includes("-h") || args.includes("--help"))
185
+ usage();
186
+ const valuedFlags = new Set(["--name", "--repo", "--agent", "--model", "--approval", "--sandbox", "--max-attempts"]);
187
+ const positional = [];
188
+ for (let i = 0; i < args.length; i += 1) {
189
+ const arg = args[i];
190
+ if (valuedFlags.has(arg)) {
191
+ i += 1;
192
+ continue;
193
+ }
194
+ if (arg.startsWith("--") && arg.includes("="))
195
+ continue;
196
+ if (arg === "--json")
197
+ continue;
198
+ positional.push(arg);
199
+ }
200
+ let instructions = positional.join(" ").trim();
201
+ if (instructions === "-")
202
+ instructions = fs.readFileSync(0, "utf8").trim();
203
+ if (!instructions)
204
+ throw new Error("instructions are required (or pass '-' to read stdin)");
205
+ const appDir = appDataDir();
206
+ const relay = relayConfig(appDir);
207
+ const identity = NodeIdentity.load(appDir);
208
+ const pairing = PairingStore.load(appDir, relay.e2eKey);
209
+ const approvalMode = value(args, "--approval") ?? "risky";
210
+ const sandbox = value(args, "--sandbox") ?? "workspace-write";
211
+ const maxAttempts = Number(value(args, "--max-attempts") ?? 2);
212
+ const title = (value(args, "--name") ?? instructions.split(/\r?\n/, 1)[0] ?? "One-off Run").slice(0, 120);
213
+ const created = await request(relay, "/node/automation-runs", {
214
+ method: "POST",
215
+ body: JSON.stringify({
216
+ title,
217
+ body: `bivy-room-v1:${identity.nodeId}:${seal(pairing.roomKey(), instructions)}`,
218
+ repo: value(args, "--repo"),
219
+ runtimeId: value(args, "--agent"),
220
+ model: value(args, "--model"),
221
+ approvalMode,
222
+ sandbox,
223
+ maxAttempts,
224
+ }),
225
+ });
226
+ if (args.includes("--json"))
227
+ console.log(JSON.stringify(created, null, 2));
228
+ else {
229
+ console.log(`Queued Run ${created.id} on ${identity.name}.`);
230
+ console.log(`Status: ${created.status}`);
231
+ }
232
+ return;
233
+ }
178
234
  if (command === "init") {
179
235
  const file = configPath(args);
180
236
  if (fs.existsSync(file) && !args.includes("--force"))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.10.1-staging.284",
3
+ "version": "0.10.1-staging.286",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",