@nmakarov/cli-toolkit 0.66.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;
@@ -8155,8 +8359,18 @@ var LOGGER_RUNTIME_KEYS = [
8155
8359
  "progressThrottleMs"
8156
8360
  ];
8157
8361
  var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
8158
- function controlLaneTaskNames() {
8159
- return [...CONTROL_LANE_TASK_NAMES];
8362
+ function controlLaneTaskNames(extra) {
8363
+ const names = [...CONTROL_LANE_TASK_NAMES];
8364
+ if (extra == null || extra === "") return names;
8365
+ const more = Array.isArray(extra) ? extra : String(extra).split(",");
8366
+ const seen = new Set(names);
8367
+ for (const raw of more) {
8368
+ const n = String(raw ?? "").trim();
8369
+ if (!n || seen.has(n)) continue;
8370
+ seen.add(n);
8371
+ names.push(n);
8372
+ }
8373
+ return names;
8160
8374
  }
8161
8375
  function asPositiveInt(value, key, { min = 1 } = {}) {
8162
8376
  const n = Number(value);
@@ -8963,6 +9177,7 @@ async function runTasksLoop(context, options) {
8963
9177
  const queueName = options.queueName ?? "tasks";
8964
9178
  const target = options.target;
8965
9179
  const allowedTasks = normalizeAllowedTasks(options.allowedTasks);
9180
+ const controlLaneNames = controlLaneTaskNames(options.controlLaneTasks);
8966
9181
  const registry = normalizeRegistry(options.registry);
8967
9182
  const { tasksTable, historyTable } = queueToTableNames(queueName);
8968
9183
  if (!target) throw new Error("runTasksLoop: target is required");
@@ -9033,7 +9248,7 @@ async function runTasksLoop(context, options) {
9033
9248
  target,
9034
9249
  registry,
9035
9250
  10,
9036
- controlLaneTaskNames(),
9251
+ controlLaneNames,
9037
9252
  runnerIdentity
9038
9253
  );
9039
9254
  if (claimedControlTask) {
@@ -9306,7 +9521,7 @@ export {
9306
9521
  AbstractTask,
9307
9522
  Args,
9308
9523
  Aws,
9309
- Box4 as Box,
9524
+ Box5 as Box,
9310
9525
  Db,
9311
9526
  Divider,
9312
9527
  FileDatabase,
@@ -9331,6 +9546,7 @@ export {
9331
9546
  ScreenFooter,
9332
9547
  ScreenRow,
9333
9548
  ScreenTitle,
9549
+ ScrollableText,
9334
9550
  TaskGetLogs,
9335
9551
  TaskPing,
9336
9552
  TaskSampleProcess,
@@ -9341,13 +9557,15 @@ export {
9341
9557
  TaskSystemInfo,
9342
9558
  TasksManager,
9343
9559
  TasksRegistry,
9344
- Text5 as Text,
9560
+ Text6 as Text,
9345
9561
  TextBlock,
9346
9562
  activateRelease,
9347
9563
  appendDeployLog,
9348
9564
  appendTaskIpcLog,
9349
9565
  applyRuntimeParam,
9350
9566
  applyRuntimePatch,
9567
+ bindingIdentity,
9568
+ bindingMatchesInput,
9351
9569
  bootstrapHost,
9352
9570
  buildBreadcrumb,
9353
9571
  buildDetailBreadcrumb,
@@ -9380,9 +9598,10 @@ export {
9380
9598
  ensureTaskTables,
9381
9599
  ensureTasksRuntime,
9382
9600
  flushTaskIpcLogs,
9601
+ formatBindingKey,
9383
9602
  getArgsInstance,
9384
9603
  getPm2Process,
9385
- createElement2 as h,
9604
+ createElement3 as h,
9386
9605
  initServiceStructure,
9387
9606
  installDeps,
9388
9607
  installOperatorShell,
@@ -9437,6 +9656,7 @@ export {
9437
9656
  runRemoteStatus,
9438
9657
  runShell,
9439
9658
  runTasksLoop,
9659
+ scrollbarGlyphs,
9440
9660
  scrubEnvContent,
9441
9661
  servicePaths,
9442
9662
  setupContext,
@@ -9460,14 +9680,15 @@ export {
9460
9680
  updateServicesRegistryMetadata,
9461
9681
  updateTaskProgress,
9462
9682
  useCallback,
9463
- useEffect2 as useEffect,
9683
+ useEffect3 as useEffect,
9464
9684
  useInput2 as useInput,
9465
9685
  useLayoutEffect,
9466
- useMemo,
9467
- useRef2 as useRef,
9468
- useState3 as useState,
9686
+ useMemo2 as useMemo,
9687
+ useRef3 as useRef,
9688
+ useState4 as useState,
9469
9689
  waitForTaskResult,
9470
9690
  waitPm2,
9691
+ wrapTextLines,
9471
9692
  writeReleaseBuildInfo
9472
9693
  };
9473
9694
  //# sourceMappingURL=index.js.map