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.js CHANGED
@@ -32,7 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  Bar: () => Bar,
34
34
  Chart: () => Chart_exports,
35
- Color: () => import_cosmetic4.default,
35
+ Color: () => import_cosmetic5.default,
36
36
  Column: () => Column,
37
37
  Command: () => Command,
38
38
  Input: () => Input,
@@ -43,6 +43,7 @@ __export(index_exports, {
43
43
  Program: () => Program,
44
44
  Scrollbox: () => Scrollbox,
45
45
  Select: () => Select,
46
+ Shell: () => Shell,
46
47
  Spinner: () => Spinner,
47
48
  Table: () => Table,
48
49
  TermKit: () => TermKit,
@@ -191,6 +192,8 @@ function findCommand(array, commands) {
191
192
  var RESET = "\x1B[0m";
192
193
  var SHOW_CURSOR = "\x1B[?25h";
193
194
  var HIDE_CURSOR = "\x1B[?25l";
195
+ var DISABLE_WRAP = "\x1B[?7l";
196
+ var ENABLE_WRAP = "\x1B[?7h";
194
197
  var BOLD = "\x1B[1m";
195
198
  var FAINT = "\x1B[2m";
196
199
  var GREEN = "\x1B[32m";
@@ -557,15 +560,22 @@ var Select = class {
557
560
  if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("Select requires an interactive terminal");
558
561
  let selectedIndex = 0;
559
562
  let viewportOffset = 0;
563
+ let visibleStart = 0;
560
564
  let searchQuery = "";
561
565
  let lastDrawnLines = 0;
562
566
  const skipItem = { label: this.skipLabel };
567
+ const computeMaxHeight = () => {
568
+ const termRows = process.stdout.rows ?? 24;
569
+ const reserved = 1 + (this.searchEnabled ? 1 : 0);
570
+ const fit = Math.max(1, termRows - reserved - 1);
571
+ return this.maxHeight ? Math.min(this.maxHeight, fit) : fit;
572
+ };
563
573
  const getFiltered = () => {
564
574
  if (!this.searchEnabled || searchQuery === "") return items;
565
575
  const q = searchQuery.toLowerCase();
566
576
  return items.filter((item) => item.label.toLowerCase().includes(q) || (item.description?.toLowerCase().includes(q) ?? false));
567
577
  };
568
- process.stdout.write(HIDE_CURSOR);
578
+ process.stdout.write(HIDE_CURSOR + DISABLE_WRAP);
569
579
  const glyph = this.promptGlyph ? `${colorText(this.promptColor, this.promptGlyph)} ` : "";
570
580
  const indent = " ".repeat(this.promptGlyph ? stringLength(this.promptGlyph) + 1 : 0);
571
581
  process.stdout.write(`${glyph}${prompt}
@@ -574,13 +584,17 @@ var Select = class {
574
584
  const filtered = getFiltered();
575
585
  const allItems = [...filtered, skipItem];
576
586
  if (selectedIndex >= allItems.length) selectedIndex = allItems.length - 1;
577
- if (this.maxHeight) {
587
+ const maxHeight = computeMaxHeight();
588
+ const useViewport = allItems.length > maxHeight;
589
+ if (useViewport) {
578
590
  if (selectedIndex < viewportOffset) viewportOffset = selectedIndex;
579
- else if (selectedIndex >= viewportOffset + this.maxHeight) viewportOffset = selectedIndex - this.maxHeight + 1;
580
- viewportOffset = Math.max(0, Math.min(viewportOffset, Math.max(0, allItems.length - this.maxHeight)));
591
+ else if (selectedIndex >= viewportOffset + maxHeight) viewportOffset = selectedIndex - maxHeight + 1;
592
+ viewportOffset = Math.max(0, Math.min(viewportOffset, Math.max(0, allItems.length - maxHeight)));
593
+ } else {
594
+ viewportOffset = 0;
581
595
  }
582
- const visibleStart = this.maxHeight ? viewportOffset : 0;
583
- const visibleEnd = this.maxHeight ? Math.min(allItems.length, viewportOffset + this.maxHeight) : allItems.length;
596
+ visibleStart = useViewport ? viewportOffset : 0;
597
+ const visibleEnd = useViewport ? Math.min(allItems.length, viewportOffset + maxHeight) : allItems.length;
584
598
  if (redraw) {
585
599
  if (lastDrawnLines > 0) process.stdout.write(CURSOR_UP(lastDrawnLines));
586
600
  process.stdout.write("\r\x1B[0J");
@@ -596,8 +610,7 @@ var Select = class {
596
610
  const item = allItems[i];
597
611
  const isSelected = i === selectedIndex;
598
612
  const isSkip = i === filtered.length;
599
- const relativeNum = i - visibleStart + 1;
600
- const numStr = isSkip ? "0." : `${relativeNum}.`;
613
+ const numStr = isSkip ? "0." : `${i + 1}.`;
601
614
  const desc = item.description ? ` ${colorText(this.descriptionColor, `\u2014 ${item.description}`)}` : "";
602
615
  let marker, tail;
603
616
  if (isSelected && pulse) {
@@ -624,7 +637,7 @@ var Select = class {
624
637
  process.stdin.setRawMode(false);
625
638
  process.stdin.pause();
626
639
  process.stdin.removeListener("data", onKey);
627
- process.stdout.write(SHOW_CURSOR);
640
+ process.stdout.write(ENABLE_WRAP + SHOW_CURSOR);
628
641
  });
629
642
  const cleanup = () => {
630
643
  deregisterCleanup();
@@ -635,7 +648,7 @@ var Select = class {
635
648
  process.stdin.setRawMode(false);
636
649
  process.stdin.pause();
637
650
  process.stdin.removeListener("data", onKey);
638
- process.stdout.write(SHOW_CURSOR);
651
+ process.stdout.write(ENABLE_WRAP + SHOW_CURSOR);
639
652
  };
640
653
  if (this._parsedColors.length >= 2) {
641
654
  timer = setInterval(() => {
@@ -678,8 +691,7 @@ var Select = class {
678
691
  } else {
679
692
  const n = parseInt(str);
680
693
  if (!isNaN(n) && n >= 0 && n <= Math.min(items.length, 9)) {
681
- const visibleStart = this.maxHeight ? viewportOffset : 0;
682
- selectedIndex = n === 0 ? allItems.length - 1 : visibleStart + n - 1;
694
+ selectedIndex = n === 0 ? allItems.length - 1 : n - 1;
683
695
  selectedIndex = Math.max(0, Math.min(selectedIndex, allItems.length - 1));
684
696
  renderList(true);
685
697
  }
@@ -860,7 +872,7 @@ var Command = class {
860
872
  constructor(data) {
861
873
  this.actionFunction = null;
862
874
  this.commandsArray = [];
863
- this.commandStrings = ["help"];
875
+ this.commandStrings = ["help", "version"];
864
876
  this.info = null;
865
877
  this.middlewaresArray = [];
866
878
  this.name = null;
@@ -930,6 +942,9 @@ var Command = class {
930
942
  const recursive = source?.includes("-r") === true || source?.includes("--recursive") === true;
931
943
  this.printHelp(this.name ?? "Program", recursive);
932
944
  }
945
+ printVersion() {
946
+ console.log(this.versionString ?? "");
947
+ }
933
948
  printHelp(fullName, recursive) {
934
949
  const table = [];
935
950
  let program = fullName;
@@ -989,29 +1004,32 @@ var Command = class {
989
1004
  }
990
1005
  }
991
1006
  }
992
- async parse(input2) {
993
- const array = [...input2];
994
- array.splice(0, 2);
1007
+ async _execute(tokens) {
1008
+ const array = [...tokens];
995
1009
  let command = this;
996
1010
  const options = { _source: Array.from(array) };
1011
+ if (this.versionString && (array.includes("--version") || array.includes("-V"))) {
1012
+ return this.printVersion();
1013
+ }
997
1014
  const ddIdx = array.indexOf("--");
998
1015
  if (ddIdx !== -1) {
999
1016
  options._ = array.splice(ddIdx + 1);
1000
1017
  array.splice(ddIdx, 1);
1001
1018
  }
1002
1019
  while (array.length) {
1003
- if (!array.includes("help")) {
1020
+ if (!array.includes("help") && !array.includes("version")) {
1004
1021
  Object.assign(options, await findOptions(array, command));
1005
1022
  const cmdVars = await findCommandVariables(array, command);
1006
1023
  if (cmdVars) Object.assign(options, cmdVars);
1007
1024
  Object.assign(options, await findOptions(array, command));
1008
1025
  }
1009
1026
  if (array.length) {
1010
- if (!array.includes("help")) {
1027
+ if (!array.includes("help") && !array.includes("version")) {
1011
1028
  for (const mw of command.middlewaresArray) await mw(options);
1012
1029
  }
1013
1030
  const next = findCommand(array, command.commandsArray);
1014
1031
  if (!next && array[0] === "help") return command.help(options._source);
1032
+ if (!next && array[0] === "version") return this.printVersion();
1015
1033
  if (!next) throw new SyntaxError(`Unknown command: ${array[0]}`);
1016
1034
  const name = command.name ?? "_base";
1017
1035
  if (!options._parents) options._parents = {};
@@ -1035,14 +1053,229 @@ var Command = class {
1035
1053
  }
1036
1054
  for (const mw of command.middlewaresArray) await mw(options);
1037
1055
  if (command.actionFunction) return command.actionFunction(options);
1038
- if (options._source.length === 2) return command.help(options._source);
1039
- throw new Error(`No action for command: ${command.name ?? "_base"}`);
1056
+ return command.help(options._source);
1057
+ }
1058
+ async parse(input2) {
1059
+ return this._execute(input2.slice(2));
1060
+ }
1061
+ };
1062
+
1063
+ // src/models/Shell.ts
1064
+ var import_cosmetic2 = __toESM(require("cosmetic"));
1065
+ var readline = __toESM(require("readline"));
1066
+ var Shell = class {
1067
+ constructor(root, opts = {}) {
1068
+ this.rl = null;
1069
+ this.root = root;
1070
+ this.opts = {
1071
+ mode: opts.mode ?? "drill",
1072
+ prompt: opts.prompt ?? root.name ?? "shell",
1073
+ promptColor: opts.promptColor ?? "",
1074
+ banner: opts.banner ?? "",
1075
+ exitCommands: opts.exitCommands ?? ["exit", "quit"],
1076
+ historySize: opts.historySize ?? 100
1077
+ };
1078
+ }
1079
+ async run() {
1080
+ this.rl = readline.createInterface({
1081
+ input: process.stdin,
1082
+ output: process.stdout,
1083
+ terminal: true,
1084
+ historySize: this.opts.historySize,
1085
+ completer: this.opts.mode === "free" ? makeCompleter(this.root) : void 0
1086
+ });
1087
+ if (this.opts.banner) console.log(this.opts.banner);
1088
+ try {
1089
+ if (this.opts.mode === "free") {
1090
+ await this.freeLoop();
1091
+ } else {
1092
+ await this.drillLoop();
1093
+ }
1094
+ } finally {
1095
+ this.rl.close();
1096
+ }
1097
+ }
1098
+ // ── Drill mode ────────────────────────────────────────────────────────
1099
+ async drillLoop() {
1100
+ while (true) {
1101
+ const exited = await this.drillFrom(this.root, [this.root.name ?? "shell"]);
1102
+ if (exited) return;
1103
+ }
1104
+ }
1105
+ async drillFrom(cmd, breadcrumb) {
1106
+ while (true) {
1107
+ const token = await this.promptDrill(cmd, breadcrumb);
1108
+ if (token === null) {
1109
+ return breadcrumb.length === 1;
1110
+ }
1111
+ if (this.opts.exitCommands.includes(token)) process.exit(0);
1112
+ if (token === "..") return false;
1113
+ if (token === "help") {
1114
+ cmd.help();
1115
+ continue;
1116
+ }
1117
+ const sub = cmd.commandsArray.find((c) => c.name === token);
1118
+ if (!sub) {
1119
+ process.stderr.write(`Unknown command: ${token}
1120
+ `);
1121
+ continue;
1122
+ }
1123
+ if (sub.commandsArray.length > 0) {
1124
+ const exited = await this.drillFrom(sub, [...breadcrumb, sub.name ?? ""]);
1125
+ if (exited) return true;
1126
+ continue;
1127
+ }
1128
+ const vars = await this.gatherVariables(sub);
1129
+ const tokens = buildTokens(sub, vars);
1130
+ try {
1131
+ await sub._execute(tokens);
1132
+ } catch (err) {
1133
+ process.stderr.write(`${err instanceof Error ? err.message : err}
1134
+ `);
1135
+ }
1136
+ return false;
1137
+ }
1138
+ }
1139
+ async promptDrill(cmd, breadcrumb) {
1140
+ const subs = cmd.commandsArray.map((c) => c.name ?? "").filter(Boolean);
1141
+ const label = this.colorize(breadcrumb.join(" "));
1142
+ const choices = [...subs, "help"].join(", ");
1143
+ process.stdout.write(`
1144
+ ${label} ${choices}
1145
+ `);
1146
+ return new Promise((resolve) => {
1147
+ let resolved = false;
1148
+ const done = (val) => {
1149
+ if (!resolved) {
1150
+ resolved = true;
1151
+ resolve(val);
1152
+ }
1153
+ };
1154
+ this.rl.question("> ", (answer) => done(answer.trim() || null));
1155
+ this.rl.once("close", () => done(null));
1156
+ });
1157
+ }
1158
+ async gatherVariables(cmd) {
1159
+ const result = {};
1160
+ if (!cmd.variables) return result;
1161
+ for (const v of cmd.variables) {
1162
+ const name = v.name ?? "value";
1163
+ const hint = v.hint ? ` ${v.hint}` : "";
1164
+ while (true) {
1165
+ const answer = await new Promise((resolve) => {
1166
+ let resolved = false;
1167
+ const done = (val) => {
1168
+ if (!resolved) {
1169
+ resolved = true;
1170
+ resolve(val);
1171
+ }
1172
+ };
1173
+ this.rl.question(` ${name}${hint}: `, (ans) => done(ans.trim() || null));
1174
+ this.rl.once("close", () => done(null));
1175
+ });
1176
+ const value = answer ?? v.default ?? null;
1177
+ if (!value && v.required) {
1178
+ process.stderr.write(` ${name} is required
1179
+ `);
1180
+ continue;
1181
+ }
1182
+ if (value) {
1183
+ if (v.type === "enum" && v.enum && !v.enum.includes(value)) {
1184
+ process.stderr.write(` Must be one of: ${v.enum.join(", ")}
1185
+ `);
1186
+ continue;
1187
+ }
1188
+ result[name] = value;
1189
+ }
1190
+ break;
1191
+ }
1192
+ }
1193
+ return result;
1194
+ }
1195
+ // ── Free mode ─────────────────────────────────────────────────────────
1196
+ async freeLoop() {
1197
+ const prompt = `${this.colorize(this.opts.prompt)} > `;
1198
+ this.rl.setPrompt(prompt);
1199
+ this.rl.prompt();
1200
+ for await (const line of this.rl) {
1201
+ const trimmed = line.trim();
1202
+ if (!trimmed) {
1203
+ this.rl.prompt();
1204
+ continue;
1205
+ }
1206
+ if (this.opts.exitCommands.includes(trimmed)) break;
1207
+ const tokens = tokenize(trimmed);
1208
+ try {
1209
+ await this.root._execute(tokens);
1210
+ } catch (err) {
1211
+ process.stderr.write(`${err instanceof Error ? err.message : err}
1212
+ `);
1213
+ }
1214
+ this.rl.prompt();
1215
+ }
1216
+ }
1217
+ // ── Helpers ───────────────────────────────────────────────────────────
1218
+ colorize(text) {
1219
+ const c = this.opts.promptColor;
1220
+ if (!c || !process.stdout.isTTY) return text;
1221
+ try {
1222
+ if (c.startsWith("#")) return import_cosmetic2.default.hex(c).encoder(text);
1223
+ if (/^\d+$/.test(c)) return import_cosmetic2.default.xterm(Number(c)).encoder(text);
1224
+ const style = import_cosmetic2.default[c];
1225
+ if (style && typeof style.encoder === "function") return style.encoder(text);
1226
+ } catch {
1227
+ }
1228
+ return text;
1040
1229
  }
1041
1230
  };
1231
+ function tokenize(line) {
1232
+ const tokens = [];
1233
+ let current = "";
1234
+ let inQuote = null;
1235
+ for (const ch of line) {
1236
+ if (inQuote) {
1237
+ if (ch === inQuote) {
1238
+ inQuote = null;
1239
+ } else {
1240
+ current += ch;
1241
+ }
1242
+ } else if (ch === '"' || ch === "'") {
1243
+ inQuote = ch;
1244
+ } else if (ch === " " || ch === " ") {
1245
+ if (current) {
1246
+ tokens.push(current);
1247
+ current = "";
1248
+ }
1249
+ } else {
1250
+ current += ch;
1251
+ }
1252
+ }
1253
+ if (current) tokens.push(current);
1254
+ return tokens;
1255
+ }
1256
+ function buildTokens(cmd, vars) {
1257
+ if (!cmd.variables) return [];
1258
+ return cmd.variables.map((v) => vars[v.name ?? ""]).filter((v) => v !== void 0 && v !== "");
1259
+ }
1260
+ function makeCompleter(root) {
1261
+ return (line) => {
1262
+ const tokens = tokenize(line);
1263
+ let cmd = root;
1264
+ for (const token of tokens.slice(0, -1)) {
1265
+ const sub = cmd.commandsArray.find((c) => c.name === token);
1266
+ if (!sub) break;
1267
+ cmd = sub;
1268
+ }
1269
+ const partial = tokens[tokens.length - 1] ?? "";
1270
+ const names = [...cmd.commandsArray.map((c) => c.name ?? "").filter(Boolean), "help"];
1271
+ const hits = names.filter((n) => n.startsWith(partial));
1272
+ return [hits.length ? hits : names, partial];
1273
+ };
1274
+ }
1042
1275
 
1043
1276
  // src/models/Bar.ts
1044
- var Bar = class {
1045
- constructor(options = {}) {
1277
+ var _Bar = class _Bar {
1278
+ constructor(text, options) {
1046
1279
  // used by MultiBar — not intended for direct external use
1047
1280
  this._isManaged = false;
1048
1281
  this._managedFinalLine = null;
@@ -1060,37 +1293,38 @@ var Bar = class {
1060
1293
  this._tickCount = 0;
1061
1294
  this._startTime = null;
1062
1295
  this._etaSuffix = "";
1296
+ const opts = typeof text === "string" ? { ...options, text } : text ?? {};
1063
1297
  this.running = false;
1064
1298
  this.forwardMotion = true;
1065
- this._autoLength = options.length === void 0;
1066
- this.length = options.length ?? process.stdout.columns ?? 80;
1067
- this.prefixString = options.prefix ?? "[";
1068
- this.suffixString = options.suffix ?? "]";
1069
- this.character = options.character ?? "\u2500\u2500";
1070
- this.beforeEmpty = options.before ?? " ";
1071
- this.afterEmpty = options.after ?? " ";
1299
+ this._autoLength = opts.length === void 0;
1300
+ this.length = opts.length ?? process.stdout.columns ?? 80;
1301
+ this.prefixString = opts.prefix ?? "[";
1302
+ this.suffixString = opts.suffix ?? "]";
1303
+ this.character = opts.character ?? "\u2500\u2500";
1304
+ this.beforeEmpty = opts.before ?? " ";
1305
+ this.afterEmpty = opts.after ?? " ";
1072
1306
  this.position = 1;
1073
- this.interval = options.interval ?? 35;
1074
- this.mode = options.mode ?? "bounce";
1075
- this.progress = options.progress;
1076
- this.colorFill = options.colorFill ?? false;
1077
- this.colorCycle = options.colorCycle ?? 0.5;
1078
- this.shimmer = options.shimmer ?? 0;
1079
- this.text = options.text ?? "";
1080
- this.reverse = options.reverse ?? false;
1081
- this.onBounce = options.onBounce;
1082
- this.onLoop = options.onLoop;
1083
- this.onComplete = options.onComplete;
1084
- this.colors = options.colors ?? ["#c026d3", "#e879f9"];
1085
- this.bgColors = options.bgColors ?? [];
1086
- this._successColor = options.successColor ?? GREEN;
1087
- this._failColor = options.failColor ?? RED;
1088
- this._warnColor = options.warnColor ?? YELLOW;
1089
- this._infoColor = options.infoColor ?? BLUE;
1090
- this._glyphs = options.glyphs ?? true;
1091
- this._showRate = options.showRate ?? false;
1092
- this._showEta = options.showEta ?? false;
1093
- this._rateUnit = options.rateUnit ?? "";
1307
+ this.interval = opts.interval ?? 35;
1308
+ this.mode = opts.mode ?? "bounce";
1309
+ this.progress = opts.progress;
1310
+ this.colorFill = opts.colorFill ?? false;
1311
+ this.colorCycle = opts.colorCycle ?? 0.5;
1312
+ this.shimmer = opts.shimmer ?? 0;
1313
+ this.text = opts.text ?? "";
1314
+ this.reverse = opts.reverse ?? false;
1315
+ this.onBounce = opts.onBounce;
1316
+ this.onLoop = opts.onLoop;
1317
+ this.onComplete = opts.onComplete;
1318
+ this.colors = opts.colors ?? ["#c026d3", "#e879f9"];
1319
+ this.bgColors = opts.bgColors ?? [];
1320
+ this._successColor = opts.successColor ?? GREEN;
1321
+ this._failColor = opts.failColor ?? RED;
1322
+ this._warnColor = opts.warnColor ?? YELLOW;
1323
+ this._infoColor = opts.infoColor ?? BLUE;
1324
+ this._glyphs = opts.glyphs ?? true;
1325
+ this._showRate = opts.showRate ?? false;
1326
+ this._showEta = opts.showEta ?? false;
1327
+ this._rateUnit = opts.rateUnit ?? "";
1094
1328
  }
1095
1329
  get colors() {
1096
1330
  return this._colors;
@@ -1139,6 +1373,7 @@ var Bar = class {
1139
1373
  if (this._isManaged) return;
1140
1374
  if (!process.stdout.isTTY) return;
1141
1375
  this.running = true;
1376
+ _Bar.current = this;
1142
1377
  process.stdout.write(HIDE_CURSOR);
1143
1378
  this._cleanupDeregister = registerCleanup(() => {
1144
1379
  this.running = false;
@@ -1164,6 +1399,7 @@ var Bar = class {
1164
1399
  this._managedFinalLine = message ?? "";
1165
1400
  return this;
1166
1401
  }
1402
+ if (_Bar.current === this) _Bar.current = null;
1167
1403
  this._cleanupDeregister?.();
1168
1404
  this._cleanupDeregister = null;
1169
1405
  if (process.stdout.isTTY) {
@@ -1178,10 +1414,18 @@ var Bar = class {
1178
1414
  `);
1179
1415
  return this;
1180
1416
  }
1181
- message(string) {
1417
+ update(string) {
1182
1418
  this.text = string;
1183
1419
  return this;
1184
1420
  }
1421
+ log(message, glyph = `${FAINT}\xB7${RESET}`) {
1422
+ if (process.stdout.isTTY) {
1423
+ this.clear();
1424
+ }
1425
+ process.stdout.write(`${glyph ? `${glyph} ` : ""}${message}
1426
+ `);
1427
+ return this;
1428
+ }
1185
1429
  track(total, options) {
1186
1430
  this._total = total;
1187
1431
  this._tickCount = 0;
@@ -1228,6 +1472,7 @@ var Bar = class {
1228
1472
  }
1229
1473
  succeed(string) {
1230
1474
  this.running = false;
1475
+ if (_Bar.current === this) _Bar.current = null;
1231
1476
  if (this._isManaged) {
1232
1477
  const glyph = this._glyphs ? colorText(this._successColor, "\u2714") + " " : "";
1233
1478
  this._managedFinalLine = `${glyph}${string ?? ""}`;
@@ -1251,6 +1496,7 @@ var Bar = class {
1251
1496
  }
1252
1497
  fail(string) {
1253
1498
  this.running = false;
1499
+ if (_Bar.current === this) _Bar.current = null;
1254
1500
  if (this._isManaged) {
1255
1501
  const glyph = this._glyphs ? colorText(this._failColor, "\u2716") + " " : "";
1256
1502
  this._managedFinalLine = `${glyph}${string ?? ""}`;
@@ -1274,6 +1520,7 @@ var Bar = class {
1274
1520
  }
1275
1521
  warn(string) {
1276
1522
  this.running = false;
1523
+ if (_Bar.current === this) _Bar.current = null;
1277
1524
  if (this._isManaged) {
1278
1525
  const glyph = this._glyphs ? colorText(this._warnColor, "\u26A0") + " " : "";
1279
1526
  this._managedFinalLine = `${glyph}${string ?? ""}`;
@@ -1297,6 +1544,7 @@ var Bar = class {
1297
1544
  }
1298
1545
  info(string) {
1299
1546
  this.running = false;
1547
+ if (_Bar.current === this) _Bar.current = null;
1300
1548
  if (this._isManaged) {
1301
1549
  const glyph = this._glyphs ? colorText(this._infoColor, "\u2139") + " " : "";
1302
1550
  this._managedFinalLine = `${glyph}${string ?? ""}`;
@@ -1464,7 +1712,8 @@ var Bar = class {
1464
1712
  }, this.interval);
1465
1713
  }
1466
1714
  };
1467
- Bar.COLORS = {
1715
+ _Bar.current = null;
1716
+ _Bar.COLORS = {
1468
1717
  blueRed: ["#0000ff", "#ff0000"],
1469
1718
  redBlue: ["#ff0000", "#0000ff"],
1470
1719
  rainbow: ["#ff0000", "#ff7f00", "#ffff00", "#00ff00", "#0000ff", "#8b00ff"],
@@ -1472,6 +1721,7 @@ Bar.COLORS = {
1472
1721
  cool: ["#00ffff", "#0000ff", "#8b00ff"],
1473
1722
  sunset: ["#8b00ff", "#ff0000", "#ff7f00"]
1474
1723
  };
1724
+ var Bar = _Bar;
1475
1725
 
1476
1726
  // src/models/Chart.ts
1477
1727
  var Chart_exports = {};
@@ -1483,7 +1733,7 @@ __export(Chart_exports, {
1483
1733
  Sparkline: () => Sparkline,
1484
1734
  VerticalBar: () => VerticalBar
1485
1735
  });
1486
- var import_cosmetic2 = __toESM(require("cosmetic"));
1736
+ var import_cosmetic3 = __toESM(require("cosmetic"));
1487
1737
 
1488
1738
  // src/utils/padLeft.ts
1489
1739
  var padLeft = (string, padding) => {
@@ -1504,9 +1754,9 @@ function formatNum(n) {
1504
1754
  }
1505
1755
  function applyConfigColor(s) {
1506
1756
  const c = config.color;
1507
- if (typeof c === "number") return import_cosmetic2.default.xterm(c).encoder(s);
1508
- if (c.startsWith("#")) return import_cosmetic2.default.hex(c).encoder(s);
1509
- return import_cosmetic2.default[c].encoder(s);
1757
+ if (typeof c === "number") return import_cosmetic3.default.xterm(c).encoder(s);
1758
+ if (c.startsWith("#")) return import_cosmetic3.default.hex(c).encoder(s);
1759
+ return import_cosmetic3.default[c].encoder(s);
1510
1760
  }
1511
1761
  function applyPadding(str, paddingX, paddingY) {
1512
1762
  const lines = str.split("\n");
@@ -1532,7 +1782,7 @@ var Bar2 = class {
1532
1782
  const cols = options.width ?? process.stdout.columns ?? 80;
1533
1783
  const available = cols - maxKeyLen - maxValueLen - 3;
1534
1784
  const scale = maxValue > 0 && available > 0 ? available / maxValue : 0;
1535
- const encodeKey = (s) => typeof config.color === "number" ? import_cosmetic2.default.xterm(config.color).encoder(s) : config.color.startsWith("#") ? import_cosmetic2.default.hex(config.color).encoder(s) : import_cosmetic2.default[config.color].encoder(s);
1785
+ const encodeKey = (s) => typeof config.color === "number" ? import_cosmetic3.default.xterm(config.color).encoder(s) : config.color.startsWith("#") ? import_cosmetic3.default.hex(config.color).encoder(s) : import_cosmetic3.default[config.color].encoder(s);
1536
1786
  for (const item of data) {
1537
1787
  if (!item) {
1538
1788
  this.string += "\n";
@@ -1542,7 +1792,7 @@ var Bar2 = class {
1542
1792
  const keyPart = (rawKey ? encodeKey(rawKey) : "") + " ".repeat(maxKeyLen - stringLength(rawKey));
1543
1793
  const barWidth = Math.max(1, Math.floor(item.value * scale));
1544
1794
  let bar = (item.character ?? " ").repeat(barWidth);
1545
- bar = item.style ? item.style(bar) : import_cosmetic2.default.background.white.encoder(bar);
1795
+ bar = item.style ? item.style(bar) : import_cosmetic3.default.background.white.encoder(bar);
1546
1796
  this.string += `${keyPart}|${bar} ${item.value}
1547
1797
  `;
1548
1798
  }
@@ -1591,7 +1841,7 @@ var VerticalBar = class {
1591
1841
  }
1592
1842
  this.string += "\u2500".repeat(data.length * colWidth) + "\n";
1593
1843
  if (data.some((item) => item?.key)) {
1594
- const encodeLabel = (s) => typeof config.color === "number" ? import_cosmetic2.default.xterm(config.color).encoder(s) : config.color.startsWith("#") ? import_cosmetic2.default.hex(config.color).encoder(s) : import_cosmetic2.default[config.color].encoder(s);
1844
+ const encodeLabel = (s) => typeof config.color === "number" ? import_cosmetic3.default.xterm(config.color).encoder(s) : config.color.startsWith("#") ? import_cosmetic3.default.hex(config.color).encoder(s) : import_cosmetic3.default[config.color].encoder(s);
1595
1845
  let labels = "";
1596
1846
  for (const item of data) {
1597
1847
  if (!item?.key) {
@@ -1995,7 +2245,7 @@ var MultiBar = class {
1995
2245
  this._interval = options.interval ?? 35;
1996
2246
  }
1997
2247
  // Add a bar to the group. Must be called before start().
1998
- // Returns the Bar instance — use .message(), .progress, .tick(), .succeed(), etc.
2248
+ // Returns the Bar instance — use .update(), .progress, .tick(), .succeed(), etc.
1999
2249
  add(options = {}) {
2000
2250
  const bar = new Bar(options);
2001
2251
  bar._isManaged = true;
@@ -2091,7 +2341,7 @@ var MultiSelect = class {
2091
2341
  const checked = /* @__PURE__ */ new Set();
2092
2342
  let error = null;
2093
2343
  let lastDrawnLines = 0;
2094
- process.stdout.write(HIDE_CURSOR);
2344
+ process.stdout.write(HIDE_CURSOR + DISABLE_WRAP);
2095
2345
  const glyph = this.promptGlyph ? `${colorText(this.promptColor, this.promptGlyph)} ` : "";
2096
2346
  const indent = " ".repeat(this.promptGlyph ? stringLength(this.promptGlyph) + 1 : 0);
2097
2347
  process.stdout.write(`${glyph}${prompt}
@@ -2101,16 +2351,26 @@ var MultiSelect = class {
2101
2351
  const q = searchQuery.toLowerCase();
2102
2352
  return items.map((item, i) => ({ item, originalIndex: i })).filter(({ item }) => item.label.toLowerCase().includes(q) || (item.description?.toLowerCase().includes(q) ?? false));
2103
2353
  };
2354
+ const computeMaxHeight = () => {
2355
+ const termRows = process.stdout.rows ?? 24;
2356
+ const reserved = 1 + (this.searchEnabled ? 1 : 0) + 1 + 1;
2357
+ const fit = Math.max(1, termRows - reserved - 1);
2358
+ return this.maxHeight ? Math.min(this.maxHeight, fit) : fit;
2359
+ };
2104
2360
  const renderList = (redraw) => {
2105
2361
  const filtered = getFiltered();
2106
2362
  if (cursor >= filtered.length) cursor = Math.max(0, filtered.length - 1);
2107
- if (this.maxHeight) {
2363
+ const maxHeight = computeMaxHeight();
2364
+ const useViewport = filtered.length > maxHeight;
2365
+ if (useViewport) {
2108
2366
  if (cursor < viewportOffset) viewportOffset = cursor;
2109
- else if (cursor >= viewportOffset + this.maxHeight) viewportOffset = cursor - this.maxHeight + 1;
2110
- viewportOffset = Math.max(0, Math.min(viewportOffset, Math.max(0, filtered.length - this.maxHeight)));
2367
+ else if (cursor >= viewportOffset + maxHeight) viewportOffset = cursor - maxHeight + 1;
2368
+ viewportOffset = Math.max(0, Math.min(viewportOffset, Math.max(0, filtered.length - maxHeight)));
2369
+ } else {
2370
+ viewportOffset = 0;
2111
2371
  }
2112
- const visibleStart = this.maxHeight ? viewportOffset : 0;
2113
- const visibleEnd = this.maxHeight ? Math.min(filtered.length, viewportOffset + this.maxHeight) : filtered.length;
2372
+ const visibleStart = useViewport ? viewportOffset : 0;
2373
+ const visibleEnd = useViewport ? Math.min(filtered.length, viewportOffset + maxHeight) : filtered.length;
2114
2374
  if (redraw) {
2115
2375
  if (lastDrawnLines > 0) process.stdout.write(CURSOR_UP3(lastDrawnLines));
2116
2376
  process.stdout.write("\r\x1B[0J");
@@ -2126,14 +2386,15 @@ var MultiSelect = class {
2126
2386
  const { item, originalIndex } = filtered[vi];
2127
2387
  const isCursor = vi === cursor;
2128
2388
  const isChecked = checked.has(originalIndex);
2389
+ const numStr = `${vi + 1}.`;
2129
2390
  const desc = item.description ? ` ${colorText(this.descriptionColor, `\u2014 ${item.description}`)}` : "";
2130
2391
  const checkMark = isChecked ? pulse ? `${pulse}${this.checkedPrefix}${RESET}` : colorText(this.promptColor, this.checkedPrefix) : this.uncheckedPrefix;
2131
2392
  const label = isCursor ? pulse ? `${pulse}${item.label}${RESET}` : colorText(this.promptColor, item.label) : item.label;
2132
- process.stdout.write(`\r${indent}${checkMark} ${label}${desc}
2393
+ process.stdout.write(`\r${indent}${numStr} ${checkMark} ${label}${desc}
2133
2394
  `);
2134
2395
  lastDrawnLines++;
2135
2396
  }
2136
- const hintContent = `\u2191\u2193 move space/tab toggle \u2190\u2192 deselect/select${this.searchEnabled ? " type to filter" : " a all"} enter confirm`;
2397
+ const hintContent = `\u2191\u2193 move space/tab toggle \u2190\u2192 deselect/select${this.searchEnabled ? " type to filter" : " 1-9 jump a all"} enter confirm`;
2137
2398
  const maxHintCols = Math.max(10, (process.stdout.columns ?? 80) - stringLength(indent) - 1);
2138
2399
  const hint = stringLength(hintContent) > maxHintCols ? hintContent.slice(0, maxHintCols) : hintContent;
2139
2400
  process.stdout.write(`\r${indent}${DIM2}${hint}${RESET}
@@ -2156,7 +2417,7 @@ var MultiSelect = class {
2156
2417
  process.stdin.setRawMode(false);
2157
2418
  process.stdin.pause();
2158
2419
  process.stdin.removeListener("data", onKey);
2159
- process.stdout.write(SHOW_CURSOR);
2420
+ process.stdout.write(ENABLE_WRAP + SHOW_CURSOR);
2160
2421
  });
2161
2422
  const cleanup = (result) => {
2162
2423
  deregisterCleanup();
@@ -2166,6 +2427,7 @@ var MultiSelect = class {
2166
2427
  }
2167
2428
  if (lastDrawnLines > 0) process.stdout.write(CURSOR_UP3(lastDrawnLines));
2168
2429
  process.stdout.write("\x1B[0J");
2430
+ process.stdout.write(ENABLE_WRAP);
2169
2431
  const bulletWidth = stringLength(this.checkedPrefix) + 1;
2170
2432
  for (let i = 0; i < items.length; i++) {
2171
2433
  const item = items[i];
@@ -2177,7 +2439,7 @@ var MultiSelect = class {
2177
2439
  process.stdin.setRawMode(false);
2178
2440
  process.stdin.pause();
2179
2441
  process.stdin.removeListener("data", onKey);
2180
- process.stdout.write(SHOW_CURSOR);
2442
+ process.stdout.write(ENABLE_WRAP + SHOW_CURSOR);
2181
2443
  resolve(result);
2182
2444
  };
2183
2445
  if (this._parsedColors.length >= 2) {
@@ -2266,7 +2528,7 @@ var MultiSelect = class {
2266
2528
  process.stdin.setRawMode(false);
2267
2529
  process.stdin.pause();
2268
2530
  process.stdin.removeListener("data", onKey);
2269
- process.stdout.write(SHOW_CURSOR);
2531
+ process.stdout.write(ENABLE_WRAP + SHOW_CURSOR);
2270
2532
  process.exit(130);
2271
2533
  } else if (this.searchEnabled) {
2272
2534
  if (str === "\x7F" || str === "\b") {
@@ -2286,7 +2548,7 @@ var MultiSelect = class {
2286
2548
  const n = parseInt(str);
2287
2549
  if (!isNaN(n) && n >= 1 && n <= Math.min(filtered.length, 9)) {
2288
2550
  error = null;
2289
- cursor = viewportOffset + n - 1;
2551
+ cursor = n - 1;
2290
2552
  renderList(true);
2291
2553
  }
2292
2554
  }
@@ -2463,7 +2725,7 @@ async function scrollbox(lines, options) {
2463
2725
 
2464
2726
  // src/models/Spinner.ts
2465
2727
  var _Spinner = class _Spinner {
2466
- constructor(options = {}) {
2728
+ constructor(text, options) {
2467
2729
  this._colors = [];
2468
2730
  this._parsedColors = [];
2469
2731
  this._bgColors = [];
@@ -2472,24 +2734,25 @@ var _Spinner = class _Spinner {
2472
2734
  this._shimmerPhase = 0;
2473
2735
  this._lastLineLength = 0;
2474
2736
  this._cleanupDeregister = null;
2737
+ const opts = typeof text === "string" ? { ...options, text } : text ?? {};
2475
2738
  this.running = false;
2476
2739
  this.frameIndex = 0;
2477
- this.frames = options.frames ?? (config.glyphs ? _Spinner.FRAMES.braille : _Spinner.FRAMES.line);
2478
- this.prefixString = options.prefix ?? "";
2479
- this.suffixString = options.suffix ?? "";
2480
- this.text = options.text ?? "";
2481
- this.reverse = options.reverse ?? false;
2482
- this.interval = options.interval ?? 80;
2483
- this.colorCycle = options.colorCycle ?? 1;
2484
- this.shimmer = options.shimmer ?? 0;
2485
- this.onSpin = options.onSpin;
2486
- this.colors = options.colors ?? ["#c026d3", "#e879f9"];
2487
- this.bgColors = options.bgColors ?? [];
2488
- this._successColor = options.successColor ?? GREEN;
2489
- this._failColor = options.failColor ?? RED;
2490
- this._warnColor = options.warnColor ?? YELLOW;
2491
- this._infoColor = options.infoColor ?? BLUE;
2492
- this._glyphs = options.glyphs ?? config.glyphs;
2740
+ this.frames = opts.frames ?? (config.glyphs ? _Spinner.FRAMES.braille : _Spinner.FRAMES.line);
2741
+ this._prefix = opts.prefix ?? "";
2742
+ this._suffix = opts.suffix ?? "";
2743
+ this.text = opts.text ?? "";
2744
+ this.reverse = opts.reverse ?? false;
2745
+ this.interval = opts.interval ?? 80;
2746
+ this.colorCycle = opts.colorCycle ?? 1;
2747
+ this.shimmer = opts.shimmer ?? 0;
2748
+ this.onSpin = opts.onSpin;
2749
+ this.colors = opts.colors ?? ["#c026d3", "#e879f9"];
2750
+ this.bgColors = opts.bgColors ?? [];
2751
+ this._successColor = opts.successColor ?? GREEN;
2752
+ this._failColor = opts.failColor ?? RED;
2753
+ this._warnColor = opts.warnColor ?? YELLOW;
2754
+ this._infoColor = opts.infoColor ?? BLUE;
2755
+ this._glyphs = opts.glyphs ?? config.glyphs;
2493
2756
  }
2494
2757
  get colors() {
2495
2758
  return this._colors;
@@ -2508,6 +2771,7 @@ var _Spinner = class _Spinner {
2508
2771
  start() {
2509
2772
  if (!process.stdout.isTTY) return;
2510
2773
  this.running = true;
2774
+ _Spinner.current = this;
2511
2775
  process.stdout.write(HIDE_CURSOR);
2512
2776
  this._cleanupDeregister = registerCleanup(() => {
2513
2777
  this.running = false;
@@ -2518,6 +2782,7 @@ var _Spinner = class _Spinner {
2518
2782
  }
2519
2783
  stop(message) {
2520
2784
  this.running = false;
2785
+ if (_Spinner.current === this) _Spinner.current = null;
2521
2786
  this._cleanupDeregister?.();
2522
2787
  this._cleanupDeregister = null;
2523
2788
  if (process.stdout.isTTY) {
@@ -2532,8 +2797,17 @@ var _Spinner = class _Spinner {
2532
2797
  this.text = string;
2533
2798
  return this;
2534
2799
  }
2800
+ prefix(string) {
2801
+ this._prefix = string;
2802
+ return this;
2803
+ }
2804
+ suffix(string) {
2805
+ this._suffix = string;
2806
+ return this;
2807
+ }
2535
2808
  succeed(string) {
2536
2809
  this.running = false;
2810
+ if (_Spinner.current === this) _Spinner.current = null;
2537
2811
  this._cleanupDeregister?.();
2538
2812
  this._cleanupDeregister = null;
2539
2813
  if (process.stdout.isTTY) {
@@ -2549,6 +2823,7 @@ var _Spinner = class _Spinner {
2549
2823
  }
2550
2824
  fail(string) {
2551
2825
  this.running = false;
2826
+ if (_Spinner.current === this) _Spinner.current = null;
2552
2827
  this._cleanupDeregister?.();
2553
2828
  this._cleanupDeregister = null;
2554
2829
  if (process.stdout.isTTY) {
@@ -2564,6 +2839,7 @@ var _Spinner = class _Spinner {
2564
2839
  }
2565
2840
  warn(string) {
2566
2841
  this.running = false;
2842
+ if (_Spinner.current === this) _Spinner.current = null;
2567
2843
  this._cleanupDeregister?.();
2568
2844
  this._cleanupDeregister = null;
2569
2845
  if (process.stdout.isTTY) {
@@ -2579,6 +2855,7 @@ var _Spinner = class _Spinner {
2579
2855
  }
2580
2856
  info(string) {
2581
2857
  this.running = false;
2858
+ if (_Spinner.current === this) _Spinner.current = null;
2582
2859
  this._cleanupDeregister?.();
2583
2860
  this._cleanupDeregister = null;
2584
2861
  if (process.stdout.isTTY) {
@@ -2614,9 +2891,9 @@ var _Spinner = class _Spinner {
2614
2891
  }
2615
2892
  run() {
2616
2893
  const frame = this.coloredFrame();
2617
- const t = this.text;
2618
- const content = this.reverse ? t ? `${t} ${frame}` : frame : t ? `${frame} ${t}` : frame;
2619
- const raw = `${this.prefixString}${content}${this.suffixString}`;
2894
+ const label = [this._prefix, this.text, this._suffix].filter(Boolean).join(" ");
2895
+ const content = this.reverse ? label ? `${label} ${frame}` : frame : label ? `${frame} ${label}` : frame;
2896
+ const raw = content;
2620
2897
  const displayLen = stringLength(raw);
2621
2898
  const padding = " ".repeat(Math.max(0, this._lastLineLength - displayLen));
2622
2899
  this._lastLineLength = displayLen;
@@ -2634,6 +2911,7 @@ var _Spinner = class _Spinner {
2634
2911
  }, this.interval);
2635
2912
  }
2636
2913
  };
2914
+ _Spinner.current = null;
2637
2915
  _Spinner.FRAMES = {
2638
2916
  braille: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
2639
2917
  dots: ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"],
@@ -2644,7 +2922,7 @@ _Spinner.FRAMES = {
2644
2922
  var Spinner = _Spinner;
2645
2923
 
2646
2924
  // src/models/Table.ts
2647
- var import_cosmetic3 = __toESM(require("cosmetic"));
2925
+ var import_cosmetic4 = __toESM(require("cosmetic"));
2648
2926
 
2649
2927
  // src/utils/padSides.ts
2650
2928
  var padSides = (string, padding) => {
@@ -2722,7 +3000,7 @@ var Table = class {
2722
3000
  header += pad(column.title, column.padding + this.margin);
2723
3001
  if (i < keys.length - 1) header += this.separator;
2724
3002
  }
2725
- const styled = typeof config.color === "number" ? import_cosmetic3.default.xterm(config.color) : config.color.startsWith("#") ? import_cosmetic3.default.hex(config.color) : import_cosmetic3.default[config.color];
3003
+ const styled = typeof config.color === "number" ? import_cosmetic4.default.xterm(config.color) : config.color.startsWith("#") ? import_cosmetic4.default.hex(config.color) : import_cosmetic4.default[config.color];
2726
3004
  this.string += `${styled.underline.encoder(header)}
2727
3005
  `;
2728
3006
  for (const [ri, row] of this.rows.entries()) {
@@ -2793,13 +3071,13 @@ _TermKit.commandDefaults = {};
2793
3071
  var TermKit = _TermKit;
2794
3072
 
2795
3073
  // src/index.ts
2796
- var import_cosmetic4 = __toESM(require("cosmetic"));
3074
+ var import_cosmetic5 = __toESM(require("cosmetic"));
2797
3075
  var base = null;
2798
3076
  var commandDefaults = {};
2799
3077
  var Program = {
2800
3078
  command: (name, variables, info) => {
2801
3079
  const cmd = new Command(Object.assign({ name, variables, info }, commandDefaults));
2802
- if (!base) base = cmd;
3080
+ base = cmd;
2803
3081
  return cmd;
2804
3082
  },
2805
3083
  option: (short, long, variables, info) => new Option({ short, long, variables, info }),
@@ -2822,6 +3100,10 @@ var Program = {
2822
3100
  },
2823
3101
  setDefaults: (data) => {
2824
3102
  commandDefaults = data;
3103
+ },
3104
+ shell: async (options) => {
3105
+ if (!base) throw new Error("No command defined");
3106
+ return new Shell(base, options).run();
2825
3107
  }
2826
3108
  };
2827
3109
  // Annotate the CommonJS export names for ESM import in node:
@@ -2839,6 +3121,7 @@ var Program = {
2839
3121
  Program,
2840
3122
  Scrollbox,
2841
3123
  Select,
3124
+ Shell,
2842
3125
  Spinner,
2843
3126
  Table,
2844
3127
  TermKit,