@husar.ai/cli 0.4.3 → 0.4.4

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/ui.ts ADDED
@@ -0,0 +1,260 @@
1
+ /**
2
+ * UI utilities for beautiful CLI output
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import boxen from 'boxen';
7
+ import ora, { Ora } from 'ora';
8
+ import logSymbols from 'log-symbols';
9
+ import Enquirer from 'enquirer';
10
+
11
+ // ═══════════════════════════════════════════════════════════════════════════
12
+ // COLORS & THEME
13
+ // ═══════════════════════════════════════════════════════════════════════════
14
+
15
+ export const theme = {
16
+ primary: chalk.hex('#6366f1'), // Indigo
17
+ secondary: chalk.hex('#8b5cf6'), // Purple
18
+ success: chalk.hex('#22c55e'), // Green
19
+ warning: chalk.hex('#f59e0b'), // Amber
20
+ error: chalk.hex('#ef4444'), // Red
21
+ info: chalk.hex('#3b82f6'), // Blue
22
+ muted: chalk.gray,
23
+ bold: chalk.bold,
24
+ dim: chalk.dim,
25
+ link: chalk.cyan.underline,
26
+ };
27
+
28
+ // ═══════════════════════════════════════════════════════════════════════════
29
+ // BANNER
30
+ // ═══════════════════════════════════════════════════════════════════════════
31
+
32
+ const HUSAR_LOGO = `
33
+ ██╗ ██╗██╗ ██╗███████╗ █████╗ ██████╔╗
34
+ ██║ ██║██║ ██║██╔════╝██╔══██╗██╔══██╗
35
+ ███████║██║ ██║███████╗███████║██████╔╝
36
+ ██╔══██║██║ ██║╚════██║██╔══██║██╔══██╗
37
+ ██║ ██║╚██████╔╝███████║██║ ██║██║ ██║
38
+ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝`;
39
+
40
+ export function printBanner(subtitle?: string): void {
41
+ const gradient = HUSAR_LOGO.split('\n')
42
+ .map((line, i) => {
43
+ const colors = ['#818cf8', '#8b5cf6', '#a855f7', '#c084fc', '#d8b4fe', '#e9d5ff'];
44
+ return chalk.hex(colors[i % colors.length])(line);
45
+ })
46
+ .join('\n');
47
+
48
+ console.log(gradient);
49
+ console.log();
50
+ console.log(theme.muted(' Headless CMS for Modern Developers'));
51
+ if (subtitle) {
52
+ console.log(theme.primary(` ${subtitle}`));
53
+ }
54
+ console.log();
55
+ }
56
+
57
+ export function printCompactBanner(): void {
58
+ console.log();
59
+ console.log(chalk.bold(theme.primary(' HUSAR.AI')) + theme.muted(' • Headless CMS'));
60
+ console.log();
61
+ }
62
+
63
+ // ═══════════════════════════════════════════════════════════════════════════
64
+ // BOXES
65
+ // ═══════════════════════════════════════════════════════════════════════════
66
+
67
+ export function box(content: string, title?: string): void {
68
+ console.log(
69
+ boxen(content, {
70
+ padding: 1,
71
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
72
+ borderStyle: 'round',
73
+ borderColor: '#6366f1',
74
+ title: title,
75
+ titleAlignment: 'left',
76
+ }),
77
+ );
78
+ }
79
+
80
+ export function successBox(content: string, title = 'Success'): void {
81
+ console.log(
82
+ boxen(content, {
83
+ padding: 1,
84
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
85
+ borderStyle: 'round',
86
+ borderColor: '#22c55e',
87
+ title: `${logSymbols.success} ${title}`,
88
+ titleAlignment: 'left',
89
+ }),
90
+ );
91
+ }
92
+
93
+ export function errorBox(content: string, title = 'Error'): void {
94
+ console.log(
95
+ boxen(content, {
96
+ padding: 1,
97
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
98
+ borderStyle: 'round',
99
+ borderColor: '#ef4444',
100
+ title: `${logSymbols.error} ${title}`,
101
+ titleAlignment: 'left',
102
+ }),
103
+ );
104
+ }
105
+
106
+ export function infoBox(content: string, title = 'Info'): void {
107
+ console.log(
108
+ boxen(content, {
109
+ padding: 1,
110
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
111
+ borderStyle: 'round',
112
+ borderColor: '#3b82f6',
113
+ title: `${logSymbols.info} ${title}`,
114
+ titleAlignment: 'left',
115
+ }),
116
+ );
117
+ }
118
+
119
+ // ═══════════════════════════════════════════════════════════════════════════
120
+ // SPINNERS
121
+ // ═══════════════════════════════════════════════════════════════════════════
122
+
123
+ export function spinner(text: string): Ora {
124
+ return ora({
125
+ text,
126
+ color: 'magenta',
127
+ spinner: 'dots',
128
+ }).start();
129
+ }
130
+
131
+ // ═══════════════════════════════════════════════════════════════════════════
132
+ // LOG MESSAGES
133
+ // ═══════════════════════════════════════════════════════════════════════════
134
+
135
+ export const log = {
136
+ success: (message: string) => console.log(`${logSymbols.success} ${theme.success(message)}`),
137
+ error: (message: string) => console.log(`${logSymbols.error} ${theme.error(message)}`),
138
+ warning: (message: string) => console.log(`${logSymbols.warning} ${theme.warning(message)}`),
139
+ info: (message: string) => console.log(`${logSymbols.info} ${theme.info(message)}`),
140
+ step: (message: string) => console.log(` ${theme.primary('→')} ${message}`),
141
+ substep: (message: string) => console.log(` ${theme.muted('•')} ${message}`),
142
+ done: (message: string) => console.log(` ${theme.success('✓')} ${message}`),
143
+ fail: (message: string) => console.log(` ${theme.error('✗')} ${message}`),
144
+ dim: (message: string) => console.log(theme.dim(` ${message}`)),
145
+ blank: () => console.log(),
146
+ };
147
+
148
+ // ═══════════════════════════════════════════════════════════════════════════
149
+ // PROMPTS (using Enquirer)
150
+ // ═══════════════════════════════════════════════════════════════════════════
151
+
152
+ interface SelectChoice {
153
+ name: string;
154
+ message: string;
155
+ hint?: string;
156
+ }
157
+
158
+ const enquirer = new Enquirer();
159
+
160
+ export async function select<T extends string>(message: string, choices: SelectChoice[]): Promise<T> {
161
+ const response = (await enquirer.prompt({
162
+ type: 'select',
163
+ name: 'value',
164
+ message: theme.primary(message),
165
+ choices: choices.map((c) => ({
166
+ name: c.name,
167
+ message: c.message,
168
+ hint: c.hint ? theme.dim(c.hint) : undefined,
169
+ })),
170
+ })) as { value: T };
171
+ return response.value;
172
+ }
173
+
174
+ export async function input(message: string, defaultValue?: string): Promise<string> {
175
+ const response = (await enquirer.prompt({
176
+ type: 'input',
177
+ name: 'value',
178
+ message: theme.primary(message),
179
+ initial: defaultValue,
180
+ })) as { value: string };
181
+ return response.value;
182
+ }
183
+
184
+ export async function confirm(message: string, defaultValue = true): Promise<boolean> {
185
+ const response = (await enquirer.prompt({
186
+ type: 'confirm',
187
+ name: 'value',
188
+ message: theme.primary(message),
189
+ initial: defaultValue,
190
+ })) as { value: boolean };
191
+ return response.value;
192
+ }
193
+
194
+ export async function password(message: string): Promise<string> {
195
+ const response = (await enquirer.prompt({
196
+ type: 'password',
197
+ name: 'value',
198
+ message: theme.primary(message),
199
+ })) as { value: string };
200
+ return response.value;
201
+ }
202
+
203
+ // ═══════════════════════════════════════════════════════════════════════════
204
+ // TABLES & LISTS
205
+ // ═══════════════════════════════════════════════════════════════════════════
206
+
207
+ export function list(items: string[], title?: string): void {
208
+ if (title) {
209
+ console.log(chalk.bold(title));
210
+ }
211
+ items.forEach((item) => {
212
+ console.log(` ${theme.primary('•')} ${item}`);
213
+ });
214
+ }
215
+
216
+ export function keyValue(pairs: Record<string, string | undefined>, title?: string): void {
217
+ if (title) {
218
+ console.log(chalk.bold(title));
219
+ }
220
+ const maxKeyLen = Math.max(...Object.keys(pairs).map((k) => k.length));
221
+ Object.entries(pairs).forEach(([key, value]) => {
222
+ if (value !== undefined) {
223
+ const paddedKey = key.padEnd(maxKeyLen);
224
+ console.log(` ${theme.muted(paddedKey)} ${value}`);
225
+ }
226
+ });
227
+ }
228
+
229
+ // ═══════════════════════════════════════════════════════════════════════════
230
+ // DIVIDERS
231
+ // ═══════════════════════════════════════════════════════════════════════════
232
+
233
+ export function divider(char = '─', length = 50): void {
234
+ console.log(theme.muted(char.repeat(length)));
235
+ }
236
+
237
+ export function header(text: string): void {
238
+ console.log();
239
+ console.log(theme.primary(chalk.bold(text)));
240
+ divider();
241
+ }
242
+
243
+ // ═══════════════════════════════════════════════════════════════════════════
244
+ // NEXT STEPS
245
+ // ═══════════════════════════════════════════════════════════════════════════
246
+
247
+ export function nextSteps(steps: string[], title = 'Next Steps'): void {
248
+ console.log();
249
+ console.log(chalk.bold(title));
250
+ steps.forEach((step, i) => {
251
+ console.log(` ${theme.primary(`${i + 1}.`)} ${step}`);
252
+ });
253
+ console.log();
254
+ }
255
+
256
+ // ═══════════════════════════════════════════════════════════════════════════
257
+ // EXPORTS
258
+ // ═══════════════════════════════════════════════════════════════════════════
259
+
260
+ export { chalk, boxen, ora, logSymbols, Enquirer };