@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.
- package/dist/commands/index.js +2 -0
- package/dist/commands/publish.js +28 -6
- package/dist/commands/run.js +36 -1
- package/dist/commands/schedule.js +297 -0
- package/dist/lib/api.js +5 -0
- package/package.json +1 -1
- package/src/resources/agent_runner.py +27 -4
package/dist/commands/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/commands/publish.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
277
|
-
|
|
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
|
-
|
|
280
|
-
|
|
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);
|
package/dist/commands/run.js
CHANGED
|
@@ -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
|
@@ -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
|
|