@mindstudio-ai/local-model-tunnel 0.3.4 → 0.4.0

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,26 +1,34 @@
1
1
  import {
2
2
  TunnelRunner,
3
+ deleteLocalInterfacePath,
3
4
  detectAllProviderStatuses,
4
5
  discoverAllModelsWithParameters,
6
+ getApiBaseUrl,
5
7
  getApiKey,
6
8
  getConfigPath,
9
+ getEditorSessions,
7
10
  getEnvironment,
11
+ getLocalInterfacePath,
12
+ getLocalInterfacesDir,
8
13
  getSyncedModels,
14
+ getUserId,
9
15
  pollDeviceAuth,
10
16
  requestDeviceAuth,
11
17
  requestEvents,
12
18
  setApiKey,
19
+ setLocalInterfacePath,
20
+ setUserId,
13
21
  syncModels,
14
22
  verifyApiKey
15
- } from "./chunk-PPP2FEIN.js";
23
+ } from "./chunk-44NXQXRB.js";
16
24
 
17
25
  // src/tui/index.tsx
18
26
  import { render } from "ink";
19
- import { execFileSync, execSync } from "child_process";
27
+ import { execFileSync, execSync as execSync2 } from "child_process";
20
28
 
21
29
  // src/tui/App.tsx
22
- import { useEffect as useEffect10, useCallback as useCallback8, useState as useState10, useRef as useRef5 } from "react";
23
- import { Box as Box7, useApp, useStdout as useStdout6 } from "ink";
30
+ import { useEffect as useEffect15, useCallback as useCallback10, useState as useState15, useRef as useRef9 } from "react";
31
+ import { Box as Box10, useApp, useStdout as useStdout7 } from "ink";
24
32
 
25
33
  // src/tui/components/Header.tsx
26
34
  import os from "os";
@@ -111,8 +119,6 @@ function NavigationMenu({ items, onSelect, title }) {
111
119
  const { stdout } = useStdout();
112
120
  const compact = (stdout?.rows ?? 24) < 40;
113
121
  const getDefaultIndex = () => {
114
- const backIdx = items.findIndex((i) => i.id === "back");
115
- if (backIdx >= 0) return backIdx;
116
122
  const firstIdx = items.findIndex((i) => !i.disabled && !i.isSeparator);
117
123
  return firstIdx >= 0 ? firstIdx : 0;
118
124
  };
@@ -212,7 +218,8 @@ function useConnection() {
212
218
  setStatus("connecting");
213
219
  setError(null);
214
220
  const apiKey = getApiKey();
215
- if (!apiKey) {
221
+ const userId = getUserId();
222
+ if (!apiKey || !userId) {
216
223
  setStatus("not_authenticated");
217
224
  return;
218
225
  }
@@ -239,7 +246,7 @@ function useConnection() {
239
246
  };
240
247
  }
241
248
 
242
- // src/tui/hooks/useSetupProviders.ts
249
+ // src/tui/models/hooks/useSetupProviders.ts
243
250
  import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2, useRef } from "react";
244
251
  function useSetupProviders() {
245
252
  const [providers, setProviders] = useState3([]);
@@ -264,7 +271,7 @@ function useSetupProviders() {
264
271
  return { providers, loading, refreshing, refresh };
265
272
  }
266
273
 
267
- // src/tui/hooks/useModels.ts
274
+ // src/tui/models/hooks/useModels.ts
268
275
  import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3, useRef as useRef2 } from "react";
269
276
  function useModels() {
270
277
  const [models, setModels] = useState4([]);
@@ -303,7 +310,7 @@ function useModels() {
303
310
  };
304
311
  }
305
312
 
306
- // src/tui/hooks/useRequests.ts
313
+ // src/tui/models/hooks/useRequests.ts
307
314
  import { useState as useState5, useEffect as useEffect5, useCallback as useCallback4, useRef as useRef3 } from "react";
308
315
  function useRequests(maxHistory = 50) {
309
316
  const [requests, setRequests] = useState5([]);
@@ -379,7 +386,7 @@ function useRequests(maxHistory = 50) {
379
386
  };
380
387
  }
381
388
 
382
- // src/tui/hooks/useRegisteredModels.ts
389
+ // src/tui/models/hooks/useRegisteredModels.ts
383
390
  import { useState as useState6, useEffect as useEffect6, useCallback as useCallback5 } from "react";
384
391
  function useSyncedModels(connectionStatus) {
385
392
  const [syncedNames, setSyncedNames] = useState6(
@@ -409,12 +416,12 @@ function useSyncedModels(connectionStatus) {
409
416
  };
410
417
  }
411
418
 
412
- // src/tui/pages/DashboardPage.tsx
419
+ // src/tui/models/pages/DashboardPage.tsx
413
420
  import { useMemo } from "react";
414
421
  import { Box as Box4, Text as Text4, useStdout as useStdout3 } from "ink";
415
422
  import Spinner2 from "ink-spinner";
416
423
 
417
- // src/tui/components/RequestLog.tsx
424
+ // src/tui/models/components/RequestLog.tsx
418
425
  import { Box as Box3, Text as Text3, useStdout as useStdout2 } from "ink";
419
426
  import Spinner from "ink-spinner";
420
427
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -580,7 +587,7 @@ function RequestLog({ requests, maxVisible = 8, hasModels = true }) {
580
587
  );
581
588
  }
582
589
 
583
- // src/tui/pages/DashboardPage.tsx
590
+ // src/tui/models/pages/DashboardPage.tsx
584
591
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
585
592
  function getWorkflowCount(model) {
586
593
  const param = model.parameters?.find((p) => p.type === "comfyWorkflow");
@@ -624,6 +631,11 @@ function DashboardPage({
624
631
  const syncDescription = syncStatus === "syncing" ? "Syncing..." : syncStatus === "synced" ? "\u2713 Synced" : "Re-detect providers and sync models to MindStudio";
625
632
  const menuItems = useMemo(() => {
626
633
  return [
634
+ {
635
+ id: "interfaces",
636
+ label: "Connect to IDE",
637
+ description: "Connect your local editor to a MindStudio interface or script"
638
+ },
627
639
  {
628
640
  id: "refresh",
629
641
  label: "Sync Models",
@@ -735,7 +747,7 @@ function DashboardPage({
735
747
  ] });
736
748
  }
737
749
 
738
- // src/tui/pages/SetupPage.tsx
750
+ // src/tui/models/pages/SetupPage.tsx
739
751
  import { useState as useState7, useMemo as useMemo3, useEffect as useEffect7 } from "react";
740
752
  import { Box as Box5, Text as Text6, useInput as useInput2, useStdout as useStdout5 } from "ink";
741
753
  import Spinner3 from "ink-spinner";
@@ -775,7 +787,7 @@ function renderMarkdown(content, width) {
775
787
  return marked.parse(content).trimEnd();
776
788
  }
777
789
 
778
- // src/tui/pages/SetupPage.tsx
790
+ // src/tui/models/pages/SetupPage.tsx
779
791
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
780
792
  function ProviderDetailView({
781
793
  provider,
@@ -895,10 +907,10 @@ function SetupPage({ onBack }) {
895
907
  );
896
908
  const totalItems = allProviders.length + 1;
897
909
  const backIndex = allProviders.length;
898
- const [cursorIndex, setCursorIndex] = useState7(backIndex);
910
+ const [cursorIndex, setCursorIndex] = useState7(0);
899
911
  useEffect7(() => {
900
- setCursorIndex(backIndex);
901
- }, [backIndex]);
912
+ setCursorIndex(0);
913
+ }, [allProviders.length]);
902
914
  useInput2((input, key) => {
903
915
  if (selectedProvider) return;
904
916
  if (input === "q" || key.escape) {
@@ -1075,30 +1087,986 @@ function SetupPage({ onBack }) {
1075
1087
  ] }) });
1076
1088
  }
1077
1089
 
1078
- // src/tui/pages/OnboardingPage.tsx
1079
- import { useEffect as useEffect9, useCallback as useCallback7, useState as useState9, useMemo as useMemo4 } from "react";
1090
+ // src/tui/interfaces/pages/InterfacesPage.tsx
1091
+ import { useState as useState12, useMemo as useMemo5, useEffect as useEffect12, useRef as useRef7 } from "react";
1092
+ import { Box as Box8, Text as Text9, useInput as useInput5 } from "ink";
1093
+ import Spinner5 from "ink-spinner";
1094
+
1095
+ // src/tui/interfaces/hooks/useEditorSessions.ts
1096
+ import { useState as useState8, useEffect as useEffect8, useCallback as useCallback6, useRef as useRef4 } from "react";
1097
+ function useEditorSessions() {
1098
+ const [sessions, setSessions] = useState8([]);
1099
+ const [loading, setLoading] = useState8(true);
1100
+ const [error, setError] = useState8(null);
1101
+ const [refreshStatus, setRefreshStatus] = useState8("idle");
1102
+ const initialLoadDone = useRef4(false);
1103
+ const timerRef = useRef4();
1104
+ const refresh = useCallback6(async () => {
1105
+ if (!initialLoadDone.current) {
1106
+ setLoading(true);
1107
+ } else {
1108
+ setRefreshStatus("refreshing");
1109
+ }
1110
+ setError(null);
1111
+ try {
1112
+ const data = await getEditorSessions();
1113
+ setSessions(data);
1114
+ initialLoadDone.current = true;
1115
+ setRefreshStatus("refreshed");
1116
+ clearTimeout(timerRef.current);
1117
+ timerRef.current = setTimeout(() => setRefreshStatus("idle"), 1500);
1118
+ } catch (err) {
1119
+ setError(err instanceof Error ? err.message : "Failed to fetch sessions");
1120
+ setRefreshStatus("idle");
1121
+ } finally {
1122
+ setLoading(false);
1123
+ }
1124
+ }, []);
1125
+ useEffect8(() => {
1126
+ refresh();
1127
+ }, [refresh]);
1128
+ useEffect8(() => {
1129
+ return () => clearTimeout(timerRef.current);
1130
+ }, []);
1131
+ return { sessions, loading, error, refreshStatus, refresh };
1132
+ }
1133
+
1134
+ // src/tui/interfaces/hooks/useLocalInterface.ts
1135
+ import { useState as useState9, useCallback as useCallback7, useRef as useRef5, useEffect as useEffect9 } from "react";
1136
+ import { spawn } from "child_process";
1137
+ import crypto from "crypto";
1138
+ import fs from "fs";
1139
+ import path from "path";
1140
+ var INTERFACE_SCAFFOLD_REPO = "https://github.com/mindstudio-ai/spa-bundle-scaffold";
1141
+ var SCRIPT_SCAFFOLD_REPO = "https://github.com/mindstudio-ai/script-scaffold";
1142
+ var MAX_OUTPUT_LINES = 500;
1143
+ function useLocalInterface({
1144
+ mode,
1145
+ appId,
1146
+ stepId,
1147
+ workflowId,
1148
+ sessionId
1149
+ }) {
1150
+ const key = `${appId}:${stepId}`;
1151
+ const [hasLocalCopy, setHasLocalCopy] = useState9(() => {
1152
+ const existing = getLocalInterfacePath(key);
1153
+ if (!existing) return false;
1154
+ try {
1155
+ return fs.existsSync(existing);
1156
+ } catch {
1157
+ return false;
1158
+ }
1159
+ });
1160
+ const [phase, setPhase] = useState9("idle");
1161
+ const [outputLines, setOutputLines] = useState9([]);
1162
+ const [errorMessage, setErrorMessage] = useState9(null);
1163
+ const processRef = useRef5(null);
1164
+ const mountedRef = useRef5(true);
1165
+ const stoppedRef = useRef5(false);
1166
+ useEffect9(() => {
1167
+ mountedRef.current = true;
1168
+ return () => {
1169
+ mountedRef.current = false;
1170
+ if (processRef.current) {
1171
+ processRef.current.kill("SIGTERM");
1172
+ processRef.current = null;
1173
+ }
1174
+ };
1175
+ }, []);
1176
+ const appendOutput = useCallback7((line) => {
1177
+ if (!mountedRef.current) return;
1178
+ setOutputLines((prev) => {
1179
+ const next = [...prev, line];
1180
+ return next.length > MAX_OUTPUT_LINES ? next.slice(next.length - MAX_OUTPUT_LINES) : next;
1181
+ });
1182
+ }, []);
1183
+ const runCommand = useCallback7(
1184
+ (command, args, options = {}) => {
1185
+ return new Promise((resolve, reject) => {
1186
+ const fullCommand = [command, ...args].join(" ");
1187
+ const proc = spawn(fullCommand, [], {
1188
+ cwd: options.cwd,
1189
+ shell: true,
1190
+ env: { ...process.env, FORCE_COLOR: "1", ...options.env }
1191
+ });
1192
+ processRef.current = proc;
1193
+ const handleData = (data) => {
1194
+ const lines = data.toString().split("\n");
1195
+ for (const line of lines) {
1196
+ if (line.length > 0) {
1197
+ appendOutput(line);
1198
+ }
1199
+ }
1200
+ };
1201
+ proc.stdout?.on("data", handleData);
1202
+ proc.stderr?.on("data", handleData);
1203
+ proc.on("close", (code) => {
1204
+ processRef.current = null;
1205
+ resolve(code ?? 0);
1206
+ });
1207
+ proc.on("error", (err) => {
1208
+ processRef.current = null;
1209
+ reject(err);
1210
+ });
1211
+ });
1212
+ },
1213
+ [appendOutput]
1214
+ );
1215
+ const getScaffoldRepo = () => mode === "script" ? SCRIPT_SCAFFOLD_REPO : INTERFACE_SCAFFOLD_REPO;
1216
+ const getDirPrefix = () => mode === "script" ? "script" : "interface";
1217
+ const getDevLocalArgs = () => {
1218
+ const env = {};
1219
+ if (getEnvironment() === "local") {
1220
+ env.MINDSTUDIO_API_URL = getApiBaseUrl();
1221
+ }
1222
+ if (mode === "script") {
1223
+ env.MINDSTUDIO_API_KEY = getApiKey() ?? "";
1224
+ return {
1225
+ args: ["run", "dev:local", "--", "--app", appId, "--workflow", workflowId, "--step", stepId],
1226
+ env
1227
+ };
1228
+ }
1229
+ return {
1230
+ args: ["run", "dev:local", "--", sessionId ?? ""],
1231
+ env
1232
+ };
1233
+ };
1234
+ const start = useCallback7(() => {
1235
+ setErrorMessage(null);
1236
+ setOutputLines([]);
1237
+ stoppedRef.current = false;
1238
+ const run = async () => {
1239
+ const localPath = getLocalInterfacePath(key);
1240
+ const dirExists = localPath && fs.existsSync(localPath);
1241
+ try {
1242
+ if (!dirExists) {
1243
+ setPhase("cloning");
1244
+ const interfacesDir = getLocalInterfacesDir();
1245
+ fs.mkdirSync(interfacesDir, { recursive: true });
1246
+ const shortId = crypto.randomBytes(4).toString("hex");
1247
+ const dirName = `${getDirPrefix()}-${shortId}`;
1248
+ const targetDir = path.join(interfacesDir, dirName);
1249
+ appendOutput(`Cloning scaffold into ${targetDir}...`);
1250
+ const cloneCode = await runCommand("git", [
1251
+ "clone",
1252
+ "--depth",
1253
+ "1",
1254
+ getScaffoldRepo(),
1255
+ targetDir
1256
+ ]);
1257
+ if (!mountedRef.current) return;
1258
+ if (cloneCode !== 0) {
1259
+ throw new Error(`git clone failed with exit code ${cloneCode}`);
1260
+ }
1261
+ setPhase("installing");
1262
+ appendOutput("Installing dependencies...");
1263
+ const installCode = await runCommand("npm", ["install"], {
1264
+ cwd: targetDir
1265
+ });
1266
+ if (!mountedRef.current) return;
1267
+ if (installCode !== 0) {
1268
+ throw new Error(`npm install failed with exit code ${installCode}`);
1269
+ }
1270
+ setLocalInterfacePath(key, targetDir);
1271
+ setHasLocalCopy(true);
1272
+ setPhase("running");
1273
+ const { args, env } = getDevLocalArgs();
1274
+ appendOutput("Starting local dev server...");
1275
+ await runCommand("npm", args, { cwd: targetDir, env });
1276
+ if (mountedRef.current) {
1277
+ setPhase("idle");
1278
+ setOutputLines([]);
1279
+ }
1280
+ } else {
1281
+ setPhase("running");
1282
+ const { args, env } = getDevLocalArgs();
1283
+ appendOutput("Starting local dev server...");
1284
+ await runCommand("npm", args, { cwd: localPath, env });
1285
+ if (mountedRef.current) {
1286
+ setPhase("idle");
1287
+ setOutputLines([]);
1288
+ }
1289
+ }
1290
+ } catch (err) {
1291
+ if (mountedRef.current) {
1292
+ setPhase("error");
1293
+ setErrorMessage(
1294
+ err instanceof Error ? err.message : "Unknown error"
1295
+ );
1296
+ }
1297
+ }
1298
+ };
1299
+ run();
1300
+ }, [key, mode, appId, stepId, workflowId, sessionId, appendOutput, runCommand]);
1301
+ const stop = useCallback7(() => {
1302
+ stoppedRef.current = true;
1303
+ if (processRef.current) {
1304
+ processRef.current.kill("SIGTERM");
1305
+ const proc = processRef.current;
1306
+ setTimeout(() => {
1307
+ if (proc && !proc.killed) {
1308
+ proc.kill("SIGKILL");
1309
+ }
1310
+ }, 2e3);
1311
+ processRef.current = null;
1312
+ }
1313
+ setPhase("idle");
1314
+ }, []);
1315
+ const deleteLocalCopy = useCallback7(() => {
1316
+ const localPath = getLocalInterfacePath(key);
1317
+ if (!localPath) return;
1318
+ setPhase("deleting");
1319
+ setOutputLines([]);
1320
+ appendOutput(`Deleting ${localPath}...`);
1321
+ try {
1322
+ fs.rmSync(localPath, { recursive: true, force: true });
1323
+ deleteLocalInterfacePath(key);
1324
+ setHasLocalCopy(false);
1325
+ appendOutput("Deleted successfully.");
1326
+ } catch (err) {
1327
+ setErrorMessage(
1328
+ err instanceof Error ? err.message : "Failed to delete"
1329
+ );
1330
+ }
1331
+ setPhase("idle");
1332
+ }, [key, appendOutput]);
1333
+ return {
1334
+ phase,
1335
+ hasLocalCopy,
1336
+ localPath: getLocalInterfacePath(key),
1337
+ outputLines,
1338
+ errorMessage,
1339
+ start,
1340
+ stop,
1341
+ deleteLocalCopy
1342
+ };
1343
+ }
1344
+
1345
+ // src/tui/interfaces/pages/InterfaceSessionView.tsx
1346
+ import { useState as useState10, useEffect as useEffect10 } from "react";
1080
1347
  import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
1348
+ import os2 from "os";
1349
+ import open from "open";
1350
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1351
+ function InterfaceSessionView({
1352
+ item,
1353
+ onStart,
1354
+ onDelete,
1355
+ onBack,
1356
+ hasLocalCopy,
1357
+ localPath
1358
+ }) {
1359
+ const menuItems = [
1360
+ { id: "start", label: "Start Locally" }
1361
+ ];
1362
+ if (hasLocalCopy) {
1363
+ menuItems.push({ id: "reveal", label: "Open Folder" });
1364
+ menuItems.push({ id: "delete", label: "Delete Local Copy" });
1365
+ }
1366
+ menuItems.push({ id: "back", label: "Back" });
1367
+ const [cursorIndex, setCursorIndex] = useState10(0);
1368
+ useEffect10(() => {
1369
+ setCursorIndex(0);
1370
+ }, [hasLocalCopy]);
1371
+ useInput3((input, key) => {
1372
+ if (input === "q" || key.escape) {
1373
+ onBack();
1374
+ return;
1375
+ }
1376
+ if (key.upArrow) {
1377
+ setCursorIndex((prev) => Math.max(0, prev - 1));
1378
+ } else if (key.downArrow) {
1379
+ setCursorIndex((prev) => Math.min(menuItems.length - 1, prev + 1));
1380
+ } else if (key.return) {
1381
+ const selected = menuItems[cursorIndex];
1382
+ if (!selected) return;
1383
+ switch (selected.id) {
1384
+ case "start":
1385
+ onStart();
1386
+ break;
1387
+ case "reveal":
1388
+ if (localPath) {
1389
+ open(localPath);
1390
+ }
1391
+ break;
1392
+ case "delete":
1393
+ onDelete();
1394
+ break;
1395
+ case "back":
1396
+ onBack();
1397
+ break;
1398
+ }
1399
+ }
1400
+ });
1401
+ const name = `${item.step.workflowName} - ${item.step.displayName}`;
1402
+ const displayPath = localPath?.replace(os2.homedir(), "~");
1403
+ let sessionInfo = null;
1404
+ if (item.kind === "interface") {
1405
+ const hotUpdateDomain = item.step.spaEditorSession?.hotUpdateDomain ?? "";
1406
+ sessionInfo = hotUpdateDomain.replace(/^https?:\/\//, "").split(".")[0] || null;
1407
+ }
1408
+ return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
1409
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "white", underline: true, children: name }),
1410
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1411
+ sessionInfo && /* @__PURE__ */ jsxs6(Text7, { color: "gray", children: [
1412
+ "Session: ",
1413
+ sessionInfo
1414
+ ] }),
1415
+ hasLocalCopy && localPath ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
1416
+ /* @__PURE__ */ jsx7(Text7, { color: "green", children: "Local copy exists" }),
1417
+ /* @__PURE__ */ jsxs6(Text7, { color: "gray", children: [
1418
+ "Path: ",
1419
+ displayPath
1420
+ ] })
1421
+ ] }) : /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: 'No local copy. "Start Locally" will clone the scaffold and install dependencies.' })
1422
+ ] }),
1423
+ /* @__PURE__ */ jsx7(Box6, { marginTop: 1, flexDirection: "column", children: menuItems.map((menuItem, i) => {
1424
+ const isSelected = i === cursorIndex;
1425
+ return /* @__PURE__ */ jsxs6(
1426
+ Text7,
1427
+ {
1428
+ color: isSelected ? "cyan" : "white",
1429
+ bold: isSelected,
1430
+ children: [
1431
+ isSelected ? "\u276F" : " ",
1432
+ " ",
1433
+ menuItem.label
1434
+ ]
1435
+ },
1436
+ menuItem.id
1437
+ );
1438
+ }) }),
1439
+ /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "gray", children: [
1440
+ "Up/Down Navigate ",
1441
+ "\u2022",
1442
+ " Enter Select ",
1443
+ "\u2022",
1444
+ " q/Esc Back"
1445
+ ] }) })
1446
+ ] }) });
1447
+ }
1448
+
1449
+ // src/tui/interfaces/pages/InterfaceRunningView.tsx
1450
+ import { useState as useState11, useMemo as useMemo4, useRef as useRef6, useEffect as useEffect11 } from "react";
1451
+ import { Box as Box7, Text as Text8, useInput as useInput4, useStdout as useStdout6 } from "ink";
1452
+ import { execSync } from "child_process";
1453
+ import os3 from "os";
1454
+ import open2 from "open";
1081
1455
  import Spinner4 from "ink-spinner";
1456
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1457
+ function copyToClipboard(text) {
1458
+ try {
1459
+ if (process.platform === "darwin") {
1460
+ execSync("pbcopy", { input: text });
1461
+ } else if (process.platform === "win32") {
1462
+ execSync("clip", { input: text });
1463
+ } else {
1464
+ execSync("xclip -selection clipboard", { input: text });
1465
+ }
1466
+ return true;
1467
+ } catch {
1468
+ return false;
1469
+ }
1470
+ }
1471
+ function getPhaseLabel(phase) {
1472
+ switch (phase) {
1473
+ case "cloning":
1474
+ return { text: "Cloning scaffold...", color: "cyan", showSpinner: true };
1475
+ case "installing":
1476
+ return { text: "Installing dependencies...", color: "cyan", showSpinner: true };
1477
+ case "running":
1478
+ return { text: "Dev server running", color: "green", showSpinner: true };
1479
+ case "error":
1480
+ return { text: "Error", color: "red", showSpinner: false };
1481
+ case "deleting":
1482
+ return { text: "Deleting local copy...", color: "yellow", showSpinner: true };
1483
+ default:
1484
+ return { text: "Idle", color: "gray", showSpinner: false };
1485
+ }
1486
+ }
1487
+ function InterfaceRunningView({
1488
+ name,
1489
+ phase,
1490
+ outputLines,
1491
+ errorMessage,
1492
+ localPath,
1493
+ onStop,
1494
+ onBack
1495
+ }) {
1496
+ const { stdout } = useStdout6();
1497
+ const termHeight = (stdout?.rows ?? 24) - 4;
1498
+ const isActive = phase === "cloning" || phase === "installing" || phase === "running" || phase === "deleting";
1499
+ const displayPath = localPath?.replace(os3.homedir(), "~");
1500
+ const menuItems = useMemo4(() => {
1501
+ const items = [];
1502
+ if (isActive && displayPath) {
1503
+ items.push({ id: "claude", label: "Copy Claude Code Command", copyValue: `cd ${displayPath} && claude` });
1504
+ items.push({ id: "codex", label: "Copy Codex Command", copyValue: `cd ${displayPath} && codex` });
1505
+ items.push({ id: "reveal", label: "Open Folder" });
1506
+ }
1507
+ items.push({ id: "action", label: isActive ? "Stop" : "Back" });
1508
+ return items;
1509
+ }, [isActive, displayPath]);
1510
+ const [cursorIndex, setCursorIndex] = useState11(0);
1511
+ const [copiedId, setCopiedId] = useState11(null);
1512
+ const copiedTimerRef = useRef6();
1513
+ useEffect11(() => {
1514
+ return () => clearTimeout(copiedTimerRef.current);
1515
+ }, []);
1516
+ const headerHeight = 14;
1517
+ const menuHeight = menuItems.length;
1518
+ const chromeLines = 7 + menuHeight;
1519
+ const logHeight = Math.max(3, termHeight - headerHeight - chromeLines);
1520
+ const maxScroll = Math.max(0, outputLines.length - logHeight);
1521
+ const effectiveOffset = maxScroll;
1522
+ const copyItem = (item) => {
1523
+ if (!item.copyValue) return;
1524
+ const success = copyToClipboard(item.copyValue);
1525
+ if (success) {
1526
+ setCopiedId(item.id);
1527
+ clearTimeout(copiedTimerRef.current);
1528
+ copiedTimerRef.current = setTimeout(() => setCopiedId(null), 2e3);
1529
+ }
1530
+ };
1531
+ useInput4((input, key) => {
1532
+ if (key.upArrow) {
1533
+ setCursorIndex((prev) => Math.max(0, prev - 1));
1534
+ } else if (key.downArrow) {
1535
+ setCursorIndex((prev) => Math.min(menuItems.length - 1, prev + 1));
1536
+ } else if (key.return) {
1537
+ const item = menuItems[cursorIndex];
1538
+ if (!item) return;
1539
+ if (item.copyValue) {
1540
+ copyItem(item);
1541
+ } else if (item.id === "reveal" && localPath) {
1542
+ open2(localPath);
1543
+ } else if (isActive) {
1544
+ onStop();
1545
+ } else {
1546
+ onBack();
1547
+ }
1548
+ } else if (input === "q" || key.escape) {
1549
+ if (isActive) {
1550
+ onStop();
1551
+ } else {
1552
+ onBack();
1553
+ }
1554
+ }
1555
+ });
1556
+ const visibleLines = outputLines.slice(
1557
+ effectiveOffset,
1558
+ effectiveOffset + logHeight
1559
+ );
1560
+ const phaseInfo = getPhaseLabel(phase);
1561
+ return /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
1562
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: "white", underline: true, children: name }),
1563
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
1564
+ phaseInfo.showSpinner && /* @__PURE__ */ jsxs7(Text8, { color: phaseInfo.color, children: [
1565
+ /* @__PURE__ */ jsx8(Spinner4, { type: "dots" }),
1566
+ " "
1567
+ ] }),
1568
+ /* @__PURE__ */ jsx8(Text8, { color: phaseInfo.color, children: phaseInfo.text })
1569
+ ] }),
1570
+ errorMessage && /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "red", children: errorMessage }) }),
1571
+ /* @__PURE__ */ jsx8(Box7, { marginTop: 1, height: logHeight, children: /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleLines.map((line, i) => /* @__PURE__ */ jsx8(Text8, { wrap: "truncate-end", color: "gray", children: line }, effectiveOffset + i)) }) }),
1572
+ /* @__PURE__ */ jsx8(Box7, { marginTop: 1, flexDirection: "column", children: menuItems.map((item, i) => {
1573
+ const isSelected = i === cursorIndex;
1574
+ const isCopied = copiedId === item.id;
1575
+ return /* @__PURE__ */ jsxs7(Box7, { children: [
1576
+ /* @__PURE__ */ jsxs7(Text8, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
1577
+ isSelected ? "\u276F" : " ",
1578
+ " ",
1579
+ item.label
1580
+ ] }),
1581
+ isCopied && /* @__PURE__ */ jsxs7(Text8, { color: "green", children: [
1582
+ " ",
1583
+ "\u2713",
1584
+ " Copied!"
1585
+ ] })
1586
+ ] }, item.id);
1587
+ }) }),
1588
+ /* @__PURE__ */ jsx8(Box7, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsxs7(Text8, { color: "gray", wrap: "truncate-end", children: [
1589
+ "Up/Down Navigate ",
1590
+ "\u2022",
1591
+ " Enter Select ",
1592
+ "\u2022",
1593
+ " q/Esc ",
1594
+ isActive ? "Stop" : "Back"
1595
+ ] }) })
1596
+ ] }) });
1597
+ }
1598
+
1599
+ // src/tui/interfaces/pages/InterfacesPage.tsx
1600
+ import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1601
+ function getSessionStatus(item) {
1602
+ if (item.kind === "script") return "running";
1603
+ const status = item.step.spaEditorSession?.status;
1604
+ if (status === "running" || status === "compiling") return status;
1605
+ if (status === "starting") return "starting";
1606
+ return "stopped";
1607
+ }
1608
+ function isItemSelectable(item) {
1609
+ const status = getSessionStatus(item);
1610
+ return status === "running" || status === "compiling";
1611
+ }
1612
+ function getStatusLabel(status) {
1613
+ switch (status) {
1614
+ case "running":
1615
+ case "compiling":
1616
+ return { text: "Online", color: "green" };
1617
+ case "starting":
1618
+ return { text: "Starting", color: "yellow" };
1619
+ case "stopped":
1620
+ return { text: "Offline", color: "gray" };
1621
+ }
1622
+ }
1623
+ function getRefreshSuffix(status) {
1624
+ switch (status) {
1625
+ case "refreshing":
1626
+ return "Refreshing...";
1627
+ case "refreshed":
1628
+ return "\u2713 Refreshed";
1629
+ case "idle":
1630
+ return null;
1631
+ }
1632
+ }
1633
+ function formatCount(interfaces, scripts) {
1634
+ const parts = [];
1635
+ if (interfaces > 0) {
1636
+ parts.push(`${interfaces} Interface${interfaces !== 1 ? "s" : ""}`);
1637
+ }
1638
+ if (scripts > 0) {
1639
+ parts.push(`${scripts} Script${scripts !== 1 ? "s" : ""}`);
1640
+ }
1641
+ return parts.join(", ");
1642
+ }
1643
+ function OfflineView({
1644
+ item,
1645
+ onBack
1646
+ }) {
1647
+ useInput5((input, key) => {
1648
+ if (input === "q" || key.escape || key.return) {
1649
+ onBack();
1650
+ }
1651
+ });
1652
+ const name = item.kind === "interface" ? `${item.step.workflowName} - ${item.step.displayName}` : `${item.step.workflowName} - ${item.step.displayName}`;
1653
+ return /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
1654
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: name }),
1655
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
1656
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "This interface is offline." }),
1657
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", children: "Start the interface designer in MindStudio to connect to it." })
1658
+ ] }),
1659
+ /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { color: "cyan", bold: true, children: [
1660
+ "\u276F",
1661
+ " Back"
1662
+ ] }) }),
1663
+ /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "gray", children: "Enter/q/Esc Back" }) })
1664
+ ] }) });
1665
+ }
1666
+ function AgentListView({
1667
+ sessions,
1668
+ onBack,
1669
+ onSelectAgent,
1670
+ onRefresh,
1671
+ refreshStatus
1672
+ }) {
1673
+ const refreshIndex = sessions.length;
1674
+ const backIndex = sessions.length + 1;
1675
+ const totalItems = sessions.length + 2;
1676
+ const [cursorIndex, setCursorIndex] = useState12(0);
1677
+ useEffect12(() => {
1678
+ setCursorIndex(sessions.length > 0 ? 0 : backIndex);
1679
+ }, [sessions.length, backIndex]);
1680
+ useInput5((input, key) => {
1681
+ if (input === "q" || key.escape) {
1682
+ onBack();
1683
+ return;
1684
+ }
1685
+ if (key.upArrow) {
1686
+ setCursorIndex((prev) => Math.max(0, prev - 1));
1687
+ } else if (key.downArrow) {
1688
+ setCursorIndex((prev) => Math.min(totalItems - 1, prev + 1));
1689
+ } else if (key.return) {
1690
+ if (cursorIndex === backIndex) {
1691
+ onBack();
1692
+ } else if (cursorIndex === refreshIndex) {
1693
+ onRefresh();
1694
+ } else if (sessions[cursorIndex]) {
1695
+ onSelectAgent(sessions[cursorIndex].appId);
1696
+ }
1697
+ }
1698
+ });
1699
+ return /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
1700
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: "Choose an Agent" }),
1701
+ sessions.length === 0 ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
1702
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "No active editor sessions." }),
1703
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", children: "Open an app in MindStudio's editor to see sessions here." })
1704
+ ] }) : /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", marginTop: 1, children: sessions.map((session, i) => {
1705
+ const isSelected = i === cursorIndex;
1706
+ const stats = formatCount(
1707
+ session.customInterfaceSteps.length,
1708
+ session.scriptSteps.length
1709
+ );
1710
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: i > 0 ? 1 : 0, children: [
1711
+ /* @__PURE__ */ jsxs8(Box8, { children: [
1712
+ /* @__PURE__ */ jsxs8(Text9, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
1713
+ isSelected ? "\u276F" : " ",
1714
+ " ",
1715
+ session.appName
1716
+ ] }),
1717
+ isSelected && /* @__PURE__ */ jsx9(Text9, { color: "gray", children: " - Connect to this Agent" })
1718
+ ] }),
1719
+ /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
1720
+ " ",
1721
+ "https://app.mindstudio.ai/agents/",
1722
+ session.appId,
1723
+ "/edit"
1724
+ ] }),
1725
+ /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
1726
+ " ",
1727
+ stats
1728
+ ] })
1729
+ ] }, session.appId);
1730
+ }) }),
1731
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
1732
+ /* @__PURE__ */ jsxs8(Box8, { children: [
1733
+ /* @__PURE__ */ jsxs8(
1734
+ Text9,
1735
+ {
1736
+ color: cursorIndex === refreshIndex ? "cyan" : "white",
1737
+ bold: cursorIndex === refreshIndex,
1738
+ children: [
1739
+ cursorIndex === refreshIndex ? "\u276F" : " ",
1740
+ " Refresh"
1741
+ ]
1742
+ }
1743
+ ),
1744
+ getRefreshSuffix(refreshStatus) && /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
1745
+ " ",
1746
+ getRefreshSuffix(refreshStatus)
1747
+ ] })
1748
+ ] }),
1749
+ /* @__PURE__ */ jsxs8(
1750
+ Text9,
1751
+ {
1752
+ color: cursorIndex === backIndex ? "cyan" : "white",
1753
+ bold: cursorIndex === backIndex,
1754
+ children: [
1755
+ cursorIndex === backIndex ? "\u276F" : " ",
1756
+ " Back"
1757
+ ]
1758
+ }
1759
+ )
1760
+ ] }),
1761
+ /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
1762
+ "Up/Down Navigate ",
1763
+ "\u2022",
1764
+ " Enter Select ",
1765
+ "\u2022",
1766
+ " q/Esc Back"
1767
+ ] }) })
1768
+ ] }) });
1769
+ }
1770
+ function AgentDetailView({
1771
+ session,
1772
+ onBack,
1773
+ onSelect,
1774
+ onRefresh,
1775
+ refreshStatus
1776
+ }) {
1777
+ const [offlineItem, setOfflineItem] = useState12(null);
1778
+ useEffect12(() => {
1779
+ onRefresh();
1780
+ }, []);
1781
+ const interfaces = useMemo5(
1782
+ () => session.customInterfaceSteps.map((step) => ({
1783
+ kind: "interface",
1784
+ appId: session.appId,
1785
+ appName: session.appName,
1786
+ step
1787
+ })),
1788
+ [session]
1789
+ );
1790
+ const scripts = useMemo5(
1791
+ () => session.scriptSteps.map((step) => ({
1792
+ kind: "script",
1793
+ appId: session.appId,
1794
+ appName: session.appName,
1795
+ step
1796
+ })),
1797
+ [session]
1798
+ );
1799
+ const allItems = useMemo5(
1800
+ () => [...interfaces, ...scripts],
1801
+ [interfaces, scripts]
1802
+ );
1803
+ const refreshIndex = allItems.length;
1804
+ const backIndex = allItems.length + 1;
1805
+ const totalItems = allItems.length + 2;
1806
+ const [cursorIndex, setCursorIndex] = useState12(0);
1807
+ useEffect12(() => {
1808
+ setCursorIndex(0);
1809
+ }, [allItems.length]);
1810
+ useInput5((input, key) => {
1811
+ if (offlineItem) return;
1812
+ if (input === "q" || key.escape) {
1813
+ onBack();
1814
+ return;
1815
+ }
1816
+ if (key.upArrow) {
1817
+ setCursorIndex((prev) => Math.max(0, prev - 1));
1818
+ } else if (key.downArrow) {
1819
+ setCursorIndex((prev) => Math.min(totalItems - 1, prev + 1));
1820
+ } else if (key.return) {
1821
+ if (cursorIndex === backIndex) {
1822
+ onBack();
1823
+ } else if (cursorIndex === refreshIndex) {
1824
+ onRefresh();
1825
+ } else {
1826
+ const item = allItems[cursorIndex];
1827
+ if (item) {
1828
+ if (isItemSelectable(item)) {
1829
+ onSelect(item);
1830
+ } else {
1831
+ setOfflineItem(item);
1832
+ }
1833
+ }
1834
+ }
1835
+ }
1836
+ });
1837
+ if (offlineItem) {
1838
+ return /* @__PURE__ */ jsx9(OfflineView, { item: offlineItem, onBack: () => setOfflineItem(null) });
1839
+ }
1840
+ const scriptsOffset = interfaces.length;
1841
+ return /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
1842
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: session.appName }),
1843
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", children: "Select an interface or script to connect your local editor." }),
1844
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
1845
+ interfaces.length > 0 && /* @__PURE__ */ jsxs8(Fragment4, { children: [
1846
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: "Interfaces" }),
1847
+ /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", marginTop: 1, children: interfaces.map((item, i) => {
1848
+ const isSelected = i === cursorIndex;
1849
+ const status = getSessionStatus(item);
1850
+ const statusLabel = getStatusLabel(status);
1851
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: i > 0 ? 1 : 0, children: [
1852
+ /* @__PURE__ */ jsxs8(Text9, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
1853
+ isSelected ? "\u276F" : " ",
1854
+ " ",
1855
+ item.step.workflowName,
1856
+ " - ",
1857
+ item.step.displayName
1858
+ ] }),
1859
+ /* @__PURE__ */ jsx9(Box8, { children: /* @__PURE__ */ jsxs8(Text9, { color: statusLabel.color, children: [
1860
+ " ",
1861
+ statusLabel.text
1862
+ ] }) })
1863
+ ] }, `${item.step.workflowId}:${item.step.stepId}`);
1864
+ }) })
1865
+ ] }),
1866
+ scripts.length > 0 && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: interfaces.length > 0 ? 1 : 0, children: [
1867
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: "Scripts" }),
1868
+ /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", marginTop: 1, children: scripts.map((item, i) => {
1869
+ const index = scriptsOffset + i;
1870
+ const isSelected = index === cursorIndex;
1871
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: i > 0 ? 1 : 0, children: [
1872
+ /* @__PURE__ */ jsxs8(Text9, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
1873
+ isSelected ? "\u276F" : " ",
1874
+ " ",
1875
+ item.step.workflowName,
1876
+ " - ",
1877
+ item.step.displayName
1878
+ ] }),
1879
+ /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
1880
+ " ",
1881
+ item.step.entryFile
1882
+ ] })
1883
+ ] }, `${item.step.workflowId}:${item.step.stepId}`);
1884
+ }) })
1885
+ ] }),
1886
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
1887
+ /* @__PURE__ */ jsxs8(Box8, { children: [
1888
+ /* @__PURE__ */ jsxs8(
1889
+ Text9,
1890
+ {
1891
+ color: cursorIndex === refreshIndex ? "cyan" : "white",
1892
+ bold: cursorIndex === refreshIndex,
1893
+ children: [
1894
+ cursorIndex === refreshIndex ? "\u276F" : " ",
1895
+ " Refresh"
1896
+ ]
1897
+ }
1898
+ ),
1899
+ getRefreshSuffix(refreshStatus) && /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
1900
+ " ",
1901
+ getRefreshSuffix(refreshStatus)
1902
+ ] })
1903
+ ] }),
1904
+ /* @__PURE__ */ jsxs8(
1905
+ Text9,
1906
+ {
1907
+ color: cursorIndex === backIndex ? "cyan" : "white",
1908
+ bold: cursorIndex === backIndex,
1909
+ children: [
1910
+ cursorIndex === backIndex ? "\u276F" : " ",
1911
+ " Back"
1912
+ ]
1913
+ }
1914
+ )
1915
+ ] })
1916
+ ] }),
1917
+ /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
1918
+ "Up/Down Navigate ",
1919
+ "\u2022",
1920
+ " Enter Select ",
1921
+ "\u2022",
1922
+ " q/Esc Back"
1923
+ ] }) })
1924
+ ] }) });
1925
+ }
1926
+ function LocalDevView({
1927
+ item,
1928
+ onBack
1929
+ }) {
1930
+ const mode = item.kind === "script" ? "script" : "interface";
1931
+ let sessionId = "";
1932
+ if (item.kind === "interface") {
1933
+ const hotUpdateDomain = item.step.spaEditorSession?.hotUpdateDomain ?? "";
1934
+ sessionId = hotUpdateDomain.replace(/^https?:\/\//, "").split(".")[0] || "";
1935
+ }
1936
+ const {
1937
+ phase,
1938
+ hasLocalCopy,
1939
+ localPath,
1940
+ outputLines,
1941
+ errorMessage,
1942
+ start,
1943
+ stop,
1944
+ deleteLocalCopy
1945
+ } = useLocalInterface({
1946
+ mode,
1947
+ appId: item.appId,
1948
+ stepId: item.step.stepId,
1949
+ workflowId: item.step.workflowId,
1950
+ sessionId
1951
+ });
1952
+ const name = `${item.step.workflowName} - ${item.step.displayName}`;
1953
+ const isActive = phase === "cloning" || phase === "installing" || phase === "running" || phase === "deleting";
1954
+ const wasActiveRef = useRef7(false);
1955
+ useEffect12(() => {
1956
+ if (isActive) {
1957
+ wasActiveRef.current = true;
1958
+ } else if (wasActiveRef.current && phase === "idle") {
1959
+ wasActiveRef.current = false;
1960
+ onBack();
1961
+ }
1962
+ }, [phase, isActive, onBack]);
1963
+ if (isActive || phase === "error") {
1964
+ return /* @__PURE__ */ jsx9(
1965
+ InterfaceRunningView,
1966
+ {
1967
+ name,
1968
+ phase,
1969
+ outputLines,
1970
+ errorMessage,
1971
+ localPath,
1972
+ onStop: stop,
1973
+ onBack
1974
+ }
1975
+ );
1976
+ }
1977
+ return /* @__PURE__ */ jsx9(
1978
+ InterfaceSessionView,
1979
+ {
1980
+ item,
1981
+ onStart: start,
1982
+ onDelete: deleteLocalCopy,
1983
+ onBack,
1984
+ hasLocalCopy,
1985
+ localPath
1986
+ }
1987
+ );
1988
+ }
1989
+ function InterfacesPage({ onBack }) {
1990
+ const { sessions, loading, error, refreshStatus, refresh } = useEditorSessions();
1991
+ const [selectedAppId, setSelectedAppId] = useState12(null);
1992
+ const [selectedItem, setSelectedItem] = useState12(null);
1993
+ if (loading) {
1994
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", flexGrow: 1, paddingX: 1, marginTop: 1, children: [
1995
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: "Choose an Agent" }),
1996
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
1997
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: /* @__PURE__ */ jsx9(Spinner5, { type: "dots" }) }),
1998
+ /* @__PURE__ */ jsx9(Text9, { children: " Loading editor sessions..." })
1999
+ ] })
2000
+ ] });
2001
+ }
2002
+ if (error) {
2003
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", flexGrow: 1, paddingX: 1, marginTop: 1, children: [
2004
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "white", underline: true, children: "Choose an Agent" }),
2005
+ /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "red", children: error }) })
2006
+ ] });
2007
+ }
2008
+ if (selectedItem) {
2009
+ return /* @__PURE__ */ jsx9(
2010
+ LocalDevView,
2011
+ {
2012
+ item: selectedItem,
2013
+ onBack: () => setSelectedItem(null)
2014
+ }
2015
+ );
2016
+ }
2017
+ if (selectedAppId) {
2018
+ const session = sessions.find((s) => s.appId === selectedAppId);
2019
+ if (session) {
2020
+ return /* @__PURE__ */ jsx9(
2021
+ AgentDetailView,
2022
+ {
2023
+ session,
2024
+ onBack: () => setSelectedAppId(null),
2025
+ onSelect: (item) => {
2026
+ setSelectedItem(item);
2027
+ },
2028
+ onRefresh: refresh,
2029
+ refreshStatus
2030
+ }
2031
+ );
2032
+ }
2033
+ }
2034
+ return /* @__PURE__ */ jsx9(
2035
+ AgentListView,
2036
+ {
2037
+ sessions,
2038
+ onBack,
2039
+ onSelectAgent: setSelectedAppId,
2040
+ onRefresh: refresh,
2041
+ refreshStatus
2042
+ }
2043
+ );
2044
+ }
2045
+
2046
+ // src/tui/pages/OnboardingPage.tsx
2047
+ import { useEffect as useEffect14, useCallback as useCallback9, useState as useState14, useMemo as useMemo6 } from "react";
2048
+ import { Box as Box9, Text as Text10, useInput as useInput6 } from "ink";
2049
+ import Spinner6 from "ink-spinner";
1082
2050
  import chalk2 from "chalk";
1083
2051
 
1084
2052
  // src/tui/hooks/useAuth.ts
1085
- import { useState as useState8, useCallback as useCallback6, useRef as useRef4, useEffect as useEffect8 } from "react";
1086
- import open from "open";
2053
+ import { useState as useState13, useCallback as useCallback8, useRef as useRef8, useEffect as useEffect13 } from "react";
2054
+ import open3 from "open";
1087
2055
  var POLL_INTERVAL = 2e3;
1088
2056
  var MAX_ATTEMPTS = 30;
1089
2057
  function useAuth() {
1090
- const [status, setStatus] = useState8("idle");
1091
- const [authUrl, setAuthUrl] = useState8(null);
1092
- const [timeRemaining, setTimeRemaining] = useState8(0);
1093
- const cancelledRef = useRef4(false);
1094
- const timerRef = useRef4(null);
1095
- useEffect8(() => {
2058
+ const [status, setStatus] = useState13("idle");
2059
+ const [authUrl, setAuthUrl] = useState13(null);
2060
+ const [timeRemaining, setTimeRemaining] = useState13(0);
2061
+ const cancelledRef = useRef8(false);
2062
+ const timerRef = useRef8(null);
2063
+ useEffect13(() => {
1096
2064
  return () => {
1097
2065
  cancelledRef.current = true;
1098
2066
  if (timerRef.current) clearInterval(timerRef.current);
1099
2067
  };
1100
2068
  }, []);
1101
- const cancel = useCallback6(() => {
2069
+ const cancel = useCallback8(() => {
1102
2070
  cancelledRef.current = true;
1103
2071
  if (timerRef.current) {
1104
2072
  clearInterval(timerRef.current);
@@ -1108,7 +2076,7 @@ function useAuth() {
1108
2076
  setAuthUrl(null);
1109
2077
  setTimeRemaining(0);
1110
2078
  }, []);
1111
- const startAuth = useCallback6(() => {
2079
+ const startAuth = useCallback8(() => {
1112
2080
  cancelledRef.current = false;
1113
2081
  const run = async () => {
1114
2082
  try {
@@ -1128,7 +2096,7 @@ function useAuth() {
1128
2096
  return Math.max(0, next);
1129
2097
  });
1130
2098
  }, 1e3);
1131
- await open(url);
2099
+ await open3(url);
1132
2100
  for (let i = 0; i < MAX_ATTEMPTS; i++) {
1133
2101
  await new Promise((r) => setTimeout(r, POLL_INTERVAL));
1134
2102
  if (cancelledRef.current) return;
@@ -1136,6 +2104,9 @@ function useAuth() {
1136
2104
  if (result.status === "completed" && result.apiKey) {
1137
2105
  if (timerRef.current) clearInterval(timerRef.current);
1138
2106
  setApiKey(result.apiKey);
2107
+ if (result.userId) {
2108
+ setUserId(result.userId);
2109
+ }
1139
2110
  setStatus("success");
1140
2111
  return;
1141
2112
  }
@@ -1166,12 +2137,12 @@ function useAuth() {
1166
2137
  }
1167
2138
 
1168
2139
  // src/tui/pages/OnboardingPage.tsx
1169
- import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2140
+ import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1170
2141
  var SHIMMER_SPEED = 35;
1171
2142
  function useShimmerLogo() {
1172
- const [frame, setFrame] = useState9(0);
1173
- const lines = useMemo4(() => LogoString.split("\n"), []);
1174
- const totalChars = useMemo4(() => {
2143
+ const [frame, setFrame] = useState14(0);
2144
+ const lines = useMemo6(() => LogoString.split("\n"), []);
2145
+ const totalChars = useMemo6(() => {
1175
2146
  let count = 0;
1176
2147
  for (const line of lines) {
1177
2148
  for (const ch of line) {
@@ -1181,13 +2152,13 @@ function useShimmerLogo() {
1181
2152
  return count;
1182
2153
  }, [lines]);
1183
2154
  const cycleLength = totalChars + 40;
1184
- useEffect9(() => {
2155
+ useEffect14(() => {
1185
2156
  const interval = setInterval(() => {
1186
2157
  setFrame((f) => (f + 1) % cycleLength);
1187
2158
  }, SHIMMER_SPEED);
1188
2159
  return () => clearInterval(interval);
1189
2160
  }, [cycleLength]);
1190
- return useMemo4(() => {
2161
+ return useMemo6(() => {
1191
2162
  const sweepPos = frame;
1192
2163
  const holdEnd = totalChars + 20;
1193
2164
  let charIdx = 0;
@@ -1234,66 +2205,66 @@ function OnboardingPage({ onComplete }) {
1234
2205
  cancel: cancelAuth
1235
2206
  } = useAuth();
1236
2207
  const shimmerLogo = useShimmerLogo();
1237
- useEffect9(() => {
2208
+ useEffect14(() => {
1238
2209
  if (authStatus === "success") {
1239
2210
  const timer = setTimeout(() => onComplete(), 1500);
1240
2211
  return () => clearTimeout(timer);
1241
2212
  }
1242
2213
  }, [authStatus, onComplete]);
1243
- useEffect9(() => {
2214
+ useEffect14(() => {
1244
2215
  return () => cancelAuth();
1245
2216
  }, []);
1246
- const handleAction = useCallback7(() => {
2217
+ const handleAction = useCallback9(() => {
1247
2218
  cancelAuth();
1248
2219
  startAuth();
1249
2220
  }, [cancelAuth, startAuth]);
1250
2221
  const canAct = authStatus === "idle" || authStatus === "expired" || authStatus === "timeout";
1251
- useInput3((_input, key) => {
2222
+ useInput6((_input, key) => {
1252
2223
  if (canAct && !key.ctrl) {
1253
2224
  handleAction();
1254
2225
  }
1255
2226
  });
1256
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
1257
- /* @__PURE__ */ jsx7(Box6, { flexGrow: 1 }),
1258
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", alignItems: "center", children: [
1259
- /* @__PURE__ */ jsx7(Text7, { children: shimmerLogo }),
1260
- /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", alignItems: "center", marginTop: 2, children: /* @__PURE__ */ jsx7(Text7, { bold: true, color: "white", children: "MindStudio Local Tunnel" }) }),
1261
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", alignItems: "center", children: [
1262
- authStatus === "idle" && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1263
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "Connect your MindStudio account to get started." }),
1264
- /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "cyan", bold: true, children: "Press any key to Connect Account" }) })
2227
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", flexGrow: 1, children: [
2228
+ /* @__PURE__ */ jsx10(Box9, { flexGrow: 1 }),
2229
+ /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", alignItems: "center", children: [
2230
+ /* @__PURE__ */ jsx10(Text10, { children: shimmerLogo }),
2231
+ /* @__PURE__ */ jsx10(Box9, { flexDirection: "column", alignItems: "center", marginTop: 2, children: /* @__PURE__ */ jsx10(Text10, { bold: true, color: "white", children: "MindStudio Local Tunnel" }) }),
2232
+ /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", alignItems: "center", children: [
2233
+ authStatus === "idle" && /* @__PURE__ */ jsxs9(Fragment5, { children: [
2234
+ /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Connect your MindStudio account to get started." }),
2235
+ /* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: "Press any key to Connect Account" }) })
1265
2236
  ] }),
1266
- (authStatus === "expired" || authStatus === "timeout") && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1267
- /* @__PURE__ */ jsx7(Text7, { color: "red", children: authStatus === "expired" ? "Authorization expired." : "Authorization timed out." }),
1268
- /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "cyan", bold: true, children: "Press any key to Try Again" }) })
2237
+ (authStatus === "expired" || authStatus === "timeout") && /* @__PURE__ */ jsxs9(Fragment5, { children: [
2238
+ /* @__PURE__ */ jsx10(Text10, { color: "red", children: authStatus === "expired" ? "Authorization expired." : "Authorization timed out." }),
2239
+ /* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: "Press any key to Try Again" }) })
1269
2240
  ] }),
1270
- authStatus === "waiting" && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1271
- /* @__PURE__ */ jsxs6(Box6, { children: [
1272
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: /* @__PURE__ */ jsx7(Spinner4, { type: "dots" }) }),
1273
- /* @__PURE__ */ jsxs6(Text7, { children: [
2241
+ authStatus === "waiting" && /* @__PURE__ */ jsxs9(Fragment5, { children: [
2242
+ /* @__PURE__ */ jsxs9(Box9, { children: [
2243
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: /* @__PURE__ */ jsx10(Spinner6, { type: "dots" }) }),
2244
+ /* @__PURE__ */ jsxs9(Text10, { children: [
1274
2245
  " ",
1275
2246
  "Waiting for browser authorization... (",
1276
2247
  timeRemaining,
1277
2248
  "s remaining)"
1278
2249
  ] })
1279
2250
  ] }),
1280
- authUrl && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", alignItems: "center", marginTop: 1, children: [
1281
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "If browser didn't open, visit:" }),
1282
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: authUrl })
2251
+ authUrl && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", alignItems: "center", marginTop: 1, children: [
2252
+ /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "If browser didn't open, visit:" }),
2253
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: authUrl })
1283
2254
  ] })
1284
2255
  ] }),
1285
- authStatus === "success" && /* @__PURE__ */ jsxs6(Text7, { color: "green", children: [
2256
+ authStatus === "success" && /* @__PURE__ */ jsxs9(Text10, { color: "green", children: [
1286
2257
  "\u2713",
1287
2258
  " Authenticated!"
1288
2259
  ] })
1289
2260
  ] })
1290
2261
  ] }),
1291
- /* @__PURE__ */ jsx7(Box6, { flexGrow: 1 })
2262
+ /* @__PURE__ */ jsx10(Box9, { flexGrow: 1 })
1292
2263
  ] });
1293
2264
  }
1294
2265
 
1295
2266
  // src/tui/App.tsx
1296
- import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2267
+ import { Fragment as Fragment6, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1297
2268
  var MODEL_TYPE_MAP = {
1298
2269
  text: "llm_chat",
1299
2270
  image: "image_generation",
@@ -1301,7 +2272,7 @@ var MODEL_TYPE_MAP = {
1301
2272
  };
1302
2273
  function App({ runner }) {
1303
2274
  const { exit } = useApp();
1304
- const { stdout } = useStdout6();
2275
+ const { stdout } = useStdout7();
1305
2276
  const {
1306
2277
  status: connectionStatus,
1307
2278
  environment,
@@ -1325,32 +2296,32 @@ function App({ runner }) {
1325
2296
  syncedModels,
1326
2297
  refresh: refreshSynced
1327
2298
  } = useSyncedModels(connectionStatus);
1328
- const shouldOnboard = getApiKey() === void 0;
1329
- const [page, setPage] = useState10(
2299
+ const shouldOnboard = getApiKey() === void 0 || getUserId() === void 0;
2300
+ const [page, setPage] = useState15(
1330
2301
  shouldOnboard ? "onboarding" : "dashboard"
1331
2302
  );
1332
- const [syncStatus, setSyncStatus] = useState10(
2303
+ const [syncStatus, setSyncStatus] = useState15(
1333
2304
  "idle"
1334
2305
  );
1335
- const syncTimerRef = useRef5();
1336
- const lastSyncPayloadRef = useRef5("");
1337
- useEffect10(() => {
2306
+ const syncTimerRef = useRef9();
2307
+ const lastSyncPayloadRef = useRef9("");
2308
+ useEffect15(() => {
1338
2309
  if (connectionStatus === "not_authenticated") {
1339
2310
  setPage("onboarding");
1340
2311
  }
1341
2312
  }, [connectionStatus]);
1342
- useEffect10(() => {
2313
+ useEffect15(() => {
1343
2314
  if (page === "dashboard") {
1344
2315
  refreshAll();
1345
2316
  }
1346
2317
  }, [page]);
1347
- useEffect10(() => {
2318
+ useEffect15(() => {
1348
2319
  if (connectionStatus === "connected" && syncedModels.length > 0) {
1349
2320
  runner.start(syncedModels);
1350
2321
  }
1351
2322
  }, [connectionStatus, syncedModels, runner]);
1352
- useEffect10(() => () => runner.stop(), [runner]);
1353
- const refreshAll = useCallback8(
2323
+ useEffect15(() => () => runner.stop(), [runner]);
2324
+ const refreshAll = useCallback10(
1354
2325
  async (silent = false) => {
1355
2326
  if (!silent) setSyncStatus("syncing");
1356
2327
  const [discoveredModels] = await Promise.all([
@@ -1380,23 +2351,26 @@ function App({ runner }) {
1380
2351
  },
1381
2352
  [refreshProviders, refreshModels, refreshSynced]
1382
2353
  );
1383
- useEffect10(() => {
2354
+ useEffect15(() => {
1384
2355
  if (connectionStatus !== "connected" || page !== "dashboard") return;
1385
2356
  const interval = setInterval(() => refreshAll(true), 1500);
1386
2357
  return () => clearInterval(interval);
1387
2358
  }, [connectionStatus, page, refreshAll]);
1388
- const handleQuit = useCallback8(() => {
2359
+ const handleQuit = useCallback10(() => {
1389
2360
  runner.stop();
1390
2361
  exit();
1391
2362
  }, [runner, exit]);
1392
- const handleOnboardingComplete = useCallback8(() => {
2363
+ const handleOnboardingComplete = useCallback10(() => {
1393
2364
  retryConnection();
1394
2365
  refreshAll();
1395
2366
  setPage("dashboard");
1396
2367
  }, [retryConnection, refreshAll]);
1397
- const handleNavigate = useCallback8(
2368
+ const handleNavigate = useCallback10(
1398
2369
  (id) => {
1399
2370
  switch (id) {
2371
+ case "interfaces":
2372
+ setPage("interfaces");
2373
+ break;
1400
2374
  case "auth":
1401
2375
  setPage("onboarding");
1402
2376
  break;
@@ -1416,7 +2390,7 @@ function App({ runner }) {
1416
2390
  const subpageMenuItems = [
1417
2391
  { id: "back", label: "Back", description: "Return to dashboard" }
1418
2392
  ];
1419
- const handleSubpageNavigate = useCallback8(
2393
+ const handleSubpageNavigate = useCallback10(
1420
2394
  (id) => {
1421
2395
  if (id === "back") {
1422
2396
  setPage("dashboard");
@@ -1426,11 +2400,11 @@ function App({ runner }) {
1426
2400
  },
1427
2401
  [handleNavigate]
1428
2402
  );
1429
- const [termSize, setTermSize] = useState10({
2403
+ const [termSize, setTermSize] = useState15({
1430
2404
  rows: stdout?.rows ?? 24,
1431
2405
  columns: stdout?.columns ?? 80
1432
2406
  });
1433
- useEffect10(() => {
2407
+ useEffect15(() => {
1434
2408
  if (!stdout) return;
1435
2409
  const onResize = () => {
1436
2410
  stdout.write("\x1B[2J\x1B[H");
@@ -1443,8 +2417,8 @@ function App({ runner }) {
1443
2417
  }, [stdout]);
1444
2418
  const termHeight = termSize.rows - 4;
1445
2419
  const compactHeader = termSize.rows <= 45 || termSize.columns <= 90;
1446
- return /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", height: termHeight, overflow: "hidden", children: page === "onboarding" ? /* @__PURE__ */ jsx8(OnboardingPage, { onComplete: handleOnboardingComplete }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
1447
- /* @__PURE__ */ jsx8(
2420
+ return /* @__PURE__ */ jsx11(Box10, { flexDirection: "column", height: termHeight, overflow: "hidden", children: page === "onboarding" ? /* @__PURE__ */ jsx11(OnboardingPage, { onComplete: handleOnboardingComplete }) : /* @__PURE__ */ jsxs10(Fragment6, { children: [
2421
+ /* @__PURE__ */ jsx11(
1448
2422
  Header,
1449
2423
  {
1450
2424
  connection: connectionStatus,
@@ -1454,7 +2428,7 @@ function App({ runner }) {
1454
2428
  compact: compactHeader
1455
2429
  }
1456
2430
  ),
1457
- page === "dashboard" && /* @__PURE__ */ jsx8(
2431
+ page === "dashboard" && /* @__PURE__ */ jsx11(
1458
2432
  DashboardPage,
1459
2433
  {
1460
2434
  requests,
@@ -1468,9 +2442,15 @@ function App({ runner }) {
1468
2442
  onNavigate: handleNavigate
1469
2443
  }
1470
2444
  ),
1471
- page === "setup" && /* @__PURE__ */ jsx8(SetupPage, { onBack: () => setPage("dashboard") }),
1472
- page !== "dashboard" && page !== "setup" && /* @__PURE__ */ jsx8(Box7, { flexGrow: 1 }),
1473
- page !== "dashboard" && page !== "setup" && /* @__PURE__ */ jsx8(
2445
+ page === "setup" && /* @__PURE__ */ jsx11(SetupPage, { onBack: () => setPage("dashboard") }),
2446
+ page === "interfaces" && /* @__PURE__ */ jsx11(
2447
+ InterfacesPage,
2448
+ {
2449
+ onBack: () => setPage("dashboard")
2450
+ }
2451
+ ),
2452
+ page !== "dashboard" && page !== "setup" && page !== "interfaces" && /* @__PURE__ */ jsx11(Box10, { flexGrow: 1 }),
2453
+ page !== "dashboard" && page !== "setup" && page !== "interfaces" && /* @__PURE__ */ jsx11(
1474
2454
  NavigationMenu,
1475
2455
  {
1476
2456
  items: subpageMenuItems,
@@ -1520,24 +2500,24 @@ async function checkForUpdate() {
1520
2500
  }
1521
2501
 
1522
2502
  // src/tui/components/UpdatePrompt.tsx
1523
- import { Box as Box8, Text as Text8, useInput as useInput4 } from "ink";
1524
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2503
+ import { Box as Box11, Text as Text11, useInput as useInput7 } from "ink";
2504
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1525
2505
  function UpdatePrompt({
1526
2506
  currentVersion,
1527
2507
  latestVersion,
1528
2508
  onChoice
1529
2509
  }) {
1530
- useInput4((input) => {
2510
+ useInput7((input) => {
1531
2511
  if (input.toLowerCase() === "y") {
1532
2512
  onChoice(true);
1533
2513
  } else {
1534
2514
  onChoice(false);
1535
2515
  }
1536
2516
  });
1537
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
1538
- /* @__PURE__ */ jsxs8(Text8, { children: [
1539
- /* @__PURE__ */ jsx9(Text8, { color: "yellow", bold: true, children: "Update available:" }),
1540
- /* @__PURE__ */ jsxs8(Text8, { children: [
2517
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
2518
+ /* @__PURE__ */ jsxs11(Text11, { children: [
2519
+ /* @__PURE__ */ jsx12(Text11, { color: "yellow", bold: true, children: "Update available:" }),
2520
+ /* @__PURE__ */ jsxs11(Text11, { children: [
1541
2521
  " ",
1542
2522
  "v",
1543
2523
  currentVersion,
@@ -1547,20 +2527,20 @@ function UpdatePrompt({
1547
2527
  latestVersion
1548
2528
  ] })
1549
2529
  ] }),
1550
- /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2530
+ /* @__PURE__ */ jsx12(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text11, { children: [
1551
2531
  "Press ",
1552
- /* @__PURE__ */ jsx9(Text8, { bold: true, color: "cyan", children: "y" }),
2532
+ /* @__PURE__ */ jsx12(Text11, { bold: true, color: "cyan", children: "y" }),
1553
2533
  " to update, any other key to skip"
1554
2534
  ] }) })
1555
2535
  ] });
1556
2536
  }
1557
2537
 
1558
2538
  // src/tui/index.tsx
1559
- import { jsx as jsx10 } from "react/jsx-runtime";
2539
+ import { jsx as jsx13 } from "react/jsx-runtime";
1560
2540
  async function promptForUpdate(currentVersion, latestVersion) {
1561
2541
  return new Promise((resolve) => {
1562
2542
  const { unmount } = render(
1563
- /* @__PURE__ */ jsx10(
2543
+ /* @__PURE__ */ jsx13(
1564
2544
  UpdatePrompt,
1565
2545
  {
1566
2546
  currentVersion,
@@ -1586,7 +2566,7 @@ async function startTUI() {
1586
2566
  if (shouldUpdate) {
1587
2567
  console.log("\nUpdating to v" + update.latestVersion + "...\n");
1588
2568
  try {
1589
- execSync("npm install -g @mindstudio-ai/local-model-tunnel@latest", {
2569
+ execSync2("npm install -g @mindstudio-ai/local-model-tunnel@latest", {
1590
2570
  stdio: "inherit"
1591
2571
  });
1592
2572
  console.log("\nRestarting...\n");
@@ -1602,7 +2582,7 @@ async function startTUI() {
1602
2582
  }
1603
2583
  const runner = new TunnelRunner();
1604
2584
  const { waitUntilExit } = render(
1605
- /* @__PURE__ */ jsx10(App, { runner }),
2585
+ /* @__PURE__ */ jsx13(App, { runner }),
1606
2586
  {
1607
2587
  exitOnCtrlC: true
1608
2588
  }
@@ -1613,4 +2593,4 @@ async function startTUI() {
1613
2593
  export {
1614
2594
  startTUI
1615
2595
  };
1616
- //# sourceMappingURL=tui-4ZB4JAV4.js.map
2596
+ //# sourceMappingURL=tui-BW6XKMWK.js.map