glad-web 1.0.46 → 2.0.2
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/README.md +4 -192
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/bin/glad.cjs +56 -0
- package/package.json +19 -61
- package/README.zh-CN.md +0 -198
- package/assets/logo.svg +0 -43
- package/bin/cli.js +0 -65
- package/lib/ai-tools/demo/enhanced-demo.js +0 -625
- package/lib/ai-tools/demo/index.js +0 -24
- package/lib/ai-tools/demo/responses.js +0 -88
- package/lib/ai-tools/detector.js +0 -76
- package/lib/ai-tools/registry.js +0 -300
- package/lib/claude/cli-usage.js +0 -95
- package/lib/claude/config.js +0 -82
- package/lib/claude/structured-session.js +0 -884
- package/lib/claude/transcript-repository.js +0 -216
- package/lib/codex/image-store.js +0 -174
- package/lib/codex/structured-session.js +0 -1590
- package/lib/commands/config.js +0 -78
- package/lib/commands/tools.js +0 -128
- package/lib/commands/web.js +0 -605
- package/lib/config/constants.js +0 -17
- package/lib/config/manager.js +0 -108
- package/lib/git/service.js +0 -83
- package/lib/notifications/message-formatter.js +0 -94
- package/lib/notifications/notification-service.js +0 -143
- package/lib/notifications/serverchan-client.js +0 -58
- package/lib/notifications/serverchan-settings-store.js +0 -115
- package/lib/schedule/job-runner.js +0 -162
- package/lib/schedule/job-store.js +0 -167
- package/lib/schedule/key-sequences.js +0 -49
- package/lib/schedule/scheduler-service.js +0 -39
- package/lib/server/routes/notifications.js +0 -52
- package/lib/server/routes/providers.js +0 -114
- package/lib/server/routes/schedules.js +0 -54
- package/lib/server/routes/skillhub.js +0 -104
- package/lib/server/routes/usage.js +0 -23
- package/lib/server/routes/workspace.js +0 -77
- package/lib/session/buffer.js +0 -102
- package/lib/session/file-attachment-store.js +0 -168
- package/lib/session/pty-manager.js +0 -255
- package/lib/session/rendered-history.js +0 -225
- package/lib/session/session-manager.js +0 -1032
- package/lib/session/text-history.js +0 -274
- package/lib/skillhub/client.js +0 -121
- package/lib/skillhub/settings-store.js +0 -168
- package/lib/skillhub/skill-installer.js +0 -320
- package/lib/usage/ccusage-runner.js +0 -128
- package/lib/usage/source-catalog.js +0 -26
- package/lib/usage/usage-service.js +0 -226
- package/lib/utils/logger.js +0 -74
- package/lib/utils/pid.js +0 -67
- package/lib/utils/validation.js +0 -53
- package/lib/web/bootstrap.js +0 -34
- package/lib/web/claude.js +0 -1150
- package/lib/web/codex.js +0 -1045
- package/lib/web/composer.js +0 -493
- package/lib/web/core.js +0 -385
- package/lib/web/git.js +0 -535
- package/lib/web/gitgraph.js +0 -293
- package/lib/web/index.html +0 -547
- package/lib/web/layout.js +0 -69
- package/lib/web/notifications.js +0 -164
- package/lib/web/schedules.js +0 -245
- package/lib/web/session.js +0 -361
- package/lib/web/shell.js +0 -74
- package/lib/web/skillhub.js +0 -197
- package/lib/web/styles.css +0 -932
- package/lib/web/terminal-scroll.js +0 -81
- package/lib/web/theme.js +0 -60
- package/lib/web/timed-inputs.js +0 -216
- package/lib/web/usage.js +0 -323
- package/lib/workspace/service.js +0 -77
- package/scripts/check-syntax.js +0 -26
|
@@ -1,625 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const { getRandomResponse } = require('./responses');
|
|
5
|
-
|
|
6
|
-
// Gradient colors
|
|
7
|
-
const COLOR_PRIMARY = '#7C3AED'; // violet-600
|
|
8
|
-
const COLOR_SECONDARY = '#EC4899'; // pink-500
|
|
9
|
-
|
|
10
|
-
// All logo frames from 7 different animation scenarios (6 lines each: 5-line box + 1-line prompt)
|
|
11
|
-
const COMPACT_LOGO_FRAMES = [
|
|
12
|
-
// Scenario 1: "Typing Code" - Eyes follow prompt (left, center, right)
|
|
13
|
-
['╭·····╮', ':○○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
14
|
-
['╭·····╮', ': ○ ○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
15
|
-
['╭·····╮', ': ○○:', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
16
|
-
|
|
17
|
-
// Scenario 2: "Emotional Journey" (happy, surprised, thinking, laughing)
|
|
18
|
-
['╭·····╮', ': ○ ○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
19
|
-
['╭·····╮', ': ◉ ◉ :', ': :', ': O :', '╰·····╯', ' > _'],
|
|
20
|
-
['╭·····╮', ':○ ○:', ': :', ': ═══ :', '╰·····╯', ' > _'],
|
|
21
|
-
['╭·····╮', ': ● ● :', ': :', ':╰───╯:', '╰·····╯', ' > _'],
|
|
22
|
-
|
|
23
|
-
// Scenario 3: "Sleepy → Waking Up" (yawn, drowsy, sleep, awake)
|
|
24
|
-
['╭·····╮', ': - - :', ': :', ': o :', '╰·····╯', ' > _'],
|
|
25
|
-
['╭·····╮', ': ˘ ˘ :', ': :', ': ═══ :', '╰·····╯', ' > _'],
|
|
26
|
-
['╭·····╮', ': ─ ─ :', ': :', ': ─── :', '╰·····╯', ' > _'],
|
|
27
|
-
['╭·····╮', ': ○ ○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
28
|
-
|
|
29
|
-
// Scenario 4: "Head Rotation" (front, turning, profile, return)
|
|
30
|
-
['╭·····╮', ': ○ ○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
31
|
-
['╭·····╮', ': ○○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
32
|
-
['╭·····╮', ':○) :', ':│ :', ':╰ :', '╰·····╯', ' > _'],
|
|
33
|
-
['╭·····╮', ': ○○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
34
|
-
|
|
35
|
-
// Scenario 5: "Terminal Glitch" (normal, glitch, heavy glitch, restore)
|
|
36
|
-
['╭·····╮', ': ○ ○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
37
|
-
['╭·╳·╳·╮', ':○ ◒ ○:', ': ░▒▓ :', ':╰─░─╯:', '╰·░·▒·╯', ' > _'],
|
|
38
|
-
['╭░▒▓█▓╮', ':◐ ◑ ◒:', ':░▒▓█▒:', ':═╳═╳═:', '╰▓█▒░▓╯', ' > _'],
|
|
39
|
-
['╭·····╮', ': ○ ○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
40
|
-
|
|
41
|
-
// Scenario 6: "Talking Face" (closed, small O, open wide, smile)
|
|
42
|
-
['╭·····╮', ': ○ ○ :', ': :', ': ─── :', '╰·····╯', ' > _'],
|
|
43
|
-
['╭·····╮', ': ○ ○ :', ': :', ': o :', '╰·····╯', ' > _'],
|
|
44
|
-
['╭·····╮', ': ○ ○ :', ': :', ': ═══ :', '╰·····╯', ' > _'],
|
|
45
|
-
['╭·····╮', ': ○ ○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
46
|
-
|
|
47
|
-
// Scenario 7: "Blink + Wink" (open, blink, wink right, wink both)
|
|
48
|
-
['╭·····╮', ': ○ ○ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
49
|
-
['╭·····╮', ': ─ ─ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
50
|
-
['╭·····╮', ': ○ ◠ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _'],
|
|
51
|
-
['╭·····╮', ': ◠ ◠ :', ': :', ': ╰─╯ :', '╰·····╯', ' > _']
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
// Screen layout constants
|
|
55
|
-
const HEADER_HEIGHT = 6;
|
|
56
|
-
const CHAT_HEIGHT = 10;
|
|
57
|
-
const FOOTER_HEIGHT = 2;
|
|
58
|
-
|
|
59
|
-
// Helper function to get visible length of string (excluding ANSI codes)
|
|
60
|
-
function getVisibleLength(str) {
|
|
61
|
-
// Remove ANSI escape codes
|
|
62
|
-
return str.replace(/\x1B\[[0-9;]*m/g, '').length;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Helper function to wrap text to terminal width
|
|
66
|
-
function wrapText(text, maxWidth) {
|
|
67
|
-
const visibleLength = getVisibleLength(text);
|
|
68
|
-
|
|
69
|
-
// If text fits in one line, return as-is
|
|
70
|
-
if (visibleLength <= maxWidth) {
|
|
71
|
-
return [text];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// For colored text, we need to preserve ANSI codes
|
|
75
|
-
// Simple approach: try to find a good break point
|
|
76
|
-
const lines = [];
|
|
77
|
-
let currentLine = '';
|
|
78
|
-
let currentVisibleLength = 0;
|
|
79
|
-
|
|
80
|
-
// Extract ANSI codes and text parts
|
|
81
|
-
const parts = text.split(/(\x1B\[[0-9;]*m)/);
|
|
82
|
-
|
|
83
|
-
for (const part of parts) {
|
|
84
|
-
// If it's an ANSI code, add it without counting length
|
|
85
|
-
if (part.match(/\x1B\[[0-9;]*m/)) {
|
|
86
|
-
currentLine += part;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Process text part word by word
|
|
91
|
-
const words = part.split(' ');
|
|
92
|
-
for (let i = 0; i < words.length; i++) {
|
|
93
|
-
const word = words[i];
|
|
94
|
-
const wordLength = word.length;
|
|
95
|
-
const spaceLength = (i > 0) ? 1 : 0; // Add space before word (except first)
|
|
96
|
-
|
|
97
|
-
if (currentVisibleLength + spaceLength + wordLength > maxWidth) {
|
|
98
|
-
// Word doesn't fit, start new line
|
|
99
|
-
if (currentLine) {
|
|
100
|
-
lines.push(currentLine);
|
|
101
|
-
currentLine = word;
|
|
102
|
-
currentVisibleLength = wordLength;
|
|
103
|
-
} else {
|
|
104
|
-
// Word is too long for one line, break it
|
|
105
|
-
lines.push(word.substring(0, maxWidth));
|
|
106
|
-
currentLine = word.substring(maxWidth);
|
|
107
|
-
currentVisibleLength = word.length - maxWidth;
|
|
108
|
-
}
|
|
109
|
-
} else {
|
|
110
|
-
// Word fits
|
|
111
|
-
if (i > 0 && currentLine) {
|
|
112
|
-
currentLine += ' ';
|
|
113
|
-
currentVisibleLength += 1;
|
|
114
|
-
}
|
|
115
|
-
currentLine += word;
|
|
116
|
-
currentVisibleLength += wordLength;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (currentLine) {
|
|
122
|
-
lines.push(currentLine);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return lines.length > 0 ? lines : [text];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Chat buffer (circular buffer for 10 lines)
|
|
129
|
-
class ChatBuffer {
|
|
130
|
-
constructor(maxLines = 10) {
|
|
131
|
-
this.maxLines = maxLines;
|
|
132
|
-
this.lines = [];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
addLine(text) {
|
|
136
|
-
// Filter out system messages (but keep __INPUT__ placeholder)
|
|
137
|
-
if (text === '__INPUT__') {
|
|
138
|
-
// __INPUT__ is our internal placeholder, allow it
|
|
139
|
-
this.lines.push(text);
|
|
140
|
-
if (this.lines.length > this.maxLines) {
|
|
141
|
-
this.lines.shift();
|
|
142
|
-
}
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const lowerText = text.toLowerCase();
|
|
147
|
-
const isSystemMessage = text.startsWith('__') ||
|
|
148
|
-
lowerText.includes('mobile') &&
|
|
149
|
-
(lowerText.includes('connected') ||
|
|
150
|
-
lowerText.includes('disconnected')) ||
|
|
151
|
-
text.includes('📱');
|
|
152
|
-
|
|
153
|
-
// Don't add system messages to buffer
|
|
154
|
-
if (isSystemMessage) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
this.lines.push(text);
|
|
159
|
-
if (this.lines.length > this.maxLines) {
|
|
160
|
-
this.lines.shift();
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
getLines() {
|
|
165
|
-
// Pad with empty lines if less than maxLines
|
|
166
|
-
const padded = [...this.lines];
|
|
167
|
-
while (padded.length < this.maxLines) {
|
|
168
|
-
padded.push('');
|
|
169
|
-
}
|
|
170
|
-
return padded;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
clear() {
|
|
174
|
-
this.lines = [];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Clean existing lines from system messages
|
|
178
|
-
cleanSystemMessages() {
|
|
179
|
-
this.lines = this.lines.filter(line => {
|
|
180
|
-
if (line === '__INPUT__') return true; // Keep our placeholder
|
|
181
|
-
|
|
182
|
-
const lowerText = line.toLowerCase();
|
|
183
|
-
const isSystemMessage = line.startsWith('__') ||
|
|
184
|
-
lowerText.includes('mobile') &&
|
|
185
|
-
(lowerText.includes('connected') ||
|
|
186
|
-
lowerText.includes('disconnected')) ||
|
|
187
|
-
line.includes('📱');
|
|
188
|
-
|
|
189
|
-
return !isSystemMessage; // Keep only non-system messages
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Animation state
|
|
195
|
-
class AnimationState {
|
|
196
|
-
constructor() {
|
|
197
|
-
this.logoFrame = 0;
|
|
198
|
-
this.terminalWidth = process.stdout.columns || 80;
|
|
199
|
-
|
|
200
|
-
this.line1Text = '✨→→→ Demo Mode Active →→→✨';
|
|
201
|
-
this.line2Text = '✨←←← Demo Mode Active ←←←✨';
|
|
202
|
-
|
|
203
|
-
this.line1Pos = -this.line1Text.length; // Line 1 starts off-screen left
|
|
204
|
-
this.line2Pos = this.terminalWidth; // Line 2 starts off-screen right
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
updateLogo() {
|
|
208
|
-
// Random selection from all available frames
|
|
209
|
-
this.logoFrame = Math.floor(Math.random() * COMPACT_LOGO_FRAMES.length);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
updateBanners() {
|
|
213
|
-
// Line 1 moves left to right
|
|
214
|
-
this.line1Pos += 1;
|
|
215
|
-
if (this.line1Pos > this.terminalWidth) {
|
|
216
|
-
this.line1Pos = -this.line1Text.length;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Line 2 moves right to left
|
|
220
|
-
this.line2Pos -= 1;
|
|
221
|
-
if (this.line2Pos < -this.line2Text.length) {
|
|
222
|
-
this.line2Pos = this.terminalWidth;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Screen renderer
|
|
228
|
-
class ScreenRenderer {
|
|
229
|
-
constructor() {
|
|
230
|
-
this.chatBuffer = new ChatBuffer(CHAT_HEIGHT);
|
|
231
|
-
this.animState = new AnimationState();
|
|
232
|
-
this.currentInput = '';
|
|
233
|
-
this.cursorVisible = true;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
hideCursor() {
|
|
237
|
-
process.stdout.write('\x1B[?25l'); // Hide cursor
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
showCursor() {
|
|
241
|
-
process.stdout.write('\x1B[?25h'); // Show cursor
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
moveCursor(row, col) {
|
|
245
|
-
process.stdout.write(`\x1B[${row};${col}H`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
clearScreen() {
|
|
249
|
-
process.stdout.write('\x1B[2J\x1B[H');
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
renderHeader() {
|
|
253
|
-
const logo = COMPACT_LOGO_FRAMES[this.animState.logoFrame];
|
|
254
|
-
const description = [
|
|
255
|
-
'Glad Demo Mode',
|
|
256
|
-
'Demonstration version with',
|
|
257
|
-
'randomized responses.',
|
|
258
|
-
'Feel free to type anything',
|
|
259
|
-
'and explore the experience!',
|
|
260
|
-
' Commands: /help, /exit'
|
|
261
|
-
];
|
|
262
|
-
|
|
263
|
-
// Render logo and description side by side
|
|
264
|
-
for (let i = 0; i < HEADER_HEIGHT; i++) {
|
|
265
|
-
this.moveCursor(i + 1, 1);
|
|
266
|
-
process.stdout.write('\x1B[K'); // Clear line
|
|
267
|
-
|
|
268
|
-
const logoLine = logo[i] || '';
|
|
269
|
-
const descLine = description[i] || '';
|
|
270
|
-
|
|
271
|
-
// Logo in primary color, description in secondary color
|
|
272
|
-
process.stdout.write(chalk.hex(COLOR_PRIMARY)(logoLine));
|
|
273
|
-
if (descLine) {
|
|
274
|
-
process.stdout.write(' ' + chalk.hex(COLOR_SECONDARY)(descLine));
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
renderSeparator(row) {
|
|
280
|
-
this.moveCursor(row, 1);
|
|
281
|
-
process.stdout.write(chalk.dim('─'.repeat(process.stdout.columns || 80)));
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
renderChat() {
|
|
285
|
-
const startRow = HEADER_HEIGHT + 2; // +1 for separator
|
|
286
|
-
|
|
287
|
-
// Clean system messages before rendering
|
|
288
|
-
this.chatBuffer.cleanSystemMessages();
|
|
289
|
-
|
|
290
|
-
const lines = this.chatBuffer.getLines();
|
|
291
|
-
|
|
292
|
-
for (let i = 0; i < lines.length; i++) {
|
|
293
|
-
this.moveCursor(startRow + i, 1);
|
|
294
|
-
|
|
295
|
-
// Clear line first
|
|
296
|
-
process.stdout.write('\x1B[K');
|
|
297
|
-
|
|
298
|
-
const line = lines[i];
|
|
299
|
-
|
|
300
|
-
// Check if this is the input placeholder
|
|
301
|
-
if (line === '__INPUT__') {
|
|
302
|
-
// Render current input with '>' prefix
|
|
303
|
-
process.stdout.write(chalk.cyan('> ') + chalk.cyan(this.currentInput));
|
|
304
|
-
|
|
305
|
-
// Add reverse video cursor
|
|
306
|
-
if (this.cursorVisible) {
|
|
307
|
-
process.stdout.write('\x1B[7m \x1B[27m'); // Reverse video space
|
|
308
|
-
}
|
|
309
|
-
} else if (line) {
|
|
310
|
-
// Normal line
|
|
311
|
-
process.stdout.write(line);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
renderFooter() {
|
|
317
|
-
const footerRow = HEADER_HEIGHT + CHAT_HEIGHT + 3; // +2 for separators, +1 for 0-index
|
|
318
|
-
const termWidth = this.animState.terminalWidth;
|
|
319
|
-
|
|
320
|
-
const { line1Pos, line2Pos, line1Text, line2Text } = this.animState;
|
|
321
|
-
|
|
322
|
-
// Clear line 1 completely - use \x1B[0K to clear to end of line
|
|
323
|
-
this.moveCursor(footerRow, 1);
|
|
324
|
-
process.stdout.write('\x1B[2K\x1B[0K'); // Clear entire line + clear to end
|
|
325
|
-
|
|
326
|
-
// Render line 1 (moves left to right)
|
|
327
|
-
if (line1Pos >= 0 && line1Pos < termWidth) {
|
|
328
|
-
this.moveCursor(footerRow, line1Pos + 1);
|
|
329
|
-
const visibleText = line1Text.substring(0, Math.max(0, termWidth - line1Pos));
|
|
330
|
-
if (visibleText) {
|
|
331
|
-
process.stdout.write(chalk.hex(COLOR_PRIMARY)(visibleText));
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
// Clear to end of line after rendering
|
|
335
|
-
process.stdout.write('\x1B[0K');
|
|
336
|
-
|
|
337
|
-
// Clear line 2 completely - use \x1B[0K to clear to end of line
|
|
338
|
-
this.moveCursor(footerRow + 1, 1);
|
|
339
|
-
process.stdout.write('\x1B[2K\x1B[0K'); // Clear entire line + clear to end
|
|
340
|
-
|
|
341
|
-
// Render line 2 (moves right to left)
|
|
342
|
-
if (line2Pos >= 0 && line2Pos < termWidth) {
|
|
343
|
-
this.moveCursor(footerRow + 1, line2Pos + 1);
|
|
344
|
-
const visibleText = line2Text.substring(0, Math.max(0, termWidth - line2Pos));
|
|
345
|
-
if (visibleText) {
|
|
346
|
-
process.stdout.write(chalk.hex(COLOR_SECONDARY)(visibleText));
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
// Clear to end of line after rendering
|
|
350
|
-
process.stdout.write('\x1B[0K');
|
|
351
|
-
|
|
352
|
-
// Clear any lines below footer (rows 23+) to prevent artifacts
|
|
353
|
-
for (let i = footerRow + 2; i <= footerRow + 5; i++) {
|
|
354
|
-
this.moveCursor(i, 1);
|
|
355
|
-
process.stdout.write('\x1B[2K\x1B[0K');
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
render() {
|
|
360
|
-
this.renderHeader();
|
|
361
|
-
this.renderSeparator(HEADER_HEIGHT + 1);
|
|
362
|
-
this.renderChat();
|
|
363
|
-
this.renderSeparator(HEADER_HEIGHT + CHAT_HEIGHT + 2);
|
|
364
|
-
this.renderFooter();
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
addUserInput(text) {
|
|
368
|
-
// Calculate available width (terminal width minus prefix "> ")
|
|
369
|
-
const termWidth = this.animState.terminalWidth;
|
|
370
|
-
const prefixLength = 2; // "> " = 2 chars
|
|
371
|
-
const maxWidth = Math.max(20, termWidth - prefixLength); // Minimum 20 chars
|
|
372
|
-
|
|
373
|
-
// Wrap text if needed
|
|
374
|
-
const wrappedLines = wrapText(text, maxWidth);
|
|
375
|
-
|
|
376
|
-
// Add each wrapped line with prefix (only first line gets >, others get continuation indent)
|
|
377
|
-
wrappedLines.forEach((line, index) => {
|
|
378
|
-
if (index === 0) {
|
|
379
|
-
// First line gets the "> " prefix
|
|
380
|
-
this.chatBuffer.addLine(chalk.cyan('> ') + chalk.cyan(line));
|
|
381
|
-
} else {
|
|
382
|
-
// Continuation lines get two spaces for alignment
|
|
383
|
-
this.chatBuffer.addLine(chalk.cyan(' ') + chalk.cyan(line));
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
this.currentInput = '';
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
addAIResponse(text) {
|
|
391
|
-
// Calculate available width (terminal width minus prefix "● ")
|
|
392
|
-
const termWidth = this.animState.terminalWidth;
|
|
393
|
-
const prefixLength = 2; // "● " = 2 chars
|
|
394
|
-
const maxWidth = Math.max(20, termWidth - prefixLength); // Minimum 20 chars
|
|
395
|
-
|
|
396
|
-
// Wrap text if needed
|
|
397
|
-
const wrappedLines = wrapText(text, maxWidth);
|
|
398
|
-
|
|
399
|
-
// Add each wrapped line with prefix (only first line gets ●, others get continuation indent)
|
|
400
|
-
wrappedLines.forEach((line, index) => {
|
|
401
|
-
if (index === 0) {
|
|
402
|
-
// First line gets the "● " prefix
|
|
403
|
-
this.chatBuffer.addLine(chalk.hex(COLOR_SECONDARY)('● ') + chalk.hex(COLOR_SECONDARY)(line));
|
|
404
|
-
} else {
|
|
405
|
-
// Continuation lines get two spaces for alignment
|
|
406
|
-
this.chatBuffer.addLine(chalk.hex(COLOR_SECONDARY)(' ') + chalk.hex(COLOR_SECONDARY)(line));
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Main demo controller
|
|
413
|
-
class DemoController {
|
|
414
|
-
constructor() {
|
|
415
|
-
this.renderer = new ScreenRenderer();
|
|
416
|
-
this.logoInterval = null;
|
|
417
|
-
this.bannerInterval = null;
|
|
418
|
-
this.cursorInterval = null;
|
|
419
|
-
this.running = false;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
start() {
|
|
423
|
-
this.running = true;
|
|
424
|
-
|
|
425
|
-
// Hide real cursor
|
|
426
|
-
this.renderer.hideCursor();
|
|
427
|
-
|
|
428
|
-
// Clear screen initially
|
|
429
|
-
this.renderer.clearScreen();
|
|
430
|
-
|
|
431
|
-
// Add welcome message
|
|
432
|
-
this.renderer.chatBuffer.addLine(' ' + chalk.hex(COLOR_PRIMARY)('Welcome to Glad Demo Mode!'));
|
|
433
|
-
this.renderer.chatBuffer.addLine(' ' + chalk.dim('Type anything or use /help, /exit'));
|
|
434
|
-
this.renderer.chatBuffer.addLine('');
|
|
435
|
-
this.renderer.chatBuffer.addLine('__INPUT__'); // Placeholder for current input
|
|
436
|
-
|
|
437
|
-
// Start animations
|
|
438
|
-
this.logoInterval = setInterval(() => {
|
|
439
|
-
this.renderer.animState.updateLogo();
|
|
440
|
-
this.renderer.render();
|
|
441
|
-
}, 200); // Slow rotation
|
|
442
|
-
|
|
443
|
-
this.bannerInterval = setInterval(() => {
|
|
444
|
-
this.renderer.animState.updateBanners();
|
|
445
|
-
this.renderer.render();
|
|
446
|
-
}, 80); // Fast movement
|
|
447
|
-
|
|
448
|
-
// Cursor blink
|
|
449
|
-
this.cursorInterval = setInterval(() => {
|
|
450
|
-
this.renderer.cursorVisible = !this.renderer.cursorVisible;
|
|
451
|
-
this.renderer.renderChat();
|
|
452
|
-
}, 500);
|
|
453
|
-
|
|
454
|
-
// Initial render
|
|
455
|
-
this.renderer.render();
|
|
456
|
-
|
|
457
|
-
// Setup input handling
|
|
458
|
-
this.setupInput();
|
|
459
|
-
|
|
460
|
-
// Setup terminal resize handling
|
|
461
|
-
this.setupResize();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
stop() {
|
|
465
|
-
this.running = false;
|
|
466
|
-
|
|
467
|
-
if (this.logoInterval) clearInterval(this.logoInterval);
|
|
468
|
-
if (this.bannerInterval) clearInterval(this.bannerInterval);
|
|
469
|
-
if (this.cursorInterval) clearInterval(this.cursorInterval);
|
|
470
|
-
|
|
471
|
-
// Remove resize listener
|
|
472
|
-
if (this.resizeHandler) {
|
|
473
|
-
process.stdout.removeListener('resize', this.resizeHandler);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
this.renderer.showCursor();
|
|
477
|
-
this.renderer.clearScreen();
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
setupResize() {
|
|
481
|
-
// Handle terminal resize
|
|
482
|
-
this.resizeHandler = () => {
|
|
483
|
-
const oldWidth = this.renderer.animState.terminalWidth;
|
|
484
|
-
const newWidth = process.stdout.columns || 80;
|
|
485
|
-
|
|
486
|
-
// Clear footer lines + 2 extra lines below to remove any artifacts
|
|
487
|
-
const footerRow = HEADER_HEIGHT + CHAT_HEIGHT + 3;
|
|
488
|
-
for (let i = 0; i < FOOTER_HEIGHT + 2; i++) {
|
|
489
|
-
this.renderer.moveCursor(footerRow + i, 1);
|
|
490
|
-
process.stdout.write('\x1B[2K\x1B[0K'); // Clear entire line + clear to end
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Update terminal width
|
|
494
|
-
this.renderer.animState.terminalWidth = newWidth;
|
|
495
|
-
|
|
496
|
-
// Clear and redraw screen
|
|
497
|
-
this.renderer.clearScreen();
|
|
498
|
-
this.renderer.render();
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
process.stdout.on('resize', this.resizeHandler);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
setupInput() {
|
|
505
|
-
process.stdin.setEncoding('utf8');
|
|
506
|
-
if (process.stdin.isTTY) {
|
|
507
|
-
process.stdin.setRawMode(true);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
let buffer = '';
|
|
511
|
-
|
|
512
|
-
process.stdin.on('data', (chunk) => {
|
|
513
|
-
for (const char of chunk) {
|
|
514
|
-
const code = char.charCodeAt(0);
|
|
515
|
-
|
|
516
|
-
// Ctrl+C
|
|
517
|
-
if (code === 3) {
|
|
518
|
-
this.stop();
|
|
519
|
-
console.log(chalk.yellow('\n\nExiting demo mode. Thanks for exploring Glad!\n'));
|
|
520
|
-
process.exit(0);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Enter
|
|
524
|
-
if (code === 13 || code === 10) {
|
|
525
|
-
const trimmed = buffer.trim();
|
|
526
|
-
|
|
527
|
-
// Check if system message
|
|
528
|
-
const isSystemMessage = trimmed.startsWith('__') ||
|
|
529
|
-
trimmed.toLowerCase().includes('mobile') &&
|
|
530
|
-
(trimmed.toLowerCase().includes('connected') ||
|
|
531
|
-
trimmed.toLowerCase().includes('disconnected')) ||
|
|
532
|
-
trimmed.includes('📱');
|
|
533
|
-
|
|
534
|
-
if (isSystemMessage) {
|
|
535
|
-
// System message detected - clear currentInput and redraw screen
|
|
536
|
-
this.renderer.currentInput = ''; // Clear the input that was being typed
|
|
537
|
-
buffer = ''; // Clear buffer
|
|
538
|
-
this.renderer.clearScreen();
|
|
539
|
-
this.renderer.render();
|
|
540
|
-
continue;
|
|
541
|
-
} else if (trimmed) {
|
|
542
|
-
this.handleInput(trimmed);
|
|
543
|
-
}
|
|
544
|
-
buffer = '';
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Backspace
|
|
549
|
-
if (code === 127 || code === 8) {
|
|
550
|
-
// Remove last character (handle UTF-8 multi-byte)
|
|
551
|
-
if (buffer.length > 0) {
|
|
552
|
-
buffer = buffer.slice(0, -1);
|
|
553
|
-
this.updateCurrentInput(buffer);
|
|
554
|
-
}
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// Allow all printable characters including UTF-8 (Cyrillic, etc.)
|
|
559
|
-
// Accept: ASCII printable (32-126) + all UTF-8 characters (>= 128)
|
|
560
|
-
if (code >= 32) {
|
|
561
|
-
buffer += char;
|
|
562
|
-
this.updateCurrentInput(buffer);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
process.on('SIGINT', () => {
|
|
568
|
-
this.stop();
|
|
569
|
-
console.log(chalk.yellow('\n\nExiting demo mode. Thanks for exploring Glad!\n'));
|
|
570
|
-
process.exit(0);
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
updateCurrentInput(text) {
|
|
575
|
-
// Update current input in renderer
|
|
576
|
-
this.renderer.currentInput = text;
|
|
577
|
-
// No need to call full render, cursor blink and animation will update
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
handleInput(input) {
|
|
581
|
-
// Add user input to buffer (replace __INPUT__ placeholder)
|
|
582
|
-
// Note: System messages are already filtered in setupInput()
|
|
583
|
-
const lines = this.renderer.chatBuffer.lines;
|
|
584
|
-
const inputIdx = lines.findIndex(l => l === '__INPUT__');
|
|
585
|
-
if (inputIdx >= 0) {
|
|
586
|
-
lines[inputIdx] = chalk.cyan('> ') + chalk.cyan(input);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Reset current input
|
|
590
|
-
this.renderer.currentInput = '';
|
|
591
|
-
|
|
592
|
-
// Handle special commands
|
|
593
|
-
if (input === '/exit') {
|
|
594
|
-
this.stop();
|
|
595
|
-
console.log(chalk.yellow('\n\nExiting demo mode. Thanks for exploring Glad!\n'));
|
|
596
|
-
process.exit(0);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if (input === '/help') {
|
|
600
|
-
this.renderer.addAIResponse('Commands: /help, /exit');
|
|
601
|
-
this.renderer.chatBuffer.addLine('__INPUT__'); // New input placeholder
|
|
602
|
-
this.renderer.render();
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// Get random AI response
|
|
607
|
-
const response = getRandomResponse();
|
|
608
|
-
this.renderer.addAIResponse(response);
|
|
609
|
-
|
|
610
|
-
// Add new input placeholder
|
|
611
|
-
this.renderer.chatBuffer.addLine('__INPUT__');
|
|
612
|
-
|
|
613
|
-
this.renderer.render();
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Run if executed directly
|
|
618
|
-
if (require.main === module) {
|
|
619
|
-
const demo = new DemoController();
|
|
620
|
-
demo.start();
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
module.exports = {
|
|
624
|
-
DemoController
|
|
625
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { DemoController } = require('./enhanced-demo');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Main demo loop - now uses enhanced demo controller
|
|
7
|
-
*/
|
|
8
|
-
async function runDemo() {
|
|
9
|
-
const demo = new DemoController();
|
|
10
|
-
demo.start();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Run if executed directly
|
|
14
|
-
if (require.main === module) {
|
|
15
|
-
runDemo().catch((error) => {
|
|
16
|
-
console.error(chalk.red('Demo error:'), error);
|
|
17
|
-
process.exit(1);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
module.exports = {
|
|
22
|
-
runDemo,
|
|
23
|
-
DemoController
|
|
24
|
-
};
|