@orchagent/cli 0.1.0

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,330 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for the run command and LLM utilities.
4
+ *
5
+ * These tests cover downloading and running agents locally:
6
+ * - parseAgentRef() parsing org/agent@version formats
7
+ * - downloadAgent() fetching from API
8
+ * - LLM key detection from environment
9
+ * - Building prompts with variable substitution
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const vitest_1 = require("vitest");
16
+ const commander_1 = require("commander");
17
+ // Mock modules before importing
18
+ vitest_1.vi.mock('fs/promises');
19
+ vitest_1.vi.mock('../lib/config');
20
+ vitest_1.vi.mock('../lib/api');
21
+ const promises_1 = __importDefault(require("fs/promises"));
22
+ const run_1 = require("./run");
23
+ const config_1 = require("../lib/config");
24
+ const api_1 = require("../lib/api");
25
+ const llm_1 = require("../lib/llm");
26
+ const mockFs = vitest_1.vi.mocked(promises_1.default);
27
+ const mockGetResolvedConfig = vitest_1.vi.mocked(config_1.getResolvedConfig);
28
+ const mockPublicRequest = vitest_1.vi.mocked(api_1.publicRequest);
29
+ const mockGetPublicAgent = vitest_1.vi.mocked(api_1.getPublicAgent);
30
+ (0, vitest_1.describe)('run command - agent ref parsing', () => {
31
+ let program;
32
+ let stdoutSpy;
33
+ let stderrSpy;
34
+ (0, vitest_1.beforeEach)(() => {
35
+ vitest_1.vi.clearAllMocks();
36
+ program = new commander_1.Command();
37
+ program.exitOverride();
38
+ (0, run_1.registerRunCommand)(program);
39
+ stdoutSpy = vitest_1.vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
40
+ stderrSpy = vitest_1.vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
41
+ mockGetResolvedConfig.mockResolvedValue({
42
+ apiKey: 'sk_test_123',
43
+ apiUrl: 'https://api.test.com',
44
+ defaultOrg: 'default-org',
45
+ });
46
+ // Mock mkdir for agent saving
47
+ mockFs.mkdir.mockResolvedValue(undefined);
48
+ mockFs.writeFile.mockResolvedValue(undefined);
49
+ });
50
+ (0, vitest_1.afterEach)(() => {
51
+ stdoutSpy.mockRestore();
52
+ stderrSpy.mockRestore();
53
+ vitest_1.vi.restoreAllMocks();
54
+ });
55
+ (0, vitest_1.it)('parses org/agent@version format', async () => {
56
+ mockPublicRequest.mockResolvedValue({
57
+ type: 'code',
58
+ name: 'my-agent',
59
+ version: 'v2',
60
+ supported_providers: ['any'],
61
+ });
62
+ await program.parseAsync(['node', 'test', 'run', 'myorg/my-agent@v2']);
63
+ (0, vitest_1.expect)(mockPublicRequest).toHaveBeenCalledWith(vitest_1.expect.any(Object), '/public/agents/myorg/my-agent/v2/download');
64
+ });
65
+ (0, vitest_1.it)('parses org/agent format with default version', async () => {
66
+ mockPublicRequest.mockResolvedValue({
67
+ type: 'code',
68
+ name: 'my-agent',
69
+ version: 'latest',
70
+ supported_providers: ['any'],
71
+ });
72
+ await program.parseAsync(['node', 'test', 'run', 'myorg/my-agent']);
73
+ (0, vitest_1.expect)(mockPublicRequest).toHaveBeenCalledWith(vitest_1.expect.any(Object), '/public/agents/myorg/my-agent/latest/download');
74
+ });
75
+ (0, vitest_1.it)('uses defaultOrg when no org specified', async () => {
76
+ mockPublicRequest.mockResolvedValue({
77
+ type: 'code',
78
+ name: 'my-agent',
79
+ version: 'latest',
80
+ supported_providers: ['any'],
81
+ });
82
+ await program.parseAsync(['node', 'test', 'run', 'my-agent']);
83
+ (0, vitest_1.expect)(mockPublicRequest).toHaveBeenCalledWith(vitest_1.expect.any(Object), '/public/agents/default-org/my-agent/latest/download');
84
+ });
85
+ (0, vitest_1.it)('parses agent@version format', async () => {
86
+ mockPublicRequest.mockResolvedValue({
87
+ type: 'code',
88
+ name: 'my-agent',
89
+ version: 'v3',
90
+ supported_providers: ['any'],
91
+ });
92
+ await program.parseAsync(['node', 'test', 'run', 'my-agent@v3']);
93
+ (0, vitest_1.expect)(mockPublicRequest).toHaveBeenCalledWith(vitest_1.expect.any(Object), '/public/agents/default-org/my-agent/v3/download');
94
+ });
95
+ (0, vitest_1.it)('throws error for invalid agent ref format', async () => {
96
+ mockGetResolvedConfig.mockResolvedValue({
97
+ apiUrl: 'https://api.test.com',
98
+ // No defaultOrg
99
+ });
100
+ await (0, vitest_1.expect)(program.parseAsync(['node', 'test', 'run', 'just-agent'])).rejects.toThrow('Missing org');
101
+ });
102
+ (0, vitest_1.it)('throws error for too many segments', async () => {
103
+ await (0, vitest_1.expect)(program.parseAsync(['node', 'test', 'run', 'a/b/c/d'])).rejects.toThrow('Invalid agent reference');
104
+ });
105
+ });
106
+ (0, vitest_1.describe)('run command - download agent', () => {
107
+ let program;
108
+ let stdoutSpy;
109
+ let stderrSpy;
110
+ (0, vitest_1.beforeEach)(() => {
111
+ vitest_1.vi.clearAllMocks();
112
+ program = new commander_1.Command();
113
+ program.exitOverride();
114
+ (0, run_1.registerRunCommand)(program);
115
+ stdoutSpy = vitest_1.vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
116
+ stderrSpy = vitest_1.vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
117
+ mockGetResolvedConfig.mockResolvedValue({
118
+ apiKey: 'sk_test_123',
119
+ apiUrl: 'https://api.test.com',
120
+ defaultOrg: 'test-org',
121
+ });
122
+ mockFs.mkdir.mockResolvedValue(undefined);
123
+ mockFs.writeFile.mockResolvedValue(undefined);
124
+ });
125
+ (0, vitest_1.afterEach)(() => {
126
+ stdoutSpy.mockRestore();
127
+ stderrSpy.mockRestore();
128
+ vitest_1.vi.restoreAllMocks();
129
+ });
130
+ (0, vitest_1.it)('downloads agent via public API', async () => {
131
+ mockPublicRequest.mockResolvedValue({
132
+ type: 'prompt',
133
+ name: 'test-agent',
134
+ version: 'v1',
135
+ description: 'A test agent',
136
+ prompt: 'You are a test assistant.',
137
+ supported_providers: ['openai'],
138
+ });
139
+ await program.parseAsync(['node', 'test', 'run', 'test-org/test-agent@v1']);
140
+ (0, vitest_1.expect)(mockPublicRequest).toHaveBeenCalledWith({ apiKey: 'sk_test_123', apiUrl: 'https://api.test.com', defaultOrg: 'test-org' }, '/public/agents/test-org/test-agent/v1/download');
141
+ });
142
+ (0, vitest_1.it)('falls back to getPublicAgent if download fails', async () => {
143
+ mockPublicRequest.mockRejectedValue(new Error('Not found'));
144
+ mockGetPublicAgent.mockResolvedValue({
145
+ id: 'agent-123',
146
+ org_id: 'org-123',
147
+ name: 'fallback-agent',
148
+ version: 'v1',
149
+ type: 'code',
150
+ supported_providers: ['any'],
151
+ is_public: true,
152
+ });
153
+ await program.parseAsync(['node', 'test', 'run', 'test-org/fallback-agent@v1']);
154
+ (0, vitest_1.expect)(mockGetPublicAgent).toHaveBeenCalledWith(vitest_1.expect.any(Object), 'test-org', 'fallback-agent', 'v1');
155
+ });
156
+ (0, vitest_1.it)('saves agent metadata locally', async () => {
157
+ mockPublicRequest.mockResolvedValue({
158
+ type: 'prompt',
159
+ name: 'saved-agent',
160
+ version: 'v1',
161
+ prompt: 'Test prompt',
162
+ supported_providers: ['any'],
163
+ });
164
+ await program.parseAsync(['node', 'test', 'run', 'test-org/saved-agent@v1']);
165
+ (0, vitest_1.expect)(mockFs.mkdir).toHaveBeenCalled();
166
+ (0, vitest_1.expect)(mockFs.writeFile).toHaveBeenCalledWith(vitest_1.expect.stringContaining('agent.json'), vitest_1.expect.stringContaining('"name": "saved-agent"'));
167
+ });
168
+ (0, vitest_1.it)('saves prompt.md for prompt agents', async () => {
169
+ mockPublicRequest.mockResolvedValue({
170
+ type: 'prompt',
171
+ name: 'prompt-agent',
172
+ version: 'v1',
173
+ prompt: 'You are a helpful assistant.',
174
+ supported_providers: ['any'],
175
+ });
176
+ await program.parseAsync(['node', 'test', 'run', 'test-org/prompt-agent@v1']);
177
+ (0, vitest_1.expect)(mockFs.writeFile).toHaveBeenCalledWith(vitest_1.expect.stringContaining('prompt.md'), 'You are a helpful assistant.');
178
+ });
179
+ (0, vitest_1.it)('handles --download-only flag', async () => {
180
+ mockPublicRequest.mockResolvedValue({
181
+ type: 'prompt',
182
+ name: 'download-only-agent',
183
+ version: 'v1',
184
+ prompt: 'Test',
185
+ supported_providers: ['any'],
186
+ });
187
+ await program.parseAsync([
188
+ 'node',
189
+ 'test',
190
+ 'run',
191
+ 'test-org/download-only-agent@v1',
192
+ '--download-only',
193
+ ]);
194
+ (0, vitest_1.expect)(stdoutSpy).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Agent downloaded'));
195
+ });
196
+ });
197
+ (0, vitest_1.describe)('LLM utilities - detectLlmKeyFromEnv', () => {
198
+ const originalEnv = process.env;
199
+ (0, vitest_1.beforeEach)(() => {
200
+ process.env = { ...originalEnv };
201
+ delete process.env.OPENAI_API_KEY;
202
+ delete process.env.ANTHROPIC_API_KEY;
203
+ delete process.env.GEMINI_API_KEY;
204
+ });
205
+ (0, vitest_1.afterEach)(() => {
206
+ process.env = originalEnv;
207
+ });
208
+ (0, vitest_1.it)('returns null when no keys found', () => {
209
+ const result = (0, llm_1.detectLlmKeyFromEnv)(['openai']);
210
+ (0, vitest_1.expect)(result).toBeNull();
211
+ });
212
+ (0, vitest_1.it)('detects OpenAI key', () => {
213
+ process.env.OPENAI_API_KEY = 'sk-openai-test';
214
+ const result = (0, llm_1.detectLlmKeyFromEnv)(['openai']);
215
+ (0, vitest_1.expect)(result).toEqual({ provider: 'openai', key: 'sk-openai-test' });
216
+ });
217
+ (0, vitest_1.it)('detects Anthropic key', () => {
218
+ process.env.ANTHROPIC_API_KEY = 'sk-ant-test';
219
+ const result = (0, llm_1.detectLlmKeyFromEnv)(['anthropic']);
220
+ (0, vitest_1.expect)(result).toEqual({ provider: 'anthropic', key: 'sk-ant-test' });
221
+ });
222
+ (0, vitest_1.it)('detects Gemini key', () => {
223
+ process.env.GEMINI_API_KEY = 'AIza-gemini-test';
224
+ const result = (0, llm_1.detectLlmKeyFromEnv)(['gemini']);
225
+ (0, vitest_1.expect)(result).toEqual({ provider: 'gemini', key: 'AIza-gemini-test' });
226
+ });
227
+ (0, vitest_1.it)('returns first matching provider in order', () => {
228
+ process.env.ANTHROPIC_API_KEY = 'sk-ant-test';
229
+ process.env.OPENAI_API_KEY = 'sk-openai-test';
230
+ const result = (0, llm_1.detectLlmKeyFromEnv)(['openai', 'anthropic']);
231
+ (0, vitest_1.expect)(result).toEqual({ provider: 'openai', key: 'sk-openai-test' });
232
+ });
233
+ (0, vitest_1.it)('handles "any" provider - checks all in order', () => {
234
+ process.env.GEMINI_API_KEY = 'AIza-gemini-test';
235
+ const result = (0, llm_1.detectLlmKeyFromEnv)(['any']);
236
+ // 'any' should find the first available key
237
+ (0, vitest_1.expect)(result?.key).toBe('AIza-gemini-test');
238
+ });
239
+ (0, vitest_1.it)('handles "any" provider - returns first found', () => {
240
+ process.env.OPENAI_API_KEY = 'sk-openai-test';
241
+ process.env.ANTHROPIC_API_KEY = 'sk-ant-test';
242
+ const result = (0, llm_1.detectLlmKeyFromEnv)(['any']);
243
+ // OpenAI is checked first in the provider order
244
+ (0, vitest_1.expect)(result).toEqual({ provider: 'openai', key: 'sk-openai-test' });
245
+ });
246
+ (0, vitest_1.it)('skips unavailable providers', () => {
247
+ process.env.ANTHROPIC_API_KEY = 'sk-ant-test';
248
+ // No OpenAI key
249
+ const result = (0, llm_1.detectLlmKeyFromEnv)(['openai', 'anthropic']);
250
+ (0, vitest_1.expect)(result).toEqual({ provider: 'anthropic', key: 'sk-ant-test' });
251
+ });
252
+ });
253
+ (0, vitest_1.describe)('LLM utilities - getDefaultModel', () => {
254
+ (0, vitest_1.it)('returns default model for OpenAI', () => {
255
+ (0, vitest_1.expect)((0, llm_1.getDefaultModel)('openai')).toBe('gpt-4o');
256
+ });
257
+ (0, vitest_1.it)('returns default model for Anthropic', () => {
258
+ (0, vitest_1.expect)((0, llm_1.getDefaultModel)('anthropic')).toBe('claude-sonnet-4-20250514');
259
+ });
260
+ (0, vitest_1.it)('returns default model for Gemini', () => {
261
+ (0, vitest_1.expect)((0, llm_1.getDefaultModel)('gemini')).toBe('gemini-1.5-pro');
262
+ });
263
+ (0, vitest_1.it)('returns gpt-4o for unknown provider', () => {
264
+ (0, vitest_1.expect)((0, llm_1.getDefaultModel)('unknown')).toBe('gpt-4o');
265
+ });
266
+ });
267
+ (0, vitest_1.describe)('LLM utilities - buildPrompt', () => {
268
+ (0, vitest_1.it)('substitutes single variable', () => {
269
+ const template = 'Analyze this: {{input}}';
270
+ const result = (0, llm_1.buildPrompt)(template, { input: 'Hello world' });
271
+ (0, vitest_1.expect)(result).toContain('Analyze this: Hello world');
272
+ });
273
+ (0, vitest_1.it)('substitutes multiple variables', () => {
274
+ const template = 'Name: {{name}}, Age: {{age}}';
275
+ const result = (0, llm_1.buildPrompt)(template, { name: 'Alice', age: 30 });
276
+ (0, vitest_1.expect)(result).toContain('Name: Alice, Age: 30');
277
+ });
278
+ (0, vitest_1.it)('appends JSON input block', () => {
279
+ const template = 'Process this';
280
+ const result = (0, llm_1.buildPrompt)(template, { key: 'value' });
281
+ (0, vitest_1.expect)(result).toContain('Input:');
282
+ (0, vitest_1.expect)(result).toContain('```json');
283
+ (0, vitest_1.expect)(result).toContain('"key": "value"');
284
+ });
285
+ (0, vitest_1.it)('handles empty input data', () => {
286
+ const template = 'No inputs needed';
287
+ const result = (0, llm_1.buildPrompt)(template, {});
288
+ (0, vitest_1.expect)(result).toBe('No inputs needed');
289
+ });
290
+ (0, vitest_1.it)('handles multiple occurrences of same variable', () => {
291
+ const template = '{{name}} said hello. {{name}} waved goodbye.';
292
+ const result = (0, llm_1.buildPrompt)(template, { name: 'Bob' });
293
+ (0, vitest_1.expect)(result).toContain('Bob said hello. Bob waved goodbye.');
294
+ });
295
+ (0, vitest_1.it)('preserves unused placeholders', () => {
296
+ const template = 'Value: {{exists}}, Missing: {{missing}}';
297
+ const result = (0, llm_1.buildPrompt)(template, { exists: 'here' });
298
+ (0, vitest_1.expect)(result).toContain('Value: here');
299
+ (0, vitest_1.expect)(result).toContain('{{missing}}');
300
+ });
301
+ (0, vitest_1.it)('handles complex nested objects in JSON', () => {
302
+ const template = 'Process data';
303
+ const inputData = {
304
+ nested: { deep: { value: 42 } },
305
+ array: [1, 2, 3],
306
+ };
307
+ const result = (0, llm_1.buildPrompt)(template, inputData);
308
+ (0, vitest_1.expect)(result).toContain('"nested"');
309
+ (0, vitest_1.expect)(result).toContain('"deep"');
310
+ (0, vitest_1.expect)(result).toContain('"array"');
311
+ });
312
+ (0, vitest_1.it)('converts non-string values to strings', () => {
313
+ const template = 'Count: {{count}}, Active: {{active}}';
314
+ const result = (0, llm_1.buildPrompt)(template, { count: 42, active: true });
315
+ (0, vitest_1.expect)(result).toContain('Count: 42');
316
+ (0, vitest_1.expect)(result).toContain('Active: true');
317
+ });
318
+ });
319
+ (0, vitest_1.describe)('LLM utilities - constants', () => {
320
+ (0, vitest_1.it)('has correct provider env vars', () => {
321
+ (0, vitest_1.expect)(llm_1.PROVIDER_ENV_VARS.openai).toBe('OPENAI_API_KEY');
322
+ (0, vitest_1.expect)(llm_1.PROVIDER_ENV_VARS.anthropic).toBe('ANTHROPIC_API_KEY');
323
+ (0, vitest_1.expect)(llm_1.PROVIDER_ENV_VARS.gemini).toBe('GEMINI_API_KEY');
324
+ });
325
+ (0, vitest_1.it)('has correct default models', () => {
326
+ (0, vitest_1.expect)(llm_1.DEFAULT_MODELS.openai).toBe('gpt-4o');
327
+ (0, vitest_1.expect)(llm_1.DEFAULT_MODELS.anthropic).toBe('claude-sonnet-4-20250514');
328
+ (0, vitest_1.expect)(llm_1.DEFAULT_MODELS.gemini).toBe('gemini-1.5-pro');
329
+ });
330
+ });
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSearchCommand = registerSearchCommand;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const output_1 = require("../lib/output");
7
+ const analytics_1 = require("../lib/analytics");
8
+ function registerSearchCommand(program) {
9
+ program
10
+ .command('search')
11
+ .description('Search for public agents')
12
+ .argument('[query]', 'Search query')
13
+ .option('--sort <field>', 'Sort by: stars, recent, name (default: stars)', 'stars')
14
+ .option('--json', 'Output raw JSON')
15
+ .action(async (query, options) => {
16
+ const config = await (0, config_1.getResolvedConfig)();
17
+ let agents;
18
+ if (query) {
19
+ agents = await (0, api_1.searchAgents)(config, query);
20
+ await (0, analytics_1.track)('cli_search', { query });
21
+ }
22
+ else {
23
+ agents = await (0, api_1.listPublicAgents)(config);
24
+ await (0, analytics_1.track)('cli_search');
25
+ }
26
+ // Sort results
27
+ if (options.sort === 'stars') {
28
+ agents.sort((a, b) => (b.stars_count ?? 0) - (a.stars_count ?? 0));
29
+ }
30
+ else if (options.sort === 'recent') {
31
+ agents.sort((a, b) => (b.created_at ?? '').localeCompare(a.created_at ?? ''));
32
+ }
33
+ else {
34
+ agents.sort((a, b) => a.name.localeCompare(b.name));
35
+ }
36
+ if (options.json) {
37
+ (0, output_1.printJson)(agents);
38
+ return;
39
+ }
40
+ if (agents.length === 0) {
41
+ process.stdout.write(query ? 'No agents found matching your search.\n' : 'No public agents found.\n');
42
+ return;
43
+ }
44
+ (0, output_1.printAgentsTable)(agents);
45
+ });
46
+ }
@@ -0,0 +1,141 @@
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.registerSkillCommand = registerSkillCommand;
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 output_1 = require("../lib/output");
14
+ const analytics_1 = require("../lib/analytics");
15
+ const DEFAULT_VERSION = 'v1';
16
+ /**
17
+ * AI tool skill directories.
18
+ *
19
+ * Each AI coding tool has its own directory for skills. When installing a skill,
20
+ * we write to all known directories so the skill works with any tool.
21
+ *
22
+ * TODO: Research and add more AI tool directories as the ecosystem evolves.
23
+ * Known tools to research: Gemini CLI, Aider, OpenCode, Amp, Windsurf, Cline,
24
+ * GitHub Copilot CLI, Codex CLI, Qwen Code, Kimi Code, etc.
25
+ *
26
+ * References:
27
+ * - https://github.com/gotalab/skillport
28
+ * - https://github.com/numman-ali/openskills
29
+ * - https://github.com/skillcreatorai/Ai-Agent-Skills
30
+ */
31
+ const AI_TOOL_SKILL_DIRS = [
32
+ { name: 'Claude Code', path: '.claude/skills' },
33
+ { name: 'Cursor', path: '.cursor/skills' },
34
+ { name: 'Codex', path: '.codex/skills' },
35
+ { name: 'Amp', path: '.agents/skills' },
36
+ { name: 'OpenCode', path: '.opencode/skill' },
37
+ { name: 'Antigravity', path: '.agent/skills' },
38
+ // TODO: Add more as we research them:
39
+ // { name: 'Windsurf', path: '.windsurf/skills' },
40
+ ];
41
+ function parseSkillRef(value) {
42
+ const [ref, versionPart] = value.split('@');
43
+ const version = versionPart?.trim() || DEFAULT_VERSION;
44
+ const segments = ref.split('/');
45
+ if (segments.length === 1) {
46
+ return { skill: segments[0], version };
47
+ }
48
+ if (segments.length === 2) {
49
+ return { org: segments[0], skill: segments[1], version };
50
+ }
51
+ throw new errors_1.CliError('Invalid skill reference. Use org/skill or skill format.');
52
+ }
53
+ function registerSkillCommand(program) {
54
+ const skill = program.command('skill').description('Manage and install skills');
55
+ // orch skill list
56
+ skill
57
+ .command('list')
58
+ .description('List available skills')
59
+ .option('--json', 'Output raw JSON')
60
+ .action(async (options) => {
61
+ const resolved = await (0, config_1.getResolvedConfig)();
62
+ const skills = await (0, api_1.publicRequest)(resolved, '/public/agents?type=skills');
63
+ await (0, analytics_1.track)('cli_skill_list');
64
+ if (options.json) {
65
+ (0, output_1.printJson)(skills);
66
+ return;
67
+ }
68
+ if (!skills.length) {
69
+ process.stdout.write('No skills found.\n');
70
+ return;
71
+ }
72
+ for (const skill of skills) {
73
+ const stars = skill.stars_count ? ` ⭐${skill.stars_count}` : '';
74
+ process.stdout.write(`${skill.org_slug}/${skill.name}@${skill.version}${stars}\n` +
75
+ ` ${skill.description || 'No description'}\n\n`);
76
+ }
77
+ });
78
+ // orch skill install <skill>
79
+ skill
80
+ .command('install <skill>')
81
+ .description('Install skill to local AI tool directories (Claude Code, Cursor, etc.)')
82
+ .option('--global', 'Install to home directory (default: current directory)')
83
+ .action(async (skillRef, options) => {
84
+ const resolved = await (0, config_1.getResolvedConfig)();
85
+ const parsed = parseSkillRef(skillRef);
86
+ const org = parsed.org ?? resolved.defaultOrg;
87
+ if (!org) {
88
+ throw new errors_1.CliError('Missing org. Use org/skill or set default org.');
89
+ }
90
+ // Fetch skill metadata to verify it's a skill
91
+ const skillMeta = await (0, api_1.publicRequest)(resolved, `/public/agents/${org}/${parsed.skill}/${parsed.version}`);
92
+ const skillType = skillMeta.type;
93
+ if (skillType !== 'skill') {
94
+ throw new errors_1.CliError(`${org}/${parsed.skill} is not a skill (type: ${skillType || 'prompt'})`);
95
+ }
96
+ // Download skill content
97
+ const skillData = await (0, api_1.publicRequest)(resolved, `/public/agents/${org}/${parsed.skill}/${parsed.version}/download`);
98
+ if (!skillData.prompt) {
99
+ throw new errors_1.CliError('Skill has no content');
100
+ }
101
+ // Determine base directory (home or current)
102
+ const baseDir = options.global ? os_1.default.homedir() : process.cwd();
103
+ // Build skill content with header
104
+ const skillContent = `# ${skillData.name}
105
+
106
+ ${skillData.description || ''}
107
+
108
+ ---
109
+
110
+ ${skillData.prompt}
111
+ `;
112
+ // Install to all AI tool directories
113
+ const installed = [];
114
+ for (const tool of AI_TOOL_SKILL_DIRS) {
115
+ const skillDir = path_1.default.join(baseDir, tool.path);
116
+ const skillFile = path_1.default.join(skillDir, `${parsed.skill}.md`);
117
+ try {
118
+ await promises_1.default.mkdir(skillDir, { recursive: true });
119
+ await promises_1.default.writeFile(skillFile, skillContent);
120
+ installed.push(tool.name);
121
+ }
122
+ catch (err) {
123
+ // Skip if we can't write (e.g., permission issues)
124
+ process.stderr.write(`Warning: Could not install to ${tool.path}: ${err.message}\n`);
125
+ }
126
+ }
127
+ if (installed.length === 0) {
128
+ throw new errors_1.CliError('Failed to install skill to any directory');
129
+ }
130
+ await (0, analytics_1.track)('cli_skill_install', {
131
+ skill: `${org}/${parsed.skill}`,
132
+ global: Boolean(options.global),
133
+ });
134
+ process.stdout.write(`Installed ${org}/${parsed.skill}@${parsed.version}\n`);
135
+ process.stdout.write(`\nAvailable for:\n`);
136
+ for (const tool of installed) {
137
+ process.stdout.write(` - ${tool}\n`);
138
+ }
139
+ process.stdout.write(`\nLocation: ${options.global ? '~/' : './'}\n`);
140
+ });
141
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerStarCommand = registerStarCommand;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const errors_1 = require("../lib/errors");
7
+ const analytics_1 = require("../lib/analytics");
8
+ function parseAgentRef(ref) {
9
+ // Format: org/agent/version or org/agent (defaults to v1)
10
+ const parts = ref.split('/');
11
+ if (parts.length < 2 || parts.length > 3) {
12
+ throw new errors_1.CliError('Invalid agent reference. Use: org/agent or org/agent/version');
13
+ }
14
+ return {
15
+ org: parts[0],
16
+ name: parts[1],
17
+ version: parts[2] || 'v1',
18
+ };
19
+ }
20
+ function registerStarCommand(program) {
21
+ program
22
+ .command('star')
23
+ .description('Star an agent')
24
+ .argument('<agent>', 'Agent reference (org/name or org/name/version)')
25
+ .option('--remove', 'Remove star instead of adding')
26
+ .action(async (agent, options) => {
27
+ const config = await (0, config_1.getResolvedConfig)();
28
+ const { org, name, version } = parseAgentRef(agent);
29
+ // Get the agent to get its ID
30
+ const agentInfo = await (0, api_1.getPublicAgent)(config, org, name, version);
31
+ if (options.remove) {
32
+ await (0, api_1.unstarAgent)(config, agentInfo.id);
33
+ process.stdout.write(`Removed star from ${org}/${name}/${version}\n`);
34
+ }
35
+ else {
36
+ await (0, api_1.starAgent)(config, agentInfo.id);
37
+ await (0, analytics_1.track)('cli_star', { agent: `${org}/${name}/${version}` });
38
+ process.stdout.write(`Starred ${org}/${name}/${version}\n`);
39
+ }
40
+ });
41
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerWhoamiCommand = registerWhoamiCommand;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ function registerWhoamiCommand(program) {
7
+ program
8
+ .command('whoami')
9
+ .description('Show current user/org info')
10
+ .action(async () => {
11
+ const config = await (0, config_1.getResolvedConfig)();
12
+ const org = await (0, api_1.getOrg)(config);
13
+ process.stdout.write(`Logged in as: ${org.name}\n`);
14
+ process.stdout.write(`Org slug: ${org.slug}\n`);
15
+ process.stdout.write(`API URL: ${config.apiUrl}\n`);
16
+ });
17
+ }
package/dist/index.js ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const Sentry = __importStar(require("@sentry/node"));
38
+ if (process.env.SENTRY_DSN) {
39
+ Sentry.init({ dsn: process.env.SENTRY_DSN });
40
+ }
41
+ const commander_1 = require("commander");
42
+ const commands_1 = require("./commands");
43
+ const errors_1 = require("./lib/errors");
44
+ const analytics_1 = require("./lib/analytics");
45
+ (0, analytics_1.initPostHog)();
46
+ const program = new commander_1.Command();
47
+ program
48
+ .name('orchagent')
49
+ .description('OrchAgent CLI')
50
+ .version('0.1.0');
51
+ (0, commands_1.registerCommands)(program);
52
+ program
53
+ .parseAsync(process.argv)
54
+ .catch(errors_1.exitWithError)
55
+ .finally(() => (0, analytics_1.shutdownPostHog)());
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initPostHog = initPostHog;
4
+ exports.shutdownPostHog = shutdownPostHog;
5
+ exports.track = track;
6
+ const posthog_node_1 = require("posthog-node");
7
+ const config_1 = require("./config");
8
+ let client = null;
9
+ function initPostHog() {
10
+ if (process.env.POSTHOG_API_KEY) {
11
+ client = new posthog_node_1.PostHog(process.env.POSTHOG_API_KEY, {
12
+ host: 'https://us.i.posthog.com',
13
+ });
14
+ }
15
+ }
16
+ async function shutdownPostHog() {
17
+ if (client) {
18
+ await client.shutdown();
19
+ }
20
+ }
21
+ async function track(event, properties) {
22
+ if (!client)
23
+ return;
24
+ const config = await (0, config_1.getResolvedConfig)();
25
+ const distinctId = config.defaultOrg || 'anonymous';
26
+ client.capture({ distinctId, event, properties });
27
+ }