@orchagent/cli 0.3.84 → 0.3.85
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/adapters/claude-code.js +4 -8
- package/dist/commands/init.js +151 -9
- package/dist/commands/logs.js +40 -4
- package/dist/commands/publish.js +89 -11
- package/dist/commands/pull.js +8 -2
- package/dist/commands/run.js +16 -11
- package/dist/commands/schedule.js +29 -2
- package/dist/commands/secrets.js +2 -1
- package/dist/commands/service.js +10 -3
- package/dist/commands/skill.js +8 -8
- package/dist/commands/test.js +1 -1
- package/dist/lib/doctor/checks/environment.js +145 -0
- package/dist/lib/doctor/output.js +1 -1
- package/package.json +3 -2
|
@@ -37,13 +37,6 @@ exports.claudeCodeAdapter = {
|
|
|
37
37
|
errors.push('Agent has no prompt content');
|
|
38
38
|
return { canConvert: false, warnings, errors };
|
|
39
39
|
}
|
|
40
|
-
// Warnings for fields that can't be fully mapped
|
|
41
|
-
if (agent.input_schema) {
|
|
42
|
-
warnings.push('input_schema will be described in the prompt body, not enforced');
|
|
43
|
-
}
|
|
44
|
-
if (agent.output_schema) {
|
|
45
|
-
warnings.push('output_schema will be described in the prompt body, not enforced');
|
|
46
|
-
}
|
|
47
40
|
return { canConvert: true, warnings, errors };
|
|
48
41
|
},
|
|
49
42
|
convert(agent) {
|
|
@@ -52,8 +45,11 @@ exports.claudeCodeAdapter = {
|
|
|
52
45
|
const frontmatter = {
|
|
53
46
|
name: normalizedName,
|
|
54
47
|
description: agent.description || `Delegatable agent: ${agent.name}`,
|
|
55
|
-
tools: 'Read, Glob, Grep', // Safe defaults - read-only
|
|
56
48
|
};
|
|
49
|
+
// Only include tools for agent-type (prompt and skill types are single LLM calls)
|
|
50
|
+
if (agent.type === 'agent') {
|
|
51
|
+
frontmatter.tools = 'Read, Glob, Grep'; // Safe defaults - read-only
|
|
52
|
+
}
|
|
57
53
|
// Map model if specified
|
|
58
54
|
if (agent.default_models?.anthropic) {
|
|
59
55
|
const modelAlias = (0, utils_1.mapModelToAlias)(agent.default_models.anthropic);
|
package/dist/commands/init.js
CHANGED
|
@@ -123,6 +123,133 @@ function main() {
|
|
|
123
123
|
|
|
124
124
|
main();
|
|
125
125
|
`;
|
|
126
|
+
const ALWAYS_ON_TEMPLATE_PY = `"""
|
|
127
|
+
orchagent always-on service entrypoint.
|
|
128
|
+
|
|
129
|
+
Runs a long-lived HTTP server that handles requests over HTTP.
|
|
130
|
+
This is the standard pattern for always-on services on orchagent.
|
|
131
|
+
|
|
132
|
+
IMPORTANT: Port 8080 is reserved by the platform health server.
|
|
133
|
+
Use a different port (default: 3000).
|
|
134
|
+
|
|
135
|
+
Local development:
|
|
136
|
+
python main.py
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
import json
|
|
140
|
+
import os
|
|
141
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
142
|
+
|
|
143
|
+
PORT = int(os.environ.get("PORT", "3000"))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class Handler(BaseHTTPRequestHandler):
|
|
147
|
+
def do_POST(self):
|
|
148
|
+
content_length = int(self.headers.get("Content-Length", 0))
|
|
149
|
+
body = self.rfile.read(content_length)
|
|
150
|
+
try:
|
|
151
|
+
data = json.loads(body) if body else {}
|
|
152
|
+
except json.JSONDecodeError:
|
|
153
|
+
self._respond(400, {"error": "Invalid JSON"})
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
user_input = data.get("input", "")
|
|
157
|
+
|
|
158
|
+
# --- Your logic here ---
|
|
159
|
+
# To use workspace secrets, add them to "required_secrets" in orchagent.json:
|
|
160
|
+
# "required_secrets": ["MY_API_KEY"]
|
|
161
|
+
# Then access via: os.environ["MY_API_KEY"]
|
|
162
|
+
result = f"Received: {user_input}"
|
|
163
|
+
# --- End your logic ---
|
|
164
|
+
|
|
165
|
+
self._respond(200, {"result": result})
|
|
166
|
+
|
|
167
|
+
def do_GET(self):
|
|
168
|
+
if self.path == "/health":
|
|
169
|
+
self._respond(200, {"status": "ok"})
|
|
170
|
+
return
|
|
171
|
+
self._respond(200, {"status": "running"})
|
|
172
|
+
|
|
173
|
+
def _respond(self, code, body):
|
|
174
|
+
self.send_response(code)
|
|
175
|
+
self.send_header("Content-Type", "application/json")
|
|
176
|
+
self.end_headers()
|
|
177
|
+
self.wfile.write(json.dumps(body).encode())
|
|
178
|
+
|
|
179
|
+
def log_message(self, format, *args):
|
|
180
|
+
print(f"[{self.log_date_time_string()}] {format % args}")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
server = HTTPServer(("0.0.0.0", PORT), Handler)
|
|
185
|
+
print(f"Always-on service listening on port {PORT}")
|
|
186
|
+
server.serve_forever()
|
|
187
|
+
`;
|
|
188
|
+
const ALWAYS_ON_TEMPLATE_JS = `/**
|
|
189
|
+
* orchagent always-on service entrypoint.
|
|
190
|
+
*
|
|
191
|
+
* Runs a long-lived HTTP server that handles requests over HTTP.
|
|
192
|
+
* This is the standard pattern for always-on services on orchagent.
|
|
193
|
+
*
|
|
194
|
+
* IMPORTANT: Port 8080 is reserved by the platform health server.
|
|
195
|
+
* Use a different port (default: 3000).
|
|
196
|
+
*
|
|
197
|
+
* Local development:
|
|
198
|
+
* node main.js
|
|
199
|
+
*/
|
|
200
|
+
|
|
201
|
+
const http = require('http');
|
|
202
|
+
|
|
203
|
+
const PORT = parseInt(process.env.PORT || '3000', 10);
|
|
204
|
+
|
|
205
|
+
const server = http.createServer((req, res) => {
|
|
206
|
+
if (req.method === 'GET') {
|
|
207
|
+
if (req.url === '/health') {
|
|
208
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
209
|
+
res.end(JSON.stringify({ status: 'ok' }));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
213
|
+
res.end(JSON.stringify({ status: 'running' }));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (req.method === 'POST') {
|
|
218
|
+
let body = '';
|
|
219
|
+
req.on('data', chunk => { body += chunk; });
|
|
220
|
+
req.on('end', () => {
|
|
221
|
+
let data;
|
|
222
|
+
try {
|
|
223
|
+
data = body ? JSON.parse(body) : {};
|
|
224
|
+
} catch {
|
|
225
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
226
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const input = data.input || '';
|
|
231
|
+
|
|
232
|
+
// --- Your logic here ---
|
|
233
|
+
// To use workspace secrets, add them to "required_secrets" in orchagent.json:
|
|
234
|
+
// "required_secrets": ["MY_API_KEY"]
|
|
235
|
+
// Then access via: process.env.MY_API_KEY
|
|
236
|
+
const result = \`Received: \${input}\`;
|
|
237
|
+
// --- End your logic ---
|
|
238
|
+
|
|
239
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
240
|
+
res.end(JSON.stringify({ result }));
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
246
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
server.listen(PORT, '0.0.0.0', () => {
|
|
250
|
+
console.log(\`Always-on service listening on port \${PORT}\`);
|
|
251
|
+
});
|
|
252
|
+
`;
|
|
126
253
|
const DISCORD_MAIN_JS = `/**
|
|
127
254
|
* Discord bot agent — powered by Claude.
|
|
128
255
|
*
|
|
@@ -266,6 +393,7 @@ DISCORD_CHANNEL_IDS=
|
|
|
266
393
|
# MODEL=claude-sonnet-4-5-20250929
|
|
267
394
|
# MAX_TOKENS=1024
|
|
268
395
|
`;
|
|
396
|
+
const AGENT_BUILDER_HINT = `\n Tip: orch skill install orchagent-public/agent-builder — gives your AI the full platform builder reference\n`;
|
|
269
397
|
function readmeTemplate(agentName, flavor) {
|
|
270
398
|
if (flavor === 'support_agent') {
|
|
271
399
|
return `# ${agentName}
|
|
@@ -852,6 +980,11 @@ function registerInitCommand(program) {
|
|
|
852
980
|
throw new errors_1.CliError('JavaScript agent-type agents are not yet supported. Use --type tool for JavaScript agents.');
|
|
853
981
|
}
|
|
854
982
|
// JS orchestrators are now supported via the orchagent-sdk npm package
|
|
983
|
+
// Block --language for types that don't create runtime files
|
|
984
|
+
if (isJavaScript && (initMode.type === 'prompt' || initMode.type === 'skill')) {
|
|
985
|
+
throw new errors_1.CliError(`The --language flag has no effect for ${initMode.type} types (no runtime files are created). ` +
|
|
986
|
+
'Use --type tool or --type agent to create a project with runtime scaffolding.');
|
|
987
|
+
}
|
|
855
988
|
if (options.template) {
|
|
856
989
|
const template = options.template.trim().toLowerCase();
|
|
857
990
|
const validTemplates = ['support-agent', 'discord', 'discord-js', 'github-weekly-summary'];
|
|
@@ -988,7 +1121,7 @@ function registerInitCommand(program) {
|
|
|
988
1121
|
process.stdout.write(` ${s + 2}. Copy .env.example to .env and add platform tokens\n`);
|
|
989
1122
|
process.stdout.write(` ${s + 3}. Test locally: pip install -r requirements.txt && python main.py\n`);
|
|
990
1123
|
process.stdout.write(` ${s + 4}. Deploy: orch publish && orch service deploy\n`);
|
|
991
|
-
process.stdout.write(
|
|
1124
|
+
process.stdout.write(AGENT_BUILDER_HINT);
|
|
992
1125
|
return;
|
|
993
1126
|
}
|
|
994
1127
|
// Handle github-weekly-summary template separately (own file set + output)
|
|
@@ -1044,7 +1177,7 @@ function registerInitCommand(program) {
|
|
|
1044
1177
|
process.stdout.write(` ${s + 3}. orch run <org>/${agentName} Test it\n`);
|
|
1045
1178
|
process.stdout.write(` ${s + 4}. orch schedule create <org>/${agentName} --cron "0 9 * * 1" Schedule weekly\n`);
|
|
1046
1179
|
process.stdout.write(`\n See README.md for full setup guide.\n`);
|
|
1047
|
-
process.stdout.write(
|
|
1180
|
+
process.stdout.write(AGENT_BUILDER_HINT);
|
|
1048
1181
|
return;
|
|
1049
1182
|
}
|
|
1050
1183
|
// Handle discord-js template separately (JS Discord bot)
|
|
@@ -1094,7 +1227,7 @@ function registerInitCommand(program) {
|
|
|
1094
1227
|
process.stdout.write(` ${stepNum + 2}. Copy .env.example to .env and fill in your tokens\n`);
|
|
1095
1228
|
process.stdout.write(` ${stepNum + 3}. Test locally: npm install && node main.js\n`);
|
|
1096
1229
|
process.stdout.write(` ${stepNum + 4}. Deploy: orch publish\n`);
|
|
1097
|
-
process.stdout.write(
|
|
1230
|
+
process.stdout.write(AGENT_BUILDER_HINT);
|
|
1098
1231
|
return;
|
|
1099
1232
|
}
|
|
1100
1233
|
const manifestPath = path_1.default.join(targetDir, 'orchagent.json');
|
|
@@ -1182,7 +1315,7 @@ function registerInitCommand(program) {
|
|
|
1182
1315
|
}
|
|
1183
1316
|
else if (initMode.flavor === 'code_runtime') {
|
|
1184
1317
|
if (isJavaScript) {
|
|
1185
|
-
await promises_1.default.writeFile(path_1.default.join(targetDir, 'main.js'), CODE_TEMPLATE_JS);
|
|
1318
|
+
await promises_1.default.writeFile(path_1.default.join(targetDir, 'main.js'), runMode === 'always_on' ? ALWAYS_ON_TEMPLATE_JS : CODE_TEMPLATE_JS);
|
|
1186
1319
|
await promises_1.default.writeFile(path_1.default.join(targetDir, 'package.json'), JSON.stringify({
|
|
1187
1320
|
name: agentName,
|
|
1188
1321
|
private: true,
|
|
@@ -1191,7 +1324,7 @@ function registerInitCommand(program) {
|
|
|
1191
1324
|
}, null, 2) + '\n');
|
|
1192
1325
|
}
|
|
1193
1326
|
else {
|
|
1194
|
-
await promises_1.default.writeFile(path_1.default.join(targetDir, 'main.py'), CODE_TEMPLATE_PY);
|
|
1327
|
+
await promises_1.default.writeFile(path_1.default.join(targetDir, 'main.py'), runMode === 'always_on' ? ALWAYS_ON_TEMPLATE_PY : CODE_TEMPLATE_PY);
|
|
1195
1328
|
}
|
|
1196
1329
|
await promises_1.default.writeFile(schemaPath, SCHEMA_TEMPLATE);
|
|
1197
1330
|
}
|
|
@@ -1226,12 +1359,13 @@ function registerInitCommand(program) {
|
|
|
1226
1359
|
process.stdout.write(` ${prefix}.env.example - Environment variables template\n`);
|
|
1227
1360
|
}
|
|
1228
1361
|
else if (initMode.flavor === 'code_runtime') {
|
|
1362
|
+
const entrypointDesc = runMode === 'always_on' ? 'Always-on HTTP server' : 'Agent entrypoint (stdin/stdout JSON)';
|
|
1229
1363
|
if (isJavaScript) {
|
|
1230
|
-
process.stdout.write(` ${prefix}main.js -
|
|
1364
|
+
process.stdout.write(` ${prefix}main.js - ${entrypointDesc}\n`);
|
|
1231
1365
|
process.stdout.write(` ${prefix}package.json - npm dependencies\n`);
|
|
1232
1366
|
}
|
|
1233
1367
|
else {
|
|
1234
|
-
process.stdout.write(` ${prefix}main.py -
|
|
1368
|
+
process.stdout.write(` ${prefix}main.py - ${entrypointDesc}\n`);
|
|
1235
1369
|
}
|
|
1236
1370
|
}
|
|
1237
1371
|
else {
|
|
@@ -1274,7 +1408,15 @@ function registerInitCommand(program) {
|
|
|
1274
1408
|
if (name) {
|
|
1275
1409
|
process.stdout.write(` 1. cd ${name}\n`);
|
|
1276
1410
|
}
|
|
1277
|
-
if (
|
|
1411
|
+
if (runMode === 'always_on') {
|
|
1412
|
+
const mainFile = isJavaScript ? 'main.js' : 'main.py';
|
|
1413
|
+
const testCmd = isJavaScript ? 'node main.js' : 'python main.py';
|
|
1414
|
+
process.stdout.write(` ${stepNum}. Edit ${mainFile} with your service logic\n`);
|
|
1415
|
+
process.stdout.write(` ${stepNum + 1}. Test locally: ${testCmd}\n`);
|
|
1416
|
+
process.stdout.write(` ${stepNum + 2}. Publish: orch publish\n`);
|
|
1417
|
+
process.stdout.write(` ${stepNum + 3}. Deploy: orch service deploy\n`);
|
|
1418
|
+
}
|
|
1419
|
+
else if (isJavaScript) {
|
|
1278
1420
|
process.stdout.write(` ${stepNum}. Edit main.js with your agent logic\n`);
|
|
1279
1421
|
process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
|
|
1280
1422
|
process.stdout.write(` ${stepNum + 2}. Test: echo '{"input": "test"}' | node main.js\n`);
|
|
@@ -1296,6 +1438,6 @@ function registerInitCommand(program) {
|
|
|
1296
1438
|
process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
|
|
1297
1439
|
process.stdout.write(` ${stepNum + 2}. Run: orchagent publish\n`);
|
|
1298
1440
|
}
|
|
1299
|
-
process.stdout.write(
|
|
1441
|
+
process.stdout.write(AGENT_BUILDER_HINT);
|
|
1300
1442
|
});
|
|
1301
1443
|
}
|
package/dist/commands/logs.js
CHANGED
|
@@ -56,10 +56,14 @@ function formatDuration(ms) {
|
|
|
56
56
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
57
57
|
return `${(ms / 60000).toFixed(1)}m`;
|
|
58
58
|
}
|
|
59
|
-
/** Detect if a string looks like a UUID (run ID) */
|
|
59
|
+
/** Detect if a string looks like a full UUID (run ID) */
|
|
60
60
|
function isUuid(value) {
|
|
61
61
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
62
62
|
}
|
|
63
|
+
/** Detect if a string looks like a short UUID prefix (8+ hex chars) */
|
|
64
|
+
function isShortUuid(value) {
|
|
65
|
+
return /^[0-9a-f]{7,}$/i.test(value) && !value.includes('/');
|
|
66
|
+
}
|
|
63
67
|
// ============================================
|
|
64
68
|
// COMMAND REGISTRATION
|
|
65
69
|
// ============================================
|
|
@@ -77,16 +81,38 @@ function registerLogsCommand(program) {
|
|
|
77
81
|
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
78
82
|
}
|
|
79
83
|
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
80
|
-
// If target looks like a UUID, show detailed logs for that run
|
|
84
|
+
// If target looks like a UUID (full or short prefix), show detailed logs for that run
|
|
81
85
|
if (target && isUuid(target)) {
|
|
82
86
|
await showRunLogs(config, workspaceId, target, options.json);
|
|
83
87
|
return;
|
|
84
88
|
}
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
if (target && isShortUuid(target)) {
|
|
90
|
+
// Short UUID prefix — find the matching run from the list
|
|
91
|
+
const fullId = await resolveShortRunId(config, workspaceId, target);
|
|
92
|
+
await showRunLogs(config, workspaceId, fullId, options.json);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Otherwise list runs, optionally filtered by agent name.
|
|
96
|
+
// Strip org prefix if provided (e.g. "joe/my-agent" → "my-agent")
|
|
97
|
+
const agentFilter = target?.includes('/') ? target.split('/').pop() : target;
|
|
98
|
+
await listRuns(config, workspaceId, agentFilter, options);
|
|
87
99
|
});
|
|
88
100
|
}
|
|
89
101
|
// ============================================
|
|
102
|
+
// SHORT RUN ID RESOLUTION
|
|
103
|
+
// ============================================
|
|
104
|
+
async function resolveShortRunId(config, workspaceId, shortId) {
|
|
105
|
+
// Server-side prefix matching — searches ALL runs, not just the last 200
|
|
106
|
+
const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs?limit=200&run_id_prefix=${encodeURIComponent(shortId)}`);
|
|
107
|
+
if (result.runs.length === 0) {
|
|
108
|
+
throw new errors_1.CliError(`No run found matching '${shortId}'.`);
|
|
109
|
+
}
|
|
110
|
+
if (result.runs.length > 1) {
|
|
111
|
+
throw new errors_1.CliError(`Ambiguous run ID '${shortId}' — matches ${result.runs.length} runs. Use more characters to narrow it down.`);
|
|
112
|
+
}
|
|
113
|
+
return result.runs[0].id;
|
|
114
|
+
}
|
|
115
|
+
// ============================================
|
|
90
116
|
// LIST RUNS
|
|
91
117
|
// ============================================
|
|
92
118
|
async function listRuns(config, workspaceId, agentName, options) {
|
|
@@ -161,6 +187,16 @@ async function showRunLogs(config, workspaceId, runId, json) {
|
|
|
161
187
|
const exitLabel = result.exit_code === 0 ? chalk_1.default.green(String(result.exit_code)) : chalk_1.default.red(String(result.exit_code));
|
|
162
188
|
process.stdout.write(`Exit code: ${exitLabel}\n`);
|
|
163
189
|
}
|
|
190
|
+
// Input data
|
|
191
|
+
if (result.input_data != null && Object.keys(result.input_data).length > 0) {
|
|
192
|
+
process.stdout.write('\n' + chalk_1.default.bold.blue('--- input ---') + '\n' +
|
|
193
|
+
JSON.stringify(result.input_data, null, 2) + '\n');
|
|
194
|
+
}
|
|
195
|
+
// Output data
|
|
196
|
+
if (result.output_data != null && Object.keys(result.output_data).length > 0) {
|
|
197
|
+
process.stdout.write('\n' + chalk_1.default.bold.green('--- output ---') + '\n' +
|
|
198
|
+
JSON.stringify(result.output_data, null, 2) + '\n');
|
|
199
|
+
}
|
|
164
200
|
// Error message
|
|
165
201
|
if (result.error_message) {
|
|
166
202
|
process.stdout.write('\n' + chalk_1.default.red.bold('Error:\n') + chalk_1.default.red(result.error_message) + '\n');
|
package/dist/commands/publish.js
CHANGED
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.extractTemplateVariables = extractTemplateVariables;
|
|
40
40
|
exports.deriveInputSchema = deriveInputSchema;
|
|
41
41
|
exports.scanUndeclaredEnvVars = scanUndeclaredEnvVars;
|
|
42
|
+
exports.scanReservedPort = scanReservedPort;
|
|
42
43
|
exports.detectSdkCompatible = detectSdkCompatible;
|
|
43
44
|
exports.checkDependencies = checkDependencies;
|
|
44
45
|
exports.registerPublishCommand = registerPublishCommand;
|
|
@@ -167,6 +168,68 @@ async function scanUndeclaredEnvVars(agentDir, requiredSecrets) {
|
|
|
167
168
|
// Return env vars that are referenced but not declared or auto-injected
|
|
168
169
|
return [...found].filter(v => !declared.has(v) && !autoInjected.has(v)).sort();
|
|
169
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Scan code files for patterns that bind to port 8080 — the platform reserves
|
|
173
|
+
* this port for the always-on service health server. Returns true if a likely
|
|
174
|
+
* port-8080 binding is detected.
|
|
175
|
+
*/
|
|
176
|
+
async function scanReservedPort(agentDir) {
|
|
177
|
+
// Patterns that indicate binding to port 8080 in Python / JS / TS
|
|
178
|
+
const pyPatterns = [
|
|
179
|
+
/\.listen\s*\(\s*8080\b/, // server.listen(8080
|
|
180
|
+
/\.run\s*\([^)]*port\s*=\s*8080\b/, // app.run(port=8080
|
|
181
|
+
/\.run\s*\([^)]*host\s*=\s*['"][^'"]*['"],?\s*8080\b/, // app.run("0.0.0.0", 8080
|
|
182
|
+
/bind\s*\(\s*\(?['"][^'"]*['"]\s*,\s*8080\b/, // bind(("0.0.0.0", 8080
|
|
183
|
+
/PORT\s*=\s*8080\b/, // PORT = 8080
|
|
184
|
+
/port\s*=\s*8080\b/, // port=8080
|
|
185
|
+
];
|
|
186
|
+
const jsPatterns = [
|
|
187
|
+
/\.listen\s*\(\s*8080\b/, // app.listen(8080
|
|
188
|
+
/port\s*[:=]\s*8080\b/, // port: 8080 or port = 8080
|
|
189
|
+
/PORT\s*[:=]\s*8080\b/, // PORT = 8080
|
|
190
|
+
];
|
|
191
|
+
async function scanDir(dir, depth) {
|
|
192
|
+
let entries;
|
|
193
|
+
try {
|
|
194
|
+
entries = await promises_1.default.readdir(dir, { withFileTypes: true });
|
|
195
|
+
if (!entries || !Array.isArray(entries))
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
for (const entry of entries) {
|
|
202
|
+
const name = entry.name;
|
|
203
|
+
const fullPath = path_1.default.join(dir, name);
|
|
204
|
+
if (entry.isDirectory() && depth < 2 && !name.startsWith('.') && name !== 'node_modules' && name !== '__pycache__' && name !== 'venv' && name !== '.venv') {
|
|
205
|
+
if (await scanDir(fullPath, depth + 1))
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
else if (entry.isFile() && name.endsWith('.py')) {
|
|
209
|
+
try {
|
|
210
|
+
const content = await promises_1.default.readFile(fullPath, 'utf-8');
|
|
211
|
+
for (const re of pyPatterns) {
|
|
212
|
+
if (re.test(content))
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch { /* skip */ }
|
|
217
|
+
}
|
|
218
|
+
else if (entry.isFile() && (name.endsWith('.js') || name.endsWith('.ts'))) {
|
|
219
|
+
try {
|
|
220
|
+
const content = await promises_1.default.readFile(fullPath, 'utf-8');
|
|
221
|
+
for (const re of jsPatterns) {
|
|
222
|
+
if (re.test(content))
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch { /* skip */ }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
return scanDir(agentDir, 0);
|
|
232
|
+
}
|
|
170
233
|
/**
|
|
171
234
|
* Check if orchagent-sdk is listed in requirements.txt or pyproject.toml
|
|
172
235
|
*/
|
|
@@ -319,11 +382,8 @@ function inferExecutionEngineFromManifest(manifest, rawType) {
|
|
|
319
382
|
return 'managed_loop';
|
|
320
383
|
if (rawType === 'tool' || rawType === 'code')
|
|
321
384
|
return 'code_runtime';
|
|
322
|
-
if (rawType === 'agentic')
|
|
323
|
-
return 'managed_loop';
|
|
324
|
-
if (rawType === 'agent' && (manifest.custom_tools?.length || manifest.max_turns)) {
|
|
385
|
+
if (rawType === 'agentic' || rawType === 'agent')
|
|
325
386
|
return 'managed_loop';
|
|
326
|
-
}
|
|
327
387
|
return 'direct_llm';
|
|
328
388
|
}
|
|
329
389
|
function commandForEntrypoint(entrypoint) {
|
|
@@ -485,6 +545,7 @@ function registerPublishCommand(program) {
|
|
|
485
545
|
description: skillData.frontmatter.description,
|
|
486
546
|
prompt: skillData.body,
|
|
487
547
|
is_public: false,
|
|
548
|
+
callable: false,
|
|
488
549
|
supported_providers: ['any'],
|
|
489
550
|
default_skills: skillsFromFlag,
|
|
490
551
|
skills_locked: options.skillsLocked || undefined,
|
|
@@ -542,7 +603,7 @@ function registerPublishCommand(program) {
|
|
|
542
603
|
const { canonicalType, rawType } = canonicalizeManifestType(manifest.type);
|
|
543
604
|
const runMode = normalizeRunMode(manifest.run_mode);
|
|
544
605
|
const executionEngine = inferExecutionEngineFromManifest(manifest, rawType);
|
|
545
|
-
const callable = Boolean(manifest.callable);
|
|
606
|
+
const callable = manifest.callable !== undefined ? Boolean(manifest.callable) : true;
|
|
546
607
|
if (canonicalType === 'skill') {
|
|
547
608
|
throw new errors_1.CliError("Use SKILL.md for publishing skills. Remove orchagent.json and run 'orchagent publish' from a skill directory.");
|
|
548
609
|
}
|
|
@@ -767,12 +828,13 @@ function registerPublishCommand(program) {
|
|
|
767
828
|
` Publish each dependency first, then re-run this publish.\n\n`);
|
|
768
829
|
}
|
|
769
830
|
if (notCallable.length > 0) {
|
|
770
|
-
process.stderr.write(chalk_1.default.yellow(`\n⚠ Dependencies
|
|
831
|
+
process.stderr.write(chalk_1.default.yellow(`\n⚠ Dependencies have callable: false:\n`));
|
|
771
832
|
for (const dep of notCallable) {
|
|
772
833
|
process.stderr.write(chalk_1.default.yellow(` - ${dep.ref}\n`));
|
|
773
834
|
}
|
|
774
|
-
process.stderr.write(`\n
|
|
775
|
-
`
|
|
835
|
+
process.stderr.write(`\n These agents have explicitly set callable: false, which blocks\n` +
|
|
836
|
+
` agent-to-agent calls at runtime. Set callable: true (or remove\n` +
|
|
837
|
+
` the field to use the default) and republish each dependency.\n\n`);
|
|
776
838
|
}
|
|
777
839
|
}
|
|
778
840
|
// Handle dry-run for agents
|
|
@@ -883,18 +945,35 @@ function registerPublishCommand(program) {
|
|
|
883
945
|
` (Platform-injected vars like LLM API keys are already excluded.)\n\n`);
|
|
884
946
|
}
|
|
885
947
|
}
|
|
948
|
+
// Warn if always_on code binds to port 8080 (reserved for platform health server)
|
|
949
|
+
if (runMode === 'always_on' && executionEngine === 'code_runtime') {
|
|
950
|
+
const usesReservedPort = await scanReservedPort(cwd);
|
|
951
|
+
if (usesReservedPort) {
|
|
952
|
+
process.stderr.write(chalk_1.default.yellow(`\n⚠ Your code appears to bind to port 8080, which is reserved by the\n`) +
|
|
953
|
+
chalk_1.default.yellow(` platform health server for always-on services.\n\n`) +
|
|
954
|
+
` Your service will crash with EADDRINUSE at runtime.\n` +
|
|
955
|
+
` Use a different port (e.g. 3000) or read the ORCHAGENT_HEALTH_PORT\n` +
|
|
956
|
+
` env var to detect the reserved port.\n\n`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
886
959
|
// C-1: Block publish if tool/agent type has no required_secrets declared.
|
|
887
960
|
// Prompt and skill types are exempt (prompt agents get LLM keys from platform,
|
|
888
961
|
// skills don't run standalone).
|
|
962
|
+
// An explicit empty array (required_secrets: []) is a valid declaration
|
|
963
|
+
// meaning "this agent deliberately needs no secrets."
|
|
889
964
|
if ((canonicalType === 'tool' || canonicalType === 'agent') &&
|
|
890
|
-
|
|
965
|
+
manifest.required_secrets === undefined &&
|
|
891
966
|
options.requiredSecrets !== false) {
|
|
892
967
|
process.stderr.write(chalk_1.default.red(`\nError: ${canonicalType} agents must declare required_secrets in orchagent.json.\n\n`) +
|
|
893
968
|
` Add the env vars your code needs at runtime:\n` +
|
|
894
969
|
` ${chalk_1.default.cyan('"required_secrets": ["ANTHROPIC_API_KEY", "MY_TOKEN"]')}\n\n` +
|
|
970
|
+
` If this agent genuinely needs no secrets, add an empty array:\n` +
|
|
971
|
+
` ${chalk_1.default.cyan('"required_secrets": []')}\n\n` +
|
|
895
972
|
` These are matched by name against your workspace secrets vault.\n` +
|
|
896
973
|
` Use ${chalk_1.default.cyan('--no-required-secrets')} to skip this check.\n`);
|
|
897
|
-
|
|
974
|
+
const err = new errors_1.CliError('Missing required_secrets declaration', errors_1.ExitCodes.INVALID_INPUT);
|
|
975
|
+
err.displayed = true;
|
|
976
|
+
throw err;
|
|
898
977
|
}
|
|
899
978
|
// Create the agent (server auto-assigns version)
|
|
900
979
|
let result;
|
|
@@ -1125,6 +1204,5 @@ function registerPublishCommand(program) {
|
|
|
1125
1204
|
process.stdout.write(`\nNote: Hosted code execution is in beta. Contact support for full deployment.\n`);
|
|
1126
1205
|
}
|
|
1127
1206
|
process.stdout.write(`\nView analytics and usage: https://orchagent.io/dashboard\n`);
|
|
1128
|
-
process.stdout.write(`\nSkill: orch skill install orchagent-public/agent-builder — gives your AI the full platform builder reference\n`);
|
|
1129
1207
|
});
|
|
1130
1208
|
}
|
package/dist/commands/pull.js
CHANGED
|
@@ -29,7 +29,13 @@ function parsePullRef(value) {
|
|
|
29
29
|
}
|
|
30
30
|
function canonicalType(typeValue) {
|
|
31
31
|
const normalized = (typeValue || 'agent').toLowerCase();
|
|
32
|
-
|
|
32
|
+
if (['prompt', 'tool', 'agent', 'skill'].includes(normalized))
|
|
33
|
+
return normalized;
|
|
34
|
+
if (normalized === 'code')
|
|
35
|
+
return 'tool';
|
|
36
|
+
if (normalized === 'agentic')
|
|
37
|
+
return 'agent';
|
|
38
|
+
return 'agent';
|
|
33
39
|
}
|
|
34
40
|
function resolveEngine(data) {
|
|
35
41
|
const ee = data.execution_engine;
|
|
@@ -200,7 +206,7 @@ function buildManifest(data) {
|
|
|
200
206
|
const manifest = {
|
|
201
207
|
name: data.name,
|
|
202
208
|
description: data.description || '',
|
|
203
|
-
type: canonicalType(data.type)
|
|
209
|
+
type: canonicalType(data.type),
|
|
204
210
|
};
|
|
205
211
|
if (data.run_mode)
|
|
206
212
|
manifest.run_mode = data.run_mode;
|
package/dist/commands/run.js
CHANGED
|
@@ -675,7 +675,7 @@ async function executePromptLocally(agentData, inputData, skillPrompts = [], con
|
|
|
675
675
|
if (allProviders.length === 0) {
|
|
676
676
|
const providers = providersToCheck.join(', ');
|
|
677
677
|
throw new errors_1.CliError(`No LLM key found for: ${providers}\n` +
|
|
678
|
-
`Set an environment variable (e.g., OPENAI_API_KEY), run '
|
|
678
|
+
`Set an environment variable (e.g., OPENAI_API_KEY), run 'orch secrets set <PROVIDER>_API_KEY <key>', or configure in web dashboard`);
|
|
679
679
|
}
|
|
680
680
|
if (modelOverride && !providerOverride && allProviders.length > 1) {
|
|
681
681
|
process.stderr.write(`Warning: --model specified without --provider. The model '${modelOverride}' will be used for all ${allProviders.length} fallback providers, which may cause errors if the model is incompatible.\n` +
|
|
@@ -702,7 +702,7 @@ async function executePromptLocally(agentData, inputData, skillPrompts = [], con
|
|
|
702
702
|
if (!detected) {
|
|
703
703
|
const providers = providersToCheck.join(', ');
|
|
704
704
|
throw new errors_1.CliError(`No LLM key found for: ${providers}\n` +
|
|
705
|
-
`Set an environment variable (e.g., OPENAI_API_KEY), run '
|
|
705
|
+
`Set an environment variable (e.g., OPENAI_API_KEY), run 'orch secrets set <PROVIDER>_API_KEY <key>', or configure in web dashboard`);
|
|
706
706
|
}
|
|
707
707
|
const { provider, key, model: serverModel } = detected;
|
|
708
708
|
const model = modelOverride || serverModel || agentData.default_models?.[provider] || (0, llm_1.getDefaultModel)(provider);
|
|
@@ -736,7 +736,7 @@ async function executeAgentLocally(agentDir, prompt, inputData, outputSchema, cu
|
|
|
736
736
|
if (allProviders.length === 0) {
|
|
737
737
|
const providers = providersToCheck.join(', ');
|
|
738
738
|
throw new errors_1.CliError(`No LLM key found for: ${providers}\n` +
|
|
739
|
-
`Set an environment variable (e.g., ANTHROPIC_API_KEY), run '
|
|
739
|
+
`Set an environment variable (e.g., ANTHROPIC_API_KEY), run 'orch secrets set <PROVIDER>_API_KEY <key>', or configure in web dashboard`);
|
|
740
740
|
}
|
|
741
741
|
const primary = allProviders[0];
|
|
742
742
|
const model = modelOverride || primary.model || (0, llm_1.getDefaultModel)(primary.provider);
|
|
@@ -1775,12 +1775,6 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1775
1775
|
...(options.model && { model: options.model }),
|
|
1776
1776
|
};
|
|
1777
1777
|
}
|
|
1778
|
-
else if (cloudEngine !== 'code_runtime') {
|
|
1779
|
-
const searchedProviders = effectiveProvider ? [effectiveProvider] : supportedProviders;
|
|
1780
|
-
const providerList = searchedProviders.join(', ');
|
|
1781
|
-
process.stderr.write(`Warning: No LLM key found for provider(s): ${providerList}\n` +
|
|
1782
|
-
`Set an env var (e.g., OPENAI_API_KEY), run 'orchagent keys add <provider>', use --key, or configure in web dashboard\n\n`);
|
|
1783
|
-
}
|
|
1784
1778
|
if (options.skills) {
|
|
1785
1779
|
headers['X-OrchAgent-Skills'] = options.skills;
|
|
1786
1780
|
}
|
|
@@ -2027,9 +2021,20 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2027
2021
|
const hint = typeof payload === 'object' && payload
|
|
2028
2022
|
? payload.error?.hint
|
|
2029
2023
|
: undefined;
|
|
2024
|
+
// Detect platform errors that surface as SANDBOX_ERROR (BUG-11)
|
|
2025
|
+
const lowerMessage = (message || '').toLowerCase();
|
|
2026
|
+
const isPlatformError = /\b403\b/.test(message || '') ||
|
|
2027
|
+
/\b401\b/.test(message || '') ||
|
|
2028
|
+
lowerMessage.includes('proxy token') ||
|
|
2029
|
+
lowerMessage.includes('orchagent_service_key') ||
|
|
2030
|
+
lowerMessage.includes('orchagent_billing_org');
|
|
2031
|
+
const attribution = isPlatformError
|
|
2032
|
+
? `This may be a platform configuration issue, not an error in the agent's code.\n` +
|
|
2033
|
+
`If this persists, contact support with the ref below.`
|
|
2034
|
+
: `This is an error in the agent's code, not the platform.\n` +
|
|
2035
|
+
`Check the agent code and requirements, then republish.`;
|
|
2030
2036
|
throw new errors_1.CliError(`${message}\n\n` +
|
|
2031
|
-
|
|
2032
|
-
`Check the agent code and requirements, then republish.` +
|
|
2037
|
+
attribution +
|
|
2033
2038
|
(hint ? `\n\nHint: ${hint}` : '') +
|
|
2034
2039
|
refSuffix);
|
|
2035
2040
|
}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.registerScheduleCommand = registerScheduleCommand;
|
|
7
7
|
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const promises_1 = __importDefault(require("readline/promises"));
|
|
9
10
|
const config_1 = require("../lib/config");
|
|
10
11
|
const api_1 = require("../lib/api");
|
|
11
12
|
const errors_1 = require("../lib/errors");
|
|
@@ -64,7 +65,8 @@ async function resolveScheduleId(config, partialId, workspaceId) {
|
|
|
64
65
|
function registerScheduleCommand(program) {
|
|
65
66
|
const schedule = program
|
|
66
67
|
.command('schedule')
|
|
67
|
-
.description('Manage scheduled agent runs (cron and webhooks)')
|
|
68
|
+
.description('Manage scheduled agent runs (cron and webhooks)')
|
|
69
|
+
.action(() => { schedule.help(); });
|
|
68
70
|
// orch schedule list
|
|
69
71
|
schedule
|
|
70
72
|
.command('list')
|
|
@@ -282,6 +284,7 @@ function registerScheduleCommand(program) {
|
|
|
282
284
|
schedule
|
|
283
285
|
.command('delete <schedule-id>')
|
|
284
286
|
.description('Delete a schedule')
|
|
287
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
285
288
|
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
286
289
|
.action(async (scheduleId, options) => {
|
|
287
290
|
const config = await (0, config_1.getResolvedConfig)();
|
|
@@ -289,6 +292,18 @@ function registerScheduleCommand(program) {
|
|
|
289
292
|
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
290
293
|
}
|
|
291
294
|
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
295
|
+
if (!options.yes) {
|
|
296
|
+
const rl = promises_1.default.createInterface({
|
|
297
|
+
input: process.stdin,
|
|
298
|
+
output: process.stdout,
|
|
299
|
+
});
|
|
300
|
+
const answer = await rl.question(`Delete schedule ${scheduleId}? (y/N): `);
|
|
301
|
+
rl.close();
|
|
302
|
+
if (answer.trim().toLowerCase() !== 'y' && answer.trim().toLowerCase() !== 'yes') {
|
|
303
|
+
process.stdout.write('Cancelled.\n');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
292
307
|
await (0, api_1.request)(config, 'DELETE', `/workspaces/${workspaceId}/schedules/${scheduleId}`);
|
|
293
308
|
process.stdout.write(chalk_1.default.green('\u2713') + ` Schedule ${scheduleId} deleted\n`);
|
|
294
309
|
});
|
|
@@ -338,6 +353,7 @@ function registerScheduleCommand(program) {
|
|
|
338
353
|
.command('info <schedule-id>')
|
|
339
354
|
.description('Show detailed schedule information with recent runs and events')
|
|
340
355
|
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
356
|
+
.option('--reveal', 'Show full webhook URL (for webhook schedules)')
|
|
341
357
|
.option('--json', 'Output as JSON')
|
|
342
358
|
.action(async (partialScheduleId, options) => {
|
|
343
359
|
const config = await (0, config_1.getResolvedConfig)();
|
|
@@ -346,8 +362,9 @@ function registerScheduleCommand(program) {
|
|
|
346
362
|
}
|
|
347
363
|
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
348
364
|
const scheduleId = await resolveScheduleId(config, partialScheduleId, workspaceId);
|
|
365
|
+
const revealParam = options.reveal ? '?reveal_webhook=true' : '';
|
|
349
366
|
const [scheduleRes, runsRes, eventsRes] = await Promise.all([
|
|
350
|
-
(0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/schedules/${scheduleId}`),
|
|
367
|
+
(0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/schedules/${scheduleId}${revealParam}`),
|
|
351
368
|
(0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/schedules/${scheduleId}/runs?limit=5`),
|
|
352
369
|
(0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/schedules/${scheduleId}/events?limit=5`),
|
|
353
370
|
]);
|
|
@@ -364,6 +381,12 @@ function registerScheduleCommand(program) {
|
|
|
364
381
|
process.stdout.write(` Cron: ${s.cron_expression}\n`);
|
|
365
382
|
process.stdout.write(` Timezone: ${s.timezone}\n`);
|
|
366
383
|
}
|
|
384
|
+
if (s.webhook_url) {
|
|
385
|
+
process.stdout.write(` Webhook: ${s.webhook_url}\n`);
|
|
386
|
+
}
|
|
387
|
+
else if (s.schedule_type === 'webhook' && !options.reveal) {
|
|
388
|
+
process.stdout.write(` Webhook: ${chalk_1.default.gray('(redacted — use --reveal to show)')}\n`);
|
|
389
|
+
}
|
|
367
390
|
process.stdout.write(` Enabled: ${s.enabled ? chalk_1.default.green('yes') : chalk_1.default.red('no')}\n`);
|
|
368
391
|
process.stdout.write(` Auto-update: ${s.auto_update === false ? chalk_1.default.yellow('pinned') : chalk_1.default.green('yes')}\n`);
|
|
369
392
|
if (s.auto_disabled_at) {
|
|
@@ -374,6 +397,10 @@ function registerScheduleCommand(program) {
|
|
|
374
397
|
if (s.next_run_at) {
|
|
375
398
|
process.stdout.write(` Next Run: ${formatDate(s.next_run_at)}\n`);
|
|
376
399
|
}
|
|
400
|
+
if (s.input_data && Object.keys(s.input_data).length > 0) {
|
|
401
|
+
const inputStr = JSON.stringify(s.input_data);
|
|
402
|
+
process.stdout.write(` Input: ${inputStr.length > 100 ? inputStr.slice(0, 100) + '...' : inputStr}\n`);
|
|
403
|
+
}
|
|
377
404
|
if (s.alert_webhook_url) {
|
|
378
405
|
process.stdout.write(` Alert URL: ${s.alert_webhook_url.slice(0, 50)}...\n`);
|
|
379
406
|
}
|
package/dist/commands/secrets.js
CHANGED
|
@@ -54,7 +54,8 @@ async function findSecretByName(config, workspaceId, name) {
|
|
|
54
54
|
function registerSecretsCommand(program) {
|
|
55
55
|
const secrets = program
|
|
56
56
|
.command('secrets')
|
|
57
|
-
.description('Manage workspace secrets (injected as env vars into agent sandboxes)')
|
|
57
|
+
.description('Manage workspace secrets (injected as env vars into agent sandboxes)')
|
|
58
|
+
.action(() => { secrets.help(); });
|
|
58
59
|
// orch secrets list
|
|
59
60
|
secrets
|
|
60
61
|
.command('list')
|
package/dist/commands/service.js
CHANGED
|
@@ -60,6 +60,13 @@ function healthColor(health) {
|
|
|
60
60
|
return chalk_1.default.gray(health);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
function formatServiceUrl(svc) {
|
|
64
|
+
const url = svc.provider_url || svc.cloud_run_url || '-';
|
|
65
|
+
if (url !== '-' && svc.infrastructure_provider === 'flyio') {
|
|
66
|
+
return `${url} ${chalk_1.default.gray('(internal — not a public endpoint)')}`;
|
|
67
|
+
}
|
|
68
|
+
return url;
|
|
69
|
+
}
|
|
63
70
|
function severityColor(severity, message) {
|
|
64
71
|
switch (severity.toUpperCase()) {
|
|
65
72
|
case 'ERROR':
|
|
@@ -184,7 +191,7 @@ function registerServiceCommand(program) {
|
|
|
184
191
|
process.stdout.write(` ${chalk_1.default.bold('Name:')} ${svc.service_name}\n`);
|
|
185
192
|
process.stdout.write(` ${chalk_1.default.bold('Agent:')} ${svc.agent_name}@${svc.agent_version}\n`);
|
|
186
193
|
process.stdout.write(` ${chalk_1.default.bold('State:')} ${stateColor(svc.current_state)}\n`);
|
|
187
|
-
process.stdout.write(` ${chalk_1.default.bold('URL:')} ${svc
|
|
194
|
+
process.stdout.write(` ${chalk_1.default.bold('URL:')} ${formatServiceUrl(svc)}\n`);
|
|
188
195
|
if (options.pin) {
|
|
189
196
|
process.stdout.write(` ${chalk_1.default.bold('Pinned:')} ${chalk_1.default.yellow(`yes (won't auto-update on publish)`)}\n`);
|
|
190
197
|
}
|
|
@@ -192,7 +199,7 @@ function registerServiceCommand(program) {
|
|
|
192
199
|
process.stdout.write(chalk_1.default.gray(`View logs: orch service logs ${svc.id}\n`));
|
|
193
200
|
}
|
|
194
201
|
catch (e) {
|
|
195
|
-
deploySpinner.
|
|
202
|
+
deploySpinner.stop();
|
|
196
203
|
throw e;
|
|
197
204
|
}
|
|
198
205
|
});
|
|
@@ -348,7 +355,7 @@ function registerServiceCommand(program) {
|
|
|
348
355
|
}
|
|
349
356
|
process.stdout.write(` Instances: ${svc.min_instances}-${svc.max_instances}\n`);
|
|
350
357
|
process.stdout.write(` Service ID: ${svc.provider_service_id || svc.cloud_run_service || '-'}\n`);
|
|
351
|
-
process.stdout.write(` URL: ${svc
|
|
358
|
+
process.stdout.write(` URL: ${formatServiceUrl(svc)}\n`);
|
|
352
359
|
process.stdout.write(` Deployed: ${formatDate(svc.last_deployed_at)}\n`);
|
|
353
360
|
process.stdout.write(` Last Restart: ${formatDate(svc.last_restart_at)}\n`);
|
|
354
361
|
if (svc.last_error) {
|
package/dist/commands/skill.js
CHANGED
|
@@ -204,18 +204,18 @@ async function downloadSkillWithFallback(config, org, skill, version, workspaceI
|
|
|
204
204
|
}
|
|
205
205
|
function registerSkillCommand(program) {
|
|
206
206
|
const skill = program.command('skill').description('Manage and install skills');
|
|
207
|
-
|
|
207
|
+
skill.action(() => { skill.help(); });
|
|
208
|
+
// orch skill list
|
|
208
209
|
skill
|
|
209
210
|
.command('list')
|
|
210
|
-
.description('
|
|
211
|
+
.description('Browse available skills')
|
|
211
212
|
.option('--json', 'Output raw JSON')
|
|
212
213
|
.action(async () => {
|
|
213
|
-
process.stdout.write('
|
|
214
|
-
'
|
|
215
|
-
'
|
|
216
|
-
'
|
|
217
|
-
'
|
|
218
|
-
'View all skills at: https://orchagent.io/explore\n');
|
|
214
|
+
process.stdout.write('Browse and discover skills at: https://orchagent.io/explore\n\n' +
|
|
215
|
+
'Install a skill:\n' +
|
|
216
|
+
' orch skill install <org>/<skill-name>\n\n' +
|
|
217
|
+
'View installed skills:\n' +
|
|
218
|
+
' orch update --check\n');
|
|
219
219
|
process.exit(0);
|
|
220
220
|
});
|
|
221
221
|
// orch skill create [name]
|
package/dist/commands/test.js
CHANGED
|
@@ -514,7 +514,7 @@ async function runPromptFixtureTests(agentDir, fixtures, verbose, config) {
|
|
|
514
514
|
const detected = await (0, llm_1.detectLlmKey)(['any'], config);
|
|
515
515
|
if (!detected) {
|
|
516
516
|
throw new errors_1.CliError('No LLM key found for fixture tests.\n' +
|
|
517
|
-
'Set an environment variable (e.g., OPENAI_API_KEY) or run `
|
|
517
|
+
'Set an environment variable (e.g., OPENAI_API_KEY) or run `orch secrets set <PROVIDER>_API_KEY <key>`');
|
|
518
518
|
}
|
|
519
519
|
const { provider, key, model: serverModel } = detected;
|
|
520
520
|
const model = serverModel ?? (0, llm_1.getDefaultModel)(provider);
|
|
@@ -6,8 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.checkNodeVersion = checkNodeVersion;
|
|
7
7
|
exports.checkCliVersion = checkCliVersion;
|
|
8
8
|
exports.checkGitAvailable = checkGitAvailable;
|
|
9
|
+
exports.checkDualInstallation = checkDualInstallation;
|
|
9
10
|
exports.runEnvironmentChecks = runEnvironmentChecks;
|
|
10
11
|
const child_process_1 = require("child_process");
|
|
12
|
+
const fs_1 = require("fs");
|
|
11
13
|
const package_json_1 = __importDefault(require("../../../../package.json"));
|
|
12
14
|
const update_notifier_1 = require("../../update-notifier");
|
|
13
15
|
const REQUIRED_NODE_MAJOR = 18;
|
|
@@ -142,6 +144,148 @@ async function checkGitAvailable() {
|
|
|
142
144
|
};
|
|
143
145
|
}
|
|
144
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Find all binary paths for a given command name using `which -a`.
|
|
149
|
+
* Returns an empty array if the command is not found.
|
|
150
|
+
* Note: Uses execSync with a hardcoded binary name — no user input, safe from injection.
|
|
151
|
+
*/
|
|
152
|
+
function findAllBinaryPaths(binaryName) {
|
|
153
|
+
try {
|
|
154
|
+
const output = (0, child_process_1.execSync)(`which -a ${binaryName}`, {
|
|
155
|
+
encoding: 'utf-8',
|
|
156
|
+
timeout: 5000,
|
|
157
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
158
|
+
});
|
|
159
|
+
return output.trim().split('\n').filter(Boolean);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get the CLI version from a binary path by running it with --version.
|
|
167
|
+
* Uses execFileSync (no shell) to avoid injection via path characters.
|
|
168
|
+
*/
|
|
169
|
+
function getVersionFromBinary(binPath) {
|
|
170
|
+
try {
|
|
171
|
+
const output = (0, child_process_1.execFileSync)(binPath, ['--version'], {
|
|
172
|
+
encoding: 'utf-8',
|
|
173
|
+
timeout: 5000,
|
|
174
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
175
|
+
});
|
|
176
|
+
const match = output.match(/(\d+\.\d+\.\d+)/);
|
|
177
|
+
return match ? match[1] : null;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Resolve a path through symlinks. Returns the original path if resolution fails.
|
|
185
|
+
*/
|
|
186
|
+
function safeRealpathSync(p) {
|
|
187
|
+
try {
|
|
188
|
+
return (0, fs_1.realpathSync)(p);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return p;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Detect multiple CLI installations at different paths/versions (BUG-008).
|
|
196
|
+
*
|
|
197
|
+
* System-level (/usr/local/bin/orchagent) and user-level (~/.npm-global/bin/orch)
|
|
198
|
+
* installs can coexist silently. `npm update -g` updates one but not the other,
|
|
199
|
+
* leaving the user running an outdated version without knowing it.
|
|
200
|
+
*/
|
|
201
|
+
async function checkDualInstallation() {
|
|
202
|
+
// Skip on Windows (which -a not available)
|
|
203
|
+
if (process.platform === 'win32') {
|
|
204
|
+
return {
|
|
205
|
+
category: 'environment',
|
|
206
|
+
name: 'dual_installation',
|
|
207
|
+
status: 'info',
|
|
208
|
+
message: 'Installation path check skipped (Windows)',
|
|
209
|
+
details: { skipped: true, reason: 'Windows not supported' },
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const binaryNames = ['orch', 'orchagent'];
|
|
214
|
+
const installations = new Map();
|
|
215
|
+
for (const binary of binaryNames) {
|
|
216
|
+
const paths = findAllBinaryPaths(binary);
|
|
217
|
+
for (const binPath of paths) {
|
|
218
|
+
const realPath = safeRealpathSync(binPath);
|
|
219
|
+
// Deduplicate by resolved real path
|
|
220
|
+
if (installations.has(realPath))
|
|
221
|
+
continue;
|
|
222
|
+
const version = getVersionFromBinary(binPath) || 'unknown';
|
|
223
|
+
installations.set(realPath, {
|
|
224
|
+
path: binPath,
|
|
225
|
+
realPath,
|
|
226
|
+
version,
|
|
227
|
+
binary,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (installations.size <= 1) {
|
|
232
|
+
return {
|
|
233
|
+
category: 'environment',
|
|
234
|
+
name: 'dual_installation',
|
|
235
|
+
status: 'success',
|
|
236
|
+
message: 'Single CLI installation',
|
|
237
|
+
details: {
|
|
238
|
+
installationCount: installations.size,
|
|
239
|
+
installations: [...installations.values()],
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// Multiple installations found
|
|
244
|
+
const allInstalls = [...installations.values()];
|
|
245
|
+
const versions = new Set(allInstalls.map((i) => i.version));
|
|
246
|
+
const versionsDiffer = versions.size > 1;
|
|
247
|
+
const pathList = allInstalls
|
|
248
|
+
.map((i) => `${i.path} (v${i.version})`)
|
|
249
|
+
.join(', ');
|
|
250
|
+
if (versionsDiffer) {
|
|
251
|
+
return {
|
|
252
|
+
category: 'environment',
|
|
253
|
+
name: 'dual_installation',
|
|
254
|
+
status: 'warning',
|
|
255
|
+
message: `Multiple CLI versions found: ${pathList}`,
|
|
256
|
+
fix: 'Remove the outdated installation. Run `which -a orch orchagent` to see all paths, then remove the older binary',
|
|
257
|
+
details: {
|
|
258
|
+
installationCount: installations.size,
|
|
259
|
+
versionMismatch: true,
|
|
260
|
+
installations: allInstalls,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// Same version at multiple paths — informational only
|
|
265
|
+
return {
|
|
266
|
+
category: 'environment',
|
|
267
|
+
name: 'dual_installation',
|
|
268
|
+
status: 'info',
|
|
269
|
+
message: `Multiple CLI paths (same version v${allInstalls[0].version}): ${pathList}`,
|
|
270
|
+
details: {
|
|
271
|
+
installationCount: installations.size,
|
|
272
|
+
versionMismatch: false,
|
|
273
|
+
installations: allInstalls,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
return {
|
|
279
|
+
category: 'environment',
|
|
280
|
+
name: 'dual_installation',
|
|
281
|
+
status: 'info',
|
|
282
|
+
message: 'Could not check for duplicate installations',
|
|
283
|
+
details: {
|
|
284
|
+
error: err instanceof Error ? err.message : 'unknown error',
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
145
289
|
/**
|
|
146
290
|
* Run all environment checks.
|
|
147
291
|
*/
|
|
@@ -150,6 +294,7 @@ async function runEnvironmentChecks() {
|
|
|
150
294
|
checkNodeVersion(),
|
|
151
295
|
checkCliVersion(),
|
|
152
296
|
checkGitAvailable(),
|
|
297
|
+
checkDualInstallation(),
|
|
153
298
|
]);
|
|
154
299
|
return results;
|
|
155
300
|
}
|
|
@@ -54,7 +54,7 @@ function renderLlmSection(checks, verbose) {
|
|
|
54
54
|
// Build location text
|
|
55
55
|
let location;
|
|
56
56
|
if (serverVal === null) {
|
|
57
|
-
location = localVal ? '
|
|
57
|
+
location = localVal ? 'Local configured (vault keys not checked)' : 'Not configured locally (vault keys used for cloud runs)';
|
|
58
58
|
}
|
|
59
59
|
else if (serverVal && localVal) {
|
|
60
60
|
location = 'Configured (server + local)';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchagent/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.85",
|
|
4
4
|
"description": "Command-line interface for orchagent — deploy and run AI agents for your team",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "orchagent <hello@orchagent.io>",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"node": ">=18"
|
|
26
26
|
},
|
|
27
27
|
"bin": {
|
|
28
|
-
"orch": "dist/index.js"
|
|
28
|
+
"orch": "dist/index.js",
|
|
29
|
+
"orchagent": "dist/index.js"
|
|
29
30
|
},
|
|
30
31
|
"files": [
|
|
31
32
|
"dist",
|