gbos 1.1.6 → 1.1.8
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/package.json +1 -1
- package/src/lib/display.js +103 -128
package/package.json
CHANGED
package/src/lib/display.js
CHANGED
|
@@ -61,9 +61,42 @@ function isBackground(pixel) {
|
|
|
61
61
|
return pixel.a < 50 || (pixel.r > 240 && pixel.g > 240 && pixel.b > 240);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// Shading characters for smooth edges (from light to full)
|
|
65
|
+
const SHADE_CHARS = [' ', '░', '▒', '▓', '█'];
|
|
66
|
+
|
|
67
|
+
// Get average alpha/coverage for a region (for anti-aliasing)
|
|
68
|
+
function getRegionCoverage(png, startX, startY, width, height) {
|
|
69
|
+
let totalAlpha = 0;
|
|
70
|
+
let totalR = 0, totalG = 0, totalB = 0;
|
|
71
|
+
let samples = 0;
|
|
72
|
+
|
|
73
|
+
for (let y = startY; y < startY + height; y++) {
|
|
74
|
+
for (let x = startX; x < startX + width; x++) {
|
|
75
|
+
const pixel = samplePixel(png, x, y);
|
|
76
|
+
if (!isBackground(pixel)) {
|
|
77
|
+
totalAlpha += pixel.a;
|
|
78
|
+
totalR += pixel.r;
|
|
79
|
+
totalG += pixel.g;
|
|
80
|
+
totalB += pixel.b;
|
|
81
|
+
samples++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (samples === 0) return { coverage: 0, r: 0, g: 0, b: 0 };
|
|
87
|
+
|
|
88
|
+
const totalPossible = width * height;
|
|
89
|
+
return {
|
|
90
|
+
coverage: samples / totalPossible,
|
|
91
|
+
r: Math.round(totalR / samples),
|
|
92
|
+
g: Math.round(totalG / samples),
|
|
93
|
+
b: Math.round(totalB / samples),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Convert PNG to true-color pixel art with smooth edges
|
|
98
|
+
// Uses shading characters for anti-aliasing on edges
|
|
99
|
+
function imageToPixels(imagePath, targetWidth = 24, targetHeight = 5) {
|
|
67
100
|
try {
|
|
68
101
|
const data = fs.readFileSync(imagePath);
|
|
69
102
|
const png = PNG.sync.read(data);
|
|
@@ -78,30 +111,62 @@ function imageToPixels(imagePath, targetWidth = 24, targetHeight = 12) {
|
|
|
78
111
|
for (let row = 0; row < targetHeight; row++) {
|
|
79
112
|
let line = '';
|
|
80
113
|
for (let col = 0; col < targetWidth; col++) {
|
|
81
|
-
//
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
114
|
+
// Get coverage for top and bottom halves of this cell
|
|
115
|
+
const topStartY = row * 2 * cellHeight;
|
|
116
|
+
const bottomStartY = (row * 2 + 1) * cellHeight;
|
|
117
|
+
const startX = col * cellWidth;
|
|
85
118
|
|
|
86
|
-
const
|
|
87
|
-
const
|
|
119
|
+
const topRegion = getRegionCoverage(png, startX, topStartY, cellWidth, cellHeight);
|
|
120
|
+
const bottomRegion = getRegionCoverage(png, startX, bottomStartY, cellWidth, cellHeight);
|
|
88
121
|
|
|
89
|
-
const
|
|
90
|
-
const
|
|
122
|
+
const topCov = topRegion.coverage;
|
|
123
|
+
const bottomCov = bottomRegion.coverage;
|
|
91
124
|
|
|
92
|
-
|
|
93
|
-
|
|
125
|
+
// Both empty
|
|
126
|
+
if (topCov < 0.1 && bottomCov < 0.1) {
|
|
94
127
|
line += ' ';
|
|
95
|
-
|
|
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;
|
|
128
|
+
continue;
|
|
104
129
|
}
|
|
130
|
+
|
|
131
|
+
// Use dots for very light coverage (edges)
|
|
132
|
+
if (topCov < 0.3 && bottomCov < 0.3) {
|
|
133
|
+
const avgCov = (topCov + bottomCov) / 2;
|
|
134
|
+
const r = topCov > bottomCov ? topRegion.r : bottomRegion.r;
|
|
135
|
+
const g = topCov > bottomCov ? topRegion.g : bottomRegion.g;
|
|
136
|
+
const b = topCov > bottomCov ? topRegion.b : bottomRegion.b;
|
|
137
|
+
if (avgCov < 0.15) {
|
|
138
|
+
line += fg(r, g, b) + '·' + RESET;
|
|
139
|
+
} else {
|
|
140
|
+
line += fg(r, g, b) + '░' + RESET;
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Light top, solid bottom - use lower block with possible shading
|
|
146
|
+
if (topCov < 0.3 && bottomCov >= 0.3) {
|
|
147
|
+
if (topCov > 0.1) {
|
|
148
|
+
// Add dot above
|
|
149
|
+
line += fg(bottomRegion.r, bottomRegion.g, bottomRegion.b) + '▄' + RESET;
|
|
150
|
+
} else {
|
|
151
|
+
line += fg(bottomRegion.r, bottomRegion.g, bottomRegion.b) + LOWER_HALF + RESET;
|
|
152
|
+
}
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Solid top, light bottom
|
|
157
|
+
if (topCov >= 0.3 && bottomCov < 0.3) {
|
|
158
|
+
if (bottomCov > 0.1) {
|
|
159
|
+
line += fg(topRegion.r, topRegion.g, topRegion.b) + '▀' + RESET;
|
|
160
|
+
} else {
|
|
161
|
+
line += fg(topRegion.r, topRegion.g, topRegion.b) + UPPER_HALF + RESET;
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Both have significant coverage - use half block with colors
|
|
167
|
+
line += fg(topRegion.r, topRegion.g, topRegion.b) +
|
|
168
|
+
bg(bottomRegion.r, bottomRegion.g, bottomRegion.b) +
|
|
169
|
+
UPPER_HALF + RESET;
|
|
105
170
|
}
|
|
106
171
|
lines.push(line);
|
|
107
172
|
}
|
|
@@ -112,132 +177,45 @@ function imageToPixels(imagePath, targetWidth = 24, targetHeight = 12) {
|
|
|
112
177
|
}
|
|
113
178
|
}
|
|
114
179
|
|
|
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
|
-
}
|
|
172
|
-
|
|
173
|
-
return lines;
|
|
174
|
-
}
|
|
175
180
|
|
|
176
181
|
// Fallback compact logo
|
|
177
182
|
const COMPACT_LOGO = [
|
|
178
|
-
`${fg(...PURPLE.medium)}
|
|
179
|
-
`${fg(...PURPLE.medium)}▄▀${fg(...PURPLE.bright)}
|
|
180
|
-
`${fg(...PURPLE.light)}
|
|
181
|
-
`${fg(...PURPLE.light)} ▀▄ ▄▀${RESET}`,
|
|
182
|
-
`${fg(...PURPLE.bright)} ▀▀▀▀${RESET}`,
|
|
183
|
+
`${fg(...PURPLE.medium)} ▄▀▀▄${RESET}`,
|
|
184
|
+
`${fg(...PURPLE.medium)}▄▀${fg(...PURPLE.bright)}·${fg(...PURPLE.medium)} ▀▄${RESET}`,
|
|
185
|
+
`${fg(...PURPLE.light)} ▀▄▄▀${RESET}`,
|
|
183
186
|
];
|
|
184
187
|
|
|
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);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return combined;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
188
|
// Display logo with connection details (Claude Code style - clean, minimal)
|
|
202
189
|
function displayLogoWithDetails(details = null) {
|
|
203
190
|
const logoPath = path.join(__dirname, '../../images/logo.png');
|
|
204
191
|
const version = require('../../package.json').version;
|
|
205
192
|
|
|
206
|
-
// Render logo at ~
|
|
207
|
-
let logoLines = imageToPixels(logoPath,
|
|
193
|
+
// Render logo at ~20 chars wide, 5 rows tall (smooth edges)
|
|
194
|
+
let logoLines = imageToPixels(logoPath, 20, 5);
|
|
208
195
|
if (!logoLines) logoLines = COMPACT_LOGO;
|
|
209
196
|
|
|
210
|
-
//
|
|
211
|
-
const textLines = getGbosTextPixels();
|
|
212
|
-
|
|
213
|
-
// Combine logo and text
|
|
214
|
-
const leftSide = combineLogoAndText(logoLines, textLines);
|
|
215
|
-
const leftWidth = 70; // Account for escape codes
|
|
197
|
+
const logoWidth = 28; // Account for escape codes
|
|
216
198
|
|
|
217
199
|
// Build right side - Claude Code style (clean lines, no boxes)
|
|
218
200
|
const rightLines = [];
|
|
219
201
|
|
|
220
202
|
if (details) {
|
|
221
|
-
// Title line with version
|
|
222
203
|
rightLines.push(`${BOLD}${colors.purple5}GBOS${RESET} ${DIM}v${version}${RESET}`);
|
|
223
|
-
// Account and App on same line
|
|
224
204
|
rightLines.push(`${colors.white}${details.accountName || 'N/A'}${RESET} ${DIM}·${RESET} ${colors.purple5}${details.applicationName || 'N/A'}${RESET}`);
|
|
225
|
-
// Node info
|
|
226
205
|
rightLines.push(`${DIM}${details.nodeName || 'N/A'}${RESET}`);
|
|
227
206
|
}
|
|
228
207
|
|
|
229
208
|
// Print side by side
|
|
230
209
|
console.log('');
|
|
231
|
-
const maxLines = Math.max(
|
|
232
|
-
const
|
|
210
|
+
const maxLines = Math.max(logoLines.length, rightLines.length);
|
|
211
|
+
const logoStart = Math.floor((maxLines - logoLines.length) / 2);
|
|
233
212
|
const rightStart = Math.floor((maxLines - rightLines.length) / 2);
|
|
234
213
|
|
|
235
214
|
for (let i = 0; i < maxLines; i++) {
|
|
236
|
-
const left =
|
|
215
|
+
const left = logoLines[i - logoStart] || '';
|
|
237
216
|
const right = rightLines[i - rightStart] || '';
|
|
238
|
-
// Pad left side accounting for ANSI codes (rough estimate)
|
|
239
217
|
const visibleLen = left.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
240
|
-
const padding = ' '.repeat(Math.max(0,
|
|
218
|
+
const padding = ' '.repeat(Math.max(0, logoWidth - visibleLen));
|
|
241
219
|
console.log(` ${left}${padding}${right}`);
|
|
242
220
|
}
|
|
243
221
|
console.log('');
|
|
@@ -251,14 +229,11 @@ function displayAuthSuccess(data) {
|
|
|
251
229
|
const logoPath = path.join(__dirname, '../../images/logo.png');
|
|
252
230
|
const version = require('../../package.json').version;
|
|
253
231
|
|
|
254
|
-
let logoLines = imageToPixels(logoPath,
|
|
232
|
+
let logoLines = imageToPixels(logoPath, 20, 5);
|
|
255
233
|
if (!logoLines) logoLines = COMPACT_LOGO;
|
|
256
234
|
|
|
257
|
-
const
|
|
258
|
-
const leftSide = combineLogoAndText(logoLines, textLines);
|
|
259
|
-
const leftWidth = 70;
|
|
235
|
+
const logoWidth = 28;
|
|
260
236
|
|
|
261
|
-
// Claude Code style - clean lines, no boxes
|
|
262
237
|
const rightLines = [];
|
|
263
238
|
rightLines.push(`${BOLD}${colors.purple5}GBOS${RESET} ${DIM}v${version}${RESET}`);
|
|
264
239
|
rightLines.push(`${colors.purple5}✓${RESET} ${colors.white}Authenticated${RESET}`);
|
|
@@ -267,15 +242,15 @@ function displayAuthSuccess(data) {
|
|
|
267
242
|
rightLines.push(`${DIM}Run "gbos connect" to connect${RESET}`);
|
|
268
243
|
|
|
269
244
|
console.log('');
|
|
270
|
-
const maxLines = Math.max(
|
|
271
|
-
const
|
|
245
|
+
const maxLines = Math.max(logoLines.length, rightLines.length);
|
|
246
|
+
const logoStart = Math.floor((maxLines - logoLines.length) / 2);
|
|
272
247
|
const rightStart = Math.floor((maxLines - rightLines.length) / 2);
|
|
273
248
|
|
|
274
249
|
for (let i = 0; i < maxLines; i++) {
|
|
275
|
-
const left =
|
|
250
|
+
const left = logoLines[i - logoStart] || '';
|
|
276
251
|
const right = rightLines[i - rightStart] || '';
|
|
277
252
|
const visibleLen = left.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
278
|
-
const padding = ' '.repeat(Math.max(0,
|
|
253
|
+
const padding = ' '.repeat(Math.max(0, logoWidth - visibleLen));
|
|
279
254
|
console.log(` ${left}${padding}${right}`);
|
|
280
255
|
}
|
|
281
256
|
console.log('');
|