glassbox 0.15.0-beta.3 → 0.15.0-beta.6
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 +79 -15
- package/dist/client/app.global.js +33 -33
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -16838,7 +16838,12 @@ import { chmodSync, existsSync, mkdirSync as mkdirSync2, readFileSync, writeFile
|
|
|
16838
16838
|
import { homedir } from "os";
|
|
16839
16839
|
import { join as join2 } from "path";
|
|
16840
16840
|
var GlobalConfigSchema = external_exports.record(external_exports.string(), external_exports.unknown());
|
|
16841
|
-
|
|
16841
|
+
function resolveGlobalConfigDir() {
|
|
16842
|
+
const override = process.env.GLASSBOX_CONFIG_DIR;
|
|
16843
|
+
if (override !== void 0 && override.trim() !== "") return override;
|
|
16844
|
+
return join2(homedir(), ".glassbox");
|
|
16845
|
+
}
|
|
16846
|
+
var GLOBAL_CONFIG_DIR = resolveGlobalConfigDir();
|
|
16842
16847
|
var GLOBAL_CONFIG_PATH = join2(GLOBAL_CONFIG_DIR, "config.json");
|
|
16843
16848
|
function readGlobalConfig() {
|
|
16844
16849
|
try {
|
|
@@ -16981,6 +16986,7 @@ var PLATFORMS = {
|
|
|
16981
16986
|
apple: "Apple"
|
|
16982
16987
|
};
|
|
16983
16988
|
var APPLE_ON_DEVICE_MODEL_ID = "apple-on-device";
|
|
16989
|
+
var APPLE_FM_ANALYSIS_ENABLED = true;
|
|
16984
16990
|
var MODELS = {
|
|
16985
16991
|
anthropic: [
|
|
16986
16992
|
{ id: "claude-opus-4-8", name: "Claude Opus 4.8", contextWindow: 1e6, isDefault: false },
|
|
@@ -17138,7 +17144,10 @@ var ConfigFileSchema = external_exports.object({
|
|
|
17138
17144
|
platform: external_exports.string().optional(),
|
|
17139
17145
|
model: external_exports.string().optional(),
|
|
17140
17146
|
keys: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
17141
|
-
localEndpoint: external_exports.string().optional()
|
|
17147
|
+
localEndpoint: external_exports.string().optional(),
|
|
17148
|
+
// Secondary model used when the primary (Apple FM) fails a batch.
|
|
17149
|
+
fallbackPlatform: external_exports.string().optional(),
|
|
17150
|
+
fallbackModel: external_exports.string().optional()
|
|
17142
17151
|
}).optional(),
|
|
17143
17152
|
guidedReview: external_exports.object({
|
|
17144
17153
|
enabled: external_exports.boolean().optional(),
|
|
@@ -17150,16 +17159,39 @@ function readConfigFile() {
|
|
|
17150
17159
|
const parsed = ConfigFileSchema.safeParse(raw2);
|
|
17151
17160
|
return parsed.success ? parsed.data : {};
|
|
17152
17161
|
}
|
|
17153
|
-
function
|
|
17154
|
-
const
|
|
17155
|
-
const platformRaw = config2.ai?.platform ?? "anthropic";
|
|
17156
|
-
const platform = AIPlatformSchema.safeParse(platformRaw).success ? AIPlatformSchema.parse(platformRaw) : "anthropic";
|
|
17157
|
-
const rawModel = config2.ai?.model ?? getDefaultModel(platform);
|
|
17162
|
+
function resolvePlatformConfig(platform, rawModelOrUndefined) {
|
|
17163
|
+
const rawModel = rawModelOrUndefined ?? getDefaultModel(platform);
|
|
17158
17164
|
const model = KEYLESS_PLATFORMS.has(platform) ? rawModel : resolveModelId(platform, rawModel);
|
|
17159
17165
|
const { key, source } = resolveAPIKey(platform);
|
|
17160
17166
|
const baseUrl = platform === "local" ? resolveLocalEndpoint() : void 0;
|
|
17161
17167
|
return { platform, model, apiKey: key, keySource: source, baseUrl };
|
|
17162
17168
|
}
|
|
17169
|
+
function loadAIConfig() {
|
|
17170
|
+
const config2 = readConfigFile();
|
|
17171
|
+
const platformRaw = config2.ai?.platform ?? "anthropic";
|
|
17172
|
+
let platform = AIPlatformSchema.safeParse(platformRaw).success ? AIPlatformSchema.parse(platformRaw) : "anthropic";
|
|
17173
|
+
if (platform === "apple" && !APPLE_FM_ANALYSIS_ENABLED) platform = "anthropic";
|
|
17174
|
+
const primary = resolvePlatformConfig(platform, config2.ai?.model);
|
|
17175
|
+
if (platform === "apple") {
|
|
17176
|
+
const fallback = resolveFallbackConfig(config2);
|
|
17177
|
+
if (fallback !== null) primary.fallback = fallback;
|
|
17178
|
+
}
|
|
17179
|
+
return primary;
|
|
17180
|
+
}
|
|
17181
|
+
function fallbackSelectionFrom(config2) {
|
|
17182
|
+
const raw2 = config2.ai?.fallbackPlatform;
|
|
17183
|
+
if (raw2 === void 0 || raw2 === "") return null;
|
|
17184
|
+
const parsed = AIPlatformSchema.safeParse(raw2);
|
|
17185
|
+
if (!parsed.success || parsed.data === "apple") return null;
|
|
17186
|
+
return { platform: parsed.data, model: config2.ai?.fallbackModel ?? getDefaultModel(parsed.data) };
|
|
17187
|
+
}
|
|
17188
|
+
function loadFallbackSelection() {
|
|
17189
|
+
return fallbackSelectionFrom(readConfigFile());
|
|
17190
|
+
}
|
|
17191
|
+
function resolveFallbackConfig(config2) {
|
|
17192
|
+
const sel = fallbackSelectionFrom(config2);
|
|
17193
|
+
return sel === null ? null : resolvePlatformConfig(sel.platform, sel.model);
|
|
17194
|
+
}
|
|
17163
17195
|
function saveAIConfigPreferences(platform, model, opts = {}) {
|
|
17164
17196
|
updateGlobalConfig((config2) => {
|
|
17165
17197
|
const parsed = ConfigFileSchema.safeParse(config2);
|
|
@@ -17171,6 +17203,12 @@ function saveAIConfigPreferences(platform, model, opts = {}) {
|
|
|
17171
17203
|
const trimmed = opts.localEndpoint.trim();
|
|
17172
17204
|
cfg.ai.localEndpoint = trimmed === "" ? void 0 : trimmed;
|
|
17173
17205
|
}
|
|
17206
|
+
if (opts.fallbackPlatform !== void 0) {
|
|
17207
|
+
const fp = opts.fallbackPlatform.trim();
|
|
17208
|
+
const fm = opts.fallbackModel?.trim();
|
|
17209
|
+
cfg.ai.fallbackPlatform = fp === "" ? void 0 : fp;
|
|
17210
|
+
cfg.ai.fallbackModel = fp === "" || fm === void 0 || fm === "" ? void 0 : fm;
|
|
17211
|
+
}
|
|
17174
17212
|
return cfg;
|
|
17175
17213
|
});
|
|
17176
17214
|
}
|
|
@@ -19006,6 +19044,16 @@ function extractJSON(text) {
|
|
|
19006
19044
|
throw new Error(`Could not extract JSON from AI response: ${text.slice(0, 300)}`);
|
|
19007
19045
|
}
|
|
19008
19046
|
async function runAnalysisBatch(files, config2, repoRoot, options) {
|
|
19047
|
+
try {
|
|
19048
|
+
return await runAnalysisBatchOnce(files, config2, repoRoot, options);
|
|
19049
|
+
} catch (err) {
|
|
19050
|
+
if (config2.fallback === void 0) throw err;
|
|
19051
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19052
|
+
debugLog(`[${options.analysisName}] primary platform '${config2.platform}' failed for batch (${msg.slice(0, 160)}); retrying with fallback '${config2.fallback.platform}'`);
|
|
19053
|
+
return runAnalysisBatchOnce(files, config2.fallback, repoRoot, options);
|
|
19054
|
+
}
|
|
19055
|
+
}
|
|
19056
|
+
async function runAnalysisBatchOnce(files, config2, repoRoot, options) {
|
|
19009
19057
|
const contextWindow = getModelContextWindow(config2.platform, config2.model);
|
|
19010
19058
|
const charBudget = Math.floor(contextWindow * 0.7 * 3);
|
|
19011
19059
|
const contexts = buildFileContexts(files, charBudget);
|
|
@@ -19696,13 +19744,20 @@ var AIConfigRespSchema = external_exports.object({
|
|
|
19696
19744
|
keySource: KeySourceSchema,
|
|
19697
19745
|
/** Base URL for the `local` (OpenAI-compatible) platform. */
|
|
19698
19746
|
localEndpoint: external_exports.string(),
|
|
19699
|
-
guidedReview: GuidedReviewConfigShapeSchema
|
|
19747
|
+
guidedReview: GuidedReviewConfigShapeSchema,
|
|
19748
|
+
/** Secondary model used when the primary (Apple FM) fails a batch. `null`
|
|
19749
|
+
* when none is configured. */
|
|
19750
|
+
fallbackPlatform: AIPlatformSchema.nullable(),
|
|
19751
|
+
fallbackModel: external_exports.string().nullable()
|
|
19700
19752
|
});
|
|
19701
19753
|
var SaveAIConfigReqSchema = external_exports.object({
|
|
19702
19754
|
platform: AIPlatformSchema,
|
|
19703
19755
|
model: external_exports.string().min(1),
|
|
19704
19756
|
localEndpoint: external_exports.string().optional(),
|
|
19705
|
-
guidedReview: GuidedReviewConfigShapeSchema.optional()
|
|
19757
|
+
guidedReview: GuidedReviewConfigShapeSchema.optional(),
|
|
19758
|
+
/** Apple-FM fallback selection. An empty `fallbackPlatform` clears it. */
|
|
19759
|
+
fallbackPlatform: external_exports.string().optional(),
|
|
19760
|
+
fallbackModel: external_exports.string().optional()
|
|
19706
19761
|
});
|
|
19707
19762
|
var SaveAIConfigRespSchema = OkResponseSchema;
|
|
19708
19763
|
var ListAIModelsRespSchema = external_exports.object({
|
|
@@ -21060,7 +21115,7 @@ aiAnalysisRoutes.post("/analyze", async (c) => {
|
|
|
21060
21115
|
debugLog(`POST /analyze: type=${analysisType}, reviewId=${reviewId}`);
|
|
21061
21116
|
const testMode = isAIServiceTest();
|
|
21062
21117
|
const config2 = loadAIConfig();
|
|
21063
|
-
if (config2.apiKey === null && !testMode) {
|
|
21118
|
+
if (config2.apiKey === null && !testMode && !KEYLESS_PLATFORMS.has(config2.platform)) {
|
|
21064
21119
|
debugLog("POST /analyze: no API key configured");
|
|
21065
21120
|
return c.json({ error: "No API key configured" }, 400);
|
|
21066
21121
|
}
|
|
@@ -21540,21 +21595,30 @@ async function fetchAvailableModels(platform, apiKey, opts = {}) {
|
|
|
21540
21595
|
var aiConfigRoutes = new Hono2();
|
|
21541
21596
|
aiConfigRoutes.get("/config", async (c) => {
|
|
21542
21597
|
const config2 = loadAIConfig();
|
|
21543
|
-
const appleReady = config2.platform === "apple" && await isAppleFoundationAvailable();
|
|
21598
|
+
const appleReady = APPLE_FM_ANALYSIS_ENABLED && config2.platform === "apple" && await isAppleFoundationAvailable();
|
|
21599
|
+
const fallbackSelection = loadFallbackSelection();
|
|
21544
21600
|
return c.json({
|
|
21545
21601
|
platform: config2.platform,
|
|
21546
21602
|
model: config2.model,
|
|
21547
21603
|
keyConfigured: config2.apiKey !== null || config2.platform === "local" || appleReady || isAIServiceTest() || getDemoMode() !== null,
|
|
21548
21604
|
keySource: config2.keySource,
|
|
21549
21605
|
localEndpoint: resolveLocalEndpoint(),
|
|
21550
|
-
guidedReview: loadGuidedReviewConfig()
|
|
21606
|
+
guidedReview: loadGuidedReviewConfig(),
|
|
21607
|
+
// Apple-FM fallback selection as stored, regardless of the current primary
|
|
21608
|
+
// platform, so the settings dialog can show/preserve it; `null` when unset.
|
|
21609
|
+
fallbackPlatform: fallbackSelection?.platform ?? null,
|
|
21610
|
+
fallbackModel: fallbackSelection?.model ?? null
|
|
21551
21611
|
});
|
|
21552
21612
|
});
|
|
21553
21613
|
aiConfigRoutes.post("/config", async (c) => {
|
|
21554
21614
|
const parsed = await parseBody(c, SaveAIConfigReqSchema);
|
|
21555
21615
|
if (!parsed.ok) return parsed.response;
|
|
21556
21616
|
const body = parsed.data;
|
|
21557
|
-
saveAIConfigPreferences(body.platform, body.model, {
|
|
21617
|
+
saveAIConfigPreferences(body.platform, body.model, {
|
|
21618
|
+
localEndpoint: body.localEndpoint,
|
|
21619
|
+
fallbackPlatform: body.fallbackPlatform,
|
|
21620
|
+
fallbackModel: body.fallbackModel
|
|
21621
|
+
});
|
|
21558
21622
|
if (body.guidedReview !== void 0) {
|
|
21559
21623
|
saveGuidedReviewConfig(body.guidedReview);
|
|
21560
21624
|
}
|
|
@@ -21580,7 +21644,7 @@ aiConfigRoutes.get("/models", async (c) => {
|
|
|
21580
21644
|
const localLive = await fetchAvailableModels("local", localKey ?? "", { baseUrl: resolveLocalEndpoint() });
|
|
21581
21645
|
if (localLive !== null && localLive.length > 0) models.local = localLive;
|
|
21582
21646
|
}
|
|
21583
|
-
const appleAvailable = await isAppleFoundationAvailable();
|
|
21647
|
+
const appleAvailable = APPLE_FM_ANALYSIS_ENABLED && (isAIServiceTest() || await isAppleFoundationAvailable());
|
|
21584
21648
|
return c.json({ platforms: PLATFORMS, models, appleAvailable });
|
|
21585
21649
|
});
|
|
21586
21650
|
aiConfigRoutes.get("/key-status", (c) => {
|
|
@@ -25221,7 +25285,7 @@ async function main() {
|
|
|
25221
25285
|
console.log("AI service test mode enabled \u2014 using mock AI responses");
|
|
25222
25286
|
}
|
|
25223
25287
|
if (debug) {
|
|
25224
|
-
console.log(`[debug] Build timestamp: ${"2026-06-
|
|
25288
|
+
console.log(`[debug] Build timestamp: ${"2026-06-19T07:12:20.944Z"}`);
|
|
25225
25289
|
}
|
|
25226
25290
|
if (projectDir !== null) {
|
|
25227
25291
|
process.chdir(projectDir);
|