@kmugalkhod/lightcode 0.10.0 → 0.11.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 (2) hide show
  1. package/cli.js +464 -266
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -124,7 +124,7 @@ var init_logger = __esm(() => {
124
124
  });
125
125
 
126
126
  // packages/shared/src/version.ts
127
- var lightcodeVersion = "0.10.0";
127
+ var lightcodeVersion = "0.11.0";
128
128
 
129
129
  // packages/shared/src/index.ts
130
130
  var productName = "Lightcode";
@@ -577,11 +577,11 @@ var init_help_overlay = __esm(() => {
577
577
  init_keymap();
578
578
  init_cli_theme();
579
579
  screenHints = [
580
- { context: "Chat", hint: "Tab/Ctrl+T switch mode | @ attach files | /compact, /undo actions" },
581
- { context: "Chat", hint: "Ctrl+O expand tool output | Ctrl+R show reasoning | Enter send | Ctrl+Enter newline" },
582
- { context: "IDE", hint: "F2 IDE layout (Explorer \xB7 Editor \xB7 Chat) | Tab focus Explorer | \u2192/Enter open file | click \u270E or e to edit, Ctrl+S save | [ ] tabs" },
583
- { context: "Copy", hint: "Ctrl+Y copy mode (\u2191/\u2193 pick, Enter copy, c code) | /copy [last|code|all]" },
584
- { context: "Sessions", hint: "Enter resume | r rename | f fork | / filter | e export (.md) | d delete" }
580
+ { context: "Chat", hint: "Tab/Ctrl+T switch mode \xB7 @ attach files \xB7 /compact, /undo actions" },
581
+ { context: "Chat", hint: "Ctrl+O expand tool output \xB7 Ctrl+R show reasoning \xB7 Enter send \xB7 Ctrl+Enter newline" },
582
+ { context: "IDE", hint: "F2 IDE layout (Explorer \xB7 Editor \xB7 Chat) \xB7 Tab focus Explorer \xB7 \u2192/Enter open file \xB7 e edit, Ctrl+S save \xB7 [ ] tabs" },
583
+ { context: "Copy", hint: "Ctrl+Y copy mode (\u2191/\u2193 pick, Enter copy, c code) \xB7 /copy [last|code|all]" },
584
+ { context: "Sessions", hint: "Enter resume \xB7 r rename \xB7 f fork \xB7 / filter \xB7 e export (.md) \xB7 d delete" }
585
585
  ];
586
586
  });
587
587
 
@@ -595,7 +595,7 @@ function getSlashPageRoutes(query = "") {
595
595
  return getNavigationRoutes();
596
596
  }
597
597
  return getNavigationRoutes().filter((route) => {
598
- return route.path.toLowerCase().includes(normalizedQuery) || route.shortcut.replace(/^\//, "").toLowerCase().includes(normalizedQuery) || route.label.toLowerCase().includes(normalizedQuery) || route.description.toLowerCase().includes(normalizedQuery);
598
+ return route.shortcut.replace(/^\//, "").toLowerCase().startsWith(normalizedQuery) || route.label.toLowerCase().includes(normalizedQuery);
599
599
  });
600
600
  }
601
601
  function getPathFromAction(action) {
@@ -807,7 +807,7 @@ function CommandPalette({ query, setQuery, selectedIndex }) {
807
807
  gap: 2,
808
808
  children: [
809
809
  /* @__PURE__ */ jsxDEV2(PaletteHint, {
810
- text: "Up/Down",
810
+ text: "\u2191/\u2193",
811
811
  description: "navigate"
812
812
  }, undefined, false, undefined, this),
813
813
  /* @__PURE__ */ jsxDEV2(PaletteHint, {
@@ -942,6 +942,33 @@ var init_command_palette = __esm(() => {
942
942
  init_cli_theme();
943
943
  });
944
944
 
945
+ // apps/cli/src/ui/cli-theme-capabilities.ts
946
+ function isUtf8Locale(env) {
947
+ const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
948
+ if (!locale) {
949
+ return true;
950
+ }
951
+ return /utf-?8/i.test(locale);
952
+ }
953
+ function detectTerminalCapabilities(env = process.env) {
954
+ const noColor = Boolean(env.NO_COLOR);
955
+ const dumbTerm = env.TERM === "dumb";
956
+ return {
957
+ color: !noColor && !dumbTerm,
958
+ unicode: isUtf8Locale(env),
959
+ reducedMotion: Boolean(env.NO_COLOR || env.LIGHTCODE_REDUCED_MOTION)
960
+ };
961
+ }
962
+ function resolveGlyphs(caps) {
963
+ return caps.unicode ? glyphs.unicode : glyphs.ascii;
964
+ }
965
+ var terminalCapabilities, activeGlyphs;
966
+ var init_cli_theme_capabilities = __esm(() => {
967
+ init_cli_theme();
968
+ terminalCapabilities = detectTerminalCapabilities();
969
+ activeGlyphs = resolveGlyphs(terminalCapabilities);
970
+ });
971
+
945
972
  // apps/cli/src/commands/slash-page-menu.tsx
946
973
  import { jsxDEV as jsxDEV3 } from "@opentui/react/jsx-dev-runtime";
947
974
  function SlashPageMenu({
@@ -949,7 +976,7 @@ function SlashPageMenu({
949
976
  selectedIndex,
950
977
  routes
951
978
  }) {
952
- const normalizedQuery = query.trim();
979
+ const normalizedQuery = query.trim().replace(/^\//, "");
953
980
  const isFiltering = normalizedQuery.length > 0;
954
981
  return /* @__PURE__ */ jsxDEV3("box", {
955
982
  width: "100%",
@@ -966,7 +993,7 @@ function SlashPageMenu({
966
993
  }, undefined, false, undefined, this) : routes.map((route, index) => /* @__PURE__ */ jsxDEV3(MenuItem, {
967
994
  route,
968
995
  selected: index === selectedIndex
969
- }, route.id, false, undefined, this))
996
+ }, route.shortcut, false, undefined, this))
970
997
  }, undefined, false, undefined, this),
971
998
  /* @__PURE__ */ jsxDEV3("box", {
972
999
  flexDirection: "row",
@@ -979,22 +1006,14 @@ function SlashPageMenu({
979
1006
  children: [
980
1007
  /* @__PURE__ */ jsxDEV3("text", {
981
1008
  fg: cliTheme.overlay.footerText,
982
- children: isFiltering ? `Showing: "${normalizedQuery.replace(/^\//, "")}" - ${routes.length} result${routes.length !== 1 ? "s" : ""}` : `${routes.length} page${routes.length !== 1 ? "s" : ""}`
1009
+ children: isFiltering ? `"${normalizedQuery}" \xB7 ${routes.length} result${routes.length !== 1 ? "s" : ""}` : `${routes.length} page${routes.length !== 1 ? "s" : ""}`
983
1010
  }, undefined, false, undefined, this),
984
1011
  /* @__PURE__ */ jsxDEV3("box", {
985
1012
  flexGrow: 1
986
1013
  }, undefined, false, undefined, this),
987
1014
  /* @__PURE__ */ jsxDEV3("text", {
988
1015
  ...typeRole("caption"),
989
- children: "Enter open | "
990
- }, undefined, false, undefined, this),
991
- /* @__PURE__ */ jsxDEV3("text", {
992
- ...typeRole("emphasis"),
993
- children: "Up/Down"
994
- }, undefined, false, undefined, this),
995
- /* @__PURE__ */ jsxDEV3("text", {
996
- ...typeRole("caption"),
997
- children: " select | Esc close"
1016
+ children: "\u2191/\u2193 select \xB7 Enter open \xB7 Esc close"
998
1017
  }, undefined, false, undefined, this)
999
1018
  ]
1000
1019
  }, undefined, true, undefined, this)
@@ -1014,13 +1033,14 @@ function MenuItem({ route, selected }) {
1014
1033
  /* @__PURE__ */ jsxDEV3("text", {
1015
1034
  width: 2,
1016
1035
  fg: selected ? cliTheme.overlay.selectedBorder : cliTheme.text.muted,
1017
- children: selected ? ">" : " "
1036
+ children: selected ? activeGlyphs.roleUser : " "
1018
1037
  }, undefined, false, undefined, this),
1019
1038
  /* @__PURE__ */ jsxDEV3("box", {
1020
1039
  backgroundColor: cliTheme.overlay.badgeBackground,
1021
1040
  paddingX: 1,
1022
1041
  paddingY: 0,
1023
1042
  marginRight: 1,
1043
+ flexShrink: 0,
1024
1044
  children: /* @__PURE__ */ jsxDEV3("text", {
1025
1045
  fg: cliTheme.overlay.badgeText,
1026
1046
  children: [
@@ -1030,12 +1050,18 @@ function MenuItem({ route, selected }) {
1030
1050
  }, undefined, true, undefined, this)
1031
1051
  }, undefined, false, undefined, this),
1032
1052
  /* @__PURE__ */ jsxDEV3("text", {
1053
+ wrapMode: "none",
1054
+ truncate: true,
1055
+ flexShrink: 0,
1033
1056
  fg: selected ? cliTheme.overlay.selectedRowText : cliTheme.text.primary,
1034
1057
  attributes: selected ? TextAttributes.BOLD : TextAttributes.NONE,
1035
1058
  marginRight: 2,
1036
1059
  children: route.label
1037
1060
  }, undefined, false, undefined, this),
1038
1061
  /* @__PURE__ */ jsxDEV3("text", {
1062
+ wrapMode: "none",
1063
+ truncate: true,
1064
+ flexShrink: 1,
1039
1065
  fg: selected ? cliTheme.overlay.selectedRowText : cliTheme.overlay.description,
1040
1066
  children: route.description
1041
1067
  }, undefined, false, undefined, this)
@@ -1076,6 +1102,7 @@ function EmptyState({ query }) {
1076
1102
  var init_slash_page_menu = __esm(() => {
1077
1103
  init_cli_theme();
1078
1104
  init_cli_theme();
1105
+ init_cli_theme_capabilities();
1079
1106
  });
1080
1107
 
1081
1108
  // packages/ai/src/constants.ts
@@ -8759,7 +8786,7 @@ function filterChatSlashActions(query) {
8759
8786
  if (!normalized) {
8760
8787
  return chatSlashActions;
8761
8788
  }
8762
- return chatSlashActions.filter((action) => action.shortcut.replace(/^\//, "").toLowerCase().includes(normalized) || action.label.toLowerCase().includes(normalized) || action.description.toLowerCase().includes(normalized));
8789
+ return chatSlashActions.filter((action) => action.shortcut.replace(/^\//, "").toLowerCase().startsWith(normalized) || action.label.toLowerCase().includes(normalized));
8763
8790
  }
8764
8791
  var chatSlashActions;
8765
8792
  var init_chat_slash_actions = __esm(() => {
@@ -8814,18 +8841,27 @@ var init_chat_slash_actions = __esm(() => {
8814
8841
  label: "Permission mode",
8815
8842
  description: "Switch permission level: read-only, workspace-write, or danger-full-access",
8816
8843
  shortcut: "/permission",
8844
+ availableOnHome: true,
8817
8845
  run: async () => {}
8818
8846
  }
8819
8847
  ];
8820
8848
  });
8821
8849
 
8822
8850
  // apps/cli/src/commands/slash-menu-items.ts
8823
- function getSlashMenuItems(query, { includeChatActions }) {
8851
+ function getSlashMenuItems(query, { host }) {
8824
8852
  const routes = getSlashPageRoutes(query);
8825
- if (!includeChatActions) {
8853
+ if (host === "other") {
8826
8854
  return routes;
8827
8855
  }
8828
- return [...filterChatSlashActions(query), ...routes];
8856
+ const actions = filterChatSlashActions(query).filter((action) => host === "chat" || action.availableOnHome);
8857
+ const seenShortcuts = new Set;
8858
+ return [...actions, ...routes].filter((item) => {
8859
+ if (seenShortcuts.has(item.shortcut)) {
8860
+ return false;
8861
+ }
8862
+ seenShortcuts.add(item.shortcut);
8863
+ return true;
8864
+ });
8829
8865
  }
8830
8866
  var init_slash_menu_items = __esm(() => {
8831
8867
  init_route_registry();
@@ -10327,7 +10363,6 @@ function ChatInteractionPopup({
10327
10363
  const [customAnswer, setCustomAnswer] = useState2("");
10328
10364
  const [validationMessage, setValidationMessage] = useState2(null);
10329
10365
  const submitVerb = submitLabel.toLowerCase();
10330
- const hintLabel = onCancel ? `Up/Down select | Enter ${submitVerb} | Esc dismiss` : `Up/Down select | Enter ${submitVerb}`;
10331
10366
  const rows = useMemo2(() => {
10332
10367
  const optionRows = options.map((option) => ({
10333
10368
  value: option.value,
@@ -10348,10 +10383,18 @@ function ChatInteractionPopup({
10348
10383
  }
10349
10384
  ];
10350
10385
  }, [allowCustomResponse, options]);
10386
+ const inputOnly = options.length === 0 && allowCustomResponse;
10387
+ const visibleRows = inputOnly ? [] : rows;
10351
10388
  const safeSelectedIndex = Math.min(selectedIndex, Math.max(rows.length - 1, 0));
10352
10389
  const selectedRow = rows[safeSelectedIndex];
10353
10390
  const requiresCustomResponse = Boolean(selectedRow?.isCustom || selectedRow && requireCustomResponseForValues.includes(selectedRow.value));
10354
- const canShowInput = requiresCustomResponse || rows.length === 0 && allowCustomResponse;
10391
+ const canShowInput = inputOnly || requiresCustomResponse;
10392
+ const hintLabel = [
10393
+ visibleRows.length > 1 ? "\u2191/\u2193 select" : null,
10394
+ visibleRows.length > 1 ? "1-9 pick" : null,
10395
+ `Enter ${submitVerb}`,
10396
+ onCancel ? "Esc dismiss" : null
10397
+ ].filter(Boolean).join(" \xB7 ");
10355
10398
  const submitCurrentSelection = () => {
10356
10399
  if (!selectedRow && !canShowInput) {
10357
10400
  return;
@@ -10359,14 +10402,14 @@ function ChatInteractionPopup({
10359
10402
  if (canShowInput) {
10360
10403
  const answer = customAnswer.trim();
10361
10404
  if (answer.length === 0) {
10362
- setValidationMessage("Please enter a response before submitting.");
10405
+ setValidationMessage(selectedRow && !selectedRow.isCustom && !inputOnly ? "This choice needs a short note \u2014 type it below." : "Type an answer first, then press Enter.");
10363
10406
  return;
10364
10407
  }
10365
10408
  setValidationMessage(null);
10366
10409
  onSubmit({
10367
10410
  answer,
10368
- selectedOption: selectedRow && !selectedRow.isCustom ? selectedRow.label : undefined,
10369
- selectedValue: selectedRow && !selectedRow.isCustom ? selectedRow.value : undefined,
10411
+ selectedOption: selectedRow && !selectedRow.isCustom && !inputOnly ? selectedRow.label : undefined,
10412
+ selectedValue: selectedRow && !selectedRow.isCustom && !inputOnly ? selectedRow.value : undefined,
10370
10413
  source: selectedRow?.isCustom || !selectedRow || requiresCustomResponse ? "custom" : "option"
10371
10414
  });
10372
10415
  return;
@@ -10379,6 +10422,10 @@ function ChatInteractionPopup({
10379
10422
  source: "option"
10380
10423
  });
10381
10424
  };
10425
+ const selectRow = (index) => {
10426
+ setValidationMessage(null);
10427
+ setSelectedIndex(Math.min(Math.max(index, 0), Math.max(rows.length - 1, 0)));
10428
+ };
10382
10429
  useKeyboard((keyEvent) => {
10383
10430
  if (rows.length === 0 && !canShowInput) {
10384
10431
  if (isEscapeKey(keyEvent.name.toLowerCase()) && onCancel) {
@@ -10392,15 +10439,33 @@ function ChatInteractionPopup({
10392
10439
  if (isDownKey(keyName)) {
10393
10440
  keyEvent.preventDefault();
10394
10441
  keyEvent.stopPropagation();
10395
- setValidationMessage(null);
10396
- setSelectedIndex((currentIndex) => Math.min(currentIndex + 1, Math.max(rows.length - 1, 0)));
10442
+ selectRow(safeSelectedIndex + 1);
10397
10443
  return;
10398
10444
  }
10399
10445
  if (isUpKey(keyName)) {
10400
10446
  keyEvent.preventDefault();
10401
10447
  keyEvent.stopPropagation();
10402
- setValidationMessage(null);
10403
- setSelectedIndex((currentIndex) => Math.max(currentIndex - 1, 0));
10448
+ selectRow(safeSelectedIndex - 1);
10449
+ return;
10450
+ }
10451
+ if (!canShowInput && visibleRows.length > 1 && /^[1-9]$/.test(keyName)) {
10452
+ const index = Number(keyName) - 1;
10453
+ if (index < rows.length) {
10454
+ keyEvent.preventDefault();
10455
+ keyEvent.stopPropagation();
10456
+ selectRow(index);
10457
+ const row = rows[index];
10458
+ const needsInput = row.isCustom || requireCustomResponseForValues.includes(row.value);
10459
+ if (!needsInput) {
10460
+ setValidationMessage(null);
10461
+ onSubmit({
10462
+ answer: row.label,
10463
+ selectedOption: row.label,
10464
+ selectedValue: row.value,
10465
+ source: "option"
10466
+ });
10467
+ }
10468
+ }
10404
10469
  return;
10405
10470
  }
10406
10471
  if (isEnterKey(keyName)) {
@@ -10421,23 +10486,35 @@ function ChatInteractionPopup({
10421
10486
  borderStyle: borderStyleFor.modal,
10422
10487
  borderColor: cliTheme.borders.active,
10423
10488
  backgroundColor: cliTheme.surfaces.panel,
10424
- paddingX: 1,
10489
+ paddingX: 2,
10425
10490
  paddingY: 1,
10426
10491
  gap: 1,
10427
10492
  children: [
10428
- /* @__PURE__ */ jsxDEV6("text", {
10429
- ...typeRole("title"),
10430
- children: title
10431
- }, undefined, false, undefined, this),
10493
+ /* @__PURE__ */ jsxDEV6("box", {
10494
+ width: "100%",
10495
+ flexDirection: "row",
10496
+ justifyContent: "space-between",
10497
+ children: [
10498
+ /* @__PURE__ */ jsxDEV6("text", {
10499
+ ...typeRole("title"),
10500
+ children: title
10501
+ }, undefined, false, undefined, this),
10502
+ /* @__PURE__ */ jsxDEV6("text", {
10503
+ fg: cliTheme.text.muted,
10504
+ children: submitLabel
10505
+ }, undefined, false, undefined, this)
10506
+ ]
10507
+ }, undefined, true, undefined, this),
10432
10508
  /* @__PURE__ */ jsxDEV6("text", {
10433
10509
  fg: cliTheme.text.primary,
10434
10510
  children: question
10435
10511
  }, undefined, false, undefined, this),
10436
- rows.length > 0 ? /* @__PURE__ */ jsxDEV6("box", {
10512
+ visibleRows.length > 0 ? /* @__PURE__ */ jsxDEV6("box", {
10437
10513
  width: "100%",
10438
10514
  flexDirection: "column",
10439
- children: rows.map((row, index) => {
10440
- const rowColors = getOverlayRowColors(index === safeSelectedIndex);
10515
+ children: visibleRows.map((row, index) => {
10516
+ const selected = index === safeSelectedIndex;
10517
+ const rowColors = getOverlayRowColors(selected);
10441
10518
  return /* @__PURE__ */ jsxDEV6("box", {
10442
10519
  width: "100%",
10443
10520
  flexDirection: "column",
@@ -10445,17 +10522,32 @@ function ChatInteractionPopup({
10445
10522
  backgroundColor: rowColors.backgroundColor,
10446
10523
  children: [
10447
10524
  /* @__PURE__ */ jsxDEV6("text", {
10448
- fg: rowColors.primaryTextColor,
10525
+ fg: selected ? cliTheme.accent.primary : rowColors.primaryTextColor,
10449
10526
  children: [
10450
- index + 1,
10451
- ". ",
10452
- row.label
10527
+ /* @__PURE__ */ jsxDEV6("span", {
10528
+ children: selected ? `${activeGlyphs.roleUser} ` : " "
10529
+ }, undefined, false, undefined, this),
10530
+ /* @__PURE__ */ jsxDEV6("span", {
10531
+ fg: selected ? cliTheme.accent.primary : cliTheme.text.muted,
10532
+ children: [
10533
+ index + 1,
10534
+ ".",
10535
+ " "
10536
+ ]
10537
+ }, undefined, true, undefined, this),
10538
+ /* @__PURE__ */ jsxDEV6("span", {
10539
+ fg: rowColors.primaryTextColor,
10540
+ children: row.label
10541
+ }, undefined, false, undefined, this)
10453
10542
  ]
10454
10543
  }, undefined, true, undefined, this),
10455
10544
  row.description ? /* @__PURE__ */ jsxDEV6("text", {
10456
- ...typeRole("caption"),
10457
- children: row.description
10458
- }, undefined, false, undefined, this) : null
10545
+ fg: rowColors.secondaryTextColor,
10546
+ children: [
10547
+ " ",
10548
+ row.description
10549
+ ]
10550
+ }, undefined, true, undefined, this) : null
10459
10551
  ]
10460
10552
  }, row.value, true, undefined, this);
10461
10553
  })
@@ -10465,16 +10557,19 @@ function ChatInteractionPopup({
10465
10557
  flexDirection: "column",
10466
10558
  gap: 1,
10467
10559
  children: [
10468
- selectedRow && !selectedRow.isCustom ? /* @__PURE__ */ jsxDEV6("text", {
10560
+ selectedRow && !selectedRow.isCustom && !inputOnly ? /* @__PURE__ */ jsxDEV6("text", {
10469
10561
  fg: cliTheme.text.secondary,
10470
- children: "Additional details required"
10562
+ children: "Add a short note to continue:"
10471
10563
  }, undefined, false, undefined, this) : null,
10472
10564
  /* @__PURE__ */ jsxDEV6("input", {
10473
10565
  value: customAnswer,
10474
- onChange: (value) => {
10566
+ onInput: (value) => {
10475
10567
  setValidationMessage(null);
10476
10568
  setCustomAnswer(value ?? "");
10477
10569
  },
10570
+ onSubmit: () => {
10571
+ submitCurrentSelection();
10572
+ },
10478
10573
  placeholder,
10479
10574
  width: "100%",
10480
10575
  focused: true,
@@ -10487,27 +10582,17 @@ function ChatInteractionPopup({
10487
10582
  fg: cliTheme.semantic.warning,
10488
10583
  children: validationMessage
10489
10584
  }, undefined, false, undefined, this) : null,
10490
- /* @__PURE__ */ jsxDEV6("box", {
10491
- width: "100%",
10492
- flexDirection: "row",
10493
- justifyContent: "space-between",
10494
- children: [
10495
- /* @__PURE__ */ jsxDEV6("text", {
10496
- ...typeRole("caption"),
10497
- children: hintLabel
10498
- }, undefined, false, undefined, this),
10499
- /* @__PURE__ */ jsxDEV6("text", {
10500
- fg: cliTheme.text.secondary,
10501
- children: submitLabel
10502
- }, undefined, false, undefined, this)
10503
- ]
10504
- }, undefined, true, undefined, this)
10585
+ /* @__PURE__ */ jsxDEV6("text", {
10586
+ ...typeRole("caption"),
10587
+ children: hintLabel
10588
+ }, undefined, false, undefined, this)
10505
10589
  ]
10506
10590
  }, undefined, true, undefined, this);
10507
10591
  }
10508
10592
  var customOptionValue = "__custom_answer__";
10509
10593
  var init_chat_interaction_popup = __esm(() => {
10510
10594
  init_cli_theme();
10595
+ init_cli_theme_capabilities();
10511
10596
  });
10512
10597
 
10513
10598
  // apps/cli/src/components/chat/chat-message-error-part.tsx
@@ -10850,33 +10935,6 @@ var init_chat_message_text_part = __esm(() => {
10850
10935
  markdownSyntaxStyle = codeSyntaxStyle;
10851
10936
  });
10852
10937
 
10853
- // apps/cli/src/ui/cli-theme-capabilities.ts
10854
- function isUtf8Locale(env) {
10855
- const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
10856
- if (!locale) {
10857
- return true;
10858
- }
10859
- return /utf-?8/i.test(locale);
10860
- }
10861
- function detectTerminalCapabilities(env = process.env) {
10862
- const noColor = Boolean(env.NO_COLOR);
10863
- const dumbTerm = env.TERM === "dumb";
10864
- return {
10865
- color: !noColor && !dumbTerm,
10866
- unicode: isUtf8Locale(env),
10867
- reducedMotion: Boolean(env.NO_COLOR || env.LIGHTCODE_REDUCED_MOTION)
10868
- };
10869
- }
10870
- function resolveGlyphs(caps) {
10871
- return caps.unicode ? glyphs.unicode : glyphs.ascii;
10872
- }
10873
- var terminalCapabilities, activeGlyphs;
10874
- var init_cli_theme_capabilities = __esm(() => {
10875
- init_cli_theme();
10876
- terminalCapabilities = detectTerminalCapabilities();
10877
- activeGlyphs = resolveGlyphs(terminalCapabilities);
10878
- });
10879
-
10880
10938
  // apps/cli/src/components/chat/chat-diff-card.tsx
10881
10939
  import { jsxDEV as jsxDEV11 } from "@opentui/react/jsx-dev-runtime";
10882
10940
  function collapseDiff(diff, maxLines) {
@@ -13404,7 +13462,6 @@ function PermissionModeSelector({
13404
13462
  onClose
13405
13463
  }) {
13406
13464
  const [selectedIndex, setSelectedIndex] = useState11(0);
13407
- const [keyboardActive, setKeyboardActive] = useState11(false);
13408
13465
  useEffect7(() => {
13409
13466
  if (currentMode) {
13410
13467
  const currentIndex = permissionModes.findIndex((m) => m.mode === currentMode);
@@ -13421,17 +13478,21 @@ function PermissionModeSelector({
13421
13478
  onClose();
13422
13479
  return;
13423
13480
  }
13481
+ if (keyName === "tab" || keyEvent.sequence === "\t") {
13482
+ keyEvent.preventDefault();
13483
+ keyEvent.stopPropagation();
13484
+ setSelectedIndex((current) => keyEvent.shift ? (current - 1 + permissionModes.length) % permissionModes.length : (current + 1) % permissionModes.length);
13485
+ return;
13486
+ }
13424
13487
  if (isDownKey(keyName)) {
13425
13488
  keyEvent.preventDefault();
13426
13489
  keyEvent.stopPropagation();
13427
- setKeyboardActive(true);
13428
13490
  setSelectedIndex((current) => Math.min(current + 1, permissionModes.length - 1));
13429
13491
  return;
13430
13492
  }
13431
13493
  if (isUpKey(keyName)) {
13432
13494
  keyEvent.preventDefault();
13433
13495
  keyEvent.stopPropagation();
13434
- setKeyboardActive(true);
13435
13496
  setSelectedIndex((current) => Math.max(current - 1, 0));
13436
13497
  return;
13437
13498
  }
@@ -13467,7 +13528,7 @@ function PermissionModeSelector({
13467
13528
  }, undefined, false, undefined, this),
13468
13529
  /* @__PURE__ */ jsxDEV21("text", {
13469
13530
  fg: cliTheme.overlay.footerText,
13470
- children: "\u2191/\u2193 select | Enter confirm | Esc cancel"
13531
+ children: "Tab/\u2191/\u2193 select \xB7 Enter confirm \xB7 Esc cancel"
13471
13532
  }, undefined, false, undefined, this)
13472
13533
  ]
13473
13534
  }, undefined, true, undefined, this),
@@ -13476,16 +13537,13 @@ function PermissionModeSelector({
13476
13537
  paddingY: 1,
13477
13538
  children: permissionModes.map((modeOption, index) => {
13478
13539
  const isSelected = index === selectedIndex;
13479
- const rowColors = getOverlayRowColors(isSelected && keyboardActive);
13540
+ const rowColors = getOverlayRowColors(isSelected);
13480
13541
  return /* @__PURE__ */ jsxDEV21("box", {
13481
13542
  width: "100%",
13482
13543
  flexDirection: "column",
13483
13544
  paddingX: 2,
13484
13545
  paddingY: 1,
13485
13546
  backgroundColor: rowColors.backgroundColor,
13486
- borderStyle: "single",
13487
- border: isSelected ? ["left"] : undefined,
13488
- borderColor: isSelected ? cliTheme.accent.primary : cliTheme.overlay.border,
13489
13547
  children: [
13490
13548
  /* @__PURE__ */ jsxDEV21("box", {
13491
13549
  width: "100%",
@@ -13496,7 +13554,7 @@ function PermissionModeSelector({
13496
13554
  width: 3,
13497
13555
  fg: isSelected ? cliTheme.accent.primary : cliTheme.text.muted,
13498
13556
  attributes: TextAttributes3.BOLD,
13499
- children: isSelected ? "> " : " "
13557
+ children: isSelected ? `${activeGlyphs.roleUser} ` : " "
13500
13558
  }, undefined, false, undefined, this),
13501
13559
  /* @__PURE__ */ jsxDEV21("text", {
13502
13560
  fg: isSelected ? cliTheme.overlay.selectedRowText : cliTheme.text.primary,
@@ -13524,7 +13582,8 @@ function PermissionModeSelector({
13524
13582
  fg: modeOption.mode === "danger-full-access" ? cliTheme.semantic.error : cliTheme.text.muted,
13525
13583
  attributes: TextAttributes3.DIM,
13526
13584
  children: [
13527
- "\u2022 ",
13585
+ activeGlyphs.bullet,
13586
+ " ",
13528
13587
  risk
13529
13588
  ]
13530
13589
  }, risk, true, undefined, this))
@@ -13563,6 +13622,7 @@ function PermissionModeSelector({
13563
13622
  var permissionModes;
13564
13623
  var init_permission_mode_selector = __esm(() => {
13565
13624
  init_cli_theme();
13625
+ init_cli_theme_capabilities();
13566
13626
  permissionModes = [
13567
13627
  {
13568
13628
  mode: "read-only",
@@ -13678,6 +13738,16 @@ function fuzzyScore(query, candidate) {
13678
13738
  previousMatchIndex = foundIndex;
13679
13739
  candidateIndex = foundIndex + 1;
13680
13740
  }
13741
+ const substringIndex = normalizedCandidate.indexOf(normalizedQuery);
13742
+ if (substringIndex !== -1) {
13743
+ score += 8 + normalizedQuery.length * 2;
13744
+ if (substringIndex === 0 || BOUNDARY_CHARS.has(normalizedCandidate[substringIndex - 1])) {
13745
+ score += 4;
13746
+ }
13747
+ if (substringIndex > normalizedCandidate.lastIndexOf("/")) {
13748
+ score += 4;
13749
+ }
13750
+ }
13681
13751
  return score - candidate.length / 100;
13682
13752
  }
13683
13753
  function fuzzyFilter(query, candidates, limit = 6) {
@@ -13863,8 +13933,11 @@ function ChatTextArea({
13863
13933
  if (mentionMatches.length === 0) {
13864
13934
  return;
13865
13935
  }
13866
- if (event.name === "tab") {
13936
+ if (event.name === "tab" || isEnterLike) {
13867
13937
  event.preventDefault();
13938
+ if (isEnterLike) {
13939
+ lastManualNewlineAt.current = Date.now();
13940
+ }
13868
13941
  acceptMention(mentionMatches[selectedMentionIndex]);
13869
13942
  } else if (event.name === "down") {
13870
13943
  event.preventDefault();
@@ -13939,11 +14012,11 @@ function ChatTextArea({
13939
14012
  fg: index === selectedMentionIndex ? cliTheme.overlay.selectedRowText : cliTheme.text.secondary,
13940
14013
  bg: index === selectedMentionIndex ? cliTheme.overlay.selectedRowBackground : undefined,
13941
14014
  attributes: index === selectedMentionIndex ? TextAttributes4.BOLD : TextAttributes4.NONE,
13942
- children: `${index === selectedMentionIndex ? "> " : " "}${mentionPath}`
14015
+ children: `${index === selectedMentionIndex ? `${activeGlyphs.roleUser} ` : " "}${mentionPath}`
13943
14016
  }, mentionPath, false, undefined, this)),
13944
14017
  /* @__PURE__ */ jsxDEV22("text", {
13945
14018
  ...typeRole("caption"),
13946
- children: "Tab attach | Up/Down select | Esc dismiss"
14019
+ children: "\u2191/\u2193 select \xB7 Enter attach \xB7 Esc dismiss"
13947
14020
  }, undefined, false, undefined, this)
13948
14021
  ]
13949
14022
  }, undefined, true, undefined, this) : null,
@@ -13990,6 +14063,7 @@ var trailingMentionPattern, textareaKeyBindings;
13990
14063
  var init_chat_text_area = __esm(() => {
13991
14064
  init_cli_theme();
13992
14065
  init_cli_theme();
14066
+ init_cli_theme_capabilities();
13993
14067
  init_clipboard_image();
13994
14068
  init_fuzzy_match();
13995
14069
  init_image_attachments();
@@ -14127,80 +14201,53 @@ function getApprovalDescription(approval) {
14127
14201
  function ChatToolApprovalCard({
14128
14202
  approvals,
14129
14203
  onResolve,
14130
- onResolveAll
14204
+ onResolveAll,
14205
+ hasDraftText = false
14131
14206
  }) {
14132
14207
  const [selectedIndex, setSelectedIndex] = useState13(0);
14133
- const [keyboardActive, setKeyboardActive] = useState13(false);
14134
14208
  const safeSelectedIndex = Math.min(selectedIndex, Math.max(approvals.length - 1, 0));
14135
14209
  useEffect9(() => {
14136
14210
  setSelectedIndex((currentIndex) => Math.min(currentIndex, Math.max(approvals.length - 1, 0)));
14137
14211
  }, [approvals.length]);
14138
14212
  useKeyboard3((keyEvent) => {
14139
- if (approvals.length === 0) {
14213
+ if (approvals.length === 0 || hasDraftText) {
14140
14214
  return;
14141
14215
  }
14142
14216
  const keyName = keyEvent.name.toLowerCase();
14143
14217
  if (isDownKey(keyName)) {
14144
14218
  keyEvent.preventDefault();
14145
14219
  keyEvent.stopPropagation();
14146
- if (!keyboardActive) {
14147
- setKeyboardActive(true);
14148
- setSelectedIndex(0);
14149
- return;
14150
- }
14151
- setKeyboardActive(true);
14152
14220
  setSelectedIndex((currentIndex) => Math.min(currentIndex + 1, approvals.length - 1));
14153
14221
  return;
14154
14222
  }
14155
14223
  if (isUpKey(keyName)) {
14156
14224
  keyEvent.preventDefault();
14157
14225
  keyEvent.stopPropagation();
14158
- if (!keyboardActive) {
14159
- setKeyboardActive(true);
14160
- setSelectedIndex(0);
14161
- return;
14162
- }
14163
- setKeyboardActive(true);
14164
14226
  setSelectedIndex((currentIndex) => Math.max(currentIndex - 1, 0));
14165
14227
  return;
14166
14228
  }
14167
- if (!keyboardActive) {
14168
- return;
14169
- }
14170
14229
  if (isEnterKey(keyName)) {
14171
14230
  keyEvent.preventDefault();
14172
14231
  keyEvent.stopPropagation();
14173
14232
  onResolve("approve", safeSelectedIndex);
14174
- return;
14175
- }
14176
- if (keyName === "d") {
14177
- keyEvent.preventDefault();
14178
- keyEvent.stopPropagation();
14179
- onResolve("deny", safeSelectedIndex);
14180
- return;
14181
- }
14182
- if (keyName === "a") {
14183
- keyEvent.preventDefault();
14184
- keyEvent.stopPropagation();
14185
- onResolveAll("approve");
14186
- return;
14187
- }
14188
- if (isEscapeKey(keyName)) {
14189
- keyEvent.preventDefault();
14190
- keyEvent.stopPropagation();
14191
- setKeyboardActive(false);
14192
14233
  }
14193
14234
  });
14194
14235
  if (approvals.length === 0) {
14195
14236
  return null;
14196
14237
  }
14238
+ const hint = hasDraftText ? "Finish your reply below \u2014 approve/deny commands work there" : [
14239
+ approvals.length > 1 ? "\u2191/\u2193 select" : null,
14240
+ "Enter approve",
14241
+ "type deny to reject",
14242
+ approvals.length > 1 ? "approve all / deny all" : null
14243
+ ].filter(Boolean).join(" \xB7 ");
14197
14244
  return /* @__PURE__ */ jsxDEV24("box", {
14198
14245
  width: "100%",
14199
14246
  flexDirection: "column",
14200
14247
  borderStyle: "single",
14201
- borderColor: keyboardActive ? cliTheme.accent.primary : cliTheme.semantic.warning,
14248
+ borderColor: cliTheme.semantic.warning,
14202
14249
  backgroundColor: cliTheme.surfaces.panel,
14203
- paddingX: 1,
14250
+ paddingX: 2,
14204
14251
  paddingY: 1,
14205
14252
  gap: 1,
14206
14253
  children: [
@@ -14212,11 +14259,14 @@ function ChatToolApprovalCard({
14212
14259
  /* @__PURE__ */ jsxDEV24("text", {
14213
14260
  fg: cliTheme.semantic.warning,
14214
14261
  attributes: typeRole("title").attributes,
14215
- children: "Tool Approval"
14216
- }, undefined, false, undefined, this),
14262
+ children: [
14263
+ activeGlyphs.toolApproval,
14264
+ " Tool access request"
14265
+ ]
14266
+ }, undefined, true, undefined, this),
14217
14267
  /* @__PURE__ */ jsxDEV24("text", {
14218
- fg: keyboardActive ? cliTheme.accent.primary : cliTheme.text.muted,
14219
- children: keyboardActive ? `keyboard focus - ${approvals.length} pending` : `${approvals.length} pending - press Down to focus`
14268
+ fg: cliTheme.text.muted,
14269
+ children: approvals.length === 1 ? "1 pending" : `${approvals.length} pending`
14220
14270
  }, undefined, false, undefined, this)
14221
14271
  ]
14222
14272
  }, undefined, true, undefined, this),
@@ -14224,7 +14274,8 @@ function ChatToolApprovalCard({
14224
14274
  width: "100%",
14225
14275
  flexDirection: "column",
14226
14276
  children: approvals.map((approval, index) => {
14227
- const rowColors = getOverlayRowColors(keyboardActive && index === safeSelectedIndex);
14277
+ const selected = index === safeSelectedIndex;
14278
+ const rowColors = getOverlayRowColors(selected);
14228
14279
  const target = truncateInline2(getApprovalTarget(approval));
14229
14280
  return /* @__PURE__ */ jsxDEV24("box", {
14230
14281
  width: "100%",
@@ -14234,46 +14285,51 @@ function ChatToolApprovalCard({
14234
14285
  children: [
14235
14286
  /* @__PURE__ */ jsxDEV24("text", {
14236
14287
  fg: rowColors.primaryTextColor,
14237
- attributes: keyboardActive && index === safeSelectedIndex ? TextAttributes6.BOLD : TextAttributes6.NONE,
14288
+ attributes: selected ? TextAttributes6.BOLD : TextAttributes6.NONE,
14238
14289
  children: [
14239
- keyboardActive && index === safeSelectedIndex ? "> " : " ",
14240
- index + 1,
14241
- ". ",
14242
- approval.toolName,
14243
- " ",
14244
- target
14290
+ /* @__PURE__ */ jsxDEV24("span", {
14291
+ fg: selected ? cliTheme.accent.primary : cliTheme.text.muted,
14292
+ children: [
14293
+ selected ? `${activeGlyphs.roleUser} ` : " ",
14294
+ index + 1,
14295
+ ".",
14296
+ " "
14297
+ ]
14298
+ }, undefined, true, undefined, this),
14299
+ /* @__PURE__ */ jsxDEV24("span", {
14300
+ children: approval.toolName
14301
+ }, undefined, false, undefined, this),
14302
+ /* @__PURE__ */ jsxDEV24("span", {
14303
+ fg: rowColors.secondaryTextColor,
14304
+ children: [
14305
+ " ",
14306
+ target
14307
+ ]
14308
+ }, undefined, true, undefined, this)
14245
14309
  ]
14246
14310
  }, undefined, true, undefined, this),
14247
14311
  /* @__PURE__ */ jsxDEV24("text", {
14248
14312
  fg: rowColors.secondaryTextColor,
14249
14313
  attributes: TextAttributes6.DIM,
14250
- children: getApprovalDescription(approval)
14251
- }, undefined, false, undefined, this)
14314
+ children: [
14315
+ " ",
14316
+ getApprovalDescription(approval)
14317
+ ]
14318
+ }, undefined, true, undefined, this)
14252
14319
  ]
14253
14320
  }, approval.toolCallId, true, undefined, this);
14254
14321
  })
14255
14322
  }, undefined, false, undefined, this),
14256
- /* @__PURE__ */ jsxDEV24("box", {
14257
- width: "100%",
14258
- flexDirection: "row",
14259
- justifyContent: "space-between",
14260
- children: [
14261
- /* @__PURE__ */ jsxDEV24("text", {
14262
- fg: cliTheme.text.muted,
14263
- attributes: TextAttributes6.DIM,
14264
- children: keyboardActive ? "Enter approve | d deny | a approve all | Esc release focus" : "Down focus card | or type approve/deny below"
14265
- }, undefined, false, undefined, this),
14266
- /* @__PURE__ */ jsxDEV24("text", {
14267
- fg: cliTheme.text.secondary,
14268
- children: "approve/deny also works in reply"
14269
- }, undefined, false, undefined, this)
14270
- ]
14271
- }, undefined, true, undefined, this)
14323
+ /* @__PURE__ */ jsxDEV24("text", {
14324
+ ...typeRole("caption"),
14325
+ children: hint
14326
+ }, undefined, false, undefined, this)
14272
14327
  ]
14273
14328
  }, undefined, true, undefined, this);
14274
14329
  }
14275
14330
  var init_chat_tool_approval_card = __esm(() => {
14276
14331
  init_cli_theme();
14332
+ init_cli_theme_capabilities();
14277
14333
  init_text_utils();
14278
14334
  init_cli_theme();
14279
14335
  });
@@ -14659,6 +14715,53 @@ var init_file_mentions = __esm(() => {
14659
14715
  };
14660
14716
  });
14661
14717
 
14718
+ // apps/cli/src/utils/workspace-files.ts
14719
+ import { readdir as readdir3 } from "fs/promises";
14720
+ import path18 from "path";
14721
+ async function listWorkspaceFiles(root, limit = 3000) {
14722
+ const files = [];
14723
+ const queue = [root];
14724
+ while (queue.length > 0 && files.length < limit) {
14725
+ const directory = queue.shift();
14726
+ let entries;
14727
+ try {
14728
+ entries = await readdir3(directory, { withFileTypes: true });
14729
+ } catch {
14730
+ continue;
14731
+ }
14732
+ entries.sort((left, right) => left.name.localeCompare(right.name));
14733
+ for (const entry of entries) {
14734
+ if (entry.name.startsWith(".") || ignoredDirectoryNames2.has(entry.name)) {
14735
+ continue;
14736
+ }
14737
+ const absolutePath = path18.join(directory, entry.name);
14738
+ if (entry.isDirectory()) {
14739
+ queue.push(absolutePath);
14740
+ } else if (entry.isFile()) {
14741
+ files.push(path18.relative(root, absolutePath).replaceAll("\\", "/"));
14742
+ if (files.length >= limit) {
14743
+ break;
14744
+ }
14745
+ }
14746
+ }
14747
+ }
14748
+ return files;
14749
+ }
14750
+ var ignoredDirectoryNames2;
14751
+ var init_workspace_files = __esm(() => {
14752
+ ignoredDirectoryNames2 = new Set([
14753
+ ".git",
14754
+ ".next",
14755
+ ".turbo",
14756
+ ".vercel",
14757
+ "coverage",
14758
+ "dist",
14759
+ "node_modules",
14760
+ "out",
14761
+ "target"
14762
+ ]);
14763
+ });
14764
+
14662
14765
  // apps/cli/src/screens/chat-screen.tsx
14663
14766
  import { useKeyboard as useKeyboard4, useRenderer, useTerminalDimensions } from "@opentui/react";
14664
14767
  import { useCallback as useCallback7, useEffect as useEffect13, useMemo as useMemo8, useRef as useRef7, useState as useState17 } from "react";
@@ -14737,11 +14840,13 @@ function ChatScreen() {
14737
14840
  const [contextState, setContextState] = useState17(null);
14738
14841
  const [actionNotice, setActionNotice] = useState17(null);
14739
14842
  const [permissionSelectorOpen, setPermissionSelectorOpen] = useState17(false);
14843
+ const [draftHasText, setDraftHasText] = useState17(false);
14740
14844
  const slashRoutes = getSlashMenuItems(slashMenuQuery, {
14741
- includeChatActions: true
14845
+ host: "chat"
14742
14846
  });
14743
14847
  const selectedSlashRouteIndex = Math.min(slashMenuSelected, Math.max(slashRoutes.length - 1, 0));
14744
14848
  const syncSlashMenuFromInput = useCallback7((text) => {
14849
+ setDraftHasText(text.trim().length > 0);
14745
14850
  const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
14746
14851
  if (!firstLine.startsWith("/")) {
14747
14852
  if (slashMenuOpen) {
@@ -14893,10 +14998,9 @@ function ChatScreen() {
14893
14998
  let cancelled = false;
14894
14999
  async function loadMentionCandidates() {
14895
15000
  try {
14896
- const { createWorkspaceContext: createWorkspaceContext2, executeGlobSearch: executeGlobSearch2 } = await Promise.resolve().then(() => (init_runtime17(), exports_runtime));
14897
- const output = await executeGlobSearch2({ pattern: "**/*", maxResults: 200 }, createWorkspaceContext2(process.cwd()));
15001
+ const files = await listWorkspaceFiles(process.cwd());
14898
15002
  if (!cancelled) {
14899
- setMentionCandidates(output.matches.filter((match) => match.type === "file").map((match) => match.path));
15003
+ setMentionCandidates(files);
14900
15004
  }
14901
15005
  } catch {}
14902
15006
  }
@@ -14961,29 +15065,29 @@ function ChatScreen() {
14961
15065
  const [activeFilePath, setActiveFilePath] = useState17(null);
14962
15066
  const openFileContent = useFileContent(activeFilePath, process.cwd(), fileReloadToken);
14963
15067
  const isEditingFile = panelMode === "edit";
14964
- const openFileTab = useCallback7((path18) => {
14965
- setOpenFiles((current) => current.includes(path18) ? current : [...current, path18].slice(-MAX_OPEN_TABS));
14966
- setActiveFilePath(path18);
15068
+ const openFileTab = useCallback7((path19) => {
15069
+ setOpenFiles((current) => current.includes(path19) ? current : [...current, path19].slice(-MAX_OPEN_TABS));
15070
+ setActiveFilePath(path19);
14967
15071
  }, []);
14968
15072
  useEffect13(() => {
14969
15073
  if (fileTree.openFilePath) {
14970
15074
  openFileTab(fileTree.openFilePath);
14971
15075
  }
14972
15076
  }, [fileTree.openFilePath, openFileTab]);
14973
- const selectFileTab = useCallback7((path18) => {
15077
+ const selectFileTab = useCallback7((path19) => {
14974
15078
  setPanelMode("browse");
14975
- setActiveFilePath(path18);
15079
+ setActiveFilePath(path19);
14976
15080
  }, []);
14977
- const closeFileTab = useCallback7((path18) => {
15081
+ const closeFileTab = useCallback7((path19) => {
14978
15082
  setPanelMode("browse");
14979
15083
  setOpenFiles((current) => {
14980
- const index = current.indexOf(path18);
15084
+ const index = current.indexOf(path19);
14981
15085
  if (index === -1) {
14982
15086
  return current;
14983
15087
  }
14984
- const next = current.filter((file) => file !== path18);
15088
+ const next = current.filter((file) => file !== path19);
14985
15089
  setActiveFilePath((active) => {
14986
- if (active !== path18) {
15090
+ if (active !== path19) {
14987
15091
  return active;
14988
15092
  }
14989
15093
  return next[index] ?? next[index - 1] ?? null;
@@ -15066,13 +15170,13 @@ function ChatScreen() {
15066
15170
  setPanelFocused(false);
15067
15171
  }
15068
15172
  }, [panelVisible]);
15069
- const selectChangedPath = useCallback7((path18) => {
15070
- setSelectedChangedPath(path18);
15173
+ const selectChangedPath = useCallback7((path19) => {
15174
+ setSelectedChangedPath(path19);
15071
15175
  setChangedSelectionPinned(true);
15072
15176
  }, []);
15073
- const openChangedFile = useCallback7((path18) => {
15074
- selectChangedPath(path18);
15075
- openFileTab(path18);
15177
+ const openChangedFile = useCallback7((path19) => {
15178
+ selectChangedPath(path19);
15179
+ openFileTab(path19);
15076
15180
  }, [selectChangedPath, openFileTab]);
15077
15181
  const moveChangedSelection = useCallback7((delta) => {
15078
15182
  if (changesFiles.length === 0) {
@@ -15445,7 +15549,7 @@ function ChatScreen() {
15445
15549
  }
15446
15550
  const isCtrlY = keyName === "y" && keyEvent.ctrl && !keyEvent.meta && !keyEvent.super && !keyEvent.hyper && !keyEvent.shift;
15447
15551
  if (isCtrlY) {
15448
- if (!isSessionIdValid || hasBlockingPopup || slashMenuOpen || copyableMessages.length === 0) {
15552
+ if (!isSessionIdValid || hasBlockingPopup || slashMenuOpen || permissionSelectorOpen || copyableMessages.length === 0) {
15449
15553
  return;
15450
15554
  }
15451
15555
  keyEvent.preventDefault();
@@ -15459,7 +15563,7 @@ function ChatScreen() {
15459
15563
  if (!isPlainTab && !isCtrlT) {
15460
15564
  return;
15461
15565
  }
15462
- if (!isSessionIdValid || hasBlockingPopup || slashMenuOpen) {
15566
+ if (!isSessionIdValid || hasBlockingPopup || slashMenuOpen || permissionSelectorOpen) {
15463
15567
  return;
15464
15568
  }
15465
15569
  keyEvent.preventDefault();
@@ -15487,8 +15591,8 @@ function ChatScreen() {
15487
15591
  }, undefined, false, undefined, this) : null,
15488
15592
  inputArea: /* @__PURE__ */ jsxDEV27(ChatTextArea, {
15489
15593
  placeholder: isLoading ? "Waiting for response..." : "Reply... (@ to attach files)",
15490
- focused: canTypeInChat && !copyModeOpen && !panelFocused && !isEditingFile,
15491
- disabled: !canTypeInChat || copyModeOpen || panelFocused || isEditingFile,
15594
+ focused: canTypeInChat && !copyModeOpen && !permissionSelectorOpen && !panelFocused && !isEditingFile,
15595
+ disabled: !canTypeInChat || copyModeOpen || permissionSelectorOpen || panelFocused || isEditingFile,
15492
15596
  slashMenuOpen,
15493
15597
  mentionCandidates,
15494
15598
  onTextChange: syncSlashMenuFromInput,
@@ -15531,7 +15635,7 @@ function ChatScreen() {
15531
15635
  wrapMode: "none",
15532
15636
  truncate: true,
15533
15637
  fg: cliTheme.text.muted,
15534
- children: "Use approval card or approve/deny commands"
15638
+ children: "Enter approve \xB7 type deny to reject \xB7 or reply"
15535
15639
  }, undefined, false, undefined, this) : hasBlockingPopup ? /* @__PURE__ */ jsxDEV27("text", {
15536
15640
  wrapMode: "none",
15537
15641
  truncate: true,
@@ -15541,7 +15645,7 @@ function ChatScreen() {
15541
15645
  wrapMode: "none",
15542
15646
  truncate: true,
15543
15647
  fg: cliTheme.text.muted,
15544
- children: "Choose a page or press Esc"
15648
+ children: "\u2191/\u2193 select \xB7 Enter open \xB7 Esc close"
15545
15649
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV27("text", {
15546
15650
  wrapMode: "none",
15547
15651
  truncate: true,
@@ -15651,7 +15755,8 @@ function ChatScreen() {
15651
15755
  pendingApprovals.length > 0 ? /* @__PURE__ */ jsxDEV27(ChatToolApprovalCard, {
15652
15756
  approvals: pendingApprovals,
15653
15757
  onResolve: resolveToolApproval,
15654
- onResolveAll: resolveAllToolApprovals
15758
+ onResolveAll: resolveAllToolApprovals,
15759
+ hasDraftText: draftHasText
15655
15760
  }, undefined, false, undefined, this) : null,
15656
15761
  todos.length > 0 ? /* @__PURE__ */ jsxDEV27(ChatTodoStatusCard, {
15657
15762
  todos
@@ -15881,6 +15986,7 @@ var init_chat_screen = __esm(() => {
15881
15986
  init_file_mentions();
15882
15987
  init_markdown_code();
15883
15988
  init_text_utils();
15989
+ init_workspace_files();
15884
15990
  init_badge();
15885
15991
  planConfirmationAcceptKeywords = [
15886
15992
  "yes",
@@ -16550,7 +16656,7 @@ var init_home_ascii_art = __esm(() => {
16550
16656
 
16551
16657
  // apps/cli/src/components/home-text-area.tsx
16552
16658
  import { useKeyboard as useKeyboard6 } from "@opentui/react";
16553
- import { useState as useState19 } from "react";
16659
+ import { useEffect as useEffect15, useState as useState19 } from "react";
16554
16660
  import { useNavigate as useNavigate2 } from "react-router";
16555
16661
  import { jsxDEV as jsxDEV30 } from "@opentui/react/jsx-dev-runtime";
16556
16662
  function HomeTextArea() {
@@ -16562,14 +16668,27 @@ function HomeTextArea() {
16562
16668
  slashMenuSelected,
16563
16669
  setSlashMenuSelected,
16564
16670
  openSlashMenu,
16565
- closeSlashMenu
16671
+ closeSlashMenu,
16672
+ requestedChatActionId,
16673
+ clearRequestedChatAction
16566
16674
  } = useAppState();
16567
16675
  const [sessionCreateError, setSessionCreateError] = useState19(null);
16568
16676
  const [isCreatingSession, setIsCreatingSession] = useState19(false);
16569
16677
  const [mode, setMode] = useState19(defaultCodingAgentMode);
16570
- const slashRoutes = getSlashPageRoutes(slashMenuQuery);
16678
+ const [permissionMode, setPermissionMode] = useState19(undefined);
16679
+ const [permissionSelectorOpen, setPermissionSelectorOpen] = useState19(false);
16680
+ const slashRoutes = getSlashMenuItems(slashMenuQuery, { host: "home" });
16571
16681
  const selectedIndex = Math.min(slashMenuSelected, Math.max(slashRoutes.length - 1, 0));
16572
16682
  const modeDefinition = codingAgentModes[mode];
16683
+ useEffect15(() => {
16684
+ if (!requestedChatActionId) {
16685
+ return;
16686
+ }
16687
+ clearRequestedChatAction();
16688
+ if (requestedChatActionId === "permission") {
16689
+ setPermissionSelectorOpen(true);
16690
+ }
16691
+ }, [clearRequestedChatAction, requestedChatActionId]);
16573
16692
  const syncSlashMenuFromInput = (text) => {
16574
16693
  const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
16575
16694
  if (!firstLine.startsWith("/")) {
@@ -16588,7 +16707,7 @@ function HomeTextArea() {
16588
16707
  const keyName = keyEvent.name.toLowerCase();
16589
16708
  const isPlainTab = (keyName === "tab" || keyEvent.sequence === "\t") && !keyEvent.ctrl && !keyEvent.meta && !keyEvent.super && !keyEvent.hyper && !keyEvent.shift;
16590
16709
  const isCtrlT = keyName === "t" && keyEvent.ctrl && !keyEvent.meta && !keyEvent.super && !keyEvent.hyper && !keyEvent.shift;
16591
- if (!isPlainTab && !isCtrlT || slashMenuOpen || isCreatingSession) {
16710
+ if (!isPlainTab && !isCtrlT || slashMenuOpen || permissionSelectorOpen || isCreatingSession) {
16592
16711
  return;
16593
16712
  }
16594
16713
  keyEvent.preventDefault();
@@ -16606,7 +16725,8 @@ function HomeTextArea() {
16606
16725
  const response = await client.sessions.$post({
16607
16726
  json: {
16608
16727
  cwd: process.cwd(),
16609
- mode
16728
+ mode,
16729
+ permissionMode
16610
16730
  }
16611
16731
  });
16612
16732
  if (!response.ok) {
@@ -16621,7 +16741,8 @@ function HomeTextArea() {
16621
16741
  state: {
16622
16742
  input: text,
16623
16743
  skipHistoryLoad: true,
16624
- mode
16744
+ mode,
16745
+ permissionMode
16625
16746
  }
16626
16747
  });
16627
16748
  } catch (sessionCreateFailure) {
@@ -16642,11 +16763,18 @@ function HomeTextArea() {
16642
16763
  allowEmpty: true,
16643
16764
  trimOnSubmit: false,
16644
16765
  placeholder: 'Ask anything... "What is the tech stack of this project?"',
16645
- focused: !isCreatingSession,
16646
- disabled: isCreatingSession,
16766
+ focused: !isCreatingSession && !permissionSelectorOpen,
16767
+ disabled: isCreatingSession || permissionSelectorOpen,
16647
16768
  slashMenuOpen,
16648
16769
  onTextChange: syncSlashMenuFromInput,
16649
- beforeInput: slashMenuOpen ? /* @__PURE__ */ jsxDEV30(SlashPageMenu, {
16770
+ beforeInput: permissionSelectorOpen ? /* @__PURE__ */ jsxDEV30(PermissionModeSelector, {
16771
+ currentMode: permissionMode,
16772
+ onSelect: (newMode) => {
16773
+ setPermissionMode(newMode);
16774
+ setPermissionSelectorOpen(false);
16775
+ },
16776
+ onClose: () => setPermissionSelectorOpen(false)
16777
+ }, undefined, false, undefined, this) : slashMenuOpen ? /* @__PURE__ */ jsxDEV30(SlashPageMenu, {
16650
16778
  query: slashMenuQuery,
16651
16779
  selectedIndex,
16652
16780
  routes: slashRoutes
@@ -16671,7 +16799,14 @@ function HomeTextArea() {
16671
16799
  /* @__PURE__ */ jsxDEV30("span", {
16672
16800
  fg: cliTheme.text.muted,
16673
16801
  children: " mode"
16674
- }, undefined, false, undefined, this)
16802
+ }, undefined, false, undefined, this),
16803
+ permissionMode ? /* @__PURE__ */ jsxDEV30("span", {
16804
+ fg: permissionMode === "danger-full-access" ? cliTheme.semantic.warning : cliTheme.text.muted,
16805
+ children: [
16806
+ " \xB7 ",
16807
+ permissionMode
16808
+ ]
16809
+ }, undefined, true, undefined, this) : null
16675
16810
  ]
16676
16811
  }, undefined, true, undefined, this)
16677
16812
  ]
@@ -16689,21 +16824,22 @@ function HomeTextArea() {
16689
16824
  }
16690
16825
  var init_home_text_area = __esm(() => {
16691
16826
  init_src2();
16827
+ init_slash_menu_items();
16692
16828
  init_slash_page_menu();
16693
16829
  init_client();
16694
16830
  init_chat_text_area();
16695
- init_route_registry();
16831
+ init_permission_mode_selector();
16696
16832
  init_app_state();
16697
16833
  init_cli_theme();
16698
16834
  init_text_utils();
16699
16835
  });
16700
16836
 
16701
16837
  // apps/cli/src/components/server-status.tsx
16702
- import { useEffect as useEffect15, useState as useState20 } from "react";
16838
+ import { useEffect as useEffect16, useState as useState20 } from "react";
16703
16839
  import { jsxDEV as jsxDEV31 } from "@opentui/react/jsx-dev-runtime";
16704
16840
  function ServerStatus() {
16705
16841
  const [status, setStatus] = useState20("checking");
16706
- useEffect15(() => {
16842
+ useEffect16(() => {
16707
16843
  let cancelled = false;
16708
16844
  async function checkServer2() {
16709
16845
  try {
@@ -16764,7 +16900,7 @@ var init_home_screen = __esm(() => {
16764
16900
 
16765
16901
  // apps/cli/src/screens/model-screen.tsx
16766
16902
  import { useKeyboard as useKeyboard7 } from "@opentui/react";
16767
- import { useCallback as useCallback9, useEffect as useEffect16, useState as useState21 } from "react";
16903
+ import { useCallback as useCallback9, useEffect as useEffect17, useState as useState21 } from "react";
16768
16904
  import { jsxDEV as jsxDEV33 } from "@opentui/react/jsx-dev-runtime";
16769
16905
  function ModelScreen() {
16770
16906
  const [isLoading, setIsLoading] = useState21(true);
@@ -16786,7 +16922,7 @@ function ModelScreen() {
16786
16922
  setIsLoading(false);
16787
16923
  }
16788
16924
  }, []);
16789
- useEffect16(() => {
16925
+ useEffect17(() => {
16790
16926
  loadModelConfig();
16791
16927
  }, [loadModelConfig]);
16792
16928
  useKeyboard7((keyEvent) => {
@@ -16920,7 +17056,7 @@ var init_model_screen = __esm(() => {
16920
17056
  // apps/cli/src/components/chat/model-selector.tsx
16921
17057
  import { TextAttributes as TextAttributes8 } from "@opentui/core";
16922
17058
  import { useKeyboard as useKeyboard8 } from "@opentui/react";
16923
- import { useEffect as useEffect17, useMemo as useMemo10, useState as useState22 } from "react";
17059
+ import { useEffect as useEffect18, useMemo as useMemo10, useState as useState22 } from "react";
16924
17060
  import { jsxDEV as jsxDEV34 } from "@opentui/react/jsx-dev-runtime";
16925
17061
  function formatContextLength(contextLength) {
16926
17062
  if (!contextLength) {
@@ -16936,7 +17072,7 @@ function ModelSelector({ onClose, notify }) {
16936
17072
  const [isApplying, setIsApplying] = useState22(false);
16937
17073
  const [filter, setFilter] = useState22("");
16938
17074
  const [selectedIndex, setSelectedIndex] = useState22(0);
16939
- useEffect17(() => {
17075
+ useEffect18(() => {
16940
17076
  let cancelled = false;
16941
17077
  async function loadModels() {
16942
17078
  try {
@@ -16978,7 +17114,7 @@ function ModelSelector({ onClose, notify }) {
16978
17114
  }
16979
17115
  return models.filter((model) => model.id.toLowerCase().includes(normalized) || model.name.toLowerCase().includes(normalized));
16980
17116
  }, [filter, models]);
16981
- useEffect17(() => {
17117
+ useEffect18(() => {
16982
17118
  setSelectedIndex((current) => Math.min(current, Math.max(filteredModels.length - 1, 0)));
16983
17119
  }, [filteredModels.length]);
16984
17120
  const applyModel = async (model) => {
@@ -17069,7 +17205,7 @@ function ModelSelector({ onClose, notify }) {
17069
17205
  }, undefined, false, undefined, this),
17070
17206
  /* @__PURE__ */ jsxDEV34("text", {
17071
17207
  fg: cliTheme.overlay.footerText,
17072
- children: "type to filter | \u2191/\u2193 select | Enter confirm | Esc cancel"
17208
+ children: "type to filter \xB7 \u2191/\u2193 select \xB7 Enter confirm \xB7 Esc cancel"
17073
17209
  }, undefined, false, undefined, this)
17074
17210
  ]
17075
17211
  }, undefined, true, undefined, this),
@@ -17121,7 +17257,7 @@ function ModelSelector({ onClose, notify }) {
17121
17257
  width: 2,
17122
17258
  fg: isSelected ? cliTheme.accent.primary : cliTheme.text.muted,
17123
17259
  attributes: TextAttributes8.BOLD,
17124
- children: isSelected ? ">" : " "
17260
+ children: isSelected ? activeGlyphs.roleUser : " "
17125
17261
  }, undefined, false, undefined, this),
17126
17262
  /* @__PURE__ */ jsxDEV34("text", {
17127
17263
  fg: isSelected ? cliTheme.overlay.selectedRowText : cliTheme.text.primary,
@@ -17131,7 +17267,7 @@ function ModelSelector({ onClose, notify }) {
17131
17267
  /* @__PURE__ */ jsxDEV34("text", {
17132
17268
  fg: cliTheme.text.muted,
17133
17269
  marginLeft: 2,
17134
- children: `${formatContextLength(model.contextLength)}${model.supportsTools ? " | tools" : ""}${model.supportsReasoning ? " | reasoning" : ""}`
17270
+ children: `${formatContextLength(model.contextLength)}${model.supportsTools ? " \xB7 tools" : ""}${model.supportsReasoning ? " \xB7 reasoning" : ""}`
17135
17271
  }, undefined, false, undefined, this),
17136
17272
  model.id === currentModel && /* @__PURE__ */ jsxDEV34("text", {
17137
17273
  fg: cliTheme.accent.primary,
@@ -17146,7 +17282,7 @@ function ModelSelector({ onClose, notify }) {
17146
17282
  marginTop: 1,
17147
17283
  children: /* @__PURE__ */ jsxDEV34("text", {
17148
17284
  fg: cliTheme.overlay.footerText,
17149
- children: `${filteredModels.length} model${filteredModels.length === 1 ? "" : "s"}${isApplying ? " | switching..." : ""}`
17285
+ children: `${filteredModels.length} model${filteredModels.length === 1 ? "" : "s"}${isApplying ? " \xB7 switching..." : ""}`
17150
17286
  }, undefined, false, undefined, this)
17151
17287
  }, undefined, false, undefined, this)
17152
17288
  ]
@@ -17158,6 +17294,7 @@ var maxVisibleRows = 8;
17158
17294
  var init_model_selector = __esm(() => {
17159
17295
  init_client();
17160
17296
  init_cli_theme();
17297
+ init_cli_theme_capabilities();
17161
17298
  });
17162
17299
 
17163
17300
  // apps/cli/src/screens/model-select-screen.tsx
@@ -17199,13 +17336,13 @@ var init_model_select_screen = __esm(() => {
17199
17336
  import { useKeyboard as useKeyboard9 } from "@opentui/react";
17200
17337
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
17201
17338
  import os5 from "os";
17202
- import path18 from "path";
17339
+ import path19 from "path";
17203
17340
  import { useCallback as useCallback10, useState as useState24 } from "react";
17204
17341
  import { useNavigate as useNavigate4 } from "react-router";
17205
17342
  import { jsxDEV as jsxDEV36 } from "@opentui/react/jsx-dev-runtime";
17206
17343
  function getUserSettingsPath() {
17207
17344
  const configured = process.env.LIGHTCODE_USER_CONFIG?.trim();
17208
- return configured ?? path18.join(os5.homedir(), ".lightcode", "settings.json");
17345
+ return configured ?? path19.join(os5.homedir(), ".lightcode", "settings.json");
17209
17346
  }
17210
17347
  function mergeUserSettings(update) {
17211
17348
  const settingsPath = getUserSettingsPath();
@@ -17218,7 +17355,7 @@ function mergeUserSettings(update) {
17218
17355
  }
17219
17356
  } catch {}
17220
17357
  }
17221
- mkdirSync5(path18.dirname(settingsPath), { recursive: true });
17358
+ mkdirSync5(path19.dirname(settingsPath), { recursive: true });
17222
17359
  writeFileSync3(settingsPath, `${JSON.stringify({ ...existing, ...update }, null, 2)}
17223
17360
  `, "utf8");
17224
17361
  return settingsPath;
@@ -17360,7 +17497,7 @@ function OnboardingScreen() {
17360
17497
  children: [
17361
17498
  /* @__PURE__ */ jsxDEV36("text", {
17362
17499
  fg: index === selectedIndex ? cliTheme.accent.primary : cliTheme.text.muted,
17363
- children: index === selectedIndex ? ">" : " "
17500
+ children: index === selectedIndex ? activeGlyphs.roleUser : " "
17364
17501
  }, undefined, false, undefined, this),
17365
17502
  /* @__PURE__ */ jsxDEV36("text", {
17366
17503
  fg: index === selectedIndex ? cliTheme.overlay.selectedRowText : cliTheme.text.primary,
@@ -17376,7 +17513,7 @@ function OnboardingScreen() {
17376
17513
  /* @__PURE__ */ jsxDEV36("text", {
17377
17514
  fg: cliTheme.text.muted,
17378
17515
  attributes: TextAttributes.DIM,
17379
- children: "Up/Down select - Enter continue"
17516
+ children: "\u2191/\u2193 select \xB7 Enter continue"
17380
17517
  }, undefined, false, undefined, this)
17381
17518
  ]
17382
17519
  }, undefined, true, undefined, this) : null,
@@ -17543,8 +17680,8 @@ var init_onboarding_screen = __esm(() => {
17543
17680
  });
17544
17681
 
17545
17682
  // apps/cli/src/screens/session-list-screen.tsx
17546
- import { useKeyboard as useKeyboard10 } from "@opentui/react";
17547
- import { useCallback as useCallback11, useEffect as useEffect18, useMemo as useMemo11, useState as useState25 } from "react";
17683
+ import { useKeyboard as useKeyboard10, useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
17684
+ import { useCallback as useCallback11, useEffect as useEffect19, useMemo as useMemo11, useState as useState25 } from "react";
17548
17685
  import { useNavigate as useNavigate5 } from "react-router";
17549
17686
  import { jsxDEV as jsxDEV37 } from "@opentui/react/jsx-dev-runtime";
17550
17687
  function getSessionLabel(session) {
@@ -17572,6 +17709,12 @@ function SessionListScreen() {
17572
17709
  const safeSelectedIndex = Math.min(selectedIndex, Math.max(visibleSessions.length - 1, 0));
17573
17710
  const selectedSession = visibleSessions[safeSelectedIndex] ?? null;
17574
17711
  const inputActive = filterOpen || renamingSessionId !== null;
17712
+ const { height: terminalHeight } = useTerminalDimensions2();
17713
+ const maxVisibleRows2 = Math.max(5, terminalHeight - 12);
17714
+ const windowStart = Math.max(0, Math.min(safeSelectedIndex - Math.floor(maxVisibleRows2 / 2), visibleSessions.length - maxVisibleRows2));
17715
+ const windowedSessions = visibleSessions.slice(windowStart, windowStart + maxVisibleRows2);
17716
+ const hiddenAbove = windowStart;
17717
+ const hiddenBelow = Math.max(0, visibleSessions.length - (windowStart + windowedSessions.length));
17575
17718
  const applyLoadedSessions = useCallback11((loadedSessions, droppedCount) => {
17576
17719
  setSessions(loadedSessions);
17577
17720
  setSelectedIndex((currentIndex) => Math.min(currentIndex, Math.max(loadedSessions.length - 1, 0)));
@@ -17724,7 +17867,7 @@ function SessionListScreen() {
17724
17867
  setActionMessage(getErrorMessage(error, "Unable to rename session."));
17725
17868
  }
17726
17869
  }, [loadSessions]);
17727
- useEffect18(() => {
17870
+ useEffect19(() => {
17728
17871
  loadSessions();
17729
17872
  }, [loadSessions]);
17730
17873
  useKeyboard10((keyEvent) => {
@@ -17811,9 +17954,9 @@ function SessionListScreen() {
17811
17954
  });
17812
17955
  const footerText = useMemo11(() => {
17813
17956
  if (sessions.length === 0) {
17814
- return `u refresh | l resume latest | ${BACK_SHORTCUT_LABEL} back`;
17957
+ return `u refresh \xB7 l resume latest \xB7 ${BACK_SHORTCUT_LABEL} back`;
17815
17958
  }
17816
- return "Enter resume | r rename | f fork | / filter | e export | d delete | u refresh";
17959
+ return "Enter resume \xB7 r rename \xB7 f fork \xB7 / filter \xB7 e export \xB7 d delete \xB7 u refresh";
17817
17960
  }, [sessions.length]);
17818
17961
  return /* @__PURE__ */ jsxDEV37("box", {
17819
17962
  width: "100%",
@@ -17847,7 +17990,7 @@ function SessionListScreen() {
17847
17990
  paddingX: 1,
17848
17991
  children: /* @__PURE__ */ jsxDEV37("text", {
17849
17992
  fg: cliTheme.accent.primary,
17850
- children: `Filter: "${filterQuery}" (${visibleSessions.length} match${visibleSessions.length === 1 ? "" : "es"}) - Esc clears`
17993
+ children: `Filter: "${filterQuery}" (${visibleSessions.length} match${visibleSessions.length === 1 ? "" : "es"}) \xB7 Esc clears`
17851
17994
  }, undefined, false, undefined, this)
17852
17995
  }, undefined, false, undefined, this) : null,
17853
17996
  /* @__PURE__ */ jsxDEV37("box", {
@@ -17864,7 +18007,7 @@ function SessionListScreen() {
17864
18007
  /* @__PURE__ */ jsxDEV37("text", {
17865
18008
  fg: cliTheme.accent.primary,
17866
18009
  attributes: TextAttributes.BOLD,
17867
- children: sessions.length === 0 ? "\u25C6 No sessions yet" : "No matches"
18010
+ children: sessions.length === 0 ? `${activeGlyphs.roleAssistant} No sessions yet` : "No matches"
17868
18011
  }, undefined, false, undefined, this),
17869
18012
  /* @__PURE__ */ jsxDEV37("text", {
17870
18013
  fg: cliTheme.text.muted,
@@ -17872,43 +18015,79 @@ function SessionListScreen() {
17872
18015
  }, undefined, false, undefined, this)
17873
18016
  ]
17874
18017
  }, undefined, true, undefined, this) : null,
17875
- visibleSessions.map((session, index) => {
18018
+ hiddenAbove > 0 ? /* @__PURE__ */ jsxDEV37("box", {
18019
+ paddingX: 1,
18020
+ flexShrink: 0,
18021
+ children: /* @__PURE__ */ jsxDEV37("text", {
18022
+ fg: cliTheme.text.muted,
18023
+ attributes: TextAttributes.DIM,
18024
+ children: `\u2191 ${hiddenAbove} more`
18025
+ }, undefined, false, undefined, this)
18026
+ }, undefined, false, undefined, this) : null,
18027
+ windowedSessions.map((session, windowIndex) => {
18028
+ const index = windowStart + windowIndex;
17876
18029
  const isSelected = index === safeSelectedIndex;
17877
18030
  const rowColors = getOverlayRowColors(isSelected);
17878
18031
  const selectedForDelete = pendingDeleteId === session.id;
17879
18032
  return /* @__PURE__ */ jsxDEV37("box", {
17880
18033
  flexDirection: "column",
17881
18034
  paddingX: 1,
18035
+ flexShrink: 0,
17882
18036
  backgroundColor: rowColors.backgroundColor,
17883
18037
  children: [
17884
18038
  /* @__PURE__ */ jsxDEV37("box", {
17885
18039
  flexDirection: "row",
17886
18040
  justifyContent: "space-between",
17887
18041
  children: [
18042
+ /* @__PURE__ */ jsxDEV37("box", {
18043
+ flexShrink: 1,
18044
+ height: 1,
18045
+ overflow: "hidden",
18046
+ children: /* @__PURE__ */ jsxDEV37("text", {
18047
+ wrapMode: "none",
18048
+ truncate: true,
18049
+ fg: selectedForDelete ? cliTheme.semantic.error : rowColors.primaryTextColor,
18050
+ attributes: TextAttributes.BOLD,
18051
+ children: [
18052
+ isSelected ? `${activeGlyphs.roleUser} ` : " ",
18053
+ index + 1,
18054
+ ". ",
18055
+ truncateInline2(getSessionLabel(session), 64)
18056
+ ]
18057
+ }, undefined, true, undefined, this)
18058
+ }, undefined, false, undefined, this),
17888
18059
  /* @__PURE__ */ jsxDEV37("text", {
17889
- fg: selectedForDelete ? cliTheme.semantic.error : rowColors.primaryTextColor,
17890
- attributes: TextAttributes.BOLD,
17891
- children: [
17892
- index + 1,
17893
- ". ",
17894
- truncateInline2(getSessionLabel(session), 64)
17895
- ]
17896
- }, undefined, true, undefined, this),
17897
- /* @__PURE__ */ jsxDEV37("text", {
18060
+ wrapMode: "none",
18061
+ truncate: true,
18062
+ flexShrink: 0,
17898
18063
  fg: rowColors.secondaryTextColor,
17899
18064
  attributes: TextAttributes.DIM,
17900
- children: `${session.messageCount} msgs - ${session.mode} - ${formatDate2(session.updatedAt)}`
18065
+ children: `${session.messageCount} msgs \xB7 ${session.mode} \xB7 ${formatDate2(session.updatedAt)}`
17901
18066
  }, undefined, false, undefined, this)
17902
18067
  ]
17903
18068
  }, undefined, true, undefined, this),
17904
18069
  isSelected && session.latestUserPromptPreview ? /* @__PURE__ */ jsxDEV37("text", {
18070
+ wrapMode: "none",
18071
+ truncate: true,
17905
18072
  fg: rowColors.secondaryTextColor,
17906
18073
  attributes: TextAttributes.DIM,
17907
- children: truncateInline2(session.latestUserPromptPreview, 110)
17908
- }, undefined, false, undefined, this) : null
18074
+ children: [
18075
+ " ",
18076
+ truncateInline2(session.latestUserPromptPreview, 110)
18077
+ ]
18078
+ }, undefined, true, undefined, this) : null
17909
18079
  ]
17910
18080
  }, session.id, true, undefined, this);
17911
- })
18081
+ }),
18082
+ hiddenBelow > 0 ? /* @__PURE__ */ jsxDEV37("box", {
18083
+ paddingX: 1,
18084
+ flexShrink: 0,
18085
+ children: /* @__PURE__ */ jsxDEV37("text", {
18086
+ fg: cliTheme.text.muted,
18087
+ attributes: TextAttributes.DIM,
18088
+ children: `\u2193 ${hiddenBelow} more`
18089
+ }, undefined, false, undefined, this)
18090
+ }, undefined, false, undefined, this) : null
17912
18091
  ]
17913
18092
  }, undefined, true, undefined, this),
17914
18093
  actionMessage ? /* @__PURE__ */ jsxDEV37("box", {
@@ -17933,7 +18112,7 @@ function SessionListScreen() {
17933
18112
  children: /* @__PURE__ */ jsxDEV37("text", {
17934
18113
  fg: cliTheme.text.muted,
17935
18114
  attributes: TextAttributes.DIM,
17936
- children: inputActive ? "Enter confirm | Esc cancel" : footerText
18115
+ children: inputActive ? "Enter confirm \xB7 Esc cancel" : footerText
17937
18116
  }, undefined, false, undefined, this)
17938
18117
  }, undefined, false, undefined, this)
17939
18118
  ]
@@ -17946,15 +18125,16 @@ var init_session_list_screen = __esm(() => {
17946
18125
  init_keymap();
17947
18126
  init_export_session_markdown();
17948
18127
  init_chat_text_area();
18128
+ init_cli_theme_capabilities();
17949
18129
  init_cli_theme();
17950
18130
  init_text_utils();
17951
18131
  });
17952
18132
 
17953
18133
  // apps/cli/src/hooks/use-config-badge.ts
17954
- import { useEffect as useEffect19, useState as useState26 } from "react";
18134
+ import { useEffect as useEffect20, useState as useState26 } from "react";
17955
18135
  function useConfigBadge(refreshNonce = 0) {
17956
18136
  const [state, setState] = useState26({ status: "loading" });
17957
- useEffect19(() => {
18137
+ useEffect20(() => {
17958
18138
  let cancelled = false;
17959
18139
  async function loadConfig() {
17960
18140
  try {
@@ -18021,7 +18201,7 @@ __export(exports_app, {
18021
18201
  App: () => App
18022
18202
  });
18023
18203
  import { useKeyboard as useKeyboard11, useRenderer as useRenderer2 } from "@opentui/react";
18024
- import { useEffect as useEffect20, useRef as useRef8, useState as useState27 } from "react";
18204
+ import { useEffect as useEffect21, useRef as useRef8, useState as useState27 } from "react";
18025
18205
  import { MemoryRouter, Navigate, Route, Routes, useLocation as useLocation3, useNavigate as useNavigate6 } from "react-router";
18026
18206
  import { jsxDEV as jsxDEV39 } from "@opentui/react/jsx-dev-runtime";
18027
18207
  function getCurrentViewLabel(pathname) {
@@ -18071,6 +18251,7 @@ function AppContent() {
18071
18251
  closePalette,
18072
18252
  slashMenuOpen,
18073
18253
  slashMenuQuery,
18254
+ setSlashMenuQuery,
18074
18255
  slashMenuSelected,
18075
18256
  setSlashMenuSelected,
18076
18257
  openSlashMenu,
@@ -18115,7 +18296,7 @@ function AppContent() {
18115
18296
  ctrlCArmedRef.current = true;
18116
18297
  showCtrlCNotice("Press Ctrl+C again to exit");
18117
18298
  };
18118
- useEffect20(() => {
18299
+ useEffect21(() => {
18119
18300
  return () => {
18120
18301
  if (ctrlCTimerRef.current) {
18121
18302
  clearTimeout(ctrlCTimerRef.current);
@@ -18127,9 +18308,9 @@ function AppContent() {
18127
18308
  const inChatSession = location.pathname.startsWith("/sessions/");
18128
18309
  const inputHostsSlashMenu = location.pathname === "/" || location.pathname === "/home" || inChatSession;
18129
18310
  const handleAction = (action) => {
18130
- const path19 = getPathFromAction(action);
18131
- if (path19) {
18132
- navigate(path19);
18311
+ const path20 = getPathFromAction(action);
18312
+ if (path20) {
18313
+ navigate(path20);
18133
18314
  return;
18134
18315
  }
18135
18316
  switch (action) {
@@ -18140,7 +18321,9 @@ function AppContent() {
18140
18321
  openPalette();
18141
18322
  break;
18142
18323
  case "system:slashPalette":
18143
- openSlashMenu();
18324
+ if (!inputHostsSlashMenu) {
18325
+ openSlashMenu();
18326
+ }
18144
18327
  break;
18145
18328
  case "system:back":
18146
18329
  if (canGoBack) {
@@ -18171,8 +18354,9 @@ function AppContent() {
18171
18354
  }
18172
18355
  };
18173
18356
  const filteredCommands = searchCommands(paletteQuery.trim());
18357
+ const slashMenuHost = inChatSession ? "chat" : location.pathname === "/" || location.pathname === "/home" ? "home" : "other";
18174
18358
  const filteredSlashRoutes = getSlashMenuItems(slashMenuQuery, {
18175
- includeChatActions: inChatSession
18359
+ host: slashMenuHost
18176
18360
  });
18177
18361
  const selectedSlashRouteIndex = Math.min(slashMenuSelected, Math.max(filteredSlashRoutes.length - 1, 0));
18178
18362
  const isDownKey2 = (keyEvent) => isDownKey(keyEvent.name, { vim: false });
@@ -18202,10 +18386,24 @@ function AppContent() {
18202
18386
  };
18203
18387
  const handleSlashMenuKeyDown = (keyEvent) => {
18204
18388
  const maxIndex = filteredSlashRoutes.length - 1;
18205
- if (isBackspaceKey2(keyEvent) && !inputHostsSlashMenu && slashMenuQuery.trim().length <= 1) {
18206
- captureKeyEvent(keyEvent);
18207
- closeSlashMenu();
18208
- return;
18389
+ if (!inputHostsSlashMenu) {
18390
+ if (isBackspaceKey2(keyEvent)) {
18391
+ captureKeyEvent(keyEvent);
18392
+ if (slashMenuQuery.trim().length <= 1) {
18393
+ closeSlashMenu();
18394
+ } else {
18395
+ setSlashMenuQuery((query) => query.length > 1 ? query.slice(0, -1) : query);
18396
+ setSlashMenuSelected(0);
18397
+ }
18398
+ return;
18399
+ }
18400
+ const typed = keyEvent.sequence ?? "";
18401
+ if (typed.length === 1 && typed >= " " && typed !== "\x7F" && !keyEvent.ctrl && !isEnterKey2(keyEvent) && !isEscapeKey2(keyEvent) && !isUpKey2(keyEvent) && !isDownKey2(keyEvent)) {
18402
+ captureKeyEvent(keyEvent);
18403
+ setSlashMenuQuery((query) => query + typed);
18404
+ setSlashMenuSelected(0);
18405
+ return;
18406
+ }
18209
18407
  }
18210
18408
  if (maxIndex < 0) {
18211
18409
  if (isEscapeKey2(keyEvent)) {
@@ -18271,17 +18469,17 @@ function AppContent() {
18271
18469
  });
18272
18470
  const configBadge = useConfigBadge(configRefreshNonce);
18273
18471
  const needsOnboarding = configBadge.status === "available" && configBadge.missingCredentialHints.length > 0;
18274
- useEffect20(() => {
18472
+ useEffect21(() => {
18275
18473
  if (needsOnboarding && location.pathname !== "/onboarding") {
18276
18474
  navigate("/onboarding");
18277
18475
  }
18278
18476
  }, [location.pathname, navigate, needsOnboarding]);
18279
18477
  const getFooterStatus = () => {
18280
18478
  if (slashMenuOpen) {
18281
- return "Slash pages open | Enter Open | Backspace Close | Esc Cancel";
18479
+ return "Slash pages \xB7 \u2191/\u2193 select \xB7 Enter open \xB7 Esc close";
18282
18480
  }
18283
- const backHint = canGoBack ? `Esc/${BACK_SHORTCUT_LABEL} Back | ` : "";
18284
- return backHint + "/ Pages | Ctrl+P Cmd | F1 Help | Ctrl+C Quit";
18481
+ const backHint = canGoBack ? `Esc/${BACK_SHORTCUT_LABEL} back \xB7 ` : "";
18482
+ return backHint + "/ pages \xB7 Ctrl+P commands \xB7 F1 help \xB7 Ctrl+C quit";
18285
18483
  };
18286
18484
  return /* @__PURE__ */ jsxDEV39("box", {
18287
18485
  flexDirection: "column",