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 +1 -0
- package/dist/cli.js +132 -21
- package/dist/client/app.global.js +2 -2
- package/package.json +2 -3
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
|
|
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
|
-
|
|
3253
|
-
|
|
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-
|
|
5268
|
+
console.log(`[debug] Build timestamp: ${"2026-03-16T22:15:39.732Z"}`);
|
|
5158
5269
|
}
|
|
5159
5270
|
if (projectDir) {
|
|
5160
5271
|
process.chdir(projectDir);
|