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

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,29 +1,26 @@
1
1
  import {
2
2
  TunnelRunner,
3
3
  detectAllProviderStatuses,
4
- discoverAllModels,
5
4
  discoverAllModelsWithParameters,
6
5
  getApiKey,
7
6
  getConfigPath,
8
7
  getEnvironment,
9
- getProviderStatuses,
10
8
  getSyncedModels,
11
9
  pollDeviceAuth,
12
10
  requestDeviceAuth,
13
11
  requestEvents,
14
12
  setApiKey,
15
- syncLocalModel,
16
- updateLocalModel,
13
+ syncModels,
17
14
  verifyApiKey
18
- } from "./chunk-V3RKCMCQ.js";
15
+ } from "./chunk-PPP2FEIN.js";
19
16
 
20
17
  // src/tui/index.tsx
21
18
  import { render } from "ink";
22
19
  import { execFileSync, execSync } from "child_process";
23
20
 
24
21
  // src/tui/App.tsx
25
- import { useEffect as useEffect13, useCallback as useCallback10, useState as useState12 } from "react";
26
- import { Box as Box8, useApp, useStdout as useStdout5 } from "ink";
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";
27
24
 
28
25
  // src/tui/components/Header.tsx
29
26
  import os from "os";
@@ -32,7 +29,7 @@ import { createRequire } from "module";
32
29
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
33
30
  var require2 = createRequire(import.meta.url);
34
31
  var pkg = require2("../package.json");
35
- var LogoString = ` .=+-. :++.
32
+ var LogoString = ` .=+-. :++.
36
33
  *@@@@@+ :%@@@@%:
37
34
  .%@@@@@@#..@@@@@@@=
38
35
  .*@@@@@@@--@@@@@@@#.**.
@@ -59,7 +56,8 @@ function Header({
59
56
  connection,
60
57
  environment,
61
58
  configPath,
62
- connectionError
59
+ connectionError,
60
+ compact
63
61
  }) {
64
62
  const { color: connectionColor, text: connectionText } = getConnectionDisplay(connection);
65
63
  return /* @__PURE__ */ jsxs(
@@ -73,16 +71,20 @@ function Header({
73
71
  paddingY: 1,
74
72
  width: "100%",
75
73
  children: [
76
- /* @__PURE__ */ jsx(Box, { paddingLeft: 3, children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: LogoString }) }),
77
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 4, children: [
74
+ !compact && /* @__PURE__ */ jsx(Box, { paddingLeft: 3, children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: LogoString }) }),
75
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: compact ? 0 : 4, children: [
78
76
  /* @__PURE__ */ jsxs(Box, { children: [
79
77
  /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "MindStudio Local Tunnel" }),
78
+ compact && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
79
+ " v",
80
+ pkg.version
81
+ ] }),
80
82
  environment !== "prod" && /* @__PURE__ */ jsxs(Fragment, { children: [
81
83
  /* @__PURE__ */ jsx(Text, { children: " " }),
82
84
  /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "[LOCAL]" })
83
85
  ] })
84
86
  ] }),
85
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
87
+ !compact && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
86
88
  "v",
87
89
  pkg.version
88
90
  ] }),
@@ -103,9 +105,11 @@ function Header({
103
105
 
104
106
  // src/tui/components/NavigationMenu.tsx
105
107
  import { useState, useEffect } from "react";
106
- import { Box as Box2, Text as Text2, useInput } from "ink";
108
+ import { Box as Box2, Text as Text2, useInput, useStdout } from "ink";
107
109
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
108
110
  function NavigationMenu({ items, onSelect, title }) {
111
+ const { stdout } = useStdout();
112
+ const compact = (stdout?.rows ?? 24) < 40;
109
113
  const getDefaultIndex = () => {
110
114
  const backIdx = items.findIndex((i) => i.id === "back");
111
115
  if (backIdx >= 0) return backIdx;
@@ -116,6 +120,7 @@ function NavigationMenu({ items, onSelect, title }) {
116
120
  useEffect(() => {
117
121
  setSelectedIndex(getDefaultIndex());
118
122
  }, [items]);
123
+ const selectableItems = items.filter((i) => !i.isSeparator);
119
124
  const findNextEnabled = (from, direction) => {
120
125
  let idx = from;
121
126
  for (let i = 0; i < items.length; i++) {
@@ -134,9 +139,9 @@ function NavigationMenu({ items, onSelect, title }) {
134
139
  }
135
140
  return;
136
141
  }
137
- if (key.upArrow) {
142
+ if (key.upArrow || compact && key.leftArrow) {
138
143
  setSelectedIndex((prev) => findNextEnabled(prev, -1));
139
- } else if (key.downArrow) {
144
+ } else if (key.downArrow || compact && key.rightArrow) {
140
145
  setSelectedIndex((prev) => findNextEnabled(prev, 1));
141
146
  } else if (key.return) {
142
147
  const item = items[selectedIndex];
@@ -145,6 +150,24 @@ function NavigationMenu({ items, onSelect, title }) {
145
150
  }
146
151
  }
147
152
  });
153
+ const hasBack = items.some((i) => i.id === "back");
154
+ if (compact) {
155
+ const selectedItem = items[selectedIndex];
156
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingX: 1, borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: "gray", children: /* @__PURE__ */ jsx2(Box2, { height: 1, overflow: "hidden", gap: 1, children: items.map((item, index) => {
157
+ if (item.isSeparator) return null;
158
+ const isSelected = index === selectedIndex;
159
+ return /* @__PURE__ */ jsx2(
160
+ Text2,
161
+ {
162
+ color: item.disabled ? "gray" : isSelected ? "cyan" : "white",
163
+ bold: isSelected,
164
+ wrap: "truncate-end",
165
+ children: isSelected ? `\u276F ${item.label}` : ` ${item.label}`
166
+ },
167
+ item.id
168
+ );
169
+ }) }) });
170
+ }
148
171
  const separatorExtraLines = items.filter((item, idx) => item.isSeparator && idx > 0).length;
149
172
  const menuHeight = items.length + 4 + separatorExtraLines;
150
173
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, marginBottom: 1, borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: "gray", children: [
@@ -156,14 +179,14 @@ function NavigationMenu({ items, onSelect, title }) {
156
179
  const isSelected = index === selectedIndex;
157
180
  const prefix = isSelected ? "\u276F" : " ";
158
181
  if (item.disabled) {
159
- return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
182
+ return /* @__PURE__ */ jsx2(Box2, { height: 1, overflow: "hidden", children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", wrap: "truncate-end", children: [
160
183
  prefix,
161
184
  " ",
162
185
  item.label,
163
186
  item.disabledReason ? ` (${item.disabledReason})` : ""
164
187
  ] }) }, item.id);
165
188
  }
166
- return /* @__PURE__ */ jsxs2(Box2, { children: [
189
+ return /* @__PURE__ */ jsxs2(Box2, { height: 1, overflow: "hidden", children: [
167
190
  /* @__PURE__ */ jsxs2(Text2, { color: isSelected ? "cyan" : "white", bold: isSelected, wrap: "truncate-end", children: [
168
191
  prefix,
169
192
  " ",
@@ -175,7 +198,7 @@ function NavigationMenu({ items, onSelect, title }) {
175
198
  ] })
176
199
  ] }, item.id);
177
200
  }) }),
178
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", wrap: "truncate-end", children: items.some((i) => i.id === "back") ? "Up/Down Navigate \u2022 Enter Select \u2022 q/Esc Back" : "Up/Down Navigate \u2022 Enter Select \u2022 q Quit" }) })
201
+ /* @__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" }) })
179
202
  ] });
180
203
  }
181
204
 
@@ -216,47 +239,56 @@ function useConnection() {
216
239
  };
217
240
  }
218
241
 
219
- // src/tui/hooks/useProviders.ts
220
- import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2 } from "react";
221
- function useProviders(pollInterval = 1e4) {
242
+ // src/tui/hooks/useSetupProviders.ts
243
+ import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2, useRef } from "react";
244
+ function useSetupProviders() {
222
245
  const [providers, setProviders] = useState3([]);
223
246
  const [loading, setLoading] = useState3(true);
247
+ const [refreshing, setRefreshing] = useState3(false);
248
+ const initialLoadDone = useRef(false);
224
249
  const refresh = useCallback2(async () => {
225
- try {
226
- const statuses = await getProviderStatuses();
227
- setProviders(statuses);
228
- } catch {
229
- } finally {
230
- setLoading(false);
250
+ if (!initialLoadDone.current) {
251
+ setLoading(true);
252
+ } else {
253
+ setRefreshing(true);
231
254
  }
255
+ const statuses = await detectAllProviderStatuses();
256
+ setProviders(statuses);
257
+ initialLoadDone.current = true;
258
+ setLoading(false);
259
+ setRefreshing(false);
232
260
  }, []);
233
261
  useEffect3(() => {
234
262
  refresh();
235
- const interval = setInterval(refresh, pollInterval);
236
- return () => clearInterval(interval);
237
- }, [refresh, pollInterval]);
238
- return {
239
- providers,
240
- loading,
241
- refresh
242
- };
263
+ }, [refresh]);
264
+ return { providers, loading, refreshing, refresh };
243
265
  }
244
266
 
245
267
  // src/tui/hooks/useModels.ts
246
- import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3 } from "react";
268
+ import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3, useRef as useRef2 } from "react";
247
269
  function useModels() {
248
270
  const [models, setModels] = useState4([]);
249
271
  const [warnings, setWarnings] = useState4([]);
250
272
  const [loading, setLoading] = useState4(true);
273
+ const [refreshing, setRefreshing] = useState4(false);
274
+ const initialLoadDone = useRef2(false);
251
275
  const refresh = useCallback3(async () => {
252
- setLoading(true);
276
+ if (!initialLoadDone.current) {
277
+ setLoading(true);
278
+ } else {
279
+ setRefreshing(true);
280
+ }
253
281
  try {
254
- const discoveredModels = await discoverAllModels();
282
+ const discoveredModels = await discoverAllModelsWithParameters();
255
283
  setModels(discoveredModels.filter((m) => !m.statusHint));
256
284
  setWarnings(discoveredModels.filter((m) => !!m.statusHint));
285
+ initialLoadDone.current = true;
286
+ return discoveredModels;
257
287
  } catch {
288
+ return [];
258
289
  } finally {
259
290
  setLoading(false);
291
+ setRefreshing(false);
260
292
  }
261
293
  }, []);
262
294
  useEffect4(() => {
@@ -266,15 +298,16 @@ function useModels() {
266
298
  models,
267
299
  warnings,
268
300
  loading,
301
+ refreshing,
269
302
  refresh
270
303
  };
271
304
  }
272
305
 
273
306
  // src/tui/hooks/useRequests.ts
274
- import { useState as useState5, useEffect as useEffect5, useCallback as useCallback4, useRef } from "react";
307
+ import { useState as useState5, useEffect as useEffect5, useCallback as useCallback4, useRef as useRef3 } from "react";
275
308
  function useRequests(maxHistory = 50) {
276
309
  const [requests, setRequests] = useState5([]);
277
- const requestsRef = useRef(/* @__PURE__ */ new Map());
310
+ const requestsRef = useRef3(/* @__PURE__ */ new Map());
278
311
  useEffect5(() => {
279
312
  const interval = setInterval(() => {
280
313
  setRequests((prev) => {
@@ -352,14 +385,17 @@ function useSyncedModels(connectionStatus) {
352
385
  const [syncedNames, setSyncedNames] = useState6(
353
386
  /* @__PURE__ */ new Set()
354
387
  );
388
+ const [syncedModels, setSyncedModels] = useState6([]);
355
389
  const refresh = useCallback5(async () => {
356
390
  if (connectionStatus !== "connected") {
357
391
  setSyncedNames(/* @__PURE__ */ new Set());
392
+ setSyncedModels([]);
358
393
  return;
359
394
  }
360
395
  try {
361
396
  const models = await getSyncedModels();
362
397
  setSyncedNames(new Set(models.map((m) => m.name)));
398
+ setSyncedModels(models);
363
399
  } catch {
364
400
  }
365
401
  }, [connectionStatus]);
@@ -368,17 +404,18 @@ function useSyncedModels(connectionStatus) {
368
404
  }, [refresh]);
369
405
  return {
370
406
  syncedNames,
407
+ syncedModels,
371
408
  refresh
372
409
  };
373
410
  }
374
411
 
375
412
  // src/tui/pages/DashboardPage.tsx
376
413
  import { useMemo } from "react";
377
- import { Box as Box4, Text as Text4, useStdout as useStdout2 } from "ink";
414
+ import { Box as Box4, Text as Text4, useStdout as useStdout3 } from "ink";
378
415
  import Spinner2 from "ink-spinner";
379
416
 
380
417
  // src/tui/components/RequestLog.tsx
381
- import { Box as Box3, Text as Text3, useStdout } from "ink";
418
+ import { Box as Box3, Text as Text3, useStdout as useStdout2 } from "ink";
382
419
  import Spinner from "ink-spinner";
383
420
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
384
421
  function formatTime(timestamp) {
@@ -498,7 +535,7 @@ function RequestItem({ request, width }) {
498
535
  ] }) });
499
536
  }
500
537
  function RequestLog({ requests, maxVisible = 8, hasModels = true }) {
501
- const { stdout } = useStdout();
538
+ const { stdout } = useStdout2();
502
539
  const width = stdout?.columns ?? 80;
503
540
  const activeRequests = requests.filter((r) => r.status === "processing");
504
541
  const completedRequests = requests.filter((r) => r.status !== "processing");
@@ -543,23 +580,6 @@ function RequestLog({ requests, maxVisible = 8, hasModels = true }) {
543
580
  );
544
581
  }
545
582
 
546
- // src/tui/hooks/useSetupProviders.ts
547
- import { useState as useState7, useEffect as useEffect7, useCallback as useCallback6 } from "react";
548
- function useSetupProviders() {
549
- const [providers, setProviders] = useState7([]);
550
- const [loading, setLoading] = useState7(true);
551
- const refresh = useCallback6(async () => {
552
- setLoading(true);
553
- const statuses = await detectAllProviderStatuses();
554
- setProviders(statuses);
555
- setLoading(false);
556
- }, []);
557
- useEffect7(() => {
558
- refresh();
559
- }, [refresh]);
560
- return { providers, loading, refresh };
561
- }
562
-
563
583
  // src/tui/pages/DashboardPage.tsx
564
584
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
565
585
  function getWorkflowCount(model) {
@@ -583,12 +603,14 @@ function DashboardPage({
583
603
  requests,
584
604
  models,
585
605
  modelWarnings = [],
606
+ providers,
607
+ providersLoading,
586
608
  syncedNames,
587
609
  modelsLoading,
610
+ syncStatus = "idle",
588
611
  onNavigate
589
612
  }) {
590
- const { stdout } = useStdout2();
591
- const { providers, loading: setupLoading } = useSetupProviders();
613
+ const { stdout } = useStdout3();
592
614
  const installedProviders = providers.filter(({ status }) => status.installed);
593
615
  const provNameWidth = Math.max(
594
616
  ...installedProviders.map((p) => p.provider.displayName.length),
@@ -599,17 +621,13 @@ function DashboardPage({
599
621
  const unavailableSynced = [...syncedNames].filter(
600
622
  (name) => !allModelNames.has(name)
601
623
  );
624
+ const syncDescription = syncStatus === "syncing" ? "Syncing..." : syncStatus === "synced" ? "\u2713 Synced" : "Re-detect providers and sync models to MindStudio";
602
625
  const menuItems = useMemo(() => {
603
626
  return [
604
- {
605
- id: "register",
606
- label: "Sync Models",
607
- description: "Sync models with MindStudio Cloud"
608
- },
609
627
  {
610
628
  id: "refresh",
611
- label: "Refresh Providers",
612
- description: "Re-detect local AI providers and models"
629
+ label: "Sync Models",
630
+ description: syncDescription
613
631
  },
614
632
  {
615
633
  id: "setup",
@@ -627,21 +645,23 @@ function DashboardPage({
627
645
  description: "Quit the application"
628
646
  }
629
647
  ];
630
- }, []);
648
+ }, [syncDescription]);
631
649
  const termHeight = (stdout?.rows ?? 24) - 4;
632
- const headerLines = 14;
633
- const providerContentLines = setupLoading ? 1 : installedProviders.length === 0 ? 2 : installedProviders.length;
650
+ const compactHeader = (stdout?.rows ?? 24) <= 45 || (stdout?.columns ?? 80) <= 90;
651
+ const headerLines = compactHeader ? 7 : 14;
652
+ const providerContentLines = providersLoading ? 1 : installedProviders.length === 0 ? 2 : installedProviders.length;
634
653
  const providersLines = 3 + providerContentLines;
635
654
  const modelContentLines = modelsLoading ? 1 : models.length === 0 && unavailableSynced.length === 0 && modelWarnings.length === 0 ? 2 : models.length + modelWarnings.length + (unavailableSynced.length > 0 ? 1 + unavailableSynced.length : 0);
636
655
  const modelsLines = 3 + modelContentLines;
637
656
  const requestLogOverhead = 3;
638
- const menuLines = menuItems.length + 6;
657
+ const compactMenu = termHeight + 4 < 40;
658
+ const menuLines = compactMenu ? 2 : menuItems.length + 6;
639
659
  const usedLines = headerLines + providersLines + modelsLines + requestLogOverhead + menuLines;
640
660
  const maxVisible = Math.max(3, termHeight - usedLines);
641
661
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, children: [
642
662
  /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
643
663
  /* @__PURE__ */ jsx4(Text4, { bold: true, color: "white", underline: true, children: "Providers" }),
644
- setupLoading ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
664
+ providersLoading ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
645
665
  /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: /* @__PURE__ */ jsx4(Spinner2, { type: "dots" }) }),
646
666
  /* @__PURE__ */ jsx4(Text4, { children: " Detecting providers..." })
647
667
  ] }) : installedProviders.length === 0 ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
@@ -681,7 +701,7 @@ function DashboardPage({
681
701
  /* @__PURE__ */ jsx4(Text4, { color: "gray", children: displayProvider }),
682
702
  /* @__PURE__ */ jsx4(Text4, { color: "gray", children: " - " }),
683
703
  /* @__PURE__ */ jsx4(Text4, { color: cap.color, children: cap.label })
684
- ] }, model.name);
704
+ ] }, `${model.provider}:${model.name}`);
685
705
  }),
686
706
  modelWarnings.map((warning) => {
687
707
  const displayProvider = providers.find((p) => p.provider.name === warning.provider)?.provider.displayName ?? warning.provider;
@@ -692,10 +712,10 @@ function DashboardPage({
692
712
  /* @__PURE__ */ jsx4(Text4, { color: "gray", children: displayProvider }),
693
713
  /* @__PURE__ */ jsx4(Text4, { color: "gray", children: " - " }),
694
714
  /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: warning.statusHint })
695
- ] }, warning.name);
715
+ ] }, `${warning.provider}:${warning.name}`);
696
716
  }),
697
717
  unavailableSynced.length > 0 && /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: models.length > 0 ? 1 : 0, children: [
698
- /* @__PURE__ */ jsx4(Text4, { color: "gray", children: "Synced but not currently available:" }),
718
+ /* @__PURE__ */ jsx4(Text4, { color: "gray", children: "Synced but provider not running:" }),
699
719
  unavailableSynced.map((name) => /* @__PURE__ */ jsxs4(Box4, { children: [
700
720
  /* @__PURE__ */ jsx4(Text4, { color: "gray", children: "\u25CB" }),
701
721
  /* @__PURE__ */ jsx4(Text4, { color: "gray", children: ` ${name}` })
@@ -715,206 +735,18 @@ function DashboardPage({
715
735
  ] });
716
736
  }
717
737
 
718
- // src/tui/pages/RegisterPage.tsx
719
- import { useEffect as useEffect9 } from "react";
720
- import { Box as Box5, Text as Text5 } from "ink";
721
- import Spinner3 from "ink-spinner";
722
-
723
- // src/tui/hooks/useRegister.ts
724
- import { useState as useState8, useCallback as useCallback7, useRef as useRef2, useEffect as useEffect8 } from "react";
725
- var MODEL_TYPE_MAP = {
726
- text: "llm_chat",
727
- image: "image_generation",
728
- video: "video_generation"
729
- };
730
- function useSync() {
731
- const [status, setStatus] = useState8("idle");
732
- const [progress, setProgress] = useState8({
733
- current: 0,
734
- total: 0
735
- });
736
- const [syncedModels, setSyncedModels] = useState8(
737
- []
738
- );
739
- const [error, setError] = useState8(null);
740
- const cancelledRef = useRef2(false);
741
- useEffect8(() => {
742
- return () => {
743
- cancelledRef.current = true;
744
- };
745
- }, []);
746
- const cancel = useCallback7(() => {
747
- cancelledRef.current = true;
748
- setStatus("idle");
749
- }, []);
750
- const startSync = useCallback7(() => {
751
- cancelledRef.current = false;
752
- setError(null);
753
- setSyncedModels([]);
754
- const run = async () => {
755
- try {
756
- setStatus("discovering");
757
- const localModels = await discoverAllModelsWithParameters();
758
- if (cancelledRef.current) return;
759
- if (localModels.length === 0) {
760
- setError("No local models found.");
761
- setStatus("error");
762
- return;
763
- }
764
- const existingSynced = await getSyncedModels();
765
- if (cancelledRef.current) return;
766
- const remoteByName = new Map(
767
- existingSynced.map((m) => [m.name, m.id])
768
- );
769
- setStatus("syncing");
770
- setProgress({ current: 0, total: localModels.length });
771
- for (let i = 0; i < localModels.length; i++) {
772
- if (cancelledRef.current) return;
773
- const model = localModels[i];
774
- const modelType = MODEL_TYPE_MAP[model.capability];
775
- const existingId = remoteByName.get(model.name);
776
- if (existingId) {
777
- await updateLocalModel({
778
- modelId: existingId,
779
- modelName: model.name,
780
- provider: model.provider,
781
- modelType,
782
- parameters: model.parameters
783
- });
784
- } else {
785
- await syncLocalModel({
786
- modelName: model.name,
787
- provider: model.provider,
788
- modelType,
789
- parameters: model.parameters
790
- });
791
- }
792
- setProgress({ current: i + 1, total: localModels.length });
793
- }
794
- if (cancelledRef.current) return;
795
- const finalModels = localModels.map((m) => ({
796
- name: m.name,
797
- provider: m.provider,
798
- capability: m.capability,
799
- isNew: !remoteByName.has(m.name)
800
- }));
801
- setSyncedModels(finalModels);
802
- setStatus("done");
803
- } catch (err) {
804
- if (!cancelledRef.current) {
805
- setError(err instanceof Error ? err.message : "Sync failed");
806
- setStatus("error");
807
- }
808
- }
809
- };
810
- run();
811
- }, []);
812
- return {
813
- status,
814
- progress,
815
- syncedModels,
816
- error,
817
- startSync,
818
- cancel
819
- };
820
- }
821
-
822
- // src/tui/pages/RegisterPage.tsx
823
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
824
- function SyncPage() {
825
- const { status, progress, syncedModels, error, startSync, cancel } = useSync();
826
- useEffect9(() => {
827
- startSync();
828
- return () => cancel();
829
- }, []);
830
- if (status === "idle") {
831
- return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Starting model sync..." }) }) });
832
- }
833
- if (status === "error") {
834
- return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
835
- "Sync failed: ",
836
- error
837
- ] }) }) });
838
- }
839
- if (status === "discovering") {
840
- return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
841
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: /* @__PURE__ */ jsx5(Spinner3, { type: "dots" }) }),
842
- /* @__PURE__ */ jsx5(Text5, { children: " Discovering local models..." })
843
- ] }) });
844
- }
845
- if (status === "syncing") {
846
- return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
847
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: /* @__PURE__ */ jsx5(Spinner3, { type: "dots" }) }),
848
- /* @__PURE__ */ jsxs5(Text5, { children: [
849
- " ",
850
- "Syncing ",
851
- progress.current,
852
- "/",
853
- progress.total,
854
- " models..."
855
- ] })
856
- ] }) });
857
- }
858
- const newModels = syncedModels.filter((m) => m.isNew);
859
- const resyncedModels = syncedModels.filter((m) => !m.isNew);
860
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, paddingX: 1, children: [
861
- newModels.length > 0 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
862
- /* @__PURE__ */ jsxs5(Text5, { color: "green", children: [
863
- "Synced ",
864
- newModels.length,
865
- " new model",
866
- newModels.length !== 1 ? "s" : "",
867
- ":"
868
- ] }),
869
- newModels.map((m) => /* @__PURE__ */ jsxs5(Box5, { children: [
870
- /* @__PURE__ */ jsx5(Text5, { color: "green", children: " \u2713 " }),
871
- /* @__PURE__ */ jsxs5(Text5, { children: [
872
- m.name,
873
- " "
874
- ] }),
875
- /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
876
- "[",
877
- m.provider,
878
- "]"
879
- ] })
880
- ] }, m.name))
881
- ] }),
882
- resyncedModels.length > 0 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
883
- /* @__PURE__ */ jsxs5(Text5, { color: "green", children: [
884
- "Resynced ",
885
- resyncedModels.length,
886
- " existing model",
887
- resyncedModels.length !== 1 ? "s" : "",
888
- ":"
889
- ] }),
890
- resyncedModels.map((m) => /* @__PURE__ */ jsxs5(Box5, { children: [
891
- /* @__PURE__ */ jsx5(Text5, { color: "green", children: " \u2713 " }),
892
- /* @__PURE__ */ jsxs5(Text5, { children: [
893
- m.name,
894
- " "
895
- ] }),
896
- /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
897
- "[",
898
- m.provider,
899
- "]"
900
- ] })
901
- ] }, m.name))
902
- ] })
903
- ] });
904
- }
905
-
906
738
  // src/tui/pages/SetupPage.tsx
907
- import { useState as useState9, useMemo as useMemo3, useEffect as useEffect10 } from "react";
908
- import { Box as Box6, Text as Text7, useInput as useInput2, useStdout as useStdout4 } from "ink";
909
- import Spinner4 from "ink-spinner";
739
+ import { useState as useState7, useMemo as useMemo3, useEffect as useEffect7 } from "react";
740
+ import { Box as Box5, Text as Text6, useInput as useInput2, useStdout as useStdout5 } from "ink";
741
+ import Spinner3 from "ink-spinner";
910
742
 
911
743
  // src/tui/components/MarkdownText.tsx
912
744
  import { useMemo as useMemo2 } from "react";
913
- import { Text as Text6, useStdout as useStdout3 } from "ink";
745
+ import { Text as Text5, useStdout as useStdout4 } from "ink";
914
746
  import chalk from "chalk";
915
747
  import { marked } from "marked";
916
748
  import { markedTerminal } from "marked-terminal";
917
- import { jsx as jsx6 } from "react/jsx-runtime";
749
+ import { jsx as jsx5 } from "react/jsx-runtime";
918
750
  var codeStyle = chalk.cyan;
919
751
  var identity = (s) => s;
920
752
  function renderMarkdown(content, width) {
@@ -944,13 +776,13 @@ function renderMarkdown(content, width) {
944
776
  }
945
777
 
946
778
  // src/tui/pages/SetupPage.tsx
947
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
779
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
948
780
  function ProviderDetailView({
949
781
  provider,
950
782
  onBack
951
783
  }) {
952
- const [scrollOffset, setScrollOffset] = useState9(0);
953
- const { stdout } = useStdout4();
784
+ const [scrollOffset, setScrollOffset] = useState7(0);
785
+ const { stdout } = useStdout5();
954
786
  const termHeight = (stdout?.rows ?? 24) - 4;
955
787
  const headerHeight = 14;
956
788
  const footerLines = 6;
@@ -988,21 +820,21 @@ function ProviderDetailView({
988
820
  (_, i) => i >= thumbPos && i < thumbPos + thumbSize
989
821
  );
990
822
  }, [scrollOffset, maxScroll, viewHeight, renderedLines.length]);
991
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
992
- /* @__PURE__ */ jsxs6(Box6, { height: viewHeight, children: [
993
- /* @__PURE__ */ jsx7(
994
- Box6,
823
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
824
+ /* @__PURE__ */ jsxs5(Box5, { height: viewHeight, children: [
825
+ /* @__PURE__ */ jsx6(
826
+ Box5,
995
827
  {
996
828
  flexDirection: "column",
997
829
  paddingX: 1,
998
830
  paddingY: 1,
999
831
  flexGrow: 1,
1000
832
  overflow: "hidden",
1001
- children: /* @__PURE__ */ jsx7(Text7, { children: visibleContent })
833
+ children: /* @__PURE__ */ jsx6(Text6, { children: visibleContent })
1002
834
  }
1003
835
  ),
1004
- scrollbar && /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: scrollbar.map((isThumb, i) => /* @__PURE__ */ jsx7(
1005
- Text7,
836
+ scrollbar && /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", children: scrollbar.map((isThumb, i) => /* @__PURE__ */ jsx6(
837
+ Text6,
1006
838
  {
1007
839
  color: isThumb ? "cyan" : "gray",
1008
840
  dimColor: !isThumb,
@@ -1011,8 +843,8 @@ function ProviderDetailView({
1011
843
  i
1012
844
  )) })
1013
845
  ] }),
1014
- /* @__PURE__ */ jsxs6(
1015
- Box6,
846
+ /* @__PURE__ */ jsxs5(
847
+ Box5,
1016
848
  {
1017
849
  flexDirection: "column",
1018
850
  paddingX: 1,
@@ -1023,15 +855,15 @@ function ProviderDetailView({
1023
855
  borderRight: false,
1024
856
  borderColor: "gray",
1025
857
  children: [
1026
- /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "Actions" }) }),
1027
- /* @__PURE__ */ jsxs6(Box6, { children: [
1028
- /* @__PURE__ */ jsxs6(Text7, { color: "cyan", bold: true, children: [
858
+ /* @__PURE__ */ jsx6(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Actions" }) }),
859
+ /* @__PURE__ */ jsxs5(Box5, { children: [
860
+ /* @__PURE__ */ jsxs5(Text6, { color: "cyan", bold: true, children: [
1029
861
  "\u276F",
1030
862
  " Back"
1031
863
  ] }),
1032
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: " - Return to providers" })
864
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: " - Return to providers" })
1033
865
  ] }),
1034
- /* @__PURE__ */ jsx7(Box6, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "gray", wrap: "truncate-end", children: [
866
+ /* @__PURE__ */ jsx6(Box5, { marginTop: 1, height: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", wrap: "truncate-end", children: [
1035
867
  "Up/Down Scroll ",
1036
868
  "\u2022",
1037
869
  " Enter/q/Esc Back",
@@ -1044,7 +876,7 @@ function ProviderDetailView({
1044
876
  }
1045
877
  function SetupPage({ onBack }) {
1046
878
  const { providers, loading } = useSetupProviders();
1047
- const [selectedProvider, setSelectedProvider] = useState9(null);
879
+ const [selectedProvider, setSelectedProvider] = useState7(null);
1048
880
  const running = useMemo3(
1049
881
  () => providers.filter((p) => p.status.running),
1050
882
  [providers]
@@ -1063,8 +895,8 @@ function SetupPage({ onBack }) {
1063
895
  );
1064
896
  const totalItems = allProviders.length + 1;
1065
897
  const backIndex = allProviders.length;
1066
- const [cursorIndex, setCursorIndex] = useState9(backIndex);
1067
- useEffect10(() => {
898
+ const [cursorIndex, setCursorIndex] = useState7(backIndex);
899
+ useEffect7(() => {
1068
900
  setCursorIndex(backIndex);
1069
901
  }, [backIndex]);
1070
902
  useInput2((input, key) => {
@@ -1088,7 +920,7 @@ function SetupPage({ onBack }) {
1088
920
  if (selectedProvider) {
1089
921
  const found = providers.find((p) => p.provider.name === selectedProvider);
1090
922
  if (found) {
1091
- return /* @__PURE__ */ jsx7(
923
+ return /* @__PURE__ */ jsx6(
1092
924
  ProviderDetailView,
1093
925
  {
1094
926
  provider: found.provider,
@@ -1097,26 +929,26 @@ function SetupPage({ onBack }) {
1097
929
  );
1098
930
  }
1099
931
  }
1100
- return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
1101
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "white", underline: true, children: "Manage Providers" }),
1102
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "Select a provider to view its setup guide." }),
1103
- loading ? /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1104
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: /* @__PURE__ */ jsx7(Spinner4, { type: "dots" }) }),
1105
- /* @__PURE__ */ jsx7(Text7, { children: " Detecting providers..." })
1106
- ] }) : /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
1107
- running.length > 0 && /* @__PURE__ */ jsxs6(Fragment2, { children: [
1108
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "green", children: "Running" }),
932
+ return /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
933
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: "white", underline: true, children: "Manage Providers" }),
934
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Select a provider to view its setup guide." }),
935
+ loading ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
936
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: /* @__PURE__ */ jsx6(Spinner3, { type: "dots" }) }),
937
+ /* @__PURE__ */ jsx6(Text6, { children: " Detecting providers..." })
938
+ ] }) : /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
939
+ running.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
940
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: "green", children: "Running" }),
1109
941
  running.map(({ provider }, i) => {
1110
942
  const index = i;
1111
943
  const isSelected = index === cursorIndex;
1112
- return /* @__PURE__ */ jsxs6(
1113
- Box6,
944
+ return /* @__PURE__ */ jsxs5(
945
+ Box5,
1114
946
  {
1115
947
  flexDirection: "column",
1116
948
  marginTop: i > 0 ? 1 : 0,
1117
949
  children: [
1118
- /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsxs6(
1119
- Text7,
950
+ /* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsxs5(
951
+ Text6,
1120
952
  {
1121
953
  color: isSelected ? "cyan" : "white",
1122
954
  bold: isSelected,
@@ -1129,7 +961,7 @@ function SetupPage({ onBack }) {
1129
961
  ]
1130
962
  }
1131
963
  ) }),
1132
- /* @__PURE__ */ jsxs6(Text7, { color: "gray", wrap: "wrap", children: [
964
+ /* @__PURE__ */ jsxs5(Text6, { color: "gray", wrap: "wrap", children: [
1133
965
  " ",
1134
966
  provider.description
1135
967
  ] })
@@ -1139,24 +971,24 @@ function SetupPage({ onBack }) {
1139
971
  );
1140
972
  })
1141
973
  ] }),
1142
- installed.length > 0 && /* @__PURE__ */ jsxs6(
1143
- Box6,
974
+ installed.length > 0 && /* @__PURE__ */ jsxs5(
975
+ Box5,
1144
976
  {
1145
977
  flexDirection: "column",
1146
978
  marginTop: running.length > 0 ? 1 : 0,
1147
979
  children: [
1148
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "yellow", children: "Installed" }),
980
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "Installed" }),
1149
981
  installed.map(({ provider, status }, i) => {
1150
982
  const index = running.length + i;
1151
983
  const isSelected = index === cursorIndex;
1152
- return /* @__PURE__ */ jsxs6(
1153
- Box6,
984
+ return /* @__PURE__ */ jsxs5(
985
+ Box5,
1154
986
  {
1155
987
  flexDirection: "column",
1156
988
  marginTop: i > 0 ? 1 : 0,
1157
989
  children: [
1158
- /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsxs6(
1159
- Text7,
990
+ /* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsxs5(
991
+ Text6,
1160
992
  {
1161
993
  color: isSelected ? "cyan" : "white",
1162
994
  bold: isSelected,
@@ -1169,7 +1001,7 @@ function SetupPage({ onBack }) {
1169
1001
  ]
1170
1002
  }
1171
1003
  ) }),
1172
- /* @__PURE__ */ jsxs6(Text7, { color: "gray", wrap: "wrap", children: [
1004
+ /* @__PURE__ */ jsxs5(Text6, { color: "gray", wrap: "wrap", children: [
1173
1005
  " ",
1174
1006
  provider.description
1175
1007
  ] })
@@ -1181,24 +1013,24 @@ function SetupPage({ onBack }) {
1181
1013
  ]
1182
1014
  }
1183
1015
  ),
1184
- notInstalled.length > 0 && /* @__PURE__ */ jsxs6(
1185
- Box6,
1016
+ notInstalled.length > 0 && /* @__PURE__ */ jsxs5(
1017
+ Box5,
1186
1018
  {
1187
1019
  flexDirection: "column",
1188
1020
  marginTop: running.length > 0 || installed.length > 0 ? 1 : 0,
1189
1021
  children: [
1190
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "gray", children: "Not Installed" }),
1022
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: "gray", children: "Not Installed" }),
1191
1023
  notInstalled.map(({ provider }, i) => {
1192
1024
  const index = running.length + installed.length + i;
1193
1025
  const isSelected = index === cursorIndex;
1194
- return /* @__PURE__ */ jsxs6(
1195
- Box6,
1026
+ return /* @__PURE__ */ jsxs5(
1027
+ Box5,
1196
1028
  {
1197
1029
  flexDirection: "column",
1198
1030
  marginTop: i > 0 ? 1 : 0,
1199
1031
  children: [
1200
- /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsxs6(
1201
- Text7,
1032
+ /* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsxs5(
1033
+ Text6,
1202
1034
  {
1203
1035
  color: isSelected ? "cyan" : "white",
1204
1036
  bold: isSelected,
@@ -1209,7 +1041,7 @@ function SetupPage({ onBack }) {
1209
1041
  ]
1210
1042
  }
1211
1043
  ) }),
1212
- /* @__PURE__ */ jsxs6(Text7, { color: "gray", wrap: "wrap", children: [
1044
+ /* @__PURE__ */ jsxs5(Text6, { color: "gray", wrap: "wrap", children: [
1213
1045
  " ",
1214
1046
  provider.description
1215
1047
  ] })
@@ -1221,8 +1053,8 @@ function SetupPage({ onBack }) {
1221
1053
  ]
1222
1054
  }
1223
1055
  ),
1224
- /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(
1225
- Text7,
1056
+ /* @__PURE__ */ jsx6(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(
1057
+ Text6,
1226
1058
  {
1227
1059
  color: cursorIndex === backIndex ? "cyan" : "white",
1228
1060
  bold: cursorIndex === backIndex,
@@ -1233,7 +1065,7 @@ function SetupPage({ onBack }) {
1233
1065
  }
1234
1066
  ) })
1235
1067
  ] }),
1236
- /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "gray", children: [
1068
+ /* @__PURE__ */ jsx6(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
1237
1069
  "Up/Down Navigate ",
1238
1070
  "\u2022",
1239
1071
  " Enter Select ",
@@ -1244,29 +1076,29 @@ function SetupPage({ onBack }) {
1244
1076
  }
1245
1077
 
1246
1078
  // src/tui/pages/OnboardingPage.tsx
1247
- import { useEffect as useEffect12, useCallback as useCallback9, useState as useState11, useMemo as useMemo4 } from "react";
1248
- import { Box as Box7, Text as Text8, useInput as useInput3 } from "ink";
1249
- import Spinner5 from "ink-spinner";
1079
+ import { useEffect as useEffect9, useCallback as useCallback7, useState as useState9, useMemo as useMemo4 } from "react";
1080
+ import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
1081
+ import Spinner4 from "ink-spinner";
1250
1082
  import chalk2 from "chalk";
1251
1083
 
1252
1084
  // src/tui/hooks/useAuth.ts
1253
- import { useState as useState10, useCallback as useCallback8, useRef as useRef3, useEffect as useEffect11 } from "react";
1085
+ import { useState as useState8, useCallback as useCallback6, useRef as useRef4, useEffect as useEffect8 } from "react";
1254
1086
  import open from "open";
1255
1087
  var POLL_INTERVAL = 2e3;
1256
1088
  var MAX_ATTEMPTS = 30;
1257
1089
  function useAuth() {
1258
- const [status, setStatus] = useState10("idle");
1259
- const [authUrl, setAuthUrl] = useState10(null);
1260
- const [timeRemaining, setTimeRemaining] = useState10(0);
1261
- const cancelledRef = useRef3(false);
1262
- const timerRef = useRef3(null);
1263
- useEffect11(() => {
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(() => {
1264
1096
  return () => {
1265
1097
  cancelledRef.current = true;
1266
1098
  if (timerRef.current) clearInterval(timerRef.current);
1267
1099
  };
1268
1100
  }, []);
1269
- const cancel = useCallback8(() => {
1101
+ const cancel = useCallback6(() => {
1270
1102
  cancelledRef.current = true;
1271
1103
  if (timerRef.current) {
1272
1104
  clearInterval(timerRef.current);
@@ -1276,7 +1108,7 @@ function useAuth() {
1276
1108
  setAuthUrl(null);
1277
1109
  setTimeRemaining(0);
1278
1110
  }, []);
1279
- const startAuth = useCallback8(() => {
1111
+ const startAuth = useCallback6(() => {
1280
1112
  cancelledRef.current = false;
1281
1113
  const run = async () => {
1282
1114
  try {
@@ -1334,10 +1166,10 @@ function useAuth() {
1334
1166
  }
1335
1167
 
1336
1168
  // src/tui/pages/OnboardingPage.tsx
1337
- import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1169
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1338
1170
  var SHIMMER_SPEED = 35;
1339
1171
  function useShimmerLogo() {
1340
- const [frame, setFrame] = useState11(0);
1172
+ const [frame, setFrame] = useState9(0);
1341
1173
  const lines = useMemo4(() => LogoString.split("\n"), []);
1342
1174
  const totalChars = useMemo4(() => {
1343
1175
  let count = 0;
@@ -1349,7 +1181,7 @@ function useShimmerLogo() {
1349
1181
  return count;
1350
1182
  }, [lines]);
1351
1183
  const cycleLength = totalChars + 40;
1352
- useEffect12(() => {
1184
+ useEffect9(() => {
1353
1185
  const interval = setInterval(() => {
1354
1186
  setFrame((f) => (f + 1) % cycleLength);
1355
1187
  }, SHIMMER_SPEED);
@@ -1402,16 +1234,16 @@ function OnboardingPage({ onComplete }) {
1402
1234
  cancel: cancelAuth
1403
1235
  } = useAuth();
1404
1236
  const shimmerLogo = useShimmerLogo();
1405
- useEffect12(() => {
1237
+ useEffect9(() => {
1406
1238
  if (authStatus === "success") {
1407
1239
  const timer = setTimeout(() => onComplete(), 1500);
1408
1240
  return () => clearTimeout(timer);
1409
1241
  }
1410
1242
  }, [authStatus, onComplete]);
1411
- useEffect12(() => {
1243
+ useEffect9(() => {
1412
1244
  return () => cancelAuth();
1413
1245
  }, []);
1414
- const handleAction = useCallback9(() => {
1246
+ const handleAction = useCallback7(() => {
1415
1247
  cancelAuth();
1416
1248
  startAuth();
1417
1249
  }, [cancelAuth, startAuth]);
@@ -1421,57 +1253,66 @@ function OnboardingPage({ onComplete }) {
1421
1253
  handleAction();
1422
1254
  }
1423
1255
  });
1424
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", flexGrow: 1, children: [
1425
- /* @__PURE__ */ jsx8(Box7, { flexGrow: 1 }),
1426
- /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", alignItems: "center", children: [
1427
- /* @__PURE__ */ jsx8(Text8, { children: shimmerLogo }),
1428
- /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", alignItems: "center", marginTop: 2, children: /* @__PURE__ */ jsx8(Text8, { bold: true, color: "white", children: "MindStudio Local Tunnel" }) }),
1429
- /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", alignItems: "center", children: [
1430
- authStatus === "idle" && /* @__PURE__ */ jsxs7(Fragment3, { children: [
1431
- /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "Connect your MindStudio account to get started." }),
1432
- /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "cyan", bold: true, children: "Press any key to Connect Account" }) })
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" }) })
1433
1265
  ] }),
1434
- (authStatus === "expired" || authStatus === "timeout") && /* @__PURE__ */ jsxs7(Fragment3, { children: [
1435
- /* @__PURE__ */ jsx8(Text8, { color: "red", children: authStatus === "expired" ? "Authorization expired." : "Authorization timed out." }),
1436
- /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "cyan", bold: true, children: "Press any key to Try Again" }) })
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" }) })
1437
1269
  ] }),
1438
- authStatus === "waiting" && /* @__PURE__ */ jsxs7(Fragment3, { children: [
1439
- /* @__PURE__ */ jsxs7(Box7, { children: [
1440
- /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: /* @__PURE__ */ jsx8(Spinner5, { type: "dots" }) }),
1441
- /* @__PURE__ */ jsxs7(Text8, { children: [
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: [
1442
1274
  " ",
1443
1275
  "Waiting for browser authorization... (",
1444
1276
  timeRemaining,
1445
1277
  "s remaining)"
1446
1278
  ] })
1447
1279
  ] }),
1448
- authUrl && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", alignItems: "center", marginTop: 1, children: [
1449
- /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "If browser didn't open, visit:" }),
1450
- /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: authUrl })
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 })
1451
1283
  ] })
1452
1284
  ] }),
1453
- authStatus === "success" && /* @__PURE__ */ jsxs7(Text8, { color: "green", children: [
1285
+ authStatus === "success" && /* @__PURE__ */ jsxs6(Text7, { color: "green", children: [
1454
1286
  "\u2713",
1455
1287
  " Authenticated!"
1456
1288
  ] })
1457
1289
  ] })
1458
1290
  ] }),
1459
- /* @__PURE__ */ jsx8(Box7, { flexGrow: 1 })
1291
+ /* @__PURE__ */ jsx7(Box6, { flexGrow: 1 })
1460
1292
  ] });
1461
1293
  }
1462
1294
 
1463
1295
  // src/tui/App.tsx
1464
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1296
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1297
+ var MODEL_TYPE_MAP = {
1298
+ text: "llm_chat",
1299
+ image: "image_generation",
1300
+ video: "video_generation"
1301
+ };
1465
1302
  function App({ runner }) {
1466
1303
  const { exit } = useApp();
1467
- const { stdout } = useStdout5();
1304
+ const { stdout } = useStdout6();
1468
1305
  const {
1469
1306
  status: connectionStatus,
1470
1307
  environment,
1471
1308
  error: connectionError,
1472
1309
  retry: retryConnection
1473
1310
  } = useConnection();
1474
- const { refresh: refreshProviders } = useProviders();
1311
+ const {
1312
+ providers,
1313
+ loading: providersLoading,
1314
+ refresh: refreshProviders
1315
+ } = useSetupProviders();
1475
1316
  const {
1476
1317
  models,
1477
1318
  warnings: modelWarnings,
@@ -1479,47 +1320,86 @@ function App({ runner }) {
1479
1320
  refresh: refreshModels
1480
1321
  } = useModels();
1481
1322
  const { requests } = useRequests();
1482
- const { syncedNames, refresh: refreshSynced } = useSyncedModels(connectionStatus);
1323
+ const {
1324
+ syncedNames,
1325
+ syncedModels,
1326
+ refresh: refreshSynced
1327
+ } = useSyncedModels(connectionStatus);
1483
1328
  const shouldOnboard = getApiKey() === void 0;
1484
- const [page, setPage] = useState12(
1329
+ const [page, setPage] = useState10(
1485
1330
  shouldOnboard ? "onboarding" : "dashboard"
1486
1331
  );
1487
- useEffect13(() => {
1332
+ const [syncStatus, setSyncStatus] = useState10(
1333
+ "idle"
1334
+ );
1335
+ const syncTimerRef = useRef5();
1336
+ const lastSyncPayloadRef = useRef5("");
1337
+ useEffect10(() => {
1338
+ if (connectionStatus === "not_authenticated") {
1339
+ setPage("onboarding");
1340
+ }
1341
+ }, [connectionStatus]);
1342
+ useEffect10(() => {
1488
1343
  if (page === "dashboard") {
1489
1344
  refreshAll();
1490
1345
  }
1491
1346
  }, [page]);
1492
- useEffect13(() => {
1493
- if (connectionStatus === "connected" && models.length > 0) {
1494
- runner.start(models.map((m) => m.name));
1347
+ useEffect10(() => {
1348
+ if (connectionStatus === "connected" && syncedModels.length > 0) {
1349
+ runner.start(syncedModels);
1495
1350
  }
1496
- }, [connectionStatus, models, runner]);
1497
- useEffect13(() => () => runner.stop(), [runner]);
1498
- const refreshAll = useCallback10(async () => {
1499
- await Promise.all([
1500
- refreshProviders(),
1501
- refreshModels(),
1502
- refreshSynced()
1503
- ]);
1504
- }, [refreshProviders, refreshModels, refreshSynced]);
1505
- const handleQuit = useCallback10(() => {
1351
+ }, [connectionStatus, syncedModels, runner]);
1352
+ useEffect10(() => () => runner.stop(), [runner]);
1353
+ const refreshAll = useCallback8(
1354
+ async (silent = false) => {
1355
+ if (!silent) setSyncStatus("syncing");
1356
+ const [discoveredModels] = await Promise.all([
1357
+ refreshModels(),
1358
+ refreshProviders()
1359
+ ]);
1360
+ const modelsToSync = discoveredModels.filter((m) => !m.statusHint).map((m) => ({
1361
+ name: m.name,
1362
+ provider: m.provider,
1363
+ type: MODEL_TYPE_MAP[m.capability] || "llm_chat",
1364
+ parameters: m.parameters
1365
+ }));
1366
+ const payload = JSON.stringify(modelsToSync);
1367
+ if (payload !== lastSyncPayloadRef.current && modelsToSync.length > 0) {
1368
+ try {
1369
+ await syncModels(modelsToSync);
1370
+ lastSyncPayloadRef.current = payload;
1371
+ } catch {
1372
+ }
1373
+ }
1374
+ await refreshSynced();
1375
+ if (!silent) {
1376
+ setSyncStatus("synced");
1377
+ clearTimeout(syncTimerRef.current);
1378
+ syncTimerRef.current = setTimeout(() => setSyncStatus("idle"), 1500);
1379
+ }
1380
+ },
1381
+ [refreshProviders, refreshModels, refreshSynced]
1382
+ );
1383
+ useEffect10(() => {
1384
+ if (connectionStatus !== "connected" || page !== "dashboard") return;
1385
+ const interval = setInterval(() => refreshAll(true), 1500);
1386
+ return () => clearInterval(interval);
1387
+ }, [connectionStatus, page, refreshAll]);
1388
+ const handleQuit = useCallback8(() => {
1506
1389
  runner.stop();
1507
1390
  exit();
1508
1391
  }, [runner, exit]);
1509
- const handleOnboardingComplete = useCallback10(() => {
1392
+ const handleOnboardingComplete = useCallback8(() => {
1510
1393
  retryConnection();
1511
1394
  refreshAll();
1512
1395
  setPage("dashboard");
1513
1396
  }, [retryConnection, refreshAll]);
1514
- const handleNavigate = useCallback10(
1397
+ const handleNavigate = useCallback8(
1515
1398
  (id) => {
1516
1399
  switch (id) {
1517
1400
  case "auth":
1518
1401
  setPage("onboarding");
1519
1402
  break;
1520
- case "register":
1521
- setPage("sync");
1522
- break;
1523
1403
  case "setup":
1524
1404
  setPage("setup");
1525
1405
  break;
@@ -1531,12 +1411,12 @@ function App({ runner }) {
1531
1411
  break;
1532
1412
  }
1533
1413
  },
1534
- [refreshModels, refreshSynced, refreshAll, handleQuit]
1414
+ [refreshAll, handleQuit]
1535
1415
  );
1536
1416
  const subpageMenuItems = [
1537
1417
  { id: "back", label: "Back", description: "Return to dashboard" }
1538
1418
  ];
1539
- const handleSubpageNavigate = useCallback10(
1419
+ const handleSubpageNavigate = useCallback8(
1540
1420
  (id) => {
1541
1421
  if (id === "back") {
1542
1422
  setPage("dashboard");
@@ -1546,32 +1426,51 @@ function App({ runner }) {
1546
1426
  },
1547
1427
  [handleNavigate]
1548
1428
  );
1549
- const termHeight = (stdout?.rows ?? 24) - 4;
1550
- return /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", height: termHeight, overflow: "hidden", children: page === "onboarding" ? /* @__PURE__ */ jsx9(OnboardingPage, { onComplete: handleOnboardingComplete }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
1551
- /* @__PURE__ */ jsx9(
1429
+ const [termSize, setTermSize] = useState10({
1430
+ rows: stdout?.rows ?? 24,
1431
+ columns: stdout?.columns ?? 80
1432
+ });
1433
+ useEffect10(() => {
1434
+ if (!stdout) return;
1435
+ const onResize = () => {
1436
+ stdout.write("\x1B[2J\x1B[H");
1437
+ setTermSize({ rows: stdout.rows, columns: stdout.columns });
1438
+ };
1439
+ stdout.on("resize", onResize);
1440
+ return () => {
1441
+ stdout.off("resize", onResize);
1442
+ };
1443
+ }, [stdout]);
1444
+ const termHeight = termSize.rows - 4;
1445
+ 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(
1552
1448
  Header,
1553
1449
  {
1554
1450
  connection: connectionStatus,
1555
1451
  environment,
1556
1452
  configPath: getConfigPath(),
1557
- connectionError
1453
+ connectionError,
1454
+ compact: compactHeader
1558
1455
  }
1559
1456
  ),
1560
- page === "dashboard" && /* @__PURE__ */ jsx9(
1457
+ page === "dashboard" && /* @__PURE__ */ jsx8(
1561
1458
  DashboardPage,
1562
1459
  {
1563
1460
  requests,
1564
1461
  models,
1565
1462
  modelWarnings,
1463
+ providers,
1464
+ providersLoading,
1566
1465
  syncedNames,
1567
1466
  modelsLoading,
1467
+ syncStatus,
1568
1468
  onNavigate: handleNavigate
1569
1469
  }
1570
1470
  ),
1571
- page === "setup" && /* @__PURE__ */ jsx9(SetupPage, { onBack: () => setPage("dashboard") }),
1572
- page === "sync" && /* @__PURE__ */ jsx9(SyncPage, {}),
1573
- page !== "dashboard" && page !== "setup" && /* @__PURE__ */ jsx9(Box8, { flexGrow: 1 }),
1574
- page !== "dashboard" && page !== "setup" && /* @__PURE__ */ jsx9(
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(
1575
1474
  NavigationMenu,
1576
1475
  {
1577
1476
  items: subpageMenuItems,
@@ -1621,8 +1520,8 @@ async function checkForUpdate() {
1621
1520
  }
1622
1521
 
1623
1522
  // src/tui/components/UpdatePrompt.tsx
1624
- import { Box as Box9, Text as Text9, useInput as useInput4 } from "ink";
1625
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
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";
1626
1525
  function UpdatePrompt({
1627
1526
  currentVersion,
1628
1527
  latestVersion,
@@ -1635,10 +1534,10 @@ function UpdatePrompt({
1635
1534
  onChoice(false);
1636
1535
  }
1637
1536
  });
1638
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
1639
- /* @__PURE__ */ jsxs9(Text9, { children: [
1640
- /* @__PURE__ */ jsx10(Text9, { color: "yellow", bold: true, children: "Update available:" }),
1641
- /* @__PURE__ */ jsxs9(Text9, { children: [
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: [
1642
1541
  " ",
1643
1542
  "v",
1644
1543
  currentVersion,
@@ -1648,20 +1547,20 @@ function UpdatePrompt({
1648
1547
  latestVersion
1649
1548
  ] })
1650
1549
  ] }),
1651
- /* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
1550
+ /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
1652
1551
  "Press ",
1653
- /* @__PURE__ */ jsx10(Text9, { bold: true, color: "cyan", children: "y" }),
1552
+ /* @__PURE__ */ jsx9(Text8, { bold: true, color: "cyan", children: "y" }),
1654
1553
  " to update, any other key to skip"
1655
1554
  ] }) })
1656
1555
  ] });
1657
1556
  }
1658
1557
 
1659
1558
  // src/tui/index.tsx
1660
- import { jsx as jsx11 } from "react/jsx-runtime";
1559
+ import { jsx as jsx10 } from "react/jsx-runtime";
1661
1560
  async function promptForUpdate(currentVersion, latestVersion) {
1662
1561
  return new Promise((resolve) => {
1663
1562
  const { unmount } = render(
1664
- /* @__PURE__ */ jsx11(
1563
+ /* @__PURE__ */ jsx10(
1665
1564
  UpdatePrompt,
1666
1565
  {
1667
1566
  currentVersion,
@@ -1703,7 +1602,7 @@ async function startTUI() {
1703
1602
  }
1704
1603
  const runner = new TunnelRunner();
1705
1604
  const { waitUntilExit } = render(
1706
- /* @__PURE__ */ jsx11(App, { runner }),
1605
+ /* @__PURE__ */ jsx10(App, { runner }),
1707
1606
  {
1708
1607
  exitOnCtrlC: true
1709
1608
  }
@@ -1714,4 +1613,4 @@ async function startTUI() {
1714
1613
  export {
1715
1614
  startTUI
1716
1615
  };
1717
- //# sourceMappingURL=tui-YFUZJIGF.js.map
1616
+ //# sourceMappingURL=tui-4ZB4JAV4.js.map