termkit 2.3.0 → 2.4.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.mjs CHANGED
@@ -131,6 +131,8 @@ function findCommand(array, commands) {
131
131
  var RESET = "\x1B[0m";
132
132
  var SHOW_CURSOR = "\x1B[?25h";
133
133
  var HIDE_CURSOR = "\x1B[?25l";
134
+ var DISABLE_WRAP = "\x1B[?7l";
135
+ var ENABLE_WRAP = "\x1B[?7h";
134
136
  var BOLD = "\x1B[1m";
135
137
  var FAINT = "\x1B[2m";
136
138
  var GREEN = "\x1B[32m";
@@ -497,15 +499,22 @@ var Select = class {
497
499
  if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("Select requires an interactive terminal");
498
500
  let selectedIndex = 0;
499
501
  let viewportOffset = 0;
502
+ let visibleStart = 0;
500
503
  let searchQuery = "";
501
504
  let lastDrawnLines = 0;
502
505
  const skipItem = { label: this.skipLabel };
506
+ const computeMaxHeight = () => {
507
+ const termRows = process.stdout.rows ?? 24;
508
+ const reserved = 1 + (this.searchEnabled ? 1 : 0);
509
+ const fit = Math.max(1, termRows - reserved - 1);
510
+ return this.maxHeight ? Math.min(this.maxHeight, fit) : fit;
511
+ };
503
512
  const getFiltered = () => {
504
513
  if (!this.searchEnabled || searchQuery === "") return items;
505
514
  const q = searchQuery.toLowerCase();
506
515
  return items.filter((item) => item.label.toLowerCase().includes(q) || (item.description?.toLowerCase().includes(q) ?? false));
507
516
  };
508
- process.stdout.write(HIDE_CURSOR);
517
+ process.stdout.write(HIDE_CURSOR + DISABLE_WRAP);
509
518
  const glyph = this.promptGlyph ? `${colorText(this.promptColor, this.promptGlyph)} ` : "";
510
519
  const indent = " ".repeat(this.promptGlyph ? stringLength(this.promptGlyph) + 1 : 0);
511
520
  process.stdout.write(`${glyph}${prompt}
@@ -514,13 +523,17 @@ var Select = class {
514
523
  const filtered = getFiltered();
515
524
  const allItems = [...filtered, skipItem];
516
525
  if (selectedIndex >= allItems.length) selectedIndex = allItems.length - 1;
517
- if (this.maxHeight) {
526
+ const maxHeight = computeMaxHeight();
527
+ const useViewport = allItems.length > maxHeight;
528
+ if (useViewport) {
518
529
  if (selectedIndex < viewportOffset) viewportOffset = selectedIndex;
519
- else if (selectedIndex >= viewportOffset + this.maxHeight) viewportOffset = selectedIndex - this.maxHeight + 1;
520
- viewportOffset = Math.max(0, Math.min(viewportOffset, Math.max(0, allItems.length - this.maxHeight)));
530
+ else if (selectedIndex >= viewportOffset + maxHeight) viewportOffset = selectedIndex - maxHeight + 1;
531
+ viewportOffset = Math.max(0, Math.min(viewportOffset, Math.max(0, allItems.length - maxHeight)));
532
+ } else {
533
+ viewportOffset = 0;
521
534
  }
522
- const visibleStart = this.maxHeight ? viewportOffset : 0;
523
- const visibleEnd = this.maxHeight ? Math.min(allItems.length, viewportOffset + this.maxHeight) : allItems.length;
535
+ visibleStart = useViewport ? viewportOffset : 0;
536
+ const visibleEnd = useViewport ? Math.min(allItems.length, viewportOffset + maxHeight) : allItems.length;
524
537
  if (redraw) {
525
538
  if (lastDrawnLines > 0) process.stdout.write(CURSOR_UP(lastDrawnLines));
526
539
  process.stdout.write("\r\x1B[0J");
@@ -536,8 +549,7 @@ var Select = class {
536
549
  const item = allItems[i];
537
550
  const isSelected = i === selectedIndex;
538
551
  const isSkip = i === filtered.length;
539
- const relativeNum = i - visibleStart + 1;
540
- const numStr = isSkip ? "0." : `${relativeNum}.`;
552
+ const numStr = isSkip ? "0." : `${i + 1}.`;
541
553
  const desc = item.description ? ` ${colorText(this.descriptionColor, `\u2014 ${item.description}`)}` : "";
542
554
  let marker, tail;
543
555
  if (isSelected && pulse) {
@@ -564,7 +576,7 @@ var Select = class {
564
576
  process.stdin.setRawMode(false);
565
577
  process.stdin.pause();
566
578
  process.stdin.removeListener("data", onKey);
567
- process.stdout.write(SHOW_CURSOR);
579
+ process.stdout.write(ENABLE_WRAP + SHOW_CURSOR);
568
580
  });
569
581
  const cleanup = () => {
570
582
  deregisterCleanup();
@@ -575,7 +587,7 @@ var Select = class {
575
587
  process.stdin.setRawMode(false);
576
588
  process.stdin.pause();
577
589
  process.stdin.removeListener("data", onKey);
578
- process.stdout.write(SHOW_CURSOR);
590
+ process.stdout.write(ENABLE_WRAP + SHOW_CURSOR);
579
591
  };
580
592
  if (this._parsedColors.length >= 2) {
581
593
  timer = setInterval(() => {
@@ -618,8 +630,7 @@ var Select = class {
618
630
  } else {
619
631
  const n = parseInt(str);
620
632
  if (!isNaN(n) && n >= 0 && n <= Math.min(items.length, 9)) {
621
- const visibleStart = this.maxHeight ? viewportOffset : 0;
622
- selectedIndex = n === 0 ? allItems.length - 1 : visibleStart + n - 1;
633
+ selectedIndex = n === 0 ? allItems.length - 1 : n - 1;
623
634
  selectedIndex = Math.max(0, Math.min(selectedIndex, allItems.length - 1));
624
635
  renderList(true);
625
636
  }
@@ -800,7 +811,7 @@ var Command = class {
800
811
  constructor(data) {
801
812
  this.actionFunction = null;
802
813
  this.commandsArray = [];
803
- this.commandStrings = ["help"];
814
+ this.commandStrings = ["help", "version"];
804
815
  this.info = null;
805
816
  this.middlewaresArray = [];
806
817
  this.name = null;
@@ -870,6 +881,9 @@ var Command = class {
870
881
  const recursive = source?.includes("-r") === true || source?.includes("--recursive") === true;
871
882
  this.printHelp(this.name ?? "Program", recursive);
872
883
  }
884
+ printVersion() {
885
+ console.log(this.versionString ?? "");
886
+ }
873
887
  printHelp(fullName, recursive) {
874
888
  const table = [];
875
889
  let program = fullName;
@@ -929,29 +943,32 @@ var Command = class {
929
943
  }
930
944
  }
931
945
  }
932
- async parse(input2) {
933
- const array = [...input2];
934
- array.splice(0, 2);
946
+ async _execute(tokens) {
947
+ const array = [...tokens];
935
948
  let command = this;
936
949
  const options = { _source: Array.from(array) };
950
+ if (this.versionString && (array.includes("--version") || array.includes("-V"))) {
951
+ return this.printVersion();
952
+ }
937
953
  const ddIdx = array.indexOf("--");
938
954
  if (ddIdx !== -1) {
939
955
  options._ = array.splice(ddIdx + 1);
940
956
  array.splice(ddIdx, 1);
941
957
  }
942
958
  while (array.length) {
943
- if (!array.includes("help")) {
959
+ if (!array.includes("help") && !array.includes("version")) {
944
960
  Object.assign(options, await findOptions(array, command));
945
961
  const cmdVars = await findCommandVariables(array, command);
946
962
  if (cmdVars) Object.assign(options, cmdVars);
947
963
  Object.assign(options, await findOptions(array, command));
948
964
  }
949
965
  if (array.length) {
950
- if (!array.includes("help")) {
966
+ if (!array.includes("help") && !array.includes("version")) {
951
967
  for (const mw of command.middlewaresArray) await mw(options);
952
968
  }
953
969
  const next = findCommand(array, command.commandsArray);
954
970
  if (!next && array[0] === "help") return command.help(options._source);
971
+ if (!next && array[0] === "version") return this.printVersion();
955
972
  if (!next) throw new SyntaxError(`Unknown command: ${array[0]}`);
956
973
  const name = command.name ?? "_base";
957
974
  if (!options._parents) options._parents = {};
@@ -975,14 +992,229 @@ var Command = class {
975
992
  }
976
993
  for (const mw of command.middlewaresArray) await mw(options);
977
994
  if (command.actionFunction) return command.actionFunction(options);
978
- if (options._source.length === 2) return command.help(options._source);
979
- throw new Error(`No action for command: ${command.name ?? "_base"}`);
995
+ return command.help(options._source);
996
+ }
997
+ async parse(input2) {
998
+ return this._execute(input2.slice(2));
999
+ }
1000
+ };
1001
+
1002
+ // src/models/Shell.ts
1003
+ import cosmetic2 from "cosmetic";
1004
+ import * as readline from "readline";
1005
+ var Shell = class {
1006
+ constructor(root, opts = {}) {
1007
+ this.rl = null;
1008
+ this.root = root;
1009
+ this.opts = {
1010
+ mode: opts.mode ?? "drill",
1011
+ prompt: opts.prompt ?? root.name ?? "shell",
1012
+ promptColor: opts.promptColor ?? "",
1013
+ banner: opts.banner ?? "",
1014
+ exitCommands: opts.exitCommands ?? ["exit", "quit"],
1015
+ historySize: opts.historySize ?? 100
1016
+ };
1017
+ }
1018
+ async run() {
1019
+ this.rl = readline.createInterface({
1020
+ input: process.stdin,
1021
+ output: process.stdout,
1022
+ terminal: true,
1023
+ historySize: this.opts.historySize,
1024
+ completer: this.opts.mode === "free" ? makeCompleter(this.root) : void 0
1025
+ });
1026
+ if (this.opts.banner) console.log(this.opts.banner);
1027
+ try {
1028
+ if (this.opts.mode === "free") {
1029
+ await this.freeLoop();
1030
+ } else {
1031
+ await this.drillLoop();
1032
+ }
1033
+ } finally {
1034
+ this.rl.close();
1035
+ }
1036
+ }
1037
+ // ── Drill mode ────────────────────────────────────────────────────────
1038
+ async drillLoop() {
1039
+ while (true) {
1040
+ const exited = await this.drillFrom(this.root, [this.root.name ?? "shell"]);
1041
+ if (exited) return;
1042
+ }
1043
+ }
1044
+ async drillFrom(cmd, breadcrumb) {
1045
+ while (true) {
1046
+ const token = await this.promptDrill(cmd, breadcrumb);
1047
+ if (token === null) {
1048
+ return breadcrumb.length === 1;
1049
+ }
1050
+ if (this.opts.exitCommands.includes(token)) process.exit(0);
1051
+ if (token === "..") return false;
1052
+ if (token === "help") {
1053
+ cmd.help();
1054
+ continue;
1055
+ }
1056
+ const sub = cmd.commandsArray.find((c) => c.name === token);
1057
+ if (!sub) {
1058
+ process.stderr.write(`Unknown command: ${token}
1059
+ `);
1060
+ continue;
1061
+ }
1062
+ if (sub.commandsArray.length > 0) {
1063
+ const exited = await this.drillFrom(sub, [...breadcrumb, sub.name ?? ""]);
1064
+ if (exited) return true;
1065
+ continue;
1066
+ }
1067
+ const vars = await this.gatherVariables(sub);
1068
+ const tokens = buildTokens(sub, vars);
1069
+ try {
1070
+ await sub._execute(tokens);
1071
+ } catch (err) {
1072
+ process.stderr.write(`${err instanceof Error ? err.message : err}
1073
+ `);
1074
+ }
1075
+ return false;
1076
+ }
1077
+ }
1078
+ async promptDrill(cmd, breadcrumb) {
1079
+ const subs = cmd.commandsArray.map((c) => c.name ?? "").filter(Boolean);
1080
+ const label = this.colorize(breadcrumb.join(" "));
1081
+ const choices = [...subs, "help"].join(", ");
1082
+ process.stdout.write(`
1083
+ ${label} ${choices}
1084
+ `);
1085
+ return new Promise((resolve) => {
1086
+ let resolved = false;
1087
+ const done = (val) => {
1088
+ if (!resolved) {
1089
+ resolved = true;
1090
+ resolve(val);
1091
+ }
1092
+ };
1093
+ this.rl.question("> ", (answer) => done(answer.trim() || null));
1094
+ this.rl.once("close", () => done(null));
1095
+ });
1096
+ }
1097
+ async gatherVariables(cmd) {
1098
+ const result = {};
1099
+ if (!cmd.variables) return result;
1100
+ for (const v of cmd.variables) {
1101
+ const name = v.name ?? "value";
1102
+ const hint = v.hint ? ` ${v.hint}` : "";
1103
+ while (true) {
1104
+ const answer = await new Promise((resolve) => {
1105
+ let resolved = false;
1106
+ const done = (val) => {
1107
+ if (!resolved) {
1108
+ resolved = true;
1109
+ resolve(val);
1110
+ }
1111
+ };
1112
+ this.rl.question(` ${name}${hint}: `, (ans) => done(ans.trim() || null));
1113
+ this.rl.once("close", () => done(null));
1114
+ });
1115
+ const value = answer ?? v.default ?? null;
1116
+ if (!value && v.required) {
1117
+ process.stderr.write(` ${name} is required
1118
+ `);
1119
+ continue;
1120
+ }
1121
+ if (value) {
1122
+ if (v.type === "enum" && v.enum && !v.enum.includes(value)) {
1123
+ process.stderr.write(` Must be one of: ${v.enum.join(", ")}
1124
+ `);
1125
+ continue;
1126
+ }
1127
+ result[name] = value;
1128
+ }
1129
+ break;
1130
+ }
1131
+ }
1132
+ return result;
1133
+ }
1134
+ // ── Free mode ─────────────────────────────────────────────────────────
1135
+ async freeLoop() {
1136
+ const prompt = `${this.colorize(this.opts.prompt)} > `;
1137
+ this.rl.setPrompt(prompt);
1138
+ this.rl.prompt();
1139
+ for await (const line of this.rl) {
1140
+ const trimmed = line.trim();
1141
+ if (!trimmed) {
1142
+ this.rl.prompt();
1143
+ continue;
1144
+ }
1145
+ if (this.opts.exitCommands.includes(trimmed)) break;
1146
+ const tokens = tokenize(trimmed);
1147
+ try {
1148
+ await this.root._execute(tokens);
1149
+ } catch (err) {
1150
+ process.stderr.write(`${err instanceof Error ? err.message : err}
1151
+ `);
1152
+ }
1153
+ this.rl.prompt();
1154
+ }
1155
+ }
1156
+ // ── Helpers ───────────────────────────────────────────────────────────
1157
+ colorize(text) {
1158
+ const c = this.opts.promptColor;
1159
+ if (!c || !process.stdout.isTTY) return text;
1160
+ try {
1161
+ if (c.startsWith("#")) return cosmetic2.hex(c).encoder(text);
1162
+ if (/^\d+$/.test(c)) return cosmetic2.xterm(Number(c)).encoder(text);
1163
+ const style = cosmetic2[c];
1164
+ if (style && typeof style.encoder === "function") return style.encoder(text);
1165
+ } catch {
1166
+ }
1167
+ return text;
980
1168
  }
981
1169
  };
1170
+ function tokenize(line) {
1171
+ const tokens = [];
1172
+ let current = "";
1173
+ let inQuote = null;
1174
+ for (const ch of line) {
1175
+ if (inQuote) {
1176
+ if (ch === inQuote) {
1177
+ inQuote = null;
1178
+ } else {
1179
+ current += ch;
1180
+ }
1181
+ } else if (ch === '"' || ch === "'") {
1182
+ inQuote = ch;
1183
+ } else if (ch === " " || ch === " ") {
1184
+ if (current) {
1185
+ tokens.push(current);
1186
+ current = "";
1187
+ }
1188
+ } else {
1189
+ current += ch;
1190
+ }
1191
+ }
1192
+ if (current) tokens.push(current);
1193
+ return tokens;
1194
+ }
1195
+ function buildTokens(cmd, vars) {
1196
+ if (!cmd.variables) return [];
1197
+ return cmd.variables.map((v) => vars[v.name ?? ""]).filter((v) => v !== void 0 && v !== "");
1198
+ }
1199
+ function makeCompleter(root) {
1200
+ return (line) => {
1201
+ const tokens = tokenize(line);
1202
+ let cmd = root;
1203
+ for (const token of tokens.slice(0, -1)) {
1204
+ const sub = cmd.commandsArray.find((c) => c.name === token);
1205
+ if (!sub) break;
1206
+ cmd = sub;
1207
+ }
1208
+ const partial = tokens[tokens.length - 1] ?? "";
1209
+ const names = [...cmd.commandsArray.map((c) => c.name ?? "").filter(Boolean), "help"];
1210
+ const hits = names.filter((n) => n.startsWith(partial));
1211
+ return [hits.length ? hits : names, partial];
1212
+ };
1213
+ }
982
1214
 
983
1215
  // src/models/Bar.ts
984
- var Bar = class {
985
- constructor(options = {}) {
1216
+ var _Bar = class _Bar {
1217
+ constructor(text, options) {
986
1218
  // used by MultiBar — not intended for direct external use
987
1219
  this._isManaged = false;
988
1220
  this._managedFinalLine = null;
@@ -1000,37 +1232,38 @@ var Bar = class {
1000
1232
  this._tickCount = 0;
1001
1233
  this._startTime = null;
1002
1234
  this._etaSuffix = "";
1235
+ const opts = typeof text === "string" ? { ...options, text } : text ?? {};
1003
1236
  this.running = false;
1004
1237
  this.forwardMotion = true;
1005
- this._autoLength = options.length === void 0;
1006
- this.length = options.length ?? process.stdout.columns ?? 80;
1007
- this.prefixString = options.prefix ?? "[";
1008
- this.suffixString = options.suffix ?? "]";
1009
- this.character = options.character ?? "\u2500\u2500";
1010
- this.beforeEmpty = options.before ?? " ";
1011
- this.afterEmpty = options.after ?? " ";
1238
+ this._autoLength = opts.length === void 0;
1239
+ this.length = opts.length ?? process.stdout.columns ?? 80;
1240
+ this.prefixString = opts.prefix ?? "[";
1241
+ this.suffixString = opts.suffix ?? "]";
1242
+ this.character = opts.character ?? "\u2500\u2500";
1243
+ this.beforeEmpty = opts.before ?? " ";
1244
+ this.afterEmpty = opts.after ?? " ";
1012
1245
  this.position = 1;
1013
- this.interval = options.interval ?? 35;
1014
- this.mode = options.mode ?? "bounce";
1015
- this.progress = options.progress;
1016
- this.colorFill = options.colorFill ?? false;
1017
- this.colorCycle = options.colorCycle ?? 0.5;
1018
- this.shimmer = options.shimmer ?? 0;
1019
- this.text = options.text ?? "";
1020
- this.reverse = options.reverse ?? false;
1021
- this.onBounce = options.onBounce;
1022
- this.onLoop = options.onLoop;
1023
- this.onComplete = options.onComplete;
1024
- this.colors = options.colors ?? ["#c026d3", "#e879f9"];
1025
- this.bgColors = options.bgColors ?? [];
1026
- this._successColor = options.successColor ?? GREEN;
1027
- this._failColor = options.failColor ?? RED;
1028
- this._warnColor = options.warnColor ?? YELLOW;
1029
- this._infoColor = options.infoColor ?? BLUE;
1030
- this._glyphs = options.glyphs ?? true;
1031
- this._showRate = options.showRate ?? false;
1032
- this._showEta = options.showEta ?? false;
1033
- this._rateUnit = options.rateUnit ?? "";
1246
+ this.interval = opts.interval ?? 35;
1247
+ this.mode = opts.mode ?? "bounce";
1248
+ this.progress = opts.progress;
1249
+ this.colorFill = opts.colorFill ?? false;
1250
+ this.colorCycle = opts.colorCycle ?? 0.5;
1251
+ this.shimmer = opts.shimmer ?? 0;
1252
+ this.text = opts.text ?? "";
1253
+ this.reverse = opts.reverse ?? false;
1254
+ this.onBounce = opts.onBounce;
1255
+ this.onLoop = opts.onLoop;
1256
+ this.onComplete = opts.onComplete;
1257
+ this.colors = opts.colors ?? ["#c026d3", "#e879f9"];
1258
+ this.bgColors = opts.bgColors ?? [];
1259
+ this._successColor = opts.successColor ?? GREEN;
1260
+ this._failColor = opts.failColor ?? RED;
1261
+ this._warnColor = opts.warnColor ?? YELLOW;
1262
+ this._infoColor = opts.infoColor ?? BLUE;
1263
+ this._glyphs = opts.glyphs ?? true;
1264
+ this._showRate = opts.showRate ?? false;
1265
+ this._showEta = opts.showEta ?? false;
1266
+ this._rateUnit = opts.rateUnit ?? "";
1034
1267
  }
1035
1268
  get colors() {
1036
1269
  return this._colors;
@@ -1079,6 +1312,7 @@ var Bar = class {
1079
1312
  if (this._isManaged) return;
1080
1313
  if (!process.stdout.isTTY) return;
1081
1314
  this.running = true;
1315
+ _Bar.current = this;
1082
1316
  process.stdout.write(HIDE_CURSOR);
1083
1317
  this._cleanupDeregister = registerCleanup(() => {
1084
1318
  this.running = false;
@@ -1104,6 +1338,7 @@ var Bar = class {
1104
1338
  this._managedFinalLine = message ?? "";
1105
1339
  return this;
1106
1340
  }
1341
+ if (_Bar.current === this) _Bar.current = null;
1107
1342
  this._cleanupDeregister?.();
1108
1343
  this._cleanupDeregister = null;
1109
1344
  if (process.stdout.isTTY) {
@@ -1118,10 +1353,18 @@ var Bar = class {
1118
1353
  `);
1119
1354
  return this;
1120
1355
  }
1121
- message(string) {
1356
+ update(string) {
1122
1357
  this.text = string;
1123
1358
  return this;
1124
1359
  }
1360
+ log(message, glyph = `${FAINT}\xB7${RESET}`) {
1361
+ if (process.stdout.isTTY) {
1362
+ this.clear();
1363
+ }
1364
+ process.stdout.write(`${glyph ? `${glyph} ` : ""}${message}
1365
+ `);
1366
+ return this;
1367
+ }
1125
1368
  track(total, options) {
1126
1369
  this._total = total;
1127
1370
  this._tickCount = 0;
@@ -1168,6 +1411,7 @@ var Bar = class {
1168
1411
  }
1169
1412
  succeed(string) {
1170
1413
  this.running = false;
1414
+ if (_Bar.current === this) _Bar.current = null;
1171
1415
  if (this._isManaged) {
1172
1416
  const glyph = this._glyphs ? colorText(this._successColor, "\u2714") + " " : "";
1173
1417
  this._managedFinalLine = `${glyph}${string ?? ""}`;
@@ -1191,6 +1435,7 @@ var Bar = class {
1191
1435
  }
1192
1436
  fail(string) {
1193
1437
  this.running = false;
1438
+ if (_Bar.current === this) _Bar.current = null;
1194
1439
  if (this._isManaged) {
1195
1440
  const glyph = this._glyphs ? colorText(this._failColor, "\u2716") + " " : "";
1196
1441
  this._managedFinalLine = `${glyph}${string ?? ""}`;
@@ -1214,6 +1459,7 @@ var Bar = class {
1214
1459
  }
1215
1460
  warn(string) {
1216
1461
  this.running = false;
1462
+ if (_Bar.current === this) _Bar.current = null;
1217
1463
  if (this._isManaged) {
1218
1464
  const glyph = this._glyphs ? colorText(this._warnColor, "\u26A0") + " " : "";
1219
1465
  this._managedFinalLine = `${glyph}${string ?? ""}`;
@@ -1237,6 +1483,7 @@ var Bar = class {
1237
1483
  }
1238
1484
  info(string) {
1239
1485
  this.running = false;
1486
+ if (_Bar.current === this) _Bar.current = null;
1240
1487
  if (this._isManaged) {
1241
1488
  const glyph = this._glyphs ? colorText(this._infoColor, "\u2139") + " " : "";
1242
1489
  this._managedFinalLine = `${glyph}${string ?? ""}`;
@@ -1404,7 +1651,8 @@ var Bar = class {
1404
1651
  }, this.interval);
1405
1652
  }
1406
1653
  };
1407
- Bar.COLORS = {
1654
+ _Bar.current = null;
1655
+ _Bar.COLORS = {
1408
1656
  blueRed: ["#0000ff", "#ff0000"],
1409
1657
  redBlue: ["#ff0000", "#0000ff"],
1410
1658
  rainbow: ["#ff0000", "#ff7f00", "#ffff00", "#00ff00", "#0000ff", "#8b00ff"],
@@ -1412,6 +1660,7 @@ Bar.COLORS = {
1412
1660
  cool: ["#00ffff", "#0000ff", "#8b00ff"],
1413
1661
  sunset: ["#8b00ff", "#ff0000", "#ff7f00"]
1414
1662
  };
1663
+ var Bar = _Bar;
1415
1664
 
1416
1665
  // src/models/Chart.ts
1417
1666
  var Chart_exports = {};
@@ -1935,7 +2184,7 @@ var MultiBar = class {
1935
2184
  this._interval = options.interval ?? 35;
1936
2185
  }
1937
2186
  // Add a bar to the group. Must be called before start().
1938
- // Returns the Bar instance — use .message(), .progress, .tick(), .succeed(), etc.
2187
+ // Returns the Bar instance — use .update(), .progress, .tick(), .succeed(), etc.
1939
2188
  add(options = {}) {
1940
2189
  const bar = new Bar(options);
1941
2190
  bar._isManaged = true;
@@ -2031,7 +2280,7 @@ var MultiSelect = class {
2031
2280
  const checked = /* @__PURE__ */ new Set();
2032
2281
  let error = null;
2033
2282
  let lastDrawnLines = 0;
2034
- process.stdout.write(HIDE_CURSOR);
2283
+ process.stdout.write(HIDE_CURSOR + DISABLE_WRAP);
2035
2284
  const glyph = this.promptGlyph ? `${colorText(this.promptColor, this.promptGlyph)} ` : "";
2036
2285
  const indent = " ".repeat(this.promptGlyph ? stringLength(this.promptGlyph) + 1 : 0);
2037
2286
  process.stdout.write(`${glyph}${prompt}
@@ -2041,16 +2290,26 @@ var MultiSelect = class {
2041
2290
  const q = searchQuery.toLowerCase();
2042
2291
  return items.map((item, i) => ({ item, originalIndex: i })).filter(({ item }) => item.label.toLowerCase().includes(q) || (item.description?.toLowerCase().includes(q) ?? false));
2043
2292
  };
2293
+ const computeMaxHeight = () => {
2294
+ const termRows = process.stdout.rows ?? 24;
2295
+ const reserved = 1 + (this.searchEnabled ? 1 : 0) + 1 + 1;
2296
+ const fit = Math.max(1, termRows - reserved - 1);
2297
+ return this.maxHeight ? Math.min(this.maxHeight, fit) : fit;
2298
+ };
2044
2299
  const renderList = (redraw) => {
2045
2300
  const filtered = getFiltered();
2046
2301
  if (cursor >= filtered.length) cursor = Math.max(0, filtered.length - 1);
2047
- if (this.maxHeight) {
2302
+ const maxHeight = computeMaxHeight();
2303
+ const useViewport = filtered.length > maxHeight;
2304
+ if (useViewport) {
2048
2305
  if (cursor < viewportOffset) viewportOffset = cursor;
2049
- else if (cursor >= viewportOffset + this.maxHeight) viewportOffset = cursor - this.maxHeight + 1;
2050
- viewportOffset = Math.max(0, Math.min(viewportOffset, Math.max(0, filtered.length - this.maxHeight)));
2306
+ else if (cursor >= viewportOffset + maxHeight) viewportOffset = cursor - maxHeight + 1;
2307
+ viewportOffset = Math.max(0, Math.min(viewportOffset, Math.max(0, filtered.length - maxHeight)));
2308
+ } else {
2309
+ viewportOffset = 0;
2051
2310
  }
2052
- const visibleStart = this.maxHeight ? viewportOffset : 0;
2053
- const visibleEnd = this.maxHeight ? Math.min(filtered.length, viewportOffset + this.maxHeight) : filtered.length;
2311
+ const visibleStart = useViewport ? viewportOffset : 0;
2312
+ const visibleEnd = useViewport ? Math.min(filtered.length, viewportOffset + maxHeight) : filtered.length;
2054
2313
  if (redraw) {
2055
2314
  if (lastDrawnLines > 0) process.stdout.write(CURSOR_UP3(lastDrawnLines));
2056
2315
  process.stdout.write("\r\x1B[0J");
@@ -2066,14 +2325,15 @@ var MultiSelect = class {
2066
2325
  const { item, originalIndex } = filtered[vi];
2067
2326
  const isCursor = vi === cursor;
2068
2327
  const isChecked = checked.has(originalIndex);
2328
+ const numStr = `${vi + 1}.`;
2069
2329
  const desc = item.description ? ` ${colorText(this.descriptionColor, `\u2014 ${item.description}`)}` : "";
2070
2330
  const checkMark = isChecked ? pulse ? `${pulse}${this.checkedPrefix}${RESET}` : colorText(this.promptColor, this.checkedPrefix) : this.uncheckedPrefix;
2071
2331
  const label = isCursor ? pulse ? `${pulse}${item.label}${RESET}` : colorText(this.promptColor, item.label) : item.label;
2072
- process.stdout.write(`\r${indent}${checkMark} ${label}${desc}
2332
+ process.stdout.write(`\r${indent}${numStr} ${checkMark} ${label}${desc}
2073
2333
  `);
2074
2334
  lastDrawnLines++;
2075
2335
  }
2076
- const hintContent = `\u2191\u2193 move space/tab toggle \u2190\u2192 deselect/select${this.searchEnabled ? " type to filter" : " a all"} enter confirm`;
2336
+ const hintContent = `\u2191\u2193 move space/tab toggle \u2190\u2192 deselect/select${this.searchEnabled ? " type to filter" : " 1-9 jump a all"} enter confirm`;
2077
2337
  const maxHintCols = Math.max(10, (process.stdout.columns ?? 80) - stringLength(indent) - 1);
2078
2338
  const hint = stringLength(hintContent) > maxHintCols ? hintContent.slice(0, maxHintCols) : hintContent;
2079
2339
  process.stdout.write(`\r${indent}${DIM2}${hint}${RESET}
@@ -2096,7 +2356,7 @@ var MultiSelect = class {
2096
2356
  process.stdin.setRawMode(false);
2097
2357
  process.stdin.pause();
2098
2358
  process.stdin.removeListener("data", onKey);
2099
- process.stdout.write(SHOW_CURSOR);
2359
+ process.stdout.write(ENABLE_WRAP + SHOW_CURSOR);
2100
2360
  });
2101
2361
  const cleanup = (result) => {
2102
2362
  deregisterCleanup();
@@ -2106,6 +2366,7 @@ var MultiSelect = class {
2106
2366
  }
2107
2367
  if (lastDrawnLines > 0) process.stdout.write(CURSOR_UP3(lastDrawnLines));
2108
2368
  process.stdout.write("\x1B[0J");
2369
+ process.stdout.write(ENABLE_WRAP);
2109
2370
  const bulletWidth = stringLength(this.checkedPrefix) + 1;
2110
2371
  for (let i = 0; i < items.length; i++) {
2111
2372
  const item = items[i];
@@ -2117,7 +2378,7 @@ var MultiSelect = class {
2117
2378
  process.stdin.setRawMode(false);
2118
2379
  process.stdin.pause();
2119
2380
  process.stdin.removeListener("data", onKey);
2120
- process.stdout.write(SHOW_CURSOR);
2381
+ process.stdout.write(ENABLE_WRAP + SHOW_CURSOR);
2121
2382
  resolve(result);
2122
2383
  };
2123
2384
  if (this._parsedColors.length >= 2) {
@@ -2206,7 +2467,7 @@ var MultiSelect = class {
2206
2467
  process.stdin.setRawMode(false);
2207
2468
  process.stdin.pause();
2208
2469
  process.stdin.removeListener("data", onKey);
2209
- process.stdout.write(SHOW_CURSOR);
2470
+ process.stdout.write(ENABLE_WRAP + SHOW_CURSOR);
2210
2471
  process.exit(130);
2211
2472
  } else if (this.searchEnabled) {
2212
2473
  if (str === "\x7F" || str === "\b") {
@@ -2226,7 +2487,7 @@ var MultiSelect = class {
2226
2487
  const n = parseInt(str);
2227
2488
  if (!isNaN(n) && n >= 1 && n <= Math.min(filtered.length, 9)) {
2228
2489
  error = null;
2229
- cursor = viewportOffset + n - 1;
2490
+ cursor = n - 1;
2230
2491
  renderList(true);
2231
2492
  }
2232
2493
  }
@@ -2403,7 +2664,7 @@ async function scrollbox(lines, options) {
2403
2664
 
2404
2665
  // src/models/Spinner.ts
2405
2666
  var _Spinner = class _Spinner {
2406
- constructor(options = {}) {
2667
+ constructor(text, options) {
2407
2668
  this._colors = [];
2408
2669
  this._parsedColors = [];
2409
2670
  this._bgColors = [];
@@ -2412,24 +2673,25 @@ var _Spinner = class _Spinner {
2412
2673
  this._shimmerPhase = 0;
2413
2674
  this._lastLineLength = 0;
2414
2675
  this._cleanupDeregister = null;
2676
+ const opts = typeof text === "string" ? { ...options, text } : text ?? {};
2415
2677
  this.running = false;
2416
2678
  this.frameIndex = 0;
2417
- this.frames = options.frames ?? (config.glyphs ? _Spinner.FRAMES.braille : _Spinner.FRAMES.line);
2418
- this.prefixString = options.prefix ?? "";
2419
- this.suffixString = options.suffix ?? "";
2420
- this.text = options.text ?? "";
2421
- this.reverse = options.reverse ?? false;
2422
- this.interval = options.interval ?? 80;
2423
- this.colorCycle = options.colorCycle ?? 1;
2424
- this.shimmer = options.shimmer ?? 0;
2425
- this.onSpin = options.onSpin;
2426
- this.colors = options.colors ?? ["#c026d3", "#e879f9"];
2427
- this.bgColors = options.bgColors ?? [];
2428
- this._successColor = options.successColor ?? GREEN;
2429
- this._failColor = options.failColor ?? RED;
2430
- this._warnColor = options.warnColor ?? YELLOW;
2431
- this._infoColor = options.infoColor ?? BLUE;
2432
- this._glyphs = options.glyphs ?? config.glyphs;
2679
+ this.frames = opts.frames ?? (config.glyphs ? _Spinner.FRAMES.braille : _Spinner.FRAMES.line);
2680
+ this._prefix = opts.prefix ?? "";
2681
+ this._suffix = opts.suffix ?? "";
2682
+ this.text = opts.text ?? "";
2683
+ this.reverse = opts.reverse ?? false;
2684
+ this.interval = opts.interval ?? 80;
2685
+ this.colorCycle = opts.colorCycle ?? 1;
2686
+ this.shimmer = opts.shimmer ?? 0;
2687
+ this.onSpin = opts.onSpin;
2688
+ this.colors = opts.colors ?? ["#c026d3", "#e879f9"];
2689
+ this.bgColors = opts.bgColors ?? [];
2690
+ this._successColor = opts.successColor ?? GREEN;
2691
+ this._failColor = opts.failColor ?? RED;
2692
+ this._warnColor = opts.warnColor ?? YELLOW;
2693
+ this._infoColor = opts.infoColor ?? BLUE;
2694
+ this._glyphs = opts.glyphs ?? config.glyphs;
2433
2695
  }
2434
2696
  get colors() {
2435
2697
  return this._colors;
@@ -2448,6 +2710,7 @@ var _Spinner = class _Spinner {
2448
2710
  start() {
2449
2711
  if (!process.stdout.isTTY) return;
2450
2712
  this.running = true;
2713
+ _Spinner.current = this;
2451
2714
  process.stdout.write(HIDE_CURSOR);
2452
2715
  this._cleanupDeregister = registerCleanup(() => {
2453
2716
  this.running = false;
@@ -2458,6 +2721,7 @@ var _Spinner = class _Spinner {
2458
2721
  }
2459
2722
  stop(message) {
2460
2723
  this.running = false;
2724
+ if (_Spinner.current === this) _Spinner.current = null;
2461
2725
  this._cleanupDeregister?.();
2462
2726
  this._cleanupDeregister = null;
2463
2727
  if (process.stdout.isTTY) {
@@ -2472,8 +2736,17 @@ var _Spinner = class _Spinner {
2472
2736
  this.text = string;
2473
2737
  return this;
2474
2738
  }
2739
+ prefix(string) {
2740
+ this._prefix = string;
2741
+ return this;
2742
+ }
2743
+ suffix(string) {
2744
+ this._suffix = string;
2745
+ return this;
2746
+ }
2475
2747
  succeed(string) {
2476
2748
  this.running = false;
2749
+ if (_Spinner.current === this) _Spinner.current = null;
2477
2750
  this._cleanupDeregister?.();
2478
2751
  this._cleanupDeregister = null;
2479
2752
  if (process.stdout.isTTY) {
@@ -2489,6 +2762,7 @@ var _Spinner = class _Spinner {
2489
2762
  }
2490
2763
  fail(string) {
2491
2764
  this.running = false;
2765
+ if (_Spinner.current === this) _Spinner.current = null;
2492
2766
  this._cleanupDeregister?.();
2493
2767
  this._cleanupDeregister = null;
2494
2768
  if (process.stdout.isTTY) {
@@ -2504,6 +2778,7 @@ var _Spinner = class _Spinner {
2504
2778
  }
2505
2779
  warn(string) {
2506
2780
  this.running = false;
2781
+ if (_Spinner.current === this) _Spinner.current = null;
2507
2782
  this._cleanupDeregister?.();
2508
2783
  this._cleanupDeregister = null;
2509
2784
  if (process.stdout.isTTY) {
@@ -2519,6 +2794,7 @@ var _Spinner = class _Spinner {
2519
2794
  }
2520
2795
  info(string) {
2521
2796
  this.running = false;
2797
+ if (_Spinner.current === this) _Spinner.current = null;
2522
2798
  this._cleanupDeregister?.();
2523
2799
  this._cleanupDeregister = null;
2524
2800
  if (process.stdout.isTTY) {
@@ -2554,9 +2830,9 @@ var _Spinner = class _Spinner {
2554
2830
  }
2555
2831
  run() {
2556
2832
  const frame = this.coloredFrame();
2557
- const t = this.text;
2558
- const content = this.reverse ? t ? `${t} ${frame}` : frame : t ? `${frame} ${t}` : frame;
2559
- const raw = `${this.prefixString}${content}${this.suffixString}`;
2833
+ const label = [this._prefix, this.text, this._suffix].filter(Boolean).join(" ");
2834
+ const content = this.reverse ? label ? `${label} ${frame}` : frame : label ? `${frame} ${label}` : frame;
2835
+ const raw = content;
2560
2836
  const displayLen = stringLength(raw);
2561
2837
  const padding = " ".repeat(Math.max(0, this._lastLineLength - displayLen));
2562
2838
  this._lastLineLength = displayLen;
@@ -2574,6 +2850,7 @@ var _Spinner = class _Spinner {
2574
2850
  }, this.interval);
2575
2851
  }
2576
2852
  };
2853
+ _Spinner.current = null;
2577
2854
  _Spinner.FRAMES = {
2578
2855
  braille: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
2579
2856
  dots: ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"],
@@ -2584,7 +2861,7 @@ _Spinner.FRAMES = {
2584
2861
  var Spinner = _Spinner;
2585
2862
 
2586
2863
  // src/models/Table.ts
2587
- import cosmetic2 from "cosmetic";
2864
+ import cosmetic3 from "cosmetic";
2588
2865
 
2589
2866
  // src/utils/padSides.ts
2590
2867
  var padSides = (string, padding) => {
@@ -2662,7 +2939,7 @@ var Table = class {
2662
2939
  header += pad(column.title, column.padding + this.margin);
2663
2940
  if (i < keys.length - 1) header += this.separator;
2664
2941
  }
2665
- const styled = typeof config.color === "number" ? cosmetic2.xterm(config.color) : config.color.startsWith("#") ? cosmetic2.hex(config.color) : cosmetic2[config.color];
2942
+ const styled = typeof config.color === "number" ? cosmetic3.xterm(config.color) : config.color.startsWith("#") ? cosmetic3.hex(config.color) : cosmetic3[config.color];
2666
2943
  this.string += `${styled.underline.encoder(header)}
2667
2944
  `;
2668
2945
  for (const [ri, row] of this.rows.entries()) {
@@ -2739,7 +3016,7 @@ var commandDefaults = {};
2739
3016
  var Program = {
2740
3017
  command: (name, variables, info) => {
2741
3018
  const cmd = new Command(Object.assign({ name, variables, info }, commandDefaults));
2742
- if (!base) base = cmd;
3019
+ base = cmd;
2743
3020
  return cmd;
2744
3021
  },
2745
3022
  option: (short, long, variables, info) => new Option({ short, long, variables, info }),
@@ -2762,6 +3039,10 @@ var Program = {
2762
3039
  },
2763
3040
  setDefaults: (data) => {
2764
3041
  commandDefaults = data;
3042
+ },
3043
+ shell: async (options) => {
3044
+ if (!base) throw new Error("No command defined");
3045
+ return new Shell(base, options).run();
2765
3046
  }
2766
3047
  };
2767
3048
  export {
@@ -2778,6 +3059,7 @@ export {
2778
3059
  Program,
2779
3060
  Scrollbox,
2780
3061
  Select,
3062
+ Shell,
2781
3063
  Spinner,
2782
3064
  Table,
2783
3065
  TermKit,