@orchagent/cli 0.2.9 → 0.2.11

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}`,
@@ -0,0 +1,90 @@
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.registerDeleteCommand = registerDeleteCommand;
7
+ const promises_1 = __importDefault(require("readline/promises"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const config_1 = require("../lib/config");
10
+ const api_1 = require("../lib/api");
11
+ const errors_1 = require("../lib/errors");
12
+ const analytics_1 = require("../lib/analytics");
13
+ async function promptText(message) {
14
+ const rl = promises_1.default.createInterface({
15
+ input: process.stdin,
16
+ output: process.stdout,
17
+ });
18
+ const answer = await rl.question(message);
19
+ rl.close();
20
+ return answer.trim();
21
+ }
22
+ async function promptConfirm(message) {
23
+ const answer = await promptText(`${message} (y/N): `);
24
+ return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
25
+ }
26
+ function registerDeleteCommand(program) {
27
+ program
28
+ .command('delete <agent-name>')
29
+ .description('Delete an agent')
30
+ .option('--version <version>', 'Specific version to delete (default: latest)')
31
+ .option('-y, --yes', 'Skip confirmation prompt')
32
+ .action(async (agentName, options) => {
33
+ const config = await (0, config_1.getResolvedConfig)();
34
+ if (!config.apiKey) {
35
+ throw new errors_1.CliError('Not logged in. Run `orch login` first.');
36
+ }
37
+ process.stdout.write('Finding agent...\n');
38
+ // Find the agent
39
+ const agents = await (0, api_1.listMyAgents)(config);
40
+ const matching = agents.filter(a => a.name === agentName);
41
+ if (matching.length === 0) {
42
+ throw new errors_1.CliError(`Agent '${agentName}' not found`);
43
+ }
44
+ // Select version
45
+ let agent;
46
+ if (options.version) {
47
+ agent = matching.find(a => a.version === options.version);
48
+ if (!agent) {
49
+ throw new errors_1.CliError(`Version '${options.version}' not found for agent '${agentName}'`);
50
+ }
51
+ }
52
+ else {
53
+ // Get latest version
54
+ agent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
55
+ }
56
+ // Check if confirmation is required
57
+ const deleteCheck = await (0, api_1.checkAgentDelete)(config, agent.id);
58
+ // Show agent info
59
+ process.stdout.write(`\n${chalk_1.default.bold('Agent:')} ${agent.name}@${agent.version}\n`);
60
+ if (deleteCheck.stars_count > 0 || deleteCheck.fork_count > 0) {
61
+ process.stdout.write(`${chalk_1.default.bold('Stars:')} ${deleteCheck.stars_count} ${chalk_1.default.bold('Forks:')} ${deleteCheck.fork_count}\n`);
62
+ }
63
+ process.stdout.write('\n');
64
+ // Handle confirmation
65
+ if (!options.yes) {
66
+ if (deleteCheck.requires_confirmation) {
67
+ process.stdout.write(chalk_1.default.yellow('Warning: This agent has stars or forks. Type the agent name to confirm deletion.\n\n'));
68
+ const confirmName = await promptText(`Type "${agent.name}" to confirm deletion: `);
69
+ if (confirmName !== agent.name) {
70
+ process.stdout.write(chalk_1.default.red('\nDeletion cancelled. Name did not match.\n'));
71
+ process.exit(1);
72
+ }
73
+ }
74
+ else {
75
+ const confirmed = await promptConfirm(`Delete ${agent.name}@${agent.version}?`);
76
+ if (!confirmed) {
77
+ process.stdout.write(chalk_1.default.gray('Deletion cancelled.\n'));
78
+ process.exit(0);
79
+ }
80
+ }
81
+ }
82
+ // Perform deletion
83
+ process.stdout.write('Deleting agent...\n');
84
+ const confirmationName = deleteCheck.requires_confirmation ? agent.name : undefined;
85
+ await (0, api_1.deleteAgent)(config, agent.id, confirmationName);
86
+ await (0, analytics_1.track)('cli_delete', { agent_name: agent.name, version: agent.version });
87
+ process.stdout.write(`✓ Deleted ${agent.name}@${agent.version}\n`);
88
+ process.stdout.write(chalk_1.default.gray('\nData will be retained for 30 days before permanent deletion.\n'));
89
+ });
90
+ }
@@ -15,6 +15,7 @@ const keys_1 = require("./keys");
15
15
  const run_1 = require("./run");
16
16
  const info_1 = require("./info");
17
17
  const skill_1 = require("./skill");
18
+ const delete_1 = require("./delete");
18
19
  function registerCommands(program) {
19
20
  (0, login_1.registerLoginCommand)(program);
20
21
  (0, whoami_1.registerWhoamiCommand)(program);
@@ -30,4 +31,5 @@ function registerCommands(program) {
30
31
  (0, llm_config_1.registerLlmConfigCommand)(program);
31
32
  (0, keys_1.registerKeysCommand)(program);
32
33
  (0, skill_1.registerSkillCommand)(program);
34
+ (0, delete_1.registerDeleteCommand)(program);
33
35
  }
@@ -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)) {
@@ -306,7 +371,9 @@ async function unzipBundle(zipPath, destDir) {
306
371
  });
307
372
  proc.on('close', (code) => {
308
373
  if (code !== 0) {
309
- reject(new errors_1.CliError(`Failed to extract bundle: ${stderr || `exit code ${code}`}`));
374
+ const detail = stderr.trim() || `exit code ${code}`;
375
+ reject(new errors_1.CliError(`Failed to extract bundle: ${detail}\n` +
376
+ `Ensure the bundle is valid. Try re-publishing the agent.`));
310
377
  }
311
378
  else {
312
379
  resolve();
@@ -328,7 +395,7 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
328
395
  try {
329
396
  // Download the bundle
330
397
  process.stderr.write(`Downloading bundle...\n`);
331
- const bundleBuffer = await (0, api_1.downloadCodeBundle)(config, org, agentName, version);
398
+ const bundleBuffer = await downloadBundleWithFallback(config, org, agentName, version, agentData.id);
332
399
  await promises_1.default.writeFile(bundleZip, bundleBuffer);
333
400
  process.stderr.write(`Bundle downloaded (${bundleBuffer.length} bytes)\n`);
334
401
  // Extract the bundle
@@ -501,9 +568,14 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
501
568
  else if (exitCode !== 0) {
502
569
  // No stdout, check stderr
503
570
  if (stderr.trim()) {
504
- throw new errors_1.CliError(`Agent error: ${stderr.trim()}`);
571
+ throw new errors_1.CliError(`Agent exited with code ${exitCode}\n\nError output:\n${stderr.trim()}`);
505
572
  }
506
- throw new errors_1.CliError(`Agent exited with code ${exitCode} (no output)`);
573
+ throw new errors_1.CliError(`Agent exited with code ${exitCode} (no output)\n\n` +
574
+ `Common causes:\n` +
575
+ ` - Missing dependency (check requirements.txt)\n` +
576
+ ` - Syntax error in entrypoint\n` +
577
+ ` - Agent crashed before writing output\n\n` +
578
+ `Run with --verbose or check logs in dashboard.`);
507
579
  }
508
580
  }
509
581
  finally {
@@ -535,7 +607,7 @@ async function saveAgentLocally(org, agent, agentData) {
535
607
  }
536
608
  return agentDir;
537
609
  }
538
- async function saveBundleLocally(config, org, agent, version) {
610
+ async function saveBundleLocally(config, org, agent, version, agentId) {
539
611
  const agentDir = path_1.default.join(AGENTS_DIR, org, agent);
540
612
  const bundleDir = path_1.default.join(agentDir, 'bundle');
541
613
  // Check if already extracted with same version
@@ -559,7 +631,7 @@ async function saveBundleLocally(config, org, agent, version) {
559
631
  }
560
632
  // Download and extract bundle
561
633
  process.stderr.write(`Downloading bundle for ${org}/${agent}@${version}...\n`);
562
- const bundleBuffer = await (0, api_1.downloadCodeBundle)(config, org, agent, version);
634
+ const bundleBuffer = await downloadBundleWithFallback(config, org, agent, version, agentId);
563
635
  const tempZip = path_1.default.join(os_1.default.tmpdir(), `bundle-${Date.now()}.zip`);
564
636
  await promises_1.default.writeFile(tempZip, bundleBuffer);
565
637
  // 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,11 @@ 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;
55
+ exports.checkAgentDelete = checkAgentDelete;
56
+ exports.deleteAgent = deleteAgent;
52
57
  class ApiError extends Error {
53
58
  status;
54
59
  payload;
@@ -166,7 +171,7 @@ async function downloadCodeBundle(config, org, agent, version) {
166
171
  /**
167
172
  * Upload a code bundle for a hosted code agent.
168
173
  */
169
- async function uploadCodeBundle(config, agentId, bundlePath) {
174
+ async function uploadCodeBundle(config, agentId, bundlePath, entrypoint) {
170
175
  if (!config.apiKey) {
171
176
  throw new ApiError('Missing API key. Run `orchagent login` first.', 401);
172
177
  }
@@ -177,11 +182,16 @@ async function uploadCodeBundle(config, agentId, bundlePath) {
177
182
  // Create form data
178
183
  const formData = new FormData();
179
184
  formData.append('file', blob, 'bundle.zip');
185
+ // Build headers
186
+ const headers = {
187
+ Authorization: `Bearer ${config.apiKey}`,
188
+ };
189
+ if (entrypoint) {
190
+ headers['x-entrypoint'] = entrypoint;
191
+ }
180
192
  const response = await fetch(`${config.apiUrl.replace(/\/$/, '')}/agents/${agentId}/upload`, {
181
193
  method: 'POST',
182
- headers: {
183
- Authorization: `Bearer ${config.apiKey}`,
184
- },
194
+ headers,
185
195
  body: formData,
186
196
  });
187
197
  if (!response.ok) {
@@ -189,3 +199,80 @@ async function uploadCodeBundle(config, agentId, bundlePath) {
189
199
  }
190
200
  return (await response.json());
191
201
  }
202
+ /**
203
+ * Get single agent by name/version from authenticated endpoint.
204
+ */
205
+ async function getMyAgent(config, agentName, version) {
206
+ const agents = await listMyAgents(config);
207
+ const matching = agents.filter(a => a.name === agentName);
208
+ if (matching.length === 0)
209
+ return null;
210
+ if (version === 'latest') {
211
+ return matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
212
+ }
213
+ return matching.find(a => a.version === version) ?? null;
214
+ }
215
+ /**
216
+ * Try public endpoint first, fallback to authenticated for private agents.
217
+ */
218
+ async function getAgentWithFallback(config, org, agentName, version) {
219
+ try {
220
+ return await getPublicAgent(config, org, agentName, version);
221
+ }
222
+ catch (err) {
223
+ if (!(err instanceof ApiError) || err.status !== 404)
224
+ throw err;
225
+ }
226
+ if (!config.apiKey) {
227
+ throw new ApiError(`Agent '${org}/${agentName}@${version}' not found`, 404);
228
+ }
229
+ const userOrg = await getOrg(config);
230
+ if (userOrg.slug !== org) {
231
+ throw new ApiError(`Agent '${org}/${agentName}@${version}' not found`, 404);
232
+ }
233
+ const myAgent = await getMyAgent(config, agentName, version);
234
+ if (!myAgent) {
235
+ throw new ApiError(`Agent '${org}/${agentName}@${version}' not found`, 404);
236
+ }
237
+ return myAgent;
238
+ }
239
+ /**
240
+ * Download a code bundle for a private agent using authenticated endpoint.
241
+ */
242
+ async function downloadCodeBundleAuthenticated(config, agentId) {
243
+ if (!config.apiKey) {
244
+ throw new ApiError('Missing API key for authenticated bundle download', 401);
245
+ }
246
+ const response = await fetch(`${config.apiUrl.replace(/\/$/, '')}/agents/${agentId}/bundle`, {
247
+ headers: {
248
+ Authorization: `Bearer ${config.apiKey}`,
249
+ },
250
+ });
251
+ if (!response.ok) {
252
+ const text = await response.text();
253
+ let message = response.statusText;
254
+ try {
255
+ const payload = JSON.parse(text);
256
+ message = payload.error?.message || payload.message || message;
257
+ }
258
+ catch {
259
+ // Use default message
260
+ }
261
+ throw new ApiError(message, response.status);
262
+ }
263
+ const arrayBuffer = await response.arrayBuffer();
264
+ return Buffer.from(arrayBuffer);
265
+ }
266
+ /**
267
+ * Check if an agent requires confirmation for deletion.
268
+ */
269
+ async function checkAgentDelete(config, agentId) {
270
+ return request(config, 'GET', `/agents/${agentId}/delete-check`);
271
+ }
272
+ /**
273
+ * Soft delete an agent.
274
+ */
275
+ async function deleteAgent(config, agentId, confirmationName) {
276
+ const params = confirmationName ? `?confirmation_name=${encodeURIComponent(confirmationName)}` : '';
277
+ return request(config, 'DELETE', `/agents/${agentId}${params}`);
278
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Command-line interface for the OrchAgent AI agent marketplace",
5
5
  "license": "MIT",
6
6
  "author": "OrchAgent <hello@orchagent.io>",