@agent-commons/cli 0.1.14 → 0.1.17
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.js +434 -39
- package/package.json +7 -5
package/dist/bin.js
CHANGED
|
@@ -106,7 +106,7 @@ var sym = {
|
|
|
106
106
|
bullet: import_chalk.default.dim("\u2022"),
|
|
107
107
|
dot: import_chalk.default.dim("\xB7")
|
|
108
108
|
};
|
|
109
|
-
function banner(version = "0.1.
|
|
109
|
+
function banner(version = "0.1.17") {
|
|
110
110
|
const line = import_chalk.default.cyan(" \u2500".padEnd(2) + "\u2500".repeat(44));
|
|
111
111
|
console.log("");
|
|
112
112
|
console.log(line);
|
|
@@ -515,7 +515,7 @@ ${sym.ok} Agent created`);
|
|
|
515
515
|
process.exit(1);
|
|
516
516
|
}
|
|
517
517
|
});
|
|
518
|
-
const autonomy = cmd.command("autonomy").description("Manage agent heartbeat
|
|
518
|
+
const autonomy = cmd.command("autonomy").description("Manage agent heartbeat");
|
|
519
519
|
autonomy.command("status").description("Show autonomy status for an agent").requiredOption("--agent <agentId>", "Agent ID").option("--json", "Output as JSON").action(async (opts) => {
|
|
520
520
|
const client = makeClient();
|
|
521
521
|
const spinner = spin("Fetching autonomy status\u2026");
|
|
@@ -525,7 +525,7 @@ ${sym.ok} Agent created`);
|
|
|
525
525
|
const s = res.data;
|
|
526
526
|
if (opts.json) return jsonOut(s);
|
|
527
527
|
console.log(`
|
|
528
|
-
${c.bold("
|
|
528
|
+
${c.bold("Heartbeat Status")}`);
|
|
529
529
|
detail([
|
|
530
530
|
["Enabled", s.enabled ? c.bold("yes") : "no"],
|
|
531
531
|
["Interval", s.intervalSec ? `${s.intervalSec}s` : "n/a"],
|
|
@@ -539,7 +539,7 @@ ${c.bold("Autonomy Status")}`);
|
|
|
539
539
|
process.exit(1);
|
|
540
540
|
}
|
|
541
541
|
});
|
|
542
|
-
autonomy.command("enable").description("Enable
|
|
542
|
+
autonomy.command("enable").description("Enable heartbeat for an agent").requiredOption("--agent <agentId>", "Agent ID").option("--interval <seconds>", "Heartbeat interval in seconds (min 30)", "300").action(async (opts) => {
|
|
543
543
|
const client = makeClient();
|
|
544
544
|
const spinner = spin("Enabling autonomy\u2026");
|
|
545
545
|
try {
|
|
@@ -557,7 +557,7 @@ ${sym.ok} Autonomy enabled for agent ${c.id(opts.agent)}`);
|
|
|
557
557
|
process.exit(1);
|
|
558
558
|
}
|
|
559
559
|
});
|
|
560
|
-
autonomy.command("disable").description("Disable
|
|
560
|
+
autonomy.command("disable").description("Disable heartbeat for an agent").requiredOption("--agent <agentId>", "Agent ID").action(async (opts) => {
|
|
561
561
|
const client = makeClient();
|
|
562
562
|
const spinner = spin("Disabling autonomy\u2026");
|
|
563
563
|
try {
|
|
@@ -571,7 +571,7 @@ ${sym.ok} Autonomy disabled for agent ${c.id(opts.agent)}`);
|
|
|
571
571
|
process.exit(1);
|
|
572
572
|
}
|
|
573
573
|
});
|
|
574
|
-
autonomy.command("trigger").description("Trigger a single heartbeat
|
|
574
|
+
autonomy.command("trigger").description("Trigger a single heartbeat immediately").requiredOption("--agent <agentId>", "Agent ID").action(async (opts) => {
|
|
575
575
|
const client = makeClient();
|
|
576
576
|
const spinner = spin("Triggering heartbeat\u2026");
|
|
577
577
|
try {
|
|
@@ -1257,6 +1257,13 @@ var import_fs3 = require("fs");
|
|
|
1257
1257
|
var import_path3 = require("path");
|
|
1258
1258
|
var import_child_process2 = require("child_process");
|
|
1259
1259
|
var readline2 = __toESM(require("readline"));
|
|
1260
|
+
var pdfParse = require("pdf-parse/lib/pdf-parse.js");
|
|
1261
|
+
var managedProcesses = /* @__PURE__ */ new Map();
|
|
1262
|
+
function capBuffer(existing, chunk, maxBytes) {
|
|
1263
|
+
const joined = existing + chunk;
|
|
1264
|
+
if (joined.length <= maxBytes) return joined;
|
|
1265
|
+
return "\u2026(truncated)\n" + joined.slice(-(maxBytes - 20));
|
|
1266
|
+
}
|
|
1260
1267
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", "__pycache__", ".next", "dist", "build", ".DS_Store"]);
|
|
1261
1268
|
function buildDirSnapshot(dir, maxDepth = 2) {
|
|
1262
1269
|
const lines = [`${dir}/`];
|
|
@@ -1335,27 +1342,59 @@ ${fileSection}
|
|
|
1335
1342
|
|
|
1336
1343
|
| Tool | What it does |
|
|
1337
1344
|
|------|-------------|
|
|
1338
|
-
| \`cli_list_directory\` | List files and folders at a path
|
|
1339
|
-
| \`cli_read_file\` | Read
|
|
1345
|
+
| \`cli_list_directory\` | List files and folders at a path |
|
|
1346
|
+
| \`cli_read_file\` | Read a file (PDF and Word docs are extracted to text) |
|
|
1340
1347
|
| \`cli_write_file\` | Write or overwrite a file (user confirmation required) |
|
|
1341
|
-
| \`cli_search_files\` | Find files matching a pattern
|
|
1342
|
-
| \`cli_run_command\` | Run a
|
|
1348
|
+
| \`cli_search_files\` | Find files matching a pattern |
|
|
1349
|
+
| \`cli_run_command\` | Run a short command and return its output (user confirmation required) |
|
|
1350
|
+
| \`cli_start_process\` | Start a long-running command in the background; returns a processId immediately |
|
|
1351
|
+
| \`cli_wait_for_process\` | Block up to N seconds for a background process, then return current output |
|
|
1352
|
+
| \`cli_process_status\` | Instant non-blocking check on a background process |
|
|
1353
|
+
| \`cli_kill_process\` | Kill a running background process |
|
|
1354
|
+
| \`cli_list_processes\` | List all background processes started this session |
|
|
1355
|
+
|
|
1356
|
+
### Choosing between run_command and start_process
|
|
1357
|
+
|
|
1358
|
+
| Situation | Use |
|
|
1359
|
+
|-----------|-----|
|
|
1360
|
+
| Command finishes in under ~30s | \`cli_run_command\` |
|
|
1361
|
+
| Command may take minutes (npm install, build, scaffold) | \`cli_start_process\` + \`cli_wait_for_process\` |
|
|
1362
|
+
| Command needs live stdin (e.g. a REPL) | \`cli_run_command\` with \`"interactive": true\` |
|
|
1343
1363
|
|
|
1344
|
-
###
|
|
1364
|
+
### run_command options
|
|
1365
|
+
- \`timeout_seconds\` (default 120, max 300) \u2014 kill the process after N seconds
|
|
1366
|
+
- \`interactive\` (boolean) \u2014 connects the user's terminal stdin for commands that need input
|
|
1345
1367
|
|
|
1346
|
-
|
|
1368
|
+
### start_process + wait_for_process pattern
|
|
1347
1369
|
|
|
1348
|
-
|
|
1370
|
+
For long commands like \`npx create-next-app@latest my-app --yes\`:
|
|
1349
1371
|
|
|
1350
|
-
Call \`
|
|
1372
|
+
1. Call \`cli_start_process\` \u2014 returns \`{processId, status: "running"}\` immediately. Tell the user it has started.
|
|
1373
|
+
2. Call \`cli_wait_for_process\` with \`{"processId": "...", "wait_seconds": 60}\` \u2014 blocks up to 60s then returns current stdout/status. Report progress to the user.
|
|
1374
|
+
3. Repeat step 2 until \`status\` is \`"done"\` or \`"error"\`.
|
|
1375
|
+
4. Report the final output to the user.
|
|
1351
1376
|
|
|
1352
|
-
|
|
1377
|
+
Never hold the user in silence. Between each \`cli_wait_for_process\` call, tell them what you saw so far.
|
|
1353
1378
|
|
|
1354
|
-
|
|
1379
|
+
### Example \u2014 scaffolding a Next.js project
|
|
1380
|
+
|
|
1381
|
+
\`\`\`
|
|
1382
|
+
cli_start_process: {"command": "npx", "args": ["create-next-app@latest", "my-app", "--yes"], "cwd": "Desktop"}
|
|
1383
|
+
\u2192 {processId: "proc_1a2b", status: "running"}
|
|
1355
1384
|
|
|
1356
|
-
|
|
1385
|
+
Tell user: "Started! Installing dependencies, this takes a minute or two. Checking in 60s\u2026"
|
|
1357
1386
|
|
|
1358
|
-
|
|
1387
|
+
cli_wait_for_process: {"processId": "proc_1a2b", "wait_seconds": 60}
|
|
1388
|
+
\u2192 {status: "running", elapsedSec: 60, stdout: "Creating project...
|
|
1389
|
+
Installing packages\u2026"}
|
|
1390
|
+
|
|
1391
|
+
Tell user: "Still installing \u2014 here's output so far: [stdout]. Checking again\u2026"
|
|
1392
|
+
|
|
1393
|
+
cli_wait_for_process: {"processId": "proc_1a2b", "wait_seconds": 60}
|
|
1394
|
+
\u2192 {status: "done", exitCode: 0, elapsedSec: 93, stdout: "Success! Created my-app"}
|
|
1395
|
+
|
|
1396
|
+
Tell user: "Done! Project created in Desktop/my-app"
|
|
1397
|
+
\`\`\`
|
|
1359
1398
|
`;
|
|
1360
1399
|
}
|
|
1361
1400
|
var TOOL_CALL_RE = /```tool\s*\n([\s\S]*?)\n```/;
|
|
@@ -1421,6 +1460,76 @@ async function confirm(message, config, permissionKey) {
|
|
|
1421
1460
|
});
|
|
1422
1461
|
});
|
|
1423
1462
|
}
|
|
1463
|
+
var OFFICE_EXTS = /* @__PURE__ */ new Set([".docx", ".doc", ".rtf", ".odt", ".pages"]);
|
|
1464
|
+
var PDF_EXTS = /* @__PURE__ */ new Set([".pdf"]);
|
|
1465
|
+
var UNREADABLE_BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
1466
|
+
".png",
|
|
1467
|
+
".jpg",
|
|
1468
|
+
".jpeg",
|
|
1469
|
+
".gif",
|
|
1470
|
+
".bmp",
|
|
1471
|
+
".ico",
|
|
1472
|
+
".webp",
|
|
1473
|
+
".tiff",
|
|
1474
|
+
".mp3",
|
|
1475
|
+
".mp4",
|
|
1476
|
+
".wav",
|
|
1477
|
+
".aac",
|
|
1478
|
+
".ogg",
|
|
1479
|
+
".flac",
|
|
1480
|
+
".zip",
|
|
1481
|
+
".tar",
|
|
1482
|
+
".gz",
|
|
1483
|
+
".bz2",
|
|
1484
|
+
".7z",
|
|
1485
|
+
".rar",
|
|
1486
|
+
".exe",
|
|
1487
|
+
".dll",
|
|
1488
|
+
".so",
|
|
1489
|
+
".dylib",
|
|
1490
|
+
".bin",
|
|
1491
|
+
".psd",
|
|
1492
|
+
".ai",
|
|
1493
|
+
".sketch",
|
|
1494
|
+
".figma",
|
|
1495
|
+
".xlsx",
|
|
1496
|
+
".xls",
|
|
1497
|
+
".pptx",
|
|
1498
|
+
".ppt"
|
|
1499
|
+
]);
|
|
1500
|
+
function extractViaCommand(cmd, cmdArgs) {
|
|
1501
|
+
return new Promise((res) => {
|
|
1502
|
+
(0, import_child_process2.execFile)(cmd, cmdArgs, { timeout: 3e4, maxBuffer: 2 * 1024 * 1024 }, (err, stdout) => {
|
|
1503
|
+
if (err) res("");
|
|
1504
|
+
else res(stdout.trim());
|
|
1505
|
+
});
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
async function extractPdfText(abs) {
|
|
1509
|
+
try {
|
|
1510
|
+
const buffer = (0, import_fs3.readFileSync)(abs);
|
|
1511
|
+
const data = await pdfParse(buffer);
|
|
1512
|
+
const text2 = data.text?.trim();
|
|
1513
|
+
if (text2) {
|
|
1514
|
+
const MAX_CHARS = 15e4;
|
|
1515
|
+
if (text2.length > MAX_CHARS) {
|
|
1516
|
+
return text2.slice(0, MAX_CHARS) + `
|
|
1517
|
+
|
|
1518
|
+
[\u2026truncated \u2014 showing first ${MAX_CHARS.toLocaleString()} characters of ${text2.length.toLocaleString()} total]`;
|
|
1519
|
+
}
|
|
1520
|
+
return text2;
|
|
1521
|
+
}
|
|
1522
|
+
} catch {
|
|
1523
|
+
}
|
|
1524
|
+
const text = await extractViaCommand("pdftotext", [abs, "-"]);
|
|
1525
|
+
if (text) return text;
|
|
1526
|
+
return `[Cannot extract PDF text: the file may be scanned/image-only or password-protected]`;
|
|
1527
|
+
}
|
|
1528
|
+
async function extractOfficeText(abs, ext) {
|
|
1529
|
+
const text = await extractViaCommand("textutil", ["-stdout", "-cat", "txt", abs]);
|
|
1530
|
+
if (text) return text;
|
|
1531
|
+
return `[Cannot extract ${ext} text: textutil failed or is unavailable on this system]`;
|
|
1532
|
+
}
|
|
1424
1533
|
async function toolReadFile(args, cfg) {
|
|
1425
1534
|
const { path: userPath } = args;
|
|
1426
1535
|
if (!userPath) throw new Error('read_file requires a "path" argument');
|
|
@@ -1429,6 +1538,18 @@ async function toolReadFile(args, cfg) {
|
|
|
1429
1538
|
if (!(0, import_fs3.existsSync)(abs)) throw new Error(`File not found: ${userPath}`);
|
|
1430
1539
|
const stat = (0, import_fs3.statSync)(abs);
|
|
1431
1540
|
if (stat.isDirectory()) throw new Error(`"${userPath}" is a directory, not a file`);
|
|
1541
|
+
const ext = (0, import_path3.extname)(abs).toLowerCase();
|
|
1542
|
+
if (PDF_EXTS.has(ext)) {
|
|
1543
|
+
if (stat.size > 5e7) throw new Error(`PDF too large to read (${Math.round(stat.size / 1e6)} MB). Max 50 MB.`);
|
|
1544
|
+
return extractPdfText(abs);
|
|
1545
|
+
}
|
|
1546
|
+
if (OFFICE_EXTS.has(ext)) {
|
|
1547
|
+
if (stat.size > 2e7) throw new Error(`Document too large to read (${Math.round(stat.size / 1e6)} MB). Max 20 MB.`);
|
|
1548
|
+
return extractOfficeText(abs, ext);
|
|
1549
|
+
}
|
|
1550
|
+
if (UNREADABLE_BINARY_EXTS.has(ext)) {
|
|
1551
|
+
throw new Error(`Cannot read binary file "${userPath}" (${ext} format). Only text, PDF, and Office documents are supported.`);
|
|
1552
|
+
}
|
|
1432
1553
|
if (stat.size > 5e5) throw new Error(`File too large to read (${Math.round(stat.size / 1024)} KB). Max 500 KB.`);
|
|
1433
1554
|
return (0, import_fs3.readFileSync)(abs, "utf8");
|
|
1434
1555
|
}
|
|
@@ -1487,11 +1608,12 @@ async function toolSearchFiles(args, cfg) {
|
|
|
1487
1608
|
return results.length ? results.join("\n") : "No files found matching: " + pattern;
|
|
1488
1609
|
}
|
|
1489
1610
|
async function toolRunCommand(args, cfg) {
|
|
1490
|
-
const { command, args: cmdArgs = [], cwd } = args;
|
|
1611
|
+
const { command, args: cmdArgs = [], cwd, timeout_seconds, interactive } = args;
|
|
1491
1612
|
if (!command || typeof command !== "string") throw new Error('run_command requires a "command" string');
|
|
1492
1613
|
if (!Array.isArray(cmdArgs)) throw new Error('"args" must be an array of strings');
|
|
1493
1614
|
const workDir = cwd ? safePath(cfg.rootDir, cwd) : cfg.rootDir;
|
|
1494
1615
|
const preview = [command, ...cmdArgs].join(" ");
|
|
1616
|
+
const timeoutMs = Math.min((typeof timeout_seconds === "number" ? timeout_seconds : 120) * 1e3, 3e5);
|
|
1495
1617
|
const ok = await confirm(
|
|
1496
1618
|
`Agent wants to run: \x1B[1m${preview}\x1B[0m
|
|
1497
1619
|
\x1B[2min: ${workDir}\x1B[0m`,
|
|
@@ -1499,14 +1621,142 @@ async function toolRunCommand(args, cfg) {
|
|
|
1499
1621
|
"run_command"
|
|
1500
1622
|
);
|
|
1501
1623
|
if (!ok) return "User denied command execution.";
|
|
1624
|
+
if (interactive) {
|
|
1625
|
+
return new Promise((resolve2) => {
|
|
1626
|
+
const child = (0, import_child_process2.spawn)(command, cmdArgs.map(String), { cwd: workDir, stdio: "inherit" });
|
|
1627
|
+
const timer = setTimeout(() => {
|
|
1628
|
+
child.kill();
|
|
1629
|
+
resolve2(`(command timed out after ${timeoutMs / 1e3}s)`);
|
|
1630
|
+
}, timeoutMs);
|
|
1631
|
+
child.on("close", (code) => {
|
|
1632
|
+
clearTimeout(timer);
|
|
1633
|
+
resolve2(`(command exited with code ${code ?? "unknown"})`);
|
|
1634
|
+
});
|
|
1635
|
+
child.on("error", (err) => {
|
|
1636
|
+
clearTimeout(timer);
|
|
1637
|
+
resolve2(`Error: ${err.message}`);
|
|
1638
|
+
});
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1502
1641
|
return new Promise((resolve2) => {
|
|
1503
|
-
(0, import_child_process2.execFile)(command, cmdArgs.map(String), { cwd: workDir, timeout:
|
|
1642
|
+
(0, import_child_process2.execFile)(command, cmdArgs.map(String), { cwd: workDir, timeout: timeoutMs, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
1504
1643
|
const out = [stdout, stderr].filter(Boolean).join("\n--- stderr ---\n");
|
|
1505
1644
|
if (err && !out) return resolve2(`Error: ${err.message}`);
|
|
1506
1645
|
resolve2(out || "(no output)");
|
|
1507
1646
|
});
|
|
1508
1647
|
});
|
|
1509
1648
|
}
|
|
1649
|
+
async function toolStartProcess(args, cfg) {
|
|
1650
|
+
const { command, args: cmdArgs = [], cwd } = args;
|
|
1651
|
+
if (!command || typeof command !== "string") throw new Error('start_process requires a "command" string');
|
|
1652
|
+
if (!Array.isArray(cmdArgs)) throw new Error('"args" must be an array of strings');
|
|
1653
|
+
const workDir = cwd ? safePath(cfg.rootDir, cwd) : cfg.rootDir;
|
|
1654
|
+
const preview = [command, ...cmdArgs].join(" ");
|
|
1655
|
+
const ok = await confirm(
|
|
1656
|
+
`Agent wants to start background process: \x1B[1m${preview}\x1B[0m
|
|
1657
|
+
\x1B[2min: ${workDir}\x1B[0m`,
|
|
1658
|
+
cfg,
|
|
1659
|
+
"start_process"
|
|
1660
|
+
);
|
|
1661
|
+
if (!ok) return JSON.stringify({ error: "User denied process start." });
|
|
1662
|
+
const id = `proc_${Date.now().toString(36)}`;
|
|
1663
|
+
const child = (0, import_child_process2.spawn)(command, cmdArgs.map(String), {
|
|
1664
|
+
cwd: workDir,
|
|
1665
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1666
|
+
detached: false
|
|
1667
|
+
});
|
|
1668
|
+
const proc = {
|
|
1669
|
+
id,
|
|
1670
|
+
command: preview,
|
|
1671
|
+
status: "running",
|
|
1672
|
+
exitCode: null,
|
|
1673
|
+
stdout: "",
|
|
1674
|
+
stderr: "",
|
|
1675
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
1676
|
+
endedAt: null,
|
|
1677
|
+
child
|
|
1678
|
+
};
|
|
1679
|
+
child.stdout?.on("data", (chunk) => {
|
|
1680
|
+
proc.stdout = capBuffer(proc.stdout, chunk.toString(), 2e5);
|
|
1681
|
+
});
|
|
1682
|
+
child.stderr?.on("data", (chunk) => {
|
|
1683
|
+
proc.stderr = capBuffer(proc.stderr, chunk.toString(), 5e4);
|
|
1684
|
+
});
|
|
1685
|
+
child.on("close", (code) => {
|
|
1686
|
+
proc.status = code === 0 ? "done" : "error";
|
|
1687
|
+
proc.exitCode = code;
|
|
1688
|
+
proc.endedAt = /* @__PURE__ */ new Date();
|
|
1689
|
+
});
|
|
1690
|
+
child.on("error", (err) => {
|
|
1691
|
+
proc.status = "error";
|
|
1692
|
+
proc.endedAt = /* @__PURE__ */ new Date();
|
|
1693
|
+
proc.stderr = capBuffer(proc.stderr, `
|
|
1694
|
+
Spawn error: ${err.message}`, 5e4);
|
|
1695
|
+
});
|
|
1696
|
+
managedProcesses.set(id, proc);
|
|
1697
|
+
cfg.appendLog({ type: "process_start", processId: id, command: preview, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1698
|
+
return JSON.stringify({ processId: id, status: "running", command: preview });
|
|
1699
|
+
}
|
|
1700
|
+
function processSnapshot(proc) {
|
|
1701
|
+
const elapsedSec = Math.round((Date.now() - proc.startedAt.getTime()) / 1e3);
|
|
1702
|
+
const recentStdout = proc.stdout.length > 4e3 ? "\u2026(earlier output truncated)\n" + proc.stdout.slice(-4e3) : proc.stdout;
|
|
1703
|
+
return JSON.stringify({
|
|
1704
|
+
processId: proc.id,
|
|
1705
|
+
command: proc.command,
|
|
1706
|
+
status: proc.status,
|
|
1707
|
+
exitCode: proc.exitCode,
|
|
1708
|
+
elapsedSec,
|
|
1709
|
+
stdout: recentStdout || "(no output yet)",
|
|
1710
|
+
stderr: proc.stderr.slice(-1e3) || void 0
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
async function toolProcessStatus(args, _cfg) {
|
|
1714
|
+
const { processId } = args;
|
|
1715
|
+
if (!processId) throw new Error('process_status requires a "processId" argument');
|
|
1716
|
+
const proc = managedProcesses.get(processId);
|
|
1717
|
+
if (!proc) return JSON.stringify({ error: `No process found with id "${processId}"` });
|
|
1718
|
+
return processSnapshot(proc);
|
|
1719
|
+
}
|
|
1720
|
+
async function toolWaitForProcess(args, _cfg) {
|
|
1721
|
+
const { processId, wait_seconds = 60 } = args;
|
|
1722
|
+
if (!processId) throw new Error('wait_for_process requires a "processId" argument');
|
|
1723
|
+
const proc = managedProcesses.get(processId);
|
|
1724
|
+
if (!proc) return JSON.stringify({ error: `No process found with id "${processId}"` });
|
|
1725
|
+
if (proc.status !== "running") return processSnapshot(proc);
|
|
1726
|
+
const maxWait = Math.min((typeof wait_seconds === "number" ? wait_seconds : 60) * 1e3, 12e4);
|
|
1727
|
+
const deadline = Date.now() + maxWait;
|
|
1728
|
+
await new Promise((resolve2) => {
|
|
1729
|
+
const tick = setInterval(() => {
|
|
1730
|
+
if (proc.status !== "running" || Date.now() >= deadline) {
|
|
1731
|
+
clearInterval(tick);
|
|
1732
|
+
resolve2();
|
|
1733
|
+
}
|
|
1734
|
+
}, 500);
|
|
1735
|
+
});
|
|
1736
|
+
return processSnapshot(proc);
|
|
1737
|
+
}
|
|
1738
|
+
async function toolKillProcess(args, cfg) {
|
|
1739
|
+
const { processId } = args;
|
|
1740
|
+
if (!processId) throw new Error('kill_process requires a "processId" argument');
|
|
1741
|
+
const proc = managedProcesses.get(processId);
|
|
1742
|
+
if (!proc) return JSON.stringify({ error: `No process found with id "${processId}"` });
|
|
1743
|
+
if (proc.status !== "running") return JSON.stringify({ error: `Process "${processId}" is not running (status: ${proc.status})` });
|
|
1744
|
+
proc.child.kill("SIGTERM");
|
|
1745
|
+
proc.status = "killed";
|
|
1746
|
+
proc.endedAt = /* @__PURE__ */ new Date();
|
|
1747
|
+
cfg.appendLog({ type: "process_killed", processId, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1748
|
+
return JSON.stringify({ processId, status: "killed" });
|
|
1749
|
+
}
|
|
1750
|
+
async function toolListProcesses(_args, _cfg) {
|
|
1751
|
+
if (managedProcesses.size === 0) return JSON.stringify([]);
|
|
1752
|
+
const list = [...managedProcesses.values()].map((p) => ({
|
|
1753
|
+
processId: p.id,
|
|
1754
|
+
command: p.command,
|
|
1755
|
+
status: p.status,
|
|
1756
|
+
elapsedSec: Math.round((Date.now() - p.startedAt.getTime()) / 1e3)
|
|
1757
|
+
}));
|
|
1758
|
+
return JSON.stringify(list);
|
|
1759
|
+
}
|
|
1510
1760
|
async function runLocalTool(call, cfg) {
|
|
1511
1761
|
const { tool, args } = call;
|
|
1512
1762
|
cfg.appendLog({
|
|
@@ -1533,8 +1783,23 @@ async function runLocalTool(call, cfg) {
|
|
|
1533
1783
|
case "run_command":
|
|
1534
1784
|
result = await toolRunCommand(args, cfg);
|
|
1535
1785
|
break;
|
|
1786
|
+
case "start_process":
|
|
1787
|
+
result = await toolStartProcess(args, cfg);
|
|
1788
|
+
break;
|
|
1789
|
+
case "process_status":
|
|
1790
|
+
result = await toolProcessStatus(args, cfg);
|
|
1791
|
+
break;
|
|
1792
|
+
case "wait_for_process":
|
|
1793
|
+
result = await toolWaitForProcess(args, cfg);
|
|
1794
|
+
break;
|
|
1795
|
+
case "kill_process":
|
|
1796
|
+
result = await toolKillProcess(args, cfg);
|
|
1797
|
+
break;
|
|
1798
|
+
case "list_processes":
|
|
1799
|
+
result = await toolListProcesses(args, cfg);
|
|
1800
|
+
break;
|
|
1536
1801
|
default:
|
|
1537
|
-
result = `Unknown tool: "${tool}". Available: read_file, write_file, list_directory, search_files, run_command`;
|
|
1802
|
+
result = `Unknown tool: "${tool}". Available: read_file, write_file, list_directory, search_files, run_command, start_process, wait_for_process, process_status, kill_process, list_processes`;
|
|
1538
1803
|
}
|
|
1539
1804
|
} catch (err) {
|
|
1540
1805
|
result = `Error: ${err?.message ?? String(err)}`;
|
|
@@ -1775,12 +2040,13 @@ ${content}
|
|
|
1775
2040
|
messages: [{ role: "user", content: userMessage }],
|
|
1776
2041
|
...cliContext && { cliContext }
|
|
1777
2042
|
};
|
|
1778
|
-
process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
|
|
1779
2043
|
if (opts.noStream) {
|
|
1780
|
-
|
|
2044
|
+
process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
|
|
2045
|
+
const spinner = spin("thinking\u2026");
|
|
1781
2046
|
try {
|
|
1782
2047
|
const result = await client.run.once(params);
|
|
1783
2048
|
spinner.stop();
|
|
2049
|
+
process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
|
|
1784
2050
|
const text = extractText(result);
|
|
1785
2051
|
console.log(text);
|
|
1786
2052
|
appendSessionLog(sessionId, {
|
|
@@ -1798,29 +2064,57 @@ ${sym.fail} ${c.error(err.message ?? String(err))}`);
|
|
|
1798
2064
|
try {
|
|
1799
2065
|
let hasOutput = false;
|
|
1800
2066
|
let agentContent = "";
|
|
2067
|
+
let toolStartMs = 0;
|
|
2068
|
+
let lastToolName = "";
|
|
2069
|
+
const thinkingSpinner = spin("thinking\u2026");
|
|
1801
2070
|
for await (const event of client.agents.stream(params)) {
|
|
1802
2071
|
if (event.type === "token") {
|
|
2072
|
+
if (thinkingSpinner.isSpinning) {
|
|
2073
|
+
thinkingSpinner.stop();
|
|
2074
|
+
process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
|
|
2075
|
+
}
|
|
1803
2076
|
const tok = event.content ?? "";
|
|
1804
2077
|
process.stdout.write(tok);
|
|
1805
2078
|
agentContent += tok;
|
|
1806
2079
|
hasOutput = true;
|
|
1807
2080
|
} else if (event.type === "cli_tool_request" && localToolsCfg) {
|
|
2081
|
+
if (thinkingSpinner.isSpinning) thinkingSpinner.stop();
|
|
1808
2082
|
const { requestId, tool: toolName, args } = event;
|
|
1809
2083
|
const displayName = String(toolName).replace("cli_", "");
|
|
2084
|
+
const argStr = toolArgSummary(displayName, args ?? {});
|
|
2085
|
+
const isWaiting = displayName === "wait_for_process";
|
|
1810
2086
|
if (hasOutput) {
|
|
1811
2087
|
process.stdout.write("\n");
|
|
1812
2088
|
hasOutput = false;
|
|
1813
2089
|
}
|
|
1814
|
-
process.stdout.write(c.dim(
|
|
2090
|
+
process.stdout.write(` ${c.dim("\u2500")} ${c.bold(displayName)}${argStr ? " " + c.dim(argStr) : ""}`);
|
|
2091
|
+
const startMs = Date.now();
|
|
2092
|
+
let elapsedSec = 0;
|
|
2093
|
+
let elapsedInterval = null;
|
|
2094
|
+
if (isWaiting) {
|
|
2095
|
+
elapsedInterval = setInterval(() => {
|
|
2096
|
+
elapsedSec++;
|
|
2097
|
+
readline3.cursorTo(process.stdout, 0);
|
|
2098
|
+
process.stdout.write(` ${c.dim("\u2500")} ${c.bold(displayName)}${argStr ? " " + c.dim(argStr) : ""} ${c.dim(elapsedSec + "s\u2026")}`);
|
|
2099
|
+
}, 1e3);
|
|
2100
|
+
}
|
|
1815
2101
|
let result;
|
|
2102
|
+
let toolOk = true;
|
|
1816
2103
|
try {
|
|
1817
|
-
|
|
1818
|
-
result = await runLocalTool({ tool: localToolName, args: args ?? {} }, localToolsCfg);
|
|
1819
|
-
process.stdout.write(c.dim(" \u2713\n"));
|
|
2104
|
+
result = await runLocalTool({ tool: displayName, args: args ?? {} }, localToolsCfg);
|
|
1820
2105
|
} catch (err) {
|
|
1821
2106
|
result = `Error: ${err?.message ?? String(err)}`;
|
|
1822
|
-
|
|
2107
|
+
toolOk = false;
|
|
1823
2108
|
}
|
|
2109
|
+
if (elapsedInterval) clearInterval(elapsedInterval);
|
|
2110
|
+
const elapsed = ((Date.now() - startMs) / 1e3).toFixed(1);
|
|
2111
|
+
const preview = toolOk ? toolResultPreview(displayName, result) : "";
|
|
2112
|
+
readline3.cursorTo(process.stdout, 0);
|
|
2113
|
+
readline3.clearLine(process.stdout, 0);
|
|
2114
|
+
const statusIcon = toolOk ? sym.ok : sym.fail;
|
|
2115
|
+
const previewPart = preview ? ` ${c.dim(preview)}` : "";
|
|
2116
|
+
process.stdout.write(` ${c.dim("\u2500")} ${c.bold(displayName)}${argStr ? " " + c.dim(argStr) : ""} ${statusIcon}${previewPart} ${c.dim("(" + elapsed + "s)")}
|
|
2117
|
+
`);
|
|
1824
2118
|
appendSessionLog(sessionId, {
|
|
1825
2119
|
type: "local_tool_result",
|
|
1826
2120
|
tool: toolName,
|
|
@@ -1840,13 +2134,20 @@ ${sym.fail} ${c.error(err.message ?? String(err))}`);
|
|
|
1840
2134
|
console.error(c.warn(`
|
|
1841
2135
|
[local] Failed to submit tool result: ${postErr?.message}`));
|
|
1842
2136
|
}
|
|
2137
|
+
} else if (event.type === "keepalive") {
|
|
1843
2138
|
} else if (event.type === "toolStart") {
|
|
1844
|
-
|
|
2139
|
+
if (thinkingSpinner.isSpinning) thinkingSpinner.stop();
|
|
2140
|
+
lastToolName = event.toolName ?? "";
|
|
2141
|
+
toolStartMs = Date.now();
|
|
1845
2142
|
if (hasOutput) process.stdout.write("\n");
|
|
1846
|
-
process.stdout.write(c.dim(
|
|
2143
|
+
process.stdout.write(` ${c.dim("\u2500")} ${c.bold(lastToolName)}`);
|
|
1847
2144
|
hasOutput = false;
|
|
1848
2145
|
} else if (event.type === "toolEnd") {
|
|
1849
|
-
|
|
2146
|
+
const elapsed = ((Date.now() - toolStartMs) / 1e3).toFixed(1);
|
|
2147
|
+
readline3.cursorTo(process.stdout, 0);
|
|
2148
|
+
readline3.clearLine(process.stdout, 0);
|
|
2149
|
+
process.stdout.write(` ${c.dim("\u2500")} ${c.bold(lastToolName)} ${sym.ok} ${c.dim("(" + elapsed + "s)")}
|
|
2150
|
+
`);
|
|
1850
2151
|
process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
|
|
1851
2152
|
hasOutput = false;
|
|
1852
2153
|
} else if (event.type === "final") {
|
|
@@ -1883,12 +2184,14 @@ ${sym.fail} ${c.error(err.message ?? String(err))}`);
|
|
|
1883
2184
|
}
|
|
1884
2185
|
break;
|
|
1885
2186
|
} else if (event.type === "error") {
|
|
2187
|
+
if (thinkingSpinner.isSpinning) thinkingSpinner.stop();
|
|
1886
2188
|
if (hasOutput) process.stdout.write("\n");
|
|
1887
2189
|
console.error(`
|
|
1888
2190
|
${sym.fail} ${c.error(event.message ?? "Stream error")}`);
|
|
1889
2191
|
break;
|
|
1890
2192
|
}
|
|
1891
2193
|
}
|
|
2194
|
+
if (thinkingSpinner.isSpinning) thinkingSpinner.stop();
|
|
1892
2195
|
process.stdout.write("\n");
|
|
1893
2196
|
if (localToolsCfg && agentContent) {
|
|
1894
2197
|
await handleLocalToolLoop(agentContent, localToolsCfg, client, agentId, sessionId, appendSessionLog);
|
|
@@ -1899,6 +2202,8 @@ ${sym.fail} ${c.error(event.message ?? "Stream error")}`);
|
|
|
1899
2202
|
}
|
|
1900
2203
|
}
|
|
1901
2204
|
console.log();
|
|
2205
|
+
readline3.cursorTo(process.stdout, 0);
|
|
2206
|
+
readline3.clearLine(process.stdout, 0);
|
|
1902
2207
|
rl.resume();
|
|
1903
2208
|
rl.prompt();
|
|
1904
2209
|
});
|
|
@@ -1922,16 +2227,25 @@ async function handleLocalToolLoop(agentText, cfg, client, agentId, sessionId, a
|
|
|
1922
2227
|
}
|
|
1923
2228
|
const toolCall = extractToolCall(agentText);
|
|
1924
2229
|
if (!toolCall) return;
|
|
1925
|
-
|
|
1926
|
-
|
|
2230
|
+
const argStr = toolArgSummary(toolCall.tool, toolCall.args ?? {});
|
|
2231
|
+
process.stdout.write(`
|
|
2232
|
+
${c.dim("\u2500")} ${c.bold(toolCall.tool)}${argStr ? " " + c.dim(argStr) : ""}`);
|
|
2233
|
+
const startMs = Date.now();
|
|
1927
2234
|
let result;
|
|
2235
|
+
let toolOk = true;
|
|
1928
2236
|
try {
|
|
1929
2237
|
result = await runLocalTool(toolCall, cfg);
|
|
1930
|
-
process.stdout.write(c.dim(" \u2713\n"));
|
|
1931
2238
|
} catch (err) {
|
|
1932
2239
|
result = `Error: ${err?.message ?? String(err)}`;
|
|
1933
|
-
|
|
2240
|
+
toolOk = false;
|
|
1934
2241
|
}
|
|
2242
|
+
const elapsed = ((Date.now() - startMs) / 1e3).toFixed(1);
|
|
2243
|
+
const preview = toolOk ? toolResultPreview(toolCall.tool, result) : "";
|
|
2244
|
+
readline3.cursorTo(process.stdout, 0);
|
|
2245
|
+
readline3.clearLine(process.stdout, 0);
|
|
2246
|
+
const previewPart = preview ? ` ${c.dim(preview)}` : "";
|
|
2247
|
+
process.stdout.write(` ${c.dim("\u2500")} ${c.bold(toolCall.tool)}${argStr ? " " + c.dim(argStr) : ""} ${toolOk ? sym.ok : sym.fail}${previewPart} ${c.dim("(" + elapsed + "s)")}
|
|
2248
|
+
`);
|
|
1935
2249
|
const resultMsg = `[Tool result: ${toolCall.tool}]
|
|
1936
2250
|
\`\`\`
|
|
1937
2251
|
${result}
|
|
@@ -1946,6 +2260,8 @@ ${result}
|
|
|
1946
2260
|
process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
|
|
1947
2261
|
let followContent = "";
|
|
1948
2262
|
try {
|
|
2263
|
+
let loopToolName = "";
|
|
2264
|
+
let loopToolStartMs = 0;
|
|
1949
2265
|
for await (const evt of client.agents.stream({
|
|
1950
2266
|
agentId,
|
|
1951
2267
|
sessionId,
|
|
@@ -1956,11 +2272,16 @@ ${result}
|
|
|
1956
2272
|
process.stdout.write(tok);
|
|
1957
2273
|
followContent += tok;
|
|
1958
2274
|
} else if (evt.type === "toolStart") {
|
|
1959
|
-
|
|
2275
|
+
loopToolName = evt.toolName ?? "";
|
|
2276
|
+
loopToolStartMs = Date.now();
|
|
1960
2277
|
if (followContent) process.stdout.write("\n");
|
|
1961
|
-
process.stdout.write(c.dim(
|
|
2278
|
+
process.stdout.write(` ${c.dim("\u2500")} ${c.bold(loopToolName)}`);
|
|
1962
2279
|
} else if (evt.type === "toolEnd") {
|
|
1963
|
-
|
|
2280
|
+
const elapsed2 = ((Date.now() - loopToolStartMs) / 1e3).toFixed(1);
|
|
2281
|
+
readline3.cursorTo(process.stdout, 0);
|
|
2282
|
+
readline3.clearLine(process.stdout, 0);
|
|
2283
|
+
process.stdout.write(` ${c.dim("\u2500")} ${c.bold(loopToolName)} ${sym.ok} ${c.dim("(" + elapsed2 + "s)")}
|
|
2284
|
+
`);
|
|
1964
2285
|
process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
|
|
1965
2286
|
} else if (evt.type === "final") {
|
|
1966
2287
|
const txt = extractText(evt?.payload);
|
|
@@ -1984,6 +2305,80 @@ ${sym.fail} ${c.error(evt.message ?? "Stream error")}`);
|
|
|
1984
2305
|
}
|
|
1985
2306
|
await handleLocalToolLoop(followContent, cfg, client, agentId, sessionId, appendLog, depth + 1);
|
|
1986
2307
|
}
|
|
2308
|
+
function truncate(s, max) {
|
|
2309
|
+
const str = String(s ?? "");
|
|
2310
|
+
return str.length <= max ? str : str.slice(0, max - 1) + "\u2026";
|
|
2311
|
+
}
|
|
2312
|
+
function toolArgSummary(toolName, args) {
|
|
2313
|
+
switch (toolName) {
|
|
2314
|
+
case "read_file":
|
|
2315
|
+
return truncate(args.path ?? "", 60);
|
|
2316
|
+
case "write_file":
|
|
2317
|
+
return truncate(args.path ?? "", 60);
|
|
2318
|
+
case "delete_file":
|
|
2319
|
+
return truncate(args.path ?? "", 60);
|
|
2320
|
+
case "list_directory":
|
|
2321
|
+
return truncate(args.path ?? ".", 60);
|
|
2322
|
+
case "run_command":
|
|
2323
|
+
return truncate(args.command ?? "", 60);
|
|
2324
|
+
case "start_process":
|
|
2325
|
+
return truncate(args.command ?? "", 60);
|
|
2326
|
+
case "wait_for_process":
|
|
2327
|
+
return truncate(args.process_id ?? "", 20);
|
|
2328
|
+
case "process_status":
|
|
2329
|
+
return truncate(args.process_id ?? "", 20);
|
|
2330
|
+
case "kill_process":
|
|
2331
|
+
return truncate(args.process_id ?? "", 20);
|
|
2332
|
+
case "list_processes":
|
|
2333
|
+
return "";
|
|
2334
|
+
case "search_files": {
|
|
2335
|
+
const parts = [args.pattern, args.query].filter(Boolean);
|
|
2336
|
+
return truncate(parts.join(" "), 60);
|
|
2337
|
+
}
|
|
2338
|
+
default: {
|
|
2339
|
+
const first = args.path ?? args.query ?? args.command ?? args.pattern ?? "";
|
|
2340
|
+
return truncate(String(first), 60);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
function toolResultPreview(toolName, result) {
|
|
2345
|
+
if (!result || result.startsWith("Error:")) return "";
|
|
2346
|
+
switch (toolName) {
|
|
2347
|
+
case "read_file": {
|
|
2348
|
+
const lines = result.split("\n").length;
|
|
2349
|
+
return `${lines} lines`;
|
|
2350
|
+
}
|
|
2351
|
+
case "write_file":
|
|
2352
|
+
return "written";
|
|
2353
|
+
case "delete_file":
|
|
2354
|
+
return "deleted";
|
|
2355
|
+
case "list_directory": {
|
|
2356
|
+
const count = result.split("\n").filter(Boolean).length;
|
|
2357
|
+
return `${count} entries`;
|
|
2358
|
+
}
|
|
2359
|
+
case "run_command": {
|
|
2360
|
+
const first = result.split("\n").find((l) => l.trim());
|
|
2361
|
+
return first ? truncate(first.trim(), 50) : "done";
|
|
2362
|
+
}
|
|
2363
|
+
case "start_process": {
|
|
2364
|
+
const match = result.match(/process[_\s-]?id[:\s]+([a-zA-Z0-9_-]+)/i) ?? result.match(/"id"[:\s]+"([^"]+)"/);
|
|
2365
|
+
return match ? `pid ${match[1]}` : "started";
|
|
2366
|
+
}
|
|
2367
|
+
case "wait_for_process": {
|
|
2368
|
+
if (/done|complete|exit/i.test(result)) return "done";
|
|
2369
|
+
if (/running/i.test(result)) return "still running";
|
|
2370
|
+
return truncate(result.split("\n")[0]?.trim() ?? "", 40);
|
|
2371
|
+
}
|
|
2372
|
+
case "search_files": {
|
|
2373
|
+
const count = result.split("\n").filter(Boolean).length;
|
|
2374
|
+
return `${count} match${count === 1 ? "" : "es"}`;
|
|
2375
|
+
}
|
|
2376
|
+
default: {
|
|
2377
|
+
const first = result.split("\n").find((l) => l.trim());
|
|
2378
|
+
return first ? truncate(first.trim(), 50) : "";
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
1987
2382
|
function extractText(payload) {
|
|
1988
2383
|
if (!payload) return "";
|
|
1989
2384
|
if (typeof payload === "string") return payload;
|
|
@@ -3279,7 +3674,7 @@ async function pickAgentInteractively(action) {
|
|
|
3279
3674
|
return agentId;
|
|
3280
3675
|
}
|
|
3281
3676
|
var program = new import_commander16.Command();
|
|
3282
|
-
program.name("agc").description("Agent Commons CLI \u2014 interact with the Agent Commons platform").version("0.1.
|
|
3677
|
+
program.name("agc").description("Agent Commons CLI \u2014 interact with the Agent Commons platform").version("0.1.17", "-v, --version").action(async () => {
|
|
3283
3678
|
await interactiveMenu();
|
|
3284
3679
|
});
|
|
3285
3680
|
program.addCommand(loginCommand());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-commons/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Agent Commons CLI — chat, run, and manage agents from your terminal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -11,15 +11,17 @@
|
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"
|
|
14
|
+
"@types/pdf-parse": "^1.1.5",
|
|
15
15
|
"chalk": "^5.3.0",
|
|
16
|
+
"commander": "^12.1.0",
|
|
16
17
|
"ora": "^8.1.1",
|
|
17
|
-
"
|
|
18
|
+
"pdf-parse": "^1.1.1",
|
|
19
|
+
"@agent-commons/sdk": "0.1.13"
|
|
18
20
|
},
|
|
19
21
|
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.10.2",
|
|
20
23
|
"tsup": "^8.3.5",
|
|
21
|
-
"typescript": "^5.7.2"
|
|
22
|
-
"@types/node": "^22.10.2"
|
|
24
|
+
"typescript": "^5.7.2"
|
|
23
25
|
},
|
|
24
26
|
"engines": {
|
|
25
27
|
"node": ">=18"
|