gbos 1.1.3 → 1.1.5

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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbos",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "GBOS - Command line interface for GBOS services",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,283 +1,357 @@
1
- const { execSync, spawnSync } = require('child_process');
2
1
  const path = require('path');
3
2
  const fs = require('fs');
4
3
  const { PNG } = require('pngjs');
5
4
 
6
- // ANSI color codes - Dark Purple Theme
7
- const colors = {
8
- reset: '\x1b[0m',
9
- bold: '\x1b[1m',
10
- dim: '\x1b[2m',
11
- // Dark purple shades (256 color mode)
12
- purple1: '\x1b[38;5;54m', // Darkest purple
13
- purple2: '\x1b[38;5;55m', // Dark purple
14
- purple3: '\x1b[38;5;56m', // Medium dark purple
15
- purple4: '\x1b[38;5;93m', // Medium purple
16
- purple5: '\x1b[38;5;99m', // Light purple
17
- purple6: '\x1b[38;5;141m', // Lighter purple
18
- purple7: '\x1b[38;5;183m', // Lightest purple
19
- // Fallback standard colors
20
- magenta: '\x1b[35m',
21
- white: '\x1b[37m',
22
- gray: '\x1b[90m',
23
- green: '\x1b[32m',
24
- yellow: '\x1b[33m',
5
+ // ANSI escape codes
6
+ const ESC = '\x1b';
7
+ const RESET = `${ESC}[0m`;
8
+ const BOLD = `${ESC}[1m`;
9
+ const DIM = `${ESC}[2m`;
10
+
11
+ // True color ANSI helpers
12
+ const fg = (r, g, b) => `${ESC}[38;2;${r};${g};${b}m`;
13
+ const bg = (r, g, b) => `${ESC}[48;2;${r};${g};${b}m`;
14
+
15
+ // Purple theme RGB values
16
+ const PURPLE = {
17
+ dark: [75, 0, 130], // Deep purple
18
+ medium: [128, 0, 128], // Purple
19
+ light: [147, 112, 219], // Medium purple
20
+ bright: [186, 85, 211], // Medium orchid
21
+ pale: [216, 191, 216], // Thistle
25
22
  };
26
23
 
27
- // ASCII characters from dark to light
28
- const ASCII_CHARS = ' .:-=+*#%@';
29
-
30
- // Convert image to ASCII art
31
- function imageToAscii(imagePath, width = 30) {
32
- try {
33
- const data = fs.readFileSync(imagePath);
34
- const png = PNG.sync.read(data);
24
+ // 256-color fallback codes
25
+ const colors = {
26
+ reset: RESET,
27
+ bold: BOLD,
28
+ dim: DIM,
29
+ purple4: `${ESC}[38;5;93m`,
30
+ purple5: `${ESC}[38;5;99m`,
31
+ purple6: `${ESC}[38;5;141m`,
32
+ purple7: `${ESC}[38;5;183m`,
33
+ white: `${ESC}[37m`,
34
+ };
35
35
 
36
- const aspectRatio = 0.5; // Terminal characters are taller than wide
37
- const height = Math.floor((png.height / png.width) * width * aspectRatio);
36
+ // Unicode half-block characters
37
+ const UPPER_HALF = '▀';
38
+ const LOWER_HALF = '▄';
39
+ const FULL_BLOCK = '█';
38
40
 
39
- const cellWidth = png.width / width;
40
- const cellHeight = png.height / height;
41
+ // Get terminal width
42
+ function getTerminalWidth() {
43
+ return process.stdout.columns || 80;
44
+ }
41
45
 
42
- let ascii = '';
46
+ // Sample pixel from PNG at given coordinates
47
+ function samplePixel(png, x, y) {
48
+ const clampedX = Math.max(0, Math.min(Math.floor(x), png.width - 1));
49
+ const clampedY = Math.max(0, Math.min(Math.floor(y), png.height - 1));
50
+ const idx = (png.width * clampedY + clampedX) << 2;
51
+ return {
52
+ r: png.data[idx],
53
+ g: png.data[idx + 1],
54
+ b: png.data[idx + 2],
55
+ a: png.data[idx + 3],
56
+ };
57
+ }
43
58
 
44
- for (let y = 0; y < height; y++) {
45
- let row = '';
46
- for (let x = 0; x < width; x++) {
47
- // Sample the center of each cell
48
- const sampleX = Math.floor(x * cellWidth + cellWidth / 2);
49
- const sampleY = Math.floor(y * cellHeight + cellHeight / 2);
50
- const idx = (png.width * sampleY + sampleX) << 2;
59
+ // Check if pixel is transparent or white (background)
60
+ function isBackground(pixel) {
61
+ return pixel.a < 50 || (pixel.r > 240 && pixel.g > 240 && pixel.b > 240);
62
+ }
51
63
 
52
- const r = png.data[idx];
53
- const g = png.data[idx + 1];
54
- const b = png.data[idx + 2];
55
- const a = png.data[idx + 3];
64
+ // Convert PNG to true-color pixel art using half-blocks
65
+ // Each character cell represents 2 vertical pixels
66
+ function imageToPixels(imagePath, targetWidth = 24, targetHeight = 12) {
67
+ try {
68
+ const data = fs.readFileSync(imagePath);
69
+ const png = PNG.sync.read(data);
56
70
 
57
- // If transparent, use space
58
- if (a < 128) {
59
- row += ' ';
60
- continue;
71
+ // Each row of output = 2 rows of pixels (using half-blocks)
72
+ const pixelRows = targetHeight * 2;
73
+ const cellWidth = png.width / targetWidth;
74
+ const cellHeight = png.height / pixelRows;
75
+
76
+ const lines = [];
77
+
78
+ for (let row = 0; row < targetHeight; row++) {
79
+ let line = '';
80
+ for (let col = 0; col < targetWidth; col++) {
81
+ // Sample top and bottom pixels for this cell
82
+ const topY = row * 2 * cellHeight + cellHeight / 2;
83
+ const bottomY = (row * 2 + 1) * cellHeight + cellHeight / 2;
84
+ const x = col * cellWidth + cellWidth / 2;
85
+
86
+ const topPixel = samplePixel(png, x, topY);
87
+ const bottomPixel = samplePixel(png, x, bottomY);
88
+
89
+ const topBg = isBackground(topPixel);
90
+ const bottomBg = isBackground(bottomPixel);
91
+
92
+ if (topBg && bottomBg) {
93
+ // Both transparent - just a space
94
+ line += ' ';
95
+ } else if (topBg && !bottomBg) {
96
+ // Only bottom has color - use lower half block with fg color
97
+ line += fg(bottomPixel.r, bottomPixel.g, bottomPixel.b) + LOWER_HALF + RESET;
98
+ } else if (!topBg && bottomBg) {
99
+ // Only top has color - use upper half block with fg color
100
+ line += fg(topPixel.r, topPixel.g, topPixel.b) + UPPER_HALF + RESET;
101
+ } else {
102
+ // Both have color - use upper half with fg=top, bg=bottom
103
+ line += fg(topPixel.r, topPixel.g, topPixel.b) + bg(bottomPixel.r, bottomPixel.g, bottomPixel.b) + UPPER_HALF + RESET;
61
104
  }
62
-
63
- // Calculate brightness (0-255)
64
- const brightness = (r + g + b) / 3;
65
- // Map to ASCII character (inverted - darker pixels = denser characters)
66
- const charIndex = Math.floor((1 - brightness / 255) * (ASCII_CHARS.length - 1));
67
- row += ASCII_CHARS[charIndex];
68
105
  }
69
- ascii += row + '\n';
106
+ lines.push(line);
70
107
  }
71
108
 
72
- return ascii;
109
+ return lines;
73
110
  } catch (e) {
74
111
  return null;
75
112
  }
76
113
  }
77
114
 
78
- // Color the ASCII art with purple gradient based on position
79
- function colorAsciiArt(ascii, startColor = colors.purple2, endColor = colors.purple6) {
80
- const lines = ascii.split('\n').filter(line => line.trim());
81
- const purpleGradient = [colors.purple2, colors.purple3, colors.purple4, colors.purple5, colors.purple6];
115
+ // "gbos.io" pixel art (8 rows tall to match logo height)
116
+ // Each character is approximately 5 wide, total ~35 wide
117
+ function getGbosTextPixels() {
118
+ // 8-row pixel art for "gbos.io"
119
+ // Using purple gradient colors
120
+ const p1 = PURPLE.dark;
121
+ const p2 = PURPLE.medium;
122
+ const p3 = PURPLE.light;
123
+ const p4 = PURPLE.bright;
124
+
125
+ // Pixel art bitmap for "gbos.io" - 8 rows, each row is pairs of pixels
126
+ // 0 = transparent, 1-4 = purple shades
127
+ const bitmap = [
128
+ ' 222 2222 222 2222 22 222 ',
129
+ ' 2 2 2 2 2 2 2 2 2 2 2 ',
130
+ ' 2 2 2 2 2 2 2 2 2 ',
131
+ ' 2 222 2222 2 2 222 2 2 2 ',
132
+ ' 2 2 2 2 2 2 2 2 2 2 ',
133
+ ' 2 2 2 2 2 2 2 22 2 2 2 2 ',
134
+ ' 222 2222 222 2222 22 22 222 ',
135
+ ' ',
136
+ ];
137
+
138
+ const lines = [];
139
+ const colorMap = {
140
+ ' ': null,
141
+ '1': p1,
142
+ '2': p2,
143
+ '3': p3,
144
+ '4': p4,
145
+ };
146
+
147
+ // Process 2 bitmap rows at a time into 1 output row (half-block technique)
148
+ for (let row = 0; row < bitmap.length; row += 2) {
149
+ let line = '';
150
+ const topRow = bitmap[row] || '';
151
+ const bottomRow = bitmap[row + 1] || '';
152
+ const width = Math.max(topRow.length, bottomRow.length);
153
+
154
+ for (let col = 0; col < width; col++) {
155
+ const topChar = topRow[col] || ' ';
156
+ const bottomChar = bottomRow[col] || ' ';
157
+ const topColor = colorMap[topChar];
158
+ const bottomColor = colorMap[bottomChar];
159
+
160
+ if (!topColor && !bottomColor) {
161
+ line += ' ';
162
+ } else if (!topColor && bottomColor) {
163
+ line += fg(bottomColor[0], bottomColor[1], bottomColor[2]) + LOWER_HALF + RESET;
164
+ } else if (topColor && !bottomColor) {
165
+ line += fg(topColor[0], topColor[1], topColor[2]) + UPPER_HALF + RESET;
166
+ } else {
167
+ line += fg(topColor[0], topColor[1], topColor[2]) + bg(bottomColor[0], bottomColor[1], bottomColor[2]) + UPPER_HALF + RESET;
168
+ }
169
+ }
170
+ lines.push(line);
171
+ }
82
172
 
83
- return lines.map((line, i) => {
84
- const colorIndex = Math.floor((i / lines.length) * purpleGradient.length);
85
- const color = purpleGradient[Math.min(colorIndex, purpleGradient.length - 1)];
86
- return color + line + colors.reset;
87
- }).join('\n');
173
+ return lines;
88
174
  }
89
175
 
90
- // ASCII art horse head + GBOS logo combined (fallback)
91
- const ASCII_LOGO_SIMPLE = `
92
- ${colors.purple3}${colors.bold} ▄▀▀▀▄▄
93
- ${colors.purple3} ▄▀ ▀▄ ${colors.purple4}██████╗ ██████╗ ██████╗ ███████╗
94
- ${colors.purple4} ● █ ${colors.purple4}██╔════╝ ██╔══██╗██╔═══██╗██╔════╝
95
- ${colors.purple4} █ █ ${colors.purple5}██║ ███╗██████╔╝██║ ██║███████╗
96
- ${colors.purple5} ▀▄ ▄▀ ${colors.purple5}██║ ██║██╔══██╗██║ ██║╚════██║
97
- ${colors.purple5} ▄▀ ▀▀▀▀▀ ${colors.purple6}╚██████╔╝██████╔╝╚██████╔╝███████║
98
- ${colors.purple6} █ █ ${colors.purple6} ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
99
- ${colors.purple6} █ █
100
- ${colors.reset}`;
101
-
102
- // GBOS text only
103
- const GBOS_TEXT = `${colors.purple4}${colors.bold}██████╗ ██████╗ ██████╗ ███████╗
104
- ${colors.purple4}██╔════╝ ██╔══██╗██╔═══██╗██╔════╝
105
- ${colors.purple5}██║ ███╗██████╔╝██║ ██║███████╗
106
- ${colors.purple5}██║ ██║██╔══██╗██║ ██║╚════██║
107
- ${colors.purple6}╚██████╔╝██████╔╝╚██████╔╝███████║
108
- ${colors.purple6} ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝${colors.reset}`;
109
-
110
- // Check if catimg is available
111
- function hasCatimg() {
112
- try {
113
- execSync('which catimg', { stdio: 'ignore' });
114
- return true;
115
- } catch (e) {
116
- return false;
176
+ // Fallback compact logo
177
+ const COMPACT_LOGO = [
178
+ `${fg(...PURPLE.medium)} ▄▀▀▀▄▄${RESET}`,
179
+ `${fg(...PURPLE.medium)}▄▀${fg(...PURPLE.bright)}●${fg(...PURPLE.medium)} ▀▄${RESET}`,
180
+ `${fg(...PURPLE.light)}█ █${RESET}`,
181
+ `${fg(...PURPLE.light)} ▀▄ ▄▀${RESET}`,
182
+ `${fg(...PURPLE.bright)} ▀▀▀▀${RESET}`,
183
+ ];
184
+
185
+ // Combine logo and text side by side
186
+ function combineLogoAndText(logoLines, textLines) {
187
+ const combined = [];
188
+ const maxLines = Math.max(logoLines.length, textLines.length);
189
+ const logoStart = Math.floor((maxLines - logoLines.length) / 2);
190
+ const textStart = Math.floor((maxLines - textLines.length) / 2);
191
+
192
+ for (let i = 0; i < maxLines; i++) {
193
+ const logo = logoLines[i - logoStart] || '';
194
+ const text = textLines[i - textStart] || '';
195
+ combined.push(logo + ' ' + text);
117
196
  }
197
+
198
+ return combined;
118
199
  }
119
200
 
120
- // Display logo using image-to-ascii or ASCII fallback
121
- function displayLogo() {
201
+ // Display logo with connection details
202
+ function displayLogoWithDetails(details = null) {
122
203
  const logoPath = path.join(__dirname, '../../images/logo.png');
123
-
124
- // Try to convert image to ASCII
125
- if (fs.existsSync(logoPath)) {
126
- const ascii = imageToAscii(logoPath, 25);
127
- if (ascii) {
128
- // Color the ASCII art
129
- const coloredAscii = colorAsciiArt(ascii);
130
- const asciiLines = coloredAscii.split('\n');
131
- const gbosLines = GBOS_TEXT.split('\n');
132
-
133
- // Combine horse ASCII and GBOS text side by side
134
- console.log('');
135
- const maxLines = Math.max(asciiLines.length, gbosLines.length);
136
- for (let i = 0; i < maxLines; i++) {
137
- const asciiLine = asciiLines[i] || '';
138
- const gbosLine = gbosLines[i - Math.floor((maxLines - gbosLines.length) / 2)] || '';
139
- // Pad ASCII line to consistent width
140
- const paddedAscii = asciiLine.padEnd(35);
141
- console.log(` ${paddedAscii} ${gbosLine}`);
142
- }
143
- console.log('');
144
- return;
204
+ const termWidth = getTerminalWidth();
205
+
206
+ // Render logo at ~24 chars wide, 6 rows tall (12 pixel rows with half-blocks)
207
+ let logoLines = imageToPixels(logoPath, 24, 6);
208
+ if (!logoLines) logoLines = COMPACT_LOGO;
209
+
210
+ // Get pixel art text
211
+ const textLines = getGbosTextPixels();
212
+
213
+ // Combine logo and text
214
+ const leftSide = combineLogoAndText(logoLines, textLines);
215
+ const leftWidth = 70; // Account for escape codes
216
+
217
+ // Build right side (details box)
218
+ const rightWidth = Math.max(30, termWidth - 75);
219
+ const rightLines = [];
220
+
221
+ if (details) {
222
+ rightLines.push(`${colors.purple4}┌${'─'.repeat(rightWidth - 2)}┐${RESET}`);
223
+ rightLines.push(`${colors.purple4}│${RESET} ${BOLD}${colors.purple5}Connected${RESET}${' '.repeat(rightWidth - 12)}${colors.purple4}│${RESET}`);
224
+ rightLines.push(`${colors.purple4}├${''.repeat(rightWidth - 2)}┤${RESET}`);
225
+
226
+ const addField = (label, value, valueColor = colors.white) => {
227
+ const val = (value || 'N/A').toString().substring(0, rightWidth - label.length - 6);
228
+ const padding = ' '.repeat(Math.max(0, rightWidth - label.length - val.length - 5));
229
+ rightLines.push(`${colors.purple4}│${RESET} ${colors.purple7}${label}${RESET} ${valueColor}${val}${RESET}${padding}${colors.purple4}│${RESET}`);
230
+ };
231
+
232
+ addField('Account:', details.accountName, colors.white);
233
+ addField('App:', details.applicationName, colors.purple5);
234
+ addField('Node:', details.nodeName, colors.purple4);
235
+ addField('ID:', details.nodeId, colors.dim);
236
+ if (details.connectionId) {
237
+ addField('Conn:', details.connectionId.substring(0, 18) + '...', colors.dim);
145
238
  }
239
+
240
+ rightLines.push(`${colors.purple4}└${'─'.repeat(rightWidth - 2)}┘${RESET}`);
146
241
  }
147
242
 
148
- // Fallback to manual ASCII horse
149
- console.log(ASCII_LOGO_SIMPLE);
243
+ // Print side by side
244
+ console.log('');
245
+ const maxLines = Math.max(leftSide.length, rightLines.length);
246
+ const leftStart = Math.floor((maxLines - leftSide.length) / 2);
247
+ const rightStart = Math.floor((maxLines - rightLines.length) / 2);
248
+
249
+ for (let i = 0; i < maxLines; i++) {
250
+ const left = leftSide[i - leftStart] || '';
251
+ const right = rightLines[i - rightStart] || '';
252
+ // Pad left side accounting for ANSI codes (rough estimate)
253
+ const visibleLen = left.replace(/\x1b\[[0-9;]*m/g, '').length;
254
+ const padding = ' '.repeat(Math.max(0, leftWidth - visibleLen));
255
+ console.log(` ${left}${padding}${right}`);
256
+ }
257
+ console.log('');
150
258
  }
151
259
 
152
- // Display styled session summary (Gemini design + Claude Code layout) - Purple theme
153
- function displaySessionSummary(data) {
154
- const {
155
- accountName,
156
- applicationName,
157
- nodeName,
158
- nodeId,
159
- connectionId,
160
- userId,
161
- accountId,
162
- } = data;
163
-
164
- const line = '─'.repeat(61);
165
- const doubleLine = '═'.repeat(61);
166
-
167
- console.log(`\n${colors.purple3}╔${doubleLine}╗${colors.reset}`);
168
- console.log(`${colors.purple3}║${colors.reset}${colors.bold}${colors.purple5} GBOS Connected ${colors.reset}${colors.purple3}║${colors.reset}`);
169
- console.log(`${colors.purple3}╠${doubleLine}╣${colors.reset}`);
170
-
171
- // Account section
172
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple3}║${colors.reset}`);
173
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple6}Account${colors.reset} ${colors.purple3}║${colors.reset}`);
174
- console.log(`${colors.purple3}║${colors.reset} ${colors.bold}${colors.purple7}${(accountName || 'N/A').substring(0, 50).padEnd(55)}${colors.reset} ${colors.purple3}║${colors.reset}`);
175
- console.log(`${colors.purple3}║${colors.reset} ${colors.dim}${colors.purple6}ID: ${accountId || 'N/A'}${colors.reset}${' '.repeat(Math.max(0, 53 - String(accountId || 'N/A').length))}${colors.purple3}║${colors.reset}`);
176
-
177
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple3}║${colors.reset}`);
178
- console.log(`${colors.purple4}╟${line}╢${colors.reset}`);
179
-
180
- // Application section
181
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple3}║${colors.reset}`);
182
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple6}Application${colors.reset} ${colors.purple3}║${colors.reset}`);
183
- console.log(`${colors.purple3}║${colors.reset} ${colors.bold}${colors.purple5}${(applicationName || 'N/A').substring(0, 50).padEnd(55)}${colors.reset} ${colors.purple3}║${colors.reset}`);
184
-
185
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple3}║${colors.reset}`);
186
- console.log(`${colors.purple4}╟${line}╢${colors.reset}`);
187
-
188
- // Node section
189
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple3}║${colors.reset}`);
190
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple6}Development Node${colors.reset} ${colors.purple3}║${colors.reset}`);
191
- console.log(`${colors.purple3}║${colors.reset} ${colors.bold}${colors.purple4}${(nodeName || 'N/A').substring(0, 50).padEnd(55)}${colors.reset} ${colors.purple3}║${colors.reset}`);
192
- console.log(`${colors.purple3}║${colors.reset} ${colors.dim}${colors.purple6}Node ID: ${nodeId || 'N/A'}${colors.reset}${' '.repeat(Math.max(0, 48 - String(nodeId || 'N/A').length))}${colors.purple3}║${colors.reset}`);
193
-
194
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple3}║${colors.reset}`);
195
- console.log(`${colors.purple4}╟${line}╢${colors.reset}`);
196
-
197
- // Connection section
198
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple3}║${colors.reset}`);
199
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple6}Connection${colors.reset} ${colors.purple3}║${colors.reset}`);
200
- const connIdDisplay = connectionId ? connectionId.substring(0, 36) : 'N/A';
201
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple5}${connIdDisplay.padEnd(55)}${colors.reset} ${colors.purple3}║${colors.reset}`);
202
-
203
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple3}║${colors.reset}`);
204
- console.log(`${colors.purple3}╚${doubleLine}╝${colors.reset}`);
205
-
206
- console.log(`\n${colors.purple5}✓${colors.reset} ${colors.bold}${colors.purple6}Ready to work!${colors.reset}`);
207
- console.log(`${colors.dim}${colors.purple7} Session stored at ~/.gbos/session.json${colors.reset}\n`);
260
+ function displayLogo() {
261
+ displayLogoWithDetails(null);
208
262
  }
209
263
 
210
- // Display auth success screen - Purple theme
211
264
  function displayAuthSuccess(data) {
212
- displayLogo();
213
-
214
- const line = '─'.repeat(61);
215
- const doubleLine = '═'.repeat(61);
216
-
217
- console.log(`${colors.purple3}╔${doubleLine}╗${colors.reset}`);
218
- console.log(`${colors.purple3}║${colors.reset}${colors.bold}${colors.purple5} Authentication Successful ${colors.reset}${colors.purple3}║${colors.reset}`);
219
- console.log(`${colors.purple3}╠${doubleLine}╣${colors.reset}`);
220
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple3}║${colors.reset}`);
221
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple6}User ID${colors.reset} ${colors.purple7}${String(data.userId || 'N/A').padEnd(42)}${colors.reset}${colors.purple3}║${colors.reset}`);
222
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple6}Account ID${colors.reset} ${colors.purple7}${String(data.accountId || 'N/A').padEnd(42)}${colors.reset}${colors.purple3}║${colors.reset}`);
223
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple6}Session${colors.reset} ${colors.dim}${colors.purple5}${(data.sessionId || 'N/A').substring(0, 36).padEnd(42)}${colors.reset}${colors.purple3}║${colors.reset}`);
224
- console.log(`${colors.purple3}║${colors.reset} ${colors.purple3}║${colors.reset}`);
225
- console.log(`${colors.purple3}╚${doubleLine}╝${colors.reset}`);
226
-
227
- console.log(`\n${colors.purple5}✓${colors.reset} ${colors.bold}${colors.purple6}Authenticated!${colors.reset}`);
228
- console.log(`${colors.dim}${colors.purple7} Run "gbos connect" to connect to a development node.${colors.reset}\n`);
265
+ const logoPath = path.join(__dirname, '../../images/logo.png');
266
+ const termWidth = getTerminalWidth();
267
+
268
+ let logoLines = imageToPixels(logoPath, 24, 6);
269
+ if (!logoLines) logoLines = COMPACT_LOGO;
270
+
271
+ const textLines = getGbosTextPixels();
272
+ const leftSide = combineLogoAndText(logoLines, textLines);
273
+ const leftWidth = 70;
274
+
275
+ const rightWidth = Math.max(30, termWidth - 75);
276
+ const rightLines = [];
277
+
278
+ rightLines.push(`${colors.purple4}┌${'─'.repeat(rightWidth - 2)}┐${RESET}`);
279
+ rightLines.push(`${colors.purple4}│${RESET} ${BOLD}${colors.purple5}✓ Authenticated${RESET}${' '.repeat(rightWidth - 18)}${colors.purple4}│${RESET}`);
280
+ rightLines.push(`${colors.purple4}├${'─'.repeat(rightWidth - 2)}┤${RESET}`);
281
+
282
+ const addField = (label, value) => {
283
+ const val = (value || 'N/A').toString().substring(0, rightWidth - label.length - 6);
284
+ const padding = ' '.repeat(Math.max(0, rightWidth - label.length - val.length - 5));
285
+ rightLines.push(`${colors.purple4}│${RESET} ${colors.purple7}${label}${RESET} ${colors.white}${val}${RESET}${padding}${colors.purple4}│${RESET}`);
286
+ };
287
+
288
+ addField('User:', data.userId);
289
+ addField('Account:', data.accountId);
290
+ addField('Session:', (data.sessionId || '').substring(0, 24) + '...');
291
+
292
+ rightLines.push(`${colors.purple4}└${'─'.repeat(rightWidth - 2)}┘${RESET}`);
293
+ rightLines.push('');
294
+ rightLines.push(`${colors.purple7}${DIM}Run "gbos connect" to connect${RESET}`);
295
+
296
+ console.log('');
297
+ const maxLines = Math.max(leftSide.length, rightLines.length);
298
+ const leftStart = Math.floor((maxLines - leftSide.length) / 2);
299
+ const rightStart = Math.floor((maxLines - rightLines.length) / 2);
300
+
301
+ for (let i = 0; i < maxLines; i++) {
302
+ const left = leftSide[i - leftStart] || '';
303
+ const right = rightLines[i - rightStart] || '';
304
+ const visibleLen = left.replace(/\x1b\[[0-9;]*m/g, '').length;
305
+ const padding = ' '.repeat(Math.max(0, leftWidth - visibleLen));
306
+ console.log(` ${left}${padding}${right}`);
307
+ }
308
+ console.log('');
229
309
  }
230
310
 
231
- // Display connect success screen
232
311
  function displayConnectSuccess(data) {
233
- displayLogo();
234
- displaySessionSummary(data);
312
+ displayLogoWithDetails(data);
313
+ console.log(` ${colors.purple5}✓${RESET} ${BOLD}${colors.purple6}Ready to work!${RESET}`);
314
+ console.log(` ${DIM}${colors.purple7}Session: ~/.gbos/session.json${RESET}\n`);
315
+ }
316
+
317
+ function displaySessionSummary(data) {
318
+ displayLogoWithDetails(data);
235
319
  }
236
320
 
237
- // Display simple message box - Purple theme
238
321
  function displayMessageBox(title, message, type = 'info') {
239
- const colorMap = {
240
- info: colors.purple4,
241
- success: colors.purple5,
242
- warning: colors.yellow,
243
- error: '\x1b[31m',
244
- };
322
+ const colorMap = { info: colors.purple4, success: colors.purple5, warning: `${ESC}[33m`, error: `${ESC}[31m` };
245
323
  const color = colorMap[type] || colors.purple4;
246
324
  const icon = type === 'success' ? '✓' : type === 'warning' ? '⚠' : type === 'error' ? '✗' : 'ℹ';
325
+ const width = Math.min(55, getTerminalWidth() - 4);
247
326
 
248
- const line = '─'.repeat(61);
249
-
250
- console.log(`\n${color}┌${line}┐${colors.reset}`);
251
- console.log(`${color}│${colors.reset} ${icon} ${colors.bold}${colors.purple6}${title.padEnd(56)}${colors.reset}${color}│${colors.reset}`);
252
- console.log(`${color}├${line}┤${colors.reset}`);
327
+ console.log(`\n${color}┌${'─'.repeat(width)}┐${RESET}`);
328
+ console.log(`${color}│${RESET} ${icon} ${BOLD}${colors.purple6}${title.substring(0, width - 5).padEnd(width - 4)}${RESET}${color}│${RESET}`);
329
+ console.log(`${color}├${'─'.repeat(width)}┤${RESET}`);
253
330
 
254
- // Word wrap message
255
331
  const words = message.split(' ');
256
332
  let currentLine = '';
257
-
258
333
  for (const word of words) {
259
- if ((currentLine + ' ' + word).trim().length > 57) {
260
- console.log(`${color}│${colors.reset} ${colors.purple7}${currentLine.trim().padEnd(57)}${colors.reset}${color}│${colors.reset}`);
334
+ if ((currentLine + ' ' + word).trim().length > width - 4) {
335
+ console.log(`${color}│${RESET} ${colors.purple7}${currentLine.trim().padEnd(width - 2)}${RESET}${color}│${RESET}`);
261
336
  currentLine = word;
262
337
  } else {
263
338
  currentLine = currentLine ? currentLine + ' ' + word : word;
264
339
  }
265
340
  }
266
-
267
341
  if (currentLine) {
268
- console.log(`${color}│${colors.reset} ${colors.purple7}${currentLine.trim().padEnd(57)}${colors.reset}${color}│${colors.reset}`);
342
+ console.log(`${color}│${RESET} ${colors.purple7}${currentLine.trim().padEnd(width - 2)}${RESET}${color}│${RESET}`);
269
343
  }
270
-
271
- console.log(`${color}└${line}┘${colors.reset}\n`);
344
+ console.log(`${color}└${'─'.repeat(width)}┘${RESET}\n`);
272
345
  }
273
346
 
274
347
  module.exports = {
275
348
  colors,
276
349
  displayLogo,
350
+ displayLogoWithDetails,
277
351
  displaySessionSummary,
278
352
  displayAuthSuccess,
279
353
  displayConnectSuccess,
280
354
  displayMessageBox,
281
- hasCatimg,
282
- imageToAscii,
355
+ imageToPixels,
356
+ getTerminalWidth,
283
357
  };