@agent-commons/cli 0.2.0 → 0.2.1
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 +409 -45
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -45,10 +45,14 @@ var CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".agc");
|
|
|
45
45
|
var CONFIG_FILE = (0, import_path.join)(CONFIG_DIR, "config.json");
|
|
46
46
|
var DEFAULT_API_URL = process.env.AGC_API_URL ?? "https://api.agentcommons.io";
|
|
47
47
|
var DEFAULT_APP_URL = "https://www.agentcommons.io";
|
|
48
|
+
var DEFAULT_IDENTITY_URL = process.env.COMMONS_IDENTITY_URL ?? "https://auth.agentcommons.io";
|
|
49
|
+
var DEFAULT_IDENTITY_CLIENT_ID = process.env.COMMONS_IDENTITY_CLIENT_ID ?? "commons-cli";
|
|
48
50
|
function loadConfig() {
|
|
49
51
|
const fromEnv = {
|
|
50
52
|
...process.env.AGC_API_URL && { apiUrl: process.env.AGC_API_URL },
|
|
51
53
|
...process.env.AGC_API_KEY && { apiKey: process.env.AGC_API_KEY },
|
|
54
|
+
...process.env.COMMONS_ACCESS_TOKEN && { accessToken: process.env.COMMONS_ACCESS_TOKEN },
|
|
55
|
+
...process.env.COMMONS_IDENTITY_URL && { identityUrl: process.env.COMMONS_IDENTITY_URL },
|
|
52
56
|
...process.env.AGC_INITIATOR && { initiator: process.env.AGC_INITIATOR },
|
|
53
57
|
...process.env.AGC_AGENT_ID && { defaultAgentId: process.env.AGC_AGENT_ID }
|
|
54
58
|
};
|
|
@@ -61,6 +65,8 @@ function loadConfig() {
|
|
|
61
65
|
}
|
|
62
66
|
return {
|
|
63
67
|
apiUrl: DEFAULT_API_URL,
|
|
68
|
+
identityUrl: DEFAULT_IDENTITY_URL,
|
|
69
|
+
identityClientId: DEFAULT_IDENTITY_CLIENT_ID,
|
|
64
70
|
...fromFile,
|
|
65
71
|
...fromEnv
|
|
66
72
|
};
|
|
@@ -73,17 +79,65 @@ function saveConfig(updates) {
|
|
|
73
79
|
}
|
|
74
80
|
function clearConfig() {
|
|
75
81
|
if ((0, import_fs.existsSync)(CONFIG_FILE)) {
|
|
76
|
-
(0, import_fs.writeFileSync)(
|
|
82
|
+
(0, import_fs.writeFileSync)(
|
|
83
|
+
CONFIG_FILE,
|
|
84
|
+
JSON.stringify(
|
|
85
|
+
{
|
|
86
|
+
apiUrl: DEFAULT_API_URL,
|
|
87
|
+
identityUrl: DEFAULT_IDENTITY_URL,
|
|
88
|
+
identityClientId: DEFAULT_IDENTITY_CLIENT_ID
|
|
89
|
+
},
|
|
90
|
+
null,
|
|
91
|
+
2
|
|
92
|
+
),
|
|
93
|
+
{ mode: 384 }
|
|
94
|
+
);
|
|
77
95
|
}
|
|
78
96
|
}
|
|
79
97
|
function makeClient(overrides) {
|
|
80
98
|
const cfg = { ...loadConfig(), ...overrides };
|
|
81
99
|
return new import_sdk.CommonsClient({
|
|
82
100
|
baseUrl: cfg.apiUrl,
|
|
83
|
-
apiKey: cfg.apiKey,
|
|
84
|
-
initiator: cfg.initiator
|
|
101
|
+
apiKey: cfg.accessToken ?? cfg.apiKey,
|
|
102
|
+
initiator: cfg.userId ?? cfg.initiator
|
|
85
103
|
});
|
|
86
104
|
}
|
|
105
|
+
function decodeJwtPayload(token) {
|
|
106
|
+
const [, payload] = token.split(".");
|
|
107
|
+
if (!payload) return {};
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
110
|
+
} catch {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function ensureAccessToken() {
|
|
115
|
+
const cfg = loadConfig();
|
|
116
|
+
if (cfg.apiKey && !cfg.sessionToken) return cfg;
|
|
117
|
+
if (cfg.accessToken && cfg.accessTokenExpiresAt && cfg.accessTokenExpiresAt > Date.now() + 3e4) {
|
|
118
|
+
return cfg;
|
|
119
|
+
}
|
|
120
|
+
if (!cfg.sessionToken || !cfg.identityUrl) return cfg;
|
|
121
|
+
const response = await fetch(
|
|
122
|
+
`${cfg.identityUrl.replace(/\/$/, "")}/api/auth/token`,
|
|
123
|
+
{ headers: { Authorization: `Bearer ${cfg.sessionToken}` } }
|
|
124
|
+
);
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
throw new Error("Your Commons login has expired. Run `agc login` again.");
|
|
127
|
+
}
|
|
128
|
+
const data = await response.json();
|
|
129
|
+
if (!data.token) throw new Error("Commons Identity did not return an access token.");
|
|
130
|
+
const claims = decodeJwtPayload(data.token);
|
|
131
|
+
const updates = {
|
|
132
|
+
accessToken: data.token,
|
|
133
|
+
accessTokenExpiresAt: typeof claims.exp === "number" ? claims.exp * 1e3 : Date.now() + 10 * 60 * 1e3,
|
|
134
|
+
userId: typeof claims.sub === "string" ? claims.sub : cfg.userId,
|
|
135
|
+
workspaceId: typeof claims.workspace_id === "string" ? claims.workspace_id : cfg.workspaceId,
|
|
136
|
+
initiator: typeof claims.sub === "string" ? claims.sub : cfg.initiator
|
|
137
|
+
};
|
|
138
|
+
saveConfig(updates);
|
|
139
|
+
return { ...cfg, ...updates };
|
|
140
|
+
}
|
|
87
141
|
|
|
88
142
|
// src/ui.ts
|
|
89
143
|
var import_chalk = __toESM(require("chalk"));
|
|
@@ -106,7 +160,7 @@ var sym = {
|
|
|
106
160
|
bullet: import_chalk.default.dim("\u2022"),
|
|
107
161
|
dot: import_chalk.default.dim("\xB7")
|
|
108
162
|
};
|
|
109
|
-
function banner(version = "0.2.
|
|
163
|
+
function banner(version = "0.2.1") {
|
|
110
164
|
const line = import_chalk.default.cyan(" \u2500".padEnd(2) + "\u2500".repeat(44));
|
|
111
165
|
console.log("");
|
|
112
166
|
console.log(line);
|
|
@@ -282,14 +336,14 @@ function prompt(question, hidden = false) {
|
|
|
282
336
|
}
|
|
283
337
|
function loginCommand() {
|
|
284
338
|
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>", "User/initiator ID (advanced \u2014 usually auto-detected)").action(async (opts) => {
|
|
339
|
+
cmd.option("--api-url <url>", "API base URL", DEFAULT_API_URL).option("--identity-url <url>", "Commons Identity URL", DEFAULT_IDENTITY_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
340
|
try {
|
|
287
341
|
const current = loadConfig();
|
|
288
342
|
const isFirstRun = !(0, import_fs2.existsSync)(CONFIG_FILE2);
|
|
289
343
|
banner();
|
|
290
344
|
if (isFirstRun) {
|
|
291
345
|
console.log(c.bold(" Welcome to Agent Commons CLI!"));
|
|
292
|
-
console.log(c.dim("
|
|
346
|
+
console.log(c.dim(" Sign in once with your Commons account to get started.\n"));
|
|
293
347
|
} else {
|
|
294
348
|
console.log(c.bold(" Update your credentials"));
|
|
295
349
|
console.log(c.dim(" Press Enter to keep existing values.\n"));
|
|
@@ -308,7 +362,74 @@ function loginCommand() {
|
|
|
308
362
|
}
|
|
309
363
|
const appUrl = apiUrl.includes("localhost") ? "http://localhost:3000" : DEFAULT_APP_URL;
|
|
310
364
|
const apiKeysUrl = `${appUrl}/settings/api-keys`;
|
|
311
|
-
|
|
365
|
+
if (!opts.apiKey) {
|
|
366
|
+
step(1, 1, "Commons account");
|
|
367
|
+
const identityUrl = String(opts.identityUrl).replace(/\/$/, "");
|
|
368
|
+
const clientId = DEFAULT_IDENTITY_CLIENT_ID;
|
|
369
|
+
const deviceResponse = await fetch(`${identityUrl}/api/auth/device/code`, {
|
|
370
|
+
method: "POST",
|
|
371
|
+
headers: { "Content-Type": "application/json" },
|
|
372
|
+
body: JSON.stringify({
|
|
373
|
+
client_id: clientId,
|
|
374
|
+
scope: "openid profile email offline_access agents:read agents:write agents:run compute:read compute:write activity:read usage:read"
|
|
375
|
+
})
|
|
376
|
+
});
|
|
377
|
+
const device = await deviceResponse.json();
|
|
378
|
+
if (!deviceResponse.ok || !device.device_code || !device.user_code) {
|
|
379
|
+
throw new Error(device.error_description || "Could not start Commons login.");
|
|
380
|
+
}
|
|
381
|
+
const verificationUrl = device.verification_uri_complete ?? `${identityUrl}/device?user_code=${encodeURIComponent(device.user_code)}`;
|
|
382
|
+
console.log(` ${c.dim("Authorize this CLI in your browser:")}`);
|
|
383
|
+
console.log(` ${c.primary(verificationUrl)}`);
|
|
384
|
+
console.log(` ${c.dim("Code:")} ${c.bold(device.user_code)}
|
|
385
|
+
`);
|
|
386
|
+
openBrowser(verificationUrl);
|
|
387
|
+
const deadline = Date.now() + (device.expires_in ?? 600) * 1e3;
|
|
388
|
+
let intervalMs = Math.max(device.interval ?? 5, 1) * 1e3;
|
|
389
|
+
let sessionToken;
|
|
390
|
+
while (Date.now() < deadline) {
|
|
391
|
+
await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
|
|
392
|
+
const tokenResponse = await fetch(`${identityUrl}/api/auth/device/token`, {
|
|
393
|
+
method: "POST",
|
|
394
|
+
headers: { "Content-Type": "application/json" },
|
|
395
|
+
body: JSON.stringify({
|
|
396
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
397
|
+
device_code: device.device_code,
|
|
398
|
+
client_id: clientId
|
|
399
|
+
})
|
|
400
|
+
});
|
|
401
|
+
const token = await tokenResponse.json();
|
|
402
|
+
if (tokenResponse.ok && token.access_token) {
|
|
403
|
+
sessionToken = token.access_token;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
if (token.error === "slow_down") {
|
|
407
|
+
intervalMs += 1e3;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (token.error === "authorization_pending") continue;
|
|
411
|
+
throw new Error(token.error_description || token.error || "Commons login failed.");
|
|
412
|
+
}
|
|
413
|
+
if (!sessionToken) throw new Error("Commons login expired before approval.");
|
|
414
|
+
saveConfig({
|
|
415
|
+
apiUrl,
|
|
416
|
+
identityUrl,
|
|
417
|
+
identityClientId: clientId,
|
|
418
|
+
sessionToken,
|
|
419
|
+
accessToken: void 0,
|
|
420
|
+
accessTokenExpiresAt: void 0,
|
|
421
|
+
apiKey: void 0
|
|
422
|
+
});
|
|
423
|
+
const authenticated = await ensureAccessToken();
|
|
424
|
+
console.log(
|
|
425
|
+
` ${sym.ok} ${c.success("Signed in")} as ${c.id(authenticated.userId ?? "Commons user")}`
|
|
426
|
+
);
|
|
427
|
+
console.log(`
|
|
428
|
+
${sym.ok} ${c.success("All set!")} Credentials saved to ${c.dim("~/.agc/config.json")}
|
|
429
|
+
`);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
step(1, 1, "Legacy API Key");
|
|
312
433
|
let apiKey = opts.apiKey;
|
|
313
434
|
if (!apiKey) {
|
|
314
435
|
console.log(` ${c.dim("You'll need an API key from your Agent Commons account.")}`);
|
|
@@ -377,15 +498,22 @@ function whoamiCommand() {
|
|
|
377
498
|
return new import_commander.Command("whoami").description("Show current configuration and verify API connectivity").option("--json", "Output as JSON").action(async (opts) => {
|
|
378
499
|
const cfg = loadConfig();
|
|
379
500
|
if (opts.json) {
|
|
380
|
-
console.log(JSON.stringify({
|
|
501
|
+
console.log(JSON.stringify({
|
|
502
|
+
apiUrl: cfg.apiUrl,
|
|
503
|
+
identityUrl: cfg.identityUrl,
|
|
504
|
+
userId: cfg.userId ?? cfg.initiator,
|
|
505
|
+
workspaceId: cfg.workspaceId,
|
|
506
|
+
authenticated: Boolean(cfg.sessionToken || cfg.apiKey)
|
|
507
|
+
}, null, 2));
|
|
381
508
|
return;
|
|
382
509
|
}
|
|
383
510
|
console.log(`
|
|
384
511
|
${c.bold("Current configuration")}`);
|
|
385
512
|
detail([
|
|
386
513
|
["API URL", cfg.apiUrl],
|
|
387
|
-
["
|
|
388
|
-
["
|
|
514
|
+
["Identity", cfg.userId ?? cfg.initiator ?? c.dim("(not set)")],
|
|
515
|
+
["Workspace", cfg.workspaceId ?? c.dim("(not set)")],
|
|
516
|
+
["Auth", cfg.sessionToken ? "Commons account" : cfg.apiKey ? "Legacy API key" : c.dim("(not set)")],
|
|
389
517
|
["Agent ID", cfg.defaultAgentId ?? c.dim("(not set)")]
|
|
390
518
|
]);
|
|
391
519
|
try {
|
|
@@ -687,8 +815,50 @@ ${sym.ok} Session created`);
|
|
|
687
815
|
|
|
688
816
|
// src/commands/tools.ts
|
|
689
817
|
var import_commander4 = require("commander");
|
|
818
|
+
var import_fs3 = require("fs");
|
|
690
819
|
function toolsCommand() {
|
|
691
820
|
const cmd = new import_commander4.Command("tools").description("Discover and manage tools");
|
|
821
|
+
cmd.command("create").description("Create a tool from a JSON file").requiredOption("--file <path>", "Path to a JSON tool definition").option("--json", "Output as JSON").action(async (opts) => {
|
|
822
|
+
const cfg = loadConfig();
|
|
823
|
+
if (!cfg.initiator) {
|
|
824
|
+
console.error(c.error("No initiator set. Run `agc login` first."));
|
|
825
|
+
process.exit(1);
|
|
826
|
+
}
|
|
827
|
+
let payload;
|
|
828
|
+
try {
|
|
829
|
+
payload = JSON.parse((0, import_fs3.readFileSync)(opts.file, "utf8"));
|
|
830
|
+
} catch (error) {
|
|
831
|
+
console.error(c.error(`Could not read tool file: ${error.message}`));
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
if (!payload.name || !payload.schema) {
|
|
835
|
+
console.error(c.error('Tool file must include at least "name" and "schema".'));
|
|
836
|
+
process.exit(1);
|
|
837
|
+
}
|
|
838
|
+
const spinner = spin("Creating tool\u2026");
|
|
839
|
+
try {
|
|
840
|
+
const client = makeClient();
|
|
841
|
+
const res = await client.tools.create({
|
|
842
|
+
...payload,
|
|
843
|
+
owner: cfg.initiator,
|
|
844
|
+
ownerType: payload.ownerType ?? "user"
|
|
845
|
+
});
|
|
846
|
+
const tool = res?.data ?? res;
|
|
847
|
+
spinner.stop();
|
|
848
|
+
if (opts.json) return jsonOut(tool);
|
|
849
|
+
console.log(`
|
|
850
|
+
${sym.ok} Tool created`);
|
|
851
|
+
detail([
|
|
852
|
+
["Tool ID", c.id(tool.toolId)],
|
|
853
|
+
["Name", tool.name],
|
|
854
|
+
["Visibility", tool.visibility ?? payload.visibility ?? "private"]
|
|
855
|
+
]);
|
|
856
|
+
} catch (err) {
|
|
857
|
+
spinner.stop();
|
|
858
|
+
printError(err);
|
|
859
|
+
process.exit(1);
|
|
860
|
+
}
|
|
861
|
+
});
|
|
692
862
|
cmd.command("list").description("List available tools").option("--owner <id>", "Filter by owner ID").option("--json", "Output as JSON").action(async (opts) => {
|
|
693
863
|
const cfg = loadConfig();
|
|
694
864
|
const spinner = spin("Fetching tools\u2026");
|
|
@@ -786,8 +956,37 @@ ${sym.ok} ${c.label(toolName)}`);
|
|
|
786
956
|
|
|
787
957
|
// src/commands/workflow.ts
|
|
788
958
|
var import_commander5 = require("commander");
|
|
959
|
+
var import_fs4 = require("fs");
|
|
960
|
+
var import_sdk2 = require("@agent-commons/sdk");
|
|
789
961
|
function workflowCommand() {
|
|
790
962
|
const cmd = new import_commander5.Command("workflow").description("Run and monitor workflows").alias("wf");
|
|
963
|
+
async function createTemplateWorkflow(params) {
|
|
964
|
+
const client = makeClient();
|
|
965
|
+
const template = (0, import_sdk2.buildWorkflowTemplate)(params.templateName, params.ctx);
|
|
966
|
+
const toolIds = {};
|
|
967
|
+
const createdTools = [];
|
|
968
|
+
for (const tool of template.tools) {
|
|
969
|
+
const created = await client.tools.create({
|
|
970
|
+
...tool.payload,
|
|
971
|
+
owner: params.ctx.ownerId,
|
|
972
|
+
ownerType: "user"
|
|
973
|
+
});
|
|
974
|
+
const createdTool = created?.data ?? created;
|
|
975
|
+
toolIds[tool.key] = createdTool.toolId;
|
|
976
|
+
createdTools.push(createdTool);
|
|
977
|
+
}
|
|
978
|
+
const workflow = await client.workflows.create({
|
|
979
|
+
name: template.name,
|
|
980
|
+
description: template.description,
|
|
981
|
+
ownerId: params.ctx.ownerId,
|
|
982
|
+
ownerType: "user",
|
|
983
|
+
isPublic: params.isPublic,
|
|
984
|
+
category: template.category,
|
|
985
|
+
tags: template.tags,
|
|
986
|
+
definition: template.buildDefinition(toolIds, params.ctx)
|
|
987
|
+
});
|
|
988
|
+
return { template, workflow, createdTools };
|
|
989
|
+
}
|
|
791
990
|
cmd.command("list").description("List workflows owned by the current initiator").option("--json", "Output as JSON").action(async (opts) => {
|
|
792
991
|
const cfg = loadConfig();
|
|
793
992
|
if (!cfg.initiator) {
|
|
@@ -817,6 +1016,166 @@ function workflowCommand() {
|
|
|
817
1016
|
process.exit(1);
|
|
818
1017
|
}
|
|
819
1018
|
});
|
|
1019
|
+
cmd.command("create").description("Create a workflow from a JSON file").requiredOption("--file <path>", "Path to a workflow payload or definition JSON file").option("--name <name>", "Workflow name").option("--description <text>", "Workflow description").option("--public", "Make workflow public").option("--json", "Output as JSON").action(async (opts) => {
|
|
1020
|
+
const cfg = loadConfig();
|
|
1021
|
+
if (!cfg.initiator) {
|
|
1022
|
+
console.error(c.error("No initiator set. Run `agc login` first."));
|
|
1023
|
+
process.exit(1);
|
|
1024
|
+
}
|
|
1025
|
+
let fileJson;
|
|
1026
|
+
try {
|
|
1027
|
+
fileJson = JSON.parse((0, import_fs4.readFileSync)(opts.file, "utf8"));
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
console.error(c.error(`Could not read workflow file: ${error.message}`));
|
|
1030
|
+
process.exit(1);
|
|
1031
|
+
}
|
|
1032
|
+
const definition = fileJson.definition ?? fileJson;
|
|
1033
|
+
if (!Array.isArray(definition.nodes) || !Array.isArray(definition.edges)) {
|
|
1034
|
+
console.error(c.error('Workflow file must include a definition with "nodes" and "edges".'));
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
const spinner = spin("Creating workflow\u2026");
|
|
1038
|
+
try {
|
|
1039
|
+
const client = makeClient();
|
|
1040
|
+
const workflow = await client.workflows.create({
|
|
1041
|
+
name: opts.name ?? fileJson.name ?? "CLI Workflow",
|
|
1042
|
+
description: opts.description ?? fileJson.description,
|
|
1043
|
+
definition,
|
|
1044
|
+
ownerId: cfg.initiator,
|
|
1045
|
+
ownerType: "user",
|
|
1046
|
+
isPublic: opts.public ?? fileJson.isPublic,
|
|
1047
|
+
category: fileJson.category,
|
|
1048
|
+
tags: fileJson.tags
|
|
1049
|
+
});
|
|
1050
|
+
spinner.stop();
|
|
1051
|
+
if (opts.json) return jsonOut(workflow);
|
|
1052
|
+
console.log(`
|
|
1053
|
+
${sym.ok} Workflow created`);
|
|
1054
|
+
detail([
|
|
1055
|
+
["Workflow ID", c.id(workflow.workflowId)],
|
|
1056
|
+
["Name", workflow.name],
|
|
1057
|
+
["Nodes", String((workflow.definition?.nodes ?? []).length)]
|
|
1058
|
+
]);
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
spinner.stop();
|
|
1061
|
+
printError(err);
|
|
1062
|
+
process.exit(1);
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
const templates = cmd.command("templates").description("Create workflows from built-in templates");
|
|
1066
|
+
templates.command("list").description("List built-in workflow templates").option("--json", "Output as JSON").action((opts) => {
|
|
1067
|
+
const rows = (0, import_sdk2.listWorkflowTemplates)();
|
|
1068
|
+
if (opts.json) return jsonOut(rows);
|
|
1069
|
+
section(`Workflow Templates (${rows.length})`);
|
|
1070
|
+
table(
|
|
1071
|
+
rows.map((template) => ({
|
|
1072
|
+
Name: template.name,
|
|
1073
|
+
Description: template.description
|
|
1074
|
+
})),
|
|
1075
|
+
["Name", "Description"]
|
|
1076
|
+
);
|
|
1077
|
+
});
|
|
1078
|
+
templates.command("create <templateName>").description("Create a workflow template and its required API tools").option("--prefix <prefix>", "Stable prefix for generated tool/workflow names").option("--agent <agentId>", "Agent ID for agent_processor nodes").option("--reviewer-agent <agentId>", "Second agent ID for multi-agent templates").option("--child-workflow <workflowId>", "Child workflow ID for workflow-invocation-smoke").option("--public", "Make workflow public").option("--run", "Run the workflow after creating it").option("--input <json>", "Run input JSON; defaults to template sample input").option("--json", "Output as JSON").action(async (templateNameRaw, opts) => {
|
|
1079
|
+
const cfg = loadConfig();
|
|
1080
|
+
if (!cfg.initiator) {
|
|
1081
|
+
console.error(c.error("No initiator set. Run `agc login` first."));
|
|
1082
|
+
process.exit(1);
|
|
1083
|
+
}
|
|
1084
|
+
const templateNames = (0, import_sdk2.listWorkflowTemplates)().map((item) => item.name);
|
|
1085
|
+
if (!templateNames.includes(templateNameRaw)) {
|
|
1086
|
+
console.error(c.error(`Unknown template "${templateNameRaw}".`));
|
|
1087
|
+
console.error(c.dim(`Available: ${templateNames.join(", ")}`));
|
|
1088
|
+
process.exit(1);
|
|
1089
|
+
}
|
|
1090
|
+
const templateName = templateNameRaw;
|
|
1091
|
+
const needsAgent = templateName === "agent-research-summary" || templateName === "multi-agent-field-report";
|
|
1092
|
+
const agentId = opts.agent ?? cfg.defaultAgentId;
|
|
1093
|
+
if (needsAgent && !agentId) {
|
|
1094
|
+
console.error(c.error("This template requires --agent <agentId> or a configured defaultAgentId."));
|
|
1095
|
+
process.exit(1);
|
|
1096
|
+
}
|
|
1097
|
+
const prefix = opts.prefix ?? `cli_${templateName.replace(/[^a-z0-9]+/gi, "_")}_${Date.now().toString(36)}`;
|
|
1098
|
+
const spinner = spin("Creating workflow template\u2026");
|
|
1099
|
+
try {
|
|
1100
|
+
let childWorkflowId = opts.childWorkflow;
|
|
1101
|
+
let childResult;
|
|
1102
|
+
if (templateName === "workflow-invocation-smoke" && !childWorkflowId) {
|
|
1103
|
+
const childCtx = {
|
|
1104
|
+
ownerId: cfg.initiator,
|
|
1105
|
+
prefix: `${prefix}_child`
|
|
1106
|
+
};
|
|
1107
|
+
childResult = await createTemplateWorkflow({
|
|
1108
|
+
templateName: "country-weather-brief",
|
|
1109
|
+
ctx: childCtx,
|
|
1110
|
+
isPublic: opts.public
|
|
1111
|
+
});
|
|
1112
|
+
childWorkflowId = childResult.workflow.workflowId;
|
|
1113
|
+
}
|
|
1114
|
+
const ctx = {
|
|
1115
|
+
ownerId: cfg.initiator,
|
|
1116
|
+
prefix,
|
|
1117
|
+
agentId,
|
|
1118
|
+
reviewerAgentId: opts.reviewerAgent,
|
|
1119
|
+
childWorkflowId
|
|
1120
|
+
};
|
|
1121
|
+
const result = await createTemplateWorkflow({
|
|
1122
|
+
templateName,
|
|
1123
|
+
ctx,
|
|
1124
|
+
isPublic: opts.public
|
|
1125
|
+
});
|
|
1126
|
+
let execution;
|
|
1127
|
+
if (opts.run) {
|
|
1128
|
+
let inputData = result.template.sampleInput;
|
|
1129
|
+
if (opts.input) {
|
|
1130
|
+
try {
|
|
1131
|
+
inputData = JSON.parse(opts.input);
|
|
1132
|
+
} catch {
|
|
1133
|
+
throw new Error("--input must be valid JSON");
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
execution = await makeClient().workflows.execute(result.workflow.workflowId, {
|
|
1137
|
+
agentId,
|
|
1138
|
+
inputData,
|
|
1139
|
+
userId: cfg.initiator
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
spinner.stop();
|
|
1143
|
+
const output = { ...result, child: childResult, execution };
|
|
1144
|
+
if (opts.json) return jsonOut(output);
|
|
1145
|
+
console.log(`
|
|
1146
|
+
${sym.ok} Workflow template created`);
|
|
1147
|
+
if (childResult) {
|
|
1148
|
+
detail([
|
|
1149
|
+
["Child workflow", c.id(childResult.workflow.workflowId)],
|
|
1150
|
+
["Parent workflow", c.id(result.workflow.workflowId)],
|
|
1151
|
+
["Template", templateName]
|
|
1152
|
+
]);
|
|
1153
|
+
} else {
|
|
1154
|
+
detail([
|
|
1155
|
+
["Workflow ID", c.id(result.workflow.workflowId)],
|
|
1156
|
+
["Template", templateName],
|
|
1157
|
+
["Tools created", String(result.createdTools.length)]
|
|
1158
|
+
]);
|
|
1159
|
+
}
|
|
1160
|
+
if (execution) {
|
|
1161
|
+
console.log(`
|
|
1162
|
+
${sym.ok} Execution started: ${c.id(execution.executionId)}`);
|
|
1163
|
+
console.log(` Status: ${statusBadge(execution.status)}`);
|
|
1164
|
+
const resultData = execution.result ?? execution.outputData;
|
|
1165
|
+
if (resultData !== void 0) {
|
|
1166
|
+
console.log("\n" + c.label("Result"));
|
|
1167
|
+
console.log(" " + JSON.stringify(resultData, null, 2).replace(/\n/g, "\n "));
|
|
1168
|
+
}
|
|
1169
|
+
} else {
|
|
1170
|
+
console.log(c.dim(`
|
|
1171
|
+
Run it with: agc workflow run ${result.workflow.workflowId} --input '${JSON.stringify(result.template.sampleInput)}'`));
|
|
1172
|
+
}
|
|
1173
|
+
} catch (err) {
|
|
1174
|
+
spinner.stop();
|
|
1175
|
+
printError(err);
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
820
1179
|
cmd.command("get <workflowId>").description("Show workflow details").option("--json", "Output as JSON").action(async (workflowId, opts) => {
|
|
821
1180
|
const spinner = spin("Fetching workflow\u2026");
|
|
822
1181
|
try {
|
|
@@ -1189,7 +1548,7 @@ var import_commander7 = require("commander");
|
|
|
1189
1548
|
var readline3 = __toESM(require("readline"));
|
|
1190
1549
|
|
|
1191
1550
|
// src/local-tools.ts
|
|
1192
|
-
var
|
|
1551
|
+
var import_fs5 = require("fs");
|
|
1193
1552
|
var import_path3 = require("path");
|
|
1194
1553
|
var import_child_process2 = require("child_process");
|
|
1195
1554
|
var readline2 = __toESM(require("readline"));
|
|
@@ -1207,7 +1566,7 @@ function buildDirSnapshot(dir, maxDepth = 2) {
|
|
|
1207
1566
|
if (lines.length >= 300) return;
|
|
1208
1567
|
let entries;
|
|
1209
1568
|
try {
|
|
1210
|
-
entries = (0,
|
|
1569
|
+
entries = (0, import_fs5.readdirSync)(d, { withFileTypes: true });
|
|
1211
1570
|
} catch {
|
|
1212
1571
|
return;
|
|
1213
1572
|
}
|
|
@@ -1237,11 +1596,11 @@ function readFileForContext(rootDir, filePath) {
|
|
|
1237
1596
|
for (const pat of [/\/\.ssh\//, /\/\.aws\//, /\/\.env$/, /\/\.env\./, /id_rsa/, /id_ed25519/]) {
|
|
1238
1597
|
if (pat.test(abs)) return `[error: sensitive path blocked]`;
|
|
1239
1598
|
}
|
|
1240
|
-
if (!(0,
|
|
1241
|
-
const stat = (0,
|
|
1599
|
+
if (!(0, import_fs5.existsSync)(abs)) return `[error: file not found: ${filePath}]`;
|
|
1600
|
+
const stat = (0, import_fs5.statSync)(abs);
|
|
1242
1601
|
if (stat.isDirectory()) return `[error: "${filePath}" is a directory \u2014 use list_directory]`;
|
|
1243
1602
|
if (stat.size > 1e5) return `[truncated \u2014 file too large (${Math.round(stat.size / 1024)} KB). Use cli_read_file for full content]`;
|
|
1244
|
-
return (0,
|
|
1603
|
+
return (0, import_fs5.readFileSync)(abs, "utf8");
|
|
1245
1604
|
} catch (err) {
|
|
1246
1605
|
return `[error reading file: ${err?.message}]`;
|
|
1247
1606
|
}
|
|
@@ -1353,11 +1712,11 @@ var AGC_HOOK_MARKER = "# agc-session:";
|
|
|
1353
1712
|
var HOOK_BACKUP_SUFFIX = ".agc-backup";
|
|
1354
1713
|
function findGitDir(rootDir) {
|
|
1355
1714
|
const gitPath = (0, import_path3.join)(rootDir, ".git");
|
|
1356
|
-
if (!(0,
|
|
1357
|
-
const s = (0,
|
|
1715
|
+
if (!(0, import_fs5.existsSync)(gitPath)) return null;
|
|
1716
|
+
const s = (0, import_fs5.statSync)(gitPath);
|
|
1358
1717
|
if (s.isDirectory()) return gitPath;
|
|
1359
1718
|
if (s.isFile()) {
|
|
1360
|
-
const content = (0,
|
|
1719
|
+
const content = (0, import_fs5.readFileSync)(gitPath, "utf8");
|
|
1361
1720
|
const match = content.match(/^gitdir:\s*(.+)$/m);
|
|
1362
1721
|
if (match) return match[1].trim();
|
|
1363
1722
|
}
|
|
@@ -1367,17 +1726,17 @@ function installGitHook(rootDir, sessionId, agentId, agentName) {
|
|
|
1367
1726
|
const gitDir = findGitDir(rootDir);
|
|
1368
1727
|
if (!gitDir) return;
|
|
1369
1728
|
const hooksDir = (0, import_path3.join)(gitDir, "hooks");
|
|
1370
|
-
(0,
|
|
1729
|
+
(0, import_fs5.mkdirSync)(hooksDir, { recursive: true });
|
|
1371
1730
|
const hookPath = (0, import_path3.join)(hooksDir, "prepare-commit-msg");
|
|
1372
|
-
if ((0,
|
|
1373
|
-
const existing = (0,
|
|
1731
|
+
if ((0, import_fs5.existsSync)(hookPath)) {
|
|
1732
|
+
const existing = (0, import_fs5.readFileSync)(hookPath, "utf8");
|
|
1374
1733
|
if (!existing.includes(AGC_HOOK_MARKER)) {
|
|
1375
|
-
(0,
|
|
1734
|
+
(0, import_fs5.writeFileSync)(hookPath + HOOK_BACKUP_SUFFIX, existing, { mode: 493 });
|
|
1376
1735
|
}
|
|
1377
1736
|
}
|
|
1378
1737
|
const identity = agentName ? `${agentName} (agc)` : agentId ? `agc/${agentId}` : "agc agent";
|
|
1379
1738
|
const trailer = `Co-Authored-By: ${identity} <agc-agent@users.noreply.github.com>`;
|
|
1380
|
-
const chainLine = (0,
|
|
1739
|
+
const chainLine = (0, import_fs5.existsSync)(hookPath + HOOK_BACKUP_SUFFIX) ? `
|
|
1381
1740
|
# chain pre-existing hook
|
|
1382
1741
|
"$(dirname "$0")/prepare-commit-msg${HOOK_BACKUP_SUFFIX}" "$@" 2>/dev/null || true
|
|
1383
1742
|
` : "";
|
|
@@ -1391,21 +1750,21 @@ TRAILER="${trailer}"
|
|
|
1391
1750
|
grep -qF "$TRAILER" "$COMMIT_MSG_FILE" 2>/dev/null && exit 0
|
|
1392
1751
|
printf '\\n%s\\n' "$TRAILER" >> "$COMMIT_MSG_FILE"
|
|
1393
1752
|
`;
|
|
1394
|
-
(0,
|
|
1753
|
+
(0, import_fs5.writeFileSync)(hookPath, hook, { mode: 493 });
|
|
1395
1754
|
}
|
|
1396
1755
|
function removeGitHook(rootDir) {
|
|
1397
1756
|
const gitDir = findGitDir(rootDir);
|
|
1398
1757
|
if (!gitDir) return;
|
|
1399
1758
|
const hookPath = (0, import_path3.join)(gitDir, "hooks", "prepare-commit-msg");
|
|
1400
|
-
if (!(0,
|
|
1401
|
-
const content = (0,
|
|
1759
|
+
if (!(0, import_fs5.existsSync)(hookPath)) return;
|
|
1760
|
+
const content = (0, import_fs5.readFileSync)(hookPath, "utf8");
|
|
1402
1761
|
if (!content.includes(AGC_HOOK_MARKER)) return;
|
|
1403
1762
|
const backupPath = hookPath + HOOK_BACKUP_SUFFIX;
|
|
1404
|
-
if ((0,
|
|
1405
|
-
(0,
|
|
1406
|
-
(0,
|
|
1763
|
+
if ((0, import_fs5.existsSync)(backupPath)) {
|
|
1764
|
+
(0, import_fs5.writeFileSync)(hookPath, (0, import_fs5.readFileSync)(backupPath, "utf8"), { mode: 493 });
|
|
1765
|
+
(0, import_fs5.unlinkSync)(backupPath);
|
|
1407
1766
|
} else {
|
|
1408
|
-
(0,
|
|
1767
|
+
(0, import_fs5.unlinkSync)(hookPath);
|
|
1409
1768
|
}
|
|
1410
1769
|
}
|
|
1411
1770
|
function safePath(root, userPath) {
|
|
@@ -1508,7 +1867,7 @@ function extractViaCommand(cmd, cmdArgs) {
|
|
|
1508
1867
|
}
|
|
1509
1868
|
async function extractPdfText(abs) {
|
|
1510
1869
|
try {
|
|
1511
|
-
const buffer = (0,
|
|
1870
|
+
const buffer = (0, import_fs5.readFileSync)(abs);
|
|
1512
1871
|
const data = await pdfParse(buffer);
|
|
1513
1872
|
const text2 = data.text?.trim();
|
|
1514
1873
|
if (text2) {
|
|
@@ -1536,8 +1895,8 @@ async function toolReadFile(args, cfg) {
|
|
|
1536
1895
|
if (!userPath) throw new Error('read_file requires a "path" argument');
|
|
1537
1896
|
const abs = safePath(cfg.rootDir, userPath);
|
|
1538
1897
|
assertNotSensitive(abs);
|
|
1539
|
-
if (!(0,
|
|
1540
|
-
const stat = (0,
|
|
1898
|
+
if (!(0, import_fs5.existsSync)(abs)) throw new Error(`File not found: ${userPath}`);
|
|
1899
|
+
const stat = (0, import_fs5.statSync)(abs);
|
|
1541
1900
|
if (stat.isDirectory()) throw new Error(`"${userPath}" is a directory, not a file`);
|
|
1542
1901
|
const ext = (0, import_path3.extname)(abs).toLowerCase();
|
|
1543
1902
|
if (PDF_EXTS.has(ext)) {
|
|
@@ -1552,7 +1911,7 @@ async function toolReadFile(args, cfg) {
|
|
|
1552
1911
|
throw new Error(`Cannot read binary file "${userPath}" (${ext} format). Only text, PDF, and Office documents are supported.`);
|
|
1553
1912
|
}
|
|
1554
1913
|
if (stat.size > 5e5) throw new Error(`File too large to read (${Math.round(stat.size / 1024)} KB). Max 500 KB.`);
|
|
1555
|
-
return (0,
|
|
1914
|
+
return (0, import_fs5.readFileSync)(abs, "utf8");
|
|
1556
1915
|
}
|
|
1557
1916
|
async function toolWriteFile(args, cfg) {
|
|
1558
1917
|
const { path: userPath, content } = args;
|
|
@@ -1566,16 +1925,16 @@ async function toolWriteFile(args, cfg) {
|
|
|
1566
1925
|
"write_file"
|
|
1567
1926
|
);
|
|
1568
1927
|
if (!ok) return "User denied write operation.";
|
|
1569
|
-
(0,
|
|
1570
|
-
(0,
|
|
1928
|
+
(0, import_fs5.mkdirSync)((0, import_path3.dirname)(abs), { recursive: true });
|
|
1929
|
+
(0, import_fs5.writeFileSync)(abs, content, "utf8");
|
|
1571
1930
|
return `Written ${String(content).length} bytes to ${userPath}`;
|
|
1572
1931
|
}
|
|
1573
1932
|
async function toolListDirectory(args, cfg) {
|
|
1574
1933
|
const userPath = args.path ?? ".";
|
|
1575
1934
|
const abs = safePath(cfg.rootDir, userPath);
|
|
1576
1935
|
assertNotSensitive(abs);
|
|
1577
|
-
if (!(0,
|
|
1578
|
-
const entries = (0,
|
|
1936
|
+
if (!(0, import_fs5.existsSync)(abs)) throw new Error(`Directory not found: ${userPath}`);
|
|
1937
|
+
const entries = (0, import_fs5.readdirSync)(abs, { withFileTypes: true });
|
|
1579
1938
|
const lines = entries.map((e) => {
|
|
1580
1939
|
const type = e.isDirectory() ? "d" : e.isSymbolicLink() ? "l" : "f";
|
|
1581
1940
|
return `[${type}] ${e.name}`;
|
|
@@ -1595,7 +1954,7 @@ async function toolSearchFiles(args, cfg) {
|
|
|
1595
1954
|
function walk(dir, depth = 0) {
|
|
1596
1955
|
if (results.length >= 50 || depth > 10) return;
|
|
1597
1956
|
try {
|
|
1598
|
-
for (const entry of (0,
|
|
1957
|
+
for (const entry of (0, import_fs5.readdirSync)(dir, { withFileTypes: true })) {
|
|
1599
1958
|
if (entry.name.startsWith(".") && depth > 0) continue;
|
|
1600
1959
|
const full = (0, import_path3.join)(dir, entry.name);
|
|
1601
1960
|
const rel = (0, import_path3.relative)(cfg.rootDir, full);
|
|
@@ -1998,18 +2357,18 @@ ${sym.fail} ${c.error(event.message ?? "Error")}`);
|
|
|
1998
2357
|
// src/commands/chat.ts
|
|
1999
2358
|
var import_commander8 = require("commander");
|
|
2000
2359
|
var readline4 = __toESM(require("readline"));
|
|
2001
|
-
var
|
|
2360
|
+
var import_fs6 = require("fs");
|
|
2002
2361
|
var import_path4 = require("path");
|
|
2003
2362
|
var import_os3 = require("os");
|
|
2004
2363
|
var SESSIONS_DIR = (0, import_path4.join)((0, import_os3.homedir)(), ".agc", "sessions");
|
|
2005
2364
|
function ensureSessionsDir() {
|
|
2006
|
-
if (!(0,
|
|
2365
|
+
if (!(0, import_fs6.existsSync)(SESSIONS_DIR)) (0, import_fs6.mkdirSync)(SESSIONS_DIR, { recursive: true });
|
|
2007
2366
|
}
|
|
2008
2367
|
function appendSessionLog(sessionId, record) {
|
|
2009
2368
|
try {
|
|
2010
2369
|
ensureSessionsDir();
|
|
2011
2370
|
const file = (0, import_path4.join)(SESSIONS_DIR, `${sessionId}.jsonl`);
|
|
2012
|
-
(0,
|
|
2371
|
+
(0, import_fs6.appendFileSync)(file, JSON.stringify(record) + "\n", { mode: 384 });
|
|
2013
2372
|
} catch {
|
|
2014
2373
|
}
|
|
2015
2374
|
}
|
|
@@ -3494,6 +3853,7 @@ function memoryCommand() {
|
|
|
3494
3853
|
const res = await client.memory.create({
|
|
3495
3854
|
agentId: opts.agent,
|
|
3496
3855
|
content: opts.content,
|
|
3856
|
+
summary: String(opts.content).slice(0, 200),
|
|
3497
3857
|
memoryType: opts.type
|
|
3498
3858
|
});
|
|
3499
3859
|
const memory = res?.data ?? res;
|
|
@@ -3744,7 +4104,7 @@ var CONFIG_FILE3 = (0, import_path5.join)((0, import_os4.homedir)(), ".agc", "co
|
|
|
3744
4104
|
async function interactiveMenu() {
|
|
3745
4105
|
banner();
|
|
3746
4106
|
const cfg = loadConfig();
|
|
3747
|
-
const isSetup = !!(cfg.apiKey && cfg.initiator);
|
|
4107
|
+
const isSetup = !!((cfg.accessToken || cfg.apiKey || cfg.sessionToken) && (cfg.userId || cfg.initiator));
|
|
3748
4108
|
if (!isSetup) {
|
|
3749
4109
|
console.log(c.bold(" Welcome to Agent Commons CLI!"));
|
|
3750
4110
|
console.log(c.dim(" Looks like this is your first time here \u2014 let's get you set up.\n"));
|
|
@@ -3754,7 +4114,7 @@ async function interactiveMenu() {
|
|
|
3754
4114
|
return;
|
|
3755
4115
|
}
|
|
3756
4116
|
console.log(
|
|
3757
|
-
` ${c.dim("Connected to")} ${c.primary(cfg.apiUrl)} ${c.dim("\xB7")} ${c.dim("
|
|
4117
|
+
` ${c.dim("Connected to")} ${c.primary(cfg.apiUrl)} ${c.dim("\xB7")} ${c.dim("Identity")} ${c.id((cfg.userId ?? cfg.initiator).slice(0, 8) + "\u2026" + (cfg.userId ?? cfg.initiator).slice(-4))}
|
|
3758
4118
|
`
|
|
3759
4119
|
);
|
|
3760
4120
|
const action = await select("What would you like to do?", [
|
|
@@ -3872,9 +4232,13 @@ async function pickAgentInteractively(action) {
|
|
|
3872
4232
|
return agentId;
|
|
3873
4233
|
}
|
|
3874
4234
|
var program = new import_commander16.Command();
|
|
3875
|
-
program.name("agc").description("Agent Commons CLI \u2014 interact with the Agent Commons platform").version("0.2.
|
|
4235
|
+
program.name("agc").description("Agent Commons CLI \u2014 interact with the Agent Commons platform").version("0.2.1", "-v, --version").action(async () => {
|
|
3876
4236
|
await interactiveMenu();
|
|
3877
4237
|
});
|
|
4238
|
+
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
4239
|
+
if (actionCommand.name() === "login" || actionCommand.name() === "logout") return;
|
|
4240
|
+
await ensureAccessToken();
|
|
4241
|
+
});
|
|
3878
4242
|
program.addCommand(loginCommand());
|
|
3879
4243
|
program.addCommand(logoutCommand());
|
|
3880
4244
|
program.addCommand(whoamiCommand());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-commons/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Agent Commons CLI — chat, run, and manage agents from your terminal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"commander": "^12.1.0",
|
|
17
17
|
"ora": "^8.1.1",
|
|
18
18
|
"pdf-parse": "^1.1.1",
|
|
19
|
-
"@agent-commons/sdk": "0.2.
|
|
19
|
+
"@agent-commons/sdk": "0.2.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^22.10.2",
|