@orchagent/cli 0.3.89 → 0.3.91

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.
@@ -40,11 +40,13 @@ exports.registerSkillCommand = registerSkillCommand;
40
40
  const promises_1 = __importDefault(require("fs/promises"));
41
41
  const path_1 = __importDefault(require("path"));
42
42
  const os_1 = __importDefault(require("os"));
43
+ const chalk_1 = __importDefault(require("chalk"));
43
44
  const config_1 = require("../lib/config");
44
45
  const api_1 = require("../lib/api");
45
46
  const errors_1 = require("../lib/errors");
46
47
  const analytics_1 = require("../lib/analytics");
47
48
  const installed_1 = require("../lib/installed");
49
+ const agents_1 = require("./agents");
48
50
  const package_json_1 = __importDefault(require("../../package.json"));
49
51
  const DEFAULT_VERSION = 'latest';
50
52
  function stripFrontmatter(content) {
@@ -208,15 +210,89 @@ function registerSkillCommand(program) {
208
210
  // orch skill list
209
211
  skill
210
212
  .command('list')
211
- .description('Browse available skills')
213
+ .description('List your published and installed skills')
212
214
  .option('--json', 'Output raw JSON')
213
- .action(async () => {
214
- process.stdout.write('Install a skill:\n' +
215
- ' orch skill install <org>/<skill-name>\n\n' +
216
- 'View installed skills:\n' +
217
- ' orch update --check\n\n' +
218
- 'Learn more: https://docs.orchagent.io/skills\n');
219
- process.exit(0);
215
+ .action(async (options) => {
216
+ const config = await (0, config_1.getResolvedConfig)();
217
+ const jsonMode = options.json === true;
218
+ // Fetch published skills (only if authenticated)
219
+ let publishedSkills = [];
220
+ if (config.apiKey) {
221
+ const configFile = await (0, config_1.loadConfig)();
222
+ const orgSlug = configFile.workspace ?? config.defaultOrg;
223
+ const workspaceId = orgSlug ? await (0, api_1.resolveWorkspaceIdForOrg)(config, orgSlug) : undefined;
224
+ const allAgents = await (0, api_1.listMyAgents)(config, workspaceId);
225
+ publishedSkills = allAgents.filter(a => a.type === 'skill');
226
+ }
227
+ // Fetch locally installed skills
228
+ const installed = await (0, installed_1.getInstalled)();
229
+ // JSON output
230
+ if (jsonMode) {
231
+ const { agents: latestSkills } = (0, agents_1.latestOnly)(publishedSkills);
232
+ process.stdout.write(JSON.stringify({
233
+ published: latestSkills,
234
+ installed,
235
+ }, null, 2) + '\n');
236
+ return;
237
+ }
238
+ // Empty state
239
+ if (publishedSkills.length === 0 && installed.length === 0) {
240
+ process.stdout.write('No skills found.\n\n' +
241
+ 'Install a skill:\n' +
242
+ ' orch skill install <org>/<skill-name>\n\n' +
243
+ 'Create a skill:\n' +
244
+ ' orch skill create <name>\n');
245
+ return;
246
+ }
247
+ // Published skills table
248
+ if (publishedSkills.length > 0) {
249
+ const { agents: latestSkills, versionCounts } = (0, agents_1.latestOnly)(publishedSkills);
250
+ const Table = (await Promise.resolve().then(() => __importStar(require('cli-table3')))).default;
251
+ const table = new Table({
252
+ head: [
253
+ chalk_1.default.bold('Skill'),
254
+ chalk_1.default.bold('Version'),
255
+ chalk_1.default.bold('Description'),
256
+ ],
257
+ });
258
+ for (const skill of latestSkills) {
259
+ const desc = skill.description
260
+ ? skill.description.length > 60
261
+ ? skill.description.slice(0, 57) + '...'
262
+ : skill.description
263
+ : '-';
264
+ let version = skill.version;
265
+ const count = versionCounts.get(skill.name) ?? 1;
266
+ if (count > 1) {
267
+ version = `${skill.version} (${count} total)`;
268
+ }
269
+ table.push([skill.name, version, desc]);
270
+ }
271
+ process.stdout.write(`Published Skills\n${table.toString()}\n`);
272
+ process.stdout.write(`\n${latestSkills.length} skill${latestSkills.length === 1 ? '' : 's'}`);
273
+ if (publishedSkills.length > latestSkills.length) {
274
+ process.stdout.write(` (${publishedSkills.length} versions total)`);
275
+ }
276
+ process.stdout.write('\n');
277
+ }
278
+ // Installed skills table
279
+ if (installed.length > 0) {
280
+ const Table = (await Promise.resolve().then(() => __importStar(require('cli-table3')))).default;
281
+ const table = new Table({
282
+ head: [
283
+ chalk_1.default.bold('Skill'),
284
+ chalk_1.default.bold('Version'),
285
+ chalk_1.default.bold('Tool'),
286
+ chalk_1.default.bold('Scope'),
287
+ ],
288
+ });
289
+ for (const entry of installed) {
290
+ table.push([entry.agent, entry.version, entry.format, entry.scope]);
291
+ }
292
+ if (publishedSkills.length > 0)
293
+ process.stdout.write('\n');
294
+ process.stdout.write(`Installed Skills\n${table.toString()}\n`);
295
+ }
220
296
  });
221
297
  // orch skill create [name]
222
298
  skill
@@ -15,15 +15,24 @@ const output_1 = require("../lib/output");
15
15
  async function resolveWorkspaceId(config, slug) {
16
16
  const configFile = await (0, config_1.loadConfig)();
17
17
  const targetSlug = slug ?? configFile.workspace;
18
- if (!targetSlug) {
19
- throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
20
- }
21
18
  const response = await (0, api_1.request)(config, 'GET', '/workspaces');
22
- const workspace = response.workspaces.find((w) => w.slug === targetSlug);
23
- if (!workspace) {
24
- throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
19
+ if (targetSlug) {
20
+ const workspace = response.workspaces.find((w) => w.slug === targetSlug);
21
+ if (!workspace) {
22
+ throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
23
+ }
24
+ return workspace.id;
25
+ }
26
+ // No workspace specified — auto-select if user has exactly one
27
+ if (response.workspaces.length === 0) {
28
+ throw new errors_1.CliError('No workspaces found. Create one with `orch workspace create <name>`.');
29
+ }
30
+ if (response.workspaces.length === 1) {
31
+ return response.workspaces[0].id;
25
32
  }
26
- return workspace.id;
33
+ const slugs = response.workspaces.map((w) => w.slug).join(', ');
34
+ throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
35
+ 'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
27
36
  }
28
37
  function isUuid(value) {
29
38
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
@@ -20,7 +20,13 @@ function registerTreeCommand(program) {
20
20
  if (!config.apiKey) {
21
21
  throw new errors_1.CliError('Authentication required. Run: orch login');
22
22
  }
23
- const { org, agent, version } = (0, agent_ref_1.parseAgentRef)(agentArg);
23
+ const parsed = (0, agent_ref_1.parseAgentRef)(agentArg);
24
+ const configFile = await (0, config_1.loadConfig)();
25
+ const org = parsed.org ?? configFile.workspace ?? config.defaultOrg;
26
+ if (!org) {
27
+ throw new errors_1.CliError('Missing org. Use org/agent format or set default org.');
28
+ }
29
+ const { agent, version } = parsed;
24
30
  const tree = await (0, api_1.request)(config, 'GET', `/agents/${org}/${agent}/${version}/tree`);
25
31
  if (options.json) {
26
32
  console.log(JSON.stringify(tree, null, 2));
@@ -16,15 +16,24 @@ function deriveSlug(name) {
16
16
  async function resolveWorkspaceId(config, slug) {
17
17
  const configFile = await (0, config_1.loadConfig)();
18
18
  const targetSlug = slug ?? configFile.workspace;
19
- if (!targetSlug) {
20
- throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orchagent workspace use <slug>` first.');
21
- }
22
19
  const response = await (0, api_1.request)(config, 'GET', '/workspaces');
23
- const workspace = response.workspaces.find((w) => w.slug === targetSlug);
24
- if (!workspace) {
25
- throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
20
+ if (targetSlug) {
21
+ const workspace = response.workspaces.find((w) => w.slug === targetSlug);
22
+ if (!workspace) {
23
+ throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
24
+ }
25
+ return workspace.id;
26
+ }
27
+ // No workspace specified — auto-select if user has exactly one
28
+ if (response.workspaces.length === 0) {
29
+ throw new errors_1.CliError('No workspaces found. Create one with `orch workspace create <name>`.');
30
+ }
31
+ if (response.workspaces.length === 1) {
32
+ return response.workspaces[0].id;
26
33
  }
27
- return workspace.id;
34
+ const slugs = response.workspaces.map((w) => w.slug).join(', ');
35
+ throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
36
+ 'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
28
37
  }
29
38
  async function listWorkspaces(config, options) {
30
39
  const response = await (0, api_1.request)(config, 'GET', '/workspaces');
@@ -6,8 +6,11 @@ function parseAgentRef(value, defaultVersion = 'latest') {
6
6
  const [ref, versionPart] = value.split('@');
7
7
  const version = versionPart?.trim() || defaultVersion;
8
8
  const segments = ref.split('/');
9
+ if (segments.length === 1) {
10
+ return { org: undefined, agent: segments[0], version };
11
+ }
9
12
  if (segments.length === 2) {
10
13
  return { org: segments[0], agent: segments[1], version };
11
14
  }
12
- throw new errors_1.CliError('Invalid agent reference. Use org/agent[@version] format');
15
+ throw new errors_1.CliError('Invalid agent reference. Use agent or org/agent[@version] format');
13
16
  }
package/dist/lib/api.js CHANGED
@@ -116,8 +116,6 @@ async function safeFetchWithRetryForCalls(url, options) {
116
116
  parsed = JSON.parse(bodyText);
117
117
  }
118
118
  catch { /* ignore */ }
119
- const detail = parsed?.error?.message ||
120
- parsed?.message || '';
121
119
  const isRetryable = parsed?.error?.is_retryable;
122
120
  // Don't retry if server explicitly says error is not retryable
123
121
  if (isRetryable === false) {
@@ -130,8 +128,7 @@ async function safeFetchWithRetryForCalls(url, options) {
130
128
  if (attempt < MAX_RETRIES) {
131
129
  const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
132
130
  const jitter = Math.random() * 500;
133
- const detailSuffix = detail ? `: ${detail}` : '';
134
- process.stderr.write(`Request failed (${response.status}${detailSuffix}), retrying in ${Math.round((delay + jitter) / 1000)}s...\n`);
131
+ process.stderr.write(`Server error (${response.status}), retrying in ${Math.round((delay + jitter) / 1000)}s...\n`);
135
132
  await new Promise(r => setTimeout(r, delay + jitter));
136
133
  continue;
137
134
  }
@@ -174,8 +171,6 @@ async function safeFetchWithRetry(url, options) {
174
171
  parsed = JSON.parse(bodyText);
175
172
  }
176
173
  catch { /* ignore */ }
177
- const detail = parsed?.error?.message ||
178
- parsed?.message || '';
179
174
  const isRetryable = parsed?.error?.is_retryable;
180
175
  // Don't retry if server explicitly says error is not retryable
181
176
  if (isRetryable === false) {
@@ -188,8 +183,7 @@ async function safeFetchWithRetry(url, options) {
188
183
  if (attempt < MAX_RETRIES) {
189
184
  const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
190
185
  const jitter = Math.random() * 500;
191
- const detailSuffix = detail ? `: ${detail}` : '';
192
- process.stderr.write(`Request failed (${response.status}${detailSuffix}), retrying in ${Math.round((delay + jitter) / 1000)}s...\n`);
186
+ process.stderr.write(`Server error (${response.status}), retrying in ${Math.round((delay + jitter) / 1000)}s...\n`);
193
187
  await new Promise(r => setTimeout(r, delay + jitter));
194
188
  continue;
195
189
  }
@@ -59,6 +59,7 @@ async function waitForCallback(port, timeoutMs) {
59
59
  let server = null;
60
60
  const cleanup = () => {
61
61
  if (server) {
62
+ server.closeAllConnections();
62
63
  server.close();
63
64
  server = null;
64
65
  }
@@ -191,6 +191,37 @@ function safeRealpathSync(p) {
191
191
  return p;
192
192
  }
193
193
  }
194
+ /**
195
+ * Parse a version string into a comparable numeric tuple.
196
+ * Returns [-1, -1, -1] for unparseable versions (sorts below all valid versions).
197
+ */
198
+ function parseVersion(version) {
199
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
200
+ if (!match)
201
+ return [-1, -1, -1];
202
+ return [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10)];
203
+ }
204
+ /**
205
+ * Find the latest (highest) version from a list of version strings.
206
+ * Unparseable versions (e.g. "unknown") sort below all valid versions.
207
+ */
208
+ function findLatestVersion(versions) {
209
+ let latest = versions[0];
210
+ let latestParts = parseVersion(latest);
211
+ for (let i = 1; i < versions.length; i++) {
212
+ const parts = parseVersion(versions[i]);
213
+ for (let j = 0; j < 3; j++) {
214
+ if (parts[j] > latestParts[j]) {
215
+ latest = versions[i];
216
+ latestParts = parts;
217
+ break;
218
+ }
219
+ if (parts[j] < latestParts[j])
220
+ break;
221
+ }
222
+ }
223
+ return latest;
224
+ }
194
225
  /**
195
226
  * Detect multiple CLI installations at different paths/versions (BUG-008).
196
227
  *
@@ -248,12 +279,24 @@ async function checkDualInstallation() {
248
279
  .map((i) => `${i.path} (v${i.version})`)
249
280
  .join(', ');
250
281
  if (versionsDiffer) {
282
+ // Find the latest version and build a specific rm command for stale paths
283
+ const latestVersion = findLatestVersion(allInstalls.map((i) => i.version));
284
+ const stalePaths = allInstalls
285
+ .filter((i) => i.version !== latestVersion)
286
+ .map((i) => i.path);
287
+ const staleList = allInstalls
288
+ .filter((i) => i.version !== latestVersion)
289
+ .map((i) => `${i.path} (v${i.version})`)
290
+ .join(', ');
291
+ const fix = stalePaths.length === 1
292
+ ? `Remove outdated ${staleList}: run \`rm ${stalePaths[0]}\``
293
+ : `Remove outdated installations (${staleList}): run \`rm ${stalePaths.join(' ')}\``;
251
294
  return {
252
295
  category: 'environment',
253
296
  name: 'dual_installation',
254
297
  status: 'warning',
255
298
  message: `Multiple CLI versions found: ${pathList}`,
256
- fix: 'Remove the outdated installation. Run `which -a orch orchagent` to see all paths, then remove the older binary',
299
+ fix,
257
300
  details: {
258
301
  installationCount: installations.size,
259
302
  versionMismatch: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.89",
3
+ "version": "0.3.91",
4
4
  "description": "Command-line interface for orchagent — deploy and run AI agents for your team",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",