glassbox 0.13.3 → 0.14.0
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 +223 -57
- package/dist/client/app.global.js +17 -17
- package/dist/client/styles.css +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15851,7 +15851,7 @@ __export(difftool_exports, {
|
|
|
15851
15851
|
unregisterDifftool: () => unregisterDifftool
|
|
15852
15852
|
});
|
|
15853
15853
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
15854
|
-
function
|
|
15854
|
+
function git2(args, cwd) {
|
|
15855
15855
|
const r = spawnSync9("git", args, { encoding: "utf-8", cwd });
|
|
15856
15856
|
if (r.error !== void 0) {
|
|
15857
15857
|
const code = r.error.code ?? "UNKNOWN";
|
|
@@ -15860,7 +15860,7 @@ function git3(args, cwd) {
|
|
|
15860
15860
|
return { status: r.status ?? -1, stdout: (r.stdout || "").trim(), stderr: (r.stderr || "").trim() };
|
|
15861
15861
|
}
|
|
15862
15862
|
function readConfig(key, scope, cwd) {
|
|
15863
|
-
const r =
|
|
15863
|
+
const r = git2(["config", `--${scope}`, "--get", key], cwd);
|
|
15864
15864
|
return r.status === 0 ? r.stdout : null;
|
|
15865
15865
|
}
|
|
15866
15866
|
function getDifftoolStatus(scope = "global", cwd) {
|
|
@@ -15884,7 +15884,7 @@ function registerDifftool(opts = {}) {
|
|
|
15884
15884
|
["difftool.prompt", "false"]
|
|
15885
15885
|
];
|
|
15886
15886
|
for (const [key, value] of sets) {
|
|
15887
|
-
const r =
|
|
15887
|
+
const r = git2(["config", `--${scope}`, key, value], opts.cwd);
|
|
15888
15888
|
if (r.status !== 0) {
|
|
15889
15889
|
const detail = r.stderr || `(no stderr; exit ${String(r.status)})`;
|
|
15890
15890
|
return { ok: false, reason: "git-failed", message: `\`git config --${scope} ${key}\` failed: ${detail}` };
|
|
@@ -15899,7 +15899,7 @@ function unregisterDifftool(opts = {}) {
|
|
|
15899
15899
|
return { ok: true, removed: false };
|
|
15900
15900
|
}
|
|
15901
15901
|
const tryUnset = (key) => {
|
|
15902
|
-
const r =
|
|
15902
|
+
const r = git2(["config", `--${scope}`, "--unset", key], opts.cwd);
|
|
15903
15903
|
if (r.status !== 0 && r.status !== 5) {
|
|
15904
15904
|
console.error(`git config --unset ${key} failed: ${r.stderr}`);
|
|
15905
15905
|
}
|
|
@@ -16195,8 +16195,9 @@ var PLATFORMS = {
|
|
|
16195
16195
|
};
|
|
16196
16196
|
var MODELS = {
|
|
16197
16197
|
anthropic: [
|
|
16198
|
-
{ id: "claude-
|
|
16199
|
-
{ id: "claude-
|
|
16198
|
+
{ id: "claude-opus-4-8", name: "Claude Opus 4.8", contextWindow: 1e6, isDefault: false },
|
|
16199
|
+
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", contextWindow: 1e6, isDefault: true },
|
|
16200
|
+
{ id: "claude-haiku-4-5", name: "Claude Haiku 4.5", contextWindow: 2e5, isDefault: false }
|
|
16200
16201
|
],
|
|
16201
16202
|
openai: [
|
|
16202
16203
|
{ id: "gpt-4o", name: "GPT-4o", contextWindow: 128e3, isDefault: true },
|
|
@@ -16221,6 +16222,28 @@ function getModelContextWindow(platform, modelId) {
|
|
|
16221
16222
|
const model = MODELS[platform].find((m) => m.id === modelId);
|
|
16222
16223
|
return model ? model.contextWindow : 128e3;
|
|
16223
16224
|
}
|
|
16225
|
+
function modelFamily(id) {
|
|
16226
|
+
const lower = id.toLowerCase();
|
|
16227
|
+
for (const tier of ["opus", "sonnet", "haiku"]) {
|
|
16228
|
+
if (lower.includes(tier)) return `anthropic:${tier}`;
|
|
16229
|
+
}
|
|
16230
|
+
if (lower.includes("gemini")) {
|
|
16231
|
+
if (lower.includes("flash")) return "google:flash";
|
|
16232
|
+
if (lower.includes("pro")) return "google:pro";
|
|
16233
|
+
return "google:gemini";
|
|
16234
|
+
}
|
|
16235
|
+
if (lower.includes("gpt") || lower.startsWith("o1") || lower.startsWith("o3")) {
|
|
16236
|
+
return lower.includes("mini") ? "openai:mini" : "openai:gpt";
|
|
16237
|
+
}
|
|
16238
|
+
return lower;
|
|
16239
|
+
}
|
|
16240
|
+
function resolveModelId(platform, modelId) {
|
|
16241
|
+
const models = MODELS[platform];
|
|
16242
|
+
if (models.some((m) => m.id === modelId)) return modelId;
|
|
16243
|
+
const family = modelFamily(modelId);
|
|
16244
|
+
const familyMatch = models.find((m) => modelFamily(m.id) === family);
|
|
16245
|
+
return familyMatch ? familyMatch.id : getDefaultModel(platform);
|
|
16246
|
+
}
|
|
16224
16247
|
|
|
16225
16248
|
// src/ai/api-keys.ts
|
|
16226
16249
|
function getKeyFromEnv(platform) {
|
|
@@ -16317,7 +16340,7 @@ function loadAIConfig() {
|
|
|
16317
16340
|
const config2 = readConfigFile();
|
|
16318
16341
|
const platformRaw = config2.ai?.platform ?? "anthropic";
|
|
16319
16342
|
const platform = AIPlatformSchema.safeParse(platformRaw).success ? AIPlatformSchema.parse(platformRaw) : "anthropic";
|
|
16320
|
-
const model = config2.ai?.model ?? getDefaultModel(platform);
|
|
16343
|
+
const model = resolveModelId(platform, config2.ai?.model ?? getDefaultModel(platform));
|
|
16321
16344
|
const { key, source } = resolveAPIKey(platform);
|
|
16322
16345
|
return { platform, model, apiKey: key, keySource: source };
|
|
16323
16346
|
}
|
|
@@ -17017,12 +17040,14 @@ async function setupAnnotations(fileIdMap) {
|
|
|
17017
17040
|
}
|
|
17018
17041
|
|
|
17019
17042
|
// src/git/diff.ts
|
|
17020
|
-
import { spawnSync as spawnSync4 } from "child_process";
|
|
17021
17043
|
import { existsSync as existsSync2, mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
17022
17044
|
import { tmpdir } from "os";
|
|
17023
17045
|
import { basename, dirname, join as join3, resolve } from "path";
|
|
17024
17046
|
|
|
17025
17047
|
// src/git/repo.ts
|
|
17048
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
17049
|
+
|
|
17050
|
+
// src/git/spawn.ts
|
|
17026
17051
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
17027
17052
|
function scrubbedGitEnv() {
|
|
17028
17053
|
const env = { ...process.env };
|
|
@@ -17041,6 +17066,8 @@ function git(args, cwd) {
|
|
|
17041
17066
|
err.status = result.status;
|
|
17042
17067
|
throw err;
|
|
17043
17068
|
}
|
|
17069
|
+
|
|
17070
|
+
// src/git/repo.ts
|
|
17044
17071
|
function getRepoRoot(cwd) {
|
|
17045
17072
|
return git(["rev-parse", "--show-toplevel"], cwd).trim();
|
|
17046
17073
|
}
|
|
@@ -17057,7 +17084,7 @@ function isGitRepo(cwd) {
|
|
|
17057
17084
|
}
|
|
17058
17085
|
}
|
|
17059
17086
|
function getHeadCommit(cwd) {
|
|
17060
|
-
return
|
|
17087
|
+
return spawnSync4("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", env: scrubbedGitEnv() }).stdout.trim();
|
|
17061
17088
|
}
|
|
17062
17089
|
|
|
17063
17090
|
// src/git/types.ts
|
|
@@ -17107,15 +17134,13 @@ function parseDiffData(raw) {
|
|
|
17107
17134
|
|
|
17108
17135
|
// src/git/diff.ts
|
|
17109
17136
|
var toGitArg = (p) => process.platform === "win32" ? p.replace(/\\/g, "/") : p;
|
|
17110
|
-
function
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
|
|
17117
|
-
err.status = result.status;
|
|
17118
|
-
throw err;
|
|
17137
|
+
function gitOrEmpty(args, cwd) {
|
|
17138
|
+
try {
|
|
17139
|
+
return git(args, cwd);
|
|
17140
|
+
} catch (err) {
|
|
17141
|
+
debugLog(`git ${args.join(" ")} failed (treating as empty diff): ${err instanceof Error ? err.message : String(err)}`);
|
|
17142
|
+
return "";
|
|
17143
|
+
}
|
|
17119
17144
|
}
|
|
17120
17145
|
function getDiffArgs(mode) {
|
|
17121
17146
|
switch (mode.type) {
|
|
@@ -17166,12 +17191,7 @@ function normalizeDiffPaths(diffs, rootA, rootB) {
|
|
|
17166
17191
|
}
|
|
17167
17192
|
function getDirectComparisonFiles(mode, cwd) {
|
|
17168
17193
|
const { rootA, rootB } = directComparisonRoots(mode);
|
|
17169
|
-
|
|
17170
|
-
try {
|
|
17171
|
-
rawDiff = git2(["diff", "--no-index", "-U3", toGitArg(mode.pathA), toGitArg(mode.pathB)], cwd);
|
|
17172
|
-
} catch {
|
|
17173
|
-
rawDiff = "";
|
|
17174
|
-
}
|
|
17194
|
+
const rawDiff = gitOrEmpty(["diff", "--no-index", "-U3", toGitArg(mode.pathA), toGitArg(mode.pathB)], cwd);
|
|
17175
17195
|
return normalizeDiffPaths(parseDiff(rawDiff), rootA, rootB);
|
|
17176
17196
|
}
|
|
17177
17197
|
function diffRawContent(displayPath, oldContent, newContent) {
|
|
@@ -17187,12 +17207,7 @@ function diffRawContent(displayPath, oldContent, newContent) {
|
|
|
17187
17207
|
mkdirSync3(dirname(newAbs), { recursive: true });
|
|
17188
17208
|
writeFileSync2(oldAbs, oldContent);
|
|
17189
17209
|
writeFileSync2(newAbs, newContent);
|
|
17190
|
-
|
|
17191
|
-
try {
|
|
17192
|
-
rawDiff = git2(["diff", "--no-index", "-U3", toGitArg(oldAbs), toGitArg(newAbs)], work);
|
|
17193
|
-
} catch {
|
|
17194
|
-
rawDiff = "";
|
|
17195
|
-
}
|
|
17210
|
+
const rawDiff = gitOrEmpty(["diff", "--no-index", "-U3", toGitArg(oldAbs), toGitArg(newAbs)], work);
|
|
17196
17211
|
const diffs = normalizeDiffPaths(parseDiff(rawDiff), rootA, rootB);
|
|
17197
17212
|
if (diffs.length === 0) {
|
|
17198
17213
|
return { filePath: safeRel, oldPath: null, status: "modified", hunks: [], isBinary: false };
|
|
@@ -17218,15 +17233,10 @@ function getFileDiffs(mode, cwd) {
|
|
|
17218
17233
|
return getAllFiles(repoRoot);
|
|
17219
17234
|
}
|
|
17220
17235
|
const diffArgs = getDiffArgs(mode);
|
|
17221
|
-
|
|
17222
|
-
try {
|
|
17223
|
-
rawDiff = git2([...diffArgs, "-U3"], repoRoot);
|
|
17224
|
-
} catch {
|
|
17225
|
-
rawDiff = "";
|
|
17226
|
-
}
|
|
17236
|
+
const rawDiff = gitOrEmpty([...diffArgs, "-U3"], repoRoot);
|
|
17227
17237
|
const diffs = parseDiff(rawDiff);
|
|
17228
17238
|
if (mode.type === "uncommitted") {
|
|
17229
|
-
const untracked =
|
|
17239
|
+
const untracked = git(["ls-files", "--others", "--exclude-standard"], repoRoot).trim();
|
|
17230
17240
|
if (untracked) {
|
|
17231
17241
|
for (const file2 of untracked.split("\n").filter(Boolean)) {
|
|
17232
17242
|
if (!diffs.some((d) => d.filePath === file2)) {
|
|
@@ -17238,7 +17248,7 @@ function getFileDiffs(mode, cwd) {
|
|
|
17238
17248
|
return diffs;
|
|
17239
17249
|
}
|
|
17240
17250
|
function getAllFiles(repoRoot) {
|
|
17241
|
-
const files =
|
|
17251
|
+
const files = git(["ls-files"], repoRoot).trim().split("\n").filter(Boolean);
|
|
17242
17252
|
return files.map((file2) => createNewFileDiff(file2, repoRoot));
|
|
17243
17253
|
}
|
|
17244
17254
|
function createNewFileDiff(filePath, repoRoot) {
|
|
@@ -17366,7 +17376,7 @@ function getFileContent(filePath, ref, cwd) {
|
|
|
17366
17376
|
if (ref === "working") {
|
|
17367
17377
|
return readFileSync2(resolve(repoRoot, filePath), "utf-8");
|
|
17368
17378
|
}
|
|
17369
|
-
return
|
|
17379
|
+
return git(["show", `${ref}:${filePath}`], repoRoot);
|
|
17370
17380
|
} catch {
|
|
17371
17381
|
return "";
|
|
17372
17382
|
}
|
|
@@ -17418,12 +17428,7 @@ function getSingleFileDiff(mode, filePath, repoRoot, extraFlags = "") {
|
|
|
17418
17428
|
const args2 = ["diff", "--no-index", "-U3"];
|
|
17419
17429
|
if (extraFlags) args2.push(...extraFlags.split(" ").filter(Boolean));
|
|
17420
17430
|
args2.push(toGitArg(oldAbs), toGitArg(newAbs));
|
|
17421
|
-
|
|
17422
|
-
try {
|
|
17423
|
-
rawDiff2 = git2(args2, repoRoot);
|
|
17424
|
-
} catch {
|
|
17425
|
-
rawDiff2 = "";
|
|
17426
|
-
}
|
|
17431
|
+
const rawDiff2 = gitOrEmpty(args2, repoRoot);
|
|
17427
17432
|
const diffs2 = normalizeDiffPaths(parseDiff(rawDiff2), rootA, rootB);
|
|
17428
17433
|
return diffs2[0] ?? null;
|
|
17429
17434
|
}
|
|
@@ -17431,12 +17436,7 @@ function getSingleFileDiff(mode, filePath, repoRoot, extraFlags = "") {
|
|
|
17431
17436
|
const args = [...diffArgs, "-U3"];
|
|
17432
17437
|
if (extraFlags) args.push(...extraFlags.split(" ").filter(Boolean));
|
|
17433
17438
|
args.push("--", filePath);
|
|
17434
|
-
|
|
17435
|
-
try {
|
|
17436
|
-
rawDiff = git2(args, repoRoot);
|
|
17437
|
-
} catch {
|
|
17438
|
-
rawDiff = "";
|
|
17439
|
-
}
|
|
17439
|
+
const rawDiff = gitOrEmpty(args, repoRoot);
|
|
17440
17440
|
const diffs = parseDiff(rawDiff);
|
|
17441
17441
|
return diffs[0] ?? null;
|
|
17442
17442
|
}
|
|
@@ -19032,16 +19032,22 @@ async function getContextLines(req) {
|
|
|
19032
19032
|
var files_exports = {};
|
|
19033
19033
|
__export(files_exports, {
|
|
19034
19034
|
FileStatusSchema: () => FileStatusSchema,
|
|
19035
|
+
GetFilePathReqSchema: () => GetFilePathReqSchema,
|
|
19036
|
+
GetFilePathRespSchema: () => GetFilePathRespSchema,
|
|
19035
19037
|
GetFileReqSchema: () => GetFileReqSchema,
|
|
19036
19038
|
GetFileRespSchema: () => GetFileRespSchema,
|
|
19037
19039
|
ListFilesRespSchema: () => ListFilesRespSchema,
|
|
19040
|
+
OpenFileReqSchema: () => OpenFileReqSchema,
|
|
19041
|
+
OpenFileRespSchema: () => OpenFileRespSchema,
|
|
19038
19042
|
RevealFileReqSchema: () => RevealFileReqSchema,
|
|
19039
19043
|
RevealFileRespSchema: () => RevealFileRespSchema,
|
|
19040
19044
|
SetFileStatusBodySchema: () => SetFileStatusBodySchema,
|
|
19041
19045
|
SetFileStatusReqSchema: () => SetFileStatusReqSchema,
|
|
19042
19046
|
SetFileStatusRespSchema: () => SetFileStatusRespSchema,
|
|
19043
19047
|
getFile: () => getFile,
|
|
19048
|
+
getFilePath: () => getFilePath,
|
|
19044
19049
|
listFiles: () => listFiles,
|
|
19050
|
+
openFileInEditor: () => openFileInEditor,
|
|
19045
19051
|
revealFile: () => revealFile,
|
|
19046
19052
|
setFileStatus: () => setFileStatus
|
|
19047
19053
|
});
|
|
@@ -19066,6 +19072,13 @@ var SetFileStatusBodySchema = SetFileStatusReqSchema.omit({ fileId: true });
|
|
|
19066
19072
|
var SetFileStatusRespSchema = OkResponseSchema;
|
|
19067
19073
|
var RevealFileReqSchema = external_exports.object({ fileId: external_exports.string() });
|
|
19068
19074
|
var RevealFileRespSchema = OkResponseSchema;
|
|
19075
|
+
var GetFilePathReqSchema = external_exports.object({ fileId: external_exports.string() });
|
|
19076
|
+
var GetFilePathRespSchema = external_exports.object({
|
|
19077
|
+
relativePath: external_exports.string(),
|
|
19078
|
+
absolutePath: external_exports.string()
|
|
19079
|
+
});
|
|
19080
|
+
var OpenFileReqSchema = external_exports.object({ fileId: external_exports.string() });
|
|
19081
|
+
var OpenFileRespSchema = OkResponseSchema;
|
|
19069
19082
|
async function listFiles() {
|
|
19070
19083
|
return apiCall(ListFilesRespSchema, "/files");
|
|
19071
19084
|
}
|
|
@@ -19081,6 +19094,12 @@ async function setFileStatus(req) {
|
|
|
19081
19094
|
async function revealFile(req) {
|
|
19082
19095
|
return apiCall(RevealFileRespSchema, `/files/${req.fileId}/reveal`, { method: "POST" });
|
|
19083
19096
|
}
|
|
19097
|
+
async function getFilePath(req) {
|
|
19098
|
+
return apiCall(GetFilePathRespSchema, `/files/${req.fileId}/path`);
|
|
19099
|
+
}
|
|
19100
|
+
async function openFileInEditor(req) {
|
|
19101
|
+
return apiCall(OpenFileRespSchema, `/files/${req.fileId}/open`, { method: "POST" });
|
|
19102
|
+
}
|
|
19084
19103
|
|
|
19085
19104
|
// src/api/image.ts
|
|
19086
19105
|
var image_exports = {};
|
|
@@ -19998,6 +20017,7 @@ function resolveReviewId(c) {
|
|
|
19998
20017
|
|
|
19999
20018
|
// src/routes/ai-analysis.ts
|
|
20000
20019
|
var aiAnalysisRoutes = new Hono();
|
|
20020
|
+
var ANALYSIS_REUSE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
20001
20021
|
function parseAnalysisTimestamp(updatedAt) {
|
|
20002
20022
|
if (updatedAt.endsWith("Z") || /[+-]\d{2}:?\d{2}$/.test(updatedAt)) {
|
|
20003
20023
|
return new Date(updatedAt);
|
|
@@ -20058,7 +20078,7 @@ aiAnalysisRoutes.post("/analyze", async (c) => {
|
|
|
20058
20078
|
if (existing !== void 0 && existing.status === "running") {
|
|
20059
20079
|
const ageMs = Date.now() - parseAnalysisTimestamp(existing.updated_at).getTime();
|
|
20060
20080
|
debugLog(`POST /analyze: existing analysis age=${String(Math.round(ageMs / 1e3))}s`);
|
|
20061
|
-
if (ageMs <
|
|
20081
|
+
if (ageMs < ANALYSIS_REUSE_TIMEOUT_MS) {
|
|
20062
20082
|
debugLog("POST /analyze: reusing existing running analysis");
|
|
20063
20083
|
return c.json({ analysisId: existing.id, status: "running" });
|
|
20064
20084
|
}
|
|
@@ -20078,6 +20098,8 @@ aiAnalysisRoutes.post("/analyze", async (c) => {
|
|
|
20078
20098
|
repoRoot,
|
|
20079
20099
|
guidedReview,
|
|
20080
20100
|
invalidateCache
|
|
20101
|
+
}).catch((err) => {
|
|
20102
|
+
debugLog(`executeAnalysis dispatch rejected for ${analysis.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20081
20103
|
});
|
|
20082
20104
|
return c.json({ analysisId: analysis.id, status: "running" });
|
|
20083
20105
|
});
|
|
@@ -20395,6 +20417,99 @@ aiAnalysisRoutes.post("/preferences", async (c) => {
|
|
|
20395
20417
|
|
|
20396
20418
|
// src/routes/ai-config.ts
|
|
20397
20419
|
import { Hono as Hono2 } from "hono";
|
|
20420
|
+
|
|
20421
|
+
// src/ai/list-models.ts
|
|
20422
|
+
init_zod();
|
|
20423
|
+
var FETCH_TIMEOUT_MS = 8e3;
|
|
20424
|
+
async function getJson(url2, headers) {
|
|
20425
|
+
const controller = new AbortController();
|
|
20426
|
+
const timer = setTimeout(() => {
|
|
20427
|
+
controller.abort();
|
|
20428
|
+
}, FETCH_TIMEOUT_MS);
|
|
20429
|
+
try {
|
|
20430
|
+
const res = await fetch(url2, { headers, signal: controller.signal });
|
|
20431
|
+
if (!res.ok) return null;
|
|
20432
|
+
return await res.json();
|
|
20433
|
+
} catch {
|
|
20434
|
+
return null;
|
|
20435
|
+
} finally {
|
|
20436
|
+
clearTimeout(timer);
|
|
20437
|
+
}
|
|
20438
|
+
}
|
|
20439
|
+
var AnthropicListSchema = external_exports.object({
|
|
20440
|
+
data: external_exports.array(external_exports.object({ id: external_exports.string(), display_name: external_exports.string().optional() }))
|
|
20441
|
+
});
|
|
20442
|
+
function anthropicContextWindow(id) {
|
|
20443
|
+
return id.toLowerCase().includes("haiku") ? 2e5 : 1e6;
|
|
20444
|
+
}
|
|
20445
|
+
async function fetchAnthropic(apiKey) {
|
|
20446
|
+
const raw = await getJson("https://api.anthropic.com/v1/models?limit=1000", {
|
|
20447
|
+
"x-api-key": apiKey,
|
|
20448
|
+
"anthropic-version": "2023-06-01"
|
|
20449
|
+
});
|
|
20450
|
+
const parsed = AnthropicListSchema.safeParse(raw);
|
|
20451
|
+
if (!parsed.success) return null;
|
|
20452
|
+
return parsed.data.data.map((m) => ({
|
|
20453
|
+
id: m.id,
|
|
20454
|
+
name: m.display_name ?? m.id,
|
|
20455
|
+
contextWindow: anthropicContextWindow(m.id),
|
|
20456
|
+
isDefault: false
|
|
20457
|
+
}));
|
|
20458
|
+
}
|
|
20459
|
+
var OpenAIListSchema = external_exports.object({
|
|
20460
|
+
data: external_exports.array(external_exports.object({ id: external_exports.string() }))
|
|
20461
|
+
});
|
|
20462
|
+
function isOpenAIChatModel(id) {
|
|
20463
|
+
const l = id.toLowerCase();
|
|
20464
|
+
const isChat = /^(gpt-|chatgpt-|o\d)/.test(l);
|
|
20465
|
+
const isNonChat = /(embedding|whisper|tts|audio|realtime|transcribe|moderation|dall-e|image|search|babbage|davinci|instruct)/.test(l);
|
|
20466
|
+
return isChat && !isNonChat;
|
|
20467
|
+
}
|
|
20468
|
+
async function fetchOpenAI(apiKey) {
|
|
20469
|
+
const raw = await getJson("https://api.openai.com/v1/models", {
|
|
20470
|
+
Authorization: `Bearer ${apiKey}`
|
|
20471
|
+
});
|
|
20472
|
+
const parsed = OpenAIListSchema.safeParse(raw);
|
|
20473
|
+
if (!parsed.success) return null;
|
|
20474
|
+
return parsed.data.data.filter((m) => isOpenAIChatModel(m.id)).map((m) => ({ id: m.id, name: m.id, contextWindow: 128e3, isDefault: false }));
|
|
20475
|
+
}
|
|
20476
|
+
var GoogleListSchema = external_exports.object({
|
|
20477
|
+
models: external_exports.array(external_exports.object({
|
|
20478
|
+
name: external_exports.string(),
|
|
20479
|
+
displayName: external_exports.string().optional(),
|
|
20480
|
+
inputTokenLimit: external_exports.number().optional(),
|
|
20481
|
+
supportedGenerationMethods: external_exports.array(external_exports.string()).optional()
|
|
20482
|
+
}))
|
|
20483
|
+
});
|
|
20484
|
+
async function fetchGoogle(apiKey) {
|
|
20485
|
+
const raw = await getJson(
|
|
20486
|
+
`https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000&key=${encodeURIComponent(apiKey)}`,
|
|
20487
|
+
{}
|
|
20488
|
+
);
|
|
20489
|
+
const parsed = GoogleListSchema.safeParse(raw);
|
|
20490
|
+
if (!parsed.success) return null;
|
|
20491
|
+
return parsed.data.models.filter((m) => (m.supportedGenerationMethods ?? []).includes("generateContent")).map((m) => ({
|
|
20492
|
+
id: m.name.replace(/^models\//, ""),
|
|
20493
|
+
name: m.displayName ?? m.name.replace(/^models\//, ""),
|
|
20494
|
+
contextWindow: m.inputTokenLimit !== void 0 && m.inputTokenLimit > 0 ? m.inputTokenLimit : 1e6,
|
|
20495
|
+
isDefault: false
|
|
20496
|
+
}));
|
|
20497
|
+
}
|
|
20498
|
+
async function fetchAvailableModels(platform, apiKey) {
|
|
20499
|
+
let models;
|
|
20500
|
+
if (platform === "anthropic") models = await fetchAnthropic(apiKey);
|
|
20501
|
+
else if (platform === "openai") models = await fetchOpenAI(apiKey);
|
|
20502
|
+
else models = await fetchGoogle(apiKey);
|
|
20503
|
+
if (models === null || models.length === 0) return null;
|
|
20504
|
+
const defaultId = getDefaultModel(platform);
|
|
20505
|
+
const hasDefault = models.some((m) => m.id === defaultId);
|
|
20506
|
+
return models.map((m, i) => ({
|
|
20507
|
+
...m,
|
|
20508
|
+
isDefault: hasDefault ? m.id === defaultId : i === 0
|
|
20509
|
+
}));
|
|
20510
|
+
}
|
|
20511
|
+
|
|
20512
|
+
// src/routes/ai-config.ts
|
|
20398
20513
|
var aiConfigRoutes = new Hono2();
|
|
20399
20514
|
aiConfigRoutes.get("/config", (c) => {
|
|
20400
20515
|
const config2 = loadAIConfig();
|
|
@@ -20416,8 +20531,18 @@ aiConfigRoutes.post("/config", async (c) => {
|
|
|
20416
20531
|
}
|
|
20417
20532
|
return c.json({ ok: true });
|
|
20418
20533
|
});
|
|
20419
|
-
aiConfigRoutes.get("/models", (c) => {
|
|
20420
|
-
|
|
20534
|
+
aiConfigRoutes.get("/models", async (c) => {
|
|
20535
|
+
const platforms = ["anthropic", "openai", "google"];
|
|
20536
|
+
const models = { anthropic: MODELS.anthropic, openai: MODELS.openai, google: MODELS.google };
|
|
20537
|
+
if (!isAIServiceTest() && getDemoMode() === null) {
|
|
20538
|
+
await Promise.all(platforms.map(async (platform) => {
|
|
20539
|
+
const { key } = resolveAPIKey(platform);
|
|
20540
|
+
if (key === null) return;
|
|
20541
|
+
const live = await fetchAvailableModels(platform, key);
|
|
20542
|
+
if (live !== null && live.length > 0) models[platform] = live;
|
|
20543
|
+
}));
|
|
20544
|
+
}
|
|
20545
|
+
return c.json({ platforms: PLATFORMS, models });
|
|
20421
20546
|
});
|
|
20422
20547
|
aiConfigRoutes.get("/key-status", (c) => {
|
|
20423
20548
|
const platforms = ["anthropic", "openai", "google"];
|
|
@@ -20719,6 +20844,16 @@ init_queries();
|
|
|
20719
20844
|
import { execFileSync, spawn } from "child_process";
|
|
20720
20845
|
import { resolve as resolve3 } from "path";
|
|
20721
20846
|
function openOS(target, mode) {
|
|
20847
|
+
if (mode === "edit") {
|
|
20848
|
+
if (process.platform === "darwin") {
|
|
20849
|
+
launchDetached("open", [target]);
|
|
20850
|
+
} else if (process.platform === "win32") {
|
|
20851
|
+
launchDetached("cmd", ["/c", "start", "", target]);
|
|
20852
|
+
} else {
|
|
20853
|
+
launchDetached("xdg-open", [target]);
|
|
20854
|
+
}
|
|
20855
|
+
return;
|
|
20856
|
+
}
|
|
20722
20857
|
if (mode === "reveal") {
|
|
20723
20858
|
if (process.platform === "darwin") {
|
|
20724
20859
|
launchDetached("open", ["-R", target]);
|
|
@@ -20739,7 +20874,8 @@ function openOS(target, mode) {
|
|
|
20739
20874
|
}
|
|
20740
20875
|
function launchDetached(command, args) {
|
|
20741
20876
|
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
20742
|
-
child.on("error", () => {
|
|
20877
|
+
child.on("error", (err) => {
|
|
20878
|
+
debugLog(`launchDetached(${command}) failed: ${err.message}`);
|
|
20743
20879
|
});
|
|
20744
20880
|
child.unref();
|
|
20745
20881
|
}
|
|
@@ -20780,7 +20916,30 @@ filesRoutes.post("/files/:fileId/reveal", async (c) => {
|
|
|
20780
20916
|
const fullPath = resolve4(repoRoot, file2.file_path);
|
|
20781
20917
|
try {
|
|
20782
20918
|
openOS(fullPath, "reveal");
|
|
20783
|
-
} catch {
|
|
20919
|
+
} catch (err) {
|
|
20920
|
+
debugLog(`reveal failed for ${fullPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20921
|
+
}
|
|
20922
|
+
return c.json({ ok: true });
|
|
20923
|
+
});
|
|
20924
|
+
filesRoutes.get("/files/:fileId/path", async (c) => {
|
|
20925
|
+
const fileId = requirePathParam(c, "fileId");
|
|
20926
|
+
if (!fileId.ok) return fileId.response;
|
|
20927
|
+
const file2 = await getReviewFile(fileId.data);
|
|
20928
|
+
if (!file2) return c.json({ error: "Not found" }, 404);
|
|
20929
|
+
const repoRoot = c.get("repoRoot");
|
|
20930
|
+
return c.json({ relativePath: file2.file_path, absolutePath: resolve4(repoRoot, file2.file_path) });
|
|
20931
|
+
});
|
|
20932
|
+
filesRoutes.post("/files/:fileId/open", async (c) => {
|
|
20933
|
+
const fileId = requirePathParam(c, "fileId");
|
|
20934
|
+
if (!fileId.ok) return fileId.response;
|
|
20935
|
+
const file2 = await getReviewFile(fileId.data);
|
|
20936
|
+
if (!file2) return c.json({ error: "Not found" }, 404);
|
|
20937
|
+
const repoRoot = c.get("repoRoot");
|
|
20938
|
+
const fullPath = resolve4(repoRoot, file2.file_path);
|
|
20939
|
+
try {
|
|
20940
|
+
openOS(fullPath, "edit");
|
|
20941
|
+
} catch (err) {
|
|
20942
|
+
debugLog(`open-in-editor failed for ${fullPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20784
20943
|
}
|
|
20785
20944
|
return c.json({ ok: true });
|
|
20786
20945
|
});
|
|
@@ -21746,6 +21905,7 @@ function pushIndentSymbol(root, stack, sym, indent, lines, lineIdx) {
|
|
|
21746
21905
|
|
|
21747
21906
|
// src/routes/api/outline.ts
|
|
21748
21907
|
var outlineRoutes = new Hono8();
|
|
21908
|
+
var MAX_REPO_SCAN_FILES = 2e3;
|
|
21749
21909
|
outlineRoutes.get("/outline/:fileId", async (c) => {
|
|
21750
21910
|
const repoRoot = c.get("repoRoot");
|
|
21751
21911
|
const fileId = requirePathParam(c, "fileId");
|
|
@@ -21789,16 +21949,22 @@ outlineRoutes.get("/symbol-definition", async (c) => {
|
|
|
21789
21949
|
if (definitions.length === 0) {
|
|
21790
21950
|
try {
|
|
21791
21951
|
const allFiles = spawnSync7("git", ["ls-files"], { cwd: repoRoot, encoding: "utf-8" }).stdout.trim().split("\n").filter(Boolean);
|
|
21952
|
+
let scanned = 0;
|
|
21792
21953
|
for (const filePath of allFiles) {
|
|
21793
21954
|
if (searchedPaths.has(filePath)) continue;
|
|
21794
21955
|
const ext = filePath.slice(filePath.lastIndexOf("."));
|
|
21795
21956
|
if (!/\.(js|mjs|cjs|jsx|ts|tsx|mts|cts|java|go|rs|c|h|cpp|cc|cxx|hpp|cs|swift|php|kt|kts|scala|dart|groovy|py|rb)$/i.test(ext)) continue;
|
|
21957
|
+
if (scanned >= MAX_REPO_SCAN_FILES) {
|
|
21958
|
+
debugLog(`outline: repo scan hit ${String(MAX_REPO_SCAN_FILES)}-file budget for "${name}" without a match, stopping early`);
|
|
21959
|
+
break;
|
|
21960
|
+
}
|
|
21796
21961
|
let content = "";
|
|
21797
21962
|
try {
|
|
21798
21963
|
content = readFileSync9(resolve6(repoRoot, filePath), "utf-8");
|
|
21799
21964
|
} catch {
|
|
21800
21965
|
continue;
|
|
21801
21966
|
}
|
|
21967
|
+
scanned++;
|
|
21802
21968
|
if (!content) continue;
|
|
21803
21969
|
const symbols = parseOutline(content, filePath);
|
|
21804
21970
|
collectDefinitions(symbols, name, null, filePath, definitions);
|
|
@@ -23821,7 +23987,7 @@ async function main() {
|
|
|
23821
23987
|
console.log("AI service test mode enabled \u2014 using mock AI responses");
|
|
23822
23988
|
}
|
|
23823
23989
|
if (debug) {
|
|
23824
|
-
console.log(`[debug] Build timestamp: ${"2026-06-
|
|
23990
|
+
console.log(`[debug] Build timestamp: ${"2026-06-17T13:45:47.895Z"}`);
|
|
23825
23991
|
}
|
|
23826
23992
|
if (projectDir !== null) {
|
|
23827
23993
|
process.chdir(projectDir);
|