@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.
Files changed (2) hide show
  1. package/dist/bin.js +353 -48
  2. 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.13") {
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
- function buildLocalToolsManifest(rootDir) {
1261
- return `## Local File System Access
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 have direct access to the user's local machine file system. Use these tools freely to complete tasks \u2014 do not ask the user to run commands themselves.
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
- ### How to call a tool
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
- When you need to use a local tool, output ONLY the following JSON block \u2014 nothing else in that message. After receiving the result, continue your response:
1340
+ ### Available CLI tools
1273
1341
 
1274
- \`\`\`tool
1275
- {"tool": "<tool_name>", "args": {"<arg>": "<value>"}}
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
- You may call tools multiple times in sequence. Each call will be executed and the result returned to you before you continue.
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
- ### Available tools
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
- **read_file** \u2014 Read the full contents of a file.
1285
- \`\`\`tool
1286
- {"tool": "read_file", "args": {"path": "src/index.ts"}}
1287
- \`\`\`
1367
+ ### start_process + wait_for_process pattern
1288
1368
 
1289
- **write_file** \u2014 Write content to a file (creates directories as needed). User must confirm.
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
- **list_directory** \u2014 List files and directories at a path. Defaults to session root.
1295
- \`\`\`tool
1296
- {"tool": "list_directory", "args": {"path": "src"}}
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
- **search_files** \u2014 Find files matching a name/path pattern (glob-style, up to 50 results).
1300
- \`\`\`tool
1301
- {"tool": "search_files", "args": {"pattern": "*.ts", "directory": "src"}}
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
- **Important:**
1312
- - Never fabricate tool results. Always wait for the actual output.
1313
- - Sensitive paths (.ssh, .env, .aws, credentials) are blocked by the system.
1314
- - Write and run_command operations require explicit user approval before executing.
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: 3e4, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
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 (--local mode)
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: input }],
1712
- // Inject the tool manifest into the agent's system prompt server-side so
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.13", "-v, --version").action(async () => {
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());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-commons/cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.16",
4
4
  "description": "Agent Commons CLI — chat, run, and manage agents from your terminal",
5
5
  "license": "MIT",
6
6
  "bin": {