@mindstudio-ai/local-model-tunnel 0.5.7 → 0.5.9

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.
@@ -1,8 +1,17 @@
1
1
  import {
2
2
  TunnelRunner,
3
- deleteLocalInterfacePath,
4
3
  detectAllProviderStatuses,
5
4
  discoverAllModelsWithParameters,
5
+ requestEvents
6
+ } from "./chunk-253MU7UA.js";
7
+ import {
8
+ DevProxy,
9
+ DevRunner,
10
+ deleteLocalInterfacePath,
11
+ detectAppConfig,
12
+ detectGitBranch,
13
+ devRequestEvents,
14
+ findDirsNeedingInstall,
6
15
  getApiBaseUrl,
7
16
  getApiKey,
8
17
  getConfigPath,
@@ -12,15 +21,22 @@ import {
12
21
  getLocalInterfacesDir,
13
22
  getSyncedModels,
14
23
  getUserId,
24
+ getWebInterfaceConfig,
25
+ getWebProjectDir,
26
+ initLoggerInteractive,
15
27
  pollDeviceAuth,
28
+ readTableSources,
16
29
  requestDeviceAuth,
17
- requestEvents,
18
30
  setApiKey,
19
31
  setLocalInterfacePath,
20
32
  setUserId,
33
+ stablePort,
21
34
  syncModels,
22
- verifyApiKey
23
- } from "./chunk-KLOTDVWL.js";
35
+ syncSchema,
36
+ verifyApiKey,
37
+ watchConfigFile,
38
+ watchTableFiles
39
+ } from "./chunk-4CMGJFH3.js";
24
40
 
25
41
  // src/tui/index.tsx
26
42
  import { render } from "ink";
@@ -30,8 +46,8 @@ import { get as httpsGet } from "https";
30
46
  import { get as httpGet } from "http";
31
47
 
32
48
  // src/tui/App.tsx
33
- import { useEffect as useEffect16, useCallback as useCallback10, useState as useState16, useRef as useRef9 } from "react";
34
- import { Box as Box10, useApp, useStdout as useStdout7 } from "ink";
49
+ import { useEffect as useEffect20, useCallback as useCallback12, useState as useState21, useRef as useRef12 } from "react";
50
+ import { Box as Box14, useApp, useStdout as useStdout8 } from "ink";
35
51
 
36
52
  // src/tui/components/Header.tsx
37
53
  import { useState, useEffect, useMemo } from "react";
@@ -135,7 +151,7 @@ function Header({
135
151
  /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "MindStudio Local Tunnel" }),
136
152
  compact && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
137
153
  " v",
138
- "0.5.7"
154
+ "0.5.9"
139
155
  ] }),
140
156
  environment !== "prod" && /* @__PURE__ */ jsxs(Fragment, { children: [
141
157
  /* @__PURE__ */ jsx(Text, { children: " " }),
@@ -153,7 +169,7 @@ function Header({
153
169
  ] }),
154
170
  !compact && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
155
171
  "v",
156
- "0.5.7"
172
+ "0.5.9"
157
173
  ] })
158
174
  ] })
159
175
  ]
@@ -161,150 +177,11 @@ function Header({
161
177
  );
162
178
  }
163
179
 
164
- // src/tui/components/NavigationMenu.tsx
165
- import { useState as useState2, useEffect as useEffect2 } from "react";
166
- import { Box as Box2, Text as Text2, useInput, useStdout } from "ink";
167
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
168
- function NavigationMenu({
169
- items,
170
- onSelect,
171
- title
172
- }) {
173
- const { stdout } = useStdout();
174
- const compact = (stdout?.rows ?? 24) < 40;
175
- const getDefaultIndex = () => {
176
- const firstIdx = items.findIndex((i) => !i.disabled && !i.isSeparator);
177
- return firstIdx >= 0 ? firstIdx : 0;
178
- };
179
- const [selectedIndex, setSelectedIndex] = useState2(getDefaultIndex);
180
- useEffect2(() => {
181
- setSelectedIndex(getDefaultIndex());
182
- }, [items]);
183
- const selectableItems = items.filter((i) => !i.isSeparator);
184
- const findNextEnabled = (from, direction) => {
185
- let idx = from;
186
- for (let i = 0; i < items.length; i++) {
187
- idx = (idx + direction + items.length) % items.length;
188
- if (!items[idx].disabled && !items[idx].isSeparator) return idx;
189
- }
190
- return from;
191
- };
192
- useInput((input, key) => {
193
- if (input === "q" || key.escape) {
194
- const backItem = items.find((i) => i.id === "back");
195
- if (backItem) {
196
- onSelect("back");
197
- } else if (input === "q") {
198
- onSelect("quit");
199
- }
200
- return;
201
- }
202
- if (key.upArrow || compact && key.leftArrow) {
203
- setSelectedIndex((prev) => findNextEnabled(prev, -1));
204
- } else if (key.downArrow || compact && key.rightArrow) {
205
- setSelectedIndex((prev) => findNextEnabled(prev, 1));
206
- } else if (key.return) {
207
- const item = items[selectedIndex];
208
- if (item && !item.disabled) {
209
- onSelect(item.id);
210
- }
211
- }
212
- });
213
- const hasBack = items.some((i) => i.id === "back");
214
- if (compact) {
215
- const selectedItem = items[selectedIndex];
216
- return /* @__PURE__ */ jsx2(
217
- Box2,
218
- {
219
- flexDirection: "column",
220
- paddingX: 1,
221
- borderStyle: "single",
222
- borderTop: true,
223
- borderBottom: false,
224
- borderLeft: false,
225
- borderRight: false,
226
- borderColor: "gray",
227
- children: /* @__PURE__ */ jsx2(Box2, { height: 1, overflow: "hidden", gap: 1, children: items.map((item, index) => {
228
- if (item.isSeparator) return null;
229
- const isSelected = index === selectedIndex;
230
- return /* @__PURE__ */ jsx2(
231
- Text2,
232
- {
233
- color: item.disabled ? "gray" : isSelected ? "cyan" : "white",
234
- bold: isSelected,
235
- wrap: "truncate-end",
236
- children: isSelected ? `\u276F ${item.label}` : ` ${item.label}`
237
- },
238
- item.id
239
- );
240
- }) })
241
- }
242
- );
243
- }
244
- const separatorExtraLines = items.filter(
245
- (item, idx) => item.isSeparator && idx > 0
246
- ).length;
247
- const menuHeight = items.length + 4 + separatorExtraLines;
248
- return /* @__PURE__ */ jsxs2(
249
- Box2,
250
- {
251
- flexDirection: "column",
252
- paddingX: 1,
253
- marginBottom: 1,
254
- borderStyle: "single",
255
- borderTop: true,
256
- borderBottom: false,
257
- borderLeft: false,
258
- borderRight: false,
259
- borderColor: "gray",
260
- children: [
261
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: title ?? "Actions" }) }),
262
- /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: items.map((item, index) => {
263
- if (item.isSeparator) {
264
- return /* @__PURE__ */ jsx2(Box2, { marginTop: index > 0 ? 1 : 0, children: item.label ? /* @__PURE__ */ jsx2(Text2, { bold: true, color: item.color ?? "gray", wrap: "truncate-end", children: item.label }) : null }, item.id);
265
- }
266
- const isSelected = index === selectedIndex;
267
- const prefix = isSelected ? "\u276F" : " ";
268
- if (item.disabled) {
269
- return /* @__PURE__ */ jsx2(Box2, { height: 1, overflow: "hidden", children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
270
- prefix,
271
- " ",
272
- item.label,
273
- item.disabledReason ? ` (${item.disabledReason})` : ""
274
- ] }) }, item.id);
275
- }
276
- return /* @__PURE__ */ jsxs2(Box2, { height: 1, overflow: "hidden", children: [
277
- /* @__PURE__ */ jsxs2(
278
- Text2,
279
- {
280
- color: isSelected ? "cyan" : "white",
281
- bold: isSelected,
282
- wrap: "truncate-end",
283
- children: [
284
- prefix,
285
- " ",
286
- item.label
287
- ]
288
- }
289
- ),
290
- isSelected && /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
291
- " ",
292
- "- ",
293
- item.description
294
- ] })
295
- ] }, item.id);
296
- }) }),
297
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", wrap: "truncate-end", children: hasBack ? "Up/Down Navigate \u2022 Enter Select \u2022 q/Esc Back" : "Up/Down Navigate \u2022 Enter Select \u2022 q Quit" }) })
298
- ]
299
- }
300
- );
301
- }
302
-
303
180
  // src/tui/hooks/useConnection.ts
304
- import { useState as useState3, useEffect as useEffect3, useCallback } from "react";
181
+ import { useState as useState2, useEffect as useEffect2, useCallback } from "react";
305
182
  function useConnection() {
306
- const [status, setStatus] = useState3("connecting");
307
- const [error, setError] = useState3(null);
183
+ const [status, setStatus] = useState2("connecting");
184
+ const [error, setError] = useState2(null);
308
185
  const environment = getEnvironment();
309
186
  const connect = useCallback(async () => {
310
187
  setStatus("connecting");
@@ -327,7 +204,7 @@ function useConnection() {
327
204
  setError(err instanceof Error ? err.message : "Connection failed");
328
205
  }
329
206
  }, []);
330
- useEffect3(() => {
207
+ useEffect2(() => {
331
208
  connect();
332
209
  }, [connect]);
333
210
  return {
@@ -339,12 +216,12 @@ function useConnection() {
339
216
  }
340
217
 
341
218
  // src/tui/interfaces/hooks/useEditorSessions.ts
342
- import { useState as useState4, useEffect as useEffect4, useCallback as useCallback2, useRef } from "react";
219
+ import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2, useRef } from "react";
343
220
  function useEditorSessions() {
344
- const [sessions, setSessions] = useState4([]);
345
- const [loading, setLoading] = useState4(true);
346
- const [error, setError] = useState4(null);
347
- const [refreshStatus, setRefreshStatus] = useState4("idle");
221
+ const [sessions, setSessions] = useState3([]);
222
+ const [loading, setLoading] = useState3(true);
223
+ const [error, setError] = useState3(null);
224
+ const [refreshStatus, setRefreshStatus] = useState3("idle");
348
225
  const initialLoadDone = useRef(false);
349
226
  const timerRef = useRef();
350
227
  const pollRef = useRef();
@@ -369,10 +246,10 @@ function useEditorSessions() {
369
246
  setLoading(false);
370
247
  }
371
248
  }, []);
372
- useEffect4(() => {
249
+ useEffect3(() => {
373
250
  refresh();
374
251
  }, [refresh]);
375
- useEffect4(() => {
252
+ useEffect3(() => {
376
253
  pollRef.current = setInterval(async () => {
377
254
  if (!initialLoadDone.current) return;
378
255
  try {
@@ -383,18 +260,18 @@ function useEditorSessions() {
383
260
  }, 5e3);
384
261
  return () => clearInterval(pollRef.current);
385
262
  }, []);
386
- useEffect4(() => {
263
+ useEffect3(() => {
387
264
  return () => clearTimeout(timerRef.current);
388
265
  }, []);
389
266
  return { sessions, loading, error, refreshStatus, refresh };
390
267
  }
391
268
 
392
269
  // src/tui/models/hooks/useSetupProviders.ts
393
- import { useState as useState5, useEffect as useEffect5, useCallback as useCallback3, useRef as useRef2 } from "react";
270
+ import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3, useRef as useRef2 } from "react";
394
271
  function useSetupProviders() {
395
- const [providers, setProviders] = useState5([]);
396
- const [loading, setLoading] = useState5(true);
397
- const [refreshing, setRefreshing] = useState5(false);
272
+ const [providers, setProviders] = useState4([]);
273
+ const [loading, setLoading] = useState4(true);
274
+ const [refreshing, setRefreshing] = useState4(false);
398
275
  const initialLoadDone = useRef2(false);
399
276
  const refresh = useCallback3(async () => {
400
277
  if (!initialLoadDone.current) {
@@ -408,19 +285,19 @@ function useSetupProviders() {
408
285
  setLoading(false);
409
286
  setRefreshing(false);
410
287
  }, []);
411
- useEffect5(() => {
288
+ useEffect4(() => {
412
289
  refresh();
413
290
  }, [refresh]);
414
291
  return { providers, loading, refreshing, refresh };
415
292
  }
416
293
 
417
294
  // src/tui/models/hooks/useModels.ts
418
- import { useState as useState6, useEffect as useEffect6, useCallback as useCallback4, useRef as useRef3 } from "react";
295
+ import { useState as useState5, useEffect as useEffect5, useCallback as useCallback4, useRef as useRef3 } from "react";
419
296
  function useModels() {
420
- const [models, setModels] = useState6([]);
421
- const [warnings, setWarnings] = useState6([]);
422
- const [loading, setLoading] = useState6(true);
423
- const [refreshing, setRefreshing] = useState6(false);
297
+ const [models, setModels] = useState5([]);
298
+ const [warnings, setWarnings] = useState5([]);
299
+ const [loading, setLoading] = useState5(true);
300
+ const [refreshing, setRefreshing] = useState5(false);
424
301
  const initialLoadDone = useRef3(false);
425
302
  const refresh = useCallback4(async () => {
426
303
  if (!initialLoadDone.current) {
@@ -441,7 +318,7 @@ function useModels() {
441
318
  setRefreshing(false);
442
319
  }
443
320
  }, []);
444
- useEffect6(() => {
321
+ useEffect5(() => {
445
322
  refresh();
446
323
  }, [refresh]);
447
324
  return {
@@ -454,11 +331,11 @@ function useModels() {
454
331
  }
455
332
 
456
333
  // src/tui/models/hooks/useRequests.ts
457
- import { useState as useState7, useEffect as useEffect7, useCallback as useCallback5, useRef as useRef4 } from "react";
334
+ import { useState as useState6, useEffect as useEffect6, useCallback as useCallback5, useRef as useRef4 } from "react";
458
335
  function useRequests(maxHistory = 50) {
459
- const [requests, setRequests] = useState7([]);
336
+ const [requests, setRequests] = useState6([]);
460
337
  const requestsRef = useRef4(/* @__PURE__ */ new Map());
461
- useEffect7(() => {
338
+ useEffect6(() => {
462
339
  const interval = setInterval(() => {
463
340
  setRequests((prev) => {
464
341
  const hasActive = prev.some((r) => r.status === "processing");
@@ -467,7 +344,7 @@ function useRequests(maxHistory = 50) {
467
344
  }, 1e3);
468
345
  return () => clearInterval(interval);
469
346
  }, []);
470
- useEffect7(() => {
347
+ useEffect6(() => {
471
348
  const unsubStart = requestEvents.onStart((event) => {
472
349
  const entry = {
473
350
  id: event.id,
@@ -532,10 +409,10 @@ function useRequests(maxHistory = 50) {
532
409
  }
533
410
 
534
411
  // src/tui/models/hooks/useRegisteredModels.ts
535
- import { useState as useState8, useEffect as useEffect8, useCallback as useCallback6 } from "react";
412
+ import { useState as useState7, useEffect as useEffect7, useCallback as useCallback6 } from "react";
536
413
  function useSyncedModels(connectionStatus) {
537
- const [syncedNames, setSyncedNames] = useState8(/* @__PURE__ */ new Set());
538
- const [syncedModels, setSyncedModels] = useState8([]);
414
+ const [syncedNames, setSyncedNames] = useState7(/* @__PURE__ */ new Set());
415
+ const [syncedModels, setSyncedModels] = useState7([]);
539
416
  const refresh = useCallback6(async () => {
540
417
  if (connectionStatus !== "connected") {
541
418
  setSyncedNames(/* @__PURE__ */ new Set());
@@ -549,7 +426,7 @@ function useSyncedModels(connectionStatus) {
549
426
  } catch {
550
427
  }
551
428
  }, [connectionStatus]);
552
- useEffect8(() => {
429
+ useEffect7(() => {
553
430
  refresh();
554
431
  }, [refresh]);
555
432
  return {
@@ -565,9 +442,9 @@ import { Box as Box4, Text as Text4, useStdout as useStdout3 } from "ink";
565
442
  import Spinner2 from "ink-spinner";
566
443
 
567
444
  // src/tui/models/components/RequestLog.tsx
568
- import { Box as Box3, Text as Text3, useStdout as useStdout2 } from "ink";
445
+ import { Box as Box2, Text as Text2, useStdout } from "ink";
569
446
  import Spinner from "ink-spinner";
570
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
447
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
571
448
  function formatTime(timestamp) {
572
449
  const date = new Date(timestamp);
573
450
  return date.toLocaleTimeString("en-US", {
@@ -610,28 +487,28 @@ function RequestItem({
610
487
  const elapsed = Date.now() - request.startTime;
611
488
  const snippet = request.content && request.requestType === "llm_chat" ? snippetLine(request.content, snippetWidth) : null;
612
489
  const stepProgress = request.step !== void 0 && request.totalSteps ? `Step ${request.step}/${request.totalSteps}` : null;
613
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
614
- /* @__PURE__ */ jsxs3(Box3, { children: [
615
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: /* @__PURE__ */ jsx3(Spinner, { type: "dots" }) }),
616
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
490
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
491
+ /* @__PURE__ */ jsxs2(Box2, { children: [
492
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: /* @__PURE__ */ jsx2(Spinner, { type: "dots" }) }),
493
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
617
494
  " ",
618
495
  time,
619
496
  " "
620
497
  ] }),
621
- /* @__PURE__ */ jsx3(Text3, { color: "white", children: request.modelId }),
622
- /* @__PURE__ */ jsx3(Text3, { color: "gray", children: " " }),
623
- /* @__PURE__ */ jsx3(Text3, { color: typeLabel.color, children: typeLabel.label }),
624
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
498
+ /* @__PURE__ */ jsx2(Text2, { color: "white", children: request.modelId }),
499
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", children: " " }),
500
+ /* @__PURE__ */ jsx2(Text2, { color: typeLabel.color, children: typeLabel.label }),
501
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
625
502
  " ",
626
503
  formatDuration(elapsed),
627
504
  "..."
628
505
  ] })
629
506
  ] }),
630
- snippet && /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
507
+ snippet && /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
631
508
  snippetIndent,
632
509
  snippet
633
510
  ] }),
634
- stepProgress && /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
511
+ stepProgress && /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
635
512
  snippetIndent,
636
513
  stepProgress
637
514
  ] })
@@ -648,40 +525,40 @@ function RequestItem({
648
525
  resultInfo = ` \xB7 ${Math.round(request.result.videoSize / 1024 / 1024)}MB`;
649
526
  }
650
527
  const snippet = request.content && request.requestType === "llm_chat" ? snippetLine(request.content, snippetWidth) : null;
651
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
652
- /* @__PURE__ */ jsxs3(Box3, { children: [
653
- /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713" }),
654
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
528
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
529
+ /* @__PURE__ */ jsxs2(Box2, { children: [
530
+ /* @__PURE__ */ jsx2(Text2, { color: "green", children: "\u2713" }),
531
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
655
532
  " ",
656
533
  time,
657
534
  " "
658
535
  ] }),
659
- /* @__PURE__ */ jsx3(Text3, { color: "white", children: request.modelId }),
660
- /* @__PURE__ */ jsx3(Text3, { color: "gray", children: " " }),
661
- /* @__PURE__ */ jsx3(Text3, { color: typeLabel.color, children: typeLabel.label }),
662
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
536
+ /* @__PURE__ */ jsx2(Text2, { color: "white", children: request.modelId }),
537
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", children: " " }),
538
+ /* @__PURE__ */ jsx2(Text2, { color: typeLabel.color, children: typeLabel.label }),
539
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
663
540
  " ",
664
541
  duration,
665
542
  resultInfo
666
543
  ] })
667
544
  ] }),
668
- snippet && /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
545
+ snippet && /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
669
546
  snippetIndent,
670
547
  snippet
671
548
  ] })
672
549
  ] });
673
550
  }
674
- return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsxs3(Box3, { children: [
675
- /* @__PURE__ */ jsx3(Text3, { color: "red", children: "\u25CF" }),
676
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
551
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsxs2(Box2, { children: [
552
+ /* @__PURE__ */ jsx2(Text2, { color: "red", children: "\u25CF" }),
553
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
677
554
  " ",
678
555
  time,
679
556
  " "
680
557
  ] }),
681
- /* @__PURE__ */ jsx3(Text3, { color: "white", children: request.modelId }),
682
- /* @__PURE__ */ jsx3(Text3, { color: "gray", children: " " }),
683
- /* @__PURE__ */ jsx3(Text3, { color: typeLabel.color, children: typeLabel.label }),
684
- /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
558
+ /* @__PURE__ */ jsx2(Text2, { color: "white", children: request.modelId }),
559
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", children: " " }),
560
+ /* @__PURE__ */ jsx2(Text2, { color: typeLabel.color, children: typeLabel.label }),
561
+ /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
685
562
  " ",
686
563
  request.error || "Failed"
687
564
  ] })
@@ -692,7 +569,7 @@ function RequestLog({
692
569
  maxVisible = 8,
693
570
  hasModels = true
694
571
  }) {
695
- const { stdout } = useStdout2();
572
+ const { stdout } = useStdout();
696
573
  const width = stdout?.columns ?? 80;
697
574
  const activeRequests = requests.filter((r) => r.status === "processing");
698
575
  const completedRequests = requests.filter((r) => r.status !== "processing");
@@ -714,8 +591,8 @@ function RequestLog({
714
591
  }
715
592
  }
716
593
  const visibleRequests = [...completedToShow, ...activeRequests];
717
- return /* @__PURE__ */ jsxs3(
718
- Box3,
594
+ return /* @__PURE__ */ jsxs2(
595
+ Box2,
719
596
  {
720
597
  flexDirection: "column",
721
598
  flexGrow: 1,
@@ -723,71 +600,210 @@ function RequestLog({
723
600
  paddingX: 1,
724
601
  marginTop: 1,
725
602
  children: [
726
- /* @__PURE__ */ jsxs3(Box3, { children: [
727
- /* @__PURE__ */ jsx3(Text3, { bold: true, underline: true, color: "white", children: "Generation Requests" }),
728
- activeRequests.length > 0 && /* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
603
+ /* @__PURE__ */ jsxs2(Box2, { children: [
604
+ /* @__PURE__ */ jsx2(Text2, { bold: true, underline: true, color: "white", children: "Generation Requests" }),
605
+ activeRequests.length > 0 && /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
729
606
  " (",
730
607
  activeRequests.length,
731
608
  " active)"
732
609
  ] })
733
610
  ] }),
734
- requests.length === 0 ? /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx3(Text3, { color: "gray", children: hasModels ? "Tunnel is live \u2014 requests will appear here when models are used in MindStudio" : "Start a model to begin receiving generation requests." }) }) : /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginTop: 1, children: visibleRequests.map((request) => /* @__PURE__ */ jsx3(RequestItem, { request, width }, request.id)) })
611
+ requests.length === 0 ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: hasModels ? "Tunnel is live \u2014 requests will appear here when models are used in MindStudio" : "Start a model to begin receiving generation requests." }) }) : /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: visibleRequests.map((request) => /* @__PURE__ */ jsx2(RequestItem, { request, width }, request.id)) })
735
612
  ]
736
613
  }
737
614
  );
738
615
  }
739
616
 
740
- // src/tui/models/pages/DashboardPage.tsx
741
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
742
- function getWorkflowCount(model) {
743
- const param = model.parameters?.find((p) => p.type === "comfyWorkflow");
744
- if (!param) return null;
745
- return param.comfyWorkflowOptions.availableWorkflows.length;
746
- }
747
- function getCapabilityLabel(capability) {
748
- switch (capability) {
749
- case "text":
750
- return { label: "Text Generation", color: "gray" };
751
- case "image":
752
- return { label: "Image Generation", color: "gray" };
753
- case "video":
754
- return { label: "Video Generation", color: "gray" };
755
- default:
756
- return { label: capability, color: "gray" };
757
- }
758
- }
759
- function DashboardPage({
760
- requests,
761
- models,
762
- modelWarnings = [],
763
- providers,
764
- providersLoading,
765
- syncedNames,
766
- modelsLoading,
767
- syncStatus = "idle",
768
- editorSessions,
769
- editorsLoading,
770
- onNavigate
617
+ // src/tui/components/NavigationMenu.tsx
618
+ import { useState as useState8, useEffect as useEffect8 } from "react";
619
+ import { Box as Box3, Text as Text3, useInput, useStdout as useStdout2 } from "ink";
620
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
621
+ function NavigationMenu({
622
+ items,
623
+ onSelect,
624
+ title
771
625
  }) {
772
- const { stdout } = useStdout3();
773
- const installedProviders = providers.filter(({ status }) => status.installed);
774
- const provNameWidth = Math.max(
775
- ...installedProviders.map((p) => p.provider.displayName.length),
776
- 8
777
- );
778
- const provStatusWidth = "Local Server Running".length;
779
- const allModelNames = new Set(models.map((m) => m.name));
780
- const unavailableSynced = [...syncedNames].filter(
781
- (name) => !allModelNames.has(name)
782
- );
783
- const syncDescription = syncStatus === "syncing" ? "Syncing..." : syncStatus === "synced" ? "\u2713 Synced" : "Re-detect providers and sync models to MindStudio";
784
- const menuItems = useMemo2(() => {
785
- return [
626
+ const { stdout } = useStdout2();
627
+ const compact = (stdout?.rows ?? 24) < 40;
628
+ const getDefaultIndex = () => {
629
+ const firstIdx = items.findIndex((i) => !i.disabled && !i.isSeparator);
630
+ return firstIdx >= 0 ? firstIdx : 0;
631
+ };
632
+ const [selectedIndex, setSelectedIndex] = useState8(getDefaultIndex);
633
+ useEffect8(() => {
634
+ setSelectedIndex(getDefaultIndex());
635
+ }, [items]);
636
+ const selectableItems = items.filter((i) => !i.isSeparator);
637
+ const findNextEnabled = (from, direction) => {
638
+ let idx = from;
639
+ for (let i = 0; i < items.length; i++) {
640
+ idx = (idx + direction + items.length) % items.length;
641
+ if (!items[idx].disabled && !items[idx].isSeparator) return idx;
642
+ }
643
+ return from;
644
+ };
645
+ useInput((input, key) => {
646
+ if (input === "q" || key.escape) {
647
+ const backItem = items.find((i) => i.id === "back");
648
+ if (backItem) {
649
+ onSelect("back");
650
+ } else if (input === "q") {
651
+ onSelect("quit");
652
+ }
653
+ return;
654
+ }
655
+ if (key.upArrow || compact && key.leftArrow) {
656
+ setSelectedIndex((prev) => findNextEnabled(prev, -1));
657
+ } else if (key.downArrow || compact && key.rightArrow) {
658
+ setSelectedIndex((prev) => findNextEnabled(prev, 1));
659
+ } else if (key.return) {
660
+ const item = items[selectedIndex];
661
+ if (item && !item.disabled) {
662
+ onSelect(item.id);
663
+ }
664
+ }
665
+ });
666
+ const hasBack = items.some((i) => i.id === "back");
667
+ if (compact) {
668
+ const selectedItem = items[selectedIndex];
669
+ return /* @__PURE__ */ jsx3(
670
+ Box3,
786
671
  {
787
- id: "interfaces",
788
- label: "Connect to Agent",
789
- description: "Connect your local editor to a MindStudio interface or script"
790
- },
672
+ flexDirection: "column",
673
+ paddingX: 1,
674
+ borderStyle: "single",
675
+ borderTop: true,
676
+ borderBottom: false,
677
+ borderLeft: false,
678
+ borderRight: false,
679
+ borderColor: "gray",
680
+ children: /* @__PURE__ */ jsx3(Box3, { height: 1, overflow: "hidden", gap: 1, children: items.map((item, index) => {
681
+ if (item.isSeparator) return null;
682
+ const isSelected = index === selectedIndex;
683
+ return /* @__PURE__ */ jsx3(
684
+ Text3,
685
+ {
686
+ color: item.disabled ? "gray" : isSelected ? "cyan" : "white",
687
+ bold: isSelected,
688
+ wrap: "truncate-end",
689
+ children: isSelected ? `\u276F ${item.label}` : ` ${item.label}`
690
+ },
691
+ item.id
692
+ );
693
+ }) })
694
+ }
695
+ );
696
+ }
697
+ const separatorExtraLines = items.filter(
698
+ (item, idx) => item.isSeparator && idx > 0
699
+ ).length;
700
+ const menuHeight = items.length + 4 + separatorExtraLines;
701
+ return /* @__PURE__ */ jsxs3(
702
+ Box3,
703
+ {
704
+ flexDirection: "column",
705
+ paddingX: 1,
706
+ marginBottom: 1,
707
+ borderStyle: "single",
708
+ borderTop: true,
709
+ borderBottom: false,
710
+ borderLeft: false,
711
+ borderRight: false,
712
+ borderColor: "gray",
713
+ children: [
714
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { color: "gray", children: title ?? "Actions" }) }),
715
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: items.map((item, index) => {
716
+ if (item.isSeparator) {
717
+ return /* @__PURE__ */ jsx3(Box3, { marginTop: index > 0 ? 1 : 0, children: item.label ? /* @__PURE__ */ jsx3(Text3, { bold: true, color: item.color ?? "gray", wrap: "truncate-end", children: item.label }) : null }, item.id);
718
+ }
719
+ const isSelected = index === selectedIndex;
720
+ const prefix = isSelected ? "\u276F" : " ";
721
+ if (item.disabled) {
722
+ return /* @__PURE__ */ jsx3(Box3, { height: 1, overflow: "hidden", children: /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
723
+ prefix,
724
+ " ",
725
+ item.label,
726
+ item.disabledReason ? ` (${item.disabledReason})` : ""
727
+ ] }) }, item.id);
728
+ }
729
+ return /* @__PURE__ */ jsxs3(Box3, { height: 1, overflow: "hidden", children: [
730
+ /* @__PURE__ */ jsxs3(
731
+ Text3,
732
+ {
733
+ color: isSelected ? "cyan" : "white",
734
+ bold: isSelected,
735
+ wrap: "truncate-end",
736
+ children: [
737
+ prefix,
738
+ " ",
739
+ item.label
740
+ ]
741
+ }
742
+ ),
743
+ isSelected && /* @__PURE__ */ jsxs3(Text3, { color: "gray", wrap: "truncate-end", children: [
744
+ " ",
745
+ "- ",
746
+ item.description
747
+ ] })
748
+ ] }, item.id);
749
+ }) }),
750
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsx3(Text3, { color: "gray", wrap: "truncate-end", children: hasBack ? "Up/Down Navigate \u2022 Enter Select \u2022 q/Esc Back" : "Up/Down Navigate \u2022 Enter Select \u2022 q Quit" }) })
751
+ ]
752
+ }
753
+ );
754
+ }
755
+
756
+ // src/tui/models/pages/DashboardPage.tsx
757
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
758
+ function getWorkflowCount(model) {
759
+ const param = model.parameters?.find((p) => p.type === "comfyWorkflow");
760
+ if (!param) return null;
761
+ return param.comfyWorkflowOptions.availableWorkflows.length;
762
+ }
763
+ function getCapabilityLabel(capability) {
764
+ switch (capability) {
765
+ case "text":
766
+ return { label: "Text Generation", color: "gray" };
767
+ case "image":
768
+ return { label: "Image Generation", color: "gray" };
769
+ case "video":
770
+ return { label: "Video Generation", color: "gray" };
771
+ default:
772
+ return { label: capability, color: "gray" };
773
+ }
774
+ }
775
+ function DashboardPage({
776
+ requests,
777
+ models,
778
+ modelWarnings = [],
779
+ providers,
780
+ providersLoading,
781
+ syncedNames,
782
+ modelsLoading,
783
+ syncStatus = "idle",
784
+ editorSessions,
785
+ editorsLoading,
786
+ onNavigate
787
+ }) {
788
+ const { stdout } = useStdout3();
789
+ const installedProviders = providers.filter(({ status }) => status.installed);
790
+ const provNameWidth = Math.max(
791
+ ...installedProviders.map((p) => p.provider.displayName.length),
792
+ 8
793
+ );
794
+ const provStatusWidth = "Local Server Running".length;
795
+ const allModelNames = new Set(models.map((m) => m.name));
796
+ const unavailableSynced = [...syncedNames].filter(
797
+ (name) => !allModelNames.has(name)
798
+ );
799
+ const syncDescription = syncStatus === "syncing" ? "Syncing..." : syncStatus === "synced" ? "\u2713 Synced" : "Re-detect providers and sync models to MindStudio";
800
+ const menuItems = useMemo2(() => {
801
+ return [
802
+ {
803
+ id: "interfaces",
804
+ label: "Connect to Agent",
805
+ description: "Connect your local editor to a MindStudio interface or script"
806
+ },
791
807
  {
792
808
  id: "refresh",
793
809
  label: "Sync Models",
@@ -2442,16 +2458,1128 @@ function OnboardingPage({ onComplete }) {
2442
2458
  ] });
2443
2459
  }
2444
2460
 
2461
+ // src/tui/dev/pages/DevPage.tsx
2462
+ import { useState as useState20, useEffect as useEffect19, useMemo as useMemo8 } from "react";
2463
+ import { Box as Box13, Text as Text14, useInput as useInput7 } from "ink";
2464
+ import Spinner7 from "ink-spinner";
2465
+
2466
+ // src/tui/dev/components/DevRequestLog.tsx
2467
+ import { Box as Box10, Text as Text11 } from "ink";
2468
+ import Spinner6 from "ink-spinner";
2469
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2470
+ function DevRequestLog({
2471
+ requests,
2472
+ maxVisible = 10
2473
+ }) {
2474
+ if (requests.length === 0) {
2475
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
2476
+ /* @__PURE__ */ jsx11(Text11, { bold: true, color: "white", underline: true, children: "Request Log" }),
2477
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: " Waiting for requests..." })
2478
+ ] });
2479
+ }
2480
+ const active = requests.filter((r) => r.status === "processing");
2481
+ const completed = requests.filter((r) => r.status !== "processing");
2482
+ const recent = completed.slice(-Math.max(0, maxVisible - active.length));
2483
+ const visible = [...recent, ...active].slice(-maxVisible);
2484
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
2485
+ /* @__PURE__ */ jsx11(Text11, { bold: true, color: "white", underline: true, children: "Request Log" }),
2486
+ visible.map((entry) => /* @__PURE__ */ jsx11(RequestEntry, { entry }, entry.id))
2487
+ ] });
2488
+ }
2489
+ function RequestEntry({ entry }) {
2490
+ const methodLabel = entry.method ?? "unknown";
2491
+ if (entry.status === "processing") {
2492
+ const elapsed = Math.round((Date.now() - entry.startTime) / 1e3);
2493
+ return /* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
2494
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: /* @__PURE__ */ jsx11(Spinner6, { type: "dots" }) }),
2495
+ /* @__PURE__ */ jsx11(Text11, { children: methodLabel }),
2496
+ /* @__PURE__ */ jsxs10(Text11, { color: "gray", children: [
2497
+ elapsed,
2498
+ "s"
2499
+ ] })
2500
+ ] });
2501
+ }
2502
+ if (entry.status === "failed") {
2503
+ const errorLines = (entry.error ?? "Unknown error").split("\n").slice(0, 4);
2504
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
2505
+ /* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
2506
+ /* @__PURE__ */ jsx11(Text11, { color: "red", children: "\u2716" }),
2507
+ /* @__PURE__ */ jsx11(Text11, { children: methodLabel }),
2508
+ entry.duration != null && /* @__PURE__ */ jsxs10(Text11, { color: "gray", children: [
2509
+ entry.duration,
2510
+ "ms"
2511
+ ] })
2512
+ ] }),
2513
+ errorLines.map((line, i) => /* @__PURE__ */ jsxs10(Text11, { color: "red", wrap: "truncate", children: [
2514
+ " ",
2515
+ line
2516
+ ] }, i))
2517
+ ] });
2518
+ }
2519
+ const duration = entry.duration != null ? `${entry.duration}ms` : "";
2520
+ return /* @__PURE__ */ jsxs10(Box10, { gap: 1, children: [
2521
+ /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713" }),
2522
+ /* @__PURE__ */ jsx11(Text11, { children: methodLabel }),
2523
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: duration })
2524
+ ] });
2525
+ }
2526
+
2527
+ // src/tui/dev/components/DevPortPrompt.tsx
2528
+ import { useState as useState16 } from "react";
2529
+ import { Box as Box11, Text as Text12 } from "ink";
2530
+ import TextInput from "ink-text-input";
2531
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2532
+ function DevPortPrompt({ onSubmit, onSkip }) {
2533
+ const [value, setValue] = useState16("");
2534
+ const [error, setError] = useState16(null);
2535
+ const handleSubmit = (input) => {
2536
+ const trimmed = input.trim();
2537
+ if (trimmed === "" || trimmed === "skip") {
2538
+ onSkip();
2539
+ return;
2540
+ }
2541
+ const port = parseInt(trimmed, 10);
2542
+ if (isNaN(port) || port < 1 || port > 65535) {
2543
+ setError('Enter a valid port number (1-65535) or "skip"');
2544
+ return;
2545
+ }
2546
+ onSubmit(port);
2547
+ };
2548
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
2549
+ /* @__PURE__ */ jsx12(Text12, { color: "yellow", children: "No devPort found in your web interface config." }),
2550
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "What port is your local dev server running on?" }),
2551
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", dimColor: true, children: 'Type "skip" for backend-only mode (no frontend proxying).' }),
2552
+ /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
2553
+ /* @__PURE__ */ jsx12(Text12, { color: "cyan", children: "Port: " }),
2554
+ /* @__PURE__ */ jsx12(
2555
+ TextInput,
2556
+ {
2557
+ value,
2558
+ onChange: (val) => {
2559
+ setValue(val);
2560
+ setError(null);
2561
+ },
2562
+ onSubmit: handleSubmit,
2563
+ placeholder: "5173"
2564
+ }
2565
+ )
2566
+ ] }),
2567
+ error && /* @__PURE__ */ jsx12(Text12, { color: "red", children: error })
2568
+ ] });
2569
+ }
2570
+
2571
+ // src/tui/dev/components/TabBar.tsx
2572
+ import { Box as Box12, Text as Text13, useStdout as useStdout7 } from "ink";
2573
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2574
+ function TabBar({ tabs, activeTab }) {
2575
+ const { stdout } = useStdout7();
2576
+ const width = stdout?.columns ?? 80;
2577
+ const tabSegments = tabs.map((tab) => {
2578
+ const isActive = tab.id === activeTab;
2579
+ const padded = ` ${tab.label} `;
2580
+ return { tab, isActive, padded };
2581
+ });
2582
+ const labelText = "Screen ";
2583
+ const hint = " \u2190/\u2192 ";
2584
+ const tabChars = tabSegments.reduce((sum, s) => sum + s.padded.length, 0);
2585
+ const remaining = Math.max(0, width - tabChars - hint.length - labelText.length);
2586
+ return /* @__PURE__ */ jsxs12(Box12, { children: [
2587
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: labelText }),
2588
+ tabSegments.map(({ tab, isActive, padded }) => /* @__PURE__ */ jsx13(
2589
+ Text13,
2590
+ {
2591
+ bold: isActive,
2592
+ color: isActive ? "white" : "#aaaaaa",
2593
+ backgroundColor: isActive ? "blueBright" : "#333333",
2594
+ children: padded
2595
+ },
2596
+ tab.id
2597
+ )),
2598
+ /* @__PURE__ */ jsxs12(Text13, { color: "#666666", backgroundColor: "#333333", children: [
2599
+ " ".repeat(remaining),
2600
+ hint
2601
+ ] })
2602
+ ] });
2603
+ }
2604
+
2605
+ // src/tui/dev/hooks/useDevSession.ts
2606
+ import { useState as useState18, useEffect as useEffect17, useRef as useRef10, useCallback as useCallback11 } from "react";
2607
+ import { spawn as spawn3 } from "child_process";
2608
+
2609
+ // src/tui/dev/hooks/useDevServer.ts
2610
+ import { useState as useState17, useCallback as useCallback10, useRef as useRef9, useEffect as useEffect16 } from "react";
2611
+ import { spawn as spawn2 } from "child_process";
2612
+ var MAX_OUTPUT_LINES2 = 200;
2613
+ var PORT_READY_TIMEOUT_MS = 3e4;
2614
+ var PORT_CHECK_INTERVAL_MS = 500;
2615
+ function useDevServer() {
2616
+ const [phase, setPhase] = useState17("idle");
2617
+ const [outputLines, setOutputLines] = useState17([]);
2618
+ const [error, setError] = useState17(null);
2619
+ const processRef = useRef9(null);
2620
+ const mountedRef = useRef9(true);
2621
+ useEffect16(() => {
2622
+ mountedRef.current = true;
2623
+ return () => {
2624
+ mountedRef.current = false;
2625
+ killProcess();
2626
+ };
2627
+ }, []);
2628
+ const appendOutput = useCallback10((line) => {
2629
+ if (!mountedRef.current) return;
2630
+ setOutputLines((prev) => {
2631
+ const next = [...prev, line];
2632
+ return next.length > MAX_OUTPUT_LINES2 ? next.slice(next.length - MAX_OUTPUT_LINES2) : next;
2633
+ });
2634
+ }, []);
2635
+ const killProcess = useCallback10(() => {
2636
+ if (processRef.current) {
2637
+ processRef.current.kill("SIGTERM");
2638
+ const proc = processRef.current;
2639
+ setTimeout(() => {
2640
+ if (proc && !proc.killed) {
2641
+ proc.kill("SIGKILL");
2642
+ }
2643
+ }, 2e3);
2644
+ processRef.current = null;
2645
+ }
2646
+ }, []);
2647
+ const start = useCallback10(
2648
+ async (opts) => {
2649
+ setPhase("starting");
2650
+ setError(null);
2651
+ setOutputLines([]);
2652
+ appendOutput(`$ ${opts.command}`);
2653
+ const proc = spawn2(opts.command, [], {
2654
+ cwd: opts.cwd,
2655
+ shell: true,
2656
+ env: { ...process.env, FORCE_COLOR: "1" },
2657
+ stdio: ["ignore", "pipe", "pipe"]
2658
+ });
2659
+ processRef.current = proc;
2660
+ const handleData = (data) => {
2661
+ const lines = data.toString().split("\n");
2662
+ for (const line of lines) {
2663
+ if (line.length > 0) {
2664
+ appendOutput(line);
2665
+ }
2666
+ }
2667
+ };
2668
+ proc.stdout?.on("data", handleData);
2669
+ proc.stderr?.on("data", handleData);
2670
+ proc.on("close", (code) => {
2671
+ processRef.current = null;
2672
+ if (mountedRef.current && phase !== "idle") {
2673
+ if (code !== 0 && code !== null) {
2674
+ setError(`Dev server exited with code ${code}`);
2675
+ setPhase("error");
2676
+ } else {
2677
+ setPhase("idle");
2678
+ }
2679
+ }
2680
+ });
2681
+ proc.on("error", (err) => {
2682
+ processRef.current = null;
2683
+ if (mountedRef.current) {
2684
+ setError(err.message);
2685
+ setPhase("error");
2686
+ }
2687
+ });
2688
+ await waitForPort(opts.port, PORT_READY_TIMEOUT_MS);
2689
+ if (mountedRef.current && processRef.current) {
2690
+ setPhase("running");
2691
+ }
2692
+ },
2693
+ [appendOutput]
2694
+ );
2695
+ const stop = useCallback10(() => {
2696
+ killProcess();
2697
+ if (mountedRef.current) {
2698
+ setPhase("idle");
2699
+ }
2700
+ }, [killProcess]);
2701
+ return {
2702
+ phase,
2703
+ outputLines,
2704
+ error,
2705
+ start,
2706
+ stop
2707
+ };
2708
+ }
2709
+ async function waitForPort(port, timeoutMs) {
2710
+ const start = Date.now();
2711
+ while (Date.now() - start < timeoutMs) {
2712
+ try {
2713
+ const response = await fetch(`http://localhost:${port}/`, {
2714
+ signal: AbortSignal.timeout(1e3)
2715
+ });
2716
+ return;
2717
+ } catch {
2718
+ }
2719
+ await new Promise((resolve) => setTimeout(resolve, PORT_CHECK_INTERVAL_MS));
2720
+ }
2721
+ }
2722
+
2723
+ // src/tui/dev/hooks/useDevSession.ts
2724
+ function runNpmInstall(cwd) {
2725
+ return new Promise((resolve, reject) => {
2726
+ const proc = spawn3("npm", ["install"], {
2727
+ cwd,
2728
+ shell: true,
2729
+ stdio: ["ignore", "ignore", "pipe"]
2730
+ });
2731
+ let stderr = "";
2732
+ proc.stderr?.on("data", (chunk) => {
2733
+ stderr += chunk.toString();
2734
+ });
2735
+ proc.on("close", (code) => {
2736
+ if (code === 0) resolve();
2737
+ else reject(new Error(`npm install failed in ${cwd}: ${stderr.slice(-200)}`));
2738
+ });
2739
+ proc.on("error", reject);
2740
+ });
2741
+ }
2742
+ function useDevSession(appConfig) {
2743
+ const logInitRef = useRef10(false);
2744
+ if (!logInitRef.current) {
2745
+ initLoggerInteractive("error");
2746
+ logInitRef.current = true;
2747
+ }
2748
+ const [phase, setPhase] = useState18("detecting");
2749
+ const [session, setSession] = useState18(null);
2750
+ const [error, setError] = useState18(null);
2751
+ const [devPort, setDevPort] = useState18(null);
2752
+ const [proxyPort, setProxyPort] = useState18(null);
2753
+ const [webConfig, setWebConfig] = useState18(null);
2754
+ const [syncResult, setSyncResult] = useState18(null);
2755
+ const [scenarioResult, setScenarioResult] = useState18(null);
2756
+ const [roleOverride, setRoleOverride] = useState18(null);
2757
+ const [installStatus, setInstallStatus] = useState18(null);
2758
+ const [authRefreshUrl, setAuthRefreshUrl] = useState18(null);
2759
+ const runnerRef = useRef10(null);
2760
+ const proxyRef = useRef10(null);
2761
+ const tableWatcherCleanupRef = useRef10(() => {
2762
+ });
2763
+ const mountedRef = useRef10(true);
2764
+ const devServer = useDevServer();
2765
+ const cleanupTableWatchers = useCallback11(() => {
2766
+ tableWatcherCleanupRef.current();
2767
+ tableWatcherCleanupRef.current = () => {
2768
+ };
2769
+ }, []);
2770
+ const setupTableWatchers = useCallback11((config) => {
2771
+ cleanupTableWatchers();
2772
+ tableWatcherCleanupRef.current = watchTableFiles(
2773
+ config.tables,
2774
+ process.cwd(),
2775
+ () => {
2776
+ resyncRef.current?.();
2777
+ }
2778
+ );
2779
+ }, [cleanupTableWatchers]);
2780
+ useEffect17(() => {
2781
+ const config = getWebInterfaceConfig(appConfig);
2782
+ setWebConfig(config);
2783
+ if (config?.devPort) {
2784
+ setDevPort(config.devPort);
2785
+ setPhase("ready");
2786
+ } else if (!appConfig.interfaces.some(
2787
+ (i) => i.type === "web" && i.enabled !== false
2788
+ )) {
2789
+ setDevPort(null);
2790
+ setPhase("ready");
2791
+ } else {
2792
+ setPhase("needs_port");
2793
+ }
2794
+ }, [appConfig]);
2795
+ useEffect17(() => {
2796
+ const unsubExpired = devRequestEvents.onSessionExpired(() => {
2797
+ if (mountedRef.current) {
2798
+ setPhase("expired");
2799
+ setAuthRefreshUrl(null);
2800
+ runnerRef.current = null;
2801
+ }
2802
+ });
2803
+ const unsubImpersonate = devRequestEvents.onImpersonate((event) => {
2804
+ if (mountedRef.current) {
2805
+ setRoleOverride(event.roles);
2806
+ }
2807
+ });
2808
+ const unsubAuthStart = devRequestEvents.onAuthRefreshStart((url) => {
2809
+ if (mountedRef.current) {
2810
+ setAuthRefreshUrl(url);
2811
+ }
2812
+ });
2813
+ const unsubAuthSuccess = devRequestEvents.onAuthRefreshSuccess(() => {
2814
+ if (mountedRef.current) {
2815
+ setAuthRefreshUrl(null);
2816
+ }
2817
+ });
2818
+ const unsubAuthFailed = devRequestEvents.onAuthRefreshFailed(() => {
2819
+ if (mountedRef.current) {
2820
+ setAuthRefreshUrl(null);
2821
+ }
2822
+ });
2823
+ return () => {
2824
+ unsubExpired();
2825
+ unsubImpersonate();
2826
+ unsubAuthStart();
2827
+ unsubAuthSuccess();
2828
+ unsubAuthFailed();
2829
+ };
2830
+ }, []);
2831
+ useEffect17(() => {
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;
2851
+ }, [phase, devServer]);
2852
+ useEffect17(() => {
2853
+ mountedRef.current = true;
2854
+ return () => {
2855
+ mountedRef.current = false;
2856
+ cleanupTableWatchers();
2857
+ runnerRef.current?.stop().catch(() => {
2858
+ });
2859
+ proxyRef.current?.stop();
2860
+ devServer.stop();
2861
+ };
2862
+ }, []);
2863
+ const start = useCallback11(
2864
+ async (port) => {
2865
+ const currentConfig = detectAppConfig() ?? appConfig;
2866
+ const actualPort = port ?? devPort;
2867
+ if (!currentConfig.appId) {
2868
+ setPhase("error");
2869
+ return;
2870
+ }
2871
+ if (actualPort !== void 0 && actualPort !== null) {
2872
+ setDevPort(actualPort);
2873
+ }
2874
+ setPhase("starting");
2875
+ setError(null);
2876
+ try {
2877
+ const dirsToInstall = findDirsNeedingInstall(currentConfig);
2878
+ for (const dir of dirsToInstall) {
2879
+ const dirName = dir.split("/").slice(-2).join("/");
2880
+ if (mountedRef.current) {
2881
+ setInstallStatus(`Installing dependencies in ${dirName}...`);
2882
+ }
2883
+ await runNpmInstall(dir);
2884
+ }
2885
+ if (mountedRef.current) {
2886
+ setInstallStatus(null);
2887
+ }
2888
+ if (actualPort != null) {
2889
+ const webProjectDir = getWebProjectDir(currentConfig);
2890
+ if (webProjectDir) {
2891
+ const devCommand = webConfig?.devCommand ?? "npm run dev";
2892
+ await devServer.start({
2893
+ command: devCommand,
2894
+ cwd: webProjectDir,
2895
+ port: actualPort
2896
+ });
2897
+ }
2898
+ }
2899
+ const branch = detectGitBranch();
2900
+ const proxyUrl = actualPort != null ? `http://localhost:${stablePort(currentConfig.appId)}` : void 0;
2901
+ const runner = new DevRunner(
2902
+ currentConfig.appId,
2903
+ process.cwd(),
2904
+ {
2905
+ branch,
2906
+ proxyUrl,
2907
+ methods: currentConfig.methods.map((m) => ({ id: m.id, export: m.export, path: m.path }))
2908
+ }
2909
+ );
2910
+ runnerRef.current = runner;
2911
+ const devSession = await runner.start();
2912
+ if (currentConfig.tables.length > 0) {
2913
+ try {
2914
+ const tableSources = readTableSources(currentConfig);
2915
+ if (tableSources.length > 0) {
2916
+ const result = await syncSchema(
2917
+ currentConfig.appId,
2918
+ devSession.sessionId,
2919
+ tableSources
2920
+ );
2921
+ devSession.databases = result.databases;
2922
+ if (mountedRef.current) {
2923
+ setSyncResult(result);
2924
+ }
2925
+ }
2926
+ } catch {
2927
+ }
2928
+ }
2929
+ if (actualPort != null && devSession.clientContext) {
2930
+ const proxy = new DevProxy(actualPort, devSession.clientContext);
2931
+ const preferredProxyPort = stablePort(currentConfig.appId);
2932
+ const pPort = await proxy.start(preferredProxyPort);
2933
+ proxyRef.current = proxy;
2934
+ runner.setProxyUrl(`http://localhost:${pPort}`);
2935
+ runner.setProxy(proxy);
2936
+ if (mountedRef.current) {
2937
+ setProxyPort(pPort);
2938
+ }
2939
+ }
2940
+ setupTableWatchers(currentConfig);
2941
+ if (mountedRef.current) {
2942
+ setSession(devSession);
2943
+ setPhase("running");
2944
+ }
2945
+ } catch (err) {
2946
+ if (mountedRef.current) {
2947
+ setError(
2948
+ err instanceof Error ? err.message : "Failed to start dev session"
2949
+ );
2950
+ setPhase("error");
2951
+ }
2952
+ }
2953
+ },
2954
+ [appConfig, devPort, webConfig, devServer, setupTableWatchers]
2955
+ );
2956
+ const stop = useCallback11(async () => {
2957
+ cleanupTableWatchers();
2958
+ proxyRef.current?.stop();
2959
+ proxyRef.current = null;
2960
+ devServer.stop();
2961
+ if (runnerRef.current) {
2962
+ await runnerRef.current.stop().catch(() => {
2963
+ });
2964
+ runnerRef.current = null;
2965
+ }
2966
+ if (mountedRef.current) {
2967
+ setSession(null);
2968
+ setProxyPort(null);
2969
+ setPhase("stopped");
2970
+ }
2971
+ }, [devServer, cleanupTableWatchers]);
2972
+ const resyncRef = useRef10();
2973
+ const resync = useCallback11(async () => {
2974
+ if (!session) return;
2975
+ const freshConfig = detectAppConfig() ?? appConfig;
2976
+ if (!freshConfig.appId) return;
2977
+ try {
2978
+ const tableSources = readTableSources(freshConfig);
2979
+ if (tableSources.length === 0) return;
2980
+ const result = await syncSchema(
2981
+ freshConfig.appId,
2982
+ session.sessionId,
2983
+ tableSources
2984
+ );
2985
+ if (mountedRef.current) {
2986
+ setSyncResult(result);
2987
+ setSession(
2988
+ (prev) => prev ? { ...prev, databases: result.databases } : prev
2989
+ );
2990
+ }
2991
+ } catch (err) {
2992
+ if (mountedRef.current) {
2993
+ setSyncResult({
2994
+ created: [],
2995
+ altered: [],
2996
+ errors: [err instanceof Error ? err.message : "Sync failed"],
2997
+ databases: session.databases
2998
+ });
2999
+ }
3000
+ }
3001
+ }, [appConfig, session]);
3002
+ resyncRef.current = resync;
3003
+ const setImpersonation = useCallback11(async (roles) => {
3004
+ if (!runnerRef.current) return;
3005
+ await runnerRef.current.setImpersonation(roles);
3006
+ }, []);
3007
+ const clearImpersonation = useCallback11(async () => {
3008
+ if (!runnerRef.current) return;
3009
+ await runnerRef.current.clearImpersonation();
3010
+ }, []);
3011
+ const runScenario = useCallback11(async (scenarioId) => {
3012
+ if (!runnerRef.current) return;
3013
+ const freshConfig = detectAppConfig() ?? appConfig;
3014
+ const scenario = freshConfig.scenarios.find((s) => s.id === scenarioId);
3015
+ if (!scenario) return;
3016
+ const result = await runnerRef.current.runScenario(scenario);
3017
+ if (mountedRef.current) {
3018
+ setSession(
3019
+ (prev) => prev ? { ...prev, databases: result.databases } : prev
3020
+ );
3021
+ setScenarioResult({
3022
+ id: scenario.id,
3023
+ name: scenario.name,
3024
+ success: result.success,
3025
+ duration: 0,
3026
+ // filled by event listener if needed
3027
+ roles: scenario.roles,
3028
+ error: result.error
3029
+ });
3030
+ }
3031
+ }, [appConfig]);
3032
+ const submitPort = useCallback11(
3033
+ (port) => {
3034
+ setDevPort(port);
3035
+ setPhase("ready");
3036
+ },
3037
+ []
3038
+ );
3039
+ const skipFrontend = useCallback11(() => {
3040
+ setDevPort(null);
3041
+ setPhase("ready");
3042
+ }, []);
3043
+ return {
3044
+ phase,
3045
+ session,
3046
+ error,
3047
+ devPort,
3048
+ proxyPort,
3049
+ webConfig,
3050
+ devServer,
3051
+ syncResult,
3052
+ scenarioResult,
3053
+ roleOverride,
3054
+ installStatus,
3055
+ authRefreshUrl,
3056
+ start,
3057
+ stop,
3058
+ resync,
3059
+ runScenario,
3060
+ setImpersonation,
3061
+ clearImpersonation,
3062
+ submitPort,
3063
+ skipFrontend
3064
+ };
3065
+ }
3066
+
3067
+ // src/tui/dev/hooks/useDevRequests.ts
3068
+ import { useState as useState19, useEffect as useEffect18, useRef as useRef11 } from "react";
3069
+ var MAX_HISTORY = 50;
3070
+ function useDevRequests() {
3071
+ const requestsRef = useRef11(/* @__PURE__ */ new Map());
3072
+ const [requests, setRequests] = useState19([]);
3073
+ useEffect18(() => {
3074
+ const unsubStart = devRequestEvents.onStart((event) => {
3075
+ const entry = {
3076
+ id: event.id,
3077
+ type: event.type,
3078
+ method: event.method,
3079
+ status: "processing",
3080
+ startTime: event.timestamp
3081
+ };
3082
+ requestsRef.current.set(event.id, entry);
3083
+ setRequests(
3084
+ Array.from(requestsRef.current.values()).slice(-MAX_HISTORY)
3085
+ );
3086
+ });
3087
+ const unsubComplete = devRequestEvents.onComplete((event) => {
3088
+ const existing = requestsRef.current.get(event.id);
3089
+ if (existing) {
3090
+ existing.status = event.success ? "completed" : "failed";
3091
+ existing.endTime = existing.startTime + event.duration;
3092
+ existing.duration = event.duration;
3093
+ existing.error = event.error;
3094
+ setRequests(
3095
+ Array.from(requestsRef.current.values()).slice(-MAX_HISTORY)
3096
+ );
3097
+ }
3098
+ });
3099
+ return () => {
3100
+ unsubStart();
3101
+ unsubComplete();
3102
+ };
3103
+ }, []);
3104
+ useEffect18(() => {
3105
+ const hasActive = requests.some((r) => r.status === "processing");
3106
+ if (!hasActive) return;
3107
+ const interval = setInterval(() => {
3108
+ setRequests(
3109
+ Array.from(requestsRef.current.values()).slice(-MAX_HISTORY)
3110
+ );
3111
+ }, 1e3);
3112
+ return () => clearInterval(interval);
3113
+ }, [requests]);
3114
+ const activeCount = requests.filter(
3115
+ (r) => r.status === "processing"
3116
+ ).length;
3117
+ return { requests, activeCount };
3118
+ }
3119
+
3120
+ // src/tui/dev/pages/DevPage.tsx
3121
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
3122
+ var TABS = [
3123
+ { id: "info", label: "Info" },
3124
+ { id: "requests", label: "Requests" },
3125
+ { id: "methods", label: "Methods" },
3126
+ { id: "server", label: "Dev Server" }
3127
+ ];
3128
+ function DevPage({ appConfig, onNavigate, termHeight }) {
3129
+ const {
3130
+ phase,
3131
+ session,
3132
+ error,
3133
+ devPort,
3134
+ proxyPort,
3135
+ devServer,
3136
+ syncResult,
3137
+ scenarioResult,
3138
+ roleOverride,
3139
+ installStatus,
3140
+ start,
3141
+ stop,
3142
+ resync,
3143
+ runScenario,
3144
+ setImpersonation,
3145
+ clearImpersonation,
3146
+ submitPort,
3147
+ skipFrontend
3148
+ } = useDevSession(appConfig);
3149
+ const { requests, activeCount } = useDevRequests();
3150
+ const [activeTab, setActiveTab] = useState20("info");
3151
+ const [showScenarioPicker, setShowScenarioPicker] = useState20(false);
3152
+ const [showRolePicker, setShowRolePicker] = useState20(false);
3153
+ const hasScenarios = appConfig.scenarios.length > 0;
3154
+ const hasRoles = appConfig.roles.length > 0;
3155
+ const runningMenuItems = useMemo8(
3156
+ () => [
3157
+ ...hasScenarios ? [{ id: "scenario", label: "Run Scenario", description: "Seed database with test data" }] : [],
3158
+ ...hasRoles ? [{ id: "impersonate", label: "Impersonate", description: roleOverride ? `Active: ${roleOverride.join(", ")}` : "Test as a different role" }] : [],
3159
+ { id: "sync", label: "Sync Schema", description: "Re-sync table definitions from disk" },
3160
+ { id: "stop", label: "Stop Session", description: "Stop the dev session and clean up" },
3161
+ { id: "dashboard", label: "Local Models", description: "Switch to local models view" },
3162
+ { id: "quit", label: "Quit", description: "Exit the application" }
3163
+ ],
3164
+ [hasScenarios, hasRoles, roleOverride]
3165
+ );
3166
+ useInput7((_input, key) => {
3167
+ if (phase !== "running") return;
3168
+ if (key.leftArrow) {
3169
+ const idx = TABS.findIndex((t) => t.id === activeTab);
3170
+ setActiveTab(TABS[(idx - 1 + TABS.length) % TABS.length].id);
3171
+ } else if (key.rightArrow) {
3172
+ const idx = TABS.findIndex((t) => t.id === activeTab);
3173
+ setActiveTab(TABS[(idx + 1) % TABS.length].id);
3174
+ }
3175
+ });
3176
+ useEffect19(() => {
3177
+ if (phase === "ready") {
3178
+ start();
3179
+ }
3180
+ }, [phase, start]);
3181
+ if (phase === "detecting") {
3182
+ return /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { children: [
3183
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
3184
+ " Checking app configuration..."
3185
+ ] }) });
3186
+ }
3187
+ if (phase === "needs_port") {
3188
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3189
+ /* @__PURE__ */ jsx14(AppInfoHeader, { appConfig }),
3190
+ /* @__PURE__ */ jsx14(DevPortPrompt, { onSubmit: submitPort, onSkip: skipFrontend })
3191
+ ] });
3192
+ }
3193
+ if (phase === "starting") {
3194
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3195
+ /* @__PURE__ */ jsx14(AppInfoHeader, { appConfig }),
3196
+ /* @__PURE__ */ jsxs13(Box13, { paddingX: 1, marginTop: 1, flexDirection: "column", children: [
3197
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3198
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
3199
+ " Starting dev session..."
3200
+ ] }),
3201
+ installStatus && /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3202
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
3203
+ " ",
3204
+ installStatus
3205
+ ] }),
3206
+ devServer.phase === "starting" && /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3207
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
3208
+ " Waiting for dev server on port ",
3209
+ devPort,
3210
+ "..."
3211
+ ] }),
3212
+ devServer.outputLines.slice(-6).map((line, i) => /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, wrap: "truncate", children: line }, i))
3213
+ ] })
3214
+ ] });
3215
+ }
3216
+ if (phase === "error") {
3217
+ const errorMenuItems = [
3218
+ { id: "retry", label: "Retry", description: "Try starting the session again" },
3219
+ { id: "dashboard", label: "Local Models", description: "Switch to local models view" },
3220
+ { id: "quit", label: "Quit", description: "Exit the application" }
3221
+ ];
3222
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3223
+ /* @__PURE__ */ jsx14(AppInfoHeader, { appConfig }),
3224
+ /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
3225
+ error && /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
3226
+ "\u2716 ",
3227
+ error
3228
+ ] }),
3229
+ devServer.error && /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
3230
+ "Dev server: ",
3231
+ devServer.error
3232
+ ] }),
3233
+ devServer.outputLines.slice(-5).map((line, i) => /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, wrap: "truncate", children: line }, i))
3234
+ ] }),
3235
+ /* @__PURE__ */ jsx14(Box13, { flexGrow: 1 }),
3236
+ /* @__PURE__ */ jsx14(
3237
+ NavigationMenu,
3238
+ {
3239
+ items: errorMenuItems,
3240
+ onSelect: (id) => {
3241
+ if (id === "retry") start();
3242
+ else onNavigate(id);
3243
+ }
3244
+ }
3245
+ )
3246
+ ] });
3247
+ }
3248
+ if (phase === "stopped") {
3249
+ const stoppedMenuItems = [
3250
+ { id: "restart", label: "Restart", description: "Start a new dev session" },
3251
+ { id: "dashboard", label: "Local Models", description: "Switch to local models view" },
3252
+ { id: "quit", label: "Quit", description: "Exit the application" }
3253
+ ];
3254
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3255
+ /* @__PURE__ */ jsx14(AppInfoHeader, { appConfig }),
3256
+ /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: "Session stopped." }) }),
3257
+ /* @__PURE__ */ jsx14(Box13, { flexGrow: 1 }),
3258
+ /* @__PURE__ */ jsx14(
3259
+ NavigationMenu,
3260
+ {
3261
+ items: stoppedMenuItems,
3262
+ onSelect: (id) => {
3263
+ if (id === "restart") start();
3264
+ else onNavigate(id);
3265
+ }
3266
+ }
3267
+ )
3268
+ ] });
3269
+ }
3270
+ if (phase === "expired") {
3271
+ const expiredMenuItems = [
3272
+ { id: "restart", label: "Restart", description: "Start a new dev session" },
3273
+ { id: "dashboard", label: "Local Models", description: "Switch to local models view" },
3274
+ { id: "quit", label: "Quit", description: "Exit the application" }
3275
+ ];
3276
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3277
+ /* @__PURE__ */ jsx14(AppInfoHeader, { appConfig }),
3278
+ /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: "\u26A0 Dev session expired. The platform may have timed it out." }) }),
3279
+ /* @__PURE__ */ jsx14(Box13, { flexGrow: 1 }),
3280
+ /* @__PURE__ */ jsx14(
3281
+ NavigationMenu,
3282
+ {
3283
+ items: expiredMenuItems,
3284
+ onSelect: (id) => {
3285
+ if (id === "restart") start();
3286
+ else onNavigate(id);
3287
+ }
3288
+ }
3289
+ )
3290
+ ] });
3291
+ }
3292
+ const statusLines = 4;
3293
+ const menuLines = 8;
3294
+ const contentHeight = Math.max(5, (termHeight ?? 30) - statusLines - menuLines);
3295
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
3296
+ /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs13(Box13, { gap: 2, children: [
3297
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", children: appConfig.name }),
3298
+ /* @__PURE__ */ jsxs13(Text14, { color: "green", children: [
3299
+ "\u25CF ",
3300
+ session?.branch ?? "main"
3301
+ ] }),
3302
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: session?.previewUrl ?? session?.webInterfaceUrl ?? "" })
3303
+ ] }) }),
3304
+ /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
3305
+ activeTab === "info" && /* @__PURE__ */ jsx14(
3306
+ InfoTab,
3307
+ {
3308
+ appConfig,
3309
+ session,
3310
+ devPort,
3311
+ proxyPort,
3312
+ devServerPhase: devServer.phase,
3313
+ requests,
3314
+ syncResult,
3315
+ scenarioResult,
3316
+ roleOverride,
3317
+ contentHeight
3318
+ }
3319
+ ),
3320
+ activeTab === "requests" && /* @__PURE__ */ jsx14(DevRequestLog, { requests, maxVisible: contentHeight }),
3321
+ activeTab === "methods" && /* @__PURE__ */ jsx14(MethodsTab, { appConfig, contentHeight }),
3322
+ activeTab === "server" && /* @__PURE__ */ jsx14(
3323
+ DevServerTab,
3324
+ {
3325
+ devPort,
3326
+ phase: devServer.phase,
3327
+ outputLines: devServer.outputLines,
3328
+ error: devServer.error,
3329
+ contentHeight
3330
+ }
3331
+ )
3332
+ ] }),
3333
+ showScenarioPicker ? /* @__PURE__ */ jsx14(
3334
+ NavigationMenu,
3335
+ {
3336
+ title: "Select Scenario",
3337
+ items: [
3338
+ ...appConfig.scenarios.map((s) => ({
3339
+ id: `scenario:${s.id}`,
3340
+ label: s.name ?? s.id,
3341
+ description: s.description ?? `Roles: ${s.roles.length > 0 ? s.roles.join(", ") : "none"}`
3342
+ })),
3343
+ { id: "back", label: "Back", description: "Return to actions" }
3344
+ ],
3345
+ onSelect: (id) => {
3346
+ setShowScenarioPicker(false);
3347
+ if (id.startsWith("scenario:")) {
3348
+ runScenario(id.slice("scenario:".length));
3349
+ }
3350
+ }
3351
+ }
3352
+ ) : showRolePicker ? /* @__PURE__ */ jsx14(
3353
+ NavigationMenu,
3354
+ {
3355
+ title: "Impersonate Role",
3356
+ items: [
3357
+ ...appConfig.roles.map((r) => ({
3358
+ id: `role:${r.id}`,
3359
+ label: r.name ?? r.id,
3360
+ description: r.description ?? r.id
3361
+ })),
3362
+ ...roleOverride ? [{ id: "clear", label: "Clear Override", description: "Revert to default session roles" }] : [],
3363
+ { id: "back", label: "Back", description: "Return to actions" }
3364
+ ],
3365
+ onSelect: (id) => {
3366
+ setShowRolePicker(false);
3367
+ if (id === "clear") {
3368
+ clearImpersonation();
3369
+ } else if (id.startsWith("role:")) {
3370
+ setImpersonation([id.slice("role:".length)]);
3371
+ }
3372
+ }
3373
+ }
3374
+ ) : /* @__PURE__ */ jsx14(
3375
+ NavigationMenu,
3376
+ {
3377
+ items: runningMenuItems,
3378
+ onSelect: (id) => {
3379
+ if (id === "scenario") setShowScenarioPicker(true);
3380
+ else if (id === "impersonate") setShowRolePicker(true);
3381
+ else if (id === "sync") resync();
3382
+ else if (id === "stop") stop();
3383
+ else onNavigate(id);
3384
+ }
3385
+ }
3386
+ ),
3387
+ /* @__PURE__ */ jsx14(TabBar, { tabs: TABS, activeTab })
3388
+ ] });
3389
+ }
3390
+ function AppInfoHeader({ appConfig }) {
3391
+ return /* @__PURE__ */ jsxs13(
3392
+ Box13,
3393
+ {
3394
+ flexDirection: "column",
3395
+ paddingX: 1,
3396
+ paddingY: 1,
3397
+ borderStyle: "round",
3398
+ borderColor: "gray",
3399
+ children: [
3400
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", children: appConfig.name }),
3401
+ appConfig.description && /* @__PURE__ */ jsx14(Text14, { color: "gray", children: appConfig.description }),
3402
+ !appConfig.appId && /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: '\u26A0 No "appId" field in mindstudio.json \u2014 add your app ID to start a dev session' })
3403
+ ]
3404
+ }
3405
+ );
3406
+ }
3407
+ function InfoTab({
3408
+ appConfig,
3409
+ session,
3410
+ devPort,
3411
+ proxyPort,
3412
+ devServerPhase,
3413
+ requests,
3414
+ syncResult,
3415
+ scenarioResult,
3416
+ roleOverride,
3417
+ contentHeight
3418
+ }) {
3419
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
3420
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Session" }),
3421
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3422
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: "Session ID: " }),
3423
+ /* @__PURE__ */ jsx14(Text14, { children: session?.sessionId ?? "..." })
3424
+ ] }),
3425
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3426
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: "Release ID: " }),
3427
+ /* @__PURE__ */ jsx14(Text14, { children: session?.releaseId ?? "..." })
3428
+ ] }),
3429
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3430
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: "Branch: " }),
3431
+ /* @__PURE__ */ jsx14(Text14, { children: session?.branch ?? "..." })
3432
+ ] }),
3433
+ session?.user && /* @__PURE__ */ jsxs13(Text14, { children: [
3434
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: "User: " }),
3435
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3436
+ session.user.name,
3437
+ " (",
3438
+ session.user.email,
3439
+ ")"
3440
+ ] })
3441
+ ] }),
3442
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "App URL" }) }),
3443
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", bold: true, children: session?.previewUrl ?? session?.webInterfaceUrl ?? "..." }),
3444
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Dev Server" }) }),
3445
+ devPort !== null ? /* @__PURE__ */ jsxs13(Text14, { children: [
3446
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3447
+ "localhost:",
3448
+ devPort
3449
+ ] }),
3450
+ devServerPhase === "running" ? /* @__PURE__ */ jsx14(Text14, { color: "green", children: " \u25CF running" }) : devServerPhase === "starting" ? /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: " \u25CB starting" }) : /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3451
+ " \u25CB ",
3452
+ devServerPhase
3453
+ ] })
3454
+ ] }) : /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "Backend-only mode (no frontend)" }),
3455
+ roleOverride && /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
3456
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Impersonation" }),
3457
+ /* @__PURE__ */ jsxs13(Text14, { color: "yellow", children: [
3458
+ " \u25CF Active: ",
3459
+ roleOverride.join(", ")
3460
+ ] })
3461
+ ] }),
3462
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Databases" }) }),
3463
+ (session?.databases ?? []).length === 0 ? /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "No databases" }) : session?.databases.map((db) => /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
3464
+ /* @__PURE__ */ jsxs13(Text14, { children: [
3465
+ " ",
3466
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: db.name })
3467
+ ] }),
3468
+ db.tables.map((table) => /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3469
+ " ",
3470
+ table.name
3471
+ ] }, table.name))
3472
+ ] }, db.id)),
3473
+ syncResult && /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
3474
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Schema Sync" }),
3475
+ syncResult.created.length > 0 && /* @__PURE__ */ jsxs13(Text14, { color: "green", children: [
3476
+ " \u2713 Created: ",
3477
+ syncResult.created.join(", ")
3478
+ ] }),
3479
+ syncResult.altered.length > 0 && /* @__PURE__ */ jsxs13(Text14, { color: "yellow", children: [
3480
+ " \u2713 Altered: ",
3481
+ syncResult.altered.join(", ")
3482
+ ] }),
3483
+ syncResult.errors.map((err, i) => /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
3484
+ " \u2716 ",
3485
+ err
3486
+ ] }, i)),
3487
+ syncResult.created.length === 0 && syncResult.altered.length === 0 && syncResult.errors.length === 0 && /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: " No changes" })
3488
+ ] }),
3489
+ scenarioResult && /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
3490
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Last Scenario" }),
3491
+ scenarioResult.success ? /* @__PURE__ */ jsxs13(Text14, { color: "green", children: [
3492
+ ' \u2713 "',
3493
+ scenarioResult.name ?? scenarioResult.id,
3494
+ '" applied'
3495
+ ] }) : /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
3496
+ ' \u2716 "',
3497
+ scenarioResult.name ?? scenarioResult.id,
3498
+ '" failed: ',
3499
+ scenarioResult.error
3500
+ ] }),
3501
+ scenarioResult.roles.length > 0 && /* @__PURE__ */ jsxs13(Text14, { color: "gray", dimColor: true, children: [
3502
+ " Roles: ",
3503
+ scenarioResult.roles.join(", ")
3504
+ ] })
3505
+ ] }),
3506
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, color: "white", underline: true, children: "Recent Requests" }) }),
3507
+ requests.length === 0 ? /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "No requests yet" }) : requests.slice(-5).map((req) => /* @__PURE__ */ jsxs13(Box13, { gap: 1, children: [
3508
+ req.status === "completed" && /* @__PURE__ */ jsx14(Text14, { color: "green", children: "\u2713" }),
3509
+ req.status === "failed" && /* @__PURE__ */ jsx14(Text14, { color: "red", children: "\u2716" }),
3510
+ req.status === "processing" && /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: "\u25CF" }),
3511
+ /* @__PURE__ */ jsx14(Text14, { children: req.method ?? "unknown" }),
3512
+ req.duration != null && /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3513
+ req.duration,
3514
+ "ms"
3515
+ ] }),
3516
+ req.status === "failed" && req.error && /* @__PURE__ */ jsx14(Text14, { color: "red", wrap: "truncate", children: req.error.split("\n")[0] })
3517
+ ] }, req.id))
3518
+ ] });
3519
+ }
3520
+ function MethodsTab({
3521
+ appConfig,
3522
+ contentHeight
3523
+ }) {
3524
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
3525
+ /* @__PURE__ */ jsxs13(Text14, { bold: true, color: "white", underline: true, children: [
3526
+ "Methods (",
3527
+ appConfig.methods.length,
3528
+ ")"
3529
+ ] }),
3530
+ appConfig.methods.slice(0, contentHeight - 2).map((method) => /* @__PURE__ */ jsxs13(Box13, { gap: 1, children: [
3531
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: method.export }),
3532
+ /* @__PURE__ */ jsxs13(Text14, { color: "gray", dimColor: true, children: [
3533
+ "\u2192 ",
3534
+ method.id
3535
+ ] }),
3536
+ /* @__PURE__ */ jsxs13(Text14, { color: "gray", dimColor: true, children: [
3537
+ "(",
3538
+ method.path,
3539
+ ")"
3540
+ ] })
3541
+ ] }, method.id)),
3542
+ appConfig.methods.length === 0 && /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "No methods defined" })
3543
+ ] });
3544
+ }
3545
+ function DevServerTab({
3546
+ devPort,
3547
+ phase,
3548
+ outputLines,
3549
+ error,
3550
+ contentHeight
3551
+ }) {
3552
+ const visibleLines = contentHeight - 3;
3553
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
3554
+ /* @__PURE__ */ jsxs13(Text14, { bold: true, color: "white", underline: true, children: [
3555
+ "Dev Server",
3556
+ phase === "running" ? /* @__PURE__ */ jsx14(Text14, { color: "green", children: " \u25CF running" }) : phase === "starting" ? /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: " \u25CB starting" }) : phase === "error" ? /* @__PURE__ */ jsx14(Text14, { color: "red", children: " \u2716 error" }) : /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
3557
+ " \u25CB ",
3558
+ phase
3559
+ ] })
3560
+ ] }),
3561
+ devPort !== null && /* @__PURE__ */ jsxs13(Text14, { color: "gray", dimColor: true, children: [
3562
+ "localhost:",
3563
+ devPort
3564
+ ] }),
3565
+ devPort === null && /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "Backend-only mode (no frontend)" }),
3566
+ error && /* @__PURE__ */ jsx14(Text14, { color: "red", children: error }),
3567
+ outputLines.slice(-visibleLines).map((line, i) => /* @__PURE__ */ jsx14(Text14, { color: "gray", wrap: "truncate", children: line }, i)),
3568
+ outputLines.length === 0 && phase !== "idle" && !error && /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "Waiting for output..." }),
3569
+ outputLines.length === 0 && phase === "idle" && !error && /* @__PURE__ */ jsx14(Text14, { color: "gray", dimColor: true, children: "Dev server not started" })
3570
+ ] });
3571
+ }
3572
+
2445
3573
  // src/tui/App.tsx
2446
- import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3574
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2447
3575
  var MODEL_TYPE_MAP = {
2448
3576
  text: "llm_chat",
2449
3577
  image: "image_generation",
2450
3578
  video: "video_generation"
2451
3579
  };
2452
- function App({ runner }) {
3580
+ function App({ runner, appConfig }) {
2453
3581
  const { exit } = useApp();
2454
- const { stdout } = useStdout7();
3582
+ const { stdout } = useStdout8();
2455
3583
  const {
2456
3584
  status: connectionStatus,
2457
3585
  environment,
@@ -2477,31 +3605,31 @@ function App({ runner }) {
2477
3605
  refresh: refreshSynced
2478
3606
  } = useSyncedModels(connectionStatus);
2479
3607
  const shouldOnboard = getApiKey() === void 0 || getUserId() === void 0;
2480
- const [page, setPage] = useState16(
2481
- shouldOnboard ? "onboarding" : "dashboard"
3608
+ const [page, setPage] = useState21(
3609
+ shouldOnboard ? "onboarding" : appConfig ? "dev" : "dashboard"
2482
3610
  );
2483
- const [syncStatus, setSyncStatus] = useState16(
3611
+ const [syncStatus, setSyncStatus] = useState21(
2484
3612
  "idle"
2485
3613
  );
2486
- const syncTimerRef = useRef9(void 0);
2487
- const lastSyncPayloadRef = useRef9("");
2488
- useEffect16(() => {
3614
+ const syncTimerRef = useRef12(void 0);
3615
+ const lastSyncPayloadRef = useRef12("");
3616
+ useEffect20(() => {
2489
3617
  if (connectionStatus === "not_authenticated") {
2490
3618
  setPage("onboarding");
2491
3619
  }
2492
3620
  }, [connectionStatus]);
2493
- useEffect16(() => {
3621
+ useEffect20(() => {
2494
3622
  if (page === "dashboard") {
2495
3623
  refreshAll();
2496
3624
  }
2497
3625
  }, [page]);
2498
- useEffect16(() => {
3626
+ useEffect20(() => {
2499
3627
  if (connectionStatus === "connected" && syncedModels.length > 0) {
2500
3628
  runner.start(syncedModels);
2501
3629
  }
2502
3630
  }, [connectionStatus, syncedModels, runner]);
2503
- useEffect16(() => () => runner.stop(), [runner]);
2504
- const refreshAll = useCallback10(
3631
+ useEffect20(() => () => runner.stop(), [runner]);
3632
+ const refreshAll = useCallback12(
2505
3633
  async (silent = false) => {
2506
3634
  if (!silent) setSyncStatus("syncing");
2507
3635
  const [discoveredModels] = await Promise.all([
@@ -2531,21 +3659,21 @@ function App({ runner }) {
2531
3659
  },
2532
3660
  [refreshProviders, refreshModels, refreshSynced]
2533
3661
  );
2534
- useEffect16(() => {
3662
+ useEffect20(() => {
2535
3663
  if (connectionStatus !== "connected" || page !== "dashboard") return;
2536
3664
  const interval = setInterval(() => refreshAll(true), 1500);
2537
3665
  return () => clearInterval(interval);
2538
3666
  }, [connectionStatus, page, refreshAll]);
2539
- const handleQuit = useCallback10(() => {
3667
+ const handleQuit = useCallback12(() => {
2540
3668
  runner.stop();
2541
3669
  exit();
2542
3670
  }, [runner, exit]);
2543
- const handleOnboardingComplete = useCallback10(() => {
3671
+ const handleOnboardingComplete = useCallback12(() => {
2544
3672
  retryConnection();
2545
3673
  refreshAll();
2546
3674
  setPage("dashboard");
2547
3675
  }, [retryConnection, refreshAll]);
2548
- const handleNavigate = useCallback10(
3676
+ const handleNavigate = useCallback12(
2549
3677
  (id) => {
2550
3678
  switch (id) {
2551
3679
  case "interfaces":
@@ -2557,6 +3685,12 @@ function App({ runner }) {
2557
3685
  case "setup":
2558
3686
  setPage("setup");
2559
3687
  break;
3688
+ case "dev":
3689
+ setPage("dev");
3690
+ break;
3691
+ case "dashboard":
3692
+ setPage("dashboard");
3693
+ break;
2560
3694
  case "refresh":
2561
3695
  refreshAll();
2562
3696
  break;
@@ -2570,7 +3704,7 @@ function App({ runner }) {
2570
3704
  const subpageMenuItems = [
2571
3705
  { id: "back", label: "Back", description: "Return to dashboard" }
2572
3706
  ];
2573
- const handleSubpageNavigate = useCallback10(
3707
+ const handleSubpageNavigate = useCallback12(
2574
3708
  (id) => {
2575
3709
  if (id === "back") {
2576
3710
  setPage("dashboard");
@@ -2580,11 +3714,11 @@ function App({ runner }) {
2580
3714
  },
2581
3715
  [handleNavigate]
2582
3716
  );
2583
- const [termSize, setTermSize] = useState16({
3717
+ const [termSize, setTermSize] = useState21({
2584
3718
  rows: stdout?.rows ?? 24,
2585
3719
  columns: stdout?.columns ?? 80
2586
3720
  });
2587
- useEffect16(() => {
3721
+ useEffect20(() => {
2588
3722
  if (!stdout) return;
2589
3723
  const onResize = () => {
2590
3724
  stdout.write("\x1B[2J\x1B[H");
@@ -2597,8 +3731,8 @@ function App({ runner }) {
2597
3731
  }, [stdout]);
2598
3732
  const termHeight = termSize.rows - 4;
2599
3733
  const compactHeader = termSize.rows <= 45 || termSize.columns <= 90;
2600
- return /* @__PURE__ */ jsx11(Box10, { flexDirection: "column", height: termHeight, overflow: "hidden", children: page === "onboarding" ? /* @__PURE__ */ jsx11(OnboardingPage, { onComplete: handleOnboardingComplete }) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
2601
- /* @__PURE__ */ jsx11(
3734
+ return /* @__PURE__ */ jsx15(Box14, { flexDirection: "column", height: termHeight, overflow: "hidden", children: page === "onboarding" ? /* @__PURE__ */ jsx15(OnboardingPage, { onComplete: handleOnboardingComplete }) : /* @__PURE__ */ jsxs14(Fragment5, { children: [
3735
+ /* @__PURE__ */ jsx15(
2602
3736
  Header,
2603
3737
  {
2604
3738
  connection: connectionStatus,
@@ -2609,7 +3743,7 @@ function App({ runner }) {
2609
3743
  hasActiveRequest: activeRequestCount > 0
2610
3744
  }
2611
3745
  ),
2612
- page === "dashboard" && /* @__PURE__ */ jsx11(
3746
+ page === "dashboard" && /* @__PURE__ */ jsx15(
2613
3747
  DashboardPage,
2614
3748
  {
2615
3749
  requests,
@@ -2625,8 +3759,8 @@ function App({ runner }) {
2625
3759
  onNavigate: handleNavigate
2626
3760
  }
2627
3761
  ),
2628
- page === "setup" && /* @__PURE__ */ jsx11(SetupPage, { onBack: () => setPage("dashboard") }),
2629
- page === "interfaces" && /* @__PURE__ */ jsx11(
3762
+ page === "setup" && /* @__PURE__ */ jsx15(SetupPage, { onBack: () => setPage("dashboard") }),
3763
+ page === "interfaces" && /* @__PURE__ */ jsx15(
2630
3764
  InterfacesPage,
2631
3765
  {
2632
3766
  onBack: () => setPage("dashboard"),
@@ -2635,12 +3769,12 @@ function App({ runner }) {
2635
3769
  refresh: editorSessions.refresh
2636
3770
  }
2637
3771
  ),
2638
- page !== "dashboard" && page !== "setup" && page !== "interfaces" && /* @__PURE__ */ jsx11(Box10, { flexGrow: 1 }),
2639
- page !== "dashboard" && page !== "setup" && page !== "interfaces" && /* @__PURE__ */ jsx11(
2640
- NavigationMenu,
3772
+ page === "dev" && appConfig && /* @__PURE__ */ jsx15(
3773
+ DevPage,
2641
3774
  {
2642
- items: subpageMenuItems,
2643
- onSelect: handleSubpageNavigate
3775
+ appConfig,
3776
+ onNavigate: handleNavigate,
3777
+ termHeight
2644
3778
  }
2645
3779
  )
2646
3780
  ] }) });
@@ -2656,7 +3790,7 @@ function getInstallMethod() {
2656
3790
  return "npm";
2657
3791
  }
2658
3792
  function getCurrentVersion() {
2659
- return "0.5.7";
3793
+ return "0.5.9";
2660
3794
  }
2661
3795
  async function fetchLatestVersion() {
2662
3796
  try {
@@ -2702,15 +3836,15 @@ async function checkForUpdate() {
2702
3836
  }
2703
3837
 
2704
3838
  // src/tui/components/UpdatePrompt.tsx
2705
- import { useState as useState17, useEffect as useEffect17, useMemo as useMemo8 } from "react";
2706
- import { Box as Box11, Text as Text11, useInput as useInput7, useStdout as useStdout8 } from "ink";
3839
+ import { useState as useState22, useEffect as useEffect21, useMemo as useMemo9 } from "react";
3840
+ import { Box as Box15, Text as Text15, useInput as useInput8, useStdout as useStdout9 } from "ink";
2707
3841
  import chalk4 from "chalk";
2708
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3842
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
2709
3843
  var SHIMMER_SPEED2 = 35;
2710
3844
  function useShimmerLogo2() {
2711
- const [frame, setFrame] = useState17(0);
2712
- const lines = useMemo8(() => LogoString.split("\n"), []);
2713
- const totalChars = useMemo8(() => {
3845
+ const [frame, setFrame] = useState22(0);
3846
+ const lines = useMemo9(() => LogoString.split("\n"), []);
3847
+ const totalChars = useMemo9(() => {
2714
3848
  let count = 0;
2715
3849
  for (const line of lines) {
2716
3850
  for (const ch of line) {
@@ -2720,13 +3854,13 @@ function useShimmerLogo2() {
2720
3854
  return count;
2721
3855
  }, [lines]);
2722
3856
  const cycleLength = totalChars + 40;
2723
- useEffect17(() => {
3857
+ useEffect21(() => {
2724
3858
  const interval = setInterval(() => {
2725
3859
  setFrame((f) => (f + 1) % cycleLength);
2726
3860
  }, SHIMMER_SPEED2);
2727
3861
  return () => clearInterval(interval);
2728
3862
  }, [cycleLength]);
2729
- return useMemo8(() => {
3863
+ return useMemo9(() => {
2730
3864
  const sweepPos = frame;
2731
3865
  const holdEnd = totalChars + 20;
2732
3866
  let charIdx = 0;
@@ -2769,19 +3903,19 @@ function UpdatePrompt({
2769
3903
  latestVersion,
2770
3904
  onChoice
2771
3905
  }) {
2772
- const { stdout } = useStdout8();
3906
+ const { stdout } = useStdout9();
2773
3907
  const shimmerLogo = useShimmerLogo2();
2774
- useInput7(() => {
3908
+ useInput8(() => {
2775
3909
  onChoice(true);
2776
3910
  });
2777
3911
  const termHeight = (stdout?.rows ?? 24) - 4;
2778
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", height: termHeight, children: [
2779
- /* @__PURE__ */ jsx12(Box11, { flexGrow: 1 }),
2780
- /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", alignItems: "center", children: [
2781
- /* @__PURE__ */ jsx12(Text11, { children: shimmerLogo }),
2782
- /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", alignItems: "center", marginTop: 2, children: /* @__PURE__ */ jsx12(Text11, { bold: true, color: "white", children: "MindStudio Local Tunnel" }) }),
2783
- /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", alignItems: "center", children: [
2784
- /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
3912
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", height: termHeight, children: [
3913
+ /* @__PURE__ */ jsx16(Box15, { flexGrow: 1 }),
3914
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", alignItems: "center", children: [
3915
+ /* @__PURE__ */ jsx16(Text15, { children: shimmerLogo }),
3916
+ /* @__PURE__ */ jsx16(Box15, { flexDirection: "column", alignItems: "center", marginTop: 2, children: /* @__PURE__ */ jsx16(Text15, { bold: true, color: "white", children: "MindStudio Local Tunnel" }) }),
3917
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", alignItems: "center", children: [
3918
+ /* @__PURE__ */ jsxs15(Text15, { color: "gray", children: [
2785
3919
  "Update required ",
2786
3920
  "\u2022",
2787
3921
  " v",
@@ -2791,19 +3925,19 @@ function UpdatePrompt({
2791
3925
  " v",
2792
3926
  latestVersion
2793
3927
  ] }),
2794
- /* @__PURE__ */ jsx12(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text11, { color: "cyan", bold: true, children: "Press any key to update" }) })
3928
+ /* @__PURE__ */ jsx16(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text15, { color: "cyan", bold: true, children: "Press any key to update" }) })
2795
3929
  ] })
2796
3930
  ] }),
2797
- /* @__PURE__ */ jsx12(Box11, { flexGrow: 1 })
3931
+ /* @__PURE__ */ jsx16(Box15, { flexGrow: 1 })
2798
3932
  ] });
2799
3933
  }
2800
3934
 
2801
3935
  // src/tui/index.tsx
2802
- import { jsx as jsx13 } from "react/jsx-runtime";
3936
+ import { jsx as jsx17 } from "react/jsx-runtime";
2803
3937
  async function promptForUpdate(currentVersion, latestVersion) {
2804
3938
  return new Promise((resolve) => {
2805
3939
  const { unmount } = render(
2806
- /* @__PURE__ */ jsx13(
3940
+ /* @__PURE__ */ jsx17(
2807
3941
  UpdatePrompt,
2808
3942
  {
2809
3943
  currentVersion,
@@ -2886,14 +4020,16 @@ async function startTUI() {
2886
4020
  }
2887
4021
  console.clear();
2888
4022
  }
4023
+ const appConfig = detectAppConfig(process.cwd());
2889
4024
  const runner = new TunnelRunner();
2890
- const { waitUntilExit } = render(/* @__PURE__ */ jsx13(App, { runner }), {
2891
- exitOnCtrlC: true
2892
- });
4025
+ const { waitUntilExit } = render(
4026
+ /* @__PURE__ */ jsx17(App, { runner, appConfig: appConfig ?? void 0 }),
4027
+ { exitOnCtrlC: true }
4028
+ );
2893
4029
  await waitUntilExit();
2894
4030
  runner.stop();
2895
4031
  }
2896
4032
  export {
2897
4033
  startTUI
2898
4034
  };
2899
- //# sourceMappingURL=tui-UNFZSO7R.js.map
4035
+ //# sourceMappingURL=tui-343LXUFQ.js.map