glassbox 0.3.1 → 0.3.3

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/README.md CHANGED
@@ -315,6 +315,7 @@ npm link # Symlink for global 'glassbox' command
315
315
  ## See Also
316
316
 
317
317
  - **[Hot Sheet](https://github.com/brianwestphal/hotsheet)** — Lightweight local project management for AI-assisted development. Pairs well with Glassbox for tracking review feedback as actionable tickets.
318
+ - **[Changelog](CHANGELOG.md)** — Full release history
318
319
 
319
320
  ## License
320
321
 
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-16T22:15:39.732Z"}`);
5158
5269
  }
5159
5270
  if (projectDir) {
5160
5271
  process.chdir(projectDir);