@nmakarov/cli-toolkit 0.67.0 → 0.68.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.
package/dist/index.js CHANGED
@@ -472,6 +472,59 @@ var init_list_components = __esm({
472
472
  }
473
473
  });
474
474
 
475
+ // src/screen/key-bindings.js
476
+ function bindingIdentity(b) {
477
+ return [
478
+ String(b?.key ?? ""),
479
+ b?.meta ? "m" : "",
480
+ b?.ctrl ? "c" : "",
481
+ b?.shift ? "s" : ""
482
+ ].join("|");
483
+ }
484
+ function bindingMatchesInput(binding, input, key) {
485
+ if (!binding?.key) return false;
486
+ let keyMatches = false;
487
+ if (key?.[binding.key]) {
488
+ keyMatches = true;
489
+ } else if (input === binding.key) {
490
+ keyMatches = true;
491
+ } else if (key?.name === binding.key) {
492
+ keyMatches = true;
493
+ }
494
+ if (!keyMatches) return false;
495
+ const modOk = (flag, pressed) => {
496
+ if (flag === true) return !!pressed;
497
+ if (flag === false) return !pressed;
498
+ return !pressed;
499
+ };
500
+ if (!modOk(binding.meta, key?.meta)) return false;
501
+ if (!modOk(binding.ctrl, key?.ctrl)) return false;
502
+ if (binding.shift !== void 0 && !!key?.shift !== !!binding.shift) return false;
503
+ return true;
504
+ }
505
+ function formatBindingKey(binding) {
506
+ let label = KEY_LABELS[binding.key] || binding.key;
507
+ if (binding.shift) label = `\u21E7${label}`;
508
+ if (binding.ctrl) label = `^${label}`;
509
+ if (binding.meta) label = `\u2325${label}`;
510
+ return label;
511
+ }
512
+ var KEY_LABELS;
513
+ var init_key_bindings = __esm({
514
+ "src/screen/key-bindings.js"() {
515
+ KEY_LABELS = {
516
+ escape: "esc",
517
+ leftArrow: "\u2190",
518
+ rightArrow: "\u2192",
519
+ upArrow: "\u2191",
520
+ downArrow: "\u2193",
521
+ return: "enter",
522
+ pageUp: "PgUp",
523
+ pageDown: "PgDn"
524
+ };
525
+ }
526
+ });
527
+
475
528
  // src/screen/screens.js
476
529
  import { useState as useState2, createElement as h3 } from "react";
477
530
  import { render, useInput, Text as Text3 } from "ink";
@@ -487,7 +540,7 @@ function groupKeyBindings(bindings) {
487
540
  order: binding.order || 999
488
541
  };
489
542
  }
490
- groups[caption].keys.push(binding.key);
543
+ groups[caption].keys.push(formatBindingKey(binding));
491
544
  });
492
545
  return Object.values(groups);
493
546
  }
@@ -527,12 +580,14 @@ function formatKeyBindings(bindings, mode = "long") {
527
580
  }
528
581
  function formatKeys(keys) {
529
582
  const keyMap = {
530
- "escape": "esc",
531
- "leftArrow": "\u2190",
532
- "rightArrow": "\u2192",
533
- "upArrow": "\u2191",
534
- "downArrow": "\u2193",
535
- "return": "enter"
583
+ escape: "esc",
584
+ leftArrow: "\u2190",
585
+ rightArrow: "\u2192",
586
+ upArrow: "\u2191",
587
+ downArrow: "\u2193",
588
+ return: "enter",
589
+ pageUp: "PgUp",
590
+ pageDown: "PgDn"
536
591
  };
537
592
  return keys.map((k) => keyMap[k] || k).join("/");
538
593
  }
@@ -572,7 +627,10 @@ async function showScreen(config2) {
572
627
  setKeyBinding: (bindingOrBindings) => {
573
628
  const bindingsToSet = Array.isArray(bindingOrBindings) ? bindingOrBindings : [bindingOrBindings];
574
629
  bindingsToSet.forEach((binding) => {
575
- const existingIndex = keyBindings.findIndex((b) => b.key === binding.key);
630
+ const id = bindingIdentity(binding);
631
+ const existingIndex = keyBindings.findIndex(
632
+ (b) => bindingIdentity(b) === id
633
+ );
576
634
  if (existingIndex >= 0) {
577
635
  const existing = keyBindings[existingIndex];
578
636
  if (existing.protected) {
@@ -596,7 +654,10 @@ async function showScreen(config2) {
596
654
  });
597
655
  },
598
656
  updateKeyBinding: (keyName, updates) => {
599
- const index = keyBindings.findIndex((b) => b.key === keyName);
657
+ const id = updates && (updates.meta != null || updates.ctrl != null || updates.shift != null) ? bindingIdentity({ key: keyName, ...updates }) : null;
658
+ const index = keyBindings.findIndex(
659
+ (b) => id ? bindingIdentity(b) === id : b.key === keyName
660
+ );
600
661
  if (index >= 0) {
601
662
  keyBindings[index] = {
602
663
  ...keyBindings[index],
@@ -604,11 +665,13 @@ async function showScreen(config2) {
604
665
  };
605
666
  }
606
667
  },
607
- removeKeyBinding: (keyName) => {
608
- const index = keyBindings.findIndex((b) => b.key === keyName);
668
+ removeKeyBinding: (keyNameOrBinding) => {
669
+ const index = typeof keyNameOrBinding === "object" && keyNameOrBinding ? keyBindings.findIndex(
670
+ (b) => bindingIdentity(b) === bindingIdentity(keyNameOrBinding)
671
+ ) : keyBindings.findIndex((b) => b.key === keyNameOrBinding);
609
672
  if (index >= 0) {
610
673
  if (keyBindings[index].protected) {
611
- console.warn(`Cannot remove protected key: ${keyName}`);
674
+ console.warn(`Cannot remove protected key: ${keyNameOrBinding}`);
612
675
  return;
613
676
  }
614
677
  keyBindings.splice(index, 1);
@@ -649,24 +712,11 @@ async function showScreen(config2) {
649
712
  }
650
713
  let matchedBinding = null;
651
714
  for (const binding of keyBindings) {
652
- let keyMatches = false;
653
- if (key[binding.key]) {
654
- keyMatches = true;
655
- } else if (input === binding.key) {
656
- keyMatches = true;
657
- } else if (key?.name === binding.key) {
658
- keyMatches = true;
659
- }
660
- if (keyMatches) {
661
- if (binding.enabled === false) {
662
- continue;
663
- }
664
- if (binding.condition && !binding.condition(context)) {
665
- continue;
666
- }
667
- matchedBinding = binding;
668
- break;
669
- }
715
+ if (binding.enabled === false) continue;
716
+ if (!bindingMatchesInput(binding, input, key)) continue;
717
+ if (binding.condition && !binding.condition(context)) continue;
718
+ matchedBinding = binding;
719
+ break;
670
720
  }
671
721
  if (matchedBinding && actions[matchedBinding.action]) {
672
722
  actions[matchedBinding.action]({
@@ -802,6 +852,7 @@ var init_screens = __esm({
802
852
  "src/screen/screens.js"() {
803
853
  init_components();
804
854
  init_list_components();
855
+ init_key_bindings();
805
856
  showMenuScreen = showListScreen;
806
857
  showWordGridScreen = showMultiColumnListScreen;
807
858
  }
@@ -891,6 +942,157 @@ var init_ui_elements = __esm({
891
942
  }
892
943
  });
893
944
 
945
+ // src/screen/scrollable-text.js
946
+ import { useState as useState3, useEffect as useEffect2, useMemo, useRef as useRef2, createElement as createElement2 } from "react";
947
+ import { Box as Box4, Text as Text5 } from "ink";
948
+ function wrapTextLines(text, cols) {
949
+ const w = Math.max(1, Math.floor(Number(cols) || 1));
950
+ const out = [];
951
+ for (const raw of String(text ?? "").split("\n")) {
952
+ if (raw.length === 0) {
953
+ out.push("");
954
+ continue;
955
+ }
956
+ let rest = raw;
957
+ while (rest.length > w) {
958
+ out.push(rest.slice(0, w));
959
+ rest = rest.slice(w);
960
+ }
961
+ if (rest.length > 0) out.push(rest);
962
+ }
963
+ return out;
964
+ }
965
+ function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
966
+ const view = Math.max(1, Math.floor(viewportRows));
967
+ const total = Math.max(0, Math.floor(totalLines));
968
+ if (total <= view) return null;
969
+ const maxScroll = total - view;
970
+ const thumbSize = Math.max(1, Math.round(view / total * view));
971
+ const travel = Math.max(0, view - thumbSize);
972
+ const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
973
+ const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
974
+ const glyphs = [];
975
+ for (let i = 0; i < view; i++) {
976
+ glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? "\u2588" : "\u2502");
977
+ }
978
+ return glyphs;
979
+ }
980
+ function padEndVisible(s, w) {
981
+ const t = String(s ?? "");
982
+ if (t.length >= w) return t.slice(0, w);
983
+ return t + " ".repeat(w - t.length);
984
+ }
985
+ function ScrollableText({
986
+ ctx,
987
+ text = "",
988
+ lines: linesProp,
989
+ maxHeight,
990
+ wrap = true,
991
+ showScrollbar = true,
992
+ showStatus = true,
993
+ bindKeys = true,
994
+ header = null
995
+ }) {
996
+ const [scrollTop, setScrollTop] = useState3(0);
997
+ const [, bump] = useState3(0);
998
+ const termCols = process.stdout.columns || 80;
999
+ const termRows = process.stdout.rows || 24;
1000
+ const viewportRows = Math.max(
1001
+ 4,
1002
+ maxHeight != null ? Math.floor(maxHeight) : Math.max(8, termRows - 8)
1003
+ );
1004
+ const provisionalBar = showScrollbar ? 1 : 0;
1005
+ const wrapCols = Math.max(8, termCols - provisionalBar);
1006
+ const allLines = useMemo(() => {
1007
+ if (Array.isArray(linesProp)) return linesProp.map((l) => String(l ?? ""));
1008
+ return wrap ? wrapTextLines(text, wrapCols) : String(text ?? "").split("\n");
1009
+ }, [linesProp, text, wrap, wrapCols]);
1010
+ const needsBar = showScrollbar && allLines.length > viewportRows;
1011
+ const textWidth = Math.max(8, termCols - (needsBar ? 1 : 0));
1012
+ const maxScroll = Math.max(0, allLines.length - viewportRows);
1013
+ const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
1014
+ const visible = allLines.slice(clamped, clamped + viewportRows);
1015
+ const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
1016
+ useEffect2(() => {
1017
+ setScrollTop((s) => Math.min(s, maxScroll));
1018
+ }, [maxScroll]);
1019
+ const maxScrollRef = useRef2(maxScroll);
1020
+ const pageSizeRef = useRef2(viewportRows);
1021
+ maxScrollRef.current = maxScroll;
1022
+ pageSizeRef.current = viewportRows;
1023
+ useEffect2(() => {
1024
+ if (!ctx || !bindKeys) return void 0;
1025
+ ctx.setKeyBinding(SCROLL_KEYS);
1026
+ ctx.setAction("scrollUp", () => {
1027
+ setScrollTop((s) => Math.max(0, s - 1));
1028
+ bump((n) => n + 1);
1029
+ ctx.update?.();
1030
+ });
1031
+ ctx.setAction("scrollDown", () => {
1032
+ setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
1033
+ bump((n) => n + 1);
1034
+ ctx.update?.();
1035
+ });
1036
+ ctx.setAction("pageUp", () => {
1037
+ setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
1038
+ bump((n) => n + 1);
1039
+ ctx.update?.();
1040
+ });
1041
+ ctx.setAction("pageDown", () => {
1042
+ setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
1043
+ bump((n) => n + 1);
1044
+ ctx.update?.();
1045
+ });
1046
+ return void 0;
1047
+ }, [ctx, bindKeys]);
1048
+ const status = allLines.length === 0 ? "empty" : `lines ${clamped + 1}-${Math.min(clamped + visible.length, allLines.length)} of ${allLines.length}` + (needsBar ? " \xB7 \u2325\u2191/\u2193 or PgUp/Dn page" : "");
1049
+ const rowNodes = visible.map((line, i) => {
1050
+ const body = padEndVisible(line, textWidth);
1051
+ const glyph = bar ? bar[i] ?? "\u2502" : "";
1052
+ return h5(
1053
+ Box4,
1054
+ { key: `L${clamped + i}`, flexDirection: "row" },
1055
+ h5(Text5, {}, body),
1056
+ glyph ? h5(Text5, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, glyph) : null
1057
+ );
1058
+ });
1059
+ if (bar && visible.length < viewportRows) {
1060
+ for (let i = visible.length; i < viewportRows; i++) {
1061
+ rowNodes.push(
1062
+ h5(
1063
+ Box4,
1064
+ { key: `pad${i}`, flexDirection: "row" },
1065
+ h5(Text5, {}, padEndVisible("", textWidth)),
1066
+ h5(Text5, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, bar[i] ?? "\u2502")
1067
+ )
1068
+ );
1069
+ }
1070
+ }
1071
+ return h5(
1072
+ Box4,
1073
+ { flexDirection: "column" },
1074
+ header == null ? null : typeof header === "string" ? h5(Text5, { color: "gray" }, header) : header,
1075
+ showStatus ? h5(Text5, { color: "gray" }, status) : null,
1076
+ ...rowNodes
1077
+ );
1078
+ }
1079
+ var h5, SCROLL_KEYS;
1080
+ var init_scrollable_text = __esm({
1081
+ "src/screen/scrollable-text.js"() {
1082
+ h5 = createElement2;
1083
+ SCROLL_KEYS = [
1084
+ { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1085
+ { key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
1086
+ { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
1087
+ { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
1088
+ { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
1089
+ { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
1090
+ { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
1091
+ { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
1092
+ ];
1093
+ }
1094
+ });
1095
+
894
1096
  // src/screen/utils.js
895
1097
  function buildBreadcrumb(parts) {
896
1098
  if (parts.length === 0) return "";
@@ -1020,8 +1222,8 @@ var init_footer_builder = __esm({
1020
1222
  });
1021
1223
 
1022
1224
  // src/screen/index.js
1023
- import React2, { useState as useState3, useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useMemo, useCallback, memo, createElement as createElement2 } from "react";
1024
- import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
1225
+ import React2, { useState as useState4, useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useMemo as useMemo2, useCallback, memo, createElement as createElement3 } from "react";
1226
+ import { Box as Box5, Text as Text6, useInput as useInput2 } from "ink";
1025
1227
  async function load() {
1026
1228
  if (loadPromise) return loadPromise;
1027
1229
  loadPromise = Promise.all([
@@ -1038,6 +1240,8 @@ var init_screen = __esm({
1038
1240
  init_list_components();
1039
1241
  init_components();
1040
1242
  init_ui_elements();
1243
+ init_scrollable_text();
1244
+ init_key_bindings();
1041
1245
  init_utils();
1042
1246
  init_footer_builder();
1043
1247
  loadPromise = null;
@@ -9317,7 +9521,7 @@ export {
9317
9521
  AbstractTask,
9318
9522
  Args,
9319
9523
  Aws,
9320
- Box4 as Box,
9524
+ Box5 as Box,
9321
9525
  Db,
9322
9526
  Divider,
9323
9527
  FileDatabase,
@@ -9342,6 +9546,7 @@ export {
9342
9546
  ScreenFooter,
9343
9547
  ScreenRow,
9344
9548
  ScreenTitle,
9549
+ ScrollableText,
9345
9550
  TaskGetLogs,
9346
9551
  TaskPing,
9347
9552
  TaskSampleProcess,
@@ -9352,13 +9557,15 @@ export {
9352
9557
  TaskSystemInfo,
9353
9558
  TasksManager,
9354
9559
  TasksRegistry,
9355
- Text5 as Text,
9560
+ Text6 as Text,
9356
9561
  TextBlock,
9357
9562
  activateRelease,
9358
9563
  appendDeployLog,
9359
9564
  appendTaskIpcLog,
9360
9565
  applyRuntimeParam,
9361
9566
  applyRuntimePatch,
9567
+ bindingIdentity,
9568
+ bindingMatchesInput,
9362
9569
  bootstrapHost,
9363
9570
  buildBreadcrumb,
9364
9571
  buildDetailBreadcrumb,
@@ -9391,9 +9598,10 @@ export {
9391
9598
  ensureTaskTables,
9392
9599
  ensureTasksRuntime,
9393
9600
  flushTaskIpcLogs,
9601
+ formatBindingKey,
9394
9602
  getArgsInstance,
9395
9603
  getPm2Process,
9396
- createElement2 as h,
9604
+ createElement3 as h,
9397
9605
  initServiceStructure,
9398
9606
  installDeps,
9399
9607
  installOperatorShell,
@@ -9448,6 +9656,7 @@ export {
9448
9656
  runRemoteStatus,
9449
9657
  runShell,
9450
9658
  runTasksLoop,
9659
+ scrollbarGlyphs,
9451
9660
  scrubEnvContent,
9452
9661
  servicePaths,
9453
9662
  setupContext,
@@ -9471,14 +9680,15 @@ export {
9471
9680
  updateServicesRegistryMetadata,
9472
9681
  updateTaskProgress,
9473
9682
  useCallback,
9474
- useEffect2 as useEffect,
9683
+ useEffect3 as useEffect,
9475
9684
  useInput2 as useInput,
9476
9685
  useLayoutEffect,
9477
- useMemo,
9478
- useRef2 as useRef,
9479
- useState3 as useState,
9686
+ useMemo2 as useMemo,
9687
+ useRef3 as useRef,
9688
+ useState4 as useState,
9480
9689
  waitForTaskResult,
9481
9690
  waitPm2,
9691
+ wrapTextLines,
9482
9692
  writeReleaseBuildInfo
9483
9693
  };
9484
9694
  //# sourceMappingURL=index.js.map