@orchagent/cli 0.3.89 → 0.3.91
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/completion.js +379 -0
- package/dist/commands/dag.js +16 -7
- package/dist/commands/delete.js +9 -4
- package/dist/commands/diff.js +10 -2
- package/dist/commands/estimate.js +5 -2
- package/dist/commands/fork.js +7 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/info.js +8 -1
- package/dist/commands/init-wizard.js +160 -0
- package/dist/commands/init.js +38 -2
- package/dist/commands/login.js +8 -0
- package/dist/commands/logs.js +17 -7
- package/dist/commands/metrics.js +16 -7
- package/dist/commands/replay.js +16 -7
- package/dist/commands/run.js +51 -4
- package/dist/commands/schedule.js +72 -8
- package/dist/commands/secrets.js +16 -7
- package/dist/commands/service.js +16 -7
- package/dist/commands/skill.js +84 -8
- package/dist/commands/trace.js +16 -7
- package/dist/commands/tree.js +7 -1
- package/dist/commands/workspace.js +16 -7
- package/dist/lib/agent-ref.js +4 -1
- package/dist/lib/api.js +2 -8
- package/dist/lib/browser-auth.js +1 -0
- package/dist/lib/doctor/checks/environment.js +44 -1
- package/package.json +1 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Interactive wizard for `orch init`.
|
|
4
|
+
*
|
|
5
|
+
* Runs when `orch init` is invoked without arguments in a TTY.
|
|
6
|
+
* Uses Node.js built-in readline/promises — no extra dependencies.
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.TEMPLATE_REGISTRY = void 0;
|
|
13
|
+
exports.runInitWizard = runInitWizard;
|
|
14
|
+
exports.printTemplateList = printTemplateList;
|
|
15
|
+
const promises_1 = __importDefault(require("readline/promises"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
exports.TEMPLATE_REGISTRY = [
|
|
18
|
+
{ name: 'discord', description: 'Discord bot powered by Claude (Python)', type: 'agent', language: 'python', runMode: 'always_on' },
|
|
19
|
+
{ name: 'discord-js', description: 'Discord bot powered by Claude (JavaScript)', type: 'agent', language: 'javascript', runMode: 'always_on' },
|
|
20
|
+
{ name: 'support-agent', description: 'Multi-platform support agent (Discord/Telegram/Slack)', type: 'agent', language: 'python', runMode: 'always_on' },
|
|
21
|
+
{ name: 'fan-out', description: 'Parallel orchestration — call agents concurrently', type: 'agent', language: 'both', runMode: 'on_demand' },
|
|
22
|
+
{ name: 'pipeline', description: 'Sequential orchestration — chain agents in series', type: 'agent', language: 'both', runMode: 'on_demand' },
|
|
23
|
+
{ name: 'map-reduce', description: 'Map-reduce orchestration — split, process, aggregate', type: 'agent', language: 'both', runMode: 'on_demand' },
|
|
24
|
+
{ name: 'github-weekly-summary', description: 'GitHub activity analyser with Discord delivery', type: 'agent', language: 'python', runMode: 'on_demand' },
|
|
25
|
+
];
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Prompt helpers (readline-based, no dependencies)
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
function createInterface() {
|
|
30
|
+
return promises_1.default.createInterface({
|
|
31
|
+
input: process.stdin,
|
|
32
|
+
output: process.stderr, // prompts on stderr so stdout stays clean for piping
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async function promptText(rl, question, defaultValue) {
|
|
36
|
+
const suffix = defaultValue ? ` (${defaultValue})` : '';
|
|
37
|
+
const answer = await rl.question(` ${question}${suffix}: `);
|
|
38
|
+
return answer.trim() || defaultValue || '';
|
|
39
|
+
}
|
|
40
|
+
async function promptSelect(rl, question, options) {
|
|
41
|
+
process.stderr.write(`\n ${question}\n`);
|
|
42
|
+
for (let i = 0; i < options.length; i++) {
|
|
43
|
+
const opt = options[i];
|
|
44
|
+
process.stderr.write(` ${i + 1}) ${opt.label} — ${opt.description}\n`);
|
|
45
|
+
}
|
|
46
|
+
process.stderr.write('\n');
|
|
47
|
+
while (true) {
|
|
48
|
+
const answer = await rl.question(` Choice [1-${options.length}]: `);
|
|
49
|
+
const num = parseInt(answer.trim(), 10);
|
|
50
|
+
if (num >= 1 && num <= options.length) {
|
|
51
|
+
return options[num - 1].value;
|
|
52
|
+
}
|
|
53
|
+
process.stderr.write(` Please enter a number between 1 and ${options.length}.\n`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Wizard
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
async function runInitWizard() {
|
|
60
|
+
const rl = createInterface();
|
|
61
|
+
try {
|
|
62
|
+
process.stderr.write('\n orch init — interactive setup\n');
|
|
63
|
+
process.stderr.write(' ─────────────────────────────\n\n');
|
|
64
|
+
// 1. Agent name
|
|
65
|
+
const dirName = path_1.default.basename(process.cwd());
|
|
66
|
+
const name = await promptText(rl, 'Agent name', dirName);
|
|
67
|
+
const createSubdir = name !== dirName;
|
|
68
|
+
// 2. Agent type
|
|
69
|
+
const type = await promptSelect(rl, 'What type of agent?', [
|
|
70
|
+
{ value: 'prompt', label: 'prompt', description: 'Single LLM call with structured I/O' },
|
|
71
|
+
{ value: 'tool', label: 'tool', description: 'Your own code (API calls, file processing)' },
|
|
72
|
+
{ value: 'agent', label: 'agent', description: 'Multi-step LLM reasoning with tool use' },
|
|
73
|
+
{ value: 'skill', label: 'skill', description: 'Knowledge module for other agents' },
|
|
74
|
+
]);
|
|
75
|
+
// Skill and prompt don't need language/template
|
|
76
|
+
if (type === 'skill' || type === 'prompt') {
|
|
77
|
+
rl.close();
|
|
78
|
+
return {
|
|
79
|
+
name: createSubdir ? name : undefined,
|
|
80
|
+
type,
|
|
81
|
+
language: 'python',
|
|
82
|
+
template: undefined,
|
|
83
|
+
runMode: 'on_demand',
|
|
84
|
+
orchestrator: false,
|
|
85
|
+
loop: false,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// 3. Language (tool/agent only)
|
|
89
|
+
const language = await promptSelect(rl, 'Language?', [
|
|
90
|
+
{ value: 'python', label: 'Python', description: 'Recommended — broadest library support' },
|
|
91
|
+
{ value: 'javascript', label: 'JavaScript', description: 'Node.js runtime' },
|
|
92
|
+
]);
|
|
93
|
+
// 4. Template (optional)
|
|
94
|
+
const applicableTemplates = exports.TEMPLATE_REGISTRY.filter(t => {
|
|
95
|
+
if (t.language !== 'both' && t.language !== language)
|
|
96
|
+
return false;
|
|
97
|
+
return true;
|
|
98
|
+
});
|
|
99
|
+
const templateOptions = [
|
|
100
|
+
{ value: 'none', label: 'No template', description: 'Start from scratch' },
|
|
101
|
+
...applicableTemplates.map(t => ({
|
|
102
|
+
value: t.name,
|
|
103
|
+
label: t.name,
|
|
104
|
+
description: t.description,
|
|
105
|
+
})),
|
|
106
|
+
];
|
|
107
|
+
const template = await promptSelect(rl, 'Start from a template?', templateOptions);
|
|
108
|
+
// 5. Run mode (only if no template selected — templates set their own)
|
|
109
|
+
let runMode = 'on_demand';
|
|
110
|
+
if (template === 'none') {
|
|
111
|
+
runMode = await promptSelect(rl, 'Run mode?', [
|
|
112
|
+
{ value: 'on_demand', label: 'on_demand', description: 'Run per invocation (default)' },
|
|
113
|
+
{ value: 'always_on', label: 'always_on', description: 'Long-lived HTTP service' },
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
// 6. Agent subtype (only for agent type, no template)
|
|
117
|
+
let orchestrator = false;
|
|
118
|
+
let loop = false;
|
|
119
|
+
if (type === 'agent' && template === 'none') {
|
|
120
|
+
const agentSubtype = await promptSelect(rl, 'Agent execution mode?', [
|
|
121
|
+
{ value: 'code', label: 'Code runtime', description: 'You write the logic (call any LLM provider)' },
|
|
122
|
+
{ value: 'orchestrator', label: 'Orchestrator', description: 'Coordinate other agents via SDK' },
|
|
123
|
+
...(language === 'python' ? [{ value: 'loop', label: 'Managed loop', description: 'Platform-managed LLM loop with tool use' }] : []),
|
|
124
|
+
]);
|
|
125
|
+
orchestrator = agentSubtype === 'orchestrator';
|
|
126
|
+
loop = agentSubtype === 'loop';
|
|
127
|
+
}
|
|
128
|
+
rl.close();
|
|
129
|
+
return {
|
|
130
|
+
name: createSubdir ? name : undefined,
|
|
131
|
+
type,
|
|
132
|
+
language,
|
|
133
|
+
template: template === 'none' ? undefined : template,
|
|
134
|
+
runMode,
|
|
135
|
+
orchestrator,
|
|
136
|
+
loop,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
rl.close();
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// List templates (for --list-templates flag)
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
function printTemplateList() {
|
|
148
|
+
process.stdout.write('\nAvailable templates:\n\n');
|
|
149
|
+
const nameWidth = Math.max(...exports.TEMPLATE_REGISTRY.map(t => t.name.length)) + 2;
|
|
150
|
+
const langWidth = 12;
|
|
151
|
+
process.stdout.write(` ${'TEMPLATE'.padEnd(nameWidth)}${'LANGUAGE'.padEnd(langWidth)}${'RUN MODE'.padEnd(12)}DESCRIPTION\n`);
|
|
152
|
+
process.stdout.write(` ${'─'.repeat(nameWidth)}${'─'.repeat(langWidth)}${'─'.repeat(12)}${'─'.repeat(40)}\n`);
|
|
153
|
+
for (const t of exports.TEMPLATE_REGISTRY) {
|
|
154
|
+
const lang = t.language === 'both' ? 'py / js' : t.language === 'python' ? 'python' : 'javascript';
|
|
155
|
+
process.stdout.write(` ${t.name.padEnd(nameWidth)}${lang.padEnd(langWidth)}${t.runMode.padEnd(12)}${t.description}\n`);
|
|
156
|
+
}
|
|
157
|
+
process.stdout.write('\nUsage:\n');
|
|
158
|
+
process.stdout.write(' orch init my-agent --template <name>\n');
|
|
159
|
+
process.stdout.write(' orch init my-agent --template <name> --language javascript\n\n');
|
|
160
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -4,9 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.registerInitCommand = registerInitCommand;
|
|
7
|
+
const commander_1 = require("commander");
|
|
7
8
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const errors_1 = require("../lib/errors");
|
|
11
|
+
const init_wizard_1 = require("./init-wizard");
|
|
10
12
|
const github_weekly_summary_1 = require("./templates/github-weekly-summary");
|
|
11
13
|
const support_agent_1 = require("./templates/support-agent");
|
|
12
14
|
const MANIFEST_TEMPLATE = `{
|
|
@@ -1498,15 +1500,49 @@ function resolveInitFlavor(typeOption) {
|
|
|
1498
1500
|
function registerInitCommand(program) {
|
|
1499
1501
|
program
|
|
1500
1502
|
.command('init')
|
|
1501
|
-
.description('Initialize a new agent project')
|
|
1503
|
+
.description('Initialize a new agent project (interactive wizard when called without arguments)')
|
|
1502
1504
|
.argument('[name]', 'Agent name (default: current directory name)')
|
|
1503
1505
|
.option('--type <type>', 'Type: prompt, tool, agent, or skill (legacy aliases: agentic, code)', 'prompt')
|
|
1504
1506
|
.option('--orchestrator', 'Create an orchestrator agent with dependency scaffolding and SDK boilerplate')
|
|
1505
1507
|
.option('--run-mode <mode>', 'Run mode for agents: on_demand or always_on', 'on_demand')
|
|
1506
1508
|
.option('--language <lang>', 'Language: python or javascript (default: python)', 'python')
|
|
1507
1509
|
.option('--loop', 'Use platform-managed LLM loop instead of code runtime (requires --type agent)')
|
|
1508
|
-
.option('--template <name>', 'Start from a template (
|
|
1510
|
+
.option('--template <name>', 'Start from a template (use --list-templates to see options)')
|
|
1511
|
+
.option('--list-templates', 'Show available templates with descriptions')
|
|
1509
1512
|
.action(async (name, options) => {
|
|
1513
|
+
// --list-templates: print and exit
|
|
1514
|
+
if (options.listTemplates) {
|
|
1515
|
+
(0, init_wizard_1.printTemplateList)();
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
// Interactive wizard: no name, TTY, and no explicit flags that indicate non-interactive intent
|
|
1519
|
+
const rawArgs = process.argv.slice(2);
|
|
1520
|
+
const initArgIndex = rawArgs.indexOf('init');
|
|
1521
|
+
const argsAfterInit = initArgIndex >= 0 ? rawArgs.slice(initArgIndex + 1) : [];
|
|
1522
|
+
const hasExplicitFlags = argsAfterInit.some(a => a.startsWith('--'));
|
|
1523
|
+
const hasNameArg = name !== undefined;
|
|
1524
|
+
if (!hasNameArg && !hasExplicitFlags && process.stdin.isTTY) {
|
|
1525
|
+
const wizard = await (0, init_wizard_1.runInitWizard)();
|
|
1526
|
+
// Re-invoke the action with wizard results by constructing args
|
|
1527
|
+
const wizardArgs = ['node', 'orch', 'init'];
|
|
1528
|
+
if (wizard.name)
|
|
1529
|
+
wizardArgs.push(wizard.name);
|
|
1530
|
+
wizardArgs.push('--type', wizard.type);
|
|
1531
|
+
wizardArgs.push('--language', wizard.language);
|
|
1532
|
+
wizardArgs.push('--run-mode', wizard.runMode);
|
|
1533
|
+
if (wizard.template)
|
|
1534
|
+
wizardArgs.push('--template', wizard.template);
|
|
1535
|
+
if (wizard.orchestrator)
|
|
1536
|
+
wizardArgs.push('--orchestrator');
|
|
1537
|
+
if (wizard.loop)
|
|
1538
|
+
wizardArgs.push('--loop');
|
|
1539
|
+
// Create a fresh program to run with wizard args
|
|
1540
|
+
const wizardProgram = new commander_1.Command();
|
|
1541
|
+
wizardProgram.exitOverride();
|
|
1542
|
+
registerInitCommand(wizardProgram);
|
|
1543
|
+
await wizardProgram.parseAsync(wizardArgs);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1510
1546
|
const cwd = process.cwd();
|
|
1511
1547
|
let runMode = (options.runMode || 'on_demand').trim().toLowerCase();
|
|
1512
1548
|
if (!['on_demand', 'always_on'].includes(runMode)) {
|
package/dist/commands/login.js
CHANGED
|
@@ -56,6 +56,7 @@ async function keyBasedLogin(apiKey) {
|
|
|
56
56
|
const resolved = await (0, config_1.getResolvedConfig)({ api_key: apiKey });
|
|
57
57
|
const org = await (0, api_1.getOrg)(resolved);
|
|
58
58
|
const existing = await (0, config_1.loadConfig)();
|
|
59
|
+
const isFirstLogin = !existing.api_key;
|
|
59
60
|
const nextConfig = {
|
|
60
61
|
...existing,
|
|
61
62
|
api_key: apiKey,
|
|
@@ -67,12 +68,16 @@ async function keyBasedLogin(apiKey) {
|
|
|
67
68
|
await (0, config_1.saveConfig)(nextConfig);
|
|
68
69
|
await (0, analytics_1.track)('cli_login', { method: 'key' });
|
|
69
70
|
process.stdout.write(`✓ Logged in to ${org.slug}\n`);
|
|
71
|
+
if (isFirstLogin) {
|
|
72
|
+
process.stdout.write('\n Tip: Run `orch doctor` to verify your setup.\n\n');
|
|
73
|
+
}
|
|
70
74
|
}
|
|
71
75
|
/**
|
|
72
76
|
* Login via browser OAuth flow.
|
|
73
77
|
*/
|
|
74
78
|
async function browserBasedLogin(port) {
|
|
75
79
|
const existing = await (0, config_1.loadConfig)();
|
|
80
|
+
const isFirstLogin = !existing.api_key;
|
|
76
81
|
const resolved = await (0, config_1.getResolvedConfig)();
|
|
77
82
|
process.stdout.write('Opening browser for authentication...\n');
|
|
78
83
|
try {
|
|
@@ -88,6 +93,9 @@ async function browserBasedLogin(port) {
|
|
|
88
93
|
await (0, config_1.saveConfig)(nextConfig);
|
|
89
94
|
await (0, analytics_1.track)('cli_login', { method: 'browser' });
|
|
90
95
|
process.stdout.write(`\n✓ Logged in to ${result.orgSlug}\n`);
|
|
96
|
+
if (isFirstLogin) {
|
|
97
|
+
process.stdout.write('\n Tip: Run `orch doctor` to verify your setup.\n\n');
|
|
98
|
+
}
|
|
91
99
|
}
|
|
92
100
|
catch (err) {
|
|
93
101
|
if (err instanceof errors_1.CliError) {
|
package/dist/commands/logs.js
CHANGED
|
@@ -16,15 +16,25 @@ const output_1 = require("../lib/output");
|
|
|
16
16
|
async function resolveWorkspaceId(config, slug) {
|
|
17
17
|
const configFile = await (0, config_1.loadConfig)();
|
|
18
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
19
|
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
if (targetSlug) {
|
|
21
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
22
|
+
if (!workspace) {
|
|
23
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
|
|
24
|
+
}
|
|
25
|
+
return workspace.id;
|
|
26
|
+
}
|
|
27
|
+
// No workspace specified — auto-select if user has exactly one
|
|
28
|
+
if (response.workspaces.length === 0) {
|
|
29
|
+
throw new errors_1.CliError('No workspaces found. Create one with `orch workspace create <name>`.');
|
|
30
|
+
}
|
|
31
|
+
if (response.workspaces.length === 1) {
|
|
32
|
+
return response.workspaces[0].id;
|
|
26
33
|
}
|
|
27
|
-
|
|
34
|
+
// Multiple workspaces — list them and ask the user to pick
|
|
35
|
+
const slugs = response.workspaces.map((w) => w.slug).join(', ');
|
|
36
|
+
throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
|
|
37
|
+
'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
|
|
28
38
|
}
|
|
29
39
|
function formatDate(iso) {
|
|
30
40
|
if (!iso)
|
package/dist/commands/metrics.js
CHANGED
|
@@ -15,15 +15,24 @@ const output_1 = require("../lib/output");
|
|
|
15
15
|
async function resolveWorkspaceId(config, slug) {
|
|
16
16
|
const configFile = await (0, config_1.loadConfig)();
|
|
17
17
|
const targetSlug = slug ?? configFile.workspace;
|
|
18
|
-
if (!targetSlug) {
|
|
19
|
-
throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
|
|
20
|
-
}
|
|
21
18
|
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
if (targetSlug) {
|
|
20
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
21
|
+
if (!workspace) {
|
|
22
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
|
|
23
|
+
}
|
|
24
|
+
return workspace.id;
|
|
25
|
+
}
|
|
26
|
+
// No workspace specified — auto-select if user has exactly one
|
|
27
|
+
if (response.workspaces.length === 0) {
|
|
28
|
+
throw new errors_1.CliError('No workspaces found. Create one with `orch workspace create <name>`.');
|
|
29
|
+
}
|
|
30
|
+
if (response.workspaces.length === 1) {
|
|
31
|
+
return response.workspaces[0].id;
|
|
25
32
|
}
|
|
26
|
-
|
|
33
|
+
const slugs = response.workspaces.map((w) => w.slug).join(', ');
|
|
34
|
+
throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
|
|
35
|
+
'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
|
|
27
36
|
}
|
|
28
37
|
function formatDuration(ms) {
|
|
29
38
|
if (ms === 0)
|
package/dist/commands/replay.js
CHANGED
|
@@ -15,15 +15,24 @@ const output_1 = require("../lib/output");
|
|
|
15
15
|
async function resolveWorkspaceId(config, slug) {
|
|
16
16
|
const configFile = await (0, config_1.loadConfig)();
|
|
17
17
|
const targetSlug = slug ?? configFile.workspace;
|
|
18
|
-
if (!targetSlug) {
|
|
19
|
-
throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
|
|
20
|
-
}
|
|
21
18
|
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
if (targetSlug) {
|
|
20
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
21
|
+
if (!workspace) {
|
|
22
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
|
|
23
|
+
}
|
|
24
|
+
return workspace.id;
|
|
25
|
+
}
|
|
26
|
+
// No workspace specified — auto-select if user has exactly one
|
|
27
|
+
if (response.workspaces.length === 0) {
|
|
28
|
+
throw new errors_1.CliError('No workspaces found. Create one with `orch workspace create <name>`.');
|
|
29
|
+
}
|
|
30
|
+
if (response.workspaces.length === 1) {
|
|
31
|
+
return response.workspaces[0].id;
|
|
25
32
|
}
|
|
26
|
-
|
|
33
|
+
const slugs = response.workspaces.map((w) => w.slug).join(', ');
|
|
34
|
+
throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
|
|
35
|
+
'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
|
|
27
36
|
}
|
|
28
37
|
function isUuid(value) {
|
|
29
38
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
package/dist/commands/run.js
CHANGED
|
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.localCommandForEntrypoint = localCommandForEntrypoint;
|
|
40
40
|
exports.validateInputSchema = validateInputSchema;
|
|
41
|
+
exports.tryParseJsonObject = tryParseJsonObject;
|
|
41
42
|
exports.isKeyedFileArg = isKeyedFileArg;
|
|
42
43
|
exports.readKeyedFiles = readKeyedFiles;
|
|
43
44
|
exports.mountDirectory = mountDirectory;
|
|
@@ -264,6 +265,26 @@ async function readStdin() {
|
|
|
264
265
|
return null;
|
|
265
266
|
return Buffer.concat(chunks);
|
|
266
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Try to parse a Buffer as a JSON object (not array).
|
|
270
|
+
* Returns the parsed object on success, null on failure or if the content
|
|
271
|
+
* is not a JSON object (e.g. array, string, number).
|
|
272
|
+
*/
|
|
273
|
+
function tryParseJsonObject(buf) {
|
|
274
|
+
const text = buf.toString('utf8').trim();
|
|
275
|
+
if (!text.startsWith('{'))
|
|
276
|
+
return null;
|
|
277
|
+
try {
|
|
278
|
+
const parsed = JSON.parse(text);
|
|
279
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
280
|
+
return parsed;
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
267
288
|
async function buildMultipartBody(filePaths, metadata) {
|
|
268
289
|
if (!filePaths || filePaths.length === 0) {
|
|
269
290
|
const stdinData = await readStdin();
|
|
@@ -2127,13 +2148,39 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2127
2148
|
sourceLabel = multipart.sourceLabel;
|
|
2128
2149
|
}
|
|
2129
2150
|
else if (llmCredentials) {
|
|
2130
|
-
|
|
2151
|
+
// Check for piped JSON stdin to merge with credentials
|
|
2152
|
+
const stdinData = await readStdin();
|
|
2153
|
+
const stdinJson = stdinData ? tryParseJsonObject(stdinData) : null;
|
|
2154
|
+
if (stdinJson) {
|
|
2155
|
+
stdinJson.llm_credentials = llmCredentials;
|
|
2156
|
+
warnInputSchemaErrors(stdinJson, agentMeta.input_schema);
|
|
2157
|
+
body = JSON.stringify(stdinJson);
|
|
2158
|
+
sourceLabel = 'stdin';
|
|
2159
|
+
}
|
|
2160
|
+
else {
|
|
2161
|
+
body = JSON.stringify({ llm_credentials: llmCredentials });
|
|
2162
|
+
}
|
|
2131
2163
|
headers['Content-Type'] = 'application/json';
|
|
2132
2164
|
}
|
|
2133
2165
|
else {
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2166
|
+
// No --data, no --file, no --metadata — check for piped JSON stdin
|
|
2167
|
+
const stdinData = await readStdin();
|
|
2168
|
+
if (stdinData) {
|
|
2169
|
+
const stdinJson = tryParseJsonObject(stdinData);
|
|
2170
|
+
if (stdinJson) {
|
|
2171
|
+
warnInputSchemaErrors(stdinJson, agentMeta.input_schema);
|
|
2172
|
+
body = JSON.stringify(stdinJson);
|
|
2173
|
+
headers['Content-Type'] = 'application/json';
|
|
2174
|
+
sourceLabel = 'stdin';
|
|
2175
|
+
}
|
|
2176
|
+
else {
|
|
2177
|
+
// Non-JSON stdin — send as binary file attachment
|
|
2178
|
+
const form = new FormData();
|
|
2179
|
+
form.append('files[]', new Blob([new Uint8Array(stdinData)]), 'stdin');
|
|
2180
|
+
body = form;
|
|
2181
|
+
sourceLabel = 'stdin';
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2137
2184
|
}
|
|
2138
2185
|
} // end of non-injection path
|
|
2139
2186
|
const verboseQs = options.verbose ? '?verbose=true' : '';
|
|
@@ -19,15 +19,24 @@ const api_2 = require("../lib/api");
|
|
|
19
19
|
async function resolveWorkspaceId(config, slug) {
|
|
20
20
|
const configFile = await (0, config_1.loadConfig)();
|
|
21
21
|
const targetSlug = slug ?? configFile.workspace;
|
|
22
|
-
if (!targetSlug) {
|
|
23
|
-
throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
|
|
24
|
-
}
|
|
25
22
|
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
if (targetSlug) {
|
|
24
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
25
|
+
if (!workspace) {
|
|
26
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
|
|
27
|
+
}
|
|
28
|
+
return workspace.id;
|
|
29
|
+
}
|
|
30
|
+
// No workspace specified — auto-select if user has exactly one
|
|
31
|
+
if (response.workspaces.length === 0) {
|
|
32
|
+
throw new errors_1.CliError('No workspaces found. Create one with `orch workspace create <name>`.');
|
|
29
33
|
}
|
|
30
|
-
|
|
34
|
+
if (response.workspaces.length === 1) {
|
|
35
|
+
return response.workspaces[0].id;
|
|
36
|
+
}
|
|
37
|
+
const slugs = response.workspaces.map((w) => w.slug).join(', ');
|
|
38
|
+
throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
|
|
39
|
+
'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
|
|
31
40
|
}
|
|
32
41
|
function formatDate(iso) {
|
|
33
42
|
if (!iso)
|
|
@@ -148,6 +157,8 @@ function registerScheduleCommand(program) {
|
|
|
148
157
|
.option('--input <json>', 'Input data as JSON string')
|
|
149
158
|
.option('--provider <provider>', 'LLM provider (anthropic, openai, gemini)')
|
|
150
159
|
.option('--pin-version', 'Pin to this version (disable auto-update on publish)')
|
|
160
|
+
.option('--alert-webhook <url>', 'Webhook URL to POST on failure (HTTPS required)')
|
|
161
|
+
.option('--alert-on-failure-count <n>', 'Number of consecutive failures before alerting (default: 3)', parseInt)
|
|
151
162
|
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
152
163
|
.action(async (agentArg, options) => {
|
|
153
164
|
const config = await (0, config_1.getResolvedConfig)();
|
|
@@ -162,8 +173,13 @@ function registerScheduleCommand(program) {
|
|
|
162
173
|
}
|
|
163
174
|
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
164
175
|
const ref = (0, agent_ref_1.parseAgentRef)(agentArg);
|
|
176
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
177
|
+
const org = ref.org ?? configFile.workspace ?? config.defaultOrg;
|
|
178
|
+
if (!org) {
|
|
179
|
+
throw new errors_1.CliError('Missing org. Use org/agent format or set default org.');
|
|
180
|
+
}
|
|
165
181
|
// Resolve agent to get the ID (pass workspace context for private agents)
|
|
166
|
-
const agent = await (0, api_2.getAgentWithFallback)(config,
|
|
182
|
+
const agent = await (0, api_2.getAgentWithFallback)(config, org, ref.agent, ref.version, workspaceId);
|
|
167
183
|
// Parse input data
|
|
168
184
|
let inputData;
|
|
169
185
|
if (options.input) {
|
|
@@ -190,6 +206,10 @@ function registerScheduleCommand(program) {
|
|
|
190
206
|
body.llm_provider = options.provider;
|
|
191
207
|
if (options.pinVersion)
|
|
192
208
|
body.auto_update = false;
|
|
209
|
+
if (options.alertWebhook)
|
|
210
|
+
body.alert_webhook_url = options.alertWebhook;
|
|
211
|
+
if (options.alertOnFailureCount)
|
|
212
|
+
body.alert_on_failure_count = options.alertOnFailureCount;
|
|
193
213
|
const result = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/schedules`, {
|
|
194
214
|
body: JSON.stringify(body),
|
|
195
215
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -217,6 +237,10 @@ function registerScheduleCommand(program) {
|
|
|
217
237
|
if (s.llm_provider) {
|
|
218
238
|
process.stdout.write(` Provider: ${s.llm_provider}\n`);
|
|
219
239
|
}
|
|
240
|
+
if (s.alert_webhook_url) {
|
|
241
|
+
process.stdout.write(` Alert URL: ${s.alert_webhook_url}\n`);
|
|
242
|
+
process.stdout.write(` Alert after: ${s.alert_on_failure_count ?? 3} consecutive failures\n`);
|
|
243
|
+
}
|
|
220
244
|
process.stdout.write('\n');
|
|
221
245
|
});
|
|
222
246
|
// orch schedule update <schedule-id>
|
|
@@ -231,6 +255,9 @@ function registerScheduleCommand(program) {
|
|
|
231
255
|
.option('--disable', 'Disable the schedule')
|
|
232
256
|
.option('--auto-update', 'Enable auto-update on publish')
|
|
233
257
|
.option('--pin-version', 'Pin to current version (disable auto-update)')
|
|
258
|
+
.option('--alert-webhook <url>', 'Webhook URL to POST on failure (HTTPS required)')
|
|
259
|
+
.option('--alert-on-failure-count <n>', 'Number of consecutive failures before alerting', parseInt)
|
|
260
|
+
.option('--clear-alert-webhook', 'Remove the alert webhook URL')
|
|
234
261
|
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
235
262
|
.action(async (scheduleId, options) => {
|
|
236
263
|
const config = await (0, config_1.getResolvedConfig)();
|
|
@@ -243,6 +270,9 @@ function registerScheduleCommand(program) {
|
|
|
243
270
|
if (options.autoUpdate && options.pinVersion) {
|
|
244
271
|
throw new errors_1.CliError('Cannot use both --auto-update and --pin-version');
|
|
245
272
|
}
|
|
273
|
+
if (options.alertWebhook && options.clearAlertWebhook) {
|
|
274
|
+
throw new errors_1.CliError('Cannot use both --alert-webhook and --clear-alert-webhook');
|
|
275
|
+
}
|
|
246
276
|
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
247
277
|
const updates = {};
|
|
248
278
|
if (options.cron)
|
|
@@ -259,6 +289,12 @@ function registerScheduleCommand(program) {
|
|
|
259
289
|
updates.auto_update = true;
|
|
260
290
|
if (options.pinVersion)
|
|
261
291
|
updates.auto_update = false;
|
|
292
|
+
if (options.alertWebhook)
|
|
293
|
+
updates.alert_webhook_url = options.alertWebhook;
|
|
294
|
+
if (options.alertOnFailureCount)
|
|
295
|
+
updates.alert_on_failure_count = options.alertOnFailureCount;
|
|
296
|
+
if (options.clearAlertWebhook)
|
|
297
|
+
updates.alert_webhook_url = '';
|
|
262
298
|
if (options.input) {
|
|
263
299
|
try {
|
|
264
300
|
updates.input_data = JSON.parse(options.input);
|
|
@@ -285,6 +321,13 @@ function registerScheduleCommand(program) {
|
|
|
285
321
|
if (s.next_run_at) {
|
|
286
322
|
process.stdout.write(` Next: ${formatDate(s.next_run_at)}\n`);
|
|
287
323
|
}
|
|
324
|
+
if (s.alert_webhook_url) {
|
|
325
|
+
process.stdout.write(` Alert: ${s.alert_webhook_url}\n`);
|
|
326
|
+
process.stdout.write(` After: ${s.alert_on_failure_count ?? 3} failures\n`);
|
|
327
|
+
}
|
|
328
|
+
else if (options.clearAlertWebhook) {
|
|
329
|
+
process.stdout.write(` Alert: ${chalk_1.default.gray('removed')}\n`);
|
|
330
|
+
}
|
|
288
331
|
process.stdout.write('\n');
|
|
289
332
|
});
|
|
290
333
|
// orch schedule delete <schedule-id>
|
|
@@ -498,4 +541,25 @@ function registerScheduleCommand(program) {
|
|
|
498
541
|
process.stdout.write(`${table.toString()}\n`);
|
|
499
542
|
process.stdout.write(chalk_1.default.gray(`\n${result.total} run${result.total !== 1 ? 's' : ''} total\n`));
|
|
500
543
|
});
|
|
544
|
+
// orch schedule test-alert <schedule-id>
|
|
545
|
+
schedule
|
|
546
|
+
.command('test-alert <schedule-id>')
|
|
547
|
+
.description('Send a test alert to the schedule\'s configured webhook URL')
|
|
548
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
549
|
+
.action(async (partialScheduleId, options) => {
|
|
550
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
551
|
+
if (!config.apiKey) {
|
|
552
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
553
|
+
}
|
|
554
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
555
|
+
const scheduleId = await resolveScheduleId(config, partialScheduleId, workspaceId);
|
|
556
|
+
process.stdout.write('Sending test alert...\n');
|
|
557
|
+
const result = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/schedules/${scheduleId}/test-alert`);
|
|
558
|
+
if (result.success) {
|
|
559
|
+
process.stdout.write(chalk_1.default.green('\u2713') + ' Test alert delivered successfully\n');
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
process.stdout.write(chalk_1.default.red('\u2717') + ' Test alert delivery failed\n');
|
|
563
|
+
}
|
|
564
|
+
});
|
|
501
565
|
}
|
package/dist/commands/secrets.js
CHANGED
|
@@ -17,15 +17,24 @@ const SECRET_NAME_REGEX = /^[A-Z][A-Z0-9_]*$/;
|
|
|
17
17
|
async function resolveWorkspaceId(config, slug) {
|
|
18
18
|
const configFile = await (0, config_1.loadConfig)();
|
|
19
19
|
const targetSlug = slug ?? configFile.workspace;
|
|
20
|
-
if (!targetSlug) {
|
|
21
|
-
throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
|
|
22
|
-
}
|
|
23
20
|
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
if (targetSlug) {
|
|
22
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
23
|
+
if (!workspace) {
|
|
24
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
|
|
25
|
+
}
|
|
26
|
+
return workspace.id;
|
|
27
|
+
}
|
|
28
|
+
// No workspace specified — auto-select if user has exactly one
|
|
29
|
+
if (response.workspaces.length === 0) {
|
|
30
|
+
throw new errors_1.CliError('No workspaces found. Create one with `orch workspace create <name>`.');
|
|
31
|
+
}
|
|
32
|
+
if (response.workspaces.length === 1) {
|
|
33
|
+
return response.workspaces[0].id;
|
|
27
34
|
}
|
|
28
|
-
|
|
35
|
+
const slugs = response.workspaces.map((w) => w.slug).join(', ');
|
|
36
|
+
throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
|
|
37
|
+
'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
|
|
29
38
|
}
|
|
30
39
|
function formatDate(iso) {
|
|
31
40
|
if (!iso)
|
package/dist/commands/service.js
CHANGED
|
@@ -17,15 +17,24 @@ const spinner_1 = require("../lib/spinner");
|
|
|
17
17
|
async function resolveWorkspaceId(config, slug) {
|
|
18
18
|
const configFile = await (0, config_1.loadConfig)();
|
|
19
19
|
const targetSlug = slug ?? configFile.workspace;
|
|
20
|
-
if (!targetSlug) {
|
|
21
|
-
throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
|
|
22
|
-
}
|
|
23
20
|
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
if (targetSlug) {
|
|
22
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
23
|
+
if (!workspace) {
|
|
24
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
|
|
25
|
+
}
|
|
26
|
+
return workspace.id;
|
|
27
|
+
}
|
|
28
|
+
// No workspace specified — auto-select if user has exactly one
|
|
29
|
+
if (response.workspaces.length === 0) {
|
|
30
|
+
throw new errors_1.CliError('No workspaces found. Create one with `orch workspace create <name>`.');
|
|
31
|
+
}
|
|
32
|
+
if (response.workspaces.length === 1) {
|
|
33
|
+
return response.workspaces[0].id;
|
|
27
34
|
}
|
|
28
|
-
|
|
35
|
+
const slugs = response.workspaces.map((w) => w.slug).join(', ');
|
|
36
|
+
throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
|
|
37
|
+
'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
|
|
29
38
|
}
|
|
30
39
|
function formatDate(iso) {
|
|
31
40
|
if (!iso)
|