@orchagent/cli 0.3.62 → 0.3.64
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/github.js +0 -7
- package/dist/commands/index.js +2 -0
- package/dist/commands/info.js +5 -0
- package/dist/commands/init.js +132 -15
- package/dist/commands/logs.js +182 -0
- package/dist/commands/publish.js +206 -9
- package/dist/commands/run.js +63 -4
- package/dist/commands/schedule.js +17 -1
- package/dist/commands/test.js +685 -153
- package/dist/index.js +2 -0
- package/dist/lib/api.js +67 -8
- package/dist/lib/dotenv.js +64 -0
- package/dist/lib/errors.js +7 -1
- package/dist/lib/suggest.js +146 -0
- package/package.json +1 -1
package/dist/commands/github.js
CHANGED
|
@@ -226,13 +226,10 @@ async function importFromGitHub(config, repo, options) {
|
|
|
226
226
|
selectedPath = selection.path;
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
|
-
// Determine visibility (default to public)
|
|
230
|
-
const isPublic = options.private ? false : true;
|
|
231
229
|
const importResult = await (0, api_1.request)(config, 'POST', '/github/import', {
|
|
232
230
|
body: JSON.stringify({
|
|
233
231
|
repo,
|
|
234
232
|
path: selectedPath,
|
|
235
|
-
is_public: isPublic,
|
|
236
233
|
name: options.name,
|
|
237
234
|
}),
|
|
238
235
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -240,7 +237,6 @@ async function importFromGitHub(config, repo, options) {
|
|
|
240
237
|
await (0, analytics_1.track)('cli_github_import', {
|
|
241
238
|
repo,
|
|
242
239
|
path: selectedPath,
|
|
243
|
-
is_public: isPublic,
|
|
244
240
|
type: importResult.agent.type,
|
|
245
241
|
});
|
|
246
242
|
if (options.json) {
|
|
@@ -253,7 +249,6 @@ async function importFromGitHub(config, repo, options) {
|
|
|
253
249
|
process.stdout.write(` Agent: ${importResult.agent.name}\n`);
|
|
254
250
|
process.stdout.write(` Version: ${importResult.agent.version}\n`);
|
|
255
251
|
process.stdout.write(` Type: ${importResult.agent.type}\n`);
|
|
256
|
-
process.stdout.write(` Public: ${isPublic ? chalk_1.default.green('Yes') : chalk_1.default.yellow('No')}\n`);
|
|
257
252
|
process.stdout.write('\n');
|
|
258
253
|
}
|
|
259
254
|
async function getSyncConfig(config, agentId, options) {
|
|
@@ -335,8 +330,6 @@ function registerGitHubCommand(program) {
|
|
|
335
330
|
.command('import <repo>')
|
|
336
331
|
.description('Import an agent or skill from GitHub')
|
|
337
332
|
.option('--path <path>', 'Path to manifest within repo (scans if not specified)')
|
|
338
|
-
.option('--public', 'Make the agent public (default)')
|
|
339
|
-
.option('--private', 'Make the agent private')
|
|
340
333
|
.option('--name <name>', 'Override agent name')
|
|
341
334
|
.option('--json', 'Output raw JSON')
|
|
342
335
|
.action(async (repo, options) => {
|
package/dist/commands/index.js
CHANGED
|
@@ -33,6 +33,7 @@ const schedule_1 = require("./schedule");
|
|
|
33
33
|
const service_1 = require("./service");
|
|
34
34
|
const transfer_1 = require("./transfer");
|
|
35
35
|
const pull_1 = require("./pull");
|
|
36
|
+
const logs_1 = require("./logs");
|
|
36
37
|
function registerCommands(program) {
|
|
37
38
|
(0, login_1.registerLoginCommand)(program);
|
|
38
39
|
(0, whoami_1.registerWhoamiCommand)(program);
|
|
@@ -66,4 +67,5 @@ function registerCommands(program) {
|
|
|
66
67
|
(0, service_1.registerServiceCommand)(program);
|
|
67
68
|
(0, transfer_1.registerTransferCommand)(program);
|
|
68
69
|
(0, pull_1.registerPullCommand)(program);
|
|
70
|
+
(0, logs_1.registerLogsCommand)(program);
|
|
69
71
|
}
|
package/dist/commands/info.js
CHANGED
|
@@ -60,6 +60,7 @@ async function getAgentInfo(config, org, agent, version) {
|
|
|
60
60
|
version: publicMeta.version,
|
|
61
61
|
description: (publicMeta.description ?? undefined),
|
|
62
62
|
supported_providers: publicMeta.supported_providers || ['any'],
|
|
63
|
+
callable: publicMeta.callable ?? false,
|
|
63
64
|
input_schema: publicMeta.input_schema,
|
|
64
65
|
output_schema: publicMeta.output_schema,
|
|
65
66
|
source_url: meta.source_url,
|
|
@@ -103,6 +104,7 @@ async function getAgentInfo(config, org, agent, version) {
|
|
|
103
104
|
version: targetAgent.version,
|
|
104
105
|
description: targetAgent.description,
|
|
105
106
|
prompt: targetAgent.prompt,
|
|
107
|
+
callable: targetAgent.callable ?? false,
|
|
106
108
|
input_schema: targetAgent.input_schema,
|
|
107
109
|
output_schema: targetAgent.output_schema,
|
|
108
110
|
supported_providers: targetAgent.supported_providers || ['any'],
|
|
@@ -140,6 +142,9 @@ function registerInfoCommand(program) {
|
|
|
140
142
|
process.stdout.write(`${agentData.description}\n\n`);
|
|
141
143
|
}
|
|
142
144
|
process.stdout.write(`Type: ${agentData.type}\n`);
|
|
145
|
+
if (agentData.callable) {
|
|
146
|
+
process.stdout.write(`Callable: ${chalk_1.default.green('yes')} — other agents can invoke this via the orchagent SDK\n`);
|
|
147
|
+
}
|
|
143
148
|
process.stdout.write(`Providers: ${agentData.supported_providers.join(', ')}\n`);
|
|
144
149
|
// Display pricing information
|
|
145
150
|
const priceStr = (0, pricing_1.formatPrice)(agentData);
|
package/dist/commands/init.js
CHANGED
|
@@ -72,6 +72,9 @@ def main():
|
|
|
72
72
|
user_input = data.get("input", "")
|
|
73
73
|
|
|
74
74
|
# --- Your logic here ---
|
|
75
|
+
# To use workspace secrets, add them to "required_secrets" in orchagent.json:
|
|
76
|
+
# "required_secrets": ["MY_API_KEY"]
|
|
77
|
+
# Then access via: os.environ["MY_API_KEY"]
|
|
75
78
|
result = f"Received: {user_input}"
|
|
76
79
|
# --- End your logic ---
|
|
77
80
|
|
|
@@ -83,15 +86,15 @@ if __name__ == "__main__":
|
|
|
83
86
|
main()
|
|
84
87
|
`;
|
|
85
88
|
function readmeTemplate(agentName, flavor) {
|
|
86
|
-
const inputField = flavor === 'managed_loop' ? 'task' : 'input';
|
|
87
|
-
const inputDescription = flavor === 'managed_loop' ? 'The task to perform' : 'The input to process';
|
|
89
|
+
const inputField = flavor === 'managed_loop' || flavor === 'orchestrator' ? 'task' : 'input';
|
|
90
|
+
const inputDescription = flavor === 'managed_loop' || flavor === 'orchestrator' ? 'The task to perform' : 'The input to process';
|
|
88
91
|
const cloudExample = flavor === 'code_runtime'
|
|
89
92
|
? `orchagent run ${agentName} --data '{"input": "Hello world"}'`
|
|
90
93
|
: `orchagent run ${agentName} --data '{"${inputField}": "Hello world"}'`;
|
|
91
94
|
const localExample = flavor === 'code_runtime'
|
|
92
95
|
? `orchagent run ${agentName} --local --data '{"input": "Hello world"}'`
|
|
93
96
|
: `orchagent run ${agentName} --local --data '{"${inputField}": "Hello world"}'`;
|
|
94
|
-
|
|
97
|
+
let readme = `# ${agentName}
|
|
95
98
|
|
|
96
99
|
A brief description of what this agent does.
|
|
97
100
|
|
|
@@ -121,6 +124,20 @@ ${localExample}
|
|
|
121
124
|
|-------|------|-------------|
|
|
122
125
|
| \`result\` | string | The agent's response |
|
|
123
126
|
`;
|
|
127
|
+
if (flavor === 'orchestrator') {
|
|
128
|
+
readme += `
|
|
129
|
+
## Dependencies
|
|
130
|
+
|
|
131
|
+
This orchestrator calls other agents. Update \`manifest.dependencies\` in \`orchagent.json\` with your actual dependencies.
|
|
132
|
+
|
|
133
|
+
**Publish order:** Publish dependency agents first, then this orchestrator.
|
|
134
|
+
|
|
135
|
+
| Dependency | Version | Description |
|
|
136
|
+
|------------|---------|-------------|
|
|
137
|
+
| \`org/agent-name\` | v1 | TODO: describe what this agent does |
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
return readme;
|
|
124
141
|
}
|
|
125
142
|
const AGENT_PROMPT_TEMPLATE = `You are a helpful AI agent.
|
|
126
143
|
|
|
@@ -154,6 +171,65 @@ const AGENT_SCHEMA_TEMPLATE = `{
|
|
|
154
171
|
}
|
|
155
172
|
}
|
|
156
173
|
`;
|
|
174
|
+
const ORCHESTRATOR_MAIN_PY = `"""
|
|
175
|
+
orchagent orchestrator entrypoint.
|
|
176
|
+
|
|
177
|
+
Reads JSON input from stdin, calls dependency agents via the orchagent SDK,
|
|
178
|
+
and writes JSON output to stdout.
|
|
179
|
+
|
|
180
|
+
Usage:
|
|
181
|
+
echo '{"task": "do something"}' | python main.py
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
import asyncio
|
|
185
|
+
import json
|
|
186
|
+
import sys
|
|
187
|
+
|
|
188
|
+
from orchagent import AgentClient
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def main():
|
|
192
|
+
# Read JSON input from stdin
|
|
193
|
+
raw = sys.stdin.read()
|
|
194
|
+
try:
|
|
195
|
+
data = json.loads(raw) if raw.strip() else {}
|
|
196
|
+
except json.JSONDecodeError:
|
|
197
|
+
print(json.dumps({"error": "Invalid JSON input"}))
|
|
198
|
+
sys.exit(1)
|
|
199
|
+
|
|
200
|
+
task = data.get("task", "")
|
|
201
|
+
|
|
202
|
+
# --- Your orchestration logic here ---
|
|
203
|
+
# The AgentClient reads ORCHAGENT_SERVICE_KEY from the environment automatically.
|
|
204
|
+
# Do NOT add ORCHAGENT_SERVICE_KEY to required_secrets — the gateway injects it.
|
|
205
|
+
client = AgentClient()
|
|
206
|
+
|
|
207
|
+
# Call a dependency agent (must be listed in manifest.dependencies)
|
|
208
|
+
result = asyncio.run(
|
|
209
|
+
client.call("org/agent-name@v1", {"input": task})
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# You can chain multiple calls, run them in parallel, or add conditional logic:
|
|
213
|
+
#
|
|
214
|
+
# Sequential:
|
|
215
|
+
# result2 = asyncio.run(client.call("org/another-agent@v1", {"input": result}))
|
|
216
|
+
#
|
|
217
|
+
# Parallel:
|
|
218
|
+
# r1, r2 = asyncio.run(asyncio.gather(
|
|
219
|
+
# client.call("org/agent-a@v1", {"input": task}),
|
|
220
|
+
# client.call("org/agent-b@v1", {"input": task}),
|
|
221
|
+
# ))
|
|
222
|
+
# --- End orchestration logic ---
|
|
223
|
+
|
|
224
|
+
# Write JSON output to stdout
|
|
225
|
+
print(json.dumps({"result": result, "success": True}))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
if __name__ == "__main__":
|
|
229
|
+
main()
|
|
230
|
+
`;
|
|
231
|
+
const ORCHESTRATOR_REQUIREMENTS = `orchagent-sdk>=0.1.0
|
|
232
|
+
`;
|
|
157
233
|
const SKILL_TEMPLATE = `---
|
|
158
234
|
name: my-skill
|
|
159
235
|
description: When to use this skill
|
|
@@ -186,6 +262,7 @@ function registerInitCommand(program) {
|
|
|
186
262
|
.description('Initialize a new agent project')
|
|
187
263
|
.argument('[name]', 'Agent name (default: current directory name)')
|
|
188
264
|
.option('--type <type>', 'Type: prompt, tool, agent, or skill (legacy aliases: agentic, code)', 'prompt')
|
|
265
|
+
.option('--orchestrator', 'Create an orchestrator agent with dependency scaffolding and SDK boilerplate')
|
|
189
266
|
.option('--run-mode <mode>', 'Run mode for agents: on_demand or always_on', 'on_demand')
|
|
190
267
|
.action(async (name, options) => {
|
|
191
268
|
const cwd = process.cwd();
|
|
@@ -193,7 +270,13 @@ function registerInitCommand(program) {
|
|
|
193
270
|
if (!['on_demand', 'always_on'].includes(runMode)) {
|
|
194
271
|
throw new errors_1.CliError("Invalid --run-mode. Use 'on_demand' or 'always_on'.");
|
|
195
272
|
}
|
|
196
|
-
|
|
273
|
+
let initMode = resolveInitFlavor(options.type);
|
|
274
|
+
if (options.orchestrator) {
|
|
275
|
+
if (initMode.type === 'skill') {
|
|
276
|
+
throw new errors_1.CliError('Cannot use --orchestrator with --type skill. Orchestrators are agent-type agents that call other agents.');
|
|
277
|
+
}
|
|
278
|
+
initMode = { type: 'agent', flavor: 'orchestrator' };
|
|
279
|
+
}
|
|
197
280
|
// When a name is provided, create a subdirectory for the project
|
|
198
281
|
const targetDir = name ? path_1.default.join(cwd, name) : cwd;
|
|
199
282
|
const agentName = name || path_1.default.basename(cwd);
|
|
@@ -244,7 +327,7 @@ function registerInitCommand(program) {
|
|
|
244
327
|
throw err;
|
|
245
328
|
}
|
|
246
329
|
}
|
|
247
|
-
if (initMode.flavor !== 'code_runtime' && runMode === 'always_on') {
|
|
330
|
+
if (initMode.flavor !== 'code_runtime' && initMode.flavor !== 'orchestrator' && runMode === 'always_on') {
|
|
248
331
|
throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"python main.py\" }). Use --type tool for code-runtime agents.");
|
|
249
332
|
}
|
|
250
333
|
// Create manifest and type-specific files
|
|
@@ -252,17 +335,38 @@ function registerInitCommand(program) {
|
|
|
252
335
|
manifest.name = agentName;
|
|
253
336
|
manifest.type = initMode.type;
|
|
254
337
|
manifest.run_mode = runMode;
|
|
255
|
-
if (initMode.flavor === '
|
|
338
|
+
if (initMode.flavor === 'orchestrator') {
|
|
339
|
+
manifest.description = 'An orchestrator agent that coordinates other agents';
|
|
340
|
+
manifest.runtime = { command: 'python main.py' };
|
|
341
|
+
manifest.manifest = {
|
|
342
|
+
manifest_version: 1,
|
|
343
|
+
dependencies: [{ id: 'org/agent-name', version: 'v1' }],
|
|
344
|
+
max_hops: 3,
|
|
345
|
+
timeout_ms: 120000,
|
|
346
|
+
per_call_downstream_cap: 50,
|
|
347
|
+
};
|
|
348
|
+
manifest.required_secrets = [];
|
|
349
|
+
}
|
|
350
|
+
else if (initMode.flavor === 'managed_loop') {
|
|
256
351
|
manifest.description = 'An AI agent with tool use';
|
|
257
352
|
manifest.supported_providers = ['anthropic'];
|
|
258
353
|
manifest.loop = { max_turns: 25 };
|
|
354
|
+
manifest.required_secrets = [];
|
|
259
355
|
}
|
|
260
356
|
else if (initMode.flavor === 'code_runtime') {
|
|
261
357
|
manifest.description = 'A code-runtime agent';
|
|
262
358
|
manifest.runtime = { command: 'python main.py' };
|
|
359
|
+
manifest.required_secrets = [];
|
|
263
360
|
}
|
|
264
361
|
await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
265
|
-
if (initMode.flavor === '
|
|
362
|
+
if (initMode.flavor === 'orchestrator') {
|
|
363
|
+
const entrypointPath = path_1.default.join(targetDir, 'main.py');
|
|
364
|
+
const requirementsPath = path_1.default.join(targetDir, 'requirements.txt');
|
|
365
|
+
await promises_1.default.writeFile(entrypointPath, ORCHESTRATOR_MAIN_PY);
|
|
366
|
+
await promises_1.default.writeFile(requirementsPath, ORCHESTRATOR_REQUIREMENTS);
|
|
367
|
+
await promises_1.default.writeFile(schemaPath, AGENT_SCHEMA_TEMPLATE);
|
|
368
|
+
}
|
|
369
|
+
else if (initMode.flavor === 'code_runtime') {
|
|
266
370
|
const entrypointPath = path_1.default.join(targetDir, 'main.py');
|
|
267
371
|
await promises_1.default.writeFile(entrypointPath, CODE_TEMPLATE_PY);
|
|
268
372
|
await promises_1.default.writeFile(schemaPath, SCHEMA_TEMPLATE);
|
|
@@ -281,19 +385,32 @@ function registerInitCommand(program) {
|
|
|
281
385
|
process.stdout.write(`Initialized agent "${agentName}" in ${targetDir}\n`);
|
|
282
386
|
process.stdout.write(`\nFiles created:\n`);
|
|
283
387
|
const prefix = name ? name + '/' : '';
|
|
284
|
-
process.stdout.write(` ${prefix}orchagent.json
|
|
285
|
-
if (initMode.flavor === '
|
|
286
|
-
process.stdout.write(` ${prefix}main.py
|
|
388
|
+
process.stdout.write(` ${prefix}orchagent.json - Agent configuration\n`);
|
|
389
|
+
if (initMode.flavor === 'orchestrator') {
|
|
390
|
+
process.stdout.write(` ${prefix}main.py - Orchestrator entrypoint (SDK calls)\n`);
|
|
391
|
+
process.stdout.write(` ${prefix}requirements.txt - Python dependencies (orchagent-sdk)\n`);
|
|
392
|
+
}
|
|
393
|
+
else if (initMode.flavor === 'code_runtime') {
|
|
394
|
+
process.stdout.write(` ${prefix}main.py - Agent entrypoint (stdin/stdout JSON)\n`);
|
|
287
395
|
}
|
|
288
396
|
else {
|
|
289
|
-
process.stdout.write(` ${prefix}prompt.md
|
|
397
|
+
process.stdout.write(` ${prefix}prompt.md - Prompt template\n`);
|
|
290
398
|
}
|
|
291
|
-
process.stdout.write(` ${prefix}schema.json
|
|
292
|
-
process.stdout.write(` ${prefix}README.md
|
|
399
|
+
process.stdout.write(` ${prefix}schema.json - Input/output schemas\n`);
|
|
400
|
+
process.stdout.write(` ${prefix}README.md - Agent documentation\n`);
|
|
293
401
|
process.stdout.write(` Run mode: ${runMode}\n`);
|
|
294
|
-
process.stdout.write(` Execution: ${initMode.flavor}\n`);
|
|
402
|
+
process.stdout.write(` Execution: ${initMode.flavor === 'orchestrator' ? 'code_runtime (orchestrator)' : initMode.flavor}\n`);
|
|
295
403
|
process.stdout.write(`\nNext steps:\n`);
|
|
296
|
-
if (initMode.flavor === '
|
|
404
|
+
if (initMode.flavor === 'orchestrator') {
|
|
405
|
+
const stepNum = name ? 2 : 1;
|
|
406
|
+
if (name) {
|
|
407
|
+
process.stdout.write(` 1. cd ${name}\n`);
|
|
408
|
+
}
|
|
409
|
+
process.stdout.write(` ${stepNum}. Update manifest.dependencies in orchagent.json with your actual agents\n`);
|
|
410
|
+
process.stdout.write(` ${stepNum + 1}. Edit main.py with your orchestration logic\n`);
|
|
411
|
+
process.stdout.write(` ${stepNum + 2}. Publish dependency agents first, then: orchagent publish\n`);
|
|
412
|
+
}
|
|
413
|
+
else if (initMode.flavor === 'code_runtime') {
|
|
297
414
|
const stepNum = name ? 2 : 1;
|
|
298
415
|
if (name) {
|
|
299
416
|
process.stdout.write(` 1. cd ${name}\n`);
|
|
@@ -0,0 +1,182 @@
|
|
|
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.registerLogsCommand = registerLogsCommand;
|
|
7
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const config_1 = require("../lib/config");
|
|
10
|
+
const api_1 = require("../lib/api");
|
|
11
|
+
const errors_1 = require("../lib/errors");
|
|
12
|
+
const output_1 = require("../lib/output");
|
|
13
|
+
// ============================================
|
|
14
|
+
// HELPERS
|
|
15
|
+
// ============================================
|
|
16
|
+
async function resolveWorkspaceId(config, slug) {
|
|
17
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
18
|
+
const targetSlug = slug ?? configFile.workspace;
|
|
19
|
+
if (!targetSlug) {
|
|
20
|
+
throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
|
|
21
|
+
}
|
|
22
|
+
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
23
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
24
|
+
if (!workspace) {
|
|
25
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
|
|
26
|
+
}
|
|
27
|
+
return workspace.id;
|
|
28
|
+
}
|
|
29
|
+
function formatDate(iso) {
|
|
30
|
+
if (!iso)
|
|
31
|
+
return '-';
|
|
32
|
+
return new Date(iso).toLocaleString();
|
|
33
|
+
}
|
|
34
|
+
function statusColor(status) {
|
|
35
|
+
if (!status)
|
|
36
|
+
return '-';
|
|
37
|
+
switch (status) {
|
|
38
|
+
case 'completed':
|
|
39
|
+
return chalk_1.default.green(status);
|
|
40
|
+
case 'failed':
|
|
41
|
+
return chalk_1.default.red(status);
|
|
42
|
+
case 'running':
|
|
43
|
+
return chalk_1.default.yellow(status);
|
|
44
|
+
case 'timeout':
|
|
45
|
+
return chalk_1.default.red(status);
|
|
46
|
+
default:
|
|
47
|
+
return status;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function formatDuration(ms) {
|
|
51
|
+
if (ms == null)
|
|
52
|
+
return '-';
|
|
53
|
+
if (ms < 1000)
|
|
54
|
+
return `${ms}ms`;
|
|
55
|
+
if (ms < 60000)
|
|
56
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
57
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
58
|
+
}
|
|
59
|
+
/** Detect if a string looks like a UUID (run ID) */
|
|
60
|
+
function isUuid(value) {
|
|
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
|
+
}
|
|
63
|
+
// ============================================
|
|
64
|
+
// COMMAND REGISTRATION
|
|
65
|
+
// ============================================
|
|
66
|
+
function registerLogsCommand(program) {
|
|
67
|
+
program
|
|
68
|
+
.command('logs [target]')
|
|
69
|
+
.description('View execution logs. Use with no args to list recent runs, an agent name to filter, or a run ID for full detail.')
|
|
70
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
71
|
+
.option('--status <status>', 'Filter by status: running, completed, failed, timeout')
|
|
72
|
+
.option('--limit <n>', 'Number of runs to show (default: 20)', '20')
|
|
73
|
+
.option('--json', 'Output as JSON')
|
|
74
|
+
.action(async (target, options) => {
|
|
75
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
76
|
+
if (!config.apiKey) {
|
|
77
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
78
|
+
}
|
|
79
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
80
|
+
// If target looks like a UUID, show detailed logs for that run
|
|
81
|
+
if (target && isUuid(target)) {
|
|
82
|
+
await showRunLogs(config, workspaceId, target, options.json);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Otherwise list runs, optionally filtered by agent name
|
|
86
|
+
await listRuns(config, workspaceId, target, options);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// ============================================
|
|
90
|
+
// LIST RUNS
|
|
91
|
+
// ============================================
|
|
92
|
+
async function listRuns(config, workspaceId, agentName, options) {
|
|
93
|
+
const params = new URLSearchParams();
|
|
94
|
+
if (agentName)
|
|
95
|
+
params.set('agent_name', agentName);
|
|
96
|
+
if (options.status)
|
|
97
|
+
params.set('status', options.status);
|
|
98
|
+
const limit = parseInt(options.limit ?? '20', 10);
|
|
99
|
+
params.set('limit', String(Math.min(Math.max(1, limit), 200)));
|
|
100
|
+
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
101
|
+
const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs${qs}`);
|
|
102
|
+
if (options.json) {
|
|
103
|
+
(0, output_1.printJson)(result);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (result.runs.length === 0) {
|
|
107
|
+
if (agentName) {
|
|
108
|
+
process.stdout.write(`No runs found for agent '${agentName}'.\n`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
process.stdout.write('No runs found in this workspace.\n');
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const table = new cli_table3_1.default({
|
|
116
|
+
head: [
|
|
117
|
+
chalk_1.default.bold('Run ID'),
|
|
118
|
+
chalk_1.default.bold('Agent'),
|
|
119
|
+
chalk_1.default.bold('Status'),
|
|
120
|
+
chalk_1.default.bold('Duration'),
|
|
121
|
+
chalk_1.default.bold('Source'),
|
|
122
|
+
chalk_1.default.bold('Started'),
|
|
123
|
+
chalk_1.default.bold('Error'),
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
result.runs.forEach((r) => {
|
|
127
|
+
const errorPreview = r.error_message
|
|
128
|
+
? chalk_1.default.red(r.error_message.length > 50 ? r.error_message.slice(0, 50) + '...' : r.error_message)
|
|
129
|
+
: chalk_1.default.gray('-');
|
|
130
|
+
table.push([
|
|
131
|
+
r.id.slice(0, 8),
|
|
132
|
+
`${r.agent_name}@${r.agent_version}`,
|
|
133
|
+
statusColor(r.status),
|
|
134
|
+
formatDuration(r.duration_ms),
|
|
135
|
+
r.trigger_source ?? '-',
|
|
136
|
+
formatDate(r.started_at || r.created_at),
|
|
137
|
+
errorPreview,
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
process.stdout.write(table.toString() + '\n');
|
|
141
|
+
if (result.total > result.runs.length) {
|
|
142
|
+
process.stdout.write(chalk_1.default.gray(`\nShowing ${result.runs.length} of ${result.total} runs. Use --limit to see more.\n`));
|
|
143
|
+
}
|
|
144
|
+
process.stdout.write(chalk_1.default.gray('\nView detailed logs for a run: orch logs <run-id>\n'));
|
|
145
|
+
}
|
|
146
|
+
// ============================================
|
|
147
|
+
// SHOW RUN LOGS
|
|
148
|
+
// ============================================
|
|
149
|
+
async function showRunLogs(config, workspaceId, runId, json) {
|
|
150
|
+
const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs/${runId}/logs`);
|
|
151
|
+
if (json) {
|
|
152
|
+
(0, output_1.printJson)(result);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Header
|
|
156
|
+
process.stdout.write(chalk_1.default.bold(`\nRun ${runId}\n`) +
|
|
157
|
+
`Agent: ${result.agent_name ?? '-'}@${result.agent_version ?? '-'}\n` +
|
|
158
|
+
`Status: ${statusColor(result.run_status)}\n` +
|
|
159
|
+
`Duration: ${formatDuration(result.execution_time_ms)}\n`);
|
|
160
|
+
if (result.exit_code != null) {
|
|
161
|
+
const exitLabel = result.exit_code === 0 ? chalk_1.default.green(String(result.exit_code)) : chalk_1.default.red(String(result.exit_code));
|
|
162
|
+
process.stdout.write(`Exit code: ${exitLabel}\n`);
|
|
163
|
+
}
|
|
164
|
+
// Error message
|
|
165
|
+
if (result.error_message) {
|
|
166
|
+
process.stdout.write('\n' + chalk_1.default.red.bold('Error:\n') + chalk_1.default.red(result.error_message) + '\n');
|
|
167
|
+
}
|
|
168
|
+
// Stdout
|
|
169
|
+
if (result.stdout) {
|
|
170
|
+
process.stdout.write('\n' + chalk_1.default.bold.cyan('--- stdout ---') + '\n' + result.stdout + '\n');
|
|
171
|
+
}
|
|
172
|
+
// Stderr
|
|
173
|
+
if (result.stderr) {
|
|
174
|
+
process.stdout.write('\n' + chalk_1.default.bold.yellow('--- stderr ---') + '\n' + result.stderr + '\n');
|
|
175
|
+
}
|
|
176
|
+
// No execution log available
|
|
177
|
+
if (!result.has_execution_log && !result.error_message) {
|
|
178
|
+
process.stdout.write(chalk_1.default.gray('\nNo sandbox output available for this run. ' +
|
|
179
|
+
'Execution logs are captured for agents with a code runtime (tool/agent types with runtime.command).\n'));
|
|
180
|
+
}
|
|
181
|
+
process.stdout.write('\n');
|
|
182
|
+
}
|