@almyty/agents 1.2.0 → 1.3.0

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/index.js CHANGED
@@ -1,146 +1,174 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @almyty/agents — list, get, run, and inspect almyty agents.
3
+ * @almyty/agents — list, inspect, and run almyty agents.
4
4
  *
5
5
  * npx @almyty/agents list
6
6
  * npx @almyty/agents get <name|id>
7
- * npx @almyty/agents run <name|id> [--input '<json>']
7
+ * npx @almyty/agents run <name|id> --input '<json>' --watch
8
8
  * npx @almyty/agents runs <name|id>
9
- * npx @almyty/agents cancel <name|id> <runId>
9
+ * npx @almyty/agents inspect <name|id> <runId>
10
+ * npx @almyty/agents trace <name|id> <executionId>
10
11
  *
11
12
  * Reads credentials from ~/.almyty/credentials.json (created by
12
13
  * `npx @almyty/auth login`).
13
14
  */
14
- import { AlmytyClient, resolveCredentialsOrExit } from '@almyty/client';
15
- const VERSION = '0.1.0';
15
+ import { AlmytyClient, resolveCredentials, } from '@almyty/client';
16
+ import { parseArgs, flagNumber, flagString } from './args.js';
17
+ import { EXIT } from './exit-codes.js';
18
+ import { VERSION } from './version.js';
19
+ import { formatAgentDetail, formatAgentLine, formatExecutionSummary, formatNodeResults, formatRunDetail, formatRunSummary, formatStep, formatTrace, notActiveMessage, runSucceeded, } from './format.js';
20
+ /** Flags that are switches, so they never swallow the next token. */
21
+ const BOOLEAN_FLAGS = ['watch', 'steps'];
16
22
  function printHelp() {
17
- console.log(`
18
- @almyty/agents v${VERSION}
23
+ console.log(`@almyty/agents v${VERSION}
19
24
 
20
25
  Usage:
21
26
  npx @almyty/agents <command> [options]
22
27
 
23
28
  Commands:
24
- list List all agents in your organization
25
- get <name|id> Show details for one agent
26
- run <name|id> Invoke a workflow agent or start an autonomous run
27
- runs <name|id> List recent runs for an agent
28
- cancel <name|id> <runId> Cancel a running autonomous run
29
+ list List the agents in your organization
30
+ get <name|id> One agent: mode, status, pipeline shape, tools
31
+ run <name|id> Invoke a workflow agent, or start an autonomous run
32
+ runs <name|id> Recent autonomous runs, newest first
33
+ inspect <name|id> <runId> One autonomous run in full: steps, models, cost, error
34
+ executions <name|id> Recent workflow executions, newest first
35
+ trace <name|id> <execId> Where a workflow execution's calls went, hop by hop
36
+ cancel <name|id> <runId> Cancel an in-flight autonomous run
29
37
  help Show this help
30
38
 
31
39
  Run options:
32
40
  --input '<json>' Input payload (workflow: object; autonomous: string or object)
33
- --resume <conversation-id> Resume a previous conversation
34
- --max-steps <n> Autonomous: max steps (default 50)
35
- --max-cost-cents <n> Autonomous: max cost in cents (default 100)
36
- --max-duration-ms <ms> Autonomous: max wall time in ms (default 3600000)
37
- --watch Autonomous: stream steps as they arrive
38
- --json Print raw JSON instead of pretty output
41
+ --resume <conversation-id> Autonomous: continue a previous conversation
42
+ --max-steps <n> Autonomous: step ceiling
43
+ --max-cost-cents <n> Autonomous: cost ceiling, in cents
44
+ --max-duration-ms <ms> Autonomous: wall-clock ceiling
45
+ --watch Autonomous: stream steps until the run ends
46
+ --timeout <s> Autonomous --watch: give up waiting after this long (default 300)
47
+
48
+ List options (runs, executions):
49
+ --limit <n> Rows per page (default 20)
50
+ --page <n> Page number (default 1)
51
+
52
+ Inspect options:
53
+ --steps run: also print per-node detail for a workflow run
54
+
55
+ Global options:
56
+ --json Machine-readable output, no decoration
57
+ --help, -h Show this help
58
+ --version, -v Print the version
39
59
 
40
60
  Environment:
41
61
  ALMYTY_TOKEN Token override (skips ~/.almyty/credentials.json)
42
62
  ALMYTY_URL API URL override
63
+ NO_COLOR Honoured; these commands emit no ANSI colour anyway
64
+
65
+ Exit codes:
66
+ 0 success
67
+ 1 unexpected error
68
+ 2 usage error (bad flags, unknown command)
69
+ 3 not authenticated — run \`npx @almyty/auth login\`
70
+ 4 no such agent, run, or execution
71
+ 5 the run finished in a non-success state
72
+
73
+ Notes:
74
+ A workflow agent's history is under \`executions\` and \`trace\`; an
75
+ autonomous agent's is under \`runs\` and \`inspect\`. \`get\` tells you
76
+ which mode an agent is in.
43
77
 
44
78
  Login:
45
79
  npx @almyty/auth login Browser-based login (writes ~/.almyty/credentials.json)
46
80
  `);
47
81
  }
48
- function parseArgs(argv) {
49
- const result = { positional: [], flags: {} };
50
- let i = 0;
51
- while (i < argv.length) {
52
- const arg = argv[i];
53
- if (arg === '--help' || arg === '-h') {
54
- result.flags.help = true;
55
- }
56
- else if (arg === '--version' || arg === '-v') {
57
- result.flags.version = true;
58
- }
59
- else if (arg === '--watch') {
60
- result.flags.watch = true;
61
- }
62
- else if (arg === '--json') {
63
- result.flags.json = true;
64
- }
65
- else if (arg.startsWith('--')) {
66
- const key = arg.slice(2);
67
- const next = argv[i + 1];
68
- if (next && !next.startsWith('--')) {
69
- result.flags[key] = next;
70
- i++;
71
- }
72
- else {
73
- result.flags[key] = true;
74
- }
75
- }
76
- else if (!result.command) {
77
- result.command = arg;
78
- }
79
- else {
80
- result.positional.push(arg);
82
+ function emitJson(value) {
83
+ console.log(JSON.stringify(value, null, 2));
84
+ }
85
+ function fail(message, code) {
86
+ console.error(message);
87
+ process.exit(code);
88
+ }
89
+ /**
90
+ * The credential, or a clear instruction. Every command in every almyty
91
+ * CLI answers a missing credential the same way and with the same exit
92
+ * code, so a script can branch on 3 instead of matching stderr.
93
+ */
94
+ function newClient(args) {
95
+ const creds = resolveCredentials();
96
+ if (!creds?.token) {
97
+ if (args?.flags.json) {
98
+ // A --json consumer gets a parseable answer on stdout too, not
99
+ // only an exit code and prose on stderr.
100
+ emitJson({
101
+ error: 'NOT_AUTHENTICATED',
102
+ message: 'Run `npx @almyty/auth login`, or set ALMYTY_TOKEN.',
103
+ });
81
104
  }
82
- i++;
105
+ console.error('Not authenticated. Run one of:');
106
+ console.error(' npx @almyty/auth login');
107
+ console.error(' export ALMYTY_TOKEN=<your-token>');
108
+ process.exit(EXIT.AUTH);
83
109
  }
84
- return result;
85
- }
86
- function newClient() {
87
- const creds = resolveCredentialsOrExit();
88
110
  return new AlmytyClient(creds.url, creds.token);
89
111
  }
90
- function formatAgent(a, prefix = '') {
91
- const mode = a.mode ? ` [${a.mode}]` : '';
92
- const status = a.status ? ` (${a.status})` : '';
93
- const desc = a.description ? `\n${prefix} ${a.description}` : '';
94
- return `${prefix}${a.name}${mode}${status}${desc}`;
112
+ function requireArg(value, usage) {
113
+ if (!value)
114
+ fail(`Usage: npx @almyty/agents ${usage}`, EXIT.USAGE);
115
+ return value;
116
+ }
117
+ async function findAgentOrExit(client, ref) {
118
+ const agent = await client.findAgentByNameOrId(ref);
119
+ if (!agent) {
120
+ console.error(`Agent not found: ${ref}`);
121
+ console.error('Run `npx @almyty/agents list` to see what exists.');
122
+ process.exit(EXIT.NOT_FOUND);
123
+ }
124
+ return agent;
125
+ }
126
+ /** `?page=&limit=` from the shared list flags. */
127
+ function pageQuery(args) {
128
+ return {
129
+ page: flagNumber(args.flags, 'page') ?? 1,
130
+ limit: flagNumber(args.flags, 'limit') ?? 20,
131
+ };
95
132
  }
96
133
  async function cmdList(args) {
97
- const client = newClient();
134
+ const client = newClient(args);
98
135
  const agents = await client.listAgents();
99
136
  if (args.flags.json) {
100
- console.log(JSON.stringify(agents, null, 2));
137
+ emitJson(agents);
101
138
  return;
102
139
  }
103
140
  if (agents.length === 0) {
104
141
  console.log('No agents found. Create one at https://app.almyty.com/agents');
105
142
  return;
106
143
  }
107
- console.log(`\n${agents.length} agent(s):\n`);
108
- for (const a of agents) {
109
- console.log(' ' + formatAgent(a, ' ').trimStart());
144
+ console.log('');
145
+ console.log(`${agents.length} agent(s):`);
146
+ console.log('');
147
+ for (const agent of agents) {
148
+ console.log(formatAgentLine(agent));
110
149
  console.log('');
111
150
  }
112
151
  }
113
152
  async function cmdGet(args) {
114
- const ref = args.positional[0];
115
- if (!ref) {
116
- console.error('Usage: npx @almyty/agents get <name|id>');
117
- process.exit(1);
118
- }
119
- const client = newClient();
120
- const agent = await client.findAgentByNameOrId(ref);
121
- if (!agent) {
122
- console.error(`Agent not found: ${ref}`);
123
- process.exit(1);
124
- }
153
+ const ref = requireArg(args.positional[0], 'get <name|id>');
154
+ const client = newClient(args);
155
+ const found = await findAgentOrExit(client, ref);
156
+ // The list endpoint drops tools and the pipeline; fetch the full row
157
+ // so `get` can answer what the agent is actually wired to.
158
+ const agent = await client.getAgent(found.id).catch(() => found);
125
159
  if (args.flags.json) {
126
- console.log(JSON.stringify(agent, null, 2));
160
+ emitJson(agent);
127
161
  return;
128
162
  }
129
163
  console.log('');
130
- console.log(` ${agent.name}`);
131
- console.log(` id: ${agent.id}`);
132
- if (agent.mode)
133
- console.log(` mode: ${agent.mode}`);
134
- if (agent.status)
135
- console.log(` status: ${agent.status}`);
136
- if (agent.description)
137
- console.log(` desc: ${agent.description}`);
164
+ console.log(formatAgentDetail(agent));
138
165
  console.log('');
139
166
  }
140
167
  function parseInputFlag(value) {
141
- if (value === undefined || value === true || value === false)
168
+ if (typeof value !== 'string')
142
169
  return undefined;
143
- // Try JSON first; fall back to raw string.
170
+ // Try JSON first; fall back to the raw string (an autonomous agent's
171
+ // input is usually a plain sentence).
144
172
  try {
145
173
  return JSON.parse(value);
146
174
  }
@@ -148,147 +176,260 @@ function parseInputFlag(value) {
148
176
  return value;
149
177
  }
150
178
  }
151
- function formatStep(step) {
152
- if (!step)
153
- return null;
154
- switch (step.type) {
155
- case 'llm_call':
156
- if (step.output?.content) {
157
- return ` ${step.output.content}`;
158
- }
159
- return ` · llm_call (${step.tokens?.input ?? 0}↓ / ${step.tokens?.output ?? 0}↑)`;
160
- case 'tool_call':
161
- return ` · tool_call → ${step.input?.tool ?? '?'}`;
162
- case 'sub_agent_call':
163
- return ` · sub_agent_call → ${step.input?.agentId ?? '?'}`;
164
- case 'error':
165
- return ` ! error: ${step.error}`;
166
- default:
167
- return null;
168
- }
169
- }
170
179
  async function cmdRun(args) {
171
- const ref = args.positional[0];
172
- if (!ref) {
173
- console.error('Usage: npx @almyty/agents run <name|id> [--input <json>]');
174
- process.exit(1);
175
- }
176
- const client = newClient();
177
- const agent = await client.findAgentByNameOrId(ref);
178
- if (!agent) {
179
- console.error(`Agent not found: ${ref}`);
180
- process.exit(1);
180
+ const ref = requireArg(args.positional[0], "run <name|id> [--input '<json>']");
181
+ const client = newClient(args);
182
+ const agent = await findAgentOrExit(client, ref);
183
+ // A DRAFT agent answers 400 with a JSON body. The status is already
184
+ // here, so say what to do about it instead of printing the body.
185
+ if (agent.status && agent.status.toLowerCase() !== 'active') {
186
+ if (args.flags.json) {
187
+ emitJson({
188
+ error: 'AGENT_NOT_ACTIVE',
189
+ agentId: agent.id,
190
+ agentName: agent.name,
191
+ status: agent.status,
192
+ message: 'Agent must be active to run',
193
+ });
194
+ process.exit(EXIT.FAILED);
195
+ }
196
+ fail(notActiveMessage(agent), EXIT.FAILED);
181
197
  }
182
198
  const rawInput = parseInputFlag(args.flags.input);
183
199
  if (agent.mode === 'autonomous') {
184
200
  const limits = {};
185
- if (args.flags['max-steps'])
186
- limits.maxSteps = Number(args.flags['max-steps']);
187
- if (args.flags['max-cost-cents'])
188
- limits.maxCostCents = Number(args.flags['max-cost-cents']);
189
- if (args.flags['max-duration-ms'])
190
- limits.maxDurationMs = Number(args.flags['max-duration-ms']);
191
- if (typeof args.flags.resume === 'string')
192
- limits.conversationId = args.flags.resume;
201
+ const maxSteps = flagNumber(args.flags, 'max-steps');
202
+ const maxCostCents = flagNumber(args.flags, 'max-cost-cents');
203
+ const maxDurationMs = flagNumber(args.flags, 'max-duration-ms');
204
+ if (maxSteps !== undefined)
205
+ limits.maxSteps = maxSteps;
206
+ if (maxCostCents !== undefined)
207
+ limits.maxCostCents = maxCostCents;
208
+ if (maxDurationMs !== undefined)
209
+ limits.maxDurationMs = maxDurationMs;
210
+ const resume = flagString(args.flags, 'resume');
211
+ if (resume)
212
+ limits.conversationId = resume;
193
213
  const runStub = await client.startRun(agent.id, rawInput ?? '', limits);
194
214
  if (!args.flags.watch) {
195
215
  if (args.flags.json) {
196
- console.log(JSON.stringify(runStub, null, 2));
216
+ emitJson(runStub);
197
217
  }
198
218
  else {
199
219
  console.log(`Started run ${runStub.id}`);
200
- console.log(`Watch: npx @almyty/agents runs ${ref}`);
201
- console.log(`Cancel: npx @almyty/agents cancel ${ref} ${runStub.id}`);
220
+ console.log(`Watch: npx @almyty/agents inspect ${ref} ${runStub.id}`);
221
+ console.log(`Cancel: npx @almyty/agents cancel ${ref} ${runStub.id}`);
202
222
  }
203
223
  return;
204
224
  }
205
225
  let printed = 0;
226
+ const timeoutSeconds = flagNumber(args.flags, 'timeout') ?? 300;
206
227
  const final = await client.pollRun(agent.id, runStub.id, {
228
+ timeoutMs: timeoutSeconds * 1000,
207
229
  onStep: (run) => {
208
- const newSteps = (run.steps ?? []).slice(printed);
209
- for (const step of newSteps) {
210
- const line = formatStep(step);
230
+ if (args.flags.json)
231
+ return;
232
+ const steps = (run.steps ?? []);
233
+ for (let i = printed; i < steps.length; i++) {
234
+ const line = formatStep(steps[i], i);
211
235
  if (line)
212
236
  console.log(line);
213
237
  }
214
- printed = run.steps?.length ?? printed;
238
+ printed = steps.length;
215
239
  },
216
240
  });
241
+ // A failed run is a failed command.
242
+ //
243
+ // pollRun returns on ANY terminal status, and this printed
244
+ // "Run failed." and exited 0 -- so `almyty agents run deploy-check
245
+ // --watch && ./ship.sh` shipped on a failed check.
246
+ if (!runSucceeded(final.status))
247
+ process.exitCode = EXIT.FAILED;
217
248
  if (args.flags.json) {
218
- console.log(JSON.stringify(final, null, 2));
249
+ emitJson(final);
219
250
  return;
220
251
  }
221
252
  console.log('');
222
- console.log(`Run ${final.status}.`);
253
+ console.log(formatRunSummary(final));
223
254
  if (final.output != null) {
224
255
  console.log('');
225
- console.log(typeof final.output === 'string' ? final.output : JSON.stringify(final.output, null, 2));
256
+ console.log(typeof final.output === 'string'
257
+ ? final.output
258
+ : JSON.stringify(final.output, null, 2));
226
259
  }
227
- if (final.error)
260
+ if (final.error) {
261
+ console.error('');
228
262
  console.error(`Error: ${final.error}`);
263
+ console.error(`Detail: npx @almyty/agents inspect ${ref} ${final.id}`);
264
+ }
229
265
  return;
230
266
  }
231
267
  // workflow mode
232
268
  const result = await client.invokeAgent(agent.id, rawInput ?? {});
269
+ // Same rule: the endpoint answers 200 with status 'failed'.
270
+ if (result?.status && !runSucceeded(result.status))
271
+ process.exitCode = EXIT.FAILED;
233
272
  if (args.flags.json) {
234
- console.log(JSON.stringify(result, null, 2));
273
+ emitJson(result);
235
274
  return;
236
275
  }
237
- const output = result?.output ?? result;
238
- if (output == null) {
276
+ const output = result?.output ?? null;
277
+ if (output == null)
239
278
  console.log('(no output)');
240
- }
241
- else if (typeof output === 'string') {
279
+ else if (typeof output === 'string')
242
280
  console.log(output);
243
- }
244
- else {
281
+ else
245
282
  console.log(JSON.stringify(output, null, 2));
283
+ // Attribution and cost, always — a routed workflow answers from
284
+ // whichever model the policy picked, and that has to be visible.
285
+ if (result?.status) {
286
+ console.log('');
287
+ console.log(formatExecutionSummary(result));
288
+ }
289
+ if (args.flags.steps || (result?.status && !runSucceeded(result.status))) {
290
+ const lines = formatNodeResults(result?.nodeResults);
291
+ if (lines.length) {
292
+ console.log('');
293
+ console.log('Nodes:');
294
+ for (const line of lines)
295
+ console.log(line);
296
+ }
297
+ }
298
+ if (result?.error) {
299
+ console.error('');
300
+ console.error(`Error: ${result.error}`);
301
+ if (result.id) {
302
+ console.error(`Detail: npx @almyty/agents trace ${ref} ${result.id}`);
303
+ }
246
304
  }
247
305
  }
248
306
  async function cmdRuns(args) {
249
- const ref = args.positional[0];
250
- if (!ref) {
251
- console.error('Usage: npx @almyty/agents runs <name|id>');
252
- process.exit(1);
307
+ const ref = requireArg(args.positional[0], 'runs <name|id>');
308
+ const client = newClient(args);
309
+ const agent = await findAgentOrExit(client, ref);
310
+ const { page, limit } = pageQuery(args);
311
+ const { data, total } = await client.listRuns(agent.id, page, limit);
312
+ if (args.flags.json) {
313
+ emitJson({ total, page, limit, data });
314
+ return;
253
315
  }
254
- const client = newClient();
255
- const agent = await client.findAgentByNameOrId(ref);
256
- if (!agent) {
257
- console.error(`Agent not found: ${ref}`);
258
- process.exit(1);
316
+ if (data.length === 0) {
317
+ console.log(agent.mode === 'workflow'
318
+ ? `No autonomous runs — ${agent.name} is a workflow agent. Try: npx @almyty/agents executions ${ref}`
319
+ : 'No runs yet.');
320
+ return;
321
+ }
322
+ console.log('');
323
+ console.log(`${total} run(s), showing page ${page}:`);
324
+ console.log('');
325
+ for (const run of data) {
326
+ const bits = [run.id, (run.status ?? '?').padEnd(10)];
327
+ if (run.createdAt)
328
+ bits.push(run.createdAt);
329
+ console.log(` ${bits.join(' ')}`);
330
+ console.log(` ${formatRunSummary(run)}`);
331
+ }
332
+ console.log('');
333
+ console.log(`Detail: npx @almyty/agents inspect ${ref} <runId>`);
334
+ }
335
+ async function cmdInspect(args) {
336
+ const ref = requireArg(args.positional[0], 'inspect <name|id> <runId>');
337
+ const runId = requireArg(args.positional[1], 'inspect <name|id> <runId>');
338
+ const client = newClient(args);
339
+ const agent = await findAgentOrExit(client, ref);
340
+ let run;
341
+ try {
342
+ run = await client.getRun(agent.id, runId);
343
+ }
344
+ catch (err) {
345
+ fail(`Run not found on ${agent.name}: ${runId}\n${err.message}`, EXIT.NOT_FOUND);
259
346
  }
260
- const { data, total } = await client.listRuns(agent.id);
261
347
  if (args.flags.json) {
262
- console.log(JSON.stringify({ total, data }, null, 2));
348
+ emitJson(run);
349
+ }
350
+ else {
351
+ console.log('');
352
+ console.log(formatRunDetail(run));
353
+ console.log('');
354
+ }
355
+ if (!runSucceeded(run.status))
356
+ process.exitCode = EXIT.FAILED;
357
+ }
358
+ async function cmdExecutions(args) {
359
+ const ref = requireArg(args.positional[0], 'executions <name|id>');
360
+ const client = newClient(args);
361
+ const agent = await findAgentOrExit(client, ref);
362
+ const { page, limit } = pageQuery(args);
363
+ const body = await client.request(`/agents/${encodeURIComponent(agent.id)}/executions?page=${page}&limit=${limit}`);
364
+ const data = body?.data ?? [];
365
+ const total = body?.pagination?.total ?? data.length;
366
+ if (args.flags.json) {
367
+ emitJson({ total, page, limit, data });
263
368
  return;
264
369
  }
265
370
  if (data.length === 0) {
266
- console.log('No runs yet.');
371
+ console.log(agent.mode === 'autonomous'
372
+ ? `No workflow executions — ${agent.name} is an autonomous agent. Try: npx @almyty/agents runs ${ref}`
373
+ : 'No executions yet.');
267
374
  return;
268
375
  }
269
- console.log(`\n${total} run(s):\n`);
270
- for (const run of data) {
271
- console.log(` ${run.id} ${run.status}`);
376
+ console.log('');
377
+ console.log(`${total} execution(s), showing page ${page}:`);
378
+ console.log('');
379
+ for (const execution of data) {
380
+ const bits = [execution.id, (execution.status ?? '?').padEnd(10)];
381
+ if (execution.createdAt)
382
+ bits.push(execution.createdAt);
383
+ console.log(` ${bits.join(' ')}`);
384
+ console.log(` ${formatExecutionSummary(execution)}`);
272
385
  }
386
+ console.log('');
387
+ console.log(`Detail: npx @almyty/agents trace ${ref} <executionId>`);
273
388
  }
274
- async function cmdCancel(args) {
275
- const ref = args.positional[0];
276
- const runId = args.positional[1];
277
- if (!ref || !runId) {
278
- console.error('Usage: npx @almyty/agents cancel <name|id> <runId>');
279
- process.exit(1);
389
+ async function cmdTrace(args) {
390
+ const ref = requireArg(args.positional[0], 'trace <name|id> <executionId>');
391
+ const executionId = requireArg(args.positional[1], 'trace <name|id> <executionId>');
392
+ const client = newClient(args);
393
+ const agent = await findAgentOrExit(client, ref);
394
+ let body;
395
+ try {
396
+ body = await client.request(`/agents/${encodeURIComponent(agent.id)}/executions/${encodeURIComponent(executionId)}/trace`);
280
397
  }
281
- const client = newClient();
282
- const agent = await client.findAgentByNameOrId(ref);
283
- if (!agent) {
284
- console.error(`Agent not found: ${ref}`);
285
- process.exit(1);
398
+ catch (err) {
399
+ fail(`No trace for execution ${executionId} on ${agent.name}.\n${err.message}`, EXIT.NOT_FOUND);
286
400
  }
401
+ const trace = body?.data ?? body;
402
+ if (args.flags.json) {
403
+ emitJson(trace);
404
+ return;
405
+ }
406
+ console.log('');
407
+ console.log(formatTrace(trace));
408
+ console.log('');
409
+ }
410
+ async function cmdCancel(args) {
411
+ const ref = requireArg(args.positional[0], 'cancel <name|id> <runId>');
412
+ const runId = requireArg(args.positional[1], 'cancel <name|id> <runId>');
413
+ const client = newClient(args);
414
+ const agent = await findAgentOrExit(client, ref);
287
415
  await client.cancelRun(agent.id, runId);
288
- console.log(`Cancelled ${runId}`);
416
+ if (args.flags.json)
417
+ emitJson({ cancelled: true, agentId: agent.id, runId });
418
+ else
419
+ console.log(`Cancelled ${runId}`);
289
420
  }
421
+ const COMMANDS = {
422
+ list: cmdList,
423
+ get: cmdGet,
424
+ run: cmdRun,
425
+ runs: cmdRuns,
426
+ inspect: cmdInspect,
427
+ executions: cmdExecutions,
428
+ trace: cmdTrace,
429
+ cancel: cmdCancel,
430
+ };
290
431
  async function main() {
291
- const args = parseArgs(process.argv.slice(2));
432
+ const args = parseArgs(process.argv.slice(2), BOOLEAN_FLAGS);
292
433
  if (args.flags.version) {
293
434
  console.log(VERSION);
294
435
  return;
@@ -297,29 +438,18 @@ async function main() {
297
438
  printHelp();
298
439
  return;
299
440
  }
300
- switch (args.command) {
301
- case 'list':
302
- await cmdList(args);
303
- return;
304
- case 'get':
305
- await cmdGet(args);
306
- return;
307
- case 'run':
308
- await cmdRun(args);
309
- return;
310
- case 'runs':
311
- await cmdRuns(args);
312
- return;
313
- case 'cancel':
314
- await cmdCancel(args);
315
- return;
316
- default:
317
- console.error(`Unknown command: ${args.command}`);
318
- printHelp();
319
- process.exit(1);
441
+ const handler = COMMANDS[args.command];
442
+ if (!handler) {
443
+ console.error(`Unknown command: ${args.command}`);
444
+ console.error(`Commands: ${Object.keys(COMMANDS).join(', ')}. Run --help for detail.`);
445
+ process.exit(EXIT.USAGE);
320
446
  }
447
+ await handler(args);
321
448
  }
322
449
  main().catch((err) => {
323
450
  console.error(`Error: ${err.message}`);
324
- process.exit(1);
451
+ // An auth failure from anywhere in the client gets the auth code, so
452
+ // `|| almyty login` works whichever call discovered the dead token.
453
+ const authFailure = /Authentication failed|401/.test(err.message ?? '');
454
+ process.exit(authFailure ? EXIT.AUTH : EXIT.ERROR);
325
455
  });
@@ -0,0 +1,2 @@
1
+ export declare function readVersion(fallback?: string): string;
2
+ export declare const VERSION: string;