@orchagent/cli 0.3.51 → 0.3.53

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.
@@ -28,6 +28,7 @@ const test_1 = require("./test");
28
28
  const security_1 = require("./security");
29
29
  const billing_1 = require("./billing");
30
30
  const agent_keys_1 = require("./agent-keys");
31
+ const schedule_1 = require("./schedule");
31
32
  function registerCommands(program) {
32
33
  (0, login_1.registerLoginCommand)(program);
33
34
  (0, whoami_1.registerWhoamiCommand)(program);
@@ -56,4 +57,5 @@ function registerCommands(program) {
56
57
  (0, security_1.registerSecurityCommand)(program);
57
58
  (0, billing_1.registerBillingCommand)(program);
58
59
  (0, agent_keys_1.registerAgentKeysCommand)(program);
60
+ (0, schedule_1.registerScheduleCommand)(program);
59
61
  }
@@ -269,15 +269,38 @@ function registerPublishCommand(program) {
269
269
  if (!manifest.name) {
270
270
  throw new errors_1.CliError('orchagent.json must have name');
271
271
  }
272
- // Warn about deprecated fields that are ignored
272
+ // Warn about deprecated prompt field
273
273
  if (manifest.prompt) {
274
274
  process.stderr.write(chalk_1.default.yellow('Warning: "prompt" field in orchagent.json is ignored. Use prompt.md file instead.\n'));
275
275
  }
276
- if (manifest.input_schema) {
277
- process.stderr.write(chalk_1.default.yellow('Warning: "input_schema" field in orchagent.json is ignored. Use schema.json file instead.\n'));
276
+ // Auto-migrate inline schemas to schema.json
277
+ const schemaPath = path_1.default.join(cwd, 'schema.json');
278
+ let schemaFileExists = false;
279
+ try {
280
+ await promises_1.default.access(schemaPath);
281
+ schemaFileExists = true;
278
282
  }
279
- if (manifest.output_schema) {
280
- process.stderr.write(chalk_1.default.yellow('Warning: "output_schema" field in orchagent.json is ignored. Use schema.json file instead.\n'));
283
+ catch {
284
+ // File doesn't exist
285
+ }
286
+ if ((manifest.input_schema || manifest.output_schema) && !schemaFileExists) {
287
+ // Auto-create schema.json from inline schemas
288
+ const schemaContent = {};
289
+ if (manifest.input_schema)
290
+ schemaContent.input = manifest.input_schema;
291
+ if (manifest.output_schema)
292
+ schemaContent.output = manifest.output_schema;
293
+ if (options.dryRun) {
294
+ process.stderr.write(chalk_1.default.cyan('Would create schema.json from inline schemas in orchagent.json\n'));
295
+ }
296
+ else {
297
+ await promises_1.default.writeFile(schemaPath, JSON.stringify(schemaContent, null, 2) + '\n');
298
+ process.stderr.write(chalk_1.default.green('Created schema.json from inline schemas in orchagent.json\n'));
299
+ process.stderr.write(chalk_1.default.yellow('You can now remove input_schema/output_schema from orchagent.json\n'));
300
+ }
301
+ }
302
+ else if ((manifest.input_schema || manifest.output_schema) && schemaFileExists) {
303
+ process.stderr.write(chalk_1.default.yellow('Warning: inline schemas in orchagent.json are ignored (schema.json takes priority).\n'));
281
304
  }
282
305
  // Check for misplaced manifest fields at top level (common user error)
283
306
  const manifestFields = ['manifest_version', 'dependencies', 'max_hops', 'timeout_ms', 'per_call_downstream_cap'];
@@ -362,7 +385,6 @@ function registerPublishCommand(program) {
362
385
  let inputSchema;
363
386
  let outputSchema;
364
387
  let schemaFromFile = false;
365
- const schemaPath = path_1.default.join(cwd, 'schema.json');
366
388
  try {
367
389
  const raw = await promises_1.default.readFile(schemaPath, 'utf-8');
368
390
  const schemas = JSON.parse(raw);
@@ -54,6 +54,7 @@ const spinner_1 = require("../lib/spinner");
54
54
  const llm_1 = require("../lib/llm");
55
55
  const analytics_1 = require("../lib/analytics");
56
56
  const pricing_1 = require("../lib/pricing");
57
+ const package_json_1 = __importDefault(require("../../package.json"));
57
58
  const DEFAULT_VERSION = 'latest';
58
59
  const AGENTS_DIR = path_1.default.join(os_1.default.homedir(), '.orchagent', 'agents');
59
60
  // Local execution environment variables
@@ -797,13 +798,21 @@ async function executeAgentLocally(agentDir, prompt, inputData, outputSchema, cu
797
798
  proc.stdout?.on('data', (data) => {
798
799
  stdout += data.toString();
799
800
  });
801
+ let lastUsage = null;
800
802
  proc.stderr?.on('data', (data) => {
801
803
  const text = data.toString();
802
804
  stderr += text;
803
805
  // Filter out heartbeat dots and orchagent events, show human-readable lines
804
806
  for (const line of text.split('\n')) {
805
- if (line.startsWith('@@ORCHAGENT_EVENT:'))
807
+ if (line.startsWith('@@ORCHAGENT_EVENT:')) {
808
+ try {
809
+ const evt = JSON.parse(line.slice('@@ORCHAGENT_EVENT:'.length));
810
+ if (evt.usage)
811
+ lastUsage = evt.usage;
812
+ }
813
+ catch { /* ignore parse errors */ }
806
814
  continue;
815
+ }
807
816
  if (line.trim() === '.' || line.trim() === '')
808
817
  continue;
809
818
  process.stderr.write(line + '\n');
@@ -816,6 +825,12 @@ async function executeAgentLocally(agentDir, prompt, inputData, outputSchema, cu
816
825
  resolve(1);
817
826
  });
818
827
  });
828
+ // Display token usage if available
829
+ const usage = lastUsage;
830
+ if (usage && (usage.input_tokens || usage.output_tokens)) {
831
+ const total = (usage.input_tokens || 0) + (usage.output_tokens || 0);
832
+ process.stderr.write(chalk_1.default.gray(`${total.toLocaleString()} tokens (${(usage.input_tokens || 0).toLocaleString()} in, ${(usage.output_tokens || 0).toLocaleString()} out)\n`));
833
+ }
819
834
  // 7. Parse and print result
820
835
  if (stdout.trim()) {
821
836
  try {
@@ -1601,6 +1616,7 @@ async function executeCloud(agentRef, file, options) {
1601
1616
  const endpoint = options.endpoint?.trim() || agentMeta.default_endpoint || 'analyze';
1602
1617
  const headers = {
1603
1618
  Authorization: `Bearer ${resolved.apiKey}`,
1619
+ 'X-CLI-Version': package_json_1.default.version,
1604
1620
  };
1605
1621
  if (options.tenant) {
1606
1622
  headers['X-OrchAgent-Tenant'] = options.tenant;
@@ -1889,6 +1905,15 @@ async function executeCloud(agentRef, file, options) {
1889
1905
  ' orch billing balance # check current balance\n';
1890
1906
  throw new errors_1.CliError(errorMessage, errors_1.ExitCodes.PERMISSION_DENIED);
1891
1907
  }
1908
+ if (errorCode === 'CLI_VERSION_TOO_OLD') {
1909
+ spinner?.fail('CLI version too old');
1910
+ const minVersion = typeof payload === 'object' && payload
1911
+ ? payload.error?.min_version
1912
+ : undefined;
1913
+ throw new errors_1.CliError(`Your CLI version (${package_json_1.default.version}) is too old.\n\n` +
1914
+ (minVersion ? `Minimum required: ${minVersion}\n` : '') +
1915
+ 'Update with: npm update -g @orchagent/cli');
1916
+ }
1892
1917
  if (errorCode === 'LLM_KEY_REQUIRED') {
1893
1918
  spinner?.fail('LLM key required');
1894
1919
  throw new errors_1.CliError('This public agent requires you to provide an LLM key.\n' +
@@ -1978,6 +2003,11 @@ async function executeCloud(agentRef, file, options) {
1978
2003
  if (typeof meta.execution_time_ms === 'number') {
1979
2004
  parts.push(`${(meta.execution_time_ms / 1000).toFixed(1)}s execution`);
1980
2005
  }
2006
+ const usage = meta.usage;
2007
+ if (usage && (usage.input_tokens || usage.output_tokens)) {
2008
+ const total = (usage.input_tokens || 0) + (usage.output_tokens || 0);
2009
+ parts.push(`${total.toLocaleString()} tokens (${(usage.input_tokens || 0).toLocaleString()} in, ${(usage.output_tokens || 0).toLocaleString()} out)`);
2010
+ }
1981
2011
  if (parts.length > 0) {
1982
2012
  process.stderr.write(chalk_1.default.gray(`${parts.join(' · ')}\n`));
1983
2013
  }
@@ -2044,6 +2074,11 @@ async function executeCloud(agentRef, file, options) {
2044
2074
  if (typeof meta.execution_time_ms === 'number') {
2045
2075
  parts.push(`${(meta.execution_time_ms / 1000).toFixed(1)}s execution`);
2046
2076
  }
2077
+ const usage = meta.usage;
2078
+ if (usage && (usage.input_tokens || usage.output_tokens)) {
2079
+ const total = (usage.input_tokens || 0) + (usage.output_tokens || 0);
2080
+ parts.push(`${total.toLocaleString()} tokens (${(usage.input_tokens || 0).toLocaleString()} in, ${(usage.output_tokens || 0).toLocaleString()} out)`);
2081
+ }
2047
2082
  if (parts.length > 0) {
2048
2083
  process.stderr.write(chalk_1.default.gray(`\n${parts.join(' · ')}\n`));
2049
2084
  }
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerScheduleCommand = registerScheduleCommand;
7
+ const cli_table3_1 = __importDefault(require("cli-table3"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const config_1 = require("../lib/config");
10
+ const api_1 = require("../lib/api");
11
+ const errors_1 = require("../lib/errors");
12
+ const output_1 = require("../lib/output");
13
+ const agent_ref_1 = require("../lib/agent-ref");
14
+ const api_2 = require("../lib/api");
15
+ // ============================================
16
+ // HELPERS
17
+ // ============================================
18
+ async function resolveWorkspaceId(config, slug) {
19
+ const configFile = await (0, config_1.loadConfig)();
20
+ const targetSlug = slug ?? configFile.workspace;
21
+ if (!targetSlug) {
22
+ throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
23
+ }
24
+ const response = await (0, api_1.request)(config, 'GET', '/workspaces');
25
+ const workspace = response.workspaces.find((w) => w.slug === targetSlug);
26
+ if (!workspace) {
27
+ throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
28
+ }
29
+ return workspace.id;
30
+ }
31
+ function formatDate(iso) {
32
+ if (!iso)
33
+ return '-';
34
+ return new Date(iso).toLocaleString();
35
+ }
36
+ function statusColor(status) {
37
+ if (!status)
38
+ return '-';
39
+ switch (status) {
40
+ case 'completed': return chalk_1.default.green(status);
41
+ case 'failed': return chalk_1.default.red(status);
42
+ case 'running': return chalk_1.default.yellow(status);
43
+ default: return status;
44
+ }
45
+ }
46
+ // ============================================
47
+ // COMMAND REGISTRATION
48
+ // ============================================
49
+ function registerScheduleCommand(program) {
50
+ const schedule = program
51
+ .command('schedule')
52
+ .description('Manage scheduled agent runs (cron and webhooks)');
53
+ // orch schedule list
54
+ schedule
55
+ .command('list')
56
+ .description('List schedules in your workspace')
57
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
58
+ .option('--agent <name>', 'Filter by agent name')
59
+ .option('--type <type>', 'Filter by type (cron or webhook)')
60
+ .option('--json', 'Output as JSON')
61
+ .action(async (options) => {
62
+ const config = await (0, config_1.getResolvedConfig)();
63
+ if (!config.apiKey) {
64
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
65
+ }
66
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
67
+ const params = new URLSearchParams();
68
+ if (options.agent)
69
+ params.set('agent_id', options.agent);
70
+ if (options.type)
71
+ params.set('schedule_type', options.type);
72
+ params.set('limit', '100');
73
+ const qs = params.toString() ? `?${params.toString()}` : '';
74
+ const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/schedules${qs}`);
75
+ if (options.json) {
76
+ (0, output_1.printJson)(result);
77
+ return;
78
+ }
79
+ if (result.schedules.length === 0) {
80
+ process.stdout.write('No schedules found.\n');
81
+ process.stdout.write(chalk_1.default.gray('\nCreate one with: orch schedule create <org/agent> --cron "0 9 * * *"\n'));
82
+ return;
83
+ }
84
+ const table = new cli_table3_1.default({
85
+ head: [
86
+ chalk_1.default.bold('ID'),
87
+ chalk_1.default.bold('Agent'),
88
+ chalk_1.default.bold('Type'),
89
+ chalk_1.default.bold('Schedule'),
90
+ chalk_1.default.bold('Enabled'),
91
+ chalk_1.default.bold('Last Run'),
92
+ chalk_1.default.bold('Status'),
93
+ chalk_1.default.bold('Runs'),
94
+ ],
95
+ });
96
+ result.schedules.forEach((s) => {
97
+ table.push([
98
+ s.id.slice(0, 8),
99
+ `${s.agent_name}@${s.agent_version}`,
100
+ s.schedule_type,
101
+ s.schedule_type === 'cron' ? (s.cron_expression ?? '-') : 'webhook',
102
+ s.enabled ? chalk_1.default.green('yes') : chalk_1.default.red('no'),
103
+ formatDate(s.last_run_at),
104
+ statusColor(s.last_run_status),
105
+ s.run_count.toString(),
106
+ ]);
107
+ });
108
+ process.stdout.write(`\n${table.toString()}\n`);
109
+ process.stdout.write(chalk_1.default.gray(`\n${result.total} schedule(s) total\n`));
110
+ });
111
+ // orch schedule create <agent>
112
+ schedule
113
+ .command('create <agent>')
114
+ .description('Create a cron or webhook schedule (org/agent[@version])')
115
+ .option('--cron <expression>', 'Cron expression (e.g., "0 9 * * 1" for every Monday 9am)')
116
+ .option('--webhook', 'Create a webhook-triggered schedule instead of cron')
117
+ .option('--timezone <tz>', 'Timezone for cron schedule (default: UTC)', 'UTC')
118
+ .option('--input <json>', 'Input data as JSON string')
119
+ .option('--provider <provider>', 'LLM provider (anthropic, openai, gemini)')
120
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
121
+ .action(async (agentArg, options) => {
122
+ const config = await (0, config_1.getResolvedConfig)();
123
+ if (!config.apiKey) {
124
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
125
+ }
126
+ if (!options.cron && !options.webhook) {
127
+ throw new errors_1.CliError('Specify --cron <expression> or --webhook');
128
+ }
129
+ if (options.cron && options.webhook) {
130
+ throw new errors_1.CliError('Cannot use both --cron and --webhook. Choose one.');
131
+ }
132
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
133
+ const ref = (0, agent_ref_1.parseAgentRef)(agentArg);
134
+ // Resolve agent to get the ID
135
+ const agent = await (0, api_2.getAgentWithFallback)(config, ref.org, ref.agent, ref.version);
136
+ // Parse input data
137
+ let inputData;
138
+ if (options.input) {
139
+ try {
140
+ inputData = JSON.parse(options.input);
141
+ }
142
+ catch {
143
+ throw new errors_1.CliError('Invalid JSON in --input. Use single quotes: --input \'{"key": "value"}\'');
144
+ }
145
+ }
146
+ const scheduleType = options.webhook ? 'webhook' : 'cron';
147
+ const body = {
148
+ agent_id: agent.id,
149
+ agent_name: ref.agent,
150
+ agent_version: ref.version === 'latest' ? (agent.version ?? ref.version) : ref.version,
151
+ schedule_type: scheduleType,
152
+ timezone: options.timezone ?? 'UTC',
153
+ };
154
+ if (options.cron)
155
+ body.cron_expression = options.cron;
156
+ if (inputData)
157
+ body.input_data = inputData;
158
+ if (options.provider)
159
+ body.llm_provider = options.provider;
160
+ const result = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/schedules`, {
161
+ body: JSON.stringify(body),
162
+ headers: { 'Content-Type': 'application/json' },
163
+ });
164
+ const s = result.schedule;
165
+ process.stdout.write(chalk_1.default.green('\u2713') + ` Schedule created\n\n`);
166
+ process.stdout.write(` ID: ${s.id}\n`);
167
+ process.stdout.write(` Agent: ${s.agent_name}@${s.agent_version}\n`);
168
+ process.stdout.write(` Type: ${s.schedule_type}\n`);
169
+ if (s.schedule_type === 'cron') {
170
+ process.stdout.write(` Cron: ${s.cron_expression}\n`);
171
+ process.stdout.write(` TZ: ${s.timezone}\n`);
172
+ if (s.next_run_at) {
173
+ process.stdout.write(` Next: ${formatDate(s.next_run_at)}\n`);
174
+ }
175
+ }
176
+ else {
177
+ if (s.webhook_url) {
178
+ process.stdout.write(`\n ${chalk_1.default.bold('Webhook URL')} (save this — secret shown once):\n`);
179
+ process.stdout.write(` ${s.webhook_url}\n`);
180
+ }
181
+ }
182
+ process.stdout.write('\n');
183
+ });
184
+ // orch schedule update <schedule-id>
185
+ schedule
186
+ .command('update <schedule-id>')
187
+ .description('Update a schedule')
188
+ .option('--cron <expression>', 'New cron expression')
189
+ .option('--timezone <tz>', 'New timezone')
190
+ .option('--input <json>', 'New input data as JSON')
191
+ .option('--provider <provider>', 'New LLM provider')
192
+ .option('--enable', 'Enable the schedule')
193
+ .option('--disable', 'Disable the schedule')
194
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
195
+ .action(async (scheduleId, options) => {
196
+ const config = await (0, config_1.getResolvedConfig)();
197
+ if (!config.apiKey) {
198
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
199
+ }
200
+ if (options.enable && options.disable) {
201
+ throw new errors_1.CliError('Cannot use both --enable and --disable');
202
+ }
203
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
204
+ const updates = {};
205
+ if (options.cron)
206
+ updates.cron_expression = options.cron;
207
+ if (options.timezone)
208
+ updates.timezone = options.timezone;
209
+ if (options.provider)
210
+ updates.llm_provider = options.provider;
211
+ if (options.enable)
212
+ updates.enabled = true;
213
+ if (options.disable)
214
+ updates.enabled = false;
215
+ if (options.input) {
216
+ try {
217
+ updates.input_data = JSON.parse(options.input);
218
+ }
219
+ catch {
220
+ throw new errors_1.CliError('Invalid JSON in --input');
221
+ }
222
+ }
223
+ if (Object.keys(updates).length === 0) {
224
+ throw new errors_1.CliError('Nothing to update. Specify at least one option.');
225
+ }
226
+ const result = await (0, api_1.request)(config, 'PATCH', `/workspaces/${workspaceId}/schedules/${scheduleId}`, {
227
+ body: JSON.stringify(updates),
228
+ headers: { 'Content-Type': 'application/json' },
229
+ });
230
+ const s = result.schedule;
231
+ process.stdout.write(chalk_1.default.green('\u2713') + ` Schedule updated\n\n`);
232
+ process.stdout.write(` ID: ${s.id}\n`);
233
+ process.stdout.write(` Enabled: ${s.enabled ? chalk_1.default.green('yes') : chalk_1.default.red('no')}\n`);
234
+ if (s.cron_expression) {
235
+ process.stdout.write(` Cron: ${s.cron_expression}\n`);
236
+ process.stdout.write(` TZ: ${s.timezone}\n`);
237
+ }
238
+ if (s.next_run_at) {
239
+ process.stdout.write(` Next: ${formatDate(s.next_run_at)}\n`);
240
+ }
241
+ process.stdout.write('\n');
242
+ });
243
+ // orch schedule delete <schedule-id>
244
+ schedule
245
+ .command('delete <schedule-id>')
246
+ .description('Delete a schedule')
247
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
248
+ .action(async (scheduleId, options) => {
249
+ const config = await (0, config_1.getResolvedConfig)();
250
+ if (!config.apiKey) {
251
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
252
+ }
253
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
254
+ await (0, api_1.request)(config, 'DELETE', `/workspaces/${workspaceId}/schedules/${scheduleId}`);
255
+ process.stdout.write(chalk_1.default.green('\u2713') + ` Schedule ${scheduleId} deleted\n`);
256
+ });
257
+ // orch schedule trigger <schedule-id>
258
+ schedule
259
+ .command('trigger <schedule-id>')
260
+ .description('Manually trigger a schedule execution')
261
+ .option('--input <json>', 'Override input data as JSON')
262
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
263
+ .action(async (scheduleId, options) => {
264
+ const config = await (0, config_1.getResolvedConfig)();
265
+ if (!config.apiKey) {
266
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
267
+ }
268
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
269
+ let body;
270
+ if (options.input) {
271
+ try {
272
+ JSON.parse(options.input); // validate
273
+ body = options.input;
274
+ }
275
+ catch {
276
+ throw new errors_1.CliError('Invalid JSON in --input');
277
+ }
278
+ }
279
+ process.stdout.write('Triggering schedule...\n');
280
+ const result = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/schedules/${scheduleId}/trigger`, body ? {
281
+ body,
282
+ headers: { 'Content-Type': 'application/json' },
283
+ } : {});
284
+ process.stdout.write(chalk_1.default.green('\u2713') + ` Run completed\n\n`);
285
+ process.stdout.write(` Run ID: ${result.run_id}\n`);
286
+ process.stdout.write(` Status: ${statusColor(result.status)}\n`);
287
+ process.stdout.write(` Duration: ${result.duration_ms}ms\n`);
288
+ if (result.error) {
289
+ process.stdout.write(` Error: ${chalk_1.default.red(result.error)}\n`);
290
+ }
291
+ if (result.output) {
292
+ process.stdout.write(`\n Output:\n`);
293
+ process.stdout.write(` ${JSON.stringify(result.output, null, 2).split('\n').join('\n ')}\n`);
294
+ }
295
+ process.stdout.write('\n');
296
+ });
297
+ }
package/dist/lib/api.js CHANGED
@@ -32,6 +32,9 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.ApiError = void 0;
37
40
  exports.safeFetch = safeFetch;
@@ -65,6 +68,7 @@ exports.listAgentKeys = listAgentKeys;
65
68
  exports.createAgentKey = createAgentKey;
66
69
  exports.deleteAgentKey = deleteAgentKey;
67
70
  const errors_1 = require("./errors");
71
+ const package_json_1 = __importDefault(require("../../package.json"));
68
72
  const DEFAULT_TIMEOUT_MS = 15000;
69
73
  const CALL_TIMEOUT_MS = 120000; // 2 minutes for agent calls (can take time)
70
74
  const MAX_RETRIES = 3;
@@ -195,6 +199,7 @@ async function request(config, method, path, options = {}) {
195
199
  method,
196
200
  headers: {
197
201
  Authorization: `Bearer ${config.apiKey}`,
202
+ 'X-CLI-Version': package_json_1.default.version,
198
203
  ...(options.headers ?? {}),
199
204
  },
200
205
  body: options.body,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.51",
3
+ "version": "0.3.53",
4
4
  "description": "Command-line interface for orchagent — deploy and run AI agents for your team",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",
@@ -414,6 +414,12 @@ class AnthropicProvider:
414
414
  if b.type == "tool_use":
415
415
  yield b.id, b.name, b.input
416
416
 
417
+ def extract_usage(self, r):
418
+ u = getattr(r, "usage", None)
419
+ if u:
420
+ return {"input_tokens": u.input_tokens, "output_tokens": u.output_tokens}
421
+ return {"input_tokens": 0, "output_tokens": 0}
422
+
417
423
  def append_turn(self, messages, response, tool_results):
418
424
  messages.append({"role": "assistant", "content": response.content})
419
425
  results = []
@@ -466,6 +472,12 @@ class OpenAIProvider:
466
472
  for tc in r.choices[0].message.tool_calls:
467
473
  yield tc.id, tc.function.name, json.loads(tc.function.arguments)
468
474
 
475
+ def extract_usage(self, r):
476
+ u = getattr(r, "usage", None)
477
+ if u:
478
+ return {"input_tokens": getattr(u, "prompt_tokens", 0) or 0, "output_tokens": getattr(u, "completion_tokens", 0) or 0}
479
+ return {"input_tokens": 0, "output_tokens": 0}
480
+
469
481
  def append_turn(self, messages, response, tool_results):
470
482
  msg = response.choices[0].message
471
483
  # Build assistant message dict with tool_calls
@@ -598,6 +610,12 @@ class GeminiProvider:
598
610
  if p.function_call:
599
611
  yield str(i), p.function_call.name, dict(p.function_call.args)
600
612
 
613
+ def extract_usage(self, r):
614
+ u = getattr(r, "usage_metadata", None)
615
+ if u:
616
+ return {"input_tokens": getattr(u, "prompt_token_count", 0) or 0, "output_tokens": getattr(u, "candidates_token_count", 0) or 0}
617
+ return {"input_tokens": 0, "output_tokens": 0}
618
+
601
619
  def append_turn(self, messages, response, tool_results):
602
620
  types = self._genai_types
603
621
  # Append the model's response as a Content object
@@ -711,6 +729,7 @@ def main():
711
729
  tools = provider.convert_tools(canonical_tools)
712
730
 
713
731
  messages = [{"role": "user", "content": json.dumps(input_data, indent=2)}]
732
+ total_usage = {"input_tokens": 0, "output_tokens": 0}
714
733
 
715
734
  for turn in range(args.max_turns):
716
735
  emit_event("turn_start", turn=turn + 1, max_turns=args.max_turns)
@@ -721,11 +740,15 @@ def main():
721
740
  try:
722
741
  response = provider.call(system_prompt, messages, tools)
723
742
  except Exception as e:
724
- emit_event("error", message=str(e)[:200])
743
+ emit_event("error", message=str(e)[:200], usage=total_usage)
725
744
  error_exit("LLM API error (%s): %s" % (provider_name, e))
726
745
 
746
+ turn_usage = provider.extract_usage(response)
747
+ total_usage["input_tokens"] += turn_usage["input_tokens"]
748
+ total_usage["output_tokens"] += turn_usage["output_tokens"]
749
+
727
750
  if not provider.has_tool_use(response):
728
- emit_event("done")
751
+ emit_event("done", usage=total_usage)
729
752
  final_text = provider.extract_text(response)
730
753
  try:
731
754
  result = json.loads(final_text)
@@ -742,7 +765,7 @@ def main():
742
765
  emit_event("tool_result", turn=turn + 1, tool=name, status="error" if result_text.startswith("[ERROR]") else "ok")
743
766
 
744
767
  if is_submit:
745
- emit_event("done")
768
+ emit_event("done", usage=total_usage)
746
769
  try:
747
770
  result = json.loads(result_text)
748
771
  except json.JSONDecodeError:
@@ -760,7 +783,7 @@ def main():
760
783
  else:
761
784
  print("[agent] Turn %d/%d completed (%d tool calls)" % (turn + 1, args.max_turns, num_calls), file=sys.stderr)
762
785
 
763
- emit_event("error", message="max turns reached")
786
+ emit_event("error", message="max turns reached", usage=total_usage)
764
787
  error_exit("Agent reached maximum turns (%d) without submitting a result" % args.max_turns)
765
788
 
766
789