@orchagent/cli 0.3.29 → 0.3.33
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 +78 -2
- 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/search.js +47 -22
- package/dist/commands/skill.js +10 -7
- package/dist/commands/update.js +101 -66
- package/dist/index.js +4 -0
- package/dist/lib/api.js +68 -2
- package/dist/lib/config.js +11 -0
- package/dist/lib/output.js +19 -12
- package/dist/lib/skill-resolve.js +99 -0
- package/package.json +4 -3
|
@@ -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
|
|
@@ -354,8 +393,45 @@ Paid Agents:
|
|
|
354
393
|
}
|
|
355
394
|
headers['Content-Type'] = 'application/json';
|
|
356
395
|
}
|
|
396
|
+
else if ((filePaths.length > 0 || options.metadata) && agentMeta.type === 'prompt') {
|
|
397
|
+
// Prompt agent + files/metadata: read content and send as JSON
|
|
398
|
+
const fieldName = options.fileField || inferFileField(agentMeta.input_schema);
|
|
399
|
+
let bodyObj = {};
|
|
400
|
+
// Include metadata if provided
|
|
401
|
+
if (options.metadata) {
|
|
402
|
+
try {
|
|
403
|
+
bodyObj = JSON.parse(options.metadata);
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
throw new errors_1.CliError('--metadata must be valid JSON.');
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (filePaths.length === 1) {
|
|
410
|
+
// Single file: map content to the inferred/specified schema field
|
|
411
|
+
const fileContent = await promises_1.default.readFile(filePaths[0], 'utf-8');
|
|
412
|
+
bodyObj[fieldName] = fileContent;
|
|
413
|
+
sourceLabel = filePaths[0];
|
|
414
|
+
}
|
|
415
|
+
else if (filePaths.length > 1) {
|
|
416
|
+
// Multiple files: map first to the schema field, add all as files object
|
|
417
|
+
const allContents = {};
|
|
418
|
+
for (const fp of filePaths) {
|
|
419
|
+
allContents[path_1.default.basename(fp)] = await promises_1.default.readFile(fp, 'utf-8');
|
|
420
|
+
}
|
|
421
|
+
// Set the primary field to the first file's content
|
|
422
|
+
const firstContent = await promises_1.default.readFile(filePaths[0], 'utf-8');
|
|
423
|
+
bodyObj[fieldName] = firstContent;
|
|
424
|
+
bodyObj.files = allContents;
|
|
425
|
+
sourceLabel = `${filePaths.length} files`;
|
|
426
|
+
}
|
|
427
|
+
if (llmCredentials) {
|
|
428
|
+
bodyObj.llm_credentials = llmCredentials;
|
|
429
|
+
}
|
|
430
|
+
body = JSON.stringify(bodyObj);
|
|
431
|
+
headers['Content-Type'] = 'application/json';
|
|
432
|
+
}
|
|
357
433
|
else if (filePaths.length > 0 || options.metadata) {
|
|
358
|
-
//
|
|
434
|
+
// Code agent: handle multipart file uploads
|
|
359
435
|
// Inject llm_credentials into metadata if available
|
|
360
436
|
let metadata = options.metadata;
|
|
361
437
|
if (llmCredentials) {
|
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
|
}
|
package/dist/commands/install.js
CHANGED
|
@@ -12,6 +12,7 @@ const api_1 = require("../lib/api");
|
|
|
12
12
|
const errors_1 = require("../lib/errors");
|
|
13
13
|
const analytics_1 = require("../lib/analytics");
|
|
14
14
|
const adapters_1 = require("../adapters");
|
|
15
|
+
const skill_resolve_1 = require("../lib/skill-resolve");
|
|
15
16
|
const installed_1 = require("../lib/installed");
|
|
16
17
|
const agents_md_utils_1 = require("../lib/agents-md-utils");
|
|
17
18
|
const pricing_1 = require("../lib/pricing");
|
|
@@ -123,7 +124,7 @@ function registerInstallCommand(program) {
|
|
|
123
124
|
.command('install <agent>')
|
|
124
125
|
.description('Install agent as sub-agent (Claude Code, Cursor, etc.)')
|
|
125
126
|
.option('--format <formats>', 'Comma-separated format IDs (e.g., claude-code,cursor)')
|
|
126
|
-
.option('--scope <scope>', 'Install scope: user (home dir) or project (current dir)'
|
|
127
|
+
.option('--scope <scope>', 'Install scope: user (home dir) or project (current dir)')
|
|
127
128
|
.option('--dry-run', 'Show what would be installed without making changes')
|
|
128
129
|
.option('--json', 'Output result as JSON (for automation/tooling)')
|
|
129
130
|
.addHelpText('after', `
|
|
@@ -187,8 +188,8 @@ Note: Paid agents cannot be installed locally - they run on server only.
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
result.formats = targetFormats;
|
|
190
|
-
//
|
|
191
|
-
let scope = options.scope;
|
|
191
|
+
// Resolve scope: CLI flag > config default > fallback to 'user'
|
|
192
|
+
let scope = (options.scope ?? await (0, config_1.getDefaultScope)() ?? 'user');
|
|
192
193
|
if (scope !== 'user' && scope !== 'project') {
|
|
193
194
|
const errMsg = 'Scope must be "user" or "project"';
|
|
194
195
|
if (jsonMode) {
|
|
@@ -213,6 +214,18 @@ Note: Paid agents cannot be installed locally - they run on server only.
|
|
|
213
214
|
}
|
|
214
215
|
throw err;
|
|
215
216
|
}
|
|
217
|
+
// Resolve default skills if present
|
|
218
|
+
if (agent.default_skills && agent.default_skills.length > 0) {
|
|
219
|
+
log(`Resolving ${agent.default_skills.length} bundled skill(s)...\n`);
|
|
220
|
+
const skills = await (0, skill_resolve_1.resolveSkills)(resolved, agent.default_skills, (warning) => {
|
|
221
|
+
result.warnings.push(warning);
|
|
222
|
+
logErr(`Warning: ${warning}\n`);
|
|
223
|
+
});
|
|
224
|
+
if (skills.length > 0) {
|
|
225
|
+
agent.resolvedSkills = skills;
|
|
226
|
+
log(`Bundled ${skills.length} skill(s): ${skills.map(s => s.name).join(', ')}\n`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
216
229
|
// Install for each format
|
|
217
230
|
let filesWritten = 0;
|
|
218
231
|
for (const formatId of targetFormats) {
|
package/dist/commands/search.js
CHANGED
|
@@ -14,7 +14,8 @@ function registerSearchCommand(program) {
|
|
|
14
14
|
.argument('[query]', 'Search query (required unless using --popular or --recent)')
|
|
15
15
|
.option('--popular', 'Show top agents/skills by stars')
|
|
16
16
|
.option('--recent', 'Show most recently published')
|
|
17
|
-
.option('--
|
|
17
|
+
.option('--mine', 'Show only your own agents (including private)')
|
|
18
|
+
.option('--type <type>', 'Filter by type: agents, skills, code, prompt, skill, all (default: all)', 'all')
|
|
18
19
|
.option('--limit <n>', `Max results (default: ${DEFAULT_LIMIT})`, String(DEFAULT_LIMIT))
|
|
19
20
|
.option('--free', 'Show only free agents')
|
|
20
21
|
.option('--paid', 'Show only paid agents')
|
|
@@ -23,29 +24,40 @@ function registerSearchCommand(program) {
|
|
|
23
24
|
Pricing Filters:
|
|
24
25
|
--free Show only free agents
|
|
25
26
|
--paid Show only paid agents
|
|
27
|
+
|
|
28
|
+
Ownership Filters:
|
|
29
|
+
--mine Show your own agents (public and private). Requires login.
|
|
26
30
|
`)
|
|
27
31
|
.action(async (query, options) => {
|
|
28
32
|
const config = await (0, config_1.getResolvedConfig)();
|
|
29
33
|
const limit = parseInt(options.limit, 10) || DEFAULT_LIMIT;
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
+
// Map type filter for API (null means no filter)
|
|
35
|
+
const typeFilter = options.type === 'all' ? undefined : options.type;
|
|
36
|
+
const sort = options.popular ? 'stars' : options.recent ? 'recent' : undefined;
|
|
34
37
|
let agents;
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
if (options.mine) {
|
|
39
|
+
// --mine: search within user's own agents (public + private)
|
|
40
|
+
if (!config.apiKey) {
|
|
41
|
+
process.stderr.write('Error: --mine requires authentication. Run `orchagent login` first.\n');
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
agents = await (0, api_1.searchMyAgents)(config, query, { sort, type: typeFilter });
|
|
46
|
+
await (0, analytics_1.track)('cli_search', { query, type: options.type, mine: true });
|
|
38
47
|
}
|
|
39
48
|
else {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
// Default to popular when no args
|
|
50
|
+
if (!query && !options.popular && !options.recent) {
|
|
51
|
+
options.popular = true;
|
|
52
|
+
}
|
|
53
|
+
if (query) {
|
|
54
|
+
agents = await (0, api_1.searchAgents)(config, query, { sort, type: typeFilter });
|
|
55
|
+
await (0, analytics_1.track)('cli_search', { query, type: options.type });
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
agents = await (0, api_1.listPublicAgents)(config, { sort, type: typeFilter });
|
|
59
|
+
await (0, analytics_1.track)('cli_search', { mode: options.popular ? 'popular' : 'recent', type: options.type });
|
|
60
|
+
}
|
|
49
61
|
}
|
|
50
62
|
// Filter by pricing if requested
|
|
51
63
|
if (options.free) {
|
|
@@ -55,7 +67,7 @@ Pricing Filters:
|
|
|
55
67
|
agents = agents.filter(a => (0, pricing_1.isPaidAgent)(a));
|
|
56
68
|
}
|
|
57
69
|
// Sort results
|
|
58
|
-
if (options.popular || (!query && !options.recent)) {
|
|
70
|
+
if (options.popular || (!query && !options.recent && !options.mine)) {
|
|
59
71
|
agents.sort((a, b) => (b.stars_count ?? 0) - (a.stars_count ?? 0));
|
|
60
72
|
}
|
|
61
73
|
else if (options.recent) {
|
|
@@ -68,14 +80,27 @@ Pricing Filters:
|
|
|
68
80
|
return;
|
|
69
81
|
}
|
|
70
82
|
if (agents.length === 0) {
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
if (options.mine) {
|
|
84
|
+
process.stdout.write(query
|
|
85
|
+
? `No agents found matching "${query}" in your account.\n`
|
|
86
|
+
: 'You have no published agents yet.\n');
|
|
87
|
+
process.stdout.write('\nTip: Run "orchagent init" to create your first agent.\n');
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
process.stdout.write(query ? 'No results found matching your search.\n' : 'No public agents found.\n');
|
|
91
|
+
process.stdout.write('\nBrowse all agents at: https://orchagent.io/explore\n');
|
|
92
|
+
}
|
|
73
93
|
return;
|
|
74
94
|
}
|
|
75
|
-
(0, output_1.printAgentsTable)(agents);
|
|
95
|
+
(0, output_1.printAgentsTable)(agents, { showVisibility: options.mine });
|
|
76
96
|
if (agents.length === limit) {
|
|
77
97
|
process.stdout.write(`\nShowing top ${limit} results. Use --limit <n> for more.\n`);
|
|
78
98
|
}
|
|
79
|
-
|
|
99
|
+
if (options.mine) {
|
|
100
|
+
process.stdout.write('\nTip: Run "orchagent info <agent>" to see details, or "orchagent delete <agent>" to remove.\n');
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
process.stdout.write('\nTip: Run "orchagent info <agent>" to see input schema and details.\n');
|
|
104
|
+
}
|
|
80
105
|
});
|
|
81
106
|
}
|
package/dist/commands/skill.js
CHANGED
|
@@ -269,7 +269,7 @@ Instructions and guidance for AI agents...
|
|
|
269
269
|
.command('install <skill>')
|
|
270
270
|
.description('Install skill to local AI tool directories (Claude Code, Cursor, etc.)')
|
|
271
271
|
.option('--global', 'Install to home directory (default: current directory)')
|
|
272
|
-
.option('--scope <scope>', 'Install scope: user or project'
|
|
272
|
+
.option('--scope <scope>', 'Install scope: user or project')
|
|
273
273
|
.option('--dry-run', 'Show what would be installed without making changes')
|
|
274
274
|
.option('--format <formats>', 'Comma-separated format IDs (e.g., claude-code,cursor)')
|
|
275
275
|
.option('--all-formats', 'Install to all supported AI tool directories')
|
|
@@ -401,8 +401,8 @@ Instructions and guidance for AI agents...
|
|
|
401
401
|
'The skill exists but has an empty prompt. This may be a publishing issue.\n' +
|
|
402
402
|
'Try re-publishing the skill or contact the skill author.');
|
|
403
403
|
}
|
|
404
|
-
// Determine scope (--global is legacy alias for --scope user)
|
|
405
|
-
const scope = options.global ? 'user' : (options.scope || 'project');
|
|
404
|
+
// Determine scope (--global is legacy alias for --scope user; then config default; then 'project')
|
|
405
|
+
const scope = options.global ? 'user' : (options.scope || await (0, config_1.getDefaultScope)() || 'project');
|
|
406
406
|
result.scope = scope;
|
|
407
407
|
// Build skill content with header
|
|
408
408
|
const skillContent = `# ${skillData.name}
|
|
@@ -495,7 +495,10 @@ ${skillData.prompt}
|
|
|
495
495
|
for (const tool of installed) {
|
|
496
496
|
log(` - ${tool}\n`);
|
|
497
497
|
}
|
|
498
|
-
log(`\
|
|
498
|
+
log(`\nLocations:\n`);
|
|
499
|
+
for (const file of result.files) {
|
|
500
|
+
log(` - ${file.tool}: ${file.path}\n`);
|
|
501
|
+
}
|
|
499
502
|
}
|
|
500
503
|
});
|
|
501
504
|
// orch skill uninstall <skill>
|
|
@@ -503,7 +506,7 @@ ${skillData.prompt}
|
|
|
503
506
|
.command('uninstall <skill>')
|
|
504
507
|
.description('Uninstall skill from local AI tool directories')
|
|
505
508
|
.option('--global', 'Uninstall from home directory (default: current directory)')
|
|
506
|
-
.option('--scope <scope>', 'Uninstall scope: user or project'
|
|
509
|
+
.option('--scope <scope>', 'Uninstall scope: user or project')
|
|
507
510
|
.option('--json', 'Output result as JSON')
|
|
508
511
|
.action(async (skillRef, options) => {
|
|
509
512
|
const jsonMode = options.json === true;
|
|
@@ -530,8 +533,8 @@ ${skillData.prompt}
|
|
|
530
533
|
throw new errors_1.CliError(errMsg);
|
|
531
534
|
}
|
|
532
535
|
result.skill = `${org}/${parsed.skill}`;
|
|
533
|
-
// Determine scope
|
|
534
|
-
const scope = options.global ? 'user' : (options.scope || 'project');
|
|
536
|
+
// Determine scope (--global is legacy alias for --scope user; then config default; then 'project')
|
|
537
|
+
const scope = options.global ? 'user' : (options.scope || await (0, config_1.getDefaultScope)() || 'project');
|
|
535
538
|
result.scope = scope;
|
|
536
539
|
// Remove from all AI tool directories
|
|
537
540
|
for (const formatId of config_1.VALID_FORMAT_IDS) {
|
package/dist/commands/update.js
CHANGED
|
@@ -53,100 +53,135 @@ function registerUpdateCommand(program) {
|
|
|
53
53
|
process.stdout.write(`Agent "${agentRef}" is not installed.\n`);
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
// Group installed entries by agent name to avoid duplicates
|
|
57
|
+
// (same agent installed to multiple formats shows once)
|
|
58
|
+
const grouped = new Map();
|
|
59
|
+
for (const item of toCheck) {
|
|
60
|
+
const existing = grouped.get(item.agent);
|
|
61
|
+
if (existing) {
|
|
62
|
+
existing.push(item);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
grouped.set(item.agent, [item]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
process.stdout.write(`Checking ${grouped.size} installed agent(s) for updates...\n\n`);
|
|
57
69
|
let updatesAvailable = 0;
|
|
58
70
|
let updatesApplied = 0;
|
|
59
71
|
let skippedModified = 0;
|
|
60
72
|
let skippedMissing = 0;
|
|
61
|
-
for (const
|
|
62
|
-
// Check
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
for (const [agentName, entries] of grouped) {
|
|
74
|
+
// Check file status for all entries in this group
|
|
75
|
+
const fileStatuses = [];
|
|
76
|
+
for (const item of entries) {
|
|
77
|
+
fileStatuses.push({ item, status: await (0, installed_1.checkModified)(item) });
|
|
78
|
+
}
|
|
79
|
+
// Fetch latest version once per agent
|
|
80
|
+
const latest = await fetchLatestAgent(resolved, agentName);
|
|
66
81
|
if (!latest) {
|
|
67
|
-
process.stdout.write(` ${chalk_1.default.yellow('?')} ${
|
|
82
|
+
process.stdout.write(` ${chalk_1.default.yellow('?')} ${agentName} - could not fetch latest\n`);
|
|
68
83
|
continue;
|
|
69
84
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
// Use the version from the first entry (all entries for the same
|
|
86
|
+
// agent should share the same version after install/update)
|
|
87
|
+
const installedVersion = entries[0].version;
|
|
88
|
+
const hasUpdate = latest.latestVersion !== installedVersion;
|
|
89
|
+
const anyMissing = fileStatuses.some(f => f.status.missing);
|
|
90
|
+
const anyModified = fileStatuses.some(f => f.status.modified);
|
|
91
|
+
if (!hasUpdate && !anyModified && !anyMissing) {
|
|
92
|
+
const formatSuffix = entries.length > 1
|
|
93
|
+
? ` ${chalk_1.default.dim(`(${entries.map(e => e.format).join(', ')})`)}`
|
|
94
|
+
: '';
|
|
95
|
+
process.stdout.write(` ${chalk_1.default.green('✓')} ${agentName}@${installedVersion} - up to date${formatSuffix}\n`);
|
|
73
96
|
continue;
|
|
74
97
|
}
|
|
75
|
-
// Handle missing
|
|
76
|
-
if (
|
|
77
|
-
|
|
98
|
+
// Handle missing files without --force
|
|
99
|
+
if (anyMissing && !hasUpdate && !options.force) {
|
|
100
|
+
const missingFormats = fileStatuses.filter(f => f.status.missing).map(f => f.item.format);
|
|
101
|
+
process.stdout.write(` ${chalk_1.default.yellow('!')} ${agentName} - file missing [${missingFormats.join(', ')}] (use --force to reinstall)\n`);
|
|
78
102
|
skippedMissing++;
|
|
79
103
|
continue;
|
|
80
104
|
}
|
|
81
|
-
// Handle modified
|
|
82
|
-
if (
|
|
83
|
-
|
|
105
|
+
// Handle modified files without --force
|
|
106
|
+
if (anyModified && !hasUpdate && !options.force) {
|
|
107
|
+
const modifiedFormats = fileStatuses.filter(f => f.status.modified).map(f => f.item.format);
|
|
108
|
+
process.stdout.write(` ${chalk_1.default.yellow('!')} ${agentName} - local modifications [${modifiedFormats.join(', ')}] (use --force to overwrite)\n`);
|
|
84
109
|
skippedModified++;
|
|
85
110
|
continue;
|
|
86
111
|
}
|
|
87
|
-
if (hasUpdate ||
|
|
112
|
+
if (hasUpdate || anyMissing) {
|
|
88
113
|
if (hasUpdate) {
|
|
89
114
|
updatesAvailable++;
|
|
90
115
|
}
|
|
91
|
-
process.stdout.write(` ${chalk_1.default.blue('↑')} ${
|
|
92
|
-
if (
|
|
116
|
+
process.stdout.write(` ${chalk_1.default.blue('↑')} ${agentName}@${installedVersion} → ${latest.latestVersion}`);
|
|
117
|
+
if (anyModified) {
|
|
93
118
|
process.stdout.write(` ${chalk_1.default.yellow('(modified)')}`);
|
|
94
119
|
}
|
|
95
|
-
if (
|
|
120
|
+
if (anyMissing) {
|
|
96
121
|
process.stdout.write(` ${chalk_1.default.yellow('(reinstalling)')}`);
|
|
97
122
|
}
|
|
98
123
|
process.stdout.write('\n');
|
|
99
124
|
if (options.check) {
|
|
100
125
|
continue;
|
|
101
126
|
}
|
|
102
|
-
// Apply update
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
// Apply update to each format entry
|
|
128
|
+
for (const { item, status: fileStatus } of fileStatuses) {
|
|
129
|
+
// Skip unmodified and non-missing entries if no version update
|
|
130
|
+
if (!hasUpdate && !fileStatus.missing)
|
|
131
|
+
continue;
|
|
132
|
+
// Skip modified entries without --force
|
|
133
|
+
if (fileStatus.modified && !options.force) {
|
|
134
|
+
process.stderr.write(` Skipped ${item.format}: local modifications (use --force)\n`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const adapter = adapters_1.adapterRegistry.get(item.format);
|
|
138
|
+
if (!adapter) {
|
|
139
|
+
process.stderr.write(` Skipped ${item.format}: unknown format\n`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const checkResult = adapter.canConvert(latest.agent);
|
|
143
|
+
if (!checkResult.canConvert) {
|
|
144
|
+
process.stderr.write(` Skipped ${item.format}: ${checkResult.errors.join(', ')}\n`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const files = adapter.convert(latest.agent);
|
|
148
|
+
if (files.length > 1) {
|
|
149
|
+
process.stderr.write(` Skipped ${item.format}: multi-file adapters not supported for update. Reinstall instead.\n`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
for (const file of files) {
|
|
153
|
+
// Use the original path from tracking
|
|
154
|
+
const fullPath = item.path;
|
|
155
|
+
try {
|
|
156
|
+
const dir = path_1.default.dirname(fullPath);
|
|
157
|
+
await promises_1.default.mkdir(dir, { recursive: true });
|
|
158
|
+
// Special handling for AGENTS.md - append/replace instead of overwrite
|
|
159
|
+
if (file.filename === 'AGENTS.md') {
|
|
160
|
+
let existingContent = '';
|
|
161
|
+
try {
|
|
162
|
+
existingContent = await promises_1.default.readFile(fullPath, 'utf-8');
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// File doesn't exist, will create new
|
|
166
|
+
}
|
|
167
|
+
file.content = (0, agents_md_utils_1.mergeAgentsMdContent)(existingContent, file.content, item.agent);
|
|
132
168
|
}
|
|
133
|
-
|
|
169
|
+
await promises_1.default.writeFile(fullPath, file.content);
|
|
170
|
+
// Update tracking
|
|
171
|
+
const updatedItem = {
|
|
172
|
+
...item,
|
|
173
|
+
version: latest.latestVersion,
|
|
174
|
+
installedAt: new Date().toISOString(),
|
|
175
|
+
adapterVersion: adapter.version,
|
|
176
|
+
contentHash: (0, installed_1.computeHash)(file.content),
|
|
177
|
+
};
|
|
178
|
+
await (0, installed_1.trackInstall)(updatedItem);
|
|
179
|
+
process.stdout.write(` Updated: ${fullPath}\n`);
|
|
180
|
+
updatesApplied++;
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
process.stderr.write(` Error (${item.format}): ${err.message}\n`);
|
|
134
184
|
}
|
|
135
|
-
await promises_1.default.writeFile(fullPath, file.content);
|
|
136
|
-
// Update tracking
|
|
137
|
-
const updatedItem = {
|
|
138
|
-
...item,
|
|
139
|
-
version: latest.latestVersion,
|
|
140
|
-
installedAt: new Date().toISOString(),
|
|
141
|
-
adapterVersion: adapter.version,
|
|
142
|
-
contentHash: (0, installed_1.computeHash)(file.content),
|
|
143
|
-
};
|
|
144
|
-
await (0, installed_1.trackInstall)(updatedItem);
|
|
145
|
-
process.stdout.write(` Updated: ${fullPath}\n`);
|
|
146
|
-
updatesApplied++;
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
process.stderr.write(` Error: ${err.message}\n`);
|
|
150
185
|
}
|
|
151
186
|
}
|
|
152
187
|
}
|
package/dist/index.js
CHANGED
|
@@ -61,6 +61,10 @@ Quick Reference:
|
|
|
61
61
|
call Execute an agent on orchagent servers (requires login)
|
|
62
62
|
info Show agent details and input/output schemas
|
|
63
63
|
|
|
64
|
+
Installation:
|
|
65
|
+
npm install -g @orchagent/cli Install globally (then use: orch)
|
|
66
|
+
npx orchagent <command> Run without installing
|
|
67
|
+
|
|
64
68
|
Documentation: https://docs.orchagent.io
|
|
65
69
|
orchagent docs Open docs in browser
|
|
66
70
|
orchagent docs cli CLI command reference
|
package/dist/lib/api.js
CHANGED
|
@@ -48,6 +48,7 @@ exports.starAgent = starAgent;
|
|
|
48
48
|
exports.unstarAgent = unstarAgent;
|
|
49
49
|
exports.forkAgent = forkAgent;
|
|
50
50
|
exports.searchAgents = searchAgents;
|
|
51
|
+
exports.searchMyAgents = searchMyAgents;
|
|
51
52
|
exports.fetchLlmKeys = fetchLlmKeys;
|
|
52
53
|
exports.downloadCodeBundle = downloadCodeBundle;
|
|
53
54
|
exports.uploadCodeBundle = uploadCodeBundle;
|
|
@@ -233,8 +234,14 @@ async function updateOrg(config, payload) {
|
|
|
233
234
|
headers: { 'Content-Type': 'application/json' },
|
|
234
235
|
});
|
|
235
236
|
}
|
|
236
|
-
async function listPublicAgents(config) {
|
|
237
|
-
|
|
237
|
+
async function listPublicAgents(config, options) {
|
|
238
|
+
const params = new URLSearchParams();
|
|
239
|
+
if (options?.sort)
|
|
240
|
+
params.append('sort', options.sort);
|
|
241
|
+
if (options?.type)
|
|
242
|
+
params.append('type', options.type);
|
|
243
|
+
const queryStr = params.toString();
|
|
244
|
+
return publicRequest(config, `/public/agents${queryStr ? `?${queryStr}` : ''}`);
|
|
238
245
|
}
|
|
239
246
|
async function getPublicAgent(config, org, agent, version) {
|
|
240
247
|
return publicRequest(config, `/public/agents/${org}/${agent}/${version}`);
|
|
@@ -266,9 +273,68 @@ async function searchAgents(config, query, options) {
|
|
|
266
273
|
params.append('sort', options.sort);
|
|
267
274
|
if (options?.tags?.length)
|
|
268
275
|
params.append('tags', options.tags.join(','));
|
|
276
|
+
if (options?.type)
|
|
277
|
+
params.append('type', options.type);
|
|
269
278
|
const queryStr = params.toString();
|
|
270
279
|
return publicRequest(config, `/public/agents${queryStr ? `?${queryStr}` : ''}`);
|
|
271
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Search within the authenticated user's own agents (public and private).
|
|
283
|
+
* Uses the authenticated /agents endpoint with client-side filtering.
|
|
284
|
+
*/
|
|
285
|
+
async function searchMyAgents(config, query, options) {
|
|
286
|
+
let agents = await listMyAgents(config);
|
|
287
|
+
// Deduplicate: keep only latest version per agent name
|
|
288
|
+
const latestByName = new Map();
|
|
289
|
+
for (const agent of agents) {
|
|
290
|
+
const existing = latestByName.get(agent.name);
|
|
291
|
+
if (!existing || new Date(agent.created_at) > new Date(existing.created_at)) {
|
|
292
|
+
latestByName.set(agent.name, agent);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
agents = Array.from(latestByName.values());
|
|
296
|
+
// Apply type filter
|
|
297
|
+
if (options?.type) {
|
|
298
|
+
const typeFilter = options.type;
|
|
299
|
+
if (typeFilter === 'agents') {
|
|
300
|
+
agents = agents.filter(a => a.type === 'prompt' || a.type === 'code');
|
|
301
|
+
}
|
|
302
|
+
else if (typeFilter === 'skills' || typeFilter === 'skill') {
|
|
303
|
+
agents = agents.filter(a => a.type === 'skill');
|
|
304
|
+
}
|
|
305
|
+
else if (typeFilter === 'code' || typeFilter === 'prompt') {
|
|
306
|
+
agents = agents.filter(a => a.type === typeFilter);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Apply search filter (match against name and description)
|
|
310
|
+
if (query) {
|
|
311
|
+
const words = query.toLowerCase().replace(/-/g, ' ').split(/\s+/);
|
|
312
|
+
agents = agents.filter(a => {
|
|
313
|
+
const name = (a.name || '').toLowerCase().replace(/-/g, ' ');
|
|
314
|
+
const desc = (a.description || '').toLowerCase();
|
|
315
|
+
return words.every(w => name.includes(w) || desc.includes(w));
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
// Map Agent to PublicAgent-compatible objects
|
|
319
|
+
const org = await getOrg(config);
|
|
320
|
+
return agents.map(a => ({
|
|
321
|
+
id: a.id,
|
|
322
|
+
org_name: org.name || org.slug || 'unknown',
|
|
323
|
+
org_slug: a.org_slug || org.slug || 'unknown',
|
|
324
|
+
name: a.name,
|
|
325
|
+
version: a.version,
|
|
326
|
+
type: a.type,
|
|
327
|
+
description: a.description,
|
|
328
|
+
stars_count: a.stars_count ?? 0,
|
|
329
|
+
tags: a.tags ?? [],
|
|
330
|
+
default_endpoint: a.default_endpoint || 'analyze',
|
|
331
|
+
created_at: a.created_at,
|
|
332
|
+
supported_providers: a.supported_providers ?? ['any'],
|
|
333
|
+
is_public: a.is_public,
|
|
334
|
+
pricing_mode: a.pricing_mode,
|
|
335
|
+
price_per_call_cents: a.price_per_call_cents,
|
|
336
|
+
}));
|
|
337
|
+
}
|
|
272
338
|
async function fetchLlmKeys(config) {
|
|
273
339
|
const result = await request(config, 'GET', '/llm-keys/export');
|
|
274
340
|
return result.keys;
|
package/dist/lib/config.js
CHANGED
|
@@ -43,6 +43,8 @@ exports.getResolvedConfig = getResolvedConfig;
|
|
|
43
43
|
exports.getConfigPath = getConfigPath;
|
|
44
44
|
exports.getDefaultFormats = getDefaultFormats;
|
|
45
45
|
exports.setDefaultFormats = setDefaultFormats;
|
|
46
|
+
exports.getDefaultScope = getDefaultScope;
|
|
47
|
+
exports.setDefaultScope = setDefaultScope;
|
|
46
48
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
47
49
|
const path_1 = __importDefault(require("path"));
|
|
48
50
|
const os_1 = __importDefault(require("os"));
|
|
@@ -129,3 +131,12 @@ async function setDefaultFormats(formats) {
|
|
|
129
131
|
config.default_formats = formats;
|
|
130
132
|
await saveConfig(config);
|
|
131
133
|
}
|
|
134
|
+
async function getDefaultScope() {
|
|
135
|
+
const config = await loadConfig();
|
|
136
|
+
return config.default_scope;
|
|
137
|
+
}
|
|
138
|
+
async function setDefaultScope(scope) {
|
|
139
|
+
const config = await loadConfig();
|
|
140
|
+
config.default_scope = scope;
|
|
141
|
+
await saveConfig(config);
|
|
142
|
+
}
|
package/dist/lib/output.js
CHANGED
|
@@ -11,17 +11,17 @@ const pricing_1 = require("./pricing");
|
|
|
11
11
|
function printJson(value) {
|
|
12
12
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
13
13
|
}
|
|
14
|
-
function printAgentsTable(agents) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
14
|
+
function printAgentsTable(agents, options) {
|
|
15
|
+
const head = [
|
|
16
|
+
chalk_1.default.bold('Agent'),
|
|
17
|
+
chalk_1.default.bold('Type'),
|
|
18
|
+
...(options?.showVisibility ? [chalk_1.default.bold('Visibility')] : []),
|
|
19
|
+
chalk_1.default.bold('Providers'),
|
|
20
|
+
chalk_1.default.bold('Stars'),
|
|
21
|
+
chalk_1.default.bold('Price'),
|
|
22
|
+
chalk_1.default.bold('Description'),
|
|
23
|
+
];
|
|
24
|
+
const table = new cli_table3_1.default({ head });
|
|
25
25
|
agents.forEach((agent) => {
|
|
26
26
|
const fullName = `${agent.org_slug}/${agent.name}`;
|
|
27
27
|
const type = agent.type || 'code';
|
|
@@ -34,7 +34,14 @@ function printAgentsTable(agents) {
|
|
|
34
34
|
? agent.description.slice(0, 27) + '...'
|
|
35
35
|
: agent.description
|
|
36
36
|
: '-';
|
|
37
|
-
|
|
37
|
+
const visibility = agent.is_public === false
|
|
38
|
+
? chalk_1.default.yellow('private')
|
|
39
|
+
: chalk_1.default.green('public');
|
|
40
|
+
const row = [fullName, type];
|
|
41
|
+
if (options?.showVisibility)
|
|
42
|
+
row.push(visibility);
|
|
43
|
+
row.push(providers, stars.toString(), priceColored, desc);
|
|
44
|
+
table.push(row);
|
|
38
45
|
});
|
|
39
46
|
process.stdout.write(`${table.toString()}\n`);
|
|
40
47
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveSkills = resolveSkills;
|
|
4
|
+
const api_1 = require("./api");
|
|
5
|
+
function parseSkillRef(ref) {
|
|
6
|
+
const [namePart, versionPart] = ref.split('@');
|
|
7
|
+
const version = versionPart?.trim() || 'latest';
|
|
8
|
+
const segments = namePart.split('/');
|
|
9
|
+
if (segments.length !== 2 || !segments[0] || !segments[1]) {
|
|
10
|
+
throw new Error(`Invalid skill reference: ${ref}. Expected format: org/name[@version]`);
|
|
11
|
+
}
|
|
12
|
+
return { org: segments[0], name: segments[1], version };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Download a single skill's content.
|
|
16
|
+
* Tries public endpoint first, falls back to authenticated for private skills.
|
|
17
|
+
* Returns null if the skill can't be found.
|
|
18
|
+
*/
|
|
19
|
+
async function downloadSkill(config, org, name, version) {
|
|
20
|
+
// Try public download endpoint first
|
|
21
|
+
try {
|
|
22
|
+
return await (0, api_1.publicRequest)(config, `/public/agents/${org}/${name}/${version}/download`);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (!(err instanceof api_1.ApiError) || err.status !== 404) {
|
|
26
|
+
// Non-404 errors (network, 500, etc.) - rethrow
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Public not found - try authenticated endpoint for private skills
|
|
31
|
+
if (!config.apiKey) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const userOrg = await (0, api_1.getOrg)(config);
|
|
36
|
+
if (userOrg.slug !== org) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const agents = await (0, api_1.listMyAgents)(config);
|
|
40
|
+
const matching = agents.filter(a => a.name === name && a.type === 'skill');
|
|
41
|
+
if (matching.length === 0) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
let target;
|
|
45
|
+
if (version === 'latest') {
|
|
46
|
+
target = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const found = matching.find(a => a.version === version);
|
|
50
|
+
if (!found)
|
|
51
|
+
return null;
|
|
52
|
+
target = found;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
name: target.name,
|
|
56
|
+
version: target.version,
|
|
57
|
+
description: target.description,
|
|
58
|
+
prompt: target.prompt,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve an array of skill references to their full content.
|
|
67
|
+
* Fetches each skill from the API and returns resolved skills with prompts.
|
|
68
|
+
* Skills that can't be fetched are skipped with a warning.
|
|
69
|
+
*/
|
|
70
|
+
async function resolveSkills(config, skillRefs, onWarning) {
|
|
71
|
+
const resolved = [];
|
|
72
|
+
for (const ref of skillRefs) {
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = parseSkillRef(ref);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
onWarning?.(`Skipping invalid skill reference: ${ref}`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const skill = await downloadSkill(config, parsed.org, parsed.name, parsed.version);
|
|
83
|
+
if (!skill || !skill.prompt) {
|
|
84
|
+
onWarning?.(`Could not resolve skill '${ref}' (not found or empty)`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
resolved.push({
|
|
88
|
+
ref,
|
|
89
|
+
name: skill.name,
|
|
90
|
+
description: skill.description,
|
|
91
|
+
prompt: skill.prompt,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
onWarning?.(`Could not fetch skill '${ref}': ${err instanceof Error ? err.message : String(err)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return resolved;
|
|
99
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchagent/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.33",
|
|
4
4
|
"description": "Command-line interface for the orchagent AI agent marketplace",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "orchagent <hello@orchagent.io>",
|
|
7
7
|
"homepage": "https://orchagent.io",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/orchagent/orchagent.git",
|
|
10
|
+
"url": "https://github.com/orchagent/orchagent-cli.git",
|
|
11
11
|
"directory": "cli"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"test:coverage": "vitest run --coverage",
|
|
41
41
|
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
42
42
|
"test:all": "vitest run && vitest run --config vitest.e2e.config.ts",
|
|
43
|
-
"prepublishOnly": "npm run build"
|
|
43
|
+
"prepublishOnly": "npm run build",
|
|
44
|
+
"postpublish": "cd ../cli-wrapper && npm version $npm_package_version --no-git-tag-version --allow-same-version && npm publish && echo '\n✓ Wrapper published at orchagent@'$npm_package_version"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
47
|
"@sentry/node": "^9.3.0",
|