upfynai-code 2.2.0 → 2.3.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,634 @@
1
+ /**
2
+ * Upfyn-Code CLI — Terminal UI & Animations
3
+ *
4
+ * Beautiful blue-themed terminal experience inspired by Claude Code
5
+ * with rocket launch animation, ASCII branding, and styled panels.
6
+ */
7
+
8
+ // ─── ANSI escape codes ───
9
+ const ESC = '\x1b';
10
+ const CSI = `${ESC}[`;
11
+
12
+ // Blue theme palette
13
+ const theme = {
14
+ // Core blues
15
+ blue: `${CSI}38;2;59;130;246m`, // #3B82F6
16
+ brightBlue: `${CSI}38;2;96;165;250m`, // #60A5FA
17
+ skyBlue: `${CSI}38;2;125;211;252m`, // #7DD3FC
18
+ deepBlue: `${CSI}38;2;30;58;138m`, // #1E3A8A
19
+ indigo: `${CSI}38;2;99;102;241m`, // #6366F1
20
+ violet: `${CSI}38;2;139;92;246m`, // #8B5CF6
21
+
22
+ // Accent
23
+ cyan: `${CSI}38;2;34;211;238m`, // #22D3EE
24
+ teal: `${CSI}38;2;45;212;191m`, // #2DD4BF
25
+ white: `${CSI}38;2;248;250;252m`, // #F8FAFC
26
+ gray: `${CSI}38;2;148;163;184m`, // #94A3B8
27
+ dimGray: `${CSI}38;2;71;85;105m`, // #475569
28
+ darkGray: `${CSI}38;2;51;65;85m`, // #334155
29
+ yellow: `${CSI}38;2;250;204;21m`, // #FACC15
30
+ green: `${CSI}38;2;74;222;128m`, // #4ADE80
31
+ red: `${CSI}38;2;248;113;113m`, // #F87171
32
+ orange: `${CSI}38;2;251;146;60m`, // #FB923C
33
+
34
+ // Background
35
+ bgDeep: `${CSI}48;2;15;23;42m`, // #0F172A
36
+ bgBlue: `${CSI}48;2;30;58;138m`, // #1E3A8A
37
+ bgBright: `${CSI}48;2;59;130;246m`, // #3B82F6
38
+
39
+ // Formatting
40
+ bold: `${CSI}1m`,
41
+ dim: `${CSI}2m`,
42
+ italic: `${CSI}3m`,
43
+ underline: `${CSI}4m`,
44
+ reset: `${CSI}0m`,
45
+ };
46
+
47
+ const t = theme;
48
+ const r = t.reset;
49
+
50
+ // Helper — colorize
51
+ const c = {
52
+ blue: (s) => `${t.blue}${s}${r}`,
53
+ bright: (s) => `${t.brightBlue}${s}${r}`,
54
+ sky: (s) => `${t.skyBlue}${s}${r}`,
55
+ cyan: (s) => `${t.cyan}${s}${r}`,
56
+ teal: (s) => `${t.teal}${s}${r}`,
57
+ white: (s) => `${t.bold}${t.white}${s}${r}`,
58
+ gray: (s) => `${t.gray}${s}${r}`,
59
+ dim: (s) => `${t.dimGray}${s}${r}`,
60
+ dark: (s) => `${t.darkGray}${s}${r}`,
61
+ green: (s) => `${t.green}${s}${r}`,
62
+ yellow: (s) => `${t.yellow}${s}${r}`,
63
+ red: (s) => `${t.red}${s}${r}`,
64
+ orange: (s) => `${t.orange}${s}${r}`,
65
+ violet: (s) => `${t.violet}${s}${r}`,
66
+ indigo: (s) => `${t.indigo}${s}${r}`,
67
+ bold: (s) => `${t.bold}${s}${r}`,
68
+ bBlue: (s) => `${t.bold}${t.blue}${s}${r}`,
69
+ bBright: (s) => `${t.bold}${t.brightBlue}${s}${r}`,
70
+ bWhite: (s) => `${t.bold}${t.white}${s}${r}`,
71
+ bCyan: (s) => `${t.bold}${t.cyan}${s}${r}`,
72
+ };
73
+
74
+ // ─── ASCII Art Logo ───
75
+ const LOGO = [
76
+ `${t.brightBlue} __ __ ____ ${t.skyBlue} ______ __ ${r}`,
77
+ `${t.brightBlue} / / / /___ / __/_ ______ ${t.skyBlue}/ ____/___ ____/ /__ ${r}`,
78
+ `${t.brightBlue} / / / / __ \\/ /_/ / / / __ \\ ${t.blue}____${t.skyBlue}/ / / __ \\/ __ / _ \\ ${r}`,
79
+ `${t.brightBlue} / /_/ / /_/ / __/ /_/ / / / / ${t.blue}/___ ${t.skyBlue}/ /___/ /_/ / /_/ / __/ ${r}`,
80
+ `${t.brightBlue} \\____/ .___/_/ \\__, /_/ /_/ ${t.skyBlue}\\____/\\____/\\__,_/\\___/ ${r}`,
81
+ `${t.brightBlue} /_/ /____/ ${r}`,
82
+ ];
83
+
84
+ // ─── Rocket ASCII Art (16 frames for launch animation) ───
85
+ const ROCKET_FRAMES = [
86
+ // Frame 0: Pre-launch — rocket on pad
87
+ [
88
+ ` ${t.dimGray}│${r}`,
89
+ ` ${t.white}╱${t.brightBlue}▲${t.white}╲${r}`,
90
+ ` ${t.white}│${t.blue}║║${t.white}│${r}`,
91
+ ` ${t.white}│${t.blue}║║${t.white}│${r}`,
92
+ ` ${t.white}│${t.brightBlue}║║${t.white}│${r}`,
93
+ ` ${t.white}╱${t.blue}║║║${t.white}╲${r}`,
94
+ ` ${t.white}╱${t.blue}═════${t.white}╲${r}`,
95
+ ` ${t.dimGray}▔▔▔▔▔▔▔▔▔${r}`,
96
+ ],
97
+ // Frame 1: Ignition
98
+ [
99
+ ` ${t.dimGray}│${r}`,
100
+ ` ${t.white}╱${t.brightBlue}▲${t.white}╲${r}`,
101
+ ` ${t.white}│${t.blue}║║${t.white}│${r}`,
102
+ ` ${t.white}│${t.blue}║║${t.white}│${r}`,
103
+ ` ${t.white}│${t.brightBlue}║║${t.white}│${r}`,
104
+ ` ${t.white}╱${t.blue}║║║${t.white}╲${r}`,
105
+ ` ${t.white}╱${t.blue}═════${t.white}╲${r}`,
106
+ ` ${t.orange}▓${t.yellow}░${t.orange}▓${t.yellow}░${t.orange}▓${r}`,
107
+ ],
108
+ // Frame 2: Lift-off
109
+ [
110
+ ` ${t.white}╱${t.brightBlue}▲${t.white}╲${r}`,
111
+ ` ${t.white}│${t.blue}║║${t.white}│${r}`,
112
+ ` ${t.white}│${t.blue}║║${t.white}│${r}`,
113
+ ` ${t.white}│${t.brightBlue}║║${t.white}│${r}`,
114
+ ` ${t.white}╱${t.blue}║║║${t.white}╲${r}`,
115
+ ` ${t.white}╱${t.blue}═════${t.white}╲${r}`,
116
+ ` ${t.yellow}▓${t.orange}█${t.yellow}▓${t.orange}█${t.yellow}▓${r}`,
117
+ ` ${t.orange}░${t.yellow}▒${t.orange}░${r}`,
118
+ ],
119
+ // Frame 3: Ascending
120
+ [
121
+ ` ${t.white}╱${t.brightBlue}▲${t.white}╲${r}`,
122
+ ` ${t.white}│${t.blue}║║${t.white}│${r}`,
123
+ ` ${t.white}│${t.brightBlue}║║${t.white}│${r}`,
124
+ ` ${t.white}╱${t.blue}║║║${t.white}╲${r}`,
125
+ ` ${t.white}╱${t.blue}═════${t.white}╲${r}`,
126
+ ` ${t.yellow}█${t.orange}▓${t.yellow}█${t.orange}▓${t.yellow}█${r}`,
127
+ ` ${t.orange}▓${t.yellow}█${t.orange}▓${r}`,
128
+ ` ${t.yellow}░${t.orange}░${r}`,
129
+ ],
130
+ // Frame 4: Full thrust
131
+ [
132
+ ` ${t.white}╱${t.brightBlue}▲${t.white}╲${r}`,
133
+ ` ${t.white}│${t.blue}║║${t.white}│${r}`,
134
+ ` ${t.white}│${t.brightBlue}║║${t.white}│${r}`,
135
+ ` ${t.white}╱${t.blue}║║║${t.white}╲${r}`,
136
+ ` ${t.white}╱${t.blue}═════${t.white}╲${r}`,
137
+ ` ${t.yellow}█${t.orange}▓${t.yellow}█${t.orange}▓${t.yellow}█${t.orange}▓${r}`,
138
+ ` ${t.orange}▓${t.yellow}█${t.orange}▓${t.yellow}█${r}`,
139
+ ` ${t.yellow}▒${t.orange}░${t.yellow}▒${r}`,
140
+ ` ${t.orange}░${t.yellow}░${r}`,
141
+ ],
142
+ // Frame 5: Going up (smaller)
143
+ [
144
+ ``,
145
+ ` ${t.white}╱${t.brightBlue}▲${t.white}╲${r}`,
146
+ ` ${t.white}│${t.blue}║║${t.white}│${r}`,
147
+ ` ${t.white}╱${t.blue}║║║${t.white}╲${r}`,
148
+ ` ${t.white}╱${t.blue}═════${t.white}╲${r}`,
149
+ ` ${t.yellow}█${t.orange}▓${t.yellow}█${t.orange}▓${t.yellow}█${t.orange}▓${r}`,
150
+ ` ${t.orange}█${t.yellow}▓${t.orange}█${t.yellow}▓${r}`,
151
+ ` ${t.yellow}▒${t.orange}▒${t.yellow}▒${r}`,
152
+ ` ${t.orange}░${r} ${t.dimGray}·${r}`,
153
+ ],
154
+ // Frame 6: Higher
155
+ [
156
+ ``,
157
+ ``,
158
+ ` ${t.skyBlue}╱${t.brightBlue}▲${t.skyBlue}╲${r}`,
159
+ ` ${t.skyBlue}│${t.blue}║║${t.skyBlue}│${r}`,
160
+ ` ${t.skyBlue}╱${t.blue}══${t.skyBlue}╲${r}`,
161
+ ` ${t.yellow}▓${t.orange}█${t.yellow}▓${t.orange}▓${r}`,
162
+ ` ${t.orange}▒${t.yellow}▒${r}`,
163
+ ` ${t.yellow}░${r}`,
164
+ ` ${t.dimGray}·${r} ${t.dimGray}·${r}`,
165
+ ],
166
+ // Frame 7: Almost gone — tiny dot
167
+ [
168
+ ``,
169
+ ``,
170
+ ``,
171
+ ` ${t.skyBlue}╱${t.brightBlue}▲${t.skyBlue}╲${r}`,
172
+ ` ${t.blue}══${r}`,
173
+ ` ${t.orange}▒${t.yellow}▒${r}`,
174
+ ` ${t.yellow}░${r}`,
175
+ ``,
176
+ ` ${t.dimGray}·${r} ${t.dimGray}·${r} ${t.dimGray}·${r}`,
177
+ ],
178
+ // Frame 8: Gone — just stars
179
+ [
180
+ ``,
181
+ ``,
182
+ ``,
183
+ ` ${t.brightBlue}✦${r}`,
184
+ ``,
185
+ ` ${t.dimGray}·${r}`,
186
+ ``,
187
+ ` ${t.dimGray}·${r} ${t.dimGray}·${r}`,
188
+ ` ${t.dimGray}·${r} ${t.dimGray}·${r} ${t.dimGray}·${r}`,
189
+ ],
190
+ ];
191
+
192
+ // ─── Sleep utility ───
193
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
194
+
195
+ // ─── Clear screen ───
196
+ const clearScreen = () => process.stdout.write(`${CSI}2J${CSI}H`);
197
+
198
+ // ─── Move cursor ───
199
+ const moveTo = (row, col) => process.stdout.write(`${CSI}${row};${col}H`);
200
+
201
+ // ─── Hide/show cursor ───
202
+ const hideCursor = () => process.stdout.write(`${CSI}?25l`);
203
+ const showCursor = () => process.stdout.write(`${CSI}?25h`);
204
+
205
+ /**
206
+ * Play the rocket launch animation
207
+ */
208
+ async function playRocketAnimation() {
209
+ hideCursor();
210
+ clearScreen();
211
+
212
+ const cols = process.stdout.columns || 80;
213
+ const centerX = Math.floor(cols / 2) - 12;
214
+
215
+ // Show pre-launch text
216
+ moveTo(2, centerX - 5);
217
+ process.stdout.write(c.dim('Initializing Upfyn-Code...'));
218
+
219
+ await sleep(400);
220
+
221
+ // Play each frame
222
+ const delays = [500, 300, 200, 150, 120, 120, 150, 200, 400];
223
+
224
+ for (let f = 0; f < ROCKET_FRAMES.length; f++) {
225
+ // Clear the animation area (rows 4-14)
226
+ for (let row = 4; row <= 14; row++) {
227
+ moveTo(row, 1);
228
+ process.stdout.write(' '.repeat(cols));
229
+ }
230
+
231
+ const frame = ROCKET_FRAMES[f];
232
+ for (let i = 0; i < frame.length; i++) {
233
+ moveTo(4 + i, centerX);
234
+ process.stdout.write(frame[i]);
235
+ }
236
+
237
+ // Status text under animation
238
+ moveTo(14, centerX - 2);
239
+ if (f === 0) process.stdout.write(c.dim('Systems check...'));
240
+ else if (f === 1) process.stdout.write(c.orange('Ignition sequence started'));
241
+ else if (f === 2) process.stdout.write(c.yellow('Lift-off!'));
242
+ else if (f <= 4) process.stdout.write(c.bright('Ascending...'));
243
+ else if (f <= 6) process.stdout.write(c.sky('Reaching orbit...'));
244
+ else if (f === 7) process.stdout.write(c.cyan('Entering space...'));
245
+ else process.stdout.write(c.bBright('Ready to code! ✦'));
246
+
247
+ await sleep(delays[f] || 200);
248
+ }
249
+
250
+ await sleep(300);
251
+ showCursor();
252
+ }
253
+
254
+ /**
255
+ * Draw a horizontal line
256
+ */
257
+ function hLine(char = '─', width = 60) {
258
+ return c.dark(char.repeat(width));
259
+ }
260
+
261
+ /**
262
+ * Draw a box with title
263
+ */
264
+ function box(title, content, width = 58) {
265
+ const lines = [];
266
+ const inner = width - 4;
267
+
268
+ lines.push(` ${t.dimGray}╭─${r} ${c.bBright(title)} ${t.dimGray}${'─'.repeat(Math.max(0, inner - title.length - 1))}╮${r}`);
269
+
270
+ for (const line of content) {
271
+ // Pad line — we can't easily measure ANSI length, so just append
272
+ lines.push(` ${t.dimGray}│${r} ${line}`);
273
+ }
274
+
275
+ lines.push(` ${t.dimGray}╰${'─'.repeat(width - 2)}╯${r}`);
276
+ return lines;
277
+ }
278
+
279
+ /**
280
+ * Show the main welcome screen (like Claude Code's startup)
281
+ */
282
+ function showWelcomeScreen(version, options = {}) {
283
+ const cols = process.stdout.columns || 80;
284
+ const { serverUrl, username, cwd, isConnected } = options;
285
+
286
+ clearScreen();
287
+
288
+ const lines = [];
289
+
290
+ // ─── Top border with version ───
291
+ const verStr = `Upfyn-Code v${version}`;
292
+ const topLine = `${t.dimGray}── ${r}${c.bBright(verStr)}${t.dimGray} ${'─'.repeat(Math.max(0, 56 - verStr.length))}${r}`;
293
+ lines.push('');
294
+ lines.push(` ${topLine}`);
295
+ lines.push('');
296
+
297
+ // ─── Left panel: Welcome + Mascot ───
298
+ // We use a side-by-side layout like Claude Code
299
+ const leftPanel = [];
300
+ const rightPanel = [];
301
+
302
+ // Welcome message
303
+ leftPanel.push(c.bWhite(' Welcome back!'));
304
+ leftPanel.push('');
305
+
306
+ // Rocket mascot (small static version)
307
+ leftPanel.push(` ${t.dimGray}│${r}`);
308
+ leftPanel.push(` ${t.white}╱${t.brightBlue}▲${t.white}╲${r}`);
309
+ leftPanel.push(` ${t.white}│${t.blue}║${t.brightBlue}◈${t.blue}║${t.white}│${r}`);
310
+ leftPanel.push(` ${t.white}│${t.blue}║║${t.white}│${r}`);
311
+ leftPanel.push(` ${t.white}╱${t.blue}═══${t.white}╲${r}`);
312
+ leftPanel.push(` ${t.cyan}▔▔▔▔▔${r}`);
313
+ leftPanel.push('');
314
+
315
+ // Info
316
+ if (username) {
317
+ leftPanel.push(` ${c.gray('User:')} ${c.bright(username)}`);
318
+ }
319
+ leftPanel.push(` ${c.gray('Path:')} ${c.dim(cwd || process.cwd())}`);
320
+
321
+ // Right panel: Tips + Recent Activity
322
+ rightPanel.push(` ${c.bCyan('Tips for getting started')}`);
323
+ rightPanel.push(` ${c.gray('Run')} ${c.bright('uc connect')} ${c.gray('to bridge to the web UI')}`);
324
+ rightPanel.push(` ${c.gray('Run')} ${c.bright('uc start')} ${c.gray('to launch the local server')}`);
325
+ rightPanel.push(` ${c.gray('Run')} ${c.bright('uc status')} ${c.gray('to see configuration')}`);
326
+ rightPanel.push(` ${c.gray('Run')} ${c.bright('uc help')} ${c.gray('for all commands')}`);
327
+ rightPanel.push('');
328
+ rightPanel.push(` ${c.bCyan('Recent activity')}`);
329
+ if (isConnected) {
330
+ rightPanel.push(` ${c.green('●')} Connected to ${c.bright(serverUrl || 'server')}`);
331
+ } else {
332
+ rightPanel.push(` ${c.dim('No active connection')}`);
333
+ }
334
+ rightPanel.push('');
335
+ if (serverUrl) {
336
+ rightPanel.push(` ${c.gray('Server:')} ${c.cyan(serverUrl)}`);
337
+ }
338
+
339
+ // Merge panels side by side
340
+ const leftWidth = 30;
341
+ const maxRows = Math.max(leftPanel.length, rightPanel.length);
342
+
343
+ for (let i = 0; i < maxRows; i++) {
344
+ const left = leftPanel[i] || '';
345
+ const right = rightPanel[i] || '';
346
+
347
+ // Separator between panels
348
+ if (i === 0) {
349
+ lines.push(`${left}${''.padEnd(Math.max(0, leftWidth - stripAnsi(left).length))}${t.dimGray}│${r} ${right}`);
350
+ } else {
351
+ lines.push(`${left}${''.padEnd(Math.max(0, leftWidth - stripAnsi(left).length))}${t.dimGray}│${r} ${right}`);
352
+ }
353
+ }
354
+
355
+ lines.push('');
356
+
357
+ // ─── Bottom border ───
358
+ lines.push(` ${t.dimGray}${'─'.repeat(58)}${r}`);
359
+ lines.push('');
360
+
361
+ // ─── Status bar ───
362
+ const statusItems = [];
363
+ if (isConnected) {
364
+ statusItems.push(`${t.green}● Connected${r}`);
365
+ } else {
366
+ statusItems.push(`${t.dimGray}○ Not connected${r}`);
367
+ }
368
+ statusItems.push(`${t.gray}v${version}${r}`);
369
+
370
+ lines.push(` ${statusItems.join(` ${t.dimGray}·${r} `)}`);
371
+ lines.push('');
372
+
373
+ // Print all at once
374
+ process.stdout.write(lines.join('\n') + '\n');
375
+ }
376
+
377
+ /**
378
+ * Show connection success banner
379
+ */
380
+ function showConnectionBanner(username, serverUrl) {
381
+ const lines = [];
382
+ lines.push('');
383
+ lines.push(` ${t.green}╭──────────────────────────────────────────────╮${r}`);
384
+ lines.push(` ${t.green}│${r} ${c.green('✓')} ${c.bWhite('Connected successfully!')} ${t.green}│${r}`);
385
+ lines.push(` ${t.green}│${r} ${t.green}│${r}`);
386
+ lines.push(` ${t.green}│${r} ${c.gray('User:')} ${c.bright(username || 'Unknown')}${' '.repeat(Math.max(0, 30 - (username || 'Unknown').length))}${t.green}│${r}`);
387
+ lines.push(` ${t.green}│${r} ${c.gray('Server:')} ${c.cyan(serverUrl || 'Unknown')}${' '.repeat(Math.max(0, 28 - (serverUrl || 'Unknown').length))}${t.green}│${r}`);
388
+ lines.push(` ${t.green}│${r} ${t.green}│${r}`);
389
+ lines.push(` ${t.green}│${r} ${c.dim('Your machine is bridged to the web UI.')} ${t.green}│${r}`);
390
+ lines.push(` ${t.green}│${r} ${c.dim('Press Ctrl+C to disconnect.')} ${t.green}│${r}`);
391
+ lines.push(` ${t.green}╰──────────────────────────────────────────────╯${r}`);
392
+ lines.push('');
393
+
394
+ process.stdout.write(lines.join('\n') + '\n');
395
+ }
396
+
397
+ /**
398
+ * Show a spinner with message
399
+ */
400
+ function createSpinner(message) {
401
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
402
+ let i = 0;
403
+ let interval;
404
+
405
+ return {
406
+ start() {
407
+ hideCursor();
408
+ interval = setInterval(() => {
409
+ const frame = frames[i % frames.length];
410
+ process.stdout.write(`\r ${t.brightBlue}${frame}${r} ${c.gray(message)}`);
411
+ i++;
412
+ }, 80);
413
+ },
414
+ stop(finalMessage) {
415
+ clearInterval(interval);
416
+ process.stdout.write(`\r ${c.green('✓')} ${c.white(finalMessage || message)}${''.padEnd(20)}\n`);
417
+ showCursor();
418
+ },
419
+ fail(finalMessage) {
420
+ clearInterval(interval);
421
+ process.stdout.write(`\r ${c.red('✗')} ${c.red(finalMessage || message)}${''.padEnd(20)}\n`);
422
+ showCursor();
423
+ }
424
+ };
425
+ }
426
+
427
+ /**
428
+ * Show styled help
429
+ */
430
+ function showStyledHelp(version) {
431
+ const lines = [];
432
+
433
+ lines.push('');
434
+ // Header
435
+ lines.push(` ${t.brightBlue}╔══════════════════════════════════════════════════════════╗${r}`);
436
+ lines.push(` ${t.brightBlue}║${r} ${c.bBright('Upfyn-Code')} ${c.dim('—')} ${c.gray('by Thinqmesh Technologies')} ${t.brightBlue}║${r}`);
437
+ lines.push(` ${t.brightBlue}║${r} ${c.dim('Visual AI Coding Interface with Upfyn-Canvas')} ${t.brightBlue}║${r}`);
438
+ lines.push(` ${t.brightBlue}╚══════════════════════════════════════════════════════════╝${r}`);
439
+ lines.push('');
440
+
441
+ // Usage
442
+ lines.push(` ${c.bBright('Usage:')}`);
443
+ lines.push(` ${c.bright('uc')} ${c.cyan('[command]')} ${c.dim('[options]')}`);
444
+ lines.push('');
445
+
446
+ // Commands
447
+ lines.push(` ${c.bBright('Commands:')}`);
448
+ const cmds = [
449
+ ['start', 'Start the local server (default)'],
450
+ ['connect', 'Connect local machine to hosted server'],
451
+ ['status', 'Show configuration and data locations'],
452
+ ['install-commands', 'Install /upfynai-* slash commands'],
453
+ ['uninstall-commands', 'Remove /upfynai-* slash commands'],
454
+ ['update', 'Update to the latest version'],
455
+ ['help', 'Show this help information'],
456
+ ['version', 'Show version information'],
457
+ ];
458
+ for (const [cmd, desc] of cmds) {
459
+ lines.push(` ${c.cyan(cmd.padEnd(22))} ${c.gray(desc)}`);
460
+ }
461
+ lines.push('');
462
+
463
+ // Slash Commands
464
+ lines.push(` ${c.bBright('Slash Commands')} ${c.dim('(inside your AI CLI):')}`);
465
+ const slashCmds = [
466
+ ['/upfynai', 'Start the web UI server'],
467
+ ['/upfynai-connect', 'Connect CLI session to web UI'],
468
+ ['/upfynai-disconnect', 'Disconnect from web UI'],
469
+ ['/upfynai-status', 'Show connection status'],
470
+ ['/upfynai-doctor', 'Run diagnostics'],
471
+ ];
472
+ for (const [cmd, desc] of slashCmds) {
473
+ lines.push(` ${c.violet(cmd.padEnd(22))} ${c.gray(desc)}`);
474
+ }
475
+ lines.push('');
476
+
477
+ // Options
478
+ lines.push(` ${c.bBright('Options:')}`);
479
+ const opts = [
480
+ ['-p, --port <port>', 'Set server port (default: 3001)'],
481
+ ['--server <url>', 'Server URL for connect'],
482
+ ['--key <token>', 'Relay token for connect'],
483
+ ['--database-path <path>', 'Custom database location'],
484
+ ['-h, --help', 'Show help'],
485
+ ['-v, --version', 'Show version'],
486
+ ];
487
+ for (const [opt, desc] of opts) {
488
+ lines.push(` ${c.yellow(opt.padEnd(28))} ${c.gray(desc)}`);
489
+ }
490
+ lines.push('');
491
+
492
+ // Examples
493
+ lines.push(` ${c.bBright('Examples:')}`);
494
+ lines.push(` ${c.dim('$')} ${c.bright('uc')} ${c.dim('# Start with defaults')}`);
495
+ lines.push(` ${c.dim('$')} ${c.bright('uc --port 8080')} ${c.dim('# Start on port 8080')}`);
496
+ lines.push(` ${c.dim('$')} ${c.bright('uc connect --key upfyn_xxx')} ${c.dim('# Bridge to hosted server')}`);
497
+ lines.push(` ${c.dim('$')} ${c.bright('uc status')} ${c.dim('# Show configuration')}`);
498
+ lines.push('');
499
+
500
+ // Footer
501
+ lines.push(` ${c.dim('Documentation:')} ${c.cyan('https://cli.upfyn.com/docs')}`);
502
+ lines.push(` ${c.dim('Report Issues:')} ${c.cyan('https://github.com/thinqmesh/upfynai-code/issues')}`);
503
+ lines.push('');
504
+
505
+ process.stdout.write(lines.join('\n') + '\n');
506
+ }
507
+
508
+ /**
509
+ * Show styled status
510
+ */
511
+ function showStyledStatus(info) {
512
+ const lines = [];
513
+
514
+ lines.push('');
515
+ lines.push(` ${c.bBright('Upfyn-Code')} ${c.dim('— Status')}`);
516
+ lines.push(` ${c.dark('═'.repeat(50))}`);
517
+ lines.push('');
518
+
519
+ // Version
520
+ lines.push(` ${c.bright('Version')} ${c.white(info.version)}`);
521
+ lines.push('');
522
+
523
+ // Installation
524
+ lines.push(` ${c.bright('Install')} ${c.dim(info.installDir)}`);
525
+ lines.push(` ${c.bright('Database')} ${c.dim(info.dbPath)}`);
526
+ if (info.dbExists) {
527
+ lines.push(` ${c.green('✓')} ${c.gray(`Exists (${info.dbSize})`)}`);
528
+ } else {
529
+ lines.push(` ${c.yellow('○')} ${c.gray('Not created yet')}`);
530
+ }
531
+ lines.push('');
532
+
533
+ // Configuration
534
+ lines.push(` ${c.bright('Config')}`);
535
+ lines.push(` ${c.gray('PORT')} ${c.cyan(info.port)} ${info.portDefault ? c.dim('(default)') : ''}`);
536
+ lines.push(` ${c.gray('DATABASE')} ${c.dim(info.dbPath)}`);
537
+ lines.push(` ${c.gray('CLI')} ${c.dim(info.claudeCli || 'claude (default)')}`);
538
+ lines.push('');
539
+
540
+ // Bottom
541
+ lines.push(` ${c.dark('═'.repeat(50))}`);
542
+ lines.push('');
543
+ lines.push(` ${c.bCyan('Tips:')}`);
544
+ lines.push(` ${c.dim('>')} Use ${c.bright('uc --port 8080')} to run on a custom port`);
545
+ lines.push(` ${c.dim('>')} Use ${c.bright('uc connect --key upfyn_xxx')} to bridge to web UI`);
546
+ lines.push(` ${c.dim('>')} Access the UI at ${c.cyan(`http://localhost:${info.port}`)}`);
547
+ lines.push('');
548
+
549
+ process.stdout.write(lines.join('\n') + '\n');
550
+ }
551
+
552
+ /**
553
+ * Show server startup banner
554
+ */
555
+ function showServerBanner(port, version) {
556
+ const lines = [];
557
+
558
+ lines.push('');
559
+ for (const line of LOGO) lines.push(` ${line}`);
560
+ lines.push('');
561
+ lines.push(` ${c.dim('v' + version)} ${c.dim('—')} ${c.gray('by Thinqmesh Technologies')}`);
562
+ lines.push('');
563
+ lines.push(` ${c.dark('─'.repeat(52))}`);
564
+ lines.push('');
565
+ lines.push(` ${c.green('●')} ${c.bWhite('Server running')} ${c.gray('on')} ${c.bCyan(`http://localhost:${port}`)}`);
566
+ lines.push('');
567
+ lines.push(` ${c.dim('Press Ctrl+C to stop.')}`);
568
+ lines.push('');
569
+
570
+ process.stdout.write(lines.join('\n') + '\n');
571
+ }
572
+
573
+ /**
574
+ * Show connect startup with animation
575
+ */
576
+ async function showConnectStartup(serverUrl, machine, user, version) {
577
+ await playRocketAnimation();
578
+
579
+ clearScreen();
580
+
581
+ const lines = [];
582
+
583
+ lines.push('');
584
+ for (const line of LOGO) lines.push(` ${line}`);
585
+ lines.push('');
586
+ lines.push(` ${c.dim('v' + version)} ${c.dim('—')} ${c.gray('Relay Client')}`);
587
+ lines.push('');
588
+ lines.push(` ${c.dark('─'.repeat(52))}`);
589
+ lines.push('');
590
+ lines.push(` ${c.gray('Server:')} ${c.cyan(serverUrl)}`);
591
+ lines.push(` ${c.gray('Machine:')} ${c.dim(machine)}`);
592
+ lines.push(` ${c.gray('User:')} ${c.dim(user)}`);
593
+ lines.push('');
594
+
595
+ process.stdout.write(lines.join('\n') + '\n');
596
+ }
597
+
598
+ /**
599
+ * Log a relay event with icon
600
+ */
601
+ function logRelayEvent(icon, message, color = 'gray') {
602
+ const colorFn = c[color] || c.gray;
603
+ const timestamp = new Date().toLocaleTimeString('en-US', { hour12: false });
604
+ console.log(` ${c.dim(timestamp)} ${icon} ${colorFn(message)}`);
605
+ }
606
+
607
+ /**
608
+ * Strip ANSI codes for length calculation
609
+ */
610
+ function stripAnsi(str) {
611
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
612
+ }
613
+
614
+ export {
615
+ theme,
616
+ c,
617
+ LOGO,
618
+ playRocketAnimation,
619
+ showWelcomeScreen,
620
+ showConnectionBanner,
621
+ showStyledHelp,
622
+ showStyledStatus,
623
+ showServerBanner,
624
+ showConnectStartup,
625
+ logRelayEvent,
626
+ createSpinner,
627
+ hideCursor,
628
+ showCursor,
629
+ clearScreen,
630
+ sleep,
631
+ hLine,
632
+ box,
633
+ stripAnsi,
634
+ };