@bivy/bivy 0.19.4-staging.2 → 0.19.4-staging.4
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 +395 -153
- package/bin/cli-commands.mjs +273 -0
- package/bin/guides/automate.md +18 -0
- package/bin/guides/long-work.md +23 -0
- package/bin/guides/more-hands.md +18 -0
- package/bin/guides/show-the-user.md +44 -0
- package/bin/guides/talk-to-the-user.md +38 -0
- package/dist/agent-instructions.js +5 -0
- package/dist/apps/styles.css +8 -2
- package/dist/harness/mcp-serve-cli.js +70 -8
- package/dist/question.js +1 -1
- package/dist/server.js +84 -0
- package/dist/session/agent-context.js +15 -0
- package/dist/session/agent-questions.js +94 -0
- package/dist/session/event-log.js +17 -2
- package/dist/session/notices.js +25 -0
- package/package.json +1 -1
package/bin/bivy.mjs
CHANGED
|
@@ -43,6 +43,7 @@ import { findAvailablePort, reconcilePort } from "./port-picker.mjs";
|
|
|
43
43
|
import { resolveAttachSessionId } from "./attach-session-id.mjs";
|
|
44
44
|
import { detectInstallKind as classifyInstallKind, npmGlobalPrefix } from "./install-kind.mjs";
|
|
45
45
|
import { hasConfiguredService as configuredServiceExists } from "./service-state.mjs";
|
|
46
|
+
import { COMMANDS, EXIT, cliError, completionWords, describeCli, describeCommand, describeTools, renderHelp, resolveCommand, subcommandTable, suggestCommands, toolArgv, wantsJson } from "./cli-commands.mjs";
|
|
46
47
|
|
|
47
48
|
const selfScript = fileURLToPath(import.meta.url);
|
|
48
49
|
const __dirname = path.dirname(selfScript);
|
|
@@ -1822,18 +1823,16 @@ function translateExecAgent(args = []) {
|
|
|
1822
1823
|
}
|
|
1823
1824
|
|
|
1824
1825
|
// `bivy completions <bash|zsh|fish>` — print a shell completion script to eval or
|
|
1825
|
-
// install.
|
|
1826
|
+
// install. Commands and their verbs come from bin/cli-commands.mjs; `run`
|
|
1827
|
+
// completes the known integration ids.
|
|
1826
1828
|
function cmdCompletions(args = []) {
|
|
1827
1829
|
const shell = (args[0] || "").toLowerCase();
|
|
1828
|
-
const commands =
|
|
1829
|
-
"run", "runs", "sessions", "ls", "resume", "promote", "rename", "nodes", "agent", "agents", "agents:install", "shim", "takeover", "token", "exec",
|
|
1830
|
-
"send", "attach", "suggest", "delegate", "kill", "setup", "start", "stop", "restart", "status", "doctor", "diagnostics", "capabilities", "logs", "login", "logout", "signout", "provider", "model",
|
|
1831
|
-
"update", "update:log", "audit", "automation", "config", "plugin", "open", "service", "secrets", "voice", "link", "relay:setup",
|
|
1832
|
-
"github:connect", "github:app-create", "github:app-connect", "github:app-sync", "prune", "uninstall", "help", "version",
|
|
1833
|
-
];
|
|
1830
|
+
const commands = completionWords();
|
|
1834
1831
|
const agents = [...AGENT_INTEGRATIONS.keys()];
|
|
1832
|
+
const verbs = [{ words: ["run"], subcommands: agents }, ...subcommandTable()];
|
|
1835
1833
|
|
|
1836
1834
|
if (shell === "bash") {
|
|
1835
|
+
const cases = verbs.map((v) => ` ${v.words.join("|")}) COMPREPLY=( $(compgen -W "${v.subcommands.join(" ")}" -- "$cur") );;`).join("\n");
|
|
1837
1836
|
console.log(`# bivy bash completion — add to ~/.bashrc: eval "$(bivy completions bash)"
|
|
1838
1837
|
_bivy_completions() {
|
|
1839
1838
|
local cur prev
|
|
@@ -1843,41 +1842,42 @@ _bivy_completions() {
|
|
|
1843
1842
|
COMPREPLY=( $(compgen -W "${commands.join(" ")}" -- "$cur") )
|
|
1844
1843
|
return
|
|
1845
1844
|
fi
|
|
1845
|
+
[ "$COMP_CWORD" -eq 2 ] || return
|
|
1846
1846
|
case "$prev" in
|
|
1847
|
-
|
|
1848
|
-
provider|model) COMPREPLY=( $(compgen -W "login" -- "$cur") );;
|
|
1847
|
+
${cases}
|
|
1849
1848
|
esac
|
|
1850
1849
|
}
|
|
1851
1850
|
complete -F _bivy_completions bivy`);
|
|
1852
1851
|
return;
|
|
1853
1852
|
}
|
|
1854
1853
|
if (shell === "zsh") {
|
|
1854
|
+
const cases = verbs.map((v) => ` ${v.words.map((w) => `'${w}'`).join("|")}) compadd -- ${v.subcommands.map((x) => `'${x}'`).join(" ")} ;;`).join("\n");
|
|
1855
1855
|
console.log(`# bivy zsh completion — add to ~/.zshrc: eval "$(bivy completions zsh)"
|
|
1856
1856
|
_bivy() {
|
|
1857
|
-
local -a cmds
|
|
1857
|
+
local -a cmds
|
|
1858
1858
|
cmds=(${commands.map((x) => `'${x}'`).join(" ")})
|
|
1859
|
-
agents=(${agents.map((x) => `'${x}'`).join(" ")})
|
|
1860
1859
|
if (( CURRENT == 2 )); then
|
|
1861
1860
|
compadd -- $cmds
|
|
1862
|
-
|
|
1863
|
-
compadd -- $agents
|
|
1864
|
-
elif [[ \${words[2]} == provider || \${words[2]} == model ]]; then
|
|
1865
|
-
compadd -- login
|
|
1861
|
+
return
|
|
1866
1862
|
fi
|
|
1863
|
+
(( CURRENT == 3 )) || return
|
|
1864
|
+
case \${words[2]} in
|
|
1865
|
+
${cases}
|
|
1866
|
+
esac
|
|
1867
1867
|
}
|
|
1868
1868
|
compdef _bivy bivy`);
|
|
1869
1869
|
return;
|
|
1870
1870
|
}
|
|
1871
1871
|
if (shell === "fish") {
|
|
1872
|
+
const lines = verbs.map((v) => `complete -c bivy -n '__fish_seen_subcommand_from ${v.words.join(" ")}' -a '${v.subcommands.join(" ")}'`);
|
|
1872
1873
|
console.log(`# bivy fish completion — save to ~/.config/fish/completions/bivy.fish
|
|
1873
1874
|
complete -c bivy -f
|
|
1874
1875
|
complete -c bivy -n '__fish_use_subcommand' -a '${commands.join(" ")}'
|
|
1875
|
-
|
|
1876
|
-
complete -c bivy -n '__fish_seen_subcommand_from provider model' -a 'login'`);
|
|
1876
|
+
${lines.join("\n")}`);
|
|
1877
1877
|
return;
|
|
1878
1878
|
}
|
|
1879
1879
|
console.error(c.red("Usage: bivy completions <bash|zsh|fish>"));
|
|
1880
|
-
process.exit(
|
|
1880
|
+
process.exit(EXIT.usage);
|
|
1881
1881
|
}
|
|
1882
1882
|
|
|
1883
1883
|
// `bivy run <agent>` — launch a native agent in a daemon-owned PTY and bind this
|
|
@@ -2379,6 +2379,13 @@ async function cmdSend(args = []) {
|
|
|
2379
2379
|
// surfacing in the session's Artifacts list) rather than an incidental inline
|
|
2380
2380
|
// image — see packages/core/src/artifacts.ts.
|
|
2381
2381
|
async function cmdAttach(args = []) {
|
|
2382
|
+
const usage = 'Usage: bivy attach <file> [--caption "…"] [--artifact] [--name <name>] [--mime <type>] [--session <id>] [--json]';
|
|
2383
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
2384
|
+
console.log(`${usage}\n\nShow a local file in the chat: images render inline, other files as downloads. The path must be inside the session workspace. --artifact also lists it in the session's Artifacts. --json prints {"ok","name","kind","size","mimeType","hash"}.`);
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
const json = wantsJson(args);
|
|
2388
|
+
const fail = (error) => cliError(error, { json, paint: c.red });
|
|
2382
2389
|
const flag = (name) => {
|
|
2383
2390
|
const i = args.indexOf(name);
|
|
2384
2391
|
return i >= 0 && i + 1 < args.length ? args[i + 1] : undefined;
|
|
@@ -2398,40 +2405,123 @@ async function cmdAttach(args = []) {
|
|
|
2398
2405
|
file = a;
|
|
2399
2406
|
break;
|
|
2400
2407
|
}
|
|
2401
|
-
if (!file) {
|
|
2402
|
-
if (!sessionId) {
|
|
2408
|
+
if (!file) return fail({ code: "usage", message: usage, exit: EXIT.usage });
|
|
2409
|
+
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 });
|
|
2403
2410
|
const absPath = path.resolve(process.cwd(), file);
|
|
2404
|
-
if (!fs.existsSync(absPath)) {
|
|
2411
|
+
if (!fs.existsSync(absPath)) return fail({ code: "file_not_found", message: `File not found: ${file}`, exit: EXIT.notFound });
|
|
2405
2412
|
|
|
2406
2413
|
const config = loadConfig();
|
|
2407
|
-
if (!(await ensureNodeRunning(config))) {
|
|
2408
|
-
|
|
2409
|
-
|
|
2414
|
+
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 });
|
|
2415
|
+
const res = await sessionPost(config, sessionId, "attach", { path: absPath, caption, name, mimeType, artifact }).catch((error) => error);
|
|
2416
|
+
if (res instanceof Error) return fail({ code: "node_unreachable", message: `Could not reach the Bivy node: ${res.message}`, next: "bivy status", exit: EXIT.unavailable });
|
|
2417
|
+
const body = await res.json().catch(() => ({}));
|
|
2418
|
+
if (!res.ok) return fail(sessionHttpError("Attach", res.status, body));
|
|
2419
|
+
if (json) { console.log(JSON.stringify(body)); return; }
|
|
2420
|
+
console.log(c.green(`Attached ${body.name} (${body.kind}, ${body.size} bytes) to the chat.`));
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// POST to a per-session route. A token isn't required on a single-user host
|
|
2424
|
+
// (loopback bypasses auth), but include one when available so multi-user
|
|
2425
|
+
// hosts work too.
|
|
2426
|
+
async function sessionPost(config, sessionId, action, body) {
|
|
2410
2427
|
let token;
|
|
2411
2428
|
try { token = await localDeviceToken(config); } catch { token = undefined; }
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
}
|
|
2422
|
-
|
|
2423
|
-
|
|
2429
|
+
return fetch(`${url(config)}/api/session/${encodeURIComponent(sessionId)}/${action}`, {
|
|
2430
|
+
method: "POST",
|
|
2431
|
+
headers: { "content-type": "application/json", ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
2432
|
+
body: JSON.stringify(body),
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// A failed per-session request, in the shared error shape.
|
|
2437
|
+
function sessionHttpError(what, status, body) {
|
|
2438
|
+
const message = `${what} failed (${status}): ${body?.error || "unknown error"}`;
|
|
2439
|
+
if (status === 404) return { code: "session_not_found", message, hint: "The session must be open on this node.", next: "bivy context", exit: EXIT.notFound };
|
|
2440
|
+
if (status === 401 || status === 403) return { code: "denied", message, exit: EXIT.denied };
|
|
2441
|
+
if (status === 400) return { code: "invalid", message, exit: EXIT.usage };
|
|
2442
|
+
return { code: "failed", message, exit: EXIT.failed };
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
// The checkout the agent is standing in, from git itself (no node needed).
|
|
2446
|
+
function gitContext(cwd) {
|
|
2447
|
+
const git = (...gitArgs) => {
|
|
2448
|
+
const out = spawnSync("git", gitArgs, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
2449
|
+
return out.status === 0 ? out.stdout.trim() : undefined;
|
|
2450
|
+
};
|
|
2451
|
+
const root = git("rev-parse", "--show-toplevel");
|
|
2452
|
+
if (!root) return undefined;
|
|
2453
|
+
const origin = git("remote", "get-url", "origin");
|
|
2454
|
+
const repo = origin?.match(/[:/]([^/:]+\/[^/]+?)(?:\.git)?$/)?.[1];
|
|
2455
|
+
const status = git("status", "--porcelain");
|
|
2456
|
+
return { root, branch: git("branch", "--show-current") || undefined, ...(repo ? { repo } : {}), changedFiles: status ? status.split("\n").length : 0 };
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
// `bivy context [--json]` — the first thing an agent should run: which session,
|
|
2460
|
+
// workspace, branch and machine it is in, what it has published, and the
|
|
2461
|
+
// session commands it can use. Works outside a session too (session: null).
|
|
2462
|
+
async function cmdContext(args = []) {
|
|
2463
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
2464
|
+
console.log("Usage: bivy context [--json] [--session <id>]\n\nWhere this agent is running: the Bivy session ($BIVY_SESSION_ID), its workspace and git branch, the machine, apps it has published, the device that last drove it, and the commands that reach the user. --json prints it as one object.");
|
|
2424
2465
|
return;
|
|
2425
2466
|
}
|
|
2426
|
-
const
|
|
2427
|
-
|
|
2428
|
-
|
|
2467
|
+
const json = wantsJson(args);
|
|
2468
|
+
const flagAt = args.indexOf("--session");
|
|
2469
|
+
const sessionId = resolveAttachSessionId({ sessionFlag: flagAt >= 0 ? args[flagAt + 1] : undefined, env: process.env });
|
|
2470
|
+
const commands = COMMANDS.filter((command) => command.group === "session" && command.name !== "context")
|
|
2471
|
+
.map((command) => ({ command: `bivy ${command.usage}`, does: command.summary }));
|
|
2472
|
+
let node = { session: null };
|
|
2473
|
+
if (sessionId) {
|
|
2474
|
+
const config = loadConfig();
|
|
2475
|
+
let token;
|
|
2476
|
+
try { token = await localDeviceToken(config); } catch { token = undefined; }
|
|
2477
|
+
try {
|
|
2478
|
+
const res = await fetch(`${url(config)}/api/session/${encodeURIComponent(sessionId)}/context`, { headers: token ? { authorization: `Bearer ${token}` } : {} });
|
|
2479
|
+
const body = await res.json().catch(() => ({}));
|
|
2480
|
+
if (res.status === 404 && body?.error === "Session not found") {
|
|
2481
|
+
cliError({ code: "session_not_found", message: `Session ${sessionId} is not open on this node.`, next: "bivy sessions --json", exit: EXIT.notFound }, { json, paint: c.red });
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
if (res.status === 404) {
|
|
2485
|
+
cliError({ code: "node_outdated", message: "This node is older than 'bivy context'.", hint: "Update it, then try again.", next: "bivy update", exit: EXIT.unavailable }, { json, paint: c.red });
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
if (!res.ok) throw new Error(body?.error || `HTTP ${res.status}`);
|
|
2489
|
+
node = body;
|
|
2490
|
+
} catch (error) {
|
|
2491
|
+
cliError({ code: "node_unreachable", message: `Could not reach the Bivy node at ${url(config)}: ${error?.message || String(error)}`, next: "bivy status", exit: EXIT.unavailable }, { json, paint: c.red });
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
const result = { ...node, git: gitContext(process.cwd()) ?? null, commands, more: "bivy help" };
|
|
2496
|
+
if (json) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
2497
|
+
const s = result.session;
|
|
2498
|
+
if (s) {
|
|
2499
|
+
console.log(`${c.bold("Session")} ${s.name ? `${s.name} ` : ""}${c.dim(s.id)} ${s.agentName || s.agent}${s.busy ? " (working)" : ""}`);
|
|
2500
|
+
console.log(`${c.bold("Machine")} ${result.machine.name} (${result.machine.platform}/${result.machine.arch})`);
|
|
2501
|
+
console.log(`${c.bold("Workspace")} ${s.workspace}`);
|
|
2502
|
+
if (s.forkedFrom) console.log(`${c.bold("Forked")} from ${s.forkedFrom}`);
|
|
2503
|
+
if (result.lastDriver) console.log(`${c.bold("User")} last typed from ${result.lastDriver.label} (${result.lastDriver.via}, ${new Date(result.lastDriver.at).toISOString()})`);
|
|
2504
|
+
console.log(`${c.bold("Apps")} ${result.apps.length ? result.apps.map((a) => `${a.name} [${a.views.map((v) => v.kind).join(", ")}]`).join("; ") : c.dim("none published")}${result.previewAvailable ? "" : c.dim(" (previews not available on this machine)")}`);
|
|
2505
|
+
} else {
|
|
2506
|
+
console.log(c.yellow("Not inside a Bivy session ($BIVY_SESSION_ID is unset). Session commands need --session <id>."));
|
|
2507
|
+
}
|
|
2508
|
+
if (result.git) console.log(`${c.bold("Git")} ${result.git.repo ? `${result.git.repo} ` : ""}${result.git.branch ?? "(detached)"}, ${result.git.changedFiles} changed file(s)`);
|
|
2509
|
+
console.log(`\n${c.bold("Reach the user")}`);
|
|
2510
|
+
for (const row of commands) console.log(` ${c.cyan(row.command)}\n ${row.does}`);
|
|
2511
|
+
console.log(`\nEverything else: ${c.cyan("bivy help")} (or ${c.cyan("bivy help --json")}).`);
|
|
2429
2512
|
}
|
|
2430
2513
|
|
|
2431
2514
|
// `bivy suggest "<task>" [--title "…"] [--session <id>]` — propose a task the
|
|
2432
2515
|
// user can start in one tap, beside this session or in it. For an agent
|
|
2433
2516
|
// offering next steps: write the task as a complete instruction.
|
|
2434
2517
|
async function cmdSuggest(args = []) {
|
|
2518
|
+
const usage = 'Usage: bivy suggest "<task>" [--title "short label"] [--session <id>] [--json]';
|
|
2519
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
2520
|
+
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"}.`);
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
const json = wantsJson(args);
|
|
2524
|
+
const fail = (error) => cliError(error, { json, paint: c.red });
|
|
2435
2525
|
const flagsWithValue = new Set(["--session", "--title"]);
|
|
2436
2526
|
const flag = (name) => {
|
|
2437
2527
|
const i = args.indexOf(name);
|
|
@@ -2439,27 +2529,159 @@ async function cmdSuggest(args = []) {
|
|
|
2439
2529
|
};
|
|
2440
2530
|
const text = args.filter((a, i) => !a.startsWith("-") && !(i > 0 && flagsWithValue.has(args[i - 1]))).join(" ").trim();
|
|
2441
2531
|
const sessionId = resolveAttachSessionId({ sessionFlag: flag("--session"), env: process.env });
|
|
2442
|
-
if (!text) {
|
|
2443
|
-
if (!sessionId) {
|
|
2532
|
+
if (!text) return fail({ code: "usage", message: usage, exit: EXIT.usage });
|
|
2533
|
+
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 });
|
|
2444
2534
|
const config = loadConfig();
|
|
2445
|
-
if (!(await ensureNodeRunning(config))) {
|
|
2535
|
+
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 });
|
|
2536
|
+
const res = await sessionPost(config, sessionId, "suggest", { text, title: flag("--title") }).catch((error) => error);
|
|
2537
|
+
if (res instanceof Error) return fail({ code: "node_unreachable", message: `Could not reach the Bivy node: ${res.message}`, next: "bivy status", exit: EXIT.unavailable });
|
|
2538
|
+
const body = await res.json().catch(() => ({}));
|
|
2539
|
+
if (!res.ok) return fail(sessionHttpError("Suggest", res.status, body));
|
|
2540
|
+
if (json) { console.log(JSON.stringify(body)); return; }
|
|
2541
|
+
console.log(c.green("Suggested in the chat. The user can start it in one tap."));
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// Flags shared by the session commands: `--name value` pairs and bare switches,
|
|
2545
|
+
// with every other word collected as positional text.
|
|
2546
|
+
function parseSessionArgs(args, valueFlags) {
|
|
2547
|
+
const values = {};
|
|
2548
|
+
const positional = [];
|
|
2549
|
+
for (let i = 0; i < args.length; i++) {
|
|
2550
|
+
const a = args[i];
|
|
2551
|
+
if (valueFlags.includes(a)) {
|
|
2552
|
+
const v = args[i + 1];
|
|
2553
|
+
if (v === undefined) return { error: `${a} requires a value.` };
|
|
2554
|
+
(values[a] ??= []).push(v);
|
|
2555
|
+
i++;
|
|
2556
|
+
} else if (!a.startsWith("--")) positional.push(a);
|
|
2557
|
+
}
|
|
2558
|
+
return { values, positional, has: (flag) => args.includes(flag), one: (flag) => values[flag]?.[0] };
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
// "90", "90s", "10m", "2h" → seconds.
|
|
2562
|
+
function durationSeconds(text) {
|
|
2563
|
+
const m = /^(\d+)(s|m|h)?$/.exec(String(text ?? "").trim());
|
|
2564
|
+
return m ? Number(m[1]) * (m[2] === "h" ? 3600 : m[2] === "m" ? 60 : 1) : NaN;
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
// `bivy notify "<message>" [--urgent]` — reach the user: a card in the chat,
|
|
2568
|
+
// and a push naming the session (never the text) when nobody has the app open.
|
|
2569
|
+
async function cmdNotify(args = []) {
|
|
2570
|
+
const usage = 'Usage: bivy notify "<message>" [--urgent] [--session <id>] [--json]';
|
|
2571
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
2572
|
+
console.log(`${usage}
|
|
2573
|
+
|
|
2574
|
+
Send the user a message: it appears as a card in the chat, and their devices get
|
|
2575
|
+
a push notification (naming this session, not the text) when nobody has the app
|
|
2576
|
+
open. --urgent pushes even while they do. At most one push a minute per session;
|
|
2577
|
+
later messages still reach the chat. Use it when you finish long work, get
|
|
2578
|
+
blocked, or need the user to look at something. --json prints
|
|
2579
|
+
{"ok","id","push":"sent"|"user_watching"|"rate_limited","userWatching"}.`);
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
const json = wantsJson(args);
|
|
2583
|
+
const fail = (error) => cliError(error, { json, paint: c.red });
|
|
2584
|
+
const parsed = parseSessionArgs(args, ["--session"]);
|
|
2585
|
+
if (parsed.error) return fail({ code: "usage", message: parsed.error, exit: EXIT.usage });
|
|
2586
|
+
const text = parsed.positional.join(" ").trim();
|
|
2587
|
+
if (!text) return fail({ code: "usage", message: usage, exit: EXIT.usage });
|
|
2588
|
+
const sessionId = resolveAttachSessionId({ sessionFlag: parsed.one("--session"), env: process.env });
|
|
2589
|
+
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 });
|
|
2590
|
+
const config = loadConfig();
|
|
2591
|
+
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 });
|
|
2592
|
+
const res = await sessionPost(config, sessionId, "notify", { text, urgent: parsed.has("--urgent") }).catch((error) => error);
|
|
2593
|
+
if (res instanceof Error) return fail({ code: "node_unreachable", message: `Could not reach the Bivy node: ${res.message}`, next: "bivy status", exit: EXIT.unavailable });
|
|
2594
|
+
const body = await res.json().catch(() => ({}));
|
|
2595
|
+
if (!res.ok) return fail(sessionHttpError("Notify", res.status, body));
|
|
2596
|
+
if (json) { console.log(JSON.stringify(body)); return; }
|
|
2597
|
+
const pushed = { sent: "and pushed to the user's devices", user_watching: "(no push: the user has the app open; add --urgent to push anyway)", rate_limited: "(no push: this session pushed less than a minute ago)", unavailable: "(no push: this machine isn't signed in to a Bivy account)" }[body.push] ?? "";
|
|
2598
|
+
console.log(c.green(`Posted in the chat ${pushed}.`.replace(" .", ".")));
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
// `bivy ask "<question>" [--option …]` — the question card, for any agent.
|
|
2602
|
+
// Blocks until the user answers (printing the answer), or with --async returns
|
|
2603
|
+
// an id to read later with `bivy ask status|wait <id>`.
|
|
2604
|
+
const ASK_USAGE = `Usage: bivy ask "<question>" [--option <label>]… [--multi] [--header <label>] [--timeout 10m] [--async] [--json]
|
|
2605
|
+
bivy ask status <id> [--json]
|
|
2606
|
+
bivy ask wait <id> [--timeout 10m] [--json]`;
|
|
2607
|
+
|
|
2608
|
+
async function cmdAsk(args = []) {
|
|
2609
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
2610
|
+
console.log(`${ASK_USAGE}
|
|
2611
|
+
|
|
2612
|
+
Ask the user a question in the chat and wait for the answer. Their devices get a
|
|
2613
|
+
"needs your input" push. With --option (2 to 8 of them) they pick one (or
|
|
2614
|
+
several with --multi) or write their own; without options they type an answer.
|
|
2615
|
+
--header is the short label on the card.
|
|
2616
|
+
|
|
2617
|
+
Prints the answer and exits 0. Exits 1 if the user dismissed the question and 5
|
|
2618
|
+
if --timeout (default 10m, up to 24h) passes first; the card closes then.
|
|
2619
|
+
--async returns the question's id at once; 'bivy ask wait <id>' blocks for the
|
|
2620
|
+
answer later and 'bivy ask status <id>' checks without waiting.
|
|
2621
|
+
--json prints {"id","status":"pending"|"answered"|"dismissed"|"expired","answer"}.`);
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
const json = wantsJson(args);
|
|
2625
|
+
const fail = (error) => cliError(error, { json, paint: c.red });
|
|
2626
|
+
const parsed = parseSessionArgs(args, ["--session", "--option", "--header", "--timeout"]);
|
|
2627
|
+
if (parsed.error) return fail({ code: "usage", message: parsed.error, exit: EXIT.usage });
|
|
2628
|
+
const sessionId = resolveAttachSessionId({ sessionFlag: parsed.one("--session"), env: process.env });
|
|
2629
|
+
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 });
|
|
2630
|
+
const timeout = parsed.one("--timeout") === undefined ? 600 : durationSeconds(parsed.one("--timeout"));
|
|
2631
|
+
if (!Number.isFinite(timeout) || timeout < 1) return fail({ code: "usage", message: "--timeout takes seconds or a duration like 90s, 10m or 2h.", exit: EXIT.usage });
|
|
2632
|
+
const config = loadConfig();
|
|
2633
|
+
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 });
|
|
2446
2634
|
let token;
|
|
2447
2635
|
try { token = await localDeviceToken(config); } catch { token = undefined; }
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
res = await fetch(`${
|
|
2451
|
-
method: "POST",
|
|
2636
|
+
const base = `${url(config)}/api/session/${encodeURIComponent(sessionId)}/ask`;
|
|
2637
|
+
const call = async (suffix, body) => {
|
|
2638
|
+
const res = await fetch(`${base}${suffix}`, {
|
|
2639
|
+
method: body ? "POST" : "GET",
|
|
2452
2640
|
headers: { "content-type": "application/json", ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
2453
|
-
body: JSON.stringify(
|
|
2454
|
-
});
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2641
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
2642
|
+
}).catch((error) => error);
|
|
2643
|
+
if (res instanceof Error) return fail({ code: "node_unreachable", message: `Could not reach the Bivy node: ${res.message}`, next: "bivy status", exit: EXIT.unavailable });
|
|
2644
|
+
const data = await res.json().catch(() => ({}));
|
|
2645
|
+
if (!res.ok) return fail(sessionHttpError("Ask", res.status, data));
|
|
2646
|
+
return data;
|
|
2647
|
+
};
|
|
2648
|
+
const answerText = (result) => Object.values(result.answers ?? {}).join("\n");
|
|
2649
|
+
const finish = (result) => {
|
|
2650
|
+
const out = { id: result.id, status: result.status, ...(result.status === "answered" ? { answer: answerText(result) } : {}) };
|
|
2651
|
+
if (result.status === "answered") { console.log(json ? JSON.stringify(out) : out.answer); return; }
|
|
2652
|
+
if (result.status === "pending") { console.log(json ? JSON.stringify(out) : `Still waiting for an answer. Check again with: bivy ask wait ${result.id}`); process.exit(EXIT.timeout); }
|
|
2653
|
+
if (json) console.log(JSON.stringify(out));
|
|
2654
|
+
else console.error(result.status === "expired" ? "The question timed out without an answer." : "The user dismissed the question without answering.");
|
|
2655
|
+
process.exit(result.status === "expired" ? EXIT.timeout : EXIT.failed);
|
|
2656
|
+
};
|
|
2657
|
+
const waitFor = async (id, seconds) => {
|
|
2658
|
+
const deadline = Date.now() + seconds * 1000;
|
|
2659
|
+
let result = await call(`/${encodeURIComponent(id)}`);
|
|
2660
|
+
while (result.status === "pending" && Date.now() < deadline) {
|
|
2661
|
+
result = await call(`/${encodeURIComponent(id)}/wait`, { timeoutSeconds: Math.max(1, Math.min(240, Math.ceil((deadline - Date.now()) / 1000))) });
|
|
2662
|
+
}
|
|
2663
|
+
return result;
|
|
2664
|
+
};
|
|
2665
|
+
|
|
2666
|
+
const [action, id] = parsed.positional;
|
|
2667
|
+
if ((action === "status" || action === "wait") && id && parsed.positional.length === 2) {
|
|
2668
|
+
const result = action === "status" ? await call(`/${encodeURIComponent(id)}`) : await waitFor(id, timeout);
|
|
2669
|
+
if (action === "status" && result.status === "pending") { console.log(json ? JSON.stringify({ id: result.id, status: "pending" }) : "Still waiting for an answer."); return; }
|
|
2670
|
+
return finish(result);
|
|
2671
|
+
}
|
|
2672
|
+
const question = parsed.positional.join(" ").trim();
|
|
2673
|
+
if (!question) return fail({ code: "usage", message: ASK_USAGE, exit: EXIT.usage });
|
|
2674
|
+
const options = parsed.values["--option"] ?? [];
|
|
2675
|
+
if (options.length === 1) return fail({ code: "usage", message: "Offer at least two --option values, or none for a free-text answer.", exit: EXIT.usage });
|
|
2676
|
+
const started = await call("", {
|
|
2677
|
+
questions: [{ question, header: parsed.one("--header"), options, multiSelect: parsed.has("--multi") }],
|
|
2678
|
+
timeoutSeconds: timeout,
|
|
2679
|
+
});
|
|
2680
|
+
if (parsed.has("--async")) {
|
|
2681
|
+
console.log(json ? JSON.stringify({ id: started.id, status: started.status }) : `Asked. Get the answer with: bivy ask wait ${started.id}`);
|
|
2458
2682
|
return;
|
|
2459
2683
|
}
|
|
2460
|
-
|
|
2461
|
-
if (!res.ok) { console.error(c.red(`Suggest failed (${res.status}): ${body?.error || "unknown error"}`)); process.exit(1); return; }
|
|
2462
|
-
console.log(c.green("Suggested in the chat. The user can start it in one tap."));
|
|
2684
|
+
finish(await waitFor(started.id, timeout + 5));
|
|
2463
2685
|
}
|
|
2464
2686
|
|
|
2465
2687
|
// Cross-agent / cross-machine delegation (docs/agent-delegation.md): hand a
|
|
@@ -2590,7 +2812,9 @@ async function cmdDelegate(args = []) {
|
|
|
2590
2812
|
// Universal app publishing: every agent that can write JSON and run a command
|
|
2591
2813
|
// uses the same contract. Relative static directories are manifest-relative.
|
|
2592
2814
|
async function cmdApp(args = []) {
|
|
2593
|
-
|
|
2815
|
+
// Output is JSON already; --json is accepted so every session command takes it.
|
|
2816
|
+
const json = wantsJson(args);
|
|
2817
|
+
const [action, ...rest] = args.filter((arg) => arg !== "--json");
|
|
2594
2818
|
if (!action || rest.some((arg) => arg === "--help" || arg === "-h") || ["help", "--help", "-h"].includes(action)) {
|
|
2595
2819
|
console.log(`Usage: bivy app publish <manifest.json> [--session <id>]
|
|
2596
2820
|
bivy app list [--session <id>]
|
|
@@ -2658,7 +2882,7 @@ Web previews require operator setup; see docs/apps.md.`);
|
|
|
2658
2882
|
return;
|
|
2659
2883
|
}
|
|
2660
2884
|
if (INPUT_ACTIONS[action]) return appInput(action, rest);
|
|
2661
|
-
if (action === "menu") return appMenu(rest);
|
|
2885
|
+
if (action === "menu") return appMenu(rest, { json });
|
|
2662
2886
|
if (!["publish", "list", "remove", "shot", "run", "present", "share", "notes"].includes(action)) throw new Error("Unknown app command. Run bivy app --help.");
|
|
2663
2887
|
// `run -- <command> [args…]`: publish a one-window desktop app without a manifest file.
|
|
2664
2888
|
let runManifest;
|
|
@@ -2743,7 +2967,7 @@ Web previews require operator setup; see docs/apps.md.`);
|
|
|
2743
2967
|
await appRequest(action === "run" ? "publish" : action, body);
|
|
2744
2968
|
}
|
|
2745
2969
|
|
|
2746
|
-
async function appMenu(rest) {
|
|
2970
|
+
async function appMenu(rest, { json = false } = {}) {
|
|
2747
2971
|
const options = {};
|
|
2748
2972
|
const positional = [];
|
|
2749
2973
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -2760,7 +2984,7 @@ async function appMenu(rest) {
|
|
|
2760
2984
|
const body = { sessionId, ...(options["--app"] ? { target: options["--app"] } : {}) };
|
|
2761
2985
|
if (positional[0]) body.path = positional[0].split(">").map((part) => part.trim());
|
|
2762
2986
|
const result = await appRequest("menu", body, { print: false });
|
|
2763
|
-
if (!result.menus) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
2987
|
+
if (json || !result.menus) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
2764
2988
|
// A tree an agent can read: one item per line, indented by level.
|
|
2765
2989
|
const lines = [];
|
|
2766
2990
|
const walk = (items, depth) => {
|
|
@@ -5464,81 +5688,104 @@ function readSelfVersion() {
|
|
|
5464
5688
|
}
|
|
5465
5689
|
|
|
5466
5690
|
function printHelp() {
|
|
5467
|
-
|
|
5468
|
-
${c.bold
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
${
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
${
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5691
|
+
const details = { run: `Agents: ${[...AGENT_INTEGRATIONS.keys()].join(" | ")}, or -- <command>` };
|
|
5692
|
+
console.log(`\n${renderHelp({ paint: c.cyan, bold: c.bold, details })}\n`);
|
|
5693
|
+
}
|
|
5694
|
+
|
|
5695
|
+
// `bivy help [command] [--json]`: the whole table, or one command's row. The
|
|
5696
|
+
// row's --help has the full flags; JSON is for agents and scripts.
|
|
5697
|
+
function cmdHelp(args = []) {
|
|
5698
|
+
const json = wantsJson(args);
|
|
5699
|
+
const word = args.find((a) => !a.startsWith("-"));
|
|
5700
|
+
if (!word) {
|
|
5701
|
+
if (json) console.log(JSON.stringify(describeCli(), null, 2));
|
|
5702
|
+
else printHelp();
|
|
5703
|
+
return;
|
|
5704
|
+
}
|
|
5705
|
+
const entry = resolveCommand(word);
|
|
5706
|
+
if (!entry) {
|
|
5707
|
+
const close = suggestCommands(word);
|
|
5708
|
+
cliError({ code: "unknown_command", message: `Unknown command: ${word}`, hint: close.length ? `Did you mean: ${close.join(", ")}?` : undefined, next: "bivy help", exit: EXIT.usage }, { json, paint: c.red });
|
|
5709
|
+
return;
|
|
5710
|
+
}
|
|
5711
|
+
const row = describeCommand(entry);
|
|
5712
|
+
if (json) { console.log(JSON.stringify(row, null, 2)); return; }
|
|
5713
|
+
console.log(`Usage: ${row.usage}\n\n${row.summary}${row.aliases.length ? `\nAliases: ${row.aliases.join(", ")}` : ""}${row.subcommands.length ? `\nSubcommands: ${row.subcommands.join(", ")}` : ""}\n\nFull options: ${row.help}`);
|
|
5714
|
+
}
|
|
5715
|
+
|
|
5716
|
+
// `bivy guide [topic]` — short playbooks for agents, one markdown file per
|
|
5717
|
+
// topic in bin/guides (a "# Title" line, then "Summary: …").
|
|
5718
|
+
function readGuides() {
|
|
5719
|
+
const dir = path.join(__dirname, "guides");
|
|
5720
|
+
return fs.readdirSync(dir).filter((file) => file.endsWith(".md")).sort().map((file) => {
|
|
5721
|
+
const markdown = fs.readFileSync(path.join(dir, file), "utf8");
|
|
5722
|
+
return {
|
|
5723
|
+
topic: file.slice(0, -3),
|
|
5724
|
+
title: markdown.match(/^# (.+)$/m)?.[1] ?? file,
|
|
5725
|
+
summary: markdown.match(/^Summary: (.+)$/m)?.[1] ?? "",
|
|
5726
|
+
markdown: markdown.replace(/^Summary: .+\n\n?/m, ""),
|
|
5727
|
+
};
|
|
5728
|
+
});
|
|
5729
|
+
}
|
|
5730
|
+
|
|
5731
|
+
// `bivy tool <name> '<json>'` — how `bivy mcp-serve` runs a tool: map the input
|
|
5732
|
+
// to the command's words and run it with --json, so a tool and its command can't
|
|
5733
|
+
// behave differently.
|
|
5734
|
+
function cmdTool(args = []) {
|
|
5735
|
+
const [name, raw = "{}"] = args;
|
|
5736
|
+
const tool = describeTools().find((t) => t.name === name);
|
|
5737
|
+
if (!tool) return cliError({ code: "unknown_tool", message: `No tool called "${name}".`, next: "bivy help --json", exit: EXIT.usage }, { json: true });
|
|
5738
|
+
let input;
|
|
5739
|
+
try { input = JSON.parse(raw); } catch { return cliError({ code: "usage", message: "Tool input must be a JSON object.", exit: EXIT.usage }, { json: true }); }
|
|
5740
|
+
const result = spawnSync(process.execPath, [selfScript, ...toolArgv(tool.argv, input ?? {}), "--json"], { stdio: "inherit", env: process.env });
|
|
5741
|
+
process.exit(result.status ?? EXIT.failed);
|
|
5742
|
+
}
|
|
5743
|
+
|
|
5744
|
+
function cmdGuide(args = []) {
|
|
5745
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
5746
|
+
console.log("Usage: bivy guide [topic] [--json]\n\nShort playbooks for agents working in Bivy. Without a topic, lists them.");
|
|
5747
|
+
return;
|
|
5748
|
+
}
|
|
5749
|
+
const json = wantsJson(args);
|
|
5750
|
+
const guides = readGuides();
|
|
5751
|
+
const topic = args.find((a) => !a.startsWith("-"));
|
|
5752
|
+
if (!topic) {
|
|
5753
|
+
if (json) { console.log(JSON.stringify({ guides: guides.map(({ topic: t, title, summary }) => ({ topic: t, title, summary })) }, null, 2)); return; }
|
|
5754
|
+
console.log(`${c.bold("Guides")} (bivy guide <topic>)\n`);
|
|
5755
|
+
for (const guide of guides) console.log(` ${c.cyan(guide.topic.padEnd(18))} ${guide.summary}`);
|
|
5756
|
+
return;
|
|
5757
|
+
}
|
|
5758
|
+
const guide = guides.find((g) => g.topic === topic);
|
|
5759
|
+
if (!guide) {
|
|
5760
|
+
cliError({ code: "unknown_guide", message: `No guide called "${topic}".`, hint: `Guides: ${guides.map((g) => g.topic).join(", ")}.`, next: "bivy guide", exit: EXIT.notFound }, { json, paint: c.red });
|
|
5761
|
+
return;
|
|
5762
|
+
}
|
|
5763
|
+
if (json) { console.log(JSON.stringify({ topic: guide.topic, title: guide.title, markdown: guide.markdown }, null, 2)); return; }
|
|
5764
|
+
console.log(guide.markdown.trimEnd());
|
|
5526
5765
|
}
|
|
5527
5766
|
|
|
5528
5767
|
async function main() {
|
|
5529
5768
|
await hydrateCanonicalConfig();
|
|
5530
5769
|
const argv = process.argv.slice(2);
|
|
5531
5770
|
const [command, ...args] = argv;
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5771
|
+
if (command === undefined) { printHelp(); return; }
|
|
5772
|
+
const entry = resolveCommand(command);
|
|
5773
|
+
if (!entry) {
|
|
5774
|
+
const close = suggestCommands(command);
|
|
5775
|
+
cliError({
|
|
5776
|
+
code: "unknown_command",
|
|
5777
|
+
message: `Unknown command: ${command}`,
|
|
5778
|
+
hint: close.length ? `Did you mean: ${close.map((name) => `bivy ${name}`).join(", ")}?` : undefined,
|
|
5779
|
+
next: "bivy help",
|
|
5780
|
+
exit: EXIT.usage,
|
|
5781
|
+
}, { json: wantsJson(args), paint: c.red });
|
|
5782
|
+
return;
|
|
5783
|
+
}
|
|
5784
|
+
switch (entry.name) {
|
|
5536
5785
|
case "setup":
|
|
5537
|
-
case "init":
|
|
5538
5786
|
await cmdSetup(args);
|
|
5539
5787
|
break;
|
|
5540
5788
|
case "start":
|
|
5541
|
-
case "dev":
|
|
5542
5789
|
await cmdStart(args);
|
|
5543
5790
|
break;
|
|
5544
5791
|
case "run":
|
|
@@ -5559,7 +5806,6 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
5559
5806
|
await cmdRun(args);
|
|
5560
5807
|
break;
|
|
5561
5808
|
case "sessions":
|
|
5562
|
-
case "ls":
|
|
5563
5809
|
await cmdSessions(args);
|
|
5564
5810
|
break;
|
|
5565
5811
|
case "resume":
|
|
@@ -5569,7 +5815,6 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
5569
5815
|
await cmdPromote(args);
|
|
5570
5816
|
break;
|
|
5571
5817
|
case "rename":
|
|
5572
|
-
case "node:rename":
|
|
5573
5818
|
await cmdRename(args);
|
|
5574
5819
|
break;
|
|
5575
5820
|
case "nodes":
|
|
@@ -5584,7 +5829,6 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
5584
5829
|
cmdAgents(args);
|
|
5585
5830
|
break;
|
|
5586
5831
|
case "shim":
|
|
5587
|
-
case "listen":
|
|
5588
5832
|
await cmdShim(args);
|
|
5589
5833
|
break;
|
|
5590
5834
|
case "takeover":
|
|
@@ -5600,7 +5844,6 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
5600
5844
|
await cmdKill(args);
|
|
5601
5845
|
break;
|
|
5602
5846
|
case "prune":
|
|
5603
|
-
case "clean":
|
|
5604
5847
|
await cmdPrune(args);
|
|
5605
5848
|
break;
|
|
5606
5849
|
case "send":
|
|
@@ -5615,11 +5858,16 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
5615
5858
|
case "suggest":
|
|
5616
5859
|
await cmdSuggest(args);
|
|
5617
5860
|
break;
|
|
5861
|
+
case "notify":
|
|
5862
|
+
await cmdNotify(args);
|
|
5863
|
+
break;
|
|
5864
|
+
case "ask":
|
|
5865
|
+
await cmdAsk(args);
|
|
5866
|
+
break;
|
|
5618
5867
|
case "delegate":
|
|
5619
5868
|
await cmdDelegate(args);
|
|
5620
5869
|
break;
|
|
5621
5870
|
case "completions":
|
|
5622
|
-
case "completion":
|
|
5623
5871
|
cmdCompletions(args);
|
|
5624
5872
|
break;
|
|
5625
5873
|
case "runs": {
|
|
@@ -5649,8 +5897,7 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5649
5897
|
process.exit(await run(nodeBin, [...nodeScriptArgs(automationEntry), internalAction, ...runArgs], { cwd: process.cwd(), env: process.env }));
|
|
5650
5898
|
break;
|
|
5651
5899
|
}
|
|
5652
|
-
case "automation":
|
|
5653
|
-
case "automations": {
|
|
5900
|
+
case "automation": {
|
|
5654
5901
|
if (!(await ensureDeps())) process.exit(1);
|
|
5655
5902
|
process.exit(await run(nodeBin, [...nodeScriptArgs(automationEntry), ...args], { cwd: process.cwd(), env: process.env }));
|
|
5656
5903
|
break;
|
|
@@ -5660,17 +5907,14 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5660
5907
|
process.exit(await run(nodeBin, [...nodeScriptArgs(configEntry), ...args], { cwd: process.cwd(), env: process.env }));
|
|
5661
5908
|
break;
|
|
5662
5909
|
}
|
|
5663
|
-
case "auth":
|
|
5664
|
-
case "credentials":
|
|
5665
|
-
case "creds": {
|
|
5910
|
+
case "auth": {
|
|
5666
5911
|
if (!(await ensureDeps())) process.exit(1);
|
|
5667
5912
|
// Forward a subcommand-position --help to the CLI's first-arg help (like secrets).
|
|
5668
5913
|
const forwardArgs = (args.includes("-h") || args.includes("--help")) ? ["--help"] : args;
|
|
5669
5914
|
process.exit(await run(nodeBin, [...nodeScriptArgs(credentialsEntry), ...forwardArgs], { cwd: repoRoot, env: process.env }));
|
|
5670
5915
|
break;
|
|
5671
5916
|
}
|
|
5672
|
-
case "plugin":
|
|
5673
|
-
case "plugins": {
|
|
5917
|
+
case "plugin": {
|
|
5674
5918
|
if (!(await ensureDeps())) process.exit(1);
|
|
5675
5919
|
const pluginEnv = { ...process.env };
|
|
5676
5920
|
const configuredAgents = loadConfig().env?.BIVY_CUSTOM_AGENTS;
|
|
@@ -5724,11 +5968,9 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5724
5968
|
await cmdAccountLogin(args);
|
|
5725
5969
|
break;
|
|
5726
5970
|
case "logout":
|
|
5727
|
-
case "signout":
|
|
5728
5971
|
await cmdAccountLogout(args);
|
|
5729
5972
|
break;
|
|
5730
|
-
case "provider":
|
|
5731
|
-
case "model": {
|
|
5973
|
+
case "provider": {
|
|
5732
5974
|
const [action, ...providerArgs] = args;
|
|
5733
5975
|
if (action === "login") {
|
|
5734
5976
|
await cmdProviderLogin(providerArgs);
|
|
@@ -5750,7 +5992,6 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5750
5992
|
cmdAudit(args);
|
|
5751
5993
|
break;
|
|
5752
5994
|
case "agents:install":
|
|
5753
|
-
case "runtimes:install":
|
|
5754
5995
|
if (args.includes("-h") || args.includes("--help")) {
|
|
5755
5996
|
console.log(`Usage: bivy agents:install [--bridges]\n\nInstall known agent integrations (${KNOWN_AGENT_INSTALLS.map((a) => a.label).join(", ")}).\n\n --bridges only install the SDK bridges Bivy loads for Claude Code and Pi\n (they otherwise install on first use)`);
|
|
5756
5997
|
break;
|
|
@@ -5787,7 +6028,6 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5787
6028
|
await cmdRelaySetup(args);
|
|
5788
6029
|
break;
|
|
5789
6030
|
case "github:connect":
|
|
5790
|
-
case "connect-repo":
|
|
5791
6031
|
if (args.includes("-h") || args.includes("--help")) {
|
|
5792
6032
|
console.log("Usage: bivy github:connect [owner/repo]\n\nOpen Settings → GitHub App in the remote app. Self-hosted deployments with BIVY_GITHUB_OAUTH_CLIENT_ID configured use GitHub's device flow instead.");
|
|
5793
6033
|
break;
|
|
@@ -5856,8 +6096,7 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5856
6096
|
}
|
|
5857
6097
|
break;
|
|
5858
6098
|
}
|
|
5859
|
-
case "secrets":
|
|
5860
|
-
case "secret": {
|
|
6099
|
+
case "secrets": {
|
|
5861
6100
|
if (!(await ensureDeps())) process.exit(1);
|
|
5862
6101
|
// secrets-cli.ts only recognizes "--help"/"help" as the FIRST argument, so
|
|
5863
6102
|
// a subcommand-position --help (e.g. 'bivy secrets list --help') would
|
|
@@ -5866,8 +6105,7 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5866
6105
|
process.exit(await run(nodeBin, [...nodeScriptArgs(secretsEntry), ...forwardArgs], { cwd: repoRoot, env: process.env }));
|
|
5867
6106
|
break;
|
|
5868
6107
|
}
|
|
5869
|
-
case "voice":
|
|
5870
|
-
case "stt": {
|
|
6108
|
+
case "voice": {
|
|
5871
6109
|
if (!(await ensureDeps())) process.exit(1);
|
|
5872
6110
|
const forwardArgs = (args.includes("-h") || args.includes("--help")) ? ["--help"] : args;
|
|
5873
6111
|
process.exit(await run(nodeBin, [...nodeScriptArgs(sttEntry), ...forwardArgs], { cwd: repoRoot, env: process.env }));
|
|
@@ -5888,25 +6126,29 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5888
6126
|
await run(nodeBin, [...nodeScriptArgs(mcpServeEntry), ...args], { cwd: process.cwd(), env: process.env });
|
|
5889
6127
|
break;
|
|
5890
6128
|
case "help":
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
6129
|
+
cmdHelp(args);
|
|
6130
|
+
break;
|
|
6131
|
+
case "context":
|
|
6132
|
+
await cmdContext(args);
|
|
6133
|
+
break;
|
|
6134
|
+
case "guide":
|
|
6135
|
+
cmdGuide(args);
|
|
6136
|
+
break;
|
|
6137
|
+
case "tool":
|
|
6138
|
+
cmdTool(args);
|
|
5894
6139
|
break;
|
|
5895
6140
|
case "version":
|
|
5896
|
-
case "--version":
|
|
5897
|
-
case "-v":
|
|
5898
6141
|
console.log(readSelfVersion());
|
|
5899
6142
|
break;
|
|
5900
6143
|
default:
|
|
5901
|
-
|
|
5902
|
-
printHelp();
|
|
5903
|
-
process.exit(1);
|
|
6144
|
+
throw new Error(`bivy ${entry.name} is listed in bin/cli-commands.mjs but not dispatched.`);
|
|
5904
6145
|
}
|
|
5905
6146
|
}
|
|
5906
6147
|
|
|
5907
6148
|
main().catch((error) => {
|
|
5908
6149
|
// Show a clean message to users; the full stack is only useful with BIVY_DEBUG.
|
|
5909
|
-
|
|
6150
|
+
if (wantsJson(process.argv.slice(2))) process.stderr.write(`${JSON.stringify({ error: { code: "failed", message: error?.message || String(error) } })}\n`);
|
|
6151
|
+
else console.error(c.red(error?.message || String(error)));
|
|
5910
6152
|
if (process.env.BIVY_DEBUG && error?.stack) console.error(c.dim(error.stack));
|
|
5911
6153
|
process.exit(1);
|
|
5912
6154
|
});
|