@mindstudio-ai/local-model-tunnel 0.5.21 → 0.5.23

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.
@@ -22,19 +22,29 @@ import {
22
22
  syncSchema,
23
23
  watchConfigFile,
24
24
  watchTableFiles
25
- } from "./chunk-VJTZSOKC.js";
25
+ } from "./chunk-HZZVPY7J.js";
26
+
27
+ // src/dev/ipc.ts
28
+ function emitEvent(event, data) {
29
+ process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
30
+ }
31
+ function emitResponse(action, requestId, status, data) {
32
+ process.stdout.write(
33
+ JSON.stringify({ event: action, requestId, status, ...data }) + "\n"
34
+ );
35
+ }
26
36
 
27
37
  // src/dev/session-events.ts
28
- function subscribeDevEvents(emit2, shutdown) {
38
+ function subscribeDevEvents(shutdown) {
29
39
  const unsubs = [];
30
40
  unsubs.push(
31
41
  devRequestEvents.onStart((event) => {
32
- emit2("method-started", { id: event.id, method: event.method });
42
+ emitEvent("platform-method-started", { id: event.id, method: event.method });
33
43
  })
34
44
  );
35
45
  unsubs.push(
36
46
  devRequestEvents.onComplete((event) => {
37
- emit2("method-completed", {
47
+ emitEvent("platform-method-completed", {
38
48
  id: event.id,
39
49
  success: event.success,
40
50
  duration: event.duration,
@@ -44,248 +54,211 @@ function subscribeDevEvents(emit2, shutdown) {
44
54
  );
45
55
  unsubs.push(
46
56
  devRequestEvents.onConnectionWarning((message) => {
47
- emit2("connection-lost", { message });
57
+ emitEvent("connection-lost", { message });
48
58
  })
49
59
  );
50
60
  unsubs.push(
51
61
  devRequestEvents.onConnectionRestored(() => {
52
- emit2("connection-restored");
62
+ emitEvent("connection-restored");
53
63
  })
54
64
  );
55
65
  unsubs.push(
56
66
  devRequestEvents.onSessionExpired(() => {
57
- emit2("session-expired");
67
+ emitEvent("session-expired");
58
68
  shutdown().then(() => process.exit(1));
59
69
  })
60
70
  );
61
71
  unsubs.push(
62
72
  devRequestEvents.onAuthRefreshStart((url) => {
63
- emit2("auth-refresh-start", { url });
73
+ emitEvent("auth-refresh-start", { url });
64
74
  })
65
75
  );
66
76
  unsubs.push(
67
77
  devRequestEvents.onAuthRefreshSuccess(() => {
68
- emit2("auth-refresh-success");
78
+ emitEvent("auth-refresh-success");
69
79
  })
70
80
  );
71
81
  unsubs.push(
72
82
  devRequestEvents.onAuthRefreshFailed(() => {
73
- emit2("auth-refresh-failed");
74
- })
75
- );
76
- unsubs.push(
77
- devRequestEvents.onImpersonate((event) => {
78
- emit2("impersonation-changed", { roles: event.roles });
79
- })
80
- );
81
- unsubs.push(
82
- devRequestEvents.onScenarioStart((event) => {
83
- emit2("scenario-started", { id: event.id, name: event.name });
84
- })
85
- );
86
- unsubs.push(
87
- devRequestEvents.onScenarioComplete((event) => {
88
- emit2("scenario-completed", {
89
- id: event.id,
90
- success: event.success,
91
- duration: event.duration,
92
- roles: event.roles,
93
- ...event.error ? { error: event.error } : {}
94
- });
83
+ emitEvent("auth-refresh-failed");
95
84
  })
96
85
  );
97
86
  return unsubs;
98
87
  }
99
88
 
100
89
  // src/dev/stdin-commands/run-scenario.ts
101
- async function handleRunScenario(state, cwd, cmd, emit2) {
102
- if (!state.runner) {
103
- emit2("command-error", { message: "No active session" });
104
- return;
105
- }
106
- const freshConfig = detectAppConfig(cwd) ?? state.appConfig;
90
+ async function handleRunScenario(ctx, cmd) {
91
+ if (!ctx.state.runner) throw new Error("No active session");
92
+ const freshConfig = detectAppConfig(ctx.cwd) ?? ctx.state.appConfig;
107
93
  const scenario = freshConfig?.scenarios.find((s) => s.id === cmd.scenarioId);
108
- if (!scenario) {
109
- emit2("command-error", { message: `Unknown scenario: ${cmd.scenarioId}` });
110
- return;
111
- }
112
- await state.runner.runScenario(scenario);
94
+ if (!scenario) throw new Error(`Unknown scenario: ${cmd.scenarioId}`);
95
+ const scenarioName = scenario.name ?? scenario.export;
96
+ ctx.started({ scenarioId: scenario.id, name: scenarioName });
97
+ const result = await ctx.state.runner.runScenario(scenario);
98
+ return {
99
+ success: result.success,
100
+ scenarioId: scenario.id,
101
+ name: scenarioName,
102
+ ...result.error ? { error: result.error } : {}
103
+ };
113
104
  }
114
105
 
115
106
  // src/dev/stdin-commands/run-method.ts
116
- async function handleRunMethod(state, cwd, cmd, emit2) {
117
- if (!state.runner) {
118
- emit2("command-error", { message: "No active session" });
119
- return;
120
- }
107
+ async function handleRunMethod(ctx, cmd) {
108
+ if (!ctx.state.runner) throw new Error("No active session");
121
109
  const methodName = cmd.method;
122
- if (!methodName) {
123
- emit2("command-error", { message: 'run-method requires "method" (export name or ID)' });
124
- return;
125
- }
126
- const freshConfig = detectAppConfig(cwd) ?? state.appConfig;
110
+ if (!methodName) throw new Error('run-method requires "method" (export name or ID)');
111
+ const freshConfig = detectAppConfig(ctx.cwd) ?? ctx.state.appConfig;
127
112
  const method = freshConfig?.methods.find((m) => m.export === methodName) ?? freshConfig?.methods.find((m) => m.id === methodName);
128
- if (!method) {
129
- emit2("command-error", { message: `Unknown method: ${methodName}` });
130
- return;
131
- }
132
- emit2("method-run-started", { method: method.export });
133
- const result = await state.runner.runMethod({
113
+ if (!method) throw new Error(`Unknown method: ${methodName}`);
114
+ ctx.started({ method: method.export });
115
+ const result = await ctx.state.runner.runMethod({
134
116
  methodExport: method.export,
135
117
  methodPath: method.path,
136
118
  input: cmd.input ?? {}
137
119
  });
138
- emit2("method-run-completed", {
139
- method: method.export,
120
+ return {
140
121
  success: result.success,
122
+ method: method.export,
141
123
  output: result.output ?? null,
142
124
  error: result.error ?? null,
143
125
  stdout: result.stdout ?? [],
144
126
  duration: result.duration
145
- });
127
+ };
146
128
  }
147
129
 
148
130
  // src/dev/stdin-commands/impersonate.ts
149
- async function handleImpersonate(state, cmd, emit2) {
150
- if (!state.runner) {
151
- emit2("command-error", { message: "No active session" });
152
- return;
153
- }
131
+ async function handleImpersonate(ctx, cmd) {
132
+ if (!ctx.state.runner) throw new Error("No active session");
154
133
  const roles = cmd.roles;
155
- if (!Array.isArray(roles)) {
156
- emit2("command-error", { message: "impersonate requires roles array" });
157
- return;
158
- }
159
- await state.runner.setImpersonation(roles);
134
+ if (!Array.isArray(roles)) throw new Error("impersonate requires roles array");
135
+ await ctx.state.runner.setImpersonation(roles);
136
+ return { roles };
160
137
  }
161
- async function handleClearImpersonation(state, emit2) {
162
- if (!state.runner) {
163
- emit2("command-error", { message: "No active session" });
164
- return;
165
- }
166
- await state.runner.clearImpersonation();
138
+ async function handleClearImpersonation(ctx) {
139
+ if (!ctx.state.runner) throw new Error("No active session");
140
+ await ctx.state.runner.clearImpersonation();
141
+ return { roles: null };
167
142
  }
168
143
 
169
144
  // src/dev/stdin-commands/browser.ts
170
- async function handleBrowser(state, cmd, emit2) {
171
- if (!state.proxy) {
172
- emit2("command-error", { message: "No active proxy \u2014 browser commands require a web interface" });
173
- return;
174
- }
145
+ async function handleBrowser(ctx, cmd) {
146
+ if (!ctx.state.proxy) throw new Error("No active proxy \u2014 browser commands require a web interface");
175
147
  const steps = cmd.steps;
176
148
  if (!Array.isArray(steps) || steps.length === 0) {
177
- emit2("command-error", { message: 'browser action requires a non-empty "steps" array' });
178
- return;
149
+ throw new Error('browser action requires a non-empty "steps" array');
179
150
  }
180
- try {
181
- const result = await state.proxy.dispatchBrowserCommand(steps);
182
- emit2("browser-completed", {
183
- steps: result.steps,
184
- snapshot: result.snapshot,
185
- logs: result.logs,
186
- duration: result.duration
187
- });
188
- } catch (err) {
189
- emit2("command-error", {
190
- message: err instanceof Error ? err.message : "Browser command failed"
191
- });
151
+ const preparedSteps = await injectScreenshotUploads(ctx, steps);
152
+ const result = await ctx.state.proxy.dispatchBrowserCommand(preparedSteps);
153
+ const resultSteps = result.steps ?? [];
154
+ for (const step of resultSteps) {
155
+ const stepResult = step.result;
156
+ if (stepResult?.uploaded && stepResult?._publicUrl) {
157
+ stepResult.url = stepResult._publicUrl;
158
+ delete stepResult.uploaded;
159
+ delete stepResult._publicUrl;
160
+ delete stepResult.image;
161
+ }
192
162
  }
163
+ return {
164
+ steps: resultSteps,
165
+ snapshot: result.snapshot,
166
+ logs: result.logs,
167
+ duration: result.duration
168
+ };
169
+ }
170
+ async function injectScreenshotUploads(ctx, steps) {
171
+ const session = ctx.state.runner?.getSession();
172
+ const appId = ctx.state.appConfig?.appId;
173
+ if (!session || !appId) return steps;
174
+ const prepared = [];
175
+ for (const step of steps) {
176
+ if (step.command === "screenshot") {
177
+ try {
178
+ const { uploadUrl, uploadFields, publicUrl } = await getUploadUrl(
179
+ appId,
180
+ session.sessionId,
181
+ "jpg",
182
+ "image/jpeg"
183
+ );
184
+ prepared.push({ ...step, uploadUrl, uploadFields, _publicUrl: publicUrl });
185
+ } catch {
186
+ prepared.push(step);
187
+ }
188
+ } else {
189
+ prepared.push(step);
190
+ }
191
+ }
192
+ return prepared;
193
193
  }
194
194
 
195
195
  // src/dev/stdin-commands/screenshot.ts
196
- async function handleScreenshot(state, emit2) {
197
- const fail = (message) => {
198
- emit2("screenshot-completed", {
199
- url: "",
200
- width: 0,
201
- height: 0,
202
- duration: 0,
203
- error: message
204
- });
205
- };
206
- if (!state.proxy) {
207
- fail("No active proxy");
208
- return;
209
- }
210
- if (!state.proxy.isBrowserConnected()) {
211
- fail("No browser connected, please refresh the MindStudio preview");
212
- return;
196
+ async function handleScreenshot(ctx, cmd) {
197
+ if (!ctx.state.proxy) throw new Error("No active proxy");
198
+ if (!ctx.state.proxy.isBrowserConnected()) {
199
+ throw new Error("No browser connected, please refresh the MindStudio preview");
213
200
  }
214
- if (!state.runner?.getSession() || !state.appConfig?.appId) {
215
- fail("No active session");
216
- return;
201
+ if (!ctx.state.runner?.getSession() || !ctx.state.appConfig?.appId) {
202
+ throw new Error("No active session");
217
203
  }
218
- try {
219
- const startTime = Date.now();
220
- const result = await state.proxy.dispatchBrowserCommand([{ command: "screenshot" }]);
221
- const stepResult = result.steps?.[0]?.result;
222
- if (!stepResult?.image) {
223
- fail("Screenshot capture returned no image data");
224
- return;
225
- }
226
- const session = state.runner.getSession();
227
- const { uploadUrl, uploadFields, publicUrl } = await getUploadUrl(
228
- state.appConfig.appId,
229
- session.sessionId,
230
- "jpg",
231
- "image/jpeg"
232
- );
233
- const imageBuffer = Buffer.from(stepResult.image, "base64");
234
- const form = new FormData();
235
- for (const [key, value] of Object.entries(uploadFields)) {
236
- form.append(key, value);
237
- }
238
- form.append("file", new Blob([imageBuffer], { type: "image/jpeg" }), "screenshot.jpg");
239
- const uploadResult = await fetch(uploadUrl, { method: "POST", body: form });
240
- if (!uploadResult.ok) {
241
- fail(`S3 upload failed: ${uploadResult.status}`);
242
- return;
243
- }
244
- emit2("screenshot-completed", {
245
- url: publicUrl,
246
- width: stepResult.width,
247
- height: stepResult.height,
248
- duration: Date.now() - startTime
249
- });
250
- } catch (err) {
251
- fail(err instanceof Error ? err.message : "Screenshot failed");
204
+ const startTime = Date.now();
205
+ const session = ctx.state.runner.getSession();
206
+ const { uploadUrl, uploadFields, publicUrl } = await getUploadUrl(
207
+ ctx.state.appConfig.appId,
208
+ session.sessionId,
209
+ "jpg",
210
+ "image/jpeg"
211
+ );
212
+ const fullPage = cmd.fullPage !== false;
213
+ const result = await ctx.state.proxy.dispatchBrowserCommand(
214
+ [{ command: "screenshot", fullPage, scrollToTop: true, uploadUrl, uploadFields }],
215
+ 12e4
216
+ );
217
+ const stepResult = result.steps?.[0]?.result;
218
+ if (!stepResult?.uploaded) {
219
+ throw new Error("Screenshot capture or upload failed");
252
220
  }
221
+ return {
222
+ url: publicUrl,
223
+ width: stepResult.width,
224
+ height: stepResult.height,
225
+ duration: Date.now() - startTime
226
+ };
253
227
  }
254
228
 
255
229
  // src/dev/stdin-commands/dev-server-restarting.ts
256
- async function handleDevServerRestarting(state, emit2) {
257
- if (!state.proxy) {
258
- emit2("command-error", { message: "No active proxy" });
259
- return;
260
- }
261
- state.proxy.markUpstreamDown();
262
- emit2("dev-server-restarting-ack", {});
230
+ async function handleDevServerRestarting(ctx) {
231
+ if (!ctx.state.proxy) throw new Error("No active proxy");
232
+ ctx.state.proxy.markUpstreamDown();
233
+ return {};
263
234
  }
264
235
 
265
236
  // src/dev/stdin-commands/browser-status.ts
266
- function handleBrowserStatus(state, emit2) {
267
- emit2("browser-status", {
268
- connected: state.proxy?.isBrowserConnected() ?? false
269
- });
237
+ async function handleBrowserStatus(ctx) {
238
+ return { connected: ctx.state.proxy?.isBrowserConnected() ?? false };
270
239
  }
271
240
 
272
241
  // src/dev/stdin-commands/reset-browser.ts
273
- function handleResetBrowser(state, emit2) {
274
- if (!state.proxy) {
275
- emit2("command-error", { message: "No active proxy" });
276
- return;
277
- }
278
- if (!state.proxy.isBrowserConnected()) {
279
- emit2("command-error", { message: "No browser connected" });
280
- return;
281
- }
282
- state.proxy.dispatchBrowserCommand([{ command: "reload" }]).catch(() => {
283
- });
284
- emit2("reset-browser-completed", {});
242
+ async function handleResetBrowser(ctx) {
243
+ if (!ctx.state.proxy) throw new Error("No active proxy");
244
+ if (!ctx.state.proxy.isBrowserConnected()) throw new Error("No browser connected");
245
+ ctx.state.proxy.broadcastToClients("reload");
246
+ return {};
285
247
  }
286
248
 
287
249
  // src/dev/stdin-commands/index.ts
288
- function setupStdinCommands(state, cwd, emit2) {
250
+ var handlers = {
251
+ "run-method": handleRunMethod,
252
+ "run-scenario": handleRunScenario,
253
+ "impersonate": handleImpersonate,
254
+ "clear-impersonation": handleClearImpersonation,
255
+ "browser": handleBrowser,
256
+ "screenshot": handleScreenshot,
257
+ "browser-status": handleBrowserStatus,
258
+ "reset-browser": handleResetBrowser,
259
+ "dev-server-restarting": handleDevServerRestarting
260
+ };
261
+ function setupStdinCommands(state, cwd) {
289
262
  if (!process.stdin.readable) return;
290
263
  let buffer = "";
291
264
  process.stdin.setEncoding("utf-8");
@@ -296,53 +269,58 @@ function setupStdinCommands(state, cwd, emit2) {
296
269
  const line = buffer.slice(0, idx).trim();
297
270
  buffer = buffer.slice(idx + 1);
298
271
  if (!line) continue;
272
+ let cmd;
299
273
  try {
300
- const cmd = JSON.parse(line);
301
- handleStdinCommand(cmd, state, cwd, emit2);
274
+ cmd = JSON.parse(line);
302
275
  } catch {
303
- emit2("command-error", { message: `Invalid JSON on stdin: ${line.slice(0, 100)}` });
276
+ log.warn("Invalid JSON on stdin", { preview: line.slice(0, 100) });
277
+ continue;
304
278
  }
279
+ handleStdinCommand(cmd, state, cwd);
305
280
  }
306
281
  });
307
282
  }
308
- async function handleStdinCommand(cmd, state, cwd, emit2) {
309
- switch (cmd.action) {
310
- case "run-scenario":
311
- return handleRunScenario(state, cwd, cmd, emit2);
312
- case "run-method":
313
- return handleRunMethod(state, cwd, cmd, emit2);
314
- case "impersonate":
315
- return handleImpersonate(state, cmd, emit2);
316
- case "clear-impersonation":
317
- return handleClearImpersonation(state, emit2);
318
- case "browser":
319
- return handleBrowser(state, cmd, emit2);
320
- case "screenshot":
321
- return handleScreenshot(state, emit2);
322
- case "dev-server-restarting":
323
- return handleDevServerRestarting(state, emit2);
324
- case "browser-status":
325
- return handleBrowserStatus(state, emit2);
326
- case "reset-browser":
327
- return handleResetBrowser(state, emit2);
328
- default:
329
- emit2("command-error", { message: `Unknown action: ${cmd.action}` });
283
+ async function handleStdinCommand(cmd, state, cwd) {
284
+ const { requestId, action } = cmd;
285
+ if (!requestId) {
286
+ log.warn("Command rejected: missing requestId", { action });
287
+ return;
288
+ }
289
+ const handler = handlers[action];
290
+ if (!handler) {
291
+ emitResponse(action ?? "unknown", requestId, "completed", {
292
+ success: false,
293
+ error: `Unknown action: ${action}`
294
+ });
295
+ return;
296
+ }
297
+ const ctx = {
298
+ state,
299
+ cwd,
300
+ requestId,
301
+ started: (data) => emitResponse(action, requestId, "started", data)
302
+ };
303
+ try {
304
+ const result = await handler(ctx, cmd);
305
+ emitResponse(action, requestId, "completed", result);
306
+ } catch (err) {
307
+ emitResponse(action, requestId, "completed", {
308
+ success: false,
309
+ error: err instanceof Error ? err.message : String(err)
310
+ });
330
311
  }
331
312
  }
332
313
 
333
314
  // src/headless.ts
334
- function emit(event, data) {
335
- process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
336
- }
337
315
  async function startSession(cwd, opts, state, shutdown) {
338
316
  const bindAddress = opts.bindAddress ?? "127.0.0.1";
339
317
  const appConfig = detectAppConfig(cwd);
340
318
  if (!appConfig) {
341
- emit("config-error", { message: "No valid mindstudio.json found in " + cwd });
319
+ emitEvent("config-error", { message: "No valid mindstudio.json found in " + cwd });
342
320
  return false;
343
321
  }
344
322
  if (!appConfig.appId) {
345
- emit("config-error", { message: 'Missing "appId" in mindstudio.json' });
323
+ emitEvent("config-error", { message: 'Missing "appId" in mindstudio.json' });
346
324
  return false;
347
325
  }
348
326
  state.appConfig = appConfig;
@@ -351,7 +329,7 @@ async function startSession(cwd, opts, state, shutdown) {
351
329
  const webConfig = getWebInterfaceConfig(appConfig, cwd);
352
330
  devPort = webConfig?.devPort ?? null;
353
331
  }
354
- emit("session-starting", { appId: appConfig.appId, name: appConfig.name });
332
+ emitEvent("session-starting", { appId: appConfig.appId, name: appConfig.name });
355
333
  try {
356
334
  const branch = detectGitBranch();
357
335
  const runner = new DevRunner(appConfig.appId, cwd, {
@@ -368,7 +346,7 @@ async function startSession(cwd, opts, state, shutdown) {
368
346
  if (tableSources.length > 0) {
369
347
  const syncResult = await syncSchema(appConfig.appId, session.sessionId, tableSources);
370
348
  session.databases = syncResult.databases;
371
- emit("schema-sync-completed", {
349
+ emitEvent("schema-sync-completed", {
372
350
  created: syncResult.created,
373
351
  altered: syncResult.altered,
374
352
  errors: syncResult.errors
@@ -379,29 +357,32 @@ async function startSession(cwd, opts, state, shutdown) {
379
357
  });
380
358
  }
381
359
  } catch (err) {
382
- emit("schema-sync-completed", {
360
+ emitEvent("schema-sync-completed", {
383
361
  created: [],
384
362
  altered: [],
385
363
  errors: [err instanceof Error ? err.message : "Schema sync failed"]
386
364
  });
387
365
  }
388
366
  }
389
- let proxyPort = null;
390
367
  if (devPort !== null && session.clientContext) {
391
- const proxy = new DevProxy(devPort, session.clientContext, bindAddress, opts.browserAgentUrl);
392
- const preferred = opts.proxyPort ?? stablePort(appConfig.appId);
393
- proxyPort = await proxy.start(preferred);
394
- runner.setProxyUrl(`http://${bindAddress === "0.0.0.0" ? "localhost" : bindAddress}:${proxyPort}`);
395
- runner.setProxy(proxy);
396
- state.proxy = proxy;
368
+ if (state.proxy) {
369
+ state.proxy.updateClientContext(session.clientContext);
370
+ } else {
371
+ const proxy = new DevProxy(devPort, session.clientContext, bindAddress, opts.browserAgentUrl);
372
+ const preferred = opts.proxyPort ?? stablePort(appConfig.appId);
373
+ const proxyPort = await proxy.start(preferred);
374
+ state.proxy = proxy;
375
+ state.proxyPort = proxyPort;
376
+ }
377
+ runner.setProxyUrl(`http://${bindAddress === "0.0.0.0" ? "localhost" : bindAddress}:${state.proxyPort}`);
378
+ runner.setProxy(state.proxy);
397
379
  }
398
- state.proxyPort = proxyPort;
399
- emit("session-started", {
380
+ emitEvent("session-started", {
400
381
  sessionId: session.sessionId,
401
382
  releaseId: session.releaseId,
402
383
  branch: session.branch,
403
- proxyPort,
404
- proxyUrl: proxyPort ? `http://${bindAddress === "0.0.0.0" ? "localhost" : bindAddress}:${proxyPort}/` : null,
384
+ proxyPort: state.proxyPort,
385
+ proxyUrl: state.proxyPort ? `http://${bindAddress === "0.0.0.0" ? "localhost" : bindAddress}:${state.proxyPort}/` : null,
405
386
  webInterfaceUrl: session.webInterfaceUrl,
406
387
  roles: appConfig.roles.map((r) => ({ id: r.id, name: r.name ?? r.id, description: r.description })),
407
388
  scenarios: appConfig.scenarios.map((s) => ({
@@ -412,11 +393,11 @@ async function startSession(cwd, opts, state, shutdown) {
412
393
  roles: s.roles
413
394
  }))
414
395
  });
415
- state.unsubscribers.push(...subscribeDevEvents(emit, shutdown));
396
+ state.unsubscribers.push(...subscribeDevEvents(shutdown));
416
397
  setupTableWatchers(cwd, state);
417
398
  return true;
418
399
  } catch (err) {
419
- emit("config-error", {
400
+ emitEvent("config-error", {
420
401
  message: err instanceof Error ? err.message : "Failed to start session"
421
402
  });
422
403
  return false;
@@ -428,14 +409,14 @@ function setupTableWatchers(cwd, state) {
428
409
  if (!state.runner || !state.appConfig?.appId) return;
429
410
  const session = state.runner.getSession();
430
411
  if (!session) return;
431
- emit("schema-sync-started");
412
+ emitEvent("schema-sync-started");
432
413
  log.info("Table source file changed, syncing schema");
433
414
  try {
434
415
  const tableSources = readTableSources(state.appConfig, cwd);
435
416
  if (tableSources.length > 0) {
436
417
  const result = await syncSchema(state.appConfig.appId, session.sessionId, tableSources);
437
418
  session.databases = result.databases;
438
- emit("schema-sync-completed", {
419
+ emitEvent("schema-sync-completed", {
439
420
  created: result.created,
440
421
  altered: result.altered,
441
422
  errors: result.errors
@@ -448,18 +429,15 @@ function setupTableWatchers(cwd, state) {
448
429
  }
449
430
  } catch (err) {
450
431
  const message = err instanceof Error ? err.message : "Schema sync failed";
451
- emit("command-error", { message });
432
+ emitEvent("schema-sync-completed", { created: [], altered: [], errors: [message] });
452
433
  log.warn("Schema sync failed", { error: message });
453
434
  }
454
435
  });
455
436
  state.unsubscribers.push(cleanup);
456
437
  }
457
- async function teardownSession(state) {
438
+ async function teardownRunner(state) {
458
439
  for (const unsub of state.unsubscribers) unsub();
459
440
  state.unsubscribers = [];
460
- state.proxy?.stop();
461
- state.proxy = null;
462
- state.proxyPort = null;
463
441
  if (state.runner) {
464
442
  await state.runner.stop().catch(() => {
465
443
  });
@@ -468,6 +446,12 @@ async function teardownSession(state) {
468
446
  closeRequestLog();
469
447
  closeBrowserLog();
470
448
  }
449
+ async function teardownAll(state) {
450
+ await teardownRunner(state);
451
+ state.proxy?.stop();
452
+ state.proxy = null;
453
+ state.proxyPort = null;
454
+ }
471
455
  async function startHeadless(opts = {}) {
472
456
  initLoggerHeadless(opts.logLevel ?? "info");
473
457
  const cwd = opts.cwd ?? process.cwd();
@@ -496,10 +480,10 @@ async function startHeadless(opts = {}) {
496
480
  const shutdown = async () => {
497
481
  if (stopping) return;
498
482
  stopping = true;
499
- emit("session-stopping");
483
+ emitEvent("session-stopping");
500
484
  cleanupConfigWatcher?.();
501
- await teardownSession(state);
502
- emit("session-stopped");
485
+ await teardownAll(state);
486
+ emitEvent("session-stopped");
503
487
  };
504
488
  process.on("SIGTERM", () => {
505
489
  shutdown().then(() => process.exit(0));
@@ -511,18 +495,17 @@ async function startHeadless(opts = {}) {
511
495
  if (!ok) {
512
496
  process.exit(1);
513
497
  }
514
- setupStdinCommands(state, cwd, emit);
498
+ setupStdinCommands(state, cwd);
515
499
  cleanupConfigWatcher = watchConfigFile(cwd, async () => {
516
500
  if (stopping || restarting) return;
517
501
  restarting = true;
518
502
  try {
519
503
  log.info("mindstudio.json changed, restarting dev session");
520
- emit("config-changed");
521
- await teardownSession(state);
504
+ emitEvent("config-changed");
505
+ await teardownRunner(state);
522
506
  const ok2 = await startSession(cwd, opts, state, shutdown);
523
507
  if (ok2 && state.proxy) {
524
- state.proxy.dispatchBrowserCommand([{ command: "reload" }]).catch(() => {
525
- });
508
+ state.proxy.broadcastToClients("reload");
526
509
  }
527
510
  } finally {
528
511
  restarting = false;
@@ -535,4 +518,4 @@ async function startHeadless(opts = {}) {
535
518
  export {
536
519
  startHeadless
537
520
  };
538
- //# sourceMappingURL=chunk-YTHFWJU6.js.map
521
+ //# sourceMappingURL=chunk-ALEWMAQ4.js.map