ultracontext 1.4.13 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/cli/entry.mjs +9 -3
  2. package/dist/cli/entry.mjs.map +1 -1
  3. package/dist/cli/onboarding.mjs +268 -111
  4. package/dist/cli/onboarding.mjs.map +1 -1
  5. package/dist/cli/sdk-sync.mjs +199 -939
  6. package/dist/cli/sdk-sync.mjs.map +1 -1
  7. package/dist/cli/switch.mjs +168 -0
  8. package/dist/cli/switch.mjs.map +1 -0
  9. package/dist/{ctl-CXfNEPN8.mjs → ctl-DTQZxn3N.mjs} +2 -2
  10. package/dist/{ctl-CXfNEPN8.mjs.map → ctl-DTQZxn3N.mjs.map} +1 -1
  11. package/dist/hero-art-C03HmDXN.mjs +46 -0
  12. package/dist/hero-art-C03HmDXN.mjs.map +1 -0
  13. package/dist/index.d.mts +21 -1
  14. package/dist/index.d.mts.map +1 -1
  15. package/dist/index.mjs +25 -3
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/{launcher-BMMjzr5k.mjs → launcher-ZylswrpR.mjs} +3 -3
  18. package/dist/{launcher-BMMjzr5k.mjs.map → launcher-ZylswrpR.mjs.map} +1 -1
  19. package/dist/{lock-5aJnda81.mjs → lock-BhZX2aF3.mjs} +2 -2
  20. package/dist/{lock-5aJnda81.mjs.map → lock-BhZX2aF3.mjs.map} +1 -1
  21. package/dist/onboarding-preferences-Alhblobi.mjs +76 -0
  22. package/dist/onboarding-preferences-Alhblobi.mjs.map +1 -0
  23. package/dist/src-Bovo1ukU.mjs +1200 -0
  24. package/dist/src-Bovo1ukU.mjs.map +1 -0
  25. package/dist/{tui-DZ1SDOH2.mjs → tui-DLEjew3K.mjs} +334 -115
  26. package/dist/tui-DLEjew3K.mjs.map +1 -0
  27. package/dist/utils-BTfShW0g.mjs +36 -0
  28. package/dist/utils-BTfShW0g.mjs.map +1 -0
  29. package/dist/{utils-CmuIYHtm.mjs → utils-D9CKnbke.mjs} +26 -34
  30. package/dist/utils-D9CKnbke.mjs.map +1 -0
  31. package/lib/register-skills.mjs +96 -0
  32. package/package.json +8 -3
  33. package/plugin/.claude-plugin/plugin.json +6 -0
  34. package/plugin/README.md +112 -0
  35. package/plugin/marketplace.json +17 -0
  36. package/plugin/skills/switch/SKILL.md +27 -0
  37. package/postinstall.mjs +35 -2
  38. package/dist/Spinner-CwBjkXHv.mjs +0 -153
  39. package/dist/Spinner-CwBjkXHv.mjs.map +0 -1
  40. package/dist/tui-DZ1SDOH2.mjs.map +0 -1
  41. package/dist/utils-CmuIYHtm.mjs.map +0 -1
@@ -1,19 +1,42 @@
1
- import { a as UC_CLAUDE_ORANGE, c as heroArtForWidth, i as UC_BRAND_BLUE, n as MENU_TABS, o as UC_CODEX_BLUE, r as UC_BLUE_LIGHT, s as UC_OPENCLAW_RED, t as Spinner } from "./Spinner-CwBjkXHv.mjs";
2
- import { i as toInt, s as expandHome, t as boolFromEnv } from "./utils-CmuIYHtm.mjs";
1
+ import { a as writeClaudeSession, i as hasLocalClaudeSession, n as hasLocalCodexSession, r as writeCodexSession } from "./src-Bovo1ukU.mjs";
2
+ import { t as heroArtForWidth } from "./hero-art-C03HmDXN.mjs";
3
3
  import { n as normalizeBootstrapMode } from "./protocol-BI9ficcl.mjs";
4
- import { i as writeClaudeSession, n as writeCodexSession, r as hasLocalClaudeSession, t as hasLocalCodexSession } from "./cli/sdk-sync.mjs";
4
+ import { i as expandHome } from "./utils-D9CKnbke.mjs";
5
+ import { i as toInt, t as boolFromEnv } from "./utils-BTfShW0g.mjs";
5
6
  import process$1 from "node:process";
6
7
  import { fileURLToPath } from "node:url";
7
8
  import path from "node:path";
8
9
  import fs from "node:fs";
9
10
  import os from "node:os";
10
11
  import { spawnSync } from "node:child_process";
11
- import React from "react";
12
+ import React, { useEffect, useState } from "react";
12
13
  import { Box, Text, render, useInput, useStdout } from "ink";
13
14
  import { TitledBox } from "@mishieck/ink-titled-box";
14
15
  import fs$1 from "node:fs/promises";
15
16
  import { UltraContext } from "ultracontext";
16
17
 
18
+ //#region ../sync/src/ui/constants.mjs
19
+ const UC_BRAND_BLUE = "#2f6fb3";
20
+ const UC_BLUE_LIGHT = "#7ec3ff";
21
+ const UC_CLAUDE_ORANGE = "#f4a261";
22
+ const UC_CODEX_BLUE = "#5fb2ff";
23
+ const UC_OPENCLAW_RED = "#e76f51";
24
+ const MENU_TABS = [
25
+ {
26
+ id: "logs",
27
+ label: "Live View"
28
+ },
29
+ {
30
+ id: "contexts",
31
+ label: "Contexts"
32
+ },
33
+ {
34
+ id: "configs",
35
+ label: "Configs"
36
+ }
37
+ ];
38
+
39
+ //#endregion
17
40
  //#region ../sync/src/ui/layout.mjs
18
41
  function computeTuiLayout(stdoutColumns, stdoutRows) {
19
42
  const safeCols = Math.max(Math.floor(Number(stdoutColumns) || 0), 1);
@@ -210,6 +233,10 @@ function handleContextsViewInput({ input, key, actions, snapshot }) {
210
233
  actions.moveResume(1);
211
234
  return true;
212
235
  }
236
+ if (input === "f") {
237
+ actions.cycleSourceFilter();
238
+ return true;
239
+ }
213
240
  if (input === "r") {
214
241
  actions.refreshResume();
215
242
  return true;
@@ -333,14 +360,11 @@ function createInputHandler({ snapshot, actions, focusMode, menuIndex, selectedT
333
360
  //#endregion
334
361
  //#region ../sync/src/ui/format.mjs
335
362
  function compact(value, max = 80) {
336
- const raw = String(value ?? "");
363
+ const raw = String(value ?? "").replace(/[\r\n\t\v\f\x00-\x1f]+/g, " ");
337
364
  if (raw.length <= max) return raw;
338
365
  if (max <= 3) return raw.slice(0, max);
339
366
  return `${raw.slice(0, max - 3)}...`;
340
367
  }
341
- function formatTime(value = Date.now()) {
342
- return new Date(value).toISOString().slice(11, 19);
343
- }
344
368
  function formatContextDate(value) {
345
369
  if (!value) return "-";
346
370
  const d = new Date(value);
@@ -413,7 +437,7 @@ function footerHelpText({ updatePromptActive, bootstrapActive, resumeTargetPicke
413
437
  if (resumeTargetPickerActive) return "Resume target: choose Claude Code or Codex (↑/↓, 1/2, Enter), Esc/← cancel.";
414
438
  if (focusMode !== "view") return "Controls: ↑/↓ navigate, Enter focus/open, ← back, q/Ctrl+C quit.";
415
439
  if (selectedTab === "contexts" && detailViewActive) return "Detail: ↑/↓ messages, j/k lines, r refresh, Esc/← back.";
416
- if (selectedTab === "contexts") return "Contexts: ↑/↓ select, Enter open, r refresh, ← back, q/Ctrl+C quit.";
440
+ if (selectedTab === "contexts") return "Contexts: ↑/↓ select, Enter open, f filter, r refresh, ← back, q/Ctrl+C quit.";
417
441
  if (selectedTab === "configs") return "Controls: ↑/↓ select config, Enter/→ apply, ← back, q/Ctrl+C quit.";
418
442
  return "Controls: ↑/↓ navigate, Enter focus/open, ← back, q/Ctrl+C quit.";
419
443
  }
@@ -863,16 +887,60 @@ function renderHeader({ badge, dv, scrollInfo }) {
863
887
 
864
888
  //#endregion
865
889
  //#region ../sync/src/ui/panels/ContextsContent.mjs
890
+ function renderFilterBar(activeFilter) {
891
+ const parts = [
892
+ {
893
+ id: "all",
894
+ label: "All",
895
+ color: "white"
896
+ },
897
+ {
898
+ id: "claude",
899
+ label: "Claude",
900
+ color: UC_CLAUDE_ORANGE
901
+ },
902
+ {
903
+ id: "codex",
904
+ label: "Codex",
905
+ color: UC_CODEX_BLUE
906
+ },
907
+ {
908
+ id: "openclaw",
909
+ label: "OpenClaw",
910
+ color: UC_OPENCLAW_RED
911
+ },
912
+ {
913
+ id: "cursor",
914
+ label: "Cursor",
915
+ color: "cyan"
916
+ },
917
+ {
918
+ id: "gemini",
919
+ label: "Gemini",
920
+ color: "blue"
921
+ }
922
+ ].map((f) => React.createElement(Text, {
923
+ key: `filter-${f.id}`,
924
+ color: activeFilter === f.id ? f.color : "gray",
925
+ bold: activeFilter === f.id,
926
+ dimColor: activeFilter !== f.id
927
+ }, activeFilter === f.id ? `[${f.label}]` : ` ${f.label} `));
928
+ return React.createElement(Box, {
929
+ key: "filter-bar",
930
+ flexDirection: "row",
931
+ gap: 1
932
+ }, ...parts);
933
+ }
866
934
  function ContextsContent({ snapshot, viewFocused, maxRows, maxCols }) {
867
935
  if (snapshot.detailView?.active) return React.createElement(ContextDetailContent, {
868
936
  snapshot,
869
937
  maxRows,
870
938
  maxCols
871
939
  });
872
- const contexts = snapshot.resume.contexts;
940
+ const contexts = snapshot.resume.filteredContexts ?? snapshot.resume.contexts;
873
941
  const total = contexts.length;
874
942
  const selected = Math.max(Math.min(snapshot.resume.selectedIndex, Math.max(total - 1, 0)), 0);
875
- const rows = [];
943
+ const rows = [renderFilterBar(snapshot.resume.sourceFilter ?? "all")];
876
944
  const tailRows = [];
877
945
  if (total > 0 && (snapshot.resume.notice || snapshot.resume.error || snapshot.resume.summaryPath || snapshot.resume.command)) {
878
946
  const selectedContext = contexts[selected];
@@ -908,7 +976,7 @@ function ContextsContent({ snapshot, viewFocused, maxRows, maxCols }) {
908
976
  color: "gray"
909
977
  }, `command file: ${compact(snapshot.resume.commandPath, 120)}`));
910
978
  const availableRows = Math.max(maxRows, 4);
911
- const listCapacity = Math.max(availableRows - tailRows.length, 1);
979
+ const listCapacity = Math.max(availableRows - tailRows.length - 1, 1);
912
980
  if (total === 0) rows.push(React.createElement(Text, {
913
981
  key: "contexts-empty",
914
982
  color: "yellow"
@@ -925,20 +993,103 @@ function ContextsContent({ snapshot, viewFocused, maxRows, maxCols }) {
925
993
  const sourceInfo = contextBadge(md.source || "unknown");
926
994
  const createdAt = formatContextDate(ctx?.created_at);
927
995
  const user = compact(md.user_id ?? "-", 12);
928
- const sessionId = compact(md.session_id ?? "-", 28);
996
+ const label = compact(md.title ?? md.session_id ?? "-", 48);
929
997
  rows.push(React.createElement(Text, {
930
998
  key: `contexts-row-${i}`,
931
999
  color: rowColor
932
1000
  }, `${marker} `, React.createElement(Text, {
933
1001
  color: sourceInfo.color,
934
1002
  bold: true
935
- }, `[${sourceInfo.text}]`), ` ${createdAt} ${user} ${sessionId}`));
1003
+ }, `[${sourceInfo.text}]`), ` ${createdAt} ${user} ${label}`));
936
1004
  }
937
1005
  }
938
1006
  rows.push(...tailRows);
939
1007
  return React.createElement(Box, { flexDirection: "column" }, ...padElements(rows, maxRows, "ctx"));
940
1008
  }
941
1009
 
1010
+ //#endregion
1011
+ //#region ../sync/src/Spinner.mjs
1012
+ const WIDTH = 28;
1013
+ const HEIGHT = 10;
1014
+ const SCALE = 40;
1015
+ const CAMERA_Z = 20;
1016
+ const CHARS = "··..,,--::;;==!!**##$$@@";
1017
+ const zBuffer = new Float32Array(WIDTH * HEIGHT);
1018
+ const screenBuffer = new Uint8Array(WIDTH * HEIGHT);
1019
+ const pointsData = [];
1020
+ function addPoint(x, y, z, type) {
1021
+ pointsData.push(x, y, z, type);
1022
+ }
1023
+ function addLine(x1, y1, z1, x2, y2, z2) {
1024
+ const density = 15;
1025
+ for (let i = 0; i <= density; i++) {
1026
+ const t = i / density;
1027
+ addPoint(x1 + (x2 - x1) * t, y1 + (y2 - y1) * t, z1 + (z2 - z1) * t, 0);
1028
+ }
1029
+ }
1030
+ addLine(-1.8, -1.2, 0, -1.8, 1.2, 0);
1031
+ addLine(-1.8, 1.2, 0, -1, 1.2, 0);
1032
+ addLine(-1.8, -1.2, 0, -1, -1.2, 0);
1033
+ addLine(1.8, -1.2, 0, 1.8, 1.2, 0);
1034
+ addLine(1.8, 1.2, 0, 1, 1.2, 0);
1035
+ addLine(1.8, -1.2, 0, 1, -1.2, 0);
1036
+ addPoint(0, 0, 0, 1);
1037
+ const points = new Float32Array(pointsData);
1038
+ const pointCount = points.length / 4;
1039
+ function renderFrame(angle) {
1040
+ zBuffer.fill(-Infinity);
1041
+ screenBuffer.fill(32);
1042
+ const cosA = Math.cos(angle);
1043
+ const sinA = Math.sin(angle);
1044
+ for (let i = 0; i < pointCount; i++) {
1045
+ const idx = i * 4;
1046
+ const px = points[idx];
1047
+ const py = points[idx + 1];
1048
+ const pz = points[idx + 2];
1049
+ const ptype = points[idx + 3];
1050
+ const xRot = px * cosA - pz * sinA;
1051
+ const yRot = py;
1052
+ const zRot = px * sinA + pz * cosA;
1053
+ const ooz = -1 / (zRot - CAMERA_Z);
1054
+ const screenX = Math.floor(WIDTH / 2 + xRot * ooz * SCALE * 2);
1055
+ const screenY = Math.floor(HEIGHT / 2 - yRot * ooz * SCALE);
1056
+ if (screenX >= 0 && screenX < WIDTH && screenY >= 0 && screenY < HEIGHT) {
1057
+ const bufIdx = screenX + screenY * WIDTH;
1058
+ if (ooz > zBuffer[bufIdx]) {
1059
+ zBuffer[bufIdx] = ooz;
1060
+ if (ptype === 1) screenBuffer[bufIdx] = 79;
1061
+ else {
1062
+ let charIdx = Math.floor((zRot + 2) * 4.5);
1063
+ if (charIdx < 0) charIdx = 0;
1064
+ if (charIdx >= 24) charIdx = 23;
1065
+ screenBuffer[bufIdx] = CHARS.charCodeAt(charIdx);
1066
+ }
1067
+ }
1068
+ }
1069
+ }
1070
+ const lines = [];
1071
+ for (let y = 0; y < HEIGHT; y++) {
1072
+ let row = "";
1073
+ const offset = y * WIDTH;
1074
+ for (let x = 0; x < WIDTH; x++) row += String.fromCharCode(screenBuffer[offset + x]);
1075
+ lines.push(row);
1076
+ }
1077
+ return lines;
1078
+ }
1079
+ const Spinner = ({ color = "green", prefix = "", suffix = "", prefixColor = "white", suffixColor = "white", sideLines = [], sideGap = 0, sideColor = "white" }) => {
1080
+ const [frameRows, setFrameRows] = useState(() => renderFrame(0));
1081
+ useEffect(() => {
1082
+ let angle = 0;
1083
+ const timer = setInterval(() => {
1084
+ angle += .05;
1085
+ setFrameRows(renderFrame(angle));
1086
+ }, 33);
1087
+ timer.unref?.();
1088
+ return () => clearInterval(timer);
1089
+ }, []);
1090
+ return React.createElement(Box, { flexDirection: "column" }, ...frameRows.map((row, index) => React.createElement(Text, { key: `spinner-row-${index}` }, prefix ? React.createElement(Text, { color: prefixColor }, prefix) : "", React.createElement(Text, { color }, row), sideLines.length > 0 ? React.createElement(Text, { color: sideColor }, `${" ".repeat(Math.max(sideGap, 0))}${sideLines[index] ?? ""}`) : "", suffix ? React.createElement(Text, { color: suffixColor }, suffix) : "")));
1091
+ };
1092
+
942
1093
  //#endregion
943
1094
  //#region ../sync/src/ui/panels/HeaderPanel.mjs
944
1095
  function formatUptime(value) {
@@ -952,56 +1103,26 @@ function formatUptime(value) {
952
1103
  return `${seconds}s`;
953
1104
  }
954
1105
  function HeaderPanel({ snapshot, stdoutColumns }) {
955
- const [pulseOn, setPulseOn] = React.useState(true);
956
- React.useEffect(() => {
957
- const timer = setInterval(() => {
958
- setPulseOn((current) => !current);
959
- }, 320);
960
- timer.unref?.();
961
- return () => clearInterval(timer);
962
- }, []);
963
- const health = snapshot.stats.errors > 0 ? "DEGRADED" : "HEALTHY";
964
- const healthColor = health === "HEALTHY" ? "green" : "yellow";
965
1106
  const tagline = "Same context, everywhere";
966
1107
  const innerWidth = Math.max(stdoutColumns, 40);
967
1108
  const spinnerVisualWidth = 28;
968
- const gap = innerWidth >= 96 ? 3 : 2;
1109
+ const gap = 2;
969
1110
  const artWidth = Math.max(innerWidth - spinnerVisualWidth - gap, 8);
970
- const statusPrefix = "status ";
971
- const healthToken = `● ${health}`;
972
- const clientsOnline = Array.isArray(snapshot.onlineClients) ? snapshot.onlineClients.length : 0;
973
- const fittedTail = fitToWidth([
974
- `live ${formatTime(snapshot.now)}`,
975
- `☻ ${snapshot.cfg.userId}`,
976
- `clients ${clientsOnline}`,
977
- `uptime ${formatUptime(Date.now() - Number(snapshot.stats.startedAt ?? 0))}`
978
- ].join(" │ "), Math.max(innerWidth - 7 - healthToken.length - 3, 0));
1111
+ const padLeft = 1;
979
1112
  return React.createElement(Box, {
980
1113
  flexDirection: "column",
981
- width: innerWidth
1114
+ width: innerWidth,
1115
+ paddingX: padLeft,
1116
+ paddingTop: 4,
1117
+ paddingBottom: 3
982
1118
  }, React.createElement(Box, {
983
1119
  flexDirection: "row",
984
- alignItems: "center",
985
- width: innerWidth
1120
+ alignItems: "flex-end",
1121
+ width: innerWidth - padLeft * 2
986
1122
  }, React.createElement(Spinner, { color: "white" }), React.createElement(Box, { width: gap }), React.createElement(HeroLockup, {
987
- width: artWidth,
1123
+ width: artWidth - padLeft * 2,
988
1124
  tagline
989
- })), React.createElement(Box, {
990
- flexDirection: "row",
991
- width: innerWidth
992
- }, React.createElement(Text, {
993
- color: "gray",
994
- dim: true
995
- }, statusPrefix), React.createElement(Text, {
996
- color: healthColor,
997
- bold: true
998
- }, React.createElement(Text, {
999
- color: healthColor,
1000
- dim: !pulseOn
1001
- }, "●"), ` ${health}`), fittedTail ? React.createElement(Text, {
1002
- color: "gray",
1003
- dim: true
1004
- }, ` │ ${fittedTail}`) : null));
1125
+ })));
1005
1126
  }
1006
1127
 
1007
1128
  //#endregion
@@ -1043,8 +1164,11 @@ function LogsContent({ snapshot, maxRows, maxCols = 100 }) {
1043
1164
  wrap: "truncate-end"
1044
1165
  }, fitToWidth("waiting for activity...", safeCols)));
1045
1166
  else rows.push(...visibleLogs.map((entry, index) => {
1046
- const category = classifyLog(entry);
1047
1167
  const sourceTag = entry.source ? `[${sourceLabel(entry.source)}]` : "";
1168
+ const category = sourceTag ? {
1169
+ label: "",
1170
+ color: "gray"
1171
+ } : classifyLog(entry);
1048
1172
  const timePrefix = `${String(entry.ts ?? "--:--:--")} `;
1049
1173
  const typePrefix = category.label ? `[${category.label}] ` : "";
1050
1174
  const sourcePrefix = sourceTag ? `${sourceTag} ` : "";
@@ -1274,39 +1398,6 @@ function UpdatePromptPanel({ snapshot, width }) {
1274
1398
 
1275
1399
  //#endregion
1276
1400
  //#region ../sync/src/ui/DaemonTui.mjs
1277
- const FOOTER_QUIPS = [
1278
- "Who is John Galt?",
1279
- "ultrathink -> ultracontext",
1280
- "Welcome to the beginning of infinity.",
1281
- "Our job is to take the jobs."
1282
- ];
1283
- function renderFooterQuip(quip) {
1284
- const index = String(quip ?? "").toLowerCase().indexOf("ultrathink");
1285
- if (index < 0) return React.createElement(Text, {
1286
- color: "gray",
1287
- bold: true
1288
- }, quip);
1289
- const before = quip.slice(0, index);
1290
- const match = quip.slice(index, index + 10);
1291
- const after = quip.slice(index + 10);
1292
- return React.createElement(Box, {
1293
- flexDirection: "row",
1294
- flexShrink: 0
1295
- }, before ? React.createElement(Text, {
1296
- key: "quip-before",
1297
- color: "gray",
1298
- bold: true
1299
- }, before) : null, React.createElement(Text, {
1300
- key: "quip-strike",
1301
- color: "gray",
1302
- bold: true,
1303
- strikethrough: true
1304
- }, match), after ? React.createElement(Text, {
1305
- key: "quip-after",
1306
- color: "gray",
1307
- bold: true
1308
- }, after) : null);
1309
- }
1310
1401
  function renderMainFrameTop(width, title) {
1311
1402
  const safeWidth = Math.max(Number(width) || 0, 4);
1312
1403
  const innerWidth = Math.max(safeWidth - 2, 1);
@@ -1352,16 +1443,9 @@ function DaemonTui({ snapshot, actions }) {
1352
1443
  const resumeTargetPickerActive = Boolean(snapshot.resumeTargetPicker?.active);
1353
1444
  const [focusMode, setFocusMode] = React.useState("menu");
1354
1445
  const [menuIndex, setMenuIndex] = React.useState(selectedTabIndex);
1355
- const [quipIndex, setQuipIndex] = React.useState(0);
1356
1446
  React.useEffect(() => {
1357
1447
  if (focusMode === "menu") setMenuIndex(selectedTabIndex);
1358
1448
  }, [focusMode, selectedTabIndex]);
1359
- React.useEffect(() => {
1360
- const timer = setInterval(() => {
1361
- setQuipIndex((current) => (current + 1) % FOOTER_QUIPS.length);
1362
- }, 3800);
1363
- return () => clearInterval(timer);
1364
- }, []);
1365
1449
  useInput(createInputHandler({
1366
1450
  snapshot,
1367
1451
  actions,
@@ -1406,12 +1490,13 @@ function DaemonTui({ snapshot, actions }) {
1406
1490
  focusMode
1407
1491
  });
1408
1492
  const footerWidth = Math.max(layout.containerWidth, 24);
1409
- const quipRaw = FOOTER_QUIPS[quipIndex % FOOTER_QUIPS.length];
1410
- const quip = fitToWidth(quipRaw, Math.max(Math.floor(footerWidth * .48), 18));
1411
- const left = fitToWidth(footerLeft, Math.max(footerWidth - quip.length - 2, 12));
1412
- const gap = " ".repeat(Math.max(footerWidth - left.length - quip.length, 1));
1493
+ const health = snapshot.stats.errors > 0 ? "DEGRADED" : "HEALTHY";
1494
+ const healthColor = health === "HEALTHY" ? "green" : "yellow";
1495
+ const statusRight = `● ${health} · uptime ${formatUptime(Date.now() - Number(snapshot.stats.startedAt ?? 0))}`;
1496
+ const left = fitToWidth(footerLeft, Math.max(footerWidth - statusRight.length - 2, 12));
1497
+ const gap = " ".repeat(Math.max(footerWidth - left.length - statusRight.length, 1));
1413
1498
  const footerRule = "─".repeat(Math.max(footerWidth, 1));
1414
- return React.createElement(Box, { flexDirection: "column" }, React.createElement(Box, { width: layout.containerWidth }, renderMainFrameTop(layout.containerWidth, snapshot.updateAvailable ? `UltraContext v${snapshot.currentVersion} ↑` : `UltraContext v${snapshot.currentVersion}`)), React.createElement(Box, {
1499
+ return React.createElement(Box, { flexDirection: "column" }, React.createElement(Box, { width: layout.containerWidth }, renderMainFrameTop(layout.containerWidth, process.env.ULTRACONTEXT_DEV ? `UltraContext v${snapshot.currentVersion} [dev]` : snapshot.updateAvailable ? `UltraContext v${snapshot.currentVersion} ↑` : `UltraContext v${snapshot.currentVersion}`)), React.createElement(Box, {
1415
1500
  borderStyle: "single",
1416
1501
  borderTop: false,
1417
1502
  borderColor: UC_BRAND_BLUE,
@@ -1435,7 +1520,10 @@ function DaemonTui({ snapshot, actions }) {
1435
1520
  }, React.createElement(Text, {
1436
1521
  color: "gray",
1437
1522
  wrap: "truncate-end"
1438
- }, `${left}${gap}`), renderFooterQuip(quip))));
1523
+ }, `${left}${gap}`), React.createElement(Text, {
1524
+ color: healthColor,
1525
+ bold: true
1526
+ }, statusRight))));
1439
1527
  }
1440
1528
 
1441
1529
  //#endregion
@@ -1538,6 +1626,14 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
1538
1626
  {
1539
1627
  id: "warp",
1540
1628
  label: "Warp"
1629
+ },
1630
+ {
1631
+ id: "tmux",
1632
+ label: "tmux"
1633
+ },
1634
+ {
1635
+ id: "cmux",
1636
+ label: "cmux"
1541
1637
  }
1542
1638
  ];
1543
1639
  const RESUME_TARGET_OPTIONS = [{
@@ -1551,6 +1647,14 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
1551
1647
  id: "inspect",
1552
1648
  label: "Inspect messages"
1553
1649
  };
1650
+ const SOURCE_FILTERS = [
1651
+ "all",
1652
+ "claude",
1653
+ "codex",
1654
+ "openclaw",
1655
+ "cursor",
1656
+ "gemini"
1657
+ ];
1554
1658
  const PERSISTED_CONFIG_FIELDS = [
1555
1659
  "bootstrapMode",
1556
1660
  "resumeTerminal",
@@ -1570,6 +1674,8 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
1570
1674
  const value = String(raw ?? "terminal").trim().toLowerCase();
1571
1675
  if (value === "warp") return "warp";
1572
1676
  if (value === "ghostty") return "ghostty";
1677
+ if (value === "tmux") return "tmux";
1678
+ if (value === "cmux") return "cmux";
1573
1679
  return "terminal";
1574
1680
  }
1575
1681
  function resolveRuntimeConfigPath() {
@@ -1610,8 +1716,11 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
1610
1716
  };
1611
1717
  function readTuiVersion() {
1612
1718
  try {
1613
- const pkgPath = path.resolve(APP_ROOT, "package.json");
1614
- return JSON.parse(fs.readFileSync(pkgPath, "utf8")).version ?? "unknown";
1719
+ const candidates = process$1.env.ULTRACONTEXT_DEV ? [path.resolve(APP_ROOT, "..", "js-sdk", "package.json"), path.resolve(APP_ROOT, "package.json")] : [path.resolve(APP_ROOT, "package.json")];
1720
+ for (const p of candidates) try {
1721
+ return JSON.parse(fs.readFileSync(p, "utf8")).version ?? "unknown";
1722
+ } catch {}
1723
+ return "unknown";
1615
1724
  } catch {
1616
1725
  return "unknown";
1617
1726
  }
@@ -1637,6 +1746,7 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
1637
1746
  return false;
1638
1747
  }
1639
1748
  async function checkForUpdateSilent() {
1749
+ if (process$1.env.ULTRACONTEXT_DEV) return;
1640
1750
  const current = readTuiVersion();
1641
1751
  if (current === "unknown") return;
1642
1752
  const notifyUpdate = (latest) => {
@@ -1674,6 +1784,8 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
1674
1784
  loading: false,
1675
1785
  syncing: false,
1676
1786
  contexts: [],
1787
+ filteredContexts: [],
1788
+ sourceFilter: "all",
1677
1789
  selectedIndex: 0,
1678
1790
  loadedAt: 0,
1679
1791
  error: "",
@@ -1724,7 +1836,9 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
1724
1836
  lastSnapshot: null,
1725
1837
  cachedLogSlice: [],
1726
1838
  cachedLogLen: 0,
1727
- stopResolve: null
1839
+ stopResolve: null,
1840
+ titleCache: /* @__PURE__ */ new Map(),
1841
+ titleInflight: /* @__PURE__ */ new Set()
1728
1842
  };
1729
1843
  function applyDaemonStatus(status) {
1730
1844
  if (!status) {
@@ -2036,7 +2150,49 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2036
2150
  method: "ghostty_applescript"
2037
2151
  };
2038
2152
  }
2153
+ function resumeOpenCmuxTab(command) {
2154
+ return {
2155
+ ...runAppleScriptLines([
2156
+ "set _uc_prev_clipboard to the clipboard",
2157
+ `set the clipboard to ${resumeAppleScriptString(command)}`,
2158
+ "tell application \"cmux\" to activate",
2159
+ "delay 0.4",
2160
+ "tell application \"System Events\"",
2161
+ "keystroke \"t\" using {command down}",
2162
+ "delay 0.3",
2163
+ "keystroke \"v\" using {command down}",
2164
+ "delay 0.15",
2165
+ "key code 36",
2166
+ "end tell",
2167
+ "delay 0.05",
2168
+ "set the clipboard to _uc_prev_clipboard"
2169
+ ]),
2170
+ method: "cmux_applescript"
2171
+ };
2172
+ }
2173
+ function resumeOpenTmuxWindow(command) {
2174
+ if (!process$1.env.TMUX) return {
2175
+ ok: false,
2176
+ reason: "not inside a tmux session"
2177
+ };
2178
+ const out = spawnSync("tmux", ["new-window", command], {
2179
+ stdio: "pipe",
2180
+ encoding: "utf8",
2181
+ timeout: 5e3
2182
+ });
2183
+ if (out.status === 0) return {
2184
+ ok: true,
2185
+ method: "tmux"
2186
+ };
2187
+ return {
2188
+ ok: false,
2189
+ reason: out.stderr?.trim() || "tmux new-window failed",
2190
+ method: "tmux"
2191
+ };
2192
+ }
2039
2193
  function resumeOpenTerminalTab(command) {
2194
+ if (cfg.resumeTerminal === "tmux") return resumeOpenTmuxWindow(command);
2195
+ if (cfg.resumeTerminal === "cmux") return resumeOpenCmuxTab(command);
2040
2196
  if (process$1.platform !== "darwin") return {
2041
2197
  ok: false,
2042
2198
  reason: "open-tab is available only on macOS"
@@ -2110,6 +2266,21 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2110
2266
  return true;
2111
2267
  });
2112
2268
  }
2269
+ function applySourceFilter() {
2270
+ const filter = ui.resume.sourceFilter;
2271
+ if (filter === "all") ui.resume.filteredContexts = ui.resume.contexts;
2272
+ else ui.resume.filteredContexts = ui.resume.contexts.filter((ctx) => {
2273
+ return String(ctx?.metadata?.source ?? "").toLowerCase() === filter;
2274
+ });
2275
+ if (ui.resume.selectedIndex >= ui.resume.filteredContexts.length) ui.resume.selectedIndex = Math.max(ui.resume.filteredContexts.length - 1, 0);
2276
+ }
2277
+ function cycleSourceFilter() {
2278
+ const current = ui.resume.sourceFilter;
2279
+ const idx = SOURCE_FILTERS.indexOf(current);
2280
+ ui.resume.sourceFilter = SOURCE_FILTERS[(idx + 1) % SOURCE_FILTERS.length];
2281
+ applySourceFilter();
2282
+ renderDashboard();
2283
+ }
2113
2284
  function resumeSortContexts(contexts) {
2114
2285
  const ts = (ctx) => {
2115
2286
  const candidates = [
@@ -2201,6 +2372,42 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2201
2372
  lines.push("", "## Resume Instructions", "1. Use the generated adapter command from the Contexts tab.", "2. Continue from the latest unresolved request.", "");
2202
2373
  return lines.join("\n");
2203
2374
  }
2375
+ function enrichContextTitles(contexts) {
2376
+ const missing = contexts.filter((ctx) => ctx?.id && !ctx?.metadata?.title && !runtime.titleCache.has(ctx.id) && !runtime.titleInflight.has(ctx.id)).slice(0, 20);
2377
+ if (!missing.length || !runtime.uc) return;
2378
+ for (const ctx of missing) {
2379
+ runtime.titleInflight.add(ctx.id);
2380
+ runtime.uc.get(ctx.id, { at: 30 }).then((res) => {
2381
+ const msgs = res?.data ?? [];
2382
+ const isRealUser = (m) => {
2383
+ if (m?.role !== "user") return false;
2384
+ if (m?.content?.event_type === "response_item.message") return false;
2385
+ const msg = typeof m?.content?.message === "string" ? m.content.message : "";
2386
+ if (msg.startsWith("A new session was started")) return false;
2387
+ if (msg.startsWith("[result]")) return false;
2388
+ if (msg.startsWith("<")) return false;
2389
+ return true;
2390
+ };
2391
+ const firstUser = msgs.find(isRealUser) ?? msgs.find((m) => m?.role === "user");
2392
+ if (!firstUser) {
2393
+ runtime.titleCache.set(ctx.id, null);
2394
+ return;
2395
+ }
2396
+ const text = firstUser?.content?.message ?? firstUser?.content ?? "";
2397
+ const title = (typeof text === "string" ? text : JSON.stringify(text)).replace(/[\r\n\t\v\f\x00-\x1f]+/g, " ").replace(/\s{2,}/g, " ").trim().slice(0, 120);
2398
+ runtime.titleCache.set(ctx.id, title || null);
2399
+ if (title) {
2400
+ ctx.metadata = {
2401
+ ...ctx.metadata,
2402
+ title
2403
+ };
2404
+ markDirty();
2405
+ }
2406
+ }).catch(() => {
2407
+ runtime.titleCache.set(ctx.id, null);
2408
+ }).finally(() => runtime.titleInflight.delete(ctx.id));
2409
+ }
2410
+ }
2204
2411
  async function loadResumeContexts({ silent = false } = {}) {
2205
2412
  if (!runtime.uc || ui.resume.loading) return;
2206
2413
  ui.resume.loading = true;
@@ -2218,25 +2425,34 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2218
2425
  }
2219
2426
  runtime.resumeKnownContextIds = nextIds;
2220
2427
  if (!runtime.resumeBaselineReady) runtime.resumeBaselineReady = true;
2428
+ for (const ctx of filtered) {
2429
+ const cached = runtime.titleCache.get(ctx.id);
2430
+ if (cached && !ctx.metadata?.title) ctx.metadata = {
2431
+ ...ctx.metadata,
2432
+ title: cached
2433
+ };
2434
+ }
2221
2435
  ui.resume.contexts = filtered;
2222
- if (ui.resume.selectedIndex >= filtered.length) ui.resume.selectedIndex = Math.max(filtered.length - 1, 0);
2436
+ applySourceFilter();
2437
+ if (!silent) enrichContextTitles(filtered);
2223
2438
  ui.resume.loadedAt = Date.now();
2224
2439
  const sourceCounts = {
2225
2440
  codex: 0,
2226
2441
  claude: 0,
2227
2442
  openclaw: 0,
2443
+ cursor: 0,
2444
+ gemini: 0,
2228
2445
  other: 0
2229
2446
  };
2230
2447
  for (const ctx of filtered) {
2231
2448
  const source = String(ctx?.metadata?.source ?? "").toLowerCase();
2232
- if (source === "codex") sourceCounts.codex += 1;
2233
- else if (source === "claude") sourceCounts.claude += 1;
2234
- else if (source === "openclaw") sourceCounts.openclaw += 1;
2449
+ if (sourceCounts[source] !== void 0) sourceCounts[source] += 1;
2235
2450
  else sourceCounts.other += 1;
2236
2451
  }
2237
2452
  if (!silent) {
2238
2453
  const filterLabel = cfg.resumeSourceFilter === "all" ? "all sources" : cfg.resumeSourceFilter;
2239
- ui.resume.notice = `Loaded ${filtered.length} session contexts (${filterLabel}: codex=${sourceCounts.codex}, claude=${sourceCounts.claude}, openclaw=${sourceCounts.openclaw}, other=${sourceCounts.other})`;
2454
+ const counts = Object.entries(sourceCounts).map(([k, v]) => `${k}=${v}`).join(", ");
2455
+ ui.resume.notice = `Loaded ${filtered.length} session contexts (${filterLabel}: ${counts})`;
2240
2456
  if (filtered.length === 0) ui.resume.notice = `No contexts found for filter=${cfg.resumeSourceFilter}`;
2241
2457
  }
2242
2458
  } catch (error) {
@@ -2251,7 +2467,7 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2251
2467
  }
2252
2468
  }
2253
2469
  function moveResumeSelection(delta) {
2254
- const total = ui.resume.contexts.length;
2470
+ const total = ui.resume.filteredContexts.length;
2255
2471
  if (!total) return;
2256
2472
  const next = ui.resume.selectedIndex + delta;
2257
2473
  if (next < 0) {
@@ -2271,7 +2487,7 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2271
2487
  }
2272
2488
  function openResumeTargetPicker() {
2273
2489
  if (ui.resume.syncing) return false;
2274
- const context = ui.resume.contexts[ui.resume.selectedIndex];
2490
+ const context = ui.resume.filteredContexts[ui.resume.selectedIndex];
2275
2491
  if (!context) {
2276
2492
  ui.resume.notice = "No context selected";
2277
2493
  renderDashboard();
@@ -2365,7 +2581,7 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2365
2581
  }
2366
2582
  async function resumeSelectedContext({ targetAgentOverride = "" } = {}) {
2367
2583
  if (!runtime.uc || ui.resume.syncing) return;
2368
- const context = ui.resume.contexts[ui.resume.selectedIndex];
2584
+ const context = ui.resume.filteredContexts[ui.resume.selectedIndex];
2369
2585
  if (!context) {
2370
2586
  ui.resume.notice = "No context selected";
2371
2587
  renderDashboard();
@@ -2669,7 +2885,7 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2669
2885
  }
2670
2886
  async function openContextDetail() {
2671
2887
  if (ui.detailView.loading) return;
2672
- const context = ui.resume.contexts[ui.resume.selectedIndex];
2888
+ const context = ui.resume.filteredContexts[ui.resume.selectedIndex];
2673
2889
  if (!context) return;
2674
2890
  ui.detailView.active = true;
2675
2891
  ui.detailView.contextId = context.id;
@@ -2733,7 +2949,7 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2733
2949
  renderDashboard();
2734
2950
  }
2735
2951
  function enterContext() {
2736
- const context = ui.resume.contexts[ui.resume.selectedIndex];
2952
+ const context = ui.resume.filteredContexts[ui.resume.selectedIndex];
2737
2953
  if (!context) {
2738
2954
  ui.resume.notice = "No context selected";
2739
2955
  renderDashboard();
@@ -2856,6 +3072,9 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2856
3072
  refreshResume: () => {
2857
3073
  loadResumeContexts();
2858
3074
  },
3075
+ cycleSourceFilter: () => {
3076
+ cycleSourceFilter();
3077
+ },
2859
3078
  promptResumeTarget: () => {
2860
3079
  openResumeTargetPicker();
2861
3080
  },
@@ -2949,4 +3168,4 @@ async function tuiBoot({ assetsRoot, onFatalError } = {}) {
2949
3168
 
2950
3169
  //#endregion
2951
3170
  export { tuiBoot };
2952
- //# sourceMappingURL=tui-DZ1SDOH2.mjs.map
3171
+ //# sourceMappingURL=tui-DLEjew3K.mjs.map