@agent-commons/cli 0.1.6 → 0.1.10

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 +570 -72
  2. package/package.json +9 -9
package/dist/bin.js CHANGED
@@ -25,9 +25,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  // src/bin.ts
27
27
  var import_commander16 = require("commander");
28
- var import_path3 = require("path");
29
- var import_os3 = require("os");
30
- var import_child_process2 = require("child_process");
28
+ var import_path5 = require("path");
29
+ var import_os4 = require("os");
30
+ var import_child_process3 = require("child_process");
31
31
 
32
32
  // src/commands/login.ts
33
33
  var import_commander = require("commander");
@@ -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.4") {
109
+ function banner(version = "0.1.10") {
110
110
  const line = import_chalk.default.cyan(" \u2500".padEnd(2) + "\u2500".repeat(44));
111
111
  console.log("");
112
112
  console.log(line);
@@ -145,7 +145,7 @@ async function select(prompt2, choices) {
145
145
  }
146
146
  };
147
147
  render(true);
148
- return new Promise((resolve) => {
148
+ return new Promise((resolve2) => {
149
149
  process.stdin.setRawMode(true);
150
150
  process.stdin.resume();
151
151
  process.stdin.setEncoding("utf8");
@@ -160,7 +160,7 @@ async function select(prompt2, choices) {
160
160
  } else if (key === "\r" || key === "\n" || key === " ") {
161
161
  cleanup();
162
162
  process.stdout.write("\n");
163
- resolve(choices[idx].value);
163
+ resolve2(choices[idx].value);
164
164
  } else if (key === "") {
165
165
  cleanup();
166
166
  process.stdout.write("\n");
@@ -258,7 +258,7 @@ function statusBadge(status) {
258
258
  // src/commands/login.ts
259
259
  var CONFIG_FILE2 = (0, import_path2.join)((0, import_os2.homedir)(), ".agc", "config.json");
260
260
  function prompt(question, hidden = false) {
261
- return new Promise((resolve) => {
261
+ return new Promise((resolve2) => {
262
262
  const rl = readline.createInterface({
263
263
  input: process.stdin,
264
264
  output: hidden ? void 0 : process.stdout,
@@ -269,85 +269,96 @@ function prompt(question, hidden = false) {
269
269
  process.stdin.once("data", (data) => {
270
270
  process.stdout.write("\n");
271
271
  rl.close();
272
- resolve(data.toString().trim());
272
+ resolve2(data.toString().trim());
273
273
  });
274
274
  process.stdin.setRawMode?.(false);
275
275
  } else {
276
276
  rl.question(question, (ans) => {
277
277
  rl.close();
278
- resolve(ans.trim());
278
+ resolve2(ans.trim());
279
279
  });
280
280
  }
281
281
  });
282
282
  }
283
283
  function loginCommand() {
284
284
  const cmd = new import_commander.Command("login").description("Configure API credentials");
285
- cmd.option("--api-url <url>", "API base URL", DEFAULT_API_URL).option("--api-key <key>", "API key (or set AGC_API_KEY env var)").option("--initiator <id>", "Default initiator ID (wallet address or user ID)").action(async (opts) => {
285
+ cmd.option("--api-url <url>", "API base URL", DEFAULT_API_URL).option("--api-key <key>", "API key (or set AGC_API_KEY env var)").option("--initiator <id>", "User/initiator ID (advanced \u2014 usually auto-detected)").action(async (opts) => {
286
286
  try {
287
287
  const current = loadConfig();
288
288
  const isFirstRun = !(0, import_fs2.existsSync)(CONFIG_FILE2);
289
289
  banner();
290
290
  if (isFirstRun) {
291
291
  console.log(c.bold(" Welcome to Agent Commons CLI!"));
292
- console.log(c.dim(" Let's get you set up in three quick steps.\n"));
292
+ console.log(c.dim(" You just need an API key to get started.\n"));
293
293
  } else {
294
294
  console.log(c.bold(" Update your credentials"));
295
295
  console.log(c.dim(" Press Enter to keep existing values.\n"));
296
296
  }
297
- step(1, 3, "API Endpoint");
298
- const defaultUrl = current.apiUrl ?? DEFAULT_API_URL;
299
297
  let apiUrl;
300
298
  if (opts.apiUrl !== DEFAULT_API_URL) {
301
299
  apiUrl = opts.apiUrl;
302
- console.log(` ${c.dim("Using:")} ${apiUrl}`);
300
+ console.log(` ${c.dim("Using API endpoint:")} ${apiUrl}
301
+ `);
302
+ } else if (current.apiUrl && current.apiUrl !== DEFAULT_API_URL) {
303
+ apiUrl = current.apiUrl;
304
+ console.log(` ${c.dim("Using existing endpoint:")} ${apiUrl}
305
+ `);
303
306
  } else {
304
- const answer = await prompt(
305
- ` ${c.dim("URL")} [${c.dim(defaultUrl)}]: `
306
- );
307
- apiUrl = answer || defaultUrl;
307
+ apiUrl = DEFAULT_API_URL;
308
308
  }
309
- console.log(` ${sym.ok} ${c.dim("Endpoint:")} ${c.primary(apiUrl)}`);
310
309
  const appUrl = apiUrl.includes("localhost") ? "http://localhost:3000" : DEFAULT_APP_URL;
311
- const settingsUrl = `${appUrl}/settings`;
312
- step(2, 3, "API Key");
310
+ const apiKeysUrl = `${appUrl}/settings/api-keys`;
311
+ step(1, 1, "API Key");
313
312
  let apiKey = opts.apiKey;
314
313
  if (!apiKey) {
315
- console.log(` ${sym.arrow} Opening your browser to generate an API key\u2026`);
316
- console.log(` ${c.dim(settingsUrl)}
314
+ console.log(` ${c.dim("You'll need an API key from your Agent Commons account.")}`);
315
+ console.log(` ${c.dim("We'll open the API Keys page in your browser.")}
316
+ `);
317
+ console.log(` ${c.dim("On that page:")}`);
318
+ console.log(` ${sym.bullet} ${c.dim("Click")} ${c.bold('"Generate new key"')}`);
319
+ console.log(` ${sym.bullet} ${c.dim("Copy the key (it starts with")} ${c.bold("sk-ac-\u2026")}${c.dim(")")}`);
320
+ console.log(` ${sym.bullet} ${c.dim("Paste it here when prompted")}
321
+ `);
322
+ const openNow = await prompt(` ${c.dim("Open browser now? [Y/n]:")} `);
323
+ if (!openNow || openNow.toLowerCase() !== "n") {
324
+ openBrowser(apiKeysUrl);
325
+ console.log(` ${sym.ok} ${c.dim("Opened:")} ${c.primary(apiKeysUrl)}
317
326
  `);
318
- openBrowser(settingsUrl);
319
- console.log(c.dim(" Once you have your key, paste it below."));
320
- console.log(c.dim(" (Keys look like: sk-ac-xxxxxxxxxxxxxxxx)\n"));
321
- apiKey = await prompt(` ${c.dim("API Key:")} `);
327
+ } else {
328
+ console.log(` ${c.dim("You can open it manually:")} ${c.primary(apiKeysUrl)}
329
+ `);
330
+ }
331
+ console.log(c.dim(" Paste your API key below (input is hidden):"));
332
+ apiKey = await prompt(` ${c.dim("API Key:")} `, true);
322
333
  if (!apiKey) apiKey = current.apiKey;
323
334
  }
324
335
  if (!apiKey) {
325
336
  console.log(`
326
- ${c.warn("\u26A0")} No API key provided \u2014 you can set one later with ${c.bold("agc config set apiKey <key>")}`);
337
+ ${c.warn("\u26A0")} No API key provided \u2014 set one later with ${c.bold("agc config set apiKey <key>")}`);
327
338
  } else {
328
339
  console.log(` ${sym.ok} ${c.dim("Key saved:")} ****${apiKey.slice(-4)}`);
329
340
  }
330
- step(3, 3, "Your Identity");
331
- let initiator = opts.initiator;
332
- if (!initiator) {
333
- const defaultInitiator = current.initiator ?? "";
334
- const hint = defaultInitiator ? `[${c.dim(defaultInitiator.slice(0, 8) + "\u2026")}] ` : "";
335
- initiator = await prompt(` ${c.dim("Wallet address (0x\u2026):")} ${hint}`);
336
- if (!initiator) initiator = current.initiator;
337
- }
338
- if (!initiator) {
339
- console.log(` ${c.warn("\u26A0")} No wallet address \u2014 set one later with ${c.bold("agc config set initiator <address>")}`);
340
- } else {
341
- console.log(` ${sym.ok} ${c.dim("Address:")} ${c.id(initiator.slice(0, 10) + "\u2026" + initiator.slice(-6))}`);
341
+ let initiator = opts.initiator ?? current.initiator;
342
+ if (!initiator && apiKey) {
343
+ try {
344
+ const { CommonsClient: CommonsClient2 } = await import("@agent-commons/sdk");
345
+ const client = new CommonsClient2({ baseUrl: apiUrl, apiKey });
346
+ const me = await client.auth.me();
347
+ if (me?.principalId && me.principalType === "user") {
348
+ initiator = me.principalId;
349
+ console.log(` ${sym.ok} ${c.dim("Identity detected:")} ${c.id(initiator.slice(0, 10) + "\u2026" + initiator.slice(-6))}`);
350
+ }
351
+ } catch {
352
+ }
342
353
  }
343
- saveConfig({ apiUrl, apiKey, initiator });
354
+ saveConfig({ apiUrl, apiKey, ...initiator ? { initiator } : {} });
344
355
  console.log(`
345
356
  ${sym.ok} ${c.success("All set!")} Credentials saved to ${c.dim("~/.agc/config.json")}`);
346
357
  console.log(`
347
358
  ${c.dim("Next steps:")}`);
348
- console.log(` ${sym.arrow} ${c.dim("Run")} ${c.bold("agc")} ${c.dim("for an interactive menu")}`);
349
- console.log(` ${sym.arrow} ${c.dim("Run")} ${c.bold("agc whoami")} ${c.dim("to verify your connection")}`);
350
- console.log(` ${sym.arrow} ${c.dim("Run")} ${c.bold("agc chat --agent <id>")} ${c.dim("to start chatting")}
359
+ console.log(` ${sym.arrow} ${c.dim("Run")} ${c.bold("agc")} ${c.dim("to open the interactive menu")}`);
360
+ console.log(` ${sym.arrow} ${c.dim("Run")} ${c.bold("agc agents list")} ${c.dim("to see your agents")}`);
361
+ console.log(` ${sym.arrow} ${c.dim("Run")} ${c.bold("agc chat")} ${c.dim("to start chatting with an agent")}
351
362
  `);
352
363
  } catch (err) {
353
364
  printError(err);
@@ -426,10 +437,6 @@ function agentsCommand() {
426
437
  const cmd = new import_commander2.Command("agents").description("Manage agents");
427
438
  cmd.command("list").description("List agents owned by the current initiator").option("--json", "Output as JSON").action(async (opts) => {
428
439
  const cfg = loadConfig();
429
- if (!cfg.initiator) {
430
- console.error(c.error("No initiator set. Run `agc login` first."));
431
- process.exit(1);
432
- }
433
440
  const spinner = spin("Fetching agents\u2026");
434
441
  try {
435
442
  const client = makeClient();
@@ -1240,16 +1247,301 @@ ${sym.fail} ${c.error(event.message ?? "Error")}`);
1240
1247
 
1241
1248
  // src/commands/chat.ts
1242
1249
  var import_commander8 = require("commander");
1250
+ var readline3 = __toESM(require("readline"));
1251
+ var import_fs4 = require("fs");
1252
+ var import_path4 = require("path");
1253
+ var import_os3 = require("os");
1254
+
1255
+ // src/local-tools.ts
1256
+ var import_fs3 = require("fs");
1257
+ var import_path3 = require("path");
1258
+ var import_child_process2 = require("child_process");
1243
1259
  var readline2 = __toESM(require("readline"));
1260
+ function buildLocalToolsManifest(rootDir) {
1261
+ return `## Local File System Access
1262
+
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.
1264
+
1265
+ **Session root:** ${rootDir}
1266
+ All paths are relative to the session root unless absolute.
1267
+
1268
+ ---
1269
+
1270
+ ### How to call a tool
1271
+
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:
1273
+
1274
+ \`\`\`tool
1275
+ {"tool": "<tool_name>", "args": {"<arg>": "<value>"}}
1276
+ \`\`\`
1277
+
1278
+ You may call tools multiple times in sequence. Each call will be executed and the result returned to you before you continue.
1279
+
1280
+ ---
1281
+
1282
+ ### Available tools
1283
+
1284
+ **read_file** \u2014 Read the full contents of a file.
1285
+ \`\`\`tool
1286
+ {"tool": "read_file", "args": {"path": "src/index.ts"}}
1287
+ \`\`\`
1288
+
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
+ \`\`\`
1293
+
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
+ \`\`\`
1298
+
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
+ \`\`\`
1303
+
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
+ \`\`\`
1308
+
1309
+ ---
1310
+
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.
1315
+ `;
1316
+ }
1317
+ var TOOL_CALL_RE = /```tool\s*\n([\s\S]*?)\n```/;
1318
+ function extractToolCall(text) {
1319
+ const match = text.match(TOOL_CALL_RE);
1320
+ if (!match) return null;
1321
+ try {
1322
+ const parsed = JSON.parse(match[1].trim());
1323
+ if (typeof parsed.tool === "string") return parsed;
1324
+ } catch {
1325
+ }
1326
+ return null;
1327
+ }
1328
+ function safePath(root, userPath) {
1329
+ const abs = (0, import_path3.resolve)(root, userPath);
1330
+ const rel = (0, import_path3.relative)(root, abs);
1331
+ if (rel.startsWith("..") || rel.startsWith("/")) {
1332
+ throw new Error(`Path "${userPath}" escapes the session root. Access denied.`);
1333
+ }
1334
+ return abs;
1335
+ }
1336
+ var BLOCKED_PATTERNS = [
1337
+ /\/\.ssh\//,
1338
+ /\/\.gnupg\//,
1339
+ /\/\.agc\//,
1340
+ /\/\.aws\//,
1341
+ /\/\.env$/,
1342
+ /\/\.env\./,
1343
+ /id_rsa/,
1344
+ /id_ed25519/
1345
+ ];
1346
+ function assertNotSensitive(abs) {
1347
+ for (const pat of BLOCKED_PATTERNS) {
1348
+ if (pat.test(abs)) {
1349
+ throw new Error(`Access to "${abs}" is blocked for security reasons.`);
1350
+ }
1351
+ }
1352
+ }
1353
+ async function confirm(message, config, permissionKey) {
1354
+ const cached = config.permissions.get(permissionKey);
1355
+ if (cached === "allow") return true;
1356
+ if (cached === "deny") return false;
1357
+ return new Promise((resolve2) => {
1358
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
1359
+ process.stdout.write(
1360
+ `
1361
+ \x1B[33m\u26A0\x1B[0m ${message}
1362
+ \x1B[2m[y] Yes [n] No [A] Always allow this type [N] Never allow this type\x1B[0m
1363
+ \x1B[36m?\x1B[0m `
1364
+ );
1365
+ rl.once("line", (answer) => {
1366
+ rl.close();
1367
+ const a = answer.trim().toLowerCase();
1368
+ if (a === "a") {
1369
+ config.permissions.set(permissionKey, "allow");
1370
+ resolve2(true);
1371
+ } else if (a === "n" || a === "nn") {
1372
+ config.permissions.set(permissionKey, "deny");
1373
+ resolve2(false);
1374
+ } else {
1375
+ resolve2(a === "y" || a === "yes" || a === "");
1376
+ }
1377
+ });
1378
+ });
1379
+ }
1380
+ async function toolReadFile(args, cfg) {
1381
+ const { path: userPath } = args;
1382
+ if (!userPath) throw new Error('read_file requires a "path" argument');
1383
+ const abs = safePath(cfg.rootDir, userPath);
1384
+ assertNotSensitive(abs);
1385
+ if (!(0, import_fs3.existsSync)(abs)) throw new Error(`File not found: ${userPath}`);
1386
+ const stat = (0, import_fs3.statSync)(abs);
1387
+ if (stat.isDirectory()) throw new Error(`"${userPath}" is a directory, not a file`);
1388
+ if (stat.size > 5e5) throw new Error(`File too large to read (${Math.round(stat.size / 1024)} KB). Max 500 KB.`);
1389
+ return (0, import_fs3.readFileSync)(abs, "utf8");
1390
+ }
1391
+ async function toolWriteFile(args, cfg) {
1392
+ const { path: userPath, content } = args;
1393
+ if (!userPath) throw new Error('write_file requires a "path" argument');
1394
+ if (content === void 0) throw new Error('write_file requires a "content" argument');
1395
+ const abs = safePath(cfg.rootDir, userPath);
1396
+ assertNotSensitive(abs);
1397
+ const ok = await confirm(
1398
+ `Agent wants to write file: \x1B[1m${abs}\x1B[0m (${String(content).length} chars)`,
1399
+ cfg,
1400
+ "write_file"
1401
+ );
1402
+ if (!ok) return "User denied write operation.";
1403
+ (0, import_fs3.mkdirSync)((0, import_path3.dirname)(abs), { recursive: true });
1404
+ (0, import_fs3.writeFileSync)(abs, content, "utf8");
1405
+ return `Written ${String(content).length} bytes to ${userPath}`;
1406
+ }
1407
+ async function toolListDirectory(args, cfg) {
1408
+ const userPath = args.path ?? ".";
1409
+ const abs = safePath(cfg.rootDir, userPath);
1410
+ assertNotSensitive(abs);
1411
+ if (!(0, import_fs3.existsSync)(abs)) throw new Error(`Directory not found: ${userPath}`);
1412
+ const entries = (0, import_fs3.readdirSync)(abs, { withFileTypes: true });
1413
+ const lines = entries.map((e) => {
1414
+ const type = e.isDirectory() ? "d" : e.isSymbolicLink() ? "l" : "f";
1415
+ return `[${type}] ${e.name}`;
1416
+ });
1417
+ return lines.join("\n") || "(empty directory)";
1418
+ }
1419
+ async function toolSearchFiles(args, cfg) {
1420
+ const { pattern, directory } = args;
1421
+ if (!pattern) throw new Error('search_files requires a "pattern" argument');
1422
+ const baseDir = safePath(cfg.rootDir, directory ?? ".");
1423
+ assertNotSensitive(baseDir);
1424
+ const results = [];
1425
+ const pat = new RegExp(
1426
+ pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, "."),
1427
+ "i"
1428
+ );
1429
+ function walk(dir, depth = 0) {
1430
+ if (results.length >= 50 || depth > 10) return;
1431
+ try {
1432
+ for (const entry of (0, import_fs3.readdirSync)(dir, { withFileTypes: true })) {
1433
+ if (entry.name.startsWith(".") && depth > 0) continue;
1434
+ const full = (0, import_path3.join)(dir, entry.name);
1435
+ const rel = (0, import_path3.relative)(cfg.rootDir, full);
1436
+ if (pat.test(entry.name) || pat.test(rel)) results.push(rel);
1437
+ if (entry.isDirectory()) walk(full, depth + 1);
1438
+ }
1439
+ } catch {
1440
+ }
1441
+ }
1442
+ walk(baseDir);
1443
+ return results.length ? results.join("\n") : "No files found matching: " + pattern;
1444
+ }
1445
+ async function toolRunCommand(args, cfg) {
1446
+ const { command, args: cmdArgs = [], cwd } = args;
1447
+ if (!command || typeof command !== "string") throw new Error('run_command requires a "command" string');
1448
+ if (!Array.isArray(cmdArgs)) throw new Error('"args" must be an array of strings');
1449
+ const workDir = cwd ? safePath(cfg.rootDir, cwd) : cfg.rootDir;
1450
+ const preview = [command, ...cmdArgs].join(" ");
1451
+ const ok = await confirm(
1452
+ `Agent wants to run: \x1B[1m${preview}\x1B[0m
1453
+ \x1B[2min: ${workDir}\x1B[0m`,
1454
+ cfg,
1455
+ "run_command"
1456
+ );
1457
+ if (!ok) return "User denied command execution.";
1458
+ return new Promise((resolve2) => {
1459
+ (0, import_child_process2.execFile)(command, cmdArgs.map(String), { cwd: workDir, timeout: 3e4, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
1460
+ const out = [stdout, stderr].filter(Boolean).join("\n--- stderr ---\n");
1461
+ if (err && !out) return resolve2(`Error: ${err.message}`);
1462
+ resolve2(out || "(no output)");
1463
+ });
1464
+ });
1465
+ }
1466
+ async function runLocalTool(call, cfg) {
1467
+ const { tool, args } = call;
1468
+ cfg.appendLog({
1469
+ type: "local_tool_call",
1470
+ tool,
1471
+ args,
1472
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1473
+ });
1474
+ let result;
1475
+ try {
1476
+ switch (tool) {
1477
+ case "read_file":
1478
+ result = await toolReadFile(args, cfg);
1479
+ break;
1480
+ case "write_file":
1481
+ result = await toolWriteFile(args, cfg);
1482
+ break;
1483
+ case "list_directory":
1484
+ result = await toolListDirectory(args, cfg);
1485
+ break;
1486
+ case "search_files":
1487
+ result = await toolSearchFiles(args, cfg);
1488
+ break;
1489
+ case "run_command":
1490
+ result = await toolRunCommand(args, cfg);
1491
+ break;
1492
+ default:
1493
+ result = `Unknown tool: "${tool}". Available: read_file, write_file, list_directory, search_files, run_command`;
1494
+ }
1495
+ } catch (err) {
1496
+ result = `Error: ${err?.message ?? String(err)}`;
1497
+ }
1498
+ cfg.appendLog({
1499
+ type: "local_tool_result",
1500
+ tool,
1501
+ result: result.slice(0, 2e3),
1502
+ // cap log size
1503
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1504
+ });
1505
+ return result;
1506
+ }
1507
+
1508
+ // src/commands/chat.ts
1509
+ var SESSIONS_DIR = (0, import_path4.join)((0, import_os3.homedir)(), ".agc", "sessions");
1510
+ function ensureSessionsDir() {
1511
+ if (!(0, import_fs4.existsSync)(SESSIONS_DIR)) (0, import_fs4.mkdirSync)(SESSIONS_DIR, { recursive: true });
1512
+ }
1513
+ function appendSessionLog(sessionId, record) {
1514
+ try {
1515
+ ensureSessionsDir();
1516
+ const file = (0, import_path4.join)(SESSIONS_DIR, `${sessionId}.jsonl`);
1517
+ (0, import_fs4.appendFileSync)(file, JSON.stringify(record) + "\n", { mode: 384 });
1518
+ } catch {
1519
+ }
1520
+ }
1244
1521
  var HELP_TEXT = `
1245
1522
  ${c.label("Slash commands")}
1246
1523
  /help Show this help
1247
1524
  /session Print the current session ID (copy it to resume later)
1525
+ /tools Show local tool status and permissions (--local mode)
1248
1526
  /clear Clear the terminal screen
1249
1527
  /quit Exit (session is preserved \u2014 resume with --resume <id>)
1250
1528
  `;
1529
+ var LOCAL_TOOLS_DISCLAIMER = `
1530
+ ${c.warn("\u26A0")} ${c.bold("Local file system access enabled")}
1531
+
1532
+ ${c.dim("The agent can read and write files, list directories, search files,")}
1533
+ ${c.dim("and execute shell commands on your machine.")}
1534
+
1535
+ ${c.dim("Rules:")}
1536
+ ${sym.bullet} ${c.dim("All paths are restricted to:")} ${c.primary(process.cwd())}
1537
+ ${sym.bullet} ${c.dim("Sensitive paths (.ssh, .env, .aws, credentials) are always blocked")}
1538
+ ${sym.bullet} ${c.dim("Write and run_command operations require your confirmation")}
1539
+ ${sym.bullet} ${c.dim("You can deny any individual request")}
1540
+
1541
+ ${c.dim("Session activity is logged to")} ${c.primary("~/.agc/sessions/")}
1542
+ `;
1251
1543
  function chatCommand() {
1252
- return new import_commander8.Command("chat").description("Start an interactive chat REPL with an agent").option("--agent <agentId>", "Agent ID (or set defaultAgentId in config)").option("--resume <sessionId>", "Resume an existing session by ID").option("--no-stream", "Disable token streaming (wait for full response)").action(async (opts) => {
1544
+ return new import_commander8.Command("chat").description("Start an interactive chat REPL with an agent").option("--agent <agentId>", "Agent ID (or set defaultAgentId in config)").option("--resume <sessionId>", "Resume an existing session by ID").option("--no-stream", "Disable token streaming (wait for full response)").option("--local", "Enable local file system access for the agent (see disclaimer)").action(async (opts) => {
1253
1545
  const cfg = loadConfig();
1254
1546
  const agentId = opts.agent ?? cfg.defaultAgentId;
1255
1547
  if (!agentId) {
@@ -1263,17 +1555,27 @@ function chatCommand() {
1263
1555
  const client = makeClient();
1264
1556
  let sessionId = opts.resume ?? "";
1265
1557
  const isResume = !!opts.resume;
1558
+ const initiator = cfg.initiator ?? "";
1266
1559
  if (!isResume) {
1267
1560
  const spinner = spin("Creating session\u2026");
1268
1561
  try {
1269
1562
  const res = await client.sessions.create({
1270
1563
  agentId,
1271
- initiator: cfg.initiator,
1272
- title: `agc chat ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`
1564
+ initiator,
1565
+ title: `agc chat ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`,
1566
+ source: "cli"
1273
1567
  });
1274
1568
  const session = res?.data ?? res;
1275
1569
  sessionId = session.sessionId;
1276
1570
  spinner.stop();
1571
+ appendSessionLog(sessionId, {
1572
+ type: "session_start",
1573
+ sessionId,
1574
+ agentId,
1575
+ initiator,
1576
+ source: "cli",
1577
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1578
+ });
1277
1579
  } catch (err) {
1278
1580
  spinner.stop();
1279
1581
  printError(err);
@@ -1316,9 +1618,26 @@ ${c.bold("Agent Commons Chat")}`);
1316
1618
  ["Session", c.id(sessionId) + (isResume ? c.dim(" (resumed)") : c.dim(" (new)"))]
1317
1619
  ];
1318
1620
  if (walletLine) headerRows.push(["Wallet", walletLine]);
1621
+ if (opts.local) headerRows.push(["Local tools", c.success("enabled") + c.dim(" (read, write, search, run)")]);
1319
1622
  detail(headerRows);
1623
+ let localToolsCfg = null;
1624
+ if (opts.local) {
1625
+ console.log(LOCAL_TOOLS_DISCLAIMER);
1626
+ const rootDir = process.cwd();
1627
+ localToolsCfg = {
1628
+ rootDir,
1629
+ sessionId,
1630
+ appendLog: (record) => appendSessionLog(sessionId, record),
1631
+ permissions: /* @__PURE__ */ new Map()
1632
+ };
1633
+ appendSessionLog(sessionId, {
1634
+ type: "local_tools_enabled",
1635
+ rootDir,
1636
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1637
+ });
1638
+ }
1320
1639
  console.log(c.dim("\nType your message and press Enter. Type /help for commands.\n"));
1321
- const rl = readline2.createInterface({
1640
+ const rl = readline3.createInterface({
1322
1641
  input: process.stdin,
1323
1642
  output: process.stdout,
1324
1643
  terminal: true,
@@ -1342,6 +1661,26 @@ Session saved. Resume with: agc chat --resume ${sessionId}`));
1342
1661
  rl.prompt();
1343
1662
  return;
1344
1663
  }
1664
+ if (input === "/tools") {
1665
+ if (!localToolsCfg) {
1666
+ console.log(c.dim(` Local tools are disabled. Restart with ${c.bold("agc chat --local")} to enable them.`));
1667
+ } else {
1668
+ console.log(`
1669
+ ${c.bold("Local tools")} ${c.success("enabled")}`);
1670
+ console.log(` ${c.dim("Root directory:")} ${c.primary(localToolsCfg.rootDir)}`);
1671
+ const perms = [...localToolsCfg.permissions.entries()];
1672
+ if (perms.length) {
1673
+ console.log(` ${c.dim("Cached permissions:")}`);
1674
+ for (const [k, v] of perms) {
1675
+ const badge = v === "allow" ? c.success("allow") : c.error("deny");
1676
+ console.log(` ${sym.bullet} ${k}: ${badge}`);
1677
+ }
1678
+ }
1679
+ }
1680
+ console.log();
1681
+ rl.prompt();
1682
+ return;
1683
+ }
1345
1684
  if (input === "/session") {
1346
1685
  console.log(c.dim(` ${sessionId}`));
1347
1686
  console.log(c.dim(` Resume with: agc chat --resume ${sessionId}`));
@@ -1359,10 +1698,20 @@ Session saved. Resume with: agc chat --resume ${sessionId}`));
1359
1698
  return;
1360
1699
  }
1361
1700
  rl.pause();
1701
+ appendSessionLog(sessionId, {
1702
+ type: "message",
1703
+ role: "user",
1704
+ content: input,
1705
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1706
+ });
1707
+ const outgoingMessages = localToolsCfg ? [
1708
+ { role: "system", content: buildLocalToolsManifest(localToolsCfg.rootDir) },
1709
+ { role: "user", content: input }
1710
+ ] : [{ role: "user", content: input }];
1362
1711
  const params = {
1363
1712
  agentId,
1364
1713
  sessionId,
1365
- messages: [{ role: "user", content: input }]
1714
+ messages: outgoingMessages
1366
1715
  };
1367
1716
  process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
1368
1717
  if (opts.noStream) {
@@ -1372,6 +1721,12 @@ Session saved. Resume with: agc chat --resume ${sessionId}`));
1372
1721
  spinner.stop();
1373
1722
  const text = extractText(result);
1374
1723
  console.log(text);
1724
+ appendSessionLog(sessionId, {
1725
+ type: "message",
1726
+ role: "assistant",
1727
+ content: text,
1728
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1729
+ });
1375
1730
  } catch (err) {
1376
1731
  spinner.stop();
1377
1732
  console.error(`
@@ -1380,9 +1735,12 @@ ${sym.fail} ${c.error(err.message ?? String(err))}`);
1380
1735
  } else {
1381
1736
  try {
1382
1737
  let hasOutput = false;
1738
+ let agentContent = "";
1383
1739
  for await (const event of client.agents.stream(params)) {
1384
1740
  if (event.type === "token") {
1385
- process.stdout.write(event.content ?? "");
1741
+ const tok = event.content ?? "";
1742
+ process.stdout.write(tok);
1743
+ agentContent += tok;
1386
1744
  hasOutput = true;
1387
1745
  } else if (event.type === "toolStart") {
1388
1746
  const name = event.toolName ?? "";
@@ -1396,13 +1754,34 @@ ${sym.fail} ${c.error(err.message ?? String(err))}`);
1396
1754
  } else if (event.type === "final") {
1397
1755
  const e = event;
1398
1756
  const text = extractText(e?.payload);
1399
- if (text && !hasOutput) process.stdout.write(text);
1757
+ if (text && !hasOutput) {
1758
+ process.stdout.write(text);
1759
+ agentContent += text;
1760
+ }
1400
1761
  const usage = e?.payload?.usage;
1401
1762
  if (usage) {
1402
- const tokens = usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
1763
+ const inputTok = usage.inputTokens ?? 0;
1764
+ const outputTok = usage.outputTokens ?? 0;
1765
+ const cachedTok = usage.cachedTokens ?? 0;
1766
+ const total = usage.totalTokens ?? inputTok + outputTok;
1403
1767
  const cost = typeof usage.costUsd === "number" ? `$${usage.costUsd.toFixed(4)}` : "";
1404
- const parts = [tokens ? `${tokens.toLocaleString()} tokens` : "", cost].filter(Boolean);
1768
+ const parts = [total ? `${total.toLocaleString()} tokens` : "", cost].filter(Boolean);
1405
1769
  if (parts.length) process.stdout.write("\n" + c.dim(` \u21B3 ${parts.join(" \xB7 ")}`));
1770
+ if (cachedTok > 0) process.stdout.write(c.dim(` (${cachedTok.toLocaleString()} cached)`));
1771
+ appendSessionLog(sessionId, {
1772
+ type: "message",
1773
+ role: "assistant",
1774
+ content: agentContent,
1775
+ usage: { inputTokens: inputTok, outputTokens: outputTok, cachedTokens: cachedTok, totalTokens: total, costUsd: usage.costUsd },
1776
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1777
+ });
1778
+ } else {
1779
+ appendSessionLog(sessionId, {
1780
+ type: "message",
1781
+ role: "assistant",
1782
+ content: agentContent,
1783
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1784
+ });
1406
1785
  }
1407
1786
  break;
1408
1787
  } else if (event.type === "error") {
@@ -1413,6 +1792,9 @@ ${sym.fail} ${c.error(event.message ?? "Stream error")}`);
1413
1792
  }
1414
1793
  }
1415
1794
  process.stdout.write("\n");
1795
+ if (localToolsCfg && agentContent) {
1796
+ await handleLocalToolLoop(agentContent, localToolsCfg, client, agentId, sessionId, appendSessionLog);
1797
+ }
1416
1798
  } catch (err) {
1417
1799
  process.stdout.write("\n");
1418
1800
  console.error(`${sym.fail} ${c.error(err.message ?? String(err))}`);
@@ -1432,6 +1814,78 @@ Session preserved. Resume with: agc chat --resume ${sessionId}`));
1432
1814
  });
1433
1815
  });
1434
1816
  }
1817
+ var MAX_TOOL_DEPTH = 10;
1818
+ async function handleLocalToolLoop(agentText, cfg, client, agentId, sessionId, appendLog, depth = 0) {
1819
+ if (depth >= MAX_TOOL_DEPTH) {
1820
+ console.log(c.dim(`
1821
+ [local] Max tool depth reached (${MAX_TOOL_DEPTH}). Stopping tool loop.
1822
+ `));
1823
+ return;
1824
+ }
1825
+ const toolCall = extractToolCall(agentText);
1826
+ if (!toolCall) return;
1827
+ process.stdout.write(c.dim(`
1828
+ [local] ${toolCall.tool}`));
1829
+ let result;
1830
+ try {
1831
+ result = await runLocalTool(toolCall, cfg);
1832
+ process.stdout.write(c.dim(" \u2713\n"));
1833
+ } catch (err) {
1834
+ result = `Error: ${err?.message ?? String(err)}`;
1835
+ process.stdout.write(c.dim(" \u2717\n"));
1836
+ }
1837
+ const resultMsg = `[Tool result: ${toolCall.tool}]
1838
+ \`\`\`
1839
+ ${result}
1840
+ \`\`\``;
1841
+ appendLog(sessionId, {
1842
+ type: "message",
1843
+ role: "tool",
1844
+ tool: toolCall.tool,
1845
+ result: result.slice(0, 4e3),
1846
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1847
+ });
1848
+ process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
1849
+ let followContent = "";
1850
+ try {
1851
+ for await (const evt of client.agents.stream({
1852
+ agentId,
1853
+ sessionId,
1854
+ messages: [{ role: "user", content: resultMsg }]
1855
+ })) {
1856
+ if (evt.type === "token") {
1857
+ const tok = evt.content ?? "";
1858
+ process.stdout.write(tok);
1859
+ followContent += tok;
1860
+ } else if (evt.type === "toolStart") {
1861
+ const name = evt.toolName ?? "";
1862
+ if (followContent) process.stdout.write("\n");
1863
+ process.stdout.write(c.dim(` [tool] ${name}\u2026`));
1864
+ } else if (evt.type === "toolEnd") {
1865
+ process.stdout.write(c.dim(" done\n"));
1866
+ process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
1867
+ } else if (evt.type === "final") {
1868
+ const txt = extractText(evt?.payload);
1869
+ if (txt && !followContent) {
1870
+ process.stdout.write(txt);
1871
+ followContent += txt;
1872
+ }
1873
+ appendLog(sessionId, { type: "message", role: "assistant", content: followContent, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
1874
+ break;
1875
+ } else if (evt.type === "error") {
1876
+ console.error(`
1877
+ ${sym.fail} ${c.error(evt.message ?? "Stream error")}`);
1878
+ break;
1879
+ }
1880
+ }
1881
+ process.stdout.write("\n");
1882
+ } catch (err) {
1883
+ process.stdout.write("\n");
1884
+ console.error(`${sym.fail} ${c.error(err?.message ?? String(err))}`);
1885
+ return;
1886
+ }
1887
+ await handleLocalToolLoop(followContent, cfg, client, agentId, sessionId, appendLog, depth + 1);
1888
+ }
1435
1889
  function extractText(payload) {
1436
1890
  if (!payload) return "";
1437
1891
  if (typeof payload === "string") return payload;
@@ -1970,10 +2424,10 @@ function skillsCommand() {
1970
2424
  });
1971
2425
  cmd.command("delete <slug>").description("Permanently delete a skill").option("--yes", "Skip confirmation prompt").option("--json", "Output result as JSON").action(async (slug, opts) => {
1972
2426
  if (!opts.yes) {
1973
- const readline3 = await import("readline");
1974
- const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
2427
+ const readline4 = await import("readline");
2428
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
1975
2429
  const answer = await new Promise(
1976
- (resolve) => rl.question(c.warn(`Delete skill "${slug}"? This cannot be undone. [y/N] `), resolve)
2430
+ (resolve2) => rl.question(c.warn(`Delete skill "${slug}"? This cannot be undone. [y/N] `), resolve2)
1977
2431
  );
1978
2432
  rl.close();
1979
2433
  if (!["y", "yes"].includes(answer.trim().toLowerCase())) {
@@ -2595,7 +3049,7 @@ function logsCommand() {
2595
3049
  }
2596
3050
 
2597
3051
  // src/bin.ts
2598
- var CONFIG_FILE3 = (0, import_path3.join)((0, import_os3.homedir)(), ".agc", "config.json");
3052
+ var CONFIG_FILE3 = (0, import_path5.join)((0, import_os4.homedir)(), ".agc", "config.json");
2599
3053
  async function interactiveMenu() {
2600
3054
  banner();
2601
3055
  const cfg = loadConfig();
@@ -2646,27 +3100,71 @@ async function interactiveMenu() {
2646
3100
  exit: []
2647
3101
  };
2648
3102
  if ((action === "chat" || action === "run") && !cfg.defaultAgentId) {
2649
- console.log(
2650
- `
2651
- ${c.warn("\u26A0")} No default agent configured.
2652
- ${sym.arrow} ${c.dim("Run")} ${c.bold("agc agents list")} ${c.dim("to find an agent ID, then")}
2653
- ${sym.arrow} ${c.dim("Run")} ${c.bold("agc config set defaultAgentId <id>")} ${c.dim("to set a default.")}
2654
- `
2655
- );
2656
- console.log(` ${c.dim("Or pass it directly: ")}${c.bold(`agc ${action} --agent <id>`)}
2657
- `);
3103
+ const pickedId = await pickAgentInteractively(action);
3104
+ if (!pickedId) return;
3105
+ runSubcommand([action, "--agent", pickedId]);
2658
3106
  return;
2659
3107
  }
2660
3108
  runSubcommand(commandMap[action]);
2661
3109
  }
2662
3110
  function runSubcommand(args) {
2663
- const child = (0, import_child_process2.spawn)(process.argv[0], [process.argv[1], ...args], {
3111
+ const child = (0, import_child_process3.spawn)(process.argv[0], [process.argv[1], ...args], {
2664
3112
  stdio: "inherit"
2665
3113
  });
2666
3114
  child.on("exit", (code) => process.exit(code ?? 0));
2667
3115
  }
3116
+ async function pickAgentInteractively(action) {
3117
+ const cfg = loadConfig();
3118
+ const spinner = spin("Fetching your agents\u2026");
3119
+ let agents = [];
3120
+ try {
3121
+ const client = makeClient();
3122
+ const res = await client.agents.list(cfg.initiator);
3123
+ agents = res?.data ?? (Array.isArray(res) ? res : []);
3124
+ spinner.stop();
3125
+ } catch {
3126
+ spinner.stop();
3127
+ console.log(`
3128
+ ${c.warn("\u26A0")} Could not fetch agents. Check your API key and connection.
3129
+ `);
3130
+ return null;
3131
+ }
3132
+ if (agents.length === 0) {
3133
+ console.log(`
3134
+ ${c.warn("\u26A0")} You don't have any agents yet.
3135
+ `);
3136
+ const choice = await select("What would you like to do?", [
3137
+ { label: "Create a new agent now", value: "create", hint: "agc agents create" },
3138
+ { label: "Go back", value: "cancel" }
3139
+ ]);
3140
+ if (choice === "create") {
3141
+ runSubcommand(["agents", "create"]);
3142
+ }
3143
+ return null;
3144
+ }
3145
+ console.log();
3146
+ const agentId = await select(
3147
+ `Choose an agent to ${action} with:`,
3148
+ agents.map((a) => ({
3149
+ label: a.name,
3150
+ value: a.agentId,
3151
+ hint: `${a.modelProvider}/${a.modelId}`
3152
+ }))
3153
+ );
3154
+ const saveDefault = await select("Set as your default agent?", [
3155
+ { label: "Yes \u2014 remember this agent for next time", value: true },
3156
+ { label: "No \u2014 just this once", value: false }
3157
+ ]);
3158
+ if (saveDefault) {
3159
+ saveConfig({ defaultAgentId: agentId });
3160
+ const chosen = agents.find((a) => a.agentId === agentId);
3161
+ console.log(` ${sym.ok} ${c.dim("Default agent set to")} ${c.bold(chosen?.name ?? agentId)}
3162
+ `);
3163
+ }
3164
+ return agentId;
3165
+ }
2668
3166
  var program = new import_commander16.Command();
2669
- program.name("agc").description("Agent Commons CLI \u2014 interact with the Agent Commons platform").version("0.1.4", "-v, --version").action(async () => {
3167
+ program.name("agc").description("Agent Commons CLI \u2014 interact with the Agent Commons platform").version("0.1.10", "-v, --version").action(async () => {
2670
3168
  await interactiveMenu();
2671
3169
  });
2672
3170
  program.addCommand(loginCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-commons/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.10",
4
4
  "description": "Agent Commons CLI — chat, run, and manage agents from your terminal",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -10,11 +10,16 @@
10
10
  "files": [
11
11
  "dist"
12
12
  ],
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "dev": "tsup --watch",
16
+ "typecheck": "tsc --noEmit"
17
+ },
13
18
  "dependencies": {
19
+ "@agent-commons/sdk": "workspace:*",
14
20
  "commander": "^12.1.0",
15
21
  "chalk": "^5.3.0",
16
- "ora": "^8.1.1",
17
- "@agent-commons/sdk": "0.1.6"
22
+ "ora": "^8.1.1"
18
23
  },
19
24
  "devDependencies": {
20
25
  "tsup": "^8.3.5",
@@ -26,10 +31,5 @@
26
31
  },
27
32
  "publishConfig": {
28
33
  "access": "public"
29
- },
30
- "scripts": {
31
- "build": "tsup",
32
- "dev": "tsup --watch",
33
- "typecheck": "tsc --noEmit"
34
34
  }
35
- }
35
+ }