upfynai-code 2.1.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.
- package/client/dist/assets/index-BnXuHrpJ.js +523 -0
- package/client/dist/assets/index-BwxNox94.css +1 -0
- package/client/dist/index.html +66 -128
- package/client/dist/llms.txt +40 -0
- package/client/dist/manifest.json +15 -61
- package/client/dist/robots.txt +11 -0
- package/client/dist/sitemap.xml +45 -0
- package/client/dist/sw.js +55 -19
- package/package.json +1 -1
- package/server/cli-ui.js +634 -0
- package/server/cli.js +57 -156
- package/server/database/db.js +2 -2
- package/server/index.js +1 -1
- package/server/mcp-server.js +3 -3
- package/server/middleware/auth.js +12 -1
- package/server/relay-client.js +68 -44
- package/server/routes/auth.js +39 -13
package/server/cli-ui.js
ADDED
|
@@ -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
|
+
};
|