@orb44/cli 0.1.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.
@@ -0,0 +1,133 @@
1
+ export function incompleteEscape(seq) {
2
+ return seq === "\u001b" || seq === "\u001b[" || seq === "\u001bO";
3
+ }
4
+
5
+ export function parseMenuKey(raw) {
6
+ const s = typeof raw === "string" ? raw : Buffer.from(raw || "").toString("utf8");
7
+ if (s === "\u0003") return "ctrl-c";
8
+ if (s === "\r" || s === "\n") return "enter";
9
+ if (s === "\u001b[A" || s === "\u001bOA" || s === "k" || s === "K") return "up";
10
+ if (s === "\u001b[B" || s === "\u001bOB" || s === "j" || s === "J") return "down";
11
+ if (incompleteEscape(s)) return "esc-wait";
12
+ if (/^[1-9]$/.test(s)) return { digit: Number(s) };
13
+ return null;
14
+ }
15
+
16
+ export function moveIndex(i, dir, n) {
17
+ if (!n) return 0;
18
+ if (dir === "up") return (i + n - 1) % n;
19
+ if (dir === "down") return (i + 1) % n;
20
+ return i;
21
+ }
22
+
23
+ function paint(stdout, items, index, hint) {
24
+ const n = items.length + (hint ? 1 : 0);
25
+ stdout.write(`\x1b[${n}A`);
26
+ for (let i = 0; i < items.length; i++) {
27
+ const cur = i === index;
28
+ const mark = cur ? "▸" : " ";
29
+ const body = cur ? `\x1b[1m${items[i]}\x1b[0m` : items[i];
30
+ stdout.write(`\x1b[2K ${mark} ${body}\n`);
31
+ }
32
+ if (hint) stdout.write(`\x1b[2K\x1b[2m${hint}\x1b[0m\n`);
33
+ }
34
+
35
+ export async function pickFromList({
36
+ title,
37
+ items,
38
+ index = 0,
39
+ hint = "↑↓ Enter",
40
+ stdin = process.stdin,
41
+ stdout = process.stdout,
42
+ } = {}) {
43
+ if (!items?.length) return 0;
44
+ let i = Math.max(0, Math.min(items.length - 1, Number(index) || 0));
45
+ if (!stdin.isTTY || !stdout.isTTY || typeof stdin.setRawMode !== "function") return i;
46
+
47
+ stdout.write(`\n${title}\n\n`);
48
+ for (let k = 0; k < items.length; k++) {
49
+ const cur = k === i;
50
+ stdout.write(` ${cur ? "▸" : " "} ${cur ? `\x1b[1m${items[k]}\x1b[0m` : items[k]}\n`);
51
+ }
52
+ if (hint) stdout.write(`\x1b[2m${hint}\x1b[0m\n`);
53
+
54
+ stdin.setRawMode(true);
55
+ if (stdin.isPaused?.()) stdin.resume();
56
+ stdout.write("\x1b[?25l");
57
+
58
+ return await new Promise((resolve) => {
59
+ let seq = "";
60
+ let escTimer = null;
61
+ const finish = (fn) => {
62
+ clearTimeout(escTimer);
63
+ stdin.off("data", onData);
64
+ try {
65
+ stdin.setRawMode(false);
66
+ } catch {
67
+ /* already cooked */
68
+ }
69
+ try {
70
+ stdin.pause?.();
71
+ } catch {
72
+ /* ignore */
73
+ }
74
+ stdout.write("\x1b[?25h");
75
+ fn();
76
+ };
77
+ const applyKey = (key) => {
78
+ if (!key || key === "esc-wait") return false;
79
+ if (key === "ctrl-c") {
80
+ finish(() => {
81
+ stdout.write("\n");
82
+ process.exit(130);
83
+ });
84
+ return true;
85
+ }
86
+ if (key === "up" || key === "down") {
87
+ i = moveIndex(i, key, items.length);
88
+ paint(stdout, items, i, hint);
89
+ return false;
90
+ }
91
+ if (key === "enter") {
92
+ finish(() => {
93
+ stdout.write("\n");
94
+ resolve(i);
95
+ });
96
+ return true;
97
+ }
98
+ if (key?.digit >= 1 && key.digit <= items.length) {
99
+ i = key.digit - 1;
100
+ finish(() => {
101
+ stdout.write("\n");
102
+ resolve(i);
103
+ });
104
+ return true;
105
+ }
106
+ return false;
107
+ };
108
+ const onData = (chunk) => {
109
+ seq += Buffer.from(chunk).toString("utf8");
110
+ clearTimeout(escTimer);
111
+ while (seq) {
112
+ if (incompleteEscape(seq)) {
113
+ escTimer = setTimeout(() => {
114
+ seq = "";
115
+ }, 50);
116
+ return;
117
+ }
118
+ let take = seq;
119
+ let rest = "";
120
+ if (seq.startsWith("\u001b") && seq.length >= 3) {
121
+ take = seq.slice(0, 3);
122
+ rest = seq.slice(3);
123
+ } else if (!seq.startsWith("\u001b")) {
124
+ take = seq[0];
125
+ rest = seq.slice(1);
126
+ }
127
+ seq = rest;
128
+ if (applyKey(parseMenuKey(take))) return;
129
+ }
130
+ };
131
+ stdin.on("data", onData);
132
+ });
133
+ }