@orchagent/cli 0.2.8 → 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.
- package/dist/commands/call.js +1 -1
- 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 +167 -6
- package/dist/commands/skill.js +91 -8
- package/dist/lib/api.js +76 -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}`,
|
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
|
@@ -48,6 +48,13 @@ const output_1 = require("../lib/output");
|
|
|
48
48
|
const llm_1 = require("../lib/llm");
|
|
49
49
|
const DEFAULT_VERSION = 'latest';
|
|
50
50
|
const AGENTS_DIR = path_1.default.join(os_1.default.homedir(), '.orchagent', 'agents');
|
|
51
|
+
// Local execution environment variables
|
|
52
|
+
const LOCAL_EXECUTION_ENV = 'ORCHAGENT_LOCAL_EXECUTION';
|
|
53
|
+
const AGENTS_DIR_ENV = 'ORCHAGENT_AGENTS_DIR';
|
|
54
|
+
const CALL_CHAIN_ENV = 'ORCHAGENT_CALL_CHAIN';
|
|
55
|
+
const DEADLINE_MS_ENV = 'ORCHAGENT_DEADLINE_MS';
|
|
56
|
+
const MAX_HOPS_ENV = 'ORCHAGENT_MAX_HOPS';
|
|
57
|
+
const DOWNSTREAM_REMAINING_ENV = 'ORCHAGENT_DOWNSTREAM_REMAINING';
|
|
51
58
|
function parseAgentRef(value) {
|
|
52
59
|
const [ref, versionPart] = value.split('@');
|
|
53
60
|
const version = versionPart?.trim() || DEFAULT_VERSION;
|
|
@@ -61,8 +68,73 @@ function parseAgentRef(value) {
|
|
|
61
68
|
throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
|
|
62
69
|
}
|
|
63
70
|
async function downloadAgent(config, org, agent, version) {
|
|
64
|
-
//
|
|
65
|
-
|
|
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);
|
|
66
138
|
}
|
|
67
139
|
async function checkDependencies(config, dependencies) {
|
|
68
140
|
const results = [];
|
|
@@ -70,7 +142,7 @@ async function checkDependencies(config, dependencies) {
|
|
|
70
142
|
const [org, agent] = dep.id.split('/');
|
|
71
143
|
try {
|
|
72
144
|
const agentData = await downloadAgent(config, org, agent, dep.version);
|
|
73
|
-
const downloadable = !!(agentData.source_url || agentData.pip_package || agentData.type === 'prompt');
|
|
145
|
+
const downloadable = !!(agentData.source_url || agentData.pip_package || agentData.has_bundle || agentData.type === 'prompt');
|
|
74
146
|
results.push({ dep, downloadable, agentData });
|
|
75
147
|
}
|
|
76
148
|
catch {
|
|
@@ -117,6 +189,12 @@ async function promptUserForDeps(depStatuses) {
|
|
|
117
189
|
});
|
|
118
190
|
});
|
|
119
191
|
}
|
|
192
|
+
async function downloadSkillDependency(config, ref, defaultOrg) {
|
|
193
|
+
const parsed = parseSkillRef(ref);
|
|
194
|
+
const org = parsed.org ?? defaultOrg;
|
|
195
|
+
const skillData = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${parsed.skill}/${parsed.version}/download`);
|
|
196
|
+
await saveAgentLocally(org, parsed.skill, skillData);
|
|
197
|
+
}
|
|
120
198
|
async function downloadDependenciesRecursively(config, depStatuses, visited = new Set()) {
|
|
121
199
|
for (const status of depStatuses) {
|
|
122
200
|
if (!status.downloadable || !status.agentData)
|
|
@@ -127,12 +205,27 @@ async function downloadDependenciesRecursively(config, depStatuses, visited = ne
|
|
|
127
205
|
visited.add(depRef);
|
|
128
206
|
const [org, agent] = status.dep.id.split('/');
|
|
129
207
|
process.stderr.write(`\nDownloading dependency: ${depRef}...\n`);
|
|
130
|
-
// Save the dependency locally
|
|
208
|
+
// Save the dependency metadata locally
|
|
131
209
|
await saveAgentLocally(org, agent, status.agentData);
|
|
132
|
-
//
|
|
210
|
+
// For bundle-based agents, also extract the bundle
|
|
211
|
+
if (status.agentData.has_bundle) {
|
|
212
|
+
await saveBundleLocally(config, org, agent, status.dep.version, status.agentData.id);
|
|
213
|
+
}
|
|
214
|
+
// Install if it's a pip/source code agent
|
|
133
215
|
if (status.agentData.type === 'code' && (status.agentData.source_url || status.agentData.pip_package)) {
|
|
134
216
|
await installCodeAgent(status.agentData);
|
|
135
217
|
}
|
|
218
|
+
// Download default skills
|
|
219
|
+
const defaultSkills = status.agentData.default_skills || [];
|
|
220
|
+
for (const skillRef of defaultSkills) {
|
|
221
|
+
try {
|
|
222
|
+
await downloadSkillDependency(config, skillRef, org);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Skill download failed - not critical, continue
|
|
226
|
+
process.stderr.write(` Warning: Failed to download skill ${skillRef}\n`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
136
229
|
// Recursively download its dependencies
|
|
137
230
|
if (status.agentData.dependencies && status.agentData.dependencies.length > 0) {
|
|
138
231
|
const nestedStatuses = await checkDependencies(config, status.agentData.dependencies);
|
|
@@ -300,7 +393,7 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
|
|
|
300
393
|
try {
|
|
301
394
|
// Download the bundle
|
|
302
395
|
process.stderr.write(`Downloading bundle...\n`);
|
|
303
|
-
const bundleBuffer = await (
|
|
396
|
+
const bundleBuffer = await downloadBundleWithFallback(config, org, agentName, version, agentData.id);
|
|
304
397
|
await promises_1.default.writeFile(bundleZip, bundleBuffer);
|
|
305
398
|
process.stderr.write(`Bundle downloaded (${bundleBuffer.length} bytes)\n`);
|
|
306
399
|
// Extract the bundle
|
|
@@ -393,9 +486,32 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
|
|
|
393
486
|
}
|
|
394
487
|
// Run the entrypoint with input via stdin
|
|
395
488
|
process.stderr.write(`\nRunning: python3 ${entrypoint}\n\n`);
|
|
489
|
+
// Pass auth credentials to subprocess for orchestrator agents calling sub-agents
|
|
490
|
+
const subprocessEnv = { ...process.env };
|
|
491
|
+
if (config.apiKey) {
|
|
492
|
+
subprocessEnv.ORCHAGENT_SERVICE_KEY = config.apiKey;
|
|
493
|
+
subprocessEnv.ORCHAGENT_API_URL = config.apiUrl;
|
|
494
|
+
}
|
|
495
|
+
// For orchestrator agents with dependencies, enable local execution mode
|
|
496
|
+
if (agentData.dependencies && agentData.dependencies.length > 0) {
|
|
497
|
+
subprocessEnv[LOCAL_EXECUTION_ENV] = 'true';
|
|
498
|
+
subprocessEnv[AGENTS_DIR_ENV] = AGENTS_DIR;
|
|
499
|
+
// Initialize call chain with this agent
|
|
500
|
+
const agentRef = `${org}/${agentName}@${version}`;
|
|
501
|
+
subprocessEnv[CALL_CHAIN_ENV] = agentRef;
|
|
502
|
+
// Set deadline from manifest timeout (default 120s)
|
|
503
|
+
const manifest = agentData;
|
|
504
|
+
const timeoutMs = manifest.manifest?.timeout_ms || 120000;
|
|
505
|
+
subprocessEnv[DEADLINE_MS_ENV] = String(Date.now() + timeoutMs);
|
|
506
|
+
// Set max hops from manifest (default 10)
|
|
507
|
+
subprocessEnv[MAX_HOPS_ENV] = String(manifest.manifest?.max_hops || 10);
|
|
508
|
+
// Set downstream cap
|
|
509
|
+
subprocessEnv[DOWNSTREAM_REMAINING_ENV] = String(manifest.manifest?.per_call_downstream_cap || 100);
|
|
510
|
+
}
|
|
396
511
|
const proc = (0, child_process_1.spawn)('python3', [entrypointPath], {
|
|
397
512
|
cwd: extractDir,
|
|
398
513
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
514
|
+
env: subprocessEnv,
|
|
399
515
|
});
|
|
400
516
|
// Send input JSON via stdin
|
|
401
517
|
proc.stdin.write(inputJson);
|
|
@@ -484,6 +600,51 @@ async function saveAgentLocally(org, agent, agentData) {
|
|
|
484
600
|
}
|
|
485
601
|
return agentDir;
|
|
486
602
|
}
|
|
603
|
+
async function saveBundleLocally(config, org, agent, version, agentId) {
|
|
604
|
+
const agentDir = path_1.default.join(AGENTS_DIR, org, agent);
|
|
605
|
+
const bundleDir = path_1.default.join(agentDir, 'bundle');
|
|
606
|
+
// Check if already extracted with same version
|
|
607
|
+
const metaPath = path_1.default.join(agentDir, 'agent.json');
|
|
608
|
+
try {
|
|
609
|
+
const existingMeta = await promises_1.default.readFile(metaPath, 'utf-8');
|
|
610
|
+
const existing = JSON.parse(existingMeta);
|
|
611
|
+
if (existing.version === version) {
|
|
612
|
+
// Check if bundle dir exists
|
|
613
|
+
try {
|
|
614
|
+
await promises_1.default.access(bundleDir);
|
|
615
|
+
return bundleDir; // Already cached
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
// Bundle dir doesn't exist, need to extract
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
// Metadata doesn't exist, need to download
|
|
624
|
+
}
|
|
625
|
+
// Download and extract bundle
|
|
626
|
+
process.stderr.write(`Downloading bundle for ${org}/${agent}@${version}...\n`);
|
|
627
|
+
const bundleBuffer = await downloadBundleWithFallback(config, org, agent, version, agentId);
|
|
628
|
+
const tempZip = path_1.default.join(os_1.default.tmpdir(), `bundle-${Date.now()}.zip`);
|
|
629
|
+
await promises_1.default.writeFile(tempZip, bundleBuffer);
|
|
630
|
+
// Clean and recreate bundle directory
|
|
631
|
+
try {
|
|
632
|
+
await promises_1.default.rm(bundleDir, { recursive: true, force: true });
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
635
|
+
// Directory might not exist
|
|
636
|
+
}
|
|
637
|
+
await promises_1.default.mkdir(bundleDir, { recursive: true });
|
|
638
|
+
await unzipBundle(tempZip, bundleDir);
|
|
639
|
+
// Clean up temp file
|
|
640
|
+
try {
|
|
641
|
+
await promises_1.default.rm(tempZip);
|
|
642
|
+
}
|
|
643
|
+
catch {
|
|
644
|
+
// Ignore cleanup errors
|
|
645
|
+
}
|
|
646
|
+
return bundleDir;
|
|
647
|
+
}
|
|
487
648
|
function registerRunCommand(program) {
|
|
488
649
|
program
|
|
489
650
|
.command('run <agent> [args...]')
|
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,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
|
+
}
|