glassbox 0.2.3 → 0.2.4
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 +132 -33
- package/dist/client/app.global.js +8 -8
- package/dist/client/styles.css +1 -1
- package/package.json +2 -1
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
|
|
1668
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
1621
1669
|
import { Hono as Hono4 } from "hono";
|
|
1622
|
-
import { dirname, join as
|
|
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
|
|
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
|
|
2848
|
-
var DISMISS_FILE =
|
|
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(
|
|
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 =
|
|
2908
|
+
const dir = join4(homedir3(), ".glassbox");
|
|
2859
2909
|
mkdirSync3(dir, { recursive: true });
|
|
2860
|
-
|
|
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 =
|
|
2882
|
-
if (
|
|
2883
|
-
const content =
|
|
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
|
-
|
|
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 =
|
|
2900
|
-
const archivePath =
|
|
2901
|
-
if (
|
|
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 =
|
|
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 =
|
|
2975
|
-
|
|
3024
|
+
const archivePath = join4(exportDir, `review-${review.id}.md`);
|
|
3025
|
+
writeFileSync3(archivePath, content, "utf-8");
|
|
2976
3026
|
if (isCurrent) {
|
|
2977
|
-
const latestPath =
|
|
2978
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
4271
|
+
import { dirname as dirname2, join as join7 } from "path";
|
|
4181
4272
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4182
|
-
var DATA_DIR =
|
|
4183
|
-
var CHECK_FILE =
|
|
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(
|
|
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 (
|
|
4197
|
-
return
|
|
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
|
-
|
|
4205
|
-
|
|
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-
|
|
4516
|
+
console.log(`[debug] Build timestamp: ${"2026-03-13T15:08:50.826Z"}`);
|
|
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) {
|