@orchagent/cli 0.3.56 → 0.3.58
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/fork.js +108 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/pull.js +419 -0
- package/dist/index.js +1 -0
- package/dist/lib/api.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
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.registerForkCommand = registerForkCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const config_1 = require("../lib/config");
|
|
9
|
+
const api_1 = require("../lib/api");
|
|
10
|
+
const agent_ref_1 = require("../lib/agent-ref");
|
|
11
|
+
const errors_1 = require("../lib/errors");
|
|
12
|
+
const analytics_1 = require("../lib/analytics");
|
|
13
|
+
const output_1 = require("../lib/output");
|
|
14
|
+
function getWorkspaceAuthError(err) {
|
|
15
|
+
if (!(err instanceof api_1.ApiError) || (err.status !== 401 && err.status !== 403)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const message = err.message.toLowerCase();
|
|
19
|
+
if (message.includes('workspace targeting') ||
|
|
20
|
+
message.includes('specified workspace') ||
|
|
21
|
+
message.includes('user authentication') ||
|
|
22
|
+
message.includes('clerk')) {
|
|
23
|
+
return new errors_1.CliError('Forking into a specific workspace requires a user session key. Run `orch login` (browser sign-in, without `--key`) and retry.');
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
async function resolveWorkspace(config, workspaceSlug) {
|
|
28
|
+
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
29
|
+
const workspace = response.workspaces.find((w) => w.slug === workspaceSlug);
|
|
30
|
+
if (!workspace) {
|
|
31
|
+
throw new errors_1.CliError(`Workspace '${workspaceSlug}' not found. Run \`orchagent workspace list\` to see available workspaces.`);
|
|
32
|
+
}
|
|
33
|
+
return workspace;
|
|
34
|
+
}
|
|
35
|
+
function registerForkCommand(program) {
|
|
36
|
+
program
|
|
37
|
+
.command('fork <agent>')
|
|
38
|
+
.description('Fork a public agent into your workspace')
|
|
39
|
+
.option('--name <new-name>', 'Rename the forked agent')
|
|
40
|
+
.option('-w, --workspace <workspace-slug>', 'Target workspace slug')
|
|
41
|
+
.option('--json', 'Output raw JSON')
|
|
42
|
+
.addHelpText('after', `
|
|
43
|
+
Examples:
|
|
44
|
+
orch fork orchagent/my-discord-agent
|
|
45
|
+
orch fork orchagent/my-discord-agent --workspace acme-corp
|
|
46
|
+
orch fork orchagent/my-discord-agent --name customer-support-bot
|
|
47
|
+
orch fork orchagent/my-discord-agent@v2 --json
|
|
48
|
+
`)
|
|
49
|
+
.action(async (agentRef, options) => {
|
|
50
|
+
const write = (message) => {
|
|
51
|
+
if (!options.json)
|
|
52
|
+
process.stdout.write(message);
|
|
53
|
+
};
|
|
54
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
55
|
+
if (!config.apiKey) {
|
|
56
|
+
throw new errors_1.CliError('Not logged in. Run `orchagent login` first.');
|
|
57
|
+
}
|
|
58
|
+
const { org, agent, version } = (0, agent_ref_1.parseAgentRef)(agentRef);
|
|
59
|
+
write('Resolving source agent...\n');
|
|
60
|
+
const source = await (0, api_1.getPublicAgent)(config, org, agent, version);
|
|
61
|
+
if (!source.id) {
|
|
62
|
+
throw new errors_1.CliError(`Could not resolve source agent ID for '${org}/${agent}@${version}'.`);
|
|
63
|
+
}
|
|
64
|
+
let targetWorkspace = null;
|
|
65
|
+
if (options.workspace) {
|
|
66
|
+
write('Resolving target workspace...\n');
|
|
67
|
+
targetWorkspace = await resolveWorkspace(config, options.workspace);
|
|
68
|
+
}
|
|
69
|
+
write('Forking agent...\n');
|
|
70
|
+
const payload = {};
|
|
71
|
+
if (targetWorkspace)
|
|
72
|
+
payload.workspace_id = targetWorkspace.id;
|
|
73
|
+
const requestedName = options.name?.trim();
|
|
74
|
+
if (requestedName)
|
|
75
|
+
payload.new_name = requestedName;
|
|
76
|
+
let result;
|
|
77
|
+
try {
|
|
78
|
+
result = await (0, api_1.forkAgent)(config, source.id, payload);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const authErr = getWorkspaceAuthError(err);
|
|
82
|
+
if (authErr)
|
|
83
|
+
throw authErr;
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
await (0, analytics_1.track)('cli_fork', {
|
|
87
|
+
source_org: org,
|
|
88
|
+
source_agent: agent,
|
|
89
|
+
source_version: version,
|
|
90
|
+
target_workspace: targetWorkspace?.slug ?? null,
|
|
91
|
+
});
|
|
92
|
+
if (options.json) {
|
|
93
|
+
(0, output_1.printJson)(result);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const forked = result.agent;
|
|
97
|
+
const targetOrgSlug = forked.org_slug ?? targetWorkspace?.slug ?? 'current-workspace';
|
|
98
|
+
write(`\n${chalk_1.default.green('\u2713')} Forked ${org}/${agent}@${version}\n`);
|
|
99
|
+
write(` New agent: ${targetOrgSlug}/${forked.name}@${forked.version}\n`);
|
|
100
|
+
if (targetWorkspace) {
|
|
101
|
+
write(` Workspace: ${targetWorkspace.name} (${targetWorkspace.slug})\n`);
|
|
102
|
+
}
|
|
103
|
+
if (result.service_key) {
|
|
104
|
+
write(`\nService key (save this - shown only once):\n`);
|
|
105
|
+
write(` ${result.service_key}\n`);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
package/dist/commands/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const run_1 = require("./run");
|
|
|
12
12
|
const info_1 = require("./info");
|
|
13
13
|
const skill_1 = require("./skill");
|
|
14
14
|
const delete_1 = require("./delete");
|
|
15
|
+
const fork_1 = require("./fork");
|
|
15
16
|
const github_1 = require("./github");
|
|
16
17
|
const doctor_1 = require("./doctor");
|
|
17
18
|
const status_1 = require("./status");
|
|
@@ -31,6 +32,7 @@ const agent_keys_1 = require("./agent-keys");
|
|
|
31
32
|
const schedule_1 = require("./schedule");
|
|
32
33
|
const service_1 = require("./service");
|
|
33
34
|
const transfer_1 = require("./transfer");
|
|
35
|
+
const pull_1 = require("./pull");
|
|
34
36
|
function registerCommands(program) {
|
|
35
37
|
(0, login_1.registerLoginCommand)(program);
|
|
36
38
|
(0, whoami_1.registerWhoamiCommand)(program);
|
|
@@ -43,6 +45,7 @@ function registerCommands(program) {
|
|
|
43
45
|
(0, keys_1.registerKeysCommand)(program);
|
|
44
46
|
(0, skill_1.registerSkillCommand)(program);
|
|
45
47
|
(0, delete_1.registerDeleteCommand)(program);
|
|
48
|
+
(0, fork_1.registerForkCommand)(program);
|
|
46
49
|
(0, github_1.registerGitHubCommand)(program);
|
|
47
50
|
(0, doctor_1.registerDoctorCommand)(program);
|
|
48
51
|
(0, status_1.registerStatusCommand)(program);
|
|
@@ -62,4 +65,5 @@ function registerCommands(program) {
|
|
|
62
65
|
(0, schedule_1.registerScheduleCommand)(program);
|
|
63
66
|
(0, service_1.registerServiceCommand)(program);
|
|
64
67
|
(0, transfer_1.registerTransferCommand)(program);
|
|
68
|
+
(0, pull_1.registerPullCommand)(program);
|
|
65
69
|
}
|
|
@@ -0,0 +1,419 @@
|
|
|
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.registerPullCommand = registerPullCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const config_1 = require("../lib/config");
|
|
13
|
+
const api_1 = require("../lib/api");
|
|
14
|
+
const errors_1 = require("../lib/errors");
|
|
15
|
+
const analytics_1 = require("../lib/analytics");
|
|
16
|
+
const output_1 = require("../lib/output");
|
|
17
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
18
|
+
function parsePullRef(value) {
|
|
19
|
+
const [ref, versionPart] = value.split('@');
|
|
20
|
+
const version = versionPart?.trim() || 'latest';
|
|
21
|
+
const segments = ref.split('/');
|
|
22
|
+
if (segments.length === 1) {
|
|
23
|
+
return { agent: segments[0], version };
|
|
24
|
+
}
|
|
25
|
+
if (segments.length === 2) {
|
|
26
|
+
return { org: segments[0], agent: segments[1], version };
|
|
27
|
+
}
|
|
28
|
+
throw new errors_1.CliError('Invalid agent reference. Use org/agent[@version] or agent[@version] format.');
|
|
29
|
+
}
|
|
30
|
+
function canonicalType(typeValue) {
|
|
31
|
+
const normalized = (typeValue || 'agent').toLowerCase();
|
|
32
|
+
return normalized === 'skill' ? 'skill' : 'agent';
|
|
33
|
+
}
|
|
34
|
+
function resolveEngine(data) {
|
|
35
|
+
const ee = data.execution_engine;
|
|
36
|
+
if (ee === 'direct_llm' || ee === 'managed_loop' || ee === 'code_runtime') {
|
|
37
|
+
return ee;
|
|
38
|
+
}
|
|
39
|
+
const normalized = (data.type || '').toLowerCase();
|
|
40
|
+
if (normalized === 'tool' || normalized === 'code')
|
|
41
|
+
return 'code_runtime';
|
|
42
|
+
if (normalized === 'agentic')
|
|
43
|
+
return 'managed_loop';
|
|
44
|
+
return 'direct_llm';
|
|
45
|
+
}
|
|
46
|
+
// ─── Agent Resolution ───────────────────────────────────────────────────────
|
|
47
|
+
async function resolveAgent(config, org, agent, version) {
|
|
48
|
+
// 1. Try public download endpoint
|
|
49
|
+
try {
|
|
50
|
+
const data = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${agent}/${version}/download`);
|
|
51
|
+
return {
|
|
52
|
+
name: data.name,
|
|
53
|
+
version: data.version,
|
|
54
|
+
description: data.description,
|
|
55
|
+
type: data.type || 'agent',
|
|
56
|
+
run_mode: data.run_mode,
|
|
57
|
+
execution_engine: data.execution_engine,
|
|
58
|
+
callable: data.callable,
|
|
59
|
+
prompt: data.prompt,
|
|
60
|
+
input_schema: data.input_schema,
|
|
61
|
+
output_schema: data.output_schema,
|
|
62
|
+
supported_providers: data.supported_providers,
|
|
63
|
+
default_models: data.default_models,
|
|
64
|
+
default_skills: data.default_skills,
|
|
65
|
+
skills_locked: data.skills_locked,
|
|
66
|
+
source_url: data.source_url,
|
|
67
|
+
pip_package: data.pip_package,
|
|
68
|
+
run_command: data.run_command,
|
|
69
|
+
entrypoint: data.entrypoint,
|
|
70
|
+
has_bundle: data.has_bundle,
|
|
71
|
+
source: 'public_download',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
// 2. Handle 403 (server-only / download-disabled)
|
|
76
|
+
if (err instanceof api_1.ApiError && err.status === 403) {
|
|
77
|
+
const payload = err.payload;
|
|
78
|
+
const errorCode = payload?.error?.code;
|
|
79
|
+
if (errorCode === 'PAID_AGENT_SERVER_ONLY' || errorCode === 'DOWNLOAD_DISABLED') {
|
|
80
|
+
// Try authenticated owner path
|
|
81
|
+
if (config.apiKey) {
|
|
82
|
+
const ownerData = await tryOwnerFallback(config, org, agent, version);
|
|
83
|
+
if (ownerData)
|
|
84
|
+
return { ...ownerData, source: 'owner_authenticated' };
|
|
85
|
+
}
|
|
86
|
+
// Not owner - block with message
|
|
87
|
+
if (errorCode === 'PAID_AGENT_SERVER_ONLY') {
|
|
88
|
+
throw new errors_1.CliError(`This agent is paid and runs on server only.\n\n` +
|
|
89
|
+
`Use cloud execution: orch run ${org}/${agent}@${version} --data '{...}'`);
|
|
90
|
+
}
|
|
91
|
+
throw new errors_1.CliError(`This agent is server-only and cannot be downloaded.\n\n` +
|
|
92
|
+
`Use cloud execution: orch run ${org}/${agent}@${version} --data '{...}'`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// 3. Handle 404 - try private authenticated fallback
|
|
96
|
+
if (!(err instanceof api_1.ApiError) || err.status !== 404)
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
// 4. Private agent fallback (authenticated)
|
|
100
|
+
if (!config.apiKey) {
|
|
101
|
+
throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
|
|
102
|
+
}
|
|
103
|
+
const userOrg = await (0, api_1.getOrg)(config);
|
|
104
|
+
if (userOrg.slug !== org) {
|
|
105
|
+
throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
|
|
106
|
+
}
|
|
107
|
+
const data = await resolveFromMyAgents(config, agent, version, org);
|
|
108
|
+
if (!data) {
|
|
109
|
+
throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
|
|
110
|
+
}
|
|
111
|
+
return { ...data, source: 'private_authenticated' };
|
|
112
|
+
}
|
|
113
|
+
async function tryOwnerFallback(config, org, agent, version) {
|
|
114
|
+
try {
|
|
115
|
+
const myAgents = await (0, api_1.listMyAgents)(config);
|
|
116
|
+
let match;
|
|
117
|
+
if (version === 'latest') {
|
|
118
|
+
match = myAgents
|
|
119
|
+
.filter(a => a.name === agent && a.org_slug === org)
|
|
120
|
+
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
match = myAgents.find(a => a.name === agent && a.version === version && a.org_slug === org);
|
|
124
|
+
}
|
|
125
|
+
if (!match)
|
|
126
|
+
return null;
|
|
127
|
+
const agentData = await (0, api_1.request)(config, 'GET', `/agents/${match.id}`);
|
|
128
|
+
return mapAgentToPullData(agentData);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function resolveFromMyAgents(config, agent, version, org) {
|
|
135
|
+
const agents = await (0, api_1.listMyAgents)(config);
|
|
136
|
+
const matching = agents.filter(a => a.name === agent);
|
|
137
|
+
if (matching.length === 0)
|
|
138
|
+
return null;
|
|
139
|
+
let target;
|
|
140
|
+
if (version === 'latest') {
|
|
141
|
+
target = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
const found = matching.find(a => a.version === version);
|
|
145
|
+
if (!found)
|
|
146
|
+
return null;
|
|
147
|
+
target = found;
|
|
148
|
+
}
|
|
149
|
+
const agentData = await (0, api_1.request)(config, 'GET', `/agents/${target.id}`);
|
|
150
|
+
return mapAgentToPullData(agentData);
|
|
151
|
+
}
|
|
152
|
+
function mapAgentToPullData(agent) {
|
|
153
|
+
return {
|
|
154
|
+
name: agent.name,
|
|
155
|
+
version: agent.version,
|
|
156
|
+
description: agent.description,
|
|
157
|
+
type: agent.type,
|
|
158
|
+
run_mode: agent.run_mode ?? null,
|
|
159
|
+
execution_engine: agent.execution_engine ?? null,
|
|
160
|
+
callable: agent.callable,
|
|
161
|
+
prompt: agent.prompt,
|
|
162
|
+
input_schema: agent.input_schema,
|
|
163
|
+
output_schema: agent.output_schema,
|
|
164
|
+
supported_providers: agent.supported_providers,
|
|
165
|
+
default_models: agent.default_models,
|
|
166
|
+
tags: agent.tags,
|
|
167
|
+
default_skills: agent.default_skills,
|
|
168
|
+
skills_locked: agent.skills_locked,
|
|
169
|
+
source_url: agent.source_url,
|
|
170
|
+
pip_package: agent.pip_package,
|
|
171
|
+
run_command: agent.run_command,
|
|
172
|
+
entrypoint: agent.entrypoint,
|
|
173
|
+
has_bundle: !!agent.code_bundle_url,
|
|
174
|
+
manifest: agent.manifest,
|
|
175
|
+
agentId: agent.id,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// ─── Manifest Reconstruction ────────────────────────────────────────────────
|
|
179
|
+
function buildManifest(data) {
|
|
180
|
+
const manifest = {
|
|
181
|
+
name: data.name,
|
|
182
|
+
description: data.description || '',
|
|
183
|
+
type: canonicalType(data.type) === 'skill' ? 'skill' : 'agent',
|
|
184
|
+
};
|
|
185
|
+
if (data.run_mode)
|
|
186
|
+
manifest.run_mode = data.run_mode;
|
|
187
|
+
if (data.callable !== undefined)
|
|
188
|
+
manifest.callable = data.callable;
|
|
189
|
+
if (data.tags && data.tags.length > 0)
|
|
190
|
+
manifest.tags = data.tags;
|
|
191
|
+
if (data.supported_providers && data.supported_providers.length > 0) {
|
|
192
|
+
// Don't include if it's just ['any'] (the default)
|
|
193
|
+
if (!(data.supported_providers.length === 1 && data.supported_providers[0] === 'any')) {
|
|
194
|
+
manifest.supported_providers = data.supported_providers;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (data.default_models && Object.keys(data.default_models).length > 0) {
|
|
198
|
+
manifest.default_models = data.default_models;
|
|
199
|
+
}
|
|
200
|
+
// Skills
|
|
201
|
+
if (data.default_skills && data.default_skills.length > 0) {
|
|
202
|
+
manifest.default_skills = data.default_skills;
|
|
203
|
+
}
|
|
204
|
+
if (data.skills_locked !== undefined && data.skills_locked) {
|
|
205
|
+
manifest.skills_locked = true;
|
|
206
|
+
}
|
|
207
|
+
// Engine-specific fields
|
|
208
|
+
const engine = resolveEngine(data);
|
|
209
|
+
if (engine === 'code_runtime') {
|
|
210
|
+
if (data.entrypoint && data.entrypoint !== 'sandbox_main.py') {
|
|
211
|
+
manifest.entrypoint = data.entrypoint;
|
|
212
|
+
}
|
|
213
|
+
if (data.source_url)
|
|
214
|
+
manifest.source_url = data.source_url;
|
|
215
|
+
if (data.pip_package)
|
|
216
|
+
manifest.pip_package = data.pip_package;
|
|
217
|
+
if (data.run_command)
|
|
218
|
+
manifest.run_command = data.run_command;
|
|
219
|
+
}
|
|
220
|
+
// Include orchestration manifest if present (for dependencies, etc.)
|
|
221
|
+
if (data.manifest && typeof data.manifest === 'object') {
|
|
222
|
+
const m = { ...data.manifest };
|
|
223
|
+
// Clean up fields that are already top-level
|
|
224
|
+
delete m.runtime;
|
|
225
|
+
delete m.loop;
|
|
226
|
+
if (Object.keys(m).length > 0) {
|
|
227
|
+
manifest.manifest = m;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return manifest;
|
|
231
|
+
}
|
|
232
|
+
// ─── Bundle Download + Extraction ───────────────────────────────────────────
|
|
233
|
+
async function downloadBundle(config, org, agent, version, agentId) {
|
|
234
|
+
try {
|
|
235
|
+
return await (0, api_1.downloadCodeBundle)(config, org, agent, version);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
if (!(err instanceof api_1.ApiError) || err.status !== 404)
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
241
|
+
if (config.apiKey && agentId) {
|
|
242
|
+
try {
|
|
243
|
+
return await (0, api_1.downloadCodeBundleAuthenticated)(config, agentId);
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
if (!(err instanceof api_1.ApiError) || err.status !== 404)
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
async function unzipBundle(zipPath, destDir) {
|
|
253
|
+
return new Promise((resolve, reject) => {
|
|
254
|
+
const proc = (0, child_process_1.spawn)('unzip', ['-q', zipPath, '-d', destDir], {
|
|
255
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
256
|
+
});
|
|
257
|
+
let stderr = '';
|
|
258
|
+
proc.stderr?.on('data', (chunk) => {
|
|
259
|
+
stderr += chunk.toString();
|
|
260
|
+
});
|
|
261
|
+
proc.on('close', (code) => {
|
|
262
|
+
if (code !== 0) {
|
|
263
|
+
const detail = stderr.trim() || `exit code ${code}`;
|
|
264
|
+
reject(new errors_1.CliError(`Failed to extract bundle: ${detail}`));
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
resolve();
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
proc.on('error', (err) => {
|
|
271
|
+
reject(new errors_1.CliError(`Failed to run unzip: ${err.message}. Make sure unzip is installed.`));
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
// ─── Command ────────────────────────────────────────────────────────────────
|
|
276
|
+
function registerPullCommand(program) {
|
|
277
|
+
program
|
|
278
|
+
.command('pull <agent>')
|
|
279
|
+
.description('Pull a published agent into a local project directory')
|
|
280
|
+
.option('-o, --output <path>', 'Output directory (default: ./<agent-name>/)')
|
|
281
|
+
.option('--overwrite', 'Replace existing output directory contents')
|
|
282
|
+
.option('--json', 'Print machine-readable result summary')
|
|
283
|
+
.addHelpText('after', `
|
|
284
|
+
Examples:
|
|
285
|
+
orch pull acme/my-agent
|
|
286
|
+
orch pull acme/my-agent@v2
|
|
287
|
+
orch pull my-agent --output ./custom-dir
|
|
288
|
+
orch pull acme/my-agent --overwrite
|
|
289
|
+
orch pull acme/my-agent --json
|
|
290
|
+
`)
|
|
291
|
+
.action(async (agentRef, options) => {
|
|
292
|
+
const write = (message) => {
|
|
293
|
+
if (!options.json)
|
|
294
|
+
process.stdout.write(message);
|
|
295
|
+
};
|
|
296
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
297
|
+
const parsed = parsePullRef(agentRef);
|
|
298
|
+
// Resolve org from workspace / defaultOrg fallback
|
|
299
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
300
|
+
const org = parsed.org ?? configFile.workspace ?? config.defaultOrg;
|
|
301
|
+
if (!org) {
|
|
302
|
+
throw new errors_1.CliError('Missing org. Use org/agent[@version] format, or set a default org with:\n' +
|
|
303
|
+
' orch config set default-org <org>');
|
|
304
|
+
}
|
|
305
|
+
write(`Resolving ${org}/${parsed.agent}@${parsed.version}...\n`);
|
|
306
|
+
// Resolve agent data
|
|
307
|
+
const data = await resolveAgent(config, org, parsed.agent, parsed.version);
|
|
308
|
+
// Reject skills
|
|
309
|
+
if (canonicalType(data.type) === 'skill') {
|
|
310
|
+
throw new errors_1.CliError("This is a skill. Use 'orch skill install <ref>' instead.");
|
|
311
|
+
}
|
|
312
|
+
// Resolve output path
|
|
313
|
+
const outputDir = path_1.default.resolve(options.output || `./${data.name}`);
|
|
314
|
+
// Check if output path already exists
|
|
315
|
+
try {
|
|
316
|
+
const stat = await promises_1.default.stat(outputDir);
|
|
317
|
+
if (stat.isFile()) {
|
|
318
|
+
throw new errors_1.CliError(`Output path '${outputDir}' is a file. Please specify a directory.`);
|
|
319
|
+
}
|
|
320
|
+
if (!options.overwrite) {
|
|
321
|
+
throw new errors_1.CliError(`Output directory '${outputDir}' already exists.\n` +
|
|
322
|
+
`Use --overwrite to replace its contents.`);
|
|
323
|
+
}
|
|
324
|
+
// Overwrite: clear and recreate
|
|
325
|
+
await promises_1.default.rm(outputDir, { recursive: true });
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
if (err.code !== 'ENOENT') {
|
|
329
|
+
if (err instanceof errors_1.CliError)
|
|
330
|
+
throw err;
|
|
331
|
+
throw err;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
await promises_1.default.mkdir(outputDir, { recursive: true });
|
|
335
|
+
const engine = resolveEngine(data);
|
|
336
|
+
const filesWritten = [];
|
|
337
|
+
const warnings = [];
|
|
338
|
+
let bundleExtracted = false;
|
|
339
|
+
// Write orchagent.json
|
|
340
|
+
const manifest = buildManifest(data);
|
|
341
|
+
await promises_1.default.writeFile(path_1.default.join(outputDir, 'orchagent.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
342
|
+
filesWritten.push('orchagent.json');
|
|
343
|
+
// Write prompt.md (for prompt-driven engines)
|
|
344
|
+
if (data.prompt && (engine === 'direct_llm' || engine === 'managed_loop')) {
|
|
345
|
+
await promises_1.default.writeFile(path_1.default.join(outputDir, 'prompt.md'), data.prompt);
|
|
346
|
+
filesWritten.push('prompt.md');
|
|
347
|
+
}
|
|
348
|
+
// Write schema.json (if schemas exist)
|
|
349
|
+
if (data.input_schema || data.output_schema) {
|
|
350
|
+
const schema = {};
|
|
351
|
+
if (data.input_schema)
|
|
352
|
+
schema.input = data.input_schema;
|
|
353
|
+
if (data.output_schema)
|
|
354
|
+
schema.output = data.output_schema;
|
|
355
|
+
await promises_1.default.writeFile(path_1.default.join(outputDir, 'schema.json'), JSON.stringify(schema, null, 2) + '\n');
|
|
356
|
+
filesWritten.push('schema.json');
|
|
357
|
+
}
|
|
358
|
+
// Bundle download for code_runtime agents
|
|
359
|
+
if (engine === 'code_runtime' && data.has_bundle) {
|
|
360
|
+
write('Downloading code bundle...\n');
|
|
361
|
+
const bundle = await downloadBundle(config, org, data.name, data.version, data.agentId);
|
|
362
|
+
if (bundle) {
|
|
363
|
+
const tempDir = path_1.default.join(os_1.default.tmpdir(), `orchagent-pull-${Date.now()}`);
|
|
364
|
+
const zipPath = path_1.default.join(tempDir, 'bundle.zip');
|
|
365
|
+
try {
|
|
366
|
+
await promises_1.default.mkdir(tempDir, { recursive: true });
|
|
367
|
+
await promises_1.default.writeFile(zipPath, bundle);
|
|
368
|
+
await unzipBundle(zipPath, outputDir);
|
|
369
|
+
bundleExtracted = true;
|
|
370
|
+
write('Bundle extracted.\n');
|
|
371
|
+
}
|
|
372
|
+
finally {
|
|
373
|
+
await promises_1.default.rm(tempDir, { recursive: true, force: true }).catch(() => { });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
warnings.push('No downloadable bundle available for this version.');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
else if (engine === 'code_runtime' && !data.has_bundle) {
|
|
381
|
+
warnings.push('No downloadable bundle available for this version.');
|
|
382
|
+
}
|
|
383
|
+
// Track analytics
|
|
384
|
+
await (0, analytics_1.track)('cli_pull', {
|
|
385
|
+
org,
|
|
386
|
+
agent: parsed.agent,
|
|
387
|
+
version: data.version,
|
|
388
|
+
engine,
|
|
389
|
+
source: data.source,
|
|
390
|
+
});
|
|
391
|
+
// Output
|
|
392
|
+
const resolvedRef = `${org}/${data.name}@${data.version}`;
|
|
393
|
+
if (options.json) {
|
|
394
|
+
const result = {
|
|
395
|
+
success: true,
|
|
396
|
+
requested_ref: `${org}/${parsed.agent}@${parsed.version}`,
|
|
397
|
+
resolved_ref: resolvedRef,
|
|
398
|
+
output_dir: outputDir,
|
|
399
|
+
engine,
|
|
400
|
+
source: data.source,
|
|
401
|
+
files_written: filesWritten,
|
|
402
|
+
bundle_extracted: bundleExtracted,
|
|
403
|
+
warnings,
|
|
404
|
+
};
|
|
405
|
+
(0, output_1.printJson)(result);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
write(`\n${chalk_1.default.green('\u2713')} Pulled ${resolvedRef}\n`);
|
|
409
|
+
write(` Output: ${outputDir}\n`);
|
|
410
|
+
write(` Engine: ${engine}\n`);
|
|
411
|
+
write(` Files: ${filesWritten.join(', ')}\n`);
|
|
412
|
+
if (bundleExtracted) {
|
|
413
|
+
write(` Bundle: extracted\n`);
|
|
414
|
+
}
|
|
415
|
+
for (const w of warnings) {
|
|
416
|
+
write(` ${chalk_1.default.yellow('Warning:')} ${w}\n`);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -61,6 +61,7 @@ program
|
|
|
61
61
|
Quick Reference:
|
|
62
62
|
run Run an agent (cloud by default, --local for local execution)
|
|
63
63
|
info Show agent details and input/output schemas
|
|
64
|
+
fork Fork a public template into your workspace
|
|
64
65
|
|
|
65
66
|
Installation:
|
|
66
67
|
npm install -g @orchagent/cli Install globally (then use: orch)
|
package/dist/lib/api.js
CHANGED
|
@@ -54,6 +54,7 @@ exports.getAgentWithFallback = getAgentWithFallback;
|
|
|
54
54
|
exports.downloadCodeBundleAuthenticated = downloadCodeBundleAuthenticated;
|
|
55
55
|
exports.checkAgentDelete = checkAgentDelete;
|
|
56
56
|
exports.deleteAgent = deleteAgent;
|
|
57
|
+
exports.forkAgent = forkAgent;
|
|
57
58
|
exports.checkAgentTransfer = checkAgentTransfer;
|
|
58
59
|
exports.transferAgent = transferAgent;
|
|
59
60
|
exports.previewAgentVersion = previewAgentVersion;
|
|
@@ -368,6 +369,15 @@ async function deleteAgent(config, agentId, confirmationName) {
|
|
|
368
369
|
const params = confirmationName ? `?confirmation_name=${encodeURIComponent(confirmationName)}` : '';
|
|
369
370
|
return request(config, 'DELETE', `/agents/${agentId}${params}`);
|
|
370
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Fork a public agent into the caller's workspace (or an explicit workspace_id).
|
|
374
|
+
*/
|
|
375
|
+
async function forkAgent(config, sourceAgentId, data = {}) {
|
|
376
|
+
return request(config, 'POST', `/agents/${sourceAgentId}/fork`, {
|
|
377
|
+
body: JSON.stringify(data),
|
|
378
|
+
headers: { 'Content-Type': 'application/json' },
|
|
379
|
+
});
|
|
380
|
+
}
|
|
371
381
|
/**
|
|
372
382
|
* Check if an agent can be transferred to another workspace.
|
|
373
383
|
*/
|
package/package.json
CHANGED