@orchagent/cli 0.3.10 → 0.3.12

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.
@@ -13,6 +13,41 @@ const output_1 = require("../lib/output");
13
13
  const llm_1 = require("../lib/llm");
14
14
  const analytics_1 = require("../lib/analytics");
15
15
  const DEFAULT_VERSION = 'latest';
16
+ // Keys that might indicate local file path references in JSON payloads
17
+ const LOCAL_PATH_KEYS = ['path', 'directory', 'file', 'filepath', 'dir', 'folder', 'local'];
18
+ /**
19
+ * Check if a parsed JSON object contains keys that might reference local filesystem paths.
20
+ * Returns the first matching key found, or undefined if none found.
21
+ */
22
+ function findLocalPathKey(obj) {
23
+ if (typeof obj !== 'object' || obj === null) {
24
+ return undefined;
25
+ }
26
+ const keys = Object.keys(obj);
27
+ for (const key of keys) {
28
+ if (LOCAL_PATH_KEYS.includes(key.toLowerCase())) {
29
+ return key;
30
+ }
31
+ }
32
+ return undefined;
33
+ }
34
+ /**
35
+ * Emit a warning to stderr if the payload contains local path references.
36
+ */
37
+ function warnIfLocalPathReference(jsonBody) {
38
+ try {
39
+ const parsed = JSON.parse(jsonBody);
40
+ const pathKey = findLocalPathKey(parsed);
41
+ if (pathKey) {
42
+ process.stderr.write(`Warning: Your payload contains a local path reference ('${pathKey}').\n` +
43
+ `Remote agents cannot access your local filesystem. The path will be interpreted\n` +
44
+ `by the server, not your local machine. Use --help for more information.\n\n`);
45
+ }
46
+ }
47
+ catch {
48
+ // If parsing fails, skip the warning (the actual error will be thrown later)
49
+ }
50
+ }
16
51
  function parseAgentRef(value) {
17
52
  const [ref, versionPart] = value.split('@');
18
53
  const version = versionPart?.trim() || DEFAULT_VERSION;
@@ -118,6 +153,11 @@ Examples:
118
153
  orch call acme/image-processor photo.jpg --output result.png
119
154
 
120
155
  Note: Use 'call' for server-side execution (requires login), 'run' for local execution.
156
+
157
+ Important: Remote agents cannot access your local filesystem. If your --data payload
158
+ contains keys like 'path', 'directory', 'file', etc., those values will be interpreted
159
+ by the server, not your local machine. To send local files, use the positional file
160
+ argument or --file option instead.
121
161
  `)
122
162
  .action(async (agentRef, file, options) => {
123
163
  // Merge --input alias into --data
@@ -194,6 +234,8 @@ Note: Use 'call' for server-side execution (requires login), 'run' for local exe
194
234
  }
195
235
  // Parse JSON and inject llm_credentials if available
196
236
  const resolvedBody = await resolveJsonBody(options.data);
237
+ // Warn if payload contains local path references
238
+ warnIfLocalPathReference(resolvedBody);
197
239
  if (llmCredentials) {
198
240
  const bodyObj = JSON.parse(resolvedBody);
199
241
  bodyObj.llm_credentials = llmCredentials;
@@ -52,6 +52,13 @@ async function resolveWorkspaceId(config, slug) {
52
52
  const org = await (0, api_1.getOrg)(config);
53
53
  return org.id;
54
54
  }
55
+ // First check if the target slug matches the user's own org
56
+ // This avoids calling /workspaces which requires Clerk user identity
57
+ const org = await (0, api_1.getOrg)(config);
58
+ if (org.slug === targetSlug) {
59
+ return org.id;
60
+ }
61
+ // Only call /workspaces if accessing a different team workspace
55
62
  const response = await (0, api_1.request)(config, 'GET', '/workspaces');
56
63
  const workspace = response.workspaces.find((w) => w.slug === targetSlug);
57
64
  if (!workspace) {
@@ -26,6 +26,7 @@ const install_1 = require("./install");
26
26
  const formats_1 = require("./formats");
27
27
  const update_1 = require("./update");
28
28
  const env_1 = require("./env");
29
+ const list_1 = require("./list");
29
30
  function registerCommands(program) {
30
31
  (0, login_1.registerLoginCommand)(program);
31
32
  (0, whoami_1.registerWhoamiCommand)(program);
@@ -52,4 +53,5 @@ function registerCommands(program) {
52
53
  (0, formats_1.registerFormatsCommand)(program);
53
54
  (0, update_1.registerUpdateCommand)(program);
54
55
  (0, env_1.registerEnvCommand)(program);
56
+ (0, list_1.registerListCommand)(program);
55
57
  }
@@ -14,7 +14,7 @@ const analytics_1 = require("../lib/analytics");
14
14
  const adapters_1 = require("../adapters");
15
15
  const installed_1 = require("../lib/installed");
16
16
  const agents_md_utils_1 = require("../lib/agents-md-utils");
17
- const DEFAULT_VERSION = 'v1';
17
+ const DEFAULT_VERSION = 'latest';
18
18
  function parseAgentRef(value) {
19
19
  const [ref, versionPart] = value.split('@');
20
20
  const version = versionPart?.trim() || DEFAULT_VERSION;
@@ -95,7 +95,7 @@ function registerInstallCommand(program) {
95
95
  if (jsonMode) {
96
96
  result.errors.push('Missing org. Use org/agent format or set default org.');
97
97
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
98
- process.exit(1);
98
+ process.exit(errors_1.ExitCodes.INVALID_INPUT);
99
99
  }
100
100
  throw new errors_1.CliError('Missing org. Use org/agent format or set default org.');
101
101
  }
@@ -111,7 +111,7 @@ function registerInstallCommand(program) {
111
111
  if (jsonMode) {
112
112
  result.errors.push(errMsg);
113
113
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
114
- process.exit(1);
114
+ process.exit(errors_1.ExitCodes.INVALID_INPUT);
115
115
  }
116
116
  throw new errors_1.CliError(errMsg);
117
117
  }
@@ -134,7 +134,7 @@ function registerInstallCommand(program) {
134
134
  if (jsonMode) {
135
135
  result.errors.push(errMsg);
136
136
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
137
- process.exit(1);
137
+ process.exit(errors_1.ExitCodes.INVALID_INPUT);
138
138
  }
139
139
  throw new errors_1.CliError(errMsg);
140
140
  }
@@ -149,7 +149,7 @@ function registerInstallCommand(program) {
149
149
  if (jsonMode) {
150
150
  result.errors.push(err instanceof Error ? err.message : String(err));
151
151
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
152
- process.exit(1);
152
+ process.exit(errors_1.ExitCodes.NOT_FOUND);
153
153
  }
154
154
  throw err;
155
155
  }
@@ -262,11 +262,11 @@ function registerInstallCommand(program) {
262
262
  if (jsonMode) {
263
263
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
264
264
  if (!result.success) {
265
- process.exit(1);
265
+ process.exit(errors_1.ExitCodes.GENERAL_ERROR);
266
266
  }
267
267
  }
268
268
  else if (!result.success && !options.dryRun) {
269
- process.exit(1);
269
+ process.exit(errors_1.ExitCodes.GENERAL_ERROR);
270
270
  }
271
271
  });
272
272
  }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerListCommand = registerListCommand;
4
+ const installed_1 = require("../lib/installed");
5
+ const output_1 = require("../lib/output");
6
+ function registerListCommand(program) {
7
+ program
8
+ .command('list')
9
+ .alias('ls')
10
+ .description('List installed agents and skills')
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')
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
+ }
32
+ const installed = await (0, installed_1.getInstalled)();
33
+ if (options.json) {
34
+ (0, output_1.printJson)(installed);
35
+ return;
36
+ }
37
+ if (installed.length === 0) {
38
+ process.stdout.write('No agents installed.\n');
39
+ process.stdout.write('Install agents with: orch install <agent>\n');
40
+ return;
41
+ }
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
+ }
67
+ }
68
+ });
69
+ }
@@ -153,6 +153,11 @@ 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
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';
160
+ }
156
161
  const readline = await Promise.resolve().then(() => __importStar(require('readline')));
157
162
  const rl = readline.createInterface({
158
163
  input: process.stdin,
@@ -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,8 +44,9 @@ 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
- const DEFAULT_VERSION = 'v1';
49
+ const DEFAULT_VERSION = 'latest';
16
50
  /**
17
51
  * AI tool skill directories.
18
52
  *
@@ -21,7 +55,7 @@ const DEFAULT_VERSION = 'v1';
21
55
  *
22
56
  * TODO: Research and add more AI tool directories as the ecosystem evolves.
23
57
  * Known tools to research: Gemini CLI, Aider, OpenCode, Amp, Windsurf, Cline,
24
- * GitHub Copilot CLI, Codex CLI, Qwen Code, Kimi Code, etc.
58
+ * GitHub Copilot CLI, Qwen Code, Kimi Code, etc.
25
59
  *
26
60
  * References:
27
61
  * - https://github.com/gotalab/skillport
@@ -31,7 +65,6 @@ const DEFAULT_VERSION = 'v1';
31
65
  const AI_TOOL_SKILL_DIRS = [
32
66
  { name: 'Claude Code', projectPath: '.claude/skills', userPath: '.claude/skills' },
33
67
  { name: 'Cursor', projectPath: '.cursor/skills', userPath: '.cursor/skills' },
34
- { name: 'Codex', projectPath: '.codex/skills', userPath: '.codex/skills' },
35
68
  { name: 'Amp', projectPath: '.agents/skills', userPath: '.agents/skills' },
36
69
  { name: 'OpenCode', projectPath: '.opencode/skill', userPath: '.opencode/skill' },
37
70
  { name: 'Antigravity', projectPath: '.agent/skills', userPath: '.agent/skills' },
@@ -164,6 +197,7 @@ Instructions and guidance for AI agents...
164
197
  .option('--scope <scope>', 'Install scope: user or project', 'project')
165
198
  .option('--dry-run', 'Show what would be installed without making changes')
166
199
  .option('--format <formats>', 'Comma-separated format IDs (e.g., claude-code,cursor)')
200
+ .option('--all-formats', 'Install to all supported AI tool directories')
167
201
  .option('--json', 'Output result as JSON (for automation/tooling)')
168
202
  .action(async (skillRef, options) => {
169
203
  const jsonMode = options.json === true;
@@ -194,7 +228,7 @@ Instructions and guidance for AI agents...
194
228
  if (jsonMode) {
195
229
  result.errors.push(errMsg);
196
230
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
197
- process.exit(1);
231
+ process.exit(errors_1.ExitCodes.INVALID_INPUT);
198
232
  }
199
233
  throw new errors_1.CliError(errMsg);
200
234
  }
@@ -212,10 +246,48 @@ Instructions and guidance for AI agents...
212
246
  }
213
247
  }
214
248
  }
215
- // Determine target directories
216
- const toolDirs = targetFormats.length > 0
217
- ? targetFormats.map(f => config_1.FORMAT_SKILL_DIRS[f])
218
- : 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
+ }
219
291
  const parsed = parseSkillRef(skillRef);
220
292
  const org = parsed.org ?? resolved.defaultOrg;
221
293
  if (!org) {
@@ -223,7 +295,7 @@ Instructions and guidance for AI agents...
223
295
  if (jsonMode) {
224
296
  result.errors.push(errMsg);
225
297
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
226
- process.exit(1);
298
+ process.exit(errors_1.ExitCodes.INVALID_INPUT);
227
299
  }
228
300
  throw new errors_1.CliError(errMsg);
229
301
  }
@@ -238,7 +310,7 @@ Instructions and guidance for AI agents...
238
310
  if (jsonMode) {
239
311
  result.errors.push(err instanceof Error ? err.message : String(err));
240
312
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
241
- process.exit(1);
313
+ process.exit(errors_1.ExitCodes.NOT_FOUND);
242
314
  }
243
315
  throw err;
244
316
  }
@@ -247,7 +319,7 @@ Instructions and guidance for AI agents...
247
319
  if (jsonMode) {
248
320
  result.errors.push(errMsg);
249
321
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
250
- process.exit(1);
322
+ process.exit(errors_1.ExitCodes.INVALID_INPUT);
251
323
  }
252
324
  throw new errors_1.CliError('Skill has no content.\n\n' +
253
325
  'The skill exists but has an empty prompt. This may be a publishing issue.\n' +
@@ -269,7 +341,8 @@ ${skillData.prompt}
269
341
  if (options.dryRun) {
270
342
  log(`Would install ${org}/${parsed.skill}@${parsed.version}\n\n`);
271
343
  log(`Target directories (scope: ${scope}):\n`);
272
- for (const tool of toolDirs) {
344
+ for (const formatId of targetFormats) {
345
+ const tool = config_1.FORMAT_SKILL_DIRS[formatId];
273
346
  const baseDir = scope === 'user' ? os_1.default.homedir() : process.cwd();
274
347
  const toolPath = scope === 'user' ? tool.userPath : tool.projectPath;
275
348
  const skillDir = path_1.default.join(baseDir, toolPath);
@@ -286,7 +359,8 @@ ${skillData.prompt}
286
359
  }
287
360
  // Install to target AI tool directories
288
361
  const installed = [];
289
- for (const tool of toolDirs) {
362
+ for (const formatId of targetFormats) {
363
+ const tool = config_1.FORMAT_SKILL_DIRS[formatId];
290
364
  const baseDir = scope === 'user' ? os_1.default.homedir() : process.cwd();
291
365
  const toolPath = scope === 'user' ? tool.userPath : tool.projectPath;
292
366
  const skillDir = path_1.default.join(baseDir, toolPath);
@@ -296,6 +370,18 @@ ${skillData.prompt}
296
370
  await promises_1.default.writeFile(skillFile, skillContent);
297
371
  installed.push(tool.name);
298
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);
299
385
  }
300
386
  catch (err) {
301
387
  // Skip if we can't write (e.g., permission issues)
@@ -309,7 +395,7 @@ ${skillData.prompt}
309
395
  if (jsonMode) {
310
396
  result.errors.push(errMsg);
311
397
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
312
- process.exit(1);
398
+ process.exit(errors_1.ExitCodes.GENERAL_ERROR);
313
399
  }
314
400
  throw new errors_1.CliError(errMsg);
315
401
  }
@@ -336,4 +422,82 @@ ${skillData.prompt}
336
422
  log(`\nLocation: ${scope === 'user' ? '~/' : './'}\n`);
337
423
  }
338
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
+ });
339
503
  }
@@ -110,7 +110,10 @@ const DEFAULT_EXCLUDES = [
110
110
  * @returns Promise that resolves with bundle metadata
111
111
  */
112
112
  async function createCodeBundle(sourceDir, outputPath, options = {}) {
113
- const excludePatterns = [...DEFAULT_EXCLUDES, ...(options.exclude || [])];
113
+ // Build exclude patterns, but remove any that are in the include list
114
+ const includeSet = new Set(options.include || []);
115
+ const excludePatterns = [...DEFAULT_EXCLUDES, ...(options.exclude || [])]
116
+ .filter(pattern => !includeSet.has(pattern));
114
117
  // Verify source directory exists
115
118
  const stat = await promises_1.default.stat(sourceDir);
116
119
  if (!stat.isDirectory()) {
@@ -50,12 +50,11 @@ const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.orchagent');
50
50
  const CONFIG_PATH = path_1.default.join(CONFIG_DIR, 'config.json');
51
51
  const DEFAULT_API_URL = 'https://api.orchagent.io';
52
52
  // Valid format IDs for multi-format agent export
53
- exports.VALID_FORMAT_IDS = ['claude-code', 'cursor', 'codex', 'amp', 'opencode', 'antigravity'];
53
+ exports.VALID_FORMAT_IDS = ['claude-code', 'cursor', 'amp', 'opencode', 'antigravity'];
54
54
  // Map format IDs to skill directories (used by skill install and agent install)
55
55
  exports.FORMAT_SKILL_DIRS = {
56
56
  'claude-code': { name: 'Claude Code', projectPath: '.claude/skills', userPath: '.claude/skills' },
57
57
  'cursor': { name: 'Cursor', projectPath: '.cursor/skills', userPath: '.cursor/skills' },
58
- 'codex': { name: 'Codex', projectPath: '.codex/skills', userPath: '.codex/skills' },
59
58
  'amp': { name: 'Amp', projectPath: '.agents/skills', userPath: '.agents/skills' },
60
59
  'opencode': { name: 'OpenCode', projectPath: '.opencode/skill', userPath: '.opencode/skill' },
61
60
  'antigravity': { name: 'Antigravity', projectPath: '.agent/skills', userPath: '.agent/skills' },
@@ -36,9 +36,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.NetworkError = exports.ExitCodes = exports.CliError = void 0;
37
37
  exports.formatError = formatError;
38
38
  exports.exitWithError = exitWithError;
39
+ exports.mapHttpStatusToExitCode = mapHttpStatusToExitCode;
39
40
  exports.jsonInputError = jsonInputError;
40
41
  const Sentry = __importStar(require("@sentry/node"));
41
42
  const analytics_1 = require("./analytics");
43
+ const api_1 = require("./api");
42
44
  class CliError extends Error {
43
45
  exitCode;
44
46
  cause;
@@ -73,6 +75,10 @@ async function exitWithError(err) {
73
75
  if (err instanceof CliError) {
74
76
  process.exit(err.exitCode);
75
77
  }
78
+ // Handle API errors with proper exit codes
79
+ if (err instanceof api_1.ApiError) {
80
+ process.exit(mapHttpStatusToExitCode(err.status));
81
+ }
76
82
  process.exit(1);
77
83
  }
78
84
  exports.ExitCodes = {
@@ -87,6 +93,22 @@ exports.ExitCodes = {
87
93
  SERVER_ERROR: 8,
88
94
  NETWORK_ERROR: 9,
89
95
  };
96
+ /**
97
+ * Map HTTP status codes to CLI exit codes.
98
+ */
99
+ function mapHttpStatusToExitCode(status) {
100
+ if (status === 401)
101
+ return exports.ExitCodes.AUTH_ERROR;
102
+ if (status === 403)
103
+ return exports.ExitCodes.PERMISSION_DENIED;
104
+ if (status === 404)
105
+ return exports.ExitCodes.NOT_FOUND;
106
+ if (status === 429)
107
+ return exports.ExitCodes.RATE_LIMITED;
108
+ if (status >= 500 && status <= 599)
109
+ return exports.ExitCodes.SERVER_ERROR;
110
+ return exports.ExitCodes.GENERAL_ERROR;
111
+ }
90
112
  class NetworkError extends CliError {
91
113
  constructor(url, cause) {
92
114
  const host = new URL(url).host;
@@ -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.10",
3
+ "version": "0.3.12",
4
4
  "description": "Command-line interface for the orchagent AI agent marketplace",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",