@agent-commons/cli 0.1.13 → 0.1.16
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 +353 -48
- package/package.json +1 -1
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.16") {
|
|
110
110
|
const line = import_chalk.default.cyan(" \u2500".padEnd(2) + "\u2500".repeat(44));
|
|
111
111
|
console.log("");
|
|
112
112
|
console.log(line);
|
|
@@ -1257,61 +1257,143 @@ 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
|
-
|
|
1261
|
-
|
|
1260
|
+
var managedProcesses = /* @__PURE__ */ new Map();
|
|
1261
|
+
function capBuffer(existing, chunk, maxBytes) {
|
|
1262
|
+
const joined = existing + chunk;
|
|
1263
|
+
if (joined.length <= maxBytes) return joined;
|
|
1264
|
+
return "\u2026(truncated)\n" + joined.slice(-(maxBytes - 20));
|
|
1265
|
+
}
|
|
1266
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", "__pycache__", ".next", "dist", "build", ".DS_Store"]);
|
|
1267
|
+
function buildDirSnapshot(dir, maxDepth = 2) {
|
|
1268
|
+
const lines = [`${dir}/`];
|
|
1269
|
+
function walk(d, depth, prefix) {
|
|
1270
|
+
if (lines.length >= 300) return;
|
|
1271
|
+
let entries;
|
|
1272
|
+
try {
|
|
1273
|
+
entries = (0, import_fs3.readdirSync)(d, { withFileTypes: true });
|
|
1274
|
+
} catch {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const sorted = entries.sort((a, b) => {
|
|
1278
|
+
if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
|
|
1279
|
+
return a.name.localeCompare(b.name);
|
|
1280
|
+
});
|
|
1281
|
+
for (const entry of sorted) {
|
|
1282
|
+
if (lines.length >= 300) {
|
|
1283
|
+
lines.push(`${prefix}... (truncated)`);
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
1287
|
+
const isDir = entry.isDirectory();
|
|
1288
|
+
lines.push(`${prefix}${entry.name}${isDir ? "/" : ""}`);
|
|
1289
|
+
if (isDir && depth < maxDepth) walk((0, import_path3.join)(d, entry.name), depth + 1, prefix + " ");
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
walk(dir, 1, " ");
|
|
1293
|
+
return lines.join("\n");
|
|
1294
|
+
}
|
|
1295
|
+
function readFileForContext(rootDir, filePath) {
|
|
1296
|
+
try {
|
|
1297
|
+
const abs = (0, import_path3.resolve)(rootDir, filePath);
|
|
1298
|
+
const rel = (0, import_path3.relative)(rootDir, abs);
|
|
1299
|
+
if (rel.startsWith("..") || rel.startsWith("/")) return `[error: path escapes session root]`;
|
|
1300
|
+
for (const pat of [/\/\.ssh\//, /\/\.aws\//, /\/\.env$/, /\/\.env\./, /id_rsa/, /id_ed25519/]) {
|
|
1301
|
+
if (pat.test(abs)) return `[error: sensitive path blocked]`;
|
|
1302
|
+
}
|
|
1303
|
+
if (!(0, import_fs3.existsSync)(abs)) return `[error: file not found: ${filePath}]`;
|
|
1304
|
+
const stat = (0, import_fs3.statSync)(abs);
|
|
1305
|
+
if (stat.isDirectory()) return `[error: "${filePath}" is a directory \u2014 use list_directory]`;
|
|
1306
|
+
if (stat.size > 1e5) return `[truncated \u2014 file too large (${Math.round(stat.size / 1024)} KB). Use cli_read_file for full content]`;
|
|
1307
|
+
return (0, import_fs3.readFileSync)(abs, "utf8");
|
|
1308
|
+
} catch (err) {
|
|
1309
|
+
return `[error reading file: ${err?.message}]`;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
function buildLocalToolsManifest(rootDir, snapshot, fileContextBlocks = []) {
|
|
1313
|
+
const fileSection = fileContextBlocks.length ? `
|
|
1314
|
+
### File contents included in this turn
|
|
1315
|
+
|
|
1316
|
+
${fileContextBlocks.join("\n\n")}
|
|
1317
|
+
` : "";
|
|
1318
|
+
return `
|
|
1319
|
+
## CLI Local File System \u2014 ACTIVE
|
|
1262
1320
|
|
|
1263
|
-
You
|
|
1321
|
+
You are running inside a CLI session with DIRECT access to the user's local machine. The following tools are in your tool list and execute on the user's machine in real time.
|
|
1264
1322
|
|
|
1265
1323
|
**Session root:** ${rootDir}
|
|
1266
|
-
All paths are relative to the session root unless absolute.
|
|
1267
1324
|
|
|
1268
|
-
|
|
1325
|
+
### Current file system (live snapshot)
|
|
1326
|
+
|
|
1327
|
+
\`\`\`
|
|
1328
|
+
${snapshot}
|
|
1329
|
+
\`\`\`
|
|
1330
|
+
${fileSection}
|
|
1331
|
+
|
|
1332
|
+
### MANDATORY RULES \u2014 READ CAREFULLY
|
|
1269
1333
|
|
|
1270
|
-
|
|
1334
|
+
1. **Call cli_* tools immediately and directly.** Do NOT create tasks (createTask) for local file operations. Do NOT delegate to sub-agents. Do NOT ask the user to run commands themselves.
|
|
1335
|
+
2. **Always show the actual output** returned by the tool in your response. Never say "I listed the files" without showing them. Report exactly what the tool returns.
|
|
1336
|
+
3. **Never fabricate results.** Wait for the real tool output before responding.
|
|
1337
|
+
4. **Sensitive paths are blocked** (.ssh, .gnupg, .aws, .env, credentials). Attempting to access them will return an error.
|
|
1338
|
+
5. **cli_write_file and cli_run_command require the user to confirm** before executing \u2014 you will see the result after they approve.
|
|
1271
1339
|
|
|
1272
|
-
|
|
1340
|
+
### Available CLI tools
|
|
1273
1341
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1342
|
+
| Tool | What it does |
|
|
1343
|
+
|------|-------------|
|
|
1344
|
+
| \`cli_list_directory\` | List files and folders at a path |
|
|
1345
|
+
| \`cli_read_file\` | Read a file (PDF and Word docs are extracted to text) |
|
|
1346
|
+
| \`cli_write_file\` | Write or overwrite a file (user confirmation required) |
|
|
1347
|
+
| \`cli_search_files\` | Find files matching a pattern |
|
|
1348
|
+
| \`cli_run_command\` | Run a short command and return its output (user confirmation required) |
|
|
1349
|
+
| \`cli_start_process\` | Start a long-running command in the background; returns a processId immediately |
|
|
1350
|
+
| \`cli_wait_for_process\` | Block up to N seconds for a background process, then return current output |
|
|
1351
|
+
| \`cli_process_status\` | Instant non-blocking check on a background process |
|
|
1352
|
+
| \`cli_kill_process\` | Kill a running background process |
|
|
1353
|
+
| \`cli_list_processes\` | List all background processes started this session |
|
|
1277
1354
|
|
|
1278
|
-
|
|
1355
|
+
### Choosing between run_command and start_process
|
|
1279
1356
|
|
|
1280
|
-
|
|
1357
|
+
| Situation | Use |
|
|
1358
|
+
|-----------|-----|
|
|
1359
|
+
| Command finishes in under ~30s | \`cli_run_command\` |
|
|
1360
|
+
| Command may take minutes (npm install, build, scaffold) | \`cli_start_process\` + \`cli_wait_for_process\` |
|
|
1361
|
+
| Command needs live stdin (e.g. a REPL) | \`cli_run_command\` with \`"interactive": true\` |
|
|
1281
1362
|
|
|
1282
|
-
###
|
|
1363
|
+
### run_command options
|
|
1364
|
+
- \`timeout_seconds\` (default 120, max 300) \u2014 kill the process after N seconds
|
|
1365
|
+
- \`interactive\` (boolean) \u2014 connects the user's terminal stdin for commands that need input
|
|
1283
1366
|
|
|
1284
|
-
|
|
1285
|
-
\`\`\`tool
|
|
1286
|
-
{"tool": "read_file", "args": {"path": "src/index.ts"}}
|
|
1287
|
-
\`\`\`
|
|
1367
|
+
### start_process + wait_for_process pattern
|
|
1288
1368
|
|
|
1289
|
-
|
|
1290
|
-
\`\`\`tool
|
|
1291
|
-
{"tool": "write_file", "args": {"path": "output.txt", "content": "Hello world"}}
|
|
1292
|
-
\`\`\`
|
|
1369
|
+
For long commands like \`npx create-next-app@latest my-app --yes\`:
|
|
1293
1370
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1371
|
+
1. Call \`cli_start_process\` \u2014 returns \`{processId, status: "running"}\` immediately. Tell the user it has started.
|
|
1372
|
+
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.
|
|
1373
|
+
3. Repeat step 2 until \`status\` is \`"done"\` or \`"error"\`.
|
|
1374
|
+
4. Report the final output to the user.
|
|
1298
1375
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
\`\`\`
|
|
1376
|
+
Never hold the user in silence. Between each \`cli_wait_for_process\` call, tell them what you saw so far.
|
|
1377
|
+
|
|
1378
|
+
### Example \u2014 scaffolding a Next.js project
|
|
1303
1379
|
|
|
1304
|
-
**run_command** \u2014 Execute a shell command and return stdout/stderr. User must confirm. 30s timeout.
|
|
1305
|
-
\`\`\`tool
|
|
1306
|
-
{"tool": "run_command", "args": {"command": "node", "args": ["--version"]}}
|
|
1307
1380
|
\`\`\`
|
|
1381
|
+
cli_start_process: {"command": "npx", "args": ["create-next-app@latest", "my-app", "--yes"], "cwd": "Desktop"}
|
|
1382
|
+
\u2192 {processId: "proc_1a2b", status: "running"}
|
|
1383
|
+
|
|
1384
|
+
Tell user: "Started! Installing dependencies, this takes a minute or two. Checking in 60s\u2026"
|
|
1308
1385
|
|
|
1309
|
-
|
|
1386
|
+
cli_wait_for_process: {"processId": "proc_1a2b", "wait_seconds": 60}
|
|
1387
|
+
\u2192 {status: "running", elapsedSec: 60, stdout: "Creating project...
|
|
1388
|
+
Installing packages\u2026"}
|
|
1310
1389
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1390
|
+
Tell user: "Still installing \u2014 here's output so far: [stdout]. Checking again\u2026"
|
|
1391
|
+
|
|
1392
|
+
cli_wait_for_process: {"processId": "proc_1a2b", "wait_seconds": 60}
|
|
1393
|
+
\u2192 {status: "done", exitCode: 0, elapsedSec: 93, stdout: "Success! Created my-app"}
|
|
1394
|
+
|
|
1395
|
+
Tell user: "Done! Project created in Desktop/my-app"
|
|
1396
|
+
\`\`\`
|
|
1315
1397
|
`;
|
|
1316
1398
|
}
|
|
1317
1399
|
var TOOL_CALL_RE = /```tool\s*\n([\s\S]*?)\n```/;
|
|
@@ -1377,6 +1459,61 @@ async function confirm(message, config, permissionKey) {
|
|
|
1377
1459
|
});
|
|
1378
1460
|
});
|
|
1379
1461
|
}
|
|
1462
|
+
var OFFICE_EXTS = /* @__PURE__ */ new Set([".docx", ".doc", ".rtf", ".odt", ".pages"]);
|
|
1463
|
+
var PDF_EXTS = /* @__PURE__ */ new Set([".pdf"]);
|
|
1464
|
+
var UNREADABLE_BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
1465
|
+
".png",
|
|
1466
|
+
".jpg",
|
|
1467
|
+
".jpeg",
|
|
1468
|
+
".gif",
|
|
1469
|
+
".bmp",
|
|
1470
|
+
".ico",
|
|
1471
|
+
".webp",
|
|
1472
|
+
".tiff",
|
|
1473
|
+
".mp3",
|
|
1474
|
+
".mp4",
|
|
1475
|
+
".wav",
|
|
1476
|
+
".aac",
|
|
1477
|
+
".ogg",
|
|
1478
|
+
".flac",
|
|
1479
|
+
".zip",
|
|
1480
|
+
".tar",
|
|
1481
|
+
".gz",
|
|
1482
|
+
".bz2",
|
|
1483
|
+
".7z",
|
|
1484
|
+
".rar",
|
|
1485
|
+
".exe",
|
|
1486
|
+
".dll",
|
|
1487
|
+
".so",
|
|
1488
|
+
".dylib",
|
|
1489
|
+
".bin",
|
|
1490
|
+
".psd",
|
|
1491
|
+
".ai",
|
|
1492
|
+
".sketch",
|
|
1493
|
+
".figma",
|
|
1494
|
+
".xlsx",
|
|
1495
|
+
".xls",
|
|
1496
|
+
".pptx",
|
|
1497
|
+
".ppt"
|
|
1498
|
+
]);
|
|
1499
|
+
function extractViaCommand(cmd, cmdArgs) {
|
|
1500
|
+
return new Promise((res) => {
|
|
1501
|
+
(0, import_child_process2.execFile)(cmd, cmdArgs, { timeout: 3e4, maxBuffer: 2 * 1024 * 1024 }, (err, stdout) => {
|
|
1502
|
+
if (err) res("");
|
|
1503
|
+
else res(stdout.trim());
|
|
1504
|
+
});
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
async function extractPdfText(abs) {
|
|
1508
|
+
const text = await extractViaCommand("pdftotext", [abs, "-"]);
|
|
1509
|
+
if (text) return text;
|
|
1510
|
+
return `[Cannot extract PDF text: pdftotext not found. Install with: brew install poppler]`;
|
|
1511
|
+
}
|
|
1512
|
+
async function extractOfficeText(abs, ext) {
|
|
1513
|
+
const text = await extractViaCommand("textutil", ["-stdout", "-cat", "txt", abs]);
|
|
1514
|
+
if (text) return text;
|
|
1515
|
+
return `[Cannot extract ${ext} text: textutil failed or is unavailable on this system]`;
|
|
1516
|
+
}
|
|
1380
1517
|
async function toolReadFile(args, cfg) {
|
|
1381
1518
|
const { path: userPath } = args;
|
|
1382
1519
|
if (!userPath) throw new Error('read_file requires a "path" argument');
|
|
@@ -1386,6 +1523,12 @@ async function toolReadFile(args, cfg) {
|
|
|
1386
1523
|
const stat = (0, import_fs3.statSync)(abs);
|
|
1387
1524
|
if (stat.isDirectory()) throw new Error(`"${userPath}" is a directory, not a file`);
|
|
1388
1525
|
if (stat.size > 5e5) throw new Error(`File too large to read (${Math.round(stat.size / 1024)} KB). Max 500 KB.`);
|
|
1526
|
+
const ext = (0, import_path3.extname)(abs).toLowerCase();
|
|
1527
|
+
if (PDF_EXTS.has(ext)) return extractPdfText(abs);
|
|
1528
|
+
if (OFFICE_EXTS.has(ext)) return extractOfficeText(abs, ext);
|
|
1529
|
+
if (UNREADABLE_BINARY_EXTS.has(ext)) {
|
|
1530
|
+
throw new Error(`Cannot read binary file "${userPath}" (${ext} format). Only text, PDF, and Word documents are supported.`);
|
|
1531
|
+
}
|
|
1389
1532
|
return (0, import_fs3.readFileSync)(abs, "utf8");
|
|
1390
1533
|
}
|
|
1391
1534
|
async function toolWriteFile(args, cfg) {
|
|
@@ -1443,11 +1586,12 @@ async function toolSearchFiles(args, cfg) {
|
|
|
1443
1586
|
return results.length ? results.join("\n") : "No files found matching: " + pattern;
|
|
1444
1587
|
}
|
|
1445
1588
|
async function toolRunCommand(args, cfg) {
|
|
1446
|
-
const { command, args: cmdArgs = [], cwd } = args;
|
|
1589
|
+
const { command, args: cmdArgs = [], cwd, timeout_seconds, interactive } = args;
|
|
1447
1590
|
if (!command || typeof command !== "string") throw new Error('run_command requires a "command" string');
|
|
1448
1591
|
if (!Array.isArray(cmdArgs)) throw new Error('"args" must be an array of strings');
|
|
1449
1592
|
const workDir = cwd ? safePath(cfg.rootDir, cwd) : cfg.rootDir;
|
|
1450
1593
|
const preview = [command, ...cmdArgs].join(" ");
|
|
1594
|
+
const timeoutMs = Math.min((typeof timeout_seconds === "number" ? timeout_seconds : 120) * 1e3, 3e5);
|
|
1451
1595
|
const ok = await confirm(
|
|
1452
1596
|
`Agent wants to run: \x1B[1m${preview}\x1B[0m
|
|
1453
1597
|
\x1B[2min: ${workDir}\x1B[0m`,
|
|
@@ -1455,14 +1599,142 @@ async function toolRunCommand(args, cfg) {
|
|
|
1455
1599
|
"run_command"
|
|
1456
1600
|
);
|
|
1457
1601
|
if (!ok) return "User denied command execution.";
|
|
1602
|
+
if (interactive) {
|
|
1603
|
+
return new Promise((resolve2) => {
|
|
1604
|
+
const child = (0, import_child_process2.spawn)(command, cmdArgs.map(String), { cwd: workDir, stdio: "inherit" });
|
|
1605
|
+
const timer = setTimeout(() => {
|
|
1606
|
+
child.kill();
|
|
1607
|
+
resolve2(`(command timed out after ${timeoutMs / 1e3}s)`);
|
|
1608
|
+
}, timeoutMs);
|
|
1609
|
+
child.on("close", (code) => {
|
|
1610
|
+
clearTimeout(timer);
|
|
1611
|
+
resolve2(`(command exited with code ${code ?? "unknown"})`);
|
|
1612
|
+
});
|
|
1613
|
+
child.on("error", (err) => {
|
|
1614
|
+
clearTimeout(timer);
|
|
1615
|
+
resolve2(`Error: ${err.message}`);
|
|
1616
|
+
});
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1458
1619
|
return new Promise((resolve2) => {
|
|
1459
|
-
(0, import_child_process2.execFile)(command, cmdArgs.map(String), { cwd: workDir, timeout:
|
|
1620
|
+
(0, import_child_process2.execFile)(command, cmdArgs.map(String), { cwd: workDir, timeout: timeoutMs, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
1460
1621
|
const out = [stdout, stderr].filter(Boolean).join("\n--- stderr ---\n");
|
|
1461
1622
|
if (err && !out) return resolve2(`Error: ${err.message}`);
|
|
1462
1623
|
resolve2(out || "(no output)");
|
|
1463
1624
|
});
|
|
1464
1625
|
});
|
|
1465
1626
|
}
|
|
1627
|
+
async function toolStartProcess(args, cfg) {
|
|
1628
|
+
const { command, args: cmdArgs = [], cwd } = args;
|
|
1629
|
+
if (!command || typeof command !== "string") throw new Error('start_process requires a "command" string');
|
|
1630
|
+
if (!Array.isArray(cmdArgs)) throw new Error('"args" must be an array of strings');
|
|
1631
|
+
const workDir = cwd ? safePath(cfg.rootDir, cwd) : cfg.rootDir;
|
|
1632
|
+
const preview = [command, ...cmdArgs].join(" ");
|
|
1633
|
+
const ok = await confirm(
|
|
1634
|
+
`Agent wants to start background process: \x1B[1m${preview}\x1B[0m
|
|
1635
|
+
\x1B[2min: ${workDir}\x1B[0m`,
|
|
1636
|
+
cfg,
|
|
1637
|
+
"start_process"
|
|
1638
|
+
);
|
|
1639
|
+
if (!ok) return JSON.stringify({ error: "User denied process start." });
|
|
1640
|
+
const id = `proc_${Date.now().toString(36)}`;
|
|
1641
|
+
const child = (0, import_child_process2.spawn)(command, cmdArgs.map(String), {
|
|
1642
|
+
cwd: workDir,
|
|
1643
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1644
|
+
detached: false
|
|
1645
|
+
});
|
|
1646
|
+
const proc = {
|
|
1647
|
+
id,
|
|
1648
|
+
command: preview,
|
|
1649
|
+
status: "running",
|
|
1650
|
+
exitCode: null,
|
|
1651
|
+
stdout: "",
|
|
1652
|
+
stderr: "",
|
|
1653
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
1654
|
+
endedAt: null,
|
|
1655
|
+
child
|
|
1656
|
+
};
|
|
1657
|
+
child.stdout?.on("data", (chunk) => {
|
|
1658
|
+
proc.stdout = capBuffer(proc.stdout, chunk.toString(), 2e5);
|
|
1659
|
+
});
|
|
1660
|
+
child.stderr?.on("data", (chunk) => {
|
|
1661
|
+
proc.stderr = capBuffer(proc.stderr, chunk.toString(), 5e4);
|
|
1662
|
+
});
|
|
1663
|
+
child.on("close", (code) => {
|
|
1664
|
+
proc.status = code === 0 ? "done" : "error";
|
|
1665
|
+
proc.exitCode = code;
|
|
1666
|
+
proc.endedAt = /* @__PURE__ */ new Date();
|
|
1667
|
+
});
|
|
1668
|
+
child.on("error", (err) => {
|
|
1669
|
+
proc.status = "error";
|
|
1670
|
+
proc.endedAt = /* @__PURE__ */ new Date();
|
|
1671
|
+
proc.stderr = capBuffer(proc.stderr, `
|
|
1672
|
+
Spawn error: ${err.message}`, 5e4);
|
|
1673
|
+
});
|
|
1674
|
+
managedProcesses.set(id, proc);
|
|
1675
|
+
cfg.appendLog({ type: "process_start", processId: id, command: preview, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1676
|
+
return JSON.stringify({ processId: id, status: "running", command: preview });
|
|
1677
|
+
}
|
|
1678
|
+
function processSnapshot(proc) {
|
|
1679
|
+
const elapsedSec = Math.round((Date.now() - proc.startedAt.getTime()) / 1e3);
|
|
1680
|
+
const recentStdout = proc.stdout.length > 4e3 ? "\u2026(earlier output truncated)\n" + proc.stdout.slice(-4e3) : proc.stdout;
|
|
1681
|
+
return JSON.stringify({
|
|
1682
|
+
processId: proc.id,
|
|
1683
|
+
command: proc.command,
|
|
1684
|
+
status: proc.status,
|
|
1685
|
+
exitCode: proc.exitCode,
|
|
1686
|
+
elapsedSec,
|
|
1687
|
+
stdout: recentStdout || "(no output yet)",
|
|
1688
|
+
stderr: proc.stderr.slice(-1e3) || void 0
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
async function toolProcessStatus(args, _cfg) {
|
|
1692
|
+
const { processId } = args;
|
|
1693
|
+
if (!processId) throw new Error('process_status requires a "processId" argument');
|
|
1694
|
+
const proc = managedProcesses.get(processId);
|
|
1695
|
+
if (!proc) return JSON.stringify({ error: `No process found with id "${processId}"` });
|
|
1696
|
+
return processSnapshot(proc);
|
|
1697
|
+
}
|
|
1698
|
+
async function toolWaitForProcess(args, _cfg) {
|
|
1699
|
+
const { processId, wait_seconds = 60 } = args;
|
|
1700
|
+
if (!processId) throw new Error('wait_for_process requires a "processId" argument');
|
|
1701
|
+
const proc = managedProcesses.get(processId);
|
|
1702
|
+
if (!proc) return JSON.stringify({ error: `No process found with id "${processId}"` });
|
|
1703
|
+
if (proc.status !== "running") return processSnapshot(proc);
|
|
1704
|
+
const maxWait = Math.min((typeof wait_seconds === "number" ? wait_seconds : 60) * 1e3, 12e4);
|
|
1705
|
+
const deadline = Date.now() + maxWait;
|
|
1706
|
+
await new Promise((resolve2) => {
|
|
1707
|
+
const tick = setInterval(() => {
|
|
1708
|
+
if (proc.status !== "running" || Date.now() >= deadline) {
|
|
1709
|
+
clearInterval(tick);
|
|
1710
|
+
resolve2();
|
|
1711
|
+
}
|
|
1712
|
+
}, 500);
|
|
1713
|
+
});
|
|
1714
|
+
return processSnapshot(proc);
|
|
1715
|
+
}
|
|
1716
|
+
async function toolKillProcess(args, cfg) {
|
|
1717
|
+
const { processId } = args;
|
|
1718
|
+
if (!processId) throw new Error('kill_process requires a "processId" argument');
|
|
1719
|
+
const proc = managedProcesses.get(processId);
|
|
1720
|
+
if (!proc) return JSON.stringify({ error: `No process found with id "${processId}"` });
|
|
1721
|
+
if (proc.status !== "running") return JSON.stringify({ error: `Process "${processId}" is not running (status: ${proc.status})` });
|
|
1722
|
+
proc.child.kill("SIGTERM");
|
|
1723
|
+
proc.status = "killed";
|
|
1724
|
+
proc.endedAt = /* @__PURE__ */ new Date();
|
|
1725
|
+
cfg.appendLog({ type: "process_killed", processId, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1726
|
+
return JSON.stringify({ processId, status: "killed" });
|
|
1727
|
+
}
|
|
1728
|
+
async function toolListProcesses(_args, _cfg) {
|
|
1729
|
+
if (managedProcesses.size === 0) return JSON.stringify([]);
|
|
1730
|
+
const list = [...managedProcesses.values()].map((p) => ({
|
|
1731
|
+
processId: p.id,
|
|
1732
|
+
command: p.command,
|
|
1733
|
+
status: p.status,
|
|
1734
|
+
elapsedSec: Math.round((Date.now() - p.startedAt.getTime()) / 1e3)
|
|
1735
|
+
}));
|
|
1736
|
+
return JSON.stringify(list);
|
|
1737
|
+
}
|
|
1466
1738
|
async function runLocalTool(call, cfg) {
|
|
1467
1739
|
const { tool, args } = call;
|
|
1468
1740
|
cfg.appendLog({
|
|
@@ -1489,8 +1761,23 @@ async function runLocalTool(call, cfg) {
|
|
|
1489
1761
|
case "run_command":
|
|
1490
1762
|
result = await toolRunCommand(args, cfg);
|
|
1491
1763
|
break;
|
|
1764
|
+
case "start_process":
|
|
1765
|
+
result = await toolStartProcess(args, cfg);
|
|
1766
|
+
break;
|
|
1767
|
+
case "process_status":
|
|
1768
|
+
result = await toolProcessStatus(args, cfg);
|
|
1769
|
+
break;
|
|
1770
|
+
case "wait_for_process":
|
|
1771
|
+
result = await toolWaitForProcess(args, cfg);
|
|
1772
|
+
break;
|
|
1773
|
+
case "kill_process":
|
|
1774
|
+
result = await toolKillProcess(args, cfg);
|
|
1775
|
+
break;
|
|
1776
|
+
case "list_processes":
|
|
1777
|
+
result = await toolListProcesses(args, cfg);
|
|
1778
|
+
break;
|
|
1492
1779
|
default:
|
|
1493
|
-
result = `Unknown tool: "${tool}". Available: read_file, write_file, list_directory, search_files, run_command`;
|
|
1780
|
+
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`;
|
|
1494
1781
|
}
|
|
1495
1782
|
} catch (err) {
|
|
1496
1783
|
result = `Error: ${err?.message ?? String(err)}`;
|
|
@@ -1522,9 +1809,13 @@ var HELP_TEXT = `
|
|
|
1522
1809
|
${c.label("Slash commands")}
|
|
1523
1810
|
/help Show this help
|
|
1524
1811
|
/session Print the current session ID (copy it to resume later)
|
|
1525
|
-
/tools Show local tool status and permissions
|
|
1812
|
+
/tools Show local tool status and permissions
|
|
1526
1813
|
/clear Clear the terminal screen
|
|
1527
1814
|
/quit Exit (session is preserved \u2014 resume with --resume <id>)
|
|
1815
|
+
|
|
1816
|
+
${c.label("File context")}
|
|
1817
|
+
Use @path/to/file in your message to inject that file's contents into context.
|
|
1818
|
+
Example: "review @src/index.ts and suggest improvements"
|
|
1528
1819
|
`;
|
|
1529
1820
|
var LOCAL_TOOLS_DISCLAIMER = `
|
|
1530
1821
|
${c.warn("\u26A0")} ${c.bold("Local file system access enabled")}
|
|
@@ -1705,14 +1996,27 @@ Session saved. Resume with: agc chat --resume ${sessionId}`));
|
|
|
1705
1996
|
content: input,
|
|
1706
1997
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1707
1998
|
});
|
|
1999
|
+
let userMessage = input;
|
|
2000
|
+
let cliContext;
|
|
2001
|
+
if (localToolsCfg) {
|
|
2002
|
+
const rootDir = localToolsCfg.rootDir;
|
|
2003
|
+
const atRefs = [...input.matchAll(/@([\S]+)/g)].map((m) => m[1]);
|
|
2004
|
+
const fileContextBlocks = [];
|
|
2005
|
+
for (const ref of atRefs) {
|
|
2006
|
+
const content = readFileForContext(rootDir, ref);
|
|
2007
|
+
fileContextBlocks.push(`**${ref}**
|
|
2008
|
+
\`\`\`
|
|
2009
|
+
${content}
|
|
2010
|
+
\`\`\``);
|
|
2011
|
+
}
|
|
2012
|
+
const snapshot = buildDirSnapshot(rootDir, 2);
|
|
2013
|
+
cliContext = buildLocalToolsManifest(rootDir, snapshot, fileContextBlocks);
|
|
2014
|
+
}
|
|
1708
2015
|
const params = {
|
|
1709
2016
|
agentId,
|
|
1710
2017
|
sessionId,
|
|
1711
|
-
messages: [{ role: "user", content:
|
|
1712
|
-
|
|
1713
|
-
// the LLM receives it as part of its actual instructions, not as a stray
|
|
1714
|
-
// second system message appended after the conversation history.
|
|
1715
|
-
...localToolsCfg && { cliContext: buildLocalToolsManifest(localToolsCfg.rootDir) }
|
|
2018
|
+
messages: [{ role: "user", content: userMessage }],
|
|
2019
|
+
...cliContext && { cliContext }
|
|
1716
2020
|
};
|
|
1717
2021
|
process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
|
|
1718
2022
|
if (opts.noStream) {
|
|
@@ -1779,6 +2083,7 @@ ${sym.fail} ${c.error(err.message ?? String(err))}`);
|
|
|
1779
2083
|
console.error(c.warn(`
|
|
1780
2084
|
[local] Failed to submit tool result: ${postErr?.message}`));
|
|
1781
2085
|
}
|
|
2086
|
+
} else if (event.type === "ping") {
|
|
1782
2087
|
} else if (event.type === "toolStart") {
|
|
1783
2088
|
const name = event.toolName ?? "";
|
|
1784
2089
|
if (hasOutput) process.stdout.write("\n");
|
|
@@ -3218,7 +3523,7 @@ async function pickAgentInteractively(action) {
|
|
|
3218
3523
|
return agentId;
|
|
3219
3524
|
}
|
|
3220
3525
|
var program = new import_commander16.Command();
|
|
3221
|
-
program.name("agc").description("Agent Commons CLI \u2014 interact with the Agent Commons platform").version("0.1.
|
|
3526
|
+
program.name("agc").description("Agent Commons CLI \u2014 interact with the Agent Commons platform").version("0.1.16", "-v, --version").action(async () => {
|
|
3222
3527
|
await interactiveMenu();
|
|
3223
3528
|
});
|
|
3224
3529
|
program.addCommand(loginCommand());
|