@orchagent/cli 0.3.31 → 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.
@@ -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
+ });
@@ -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('--type <type>', 'Filter by type: agents, skills, all (default: all)', 'all')
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
- // Default to popular when no args
31
- if (!query && !options.popular && !options.recent) {
32
- options.popular = true;
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 (query) {
36
- agents = await (0, api_1.searchAgents)(config, query);
37
- await (0, analytics_1.track)('cli_search', { query, type: options.type });
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
- agents = await (0, api_1.listPublicAgents)(config);
41
- await (0, analytics_1.track)('cli_search', { mode: options.popular ? 'popular' : 'recent', type: options.type });
42
- }
43
- // Filter by type
44
- if (options.type === 'agents') {
45
- agents = agents.filter(a => a.type !== 'skill');
46
- }
47
- else if (options.type === 'skills') {
48
- agents = agents.filter(a => a.type === 'skill');
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
- process.stdout.write(query ? 'No results found matching your search.\n' : 'No public agents found.\n');
72
- process.stdout.write('\nBrowse all agents at: https://orchagent.io/explore\n');
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
- process.stdout.write('\nTip: Run "orchagent info <agent>" to see input schema and details.\n');
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
  }
@@ -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', '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(`\nLocation: ${scope === 'user' ? '~/.claude/skills/ (and other AI tool dirs)' : './<tool>/skills/'}\n`);
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', '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) {