@orchagent/cli 0.2.11 → 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.
@@ -10,12 +10,14 @@ 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");
17
16
  const skill_1 = require("./skill");
18
17
  const delete_1 = require("./delete");
18
+ const github_1 = require("./github");
19
+ const doctor_1 = require("./doctor");
20
+ const status_1 = require("./status");
19
21
  function registerCommands(program) {
20
22
  (0, login_1.registerLoginCommand)(program);
21
23
  (0, whoami_1.registerWhoamiCommand)(program);
@@ -28,8 +30,10 @@ function registerCommands(program) {
28
30
  (0, search_1.registerSearchCommand)(program);
29
31
  (0, star_1.registerStarCommand)(program);
30
32
  (0, fork_1.registerForkCommand)(program);
31
- (0, llm_config_1.registerLlmConfigCommand)(program);
32
33
  (0, keys_1.registerKeysCommand)(program);
33
34
  (0, skill_1.registerSkillCommand)(program);
34
35
  (0, delete_1.registerDeleteCommand)(program);
36
+ (0, github_1.registerGitHubCommand)(program);
37
+ (0, doctor_1.registerDoctorCommand)(program);
38
+ (0, status_1.registerStatusCommand)(program);
35
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
+ }
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerWorkspaceCommand = registerWorkspaceCommand;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ function registerWorkspaceCommand(program) {
7
+ const workspace = program
8
+ .command('workspace')
9
+ .description('Manage workspaces');
10
+ // List workspaces
11
+ workspace
12
+ .command('list')
13
+ .description('List all workspaces')
14
+ .action(async () => {
15
+ const config = await (0, config_1.getResolvedConfig)();
16
+ const fileConfig = await (0, config_1.loadConfig)();
17
+ const workspaces = await (0, api_1.listWorkspaces)(config);
18
+ if (workspaces.length === 0) {
19
+ process.stdout.write('No workspaces found.\n');
20
+ return;
21
+ }
22
+ const currentId = fileConfig.current_workspace;
23
+ for (const ws of workspaces) {
24
+ const isCurrent = ws.id === currentId || ws.slug === currentId;
25
+ const marker = isCurrent ? '* ' : ' ';
26
+ const type = ws.type === 'team' ? '(team)' : '(personal)';
27
+ const members = ws.type === 'team' ? ` - ${ws.member_count} members` : '';
28
+ process.stdout.write(`${marker}${ws.slug} - ${ws.name} ${type}${members}\n`);
29
+ }
30
+ });
31
+ // Switch workspace
32
+ workspace
33
+ .command('use <slug>')
34
+ .description('Switch to a different workspace')
35
+ .action(async (slug) => {
36
+ const config = await (0, config_1.getResolvedConfig)();
37
+ const workspaces = await (0, api_1.listWorkspaces)(config);
38
+ const target = workspaces.find(ws => ws.slug === slug || ws.id === slug);
39
+ if (!target) {
40
+ process.stderr.write(`Workspace "${slug}" not found.\n`);
41
+ process.exit(1);
42
+ }
43
+ const fileConfig = await (0, config_1.loadConfig)();
44
+ fileConfig.current_workspace = target.id;
45
+ await (0, config_1.saveConfig)(fileConfig);
46
+ process.stdout.write(`Switched to workspace: ${target.name} (${target.slug})\n`);
47
+ });
48
+ // Create workspace
49
+ workspace
50
+ .command('create <name>')
51
+ .description('Create a new team workspace')
52
+ .option('--slug <slug>', 'Workspace URL slug')
53
+ .action(async (name, options) => {
54
+ const config = await (0, config_1.getResolvedConfig)();
55
+ // Auto-generate slug from name if not provided
56
+ const slug = options.slug || name
57
+ .toLowerCase()
58
+ .replace(/\s+/g, '-')
59
+ .replace(/[^a-z0-9-]/g, '')
60
+ .replace(/-+/g, '-')
61
+ .slice(0, 30);
62
+ try {
63
+ const workspace = await (0, api_1.createWorkspace)(config, { name, slug });
64
+ // Switch to the new workspace
65
+ const fileConfig = await (0, config_1.loadConfig)();
66
+ fileConfig.current_workspace = workspace.id;
67
+ await (0, config_1.saveConfig)(fileConfig);
68
+ process.stdout.write(`Created workspace: ${workspace.name} (${workspace.slug})\n`);
69
+ process.stdout.write(`Switched to new workspace.\n`);
70
+ }
71
+ catch (err) {
72
+ process.stderr.write(`Failed to create workspace: ${err instanceof Error ? err.message : 'Unknown error'}\n`);
73
+ process.exit(1);
74
+ }
75
+ });
76
+ // Invite member
77
+ workspace
78
+ .command('invite <email>')
79
+ .description('Invite a member to the current workspace')
80
+ .option('--role <role>', 'Role: owner or member', 'member')
81
+ .action(async (email, options) => {
82
+ const config = await (0, config_1.getResolvedConfig)();
83
+ const fileConfig = await (0, config_1.loadConfig)();
84
+ if (!fileConfig.current_workspace) {
85
+ process.stderr.write('No workspace selected. Run `orch workspace use <slug>` first.\n');
86
+ process.exit(1);
87
+ }
88
+ const role = options.role;
89
+ if (role !== 'owner' && role !== 'member') {
90
+ process.stderr.write('Role must be "owner" or "member".\n');
91
+ process.exit(1);
92
+ }
93
+ try {
94
+ await (0, api_1.inviteToWorkspace)(config, fileConfig.current_workspace, { email, role });
95
+ process.stdout.write(`Invited ${email} as ${role}.\n`);
96
+ }
97
+ catch (err) {
98
+ process.stderr.write(`Failed to send invite: ${err instanceof Error ? err.message : 'Unknown error'}\n`);
99
+ process.exit(1);
100
+ }
101
+ });
102
+ // List members
103
+ workspace
104
+ .command('members')
105
+ .description('List members of the current workspace')
106
+ .action(async () => {
107
+ const config = await (0, config_1.getResolvedConfig)();
108
+ const fileConfig = await (0, config_1.loadConfig)();
109
+ if (!fileConfig.current_workspace) {
110
+ process.stderr.write('No workspace selected. Run `orch workspace use <slug>` first.\n');
111
+ process.exit(1);
112
+ }
113
+ try {
114
+ const { members, invites } = await (0, api_1.getWorkspaceMembers)(config, fileConfig.current_workspace);
115
+ process.stdout.write('Members:\n');
116
+ for (const member of members) {
117
+ const roleLabel = member.role === 'owner' ? '[owner]' : '[member]';
118
+ process.stdout.write(` ${member.clerk_user_id} ${roleLabel}\n`);
119
+ }
120
+ if (invites.length > 0) {
121
+ process.stdout.write('\nPending invites:\n');
122
+ for (const invite of invites) {
123
+ const expires = new Date(invite.expires_at).toLocaleDateString();
124
+ process.stdout.write(` ${invite.email} (${invite.role}) - expires ${expires}\n`);
125
+ }
126
+ }
127
+ }
128
+ catch (err) {
129
+ process.stderr.write(`Failed to list members: ${err instanceof Error ? err.message : 'Unknown error'}\n`);
130
+ process.exit(1);
131
+ }
132
+ });
133
+ }
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
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseAuthError = parseAuthError;
4
+ const errors_1 = require("./errors");
5
+ const AUTH_MESSAGES = {
6
+ 400: 'Invalid authentication request. Please try again.',
7
+ 401: 'Authentication failed. Run `orchagent login` again.',
8
+ 403: 'Access denied. You may not have permission for this organization.',
9
+ 429: 'Too many attempts. Wait a moment and try again.',
10
+ 500: 'Server error. Please try again later.',
11
+ 502: 'Server temporarily unavailable. Try again later.',
12
+ };
13
+ async function parseAuthError(response, context) {
14
+ try {
15
+ const json = await response.json();
16
+ const msg = json?.error?.message || json?.message;
17
+ if (msg && typeof msg === 'string' && msg.length < 200) {
18
+ return new errors_1.CliError(msg);
19
+ }
20
+ }
21
+ catch { }
22
+ const ctx = context === 'init'
23
+ ? 'Failed to start authentication'
24
+ : 'Failed to complete authentication';
25
+ const action = AUTH_MESSAGES[response.status] || 'Please try again.';
26
+ return new errors_1.CliError(`${ctx}: ${action}`);
27
+ }
@@ -8,6 +8,7 @@ exports.startBrowserAuth = startBrowserAuth;
8
8
  const http_1 = __importDefault(require("http"));
9
9
  const open_1 = __importDefault(require("open"));
10
10
  const errors_1 = require("./errors");
11
+ const auth_errors_1 = require("./auth-errors");
11
12
  const DEFAULT_PORT = 8374;
12
13
  const AUTH_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
13
14
  /**
@@ -26,8 +27,7 @@ async function browserAuthFlow(apiUrl, port = DEFAULT_PORT) {
26
27
  body: JSON.stringify({ redirect_port: port }),
27
28
  });
28
29
  if (!initResponse.ok) {
29
- const error = await initResponse.json().catch(() => ({}));
30
- throw new errors_1.CliError(error?.error?.message || `Failed to initialize auth: ${initResponse.statusText}`);
30
+ throw await (0, auth_errors_1.parseAuthError)(initResponse, 'init');
31
31
  }
32
32
  const { auth_url } = await initResponse.json();
33
33
  // Step 2: Start local server and wait for callback
@@ -39,8 +39,7 @@ async function browserAuthFlow(apiUrl, port = DEFAULT_PORT) {
39
39
  body: JSON.stringify({ token }),
40
40
  });
41
41
  if (!exchangeResponse.ok) {
42
- const error = await exchangeResponse.json().catch(() => ({}));
43
- throw new errors_1.CliError(error?.error?.message || `Failed to exchange token: ${exchangeResponse.statusText}`);
42
+ throw await (0, auth_errors_1.parseAuthError)(exchangeResponse, 'exchange');
44
43
  }
45
44
  const result = await exchangeResponse.json();
46
45
  // Step 4: Open browser (do this after server is ready but before waiting)
@@ -125,8 +124,7 @@ async function startBrowserAuth(apiUrl, port = DEFAULT_PORT) {
125
124
  body: JSON.stringify({ redirect_port: port }),
126
125
  });
127
126
  if (!initResponse.ok) {
128
- const error = await initResponse.json().catch(() => ({}));
129
- throw new errors_1.CliError(error?.error?.message || `Failed to initialize auth: ${initResponse.statusText}`);
127
+ throw await (0, auth_errors_1.parseAuthError)(initResponse, 'init');
130
128
  }
131
129
  const { auth_url } = await initResponse.json();
132
130
  // Step 2: Start local server to receive callback
@@ -148,8 +146,7 @@ async function startBrowserAuth(apiUrl, port = DEFAULT_PORT) {
148
146
  body: JSON.stringify({ token }),
149
147
  });
150
148
  if (!exchangeResponse.ok) {
151
- const error = await exchangeResponse.json().catch(() => ({}));
152
- throw new errors_1.CliError(error?.error?.message || `Failed to complete authentication: ${exchangeResponse.statusText}`);
149
+ throw await (0, auth_errors_1.parseAuthError)(exchangeResponse, 'exchange');
153
150
  }
154
151
  const result = await exchangeResponse.json();
155
152
  return {
@@ -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
  *