glassbox 0.19.0 → 0.20.0-beta.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 +1889 -414
- package/dist/client/app.global.js +18 -18
- package/dist/client/favicon.svg +60 -0
- package/dist/client/styles.css +1 -1
- package/dist/plugins/graphviz/index.js +7227 -0
- package/dist/plugins/graphviz/manifest.json +31 -0
- package/dist/plugins/image-codecs/index.js +3 -0
- package/dist/plugins/image-codecs/manifest.json +10 -0
- package/dist/plugins/mermaid/index.js +1 -0
- package/dist/plugins/mermaid/manifest.json +31 -0
- package/dist/plugins/plantuml/index.js +1 -0
- package/dist/plugins/plantuml/manifest.json +30 -0
- package/package.json +10 -6
package/dist/cli.js
CHANGED
|
@@ -11937,21 +11937,21 @@ var init_to_json_schema = __esm({
|
|
|
11937
11937
|
// node_modules/zod/v4/core/json-schema-processors.js
|
|
11938
11938
|
function toJSONSchema(input, params) {
|
|
11939
11939
|
if ("_idmap" in input) {
|
|
11940
|
-
const
|
|
11940
|
+
const registry3 = input;
|
|
11941
11941
|
const ctx2 = initializeContext({ ...params, processors: allProcessors });
|
|
11942
11942
|
const defs = {};
|
|
11943
|
-
for (const entry of
|
|
11943
|
+
for (const entry of registry3._idmap.entries()) {
|
|
11944
11944
|
const [_, schema] = entry;
|
|
11945
11945
|
process2(schema, ctx2);
|
|
11946
11946
|
}
|
|
11947
11947
|
const schemas = {};
|
|
11948
11948
|
const external = {
|
|
11949
|
-
registry:
|
|
11949
|
+
registry: registry3,
|
|
11950
11950
|
uri: params?.uri,
|
|
11951
11951
|
defs
|
|
11952
11952
|
};
|
|
11953
11953
|
ctx2.external = external;
|
|
11954
|
-
for (const entry of
|
|
11954
|
+
for (const entry of registry3._idmap.entries()) {
|
|
11955
11955
|
const [key, schema] = entry;
|
|
11956
11956
|
extractDefs(ctx2, schema);
|
|
11957
11957
|
schemas[key] = finalize(ctx2, schema);
|
|
@@ -17254,6 +17254,178 @@ var init_debug = __esm({
|
|
|
17254
17254
|
}
|
|
17255
17255
|
});
|
|
17256
17256
|
|
|
17257
|
+
// src/global-config.ts
|
|
17258
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
17259
|
+
import { homedir } from "os";
|
|
17260
|
+
import { join as join4 } from "path";
|
|
17261
|
+
function resolveGlobalConfigDir() {
|
|
17262
|
+
const override = process.env.GLASSBOX_CONFIG_DIR;
|
|
17263
|
+
if (override !== void 0 && override.trim() !== "") return override;
|
|
17264
|
+
return join4(homedir(), ".glassbox");
|
|
17265
|
+
}
|
|
17266
|
+
function readGlobalConfig() {
|
|
17267
|
+
try {
|
|
17268
|
+
if (existsSync4(GLOBAL_CONFIG_PATH)) {
|
|
17269
|
+
const raw2 = JSON.parse(readFileSync3(GLOBAL_CONFIG_PATH, "utf-8"));
|
|
17270
|
+
const parsed = GlobalConfigSchema.safeParse(raw2);
|
|
17271
|
+
if (parsed.success) return parsed.data;
|
|
17272
|
+
}
|
|
17273
|
+
} catch {
|
|
17274
|
+
}
|
|
17275
|
+
return {};
|
|
17276
|
+
}
|
|
17277
|
+
function writeGlobalConfig(config2) {
|
|
17278
|
+
mkdirSync5(GLOBAL_CONFIG_DIR, { recursive: true });
|
|
17279
|
+
writeFileSync3(GLOBAL_CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
17280
|
+
try {
|
|
17281
|
+
chmodSync(GLOBAL_CONFIG_PATH, 384);
|
|
17282
|
+
} catch {
|
|
17283
|
+
}
|
|
17284
|
+
}
|
|
17285
|
+
function updateGlobalConfig(mutator) {
|
|
17286
|
+
const cfg = readGlobalConfig();
|
|
17287
|
+
const result = mutator(cfg);
|
|
17288
|
+
writeGlobalConfig(result === void 0 ? cfg : result);
|
|
17289
|
+
}
|
|
17290
|
+
var GlobalConfigSchema, GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_PATH;
|
|
17291
|
+
var init_global_config = __esm({
|
|
17292
|
+
"src/global-config.ts"() {
|
|
17293
|
+
"use strict";
|
|
17294
|
+
init_zod();
|
|
17295
|
+
GlobalConfigSchema = external_exports.record(external_exports.string(), external_exports.unknown());
|
|
17296
|
+
GLOBAL_CONFIG_DIR = resolveGlobalConfigDir();
|
|
17297
|
+
GLOBAL_CONFIG_PATH = join4(GLOBAL_CONFIG_DIR, "config.json");
|
|
17298
|
+
}
|
|
17299
|
+
});
|
|
17300
|
+
|
|
17301
|
+
// src/ai/keychain.ts
|
|
17302
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
17303
|
+
function winTargetForAccount(account) {
|
|
17304
|
+
return `glassbox-${account}`;
|
|
17305
|
+
}
|
|
17306
|
+
function winCredTarget(platform) {
|
|
17307
|
+
return winTargetForAccount(`${platform}-api-key`);
|
|
17308
|
+
}
|
|
17309
|
+
function getSecretFromKeychain(account) {
|
|
17310
|
+
const os = process.platform;
|
|
17311
|
+
try {
|
|
17312
|
+
if (os === "darwin") {
|
|
17313
|
+
const r = spawnSync5("security", ["find-generic-password", "-s", "glassbox", "-a", account, "-w"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
17314
|
+
const result = r.stdout.trim();
|
|
17315
|
+
return r.status === 0 && result !== "" ? result : null;
|
|
17316
|
+
}
|
|
17317
|
+
if (os === "linux") {
|
|
17318
|
+
const r = spawnSync5("secret-tool", ["lookup", "service", "glassbox", "account", account], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
17319
|
+
const result = r.stdout.trim();
|
|
17320
|
+
return r.status === 0 && result !== "" ? result : null;
|
|
17321
|
+
}
|
|
17322
|
+
if (os === "win32") {
|
|
17323
|
+
const target = winTargetForAccount(account);
|
|
17324
|
+
const list = spawnSync5("cmdkey", ["/list:" + target], { encoding: "utf-8" });
|
|
17325
|
+
if (list.status !== 0 || (list.stdout || "").includes("* NONE *")) return null;
|
|
17326
|
+
const script = WIN_CRED_READ_PS + `Write-Output ([CredHelper]::Read('${target}'))`;
|
|
17327
|
+
const r = spawnSync5("powershell", ["-NoProfile", "-Command", "-"], { input: script, encoding: "utf-8" });
|
|
17328
|
+
const result = r.stdout.trim();
|
|
17329
|
+
return r.status === 0 && result !== "" ? result : null;
|
|
17330
|
+
}
|
|
17331
|
+
} catch {
|
|
17332
|
+
return null;
|
|
17333
|
+
}
|
|
17334
|
+
return null;
|
|
17335
|
+
}
|
|
17336
|
+
function getKeyFromKeychain(platform) {
|
|
17337
|
+
return getSecretFromKeychain(`${platform}-api-key`);
|
|
17338
|
+
}
|
|
17339
|
+
function assertSpawnOk(label, r) {
|
|
17340
|
+
if (r.error) throw new Error(`${label} failed: ${r.error.message}`);
|
|
17341
|
+
if (r.status !== 0) {
|
|
17342
|
+
const detail = (String(r.stderr) || String(r.stdout)).trim();
|
|
17343
|
+
throw new Error(`${label} failed (exit ${String(r.status)})${detail ? `: ${detail}` : ""}`);
|
|
17344
|
+
}
|
|
17345
|
+
}
|
|
17346
|
+
function saveSecretToKeychain(account, value, label = "Glassbox") {
|
|
17347
|
+
const os = process.platform;
|
|
17348
|
+
if (os === "darwin") {
|
|
17349
|
+
spawnSync5("security", ["delete-generic-password", "-s", "glassbox", "-a", account], { stdio: "pipe" });
|
|
17350
|
+
assertSpawnOk("Keychain write", spawnSync5("security", ["add-generic-password", "-s", "glassbox", "-a", account, "-w", value], { encoding: "utf-8" }));
|
|
17351
|
+
return;
|
|
17352
|
+
}
|
|
17353
|
+
if (os === "linux") {
|
|
17354
|
+
assertSpawnOk("System keyring write", spawnSync5("secret-tool", ["store", `--label=${label}`, "service", "glassbox", "account", account], { input: value, encoding: "utf-8" }));
|
|
17355
|
+
return;
|
|
17356
|
+
}
|
|
17357
|
+
if (os === "win32") {
|
|
17358
|
+
const target = winTargetForAccount(account);
|
|
17359
|
+
const escapedValue = value.replace(/'/g, "''");
|
|
17360
|
+
const script = `cmdkey /generic:'${target}' /user:'glassbox' /pass:'${escapedValue}'`;
|
|
17361
|
+
assertSpawnOk("Credential Manager write", spawnSync5("powershell", ["-NoProfile", "-Command", "-"], { input: script, encoding: "utf-8" }));
|
|
17362
|
+
}
|
|
17363
|
+
}
|
|
17364
|
+
function deleteSecretFromKeychain(account) {
|
|
17365
|
+
const os = process.platform;
|
|
17366
|
+
try {
|
|
17367
|
+
if (os === "darwin") {
|
|
17368
|
+
spawnSync5("security", ["delete-generic-password", "-s", "glassbox", "-a", account], { stdio: "pipe" });
|
|
17369
|
+
} else if (os === "linux") {
|
|
17370
|
+
spawnSync5("secret-tool", ["clear", "service", "glassbox", "account", account], { stdio: "pipe" });
|
|
17371
|
+
} else if (os === "win32") {
|
|
17372
|
+
spawnSync5("powershell", ["-NoProfile", "-Command", "-"], { input: `cmdkey /delete:'${winTargetForAccount(account)}'`, encoding: "utf-8" });
|
|
17373
|
+
}
|
|
17374
|
+
} catch {
|
|
17375
|
+
}
|
|
17376
|
+
}
|
|
17377
|
+
function saveKeyToKeychain(platform, key) {
|
|
17378
|
+
saveSecretToKeychain(`${platform}-api-key`, key, "Glassbox API Key");
|
|
17379
|
+
}
|
|
17380
|
+
function isKeychainAvailable() {
|
|
17381
|
+
const os = process.platform;
|
|
17382
|
+
if (os === "darwin" || os === "win32") return true;
|
|
17383
|
+
if (os === "linux") {
|
|
17384
|
+
return spawnSync5("which", ["secret-tool"], { stdio: "pipe" }).status === 0;
|
|
17385
|
+
}
|
|
17386
|
+
return false;
|
|
17387
|
+
}
|
|
17388
|
+
function getKeychainLabel() {
|
|
17389
|
+
const os = process.platform;
|
|
17390
|
+
if (os === "darwin") return "Keychain";
|
|
17391
|
+
if (os === "linux") return "System Keyring";
|
|
17392
|
+
if (os === "win32") return "Credential Manager";
|
|
17393
|
+
return "System Keychain";
|
|
17394
|
+
}
|
|
17395
|
+
var WIN_CRED_READ_PS;
|
|
17396
|
+
var init_keychain = __esm({
|
|
17397
|
+
"src/ai/keychain.ts"() {
|
|
17398
|
+
"use strict";
|
|
17399
|
+
WIN_CRED_READ_PS = `
|
|
17400
|
+
Add-Type -TypeDefinition @'
|
|
17401
|
+
using System;
|
|
17402
|
+
using System.Runtime.InteropServices;
|
|
17403
|
+
public class CredHelper {
|
|
17404
|
+
[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
17405
|
+
static extern bool CredRead(string t, int type, int f, out IntPtr p);
|
|
17406
|
+
[DllImport("advapi32")]
|
|
17407
|
+
static extern void CredFree(IntPtr p);
|
|
17408
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
17409
|
+
struct CRED {
|
|
17410
|
+
public int Flags; public int Type; public string TargetName; public string Comment;
|
|
17411
|
+
public long LastWritten; public int CredentialBlobSize; public IntPtr CredentialBlob;
|
|
17412
|
+
public int Persist; public int AttributeCount; public IntPtr Attributes;
|
|
17413
|
+
public string TargetAlias; public string UserName;
|
|
17414
|
+
}
|
|
17415
|
+
public static string Read(string target) {
|
|
17416
|
+
IntPtr ptr;
|
|
17417
|
+
if (!CredRead(target, 1, 0, out ptr)) return "";
|
|
17418
|
+
CRED c = (CRED)Marshal.PtrToStructure(ptr, typeof(CRED));
|
|
17419
|
+
string r = Marshal.PtrToStringUni(c.CredentialBlob, c.CredentialBlobSize / 2);
|
|
17420
|
+
CredFree(ptr);
|
|
17421
|
+
return r;
|
|
17422
|
+
}
|
|
17423
|
+
}
|
|
17424
|
+
'@
|
|
17425
|
+
`;
|
|
17426
|
+
}
|
|
17427
|
+
});
|
|
17428
|
+
|
|
17257
17429
|
// src/git/image-blobs.ts
|
|
17258
17430
|
var image_blobs_exports = {};
|
|
17259
17431
|
__export(image_blobs_exports, {
|
|
@@ -17266,33 +17438,1004 @@ import { join as join5 } from "path";
|
|
|
17266
17438
|
function blobDir(dataDir) {
|
|
17267
17439
|
return join5(dataDir, "image-blobs");
|
|
17268
17440
|
}
|
|
17269
|
-
function blobName(fileId, side) {
|
|
17270
|
-
return `${fileId.replace(/[^a-z0-9]/gi, "")}-${side}`;
|
|
17441
|
+
function blobName(fileId, side) {
|
|
17442
|
+
return `${fileId.replace(/[^a-z0-9]/gi, "")}-${side}`;
|
|
17443
|
+
}
|
|
17444
|
+
function writeImageBlob(dataDir, fileId, side, bytes) {
|
|
17445
|
+
if (bytes.length === 0) return;
|
|
17446
|
+
const dir = blobDir(dataDir);
|
|
17447
|
+
mkdirSync6(dir, { recursive: true });
|
|
17448
|
+
writeFileSync4(join5(dir, blobName(fileId, side)), bytes);
|
|
17449
|
+
}
|
|
17450
|
+
function readImageBlob(dataDir, fileId, side) {
|
|
17451
|
+
const path = join5(blobDir(dataDir), blobName(fileId, side));
|
|
17452
|
+
if (!existsSync5(path)) return null;
|
|
17453
|
+
try {
|
|
17454
|
+
return readFileSync4(path);
|
|
17455
|
+
} catch {
|
|
17456
|
+
return null;
|
|
17457
|
+
}
|
|
17458
|
+
}
|
|
17459
|
+
function clearImageBlobs(dataDir) {
|
|
17460
|
+
try {
|
|
17461
|
+
rmSync3(blobDir(dataDir), { recursive: true, force: true });
|
|
17462
|
+
} catch {
|
|
17463
|
+
}
|
|
17464
|
+
}
|
|
17465
|
+
var init_image_blobs = __esm({
|
|
17466
|
+
"src/git/image-blobs.ts"() {
|
|
17467
|
+
"use strict";
|
|
17468
|
+
}
|
|
17469
|
+
});
|
|
17470
|
+
|
|
17471
|
+
// src/feature-flags.ts
|
|
17472
|
+
var PLUGINS_ENABLED;
|
|
17473
|
+
var init_feature_flags = __esm({
|
|
17474
|
+
"src/feature-flags.ts"() {
|
|
17475
|
+
"use strict";
|
|
17476
|
+
PLUGINS_ENABLED = process.env.GLASSBOX_PLUGINS_DISABLED === "1" ? false : true;
|
|
17477
|
+
}
|
|
17478
|
+
});
|
|
17479
|
+
|
|
17480
|
+
// src/api/_runner.ts
|
|
17481
|
+
function currentReviewId() {
|
|
17482
|
+
if (typeof document === "undefined") return "";
|
|
17483
|
+
return document.body.dataset.reviewId ?? "";
|
|
17484
|
+
}
|
|
17485
|
+
async function apiCall(responseSchema, path, opts = {}) {
|
|
17486
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
17487
|
+
const url2 = "/api" + path + separator + "reviewId=" + encodeURIComponent(currentReviewId());
|
|
17488
|
+
const res = await fetch(url2, {
|
|
17489
|
+
headers: { "Content-Type": "application/json" },
|
|
17490
|
+
method: opts.method,
|
|
17491
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
17492
|
+
});
|
|
17493
|
+
const json2 = await res.json();
|
|
17494
|
+
const result = responseSchema.safeParse(json2);
|
|
17495
|
+
if (!result.success) {
|
|
17496
|
+
const summary = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
17497
|
+
throw new Error(`API response from ${path} failed validation: ${summary}`);
|
|
17498
|
+
}
|
|
17499
|
+
return result.data;
|
|
17500
|
+
}
|
|
17501
|
+
function qs(params) {
|
|
17502
|
+
const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== null);
|
|
17503
|
+
if (entries.length === 0) return "";
|
|
17504
|
+
const usp = new URLSearchParams();
|
|
17505
|
+
for (const [k, v] of entries) usp.set(k, String(v));
|
|
17506
|
+
return "?" + usp.toString();
|
|
17507
|
+
}
|
|
17508
|
+
var OkResponseSchema;
|
|
17509
|
+
var init_runner = __esm({
|
|
17510
|
+
"src/api/_runner.ts"() {
|
|
17511
|
+
"use strict";
|
|
17512
|
+
init_zod();
|
|
17513
|
+
OkResponseSchema = external_exports.object({ ok: external_exports.literal(true) });
|
|
17514
|
+
}
|
|
17515
|
+
});
|
|
17516
|
+
|
|
17517
|
+
// src/api/project-settings.ts
|
|
17518
|
+
var project_settings_exports = {};
|
|
17519
|
+
__export(project_settings_exports, {
|
|
17520
|
+
GetProjectSettingsRespSchema: () => GetProjectSettingsRespSchema,
|
|
17521
|
+
ProjectSettingsSchema: () => ProjectSettingsSchema,
|
|
17522
|
+
UpdateProjectSettingsReqSchema: () => UpdateProjectSettingsReqSchema,
|
|
17523
|
+
UpdateProjectSettingsRespSchema: () => UpdateProjectSettingsRespSchema,
|
|
17524
|
+
getProjectSettings: () => getProjectSettings,
|
|
17525
|
+
updateProjectSettings: () => updateProjectSettings
|
|
17526
|
+
});
|
|
17527
|
+
async function getProjectSettings() {
|
|
17528
|
+
return apiCall(GetProjectSettingsRespSchema, "/project-settings");
|
|
17529
|
+
}
|
|
17530
|
+
async function updateProjectSettings(req) {
|
|
17531
|
+
return apiCall(UpdateProjectSettingsRespSchema, "/project-settings", { method: "PATCH", body: req });
|
|
17532
|
+
}
|
|
17533
|
+
var ProjectSettingsSchema, GetProjectSettingsRespSchema, UpdateProjectSettingsReqSchema, UpdateProjectSettingsRespSchema;
|
|
17534
|
+
var init_project_settings = __esm({
|
|
17535
|
+
"src/api/project-settings.ts"() {
|
|
17536
|
+
"use strict";
|
|
17537
|
+
init_zod();
|
|
17538
|
+
init_runner();
|
|
17539
|
+
ProjectSettingsSchema = external_exports.object({
|
|
17540
|
+
appName: external_exports.string().optional(),
|
|
17541
|
+
/** Content-plugin ids disabled for THIS project (doc 29 FR-29.16). A plugin is
|
|
17542
|
+
* enabled unless it appears here or in the global disabled list. */
|
|
17543
|
+
disabledPlugins: external_exports.array(external_exports.string()).optional(),
|
|
17544
|
+
/** Per-project plugin preference values (doc 29 FR-29.12): pluginId → key → value. */
|
|
17545
|
+
pluginSettings: external_exports.record(external_exports.string(), external_exports.record(external_exports.string(), external_exports.string())).optional()
|
|
17546
|
+
});
|
|
17547
|
+
GetProjectSettingsRespSchema = ProjectSettingsSchema;
|
|
17548
|
+
UpdateProjectSettingsReqSchema = ProjectSettingsSchema.partial();
|
|
17549
|
+
UpdateProjectSettingsRespSchema = ProjectSettingsSchema;
|
|
17550
|
+
}
|
|
17551
|
+
});
|
|
17552
|
+
|
|
17553
|
+
// src/project-settings-store.ts
|
|
17554
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
17555
|
+
import { join as join10 } from "path";
|
|
17556
|
+
function settingsPath(repoRoot) {
|
|
17557
|
+
return join10(repoRoot, ".glassbox", "settings.json");
|
|
17558
|
+
}
|
|
17559
|
+
function readProjectSettings(repoRoot) {
|
|
17560
|
+
try {
|
|
17561
|
+
const path = settingsPath(repoRoot);
|
|
17562
|
+
if (existsSync10(path)) {
|
|
17563
|
+
const raw2 = JSON.parse(readFileSync9(path, "utf-8"));
|
|
17564
|
+
const parsed = ProjectSettingsSchema.safeParse(raw2);
|
|
17565
|
+
if (parsed.success) return parsed.data;
|
|
17566
|
+
}
|
|
17567
|
+
} catch {
|
|
17568
|
+
}
|
|
17569
|
+
return {};
|
|
17570
|
+
}
|
|
17571
|
+
function updateProjectSettings2(repoRoot, mutator) {
|
|
17572
|
+
const current = readProjectSettings(repoRoot);
|
|
17573
|
+
const result = mutator(current);
|
|
17574
|
+
const next = result === void 0 ? current : result;
|
|
17575
|
+
const dir = join10(repoRoot, ".glassbox");
|
|
17576
|
+
mkdirSync8(dir, { recursive: true });
|
|
17577
|
+
writeFileSync9(join10(dir, "settings.json"), JSON.stringify(next, null, 2), "utf-8");
|
|
17578
|
+
}
|
|
17579
|
+
var init_project_settings_store = __esm({
|
|
17580
|
+
"src/project-settings-store.ts"() {
|
|
17581
|
+
"use strict";
|
|
17582
|
+
init_project_settings();
|
|
17583
|
+
}
|
|
17584
|
+
});
|
|
17585
|
+
|
|
17586
|
+
// src/plugins/enablement.ts
|
|
17587
|
+
function toStringArray(v) {
|
|
17588
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
17589
|
+
}
|
|
17590
|
+
function readGlobalDisabled() {
|
|
17591
|
+
return toStringArray(readGlobalConfig().disabledPlugins);
|
|
17592
|
+
}
|
|
17593
|
+
function setGlobalDisabled(id, disabled) {
|
|
17594
|
+
updateGlobalConfig((cfg) => {
|
|
17595
|
+
const cur = toStringArray(cfg.disabledPlugins);
|
|
17596
|
+
const next = disabled ? [.../* @__PURE__ */ new Set([...cur, id])] : cur.filter((x) => x !== id);
|
|
17597
|
+
return { ...cfg, disabledPlugins: next };
|
|
17598
|
+
});
|
|
17599
|
+
}
|
|
17600
|
+
function readProjectDisabled(repoRoot) {
|
|
17601
|
+
return toStringArray(readProjectSettings(repoRoot).disabledPlugins);
|
|
17602
|
+
}
|
|
17603
|
+
function setProjectDisabled(repoRoot, id, disabled) {
|
|
17604
|
+
updateProjectSettings2(repoRoot, (s) => {
|
|
17605
|
+
const cur = toStringArray(s.disabledPlugins);
|
|
17606
|
+
s.disabledPlugins = disabled ? [.../* @__PURE__ */ new Set([...cur, id])] : cur.filter((x) => x !== id);
|
|
17607
|
+
});
|
|
17608
|
+
}
|
|
17609
|
+
function disabledScope(id, lists) {
|
|
17610
|
+
if (lists.globalDisabled.includes(id)) return "global";
|
|
17611
|
+
if (lists.projectDisabled.includes(id)) return "project";
|
|
17612
|
+
return null;
|
|
17613
|
+
}
|
|
17614
|
+
function readEnablementLists(repoRoot) {
|
|
17615
|
+
return { globalDisabled: readGlobalDisabled(), projectDisabled: readProjectDisabled(repoRoot) };
|
|
17616
|
+
}
|
|
17617
|
+
var init_enablement = __esm({
|
|
17618
|
+
"src/plugins/enablement.ts"() {
|
|
17619
|
+
"use strict";
|
|
17620
|
+
init_global_config();
|
|
17621
|
+
init_project_settings_store();
|
|
17622
|
+
}
|
|
17623
|
+
});
|
|
17624
|
+
|
|
17625
|
+
// src/plugins/manifest.ts
|
|
17626
|
+
function parseManifest(raw2) {
|
|
17627
|
+
const parsed = PluginManifestSchema.safeParse(raw2);
|
|
17628
|
+
return parsed.success ? parsed.data : null;
|
|
17629
|
+
}
|
|
17630
|
+
var ContentTypeSchema, PluginPreferenceSchema, ConfigLabelColorSchema, ConfigLayoutItemSchema, PluginRequirementSchema, PluginProvisionStepSchema, PluginInstallSchema, PluginManifestSchema;
|
|
17631
|
+
var init_manifest2 = __esm({
|
|
17632
|
+
"src/plugins/manifest.ts"() {
|
|
17633
|
+
"use strict";
|
|
17634
|
+
init_zod();
|
|
17635
|
+
ContentTypeSchema = external_exports.object({
|
|
17636
|
+
extensions: external_exports.array(external_exports.string()).optional(),
|
|
17637
|
+
mimeTypes: external_exports.array(external_exports.string()).optional()
|
|
17638
|
+
}).loose();
|
|
17639
|
+
PluginPreferenceSchema = external_exports.object({
|
|
17640
|
+
key: external_exports.string().min(1),
|
|
17641
|
+
label: external_exports.string().min(1),
|
|
17642
|
+
type: external_exports.enum(["string", "number", "boolean", "select"]),
|
|
17643
|
+
/** Stored as a string; the plugin coerces. */
|
|
17644
|
+
default: external_exports.string().optional(),
|
|
17645
|
+
description: external_exports.string().optional(),
|
|
17646
|
+
/** `select` options (value === label unless an object form is used later). */
|
|
17647
|
+
options: external_exports.array(external_exports.string()).optional(),
|
|
17648
|
+
/** Per-project vs global storage (default global). Ignored for secrets
|
|
17649
|
+
* (always keychain). */
|
|
17650
|
+
scope: external_exports.enum(["global", "project"]).optional(),
|
|
17651
|
+
/** Secret prefs are stored in the OS keychain, never in config (GB-1054). */
|
|
17652
|
+
secret: external_exports.boolean().optional()
|
|
17653
|
+
}).loose();
|
|
17654
|
+
ConfigLabelColorSchema = external_exports.enum(["default", "success", "error", "warning", "transient"]);
|
|
17655
|
+
ConfigLayoutItemSchema = external_exports.lazy(
|
|
17656
|
+
() => external_exports.object({
|
|
17657
|
+
type: external_exports.enum(["preference", "divider", "spacer", "label", "button", "group"]),
|
|
17658
|
+
key: external_exports.string().optional(),
|
|
17659
|
+
id: external_exports.string().optional(),
|
|
17660
|
+
text: external_exports.string().optional(),
|
|
17661
|
+
color: ConfigLabelColorSchema.optional(),
|
|
17662
|
+
label: external_exports.string().optional(),
|
|
17663
|
+
action: external_exports.string().optional(),
|
|
17664
|
+
style: external_exports.string().optional(),
|
|
17665
|
+
title: external_exports.string().optional(),
|
|
17666
|
+
collapsed: external_exports.boolean().optional(),
|
|
17667
|
+
items: external_exports.array(ConfigLayoutItemSchema).optional()
|
|
17668
|
+
}).loose()
|
|
17669
|
+
);
|
|
17670
|
+
PluginRequirementSchema = external_exports.object({
|
|
17671
|
+
id: external_exports.string().min(1),
|
|
17672
|
+
label: external_exports.string().min(1),
|
|
17673
|
+
/** Executable whose presence proves the requirement (run with `checkArgs`). */
|
|
17674
|
+
command: external_exports.string().min(1),
|
|
17675
|
+
/** Args for the presence check (default `['--version']`). */
|
|
17676
|
+
checkArgs: external_exports.array(external_exports.string()).optional(),
|
|
17677
|
+
/** Human remediation shown when the requirement is missing. */
|
|
17678
|
+
hint: external_exports.string().min(1),
|
|
17679
|
+
docUrl: external_exports.string().optional()
|
|
17680
|
+
}).loose();
|
|
17681
|
+
PluginProvisionStepSchema = external_exports.union([
|
|
17682
|
+
external_exports.object({
|
|
17683
|
+
kind: external_exports.literal("fetch"),
|
|
17684
|
+
url: external_exports.string().min(1),
|
|
17685
|
+
/** Destination filename inside the install dir. */
|
|
17686
|
+
dest: external_exports.string().min(1),
|
|
17687
|
+
/** Optional expected sha-256 (hex) of the downloaded bytes. */
|
|
17688
|
+
sha256: external_exports.string().optional()
|
|
17689
|
+
}).loose(),
|
|
17690
|
+
external_exports.object({
|
|
17691
|
+
kind: external_exports.literal("npm-install"),
|
|
17692
|
+
packages: external_exports.array(external_exports.string().min(1)).min(1),
|
|
17693
|
+
/** Requirement id whose command must be present to run this (default `npm`). */
|
|
17694
|
+
requires: external_exports.string().optional(),
|
|
17695
|
+
note: external_exports.string().optional()
|
|
17696
|
+
}).loose()
|
|
17697
|
+
]);
|
|
17698
|
+
PluginInstallSchema = external_exports.object({
|
|
17699
|
+
requirements: external_exports.array(PluginRequirementSchema).optional(),
|
|
17700
|
+
provision: external_exports.array(PluginProvisionStepSchema).optional(),
|
|
17701
|
+
/** Fallback command to run by hand when auto-provisioning can't complete. */
|
|
17702
|
+
cliHint: external_exports.string().optional()
|
|
17703
|
+
}).loose();
|
|
17704
|
+
PluginManifestSchema = external_exports.object({
|
|
17705
|
+
id: external_exports.string().min(1),
|
|
17706
|
+
name: external_exports.string().min(1),
|
|
17707
|
+
version: external_exports.string().min(1),
|
|
17708
|
+
/** Entry module relative to the plugin dir; defaults to `index.js`. */
|
|
17709
|
+
entry: external_exports.string().min(1).optional(),
|
|
17710
|
+
description: external_exports.string().optional(),
|
|
17711
|
+
author: external_exports.string().optional(),
|
|
17712
|
+
/** The content types this plugin handles (informational for the UI; the
|
|
17713
|
+
* authoritative match lives in the registered renderer/differ). */
|
|
17714
|
+
contentTypes: external_exports.array(ContentTypeSchema).optional(),
|
|
17715
|
+
/** User-configurable preferences (doc 29 FR-29.12); rendered in Settings. */
|
|
17716
|
+
preferences: external_exports.array(PluginPreferenceSchema).optional(),
|
|
17717
|
+
/** Optional arrangement of the preferences into groups/dividers/labels/
|
|
17718
|
+
* buttons (doc 29 FR-29.18). Omitted → flat preference list. */
|
|
17719
|
+
configLayout: external_exports.array(ConfigLayoutItemSchema).optional(),
|
|
17720
|
+
/** `false` marks a **separately-installable** plugin (e.g. one with a system
|
|
17721
|
+
* requirement like Java): it's built + may ship in the bundle, but
|
|
17722
|
+
* `installBundledPlugins` does NOT auto-install it — the user opts in
|
|
17723
|
+
* (GB-1046). Defaults to auto-install (true) when omitted. */
|
|
17724
|
+
autoInstall: external_exports.boolean().optional(),
|
|
17725
|
+
/** How to install an opt-in plugin from the UI (doc 29 §29.2, GB-1069):
|
|
17726
|
+
* system requirements + auto-run provisioning steps. Absent → self-contained. */
|
|
17727
|
+
install: PluginInstallSchema.optional()
|
|
17728
|
+
}).loose();
|
|
17729
|
+
}
|
|
17730
|
+
});
|
|
17731
|
+
|
|
17732
|
+
// src/plugins/registry.ts
|
|
17733
|
+
import { extname } from "path";
|
|
17734
|
+
function matchSpecificity(match, input) {
|
|
17735
|
+
if (match.sniff && input.bytes.length > 0) {
|
|
17736
|
+
try {
|
|
17737
|
+
if (match.sniff(input.bytes)) return 3;
|
|
17738
|
+
} catch {
|
|
17739
|
+
}
|
|
17740
|
+
}
|
|
17741
|
+
if (match.mimeTypes && input.mime !== void 0 && match.mimeTypes.includes(input.mime)) return 2;
|
|
17742
|
+
const ext = extname(input.path).toLowerCase();
|
|
17743
|
+
if (ext !== "" && match.extensions?.some((e) => e.toLowerCase() === ext) === true) return 1;
|
|
17744
|
+
return 0;
|
|
17745
|
+
}
|
|
17746
|
+
function pathMatches(match, path, mime) {
|
|
17747
|
+
const ext = extname(path).toLowerCase();
|
|
17748
|
+
if (ext !== "" && match.extensions?.some((e) => e.toLowerCase() === ext) === true) return true;
|
|
17749
|
+
if (mime !== void 0 && match.mimeTypes?.includes(mime) === true) return true;
|
|
17750
|
+
return false;
|
|
17751
|
+
}
|
|
17752
|
+
function pickBest(handlers, input) {
|
|
17753
|
+
let best;
|
|
17754
|
+
let bestScore = 0;
|
|
17755
|
+
for (const h of handlers) {
|
|
17756
|
+
const spec = matchSpecificity(h.match, input);
|
|
17757
|
+
if (spec === 0) continue;
|
|
17758
|
+
const score = (h.priority ?? 0) * 10 + spec;
|
|
17759
|
+
if (score > bestScore) {
|
|
17760
|
+
bestScore = score;
|
|
17761
|
+
best = h;
|
|
17762
|
+
}
|
|
17763
|
+
}
|
|
17764
|
+
return best;
|
|
17765
|
+
}
|
|
17766
|
+
var ContentPluginRegistry;
|
|
17767
|
+
var init_registry = __esm({
|
|
17768
|
+
"src/plugins/registry.ts"() {
|
|
17769
|
+
"use strict";
|
|
17770
|
+
ContentPluginRegistry = class {
|
|
17771
|
+
renderers = [];
|
|
17772
|
+
differs = [];
|
|
17773
|
+
imageDecoders = [];
|
|
17774
|
+
addRenderers(rs) {
|
|
17775
|
+
if (rs) this.renderers.push(...rs);
|
|
17776
|
+
}
|
|
17777
|
+
addDiffers(ds) {
|
|
17778
|
+
if (ds) this.differs.push(...ds);
|
|
17779
|
+
}
|
|
17780
|
+
addImageDecoders(ds) {
|
|
17781
|
+
if (ds) this.imageDecoders.push(...ds);
|
|
17782
|
+
}
|
|
17783
|
+
/** The best-matching renderer for a single content blob, or undefined. */
|
|
17784
|
+
findRenderer(input) {
|
|
17785
|
+
return pickBest(this.renderers, input);
|
|
17786
|
+
}
|
|
17787
|
+
/** The best-matching differ for a pair; matching keys off the new side. */
|
|
17788
|
+
findDiffer(input) {
|
|
17789
|
+
return pickBest(this.differs, input.new);
|
|
17790
|
+
}
|
|
17791
|
+
/** The best-matching image decoder (bytes → RGBA) for a blob, or undefined. */
|
|
17792
|
+
findImageDecoder(input) {
|
|
17793
|
+
return pickBest(this.imageDecoders, input);
|
|
17794
|
+
}
|
|
17795
|
+
/** Cheap path pre-check: could any renderer/differ handle this path (by ext /
|
|
17796
|
+
* MIME)? Skips sniff so no bytes are read. */
|
|
17797
|
+
mightHandleByPath(path, mime) {
|
|
17798
|
+
for (const h of this.renderers) if (pathMatches(h.match, path, mime)) return true;
|
|
17799
|
+
for (const h of this.differs) if (pathMatches(h.match, path, mime)) return true;
|
|
17800
|
+
return false;
|
|
17801
|
+
}
|
|
17802
|
+
get rendererCount() {
|
|
17803
|
+
return this.renderers.length;
|
|
17804
|
+
}
|
|
17805
|
+
get differCount() {
|
|
17806
|
+
return this.differs.length;
|
|
17807
|
+
}
|
|
17808
|
+
get imageDecoderCount() {
|
|
17809
|
+
return this.imageDecoders.length;
|
|
17810
|
+
}
|
|
17811
|
+
};
|
|
17812
|
+
}
|
|
17813
|
+
});
|
|
17814
|
+
|
|
17815
|
+
// src/plugins/settings.ts
|
|
17816
|
+
function secretAccount(pluginId, key) {
|
|
17817
|
+
return `plugin-${pluginId}-${key}`;
|
|
17818
|
+
}
|
|
17819
|
+
function coerceSettingsMap(raw2) {
|
|
17820
|
+
const out = {};
|
|
17821
|
+
if (raw2 === null || typeof raw2 !== "object") return out;
|
|
17822
|
+
for (const [pluginId, byKey] of Object.entries(raw2)) {
|
|
17823
|
+
if (byKey === null || typeof byKey !== "object") continue;
|
|
17824
|
+
const entry = {};
|
|
17825
|
+
for (const [k, v] of Object.entries(byKey)) {
|
|
17826
|
+
if (typeof v === "string") entry[k] = v;
|
|
17827
|
+
}
|
|
17828
|
+
out[pluginId] = entry;
|
|
17829
|
+
}
|
|
17830
|
+
return out;
|
|
17831
|
+
}
|
|
17832
|
+
function prefFor(manifest, key) {
|
|
17833
|
+
return manifest.preferences?.find((p) => p.key === key);
|
|
17834
|
+
}
|
|
17835
|
+
function scopeOf(pref) {
|
|
17836
|
+
return pref?.scope === "project" ? "project" : "global";
|
|
17837
|
+
}
|
|
17838
|
+
function readGlobalSettings() {
|
|
17839
|
+
return coerceSettingsMap(readGlobalConfig().pluginSettings);
|
|
17840
|
+
}
|
|
17841
|
+
function readProjectSettingsMap(repoRoot) {
|
|
17842
|
+
return coerceSettingsMap(readProjectSettings(repoRoot).pluginSettings);
|
|
17843
|
+
}
|
|
17844
|
+
function lookup(map2, pluginId, key) {
|
|
17845
|
+
return map2[pluginId]?.[key];
|
|
17846
|
+
}
|
|
17847
|
+
function readPluginSetting(manifest, repoRoot, key) {
|
|
17848
|
+
const pref = prefFor(manifest, key);
|
|
17849
|
+
if (pref?.secret === true) {
|
|
17850
|
+
return getSecretFromKeychain(secretAccount(manifest.id, key)) ?? pref.default ?? null;
|
|
17851
|
+
}
|
|
17852
|
+
const map2 = scopeOf(pref) === "project" ? readProjectSettingsMap(repoRoot) : readGlobalSettings();
|
|
17853
|
+
const stored = lookup(map2, manifest.id, key);
|
|
17854
|
+
if (stored !== void 0) return stored;
|
|
17855
|
+
return pref?.default ?? null;
|
|
17856
|
+
}
|
|
17857
|
+
function writePluginSetting(manifest, repoRoot, key, value) {
|
|
17858
|
+
const pref = prefFor(manifest, key);
|
|
17859
|
+
if (pref?.secret === true) {
|
|
17860
|
+
if (value === "") deleteSecretFromKeychain(secretAccount(manifest.id, key));
|
|
17861
|
+
else saveSecretToKeychain(secretAccount(manifest.id, key), value, `Glassbox plugin: ${manifest.id}`);
|
|
17862
|
+
return;
|
|
17863
|
+
}
|
|
17864
|
+
if (scopeOf(pref) === "project") {
|
|
17865
|
+
updateProjectSettings2(repoRoot, (s) => {
|
|
17866
|
+
const map2 = coerceSettingsMap(s.pluginSettings);
|
|
17867
|
+
map2[manifest.id] = { ...map2[manifest.id], [key]: value };
|
|
17868
|
+
s.pluginSettings = map2;
|
|
17869
|
+
});
|
|
17870
|
+
} else {
|
|
17871
|
+
updateGlobalConfig((cfg) => {
|
|
17872
|
+
const map2 = coerceSettingsMap(cfg.pluginSettings);
|
|
17873
|
+
map2[manifest.id] = { ...map2[manifest.id], [key]: value };
|
|
17874
|
+
return { ...cfg, pluginSettings: map2 };
|
|
17875
|
+
});
|
|
17876
|
+
}
|
|
17877
|
+
}
|
|
17878
|
+
function readPluginPreferenceDisplay(manifest, repoRoot) {
|
|
17879
|
+
const values = {};
|
|
17880
|
+
const secretConfigured = [];
|
|
17881
|
+
for (const p of manifest.preferences ?? []) {
|
|
17882
|
+
if (p.secret === true) {
|
|
17883
|
+
const stored = getSecretFromKeychain(secretAccount(manifest.id, p.key));
|
|
17884
|
+
if (stored !== null && stored !== "") secretConfigured.push(p.key);
|
|
17885
|
+
values[p.key] = "";
|
|
17886
|
+
} else {
|
|
17887
|
+
values[p.key] = readPluginSetting(manifest, repoRoot, p.key) ?? "";
|
|
17888
|
+
}
|
|
17889
|
+
}
|
|
17890
|
+
return { values, secretConfigured };
|
|
17891
|
+
}
|
|
17892
|
+
var init_settings = __esm({
|
|
17893
|
+
"src/plugins/settings.ts"() {
|
|
17894
|
+
"use strict";
|
|
17895
|
+
init_keychain();
|
|
17896
|
+
init_global_config();
|
|
17897
|
+
init_project_settings_store();
|
|
17898
|
+
}
|
|
17899
|
+
});
|
|
17900
|
+
|
|
17901
|
+
// src/plugins/loader.ts
|
|
17902
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
|
|
17903
|
+
import { join as join11 } from "path";
|
|
17904
|
+
import { pathToFileURL } from "url";
|
|
17905
|
+
function getConfigLabelOverride(pluginId, labelId) {
|
|
17906
|
+
return configLabelOverrides.get(`${pluginId}:${labelId}`);
|
|
17907
|
+
}
|
|
17908
|
+
function clearConfigLabelOverrides(pluginId) {
|
|
17909
|
+
const prefix = `${pluginId}:`;
|
|
17910
|
+
for (const key of configLabelOverrides.keys()) {
|
|
17911
|
+
if (key.startsWith(prefix)) configLabelOverrides.delete(key);
|
|
17912
|
+
}
|
|
17913
|
+
}
|
|
17914
|
+
function getPluginUIElements() {
|
|
17915
|
+
return Array.from(pluginUIElements.entries()).map(([pluginId, elements]) => ({ pluginId, elements }));
|
|
17916
|
+
}
|
|
17917
|
+
function clearPluginUIElements(pluginId) {
|
|
17918
|
+
pluginUIElements.delete(pluginId);
|
|
17919
|
+
}
|
|
17920
|
+
function clearAllPluginUIElements() {
|
|
17921
|
+
pluginUIElements.clear();
|
|
17922
|
+
}
|
|
17923
|
+
function pluginsDir() {
|
|
17924
|
+
return join11(GLOBAL_CONFIG_DIR, "plugins");
|
|
17925
|
+
}
|
|
17926
|
+
function discoverPluginDirs(root = pluginsDir()) {
|
|
17927
|
+
if (!existsSync11(root)) return [];
|
|
17928
|
+
let entries;
|
|
17929
|
+
try {
|
|
17930
|
+
entries = readdirSync2(root);
|
|
17931
|
+
} catch {
|
|
17932
|
+
return [];
|
|
17933
|
+
}
|
|
17934
|
+
const out = [];
|
|
17935
|
+
for (const name of entries) {
|
|
17936
|
+
if (name.startsWith(".")) continue;
|
|
17937
|
+
const dir = join11(root, name);
|
|
17938
|
+
try {
|
|
17939
|
+
if (statSync3(dir).isDirectory()) out.push(dir);
|
|
17940
|
+
} catch {
|
|
17941
|
+
}
|
|
17942
|
+
}
|
|
17943
|
+
return out.sort();
|
|
17944
|
+
}
|
|
17945
|
+
function readManifest(dir) {
|
|
17946
|
+
const manifestPath = join11(dir, "manifest.json");
|
|
17947
|
+
if (existsSync11(manifestPath)) {
|
|
17948
|
+
try {
|
|
17949
|
+
return parseManifest(JSON.parse(readFileSync10(manifestPath, "utf-8")));
|
|
17950
|
+
} catch {
|
|
17951
|
+
return null;
|
|
17952
|
+
}
|
|
17953
|
+
}
|
|
17954
|
+
const pkgPath = join11(dir, "package.json");
|
|
17955
|
+
if (existsSync11(pkgPath)) {
|
|
17956
|
+
try {
|
|
17957
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
17958
|
+
const gb = pkg.glassbox;
|
|
17959
|
+
if (gb !== null && typeof gb === "object") {
|
|
17960
|
+
return parseManifest({ id: pkg.name, version: pkg.version, entry: pkg.main, ...gb });
|
|
17961
|
+
}
|
|
17962
|
+
} catch {
|
|
17963
|
+
return null;
|
|
17964
|
+
}
|
|
17965
|
+
}
|
|
17966
|
+
return null;
|
|
17967
|
+
}
|
|
17968
|
+
function makeContext(manifest, repoRoot) {
|
|
17969
|
+
const id = manifest.id;
|
|
17970
|
+
return {
|
|
17971
|
+
log: (level, message) => {
|
|
17972
|
+
const line = `[plugin:${id}] ${message}`;
|
|
17973
|
+
if (level === "error") console.error(line);
|
|
17974
|
+
else if (level === "warn") console.warn(line);
|
|
17975
|
+
else console.log(line);
|
|
17976
|
+
},
|
|
17977
|
+
getSetting: (key) => Promise.resolve(readPluginSetting(manifest, repoRoot, key)),
|
|
17978
|
+
setSetting: (key, value) => {
|
|
17979
|
+
writePluginSetting(manifest, repoRoot, key, value);
|
|
17980
|
+
return Promise.resolve();
|
|
17981
|
+
},
|
|
17982
|
+
updateConfigLabel: (labelId, text, color) => {
|
|
17983
|
+
configLabelOverrides.set(`${id}:${labelId}`, { text, color });
|
|
17984
|
+
},
|
|
17985
|
+
registerUI: (elements) => {
|
|
17986
|
+
pluginUIElements.set(id, elements);
|
|
17987
|
+
}
|
|
17988
|
+
};
|
|
17989
|
+
}
|
|
17990
|
+
async function loadPluginDir(dir, registry3, isEnabled = ALWAYS_ENABLED, repoRoot = "") {
|
|
17991
|
+
const manifest = readManifest(dir);
|
|
17992
|
+
if (manifest === null) {
|
|
17993
|
+
return { id: dir, dir, manifest: null, status: "error", error: "invalid or missing manifest" };
|
|
17994
|
+
}
|
|
17995
|
+
const enablement = isEnabled(manifest.id);
|
|
17996
|
+
if (enablement.disabled) {
|
|
17997
|
+
return { id: manifest.id, dir, manifest, status: "disabled", disabledScope: enablement.scope };
|
|
17998
|
+
}
|
|
17999
|
+
try {
|
|
18000
|
+
const entry = join11(dir, manifest.entry ?? "index.js");
|
|
18001
|
+
if (!existsSync11(entry)) throw new Error(`entry not found: ${manifest.entry ?? "index.js"}`);
|
|
18002
|
+
const mod = await import(pathToFileURL(entry).href);
|
|
18003
|
+
const plugin = mod.default ?? (typeof mod.activate === "function" ? { activate: mod.activate, onAction: mod.onAction } : void 0);
|
|
18004
|
+
if (plugin === void 0 || typeof plugin.activate !== "function") {
|
|
18005
|
+
throw new Error("plugin exports no activate()");
|
|
18006
|
+
}
|
|
18007
|
+
const context = makeContext(manifest, repoRoot);
|
|
18008
|
+
const registration = await plugin.activate(context) ?? void 0;
|
|
18009
|
+
if (registration !== void 0) {
|
|
18010
|
+
registry3.addRenderers(registration.renderers);
|
|
18011
|
+
registry3.addDiffers(registration.differs);
|
|
18012
|
+
registry3.addImageDecoders(registration.imageDecoders);
|
|
18013
|
+
}
|
|
18014
|
+
return { id: manifest.id, dir, manifest, status: "loaded", registration, instance: plugin, context };
|
|
18015
|
+
} catch (e) {
|
|
18016
|
+
return { id: manifest.id, dir, manifest, status: "error", error: e instanceof Error ? e.message : String(e) };
|
|
18017
|
+
}
|
|
18018
|
+
}
|
|
18019
|
+
async function loadAllPlugins(root = pluginsDir(), isEnabled = ALWAYS_ENABLED, repoRoot = "") {
|
|
18020
|
+
const registry3 = new ContentPluginRegistry();
|
|
18021
|
+
if (!PLUGINS_ENABLED) return { registry: registry3, loaded: [] };
|
|
18022
|
+
const loaded = [];
|
|
18023
|
+
for (const dir of discoverPluginDirs(root)) {
|
|
18024
|
+
loaded.push(await loadPluginDir(dir, registry3, isEnabled, repoRoot));
|
|
18025
|
+
}
|
|
18026
|
+
return { registry: registry3, loaded };
|
|
18027
|
+
}
|
|
18028
|
+
var configLabelOverrides, pluginUIElements, ALWAYS_ENABLED;
|
|
18029
|
+
var init_loader = __esm({
|
|
18030
|
+
"src/plugins/loader.ts"() {
|
|
18031
|
+
"use strict";
|
|
18032
|
+
init_feature_flags();
|
|
18033
|
+
init_global_config();
|
|
18034
|
+
init_manifest2();
|
|
18035
|
+
init_registry();
|
|
18036
|
+
init_settings();
|
|
18037
|
+
configLabelOverrides = /* @__PURE__ */ new Map();
|
|
18038
|
+
pluginUIElements = /* @__PURE__ */ new Map();
|
|
18039
|
+
ALWAYS_ENABLED = () => ({ disabled: false });
|
|
18040
|
+
}
|
|
18041
|
+
});
|
|
18042
|
+
|
|
18043
|
+
// src/plugins/install.ts
|
|
18044
|
+
import { createHash as createHash3 } from "crypto";
|
|
18045
|
+
import { cpSync, existsSync as existsSync12, mkdirSync as mkdirSync9, readdirSync as readdirSync3, readFileSync as readFileSync11, rmSync as rmSync6, statSync as statSync4, symlinkSync, writeFileSync as writeFileSync10 } from "fs";
|
|
18046
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
18047
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18048
|
+
function dismissedPathFor(userDir) {
|
|
18049
|
+
return join12(dirname7(userDir), DISMISSED_FILE);
|
|
18050
|
+
}
|
|
18051
|
+
function bundledPluginsDir() {
|
|
18052
|
+
const override = process.env.GLASSBOX_BUNDLED_PLUGINS_DIR;
|
|
18053
|
+
if (override !== void 0 && override.trim() !== "") return override;
|
|
18054
|
+
const sibling = join12(dirname7(fileURLToPath2(import.meta.url)), "plugins");
|
|
18055
|
+
if (existsSync12(sibling)) return sibling;
|
|
18056
|
+
return join12(process.cwd(), "dist", "plugins");
|
|
18057
|
+
}
|
|
18058
|
+
function discoverBundledPlugins(bundledDir = bundledPluginsDir()) {
|
|
18059
|
+
if (!existsSync12(bundledDir)) return [];
|
|
18060
|
+
let entries;
|
|
18061
|
+
try {
|
|
18062
|
+
entries = readdirSync3(bundledDir);
|
|
18063
|
+
} catch {
|
|
18064
|
+
return [];
|
|
18065
|
+
}
|
|
18066
|
+
const out = [];
|
|
18067
|
+
for (const name of entries.sort()) {
|
|
18068
|
+
const dir = join12(bundledDir, name);
|
|
18069
|
+
try {
|
|
18070
|
+
if (!statSync4(dir).isDirectory()) continue;
|
|
18071
|
+
const manifest = readManifest(dir);
|
|
18072
|
+
if (manifest !== null) out.push({ dir, manifest });
|
|
18073
|
+
} catch {
|
|
18074
|
+
}
|
|
18075
|
+
}
|
|
18076
|
+
return out;
|
|
18077
|
+
}
|
|
18078
|
+
function readDismissed(userDir = pluginsDir()) {
|
|
18079
|
+
try {
|
|
18080
|
+
const raw2 = JSON.parse(readFileSync11(dismissedPathFor(userDir), "utf-8"));
|
|
18081
|
+
return Array.isArray(raw2) ? raw2.filter((x) => typeof x === "string") : [];
|
|
18082
|
+
} catch {
|
|
18083
|
+
return [];
|
|
18084
|
+
}
|
|
17271
18085
|
}
|
|
17272
|
-
function
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
writeFileSync4(join5(dir, blobName(fileId, side)), bytes);
|
|
18086
|
+
function writeDismissed(ids, userDir) {
|
|
18087
|
+
const path = dismissedPathFor(userDir);
|
|
18088
|
+
mkdirSync9(dirname7(path), { recursive: true });
|
|
18089
|
+
writeFileSync10(path, JSON.stringify([...new Set(ids)], null, 2), "utf-8");
|
|
17277
18090
|
}
|
|
17278
|
-
function
|
|
17279
|
-
const
|
|
17280
|
-
|
|
18091
|
+
function compareVersions(a, b) {
|
|
18092
|
+
const pa = a.split(".").map((p) => parseInt(p, 10) || 0);
|
|
18093
|
+
const pb = b.split(".").map((p) => parseInt(p, 10) || 0);
|
|
18094
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
18095
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
18096
|
+
if (d !== 0) return d > 0 ? 1 : -1;
|
|
18097
|
+
}
|
|
18098
|
+
return 0;
|
|
18099
|
+
}
|
|
18100
|
+
function hashPluginDir(dir) {
|
|
18101
|
+
const h = createHash3("sha1");
|
|
18102
|
+
const walk = (d, rel) => {
|
|
18103
|
+
for (const name of readdirSync3(d).sort()) {
|
|
18104
|
+
const abs = join12(d, name);
|
|
18105
|
+
const r = rel === "" ? name : `${rel}/${name}`;
|
|
18106
|
+
if (statSync4(abs).isDirectory()) walk(abs, r);
|
|
18107
|
+
else {
|
|
18108
|
+
h.update(r);
|
|
18109
|
+
h.update(readFileSync11(abs));
|
|
18110
|
+
}
|
|
18111
|
+
}
|
|
18112
|
+
};
|
|
18113
|
+
walk(dir, "");
|
|
18114
|
+
return h.digest("hex");
|
|
18115
|
+
}
|
|
18116
|
+
function shouldInstall(src, dest, bundledVersion) {
|
|
18117
|
+
if (!existsSync12(dest)) return true;
|
|
18118
|
+
const destManifest = readManifest(dest);
|
|
18119
|
+
const destVersion = destManifest?.version ?? "0";
|
|
18120
|
+
const cmp = compareVersions(bundledVersion, destVersion);
|
|
18121
|
+
if (cmp > 0) return true;
|
|
18122
|
+
if (cmp < 0) return false;
|
|
17281
18123
|
try {
|
|
17282
|
-
return
|
|
18124
|
+
return hashPluginDir(src) !== hashPluginDir(dest);
|
|
18125
|
+
} catch {
|
|
18126
|
+
return false;
|
|
18127
|
+
}
|
|
18128
|
+
}
|
|
18129
|
+
function installBundledPlugins(opts) {
|
|
18130
|
+
const bundledDir = opts?.bundledDir ?? bundledPluginsDir();
|
|
18131
|
+
const userDir = opts?.userDir ?? pluginsDir();
|
|
18132
|
+
if (!existsSync12(bundledDir)) return;
|
|
18133
|
+
const dismissed = new Set(readDismissed(userDir));
|
|
18134
|
+
let entries;
|
|
18135
|
+
try {
|
|
18136
|
+
entries = readdirSync3(bundledDir);
|
|
18137
|
+
} catch {
|
|
18138
|
+
return;
|
|
18139
|
+
}
|
|
18140
|
+
for (const name of entries) {
|
|
18141
|
+
const src = join12(bundledDir, name);
|
|
18142
|
+
try {
|
|
18143
|
+
if (!statSync4(src).isDirectory()) continue;
|
|
18144
|
+
const manifest = readManifest(src);
|
|
18145
|
+
if (manifest === null || dismissed.has(manifest.id)) continue;
|
|
18146
|
+
if (manifest.autoInstall === false) continue;
|
|
18147
|
+
const dest = join12(userDir, manifest.id);
|
|
18148
|
+
if (!shouldInstall(src, dest, manifest.version)) continue;
|
|
18149
|
+
mkdirSync9(userDir, { recursive: true });
|
|
18150
|
+
rmSync6(dest, { recursive: true, force: true });
|
|
18151
|
+
cpSync(src, dest, { recursive: true });
|
|
18152
|
+
} catch {
|
|
18153
|
+
}
|
|
18154
|
+
}
|
|
18155
|
+
}
|
|
18156
|
+
function installPluginFromDisk(sourceDir, opts) {
|
|
18157
|
+
const manifest = readManifest(sourceDir);
|
|
18158
|
+
if (manifest === null) throw new Error("The selected folder is not a Glassbox plugin (no manifest.json).");
|
|
18159
|
+
const userDir = opts?.userDir ?? pluginsDir();
|
|
18160
|
+
mkdirSync9(userDir, { recursive: true });
|
|
18161
|
+
const dest = join12(userDir, manifest.id);
|
|
18162
|
+
rmSync6(dest, { recursive: true, force: true });
|
|
18163
|
+
try {
|
|
18164
|
+
symlinkSync(sourceDir, dest, "dir");
|
|
18165
|
+
} catch {
|
|
18166
|
+
cpSync(sourceDir, dest, { recursive: true });
|
|
18167
|
+
}
|
|
18168
|
+
writeDismissed(readDismissed(userDir).filter((x) => x !== manifest.id), userDir);
|
|
18169
|
+
return { id: manifest.id };
|
|
18170
|
+
}
|
|
18171
|
+
function undismissPlugin(id, userDir = pluginsDir()) {
|
|
18172
|
+
writeDismissed(readDismissed(userDir).filter((x) => x !== id), userDir);
|
|
18173
|
+
}
|
|
18174
|
+
function uninstallPlugin(id, opts) {
|
|
18175
|
+
const userDir = opts?.userDir ?? pluginsDir();
|
|
18176
|
+
rmSync6(join12(userDir, id), { recursive: true, force: true });
|
|
18177
|
+
writeDismissed([...readDismissed(userDir), id], userDir);
|
|
18178
|
+
}
|
|
18179
|
+
var DISMISSED_FILE;
|
|
18180
|
+
var init_install = __esm({
|
|
18181
|
+
"src/plugins/install.ts"() {
|
|
18182
|
+
"use strict";
|
|
18183
|
+
init_loader();
|
|
18184
|
+
DISMISSED_FILE = "dismissed-plugins.json";
|
|
18185
|
+
}
|
|
18186
|
+
});
|
|
18187
|
+
|
|
18188
|
+
// src/plugins/index.ts
|
|
18189
|
+
var plugins_exports = {};
|
|
18190
|
+
__export(plugins_exports, {
|
|
18191
|
+
__resetContentPluginsForTest: () => __resetContentPluginsForTest,
|
|
18192
|
+
__setContentRegistryForTest: () => __setContentRegistryForTest,
|
|
18193
|
+
decodeImageWithPlugin: () => decodeImageWithPlugin,
|
|
18194
|
+
describeInstalledPlugins: () => describeInstalledPlugins,
|
|
18195
|
+
diffContent: () => diffContent,
|
|
18196
|
+
getLoadedPlugins: () => getLoadedPlugins,
|
|
18197
|
+
getPluginManifest: () => getPluginManifest,
|
|
18198
|
+
initContentPlugins: () => initContentPlugins,
|
|
18199
|
+
listPluginUIElements: () => listPluginUIElements,
|
|
18200
|
+
mightHandleFile: () => mightHandleFile,
|
|
18201
|
+
notifyReviewCompleted: () => notifyReviewCompleted,
|
|
18202
|
+
notifyReviewCreated: () => notifyReviewCreated,
|
|
18203
|
+
persistPluginUIState: () => persistPluginUIState,
|
|
18204
|
+
pluginsEnabled: () => pluginsEnabled,
|
|
18205
|
+
reloadContentPlugins: () => reloadContentPlugins,
|
|
18206
|
+
renderContent: () => renderContent,
|
|
18207
|
+
runPluginAction: () => runPluginAction
|
|
18208
|
+
});
|
|
18209
|
+
function pluginsEnabled() {
|
|
18210
|
+
return PLUGINS_ENABLED;
|
|
18211
|
+
}
|
|
18212
|
+
function enablementCheckFor(repoRoot) {
|
|
18213
|
+
const lists = readEnablementLists(repoRoot);
|
|
18214
|
+
return (id) => {
|
|
18215
|
+
const scope = disabledScope(id, lists);
|
|
18216
|
+
return scope === null ? { disabled: false } : { disabled: true, scope };
|
|
18217
|
+
};
|
|
18218
|
+
}
|
|
18219
|
+
async function loadFor(repoRoot) {
|
|
18220
|
+
installBundledPlugins();
|
|
18221
|
+
clearAllPluginUIElements();
|
|
18222
|
+
const res = await loadAllPlugins(void 0, enablementCheckFor(repoRoot), repoRoot);
|
|
18223
|
+
registry2 = res.registry;
|
|
18224
|
+
loadedPlugins = res.loaded;
|
|
18225
|
+
currentRepoRoot = repoRoot;
|
|
18226
|
+
const failed = loadedPlugins.filter((p) => p.status === "error").length;
|
|
18227
|
+
const disabled = loadedPlugins.filter((p) => p.status === "disabled").length;
|
|
18228
|
+
if (loadedPlugins.length > 0) {
|
|
18229
|
+
const parts = [`${loadedPlugins.length - failed - disabled} loaded`];
|
|
18230
|
+
if (disabled > 0) parts.push(`${disabled} disabled`);
|
|
18231
|
+
if (failed > 0) parts.push(`${failed} failed`);
|
|
18232
|
+
console.log(` Content plugins: ${parts.join(", ")}.`);
|
|
18233
|
+
}
|
|
18234
|
+
}
|
|
18235
|
+
async function initContentPlugins(repoRoot = "") {
|
|
18236
|
+
if (initialized) return;
|
|
18237
|
+
initialized = true;
|
|
18238
|
+
if (!PLUGINS_ENABLED) return;
|
|
18239
|
+
try {
|
|
18240
|
+
await loadFor(repoRoot);
|
|
18241
|
+
} catch (e) {
|
|
18242
|
+
console.warn(` Content plugins failed to load: ${e instanceof Error ? e.message : String(e)}`);
|
|
18243
|
+
}
|
|
18244
|
+
}
|
|
18245
|
+
async function reloadContentPlugins(repoRoot = currentRepoRoot) {
|
|
18246
|
+
if (!PLUGINS_ENABLED) return;
|
|
18247
|
+
initialized = true;
|
|
18248
|
+
try {
|
|
18249
|
+
await loadFor(repoRoot);
|
|
18250
|
+
} catch (e) {
|
|
18251
|
+
console.warn(` Content plugins failed to reload: ${e instanceof Error ? e.message : String(e)}`);
|
|
18252
|
+
}
|
|
18253
|
+
}
|
|
18254
|
+
function getLoadedPlugins() {
|
|
18255
|
+
return loadedPlugins;
|
|
18256
|
+
}
|
|
18257
|
+
function resolveConfigLabels(manifest) {
|
|
18258
|
+
const out = {};
|
|
18259
|
+
const walk = (items) => {
|
|
18260
|
+
for (const item of items ?? []) {
|
|
18261
|
+
if (item.type === "label" && item.id !== void 0 && item.id !== "") {
|
|
18262
|
+
const override = getConfigLabelOverride(manifest.id, item.id);
|
|
18263
|
+
out[item.id] = override ?? { text: item.text ?? "", color: item.color };
|
|
18264
|
+
}
|
|
18265
|
+
if (item.type === "group") walk(item.items);
|
|
18266
|
+
}
|
|
18267
|
+
};
|
|
18268
|
+
walk(manifest.configLayout);
|
|
18269
|
+
return out;
|
|
18270
|
+
}
|
|
18271
|
+
function describeInstalledPlugins(repoRoot) {
|
|
18272
|
+
const globalDisabled = new Set(readGlobalDisabled());
|
|
18273
|
+
const projectDisabled = new Set(readProjectDisabled(repoRoot));
|
|
18274
|
+
return loadedPlugins.map((p) => {
|
|
18275
|
+
const extensions = (p.manifest?.contentTypes ?? []).flatMap((ct) => ct.extensions ?? []);
|
|
18276
|
+
const display = p.manifest ? readPluginPreferenceDisplay(p.manifest, repoRoot) : { values: {}, secretConfigured: [] };
|
|
18277
|
+
return {
|
|
18278
|
+
id: p.id,
|
|
18279
|
+
name: p.manifest?.name ?? p.id,
|
|
18280
|
+
version: p.manifest?.version ?? "0",
|
|
18281
|
+
extensions,
|
|
18282
|
+
status: p.status,
|
|
18283
|
+
error: p.error,
|
|
18284
|
+
enabled: p.status === "loaded",
|
|
18285
|
+
disabledScope: p.disabledScope,
|
|
18286
|
+
globalDisabled: globalDisabled.has(p.id),
|
|
18287
|
+
projectDisabled: projectDisabled.has(p.id),
|
|
18288
|
+
preferences: p.manifest?.preferences ?? [],
|
|
18289
|
+
preferenceValues: display.values,
|
|
18290
|
+
secretConfigured: display.secretConfigured,
|
|
18291
|
+
configLayout: p.manifest?.configLayout,
|
|
18292
|
+
configLabels: p.manifest ? resolveConfigLabels(p.manifest) : {}
|
|
18293
|
+
};
|
|
18294
|
+
});
|
|
18295
|
+
}
|
|
18296
|
+
function getPluginManifest(id) {
|
|
18297
|
+
return loadedPlugins.find((p) => p.id === id)?.manifest ?? void 0;
|
|
18298
|
+
}
|
|
18299
|
+
async function runPluginAction(id, actionId, value) {
|
|
18300
|
+
if (!PLUGINS_ENABLED) throw new Error("Plugins are disabled");
|
|
18301
|
+
const loaded = loadedPlugins.find((p) => p.id === id);
|
|
18302
|
+
if (loaded === void 0 || loaded.status !== "loaded" || loaded.instance === void 0 || loaded.context === void 0) {
|
|
18303
|
+
throw new Error("Plugin not active");
|
|
18304
|
+
}
|
|
18305
|
+
if (typeof loaded.instance.onAction !== "function") {
|
|
18306
|
+
throw new Error("Plugin does not handle actions");
|
|
18307
|
+
}
|
|
18308
|
+
return await loaded.instance.onAction(actionId, loaded.context, value);
|
|
18309
|
+
}
|
|
18310
|
+
function statefulKey(e) {
|
|
18311
|
+
return e.type === "toggle" || e.type === "switch" || e.type === "segmented-control" ? e.stateKey : void 0;
|
|
18312
|
+
}
|
|
18313
|
+
function elementAction(e) {
|
|
18314
|
+
return e.type === "link" ? void 0 : e.action;
|
|
18315
|
+
}
|
|
18316
|
+
function persistPluginUIState(id, actionId, value, repoRoot) {
|
|
18317
|
+
const manifest = loadedPlugins.find((p) => p.id === id && p.status === "loaded")?.manifest;
|
|
18318
|
+
if (manifest === null || manifest === void 0) return;
|
|
18319
|
+
const group = getPluginUIElements().find((g) => g.pluginId === id);
|
|
18320
|
+
const el = group?.elements.find((e) => elementAction(e) === actionId && statefulKey(e) !== void 0 && statefulKey(e) !== "");
|
|
18321
|
+
const key = el ? statefulKey(el) : void 0;
|
|
18322
|
+
if (key === void 0 || key === "") return;
|
|
18323
|
+
try {
|
|
18324
|
+
writePluginSetting(manifest, repoRoot, key, value);
|
|
18325
|
+
} catch {
|
|
18326
|
+
}
|
|
18327
|
+
}
|
|
18328
|
+
function toReviewHookInfo(review) {
|
|
18329
|
+
return { id: review.id, repoPath: review.repo_path, repoName: review.repo_name, mode: review.mode, status: review.status };
|
|
18330
|
+
}
|
|
18331
|
+
function toAnnotationHookInfo(a) {
|
|
18332
|
+
return { id: a.id, filePath: a.file_path, lineNumber: a.line_number, side: a.side, category: a.category, content: a.content };
|
|
18333
|
+
}
|
|
18334
|
+
async function notifyReviewCreated(review) {
|
|
18335
|
+
if (!PLUGINS_ENABLED) return;
|
|
18336
|
+
const info = toReviewHookInfo(review);
|
|
18337
|
+
for (const p of loadedPlugins) {
|
|
18338
|
+
const hook = p.registration?.reviewHooks?.onReviewCreated;
|
|
18339
|
+
if (p.status !== "loaded" || hook === void 0 || p.context === void 0) continue;
|
|
18340
|
+
try {
|
|
18341
|
+
await hook(info, p.context);
|
|
18342
|
+
} catch (e) {
|
|
18343
|
+
console.warn(` [plugin:${p.id}] onReviewCreated hook failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
18344
|
+
}
|
|
18345
|
+
}
|
|
18346
|
+
}
|
|
18347
|
+
async function notifyReviewCompleted(review, annotations, exportPath) {
|
|
18348
|
+
if (!PLUGINS_ENABLED) return;
|
|
18349
|
+
const info = toReviewHookInfo(review);
|
|
18350
|
+
const anns = annotations.map(toAnnotationHookInfo);
|
|
18351
|
+
for (const p of loadedPlugins) {
|
|
18352
|
+
const hook = p.registration?.reviewHooks?.onReviewCompleted;
|
|
18353
|
+
if (p.status !== "loaded" || hook === void 0 || p.context === void 0) continue;
|
|
18354
|
+
try {
|
|
18355
|
+
await hook(info, anns, exportPath, p.context);
|
|
18356
|
+
} catch (e) {
|
|
18357
|
+
console.warn(` [plugin:${p.id}] onReviewCompleted hook failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
18358
|
+
}
|
|
18359
|
+
}
|
|
18360
|
+
}
|
|
18361
|
+
function listPluginUIElements(repoRoot = "") {
|
|
18362
|
+
if (!PLUGINS_ENABLED) return [];
|
|
18363
|
+
const loaded = new Map(loadedPlugins.filter((p) => p.status === "loaded").map((p) => [p.id, p]));
|
|
18364
|
+
const out = [];
|
|
18365
|
+
for (const { pluginId, elements } of getPluginUIElements()) {
|
|
18366
|
+
const plugin = loaded.get(pluginId);
|
|
18367
|
+
if (plugin === void 0) continue;
|
|
18368
|
+
for (const el of elements) {
|
|
18369
|
+
const key = statefulKey(el);
|
|
18370
|
+
const value = key !== void 0 && key !== "" && plugin.manifest !== null ? readPluginSetting(plugin.manifest, repoRoot, key) ?? void 0 : void 0;
|
|
18371
|
+
out.push({ ...el, pluginId, value });
|
|
18372
|
+
}
|
|
18373
|
+
}
|
|
18374
|
+
return out;
|
|
18375
|
+
}
|
|
18376
|
+
function mightHandleFile(path, mime) {
|
|
18377
|
+
return PLUGINS_ENABLED && registry2.mightHandleByPath(path, mime);
|
|
18378
|
+
}
|
|
18379
|
+
function usableView(view) {
|
|
18380
|
+
if (view === void 0) return null;
|
|
18381
|
+
const svg = typeof view.svg === "string" && view.svg !== "";
|
|
18382
|
+
const html = typeof view.html === "string" && view.html !== "";
|
|
18383
|
+
return svg || html ? view : null;
|
|
18384
|
+
}
|
|
18385
|
+
async function renderContent(input) {
|
|
18386
|
+
if (!PLUGINS_ENABLED) return null;
|
|
18387
|
+
const renderer = registry2.findRenderer(input);
|
|
18388
|
+
if (renderer === void 0) return null;
|
|
18389
|
+
try {
|
|
18390
|
+
return usableView(await renderer.render(input));
|
|
17283
18391
|
} catch {
|
|
17284
18392
|
return null;
|
|
17285
18393
|
}
|
|
17286
18394
|
}
|
|
17287
|
-
function
|
|
18395
|
+
async function decodeImageWithPlugin(bytes, path, mime) {
|
|
18396
|
+
if (!PLUGINS_ENABLED) return null;
|
|
18397
|
+
const decoder = registry2.findImageDecoder({ bytes, path, mime });
|
|
18398
|
+
if (decoder === void 0) return null;
|
|
17288
18399
|
try {
|
|
17289
|
-
|
|
18400
|
+
return await decoder.decode({ bytes, path }) ?? null;
|
|
17290
18401
|
} catch {
|
|
18402
|
+
return null;
|
|
17291
18403
|
}
|
|
17292
18404
|
}
|
|
17293
|
-
|
|
17294
|
-
|
|
18405
|
+
async function diffContent(input) {
|
|
18406
|
+
if (!PLUGINS_ENABLED) return null;
|
|
18407
|
+
const differ = registry2.findDiffer(input);
|
|
18408
|
+
if (differ === void 0) return null;
|
|
18409
|
+
try {
|
|
18410
|
+
return usableView(await differ.diff(input));
|
|
18411
|
+
} catch {
|
|
18412
|
+
return null;
|
|
18413
|
+
}
|
|
18414
|
+
}
|
|
18415
|
+
function __setContentRegistryForTest(r, loaded = []) {
|
|
18416
|
+
registry2 = r;
|
|
18417
|
+
loadedPlugins = loaded;
|
|
18418
|
+
initialized = true;
|
|
18419
|
+
}
|
|
18420
|
+
function __resetContentPluginsForTest() {
|
|
18421
|
+
registry2 = new ContentPluginRegistry();
|
|
18422
|
+
loadedPlugins = [];
|
|
18423
|
+
initialized = false;
|
|
18424
|
+
}
|
|
18425
|
+
var registry2, loadedPlugins, initialized, currentRepoRoot;
|
|
18426
|
+
var init_plugins = __esm({
|
|
18427
|
+
"src/plugins/index.ts"() {
|
|
17295
18428
|
"use strict";
|
|
18429
|
+
init_feature_flags();
|
|
18430
|
+
init_enablement();
|
|
18431
|
+
init_install();
|
|
18432
|
+
init_loader();
|
|
18433
|
+
init_registry();
|
|
18434
|
+
init_settings();
|
|
18435
|
+
registry2 = new ContentPluginRegistry();
|
|
18436
|
+
loadedPlugins = [];
|
|
18437
|
+
initialized = false;
|
|
18438
|
+
currentRepoRoot = "";
|
|
17296
18439
|
}
|
|
17297
18440
|
});
|
|
17298
18441
|
|
|
@@ -17449,17 +18592,17 @@ __export(difftool_discovery_exports, {
|
|
|
17449
18592
|
tryAcquireStartingLock: () => tryAcquireStartingLock,
|
|
17450
18593
|
writeDiscovery: () => writeDiscovery
|
|
17451
18594
|
});
|
|
17452
|
-
import { existsSync as
|
|
18595
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync16, readFileSync as readFileSync22, rmSync as rmSync7, statSync as statSync7, writeFileSync as writeFileSync16 } from "fs";
|
|
17453
18596
|
import { homedir as homedir3 } from "os";
|
|
17454
|
-
import { join as
|
|
18597
|
+
import { join as join23 } from "path";
|
|
17455
18598
|
function difftoolHome() {
|
|
17456
|
-
return
|
|
18599
|
+
return join23(homedir3(), ".glassbox");
|
|
17457
18600
|
}
|
|
17458
18601
|
function discoveryPath(home = difftoolHome()) {
|
|
17459
|
-
return
|
|
18602
|
+
return join23(home, "difftool.lock");
|
|
17460
18603
|
}
|
|
17461
18604
|
function startingLockPath(home = difftoolHome()) {
|
|
17462
|
-
return
|
|
18605
|
+
return join23(home, "difftool-starting.lock");
|
|
17463
18606
|
}
|
|
17464
18607
|
function parseDiscovery(raw2) {
|
|
17465
18608
|
let parsed;
|
|
@@ -17473,35 +18616,35 @@ function parseDiscovery(raw2) {
|
|
|
17473
18616
|
}
|
|
17474
18617
|
function readDiscovery(home = difftoolHome()) {
|
|
17475
18618
|
const path = discoveryPath(home);
|
|
17476
|
-
if (!
|
|
18619
|
+
if (!existsSync19(path)) return null;
|
|
17477
18620
|
try {
|
|
17478
|
-
return parseDiscovery(
|
|
18621
|
+
return parseDiscovery(readFileSync22(path, "utf-8"));
|
|
17479
18622
|
} catch {
|
|
17480
18623
|
return null;
|
|
17481
18624
|
}
|
|
17482
18625
|
}
|
|
17483
18626
|
function writeDiscovery(port, home = difftoolHome()) {
|
|
17484
|
-
|
|
17485
|
-
|
|
18627
|
+
mkdirSync16(home, { recursive: true });
|
|
18628
|
+
writeFileSync16(discoveryPath(home), JSON.stringify({ port, pid: process.pid }));
|
|
17486
18629
|
}
|
|
17487
18630
|
function clearDiscovery(home = difftoolHome()) {
|
|
17488
18631
|
try {
|
|
17489
|
-
|
|
18632
|
+
rmSync7(discoveryPath(home), { force: true });
|
|
17490
18633
|
} catch {
|
|
17491
18634
|
}
|
|
17492
18635
|
}
|
|
17493
18636
|
function tryAcquireStartingLock(home = difftoolHome()) {
|
|
17494
|
-
|
|
18637
|
+
mkdirSync16(home, { recursive: true });
|
|
17495
18638
|
const path = startingLockPath(home);
|
|
17496
18639
|
try {
|
|
17497
|
-
|
|
18640
|
+
writeFileSync16(path, String(process.pid), { flag: "wx" });
|
|
17498
18641
|
return true;
|
|
17499
18642
|
} catch {
|
|
17500
18643
|
try {
|
|
17501
|
-
const ageMs = Date.now() -
|
|
18644
|
+
const ageMs = Date.now() - statSync7(path).mtimeMs;
|
|
17502
18645
|
if (ageMs > STARTING_LOCK_STALE_MS) {
|
|
17503
|
-
|
|
17504
|
-
|
|
18646
|
+
rmSync7(path, { force: true });
|
|
18647
|
+
writeFileSync16(path, String(process.pid), { flag: "wx" });
|
|
17505
18648
|
return true;
|
|
17506
18649
|
}
|
|
17507
18650
|
} catch {
|
|
@@ -17511,7 +18654,7 @@ function tryAcquireStartingLock(home = difftoolHome()) {
|
|
|
17511
18654
|
}
|
|
17512
18655
|
function releaseStartingLock(home = difftoolHome()) {
|
|
17513
18656
|
try {
|
|
17514
|
-
|
|
18657
|
+
rmSync7(startingLockPath(home), { force: true });
|
|
17515
18658
|
} catch {
|
|
17516
18659
|
}
|
|
17517
18660
|
}
|
|
@@ -21966,16 +23109,16 @@ __export(perceptual_diff_exports, {
|
|
|
21966
23109
|
decodeImage: () => decodeImage,
|
|
21967
23110
|
isIdentical: () => isIdentical
|
|
21968
23111
|
});
|
|
21969
|
-
import { readFileSync as
|
|
21970
|
-
import { extname as
|
|
23112
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
23113
|
+
import { extname as extname3 } from "path";
|
|
21971
23114
|
function decodeImage(path) {
|
|
21972
23115
|
let buf;
|
|
21973
23116
|
try {
|
|
21974
|
-
buf =
|
|
23117
|
+
buf = readFileSync23(path);
|
|
21975
23118
|
} catch {
|
|
21976
23119
|
return null;
|
|
21977
23120
|
}
|
|
21978
|
-
const ext =
|
|
23121
|
+
const ext = extname3(path).toLowerCase();
|
|
21979
23122
|
try {
|
|
21980
23123
|
if (ext === ".png") {
|
|
21981
23124
|
const png = import_pngjs.PNG.sync.read(buf);
|
|
@@ -21990,9 +23133,24 @@ function decodeImage(path) {
|
|
|
21990
23133
|
}
|
|
21991
23134
|
return null;
|
|
21992
23135
|
}
|
|
21993
|
-
function
|
|
21994
|
-
const
|
|
21995
|
-
|
|
23136
|
+
async function decodeWithFallback(path, pluginDecode) {
|
|
23137
|
+
const core = decodeImage(path);
|
|
23138
|
+
if (core !== null) return core;
|
|
23139
|
+
let buf;
|
|
23140
|
+
try {
|
|
23141
|
+
buf = readFileSync23(path);
|
|
23142
|
+
} catch {
|
|
23143
|
+
return null;
|
|
23144
|
+
}
|
|
23145
|
+
try {
|
|
23146
|
+
return await pluginDecode(new Uint8Array(buf), path);
|
|
23147
|
+
} catch {
|
|
23148
|
+
return null;
|
|
23149
|
+
}
|
|
23150
|
+
}
|
|
23151
|
+
async function comparePerceptual(actualPath, expectedPath, pluginDecode = decodeImageWithPlugin) {
|
|
23152
|
+
const actual = await decodeWithFallback(actualPath, pluginDecode);
|
|
23153
|
+
const expected = await decodeWithFallback(expectedPath, pluginDecode);
|
|
21996
23154
|
if (actual === null || expected === null) {
|
|
21997
23155
|
return { scorable: false, score: null, reason: "undecodable" };
|
|
21998
23156
|
}
|
|
@@ -22023,14 +23181,15 @@ var init_perceptual_diff = __esm({
|
|
|
22023
23181
|
import_jpeg_js = __toESM(require_jpeg_js(), 1);
|
|
22024
23182
|
init_pixelmatch();
|
|
22025
23183
|
import_pngjs = __toESM(require_png(), 1);
|
|
23184
|
+
init_plugins();
|
|
22026
23185
|
PERCEPTUAL_THRESHOLD = 0.1;
|
|
22027
23186
|
}
|
|
22028
23187
|
});
|
|
22029
23188
|
|
|
22030
23189
|
// src/cli.ts
|
|
22031
|
-
import { existsSync as
|
|
23190
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync17, realpathSync, statSync as statSync8 } from "fs";
|
|
22032
23191
|
import { tmpdir as tmpdir2 } from "os";
|
|
22033
|
-
import { basename as basename5, join as
|
|
23192
|
+
import { basename as basename5, join as join24, resolve as resolve12 } from "path";
|
|
22034
23193
|
|
|
22035
23194
|
// src/cli-subcommands.ts
|
|
22036
23195
|
import { resolve as resolve3 } from "path";
|
|
@@ -22095,160 +23254,23 @@ async function handleDifftoolRegistration(action, local, force) {
|
|
|
22095
23254
|
} else {
|
|
22096
23255
|
console.log(`Nothing to unregister at --${scope} scope (current tool: ${status.tool ?? "none"}).`);
|
|
22097
23256
|
}
|
|
22098
|
-
process.exit(0);
|
|
22099
|
-
}
|
|
22100
|
-
|
|
22101
|
-
// src/cli.ts
|
|
22102
|
-
init_connection();
|
|
22103
|
-
init_queries();
|
|
22104
|
-
init_debug();
|
|
22105
|
-
|
|
22106
|
-
// src/ai/config.ts
|
|
22107
|
-
init_zod();
|
|
22108
|
-
|
|
22109
|
-
// src/global-config.ts
|
|
22110
|
-
init_zod();
|
|
22111
|
-
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
22112
|
-
import { homedir } from "os";
|
|
22113
|
-
import { join as join4 } from "path";
|
|
22114
|
-
var GlobalConfigSchema = external_exports.record(external_exports.string(), external_exports.unknown());
|
|
22115
|
-
function resolveGlobalConfigDir() {
|
|
22116
|
-
const override = process.env.GLASSBOX_CONFIG_DIR;
|
|
22117
|
-
if (override !== void 0 && override.trim() !== "") return override;
|
|
22118
|
-
return join4(homedir(), ".glassbox");
|
|
22119
|
-
}
|
|
22120
|
-
var GLOBAL_CONFIG_DIR = resolveGlobalConfigDir();
|
|
22121
|
-
var GLOBAL_CONFIG_PATH = join4(GLOBAL_CONFIG_DIR, "config.json");
|
|
22122
|
-
function readGlobalConfig() {
|
|
22123
|
-
try {
|
|
22124
|
-
if (existsSync4(GLOBAL_CONFIG_PATH)) {
|
|
22125
|
-
const raw2 = JSON.parse(readFileSync3(GLOBAL_CONFIG_PATH, "utf-8"));
|
|
22126
|
-
const parsed = GlobalConfigSchema.safeParse(raw2);
|
|
22127
|
-
if (parsed.success) return parsed.data;
|
|
22128
|
-
}
|
|
22129
|
-
} catch {
|
|
22130
|
-
}
|
|
22131
|
-
return {};
|
|
22132
|
-
}
|
|
22133
|
-
function writeGlobalConfig(config2) {
|
|
22134
|
-
mkdirSync5(GLOBAL_CONFIG_DIR, { recursive: true });
|
|
22135
|
-
writeFileSync3(GLOBAL_CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
22136
|
-
try {
|
|
22137
|
-
chmodSync(GLOBAL_CONFIG_PATH, 384);
|
|
22138
|
-
} catch {
|
|
22139
|
-
}
|
|
22140
|
-
}
|
|
22141
|
-
function updateGlobalConfig(mutator) {
|
|
22142
|
-
const cfg = readGlobalConfig();
|
|
22143
|
-
const result = mutator(cfg);
|
|
22144
|
-
writeGlobalConfig(result === void 0 ? cfg : result);
|
|
22145
|
-
}
|
|
22146
|
-
|
|
22147
|
-
// src/ai/api-keys.ts
|
|
22148
|
-
import { spawnSync as spawnSync6 } from "child_process";
|
|
22149
|
-
|
|
22150
|
-
// src/ai/keychain.ts
|
|
22151
|
-
import { spawnSync as spawnSync5 } from "child_process";
|
|
22152
|
-
var WIN_CRED_READ_PS = `
|
|
22153
|
-
Add-Type -TypeDefinition @'
|
|
22154
|
-
using System;
|
|
22155
|
-
using System.Runtime.InteropServices;
|
|
22156
|
-
public class CredHelper {
|
|
22157
|
-
[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
22158
|
-
static extern bool CredRead(string t, int type, int f, out IntPtr p);
|
|
22159
|
-
[DllImport("advapi32")]
|
|
22160
|
-
static extern void CredFree(IntPtr p);
|
|
22161
|
-
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
22162
|
-
struct CRED {
|
|
22163
|
-
public int Flags; public int Type; public string TargetName; public string Comment;
|
|
22164
|
-
public long LastWritten; public int CredentialBlobSize; public IntPtr CredentialBlob;
|
|
22165
|
-
public int Persist; public int AttributeCount; public IntPtr Attributes;
|
|
22166
|
-
public string TargetAlias; public string UserName;
|
|
22167
|
-
}
|
|
22168
|
-
public static string Read(string target) {
|
|
22169
|
-
IntPtr ptr;
|
|
22170
|
-
if (!CredRead(target, 1, 0, out ptr)) return "";
|
|
22171
|
-
CRED c = (CRED)Marshal.PtrToStructure(ptr, typeof(CRED));
|
|
22172
|
-
string r = Marshal.PtrToStringUni(c.CredentialBlob, c.CredentialBlobSize / 2);
|
|
22173
|
-
CredFree(ptr);
|
|
22174
|
-
return r;
|
|
22175
|
-
}
|
|
22176
|
-
}
|
|
22177
|
-
'@
|
|
22178
|
-
`;
|
|
22179
|
-
function winCredTarget(platform) {
|
|
22180
|
-
return `glassbox-${platform}-api-key`;
|
|
22181
|
-
}
|
|
22182
|
-
function getKeyFromKeychain(platform) {
|
|
22183
|
-
const os = process.platform;
|
|
22184
|
-
const account = `${platform}-api-key`;
|
|
22185
|
-
try {
|
|
22186
|
-
if (os === "darwin") {
|
|
22187
|
-
const r = spawnSync5("security", ["find-generic-password", "-s", "glassbox", "-a", account, "-w"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
22188
|
-
const result = r.stdout.trim();
|
|
22189
|
-
return r.status === 0 && result !== "" ? result : null;
|
|
22190
|
-
}
|
|
22191
|
-
if (os === "linux") {
|
|
22192
|
-
const r = spawnSync5("secret-tool", ["lookup", "service", "glassbox", "account", account], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
22193
|
-
const result = r.stdout.trim();
|
|
22194
|
-
return r.status === 0 && result !== "" ? result : null;
|
|
22195
|
-
}
|
|
22196
|
-
if (os === "win32") {
|
|
22197
|
-
const target = winCredTarget(platform);
|
|
22198
|
-
const list = spawnSync5("cmdkey", ["/list:" + target], { encoding: "utf-8" });
|
|
22199
|
-
if (list.status !== 0 || (list.stdout || "").includes("* NONE *")) return null;
|
|
22200
|
-
const script = WIN_CRED_READ_PS + `Write-Output ([CredHelper]::Read('${target}'))`;
|
|
22201
|
-
const r = spawnSync5("powershell", ["-NoProfile", "-Command", "-"], { input: script, encoding: "utf-8" });
|
|
22202
|
-
const result = r.stdout.trim();
|
|
22203
|
-
return r.status === 0 && result !== "" ? result : null;
|
|
22204
|
-
}
|
|
22205
|
-
} catch {
|
|
22206
|
-
return null;
|
|
22207
|
-
}
|
|
22208
|
-
return null;
|
|
22209
|
-
}
|
|
22210
|
-
function assertSpawnOk(label, r) {
|
|
22211
|
-
if (r.error) throw new Error(`${label} failed: ${r.error.message}`);
|
|
22212
|
-
if (r.status !== 0) {
|
|
22213
|
-
const detail = (String(r.stderr) || String(r.stdout)).trim();
|
|
22214
|
-
throw new Error(`${label} failed (exit ${String(r.status)})${detail ? `: ${detail}` : ""}`);
|
|
22215
|
-
}
|
|
22216
|
-
}
|
|
22217
|
-
function saveKeyToKeychain(platform, key) {
|
|
22218
|
-
const os = process.platform;
|
|
22219
|
-
const account = `${platform}-api-key`;
|
|
22220
|
-
if (os === "darwin") {
|
|
22221
|
-
spawnSync5("security", ["delete-generic-password", "-s", "glassbox", "-a", account], { stdio: "pipe" });
|
|
22222
|
-
assertSpawnOk("Keychain write", spawnSync5("security", ["add-generic-password", "-s", "glassbox", "-a", account, "-w", key], { encoding: "utf-8" }));
|
|
22223
|
-
return;
|
|
22224
|
-
}
|
|
22225
|
-
if (os === "linux") {
|
|
22226
|
-
assertSpawnOk("System keyring write", spawnSync5("secret-tool", ["store", "--label=Glassbox API Key", "service", "glassbox", "account", account], { input: key, encoding: "utf-8" }));
|
|
22227
|
-
return;
|
|
22228
|
-
}
|
|
22229
|
-
if (os === "win32") {
|
|
22230
|
-
const target = winCredTarget(platform);
|
|
22231
|
-
const escapedKey = key.replace(/'/g, "''");
|
|
22232
|
-
const script = `cmdkey /generic:'${target}' /user:'glassbox' /pass:'${escapedKey}'`;
|
|
22233
|
-
assertSpawnOk("Credential Manager write", spawnSync5("powershell", ["-NoProfile", "-Command", "-"], { input: script, encoding: "utf-8" }));
|
|
22234
|
-
}
|
|
22235
|
-
}
|
|
22236
|
-
function isKeychainAvailable() {
|
|
22237
|
-
const os = process.platform;
|
|
22238
|
-
if (os === "darwin" || os === "win32") return true;
|
|
22239
|
-
if (os === "linux") {
|
|
22240
|
-
return spawnSync5("which", ["secret-tool"], { stdio: "pipe" }).status === 0;
|
|
22241
|
-
}
|
|
22242
|
-
return false;
|
|
22243
|
-
}
|
|
22244
|
-
function getKeychainLabel() {
|
|
22245
|
-
const os = process.platform;
|
|
22246
|
-
if (os === "darwin") return "Keychain";
|
|
22247
|
-
if (os === "linux") return "System Keyring";
|
|
22248
|
-
if (os === "win32") return "Credential Manager";
|
|
22249
|
-
return "System Keychain";
|
|
23257
|
+
process.exit(0);
|
|
22250
23258
|
}
|
|
22251
23259
|
|
|
23260
|
+
// src/cli.ts
|
|
23261
|
+
init_connection();
|
|
23262
|
+
init_queries();
|
|
23263
|
+
init_debug();
|
|
23264
|
+
|
|
23265
|
+
// src/ai/config.ts
|
|
23266
|
+
init_zod();
|
|
23267
|
+
init_global_config();
|
|
23268
|
+
|
|
23269
|
+
// src/ai/api-keys.ts
|
|
23270
|
+
init_global_config();
|
|
23271
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
23272
|
+
init_keychain();
|
|
23273
|
+
|
|
22252
23274
|
// src/ai/models.ts
|
|
22253
23275
|
init_zod();
|
|
22254
23276
|
var AIModelSchema = external_exports.object({
|
|
@@ -22340,6 +23362,7 @@ function resolveModelId(platform, modelId) {
|
|
|
22340
23362
|
}
|
|
22341
23363
|
|
|
22342
23364
|
// src/ai/api-keys.ts
|
|
23365
|
+
init_keychain();
|
|
22343
23366
|
function getKeyFromEnv(platform) {
|
|
22344
23367
|
const envName = ENV_KEY_NAMES[platform];
|
|
22345
23368
|
return process.env[envName] ?? null;
|
|
@@ -23551,8 +24574,10 @@ function getFileDiffs(mode, cwd) {
|
|
|
23551
24574
|
const diffArgs = getDiffArgs(mode);
|
|
23552
24575
|
const rawDiff = gitOrEmpty([...diffArgs, "-U3"], repoRoot);
|
|
23553
24576
|
const diffs = parseDiff(rawDiff);
|
|
23554
|
-
if (mode.type === "uncommitted") {
|
|
23555
|
-
const
|
|
24577
|
+
if (mode.type === "uncommitted" || mode.type === "files") {
|
|
24578
|
+
const lsArgs = ["ls-files", "--others", "--exclude-standard"];
|
|
24579
|
+
if (mode.type === "files") lsArgs.push("--", ...mode.patterns);
|
|
24580
|
+
const untracked = git(lsArgs, repoRoot).trim();
|
|
23556
24581
|
if (untracked) {
|
|
23557
24582
|
for (const file2 of untracked.split("\n").filter(Boolean)) {
|
|
23558
24583
|
if (!diffs.some((d) => d.filePath === file2)) {
|
|
@@ -23950,10 +24975,10 @@ async function updateReviewDiffs(reviewId, newDiffs, headCommit) {
|
|
|
23950
24975
|
|
|
23951
24976
|
// src/server.ts
|
|
23952
24977
|
import { serve } from "@hono/node-server";
|
|
23953
|
-
import { existsSync as
|
|
23954
|
-
import { Hono as
|
|
23955
|
-
import { dirname as
|
|
23956
|
-
import { fileURLToPath as
|
|
24978
|
+
import { existsSync as existsSync16, readFileSync as readFileSync19 } from "fs";
|
|
24979
|
+
import { Hono as Hono21 } from "hono";
|
|
24980
|
+
import { dirname as dirname8, join as join20 } from "path";
|
|
24981
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
23957
24982
|
|
|
23958
24983
|
// src/channel-config.ts
|
|
23959
24984
|
init_zod();
|
|
@@ -24053,6 +25078,11 @@ async function triggerChannel(dataDir, message) {
|
|
|
24053
25078
|
}
|
|
24054
25079
|
}
|
|
24055
25080
|
|
|
25081
|
+
// src/server.ts
|
|
25082
|
+
init_queries();
|
|
25083
|
+
init_global_config();
|
|
25084
|
+
init_plugins();
|
|
25085
|
+
|
|
24056
25086
|
// src/routes/ai-api.ts
|
|
24057
25087
|
import { Hono as Hono3 } from "hono";
|
|
24058
25088
|
|
|
@@ -25294,39 +26324,7 @@ __export(ai_exports, {
|
|
|
25294
26324
|
startAnalysis: () => startAnalysis
|
|
25295
26325
|
});
|
|
25296
26326
|
init_zod();
|
|
25297
|
-
|
|
25298
|
-
// src/api/_runner.ts
|
|
25299
|
-
init_zod();
|
|
25300
|
-
var OkResponseSchema = external_exports.object({ ok: external_exports.literal(true) });
|
|
25301
|
-
function currentReviewId() {
|
|
25302
|
-
if (typeof document === "undefined") return "";
|
|
25303
|
-
return document.body.dataset.reviewId ?? "";
|
|
25304
|
-
}
|
|
25305
|
-
async function apiCall(responseSchema, path, opts = {}) {
|
|
25306
|
-
const separator = path.includes("?") ? "&" : "?";
|
|
25307
|
-
const url2 = "/api" + path + separator + "reviewId=" + encodeURIComponent(currentReviewId());
|
|
25308
|
-
const res = await fetch(url2, {
|
|
25309
|
-
headers: { "Content-Type": "application/json" },
|
|
25310
|
-
method: opts.method,
|
|
25311
|
-
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
25312
|
-
});
|
|
25313
|
-
const json2 = await res.json();
|
|
25314
|
-
const result = responseSchema.safeParse(json2);
|
|
25315
|
-
if (!result.success) {
|
|
25316
|
-
const summary = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
25317
|
-
throw new Error(`API response from ${path} failed validation: ${summary}`);
|
|
25318
|
-
}
|
|
25319
|
-
return result.data;
|
|
25320
|
-
}
|
|
25321
|
-
function qs(params) {
|
|
25322
|
-
const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== null);
|
|
25323
|
-
if (entries.length === 0) return "";
|
|
25324
|
-
const usp = new URLSearchParams();
|
|
25325
|
-
for (const [k, v] of entries) usp.set(k, String(v));
|
|
25326
|
-
return "?" + usp.toString();
|
|
25327
|
-
}
|
|
25328
|
-
|
|
25329
|
-
// src/api/ai.ts
|
|
26327
|
+
init_runner();
|
|
25330
26328
|
var KeySourceSchema = external_exports.enum(["env", "keychain", "config"]).nullable();
|
|
25331
26329
|
var KeyStorageSchema = external_exports.enum(["keychain", "config"]);
|
|
25332
26330
|
var GuidedReviewConfigShapeSchema = external_exports.object({
|
|
@@ -25539,6 +26537,7 @@ __export(annotations_exports, {
|
|
|
25539
26537
|
});
|
|
25540
26538
|
init_zod();
|
|
25541
26539
|
init_schemas3();
|
|
26540
|
+
init_runner();
|
|
25542
26541
|
var AnnotationCategorySchema = external_exports.enum([
|
|
25543
26542
|
"bug",
|
|
25544
26543
|
"fix",
|
|
@@ -25644,6 +26643,7 @@ __export(channel_exports, {
|
|
|
25644
26643
|
triggerChannel: () => triggerChannel2
|
|
25645
26644
|
});
|
|
25646
26645
|
init_zod();
|
|
26646
|
+
init_runner();
|
|
25647
26647
|
var GetChannelStatusRespSchema = external_exports.object({
|
|
25648
26648
|
enabled: external_exports.boolean(),
|
|
25649
26649
|
connected: external_exports.boolean()
|
|
@@ -25687,6 +26687,7 @@ __export(context_exports, {
|
|
|
25687
26687
|
getContextLines: () => getContextLines
|
|
25688
26688
|
});
|
|
25689
26689
|
init_zod();
|
|
26690
|
+
init_runner();
|
|
25690
26691
|
var ContextLineSchema = external_exports.object({
|
|
25691
26692
|
num: external_exports.number().int(),
|
|
25692
26693
|
content: external_exports.string()
|
|
@@ -25736,6 +26737,7 @@ __export(files_exports, {
|
|
|
25736
26737
|
});
|
|
25737
26738
|
init_zod();
|
|
25738
26739
|
init_schemas3();
|
|
26740
|
+
init_runner();
|
|
25739
26741
|
var FileStatusSchema = external_exports.enum(["pending", "reviewed"]);
|
|
25740
26742
|
var GroundTruthMetaSchema = external_exports.object({
|
|
25741
26743
|
label: external_exports.string().optional(),
|
|
@@ -25750,6 +26752,10 @@ var GroundTruthMetaSchema = external_exports.object({
|
|
|
25750
26752
|
});
|
|
25751
26753
|
var ListFilesRespSchema = external_exports.object({
|
|
25752
26754
|
files: external_exports.array(ReviewFileSchema),
|
|
26755
|
+
/** Ids of files a content plugin renders (doc 29, GB-1052) — the client shows
|
|
26756
|
+
* the Code/Rendered toggle and routes the Rendered view through the image
|
|
26757
|
+
* viewer for these, like an SVG file. */
|
|
26758
|
+
pluginRendered: external_exports.array(external_exports.string()).optional(),
|
|
25753
26759
|
annotationCounts: external_exports.record(external_exports.string(), external_exports.number()),
|
|
25754
26760
|
staleCounts: external_exports.record(external_exports.string(), external_exports.number()),
|
|
25755
26761
|
/** Keyed by review-file id; omitted/empty for non-ground-truth reviews. */
|
|
@@ -25807,6 +26813,7 @@ __export(image_exports, {
|
|
|
25807
26813
|
imageUrl: () => imageUrl
|
|
25808
26814
|
});
|
|
25809
26815
|
init_zod();
|
|
26816
|
+
init_runner();
|
|
25810
26817
|
var ImageSideSchema = external_exports.enum(["old", "new"]);
|
|
25811
26818
|
var GetImageMetadataReqSchema = external_exports.object({ fileId: external_exports.string() });
|
|
25812
26819
|
var GetImageMetadataRespSchema = external_exports.object({
|
|
@@ -25833,6 +26840,7 @@ __export(outline_exports, {
|
|
|
25833
26840
|
getOutline: () => getOutline
|
|
25834
26841
|
});
|
|
25835
26842
|
init_zod();
|
|
26843
|
+
init_runner();
|
|
25836
26844
|
var baseOutlineSymbol = external_exports.object({
|
|
25837
26845
|
name: external_exports.string(),
|
|
25838
26846
|
kind: external_exports.enum(["class", "function"]),
|
|
@@ -25868,29 +26876,8 @@ async function findSymbolDefinition(req) {
|
|
|
25868
26876
|
);
|
|
25869
26877
|
}
|
|
25870
26878
|
|
|
25871
|
-
// src/api/
|
|
25872
|
-
|
|
25873
|
-
__export(project_settings_exports, {
|
|
25874
|
-
GetProjectSettingsRespSchema: () => GetProjectSettingsRespSchema,
|
|
25875
|
-
ProjectSettingsSchema: () => ProjectSettingsSchema,
|
|
25876
|
-
UpdateProjectSettingsReqSchema: () => UpdateProjectSettingsReqSchema,
|
|
25877
|
-
UpdateProjectSettingsRespSchema: () => UpdateProjectSettingsRespSchema,
|
|
25878
|
-
getProjectSettings: () => getProjectSettings,
|
|
25879
|
-
updateProjectSettings: () => updateProjectSettings
|
|
25880
|
-
});
|
|
25881
|
-
init_zod();
|
|
25882
|
-
var ProjectSettingsSchema = external_exports.object({
|
|
25883
|
-
appName: external_exports.string().optional()
|
|
25884
|
-
});
|
|
25885
|
-
var GetProjectSettingsRespSchema = ProjectSettingsSchema;
|
|
25886
|
-
var UpdateProjectSettingsReqSchema = ProjectSettingsSchema.partial();
|
|
25887
|
-
var UpdateProjectSettingsRespSchema = ProjectSettingsSchema;
|
|
25888
|
-
async function getProjectSettings() {
|
|
25889
|
-
return apiCall(GetProjectSettingsRespSchema, "/project-settings");
|
|
25890
|
-
}
|
|
25891
|
-
async function updateProjectSettings(req) {
|
|
25892
|
-
return apiCall(UpdateProjectSettingsRespSchema, "/project-settings", { method: "PATCH", body: req });
|
|
25893
|
-
}
|
|
26879
|
+
// src/api/index.ts
|
|
26880
|
+
init_project_settings();
|
|
25894
26881
|
|
|
25895
26882
|
// src/api/reviews.ts
|
|
25896
26883
|
var reviews_exports = {};
|
|
@@ -25916,6 +26903,7 @@ __export(reviews_exports, {
|
|
|
25916
26903
|
});
|
|
25917
26904
|
init_zod();
|
|
25918
26905
|
init_schemas3();
|
|
26906
|
+
init_runner();
|
|
25919
26907
|
var ListReviewsRespSchema = external_exports.array(ReviewSchema);
|
|
25920
26908
|
var GetCurrentReviewRespSchema = ReviewSchema.nullable();
|
|
25921
26909
|
var OnCompleteHookResultSchema = external_exports.object({
|
|
@@ -25979,6 +26967,7 @@ __export(share_prompt_exports, {
|
|
|
25979
26967
|
tickSharePrompt: () => tickSharePrompt
|
|
25980
26968
|
});
|
|
25981
26969
|
init_zod();
|
|
26970
|
+
init_runner();
|
|
25982
26971
|
var GetSharePromptStateRespSchema = external_exports.object({
|
|
25983
26972
|
dismissedAt: external_exports.number().nullable(),
|
|
25984
26973
|
totalOpenMs: external_exports.number()
|
|
@@ -26006,6 +26995,7 @@ __export(system_exports, {
|
|
|
26006
26995
|
openExternal: () => openExternal
|
|
26007
26996
|
});
|
|
26008
26997
|
init_zod();
|
|
26998
|
+
init_runner();
|
|
26009
26999
|
var OpenExternalReqSchema = external_exports.object({
|
|
26010
27000
|
// Restricted to http(s): this hands the value to the OS "open" handler, so
|
|
26011
27001
|
// we don't want to let it launch arbitrary schemes (file:, custom apps).
|
|
@@ -26519,6 +27509,7 @@ function themeToInlineStyle(colors) {
|
|
|
26519
27509
|
}
|
|
26520
27510
|
|
|
26521
27511
|
// src/api/themes.ts
|
|
27512
|
+
init_runner();
|
|
26522
27513
|
function buildThemeColorsSchema() {
|
|
26523
27514
|
const shape = {};
|
|
26524
27515
|
for (const key of THEME_VARIABLES) shape[key] = external_exports.string();
|
|
@@ -26611,11 +27602,13 @@ async function deleteTheme(req) {
|
|
|
26611
27602
|
// src/api/attachments.ts
|
|
26612
27603
|
init_zod();
|
|
26613
27604
|
init_schemas3();
|
|
27605
|
+
init_runner();
|
|
26614
27606
|
var ListAttachmentsRespSchema = external_exports.array(AttachmentSchema);
|
|
26615
27607
|
|
|
26616
27608
|
// src/api/difftool.ts
|
|
26617
27609
|
init_zod();
|
|
26618
27610
|
init_schemas3();
|
|
27611
|
+
init_runner();
|
|
26619
27612
|
var DifftoolStatusRespSchema = external_exports.object({
|
|
26620
27613
|
tool: external_exports.string().nullable(),
|
|
26621
27614
|
cmd: external_exports.string().nullable(),
|
|
@@ -26650,8 +27643,163 @@ var DifftoolPollRespSchema = external_exports.object({
|
|
|
26650
27643
|
});
|
|
26651
27644
|
var DifftoolEndRespSchema = external_exports.object({ ok: external_exports.literal(true) });
|
|
26652
27645
|
|
|
27646
|
+
// src/api/plugins.ts
|
|
27647
|
+
init_zod();
|
|
27648
|
+
init_runner();
|
|
27649
|
+
var PluginStatusSchema = external_exports.enum(["loaded", "disabled", "error"]);
|
|
27650
|
+
var PluginPreferenceInfoSchema = external_exports.object({
|
|
27651
|
+
key: external_exports.string(),
|
|
27652
|
+
label: external_exports.string(),
|
|
27653
|
+
type: external_exports.enum(["string", "number", "boolean", "select"]),
|
|
27654
|
+
default: external_exports.string().optional(),
|
|
27655
|
+
description: external_exports.string().optional(),
|
|
27656
|
+
options: external_exports.array(external_exports.string()).optional(),
|
|
27657
|
+
scope: external_exports.enum(["global", "project"]).optional(),
|
|
27658
|
+
secret: external_exports.boolean().optional()
|
|
27659
|
+
});
|
|
27660
|
+
var ConfigLabelColorSchema2 = external_exports.enum(["default", "success", "error", "warning", "transient"]);
|
|
27661
|
+
var ConfigLayoutItemSchema2 = external_exports.lazy(
|
|
27662
|
+
() => external_exports.object({
|
|
27663
|
+
type: external_exports.enum(["preference", "divider", "spacer", "label", "button", "group"]),
|
|
27664
|
+
key: external_exports.string().optional(),
|
|
27665
|
+
id: external_exports.string().optional(),
|
|
27666
|
+
text: external_exports.string().optional(),
|
|
27667
|
+
color: ConfigLabelColorSchema2.optional(),
|
|
27668
|
+
label: external_exports.string().optional(),
|
|
27669
|
+
action: external_exports.string().optional(),
|
|
27670
|
+
style: external_exports.string().optional(),
|
|
27671
|
+
title: external_exports.string().optional(),
|
|
27672
|
+
collapsed: external_exports.boolean().optional(),
|
|
27673
|
+
items: external_exports.array(ConfigLayoutItemSchema2).optional()
|
|
27674
|
+
})
|
|
27675
|
+
);
|
|
27676
|
+
var PluginInfoSchema = external_exports.object({
|
|
27677
|
+
id: external_exports.string(),
|
|
27678
|
+
name: external_exports.string(),
|
|
27679
|
+
version: external_exports.string(),
|
|
27680
|
+
/** Extensions the plugin's content types declare (informational). */
|
|
27681
|
+
extensions: external_exports.array(external_exports.string()),
|
|
27682
|
+
status: PluginStatusSchema,
|
|
27683
|
+
error: external_exports.string().optional(),
|
|
27684
|
+
/** True when active (loaded). False when disabled by either scope. */
|
|
27685
|
+
enabled: external_exports.boolean(),
|
|
27686
|
+
/** Which scope disables it (global wins), when disabled. */
|
|
27687
|
+
disabledScope: external_exports.enum(["global", "project"]).optional(),
|
|
27688
|
+
/** The two independent disable flags, so the UI can render both toggles. */
|
|
27689
|
+
globalDisabled: external_exports.boolean(),
|
|
27690
|
+
projectDisabled: external_exports.boolean(),
|
|
27691
|
+
/** Manifest-declared preferences + their current values (doc 29 FR-29.12).
|
|
27692
|
+
* Secret values are never sent; `secretConfigured` names the secret keys that
|
|
27693
|
+
* have a stored value (GB-1054). */
|
|
27694
|
+
preferences: external_exports.array(PluginPreferenceInfoSchema),
|
|
27695
|
+
preferenceValues: external_exports.record(external_exports.string(), external_exports.string()),
|
|
27696
|
+
secretConfigured: external_exports.array(external_exports.string()),
|
|
27697
|
+
/** Optional manifest arrangement of the preferences (doc 29 FR-29.18). */
|
|
27698
|
+
configLayout: external_exports.array(ConfigLayoutItemSchema2).optional(),
|
|
27699
|
+
/** Effective `label`-item text/color, keyed by label id (doc 29 FR-29.18). */
|
|
27700
|
+
configLabels: external_exports.record(external_exports.string(), external_exports.object({ text: external_exports.string(), color: ConfigLabelColorSchema2.optional() }))
|
|
27701
|
+
});
|
|
27702
|
+
var ListPluginsRespSchema = external_exports.object({
|
|
27703
|
+
plugins: external_exports.array(PluginInfoSchema),
|
|
27704
|
+
/** Set when a mutation (e.g. install-from-disk of a non-plugin folder) failed;
|
|
27705
|
+
* the list is still returned so the UI stays in sync + can show the message. */
|
|
27706
|
+
error: external_exports.string().optional()
|
|
27707
|
+
});
|
|
27708
|
+
var SetPluginDisabledReqSchema = external_exports.object({
|
|
27709
|
+
scope: external_exports.enum(["global", "project"]),
|
|
27710
|
+
disabled: external_exports.boolean()
|
|
27711
|
+
});
|
|
27712
|
+
var InstallPluginReqSchema = external_exports.object({ path: external_exports.string().min(1) });
|
|
27713
|
+
var RequirementStatusSchema = external_exports.object({
|
|
27714
|
+
id: external_exports.string(),
|
|
27715
|
+
label: external_exports.string(),
|
|
27716
|
+
met: external_exports.boolean(),
|
|
27717
|
+
hint: external_exports.string(),
|
|
27718
|
+
docUrl: external_exports.string().optional()
|
|
27719
|
+
});
|
|
27720
|
+
var AvailablePluginSchema = external_exports.object({
|
|
27721
|
+
id: external_exports.string(),
|
|
27722
|
+
name: external_exports.string(),
|
|
27723
|
+
version: external_exports.string(),
|
|
27724
|
+
description: external_exports.string().optional(),
|
|
27725
|
+
extensions: external_exports.array(external_exports.string()),
|
|
27726
|
+
requirements: external_exports.array(RequirementStatusSchema),
|
|
27727
|
+
provisionNotes: external_exports.array(external_exports.string()),
|
|
27728
|
+
selfContained: external_exports.boolean(),
|
|
27729
|
+
cliHint: external_exports.string().optional()
|
|
27730
|
+
});
|
|
27731
|
+
var ListAvailablePluginsRespSchema = external_exports.object({ available: external_exports.array(AvailablePluginSchema) });
|
|
27732
|
+
var ProvisionOutcomeSchema = external_exports.object({
|
|
27733
|
+
step: external_exports.string(),
|
|
27734
|
+
ok: external_exports.boolean(),
|
|
27735
|
+
skipped: external_exports.boolean(),
|
|
27736
|
+
detail: external_exports.string()
|
|
27737
|
+
});
|
|
27738
|
+
var InstallResultSchema = external_exports.object({
|
|
27739
|
+
id: external_exports.string(),
|
|
27740
|
+
installed: external_exports.boolean(),
|
|
27741
|
+
status: external_exports.enum(["ready", "needs-setup", "error"]),
|
|
27742
|
+
requirements: external_exports.array(RequirementStatusSchema),
|
|
27743
|
+
provisioned: external_exports.array(ProvisionOutcomeSchema),
|
|
27744
|
+
instructions: external_exports.array(external_exports.string()),
|
|
27745
|
+
error: external_exports.string().optional()
|
|
27746
|
+
});
|
|
27747
|
+
var InstallBundledPluginRespSchema = external_exports.object({
|
|
27748
|
+
result: InstallResultSchema,
|
|
27749
|
+
plugins: external_exports.array(PluginInfoSchema),
|
|
27750
|
+
available: external_exports.array(AvailablePluginSchema)
|
|
27751
|
+
});
|
|
27752
|
+
var UninstallPluginRespSchema = ListPluginsRespSchema.extend({
|
|
27753
|
+
available: external_exports.array(AvailablePluginSchema).optional()
|
|
27754
|
+
});
|
|
27755
|
+
var SetPluginPreferenceReqSchema = external_exports.object({ key: external_exports.string().min(1), value: external_exports.string() });
|
|
27756
|
+
var RunPluginActionReqSchema = external_exports.object({ actionId: external_exports.string().min(1), value: external_exports.string().optional() });
|
|
27757
|
+
var UIElementFaceSchema = external_exports.object({
|
|
27758
|
+
label: external_exports.string().optional(),
|
|
27759
|
+
icon: external_exports.string().optional(),
|
|
27760
|
+
title: external_exports.string().optional(),
|
|
27761
|
+
style: external_exports.string().optional()
|
|
27762
|
+
});
|
|
27763
|
+
var UISegmentSchema = external_exports.object({
|
|
27764
|
+
id: external_exports.string(),
|
|
27765
|
+
label: external_exports.string().optional(),
|
|
27766
|
+
icon: external_exports.string().optional(),
|
|
27767
|
+
title: external_exports.string().optional()
|
|
27768
|
+
});
|
|
27769
|
+
var PluginUIElementSchema = external_exports.object({
|
|
27770
|
+
pluginId: external_exports.string(),
|
|
27771
|
+
id: external_exports.string(),
|
|
27772
|
+
type: external_exports.string(),
|
|
27773
|
+
location: external_exports.string(),
|
|
27774
|
+
label: external_exports.string().optional(),
|
|
27775
|
+
icon: external_exports.string().optional(),
|
|
27776
|
+
title: external_exports.string().optional(),
|
|
27777
|
+
style: external_exports.string().optional(),
|
|
27778
|
+
action: external_exports.string().optional(),
|
|
27779
|
+
url: external_exports.string().optional(),
|
|
27780
|
+
// Stateful controls (doc 30 FR-30.3): toggle/switch (`on`/`off`), segmented
|
|
27781
|
+
// (`segments`/`selectionMode`); `stateKey` persists, `value` is the resolved
|
|
27782
|
+
// current state the host attaches when listing.
|
|
27783
|
+
on: UIElementFaceSchema.optional(),
|
|
27784
|
+
off: UIElementFaceSchema.optional(),
|
|
27785
|
+
onLabel: external_exports.string().optional(),
|
|
27786
|
+
offLabel: external_exports.string().optional(),
|
|
27787
|
+
segments: external_exports.array(UISegmentSchema).optional(),
|
|
27788
|
+
selectionMode: external_exports.string().optional(),
|
|
27789
|
+
stateKey: external_exports.string().optional(),
|
|
27790
|
+
value: external_exports.string().optional()
|
|
27791
|
+
});
|
|
27792
|
+
var ListPluginUiRespSchema = external_exports.object({ elements: external_exports.array(PluginUIElementSchema) });
|
|
27793
|
+
var RunPluginActionRespSchema = ListPluginsRespSchema.extend({
|
|
27794
|
+
result: external_exports.object({ message: external_exports.string().optional() }).optional()
|
|
27795
|
+
});
|
|
27796
|
+
|
|
27797
|
+
// src/api/index.ts
|
|
27798
|
+
init_project_settings();
|
|
27799
|
+
|
|
26653
27800
|
// src/api/review-notes.ts
|
|
26654
27801
|
init_zod();
|
|
27802
|
+
init_runner();
|
|
26655
27803
|
var DiscardReviewNoteReqSchema = external_exports.object({
|
|
26656
27804
|
guid: external_exports.string().min(1),
|
|
26657
27805
|
/** Repo-relative source file the note is on (scopes the shard search). */
|
|
@@ -27098,7 +28246,7 @@ aiApiRoutes.route("/", aiConfigRoutes);
|
|
|
27098
28246
|
aiApiRoutes.route("/", aiAnalysisRoutes);
|
|
27099
28247
|
|
|
27100
28248
|
// src/routes/api.ts
|
|
27101
|
-
import { Hono as
|
|
28249
|
+
import { Hono as Hono16 } from "hono";
|
|
27102
28250
|
|
|
27103
28251
|
// src/routes/api/annotations.ts
|
|
27104
28252
|
import { Hono as Hono4 } from "hono";
|
|
@@ -27108,8 +28256,8 @@ init_queries();
|
|
|
27108
28256
|
init_attachment_queries();
|
|
27109
28257
|
init_queries();
|
|
27110
28258
|
init_schemas3();
|
|
27111
|
-
import { existsSync as
|
|
27112
|
-
import { join as
|
|
28259
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
|
|
28260
|
+
import { join as join13 } from "path";
|
|
27113
28261
|
init_image_metadata();
|
|
27114
28262
|
|
|
27115
28263
|
// src/utils/formatReviewMode.ts
|
|
@@ -27328,10 +28476,10 @@ function buildRegion(a, gt, dimsFor) {
|
|
|
27328
28476
|
|
|
27329
28477
|
// src/export/generate.ts
|
|
27330
28478
|
function deleteReviewExport(reviewId, repoRoot) {
|
|
27331
|
-
const exportDir =
|
|
28479
|
+
const exportDir = join13(repoRoot, ".glassbox");
|
|
27332
28480
|
for (const ext of ["md", "json"]) {
|
|
27333
|
-
const archivePath =
|
|
27334
|
-
if (
|
|
28481
|
+
const archivePath = join13(exportDir, `review-${reviewId}.${ext}`);
|
|
28482
|
+
if (existsSync13(archivePath)) unlinkSync2(archivePath);
|
|
27335
28483
|
}
|
|
27336
28484
|
}
|
|
27337
28485
|
function annotationAnchorLabel(a) {
|
|
@@ -27358,8 +28506,8 @@ async function generateReviewExport(reviewId, repoRoot, isCurrent) {
|
|
|
27358
28506
|
if (!review) throw new Error("Review not found");
|
|
27359
28507
|
const files = await getReviewFiles(reviewId);
|
|
27360
28508
|
const annotations = await getAnnotationsForReview(reviewId);
|
|
27361
|
-
const exportDir =
|
|
27362
|
-
|
|
28509
|
+
const exportDir = join13(repoRoot, ".glassbox");
|
|
28510
|
+
mkdirSync10(exportDir, { recursive: true });
|
|
27363
28511
|
const byFile = {};
|
|
27364
28512
|
for (const a of annotations) {
|
|
27365
28513
|
if (!(a.file_path in byFile)) byFile[a.file_path] = [];
|
|
@@ -27452,20 +28600,20 @@ async function generateReviewExport(reviewId, repoRoot, isCurrent) {
|
|
|
27452
28600
|
resolveDims: resolveImageDims
|
|
27453
28601
|
});
|
|
27454
28602
|
const json2 = JSON.stringify(exportData, null, 2);
|
|
27455
|
-
const archivePath =
|
|
27456
|
-
|
|
27457
|
-
|
|
28603
|
+
const archivePath = join13(exportDir, `review-${review.id}.md`);
|
|
28604
|
+
writeFileSync11(archivePath, content, "utf-8");
|
|
28605
|
+
writeFileSync11(join13(exportDir, `review-${review.id}.json`), json2, "utf-8");
|
|
27458
28606
|
if (isCurrent) {
|
|
27459
|
-
const latestPath =
|
|
27460
|
-
|
|
27461
|
-
|
|
28607
|
+
const latestPath = join13(exportDir, "latest-review.md");
|
|
28608
|
+
writeFileSync11(latestPath, content, "utf-8");
|
|
28609
|
+
writeFileSync11(join13(exportDir, "latest-review.json"), json2, "utf-8");
|
|
27462
28610
|
return latestPath;
|
|
27463
28611
|
}
|
|
27464
28612
|
return archivePath;
|
|
27465
28613
|
}
|
|
27466
28614
|
function resolveImageDims(absPath) {
|
|
27467
28615
|
try {
|
|
27468
|
-
const meta3 = extractMetadata(
|
|
28616
|
+
const meta3 = extractMetadata(readFileSync12(absPath), absPath);
|
|
27469
28617
|
if (meta3.width !== null && meta3.height !== null) {
|
|
27470
28618
|
return { width: meta3.width, height: meta3.height };
|
|
27471
28619
|
}
|
|
@@ -27570,7 +28718,7 @@ annotationsRoutes.get("/annotations/all", async (c) => {
|
|
|
27570
28718
|
init_store2();
|
|
27571
28719
|
init_attachment_queries();
|
|
27572
28720
|
init_queries();
|
|
27573
|
-
import { readFileSync as
|
|
28721
|
+
import { readFileSync as readFileSync13, statSync as statSync5 } from "fs";
|
|
27574
28722
|
import { Hono as Hono5 } from "hono";
|
|
27575
28723
|
|
|
27576
28724
|
// src/utils/mime.ts
|
|
@@ -27684,9 +28832,9 @@ attachmentsRoutes.get("/attachments/:id/raw", async (c) => {
|
|
|
27684
28832
|
const attachment = await getAttachment(idParam.data);
|
|
27685
28833
|
if (attachment === void 0) return c.text("Not found", 404);
|
|
27686
28834
|
try {
|
|
27687
|
-
const stat =
|
|
28835
|
+
const stat = statSync5(attachment.stored_path);
|
|
27688
28836
|
if (!stat.isFile()) return c.text("Not found", 404);
|
|
27689
|
-
const bytes =
|
|
28837
|
+
const bytes = readFileSync13(attachment.stored_path);
|
|
27690
28838
|
return new Response(new Uint8Array(bytes), {
|
|
27691
28839
|
headers: {
|
|
27692
28840
|
"Content-Type": attachment.mime_type,
|
|
@@ -27704,7 +28852,7 @@ attachmentsRoutes.post("/attachments/:id/quicklook", async (c) => {
|
|
|
27704
28852
|
const attachment = await getAttachment(idParam.data);
|
|
27705
28853
|
if (attachment === void 0) return errorResponse(c, "Attachment not found", 404);
|
|
27706
28854
|
try {
|
|
27707
|
-
|
|
28855
|
+
statSync5(attachment.stored_path);
|
|
27708
28856
|
} catch {
|
|
27709
28857
|
return errorResponse(c, "Attachment file missing", 404);
|
|
27710
28858
|
}
|
|
@@ -27806,6 +28954,19 @@ function groundTruthMetaByFileId(mode, files) {
|
|
|
27806
28954
|
return out;
|
|
27807
28955
|
}
|
|
27808
28956
|
|
|
28957
|
+
// src/plugins/fileView.ts
|
|
28958
|
+
init_plugins();
|
|
28959
|
+
function pluginRendersFile(filePath) {
|
|
28960
|
+
return pluginsEnabled() && mightHandleFile(filePath);
|
|
28961
|
+
}
|
|
28962
|
+
async function renderPluginSvgSide(mode, filePath, oldPath, side, cwd) {
|
|
28963
|
+
if (mode === null || !pluginRendersFile(filePath)) return null;
|
|
28964
|
+
const source = getModeFileContent(mode, side === "old" ? oldPath : filePath, side, cwd);
|
|
28965
|
+
if (source.trim() === "") return null;
|
|
28966
|
+
const view = await renderContent({ bytes: new TextEncoder().encode(source), text: source, path: filePath, side });
|
|
28967
|
+
return view?.svg !== void 0 && view.svg !== "" ? view.svg : null;
|
|
28968
|
+
}
|
|
28969
|
+
|
|
27809
28970
|
// src/routes/api/files.ts
|
|
27810
28971
|
init_openOS();
|
|
27811
28972
|
var filesRoutes = new Hono7();
|
|
@@ -27818,7 +28979,14 @@ filesRoutes.get("/files", async (c) => {
|
|
|
27818
28979
|
getReview(reviewId)
|
|
27819
28980
|
]);
|
|
27820
28981
|
const groundTruth = review ? groundTruthMetaByFileId(parseModeString(review.mode), files) : void 0;
|
|
27821
|
-
|
|
28982
|
+
const pluginRendered = files.filter((f) => pluginRendersFile(f.file_path)).map((f) => f.id);
|
|
28983
|
+
return c.json({
|
|
28984
|
+
files,
|
|
28985
|
+
annotationCounts,
|
|
28986
|
+
staleCounts,
|
|
28987
|
+
...groundTruth ? { groundTruth } : {},
|
|
28988
|
+
...pluginRendered.length > 0 ? { pluginRendered } : {}
|
|
28989
|
+
});
|
|
27822
28990
|
});
|
|
27823
28991
|
filesRoutes.get("/files/:fileId", async (c) => {
|
|
27824
28992
|
const fileId = requirePathParam(c, "fileId");
|
|
@@ -27880,8 +29048,8 @@ import { Hono as Hono8 } from "hono";
|
|
|
27880
29048
|
|
|
27881
29049
|
// src/git/image.ts
|
|
27882
29050
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
27883
|
-
import { readFileSync as
|
|
27884
|
-
import { join as
|
|
29051
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
29052
|
+
import { join as join14, resolve as resolve8 } from "path";
|
|
27885
29053
|
init_image_metadata();
|
|
27886
29054
|
function getOldRef(mode) {
|
|
27887
29055
|
switch (mode.type) {
|
|
@@ -27945,14 +29113,14 @@ function gitShowFile(ref, filePath, repoRoot) {
|
|
|
27945
29113
|
}
|
|
27946
29114
|
function readWorkingFile(filePath, repoRoot) {
|
|
27947
29115
|
try {
|
|
27948
|
-
return
|
|
29116
|
+
return readFileSync14(resolve8(repoRoot, filePath));
|
|
27949
29117
|
} catch {
|
|
27950
29118
|
return null;
|
|
27951
29119
|
}
|
|
27952
29120
|
}
|
|
27953
29121
|
function readDiskImage(absPath) {
|
|
27954
29122
|
try {
|
|
27955
|
-
const data =
|
|
29123
|
+
const data = readFileSync14(absPath);
|
|
27956
29124
|
return { data, size: data.length };
|
|
27957
29125
|
} catch {
|
|
27958
29126
|
return null;
|
|
@@ -27963,7 +29131,7 @@ function groundTruthEntry(mode, filePath) {
|
|
|
27963
29131
|
}
|
|
27964
29132
|
function getOldImage(mode, filePath, oldPath, repoRoot) {
|
|
27965
29133
|
if (mode.type === "diff") {
|
|
27966
|
-
return readDiskImage(
|
|
29134
|
+
return readDiskImage(join14(directComparisonRoots(mode).rootA, oldPath ?? filePath));
|
|
27967
29135
|
}
|
|
27968
29136
|
if (mode.type === "ground-truth") {
|
|
27969
29137
|
const entry = groundTruthEntry(mode, filePath);
|
|
@@ -27983,7 +29151,7 @@ function getOldImage(mode, filePath, oldPath, repoRoot) {
|
|
|
27983
29151
|
}
|
|
27984
29152
|
function getNewImage(mode, filePath, repoRoot) {
|
|
27985
29153
|
if (mode.type === "diff") {
|
|
27986
|
-
return readDiskImage(
|
|
29154
|
+
return readDiskImage(join14(directComparisonRoots(mode).rootB, filePath));
|
|
27987
29155
|
}
|
|
27988
29156
|
if (mode.type === "ground-truth") {
|
|
27989
29157
|
const entry = groundTruthEntry(mode, filePath);
|
|
@@ -28057,6 +29225,12 @@ imageRoutes.get("/image/:fileId/:side", async (c) => {
|
|
|
28057
29225
|
const diff = parseDiffData(file2.diff_data);
|
|
28058
29226
|
const oldPath = diff?.oldPath ?? null;
|
|
28059
29227
|
const status = diff?.status ?? "modified";
|
|
29228
|
+
if (!isImageFile(file2.file_path) && !(side === "old" && status === "added") && !(side === "new" && status === "deleted")) {
|
|
29229
|
+
const svg = await renderPluginSvgSide(mode, file2.file_path, oldPath ?? file2.file_path, side, repoRoot);
|
|
29230
|
+
if (svg !== null) {
|
|
29231
|
+
return new Response(svg, { headers: { "Content-Type": "image/svg+xml", "Cache-Control": "no-cache" } });
|
|
29232
|
+
}
|
|
29233
|
+
}
|
|
28060
29234
|
const image = resolveImageSide(
|
|
28061
29235
|
fileIdParam.data,
|
|
28062
29236
|
side,
|
|
@@ -28079,7 +29253,7 @@ imageRoutes.get("/image/:fileId/:side", async (c) => {
|
|
|
28079
29253
|
init_queries();
|
|
28080
29254
|
init_debug();
|
|
28081
29255
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
28082
|
-
import { readFileSync as
|
|
29256
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
28083
29257
|
import { Hono as Hono9 } from "hono";
|
|
28084
29258
|
import { resolve as resolve9 } from "path";
|
|
28085
29259
|
|
|
@@ -28457,7 +29631,7 @@ outlineRoutes.get("/symbol-definition", async (c) => {
|
|
|
28457
29631
|
}
|
|
28458
29632
|
let content = "";
|
|
28459
29633
|
try {
|
|
28460
|
-
content =
|
|
29634
|
+
content = readFileSync15(resolve9(repoRoot, filePath), "utf-8");
|
|
28461
29635
|
} catch {
|
|
28462
29636
|
continue;
|
|
28463
29637
|
}
|
|
@@ -28492,48 +29666,268 @@ function collectDefinitions(symbols, targetName, fileId, filePath, out) {
|
|
|
28492
29666
|
}
|
|
28493
29667
|
}
|
|
28494
29668
|
|
|
28495
|
-
// src/routes/api/
|
|
28496
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "fs";
|
|
29669
|
+
// src/routes/api/plugins.ts
|
|
28497
29670
|
import { Hono as Hono10 } from "hono";
|
|
28498
|
-
|
|
28499
|
-
|
|
28500
|
-
|
|
28501
|
-
|
|
29671
|
+
|
|
29672
|
+
// src/plugins/available.ts
|
|
29673
|
+
init_install();
|
|
29674
|
+
init_loader();
|
|
29675
|
+
import { existsSync as existsSync14 } from "fs";
|
|
29676
|
+
import { join as join15 } from "path";
|
|
29677
|
+
|
|
29678
|
+
// src/plugins/readiness.ts
|
|
29679
|
+
import { spawnSync as spawnSync10 } from "child_process";
|
|
29680
|
+
var defaultProbe = (command, args) => {
|
|
28502
29681
|
try {
|
|
28503
|
-
|
|
28504
|
-
|
|
28505
|
-
const parsed = ProjectSettingsSchema.safeParse(raw2);
|
|
28506
|
-
if (parsed.success) return parsed.data;
|
|
28507
|
-
}
|
|
29682
|
+
const r = spawnSync10(command, args, { stdio: "ignore", timeout: 1e4 });
|
|
29683
|
+
return r.error === void 0 && r.status === 0;
|
|
28508
29684
|
} catch {
|
|
29685
|
+
return false;
|
|
28509
29686
|
}
|
|
28510
|
-
|
|
29687
|
+
};
|
|
29688
|
+
function checkRequirement(req, runProbe = defaultProbe) {
|
|
29689
|
+
const met = runProbe(req.command, req.checkArgs ?? ["--version"]);
|
|
29690
|
+
return { id: req.id, label: req.label, met, hint: req.hint, docUrl: req.docUrl };
|
|
28511
29691
|
}
|
|
28512
|
-
function
|
|
28513
|
-
|
|
28514
|
-
mkdirSync9(dir, { recursive: true });
|
|
28515
|
-
writeFileSync10(join12(dir, "settings.json"), JSON.stringify(settings, null, 2), "utf-8");
|
|
29692
|
+
function checkRequirements(reqs, runProbe = defaultProbe) {
|
|
29693
|
+
return (reqs ?? []).map((r) => checkRequirement(r, runProbe));
|
|
28516
29694
|
}
|
|
28517
|
-
|
|
29695
|
+
function requirementMet(statuses, id) {
|
|
29696
|
+
return statuses.find((s) => s.id === id)?.met ?? false;
|
|
29697
|
+
}
|
|
29698
|
+
|
|
29699
|
+
// src/plugins/available.ts
|
|
29700
|
+
function manifestExtensions(manifest) {
|
|
29701
|
+
return (manifest.contentTypes ?? []).flatMap((ct) => ct.extensions ?? []);
|
|
29702
|
+
}
|
|
29703
|
+
function provisionNotes(install) {
|
|
29704
|
+
const notes = [];
|
|
29705
|
+
for (const step of install?.provision ?? []) {
|
|
29706
|
+
if (step.kind === "fetch") notes.push(`Downloads ${step.dest}.`);
|
|
29707
|
+
else notes.push(step.note ?? `Installs ${step.packages.join(", ")}.`);
|
|
29708
|
+
}
|
|
29709
|
+
return notes;
|
|
29710
|
+
}
|
|
29711
|
+
function describeAvailablePlugin(manifest, runProbe) {
|
|
29712
|
+
const requirements = checkRequirements(manifest.install?.requirements, runProbe);
|
|
29713
|
+
const notes = provisionNotes(manifest.install);
|
|
29714
|
+
return {
|
|
29715
|
+
id: manifest.id,
|
|
29716
|
+
name: manifest.name,
|
|
29717
|
+
version: manifest.version,
|
|
29718
|
+
description: manifest.description,
|
|
29719
|
+
extensions: manifestExtensions(manifest),
|
|
29720
|
+
requirements,
|
|
29721
|
+
provisionNotes: notes,
|
|
29722
|
+
selfContained: requirements.length === 0 && notes.length === 0,
|
|
29723
|
+
cliHint: manifest.install?.cliHint
|
|
29724
|
+
};
|
|
29725
|
+
}
|
|
29726
|
+
function listAvailablePlugins(opts) {
|
|
29727
|
+
const bundledDir = opts?.bundledDir ?? bundledPluginsDir();
|
|
29728
|
+
const userDir = opts?.userDir ?? pluginsDir();
|
|
29729
|
+
const out = [];
|
|
29730
|
+
for (const { manifest } of discoverBundledPlugins(bundledDir)) {
|
|
29731
|
+
if (manifest.autoInstall !== false) continue;
|
|
29732
|
+
if (existsSync14(join15(userDir, manifest.id))) continue;
|
|
29733
|
+
out.push(describeAvailablePlugin(manifest, opts?.runProbe));
|
|
29734
|
+
}
|
|
29735
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
29736
|
+
}
|
|
29737
|
+
|
|
29738
|
+
// src/routes/api/plugins.ts
|
|
29739
|
+
init_enablement();
|
|
29740
|
+
init_plugins();
|
|
29741
|
+
init_install();
|
|
29742
|
+
|
|
29743
|
+
// src/plugins/install-action.ts
|
|
29744
|
+
init_install();
|
|
29745
|
+
init_loader();
|
|
29746
|
+
import { spawnSync as spawnSync11 } from "child_process";
|
|
29747
|
+
import { createHash as createHash4 } from "crypto";
|
|
29748
|
+
import { cpSync as cpSync2, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
|
|
29749
|
+
import { join as join16 } from "path";
|
|
29750
|
+
var defaultFetchBytes = async (url2) => {
|
|
29751
|
+
const res = await fetch(url2);
|
|
29752
|
+
if (!res.ok) throw new Error(`HTTP ${String(res.status)}`);
|
|
29753
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
29754
|
+
};
|
|
29755
|
+
var defaultRunNpmInstall = (dir, packages) => {
|
|
29756
|
+
const r = spawnSync11("npm", ["install", "--no-save", "--prefix", dir, ...packages], { stdio: "ignore", timeout: 6e5 });
|
|
29757
|
+
if (r.error !== void 0) return { ok: false, detail: r.error.message };
|
|
29758
|
+
if (r.status !== 0) return { ok: false, detail: `npm exited ${String(r.status)}` };
|
|
29759
|
+
return { ok: true, detail: "installed" };
|
|
29760
|
+
};
|
|
29761
|
+
async function runProvisionStep(step, installDir, requirements, deps) {
|
|
29762
|
+
if (step.kind === "fetch") {
|
|
29763
|
+
const label2 = `Download ${step.dest}`;
|
|
29764
|
+
try {
|
|
29765
|
+
const bytes = await deps.fetchBytes(step.url);
|
|
29766
|
+
if (step.sha256 !== void 0) {
|
|
29767
|
+
const got = createHash4("sha256").update(bytes).digest("hex");
|
|
29768
|
+
if (got.toLowerCase() !== step.sha256.toLowerCase()) {
|
|
29769
|
+
return { step: label2, ok: false, skipped: false, detail: `checksum mismatch (expected ${step.sha256})` };
|
|
29770
|
+
}
|
|
29771
|
+
}
|
|
29772
|
+
writeFileSync12(join16(installDir, step.dest), bytes);
|
|
29773
|
+
return { step: label2, ok: true, skipped: false, detail: `saved ${step.dest}` };
|
|
29774
|
+
} catch (e) {
|
|
29775
|
+
return { step: label2, ok: false, skipped: false, detail: e instanceof Error ? e.message : "download failed" };
|
|
29776
|
+
}
|
|
29777
|
+
}
|
|
29778
|
+
const label = `Install ${step.packages.join(", ")}`;
|
|
29779
|
+
const reqId = step.requires ?? "npm";
|
|
29780
|
+
if (!requirementMet(requirements, reqId)) {
|
|
29781
|
+
return { step: label, ok: false, skipped: true, detail: `${reqId} is not available` };
|
|
29782
|
+
}
|
|
29783
|
+
const r = deps.runNpmInstall(installDir, step.packages);
|
|
29784
|
+
return { step: label, ok: r.ok, skipped: false, detail: r.detail };
|
|
29785
|
+
}
|
|
29786
|
+
function buildInstructions(manifest, requirements, provisioned) {
|
|
29787
|
+
const out = [];
|
|
29788
|
+
for (const r of requirements) if (!r.met) out.push(`${r.label}: ${r.hint}`);
|
|
29789
|
+
for (const p of provisioned) {
|
|
29790
|
+
if (p.ok) continue;
|
|
29791
|
+
out.push(p.skipped ? `${p.step} was skipped \u2014 ${p.detail}.` : `${p.step} failed \u2014 ${p.detail}.`);
|
|
29792
|
+
}
|
|
29793
|
+
const cliHint = manifest.install?.cliHint;
|
|
29794
|
+
if (out.length > 0 && cliHint !== void 0 && cliHint !== "") out.push(`Or finish from a terminal: ${cliHint}`);
|
|
29795
|
+
return out;
|
|
29796
|
+
}
|
|
29797
|
+
async function installAvailablePlugin(id, deps = {}) {
|
|
29798
|
+
const bundledDir = deps.bundledDir ?? bundledPluginsDir();
|
|
29799
|
+
const userDir = deps.userDir ?? pluginsDir();
|
|
29800
|
+
const fetchBytes = deps.fetchBytes ?? defaultFetchBytes;
|
|
29801
|
+
const runNpmInstall = deps.runNpmInstall ?? defaultRunNpmInstall;
|
|
29802
|
+
const bundled = discoverBundledPlugins(bundledDir).find((b) => b.manifest.id === id);
|
|
29803
|
+
if (bundled === void 0) {
|
|
29804
|
+
return { id, installed: false, status: "error", requirements: [], provisioned: [], instructions: [], error: "Not a bundled plugin available to install." };
|
|
29805
|
+
}
|
|
29806
|
+
const manifest = bundled.manifest;
|
|
29807
|
+
const dest = join16(userDir, id);
|
|
29808
|
+
try {
|
|
29809
|
+
mkdirSync11(userDir, { recursive: true });
|
|
29810
|
+
cpSync2(bundled.dir, dest, { recursive: true });
|
|
29811
|
+
undismissPlugin(id, userDir);
|
|
29812
|
+
} catch (e) {
|
|
29813
|
+
return { id, installed: false, status: "error", requirements: [], provisioned: [], instructions: [], error: e instanceof Error ? e.message : "Copy failed." };
|
|
29814
|
+
}
|
|
29815
|
+
const requirements = checkRequirements(manifest.install?.requirements, deps.runProbe);
|
|
29816
|
+
const provisioned = [];
|
|
29817
|
+
for (const step of manifest.install?.provision ?? []) {
|
|
29818
|
+
provisioned.push(await runProvisionStep(step, dest, requirements, { fetchBytes, runNpmInstall }));
|
|
29819
|
+
}
|
|
29820
|
+
const instructions = buildInstructions(manifest, requirements, provisioned);
|
|
29821
|
+
const status = instructions.length === 0 ? "ready" : "needs-setup";
|
|
29822
|
+
return { id, installed: true, status, requirements, provisioned, instructions };
|
|
29823
|
+
}
|
|
29824
|
+
|
|
29825
|
+
// src/routes/api/plugins.ts
|
|
29826
|
+
init_loader();
|
|
29827
|
+
init_settings();
|
|
29828
|
+
var pluginsRoutes = new Hono10();
|
|
29829
|
+
pluginsRoutes.get("/plugins", (c) => {
|
|
29830
|
+
return c.json({ plugins: describeInstalledPlugins(c.get("repoRoot")) });
|
|
29831
|
+
});
|
|
29832
|
+
pluginsRoutes.get("/plugins/ui", (c) => {
|
|
29833
|
+
return c.json({ elements: listPluginUIElements(c.get("repoRoot")) });
|
|
29834
|
+
});
|
|
29835
|
+
pluginsRoutes.get("/plugins/available", (c) => {
|
|
29836
|
+
return c.json({ available: listAvailablePlugins() });
|
|
29837
|
+
});
|
|
29838
|
+
pluginsRoutes.post("/plugins/:id/install-bundled", async (c) => {
|
|
29839
|
+
const id = requirePathParam(c, "id");
|
|
29840
|
+
if (!id.ok) return id.response;
|
|
28518
29841
|
const repoRoot = c.get("repoRoot");
|
|
28519
|
-
|
|
29842
|
+
const result = await installAvailablePlugin(id.data);
|
|
29843
|
+
if (result.installed) await reloadContentPlugins(repoRoot);
|
|
29844
|
+
return c.json({ result, plugins: describeInstalledPlugins(repoRoot), available: listAvailablePlugins() });
|
|
29845
|
+
});
|
|
29846
|
+
pluginsRoutes.post("/plugins/:id/disabled", async (c) => {
|
|
29847
|
+
const id = requirePathParam(c, "id");
|
|
29848
|
+
if (!id.ok) return id.response;
|
|
29849
|
+
const parsed = await parseBody(c, SetPluginDisabledReqSchema);
|
|
29850
|
+
if (!parsed.ok) return parsed.response;
|
|
29851
|
+
const repoRoot = c.get("repoRoot");
|
|
29852
|
+
if (parsed.data.scope === "global") setGlobalDisabled(id.data, parsed.data.disabled);
|
|
29853
|
+
else setProjectDisabled(repoRoot, id.data, parsed.data.disabled);
|
|
29854
|
+
await reloadContentPlugins(repoRoot);
|
|
29855
|
+
return c.json({ plugins: describeInstalledPlugins(repoRoot) });
|
|
29856
|
+
});
|
|
29857
|
+
pluginsRoutes.post("/plugins/install", async (c) => {
|
|
29858
|
+
const parsed = await parseBody(c, InstallPluginReqSchema);
|
|
29859
|
+
if (!parsed.ok) return parsed.response;
|
|
29860
|
+
const repoRoot = c.get("repoRoot");
|
|
29861
|
+
try {
|
|
29862
|
+
installPluginFromDisk(parsed.data.path);
|
|
29863
|
+
} catch (e) {
|
|
29864
|
+
return c.json({ plugins: describeInstalledPlugins(repoRoot), error: e instanceof Error ? e.message : "Install failed" }, 400);
|
|
29865
|
+
}
|
|
29866
|
+
await reloadContentPlugins(repoRoot);
|
|
29867
|
+
return c.json({ plugins: describeInstalledPlugins(repoRoot) });
|
|
29868
|
+
});
|
|
29869
|
+
pluginsRoutes.post("/plugins/:id/preferences", async (c) => {
|
|
29870
|
+
const id = requirePathParam(c, "id");
|
|
29871
|
+
if (!id.ok) return id.response;
|
|
29872
|
+
const parsed = await parseBody(c, SetPluginPreferenceReqSchema);
|
|
29873
|
+
if (!parsed.ok) return parsed.response;
|
|
29874
|
+
const manifest = getPluginManifest(id.data);
|
|
29875
|
+
if (manifest === void 0) return c.json({ error: "Plugin not found" }, 404);
|
|
29876
|
+
const repoRoot = c.get("repoRoot");
|
|
29877
|
+
writePluginSetting(manifest, repoRoot, parsed.data.key, parsed.data.value);
|
|
29878
|
+
await reloadContentPlugins(repoRoot);
|
|
29879
|
+
return c.json({ plugins: describeInstalledPlugins(repoRoot) });
|
|
29880
|
+
});
|
|
29881
|
+
pluginsRoutes.post("/plugins/:id/action", async (c) => {
|
|
29882
|
+
const id = requirePathParam(c, "id");
|
|
29883
|
+
if (!id.ok) return id.response;
|
|
29884
|
+
const parsed = await parseBody(c, RunPluginActionReqSchema);
|
|
29885
|
+
if (!parsed.ok) return parsed.response;
|
|
29886
|
+
const repoRoot = c.get("repoRoot");
|
|
29887
|
+
if (parsed.data.value !== void 0) persistPluginUIState(id.data, parsed.data.actionId, parsed.data.value, repoRoot);
|
|
29888
|
+
let result;
|
|
29889
|
+
try {
|
|
29890
|
+
result = await runPluginAction(id.data, parsed.data.actionId, parsed.data.value);
|
|
29891
|
+
} catch (e) {
|
|
29892
|
+
return c.json({ plugins: describeInstalledPlugins(repoRoot), error: e instanceof Error ? e.message : "Action failed" }, 400);
|
|
29893
|
+
}
|
|
29894
|
+
return c.json({ plugins: describeInstalledPlugins(repoRoot), result: result ?? void 0 });
|
|
29895
|
+
});
|
|
29896
|
+
pluginsRoutes.delete("/plugins/:id", async (c) => {
|
|
29897
|
+
const id = requirePathParam(c, "id");
|
|
29898
|
+
if (!id.ok) return id.response;
|
|
29899
|
+
const repoRoot = c.get("repoRoot");
|
|
29900
|
+
uninstallPlugin(id.data);
|
|
29901
|
+
clearConfigLabelOverrides(id.data);
|
|
29902
|
+
clearPluginUIElements(id.data);
|
|
29903
|
+
await reloadContentPlugins(repoRoot);
|
|
29904
|
+
return c.json({ plugins: describeInstalledPlugins(repoRoot), available: listAvailablePlugins() });
|
|
29905
|
+
});
|
|
29906
|
+
|
|
29907
|
+
// src/routes/api/project-settings.ts
|
|
29908
|
+
init_project_settings();
|
|
29909
|
+
init_project_settings_store();
|
|
29910
|
+
import { Hono as Hono11 } from "hono";
|
|
29911
|
+
var projectSettingsRoutes = new Hono11();
|
|
29912
|
+
projectSettingsRoutes.get("/project-settings", (c) => {
|
|
29913
|
+
return c.json(readProjectSettings(c.get("repoRoot")));
|
|
28520
29914
|
});
|
|
28521
29915
|
projectSettingsRoutes.patch("/project-settings", async (c) => {
|
|
28522
29916
|
const repoRoot = c.get("repoRoot");
|
|
28523
29917
|
const parsed = await parseBody(c, UpdateProjectSettingsReqSchema);
|
|
28524
29918
|
if (!parsed.ok) return parsed.response;
|
|
28525
|
-
|
|
28526
|
-
|
|
28527
|
-
|
|
28528
|
-
return c.json(
|
|
29919
|
+
updateProjectSettings2(repoRoot, (s) => {
|
|
29920
|
+
if (parsed.data.appName !== void 0) s.appName = parsed.data.appName || void 0;
|
|
29921
|
+
});
|
|
29922
|
+
return c.json(readProjectSettings(repoRoot));
|
|
28529
29923
|
});
|
|
28530
29924
|
|
|
28531
29925
|
// src/routes/api/review-notes.ts
|
|
28532
29926
|
init_store();
|
|
28533
|
-
import { readFileSync as
|
|
28534
|
-
import { Hono as
|
|
28535
|
-
import { extname, relative as relative2, resolve as resolve10 } from "path";
|
|
28536
|
-
var reviewNotesRoutes = new
|
|
29927
|
+
import { readFileSync as readFileSync16, statSync as statSync6 } from "fs";
|
|
29928
|
+
import { Hono as Hono12 } from "hono";
|
|
29929
|
+
import { extname as extname2, relative as relative2, resolve as resolve10 } from "path";
|
|
29930
|
+
var reviewNotesRoutes = new Hono12();
|
|
28537
29931
|
var ARTIFACT_SERVE_MAX_BYTES = 1e7;
|
|
28538
29932
|
var IMAGE_CONTENT_TYPES = {
|
|
28539
29933
|
".png": "image/png",
|
|
@@ -28551,13 +29945,13 @@ reviewNotesRoutes.get("/review-notes/artifact", (c) => {
|
|
|
28551
29945
|
const abs = resolve10(repoRoot, file2);
|
|
28552
29946
|
const rel = relative2(repoRoot, abs);
|
|
28553
29947
|
if (rel === "" || rel.startsWith("..") || rel.startsWith("/")) return c.text("Forbidden", 403);
|
|
28554
|
-
const ext =
|
|
29948
|
+
const ext = extname2(abs).toLowerCase();
|
|
28555
29949
|
if (!(ext in IMAGE_CONTENT_TYPES)) return c.text("Unsupported artifact type", 415);
|
|
28556
29950
|
const contentType = IMAGE_CONTENT_TYPES[ext];
|
|
28557
29951
|
try {
|
|
28558
|
-
const stat =
|
|
29952
|
+
const stat = statSync6(abs);
|
|
28559
29953
|
if (!stat.isFile() || stat.size > ARTIFACT_SERVE_MAX_BYTES) return c.text("Not found", 404);
|
|
28560
|
-
const body =
|
|
29954
|
+
const body = readFileSync16(abs);
|
|
28561
29955
|
return c.body(body, 200, { "Content-Type": contentType });
|
|
28562
29956
|
} catch {
|
|
28563
29957
|
return c.text("Not found", 404);
|
|
@@ -28574,17 +29968,17 @@ reviewNotesRoutes.delete("/review-notes/:guid", (c) => {
|
|
|
28574
29968
|
|
|
28575
29969
|
// src/routes/api/reviews.ts
|
|
28576
29970
|
init_queries();
|
|
28577
|
-
import { Hono as
|
|
29971
|
+
import { Hono as Hono13 } from "hono";
|
|
28578
29972
|
|
|
28579
29973
|
// src/export/on-complete-hook.ts
|
|
28580
29974
|
import { spawn as spawn2 } from "child_process";
|
|
28581
29975
|
import { appendFileSync } from "fs";
|
|
28582
|
-
import { join as
|
|
29976
|
+
import { join as join17 } from "path";
|
|
28583
29977
|
function runOnCompleteHook(command, ctx) {
|
|
28584
29978
|
if (command === null || command.trim() === "") {
|
|
28585
29979
|
return Promise.resolve({ ran: false, ok: true, exitCode: 0 });
|
|
28586
29980
|
}
|
|
28587
|
-
const logPath =
|
|
29981
|
+
const logPath = join17(ctx.repoRoot, ".glassbox", "on-complete.log");
|
|
28588
29982
|
const log = (s) => {
|
|
28589
29983
|
try {
|
|
28590
29984
|
appendFileSync(logPath, s);
|
|
@@ -28636,7 +30030,8 @@ $ ${command}
|
|
|
28636
30030
|
}
|
|
28637
30031
|
|
|
28638
30032
|
// src/routes/api/reviews.ts
|
|
28639
|
-
|
|
30033
|
+
init_plugins();
|
|
30034
|
+
var reviewsRoutes = new Hono13();
|
|
28640
30035
|
reviewsRoutes.get("/reviews", async (c) => {
|
|
28641
30036
|
const repoRoot = c.get("repoRoot");
|
|
28642
30037
|
const reviews = await listReviews(repoRoot);
|
|
@@ -28660,6 +30055,13 @@ reviewsRoutes.post("/review/complete", async (c) => {
|
|
|
28660
30055
|
jsonPath: exportPath.replace(/\.md$/, ".json"),
|
|
28661
30056
|
markdownPath: exportPath
|
|
28662
30057
|
});
|
|
30058
|
+
try {
|
|
30059
|
+
const completed = await getReview(reviewId);
|
|
30060
|
+
if (completed !== void 0) {
|
|
30061
|
+
await notifyReviewCompleted(completed, await getAnnotationsForReview(reviewId), exportPath);
|
|
30062
|
+
}
|
|
30063
|
+
} catch {
|
|
30064
|
+
}
|
|
28663
30065
|
return c.json({ status: "completed", exportPath, isCurrent, reviewId, hook });
|
|
28664
30066
|
});
|
|
28665
30067
|
reviewsRoutes.post("/review/reopen", async (c) => {
|
|
@@ -28721,8 +30123,9 @@ reviewsRoutes.post("/reviews/delete-all", async (c) => {
|
|
|
28721
30123
|
|
|
28722
30124
|
// src/routes/api/share-prompt.ts
|
|
28723
30125
|
init_zod();
|
|
28724
|
-
import { Hono as
|
|
28725
|
-
|
|
30126
|
+
import { Hono as Hono14 } from "hono";
|
|
30127
|
+
init_global_config();
|
|
30128
|
+
var sharePromptRoutes = new Hono14();
|
|
28726
30129
|
var SharePromptShapeSchema = external_exports.object({
|
|
28727
30130
|
dismissedAt: external_exports.number().nullable().optional(),
|
|
28728
30131
|
totalOpenMs: external_exports.number().optional()
|
|
@@ -28761,9 +30164,9 @@ sharePromptRoutes.post("/share-prompt/tick", async (c) => {
|
|
|
28761
30164
|
});
|
|
28762
30165
|
|
|
28763
30166
|
// src/routes/api/system.ts
|
|
28764
|
-
import { Hono as
|
|
30167
|
+
import { Hono as Hono15 } from "hono";
|
|
28765
30168
|
init_openOS();
|
|
28766
|
-
var systemRoutes = new
|
|
30169
|
+
var systemRoutes = new Hono15();
|
|
28767
30170
|
systemRoutes.post("/open-external", async (c) => {
|
|
28768
30171
|
const parsed = await parseBody(c, OpenExternalReqSchema);
|
|
28769
30172
|
if (!parsed.ok) return parsed.response;
|
|
@@ -28775,7 +30178,7 @@ systemRoutes.post("/open-external", async (c) => {
|
|
|
28775
30178
|
});
|
|
28776
30179
|
|
|
28777
30180
|
// src/routes/api.ts
|
|
28778
|
-
var apiRoutes = new
|
|
30181
|
+
var apiRoutes = new Hono16();
|
|
28779
30182
|
apiRoutes.route("/", reviewsRoutes);
|
|
28780
30183
|
apiRoutes.route("/", filesRoutes);
|
|
28781
30184
|
apiRoutes.route("/", annotationsRoutes);
|
|
@@ -28783,22 +30186,24 @@ apiRoutes.route("/", attachmentsRoutes);
|
|
|
28783
30186
|
apiRoutes.route("/", outlineRoutes);
|
|
28784
30187
|
apiRoutes.route("/", contextRoutes);
|
|
28785
30188
|
apiRoutes.route("/", projectSettingsRoutes);
|
|
30189
|
+
apiRoutes.route("/", pluginsRoutes);
|
|
28786
30190
|
apiRoutes.route("/", imageRoutes);
|
|
28787
30191
|
apiRoutes.route("/", reviewNotesRoutes);
|
|
28788
30192
|
apiRoutes.route("/", sharePromptRoutes);
|
|
28789
30193
|
apiRoutes.route("/", systemRoutes);
|
|
28790
30194
|
|
|
28791
30195
|
// src/routes/channel-api.ts
|
|
28792
|
-
import { spawnSync as
|
|
28793
|
-
import { mkdirSync as
|
|
28794
|
-
import { Hono as
|
|
28795
|
-
import { join as
|
|
28796
|
-
|
|
30196
|
+
import { spawnSync as spawnSync12 } from "child_process";
|
|
30197
|
+
import { mkdirSync as mkdirSync12 } from "fs";
|
|
30198
|
+
import { Hono as Hono17 } from "hono";
|
|
30199
|
+
import { join as join18 } from "path";
|
|
30200
|
+
init_global_config();
|
|
30201
|
+
var channelApiRoutes = new Hono17();
|
|
28797
30202
|
channelApiRoutes.get("/status", async (c) => {
|
|
28798
30203
|
const config2 = readGlobalConfig();
|
|
28799
30204
|
const enabled = config2.channelEnabled === true;
|
|
28800
30205
|
const repoRoot = c.get("repoRoot");
|
|
28801
|
-
const dataDir =
|
|
30206
|
+
const dataDir = join18(repoRoot, ".glassbox");
|
|
28802
30207
|
const connected = enabled ? await isChannelAlive(dataDir) : false;
|
|
28803
30208
|
return c.json({ enabled, connected });
|
|
28804
30209
|
});
|
|
@@ -28807,8 +30212,8 @@ channelApiRoutes.post("/enable", (c) => {
|
|
|
28807
30212
|
config2.channelEnabled = true;
|
|
28808
30213
|
});
|
|
28809
30214
|
const repoRoot = c.get("repoRoot");
|
|
28810
|
-
const dataDir =
|
|
28811
|
-
|
|
30215
|
+
const dataDir = join18(repoRoot, ".glassbox");
|
|
30216
|
+
mkdirSync12(dataDir, { recursive: true });
|
|
28812
30217
|
registerChannel(dataDir);
|
|
28813
30218
|
return c.json({ ok: true });
|
|
28814
30219
|
});
|
|
@@ -28817,7 +30222,7 @@ channelApiRoutes.post("/disable", (c) => {
|
|
|
28817
30222
|
config2.channelEnabled = false;
|
|
28818
30223
|
});
|
|
28819
30224
|
const repoRoot = c.get("repoRoot");
|
|
28820
|
-
const dataDir =
|
|
30225
|
+
const dataDir = join18(repoRoot, ".glassbox");
|
|
28821
30226
|
unregisterChannel(dataDir);
|
|
28822
30227
|
return c.json({ ok: true });
|
|
28823
30228
|
});
|
|
@@ -28825,7 +30230,7 @@ channelApiRoutes.post("/trigger", async (c) => {
|
|
|
28825
30230
|
const parsed = await parseBody(c, TriggerChannelReqSchema);
|
|
28826
30231
|
if (!parsed.ok) return parsed.response;
|
|
28827
30232
|
const repoRoot = c.get("repoRoot");
|
|
28828
|
-
const dataDir =
|
|
30233
|
+
const dataDir = join18(repoRoot, ".glassbox");
|
|
28829
30234
|
const sent = await triggerChannel(dataDir, parsed.data.message);
|
|
28830
30235
|
if (!sent) {
|
|
28831
30236
|
return c.json({ error: "Channel not connected" }, 503);
|
|
@@ -28834,7 +30239,7 @@ channelApiRoutes.post("/trigger", async (c) => {
|
|
|
28834
30239
|
});
|
|
28835
30240
|
channelApiRoutes.get("/claude-check", (c) => {
|
|
28836
30241
|
try {
|
|
28837
|
-
const result =
|
|
30242
|
+
const result = spawnSync12("claude", ["--version"], { encoding: "utf-8", timeout: 5e3 });
|
|
28838
30243
|
if (result.status !== 0) {
|
|
28839
30244
|
return c.json({ installed: false, version: null, meetsMinimum: false });
|
|
28840
30245
|
}
|
|
@@ -28853,13 +30258,13 @@ channelApiRoutes.get("/claude-check", (c) => {
|
|
|
28853
30258
|
});
|
|
28854
30259
|
|
|
28855
30260
|
// src/routes/difftool-api.ts
|
|
28856
|
-
import { Hono as
|
|
30261
|
+
import { Hono as Hono18 } from "hono";
|
|
28857
30262
|
init_connection();
|
|
28858
30263
|
init_queries();
|
|
28859
30264
|
init_session();
|
|
28860
30265
|
init_difftool();
|
|
28861
30266
|
init_image_blobs();
|
|
28862
|
-
var difftoolApiRoutes = new
|
|
30267
|
+
var difftoolApiRoutes = new Hono18();
|
|
28863
30268
|
difftoolApiRoutes.get("/status", (c) => {
|
|
28864
30269
|
return c.json(getDifftoolStatus("global"));
|
|
28865
30270
|
});
|
|
@@ -28936,8 +30341,8 @@ difftoolApiRoutes.post("/end", (c) => {
|
|
|
28936
30341
|
});
|
|
28937
30342
|
|
|
28938
30343
|
// src/routes/pages.tsx
|
|
28939
|
-
import { readFileSync as
|
|
28940
|
-
import { Hono as
|
|
30344
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
30345
|
+
import { Hono as Hono19 } from "hono";
|
|
28941
30346
|
import { resolve as resolve11 } from "path";
|
|
28942
30347
|
|
|
28943
30348
|
// src/components/diffView.tsx
|
|
@@ -29728,7 +31133,28 @@ function ReviewNoteRows({ notes, repliesByNote }) {
|
|
|
29728
31133
|
/* @__PURE__ */ jsx4("button", { className: "ai-note-discard-btn", title: "Remove this note from .pr-notes/", children: "Discard" })
|
|
29729
31134
|
] }) : null
|
|
29730
31135
|
] }),
|
|
29731
|
-
n.artifacts !== void 0 && n.artifacts.length > 0 ? /* @__PURE__ */ jsx4("div", { className: "ai-note-artifacts", children: n.artifacts.map((a) => a.
|
|
31136
|
+
n.artifacts !== void 0 && n.artifacts.length > 0 ? /* @__PURE__ */ jsx4("div", { className: "ai-note-artifacts", children: n.artifacts.map((a) => a.renderedSvg !== void 0 ? /* @__PURE__ */ jsxs4("details", { className: "ai-note-artifact", open: true, children: [
|
|
31137
|
+
/* @__PURE__ */ jsxs4("summary", { className: "ai-note-artifact-label", children: [
|
|
31138
|
+
/* @__PURE__ */ jsx4(IconPaperclip, {}),
|
|
31139
|
+
/* @__PURE__ */ jsx4("span", { children: a.uri })
|
|
31140
|
+
] }),
|
|
31141
|
+
/* @__PURE__ */ jsx4("div", { className: "ai-note-artifact-imgwrap", children: /* @__PURE__ */ jsx4(
|
|
31142
|
+
"img",
|
|
31143
|
+
{
|
|
31144
|
+
className: "ai-note-artifact-img",
|
|
31145
|
+
loading: "lazy",
|
|
31146
|
+
alt: a.uri,
|
|
31147
|
+
draggable: false,
|
|
31148
|
+
src: `data:image/svg+xml;utf8,${encodeURIComponent(a.renderedSvg)}`
|
|
31149
|
+
}
|
|
31150
|
+
) })
|
|
31151
|
+
] }) : a.renderedHtml !== void 0 ? /* @__PURE__ */ jsxs4("details", { className: "ai-note-artifact", open: true, children: [
|
|
31152
|
+
/* @__PURE__ */ jsxs4("summary", { className: "ai-note-artifact-label", children: [
|
|
31153
|
+
/* @__PURE__ */ jsx4(IconPaperclip, {}),
|
|
31154
|
+
/* @__PURE__ */ jsx4("span", { children: a.uri })
|
|
31155
|
+
] }),
|
|
31156
|
+
/* @__PURE__ */ jsx4("div", { className: "ai-note-artifact-rendered", children: raw(a.renderedHtml) })
|
|
31157
|
+
] }) : a.content !== void 0 ? /* @__PURE__ */ jsxs4("details", { className: "ai-note-artifact", children: [
|
|
29732
31158
|
/* @__PURE__ */ jsxs4("summary", { className: "ai-note-artifact-label", children: [
|
|
29733
31159
|
/* @__PURE__ */ jsx4(IconPaperclip, {}),
|
|
29734
31160
|
/* @__PURE__ */ jsx4("span", { children: a.uri })
|
|
@@ -29799,9 +31225,10 @@ function AnnotationRows({ annotations }) {
|
|
|
29799
31225
|
}
|
|
29800
31226
|
|
|
29801
31227
|
// src/themes/config.ts
|
|
29802
|
-
import { existsSync as
|
|
29803
|
-
import { join as
|
|
29804
|
-
|
|
31228
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync13, readdirSync as readdirSync4, readFileSync as readFileSync17, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "fs";
|
|
31229
|
+
import { join as join19 } from "path";
|
|
31230
|
+
init_global_config();
|
|
31231
|
+
var THEMES_DIR = join19(GLOBAL_CONFIG_DIR, "themes");
|
|
29805
31232
|
function getActiveThemeId() {
|
|
29806
31233
|
const config2 = readGlobalConfig();
|
|
29807
31234
|
const theme = config2.theme;
|
|
@@ -29815,13 +31242,13 @@ function setActiveThemeId(id) {
|
|
|
29815
31242
|
});
|
|
29816
31243
|
}
|
|
29817
31244
|
function loadCustomThemes() {
|
|
29818
|
-
if (!
|
|
31245
|
+
if (!existsSync15(THEMES_DIR)) return [];
|
|
29819
31246
|
const themes = [];
|
|
29820
31247
|
try {
|
|
29821
|
-
const files =
|
|
31248
|
+
const files = readdirSync4(THEMES_DIR).filter((f) => f.endsWith(".json"));
|
|
29822
31249
|
for (const file2 of files) {
|
|
29823
31250
|
try {
|
|
29824
|
-
const parsed = StoredCustomThemeSchema.safeParse(JSON.parse(
|
|
31251
|
+
const parsed = StoredCustomThemeSchema.safeParse(JSON.parse(readFileSync17(join19(THEMES_DIR, file2), "utf-8")));
|
|
29825
31252
|
if (!parsed.success) continue;
|
|
29826
31253
|
const d = parsed.data;
|
|
29827
31254
|
themes.push({ id: d.id, name: d.name, colors: d.colors, builtIn: false, baseTheme: d.baseTheme ?? "" });
|
|
@@ -29833,21 +31260,21 @@ function loadCustomThemes() {
|
|
|
29833
31260
|
return themes;
|
|
29834
31261
|
}
|
|
29835
31262
|
function saveCustomTheme(theme) {
|
|
29836
|
-
|
|
29837
|
-
const filePath =
|
|
29838
|
-
|
|
31263
|
+
mkdirSync13(THEMES_DIR, { recursive: true });
|
|
31264
|
+
const filePath = join19(THEMES_DIR, `${theme.id}.json`);
|
|
31265
|
+
writeFileSync13(filePath, JSON.stringify(theme, null, 2), "utf-8");
|
|
29839
31266
|
}
|
|
29840
31267
|
function deleteCustomTheme(id) {
|
|
29841
|
-
const filePath =
|
|
29842
|
-
if (
|
|
31268
|
+
const filePath = join19(THEMES_DIR, `${id}.json`);
|
|
31269
|
+
if (existsSync15(filePath)) {
|
|
29843
31270
|
unlinkSync3(filePath);
|
|
29844
31271
|
}
|
|
29845
31272
|
}
|
|
29846
31273
|
function getCustomTheme(id) {
|
|
29847
|
-
const filePath =
|
|
29848
|
-
if (!
|
|
31274
|
+
const filePath = join19(THEMES_DIR, `${id}.json`);
|
|
31275
|
+
if (!existsSync15(filePath)) return void 0;
|
|
29849
31276
|
try {
|
|
29850
|
-
const parsed = StoredCustomThemeSchema.safeParse(JSON.parse(
|
|
31277
|
+
const parsed = StoredCustomThemeSchema.safeParse(JSON.parse(readFileSync17(filePath, "utf-8")));
|
|
29851
31278
|
if (!parsed.success) return void 0;
|
|
29852
31279
|
const d = parsed.data;
|
|
29853
31280
|
return { id: d.id, name: d.name, colors: d.colors, builtIn: false, baseTheme: d.baseTheme ?? "" };
|
|
@@ -29884,6 +31311,7 @@ function Layout({ title, reviewId, difftool, children }) {
|
|
|
29884
31311
|
/* @__PURE__ */ jsx5("meta", { charSet: "utf-8" }),
|
|
29885
31312
|
/* @__PURE__ */ jsx5("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
|
|
29886
31313
|
/* @__PURE__ */ jsx5("title", { children: title }),
|
|
31314
|
+
/* @__PURE__ */ jsx5("link", { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" }),
|
|
29887
31315
|
/* @__PURE__ */ jsx5("link", { rel: "stylesheet", href: "/static/styles.css" })
|
|
29888
31316
|
] }),
|
|
29889
31317
|
/* @__PURE__ */ jsxs5("body", { "data-review-id": reviewId, "data-difftool": difftool === true ? "1" : void 0, children: [
|
|
@@ -30046,7 +31474,10 @@ function ReviewShell({ reviewId, review, files, annotationCounts, staleCounts, f
|
|
|
30046
31474
|
/* @__PURE__ */ jsx8("div", { className: "file-filter", children: /* @__PURE__ */ jsx8("input", { type: "text", className: "file-filter-input", id: "file-filter", placeholder: "Filter files..." }) }),
|
|
30047
31475
|
/* @__PURE__ */ jsx8(FileList, { files, annotationCounts, staleCounts }),
|
|
30048
31476
|
/* @__PURE__ */ jsx8("div", { className: "sidebar-share", id: "sidebar-share" }),
|
|
30049
|
-
/* @__PURE__ */
|
|
31477
|
+
/* @__PURE__ */ jsxs8("div", { className: "sidebar-footer", children: [
|
|
31478
|
+
footer,
|
|
31479
|
+
/* @__PURE__ */ jsx8("div", { className: "plugin-ui-slot", id: "plugin-ui-sidebar-footer" })
|
|
31480
|
+
] })
|
|
30050
31481
|
] }),
|
|
30051
31482
|
/* @__PURE__ */ jsx8("div", { className: "sidebar-resize", id: "sidebar-resize" }),
|
|
30052
31483
|
/* @__PURE__ */ jsxs8("main", { className: "main-content", children: [
|
|
@@ -30065,7 +31496,8 @@ function ReviewShell({ reviewId, review, files, annotationCounts, staleCounts, f
|
|
|
30065
31496
|
] }) }),
|
|
30066
31497
|
/* @__PURE__ */ jsx8("button", { className: "nav-btn disabled", id: "nav-back-btn", disabled: true, title: "Back", children: /* @__PURE__ */ jsx8("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx8("path", { d: "m15 18-6-6 6-6" }) }) }),
|
|
30067
31498
|
/* @__PURE__ */ jsx8("button", { className: "nav-btn disabled", id: "nav-forward-btn", disabled: true, title: "Forward", children: /* @__PURE__ */ jsx8("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx8("path", { d: "m9 18 6-6-6-6" }) }) }),
|
|
30068
|
-
/* @__PURE__ */ jsx8("span", { className: "nav-file-path", id: "nav-file-path" })
|
|
31499
|
+
/* @__PURE__ */ jsx8("span", { className: "nav-file-path", id: "nav-file-path" }),
|
|
31500
|
+
/* @__PURE__ */ jsx8("span", { className: "plugin-ui-slot", id: "plugin-ui-header" })
|
|
30069
31501
|
] }),
|
|
30070
31502
|
/* @__PURE__ */ jsx8("div", { className: "diff-container", id: "diff-container", style: "display:none" }),
|
|
30071
31503
|
/* @__PURE__ */ jsxs8("div", { className: "diff-toolbar", id: "diff-toolbar", style: "display:none", children: [
|
|
@@ -30106,7 +31538,8 @@ function ReviewShell({ reviewId, review, files, annotationCounts, staleCounts, f
|
|
|
30106
31538
|
/* @__PURE__ */ jsx8("button", { className: "image-zoom-btn", "data-zoom-action": "actual", title: "Actual size (1:1)", children: /* @__PURE__ */ jsx8(IconActualSize, {}) }),
|
|
30107
31539
|
/* @__PURE__ */ jsx8("button", { className: "image-zoom-btn", "data-zoom-action": "in", title: "Zoom in", children: /* @__PURE__ */ jsx8(IconZoomIn, {}) })
|
|
30108
31540
|
] })
|
|
30109
|
-
] })
|
|
31541
|
+
] }),
|
|
31542
|
+
/* @__PURE__ */ jsx8("div", { className: "plugin-ui-slot", id: "plugin-ui-diff-toolbar" })
|
|
30110
31543
|
] })
|
|
30111
31544
|
] })
|
|
30112
31545
|
] })
|
|
@@ -30140,6 +31573,25 @@ function svgUsesExternalFonts(svgData) {
|
|
|
30140
31573
|
return /<text[\s>]/i.test(svg) || /font-family/i.test(svg) || /@font-face/i.test(svg);
|
|
30141
31574
|
}
|
|
30142
31575
|
|
|
31576
|
+
// src/plugins/artifacts.ts
|
|
31577
|
+
init_plugins();
|
|
31578
|
+
async function renderNoteArtifacts(views) {
|
|
31579
|
+
for (const view of views) {
|
|
31580
|
+
for (const artifact of view.artifacts ?? []) {
|
|
31581
|
+
if (artifact.isImage === true || artifact.content === void 0) continue;
|
|
31582
|
+
const rendered = await renderContent({
|
|
31583
|
+
bytes: new TextEncoder().encode(artifact.content),
|
|
31584
|
+
text: artifact.content,
|
|
31585
|
+
path: artifact.uri
|
|
31586
|
+
});
|
|
31587
|
+
if (rendered === null) continue;
|
|
31588
|
+
if (rendered.svg !== void 0 && rendered.svg !== "") artifact.renderedSvg = rendered.svg;
|
|
31589
|
+
else if (rendered.html !== void 0 && rendered.html !== "") artifact.renderedHtml = rendered.html;
|
|
31590
|
+
}
|
|
31591
|
+
}
|
|
31592
|
+
return views;
|
|
31593
|
+
}
|
|
31594
|
+
|
|
30143
31595
|
// src/review-notes/reanchor.ts
|
|
30144
31596
|
var MATCH_RADIUS = 50;
|
|
30145
31597
|
function reanchorReviewNotes(notes, diff) {
|
|
@@ -30173,7 +31625,7 @@ function reanchorReviewNotes(notes, diff) {
|
|
|
30173
31625
|
// src/routes/pages.tsx
|
|
30174
31626
|
init_store();
|
|
30175
31627
|
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs9 } from "kerfjs/jsx-runtime";
|
|
30176
|
-
var pageRoutes = new
|
|
31628
|
+
var pageRoutes = new Hono19();
|
|
30177
31629
|
pageRoutes.get("/", async (c) => {
|
|
30178
31630
|
const reviewId = c.get("reviewId");
|
|
30179
31631
|
const review = await getReview(reviewId);
|
|
@@ -30202,12 +31654,14 @@ pageRoutes.get("/file/:fileId", async (c) => {
|
|
|
30202
31654
|
const reviewMode = review ? parseModeString(review.mode) : null;
|
|
30203
31655
|
const imageSideLabels = reviewMode ? groundTruthSideLabels(reviewMode) : void 0;
|
|
30204
31656
|
const stepNav = reviewMode?.type === "ground-truth" ? groundTruthStepNav(reviewMode, file2.id, await getReviewFiles(file2.review_id)) : void 0;
|
|
30205
|
-
|
|
31657
|
+
const isSvg = isSvgFile(file2.file_path);
|
|
31658
|
+
const isPluginSvg = !isSvg && !diff.isBinary && pluginRendersFile(file2.file_path);
|
|
31659
|
+
if (view === "rendered" && (isSvg || isPluginSvg)) {
|
|
30206
31660
|
const repoRoot = c.get("repoRoot");
|
|
30207
31661
|
let fontWarning = false;
|
|
30208
31662
|
let svgBaseWidth = 300;
|
|
30209
31663
|
let svgBaseHeight = 150;
|
|
30210
|
-
if (reviewMode) {
|
|
31664
|
+
if (isSvg && reviewMode) {
|
|
30211
31665
|
const oldImg = diff.status !== "added" ? getOldImage(reviewMode, file2.file_path, diff.oldPath ?? null, repoRoot) : null;
|
|
30212
31666
|
const newImg = diff.status !== "deleted" ? getNewImage(reviewMode, file2.file_path, repoRoot) : null;
|
|
30213
31667
|
const svgData = newImg ?? oldImg;
|
|
@@ -30219,6 +31673,14 @@ pageRoutes.get("/file/:fileId", async (c) => {
|
|
|
30219
31673
|
if (oldImg && svgUsesExternalFonts(oldImg.data) || newImg && svgUsesExternalFonts(newImg.data)) {
|
|
30220
31674
|
fontWarning = true;
|
|
30221
31675
|
}
|
|
31676
|
+
} else if (isPluginSvg) {
|
|
31677
|
+
const svg = await renderPluginSvgSide(reviewMode, file2.file_path, diff.oldPath ?? file2.file_path, diff.status !== "deleted" ? "new" : "old", repoRoot);
|
|
31678
|
+
if (svg !== null) {
|
|
31679
|
+
const dims = parseSvgDimensions(svg);
|
|
31680
|
+
svgBaseWidth = dims.width;
|
|
31681
|
+
svgBaseHeight = dims.height;
|
|
31682
|
+
if (svgUsesExternalFonts(Buffer.from(svg))) fontWarning = true;
|
|
31683
|
+
}
|
|
30222
31684
|
}
|
|
30223
31685
|
const html2 = /* @__PURE__ */ jsxs9("div", { className: "diff-view", "data-file-id": file2.id, "data-file-path": file2.file_path, "data-is-svg": "true", children: [
|
|
30224
31686
|
/* @__PURE__ */ jsxs9("div", { className: "diff-header", children: [
|
|
@@ -30283,6 +31745,7 @@ pageRoutes.get("/file/:fileId", async (c) => {
|
|
|
30283
31745
|
}
|
|
30284
31746
|
const rawNotes = getDemoMode() !== null ? demoReviewNotes(file2.file_path) : loadReviewNotesForFile(c.get("repoRoot"), file2.file_path);
|
|
30285
31747
|
const reviewNotes = reanchorReviewNotes(rawNotes, finalDiff);
|
|
31748
|
+
await renderNoteArtifacts(reviewNotes);
|
|
30286
31749
|
const html = /* @__PURE__ */ jsx9(DiffView, { file: file2, diff: finalDiff, annotations, mode, reviewNotes, imageSideLabels, stepNav });
|
|
30287
31750
|
return c.html(html.toString());
|
|
30288
31751
|
});
|
|
@@ -30292,7 +31755,7 @@ pageRoutes.get("/file-raw", (c) => {
|
|
|
30292
31755
|
const repoRoot = c.get("repoRoot");
|
|
30293
31756
|
let content;
|
|
30294
31757
|
try {
|
|
30295
|
-
content =
|
|
31758
|
+
content = readFileSync18(resolve11(repoRoot, filePath), "utf-8");
|
|
30296
31759
|
} catch {
|
|
30297
31760
|
return c.text("File not found", 404);
|
|
30298
31761
|
}
|
|
@@ -30348,8 +31811,8 @@ pageRoutes.get("/history", async (c) => {
|
|
|
30348
31811
|
});
|
|
30349
31812
|
|
|
30350
31813
|
// src/routes/theme-api.ts
|
|
30351
|
-
import { Hono as
|
|
30352
|
-
var themeApiRoutes = new
|
|
31814
|
+
import { Hono as Hono20 } from "hono";
|
|
31815
|
+
var themeApiRoutes = new Hono20();
|
|
30353
31816
|
themeApiRoutes.get("/", (c) => {
|
|
30354
31817
|
const themes = getAllThemes();
|
|
30355
31818
|
const activeId = getActiveThemeId();
|
|
@@ -30476,7 +31939,7 @@ function tryServe(appFetch, port) {
|
|
|
30476
31939
|
});
|
|
30477
31940
|
}
|
|
30478
31941
|
async function startServer(port, reviewId, repoRoot, options) {
|
|
30479
|
-
const app = new
|
|
31942
|
+
const app = new Hono21();
|
|
30480
31943
|
const onCompleteCommand = options?.onComplete ?? null;
|
|
30481
31944
|
app.use("*", async (c, next) => {
|
|
30482
31945
|
c.set("reviewId", reviewId);
|
|
@@ -30485,20 +31948,24 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
30485
31948
|
c.set("onCompleteCommand", onCompleteCommand);
|
|
30486
31949
|
await next();
|
|
30487
31950
|
});
|
|
30488
|
-
const selfDir =
|
|
30489
|
-
const distDir =
|
|
31951
|
+
const selfDir = dirname8(fileURLToPath3(import.meta.url));
|
|
31952
|
+
const distDir = existsSync16(join20(selfDir, "client", "styles.css")) ? join20(selfDir, "client") : join20(selfDir, "..", "dist", "client");
|
|
30490
31953
|
app.get("/static/styles.css", (c) => {
|
|
30491
|
-
const css =
|
|
31954
|
+
const css = readFileSync19(join20(distDir, "styles.css"), "utf-8");
|
|
30492
31955
|
return c.text(css, 200, { "Content-Type": "text/css", "Cache-Control": "no-cache" });
|
|
30493
31956
|
});
|
|
30494
31957
|
app.get("/static/app.js", (c) => {
|
|
30495
|
-
const js =
|
|
31958
|
+
const js = readFileSync19(join20(distDir, "app.global.js"), "utf-8");
|
|
30496
31959
|
return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
|
|
30497
31960
|
});
|
|
30498
31961
|
app.get("/static/history.js", (c) => {
|
|
30499
|
-
const js =
|
|
31962
|
+
const js = readFileSync19(join20(distDir, "history.global.js"), "utf-8");
|
|
30500
31963
|
return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
|
|
30501
31964
|
});
|
|
31965
|
+
app.get("/favicon.svg", (c) => {
|
|
31966
|
+
const svg = readFileSync19(join20(distDir, "favicon.svg"), "utf-8");
|
|
31967
|
+
return c.body(svg, 200, { "Content-Type": "image/svg+xml", "Cache-Control": "no-cache" });
|
|
31968
|
+
});
|
|
30502
31969
|
app.get("/favicon.ico", (c) => c.body(null, 204));
|
|
30503
31970
|
app.route("/api", apiRoutes);
|
|
30504
31971
|
app.route("/api/ai", aiApiRoutes);
|
|
@@ -30531,10 +31998,16 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
30531
31998
|
console.log(`
|
|
30532
31999
|
Glassbox running at ${url2}
|
|
30533
32000
|
`);
|
|
32001
|
+
await initContentPlugins(repoRoot);
|
|
32002
|
+
try {
|
|
32003
|
+
const review = await getReview(reviewId);
|
|
32004
|
+
if (review !== void 0) await notifyReviewCreated(review);
|
|
32005
|
+
} catch {
|
|
32006
|
+
}
|
|
30534
32007
|
try {
|
|
30535
32008
|
const globalConfig2 = readGlobalConfig();
|
|
30536
32009
|
if (globalConfig2.channelEnabled === true) {
|
|
30537
|
-
const dataDir =
|
|
32010
|
+
const dataDir = join20(repoRoot, ".glassbox");
|
|
30538
32011
|
registerChannel(dataDir);
|
|
30539
32012
|
}
|
|
30540
32013
|
} catch {
|
|
@@ -30549,8 +32022,8 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
30549
32022
|
}
|
|
30550
32023
|
|
|
30551
32024
|
// src/skills.ts
|
|
30552
|
-
import { existsSync as
|
|
30553
|
-
import { join as
|
|
32025
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "fs";
|
|
32026
|
+
import { join as join21 } from "path";
|
|
30554
32027
|
var SKILL_VERSION = 1;
|
|
30555
32028
|
function versionHeader() {
|
|
30556
32029
|
return `<!-- glassbox-skill-version: ${SKILL_VERSION} -->`;
|
|
@@ -30561,14 +32034,14 @@ function parseVersionHeader(content) {
|
|
|
30561
32034
|
return parseInt(match[1], 10);
|
|
30562
32035
|
}
|
|
30563
32036
|
function updateFile(path, content) {
|
|
30564
|
-
if (
|
|
30565
|
-
const existing =
|
|
32037
|
+
if (existsSync17(path)) {
|
|
32038
|
+
const existing = readFileSync20(path, "utf-8");
|
|
30566
32039
|
const version2 = parseVersionHeader(existing);
|
|
30567
32040
|
if (version2 !== null && version2 >= SKILL_VERSION) {
|
|
30568
32041
|
return false;
|
|
30569
32042
|
}
|
|
30570
32043
|
}
|
|
30571
|
-
|
|
32044
|
+
writeFileSync14(path, content, "utf-8");
|
|
30572
32045
|
return true;
|
|
30573
32046
|
}
|
|
30574
32047
|
function skillBody() {
|
|
@@ -30588,8 +32061,8 @@ function skillBody() {
|
|
|
30588
32061
|
].join("\n");
|
|
30589
32062
|
}
|
|
30590
32063
|
function ensureClaudeSkills(cwd) {
|
|
30591
|
-
const dir =
|
|
30592
|
-
|
|
32064
|
+
const dir = join21(cwd, ".claude", "skills", "glassbox");
|
|
32065
|
+
mkdirSync14(dir, { recursive: true });
|
|
30593
32066
|
const content = [
|
|
30594
32067
|
"---",
|
|
30595
32068
|
"name: glassbox",
|
|
@@ -30601,11 +32074,11 @@ function ensureClaudeSkills(cwd) {
|
|
|
30601
32074
|
skillBody(),
|
|
30602
32075
|
""
|
|
30603
32076
|
].join("\n");
|
|
30604
|
-
return updateFile(
|
|
32077
|
+
return updateFile(join21(dir, "SKILL.md"), content);
|
|
30605
32078
|
}
|
|
30606
32079
|
function ensureCursorRules(cwd) {
|
|
30607
|
-
const rulesDir =
|
|
30608
|
-
|
|
32080
|
+
const rulesDir = join21(cwd, ".cursor", "rules");
|
|
32081
|
+
mkdirSync14(rulesDir, { recursive: true });
|
|
30609
32082
|
const content = [
|
|
30610
32083
|
"---",
|
|
30611
32084
|
"description: Read the latest Glassbox code review and apply all feedback annotations",
|
|
@@ -30616,11 +32089,11 @@ function ensureCursorRules(cwd) {
|
|
|
30616
32089
|
skillBody(),
|
|
30617
32090
|
""
|
|
30618
32091
|
].join("\n");
|
|
30619
|
-
return updateFile(
|
|
32092
|
+
return updateFile(join21(rulesDir, "glassbox.mdc"), content);
|
|
30620
32093
|
}
|
|
30621
32094
|
function ensureCopilotPrompts(cwd) {
|
|
30622
|
-
const promptsDir =
|
|
30623
|
-
|
|
32095
|
+
const promptsDir = join21(cwd, ".github", "prompts");
|
|
32096
|
+
mkdirSync14(promptsDir, { recursive: true });
|
|
30624
32097
|
const content = [
|
|
30625
32098
|
"---",
|
|
30626
32099
|
"description: Read the latest Glassbox code review and apply all feedback annotations",
|
|
@@ -30630,11 +32103,11 @@ function ensureCopilotPrompts(cwd) {
|
|
|
30630
32103
|
skillBody(),
|
|
30631
32104
|
""
|
|
30632
32105
|
].join("\n");
|
|
30633
|
-
return updateFile(
|
|
32106
|
+
return updateFile(join21(promptsDir, "glassbox.prompt.md"), content);
|
|
30634
32107
|
}
|
|
30635
32108
|
function ensureWindsurfRules(cwd) {
|
|
30636
|
-
const rulesDir =
|
|
30637
|
-
|
|
32109
|
+
const rulesDir = join21(cwd, ".windsurf", "rules");
|
|
32110
|
+
mkdirSync14(rulesDir, { recursive: true });
|
|
30638
32111
|
const content = [
|
|
30639
32112
|
"---",
|
|
30640
32113
|
"trigger: manual",
|
|
@@ -30645,21 +32118,21 @@ function ensureWindsurfRules(cwd) {
|
|
|
30645
32118
|
skillBody(),
|
|
30646
32119
|
""
|
|
30647
32120
|
].join("\n");
|
|
30648
|
-
return updateFile(
|
|
32121
|
+
return updateFile(join21(rulesDir, "glassbox.md"), content);
|
|
30649
32122
|
}
|
|
30650
32123
|
function ensureSkills() {
|
|
30651
32124
|
const cwd = process.cwd();
|
|
30652
32125
|
const platforms = [];
|
|
30653
|
-
if (
|
|
32126
|
+
if (existsSync17(join21(cwd, ".claude"))) {
|
|
30654
32127
|
if (ensureClaudeSkills(cwd)) platforms.push("Claude Code");
|
|
30655
32128
|
}
|
|
30656
|
-
if (
|
|
32129
|
+
if (existsSync17(join21(cwd, ".cursor"))) {
|
|
30657
32130
|
if (ensureCursorRules(cwd)) platforms.push("Cursor");
|
|
30658
32131
|
}
|
|
30659
|
-
if (
|
|
32132
|
+
if (existsSync17(join21(cwd, ".github", "prompts")) || existsSync17(join21(cwd, ".github", "copilot-instructions.md"))) {
|
|
30660
32133
|
if (ensureCopilotPrompts(cwd)) platforms.push("GitHub Copilot");
|
|
30661
32134
|
}
|
|
30662
|
-
if (
|
|
32135
|
+
if (existsSync17(join21(cwd, ".windsurf"))) {
|
|
30663
32136
|
if (ensureWindsurfRules(cwd)) platforms.push("Windsurf");
|
|
30664
32137
|
}
|
|
30665
32138
|
return platforms;
|
|
@@ -30667,19 +32140,19 @@ function ensureSkills() {
|
|
|
30667
32140
|
|
|
30668
32141
|
// src/update-check.ts
|
|
30669
32142
|
init_zod();
|
|
30670
|
-
import { existsSync as
|
|
32143
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync15, readFileSync as readFileSync21, writeFileSync as writeFileSync15 } from "fs";
|
|
30671
32144
|
import { get } from "https";
|
|
30672
32145
|
import { homedir as homedir2 } from "os";
|
|
30673
|
-
import { dirname as
|
|
30674
|
-
import { fileURLToPath as
|
|
30675
|
-
var DATA_DIR =
|
|
30676
|
-
var CHECK_FILE =
|
|
32146
|
+
import { dirname as dirname9, join as join22 } from "path";
|
|
32147
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
32148
|
+
var DATA_DIR = join22(homedir2(), ".glassbox");
|
|
32149
|
+
var CHECK_FILE = join22(DATA_DIR, "last-update-check");
|
|
30677
32150
|
var PACKAGE_NAME = "glassbox";
|
|
30678
32151
|
var VersionPayloadSchema = external_exports.object({ version: external_exports.string() });
|
|
30679
32152
|
function getCurrentVersion() {
|
|
30680
32153
|
try {
|
|
30681
|
-
const dir =
|
|
30682
|
-
const raw2 = JSON.parse(
|
|
32154
|
+
const dir = dirname9(fileURLToPath4(import.meta.url));
|
|
32155
|
+
const raw2 = JSON.parse(readFileSync21(join22(dir, "..", "package.json"), "utf-8"));
|
|
30683
32156
|
return VersionPayloadSchema.parse(raw2).version;
|
|
30684
32157
|
} catch {
|
|
30685
32158
|
return "0.0.0";
|
|
@@ -30687,16 +32160,16 @@ function getCurrentVersion() {
|
|
|
30687
32160
|
}
|
|
30688
32161
|
function getLastCheckDate() {
|
|
30689
32162
|
try {
|
|
30690
|
-
if (
|
|
30691
|
-
return
|
|
32163
|
+
if (existsSync18(CHECK_FILE)) {
|
|
32164
|
+
return readFileSync21(CHECK_FILE, "utf-8").trim();
|
|
30692
32165
|
}
|
|
30693
32166
|
} catch {
|
|
30694
32167
|
}
|
|
30695
32168
|
return null;
|
|
30696
32169
|
}
|
|
30697
32170
|
function saveCheckDate() {
|
|
30698
|
-
|
|
30699
|
-
|
|
32171
|
+
mkdirSync15(DATA_DIR, { recursive: true });
|
|
32172
|
+
writeFileSync15(CHECK_FILE, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), "utf-8");
|
|
30700
32173
|
}
|
|
30701
32174
|
function isFirstUseToday() {
|
|
30702
32175
|
const last = getLastCheckDate();
|
|
@@ -30746,7 +32219,7 @@ function detectUpgradeCommand() {
|
|
|
30746
32219
|
}
|
|
30747
32220
|
return `npm update -g ${PACKAGE_NAME}`;
|
|
30748
32221
|
}
|
|
30749
|
-
function
|
|
32222
|
+
function compareVersions2(current, latest) {
|
|
30750
32223
|
const a = current.split(".").map(Number);
|
|
30751
32224
|
const b = latest.split(".").map(Number);
|
|
30752
32225
|
for (let i = 0; i < 3; i++) {
|
|
@@ -30760,7 +32233,7 @@ async function checkForUpdates(force) {
|
|
|
30760
32233
|
const current = getCurrentVersion();
|
|
30761
32234
|
const latest = await fetchLatestVersion();
|
|
30762
32235
|
saveCheckDate();
|
|
30763
|
-
if (latest === null ||
|
|
32236
|
+
if (latest === null || compareVersions2(current, latest) >= 0) return;
|
|
30764
32237
|
const cmd = detectUpgradeCommand();
|
|
30765
32238
|
const updateLine = `Update available: ${current} \u2192 ${latest}`;
|
|
30766
32239
|
const cmdLine = `Run: ${cmd}`;
|
|
@@ -31001,23 +32474,23 @@ async function main() {
|
|
|
31001
32474
|
console.log("AI service test mode enabled \u2014 using mock AI responses");
|
|
31002
32475
|
}
|
|
31003
32476
|
if (debug) {
|
|
31004
|
-
console.log(`[debug] Build timestamp: ${"2026-07-
|
|
32477
|
+
console.log(`[debug] Build timestamp: ${"2026-07-22T04:50:30.153Z"}`);
|
|
31005
32478
|
}
|
|
31006
32479
|
if (projectDir !== null) {
|
|
31007
|
-
if (!
|
|
32480
|
+
if (!existsSync20(projectDir) || !statSync8(projectDir).isDirectory()) {
|
|
31008
32481
|
console.error(`--project-dir is not a directory: ${projectDir}`);
|
|
31009
32482
|
process.exit(1);
|
|
31010
32483
|
}
|
|
31011
32484
|
process.chdir(projectDir);
|
|
31012
32485
|
}
|
|
31013
32486
|
if (dataDir === null) {
|
|
31014
|
-
dataDir =
|
|
32487
|
+
dataDir = join24(process.cwd(), ".glassbox");
|
|
31015
32488
|
}
|
|
31016
32489
|
if (difftoolServe) {
|
|
31017
32490
|
const { initDifftoolSession: initDifftoolSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
31018
32491
|
const { writeDiscovery: writeDiscovery2, clearDiscovery: clearDiscovery2, releaseStartingLock: releaseStartingLock2 } = await Promise.resolve().then(() => (init_difftool_discovery(), difftool_discovery_exports));
|
|
31019
32492
|
const { clearImageBlobs: clearImageBlobs2 } = await Promise.resolve().then(() => (init_image_blobs(), image_blobs_exports));
|
|
31020
|
-
|
|
32493
|
+
mkdirSync17(dataDir, { recursive: true });
|
|
31021
32494
|
setDataDir(dataDir);
|
|
31022
32495
|
ensureGlassboxGitignored(dataDir);
|
|
31023
32496
|
const sessionDataDir = dataDir;
|
|
@@ -31053,13 +32526,13 @@ async function main() {
|
|
|
31053
32526
|
}
|
|
31054
32527
|
process.exit(1);
|
|
31055
32528
|
}
|
|
31056
|
-
dataDir =
|
|
32529
|
+
dataDir = join24(tmpdir2(), `glassbox-demo-${demo}-${Date.now()}`);
|
|
31057
32530
|
setDemoMode(demo);
|
|
31058
32531
|
console.log(`
|
|
31059
32532
|
DEMO MODE: ${scenario.label}
|
|
31060
32533
|
`);
|
|
31061
32534
|
}
|
|
31062
|
-
|
|
32535
|
+
mkdirSync17(dataDir, { recursive: true });
|
|
31063
32536
|
if (demo === null) {
|
|
31064
32537
|
acquireLock(dataDir);
|
|
31065
32538
|
}
|
|
@@ -31084,12 +32557,12 @@ async function main() {
|
|
|
31084
32557
|
if (mode.type === "diff") {
|
|
31085
32558
|
const { pathA, pathB } = mode;
|
|
31086
32559
|
for (const p of [pathA, pathB]) {
|
|
31087
|
-
if (!
|
|
32560
|
+
if (!existsSync20(p)) {
|
|
31088
32561
|
console.error(`Error: path does not exist: ${p}`);
|
|
31089
32562
|
process.exit(1);
|
|
31090
32563
|
}
|
|
31091
32564
|
}
|
|
31092
|
-
if (
|
|
32565
|
+
if (statSync8(pathA).isDirectory() !== statSync8(pathB).isDirectory()) {
|
|
31093
32566
|
console.error("Error: --diff requires two files or two folders, not a mix of both.");
|
|
31094
32567
|
process.exit(1);
|
|
31095
32568
|
}
|
|
@@ -31106,17 +32579,19 @@ async function main() {
|
|
|
31106
32579
|
}
|
|
31107
32580
|
for (const entry of comparisons) {
|
|
31108
32581
|
for (const [role, p] of [["actual", entry.actualPath], ["expected", entry.expectedPath]]) {
|
|
31109
|
-
if (!
|
|
32582
|
+
if (!existsSync20(p)) {
|
|
31110
32583
|
console.error(`Error: ${role} image does not exist: ${p}`);
|
|
31111
32584
|
process.exit(1);
|
|
31112
32585
|
}
|
|
31113
32586
|
}
|
|
31114
32587
|
}
|
|
32588
|
+
const { initContentPlugins: initContentPlugins2 } = await Promise.resolve().then(() => (init_plugins(), plugins_exports));
|
|
32589
|
+
await initContentPlugins2(cwd);
|
|
31115
32590
|
const { comparePerceptual: comparePerceptual2 } = await Promise.resolve().then(() => (init_perceptual_diff(), perceptual_diff_exports));
|
|
31116
32591
|
let identical = 0;
|
|
31117
32592
|
let undecodable = 0;
|
|
31118
32593
|
for (const entry of comparisons) {
|
|
31119
|
-
const result = comparePerceptual2(entry.actualPath, entry.expectedPath);
|
|
32594
|
+
const result = await comparePerceptual2(entry.actualPath, entry.expectedPath);
|
|
31120
32595
|
groundTruthScores.set(entry.key, result.score);
|
|
31121
32596
|
if (result.reason === "undecodable") undecodable++;
|
|
31122
32597
|
else if (result.score === 0) identical++;
|