@ouro.bot/cli 0.1.0-alpha.160 → 0.1.0-alpha.161

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/README.md CHANGED
@@ -221,3 +221,4 @@ See `skills/configure-dev-tools.md` for the full tool inventory and troubleshoot
221
221
  ## A Note To Future Maintainers
222
222
 
223
223
  If you discover a doc that lies, fix it or remove it. Accuracy is a kindness. A future agent should not have to untangle a fossil record just to understand where their hands are.
224
+ # Production SPA serving
package/changelog.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.161",
6
+ "changes": [
7
+ "feat(outlook): serve React SPA at root (/), daemon serves static assets",
8
+ "fix(daemon): graceful EADDRINUSE handling for Outlook port"
9
+ ]
10
+ },
4
11
  {
5
12
  "version": "0.1.0-alpha.160",
6
13
  "changes": [
@@ -295,35 +295,46 @@ class OuroDaemon {
295
295
  await this.drainPendingSenseMessages();
296
296
  /* v8 ignore start — Outlook server startup, tested via outlook-http.test.ts */
297
297
  if (!this.outlookServer) {
298
- this.outlookServer = await (0, outlook_http_1.startOutlookHttpServer)({
299
- host: "127.0.0.1",
300
- port: outlook_types_1.OUTLOOK_DEFAULT_PORT,
301
- readMachineState: () => (0, outlook_read_1.readOutlookMachineState)({ bundlesRoot: this.bundlesRoot }),
302
- readMachineView: ({ machine }) => {
303
- const overview = this.buildStatusPayload().overview;
304
- return (0, outlook_view_1.buildOutlookMachineView)({
305
- machine,
306
- daemon: {
307
- status: overview.daemon,
308
- health: overview.health,
309
- mode: overview.mode,
310
- socketPath: overview.socketPath,
311
- outlookUrl: overview.outlookUrl,
312
- entryPath: overview.entryPath,
313
- workerCount: overview.workerCount,
314
- senseCount: overview.senseCount,
315
- },
316
- });
317
- },
318
- readAgentState: (agentName) => (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot }),
319
- readAgentView: (agentName) => {
320
- const agent = (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot });
321
- return (0, outlook_view_1.buildOutlookAgentView)({
322
- agent,
323
- viewer: { kind: "human" },
324
- });
325
- },
326
- });
298
+ try {
299
+ this.outlookServer = await (0, outlook_http_1.startOutlookHttpServer)({
300
+ host: "127.0.0.1",
301
+ port: outlook_types_1.OUTLOOK_DEFAULT_PORT,
302
+ readMachineState: () => (0, outlook_read_1.readOutlookMachineState)({ bundlesRoot: this.bundlesRoot }),
303
+ readMachineView: ({ machine }) => {
304
+ const overview = this.buildStatusPayload().overview;
305
+ return (0, outlook_view_1.buildOutlookMachineView)({
306
+ machine,
307
+ daemon: {
308
+ status: overview.daemon,
309
+ health: overview.health,
310
+ mode: overview.mode,
311
+ socketPath: overview.socketPath,
312
+ outlookUrl: overview.outlookUrl,
313
+ entryPath: overview.entryPath,
314
+ workerCount: overview.workerCount,
315
+ senseCount: overview.senseCount,
316
+ },
317
+ });
318
+ },
319
+ readAgentState: (agentName) => (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot }),
320
+ readAgentView: (agentName) => {
321
+ const agent = (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot });
322
+ return (0, outlook_view_1.buildOutlookAgentView)({
323
+ agent,
324
+ viewer: { kind: "human" },
325
+ });
326
+ },
327
+ });
328
+ }
329
+ catch (error) {
330
+ (0, runtime_1.emitNervesEvent)({
331
+ level: "warn",
332
+ component: "daemon",
333
+ event: "daemon.outlook_start_failed",
334
+ message: `Outlook server failed to start: ${String(error)}`,
335
+ meta: { port: outlook_types_1.OUTLOOK_DEFAULT_PORT },
336
+ });
337
+ }
327
338
  }
328
339
  /* v8 ignore stop */
329
340
  if (fs.existsSync(this.socketPath)) {
@@ -39,7 +39,6 @@ const http = __importStar(require("http"));
39
39
  const path = __importStar(require("path"));
40
40
  const runtime_1 = require("../../nerves/runtime");
41
41
  const outlook_read_1 = require("./outlook-read");
42
- const outlook_render_1 = require("./outlook-render");
43
42
  function createSseBroadcaster() {
44
43
  let nextId = 1;
45
44
  const clients = new Set();
@@ -114,10 +113,54 @@ function writeJson(response, statusCode, payload) {
114
113
  response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8" });
115
114
  response.end(`${JSON.stringify(payload, null, 2)}\n`);
116
115
  }
117
- function writeHtml(response, html) {
118
- response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
119
- response.end(html);
116
+ /* v8 ignore start — SPA static file serving infrastructure */
117
+ const MIME_TYPES = {
118
+ ".html": "text/html",
119
+ ".js": "application/javascript",
120
+ ".css": "text/css",
121
+ ".json": "application/json",
122
+ ".svg": "image/svg+xml",
123
+ ".png": "image/png",
124
+ ".ico": "image/x-icon",
125
+ ".woff2": "font/woff2",
126
+ };
127
+ function resolveSpaDistDir() {
128
+ // Look for the built SPA relative to this file's location
129
+ // In production: dist/heart/daemon/outlook-http.js -> ../../packages/outlook-ui/dist/
130
+ // In dev: packages/outlook-ui/dist/
131
+ const candidates = [
132
+ path.resolve(__dirname, "..", "..", "..", "packages", "outlook-ui", "dist"),
133
+ path.resolve(__dirname, "..", "..", "packages", "outlook-ui", "dist"),
134
+ path.resolve(__dirname, "..", "..", "..", "..", "packages", "outlook-ui", "dist"),
135
+ // npm-published layout: the SPA dist is copied to dist/outlook-ui/
136
+ path.resolve(__dirname, "..", "..", "outlook-ui"),
137
+ path.resolve(__dirname, "..", "outlook-ui"),
138
+ ];
139
+ for (const candidate of candidates) {
140
+ if (fs.existsSync(path.join(candidate, "index.html")))
141
+ return candidate;
142
+ }
143
+ return null;
144
+ }
145
+ function serveStaticFile(response, filePath) {
146
+ try {
147
+ if (!fs.existsSync(filePath))
148
+ return false;
149
+ const ext = path.extname(filePath);
150
+ const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
151
+ const content = fs.readFileSync(filePath);
152
+ response.writeHead(200, {
153
+ "content-type": contentType,
154
+ "cache-control": ext === ".html" ? "no-cache" : "public, max-age=31536000, immutable",
155
+ });
156
+ response.end(content);
157
+ return true;
158
+ }
159
+ catch {
160
+ return false;
161
+ }
120
162
  }
163
+ /* v8 ignore stop */
121
164
  function normalizePath(urlValue = "/") {
122
165
  const parsed = new URL(urlValue, "http://127.0.0.1");
123
166
  const normalizedPath = parsed.pathname.replace(/\/+$/, "");
@@ -140,7 +183,6 @@ async function startOutlookHttpServer(options = {}) {
140
183
  });
141
184
  /* v8 ignore stop */
142
185
  const readAgentView = options.readAgentView;
143
- const renderApp = options.renderApp ?? outlook_render_1.renderOutlookApp;
144
186
  /* v8 ignore start — default hook wiring, tested via integration */
145
187
  const agentRoot = (agentName) => {
146
188
  const base = bundlesRoot ?? "";
@@ -174,10 +216,23 @@ async function startOutlookHttpServer(options = {}) {
174
216
  sse.add(response);
175
217
  return;
176
218
  }
219
+ /* v8 ignore start — SPA static asset serving */
220
+ // Serve built SPA static assets: /assets/*
221
+ if (pathname.startsWith("/assets/")) {
222
+ const spaDir = resolveSpaDistDir();
223
+ if (spaDir) {
224
+ const assetPath = path.join(spaDir, pathname);
225
+ if (serveStaticFile(response, assetPath))
226
+ return;
227
+ }
228
+ writeJson(response, 404, { ok: false, error: "asset not found" });
229
+ return;
230
+ }
231
+ /* v8 ignore stop */
232
+ // Legacy /outlook route — redirect to root
177
233
  if (pathname === "/outlook") {
178
- const machine = readMachineState();
179
- const machineView = readMachineView?.({ origin, machine });
180
- writeHtml(response, renderApp({ origin, machine, machineView }));
234
+ response.writeHead(301, { location: "/" });
235
+ response.end();
181
236
  return;
182
237
  }
183
238
  if (pathname === "/outlook/api/machine") {
@@ -273,7 +328,14 @@ async function startOutlookHttpServer(options = {}) {
273
328
  writeJson(response, 404, { ok: false, error: `unknown agent surface: ${surface}` });
274
329
  return;
275
330
  }
276
- writeJson(response, 404, { ok: false, error: `unknown outlook path: ${pathname}` });
331
+ /* v8 ignore start — SPA fallback for client-side routing */
332
+ const spaDir = resolveSpaDistDir();
333
+ if (spaDir) {
334
+ if (serveStaticFile(response, path.join(spaDir, "index.html")))
335
+ return;
336
+ }
337
+ /* v8 ignore stop */
338
+ writeJson(response, 404, { ok: false, error: `not found: ${pathname}` });
277
339
  });
278
340
  await new Promise((resolve, reject) => {
279
341
  server.once("error", reject);