glad-web 1.0.45 → 2.0.1

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.
Files changed (68) hide show
  1. package/README.md +4 -192
  2. package/THIRD_PARTY_NOTICES.md +27 -0
  3. package/bin/glad.cjs +56 -0
  4. package/package.json +19 -58
  5. package/README.zh-CN.md +0 -198
  6. package/assets/logo.svg +0 -43
  7. package/bin/cli.js +0 -65
  8. package/lib/ai-tools/demo/enhanced-demo.js +0 -625
  9. package/lib/ai-tools/demo/index.js +0 -24
  10. package/lib/ai-tools/demo/responses.js +0 -88
  11. package/lib/ai-tools/detector.js +0 -76
  12. package/lib/ai-tools/registry.js +0 -300
  13. package/lib/claude/cli-usage.js +0 -95
  14. package/lib/claude/config.js +0 -82
  15. package/lib/claude/structured-session.js +0 -884
  16. package/lib/claude/transcript-repository.js +0 -216
  17. package/lib/codex/image-store.js +0 -174
  18. package/lib/codex/structured-session.js +0 -1578
  19. package/lib/commands/config.js +0 -78
  20. package/lib/commands/tools.js +0 -128
  21. package/lib/commands/web.js +0 -586
  22. package/lib/config/constants.js +0 -17
  23. package/lib/config/manager.js +0 -89
  24. package/lib/git/service.js +0 -83
  25. package/lib/notifications/message-formatter.js +0 -94
  26. package/lib/notifications/notification-service.js +0 -143
  27. package/lib/notifications/serverchan-client.js +0 -58
  28. package/lib/notifications/serverchan-settings-store.js +0 -115
  29. package/lib/schedule/job-runner.js +0 -162
  30. package/lib/schedule/job-store.js +0 -167
  31. package/lib/schedule/key-sequences.js +0 -49
  32. package/lib/schedule/scheduler-service.js +0 -39
  33. package/lib/server/routes/notifications.js +0 -52
  34. package/lib/server/routes/providers.js +0 -114
  35. package/lib/server/routes/schedules.js +0 -54
  36. package/lib/server/routes/usage.js +0 -23
  37. package/lib/server/routes/workspace.js +0 -77
  38. package/lib/session/buffer.js +0 -102
  39. package/lib/session/file-attachment-store.js +0 -168
  40. package/lib/session/pty-manager.js +0 -255
  41. package/lib/session/rendered-history.js +0 -225
  42. package/lib/session/session-manager.js +0 -1001
  43. package/lib/session/text-history.js +0 -274
  44. package/lib/usage/ccusage-runner.js +0 -128
  45. package/lib/usage/source-catalog.js +0 -26
  46. package/lib/usage/usage-service.js +0 -226
  47. package/lib/utils/logger.js +0 -74
  48. package/lib/utils/pid.js +0 -67
  49. package/lib/utils/validation.js +0 -53
  50. package/lib/web/claude.js +0 -1129
  51. package/lib/web/codex.js +0 -1042
  52. package/lib/web/composer.js +0 -463
  53. package/lib/web/core.js +0 -373
  54. package/lib/web/git.js +0 -535
  55. package/lib/web/gitgraph.js +0 -293
  56. package/lib/web/index.html +0 -516
  57. package/lib/web/layout.js +0 -72
  58. package/lib/web/notifications.js +0 -163
  59. package/lib/web/schedules.js +0 -245
  60. package/lib/web/session.js +0 -360
  61. package/lib/web/shell.js +0 -59
  62. package/lib/web/styles.css +0 -905
  63. package/lib/web/terminal-scroll.js +0 -81
  64. package/lib/web/theme.js +0 -60
  65. package/lib/web/timed-inputs.js +0 -216
  66. package/lib/web/usage.js +0 -323
  67. package/lib/workspace/service.js +0 -77
  68. 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
- };