gbos 1.2.0 → 1.2.6

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.2.0",
3
+ "version": "1.2.6",
4
4
  "description": "GBOS - Command line interface for GBOS services",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -39,7 +39,6 @@
39
39
  ],
40
40
  "dependencies": {
41
41
  "commander": "^12.1.0",
42
- "pngjs": "^7.0.0",
43
- "terminal-image": "^4.2.0"
42
+ "pngjs": "^7.0.0"
44
43
  }
45
44
  }
@@ -151,42 +151,75 @@ async function connectCommand(options) {
151
151
 
152
152
  console.log('\nFetching available applications...\n');
153
153
 
154
- // Fetch available nodes
155
- const nodesResponse = await api.listNodes();
156
- const nodes = nodesResponse.data || [];
154
+ // Try to fetch applications directly first
155
+ let appOptions = [];
156
+ let nodesByApp = {};
157
+
158
+ try {
159
+ // Try the applications endpoint first
160
+ const appsResponse = await api.listApplications();
161
+ const applications = appsResponse.data || [];
162
+
163
+ if (applications.length > 0) {
164
+ appOptions = applications.map((app) => ({
165
+ id: app.id,
166
+ name: app.name || `Application ${app.id}`,
167
+ description: app.description,
168
+ nodeCount: app.nodes_count || app.nodesCount || '?',
169
+ application: app,
170
+ }));
171
+ }
172
+ } catch (err) {
173
+ // Applications endpoint not available, fall back to deriving from nodes
174
+ if (process.env.DEBUG) {
175
+ console.log('Note: /cli/applications endpoint not available, falling back to nodes');
176
+ }
177
+ }
157
178
 
158
- if (nodes.length === 0) {
179
+ // If no applications from direct endpoint, derive from nodes
180
+ if (appOptions.length === 0) {
181
+ const nodesResponse = await api.listNodes();
182
+ const nodes = nodesResponse.data || [];
183
+
184
+ if (nodes.length === 0) {
185
+ displayMessageBox(
186
+ 'No Nodes Available',
187
+ 'No development nodes available. Please create a development node in the GBOS web interface.',
188
+ 'warning'
189
+ );
190
+ process.exit(1);
191
+ }
192
+
193
+ // Group nodes by application
194
+ nodes.forEach((node) => {
195
+ const appId = node.application_id || 'unassigned';
196
+ if (nodesByApp[appId] === undefined) {
197
+ nodesByApp[appId] = {
198
+ application: node.application,
199
+ nodes: [],
200
+ };
201
+ }
202
+ nodesByApp[appId].nodes.push(node);
203
+ });
204
+
205
+ const appIds = Object.keys(nodesByApp);
206
+ appOptions = appIds.map((appId) => ({
207
+ id: appId,
208
+ name: nodesByApp[appId].application?.name || `Application ${appId}`,
209
+ nodeCount: nodesByApp[appId].nodes.length,
210
+ application: nodesByApp[appId].application,
211
+ }));
212
+ }
213
+
214
+ if (appOptions.length === 0) {
159
215
  displayMessageBox(
160
- 'No Nodes Available',
161
- 'No development nodes available. Please create a development node in the GBOS web interface.',
216
+ 'No Applications Available',
217
+ 'No applications found. Please create an application in the GBOS web interface.',
162
218
  'warning'
163
219
  );
164
220
  process.exit(1);
165
221
  }
166
222
 
167
- // Group nodes by application
168
- const nodesByApp = {};
169
- nodes.forEach((node) => {
170
- const appId = node.application_id || 'unassigned';
171
- if (!nodesByApp[appId]) {
172
- nodesByApp[appId] = {
173
- application: node.application,
174
- nodes: [],
175
- };
176
- }
177
- nodesByApp[appId].nodes.push(node);
178
- });
179
-
180
- const appIds = Object.keys(nodesByApp);
181
-
182
- // Build application options
183
- const appOptions = appIds.map((appId) => ({
184
- id: appId,
185
- name: nodesByApp[appId].application?.name || `Application ${appId}`,
186
- nodeCount: nodesByApp[appId].nodes.length,
187
- application: nodesByApp[appId].application,
188
- }));
189
-
190
223
  // Always show application selection (even if only one)
191
224
  const selectedApp = await selectWithArrows(
192
225
  'Select an application:',
@@ -200,8 +233,28 @@ async function connectCommand(options) {
200
233
  }
201
234
 
202
235
  // Get nodes for selected application
203
- const appNodes = nodesByApp[selectedApp.id].nodes;
204
- const selectedApplication = nodesByApp[selectedApp.id].application;
236
+ let appNodes;
237
+ let selectedApplication = selectedApp.application;
238
+
239
+ if (nodesByApp[selectedApp.id]) {
240
+ // We already have nodes from the fallback path
241
+ appNodes = nodesByApp[selectedApp.id].nodes;
242
+ selectedApplication = nodesByApp[selectedApp.id].application || selectedApp.application;
243
+ } else {
244
+ // Fetch nodes for the selected application
245
+ console.log(`\nFetching nodes for ${selectedApp.name}...\n`);
246
+ const nodesResponse = await api.listNodes(selectedApp.id);
247
+ appNodes = nodesResponse.data || [];
248
+
249
+ if (appNodes.length === 0) {
250
+ displayMessageBox(
251
+ 'No Nodes Available',
252
+ `No development nodes found for "${selectedApp.name}". Please create a development node in the GBOS web interface.`,
253
+ 'warning'
254
+ );
255
+ return;
256
+ }
257
+ }
205
258
 
206
259
  // Build node options
207
260
  const nodeOptions = appNodes.map((node) => ({
@@ -2,14 +2,18 @@ const path = require('path');
2
2
  const { displayImage, getTerminalWidth } = require('../lib/display');
3
3
 
4
4
  async function logoCommand() {
5
- const logoPath = path.join(__dirname, '../../images/logo-2.png');
5
+ const logoPath = path.join(__dirname, '../../images/logo.png');
6
6
  const terminalWidth = getTerminalWidth();
7
- const targetWidth = Math.min(60, Math.max(30, terminalWidth - 10));
7
+ const targetWidth = Math.max(16, Math.floor(terminalWidth * 0.2));
8
8
 
9
9
  await displayImage(logoPath, {
10
10
  width: targetWidth,
11
- fallbackWidth: 40,
12
- fallbackHeight: 12,
11
+ fallbackWidth: targetWidth,
12
+ fallbackHeight: 7,
13
+ sharp: false,
14
+ crop: true,
15
+ alphaThreshold: 200,
16
+ cropAlphaThreshold: 200,
13
17
  });
14
18
  }
15
19
 
package/src/lib/api.js CHANGED
@@ -81,6 +81,11 @@ class GbosApiClient {
81
81
  });
82
82
  }
83
83
 
84
+ // Application endpoints
85
+ async listApplications() {
86
+ return this.request('/cli/applications', { method: 'GET' });
87
+ }
88
+
84
89
  // Node endpoints
85
90
  async listNodes(applicationId = null) {
86
91
  let endpoint = '/cli/nodes';
@@ -57,15 +57,18 @@ function samplePixel(png, x, y) {
57
57
  }
58
58
 
59
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);
60
+ function isBackground(pixel, options = {}) {
61
+ const alphaThreshold = options.alphaThreshold ?? 50;
62
+ const backgroundLuminance = options.backgroundLuminance ?? 240;
63
+ return pixel.a < alphaThreshold ||
64
+ (pixel.r > backgroundLuminance && pixel.g > backgroundLuminance && pixel.b > backgroundLuminance);
62
65
  }
63
66
 
64
67
  // Shading characters for smooth edges (from light to full)
65
68
  const SHADE_CHARS = [' ', '░', '▒', '▓', '█'];
66
69
 
67
70
  // Get average alpha/coverage for a region (for anti-aliasing)
68
- function getRegionCoverage(png, startX, startY, width, height) {
71
+ function getRegionCoverage(png, startX, startY, width, height, backgroundOptions = {}) {
69
72
  let totalAlpha = 0;
70
73
  let totalR = 0, totalG = 0, totalB = 0;
71
74
  let samples = 0;
@@ -73,7 +76,7 @@ function getRegionCoverage(png, startX, startY, width, height) {
73
76
  for (let y = startY; y < startY + height; y++) {
74
77
  for (let x = startX; x < startX + width; x++) {
75
78
  const pixel = samplePixel(png, x, y);
76
- if (!isBackground(pixel)) {
79
+ if (!isBackground(pixel, backgroundOptions)) {
77
80
  totalAlpha += pixel.a;
78
81
  totalR += pixel.r;
79
82
  totalG += pixel.g;
@@ -94,30 +97,111 @@ function getRegionCoverage(png, startX, startY, width, height) {
94
97
  };
95
98
  }
96
99
 
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) {
100
+ function cropPng(png, alphaThreshold = 1) {
101
+ let minX = png.width;
102
+ let minY = png.height;
103
+ let maxX = -1;
104
+ let maxY = -1;
105
+
106
+ for (let y = 0; y < png.height; y++) {
107
+ for (let x = 0; x < png.width; x++) {
108
+ const idx = (png.width * y + x) << 2;
109
+ if (png.data[idx + 3] >= alphaThreshold) {
110
+ minX = Math.min(minX, x);
111
+ minY = Math.min(minY, y);
112
+ maxX = Math.max(maxX, x);
113
+ maxY = Math.max(maxY, y);
114
+ }
115
+ }
116
+ }
117
+
118
+ if (maxX < minX || maxY < minY) return png;
119
+
120
+ const width = maxX - minX + 1;
121
+ const height = maxY - minY + 1;
122
+ const cropped = new PNG({ width, height });
123
+
124
+ for (let y = 0; y < height; y++) {
125
+ const srcStart = ((minY + y) * png.width + minX) << 2;
126
+ const srcEnd = srcStart + (width << 2);
127
+ const destStart = (width * y) << 2;
128
+ png.data.copy(cropped.data, destStart, srcStart, srcEnd);
129
+ }
130
+
131
+ return cropped;
132
+ }
133
+
134
+ // Convert PNG to true-color pixel art
135
+ // Uses shading characters for anti-aliasing on edges when sampling coverage
136
+ function imageToPixels(imagePath, targetWidth = 24, targetHeight = 5, options = {}) {
137
+ const {
138
+ alphaThreshold = 50,
139
+ backgroundLuminance = 240,
140
+ sampleMode = 'coverage',
141
+ crop = false,
142
+ cropAlphaThreshold = 1,
143
+ } = options;
144
+
100
145
  try {
101
146
  const data = fs.readFileSync(imagePath);
102
- const png = PNG.sync.read(data);
147
+ let png = PNG.sync.read(data);
148
+ if (crop) {
149
+ png = cropPng(png, cropAlphaThreshold);
150
+ }
151
+
152
+ const resolvedWidth = Math.max(1, Math.round(targetWidth));
153
+ const resolvedHeight = targetHeight == null
154
+ ? Math.max(1, Math.round((resolvedWidth * png.height) / (png.width * 2)))
155
+ : Math.max(1, Math.round(targetHeight));
103
156
 
104
157
  // Each row of output = 2 rows of pixels (using half-blocks)
105
- const pixelRows = targetHeight * 2;
106
- const cellWidth = png.width / targetWidth;
158
+ const pixelRows = resolvedHeight * 2;
159
+ const cellWidth = png.width / resolvedWidth;
107
160
  const cellHeight = png.height / pixelRows;
161
+ const backgroundOptions = { alphaThreshold, backgroundLuminance };
108
162
 
109
163
  const lines = [];
110
164
 
111
- for (let row = 0; row < targetHeight; row++) {
165
+ for (let row = 0; row < resolvedHeight; row++) {
112
166
  let line = '';
113
- for (let col = 0; col < targetWidth; col++) {
167
+ for (let col = 0; col < resolvedWidth; col++) {
168
+ if (sampleMode === 'nearest') {
169
+ const topStartY = row * 2 * cellHeight;
170
+ const bottomStartY = (row * 2 + 1) * cellHeight;
171
+ const startX = col * cellWidth;
172
+
173
+ const topPixel = samplePixel(png, startX + cellWidth * 0.5, topStartY + cellHeight * 0.5);
174
+ const bottomPixel = samplePixel(png, startX + cellWidth * 0.5, bottomStartY + cellHeight * 0.5);
175
+ const topBg = isBackground(topPixel, backgroundOptions);
176
+ const bottomBg = isBackground(bottomPixel, backgroundOptions);
177
+
178
+ if (topBg && bottomBg) {
179
+ line += ' ';
180
+ continue;
181
+ }
182
+
183
+ if (!topBg && !bottomBg) {
184
+ line += fg(topPixel.r, topPixel.g, topPixel.b) +
185
+ bg(bottomPixel.r, bottomPixel.g, bottomPixel.b) +
186
+ UPPER_HALF + RESET;
187
+ continue;
188
+ }
189
+
190
+ if (!topBg) {
191
+ line += fg(topPixel.r, topPixel.g, topPixel.b) + UPPER_HALF + RESET;
192
+ } else {
193
+ line += fg(bottomPixel.r, bottomPixel.g, bottomPixel.b) + LOWER_HALF + RESET;
194
+ }
195
+ continue;
196
+ }
197
+
114
198
  // Get coverage for top and bottom halves of this cell
115
199
  const topStartY = row * 2 * cellHeight;
116
200
  const bottomStartY = (row * 2 + 1) * cellHeight;
117
201
  const startX = col * cellWidth;
118
202
 
119
- const topRegion = getRegionCoverage(png, startX, topStartY, cellWidth, cellHeight);
120
- const bottomRegion = getRegionCoverage(png, startX, bottomStartY, cellWidth, cellHeight);
203
+ const topRegion = getRegionCoverage(png, startX, topStartY, cellWidth, cellHeight, backgroundOptions);
204
+ const bottomRegion = getRegionCoverage(png, startX, bottomStartY, cellWidth, cellHeight, backgroundOptions);
121
205
 
122
206
  const topCov = topRegion.coverage;
123
207
  const bottomCov = bottomRegion.coverage;
@@ -178,29 +262,27 @@ function imageToPixels(imagePath, targetWidth = 24, targetHeight = 5) {
178
262
  }
179
263
 
180
264
  async function displayImage(imagePath, options = {}) {
181
- const fallbackWidth = options.fallbackWidth || 40;
182
- const fallbackHeight = options.fallbackHeight || 12;
183
- const renderOptions = { ...options };
184
- delete renderOptions.fallbackWidth;
185
- delete renderOptions.fallbackHeight;
186
-
187
- try {
188
- const terminalImage = await import('terminal-image');
189
- const renderer = terminalImage.default || terminalImage;
190
- const output = await renderer.file(imagePath, renderOptions);
191
- process.stdout.write(output);
192
- if (!output.endsWith('\n')) process.stdout.write('\n');
193
- return true;
194
- } catch (error) {
195
- const fallbackLines = imageToPixels(imagePath, fallbackWidth, fallbackHeight);
196
- if (!fallbackLines) {
197
- throw error;
198
- }
199
- console.log('');
200
- fallbackLines.forEach((line) => console.log(line));
201
- console.log('');
202
- return false;
265
+ const width = options.fallbackWidth ?? options.width ?? 40;
266
+ const height = options.fallbackHeight ?? options.height ?? 12;
267
+ const sharp = options.sharp ?? false;
268
+ const alphaThreshold = options.alphaThreshold ?? 50;
269
+ const crop = options.crop ?? false;
270
+ const cropAlphaThreshold = options.cropAlphaThreshold;
271
+ const backgroundLuminance = options.backgroundLuminance ?? 240;
272
+
273
+ const lines = imageToPixels(imagePath, width, height, {
274
+ sampleMode: sharp ? 'nearest' : 'coverage',
275
+ alphaThreshold,
276
+ backgroundLuminance,
277
+ crop,
278
+ cropAlphaThreshold: cropAlphaThreshold ?? alphaThreshold,
279
+ });
280
+ if (!lines) {
281
+ throw new Error('Unable to render image.');
203
282
  }
283
+ console.log('');
284
+ lines.forEach((line) => console.log(line));
285
+ console.log('');
204
286
  }
205
287
 
206
288
 
@@ -213,20 +295,25 @@ const COMPACT_LOGO = [
213
295
 
214
296
  // Display logo with connection details (Claude Code style - clean, minimal)
215
297
  function displayLogoWithDetails(details = null) {
216
- const logoPath = path.join(__dirname, '../../images/logo-2.png');
298
+ const logoPath = path.join(__dirname, '../../images/logo.png');
217
299
  const version = require('../../package.json').version;
218
300
 
219
- // Render logo at ~20 chars wide, 5 rows tall (smooth edges)
220
- let logoLines = imageToPixels(logoPath, 20, 5);
301
+ // Render logo at ~16 chars wide, 7 rows tall with smooth edges
302
+ let logoLines = imageToPixels(logoPath, 16, 7, {
303
+ alphaThreshold: 200,
304
+ crop: true,
305
+ cropAlphaThreshold: 200,
306
+ sampleMode: 'coverage',
307
+ });
221
308
  if (!logoLines) logoLines = COMPACT_LOGO;
222
309
 
223
- const logoWidth = 28; // Account for escape codes
310
+ const logoWidth = 22; // Account for escape codes
224
311
 
225
312
  // Build right side - Claude Code style (clean lines, no boxes)
226
313
  const rightLines = [];
227
314
 
228
315
  if (details) {
229
- rightLines.push(`${BOLD}${colors.purple5}GBOS${RESET} ${DIM}v${version}${RESET}`);
316
+ rightLines.push(`${BOLD}${colors.purple5}gbos.io${RESET} ${DIM}v${version}${RESET}`);
230
317
  rightLines.push(`${colors.white}${details.accountName || 'N/A'}${RESET} ${DIM}·${RESET} ${colors.purple5}${details.applicationName || 'N/A'}${RESET}`);
231
318
  rightLines.push(`${DIM}${details.nodeName || 'N/A'}${RESET}`);
232
319
  }
@@ -252,16 +339,21 @@ function displayLogo() {
252
339
  }
253
340
 
254
341
  function displayAuthSuccess(data) {
255
- const logoPath = path.join(__dirname, '../../images/logo-2.png');
342
+ const logoPath = path.join(__dirname, '../../images/logo.png');
256
343
  const version = require('../../package.json').version;
257
344
 
258
- let logoLines = imageToPixels(logoPath, 20, 5);
345
+ let logoLines = imageToPixels(logoPath, 16, 7, {
346
+ alphaThreshold: 200,
347
+ crop: true,
348
+ cropAlphaThreshold: 200,
349
+ sampleMode: 'coverage',
350
+ });
259
351
  if (!logoLines) logoLines = COMPACT_LOGO;
260
352
 
261
- const logoWidth = 28;
353
+ const logoWidth = 22;
262
354
 
263
355
  const rightLines = [];
264
- rightLines.push(`${BOLD}${colors.purple5}GBOS${RESET} ${DIM}v${version}${RESET}`);
356
+ rightLines.push(`${BOLD}${colors.purple5}gbos.io${RESET} ${DIM}v${version}${RESET}`);
265
357
  rightLines.push(`${colors.purple5}✓${RESET} ${colors.white}Authenticated${RESET}`);
266
358
  rightLines.push(`${colors.white}${data.userName || 'N/A'}${RESET} ${DIM}·${RESET} ${colors.purple5}${data.accountName || 'N/A'}${RESET}`);
267
359
  rightLines.push('');