glassbox 0.5.1 → 0.6.0

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 +63 -91
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -426,7 +426,7 @@ function debugLog(...args) {
426
426
  }
427
427
 
428
428
  // src/ai/config.ts
429
- import { execSync } from "child_process";
429
+ import { spawnSync } from "child_process";
430
430
  import { chmodSync, existsSync, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
431
431
  import { homedir } from "os";
432
432
  import { join as join2 } from "path";
@@ -525,27 +525,21 @@ function getKeyFromKeychain(platform) {
525
525
  const account = `${platform}-api-key`;
526
526
  try {
527
527
  if (os === "darwin") {
528
- const result = execSync(
529
- `security find-generic-password -s glassbox -a "${account}" -w 2>/dev/null`,
530
- { encoding: "utf-8" }
531
- ).trim();
532
- return result !== "" ? result : null;
528
+ const r = spawnSync("security", ["find-generic-password", "-s", "glassbox", "-a", account, "-w"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
529
+ const result = (r.stdout ?? "").trim();
530
+ return r.status === 0 && result !== "" ? result : null;
533
531
  }
534
532
  if (os === "linux") {
535
- const result = execSync(
536
- `secret-tool lookup service glassbox account "${account}" 2>/dev/null`,
537
- { encoding: "utf-8" }
538
- ).trim();
539
- return result !== "" ? result : null;
533
+ const r = spawnSync("secret-tool", ["lookup", "service", "glassbox", "account", account], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
534
+ const result = (r.stdout ?? "").trim();
535
+ return r.status === 0 && result !== "" ? result : null;
540
536
  }
541
537
  if (os === "win32") {
542
538
  const target = winCredTarget(platform);
543
539
  const script = WIN_CRED_READ_PS + `Write-Output ([CredHelper]::Read('${target}'))`;
544
- const result = execSync("powershell -NoProfile -Command -", {
545
- input: script,
546
- encoding: "utf-8"
547
- }).trim();
548
- return result !== "" ? result : null;
540
+ const r = spawnSync("powershell", ["-NoProfile", "-Command", "-"], { input: script, encoding: "utf-8" });
541
+ const result = (r.stdout ?? "").trim();
542
+ return r.status === 0 && result !== "" ? result : null;
549
543
  }
550
544
  } catch {
551
545
  return null;
@@ -600,30 +594,19 @@ function saveKeyToKeychain(platform, key) {
600
594
  const os = process.platform;
601
595
  const account = `${platform}-api-key`;
602
596
  if (os === "darwin") {
603
- try {
604
- execSync(`security delete-generic-password -s glassbox -a "${account}" 2>/dev/null`);
605
- } catch {
606
- }
607
- execSync(
608
- `security add-generic-password -s glassbox -a "${account}" -w "${key.replace(/"/g, '\\"')}"`
609
- );
597
+ spawnSync("security", ["delete-generic-password", "-s", "glassbox", "-a", account], { stdio: "pipe" });
598
+ spawnSync("security", ["add-generic-password", "-s", "glassbox", "-a", account, "-w", key]);
610
599
  return;
611
600
  }
612
601
  if (os === "linux") {
613
- execSync(
614
- `secret-tool store --label='Glassbox API Key' service glassbox account "${account}"`,
615
- { input: key, encoding: "utf-8" }
616
- );
602
+ spawnSync("secret-tool", ["store", "--label=Glassbox API Key", "service", "glassbox", "account", account], { input: key, encoding: "utf-8" });
617
603
  return;
618
604
  }
619
605
  if (os === "win32") {
620
606
  const target = winCredTarget(platform);
621
607
  const escapedKey = key.replace(/'/g, "''");
622
608
  const script = `cmdkey /generic:'${target}' /user:'glassbox' /pass:'${escapedKey}'`;
623
- execSync("powershell -NoProfile -Command -", {
624
- input: script,
625
- encoding: "utf-8"
626
- });
609
+ spawnSync("powershell", ["-NoProfile", "-Command", "-"], { input: script, encoding: "utf-8" });
627
610
  }
628
611
  }
629
612
  function deleteAPIKey(platform) {
@@ -631,15 +614,12 @@ function deleteAPIKey(platform) {
631
614
  const account = `${platform}-api-key`;
632
615
  try {
633
616
  if (os === "darwin") {
634
- execSync(`security delete-generic-password -s glassbox -a "${account}" 2>/dev/null`);
617
+ spawnSync("security", ["delete-generic-password", "-s", "glassbox", "-a", account], { stdio: "pipe" });
635
618
  } else if (os === "linux") {
636
- execSync(`secret-tool clear service glassbox account "${account}" 2>/dev/null`);
619
+ spawnSync("secret-tool", ["clear", "service", "glassbox", "account", account], { stdio: "pipe" });
637
620
  } else if (os === "win32") {
638
621
  const target = winCredTarget(platform);
639
- execSync("powershell -NoProfile -Command -", {
640
- input: `cmdkey /delete:'${target}'`,
641
- encoding: "utf-8"
642
- });
622
+ spawnSync("powershell", ["-NoProfile", "-Command", "-"], { input: `cmdkey /delete:'${target}'`, encoding: "utf-8" });
643
623
  }
644
624
  } catch {
645
625
  }
@@ -663,12 +643,7 @@ function isKeychainAvailable() {
663
643
  const os = process.platform;
664
644
  if (os === "darwin" || os === "win32") return true;
665
645
  if (os === "linux") {
666
- try {
667
- execSync("which secret-tool 2>/dev/null", { encoding: "utf-8" });
668
- return true;
669
- } catch {
670
- return false;
671
- }
646
+ return spawnSync("which", ["secret-tool"], { stdio: "pipe" }).status === 0;
672
647
  }
673
648
  return false;
674
649
  }
@@ -1291,20 +1266,21 @@ async function setupAnnotations(fileIdMap) {
1291
1266
  }
1292
1267
 
1293
1268
  // src/git/diff.ts
1294
- import { execSync as execSync2 } from "child_process";
1269
+ import { spawnSync as spawnSync2 } from "child_process";
1295
1270
  import { readFileSync as readFileSync2 } from "fs";
1296
1271
  import { resolve } from "path";
1297
1272
  function git(args, cwd) {
1298
- try {
1299
- return execSync2(`git ${args}`, { cwd, encoding: "utf-8", maxBuffer: 50 * 1024 * 1024 });
1300
- } catch (e) {
1301
- const err = e;
1302
- if (err.stdout !== void 0 && err.stdout !== "") return err.stdout;
1303
- throw e;
1304
- }
1273
+ const result = spawnSync2("git", args, { cwd, encoding: "utf-8", maxBuffer: 50 * 1024 * 1024 });
1274
+ if (result.status === 0) return result.stdout;
1275
+ if (result.stdout !== "") return result.stdout;
1276
+ const err = new Error(result.stderr);
1277
+ err.stdout = result.stdout;
1278
+ err.stderr = result.stderr;
1279
+ err.status = result.status;
1280
+ throw err;
1305
1281
  }
1306
1282
  function getRepoRoot(cwd) {
1307
- return git("rev-parse --show-toplevel", cwd).trim();
1283
+ return git(["rev-parse", "--show-toplevel"], cwd).trim();
1308
1284
  }
1309
1285
  function getRepoName(cwd) {
1310
1286
  const root = getRepoRoot(cwd);
@@ -1312,7 +1288,7 @@ function getRepoName(cwd) {
1312
1288
  }
1313
1289
  function isGitRepo(cwd) {
1314
1290
  try {
1315
- git("rev-parse --is-inside-work-tree", cwd);
1291
+ git(["rev-parse", "--is-inside-work-tree"], cwd);
1316
1292
  return true;
1317
1293
  } catch {
1318
1294
  return false;
@@ -1321,22 +1297,21 @@ function isGitRepo(cwd) {
1321
1297
  function getDiffArgs(mode) {
1322
1298
  switch (mode.type) {
1323
1299
  case "uncommitted":
1324
- return "diff HEAD";
1300
+ return ["diff", "HEAD"];
1325
1301
  case "staged":
1326
- return "diff --cached";
1302
+ return ["diff", "--cached"];
1327
1303
  case "unstaged":
1328
- return "diff";
1304
+ return ["diff"];
1329
1305
  case "commit":
1330
- return `diff ${mode.sha}~1 ${mode.sha}`;
1306
+ return ["diff", `${mode.sha}~1`, mode.sha];
1331
1307
  case "range":
1332
- return `diff ${mode.from} ${mode.to}`;
1333
- case "branch": {
1334
- return `diff ${mode.name}...HEAD`;
1335
- }
1308
+ return ["diff", mode.from, mode.to];
1309
+ case "branch":
1310
+ return ["diff", `${mode.name}...HEAD`];
1336
1311
  case "files":
1337
- return `diff HEAD -- ${mode.patterns.join(" ")}`;
1312
+ return ["diff", "HEAD", "--", ...mode.patterns];
1338
1313
  case "all":
1339
- return "diff --no-index /dev/null .";
1314
+ return ["diff", "--no-index", "/dev/null", "."];
1340
1315
  }
1341
1316
  }
1342
1317
  function getFileDiffs(mode, cwd) {
@@ -1347,13 +1322,13 @@ function getFileDiffs(mode, cwd) {
1347
1322
  const diffArgs = getDiffArgs(mode);
1348
1323
  let rawDiff;
1349
1324
  try {
1350
- rawDiff = git(`${diffArgs} -U3`, repoRoot);
1325
+ rawDiff = git([...diffArgs, "-U3"], repoRoot);
1351
1326
  } catch {
1352
1327
  rawDiff = "";
1353
1328
  }
1354
1329
  const diffs = parseDiff(rawDiff);
1355
1330
  if (mode.type === "uncommitted") {
1356
- const untracked = git("ls-files --others --exclude-standard", repoRoot).trim();
1331
+ const untracked = git(["ls-files", "--others", "--exclude-standard"], repoRoot).trim();
1357
1332
  if (untracked) {
1358
1333
  for (const file of untracked.split("\n").filter(Boolean)) {
1359
1334
  if (!diffs.some((d) => d.filePath === file)) {
@@ -1365,7 +1340,7 @@ function getFileDiffs(mode, cwd) {
1365
1340
  return diffs;
1366
1341
  }
1367
1342
  function getAllFiles(repoRoot) {
1368
- const files = git("ls-files", repoRoot).trim().split("\n").filter(Boolean);
1343
+ const files = git(["ls-files"], repoRoot).trim().split("\n").filter(Boolean);
1369
1344
  return files.map((file) => createNewFileDiff(file, repoRoot));
1370
1345
  }
1371
1346
  function createNewFileDiff(filePath, repoRoot) {
@@ -1492,15 +1467,15 @@ function getFileContent(filePath, ref, cwd) {
1492
1467
  const repoRoot = getRepoRoot(cwd);
1493
1468
  try {
1494
1469
  if (ref === "working") {
1495
- return execSync2(`cat "${resolve(repoRoot, filePath)}"`, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
1470
+ return readFileSync2(resolve(repoRoot, filePath), "utf-8");
1496
1471
  }
1497
- return git(`show ${ref}:${filePath}`, repoRoot);
1472
+ return git(["show", `${ref}:${filePath}`], repoRoot);
1498
1473
  } catch {
1499
1474
  return "";
1500
1475
  }
1501
1476
  }
1502
1477
  function getHeadCommit(cwd) {
1503
- return execSync2("git rev-parse HEAD", { cwd, encoding: "utf-8" }).trim();
1478
+ return spawnSync2("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8" }).stdout.trim();
1504
1479
  }
1505
1480
  function parseModeString(modeStr) {
1506
1481
  if (modeStr === "uncommitted") return { type: "uncommitted" };
@@ -1521,9 +1496,12 @@ function getSingleFileDiff(mode, filePath, repoRoot, extraFlags = "") {
1521
1496
  return createNewFileDiff(filePath, repoRoot);
1522
1497
  }
1523
1498
  const diffArgs = getDiffArgs(mode);
1499
+ const args = [...diffArgs, "-U3"];
1500
+ if (extraFlags) args.push(...extraFlags.split(" ").filter(Boolean));
1501
+ args.push("--", filePath);
1524
1502
  let rawDiff;
1525
1503
  try {
1526
- rawDiff = git(`${diffArgs} -U3 ${extraFlags} -- ${filePath}`, repoRoot);
1504
+ rawDiff = git(args, repoRoot);
1527
1505
  } catch {
1528
1506
  rawDiff = "";
1529
1507
  }
@@ -2943,14 +2921,14 @@ aiApiRoutes.post("/preferences", async (c) => {
2943
2921
 
2944
2922
  // src/routes/api.ts
2945
2923
  init_queries();
2946
- import { execSync as execSync5 } from "child_process";
2924
+ import { execFileSync, spawnSync as spawnSync5 } from "child_process";
2947
2925
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2948
2926
  import { Hono as Hono2 } from "hono";
2949
2927
  import { join as join6, resolve as resolve3 } from "path";
2950
2928
 
2951
2929
  // src/export/generate.ts
2952
2930
  init_queries();
2953
- import { execSync as execSync3 } from "child_process";
2931
+ import { spawnSync as spawnSync3 } from "child_process";
2954
2932
  import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
2955
2933
  import { homedir as homedir2 } from "os";
2956
2934
  import { join as join4 } from "path";
@@ -2969,12 +2947,8 @@ function saveDismissals(data) {
2969
2947
  writeFileSync3(DISMISS_FILE, JSON.stringify(data), "utf-8");
2970
2948
  }
2971
2949
  function isGlassboxGitignored(repoRoot) {
2972
- try {
2973
- execSync3("git check-ignore -q .glassbox", { cwd: repoRoot, stdio: "pipe" });
2974
- return true;
2975
- } catch {
2976
- return false;
2977
- }
2950
+ const result = spawnSync3("git", ["check-ignore", "-q", ".glassbox"], { cwd: repoRoot, stdio: "pipe" });
2951
+ return result.status === 0;
2978
2952
  }
2979
2953
  function shouldPromptGitignore(repoRoot) {
2980
2954
  if (isGlassboxGitignored(repoRoot)) return false;
@@ -3102,7 +3076,7 @@ function scheduleAutoExport(reviewId, repoRoot) {
3102
3076
  }
3103
3077
 
3104
3078
  // src/git/image.ts
3105
- import { execSync as execSync4 } from "child_process";
3079
+ import { spawnSync as spawnSync4 } from "child_process";
3106
3080
  import { readFileSync as readFileSync5 } from "fs";
3107
3081
  import { resolve as resolve2 } from "path";
3108
3082
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"]);
@@ -3176,12 +3150,10 @@ function getNewRef(mode) {
3176
3150
  }
3177
3151
  }
3178
3152
  function gitShowFile(ref, filePath, repoRoot) {
3179
- try {
3180
- const spec = ref === ":" ? `:${filePath}` : `${ref}:${filePath}`;
3181
- return execSync4(`git show "${spec}"`, { cwd: repoRoot, maxBuffer: 50 * 1024 * 1024 });
3182
- } catch {
3183
- return null;
3184
- }
3153
+ const spec = ref === ":" ? `:${filePath}` : `${ref}:${filePath}`;
3154
+ const result = spawnSync4("git", ["show", spec], { cwd: repoRoot, maxBuffer: 50 * 1024 * 1024 });
3155
+ if (result.status !== 0 || result.stdout.length === 0) return null;
3156
+ return result.stdout;
3185
3157
  }
3186
3158
  function readWorkingFile(filePath, repoRoot) {
3187
3159
  try {
@@ -3976,11 +3948,11 @@ apiRoutes.post("/files/:fileId/reveal", async (c) => {
3976
3948
  const fullPath = resolve3(repoRoot, file.file_path);
3977
3949
  try {
3978
3950
  if (process.platform === "darwin") {
3979
- execSync5(`open -R "${fullPath}"`);
3951
+ execFileSync("open", ["-R", fullPath]);
3980
3952
  } else if (process.platform === "win32") {
3981
- execSync5(`explorer /select,"${fullPath}"`);
3953
+ execFileSync("explorer", ["/select," + fullPath]);
3982
3954
  } else {
3983
- execSync5(`xdg-open "${resolve3(fullPath, "..")}"`);
3955
+ execFileSync("xdg-open", [resolve3(fullPath, "..")]);
3984
3956
  }
3985
3957
  } catch {
3986
3958
  }
@@ -4084,7 +4056,7 @@ apiRoutes.get("/symbol-definition", async (c) => {
4084
4056
  }
4085
4057
  if (definitions.length === 0) {
4086
4058
  try {
4087
- const allFiles = execSync5("git ls-files", { cwd: repoRoot, encoding: "utf-8" }).trim().split("\n").filter(Boolean);
4059
+ const allFiles = spawnSync5("git", ["ls-files"], { cwd: repoRoot, encoding: "utf-8" }).stdout.trim().split("\n").filter(Boolean);
4088
4060
  for (const filePath of allFiles) {
4089
4061
  if (searchedPaths.has(filePath)) continue;
4090
4062
  const ext = filePath.slice(filePath.lastIndexOf("."));
@@ -5368,7 +5340,7 @@ pageRoutes.get("/history", async (c) => {
5368
5340
  // src/server.ts
5369
5341
  function tryServe(fetch2, port) {
5370
5342
  return new Promise((resolve6, reject) => {
5371
- const server = serve({ fetch: fetch2, port });
5343
+ const server = serve({ fetch: fetch2, port, hostname: "127.0.0.1" });
5372
5344
  server.on("listening", () => {
5373
5345
  resolve6(port);
5374
5346
  });
@@ -5807,7 +5779,7 @@ async function main() {
5807
5779
  console.log("AI service test mode enabled \u2014 using mock AI responses");
5808
5780
  }
5809
5781
  if (debug) {
5810
- console.log(`[debug] Build timestamp: ${"2026-03-26T23:51:09.647Z"}`);
5782
+ console.log(`[debug] Build timestamp: ${"2026-03-27T03:39:52.371Z"}`);
5811
5783
  }
5812
5784
  if (projectDir) {
5813
5785
  process.chdir(projectDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glassbox",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
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",