umbrella-context 0.1.37 → 0.1.39

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.
@@ -8,7 +8,7 @@ import { formatUmbrellaAuthStatus } from "../auth-state.js";
8
8
  import { getUmbrellaContextRuntimeSummary, useUmbrellaContextRuntimeBridgeStore, hydrateUmbrellaContextRuntimeBridgeFromSnapshot, } from "../adapters/byterover-context-runtime-store.js";
9
9
  import { getUmbrellaTransportTaskBridgeSummary, useUmbrellaTransportTaskBridgeStore, hydrateUmbrellaTransportTaskBridgeFromSnapshot, } from "../adapters/byterover-transport-task-store.js";
10
10
  import { buildVendorRuntimeBridgeSnapshot, } from "../adapters/byterover-runtime-bridge.js";
11
- import { addPendingMemory, completeTask, createTask, ensureRepoContext, getContextTreeState, ensureSessionState, getConnectorRuns, getInstalledConnectors, getInstalledHubEntries, getPendingMemories, getPulledFixes, getPulledMemories, getRepoContext, getSessionState, getTasks, getTransportState, recordSessionEvent, setSessionPanel, summarizeLocalMemoryMatches, } from "../repo-state.js";
11
+ import { addPendingMemory, completeTask, createTask, ensureRepoContext, getContextTreeState, ensureSessionState, getConnectorRuns, getInstalledConnectors, getInstalledHubEntries, getPendingMemories, getPulledFixes, getPulledMemories, getRepoContext, removeTask, getSessionState, getTasks, getTransportState, recordSessionEvent, setSessionPanel, summarizeLocalMemoryMatches, } from "../repo-state.js";
12
12
  import { configManager } from "../config.js";
13
13
  import { getStoredUmbrellaAuthSnapshot, setUmbrellaAuthAuthorized, setUmbrellaAuthChecking, setUmbrellaAuthFailure, } from "../adapters/umbrella-auth-runtime.js";
14
14
  import { checkUmbrellaOnboardingServer, connectUmbrellaDevice, createUmbrellaOnboardingCompany, createUmbrellaOnboardingSpace, loadUmbrellaOnboardingSpaces, signInUmbrellaOnboarding, } from "../adapters/umbrella-onboarding.js";
@@ -20,6 +20,7 @@ import { pullCommandAction } from "./pull.js";
20
20
  import { pushCommandAction } from "./push.js";
21
21
  import { getProviderReadinessSummary, providersCommandAction } from "./providers.js";
22
22
  import { spaceCommandAction } from "./space.js";
23
+ import { buildConnectionPresentation } from "./status.js";
23
24
  const PANELS = [
24
25
  { id: "home", title: "Home", description: "Repo status, sync, and next steps" },
25
26
  { id: "tasks", title: "Tasks", description: "Live local task activity from .um" },
@@ -31,12 +32,52 @@ const PANELS = [
31
32
  { id: "connectors", title: "Connectors", description: "Repo automations and MCP wiring" },
32
33
  { id: "session", title: "Session", description: "Live terminal memory for this repo" },
33
34
  ];
35
+ const PANEL_SHORTCUTS = [
36
+ { id: "home", key: "h", label: "Home" },
37
+ { id: "tasks", key: "t", label: "Tasks" },
38
+ { id: "activity", key: "a", label: "Activity" },
39
+ { id: "providers", key: "v", label: "Providers" },
40
+ { id: "model", key: "m", label: "Models" },
41
+ { id: "spaces", key: "w", label: "Spaces" },
42
+ { id: "hub", key: "b", label: "Hub" },
43
+ { id: "connectors", key: "c", label: "Connectors" },
44
+ { id: "session", key: "u", label: "Session" },
45
+ ];
46
+ const DEFAULT_UMBRELLA_SERVER_URL = "http://5.161.55.138:3100";
47
+ const LOCAL_UMBRELLA_SERVER_URL = "http://127.0.0.1:3100";
48
+ const SETUP_SERVER_CHOICES = [
49
+ {
50
+ value: "live",
51
+ title: `Live Umbrella server (${DEFAULT_UMBRELLA_SERVER_URL})`,
52
+ description: "Recommended for most people. Uses the shared live Umbrella server.",
53
+ },
54
+ {
55
+ value: "local",
56
+ title: `Local development server (${LOCAL_UMBRELLA_SERVER_URL})`,
57
+ description: "Only use this if Umbrella is running on this same computer right now.",
58
+ },
59
+ {
60
+ value: "custom",
61
+ title: "Custom URL",
62
+ description: "Type a different Umbrella server URL yourself.",
63
+ },
64
+ ];
34
65
  function trimSlash(value) {
35
66
  return value.trim().replace(/\/+$/, "");
36
67
  }
37
68
  function panelIndexFor(id) {
38
69
  return Math.max(0, PANELS.findIndex((panel) => panel.id === id));
39
70
  }
71
+ function panelForShortcut(input) {
72
+ const normalized = input.trim().toLowerCase();
73
+ if (!normalized)
74
+ return null;
75
+ const number = Number(normalized);
76
+ if (Number.isInteger(number) && number >= 1 && number <= PANELS.length) {
77
+ return PANELS[number - 1]?.id ?? null;
78
+ }
79
+ return PANEL_SHORTCUTS.find((entry) => entry.key === normalized)?.id ?? null;
80
+ }
40
81
  function panelForTaskKind(kind) {
41
82
  switch (kind) {
42
83
  case "provider":
@@ -194,20 +235,23 @@ function ActivityPanelView(props) {
194
235
  return (_jsxs(Text, { color: highlighted ? "yellow" : event.status === "success" ? "green" : event.status === "failure" ? "red" : event.status === "warning" ? "yellow" : "white", children: [highlighted ? ">" : " ", " [", event.kind, "] ", event.title] }, event.id));
195
236
  }) }), _jsx(Section, { title: "Selected Event", children: selectedEvent ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: selectedEvent.detail ?? "No extra detail recorded." }), _jsxs(Text, { color: "gray", children: ["When: ", new Date(selectedEvent.at).toLocaleString()] }), _jsxs(Text, { color: "gray", children: ["Panel: ", panelForActivityItem(selectedEvent) ?? "None", " | Focus: ", selectedEvent.focus ?? "None"] }), mode === "inspect" ? (_jsxs(Section, { title: "Deep Inspect", children: [_jsxs(Text, { color: "gray", children: ["Kind: ", selectedEvent.kind] }), _jsxs(Text, { color: "gray", children: ["Status: ", selectedEvent.status] }), _jsxs(Text, { color: "gray", children: ["Suggested panel: ", panelForActivityItem(selectedEvent) ?? "None"] }), _jsx(Text, { color: "gray", children: "Open the related panel with Enter or o." })] })) : null] })) : (_jsx(Text, { color: "gray", children: "Choose an event to view its detail." })) }), _jsx(Text, { color: "gray", children: mode === "inspect" ? "Esc goes back to the event list. Enter or o jumps to the related panel. r refreshes the runtime." : "Up and down move through events. Enter or o jumps to the related panel when one exists. r refreshes the runtime." })] }));
196
237
  }
197
- function PanelShell({ activePanel, children, footer, message, selectedPanelIndex, }) {
198
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "UMBRELLA CONTEXT TUI" }), _jsx(Text, { color: "gray", children: " pane-based repo Context terminal" })] }), _jsxs(Box, { children: [_jsx(Box, { flexDirection: "column", width: 26, marginRight: 2, borderStyle: "round", paddingX: 1, paddingY: 1, children: PANELS.map((panel, index) => {
199
- const active = panel.id === activePanel;
200
- const selected = index === selectedPanelIndex;
201
- return (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: active ? "green" : selected ? "yellow" : "white", children: [active ? "*" : selected ? ">" : " ", " ", panel.title] }) }, panel.id));
202
- }) }), _jsx(Box, { flexDirection: "column", flexGrow: 1, borderStyle: "round", paddingX: 1, paddingY: 1, children: children })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [message ? _jsx(Text, { color: "green", children: message }) : null, _jsx(Text, { color: "gray", children: footer })] })] }));
238
+ function PanelShell({ activePanel, children, footer, message, navigationFocus, selectedPanelIndex, }) {
239
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "UMBRELLA CONTEXT TUI" }), _jsx(Text, { color: "gray", children: " pane-based repo Context terminal" })] }), _jsxs(Box, { marginBottom: 1, gap: 2, children: [_jsx(Text, { color: "gray", children: "Active panel:" }), _jsx(Text, { color: "white", children: PANELS.find((panel) => panel.id === activePanel)?.title ?? activePanel }), _jsx(Text, { color: "gray", children: "Focus:" }), _jsx(Text, { color: navigationFocus === "sidebar" ? "yellow" : "green", children: navigationFocus === "sidebar" ? "Sidebar" : "Panel" }), _jsx(Text, { color: "gray", children: navigationFocus === "sidebar"
240
+ ? "Enter or Right opens the panel."
241
+ : "Left or Esc returns to the sidebar." })] }), _jsxs(Box, { children: [_jsxs(Box, { flexDirection: "column", width: 26, marginRight: 2, borderStyle: "round", borderColor: navigationFocus === "sidebar" ? "yellow" : undefined, paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: navigationFocus === "sidebar" ? "yellow" : "gray", children: navigationFocus === "sidebar" ? "Sidebar focus" : "Panels" }), PANELS.map((panel, index) => {
242
+ const active = panel.id === activePanel;
243
+ const selected = index === selectedPanelIndex;
244
+ const shortcut = PANEL_SHORTCUTS.find((entry) => entry.id === panel.id)?.key ?? String(index + 1);
245
+ return (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: active ? "green" : selected ? "yellow" : "white", children: [active ? "*" : selected ? ">" : " ", " [", shortcut, "] ", panel.title] }) }, panel.id));
246
+ })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, borderStyle: "round", paddingX: 1, paddingY: 1, children: children })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [message ? _jsx(Text, { color: "green", children: message }) : null, _jsx(Text, { color: "gray", children: footer }), _jsx(Text, { color: "gray", children: "Tip: number keys or h/t/a/v/m/w/b/c/u jump straight to a panel." })] })] }));
203
247
  }
204
248
  function SetupView(props) {
205
- const { busyLabel, companies, config, authSnapshot, inputValue, message, onInputChange, selectedCompanyIndex, selectedSpaceIndex, serverUrl, spaces, step, } = props;
206
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: config ? "Connected Device" : "Connect This Device" }), _jsx(Text, { color: "gray", children: "Sign into Umbrella, choose a company, then choose the team space for this repo." }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(StatusLine, { label: "Server", value: serverUrl || "Not set" }), _jsx(StatusLine, { label: "Step", value: step }), _jsx(StatusLine, { label: "Auth state", value: formatUmbrellaAuthStatus(authSnapshot.status) }), _jsx(StatusLine, { label: "Auth message", value: authSnapshot.message ?? "No auth check yet" }), _jsx(StatusLine, { label: "Auth hint", value: authSnapshot.hint ?? "No auth hint yet" }), config ? _jsx(StatusLine, { label: "Current link", value: `${config.companyName} / ${config.projectName}` }) : null] }), busyLabel ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "cyan", children: [_jsx(Spinner, { type: "dots" }), " ", busyLabel] }) })) : null, message ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "green", children: message }) })) : null, (step === "server" || step === "email" || step === "password" || step === "company-create" || step === "space-create") ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: step === "server" ? "Server URL" :
249
+ const { busyLabel, companies, config, authSnapshot, inputValue, message, onInputChange, selectedCompanyIndex, selectedServerChoiceIndex, selectedSpaceIndex, serverUrl, spaces, step, } = props;
250
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: config ? "Connected Device" : "Connect This Device" }), _jsx(Text, { color: "gray", children: "Sign into Umbrella, choose a company, then choose the team space for this repo." }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(StatusLine, { label: "Server", value: serverUrl || "Not set" }), _jsx(StatusLine, { label: "Step", value: step }), _jsx(StatusLine, { label: "Auth state", value: formatUmbrellaAuthStatus(authSnapshot.status) }), _jsx(StatusLine, { label: "Auth message", value: authSnapshot.message ?? "No auth check yet" }), _jsx(StatusLine, { label: "Auth hint", value: authSnapshot.hint ?? "No auth hint yet" }), config ? _jsx(StatusLine, { label: "Current link", value: `${config.companyName} / ${config.projectName}` }) : null] }), busyLabel ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "cyan", children: [_jsx(Spinner, { type: "dots" }), " ", busyLabel] }) })) : null, message ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "green", children: message }) })) : null, step === "server-choice" ? (_jsx(Section, { title: "Choose an Umbrella Server", children: SETUP_SERVER_CHOICES.map((choice, index) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: index === selectedServerChoiceIndex ? "yellow" : "white", children: [index === selectedServerChoiceIndex ? ">" : " ", " ", choice.title] }), _jsx(Text, { color: "gray", children: choice.description })] }, choice.value))) })) : null, (step === "server" || step === "email" || step === "password" || step === "company-create" || step === "space-create") ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: step === "server" ? "Server URL" :
207
251
  step === "email" ? "Umbrella email" :
208
252
  step === "password" ? "Umbrella password" :
209
253
  step === "company-create" ? "New company name" :
210
- "New space name" }), _jsx(TextInput, { value: inputValue, onChange: onInputChange })] })) : null, step === "company" ? (_jsxs(Section, { title: "Companies", children: [companies.map((company, index) => (_jsxs(Text, { color: index === selectedCompanyIndex ? "yellow" : "white", children: [index === selectedCompanyIndex ? ">" : " ", " ", company.name, " (", company.issuePrefix, ")"] }, company.id))), _jsxs(Text, { color: selectedCompanyIndex === companies.length ? "yellow" : "white", children: [selectedCompanyIndex === companies.length ? ">" : " ", " + Create new company"] })] })) : null, step === "space" ? (_jsxs(Section, { title: "Spaces", children: [spaces.map((space, index) => (_jsxs(Text, { color: index === selectedSpaceIndex ? "yellow" : "white", children: [index === selectedSpaceIndex ? ">" : " ", " ", space.name, space.isPrimary ? " (core)" : ""] }, space.id))), _jsxs(Text, { color: selectedSpaceIndex === spaces.length ? "yellow" : "white", children: [selectedSpaceIndex === spaces.length ? ">" : " ", " + Create new space"] })] })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "gray", children: "Enter confirms the current step. Up and down move through companies or spaces." }), _jsx(Text, { color: "gray", children: "Esc leaves setup. This terminal saves the final link into your device config and the repo's .um folder." })] })] }));
254
+ "New space name" }), _jsx(TextInput, { value: inputValue, onChange: onInputChange })] })) : null, step === "company" ? (_jsxs(Section, { title: "Companies", children: [companies.map((company, index) => (_jsxs(Text, { color: index === selectedCompanyIndex ? "yellow" : "white", children: [index === selectedCompanyIndex ? ">" : " ", " ", company.name, " (", company.issuePrefix, ")"] }, company.id))), _jsxs(Text, { color: selectedCompanyIndex === companies.length ? "yellow" : "white", children: [selectedCompanyIndex === companies.length ? ">" : " ", " + Create new company"] })] })) : null, step === "space" ? (_jsxs(Section, { title: "Spaces", children: [spaces.map((space, index) => (_jsxs(Text, { color: index === selectedSpaceIndex ? "yellow" : "white", children: [index === selectedSpaceIndex ? ">" : " ", " ", space.name, space.isPrimary ? " (core)" : ""] }, space.id))), _jsxs(Text, { color: selectedSpaceIndex === spaces.length ? "yellow" : "white", children: [selectedSpaceIndex === spaces.length ? ">" : " ", " + Create new space"] })] })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "gray", children: "Enter confirms the current step. Up and down move through server choices, companies, or spaces." }), _jsx(Text, { color: "gray", children: "Esc leaves setup. This terminal saves the final link into your device config and the repo's .um folder." })] })] }));
211
255
  }
212
256
  function App() {
213
257
  const { exit } = useApp();
@@ -221,6 +265,7 @@ function App() {
221
265
  const vendorConnectorsStore = useUmbrellaContextRuntimeBridgeStore((state) => state.connectorsStore);
222
266
  const [selectedPanelIndex, setSelectedPanelIndex] = useState(0);
223
267
  const [activePanel, setActivePanel] = useState(configManager.config ? "home" : "setup");
268
+ const [navigationFocus, setNavigationFocus] = useState("sidebar");
224
269
  const [refreshTick, setRefreshTick] = useState(0);
225
270
  const [message, setMessage] = useState(null);
226
271
  const [busyLabel, setBusyLabel] = useState(null);
@@ -228,8 +273,9 @@ function App() {
228
273
  const [inputValue, setInputValue] = useState("");
229
274
  const [queryResult, setQueryResult] = useState(null);
230
275
  const [refreshState, setRefreshState] = useState(null);
231
- const [setupStep, setSetupStep] = useState(configManager.config ? "done" : "server");
232
- const [serverUrl, setServerUrl] = useState(configManager.config?.umbrellaUrl ?? configManager.config?.serverUrl ?? "http://127.0.0.1:3100");
276
+ const [setupStep, setSetupStep] = useState(configManager.config ? "done" : "server-choice");
277
+ const [serverUrl, setServerUrl] = useState(configManager.config?.umbrellaUrl ?? configManager.config?.serverUrl ?? DEFAULT_UMBRELLA_SERVER_URL);
278
+ const [selectedServerChoiceIndex, setSelectedServerChoiceIndex] = useState(0);
233
279
  const [setupEmail, setSetupEmail] = useState("");
234
280
  const [authSnapshot, setAuthSnapshot] = useState(getStoredUmbrellaAuthSnapshot());
235
281
  const [setupCookie, setSetupCookie] = useState(null);
@@ -251,12 +297,18 @@ function App() {
251
297
  const [modelPanelRecentModel, setModelPanelRecentModel] = useState(null);
252
298
  const [hubPanelMode, setHubPanelMode] = useState("browse");
253
299
  const [hubPanelSelectedIndex, setHubPanelSelectedIndex] = useState(0);
300
+ const [hubRegistryEntries, setHubRegistryEntries] = useState([]);
254
301
  const [connectorsPanelMode, setConnectorsPanelMode] = useState("browse");
255
302
  const [connectorsPanelSelectedIndex, setConnectorsPanelSelectedIndex] = useState(0);
256
303
  const [tasksPanelMode, setTasksPanelMode] = useState("browse");
257
304
  const [tasksPanelSelectedIndex, setTasksPanelSelectedIndex] = useState(0);
258
305
  const [activityPanelMode, setActivityPanelMode] = useState("browse");
259
306
  const [activityPanelSelectedIndex, setActivityPanelSelectedIndex] = useState(0);
307
+ function focusPanel(panelId, focus = "sidebar") {
308
+ setActivePanel(panelId);
309
+ setSelectedPanelIndex(panelIndexFor(panelId));
310
+ setNavigationFocus(focus);
311
+ }
260
312
  async function refreshAll() {
261
313
  try {
262
314
  if (configManager.config) {
@@ -309,6 +361,12 @@ function App() {
309
361
  const vendorContextSummary = getUmbrellaContextRuntimeSummary();
310
362
  const vendorTasks = useMemo(() => buildVendorTasksList(vendorTaskStore), [vendorTaskStore]);
311
363
  const vendorActivity = useMemo(() => buildActivityItems(vendorTransportStore, refreshState?.session?.events ?? []), [vendorTransportStore, refreshState?.session?.events]);
364
+ const connectionPresentation = useMemo(() => configManager.config
365
+ ? buildConnectionPresentation({
366
+ umbrellaUrl: configManager.config.umbrellaUrl ?? undefined,
367
+ serverUrl: configManager.config.serverUrl,
368
+ }, authSnapshot)
369
+ : null, [authSnapshot]);
312
370
  const vendorProviders = useMemo(() => vendorProviderStore?.providers ?? [], [vendorProviderStore]);
313
371
  const vendorModels = useMemo(() => (vendorModelStore?.models ?? []).map((model) => model.label), [vendorModelStore]);
314
372
  const vendorSpaces = useMemo(() => (vendorSpaceStore?.spaces ?? []).map((space) => ({
@@ -347,11 +405,14 @@ function App() {
347
405
  useEffect(() => {
348
406
  if (activePanel !== "hub" || busyLabel)
349
407
  return;
350
- const registryEntries = listHubRegistryEntries();
351
- const recentSlug = vendorHubStore?.recentSlug ?? null;
352
- const recentIndex = registryEntries.findIndex((entry) => entry.slug === recentSlug);
353
- setHubPanelSelectedIndex(recentIndex >= 0 ? recentIndex : 0);
354
- setHubPanelMode("browse");
408
+ void (async () => {
409
+ const registryEntries = await listHubRegistryEntries();
410
+ setHubRegistryEntries(registryEntries);
411
+ const recentSlug = vendorHubStore?.recentSlug ?? null;
412
+ const recentIndex = registryEntries.findIndex((entry) => entry.slug === recentSlug);
413
+ setHubPanelSelectedIndex(recentIndex >= 0 ? recentIndex : 0);
414
+ setHubPanelMode("browse");
415
+ })();
355
416
  }, [activePanel, refreshTick, busyLabel, vendorHubStore?.recentSlug]);
356
417
  useEffect(() => {
357
418
  if (activePanel !== "connectors" || busyLabel)
@@ -369,14 +430,24 @@ function App() {
369
430
  vendorTransportStore?.queue.find((item) => item.status === "running")?.id ??
370
431
  null;
371
432
  const activeIndex = Math.max(0, vendorTasks.findIndex((task) => task.taskId === activeTaskId));
372
- setTasksPanelSelectedIndex(activeIndex === -1 ? 0 : activeIndex);
373
- setTasksPanelMode("browse");
433
+ setTasksPanelSelectedIndex((current) => {
434
+ if (vendorTasks.length === 0)
435
+ return 0;
436
+ if (current >= 0 && current < vendorTasks.length)
437
+ return current;
438
+ return activeIndex === -1 ? 0 : activeIndex;
439
+ });
374
440
  }, [activePanel, busyLabel, refreshState, vendorTasks, vendorTransportStore]);
375
441
  useEffect(() => {
376
442
  if (activePanel !== "activity" || busyLabel || !refreshState)
377
443
  return;
378
- setActivityPanelSelectedIndex(0);
379
- setActivityPanelMode("browse");
444
+ setActivityPanelSelectedIndex((current) => {
445
+ if (vendorActivity.length === 0)
446
+ return 0;
447
+ if (current >= 0 && current < vendorActivity.length)
448
+ return current;
449
+ return 0;
450
+ });
380
451
  }, [activePanel, busyLabel, refreshState, vendorActivity]);
381
452
  async function runPanelAction(busyText, action, successMessage) {
382
453
  setBusyLabel(busyText);
@@ -434,6 +505,12 @@ function App() {
434
505
  createdAt: entry.createdAt ?? new Date().toISOString(),
435
506
  })),
436
507
  });
508
+ if ((data.results ?? []).length === 0 && localMatches.length === 0) {
509
+ await removeTask(task.id);
510
+ setMessage(`No local or shared context matched "${query}".`);
511
+ setRefreshTick((value) => value + 1);
512
+ return;
513
+ }
437
514
  await recordSessionEvent({
438
515
  kind: "query",
439
516
  title: `Queried Context for "${query}"`,
@@ -444,8 +521,7 @@ function App() {
444
521
  });
445
522
  await completeTask(task.id, "completed", "Query finished.");
446
523
  setMessage(`Query finished for "${query}".`);
447
- setActivePanel("tasks");
448
- setSelectedPanelIndex(panelIndexFor("tasks"));
524
+ focusPanel("tasks", "panel");
449
525
  setRefreshTick((value) => value + 1);
450
526
  }
451
527
  finally {
@@ -472,6 +548,9 @@ function App() {
472
548
  const entry = await addPendingMemory({
473
549
  content,
474
550
  tags: [],
551
+ keywords: [],
552
+ category: null,
553
+ accessLevel: "space",
475
554
  source: "cli",
476
555
  systemType: "system1_knowledge",
477
556
  });
@@ -485,46 +564,48 @@ function App() {
485
564
  });
486
565
  await completeTask(task.id, "completed", `Saved local draft ${entry.id}.`);
487
566
  setMessage(`Saved local Context note for ${config.companyName} / ${config.projectName}.`);
488
- setActivePanel("tasks");
489
- setSelectedPanelIndex(panelIndexFor("tasks"));
567
+ focusPanel("tasks", "panel");
490
568
  setRefreshTick((value) => value + 1);
491
569
  }
492
570
  finally {
493
571
  setBusyLabel(null);
494
572
  }
495
573
  }
574
+ async function startSetupServerCheck(nextServer) {
575
+ const normalizedServer = trimSlash(nextServer);
576
+ setServerUrl(normalizedServer);
577
+ setInputValue("");
578
+ setBusyLabel("Checking Umbrella server...");
579
+ try {
580
+ setAuthSnapshot(setUmbrellaAuthChecking(normalizedServer, setupEmail || null));
581
+ const serverCheck = await checkUmbrellaOnboardingServer(normalizedServer);
582
+ if (serverCheck.deploymentMode === "authenticated") {
583
+ setSetupStep("email");
584
+ setMessage("Umbrella is reachable. Enter the email for the account you want to use on this device.");
585
+ }
586
+ else {
587
+ setAuthSnapshot(setUmbrellaAuthAuthorized(normalizedServer, null, serverCheck.companies.length));
588
+ setCompanies(serverCheck.companies);
589
+ setSelectedCompanyIndex(0);
590
+ setSetupStep("company");
591
+ setMessage("Umbrella is reachable and trusted mode is active. Choose a company next.");
592
+ }
593
+ }
594
+ catch (error) {
595
+ const snapshot = setUmbrellaAuthFailure(normalizedServer, error, setupEmail || null);
596
+ setAuthSnapshot(snapshot);
597
+ setMessage(`Could not reach Umbrella: ${snapshot.message}${snapshot.hint ? ` ${snapshot.hint}` : ""}`);
598
+ }
599
+ finally {
600
+ setBusyLabel(null);
601
+ }
602
+ }
496
603
  async function submitSetupText() {
497
604
  const nextValue = inputValue.trim();
498
605
  if (!nextValue && setupStep !== "password")
499
606
  return;
500
607
  if (setupStep === "server") {
501
- const nextServer = trimSlash(nextValue || serverUrl);
502
- setServerUrl(nextServer);
503
- setInputValue("");
504
- setBusyLabel("Checking Umbrella server...");
505
- try {
506
- setAuthSnapshot(setUmbrellaAuthChecking(nextServer, setupEmail || null));
507
- const serverCheck = await checkUmbrellaOnboardingServer(nextServer);
508
- if (serverCheck.deploymentMode === "authenticated") {
509
- setSetupStep("email");
510
- setMessage("Umbrella is reachable. Enter the email for the account you want to use on this device.");
511
- }
512
- else {
513
- setAuthSnapshot(setUmbrellaAuthAuthorized(nextServer, null, serverCheck.companies.length));
514
- setCompanies(serverCheck.companies);
515
- setSelectedCompanyIndex(0);
516
- setSetupStep("company");
517
- setMessage("Umbrella is reachable and trusted mode is active. Choose a company next.");
518
- }
519
- }
520
- catch (error) {
521
- const snapshot = setUmbrellaAuthFailure(nextServer, error, setupEmail || null);
522
- setAuthSnapshot(snapshot);
523
- setMessage(`Could not reach Umbrella: ${snapshot.message}${snapshot.hint ? ` ${snapshot.hint}` : ""}`);
524
- }
525
- finally {
526
- setBusyLabel(null);
527
- }
608
+ await startSetupServerCheck(nextValue || serverUrl);
528
609
  return;
529
610
  }
530
611
  if (setupStep === "email") {
@@ -600,6 +681,17 @@ function App() {
600
681
  }
601
682
  }
602
683
  async function confirmSetupSelection() {
684
+ if (setupStep === "server-choice") {
685
+ const choice = SETUP_SERVER_CHOICES[selectedServerChoiceIndex]?.value ?? "live";
686
+ if (choice === "custom") {
687
+ setInputValue(serverUrl);
688
+ setSetupStep("server");
689
+ setMessage("Type the Umbrella server URL you want this device to use.");
690
+ return;
691
+ }
692
+ await startSetupServerCheck(choice === "local" ? LOCAL_UMBRELLA_SERVER_URL : DEFAULT_UMBRELLA_SERVER_URL);
693
+ return;
694
+ }
603
695
  if (setupStep === "company") {
604
696
  if (selectedCompanyIndex === companies.length) {
605
697
  setInputValue("");
@@ -649,8 +741,7 @@ function App() {
649
741
  setAuthSnapshot(setUmbrellaAuthAuthorized(serverUrl, setupEmail || null, companies.length));
650
742
  setSetupStep("done");
651
743
  setMessage(`Connected to ${setup.companyName} / ${setup.activeSpaceName}.`);
652
- setActivePanel("home");
653
- setSelectedPanelIndex(panelIndexFor("home"));
744
+ focusPanel("home", "sidebar");
654
745
  setRefreshTick((value) => value + 1);
655
746
  }
656
747
  catch (error) {
@@ -690,7 +781,7 @@ function App() {
690
781
  }
691
782
  if (activePanel === "setup" && setupStep !== "done") {
692
783
  if (key.return) {
693
- if (setupStep === "company" || setupStep === "space") {
784
+ if (setupStep === "server-choice" || setupStep === "company" || setupStep === "space") {
694
785
  void confirmSetupSelection();
695
786
  }
696
787
  else {
@@ -700,11 +791,18 @@ function App() {
700
791
  }
701
792
  if (key.escape) {
702
793
  if (configManager.config) {
703
- setActivePanel("home");
704
- setSelectedPanelIndex(panelIndexFor("home"));
794
+ focusPanel("home", "sidebar");
705
795
  }
706
796
  return;
707
797
  }
798
+ if (key.upArrow && setupStep === "server-choice") {
799
+ setSelectedServerChoiceIndex((value) => Math.max(0, value - 1));
800
+ return;
801
+ }
802
+ if (key.downArrow && setupStep === "server-choice") {
803
+ setSelectedServerChoiceIndex((value) => Math.min(SETUP_SERVER_CHOICES.length - 1, value + 1));
804
+ return;
805
+ }
708
806
  if (key.upArrow && setupStep === "company") {
709
807
  setSelectedCompanyIndex((value) => Math.max(0, value - 1));
710
808
  return;
@@ -844,33 +942,89 @@ function App() {
844
942
  exit();
845
943
  return;
846
944
  }
847
- const providerPanelOwnsArrows = activePanel === "providers";
848
- const modelPanelOwnsArrows = activePanel === "model";
849
- const spacePanelOwnsArrows = activePanel === "spaces";
850
- const hubPanelOwnsArrows = activePanel === "hub";
851
- const connectorsPanelOwnsArrows = activePanel === "connectors";
852
- const tasksPanelOwnsArrows = activePanel === "tasks";
853
- const activityPanelOwnsArrows = activePanel === "activity";
945
+ if (key.backspace && activePanel !== "setup") {
946
+ setNavigationFocus("sidebar");
947
+ if (activePanel === "tasks")
948
+ setTasksPanelMode("browse");
949
+ if (activePanel === "activity")
950
+ setActivityPanelMode("browse");
951
+ setMessage("Returned focus to the sidebar.");
952
+ return;
953
+ }
954
+ if (key.leftArrow) {
955
+ setNavigationFocus("sidebar");
956
+ if (activePanel === "tasks")
957
+ setTasksPanelMode("browse");
958
+ if (activePanel === "activity")
959
+ setActivityPanelMode("browse");
960
+ setMessage("Returned focus to the sidebar.");
961
+ return;
962
+ }
963
+ if (key.escape) {
964
+ if (navigationFocus === "panel") {
965
+ setNavigationFocus("sidebar");
966
+ if (activePanel === "tasks")
967
+ setTasksPanelMode("browse");
968
+ if (activePanel === "activity")
969
+ setActivityPanelMode("browse");
970
+ setMessage("Returned focus to the sidebar.");
971
+ return;
972
+ }
973
+ }
974
+ if (key.rightArrow && activePanel !== "setup") {
975
+ setNavigationFocus("panel");
976
+ return;
977
+ }
978
+ if (key.return && navigationFocus === "sidebar" && activePanel !== "setup") {
979
+ setNavigationFocus("panel");
980
+ setMessage(`Opened ${PANELS[selectedPanelIndex]?.title ?? "panel"}.`);
981
+ return;
982
+ }
983
+ const shortcutPanel = panelForShortcut(input);
984
+ if (shortcutPanel && activePanel !== "setup") {
985
+ focusPanel(shortcutPanel, "panel");
986
+ setMessage(`Jumped to ${PANELS[panelIndexFor(shortcutPanel)]?.title ?? shortcutPanel}.`);
987
+ return;
988
+ }
854
989
  if (key.upArrow || input === "k") {
855
- if (!providerPanelOwnsArrows && !modelPanelOwnsArrows && !spacePanelOwnsArrows && !hubPanelOwnsArrows && !connectorsPanelOwnsArrows && !tasksPanelOwnsArrows && !activityPanelOwnsArrows) {
990
+ if (navigationFocus === "sidebar") {
856
991
  const nextIndex = Math.max(0, selectedPanelIndex - 1);
857
- setSelectedPanelIndex(nextIndex);
858
- setActivePanel(PANELS[nextIndex]?.id ?? "home");
992
+ focusPanel(PANELS[nextIndex]?.id ?? "home", "sidebar");
859
993
  return;
860
994
  }
861
995
  }
862
996
  if (key.downArrow || input === "j") {
863
- if (!providerPanelOwnsArrows && !modelPanelOwnsArrows && !spacePanelOwnsArrows && !hubPanelOwnsArrows && !connectorsPanelOwnsArrows && !tasksPanelOwnsArrows && !activityPanelOwnsArrows) {
997
+ if (navigationFocus === "sidebar") {
864
998
  const nextIndex = Math.min(PANELS.length - 1, selectedPanelIndex + 1);
865
- setSelectedPanelIndex(nextIndex);
866
- setActivePanel(PANELS[nextIndex]?.id ?? "home");
999
+ focusPanel(PANELS[nextIndex]?.id ?? "home", "sidebar");
867
1000
  return;
868
1001
  }
869
1002
  }
870
- if (key.tab) {
871
- const nextIndex = (selectedPanelIndex + 1) % PANELS.length;
872
- setSelectedPanelIndex(nextIndex);
873
- setActivePanel(PANELS[nextIndex]?.id ?? "home");
1003
+ if (input === "[" && activePanel !== "setup") {
1004
+ const nextIndex = selectedPanelIndex === 0 ? PANELS.length - 1 : selectedPanelIndex - 1;
1005
+ focusPanel(PANELS[nextIndex]?.id ?? "home", navigationFocus);
1006
+ setMessage(`Moved to ${PANELS[nextIndex]?.title ?? "panel"}.`);
1007
+ return;
1008
+ }
1009
+ if (input === "]" && activePanel !== "setup") {
1010
+ const nextIndex = selectedPanelIndex === PANELS.length - 1 ? 0 : selectedPanelIndex + 1;
1011
+ focusPanel(PANELS[nextIndex]?.id ?? "home", navigationFocus);
1012
+ setMessage(`Moved to ${PANELS[nextIndex]?.title ?? "panel"}.`);
1013
+ return;
1014
+ }
1015
+ if (key.tab && activePanel !== "setup") {
1016
+ if (navigationFocus === "sidebar") {
1017
+ setNavigationFocus("panel");
1018
+ setMessage(`Opened ${PANELS[selectedPanelIndex]?.title ?? "panel"}.`);
1019
+ }
1020
+ else {
1021
+ setNavigationFocus("sidebar");
1022
+ if (activePanel === "tasks")
1023
+ setTasksPanelMode("browse");
1024
+ if (activePanel === "activity")
1025
+ setActivityPanelMode("browse");
1026
+ setMessage("Returned focus to the sidebar.");
1027
+ }
874
1028
  return;
875
1029
  }
876
1030
  if (input === "?") {
@@ -886,9 +1040,12 @@ function App() {
886
1040
  if (input === "s") {
887
1041
  setActivePanel("setup");
888
1042
  setSelectedPanelIndex(0);
1043
+ setNavigationFocus("panel");
889
1044
  return;
890
1045
  }
891
1046
  if (activePanel === "providers") {
1047
+ if (navigationFocus === "sidebar")
1048
+ return;
892
1049
  if (key.upArrow || input === "k") {
893
1050
  setProviderPanelSelectedIndex((value) => Math.max(0, value - 1));
894
1051
  return;
@@ -941,12 +1098,13 @@ function App() {
941
1098
  return;
942
1099
  }
943
1100
  if (input === "m") {
944
- setActivePanel("model");
945
- setSelectedPanelIndex(panelIndexFor("model"));
1101
+ focusPanel("model", "panel");
946
1102
  return;
947
1103
  }
948
1104
  }
949
1105
  if (activePanel === "model") {
1106
+ if (navigationFocus === "sidebar")
1107
+ return;
950
1108
  if (key.upArrow || input === "k") {
951
1109
  setModelPanelSelectedIndex((value) => Math.max(0, value - 1));
952
1110
  return;
@@ -979,6 +1137,8 @@ function App() {
979
1137
  }
980
1138
  }
981
1139
  if (activePanel === "spaces") {
1140
+ if (navigationFocus === "sidebar")
1141
+ return;
982
1142
  if (key.upArrow || input === "k") {
983
1143
  setSpacePanelSelectedIndex((value) => Math.max(0, value - 1));
984
1144
  return;
@@ -1018,6 +1178,19 @@ function App() {
1018
1178
  }
1019
1179
  }
1020
1180
  if (activePanel === "tasks") {
1181
+ if (key.escape) {
1182
+ if (tasksPanelMode === "inspect") {
1183
+ setTasksPanelMode("browse");
1184
+ setMessage("Returned to the task list.");
1185
+ }
1186
+ else {
1187
+ setNavigationFocus("sidebar");
1188
+ setMessage("Returned focus to the sidebar.");
1189
+ }
1190
+ return;
1191
+ }
1192
+ if (navigationFocus === "sidebar")
1193
+ return;
1021
1194
  if (key.upArrow || input === "k") {
1022
1195
  if (tasksPanelMode === "inspect")
1023
1196
  return;
@@ -1030,28 +1203,52 @@ function App() {
1030
1203
  setTasksPanelSelectedIndex((value) => Math.min(Math.max(0, vendorTasks.length - 1), value + 1));
1031
1204
  return;
1032
1205
  }
1033
- if (key.return || input === "i") {
1206
+ if (input === "i") {
1034
1207
  const task = vendorTasks[tasksPanelSelectedIndex];
1035
1208
  if (!task)
1036
1209
  return;
1037
1210
  setTasksPanelMode("inspect");
1211
+ setMessage(`Inspecting task ${task.taskId}.`);
1038
1212
  return;
1039
1213
  }
1040
- if (input === "o") {
1214
+ if (key.return) {
1041
1215
  const task = vendorTasks[tasksPanelSelectedIndex];
1042
1216
  if (!task)
1043
1217
  return;
1218
+ if (tasksPanelMode === "browse") {
1219
+ setTasksPanelMode("inspect");
1220
+ setMessage(`Inspecting task ${task.taskId}.`);
1221
+ return;
1222
+ }
1044
1223
  const nextPanel = panelForTaskKind(task.type);
1045
- setActivePanel(nextPanel);
1046
- setSelectedPanelIndex(panelIndexFor(nextPanel));
1224
+ focusPanel(nextPanel, "panel");
1225
+ setMessage(`Opened ${PANELS[panelIndexFor(nextPanel)]?.title ?? nextPanel} from task ${task.taskId}.`);
1047
1226
  return;
1048
1227
  }
1049
- if (key.escape && tasksPanelMode === "inspect") {
1050
- setTasksPanelMode("browse");
1228
+ if (input === "o") {
1229
+ const task = vendorTasks[tasksPanelSelectedIndex];
1230
+ if (!task)
1231
+ return;
1232
+ const nextPanel = panelForTaskKind(task.type);
1233
+ focusPanel(nextPanel, "panel");
1234
+ setMessage(`Opened ${PANELS[panelIndexFor(nextPanel)]?.title ?? nextPanel} from task ${task.taskId}.`);
1051
1235
  return;
1052
1236
  }
1053
1237
  }
1054
1238
  if (activePanel === "activity") {
1239
+ if (key.escape) {
1240
+ if (activityPanelMode === "inspect") {
1241
+ setActivityPanelMode("browse");
1242
+ setMessage("Returned to the activity list.");
1243
+ }
1244
+ else {
1245
+ setNavigationFocus("sidebar");
1246
+ setMessage("Returned focus to the sidebar.");
1247
+ }
1248
+ return;
1249
+ }
1250
+ if (navigationFocus === "sidebar")
1251
+ return;
1055
1252
  if (key.upArrow || input === "k") {
1056
1253
  if (activityPanelMode === "inspect")
1057
1254
  return;
@@ -1064,32 +1261,48 @@ function App() {
1064
1261
  setActivityPanelSelectedIndex((value) => Math.min(Math.max(0, vendorActivity.length - 1), value + 1));
1065
1262
  return;
1066
1263
  }
1067
- if (key.return || input === "o") {
1264
+ if (key.return) {
1068
1265
  const event = vendorActivity[activityPanelSelectedIndex];
1069
1266
  if (!event)
1070
1267
  return;
1071
- if (input !== "o" && key.return && activityPanelMode === "browse") {
1268
+ if (activityPanelMode === "browse") {
1072
1269
  setActivityPanelMode("inspect");
1270
+ setMessage(`Inspecting activity item: ${event.title}.`);
1073
1271
  return;
1074
1272
  }
1075
1273
  const nextPanel = panelForActivityItem(event);
1076
1274
  if (!nextPanel)
1077
1275
  return;
1078
- setActivePanel(nextPanel);
1079
- setSelectedPanelIndex(panelIndexFor(nextPanel));
1080
- return;
1081
- }
1082
- if (key.escape && activityPanelMode === "inspect") {
1083
- setActivityPanelMode("browse");
1276
+ focusPanel(nextPanel, "panel");
1277
+ setMessage(`Opened ${PANELS[panelIndexFor(nextPanel)]?.title ?? nextPanel} from activity.`);
1084
1278
  return;
1085
1279
  }
1086
1280
  if (input === "i") {
1281
+ const event = vendorActivity[activityPanelSelectedIndex];
1087
1282
  setActivityPanelMode("inspect");
1283
+ if (event) {
1284
+ setMessage(`Inspecting activity item: ${event.title}.`);
1285
+ }
1286
+ return;
1287
+ }
1288
+ if (input === "o") {
1289
+ const event = vendorActivity[activityPanelSelectedIndex];
1290
+ if (!event)
1291
+ return;
1292
+ const nextPanel = panelForActivityItem(event);
1293
+ if (!nextPanel) {
1294
+ setMessage("That activity item does not point to a related panel.");
1295
+ return;
1296
+ }
1297
+ focusPanel(nextPanel, "panel");
1298
+ setMessage(`Opened ${PANELS[panelIndexFor(nextPanel)]?.title ?? nextPanel} from activity.`);
1088
1299
  return;
1089
1300
  }
1090
1301
  }
1091
1302
  if (activePanel === "hub") {
1092
- const registryEntries = listHubRegistryEntries();
1303
+ if (navigationFocus === "sidebar")
1304
+ return;
1305
+ const registryEntries = hubRegistryEntries;
1093
1306
  if (key.upArrow || input === "k") {
1094
1307
  setHubPanelSelectedIndex((value) => Math.max(0, value - 1));
1095
1308
  return;
@@ -1129,6 +1342,8 @@ function App() {
1129
1342
  }
1130
1343
  }
1131
1344
  if (activePanel === "connectors") {
1345
+ if (navigationFocus === "sidebar")
1346
+ return;
1132
1347
  const registryEntries = listConnectorTemplates();
1133
1348
  if (key.upArrow || input === "k") {
1134
1349
  setConnectorsPanelSelectedIndex((value) => Math.max(0, value - 1));
@@ -1205,43 +1420,61 @@ function App() {
1205
1420
  ? "Setup mode: Enter confirms, Up/Down move through companies or spaces, Esc leaves setup"
1206
1421
  : activePanel === "providers"
1207
1422
  ? providerPanelMode === "browse"
1208
- ? "Providers: Up/Down choose | Enter or w switch | c connect | d disconnect | t test | i inspect recent | g guided setup | m model | ? query | + curate | q quit"
1423
+ ? navigationFocus === "sidebar"
1424
+ ? "Sidebar focus: Up/Down choose panel | Enter, Right, or Tab opens it | [ ] cycle panels | 1-9 or h/t/a/v/m/w/b/c/u jumps panels | ? query | + curate | p push | l pull | s setup | q quit"
1425
+ : "Providers: Up/Down choose | Enter or w switch | c connect | d disconnect | t test | i inspect recent | g guided setup | m model | Esc or Left returns to sidebar | ? query | + curate | q quit"
1209
1426
  : providerPanelMode === "connect-kind"
1210
1427
  ? "Providers connect: Up/Down choose provider type | Enter confirm | Esc back"
1211
1428
  : "Providers connect: type value | Enter continue | Esc back"
1212
1429
  : activePanel === "model"
1213
1430
  ? modelPanelMode === "browse"
1214
- ? "Model: Up/Down choose | Enter or w switch | i inspect recent | t readiness | ? query | + curate | q quit"
1431
+ ? navigationFocus === "sidebar"
1432
+ ? "Sidebar focus: Up/Down choose panel | Enter, Right, or Tab opens it | [ ] cycle panels | 1-9 or h/t/a/v/m/w/b/c/u jumps panels | ? query | + curate | p push | l pull | s setup | q quit"
1433
+ : "Model: Up/Down choose | Enter or w switch | i inspect recent | t readiness | Esc or Left returns to sidebar | ? query | + curate | q quit"
1215
1434
  : "Model runtime panel"
1216
1435
  : activePanel === "spaces"
1217
- ? "Spaces: i list | w switch | n create | ? query | + curate | q quit"
1436
+ ? navigationFocus === "sidebar"
1437
+ ? "Sidebar focus: Up/Down choose panel | Enter, Right, or Tab opens it | [ ] cycle panels | 1-9 or h/t/a/v/m/w/b/c/u jumps panels | ? query | + curate | p push | l pull | s setup | q quit"
1438
+ : "Spaces: Enter or w switch | n create | i refresh | Esc or Left returns to sidebar | ? query | + curate | q quit"
1218
1439
  : activePanel === "hub"
1219
1440
  ? hubPanelMode === "browse"
1220
- ? "Hub: Up/Down choose | Enter or n install | i inspect | b guided browse | l refresh | ? query | + curate | q quit"
1441
+ ? navigationFocus === "sidebar"
1442
+ ? "Sidebar focus: Up/Down choose panel | Enter, Right, or Tab opens it | [ ] cycle panels | 1-9 or h/t/a/v/m/w/b/c/u jumps panels | ? query | + curate | p push | l pull | s setup | q quit"
1443
+ : "Hub: Up/Down choose | Enter or n install | i inspect | b guided browse | l refresh | Esc or Left returns to sidebar | ? query | + curate | q quit"
1221
1444
  : "Hub live panel"
1222
1445
  : activePanel === "connectors"
1223
1446
  ? connectorsPanelMode === "browse"
1224
- ? "Connectors: Up/Down choose | Enter or i install | r run | o outputs | v inspect | ? query | + curate | q quit"
1447
+ ? navigationFocus === "sidebar"
1448
+ ? "Sidebar focus: Up/Down choose panel | Enter, Right, or Tab opens it | [ ] cycle panels | 1-9 or h/t/a/v/m/w/b/c/u jumps panels | ? query | + curate | p push | l pull | s setup | q quit"
1449
+ : "Connectors: Up/Down choose | Enter or i install | r run | o outputs | v inspect | Esc or Left returns to sidebar | ? query | + curate | q quit"
1225
1450
  : "Connectors live panel"
1226
1451
  : activePanel === "tasks"
1227
1452
  ? tasksPanelMode === "browse"
1228
- ? "Tasks: Up/Down choose | Enter or i inspect | o open related panel | r refresh | ? query | + curate | q quit"
1229
- : "Tasks inspect: Esc back | o open related panel | r refresh | ? query | + curate | q quit"
1453
+ ? navigationFocus === "sidebar"
1454
+ ? "Sidebar focus: Up/Down choose panel | Enter, Right, or Tab opens tasks | [ ] cycle panels | 1-9 or h/t/a/v/m/w/b/c/u jumps panels | ? query | + curate | p push | l pull | s setup | q quit"
1455
+ : "Tasks: Up/Down choose | Enter or i inspect | o open related panel | Esc or Left returns to sidebar | r refresh | ? query | + curate | q quit"
1456
+ : "Tasks inspect: Esc goes back to the task list | Enter or o opens related panel | Left returns to sidebar | r refresh | ? query | + curate | q quit"
1230
1457
  : activePanel === "activity"
1231
1458
  ? activityPanelMode === "browse"
1232
- ? "Activity: Up/Down choose | Enter or i inspect | o open related panel | r refresh | ? query | + curate | q quit"
1233
- : "Activity inspect: Esc back | Enter or o open related panel | r refresh | ? query | + curate | q quit"
1234
- : "Up/Down or j/k move panels | Tab cycles | ? query | + curate | p push | l pull | r refresh | s setup | q quit";
1459
+ ? navigationFocus === "sidebar"
1460
+ ? "Sidebar focus: Up/Down choose panel | Enter, Right, or Tab opens activity | [ ] cycle panels | 1-9 or h/t/a/v/m/w/b/c/u jumps panels | ? query | + curate | p push | l pull | s setup | q quit"
1461
+ : "Activity: Up/Down choose | Enter or i inspect | o open related panel | Esc or Left returns to sidebar | r refresh | ? query | + curate | q quit"
1462
+ : "Activity inspect: Esc goes back to the event list | Left returns to sidebar | Enter or o opens related panel | r refresh | ? query | + curate | q quit"
1463
+ : "Sidebar focus: Up/Down or j/k move panels | Enter, Right, or Tab opens a panel | [ ] cycle panels | 1-9 or h/t/a/v/m/w/b/c/u jumps panels | ? query | + curate | p push | l pull | r refresh | s setup | q quit";
1235
1464
  const config = configManager.config;
1236
1465
  let mainContent = null;
1237
1466
  if (activePanel === "setup") {
1238
- mainContent = (_jsx(SetupView, { authSnapshot: authSnapshot, busyLabel: busyLabel, companies: companies, config: config, inputValue: inputValue, message: message, onInputChange: setInputValue, selectedCompanyIndex: selectedCompanyIndex, selectedSpaceIndex: selectedSpaceIndex, serverUrl: serverUrl, spaces: spaces, step: setupStep }));
1467
+ mainContent = (_jsx(SetupView, { authSnapshot: authSnapshot, busyLabel: busyLabel, companies: companies, config: config, inputValue: inputValue, message: message, onInputChange: setInputValue, selectedCompanyIndex: selectedCompanyIndex, selectedServerChoiceIndex: selectedServerChoiceIndex, selectedSpaceIndex: selectedSpaceIndex, serverUrl: serverUrl, spaces: spaces, step: setupStep }));
1239
1468
  }
1240
1469
  else if (!config || !refreshState) {
1241
- mainContent = (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "This device is not linked yet." }), _jsx(Text, { color: "gray", children: "Move to Setup and connect the repo to a company and space first." })] }));
1470
+ mainContent = (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "This terminal app can open before live sign-in is finished." }), _jsx(Text, { color: "gray", children: "Right now this repo is not fully linked to a company and space yet." }), _jsx(Text, { color: "gray", children: "Open Setup and finish the device link before you expect shared server context to work." })] }));
1242
1471
  }
1243
1472
  else if (activePanel === "home") {
1244
- mainContent = (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [config.companyName, " / ", config.projectName] }), _jsx(Text, { color: "gray", children: "This is the repo-level Context home for the current company space." }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Section, { title: "Sync", children: [_jsx(StatusLine, { label: "Pending drafts", value: String(refreshState.pending.length) }), _jsx(StatusLine, { label: "Pulled context", value: String(refreshState.pulled.length) }), _jsx(StatusLine, { label: "Known fixes", value: String(refreshState.fixes.length) }), _jsx(StatusLine, { label: "Last push", value: formatAgo(refreshState.repoContext.state?.lastPushAt) }), _jsx(StatusLine, { label: "Last pull", value: formatAgo(refreshState.repoContext.state?.lastPullAt) })] }), _jsxs(Section, { title: "Runtime", children: [_jsx(StatusLine, { label: "Provider", value: readiness.activeProvider?.name ?? "Not connected" }), _jsx(StatusLine, { label: "Model", value: readiness.activeModel ?? "Not selected" }), _jsx(StatusLine, { label: "Transport", value: vendorTransportStore?.status ?? refreshState.transport.status }), _jsx(StatusLine, { label: "Repo .um", value: refreshState.repoContext.umDir })] }), _jsxs(Section, { title: "Context Tree", children: [_jsx(StatusLine, { label: "Summary", value: vendorTreeStore?.summaryHandle ?? refreshState.treeState.summaryHandle }), _jsx(StatusLine, { label: "Nodes", value: String(vendorTreeStore?.nodes.length ?? refreshState.treeState.nodes.length) }), _jsx(StatusLine, { label: "Runtime provider", value: vendorTreeStore?.runtime.provider ?? "No provider" }), _jsx(StatusLine, { label: "Runtime model", value: vendorTreeStore?.runtime.model ?? "No model" })] }), queryResult ? (_jsxs(Section, { title: `Latest Query: ${queryResult.query}`, children: [_jsxs(Text, { children: ["Local matches: ", String(queryResult.localMatches.length)] }), _jsxs(Text, { children: ["Shared matches: ", String(queryResult.remoteMatches.length)] })] })) : null] })] }));
1473
+ mainContent = (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [config.companyName, " / ", config.projectName] }), _jsx(Text, { color: "gray", children: "This is the repo-level Context home for the current company space." }), _jsx(Text, { color: "gray", children: "The TUI is local-first: it reads this repo's .um folder even before the live server is queried." }), connectionPresentation ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: connectionPresentation.connectionMode === "verified_live_connection" ? "green" : "yellow", children: connectionPresentation.connectionMode === "verified_live_connection"
1474
+ ? "Live connection verified"
1475
+ : "Saved local setup only" }), _jsx(Text, { color: "gray", children: connectionPresentation.connectionSummary }), connectionPresentation.warnings.map((warning) => (_jsxs(Text, { color: "yellow", children: ["- ", warning] }, warning)))] })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Section, { title: "Connection", children: [_jsx(StatusLine, { label: "Umbrella server", value: config.umbrellaUrl ?? config.serverUrl }), _jsx(StatusLine, { label: "Auth state", value: formatUmbrellaAuthStatus(connectionPresentation?.authSnapshot.status ?? authSnapshot.status) }), _jsx(StatusLine, { label: "Shared API key", value: config.apiKey ? "Saved" : "Missing" }), _jsx(StatusLine, { label: "Connection mode", value: connectionPresentation?.connectionMode === "verified_live_connection"
1476
+ ? "Verified live connection"
1477
+ : "Saved local config only" })] }), _jsxs(Section, { title: "Sync", children: [_jsx(StatusLine, { label: "Pending drafts", value: String(refreshState.pending.length) }), _jsx(StatusLine, { label: "Pulled context", value: String(refreshState.pulled.length) }), _jsx(StatusLine, { label: "Known fixes", value: String(refreshState.fixes.length) }), _jsx(StatusLine, { label: "Last push", value: formatAgo(refreshState.repoContext.state?.lastPushAt) }), _jsx(StatusLine, { label: "Last pull", value: formatAgo(refreshState.repoContext.state?.lastPullAt) })] }), _jsxs(Section, { title: "Runtime", children: [_jsx(StatusLine, { label: "Provider", value: readiness.activeProvider?.name ?? "Not connected" }), _jsx(StatusLine, { label: "Model", value: readiness.activeModel ?? "Not selected" }), _jsx(StatusLine, { label: "Transport", value: vendorTransportStore?.status ?? refreshState.transport.status }), _jsx(StatusLine, { label: "Repo .um", value: refreshState.repoContext.umDir })] }), _jsxs(Section, { title: "Context Tree", children: [_jsx(StatusLine, { label: "Summary", value: vendorTreeStore?.summaryHandle ?? refreshState.treeState.summaryHandle }), _jsx(StatusLine, { label: "Nodes", value: String(vendorTreeStore?.nodes.length ?? refreshState.treeState.nodes.length) }), _jsx(StatusLine, { label: "Runtime provider", value: vendorTreeStore?.runtime.provider ?? "No provider" }), _jsx(StatusLine, { label: "Runtime model", value: vendorTreeStore?.runtime.model ?? "No model" })] }), queryResult ? (_jsxs(Section, { title: `Latest Query: ${queryResult.query}`, children: [_jsxs(Text, { children: ["Local matches: ", String(queryResult.localMatches.length)] }), _jsxs(Text, { children: ["Shared matches: ", String(queryResult.remoteMatches.length)] })] })) : null, connectionPresentation?.connectionMode === "saved_local_config" ? (_jsx(Section, { title: "Next Step", children: _jsx(Text, { color: "yellow", children: "Run setup again before you trust shared server actions for this repo." }) })) : null] })] }));
1245
1478
  }
1246
1479
  else if (activePanel === "tasks") {
1247
1480
  mainContent = (_jsx(TasksPanelView, { mode: tasksPanelMode, selectedIndex: tasksPanelSelectedIndex, tasks: vendorTasks, transportStatus: vendorTransportStore?.status ?? vendorTransportSummary.transportStatus, queueLength: vendorTransportStore?.queue.length ?? vendorTransportSummary.queueDepth, activeTaskId: vendorTransportStore?.queue[0]?.id ??
@@ -1266,8 +1499,7 @@ function App() {
1266
1499
  mainContent = (_jsx(SpacePanelView, { companyName: vendorContextSummary.companyName ?? config.companyName, currentSpaceName: vendorContextSummary.spaceName ?? config.projectName, draftName: spacePanelDraftName, mode: spacePanelMode, onDraftChange: setSpacePanelDraftName, selectedIndex: spacePanelSelectedIndex, spaces: vendorSpaces }));
1267
1500
  }
1268
1501
  else if (activePanel === "hub") {
1269
- const registryEntries = listHubRegistryEntries();
1270
- mainContent = (_jsx(HubPanelView, { installedSlugs: vendorHubStore?.installedSlugs ?? [], recentSlug: vendorHubStore?.recentSlug ?? null, registryEntries: registryEntries, selectedIndex: hubPanelSelectedIndex }));
1502
+ mainContent = (_jsx(HubPanelView, { installedSlugs: vendorHubStore?.installedSlugs ?? [], recentSlug: vendorHubStore?.recentSlug ?? null, registryEntries: hubRegistryEntries, selectedIndex: hubPanelSelectedIndex }));
1271
1503
  }
1272
1504
  else if (activePanel === "connectors") {
1273
1505
  const registryEntries = listConnectorTemplates();
@@ -1276,7 +1508,7 @@ function App() {
1276
1508
  else {
1277
1509
  mainContent = (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Session Memory" }), _jsx(Text, { color: "gray", children: "This is the live terminal memory saved inside the repo, plus the internal runtime stores hydrated from the same Context engine." }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(StatusLine, { label: "Session ID", value: refreshState.session?.id ?? "None" }), _jsx(StatusLine, { label: "Current panel", value: refreshState.session?.currentPanel ?? "None" }), _jsx(StatusLine, { label: "Current focus", value: refreshState.session?.currentFocus ?? "None" }), _jsx(StatusLine, { label: "Commands", value: String(refreshState.session?.commandHistory.length ?? 0) }), _jsx(StatusLine, { label: "Events", value: String(refreshState.session?.events.length ?? 0) }), _jsx(StatusLine, { label: "Transport", value: vendorTransportStore?.status ?? refreshState.transport.status }), _jsx(StatusLine, { label: "Tree summary", value: vendorTreeStore?.summaryHandle ?? refreshState.treeState.summaryHandle })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Section, { title: "Transport and Tasks", children: [_jsx(StatusLine, { label: "Queue depth", value: String(vendorTransportStore?.queue.length ?? 0) }), _jsx(StatusLine, { label: "Recent tasks", value: String(vendorTasks.length) }), _jsx(StatusLine, { label: "Active task", value: vendorTransportSummary.activeTaskId ?? "None" }), _jsx(StatusLine, { label: "Started tasks", value: String(vendorTransportSummary.startedTasks) }), vendorRecentTransportEvents.length ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, color: "yellow", children: "Recent transport events" }), vendorRecentTransportEvents.map((event) => (_jsxs(Text, { color: "gray", children: [new Date(event.at).toLocaleString(), " | ", event.kind, " | ", event.title] }, `${event.kind}-${event.at}-${event.taskId ?? "no-task"}`)))] })) : (_jsx(Text, { color: "gray", children: "No transport events yet." }))] }), _jsxs(Section, { title: "Context Runtime Tree", children: [_jsx(StatusLine, { label: "Nodes", value: String(vendorTreeStore?.nodes.length ?? 0) }), _jsx(StatusLine, { label: "Pending drafts", value: String(vendorTreeStore?.stats.pendingDrafts ?? 0) }), _jsx(StatusLine, { label: "Pulled context", value: String(vendorTreeStore?.stats.pulledContext ?? 0) }), _jsx(StatusLine, { label: "Known fixes", value: String(vendorTreeStore?.stats.pulledFixes ?? 0) }), _jsx(StatusLine, { label: "Runtime transport", value: vendorTreeStore?.runtime.transportStatus ?? "idle" }), vendorRootTreeNodes.length ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, color: "yellow", children: "Top runtime nodes" }), vendorRootTreeNodes.map((node) => (_jsx(Text, { color: "gray", children: `${" ".repeat(node.depth)}- ${node.label}${node.detail ? ` | ${node.detail}` : ""}` }, node.id)))] })) : (_jsx(Text, { color: "gray", children: "The context tree has not been built yet." }))] }), _jsxs(Section, { title: "Session Continuity", children: [_jsx(StatusLine, { label: "Panel history", value: refreshState.session?.panelHistory.join(" -> ") || "None" }), _jsx(StatusLine, { label: "Recent providers", value: refreshState.session?.recentProviderIds.join(", ") || "None" }), _jsx(StatusLine, { label: "Recent models", value: refreshState.session?.recentModels.join(", ") || "None" }), _jsx(StatusLine, { label: "Recent hub items", value: refreshState.session?.recentHubSlugs.join(", ") || "None" }), _jsx(StatusLine, { label: "Recent connectors", value: refreshState.session?.recentConnectorSources.join(", ") || "None" })] }), _jsxs(Section, { title: "Runtime Bridge", children: [_jsx(StatusLine, { label: "Hydrated", value: vendorTransportSummary.isHydrated ? "Yes" : "No" }), _jsx(StatusLine, { label: "Generated", value: formatAgo(vendorTransportSummary.generatedAt) }), _jsx(StatusLine, { label: "Bridge queue", value: String(vendorTransportSummary.queueDepth) }), _jsx(StatusLine, { label: "Bridge tasks", value: String(vendorTransportSummary.recentTaskCount) }), _jsx(StatusLine, { label: "Bridge company", value: vendorContextSummary.companyName ?? "Unlinked company" }), _jsx(StatusLine, { label: "Bridge space", value: vendorContextSummary.spaceName ?? "Unlinked space" }), _jsx(StatusLine, { label: "Bridge provider", value: vendorContextSummary.providerName ?? "No provider" }), _jsx(StatusLine, { label: "Bridge model", value: vendorContextSummary.modelName ?? "No model" }), _jsx(StatusLine, { label: "Tree nodes", value: String(vendorContextSummary.treeNodes) }), _jsx(StatusLine, { label: "Runtime status", value: vendorContextSummary.runtimeStatus })] })] })] }));
1278
1510
  }
1279
- return (_jsxs(PanelShell, { activePanel: activePanel, footer: footer, message: busyLabel ? `${busyLabel}` : message, selectedPanelIndex: selectedPanelIndex, children: [mainContent, inputMode ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: inputMode === "query" ? "Ask Context" : "Save a local Context note" }), _jsx(TextInput, { value: inputValue, onChange: setInputValue })] })) : null] }));
1511
+ return (_jsxs(PanelShell, { activePanel: activePanel, footer: footer, message: busyLabel ? `${busyLabel}` : message, navigationFocus: navigationFocus, selectedPanelIndex: selectedPanelIndex, children: [mainContent, inputMode ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: inputMode === "query" ? "Ask Context" : "Save a local Context note" }), _jsx(TextInput, { value: inputValue, onChange: setInputValue })] })) : null] }));
1280
1512
  }
1281
1513
  export async function runTui() {
1282
1514
  if (!process.stdin.isTTY || !process.stdout.isTTY) {