@orchagent/cli 0.3.86 → 0.3.87
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/agent-keys.js +21 -7
- package/dist/commands/agents.js +60 -5
- package/dist/commands/config.js +4 -0
- package/dist/commands/delete.js +2 -2
- package/dist/commands/dev.js +226 -0
- package/dist/commands/diff.js +418 -0
- package/dist/commands/estimate.js +105 -0
- package/dist/commands/fork.js +11 -1
- package/dist/commands/health.js +226 -0
- package/dist/commands/index.js +8 -0
- package/dist/commands/info.js +75 -0
- package/dist/commands/init.js +729 -38
- package/dist/commands/publish.js +237 -21
- package/dist/commands/run.js +272 -28
- package/dist/commands/schedule.js +11 -6
- package/dist/commands/test.js +68 -1
- package/dist/lib/api.js +29 -4
- package/dist/lib/batch-publish.js +223 -0
- package/dist/lib/dev-server.js +425 -0
- package/dist/lib/doctor/checks/environment.js +1 -1
- package/dist/lib/key-store.js +121 -0
- package/dist/lib/spinner.js +50 -0
- package/dist/lib/test-mock-runner.js +334 -0
- package/dist/lib/update-notifier.js +1 -1
- package/package.json +1 -1
- package/src/resources/__pycache__/agent_runner.cpython-311.pyc +0 -0
- package/src/resources/__pycache__/agent_runner.cpython-312.pyc +0 -0
- package/src/resources/__pycache__/test_agent_runner_mocks.cpython-311-pytest-9.0.2.pyc +0 -0
- package/src/resources/__pycache__/test_agent_runner_mocks.cpython-312-pytest-8.4.2.pyc +0 -0
- package/src/resources/agent_runner.py +29 -2
- package/src/resources/test_agent_runner_mocks.py +290 -0
|
@@ -8,6 +8,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
8
8
|
const config_1 = require("../lib/config");
|
|
9
9
|
const api_1 = require("../lib/api");
|
|
10
10
|
const errors_1 = require("../lib/errors");
|
|
11
|
+
const key_store_1 = require("../lib/key-store");
|
|
11
12
|
/**
|
|
12
13
|
* Resolve an agent reference ("org/agent" or just "agent") to an agent ID.
|
|
13
14
|
* Uses the authenticated list-agents endpoint and finds the latest version.
|
|
@@ -27,7 +28,7 @@ async function resolveAgentId(config, ref) {
|
|
|
27
28
|
}
|
|
28
29
|
// Use the latest version
|
|
29
30
|
const latest = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
|
30
|
-
return { agent: latest, agentId: latest.id };
|
|
31
|
+
return { agent: latest, agentId: latest.id, orgSlug: latest.org_slug ?? resolvedOrg ?? '' };
|
|
31
32
|
}
|
|
32
33
|
function registerAgentKeysCommand(program) {
|
|
33
34
|
const agentKeys = program
|
|
@@ -41,22 +42,29 @@ function registerAgentKeysCommand(program) {
|
|
|
41
42
|
if (!config.apiKey) {
|
|
42
43
|
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
43
44
|
}
|
|
44
|
-
const { agent, agentId } = await resolveAgentId(config, ref);
|
|
45
|
+
const { agent, agentId, orgSlug } = await resolveAgentId(config, ref);
|
|
45
46
|
const result = await (0, api_1.listAgentKeys)(config, agentId);
|
|
47
|
+
// Load locally-saved keys for this agent
|
|
48
|
+
const localKeys = await (0, key_store_1.loadServiceKeys)(orgSlug, agent.name);
|
|
49
|
+
const localPrefixes = new Set(localKeys.map(k => k.prefix));
|
|
46
50
|
if (result.keys.length === 0) {
|
|
47
51
|
process.stdout.write(`No service keys for ${agent.name}.\n`);
|
|
48
52
|
process.stdout.write(`\nCreate one with: orchagent agent-keys create ${ref}\n`);
|
|
49
53
|
return;
|
|
50
54
|
}
|
|
51
55
|
process.stdout.write(`Service keys for ${agent.name}:\n\n`);
|
|
52
|
-
process.stdout.write(` ${'ID'.padEnd(38)} ${'PREFIX'.padEnd(14)} ${'CREATED'.padEnd(22)} LAST USED\n`);
|
|
53
|
-
process.stdout.write(` ${'─'.repeat(38)} ${'─'.repeat(14)} ${'─'.repeat(22)} ${'─'.repeat(22)}\n`);
|
|
56
|
+
process.stdout.write(` ${'ID'.padEnd(38)} ${'PREFIX'.padEnd(14)} ${'CREATED'.padEnd(22)} ${'LAST USED'.padEnd(22)} SAVED\n`);
|
|
57
|
+
process.stdout.write(` ${'─'.repeat(38)} ${'─'.repeat(14)} ${'─'.repeat(22)} ${'─'.repeat(22)} ${'─'.repeat(5)}\n`);
|
|
54
58
|
for (const key of result.keys) {
|
|
55
59
|
const created = new Date(key.created_at).toLocaleDateString();
|
|
56
60
|
const lastUsed = key.last_used_at
|
|
57
61
|
? new Date(key.last_used_at).toLocaleDateString()
|
|
58
62
|
: chalk_1.default.gray('never');
|
|
59
|
-
|
|
63
|
+
const saved = localPrefixes.has(key.prefix) ? chalk_1.default.green('yes') : chalk_1.default.gray('no');
|
|
64
|
+
process.stdout.write(` ${key.id.padEnd(38)} ${key.prefix.padEnd(14)} ${created.padEnd(22)} ${String(lastUsed).padEnd(22)} ${saved}\n`);
|
|
65
|
+
}
|
|
66
|
+
if (localKeys.length > 0) {
|
|
67
|
+
process.stdout.write(`\n ${chalk_1.default.gray('Keys marked "yes" can be retrieved from ~/.orchagent/keys/')}\n`);
|
|
60
68
|
}
|
|
61
69
|
process.stdout.write('\n');
|
|
62
70
|
});
|
|
@@ -68,11 +76,17 @@ function registerAgentKeysCommand(program) {
|
|
|
68
76
|
if (!config.apiKey) {
|
|
69
77
|
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
70
78
|
}
|
|
71
|
-
const { agent } = await resolveAgentId(config, ref);
|
|
79
|
+
const { agent, orgSlug } = await resolveAgentId(config, ref);
|
|
72
80
|
const result = await (0, api_1.createAgentKey)(config, agent.id);
|
|
73
81
|
process.stdout.write(`\nNew service key for ${agent.name}:\n\n`);
|
|
74
82
|
process.stdout.write(` ${result.key}\n\n`);
|
|
75
|
-
|
|
83
|
+
try {
|
|
84
|
+
const savedPath = await (0, key_store_1.saveServiceKey)(orgSlug, agent.name, agent.version, result.key, result.prefix);
|
|
85
|
+
process.stdout.write(` ${chalk_1.default.gray(`Saved to ${savedPath}`)}\n`);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
process.stderr.write(chalk_1.default.yellow('Could not save key locally. Copy it now — it cannot be retrieved from the server.\n'));
|
|
89
|
+
}
|
|
76
90
|
});
|
|
77
91
|
agentKeys
|
|
78
92
|
.command('delete <agent> <key-id>')
|
package/dist/commands/agents.js
CHANGED
|
@@ -36,16 +36,42 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.latestOnly = latestOnly;
|
|
39
40
|
exports.registerAgentsCommand = registerAgentsCommand;
|
|
40
41
|
const chalk_1 = __importDefault(require("chalk"));
|
|
41
42
|
const config_1 = require("../lib/config");
|
|
42
43
|
const api_1 = require("../lib/api");
|
|
43
44
|
const output_1 = require("../lib/output");
|
|
45
|
+
/**
|
|
46
|
+
* Given a list of agents, return only the latest version of each agent name.
|
|
47
|
+
* "Latest" = highest created_at timestamp (most recently published).
|
|
48
|
+
* Also returns the total version count per agent name for display.
|
|
49
|
+
*/
|
|
50
|
+
function latestOnly(agents) {
|
|
51
|
+
const byName = new Map();
|
|
52
|
+
for (const agent of agents) {
|
|
53
|
+
const existing = byName.get(agent.name) ?? [];
|
|
54
|
+
existing.push(agent);
|
|
55
|
+
byName.set(agent.name, existing);
|
|
56
|
+
}
|
|
57
|
+
const result = [];
|
|
58
|
+
const versionCounts = new Map();
|
|
59
|
+
for (const [name, versions] of byName) {
|
|
60
|
+
versionCounts.set(name, versions.length);
|
|
61
|
+
// Sort by created_at descending, take the first (latest)
|
|
62
|
+
versions.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
63
|
+
result.push(versions[0]);
|
|
64
|
+
}
|
|
65
|
+
// Sort final list alphabetically by name for stable output
|
|
66
|
+
result.sort((a, b) => a.name.localeCompare(b.name));
|
|
67
|
+
return { agents: result, versionCounts };
|
|
68
|
+
}
|
|
44
69
|
function registerAgentsCommand(program) {
|
|
45
70
|
program
|
|
46
71
|
.command('agents')
|
|
47
72
|
.description('List your published agents')
|
|
48
73
|
.option('--filter <text>', 'Filter by name')
|
|
74
|
+
.option('--all-versions', 'Show all versions (default: latest only)')
|
|
49
75
|
.option('--json', 'Output raw JSON')
|
|
50
76
|
.action(async (options) => {
|
|
51
77
|
const config = await (0, config_1.getResolvedConfig)();
|
|
@@ -58,11 +84,22 @@ function registerAgentsCommand(program) {
|
|
|
58
84
|
const filteredAgents = options.filter
|
|
59
85
|
? agents.filter(a => a.name.toLowerCase().includes(options.filter.toLowerCase()))
|
|
60
86
|
: agents;
|
|
87
|
+
// Determine display set: latest-only (default) or all versions
|
|
88
|
+
let displayAgents;
|
|
89
|
+
let versionCounts;
|
|
90
|
+
if (options.allVersions) {
|
|
91
|
+
displayAgents = filteredAgents;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const grouped = latestOnly(filteredAgents);
|
|
95
|
+
displayAgents = grouped.agents;
|
|
96
|
+
versionCounts = grouped.versionCounts;
|
|
97
|
+
}
|
|
61
98
|
if (options.json) {
|
|
62
|
-
(0, output_1.printJson)(
|
|
99
|
+
(0, output_1.printJson)(displayAgents);
|
|
63
100
|
return;
|
|
64
101
|
}
|
|
65
|
-
if (
|
|
102
|
+
if (displayAgents.length === 0) {
|
|
66
103
|
process.stdout.write(options.filter
|
|
67
104
|
? `No agents found matching "${options.filter}"\n`
|
|
68
105
|
: 'No agents published yet.\n\nPublish an agent: orch publish\n');
|
|
@@ -77,18 +114,36 @@ function registerAgentsCommand(program) {
|
|
|
77
114
|
chalk_1.default.bold('Description'),
|
|
78
115
|
],
|
|
79
116
|
});
|
|
80
|
-
|
|
117
|
+
displayAgents.forEach((agent) => {
|
|
81
118
|
const name = agent.name;
|
|
82
|
-
const version = agent.version;
|
|
83
119
|
const type = agent.type || 'tool';
|
|
84
120
|
const desc = agent.description
|
|
85
121
|
? agent.description.length > 60
|
|
86
122
|
? agent.description.slice(0, 57) + '...'
|
|
87
123
|
: agent.description
|
|
88
124
|
: '-';
|
|
125
|
+
// In latest-only mode, show version count if > 1
|
|
126
|
+
let version = agent.version;
|
|
127
|
+
if (!options.allVersions && versionCounts) {
|
|
128
|
+
const count = versionCounts.get(agent.name) ?? 1;
|
|
129
|
+
if (count > 1) {
|
|
130
|
+
version = `${agent.version} (${count} total)`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
89
133
|
table.push([name, version, type, desc]);
|
|
90
134
|
});
|
|
91
135
|
process.stdout.write(`${table.toString()}\n`);
|
|
92
|
-
|
|
136
|
+
if (options.allVersions) {
|
|
137
|
+
process.stdout.write(`\nTotal: ${displayAgents.length} version${displayAgents.length === 1 ? '' : 's'}\n`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const totalVersions = filteredAgents.length;
|
|
141
|
+
const agentCount = displayAgents.length;
|
|
142
|
+
process.stdout.write(`\n${agentCount} agent${agentCount === 1 ? '' : 's'}`);
|
|
143
|
+
if (totalVersions > agentCount) {
|
|
144
|
+
process.stdout.write(` (${totalVersions} versions total, use --all-versions to show all)`);
|
|
145
|
+
}
|
|
146
|
+
process.stdout.write('\n');
|
|
147
|
+
}
|
|
93
148
|
});
|
|
94
149
|
}
|
package/dist/commands/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setConfigValue = setConfigValue;
|
|
3
4
|
exports.registerConfigCommand = registerConfigCommand;
|
|
4
5
|
const config_1 = require("../lib/config");
|
|
5
6
|
const errors_1 = require("../lib/errors");
|
|
@@ -18,6 +19,9 @@ async function setConfigValue(key, value) {
|
|
|
18
19
|
if (!isValidKey(key)) {
|
|
19
20
|
throw new errors_1.CliError(`Unknown config key: ${key}. Supported keys: ${SUPPORTED_KEYS.join(', ')}`);
|
|
20
21
|
}
|
|
22
|
+
if (!value || !value.trim()) {
|
|
23
|
+
throw new errors_1.CliError(`Cannot set ${key} to an empty value. To clear this setting, use: orch config unset ${key}`);
|
|
24
|
+
}
|
|
21
25
|
if (key === 'default-format') {
|
|
22
26
|
const formats = value.split(',').map((f) => f.trim()).filter(Boolean);
|
|
23
27
|
// Validate format IDs against union of skill formats and agent adapters
|
package/dist/commands/delete.js
CHANGED
|
@@ -64,7 +64,7 @@ Examples:
|
|
|
64
64
|
selectedAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
|
65
65
|
}
|
|
66
66
|
// Check if confirmation is required
|
|
67
|
-
const deleteCheck = await (0, api_1.checkAgentDelete)(config, selectedAgent.id);
|
|
67
|
+
const deleteCheck = await (0, api_1.checkAgentDelete)(config, selectedAgent.id, workspaceId);
|
|
68
68
|
// Show agent info
|
|
69
69
|
process.stdout.write(`\n${chalk_1.default.bold('Agent:')} ${selectedAgent.name}@${selectedAgent.version}\n`);
|
|
70
70
|
process.stdout.write('\n');
|
|
@@ -97,7 +97,7 @@ Examples:
|
|
|
97
97
|
// Perform deletion
|
|
98
98
|
process.stdout.write('Deleting agent...\n');
|
|
99
99
|
const confirmationName = deleteCheck.requires_confirmation ? selectedAgent.name : undefined;
|
|
100
|
-
await (0, api_1.deleteAgent)(config, selectedAgent.id, confirmationName);
|
|
100
|
+
await (0, api_1.deleteAgent)(config, selectedAgent.id, confirmationName, workspaceId);
|
|
101
101
|
await (0, analytics_1.track)('cli_delete', { agent_name: selectedAgent.name, version: selectedAgent.version });
|
|
102
102
|
process.stdout.write(`✓ Deleted ${selectedAgent.name}@${selectedAgent.version}\n`);
|
|
103
103
|
process.stdout.write(chalk_1.default.gray('\nData will be retained for 30 days before permanent deletion.\n'));
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `orch dev` — local development server with hot-reload.
|
|
4
|
+
*
|
|
5
|
+
* Starts an HTTP server that accepts JSON input and runs the agent locally.
|
|
6
|
+
* Watches for file changes and reloads agent configuration automatically.
|
|
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.registerDevCommand = registerDevCommand;
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
15
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
16
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
17
|
+
const errors_1 = require("../lib/errors");
|
|
18
|
+
const dotenv_1 = require("../lib/dotenv");
|
|
19
|
+
const dev_server_1 = require("../lib/dev-server");
|
|
20
|
+
// ─── Console UI ─────────────────────────────────────────────────────────────
|
|
21
|
+
function printBanner(config, port) {
|
|
22
|
+
const name = config.manifest.name || 'unknown';
|
|
23
|
+
const version = config.manifest.version || 'local';
|
|
24
|
+
const engine = (0, dev_server_1.engineLabel)(config.engine);
|
|
25
|
+
process.stderr.write('\n');
|
|
26
|
+
process.stderr.write(chalk_1.default.cyan.bold(` orch dev`) + chalk_1.default.gray(` — local development server\n`));
|
|
27
|
+
process.stderr.write('\n');
|
|
28
|
+
process.stderr.write(` ${chalk_1.default.bold('Agent:')} ${name}@${version}\n`);
|
|
29
|
+
process.stderr.write(` ${chalk_1.default.bold('Engine:')} ${engine}`);
|
|
30
|
+
if (config.entrypoint) {
|
|
31
|
+
process.stderr.write(chalk_1.default.gray(` (${config.entrypoint})`));
|
|
32
|
+
}
|
|
33
|
+
process.stderr.write('\n');
|
|
34
|
+
process.stderr.write(` ${chalk_1.default.bold('Server:')} ${chalk_1.default.green(`http://localhost:${port}`)}\n`);
|
|
35
|
+
process.stderr.write('\n');
|
|
36
|
+
process.stderr.write(chalk_1.default.gray(` POST http://localhost:${port}/run Execute agent\n`));
|
|
37
|
+
process.stderr.write(chalk_1.default.gray(` GET http://localhost:${port}/health Agent info\n`));
|
|
38
|
+
process.stderr.write('\n');
|
|
39
|
+
process.stderr.write(chalk_1.default.gray(' Watching for file changes... (Ctrl+C to stop)\n'));
|
|
40
|
+
process.stderr.write(chalk_1.default.gray(' ─'.repeat(32)) + '\n');
|
|
41
|
+
}
|
|
42
|
+
function printRequestLog(log) {
|
|
43
|
+
const status = log.statusCode < 400
|
|
44
|
+
? chalk_1.default.green(`${log.statusCode}`)
|
|
45
|
+
: chalk_1.default.red(`${log.statusCode}`);
|
|
46
|
+
const duration = chalk_1.default.gray(`${log.durationMs}ms`);
|
|
47
|
+
const id = chalk_1.default.gray(`#${log.id}`);
|
|
48
|
+
if (log.error) {
|
|
49
|
+
process.stderr.write(` ${id} ${log.method} ${log.path} ${status} ${duration}\n` +
|
|
50
|
+
chalk_1.default.red(` ${log.error.split('\n')[0].slice(0, 120)}\n`));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
process.stderr.write(` ${id} ${log.method} ${log.path} ${status} ${duration}\n`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function printReload(reason) {
|
|
57
|
+
const time = new Date().toLocaleTimeString();
|
|
58
|
+
process.stderr.write(chalk_1.default.cyan(`\n [${time}] ${reason}\n`));
|
|
59
|
+
}
|
|
60
|
+
function printReloadError(error) {
|
|
61
|
+
process.stderr.write(chalk_1.default.red(` Reload failed: ${error}\n`));
|
|
62
|
+
process.stderr.write(chalk_1.default.yellow(` Server still running with previous configuration.\n`));
|
|
63
|
+
}
|
|
64
|
+
function setupWatcher(agentDir, state, verbose) {
|
|
65
|
+
const watcher = chokidar_1.default.watch(agentDir, {
|
|
66
|
+
ignored: /(node_modules|__pycache__|\.git|dist|build|\.venv|venv|\.next|target)/,
|
|
67
|
+
persistent: true,
|
|
68
|
+
ignoreInitial: true,
|
|
69
|
+
});
|
|
70
|
+
const reloadConfig = async (filePath) => {
|
|
71
|
+
const relPath = path_1.default.relative(agentDir, filePath);
|
|
72
|
+
printReload(`Changed: ${relPath}`);
|
|
73
|
+
try {
|
|
74
|
+
const newConfig = await (0, dev_server_1.loadAgentConfig)(agentDir);
|
|
75
|
+
state.config = newConfig;
|
|
76
|
+
const engine = (0, dev_server_1.engineLabel)(newConfig.engine);
|
|
77
|
+
const ep = newConfig.entrypoint ? `, ${newConfig.entrypoint}` : '';
|
|
78
|
+
process.stderr.write(chalk_1.default.green(` Reloaded`) + chalk_1.default.gray(` (${engine}${ep})\n`));
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
82
|
+
printReloadError(message);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const onChange = (filePath) => {
|
|
86
|
+
if (state.debounceTimer)
|
|
87
|
+
clearTimeout(state.debounceTimer);
|
|
88
|
+
state.debounceTimer = setTimeout(() => reloadConfig(filePath), 300);
|
|
89
|
+
};
|
|
90
|
+
watcher
|
|
91
|
+
.on('change', onChange)
|
|
92
|
+
.on('add', onChange)
|
|
93
|
+
.on('unlink', onChange)
|
|
94
|
+
.on('error', (error) => {
|
|
95
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
96
|
+
process.stderr.write(chalk_1.default.red(` Watcher error: ${message}\n`));
|
|
97
|
+
});
|
|
98
|
+
return watcher;
|
|
99
|
+
}
|
|
100
|
+
// ─── Command registration ───────────────────────────────────────────────────
|
|
101
|
+
function registerDevCommand(program) {
|
|
102
|
+
program
|
|
103
|
+
.command('dev [path]')
|
|
104
|
+
.description('Start a local development server with hot-reload')
|
|
105
|
+
.option('-p, --port <port>', 'Server port', '4900')
|
|
106
|
+
.option('-v, --verbose', 'Show detailed execution output')
|
|
107
|
+
.option('--no-watch', 'Disable file watching')
|
|
108
|
+
.addHelpText('after', `
|
|
109
|
+
Examples:
|
|
110
|
+
orch dev Start dev server in current directory
|
|
111
|
+
orch dev ./my-agent Start dev server for agent in specified directory
|
|
112
|
+
orch dev --port 3001 Use custom port
|
|
113
|
+
orch dev --verbose Show detailed execution output
|
|
114
|
+
orch dev --no-watch Disable file watching (no hot-reload)
|
|
115
|
+
|
|
116
|
+
Then send requests:
|
|
117
|
+
curl -X POST http://localhost:4900/run \\
|
|
118
|
+
-H "Content-Type: application/json" \\
|
|
119
|
+
-d '{"task": "hello world"}'
|
|
120
|
+
`)
|
|
121
|
+
.action(async (dirPath, options) => {
|
|
122
|
+
const agentDir = path_1.default.resolve(dirPath || '.');
|
|
123
|
+
const port = parseInt(options.port, 10);
|
|
124
|
+
const verbose = options.verbose ?? false;
|
|
125
|
+
const watchEnabled = options.watch !== false;
|
|
126
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
127
|
+
throw new errors_1.CliError('Port must be a number between 1 and 65535');
|
|
128
|
+
}
|
|
129
|
+
// Verify directory exists
|
|
130
|
+
try {
|
|
131
|
+
const stat = await promises_1.default.stat(agentDir);
|
|
132
|
+
if (!stat.isDirectory()) {
|
|
133
|
+
throw new errors_1.CliError(`Not a directory: ${agentDir}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (err instanceof errors_1.CliError)
|
|
138
|
+
throw err;
|
|
139
|
+
throw new errors_1.CliError(`Directory not found: ${agentDir}`);
|
|
140
|
+
}
|
|
141
|
+
// Verify orchagent.json exists
|
|
142
|
+
try {
|
|
143
|
+
await promises_1.default.access(path_1.default.join(agentDir, 'orchagent.json'));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new errors_1.CliError(`No orchagent.json found in ${agentDir}\n\n` +
|
|
147
|
+
`To start a dev server, the directory must contain orchagent.json.\n` +
|
|
148
|
+
`Create one with: orch init`);
|
|
149
|
+
}
|
|
150
|
+
// Load .env
|
|
151
|
+
const dotEnvVars = await (0, dotenv_1.loadDotEnv)(agentDir);
|
|
152
|
+
const dotEnvCount = Object.keys(dotEnvVars).length;
|
|
153
|
+
if (dotEnvCount > 0) {
|
|
154
|
+
for (const [key, value] of Object.entries(dotEnvVars)) {
|
|
155
|
+
if (!(key in process.env) || process.env[key] === undefined) {
|
|
156
|
+
process.env[key] = value;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Initial config load
|
|
161
|
+
let initialConfig;
|
|
162
|
+
try {
|
|
163
|
+
initialConfig = await (0, dev_server_1.loadAgentConfig)(agentDir);
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
167
|
+
throw new errors_1.CliError(`Failed to load agent configuration: ${message}`);
|
|
168
|
+
}
|
|
169
|
+
const type = (initialConfig.manifest.type || 'agent').toLowerCase();
|
|
170
|
+
if (type === 'skill') {
|
|
171
|
+
throw new errors_1.CliError('Skills cannot be served as a dev server.\n' +
|
|
172
|
+
'Skills are instructions meant to be injected into AI agent contexts.');
|
|
173
|
+
}
|
|
174
|
+
// Set up state
|
|
175
|
+
const state = {
|
|
176
|
+
config: initialConfig,
|
|
177
|
+
debounceTimer: null,
|
|
178
|
+
};
|
|
179
|
+
// Create server
|
|
180
|
+
const { server, close } = (0, dev_server_1.createDevServer)(port, verbose, () => state.config, {
|
|
181
|
+
onRequest: printRequestLog,
|
|
182
|
+
onError: verbose ? (err) => {
|
|
183
|
+
process.stderr.write(chalk_1.default.red(` Detail: ${err.message}\n`));
|
|
184
|
+
} : undefined,
|
|
185
|
+
});
|
|
186
|
+
// Start server
|
|
187
|
+
await new Promise((resolve, reject) => {
|
|
188
|
+
server.on('error', (err) => {
|
|
189
|
+
if (err.code === 'EADDRINUSE') {
|
|
190
|
+
reject(new errors_1.CliError(`Port ${port} is already in use.\n\n` +
|
|
191
|
+
`Try a different port: orch dev --port ${port + 1}`));
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
reject(new errors_1.CliError(`Server error: ${err.message}`));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
server.listen(port, () => resolve());
|
|
198
|
+
});
|
|
199
|
+
// Print banner
|
|
200
|
+
printBanner(initialConfig, port);
|
|
201
|
+
if (dotEnvCount > 0) {
|
|
202
|
+
process.stderr.write(chalk_1.default.gray(` Loaded ${dotEnvCount} variable${dotEnvCount === 1 ? '' : 's'} from .env\n`));
|
|
203
|
+
}
|
|
204
|
+
// Set up file watcher
|
|
205
|
+
let watcher = null;
|
|
206
|
+
if (watchEnabled) {
|
|
207
|
+
watcher = setupWatcher(agentDir, state, verbose);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
process.stderr.write(chalk_1.default.gray(` File watching disabled\n`));
|
|
211
|
+
}
|
|
212
|
+
// Handle shutdown
|
|
213
|
+
const shutdown = async () => {
|
|
214
|
+
process.stderr.write(chalk_1.default.gray('\n Shutting down...\n'));
|
|
215
|
+
if (watcher) {
|
|
216
|
+
await watcher.close();
|
|
217
|
+
}
|
|
218
|
+
await close();
|
|
219
|
+
process.exit(0);
|
|
220
|
+
};
|
|
221
|
+
process.on('SIGINT', shutdown);
|
|
222
|
+
process.on('SIGTERM', shutdown);
|
|
223
|
+
// Keep process alive
|
|
224
|
+
await new Promise(() => { });
|
|
225
|
+
});
|
|
226
|
+
}
|