@orchagent/cli 0.1.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.
@@ -0,0 +1,421 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.registerRunCommand = registerRunCommand;
40
+ const promises_1 = __importDefault(require("fs/promises"));
41
+ const path_1 = __importDefault(require("path"));
42
+ const os_1 = __importDefault(require("os"));
43
+ const child_process_1 = require("child_process");
44
+ const config_1 = require("../lib/config");
45
+ const api_1 = require("../lib/api");
46
+ const errors_1 = require("../lib/errors");
47
+ const output_1 = require("../lib/output");
48
+ const llm_1 = require("../lib/llm");
49
+ const DEFAULT_VERSION = 'latest';
50
+ const AGENTS_DIR = path_1.default.join(os_1.default.homedir(), '.orchagent', 'agents');
51
+ function parseAgentRef(value) {
52
+ const [ref, versionPart] = value.split('@');
53
+ const version = versionPart?.trim() || DEFAULT_VERSION;
54
+ const segments = ref.split('/');
55
+ if (segments.length === 1) {
56
+ return { agent: segments[0], version };
57
+ }
58
+ if (segments.length === 2) {
59
+ return { org: segments[0], agent: segments[1], version };
60
+ }
61
+ throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
62
+ }
63
+ async function downloadAgent(config, org, agent, version) {
64
+ // Public endpoint - no auth required
65
+ return (0, api_1.publicRequest)(config, `/public/agents/${org}/${agent}/${version}/download`);
66
+ }
67
+ async function checkDependencies(config, dependencies) {
68
+ const results = [];
69
+ for (const dep of dependencies) {
70
+ const [org, agent] = dep.id.split('/');
71
+ try {
72
+ const agentData = await downloadAgent(config, org, agent, dep.version);
73
+ const downloadable = !!(agentData.source_url || agentData.pip_package || agentData.type === 'prompt');
74
+ results.push({ dep, downloadable, agentData });
75
+ }
76
+ catch {
77
+ // Agent not found or not downloadable
78
+ results.push({ dep, downloadable: false });
79
+ }
80
+ }
81
+ return results;
82
+ }
83
+ async function promptUserForDeps(depStatuses) {
84
+ const readline = await Promise.resolve().then(() => __importStar(require('readline')));
85
+ const rl = readline.createInterface({
86
+ input: process.stdin,
87
+ output: process.stderr,
88
+ });
89
+ const downloadableCount = depStatuses.filter(d => d.downloadable).length;
90
+ const cloudOnlyCount = depStatuses.length - downloadableCount;
91
+ process.stderr.write('\n⚠️ This agent has dependencies:\n');
92
+ for (const status of depStatuses) {
93
+ const icon = status.downloadable ? '✓' : '☁️';
94
+ const note = status.downloadable ? '(downloadable)' : '(cloud-only)';
95
+ process.stderr.write(` ${icon} ${status.dep.id}@${status.dep.version} ${note}\n`);
96
+ }
97
+ process.stderr.write('\n');
98
+ if (cloudOnlyCount > 0) {
99
+ process.stderr.write(`Note: ${cloudOnlyCount} dependency(s) are cloud-only and cannot run locally.\n\n`);
100
+ }
101
+ process.stderr.write('Options:\n');
102
+ process.stderr.write(' [1] Run on server (orch call) - recommended\n');
103
+ if (downloadableCount > 0) {
104
+ process.stderr.write(` [2] Download ${downloadableCount} available deps, run locally\n`);
105
+ }
106
+ process.stderr.write(' [3] Cancel\n\n');
107
+ return new Promise((resolve) => {
108
+ rl.question('Choose [1/2/3]: ', (answer) => {
109
+ rl.close();
110
+ const choice = answer.trim();
111
+ if (choice === '1')
112
+ resolve('server');
113
+ else if (choice === '2' && downloadableCount > 0)
114
+ resolve('local');
115
+ else
116
+ resolve('cancel');
117
+ });
118
+ });
119
+ }
120
+ async function downloadDependenciesRecursively(config, depStatuses, visited = new Set()) {
121
+ for (const status of depStatuses) {
122
+ if (!status.downloadable || !status.agentData)
123
+ continue;
124
+ const depRef = `${status.dep.id}@${status.dep.version}`;
125
+ if (visited.has(depRef))
126
+ continue;
127
+ visited.add(depRef);
128
+ const [org, agent] = status.dep.id.split('/');
129
+ process.stderr.write(`\nDownloading dependency: ${depRef}...\n`);
130
+ // Save the dependency locally
131
+ await saveAgentLocally(org, agent, status.agentData);
132
+ // Install if it's a code agent
133
+ if (status.agentData.type === 'code' && (status.agentData.source_url || status.agentData.pip_package)) {
134
+ await installCodeAgent(status.agentData);
135
+ }
136
+ // Recursively download its dependencies
137
+ if (status.agentData.dependencies && status.agentData.dependencies.length > 0) {
138
+ const nestedStatuses = await checkDependencies(config, status.agentData.dependencies);
139
+ await downloadDependenciesRecursively(config, nestedStatuses, visited);
140
+ }
141
+ }
142
+ }
143
+ async function executePromptLocally(agentData, inputData, skillPrompts = [], config) {
144
+ const detected = await (0, llm_1.detectLlmKey)(agentData.supported_providers, config);
145
+ if (!detected) {
146
+ const providers = agentData.supported_providers.join(', ');
147
+ throw new errors_1.CliError(`No LLM key found. This agent requires: ${providers}\n` +
148
+ `Set an environment variable (e.g., OPENAI_API_KEY) or configure in web dashboard`);
149
+ }
150
+ const { provider, key, model: serverModel } = detected;
151
+ // Priority: server config model > agent default model > hardcoded default
152
+ const model = serverModel || agentData.default_models?.[provider] || (0, llm_1.getDefaultModel)(provider);
153
+ // Combine skill prompts with agent prompt (skills first, then agent)
154
+ let basePrompt = agentData.prompt || '';
155
+ if (skillPrompts.length > 0) {
156
+ basePrompt = [...skillPrompts, basePrompt].join('\n\n---\n\n');
157
+ }
158
+ // Build the prompt with input data (matches server behavior)
159
+ const prompt = (0, llm_1.buildPrompt)(basePrompt, inputData);
160
+ // Call the LLM directly
161
+ const response = await (0, llm_1.callLlm)(provider, key, model, prompt, agentData.output_schema);
162
+ return response;
163
+ }
164
+ function parseSkillRef(value) {
165
+ const [ref, versionPart] = value.split('@');
166
+ const version = versionPart?.trim() || 'v1';
167
+ const segments = ref.split('/');
168
+ if (segments.length === 1) {
169
+ return { skill: segments[0], version };
170
+ }
171
+ if (segments.length === 2) {
172
+ return { org: segments[0], skill: segments[1], version };
173
+ }
174
+ throw new errors_1.CliError('Invalid skill reference. Use org/skill or skill format.');
175
+ }
176
+ async function loadSkillPrompts(config, skillRefs, defaultOrg) {
177
+ const prompts = [];
178
+ for (const ref of skillRefs) {
179
+ const parsed = parseSkillRef(ref.trim());
180
+ const org = parsed.org ?? defaultOrg;
181
+ if (!org) {
182
+ throw new errors_1.CliError(`Missing org for skill: ${ref}. Use org/skill format.`);
183
+ }
184
+ // Fetch skill metadata
185
+ const skillMeta = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${parsed.skill}/${parsed.version}`);
186
+ // Verify it's a skill
187
+ const skillType = skillMeta.type;
188
+ if (skillType !== 'skill') {
189
+ throw new errors_1.CliError(`${org}/${parsed.skill} is not a skill (type: ${skillType || 'prompt'})`);
190
+ }
191
+ // Get the skill prompt (need to download for full content)
192
+ const skillData = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${parsed.skill}/${parsed.version}/download`);
193
+ if (!skillData.prompt) {
194
+ throw new errors_1.CliError(`Skill has no content: ${ref}`);
195
+ }
196
+ prompts.push(skillData.prompt);
197
+ }
198
+ return prompts;
199
+ }
200
+ function runCommand(command, args) {
201
+ return new Promise((resolve) => {
202
+ const proc = (0, child_process_1.spawn)(command, args, {
203
+ stdio: ['inherit', 'pipe', 'pipe'],
204
+ shell: true,
205
+ });
206
+ let stdout = '';
207
+ let stderr = '';
208
+ proc.stdout?.on('data', (data) => {
209
+ const text = data.toString();
210
+ stdout += text;
211
+ process.stdout.write(text);
212
+ });
213
+ proc.stderr?.on('data', (data) => {
214
+ const text = data.toString();
215
+ stderr += text;
216
+ process.stderr.write(text);
217
+ });
218
+ proc.on('close', (code) => {
219
+ resolve({ code: code ?? 1, stdout, stderr });
220
+ });
221
+ });
222
+ }
223
+ async function checkPackageInstalled(packageName) {
224
+ try {
225
+ const { code } = await runCommand('pip', ['show', packageName, '--quiet']);
226
+ return code === 0;
227
+ }
228
+ catch {
229
+ return false;
230
+ }
231
+ }
232
+ async function installCodeAgent(agentData) {
233
+ const installSource = agentData.pip_package || agentData.source_url;
234
+ if (!installSource) {
235
+ throw new errors_1.CliError('This code agent does not support local execution.\n' +
236
+ 'Use `orch call` to run it on the server instead.');
237
+ }
238
+ // Check if already installed (for pip packages)
239
+ if (agentData.pip_package) {
240
+ const installed = await checkPackageInstalled(agentData.pip_package);
241
+ if (installed) {
242
+ process.stderr.write(`Package ${agentData.pip_package} already installed.\n`);
243
+ return;
244
+ }
245
+ }
246
+ process.stderr.write(`Installing ${installSource}...\n`);
247
+ const { code } = await runCommand('python3', ['-m', 'pip', 'install', '--quiet', installSource]);
248
+ if (code !== 0) {
249
+ throw new errors_1.CliError(`Failed to install agent. Exit code: ${code}`);
250
+ }
251
+ process.stderr.write(`Installation complete.\n`);
252
+ }
253
+ async function executeCodeAgent(agentData, args) {
254
+ if (!agentData.run_command) {
255
+ throw new errors_1.CliError('This code agent does not have a run command defined.\n' +
256
+ 'Use `orch call` to run it on the server instead.');
257
+ }
258
+ // Install the agent if needed
259
+ await installCodeAgent(agentData);
260
+ // Parse the run command and append user args
261
+ const [cmd, ...cmdArgs] = agentData.run_command.split(' ');
262
+ const fullArgs = [...cmdArgs, ...args];
263
+ process.stderr.write(`\nRunning: ${cmd} ${fullArgs.join(' ')}\n\n`);
264
+ const { code } = await runCommand(cmd, fullArgs);
265
+ if (code !== 0) {
266
+ process.exit(code);
267
+ }
268
+ }
269
+ async function saveAgentLocally(org, agent, agentData) {
270
+ const agentDir = path_1.default.join(AGENTS_DIR, org, agent);
271
+ await promises_1.default.mkdir(agentDir, { recursive: true });
272
+ // Save metadata
273
+ await promises_1.default.writeFile(path_1.default.join(agentDir, 'agent.json'), JSON.stringify(agentData, null, 2));
274
+ // For prompt agents, save the prompt
275
+ if (agentData.type === 'prompt' && agentData.prompt) {
276
+ await promises_1.default.writeFile(path_1.default.join(agentDir, 'prompt.md'), agentData.prompt);
277
+ }
278
+ // For code agents, save files if provided
279
+ if (agentData.files) {
280
+ for (const file of agentData.files) {
281
+ const filePath = path_1.default.join(agentDir, file.path);
282
+ await promises_1.default.mkdir(path_1.default.dirname(filePath), { recursive: true });
283
+ await promises_1.default.writeFile(filePath, file.content);
284
+ }
285
+ }
286
+ return agentDir;
287
+ }
288
+ function registerRunCommand(program) {
289
+ program
290
+ .command('run <agent> [args...]')
291
+ .description('Download and run an agent locally')
292
+ .option('--local', 'Run locally using local LLM keys (default for run command)')
293
+ .option('--input <json>', 'JSON input data')
294
+ .option('--download-only', 'Just download the agent, do not execute')
295
+ .option('--with-deps', 'Automatically download all dependencies (skip prompt)')
296
+ .option('--json', 'Output raw JSON')
297
+ .option('--skills <skills>', 'Add skills (comma-separated)')
298
+ .option('--skills-only <skills>', 'Use only these skills')
299
+ .option('--no-skills', 'Ignore default skills')
300
+ .action(async (agentRef, args, options) => {
301
+ const resolved = await (0, config_1.getResolvedConfig)();
302
+ const parsed = parseAgentRef(agentRef);
303
+ const org = parsed.org ?? resolved.defaultOrg;
304
+ if (!org) {
305
+ throw new errors_1.CliError('Missing org. Use org/agent format.');
306
+ }
307
+ // Download agent definition
308
+ process.stderr.write(`Downloading ${org}/${parsed.agent}@${parsed.version}...\n`);
309
+ let agentData;
310
+ try {
311
+ agentData = await downloadAgent(resolved, org, parsed.agent, parsed.version);
312
+ }
313
+ catch (err) {
314
+ // Fall back to getting public agent info if download endpoint not available
315
+ const agentMeta = await (0, api_1.getPublicAgent)(resolved, org, parsed.agent, parsed.version);
316
+ agentData = {
317
+ type: agentMeta.type || 'code',
318
+ name: agentMeta.name,
319
+ version: agentMeta.version,
320
+ description: agentMeta.description || undefined,
321
+ supported_providers: agentMeta.supported_providers || ['any'],
322
+ };
323
+ }
324
+ // Check for dependencies (orchestrator agents)
325
+ if (agentData.dependencies && agentData.dependencies.length > 0) {
326
+ process.stderr.write(`\nChecking dependencies...\n`);
327
+ const depStatuses = await checkDependencies(resolved, agentData.dependencies);
328
+ let choice;
329
+ if (options.withDeps) {
330
+ // Auto-download deps without prompting
331
+ choice = 'local';
332
+ }
333
+ else {
334
+ choice = await promptUserForDeps(depStatuses);
335
+ }
336
+ if (choice === 'cancel') {
337
+ process.stderr.write('\nCancelled.\n');
338
+ process.exit(0);
339
+ }
340
+ if (choice === 'server') {
341
+ process.stderr.write(`\nUse server execution instead:\n`);
342
+ process.stderr.write(` orch call ${org}/${parsed.agent}@${parsed.version} --input '{...}'\n\n`);
343
+ process.exit(0);
344
+ }
345
+ // choice === 'local' - download dependencies
346
+ process.stderr.write(`\nDownloading dependencies...\n`);
347
+ await downloadDependenciesRecursively(resolved, depStatuses);
348
+ process.stderr.write(`\nAll dependencies downloaded.\n`);
349
+ }
350
+ // Save locally
351
+ const agentDir = await saveAgentLocally(org, parsed.agent, agentData);
352
+ process.stderr.write(`\nAgent saved to: ${agentDir}\n`);
353
+ if (agentData.type === 'code') {
354
+ // Check if this agent supports local execution
355
+ if (agentData.run_command && (agentData.source_url || agentData.pip_package)) {
356
+ if (options.downloadOnly) {
357
+ process.stdout.write(`\nCode agent ready for local execution.\n`);
358
+ process.stdout.write(`Run with: orch run ${org}/${parsed.agent} [args...]\n`);
359
+ return;
360
+ }
361
+ // Execute the code agent locally
362
+ await executeCodeAgent(agentData, args);
363
+ return;
364
+ }
365
+ // Fallback: agent doesn't support local execution
366
+ process.stdout.write(`\nThis is a code-based agent that runs on the server.\n`);
367
+ process.stdout.write(`\nUse: orch call ${org}/${parsed.agent}@${parsed.version} --input '{...}'\n`);
368
+ return;
369
+ }
370
+ if (options.downloadOnly) {
371
+ process.stdout.write(`\nAgent downloaded. Run with:\n`);
372
+ process.stdout.write(` orchagent run ${org}/${parsed.agent}@${parsed.version} --input '{...}'\n`);
373
+ return;
374
+ }
375
+ // For prompt-based agents, execute locally
376
+ if (!options.input) {
377
+ process.stdout.write(`\nPrompt-based agent ready.\n`);
378
+ process.stdout.write(`Run with: orchagent run ${org}/${parsed.agent}@${parsed.version} --input '{...}'\n`);
379
+ return;
380
+ }
381
+ // Parse input
382
+ let inputData;
383
+ try {
384
+ inputData = JSON.parse(options.input);
385
+ }
386
+ catch {
387
+ throw new errors_1.CliError('Invalid JSON input');
388
+ }
389
+ // Handle skill composition
390
+ let skillPrompts = [];
391
+ if (!options.noSkills) {
392
+ const skillRefs = [];
393
+ if (options.skillsOnly) {
394
+ // Use only the specified skills (ignore defaults)
395
+ skillRefs.push(...options.skillsOnly.split(',').map((s) => s.trim()));
396
+ }
397
+ else {
398
+ // Start with agent's default skills (if any)
399
+ const defaultSkills = agentData.default_skills || [];
400
+ skillRefs.push(...defaultSkills);
401
+ // Add any additional skills specified via --skills
402
+ if (options.skills) {
403
+ skillRefs.push(...options.skills.split(',').map((s) => s.trim()));
404
+ }
405
+ }
406
+ if (skillRefs.length > 0) {
407
+ process.stderr.write(`Loading ${skillRefs.length} skill(s)...\n`);
408
+ skillPrompts = await loadSkillPrompts(resolved, skillRefs, org);
409
+ }
410
+ }
411
+ // Execute locally
412
+ process.stderr.write(`Executing locally...\n\n`);
413
+ const result = await executePromptLocally(agentData, inputData, skillPrompts, resolved);
414
+ if (options.json) {
415
+ (0, output_1.printJson)(result);
416
+ }
417
+ else {
418
+ (0, output_1.printJson)(result);
419
+ }
420
+ });
421
+ }