chill-agent 0.0.0-stage → 1.0.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/src/prompts.js ADDED
@@ -0,0 +1,192 @@
1
+ // src/prompts.js - Zero-dependency terminal interactive prompts
2
+ import readline from 'node:readline';
3
+
4
+ export const c = {
5
+ reset: '\x1b[0m',
6
+ bold: '\x1b[1m',
7
+ dim: '\x1b[2m',
8
+ italic: '\x1b[3m',
9
+ underline: '\x1b[4m',
10
+ // Colors
11
+ red: '\x1b[38;5;203m',
12
+ green: '\x1b[38;5;48m',
13
+ yellow: '\x1b[38;5;214m',
14
+ blue: '\x1b[38;5;75m',
15
+ magenta: '\x1b[38;5;176m',
16
+ cyan: '\x1b[38;5;81m',
17
+ white: '\x1b[38;5;255m',
18
+ gray: '\x1b[38;5;244m',
19
+ darkGray: '\x1b[38;5;238m',
20
+ bgDark: '\x1b[48;5;235m',
21
+ emerald: '\x1b[38;5;48m',
22
+ coral: '\x1b[38;5;204m',
23
+ indigo: '\x1b[38;5;141m',
24
+ amber: '\x1b[38;5;214m'
25
+ };
26
+
27
+ const SYMBOLS = {
28
+ pointer: '›',
29
+ checkboxOn: '◉',
30
+ checkboxOff: '◯',
31
+ check: '✔',
32
+ cross: '✖',
33
+ info: 'ℹ',
34
+ bullet: '▪'
35
+ };
36
+
37
+ export async function select({ message, choices, defaultIndex = 0 }) {
38
+ if (!process.stdin.isTTY) {
39
+ return choices[defaultIndex].value;
40
+ }
41
+
42
+ return new Promise((resolve) => {
43
+ let index = defaultIndex;
44
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
45
+ readline.emitKeypressEvents(process.stdin, rl);
46
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
47
+ process.stdout.write('\x1b[?25l'); // hide cursor
48
+
49
+ function render(initial = false) {
50
+ if (!initial) {
51
+ process.stdout.write(`\x1b[${choices.length + 1}A\r`);
52
+ }
53
+ process.stdout.write(`${c.bold}${c.cyan}?${c.reset} ${c.bold}${message}${c.reset}\n`);
54
+ choices.forEach((choice, i) => {
55
+ const isSelected = i === index;
56
+ const prefix = isSelected ? `${c.cyan}${SYMBOLS.pointer}${c.reset} ` : ' ';
57
+ const label = isSelected ? `${c.bold}${c.white}${choice.label}${c.reset}` : `${c.gray}${choice.label}${c.reset}`;
58
+ const desc = choice.description ? ` ${c.dim}(${choice.description})${c.reset}` : '';
59
+ process.stdout.write(`\x1b[2K${prefix}${label}${desc}\n`);
60
+ });
61
+ }
62
+
63
+ render(true);
64
+
65
+ function onKeypress(str, key) {
66
+ if (!key) return;
67
+ if (key.name === 'up' || (key.name === 'k' && !key.ctrl)) {
68
+ index = (index - 1 + choices.length) % choices.length;
69
+ render();
70
+ } else if (key.name === 'down' || (key.name === 'j' && !key.ctrl)) {
71
+ index = (index + 1) % choices.length;
72
+ render();
73
+ } else if (key.name === 'return' || key.name === 'enter') {
74
+ cleanup();
75
+ process.stdout.write(`\x1b[2K\r${c.green}${SYMBOLS.check}${c.reset} ${c.bold}${message}${c.reset} → ${c.cyan}${choices[index].label}${c.reset}\n`);
76
+ resolve(choices[index].value);
77
+ } else if (key.name === 'c' && key.ctrl) {
78
+ cleanup();
79
+ process.exit(0);
80
+ }
81
+ }
82
+
83
+ function cleanup() {
84
+ process.stdin.removeListener('keypress', onKeypress);
85
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
86
+ process.stdout.write('\x1b[?25h'); // show cursor
87
+ rl.close();
88
+ }
89
+
90
+ process.stdin.on('keypress', onKeypress);
91
+ });
92
+ }
93
+
94
+ export async function multiselect({ message, choices, defaultSelected = [] }) {
95
+ if (!process.stdin.isTTY) {
96
+ return defaultSelected.length ? defaultSelected : choices.map((c) => c.value);
97
+ }
98
+
99
+ return new Promise((resolve) => {
100
+ let cursor = 0;
101
+ const selected = new Set(defaultSelected);
102
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
103
+ readline.emitKeypressEvents(process.stdin, rl);
104
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
105
+ process.stdout.write('\x1b[?25l');
106
+
107
+ function render(initial = false) {
108
+ if (!initial) {
109
+ process.stdout.write(`\x1b[${choices.length + 2}A\r`);
110
+ }
111
+ process.stdout.write(`${c.bold}${c.cyan}?${c.reset} ${c.bold}${message}${c.reset} ${c.dim}(Space to toggle, Enter to confirm)${c.reset}\n`);
112
+ choices.forEach((choice, i) => {
113
+ const isFocused = i === cursor;
114
+ const isChecked = selected.has(choice.value);
115
+ const focusPointer = isFocused ? `${c.cyan}${SYMBOLS.pointer}${c.reset}` : ' ';
116
+ const checkIcon = isChecked ? `${c.emerald}${SYMBOLS.checkboxOn}${c.reset}` : `${c.darkGray}${SYMBOLS.checkboxOff}${c.reset}`;
117
+ const label = isFocused ? `${c.bold}${c.white}${choice.label}${c.reset}` : `${c.gray}${choice.label}${c.reset}`;
118
+ const desc = choice.description ? ` ${c.dim}— ${choice.description}${c.reset}` : '';
119
+ process.stdout.write(`\x1b[2K ${focusPointer} ${checkIcon} ${label}${desc}\n`);
120
+ });
121
+ process.stdout.write(`\x1b[2K ${c.dim}[a] Select all [n] Select none${c.reset}\n`);
122
+ }
123
+
124
+ render(true);
125
+
126
+ function onKeypress(str, key) {
127
+ if (!key) return;
128
+ if (key.name === 'up' || (key.name === 'k' && !key.ctrl)) {
129
+ cursor = (cursor - 1 + choices.length) % choices.length;
130
+ render();
131
+ } else if (key.name === 'down' || (key.name === 'j' && !key.ctrl)) {
132
+ cursor = (cursor + 1) % choices.length;
133
+ render();
134
+ } else if (key.name === 'space') {
135
+ const val = choices[cursor].value;
136
+ if (selected.has(val)) selected.delete(val);
137
+ else selected.add(val);
138
+ render();
139
+ } else if (key.name === 'a') {
140
+ choices.forEach((ch) => selected.add(ch.value));
141
+ render();
142
+ } else if (key.name === 'n') {
143
+ selected.clear();
144
+ render();
145
+ } else if (key.name === 'return' || key.name === 'enter') {
146
+ cleanup();
147
+ const selectedList = choices.filter((ch) => selected.has(ch.value));
148
+ const labels = selectedList.map((ch) => ch.label).join(', ');
149
+ process.stdout.write(`\x1b[2K\r${c.green}${SYMBOLS.check}${c.reset} ${c.bold}${message}${c.reset} → ${c.cyan}${labels || 'none'}${c.reset}\n`);
150
+ resolve(Array.from(selected));
151
+ } else if (key.name === 'c' && key.ctrl) {
152
+ cleanup();
153
+ process.exit(0);
154
+ }
155
+ }
156
+
157
+ function cleanup() {
158
+ process.stdin.removeListener('keypress', onKeypress);
159
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
160
+ process.stdout.write('\x1b[?25h');
161
+ rl.close();
162
+ }
163
+
164
+ process.stdin.on('keypress', onKeypress);
165
+ });
166
+ }
167
+
168
+ export async function confirm({ message, defaultYes = true }) {
169
+ if (!process.stdin.isTTY) return defaultYes;
170
+ const choice = await select({
171
+ message,
172
+ choices: [
173
+ { label: defaultYes ? 'Yes (Recommended)' : 'Yes', value: true },
174
+ { label: defaultYes ? 'No' : 'No (Recommended)', value: false }
175
+ ],
176
+ defaultIndex: defaultYes ? 0 : 1
177
+ });
178
+ return choice;
179
+ }
180
+
181
+ export function banner() {
182
+ console.log(`
183
+ ${c.indigo}${c.bold} ____ _ _ _ _ _ ${c.reset}
184
+ ${c.indigo}${c.bold} / ___| |__ (_) | | __ _ __ _ ___| |_ ${c.reset}
185
+ ${c.indigo}${c.bold} | | | '_ \\| | | | / _\` |/ _\` |/ _ \\ __|${c.reset}
186
+ ${c.cyan}${c.bold} | |___| | | | | | |_| (_| | (_| | __/ |_ ${c.reset}
187
+ ${c.cyan}${c.bold} \\____|_| |_|_|_|_(_)\\__,_|\\__, |\\___|\\__|${c.reset}
188
+ ${c.cyan}${c.bold} |___/ ${c.reset}
189
+ ${c.bold}ChillAgent Anti-Slop 2.0 Engine${c.reset} ${c.dim}• High-Craft & Time-Travel Debugger${c.reset}
190
+ ${c.dim}Zero-Dependency • Linear/Geist Standard • Multi-Agent Ready${c.reset}
191
+ `);
192
+ }