@orchagent/cli 0.2.9 → 0.2.10

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.
@@ -128,7 +128,7 @@ Note: Use 'call' for server-side execution (requires login), 'run' for local exe
128
128
  if (!org) {
129
129
  throw new errors_1.CliError('Missing org. Use org/agent or set default org.');
130
130
  }
131
- const agentMeta = await (0, api_1.getPublicAgent)(resolved, org, parsed.agent, parsed.version);
131
+ const agentMeta = await (0, api_1.getAgentWithFallback)(resolved, org, parsed.agent, parsed.version);
132
132
  const endpoint = options.endpoint?.trim() || agentMeta.default_endpoint || 'analyze';
133
133
  const headers = {
134
134
  Authorization: `Bearer ${resolved.apiKey}`,
@@ -54,6 +54,57 @@ async function fetchReadme(url) {
54
54
  return null;
55
55
  }
56
56
  }
57
+ async function downloadAgentWithFallback(config, org, agent, version) {
58
+ // Try public endpoint first
59
+ try {
60
+ return await (0, api_1.publicRequest)(config, `/public/agents/${org}/${agent}/${version}/download`);
61
+ }
62
+ catch (err) {
63
+ if (!(err instanceof api_1.ApiError) || err.status !== 404)
64
+ throw err;
65
+ }
66
+ // Fallback to authenticated endpoint for private agents
67
+ if (!config.apiKey) {
68
+ throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
69
+ }
70
+ const userOrg = await (0, api_1.getOrg)(config);
71
+ if (userOrg.slug !== org) {
72
+ throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
73
+ }
74
+ // Find agent in user's list and construct download data
75
+ const agents = await (0, api_1.listMyAgents)(config);
76
+ const matching = agents.filter(a => a.name === agent);
77
+ if (matching.length === 0) {
78
+ throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
79
+ }
80
+ let targetAgent = matching[0];
81
+ if (version !== 'latest') {
82
+ const found = matching.find(a => a.version === version);
83
+ if (!found) {
84
+ throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
85
+ }
86
+ targetAgent = found;
87
+ }
88
+ else {
89
+ // Get most recent
90
+ targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
91
+ }
92
+ // Convert Agent to AgentDownload format
93
+ return {
94
+ type: targetAgent.type,
95
+ name: targetAgent.name,
96
+ version: targetAgent.version,
97
+ description: targetAgent.description,
98
+ prompt: targetAgent.prompt,
99
+ input_schema: targetAgent.input_schema,
100
+ output_schema: targetAgent.output_schema,
101
+ supported_providers: targetAgent.supported_providers || ['any'],
102
+ source_url: targetAgent.source_url,
103
+ run_command: targetAgent.run_command,
104
+ url: targetAgent.url,
105
+ sdk_compatible: false,
106
+ };
107
+ }
57
108
  function registerInfoCommand(program) {
58
109
  program
59
110
  .command('info <agent>')
@@ -63,7 +114,7 @@ function registerInfoCommand(program) {
63
114
  const config = await (0, config_1.getResolvedConfig)();
64
115
  const { org, agent, version } = parseAgentRef(agentArg);
65
116
  // Fetch agent metadata
66
- const agentData = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${agent}/${version}/download`);
117
+ const agentData = await downloadAgentWithFallback(config, org, agent, version);
67
118
  if (options.json) {
68
119
  // Don't expose internal routing URLs in JSON output
69
120
  const output = { ...agentData };
@@ -93,6 +144,10 @@ function registerInfoCommand(program) {
93
144
  if (agentData.run_command) {
94
145
  process.stdout.write(`Run: ${agentData.run_command}\n`);
95
146
  }
147
+ // Display SDK/Local Ready status
148
+ if (agentData.sdk_compatible) {
149
+ process.stdout.write(`Local Ready: yes (uses orchagent-sdk)\n`);
150
+ }
96
151
  }
97
152
  // Display input schema if available
98
153
  if (agentData.input_schema?.properties && Object.keys(agentData.input_schema.properties).length > 0) {
@@ -46,15 +46,38 @@ const SCHEMA_TEMPLATE = `{
46
46
  }
47
47
  }
48
48
  `;
49
+ const SKILL_TEMPLATE = `---
50
+ name: my-skill
51
+ description: When to use this skill
52
+ license: MIT
53
+ ---
54
+
55
+ # My Skill
56
+
57
+ Instructions and guidance for AI agents...
58
+ `;
49
59
  function registerInitCommand(program) {
50
60
  program
51
61
  .command('init')
52
62
  .description('Initialize a new agent project')
53
63
  .argument('[name]', 'Agent name (default: current directory name)')
54
- .option('--type <type>', 'Agent type: prompt or code (default: prompt)', 'prompt')
64
+ .option('--type <type>', 'Type: prompt, code, or skill (default: prompt)', 'prompt')
55
65
  .action(async (name, options) => {
56
66
  const cwd = process.cwd();
57
67
  const agentName = name || path_1.default.basename(cwd);
68
+ // Handle skill type separately
69
+ if (options.type === 'skill') {
70
+ const skillPath = path_1.default.join(cwd, 'SKILL.md');
71
+ const skillContent = SKILL_TEMPLATE.replace('my-skill', agentName);
72
+ await promises_1.default.writeFile(skillPath, skillContent);
73
+ process.stdout.write(`Initialized skill "${agentName}" in ${cwd}\n`);
74
+ process.stdout.write(`\nFiles created:\n`);
75
+ process.stdout.write(` SKILL.md - Skill content with frontmatter\n`);
76
+ process.stdout.write(`\nNext steps:\n`);
77
+ process.stdout.write(` 1. Edit SKILL.md with your skill content\n`);
78
+ process.stdout.write(` 2. Run: orchagent publish\n`);
79
+ return;
80
+ }
58
81
  const manifestPath = path_1.default.join(cwd, 'orchagent.json');
59
82
  const promptPath = path_1.default.join(cwd, 'prompt.md');
60
83
  const schemaPath = path_1.default.join(cwd, 'schema.json');
@@ -71,7 +94,7 @@ function registerInitCommand(program) {
71
94
  // Create manifest
72
95
  const manifest = JSON.parse(MANIFEST_TEMPLATE);
73
96
  manifest.name = agentName;
74
- manifest.type = options.type === 'code' ? 'code' : 'prompt';
97
+ manifest.type = ['code', 'skill'].includes(options.type) ? options.type : 'prompt';
75
98
  await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
76
99
  // Create prompt template (for prompt-based agents)
77
100
  if (options.type !== 'code') {
@@ -13,6 +13,37 @@ const api_1 = require("../lib/api");
13
13
  const errors_1 = require("../lib/errors");
14
14
  const analytics_1 = require("../lib/analytics");
15
15
  const bundle_1 = require("../lib/bundle");
16
+ /**
17
+ * Check if orchagent-sdk is listed in requirements.txt or pyproject.toml
18
+ */
19
+ async function detectSdkCompatible(agentDir) {
20
+ // Check requirements.txt
21
+ const requirementsPath = path_1.default.join(agentDir, 'requirements.txt');
22
+ try {
23
+ const content = await promises_1.default.readFile(requirementsPath, 'utf-8');
24
+ // Match orchagent-sdk with or without version specifier (e.g., orchagent-sdk, orchagent-sdk>=0.1.0)
25
+ if (/^orchagent-sdk\b/m.test(content)) {
26
+ return true;
27
+ }
28
+ }
29
+ catch {
30
+ // File doesn't exist, continue checking pyproject.toml
31
+ }
32
+ // Check pyproject.toml
33
+ const pyprojectPath = path_1.default.join(agentDir, 'pyproject.toml');
34
+ try {
35
+ const content = await promises_1.default.readFile(pyprojectPath, 'utf-8');
36
+ // Match orchagent-sdk in dependencies (various formats in TOML)
37
+ // e.g., "orchagent-sdk", 'orchagent-sdk>=0.1.0', orchagent-sdk = ">=0.1.0"
38
+ if (/["']?orchagent-sdk["']?\s*[=<>~!]?/m.test(content)) {
39
+ return true;
40
+ }
41
+ }
42
+ catch {
43
+ // File doesn't exist
44
+ }
45
+ return false;
46
+ }
16
47
  async function parseSkillMd(filePath) {
17
48
  try {
18
49
  const content = await promises_1.default.readFile(filePath, 'utf-8');
@@ -32,7 +63,7 @@ async function parseSkillMd(filePath) {
32
63
  function registerPublishCommand(program) {
33
64
  program
34
65
  .command('publish')
35
- .description('Publish agent from local files')
66
+ .description('Publish agent or skill from local files')
36
67
  .option('--url <url>', 'Agent URL (for code-based agents)')
37
68
  .option('--public', 'Make agent public (default: true)', true)
38
69
  .option('--private', 'Make agent private')
@@ -77,16 +108,17 @@ function registerPublishCommand(program) {
77
108
  if (!manifest.name) {
78
109
  throw new errors_1.CliError('orchagent.json must have name');
79
110
  }
80
- // Read prompt (for prompt-based agents)
111
+ // Read prompt (for prompt-based agents and skills)
81
112
  let prompt;
82
- if (manifest.type === 'prompt') {
113
+ if (manifest.type === 'prompt' || manifest.type === 'skill') {
83
114
  const promptPath = path_1.default.join(cwd, 'prompt.md');
84
115
  try {
85
116
  prompt = await promises_1.default.readFile(promptPath, 'utf-8');
86
117
  }
87
118
  catch (err) {
88
119
  if (err.code === 'ENOENT') {
89
- throw new errors_1.CliError('No prompt.md found for prompt-based agent');
120
+ const agentTypeName = manifest.type === 'skill' ? 'skill' : 'prompt-based agent';
121
+ throw new errors_1.CliError(`No prompt.md found for ${agentTypeName}`);
90
122
  }
91
123
  throw err;
92
124
  }
@@ -128,6 +160,14 @@ function registerPublishCommand(program) {
128
160
  const org = await (0, api_1.getOrg)(config);
129
161
  // Default to 'any' provider if not specified
130
162
  const supportedProviders = manifest.supported_providers || ['any'];
163
+ // Detect SDK compatibility for code agents
164
+ let sdkCompatible = false;
165
+ if (manifest.type === 'code') {
166
+ sdkCompatible = await detectSdkCompatible(cwd);
167
+ if (sdkCompatible) {
168
+ process.stdout.write(`SDK detected - agent will be marked as Local Ready\n`);
169
+ }
170
+ }
131
171
  // Create the agent (server auto-assigns version)
132
172
  const result = await (0, api_1.createAgent)(config, {
133
173
  name: manifest.name,
@@ -145,6 +185,10 @@ function registerPublishCommand(program) {
145
185
  source_url: manifest.source_url,
146
186
  pip_package: manifest.pip_package,
147
187
  run_command: manifest.run_command,
188
+ // SDK compatibility flag
189
+ sdk_compatible: sdkCompatible || undefined,
190
+ // Orchestration manifest (includes dependencies)
191
+ manifest: manifest.manifest,
148
192
  });
149
193
  const assignedVersion = result.agent?.version || 'v1';
150
194
  const agentId = result.agent?.id;
@@ -165,9 +209,9 @@ function registerPublishCommand(program) {
165
209
  if (!validation.valid) {
166
210
  throw new errors_1.CliError(`Bundle validation failed: ${validation.error}`);
167
211
  }
168
- // Upload the bundle
212
+ // Upload the bundle with entrypoint
169
213
  process.stdout.write(` Uploading bundle...\n`);
170
- const uploadResult = await (0, api_1.uploadCodeBundle)(config, agentId, bundlePath);
214
+ const uploadResult = await (0, api_1.uploadCodeBundle)(config, agentId, bundlePath, manifest.entrypoint);
171
215
  process.stdout.write(` Uploaded: ${uploadResult.code_hash.substring(0, 12)}...\n`);
172
216
  }
173
217
  finally {
@@ -68,8 +68,73 @@ function parseAgentRef(value) {
68
68
  throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
69
69
  }
70
70
  async function downloadAgent(config, org, agent, version) {
71
- // Public endpoint - no auth required
72
- return (0, api_1.publicRequest)(config, `/public/agents/${org}/${agent}/${version}/download`);
71
+ // Try public endpoint first
72
+ try {
73
+ return await (0, api_1.publicRequest)(config, `/public/agents/${org}/${agent}/${version}/download`);
74
+ }
75
+ catch (err) {
76
+ if (!(err instanceof api_1.ApiError) || err.status !== 404)
77
+ throw err;
78
+ }
79
+ // Fallback to authenticated endpoint for private agents
80
+ if (!config.apiKey) {
81
+ throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
82
+ }
83
+ const userOrg = await (0, api_1.getOrg)(config);
84
+ if (userOrg.slug !== org) {
85
+ throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
86
+ }
87
+ // Find agent in user's list
88
+ const agents = await (0, api_1.listMyAgents)(config);
89
+ const matching = agents.filter(a => a.name === agent);
90
+ if (matching.length === 0) {
91
+ throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
92
+ }
93
+ let targetAgent = matching[0];
94
+ if (version !== 'latest') {
95
+ const found = matching.find(a => a.version === version);
96
+ if (!found) {
97
+ throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
98
+ }
99
+ targetAgent = found;
100
+ }
101
+ else {
102
+ targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
103
+ }
104
+ // Convert Agent to AgentDownload format
105
+ return {
106
+ id: targetAgent.id,
107
+ type: targetAgent.type,
108
+ name: targetAgent.name,
109
+ version: targetAgent.version,
110
+ description: targetAgent.description,
111
+ prompt: targetAgent.prompt,
112
+ input_schema: targetAgent.input_schema,
113
+ output_schema: targetAgent.output_schema,
114
+ supported_providers: targetAgent.supported_providers || ['any'],
115
+ default_models: targetAgent.default_models,
116
+ source_url: targetAgent.source_url,
117
+ pip_package: targetAgent.pip_package,
118
+ run_command: targetAgent.run_command,
119
+ url: targetAgent.url,
120
+ has_bundle: !!targetAgent.code_bundle_url,
121
+ entrypoint: targetAgent.entrypoint,
122
+ };
123
+ }
124
+ async function downloadBundleWithFallback(config, org, agentName, version, agentId) {
125
+ // Try public endpoint first
126
+ try {
127
+ return await (0, api_1.downloadCodeBundle)(config, org, agentName, version);
128
+ }
129
+ catch (err) {
130
+ if (!(err instanceof api_1.ApiError) || err.status !== 404)
131
+ throw err;
132
+ }
133
+ // Fallback to authenticated endpoint
134
+ if (!config.apiKey || !agentId) {
135
+ throw new api_1.ApiError(`Bundle for '${org}/${agentName}@${version}' not found`, 404);
136
+ }
137
+ return await (0, api_1.downloadCodeBundleAuthenticated)(config, agentId);
73
138
  }
74
139
  async function checkDependencies(config, dependencies) {
75
140
  const results = [];
@@ -144,7 +209,7 @@ async function downloadDependenciesRecursively(config, depStatuses, visited = ne
144
209
  await saveAgentLocally(org, agent, status.agentData);
145
210
  // For bundle-based agents, also extract the bundle
146
211
  if (status.agentData.has_bundle) {
147
- await saveBundleLocally(config, org, agent, status.dep.version);
212
+ await saveBundleLocally(config, org, agent, status.dep.version, status.agentData.id);
148
213
  }
149
214
  // Install if it's a pip/source code agent
150
215
  if (status.agentData.type === 'code' && (status.agentData.source_url || status.agentData.pip_package)) {
@@ -328,7 +393,7 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
328
393
  try {
329
394
  // Download the bundle
330
395
  process.stderr.write(`Downloading bundle...\n`);
331
- const bundleBuffer = await (0, api_1.downloadCodeBundle)(config, org, agentName, version);
396
+ const bundleBuffer = await downloadBundleWithFallback(config, org, agentName, version, agentData.id);
332
397
  await promises_1.default.writeFile(bundleZip, bundleBuffer);
333
398
  process.stderr.write(`Bundle downloaded (${bundleBuffer.length} bytes)\n`);
334
399
  // Extract the bundle
@@ -535,7 +600,7 @@ async function saveAgentLocally(org, agent, agentData) {
535
600
  }
536
601
  return agentDir;
537
602
  }
538
- async function saveBundleLocally(config, org, agent, version) {
603
+ async function saveBundleLocally(config, org, agent, version, agentId) {
539
604
  const agentDir = path_1.default.join(AGENTS_DIR, org, agent);
540
605
  const bundleDir = path_1.default.join(agentDir, 'bundle');
541
606
  // Check if already extracted with same version
@@ -559,7 +624,7 @@ async function saveBundleLocally(config, org, agent, version) {
559
624
  }
560
625
  // Download and extract bundle
561
626
  process.stderr.write(`Downloading bundle for ${org}/${agent}@${version}...\n`);
562
- const bundleBuffer = await (0, api_1.downloadCodeBundle)(config, org, agent, version);
627
+ const bundleBuffer = await downloadBundleWithFallback(config, org, agent, version, agentId);
563
628
  const tempZip = path_1.default.join(os_1.default.tmpdir(), `bundle-${Date.now()}.zip`);
564
629
  await promises_1.default.writeFile(tempZip, bundleBuffer);
565
630
  // Clean and recreate bundle directory
@@ -49,6 +49,61 @@ function parseSkillRef(value) {
49
49
  }
50
50
  throw new errors_1.CliError('Invalid skill reference. Use org/skill or skill format.');
51
51
  }
52
+ async function downloadSkillWithFallback(config, org, skill, version) {
53
+ // Try public endpoint first
54
+ try {
55
+ const meta = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${skill}/${version}`);
56
+ // Verify it's a skill type before downloading
57
+ const skillType = meta.type;
58
+ if (skillType !== 'skill') {
59
+ throw new errors_1.CliError(`${org}/${skill} is not a skill (type: ${skillType || 'prompt'})`);
60
+ }
61
+ // Download content from public endpoint
62
+ return await (0, api_1.publicRequest)(config, `/public/agents/${org}/${skill}/${version}/download`);
63
+ }
64
+ catch (err) {
65
+ if (!(err instanceof api_1.ApiError) || err.status !== 404)
66
+ throw err;
67
+ }
68
+ // Fallback to authenticated endpoint for private skills
69
+ if (!config.apiKey) {
70
+ throw new api_1.ApiError(`Skill '${org}/${skill}@${version}' not found`, 404);
71
+ }
72
+ const userOrg = await (0, api_1.getOrg)(config);
73
+ if (userOrg.slug !== org) {
74
+ throw new api_1.ApiError(`Skill '${org}/${skill}@${version}' not found`, 404);
75
+ }
76
+ // Find skill in user's list
77
+ const agents = await (0, api_1.listMyAgents)(config);
78
+ const matching = agents.filter(a => a.name === skill && a.type === 'skill');
79
+ if (matching.length === 0) {
80
+ throw new api_1.ApiError(`Skill '${org}/${skill}@${version}' not found`, 404);
81
+ }
82
+ let targetAgent = matching[0];
83
+ if (version !== 'v1' && version !== 'latest') {
84
+ const found = matching.find(a => a.version === version);
85
+ if (!found) {
86
+ throw new api_1.ApiError(`Skill '${org}/${skill}@${version}' not found`, 404);
87
+ }
88
+ targetAgent = found;
89
+ }
90
+ else {
91
+ // Get most recent
92
+ targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
93
+ }
94
+ // Verify it's a skill type
95
+ if (targetAgent.type !== 'skill') {
96
+ throw new errors_1.CliError(`${org}/${skill} is not a skill (type: ${targetAgent.type || 'prompt'})`);
97
+ }
98
+ // Convert Agent to SkillDownload format
99
+ return {
100
+ type: targetAgent.type,
101
+ name: targetAgent.name,
102
+ version: targetAgent.version,
103
+ description: targetAgent.description,
104
+ prompt: targetAgent.prompt,
105
+ };
106
+ }
52
107
  function registerSkillCommand(program) {
53
108
  const skill = program.command('skill').description('Manage and install skills');
54
109
  // orch skill list (deprecated)
@@ -65,6 +120,40 @@ function registerSkillCommand(program) {
65
120
  'Browse all skills at: https://orchagent.io/explore\n');
66
121
  process.exit(0);
67
122
  });
123
+ // orch skill create [name]
124
+ skill
125
+ .command('create [name]')
126
+ .description('Create a new skill from template')
127
+ .action(async (name) => {
128
+ const cwd = process.cwd();
129
+ const skillName = name || path_1.default.basename(cwd);
130
+ const skillPath = path_1.default.join(cwd, 'SKILL.md');
131
+ // Check if SKILL.md already exists
132
+ try {
133
+ await promises_1.default.access(skillPath);
134
+ throw new errors_1.CliError('SKILL.md already exists');
135
+ }
136
+ catch (err) {
137
+ if (err.code !== 'ENOENT')
138
+ throw err;
139
+ }
140
+ const template = `---
141
+ name: ${skillName}
142
+ description: When to use this skill
143
+ license: MIT
144
+ ---
145
+
146
+ # ${skillName}
147
+
148
+ Instructions and guidance for AI agents...
149
+ `;
150
+ await promises_1.default.writeFile(skillPath, template);
151
+ await (0, analytics_1.track)('cli_skill_create', { name: skillName });
152
+ process.stdout.write(`Created skill: ${skillPath}\n`);
153
+ process.stdout.write(`\nNext steps:\n`);
154
+ process.stdout.write(` 1. Edit SKILL.md with your skill content\n`);
155
+ process.stdout.write(` 2. Run: orchagent publish\n`);
156
+ });
68
157
  // orch skill install <skill>
69
158
  skill
70
159
  .command('install <skill>')
@@ -77,14 +166,8 @@ function registerSkillCommand(program) {
77
166
  if (!org) {
78
167
  throw new errors_1.CliError('Missing org. Use org/skill or set default org.');
79
168
  }
80
- // Fetch skill metadata to verify it's a skill
81
- const skillMeta = await (0, api_1.publicRequest)(resolved, `/public/agents/${org}/${parsed.skill}/${parsed.version}`);
82
- const skillType = skillMeta.type;
83
- if (skillType !== 'skill') {
84
- throw new errors_1.CliError(`${org}/${parsed.skill} is not a skill (type: ${skillType || 'prompt'})`);
85
- }
86
- // Download skill content
87
- const skillData = await (0, api_1.publicRequest)(resolved, `/public/agents/${org}/${parsed.skill}/${parsed.version}/download`);
169
+ // Download skill (tries public first, falls back to authenticated for private)
170
+ const skillData = await downloadSkillWithFallback(resolved, org, parsed.skill, parsed.version);
88
171
  if (!skillData.prompt) {
89
172
  throw new errors_1.CliError('Skill has no content');
90
173
  }
package/dist/lib/api.js CHANGED
@@ -49,6 +49,9 @@ exports.searchAgents = searchAgents;
49
49
  exports.fetchLlmKeys = fetchLlmKeys;
50
50
  exports.downloadCodeBundle = downloadCodeBundle;
51
51
  exports.uploadCodeBundle = uploadCodeBundle;
52
+ exports.getMyAgent = getMyAgent;
53
+ exports.getAgentWithFallback = getAgentWithFallback;
54
+ exports.downloadCodeBundleAuthenticated = downloadCodeBundleAuthenticated;
52
55
  class ApiError extends Error {
53
56
  status;
54
57
  payload;
@@ -166,7 +169,7 @@ async function downloadCodeBundle(config, org, agent, version) {
166
169
  /**
167
170
  * Upload a code bundle for a hosted code agent.
168
171
  */
169
- async function uploadCodeBundle(config, agentId, bundlePath) {
172
+ async function uploadCodeBundle(config, agentId, bundlePath, entrypoint) {
170
173
  if (!config.apiKey) {
171
174
  throw new ApiError('Missing API key. Run `orchagent login` first.', 401);
172
175
  }
@@ -177,11 +180,16 @@ async function uploadCodeBundle(config, agentId, bundlePath) {
177
180
  // Create form data
178
181
  const formData = new FormData();
179
182
  formData.append('file', blob, 'bundle.zip');
183
+ // Build headers
184
+ const headers = {
185
+ Authorization: `Bearer ${config.apiKey}`,
186
+ };
187
+ if (entrypoint) {
188
+ headers['x-entrypoint'] = entrypoint;
189
+ }
180
190
  const response = await fetch(`${config.apiUrl.replace(/\/$/, '')}/agents/${agentId}/upload`, {
181
191
  method: 'POST',
182
- headers: {
183
- Authorization: `Bearer ${config.apiKey}`,
184
- },
192
+ headers,
185
193
  body: formData,
186
194
  });
187
195
  if (!response.ok) {
@@ -189,3 +197,67 @@ async function uploadCodeBundle(config, agentId, bundlePath) {
189
197
  }
190
198
  return (await response.json());
191
199
  }
200
+ /**
201
+ * Get single agent by name/version from authenticated endpoint.
202
+ */
203
+ async function getMyAgent(config, agentName, version) {
204
+ const agents = await listMyAgents(config);
205
+ const matching = agents.filter(a => a.name === agentName);
206
+ if (matching.length === 0)
207
+ return null;
208
+ if (version === 'latest') {
209
+ return matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
210
+ }
211
+ return matching.find(a => a.version === version) ?? null;
212
+ }
213
+ /**
214
+ * Try public endpoint first, fallback to authenticated for private agents.
215
+ */
216
+ async function getAgentWithFallback(config, org, agentName, version) {
217
+ try {
218
+ return await getPublicAgent(config, org, agentName, version);
219
+ }
220
+ catch (err) {
221
+ if (!(err instanceof ApiError) || err.status !== 404)
222
+ throw err;
223
+ }
224
+ if (!config.apiKey) {
225
+ throw new ApiError(`Agent '${org}/${agentName}@${version}' not found`, 404);
226
+ }
227
+ const userOrg = await getOrg(config);
228
+ if (userOrg.slug !== org) {
229
+ throw new ApiError(`Agent '${org}/${agentName}@${version}' not found`, 404);
230
+ }
231
+ const myAgent = await getMyAgent(config, agentName, version);
232
+ if (!myAgent) {
233
+ throw new ApiError(`Agent '${org}/${agentName}@${version}' not found`, 404);
234
+ }
235
+ return myAgent;
236
+ }
237
+ /**
238
+ * Download a code bundle for a private agent using authenticated endpoint.
239
+ */
240
+ async function downloadCodeBundleAuthenticated(config, agentId) {
241
+ if (!config.apiKey) {
242
+ throw new ApiError('Missing API key for authenticated bundle download', 401);
243
+ }
244
+ const response = await fetch(`${config.apiUrl.replace(/\/$/, '')}/agents/${agentId}/bundle`, {
245
+ headers: {
246
+ Authorization: `Bearer ${config.apiKey}`,
247
+ },
248
+ });
249
+ if (!response.ok) {
250
+ const text = await response.text();
251
+ let message = response.statusText;
252
+ try {
253
+ const payload = JSON.parse(text);
254
+ message = payload.error?.message || payload.message || message;
255
+ }
256
+ catch {
257
+ // Use default message
258
+ }
259
+ throw new ApiError(message, response.status);
260
+ }
261
+ const arrayBuffer = await response.arrayBuffer();
262
+ return Buffer.from(arrayBuffer);
263
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "Command-line interface for the OrchAgent AI agent marketplace",
5
5
  "license": "MIT",
6
6
  "author": "OrchAgent <hello@orchagent.io>",