@orchagent/cli 0.2.8 → 0.2.9

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.
@@ -48,6 +48,13 @@ const output_1 = require("../lib/output");
48
48
  const llm_1 = require("../lib/llm");
49
49
  const DEFAULT_VERSION = 'latest';
50
50
  const AGENTS_DIR = path_1.default.join(os_1.default.homedir(), '.orchagent', 'agents');
51
+ // Local execution environment variables
52
+ const LOCAL_EXECUTION_ENV = 'ORCHAGENT_LOCAL_EXECUTION';
53
+ const AGENTS_DIR_ENV = 'ORCHAGENT_AGENTS_DIR';
54
+ const CALL_CHAIN_ENV = 'ORCHAGENT_CALL_CHAIN';
55
+ const DEADLINE_MS_ENV = 'ORCHAGENT_DEADLINE_MS';
56
+ const MAX_HOPS_ENV = 'ORCHAGENT_MAX_HOPS';
57
+ const DOWNSTREAM_REMAINING_ENV = 'ORCHAGENT_DOWNSTREAM_REMAINING';
51
58
  function parseAgentRef(value) {
52
59
  const [ref, versionPart] = value.split('@');
53
60
  const version = versionPart?.trim() || DEFAULT_VERSION;
@@ -70,7 +77,7 @@ async function checkDependencies(config, dependencies) {
70
77
  const [org, agent] = dep.id.split('/');
71
78
  try {
72
79
  const agentData = await downloadAgent(config, org, agent, dep.version);
73
- const downloadable = !!(agentData.source_url || agentData.pip_package || agentData.type === 'prompt');
80
+ const downloadable = !!(agentData.source_url || agentData.pip_package || agentData.has_bundle || agentData.type === 'prompt');
74
81
  results.push({ dep, downloadable, agentData });
75
82
  }
76
83
  catch {
@@ -117,6 +124,12 @@ async function promptUserForDeps(depStatuses) {
117
124
  });
118
125
  });
119
126
  }
127
+ async function downloadSkillDependency(config, ref, defaultOrg) {
128
+ const parsed = parseSkillRef(ref);
129
+ const org = parsed.org ?? defaultOrg;
130
+ const skillData = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${parsed.skill}/${parsed.version}/download`);
131
+ await saveAgentLocally(org, parsed.skill, skillData);
132
+ }
120
133
  async function downloadDependenciesRecursively(config, depStatuses, visited = new Set()) {
121
134
  for (const status of depStatuses) {
122
135
  if (!status.downloadable || !status.agentData)
@@ -127,12 +140,27 @@ async function downloadDependenciesRecursively(config, depStatuses, visited = ne
127
140
  visited.add(depRef);
128
141
  const [org, agent] = status.dep.id.split('/');
129
142
  process.stderr.write(`\nDownloading dependency: ${depRef}...\n`);
130
- // Save the dependency locally
143
+ // Save the dependency metadata locally
131
144
  await saveAgentLocally(org, agent, status.agentData);
132
- // Install if it's a code agent
145
+ // For bundle-based agents, also extract the bundle
146
+ if (status.agentData.has_bundle) {
147
+ await saveBundleLocally(config, org, agent, status.dep.version);
148
+ }
149
+ // Install if it's a pip/source code agent
133
150
  if (status.agentData.type === 'code' && (status.agentData.source_url || status.agentData.pip_package)) {
134
151
  await installCodeAgent(status.agentData);
135
152
  }
153
+ // Download default skills
154
+ const defaultSkills = status.agentData.default_skills || [];
155
+ for (const skillRef of defaultSkills) {
156
+ try {
157
+ await downloadSkillDependency(config, skillRef, org);
158
+ }
159
+ catch {
160
+ // Skill download failed - not critical, continue
161
+ process.stderr.write(` Warning: Failed to download skill ${skillRef}\n`);
162
+ }
163
+ }
136
164
  // Recursively download its dependencies
137
165
  if (status.agentData.dependencies && status.agentData.dependencies.length > 0) {
138
166
  const nestedStatuses = await checkDependencies(config, status.agentData.dependencies);
@@ -393,9 +421,32 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
393
421
  }
394
422
  // Run the entrypoint with input via stdin
395
423
  process.stderr.write(`\nRunning: python3 ${entrypoint}\n\n`);
424
+ // Pass auth credentials to subprocess for orchestrator agents calling sub-agents
425
+ const subprocessEnv = { ...process.env };
426
+ if (config.apiKey) {
427
+ subprocessEnv.ORCHAGENT_SERVICE_KEY = config.apiKey;
428
+ subprocessEnv.ORCHAGENT_API_URL = config.apiUrl;
429
+ }
430
+ // For orchestrator agents with dependencies, enable local execution mode
431
+ if (agentData.dependencies && agentData.dependencies.length > 0) {
432
+ subprocessEnv[LOCAL_EXECUTION_ENV] = 'true';
433
+ subprocessEnv[AGENTS_DIR_ENV] = AGENTS_DIR;
434
+ // Initialize call chain with this agent
435
+ const agentRef = `${org}/${agentName}@${version}`;
436
+ subprocessEnv[CALL_CHAIN_ENV] = agentRef;
437
+ // Set deadline from manifest timeout (default 120s)
438
+ const manifest = agentData;
439
+ const timeoutMs = manifest.manifest?.timeout_ms || 120000;
440
+ subprocessEnv[DEADLINE_MS_ENV] = String(Date.now() + timeoutMs);
441
+ // Set max hops from manifest (default 10)
442
+ subprocessEnv[MAX_HOPS_ENV] = String(manifest.manifest?.max_hops || 10);
443
+ // Set downstream cap
444
+ subprocessEnv[DOWNSTREAM_REMAINING_ENV] = String(manifest.manifest?.per_call_downstream_cap || 100);
445
+ }
396
446
  const proc = (0, child_process_1.spawn)('python3', [entrypointPath], {
397
447
  cwd: extractDir,
398
448
  stdio: ['pipe', 'pipe', 'pipe'],
449
+ env: subprocessEnv,
399
450
  });
400
451
  // Send input JSON via stdin
401
452
  proc.stdin.write(inputJson);
@@ -484,6 +535,51 @@ async function saveAgentLocally(org, agent, agentData) {
484
535
  }
485
536
  return agentDir;
486
537
  }
538
+ async function saveBundleLocally(config, org, agent, version) {
539
+ const agentDir = path_1.default.join(AGENTS_DIR, org, agent);
540
+ const bundleDir = path_1.default.join(agentDir, 'bundle');
541
+ // Check if already extracted with same version
542
+ const metaPath = path_1.default.join(agentDir, 'agent.json');
543
+ try {
544
+ const existingMeta = await promises_1.default.readFile(metaPath, 'utf-8');
545
+ const existing = JSON.parse(existingMeta);
546
+ if (existing.version === version) {
547
+ // Check if bundle dir exists
548
+ try {
549
+ await promises_1.default.access(bundleDir);
550
+ return bundleDir; // Already cached
551
+ }
552
+ catch {
553
+ // Bundle dir doesn't exist, need to extract
554
+ }
555
+ }
556
+ }
557
+ catch {
558
+ // Metadata doesn't exist, need to download
559
+ }
560
+ // Download and extract bundle
561
+ process.stderr.write(`Downloading bundle for ${org}/${agent}@${version}...\n`);
562
+ const bundleBuffer = await (0, api_1.downloadCodeBundle)(config, org, agent, version);
563
+ const tempZip = path_1.default.join(os_1.default.tmpdir(), `bundle-${Date.now()}.zip`);
564
+ await promises_1.default.writeFile(tempZip, bundleBuffer);
565
+ // Clean and recreate bundle directory
566
+ try {
567
+ await promises_1.default.rm(bundleDir, { recursive: true, force: true });
568
+ }
569
+ catch {
570
+ // Directory might not exist
571
+ }
572
+ await promises_1.default.mkdir(bundleDir, { recursive: true });
573
+ await unzipBundle(tempZip, bundleDir);
574
+ // Clean up temp file
575
+ try {
576
+ await promises_1.default.rm(tempZip);
577
+ }
578
+ catch {
579
+ // Ignore cleanup errors
580
+ }
581
+ return bundleDir;
582
+ }
487
583
  function registerRunCommand(program) {
488
584
  program
489
585
  .command('run <agent> [args...]')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Command-line interface for the OrchAgent AI agent marketplace",
5
5
  "license": "MIT",
6
6
  "author": "OrchAgent <hello@orchagent.io>",