@tsuzuku/arise 1.1.1 → 1.2.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,629 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const readline = require('readline');
5
+ const { ANSI, truncate } = require('./ansi');
6
+
7
+ function createPathCompleter(options = {}) {
8
+ return function pathCompleter(line) {
9
+ const raw = (line || '').trim();
10
+
11
+ if (raw === '~') {
12
+ return [['~/'], '~'];
13
+ }
14
+
15
+ let expanded = raw;
16
+ if (raw.startsWith('~' + path.sep) || raw.startsWith('~/')) {
17
+ expanded = path.join(os.homedir(), raw.slice(2));
18
+ } else if (raw === '~') {
19
+ expanded = os.homedir();
20
+ }
21
+
22
+ let searchDir;
23
+ let partial;
24
+
25
+ if (raw.endsWith('/') || raw.endsWith(path.sep)) {
26
+ searchDir = expanded;
27
+ partial = '';
28
+ } else {
29
+ const lastSlashIndex = Math.max(raw.lastIndexOf('/'), raw.lastIndexOf('\\'));
30
+ if (lastSlashIndex >= 0) {
31
+ const dirPart = expanded.slice(0, expanded.lastIndexOf(path.sep) + 1);
32
+ searchDir = dirPart || (path.sep === '\\' ? 'C:\\' : '/');
33
+ partial = raw.slice(lastSlashIndex + 1);
34
+ } else {
35
+ searchDir = process.cwd();
36
+ partial = raw;
37
+ }
38
+ }
39
+
40
+ try {
41
+ const entries = fs.readdirSync(searchDir || '.', { withFileTypes: true });
42
+ const hits = [];
43
+
44
+ for (const entry of entries) {
45
+ // Skip hidden files unless user started typing with a dot
46
+ if (!partial.startsWith('.') && entry.name.startsWith('.')) {
47
+ continue;
48
+ }
49
+
50
+ if (entry.name.startsWith(partial)) {
51
+ let isDir = entry.isDirectory();
52
+ if (!isDir && entry.isSymbolicLink()) {
53
+ try {
54
+ const fullPath = path.join(searchDir || '.', entry.name);
55
+ isDir = fs.statSync(fullPath).isDirectory();
56
+ } catch {
57
+ isDir = false;
58
+ }
59
+ }
60
+
61
+ if (options.directoriesOnly && !isDir) {
62
+ continue;
63
+ }
64
+
65
+ let name = entry.name;
66
+ if (isDir) {
67
+ name += '/';
68
+ }
69
+ hits.push(name);
70
+ }
71
+ }
72
+
73
+ hits.sort((a, b) => a.localeCompare(b));
74
+ return [hits, partial];
75
+ } catch {
76
+ return [[], partial];
77
+ }
78
+ };
79
+ }
80
+
81
+ const pathCompleter = createPathCompleter();
82
+
83
+ async function promptSelect(options) {
84
+ const message = options.message || options.title || '';
85
+ const choices = options.choices || options.items || [];
86
+ const defaultIndex = options.defaultIndex || 0;
87
+
88
+ if (choices.length === 0) {
89
+ return null;
90
+ }
91
+
92
+ // Normalize choices to { label, value, hint, description }
93
+ const normalizedChoices = choices.map((c) => {
94
+ if (typeof c === 'object' && c !== null) {
95
+ return {
96
+ label: c.label || c.title || String(c.value),
97
+ value: c.value !== undefined ? c.value : c,
98
+ hint: c.hint || c.description,
99
+ };
100
+ }
101
+ return {
102
+ label: String(c),
103
+ value: c,
104
+ };
105
+ });
106
+
107
+ if (!process.stdin.isTTY) {
108
+ // Non-interactive fallback
109
+ return normalizedChoices[Math.min(defaultIndex, normalizedChoices.length - 1)].value;
110
+ }
111
+
112
+ return new Promise((resolvePromise) => {
113
+ let selectedIndex = Math.max(0, Math.min(defaultIndex, normalizedChoices.length - 1));
114
+ let isRendered = false;
115
+
116
+ const render = () => {
117
+ const termWidth = Math.max(20, process.stdout.columns || 80);
118
+ if (isRendered) {
119
+ process.stdout.write(`\r\x1b[${normalizedChoices.length + 1}A`);
120
+ }
121
+ isRendered = true;
122
+
123
+ const titleLine = `${ANSI.bold}${ANSI.cyan}?${ANSI.reset} ${ANSI.bold}${message}${ANSI.reset} ${ANSI.gray}(Use arrow keys or numbers, Enter to select)${ANSI.reset}`;
124
+ process.stdout.write(ANSI.clearLine + truncate(titleLine, termWidth - 1) + '\n');
125
+
126
+ normalizedChoices.forEach((choice, index) => {
127
+ const isSelected = index === selectedIndex;
128
+ const pointer = isSelected ? `${ANSI.brightCyan}❯${ANSI.reset}` : ' ';
129
+ const numStr = `${ANSI.gray}${index + 1})${ANSI.reset}`;
130
+ const label = isSelected
131
+ ? `${ANSI.bold}${ANSI.brightCyan}${choice.label}${ANSI.reset}`
132
+ : `${ANSI.white}${choice.label}${ANSI.reset}`;
133
+ const hint = choice.hint ? ` ${ANSI.gray}(${choice.hint})${ANSI.reset}` : '';
134
+
135
+ const line = ` ${pointer} ${numStr} ${label}${hint}`;
136
+ process.stdout.write(ANSI.clearLine + truncate(line, termWidth - 1) + '\n');
137
+ });
138
+ };
139
+
140
+ const cleanup = () => {
141
+ if (process.stdin.setRawMode) {
142
+ process.stdin.setRawMode(false);
143
+ }
144
+ process.stdin.pause();
145
+ process.stdin.removeListener('data', onData);
146
+ process.stdout.write(ANSI.showCursor);
147
+ };
148
+
149
+ const onData = (chunk) => {
150
+ const key = chunk.toString();
151
+
152
+ // Enter key
153
+ if (key === '\r' || key === '\n') {
154
+ cleanup();
155
+ const termWidth = Math.max(20, process.stdout.columns || 80);
156
+ const selected = normalizedChoices[selectedIndex];
157
+ const summary = `${ANSI.bold}${ANSI.green}✔${ANSI.reset} ${message} ${ANSI.cyan}${selected.label}${ANSI.reset}`;
158
+ process.stdout.write(ANSI.clearLine + truncate(summary, termWidth - 1) + '\n\n');
159
+ resolvePromise(selected.value);
160
+ return;
161
+ }
162
+
163
+ // Ctrl+C
164
+ if (key === '\u0003') {
165
+ cleanup();
166
+ process.exit(130);
167
+ }
168
+
169
+ // Escape key or 'q'
170
+ if (key === '\u001b' || (chunk.length === 1 && chunk[0] === 0x1b)) {
171
+ cleanup();
172
+ const termWidth = Math.max(20, process.stdout.columns || 80);
173
+ const backSummary = `${ANSI.bold}${ANSI.gray}↩${ANSI.reset} ${ANSI.dim}${message} (Cancelled)${ANSI.reset}`;
174
+ process.stdout.write(ANSI.clearLine + truncate(backSummary, termWidth - 1) + '\n\n');
175
+ resolvePromise(null);
176
+ return;
177
+ }
178
+
179
+ // Number key selection (1..9)
180
+ const num = parseInt(key, 10);
181
+ if (!isNaN(num) && num >= 1 && num <= normalizedChoices.length) {
182
+ selectedIndex = num - 1;
183
+ cleanup();
184
+ const termWidth = Math.max(20, process.stdout.columns || 80);
185
+ const selected = normalizedChoices[selectedIndex];
186
+ const summary = `${ANSI.bold}${ANSI.green}✔${ANSI.reset} ${message} ${ANSI.cyan}${selected.label}${ANSI.reset}`;
187
+ process.stdout.write(ANSI.clearLine + truncate(summary, termWidth - 1) + '\n\n');
188
+ resolvePromise(selected.value);
189
+ return;
190
+ }
191
+
192
+ // Quick 'y' / 'Y' key selection for boolean / confirmation choices
193
+ if (key === 'y' || key === 'Y') {
194
+ const yesIdx = normalizedChoices.findIndex(
195
+ (c) => c.value === true || c.label.toLowerCase().startsWith('yes')
196
+ );
197
+ if (yesIdx >= 0) {
198
+ selectedIndex = yesIdx;
199
+ cleanup();
200
+ const termWidth = Math.max(20, process.stdout.columns || 80);
201
+ const selected = normalizedChoices[selectedIndex];
202
+ const summary = `${ANSI.bold}${ANSI.green}✔${ANSI.reset} ${message} ${ANSI.cyan}${selected.label}${ANSI.reset}`;
203
+ process.stdout.write(ANSI.clearLine + truncate(summary, termWidth - 1) + '\n\n');
204
+ resolvePromise(selected.value);
205
+ return;
206
+ }
207
+ }
208
+
209
+ // Quick 'n' / 'N' key selection for boolean / confirmation choices
210
+ if (key === 'n' || key === 'N') {
211
+ const noIdx = normalizedChoices.findIndex(
212
+ (c) => c.value === false || c.label.toLowerCase().startsWith('no')
213
+ );
214
+ if (noIdx >= 0) {
215
+ selectedIndex = noIdx;
216
+ cleanup();
217
+ const termWidth = Math.max(20, process.stdout.columns || 80);
218
+ const selected = normalizedChoices[selectedIndex];
219
+ const summary = `${ANSI.bold}${ANSI.green}✔${ANSI.reset} ${message} ${ANSI.cyan}${selected.label}${ANSI.reset}`;
220
+ process.stdout.write(ANSI.clearLine + truncate(summary, termWidth - 1) + '\n\n');
221
+ resolvePromise(selected.value);
222
+ return;
223
+ }
224
+ }
225
+
226
+ // Up arrow or k
227
+ if (key === '\u001b[A' || key === '\u001bOA' || key === 'k') {
228
+ selectedIndex = (selectedIndex - 1 + normalizedChoices.length) % normalizedChoices.length;
229
+ render();
230
+ }
231
+ // Down arrow or j
232
+ else if (key === '\u001b[B' || key === '\u001bOB' || key === 'j') {
233
+ selectedIndex = (selectedIndex + 1) % normalizedChoices.length;
234
+ render();
235
+ }
236
+ };
237
+
238
+ if (process.stdin.setRawMode) {
239
+ process.stdin.setRawMode(true);
240
+ }
241
+ process.stdin.resume();
242
+ process.stdout.write(ANSI.hideCursor);
243
+ render();
244
+ process.stdin.on('data', onData);
245
+ });
246
+ }
247
+
248
+ async function promptMultiSelect(options) {
249
+ const message = options.message || options.title || '';
250
+ const choices = options.choices || options.items || [];
251
+ const pageSize = options.pageSize || 8;
252
+ const allowCustomInput = options.allowCustomInput !== false;
253
+
254
+ if (choices.length === 0 && !allowCustomInput) {
255
+ return [];
256
+ }
257
+
258
+ const allItems = choices.map((c) => {
259
+ if (typeof c === 'object' && c !== null) {
260
+ return {
261
+ label: c.label || c.title || String(c.value),
262
+ value: c.value !== undefined ? c.value : c,
263
+ hint: c.hint || c.description,
264
+ selected: Boolean(c.selected),
265
+ };
266
+ }
267
+ return {
268
+ label: String(c),
269
+ value: c,
270
+ selected: false,
271
+ };
272
+ });
273
+
274
+ if (!process.stdin.isTTY) {
275
+ const selected = allItems.filter((c) => c.selected).map((c) => c.value);
276
+ return selected.length > 0 ? selected : allItems.length > 0 ? [allItems[0].value] : [];
277
+ }
278
+
279
+ return new Promise((resolvePromise) => {
280
+ let filterText = '';
281
+ let selectedIndex = 0;
282
+ let lastRenderedLineCount = 0;
283
+
284
+ const getFilteredItems = () => {
285
+ const lower = filterText.toLowerCase().trim();
286
+ const matched = lower.length === 0
287
+ ? allItems.slice()
288
+ : allItems.filter(
289
+ (item) =>
290
+ item.label.toLowerCase().includes(lower) ||
291
+ String(item.value).toLowerCase().includes(lower) ||
292
+ (item.hint && item.hint.toLowerCase().includes(lower))
293
+ );
294
+
295
+ if (allowCustomInput && lower.length > 0) {
296
+ const exactMatch = allItems.some(
297
+ (i) => i.label.toLowerCase() === lower || String(i.value).toLowerCase() === lower
298
+ );
299
+ if (!exactMatch) {
300
+ matched.push({
301
+ label: `➕ Add "${filterText}"`,
302
+ value: filterText,
303
+ hint: 'custom entry',
304
+ selected: false,
305
+ isCustom: true,
306
+ });
307
+ }
308
+ }
309
+
310
+ return matched;
311
+ };
312
+
313
+ const render = () => {
314
+ const termWidth = Math.max(20, process.stdout.columns || 80);
315
+ const filtered = getFilteredItems();
316
+
317
+ if (filtered.length === 0) {
318
+ selectedIndex = 0;
319
+ } else if (selectedIndex >= filtered.length) {
320
+ selectedIndex = filtered.length - 1;
321
+ } else if (selectedIndex < 0) {
322
+ selectedIndex = 0;
323
+ }
324
+
325
+ const total = filtered.length;
326
+ let startIndex = 0;
327
+ let endIndex = Math.min(pageSize, total);
328
+
329
+ if (selectedIndex >= endIndex) {
330
+ endIndex = selectedIndex + 1;
331
+ startIndex = Math.max(0, endIndex - pageSize);
332
+ } else if (selectedIndex < startIndex) {
333
+ startIndex = selectedIndex;
334
+ endIndex = Math.min(total, startIndex + pageSize);
335
+ }
336
+
337
+ const visibleItems = filtered.slice(startIndex, endIndex);
338
+
339
+ if (lastRenderedLineCount > 0) {
340
+ process.stdout.write(`\r\x1b[${lastRenderedLineCount}A`);
341
+ }
342
+
343
+ const lines = [];
344
+
345
+ // 1. Title line
346
+ const title = `${ANSI.bold}${ANSI.cyan}?${ANSI.reset} ${ANSI.bold}${message}${ANSI.reset} ${ANSI.dim}(<space> to toggle, "a" to toggle all, <enter> to confirm)${ANSI.reset}`;
347
+ lines.push(ANSI.clearLine + truncate(title, termWidth - 1));
348
+
349
+ // 2. Search Filter line
350
+ const searchDisplay = filterText.length > 0
351
+ ? `${ANSI.yellow}${filterText}${ANSI.reset}`
352
+ : `${ANSI.dim}type to search...${ANSI.reset}`;
353
+ lines.push(ANSI.clearLine + ` ${ANSI.bold}Search:${ANSI.reset} ${searchDisplay}`);
354
+
355
+ // 3. Top scroll indicator
356
+ if (startIndex > 0) {
357
+ lines.push(ANSI.clearLine + ` ${ANSI.dim}▲ ${startIndex} more above...${ANSI.reset}`);
358
+ }
359
+
360
+ // 4. Visible items
361
+ if (filtered.length === 0) {
362
+ lines.push(ANSI.clearLine + ` ${ANSI.gray}No matching items found${ANSI.reset}`);
363
+ } else {
364
+ visibleItems.forEach((item, idx) => {
365
+ const globalIdx = startIndex + idx;
366
+ const isCursor = globalIdx === selectedIndex;
367
+ const pointer = isCursor ? `${ANSI.brightCyan}❯${ANSI.reset}` : ' ';
368
+ const checkbox = item.selected
369
+ ? `${ANSI.bold}${ANSI.green}[✔]${ANSI.reset}`
370
+ : `${ANSI.gray}[ ]${ANSI.reset}`;
371
+
372
+ const label = isCursor
373
+ ? `${ANSI.bold}${ANSI.brightCyan}${item.label}${ANSI.reset}`
374
+ : item.selected
375
+ ? `${ANSI.bold}${item.label}${ANSI.reset}`
376
+ : `${ANSI.white}${item.label}${ANSI.reset}`;
377
+
378
+ const hint = item.hint ? ` ${ANSI.gray}(${item.hint})${ANSI.reset}` : '';
379
+ lines.push(ANSI.clearLine + ` ${pointer} ${checkbox} ${label}${hint}`);
380
+ });
381
+ }
382
+
383
+ // 5. Bottom scroll indicator
384
+ if (endIndex < total) {
385
+ lines.push(ANSI.clearLine + ` ${ANSI.dim}▼ ${total - endIndex} more below...${ANSI.reset}`);
386
+ }
387
+
388
+ // 6. Selected counter footer
389
+ const selectedTotal = allItems.filter((i) => i.selected).length;
390
+ const countDisplay = `${ANSI.dim}(${selectedTotal} selected)${ANSI.reset}`;
391
+ lines.push(ANSI.clearLine + ` ${countDisplay}`);
392
+
393
+ lastRenderedLineCount = lines.length;
394
+ process.stdout.write(lines.join('\n') + '\n');
395
+ };
396
+
397
+ const cleanup = () => {
398
+ if (process.stdin.setRawMode) {
399
+ process.stdin.setRawMode(false);
400
+ }
401
+ process.stdin.pause();
402
+ process.stdin.removeListener('data', onData);
403
+ process.stdout.write(ANSI.showCursor);
404
+ };
405
+
406
+ const onData = (chunk) => {
407
+ const key = chunk.toString();
408
+
409
+ // Enter key
410
+ if (key === '\r' || key === '\n') {
411
+ cleanup();
412
+ const selected = allItems.filter((i) => i.selected).map((i) => i.value);
413
+ const filtered = getFilteredItems();
414
+ let finalValues = selected;
415
+ if (finalValues.length === 0 && filtered.length > 0) {
416
+ const current = filtered[selectedIndex];
417
+ if (current) {
418
+ finalValues = [current.value];
419
+ }
420
+ }
421
+
422
+ const termWidth = Math.max(20, process.stdout.columns || 80);
423
+ const summary = `${ANSI.bold}${ANSI.green}✔${ANSI.reset} ${message} ${ANSI.cyan}[${finalValues.join(', ')}]${ANSI.reset}`;
424
+ process.stdout.write(ANSI.clearLine + truncate(summary, termWidth - 1) + '\n\n');
425
+ resolvePromise(finalValues);
426
+ return;
427
+ }
428
+
429
+ // Ctrl+C
430
+ if (key === '\u0003') {
431
+ cleanup();
432
+ process.exit(130);
433
+ }
434
+
435
+ // Standalone Escape
436
+ if (key === '\u001b' || (chunk.length === 1 && chunk[0] === 0x1b)) {
437
+ cleanup();
438
+ const termWidth = Math.max(20, process.stdout.columns || 80);
439
+ const backSummary = `${ANSI.bold}${ANSI.gray}↩${ANSI.reset} ${ANSI.dim}${message} (Cancelled)${ANSI.reset}`;
440
+ process.stdout.write(ANSI.clearLine + truncate(backSummary, termWidth - 1) + '\n\n');
441
+ resolvePromise(null);
442
+ return;
443
+ }
444
+
445
+ // Up arrow or Ctrl+P
446
+ if (key === '\u001b[A' || key === '\u001bOA' || key === '\u0010') {
447
+ const filtered = getFilteredItems();
448
+ if (filtered.length > 0) {
449
+ selectedIndex = (selectedIndex - 1 + filtered.length) % filtered.length;
450
+ render();
451
+ }
452
+ return;
453
+ }
454
+
455
+ // Down arrow or Ctrl+N
456
+ if (key === '\u001b[B' || key === '\u001bOB' || key === '\u000e') {
457
+ const filtered = getFilteredItems();
458
+ if (filtered.length > 0) {
459
+ selectedIndex = (selectedIndex + 1) % filtered.length;
460
+ render();
461
+ }
462
+ return;
463
+ }
464
+
465
+ // Space key: Toggle checkbox
466
+ if (key === ' ' || (chunk.length === 1 && chunk[0] === 32)) {
467
+ const filtered = getFilteredItems();
468
+ const current = filtered[selectedIndex];
469
+ if (current) {
470
+ if (current.isCustom) {
471
+ const newItem = {
472
+ label: String(current.value),
473
+ value: current.value,
474
+ selected: true,
475
+ };
476
+ allItems.push(newItem);
477
+ filterText = '';
478
+ selectedIndex = allItems.length - 1;
479
+ } else {
480
+ current.selected = !current.selected;
481
+ const original = allItems.find((i) => i.value === current.value);
482
+ if (original) original.selected = current.selected;
483
+ }
484
+ render();
485
+ }
486
+ return;
487
+ }
488
+
489
+ // Backspace: Delete from search filter
490
+ if (chunk.length === 1 && (chunk[0] === 0x7f || chunk[0] === 0x08)) {
491
+ if (filterText.length > 0) {
492
+ filterText = filterText.slice(0, -1);
493
+ selectedIndex = 0;
494
+ render();
495
+ }
496
+ return;
497
+ }
498
+
499
+ // Ctrl+A (Toggle all visible items) or 'a' if filter is empty
500
+ if (chunk.length === 1 && (chunk[0] === 0x01 || (filterText === '' && (key === 'a' || key === 'A')))) {
501
+ const filtered = getFilteredItems();
502
+ const allSelected = filtered.every((i) => i.selected);
503
+ for (const item of filtered) {
504
+ if (!item.isCustom) {
505
+ item.selected = !allSelected;
506
+ const original = allItems.find((i) => i.value === item.value);
507
+ if (original) original.selected = item.selected;
508
+ }
509
+ }
510
+ render();
511
+ return;
512
+ }
513
+
514
+ // Printable character typing (for real-time filter search)
515
+ if (chunk.length === 1 && chunk[0] >= 32 && chunk[0] <= 126) {
516
+ filterText += chunk.toString();
517
+ selectedIndex = 0;
518
+ render();
519
+ return;
520
+ }
521
+ };
522
+
523
+ if (process.stdin.setRawMode) {
524
+ process.stdin.setRawMode(true);
525
+ }
526
+ process.stdin.resume();
527
+ process.stdout.write(ANSI.hideCursor);
528
+ render();
529
+ process.stdin.on('data', onData);
530
+ });
531
+ }
532
+
533
+ async function promptText(options) {
534
+ const message = options.message || options.question || '';
535
+ const defaultValue = options.defaultValue || '';
536
+ const validate = options.validate || null;
537
+
538
+ if (!process.stdin.isTTY) {
539
+ return defaultValue;
540
+ }
541
+
542
+ return new Promise((resolvePromise) => {
543
+ let resolvedCompleter;
544
+ if (typeof options.completer === 'function') {
545
+ resolvedCompleter = options.completer;
546
+ } else if (options.completer === 'dir') {
547
+ resolvedCompleter = createPathCompleter({ directoriesOnly: true });
548
+ } else if (options.completer === 'path') {
549
+ resolvedCompleter = pathCompleter;
550
+ }
551
+
552
+ process.stdin.resume();
553
+
554
+ const rl = readline.createInterface({
555
+ input: process.stdin,
556
+ output: process.stdout,
557
+ completer: resolvedCompleter,
558
+ });
559
+
560
+ const defaultHint = defaultValue ? `${ANSI.gray}(${defaultValue})${ANSI.reset} ` : '';
561
+ const promptMsg = `${ANSI.bold}${ANSI.cyan}?${ANSI.reset} ${ANSI.bold}${message}${ANSI.reset} ${defaultHint}`;
562
+
563
+ const onData = (chunk) => {
564
+ // Standalone Escape key
565
+ if (chunk.length === 1 && chunk[0] === 0x1b) {
566
+ cleanup();
567
+ process.stdout.write(`\r${ANSI.clearLine}${ANSI.bold}${ANSI.gray}↩${ANSI.reset} ${ANSI.dim}${message} (Cancelled)${ANSI.reset}\n\n`);
568
+ resolvePromise(null);
569
+ }
570
+ };
571
+
572
+ const cleanup = () => {
573
+ process.stdin.removeListener('data', onData);
574
+ rl.close();
575
+ };
576
+
577
+ process.stdin.on('data', onData);
578
+
579
+ const ask = () => {
580
+ rl.question(promptMsg, (answer) => {
581
+ const val = answer.trim() || defaultValue;
582
+ if (validate) {
583
+ const res = validate(val);
584
+ if (res !== true) {
585
+ console.log(` ${ANSI.red}✖${ANSI.reset} ${res || 'Invalid input'}`);
586
+ ask();
587
+ return;
588
+ }
589
+ }
590
+ cleanup();
591
+ process.stdout.write('\n');
592
+ resolvePromise(val);
593
+ });
594
+ };
595
+
596
+ ask();
597
+ });
598
+ }
599
+
600
+ async function promptConfirm(options) {
601
+ const message = typeof options === 'string' ? options : (options.message || options.question || '');
602
+ const defaultYes = typeof options === 'object' ? Boolean(options.defaultYes) : false;
603
+
604
+ if (!process.stdin.isTTY) {
605
+ return defaultYes;
606
+ }
607
+
608
+ const choices = [
609
+ { label: 'Yes', value: true },
610
+ { label: 'No', value: false },
611
+ ];
612
+
613
+ const result = await promptSelect({
614
+ message,
615
+ choices,
616
+ defaultIndex: defaultYes ? 0 : 1,
617
+ });
618
+
619
+ return result !== null ? result : defaultYes;
620
+ }
621
+
622
+ module.exports = {
623
+ createPathCompleter,
624
+ pathCompleter,
625
+ promptSelect,
626
+ promptMultiSelect,
627
+ promptText,
628
+ promptConfirm,
629
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsuzuku/arise",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -11,6 +11,12 @@ Use this skill to answer questions about `arise` and run worktree management com
11
11
 
12
12
  ## 1. CLI Quick Reference & Cheatsheet
13
13
 
14
+ ### Interactive Mode
15
+ ```bash
16
+ # Launch interactive menu (create, switch/open, list, nuke)
17
+ arise
18
+ ```
19
+
14
20
  ### Creating Worktrees
15
21
  ```bash
16
22
  # Create or open a worktree for a branch (auto-detects preset, boots Herdr workspace)