@portl/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,210 @@
1
+ /**
2
+ * Enhanced prompt utilities for intuitive CLI interactions
3
+ */
4
+
5
+ const ANSI = {
6
+ reset: '\x1b[0m',
7
+ bold: '\x1b[1m',
8
+ dim: '\x1b[2m',
9
+ cyan: '\x1b[36m',
10
+ green: '\x1b[32m',
11
+ yellow: '\x1b[33m',
12
+ red: '\x1b[31m',
13
+ blue: '\x1b[34m',
14
+ magenta: '\x1b[35m',
15
+ };
16
+
17
+ function c(text, color) {
18
+ return `${color}${text}${ANSI.reset}`;
19
+ }
20
+
21
+ /**
22
+ * Display a visual section separator with emoji
23
+ */
24
+ export function showSection(title, emoji = 'šŸ“¦') {
25
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
26
+ console.log(`${emoji} ${c(title.toUpperCase(), ANSI.bold)}`);
27
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
28
+ }
29
+
30
+ /**
31
+ * Display an inline tip/help message
32
+ */
33
+ export function showTip(message) {
34
+ console.log(c(`šŸ’” ${message}`, ANSI.dim));
35
+ }
36
+
37
+ /**
38
+ * Display a success message
39
+ */
40
+ export function showSuccess(message) {
41
+ console.log(c(`āœ“ ${message}`, ANSI.green));
42
+ }
43
+
44
+ /**
45
+ * Display an error message
46
+ */
47
+ export function showError(message) {
48
+ console.log(c(`āœ— ${message}`, ANSI.red));
49
+ }
50
+
51
+ /**
52
+ * Display a warning message
53
+ */
54
+ export function showWarning(message) {
55
+ console.log(c(`⚠ ${message}`, ANSI.yellow));
56
+ }
57
+
58
+ /**
59
+ * Display an info message
60
+ */
61
+ export function showInfo(message) {
62
+ console.log(c(`ℹ ${message}`, ANSI.blue));
63
+ }
64
+
65
+ /**
66
+ * Ask a simple yes/no question
67
+ * Returns true for yes, false for no
68
+ */
69
+ export async function askYesNo(rl, question, defaultYes = true) {
70
+ const suffix = defaultYes ? c('(Y/n)', ANSI.dim) : c('(y/N)', ANSI.dim);
71
+ const answer = await rl.question(`${question} ${suffix}: `);
72
+
73
+ if (!answer || !answer.trim()) {
74
+ return defaultYes;
75
+ }
76
+
77
+ const normalized = answer.trim().toLowerCase();
78
+ return normalized === 'y' || normalized === 'yes' || normalized === 's' || normalized === 'si';
79
+ }
80
+
81
+ /**
82
+ * Ask a question with a default value
83
+ */
84
+ export async function ask(rl, question, defaultValue = '') {
85
+ const suffix = defaultValue ? ` ${c(`(${defaultValue})`, ANSI.dim)}` : '';
86
+ const answer = await rl.question(`${question}${suffix}: `);
87
+ const trimmed = answer.trim();
88
+ return trimmed || defaultValue || '';
89
+ }
90
+
91
+ /**
92
+ * Ask a question that requires a non-empty answer
93
+ */
94
+ export async function askNonEmpty(rl, question, defaultValue = '') {
95
+ while (true) {
96
+ const value = await ask(rl, question, defaultValue);
97
+ if (value.trim()) {
98
+ return value.trim();
99
+ }
100
+ showWarning('Value cannot be empty. Try again.');
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Display a numbered list and let user select one option
106
+ */
107
+ export async function selectOption(rl, title, options) {
108
+ console.log(`\n${c(title, ANSI.bold)}`);
109
+ options.forEach((option, index) => {
110
+ const description = option.description ? c(` - ${option.description}`, ANSI.dim) : '';
111
+ console.log(` ${c(String(index + 1), ANSI.cyan)}. ${option.label}${description}`);
112
+ });
113
+
114
+ while (true) {
115
+ const answer = await rl.question(`\n${c('Select (1-' + options.length + '):', ANSI.bold)} `);
116
+ const selected = Number.parseInt(answer.trim(), 10);
117
+ if (!Number.isNaN(selected) && selected >= 1 && selected <= options.length) {
118
+ return options[selected - 1];
119
+ }
120
+
121
+ showWarning('Invalid selection. Try again.');
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Display the Portl ASCII banner
127
+ */
128
+ export function showBanner() {
129
+ const banner = `
130
+ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā•—
131
+ ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā•šā•ā•ā–ˆā–ˆā•”ā•ā•ā•ā–ˆā–ˆā•‘
132
+ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā• ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘
133
+ ā–ˆā–ˆā•”ā•ā•ā•ā• ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•— ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘
134
+ ā–ˆā–ˆā•‘ ā•šā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—
135
+ ā•šā•ā• ā•šā•ā•ā•ā•ā•ā• ā•šā•ā• ā•šā•ā• ā•šā•ā• ā•šā•ā•ā•ā•ā•ā•ā•
136
+ `;
137
+ console.log(c(banner, ANSI.cyan));
138
+ console.log(c('Portl Setup Wizard', ANSI.bold));
139
+ console.log(c('Bring your infra. Ship with AI.', ANSI.dim));
140
+ console.log('');
141
+ }
142
+
143
+ /**
144
+ * Display a summary section
145
+ */
146
+ export function showSummary(title, items) {
147
+ showSection(title, 'āœ…');
148
+ if (items.length === 0) {
149
+ console.log(c(' No integrations configured', ANSI.dim));
150
+ } else {
151
+ items.forEach(item => {
152
+ console.log(` ${c('āœ“', ANSI.green)} ${item}`);
153
+ });
154
+ }
155
+ console.log('');
156
+ }
157
+
158
+ /**
159
+ * Display "next steps" instructions
160
+ */
161
+ export function showNextSteps(steps) {
162
+ console.log(c('Next steps:', ANSI.bold));
163
+ steps.forEach((step, index) => {
164
+ console.log(` ${c(String(index + 1), ANSI.cyan)}. ${step}`);
165
+ });
166
+ console.log('');
167
+ }
168
+
169
+ /**
170
+ * Simple spinner for loading states
171
+ */
172
+ export class Spinner {
173
+ constructor(message) {
174
+ this.message = message;
175
+ this.frames = ['ā ‹', 'ā ™', 'ā ¹', 'ā ø', 'ā ¼', 'ā “', 'ā ¦', 'ā §', 'ā ‡', 'ā '];
176
+ this.index = 0;
177
+ this.intervalId = null;
178
+ }
179
+
180
+ start() {
181
+ process.stdout.write(`${this.frames[0]} ${this.message}`);
182
+ this.intervalId = setInterval(() => {
183
+ this.index = (this.index + 1) % this.frames.length;
184
+ process.stdout.write(`\r${this.frames[this.index]} ${this.message}`);
185
+ }, 80);
186
+ }
187
+
188
+ success(message) {
189
+ if (this.intervalId) {
190
+ clearInterval(this.intervalId);
191
+ }
192
+ process.stdout.write(`\r${c('āœ“', ANSI.green)} ${message || this.message}\n`);
193
+ }
194
+
195
+ fail(message) {
196
+ if (this.intervalId) {
197
+ clearInterval(this.intervalId);
198
+ }
199
+ process.stdout.write(`\r${c('āœ—', ANSI.red)} ${message || this.message}\n`);
200
+ }
201
+
202
+ stop() {
203
+ if (this.intervalId) {
204
+ clearInterval(this.intervalId);
205
+ }
206
+ process.stdout.write('\r' + ' '.repeat(this.message.length + 3) + '\r');
207
+ }
208
+ }
209
+
210
+ export { ANSI, c };