@orchagent/cli 0.3.31 → 0.3.34

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.
@@ -12,6 +12,7 @@ const api_1 = require("../lib/api");
12
12
  const errors_1 = require("../lib/errors");
13
13
  const analytics_1 = require("../lib/analytics");
14
14
  const adapters_1 = require("../adapters");
15
+ const skill_resolve_1 = require("../lib/skill-resolve");
15
16
  const installed_1 = require("../lib/installed");
16
17
  const agents_md_utils_1 = require("../lib/agents-md-utils");
17
18
  const pricing_1 = require("../lib/pricing");
@@ -123,7 +124,7 @@ function registerInstallCommand(program) {
123
124
  .command('install <agent>')
124
125
  .description('Install agent as sub-agent (Claude Code, Cursor, etc.)')
125
126
  .option('--format <formats>', 'Comma-separated format IDs (e.g., claude-code,cursor)')
126
- .option('--scope <scope>', 'Install scope: user (home dir) or project (current dir)', 'user')
127
+ .option('--scope <scope>', 'Install scope: user (home dir) or project (current dir)')
127
128
  .option('--dry-run', 'Show what would be installed without making changes')
128
129
  .option('--json', 'Output result as JSON (for automation/tooling)')
129
130
  .addHelpText('after', `
@@ -187,8 +188,8 @@ Note: Paid agents cannot be installed locally - they run on server only.
187
188
  }
188
189
  }
189
190
  result.formats = targetFormats;
190
- // Validate scope
191
- let scope = options.scope;
191
+ // Resolve scope: CLI flag > config default > fallback to 'user'
192
+ let scope = (options.scope ?? await (0, config_1.getDefaultScope)() ?? 'user');
192
193
  if (scope !== 'user' && scope !== 'project') {
193
194
  const errMsg = 'Scope must be "user" or "project"';
194
195
  if (jsonMode) {
@@ -213,6 +214,18 @@ Note: Paid agents cannot be installed locally - they run on server only.
213
214
  }
214
215
  throw err;
215
216
  }
217
+ // Resolve default skills if present
218
+ if (agent.default_skills && agent.default_skills.length > 0) {
219
+ log(`Resolving ${agent.default_skills.length} bundled skill(s)...\n`);
220
+ const skills = await (0, skill_resolve_1.resolveSkills)(resolved, agent.default_skills, (warning) => {
221
+ result.warnings.push(warning);
222
+ logErr(`Warning: ${warning}\n`);
223
+ });
224
+ if (skills.length > 0) {
225
+ agent.resolvedSkills = skills;
226
+ log(`Bundled ${skills.length} skill(s): ${skills.map(s => s.name).join(', ')}\n`);
227
+ }
228
+ }
216
229
  // Install for each format
217
230
  let filesWritten = 0;
218
231
  for (const formatId of targetFormats) {
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractTemplateVariables = extractTemplateVariables;
7
+ exports.deriveInputSchema = deriveInputSchema;
6
8
  exports.registerPublishCommand = registerPublishCommand;
7
9
  const promises_1 = __importDefault(require("fs/promises"));
8
10
  const path_1 = __importDefault(require("path"));
@@ -14,6 +16,44 @@ const api_1 = require("../lib/api");
14
16
  const errors_1 = require("../lib/errors");
15
17
  const analytics_1 = require("../lib/analytics");
16
18
  const bundle_1 = require("../lib/bundle");
19
+ /**
20
+ * Extract template placeholders from a prompt template.
21
+ * Matches double-brace patterns like {{variable}}.
22
+ * Returns unique variable names in order of first appearance.
23
+ */
24
+ function extractTemplateVariables(template) {
25
+ const seen = new Set();
26
+ const result = [];
27
+ // Match double-brace template variables: two opening braces, word chars, two closing braces
28
+ const pattern = /\{\{(\w+)\}\}/g;
29
+ let match;
30
+ while ((match = pattern.exec(template)) !== null) {
31
+ const name = match[1];
32
+ if (!seen.has(name)) {
33
+ seen.add(name);
34
+ result.push(name);
35
+ }
36
+ }
37
+ return result;
38
+ }
39
+ /**
40
+ * Derive a JSON Schema input object from template variable names.
41
+ * Each variable becomes a required string property.
42
+ */
43
+ function deriveInputSchema(variables) {
44
+ const properties = {};
45
+ for (const name of variables) {
46
+ properties[name] = {
47
+ type: 'string',
48
+ description: `Value for the ${name} template variable`,
49
+ };
50
+ }
51
+ return {
52
+ type: 'object',
53
+ properties,
54
+ required: [...variables],
55
+ };
56
+ }
17
57
  /**
18
58
  * Handle security flagged error response (422 with error: 'content_flagged')
19
59
  * Returns true if the error was handled, false otherwise
@@ -273,6 +313,7 @@ function registerPublishCommand(program) {
273
313
  const price = parseFloat(options.price);
274
314
  process.stdout.write(`Pricing: $${price.toFixed(2)} USD per call\n`);
275
315
  }
316
+ process.stdout.write(`\nView analytics and usage: https://orchagent.io/dashboard\n`);
276
317
  }
277
318
  catch (err) {
278
319
  if (handleSecurityFlaggedError(err)) {
@@ -338,12 +379,14 @@ function registerPublishCommand(program) {
338
379
  // Read schemas
339
380
  let inputSchema;
340
381
  let outputSchema;
382
+ let schemaFromFile = false;
341
383
  const schemaPath = path_1.default.join(cwd, 'schema.json');
342
384
  try {
343
385
  const raw = await promises_1.default.readFile(schemaPath, 'utf-8');
344
386
  const schemas = JSON.parse(raw);
345
387
  inputSchema = schemas.input;
346
388
  outputSchema = schemas.output;
389
+ schemaFromFile = true;
347
390
  }
348
391
  catch (err) {
349
392
  // Schema is optional
@@ -351,6 +394,33 @@ function registerPublishCommand(program) {
351
394
  throw new errors_1.CliError(`Failed to read schema.json: ${err}`);
352
395
  }
353
396
  }
397
+ // For prompt agents, derive input schema from template variables if needed
398
+ if (prompt && (manifest.type === 'prompt' || manifest.type === 'skill')) {
399
+ const templateVars = extractTemplateVariables(prompt);
400
+ if (templateVars.length > 0) {
401
+ if (!schemaFromFile) {
402
+ // No schema.json provided - auto-generate from template variables
403
+ inputSchema = deriveInputSchema(templateVars);
404
+ }
405
+ else if (inputSchema && 'properties' in inputSchema) {
406
+ // schema.json exists - check for mismatches with template variables
407
+ const schemaProps = Object.keys(inputSchema.properties || {});
408
+ const missing = templateVars.filter(v => !schemaProps.includes(v));
409
+ const extra = schemaProps.filter(p => !templateVars.includes(p));
410
+ if (missing.length > 0 || extra.length > 0) {
411
+ const parts = [];
412
+ if (missing.length > 0) {
413
+ parts.push(`template uses {{${missing.join('}}, {{')}}} but schema.json doesn't define ${missing.join(', ')}`);
414
+ }
415
+ if (extra.length > 0) {
416
+ parts.push(`schema.json defines ${extra.join(', ')} but template doesn't use {{${extra.join('}}, {{')}}}`);
417
+ }
418
+ process.stderr.write(chalk_1.default.yellow(`Warning: Schema mismatch - ${parts.join('; ')}.\n`));
419
+ process.stderr.write(chalk_1.default.yellow(` Consider updating schema.json to match your prompt.md template variables.\n`));
420
+ }
421
+ }
422
+ }
423
+ }
354
424
  // For code-based agents, either --url is required OR we bundle the code
355
425
  let agentUrl = options.url;
356
426
  let shouldUploadBundle = false;
@@ -393,10 +463,14 @@ function registerPublishCommand(program) {
393
463
  // Prompt agent validations
394
464
  const promptBytes = prompt ? Buffer.byteLength(prompt, 'utf-8') : 0;
395
465
  process.stderr.write(` ✓ prompt.md found (${promptBytes.toLocaleString()} bytes)\n`);
396
- if (inputSchema || outputSchema) {
466
+ if (schemaFromFile) {
397
467
  const schemaTypes = [inputSchema ? 'input' : null, outputSchema ? 'output' : null].filter(Boolean).join(' + ');
398
468
  process.stderr.write(` ✓ schema.json found (${schemaTypes} schemas)\n`);
399
469
  }
470
+ else if (inputSchema) {
471
+ const vars = prompt ? extractTemplateVariables(prompt) : [];
472
+ process.stderr.write(` ✓ Input schema derived from template variables: ${vars.join(', ')}\n`);
473
+ }
400
474
  }
401
475
  else if (manifest.type === 'code') {
402
476
  // Code agent validations
@@ -583,5 +657,6 @@ function registerPublishCommand(program) {
583
657
  if (shouldUploadBundle) {
584
658
  process.stdout.write(`\nNote: Hosted code execution is in beta. Contact support for full deployment.\n`);
585
659
  }
660
+ process.stdout.write(`\nView analytics and usage: https://orchagent.io/dashboard\n`);
586
661
  });
587
662
  }
@@ -14,7 +14,9 @@ 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')
19
+ .option('--tags <tags>', 'Filter by tags (comma-separated, e.g., security,devops)')
18
20
  .option('--limit <n>', `Max results (default: ${DEFAULT_LIMIT})`, String(DEFAULT_LIMIT))
19
21
  .option('--free', 'Show only free agents')
20
22
  .option('--paid', 'Show only paid agents')
@@ -23,29 +25,44 @@ function registerSearchCommand(program) {
23
25
  Pricing Filters:
24
26
  --free Show only free agents
25
27
  --paid Show only paid agents
28
+
29
+ Tag Filters:
30
+ --tags security,devops Show agents matching any of these tags
31
+
32
+ Ownership Filters:
33
+ --mine Show your own agents (public and private). Requires login.
26
34
  `)
27
35
  .action(async (query, options) => {
28
36
  const config = await (0, config_1.getResolvedConfig)();
29
37
  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
- }
38
+ // Map type filter for API (null means no filter)
39
+ const typeFilter = options.type === 'all' ? undefined : options.type;
40
+ const sort = options.popular ? 'stars' : options.recent ? 'recent' : undefined;
41
+ const tags = options.tags ? options.tags.split(',').map(t => t.trim()).filter(Boolean) : undefined;
34
42
  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 });
43
+ if (options.mine) {
44
+ // --mine: search within user's own agents (public + private)
45
+ if (!config.apiKey) {
46
+ process.stderr.write('Error: --mine requires authentication. Run `orchagent login` first.\n');
47
+ process.exitCode = 1;
48
+ return;
49
+ }
50
+ agents = await (0, api_1.searchMyAgents)(config, query, { sort, type: typeFilter });
51
+ await (0, analytics_1.track)('cli_search', { query, type: options.type, mine: true });
38
52
  }
39
53
  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');
54
+ // Default to popular when no args
55
+ if (!query && !options.popular && !options.recent) {
56
+ options.popular = true;
57
+ }
58
+ if (query) {
59
+ agents = await (0, api_1.searchAgents)(config, query, { sort, tags, type: typeFilter });
60
+ await (0, analytics_1.track)('cli_search', { query, type: options.type, tags: options.tags });
61
+ }
62
+ else {
63
+ agents = await (0, api_1.listPublicAgents)(config, { sort, tags, type: typeFilter });
64
+ await (0, analytics_1.track)('cli_search', { mode: options.popular ? 'popular' : 'recent', type: options.type, tags: options.tags });
65
+ }
49
66
  }
50
67
  // Filter by pricing if requested
51
68
  if (options.free) {
@@ -55,7 +72,7 @@ Pricing Filters:
55
72
  agents = agents.filter(a => (0, pricing_1.isPaidAgent)(a));
56
73
  }
57
74
  // Sort results
58
- if (options.popular || (!query && !options.recent)) {
75
+ if (options.popular || (!query && !options.recent && !options.mine)) {
59
76
  agents.sort((a, b) => (b.stars_count ?? 0) - (a.stars_count ?? 0));
60
77
  }
61
78
  else if (options.recent) {
@@ -68,14 +85,27 @@ Pricing Filters:
68
85
  return;
69
86
  }
70
87
  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');
88
+ if (options.mine) {
89
+ process.stdout.write(query
90
+ ? `No agents found matching "${query}" in your account.\n`
91
+ : 'You have no published agents yet.\n');
92
+ process.stdout.write('\nTip: Run "orchagent init" to create your first agent.\n');
93
+ }
94
+ else {
95
+ process.stdout.write(query ? 'No results found matching your search.\n' : 'No public agents found.\n');
96
+ process.stdout.write('\nBrowse all agents at: https://orchagent.io/explore\n');
97
+ }
73
98
  return;
74
99
  }
75
- (0, output_1.printAgentsTable)(agents);
100
+ (0, output_1.printAgentsTable)(agents, { showVisibility: options.mine });
76
101
  if (agents.length === limit) {
77
102
  process.stdout.write(`\nShowing top ${limit} results. Use --limit <n> for more.\n`);
78
103
  }
79
- process.stdout.write('\nTip: Run "orchagent info <agent>" to see input schema and details.\n');
104
+ if (options.mine) {
105
+ process.stdout.write('\nTip: Run "orchagent info <agent>" to see details, or "orchagent delete <agent>" to remove.\n');
106
+ }
107
+ else {
108
+ process.stdout.write('\nTip: Run "orchagent info <agent>" to see input schema and details.\n');
109
+ }
80
110
  });
81
111
  }
@@ -67,7 +67,7 @@ function formatSummaryOutput(result) {
67
67
  process.stdout.write('━'.repeat(50) + '\n\n');
68
68
  // Agent info
69
69
  process.stdout.write(`${chalk_1.default.bold('Agent:')} ${result.agent_id}\n`);
70
- process.stdout.write(`${chalk_1.default.bold('Scan Time:')} ${result.scan_timestamp}\n\n`);
70
+ process.stdout.write(`${chalk_1.default.bold('Scan Time:')} ${result.scanned_at}\n\n`);
71
71
  // Risk level banner
72
72
  process.stdout.write(`${chalk_1.default.bold('Risk Level:')} ${riskLevelColor(result.risk_level)}\n\n`);
73
73
  // Summary stats
@@ -282,7 +282,7 @@ function generateMarkdownReport(result) {
282
282
  lines.push('# Security Scan Report');
283
283
  lines.push('');
284
284
  lines.push(`**Agent:** ${result.agent_id}`);
285
- lines.push(`**Scan Time:** ${result.scan_timestamp}`);
285
+ lines.push(`**Scan Time:** ${result.scanned_at}`);
286
286
  lines.push(`**Risk Level:** ${result.risk_level.toUpperCase()}`);
287
287
  lines.push('');
288
288
  lines.push('## Summary');
@@ -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) {
@@ -33,9 +33,16 @@ function registerStarCommand(program) {
33
33
  process.stdout.write(`Removed star from ${org}/${name}/${version}\n`);
34
34
  }
35
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`);
36
+ const result = await (0, api_1.starAgent)(config, agentInfo.id);
37
+ if (result.starred) {
38
+ await (0, analytics_1.track)('cli_star', { agent: `${org}/${name}/${version}` });
39
+ process.stdout.write(`Starred ${org}/${name}/${version}\n`);
40
+ }
41
+ else {
42
+ // Already starred — toggle off
43
+ await (0, api_1.unstarAgent)(config, agentInfo.id);
44
+ process.stdout.write(`Unstarred ${org}/${name}/${version}\n`);
45
+ }
39
46
  }
40
47
  });
41
48
  }
@@ -53,100 +53,135 @@ function registerUpdateCommand(program) {
53
53
  process.stdout.write(`Agent "${agentRef}" is not installed.\n`);
54
54
  return;
55
55
  }
56
- process.stdout.write(`Checking ${toCheck.length} installed agent(s) for updates...\n\n`);
56
+ // Group installed entries by agent name to avoid duplicates
57
+ // (same agent installed to multiple formats shows once)
58
+ const grouped = new Map();
59
+ for (const item of toCheck) {
60
+ const existing = grouped.get(item.agent);
61
+ if (existing) {
62
+ existing.push(item);
63
+ }
64
+ else {
65
+ grouped.set(item.agent, [item]);
66
+ }
67
+ }
68
+ process.stdout.write(`Checking ${grouped.size} installed agent(s) for updates...\n\n`);
57
69
  let updatesAvailable = 0;
58
70
  let updatesApplied = 0;
59
71
  let skippedModified = 0;
60
72
  let skippedMissing = 0;
61
- for (const item of toCheck) {
62
- // Check if file was modified locally or is missing
63
- const fileStatus = await (0, installed_1.checkModified)(item);
64
- // Fetch latest version
65
- const latest = await fetchLatestAgent(resolved, item.agent);
73
+ for (const [agentName, entries] of grouped) {
74
+ // Check file status for all entries in this group
75
+ const fileStatuses = [];
76
+ for (const item of entries) {
77
+ fileStatuses.push({ item, status: await (0, installed_1.checkModified)(item) });
78
+ }
79
+ // Fetch latest version once per agent
80
+ const latest = await fetchLatestAgent(resolved, agentName);
66
81
  if (!latest) {
67
- process.stdout.write(` ${chalk_1.default.yellow('?')} ${item.agent} - could not fetch latest\n`);
82
+ process.stdout.write(` ${chalk_1.default.yellow('?')} ${agentName} - could not fetch latest\n`);
68
83
  continue;
69
84
  }
70
- const hasUpdate = latest.latestVersion !== item.version;
71
- if (!hasUpdate && !fileStatus.modified && !fileStatus.missing) {
72
- process.stdout.write(` ${chalk_1.default.green('✓')} ${item.agent}@${item.version} - up to date\n`);
85
+ // Use the version from the first entry (all entries for the same
86
+ // agent should share the same version after install/update)
87
+ const installedVersion = entries[0].version;
88
+ const hasUpdate = latest.latestVersion !== installedVersion;
89
+ const anyMissing = fileStatuses.some(f => f.status.missing);
90
+ const anyModified = fileStatuses.some(f => f.status.modified);
91
+ if (!hasUpdate && !anyModified && !anyMissing) {
92
+ const formatSuffix = entries.length > 1
93
+ ? ` ${chalk_1.default.dim(`(${entries.map(e => e.format).join(', ')})`)}`
94
+ : '';
95
+ process.stdout.write(` ${chalk_1.default.green('✓')} ${agentName}@${installedVersion} - up to date${formatSuffix}\n`);
73
96
  continue;
74
97
  }
75
- // Handle missing file without --force
76
- if (fileStatus.missing && !options.force) {
77
- process.stdout.write(` ${chalk_1.default.yellow('!')} ${item.agent} - file missing (use --force to reinstall)\n`);
98
+ // Handle missing files without --force
99
+ if (anyMissing && !hasUpdate && !options.force) {
100
+ const missingFormats = fileStatuses.filter(f => f.status.missing).map(f => f.item.format);
101
+ process.stdout.write(` ${chalk_1.default.yellow('!')} ${agentName} - file missing [${missingFormats.join(', ')}] (use --force to reinstall)\n`);
78
102
  skippedMissing++;
79
103
  continue;
80
104
  }
81
- // Handle modified file without --force
82
- if (fileStatus.modified && !options.force) {
83
- process.stdout.write(` ${chalk_1.default.yellow('!')} ${item.agent} - local modifications (use --force to overwrite)\n`);
105
+ // Handle modified files without --force
106
+ if (anyModified && !hasUpdate && !options.force) {
107
+ const modifiedFormats = fileStatuses.filter(f => f.status.modified).map(f => f.item.format);
108
+ process.stdout.write(` ${chalk_1.default.yellow('!')} ${agentName} - local modifications [${modifiedFormats.join(', ')}] (use --force to overwrite)\n`);
84
109
  skippedModified++;
85
110
  continue;
86
111
  }
87
- if (hasUpdate || fileStatus.missing) {
112
+ if (hasUpdate || anyMissing) {
88
113
  if (hasUpdate) {
89
114
  updatesAvailable++;
90
115
  }
91
- process.stdout.write(` ${chalk_1.default.blue('↑')} ${item.agent}@${item.version} → ${latest.latestVersion}`);
92
- if (fileStatus.modified) {
116
+ process.stdout.write(` ${chalk_1.default.blue('↑')} ${agentName}@${installedVersion} → ${latest.latestVersion}`);
117
+ if (anyModified) {
93
118
  process.stdout.write(` ${chalk_1.default.yellow('(modified)')}`);
94
119
  }
95
- if (fileStatus.missing) {
120
+ if (anyMissing) {
96
121
  process.stdout.write(` ${chalk_1.default.yellow('(reinstalling)')}`);
97
122
  }
98
123
  process.stdout.write('\n');
99
124
  if (options.check) {
100
125
  continue;
101
126
  }
102
- // Apply update
103
- const adapter = adapters_1.adapterRegistry.get(item.format);
104
- if (!adapter) {
105
- process.stderr.write(` Skipped: Unknown format "${item.format}"\n`);
106
- continue;
107
- }
108
- const checkResult = adapter.canConvert(latest.agent);
109
- if (!checkResult.canConvert) {
110
- process.stderr.write(` Skipped: ${checkResult.errors.join(', ')}\n`);
111
- continue;
112
- }
113
- const files = adapter.convert(latest.agent);
114
- if (files.length > 1) {
115
- process.stderr.write(` Skipped: Multi-file adapters not supported for update. Reinstall instead.\n`);
116
- continue;
117
- }
118
- for (const file of files) {
119
- // Use the original path from tracking
120
- const fullPath = item.path;
121
- try {
122
- const dir = path_1.default.dirname(fullPath);
123
- await promises_1.default.mkdir(dir, { recursive: true });
124
- // Special handling for AGENTS.md - append/replace instead of overwrite
125
- if (file.filename === 'AGENTS.md') {
126
- let existingContent = '';
127
- try {
128
- existingContent = await promises_1.default.readFile(fullPath, 'utf-8');
129
- }
130
- catch {
131
- // File doesn't exist, will create new
127
+ // Apply update to each format entry
128
+ for (const { item, status: fileStatus } of fileStatuses) {
129
+ // Skip unmodified and non-missing entries if no version update
130
+ if (!hasUpdate && !fileStatus.missing)
131
+ continue;
132
+ // Skip modified entries without --force
133
+ if (fileStatus.modified && !options.force) {
134
+ process.stderr.write(` Skipped ${item.format}: local modifications (use --force)\n`);
135
+ continue;
136
+ }
137
+ const adapter = adapters_1.adapterRegistry.get(item.format);
138
+ if (!adapter) {
139
+ process.stderr.write(` Skipped ${item.format}: unknown format\n`);
140
+ continue;
141
+ }
142
+ const checkResult = adapter.canConvert(latest.agent);
143
+ if (!checkResult.canConvert) {
144
+ process.stderr.write(` Skipped ${item.format}: ${checkResult.errors.join(', ')}\n`);
145
+ continue;
146
+ }
147
+ const files = adapter.convert(latest.agent);
148
+ if (files.length > 1) {
149
+ process.stderr.write(` Skipped ${item.format}: multi-file adapters not supported for update. Reinstall instead.\n`);
150
+ continue;
151
+ }
152
+ for (const file of files) {
153
+ // Use the original path from tracking
154
+ const fullPath = item.path;
155
+ try {
156
+ const dir = path_1.default.dirname(fullPath);
157
+ await promises_1.default.mkdir(dir, { recursive: true });
158
+ // Special handling for AGENTS.md - append/replace instead of overwrite
159
+ if (file.filename === 'AGENTS.md') {
160
+ let existingContent = '';
161
+ try {
162
+ existingContent = await promises_1.default.readFile(fullPath, 'utf-8');
163
+ }
164
+ catch {
165
+ // File doesn't exist, will create new
166
+ }
167
+ file.content = (0, agents_md_utils_1.mergeAgentsMdContent)(existingContent, file.content, item.agent);
132
168
  }
133
- file.content = (0, agents_md_utils_1.mergeAgentsMdContent)(existingContent, file.content, item.agent);
169
+ await promises_1.default.writeFile(fullPath, file.content);
170
+ // Update tracking
171
+ const updatedItem = {
172
+ ...item,
173
+ version: latest.latestVersion,
174
+ installedAt: new Date().toISOString(),
175
+ adapterVersion: adapter.version,
176
+ contentHash: (0, installed_1.computeHash)(file.content),
177
+ };
178
+ await (0, installed_1.trackInstall)(updatedItem);
179
+ process.stdout.write(` Updated: ${fullPath}\n`);
180
+ updatesApplied++;
181
+ }
182
+ catch (err) {
183
+ process.stderr.write(` Error (${item.format}): ${err.message}\n`);
134
184
  }
135
- await promises_1.default.writeFile(fullPath, file.content);
136
- // Update tracking
137
- const updatedItem = {
138
- ...item,
139
- version: latest.latestVersion,
140
- installedAt: new Date().toISOString(),
141
- adapterVersion: adapter.version,
142
- contentHash: (0, installed_1.computeHash)(file.content),
143
- };
144
- await (0, installed_1.trackInstall)(updatedItem);
145
- process.stdout.write(` Updated: ${fullPath}\n`);
146
- updatesApplied++;
147
- }
148
- catch (err) {
149
- process.stderr.write(` Error: ${err.message}\n`);
150
185
  }
151
186
  }
152
187
  }
@@ -13,6 +13,24 @@ function registerWhoamiCommand(program) {
13
13
  process.stdout.write(`Logged in as: ${org.name}\n`);
14
14
  process.stdout.write(`Org slug: ${org.slug}\n`);
15
15
  process.stdout.write(`API URL: ${config.apiUrl}\n`);
16
+ // Show active workspace if one is set
17
+ const configFile = await (0, config_1.loadConfig)();
18
+ if (configFile.workspace) {
19
+ try {
20
+ const response = await (0, api_1.request)(config, 'GET', '/workspaces');
21
+ const workspace = response.workspaces.find((w) => w.slug === configFile.workspace);
22
+ if (workspace) {
23
+ process.stdout.write(`Active workspace: ${workspace.name} (${workspace.slug})\n`);
24
+ }
25
+ else {
26
+ process.stdout.write(`Active workspace: ${configFile.workspace} (not found)\n`);
27
+ }
28
+ }
29
+ catch {
30
+ // Workspace fetch failed - show slug only
31
+ process.stdout.write(`Active workspace: ${configFile.workspace}\n`);
32
+ }
33
+ }
16
34
  // Show balance after org info
17
35
  try {
18
36
  const balance = await (0, api_1.getCreditsBalance)(config);
package/dist/index.js CHANGED
@@ -47,8 +47,10 @@ const errors_1 = require("./lib/errors");
47
47
  const analytics_1 = require("./lib/analytics");
48
48
  const config_1 = require("./lib/config");
49
49
  const spinner_1 = require("./lib/spinner");
50
+ const update_notifier_1 = require("./lib/update-notifier");
50
51
  const package_json_1 = __importDefault(require("../package.json"));
51
52
  (0, analytics_1.initPostHog)();
53
+ (0, update_notifier_1.checkForUpdates)();
52
54
  const program = new commander_1.Command();
53
55
  program
54
56
  .name('orchagent')
@@ -89,4 +91,7 @@ async function main() {
89
91
  }
90
92
  main()
91
93
  .catch(errors_1.exitWithError)
92
- .finally(() => (0, analytics_1.shutdownPostHog)());
94
+ .finally(() => {
95
+ (0, update_notifier_1.printUpdateNotification)();
96
+ return (0, analytics_1.shutdownPostHog)();
97
+ });