glassbox 0.3.1 → 0.3.2

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/dist/cli.js +132 -21
  2. package/package.json +2 -3
package/dist/cli.js CHANGED
@@ -3238,27 +3238,10 @@ async function extractMetadata(data, filePath) {
3238
3238
  exif: null
3239
3239
  };
3240
3240
  }
3241
- const sharp = (await import("sharp")).default;
3242
- const meta = await sharp(data).metadata();
3243
- let exif = null;
3244
- if (meta.exif) {
3245
- try {
3246
- exif = {};
3247
- if (meta.orientation) exif["Orientation"] = String(meta.orientation);
3248
- } catch {
3249
- }
3250
- }
3241
+ const parsed = parseImageHeaders(data, ext);
3251
3242
  return {
3252
- format: meta.format ?? ext.slice(1),
3253
- width: meta.width ?? null,
3254
- height: meta.height ?? null,
3255
- fileSize: data.length,
3256
- colorSpace: meta.space ?? null,
3257
- channels: meta.channels ?? null,
3258
- depth: meta.depth ?? null,
3259
- hasAlpha: meta.hasAlpha ?? null,
3260
- density: meta.density ?? null,
3261
- exif
3243
+ ...parsed,
3244
+ fileSize: data.length
3262
3245
  };
3263
3246
  }
3264
3247
  function formatMetadataLines(meta) {
@@ -3285,6 +3268,134 @@ function formatBytes(bytes) {
3285
3268
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3286
3269
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3287
3270
  }
3271
+ function parseImageHeaders(data, ext) {
3272
+ try {
3273
+ if (ext === ".png") return parsePng(data);
3274
+ if (ext === ".jpg" || ext === ".jpeg") return parseJpeg(data);
3275
+ if (ext === ".gif") return parseGif(data);
3276
+ if (ext === ".webp") return parseWebp(data);
3277
+ } catch {
3278
+ }
3279
+ return emptyMeta(ext.slice(1));
3280
+ }
3281
+ function emptyMeta(format) {
3282
+ return { format, width: null, height: null, colorSpace: null, channels: null, depth: null, hasAlpha: null, density: null, exif: null };
3283
+ }
3284
+ function parsePng(data) {
3285
+ if (data.length < 26) return emptyMeta("png");
3286
+ const width = data.readUInt32BE(16);
3287
+ const height = data.readUInt32BE(20);
3288
+ const bitDepth = data[24];
3289
+ const colorType = data[25];
3290
+ let colorSpace = null;
3291
+ let channels = null;
3292
+ let hasAlpha = null;
3293
+ switch (colorType) {
3294
+ case 0:
3295
+ colorSpace = "grayscale";
3296
+ channels = 1;
3297
+ hasAlpha = false;
3298
+ break;
3299
+ case 2:
3300
+ colorSpace = "srgb";
3301
+ channels = 3;
3302
+ hasAlpha = false;
3303
+ break;
3304
+ case 3:
3305
+ colorSpace = "indexed";
3306
+ channels = 1;
3307
+ hasAlpha = false;
3308
+ break;
3309
+ case 4:
3310
+ colorSpace = "grayscale";
3311
+ channels = 2;
3312
+ hasAlpha = true;
3313
+ break;
3314
+ case 6:
3315
+ colorSpace = "srgb";
3316
+ channels = 4;
3317
+ hasAlpha = true;
3318
+ break;
3319
+ }
3320
+ const density = parsePngDensity(data);
3321
+ return { format: "png", width, height, colorSpace, channels, depth: bitDepth ? String(bitDepth) : null, hasAlpha, density, exif: null };
3322
+ }
3323
+ function parsePngDensity(data) {
3324
+ const marker = Buffer.from("pHYs");
3325
+ const idx = data.indexOf(marker);
3326
+ if (idx === -1 || idx + 13 > data.length) return null;
3327
+ const unit = data[idx + 12];
3328
+ if (unit !== 1) return null;
3329
+ const ppuX = data.readUInt32BE(idx + 4);
3330
+ return Math.round(ppuX / 39.3701);
3331
+ }
3332
+ function parseJpeg(data) {
3333
+ let width = null;
3334
+ let height = null;
3335
+ let channels = null;
3336
+ let depth = null;
3337
+ let density = null;
3338
+ let i = 2;
3339
+ while (i < data.length - 1) {
3340
+ if (data[i] !== 255) {
3341
+ i++;
3342
+ continue;
3343
+ }
3344
+ const marker = data[i + 1];
3345
+ if (marker >= 192 && marker <= 195 || marker >= 197 && marker <= 199 || marker >= 201 && marker <= 203 || marker >= 205 && marker <= 207) {
3346
+ if (i + 9 < data.length) {
3347
+ depth = String(data[i + 4]);
3348
+ height = data.readUInt16BE(i + 5);
3349
+ width = data.readUInt16BE(i + 7);
3350
+ channels = data[i + 9];
3351
+ }
3352
+ break;
3353
+ }
3354
+ if (marker === 224 && i + 14 < data.length) {
3355
+ const units = data[i + 11];
3356
+ const xDensity = data.readUInt16BE(i + 12);
3357
+ if (units === 1) density = xDensity;
3358
+ else if (units === 2) density = Math.round(xDensity * 2.54);
3359
+ }
3360
+ if (i + 3 < data.length) {
3361
+ const len = data.readUInt16BE(i + 2);
3362
+ i += 2 + len;
3363
+ } else {
3364
+ break;
3365
+ }
3366
+ }
3367
+ const colorSpace = channels === 1 ? "grayscale" : channels === 3 ? "srgb" : null;
3368
+ return { format: "jpeg", width, height, colorSpace, channels, depth, hasAlpha: false, density, exif: null };
3369
+ }
3370
+ function parseGif(data) {
3371
+ if (data.length < 10) return emptyMeta("gif");
3372
+ const width = data.readUInt16LE(6);
3373
+ const height = data.readUInt16LE(8);
3374
+ return { format: "gif", width, height, colorSpace: "indexed", channels: null, depth: null, hasAlpha: null, density: null, exif: null };
3375
+ }
3376
+ function parseWebp(data) {
3377
+ if (data.length < 30) return emptyMeta("webp");
3378
+ let width = null;
3379
+ let height = null;
3380
+ let hasAlpha = null;
3381
+ const chunk = data.toString("ascii", 12, 16);
3382
+ if (chunk === "VP8 " && data.length >= 30) {
3383
+ width = data.readUInt16LE(26) & 16383;
3384
+ height = data.readUInt16LE(28) & 16383;
3385
+ hasAlpha = false;
3386
+ } else if (chunk === "VP8L" && data.length >= 25) {
3387
+ const bits = data.readUInt32LE(21);
3388
+ width = (bits & 16383) + 1;
3389
+ height = (bits >> 14 & 16383) + 1;
3390
+ hasAlpha = (bits >> 28 & 1) === 1;
3391
+ } else if (chunk === "VP8X" && data.length >= 30) {
3392
+ const flags = data[20];
3393
+ hasAlpha = (flags & 16) !== 0;
3394
+ width = (data[24] | data[25] << 8 | data[26] << 16) + 1;
3395
+ height = (data[27] | data[28] << 8 | data[29] << 16) + 1;
3396
+ }
3397
+ return { format: "webp", width, height, colorSpace: "srgb", channels: hasAlpha ? 4 : 3, depth: null, hasAlpha, density: null, exif: null };
3398
+ }
3288
3399
 
3289
3400
  // src/outline/parser.ts
3290
3401
  var BRACE_LANGS = /* @__PURE__ */ new Set([
@@ -5154,7 +5265,7 @@ async function main() {
5154
5265
  console.log("AI service test mode enabled \u2014 using mock AI responses");
5155
5266
  }
5156
5267
  if (debug) {
5157
- console.log(`[debug] Build timestamp: ${"2026-03-16T09:22:20.931Z"}`);
5268
+ console.log(`[debug] Build timestamp: ${"2026-03-16T09:50:09.819Z"}`);
5158
5269
  }
5159
5270
  if (projectDir) {
5160
5271
  process.chdir(projectDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glassbox",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "A local code review tool for AI-generated code. Review diffs, annotate lines, and export structured feedback that AI tools can act on.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -51,8 +51,7 @@
51
51
  "dependencies": {
52
52
  "@electric-sql/pglite": "^0.3.15",
53
53
  "@hono/node-server": "^1.19.9",
54
- "hono": "^4.12.2",
55
- "sharp": "^0.34.5"
54
+ "hono": "^4.12.2"
56
55
  },
57
56
  "devDependencies": {
58
57
  "@eslint/js": "^9.39.3",