@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.
- package/dist/commands/call.js +1 -1
- package/dist/commands/delete.js +90 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/info.js +56 -1
- package/dist/commands/init.js +25 -2
- package/dist/commands/publish.js +50 -6
- package/dist/commands/run.js +81 -9
- package/dist/commands/skill.js +91 -8
- package/dist/lib/api.js +91 -4
- package/package.json +1 -1
package/dist/commands/call.js
CHANGED
|
@@ -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.
|
|
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
|
+
}
|
package/dist/commands/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/commands/info.js
CHANGED
|
@@ -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 (
|
|
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) {
|
package/dist/commands/init.js
CHANGED
|
@@ -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>', '
|
|
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 =
|
|
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') {
|
package/dist/commands/publish.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
package/dist/commands/run.js
CHANGED
|
@@ -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
|
-
//
|
|
72
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
package/dist/commands/skill.js
CHANGED
|
@@ -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
|
-
//
|
|
81
|
-
const
|
|
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
|
+
}
|