glassbox 0.2.0 → 0.2.2
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 +103 -46
- package/dist/cli.js +44 -20
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -12,15 +12,30 @@ No accounts. No pull requests. No waiting. Just you, the diff, and a tight feedb
|
|
|
12
12
|
|
|
13
13
|
<br>
|
|
14
14
|
|
|
15
|
+
**Desktop app** (recommended) — download from [GitHub Releases](https://github.com/brianwestphal/glassbox/releases):
|
|
16
|
+
|
|
17
|
+
| Platform | Download |
|
|
18
|
+
|----------|----------|
|
|
19
|
+
| macOS (Apple Silicon) | `.dmg` (arm64) |
|
|
20
|
+
| macOS (Intel) | `.dmg` (x64) |
|
|
21
|
+
| Linux | `.AppImage` / `.deb` |
|
|
22
|
+
| Windows | `.msi` / `.exe` |
|
|
23
|
+
|
|
24
|
+
After installing, open the app and click **Install CLI** to add the `glassbox` command to your PATH.
|
|
25
|
+
|
|
26
|
+
**Or install via npm:**
|
|
27
|
+
|
|
15
28
|
```bash
|
|
16
29
|
npm install -g glassbox
|
|
17
30
|
```
|
|
18
31
|
|
|
32
|
+
Then, from any git repository:
|
|
33
|
+
|
|
19
34
|
```bash
|
|
20
35
|
glassbox
|
|
21
36
|
```
|
|
22
37
|
|
|
23
|
-
That's it.
|
|
38
|
+
That's it. Data stays local. Works in any git repo.
|
|
24
39
|
|
|
25
40
|
<br>
|
|
26
41
|
|
|
@@ -34,21 +49,21 @@ That's it. Opens in your browser. Works in any git repo.
|
|
|
34
49
|
|
|
35
50
|
AI coding tools generate a lot of code fast. But "fast" doesn't mean "correct." The bottleneck isn't generation — it's **review**.
|
|
36
51
|
|
|
37
|
-
Most developers review AI output by skimming files in their editor, mentally diffing what changed, and then either accepting it or rewriting it by hand. That's slow, error-prone, and throws away the most valuable signal: your expert judgment about
|
|
52
|
+
Most developers review AI output by skimming files in their editor, mentally diffing what changed, and then either accepting it or rewriting it by hand. That's slow, error-prone, and throws away the most valuable signal: your expert judgment about _what specifically_ was wrong and why.
|
|
38
53
|
|
|
39
54
|
Glassbox gives you a proper diff viewer with annotation categories designed for AI feedback:
|
|
40
55
|
|
|
41
56
|
<img src="assets/demo-annotations.png" alt="Inline annotations with category badges" width="720">
|
|
42
57
|
|
|
43
|
-
| Category
|
|
44
|
-
|
|
45
|
-
| **Bug**
|
|
46
|
-
| **Fix needed**
|
|
47
|
-
| **Style**
|
|
48
|
-
| **Pattern to follow** | "This is good. Keep doing this."
|
|
49
|
-
| **Pattern to avoid**
|
|
50
|
-
| **Note**
|
|
51
|
-
| **Remember**
|
|
58
|
+
| Category | What it tells the AI |
|
|
59
|
+
| --------------------- | ----------------------------------------------- |
|
|
60
|
+
| **Bug** | "This is broken. Fix it." |
|
|
61
|
+
| **Fix needed** | "This needs a specific change." |
|
|
62
|
+
| **Style** | "I prefer it done this way." |
|
|
63
|
+
| **Pattern to follow** | "This is good. Keep doing this." |
|
|
64
|
+
| **Pattern to avoid** | "This is an anti-pattern. Stop." |
|
|
65
|
+
| **Note** | Context for the AI to consider. |
|
|
66
|
+
| **Remember** | A rule to persist to the AI's long-term config. |
|
|
52
67
|
|
|
53
68
|
When you're done, click **Complete Review** and tell your AI tool:
|
|
54
69
|
|
|
@@ -85,7 +100,7 @@ Then you run `glassbox` again. Your previous annotations carry forward — match
|
|
|
85
100
|
- **Automatic .gitignore prompt** — reminds you to exclude `.glassbox/` from version control
|
|
86
101
|
- **Auto port selection** — if the default port is busy, it finds an open one
|
|
87
102
|
- **Fully local** — no network calls (unless you opt into AI features), no accounts, no telemetry. Your code stays on your machine.
|
|
88
|
-
- **AI-powered analysis**
|
|
103
|
+
- **AI-powered analysis** _(optional)_ — risk scoring, narrative reading order, and guided review to help you focus and learn as you review
|
|
89
104
|
|
|
90
105
|
---
|
|
91
106
|
|
|
@@ -93,7 +108,7 @@ Then you run `glassbox` again. Your previous annotations carry forward — match
|
|
|
93
108
|
|
|
94
109
|
> Entirely optional. Glassbox is fully functional without it.
|
|
95
110
|
|
|
96
|
-
When reviewing a large diff, knowing
|
|
111
|
+
When reviewing a large diff, knowing _where to look first_ is half the battle. Glassbox can optionally connect to an AI provider to analyze your changes and surface what matters:
|
|
97
112
|
|
|
98
113
|
### Risk Analysis
|
|
99
114
|
|
|
@@ -123,11 +138,11 @@ Click the shield or book icon in the sidebar to switch from the default folder v
|
|
|
123
138
|
|
|
124
139
|
### Supported providers
|
|
125
140
|
|
|
126
|
-
| Provider
|
|
127
|
-
|
|
128
|
-
| **Anthropic** | Claude Sonnet 4, Claude Haiku 4
|
|
129
|
-
| **OpenAI**
|
|
130
|
-
| **Google**
|
|
141
|
+
| Provider | Models | Env variable |
|
|
142
|
+
| ------------- | -------------------------------- | ------------------- |
|
|
143
|
+
| **Anthropic** | Claude Sonnet 4, Claude Haiku 4 | `ANTHROPIC_API_KEY` |
|
|
144
|
+
| **OpenAI** | GPT-4o, GPT-4o Mini | `OPENAI_API_KEY` |
|
|
145
|
+
| **Google** | Gemini 2.5 Flash, Gemini 2.5 Pro | `GEMINI_API_KEY` |
|
|
131
146
|
|
|
132
147
|
You can switch providers and models in the settings dialog (gear icon in the sidebar).
|
|
133
148
|
|
|
@@ -145,6 +160,28 @@ Keys entered through the settings dialog are stored in the OS keychain by defaul
|
|
|
145
160
|
|
|
146
161
|
## Install
|
|
147
162
|
|
|
163
|
+
### Desktop app (recommended)
|
|
164
|
+
|
|
165
|
+
Download the latest release for your platform from [GitHub Releases](https://github.com/brianwestphal/glassbox/releases).
|
|
166
|
+
|
|
167
|
+
On first launch, the app will prompt you to install the `glassbox` CLI command. This creates a symlink so you can launch the desktop app from any project directory. You can also install it manually:
|
|
168
|
+
|
|
169
|
+
**macOS:**
|
|
170
|
+
```bash
|
|
171
|
+
sudo ln -sf "/Applications/Glassbox.app/Contents/Resources/resources/glassbox" /usr/local/bin/glassbox
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Linux:**
|
|
175
|
+
```bash
|
|
176
|
+
ln -sf /path/to/glassbox/resources/glassbox-linux ~/.local/bin/glassbox
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The desktop app includes automatic updates — new versions are downloaded and applied in the background.
|
|
180
|
+
|
|
181
|
+
### npm
|
|
182
|
+
|
|
183
|
+
Alternatively, install via npm (runs in your browser instead of a native window):
|
|
184
|
+
|
|
148
185
|
```bash
|
|
149
186
|
npm install -g glassbox
|
|
150
187
|
```
|
|
@@ -185,22 +222,37 @@ glassbox --resume
|
|
|
185
222
|
|
|
186
223
|
### All options
|
|
187
224
|
|
|
188
|
-
| Flag
|
|
189
|
-
|
|
190
|
-
|
|
|
191
|
-
| `--uncommitted`
|
|
192
|
-
| `--staged`
|
|
193
|
-
| `--unstaged`
|
|
194
|
-
| `--commit <sha>`
|
|
195
|
-
| `--range <from>..<to>` | Changes between two refs
|
|
196
|
-
| `--branch <name>`
|
|
197
|
-
| `--files <patterns>`
|
|
198
|
-
| `--all`
|
|
199
|
-
| `--port <number>`
|
|
200
|
-
| `--resume`
|
|
201
|
-
| `--
|
|
202
|
-
| `--
|
|
203
|
-
| `--
|
|
225
|
+
| Flag | Description |
|
|
226
|
+
| ---------------------- | -------------------------------------------------- |
|
|
227
|
+
| _(no flag)_ | Same as `--uncommitted` |
|
|
228
|
+
| `--uncommitted` | Staged + unstaged + untracked changes |
|
|
229
|
+
| `--staged` | Only staged changes |
|
|
230
|
+
| `--unstaged` | Only unstaged changes |
|
|
231
|
+
| `--commit <sha>` | Changes from a specific commit |
|
|
232
|
+
| `--range <from>..<to>` | Changes between two refs |
|
|
233
|
+
| `--branch <name>` | Current branch vs the named branch |
|
|
234
|
+
| `--files <patterns>` | Specific files (comma-separated globs) |
|
|
235
|
+
| `--all` | Entire codebase (all tracked files) |
|
|
236
|
+
| `--port <number>` | Port to run on (default: 4183) |
|
|
237
|
+
| `--resume` | Resume the latest in-progress review for this mode |
|
|
238
|
+
| `--browser` | Open in browser instead of desktop window |
|
|
239
|
+
| `--check-for-updates` | Check for a newer version on npm |
|
|
240
|
+
| `--debug` | Show build timestamp and debug info |
|
|
241
|
+
| `--help` | Show help |
|
|
242
|
+
|
|
243
|
+
### Settings file
|
|
244
|
+
|
|
245
|
+
Create `.glassbox/settings.json` in your project directory to configure per-project options:
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"appName": "Glassbox — My Project"
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
| Key | Description |
|
|
254
|
+
|-----|-------------|
|
|
255
|
+
| `appName` | Custom window title and Dock name (defaults to "Glassbox — _folder name_") |
|
|
204
256
|
|
|
205
257
|
---
|
|
206
258
|
|
|
@@ -231,17 +283,20 @@ Point the tool at the file. The export includes an "Instructions for AI Tools" s
|
|
|
231
283
|
|
|
232
284
|
## Architecture
|
|
233
285
|
|
|
234
|
-
| Layer
|
|
235
|
-
|
|
236
|
-
|
|
|
237
|
-
|
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
286
|
+
| Layer | Technology |
|
|
287
|
+
| -------- | ---------------------------------------------------- |
|
|
288
|
+
| Desktop | Tauri v2 (native window, auto-updates) |
|
|
289
|
+
| CLI | TypeScript, Node.js |
|
|
290
|
+
| Server | Hono |
|
|
291
|
+
| Database | PGLite (embedded PostgreSQL) |
|
|
292
|
+
| UI | Custom server-side JSX (no React), vanilla client JS |
|
|
293
|
+
| Build | tsup (single-file bundle) |
|
|
294
|
+
| Storage | `~/.glassbox/data/` |
|
|
242
295
|
|
|
243
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.
|
|
244
297
|
|
|
298
|
+
> **Note:** We're actively developing and testing on macOS. Linux and Windows builds are provided but less tested — if you run into issues on those platforms, please [open an issue](https://github.com/brianwestphal/glassbox/issues).
|
|
299
|
+
|
|
245
300
|
## Development
|
|
246
301
|
|
|
247
302
|
```bash
|
|
@@ -249,10 +304,12 @@ git clone <repo-url>
|
|
|
249
304
|
cd glassbox
|
|
250
305
|
npm install
|
|
251
306
|
|
|
252
|
-
npm run dev
|
|
253
|
-
npm run build
|
|
254
|
-
npm run
|
|
255
|
-
npm
|
|
307
|
+
npm run dev # Build client assets, then run via tsx
|
|
308
|
+
npm run build # Build to dist/cli.js
|
|
309
|
+
npm run tauri:dev # Run desktop app in dev mode
|
|
310
|
+
npm run tauri:build # Build desktop app for distribution
|
|
311
|
+
npm run clean # Remove dist and caches
|
|
312
|
+
npm link # Symlink for global 'glassbox' command
|
|
256
313
|
```
|
|
257
314
|
|
|
258
315
|
## License
|
package/dist/cli.js
CHANGED
|
@@ -4123,7 +4123,7 @@ function tryServe(fetch2, port) {
|
|
|
4123
4123
|
});
|
|
4124
4124
|
});
|
|
4125
4125
|
}
|
|
4126
|
-
async function startServer(port, reviewId, repoRoot) {
|
|
4126
|
+
async function startServer(port, reviewId, repoRoot, options) {
|
|
4127
4127
|
const app = new Hono4();
|
|
4128
4128
|
app.use("*", async (c, next) => {
|
|
4129
4129
|
c.set("reviewId", reviewId);
|
|
@@ -4145,15 +4145,19 @@ async function startServer(port, reviewId, repoRoot) {
|
|
|
4145
4145
|
app.route("/api/ai", aiApiRoutes);
|
|
4146
4146
|
app.route("/", pageRoutes);
|
|
4147
4147
|
let actualPort = port;
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4148
|
+
if (options?.strictPort) {
|
|
4149
|
+
actualPort = await tryServe(app.fetch, port);
|
|
4150
|
+
} else {
|
|
4151
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
4152
|
+
try {
|
|
4153
|
+
actualPort = await tryServe(app.fetch, port + attempt);
|
|
4154
|
+
break;
|
|
4155
|
+
} catch (err) {
|
|
4156
|
+
if (err instanceof Error && err.code === "EADDRINUSE" && attempt < 19) {
|
|
4157
|
+
continue;
|
|
4158
|
+
}
|
|
4159
|
+
throw err;
|
|
4155
4160
|
}
|
|
4156
|
-
throw err;
|
|
4157
4161
|
}
|
|
4158
4162
|
}
|
|
4159
4163
|
if (actualPort !== port) {
|
|
@@ -4163,8 +4167,10 @@ async function startServer(port, reviewId, repoRoot) {
|
|
|
4163
4167
|
console.log(`
|
|
4164
4168
|
Glassbox running at ${url}
|
|
4165
4169
|
`);
|
|
4166
|
-
|
|
4167
|
-
|
|
4170
|
+
if (!options?.noOpen) {
|
|
4171
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
4172
|
+
exec(`${openCmd} ${url}`);
|
|
4173
|
+
}
|
|
4168
4174
|
}
|
|
4169
4175
|
|
|
4170
4176
|
// src/update-check.ts
|
|
@@ -4297,8 +4303,11 @@ Modes (pick one):
|
|
|
4297
4303
|
--all Review entire codebase
|
|
4298
4304
|
|
|
4299
4305
|
Options:
|
|
4300
|
-
--port <number> Port to run on (default:
|
|
4306
|
+
--port <number> Port to run on (default: 4183)
|
|
4301
4307
|
--resume Resume the latest in-progress review for this mode
|
|
4308
|
+
--no-open Don't open browser automatically
|
|
4309
|
+
--strict-port Fail if the requested port is in use
|
|
4310
|
+
--project-dir <dir> Run as if invoked from <dir> (used by Tauri desktop app)
|
|
4302
4311
|
--check-for-updates Check for a newer version on npm
|
|
4303
4312
|
--ai-service-test Use mock AI responses (no API calls, no tokens used)
|
|
4304
4313
|
--help Show this help message
|
|
@@ -4314,12 +4323,15 @@ Examples:
|
|
|
4314
4323
|
function parseArgs(argv) {
|
|
4315
4324
|
const args = argv.slice(2);
|
|
4316
4325
|
let mode = null;
|
|
4317
|
-
let port =
|
|
4326
|
+
let port = 4183;
|
|
4318
4327
|
let resume = false;
|
|
4319
4328
|
let forceUpdateCheck = false;
|
|
4320
4329
|
let debug = false;
|
|
4321
4330
|
let aiServiceTest = false;
|
|
4322
4331
|
let demo = null;
|
|
4332
|
+
let noOpen = false;
|
|
4333
|
+
let strictPort = false;
|
|
4334
|
+
let projectDir = null;
|
|
4323
4335
|
for (let i = 0; i < args.length; i++) {
|
|
4324
4336
|
const arg = args[i];
|
|
4325
4337
|
switch (arg) {
|
|
@@ -4369,6 +4381,15 @@ function parseArgs(argv) {
|
|
|
4369
4381
|
case "--ai-service-test":
|
|
4370
4382
|
aiServiceTest = true;
|
|
4371
4383
|
break;
|
|
4384
|
+
case "--no-open":
|
|
4385
|
+
noOpen = true;
|
|
4386
|
+
break;
|
|
4387
|
+
case "--strict-port":
|
|
4388
|
+
strictPort = true;
|
|
4389
|
+
break;
|
|
4390
|
+
case "--project-dir":
|
|
4391
|
+
projectDir = args[++i];
|
|
4392
|
+
break;
|
|
4372
4393
|
default:
|
|
4373
4394
|
if (arg.startsWith("--demo:")) {
|
|
4374
4395
|
demo = parseInt(arg.slice(7), 10);
|
|
@@ -4386,7 +4407,7 @@ function parseArgs(argv) {
|
|
|
4386
4407
|
if (!mode) {
|
|
4387
4408
|
mode = { type: "uncommitted" };
|
|
4388
4409
|
}
|
|
4389
|
-
return { mode, port, resume, forceUpdateCheck, debug, aiServiceTest, demo };
|
|
4410
|
+
return { mode, port, resume, forceUpdateCheck, debug, aiServiceTest, demo, noOpen, strictPort, projectDir };
|
|
4390
4411
|
}
|
|
4391
4412
|
async function main() {
|
|
4392
4413
|
const parsed = parseArgs(process.argv);
|
|
@@ -4394,14 +4415,17 @@ async function main() {
|
|
|
4394
4415
|
printUsage();
|
|
4395
4416
|
process.exit(1);
|
|
4396
4417
|
}
|
|
4397
|
-
const { mode, port, resume, forceUpdateCheck, debug, aiServiceTest, demo } = parsed;
|
|
4418
|
+
const { mode, port, resume, forceUpdateCheck, debug, aiServiceTest, demo, noOpen, strictPort, projectDir } = parsed;
|
|
4398
4419
|
setDebug(debug);
|
|
4399
4420
|
setAIServiceTest(aiServiceTest);
|
|
4400
4421
|
if (aiServiceTest) {
|
|
4401
4422
|
console.log("AI service test mode enabled \u2014 using mock AI responses");
|
|
4402
4423
|
}
|
|
4403
4424
|
if (debug) {
|
|
4404
|
-
console.log(`[debug] Build timestamp: ${"2026-03-
|
|
4425
|
+
console.log(`[debug] Build timestamp: ${"2026-03-13T08:17:01.192Z"}`);
|
|
4426
|
+
}
|
|
4427
|
+
if (projectDir) {
|
|
4428
|
+
process.chdir(projectDir);
|
|
4405
4429
|
}
|
|
4406
4430
|
if (demo !== null) {
|
|
4407
4431
|
const scenario = DEMO_SCENARIOS.find((s) => s.id === demo);
|
|
@@ -4418,7 +4442,7 @@ async function main() {
|
|
|
4418
4442
|
DEMO MODE: ${scenario.label}
|
|
4419
4443
|
`);
|
|
4420
4444
|
const { reviewId } = await setupDemoReview(demo);
|
|
4421
|
-
await startServer(port, reviewId, process.cwd());
|
|
4445
|
+
await startServer(port, reviewId, process.cwd(), { noOpen, strictPort });
|
|
4422
4446
|
return;
|
|
4423
4447
|
}
|
|
4424
4448
|
await checkForUpdates(forceUpdateCheck);
|
|
@@ -4439,12 +4463,12 @@ async function main() {
|
|
|
4439
4463
|
const diffs2 = getFileDiffs(mode, cwd);
|
|
4440
4464
|
const result = await updateReviewDiffs(existing.id, diffs2, headCommit);
|
|
4441
4465
|
console.log(`Updated ${result.updated} file(s), ${result.added} added, ${result.stale} stale annotation(s)`);
|
|
4442
|
-
await startServer(port, existing.id, repoRoot);
|
|
4466
|
+
await startServer(port, existing.id, repoRoot, { noOpen, strictPort });
|
|
4443
4467
|
return;
|
|
4444
4468
|
}
|
|
4445
4469
|
if (resume) {
|
|
4446
4470
|
console.log(`Resuming review ${existing.id} (started ${existing.created_at})`);
|
|
4447
|
-
await startServer(port, existing.id, repoRoot);
|
|
4471
|
+
await startServer(port, existing.id, repoRoot, { noOpen, strictPort });
|
|
4448
4472
|
return;
|
|
4449
4473
|
}
|
|
4450
4474
|
} else if (resume) {
|
|
@@ -4462,7 +4486,7 @@ async function main() {
|
|
|
4462
4486
|
await addReviewFile(review.id, diff.filePath, JSON.stringify(diff));
|
|
4463
4487
|
}
|
|
4464
4488
|
console.log(`Review ${review.id} created.`);
|
|
4465
|
-
await startServer(port, review.id, repoRoot);
|
|
4489
|
+
await startServer(port, review.id, repoRoot, { noOpen, strictPort });
|
|
4466
4490
|
}
|
|
4467
4491
|
main().catch((err) => {
|
|
4468
4492
|
console.error(err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glassbox",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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",
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
"dev": "npm run build:client && tsx --tsconfig tsconfig.json src/cli.ts",
|
|
32
32
|
"build": "tsup",
|
|
33
33
|
"build:client": "mkdir -p dist/client && npx esbuild src/client/app.ts --bundle --format=iife --outfile=dist/client/app.global.js --target=es2020 --jsx=automatic --jsx-import-source=#jsx --alias:#jsx/jsx-runtime=./src/jsx-runtime.ts && npx sass src/client/styles.scss dist/client/styles.css --style compressed --no-source-map",
|
|
34
|
+
"dev:server": "npm run build:client && tsx --tsconfig tsconfig.json src/cli.ts --no-open --strict-port",
|
|
35
|
+
"tauri": "tauri",
|
|
36
|
+
"tauri:dev": "bash scripts/ensure-sidecar-stub.sh && tauri dev",
|
|
37
|
+
"tauri:build": "bash scripts/build-sidecar.sh && tauri build",
|
|
34
38
|
"lint": "eslint src/",
|
|
35
39
|
"clean": "rm -rf dist node_modules/.cache",
|
|
36
40
|
"release": "bash scripts/release.sh",
|
|
@@ -47,6 +51,7 @@
|
|
|
47
51
|
},
|
|
48
52
|
"devDependencies": {
|
|
49
53
|
"@eslint/js": "^9.39.3",
|
|
54
|
+
"@tauri-apps/cli": "^2.10.1",
|
|
50
55
|
"@types/node": "^22.0.0",
|
|
51
56
|
"esbuild": "^0.27.3",
|
|
52
57
|
"eslint": "^9.39.3",
|