@orchagent/cli 0.2.12 → 0.2.14
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 +2 -3
- package/dist/commands/index.js +2 -2
- package/dist/commands/publish.js +86 -2
- package/dist/commands/run.js +7 -2
- package/dist/commands/skill.js +23 -7
- package/dist/commands/status.js +101 -0
- package/dist/lib/api.js +30 -5
- package/dist/lib/bundle.js +48 -0
- package/dist/lib/doctor/checks/llm.js +12 -3
- package/dist/lib/doctor/output.js +4 -3
- package/dist/lib/doctor/runner.js +2 -1
- package/dist/lib/errors.js +41 -1
- package/package.json +1 -1
package/dist/commands/call.js
CHANGED
|
@@ -90,7 +90,7 @@ async function resolveJsonBody(input) {
|
|
|
90
90
|
return JSON.stringify(JSON.parse(raw));
|
|
91
91
|
}
|
|
92
92
|
catch {
|
|
93
|
-
throw
|
|
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
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
228
|
+
const response = await (0, api_1.safeFetch)(url, {
|
|
202
229
|
method: 'POST',
|
|
203
230
|
headers,
|
|
204
231
|
body,
|
package/dist/commands/delete.js
CHANGED
|
@@ -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) {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -18,9 +18,8 @@ function registerDoctorCommand(program) {
|
|
|
18
18
|
else {
|
|
19
19
|
(0, doctor_1.printHumanOutput)(results, summary, options.verbose ?? false);
|
|
20
20
|
}
|
|
21
|
-
// Exit with code 1 if
|
|
22
|
-
|
|
23
|
-
if (hasIssues) {
|
|
21
|
+
// Exit with code 1 only if there are errors (not warnings)
|
|
22
|
+
if (summary.errors > 0) {
|
|
24
23
|
process.exit(1);
|
|
25
24
|
}
|
|
26
25
|
});
|
package/dist/commands/index.js
CHANGED
|
@@ -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
|
}
|
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'})`);
|
|
@@ -159,6 +160,7 @@ Instructions and guidance for AI agents...
|
|
|
159
160
|
.command('install <skill>')
|
|
160
161
|
.description('Install skill to local AI tool directories (Claude Code, Cursor, etc.)')
|
|
161
162
|
.option('--global', 'Install to home directory (default: current directory)')
|
|
163
|
+
.option('--dry-run', 'Show what would be installed without making changes')
|
|
162
164
|
.action(async (skillRef, options) => {
|
|
163
165
|
const resolved = await (0, config_1.getResolvedConfig)();
|
|
164
166
|
const parsed = parseSkillRef(skillRef);
|
|
@@ -169,7 +171,9 @@ Instructions and guidance for AI agents...
|
|
|
169
171
|
// Download skill (tries public first, falls back to authenticated for private)
|
|
170
172
|
const skillData = await downloadSkillWithFallback(resolved, org, parsed.skill, parsed.version);
|
|
171
173
|
if (!skillData.prompt) {
|
|
172
|
-
throw new errors_1.CliError('Skill has no content'
|
|
174
|
+
throw new errors_1.CliError('Skill has no content.\n\n' +
|
|
175
|
+
'The skill exists but has an empty prompt. This may be a publishing issue.\n' +
|
|
176
|
+
'Try re-publishing the skill or contact the skill author.');
|
|
173
177
|
}
|
|
174
178
|
// Determine base directory (home or current)
|
|
175
179
|
const baseDir = options.global ? os_1.default.homedir() : process.cwd();
|
|
@@ -182,6 +186,18 @@ ${skillData.description || ''}
|
|
|
182
186
|
|
|
183
187
|
${skillData.prompt}
|
|
184
188
|
`;
|
|
189
|
+
// Dry run - show what would be installed
|
|
190
|
+
if (options.dryRun) {
|
|
191
|
+
process.stdout.write(`Would install ${org}/${parsed.skill}@${parsed.version}\n\n`);
|
|
192
|
+
process.stdout.write(`Target directories:\n`);
|
|
193
|
+
for (const tool of AI_TOOL_SKILL_DIRS) {
|
|
194
|
+
const skillDir = path_1.default.join(baseDir, tool.path);
|
|
195
|
+
const skillFile = path_1.default.join(skillDir, `${parsed.skill}.md`);
|
|
196
|
+
process.stdout.write(` - ${tool.name}: ${skillFile}\n`);
|
|
197
|
+
}
|
|
198
|
+
process.stdout.write(`\nNo changes made (dry run)\n`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
185
201
|
// Install to all AI tool directories
|
|
186
202
|
const installed = [];
|
|
187
203
|
for (const tool of AI_TOOL_SKILL_DIRS) {
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
// Handle missing or malformed services array
|
|
81
|
+
if (!data.services || !Array.isArray(data.services)) {
|
|
82
|
+
data.services = [];
|
|
83
|
+
}
|
|
84
|
+
if (options.json) {
|
|
85
|
+
(0, output_1.printJson)(data);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
printHumanStatus(data);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
if (options.json) {
|
|
92
|
+
(0, output_1.printJson)({ error: 'Unable to fetch status', details: String(err) });
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
process.stderr.write(chalk_1.default.red('\nUnable to fetch status\n\n') +
|
|
96
|
+
chalk_1.default.gray(`Error: ${err instanceof Error ? err.message : String(err)}\n\n`) +
|
|
97
|
+
`Check ${chalk_1.default.cyan(STATUS_PAGE_URL)} for current status.\n`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
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
|
+
}
|
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
|
*
|
|
@@ -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
|
|
37
|
+
fix: 'Run `orch keys add <provider>` or add keys at orchagent.io/settings',
|
|
38
38
|
details: { count: 0, providers: [] },
|
|
39
39
|
};
|
|
40
40
|
}
|
|
@@ -101,8 +101,17 @@ async function checkLocalLlmEnvVars() {
|
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
103
|
* Run all LLM configuration checks.
|
|
104
|
+
* If server keys are configured, local keys warning becomes informational.
|
|
104
105
|
*/
|
|
105
106
|
async function runLlmChecks() {
|
|
106
|
-
const
|
|
107
|
-
|
|
107
|
+
const serverResult = await checkServerLlmKeys();
|
|
108
|
+
const localResult = await checkLocalLlmEnvVars();
|
|
109
|
+
// If server keys are configured, downgrade local keys warning to info
|
|
110
|
+
// Users who only use server-side calls don't need local keys
|
|
111
|
+
if (serverResult.status === 'success' && localResult.status === 'warning') {
|
|
112
|
+
localResult.status = 'info';
|
|
113
|
+
localResult.message = 'No local LLM API keys (using server keys)';
|
|
114
|
+
localResult.fix = undefined;
|
|
115
|
+
}
|
|
116
|
+
return [serverResult, localResult];
|
|
108
117
|
}
|
|
@@ -11,6 +11,7 @@ const SYMBOLS = {
|
|
|
11
11
|
success: chalk_1.default.green('\u2713'), // checkmark
|
|
12
12
|
warning: chalk_1.default.yellow('\u26a0'), // warning sign
|
|
13
13
|
error: chalk_1.default.red('\u2717'), // X mark
|
|
14
|
+
info: chalk_1.default.blue('\u2139'), // info sign
|
|
14
15
|
};
|
|
15
16
|
// Category display names
|
|
16
17
|
const CATEGORY_NAMES = {
|
|
@@ -65,10 +66,10 @@ function printHumanOutput(results, summary, verbose) {
|
|
|
65
66
|
const displayName = CATEGORY_NAMES[category] || category;
|
|
66
67
|
process.stdout.write(chalk_1.default.bold(`${displayName}\n`));
|
|
67
68
|
for (const check of checks) {
|
|
68
|
-
const symbol = SYMBOLS[check.status];
|
|
69
|
+
const symbol = SYMBOLS[check.status] || SYMBOLS.info;
|
|
69
70
|
process.stdout.write(` ${symbol} ${check.message}\n`);
|
|
70
|
-
// Show fix suggestion for warnings/errors
|
|
71
|
-
if (check.fix && check.status
|
|
71
|
+
// Show fix suggestion for warnings/errors (not for success/info)
|
|
72
|
+
if (check.fix && (check.status === 'warning' || check.status === 'error')) {
|
|
72
73
|
process.stdout.write(chalk_1.default.dim(` \u2192 ${check.fix}\n`));
|
|
73
74
|
}
|
|
74
75
|
// Show details in verbose mode
|
|
@@ -58,10 +58,11 @@ async function runAllChecks() {
|
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
60
|
* Calculate summary statistics from check results.
|
|
61
|
+
* 'info' status counts as passed (informational, not a problem).
|
|
61
62
|
*/
|
|
62
63
|
function calculateSummary(results) {
|
|
63
64
|
return {
|
|
64
|
-
passed: results.filter((r) => r.status === 'success').length,
|
|
65
|
+
passed: results.filter((r) => r.status === 'success' || r.status === 'info').length,
|
|
65
66
|
warnings: results.filter((r) => r.status === 'warning').length,
|
|
66
67
|
errors: results.filter((r) => r.status === 'error').length,
|
|
67
68
|
};
|
package/dist/lib/errors.js
CHANGED
|
@@ -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
|
+
}
|