botapp-cli 0.2.5 → 0.2.7

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/dist/bin/bot.js CHANGED
@@ -7,8 +7,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  });
8
8
 
9
9
  // src/index.ts
10
- import { Command as Command24 } from "commander";
11
- import pc25 from "picocolors";
10
+ import { Command as Command25 } from "commander";
11
+ import pc26 from "picocolors";
12
12
 
13
13
  // src/commands/server.ts
14
14
  import { Command } from "commander";
@@ -195,7 +195,8 @@ function writeYaml(profiles) {
195
195
  server: p.server,
196
196
  daemonId: p.daemonId,
197
197
  daemonName: p.daemonName,
198
- token: p.token
198
+ token: p.token,
199
+ ...p.userEmail ? { userEmail: p.userEmail } : {}
199
200
  };
200
201
  }
201
202
  writeFileSync2(DAEMON_FILE, stringify2({ profiles: map }), "utf-8");
@@ -235,7 +236,8 @@ function loadDaemonProfiles() {
235
236
  server: normalizeServer(value.server),
236
237
  daemonId: value.daemonId,
237
238
  daemonName: value.daemonName ?? value.daemonId,
238
- token: value.token
239
+ token: value.token,
240
+ userEmail: value.userEmail
239
241
  });
240
242
  }
241
243
  }
@@ -271,11 +273,21 @@ function saveDaemonProfile(input2) {
271
273
  server,
272
274
  daemonId: input2.daemonId,
273
275
  daemonName: input2.daemonName ?? input2.daemonId,
274
- token: input2.token
276
+ token: input2.token,
277
+ userEmail: input2.userEmail
275
278
  };
276
279
  writeYaml([...remaining, next]);
277
280
  return next;
278
281
  }
282
+ function removeDaemonProfile(serverOrAlias) {
283
+ const target = findDaemonProfile(serverOrAlias);
284
+ if (!target) return false;
285
+ const remaining = loadDaemonProfiles().filter(
286
+ (p) => p.alias !== target.alias
287
+ );
288
+ writeYaml(remaining);
289
+ return true;
290
+ }
279
291
 
280
292
  // src/commands/daemon-agent-config.ts
281
293
  import { spawnSync } from "child_process";
@@ -1073,6 +1085,28 @@ function pickProfilesToRun(alias, server) {
1073
1085
  }
1074
1086
  return loadDaemonProfiles();
1075
1087
  }
1088
+ daemonCommand.command("unpair").description("Remove a paired profile from ~/.botapp/daemon.yaml (does not touch the server)").argument("<aliasOrServer>", "Profile alias (e.g. local) or server URL").action((aliasOrServer) => {
1089
+ const removed = removeDaemonProfile(aliasOrServer);
1090
+ if (!removed) {
1091
+ console.error(pc3.red(`No paired profile matching "${aliasOrServer}".`));
1092
+ process.exitCode = 1;
1093
+ return;
1094
+ }
1095
+ console.log(pc3.green(`Removed daemon profile: ${aliasOrServer}`));
1096
+ });
1097
+ daemonCommand.command("list").alias("ls").description("List paired daemon profiles from ~/.botapp/daemon.yaml").action(() => {
1098
+ const profiles = loadDaemonProfiles();
1099
+ if (profiles.length === 0) {
1100
+ console.log(pc3.dim("No paired daemons. Run `bot pair` to add one."));
1101
+ return;
1102
+ }
1103
+ for (const p of profiles) {
1104
+ console.log(pc3.bold(p.alias ?? p.server));
1105
+ console.log(` Server: ${p.server}`);
1106
+ console.log(` Daemon: ${p.daemonName} ${pc3.dim(`(${p.daemonId})`)}`);
1107
+ console.log(` User: ${p.userEmail ?? pc3.dim("unknown \u2014 re-pair to capture")}`);
1108
+ }
1109
+ });
1076
1110
  daemonCommand.command("stop").description("Stop the background daemon started by `bot launch`").action(async () => {
1077
1111
  const pidFile = join5(homedir5(), ".botapp", "daemon.pid");
1078
1112
  if (!existsSync5(pidFile)) {
@@ -1101,16 +1135,17 @@ daemonCommand.command("agent").description("Manage agents hosted by this daemon"
1101
1135
  for (const agent of data.agents ?? []) {
1102
1136
  console.log(`${pc3.bold(agent.name)} ${pc3.dim(`(${agent.id})`)}`);
1103
1137
  console.log(` Kind: ${agent.kind}`);
1138
+ if (agent.model) console.log(` Model: ${agent.model}`);
1104
1139
  console.log(` Command: ${agent.command} ${(agent.args ?? []).join(" ")}`);
1105
1140
  if (agent.cwd) console.log(` CWD: ${agent.cwd}`);
1106
1141
  }
1107
1142
  })
1108
1143
  ).addCommand(createDaemonAgentConfigCommand()).addCommand(
1109
- new Command3("add").description("Register a local agent command").argument("<name>", "Name, e.g. codex, claude-code, openclaw, hermes, or hermes-agent").requiredOption("--command <command>", "Executable command").option(
1144
+ new Command3("add").description("Register a local agent command").argument("<name>", "Name, e.g. codex, claude-code, kimi, openclaw, hermes, or hermes-agent").requiredOption("--command <command>", "Executable command").option(
1110
1145
  "--kind <kind>",
1111
- "Agent adapter kind: acp, codex, claude-code, openclaw, hermes, hermes-agent, or shell",
1146
+ "Agent adapter kind: acp, codex, claude-code, kimi, openclaw, hermes, hermes-agent, or shell",
1112
1147
  "acp"
1113
- ).option("--arg <arg...>", "Argument passed to the executable").option("--cwd <cwd>", "Working directory for the agent process").option("--env <entry...>", "Environment entries in KEY=VALUE form").option("--profile <alias>", "Daemon profile alias to register against").option("--server <url>", "Daemon profile by server URL").action(async (name, opts) => {
1148
+ ).option("--arg <arg...>", "Argument passed to the executable").option("--cwd <cwd>", "Working directory for the agent process").option("--env <entry...>", "Environment entries in KEY=VALUE form").option("--model <model>", "Provider model to pass to the agent runtime").option("--profile <alias>", "Daemon profile alias to register against").option("--server <url>", "Daemon profile by server URL").action(async (name, opts) => {
1114
1149
  const profile = requireSelectedProfile(opts);
1115
1150
  if (!profile) return;
1116
1151
  const env = parseEnv(opts.env ?? []);
@@ -1126,12 +1161,14 @@ daemonCommand.command("agent").description("Manage agents hosted by this daemon"
1126
1161
  command: opts.command,
1127
1162
  args: opts.arg ?? [],
1128
1163
  cwd: opts.cwd,
1129
- env
1164
+ env,
1165
+ model: opts.model
1130
1166
  }
1131
1167
  }
1132
1168
  );
1133
1169
  console.log(pc3.green(`Registered daemon agent: ${pc3.bold(data.agent.name)}`));
1134
1170
  console.log(` ID: ${data.agent.id}`);
1171
+ if (data.agent.model) console.log(` Model: ${data.agent.model}`);
1135
1172
  console.log(` Command: ${data.agent.command} ${(data.agent.args ?? []).join(" ")}`);
1136
1173
  })
1137
1174
  ).addCommand(
@@ -1297,7 +1334,10 @@ async function runAgentJob(job, update) {
1297
1334
  if (job.agent.kind === "claude-code" || job.agent.kind === "claude_code") {
1298
1335
  return runClaudeCodeAgent(job, update);
1299
1336
  }
1300
- if (job.agent.kind === "openclaw" || job.agent.kind === "kimiclaw") {
1337
+ if (job.agent.kind === "kimi") {
1338
+ return runKimiCodeAgent(job, update);
1339
+ }
1340
+ if (job.agent.kind === "openclaw") {
1301
1341
  return runOpenClawAgent(job, update);
1302
1342
  }
1303
1343
  if (job.agent.kind === "hermes" || job.agent.kind === "hermes-agent") {
@@ -1338,6 +1378,9 @@ async function runCodexAgent(job, update) {
1338
1378
  if (!args.includes("--skip-git-repo-check")) {
1339
1379
  args.push("--skip-git-repo-check");
1340
1380
  }
1381
+ if (job.agent.model && !hasAnyFlag(args, ["--model", "-m"])) {
1382
+ args.push("--model", job.agent.model);
1383
+ }
1341
1384
  if (!hasAnyFlag(args, [
1342
1385
  "--sandbox",
1343
1386
  "-s",
@@ -1366,12 +1409,19 @@ async function runCodexAgent(job, update) {
1366
1409
  rawEvents: []
1367
1410
  };
1368
1411
  const toolCalls = /* @__PURE__ */ new Map();
1412
+ const errors = [];
1369
1413
  function processLine(line) {
1370
1414
  if (!line.trim()) return;
1371
1415
  try {
1372
1416
  const event = JSON.parse(line);
1373
1417
  result.rawEvents.push(event);
1374
1418
  update({ kind: "codex_event", event });
1419
+ if (event?.type === "error" && typeof event.message === "string") {
1420
+ errors.push(event.message);
1421
+ }
1422
+ if (event?.type === "turn.failed" && typeof event.error?.message === "string") {
1423
+ errors.push(event.error.message);
1424
+ }
1375
1425
  if (event?.type === "item.completed" && event.item?.type === "agent_message") {
1376
1426
  if (typeof event.item.text === "string") {
1377
1427
  result.messages.push(event.item.text);
@@ -1401,7 +1451,7 @@ async function runCodexAgent(job, update) {
1401
1451
  rl.on("line", processLine);
1402
1452
  const code = await new Promise((resolve11) => child.on("close", resolve11));
1403
1453
  if (code !== 0) {
1404
- throw new Error(stderr.trim() || `Codex exited with code ${code}`);
1454
+ throw new Error(errors.at(-1) ?? (stderr.trim() || `Codex exited with code ${code}`));
1405
1455
  }
1406
1456
  result.text = result.messages.at(-1)?.trim() || "";
1407
1457
  result.toolCalls = [...toolCalls.values()];
@@ -1418,6 +1468,9 @@ async function runClaudeCodeAgent(job, update) {
1418
1468
  if (!args.includes("--verbose")) {
1419
1469
  args.push("--verbose");
1420
1470
  }
1471
+ if (job.agent.model && !hasAnyFlag(args, ["--model"])) {
1472
+ args.push("--model", job.agent.model);
1473
+ }
1421
1474
  if (!hasAnyFlag(args, [
1422
1475
  "--permission-mode",
1423
1476
  "--dangerously-skip-permissions",
@@ -1440,6 +1493,9 @@ async function runClaudeCodeAgent(job, update) {
1440
1493
  child.stderr.on("data", (chunk) => {
1441
1494
  stderr += chunk.toString();
1442
1495
  });
1496
+ const noiseLines = [];
1497
+ const errorEvents = [];
1498
+ const NOISE_TAIL = 6;
1443
1499
  const result = {
1444
1500
  kind: "claude-code",
1445
1501
  text: "",
@@ -1497,11 +1553,19 @@ async function runClaudeCodeAgent(job, update) {
1497
1553
  try {
1498
1554
  event = JSON.parse(line);
1499
1555
  } catch {
1556
+ noiseLines.push(line);
1557
+ if (noiseLines.length > NOISE_TAIL) noiseLines.shift();
1500
1558
  return;
1501
1559
  }
1502
1560
  result.rawEvents.push(event);
1503
1561
  update({ kind: "claude_code_event", event });
1504
1562
  captureSessionId(event);
1563
+ if (event && (event.is_error === true || event.subtype === "error" || event.type === "result" && event.is_error)) {
1564
+ try {
1565
+ errorEvents.push(JSON.stringify(event).slice(0, 1e3));
1566
+ } catch {
1567
+ }
1568
+ }
1505
1569
  if (event.type === "assistant" && event.message?.content) {
1506
1570
  ingestAssistantContent(
1507
1571
  Array.isArray(event.message.content) ? event.message.content : []
@@ -1522,7 +1586,146 @@ async function runClaudeCodeAgent(job, update) {
1522
1586
  rl.on("line", processLine);
1523
1587
  const code = await new Promise((resolve11) => child.on("close", resolve11));
1524
1588
  if (code !== 0) {
1525
- throw new Error(stderr.trim() || `Claude Code exited with code ${code}`);
1589
+ const parts = [];
1590
+ if (stderr.trim()) parts.push(stderr.trim());
1591
+ if (errorEvents.length > 0) {
1592
+ parts.push(`Error events: ${errorEvents.join(" | ")}`);
1593
+ }
1594
+ if (noiseLines.length > 0 && parts.length === 0) {
1595
+ parts.push(`Last stdout lines: ${noiseLines.join(" | ").slice(-1500)}`);
1596
+ }
1597
+ if (parts.length === 0) {
1598
+ parts.push(`Claude Code exited with code ${code} (no stderr or stdout output)`);
1599
+ }
1600
+ throw new Error(parts.join(" \u2014 "));
1601
+ }
1602
+ if (!result.text) {
1603
+ result.text = result.messages.at(-1)?.trim() ?? "";
1604
+ }
1605
+ result.toolCalls = [...toolCalls.values()];
1606
+ return JSON.stringify(result);
1607
+ }
1608
+ async function runKimiCodeAgent(job, update) {
1609
+ const args = [...job.agent.args];
1610
+ if (!args.includes("--print") && !args.includes("-p")) {
1611
+ args.push("--print");
1612
+ }
1613
+ if (!args.includes("--output-format")) {
1614
+ args.push("--output-format", "stream-json");
1615
+ }
1616
+ if (!hasAnyFlag(args, ["--yolo", "--yes", "-y"])) {
1617
+ args.push("--yolo");
1618
+ }
1619
+ if (job.agent.model && !hasAnyFlag(args, ["--model", "-m"])) {
1620
+ args.push("--model", job.agent.model);
1621
+ }
1622
+ const resume = job.resumeSessionId ?? job.agent.env?.KIMI_SESSION_ID ?? null;
1623
+ if (resume && !hasAnyFlag(args, ["--session", "-S", "-r", "--resume", "--continue", "-C"])) {
1624
+ args.push("-r", resume);
1625
+ }
1626
+ const cwd = job.agent.cwd ?? process.cwd();
1627
+ let prompt = job.query;
1628
+ if (!resume) {
1629
+ const agentsMdPath = join5(cwd, "AGENTS.md");
1630
+ if (existsSync5(agentsMdPath)) {
1631
+ try {
1632
+ const primer = readFileSync3(agentsMdPath, "utf-8").trim();
1633
+ if (primer) {
1634
+ prompt = `# System instructions (read carefully before responding)
1635
+
1636
+ ${primer}
1637
+
1638
+ ---
1639
+
1640
+ # Your task
1641
+
1642
+ ${job.query}`;
1643
+ }
1644
+ } catch {
1645
+ }
1646
+ }
1647
+ }
1648
+ const child = spawn3(job.agent.command, args, {
1649
+ cwd,
1650
+ env: { ...process.env, ...job.agent.env ?? {} },
1651
+ stdio: ["pipe", "pipe", "pipe"]
1652
+ });
1653
+ child.stdin.write(prompt);
1654
+ child.stdin.end();
1655
+ let stderr = "";
1656
+ child.stderr.on("data", (chunk) => {
1657
+ stderr += chunk.toString();
1658
+ });
1659
+ const result = {
1660
+ kind: "kimi",
1661
+ text: "",
1662
+ messages: [],
1663
+ toolCalls: [],
1664
+ sessionId: resume,
1665
+ rawEvents: []
1666
+ };
1667
+ const toolCalls = /* @__PURE__ */ new Map();
1668
+ function ingestAssistantContent(blocks) {
1669
+ for (const block of blocks) {
1670
+ if (!block || typeof block !== "object") continue;
1671
+ if (block.type === "text" && typeof block.text === "string") {
1672
+ result.messages.push(block.text);
1673
+ update({ kind: "message", text: block.text });
1674
+ }
1675
+ }
1676
+ }
1677
+ function ingestToolCalls(calls) {
1678
+ for (const call of calls) {
1679
+ if (!call || typeof call !== "object") continue;
1680
+ const id = typeof call.id === "string" ? call.id : null;
1681
+ if (!id) continue;
1682
+ const name = String(call.function?.name ?? call.name ?? "tool");
1683
+ let input2 = call.function?.arguments ?? call.arguments;
1684
+ if (typeof input2 === "string") {
1685
+ try {
1686
+ input2 = JSON.parse(input2);
1687
+ } catch {
1688
+ }
1689
+ }
1690
+ const next = { ...toolCalls.get(id) ?? {}, id, name, input: input2 };
1691
+ toolCalls.set(id, next);
1692
+ update({ kind: "tool_call", toolCall: next });
1693
+ }
1694
+ }
1695
+ function ingestToolResult(event) {
1696
+ const id = event?.tool_call_id;
1697
+ if (typeof id !== "string") return;
1698
+ const existing = toolCalls.get(id) ?? { id, name: "tool" };
1699
+ const output2 = typeof event.content === "string" ? event.content : JSON.stringify(event.content);
1700
+ const next = { ...existing, output: output2 };
1701
+ toolCalls.set(id, next);
1702
+ update({ kind: "tool_call", toolCall: next });
1703
+ }
1704
+ function processLine(line) {
1705
+ const trimmed = line.trim();
1706
+ if (!trimmed || trimmed[0] !== "{") return;
1707
+ let event;
1708
+ try {
1709
+ event = JSON.parse(trimmed);
1710
+ } catch {
1711
+ return;
1712
+ }
1713
+ result.rawEvents.push(event);
1714
+ update({ kind: "kimi_event", event });
1715
+ if (event.role === "assistant") {
1716
+ if (Array.isArray(event.content)) ingestAssistantContent(event.content);
1717
+ if (Array.isArray(event.tool_calls)) ingestToolCalls(event.tool_calls);
1718
+ } else if (event.role === "tool") {
1719
+ ingestToolResult(event);
1720
+ }
1721
+ }
1722
+ const rl = createInterface2({ input: child.stdout });
1723
+ rl.on("line", processLine);
1724
+ const code = await new Promise((resolve11) => child.on("close", resolve11));
1725
+ const resumeMatch = stderr.match(/To resume this session:\s*kimi\s+-r\s+([A-Za-z0-9_-]+)/);
1726
+ if (resumeMatch) result.sessionId = resumeMatch[1];
1727
+ if (code !== 0) {
1728
+ throw new Error(stderr.trim() || `Kimi Code exited with code ${code}`);
1526
1729
  }
1527
1730
  if (!result.text) {
1528
1731
  result.text = result.messages.at(-1)?.trim() ?? "";
@@ -1586,6 +1789,7 @@ async function runOpenClawAgent(job, update) {
1586
1789
  };
1587
1790
  const toolCalls = /* @__PURE__ */ new Map();
1588
1791
  let stderr = "";
1792
+ const stdoutLines = [];
1589
1793
  let stopPolling = false;
1590
1794
  const child = spawn3(job.agent.command, args, {
1591
1795
  cwd: job.agent.cwd ?? process.cwd(),
@@ -1593,7 +1797,15 @@ async function runOpenClawAgent(job, update) {
1593
1797
  stdio: ["ignore", "pipe", "pipe"]
1594
1798
  });
1595
1799
  const stdout = createInterface2({ input: child.stdout });
1596
- stdout.on("line", () => {
1800
+ stdout.on("line", (line) => {
1801
+ const text = line.trim();
1802
+ if (!text) return;
1803
+ stdoutLines.push(text);
1804
+ const stdoutText = extractOpenClawStdoutText(text);
1805
+ if (!stdoutText) return;
1806
+ result.text = stdoutText;
1807
+ result.messages.push(stdoutText);
1808
+ update({ kind: "message", text: stdoutText });
1597
1809
  });
1598
1810
  const stderrReader = createInterface2({ input: child.stderr });
1599
1811
  stderrReader.on("line", (line) => {
@@ -1625,11 +1837,11 @@ async function runOpenClawAgent(job, update) {
1625
1837
  }
1626
1838
  result.sessionFile = state.selectedFile;
1627
1839
  result.toolCalls = [...toolCalls.values()];
1628
- result.text = result.text || result.messages.at(-1)?.trim() || "";
1840
+ result.text = result.text || result.messages.at(-1)?.trim() || extractOpenClawStdoutText(stdoutLines.at(-1) ?? "") || "";
1629
1841
  if (code !== 0) {
1630
1842
  throw new Error(stderr.trim() || `OpenClaw exited with code ${code}`);
1631
1843
  }
1632
- if (!result.sessionId) {
1844
+ if (!result.sessionId && !result.text && result.toolCalls.length === 0) {
1633
1845
  throw new Error(
1634
1846
  [
1635
1847
  "OpenClaw completed, but the session key was not resolved from sessions.json.",
@@ -1650,6 +1862,17 @@ async function runOpenClawAgent(job, update) {
1650
1862
  }
1651
1863
  return JSON.stringify(result);
1652
1864
  }
1865
+ function extractOpenClawStdoutText(line) {
1866
+ const text = line.trim();
1867
+ if (!text) return "";
1868
+ try {
1869
+ const parsed = JSON.parse(text);
1870
+ if (parsed && typeof parsed === "object") return JSON.stringify(parsed);
1871
+ if (typeof parsed === "string") return parsed;
1872
+ } catch {
1873
+ }
1874
+ return text;
1875
+ }
1653
1876
  function resolveOpenClawSessionDir(args, env) {
1654
1877
  const configured = env.BOTAPP_OPENCLAW_SESSION_DIR ?? env.OPENCLAW_SESSION_DIR;
1655
1878
  if (configured) return expandPath(configured);
@@ -2162,7 +2385,7 @@ Invalid ACP stdout: ${line}`;
2162
2385
  clientInfo: {
2163
2386
  name: "botapp-daemon",
2164
2387
  title: "botapp daemon",
2165
- version: "0.2.5"
2388
+ version: "0.2.7"
2166
2389
  }
2167
2390
  });
2168
2391
  const session = await request2("session/new", {
@@ -2737,7 +2960,8 @@ Configured for ${serverUrl}`));
2737
2960
  server: result.serverUrl ?? serverUrl,
2738
2961
  daemonId: data.daemon.id,
2739
2962
  daemonName: data.daemon.name,
2740
- token: data.token
2963
+ token: data.token,
2964
+ userEmail: result.userEmail
2741
2965
  });
2742
2966
  console.log(
2743
2967
  ` Daemon: ${pc5.bold(data.daemon.name)} ${pc5.dim(`(${data.daemon.id})`)}` + pc5.dim(` profile=${savedProfile.alias}`)
@@ -3796,7 +4020,8 @@ var pairingCommand = new Command18("pairing").alias("pair").description("Pair th
3796
4020
  server: serverUrl,
3797
4021
  daemonId: data.daemon.id,
3798
4022
  daemonName: data.daemon.name,
3799
- token: data.token
4023
+ token: data.token,
4024
+ userEmail: grant.userEmail
3800
4025
  });
3801
4026
  console.log(pc19.green(`Paired daemon: ${pc19.bold(data.daemon.name)}`));
3802
4027
  console.log(` ID: ${data.daemon.id}`);
@@ -3832,13 +4057,130 @@ async function obtainPairingToken(opts) {
3832
4057
  return { pairingToken: result.pairingToken, userEmail: result.userEmail };
3833
4058
  }
3834
4059
 
4060
+ // src/commands/doctor.ts
4061
+ import { Command as Command19 } from "commander";
4062
+ import pc20 from "picocolors";
4063
+ var doctorCommand = new Command19("doctor").description("Diagnose paired daemon profiles (server reachability, token validity, account ownership)").action(async () => {
4064
+ const profiles = loadDaemonProfiles();
4065
+ if (profiles.length === 0) {
4066
+ console.log(pc20.dim("No paired daemons in ~/.botapp/daemon.yaml."));
4067
+ console.log(pc20.dim("Run `bot pair` to add one."));
4068
+ return;
4069
+ }
4070
+ let warns = 0;
4071
+ let fails = 0;
4072
+ for (const profile of profiles) {
4073
+ console.log(pc20.bold(`
4074
+ [${profile.alias ?? profile.server}]`));
4075
+ console.log(` Server: ${profile.server}`);
4076
+ console.log(` Daemon: ${profile.daemonName} ${pc20.dim(`(${profile.daemonId})`)}`);
4077
+ const findings = await checkProfile(profile);
4078
+ for (const f of findings) {
4079
+ const tag = f.severity === "ok" ? pc20.green(" \u2713 ") : f.severity === "warn" ? pc20.yellow(" \u26A0 ") : pc20.red(" \u2717 ");
4080
+ console.log(`${tag}${f.message}`);
4081
+ if (f.fix) console.log(pc20.dim(` \u2192 ${f.fix}`));
4082
+ if (f.severity === "warn") warns += 1;
4083
+ if (f.severity === "fail") fails += 1;
4084
+ }
4085
+ }
4086
+ console.log("");
4087
+ if (fails === 0 && warns === 0) {
4088
+ console.log(pc20.green("All checks passed."));
4089
+ } else {
4090
+ const parts = [];
4091
+ if (fails > 0) parts.push(pc20.red(`${fails} failing`));
4092
+ if (warns > 0) parts.push(pc20.yellow(`${warns} warning${warns === 1 ? "" : "s"}`));
4093
+ console.log(parts.join(", ") + ".");
4094
+ if (fails > 0) process.exitCode = 1;
4095
+ }
4096
+ });
4097
+ async function checkProfile(profile) {
4098
+ const findings = [];
4099
+ let serverOk = false;
4100
+ try {
4101
+ const res = await fetch(`${profile.server}/health`, {
4102
+ signal: AbortSignal.timeout(5e3)
4103
+ });
4104
+ if (res.ok) {
4105
+ findings.push({ severity: "ok", message: "Server reachable" });
4106
+ serverOk = true;
4107
+ } else {
4108
+ findings.push({
4109
+ severity: "fail",
4110
+ message: `Server returned ${res.status} ${res.statusText}`,
4111
+ fix: `Verify the server at ${profile.server} is running.`
4112
+ });
4113
+ }
4114
+ } catch (e) {
4115
+ findings.push({
4116
+ severity: "fail",
4117
+ message: `Server unreachable: ${e.message ?? e}`,
4118
+ fix: `Start the server, or remove this profile with \`bot daemon unpair ${profile.alias ?? profile.server}\`.`
4119
+ });
4120
+ }
4121
+ if (!serverOk) return findings;
4122
+ let serverEmail = null;
4123
+ try {
4124
+ const data = await daemonRequest(profile.server, profile.token, "/api/daemon/self");
4125
+ findings.push({ severity: "ok", message: "Token valid" });
4126
+ serverEmail = data.user?.email ?? null;
4127
+ if (serverEmail) {
4128
+ findings.push({
4129
+ severity: "ok",
4130
+ message: `Token paired under ${pc20.bold(serverEmail)}`
4131
+ });
4132
+ } else {
4133
+ findings.push({
4134
+ severity: "warn",
4135
+ message: "Server returned no user record for this daemon",
4136
+ fix: "The owning user may have been deleted. Re-pair with `bot pair`."
4137
+ });
4138
+ }
4139
+ if (data.daemon?.status === "online") {
4140
+ findings.push({ severity: "ok", message: "Daemon currently online" });
4141
+ } else {
4142
+ findings.push({
4143
+ severity: "warn",
4144
+ message: `Daemon status: ${data.daemon?.status ?? "unknown"}`,
4145
+ fix: `Run \`bot daemon run --server ${profile.server}\` to bring it online.`
4146
+ });
4147
+ }
4148
+ } catch (e) {
4149
+ const msg = e?.message ?? String(e);
4150
+ if (/unauthor/i.test(msg) || /token/i.test(msg)) {
4151
+ findings.push({
4152
+ severity: "fail",
4153
+ message: `Token rejected: ${msg}`,
4154
+ fix: "The token is stale (likely the server DB was reset). Re-pair with `bot pair`."
4155
+ });
4156
+ return findings;
4157
+ }
4158
+ findings.push({ severity: "fail", message: `Identity probe failed: ${msg}` });
4159
+ return findings;
4160
+ }
4161
+ if (profile.userEmail && serverEmail && profile.userEmail !== serverEmail) {
4162
+ findings.push({
4163
+ severity: "warn",
4164
+ message: `daemon.yaml says \`userEmail: ${profile.userEmail}\` but server says \`${serverEmail}\``,
4165
+ fix: "Re-pair to refresh the recorded email."
4166
+ });
4167
+ } else if (!profile.userEmail && serverEmail) {
4168
+ findings.push({
4169
+ severity: "warn",
4170
+ message: "daemon.yaml has no userEmail recorded",
4171
+ fix: "Re-pair to capture it (cosmetic \u2014 auth still works)."
4172
+ });
4173
+ }
4174
+ return findings;
4175
+ }
4176
+
3835
4177
  // src/commands/update.ts
3836
4178
  import { spawn as spawn7 } from "child_process";
3837
4179
  import { realpathSync as realpathSync2 } from "fs";
3838
- import { Command as Command19 } from "commander";
3839
- import pc20 from "picocolors";
4180
+ import { Command as Command20 } from "commander";
4181
+ import pc21 from "picocolors";
3840
4182
  var PACKAGE_NAME = "botapp-cli";
3841
- var updateCommand = new Command19("update").description("Update the `bot` CLI itself to the latest published version").option(
4183
+ var updateCommand = new Command20("update").description("Update the `bot` CLI itself to the latest published version").option(
3842
4184
  "--manager <pm>",
3843
4185
  "Force a package manager: npm | pnpm | yarn | brew (default: auto-detect)"
3844
4186
  ).option("--dry-run", "Print the command that would run, but do not execute it").action(async (opts) => {
@@ -3846,12 +4188,12 @@ var updateCommand = new Command19("update").description("Update the `bot` CLI it
3846
4188
  const manager = forced ?? detectPackageManager();
3847
4189
  if (manager === "npx") {
3848
4190
  console.log(
3849
- pc20.green(
4191
+ pc21.green(
3850
4192
  "You are running `bot` via `npx -y botapp-cli@latest` \u2014 every invocation already fetches the latest version."
3851
4193
  )
3852
4194
  );
3853
4195
  console.log(
3854
- pc20.dim(
4196
+ pc21.dim(
3855
4197
  "For a faster startup, install it globally instead:\n npm i -g botapp-cli@latest\n pnpm add -g botapp-cli@latest"
3856
4198
  )
3857
4199
  );
@@ -3859,34 +4201,34 @@ var updateCommand = new Command19("update").description("Update the `bot` CLI it
3859
4201
  }
3860
4202
  if (!manager) {
3861
4203
  console.error(
3862
- pc20.red(
4204
+ pc21.red(
3863
4205
  "Couldn't detect how `bot` was installed. Pick one of these manually:"
3864
4206
  )
3865
4207
  );
3866
- console.log(pc20.cyan(" npm i -g botapp-cli@latest"));
3867
- console.log(pc20.cyan(" pnpm add -g botapp-cli@latest"));
3868
- console.log(pc20.cyan(" yarn global add botapp-cli@latest"));
3869
- console.log(pc20.cyan(" brew upgrade botapp-cli"));
4208
+ console.log(pc21.cyan(" npm i -g botapp-cli@latest"));
4209
+ console.log(pc21.cyan(" pnpm add -g botapp-cli@latest"));
4210
+ console.log(pc21.cyan(" yarn global add botapp-cli@latest"));
4211
+ console.log(pc21.cyan(" brew upgrade botapp-cli"));
3870
4212
  process.exitCode = 1;
3871
4213
  return;
3872
4214
  }
3873
4215
  const { command, args } = updateCommandFor(manager);
3874
- console.log(pc20.dim(`$ ${command} ${args.join(" ")}`));
4216
+ console.log(pc21.dim(`$ ${command} ${args.join(" ")}`));
3875
4217
  if (opts.dryRun) return;
3876
4218
  const child = spawn7(command, args, { stdio: "inherit" });
3877
4219
  const code = await new Promise((resolve11) => {
3878
4220
  child.once("error", (err) => {
3879
- console.error(pc20.red(`Failed to spawn ${command}: ${err.message}`));
4221
+ console.error(pc21.red(`Failed to spawn ${command}: ${err.message}`));
3880
4222
  resolve11(127);
3881
4223
  });
3882
4224
  child.once("close", resolve11);
3883
4225
  });
3884
4226
  if (code !== 0) {
3885
- console.error(pc20.red(`Update failed (exit ${code}).`));
4227
+ console.error(pc21.red(`Update failed (exit ${code}).`));
3886
4228
  process.exitCode = code ?? 1;
3887
4229
  return;
3888
4230
  }
3889
- console.log(pc20.green("botapp-cli updated. Run `bot --version` to confirm."));
4231
+ console.log(pc21.green("botapp-cli updated. Run `bot --version` to confirm."));
3890
4232
  });
3891
4233
  function detectPackageManager() {
3892
4234
  const argv = process.argv[1] ?? "";
@@ -3921,27 +4263,27 @@ function updateCommandFor(manager) {
3921
4263
  }
3922
4264
 
3923
4265
  // src/commands/simulate.ts
3924
- import { Command as Command20 } from "commander";
4266
+ import { Command as Command21 } from "commander";
3925
4267
  import { resolve as resolve8, join as join11 } from "path";
3926
4268
  import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
3927
4269
  import { spawn as spawn8 } from "child_process";
3928
- import pc21 from "picocolors";
3929
- var simulateCommand = new Command20("simulate").description("Dev-tunnel a local app to a botapp server (per-user shadow)").argument("[path]", "Path to the app directory", ".").option("-s, --server <url>", "botapp server URL (default: active profile / $BOTAPP_SERVER)").option("-t, --token <token>", "auth token (default: active profile / $BOTAPP_TOKEN)").option("--entry <file>", "override the manifest entry path").option("--lifetime <minutes>", "dev-session token lifetime in minutes", "480").action(async (appPath, opts) => {
4270
+ import pc22 from "picocolors";
4271
+ var simulateCommand = new Command21("simulate").description("Dev-tunnel a local app to a botapp server (per-user shadow)").argument("[path]", "Path to the app directory", ".").option("-s, --server <url>", "botapp server URL (default: active profile / $BOTAPP_SERVER)").option("-t, --token <token>", "auth token (default: active profile / $BOTAPP_TOKEN)").option("--entry <file>", "override the manifest entry path").option("--lifetime <minutes>", "dev-session token lifetime in minutes", "480").action(async (appPath, opts) => {
3930
4272
  const absPath = resolve8(appPath);
3931
4273
  if (!existsSync13(absPath)) {
3932
- console.error(pc21.red(`Path not found: ${absPath}`));
4274
+ console.error(pc22.red(`Path not found: ${absPath}`));
3933
4275
  process.exitCode = 1;
3934
4276
  return;
3935
4277
  }
3936
4278
  const manifestPath = join11(absPath, "botapp.app.json");
3937
4279
  if (!existsSync13(manifestPath)) {
3938
- console.error(pc21.red(`No botapp.app.json found in ${absPath}`));
4280
+ console.error(pc22.red(`No botapp.app.json found in ${absPath}`));
3939
4281
  process.exitCode = 1;
3940
4282
  return;
3941
4283
  }
3942
4284
  const manifest = JSON.parse(readFileSync9(manifestPath, "utf8"));
3943
4285
  if (!manifest.name) {
3944
- console.error(pc21.red('manifest missing "name"'));
4286
+ console.error(pc22.red('manifest missing "name"'));
3945
4287
  process.exitCode = 1;
3946
4288
  return;
3947
4289
  }
@@ -3949,31 +4291,31 @@ var simulateCommand = new Command20("simulate").description("Dev-tunnel a local
3949
4291
  const wsServer = httpServer.replace(/^http:/, "ws:").replace(/^https:/, "wss:") + "/ws/host";
3950
4292
  const token = resolveToken(opts.token);
3951
4293
  if (!token) {
3952
- console.error(pc21.red("not logged in: run `bot login` or pass --token"));
4294
+ console.error(pc22.red("not logged in: run `bot login` or pass --token"));
3953
4295
  process.exitCode = 1;
3954
4296
  return;
3955
4297
  }
3956
4298
  const lifetimeMs = Math.max(6e4, Number(opts.lifetime) * 6e4);
3957
- console.log(pc21.dim(`requesting dev token for "${manifest.name}" from ${httpServer}...`));
4299
+ console.log(pc22.dim(`requesting dev token for "${manifest.name}" from ${httpServer}...`));
3958
4300
  const devToken = await issueDevToken({
3959
4301
  serverUrl: httpServer,
3960
4302
  token,
3961
4303
  appName: manifest.name,
3962
4304
  lifetimeMs
3963
4305
  });
3964
- console.log(pc21.green("\u2713"), `dev token issued (lifetime ${opts.lifetime} min)`);
4306
+ console.log(pc22.green("\u2713"), `dev token issued (lifetime ${opts.lifetime} min)`);
3965
4307
  const entryPath = resolveEntry(absPath, opts.entry ?? manifest.entry);
3966
4308
  if (!existsSync13(entryPath)) {
3967
- console.error(pc21.red(`entry not found: ${entryPath}`));
3968
- console.error(pc21.dim(" run `pnpm build` (or your build script) first."));
4309
+ console.error(pc22.red(`entry not found: ${entryPath}`));
4310
+ console.error(pc22.dim(" run `pnpm build` (or your build script) first."));
3969
4311
  process.exitCode = 1;
3970
4312
  return;
3971
4313
  }
3972
- console.log(pc21.dim(`spawning ${entryPath} ...`));
3973
- console.log(pc21.dim(` BOTAPP_SERVER=${wsServer}`));
3974
- console.log(pc21.dim(` BOTAPP_APP_NAME=${manifest.name}`));
4314
+ console.log(pc22.dim(`spawning ${entryPath} ...`));
4315
+ console.log(pc22.dim(` BOTAPP_SERVER=${wsServer}`));
4316
+ console.log(pc22.dim(` BOTAPP_APP_NAME=${manifest.name}`));
3975
4317
  console.log(
3976
- pc21.cyan(
4318
+ pc22.cyan(
3977
4319
  `
3978
4320
  When ready, your dashboard at ${httpServer} will route "${manifest.name}" to this process for your account only.
3979
4321
  `
@@ -3991,7 +4333,7 @@ When ready, your dashboard at ${httpServer} will route "${manifest.name}" to thi
3991
4333
  stdio: "inherit"
3992
4334
  });
3993
4335
  const stop = (signal) => {
3994
- console.log(pc21.dim(`
4336
+ console.log(pc22.dim(`
3995
4337
  stopping (${signal})...`));
3996
4338
  if (!child.killed) child.kill(signal);
3997
4339
  };
@@ -3999,7 +4341,7 @@ stopping (${signal})...`));
3999
4341
  process.on("SIGTERM", () => stop("SIGTERM"));
4000
4342
  child.on("exit", (code, sig) => {
4001
4343
  const reason = sig ? `signal ${sig}` : `exit ${code}`;
4002
- console.log(pc21.dim(`child exited (${reason})`));
4344
+ console.log(pc22.dim(`child exited (${reason})`));
4003
4345
  process.exit(typeof code === "number" ? code : 0);
4004
4346
  });
4005
4347
  });
@@ -4036,14 +4378,14 @@ function resolveEntry(appDir, entry) {
4036
4378
  }
4037
4379
 
4038
4380
  // src/commands/init.ts
4039
- import { Command as Command21 } from "commander";
4381
+ import { Command as Command22 } from "commander";
4040
4382
  import { existsSync as existsSync14, mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "fs";
4041
4383
  import { resolve as resolve9, join as join12 } from "path";
4042
- import pc22 from "picocolors";
4043
- var initCommand = new Command21("init").description("Scaffold a new hosted-tier botapp app project").argument("<name>", "App name (kebab-case; used in URL paths and manifest)").option("-d, --dir <path>", "Output directory (default: ./<name>)").option("--headless", "No frontend \u2014 backend-only app (skip src/, tailwind, etc.)").option("--description <text>", "Short description for AI agents to read").option("-f, --force", "Overwrite existing directory contents").action(async (name, opts) => {
4384
+ import pc23 from "picocolors";
4385
+ var initCommand = new Command22("init").description("Scaffold a new hosted-tier botapp app project").argument("<name>", "App name (kebab-case; used in URL paths and manifest)").option("-d, --dir <path>", "Output directory (default: ./<name>)").option("--headless", "No frontend \u2014 backend-only app (skip src/, tailwind, etc.)").option("--description <text>", "Short description for AI agents to read").option("-f, --force", "Overwrite existing directory contents").action(async (name, opts) => {
4044
4386
  if (!/^[a-z][a-z0-9-]*$/.test(name)) {
4045
- console.error(pc22.red("Invalid name. Use lowercase letters, digits, dashes; must start with a letter."));
4046
- console.error(pc22.dim(" e.g. my-app, todo-tracker, gomoku-2"));
4387
+ console.error(pc23.red("Invalid name. Use lowercase letters, digits, dashes; must start with a letter."));
4388
+ console.error(pc23.dim(" e.g. my-app, todo-tracker, gomoku-2"));
4047
4389
  process.exitCode = 1;
4048
4390
  return;
4049
4391
  }
@@ -4057,8 +4399,8 @@ var initCommand = new Command21("init").description("Scaffold a new hosted-tier
4057
4399
  }
4058
4400
  })();
4059
4401
  if (Array.isArray(stat) && stat.length > 0) {
4060
- console.error(pc22.red(`Target directory exists and is not empty: ${targetDir}`));
4061
- console.error(pc22.dim(" pass --force to overwrite, or pick a different --dir"));
4402
+ console.error(pc23.red(`Target directory exists and is not empty: ${targetDir}`));
4403
+ console.error(pc23.dim(" pass --force to overwrite, or pick a different --dir"));
4062
4404
  process.exitCode = 1;
4063
4405
  return;
4064
4406
  }
@@ -4075,18 +4417,18 @@ var initCommand = new Command21("init").description("Scaffold a new hosted-tier
4075
4417
  mkdirSync6(dirname2(full), { recursive: true });
4076
4418
  writeFileSync4(full, content);
4077
4419
  }
4078
- console.log(pc22.green("\u2713"), `Scaffolded ${ctx.headless ? "headless " : ""}app at`, pc22.cyan(targetDir));
4420
+ console.log(pc23.green("\u2713"), `Scaffolded ${ctx.headless ? "headless " : ""}app at`, pc23.cyan(targetDir));
4079
4421
  console.log();
4080
4422
  console.log("Next steps:");
4081
- console.log(pc22.dim(` cd ${targetDir.replace(process.cwd() + "/", "")}`));
4082
- console.log(pc22.dim(" pnpm install # or npm install"));
4083
- if (!ctx.headless) console.log(pc22.dim(" pnpm build # builds api/ + frontend (dist/)"));
4084
- else console.log(pc22.dim(" pnpm build # builds api/ to dist/api.js"));
4085
- console.log(pc22.dim(` bot login --server <your-botapp-server>`));
4086
- console.log(pc22.dim(` bot simulate # dev-tunnel into the server, hot-reload as you build`));
4423
+ console.log(pc23.dim(` cd ${targetDir.replace(process.cwd() + "/", "")}`));
4424
+ console.log(pc23.dim(" pnpm install # or npm install"));
4425
+ if (!ctx.headless) console.log(pc23.dim(" pnpm build # builds api/ + frontend (dist/)"));
4426
+ else console.log(pc23.dim(" pnpm build # builds api/ to dist/api.js"));
4427
+ console.log(pc23.dim(` bot login --server <your-botapp-server>`));
4428
+ console.log(pc23.dim(` bot simulate # dev-tunnel into the server, hot-reload as you build`));
4087
4429
  console.log();
4088
4430
  console.log(
4089
- pc22.dim("Once it works, `bot publish` ships it to the server (per-user install by default).")
4431
+ pc23.dim("Once it works, `bot publish` ships it to the server (per-user install by default).")
4090
4432
  );
4091
4433
  });
4092
4434
  function fullFiles(ctx) {
@@ -4466,38 +4808,38 @@ function dirname2(p) {
4466
4808
  }
4467
4809
 
4468
4810
  // src/commands/publish.ts
4469
- import { Command as Command22 } from "commander";
4811
+ import { Command as Command23 } from "commander";
4470
4812
  import { resolve as resolve10, join as join13, relative as relative2 } from "path";
4471
4813
  import { existsSync as existsSync15, readFileSync as readFileSync10, statSync as statSync4, readdirSync as readdirSync2 } from "fs";
4472
4814
  import { createGzip } from "zlib";
4473
4815
  import { spawn as spawn9 } from "child_process";
4474
- import pc23 from "picocolors";
4475
- var publishCommand = new Command22("publish").description("Build, pack, and upload an app to a botapp server").argument("[path]", "App directory", ".").option("-s, --server <url>", "Server URL (default: active profile / $BOTAPP_SERVER)").option("-t, --token <token>", "Auth token (default: active profile / $BOTAPP_TOKEN)").option("--public", "Request public visibility (queues admin review). Default: private.").option("--no-build", "Skip running `pnpm build` / `npm run build`").option("--bundle-dir <dir>", "Directory to bundle into tar.gz (default: dist/public if exists, else dist)").action(async (appPath, opts) => {
4816
+ import pc24 from "picocolors";
4817
+ var publishCommand = new Command23("publish").description("Build, pack, and upload an app to a botapp server").argument("[path]", "App directory", ".").option("-s, --server <url>", "Server URL (default: active profile / $BOTAPP_SERVER)").option("-t, --token <token>", "Auth token (default: active profile / $BOTAPP_TOKEN)").option("--public", "Request public visibility (queues admin review). Default: private.").option("--no-build", "Skip running `pnpm build` / `npm run build`").option("--bundle-dir <dir>", "Directory to bundle into tar.gz (default: dist/public if exists, else dist)").action(async (appPath, opts) => {
4476
4818
  const absPath = resolve10(appPath);
4477
4819
  const manifestPath = join13(absPath, "botapp.app.json");
4478
4820
  if (!existsSync15(manifestPath)) {
4479
- console.error(pc23.red(`No botapp.app.json found in ${absPath}`));
4821
+ console.error(pc24.red(`No botapp.app.json found in ${absPath}`));
4480
4822
  process.exitCode = 1;
4481
4823
  return;
4482
4824
  }
4483
4825
  const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
4484
4826
  if (!manifest.name) {
4485
- console.error(pc23.red('manifest missing "name"'));
4827
+ console.error(pc24.red('manifest missing "name"'));
4486
4828
  process.exitCode = 1;
4487
4829
  return;
4488
4830
  }
4489
4831
  const server = resolveServerUrl(opts.server);
4490
4832
  const token = resolveToken(opts.token);
4491
4833
  if (!token) {
4492
- console.error(pc23.red("not logged in: run `bot login` or pass --token"));
4834
+ console.error(pc24.red("not logged in: run `bot login` or pass --token"));
4493
4835
  process.exitCode = 1;
4494
4836
  return;
4495
4837
  }
4496
4838
  if (opts.build !== false) {
4497
- console.log(pc23.dim("building app..."));
4839
+ console.log(pc24.dim("building app..."));
4498
4840
  const ok = await runBuild(absPath);
4499
4841
  if (!ok) {
4500
- console.error(pc23.red("build failed; aborting"));
4842
+ console.error(pc24.red("build failed; aborting"));
4501
4843
  process.exitCode = 1;
4502
4844
  return;
4503
4845
  }
@@ -4505,14 +4847,14 @@ var publishCommand = new Command22("publish").description("Build, pack, and uplo
4505
4847
  const bundleDir = opts.bundleDir ? resolve10(absPath, opts.bundleDir) : pickBundleDir(absPath);
4506
4848
  let bundleB64;
4507
4849
  if (bundleDir && existsSync15(bundleDir)) {
4508
- console.log(pc23.dim(`packing ${relative2(absPath, bundleDir)}/ \u2192 tar.gz...`));
4850
+ console.log(pc24.dim(`packing ${relative2(absPath, bundleDir)}/ \u2192 tar.gz...`));
4509
4851
  const bytes = await packDirToTarGz(bundleDir);
4510
4852
  bundleB64 = bytes.toString("base64");
4511
- console.log(pc23.dim(` bundle: ${(bytes.length / 1024).toFixed(1)} KiB`));
4853
+ console.log(pc24.dim(` bundle: ${(bytes.length / 1024).toFixed(1)} KiB`));
4512
4854
  } else {
4513
- console.log(pc23.dim("no frontend bundle to upload (headless app or no dist/public)"));
4855
+ console.log(pc24.dim("no frontend bundle to upload (headless app or no dist/public)"));
4514
4856
  }
4515
- console.log(pc23.dim(`uploading to ${server}/api/apps/upload (${opts.public ? "public" : "private"})...`));
4857
+ console.log(pc24.dim(`uploading to ${server}/api/apps/upload (${opts.public ? "public" : "private"})...`));
4516
4858
  const res = await fetch(`${server}/api/apps/upload`, {
4517
4859
  method: "POST",
4518
4860
  headers: authHeaders(token),
@@ -4524,23 +4866,23 @@ var publishCommand = new Command22("publish").description("Build, pack, and uplo
4524
4866
  });
4525
4867
  if (!res.ok) {
4526
4868
  const body = await res.text().catch(() => "");
4527
- console.error(pc23.red(`upload failed (${res.status}): ${body}`));
4869
+ console.error(pc24.red(`upload failed (${res.status}): ${body}`));
4528
4870
  process.exitCode = 1;
4529
4871
  return;
4530
4872
  }
4531
4873
  const data = await res.json();
4532
4874
  if (!data.ok) {
4533
- console.error(pc23.red(`upload rejected: ${data.error ?? "unknown"}`));
4875
+ console.error(pc24.red(`upload rejected: ${data.error ?? "unknown"}`));
4534
4876
  process.exitCode = 1;
4535
4877
  return;
4536
4878
  }
4537
- console.log(pc23.green("\u2713"), data.message ?? "uploaded");
4879
+ console.log(pc24.green("\u2713"), data.message ?? "uploaded");
4538
4880
  if (data.install) {
4539
- console.log(pc23.dim(` id: ${data.install.id}`));
4540
- console.log(pc23.dim(` version: ${data.install.version}`));
4541
- console.log(pc23.dim(` visibility: ${data.install.visibility}`));
4881
+ console.log(pc24.dim(` id: ${data.install.id}`));
4882
+ console.log(pc24.dim(` version: ${data.install.version}`));
4883
+ console.log(pc24.dim(` visibility: ${data.install.visibility}`));
4542
4884
  if (data.install.reviewStatus !== "none") {
4543
- console.log(pc23.dim(` review: ${data.install.reviewStatus}`));
4885
+ console.log(pc24.dim(` review: ${data.install.reviewStatus}`));
4544
4886
  }
4545
4887
  }
4546
4888
  });
@@ -4615,9 +4957,9 @@ function gzip(input2) {
4615
4957
  }
4616
4958
 
4617
4959
  // src/commands/review.ts
4618
- import { Command as Command23 } from "commander";
4619
- import pc24 from "picocolors";
4620
- var reviewCommand = new Command23("review").description("Admin: review queue for public-visibility uploads");
4960
+ import { Command as Command24 } from "commander";
4961
+ import pc25 from "picocolors";
4962
+ var reviewCommand = new Command24("review").description("Admin: review queue for public-visibility uploads");
4621
4963
  reviewCommand.command("list").description("List pending public-visibility uploads").option("-s, --server <url>", "Server URL").option("-t, --token <token>", "Auth token").action(async (opts) => {
4622
4964
  const server = resolveServerUrl(opts.server);
4623
4965
  const token = resolveToken(opts.token);
@@ -4629,15 +4971,15 @@ reviewCommand.command("list").description("List pending public-visibility upload
4629
4971
  }
4630
4972
  const data = await res.json();
4631
4973
  if (!data.pending?.length) {
4632
- console.log(pc24.dim("queue empty"));
4974
+ console.log(pc25.dim("queue empty"));
4633
4975
  return;
4634
4976
  }
4635
4977
  for (const i of data.pending) {
4636
- console.log(pc24.bold(i.id), pc24.cyan(i.appName), pc24.dim(`v${i.version}`));
4637
- console.log(pc24.dim(` uploader: ${i.ownerUserId ?? "(server-wide)"}`));
4638
- console.log(pc24.dim(` uploaded: ${i.uploadedAt}`));
4978
+ console.log(pc25.bold(i.id), pc25.cyan(i.appName), pc25.dim(`v${i.version}`));
4979
+ console.log(pc25.dim(` uploader: ${i.ownerUserId ?? "(server-wide)"}`));
4980
+ console.log(pc25.dim(` uploaded: ${i.uploadedAt}`));
4639
4981
  const desc = i.manifest?.description;
4640
- if (desc) console.log(pc24.dim(` description: ${desc}`));
4982
+ if (desc) console.log(pc25.dim(` description: ${desc}`));
4641
4983
  console.log();
4642
4984
  }
4643
4985
  });
@@ -4658,17 +5000,17 @@ async function decide(id, decision, opts) {
4658
5000
  }
4659
5001
  const data = await res.json();
4660
5002
  if (!data.ok) return die(`server rejected: ${data.error ?? "unknown"}`);
4661
- console.log(pc24.green("\u2713"), `${decision}d`, data.install?.appName, pc24.dim(`(${data.install?.id})`));
4662
- if (data.install?.reviewNotes) console.log(pc24.dim(` notes: ${data.install.reviewNotes}`));
5003
+ console.log(pc25.green("\u2713"), `${decision}d`, data.install?.appName, pc25.dim(`(${data.install?.id})`));
5004
+ if (data.install?.reviewNotes) console.log(pc25.dim(` notes: ${data.install.reviewNotes}`));
4663
5005
  }
4664
5006
  function die(msg) {
4665
- console.error(pc24.red(msg));
5007
+ console.error(pc25.red(msg));
4666
5008
  process.exitCode = 1;
4667
5009
  }
4668
5010
 
4669
5011
  // src/index.ts
4670
- var version = "0.2.5";
4671
- var program = new Command24().name("bot").description("botapp CLI \u2014 operate apps from the command line").version(version).enablePositionalOptions(true).option("--json", "Output as JSON").option("-s, --server <url>", "Server URL override").option("-t, --token <token>", "Auth token override").option("-v, --verbose", "Verbose output");
5012
+ var version = "0.2.7";
5013
+ var program = new Command25().name("bot").description("botapp CLI \u2014 operate apps from the command line").version(version).enablePositionalOptions(true).option("--json", "Output as JSON").option("-s, --server <url>", "Server URL override").option("-t, --token <token>", "Auth token override").option("-v, --verbose", "Verbose output");
4672
5014
  program.addCommand(launchCommand);
4673
5015
  program.addCommand(runCommand);
4674
5016
  program.addCommand(appsCommand);
@@ -4679,6 +5021,7 @@ program.addCommand(loginCommand);
4679
5021
  program.addCommand(agentCommand);
4680
5022
  program.addCommand(pairingCommand);
4681
5023
  program.addCommand(daemonCommand);
5024
+ program.addCommand(doctorCommand);
4682
5025
  program.addCommand(initCommand);
4683
5026
  program.addCommand(installCommand);
4684
5027
  program.addCommand(uninstallCommand);
@@ -4730,29 +5073,29 @@ To discover what params a command accepts:
4730
5073
  program.on("command:*", (operands) => {
4731
5074
  const first = operands[0];
4732
5075
  const known = program.commands.filter((c) => !c._hidden).map((c) => c.name());
4733
- const topLevelHint = `Run ${pc25.cyan("bot --help")} for the list of top-level commands.`;
5076
+ const topLevelHint = `Run ${pc26.cyan("bot --help")} for the list of top-level commands.`;
4734
5077
  const argv = process.argv.slice(2);
4735
5078
  const firstIdx = argv.indexOf(first);
4736
5079
  const tail = firstIdx >= 0 ? argv.slice(firstIdx + 1) : [];
4737
5080
  if (tail.length > 0) {
4738
5081
  const suggested = `bot run ${first} ${tail.join(" ")}`;
4739
5082
  console.error(
4740
- pc25.red(`error: unknown command '${first}'`) + `
5083
+ pc26.red(`error: unknown command '${first}'`) + `
4741
5084
 
4742
- App commands go through ${pc25.bold("bot run")}. Did you mean:
5085
+ App commands go through ${pc26.bold("bot run")}. Did you mean:
4743
5086
 
4744
- ${pc25.cyan(suggested)}
5087
+ ${pc26.cyan(suggested)}
4745
5088
 
4746
5089
  ${topLevelHint}`
4747
5090
  );
4748
5091
  process.exit(1);
4749
5092
  }
4750
5093
  console.error(
4751
- pc25.red(`error: unknown command '${first}'`) + `
5094
+ pc26.red(`error: unknown command '${first}'`) + `
4752
5095
 
4753
5096
  If '${first}' is an app name, invoke one of its commands with:
4754
- ${pc25.cyan(`bot run ${first} <command> [--key value ...]`)}
4755
- ${pc25.cyan("bot apps --json")} ${pc25.dim("(to see what commands exist)")}
5097
+ ${pc26.cyan(`bot run ${first} <command> [--key value ...]`)}
5098
+ ${pc26.cyan("bot apps --json")} ${pc26.dim("(to see what commands exist)")}
4756
5099
 
4757
5100
  Top-level commands: ${known.join(", ")}
4758
5101
  ${topLevelHint}`