@mindstudio-ai/local-model-tunnel 0.5.8 → 0.5.10

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.
@@ -15,12 +15,11 @@ import {
15
15
  readTableSources,
16
16
  stablePort,
17
17
  syncSchema,
18
+ watchConfigFile,
18
19
  watchTableFiles
19
- } from "./chunk-C3JPRLSS.js";
20
+ } from "./chunk-C4IDR7TD.js";
20
21
 
21
22
  // src/headless.ts
22
- import { watch } from "fs";
23
- import { join } from "path";
24
23
  function emit(event, data) {
25
24
  process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
26
25
  }
@@ -258,16 +257,14 @@ async function startHeadless(opts = {}) {
258
257
  proxyPort: null,
259
258
  unsubscribers: []
260
259
  };
261
- let restartTimer;
262
260
  let restarting = false;
263
- let watcher;
261
+ let cleanupConfigWatcher;
264
262
  let stopping = false;
265
263
  const shutdown = async () => {
266
264
  if (stopping) return;
267
265
  stopping = true;
268
266
  emit("session-stopping");
269
- clearTimeout(restartTimer);
270
- watcher?.close();
267
+ cleanupConfigWatcher?.();
271
268
  await teardownSession(state);
272
269
  emit("session-stopped");
273
270
  };
@@ -282,25 +279,18 @@ async function startHeadless(opts = {}) {
282
279
  emit("error", { message: "No valid mindstudio.json found in " + cwd });
283
280
  }
284
281
  setupStdinCommands(state, cwd);
285
- try {
286
- const configPath = join(cwd, "mindstudio.json");
287
- watcher = watch(configPath, () => {
288
- clearTimeout(restartTimer);
289
- restartTimer = setTimeout(async () => {
290
- if (stopping || restarting) return;
291
- restarting = true;
292
- try {
293
- log.info("headless Config changed, restarting session");
294
- emit("config-changed");
295
- await teardownSession(state);
296
- await startSession(cwd, opts, state, shutdown);
297
- } finally {
298
- restarting = false;
299
- }
300
- }, 500);
301
- });
302
- } catch {
303
- }
282
+ cleanupConfigWatcher = watchConfigFile(cwd, async () => {
283
+ if (stopping || restarting) return;
284
+ restarting = true;
285
+ try {
286
+ log.info("headless Config changed, restarting session");
287
+ emit("config-changed");
288
+ await teardownSession(state);
289
+ await startSession(cwd, opts, state, shutdown);
290
+ } finally {
291
+ restarting = false;
292
+ }
293
+ });
304
294
  await new Promise(() => {
305
295
  });
306
296
  }
@@ -375,4 +365,4 @@ async function handleStdinCommand(cmd, state, cwd) {
375
365
  export {
376
366
  startHeadless
377
367
  };
378
- //# sourceMappingURL=chunk-WFQXIMTS.js.map
368
+ //# sourceMappingURL=chunk-ISB2N2SP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/headless.ts"],"sourcesContent":["/**\n * Headless Dev Mode\n *\n * Runs the MindStudio dev tunnel without a TUI. Designed for programmatic\n * control by a parent process (e.g., a sandbox C&C server or CI pipeline).\n *\n * Outputs structured JSON events to stdout (one per line, newline-delimited).\n * The parent process reads these to track session state, method execution,\n * errors, and connection health.\n *\n * Does NOT start a dev server — the parent process manages that separately.\n * The tunnel just needs to know which port to proxy to.\n *\n * ## JSON Event Protocol\n *\n * Every line written to stdout is a JSON object with an `event` field:\n *\n * | Event | When | Key Fields |\n * |------------------------|-----------------------------------------|-----------------------------------------------|\n * | `session-starting` | Session initializing | `appId`, `name` |\n * | `session-started` | Platform session active, proxy running | `sessionId`, `branch`, `proxyPort`, `proxyUrl`|\n * | `session-stopping` | Graceful shutdown initiated | |\n * | `session-stopped` | All resources cleaned up | |\n * | `session-expired` | Platform expired the dev session | |\n * | `method-started` | Method execution request received | `id`, `method` |\n * | `method-completed` | Method execution finished | `id`, `success`, `duration`, `error?` |\n * | `scenario-started` | Scenario execution started | `id`, `name` |\n * | `scenario-completed` | Scenario execution finished | `id`, `success`, `duration`, `roles`, `error?`|\n * | `schema-sync-started` | Table file changed, syncing schema | |\n * | `schema-sync-completed`| Schema synced to platform | `created`, `altered`, `errors` |\n * | `impersonation-changed`| Role override set or cleared | `roles` |\n * | `connection-lost` | Lost connection, retrying with backoff | `message` |\n * | `connection-restored` | Reconnected after connection loss | |\n * | `config-changed` | mindstudio.json changed, restarting | |\n * | `config-error` | Config invalid (non-fatal) | `message` |\n * | `command-error` | Stdin command failed (non-fatal) | `message` |\n * | `error` | Fatal startup error | `message` |\n *\n * ## Usage\n *\n * CLI:\n * ```bash\n * mindstudio-local --headless --port 5173 --bind 0.0.0.0\n * ```\n *\n * Programmatic:\n * ```typescript\n * import { startHeadless } from '@mindstudio-ai/local-model-tunnel';\n *\n * await startHeadless({\n * cwd: '/path/to/project',\n * devPort: 5173,\n * bindAddress: '0.0.0.0',\n * });\n * ```\n *\n * @module\n */\n\nimport { DevRunner } from './dev/runner';\nimport { DevProxy } from './dev/proxy';\nimport { devRequestEvents } from './dev/events';\nimport { syncSchema } from './dev/api';\nimport {\n detectAppConfig,\n getWebInterfaceConfig,\n readTableSources,\n} from './dev/app-config';\nimport type { AppConfig } from './dev/types';\nimport {\n getApiKey,\n getApiBaseUrl,\n getUserId,\n getEnvironment,\n getConfigPath,\n} from './config';\nimport { initLoggerHeadless, log, type LogLevel } from './dev/logger';\nimport { stablePort, detectGitBranch } from './dev/utils';\nimport { watchTableFiles } from './dev/table-watcher';\nimport { watchConfigFile } from './dev/config-watcher';\n\n/**\n * Options for headless dev mode.\n */\nexport interface HeadlessOptions {\n /** Working directory containing mindstudio.json. Defaults to process.cwd(). */\n cwd?: string;\n /** Port the dev server is running on. If omitted, reads from web.json. If neither, proxy is skipped. */\n devPort?: number;\n /** Preferred port for the local proxy. Defaults to a stable port derived from the app ID. */\n proxyPort?: number;\n /** Bind address for the proxy server. Use '0.0.0.0' for hosted sandboxes. Defaults to '127.0.0.1'. */\n bindAddress?: string;\n /** Log level for stderr output. Defaults to 'info'. */\n logLevel?: LogLevel;\n}\n\n/** Mutable state shared across the session lifecycle, stdin commands, and file watcher. */\ninterface SessionState {\n runner: DevRunner | null;\n proxy: DevProxy | null;\n appConfig: AppConfig | null;\n proxyPort: number | null;\n unsubscribers: Array<() => void>;\n}\n\n/** Write a JSON event to stdout. */\nfunction emit(event: string, data?: Record<string, unknown>): void {\n process.stdout.write(JSON.stringify({ event, ...data }) + '\\n');\n}\n\n/**\n * Start a dev session: read config, start runner, sync schema, start proxy,\n * subscribe to events. Returns true on success, false on config/startup error\n * (non-fatal — caller can retry on next config change).\n */\nasync function startSession(\n cwd: string,\n opts: HeadlessOptions,\n state: SessionState,\n shutdown: () => Promise<void>,\n): Promise<boolean> {\n const bindAddress = opts.bindAddress ?? '127.0.0.1';\n\n // Read fresh config\n const appConfig = detectAppConfig(cwd);\n if (!appConfig) {\n emit('config-error', {\n message: 'No valid mindstudio.json found in ' + cwd,\n });\n return false;\n }\n\n if (!appConfig.appId) {\n emit('config-error', { message: 'Missing \"appId\" in mindstudio.json' });\n return false;\n }\n\n state.appConfig = appConfig;\n\n // Resolve dev port\n let devPort = opts.devPort ?? null;\n if (devPort === null) {\n const webConfig = getWebInterfaceConfig(appConfig, cwd);\n devPort = webConfig?.devPort ?? null;\n }\n\n emit('session-starting', { appId: appConfig.appId, name: appConfig.name });\n\n try {\n // Start platform session\n const branch = detectGitBranch();\n const runner = new DevRunner(appConfig.appId, cwd, {\n branch,\n methods: appConfig.methods.map((m) => ({\n id: m.id,\n export: m.export,\n path: m.path,\n })),\n });\n const session = await runner.start();\n state.runner = runner;\n\n // Sync schema\n if (appConfig.tables.length > 0) {\n try {\n const tableSources = readTableSources(appConfig, cwd);\n if (tableSources.length > 0) {\n const syncResult = await syncSchema(\n appConfig.appId,\n session.sessionId,\n tableSources,\n );\n session.databases = syncResult.databases;\n emit('schema-sync-completed', {\n created: syncResult.created,\n altered: syncResult.altered,\n errors: syncResult.errors,\n });\n }\n } catch (err) {\n emit('schema-sync-completed', {\n created: [],\n altered: [],\n errors: [err instanceof Error ? err.message : 'Schema sync failed'],\n });\n }\n }\n\n // Start proxy — sits in front of the dev server, injects __MINDSTUDIO__.\n // Only started if we have a dev server port and the platform returned clientContext.\n // In headless mode we don't start the dev server (caller manages it), but we\n // do start the proxy so the preview URL works.\n let proxyPort: number | null = null;\n if (devPort !== null && session.clientContext) {\n const proxy = new DevProxy(devPort, session.clientContext, bindAddress);\n const preferred = opts.proxyPort ?? stablePort(appConfig.appId);\n proxyPort = await proxy.start(preferred);\n runner.setProxyUrl(\n `http://${bindAddress === '0.0.0.0' ? 'localhost' : bindAddress}:${proxyPort}`,\n );\n runner.setProxy(proxy);\n state.proxy = proxy;\n }\n state.proxyPort = proxyPort;\n\n emit('session-started', {\n sessionId: session.sessionId,\n releaseId: session.releaseId,\n branch: session.branch,\n proxyPort,\n proxyUrl: proxyPort\n ? `http://${bindAddress === '0.0.0.0' ? 'localhost' : bindAddress}:${proxyPort}/`\n : null,\n webInterfaceUrl: session.webInterfaceUrl,\n roles: appConfig.roles.map((r) => ({\n id: r.id,\n name: r.name ?? r.id,\n description: r.description,\n })),\n scenarios: appConfig.scenarios.map((s) => ({\n id: s.id,\n name: s.name ?? s.export,\n description: s.description,\n path: s.path,\n roles: s.roles,\n })),\n });\n\n // Subscribe to events and relay as JSON.\n // Store unsubscribe functions so we can clean up on restart.\n const unsubs = state.unsubscribers;\n\n unsubs.push(\n devRequestEvents.onStart((event) => {\n emit('method-started', { id: event.id, method: event.method });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onComplete((event) => {\n emit('method-completed', {\n id: event.id,\n success: event.success,\n duration: event.duration,\n ...(event.error ? { error: event.error } : {}),\n });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onConnectionWarning((message) => {\n emit('connection-lost', { message });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onConnectionRestored(() => {\n emit('connection-restored');\n }),\n );\n\n unsubs.push(\n devRequestEvents.onSessionExpired(() => {\n emit('session-expired');\n shutdown().then(() => process.exit(1));\n }),\n );\n\n unsubs.push(\n devRequestEvents.onAuthRefreshStart((url) => {\n emit('auth-refresh-start', { url });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onAuthRefreshSuccess(() => {\n emit('auth-refresh-success');\n }),\n );\n\n unsubs.push(\n devRequestEvents.onAuthRefreshFailed(() => {\n emit('auth-refresh-failed');\n }),\n );\n\n unsubs.push(\n devRequestEvents.onImpersonate((event) => {\n emit('impersonation-changed', { roles: event.roles });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onScenarioStart((event) => {\n emit('scenario-started', { id: event.id, name: event.name });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onScenarioComplete((event) => {\n emit('scenario-completed', {\n id: event.id,\n success: event.success,\n duration: event.duration,\n roles: event.roles,\n ...(event.error ? { error: event.error } : {}),\n });\n }),\n );\n\n // Watch table source files for changes — auto-sync without session restart\n setupTableWatchers(cwd, state);\n\n return true;\n } catch (err) {\n emit('config-error', {\n message: err instanceof Error ? err.message : 'Failed to start session',\n });\n return false;\n }\n}\n\n/** Set up table file watchers that auto-sync schema on change. */\nfunction setupTableWatchers(cwd: string, state: SessionState): void {\n if (!state.appConfig || state.appConfig.tables.length === 0) return;\n\n const cleanup = watchTableFiles(state.appConfig.tables, cwd, async () => {\n if (!state.runner || !state.appConfig?.appId) return;\n const session = state.runner.getSession();\n if (!session) return;\n\n emit('schema-sync-started');\n log.info('headless Table file changed, syncing schema');\n\n try {\n const tableSources = readTableSources(state.appConfig, cwd);\n if (tableSources.length > 0) {\n const result = await syncSchema(\n state.appConfig.appId,\n session.sessionId,\n tableSources,\n );\n session.databases = result.databases;\n emit('schema-sync-completed', {\n created: result.created,\n altered: result.altered,\n errors: result.errors,\n });\n log.info('headless Schema synced', {\n created: result.created,\n altered: result.altered,\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Schema sync failed';\n emit('command-error', { message });\n log.warn('headless Schema sync failed', { error: message });\n }\n });\n\n state.unsubscribers.push(cleanup);\n}\n\n/** Tear down the current session: unsubscribe events, stop proxy, stop runner. */\nasync function teardownSession(state: SessionState): Promise<void> {\n for (const unsub of state.unsubscribers) unsub();\n state.unsubscribers = [];\n\n state.proxy?.stop();\n state.proxy = null;\n state.proxyPort = null;\n\n if (state.runner) {\n await state.runner.stop().catch(() => {});\n state.runner = null;\n }\n}\n\n/**\n * Start the dev tunnel in headless mode.\n *\n * Reads mindstudio.json, starts a platform session, syncs schema,\n * starts the local proxy, and enters the poll loop. Outputs JSON\n * events to stdout. Does not return until shutdown (SIGTERM/SIGINT).\n *\n * Watches mindstudio.json for changes and automatically restarts the\n * session when the config is updated (same behavior as the TUI).\n *\n * Does NOT start a dev server — the caller is responsible for that.\n *\n * @param opts - Configuration options\n *\n * @example\n * ```typescript\n * // From a C&C server — spawn and read events\n * import { startHeadless } from '@mindstudio-ai/local-model-tunnel';\n *\n * await startHeadless({\n * cwd: '/workspace/my-app',\n * devPort: 5173,\n * bindAddress: '0.0.0.0',\n * });\n * ```\n */\nexport async function startHeadless(opts: HeadlessOptions = {}): Promise<void> {\n initLoggerHeadless(opts.logLevel ?? 'info');\n\n const cwd = opts.cwd ?? process.cwd();\n\n // Log auth config so sandbox operators can diagnose issues\n const apiKey = getApiKey();\n const userId = getUserId();\n log.info('headless Auth config', {\n configPath: getConfigPath(),\n environment: getEnvironment(),\n apiBaseUrl: getApiBaseUrl(),\n hasApiKey: !!apiKey,\n apiKeyPrefix: apiKey ? apiKey.slice(0, 8) + '...' : null,\n hasUserId: !!userId,\n userId: userId ?? null,\n cwd,\n });\n\n const state: SessionState = {\n runner: null,\n proxy: null,\n appConfig: null,\n proxyPort: null,\n unsubscribers: [],\n };\n\n // File watcher state\n let restarting = false;\n let cleanupConfigWatcher: (() => void) | undefined;\n\n // Graceful shutdown\n let stopping = false;\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n emit('session-stopping');\n cleanupConfigWatcher?.();\n await teardownSession(state);\n emit('session-stopped');\n };\n\n process.on('SIGTERM', () => {\n shutdown().then(() => process.exit(0));\n });\n process.on('SIGINT', () => {\n shutdown().then(() => process.exit(0));\n });\n\n // Initial session start\n const ok = await startSession(cwd, opts, state, shutdown);\n if (!ok && !state.appConfig) {\n // No valid config at all on first try — emit fatal error.\n // The watcher below will still start if the file exists, so the\n // process stays alive to retry on config fix.\n emit('error', { message: 'No valid mindstudio.json found in ' + cwd });\n }\n\n // Stdin command loop — reads from state so it always sees current runner/config\n setupStdinCommands(state, cwd);\n\n // Watch mindstudio.json for changes — restart session on edit\n cleanupConfigWatcher = watchConfigFile(cwd, async () => {\n if (stopping || restarting) return;\n restarting = true;\n try {\n log.info('headless Config changed, restarting session');\n emit('config-changed');\n await teardownSession(state);\n await startSession(cwd, opts, state, shutdown);\n } finally {\n restarting = false;\n }\n });\n\n // Keep the process alive — the poll loop runs in DevRunner\n await new Promise<void>(() => {});\n}\n\n/**\n * Read NDJSON commands from stdin and dispatch them.\n * Uses the shared state object so commands always reference the current session.\n */\nfunction setupStdinCommands(state: SessionState, cwd: string): void {\n if (!process.stdin.readable) return;\n\n let buffer = '';\n process.stdin.setEncoding('utf-8');\n process.stdin.on('data', (chunk: string) => {\n buffer += chunk;\n let idx: number;\n while ((idx = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, idx).trim();\n buffer = buffer.slice(idx + 1);\n if (!line) continue;\n\n try {\n const cmd = JSON.parse(line) as {\n action: string;\n [key: string]: unknown;\n };\n handleStdinCommand(cmd, state, cwd);\n } catch {\n emit('command-error', {\n message: `Invalid JSON on stdin: ${line.slice(0, 100)}`,\n });\n }\n }\n });\n}\n\nasync function handleStdinCommand(\n cmd: { action: string; [key: string]: unknown },\n state: SessionState,\n cwd: string,\n): Promise<void> {\n switch (cmd.action) {\n case 'run-scenario': {\n if (!state.runner) {\n emit('command-error', { message: 'No active session' });\n return;\n }\n const freshConfig = detectAppConfig(cwd) ?? state.appConfig;\n const scenario = freshConfig?.scenarios.find(\n (s) => s.id === cmd.scenarioId,\n );\n if (!scenario) {\n emit('command-error', {\n message: `Unknown scenario: ${cmd.scenarioId}`,\n });\n return;\n }\n // Runner emits scenario-start/complete events which are already relayed\n await state.runner.runScenario(scenario);\n break;\n }\n\n case 'impersonate': {\n if (!state.runner) {\n emit('command-error', { message: 'No active session' });\n return;\n }\n const roles = cmd.roles as string[];\n if (!Array.isArray(roles)) {\n emit('command-error', { message: 'impersonate requires roles array' });\n return;\n }\n await state.runner.setImpersonation(roles);\n break;\n }\n\n case 'clear-impersonation': {\n if (!state.runner) {\n emit('command-error', { message: 'No active session' });\n return;\n }\n await state.runner.clearImpersonation();\n break;\n }\n\n default:\n emit('command-error', { message: `Unknown action: ${cmd.action}` });\n }\n}\n"],"mappings":"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","names":[]}
@@ -7,7 +7,7 @@ import {
7
7
  setProviderInstallPath,
8
8
  submitProgress,
9
9
  submitResult
10
- } from "./chunk-C3JPRLSS.js";
10
+ } from "./chunk-C4IDR7TD.js";
11
11
 
12
12
  // src/providers/ollama/index.ts
13
13
  import { Ollama } from "ollama";
@@ -1395,4 +1395,4 @@ export {
1395
1395
  requestEvents,
1396
1396
  TunnelRunner
1397
1397
  };
1398
- //# sourceMappingURL=chunk-QALGC7T7.js.map
1398
+ //# sourceMappingURL=chunk-WVEIWPL3.js.map
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ async function main() {
17
17
  logLevel: getFlag("--log-level") ?? void 0
18
18
  });
19
19
  } else {
20
- const { startTUI } = await import("./tui-4PJCFILV.js");
20
+ const { startTUI } = await import("./tui-4CPF4HWT.js");
21
21
  await startTUI();
22
22
  }
23
23
  process.exit(0);
package/dist/headless.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  startHeadless
3
- } from "./chunk-WFQXIMTS.js";
4
- import "./chunk-C3JPRLSS.js";
3
+ } from "./chunk-ISB2N2SP.js";
4
+ import "./chunk-C4IDR7TD.js";
5
5
  export {
6
6
  startHeadless
7
7
  };
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  startHeadless
3
- } from "./chunk-WFQXIMTS.js";
3
+ } from "./chunk-ISB2N2SP.js";
4
4
  import {
5
5
  TunnelRunner
6
- } from "./chunk-QALGC7T7.js";
7
- import "./chunk-C3JPRLSS.js";
6
+ } from "./chunk-WVEIWPL3.js";
7
+ import "./chunk-C4IDR7TD.js";
8
8
  export {
9
9
  TunnelRunner,
10
10
  startHeadless
@@ -3,7 +3,7 @@ import {
3
3
  detectAllProviderStatuses,
4
4
  discoverAllModelsWithParameters,
5
5
  requestEvents
6
- } from "./chunk-QALGC7T7.js";
6
+ } from "./chunk-WVEIWPL3.js";
7
7
  import {
8
8
  DevProxy,
9
9
  DevRunner,
@@ -34,8 +34,9 @@ import {
34
34
  syncModels,
35
35
  syncSchema,
36
36
  verifyApiKey,
37
+ watchConfigFile,
37
38
  watchTableFiles
38
- } from "./chunk-C3JPRLSS.js";
39
+ } from "./chunk-C4IDR7TD.js";
39
40
 
40
41
  // src/tui/index.tsx
41
42
  import { render } from "ink";
@@ -150,7 +151,7 @@ function Header({
150
151
  /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "MindStudio Local Tunnel" }),
151
152
  compact && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
152
153
  " v",
153
- "0.5.8"
154
+ "0.5.10"
154
155
  ] }),
155
156
  environment !== "prod" && /* @__PURE__ */ jsxs(Fragment, { children: [
156
157
  /* @__PURE__ */ jsx(Text, { children: " " }),
@@ -168,7 +169,7 @@ function Header({
168
169
  ] }),
169
170
  !compact && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
170
171
  "v",
171
- "0.5.8"
172
+ "0.5.10"
172
173
  ] })
173
174
  ] })
174
175
  ]
@@ -2604,8 +2605,6 @@ function TabBar({ tabs, activeTab }) {
2604
2605
  // src/tui/dev/hooks/useDevSession.ts
2605
2606
  import { useState as useState18, useEffect as useEffect17, useRef as useRef10, useCallback as useCallback11 } from "react";
2606
2607
  import { spawn as spawn3 } from "child_process";
2607
- import { watch } from "fs";
2608
- import { join } from "path";
2609
2608
 
2610
2609
  // src/tui/dev/hooks/useDevServer.ts
2611
2610
  import { useState as useState17, useCallback as useCallback10, useRef as useRef9, useEffect as useEffect16 } from "react";
@@ -2829,38 +2828,26 @@ function useDevSession(appConfig) {
2829
2828
  unsubAuthFailed();
2830
2829
  };
2831
2830
  }, []);
2832
- const restartTimerRef = useRef10(void 0);
2833
2831
  useEffect17(() => {
2834
- let watcher;
2835
- try {
2836
- const configPath = join(process.cwd(), "mindstudio.json");
2837
- watcher = watch(configPath, () => {
2838
- clearTimeout(restartTimerRef.current);
2839
- restartTimerRef.current = setTimeout(async () => {
2840
- if (!mountedRef.current || phase !== "running") return;
2841
- cleanupTableWatchers();
2842
- proxyRef.current?.stop();
2843
- proxyRef.current = null;
2844
- devServer.stop();
2845
- if (runnerRef.current) {
2846
- await runnerRef.current.stop().catch(() => {
2847
- });
2848
- runnerRef.current = null;
2849
- }
2850
- if (mountedRef.current) {
2851
- setSession(null);
2852
- setProxyPort(null);
2853
- setSyncResult(null);
2854
- setPhase("ready");
2855
- }
2856
- }, 500);
2857
- });
2858
- } catch {
2859
- }
2860
- return () => {
2861
- clearTimeout(restartTimerRef.current);
2862
- watcher?.close();
2863
- };
2832
+ const cleanup = watchConfigFile(process.cwd(), async () => {
2833
+ if (!mountedRef.current || phase !== "running") return;
2834
+ cleanupTableWatchers();
2835
+ proxyRef.current?.stop();
2836
+ proxyRef.current = null;
2837
+ devServer.stop();
2838
+ if (runnerRef.current) {
2839
+ await runnerRef.current.stop().catch(() => {
2840
+ });
2841
+ runnerRef.current = null;
2842
+ }
2843
+ if (mountedRef.current) {
2844
+ setSession(null);
2845
+ setProxyPort(null);
2846
+ setSyncResult(null);
2847
+ setPhase("ready");
2848
+ }
2849
+ });
2850
+ return cleanup;
2864
2851
  }, [phase, devServer]);
2865
2852
  useEffect17(() => {
2866
2853
  mountedRef.current = true;
@@ -3803,7 +3790,7 @@ function getInstallMethod() {
3803
3790
  return "npm";
3804
3791
  }
3805
3792
  function getCurrentVersion() {
3806
- return "0.5.8";
3793
+ return "0.5.10";
3807
3794
  }
3808
3795
  async function fetchLatestVersion() {
3809
3796
  try {
@@ -4045,4 +4032,4 @@ async function startTUI() {
4045
4032
  export {
4046
4033
  startTUI
4047
4034
  };
4048
- //# sourceMappingURL=tui-4PJCFILV.js.map
4035
+ //# sourceMappingURL=tui-4CPF4HWT.js.map