@orchagent/cli 0.3.86 → 0.3.87
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/agent-keys.js +21 -7
- package/dist/commands/agents.js +60 -5
- package/dist/commands/config.js +4 -0
- package/dist/commands/delete.js +2 -2
- package/dist/commands/dev.js +226 -0
- package/dist/commands/diff.js +418 -0
- package/dist/commands/estimate.js +105 -0
- package/dist/commands/fork.js +11 -1
- package/dist/commands/health.js +226 -0
- package/dist/commands/index.js +8 -0
- package/dist/commands/info.js +75 -0
- package/dist/commands/init.js +729 -38
- package/dist/commands/publish.js +237 -21
- package/dist/commands/run.js +272 -28
- package/dist/commands/schedule.js +11 -6
- package/dist/commands/test.js +68 -1
- package/dist/lib/api.js +29 -4
- package/dist/lib/batch-publish.js +223 -0
- package/dist/lib/dev-server.js +425 -0
- package/dist/lib/doctor/checks/environment.js +1 -1
- package/dist/lib/key-store.js +121 -0
- package/dist/lib/spinner.js +50 -0
- package/dist/lib/test-mock-runner.js +334 -0
- package/dist/lib/update-notifier.js +1 -1
- package/package.json +1 -1
- package/src/resources/__pycache__/agent_runner.cpython-311.pyc +0 -0
- package/src/resources/__pycache__/agent_runner.cpython-312.pyc +0 -0
- package/src/resources/__pycache__/test_agent_runner_mocks.cpython-311-pytest-9.0.2.pyc +0 -0
- package/src/resources/__pycache__/test_agent_runner_mocks.cpython-312-pytest-8.4.2.pyc +0 -0
- package/src/resources/agent_runner.py +29 -2
- package/src/resources/test_agent_runner_mocks.py +290 -0
|
@@ -0,0 +1,226 @@
|
|
|
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.registerHealthCommand = registerHealthCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const config_1 = require("../lib/config");
|
|
9
|
+
const api_1 = require("../lib/api");
|
|
10
|
+
const agent_ref_1 = require("../lib/agent-ref");
|
|
11
|
+
const errors_1 = require("../lib/errors");
|
|
12
|
+
const spinner_1 = require("../lib/spinner");
|
|
13
|
+
const output_1 = require("../lib/output");
|
|
14
|
+
const package_json_1 = __importDefault(require("../../package.json"));
|
|
15
|
+
/**
|
|
16
|
+
* Generate a minimal sample input from an agent's input schema.
|
|
17
|
+
* Produces just enough to satisfy required fields.
|
|
18
|
+
*/
|
|
19
|
+
function generateSampleInput(schema) {
|
|
20
|
+
if (!schema?.properties)
|
|
21
|
+
return undefined;
|
|
22
|
+
const required = new Set(schema.required || []);
|
|
23
|
+
const result = {};
|
|
24
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
25
|
+
if (!required.has(key))
|
|
26
|
+
continue;
|
|
27
|
+
result[key] = sampleValue(prop);
|
|
28
|
+
}
|
|
29
|
+
// If no required fields, fill one optional field so the request isn't empty
|
|
30
|
+
if (Object.keys(result).length === 0) {
|
|
31
|
+
const firstKey = Object.keys(schema.properties)[0];
|
|
32
|
+
if (firstKey) {
|
|
33
|
+
result[firstKey] = sampleValue(schema.properties[firstKey]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
37
|
+
}
|
|
38
|
+
function sampleValue(prop) {
|
|
39
|
+
if (prop.default !== undefined)
|
|
40
|
+
return prop.default;
|
|
41
|
+
if (prop.enum?.length)
|
|
42
|
+
return prop.enum[0];
|
|
43
|
+
switch (prop.type) {
|
|
44
|
+
case 'string': return 'test';
|
|
45
|
+
case 'number':
|
|
46
|
+
case 'integer': return 1;
|
|
47
|
+
case 'boolean': return true;
|
|
48
|
+
case 'array': return [];
|
|
49
|
+
case 'object': return {};
|
|
50
|
+
default: return 'test';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function registerHealthCommand(program) {
|
|
54
|
+
program
|
|
55
|
+
.command('health <agent>')
|
|
56
|
+
.description('Smoke test an agent with a minimal cloud execution')
|
|
57
|
+
.option('--json', 'Output result as JSON')
|
|
58
|
+
.option('--data <json>', 'Custom input data (JSON string)')
|
|
59
|
+
.option('--timeout <ms>', 'Execution timeout in milliseconds', '30000')
|
|
60
|
+
.action(async (agentArg, options) => {
|
|
61
|
+
const startTime = Date.now();
|
|
62
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
63
|
+
if (!config.apiKey) {
|
|
64
|
+
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
65
|
+
}
|
|
66
|
+
const parsed = (0, agent_ref_1.parseAgentRef)(agentArg);
|
|
67
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
68
|
+
const org = parsed.org ?? configFile.workspace ?? config.defaultOrg;
|
|
69
|
+
if (!org) {
|
|
70
|
+
throw new errors_1.CliError('Missing org. Use org/agent[@version] format or set default org.');
|
|
71
|
+
}
|
|
72
|
+
const timeoutMs = parseInt(options.timeout || '30000', 10);
|
|
73
|
+
if (isNaN(timeoutMs) || timeoutMs < 1000) {
|
|
74
|
+
throw new errors_1.CliError('Timeout must be at least 1000ms.');
|
|
75
|
+
}
|
|
76
|
+
const result = {
|
|
77
|
+
agent: `${org}/${parsed.agent}`,
|
|
78
|
+
version: parsed.version,
|
|
79
|
+
status: 'fail',
|
|
80
|
+
latency_ms: 0,
|
|
81
|
+
checks: { resolve: 'fail', execute: 'skip' },
|
|
82
|
+
};
|
|
83
|
+
const { spinner, dispose } = options.json
|
|
84
|
+
? { spinner: null, dispose: () => { } }
|
|
85
|
+
: (0, spinner_1.createElapsedSpinner)(`Health check: ${org}/${parsed.agent}@${parsed.version}`);
|
|
86
|
+
spinner?.start();
|
|
87
|
+
// --- Step 1: Resolve agent metadata ---
|
|
88
|
+
let agentMeta;
|
|
89
|
+
let workspaceId;
|
|
90
|
+
try {
|
|
91
|
+
workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(config, org);
|
|
92
|
+
agentMeta = await (0, api_1.getAgentWithFallback)(config, org, parsed.agent, parsed.version, workspaceId);
|
|
93
|
+
result.checks.resolve = 'pass';
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
result.latency_ms = Date.now() - startTime;
|
|
97
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
98
|
+
dispose();
|
|
99
|
+
return reportResult(result, options.json, spinner);
|
|
100
|
+
}
|
|
101
|
+
// Skills are not runnable
|
|
102
|
+
const agentType = agentMeta.type || 'prompt';
|
|
103
|
+
if (agentType === 'skill') {
|
|
104
|
+
result.latency_ms = Date.now() - startTime;
|
|
105
|
+
result.error = 'Skills are not runnable — nothing to health check.';
|
|
106
|
+
result.checks.execute = 'skip';
|
|
107
|
+
dispose();
|
|
108
|
+
return reportResult(result, options.json, spinner);
|
|
109
|
+
}
|
|
110
|
+
// --- Step 2: Execute agent ---
|
|
111
|
+
const inputSchema = agentMeta.input_schema;
|
|
112
|
+
let body;
|
|
113
|
+
if (options.data) {
|
|
114
|
+
try {
|
|
115
|
+
JSON.parse(options.data);
|
|
116
|
+
body = options.data;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
throw new errors_1.CliError('Invalid JSON in --data option.');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const sample = generateSampleInput(inputSchema);
|
|
124
|
+
body = JSON.stringify(sample || {});
|
|
125
|
+
}
|
|
126
|
+
const endpoint = agentMeta.default_endpoint || 'analyze';
|
|
127
|
+
const url = `${config.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}`;
|
|
128
|
+
const headers = {
|
|
129
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
130
|
+
'Content-Type': 'application/json',
|
|
131
|
+
'X-CLI-Version': package_json_1.default.version,
|
|
132
|
+
'X-OrchAgent-Client': 'cli',
|
|
133
|
+
};
|
|
134
|
+
if (workspaceId) {
|
|
135
|
+
headers['X-Workspace-Id'] = workspaceId;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const response = await (0, api_1.safeFetchWithRetryForCalls)(url, {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
headers,
|
|
141
|
+
body,
|
|
142
|
+
timeoutMs: timeoutMs,
|
|
143
|
+
});
|
|
144
|
+
result.latency_ms = Date.now() - startTime;
|
|
145
|
+
// Extract run ID from response headers or body
|
|
146
|
+
const runId = response.headers.get('x-orchagent-run-id');
|
|
147
|
+
if (runId)
|
|
148
|
+
result.run_id = runId;
|
|
149
|
+
if (response.ok) {
|
|
150
|
+
// Try to extract run_id from body if not in headers
|
|
151
|
+
const text = await response.text();
|
|
152
|
+
if (!result.run_id) {
|
|
153
|
+
try {
|
|
154
|
+
const parsed = JSON.parse(text);
|
|
155
|
+
if (parsed?.run_id)
|
|
156
|
+
result.run_id = parsed.run_id;
|
|
157
|
+
}
|
|
158
|
+
catch { /* not JSON, that's fine */ }
|
|
159
|
+
}
|
|
160
|
+
result.checks.execute = 'pass';
|
|
161
|
+
result.status = 'pass';
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
result.checks.execute = 'fail';
|
|
165
|
+
const text = await response.text();
|
|
166
|
+
let detail;
|
|
167
|
+
try {
|
|
168
|
+
const parsed = JSON.parse(text);
|
|
169
|
+
detail = parsed?.error?.message || parsed?.message || parsed?.detail || `HTTP ${response.status}`;
|
|
170
|
+
if (parsed?.run_id)
|
|
171
|
+
result.run_id = parsed.run_id;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
detail = `HTTP ${response.status}: ${text.slice(0, 200)}`;
|
|
175
|
+
}
|
|
176
|
+
result.error = detail;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
result.latency_ms = Date.now() - startTime;
|
|
181
|
+
result.checks.execute = 'fail';
|
|
182
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
183
|
+
}
|
|
184
|
+
dispose();
|
|
185
|
+
reportResult(result, options.json, spinner);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
function reportResult(result, json, spinner) {
|
|
189
|
+
if (json) {
|
|
190
|
+
(0, output_1.printJson)(result);
|
|
191
|
+
if (result.status === 'fail') {
|
|
192
|
+
process.exitCode = 1;
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const passed = result.status === 'pass';
|
|
197
|
+
const latency = `${result.latency_ms}ms`;
|
|
198
|
+
if (passed) {
|
|
199
|
+
spinner?.succeed(`${chalk_1.default.green('PASS')} ${result.agent}@${result.version} — ${latency}`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
spinner?.fail(`${chalk_1.default.red('FAIL')} ${result.agent}@${result.version} — ${latency}`);
|
|
203
|
+
}
|
|
204
|
+
// Detail lines
|
|
205
|
+
process.stderr.write('\n');
|
|
206
|
+
process.stderr.write(` Resolve: ${checkMark(result.checks.resolve)}\n`);
|
|
207
|
+
process.stderr.write(` Execute: ${checkMark(result.checks.execute)}\n`);
|
|
208
|
+
if (result.run_id) {
|
|
209
|
+
process.stderr.write(` Run ID: ${chalk_1.default.gray(result.run_id)}\n`);
|
|
210
|
+
process.stderr.write(` Logs: ${chalk_1.default.gray(`orch logs ${result.run_id}`)}\n`);
|
|
211
|
+
}
|
|
212
|
+
if (result.error) {
|
|
213
|
+
process.stderr.write(`\n ${chalk_1.default.red('Error:')} ${result.error}\n`);
|
|
214
|
+
}
|
|
215
|
+
process.stderr.write('\n');
|
|
216
|
+
if (!passed) {
|
|
217
|
+
process.exitCode = 1;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function checkMark(status) {
|
|
221
|
+
switch (status) {
|
|
222
|
+
case 'pass': return chalk_1.default.green('pass');
|
|
223
|
+
case 'fail': return chalk_1.default.red('fail');
|
|
224
|
+
case 'skip': return chalk_1.default.gray('skip');
|
|
225
|
+
}
|
|
226
|
+
}
|
package/dist/commands/index.js
CHANGED
|
@@ -36,6 +36,10 @@ const transfer_1 = require("./transfer");
|
|
|
36
36
|
const pull_1 = require("./pull");
|
|
37
37
|
const logs_1 = require("./logs");
|
|
38
38
|
const secrets_1 = require("./secrets");
|
|
39
|
+
const diff_1 = require("./diff");
|
|
40
|
+
const health_1 = require("./health");
|
|
41
|
+
const dev_1 = require("./dev");
|
|
42
|
+
const estimate_1 = require("./estimate");
|
|
39
43
|
function registerCommands(program) {
|
|
40
44
|
(0, login_1.registerLoginCommand)(program);
|
|
41
45
|
(0, logout_1.registerLogoutCommand)(program);
|
|
@@ -72,4 +76,8 @@ function registerCommands(program) {
|
|
|
72
76
|
(0, pull_1.registerPullCommand)(program);
|
|
73
77
|
(0, logs_1.registerLogsCommand)(program);
|
|
74
78
|
(0, secrets_1.registerSecretsCommand)(program);
|
|
79
|
+
(0, diff_1.registerDiffCommand)(program);
|
|
80
|
+
(0, health_1.registerHealthCommand)(program);
|
|
81
|
+
(0, dev_1.registerDevCommand)(program);
|
|
82
|
+
(0, estimate_1.registerEstimateCommand)(program);
|
|
75
83
|
}
|
package/dist/commands/info.js
CHANGED
|
@@ -48,11 +48,32 @@ async function fetchReadme(url) {
|
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
function extractDependencies(manifest) {
|
|
52
|
+
if (!manifest)
|
|
53
|
+
return [];
|
|
54
|
+
const deps = manifest.dependencies;
|
|
55
|
+
if (!Array.isArray(deps))
|
|
56
|
+
return [];
|
|
57
|
+
return deps
|
|
58
|
+
.filter(d => d && typeof d.id === 'string' && typeof d.version === 'string')
|
|
59
|
+
.map(d => ({ id: d.id, version: d.version }));
|
|
60
|
+
}
|
|
61
|
+
function extractCustomTools(manifest) {
|
|
62
|
+
if (!manifest)
|
|
63
|
+
return [];
|
|
64
|
+
const tools = manifest.custom_tools;
|
|
65
|
+
if (!Array.isArray(tools))
|
|
66
|
+
return [];
|
|
67
|
+
return tools
|
|
68
|
+
.filter(t => t && typeof t.name === 'string')
|
|
69
|
+
.map(t => ({ name: t.name, description: t.description, command: t.command }));
|
|
70
|
+
}
|
|
51
71
|
async function getAgentInfo(config, org, agent, version, workspaceId) {
|
|
52
72
|
// Use public metadata endpoint as primary source — never blocked by download restrictions
|
|
53
73
|
try {
|
|
54
74
|
const publicMeta = await (0, api_1.getPublicAgent)(config, org, agent, version);
|
|
55
75
|
const meta = publicMeta;
|
|
76
|
+
const manifest = meta.manifest;
|
|
56
77
|
return {
|
|
57
78
|
type: (publicMeta.type || 'tool'),
|
|
58
79
|
name: publicMeta.name,
|
|
@@ -65,6 +86,10 @@ async function getAgentInfo(config, org, agent, version, workspaceId) {
|
|
|
65
86
|
source_url: meta.source_url,
|
|
66
87
|
run_command: meta.run_command,
|
|
67
88
|
url: meta.url,
|
|
89
|
+
dependencies: extractDependencies(manifest),
|
|
90
|
+
default_skills: meta.default_skills || [],
|
|
91
|
+
custom_tools: extractCustomTools(manifest),
|
|
92
|
+
environment: manifest?.environment,
|
|
68
93
|
};
|
|
69
94
|
}
|
|
70
95
|
catch (err) {
|
|
@@ -95,6 +120,7 @@ async function getAgentInfo(config, org, agent, version, workspaceId) {
|
|
|
95
120
|
else {
|
|
96
121
|
targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
|
97
122
|
}
|
|
123
|
+
const agentManifest = targetAgent.manifest;
|
|
98
124
|
return {
|
|
99
125
|
type: targetAgent.type,
|
|
100
126
|
name: targetAgent.name,
|
|
@@ -108,6 +134,10 @@ async function getAgentInfo(config, org, agent, version, workspaceId) {
|
|
|
108
134
|
source_url: targetAgent.source_url,
|
|
109
135
|
run_command: targetAgent.run_command,
|
|
110
136
|
url: targetAgent.url,
|
|
137
|
+
dependencies: extractDependencies(agentManifest),
|
|
138
|
+
default_skills: targetAgent.default_skills || [],
|
|
139
|
+
custom_tools: extractCustomTools(agentManifest),
|
|
140
|
+
environment: agentManifest?.environment,
|
|
111
141
|
};
|
|
112
142
|
}
|
|
113
143
|
function registerInfoCommand(program) {
|
|
@@ -169,6 +199,51 @@ function registerInfoCommand(program) {
|
|
|
169
199
|
process.stdout.write('\nOutput Schema:\n');
|
|
170
200
|
process.stdout.write(formatSchema(agentData.output_schema) + '\n');
|
|
171
201
|
}
|
|
202
|
+
// Display dependencies
|
|
203
|
+
const hasDeps = agentData.dependencies && agentData.dependencies.length > 0;
|
|
204
|
+
const hasSkills = agentData.default_skills && agentData.default_skills.length > 0;
|
|
205
|
+
const hasTools = agentData.custom_tools && agentData.custom_tools.length > 0;
|
|
206
|
+
if (hasDeps) {
|
|
207
|
+
process.stdout.write('\nDependencies:\n');
|
|
208
|
+
for (const dep of agentData.dependencies) {
|
|
209
|
+
process.stdout.write(` ${chalk_1.default.cyan(dep.id)}@${dep.version}\n`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (hasSkills) {
|
|
213
|
+
process.stdout.write('\nSkills:\n');
|
|
214
|
+
for (const skill of agentData.default_skills) {
|
|
215
|
+
process.stdout.write(` ${chalk_1.default.yellow(skill)}\n`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (hasTools) {
|
|
219
|
+
process.stdout.write('\nCustom Tools:\n');
|
|
220
|
+
for (const tool of agentData.custom_tools) {
|
|
221
|
+
let line = ` ${chalk_1.default.magenta(tool.name)}`;
|
|
222
|
+
if (tool.description) {
|
|
223
|
+
line += ` — ${tool.description}`;
|
|
224
|
+
}
|
|
225
|
+
process.stdout.write(line + '\n');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Display environment pinning
|
|
229
|
+
if (agentData.environment) {
|
|
230
|
+
const env = agentData.environment;
|
|
231
|
+
const parts = [];
|
|
232
|
+
if (env.python_version)
|
|
233
|
+
parts.push(`Python ${env.python_version}`);
|
|
234
|
+
if (env.node_version)
|
|
235
|
+
parts.push(`Node ${env.node_version}`);
|
|
236
|
+
if (env.pip_flags)
|
|
237
|
+
parts.push(`pip flags: ${env.pip_flags}`);
|
|
238
|
+
if (env.npm_flags)
|
|
239
|
+
parts.push(`npm flags: ${env.npm_flags}`);
|
|
240
|
+
if (parts.length) {
|
|
241
|
+
process.stdout.write('\nEnvironment:\n');
|
|
242
|
+
for (const part of parts) {
|
|
243
|
+
process.stdout.write(` ${part}\n`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
172
247
|
// Fetch and display README if available
|
|
173
248
|
if (agentData.source_url) {
|
|
174
249
|
const readmeUrl = deriveReadmeUrl(agentData.source_url);
|