@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.
- package/dist/commands/call.js +37 -10
- package/dist/commands/delete.js +12 -0
- package/dist/commands/doctor.js +27 -0
- package/dist/commands/github.js +543 -0
- package/dist/commands/index.js +6 -2
- package/dist/commands/publish.js +86 -2
- package/dist/commands/run.js +7 -2
- package/dist/commands/skill.js +10 -7
- package/dist/commands/status.js +97 -0
- package/dist/commands/workspace.js +133 -0
- package/dist/lib/api.js +30 -5
- package/dist/lib/auth-errors.js +27 -0
- package/dist/lib/browser-auth.js +5 -8
- package/dist/lib/bundle.js +48 -0
- package/dist/lib/doctor/checks/auth.js +115 -0
- package/dist/lib/doctor/checks/config.js +119 -0
- package/dist/lib/doctor/checks/connectivity.js +109 -0
- package/dist/lib/doctor/checks/environment.js +151 -0
- package/dist/lib/doctor/checks/llm.js +108 -0
- package/dist/lib/doctor/index.js +19 -0
- package/dist/lib/doctor/output.js +105 -0
- package/dist/lib/doctor/runner.js +68 -0
- package/dist/lib/doctor/types.js +2 -0
- package/dist/lib/errors.js +41 -1
- package/dist/lib/llm-errors.js +79 -0
- package/dist/lib/llm.js +4 -3
- package/package.json +1 -1
package/dist/commands/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/commands/publish.js
CHANGED
|
@@ -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,
|
package/dist/commands/run.js
CHANGED
|
@@ -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
|
|
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
|
|
454
|
+
throw (0, errors_1.jsonInputError)('input');
|
|
450
455
|
}
|
|
451
456
|
}
|
|
452
457
|
else if (args.length > 0) {
|
package/dist/commands/skill.js
CHANGED
|
@@ -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
|
|
83
|
-
if (version
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
}
|
package/dist/lib/browser-auth.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
package/dist/lib/bundle.js
CHANGED
|
@@ -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
|
*
|