@agent-commons/cli 0.1.5 → 0.1.7
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 +748 -47
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -25,10 +25,16 @@ 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_path5 = require("path");
|
|
29
|
+
var import_os4 = require("os");
|
|
30
|
+
var import_child_process3 = require("child_process");
|
|
28
31
|
|
|
29
32
|
// src/commands/login.ts
|
|
30
33
|
var import_commander = require("commander");
|
|
31
34
|
var readline = __toESM(require("readline"));
|
|
35
|
+
var import_fs2 = require("fs");
|
|
36
|
+
var import_path2 = require("path");
|
|
37
|
+
var import_os2 = require("os");
|
|
32
38
|
|
|
33
39
|
// src/config.ts
|
|
34
40
|
var import_fs = require("fs");
|
|
@@ -37,7 +43,8 @@ var import_path = require("path");
|
|
|
37
43
|
var import_sdk = require("@agent-commons/sdk");
|
|
38
44
|
var CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".agc");
|
|
39
45
|
var CONFIG_FILE = (0, import_path.join)(CONFIG_DIR, "config.json");
|
|
40
|
-
var DEFAULT_API_URL = process.env.AGC_API_URL ?? "
|
|
46
|
+
var DEFAULT_API_URL = process.env.AGC_API_URL ?? "https://api.agentcommons.io";
|
|
47
|
+
var DEFAULT_APP_URL = "https://www.agentcommons.io";
|
|
41
48
|
function loadConfig() {
|
|
42
49
|
const fromEnv = {
|
|
43
50
|
...process.env.AGC_API_URL && { apiUrl: process.env.AGC_API_URL },
|
|
@@ -81,6 +88,7 @@ function makeClient(overrides) {
|
|
|
81
88
|
// src/ui.ts
|
|
82
89
|
var import_chalk = __toESM(require("chalk"));
|
|
83
90
|
var import_ora = __toESM(require("ora"));
|
|
91
|
+
var import_child_process = require("child_process");
|
|
84
92
|
var c = {
|
|
85
93
|
primary: (s) => import_chalk.default.cyan(s),
|
|
86
94
|
success: (s) => import_chalk.default.green(s),
|
|
@@ -98,6 +106,80 @@ var sym = {
|
|
|
98
106
|
bullet: import_chalk.default.dim("\u2022"),
|
|
99
107
|
dot: import_chalk.default.dim("\xB7")
|
|
100
108
|
};
|
|
109
|
+
function banner(version = "0.1.4") {
|
|
110
|
+
const line = import_chalk.default.cyan(" \u2500".padEnd(2) + "\u2500".repeat(44));
|
|
111
|
+
console.log("");
|
|
112
|
+
console.log(line);
|
|
113
|
+
console.log(
|
|
114
|
+
import_chalk.default.cyan(" \u2502 ") + import_chalk.default.bold.white(" \u25C8 Agent Commons") + import_chalk.default.dim(" \xB7 CLI") + " " + import_chalk.default.cyan(`v${version}`)
|
|
115
|
+
);
|
|
116
|
+
console.log(import_chalk.default.cyan(" \u2502 ") + import_chalk.default.dim(" The Open AI Agent Network \xB7 agentcommons.io"));
|
|
117
|
+
console.log(line);
|
|
118
|
+
console.log("");
|
|
119
|
+
}
|
|
120
|
+
function step(n, total, title) {
|
|
121
|
+
const fraction = import_chalk.default.dim(`${n}/${total}`);
|
|
122
|
+
console.log(`
|
|
123
|
+
${import_chalk.default.cyan.bold(" Step " + n)} ${fraction} ${import_chalk.default.bold(title)}`);
|
|
124
|
+
console.log(import_chalk.default.dim(" " + "\u2500".repeat(38)));
|
|
125
|
+
}
|
|
126
|
+
async function select(prompt2, choices) {
|
|
127
|
+
if (!process.stdin.isTTY) {
|
|
128
|
+
return choices[0].value;
|
|
129
|
+
}
|
|
130
|
+
let idx = 0;
|
|
131
|
+
const total = choices.length;
|
|
132
|
+
const render = (first = false) => {
|
|
133
|
+
if (!first) {
|
|
134
|
+
process.stdout.write(`\x1B[${total + 2}A\x1B[0J`);
|
|
135
|
+
}
|
|
136
|
+
console.log("\n" + import_chalk.default.bold(" " + prompt2));
|
|
137
|
+
for (let i = 0; i < total; i++) {
|
|
138
|
+
const { label, hint } = choices[i];
|
|
139
|
+
if (i === idx) {
|
|
140
|
+
const hintStr = hint ? import_chalk.default.dim(" " + hint) : "";
|
|
141
|
+
process.stdout.write(import_chalk.default.cyan(" \u203A ") + import_chalk.default.bold.white(label) + hintStr + "\n");
|
|
142
|
+
} else {
|
|
143
|
+
process.stdout.write(import_chalk.default.dim(" " + label) + "\n");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
render(true);
|
|
148
|
+
return new Promise((resolve2) => {
|
|
149
|
+
process.stdin.setRawMode(true);
|
|
150
|
+
process.stdin.resume();
|
|
151
|
+
process.stdin.setEncoding("utf8");
|
|
152
|
+
const handler = (data) => {
|
|
153
|
+
const key = String(data);
|
|
154
|
+
if (key === "\x1B[A" || key === "k") {
|
|
155
|
+
idx = (idx - 1 + total) % total;
|
|
156
|
+
render();
|
|
157
|
+
} else if (key === "\x1B[B" || key === "j") {
|
|
158
|
+
idx = (idx + 1) % total;
|
|
159
|
+
render();
|
|
160
|
+
} else if (key === "\r" || key === "\n" || key === " ") {
|
|
161
|
+
cleanup();
|
|
162
|
+
process.stdout.write("\n");
|
|
163
|
+
resolve2(choices[idx].value);
|
|
164
|
+
} else if (key === "") {
|
|
165
|
+
cleanup();
|
|
166
|
+
process.stdout.write("\n");
|
|
167
|
+
process.exit(130);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const cleanup = () => {
|
|
171
|
+
process.stdin.removeListener("data", handler);
|
|
172
|
+
process.stdin.setRawMode(false);
|
|
173
|
+
process.stdin.pause();
|
|
174
|
+
};
|
|
175
|
+
process.stdin.on("data", handler);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function openBrowser(url) {
|
|
179
|
+
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
180
|
+
(0, import_child_process.exec)(cmd, () => {
|
|
181
|
+
});
|
|
182
|
+
}
|
|
101
183
|
function spin(text) {
|
|
102
184
|
return (0, import_ora.default)({ text, color: "cyan" }).start();
|
|
103
185
|
}
|
|
@@ -174,8 +256,9 @@ function statusBadge(status) {
|
|
|
174
256
|
}
|
|
175
257
|
|
|
176
258
|
// src/commands/login.ts
|
|
259
|
+
var CONFIG_FILE2 = (0, import_path2.join)((0, import_os2.homedir)(), ".agc", "config.json");
|
|
177
260
|
function prompt(question, hidden = false) {
|
|
178
|
-
return new Promise((
|
|
261
|
+
return new Promise((resolve2) => {
|
|
179
262
|
const rl = readline.createInterface({
|
|
180
263
|
input: process.stdin,
|
|
181
264
|
output: hidden ? void 0 : process.stdout,
|
|
@@ -186,42 +269,97 @@ function prompt(question, hidden = false) {
|
|
|
186
269
|
process.stdin.once("data", (data) => {
|
|
187
270
|
process.stdout.write("\n");
|
|
188
271
|
rl.close();
|
|
189
|
-
|
|
272
|
+
resolve2(data.toString().trim());
|
|
190
273
|
});
|
|
191
274
|
process.stdin.setRawMode?.(false);
|
|
192
275
|
} else {
|
|
193
276
|
rl.question(question, (ans) => {
|
|
194
277
|
rl.close();
|
|
195
|
-
|
|
278
|
+
resolve2(ans.trim());
|
|
196
279
|
});
|
|
197
280
|
}
|
|
198
281
|
});
|
|
199
282
|
}
|
|
200
283
|
function loginCommand() {
|
|
201
284
|
const cmd = new import_commander.Command("login").description("Configure API credentials");
|
|
202
|
-
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>", "
|
|
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) => {
|
|
203
286
|
try {
|
|
204
287
|
const current = loadConfig();
|
|
205
|
-
const
|
|
206
|
-
|
|
288
|
+
const isFirstRun = !(0, import_fs2.existsSync)(CONFIG_FILE2);
|
|
289
|
+
banner();
|
|
290
|
+
if (isFirstRun) {
|
|
291
|
+
console.log(c.bold(" Welcome to Agent Commons CLI!"));
|
|
292
|
+
console.log(c.dim(" You just need an API key to get started.\n"));
|
|
293
|
+
} else {
|
|
294
|
+
console.log(c.bold(" Update your credentials"));
|
|
295
|
+
console.log(c.dim(" Press Enter to keep existing values.\n"));
|
|
296
|
+
}
|
|
297
|
+
let apiUrl;
|
|
298
|
+
if (opts.apiUrl !== DEFAULT_API_URL) {
|
|
299
|
+
apiUrl = opts.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
|
+
`);
|
|
306
|
+
} else {
|
|
307
|
+
apiUrl = DEFAULT_API_URL;
|
|
308
|
+
}
|
|
309
|
+
const appUrl = apiUrl.includes("localhost") ? "http://localhost:3000" : DEFAULT_APP_URL;
|
|
310
|
+
const apiKeysUrl = `${appUrl}/settings/api-keys`;
|
|
311
|
+
step(1, 1, "API Key");
|
|
207
312
|
let apiKey = opts.apiKey;
|
|
208
313
|
if (!apiKey) {
|
|
209
|
-
console.log(`
|
|
210
|
-
|
|
211
|
-
${c.bold(`${appUrl}/settings/api-keys`)}
|
|
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.")}
|
|
212
316
|
`);
|
|
213
|
-
|
|
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)}
|
|
326
|
+
`);
|
|
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);
|
|
214
333
|
if (!apiKey) apiKey = current.apiKey;
|
|
215
334
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
335
|
+
if (!apiKey) {
|
|
336
|
+
console.log(`
|
|
337
|
+
${c.warn("\u26A0")} No API key provided \u2014 set one later with ${c.bold("agc config set apiKey <key>")}`);
|
|
338
|
+
} else {
|
|
339
|
+
console.log(` ${sym.ok} ${c.dim("Key saved:")} ****${apiKey.slice(-4)}`);
|
|
220
340
|
}
|
|
221
|
-
|
|
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
|
+
}
|
|
353
|
+
}
|
|
354
|
+
saveConfig({ apiUrl, apiKey, ...initiator ? { initiator } : {} });
|
|
222
355
|
console.log(`
|
|
223
|
-
${sym.ok} Credentials saved to ~/.agc/config.json`);
|
|
224
|
-
console.log(
|
|
356
|
+
${sym.ok} ${c.success("All set!")} Credentials saved to ${c.dim("~/.agc/config.json")}`);
|
|
357
|
+
console.log(`
|
|
358
|
+
${c.dim("Next steps:")}`);
|
|
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")}
|
|
362
|
+
`);
|
|
225
363
|
} catch (err) {
|
|
226
364
|
printError(err);
|
|
227
365
|
process.exit(1);
|
|
@@ -299,10 +437,6 @@ function agentsCommand() {
|
|
|
299
437
|
const cmd = new import_commander2.Command("agents").description("Manage agents");
|
|
300
438
|
cmd.command("list").description("List agents owned by the current initiator").option("--json", "Output as JSON").action(async (opts) => {
|
|
301
439
|
const cfg = loadConfig();
|
|
302
|
-
if (!cfg.initiator) {
|
|
303
|
-
console.error(c.error("No initiator set. Run `agc login` first."));
|
|
304
|
-
process.exit(1);
|
|
305
|
-
}
|
|
306
440
|
const spinner = spin("Fetching agents\u2026");
|
|
307
441
|
try {
|
|
308
442
|
const client = makeClient();
|
|
@@ -739,12 +873,12 @@ ${sym.ok} Execution started: ${c.id(execution.executionId)}`);
|
|
|
739
873
|
const steps = execution.stepResults ?? execution.nodeResults;
|
|
740
874
|
if (steps && Object.keys(steps).length > 0) {
|
|
741
875
|
console.log("\n" + c.label("Step Results"));
|
|
742
|
-
for (const [nodeId,
|
|
743
|
-
const icon =
|
|
744
|
-
const dur =
|
|
876
|
+
for (const [nodeId, step2] of Object.entries(steps)) {
|
|
877
|
+
const icon = step2.status === "success" ? sym.ok : step2.status === "error" ? sym.fail : "\xB7";
|
|
878
|
+
const dur = step2.duration != null ? c.dim(` (${(step2.duration / 1e3).toFixed(2)}s)`) : "";
|
|
745
879
|
console.log(` ${icon} ${c.id(nodeId)}${dur}`);
|
|
746
|
-
if (
|
|
747
|
-
else if (
|
|
880
|
+
if (step2.error) console.log(` ${c.error(step2.error)}`);
|
|
881
|
+
else if (step2.output !== void 0) console.log(` ${JSON.stringify(step2.output, null, 2).replace(/\n/g, "\n ")}`);
|
|
748
882
|
}
|
|
749
883
|
}
|
|
750
884
|
} else {
|
|
@@ -769,12 +903,12 @@ ${sym.ok} ${c.success("Completed")}`);
|
|
|
769
903
|
}
|
|
770
904
|
if (e.nodeResults && Object.keys(e.nodeResults).length > 0) {
|
|
771
905
|
console.log("\n" + c.label("Step Results"));
|
|
772
|
-
for (const [nodeId,
|
|
773
|
-
const icon =
|
|
774
|
-
const dur =
|
|
906
|
+
for (const [nodeId, step2] of Object.entries(e.nodeResults)) {
|
|
907
|
+
const icon = step2.status === "success" ? sym.ok : step2.status === "error" ? sym.fail : "\xB7";
|
|
908
|
+
const dur = step2.duration != null ? c.dim(` (${(step2.duration / 1e3).toFixed(2)}s)`) : "";
|
|
775
909
|
console.log(` ${icon} ${c.id(nodeId)}${dur}`);
|
|
776
|
-
if (
|
|
777
|
-
else if (
|
|
910
|
+
if (step2.error) console.log(` ${c.error(step2.error)}`);
|
|
911
|
+
else if (step2.output !== void 0) console.log(` ${JSON.stringify(step2.output, null, 2).replace(/\n/g, "\n ")}`);
|
|
778
912
|
}
|
|
779
913
|
}
|
|
780
914
|
break;
|
|
@@ -1113,16 +1247,301 @@ ${sym.fail} ${c.error(event.message ?? "Error")}`);
|
|
|
1113
1247
|
|
|
1114
1248
|
// src/commands/chat.ts
|
|
1115
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");
|
|
1116
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
|
+
}
|
|
1117
1521
|
var HELP_TEXT = `
|
|
1118
1522
|
${c.label("Slash commands")}
|
|
1119
1523
|
/help Show this help
|
|
1120
1524
|
/session Print the current session ID (copy it to resume later)
|
|
1525
|
+
/tools Show local tool status and permissions (--local mode)
|
|
1121
1526
|
/clear Clear the terminal screen
|
|
1122
1527
|
/quit Exit (session is preserved \u2014 resume with --resume <id>)
|
|
1123
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
|
+
`;
|
|
1124
1543
|
function chatCommand() {
|
|
1125
|
-
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) => {
|
|
1126
1545
|
const cfg = loadConfig();
|
|
1127
1546
|
const agentId = opts.agent ?? cfg.defaultAgentId;
|
|
1128
1547
|
if (!agentId) {
|
|
@@ -1136,17 +1555,27 @@ function chatCommand() {
|
|
|
1136
1555
|
const client = makeClient();
|
|
1137
1556
|
let sessionId = opts.resume ?? "";
|
|
1138
1557
|
const isResume = !!opts.resume;
|
|
1558
|
+
const initiator = cfg.initiator ?? "";
|
|
1139
1559
|
if (!isResume) {
|
|
1140
1560
|
const spinner = spin("Creating session\u2026");
|
|
1141
1561
|
try {
|
|
1142
1562
|
const res = await client.sessions.create({
|
|
1143
1563
|
agentId,
|
|
1144
|
-
initiator
|
|
1145
|
-
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"
|
|
1146
1567
|
});
|
|
1147
1568
|
const session = res?.data ?? res;
|
|
1148
1569
|
sessionId = session.sessionId;
|
|
1149
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
|
+
});
|
|
1150
1579
|
} catch (err) {
|
|
1151
1580
|
spinner.stop();
|
|
1152
1581
|
printError(err);
|
|
@@ -1189,9 +1618,26 @@ ${c.bold("Agent Commons Chat")}`);
|
|
|
1189
1618
|
["Session", c.id(sessionId) + (isResume ? c.dim(" (resumed)") : c.dim(" (new)"))]
|
|
1190
1619
|
];
|
|
1191
1620
|
if (walletLine) headerRows.push(["Wallet", walletLine]);
|
|
1621
|
+
if (opts.local) headerRows.push(["Local tools", c.success("enabled") + c.dim(" (read, write, search, run)")]);
|
|
1192
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
|
+
}
|
|
1193
1639
|
console.log(c.dim("\nType your message and press Enter. Type /help for commands.\n"));
|
|
1194
|
-
const rl =
|
|
1640
|
+
const rl = readline3.createInterface({
|
|
1195
1641
|
input: process.stdin,
|
|
1196
1642
|
output: process.stdout,
|
|
1197
1643
|
terminal: true,
|
|
@@ -1215,6 +1661,26 @@ Session saved. Resume with: agc chat --resume ${sessionId}`));
|
|
|
1215
1661
|
rl.prompt();
|
|
1216
1662
|
return;
|
|
1217
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
|
+
}
|
|
1218
1684
|
if (input === "/session") {
|
|
1219
1685
|
console.log(c.dim(` ${sessionId}`));
|
|
1220
1686
|
console.log(c.dim(` Resume with: agc chat --resume ${sessionId}`));
|
|
@@ -1232,10 +1698,20 @@ Session saved. Resume with: agc chat --resume ${sessionId}`));
|
|
|
1232
1698
|
return;
|
|
1233
1699
|
}
|
|
1234
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 }];
|
|
1235
1711
|
const params = {
|
|
1236
1712
|
agentId,
|
|
1237
1713
|
sessionId,
|
|
1238
|
-
messages:
|
|
1714
|
+
messages: outgoingMessages
|
|
1239
1715
|
};
|
|
1240
1716
|
process.stdout.write(c.primary("agent") + c.dim(" \u203A "));
|
|
1241
1717
|
if (opts.noStream) {
|
|
@@ -1245,6 +1721,12 @@ Session saved. Resume with: agc chat --resume ${sessionId}`));
|
|
|
1245
1721
|
spinner.stop();
|
|
1246
1722
|
const text = extractText(result);
|
|
1247
1723
|
console.log(text);
|
|
1724
|
+
appendSessionLog(sessionId, {
|
|
1725
|
+
type: "message",
|
|
1726
|
+
role: "assistant",
|
|
1727
|
+
content: text,
|
|
1728
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1729
|
+
});
|
|
1248
1730
|
} catch (err) {
|
|
1249
1731
|
spinner.stop();
|
|
1250
1732
|
console.error(`
|
|
@@ -1253,9 +1735,12 @@ ${sym.fail} ${c.error(err.message ?? String(err))}`);
|
|
|
1253
1735
|
} else {
|
|
1254
1736
|
try {
|
|
1255
1737
|
let hasOutput = false;
|
|
1738
|
+
let agentContent = "";
|
|
1256
1739
|
for await (const event of client.agents.stream(params)) {
|
|
1257
1740
|
if (event.type === "token") {
|
|
1258
|
-
|
|
1741
|
+
const tok = event.content ?? "";
|
|
1742
|
+
process.stdout.write(tok);
|
|
1743
|
+
agentContent += tok;
|
|
1259
1744
|
hasOutput = true;
|
|
1260
1745
|
} else if (event.type === "toolStart") {
|
|
1261
1746
|
const name = event.toolName ?? "";
|
|
@@ -1269,13 +1754,34 @@ ${sym.fail} ${c.error(err.message ?? String(err))}`);
|
|
|
1269
1754
|
} else if (event.type === "final") {
|
|
1270
1755
|
const e = event;
|
|
1271
1756
|
const text = extractText(e?.payload);
|
|
1272
|
-
if (text && !hasOutput)
|
|
1757
|
+
if (text && !hasOutput) {
|
|
1758
|
+
process.stdout.write(text);
|
|
1759
|
+
agentContent += text;
|
|
1760
|
+
}
|
|
1273
1761
|
const usage = e?.payload?.usage;
|
|
1274
1762
|
if (usage) {
|
|
1275
|
-
const
|
|
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;
|
|
1276
1767
|
const cost = typeof usage.costUsd === "number" ? `$${usage.costUsd.toFixed(4)}` : "";
|
|
1277
|
-
const parts = [
|
|
1768
|
+
const parts = [total ? `${total.toLocaleString()} tokens` : "", cost].filter(Boolean);
|
|
1278
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
|
+
});
|
|
1279
1785
|
}
|
|
1280
1786
|
break;
|
|
1281
1787
|
} else if (event.type === "error") {
|
|
@@ -1286,6 +1792,9 @@ ${sym.fail} ${c.error(event.message ?? "Stream error")}`);
|
|
|
1286
1792
|
}
|
|
1287
1793
|
}
|
|
1288
1794
|
process.stdout.write("\n");
|
|
1795
|
+
if (localToolsCfg && agentContent) {
|
|
1796
|
+
await handleLocalToolLoop(agentContent, localToolsCfg, client, agentId, sessionId, appendSessionLog);
|
|
1797
|
+
}
|
|
1289
1798
|
} catch (err) {
|
|
1290
1799
|
process.stdout.write("\n");
|
|
1291
1800
|
console.error(`${sym.fail} ${c.error(err.message ?? String(err))}`);
|
|
@@ -1305,6 +1814,78 @@ Session preserved. Resume with: agc chat --resume ${sessionId}`));
|
|
|
1305
1814
|
});
|
|
1306
1815
|
});
|
|
1307
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
|
+
}
|
|
1308
1889
|
function extractText(payload) {
|
|
1309
1890
|
if (!payload) return "";
|
|
1310
1891
|
if (typeof payload === "string") return payload;
|
|
@@ -1843,10 +2424,10 @@ function skillsCommand() {
|
|
|
1843
2424
|
});
|
|
1844
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) => {
|
|
1845
2426
|
if (!opts.yes) {
|
|
1846
|
-
const
|
|
1847
|
-
const rl =
|
|
2427
|
+
const readline4 = await import("readline");
|
|
2428
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
1848
2429
|
const answer = await new Promise(
|
|
1849
|
-
(
|
|
2430
|
+
(resolve2) => rl.question(c.warn(`Delete skill "${slug}"? This cannot be undone. [y/N] `), resolve2)
|
|
1850
2431
|
);
|
|
1851
2432
|
rl.close();
|
|
1852
2433
|
if (!["y", "yes"].includes(answer.trim().toLowerCase())) {
|
|
@@ -2468,8 +3049,124 @@ function logsCommand() {
|
|
|
2468
3049
|
}
|
|
2469
3050
|
|
|
2470
3051
|
// src/bin.ts
|
|
3052
|
+
var CONFIG_FILE3 = (0, import_path5.join)((0, import_os4.homedir)(), ".agc", "config.json");
|
|
3053
|
+
async function interactiveMenu() {
|
|
3054
|
+
banner();
|
|
3055
|
+
const cfg = loadConfig();
|
|
3056
|
+
const isSetup = !!(cfg.apiKey && cfg.initiator);
|
|
3057
|
+
if (!isSetup) {
|
|
3058
|
+
console.log(c.bold(" Welcome to Agent Commons CLI!"));
|
|
3059
|
+
console.log(c.dim(" Looks like this is your first time here \u2014 let's get you set up.\n"));
|
|
3060
|
+
console.log(` ${sym.arrow} Running ${c.bold("agc login")} to configure your credentials\u2026
|
|
3061
|
+
`);
|
|
3062
|
+
runSubcommand(["login"]);
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
console.log(
|
|
3066
|
+
` ${c.dim("Connected to")} ${c.primary(cfg.apiUrl)} ${c.dim("\xB7")} ${c.dim("Wallet")} ${c.id(cfg.initiator.slice(0, 8) + "\u2026" + cfg.initiator.slice(-4))}
|
|
3067
|
+
`
|
|
3068
|
+
);
|
|
3069
|
+
const action = await select("What would you like to do?", [
|
|
3070
|
+
{ label: "Chat with an agent", value: "chat", hint: "agc chat" },
|
|
3071
|
+
{ label: "Run an agent (one-shot)", value: "run", hint: "agc run" },
|
|
3072
|
+
{ label: "View sessions", value: "sessions", hint: "agc sessions list" },
|
|
3073
|
+
{ label: "Manage agents", value: "agents", hint: "agc agents list" },
|
|
3074
|
+
{ label: "Tasks", value: "tasks", hint: "agc task list" },
|
|
3075
|
+
{ label: "Workflows", value: "workflows", hint: "agc workflow list" },
|
|
3076
|
+
{ label: "MCP servers", value: "mcp", hint: "agc mcp list" },
|
|
3077
|
+
{ label: "Skills", value: "skills", hint: "agc skills list" },
|
|
3078
|
+
{ label: "Wallet & balance", value: "wallet", hint: "agc wallet balance" },
|
|
3079
|
+
{ label: "Usage & cost", value: "usage", hint: "agc usage" },
|
|
3080
|
+
{ label: "Logs", value: "logs", hint: "agc logs" },
|
|
3081
|
+
{ label: "Config & credentials", value: "config", hint: "agc config get" },
|
|
3082
|
+
{ label: "Exit", value: "exit" }
|
|
3083
|
+
]);
|
|
3084
|
+
if (action === "exit") {
|
|
3085
|
+
process.exit(0);
|
|
3086
|
+
}
|
|
3087
|
+
const commandMap = {
|
|
3088
|
+
chat: cfg.defaultAgentId ? ["chat", "--agent", cfg.defaultAgentId] : ["chat", "--agent"],
|
|
3089
|
+
run: cfg.defaultAgentId ? ["run", "--agent", cfg.defaultAgentId, "--message"] : ["run", "--agent"],
|
|
3090
|
+
sessions: ["sessions", "list"],
|
|
3091
|
+
agents: ["agents", "list"],
|
|
3092
|
+
tasks: ["task", "list"],
|
|
3093
|
+
workflows: ["workflow", "list"],
|
|
3094
|
+
mcp: ["mcp", "list"],
|
|
3095
|
+
skills: ["skills", "list"],
|
|
3096
|
+
wallet: ["wallet", "balance"],
|
|
3097
|
+
usage: ["usage"],
|
|
3098
|
+
logs: ["logs"],
|
|
3099
|
+
config: ["config", "get"],
|
|
3100
|
+
exit: []
|
|
3101
|
+
};
|
|
3102
|
+
if ((action === "chat" || action === "run") && !cfg.defaultAgentId) {
|
|
3103
|
+
const pickedId = await pickAgentInteractively(action);
|
|
3104
|
+
if (!pickedId) return;
|
|
3105
|
+
runSubcommand([action, "--agent", pickedId]);
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
runSubcommand(commandMap[action]);
|
|
3109
|
+
}
|
|
3110
|
+
function runSubcommand(args) {
|
|
3111
|
+
const child = (0, import_child_process3.spawn)(process.argv[0], [process.argv[1], ...args], {
|
|
3112
|
+
stdio: "inherit"
|
|
3113
|
+
});
|
|
3114
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
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
|
+
}
|
|
2471
3166
|
var program = new import_commander16.Command();
|
|
2472
|
-
program.name("agc").description("Agent Commons CLI \u2014 interact with the Agent Commons platform").version("0.1.
|
|
3167
|
+
program.name("agc").description("Agent Commons CLI \u2014 interact with the Agent Commons platform").version("0.1.4", "-v, --version").action(async () => {
|
|
3168
|
+
await interactiveMenu();
|
|
3169
|
+
});
|
|
2473
3170
|
program.addCommand(loginCommand());
|
|
2474
3171
|
program.addCommand(logoutCommand());
|
|
2475
3172
|
program.addCommand(whoamiCommand());
|
|
@@ -2489,8 +3186,12 @@ program.addCommand(memoryCommand());
|
|
|
2489
3186
|
program.addCommand(usageCommand());
|
|
2490
3187
|
program.addCommand(logsCommand());
|
|
2491
3188
|
program.on("command:*", () => {
|
|
2492
|
-
console.error(
|
|
2493
|
-
|
|
3189
|
+
console.error(
|
|
3190
|
+
`
|
|
3191
|
+
${c.error("Unknown command:")} ${program.args.join(" ")}
|
|
3192
|
+
Run ${c.bold("agc --help")} to see available commands, or just ${c.bold("agc")} for the interactive menu.
|
|
3193
|
+
`
|
|
3194
|
+
);
|
|
2494
3195
|
process.exit(1);
|
|
2495
3196
|
});
|
|
2496
3197
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-commons/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Agent Commons CLI — chat, run, and manage agents from your terminal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"commander": "^12.1.0",
|
|
15
15
|
"chalk": "^5.3.0",
|
|
16
16
|
"ora": "^8.1.1",
|
|
17
|
-
"@agent-commons/sdk": "0.1.
|
|
17
|
+
"@agent-commons/sdk": "0.1.7"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"tsup": "^8.3.5",
|