@cef-ai/cli 0.2.7 → 0.3.0

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.
Files changed (127) hide show
  1. package/README.md +75 -1247
  2. package/dist/build/duration.d.ts +22 -0
  3. package/dist/build/duration.d.ts.map +1 -0
  4. package/dist/build/duration.js +44 -0
  5. package/dist/build/duration.js.map +1 -0
  6. package/dist/build/esbuild-plugin.d.ts +32 -0
  7. package/dist/build/esbuild-plugin.d.ts.map +1 -0
  8. package/dist/build/esbuild-plugin.js +53 -0
  9. package/dist/build/esbuild-plugin.js.map +1 -0
  10. package/dist/build/lints.d.ts +55 -0
  11. package/dist/build/lints.d.ts.map +1 -0
  12. package/dist/build/lints.js +214 -0
  13. package/dist/build/lints.js.map +1 -0
  14. package/dist/build/load-config.d.ts +114 -0
  15. package/dist/build/load-config.d.ts.map +1 -0
  16. package/dist/build/load-config.js +150 -0
  17. package/dist/build/load-config.js.map +1 -0
  18. package/dist/build/manifest.d.ts +156 -0
  19. package/dist/build/manifest.d.ts.map +1 -0
  20. package/dist/build/manifest.js +111 -0
  21. package/dist/build/manifest.js.map +1 -0
  22. package/dist/build/targeting.d.ts +15 -0
  23. package/dist/build/targeting.d.ts.map +1 -0
  24. package/dist/build/targeting.js +69 -0
  25. package/dist/build/targeting.js.map +1 -0
  26. package/dist/cli.d.ts +2 -2
  27. package/dist/cli.d.ts.map +1 -0
  28. package/dist/cli.js +107 -62
  29. package/dist/cli.js.map +1 -0
  30. package/dist/commands/build.d.ts +41 -0
  31. package/dist/commands/build.d.ts.map +1 -0
  32. package/dist/commands/build.js +252 -0
  33. package/dist/commands/build.js.map +1 -0
  34. package/dist/commands/inspect.d.ts +49 -0
  35. package/dist/commands/inspect.d.ts.map +1 -0
  36. package/dist/commands/inspect.js +135 -0
  37. package/dist/commands/inspect.js.map +1 -0
  38. package/dist/commands/typegen.d.ts +36 -0
  39. package/dist/commands/typegen.d.ts.map +1 -0
  40. package/dist/commands/typegen.js +210 -0
  41. package/dist/commands/typegen.js.map +1 -0
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +6 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/lib.d.ts +12 -0
  47. package/dist/lib.d.ts.map +1 -0
  48. package/dist/lib.js +9 -0
  49. package/dist/lib.js.map +1 -0
  50. package/dist/typegen/augment.d.ts +57 -0
  51. package/dist/typegen/augment.d.ts.map +1 -0
  52. package/dist/typegen/augment.js +87 -0
  53. package/dist/typegen/augment.js.map +1 -0
  54. package/dist/typegen/fetch.d.ts +30 -0
  55. package/dist/typegen/fetch.d.ts.map +1 -0
  56. package/dist/typegen/fetch.js +38 -0
  57. package/dist/typegen/fetch.js.map +1 -0
  58. package/dist/typegen/lockfile.d.ts +42 -0
  59. package/dist/typegen/lockfile.d.ts.map +1 -0
  60. package/dist/typegen/lockfile.js +58 -0
  61. package/dist/typegen/lockfile.js.map +1 -0
  62. package/dist/typegen/schema-to-ts.d.ts +21 -0
  63. package/dist/typegen/schema-to-ts.d.ts.map +1 -0
  64. package/dist/typegen/schema-to-ts.js +29 -0
  65. package/dist/typegen/schema-to-ts.js.map +1 -0
  66. package/package.json +20 -58
  67. package/dist/commands/auth/index.d.ts +0 -2
  68. package/dist/commands/auth/index.js +0 -162
  69. package/dist/commands/deploy/clone.d.ts +0 -13
  70. package/dist/commands/deploy/clone.js +0 -143
  71. package/dist/commands/deploy/delete.d.ts +0 -14
  72. package/dist/commands/deploy/delete.js +0 -134
  73. package/dist/commands/deploy/deploy.d.ts +0 -24
  74. package/dist/commands/deploy/deploy.js +0 -654
  75. package/dist/commands/deploy/index.d.ts +0 -2
  76. package/dist/commands/deploy/index.js +0 -216
  77. package/dist/commands/deploy/status.d.ts +0 -14
  78. package/dist/commands/deploy/status.js +0 -61
  79. package/dist/commands/dev/index.d.ts +0 -2
  80. package/dist/commands/dev/index.js +0 -134
  81. package/dist/commands/dev/server.d.ts +0 -14
  82. package/dist/commands/dev/server.js +0 -195
  83. package/dist/commands/dev/static/app.js +0 -633
  84. package/dist/commands/dev/static/index.html +0 -205
  85. package/dist/commands/dev/static/static/app.js +0 -633
  86. package/dist/commands/dev/static/static/index.html +0 -205
  87. package/dist/commands/dev/static/static/style.css +0 -184
  88. package/dist/commands/dev/static/style.css +0 -184
  89. package/dist/commands/playground/index.d.ts +0 -2
  90. package/dist/commands/playground/index.js +0 -75
  91. package/dist/commands/playground/test.d.ts +0 -15
  92. package/dist/commands/playground/test.js +0 -166
  93. package/dist/commands/service/index.d.ts +0 -2
  94. package/dist/commands/service/index.js +0 -89
  95. package/dist/lib/api.d.ts +0 -146
  96. package/dist/lib/api.js +0 -330
  97. package/dist/lib/config.d.ts +0 -12
  98. package/dist/lib/config.js +0 -145
  99. package/dist/lib/credentials.d.ts +0 -19
  100. package/dist/lib/credentials.js +0 -68
  101. package/dist/lib/runtime/agent-router.d.ts +0 -12
  102. package/dist/lib/runtime/agent-router.js +0 -67
  103. package/dist/lib/runtime/context-builder.d.ts +0 -27
  104. package/dist/lib/runtime/context-builder.js +0 -166
  105. package/dist/lib/runtime/cubby-manager.d.ts +0 -20
  106. package/dist/lib/runtime/cubby-manager.js +0 -162
  107. package/dist/lib/runtime/index.d.ts +0 -51
  108. package/dist/lib/runtime/index.js +0 -181
  109. package/dist/lib/runtime/log-collector.d.ts +0 -17
  110. package/dist/lib/runtime/log-collector.js +0 -61
  111. package/dist/lib/runtime/stream-manager.d.ts +0 -10
  112. package/dist/lib/runtime/stream-manager.js +0 -77
  113. package/dist/lib/runtime/types.d.ts +0 -76
  114. package/dist/lib/runtime/types.js +0 -2
  115. package/dist/lib/transpile.d.ts +0 -1
  116. package/dist/lib/transpile.js +0 -9
  117. package/dist/lib/typecheck.d.ts +0 -31
  118. package/dist/lib/typecheck.js +0 -175
  119. package/dist/lib/types.d.ts +0 -246
  120. package/dist/lib/types.js +0 -2
  121. package/dist/lib/utils.d.ts +0 -4
  122. package/dist/lib/utils.js +0 -33
  123. package/example/.env.example +0 -6
  124. package/example/agents/object-detection/tasks/yolo.ts +0 -28
  125. package/example/agents/parking-violation-detector/tasks/detect.ts +0 -326
  126. package/example/cef.config.yaml +0 -91
  127. package/example/engagements/engagement.ts +0 -902
@@ -1,216 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { parse } from 'yaml';
4
- import dotenv from 'dotenv';
5
- import { loadConfig, loadEnv } from '../../lib/config.js';
6
- import { runDeploy, writeIdsBack } from './deploy.js';
7
- import { runStatus } from './status.js';
8
- import { runClone } from './clone.js';
9
- import { runDelete } from './delete.js';
10
- import { errorMessage } from '../../lib/utils.js';
11
- const DEPLOY_ENTITY_TYPES = ['engagement', 'agent', 'cubby', 'workspace', 'stream', 'deployment', 'raft'];
12
- function loadEnvFromDir(dir) {
13
- const resolved = path.resolve(dir);
14
- dotenv.config({ path: path.join(resolved, '.env') });
15
- return loadEnv();
16
- }
17
- function printDeploySummary(results) {
18
- const created = results.filter(r => r.action === 'created').length;
19
- const updated = results.filter(r => r.action === 'updated').length;
20
- const dryRun = results.filter(r => r.action === 'dry-run').length;
21
- if (dryRun > 0) {
22
- console.log(`\nDry-run complete. ${dryRun} operation(s) planned.`);
23
- for (const r of results) {
24
- console.log(` [dry-run] ${r.entity}: ${r.name}`);
25
- }
26
- return;
27
- }
28
- console.log(`\nDeploy complete. ${created} created, ${updated} updated.`);
29
- for (const r of results) {
30
- const label = r.action === 'created' ? 'created' : 'updated';
31
- console.log(` [${label}] ${r.entity}: ${r.name}${r.id ? ` (${r.id})` : ''}`);
32
- }
33
- }
34
- function statusIcon(status) {
35
- if (status === 'up-to-date')
36
- return 'ok';
37
- return status.toUpperCase();
38
- }
39
- function printStatusSummary(results) {
40
- console.log('');
41
- for (const r of results) {
42
- console.log(` [${statusIcon(r.status)}] ${r.entity}: ${r.name}${r.id ? ` (${r.id})` : ''}`);
43
- if (r.tasks) {
44
- for (const t of r.tasks) {
45
- console.log(` task: ${t.name} [${statusIcon(t.status)}]`);
46
- }
47
- }
48
- }
49
- const outdated = results.filter(r => r.status === 'outdated').length;
50
- const notDeployed = results.filter(r => r.status === 'not-deployed').length;
51
- console.log(`\n${outdated} outdated, ${notDeployed} not deployed.`);
52
- }
53
- function printCloneSummary(results) {
54
- const written = results.filter(r => r.action === 'written').length;
55
- const skipped = results.filter(r => r.action === 'skipped').length;
56
- console.log(`\nClone complete. ${written} written, ${skipped} skipped.`);
57
- for (const r of results) {
58
- console.log(` [${r.action}] ${r.entity}: ${r.name}${r.file ? ` → ${r.file}` : ''}`);
59
- }
60
- }
61
- function printDeleteSummary(results) {
62
- const deleted = results.filter(r => r.action === 'deleted').length;
63
- const failed = results.filter(r => r.action === 'failed').length;
64
- const skipped = results.filter(r => r.action === 'skipped').length;
65
- console.log(`\nDelete complete. ${deleted} deleted, ${failed} failed, ${skipped} skipped.`);
66
- for (const r of results) {
67
- const suffix = r.error ? ` (${r.error})` : '';
68
- console.log(` [${r.action}] ${r.entity}: ${r.name} (${r.id})${suffix}`);
69
- }
70
- }
71
- async function handleDeploy(opts) {
72
- let resolved;
73
- try {
74
- resolved = loadConfig(opts.config);
75
- }
76
- catch (err) {
77
- console.error(`Config error: ${errorMessage(err)}`);
78
- process.exit(1);
79
- }
80
- if (opts.dryRun) {
81
- console.log(`Dry-run mode. Config: ${resolved.configPath}`);
82
- console.log(`Agent service: ${resolved.config.agentServicePubKey}`);
83
- }
84
- else {
85
- console.log(`Deploying to: ${resolved.env.orchestratorUrl}`);
86
- console.log(`Agent service: ${resolved.config.agentServicePubKey}`);
87
- if (opts.only)
88
- console.log(`Scope: --only ${opts.only}`);
89
- }
90
- try {
91
- const results = await runDeploy(resolved, { dryRun: opts.dryRun, only: opts.only });
92
- printDeploySummary(results);
93
- }
94
- catch (err) {
95
- if (!opts.dryRun) {
96
- try {
97
- writeIdsBack(resolved.configPath, resolved.config);
98
- }
99
- catch (writeErr) {
100
- console.error(`Warning: failed to save partial IDs: ${errorMessage(writeErr)}`);
101
- }
102
- }
103
- console.error(`Deploy failed: ${errorMessage(err)}`);
104
- process.exit(1);
105
- }
106
- }
107
- async function handleStatus(opts) {
108
- let resolved;
109
- try {
110
- resolved = loadConfig(opts.config);
111
- }
112
- catch (err) {
113
- console.error(`Config error: ${errorMessage(err)}`);
114
- process.exit(1);
115
- }
116
- console.log(`Checking status: ${resolved.env.orchestratorUrl}`);
117
- console.log(`Agent service: ${resolved.config.agentServicePubKey}`);
118
- try {
119
- const results = await runStatus(resolved);
120
- printStatusSummary(results);
121
- }
122
- catch (err) {
123
- console.error(`Status failed: ${errorMessage(err)}`);
124
- process.exit(1);
125
- }
126
- }
127
- async function handleClone(opts) {
128
- const dir = opts.outputDir ?? path.dirname(path.resolve(opts.config));
129
- const resolvedOutputDir = path.resolve(dir);
130
- try {
131
- const env = loadEnvFromDir(resolvedOutputDir);
132
- let agentServicePubKey = opts.pubkey;
133
- if (!agentServicePubKey) {
134
- const absConfigPath = path.resolve(opts.config);
135
- if (fs.existsSync(absConfigPath)) {
136
- const raw = fs.readFileSync(absConfigPath, 'utf8');
137
- const parsed = parse(raw);
138
- agentServicePubKey = parsed?.agentServicePubKey;
139
- }
140
- }
141
- if (!agentServicePubKey) {
142
- console.error('No agentServicePubKey found. Provide --pubkey or ensure cef.config.yaml exists.');
143
- process.exit(1);
144
- }
145
- console.log(`Cloning agent service: ${agentServicePubKey}`);
146
- console.log(`Output: ${resolvedOutputDir}`);
147
- const results = await runClone(env, {
148
- pubkey: agentServicePubKey,
149
- outputDir: resolvedOutputDir,
150
- force: opts.force ?? false,
151
- });
152
- printCloneSummary(results);
153
- }
154
- catch (err) {
155
- console.error(`Clone failed: ${errorMessage(err)}`);
156
- process.exit(1);
157
- }
158
- }
159
- async function handleDelete(opts) {
160
- let resolved;
161
- try {
162
- resolved = loadConfig(opts.config);
163
- }
164
- catch (err) {
165
- console.error(`Config error: ${errorMessage(err)}`);
166
- process.exit(1);
167
- }
168
- console.log(`Deleting from: ${resolved.env.orchestratorUrl}`);
169
- console.log(`Agent service: ${resolved.config.agentServicePubKey}`);
170
- if (opts.only)
171
- console.log(`Scope: --only ${opts.only}`);
172
- try {
173
- const results = await runDelete(resolved, { only: opts.only, force: opts.force });
174
- printDeleteSummary(results);
175
- }
176
- catch (err) {
177
- console.error(`Delete failed: ${errorMessage(err)}`);
178
- process.exit(1);
179
- }
180
- }
181
- export function registerDeployCommands(program) {
182
- const types = DEPLOY_ENTITY_TYPES.join(', ');
183
- const deploy = program
184
- .command('deploy', { isDefault: true })
185
- .description('Sync cef.config.yaml to the orchestrator')
186
- .option('--config <path>', 'Config file path', './cef.config.yaml')
187
- .option('--dry-run', 'Show planned operations without calling API')
188
- .option('--only <type>', `Sync only one entity type (${types})`)
189
- .action(async (opts) => {
190
- if (opts.only && !DEPLOY_ENTITY_TYPES.includes(opts.only)) {
191
- console.error(`Invalid --only value: "${opts.only}". Must be one of: ${types}`);
192
- process.exit(1);
193
- }
194
- await handleDeploy(opts);
195
- });
196
- deploy
197
- .command('status')
198
- .description('Compare local config with deployed state')
199
- .option('--config <path>', 'Config file path', './cef.config.yaml')
200
- .action(handleStatus);
201
- deploy
202
- .command('clone')
203
- .description('Download an existing agent service')
204
- .option('--config <path>', 'Config file path', './cef.config.yaml')
205
- .option('--pubkey <key>', 'Agent service public key (64-char hex)')
206
- .option('--output-dir <dir>', 'Directory to write files into')
207
- .option('--force', 'Overwrite existing files')
208
- .action(handleClone);
209
- deploy
210
- .command('delete')
211
- .description('Delete deployed entities in reverse dependency order')
212
- .option('--config <path>', 'Config file path', './cef.config.yaml')
213
- .option('--only <type>', `Delete only one entity type (${types})`)
214
- .option('--force', 'Skip confirmation prompt')
215
- .action(handleDelete);
216
- }
@@ -1,14 +0,0 @@
1
- import type { ResolvedConfig } from '../../lib/types.js';
2
- export type StatusValue = 'up-to-date' | 'outdated' | 'not-deployed' | 'not-found';
3
- export interface TaskStatus {
4
- name: string;
5
- status: StatusValue;
6
- }
7
- export interface StatusResult {
8
- entity: 'engagement' | 'agent';
9
- name: string;
10
- id?: string;
11
- status: StatusValue;
12
- tasks?: TaskStatus[];
13
- }
14
- export declare function runStatus(resolved: ResolvedConfig): Promise<StatusResult[]>;
@@ -1,61 +0,0 @@
1
- import { createApiClient } from '../../lib/api.js';
2
- export async function runStatus(resolved) {
3
- const { config, env } = resolved;
4
- const api = createApiClient(env, config.agentServicePubKey);
5
- const results = [];
6
- const [remoteEngagementsRes, remoteAgentsRes] = await Promise.all([
7
- api.getEngagements(),
8
- api.getAgents(),
9
- ]);
10
- const remoteEngById = new Map(remoteEngagementsRes.data.map(e => [e.engagementId, e]));
11
- // Includes concierge agents (isConcierge: true) which hold engagement tsCode
12
- const remoteAgentById = new Map(remoteAgentsRes.data.map(a => [a.agentId, a]));
13
- for (const eng of config.engagements ?? []) {
14
- const localCode = eng.tsCode ?? '';
15
- if (!eng.engagementId) {
16
- results.push({ entity: 'engagement', name: eng.name, status: 'not-deployed' });
17
- continue;
18
- }
19
- const remoteEng = remoteEngById.get(eng.engagementId);
20
- if (!remoteEng) {
21
- results.push({ entity: 'engagement', name: eng.name, id: eng.engagementId, status: 'not-found' });
22
- continue;
23
- }
24
- // Resolve tsCode via concierge agent's first task
25
- const conciergeAgent = remoteAgentById.get(remoteEng.conciergeAgentId);
26
- const remoteCode = conciergeAgent?.tasks[0]?.tsCode ?? '';
27
- results.push({
28
- entity: 'engagement',
29
- name: eng.name,
30
- id: eng.engagementId,
31
- status: localCode === remoteCode ? 'up-to-date' : 'outdated',
32
- });
33
- }
34
- for (const agent of config.agents ?? []) {
35
- if (!agent.agentId) {
36
- results.push({ entity: 'agent', name: agent.name, status: 'not-deployed' });
37
- continue;
38
- }
39
- const remote = remoteAgentById.get(agent.agentId);
40
- if (!remote) {
41
- results.push({ entity: 'agent', name: agent.name, id: agent.agentId, status: 'not-found' });
42
- continue;
43
- }
44
- const remoteTaskByName = new Map(remote.tasks.map(t => [t.name, t]));
45
- const taskStatuses = agent.tasks.map(task => {
46
- const localCode = task.tsCode ?? '';
47
- const remoteTask = remoteTaskByName.get(task.name);
48
- if (!remoteTask)
49
- return { name: task.name, status: 'not-found' };
50
- return {
51
- name: task.name,
52
- status: localCode === remoteTask.tsCode ? 'up-to-date' : 'outdated',
53
- };
54
- });
55
- const agentStatus = taskStatuses.some(t => t.status === 'outdated' || t.status === 'not-found')
56
- ? 'outdated'
57
- : 'up-to-date';
58
- results.push({ entity: 'agent', name: agent.name, id: agent.agentId, status: agentStatus, tasks: taskStatuses });
59
- }
60
- return results;
61
- }
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function registerDevCommands(program: Command): void;
@@ -1,134 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import chalk from 'chalk';
4
- import { loadConfigForDev } from '../../lib/config.js';
5
- import { LocalRuntime } from '../../lib/runtime/index.js';
6
- import { createDevServer } from './server.js';
7
- export function registerDevCommands(program) {
8
- program
9
- .command('dev')
10
- .description('Start local development server for agent testing')
11
- .option('--config <path>', 'Config file path', './cef.config.yaml')
12
- .option('--port <port>', 'Server port', '8787')
13
- .option('--host <host>', 'Server host', 'localhost')
14
- .option('--persist', 'Keep .cef-dev/ data after server stops (default: cleared on shutdown)', false)
15
- .action(handleDev);
16
- }
17
- async function handleDev(opts) {
18
- const port = parseInt(opts.port, 10);
19
- const host = opts.host;
20
- // Load config (no auth required)
21
- let resolved;
22
- try {
23
- resolved = loadConfigForDev(opts.config);
24
- }
25
- catch (err) {
26
- console.error(chalk.red(`Failed to load config: ${err.message}`));
27
- process.exit(1);
28
- }
29
- const { config, configDir } = resolved;
30
- // Data directory for local state (cleared on shutdown unless --persist)
31
- const cefDevDir = path.join(configDir, '.cef-dev');
32
- const dataDir = path.join(cefDevDir, 'cubbies');
33
- fs.mkdirSync(dataDir, { recursive: true });
34
- // Initialize runtime
35
- const runtime = new LocalRuntime(dataDir);
36
- await runtime.initialize(config);
37
- // Start HTTP server
38
- const server = createDevServer(runtime, { port, host });
39
- try {
40
- await server.start();
41
- }
42
- catch (err) {
43
- console.error(chalk.red(err.message));
44
- runtime.shutdown();
45
- process.exit(1);
46
- }
47
- // Summary
48
- const topology = runtime.getTopology();
49
- console.log('');
50
- console.log(chalk.bold('CEF Dev Server'));
51
- console.log(chalk.gray('─'.repeat(40)));
52
- console.log(` Engagements: ${chalk.cyan(String(topology.engagements.length))}`);
53
- console.log(` Agents: ${chalk.cyan(String(topology.agents.length))}`);
54
- console.log(` Cubbies: ${chalk.cyan(String(topology.cubbies.length))}`);
55
- console.log('');
56
- console.log(` ${chalk.green(`http://${host}:${port}`)}`);
57
- console.log('');
58
- console.log(chalk.gray('Endpoints:'));
59
- console.log(chalk.gray(' POST /api/trigger Trigger engagement'));
60
- console.log(chalk.gray(' POST /api/agents/:alias/:task Call agent task'));
61
- console.log(chalk.gray(' POST /api/streams/:id/push Push stream packet'));
62
- console.log(chalk.gray(' POST /api/cubbies/:alias/query SQL query'));
63
- console.log(chalk.gray(' GET /api/topology View topology'));
64
- console.log(chalk.gray(' WS /ws Real-time logs'));
65
- console.log('');
66
- console.log(chalk.gray('Watching for file changes. Press Ctrl+C to stop.'));
67
- console.log('');
68
- // Stream runtime logs to console
69
- runtime.logEmitter.on('log', (entry) => {
70
- const prefix = entry.agentAlias
71
- ? chalk.blue(`[${entry.agentAlias}${entry.taskAlias ? '.' + entry.taskAlias : ''}]`)
72
- : chalk.gray('[engagement]');
73
- const level = entry.level === 'error' ? chalk.red(entry.level)
74
- : entry.level === 'warn' ? chalk.yellow(entry.level)
75
- : chalk.gray(entry.level);
76
- const args = entry.args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
77
- console.log(`${chalk.gray(new Date(entry.timestamp).toISOString().slice(11, 23))} ${level} ${prefix} ${args}`);
78
- });
79
- // File watcher
80
- const watchedFiles = new Map();
81
- for (const agent of config.agents ?? []) {
82
- for (const task of agent.tasks) {
83
- if (task._resolvedFilePath) {
84
- watchedFiles.set(task._resolvedFilePath, `${agent.alias}/${task.alias}`);
85
- }
86
- }
87
- }
88
- for (const eng of config.engagements ?? []) {
89
- if (eng._resolvedFilePath) {
90
- watchedFiles.set(eng._resolvedFilePath, `engagement:${eng.name}`);
91
- }
92
- }
93
- for (const [filePath, label] of watchedFiles) {
94
- try {
95
- fs.watch(filePath, () => {
96
- try {
97
- const tsCode = fs.readFileSync(filePath, 'utf8');
98
- const parts = label.split('/');
99
- if (parts.length === 2 && parts[0] && parts[1]) {
100
- runtime.reloadFile(parts[0], parts[1], tsCode);
101
- }
102
- console.log(chalk.yellow(`[reload] ${path.relative(configDir, filePath)}`));
103
- }
104
- catch (err) {
105
- console.error(chalk.red(`Failed to reload ${filePath}: ${err.message}`));
106
- }
107
- });
108
- }
109
- catch {
110
- // File watch not supported on all platforms for all paths
111
- }
112
- }
113
- // Graceful shutdown with hard timeout
114
- let shuttingDown = false;
115
- const shutdown = async () => {
116
- if (shuttingDown) {
117
- process.exit(0);
118
- return;
119
- }
120
- shuttingDown = true;
121
- console.log(chalk.gray('\nShutting down...'));
122
- runtime.shutdown();
123
- if (!opts.persist && fs.existsSync(cefDevDir)) {
124
- fs.rmSync(cefDevDir, { recursive: true, force: true });
125
- console.log(chalk.gray('Cleared .cef-dev/'));
126
- }
127
- const hardExit = setTimeout(() => process.exit(0), 1000);
128
- hardExit.unref();
129
- await server.stop();
130
- process.exit(0);
131
- };
132
- process.on('SIGINT', shutdown);
133
- process.on('SIGTERM', shutdown);
134
- }
@@ -1,14 +0,0 @@
1
- import http from 'http';
2
- import { type WebSocket } from 'ws';
3
- import type { LocalRuntime } from '../../lib/runtime/index.js';
4
- interface ServerOptions {
5
- port: number;
6
- host: string;
7
- }
8
- export declare function createDevServer(runtime: LocalRuntime, options: ServerOptions): {
9
- start: () => Promise<void>;
10
- stop: () => Promise<void>;
11
- server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
12
- wss: import("ws").Server<typeof WebSocket, typeof http.IncomingMessage>;
13
- };
14
- export {};
@@ -1,195 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import http from 'http';
5
- import { WebSocketServer } from 'ws';
6
- const STATIC_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), 'static');
7
- const MIME_TYPES = {
8
- '.html': 'text/html; charset=utf-8',
9
- '.css': 'text/css; charset=utf-8',
10
- '.js': 'application/javascript; charset=utf-8',
11
- };
12
- function loadStaticFiles() {
13
- const files = new Map();
14
- for (const name of fs.readdirSync(STATIC_DIR)) {
15
- const ext = path.extname(name);
16
- const contentType = MIME_TYPES[ext];
17
- if (!contentType)
18
- continue;
19
- const content = fs.readFileSync(path.join(STATIC_DIR, name));
20
- files.set('/' + name, { content, contentType });
21
- }
22
- // Alias "/" to "/index.html"
23
- const index = files.get('/index.html');
24
- if (index)
25
- files.set('/', index);
26
- return files;
27
- }
28
- export function createDevServer(runtime, options) {
29
- const staticFiles = loadStaticFiles();
30
- const wsClients = new Set();
31
- // Stream logs and trace events to WebSocket clients
32
- const broadcast = (msg) => {
33
- for (const ws of wsClients) {
34
- if (ws.readyState === ws.OPEN)
35
- ws.send(msg);
36
- }
37
- };
38
- runtime.logEmitter.on('log', (entry) => {
39
- broadcast(JSON.stringify({ type: 'log', data: entry }));
40
- });
41
- runtime.logEmitter.on('trace', (event) => {
42
- broadcast(JSON.stringify({ type: 'trace', data: event }));
43
- });
44
- const server = http.createServer(async (req, res) => {
45
- // CORS headers for dev
46
- res.setHeader('Access-Control-Allow-Origin', '*');
47
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
48
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
49
- if (req.method === 'OPTIONS') {
50
- res.writeHead(204);
51
- res.end();
52
- return;
53
- }
54
- const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
55
- const pathname = url.pathname;
56
- try {
57
- // ─── API Routes ───────────────────────────────────────────────
58
- if (req.method === 'GET' && pathname === '/api/topology') {
59
- json(res, runtime.getTopology());
60
- return;
61
- }
62
- if (req.method === 'GET' && pathname === '/api/executions') {
63
- json(res, runtime.getLogs());
64
- return;
65
- }
66
- if (req.method === 'POST' && pathname === '/api/trigger') {
67
- const body = await readBody(req);
68
- const { engagement, payload } = body;
69
- if (!engagement) {
70
- json(res, { error: 'Missing "engagement" field' }, 400);
71
- return;
72
- }
73
- const handle = await runtime.startEngagement(engagement, payload ?? {});
74
- json(res, { executionId: handle.executionId, started: true });
75
- return;
76
- }
77
- {
78
- let m;
79
- if (req.method === 'POST' && (m = pathname.match(/^\/api\/streams\/([^/]+)\/push$/))) {
80
- const streamId = m[1];
81
- const body = await readBody(req);
82
- const packet = body.data ?? body;
83
- // Ensure payload is Uint8Array
84
- if (typeof packet.payload === 'string') {
85
- packet.payload = new TextEncoder().encode(packet.payload);
86
- }
87
- else if (Array.isArray(packet.payload)) {
88
- packet.payload = new Uint8Array(packet.payload);
89
- }
90
- else if (packet.payload && !ArrayBuffer.isView(packet.payload)) {
91
- packet.payload = new TextEncoder().encode(JSON.stringify(packet.payload));
92
- }
93
- if (!packet.sequenceNum)
94
- packet.sequenceNum = 0;
95
- runtime.pushToStream(streamId, packet);
96
- json(res, { pushed: true });
97
- return;
98
- }
99
- if (req.method === 'POST' && (m = pathname.match(/^\/api\/agents\/([^/]+)\/([^/]+)$/))) {
100
- const body = await readBody(req);
101
- const result = await runtime.callAgentTask(m[1], m[2], body.payload ?? body);
102
- json(res, result);
103
- return;
104
- }
105
- if (req.method === 'POST' && (m = pathname.match(/^\/api\/cubbies\/([^/]+)\/query$/))) {
106
- const body = await readBody(req);
107
- const result = await runtime.queryCubby(m[1], body.instanceId ?? 'default', body.sql, body.params ?? []);
108
- json(res, result);
109
- return;
110
- }
111
- if (req.method === 'POST' && (m = pathname.match(/^\/api\/cubbies\/([^/]+)\/exec$/))) {
112
- const body = await readBody(req);
113
- const result = await runtime.execCubby(m[1], body.instanceId ?? 'default', body.sql, body.params ?? []);
114
- json(res, result);
115
- return;
116
- }
117
- if (req.method === 'GET' && (m = pathname.match(/^\/api\/cubbies\/([^/]+)\/instances$/))) {
118
- json(res, { instances: runtime.listCubbyInstances(m[1]) });
119
- return;
120
- }
121
- }
122
- // ─── Static files (Dev UI) ──────────────────────────────────
123
- if (req.method === 'GET') {
124
- const file = staticFiles.get(pathname);
125
- if (file) {
126
- res.writeHead(200, { 'Content-Type': file.contentType });
127
- res.end(file.content);
128
- return;
129
- }
130
- }
131
- // ─── 404 ────────────────────────────────────────────────────
132
- json(res, { error: 'Not found' }, 404);
133
- }
134
- catch (err) {
135
- json(res, { error: err.message }, 500);
136
- }
137
- });
138
- // Track open sockets so we can destroy them on shutdown
139
- const sockets = new Set();
140
- server.on('connection', (socket) => {
141
- sockets.add(socket);
142
- socket.on('close', () => sockets.delete(socket));
143
- });
144
- // WebSocket server
145
- const wss = new WebSocketServer({ server });
146
- wss.on('connection', (ws) => {
147
- wsClients.add(ws);
148
- ws.on('close', () => wsClients.delete(ws));
149
- });
150
- return {
151
- start: () => new Promise((resolve, reject) => {
152
- server.on('error', (err) => {
153
- if (err.code === 'EADDRINUSE') {
154
- reject(new Error(`Port ${options.port} is already in use. Stop the other process or use --port <number>.`));
155
- }
156
- else {
157
- reject(err);
158
- }
159
- });
160
- server.listen(options.port, options.host, () => resolve());
161
- }),
162
- stop: () => new Promise((resolve) => {
163
- for (const ws of wsClients)
164
- ws.terminate();
165
- wsClients.clear();
166
- for (const socket of sockets)
167
- socket.destroy();
168
- sockets.clear();
169
- wss.close();
170
- server.close(() => resolve());
171
- }),
172
- server,
173
- wss,
174
- };
175
- }
176
- function json(res, data, status = 200) {
177
- res.writeHead(status, { 'Content-Type': 'application/json' });
178
- res.end(JSON.stringify(data));
179
- }
180
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
181
- function readBody(req) {
182
- return new Promise((resolve, reject) => {
183
- let body = '';
184
- req.on('data', chunk => { body += chunk; });
185
- req.on('end', () => {
186
- try {
187
- resolve(body ? JSON.parse(body) : {});
188
- }
189
- catch {
190
- reject(new Error('Invalid JSON body'));
191
- }
192
- });
193
- req.on('error', reject);
194
- });
195
- }