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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/lib/display.js +103 -128
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbos",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "GBOS - Command line interface for GBOS services",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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
- // 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) {
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
- // 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;
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 topPixel = samplePixel(png, x, topY);
87
- const bottomPixel = samplePixel(png, x, bottomY);
119
+ const topRegion = getRegionCoverage(png, startX, topStartY, cellWidth, cellHeight);
120
+ const bottomRegion = getRegionCoverage(png, startX, bottomStartY, cellWidth, cellHeight);
88
121
 
89
- const topBg = isBackground(topPixel);
90
- const bottomBg = isBackground(bottomPixel);
122
+ const topCov = topRegion.coverage;
123
+ const bottomCov = bottomRegion.coverage;
91
124
 
92
- if (topBg && bottomBg) {
93
- // Both transparent - just a space
125
+ // Both empty
126
+ if (topCov < 0.1 && bottomCov < 0.1) {
94
127
  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;
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)} ▄▀▀▀▄▄${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
+ `${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 ~24 chars wide, 6 rows tall (12 pixel rows with half-blocks)
207
- let logoLines = imageToPixels(logoPath, 24, 6);
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
- // 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
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(leftSide.length, rightLines.length);
232
- const leftStart = Math.floor((maxLines - leftSide.length) / 2);
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 = leftSide[i - leftStart] || '';
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, leftWidth - visibleLen));
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, 24, 6);
232
+ let logoLines = imageToPixels(logoPath, 20, 5);
255
233
  if (!logoLines) logoLines = COMPACT_LOGO;
256
234
 
257
- const textLines = getGbosTextPixels();
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(leftSide.length, rightLines.length);
271
- const leftStart = Math.floor((maxLines - leftSide.length) / 2);
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 = leftSide[i - leftStart] || '';
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, leftWidth - visibleLen));
253
+ const padding = ' '.repeat(Math.max(0, logoWidth - visibleLen));
279
254
  console.log(` ${left}${padding}${right}`);
280
255
  }
281
256
  console.log('');