@bivy/bivy 0.19.4-staging.1 → 0.19.4-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.
- package/bin/bivy.mjs +192 -156
- package/bin/cli-commands.mjs +214 -0
- package/dist/agent-instructions.js +1 -0
- package/dist/apps/annotate.js +85 -1
- package/dist/apps/inspector.js +82 -6
- package/dist/apps/preview-shell.js +385 -152
- package/dist/apps/review.js +30 -0
- package/dist/apps/screenshot.js +16 -2
- package/dist/apps/service.js +134 -10
- package/dist/apps/styles.css +5 -0
- package/dist/apps/types.js +16 -0
- package/dist/controllers/app-commands.js +4 -0
- package/dist/server.js +58 -2
- package/dist/session/agent-context.js +14 -0
- package/dist/session/event-log.js +22 -3
- 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, renderHelp, resolveCommand, subcommandTable, suggestCommands, 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,26 +2529,15 @@ 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))) {
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
let res;
|
|
2449
|
-
try {
|
|
2450
|
-
res = await fetch(`${url(config)}/api/session/${encodeURIComponent(sessionId)}/suggest`, {
|
|
2451
|
-
method: "POST",
|
|
2452
|
-
headers: { "content-type": "application/json", ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
2453
|
-
body: JSON.stringify({ text, title: flag("--title") }),
|
|
2454
|
-
});
|
|
2455
|
-
} catch (error) {
|
|
2456
|
-
console.error(c.red(`Could not reach the Bivy node: ${error?.message || String(error)}`));
|
|
2457
|
-
process.exit(1);
|
|
2458
|
-
return;
|
|
2459
|
-
}
|
|
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 });
|
|
2460
2538
|
const body = await res.json().catch(() => ({}));
|
|
2461
|
-
if (!res.ok)
|
|
2539
|
+
if (!res.ok) return fail(sessionHttpError("Suggest", res.status, body));
|
|
2540
|
+
if (json) { console.log(JSON.stringify(body)); return; }
|
|
2462
2541
|
console.log(c.green("Suggested in the chat. The user can start it in one tap."));
|
|
2463
2542
|
}
|
|
2464
2543
|
|
|
@@ -2590,7 +2669,9 @@ async function cmdDelegate(args = []) {
|
|
|
2590
2669
|
// Universal app publishing: every agent that can write JSON and run a command
|
|
2591
2670
|
// uses the same contract. Relative static directories are manifest-relative.
|
|
2592
2671
|
async function cmdApp(args = []) {
|
|
2593
|
-
|
|
2672
|
+
// Output is JSON already; --json is accepted so every session command takes it.
|
|
2673
|
+
const json = wantsJson(args);
|
|
2674
|
+
const [action, ...rest] = args.filter((arg) => arg !== "--json");
|
|
2594
2675
|
if (!action || rest.some((arg) => arg === "--help" || arg === "-h") || ["help", "--help", "-h"].includes(action)) {
|
|
2595
2676
|
console.log(`Usage: bivy app publish <manifest.json> [--session <id>]
|
|
2596
2677
|
bivy app list [--session <id>]
|
|
@@ -2658,7 +2739,7 @@ Web previews require operator setup; see docs/apps.md.`);
|
|
|
2658
2739
|
return;
|
|
2659
2740
|
}
|
|
2660
2741
|
if (INPUT_ACTIONS[action]) return appInput(action, rest);
|
|
2661
|
-
if (action === "menu") return appMenu(rest);
|
|
2742
|
+
if (action === "menu") return appMenu(rest, { json });
|
|
2662
2743
|
if (!["publish", "list", "remove", "shot", "run", "present", "share", "notes"].includes(action)) throw new Error("Unknown app command. Run bivy app --help.");
|
|
2663
2744
|
// `run -- <command> [args…]`: publish a one-window desktop app without a manifest file.
|
|
2664
2745
|
let runManifest;
|
|
@@ -2743,7 +2824,7 @@ Web previews require operator setup; see docs/apps.md.`);
|
|
|
2743
2824
|
await appRequest(action === "run" ? "publish" : action, body);
|
|
2744
2825
|
}
|
|
2745
2826
|
|
|
2746
|
-
async function appMenu(rest) {
|
|
2827
|
+
async function appMenu(rest, { json = false } = {}) {
|
|
2747
2828
|
const options = {};
|
|
2748
2829
|
const positional = [];
|
|
2749
2830
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -2760,7 +2841,7 @@ async function appMenu(rest) {
|
|
|
2760
2841
|
const body = { sessionId, ...(options["--app"] ? { target: options["--app"] } : {}) };
|
|
2761
2842
|
if (positional[0]) body.path = positional[0].split(">").map((part) => part.trim());
|
|
2762
2843
|
const result = await appRequest("menu", body, { print: false });
|
|
2763
|
-
if (!result.menus) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
2844
|
+
if (json || !result.menus) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
2764
2845
|
// A tree an agent can read: one item per line, indented by level.
|
|
2765
2846
|
const lines = [];
|
|
2766
2847
|
const walk = (items, depth) => {
|
|
@@ -5464,81 +5545,53 @@ function readSelfVersion() {
|
|
|
5464
5545
|
}
|
|
5465
5546
|
|
|
5466
5547
|
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
|
-
${c.cyan("bivy exec")} "<prompt>" One-shot headless session: prints the answer to stdout (pipe-friendly; --agent/--model/--name/--workspace/--session/--json)
|
|
5491
|
-
${c.cyan("bivy runs start")} "<instructions>" Queue a one-off unattended Run with checks and a Receipt
|
|
5492
|
-
${c.cyan("bivy automation")} list | trigger | init | validate | plan | test | apply
|
|
5493
|
-
${c.cyan("bivy config")} init | validate | show | get | set | explain (typed node config)
|
|
5494
|
-
${c.cyan("bivy plugin")} init | validate | doctor | test | install | list | remove
|
|
5495
|
-
${c.cyan("bivy")} Show this help
|
|
5496
|
-
${c.cyan("bivy setup")} First-run wizard: agent, model login, remote sign-in, background service
|
|
5497
|
-
${c.cyan("bivy start")} Run the daemon in the foreground
|
|
5498
|
-
${c.cyan("bivy stop")} Stop the background service
|
|
5499
|
-
${c.cyan("bivy restart")} Restart the background service (waits for active sessions to finish a turn; --force to skip)
|
|
5500
|
-
${c.cyan("bivy status")} Show config and whether the node is reachable
|
|
5501
|
-
${c.cyan("bivy doctor")} Health check: deps, node, model, remote, agents
|
|
5502
|
-
${c.cyan("bivy capabilities")} [--json] What this Machine unlocks: OS, agents, providers, Docker/GPU, plugins, workspaces
|
|
5503
|
-
${c.cyan("bivy logs")} [-f] Tail the node logs (systemd journal, launchd, or background log)
|
|
5504
|
-
${c.cyan("bivy login")} Sign this machine into a Bivy account (GitHub or email)
|
|
5505
|
-
${c.cyan("bivy logout")} Sign this machine out (alias: signout)
|
|
5506
|
-
${c.cyan("bivy provider login")} Sign into a model provider (alias: model login)
|
|
5507
|
-
${c.cyan("bivy auth import")} Import local Claude, Codex, or Grok logins into the vault
|
|
5508
|
-
${c.cyan("bivy update")} Update Bivy + install deps + restart service (waits for active sessions to finish a turn; --force to skip)
|
|
5509
|
-
${c.cyan("bivy update:log")} Show output of the last (or in-progress) update
|
|
5510
|
-
${c.cyan("bivy agent add")} Connect an existing user-owned agent
|
|
5511
|
-
${c.cyan("bivy agents:install")} Install known upstream agents (${KNOWN_AGENT_INSTALLS.map((a) => a.label).join(", ")})
|
|
5512
|
-
${c.cyan("bivy open")} Open the remote web/PWA app
|
|
5513
|
-
${c.cyan("bivy service")} install | uninstall | status
|
|
5514
|
-
${c.cyan("bivy uninstall")} Remove Bivy and all its data (--keep-sessions, --keep-worktrees, --dry-run)
|
|
5515
|
-
${c.cyan("bivy link")} Show a remote web/PWA link QR in the terminal
|
|
5516
|
-
${c.cyan("bivy relay:setup")} Enable secure remote web/PWA access (sign in once)
|
|
5517
|
-
${c.cyan("bivy github:app-create")} One-click: create + connect a GitHub App
|
|
5518
|
-
${c.cyan("bivy github:app-connect")} Connect an existing GitHub App (--app-id --key)
|
|
5519
|
-
${c.cyan("bivy github:app-sync")} [on|off] Sync connected GitHub App keys to this account's other opted-in nodes
|
|
5520
|
-
${c.cyan("bivy github:connect")} [owner/repo] Connect GitHub in the app (or use a configured self-hosted device flow)
|
|
5521
|
-
${c.cyan("bivy secrets")} list | set | ref | delete | doctor | resolve
|
|
5522
|
-
${c.cyan("bivy voice")} Configure speech-to-text: provider | key | remove | status
|
|
5523
|
-
${c.cyan("bivy completions")} <bash|zsh|fish> Print a shell completion script
|
|
5524
|
-
${c.cyan("bivy version")} Print the installed Bivy version (alias: --version, -v)
|
|
5525
|
-
`);
|
|
5548
|
+
const details = { run: `Agents: ${[...AGENT_INTEGRATIONS.keys()].join(" | ")}, or -- <command>` };
|
|
5549
|
+
console.log(`\n${renderHelp({ paint: c.cyan, bold: c.bold, details })}\n`);
|
|
5550
|
+
}
|
|
5551
|
+
|
|
5552
|
+
// `bivy help [command] [--json]`: the whole table, or one command's row. The
|
|
5553
|
+
// row's --help has the full flags; JSON is for agents and scripts.
|
|
5554
|
+
function cmdHelp(args = []) {
|
|
5555
|
+
const json = wantsJson(args);
|
|
5556
|
+
const word = args.find((a) => !a.startsWith("-"));
|
|
5557
|
+
if (!word) {
|
|
5558
|
+
if (json) console.log(JSON.stringify(describeCli(), null, 2));
|
|
5559
|
+
else printHelp();
|
|
5560
|
+
return;
|
|
5561
|
+
}
|
|
5562
|
+
const entry = resolveCommand(word);
|
|
5563
|
+
if (!entry) {
|
|
5564
|
+
const close = suggestCommands(word);
|
|
5565
|
+
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 });
|
|
5566
|
+
return;
|
|
5567
|
+
}
|
|
5568
|
+
const row = describeCommand(entry);
|
|
5569
|
+
if (json) { console.log(JSON.stringify(row, null, 2)); return; }
|
|
5570
|
+
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}`);
|
|
5526
5571
|
}
|
|
5527
5572
|
|
|
5528
5573
|
async function main() {
|
|
5529
5574
|
await hydrateCanonicalConfig();
|
|
5530
5575
|
const argv = process.argv.slice(2);
|
|
5531
5576
|
const [command, ...args] = argv;
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5577
|
+
if (command === undefined) { printHelp(); return; }
|
|
5578
|
+
const entry = resolveCommand(command);
|
|
5579
|
+
if (!entry) {
|
|
5580
|
+
const close = suggestCommands(command);
|
|
5581
|
+
cliError({
|
|
5582
|
+
code: "unknown_command",
|
|
5583
|
+
message: `Unknown command: ${command}`,
|
|
5584
|
+
hint: close.length ? `Did you mean: ${close.map((name) => `bivy ${name}`).join(", ")}?` : undefined,
|
|
5585
|
+
next: "bivy help",
|
|
5586
|
+
exit: EXIT.usage,
|
|
5587
|
+
}, { json: wantsJson(args), paint: c.red });
|
|
5588
|
+
return;
|
|
5589
|
+
}
|
|
5590
|
+
switch (entry.name) {
|
|
5536
5591
|
case "setup":
|
|
5537
|
-
case "init":
|
|
5538
5592
|
await cmdSetup(args);
|
|
5539
5593
|
break;
|
|
5540
5594
|
case "start":
|
|
5541
|
-
case "dev":
|
|
5542
5595
|
await cmdStart(args);
|
|
5543
5596
|
break;
|
|
5544
5597
|
case "run":
|
|
@@ -5559,7 +5612,6 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
5559
5612
|
await cmdRun(args);
|
|
5560
5613
|
break;
|
|
5561
5614
|
case "sessions":
|
|
5562
|
-
case "ls":
|
|
5563
5615
|
await cmdSessions(args);
|
|
5564
5616
|
break;
|
|
5565
5617
|
case "resume":
|
|
@@ -5569,7 +5621,6 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
5569
5621
|
await cmdPromote(args);
|
|
5570
5622
|
break;
|
|
5571
5623
|
case "rename":
|
|
5572
|
-
case "node:rename":
|
|
5573
5624
|
await cmdRename(args);
|
|
5574
5625
|
break;
|
|
5575
5626
|
case "nodes":
|
|
@@ -5584,7 +5635,6 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
5584
5635
|
cmdAgents(args);
|
|
5585
5636
|
break;
|
|
5586
5637
|
case "shim":
|
|
5587
|
-
case "listen":
|
|
5588
5638
|
await cmdShim(args);
|
|
5589
5639
|
break;
|
|
5590
5640
|
case "takeover":
|
|
@@ -5600,7 +5650,6 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
5600
5650
|
await cmdKill(args);
|
|
5601
5651
|
break;
|
|
5602
5652
|
case "prune":
|
|
5603
|
-
case "clean":
|
|
5604
5653
|
await cmdPrune(args);
|
|
5605
5654
|
break;
|
|
5606
5655
|
case "send":
|
|
@@ -5619,7 +5668,6 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
5619
5668
|
await cmdDelegate(args);
|
|
5620
5669
|
break;
|
|
5621
5670
|
case "completions":
|
|
5622
|
-
case "completion":
|
|
5623
5671
|
cmdCompletions(args);
|
|
5624
5672
|
break;
|
|
5625
5673
|
case "runs": {
|
|
@@ -5649,8 +5697,7 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5649
5697
|
process.exit(await run(nodeBin, [...nodeScriptArgs(automationEntry), internalAction, ...runArgs], { cwd: process.cwd(), env: process.env }));
|
|
5650
5698
|
break;
|
|
5651
5699
|
}
|
|
5652
|
-
case "automation":
|
|
5653
|
-
case "automations": {
|
|
5700
|
+
case "automation": {
|
|
5654
5701
|
if (!(await ensureDeps())) process.exit(1);
|
|
5655
5702
|
process.exit(await run(nodeBin, [...nodeScriptArgs(automationEntry), ...args], { cwd: process.cwd(), env: process.env }));
|
|
5656
5703
|
break;
|
|
@@ -5660,17 +5707,14 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5660
5707
|
process.exit(await run(nodeBin, [...nodeScriptArgs(configEntry), ...args], { cwd: process.cwd(), env: process.env }));
|
|
5661
5708
|
break;
|
|
5662
5709
|
}
|
|
5663
|
-
case "auth":
|
|
5664
|
-
case "credentials":
|
|
5665
|
-
case "creds": {
|
|
5710
|
+
case "auth": {
|
|
5666
5711
|
if (!(await ensureDeps())) process.exit(1);
|
|
5667
5712
|
// Forward a subcommand-position --help to the CLI's first-arg help (like secrets).
|
|
5668
5713
|
const forwardArgs = (args.includes("-h") || args.includes("--help")) ? ["--help"] : args;
|
|
5669
5714
|
process.exit(await run(nodeBin, [...nodeScriptArgs(credentialsEntry), ...forwardArgs], { cwd: repoRoot, env: process.env }));
|
|
5670
5715
|
break;
|
|
5671
5716
|
}
|
|
5672
|
-
case "plugin":
|
|
5673
|
-
case "plugins": {
|
|
5717
|
+
case "plugin": {
|
|
5674
5718
|
if (!(await ensureDeps())) process.exit(1);
|
|
5675
5719
|
const pluginEnv = { ...process.env };
|
|
5676
5720
|
const configuredAgents = loadConfig().env?.BIVY_CUSTOM_AGENTS;
|
|
@@ -5724,11 +5768,9 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5724
5768
|
await cmdAccountLogin(args);
|
|
5725
5769
|
break;
|
|
5726
5770
|
case "logout":
|
|
5727
|
-
case "signout":
|
|
5728
5771
|
await cmdAccountLogout(args);
|
|
5729
5772
|
break;
|
|
5730
|
-
case "provider":
|
|
5731
|
-
case "model": {
|
|
5773
|
+
case "provider": {
|
|
5732
5774
|
const [action, ...providerArgs] = args;
|
|
5733
5775
|
if (action === "login") {
|
|
5734
5776
|
await cmdProviderLogin(providerArgs);
|
|
@@ -5750,7 +5792,6 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5750
5792
|
cmdAudit(args);
|
|
5751
5793
|
break;
|
|
5752
5794
|
case "agents:install":
|
|
5753
|
-
case "runtimes:install":
|
|
5754
5795
|
if (args.includes("-h") || args.includes("--help")) {
|
|
5755
5796
|
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
5797
|
break;
|
|
@@ -5787,7 +5828,6 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5787
5828
|
await cmdRelaySetup(args);
|
|
5788
5829
|
break;
|
|
5789
5830
|
case "github:connect":
|
|
5790
|
-
case "connect-repo":
|
|
5791
5831
|
if (args.includes("-h") || args.includes("--help")) {
|
|
5792
5832
|
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
5833
|
break;
|
|
@@ -5856,8 +5896,7 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5856
5896
|
}
|
|
5857
5897
|
break;
|
|
5858
5898
|
}
|
|
5859
|
-
case "secrets":
|
|
5860
|
-
case "secret": {
|
|
5899
|
+
case "secrets": {
|
|
5861
5900
|
if (!(await ensureDeps())) process.exit(1);
|
|
5862
5901
|
// secrets-cli.ts only recognizes "--help"/"help" as the FIRST argument, so
|
|
5863
5902
|
// a subcommand-position --help (e.g. 'bivy secrets list --help') would
|
|
@@ -5866,8 +5905,7 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5866
5905
|
process.exit(await run(nodeBin, [...nodeScriptArgs(secretsEntry), ...forwardArgs], { cwd: repoRoot, env: process.env }));
|
|
5867
5906
|
break;
|
|
5868
5907
|
}
|
|
5869
|
-
case "voice":
|
|
5870
|
-
case "stt": {
|
|
5908
|
+
case "voice": {
|
|
5871
5909
|
if (!(await ensureDeps())) process.exit(1);
|
|
5872
5910
|
const forwardArgs = (args.includes("-h") || args.includes("--help")) ? ["--help"] : args;
|
|
5873
5911
|
process.exit(await run(nodeBin, [...nodeScriptArgs(sttEntry), ...forwardArgs], { cwd: repoRoot, env: process.env }));
|
|
@@ -5888,25 +5926,23 @@ Unlike 'bivy run', these commands operate on governed background Runs with check
|
|
|
5888
5926
|
await run(nodeBin, [...nodeScriptArgs(mcpServeEntry), ...args], { cwd: process.cwd(), env: process.env });
|
|
5889
5927
|
break;
|
|
5890
5928
|
case "help":
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5929
|
+
cmdHelp(args);
|
|
5930
|
+
break;
|
|
5931
|
+
case "context":
|
|
5932
|
+
await cmdContext(args);
|
|
5894
5933
|
break;
|
|
5895
5934
|
case "version":
|
|
5896
|
-
case "--version":
|
|
5897
|
-
case "-v":
|
|
5898
5935
|
console.log(readSelfVersion());
|
|
5899
5936
|
break;
|
|
5900
5937
|
default:
|
|
5901
|
-
|
|
5902
|
-
printHelp();
|
|
5903
|
-
process.exit(1);
|
|
5938
|
+
throw new Error(`bivy ${entry.name} is listed in bin/cli-commands.mjs but not dispatched.`);
|
|
5904
5939
|
}
|
|
5905
5940
|
}
|
|
5906
5941
|
|
|
5907
5942
|
main().catch((error) => {
|
|
5908
5943
|
// Show a clean message to users; the full stack is only useful with BIVY_DEBUG.
|
|
5909
|
-
|
|
5944
|
+
if (wantsJson(process.argv.slice(2))) process.stderr.write(`${JSON.stringify({ error: { code: "failed", message: error?.message || String(error) } })}\n`);
|
|
5945
|
+
else console.error(c.red(error?.message || String(error)));
|
|
5910
5946
|
if (process.env.BIVY_DEBUG && error?.stack) console.error(c.dim(error.stack));
|
|
5911
5947
|
process.exit(1);
|
|
5912
5948
|
});
|