@orchagent/cli 0.3.31 → 0.3.34
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/adapters/agents-md.js +14 -1
- package/dist/adapters/claude-code.js +11 -0
- package/dist/adapters/cursor.js +13 -1
- package/dist/commands/call.js +105 -16
- package/dist/commands/config.js +25 -1
- package/dist/commands/delete.js +11 -20
- package/dist/commands/init.js +139 -22
- package/dist/commands/install.js +16 -3
- package/dist/commands/publish.js +76 -1
- package/dist/commands/search.js +52 -22
- package/dist/commands/security.js +2 -2
- package/dist/commands/skill.js +10 -7
- package/dist/commands/star.js +10 -3
- package/dist/commands/update.js +101 -66
- package/dist/commands/whoami.js +18 -0
- package/dist/index.js +6 -1
- package/dist/lib/api.js +71 -3
- package/dist/lib/config.js +11 -0
- package/dist/lib/doctor/checks/llm.js +128 -106
- package/dist/lib/doctor/output.js +91 -13
- package/dist/lib/doctor/runner.js +3 -12
- package/dist/lib/output.js +19 -12
- package/dist/lib/skill-resolve.js +99 -0
- package/dist/lib/update-notifier.js +146 -0
- package/package.json +1 -1
|
@@ -32,13 +32,26 @@ exports.agentsMdAdapter = {
|
|
|
32
32
|
const orgSlug = agent.org_slug || 'unknown';
|
|
33
33
|
const agentRef = `${orgSlug}/${agent.name}`;
|
|
34
34
|
const description = agent.description || '';
|
|
35
|
+
// Build skills section if present
|
|
36
|
+
let skillsSection = '';
|
|
37
|
+
if (agent.resolvedSkills && agent.resolvedSkills.length > 0) {
|
|
38
|
+
skillsSection = '\n### Bundled Skills\n\nThe following skills are bundled with this agent and must be applied.';
|
|
39
|
+
for (const skill of agent.resolvedSkills) {
|
|
40
|
+
skillsSection += `\n\n#### ${skill.name}`;
|
|
41
|
+
if (skill.description) {
|
|
42
|
+
skillsSection += `\n\n${skill.description}`;
|
|
43
|
+
}
|
|
44
|
+
skillsSection += `\n\n${skill.prompt}`;
|
|
45
|
+
}
|
|
46
|
+
skillsSection += '\n';
|
|
47
|
+
}
|
|
35
48
|
// Use orchagent markers for managed section
|
|
36
49
|
const content = `<!-- orchagent:${agentRef} -->
|
|
37
50
|
## ${agent.name}
|
|
38
51
|
|
|
39
52
|
${description}
|
|
40
53
|
|
|
41
|
-
${agent.prompt || ''}
|
|
54
|
+
${agent.prompt || ''}${skillsSection}
|
|
42
55
|
<!-- /orchagent:${agentRef} -->
|
|
43
56
|
`;
|
|
44
57
|
return [
|
|
@@ -70,6 +70,17 @@ exports.claudeCodeAdapter = {
|
|
|
70
70
|
if (agent.output_schema) {
|
|
71
71
|
body += `\n\n## Output Schema\n\nThis agent should return output matching:\n\`\`\`json\n${JSON.stringify(agent.output_schema, null, 2)}\n\`\`\``;
|
|
72
72
|
}
|
|
73
|
+
// Embed resolved skills
|
|
74
|
+
if (agent.resolvedSkills && agent.resolvedSkills.length > 0) {
|
|
75
|
+
body += '\n\n## Bundled Skills\n\nThe following skills are bundled with this agent and must be applied.';
|
|
76
|
+
for (const skill of agent.resolvedSkills) {
|
|
77
|
+
body += `\n\n### ${skill.name}`;
|
|
78
|
+
if (skill.description) {
|
|
79
|
+
body += `\n\n${skill.description}`;
|
|
80
|
+
}
|
|
81
|
+
body += `\n\n${skill.prompt}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
73
84
|
// Combine frontmatter + body
|
|
74
85
|
const content = `---\n${yaml_1.default.stringify(frontmatter).trim()}\n---\n\n${body.trim()}\n`;
|
|
75
86
|
return [
|
package/dist/adapters/cursor.js
CHANGED
|
@@ -38,6 +38,18 @@ exports.cursorAdapter = {
|
|
|
38
38
|
convert(agent) {
|
|
39
39
|
const normalizedName = (0, utils_1.normalizeAgentName)(agent.name);
|
|
40
40
|
const description = agent.description || `Rules from ${agent.name}`;
|
|
41
|
+
// Build skills section if present
|
|
42
|
+
let skillsSection = '';
|
|
43
|
+
if (agent.resolvedSkills && agent.resolvedSkills.length > 0) {
|
|
44
|
+
skillsSection = '\n\n## Bundled Skills\n\nThe following skills are bundled with this agent and must be applied.';
|
|
45
|
+
for (const skill of agent.resolvedSkills) {
|
|
46
|
+
skillsSection += `\n\n### ${skill.name}`;
|
|
47
|
+
if (skill.description) {
|
|
48
|
+
skillsSection += `\n\n${skill.description}`;
|
|
49
|
+
}
|
|
50
|
+
skillsSection += `\n\n${skill.prompt}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
41
53
|
// Cursor .mdc format
|
|
42
54
|
const content = `---
|
|
43
55
|
description: ${description}
|
|
@@ -47,7 +59,7 @@ alwaysApply: false
|
|
|
47
59
|
|
|
48
60
|
# ${agent.name}
|
|
49
61
|
|
|
50
|
-
${agent.prompt || ''}
|
|
62
|
+
${agent.prompt || ''}${skillsSection}
|
|
51
63
|
`;
|
|
52
64
|
return [
|
|
53
65
|
{
|
package/dist/commands/call.js
CHANGED
|
@@ -15,6 +15,8 @@ const llm_1 = require("../lib/llm");
|
|
|
15
15
|
const analytics_1 = require("../lib/analytics");
|
|
16
16
|
const pricing_1 = require("../lib/pricing");
|
|
17
17
|
const DEFAULT_VERSION = 'latest';
|
|
18
|
+
// Well-known field names for file content in prompt agent schemas (priority order)
|
|
19
|
+
const CONTENT_FIELD_NAMES = ['code', 'content', 'text', 'source', 'input', 'file_content', 'body'];
|
|
18
20
|
// Keys that might indicate local file path references in JSON payloads
|
|
19
21
|
const LOCAL_PATH_KEYS = ['path', 'directory', 'file', 'filepath', 'dir', 'folder', 'local'];
|
|
20
22
|
/**
|
|
@@ -43,13 +45,42 @@ function warnIfLocalPathReference(jsonBody) {
|
|
|
43
45
|
if (pathKey) {
|
|
44
46
|
process.stderr.write(`Warning: Your payload contains a local path reference ('${pathKey}').\n` +
|
|
45
47
|
`Remote agents cannot access your local filesystem. The path will be interpreted\n` +
|
|
46
|
-
`by the server, not your local machine
|
|
48
|
+
`by the server, not your local machine.\n\n` +
|
|
49
|
+
`Tip: Use 'orchagent run <agent>' instead to execute locally with filesystem access.\n\n`);
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
catch {
|
|
50
53
|
// If parsing fails, skip the warning (the actual error will be thrown later)
|
|
51
54
|
}
|
|
52
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Infer the best JSON field name for file content based on the agent's input schema.
|
|
58
|
+
* Returns the field name to use, or 'content' as a safe default.
|
|
59
|
+
*/
|
|
60
|
+
function inferFileField(inputSchema) {
|
|
61
|
+
if (!inputSchema || typeof inputSchema !== 'object')
|
|
62
|
+
return 'content';
|
|
63
|
+
const props = inputSchema.properties;
|
|
64
|
+
if (!props || typeof props !== 'object')
|
|
65
|
+
return 'content';
|
|
66
|
+
const properties = props;
|
|
67
|
+
// Check for well-known field names in priority order
|
|
68
|
+
for (const field of CONTENT_FIELD_NAMES) {
|
|
69
|
+
if (properties[field] && properties[field].type === 'string')
|
|
70
|
+
return field;
|
|
71
|
+
}
|
|
72
|
+
// If there's exactly one required string property, use that
|
|
73
|
+
const required = (inputSchema.required ?? []);
|
|
74
|
+
const stringProps = Object.entries(properties)
|
|
75
|
+
.filter(([, v]) => v.type === 'string')
|
|
76
|
+
.map(([k]) => k);
|
|
77
|
+
if (stringProps.length === 1)
|
|
78
|
+
return stringProps[0];
|
|
79
|
+
const requiredStrings = stringProps.filter(k => required.includes(k));
|
|
80
|
+
if (requiredStrings.length === 1)
|
|
81
|
+
return requiredStrings[0];
|
|
82
|
+
return 'content';
|
|
83
|
+
}
|
|
53
84
|
function parseAgentRef(value) {
|
|
54
85
|
const [ref, versionPart] = value.split('@');
|
|
55
86
|
const version = versionPart?.trim() || DEFAULT_VERSION;
|
|
@@ -147,16 +178,24 @@ function registerCallCommand(program) {
|
|
|
147
178
|
.option('--skills-only <skills>', 'Use only these skills')
|
|
148
179
|
.option('--no-skills', 'Ignore default skills')
|
|
149
180
|
.option('--file <path...>', 'File(s) to upload (can specify multiple)')
|
|
181
|
+
.option('--file-field <field>', 'Schema field name for file content (prompt agents)')
|
|
150
182
|
.option('--metadata <json>', 'JSON metadata to send with files')
|
|
151
183
|
.addHelpText('after', `
|
|
152
184
|
Examples:
|
|
153
185
|
orch call orchagent/invoice-scanner invoice.pdf
|
|
186
|
+
orch call orchagent/useeffect-checker --file src/App.tsx
|
|
187
|
+
orch call orchagent/useeffect-checker --file src/App.tsx --file-field code
|
|
154
188
|
orch call orchagent/leak-finder --data '{"repo_url": "https://github.com/org/repo"}'
|
|
155
189
|
cat input.json | orch call acme/agent --data @-
|
|
156
190
|
orch call acme/image-processor photo.jpg --output result.png
|
|
157
191
|
|
|
158
192
|
Note: Use 'call' for server-side execution (requires login), 'run' for local execution.
|
|
159
193
|
|
|
194
|
+
File handling:
|
|
195
|
+
For prompt agents, file content is read and sent as JSON mapped to the agent's
|
|
196
|
+
input schema. Use --file-field to specify the field name (auto-detected by default).
|
|
197
|
+
For code agents, files are uploaded as multipart form data.
|
|
198
|
+
|
|
160
199
|
Important: Remote agents cannot access your local filesystem. If your --data payload
|
|
161
200
|
contains keys like 'path', 'directory', 'file', etc., those values will be interpreted
|
|
162
201
|
by the server, not your local machine. To send local files, use the positional file
|
|
@@ -207,7 +246,8 @@ Paid Agents:
|
|
|
207
246
|
}
|
|
208
247
|
if (isOwner) {
|
|
209
248
|
// Owner: show free message, no balance check needed
|
|
210
|
-
|
|
249
|
+
if (!options.json)
|
|
250
|
+
process.stderr.write(`Cost: FREE (author)\n\n`);
|
|
211
251
|
}
|
|
212
252
|
else {
|
|
213
253
|
// Non-owner: check balance
|
|
@@ -215,7 +255,8 @@ Paid Agents:
|
|
|
215
255
|
pricingInfo = { price_cents: price ?? null };
|
|
216
256
|
if (!price || price <= 0) {
|
|
217
257
|
// Price missing or invalid - warn but proceed (server will enforce)
|
|
218
|
-
|
|
258
|
+
if (!options.json)
|
|
259
|
+
process.stderr.write(`Warning: Pricing data unavailable. The server will verify payment.\n\n`);
|
|
219
260
|
}
|
|
220
261
|
else {
|
|
221
262
|
// Valid price - check balance
|
|
@@ -233,11 +274,13 @@ Paid Agents:
|
|
|
233
274
|
process.exit(errors_1.ExitCodes.PERMISSION_DENIED);
|
|
234
275
|
}
|
|
235
276
|
// Sufficient balance - show cost preview
|
|
236
|
-
|
|
277
|
+
if (!options.json)
|
|
278
|
+
process.stderr.write(`Cost: $${(price / 100).toFixed(2)}/call\n\n`);
|
|
237
279
|
}
|
|
238
280
|
catch (err) {
|
|
239
281
|
// Balance check failed - warn but proceed (server will enforce)
|
|
240
|
-
|
|
282
|
+
if (!options.json)
|
|
283
|
+
process.stderr.write(`Warning: Could not verify balance. The server will check payment.\n\n`);
|
|
241
284
|
}
|
|
242
285
|
}
|
|
243
286
|
}
|
|
@@ -354,8 +397,45 @@ Paid Agents:
|
|
|
354
397
|
}
|
|
355
398
|
headers['Content-Type'] = 'application/json';
|
|
356
399
|
}
|
|
400
|
+
else if ((filePaths.length > 0 || options.metadata) && agentMeta.type === 'prompt') {
|
|
401
|
+
// Prompt agent + files/metadata: read content and send as JSON
|
|
402
|
+
const fieldName = options.fileField || inferFileField(agentMeta.input_schema);
|
|
403
|
+
let bodyObj = {};
|
|
404
|
+
// Include metadata if provided
|
|
405
|
+
if (options.metadata) {
|
|
406
|
+
try {
|
|
407
|
+
bodyObj = JSON.parse(options.metadata);
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
throw new errors_1.CliError('--metadata must be valid JSON.');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (filePaths.length === 1) {
|
|
414
|
+
// Single file: map content to the inferred/specified schema field
|
|
415
|
+
const fileContent = await promises_1.default.readFile(filePaths[0], 'utf-8');
|
|
416
|
+
bodyObj[fieldName] = fileContent;
|
|
417
|
+
sourceLabel = filePaths[0];
|
|
418
|
+
}
|
|
419
|
+
else if (filePaths.length > 1) {
|
|
420
|
+
// Multiple files: map first to the schema field, add all as files object
|
|
421
|
+
const allContents = {};
|
|
422
|
+
for (const fp of filePaths) {
|
|
423
|
+
allContents[path_1.default.basename(fp)] = await promises_1.default.readFile(fp, 'utf-8');
|
|
424
|
+
}
|
|
425
|
+
// Set the primary field to the first file's content
|
|
426
|
+
const firstContent = await promises_1.default.readFile(filePaths[0], 'utf-8');
|
|
427
|
+
bodyObj[fieldName] = firstContent;
|
|
428
|
+
bodyObj.files = allContents;
|
|
429
|
+
sourceLabel = `${filePaths.length} files`;
|
|
430
|
+
}
|
|
431
|
+
if (llmCredentials) {
|
|
432
|
+
bodyObj.llm_credentials = llmCredentials;
|
|
433
|
+
}
|
|
434
|
+
body = JSON.stringify(bodyObj);
|
|
435
|
+
headers['Content-Type'] = 'application/json';
|
|
436
|
+
}
|
|
357
437
|
else if (filePaths.length > 0 || options.metadata) {
|
|
358
|
-
//
|
|
438
|
+
// Code agent: handle multipart file uploads
|
|
359
439
|
// Inject llm_credentials into metadata if available
|
|
360
440
|
let metadata = options.metadata;
|
|
361
441
|
if (llmCredentials) {
|
|
@@ -379,9 +459,9 @@ Paid Agents:
|
|
|
379
459
|
sourceLabel = multipart.sourceLabel;
|
|
380
460
|
}
|
|
381
461
|
const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}`;
|
|
382
|
-
// Make the API call with a spinner
|
|
383
|
-
const spinner = (0, spinner_1.createSpinner)(`Calling ${org}/${parsed.agent}@${parsed.version}...`);
|
|
384
|
-
spinner
|
|
462
|
+
// Make the API call with a spinner (suppress in --json mode for clean machine-readable output)
|
|
463
|
+
const spinner = options.json ? null : (0, spinner_1.createSpinner)(`Calling ${org}/${parsed.agent}@${parsed.version}...`);
|
|
464
|
+
spinner?.start();
|
|
385
465
|
let response;
|
|
386
466
|
try {
|
|
387
467
|
response = await (0, api_1.safeFetchWithRetryForCalls)(url, {
|
|
@@ -391,7 +471,7 @@ Paid Agents:
|
|
|
391
471
|
});
|
|
392
472
|
}
|
|
393
473
|
catch (err) {
|
|
394
|
-
spinner
|
|
474
|
+
spinner?.fail(`Call failed: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
395
475
|
throw err;
|
|
396
476
|
}
|
|
397
477
|
if (!response.ok) {
|
|
@@ -409,7 +489,7 @@ Paid Agents:
|
|
|
409
489
|
: undefined;
|
|
410
490
|
// Part 2: Handle 402 Payment Required
|
|
411
491
|
if (response.status === 402 || errorCode === 'INSUFFICIENT_CREDITS') {
|
|
412
|
-
spinner
|
|
492
|
+
spinner?.fail('Insufficient credits');
|
|
413
493
|
let errorMessage = 'Insufficient credits to call this agent.\n\n';
|
|
414
494
|
// Use pricing info from pre-call check if available
|
|
415
495
|
if (pricingInfo?.price_cents) {
|
|
@@ -422,22 +502,31 @@ Paid Agents:
|
|
|
422
502
|
throw new errors_1.CliError(errorMessage, errors_1.ExitCodes.PERMISSION_DENIED);
|
|
423
503
|
}
|
|
424
504
|
if (errorCode === 'LLM_KEY_REQUIRED') {
|
|
425
|
-
spinner
|
|
505
|
+
spinner?.fail('LLM key required');
|
|
426
506
|
throw new errors_1.CliError('This public agent requires you to provide an LLM key.\n' +
|
|
427
507
|
'Use --key <key> --provider <provider> or set OPENAI_API_KEY/ANTHROPIC_API_KEY env var.');
|
|
428
508
|
}
|
|
509
|
+
if (errorCode === 'LLM_RATE_LIMITED') {
|
|
510
|
+
const rateLimitMsg = typeof payload === 'object' && payload
|
|
511
|
+
? payload.error?.message || 'Rate limit exceeded'
|
|
512
|
+
: 'Rate limit exceeded';
|
|
513
|
+
spinner?.fail('Rate limited by LLM provider');
|
|
514
|
+
throw new errors_1.CliError(rateLimitMsg + '\n\n' +
|
|
515
|
+
'This is the LLM provider\'s rate limit on your API key, not an OrchAgent limit.\n' +
|
|
516
|
+
'To switch providers: orch call <agent> --provider <gemini|anthropic|openai>', errors_1.ExitCodes.RATE_LIMITED);
|
|
517
|
+
}
|
|
429
518
|
const message = typeof payload === 'object' && payload
|
|
430
519
|
? payload.error
|
|
431
520
|
?.message ||
|
|
432
521
|
payload.message ||
|
|
433
522
|
response.statusText
|
|
434
523
|
: response.statusText;
|
|
435
|
-
spinner
|
|
524
|
+
spinner?.fail(`Call failed: ${message}`);
|
|
436
525
|
throw new errors_1.CliError(message);
|
|
437
526
|
}
|
|
438
|
-
spinner
|
|
439
|
-
// After successful call, if it was a paid agent, show cost
|
|
440
|
-
if ((0, pricing_1.isPaidAgent)(agentMeta) && pricingInfo?.price_cents && pricingInfo.price_cents > 0) {
|
|
527
|
+
spinner?.succeed(`Called ${org}/${parsed.agent}@${parsed.version}`);
|
|
528
|
+
// After successful call, if it was a paid agent, show cost (suppress in --json mode)
|
|
529
|
+
if (!options.json && (0, pricing_1.isPaidAgent)(agentMeta) && pricingInfo?.price_cents && pricingInfo.price_cents > 0) {
|
|
441
530
|
process.stderr.write(`\nCost: $${(pricingInfo.price_cents / 100).toFixed(2)} USD\n`);
|
|
442
531
|
}
|
|
443
532
|
// Track successful call
|
package/dist/commands/config.js
CHANGED
|
@@ -10,7 +10,7 @@ function getAllValidFormatIds() {
|
|
|
10
10
|
const skillFormatIds = [...config_1.VALID_FORMAT_IDS];
|
|
11
11
|
return [...new Set([...adapterIds, ...skillFormatIds])];
|
|
12
12
|
}
|
|
13
|
-
const SUPPORTED_KEYS = ['default-format'];
|
|
13
|
+
const SUPPORTED_KEYS = ['default-format', 'default-scope'];
|
|
14
14
|
function isValidKey(key) {
|
|
15
15
|
return SUPPORTED_KEYS.includes(key);
|
|
16
16
|
}
|
|
@@ -29,6 +29,13 @@ async function setConfigValue(key, value) {
|
|
|
29
29
|
await (0, config_1.setDefaultFormats)(formats);
|
|
30
30
|
process.stdout.write(`Set default-format to: ${formats.join(',')}\n`);
|
|
31
31
|
}
|
|
32
|
+
if (key === 'default-scope') {
|
|
33
|
+
if (value !== 'user' && value !== 'project') {
|
|
34
|
+
throw new errors_1.CliError('Invalid scope. Must be "user" or "project"');
|
|
35
|
+
}
|
|
36
|
+
await (0, config_1.setDefaultScope)(value);
|
|
37
|
+
process.stdout.write(`Set default-scope to: ${value}\n`);
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
async function getConfigValue(key) {
|
|
34
41
|
if (!isValidKey(key)) {
|
|
@@ -44,6 +51,15 @@ async function getConfigValue(key) {
|
|
|
44
51
|
process.stdout.write(`${formats.join(',')}\n`);
|
|
45
52
|
}
|
|
46
53
|
}
|
|
54
|
+
if (key === 'default-scope') {
|
|
55
|
+
const scope = await (0, config_1.getDefaultScope)();
|
|
56
|
+
if (!scope) {
|
|
57
|
+
process.stdout.write('(not set)\n');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
process.stdout.write(`${scope}\n`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
47
63
|
}
|
|
48
64
|
async function listConfigValues() {
|
|
49
65
|
const config = await (0, config_1.loadConfig)();
|
|
@@ -56,6 +72,14 @@ async function listConfigValues() {
|
|
|
56
72
|
else {
|
|
57
73
|
process.stdout.write(' default-format: (not set)\n');
|
|
58
74
|
}
|
|
75
|
+
// default-scope
|
|
76
|
+
const scope = config.default_scope;
|
|
77
|
+
if (scope) {
|
|
78
|
+
process.stdout.write(` default-scope: ${scope}\n`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
process.stdout.write(' default-scope: (not set)\n');
|
|
82
|
+
}
|
|
59
83
|
process.stdout.write('\n');
|
|
60
84
|
}
|
|
61
85
|
function registerConfigCommand(program) {
|
package/dist/commands/delete.js
CHANGED
|
@@ -9,6 +9,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
9
9
|
const config_1 = require("../lib/config");
|
|
10
10
|
const api_1 = require("../lib/api");
|
|
11
11
|
const errors_1 = require("../lib/errors");
|
|
12
|
+
const agent_ref_1 = require("../lib/agent-ref");
|
|
12
13
|
const analytics_1 = require("../lib/analytics");
|
|
13
14
|
async function promptText(message) {
|
|
14
15
|
const rl = promises_1.default.createInterface({
|
|
@@ -23,16 +24,6 @@ async function promptConfirm(message) {
|
|
|
23
24
|
const answer = await promptText(`${message} (y/N): `);
|
|
24
25
|
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
25
26
|
}
|
|
26
|
-
function parseAgentArg(value) {
|
|
27
|
-
const atIndex = value.lastIndexOf('@');
|
|
28
|
-
if (atIndex === -1 || atIndex === 0) {
|
|
29
|
-
return { name: value, version: 'latest' };
|
|
30
|
-
}
|
|
31
|
-
return {
|
|
32
|
-
name: value.slice(0, atIndex),
|
|
33
|
-
version: value.slice(atIndex + 1) || 'latest',
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
27
|
function registerDeleteCommand(program) {
|
|
37
28
|
program
|
|
38
29
|
.command('delete <agent>')
|
|
@@ -41,29 +32,29 @@ function registerDeleteCommand(program) {
|
|
|
41
32
|
.option('--dry-run', 'Show what would be deleted without making changes')
|
|
42
33
|
.addHelpText('after', `
|
|
43
34
|
Examples:
|
|
44
|
-
orch delete my-agent # Delete latest version
|
|
45
|
-
orch delete my-agent@v1 # Delete specific version
|
|
46
|
-
orch delete my-agent --dry-run # Preview deletion
|
|
35
|
+
orch delete org/my-agent # Delete latest version
|
|
36
|
+
orch delete org/my-agent@v1 # Delete specific version
|
|
37
|
+
orch delete org/my-agent --dry-run # Preview deletion
|
|
47
38
|
`)
|
|
48
39
|
.action(async (agent, options) => {
|
|
49
|
-
const
|
|
40
|
+
const ref = (0, agent_ref_1.parseAgentRef)(agent);
|
|
50
41
|
const config = await (0, config_1.getResolvedConfig)();
|
|
51
42
|
if (!config.apiKey) {
|
|
52
43
|
throw new errors_1.CliError('Not logged in. Run `orch login` first.');
|
|
53
44
|
}
|
|
54
45
|
process.stdout.write('Finding agent...\n');
|
|
55
|
-
// Find the agent
|
|
46
|
+
// Find the agent by name, filtering by org if provided
|
|
56
47
|
const agents = await (0, api_1.listMyAgents)(config);
|
|
57
|
-
const matching = agents.filter(a => a.name ===
|
|
48
|
+
const matching = agents.filter(a => a.name === ref.agent && (!a.org_slug || a.org_slug === ref.org));
|
|
58
49
|
if (matching.length === 0) {
|
|
59
|
-
throw new errors_1.CliError(`Agent '${
|
|
50
|
+
throw new errors_1.CliError(`Agent '${ref.org}/${ref.agent}' not found`);
|
|
60
51
|
}
|
|
61
52
|
// Select version
|
|
62
53
|
let selectedAgent;
|
|
63
|
-
if (version !== 'latest') {
|
|
64
|
-
selectedAgent = matching.find(a => a.version === version);
|
|
54
|
+
if (ref.version !== 'latest') {
|
|
55
|
+
selectedAgent = matching.find(a => a.version === ref.version);
|
|
65
56
|
if (!selectedAgent) {
|
|
66
|
-
throw new errors_1.CliError(`Version '${version}' not found for agent '${
|
|
57
|
+
throw new errors_1.CliError(`Version '${ref.version}' not found for agent '${ref.org}/${ref.agent}'`);
|
|
67
58
|
}
|
|
68
59
|
}
|
|
69
60
|
else {
|
package/dist/commands/init.js
CHANGED
|
@@ -45,6 +45,80 @@ const SCHEMA_TEMPLATE = `{
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
`;
|
|
48
|
+
const CODE_TEMPLATE_PY = `"""
|
|
49
|
+
orchagent code agent entrypoint.
|
|
50
|
+
|
|
51
|
+
Reads JSON input from stdin, processes it, and writes JSON output to stdout.
|
|
52
|
+
This is the standard orchagent code agent protocol.
|
|
53
|
+
|
|
54
|
+
Usage:
|
|
55
|
+
echo '{"input": "hello"}' | python main.py
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
import json
|
|
59
|
+
import sys
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main():
|
|
63
|
+
# Read JSON input from stdin
|
|
64
|
+
raw = sys.stdin.read()
|
|
65
|
+
try:
|
|
66
|
+
data = json.loads(raw) if raw.strip() else {}
|
|
67
|
+
except json.JSONDecodeError:
|
|
68
|
+
print(json.dumps({"error": "Invalid JSON input"}))
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
user_input = data.get("input", "")
|
|
72
|
+
|
|
73
|
+
# --- Your logic here ---
|
|
74
|
+
result = f"Received: {user_input}"
|
|
75
|
+
# --- End your logic ---
|
|
76
|
+
|
|
77
|
+
# Write JSON output to stdout
|
|
78
|
+
print(json.dumps({"result": result}))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
main()
|
|
83
|
+
`;
|
|
84
|
+
function readmeTemplate(agentName, type) {
|
|
85
|
+
const callExample = type === 'code'
|
|
86
|
+
? `orchagent call ${agentName} input-file.txt`
|
|
87
|
+
: `orchagent call ${agentName} --data '{"input": "Hello world"}'`;
|
|
88
|
+
const runExample = type === 'code'
|
|
89
|
+
? `orchagent run ${agentName} --input '{"file_path": "src/app.py"}'`
|
|
90
|
+
: `orchagent run ${agentName} --input '{"input": "Hello world"}'`;
|
|
91
|
+
return `# ${agentName}
|
|
92
|
+
|
|
93
|
+
A brief description of what this agent does.
|
|
94
|
+
|
|
95
|
+
## Usage
|
|
96
|
+
|
|
97
|
+
### Server execution
|
|
98
|
+
|
|
99
|
+
\`\`\`sh
|
|
100
|
+
${callExample}
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
### Local execution
|
|
104
|
+
|
|
105
|
+
\`\`\`sh
|
|
106
|
+
${runExample}
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
## Input
|
|
110
|
+
|
|
111
|
+
| Field | Type | Description |
|
|
112
|
+
|-------|------|-------------|
|
|
113
|
+
| \`input\` | string | The input to process |
|
|
114
|
+
|
|
115
|
+
## Output
|
|
116
|
+
|
|
117
|
+
| Field | Type | Description |
|
|
118
|
+
|-------|------|-------------|
|
|
119
|
+
| \`result\` | string | The agent's response |
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
48
122
|
const SKILL_TEMPLATE = `---
|
|
49
123
|
name: my-skill
|
|
50
124
|
description: When to use this skill
|
|
@@ -63,27 +137,50 @@ function registerInitCommand(program) {
|
|
|
63
137
|
.option('--type <type>', 'Type: prompt, code, or skill (default: prompt)', 'prompt')
|
|
64
138
|
.action(async (name, options) => {
|
|
65
139
|
const cwd = process.cwd();
|
|
140
|
+
// When a name is provided, create a subdirectory for the project
|
|
141
|
+
const targetDir = name ? path_1.default.join(cwd, name) : cwd;
|
|
66
142
|
const agentName = name || path_1.default.basename(cwd);
|
|
143
|
+
// Create the subdirectory if a name was provided
|
|
144
|
+
if (name) {
|
|
145
|
+
await promises_1.default.mkdir(targetDir, { recursive: true });
|
|
146
|
+
}
|
|
67
147
|
// Handle skill type separately
|
|
68
148
|
if (options.type === 'skill') {
|
|
69
|
-
const skillPath = path_1.default.join(
|
|
149
|
+
const skillPath = path_1.default.join(targetDir, 'SKILL.md');
|
|
150
|
+
// Check if already initialized
|
|
151
|
+
try {
|
|
152
|
+
await promises_1.default.access(skillPath);
|
|
153
|
+
throw new errors_1.CliError(`Already initialized (SKILL.md exists in ${name ? name + '/' : 'current directory'})`);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
if (err.code !== 'ENOENT') {
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
70
160
|
const skillContent = SKILL_TEMPLATE.replace('my-skill', agentName);
|
|
71
161
|
await promises_1.default.writeFile(skillPath, skillContent);
|
|
72
|
-
process.stdout.write(`Initialized skill "${agentName}" in ${
|
|
162
|
+
process.stdout.write(`Initialized skill "${agentName}" in ${targetDir}\n`);
|
|
73
163
|
process.stdout.write(`\nFiles created:\n`);
|
|
74
|
-
process.stdout.write(` SKILL.md - Skill content with frontmatter\n`);
|
|
164
|
+
process.stdout.write(` ${name ? name + '/' : ''}SKILL.md - Skill content with frontmatter\n`);
|
|
75
165
|
process.stdout.write(`\nNext steps:\n`);
|
|
76
|
-
|
|
77
|
-
|
|
166
|
+
if (name) {
|
|
167
|
+
process.stdout.write(` 1. cd ${name}\n`);
|
|
168
|
+
process.stdout.write(` 2. Edit SKILL.md with your skill content\n`);
|
|
169
|
+
process.stdout.write(` 3. Run: orchagent publish\n`);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
process.stdout.write(` 1. Edit SKILL.md with your skill content\n`);
|
|
173
|
+
process.stdout.write(` 2. Run: orchagent publish\n`);
|
|
174
|
+
}
|
|
78
175
|
return;
|
|
79
176
|
}
|
|
80
|
-
const manifestPath = path_1.default.join(
|
|
81
|
-
const promptPath = path_1.default.join(
|
|
82
|
-
const schemaPath = path_1.default.join(
|
|
177
|
+
const manifestPath = path_1.default.join(targetDir, 'orchagent.json');
|
|
178
|
+
const promptPath = path_1.default.join(targetDir, 'prompt.md');
|
|
179
|
+
const schemaPath = path_1.default.join(targetDir, 'schema.json');
|
|
83
180
|
// Check if already initialized
|
|
84
181
|
try {
|
|
85
182
|
await promises_1.default.access(manifestPath);
|
|
86
|
-
throw new errors_1.CliError(`Already initialized (orchagent.json exists)`);
|
|
183
|
+
throw new errors_1.CliError(`Already initialized (orchagent.json exists in ${name ? name + '/' : 'current directory'})`);
|
|
87
184
|
}
|
|
88
185
|
catch (err) {
|
|
89
186
|
if (err.code !== 'ENOENT') {
|
|
@@ -95,29 +192,49 @@ function registerInitCommand(program) {
|
|
|
95
192
|
manifest.name = agentName;
|
|
96
193
|
manifest.type = ['code', 'skill'].includes(options.type) ? options.type : 'prompt';
|
|
97
194
|
await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
98
|
-
// Create prompt template (for prompt-based agents)
|
|
99
|
-
if (options.type
|
|
195
|
+
// Create prompt template (for prompt-based agents) or entrypoint (for code agents)
|
|
196
|
+
if (options.type === 'code') {
|
|
197
|
+
const entrypointPath = path_1.default.join(targetDir, 'main.py');
|
|
198
|
+
await promises_1.default.writeFile(entrypointPath, CODE_TEMPLATE_PY);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
100
201
|
await promises_1.default.writeFile(promptPath, PROMPT_TEMPLATE);
|
|
101
202
|
}
|
|
102
203
|
// Create schema template
|
|
103
204
|
await promises_1.default.writeFile(schemaPath, SCHEMA_TEMPLATE);
|
|
104
|
-
|
|
205
|
+
// Create README
|
|
206
|
+
const readmePath = path_1.default.join(targetDir, 'README.md');
|
|
207
|
+
await promises_1.default.writeFile(readmePath, readmeTemplate(agentName, options.type));
|
|
208
|
+
process.stdout.write(`Initialized agent "${agentName}" in ${targetDir}\n`);
|
|
105
209
|
process.stdout.write(`\nFiles created:\n`);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
210
|
+
const prefix = name ? name + '/' : '';
|
|
211
|
+
process.stdout.write(` ${prefix}orchagent.json - Agent configuration\n`);
|
|
212
|
+
if (options.type === 'code') {
|
|
213
|
+
process.stdout.write(` ${prefix}main.py - Agent entrypoint (stdin/stdout JSON)\n`);
|
|
109
214
|
}
|
|
110
|
-
|
|
215
|
+
else {
|
|
216
|
+
process.stdout.write(` ${prefix}prompt.md - Prompt template\n`);
|
|
217
|
+
}
|
|
218
|
+
process.stdout.write(` ${prefix}schema.json - Input/output schemas\n`);
|
|
219
|
+
process.stdout.write(` ${prefix}README.md - Agent documentation\n`);
|
|
111
220
|
process.stdout.write(`\nNext steps:\n`);
|
|
112
221
|
if (options.type !== 'code') {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
222
|
+
const stepNum = name ? 2 : 1;
|
|
223
|
+
if (name) {
|
|
224
|
+
process.stdout.write(` 1. cd ${name}\n`);
|
|
225
|
+
}
|
|
226
|
+
process.stdout.write(` ${stepNum}. Edit prompt.md with your prompt template\n`);
|
|
227
|
+
process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
|
|
228
|
+
process.stdout.write(` ${stepNum + 2}. Run: orchagent publish\n`);
|
|
116
229
|
}
|
|
117
230
|
else {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
231
|
+
const stepNum = name ? 2 : 1;
|
|
232
|
+
if (name) {
|
|
233
|
+
process.stdout.write(` 1. cd ${name}\n`);
|
|
234
|
+
}
|
|
235
|
+
process.stdout.write(` ${stepNum}. Edit main.py with your agent logic\n`);
|
|
236
|
+
process.stdout.write(` ${stepNum + 1}. Test: echo '{"input": "test"}' | python main.py\n`);
|
|
237
|
+
process.stdout.write(` ${stepNum + 2}. Run: orchagent publish\n`);
|
|
121
238
|
}
|
|
122
239
|
});
|
|
123
240
|
}
|