@orchagent/cli 0.3.11 → 0.3.13

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.
@@ -7,9 +7,28 @@ function registerListCommand(program) {
7
7
  program
8
8
  .command('list')
9
9
  .alias('ls')
10
- .description('List installed agents')
10
+ .description('List installed agents and skills')
11
11
  .option('--json', 'Output as JSON')
12
+ .option('-v, --verbose', 'Show format details for each installation')
13
+ .option('--verify', 'Check tracked files exist and remove orphaned entries')
12
14
  .action(async (options) => {
15
+ if (options.verify) {
16
+ const { valid, orphaned } = await (0, installed_1.verifyInstalled)(true); // Remove orphaned entries
17
+ if (orphaned.length === 0) {
18
+ process.stdout.write('All tracked installations verified. No orphaned entries found.\n');
19
+ }
20
+ else {
21
+ process.stdout.write(`Removed ${orphaned.length} orphaned entries:\n`);
22
+ for (const entry of orphaned) {
23
+ process.stdout.write(` - ${entry.agent}@${entry.version} (${entry.format})\n`);
24
+ process.stdout.write(` Missing: ${entry.path}\n`);
25
+ }
26
+ }
27
+ if (valid.length > 0) {
28
+ process.stdout.write(`\n${valid.length} valid installations remain.\n`);
29
+ }
30
+ return;
31
+ }
13
32
  const installed = await (0, installed_1.getInstalled)();
14
33
  if (options.json) {
15
34
  (0, output_1.printJson)(installed);
@@ -20,9 +39,31 @@ function registerListCommand(program) {
20
39
  process.stdout.write('Install agents with: orch install <agent>\n');
21
40
  return;
22
41
  }
23
- process.stdout.write('Installed agents:\n\n');
24
- for (const agent of installed) {
25
- process.stdout.write(` ${agent.agent}@${agent.version}\n`);
42
+ if (options.verbose) {
43
+ // Verbose mode: show all entries with format details
44
+ process.stdout.write('Installed agents:\n\n');
45
+ for (const agent of installed) {
46
+ process.stdout.write(` ${agent.agent}@${agent.version} (${agent.format}, ${agent.scope})\n`);
47
+ process.stdout.write(` Path: ${agent.path}\n`);
48
+ }
49
+ }
50
+ else {
51
+ // Default: deduplicate by agent@version
52
+ const seen = new Map();
53
+ for (const agent of installed) {
54
+ const key = `${agent.agent}@${agent.version}`;
55
+ if (!seen.has(key)) {
56
+ seen.set(key, agent);
57
+ }
58
+ }
59
+ process.stdout.write('Installed agents:\n\n');
60
+ for (const [, agent] of seen) {
61
+ process.stdout.write(` ${agent.agent}@${agent.version}\n`);
62
+ }
63
+ // Hint about verbose mode if there are duplicates
64
+ if (installed.length > seen.size) {
65
+ process.stdout.write(`\n(${installed.length - seen.size} format duplicates hidden, use --verbose to show)\n`);
66
+ }
26
67
  }
27
68
  });
28
69
  }
@@ -153,10 +153,10 @@ async function checkDependencies(config, dependencies) {
153
153
  return results;
154
154
  }
155
155
  async function promptUserForDeps(depStatuses) {
156
- // In non-interactive mode (CI, piped input), exit gracefully with helpful message
156
+ // In non-interactive mode (CI, piped input), skip deps by default and let agent run
157
157
  if (!process.stdin.isTTY) {
158
- process.stderr.write('Non-interactive mode detected. Use --with-deps to include dependencies or --no-deps to skip them.\n');
159
- return 'cancel';
158
+ process.stderr.write('Non-interactive mode: skipping dependencies (use --with-deps to include them).\n');
159
+ return 'local'; // Skip deps, let agent run
160
160
  }
161
161
  const readline = await Promise.resolve().then(() => __importStar(require('readline')));
162
162
  const rl = readline.createInterface({
@@ -1,4 +1,37 @@
1
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -11,6 +44,7 @@ const config_1 = require("../lib/config");
11
44
  const api_1 = require("../lib/api");
12
45
  const errors_1 = require("../lib/errors");
13
46
  const analytics_1 = require("../lib/analytics");
47
+ const installed_1 = require("../lib/installed");
14
48
  const package_json_1 = __importDefault(require("../../package.json"));
15
49
  const DEFAULT_VERSION = 'latest';
16
50
  /**
@@ -163,6 +197,7 @@ Instructions and guidance for AI agents...
163
197
  .option('--scope <scope>', 'Install scope: user or project', 'project')
164
198
  .option('--dry-run', 'Show what would be installed without making changes')
165
199
  .option('--format <formats>', 'Comma-separated format IDs (e.g., claude-code,cursor)')
200
+ .option('--all-formats', 'Install to all supported AI tool directories')
166
201
  .option('--json', 'Output result as JSON (for automation/tooling)')
167
202
  .action(async (skillRef, options) => {
168
203
  const jsonMode = options.json === true;
@@ -211,10 +246,48 @@ Instructions and guidance for AI agents...
211
246
  }
212
247
  }
213
248
  }
214
- // Determine target directories
215
- const toolDirs = targetFormats.length > 0
216
- ? targetFormats.map(f => config_1.FORMAT_SKILL_DIRS[f])
217
- : AI_TOOL_SKILL_DIRS; // fallback to all
249
+ // If --all-formats explicitly requested, use all formats
250
+ if (options.allFormats) {
251
+ targetFormats = config_1.VALID_FORMAT_IDS.slice();
252
+ }
253
+ // If no formats configured, prompt user (TTY) or use default (non-TTY)
254
+ if (targetFormats.length === 0) {
255
+ if (process.stdout.isTTY && !jsonMode) {
256
+ // Interactive prompt using readline
257
+ const readline = await Promise.resolve().then(() => __importStar(require('readline/promises')));
258
+ const rl = readline.createInterface({
259
+ input: process.stdin,
260
+ output: process.stdout,
261
+ });
262
+ log('Which AI tools do you use?\n');
263
+ config_1.VALID_FORMAT_IDS.forEach((f, i) => {
264
+ log(` ${i + 1}. ${config_1.FORMAT_SKILL_DIRS[f].name}\n`);
265
+ });
266
+ log('\nEnter numbers separated by commas (e.g., 1,2) or "all": ');
267
+ const answer = await rl.question('');
268
+ rl.close();
269
+ if (answer.toLowerCase() === 'all') {
270
+ targetFormats = config_1.VALID_FORMAT_IDS.slice();
271
+ }
272
+ else {
273
+ const indices = answer.split(',').map(s => parseInt(s.trim(), 10) - 1);
274
+ const validIndices = indices.filter(i => i >= 0 && i < config_1.VALID_FORMAT_IDS.length);
275
+ if (validIndices.length === 0) {
276
+ throw new errors_1.CliError('No valid tools selected. Please run the command again.');
277
+ }
278
+ targetFormats = validIndices.map(i => config_1.VALID_FORMAT_IDS[i]);
279
+ }
280
+ // Save as default for future
281
+ await (0, config_1.setDefaultFormats)(targetFormats);
282
+ log(`\nSaved as your default formats for future installs.\n\n`);
283
+ }
284
+ else {
285
+ // Non-TTY fallback
286
+ targetFormats = ['claude-code'];
287
+ log('Note: No default formats configured. Installing to Claude Code only.\n');
288
+ log('Run "orchagent config set-formats" to configure defaults.\n\n');
289
+ }
290
+ }
218
291
  const parsed = parseSkillRef(skillRef);
219
292
  const org = parsed.org ?? resolved.defaultOrg;
220
293
  if (!org) {
@@ -268,7 +341,8 @@ ${skillData.prompt}
268
341
  if (options.dryRun) {
269
342
  log(`Would install ${org}/${parsed.skill}@${parsed.version}\n\n`);
270
343
  log(`Target directories (scope: ${scope}):\n`);
271
- for (const tool of toolDirs) {
344
+ for (const formatId of targetFormats) {
345
+ const tool = config_1.FORMAT_SKILL_DIRS[formatId];
272
346
  const baseDir = scope === 'user' ? os_1.default.homedir() : process.cwd();
273
347
  const toolPath = scope === 'user' ? tool.userPath : tool.projectPath;
274
348
  const skillDir = path_1.default.join(baseDir, toolPath);
@@ -285,7 +359,8 @@ ${skillData.prompt}
285
359
  }
286
360
  // Install to target AI tool directories
287
361
  const installed = [];
288
- for (const tool of toolDirs) {
362
+ for (const formatId of targetFormats) {
363
+ const tool = config_1.FORMAT_SKILL_DIRS[formatId];
289
364
  const baseDir = scope === 'user' ? os_1.default.homedir() : process.cwd();
290
365
  const toolPath = scope === 'user' ? tool.userPath : tool.projectPath;
291
366
  const skillDir = path_1.default.join(baseDir, toolPath);
@@ -295,6 +370,18 @@ ${skillData.prompt}
295
370
  await promises_1.default.writeFile(skillFile, skillContent);
296
371
  installed.push(tool.name);
297
372
  result.files.push({ path: skillFile, tool: tool.name });
373
+ // Track the installation
374
+ const installedEntry = {
375
+ agent: `${org}/${parsed.skill}`,
376
+ version: skillData.version,
377
+ format: formatId,
378
+ scope: scope,
379
+ path: skillFile,
380
+ installedAt: new Date().toISOString(),
381
+ adapterVersion: package_json_1.default.version,
382
+ contentHash: (0, installed_1.computeHash)(skillContent),
383
+ };
384
+ await (0, installed_1.trackInstall)(installedEntry);
298
385
  }
299
386
  catch (err) {
300
387
  // Skip if we can't write (e.g., permission issues)
@@ -335,4 +422,82 @@ ${skillData.prompt}
335
422
  log(`\nLocation: ${scope === 'user' ? '~/' : './'}\n`);
336
423
  }
337
424
  });
425
+ // orch skill uninstall <skill>
426
+ skill
427
+ .command('uninstall <skill>')
428
+ .description('Uninstall skill from local AI tool directories')
429
+ .option('--global', 'Uninstall from home directory (default: current directory)')
430
+ .option('--scope <scope>', 'Uninstall scope: user or project', 'project')
431
+ .option('--json', 'Output result as JSON')
432
+ .action(async (skillRef, options) => {
433
+ const jsonMode = options.json === true;
434
+ const log = (msg) => { if (!jsonMode)
435
+ process.stdout.write(msg); };
436
+ const result = {
437
+ success: false,
438
+ skill: '',
439
+ scope: '',
440
+ removed: [],
441
+ errors: [],
442
+ };
443
+ const resolved = await (0, config_1.getResolvedConfig)();
444
+ const parsed = parseSkillRef(skillRef);
445
+ const org = parsed.org ?? resolved.defaultOrg;
446
+ if (!org) {
447
+ const errMsg = 'Missing org. Use org/skill or set default org.';
448
+ if (jsonMode) {
449
+ result.errors.push(errMsg);
450
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
451
+ process.exit(errors_1.ExitCodes.INVALID_INPUT);
452
+ }
453
+ throw new errors_1.CliError(errMsg);
454
+ }
455
+ result.skill = `${org}/${parsed.skill}`;
456
+ // Determine scope
457
+ const scope = options.global ? 'user' : (options.scope || 'project');
458
+ result.scope = scope;
459
+ // Remove from all AI tool directories
460
+ for (const formatId of config_1.VALID_FORMAT_IDS) {
461
+ const tool = config_1.FORMAT_SKILL_DIRS[formatId];
462
+ const baseDir = scope === 'user' ? os_1.default.homedir() : process.cwd();
463
+ const toolPath = scope === 'user' ? tool.userPath : tool.projectPath;
464
+ const skillFile = path_1.default.join(baseDir, toolPath, `${parsed.skill}.md`);
465
+ try {
466
+ await promises_1.default.unlink(skillFile);
467
+ result.removed.push({ path: skillFile, tool: tool.name });
468
+ // Untrack from installed.json
469
+ await (0, installed_1.untrackInstall)(`${org}/${parsed.skill}`, formatId, skillFile);
470
+ }
471
+ catch (err) {
472
+ if (err.code !== 'ENOENT') {
473
+ result.errors.push(`Failed to remove ${skillFile}: ${err.message}`);
474
+ }
475
+ // ENOENT is fine - file doesn't exist
476
+ }
477
+ }
478
+ if (result.removed.length === 0) {
479
+ const errMsg = `Skill '${org}/${parsed.skill}' not found in ${scope === 'user' ? 'home' : 'project'} directory`;
480
+ if (jsonMode) {
481
+ result.errors.push(errMsg);
482
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
483
+ process.exit(errors_1.ExitCodes.NOT_FOUND);
484
+ }
485
+ throw new errors_1.CliError(errMsg);
486
+ }
487
+ result.success = true;
488
+ await (0, analytics_1.track)('cli_skill_uninstall', {
489
+ skill: `${org}/${parsed.skill}`,
490
+ scope,
491
+ });
492
+ if (jsonMode) {
493
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
494
+ }
495
+ else {
496
+ log(`Uninstalled ${org}/${parsed.skill}\n`);
497
+ log(`\nRemoved from:\n`);
498
+ for (const item of result.removed) {
499
+ log(` - ${item.tool}\n`);
500
+ }
501
+ }
502
+ });
338
503
  }
package/dist/lib/api.js CHANGED
@@ -205,7 +205,13 @@ async function request(config, method, path, options = {}) {
205
205
  return (await response.json());
206
206
  }
207
207
  async function publicRequest(config, path) {
208
- const response = await safeFetchWithRetry(buildUrl(config.apiUrl, path));
208
+ // Pass API key if available - allows server to skip IP-based rate limiting
209
+ // for authenticated users while still using the public endpoint for data
210
+ const headers = {};
211
+ if (config.apiKey) {
212
+ headers['Authorization'] = `Bearer ${config.apiKey}`;
213
+ }
214
+ const response = await safeFetchWithRetry(buildUrl(config.apiUrl, path), { headers });
209
215
  if (!response.ok) {
210
216
  throw await parseError(response);
211
217
  }
@@ -11,6 +11,7 @@ exports.getInstalled = getInstalled;
11
11
  exports.getInstalledByFormat = getInstalledByFormat;
12
12
  exports.checkModified = checkModified;
13
13
  exports.untrackInstall = untrackInstall;
14
+ exports.verifyInstalled = verifyInstalled;
14
15
  const promises_1 = __importDefault(require("fs/promises"));
15
16
  const path_1 = __importDefault(require("path"));
16
17
  const os_1 = __importDefault(require("os"));
@@ -92,3 +93,26 @@ async function untrackInstall(agentRef, format, filePath) {
92
93
  data.installed = data.installed.filter(i => !(i.agent === agentRef && i.format === format && i.path === filePath));
93
94
  await saveInstalled(data);
94
95
  }
96
+ /**
97
+ * Verify installed entries and optionally remove orphaned ones
98
+ * Returns { valid: InstalledAgent[], orphaned: InstalledAgent[] }
99
+ */
100
+ async function verifyInstalled(removeOrphaned = false) {
101
+ const data = await loadInstalled();
102
+ const valid = [];
103
+ const orphaned = [];
104
+ for (const agent of data.installed) {
105
+ try {
106
+ await promises_1.default.access(agent.path);
107
+ valid.push(agent);
108
+ }
109
+ catch {
110
+ orphaned.push(agent);
111
+ }
112
+ }
113
+ if (removeOrphaned && orphaned.length > 0) {
114
+ data.installed = valid;
115
+ await saveInstalled(data);
116
+ }
117
+ return { valid, orphaned };
118
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "description": "Command-line interface for the orchagent AI agent marketplace",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",