@botpress/adk-cli
Version:
Command-line interface for the Botpress Agent Development Kit (ADK)
221 lines (218 loc) • 7.42 kB
JavaScript
// @bun
import {
ensureJsonOnlyFormat
} from "./chunk-7sfagm12.js";
import {
isAdkDevConsolePort,
readConsolePort
} from "./chunk-sgj6770p.js";
import {
createCliLogger
} from "./chunk-gzwt1qdr.js";
import {
AdkError
} from "./chunk-p0hjqn4r.js";
// src/commands/adk-ps.ts
var FETCH_TIMEOUT_MS = 2000;
async function fetchProcesses(port) {
try {
const res = await fetch(`http://localhost:${port}/api/processes`, {
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
});
if (res.ok) {
const body = await res.json();
if (body && Array.isArray(body.agents)) {
return body;
}
} else {
await res.body?.cancel();
}
} catch {}
try {
const res = await fetch(`http://localhost:${port}/api/agents`, {
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
});
if (!res.ok) {
await res.body?.cancel();
return null;
}
const body = await res.json();
if (!body || !Array.isArray(body.agents))
return null;
return {
console: { pid: 0, port, url: `http://localhost:${port}`, mode: "unknown" },
agents: body.agents
};
} catch {
return null;
}
}
function formatDuration(ms) {
const seconds = Math.floor(ms / 1000);
if (seconds < 60)
return `${seconds}s`;
const minutes = Math.floor(seconds / 60);
if (minutes < 60)
return `${minutes}m`;
const hours = Math.floor(minutes / 60);
return `${hours}h${minutes % 60}m`;
}
function formatUptime(startedAt) {
if (!startedAt)
return "unknown";
return formatDuration(Date.now() - startedAt);
}
var DASH = "-";
function renderColumns(headers, rows) {
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
const lastIdx = headers.length - 1;
const formatRow = (cells) => cells.map((c, i) => i === lastIdx ? c : c.padEnd(widths[i])).join(" ").trimEnd();
return [formatRow(headers), ...rows.map(formatRow)];
}
function pidCell(value, isCloud) {
if (isCloud)
return DASH;
return value != null && value > 0 ? String(value) : "unknown";
}
function buildAgentRow(agent, wide) {
const isCloud = agent.consoleMode === "cloud";
const statusParts = [agent.status];
if (isCloud)
statusParts.push("cloud");
if (agent.outdated)
statusParts.push("outdated");
const status = statusParts.join(", ");
const adkPid = pidCell(agent.process?.adkPid, isCloud);
const uptime = isCloud ? DASH : formatUptime(agent.process?.startedAt);
if (wide) {
const runtime = agent.runtimeVersion && agent.runtimeVersion !== "unknown" ? agent.runtimeVersion : DASH;
const backend = isCloud ? DASH : String(agent.backendPort);
const bot = isCloud ? DASH : String(agent.botPort);
const botPid = pidCell(agent.process?.botPid, isCloud);
return [agent.name, status, runtime, backend, bot, adkPid, botPid, uptime, agent.agentPath];
}
const ports = isCloud ? DASH : `be:${agent.backendPort} bot:${agent.botPort}`;
return [agent.name, status, ports, adkPid, uptime];
}
function renderTable(data, opts = {}) {
const lines = [];
const wide = !!opts.wide;
const localAgents = data.agents.filter((a) => a.consoleMode !== "cloud");
const cloudAgents = data.agents.filter((a) => a.consoleMode === "cloud");
const includeCloud = !!opts.cloud;
const visibleAgents = includeCloud ? [...localAgents, ...cloudAgents] : localAgents;
const modeSuffix = data.console.mode && data.console.mode !== "unknown" ? ` (${data.console.mode})` : "";
const consolePidStr = data.console.pid > 0 ? String(data.console.pid) : "unknown";
lines.push(`DevConsole ${data.console.url}${modeSuffix} pid ${consolePidStr}`);
lines.push("");
const cloudSummary = includeCloud ? `, ${cloudAgents.length} cloud` : "";
lines.push(`Agents (${localAgents.length} local${cloudSummary})`);
lines.push("");
if (visibleAgents.length === 0) {
lines.push(" No agents connected. Start one with `adk dev`.");
} else {
const headers = wide ? ["NAME", "STATUS", "RUNTIME", "BACKEND", "BOT", "ADK PID", "BOT PID", "UPTIME", "PATH"] : ["NAME", "STATUS", "PORTS", "PID", "UPTIME"];
const rows = visibleAgents.map((a) => buildAgentRow(a, wide));
lines.push(...renderColumns(headers, rows).map((l) => " " + l));
}
if (!includeCloud && cloudAgents.length > 0) {
lines.push("");
lines.push(` (${cloudAgents.length} cloud selection${cloudAgents.length === 1 ? "" : "s"} not shown \u2014 pass --cloud to include)`);
}
if (!wide && localAgents.length > 0) {
lines.push("");
lines.push(" (pass --wide for runtime version, both PIDs, and agent path)");
}
return lines.join(`
`);
}
async function adkPs(options = {}) {
ensureJsonOnlyFormat(options.format);
const logger = createCliLogger({ format: options.format });
if (options.watch && options.format === "json") {
logger.fatal(new AdkError({
code: "INVALID_FLAGS",
message: "--watch is not supported with --format json. Use text mode for watch output.",
expected: true
}));
}
const port = readConsolePort();
if (port === null) {
if (options.format === "json") {
logger.info("No DevConsole running").result({ console: null, agents: [] });
return;
}
logger.info("No ADK processes are running. Start one with `adk dev` or `adk dashboard`.");
return;
}
if (!await isAdkDevConsolePort(port)) {
if (options.format === "json") {
logger.info(`Stale console.port (${port})`).result({ console: null, agents: [] });
return;
}
logger.info(`No ADK processes are running.
` + `(~/.adk/console.port points at port ${port} but nothing is listening \u2014 run \`adk kill --all\` to clear stale state.)`);
return;
}
const render = async () => {
let data;
try {
data = await fetchProcesses(port);
} catch (err) {
if (options.format === "json") {
logger.fatal(new AdkError({
code: "DEVCONSOLE_UNREACHABLE",
message: `Failed to reach DevConsole: ${err instanceof Error ? err.message : String(err)}`,
expected: true
}));
} else {
throw err;
}
return;
}
if (!data) {
if (options.format === "json") {
logger.info(`Failed to fetch processes from port ${port}`).result({ console: null, agents: [] });
return;
}
logger.fatal(new AdkError({
code: "DEVCONSOLE_UNREACHABLE",
message: `Failed to fetch process list from DevConsole at port ${port}`,
expected: true
}));
return;
}
if (options.format === "json") {
logger.info(`DevConsole at port ${port}`).result(data);
return;
}
const table = renderTable(data, options);
logger.info(table).result(data);
};
if (!options.watch) {
await render();
return;
}
const intervalSeconds = typeof options.watch === "number" ? Math.max(1, options.watch) : 2;
process.stdout.write("\x1B[2J\x1B[0f");
await render();
let rendering = false;
const timer = setInterval(async () => {
if (rendering)
return;
rendering = true;
try {
process.stdout.write("\x1B[2J\x1B[0f");
await render();
} finally {
rendering = false;
}
}, intervalSeconds * 1000);
await new Promise((resolve) => {
process.once("SIGINT", () => {
clearInterval(timer);
resolve();
});
});
}
export { fetchProcesses, renderTable, adkPs };