@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.
Files changed (2) hide show
  1. package/dist/bin.js +409 -45
  2. 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)(CONFIG_FILE, JSON.stringify({ apiUrl: DEFAULT_API_URL }, null, 2));
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.0") {
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(" You just need an API key to get started.\n"));
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
- step(1, 1, "API Key");
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({ apiUrl: cfg.apiUrl, initiator: cfg.initiator, hasApiKey: !!cfg.apiKey }, null, 2));
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
- ["Initiator", cfg.initiator ?? c.dim("(not set)")],
388
- ["API Key", cfg.apiKey ? `****${cfg.apiKey.slice(-4)}` : c.dim("(not set)")],
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 import_fs3 = require("fs");
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, import_fs3.readdirSync)(d, { withFileTypes: true });
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, import_fs3.existsSync)(abs)) return `[error: file not found: ${filePath}]`;
1241
- const stat = (0, import_fs3.statSync)(abs);
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, import_fs3.readFileSync)(abs, "utf8");
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, import_fs3.existsSync)(gitPath)) return null;
1357
- const s = (0, import_fs3.statSync)(gitPath);
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, import_fs3.readFileSync)(gitPath, "utf8");
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, import_fs3.mkdirSync)(hooksDir, { recursive: true });
1729
+ (0, import_fs5.mkdirSync)(hooksDir, { recursive: true });
1371
1730
  const hookPath = (0, import_path3.join)(hooksDir, "prepare-commit-msg");
1372
- if ((0, import_fs3.existsSync)(hookPath)) {
1373
- const existing = (0, import_fs3.readFileSync)(hookPath, "utf8");
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, import_fs3.writeFileSync)(hookPath + HOOK_BACKUP_SUFFIX, existing, { mode: 493 });
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, import_fs3.existsSync)(hookPath + HOOK_BACKUP_SUFFIX) ? `
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, import_fs3.writeFileSync)(hookPath, hook, { mode: 493 });
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, import_fs3.existsSync)(hookPath)) return;
1401
- const content = (0, import_fs3.readFileSync)(hookPath, "utf8");
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, import_fs3.existsSync)(backupPath)) {
1405
- (0, import_fs3.writeFileSync)(hookPath, (0, import_fs3.readFileSync)(backupPath, "utf8"), { mode: 493 });
1406
- (0, import_fs3.unlinkSync)(backupPath);
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, import_fs3.unlinkSync)(hookPath);
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, import_fs3.readFileSync)(abs);
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, import_fs3.existsSync)(abs)) throw new Error(`File not found: ${userPath}`);
1540
- const stat = (0, import_fs3.statSync)(abs);
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, import_fs3.readFileSync)(abs, "utf8");
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, import_fs3.mkdirSync)((0, import_path3.dirname)(abs), { recursive: true });
1570
- (0, import_fs3.writeFileSync)(abs, content, "utf8");
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, import_fs3.existsSync)(abs)) throw new Error(`Directory not found: ${userPath}`);
1578
- const entries = (0, import_fs3.readdirSync)(abs, { withFileTypes: true });
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, import_fs3.readdirSync)(dir, { withFileTypes: true })) {
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 import_fs4 = require("fs");
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, import_fs4.existsSync)(SESSIONS_DIR)) (0, import_fs4.mkdirSync)(SESSIONS_DIR, { recursive: true });
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, import_fs4.appendFileSync)(file, JSON.stringify(record) + "\n", { mode: 384 });
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("Wallet")} ${c.id(cfg.initiator.slice(0, 8) + "\u2026" + cfg.initiator.slice(-4))}
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.0", "-v, --version").action(async () => {
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.0",
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.0"
19
+ "@agent-commons/sdk": "0.2.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^22.10.2",