glassbox 0.2.6 → 0.2.8
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 +5 -1
- package/dist/cli.js +57 -40
- package/dist/client/app.global.js +8 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -291,7 +291,7 @@ Point the tool at the file. The export includes an "Instructions for AI Tools" s
|
|
|
291
291
|
| Database | PGLite (embedded PostgreSQL) |
|
|
292
292
|
| UI | Custom server-side JSX (no React), vanilla client JS |
|
|
293
293
|
| Build | tsup (single-file bundle) |
|
|
294
|
-
| Storage |
|
|
294
|
+
| Storage | `.glassbox/data/` (local to each project) |
|
|
295
295
|
|
|
296
296
|
Data stays local. The only network calls are an optional once-per-day npm update check and AI analysis requests if you opt in.
|
|
297
297
|
|
|
@@ -312,6 +312,10 @@ npm run clean # Remove dist and caches
|
|
|
312
312
|
npm link # Symlink for global 'glassbox' command
|
|
313
313
|
```
|
|
314
314
|
|
|
315
|
+
## See Also
|
|
316
|
+
|
|
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
|
+
|
|
315
319
|
## License
|
|
316
320
|
|
|
317
321
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -96,16 +96,22 @@ var init_schema = __esm({
|
|
|
96
96
|
// src/db/connection.ts
|
|
97
97
|
var connection_exports = {};
|
|
98
98
|
__export(connection_exports, {
|
|
99
|
-
getDb: () => getDb
|
|
99
|
+
getDb: () => getDb,
|
|
100
|
+
setDataDir: () => setDataDir
|
|
100
101
|
});
|
|
101
102
|
import { PGlite } from "@electric-sql/pglite";
|
|
102
103
|
import { mkdirSync, rmSync } from "fs";
|
|
103
|
-
import { homedir } from "os";
|
|
104
104
|
import { join } from "path";
|
|
105
|
+
function setDataDir(dataDir) {
|
|
106
|
+
const dbDir = join(dataDir, "data");
|
|
107
|
+
mkdirSync(dbDir, { recursive: true });
|
|
108
|
+
currentDbPath = join(dbDir, "reviews");
|
|
109
|
+
}
|
|
105
110
|
async function getDb() {
|
|
106
111
|
if (db) return db;
|
|
112
|
+
if (currentDbPath === null) throw new Error("Data directory not set. Call setDataDir() first.");
|
|
107
113
|
try {
|
|
108
|
-
db = new PGlite(
|
|
114
|
+
db = new PGlite(currentDbPath);
|
|
109
115
|
await db.waitReady;
|
|
110
116
|
await initSchema(db);
|
|
111
117
|
return db;
|
|
@@ -116,10 +122,10 @@ async function getDb() {
|
|
|
116
122
|
console.error("Database appears to be corrupt. Recreating...");
|
|
117
123
|
console.error("(Previous review data will be lost.)");
|
|
118
124
|
try {
|
|
119
|
-
rmSync(
|
|
125
|
+
rmSync(currentDbPath, { recursive: true, force: true });
|
|
120
126
|
} catch {
|
|
121
127
|
}
|
|
122
|
-
db = new PGlite(
|
|
128
|
+
db = new PGlite(currentDbPath);
|
|
123
129
|
await db.waitReady;
|
|
124
130
|
await initSchema(db);
|
|
125
131
|
return db;
|
|
@@ -149,15 +155,13 @@ async function addColumnIfMissing(db2, table, column, definition) {
|
|
|
149
155
|
await db2.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
150
156
|
}
|
|
151
157
|
}
|
|
152
|
-
var
|
|
158
|
+
var db, currentDbPath;
|
|
153
159
|
var init_connection = __esm({
|
|
154
160
|
"src/db/connection.ts"() {
|
|
155
161
|
"use strict";
|
|
156
162
|
init_schema();
|
|
157
|
-
dataDir = join(homedir(), ".glassbox", "data");
|
|
158
|
-
mkdirSync(dataDir, { recursive: true });
|
|
159
|
-
dbPath = join(dataDir, "reviews");
|
|
160
163
|
db = null;
|
|
164
|
+
currentDbPath = null;
|
|
161
165
|
}
|
|
162
166
|
});
|
|
163
167
|
|
|
@@ -382,6 +386,10 @@ var init_queries = __esm({
|
|
|
382
386
|
|
|
383
387
|
// src/cli.ts
|
|
384
388
|
init_queries();
|
|
389
|
+
init_connection();
|
|
390
|
+
import { mkdirSync as mkdirSync7 } from "fs";
|
|
391
|
+
import { tmpdir } from "os";
|
|
392
|
+
import { join as join9, resolve as resolve2 } from "path";
|
|
385
393
|
|
|
386
394
|
// src/debug.ts
|
|
387
395
|
var debugEnabled = false;
|
|
@@ -414,7 +422,7 @@ function debugLog(...args) {
|
|
|
414
422
|
// src/ai/config.ts
|
|
415
423
|
import { execSync } from "child_process";
|
|
416
424
|
import { chmodSync, existsSync, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
417
|
-
import { homedir
|
|
425
|
+
import { homedir } from "os";
|
|
418
426
|
import { join as join2 } from "path";
|
|
419
427
|
|
|
420
428
|
// src/ai/models.ts
|
|
@@ -453,7 +461,7 @@ function getModelContextWindow(platform, modelId) {
|
|
|
453
461
|
}
|
|
454
462
|
|
|
455
463
|
// src/ai/config.ts
|
|
456
|
-
var CONFIG_DIR = join2(
|
|
464
|
+
var CONFIG_DIR = join2(homedir(), ".glassbox");
|
|
457
465
|
var CONFIG_PATH = join2(CONFIG_DIR, "config.json");
|
|
458
466
|
function readConfigFile() {
|
|
459
467
|
try {
|
|
@@ -1527,8 +1535,8 @@ function getModeArgs(mode) {
|
|
|
1527
1535
|
import { existsSync as existsSync2, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1528
1536
|
import { join as join3 } from "path";
|
|
1529
1537
|
var lockPath = null;
|
|
1530
|
-
function acquireLock(
|
|
1531
|
-
lockPath = join3(
|
|
1538
|
+
function acquireLock(dataDir) {
|
|
1539
|
+
lockPath = join3(dataDir, "glassbox.lock");
|
|
1532
1540
|
if (existsSync2(lockPath)) {
|
|
1533
1541
|
try {
|
|
1534
1542
|
const contents = JSON.parse(readFileSync3(lockPath, "utf-8"));
|
|
@@ -1537,7 +1545,7 @@ function acquireLock(dataDir2) {
|
|
|
1537
1545
|
process.kill(pid, 0);
|
|
1538
1546
|
console.error(`
|
|
1539
1547
|
Error: Another Glassbox instance (PID ${pid}) is already running.`);
|
|
1540
|
-
console.error(` Data directory: ${
|
|
1548
|
+
console.error(` Data directory: ${dataDir}`);
|
|
1541
1549
|
console.error(` Stop that instance first, or wait for it to exit.
|
|
1542
1550
|
`);
|
|
1543
1551
|
process.exit(1);
|
|
@@ -2400,8 +2408,8 @@ function isRetriable(err) {
|
|
|
2400
2408
|
return msg.includes("429") || msg.includes("500") || msg.includes("502") || msg.includes("503") || msg.includes("504") || msg.includes("rate_limit");
|
|
2401
2409
|
}
|
|
2402
2410
|
function sleep(ms) {
|
|
2403
|
-
return new Promise((
|
|
2404
|
-
setTimeout(
|
|
2411
|
+
return new Promise((resolve3) => {
|
|
2412
|
+
setTimeout(resolve3, ms);
|
|
2405
2413
|
});
|
|
2406
2414
|
}
|
|
2407
2415
|
|
|
@@ -2445,8 +2453,8 @@ function randomLines(count) {
|
|
|
2445
2453
|
return lines.sort((a, b) => a.line - b.line);
|
|
2446
2454
|
}
|
|
2447
2455
|
function sleep2(ms) {
|
|
2448
|
-
return new Promise((
|
|
2449
|
-
setTimeout(
|
|
2456
|
+
return new Promise((resolve3) => {
|
|
2457
|
+
setTimeout(resolve3, ms);
|
|
2450
2458
|
});
|
|
2451
2459
|
}
|
|
2452
2460
|
async function mockRiskAnalysisBatch(files) {
|
|
@@ -2906,9 +2914,9 @@ import { join as join5 } from "path";
|
|
|
2906
2914
|
init_queries();
|
|
2907
2915
|
import { execSync as execSync3 } from "child_process";
|
|
2908
2916
|
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2909
|
-
import { homedir as
|
|
2917
|
+
import { homedir as homedir2 } from "os";
|
|
2910
2918
|
import { join as join4 } from "path";
|
|
2911
|
-
var DISMISS_FILE = join4(
|
|
2919
|
+
var DISMISS_FILE = join4(homedir2(), ".glassbox", "gitignore-dismissed.json");
|
|
2912
2920
|
var DISMISS_DAYS = 30;
|
|
2913
2921
|
function loadDismissals() {
|
|
2914
2922
|
try {
|
|
@@ -2918,7 +2926,7 @@ function loadDismissals() {
|
|
|
2918
2926
|
}
|
|
2919
2927
|
}
|
|
2920
2928
|
function saveDismissals(data) {
|
|
2921
|
-
const dir = join4(
|
|
2929
|
+
const dir = join4(homedir2(), ".glassbox");
|
|
2922
2930
|
mkdirSync3(dir, { recursive: true });
|
|
2923
2931
|
writeFileSync3(DISMISS_FILE, JSON.stringify(data), "utf-8");
|
|
2924
2932
|
}
|
|
@@ -4311,10 +4319,10 @@ pageRoutes.get("/history", async (c) => {
|
|
|
4311
4319
|
|
|
4312
4320
|
// src/server.ts
|
|
4313
4321
|
function tryServe(fetch2, port) {
|
|
4314
|
-
return new Promise((
|
|
4322
|
+
return new Promise((resolve3, reject) => {
|
|
4315
4323
|
const server = serve({ fetch: fetch2, port });
|
|
4316
4324
|
server.on("listening", () => {
|
|
4317
|
-
|
|
4325
|
+
resolve3(port);
|
|
4318
4326
|
});
|
|
4319
4327
|
server.on("error", (err) => {
|
|
4320
4328
|
if (err.code === "EADDRINUSE") {
|
|
@@ -4495,10 +4503,10 @@ function ensureSkills() {
|
|
|
4495
4503
|
// src/update-check.ts
|
|
4496
4504
|
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
4497
4505
|
import { get } from "https";
|
|
4498
|
-
import { homedir as
|
|
4506
|
+
import { homedir as homedir3 } from "os";
|
|
4499
4507
|
import { dirname as dirname2, join as join8 } from "path";
|
|
4500
4508
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4501
|
-
var DATA_DIR = join8(
|
|
4509
|
+
var DATA_DIR = join8(homedir3(), ".glassbox");
|
|
4502
4510
|
var CHECK_FILE = join8(DATA_DIR, "last-update-check");
|
|
4503
4511
|
var PACKAGE_NAME = "glassbox";
|
|
4504
4512
|
function getCurrentVersion() {
|
|
@@ -4530,10 +4538,10 @@ function isFirstUseToday() {
|
|
|
4530
4538
|
return last !== today;
|
|
4531
4539
|
}
|
|
4532
4540
|
function fetchLatestVersion() {
|
|
4533
|
-
return new Promise((
|
|
4541
|
+
return new Promise((resolve3) => {
|
|
4534
4542
|
const req = get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { timeout: 5e3 }, (res) => {
|
|
4535
4543
|
if (res.statusCode !== 200) {
|
|
4536
|
-
|
|
4544
|
+
resolve3(null);
|
|
4537
4545
|
return;
|
|
4538
4546
|
}
|
|
4539
4547
|
let data = "";
|
|
@@ -4542,18 +4550,18 @@ function fetchLatestVersion() {
|
|
|
4542
4550
|
});
|
|
4543
4551
|
res.on("end", () => {
|
|
4544
4552
|
try {
|
|
4545
|
-
|
|
4553
|
+
resolve3(JSON.parse(data).version);
|
|
4546
4554
|
} catch {
|
|
4547
|
-
|
|
4555
|
+
resolve3(null);
|
|
4548
4556
|
}
|
|
4549
4557
|
});
|
|
4550
4558
|
});
|
|
4551
4559
|
req.on("error", () => {
|
|
4552
|
-
|
|
4560
|
+
resolve3(null);
|
|
4553
4561
|
});
|
|
4554
4562
|
req.on("timeout", () => {
|
|
4555
4563
|
req.destroy();
|
|
4556
|
-
|
|
4564
|
+
resolve3(null);
|
|
4557
4565
|
});
|
|
4558
4566
|
});
|
|
4559
4567
|
}
|
|
@@ -4623,6 +4631,7 @@ Modes (pick one):
|
|
|
4623
4631
|
|
|
4624
4632
|
Options:
|
|
4625
4633
|
--port <number> Port to run on (default: 4183)
|
|
4634
|
+
--data-dir <path> Store data in an alternative location (default: .glassbox/)
|
|
4626
4635
|
--resume Resume the latest in-progress review for this mode
|
|
4627
4636
|
--no-open Don't open browser automatically
|
|
4628
4637
|
--strict-port Fail if the requested port is in use
|
|
@@ -4643,6 +4652,7 @@ function parseArgs(argv) {
|
|
|
4643
4652
|
const args = argv.slice(2);
|
|
4644
4653
|
let mode = null;
|
|
4645
4654
|
let port = 4183;
|
|
4655
|
+
let dataDir = null;
|
|
4646
4656
|
let resume = false;
|
|
4647
4657
|
let forceUpdateCheck = false;
|
|
4648
4658
|
let debug = false;
|
|
@@ -4688,6 +4698,9 @@ function parseArgs(argv) {
|
|
|
4688
4698
|
case "--port":
|
|
4689
4699
|
port = parseInt(args[++i], 10);
|
|
4690
4700
|
break;
|
|
4701
|
+
case "--data-dir":
|
|
4702
|
+
dataDir = resolve2(args[++i]);
|
|
4703
|
+
break;
|
|
4691
4704
|
case "--resume":
|
|
4692
4705
|
resume = true;
|
|
4693
4706
|
break;
|
|
@@ -4726,7 +4739,7 @@ function parseArgs(argv) {
|
|
|
4726
4739
|
if (!mode) {
|
|
4727
4740
|
mode = { type: "uncommitted" };
|
|
4728
4741
|
}
|
|
4729
|
-
return { mode, port, resume, forceUpdateCheck, debug, aiServiceTest, demo, noOpen, strictPort, projectDir };
|
|
4742
|
+
return { mode, port, dataDir, resume, forceUpdateCheck, debug, aiServiceTest, demo, noOpen, strictPort, projectDir };
|
|
4730
4743
|
}
|
|
4731
4744
|
async function main() {
|
|
4732
4745
|
const parsed = parseArgs(process.argv);
|
|
@@ -4735,24 +4748,20 @@ async function main() {
|
|
|
4735
4748
|
process.exit(1);
|
|
4736
4749
|
}
|
|
4737
4750
|
const { mode, port, resume, forceUpdateCheck, debug, aiServiceTest, demo, noOpen, strictPort, projectDir } = parsed;
|
|
4751
|
+
let { dataDir } = parsed;
|
|
4738
4752
|
setDebug(debug);
|
|
4739
4753
|
setAIServiceTest(aiServiceTest);
|
|
4740
4754
|
if (aiServiceTest) {
|
|
4741
4755
|
console.log("AI service test mode enabled \u2014 using mock AI responses");
|
|
4742
4756
|
}
|
|
4743
4757
|
if (debug) {
|
|
4744
|
-
console.log(`[debug] Build timestamp: ${"2026-03-
|
|
4758
|
+
console.log(`[debug] Build timestamp: ${"2026-03-16T03:39:47.211Z"}`);
|
|
4745
4759
|
}
|
|
4746
4760
|
if (projectDir) {
|
|
4747
4761
|
process.chdir(projectDir);
|
|
4748
4762
|
}
|
|
4749
|
-
if (
|
|
4750
|
-
|
|
4751
|
-
const { join: join9 } = await import("path");
|
|
4752
|
-
const { mkdirSync: mkdirSync7 } = await import("fs");
|
|
4753
|
-
const dataDir2 = join9(homedir5(), ".glassbox");
|
|
4754
|
-
mkdirSync7(dataDir2, { recursive: true });
|
|
4755
|
-
acquireLock(dataDir2);
|
|
4763
|
+
if (dataDir === null) {
|
|
4764
|
+
dataDir = join9(process.cwd(), ".glassbox");
|
|
4756
4765
|
}
|
|
4757
4766
|
if (demo !== null) {
|
|
4758
4767
|
const scenario = DEMO_SCENARIOS.find((s) => s.id === demo);
|
|
@@ -4764,10 +4773,18 @@ async function main() {
|
|
|
4764
4773
|
}
|
|
4765
4774
|
process.exit(1);
|
|
4766
4775
|
}
|
|
4776
|
+
dataDir = join9(tmpdir(), `glassbox-demo-${demo}-${Date.now()}`);
|
|
4767
4777
|
setDemoMode(demo);
|
|
4768
4778
|
console.log(`
|
|
4769
4779
|
DEMO MODE: ${scenario.label}
|
|
4770
4780
|
`);
|
|
4781
|
+
}
|
|
4782
|
+
mkdirSync7(dataDir, { recursive: true });
|
|
4783
|
+
if (demo === null) {
|
|
4784
|
+
acquireLock(dataDir);
|
|
4785
|
+
}
|
|
4786
|
+
setDataDir(dataDir);
|
|
4787
|
+
if (demo !== null) {
|
|
4771
4788
|
const { reviewId } = await setupDemoReview(demo);
|
|
4772
4789
|
await startServer(port, reviewId, process.cwd(), { noOpen, strictPort });
|
|
4773
4790
|
return;
|