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/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
|
|
11
|
-
import
|
|
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 === "
|
|
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
|
-
|
|
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.
|
|
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
|
|
3839
|
-
import
|
|
4180
|
+
import { Command as Command20 } from "commander";
|
|
4181
|
+
import pc21 from "picocolors";
|
|
3840
4182
|
var PACKAGE_NAME = "botapp-cli";
|
|
3841
|
-
var updateCommand = new
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4204
|
+
pc21.red(
|
|
3863
4205
|
"Couldn't detect how `bot` was installed. Pick one of these manually:"
|
|
3864
4206
|
)
|
|
3865
4207
|
);
|
|
3866
|
-
console.log(
|
|
3867
|
-
console.log(
|
|
3868
|
-
console.log(
|
|
3869
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
4227
|
+
console.error(pc21.red(`Update failed (exit ${code}).`));
|
|
3886
4228
|
process.exitCode = code ?? 1;
|
|
3887
4229
|
return;
|
|
3888
4230
|
}
|
|
3889
|
-
console.log(
|
|
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
|
|
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
|
|
3929
|
-
var simulateCommand = new
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3968
|
-
console.error(
|
|
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(
|
|
3973
|
-
console.log(
|
|
3974
|
-
console.log(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
4043
|
-
var initCommand = new
|
|
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(
|
|
4046
|
-
console.error(
|
|
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(
|
|
4061
|
-
console.error(
|
|
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(
|
|
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(
|
|
4082
|
-
console.log(
|
|
4083
|
-
if (!ctx.headless) console.log(
|
|
4084
|
-
else console.log(
|
|
4085
|
-
console.log(
|
|
4086
|
-
console.log(
|
|
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
|
-
|
|
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
|
|
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
|
|
4475
|
-
var publishCommand = new
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
4839
|
+
console.log(pc24.dim("building app..."));
|
|
4498
4840
|
const ok = await runBuild(absPath);
|
|
4499
4841
|
if (!ok) {
|
|
4500
|
-
console.error(
|
|
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(
|
|
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(
|
|
4853
|
+
console.log(pc24.dim(` bundle: ${(bytes.length / 1024).toFixed(1)} KiB`));
|
|
4512
4854
|
} else {
|
|
4513
|
-
console.log(
|
|
4855
|
+
console.log(pc24.dim("no frontend bundle to upload (headless app or no dist/public)"));
|
|
4514
4856
|
}
|
|
4515
|
-
console.log(
|
|
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(
|
|
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(
|
|
4875
|
+
console.error(pc24.red(`upload rejected: ${data.error ?? "unknown"}`));
|
|
4534
4876
|
process.exitCode = 1;
|
|
4535
4877
|
return;
|
|
4536
4878
|
}
|
|
4537
|
-
console.log(
|
|
4879
|
+
console.log(pc24.green("\u2713"), data.message ?? "uploaded");
|
|
4538
4880
|
if (data.install) {
|
|
4539
|
-
console.log(
|
|
4540
|
-
console.log(
|
|
4541
|
-
console.log(
|
|
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(
|
|
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
|
|
4619
|
-
import
|
|
4620
|
-
var reviewCommand = new
|
|
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(
|
|
4974
|
+
console.log(pc25.dim("queue empty"));
|
|
4633
4975
|
return;
|
|
4634
4976
|
}
|
|
4635
4977
|
for (const i of data.pending) {
|
|
4636
|
-
console.log(
|
|
4637
|
-
console.log(
|
|
4638
|
-
console.log(
|
|
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(
|
|
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(
|
|
4662
|
-
if (data.install?.reviewNotes) console.log(
|
|
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(
|
|
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.
|
|
4671
|
-
var program = new
|
|
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 ${
|
|
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
|
-
|
|
5083
|
+
pc26.red(`error: unknown command '${first}'`) + `
|
|
4741
5084
|
|
|
4742
|
-
App commands go through ${
|
|
5085
|
+
App commands go through ${pc26.bold("bot run")}. Did you mean:
|
|
4743
5086
|
|
|
4744
|
-
${
|
|
5087
|
+
${pc26.cyan(suggested)}
|
|
4745
5088
|
|
|
4746
5089
|
${topLevelHint}`
|
|
4747
5090
|
);
|
|
4748
5091
|
process.exit(1);
|
|
4749
5092
|
}
|
|
4750
5093
|
console.error(
|
|
4751
|
-
|
|
5094
|
+
pc26.red(`error: unknown command '${first}'`) + `
|
|
4752
5095
|
|
|
4753
5096
|
If '${first}' is an app name, invoke one of its commands with:
|
|
4754
|
-
${
|
|
4755
|
-
${
|
|
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}`
|