@orchagent/cli 0.3.85 → 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.
@@ -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
+ }
@@ -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
  }
@@ -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);