@orchagent/cli 0.2.22 → 0.2.23

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # OrchAgent CLI
1
+ # orchagent CLI
2
2
 
3
- Minimal CLI for interacting with the OrchAgent platform.
3
+ Minimal CLI for interacting with the orchagent platform.
4
4
 
5
5
  ## Commands
6
6
 
@@ -6,7 +6,7 @@ const output_1 = require("../lib/output");
6
6
  function registerDoctorCommand(program) {
7
7
  program
8
8
  .command('doctor')
9
- .description('Diagnose setup issues with OrchAgent CLI')
9
+ .description('Diagnose setup issues with orchagent CLI')
10
10
  .option('-v, --verbose', 'Show detailed output for each check')
11
11
  .option('--json', 'Output results as JSON')
12
12
  .action(async (options) => {
@@ -55,7 +55,7 @@ function successHtml() {
55
55
  <html>
56
56
  <head>
57
57
  <meta charset="utf-8">
58
- <title>OrchAgent CLI - GitHub Connected</title>
58
+ <title>orchagent CLI - GitHub Connected</title>
59
59
  <style>
60
60
  body {
61
61
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
@@ -114,7 +114,7 @@ function errorHtml(message) {
114
114
  <html>
115
115
  <head>
116
116
  <meta charset="utf-8">
117
- <title>OrchAgent CLI - GitHub Connection Error</title>
117
+ <title>orchagent CLI - GitHub Connection Error</title>
118
118
  <style>
119
119
  body {
120
120
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
@@ -23,7 +23,7 @@ async function promptForKey() {
23
23
  function registerLoginCommand(program) {
24
24
  program
25
25
  .command('login')
26
- .description('Authenticate with OrchAgent via browser or API key')
26
+ .description('Authenticate with orchagent via browser or API key')
27
27
  .option('--key <key>', 'API key (for CI/CD, non-interactive)')
28
28
  .option('--port <port>', `Localhost port for browser callback (default: ${DEFAULT_AUTH_PORT})`, String(DEFAULT_AUTH_PORT))
29
29
  .action(async (options) => {
@@ -13,6 +13,36 @@ const api_1 = require("../lib/api");
13
13
  const errors_1 = require("../lib/errors");
14
14
  const analytics_1 = require("../lib/analytics");
15
15
  const bundle_1 = require("../lib/bundle");
16
+ /**
17
+ * Handle security flagged error response (422 with error: 'content_flagged')
18
+ * Returns true if the error was handled, false otherwise
19
+ */
20
+ function handleSecurityFlaggedError(err) {
21
+ if (!(err instanceof api_1.ApiError) || err.status !== 422) {
22
+ return false;
23
+ }
24
+ const payload = err.payload;
25
+ if (payload?.error !== 'content_flagged') {
26
+ return false;
27
+ }
28
+ process.stderr.write('\n');
29
+ process.stderr.write('Error: Skill flagged for security review\n\n');
30
+ if (payload.concerns && payload.concerns.length > 0) {
31
+ process.stderr.write('Concerns found:\n');
32
+ for (const concern of payload.concerns) {
33
+ const severityLabel = concern.severity.toUpperCase();
34
+ const fileInfo = concern.file_path ? ` in ${concern.file_path}` : '';
35
+ process.stderr.write(` [${severityLabel}] ${concern.category}${fileInfo}\n`);
36
+ process.stderr.write(` "${concern.description}"\n\n`);
37
+ }
38
+ }
39
+ if (payload.summary) {
40
+ process.stderr.write(`Summary: ${payload.summary}\n\n`);
41
+ }
42
+ process.stderr.write('Please review and remove suspicious patterns before publishing.\n');
43
+ process.stderr.write('If you believe this is a false positive, contact support@orchagent.com\n');
44
+ return true;
45
+ }
16
46
  /**
17
47
  * Check if orchagent-sdk is listed in requirements.txt or pyproject.toml
18
48
  */
@@ -60,6 +90,65 @@ async function parseSkillMd(filePath) {
60
90
  return null;
61
91
  }
62
92
  }
93
+ /**
94
+ * Binary file extensions to skip when collecting skill files (SC-05)
95
+ */
96
+ const BINARY_EXTENSIONS = new Set([
97
+ '.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp', '.bmp', '.tiff',
98
+ '.pdf', '.zip', '.tar', '.gz', '.7z', '.rar',
99
+ '.exe', '.dll', '.so', '.dylib', '.bin',
100
+ '.woff', '.woff2', '.ttf', '.eot', '.otf',
101
+ '.mp3', '.mp4', '.wav', '.avi', '.mov', '.mkv',
102
+ '.pyc', '.pyo', '.class', '.o', '.obj',
103
+ ]);
104
+ /**
105
+ * Collect all text files in the skill directory for SC-05 multi-file support
106
+ */
107
+ async function collectSkillFiles(skillDir, maxFiles = 20, maxTotalSize = 500_000) {
108
+ const files = [];
109
+ let totalSize = 0;
110
+ async function walkDir(dir, relativePath = '') {
111
+ if (files.length >= maxFiles || totalSize >= maxTotalSize)
112
+ return;
113
+ const entries = await promises_1.default.readdir(dir, { withFileTypes: true });
114
+ for (const entry of entries) {
115
+ if (files.length >= maxFiles || totalSize >= maxTotalSize)
116
+ break;
117
+ const fullPath = path_1.default.join(dir, entry.name);
118
+ const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
119
+ // Skip hidden files and common non-content directories
120
+ if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === '__pycache__') {
121
+ continue;
122
+ }
123
+ if (entry.isDirectory()) {
124
+ await walkDir(fullPath, relPath);
125
+ }
126
+ else if (entry.isFile()) {
127
+ // Skip binary files
128
+ const ext = path_1.default.extname(entry.name).toLowerCase();
129
+ if (BINARY_EXTENSIONS.has(ext))
130
+ continue;
131
+ try {
132
+ const stat = await promises_1.default.stat(fullPath);
133
+ if (totalSize + stat.size > maxTotalSize)
134
+ continue;
135
+ const content = await promises_1.default.readFile(fullPath, 'utf-8');
136
+ files.push({
137
+ path: relPath,
138
+ content,
139
+ size: stat.size,
140
+ });
141
+ totalSize += stat.size;
142
+ }
143
+ catch {
144
+ // Skip files that can't be read (binary, permissions, etc.)
145
+ }
146
+ }
147
+ }
148
+ }
149
+ await walkDir(skillDir);
150
+ return files;
151
+ }
63
152
  function registerPublishCommand(program) {
64
153
  program
65
154
  .command('publish')
@@ -86,10 +175,14 @@ function registerPublishCommand(program) {
86
175
  if (skillData) {
87
176
  // Publish as a skill (server auto-assigns version)
88
177
  const org = await (0, api_1.getOrg)(config);
178
+ // SC-05: Collect all files in the skill directory for multi-file support
179
+ const skillFiles = await collectSkillFiles(cwd);
180
+ const hasMultipleFiles = skillFiles.length > 1;
89
181
  // Handle dry-run for skills
90
182
  if (options.dryRun) {
91
183
  const preview = await (0, api_1.previewAgentVersion)(config, skillData.frontmatter.name);
92
184
  const skillBodyBytes = Buffer.byteLength(skillData.body, 'utf-8');
185
+ const totalFilesSize = skillFiles.reduce((sum, f) => sum + f.size, 0);
93
186
  const versionInfo = preview.existing_versions.length > 0
94
187
  ? `${preview.next_version} (new version, ${preview.existing_versions[preview.existing_versions.length - 1]} exists)`
95
188
  : `${preview.next_version} (first version)`;
@@ -97,6 +190,9 @@ function registerPublishCommand(program) {
97
190
  process.stderr.write('Validating...\n');
98
191
  process.stderr.write(` ✓ SKILL.md found and valid\n`);
99
192
  process.stderr.write(` ✓ Skill prompt (${skillBodyBytes.toLocaleString()} bytes)\n`);
193
+ if (hasMultipleFiles) {
194
+ process.stderr.write(` ✓ Skill files: ${skillFiles.length} files (${(totalFilesSize / 1024).toFixed(1)} KB)\n`);
195
+ }
100
196
  process.stderr.write(` ✓ Authentication valid (org: ${org.slug})\n`);
101
197
  process.stderr.write('\nSkill Preview:\n');
102
198
  process.stderr.write(` Name: ${skillData.frontmatter.name}\n`);
@@ -104,25 +200,47 @@ function registerPublishCommand(program) {
104
200
  process.stderr.write(` Version: ${versionInfo}\n`);
105
201
  process.stderr.write(` Visibility: ${options.public ? 'public' : 'private'}\n`);
106
202
  process.stderr.write(` Providers: any\n`);
203
+ if (hasMultipleFiles) {
204
+ process.stderr.write(` Files: ${skillFiles.length} files\n`);
205
+ for (const f of skillFiles.slice(0, 5)) {
206
+ process.stderr.write(` - ${f.path}\n`);
207
+ }
208
+ if (skillFiles.length > 5) {
209
+ process.stderr.write(` ... and ${skillFiles.length - 5} more\n`);
210
+ }
211
+ }
107
212
  process.stderr.write(`\nWould publish: ${preview.org_slug}/${skillData.frontmatter.name}@${preview.next_version}\n`);
108
213
  process.stderr.write(`API endpoint: POST ${config.apiUrl}/${preview.org_slug}/${skillData.frontmatter.name}/${preview.next_version}/run\n\n`);
109
214
  process.stderr.write('No changes made (dry run)\n');
110
215
  return;
111
216
  }
112
- const skillResult = await (0, api_1.createAgent)(config, {
113
- name: skillData.frontmatter.name,
114
- type: 'skill',
115
- description: skillData.frontmatter.description,
116
- prompt: skillData.body,
117
- is_public: options.public ? true : false,
118
- supported_providers: ['any'],
119
- default_skills: skillsFromFlag,
120
- skills_locked: options.skillsLocked || undefined,
121
- });
122
- const skillVersion = skillResult.agent?.version || 'v1';
123
- await (0, analytics_1.track)('cli_publish', { agent_type: 'skill' });
124
- process.stdout.write(`\nPublished skill: ${org.slug}/${skillData.frontmatter.name}@${skillVersion}\n`);
125
- process.stdout.write(`Public: ${options.public ? 'yes' : 'no'}\n`);
217
+ try {
218
+ const skillResult = await (0, api_1.createAgent)(config, {
219
+ name: skillData.frontmatter.name,
220
+ type: 'skill',
221
+ description: skillData.frontmatter.description,
222
+ prompt: skillData.body,
223
+ is_public: options.public ? true : false,
224
+ supported_providers: ['any'],
225
+ default_skills: skillsFromFlag,
226
+ skills_locked: options.skillsLocked || undefined,
227
+ // SC-05: Include all skill files for UI preview
228
+ skill_files: hasMultipleFiles ? skillFiles : undefined,
229
+ });
230
+ const skillVersion = skillResult.agent?.version || 'v1';
231
+ await (0, analytics_1.track)('cli_publish', { agent_type: 'skill', multi_file: hasMultipleFiles });
232
+ process.stdout.write(`\nPublished skill: ${org.slug}/${skillData.frontmatter.name}@${skillVersion}\n`);
233
+ if (hasMultipleFiles) {
234
+ process.stdout.write(`Files: ${skillFiles.length} files included\n`);
235
+ }
236
+ process.stdout.write(`Public: ${options.public ? 'yes' : 'no'}\n`);
237
+ }
238
+ catch (err) {
239
+ if (handleSecurityFlaggedError(err)) {
240
+ process.exit(1);
241
+ }
242
+ throw err;
243
+ }
126
244
  return;
127
245
  }
128
246
  // Read manifest
@@ -290,29 +408,38 @@ function registerPublishCommand(program) {
290
408
  return;
291
409
  }
292
410
  // Create the agent (server auto-assigns version)
293
- const result = await (0, api_1.createAgent)(config, {
294
- name: manifest.name,
295
- type: manifest.type,
296
- description: manifest.description,
297
- prompt,
298
- url: agentUrl,
299
- input_schema: inputSchema,
300
- output_schema: outputSchema,
301
- tags: manifest.tags,
302
- is_public: options.public ? true : false,
303
- supported_providers: supportedProviders,
304
- default_models: manifest.default_models,
305
- // Local run fields for code agents
306
- source_url: manifest.source_url,
307
- pip_package: manifest.pip_package,
308
- run_command: manifest.run_command,
309
- // SDK compatibility flag
310
- sdk_compatible: sdkCompatible || undefined,
311
- // Orchestration manifest (includes dependencies)
312
- manifest: manifest.manifest,
313
- default_skills: skillsFromFlag || manifest.default_skills,
314
- skills_locked: manifest.skills_locked || options.skillsLocked || undefined,
315
- });
411
+ let result;
412
+ try {
413
+ result = await (0, api_1.createAgent)(config, {
414
+ name: manifest.name,
415
+ type: manifest.type,
416
+ description: manifest.description,
417
+ prompt,
418
+ url: agentUrl,
419
+ input_schema: inputSchema,
420
+ output_schema: outputSchema,
421
+ tags: manifest.tags,
422
+ is_public: options.public ? true : false,
423
+ supported_providers: supportedProviders,
424
+ default_models: manifest.default_models,
425
+ // Local run fields for code agents
426
+ source_url: manifest.source_url,
427
+ pip_package: manifest.pip_package,
428
+ run_command: manifest.run_command,
429
+ // SDK compatibility flag
430
+ sdk_compatible: sdkCompatible || undefined,
431
+ // Orchestration manifest (includes dependencies)
432
+ manifest: manifest.manifest,
433
+ default_skills: skillsFromFlag || manifest.default_skills,
434
+ skills_locked: manifest.skills_locked || options.skillsLocked || undefined,
435
+ });
436
+ }
437
+ catch (err) {
438
+ if (handleSecurityFlaggedError(err)) {
439
+ process.exit(1);
440
+ }
441
+ throw err;
442
+ }
316
443
  const assignedVersion = result.agent?.version || 'v1';
317
444
  const agentId = result.agent?.id;
318
445
  // Upload code bundle if this is a hosted code agent
@@ -11,6 +11,7 @@ const config_1 = require("../lib/config");
11
11
  const api_1 = require("../lib/api");
12
12
  const errors_1 = require("../lib/errors");
13
13
  const analytics_1 = require("../lib/analytics");
14
+ const package_json_1 = __importDefault(require("../../package.json"));
14
15
  const DEFAULT_VERSION = 'v1';
15
16
  /**
16
17
  * AI tool skill directories.
@@ -220,6 +221,11 @@ ${skillData.prompt}
220
221
  skill: `${org}/${parsed.skill}`,
221
222
  global: Boolean(options.global),
222
223
  });
224
+ // Report authenticated install to backend (fire-and-forget)
225
+ // This tracks unique installers for manipulation-resistant metrics
226
+ if (resolved.apiKey) {
227
+ (0, api_1.reportInstall)(resolved, org, parsed.skill, parsed.version, package_json_1.default.version).catch(() => { });
228
+ }
223
229
  process.stdout.write(`Installed ${org}/${parsed.skill}@${parsed.version}\n`);
224
230
  process.stdout.write(`\nAvailable for:\n`);
225
231
  for (const tool of installed) {
@@ -51,7 +51,7 @@ function formatLatency(latencyMs) {
51
51
  }
52
52
  function printHumanStatus(data) {
53
53
  const overallStatus = getOverallStatus(data.services);
54
- process.stdout.write(`\nOrchAgent Status: ${overallStatus}\n\n`);
54
+ process.stdout.write(`\norchagent Status: ${overallStatus}\n\n`);
55
55
  for (const service of data.services) {
56
56
  const icon = getStatusIcon(service.status);
57
57
  const label = getStatusLabel(service.status);
@@ -64,7 +64,7 @@ function printHumanStatus(data) {
64
64
  function registerStatusCommand(program) {
65
65
  program
66
66
  .command('status')
67
- .description('Check OrchAgent service status')
67
+ .description('Check orchagent service status')
68
68
  .option('--json', 'Output raw JSON')
69
69
  .action(async (options) => {
70
70
  try {
package/dist/index.js CHANGED
@@ -53,12 +53,12 @@ const package_json_1 = __importDefault(require("../package.json"));
53
53
  const program = new commander_1.Command();
54
54
  program
55
55
  .name('orchagent')
56
- .description('OrchAgent CLI')
56
+ .description('orchagent CLI')
57
57
  .version(package_json_1.default.version)
58
58
  .addHelpText('after', `
59
59
  Quick Reference:
60
60
  run Download and run an agent locally (your machine)
61
- call Execute an agent on OrchAgent servers (requires login)
61
+ call Execute an agent on orchagent servers (requires login)
62
62
  info Show agent details and input/output schemas
63
63
  `);
64
64
  (0, commands_1.registerCommands)(program);
package/dist/lib/api.js CHANGED
@@ -56,6 +56,7 @@ exports.downloadCodeBundleAuthenticated = downloadCodeBundleAuthenticated;
56
56
  exports.checkAgentDelete = checkAgentDelete;
57
57
  exports.deleteAgent = deleteAgent;
58
58
  exports.previewAgentVersion = previewAgentVersion;
59
+ exports.reportInstall = reportInstall;
59
60
  const errors_1 = require("./errors");
60
61
  const DEFAULT_TIMEOUT_MS = 15000;
61
62
  async function safeFetch(url, options) {
@@ -301,3 +302,15 @@ async function deleteAgent(config, agentId, confirmationName) {
301
302
  async function previewAgentVersion(config, agentName) {
302
303
  return request(config, 'GET', `/agents/preview?name=${encodeURIComponent(agentName)}`);
303
304
  }
305
+ /**
306
+ * Report a skill installation to the backend.
307
+ * Only tracks authenticated installs (requires API key).
308
+ * Fire-and-forget - errors are silently ignored.
309
+ */
310
+ async function reportInstall(config, org, skill, version, cliVersion) {
311
+ if (!config.apiKey)
312
+ return;
313
+ await request(config, 'POST', `/agents/${org}/${skill}/${version}/install`, {
314
+ headers: { 'X-CLI-Version': cliVersion },
315
+ });
316
+ }
@@ -160,7 +160,7 @@ function successHtml() {
160
160
  <html>
161
161
  <head>
162
162
  <meta charset="utf-8">
163
- <title>OrchAgent CLI - Authentication Successful</title>
163
+ <title>orchagent CLI - Authentication Successful</title>
164
164
  <style>
165
165
  body {
166
166
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
@@ -219,7 +219,7 @@ function errorHtml(message) {
219
219
  <html>
220
220
  <head>
221
221
  <meta charset="utf-8">
222
- <title>OrchAgent CLI - Authentication Error</title>
222
+ <title>orchagent CLI - Authentication Error</title>
223
223
  <style>
224
224
  body {
225
225
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Code bundling utilities for hosted code agents.
4
4
  *
5
- * Creates zip bundles from project directories for upload to OrchAgent.
5
+ * Creates zip bundles from project directories for upload to orchagent.
6
6
  */
7
7
  var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -91,7 +91,7 @@ const DEFAULT_EXCLUDES = [
91
91
  'pytest.ini',
92
92
  '.coveragerc',
93
93
  'coverage/**',
94
- // OrchAgent
94
+ // orchagent
95
95
  'orchagent.json',
96
96
  'bundle.zip',
97
97
  '*.zip',
@@ -24,7 +24,7 @@ async function checkGatewayHealth() {
24
24
  name: 'gateway_reachable',
25
25
  status: 'error',
26
26
  message: `Gateway returned ${response.status}`,
27
- fix: 'Check if OrchAgent API is operational at https://status.orchagent.io',
27
+ fix: 'Check if orchagent API is operational at https://status.orchagent.io',
28
28
  details: {
29
29
  url: healthUrl,
30
30
  status: response.status,
@@ -58,7 +58,7 @@ function groupByCategory(results) {
58
58
  function printHumanOutput(results, summary, verbose) {
59
59
  // Header
60
60
  process.stdout.write('\n');
61
- process.stdout.write(chalk_1.default.bold('OrchAgent Doctor\n'));
61
+ process.stdout.write(chalk_1.default.bold('orchagent Doctor\n'));
62
62
  process.stdout.write('================\n\n');
63
63
  // Group and print results
64
64
  const groups = groupByCategory(results);
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.2.22",
4
- "description": "Command-line interface for the OrchAgent AI agent marketplace",
3
+ "version": "0.2.23",
4
+ "description": "Command-line interface for the orchagent AI agent marketplace",
5
5
  "license": "MIT",
6
- "author": "OrchAgent <hello@orchagent.io>",
6
+ "author": "orchagent <hello@orchagent.io>",
7
7
  "homepage": "https://orchagent.io",
8
8
  "repository": {
9
9
  "type": "git",