glassbox 0.8.0 → 0.8.1-rc.1
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 +14 -0
- package/dist/cli.js +170 -160
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -266,6 +266,20 @@ The exported review file is plain markdown. Any AI tool that can read files can
|
|
|
266
266
|
Read .glassbox/latest-review.md and apply the review feedback.
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
+
Glassbox automatically generates a `/glassbox` skill for Claude Code, so you can just type `/glassbox` in Claude to have it read and apply your latest review.
|
|
270
|
+
|
|
271
|
+
### Claude Channel Integration (Experimental)
|
|
272
|
+
|
|
273
|
+
Glassbox can send review feedback directly to a running Claude Code session via MCP channels — no copy-pasting needed. When you complete a review, a "Send to Claude" button appears that pushes your feedback to Claude instantly.
|
|
274
|
+
|
|
275
|
+
Enable it in Settings → Experimental → Claude Channel, then launch Claude with channel support:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
claude --dangerously-load-development-channels server:glassbox-channel
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Requires Claude Code v2.1.80+ with channel support. See [docs/17-claude-channel.md](docs/17-claude-channel.md) for details.
|
|
282
|
+
|
|
269
283
|
### Cursor / Copilot / other
|
|
270
284
|
|
|
271
285
|
Point the tool at the file. The export includes an "Instructions for AI Tools" section that explains how to interpret each annotation category.
|
package/dist/cli.js
CHANGED
|
@@ -1727,9 +1727,97 @@ import { serve } from "@hono/node-server";
|
|
|
1727
1727
|
import { exec } from "child_process";
|
|
1728
1728
|
import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
|
|
1729
1729
|
import { Hono as Hono8 } from "hono";
|
|
1730
|
+
import { homedir as homedir6 } from "os";
|
|
1730
1731
|
import { dirname as dirname2, join as join10 } from "path";
|
|
1731
1732
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1732
1733
|
|
|
1734
|
+
// src/channel-config.ts
|
|
1735
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1736
|
+
import { dirname, join as join4, resolve as resolve2 } from "path";
|
|
1737
|
+
import { fileURLToPath } from "url";
|
|
1738
|
+
var MCP_SERVER_KEY = "glassbox-channel";
|
|
1739
|
+
function getChannelServerPath() {
|
|
1740
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
1741
|
+
const distPath = resolve2(thisDir, "channel.js");
|
|
1742
|
+
if (existsSync3(distPath)) {
|
|
1743
|
+
return { command: process.execPath, args: [distPath] };
|
|
1744
|
+
}
|
|
1745
|
+
const srcPath = resolve2(thisDir, "channel.ts");
|
|
1746
|
+
if (existsSync3(srcPath)) {
|
|
1747
|
+
return { command: "npx", args: ["tsx", srcPath] };
|
|
1748
|
+
}
|
|
1749
|
+
return { command: process.execPath, args: [distPath] };
|
|
1750
|
+
}
|
|
1751
|
+
function projectRoot(dataDir) {
|
|
1752
|
+
return dataDir.replace(/\/.glassbox\/?$/, "");
|
|
1753
|
+
}
|
|
1754
|
+
function registerChannel(dataDir) {
|
|
1755
|
+
const root = projectRoot(dataDir);
|
|
1756
|
+
const mcpPath = join4(root, ".mcp.json");
|
|
1757
|
+
const { command, args } = getChannelServerPath();
|
|
1758
|
+
let config = {};
|
|
1759
|
+
if (existsSync3(mcpPath)) {
|
|
1760
|
+
try {
|
|
1761
|
+
config = JSON.parse(readFileSync4(mcpPath, "utf-8"));
|
|
1762
|
+
} catch {
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
if (config.mcpServers === void 0) config.mcpServers = {};
|
|
1766
|
+
config.mcpServers[MCP_SERVER_KEY] = {
|
|
1767
|
+
command,
|
|
1768
|
+
args: [...args, "--data-dir", dataDir]
|
|
1769
|
+
};
|
|
1770
|
+
writeFileSync3(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1771
|
+
}
|
|
1772
|
+
function unregisterChannel(dataDir) {
|
|
1773
|
+
const root = projectRoot(dataDir);
|
|
1774
|
+
const mcpPath = join4(root, ".mcp.json");
|
|
1775
|
+
if (!existsSync3(mcpPath)) return;
|
|
1776
|
+
try {
|
|
1777
|
+
const config = JSON.parse(readFileSync4(mcpPath, "utf-8"));
|
|
1778
|
+
if (config.mcpServers?.[MCP_SERVER_KEY] !== void 0) {
|
|
1779
|
+
const servers = { ...config.mcpServers };
|
|
1780
|
+
delete servers[MCP_SERVER_KEY];
|
|
1781
|
+
config.mcpServers = servers;
|
|
1782
|
+
writeFileSync3(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1783
|
+
}
|
|
1784
|
+
} catch {
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
function getChannelPort(dataDir) {
|
|
1788
|
+
try {
|
|
1789
|
+
const portStr = readFileSync4(join4(dataDir, "channel-port"), "utf-8").trim();
|
|
1790
|
+
const port = parseInt(portStr, 10);
|
|
1791
|
+
return isNaN(port) ? null : port;
|
|
1792
|
+
} catch {
|
|
1793
|
+
return null;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
async function isChannelAlive(dataDir) {
|
|
1797
|
+
const port = getChannelPort(dataDir);
|
|
1798
|
+
if (port === null) return false;
|
|
1799
|
+
try {
|
|
1800
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`);
|
|
1801
|
+
const data = await res.json();
|
|
1802
|
+
return data.ok;
|
|
1803
|
+
} catch {
|
|
1804
|
+
return false;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
async function triggerChannel(dataDir, message) {
|
|
1808
|
+
const port = getChannelPort(dataDir);
|
|
1809
|
+
if (port === null) return false;
|
|
1810
|
+
try {
|
|
1811
|
+
const res = await fetch(`http://127.0.0.1:${port}/trigger`, {
|
|
1812
|
+
method: "POST",
|
|
1813
|
+
body: message
|
|
1814
|
+
});
|
|
1815
|
+
return res.ok;
|
|
1816
|
+
} catch {
|
|
1817
|
+
return false;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1733
1821
|
// src/routes/ai-api.ts
|
|
1734
1822
|
import { Hono as Hono3 } from "hono";
|
|
1735
1823
|
|
|
@@ -3023,30 +3111,30 @@ aiApiRoutes.route("/", aiAnalysisRoutes);
|
|
|
3023
3111
|
// src/routes/api.ts
|
|
3024
3112
|
init_queries();
|
|
3025
3113
|
import { execFileSync, spawnSync as spawnSync7 } from "child_process";
|
|
3026
|
-
import { existsSync as
|
|
3114
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
3027
3115
|
import { Hono as Hono4 } from "hono";
|
|
3028
3116
|
import { homedir as homedir3 } from "os";
|
|
3029
|
-
import { join as
|
|
3117
|
+
import { join as join7, resolve as resolve4 } from "path";
|
|
3030
3118
|
|
|
3031
3119
|
// src/export/generate.ts
|
|
3032
3120
|
init_queries();
|
|
3033
3121
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
3034
|
-
import { appendFileSync, existsSync as
|
|
3122
|
+
import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
3035
3123
|
import { homedir as homedir2 } from "os";
|
|
3036
|
-
import { join as
|
|
3037
|
-
var DISMISS_FILE =
|
|
3124
|
+
import { join as join5 } from "path";
|
|
3125
|
+
var DISMISS_FILE = join5(homedir2(), ".glassbox", "gitignore-dismissed.json");
|
|
3038
3126
|
var DISMISS_DAYS = 30;
|
|
3039
3127
|
function loadDismissals() {
|
|
3040
3128
|
try {
|
|
3041
|
-
return JSON.parse(
|
|
3129
|
+
return JSON.parse(readFileSync5(DISMISS_FILE, "utf-8"));
|
|
3042
3130
|
} catch {
|
|
3043
3131
|
return {};
|
|
3044
3132
|
}
|
|
3045
3133
|
}
|
|
3046
3134
|
function saveDismissals(data) {
|
|
3047
|
-
const dir =
|
|
3135
|
+
const dir = join5(homedir2(), ".glassbox");
|
|
3048
3136
|
mkdirSync3(dir, { recursive: true });
|
|
3049
|
-
|
|
3137
|
+
writeFileSync4(DISMISS_FILE, JSON.stringify(data), "utf-8");
|
|
3050
3138
|
}
|
|
3051
3139
|
function isGlassboxGitignored(repoRoot) {
|
|
3052
3140
|
const result = spawnSync5("git", ["check-ignore", "-q", ".glassbox"], { cwd: repoRoot, stdio: "pipe" });
|
|
@@ -3063,16 +3151,16 @@ function shouldPromptGitignore(repoRoot) {
|
|
|
3063
3151
|
return true;
|
|
3064
3152
|
}
|
|
3065
3153
|
function addGlassboxToGitignore(repoRoot) {
|
|
3066
|
-
const gitignorePath =
|
|
3067
|
-
if (
|
|
3068
|
-
const content =
|
|
3154
|
+
const gitignorePath = join5(repoRoot, ".gitignore");
|
|
3155
|
+
if (existsSync4(gitignorePath)) {
|
|
3156
|
+
const content = readFileSync5(gitignorePath, "utf-8");
|
|
3069
3157
|
if (!content.endsWith("\n")) {
|
|
3070
3158
|
appendFileSync(gitignorePath, "\n.glassbox/\n", "utf-8");
|
|
3071
3159
|
} else {
|
|
3072
3160
|
appendFileSync(gitignorePath, ".glassbox/\n", "utf-8");
|
|
3073
3161
|
}
|
|
3074
3162
|
} else {
|
|
3075
|
-
|
|
3163
|
+
writeFileSync4(gitignorePath, ".glassbox/\n", "utf-8");
|
|
3076
3164
|
}
|
|
3077
3165
|
}
|
|
3078
3166
|
function dismissGitignorePrompt(repoRoot) {
|
|
@@ -3081,16 +3169,16 @@ function dismissGitignorePrompt(repoRoot) {
|
|
|
3081
3169
|
saveDismissals(dismissals);
|
|
3082
3170
|
}
|
|
3083
3171
|
function deleteReviewExport(reviewId, repoRoot) {
|
|
3084
|
-
const exportDir =
|
|
3085
|
-
const archivePath =
|
|
3086
|
-
if (
|
|
3172
|
+
const exportDir = join5(repoRoot, ".glassbox");
|
|
3173
|
+
const archivePath = join5(exportDir, `review-${reviewId}.md`);
|
|
3174
|
+
if (existsSync4(archivePath)) unlinkSync(archivePath);
|
|
3087
3175
|
}
|
|
3088
3176
|
async function generateReviewExport(reviewId, repoRoot, isCurrent) {
|
|
3089
3177
|
const review = await getReview(reviewId);
|
|
3090
3178
|
if (!review) throw new Error("Review not found");
|
|
3091
3179
|
const files = await getReviewFiles(reviewId);
|
|
3092
3180
|
const annotations = await getAnnotationsForReview(reviewId);
|
|
3093
|
-
const exportDir =
|
|
3181
|
+
const exportDir = join5(repoRoot, ".glassbox");
|
|
3094
3182
|
mkdirSync3(exportDir, { recursive: true });
|
|
3095
3183
|
const byFile = {};
|
|
3096
3184
|
for (const a of annotations) {
|
|
@@ -3156,11 +3244,11 @@ async function generateReviewExport(reviewId, repoRoot, isCurrent) {
|
|
|
3156
3244
|
lines.push("6. **note** annotations are informational context. Consider them but they may not require code changes.");
|
|
3157
3245
|
lines.push("");
|
|
3158
3246
|
const content = lines.join("\n");
|
|
3159
|
-
const archivePath =
|
|
3160
|
-
|
|
3247
|
+
const archivePath = join5(exportDir, `review-${review.id}.md`);
|
|
3248
|
+
writeFileSync4(archivePath, content, "utf-8");
|
|
3161
3249
|
if (isCurrent) {
|
|
3162
|
-
const latestPath =
|
|
3163
|
-
|
|
3250
|
+
const latestPath = join5(exportDir, "latest-review.md");
|
|
3251
|
+
writeFileSync4(latestPath, content, "utf-8");
|
|
3164
3252
|
return latestPath;
|
|
3165
3253
|
}
|
|
3166
3254
|
return archivePath;
|
|
@@ -3179,8 +3267,8 @@ function scheduleAutoExport(reviewId, repoRoot) {
|
|
|
3179
3267
|
|
|
3180
3268
|
// src/git/image.ts
|
|
3181
3269
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
3182
|
-
import { readFileSync as
|
|
3183
|
-
import { resolve as
|
|
3270
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
3271
|
+
import { resolve as resolve3 } from "path";
|
|
3184
3272
|
|
|
3185
3273
|
// src/git/image-metadata.ts
|
|
3186
3274
|
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"]);
|
|
@@ -3450,7 +3538,7 @@ function gitShowFile(ref, filePath, repoRoot) {
|
|
|
3450
3538
|
}
|
|
3451
3539
|
function readWorkingFile(filePath, repoRoot) {
|
|
3452
3540
|
try {
|
|
3453
|
-
return
|
|
3541
|
+
return readFileSync6(resolve3(repoRoot, filePath));
|
|
3454
3542
|
} catch {
|
|
3455
3543
|
return null;
|
|
3456
3544
|
}
|
|
@@ -3486,9 +3574,9 @@ function getNewImage(mode, filePath, repoRoot) {
|
|
|
3486
3574
|
}
|
|
3487
3575
|
|
|
3488
3576
|
// src/git/svg-rasterize.ts
|
|
3489
|
-
import { existsSync as
|
|
3577
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
|
|
3490
3578
|
import { createRequire } from "module";
|
|
3491
|
-
import { join as
|
|
3579
|
+
import { join as join6 } from "path";
|
|
3492
3580
|
var initialized = false;
|
|
3493
3581
|
var ResvgClass;
|
|
3494
3582
|
var fontBuffers = [];
|
|
@@ -3497,7 +3585,7 @@ async function ensureInit() {
|
|
|
3497
3585
|
const require2 = createRequire(import.meta.url);
|
|
3498
3586
|
const resvgPath = require2.resolve("@resvg/resvg-wasm");
|
|
3499
3587
|
const wasmPath = resvgPath.replace(/index\.(js|mjs)$/, "index_bg.wasm");
|
|
3500
|
-
const wasmBuffer =
|
|
3588
|
+
const wasmBuffer = readFileSync7(wasmPath);
|
|
3501
3589
|
const mod = await import("@resvg/resvg-wasm");
|
|
3502
3590
|
await mod.initWasm(wasmBuffer);
|
|
3503
3591
|
ResvgClass = mod.Resvg;
|
|
@@ -3508,9 +3596,9 @@ function loadSystemFonts() {
|
|
|
3508
3596
|
const buffers = [];
|
|
3509
3597
|
const candidates = getFontCandidates();
|
|
3510
3598
|
for (const path of candidates) {
|
|
3511
|
-
if (!
|
|
3599
|
+
if (!existsSync5(path)) continue;
|
|
3512
3600
|
try {
|
|
3513
|
-
buffers.push(
|
|
3601
|
+
buffers.push(readFileSync7(path));
|
|
3514
3602
|
} catch {
|
|
3515
3603
|
}
|
|
3516
3604
|
}
|
|
@@ -3523,24 +3611,24 @@ function getFontCandidates() {
|
|
|
3523
3611
|
const sup = "/System/Library/Fonts/Supplemental";
|
|
3524
3612
|
return [
|
|
3525
3613
|
// Core system fonts (serif, sans-serif, monospace)
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3614
|
+
join6(sys, "Helvetica.ttc"),
|
|
3615
|
+
join6(sys, "Times.ttc"),
|
|
3616
|
+
join6(sys, "Courier.ttc"),
|
|
3617
|
+
join6(sys, "Menlo.ttc"),
|
|
3618
|
+
join6(sys, "SFPro.ttf"),
|
|
3619
|
+
join6(sys, "SFNS.ttf"),
|
|
3620
|
+
join6(sys, "SFNSMono.ttf"),
|
|
3533
3621
|
// Supplemental (common named fonts in SVGs)
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3622
|
+
join6(sup, "Arial.ttf"),
|
|
3623
|
+
join6(sup, "Arial Bold.ttf"),
|
|
3624
|
+
join6(sup, "Georgia.ttf"),
|
|
3625
|
+
join6(sup, "Verdana.ttf"),
|
|
3626
|
+
join6(sup, "Tahoma.ttf"),
|
|
3627
|
+
join6(sup, "Trebuchet MS.ttf"),
|
|
3628
|
+
join6(sup, "Impact.ttf"),
|
|
3629
|
+
join6(sup, "Comic Sans MS.ttf"),
|
|
3630
|
+
join6(sup, "Courier New.ttf"),
|
|
3631
|
+
join6(sup, "Times New Roman.ttf")
|
|
3544
3632
|
];
|
|
3545
3633
|
}
|
|
3546
3634
|
if (os === "linux") {
|
|
@@ -3559,17 +3647,17 @@ function getFontCandidates() {
|
|
|
3559
3647
|
];
|
|
3560
3648
|
}
|
|
3561
3649
|
if (os === "win32") {
|
|
3562
|
-
const winFonts =
|
|
3650
|
+
const winFonts = join6(process.env.WINDIR ?? "C:\\Windows", "Fonts");
|
|
3563
3651
|
return [
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3652
|
+
join6(winFonts, "arial.ttf"),
|
|
3653
|
+
join6(winFonts, "arialbd.ttf"),
|
|
3654
|
+
join6(winFonts, "times.ttf"),
|
|
3655
|
+
join6(winFonts, "cour.ttf"),
|
|
3656
|
+
join6(winFonts, "verdana.ttf"),
|
|
3657
|
+
join6(winFonts, "tahoma.ttf"),
|
|
3658
|
+
join6(winFonts, "georgia.ttf"),
|
|
3659
|
+
join6(winFonts, "consola.ttf"),
|
|
3660
|
+
join6(winFonts, "segoeui.ttf")
|
|
3573
3661
|
];
|
|
3574
3662
|
}
|
|
3575
3663
|
return [];
|
|
@@ -4056,14 +4144,14 @@ apiRoutes.post("/files/:fileId/reveal", async (c) => {
|
|
|
4056
4144
|
const file = await getReviewFile(c.req.param("fileId"));
|
|
4057
4145
|
if (!file) return c.json({ error: "Not found" }, 404);
|
|
4058
4146
|
const repoRoot = c.get("repoRoot");
|
|
4059
|
-
const fullPath =
|
|
4147
|
+
const fullPath = resolve4(repoRoot, file.file_path);
|
|
4060
4148
|
try {
|
|
4061
4149
|
if (process.platform === "darwin") {
|
|
4062
4150
|
execFileSync("open", ["-R", fullPath]);
|
|
4063
4151
|
} else if (process.platform === "win32") {
|
|
4064
4152
|
execFileSync("explorer", ["/select," + fullPath]);
|
|
4065
4153
|
} else {
|
|
4066
|
-
execFileSync("xdg-open", [
|
|
4154
|
+
execFileSync("xdg-open", [resolve4(fullPath, "..")]);
|
|
4067
4155
|
}
|
|
4068
4156
|
} catch {
|
|
4069
4157
|
}
|
|
@@ -4201,7 +4289,7 @@ apiRoutes.get("/symbol-definition", async (c) => {
|
|
|
4201
4289
|
if (!/\.(js|mjs|cjs|jsx|ts|tsx|mts|cts|java|go|rs|c|h|cpp|cc|cxx|hpp|cs|swift|php|kt|kts|scala|dart|groovy|py|rb)$/i.test(ext)) continue;
|
|
4202
4290
|
let content = "";
|
|
4203
4291
|
try {
|
|
4204
|
-
content =
|
|
4292
|
+
content = readFileSync8(resolve4(repoRoot, filePath), "utf-8");
|
|
4205
4293
|
} catch {
|
|
4206
4294
|
continue;
|
|
4207
4295
|
}
|
|
@@ -4251,19 +4339,19 @@ apiRoutes.get("/context/:fileId", async (c) => {
|
|
|
4251
4339
|
return c.json({ lines });
|
|
4252
4340
|
});
|
|
4253
4341
|
function readProjectSettings(repoRoot) {
|
|
4254
|
-
const settingsPath =
|
|
4342
|
+
const settingsPath = join7(repoRoot, ".glassbox", "settings.json");
|
|
4255
4343
|
try {
|
|
4256
|
-
if (
|
|
4257
|
-
return JSON.parse(
|
|
4344
|
+
if (existsSync6(settingsPath)) {
|
|
4345
|
+
return JSON.parse(readFileSync8(settingsPath, "utf-8"));
|
|
4258
4346
|
}
|
|
4259
4347
|
} catch {
|
|
4260
4348
|
}
|
|
4261
4349
|
return {};
|
|
4262
4350
|
}
|
|
4263
4351
|
function writeProjectSettings(repoRoot, settings) {
|
|
4264
|
-
const dir =
|
|
4352
|
+
const dir = join7(repoRoot, ".glassbox");
|
|
4265
4353
|
mkdirSync4(dir, { recursive: true });
|
|
4266
|
-
|
|
4354
|
+
writeFileSync5(join7(dir, "settings.json"), JSON.stringify(settings, null, 2), "utf-8");
|
|
4267
4355
|
}
|
|
4268
4356
|
apiRoutes.get("/project-settings", (c) => {
|
|
4269
4357
|
const repoRoot = c.get("repoRoot");
|
|
@@ -4330,19 +4418,19 @@ apiRoutes.get("/image/:fileId/:side", async (c) => {
|
|
|
4330
4418
|
});
|
|
4331
4419
|
});
|
|
4332
4420
|
function readGlobalConfig() {
|
|
4333
|
-
const configPath =
|
|
4421
|
+
const configPath = join7(homedir3(), ".glassbox", "config.json");
|
|
4334
4422
|
try {
|
|
4335
|
-
if (
|
|
4336
|
-
return JSON.parse(
|
|
4423
|
+
if (existsSync6(configPath)) {
|
|
4424
|
+
return JSON.parse(readFileSync8(configPath, "utf-8"));
|
|
4337
4425
|
}
|
|
4338
4426
|
} catch {
|
|
4339
4427
|
}
|
|
4340
4428
|
return {};
|
|
4341
4429
|
}
|
|
4342
4430
|
function writeGlobalConfig(config) {
|
|
4343
|
-
const configDir =
|
|
4431
|
+
const configDir = join7(homedir3(), ".glassbox");
|
|
4344
4432
|
mkdirSync4(configDir, { recursive: true });
|
|
4345
|
-
|
|
4433
|
+
writeFileSync5(join7(configDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
|
|
4346
4434
|
}
|
|
4347
4435
|
apiRoutes.get("/share-prompt/state", (c) => {
|
|
4348
4436
|
const config = readGlobalConfig();
|
|
@@ -4376,95 +4464,6 @@ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as rea
|
|
|
4376
4464
|
import { Hono as Hono5 } from "hono";
|
|
4377
4465
|
import { homedir as homedir4 } from "os";
|
|
4378
4466
|
import { join as join8 } from "path";
|
|
4379
|
-
|
|
4380
|
-
// src/channel-config.ts
|
|
4381
|
-
import { existsSync as existsSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
4382
|
-
import { dirname, join as join7, resolve as resolve4 } from "path";
|
|
4383
|
-
import { fileURLToPath } from "url";
|
|
4384
|
-
var MCP_SERVER_KEY = "glassbox-channel";
|
|
4385
|
-
function getChannelServerPath() {
|
|
4386
|
-
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
4387
|
-
const distPath = resolve4(thisDir, "channel.js");
|
|
4388
|
-
if (existsSync6(distPath)) {
|
|
4389
|
-
return { command: process.execPath, args: [distPath] };
|
|
4390
|
-
}
|
|
4391
|
-
const srcPath = resolve4(thisDir, "channel.ts");
|
|
4392
|
-
if (existsSync6(srcPath)) {
|
|
4393
|
-
return { command: "npx", args: ["tsx", srcPath] };
|
|
4394
|
-
}
|
|
4395
|
-
return { command: process.execPath, args: [distPath] };
|
|
4396
|
-
}
|
|
4397
|
-
function projectRoot(dataDir) {
|
|
4398
|
-
return dataDir.replace(/\/.glassbox\/?$/, "");
|
|
4399
|
-
}
|
|
4400
|
-
function registerChannel(dataDir) {
|
|
4401
|
-
const root = projectRoot(dataDir);
|
|
4402
|
-
const mcpPath = join7(root, ".mcp.json");
|
|
4403
|
-
const { command, args } = getChannelServerPath();
|
|
4404
|
-
let config = {};
|
|
4405
|
-
if (existsSync6(mcpPath)) {
|
|
4406
|
-
try {
|
|
4407
|
-
config = JSON.parse(readFileSync8(mcpPath, "utf-8"));
|
|
4408
|
-
} catch {
|
|
4409
|
-
}
|
|
4410
|
-
}
|
|
4411
|
-
if (config.mcpServers === void 0) config.mcpServers = {};
|
|
4412
|
-
config.mcpServers[MCP_SERVER_KEY] = {
|
|
4413
|
-
command,
|
|
4414
|
-
args: [...args, "--data-dir", dataDir]
|
|
4415
|
-
};
|
|
4416
|
-
writeFileSync5(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
4417
|
-
}
|
|
4418
|
-
function unregisterChannel(dataDir) {
|
|
4419
|
-
const root = projectRoot(dataDir);
|
|
4420
|
-
const mcpPath = join7(root, ".mcp.json");
|
|
4421
|
-
if (!existsSync6(mcpPath)) return;
|
|
4422
|
-
try {
|
|
4423
|
-
const config = JSON.parse(readFileSync8(mcpPath, "utf-8"));
|
|
4424
|
-
if (config.mcpServers?.[MCP_SERVER_KEY] !== void 0) {
|
|
4425
|
-
const servers = { ...config.mcpServers };
|
|
4426
|
-
delete servers[MCP_SERVER_KEY];
|
|
4427
|
-
config.mcpServers = servers;
|
|
4428
|
-
writeFileSync5(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
4429
|
-
}
|
|
4430
|
-
} catch {
|
|
4431
|
-
}
|
|
4432
|
-
}
|
|
4433
|
-
function getChannelPort(dataDir) {
|
|
4434
|
-
try {
|
|
4435
|
-
const portStr = readFileSync8(join7(dataDir, "channel-port"), "utf-8").trim();
|
|
4436
|
-
const port = parseInt(portStr, 10);
|
|
4437
|
-
return isNaN(port) ? null : port;
|
|
4438
|
-
} catch {
|
|
4439
|
-
return null;
|
|
4440
|
-
}
|
|
4441
|
-
}
|
|
4442
|
-
async function isChannelAlive(dataDir) {
|
|
4443
|
-
const port = getChannelPort(dataDir);
|
|
4444
|
-
if (port === null) return false;
|
|
4445
|
-
try {
|
|
4446
|
-
const res = await fetch(`http://127.0.0.1:${port}/health`);
|
|
4447
|
-
const data = await res.json();
|
|
4448
|
-
return data.ok;
|
|
4449
|
-
} catch {
|
|
4450
|
-
return false;
|
|
4451
|
-
}
|
|
4452
|
-
}
|
|
4453
|
-
async function triggerChannel(dataDir, message) {
|
|
4454
|
-
const port = getChannelPort(dataDir);
|
|
4455
|
-
if (port === null) return false;
|
|
4456
|
-
try {
|
|
4457
|
-
const res = await fetch(`http://127.0.0.1:${port}/trigger`, {
|
|
4458
|
-
method: "POST",
|
|
4459
|
-
body: message
|
|
4460
|
-
});
|
|
4461
|
-
return res.ok;
|
|
4462
|
-
} catch {
|
|
4463
|
-
return false;
|
|
4464
|
-
}
|
|
4465
|
-
}
|
|
4466
|
-
|
|
4467
|
-
// src/routes/channel-api.ts
|
|
4468
4467
|
var channelApiRoutes = new Hono5();
|
|
4469
4468
|
var CONFIG_DIR2 = join8(homedir4(), ".glassbox");
|
|
4470
4469
|
var CONFIG_PATH2 = join8(CONFIG_DIR2, "config.json");
|
|
@@ -6447,6 +6446,17 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
6447
6446
|
console.log(`
|
|
6448
6447
|
Glassbox running at ${url}
|
|
6449
6448
|
`);
|
|
6449
|
+
try {
|
|
6450
|
+
const homePath = join10(homedir6(), ".glassbox", "config.json");
|
|
6451
|
+
if (existsSync9(homePath)) {
|
|
6452
|
+
const globalConfig = JSON.parse(readFileSync12(homePath, "utf-8"));
|
|
6453
|
+
if (globalConfig.channelEnabled === true) {
|
|
6454
|
+
const dataDir = join10(repoRoot, ".glassbox");
|
|
6455
|
+
registerChannel(dataDir);
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
} catch {
|
|
6459
|
+
}
|
|
6450
6460
|
if (options?.noOpen !== true) {
|
|
6451
6461
|
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
6452
6462
|
exec(`${openCmd} ${url}`);
|
|
@@ -6573,10 +6583,10 @@ function ensureSkills() {
|
|
|
6573
6583
|
// src/update-check.ts
|
|
6574
6584
|
import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
6575
6585
|
import { get } from "https";
|
|
6576
|
-
import { homedir as
|
|
6586
|
+
import { homedir as homedir7 } from "os";
|
|
6577
6587
|
import { dirname as dirname3, join as join12 } from "path";
|
|
6578
6588
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6579
|
-
var DATA_DIR = join12(
|
|
6589
|
+
var DATA_DIR = join12(homedir7(), ".glassbox");
|
|
6580
6590
|
var CHECK_FILE = join12(DATA_DIR, "last-update-check");
|
|
6581
6591
|
var PACKAGE_NAME = "glassbox";
|
|
6582
6592
|
function getCurrentVersion() {
|
|
@@ -6825,7 +6835,7 @@ async function main() {
|
|
|
6825
6835
|
console.log("AI service test mode enabled \u2014 using mock AI responses");
|
|
6826
6836
|
}
|
|
6827
6837
|
if (debug) {
|
|
6828
|
-
console.log(`[debug] Build timestamp: ${"2026-04-
|
|
6838
|
+
console.log(`[debug] Build timestamp: ${"2026-04-08T04:45:56.501Z"}`);
|
|
6829
6839
|
}
|
|
6830
6840
|
if (projectDir !== null) {
|
|
6831
6841
|
process.chdir(projectDir);
|
package/package.json
CHANGED