@newsails/veil-cli 1.0.1

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 (199) hide show
  1. package/.veil/agents/analyst/AGENT.md +21 -0
  2. package/.veil/agents/analyst/agent.json +23 -0
  3. package/.veil/agents/assistant/AGENT.md +15 -0
  4. package/.veil/agents/assistant/agent.json +19 -0
  5. package/.veil/agents/coder/AGENT.md +18 -0
  6. package/.veil/agents/coder/agent.json +19 -0
  7. package/.veil/agents/hello/AGENT.md +5 -0
  8. package/.veil/agents/hello/agent.json +13 -0
  9. package/.veil/agents/writer/AGENT.md +12 -0
  10. package/.veil/agents/writer/agent.json +17 -0
  11. package/.veil/memory/MEMORY.md +343 -0
  12. package/.veil/memory/agents/analyst/MEMORY.md +55 -0
  13. package/.veil/memory/agents/hello/MEMORY.md +12 -0
  14. package/.veil/runtime.pid +1 -0
  15. package/.veil/settings.json +10 -0
  16. package/.veil-studio/studio.db +0 -0
  17. package/.veil-studio/studio.db-shm +0 -0
  18. package/.veil-studio/studio.db-wal +0 -0
  19. package/PLAN/01-vision.md +26 -0
  20. package/PLAN/02-tech-stack.md +94 -0
  21. package/PLAN/03-agents.md +232 -0
  22. package/PLAN/04-runtime.md +171 -0
  23. package/PLAN/05-tools.md +211 -0
  24. package/PLAN/06-communication.md +243 -0
  25. package/PLAN/07-storage.md +218 -0
  26. package/PLAN/08-api-cli.md +153 -0
  27. package/PLAN/09-permissions.md +108 -0
  28. package/PLAN/10-ably.md +105 -0
  29. package/PLAN/11-file-formats.md +442 -0
  30. package/PLAN/12-folder-structure.md +205 -0
  31. package/PLAN/13-operations.md +212 -0
  32. package/PLAN/README.md +23 -0
  33. package/README.md +128 -0
  34. package/REPORT.md +174 -0
  35. package/TODO.md +45 -0
  36. package/ai-tests/FRONTEND_PROMPT.md +220 -0
  37. package/ai-tests/Research & Planning.md +814 -0
  38. package/ai-tests/prompt-001-basic-api.md +230 -0
  39. package/ai-tests/prompt-002-basic-flows.md +230 -0
  40. package/ai-tests/prompt-003-agent-behaviors.md +220 -0
  41. package/api/middleware.js +60 -0
  42. package/api/routes/agents.js +193 -0
  43. package/api/routes/chat.js +93 -0
  44. package/api/routes/completions.js +122 -0
  45. package/api/routes/daemons.js +80 -0
  46. package/api/routes/memory.js +169 -0
  47. package/api/routes/models.js +40 -0
  48. package/api/routes/remote-methods.js +74 -0
  49. package/api/routes/sessions.js +208 -0
  50. package/api/routes/settings.js +108 -0
  51. package/api/routes/system.js +50 -0
  52. package/api/routes/tasks.js +270 -0
  53. package/api/server.js +120 -0
  54. package/cli/formatter.js +70 -0
  55. package/cli/index.js +443 -0
  56. package/cli/parser.js +113 -0
  57. package/config/config.json +10 -0
  58. package/config/models.json +6826 -0
  59. package/core/agent.js +329 -0
  60. package/core/cancel.js +38 -0
  61. package/core/compaction.js +176 -0
  62. package/core/events.js +13 -0
  63. package/core/loop.js +564 -0
  64. package/core/memory.js +51 -0
  65. package/core/prompt.js +185 -0
  66. package/core/queue.js +96 -0
  67. package/core/registry.js +291 -0
  68. package/core/remote-methods.js +124 -0
  69. package/core/router.js +386 -0
  70. package/core/running-sessions.js +18 -0
  71. package/docs/api/01-system.md +84 -0
  72. package/docs/api/02-agents.md +374 -0
  73. package/docs/api/03-chat.md +269 -0
  74. package/docs/api/04-tasks.md +470 -0
  75. package/docs/api/05-sessions.md +444 -0
  76. package/docs/api/06-daemons.md +142 -0
  77. package/docs/api/07-memory.md +186 -0
  78. package/docs/api/08-settings.md +133 -0
  79. package/docs/api/09-models.md +119 -0
  80. package/docs/api/09-websocket.md +350 -0
  81. package/docs/api/10-completions.md +134 -0
  82. package/docs/api/README.md +116 -0
  83. package/docs/guide/01-quickstart.md +220 -0
  84. package/docs/guide/02-folder-structure.md +185 -0
  85. package/docs/guide/03-configuration.md +252 -0
  86. package/docs/guide/04-agents.md +267 -0
  87. package/docs/guide/05-cli.md +290 -0
  88. package/docs/guide/06-tools.md +643 -0
  89. package/docs/guide/07-permissions.md +236 -0
  90. package/docs/guide/08-memory.md +139 -0
  91. package/docs/guide/09-multi-agent.md +271 -0
  92. package/docs/guide/10-daemons.md +226 -0
  93. package/docs/guide/README.md +53 -0
  94. package/docs/index.html +623 -0
  95. package/examples/README.md +151 -0
  96. package/examples/agents/assistant/AGENT.md +31 -0
  97. package/examples/agents/assistant/SOUL.md +9 -0
  98. package/examples/agents/assistant/agent.json +74 -0
  99. package/examples/agents/hello/AGENT.md +15 -0
  100. package/examples/agents/hello/agent.json +14 -0
  101. package/examples/agents/monitor/AGENT.md +51 -0
  102. package/examples/agents/monitor/agent.json +33 -0
  103. package/examples/agents/monitor/heartbeats/monitor.md +24 -0
  104. package/examples/agents/orchestrator/AGENT.md +70 -0
  105. package/examples/agents/orchestrator/agent.json +30 -0
  106. package/examples/agents/researcher/AGENT.md +52 -0
  107. package/examples/agents/researcher/agent.json +49 -0
  108. package/examples/agents/researcher/skills/web-research.md +28 -0
  109. package/examples/skills/code-review.md +72 -0
  110. package/examples/skills/summarise.md +59 -0
  111. package/examples/skills/web-research.md +42 -0
  112. package/examples/tools/word-count/index.js +27 -0
  113. package/examples/tools/word-count/tool.json +18 -0
  114. package/infrastructure/database.js +563 -0
  115. package/infrastructure/scheduler.js +122 -0
  116. package/llm/client.js +206 -0
  117. package/migrations/001-initial.sql +121 -0
  118. package/migrations/002-debuggability.sql +13 -0
  119. package/migrations/003-drop-orphaned-columns.sql +72 -0
  120. package/migrations/004-session-message-token-fields.sql +78 -0
  121. package/migrations/005-session-thinking.sql +5 -0
  122. package/package.json +30 -0
  123. package/schemas/agent.json +143 -0
  124. package/schemas/settings.json +111 -0
  125. package/scripts/fetch-models.js +93 -0
  126. package/session-debug-scenario.md +248 -0
  127. package/settings/fields.js +52 -0
  128. package/system-prompts/base-core.md +7 -0
  129. package/system-prompts/environment.md +13 -0
  130. package/system-prompts/reminders/anti-drift.md +6 -0
  131. package/system-prompts/reminders/stall-recovery.md +10 -0
  132. package/system-prompts/safety-rules.md +25 -0
  133. package/system-prompts/task-heuristics.md +27 -0
  134. package/test/client.js +71 -0
  135. package/test/integration/01-health.test.js +25 -0
  136. package/test/integration/02-agents.test.js +80 -0
  137. package/test/integration/03-chat-hello.test.js +48 -0
  138. package/test/integration/04-chat-multiturn.test.js +61 -0
  139. package/test/integration/05-chat-writer.test.js +48 -0
  140. package/test/integration/06-task-basic.test.js +68 -0
  141. package/test/integration/07-task-tools.test.js +74 -0
  142. package/test/integration/08-task-code-analysis.test.js +69 -0
  143. package/test/integration/09-memory-analyst.test.js +63 -0
  144. package/test/integration/10-task-advanced.test.js +85 -0
  145. package/test/integration/11-sessions-advanced.test.js +84 -0
  146. package/test/integration/12-assistant-chat-tools.test.js +75 -0
  147. package/test/integration/13-edge-cases.test.js +99 -0
  148. package/test/integration/14-cancel.test.js +62 -0
  149. package/test/integration/15-debug.test.js +106 -0
  150. package/test/integration/16-memory-api.test.js +83 -0
  151. package/test/integration/17-settings-api.test.js +41 -0
  152. package/test/integration/18-tool-search-activation.test.js +119 -0
  153. package/test/results/.gitkeep +0 -0
  154. package/test/runner.js +206 -0
  155. package/test/smoke.js +216 -0
  156. package/tools/agent_message.js +85 -0
  157. package/tools/agent_send.js +80 -0
  158. package/tools/agent_spawn.js +44 -0
  159. package/tools/bash.js +49 -0
  160. package/tools/edit_file.js +41 -0
  161. package/tools/glob.js +64 -0
  162. package/tools/grep.js +82 -0
  163. package/tools/list_dir.js +63 -0
  164. package/tools/log_write.js +31 -0
  165. package/tools/memory_read.js +38 -0
  166. package/tools/memory_search.js +65 -0
  167. package/tools/memory_write.js +42 -0
  168. package/tools/read_file.js +48 -0
  169. package/tools/sleep.js +22 -0
  170. package/tools/task_create.js +41 -0
  171. package/tools/task_respond.js +37 -0
  172. package/tools/task_spawn.js +64 -0
  173. package/tools/task_status.js +39 -0
  174. package/tools/task_subscribe.js +37 -0
  175. package/tools/todo_read.js +26 -0
  176. package/tools/todo_write.js +38 -0
  177. package/tools/tool_activate.js +24 -0
  178. package/tools/tool_search.js +24 -0
  179. package/tools/web_fetch.js +50 -0
  180. package/tools/web_search.js +52 -0
  181. package/tools/write_file.js +28 -0
  182. package/ui/api.js +190 -0
  183. package/ui/app.js +281 -0
  184. package/ui/index.html +382 -0
  185. package/ui/views/agents.js +377 -0
  186. package/ui/views/chat.js +610 -0
  187. package/ui/views/connection.js +96 -0
  188. package/ui/views/daemons.js +129 -0
  189. package/ui/views/feed.js +194 -0
  190. package/ui/views/memory.js +263 -0
  191. package/ui/views/models.js +146 -0
  192. package/ui/views/sessions.js +314 -0
  193. package/ui/views/settings.js +142 -0
  194. package/ui/views/tasks.js +415 -0
  195. package/utils/context.js +49 -0
  196. package/utils/id.js +16 -0
  197. package/utils/models.js +88 -0
  198. package/utils/paths.js +213 -0
  199. package/utils/settings.js +172 -0
@@ -0,0 +1,270 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const router = express.Router({ mergeParams: true });
5
+ const { runTask } = require('../../core/router');
6
+ const { loadAgent } = require('../../core/agent');
7
+ const { sendError } = require('../middleware');
8
+ const context = require('../../utils/context');
9
+ const db = require('../../infrastructure/database');
10
+
11
+ // POST /agents/:name/task — create a new task
12
+ router.post('/agents/:name/task', async (req, res, next) => {
13
+ try {
14
+ const { name } = req.params;
15
+ const { input, priority, tags, maxIterations, maxDurationSeconds, tokenBudget } = req.body;
16
+ const cwd = context.getCwd();
17
+
18
+ if (!input || typeof input !== 'string') {
19
+ return sendError(res, 400, 'VALIDATION_ERROR', 'input is required and must be a string');
20
+ }
21
+
22
+ let agent;
23
+ try {
24
+ agent = loadAgent({ cwd, name });
25
+ } catch {
26
+ return sendError(res, 404, 'AGENT_NOT_FOUND', `Agent "${name}" not found`);
27
+ }
28
+
29
+ if (!agent.modes?.task?.enabled) {
30
+ return sendError(res, 400, 'MODE_NOT_SUPPORTED', `Agent "${name}" does not support task mode`);
31
+ }
32
+
33
+ const taskId = db.createTask({
34
+ agentName: name,
35
+ input,
36
+ priority: priority || 'normal',
37
+ tags,
38
+ maxIterations,
39
+ maxDurationSeconds,
40
+ tokenBudget: tokenBudget || null,
41
+ instanceFolder: cwd,
42
+ });
43
+
44
+ // Run task asynchronously (don't await — return taskId immediately)
45
+ const settings = req.app.locals.settings;
46
+ setImmediate(() => runTask({ taskId, cwd, settings }).catch(err => {
47
+ console.error(`Task ${taskId} failed:`, err.message);
48
+ db.updateTask(taskId, { status: 'failed', error: err.message });
49
+ }));
50
+
51
+ res.status(202).json({ taskId, status: 'pending' });
52
+ } catch (err) {
53
+ next(err);
54
+ }
55
+ });
56
+
57
+ // GET /tasks — list tasks
58
+ router.get('/tasks', (req, res, next) => {
59
+ try {
60
+ const cwd = context.getCwd();
61
+ const { agentName, status, priority, limit, cursor } = req.query;
62
+ const tasks = db.listTasks({
63
+ instanceFolder: cwd,
64
+ agentName,
65
+ status,
66
+ priority,
67
+ limit: limit ? parseInt(limit, 10) : 20,
68
+ cursor,
69
+ });
70
+ res.json({ tasks });
71
+ } catch (err) {
72
+ next(err);
73
+ }
74
+ });
75
+
76
+ // GET /tasks/:id — get task details
77
+ router.get('/tasks/:id', (req, res, next) => {
78
+ try {
79
+ const task = db.getTask(req.params.id);
80
+ if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
81
+ res.json({ task });
82
+ } catch (err) {
83
+ next(err);
84
+ }
85
+ });
86
+
87
+ // GET /tasks/:id/events — get task progress events
88
+ router.get('/tasks/:id/events', (req, res, next) => {
89
+ try {
90
+ const task = db.getTask(req.params.id);
91
+ if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
92
+ const { since, limit } = req.query;
93
+ const events = db.getTaskEvents(req.params.id, {
94
+ since: since ? parseInt(since, 10) : 0,
95
+ limit: limit ? parseInt(limit, 10) : 500,
96
+ });
97
+ res.json({ taskId: req.params.id, events });
98
+ } catch (err) {
99
+ next(err);
100
+ }
101
+ });
102
+
103
+ // GET /tasks/:id/context — LLM context snapshot for debuggability
104
+ router.get('/tasks/:id/context', (req, res, next) => {
105
+ try {
106
+ const task = db.getTask(req.params.id);
107
+ if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
108
+ const context = db.getTaskContext(req.params.id);
109
+ res.json({
110
+ taskId: req.params.id,
111
+ context,
112
+ note: context ? null : 'No context snapshot yet (task may still be pending or has not made an LLM call)',
113
+ });
114
+ } catch (err) {
115
+ next(err);
116
+ }
117
+ });
118
+
119
+ // POST /tasks/:id/respond — respond to a waiting task and resume it
120
+ router.post('/tasks/:id/respond', async (req, res, next) => {
121
+ try {
122
+ const task = db.getTask(req.params.id);
123
+ if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
124
+ if (task.status !== 'waiting') return sendError(res, 400, 'TASK_NOT_WAITING', `Task is not in waiting status (current: ${task.status})`);
125
+
126
+ const { message } = req.body;
127
+ if (!message) return sendError(res, 400, 'VALIDATION_ERROR', 'message is required');
128
+
129
+ // Enqueue the response message so it's picked up by the loop
130
+ db.enqueueAgentMessage({
131
+ targetAgent: task.agent_name,
132
+ fromAgent: 'user',
133
+ content: message,
134
+ followup: false,
135
+ });
136
+
137
+ // Respond immediately, then resume the task asynchronously
138
+ res.json({ taskId: req.params.id, status: 'processing' });
139
+
140
+ // Resume the task loop in the background
141
+ const { resumeTask } = require('../../core/router');
142
+ const cwd = req.app.locals.cwd;
143
+ const settings = req.app.locals.settings;
144
+ resumeTask({ taskId: req.params.id, cwd, settings }).catch(err => {
145
+ console.error(`[tasks] Failed to resume task ${req.params.id}:`, err.message);
146
+ });
147
+ } catch (err) {
148
+ next(err);
149
+ }
150
+ });
151
+
152
+ // DELETE /tasks/:id — hard-delete a task and all its associated data
153
+ router.delete('/tasks/:id', (req, res, next) => {
154
+ try {
155
+ const task = db.getTask(req.params.id);
156
+ if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
157
+
158
+ const ACTIVE_STATUSES = ['pending', 'processing', 'waiting'];
159
+ if (ACTIVE_STATUSES.includes(task.status) && req.query.force !== 'true') {
160
+ return sendError(res, 409, 'TASK_ACTIVE', `Task is still active (status: ${task.status}). Use ?force=true to cancel and delete.`);
161
+ }
162
+
163
+ // Cancel first if active and forced
164
+ if (ACTIVE_STATUSES.includes(task.status)) {
165
+ const cancelRegistry = require('../../core/cancel');
166
+ cancelRegistry.cancel(req.params.id);
167
+ db.updateTask(req.params.id, { status: 'canceled', finishedAt: new Date().toISOString() });
168
+ }
169
+
170
+ db.deleteTask(req.params.id);
171
+ res.json({ deleted: true, taskId: req.params.id });
172
+ } catch (err) {
173
+ next(err);
174
+ }
175
+ });
176
+
177
+ // GET /tasks/:id/stream — SSE real-time event stream for a single task
178
+ router.get('/tasks/:id/stream', (req, res, next) => {
179
+ try {
180
+ const task = db.getTask(req.params.id);
181
+ if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
182
+
183
+ const TERMINAL_STATUSES = new Set(['finished', 'failed', 'canceled']);
184
+
185
+ res.setHeader('Content-Type', 'text/event-stream');
186
+ res.setHeader('Cache-Control', 'no-cache');
187
+ res.setHeader('Connection', 'keep-alive');
188
+ res.setHeader('X-Accel-Buffering', 'no');
189
+ res.flushHeaders();
190
+
191
+ const taskId = req.params.id;
192
+
193
+ function send(eventType, data) {
194
+ try {
195
+ res.write(`event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`);
196
+ } catch { /* client disconnected */ }
197
+ }
198
+
199
+ // 1. Replay historical DB events
200
+ const history = db.getTaskEvents(taskId, { since: 0, limit: 1000 });
201
+ for (const ev of history) {
202
+ send('task', { id: ev.id, type: ev.type, data: ev.data ? JSON.parse(ev.data) : null, created_at: ev.created_at });
203
+ }
204
+
205
+ // 2. Send current task state
206
+ send('status', { taskId, status: task.status, output: task.output || null, error: task.error ? JSON.parse(task.error) : null });
207
+
208
+ // 3. If already terminal, close immediately
209
+ if (TERMINAL_STATUSES.has(task.status)) {
210
+ res.end();
211
+ return;
212
+ }
213
+
214
+ // 4. Subscribe to live events from the event bus
215
+ const eventBus = require('../../core/events');
216
+
217
+ function onEvent(msg) {
218
+ if (msg.taskId !== taskId) return;
219
+
220
+ send('task', { type: msg.type, event: msg.event, timestamp: msg.event?.timestamp });
221
+
222
+ // Auto-close when task reaches a terminal state
223
+ if (msg.type === 'task.status' && TERMINAL_STATUSES.has(msg.event?.status)) {
224
+ cleanup();
225
+ res.end();
226
+ }
227
+ }
228
+
229
+ eventBus.on('event', onEvent);
230
+
231
+ // 5. Keepalive ping every 15s to prevent proxy timeouts
232
+ const keepalive = setInterval(() => {
233
+ try { res.write(': keepalive\n\n'); } catch { cleanup(); }
234
+ }, 15000);
235
+
236
+ function cleanup() {
237
+ clearInterval(keepalive);
238
+ eventBus.off('event', onEvent);
239
+ }
240
+
241
+ req.on('close', cleanup);
242
+ req.on('error', cleanup);
243
+ } catch (err) {
244
+ next(err);
245
+ }
246
+ });
247
+
248
+ // POST /tasks/:id/cancel — cancel a task (cooperative for processing tasks)
249
+ router.post('/tasks/:id/cancel', (req, res, next) => {
250
+ try {
251
+ const task = db.getTask(req.params.id);
252
+ if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
253
+ if (['finished', 'failed', 'canceled'].includes(task.status)) {
254
+ return sendError(res, 400, 'TASK_ALREADY_TERMINAL', `Task is already in terminal state: ${task.status}`);
255
+ }
256
+ // Signal the running loop to abort (cooperative cancellation)
257
+ const cancelRegistry = require('../../core/cancel');
258
+ cancelRegistry.cancel(req.params.id);
259
+ // Also update DB immediately for pending/waiting tasks that have no running loop
260
+ if (task.status !== 'processing') {
261
+ db.updateTask(req.params.id, { status: 'canceled', finishedAt: new Date().toISOString() });
262
+ db.addTaskEvent({ taskId: req.params.id, type: 'status.change', data: { from: task.status, to: 'canceled', reason: 'user_cancel' } });
263
+ }
264
+ res.json({ taskId: req.params.id, status: 'canceled' });
265
+ } catch (err) {
266
+ next(err);
267
+ }
268
+ });
269
+
270
+ module.exports = router;
package/api/server.js ADDED
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const cors = require('cors');
5
+ const { authMiddleware, errorHandler } = require('./middleware');
6
+ const agentsRouter = require('./routes/agents');
7
+ const chatRouter = require('./routes/chat');
8
+ const tasksRouter = require('./routes/tasks');
9
+ const sessionsRouter = require('./routes/sessions');
10
+ const daemonsRouter = require('./routes/daemons');
11
+ const systemRouter = require('./routes/system');
12
+ const memoryRouter = require('./routes/memory');
13
+ const settingsRouter = require('./routes/settings');
14
+ const modelsRouter = require('./routes/models');
15
+ const completionsRouter = require('./routes/completions');
16
+ const remoteMethodsRouter = require('./routes/remote-methods');
17
+
18
+ /**
19
+ * Create and configure the Express app.
20
+ * @param {{ settings: Object, scheduler: Object }} opts
21
+ * @returns {import('express').Application}
22
+ */
23
+ function createApp({ settings, scheduler }) {
24
+ const app = express();
25
+
26
+ // Store shared state in app.locals (no circular deps)
27
+ app.locals.settings = settings;
28
+ app.locals.scheduler = scheduler;
29
+
30
+ app.use(cors());
31
+ app.use(express.json({ limit: '10mb' }));
32
+
33
+ // Auth middleware on all routes
34
+ app.use(authMiddleware);
35
+
36
+ // ── Routes ────────────────────────────────────────────────────────────────
37
+ app.use('/agents', agentsRouter);
38
+ app.use('/agents/:name/chat', chatRouter);
39
+
40
+ // Task routes are mixed — some under /agents/:name/task, some under /tasks
41
+ app.use('/', tasksRouter);
42
+
43
+ app.use('/sessions', sessionsRouter);
44
+
45
+ // Daemon routes: GET /daemons + POST /agents/:name/daemon/*
46
+ app.use('/daemons', daemonsRouter);
47
+ app.use('/agents', daemonsRouter);
48
+
49
+ // Memory API: /agents/:name/memory/* (must come before generic agents router catches :name)
50
+ app.use('/agents', memoryRouter);
51
+
52
+ // Global memory API: /memory/*
53
+ app.use('/', memoryRouter);
54
+
55
+ // Settings API: GET/PUT /settings
56
+ app.use('/settings', settingsRouter);
57
+
58
+ // Models API: GET /models, GET /models/:provider/:name, POST /models/refresh
59
+ app.use('/models', modelsRouter);
60
+
61
+ // Standalone completions: POST /completions (raw LLM call, no agent/session)
62
+ app.use('/completions', completionsRouter);
63
+
64
+ // Remote Method Execution: GET /remote-methods (SSE) + POST /remote-methods/:id/result
65
+ app.use('/remote-methods', remoteMethodsRouter);
66
+
67
+ app.use('/', systemRouter);
68
+
69
+ // Global error handler (must be last)
70
+ app.use(errorHandler);
71
+
72
+ return app;
73
+ }
74
+
75
+ /**
76
+ * Start the HTTP server (with WebSocket support on /ws).
77
+ * @param {{ settings: Object, scheduler: Object, port?: number }} opts
78
+ * @returns {Promise<import('http').Server>}
79
+ */
80
+ function startServer({ settings, scheduler, port }) {
81
+ const app = createApp({ settings, scheduler });
82
+ const listenPort = port || settings.port || 5050;
83
+
84
+ return new Promise((resolve, reject) => {
85
+ const server = app.listen(listenPort, (err) => {
86
+ if (err) return reject(err);
87
+
88
+ // Attach WebSocket server for real-time event streaming
89
+ try {
90
+ const { WebSocketServer } = require('ws');
91
+ const eventBus = require('../core/events');
92
+ const wss = new WebSocketServer({ server, path: '/ws' });
93
+
94
+ wss.on('connection', (ws) => {
95
+ const handler = (ev) => {
96
+ try {
97
+ if (ws.readyState === 1) ws.send(JSON.stringify(ev));
98
+ } catch { /* client may have disconnected */ }
99
+ };
100
+ eventBus.on('event', handler);
101
+ ws.on('close', () => eventBus.off('event', handler));
102
+ ws.on('error', () => eventBus.off('event', handler));
103
+ // Send a welcome ping so client knows connection is live
104
+ try { ws.send(JSON.stringify({ type: 'connected', timestamp: Date.now() })); } catch { /* ignore */ }
105
+ });
106
+
107
+ wss.on('error', (err) => {
108
+ console.error('[ws] WebSocket server error:', err.message);
109
+ });
110
+ } catch (wsErr) {
111
+ console.warn('[ws] WebSocket unavailable (install ws package):', wsErr.message);
112
+ }
113
+
114
+ resolve(server);
115
+ });
116
+ server.on('error', reject);
117
+ });
118
+ }
119
+
120
+ module.exports = { createApp, startServer };
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * CLI output formatting helpers.
5
+ */
6
+
7
+ function bold(text) { return `\x1b[1m${text}\x1b[0m`; }
8
+ function green(text) { return `\x1b[32m${text}\x1b[0m`; }
9
+ function yellow(text) { return `\x1b[33m${text}\x1b[0m`; }
10
+ function red(text) { return `\x1b[31m${text}\x1b[0m`; }
11
+ function dim(text) { return `\x1b[2m${text}\x1b[0m`; }
12
+ function cyan(text) { return `\x1b[36m${text}\x1b[0m`; }
13
+
14
+ /**
15
+ * Print a success message.
16
+ * @param {string} msg
17
+ */
18
+ function success(msg) { console.log(green('✓ ') + msg); }
19
+
20
+ /**
21
+ * Print a warning message.
22
+ * @param {string} msg
23
+ */
24
+ function warn(msg) { console.log(yellow('⚠ ') + msg); }
25
+
26
+ /**
27
+ * Print an error message.
28
+ * @param {string} msg
29
+ */
30
+ function error(msg) { console.error(red('✗ ') + msg); }
31
+
32
+ /**
33
+ * Print an info message.
34
+ * @param {string} msg
35
+ */
36
+ function info(msg) { console.log(dim(' ') + msg); }
37
+
38
+ /**
39
+ * Print a section header.
40
+ * @param {string} title
41
+ */
42
+ function header(title) { console.log('\n' + bold(title)); }
43
+
44
+ /**
45
+ * Format a table from an array of objects.
46
+ * @param {Object[]} rows
47
+ * @param {string[]} columns
48
+ */
49
+ function table(rows, columns) {
50
+ if (rows.length === 0) { console.log(dim(' (none)')); return; }
51
+ const widths = columns.map(col => Math.max(col.length, ...rows.map(r => String(r[col] || '').length)));
52
+ const header = columns.map((col, i) => bold(col.padEnd(widths[i]))).join(' ');
53
+ const separator = widths.map(w => '─'.repeat(w)).join(' ');
54
+ console.log(' ' + header);
55
+ console.log(' ' + dim(separator));
56
+ for (const row of rows) {
57
+ const line = columns.map((col, i) => String(row[col] || '').padEnd(widths[i])).join(' ');
58
+ console.log(' ' + line);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Print a progress event (for one-shot mode).
64
+ * @param {Object} event
65
+ */
66
+ function progress(event) {
67
+ process.stdout.write(`VEIL_PROGRESS=${JSON.stringify(event)}\n`);
68
+ }
69
+
70
+ module.exports = { bold, green, yellow, red, dim, cyan, success, warn, error, info, header, table, progress };