glassbox 0.2.3 → 0.2.5

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/dist/cli.js CHANGED
@@ -1510,6 +1510,54 @@ function getModeArgs(mode) {
1510
1510
  }
1511
1511
  }
1512
1512
 
1513
+ // src/lock.ts
1514
+ import { existsSync as existsSync2, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
1515
+ import { join as join3 } from "path";
1516
+ var lockPath = null;
1517
+ function acquireLock(dataDir2) {
1518
+ lockPath = join3(dataDir2, "glassbox.lock");
1519
+ if (existsSync2(lockPath)) {
1520
+ try {
1521
+ const contents = JSON.parse(readFileSync3(lockPath, "utf-8"));
1522
+ const pid = contents.pid;
1523
+ try {
1524
+ process.kill(pid, 0);
1525
+ console.error(`
1526
+ Error: Another Glassbox instance (PID ${pid}) is already running.`);
1527
+ console.error(` Data directory: ${dataDir2}`);
1528
+ console.error(` Stop that instance first, or wait for it to exit.
1529
+ `);
1530
+ process.exit(1);
1531
+ } catch {
1532
+ console.log(` Removing stale lock from PID ${pid}`);
1533
+ rmSync2(lockPath, { force: true });
1534
+ }
1535
+ } catch {
1536
+ rmSync2(lockPath, { force: true });
1537
+ }
1538
+ }
1539
+ writeFileSync2(lockPath, JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }));
1540
+ const cleanup = () => releaseLock();
1541
+ process.on("exit", cleanup);
1542
+ process.on("SIGINT", () => {
1543
+ cleanup();
1544
+ process.exit(0);
1545
+ });
1546
+ process.on("SIGTERM", () => {
1547
+ cleanup();
1548
+ process.exit(0);
1549
+ });
1550
+ }
1551
+ function releaseLock() {
1552
+ if (lockPath) {
1553
+ try {
1554
+ rmSync2(lockPath, { force: true });
1555
+ } catch {
1556
+ }
1557
+ lockPath = null;
1558
+ }
1559
+ }
1560
+
1513
1561
  // src/review-update.ts
1514
1562
  init_queries();
1515
1563
  function findLineContent(diff, lineNumber, side) {
@@ -1617,9 +1665,9 @@ async function updateReviewDiffs(reviewId, newDiffs, headCommit) {
1617
1665
  // src/server.ts
1618
1666
  import { serve } from "@hono/node-server";
1619
1667
  import { exec } from "child_process";
1620
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
1668
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
1621
1669
  import { Hono as Hono4 } from "hono";
1622
- import { dirname, join as join4 } from "path";
1670
+ import { dirname, join as join6 } from "path";
1623
1671
  import { fileURLToPath } from "url";
1624
1672
 
1625
1673
  // src/routes/ai-api.ts
@@ -2837,27 +2885,29 @@ aiApiRoutes.post("/preferences", async (c) => {
2837
2885
 
2838
2886
  // src/routes/api.ts
2839
2887
  init_queries();
2888
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
2840
2889
  import { Hono as Hono2 } from "hono";
2890
+ import { join as join5 } from "path";
2841
2891
 
2842
2892
  // src/export/generate.ts
2843
2893
  init_queries();
2844
2894
  import { execSync as execSync3 } from "child_process";
2845
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
2895
+ import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
2846
2896
  import { homedir as homedir3 } from "os";
2847
- import { join as join3 } from "path";
2848
- var DISMISS_FILE = join3(homedir3(), ".glassbox", "gitignore-dismissed.json");
2897
+ import { join as join4 } from "path";
2898
+ var DISMISS_FILE = join4(homedir3(), ".glassbox", "gitignore-dismissed.json");
2849
2899
  var DISMISS_DAYS = 30;
2850
2900
  function loadDismissals() {
2851
2901
  try {
2852
- return JSON.parse(readFileSync3(DISMISS_FILE, "utf-8"));
2902
+ return JSON.parse(readFileSync4(DISMISS_FILE, "utf-8"));
2853
2903
  } catch {
2854
2904
  return {};
2855
2905
  }
2856
2906
  }
2857
2907
  function saveDismissals(data) {
2858
- const dir = join3(homedir3(), ".glassbox");
2908
+ const dir = join4(homedir3(), ".glassbox");
2859
2909
  mkdirSync3(dir, { recursive: true });
2860
- writeFileSync2(DISMISS_FILE, JSON.stringify(data), "utf-8");
2910
+ writeFileSync3(DISMISS_FILE, JSON.stringify(data), "utf-8");
2861
2911
  }
2862
2912
  function isGlassboxGitignored(repoRoot) {
2863
2913
  try {
@@ -2878,16 +2928,16 @@ function shouldPromptGitignore(repoRoot) {
2878
2928
  return true;
2879
2929
  }
2880
2930
  function addGlassboxToGitignore(repoRoot) {
2881
- const gitignorePath = join3(repoRoot, ".gitignore");
2882
- if (existsSync2(gitignorePath)) {
2883
- const content = readFileSync3(gitignorePath, "utf-8");
2931
+ const gitignorePath = join4(repoRoot, ".gitignore");
2932
+ if (existsSync3(gitignorePath)) {
2933
+ const content = readFileSync4(gitignorePath, "utf-8");
2884
2934
  if (!content.endsWith("\n")) {
2885
2935
  appendFileSync(gitignorePath, "\n.glassbox/\n", "utf-8");
2886
2936
  } else {
2887
2937
  appendFileSync(gitignorePath, ".glassbox/\n", "utf-8");
2888
2938
  }
2889
2939
  } else {
2890
- writeFileSync2(gitignorePath, ".glassbox/\n", "utf-8");
2940
+ writeFileSync3(gitignorePath, ".glassbox/\n", "utf-8");
2891
2941
  }
2892
2942
  }
2893
2943
  function dismissGitignorePrompt(repoRoot) {
@@ -2896,16 +2946,16 @@ function dismissGitignorePrompt(repoRoot) {
2896
2946
  saveDismissals(dismissals);
2897
2947
  }
2898
2948
  function deleteReviewExport(reviewId, repoRoot) {
2899
- const exportDir = join3(repoRoot, ".glassbox");
2900
- const archivePath = join3(exportDir, `review-${reviewId}.md`);
2901
- if (existsSync2(archivePath)) unlinkSync(archivePath);
2949
+ const exportDir = join4(repoRoot, ".glassbox");
2950
+ const archivePath = join4(exportDir, `review-${reviewId}.md`);
2951
+ if (existsSync3(archivePath)) unlinkSync(archivePath);
2902
2952
  }
2903
2953
  async function generateReviewExport(reviewId, repoRoot, isCurrent) {
2904
2954
  const review = await getReview(reviewId);
2905
2955
  if (!review) throw new Error("Review not found");
2906
2956
  const files = await getReviewFiles(reviewId);
2907
2957
  const annotations = await getAnnotationsForReview(reviewId);
2908
- const exportDir = join3(repoRoot, ".glassbox");
2958
+ const exportDir = join4(repoRoot, ".glassbox");
2909
2959
  mkdirSync3(exportDir, { recursive: true });
2910
2960
  const byFile = {};
2911
2961
  for (const a of annotations) {
@@ -2971,11 +3021,11 @@ async function generateReviewExport(reviewId, repoRoot, isCurrent) {
2971
3021
  lines.push("6. **note** annotations are informational context. Consider them but they may not require code changes.");
2972
3022
  lines.push("");
2973
3023
  const content = lines.join("\n");
2974
- const archivePath = join3(exportDir, `review-${review.id}.md`);
2975
- writeFileSync2(archivePath, content, "utf-8");
3024
+ const archivePath = join4(exportDir, `review-${review.id}.md`);
3025
+ writeFileSync3(archivePath, content, "utf-8");
2976
3026
  if (isCurrent) {
2977
- const latestPath = join3(exportDir, "latest-review.md");
2978
- writeFileSync2(latestPath, content, "utf-8");
3027
+ const latestPath = join4(exportDir, "latest-review.md");
3028
+ writeFileSync3(latestPath, content, "utf-8");
2979
3029
  return latestPath;
2980
3030
  }
2981
3031
  return archivePath;
@@ -3473,6 +3523,33 @@ apiRoutes.get("/context/:fileId", async (c) => {
3473
3523
  }
3474
3524
  return c.json({ lines });
3475
3525
  });
3526
+ function readProjectSettings(repoRoot) {
3527
+ const settingsPath = join5(repoRoot, ".glassbox", "settings.json");
3528
+ try {
3529
+ if (existsSync4(settingsPath)) {
3530
+ return JSON.parse(readFileSync5(settingsPath, "utf-8"));
3531
+ }
3532
+ } catch {
3533
+ }
3534
+ return {};
3535
+ }
3536
+ function writeProjectSettings(repoRoot, settings) {
3537
+ const dir = join5(repoRoot, ".glassbox");
3538
+ mkdirSync4(dir, { recursive: true });
3539
+ writeFileSync4(join5(dir, "settings.json"), JSON.stringify(settings, null, 2), "utf-8");
3540
+ }
3541
+ apiRoutes.get("/project-settings", (c) => {
3542
+ const repoRoot = c.get("repoRoot");
3543
+ return c.json(readProjectSettings(repoRoot));
3544
+ });
3545
+ apiRoutes.patch("/project-settings", async (c) => {
3546
+ const repoRoot = c.get("repoRoot");
3547
+ const body = await c.req.json();
3548
+ const current = readProjectSettings(repoRoot);
3549
+ if (body.appName !== void 0) current.appName = body.appName || void 0;
3550
+ writeProjectSettings(repoRoot, current);
3551
+ return c.json(current);
3552
+ });
3476
3553
 
3477
3554
  // src/routes/pages.tsx
3478
3555
  import { Hono as Hono3 } from "hono";
@@ -3993,6 +4070,13 @@ pageRoutes.get("/", async (c) => {
3993
4070
  annotationCounts[f.id] = anns.length;
3994
4071
  }
3995
4072
  const html = /* @__PURE__ */ jsx(Layout, { title: `Glassbox - ${review.repo_name}`, reviewId, children: /* @__PURE__ */ jsx("div", { className: "review-app", "data-review-id": reviewId, children: [
4073
+ /* @__PURE__ */ jsx("div", { id: "update-banner", className: "update-banner", style: "display:none", children: [
4074
+ /* @__PURE__ */ jsx("span", { id: "update-banner-label", children: "Update available" }),
4075
+ /* @__PURE__ */ jsx("div", { className: "update-banner-actions", children: [
4076
+ /* @__PURE__ */ jsx("button", { id: "update-install-btn", className: "btn btn-sm btn-accent", children: "Install Update" }),
4077
+ /* @__PURE__ */ jsx("button", { id: "update-banner-dismiss", className: "btn btn-sm", children: "Later" })
4078
+ ] })
4079
+ ] }),
3996
4080
  /* @__PURE__ */ jsx("aside", { className: "sidebar", children: [
3997
4081
  /* @__PURE__ */ jsx("div", { className: "sidebar-header", children: [
3998
4082
  /* @__PURE__ */ jsx("h2", { children: review.repo_name }),
@@ -4058,6 +4142,13 @@ pageRoutes.get("/review/:reviewId", async (c) => {
4058
4142
  annotationCounts[f.id] = anns.length;
4059
4143
  }
4060
4144
  const html = /* @__PURE__ */ jsx(Layout, { title: `Glassbox - ${review.repo_name}`, reviewId, children: /* @__PURE__ */ jsx("div", { className: "review-app", "data-review-id": reviewId, children: [
4145
+ /* @__PURE__ */ jsx("div", { id: "update-banner", className: "update-banner", style: "display:none", children: [
4146
+ /* @__PURE__ */ jsx("span", { id: "update-banner-label", children: "Update available" }),
4147
+ /* @__PURE__ */ jsx("div", { className: "update-banner-actions", children: [
4148
+ /* @__PURE__ */ jsx("button", { id: "update-install-btn", className: "btn btn-sm btn-accent", children: "Install Update" }),
4149
+ /* @__PURE__ */ jsx("button", { id: "update-banner-dismiss", className: "btn btn-sm", children: "Later" })
4150
+ ] })
4151
+ ] }),
4061
4152
  /* @__PURE__ */ jsx("aside", { className: "sidebar", children: [
4062
4153
  /* @__PURE__ */ jsx("div", { className: "sidebar-header", children: [
4063
4154
  /* @__PURE__ */ jsx("h2", { children: review.repo_name }),
@@ -4132,13 +4223,13 @@ async function startServer(port, reviewId, repoRoot, options) {
4132
4223
  await next();
4133
4224
  });
4134
4225
  const selfDir = dirname(fileURLToPath(import.meta.url));
4135
- const distDir = existsSync3(join4(selfDir, "client", "styles.css")) ? join4(selfDir, "client") : join4(selfDir, "..", "dist", "client");
4226
+ const distDir = existsSync5(join6(selfDir, "client", "styles.css")) ? join6(selfDir, "client") : join6(selfDir, "..", "dist", "client");
4136
4227
  app.get("/static/styles.css", (c) => {
4137
- const css = readFileSync4(join4(distDir, "styles.css"), "utf-8");
4228
+ const css = readFileSync6(join6(distDir, "styles.css"), "utf-8");
4138
4229
  return c.text(css, 200, { "Content-Type": "text/css", "Cache-Control": "no-cache" });
4139
4230
  });
4140
4231
  app.get("/static/app.js", (c) => {
4141
- const js = readFileSync4(join4(distDir, "app.global.js"), "utf-8");
4232
+ const js = readFileSync6(join6(distDir, "app.global.js"), "utf-8");
4142
4233
  return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
4143
4234
  });
4144
4235
  app.route("/api", apiRoutes);
@@ -4174,18 +4265,18 @@ async function startServer(port, reviewId, repoRoot, options) {
4174
4265
  }
4175
4266
 
4176
4267
  // src/update-check.ts
4177
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
4268
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
4178
4269
  import { get } from "https";
4179
4270
  import { homedir as homedir4 } from "os";
4180
- import { dirname as dirname2, join as join5 } from "path";
4271
+ import { dirname as dirname2, join as join7 } from "path";
4181
4272
  import { fileURLToPath as fileURLToPath2 } from "url";
4182
- var DATA_DIR = join5(homedir4(), ".glassbox");
4183
- var CHECK_FILE = join5(DATA_DIR, "last-update-check");
4273
+ var DATA_DIR = join7(homedir4(), ".glassbox");
4274
+ var CHECK_FILE = join7(DATA_DIR, "last-update-check");
4184
4275
  var PACKAGE_NAME = "glassbox";
4185
4276
  function getCurrentVersion() {
4186
4277
  try {
4187
4278
  const dir = dirname2(fileURLToPath2(import.meta.url));
4188
- const pkg = JSON.parse(readFileSync5(join5(dir, "..", "package.json"), "utf-8"));
4279
+ const pkg = JSON.parse(readFileSync7(join7(dir, "..", "package.json"), "utf-8"));
4189
4280
  return pkg.version;
4190
4281
  } catch {
4191
4282
  return "0.0.0";
@@ -4193,16 +4284,16 @@ function getCurrentVersion() {
4193
4284
  }
4194
4285
  function getLastCheckDate() {
4195
4286
  try {
4196
- if (existsSync4(CHECK_FILE)) {
4197
- return readFileSync5(CHECK_FILE, "utf-8").trim();
4287
+ if (existsSync6(CHECK_FILE)) {
4288
+ return readFileSync7(CHECK_FILE, "utf-8").trim();
4198
4289
  }
4199
4290
  } catch {
4200
4291
  }
4201
4292
  return null;
4202
4293
  }
4203
4294
  function saveCheckDate() {
4204
- mkdirSync4(DATA_DIR, { recursive: true });
4205
- writeFileSync3(CHECK_FILE, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), "utf-8");
4295
+ mkdirSync5(DATA_DIR, { recursive: true });
4296
+ writeFileSync5(CHECK_FILE, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), "utf-8");
4206
4297
  }
4207
4298
  function isFirstUseToday() {
4208
4299
  const last = getLastCheckDate();
@@ -4422,11 +4513,19 @@ async function main() {
4422
4513
  console.log("AI service test mode enabled \u2014 using mock AI responses");
4423
4514
  }
4424
4515
  if (debug) {
4425
- console.log(`[debug] Build timestamp: ${"2026-03-13T08:33:54.919Z"}`);
4516
+ console.log(`[debug] Build timestamp: ${"2026-03-14T00:01:26.096Z"}`);
4426
4517
  }
4427
4518
  if (projectDir) {
4428
4519
  process.chdir(projectDir);
4429
4520
  }
4521
+ if (demo === null) {
4522
+ const { homedir: homedir5 } = await import("os");
4523
+ const { join: join8 } = await import("path");
4524
+ const { mkdirSync: mkdirSync6 } = await import("fs");
4525
+ const dataDir2 = join8(homedir5(), ".glassbox");
4526
+ mkdirSync6(dataDir2, { recursive: true });
4527
+ acquireLock(dataDir2);
4528
+ }
4430
4529
  if (demo !== null) {
4431
4530
  const scenario = DEMO_SCENARIOS.find((s) => s.id === demo);
4432
4531
  if (scenario === void 0) {