@orchagent/cli 0.2.26 → 0.3.1

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.
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentsMdAdapter = void 0;
4
+ exports.agentsMdAdapter = {
5
+ id: 'agents-md',
6
+ name: 'AGENTS.md (Universal)',
7
+ version: '1.0.0',
8
+ formatVersion: '1.0',
9
+ supportsAgentTypes: ['prompt', 'skill'],
10
+ installPaths: [
11
+ {
12
+ scope: 'project',
13
+ path: './',
14
+ description: 'Project root (AGENTS.md)',
15
+ },
16
+ ],
17
+ canConvert(agent) {
18
+ const warnings = [];
19
+ const errors = [];
20
+ if (agent.type === 'code') {
21
+ errors.push('Code agents cannot be converted to AGENTS.md');
22
+ return { canConvert: false, warnings, errors };
23
+ }
24
+ if (!agent.prompt) {
25
+ errors.push('Agent has no prompt content');
26
+ return { canConvert: false, warnings, errors };
27
+ }
28
+ warnings.push('AGENTS.md content should be appended to existing file, not replaced');
29
+ return { canConvert: true, warnings, errors };
30
+ },
31
+ convert(agent) {
32
+ const agentRef = `${agent.org_slug}/${agent.name}`;
33
+ const description = agent.description || '';
34
+ // Use orchagent markers for managed section
35
+ const content = `<!-- orchagent:${agentRef} -->
36
+ ## ${agent.name}
37
+
38
+ ${description}
39
+
40
+ ${agent.prompt || ''}
41
+ <!-- /orchagent:${agentRef} -->
42
+ `;
43
+ return [
44
+ {
45
+ filename: 'AGENTS.md',
46
+ content,
47
+ installPath: './',
48
+ },
49
+ ];
50
+ },
51
+ };
@@ -0,0 +1,105 @@
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.claudeCodeAdapter = void 0;
7
+ const yaml_1 = __importDefault(require("yaml"));
8
+ // Map Anthropic model names to Claude Code aliases
9
+ function mapModelToAlias(model) {
10
+ if (!model)
11
+ return 'inherit';
12
+ const lower = model.toLowerCase();
13
+ if (lower.includes('opus'))
14
+ return 'opus';
15
+ if (lower.includes('haiku'))
16
+ return 'haiku';
17
+ // Default to sonnet for any Claude model
18
+ if (lower.includes('claude') || lower.includes('sonnet'))
19
+ return 'sonnet';
20
+ return 'inherit';
21
+ }
22
+ // Convert agent name to valid Claude Code name (lowercase + hyphens)
23
+ function normalizeAgentName(name) {
24
+ const normalized = name
25
+ .toLowerCase()
26
+ .replace(/[^a-z0-9-]/g, '-')
27
+ .replace(/-+/g, '-')
28
+ .replace(/^-|-$/g, '');
29
+ return normalized || 'agent'; // Fallback if empty
30
+ }
31
+ exports.claudeCodeAdapter = {
32
+ id: 'claude-code',
33
+ name: 'Claude Code Sub-Agent',
34
+ version: '1.0.0',
35
+ formatVersion: '2026-01',
36
+ supportsAgentTypes: ['prompt', 'skill'],
37
+ installPaths: [
38
+ {
39
+ scope: 'user',
40
+ path: '~/.claude/agents/',
41
+ description: 'User-level (available in all projects)',
42
+ },
43
+ {
44
+ scope: 'project',
45
+ path: '.claude/agents/',
46
+ description: 'Project-level (current directory)',
47
+ },
48
+ ],
49
+ canConvert(agent) {
50
+ const warnings = [];
51
+ const errors = [];
52
+ // Code agents cannot be converted
53
+ if (agent.type === 'code') {
54
+ errors.push('Code agents cannot be converted to Claude Code sub-agents (they require execution)');
55
+ return { canConvert: false, warnings, errors };
56
+ }
57
+ // Check for prompt
58
+ if (!agent.prompt) {
59
+ errors.push('Agent has no prompt content');
60
+ return { canConvert: false, warnings, errors };
61
+ }
62
+ // Warnings for fields that can't be fully mapped
63
+ if (agent.input_schema) {
64
+ warnings.push('input_schema will be described in the prompt body, not enforced');
65
+ }
66
+ if (agent.output_schema) {
67
+ warnings.push('output_schema will be described in the prompt body, not enforced');
68
+ }
69
+ return { canConvert: true, warnings, errors };
70
+ },
71
+ convert(agent) {
72
+ const normalizedName = normalizeAgentName(agent.name);
73
+ // Build frontmatter
74
+ const frontmatter = {
75
+ name: normalizedName,
76
+ description: agent.description || `Delegatable agent: ${agent.name}`,
77
+ tools: 'Read, Glob, Grep', // Safe defaults - read-only
78
+ };
79
+ // Map model if specified
80
+ if (agent.default_models?.anthropic) {
81
+ const modelAlias = mapModelToAlias(agent.default_models.anthropic);
82
+ if (modelAlias !== 'inherit') {
83
+ frontmatter.model = modelAlias;
84
+ }
85
+ }
86
+ // Build body
87
+ let body = agent.prompt || '';
88
+ // Add schema descriptions if present
89
+ if (agent.input_schema) {
90
+ body += `\n\n## Input Schema\n\nThis agent expects input matching:\n\`\`\`json\n${JSON.stringify(agent.input_schema, null, 2)}\n\`\`\``;
91
+ }
92
+ if (agent.output_schema) {
93
+ body += `\n\n## Output Schema\n\nThis agent should return output matching:\n\`\`\`json\n${JSON.stringify(agent.output_schema, null, 2)}\n\`\`\``;
94
+ }
95
+ // Combine frontmatter + body
96
+ const content = `---\n${yaml_1.default.stringify(frontmatter).trim()}\n---\n\n${body.trim()}\n`;
97
+ return [
98
+ {
99
+ filename: `${normalizedName}.md`,
100
+ content,
101
+ installPath: '.claude/agents/',
102
+ },
103
+ ];
104
+ },
105
+ };
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cursorAdapter = void 0;
4
+ // Convert agent name to valid filename (lowercase + hyphens)
5
+ function normalizeAgentName(name) {
6
+ const normalized = name
7
+ .toLowerCase()
8
+ .replace(/[^a-z0-9-]/g, '-')
9
+ .replace(/-+/g, '-')
10
+ .replace(/^-|-$/g, '');
11
+ return normalized || 'agent'; // Fallback if empty
12
+ }
13
+ exports.cursorAdapter = {
14
+ id: 'cursor',
15
+ name: 'Cursor Rules',
16
+ version: '1.0.0',
17
+ formatVersion: '2026-01',
18
+ supportsAgentTypes: ['prompt', 'skill'],
19
+ installPaths: [
20
+ {
21
+ scope: 'project',
22
+ path: '.cursor/rules/',
23
+ description: 'Project-level Cursor rules',
24
+ },
25
+ ],
26
+ canConvert(agent) {
27
+ const warnings = [];
28
+ const errors = [];
29
+ if (agent.type === 'code') {
30
+ errors.push('Code agents cannot be converted to Cursor rules');
31
+ return { canConvert: false, warnings, errors };
32
+ }
33
+ if (!agent.prompt) {
34
+ errors.push('Agent has no prompt content');
35
+ return { canConvert: false, warnings, errors };
36
+ }
37
+ // Cursor doesn't support input/output schemas
38
+ if (agent.input_schema) {
39
+ warnings.push('input_schema is not supported in Cursor rules');
40
+ }
41
+ if (agent.output_schema) {
42
+ warnings.push('output_schema is not supported in Cursor rules');
43
+ }
44
+ return { canConvert: true, warnings, errors };
45
+ },
46
+ convert(agent) {
47
+ const normalizedName = normalizeAgentName(agent.name);
48
+ const description = agent.description || `Rules from ${agent.name}`;
49
+ // Cursor .mdc format
50
+ const content = `---
51
+ description: ${description}
52
+ globs:
53
+ alwaysApply: false
54
+ ---
55
+
56
+ # ${agent.name}
57
+
58
+ ${agent.prompt || ''}
59
+ `;
60
+ return [
61
+ {
62
+ filename: `${normalizedName}.mdc`,
63
+ content,
64
+ installPath: '.cursor/rules/',
65
+ },
66
+ ];
67
+ },
68
+ };
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.agentsMdAdapter = exports.cursorAdapter = exports.claudeCodeAdapter = exports.adapterRegistry = exports.AdapterRegistry = void 0;
18
+ // Export types
19
+ __exportStar(require("./types"), exports);
20
+ // Export registry
21
+ const registry_1 = require("./registry");
22
+ var registry_2 = require("./registry");
23
+ Object.defineProperty(exports, "AdapterRegistry", { enumerable: true, get: function () { return registry_2.AdapterRegistry; } });
24
+ Object.defineProperty(exports, "adapterRegistry", { enumerable: true, get: function () { return registry_2.adapterRegistry; } });
25
+ // Import and register adapters
26
+ const claude_code_1 = require("./claude-code");
27
+ var claude_code_2 = require("./claude-code");
28
+ Object.defineProperty(exports, "claudeCodeAdapter", { enumerable: true, get: function () { return claude_code_2.claudeCodeAdapter; } });
29
+ const cursor_1 = require("./cursor");
30
+ var cursor_2 = require("./cursor");
31
+ Object.defineProperty(exports, "cursorAdapter", { enumerable: true, get: function () { return cursor_2.cursorAdapter; } });
32
+ const agents_md_1 = require("./agents-md");
33
+ var agents_md_2 = require("./agents-md");
34
+ Object.defineProperty(exports, "agentsMdAdapter", { enumerable: true, get: function () { return agents_md_2.agentsMdAdapter; } });
35
+ // Register built-in adapters
36
+ registry_1.adapterRegistry.register(claude_code_1.claudeCodeAdapter);
37
+ registry_1.adapterRegistry.register(cursor_1.cursorAdapter);
38
+ registry_1.adapterRegistry.register(agents_md_1.agentsMdAdapter);
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.adapterRegistry = exports.AdapterRegistry = void 0;
4
+ class AdapterRegistry {
5
+ adapters = new Map();
6
+ /**
7
+ * Register an adapter
8
+ */
9
+ register(adapter) {
10
+ if (this.adapters.has(adapter.id)) {
11
+ throw new Error(`Adapter '${adapter.id}' is already registered`);
12
+ }
13
+ this.adapters.set(adapter.id, adapter);
14
+ }
15
+ /**
16
+ * Get adapter by ID
17
+ */
18
+ get(id) {
19
+ return this.adapters.get(id);
20
+ }
21
+ /**
22
+ * List all registered adapters
23
+ */
24
+ list() {
25
+ return Array.from(this.adapters.values());
26
+ }
27
+ /**
28
+ * Find adapters compatible with an agent
29
+ */
30
+ findCompatible(agent) {
31
+ return this.list().filter(adapter => {
32
+ const result = adapter.canConvert(agent);
33
+ return result.canConvert;
34
+ });
35
+ }
36
+ /**
37
+ * Check if an adapter ID is valid
38
+ */
39
+ has(id) {
40
+ return this.adapters.has(id);
41
+ }
42
+ /**
43
+ * Get all adapter IDs
44
+ */
45
+ getIds() {
46
+ return Array.from(this.adapters.keys());
47
+ }
48
+ }
49
+ exports.AdapterRegistry = AdapterRegistry;
50
+ // Global registry instance
51
+ exports.adapterRegistry = new AdapterRegistry();
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerConfigCommand = registerConfigCommand;
4
+ const config_1 = require("../lib/config");
5
+ const errors_1 = require("../lib/errors");
6
+ const adapters_1 = require("../adapters");
7
+ const SUPPORTED_KEYS = ['default-format'];
8
+ function isValidKey(key) {
9
+ return SUPPORTED_KEYS.includes(key);
10
+ }
11
+ async function setConfigValue(key, value) {
12
+ if (!isValidKey(key)) {
13
+ throw new errors_1.CliError(`Unknown config key: ${key}. Supported keys: ${SUPPORTED_KEYS.join(', ')}`);
14
+ }
15
+ if (key === 'default-format') {
16
+ const formats = value.split(',').map((f) => f.trim()).filter(Boolean);
17
+ // Validate all format IDs against adapter registry
18
+ const validFormatIds = adapters_1.adapterRegistry.getIds();
19
+ const invalidFormats = formats.filter((f) => !validFormatIds.includes(f));
20
+ if (invalidFormats.length > 0) {
21
+ throw new errors_1.CliError(`Invalid format ID(s): ${invalidFormats.join(', ')}. Valid formats: ${validFormatIds.join(', ')}`);
22
+ }
23
+ await (0, config_1.setDefaultFormats)(formats);
24
+ process.stdout.write(`Set default-format to: ${formats.join(',')}\n`);
25
+ }
26
+ }
27
+ async function getConfigValue(key) {
28
+ if (!isValidKey(key)) {
29
+ throw new errors_1.CliError(`Unknown config key: ${key}. Supported keys: ${SUPPORTED_KEYS.join(', ')}`);
30
+ }
31
+ if (key === 'default-format') {
32
+ const formats = await (0, config_1.getDefaultFormats)();
33
+ if (formats.length === 0) {
34
+ process.stdout.write('(not set)\n');
35
+ }
36
+ else {
37
+ process.stdout.write(`${formats.join(',')}\n`);
38
+ }
39
+ }
40
+ }
41
+ async function listConfigValues() {
42
+ const config = await (0, config_1.loadConfig)();
43
+ process.stdout.write('CLI Configuration:\n\n');
44
+ // default-format
45
+ const formats = config.default_formats ?? [];
46
+ if (formats.length > 0) {
47
+ process.stdout.write(` default-format: ${formats.join(',')}\n`);
48
+ }
49
+ else {
50
+ process.stdout.write(' default-format: (not set)\n');
51
+ }
52
+ process.stdout.write('\n');
53
+ }
54
+ function registerConfigCommand(program) {
55
+ const config = program
56
+ .command('config')
57
+ .description('Manage CLI configuration');
58
+ config
59
+ .command('set <key> <value>')
60
+ .description('Set a configuration value')
61
+ .action(async (key, value) => {
62
+ await setConfigValue(key, value);
63
+ });
64
+ config
65
+ .command('get <key>')
66
+ .description('Get a configuration value')
67
+ .action(async (key) => {
68
+ await getConfigValue(key);
69
+ });
70
+ config
71
+ .command('list')
72
+ .description('List all configuration values')
73
+ .action(async () => {
74
+ await listConfigValues();
75
+ });
76
+ }
@@ -0,0 +1,44 @@
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.registerFormatsCommand = registerFormatsCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const adapters_1 = require("../adapters");
9
+ function registerFormatsCommand(program) {
10
+ program
11
+ .command('formats')
12
+ .description('List available export formats for agents')
13
+ .option('--json', 'Output raw JSON')
14
+ .action(async (options) => {
15
+ const adapters = adapters_1.adapterRegistry.list();
16
+ if (options.json) {
17
+ const data = adapters.map(a => ({
18
+ id: a.id,
19
+ name: a.name,
20
+ version: a.version,
21
+ formatVersion: a.formatVersion,
22
+ supportsAgentTypes: a.supportsAgentTypes,
23
+ installPaths: a.installPaths,
24
+ }));
25
+ process.stdout.write(JSON.stringify(data, null, 2) + '\n');
26
+ return;
27
+ }
28
+ process.stdout.write('\nAvailable export formats:\n\n');
29
+ for (const adapter of adapters) {
30
+ process.stdout.write(` ${chalk_1.default.cyan(adapter.id)} ${adapter.name}\n`);
31
+ process.stdout.write(` Supports: ${adapter.supportsAgentTypes.join(', ')} agents\n`);
32
+ process.stdout.write(` Format version: ${adapter.formatVersion}\n`);
33
+ for (const installPath of adapter.installPaths) {
34
+ const pathDisplay = installPath.scope === 'user'
35
+ ? installPath.path
36
+ : installPath.path;
37
+ process.stdout.write(` ${installPath.scope}: ${pathDisplay}\n`);
38
+ }
39
+ process.stdout.write('\n');
40
+ }
41
+ process.stdout.write('Use with: orch install <agent> --format <id>\n');
42
+ process.stdout.write('Set default: orch config set default-format <ids>\n\n');
43
+ });
44
+ }
@@ -21,6 +21,10 @@ const status_1 = require("./status");
21
21
  const workspace_1 = require("./workspace");
22
22
  const tree_1 = require("./tree");
23
23
  const docs_1 = require("./docs");
24
+ const config_1 = require("./config");
25
+ const install_1 = require("./install");
26
+ const formats_1 = require("./formats");
27
+ const update_1 = require("./update");
24
28
  function registerCommands(program) {
25
29
  (0, login_1.registerLoginCommand)(program);
26
30
  (0, whoami_1.registerWhoamiCommand)(program);
@@ -42,4 +46,8 @@ function registerCommands(program) {
42
46
  (0, workspace_1.registerWorkspaceCommand)(program);
43
47
  (0, tree_1.registerTreeCommand)(program);
44
48
  (0, docs_1.registerDocsCommand)(program);
49
+ (0, config_1.registerConfigCommand)(program);
50
+ (0, install_1.registerInstallCommand)(program);
51
+ (0, formats_1.registerFormatsCommand)(program);
52
+ (0, update_1.registerUpdateCommand)(program);
45
53
  }
@@ -0,0 +1,214 @@
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.registerInstallCommand = registerInstallCommand;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const config_1 = require("../lib/config");
11
+ const api_1 = require("../lib/api");
12
+ const errors_1 = require("../lib/errors");
13
+ const analytics_1 = require("../lib/analytics");
14
+ const adapters_1 = require("../adapters");
15
+ const installed_1 = require("../lib/installed");
16
+ const DEFAULT_VERSION = 'v1';
17
+ function escapeRegex(str) {
18
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
19
+ }
20
+ function parseAgentRef(value) {
21
+ const [ref, versionPart] = value.split('@');
22
+ const version = versionPart?.trim() || DEFAULT_VERSION;
23
+ const segments = ref.split('/');
24
+ if (segments.length === 1) {
25
+ return { name: segments[0], version };
26
+ }
27
+ if (segments.length === 2) {
28
+ return { org: segments[0], name: segments[1], version };
29
+ }
30
+ throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
31
+ }
32
+ async function downloadAgentWithFallback(config, org, name, version) {
33
+ // Try public endpoint first
34
+ try {
35
+ const agent = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${name}/${version}`);
36
+ return { ...agent, org_slug: org };
37
+ }
38
+ catch (err) {
39
+ if (!(err instanceof api_1.ApiError) || err.status !== 404)
40
+ throw err;
41
+ }
42
+ // Fallback to authenticated endpoint for private agents
43
+ if (!config.apiKey) {
44
+ throw new api_1.ApiError(`Agent '${org}/${name}@${version}' not found`, 404);
45
+ }
46
+ const userOrg = await (0, api_1.getOrg)(config);
47
+ if (userOrg.slug !== org) {
48
+ throw new api_1.ApiError(`Agent '${org}/${name}@${version}' not found`, 404);
49
+ }
50
+ const agents = await (0, api_1.listMyAgents)(config);
51
+ const matching = agents.filter(a => a.name === name);
52
+ if (matching.length === 0) {
53
+ throw new api_1.ApiError(`Agent '${org}/${name}@${version}' not found`, 404);
54
+ }
55
+ let targetAgent;
56
+ if (version === 'latest') {
57
+ targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
58
+ }
59
+ else {
60
+ const found = matching.find(a => a.version === version);
61
+ if (!found) {
62
+ throw new api_1.ApiError(`Agent '${org}/${name}@${version}' not found`, 404);
63
+ }
64
+ targetAgent = found;
65
+ }
66
+ return { ...targetAgent, org_slug: org };
67
+ }
68
+ function registerInstallCommand(program) {
69
+ program
70
+ .command('install <agent>')
71
+ .description('Install agent as sub-agent (Claude Code, Cursor, etc.)')
72
+ .option('--format <formats>', 'Comma-separated format IDs (e.g., claude-code,cursor)')
73
+ .option('--scope <scope>', 'Install scope: user (home dir) or project (current dir)', 'user')
74
+ .option('--dry-run', 'Show what would be installed without making changes')
75
+ .action(async (agentArg, options) => {
76
+ const resolved = await (0, config_1.getResolvedConfig)();
77
+ const parsed = parseAgentRef(agentArg);
78
+ const org = parsed.org ?? resolved.defaultOrg;
79
+ if (!org) {
80
+ throw new errors_1.CliError('Missing org. Use org/agent format or set default org.');
81
+ }
82
+ // Determine target formats
83
+ let targetFormats = [];
84
+ if (options.format) {
85
+ targetFormats = options.format.split(',').map(f => f.trim());
86
+ const invalid = targetFormats.filter(f => !adapters_1.adapterRegistry.has(f));
87
+ if (invalid.length > 0) {
88
+ throw new errors_1.CliError(`Unknown format(s): ${invalid.join(', ')}. Available: ${adapters_1.adapterRegistry.getIds().join(', ')}`);
89
+ }
90
+ }
91
+ else {
92
+ const defaults = await (0, config_1.getDefaultFormats)();
93
+ if (defaults.length > 0) {
94
+ targetFormats = defaults;
95
+ }
96
+ else {
97
+ // No default configured - use claude-code as sensible default
98
+ targetFormats = ['claude-code'];
99
+ }
100
+ }
101
+ // Validate scope
102
+ let scope = options.scope;
103
+ if (scope !== 'user' && scope !== 'project') {
104
+ throw new errors_1.CliError('Scope must be "user" or "project"');
105
+ }
106
+ // Download agent
107
+ process.stdout.write(`Fetching ${org}/${parsed.name}@${parsed.version}...\n`);
108
+ const agent = await downloadAgentWithFallback(resolved, org, parsed.name, parsed.version);
109
+ // Install for each format
110
+ let filesWritten = 0;
111
+ for (const formatId of targetFormats) {
112
+ const adapter = adapters_1.adapterRegistry.get(formatId);
113
+ if (!adapter) {
114
+ process.stderr.write(`Warning: Unknown format '${formatId}', skipping\n`);
115
+ continue;
116
+ }
117
+ // Check if can convert
118
+ const checkResult = adapter.canConvert(agent);
119
+ if (!checkResult.canConvert) {
120
+ process.stderr.write(`Cannot convert to ${adapter.name}:\n`);
121
+ for (const err of checkResult.errors) {
122
+ process.stderr.write(` - ${err}\n`);
123
+ }
124
+ continue;
125
+ }
126
+ // Show warnings
127
+ for (const warn of checkResult.warnings) {
128
+ process.stdout.write(`Warning (${formatId}): ${warn}\n`);
129
+ }
130
+ // Check if adapter supports requested scope
131
+ const supportedScopes = adapter.installPaths.map(p => p.scope);
132
+ if (!supportedScopes.includes(scope)) {
133
+ process.stderr.write(`Warning: ${adapter.name} doesn't support '${scope}' scope. ` +
134
+ `Supported: ${supportedScopes.join(', ')}. Using '${supportedScopes[0]}' instead.\n`);
135
+ // Use the first supported scope
136
+ scope = supportedScopes[0];
137
+ }
138
+ // Convert
139
+ const files = adapter.convert(agent);
140
+ // Determine base directory
141
+ const baseDir = scope === 'user' ? os_1.default.homedir() : process.cwd();
142
+ // Install each file
143
+ for (const file of files) {
144
+ const fullDir = path_1.default.join(baseDir, file.installPath);
145
+ const fullPath = path_1.default.join(fullDir, file.filename);
146
+ if (options.dryRun) {
147
+ process.stdout.write(`Would install: ${fullPath}\n`);
148
+ process.stdout.write(`Content preview:\n${file.content.slice(0, 500)}...\n\n`);
149
+ continue;
150
+ }
151
+ // Create directory and write file
152
+ await promises_1.default.mkdir(fullDir, { recursive: true });
153
+ // Special handling for AGENTS.md - append/replace instead of overwrite
154
+ if (file.filename === 'AGENTS.md') {
155
+ let existingContent = '';
156
+ try {
157
+ existingContent = await promises_1.default.readFile(fullPath, 'utf-8');
158
+ }
159
+ catch {
160
+ // File doesn't exist, will create new
161
+ }
162
+ const agentRef = `${org}/${parsed.name}`;
163
+ const markerStart = `<!-- orchagent:${agentRef} -->`;
164
+ const markerEnd = `<!-- /orchagent:${agentRef} -->`;
165
+ if (existingContent.includes(markerStart)) {
166
+ // Replace existing section
167
+ const regex = new RegExp(`${escapeRegex(markerStart)}[\\s\\S]*?${escapeRegex(markerEnd)}`, 'g');
168
+ file.content = existingContent.replace(regex, file.content.trim());
169
+ }
170
+ else if (existingContent) {
171
+ // Append to existing file
172
+ file.content = existingContent.trimEnd() + '\n\n' + file.content;
173
+ }
174
+ // else: new file, use content as-is
175
+ }
176
+ await promises_1.default.writeFile(fullPath, file.content);
177
+ filesWritten++;
178
+ // Track installation
179
+ const installedAgent = {
180
+ agent: `${org}/${parsed.name}`,
181
+ version: parsed.version,
182
+ format: formatId,
183
+ scope,
184
+ path: fullPath,
185
+ installedAt: new Date().toISOString(),
186
+ adapterVersion: adapter.version,
187
+ contentHash: (0, installed_1.computeHash)(file.content),
188
+ };
189
+ await (0, installed_1.trackInstall)(installedAgent);
190
+ process.stdout.write(`Installed: ${fullPath}\n`);
191
+ }
192
+ }
193
+ if (!options.dryRun) {
194
+ if (filesWritten > 0) {
195
+ await (0, analytics_1.track)('cli_agent_install', {
196
+ agent: `${org}/${parsed.name}`,
197
+ formats: targetFormats,
198
+ scope,
199
+ });
200
+ process.stdout.write(`\nAgent installed successfully!\n`);
201
+ if (scope === 'user') {
202
+ process.stdout.write(`Available in all your projects.\n`);
203
+ }
204
+ else {
205
+ process.stdout.write(`Available in this project only.\n`);
206
+ }
207
+ }
208
+ else {
209
+ process.stderr.write(`\nNo files were installed. Check warnings above.\n`);
210
+ process.exit(1);
211
+ }
212
+ }
213
+ });
214
+ }
@@ -162,8 +162,34 @@ Instructions and guidance for AI agents...
162
162
  .description('Install skill to local AI tool directories (Claude Code, Cursor, etc.)')
163
163
  .option('--global', 'Install to home directory (default: current directory)')
164
164
  .option('--dry-run', 'Show what would be installed without making changes')
165
+ .option('--format <formats>', 'Comma-separated format IDs (e.g., claude-code,cursor)')
165
166
  .action(async (skillRef, options) => {
166
167
  const resolved = await (0, config_1.getResolvedConfig)();
168
+ // Determine target formats
169
+ let targetFormats = [];
170
+ if (options.format) {
171
+ targetFormats = options.format.split(',').map((f) => f.trim());
172
+ // Validate format IDs
173
+ const invalid = targetFormats.filter(f => !config_1.VALID_FORMAT_IDS.includes(f));
174
+ if (invalid.length > 0) {
175
+ throw new errors_1.CliError(`Invalid format ID(s): ${invalid.join(', ')}. Valid: ${config_1.VALID_FORMAT_IDS.join(', ')}`);
176
+ }
177
+ }
178
+ else {
179
+ const defaults = await (0, config_1.getDefaultFormats)();
180
+ if (defaults.length > 0) {
181
+ // Filter to formats that have skill directories
182
+ targetFormats = defaults.filter(f => config_1.VALID_FORMAT_IDS.includes(f));
183
+ const skipped = defaults.filter(f => !config_1.VALID_FORMAT_IDS.includes(f));
184
+ if (skipped.length > 0) {
185
+ process.stderr.write(`Note: Skipping ${skipped.join(', ')} (no skill directory)\n`);
186
+ }
187
+ }
188
+ }
189
+ // Determine target directories
190
+ const toolDirs = targetFormats.length > 0
191
+ ? targetFormats.map(f => config_1.FORMAT_SKILL_DIRS[f])
192
+ : AI_TOOL_SKILL_DIRS; // fallback to all
167
193
  const parsed = parseSkillRef(skillRef);
168
194
  const org = parsed.org ?? resolved.defaultOrg;
169
195
  if (!org) {
@@ -191,7 +217,7 @@ ${skillData.prompt}
191
217
  if (options.dryRun) {
192
218
  process.stdout.write(`Would install ${org}/${parsed.skill}@${parsed.version}\n\n`);
193
219
  process.stdout.write(`Target directories:\n`);
194
- for (const tool of AI_TOOL_SKILL_DIRS) {
220
+ for (const tool of toolDirs) {
195
221
  const skillDir = path_1.default.join(baseDir, tool.path);
196
222
  const skillFile = path_1.default.join(skillDir, `${parsed.skill}.md`);
197
223
  process.stdout.write(` - ${tool.name}: ${skillFile}\n`);
@@ -199,9 +225,9 @@ ${skillData.prompt}
199
225
  process.stdout.write(`\nNo changes made (dry run)\n`);
200
226
  return;
201
227
  }
202
- // Install to all AI tool directories
228
+ // Install to target AI tool directories
203
229
  const installed = [];
204
- for (const tool of AI_TOOL_SKILL_DIRS) {
230
+ for (const tool of toolDirs) {
205
231
  const skillDir = path_1.default.join(baseDir, tool.path);
206
232
  const skillFile = path_1.default.join(skillDir, `${parsed.skill}.md`);
207
233
  try {
@@ -0,0 +1,176 @@
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.registerUpdateCommand = registerUpdateCommand;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const config_1 = require("../lib/config");
11
+ const api_1 = require("../lib/api");
12
+ const analytics_1 = require("../lib/analytics");
13
+ const adapters_1 = require("../adapters");
14
+ const installed_1 = require("../lib/installed");
15
+ function escapeRegex(str) {
16
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
17
+ }
18
+ async function fetchLatestAgent(config, agentRef) {
19
+ const [org, name] = agentRef.split('/');
20
+ if (!org || !name)
21
+ return null;
22
+ try {
23
+ // Try to get latest version
24
+ const agent = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${name}/latest`);
25
+ return {
26
+ agent: { ...agent, org_slug: org },
27
+ latestVersion: agent.version
28
+ };
29
+ }
30
+ catch (err) {
31
+ if (err instanceof api_1.ApiError && err.status === 404) {
32
+ return null;
33
+ }
34
+ throw err;
35
+ }
36
+ }
37
+ function registerUpdateCommand(program) {
38
+ program
39
+ .command('update [agent]')
40
+ .description('Update installed agents to latest versions')
41
+ .option('--check', 'Check for updates without installing')
42
+ .option('--force', 'Force update even if file was modified locally')
43
+ .action(async (agentRef, options) => {
44
+ const resolved = await (0, config_1.getResolvedConfig)();
45
+ const installed = await (0, installed_1.getInstalled)();
46
+ if (installed.length === 0) {
47
+ process.stdout.write('No agents installed. Use "orch install <agent>" to install agents.\n');
48
+ return;
49
+ }
50
+ // Filter to specific agent if provided
51
+ const toCheck = agentRef
52
+ ? installed.filter(i => i.agent === agentRef)
53
+ : installed;
54
+ if (toCheck.length === 0) {
55
+ process.stdout.write(`Agent "${agentRef}" is not installed.\n`);
56
+ return;
57
+ }
58
+ process.stdout.write(`Checking ${toCheck.length} installed agent(s) for updates...\n\n`);
59
+ let updatesAvailable = 0;
60
+ let updatesApplied = 0;
61
+ let skippedModified = 0;
62
+ for (const item of toCheck) {
63
+ // Check if file was modified locally
64
+ const wasModified = await (0, installed_1.checkModified)(item);
65
+ // Fetch latest version
66
+ const latest = await fetchLatestAgent(resolved, item.agent);
67
+ if (!latest) {
68
+ process.stdout.write(` ${chalk_1.default.yellow('?')} ${item.agent} - could not fetch latest\n`);
69
+ continue;
70
+ }
71
+ const hasUpdate = latest.latestVersion !== item.version;
72
+ if (!hasUpdate && !wasModified) {
73
+ process.stdout.write(` ${chalk_1.default.green('✓')} ${item.agent}@${item.version} - up to date\n`);
74
+ continue;
75
+ }
76
+ if (wasModified && !options.force) {
77
+ process.stdout.write(` ${chalk_1.default.yellow('!')} ${item.agent} - local modifications (use --force to overwrite)\n`);
78
+ skippedModified++;
79
+ continue;
80
+ }
81
+ if (hasUpdate) {
82
+ updatesAvailable++;
83
+ process.stdout.write(` ${chalk_1.default.blue('↑')} ${item.agent}@${item.version} → ${latest.latestVersion}`);
84
+ if (wasModified) {
85
+ process.stdout.write(` ${chalk_1.default.yellow('(modified)')}`);
86
+ }
87
+ process.stdout.write('\n');
88
+ if (options.check) {
89
+ continue;
90
+ }
91
+ // Apply update
92
+ const adapter = adapters_1.adapterRegistry.get(item.format);
93
+ if (!adapter) {
94
+ process.stderr.write(` Skipped: Unknown format "${item.format}"\n`);
95
+ continue;
96
+ }
97
+ const checkResult = adapter.canConvert(latest.agent);
98
+ if (!checkResult.canConvert) {
99
+ process.stderr.write(` Skipped: ${checkResult.errors.join(', ')}\n`);
100
+ continue;
101
+ }
102
+ const files = adapter.convert(latest.agent);
103
+ if (files.length > 1) {
104
+ process.stderr.write(` Skipped: Multi-file adapters not supported for update. Reinstall instead.\n`);
105
+ continue;
106
+ }
107
+ for (const file of files) {
108
+ // Use the original path from tracking
109
+ const fullPath = item.path;
110
+ try {
111
+ const dir = path_1.default.dirname(fullPath);
112
+ await promises_1.default.mkdir(dir, { recursive: true });
113
+ // Special handling for AGENTS.md - append/replace instead of overwrite
114
+ if (file.filename === 'AGENTS.md') {
115
+ let existingContent = '';
116
+ try {
117
+ existingContent = await promises_1.default.readFile(fullPath, 'utf-8');
118
+ }
119
+ catch {
120
+ // File doesn't exist, will create new
121
+ }
122
+ const agentRef = item.agent;
123
+ const markerStart = `<!-- orchagent:${agentRef} -->`;
124
+ const markerEnd = `<!-- /orchagent:${agentRef} -->`;
125
+ if (existingContent.includes(markerStart)) {
126
+ // Replace existing section
127
+ const regex = new RegExp(`${escapeRegex(markerStart)}[\\s\\S]*?${escapeRegex(markerEnd)}`, 'g');
128
+ file.content = existingContent.replace(regex, file.content.trim());
129
+ }
130
+ else if (existingContent) {
131
+ // Append to existing file
132
+ file.content = existingContent.trimEnd() + '\n\n' + file.content;
133
+ }
134
+ // else: new file, use content as-is
135
+ }
136
+ await promises_1.default.writeFile(fullPath, file.content);
137
+ // Update tracking
138
+ const updatedItem = {
139
+ ...item,
140
+ version: latest.latestVersion,
141
+ installedAt: new Date().toISOString(),
142
+ adapterVersion: adapter.version,
143
+ contentHash: (0, installed_1.computeHash)(file.content),
144
+ };
145
+ await (0, installed_1.trackInstall)(updatedItem);
146
+ process.stdout.write(` Updated: ${fullPath}\n`);
147
+ updatesApplied++;
148
+ }
149
+ catch (err) {
150
+ process.stderr.write(` Error: ${err.message}\n`);
151
+ }
152
+ }
153
+ }
154
+ }
155
+ process.stdout.write('\n');
156
+ if (options.check) {
157
+ process.stdout.write(`Found ${updatesAvailable} update(s) available.\n`);
158
+ if (skippedModified > 0) {
159
+ process.stdout.write(`${skippedModified} agent(s) have local modifications.\n`);
160
+ }
161
+ process.stdout.write('Run "orch update" without --check to apply updates.\n');
162
+ }
163
+ else {
164
+ process.stdout.write(`Applied ${updatesApplied} update(s).\n`);
165
+ if (skippedModified > 0) {
166
+ process.stdout.write(`Skipped ${skippedModified} modified agent(s). Use --force to overwrite.\n`);
167
+ }
168
+ }
169
+ await (0, analytics_1.track)('cli_agent_update', {
170
+ checked: toCheck.length,
171
+ updatesAvailable,
172
+ updatesApplied,
173
+ skippedModified,
174
+ });
175
+ });
176
+ }
@@ -3,16 +3,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FORMAT_SKILL_DIRS = exports.VALID_FORMAT_IDS = void 0;
6
7
  exports.loadConfig = loadConfig;
7
8
  exports.saveConfig = saveConfig;
8
9
  exports.getResolvedConfig = getResolvedConfig;
9
10
  exports.getConfigPath = getConfigPath;
11
+ exports.getDefaultFormats = getDefaultFormats;
12
+ exports.setDefaultFormats = setDefaultFormats;
10
13
  const promises_1 = __importDefault(require("fs/promises"));
11
14
  const path_1 = __importDefault(require("path"));
12
15
  const os_1 = __importDefault(require("os"));
13
16
  const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.orchagent');
14
17
  const CONFIG_PATH = path_1.default.join(CONFIG_DIR, 'config.json');
15
18
  const DEFAULT_API_URL = 'https://api.orchagent.io';
19
+ // Valid format IDs for multi-format agent export
20
+ exports.VALID_FORMAT_IDS = ['claude-code', 'cursor', 'codex', 'amp', 'opencode', 'antigravity'];
21
+ // Map format IDs to skill directories (used by skill install and agent install)
22
+ exports.FORMAT_SKILL_DIRS = {
23
+ 'claude-code': { name: 'Claude Code', path: '.claude/skills' },
24
+ 'cursor': { name: 'Cursor', path: '.cursor/skills' },
25
+ 'codex': { name: 'Codex', path: '.codex/skills' },
26
+ 'amp': { name: 'Amp', path: '.agents/skills' },
27
+ 'opencode': { name: 'OpenCode', path: '.opencode/skill' },
28
+ 'antigravity': { name: 'Antigravity', path: '.agent/skills' },
29
+ };
16
30
  async function loadConfig() {
17
31
  try {
18
32
  const raw = await promises_1.default.readFile(CONFIG_PATH, 'utf-8');
@@ -56,3 +70,12 @@ async function getResolvedConfig(overrides = {}, profile) {
56
70
  function getConfigPath() {
57
71
  return CONFIG_PATH;
58
72
  }
73
+ async function getDefaultFormats() {
74
+ const config = await loadConfig();
75
+ return config.default_formats ?? [];
76
+ }
77
+ async function setDefaultFormats(formats) {
78
+ const config = await loadConfig();
79
+ config.default_formats = formats;
80
+ await saveConfig(config);
81
+ }
@@ -0,0 +1,94 @@
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.computeHash = computeHash;
7
+ exports.loadInstalled = loadInstalled;
8
+ exports.saveInstalled = saveInstalled;
9
+ exports.trackInstall = trackInstall;
10
+ exports.getInstalled = getInstalled;
11
+ exports.getInstalledByFormat = getInstalledByFormat;
12
+ exports.checkModified = checkModified;
13
+ exports.untrackInstall = untrackInstall;
14
+ const promises_1 = __importDefault(require("fs/promises"));
15
+ const path_1 = __importDefault(require("path"));
16
+ const os_1 = __importDefault(require("os"));
17
+ const crypto_1 = __importDefault(require("crypto"));
18
+ const ORCHAGENT_DIR = path_1.default.join(os_1.default.homedir(), '.orchagent');
19
+ const INSTALLED_PATH = path_1.default.join(ORCHAGENT_DIR, 'installed.json');
20
+ /**
21
+ * Compute SHA-256 hash of content
22
+ */
23
+ function computeHash(content) {
24
+ return crypto_1.default.createHash('sha256').update(content).digest('hex');
25
+ }
26
+ /**
27
+ * Load installed agents from tracking file
28
+ */
29
+ async function loadInstalled() {
30
+ try {
31
+ const raw = await promises_1.default.readFile(INSTALLED_PATH, 'utf-8');
32
+ return JSON.parse(raw);
33
+ }
34
+ catch (err) {
35
+ if (err.code === 'ENOENT') {
36
+ return { version: 1, installed: [] };
37
+ }
38
+ throw err;
39
+ }
40
+ }
41
+ /**
42
+ * Save installed agents to tracking file
43
+ */
44
+ async function saveInstalled(data) {
45
+ await promises_1.default.mkdir(ORCHAGENT_DIR, { recursive: true });
46
+ await promises_1.default.writeFile(INSTALLED_PATH, JSON.stringify(data, null, 2) + '\n');
47
+ }
48
+ /**
49
+ * Track a newly installed agent
50
+ */
51
+ async function trackInstall(agent) {
52
+ const data = await loadInstalled();
53
+ // Remove any existing entry for same agent/format/scope/path
54
+ data.installed = data.installed.filter(i => !(i.agent === agent.agent && i.format === agent.format && i.path === agent.path));
55
+ // Add new entry
56
+ data.installed.push(agent);
57
+ await saveInstalled(data);
58
+ }
59
+ /**
60
+ * Get all installed agents
61
+ */
62
+ async function getInstalled() {
63
+ const data = await loadInstalled();
64
+ return data.installed;
65
+ }
66
+ /**
67
+ * Get installed agents filtered by format
68
+ */
69
+ async function getInstalledByFormat(format) {
70
+ const data = await loadInstalled();
71
+ return data.installed.filter(i => i.format === format);
72
+ }
73
+ /**
74
+ * Check if a file has been modified since installation
75
+ */
76
+ async function checkModified(installed) {
77
+ try {
78
+ const content = await promises_1.default.readFile(installed.path, 'utf-8');
79
+ const currentHash = computeHash(content);
80
+ return currentHash !== installed.contentHash;
81
+ }
82
+ catch {
83
+ // File doesn't exist or can't be read
84
+ return true;
85
+ }
86
+ }
87
+ /**
88
+ * Remove an installed agent from tracking
89
+ */
90
+ async function untrackInstall(agentRef, format, filePath) {
91
+ const data = await loadInstalled();
92
+ data.installed = data.installed.filter(i => !(i.agent === agentRef && i.format === format && i.path === filePath));
93
+ await saveInstalled(data);
94
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.2.26",
3
+ "version": "0.3.1",
4
4
  "description": "Command-line interface for the orchagent AI agent marketplace",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",