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 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 | `~/.glassbox/data/` |
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(dbPath);
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(dbPath, { recursive: true, force: true });
125
+ rmSync(currentDbPath, { recursive: true, force: true });
120
126
  } catch {
121
127
  }
122
- db = new PGlite(dbPath);
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 dataDir, dbPath, db;
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 as homedir2 } from "os";
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(homedir2(), ".glassbox");
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(dataDir2) {
1531
- lockPath = join3(dataDir2, "glassbox.lock");
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: ${dataDir2}`);
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((resolve2) => {
2404
- setTimeout(resolve2, ms);
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((resolve2) => {
2449
- setTimeout(resolve2, ms);
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 homedir3 } from "os";
2917
+ import { homedir as homedir2 } from "os";
2910
2918
  import { join as join4 } from "path";
2911
- var DISMISS_FILE = join4(homedir3(), ".glassbox", "gitignore-dismissed.json");
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(homedir3(), ".glassbox");
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((resolve2, reject) => {
4322
+ return new Promise((resolve3, reject) => {
4315
4323
  const server = serve({ fetch: fetch2, port });
4316
4324
  server.on("listening", () => {
4317
- resolve2(port);
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 homedir4 } from "os";
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(homedir4(), ".glassbox");
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((resolve2) => {
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
- resolve2(null);
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
- resolve2(JSON.parse(data).version);
4553
+ resolve3(JSON.parse(data).version);
4546
4554
  } catch {
4547
- resolve2(null);
4555
+ resolve3(null);
4548
4556
  }
4549
4557
  });
4550
4558
  });
4551
4559
  req.on("error", () => {
4552
- resolve2(null);
4560
+ resolve3(null);
4553
4561
  });
4554
4562
  req.on("timeout", () => {
4555
4563
  req.destroy();
4556
- resolve2(null);
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-15T23:17:06.265Z"}`);
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 (demo === null) {
4750
- const { homedir: homedir5 } = await import("os");
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;