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 +446 -103
- package/dist/bin/bot.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +437 -103
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,8 +6,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
-
import { Command as
|
|
10
|
-
import
|
|
9
|
+
import { Command as Command25 } from "commander";
|
|
10
|
+
import pc26 from "picocolors";
|
|
11
11
|
|
|
12
12
|
// src/commands/server.ts
|
|
13
13
|
import { Command } from "commander";
|
|
@@ -194,7 +194,8 @@ function writeYaml(profiles) {
|
|
|
194
194
|
server: p.server,
|
|
195
195
|
daemonId: p.daemonId,
|
|
196
196
|
daemonName: p.daemonName,
|
|
197
|
-
token: p.token
|
|
197
|
+
token: p.token,
|
|
198
|
+
...p.userEmail ? { userEmail: p.userEmail } : {}
|
|
198
199
|
};
|
|
199
200
|
}
|
|
200
201
|
writeFileSync2(DAEMON_FILE, stringify2({ profiles: map }), "utf-8");
|
|
@@ -234,7 +235,8 @@ function loadDaemonProfiles() {
|
|
|
234
235
|
server: normalizeServer(value.server),
|
|
235
236
|
daemonId: value.daemonId,
|
|
236
237
|
daemonName: value.daemonName ?? value.daemonId,
|
|
237
|
-
token: value.token
|
|
238
|
+
token: value.token,
|
|
239
|
+
userEmail: value.userEmail
|
|
238
240
|
});
|
|
239
241
|
}
|
|
240
242
|
}
|
|
@@ -273,7 +275,8 @@ function saveDaemonProfile(input2) {
|
|
|
273
275
|
server,
|
|
274
276
|
daemonId: input2.daemonId,
|
|
275
277
|
daemonName: input2.daemonName ?? input2.daemonId,
|
|
276
|
-
token: input2.token
|
|
278
|
+
token: input2.token,
|
|
279
|
+
userEmail: input2.userEmail
|
|
277
280
|
};
|
|
278
281
|
writeYaml([...remaining, next]);
|
|
279
282
|
return next;
|
|
@@ -1084,6 +1087,28 @@ function pickProfilesToRun(alias, server) {
|
|
|
1084
1087
|
}
|
|
1085
1088
|
return loadDaemonProfiles();
|
|
1086
1089
|
}
|
|
1090
|
+
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) => {
|
|
1091
|
+
const removed = removeDaemonProfile(aliasOrServer);
|
|
1092
|
+
if (!removed) {
|
|
1093
|
+
console.error(pc3.red(`No paired profile matching "${aliasOrServer}".`));
|
|
1094
|
+
process.exitCode = 1;
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
console.log(pc3.green(`Removed daemon profile: ${aliasOrServer}`));
|
|
1098
|
+
});
|
|
1099
|
+
daemonCommand.command("list").alias("ls").description("List paired daemon profiles from ~/.botapp/daemon.yaml").action(() => {
|
|
1100
|
+
const profiles = loadDaemonProfiles();
|
|
1101
|
+
if (profiles.length === 0) {
|
|
1102
|
+
console.log(pc3.dim("No paired daemons. Run `bot pair` to add one."));
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
for (const p of profiles) {
|
|
1106
|
+
console.log(pc3.bold(p.alias ?? p.server));
|
|
1107
|
+
console.log(` Server: ${p.server}`);
|
|
1108
|
+
console.log(` Daemon: ${p.daemonName} ${pc3.dim(`(${p.daemonId})`)}`);
|
|
1109
|
+
console.log(` User: ${p.userEmail ?? pc3.dim("unknown \u2014 re-pair to capture")}`);
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1087
1112
|
daemonCommand.command("stop").description("Stop the background daemon started by `bot launch`").action(async () => {
|
|
1088
1113
|
const pidFile = join5(homedir5(), ".botapp", "daemon.pid");
|
|
1089
1114
|
if (!existsSync5(pidFile)) {
|
|
@@ -1112,16 +1137,17 @@ daemonCommand.command("agent").description("Manage agents hosted by this daemon"
|
|
|
1112
1137
|
for (const agent of data.agents ?? []) {
|
|
1113
1138
|
console.log(`${pc3.bold(agent.name)} ${pc3.dim(`(${agent.id})`)}`);
|
|
1114
1139
|
console.log(` Kind: ${agent.kind}`);
|
|
1140
|
+
if (agent.model) console.log(` Model: ${agent.model}`);
|
|
1115
1141
|
console.log(` Command: ${agent.command} ${(agent.args ?? []).join(" ")}`);
|
|
1116
1142
|
if (agent.cwd) console.log(` CWD: ${agent.cwd}`);
|
|
1117
1143
|
}
|
|
1118
1144
|
})
|
|
1119
1145
|
).addCommand(createDaemonAgentConfigCommand()).addCommand(
|
|
1120
|
-
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(
|
|
1146
|
+
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(
|
|
1121
1147
|
"--kind <kind>",
|
|
1122
|
-
"Agent adapter kind: acp, codex, claude-code, openclaw, hermes, hermes-agent, or shell",
|
|
1148
|
+
"Agent adapter kind: acp, codex, claude-code, kimi, openclaw, hermes, hermes-agent, or shell",
|
|
1123
1149
|
"acp"
|
|
1124
|
-
).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) => {
|
|
1150
|
+
).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) => {
|
|
1125
1151
|
const profile = requireSelectedProfile(opts);
|
|
1126
1152
|
if (!profile) return;
|
|
1127
1153
|
const env = parseEnv(opts.env ?? []);
|
|
@@ -1137,12 +1163,14 @@ daemonCommand.command("agent").description("Manage agents hosted by this daemon"
|
|
|
1137
1163
|
command: opts.command,
|
|
1138
1164
|
args: opts.arg ?? [],
|
|
1139
1165
|
cwd: opts.cwd,
|
|
1140
|
-
env
|
|
1166
|
+
env,
|
|
1167
|
+
model: opts.model
|
|
1141
1168
|
}
|
|
1142
1169
|
}
|
|
1143
1170
|
);
|
|
1144
1171
|
console.log(pc3.green(`Registered daemon agent: ${pc3.bold(data.agent.name)}`));
|
|
1145
1172
|
console.log(` ID: ${data.agent.id}`);
|
|
1173
|
+
if (data.agent.model) console.log(` Model: ${data.agent.model}`);
|
|
1146
1174
|
console.log(` Command: ${data.agent.command} ${(data.agent.args ?? []).join(" ")}`);
|
|
1147
1175
|
})
|
|
1148
1176
|
).addCommand(
|
|
@@ -1308,7 +1336,10 @@ async function runAgentJob(job, update) {
|
|
|
1308
1336
|
if (job.agent.kind === "claude-code" || job.agent.kind === "claude_code") {
|
|
1309
1337
|
return runClaudeCodeAgent(job, update);
|
|
1310
1338
|
}
|
|
1311
|
-
if (job.agent.kind === "
|
|
1339
|
+
if (job.agent.kind === "kimi") {
|
|
1340
|
+
return runKimiCodeAgent(job, update);
|
|
1341
|
+
}
|
|
1342
|
+
if (job.agent.kind === "openclaw") {
|
|
1312
1343
|
return runOpenClawAgent(job, update);
|
|
1313
1344
|
}
|
|
1314
1345
|
if (job.agent.kind === "hermes" || job.agent.kind === "hermes-agent") {
|
|
@@ -1349,6 +1380,9 @@ async function runCodexAgent(job, update) {
|
|
|
1349
1380
|
if (!args.includes("--skip-git-repo-check")) {
|
|
1350
1381
|
args.push("--skip-git-repo-check");
|
|
1351
1382
|
}
|
|
1383
|
+
if (job.agent.model && !hasAnyFlag(args, ["--model", "-m"])) {
|
|
1384
|
+
args.push("--model", job.agent.model);
|
|
1385
|
+
}
|
|
1352
1386
|
if (!hasAnyFlag(args, [
|
|
1353
1387
|
"--sandbox",
|
|
1354
1388
|
"-s",
|
|
@@ -1377,12 +1411,19 @@ async function runCodexAgent(job, update) {
|
|
|
1377
1411
|
rawEvents: []
|
|
1378
1412
|
};
|
|
1379
1413
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
1414
|
+
const errors = [];
|
|
1380
1415
|
function processLine(line) {
|
|
1381
1416
|
if (!line.trim()) return;
|
|
1382
1417
|
try {
|
|
1383
1418
|
const event = JSON.parse(line);
|
|
1384
1419
|
result.rawEvents.push(event);
|
|
1385
1420
|
update({ kind: "codex_event", event });
|
|
1421
|
+
if (event?.type === "error" && typeof event.message === "string") {
|
|
1422
|
+
errors.push(event.message);
|
|
1423
|
+
}
|
|
1424
|
+
if (event?.type === "turn.failed" && typeof event.error?.message === "string") {
|
|
1425
|
+
errors.push(event.error.message);
|
|
1426
|
+
}
|
|
1386
1427
|
if (event?.type === "item.completed" && event.item?.type === "agent_message") {
|
|
1387
1428
|
if (typeof event.item.text === "string") {
|
|
1388
1429
|
result.messages.push(event.item.text);
|
|
@@ -1412,7 +1453,7 @@ async function runCodexAgent(job, update) {
|
|
|
1412
1453
|
rl.on("line", processLine);
|
|
1413
1454
|
const code = await new Promise((resolve11) => child.on("close", resolve11));
|
|
1414
1455
|
if (code !== 0) {
|
|
1415
|
-
throw new Error(stderr.trim() || `Codex exited with code ${code}`);
|
|
1456
|
+
throw new Error(errors.at(-1) ?? (stderr.trim() || `Codex exited with code ${code}`));
|
|
1416
1457
|
}
|
|
1417
1458
|
result.text = result.messages.at(-1)?.trim() || "";
|
|
1418
1459
|
result.toolCalls = [...toolCalls.values()];
|
|
@@ -1429,6 +1470,9 @@ async function runClaudeCodeAgent(job, update) {
|
|
|
1429
1470
|
if (!args.includes("--verbose")) {
|
|
1430
1471
|
args.push("--verbose");
|
|
1431
1472
|
}
|
|
1473
|
+
if (job.agent.model && !hasAnyFlag(args, ["--model"])) {
|
|
1474
|
+
args.push("--model", job.agent.model);
|
|
1475
|
+
}
|
|
1432
1476
|
if (!hasAnyFlag(args, [
|
|
1433
1477
|
"--permission-mode",
|
|
1434
1478
|
"--dangerously-skip-permissions",
|
|
@@ -1451,6 +1495,9 @@ async function runClaudeCodeAgent(job, update) {
|
|
|
1451
1495
|
child.stderr.on("data", (chunk) => {
|
|
1452
1496
|
stderr += chunk.toString();
|
|
1453
1497
|
});
|
|
1498
|
+
const noiseLines = [];
|
|
1499
|
+
const errorEvents = [];
|
|
1500
|
+
const NOISE_TAIL = 6;
|
|
1454
1501
|
const result = {
|
|
1455
1502
|
kind: "claude-code",
|
|
1456
1503
|
text: "",
|
|
@@ -1508,11 +1555,19 @@ async function runClaudeCodeAgent(job, update) {
|
|
|
1508
1555
|
try {
|
|
1509
1556
|
event = JSON.parse(line);
|
|
1510
1557
|
} catch {
|
|
1558
|
+
noiseLines.push(line);
|
|
1559
|
+
if (noiseLines.length > NOISE_TAIL) noiseLines.shift();
|
|
1511
1560
|
return;
|
|
1512
1561
|
}
|
|
1513
1562
|
result.rawEvents.push(event);
|
|
1514
1563
|
update({ kind: "claude_code_event", event });
|
|
1515
1564
|
captureSessionId(event);
|
|
1565
|
+
if (event && (event.is_error === true || event.subtype === "error" || event.type === "result" && event.is_error)) {
|
|
1566
|
+
try {
|
|
1567
|
+
errorEvents.push(JSON.stringify(event).slice(0, 1e3));
|
|
1568
|
+
} catch {
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1516
1571
|
if (event.type === "assistant" && event.message?.content) {
|
|
1517
1572
|
ingestAssistantContent(
|
|
1518
1573
|
Array.isArray(event.message.content) ? event.message.content : []
|
|
@@ -1533,7 +1588,146 @@ async function runClaudeCodeAgent(job, update) {
|
|
|
1533
1588
|
rl.on("line", processLine);
|
|
1534
1589
|
const code = await new Promise((resolve11) => child.on("close", resolve11));
|
|
1535
1590
|
if (code !== 0) {
|
|
1536
|
-
|
|
1591
|
+
const parts = [];
|
|
1592
|
+
if (stderr.trim()) parts.push(stderr.trim());
|
|
1593
|
+
if (errorEvents.length > 0) {
|
|
1594
|
+
parts.push(`Error events: ${errorEvents.join(" | ")}`);
|
|
1595
|
+
}
|
|
1596
|
+
if (noiseLines.length > 0 && parts.length === 0) {
|
|
1597
|
+
parts.push(`Last stdout lines: ${noiseLines.join(" | ").slice(-1500)}`);
|
|
1598
|
+
}
|
|
1599
|
+
if (parts.length === 0) {
|
|
1600
|
+
parts.push(`Claude Code exited with code ${code} (no stderr or stdout output)`);
|
|
1601
|
+
}
|
|
1602
|
+
throw new Error(parts.join(" \u2014 "));
|
|
1603
|
+
}
|
|
1604
|
+
if (!result.text) {
|
|
1605
|
+
result.text = result.messages.at(-1)?.trim() ?? "";
|
|
1606
|
+
}
|
|
1607
|
+
result.toolCalls = [...toolCalls.values()];
|
|
1608
|
+
return JSON.stringify(result);
|
|
1609
|
+
}
|
|
1610
|
+
async function runKimiCodeAgent(job, update) {
|
|
1611
|
+
const args = [...job.agent.args];
|
|
1612
|
+
if (!args.includes("--print") && !args.includes("-p")) {
|
|
1613
|
+
args.push("--print");
|
|
1614
|
+
}
|
|
1615
|
+
if (!args.includes("--output-format")) {
|
|
1616
|
+
args.push("--output-format", "stream-json");
|
|
1617
|
+
}
|
|
1618
|
+
if (!hasAnyFlag(args, ["--yolo", "--yes", "-y"])) {
|
|
1619
|
+
args.push("--yolo");
|
|
1620
|
+
}
|
|
1621
|
+
if (job.agent.model && !hasAnyFlag(args, ["--model", "-m"])) {
|
|
1622
|
+
args.push("--model", job.agent.model);
|
|
1623
|
+
}
|
|
1624
|
+
const resume = job.resumeSessionId ?? job.agent.env?.KIMI_SESSION_ID ?? null;
|
|
1625
|
+
if (resume && !hasAnyFlag(args, ["--session", "-S", "-r", "--resume", "--continue", "-C"])) {
|
|
1626
|
+
args.push("-r", resume);
|
|
1627
|
+
}
|
|
1628
|
+
const cwd = job.agent.cwd ?? process.cwd();
|
|
1629
|
+
let prompt = job.query;
|
|
1630
|
+
if (!resume) {
|
|
1631
|
+
const agentsMdPath = join5(cwd, "AGENTS.md");
|
|
1632
|
+
if (existsSync5(agentsMdPath)) {
|
|
1633
|
+
try {
|
|
1634
|
+
const primer = readFileSync3(agentsMdPath, "utf-8").trim();
|
|
1635
|
+
if (primer) {
|
|
1636
|
+
prompt = `# System instructions (read carefully before responding)
|
|
1637
|
+
|
|
1638
|
+
${primer}
|
|
1639
|
+
|
|
1640
|
+
---
|
|
1641
|
+
|
|
1642
|
+
# Your task
|
|
1643
|
+
|
|
1644
|
+
${job.query}`;
|
|
1645
|
+
}
|
|
1646
|
+
} catch {
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
const child = spawn3(job.agent.command, args, {
|
|
1651
|
+
cwd,
|
|
1652
|
+
env: { ...process.env, ...job.agent.env ?? {} },
|
|
1653
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1654
|
+
});
|
|
1655
|
+
child.stdin.write(prompt);
|
|
1656
|
+
child.stdin.end();
|
|
1657
|
+
let stderr = "";
|
|
1658
|
+
child.stderr.on("data", (chunk) => {
|
|
1659
|
+
stderr += chunk.toString();
|
|
1660
|
+
});
|
|
1661
|
+
const result = {
|
|
1662
|
+
kind: "kimi",
|
|
1663
|
+
text: "",
|
|
1664
|
+
messages: [],
|
|
1665
|
+
toolCalls: [],
|
|
1666
|
+
sessionId: resume,
|
|
1667
|
+
rawEvents: []
|
|
1668
|
+
};
|
|
1669
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1670
|
+
function ingestAssistantContent(blocks) {
|
|
1671
|
+
for (const block of blocks) {
|
|
1672
|
+
if (!block || typeof block !== "object") continue;
|
|
1673
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
1674
|
+
result.messages.push(block.text);
|
|
1675
|
+
update({ kind: "message", text: block.text });
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
function ingestToolCalls(calls) {
|
|
1680
|
+
for (const call of calls) {
|
|
1681
|
+
if (!call || typeof call !== "object") continue;
|
|
1682
|
+
const id = typeof call.id === "string" ? call.id : null;
|
|
1683
|
+
if (!id) continue;
|
|
1684
|
+
const name = String(call.function?.name ?? call.name ?? "tool");
|
|
1685
|
+
let input2 = call.function?.arguments ?? call.arguments;
|
|
1686
|
+
if (typeof input2 === "string") {
|
|
1687
|
+
try {
|
|
1688
|
+
input2 = JSON.parse(input2);
|
|
1689
|
+
} catch {
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
const next = { ...toolCalls.get(id) ?? {}, id, name, input: input2 };
|
|
1693
|
+
toolCalls.set(id, next);
|
|
1694
|
+
update({ kind: "tool_call", toolCall: next });
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
function ingestToolResult(event) {
|
|
1698
|
+
const id = event?.tool_call_id;
|
|
1699
|
+
if (typeof id !== "string") return;
|
|
1700
|
+
const existing = toolCalls.get(id) ?? { id, name: "tool" };
|
|
1701
|
+
const output2 = typeof event.content === "string" ? event.content : JSON.stringify(event.content);
|
|
1702
|
+
const next = { ...existing, output: output2 };
|
|
1703
|
+
toolCalls.set(id, next);
|
|
1704
|
+
update({ kind: "tool_call", toolCall: next });
|
|
1705
|
+
}
|
|
1706
|
+
function processLine(line) {
|
|
1707
|
+
const trimmed = line.trim();
|
|
1708
|
+
if (!trimmed || trimmed[0] !== "{") return;
|
|
1709
|
+
let event;
|
|
1710
|
+
try {
|
|
1711
|
+
event = JSON.parse(trimmed);
|
|
1712
|
+
} catch {
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
result.rawEvents.push(event);
|
|
1716
|
+
update({ kind: "kimi_event", event });
|
|
1717
|
+
if (event.role === "assistant") {
|
|
1718
|
+
if (Array.isArray(event.content)) ingestAssistantContent(event.content);
|
|
1719
|
+
if (Array.isArray(event.tool_calls)) ingestToolCalls(event.tool_calls);
|
|
1720
|
+
} else if (event.role === "tool") {
|
|
1721
|
+
ingestToolResult(event);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
const rl = createInterface2({ input: child.stdout });
|
|
1725
|
+
rl.on("line", processLine);
|
|
1726
|
+
const code = await new Promise((resolve11) => child.on("close", resolve11));
|
|
1727
|
+
const resumeMatch = stderr.match(/To resume this session:\s*kimi\s+-r\s+([A-Za-z0-9_-]+)/);
|
|
1728
|
+
if (resumeMatch) result.sessionId = resumeMatch[1];
|
|
1729
|
+
if (code !== 0) {
|
|
1730
|
+
throw new Error(stderr.trim() || `Kimi Code exited with code ${code}`);
|
|
1537
1731
|
}
|
|
1538
1732
|
if (!result.text) {
|
|
1539
1733
|
result.text = result.messages.at(-1)?.trim() ?? "";
|
|
@@ -1597,6 +1791,7 @@ async function runOpenClawAgent(job, update) {
|
|
|
1597
1791
|
};
|
|
1598
1792
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
1599
1793
|
let stderr = "";
|
|
1794
|
+
const stdoutLines = [];
|
|
1600
1795
|
let stopPolling = false;
|
|
1601
1796
|
const child = spawn3(job.agent.command, args, {
|
|
1602
1797
|
cwd: job.agent.cwd ?? process.cwd(),
|
|
@@ -1604,7 +1799,15 @@ async function runOpenClawAgent(job, update) {
|
|
|
1604
1799
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1605
1800
|
});
|
|
1606
1801
|
const stdout = createInterface2({ input: child.stdout });
|
|
1607
|
-
stdout.on("line", () => {
|
|
1802
|
+
stdout.on("line", (line) => {
|
|
1803
|
+
const text = line.trim();
|
|
1804
|
+
if (!text) return;
|
|
1805
|
+
stdoutLines.push(text);
|
|
1806
|
+
const stdoutText = extractOpenClawStdoutText(text);
|
|
1807
|
+
if (!stdoutText) return;
|
|
1808
|
+
result.text = stdoutText;
|
|
1809
|
+
result.messages.push(stdoutText);
|
|
1810
|
+
update({ kind: "message", text: stdoutText });
|
|
1608
1811
|
});
|
|
1609
1812
|
const stderrReader = createInterface2({ input: child.stderr });
|
|
1610
1813
|
stderrReader.on("line", (line) => {
|
|
@@ -1636,11 +1839,11 @@ async function runOpenClawAgent(job, update) {
|
|
|
1636
1839
|
}
|
|
1637
1840
|
result.sessionFile = state.selectedFile;
|
|
1638
1841
|
result.toolCalls = [...toolCalls.values()];
|
|
1639
|
-
result.text = result.text || result.messages.at(-1)?.trim() || "";
|
|
1842
|
+
result.text = result.text || result.messages.at(-1)?.trim() || extractOpenClawStdoutText(stdoutLines.at(-1) ?? "") || "";
|
|
1640
1843
|
if (code !== 0) {
|
|
1641
1844
|
throw new Error(stderr.trim() || `OpenClaw exited with code ${code}`);
|
|
1642
1845
|
}
|
|
1643
|
-
if (!result.sessionId) {
|
|
1846
|
+
if (!result.sessionId && !result.text && result.toolCalls.length === 0) {
|
|
1644
1847
|
throw new Error(
|
|
1645
1848
|
[
|
|
1646
1849
|
"OpenClaw completed, but the session key was not resolved from sessions.json.",
|
|
@@ -1661,6 +1864,17 @@ async function runOpenClawAgent(job, update) {
|
|
|
1661
1864
|
}
|
|
1662
1865
|
return JSON.stringify(result);
|
|
1663
1866
|
}
|
|
1867
|
+
function extractOpenClawStdoutText(line) {
|
|
1868
|
+
const text = line.trim();
|
|
1869
|
+
if (!text) return "";
|
|
1870
|
+
try {
|
|
1871
|
+
const parsed = JSON.parse(text);
|
|
1872
|
+
if (parsed && typeof parsed === "object") return JSON.stringify(parsed);
|
|
1873
|
+
if (typeof parsed === "string") return parsed;
|
|
1874
|
+
} catch {
|
|
1875
|
+
}
|
|
1876
|
+
return text;
|
|
1877
|
+
}
|
|
1664
1878
|
function resolveOpenClawSessionDir(args, env) {
|
|
1665
1879
|
const configured = env.BOTAPP_OPENCLAW_SESSION_DIR ?? env.OPENCLAW_SESSION_DIR;
|
|
1666
1880
|
if (configured) return expandPath(configured);
|
|
@@ -2173,7 +2387,7 @@ Invalid ACP stdout: ${line}`;
|
|
|
2173
2387
|
clientInfo: {
|
|
2174
2388
|
name: "botapp-daemon",
|
|
2175
2389
|
title: "botapp daemon",
|
|
2176
|
-
version: "0.2.
|
|
2390
|
+
version: "0.2.7"
|
|
2177
2391
|
}
|
|
2178
2392
|
});
|
|
2179
2393
|
const session = await request2("session/new", {
|
|
@@ -2748,7 +2962,8 @@ Configured for ${serverUrl}`));
|
|
|
2748
2962
|
server: result.serverUrl ?? serverUrl,
|
|
2749
2963
|
daemonId: data.daemon.id,
|
|
2750
2964
|
daemonName: data.daemon.name,
|
|
2751
|
-
token: data.token
|
|
2965
|
+
token: data.token,
|
|
2966
|
+
userEmail: result.userEmail
|
|
2752
2967
|
});
|
|
2753
2968
|
console.log(
|
|
2754
2969
|
` Daemon: ${pc5.bold(data.daemon.name)} ${pc5.dim(`(${data.daemon.id})`)}` + pc5.dim(` profile=${savedProfile.alias}`)
|
|
@@ -3807,7 +4022,8 @@ var pairingCommand = new Command18("pairing").alias("pair").description("Pair th
|
|
|
3807
4022
|
server: serverUrl,
|
|
3808
4023
|
daemonId: data.daemon.id,
|
|
3809
4024
|
daemonName: data.daemon.name,
|
|
3810
|
-
token: data.token
|
|
4025
|
+
token: data.token,
|
|
4026
|
+
userEmail: grant.userEmail
|
|
3811
4027
|
});
|
|
3812
4028
|
console.log(pc19.green(`Paired daemon: ${pc19.bold(data.daemon.name)}`));
|
|
3813
4029
|
console.log(` ID: ${data.daemon.id}`);
|
|
@@ -3843,13 +4059,130 @@ async function obtainPairingToken(opts) {
|
|
|
3843
4059
|
return { pairingToken: result.pairingToken, userEmail: result.userEmail };
|
|
3844
4060
|
}
|
|
3845
4061
|
|
|
4062
|
+
// src/commands/doctor.ts
|
|
4063
|
+
import { Command as Command19 } from "commander";
|
|
4064
|
+
import pc20 from "picocolors";
|
|
4065
|
+
var doctorCommand = new Command19("doctor").description("Diagnose paired daemon profiles (server reachability, token validity, account ownership)").action(async () => {
|
|
4066
|
+
const profiles = loadDaemonProfiles();
|
|
4067
|
+
if (profiles.length === 0) {
|
|
4068
|
+
console.log(pc20.dim("No paired daemons in ~/.botapp/daemon.yaml."));
|
|
4069
|
+
console.log(pc20.dim("Run `bot pair` to add one."));
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4072
|
+
let warns = 0;
|
|
4073
|
+
let fails = 0;
|
|
4074
|
+
for (const profile of profiles) {
|
|
4075
|
+
console.log(pc20.bold(`
|
|
4076
|
+
[${profile.alias ?? profile.server}]`));
|
|
4077
|
+
console.log(` Server: ${profile.server}`);
|
|
4078
|
+
console.log(` Daemon: ${profile.daemonName} ${pc20.dim(`(${profile.daemonId})`)}`);
|
|
4079
|
+
const findings = await checkProfile(profile);
|
|
4080
|
+
for (const f of findings) {
|
|
4081
|
+
const tag = f.severity === "ok" ? pc20.green(" \u2713 ") : f.severity === "warn" ? pc20.yellow(" \u26A0 ") : pc20.red(" \u2717 ");
|
|
4082
|
+
console.log(`${tag}${f.message}`);
|
|
4083
|
+
if (f.fix) console.log(pc20.dim(` \u2192 ${f.fix}`));
|
|
4084
|
+
if (f.severity === "warn") warns += 1;
|
|
4085
|
+
if (f.severity === "fail") fails += 1;
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
console.log("");
|
|
4089
|
+
if (fails === 0 && warns === 0) {
|
|
4090
|
+
console.log(pc20.green("All checks passed."));
|
|
4091
|
+
} else {
|
|
4092
|
+
const parts = [];
|
|
4093
|
+
if (fails > 0) parts.push(pc20.red(`${fails} failing`));
|
|
4094
|
+
if (warns > 0) parts.push(pc20.yellow(`${warns} warning${warns === 1 ? "" : "s"}`));
|
|
4095
|
+
console.log(parts.join(", ") + ".");
|
|
4096
|
+
if (fails > 0) process.exitCode = 1;
|
|
4097
|
+
}
|
|
4098
|
+
});
|
|
4099
|
+
async function checkProfile(profile) {
|
|
4100
|
+
const findings = [];
|
|
4101
|
+
let serverOk = false;
|
|
4102
|
+
try {
|
|
4103
|
+
const res = await fetch(`${profile.server}/health`, {
|
|
4104
|
+
signal: AbortSignal.timeout(5e3)
|
|
4105
|
+
});
|
|
4106
|
+
if (res.ok) {
|
|
4107
|
+
findings.push({ severity: "ok", message: "Server reachable" });
|
|
4108
|
+
serverOk = true;
|
|
4109
|
+
} else {
|
|
4110
|
+
findings.push({
|
|
4111
|
+
severity: "fail",
|
|
4112
|
+
message: `Server returned ${res.status} ${res.statusText}`,
|
|
4113
|
+
fix: `Verify the server at ${profile.server} is running.`
|
|
4114
|
+
});
|
|
4115
|
+
}
|
|
4116
|
+
} catch (e) {
|
|
4117
|
+
findings.push({
|
|
4118
|
+
severity: "fail",
|
|
4119
|
+
message: `Server unreachable: ${e.message ?? e}`,
|
|
4120
|
+
fix: `Start the server, or remove this profile with \`bot daemon unpair ${profile.alias ?? profile.server}\`.`
|
|
4121
|
+
});
|
|
4122
|
+
}
|
|
4123
|
+
if (!serverOk) return findings;
|
|
4124
|
+
let serverEmail = null;
|
|
4125
|
+
try {
|
|
4126
|
+
const data = await daemonRequest(profile.server, profile.token, "/api/daemon/self");
|
|
4127
|
+
findings.push({ severity: "ok", message: "Token valid" });
|
|
4128
|
+
serverEmail = data.user?.email ?? null;
|
|
4129
|
+
if (serverEmail) {
|
|
4130
|
+
findings.push({
|
|
4131
|
+
severity: "ok",
|
|
4132
|
+
message: `Token paired under ${pc20.bold(serverEmail)}`
|
|
4133
|
+
});
|
|
4134
|
+
} else {
|
|
4135
|
+
findings.push({
|
|
4136
|
+
severity: "warn",
|
|
4137
|
+
message: "Server returned no user record for this daemon",
|
|
4138
|
+
fix: "The owning user may have been deleted. Re-pair with `bot pair`."
|
|
4139
|
+
});
|
|
4140
|
+
}
|
|
4141
|
+
if (data.daemon?.status === "online") {
|
|
4142
|
+
findings.push({ severity: "ok", message: "Daemon currently online" });
|
|
4143
|
+
} else {
|
|
4144
|
+
findings.push({
|
|
4145
|
+
severity: "warn",
|
|
4146
|
+
message: `Daemon status: ${data.daemon?.status ?? "unknown"}`,
|
|
4147
|
+
fix: `Run \`bot daemon run --server ${profile.server}\` to bring it online.`
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
4150
|
+
} catch (e) {
|
|
4151
|
+
const msg = e?.message ?? String(e);
|
|
4152
|
+
if (/unauthor/i.test(msg) || /token/i.test(msg)) {
|
|
4153
|
+
findings.push({
|
|
4154
|
+
severity: "fail",
|
|
4155
|
+
message: `Token rejected: ${msg}`,
|
|
4156
|
+
fix: "The token is stale (likely the server DB was reset). Re-pair with `bot pair`."
|
|
4157
|
+
});
|
|
4158
|
+
return findings;
|
|
4159
|
+
}
|
|
4160
|
+
findings.push({ severity: "fail", message: `Identity probe failed: ${msg}` });
|
|
4161
|
+
return findings;
|
|
4162
|
+
}
|
|
4163
|
+
if (profile.userEmail && serverEmail && profile.userEmail !== serverEmail) {
|
|
4164
|
+
findings.push({
|
|
4165
|
+
severity: "warn",
|
|
4166
|
+
message: `daemon.yaml says \`userEmail: ${profile.userEmail}\` but server says \`${serverEmail}\``,
|
|
4167
|
+
fix: "Re-pair to refresh the recorded email."
|
|
4168
|
+
});
|
|
4169
|
+
} else if (!profile.userEmail && serverEmail) {
|
|
4170
|
+
findings.push({
|
|
4171
|
+
severity: "warn",
|
|
4172
|
+
message: "daemon.yaml has no userEmail recorded",
|
|
4173
|
+
fix: "Re-pair to capture it (cosmetic \u2014 auth still works)."
|
|
4174
|
+
});
|
|
4175
|
+
}
|
|
4176
|
+
return findings;
|
|
4177
|
+
}
|
|
4178
|
+
|
|
3846
4179
|
// src/commands/update.ts
|
|
3847
4180
|
import { spawn as spawn7 } from "child_process";
|
|
3848
4181
|
import { realpathSync as realpathSync2 } from "fs";
|
|
3849
|
-
import { Command as
|
|
3850
|
-
import
|
|
4182
|
+
import { Command as Command20 } from "commander";
|
|
4183
|
+
import pc21 from "picocolors";
|
|
3851
4184
|
var PACKAGE_NAME = "botapp-cli";
|
|
3852
|
-
var updateCommand = new
|
|
4185
|
+
var updateCommand = new Command20("update").description("Update the `bot` CLI itself to the latest published version").option(
|
|
3853
4186
|
"--manager <pm>",
|
|
3854
4187
|
"Force a package manager: npm | pnpm | yarn | brew (default: auto-detect)"
|
|
3855
4188
|
).option("--dry-run", "Print the command that would run, but do not execute it").action(async (opts) => {
|
|
@@ -3857,12 +4190,12 @@ var updateCommand = new Command19("update").description("Update the `bot` CLI it
|
|
|
3857
4190
|
const manager = forced ?? detectPackageManager();
|
|
3858
4191
|
if (manager === "npx") {
|
|
3859
4192
|
console.log(
|
|
3860
|
-
|
|
4193
|
+
pc21.green(
|
|
3861
4194
|
"You are running `bot` via `npx -y botapp-cli@latest` \u2014 every invocation already fetches the latest version."
|
|
3862
4195
|
)
|
|
3863
4196
|
);
|
|
3864
4197
|
console.log(
|
|
3865
|
-
|
|
4198
|
+
pc21.dim(
|
|
3866
4199
|
"For a faster startup, install it globally instead:\n npm i -g botapp-cli@latest\n pnpm add -g botapp-cli@latest"
|
|
3867
4200
|
)
|
|
3868
4201
|
);
|
|
@@ -3870,34 +4203,34 @@ var updateCommand = new Command19("update").description("Update the `bot` CLI it
|
|
|
3870
4203
|
}
|
|
3871
4204
|
if (!manager) {
|
|
3872
4205
|
console.error(
|
|
3873
|
-
|
|
4206
|
+
pc21.red(
|
|
3874
4207
|
"Couldn't detect how `bot` was installed. Pick one of these manually:"
|
|
3875
4208
|
)
|
|
3876
4209
|
);
|
|
3877
|
-
console.log(
|
|
3878
|
-
console.log(
|
|
3879
|
-
console.log(
|
|
3880
|
-
console.log(
|
|
4210
|
+
console.log(pc21.cyan(" npm i -g botapp-cli@latest"));
|
|
4211
|
+
console.log(pc21.cyan(" pnpm add -g botapp-cli@latest"));
|
|
4212
|
+
console.log(pc21.cyan(" yarn global add botapp-cli@latest"));
|
|
4213
|
+
console.log(pc21.cyan(" brew upgrade botapp-cli"));
|
|
3881
4214
|
process.exitCode = 1;
|
|
3882
4215
|
return;
|
|
3883
4216
|
}
|
|
3884
4217
|
const { command, args } = updateCommandFor(manager);
|
|
3885
|
-
console.log(
|
|
4218
|
+
console.log(pc21.dim(`$ ${command} ${args.join(" ")}`));
|
|
3886
4219
|
if (opts.dryRun) return;
|
|
3887
4220
|
const child = spawn7(command, args, { stdio: "inherit" });
|
|
3888
4221
|
const code = await new Promise((resolve11) => {
|
|
3889
4222
|
child.once("error", (err) => {
|
|
3890
|
-
console.error(
|
|
4223
|
+
console.error(pc21.red(`Failed to spawn ${command}: ${err.message}`));
|
|
3891
4224
|
resolve11(127);
|
|
3892
4225
|
});
|
|
3893
4226
|
child.once("close", resolve11);
|
|
3894
4227
|
});
|
|
3895
4228
|
if (code !== 0) {
|
|
3896
|
-
console.error(
|
|
4229
|
+
console.error(pc21.red(`Update failed (exit ${code}).`));
|
|
3897
4230
|
process.exitCode = code ?? 1;
|
|
3898
4231
|
return;
|
|
3899
4232
|
}
|
|
3900
|
-
console.log(
|
|
4233
|
+
console.log(pc21.green("botapp-cli updated. Run `bot --version` to confirm."));
|
|
3901
4234
|
});
|
|
3902
4235
|
function detectPackageManager() {
|
|
3903
4236
|
const argv = process.argv[1] ?? "";
|
|
@@ -3932,27 +4265,27 @@ function updateCommandFor(manager) {
|
|
|
3932
4265
|
}
|
|
3933
4266
|
|
|
3934
4267
|
// src/commands/simulate.ts
|
|
3935
|
-
import { Command as
|
|
4268
|
+
import { Command as Command21 } from "commander";
|
|
3936
4269
|
import { resolve as resolve8, join as join11 } from "path";
|
|
3937
4270
|
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
|
|
3938
4271
|
import { spawn as spawn8 } from "child_process";
|
|
3939
|
-
import
|
|
3940
|
-
var simulateCommand = new
|
|
4272
|
+
import pc22 from "picocolors";
|
|
4273
|
+
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) => {
|
|
3941
4274
|
const absPath = resolve8(appPath);
|
|
3942
4275
|
if (!existsSync13(absPath)) {
|
|
3943
|
-
console.error(
|
|
4276
|
+
console.error(pc22.red(`Path not found: ${absPath}`));
|
|
3944
4277
|
process.exitCode = 1;
|
|
3945
4278
|
return;
|
|
3946
4279
|
}
|
|
3947
4280
|
const manifestPath = join11(absPath, "botapp.app.json");
|
|
3948
4281
|
if (!existsSync13(manifestPath)) {
|
|
3949
|
-
console.error(
|
|
4282
|
+
console.error(pc22.red(`No botapp.app.json found in ${absPath}`));
|
|
3950
4283
|
process.exitCode = 1;
|
|
3951
4284
|
return;
|
|
3952
4285
|
}
|
|
3953
4286
|
const manifest = JSON.parse(readFileSync9(manifestPath, "utf8"));
|
|
3954
4287
|
if (!manifest.name) {
|
|
3955
|
-
console.error(
|
|
4288
|
+
console.error(pc22.red('manifest missing "name"'));
|
|
3956
4289
|
process.exitCode = 1;
|
|
3957
4290
|
return;
|
|
3958
4291
|
}
|
|
@@ -3960,31 +4293,31 @@ var simulateCommand = new Command20("simulate").description("Dev-tunnel a local
|
|
|
3960
4293
|
const wsServer = httpServer.replace(/^http:/, "ws:").replace(/^https:/, "wss:") + "/ws/host";
|
|
3961
4294
|
const token = resolveToken(opts.token);
|
|
3962
4295
|
if (!token) {
|
|
3963
|
-
console.error(
|
|
4296
|
+
console.error(pc22.red("not logged in: run `bot login` or pass --token"));
|
|
3964
4297
|
process.exitCode = 1;
|
|
3965
4298
|
return;
|
|
3966
4299
|
}
|
|
3967
4300
|
const lifetimeMs = Math.max(6e4, Number(opts.lifetime) * 6e4);
|
|
3968
|
-
console.log(
|
|
4301
|
+
console.log(pc22.dim(`requesting dev token for "${manifest.name}" from ${httpServer}...`));
|
|
3969
4302
|
const devToken = await issueDevToken({
|
|
3970
4303
|
serverUrl: httpServer,
|
|
3971
4304
|
token,
|
|
3972
4305
|
appName: manifest.name,
|
|
3973
4306
|
lifetimeMs
|
|
3974
4307
|
});
|
|
3975
|
-
console.log(
|
|
4308
|
+
console.log(pc22.green("\u2713"), `dev token issued (lifetime ${opts.lifetime} min)`);
|
|
3976
4309
|
const entryPath = resolveEntry(absPath, opts.entry ?? manifest.entry);
|
|
3977
4310
|
if (!existsSync13(entryPath)) {
|
|
3978
|
-
console.error(
|
|
3979
|
-
console.error(
|
|
4311
|
+
console.error(pc22.red(`entry not found: ${entryPath}`));
|
|
4312
|
+
console.error(pc22.dim(" run `pnpm build` (or your build script) first."));
|
|
3980
4313
|
process.exitCode = 1;
|
|
3981
4314
|
return;
|
|
3982
4315
|
}
|
|
3983
|
-
console.log(
|
|
3984
|
-
console.log(
|
|
3985
|
-
console.log(
|
|
4316
|
+
console.log(pc22.dim(`spawning ${entryPath} ...`));
|
|
4317
|
+
console.log(pc22.dim(` BOTAPP_SERVER=${wsServer}`));
|
|
4318
|
+
console.log(pc22.dim(` BOTAPP_APP_NAME=${manifest.name}`));
|
|
3986
4319
|
console.log(
|
|
3987
|
-
|
|
4320
|
+
pc22.cyan(
|
|
3988
4321
|
`
|
|
3989
4322
|
When ready, your dashboard at ${httpServer} will route "${manifest.name}" to this process for your account only.
|
|
3990
4323
|
`
|
|
@@ -4002,7 +4335,7 @@ When ready, your dashboard at ${httpServer} will route "${manifest.name}" to thi
|
|
|
4002
4335
|
stdio: "inherit"
|
|
4003
4336
|
});
|
|
4004
4337
|
const stop = (signal) => {
|
|
4005
|
-
console.log(
|
|
4338
|
+
console.log(pc22.dim(`
|
|
4006
4339
|
stopping (${signal})...`));
|
|
4007
4340
|
if (!child.killed) child.kill(signal);
|
|
4008
4341
|
};
|
|
@@ -4010,7 +4343,7 @@ stopping (${signal})...`));
|
|
|
4010
4343
|
process.on("SIGTERM", () => stop("SIGTERM"));
|
|
4011
4344
|
child.on("exit", (code, sig) => {
|
|
4012
4345
|
const reason = sig ? `signal ${sig}` : `exit ${code}`;
|
|
4013
|
-
console.log(
|
|
4346
|
+
console.log(pc22.dim(`child exited (${reason})`));
|
|
4014
4347
|
process.exit(typeof code === "number" ? code : 0);
|
|
4015
4348
|
});
|
|
4016
4349
|
});
|
|
@@ -4047,14 +4380,14 @@ function resolveEntry(appDir, entry) {
|
|
|
4047
4380
|
}
|
|
4048
4381
|
|
|
4049
4382
|
// src/commands/init.ts
|
|
4050
|
-
import { Command as
|
|
4383
|
+
import { Command as Command22 } from "commander";
|
|
4051
4384
|
import { existsSync as existsSync14, mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
4052
4385
|
import { resolve as resolve9, join as join12 } from "path";
|
|
4053
|
-
import
|
|
4054
|
-
var initCommand = new
|
|
4386
|
+
import pc23 from "picocolors";
|
|
4387
|
+
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) => {
|
|
4055
4388
|
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
4056
|
-
console.error(
|
|
4057
|
-
console.error(
|
|
4389
|
+
console.error(pc23.red("Invalid name. Use lowercase letters, digits, dashes; must start with a letter."));
|
|
4390
|
+
console.error(pc23.dim(" e.g. my-app, todo-tracker, gomoku-2"));
|
|
4058
4391
|
process.exitCode = 1;
|
|
4059
4392
|
return;
|
|
4060
4393
|
}
|
|
@@ -4068,8 +4401,8 @@ var initCommand = new Command21("init").description("Scaffold a new hosted-tier
|
|
|
4068
4401
|
}
|
|
4069
4402
|
})();
|
|
4070
4403
|
if (Array.isArray(stat) && stat.length > 0) {
|
|
4071
|
-
console.error(
|
|
4072
|
-
console.error(
|
|
4404
|
+
console.error(pc23.red(`Target directory exists and is not empty: ${targetDir}`));
|
|
4405
|
+
console.error(pc23.dim(" pass --force to overwrite, or pick a different --dir"));
|
|
4073
4406
|
process.exitCode = 1;
|
|
4074
4407
|
return;
|
|
4075
4408
|
}
|
|
@@ -4086,18 +4419,18 @@ var initCommand = new Command21("init").description("Scaffold a new hosted-tier
|
|
|
4086
4419
|
mkdirSync6(dirname2(full), { recursive: true });
|
|
4087
4420
|
writeFileSync4(full, content);
|
|
4088
4421
|
}
|
|
4089
|
-
console.log(
|
|
4422
|
+
console.log(pc23.green("\u2713"), `Scaffolded ${ctx.headless ? "headless " : ""}app at`, pc23.cyan(targetDir));
|
|
4090
4423
|
console.log();
|
|
4091
4424
|
console.log("Next steps:");
|
|
4092
|
-
console.log(
|
|
4093
|
-
console.log(
|
|
4094
|
-
if (!ctx.headless) console.log(
|
|
4095
|
-
else console.log(
|
|
4096
|
-
console.log(
|
|
4097
|
-
console.log(
|
|
4425
|
+
console.log(pc23.dim(` cd ${targetDir.replace(process.cwd() + "/", "")}`));
|
|
4426
|
+
console.log(pc23.dim(" pnpm install # or npm install"));
|
|
4427
|
+
if (!ctx.headless) console.log(pc23.dim(" pnpm build # builds api/ + frontend (dist/)"));
|
|
4428
|
+
else console.log(pc23.dim(" pnpm build # builds api/ to dist/api.js"));
|
|
4429
|
+
console.log(pc23.dim(` bot login --server <your-botapp-server>`));
|
|
4430
|
+
console.log(pc23.dim(` bot simulate # dev-tunnel into the server, hot-reload as you build`));
|
|
4098
4431
|
console.log();
|
|
4099
4432
|
console.log(
|
|
4100
|
-
|
|
4433
|
+
pc23.dim("Once it works, `bot publish` ships it to the server (per-user install by default).")
|
|
4101
4434
|
);
|
|
4102
4435
|
});
|
|
4103
4436
|
function fullFiles(ctx) {
|
|
@@ -4477,38 +4810,38 @@ function dirname2(p) {
|
|
|
4477
4810
|
}
|
|
4478
4811
|
|
|
4479
4812
|
// src/commands/publish.ts
|
|
4480
|
-
import { Command as
|
|
4813
|
+
import { Command as Command23 } from "commander";
|
|
4481
4814
|
import { resolve as resolve10, join as join13, relative as relative2 } from "path";
|
|
4482
4815
|
import { existsSync as existsSync15, readFileSync as readFileSync10, statSync as statSync4, readdirSync as readdirSync2 } from "fs";
|
|
4483
4816
|
import { createGzip } from "zlib";
|
|
4484
4817
|
import { spawn as spawn9 } from "child_process";
|
|
4485
|
-
import
|
|
4486
|
-
var publishCommand = new
|
|
4818
|
+
import pc24 from "picocolors";
|
|
4819
|
+
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) => {
|
|
4487
4820
|
const absPath = resolve10(appPath);
|
|
4488
4821
|
const manifestPath = join13(absPath, "botapp.app.json");
|
|
4489
4822
|
if (!existsSync15(manifestPath)) {
|
|
4490
|
-
console.error(
|
|
4823
|
+
console.error(pc24.red(`No botapp.app.json found in ${absPath}`));
|
|
4491
4824
|
process.exitCode = 1;
|
|
4492
4825
|
return;
|
|
4493
4826
|
}
|
|
4494
4827
|
const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
|
|
4495
4828
|
if (!manifest.name) {
|
|
4496
|
-
console.error(
|
|
4829
|
+
console.error(pc24.red('manifest missing "name"'));
|
|
4497
4830
|
process.exitCode = 1;
|
|
4498
4831
|
return;
|
|
4499
4832
|
}
|
|
4500
4833
|
const server = resolveServerUrl(opts.server);
|
|
4501
4834
|
const token = resolveToken(opts.token);
|
|
4502
4835
|
if (!token) {
|
|
4503
|
-
console.error(
|
|
4836
|
+
console.error(pc24.red("not logged in: run `bot login` or pass --token"));
|
|
4504
4837
|
process.exitCode = 1;
|
|
4505
4838
|
return;
|
|
4506
4839
|
}
|
|
4507
4840
|
if (opts.build !== false) {
|
|
4508
|
-
console.log(
|
|
4841
|
+
console.log(pc24.dim("building app..."));
|
|
4509
4842
|
const ok = await runBuild(absPath);
|
|
4510
4843
|
if (!ok) {
|
|
4511
|
-
console.error(
|
|
4844
|
+
console.error(pc24.red("build failed; aborting"));
|
|
4512
4845
|
process.exitCode = 1;
|
|
4513
4846
|
return;
|
|
4514
4847
|
}
|
|
@@ -4516,14 +4849,14 @@ var publishCommand = new Command22("publish").description("Build, pack, and uplo
|
|
|
4516
4849
|
const bundleDir = opts.bundleDir ? resolve10(absPath, opts.bundleDir) : pickBundleDir(absPath);
|
|
4517
4850
|
let bundleB64;
|
|
4518
4851
|
if (bundleDir && existsSync15(bundleDir)) {
|
|
4519
|
-
console.log(
|
|
4852
|
+
console.log(pc24.dim(`packing ${relative2(absPath, bundleDir)}/ \u2192 tar.gz...`));
|
|
4520
4853
|
const bytes = await packDirToTarGz(bundleDir);
|
|
4521
4854
|
bundleB64 = bytes.toString("base64");
|
|
4522
|
-
console.log(
|
|
4855
|
+
console.log(pc24.dim(` bundle: ${(bytes.length / 1024).toFixed(1)} KiB`));
|
|
4523
4856
|
} else {
|
|
4524
|
-
console.log(
|
|
4857
|
+
console.log(pc24.dim("no frontend bundle to upload (headless app or no dist/public)"));
|
|
4525
4858
|
}
|
|
4526
|
-
console.log(
|
|
4859
|
+
console.log(pc24.dim(`uploading to ${server}/api/apps/upload (${opts.public ? "public" : "private"})...`));
|
|
4527
4860
|
const res = await fetch(`${server}/api/apps/upload`, {
|
|
4528
4861
|
method: "POST",
|
|
4529
4862
|
headers: authHeaders(token),
|
|
@@ -4535,23 +4868,23 @@ var publishCommand = new Command22("publish").description("Build, pack, and uplo
|
|
|
4535
4868
|
});
|
|
4536
4869
|
if (!res.ok) {
|
|
4537
4870
|
const body = await res.text().catch(() => "");
|
|
4538
|
-
console.error(
|
|
4871
|
+
console.error(pc24.red(`upload failed (${res.status}): ${body}`));
|
|
4539
4872
|
process.exitCode = 1;
|
|
4540
4873
|
return;
|
|
4541
4874
|
}
|
|
4542
4875
|
const data = await res.json();
|
|
4543
4876
|
if (!data.ok) {
|
|
4544
|
-
console.error(
|
|
4877
|
+
console.error(pc24.red(`upload rejected: ${data.error ?? "unknown"}`));
|
|
4545
4878
|
process.exitCode = 1;
|
|
4546
4879
|
return;
|
|
4547
4880
|
}
|
|
4548
|
-
console.log(
|
|
4881
|
+
console.log(pc24.green("\u2713"), data.message ?? "uploaded");
|
|
4549
4882
|
if (data.install) {
|
|
4550
|
-
console.log(
|
|
4551
|
-
console.log(
|
|
4552
|
-
console.log(
|
|
4883
|
+
console.log(pc24.dim(` id: ${data.install.id}`));
|
|
4884
|
+
console.log(pc24.dim(` version: ${data.install.version}`));
|
|
4885
|
+
console.log(pc24.dim(` visibility: ${data.install.visibility}`));
|
|
4553
4886
|
if (data.install.reviewStatus !== "none") {
|
|
4554
|
-
console.log(
|
|
4887
|
+
console.log(pc24.dim(` review: ${data.install.reviewStatus}`));
|
|
4555
4888
|
}
|
|
4556
4889
|
}
|
|
4557
4890
|
});
|
|
@@ -4626,9 +4959,9 @@ function gzip(input2) {
|
|
|
4626
4959
|
}
|
|
4627
4960
|
|
|
4628
4961
|
// src/commands/review.ts
|
|
4629
|
-
import { Command as
|
|
4630
|
-
import
|
|
4631
|
-
var reviewCommand = new
|
|
4962
|
+
import { Command as Command24 } from "commander";
|
|
4963
|
+
import pc25 from "picocolors";
|
|
4964
|
+
var reviewCommand = new Command24("review").description("Admin: review queue for public-visibility uploads");
|
|
4632
4965
|
reviewCommand.command("list").description("List pending public-visibility uploads").option("-s, --server <url>", "Server URL").option("-t, --token <token>", "Auth token").action(async (opts) => {
|
|
4633
4966
|
const server = resolveServerUrl(opts.server);
|
|
4634
4967
|
const token = resolveToken(opts.token);
|
|
@@ -4640,15 +4973,15 @@ reviewCommand.command("list").description("List pending public-visibility upload
|
|
|
4640
4973
|
}
|
|
4641
4974
|
const data = await res.json();
|
|
4642
4975
|
if (!data.pending?.length) {
|
|
4643
|
-
console.log(
|
|
4976
|
+
console.log(pc25.dim("queue empty"));
|
|
4644
4977
|
return;
|
|
4645
4978
|
}
|
|
4646
4979
|
for (const i of data.pending) {
|
|
4647
|
-
console.log(
|
|
4648
|
-
console.log(
|
|
4649
|
-
console.log(
|
|
4980
|
+
console.log(pc25.bold(i.id), pc25.cyan(i.appName), pc25.dim(`v${i.version}`));
|
|
4981
|
+
console.log(pc25.dim(` uploader: ${i.ownerUserId ?? "(server-wide)"}`));
|
|
4982
|
+
console.log(pc25.dim(` uploaded: ${i.uploadedAt}`));
|
|
4650
4983
|
const desc = i.manifest?.description;
|
|
4651
|
-
if (desc) console.log(
|
|
4984
|
+
if (desc) console.log(pc25.dim(` description: ${desc}`));
|
|
4652
4985
|
console.log();
|
|
4653
4986
|
}
|
|
4654
4987
|
});
|
|
@@ -4669,17 +5002,17 @@ async function decide(id, decision, opts) {
|
|
|
4669
5002
|
}
|
|
4670
5003
|
const data = await res.json();
|
|
4671
5004
|
if (!data.ok) return die(`server rejected: ${data.error ?? "unknown"}`);
|
|
4672
|
-
console.log(
|
|
4673
|
-
if (data.install?.reviewNotes) console.log(
|
|
5005
|
+
console.log(pc25.green("\u2713"), `${decision}d`, data.install?.appName, pc25.dim(`(${data.install?.id})`));
|
|
5006
|
+
if (data.install?.reviewNotes) console.log(pc25.dim(` notes: ${data.install.reviewNotes}`));
|
|
4674
5007
|
}
|
|
4675
5008
|
function die(msg) {
|
|
4676
|
-
console.error(
|
|
5009
|
+
console.error(pc25.red(msg));
|
|
4677
5010
|
process.exitCode = 1;
|
|
4678
5011
|
}
|
|
4679
5012
|
|
|
4680
5013
|
// src/index.ts
|
|
4681
|
-
var version = "0.2.
|
|
4682
|
-
var program = new
|
|
5014
|
+
var version = "0.2.7";
|
|
5015
|
+
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");
|
|
4683
5016
|
program.addCommand(launchCommand);
|
|
4684
5017
|
program.addCommand(runCommand);
|
|
4685
5018
|
program.addCommand(appsCommand);
|
|
@@ -4690,6 +5023,7 @@ program.addCommand(loginCommand);
|
|
|
4690
5023
|
program.addCommand(agentCommand);
|
|
4691
5024
|
program.addCommand(pairingCommand);
|
|
4692
5025
|
program.addCommand(daemonCommand);
|
|
5026
|
+
program.addCommand(doctorCommand);
|
|
4693
5027
|
program.addCommand(initCommand);
|
|
4694
5028
|
program.addCommand(installCommand);
|
|
4695
5029
|
program.addCommand(uninstallCommand);
|
|
@@ -4741,29 +5075,29 @@ To discover what params a command accepts:
|
|
|
4741
5075
|
program.on("command:*", (operands) => {
|
|
4742
5076
|
const first = operands[0];
|
|
4743
5077
|
const known = program.commands.filter((c) => !c._hidden).map((c) => c.name());
|
|
4744
|
-
const topLevelHint = `Run ${
|
|
5078
|
+
const topLevelHint = `Run ${pc26.cyan("bot --help")} for the list of top-level commands.`;
|
|
4745
5079
|
const argv = process.argv.slice(2);
|
|
4746
5080
|
const firstIdx = argv.indexOf(first);
|
|
4747
5081
|
const tail = firstIdx >= 0 ? argv.slice(firstIdx + 1) : [];
|
|
4748
5082
|
if (tail.length > 0) {
|
|
4749
5083
|
const suggested = `bot run ${first} ${tail.join(" ")}`;
|
|
4750
5084
|
console.error(
|
|
4751
|
-
|
|
5085
|
+
pc26.red(`error: unknown command '${first}'`) + `
|
|
4752
5086
|
|
|
4753
|
-
App commands go through ${
|
|
5087
|
+
App commands go through ${pc26.bold("bot run")}. Did you mean:
|
|
4754
5088
|
|
|
4755
|
-
${
|
|
5089
|
+
${pc26.cyan(suggested)}
|
|
4756
5090
|
|
|
4757
5091
|
${topLevelHint}`
|
|
4758
5092
|
);
|
|
4759
5093
|
process.exit(1);
|
|
4760
5094
|
}
|
|
4761
5095
|
console.error(
|
|
4762
|
-
|
|
5096
|
+
pc26.red(`error: unknown command '${first}'`) + `
|
|
4763
5097
|
|
|
4764
5098
|
If '${first}' is an app name, invoke one of its commands with:
|
|
4765
|
-
${
|
|
4766
|
-
${
|
|
5099
|
+
${pc26.cyan(`bot run ${first} <command> [--key value ...]`)}
|
|
5100
|
+
${pc26.cyan("bot apps --json")} ${pc26.dim("(to see what commands exist)")}
|
|
4767
5101
|
|
|
4768
5102
|
Top-level commands: ${known.join(", ")}
|
|
4769
5103
|
${topLevelHint}`
|