@orchagent/cli 0.2.12 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -90,7 +90,7 @@ async function resolveJsonBody(input) {
90
90
  return JSON.stringify(JSON.parse(raw));
91
91
  }
92
92
  catch {
93
- throw new errors_1.CliError('Invalid JSON input. Provide a valid JSON string or @file.');
93
+ throw (0, errors_1.jsonInputError)('data');
94
94
  }
95
95
  }
96
96
  function registerCallCommand(program) {
@@ -156,12 +156,11 @@ Note: Use 'call' for server-side execution (requires login), 'run' for local exe
156
156
  llmProvider = detected.provider;
157
157
  }
158
158
  }
159
- // Add LLM key to headers
160
- if (llmKey) {
161
- headers['X-LLM-API-Key'] = llmKey;
162
- if (llmProvider) {
163
- headers['X-LLM-Provider'] = llmProvider;
164
- }
159
+ // LLM credentials will be added to request body (not headers) for security
160
+ // Headers can be logged by proxies/load balancers, body is not logged by default
161
+ let llmCredentials;
162
+ if (llmKey && llmProvider) {
163
+ llmCredentials = { api_key: llmKey, provider: llmProvider };
165
164
  }
166
165
  else if (agentMeta.type === 'prompt') {
167
166
  // Warn if no key found for prompt-based agent
@@ -189,16 +188,44 @@ Note: Use 'call' for server-side execution (requires login), 'run' for local exe
189
188
  if (filePaths.length > 0 || options.metadata) {
190
189
  throw new errors_1.CliError('Cannot use --data with file uploads or --metadata.');
191
190
  }
192
- body = await resolveJsonBody(options.data);
191
+ // Parse JSON and inject llm_credentials if available
192
+ const resolvedBody = await resolveJsonBody(options.data);
193
+ if (llmCredentials) {
194
+ const bodyObj = JSON.parse(resolvedBody);
195
+ bodyObj.llm_credentials = llmCredentials;
196
+ body = JSON.stringify(bodyObj);
197
+ }
198
+ else {
199
+ body = resolvedBody;
200
+ }
201
+ headers['Content-Type'] = 'application/json';
202
+ }
203
+ else if (filePaths.length > 0 || options.metadata) {
204
+ // Handle multipart file uploads
205
+ // Inject llm_credentials into metadata if available
206
+ let metadata = options.metadata;
207
+ if (llmCredentials) {
208
+ const metaObj = metadata ? JSON.parse(metadata) : {};
209
+ metaObj.llm_credentials = llmCredentials;
210
+ metadata = JSON.stringify(metaObj);
211
+ }
212
+ const multipart = await buildMultipartBody(filePaths, metadata);
213
+ body = multipart.body;
214
+ sourceLabel = multipart.sourceLabel;
215
+ }
216
+ else if (llmCredentials) {
217
+ // No data or files, but we have LLM credentials - send as JSON body
218
+ body = JSON.stringify({ llm_credentials: llmCredentials });
193
219
  headers['Content-Type'] = 'application/json';
194
220
  }
195
221
  else {
196
- const multipart = await buildMultipartBody(filePaths.length ? filePaths : undefined, options.metadata);
222
+ // No data, files, or credentials - check for stdin
223
+ const multipart = await buildMultipartBody(undefined, options.metadata);
197
224
  body = multipart.body;
198
225
  sourceLabel = multipart.sourceLabel;
199
226
  }
200
227
  const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}`;
201
- const response = await fetch(url, {
228
+ const response = await (0, api_1.safeFetch)(url, {
202
229
  method: 'POST',
203
230
  headers,
204
231
  body,
@@ -29,6 +29,7 @@ function registerDeleteCommand(program) {
29
29
  .description('Delete an agent')
30
30
  .option('--version <version>', 'Specific version to delete (default: latest)')
31
31
  .option('-y, --yes', 'Skip confirmation prompt')
32
+ .option('--dry-run', 'Show what would be deleted without making changes')
32
33
  .action(async (agentName, options) => {
33
34
  const config = await (0, config_1.getResolvedConfig)();
34
35
  if (!config.apiKey) {
@@ -61,6 +62,17 @@ function registerDeleteCommand(program) {
61
62
  process.stdout.write(`${chalk_1.default.bold('Stars:')} ${deleteCheck.stars_count} ${chalk_1.default.bold('Forks:')} ${deleteCheck.fork_count}\n`);
62
63
  }
63
64
  process.stdout.write('\n');
65
+ // Handle dry-run
66
+ if (options.dryRun) {
67
+ process.stdout.write('\nDRY RUN - No changes will be made\n\n');
68
+ process.stdout.write(`Would delete: ${agent.name}@${agent.version}\n`);
69
+ if (deleteCheck.stars_count > 0 || deleteCheck.fork_count > 0) {
70
+ process.stdout.write(chalk_1.default.yellow('Warning: This agent has stars or forks\n'));
71
+ }
72
+ process.stdout.write(chalk_1.default.gray('\nData would be retained for 30 days before permanent deletion.\n'));
73
+ process.stdout.write('\nNo changes made (dry run)\n');
74
+ return;
75
+ }
64
76
  // Handle confirmation
65
77
  if (!options.yes) {
66
78
  if (deleteCheck.requires_confirmation) {
@@ -10,7 +10,6 @@ const whoami_1 = require("./whoami");
10
10
  const star_1 = require("./star");
11
11
  const fork_1 = require("./fork");
12
12
  const search_1 = require("./search");
13
- const llm_config_1 = require("./llm-config");
14
13
  const keys_1 = require("./keys");
15
14
  const run_1 = require("./run");
16
15
  const info_1 = require("./info");
@@ -18,6 +17,7 @@ const skill_1 = require("./skill");
18
17
  const delete_1 = require("./delete");
19
18
  const github_1 = require("./github");
20
19
  const doctor_1 = require("./doctor");
20
+ const status_1 = require("./status");
21
21
  function registerCommands(program) {
22
22
  (0, login_1.registerLoginCommand)(program);
23
23
  (0, whoami_1.registerWhoamiCommand)(program);
@@ -30,10 +30,10 @@ function registerCommands(program) {
30
30
  (0, search_1.registerSearchCommand)(program);
31
31
  (0, star_1.registerStarCommand)(program);
32
32
  (0, fork_1.registerForkCommand)(program);
33
- (0, llm_config_1.registerLlmConfigCommand)(program);
34
33
  (0, keys_1.registerKeysCommand)(program);
35
34
  (0, skill_1.registerSkillCommand)(program);
36
35
  (0, delete_1.registerDeleteCommand)(program);
37
36
  (0, github_1.registerGitHubCommand)(program);
38
37
  (0, doctor_1.registerDoctorCommand)(program);
38
+ (0, status_1.registerStatusCommand)(program);
39
39
  }
@@ -68,6 +68,7 @@ function registerPublishCommand(program) {
68
68
  .option('--public', 'Make agent public (default: true)', true)
69
69
  .option('--private', 'Make agent private')
70
70
  .option('--profile <name>', 'Use API key from named profile')
71
+ .option('--dry-run', 'Show what would be published without making changes')
71
72
  .action(async (options) => {
72
73
  const config = await (0, config_1.getResolvedConfig)({}, options.profile);
73
74
  const cwd = process.cwd();
@@ -77,6 +78,29 @@ function registerPublishCommand(program) {
77
78
  if (skillData) {
78
79
  // Publish as a skill (server auto-assigns version)
79
80
  const org = await (0, api_1.getOrg)(config);
81
+ // Handle dry-run for skills
82
+ if (options.dryRun) {
83
+ const preview = await (0, api_1.previewAgentVersion)(config, skillData.frontmatter.name);
84
+ const skillBodyBytes = Buffer.byteLength(skillData.body, 'utf-8');
85
+ const versionInfo = preview.existing_versions.length > 0
86
+ ? `${preview.next_version} (new version, ${preview.existing_versions[preview.existing_versions.length - 1]} exists)`
87
+ : `${preview.next_version} (first version)`;
88
+ process.stderr.write('\nDRY RUN - No changes will be made\n\n');
89
+ process.stderr.write('Validating...\n');
90
+ process.stderr.write(` ✓ SKILL.md found and valid\n`);
91
+ process.stderr.write(` ✓ Skill prompt (${skillBodyBytes.toLocaleString()} bytes)\n`);
92
+ process.stderr.write(` ✓ Authentication valid (org: ${org.slug})\n`);
93
+ process.stderr.write('\nSkill Preview:\n');
94
+ process.stderr.write(` Name: ${skillData.frontmatter.name}\n`);
95
+ process.stderr.write(` Type: skill\n`);
96
+ process.stderr.write(` Version: ${versionInfo}\n`);
97
+ process.stderr.write(` Visibility: ${options.private ? 'private' : 'public'}\n`);
98
+ process.stderr.write(` Providers: any\n`);
99
+ process.stderr.write(`\nWould publish: ${preview.org_slug}/${skillData.frontmatter.name}@${preview.next_version}\n`);
100
+ process.stderr.write(`API endpoint: POST ${config.apiUrl}/${preview.org_slug}/${skillData.frontmatter.name}/${preview.next_version}/run\n\n`);
101
+ process.stderr.write('No changes made (dry run)\n');
102
+ return;
103
+ }
80
104
  const skillResult = await (0, api_1.createAgent)(config, {
81
105
  name: skillData.frontmatter.name,
82
106
  type: 'skill',
@@ -118,7 +142,9 @@ function registerPublishCommand(program) {
118
142
  catch (err) {
119
143
  if (err.code === 'ENOENT') {
120
144
  const agentTypeName = manifest.type === 'skill' ? 'skill' : 'prompt-based agent';
121
- throw new errors_1.CliError(`No prompt.md found for ${agentTypeName}`);
145
+ throw new errors_1.CliError(`No prompt.md found for ${agentTypeName}.\n\n` +
146
+ 'Create a prompt.md file in the current directory with your prompt template.\n' +
147
+ 'See: https://orchagent.io/docs/publishing');
122
148
  }
123
149
  throw err;
124
150
  }
@@ -164,10 +190,68 @@ function registerPublishCommand(program) {
164
190
  let sdkCompatible = false;
165
191
  if (manifest.type === 'code') {
166
192
  sdkCompatible = await detectSdkCompatible(cwd);
167
- if (sdkCompatible) {
193
+ if (sdkCompatible && !options.dryRun) {
168
194
  process.stdout.write(`SDK detected - agent will be marked as Local Ready\n`);
169
195
  }
170
196
  }
197
+ // Handle dry-run for agents
198
+ if (options.dryRun) {
199
+ const preview = await (0, api_1.previewAgentVersion)(config, manifest.name);
200
+ const versionInfo = preview.existing_versions.length > 0
201
+ ? `${preview.next_version} (new version, ${preview.existing_versions[preview.existing_versions.length - 1]} exists)`
202
+ : `${preview.next_version} (first version)`;
203
+ process.stderr.write('\nDRY RUN - No changes will be made\n\n');
204
+ process.stderr.write('Validating...\n');
205
+ process.stderr.write(` ✓ orchagent.json found and valid\n`);
206
+ if (manifest.type === 'prompt') {
207
+ // Prompt agent validations
208
+ const promptBytes = prompt ? Buffer.byteLength(prompt, 'utf-8') : 0;
209
+ process.stderr.write(` ✓ prompt.md found (${promptBytes.toLocaleString()} bytes)\n`);
210
+ if (inputSchema || outputSchema) {
211
+ const schemaTypes = [inputSchema ? 'input' : null, outputSchema ? 'output' : null].filter(Boolean).join(' + ');
212
+ process.stderr.write(` ✓ schema.json found (${schemaTypes} schemas)\n`);
213
+ }
214
+ }
215
+ else if (manifest.type === 'code') {
216
+ // Code agent validations
217
+ const entrypoint = manifest.entrypoint || await (0, bundle_1.detectEntrypoint)(cwd);
218
+ process.stderr.write(` ✓ Entrypoint: ${entrypoint}\n`);
219
+ if (sdkCompatible) {
220
+ process.stderr.write(` ✓ SDK detected (orchagent-sdk in requirements.txt)\n`);
221
+ }
222
+ }
223
+ process.stderr.write(` ✓ Authentication valid (org: ${org.slug})\n`);
224
+ // For code agents with bundles, show bundle preview
225
+ if (shouldUploadBundle) {
226
+ const bundlePreview = await (0, bundle_1.previewBundle)(cwd, {
227
+ entrypoint: manifest.entrypoint,
228
+ exclude: manifest.bundle?.exclude,
229
+ include: manifest.bundle?.include,
230
+ });
231
+ process.stderr.write(`\nBundle Preview:\n`);
232
+ process.stderr.write(` Files: ${bundlePreview.fileCount} files\n`);
233
+ process.stderr.write(` Size: ${(bundlePreview.totalSizeBytes / 1024).toFixed(1)} KB\n`);
234
+ process.stderr.write(` Entrypoint: ${bundlePreview.entrypoint}\n`);
235
+ }
236
+ process.stderr.write('\nAgent Preview:\n');
237
+ process.stderr.write(` Name: ${manifest.name}\n`);
238
+ process.stderr.write(` Type: ${manifest.type}${shouldUploadBundle ? ' (hosted)' : ''}\n`);
239
+ process.stderr.write(` Version: ${versionInfo}\n`);
240
+ process.stderr.write(` Visibility: ${options.private ? 'private' : 'public'}\n`);
241
+ process.stderr.write(` Providers: ${supportedProviders.join(', ')}\n`);
242
+ process.stderr.write(`\nWould publish: ${preview.org_slug}/${manifest.name}@${preview.next_version}\n`);
243
+ if (shouldUploadBundle) {
244
+ const bundlePreview = await (0, bundle_1.previewBundle)(cwd, {
245
+ entrypoint: manifest.entrypoint,
246
+ exclude: manifest.bundle?.exclude,
247
+ include: manifest.bundle?.include,
248
+ });
249
+ process.stderr.write(`Would upload bundle: ${(bundlePreview.totalSizeBytes / 1024).toFixed(1)} KB\n`);
250
+ }
251
+ process.stderr.write(`API endpoint: POST ${config.apiUrl}/${preview.org_slug}/${manifest.name}/${preview.next_version}/run\n\n`);
252
+ process.stderr.write('No changes made (dry run)\n');
253
+ return;
254
+ }
171
255
  // Create the agent (server auto-assigns version)
172
256
  const result = await (0, api_1.createAgent)(config, {
173
257
  name: manifest.name,
@@ -339,7 +339,12 @@ async function installCodeAgent(agentData) {
339
339
  process.stderr.write(`Installing ${installSource}...\n`);
340
340
  const { code } = await runCommand('python3', ['-m', 'pip', 'install', '--quiet', installSource]);
341
341
  if (code !== 0) {
342
- throw new errors_1.CliError(`Failed to install agent. Exit code: ${code}`);
342
+ throw new errors_1.CliError(`Failed to install agent (exit code ${code}).\n\n` +
343
+ 'Troubleshooting:\n' +
344
+ ' - Check Python is installed: python3 --version\n' +
345
+ ' - Check pip is available: pip --version\n' +
346
+ ' - Check network connectivity\n' +
347
+ ' - Try installing manually: pip install <package>');
343
348
  }
344
349
  process.stderr.write(`Installation complete.\n`);
345
350
  }
@@ -446,7 +451,7 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
446
451
  inputJson = JSON.stringify(parsed);
447
452
  }
448
453
  catch {
449
- throw new errors_1.CliError('Invalid JSON in --input option');
454
+ throw (0, errors_1.jsonInputError)('input');
450
455
  }
451
456
  }
452
457
  else if (args.length > 0) {
@@ -79,18 +79,19 @@ async function downloadSkillWithFallback(config, org, skill, version) {
79
79
  if (matching.length === 0) {
80
80
  throw new api_1.ApiError(`Skill '${org}/${skill}@${version}' not found`, 404);
81
81
  }
82
- let targetAgent = matching[0];
83
- if (version !== 'v1' && version !== 'latest') {
82
+ let targetAgent;
83
+ if (version === 'latest') {
84
+ // For 'latest', get most recently created
85
+ targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
86
+ }
87
+ else {
88
+ // For any explicit version (v1, v2, etc.), find exact match
84
89
  const found = matching.find(a => a.version === version);
85
90
  if (!found) {
86
91
  throw new api_1.ApiError(`Skill '${org}/${skill}@${version}' not found`, 404);
87
92
  }
88
93
  targetAgent = found;
89
94
  }
90
- else {
91
- // Get most recent
92
- targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
93
- }
94
95
  // Verify it's a skill type
95
96
  if (targetAgent.type !== 'skill') {
96
97
  throw new errors_1.CliError(`${org}/${skill} is not a skill (type: ${targetAgent.type || 'prompt'})`);
@@ -169,7 +170,9 @@ Instructions and guidance for AI agents...
169
170
  // Download skill (tries public first, falls back to authenticated for private)
170
171
  const skillData = await downloadSkillWithFallback(resolved, org, parsed.skill, parsed.version);
171
172
  if (!skillData.prompt) {
172
- throw new errors_1.CliError('Skill has no content');
173
+ throw new errors_1.CliError('Skill has no content.\n\n' +
174
+ 'The skill exists but has an empty prompt. This may be a publishing issue.\n' +
175
+ 'Try re-publishing the skill or contact the skill author.');
173
176
  }
174
177
  // Determine base directory (home or current)
175
178
  const baseDir = options.global ? os_1.default.homedir() : process.cwd();
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerStatusCommand = registerStatusCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const output_1 = require("../lib/output");
9
+ const STATUS_URL = 'https://api.orchagent.io/health/detailed';
10
+ const STATUS_PAGE_URL = 'https://status.orchagent.io';
11
+ function getStatusIcon(status) {
12
+ switch (status) {
13
+ case 'operational':
14
+ return chalk_1.default.green('\u2713');
15
+ case 'degraded':
16
+ return chalk_1.default.yellow('\u26a0');
17
+ case 'outage':
18
+ return chalk_1.default.red('\u2717');
19
+ default:
20
+ return chalk_1.default.gray('?');
21
+ }
22
+ }
23
+ function getStatusLabel(status) {
24
+ switch (status) {
25
+ case 'operational':
26
+ return chalk_1.default.green('Operational');
27
+ case 'degraded':
28
+ return chalk_1.default.yellow('Degraded');
29
+ case 'outage':
30
+ return chalk_1.default.red('Outage');
31
+ default:
32
+ return chalk_1.default.gray('Unknown');
33
+ }
34
+ }
35
+ function getOverallStatus(services) {
36
+ const hasOutage = services.some((s) => s.status === 'outage');
37
+ const hasDegraded = services.some((s) => s.status === 'degraded');
38
+ if (hasOutage) {
39
+ return chalk_1.default.red('Service Outage');
40
+ }
41
+ if (hasDegraded) {
42
+ return chalk_1.default.yellow('Partial Outage');
43
+ }
44
+ return chalk_1.default.green('All Systems Operational');
45
+ }
46
+ function formatLatency(latencyMs) {
47
+ if (latencyMs === undefined) {
48
+ return '';
49
+ }
50
+ return chalk_1.default.gray(` (${latencyMs}ms)`);
51
+ }
52
+ function printHumanStatus(data) {
53
+ const overallStatus = getOverallStatus(data.services);
54
+ process.stdout.write(`\nOrchAgent Status: ${overallStatus}\n\n`);
55
+ for (const service of data.services) {
56
+ const icon = getStatusIcon(service.status);
57
+ const label = getStatusLabel(service.status);
58
+ const latency = formatLatency(service.latency_ms);
59
+ const paddedName = service.name.padEnd(18);
60
+ process.stdout.write(` ${paddedName} ${icon} ${label}${latency}\n`);
61
+ }
62
+ process.stdout.write(`\nView details: ${chalk_1.default.cyan(STATUS_PAGE_URL)}\n`);
63
+ }
64
+ function registerStatusCommand(program) {
65
+ program
66
+ .command('status')
67
+ .description('Check OrchAgent service status')
68
+ .option('--json', 'Output raw JSON')
69
+ .action(async (options) => {
70
+ try {
71
+ const response = await fetch(STATUS_URL, {
72
+ headers: {
73
+ Accept: 'application/json',
74
+ },
75
+ });
76
+ if (!response.ok) {
77
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
78
+ }
79
+ const data = (await response.json());
80
+ if (options.json) {
81
+ (0, output_1.printJson)(data);
82
+ return;
83
+ }
84
+ printHumanStatus(data);
85
+ }
86
+ catch (err) {
87
+ if (options.json) {
88
+ (0, output_1.printJson)({ error: 'Unable to fetch status', details: String(err) });
89
+ process.exit(1);
90
+ }
91
+ process.stderr.write(chalk_1.default.red('\nUnable to fetch status\n\n') +
92
+ chalk_1.default.gray(`Error: ${err instanceof Error ? err.message : String(err)}\n\n`) +
93
+ `Check ${chalk_1.default.cyan(STATUS_PAGE_URL)} for current status.\n`);
94
+ process.exit(1);
95
+ }
96
+ });
97
+ }
package/dist/lib/api.js CHANGED
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ApiError = void 0;
37
+ exports.safeFetch = safeFetch;
37
38
  exports.request = request;
38
39
  exports.publicRequest = publicRequest;
39
40
  exports.getOrg = getOrg;
@@ -54,6 +55,24 @@ exports.getAgentWithFallback = getAgentWithFallback;
54
55
  exports.downloadCodeBundleAuthenticated = downloadCodeBundleAuthenticated;
55
56
  exports.checkAgentDelete = checkAgentDelete;
56
57
  exports.deleteAgent = deleteAgent;
58
+ exports.previewAgentVersion = previewAgentVersion;
59
+ const errors_1 = require("./errors");
60
+ const DEFAULT_TIMEOUT_MS = 15000;
61
+ async function safeFetch(url, options) {
62
+ try {
63
+ return await fetch(url, {
64
+ ...options,
65
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS),
66
+ });
67
+ }
68
+ catch (err) {
69
+ if (err instanceof Error && (err.name === 'AbortError' || err.name === 'TimeoutError')) {
70
+ throw new errors_1.NetworkError(url, err);
71
+ }
72
+ // Network errors (ECONNREFUSED, DNS failure, etc.)
73
+ throw new errors_1.NetworkError(url, err instanceof Error ? err : undefined);
74
+ }
75
+ }
57
76
  class ApiError extends Error {
58
77
  status;
59
78
  payload;
@@ -88,7 +107,7 @@ async function request(config, method, path, options = {}) {
88
107
  if (!config.apiKey) {
89
108
  throw new ApiError('Missing API key. Run `orchagent login` first.', 401);
90
109
  }
91
- const response = await fetch(buildUrl(config.apiUrl, path), {
110
+ const response = await safeFetch(buildUrl(config.apiUrl, path), {
92
111
  method,
93
112
  headers: {
94
113
  Authorization: `Bearer ${config.apiKey}`,
@@ -102,7 +121,7 @@ async function request(config, method, path, options = {}) {
102
121
  return (await response.json());
103
122
  }
104
123
  async function publicRequest(config, path) {
105
- const response = await fetch(buildUrl(config.apiUrl, path));
124
+ const response = await safeFetch(buildUrl(config.apiUrl, path));
106
125
  if (!response.ok) {
107
126
  throw await parseError(response);
108
127
  }
@@ -161,7 +180,7 @@ async function fetchLlmKeys(config) {
161
180
  * Download a code bundle for local execution.
162
181
  */
163
182
  async function downloadCodeBundle(config, org, agent, version) {
164
- const response = await fetch(`${config.apiUrl.replace(/\/$/, '')}/public/agents/${org}/${agent}/${version}/bundle`);
183
+ const response = await safeFetch(`${config.apiUrl.replace(/\/$/, '')}/public/agents/${org}/${agent}/${version}/bundle`);
165
184
  if (!response.ok) {
166
185
  throw await parseError(response);
167
186
  }
@@ -189,7 +208,7 @@ async function uploadCodeBundle(config, agentId, bundlePath, entrypoint) {
189
208
  if (entrypoint) {
190
209
  headers['x-entrypoint'] = entrypoint;
191
210
  }
192
- const response = await fetch(`${config.apiUrl.replace(/\/$/, '')}/agents/${agentId}/upload`, {
211
+ const response = await safeFetch(`${config.apiUrl.replace(/\/$/, '')}/agents/${agentId}/upload`, {
193
212
  method: 'POST',
194
213
  headers,
195
214
  body: formData,
@@ -243,7 +262,7 @@ async function downloadCodeBundleAuthenticated(config, agentId) {
243
262
  if (!config.apiKey) {
244
263
  throw new ApiError('Missing API key for authenticated bundle download', 401);
245
264
  }
246
- const response = await fetch(`${config.apiUrl.replace(/\/$/, '')}/agents/${agentId}/bundle`, {
265
+ const response = await safeFetch(`${config.apiUrl.replace(/\/$/, '')}/agents/${agentId}/bundle`, {
247
266
  headers: {
248
267
  Authorization: `Bearer ${config.apiKey}`,
249
268
  },
@@ -276,3 +295,9 @@ async function deleteAgent(config, agentId, confirmationName) {
276
295
  const params = confirmationName ? `?confirmation_name=${encodeURIComponent(confirmationName)}` : '';
277
296
  return request(config, 'DELETE', `/agents/${agentId}${params}`);
278
297
  }
298
+ /**
299
+ * Preview the next version number for an agent.
300
+ */
301
+ async function previewAgentVersion(config, agentName) {
302
+ return request(config, 'GET', `/agents/preview?name=${encodeURIComponent(agentName)}`);
303
+ }
@@ -10,11 +10,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.createCodeBundle = createCodeBundle;
12
12
  exports.detectEntrypoint = detectEntrypoint;
13
+ exports.previewBundle = previewBundle;
13
14
  exports.validateBundle = validateBundle;
14
15
  const promises_1 = __importDefault(require("fs/promises"));
15
16
  const path_1 = __importDefault(require("path"));
16
17
  const archiver_1 = __importDefault(require("archiver"));
17
18
  const fs_1 = require("fs");
19
+ const glob_1 = require("glob");
18
20
  /** Default patterns to exclude from bundles */
19
21
  const DEFAULT_EXCLUDES = [
20
22
  // Python
@@ -184,6 +186,52 @@ async function detectEntrypoint(projectDir) {
184
186
  }
185
187
  return null;
186
188
  }
189
+ /**
190
+ * Preview what would be bundled without creating the actual bundle.
191
+ *
192
+ * @param sourceDir - The directory to analyze
193
+ * @param options - Bundle options
194
+ * @returns Preview information about the bundle
195
+ */
196
+ async function previewBundle(sourceDir, options = {}) {
197
+ const excludePatterns = [...DEFAULT_EXCLUDES, ...(options.exclude || [])];
198
+ // Verify source directory exists
199
+ const stat = await promises_1.default.stat(sourceDir);
200
+ if (!stat.isDirectory()) {
201
+ throw new Error(`Source path is not a directory: ${sourceDir}`);
202
+ }
203
+ // Detect or use provided entrypoint
204
+ let entrypoint = options.entrypoint;
205
+ if (!entrypoint) {
206
+ const detected = await detectEntrypoint(sourceDir);
207
+ entrypoint = detected || 'main.py';
208
+ }
209
+ // Find all files that would be included (matching createCodeBundle's glob pattern)
210
+ const files = await (0, glob_1.glob)('**/*', {
211
+ cwd: sourceDir,
212
+ ignore: excludePatterns,
213
+ dot: false,
214
+ nodir: true,
215
+ });
216
+ // Calculate total size by reading file stats
217
+ let totalSizeBytes = 0;
218
+ for (const file of files) {
219
+ const filePath = path_1.default.join(sourceDir, file);
220
+ try {
221
+ const fileStat = await promises_1.default.stat(filePath);
222
+ totalSizeBytes += fileStat.size;
223
+ }
224
+ catch {
225
+ // Skip files that can't be stat'd (e.g., broken symlinks)
226
+ }
227
+ }
228
+ return {
229
+ fileCount: files.length,
230
+ totalSizeBytes,
231
+ entrypoint,
232
+ excludePatterns,
233
+ };
234
+ }
187
235
  /**
188
236
  * Validate a bundle before upload.
189
237
  *
@@ -34,7 +34,7 @@ async function checkServerLlmKeys() {
34
34
  name: 'server_llm_keys',
35
35
  status: 'warning',
36
36
  message: 'No LLM keys configured on server',
37
- fix: 'Run `orch llm-config` or add keys at orchagent.io/settings',
37
+ fix: 'Run `orch keys add <provider>` or add keys at orchagent.io/settings',
38
38
  details: { count: 0, providers: [] },
39
39
  };
40
40
  }
@@ -33,13 +33,15 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.CliError = void 0;
36
+ exports.NetworkError = exports.ExitCodes = exports.CliError = void 0;
37
37
  exports.formatError = formatError;
38
38
  exports.exitWithError = exitWithError;
39
+ exports.jsonInputError = jsonInputError;
39
40
  const Sentry = __importStar(require("@sentry/node"));
40
41
  const analytics_1 = require("./analytics");
41
42
  class CliError extends Error {
42
43
  exitCode;
44
+ cause;
43
45
  constructor(message, exitCode = 1) {
44
46
  super(message);
45
47
  this.exitCode = exitCode;
@@ -73,3 +75,41 @@ async function exitWithError(err) {
73
75
  }
74
76
  process.exit(1);
75
77
  }
78
+ exports.ExitCodes = {
79
+ SUCCESS: 0,
80
+ GENERAL_ERROR: 1,
81
+ AUTH_ERROR: 2,
82
+ PERMISSION_DENIED: 3,
83
+ NOT_FOUND: 4,
84
+ INVALID_INPUT: 5,
85
+ RATE_LIMITED: 6,
86
+ TIMEOUT: 7,
87
+ SERVER_ERROR: 8,
88
+ NETWORK_ERROR: 9,
89
+ };
90
+ class NetworkError extends CliError {
91
+ constructor(url, cause) {
92
+ const host = new URL(url).host;
93
+ super(`Unable to connect to ${host}\n\n` +
94
+ 'Possible causes:\n' +
95
+ ' - Network connectivity issues\n' +
96
+ ' - Service temporarily unavailable\n' +
97
+ ' - Firewall or proxy blocking the request\n\n' +
98
+ 'Check status at: https://status.orchagent.io', exports.ExitCodes.NETWORK_ERROR);
99
+ this.cause = cause;
100
+ }
101
+ }
102
+ exports.NetworkError = NetworkError;
103
+ function jsonInputError(flag) {
104
+ return new CliError(`Invalid JSON in --${flag} option.\n\n` +
105
+ 'Common causes:\n' +
106
+ ' - Shell special characters (!, $, `) need escaping\n' +
107
+ ' - Missing or mismatched quotes\n\n' +
108
+ 'Shell-specific tips:\n' +
109
+ ' - Bash/Zsh: Use single quotes: --data \'{"key": "value"}\'\n' +
110
+ ' - PowerShell: Use double quotes and escape: --data "{\\"key\\": \\"value\\"}"\n' +
111
+ ' - Any shell: Use a file: --data @input.json\n\n' +
112
+ 'Alternatives:\n' +
113
+ ` - Use a file: --${flag} @input.json\n` +
114
+ ` - Use stdin: echo '{"key":"value"}' | orch call agent --${flag} @-`, exports.ExitCodes.INVALID_INPUT);
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Command-line interface for the OrchAgent AI agent marketplace",
5
5
  "license": "MIT",
6
6
  "author": "OrchAgent <hello@orchagent.io>",