cascade-ai 0.73.0 → 0.75.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/README.md +6 -3
- package/dist/cli.cjs +2140 -714
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2121 -695
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +55530 -54359
- package/dist/index.cjs +16139 -14968
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +569 -34
- package/dist/index.d.ts +569 -34
- package/dist/index.js +16261 -15108
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var Anthropic = require('@anthropic-ai/sdk');
|
|
4
|
-
var OpenAI = require('openai');
|
|
5
|
-
var genai = require('@google/genai');
|
|
6
3
|
var dns = require('dns');
|
|
7
4
|
var http = require('http');
|
|
8
5
|
var https = require('https');
|
|
9
6
|
var zlib = require('zlib');
|
|
10
7
|
var stream = require('stream');
|
|
8
|
+
var Anthropic = require('@anthropic-ai/sdk');
|
|
9
|
+
var OpenAI = require('openai');
|
|
10
|
+
var genai = require('@google/genai');
|
|
11
11
|
var Database2 = require('better-sqlite3');
|
|
12
|
-
var
|
|
13
|
-
var
|
|
12
|
+
var path35 = require('path');
|
|
13
|
+
var fs32 = require('fs');
|
|
14
14
|
var os10 = require('os');
|
|
15
15
|
var crypto = require('crypto');
|
|
16
16
|
var ink = require('ink');
|
|
17
17
|
var commander = require('commander');
|
|
18
18
|
var React2 = require('react');
|
|
19
|
-
var
|
|
19
|
+
var chalk8 = require('chalk');
|
|
20
20
|
var dotenv = require('dotenv');
|
|
21
21
|
var url = require('url');
|
|
22
22
|
var fs11 = require('fs/promises');
|
|
@@ -70,19 +70,19 @@ function _interopNamespace(e) {
|
|
|
70
70
|
return Object.freeze(n);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
var Anthropic__default = /*#__PURE__*/_interopDefault(Anthropic);
|
|
74
|
-
var OpenAI__default = /*#__PURE__*/_interopDefault(OpenAI);
|
|
75
73
|
var dns__default = /*#__PURE__*/_interopDefault(dns);
|
|
76
74
|
var http__default = /*#__PURE__*/_interopDefault(http);
|
|
77
75
|
var https__default = /*#__PURE__*/_interopDefault(https);
|
|
78
76
|
var zlib__default = /*#__PURE__*/_interopDefault(zlib);
|
|
77
|
+
var Anthropic__default = /*#__PURE__*/_interopDefault(Anthropic);
|
|
78
|
+
var OpenAI__default = /*#__PURE__*/_interopDefault(OpenAI);
|
|
79
79
|
var Database2__default = /*#__PURE__*/_interopDefault(Database2);
|
|
80
|
-
var
|
|
81
|
-
var
|
|
80
|
+
var path35__default = /*#__PURE__*/_interopDefault(path35);
|
|
81
|
+
var fs32__default = /*#__PURE__*/_interopDefault(fs32);
|
|
82
82
|
var os10__default = /*#__PURE__*/_interopDefault(os10);
|
|
83
83
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
84
84
|
var React2__default = /*#__PURE__*/_interopDefault(React2);
|
|
85
|
-
var
|
|
85
|
+
var chalk8__default = /*#__PURE__*/_interopDefault(chalk8);
|
|
86
86
|
var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
|
|
87
87
|
var fs11__default = /*#__PURE__*/_interopDefault(fs11);
|
|
88
88
|
var _ignoreModule__namespace = /*#__PURE__*/_interopNamespace(_ignoreModule);
|
|
@@ -1819,7 +1819,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
|
|
|
1819
1819
|
var init_constants = __esm({
|
|
1820
1820
|
"src/constants.ts"() {
|
|
1821
1821
|
init_pricing();
|
|
1822
|
-
CASCADE_VERSION = "0.
|
|
1822
|
+
CASCADE_VERSION = "0.75.0";
|
|
1823
1823
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1824
1824
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1825
1825
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -2248,6 +2248,244 @@ var init_constants = __esm({
|
|
|
2248
2248
|
];
|
|
2249
2249
|
}
|
|
2250
2250
|
});
|
|
2251
|
+
function preferIpv4Host(url) {
|
|
2252
|
+
return url;
|
|
2253
|
+
}
|
|
2254
|
+
function stripTrailingSlashes(value) {
|
|
2255
|
+
let end = value.length;
|
|
2256
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
|
|
2257
|
+
return end === value.length ? value : value.slice(0, end);
|
|
2258
|
+
}
|
|
2259
|
+
function normalizeEndpoint(url) {
|
|
2260
|
+
const raw = stripTrailingSlashes((url ?? "").trim());
|
|
2261
|
+
if (!raw) return "";
|
|
2262
|
+
try {
|
|
2263
|
+
const u = new URL(raw);
|
|
2264
|
+
const path40 = stripTrailingSlashes(`${u.pathname}${u.search}`);
|
|
2265
|
+
return `${u.protocol}//${u.host.toLowerCase()}${path40}`;
|
|
2266
|
+
} catch {
|
|
2267
|
+
return raw.toLowerCase();
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
function stripPathSegment(url, segment) {
|
|
2271
|
+
const trimmed = stripTrailingSlashes(url.trim());
|
|
2272
|
+
const suffix = `/${segment}`;
|
|
2273
|
+
return trimmed.endsWith(suffix) ? trimmed.slice(0, -suffix.length) : trimmed;
|
|
2274
|
+
}
|
|
2275
|
+
function clientApiRoot(type, url) {
|
|
2276
|
+
const segment = CLIENT_VERSION_SEGMENT[type];
|
|
2277
|
+
return segment ? stripPathSegment(url, segment) : stripTrailingSlashes(url.trim());
|
|
2278
|
+
}
|
|
2279
|
+
function headersFrom(raw) {
|
|
2280
|
+
const out = new Headers();
|
|
2281
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
2282
|
+
if (k === "content-encoding" || k === "content-length") continue;
|
|
2283
|
+
if (Array.isArray(v)) out.set(k, v.join(", "));
|
|
2284
|
+
else if (typeof v === "string") out.set(k, v);
|
|
2285
|
+
}
|
|
2286
|
+
return out;
|
|
2287
|
+
}
|
|
2288
|
+
async function fetchSameOrigin(url, init = {}, maxRedirects = MAX_REDIRECTS) {
|
|
2289
|
+
const origin = new URL(url).origin;
|
|
2290
|
+
let current = url;
|
|
2291
|
+
let options = { ...init };
|
|
2292
|
+
for (let hop = 0; hop <= maxRedirects; hop++) {
|
|
2293
|
+
const res = await fetch(current, { ...options, redirect: "manual" });
|
|
2294
|
+
if (![301, 302, 303, 307, 308].includes(res.status)) return res;
|
|
2295
|
+
const location = res.headers.get("location");
|
|
2296
|
+
if (!location) return res;
|
|
2297
|
+
await res.body?.cancel().catch(() => {
|
|
2298
|
+
});
|
|
2299
|
+
const next = new URL(location, current);
|
|
2300
|
+
if (next.origin !== origin) {
|
|
2301
|
+
throw new Error(
|
|
2302
|
+
`Refusing cross-origin redirect to ${next.origin}: the request carries a credential for ${origin}.`
|
|
2303
|
+
);
|
|
2304
|
+
}
|
|
2305
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
2306
|
+
const downgrades = res.status === 303 ? method !== "GET" && method !== "HEAD" : (res.status === 301 || res.status === 302) && method === "POST";
|
|
2307
|
+
if (downgrades) {
|
|
2308
|
+
const headers = new Headers(options.headers);
|
|
2309
|
+
for (const h of ["content-type", "content-length", "content-encoding", "content-language", "content-location"]) {
|
|
2310
|
+
headers.delete(h);
|
|
2311
|
+
}
|
|
2312
|
+
options = { ...options, method: "GET", body: void 0, headers };
|
|
2313
|
+
}
|
|
2314
|
+
current = next.toString();
|
|
2315
|
+
}
|
|
2316
|
+
throw new Error(`Too many redirects from ${url}`);
|
|
2317
|
+
}
|
|
2318
|
+
async function nodeHttpFetch(input, init = {}, redirectCount = 0, opts = {}) {
|
|
2319
|
+
const urlStr = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
2320
|
+
const u = new URL(preferIpv4Host(urlStr) ?? urlStr);
|
|
2321
|
+
const lib = u.protocol === "https:" ? https__default.default : http__default.default;
|
|
2322
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
2323
|
+
const headers = {};
|
|
2324
|
+
const h = init.headers;
|
|
2325
|
+
if (h instanceof Headers) h.forEach((v, k) => {
|
|
2326
|
+
headers[k] = v;
|
|
2327
|
+
});
|
|
2328
|
+
else if (Array.isArray(h)) for (const [k, v] of h) headers[k] = v;
|
|
2329
|
+
else if (h) Object.assign(headers, h);
|
|
2330
|
+
if (!Object.keys(headers).some((k) => k.toLowerCase() === "accept-encoding")) {
|
|
2331
|
+
headers["accept-encoding"] = "gzip, deflate, br";
|
|
2332
|
+
}
|
|
2333
|
+
const body = init.body == null ? void 0 : typeof init.body === "string" ? init.body : Buffer.from(init.body);
|
|
2334
|
+
return new Promise((resolve2, reject) => {
|
|
2335
|
+
const req = lib.request(
|
|
2336
|
+
{
|
|
2337
|
+
hostname: u.hostname,
|
|
2338
|
+
port: u.port || (u.protocol === "https:" ? 443 : 80),
|
|
2339
|
+
path: u.pathname + u.search,
|
|
2340
|
+
method,
|
|
2341
|
+
headers
|
|
2342
|
+
},
|
|
2343
|
+
(res) => {
|
|
2344
|
+
const status = res.statusCode ?? 200;
|
|
2345
|
+
const location = res.headers.location;
|
|
2346
|
+
if (REDIRECT_STATUSES.has(status) && location && redirectCount < MAX_REDIRECTS) {
|
|
2347
|
+
res.resume();
|
|
2348
|
+
const nextUrl = new URL(location, u).href;
|
|
2349
|
+
if (opts.allowedRedirectOrigin && new URL(nextUrl).origin !== opts.allowedRedirectOrigin) {
|
|
2350
|
+
reject(new Error(`Refusing cross-origin redirect to ${new URL(nextUrl).origin}`));
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
const downgrade = status === 303 ? method !== "GET" && method !== "HEAD" : (status === 301 || status === 302) && method === "POST";
|
|
2354
|
+
let nextInit = init;
|
|
2355
|
+
if (downgrade) {
|
|
2356
|
+
const headers2 = new Headers(init.headers);
|
|
2357
|
+
for (const h2 of ["content-type", "content-length", "content-encoding", "content-language", "content-location"]) {
|
|
2358
|
+
headers2.delete(h2);
|
|
2359
|
+
}
|
|
2360
|
+
nextInit = { ...init, method: "GET", body: void 0, headers: headers2 };
|
|
2361
|
+
}
|
|
2362
|
+
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1, opts));
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
const encoding = (res.headers["content-encoding"] ?? "").toLowerCase();
|
|
2366
|
+
let bodyStream = res;
|
|
2367
|
+
if (encoding === "gzip" || encoding === "x-gzip") bodyStream = res.pipe(zlib__default.default.createGunzip());
|
|
2368
|
+
else if (encoding === "deflate") bodyStream = res.pipe(zlib__default.default.createInflate());
|
|
2369
|
+
else if (encoding === "br") bodyStream = res.pipe(zlib__default.default.createBrotliDecompress());
|
|
2370
|
+
if (NULL_BODY_STATUSES.has(status)) {
|
|
2371
|
+
bodyStream.resume();
|
|
2372
|
+
resolve2(new Response(null, {
|
|
2373
|
+
status,
|
|
2374
|
+
statusText: res.statusMessage ?? "",
|
|
2375
|
+
headers: headersFrom(res.headers)
|
|
2376
|
+
}));
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
const stream$1 = stream.Readable.toWeb(bodyStream);
|
|
2380
|
+
resolve2(new Response(stream$1, {
|
|
2381
|
+
status,
|
|
2382
|
+
statusText: res.statusMessage ?? "",
|
|
2383
|
+
headers: headersFrom(res.headers)
|
|
2384
|
+
}));
|
|
2385
|
+
}
|
|
2386
|
+
);
|
|
2387
|
+
req.on("error", reject);
|
|
2388
|
+
req.setTimeout(15e3, () => req.destroy(new Error("request timed out")));
|
|
2389
|
+
if (body !== void 0) req.write(body);
|
|
2390
|
+
req.end();
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
var CLIENT_VERSION_SEGMENT, REDIRECT_STATUSES, NULL_BODY_STATUSES, MAX_REDIRECTS;
|
|
2394
|
+
var init_net = __esm({
|
|
2395
|
+
"src/utils/net.ts"() {
|
|
2396
|
+
try {
|
|
2397
|
+
dns__default.default.setDefaultResultOrder("ipv4first");
|
|
2398
|
+
} catch {
|
|
2399
|
+
}
|
|
2400
|
+
CLIENT_VERSION_SEGMENT = {
|
|
2401
|
+
anthropic: "v1",
|
|
2402
|
+
gemini: "v1beta"
|
|
2403
|
+
};
|
|
2404
|
+
REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
2405
|
+
NULL_BODY_STATUSES = /* @__PURE__ */ new Set([204, 205, 304]);
|
|
2406
|
+
MAX_REDIRECTS = 5;
|
|
2407
|
+
}
|
|
2408
|
+
});
|
|
2409
|
+
|
|
2410
|
+
// src/config/revoked-credentials.ts
|
|
2411
|
+
function isRevokedSubscriptionCredential(p) {
|
|
2412
|
+
if (typeof p !== "object" || p === null) return false;
|
|
2413
|
+
if (p.type !== "anthropic") return false;
|
|
2414
|
+
if (!p.authToken && !p.apiKey) return false;
|
|
2415
|
+
return isSubscriptionToken(p.authToken) || isSubscriptionToken(p.apiKey) || Boolean(p.authToken) && /claude\s*code/i.test(p.credentialSource ?? "");
|
|
2416
|
+
}
|
|
2417
|
+
function isSubscriptionToken(secret) {
|
|
2418
|
+
return !!secret && secret.trim().startsWith("sk-ant-oat");
|
|
2419
|
+
}
|
|
2420
|
+
function stripRevokedCredentials(providers, { keepForEndpoint = true } = {}) {
|
|
2421
|
+
let removed = 0;
|
|
2422
|
+
const kept = [];
|
|
2423
|
+
for (const p of providers) {
|
|
2424
|
+
if (!isRevokedSubscriptionCredential(p)) {
|
|
2425
|
+
kept.push(p);
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
removed++;
|
|
2429
|
+
const cleaned = { ...p };
|
|
2430
|
+
if (isSubscriptionToken(cleaned.apiKey)) delete cleaned.apiKey;
|
|
2431
|
+
const bearerIsDead = isSubscriptionToken(cleaned.authToken) || Boolean(cleaned.authToken) && /claude\s*code/i.test(cleaned.credentialSource ?? "");
|
|
2432
|
+
if (bearerIsDead) {
|
|
2433
|
+
delete cleaned.authToken;
|
|
2434
|
+
delete cleaned.credentialSource;
|
|
2435
|
+
}
|
|
2436
|
+
const worthKeeping = Boolean(
|
|
2437
|
+
cleaned.apiKey || cleaned.authToken || keepForEndpoint && cleaned.baseUrl
|
|
2438
|
+
);
|
|
2439
|
+
if (worthKeeping) kept.push(cleaned);
|
|
2440
|
+
}
|
|
2441
|
+
return { kept, removed };
|
|
2442
|
+
}
|
|
2443
|
+
function stripRevokedFromConfig(raw) {
|
|
2444
|
+
const result = { removed: 0 };
|
|
2445
|
+
if (typeof raw !== "object" || raw === null) return result;
|
|
2446
|
+
const cfg = raw;
|
|
2447
|
+
if (Array.isArray(cfg["providers"])) {
|
|
2448
|
+
const pass = stripRevokedCredentials(cfg["providers"]);
|
|
2449
|
+
result.removed = pass.removed;
|
|
2450
|
+
if (pass.removed > 0) cfg["providers"] = pass.kept;
|
|
2451
|
+
}
|
|
2452
|
+
return result;
|
|
2453
|
+
}
|
|
2454
|
+
function hasUsableAnthropic(providers) {
|
|
2455
|
+
return providers.some((p) => p?.type === "anthropic" && Boolean(p.apiKey || p.authToken && p.baseUrl));
|
|
2456
|
+
}
|
|
2457
|
+
function clearAnthropicPins(models, providers) {
|
|
2458
|
+
const cleared = [];
|
|
2459
|
+
if (typeof models !== "object" || models === null) return cleared;
|
|
2460
|
+
const tiers = models;
|
|
2461
|
+
for (const tier of ["t1", "t2", "t3"]) {
|
|
2462
|
+
const pin = tiers[tier];
|
|
2463
|
+
if (typeof pin !== "string" || !namesAnthropicModel(pin, providers)) continue;
|
|
2464
|
+
delete tiers[tier];
|
|
2465
|
+
cleared.push({ tier, model: pin });
|
|
2466
|
+
}
|
|
2467
|
+
return cleared;
|
|
2468
|
+
}
|
|
2469
|
+
function namesAnthropicModel(pin, providers) {
|
|
2470
|
+
const value = pin.trim().toLowerCase();
|
|
2471
|
+
if (!value) return false;
|
|
2472
|
+
if (value.includes(":")) return value.startsWith("anthropic:");
|
|
2473
|
+
const known = Object.values(MODELS).some(
|
|
2474
|
+
(m) => m.provider === "anthropic" && m.id.toLowerCase() === value
|
|
2475
|
+
);
|
|
2476
|
+
if (!known && !value.startsWith("claude-")) return false;
|
|
2477
|
+
return !canBeServedElsewhere(value, providers);
|
|
2478
|
+
}
|
|
2479
|
+
function canBeServedElsewhere(modelId, providers) {
|
|
2480
|
+
return providers.some((p) => p && p.type === "azure" && (p.deploymentName ?? "").trim().toLowerCase() === modelId);
|
|
2481
|
+
}
|
|
2482
|
+
var REVOKED_CREDENTIAL_REASON;
|
|
2483
|
+
var init_revoked_credentials = __esm({
|
|
2484
|
+
"src/config/revoked-credentials.ts"() {
|
|
2485
|
+
init_constants();
|
|
2486
|
+
REVOKED_CREDENTIAL_REASON = "Anthropic no longer permits third-party tools to use Claude subscription credentials and refuses them at the API, so the linked Claude Code token could not have worked. Add an API key from the Claude Console, or set ANTHROPIC_AUTH_TOKEN with a gateway.";
|
|
2487
|
+
}
|
|
2488
|
+
});
|
|
2251
2489
|
|
|
2252
2490
|
// src/utils/cost.ts
|
|
2253
2491
|
function resolveModelPricing(model, opts = {}) {
|
|
@@ -2317,10 +2555,17 @@ var init_cost = __esm({
|
|
|
2317
2555
|
});
|
|
2318
2556
|
|
|
2319
2557
|
// src/providers/base.ts
|
|
2320
|
-
var BaseProvider;
|
|
2558
|
+
var ProviderUnreachableError, BaseProvider;
|
|
2321
2559
|
var init_base = __esm({
|
|
2322
2560
|
"src/providers/base.ts"() {
|
|
2323
2561
|
init_cost();
|
|
2562
|
+
ProviderUnreachableError = class extends Error {
|
|
2563
|
+
transient = true;
|
|
2564
|
+
constructor(message) {
|
|
2565
|
+
super(message);
|
|
2566
|
+
this.name = "ProviderUnreachableError";
|
|
2567
|
+
}
|
|
2568
|
+
};
|
|
2324
2569
|
BaseProvider = class {
|
|
2325
2570
|
config;
|
|
2326
2571
|
model;
|
|
@@ -2393,6 +2638,8 @@ var init_model_filter = __esm({
|
|
|
2393
2638
|
var anthropic_exports = {};
|
|
2394
2639
|
__export(anthropic_exports, {
|
|
2395
2640
|
AnthropicProvider: () => AnthropicProvider,
|
|
2641
|
+
anthropicApiRoot: () => anthropicApiRoot,
|
|
2642
|
+
anthropicAuth: () => anthropicAuth,
|
|
2396
2643
|
toAnthropicToolUse: () => toAnthropicToolUse,
|
|
2397
2644
|
toAnthropicTools: () => toAnthropicTools
|
|
2398
2645
|
});
|
|
@@ -2417,25 +2664,56 @@ function toAnthropicToolUse(toolCalls) {
|
|
|
2417
2664
|
input: tc.input
|
|
2418
2665
|
}));
|
|
2419
2666
|
}
|
|
2420
|
-
|
|
2667
|
+
function anthropicApiRoot(configured) {
|
|
2668
|
+
if (!configured) return void 0;
|
|
2669
|
+
const root = clientApiRoot("anthropic", configured);
|
|
2670
|
+
return root || void 0;
|
|
2671
|
+
}
|
|
2672
|
+
function anthropicAuth(config) {
|
|
2673
|
+
const gateway = anthropicApiRoot(config.baseUrl);
|
|
2674
|
+
const bearerIsSubscription = isSubscriptionToken(config.authToken);
|
|
2675
|
+
const keyIsSubscription = isSubscriptionToken(config.apiKey);
|
|
2676
|
+
if (bearerIsSubscription || keyIsSubscription) {
|
|
2677
|
+
if (config.apiKey && !keyIsSubscription) return { mode: "apiKey", key: config.apiKey };
|
|
2678
|
+
throw new Error(
|
|
2679
|
+
"This Anthropic credential is a Claude subscription token, which Anthropic does not permit third-party clients to use. Configure an API key from console.anthropic.com instead."
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2682
|
+
if (config.authToken && gateway) return { mode: "bearer", token: config.authToken };
|
|
2683
|
+
if (config.apiKey) return { mode: "apiKey", key: config.apiKey };
|
|
2684
|
+
if (config.authToken) {
|
|
2685
|
+
throw new Error(
|
|
2686
|
+
"Anthropic bearer token configured without a gateway URL. A bearer is only valid at the gateway that issued it, so it will not be sent to the public API. Set `baseUrl` to that gateway, or configure `apiKey` instead."
|
|
2687
|
+
);
|
|
2688
|
+
}
|
|
2689
|
+
return { mode: "none" };
|
|
2690
|
+
}
|
|
2691
|
+
var sameOriginFetch, AnthropicProvider;
|
|
2421
2692
|
var init_anthropic = __esm({
|
|
2422
2693
|
"src/providers/anthropic.ts"() {
|
|
2423
2694
|
init_constants();
|
|
2424
2695
|
init_base();
|
|
2425
2696
|
init_pricing();
|
|
2426
2697
|
init_model_filter();
|
|
2698
|
+
init_net();
|
|
2699
|
+
init_revoked_credentials();
|
|
2700
|
+
sameOriginFetch = ((input, init) => {
|
|
2701
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
2702
|
+
return fetchSameOrigin(url, init);
|
|
2703
|
+
});
|
|
2427
2704
|
AnthropicProvider = class extends BaseProvider {
|
|
2428
2705
|
client;
|
|
2429
2706
|
constructor(config, model) {
|
|
2430
2707
|
super(config, model);
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
});
|
|
2708
|
+
const baseURL = anthropicApiRoot(config.baseUrl);
|
|
2709
|
+
const auth2 = anthropicAuth(config);
|
|
2710
|
+
if (auth2.mode === "bearer") {
|
|
2711
|
+
this.client = new Anthropic__default.default({ authToken: auth2.token, fetch: sameOriginFetch, baseURL });
|
|
2436
2712
|
} else {
|
|
2437
2713
|
this.client = new Anthropic__default.default({
|
|
2438
|
-
apiKey:
|
|
2714
|
+
apiKey: auth2.mode === "apiKey" ? auth2.key : void 0,
|
|
2715
|
+
fetch: sameOriginFetch,
|
|
2716
|
+
...baseURL ? { baseURL } : {}
|
|
2439
2717
|
});
|
|
2440
2718
|
}
|
|
2441
2719
|
}
|
|
@@ -2511,21 +2789,24 @@ var init_anthropic = __esm({
|
|
|
2511
2789
|
async countTokens(text) {
|
|
2512
2790
|
return Math.ceil(text.length / 4);
|
|
2513
2791
|
}
|
|
2514
|
-
async listModels() {
|
|
2792
|
+
async listModels(options = {}) {
|
|
2793
|
+
const staticCatalog = () => options.staticFallback === false ? [] : Object.values(MODELS).filter((m) => m.provider === "anthropic");
|
|
2515
2794
|
try {
|
|
2516
|
-
const
|
|
2795
|
+
const base = anthropicApiRoot(this.config.baseUrl) ?? "https://api.anthropic.com";
|
|
2796
|
+
const modelsUrl = `${base}/v1/models`;
|
|
2797
|
+
const auth2 = anthropicAuth(this.config);
|
|
2798
|
+
const resp = await fetchSameOrigin(modelsUrl, {
|
|
2517
2799
|
headers: {
|
|
2518
|
-
|
|
2800
|
+
// Same decision the constructor makes — see anthropicAuth(). Read
|
|
2801
|
+
// straight off `config`, this branch sent a bearer to the public
|
|
2802
|
+
// default host whenever no gateway was configured.
|
|
2803
|
+
...auth2.mode === "bearer" ? { authorization: `Bearer ${auth2.token}` } : { "x-api-key": auth2.mode === "apiKey" ? auth2.key : "" },
|
|
2519
2804
|
"anthropic-version": "2023-06-01"
|
|
2520
2805
|
}
|
|
2521
2806
|
});
|
|
2522
|
-
if (!resp.ok)
|
|
2523
|
-
return Object.values(MODELS).filter((m) => m.provider === "anthropic");
|
|
2524
|
-
}
|
|
2807
|
+
if (!resp.ok) return staticCatalog();
|
|
2525
2808
|
const data = await resp.json();
|
|
2526
|
-
if (!Array.isArray(data?.data))
|
|
2527
|
-
return Object.values(MODELS).filter((m) => m.provider === "anthropic");
|
|
2528
|
-
}
|
|
2809
|
+
if (!Array.isArray(data?.data)) return staticCatalog();
|
|
2529
2810
|
return data.data.filter((m) => isChatModel(m.id)).map((m) => {
|
|
2530
2811
|
const known = Object.values(MODELS).find((km) => km.id === m.id && km.provider === "anthropic");
|
|
2531
2812
|
if (known) return known;
|
|
@@ -2543,7 +2824,7 @@ var init_anthropic = __esm({
|
|
|
2543
2824
|
});
|
|
2544
2825
|
});
|
|
2545
2826
|
} catch {
|
|
2546
|
-
return
|
|
2827
|
+
return staticCatalog();
|
|
2547
2828
|
}
|
|
2548
2829
|
}
|
|
2549
2830
|
async isAvailable() {
|
|
@@ -2763,7 +3044,8 @@ var init_openai = __esm({
|
|
|
2763
3044
|
async countTokens(text) {
|
|
2764
3045
|
return Math.ceil(text.length / 4);
|
|
2765
3046
|
}
|
|
2766
|
-
async listModels() {
|
|
3047
|
+
async listModels(options = {}) {
|
|
3048
|
+
const staticCatalog = () => options.staticFallback === false ? [] : Object.values(MODELS).filter((m) => m.provider === "openai");
|
|
2767
3049
|
try {
|
|
2768
3050
|
const response = await this.client.models.list();
|
|
2769
3051
|
return response.data.filter((m) => isChatModel(m.id)).map((m) => {
|
|
@@ -2783,7 +3065,7 @@ var init_openai = __esm({
|
|
|
2783
3065
|
});
|
|
2784
3066
|
});
|
|
2785
3067
|
} catch {
|
|
2786
|
-
return
|
|
3068
|
+
return staticCatalog();
|
|
2787
3069
|
}
|
|
2788
3070
|
}
|
|
2789
3071
|
async isAvailable() {
|
|
@@ -3085,9 +3367,46 @@ var init_gemini_schema = __esm({
|
|
|
3085
3367
|
var gemini_exports = {};
|
|
3086
3368
|
__export(gemini_exports, {
|
|
3087
3369
|
GeminiProvider: () => GeminiProvider,
|
|
3370
|
+
geminiApiRoot: () => geminiApiRoot,
|
|
3088
3371
|
toGeminiFunctionCalls: () => toGeminiFunctionCalls,
|
|
3089
3372
|
toGeminiTools: () => toGeminiTools
|
|
3090
3373
|
});
|
|
3374
|
+
async function* parseSseChunks(body) {
|
|
3375
|
+
const reader = body.getReader();
|
|
3376
|
+
const decoder = new TextDecoder();
|
|
3377
|
+
let buffered = "";
|
|
3378
|
+
try {
|
|
3379
|
+
for (; ; ) {
|
|
3380
|
+
const { done, value } = await reader.read();
|
|
3381
|
+
if (done) break;
|
|
3382
|
+
buffered += decoder.decode(value, { stream: true });
|
|
3383
|
+
let cut = buffered.indexOf("\n");
|
|
3384
|
+
while (cut !== -1) {
|
|
3385
|
+
const line = buffered.slice(0, cut).trim();
|
|
3386
|
+
buffered = buffered.slice(cut + 1);
|
|
3387
|
+
if (line.startsWith("data:")) {
|
|
3388
|
+
const payload = line.slice(5).trim();
|
|
3389
|
+
if (payload && payload !== "[DONE]") {
|
|
3390
|
+
try {
|
|
3391
|
+
yield JSON.parse(payload);
|
|
3392
|
+
} catch {
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
cut = buffered.indexOf("\n");
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
} finally {
|
|
3400
|
+
reader.releaseLock();
|
|
3401
|
+
await body.cancel().catch(() => {
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
function geminiApiRoot(baseUrl) {
|
|
3406
|
+
const configured = baseUrl?.trim();
|
|
3407
|
+
if (!configured) return GEMINI_PUBLIC_ROOT;
|
|
3408
|
+
return clientApiRoot("gemini", configured) || GEMINI_PUBLIC_ROOT;
|
|
3409
|
+
}
|
|
3091
3410
|
function toGeminiTools(tools) {
|
|
3092
3411
|
return tools.map((tool) => ({
|
|
3093
3412
|
name: tool.name,
|
|
@@ -3108,7 +3427,19 @@ function toGeminiFunctionCalls(toolCalls) {
|
|
|
3108
3427
|
}
|
|
3109
3428
|
}));
|
|
3110
3429
|
}
|
|
3111
|
-
|
|
3430
|
+
function isGeminiAuthStatus(status) {
|
|
3431
|
+
return status === 400 || status === 401 || status === 403;
|
|
3432
|
+
}
|
|
3433
|
+
async function describeGeminiError(resp) {
|
|
3434
|
+
try {
|
|
3435
|
+
const body = await resp.json();
|
|
3436
|
+
const detail = body?.error?.message ?? body?.error?.status;
|
|
3437
|
+
return detail ? ` \u2014 ${detail}` : "";
|
|
3438
|
+
} catch {
|
|
3439
|
+
return "";
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
var GEMINI_API_VERSION, GEMINI_PUBLIC_ROOT, SAFETY_SETTINGS, GeminiProvider;
|
|
3112
3443
|
var init_gemini = __esm({
|
|
3113
3444
|
"src/providers/gemini.ts"() {
|
|
3114
3445
|
init_constants();
|
|
@@ -3116,11 +3447,21 @@ var init_gemini = __esm({
|
|
|
3116
3447
|
init_pricing();
|
|
3117
3448
|
init_model_filter();
|
|
3118
3449
|
init_gemini_schema();
|
|
3450
|
+
init_net();
|
|
3451
|
+
GEMINI_API_VERSION = "v1beta";
|
|
3452
|
+
GEMINI_PUBLIC_ROOT = "https://generativelanguage.googleapis.com";
|
|
3453
|
+
SAFETY_SETTINGS = [
|
|
3454
|
+
{ category: genai.HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: genai.HarmBlockThreshold.BLOCK_NONE },
|
|
3455
|
+
{ category: genai.HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: genai.HarmBlockThreshold.BLOCK_NONE }
|
|
3456
|
+
];
|
|
3119
3457
|
GeminiProvider = class extends BaseProvider {
|
|
3120
3458
|
client;
|
|
3121
3459
|
constructor(config, model) {
|
|
3122
3460
|
super(config, model);
|
|
3123
|
-
this.client = new genai.GoogleGenAI({
|
|
3461
|
+
this.client = new genai.GoogleGenAI({
|
|
3462
|
+
apiKey: config.apiKey ?? "",
|
|
3463
|
+
...config.baseUrl ? { httpOptions: { baseUrl: geminiApiRoot(config.baseUrl) } } : {}
|
|
3464
|
+
});
|
|
3124
3465
|
}
|
|
3125
3466
|
async generate(options) {
|
|
3126
3467
|
const chunks = [];
|
|
@@ -3128,15 +3469,12 @@ var init_gemini = __esm({
|
|
|
3128
3469
|
}
|
|
3129
3470
|
async generateStream(options, onChunk) {
|
|
3130
3471
|
const contents = this.buildContents(options.messages, options.images);
|
|
3131
|
-
const stream = await this.client.models.generateContentStream({
|
|
3472
|
+
const stream = this.config.baseUrl ? await this.streamOverRest(contents, options) : await this.client.models.generateContentStream({
|
|
3132
3473
|
model: this.model.id,
|
|
3133
3474
|
contents,
|
|
3134
3475
|
config: {
|
|
3135
3476
|
systemInstruction: options.systemPrompt,
|
|
3136
|
-
safetySettings:
|
|
3137
|
-
{ category: genai.HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: genai.HarmBlockThreshold.BLOCK_NONE },
|
|
3138
|
-
{ category: genai.HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: genai.HarmBlockThreshold.BLOCK_NONE }
|
|
3139
|
-
],
|
|
3477
|
+
safetySettings: SAFETY_SETTINGS,
|
|
3140
3478
|
tools: options.tools?.length ? [{ functionDeclarations: toGeminiTools(options.tools) }] : void 0,
|
|
3141
3479
|
abortSignal: options.signal
|
|
3142
3480
|
}
|
|
@@ -3187,27 +3525,40 @@ var init_gemini = __esm({
|
|
|
3187
3525
|
};
|
|
3188
3526
|
}
|
|
3189
3527
|
async countTokens(text) {
|
|
3528
|
+
const contents = [{ role: "user", parts: [{ text }] }];
|
|
3190
3529
|
try {
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3530
|
+
if (this.config.baseUrl) {
|
|
3531
|
+
const resp = await fetchSameOrigin(
|
|
3532
|
+
`${geminiApiRoot(this.config.baseUrl)}/${GEMINI_API_VERSION}/models/${encodeURIComponent(this.model.id)}:countTokens`,
|
|
3533
|
+
{
|
|
3534
|
+
method: "POST",
|
|
3535
|
+
headers: {
|
|
3536
|
+
"x-goog-api-key": this.config.apiKey ?? "",
|
|
3537
|
+
"content-type": "application/json"
|
|
3538
|
+
},
|
|
3539
|
+
body: JSON.stringify({ contents })
|
|
3540
|
+
}
|
|
3541
|
+
);
|
|
3542
|
+
if (!resp.ok) return Math.ceil(text.length / 4);
|
|
3543
|
+
const data = await resp.json();
|
|
3544
|
+
return data.totalTokens ?? 0;
|
|
3545
|
+
}
|
|
3546
|
+
const result = await this.client.models.countTokens({ model: this.model.id, contents });
|
|
3195
3547
|
return result.totalTokens ?? 0;
|
|
3196
3548
|
} catch {
|
|
3197
3549
|
return Math.ceil(text.length / 4);
|
|
3198
3550
|
}
|
|
3199
3551
|
}
|
|
3200
|
-
async listModels() {
|
|
3552
|
+
async listModels(options = {}) {
|
|
3553
|
+
const staticCatalog = () => options.staticFallback === false ? [] : Object.values(MODELS).filter((m) => m.provider === "gemini");
|
|
3201
3554
|
try {
|
|
3202
|
-
const resp = await
|
|
3203
|
-
`https://generativelanguage.googleapis.com/v1beta/models?key=${this.config.apiKey}`
|
|
3204
|
-
);
|
|
3555
|
+
const resp = await this.fetchModelList();
|
|
3205
3556
|
if (!resp.ok) {
|
|
3206
|
-
return
|
|
3557
|
+
return staticCatalog();
|
|
3207
3558
|
}
|
|
3208
3559
|
const data = await resp.json();
|
|
3209
3560
|
if (!Array.isArray(data?.models)) {
|
|
3210
|
-
return
|
|
3561
|
+
return staticCatalog();
|
|
3211
3562
|
}
|
|
3212
3563
|
return data.models.filter((m) => isChatModel(m.name.replace("models/", ""), m.supportedGenerationMethods)).map((m) => {
|
|
3213
3564
|
const id = m.name.replace("models/", "");
|
|
@@ -3229,21 +3580,99 @@ var init_gemini = __esm({
|
|
|
3229
3580
|
});
|
|
3230
3581
|
});
|
|
3231
3582
|
} catch {
|
|
3232
|
-
return
|
|
3583
|
+
return staticCatalog();
|
|
3233
3584
|
}
|
|
3234
3585
|
}
|
|
3586
|
+
/**
|
|
3587
|
+
* Is this KEY usable — not "does one particular model answer".
|
|
3588
|
+
*
|
|
3589
|
+
* This used to call countTokens() against `this.model.id`, which the router
|
|
3590
|
+
* fills from the first Gemini entry in the bundled catalogue. That made the
|
|
3591
|
+
* whole provider's fate depend on one hard-coded model id: a key that cannot
|
|
3592
|
+
* reach that one model — retired, not enabled for the key's project, served
|
|
3593
|
+
* on a different API version — failed the probe, and because every later step
|
|
3594
|
+
* is gated on the result (`validateCloudProviderModels` and
|
|
3595
|
+
* `discoverProviderModels` both return early for an unavailable provider) the
|
|
3596
|
+
* real model list was never fetched. Gemini vanished entirely, and the CLI
|
|
3597
|
+
* reported "add a provider API key first" about a key that was present and
|
|
3598
|
+
* working.
|
|
3599
|
+
*
|
|
3600
|
+
* Listing models asks the account-level question instead, the same one
|
|
3601
|
+
* OpenAI's probe asks, and it is the same request `listModels()` already
|
|
3602
|
+
* makes for discovery.
|
|
3603
|
+
*
|
|
3604
|
+
* Throws rather than returning false. The router logs the reason it catches,
|
|
3605
|
+
* and "bad key, wrong endpoint/deployment, or unreachable" — three guesses,
|
|
3606
|
+
* no answer — is what made this take a user report to find. A swallowed probe
|
|
3607
|
+
* error is the same defect that once hid a misconfigured Azure deployment.
|
|
3608
|
+
*/
|
|
3235
3609
|
async isAvailable() {
|
|
3610
|
+
if (!this.config.apiKey) throw new Error("no Gemini API key configured");
|
|
3611
|
+
let resp;
|
|
3236
3612
|
try {
|
|
3237
|
-
await this.
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
}
|
|
3243
|
-
|
|
3613
|
+
resp = await this.fetchModelList();
|
|
3614
|
+
} catch (err) {
|
|
3615
|
+
throw new ProviderUnreachableError(
|
|
3616
|
+
`could not reach the Gemini API: ${err instanceof Error ? err.message : String(err)}`
|
|
3617
|
+
);
|
|
3618
|
+
}
|
|
3619
|
+
if (resp.ok) return true;
|
|
3620
|
+
const status = `HTTP ${resp.status} ${resp.statusText}`.trim();
|
|
3621
|
+
const detail = await describeGeminiError(resp);
|
|
3622
|
+
if (isGeminiAuthStatus(resp.status)) {
|
|
3623
|
+
throw new Error(`Gemini rejected the API key: ${status}${detail}`);
|
|
3244
3624
|
}
|
|
3625
|
+
throw new ProviderUnreachableError(`Gemini model list failed: ${status}${detail}`);
|
|
3245
3626
|
}
|
|
3246
3627
|
// ── Private ──────────────────────────────────
|
|
3628
|
+
/**
|
|
3629
|
+
* The account's model list. One place so the availability probe and
|
|
3630
|
+
* discovery cannot ask different questions of the same endpoint.
|
|
3631
|
+
*
|
|
3632
|
+
* The key goes in a header, not the query string: a URL carries into proxy
|
|
3633
|
+
* logs, error reports and shell history, and this one would carry the key
|
|
3634
|
+
* with it.
|
|
3635
|
+
*/
|
|
3636
|
+
/**
|
|
3637
|
+
* Generation against a configured endpoint, issued by hand.
|
|
3638
|
+
*
|
|
3639
|
+
* Same wire protocol the SDK speaks — `:streamGenerateContent?alt=sse` on
|
|
3640
|
+
* `/v1beta`, with the same body — so the chunk shapes the caller walks are
|
|
3641
|
+
* identical and neither transport needs its own parsing. The difference that
|
|
3642
|
+
* matters is `fetchSameOrigin`: the key is never replayed on a host the
|
|
3643
|
+
* configured origin redirects to.
|
|
3644
|
+
*/
|
|
3645
|
+
async streamOverRest(contents, options) {
|
|
3646
|
+
const url = `${geminiApiRoot(this.config.baseUrl)}/${GEMINI_API_VERSION}/models/${encodeURIComponent(this.model.id)}:streamGenerateContent?alt=sse`;
|
|
3647
|
+
const resp = await fetchSameOrigin(url, {
|
|
3648
|
+
method: "POST",
|
|
3649
|
+
headers: {
|
|
3650
|
+
"x-goog-api-key": this.config.apiKey ?? "",
|
|
3651
|
+
"content-type": "application/json"
|
|
3652
|
+
},
|
|
3653
|
+
body: JSON.stringify({
|
|
3654
|
+
contents,
|
|
3655
|
+
// REST wants the object form; the SDK accepts a bare string and wraps
|
|
3656
|
+
// it itself.
|
|
3657
|
+
...options.systemPrompt ? { systemInstruction: { parts: [{ text: options.systemPrompt }] } } : {},
|
|
3658
|
+
safetySettings: SAFETY_SETTINGS,
|
|
3659
|
+
...options.tools?.length ? { tools: [{ functionDeclarations: toGeminiTools(options.tools) }] } : {}
|
|
3660
|
+
}),
|
|
3661
|
+
signal: options.signal
|
|
3662
|
+
});
|
|
3663
|
+
if (!resp.ok || !resp.body) {
|
|
3664
|
+
const detail = await resp.text().catch(() => "");
|
|
3665
|
+
throw new ProviderUnreachableError(
|
|
3666
|
+
`Gemini generation failed at ${geminiApiRoot(this.config.baseUrl)}: ${resp.status}${detail ? ` \u2014 ${detail.slice(0, 200)}` : ""}`
|
|
3667
|
+
);
|
|
3668
|
+
}
|
|
3669
|
+
return parseSseChunks(resp.body);
|
|
3670
|
+
}
|
|
3671
|
+
fetchModelList() {
|
|
3672
|
+
return fetchSameOrigin(`${geminiApiRoot(this.config.baseUrl)}/${GEMINI_API_VERSION}/models`, {
|
|
3673
|
+
headers: { "x-goog-api-key": this.config.apiKey ?? "" }
|
|
3674
|
+
});
|
|
3675
|
+
}
|
|
3247
3676
|
buildContents(messages, extraImages) {
|
|
3248
3677
|
const contents = [];
|
|
3249
3678
|
for (const m of messages) {
|
|
@@ -3328,95 +3757,17 @@ var init_gemini = __esm({
|
|
|
3328
3757
|
};
|
|
3329
3758
|
}
|
|
3330
3759
|
});
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
}
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
const
|
|
3341
|
-
|
|
3342
|
-
headers[k] = v;
|
|
3343
|
-
});
|
|
3344
|
-
else if (Array.isArray(h)) for (const [k, v] of h) headers[k] = v;
|
|
3345
|
-
else if (h) Object.assign(headers, h);
|
|
3346
|
-
if (!Object.keys(headers).some((k) => k.toLowerCase() === "accept-encoding")) {
|
|
3347
|
-
headers["accept-encoding"] = "gzip, deflate, br";
|
|
3348
|
-
}
|
|
3349
|
-
const body = init.body == null ? void 0 : typeof init.body === "string" ? init.body : Buffer.from(init.body);
|
|
3350
|
-
return new Promise((resolve2, reject) => {
|
|
3351
|
-
const req = lib.request(
|
|
3352
|
-
{
|
|
3353
|
-
hostname: u.hostname,
|
|
3354
|
-
port: u.port || (u.protocol === "https:" ? 443 : 80),
|
|
3355
|
-
path: u.pathname + u.search,
|
|
3356
|
-
method,
|
|
3357
|
-
headers
|
|
3358
|
-
},
|
|
3359
|
-
(res) => {
|
|
3360
|
-
const status = res.statusCode ?? 200;
|
|
3361
|
-
const location = res.headers.location;
|
|
3362
|
-
if (status >= 300 && status < 400 && location && redirectCount < MAX_REDIRECTS) {
|
|
3363
|
-
res.resume();
|
|
3364
|
-
const nextUrl = new URL(location, u).href;
|
|
3365
|
-
if (opts.allowedRedirectOrigin && new URL(nextUrl).origin !== opts.allowedRedirectOrigin) {
|
|
3366
|
-
reject(new Error(`Refusing cross-origin redirect to ${new URL(nextUrl).origin}`));
|
|
3367
|
-
return;
|
|
3368
|
-
}
|
|
3369
|
-
const downgrade = status === 303 || (status === 301 || status === 302) && method !== "GET" && method !== "HEAD";
|
|
3370
|
-
const nextInit = downgrade ? { ...init, method: "GET", body: void 0 } : init;
|
|
3371
|
-
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1, opts));
|
|
3372
|
-
return;
|
|
3373
|
-
}
|
|
3374
|
-
const encoding = (res.headers["content-encoding"] ?? "").toLowerCase();
|
|
3375
|
-
let bodyStream = res;
|
|
3376
|
-
if (encoding === "gzip" || encoding === "x-gzip") bodyStream = res.pipe(zlib__default.default.createGunzip());
|
|
3377
|
-
else if (encoding === "deflate") bodyStream = res.pipe(zlib__default.default.createInflate());
|
|
3378
|
-
else if (encoding === "br") bodyStream = res.pipe(zlib__default.default.createBrotliDecompress());
|
|
3379
|
-
const stream$1 = stream.Readable.toWeb(bodyStream);
|
|
3380
|
-
const respHeaders = new Headers();
|
|
3381
|
-
for (const [k, v] of Object.entries(res.headers)) {
|
|
3382
|
-
if (k === "content-encoding" || k === "content-length") continue;
|
|
3383
|
-
if (Array.isArray(v)) respHeaders.set(k, v.join(", "));
|
|
3384
|
-
else if (typeof v === "string") respHeaders.set(k, v);
|
|
3385
|
-
}
|
|
3386
|
-
resolve2(new Response(stream$1, {
|
|
3387
|
-
status,
|
|
3388
|
-
statusText: res.statusMessage ?? "",
|
|
3389
|
-
headers: respHeaders
|
|
3390
|
-
}));
|
|
3391
|
-
}
|
|
3392
|
-
);
|
|
3393
|
-
req.on("error", reject);
|
|
3394
|
-
req.setTimeout(15e3, () => req.destroy(new Error("request timed out")));
|
|
3395
|
-
if (body !== void 0) req.write(body);
|
|
3396
|
-
req.end();
|
|
3397
|
-
});
|
|
3398
|
-
}
|
|
3399
|
-
var MAX_REDIRECTS;
|
|
3400
|
-
var init_net = __esm({
|
|
3401
|
-
"src/utils/net.ts"() {
|
|
3402
|
-
try {
|
|
3403
|
-
dns__default.default.setDefaultResultOrder("ipv4first");
|
|
3404
|
-
} catch {
|
|
3405
|
-
}
|
|
3406
|
-
MAX_REDIRECTS = 5;
|
|
3407
|
-
}
|
|
3408
|
-
});
|
|
3409
|
-
|
|
3410
|
-
// src/providers/ollama.ts
|
|
3411
|
-
var ollama_exports = {};
|
|
3412
|
-
__export(ollama_exports, {
|
|
3413
|
-
OllamaProvider: () => OllamaProvider,
|
|
3414
|
-
toOllamaToolCalls: () => toOllamaToolCalls,
|
|
3415
|
-
toOllamaTools: () => toOllamaTools
|
|
3416
|
-
});
|
|
3417
|
-
function isToolCapable(modelName) {
|
|
3418
|
-
const name = modelName.toLowerCase();
|
|
3419
|
-
return TOOL_CAPABLE_FAMILIES.some((family) => name.includes(family));
|
|
3760
|
+
|
|
3761
|
+
// src/providers/ollama.ts
|
|
3762
|
+
var ollama_exports = {};
|
|
3763
|
+
__export(ollama_exports, {
|
|
3764
|
+
OllamaProvider: () => OllamaProvider,
|
|
3765
|
+
toOllamaToolCalls: () => toOllamaToolCalls,
|
|
3766
|
+
toOllamaTools: () => toOllamaTools
|
|
3767
|
+
});
|
|
3768
|
+
function isToolCapable(modelName) {
|
|
3769
|
+
const name = modelName.toLowerCase();
|
|
3770
|
+
return TOOL_CAPABLE_FAMILIES.some((family) => name.includes(family));
|
|
3420
3771
|
}
|
|
3421
3772
|
function toOllamaTools(tools) {
|
|
3422
3773
|
return tools.map((t) => ({
|
|
@@ -3694,12 +4045,38 @@ var init_openai_compatible = __esm({
|
|
|
3694
4045
|
OpenAICompatibleProvider = class extends OpenAIProvider {
|
|
3695
4046
|
constructor(config, model) {
|
|
3696
4047
|
super({ ...config, apiKey: config.apiKey ?? "not-required" }, model);
|
|
4048
|
+
if (!config.baseUrl?.trim()) {
|
|
4049
|
+
throw new Error(
|
|
4050
|
+
"An OpenAI-compatible provider requires `baseUrl`: without one the OpenAI SDK sends to api.openai.com, so a key issued by another service would go to OpenAI."
|
|
4051
|
+
);
|
|
4052
|
+
}
|
|
3697
4053
|
this.client = new OpenAI__default.default({
|
|
3698
4054
|
apiKey: config.apiKey ?? "not-required",
|
|
3699
4055
|
baseURL: preferIpv4Host(config.baseUrl),
|
|
3700
|
-
|
|
4056
|
+
// Bound to the configured origin, NOT bare `nodeHttpFetch`. That helper
|
|
4057
|
+
// follows a 3xx by reusing `init` verbatim — headers included — so an
|
|
4058
|
+
// endpoint that redirected to another host was handed
|
|
4059
|
+
// `Authorization: Bearer <apiKey>` on the second one. This is the same
|
|
4060
|
+
// leak the Anthropic paths were fixed for; `allowedRedirectOrigin` exists
|
|
4061
|
+
// precisely for callers that attach a credential, and this one attaches
|
|
4062
|
+
// it on every request.
|
|
4063
|
+
fetch: ((input, init) => nodeHttpFetch(input, init ?? {}, 0, this.redirectPolicy()))
|
|
3701
4064
|
});
|
|
3702
4065
|
}
|
|
4066
|
+
/**
|
|
4067
|
+
* Redirects may only stay on the host the credential was configured for.
|
|
4068
|
+
*
|
|
4069
|
+
* An unparseable `baseUrl` yields no policy, which is the pre-existing
|
|
4070
|
+
* behaviour — but such a config cannot address anything anyway, so the
|
|
4071
|
+
* request fails before a redirect is possible.
|
|
4072
|
+
*/
|
|
4073
|
+
redirectPolicy() {
|
|
4074
|
+
try {
|
|
4075
|
+
return { allowedRedirectOrigin: new URL(preferIpv4Host(this.config.baseUrl) ?? "").origin };
|
|
4076
|
+
} catch {
|
|
4077
|
+
return {};
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
3703
4080
|
modelsUrl() {
|
|
3704
4081
|
const base = (preferIpv4Host(this.config.baseUrl) ?? "").replace(/\/+$/, "");
|
|
3705
4082
|
return base + "/models";
|
|
@@ -3710,7 +4087,7 @@ var init_openai_compatible = __esm({
|
|
|
3710
4087
|
return h;
|
|
3711
4088
|
}
|
|
3712
4089
|
async listModels() {
|
|
3713
|
-
const res = await nodeHttpFetch(this.modelsUrl(), { headers: this.authHeaders() });
|
|
4090
|
+
const res = await nodeHttpFetch(this.modelsUrl(), { headers: this.authHeaders() }, 0, this.redirectPolicy());
|
|
3714
4091
|
if (!res.ok) throw new Error(`models endpoint ${this.modelsUrl()} returned HTTP ${res.status}`);
|
|
3715
4092
|
const body = await res.json();
|
|
3716
4093
|
const raw = Array.isArray(body?.data) ? body.data : Array.isArray(body?.models) ? body.models : [];
|
|
@@ -3740,7 +4117,7 @@ var init_openai_compatible = __esm({
|
|
|
3740
4117
|
}
|
|
3741
4118
|
async isAvailable() {
|
|
3742
4119
|
try {
|
|
3743
|
-
const res = await nodeHttpFetch(this.modelsUrl(), { headers: this.authHeaders() });
|
|
4120
|
+
const res = await nodeHttpFetch(this.modelsUrl(), { headers: this.authHeaders() }, 0, this.redirectPolicy());
|
|
3744
4121
|
return res.ok;
|
|
3745
4122
|
} catch {
|
|
3746
4123
|
return false;
|
|
@@ -3762,12 +4139,12 @@ var init_audit_logger = __esm({
|
|
|
3762
4139
|
constructor(workspacePath, debugMode = false) {
|
|
3763
4140
|
this.workspacePath = workspacePath;
|
|
3764
4141
|
this.debugMode = debugMode;
|
|
3765
|
-
const cascadeDir =
|
|
3766
|
-
if (!
|
|
3767
|
-
|
|
4142
|
+
const cascadeDir = path35__default.default.join(workspacePath, ".cascade");
|
|
4143
|
+
if (!fs32__default.default.existsSync(cascadeDir)) {
|
|
4144
|
+
fs32__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
3768
4145
|
}
|
|
3769
|
-
this.keyPath =
|
|
3770
|
-
this.dbPath =
|
|
4146
|
+
this.keyPath = path35__default.default.join(cascadeDir, "audit_log.key");
|
|
4147
|
+
this.dbPath = path35__default.default.join(cascadeDir, "audit_log.db");
|
|
3771
4148
|
this.initEncryptionKey();
|
|
3772
4149
|
this.db = new Database2__default.default(this.dbPath);
|
|
3773
4150
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -3797,11 +4174,11 @@ var init_audit_logger = __esm({
|
|
|
3797
4174
|
return crypto__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
3798
4175
|
}
|
|
3799
4176
|
initEncryptionKey() {
|
|
3800
|
-
if (
|
|
3801
|
-
this.encryptionKey =
|
|
4177
|
+
if (fs32__default.default.existsSync(this.keyPath)) {
|
|
4178
|
+
this.encryptionKey = fs32__default.default.readFileSync(this.keyPath);
|
|
3802
4179
|
} else {
|
|
3803
4180
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
3804
|
-
|
|
4181
|
+
fs32__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
3805
4182
|
}
|
|
3806
4183
|
}
|
|
3807
4184
|
encrypt(text) {
|
|
@@ -3879,9 +4256,9 @@ var init_audit_logger = __esm({
|
|
|
3879
4256
|
dumpDebugIfNeeded() {
|
|
3880
4257
|
if (!this.debugMode) return;
|
|
3881
4258
|
try {
|
|
3882
|
-
const dumpPath =
|
|
4259
|
+
const dumpPath = path35__default.default.join(os10__default.default.tmpdir(), "cascade_audit_logs_debug.json");
|
|
3883
4260
|
const entries = this.getAllLogs();
|
|
3884
|
-
|
|
4261
|
+
fs32__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
3885
4262
|
} catch (err) {
|
|
3886
4263
|
console.error("Failed to dump debug audit logs", err);
|
|
3887
4264
|
}
|
|
@@ -3936,7 +4313,7 @@ var Keystore = class {
|
|
|
3936
4313
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
3937
4314
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
3938
4315
|
this.backend = "keytar";
|
|
3939
|
-
if (password &&
|
|
4316
|
+
if (password && fs32__default.default.existsSync(this.storePath)) {
|
|
3940
4317
|
try {
|
|
3941
4318
|
const fileEntries = this.decryptFile(password);
|
|
3942
4319
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -3955,7 +4332,7 @@ var Keystore = class {
|
|
|
3955
4332
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
3956
4333
|
);
|
|
3957
4334
|
}
|
|
3958
|
-
if (!
|
|
4335
|
+
if (!fs32__default.default.existsSync(this.storePath)) {
|
|
3959
4336
|
const salt = crypto__default.default.randomBytes(SALT_LEN);
|
|
3960
4337
|
this.masterKey = this.deriveKey(password, salt);
|
|
3961
4338
|
this.writeWithSalt({}, salt);
|
|
@@ -3969,7 +4346,7 @@ var Keystore = class {
|
|
|
3969
4346
|
}
|
|
3970
4347
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
3971
4348
|
unlockSync(password) {
|
|
3972
|
-
if (!
|
|
4349
|
+
if (!fs32__default.default.existsSync(this.storePath)) {
|
|
3973
4350
|
const salt = crypto__default.default.randomBytes(SALT_LEN);
|
|
3974
4351
|
this.masterKey = this.deriveKey(password, salt);
|
|
3975
4352
|
this.writeWithSalt({}, salt);
|
|
@@ -4027,7 +4404,7 @@ var Keystore = class {
|
|
|
4027
4404
|
}
|
|
4028
4405
|
}
|
|
4029
4406
|
decryptFile(password, knownSalt) {
|
|
4030
|
-
if (!
|
|
4407
|
+
if (!fs32__default.default.existsSync(this.storePath)) return {};
|
|
4031
4408
|
try {
|
|
4032
4409
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
4033
4410
|
const useSalt = knownSalt ?? salt;
|
|
@@ -4049,8 +4426,8 @@ var Keystore = class {
|
|
|
4049
4426
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
4050
4427
|
const tag = cipher.getAuthTag();
|
|
4051
4428
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
4052
|
-
|
|
4053
|
-
|
|
4429
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(this.storePath), { recursive: true });
|
|
4430
|
+
fs32__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
4054
4431
|
}
|
|
4055
4432
|
writeWithSalt(data, salt) {
|
|
4056
4433
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -4060,11 +4437,11 @@ var Keystore = class {
|
|
|
4060
4437
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
4061
4438
|
const tag = cipher.getAuthTag();
|
|
4062
4439
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
4063
|
-
|
|
4064
|
-
|
|
4440
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(this.storePath), { recursive: true });
|
|
4441
|
+
fs32__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
4065
4442
|
}
|
|
4066
4443
|
readRaw() {
|
|
4067
|
-
const buf =
|
|
4444
|
+
const buf = fs32__default.default.readFileSync(this.storePath);
|
|
4068
4445
|
let offset = 0;
|
|
4069
4446
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
4070
4447
|
offset += SALT_LEN;
|
|
@@ -4097,7 +4474,7 @@ var CascadeIgnore = class {
|
|
|
4097
4474
|
]);
|
|
4098
4475
|
}
|
|
4099
4476
|
async load(workspacePath) {
|
|
4100
|
-
const filePath =
|
|
4477
|
+
const filePath = path35__default.default.join(workspacePath, ".cascadeignore");
|
|
4101
4478
|
try {
|
|
4102
4479
|
const content = await fs11__default.default.readFile(filePath, "utf-8");
|
|
4103
4480
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
@@ -4108,7 +4485,7 @@ var CascadeIgnore = class {
|
|
|
4108
4485
|
}
|
|
4109
4486
|
isIgnored(filePath, workspacePath) {
|
|
4110
4487
|
try {
|
|
4111
|
-
const relative = workspacePath ?
|
|
4488
|
+
const relative = workspacePath ? path35__default.default.relative(workspacePath, filePath) : filePath;
|
|
4112
4489
|
return this.ig.ignores(relative);
|
|
4113
4490
|
} catch {
|
|
4114
4491
|
return false;
|
|
@@ -4119,7 +4496,7 @@ var CascadeIgnore = class {
|
|
|
4119
4496
|
}
|
|
4120
4497
|
};
|
|
4121
4498
|
async function createDefaultIgnoreFile(workspacePath) {
|
|
4122
|
-
const filePath =
|
|
4499
|
+
const filePath = path35__default.default.join(workspacePath, ".cascadeignore");
|
|
4123
4500
|
const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
|
|
4124
4501
|
# Syntax identical to .gitignore
|
|
4125
4502
|
|
|
@@ -4149,7 +4526,7 @@ Thumbs.db
|
|
|
4149
4526
|
await fs11__default.default.writeFile(filePath, content, "utf-8");
|
|
4150
4527
|
}
|
|
4151
4528
|
async function loadCascadeMd(workspacePath) {
|
|
4152
|
-
const filePath =
|
|
4529
|
+
const filePath = path35__default.default.join(workspacePath, "CASCADE.md");
|
|
4153
4530
|
try {
|
|
4154
4531
|
const raw = await fs11__default.default.readFile(filePath, "utf-8");
|
|
4155
4532
|
return parseCascadeMd(raw);
|
|
@@ -4178,7 +4555,7 @@ ${raw.trim()}`;
|
|
|
4178
4555
|
return { raw, sections, systemPrompt };
|
|
4179
4556
|
}
|
|
4180
4557
|
async function createDefaultCascadeMd(workspacePath) {
|
|
4181
|
-
const filePath =
|
|
4558
|
+
const filePath = path35__default.default.join(workspacePath, "CASCADE.md");
|
|
4182
4559
|
const content = `# Cascade Project Instructions
|
|
4183
4560
|
|
|
4184
4561
|
This file contains project-specific instructions for the Cascade AI agent.
|
|
@@ -4213,7 +4590,7 @@ List any areas the agent should not touch.
|
|
|
4213
4590
|
var MemoryStore = class _MemoryStore {
|
|
4214
4591
|
db;
|
|
4215
4592
|
constructor(dbPath) {
|
|
4216
|
-
|
|
4593
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(dbPath), { recursive: true });
|
|
4217
4594
|
try {
|
|
4218
4595
|
this.db = new Database2__default.default(dbPath, { timeout: 5e3 });
|
|
4219
4596
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -5477,21 +5854,80 @@ function validateConfig(raw) {
|
|
|
5477
5854
|
|
|
5478
5855
|
// src/config/global-credentials.ts
|
|
5479
5856
|
init_constants();
|
|
5857
|
+
|
|
5858
|
+
// src/config/azure-endpoint.ts
|
|
5859
|
+
init_net();
|
|
5860
|
+
function normalizeAzureEndpoint(url) {
|
|
5861
|
+
return normalizeEndpoint(url);
|
|
5862
|
+
}
|
|
5863
|
+
function sameAzureEndpoint(a, b) {
|
|
5864
|
+
return normalizeAzureEndpoint(a) === normalizeAzureEndpoint(b);
|
|
5865
|
+
}
|
|
5866
|
+
|
|
5867
|
+
// src/config/endpoint-identity.ts
|
|
5868
|
+
init_net();
|
|
5869
|
+
var DEFAULT_ENDPOINT = {
|
|
5870
|
+
anthropic: "https://api.anthropic.com",
|
|
5871
|
+
openai: "https://api.openai.com/v1",
|
|
5872
|
+
gemini: "https://generativelanguage.googleapis.com"
|
|
5873
|
+
};
|
|
5874
|
+
function hasDefaultEndpoint(type) {
|
|
5875
|
+
return Object.hasOwn(DEFAULT_ENDPOINT, type);
|
|
5876
|
+
}
|
|
5877
|
+
function credentialEndpointIdentity(type, baseUrl) {
|
|
5878
|
+
const configured = baseUrl?.trim();
|
|
5879
|
+
const effective = configured || DEFAULT_ENDPOINT[type];
|
|
5880
|
+
if (!effective) return null;
|
|
5881
|
+
return normalizeEndpoint(clientApiRoot(type, effective));
|
|
5882
|
+
}
|
|
5883
|
+
function sameCredentialEndpoint(type, a, b) {
|
|
5884
|
+
const left = credentialEndpointIdentity(type, a);
|
|
5885
|
+
const right = credentialEndpointIdentity(type, b);
|
|
5886
|
+
if (left === null && right === null) return true;
|
|
5887
|
+
if (left === null || right === null) return false;
|
|
5888
|
+
return left === right;
|
|
5889
|
+
}
|
|
5890
|
+
function credentialEndpointsConflict(type, rowEndpoint, credentialEndpoint) {
|
|
5891
|
+
const row = rowEndpoint?.trim() ? credentialEndpointIdentity(type, rowEndpoint) : null;
|
|
5892
|
+
const credential = credentialEndpointIdentity(type, credentialEndpoint);
|
|
5893
|
+
if (row === null) return false;
|
|
5894
|
+
if (credential === null) return true;
|
|
5895
|
+
return row !== credential;
|
|
5896
|
+
}
|
|
5897
|
+
|
|
5898
|
+
// src/config/global-credentials.ts
|
|
5480
5899
|
function credentialsPath(globalDir2) {
|
|
5481
|
-
return
|
|
5900
|
+
return path35__default.default.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
5482
5901
|
}
|
|
5483
5902
|
function providerKey(p) {
|
|
5484
|
-
if (p.type === "azure") {
|
|
5485
|
-
return `azure:${p.deploymentName ?? p.baseUrl ?? p.label ?? ""}`;
|
|
5486
|
-
}
|
|
5487
5903
|
return p.type;
|
|
5488
5904
|
}
|
|
5905
|
+
function azureEndpoint(p) {
|
|
5906
|
+
return p.baseUrl === void 0 ? void 0 : normalizeAzureEndpoint(p.baseUrl);
|
|
5907
|
+
}
|
|
5908
|
+
function sameAzureEntry(a, b) {
|
|
5909
|
+
const strong = [
|
|
5910
|
+
[a.deploymentName, b.deploymentName],
|
|
5911
|
+
[azureEndpoint(a), azureEndpoint(b)]
|
|
5912
|
+
];
|
|
5913
|
+
let shared = 0;
|
|
5914
|
+
for (const [x, y] of strong) {
|
|
5915
|
+
if (!x || !y) continue;
|
|
5916
|
+
if (x !== y) return false;
|
|
5917
|
+
shared++;
|
|
5918
|
+
}
|
|
5919
|
+
if (shared > 0) return true;
|
|
5920
|
+
const aNamesSomething = Boolean(a.deploymentName || azureEndpoint(a));
|
|
5921
|
+
const bNamesSomething = Boolean(b.deploymentName || azureEndpoint(b));
|
|
5922
|
+
if (aNamesSomething || bNamesSomething) return false;
|
|
5923
|
+
return Boolean(a.label && b.label && a.label === b.label);
|
|
5924
|
+
}
|
|
5489
5925
|
function isPersistable(p) {
|
|
5490
5926
|
return Boolean(p.apiKey || p.authToken || p.type === "azure" || p.baseUrl);
|
|
5491
5927
|
}
|
|
5492
5928
|
function loadGlobalCredentials(globalDir2) {
|
|
5493
5929
|
try {
|
|
5494
|
-
const raw =
|
|
5930
|
+
const raw = fs32__default.default.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
5495
5931
|
const parsed = JSON.parse(raw);
|
|
5496
5932
|
if (!Array.isArray(parsed.providers)) return [];
|
|
5497
5933
|
return parsed.providers.filter(
|
|
@@ -5503,34 +5939,93 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
5503
5939
|
}
|
|
5504
5940
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
5505
5941
|
const filePath = credentialsPath(globalDir2);
|
|
5506
|
-
|
|
5942
|
+
fs32__default.default.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
5507
5943
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
5508
5944
|
const tmp = `${filePath}.tmp`;
|
|
5509
|
-
|
|
5510
|
-
|
|
5945
|
+
fs32__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
5946
|
+
fs32__default.default.renameSync(tmp, filePath);
|
|
5511
5947
|
try {
|
|
5512
|
-
|
|
5948
|
+
fs32__default.default.chmodSync(filePath, 384);
|
|
5513
5949
|
} catch {
|
|
5514
5950
|
}
|
|
5515
5951
|
}
|
|
5516
5952
|
function mergeGlobalCredentials(workspaceProviders, globalProviders) {
|
|
5517
5953
|
const merged = [...workspaceProviders];
|
|
5518
|
-
const byKey = new Map(merged.map((p) => [providerKey(p), p]));
|
|
5954
|
+
const byKey = new Map(merged.filter((p) => p.type !== "azure").map((p) => [providerKey(p), p]));
|
|
5519
5955
|
for (const g of globalProviders) {
|
|
5520
|
-
|
|
5956
|
+
if (g.type === "azure" && !g.deploymentName?.trim() && azureEndpoint(g)) {
|
|
5957
|
+
const onResource = merged.filter((p) => p.type === "azure" && sameAzureEntry(p, g));
|
|
5958
|
+
if (onResource.length > 0) {
|
|
5959
|
+
for (const row of onResource) fillFrom(row, g);
|
|
5960
|
+
continue;
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
const existing = g.type === "azure" ? merged.find((p) => p.type === "azure" && sameAzureEntry(p, g)) : byKey.get(providerKey(g));
|
|
5521
5964
|
if (!existing) {
|
|
5522
5965
|
merged.push({ ...g });
|
|
5523
|
-
byKey.set(providerKey(g), merged[merged.length - 1]);
|
|
5966
|
+
if (g.type !== "azure") byKey.set(providerKey(g), merged[merged.length - 1]);
|
|
5524
5967
|
continue;
|
|
5525
5968
|
}
|
|
5526
|
-
|
|
5527
|
-
if (!existing.authToken && g.authToken) existing.authToken = g.authToken;
|
|
5528
|
-
if (!existing.baseUrl && g.baseUrl) existing.baseUrl = g.baseUrl;
|
|
5529
|
-
if (!existing.apiVersion && g.apiVersion) existing.apiVersion = g.apiVersion;
|
|
5530
|
-
if (!existing.label && g.label) existing.label = g.label;
|
|
5969
|
+
fillFrom(existing, g);
|
|
5531
5970
|
}
|
|
5532
5971
|
return merged;
|
|
5533
5972
|
}
|
|
5973
|
+
function fillFrom(existing, g) {
|
|
5974
|
+
const credentialled = Boolean(existing.apiKey || existing.authToken);
|
|
5975
|
+
const endpointsDiffer = credentialEndpointsConflict(existing.type, existing.baseUrl, g.baseUrl);
|
|
5976
|
+
if (!credentialled && !endpointsDiffer && g.apiKey) {
|
|
5977
|
+
existing.apiKey = g.apiKey;
|
|
5978
|
+
if (g.baseUrl) existing.baseUrl = g.baseUrl;
|
|
5979
|
+
}
|
|
5980
|
+
if (!credentialled && !endpointsDiffer && !existing.apiKey && g.authToken && g.baseUrl) {
|
|
5981
|
+
existing.authToken = g.authToken;
|
|
5982
|
+
existing.baseUrl = g.baseUrl;
|
|
5983
|
+
}
|
|
5984
|
+
if (!credentialled && !existing.baseUrl && g.baseUrl) existing.baseUrl = g.baseUrl;
|
|
5985
|
+
if (!existing.apiVersion && g.apiVersion) existing.apiVersion = g.apiVersion;
|
|
5986
|
+
if (!existing.label && g.label) existing.label = g.label;
|
|
5987
|
+
}
|
|
5988
|
+
|
|
5989
|
+
// src/config/azure-routing.ts
|
|
5990
|
+
function routableAzureRows(rows) {
|
|
5991
|
+
return rows.filter((p) => p.type === "azure" && p.baseUrl?.trim());
|
|
5992
|
+
}
|
|
5993
|
+
function resolveAzureRouting(view, write, env) {
|
|
5994
|
+
const rows = routableAzureRows(view);
|
|
5995
|
+
const resourcesOf = (list) => [...new Set(list.map((p) => normalizeAzureEndpoint(p.baseUrl)))];
|
|
5996
|
+
if (rows.length === 0) return { ok: false, reason: "no-routable-rows", resources: [] };
|
|
5997
|
+
const endpoint = env.endpoint?.trim();
|
|
5998
|
+
const deployment = env.deployment?.trim();
|
|
5999
|
+
const named = deployment ? rows.filter((p) => (p.deploymentName?.trim() ?? "") === deployment) : [];
|
|
6000
|
+
let anchor;
|
|
6001
|
+
if (endpoint) {
|
|
6002
|
+
anchor = rows.find((p) => sameAzureEndpoint(p.baseUrl, endpoint));
|
|
6003
|
+
if (!anchor) {
|
|
6004
|
+
return { ok: false, reason: "endpoint-not-configured", resources: resourcesOf(rows) };
|
|
6005
|
+
}
|
|
6006
|
+
const elsewhere = named.filter((p) => !sameAzureEndpoint(p.baseUrl, endpoint));
|
|
6007
|
+
if (elsewhere.length > 0) {
|
|
6008
|
+
return { ok: false, reason: "name-on-another-resource", resources: resourcesOf(elsewhere) };
|
|
6009
|
+
}
|
|
6010
|
+
} else if (named.length > 0) {
|
|
6011
|
+
const owners = resourcesOf(named);
|
|
6012
|
+
if (owners.length > 1) return { ok: false, reason: "ambiguous-resource", resources: owners };
|
|
6013
|
+
anchor = named[0];
|
|
6014
|
+
} else {
|
|
6015
|
+
const all = resourcesOf(rows);
|
|
6016
|
+
if (all.length !== 1) return { ok: false, reason: "ambiguous-resource", resources: all };
|
|
6017
|
+
anchor = rows[0];
|
|
6018
|
+
}
|
|
6019
|
+
const resource = anchor.baseUrl;
|
|
6020
|
+
const onResource = routableAzureRows(write).filter((p) => sameAzureEndpoint(p.baseUrl, resource));
|
|
6021
|
+
const alreadyThere = deployment ? onResource.some((p) => (p.deploymentName?.trim() ?? "") === deployment) || named.some((p) => sameAzureEndpoint(p.baseUrl, resource)) : true;
|
|
6022
|
+
return {
|
|
6023
|
+
ok: true,
|
|
6024
|
+
resource,
|
|
6025
|
+
rows: onResource,
|
|
6026
|
+
...deployment && !alreadyThere ? { createDeployment: deployment } : {}
|
|
6027
|
+
};
|
|
6028
|
+
}
|
|
5534
6029
|
|
|
5535
6030
|
// src/config/retired-providers.ts
|
|
5536
6031
|
var RETIRED_PROVIDER_TYPES = {
|
|
@@ -5540,7 +6035,7 @@ function isRetiredProviderType(type) {
|
|
|
5540
6035
|
return typeof type === "string" && Object.hasOwn(RETIRED_PROVIDER_TYPES, type);
|
|
5541
6036
|
}
|
|
5542
6037
|
function didCleanupChangeAnything(c) {
|
|
5543
|
-
return c.removed.length > 0 || c.clearedPins.length > 0;
|
|
6038
|
+
return c.removed.length > 0 || c.clearedPins.length > 0 || (c.revokedCredentials ?? 0) > 0 || (c.unusableCredentials ?? 0) > 0;
|
|
5544
6039
|
}
|
|
5545
6040
|
function stripRetiredProviders(raw) {
|
|
5546
6041
|
const cleanup = { removed: [], clearedPins: [] };
|
|
@@ -5587,11 +6082,24 @@ function describeCleanup(c) {
|
|
|
5587
6082
|
for (const type of c.removed) {
|
|
5588
6083
|
parts.push(`removed the retired "${type}" provider (${RETIRED_PROVIDER_TYPES[type]})`);
|
|
5589
6084
|
}
|
|
6085
|
+
if ((c.revokedCredentials ?? 0) > 0) {
|
|
6086
|
+
parts.push("discarded a linked Claude subscription token, which Anthropic no longer permits third-party tools to use");
|
|
6087
|
+
}
|
|
6088
|
+
const unusable = c.unusableCredentials ?? 0;
|
|
6089
|
+
if (unusable > 0) {
|
|
6090
|
+
parts.push(
|
|
6091
|
+
`discarded ${unusable} synced gateway token${unusable === 1 ? "" : "s"} that named no gateway URL \u2014 a bearer is only valid at the gateway that issued it`
|
|
6092
|
+
);
|
|
6093
|
+
}
|
|
5590
6094
|
if (c.clearedPins.length > 0) {
|
|
5591
6095
|
parts.push(`reset ${c.clearedPins.map((t) => t.toUpperCase()).join("/")} to Auto, since the pin named it`);
|
|
5592
6096
|
}
|
|
6097
|
+
if (parts.length === 0) return "";
|
|
5593
6098
|
return `Cascade config migration: ${parts.join("; ")}.`;
|
|
5594
6099
|
}
|
|
6100
|
+
|
|
6101
|
+
// src/config/index.ts
|
|
6102
|
+
init_revoked_credentials();
|
|
5595
6103
|
var MCP_TOOL_PREFIX = "mcp__";
|
|
5596
6104
|
var MAX_SEGMENT_LENGTH = 24;
|
|
5597
6105
|
var SEGMENT_HASH_LENGTH = 8;
|
|
@@ -5773,10 +6281,31 @@ function removeMcpServerDenials(disabledTools, serverName) {
|
|
|
5773
6281
|
// src/config/index.ts
|
|
5774
6282
|
init_constants();
|
|
5775
6283
|
var KEY_OPTIONAL_PROVIDER_TYPES = /* @__PURE__ */ new Set(["ollama", "openai-compatible"]);
|
|
6284
|
+
function hasProviderCredential(p) {
|
|
6285
|
+
if (!p) return false;
|
|
6286
|
+
if (typeof p.apiKey === "string" && p.apiKey.length > 0) return true;
|
|
6287
|
+
return typeof p.authToken === "string" && p.authToken.length > 0 && typeof p.baseUrl === "string" && p.baseUrl.length > 0;
|
|
6288
|
+
}
|
|
6289
|
+
function isRoutable(p) {
|
|
6290
|
+
if (p.type === "openai-compatible") return Boolean(p.baseUrl?.trim());
|
|
6291
|
+
if (p.type === "azure") return Boolean(p.baseUrl?.trim() && p.deploymentName?.trim());
|
|
6292
|
+
return true;
|
|
6293
|
+
}
|
|
5776
6294
|
function hasUsableProvider(providers) {
|
|
5777
6295
|
if (!providers?.length) return false;
|
|
5778
|
-
return providers.some((p) => KEY_OPTIONAL_PROVIDER_TYPES.has(p.type) ||
|
|
6296
|
+
return providers.some((p) => isRoutable(p) && (KEY_OPTIONAL_PROVIDER_TYPES.has(p.type) || hasProviderCredential(p)));
|
|
6297
|
+
}
|
|
6298
|
+
function applyProviderApiKey(providers, type, apiKey, extra = {}) {
|
|
6299
|
+
const existing = providers.find((p) => p.type === type);
|
|
6300
|
+
if (existing) {
|
|
6301
|
+
existing.apiKey = apiKey;
|
|
6302
|
+
existing.authToken = void 0;
|
|
6303
|
+
if (extra.baseUrl) existing.baseUrl = extra.baseUrl;
|
|
6304
|
+
return;
|
|
6305
|
+
}
|
|
6306
|
+
providers.push({ type, apiKey, ...extra.baseUrl ? { baseUrl: extra.baseUrl } : {} });
|
|
5779
6307
|
}
|
|
6308
|
+
var ENV_ENDPOINT_CHANNEL = /* @__PURE__ */ new Set(["anthropic"]);
|
|
5780
6309
|
var ConfigManager = class {
|
|
5781
6310
|
config;
|
|
5782
6311
|
keystore;
|
|
@@ -5796,6 +6325,16 @@ var ConfigManager = class {
|
|
|
5796
6325
|
* fallback for a list that is empty for some unrelated reason.
|
|
5797
6326
|
*/
|
|
5798
6327
|
retiredCleanup;
|
|
6328
|
+
/**
|
|
6329
|
+
* How many dead subscription credentials THIS load removed. Per-load like
|
|
6330
|
+
* `retiredCleanup`, for the same reason: a second load() on the same instance
|
|
6331
|
+
* must not re-announce a migration that already happened.
|
|
6332
|
+
*/
|
|
6333
|
+
revokedCredentials = 0;
|
|
6334
|
+
/** Tier pins cleared by the revoked-credential migration this load. */
|
|
6335
|
+
revokedPins = [];
|
|
6336
|
+
/** This load's revoked-credential explanation, joined with any retirement one. */
|
|
6337
|
+
revokedNotice;
|
|
5799
6338
|
/**
|
|
5800
6339
|
* Human-readable migration notice held for a UI to display. `console.warn`
|
|
5801
6340
|
* is not sufficient: the REPL clears the TTY immediately after load(), and
|
|
@@ -5811,7 +6350,7 @@ var ConfigManager = class {
|
|
|
5811
6350
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
5812
6351
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
5813
6352
|
this.workspacePath = workspacePath;
|
|
5814
|
-
this.globalDir = globalDirOverride ??
|
|
6353
|
+
this.globalDir = globalDirOverride ?? path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
5815
6354
|
}
|
|
5816
6355
|
async load() {
|
|
5817
6356
|
this.config = await this.loadConfig();
|
|
@@ -5825,22 +6364,38 @@ var ConfigManager = class {
|
|
|
5825
6364
|
this.ignore = new CascadeIgnore();
|
|
5826
6365
|
await this.ignore.load(this.workspacePath);
|
|
5827
6366
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
5828
|
-
this.keystore = new Keystore(
|
|
5829
|
-
this.store = new MemoryStore(
|
|
5830
|
-
const
|
|
5831
|
-
|
|
6367
|
+
this.keystore = new Keystore(path35__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
6368
|
+
this.store = new MemoryStore(path35__default.default.join(this.workspacePath, CASCADE_DB_FILE));
|
|
6369
|
+
const globalRevoked = stripRevokedCredentials(loadGlobalCredentials(this.globalDir));
|
|
6370
|
+
const globalCreds = filterRetiredCredentials(globalRevoked.kept);
|
|
6371
|
+
if (globalRevoked.removed > 0) this.revokedCredentials += globalRevoked.removed;
|
|
6372
|
+
if (globalCreds.removed.length > 0 || globalRevoked.removed > 0) {
|
|
6373
|
+
const retiredHere = globalCreds.removed.length > 0;
|
|
5832
6374
|
try {
|
|
5833
6375
|
saveGlobalCredentials(this.globalDir, globalCreds.kept);
|
|
5834
6376
|
} catch (err) {
|
|
5835
6377
|
console.warn(`Could not rewrite the global credential store: ${err instanceof Error ? err.message : String(err)}`);
|
|
5836
6378
|
}
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
6379
|
+
if (retiredHere) {
|
|
6380
|
+
this.retiredCleanup = {
|
|
6381
|
+
removed: [.../* @__PURE__ */ new Set([...this.retiredCleanup?.removed ?? [], ...globalCreds.removed])],
|
|
6382
|
+
clearedPins: this.retiredCleanup?.clearedPins ?? []
|
|
6383
|
+
};
|
|
6384
|
+
}
|
|
5841
6385
|
}
|
|
5842
|
-
await this.injectEnvKeys();
|
|
6386
|
+
await this.injectEnvKeys(globalCreds.kept);
|
|
5843
6387
|
this.config.providers = mergeGlobalCredentials(this.config.providers, globalCreds.kept);
|
|
6388
|
+
if (this.revokedCredentials > 0 && !hasUsableAnthropic(this.config.providers)) {
|
|
6389
|
+
this.revokedPins = clearAnthropicPins(this.config.models, this.config.providers);
|
|
6390
|
+
if (this.revokedPins.length > 0) {
|
|
6391
|
+
await this.persistClearedPins(this.revokedPins.map((p) => p.tier));
|
|
6392
|
+
}
|
|
6393
|
+
}
|
|
6394
|
+
if (this.revokedCredentials > 0) {
|
|
6395
|
+
const pins = this.revokedPins.length ? ` Cleared the ${this.revokedPins.map((p) => `${p.tier.toUpperCase()} pin (${p.model})`).join(" and ")}, since it named Anthropic.` : "";
|
|
6396
|
+
this.revokedNotice = `Cascade config migration: ${REVOKED_CREDENTIAL_REASON}${pins}`;
|
|
6397
|
+
console.warn(this.revokedNotice);
|
|
6398
|
+
}
|
|
5844
6399
|
for (const type of Object.keys(RETIRED_PROVIDER_TYPES)) {
|
|
5845
6400
|
try {
|
|
5846
6401
|
this.store.purgeCachedModelsForRetiredProvider(type);
|
|
@@ -5849,10 +6404,14 @@ var ConfigManager = class {
|
|
|
5849
6404
|
}
|
|
5850
6405
|
await this.ensureDefaultIdentity();
|
|
5851
6406
|
if (mcpNamesChanged) await this.save();
|
|
6407
|
+
const notices = [];
|
|
5852
6408
|
if (this.retiredCleanup) {
|
|
5853
|
-
|
|
5854
|
-
|
|
6409
|
+
const retired = describeCleanup(this.retiredCleanup);
|
|
6410
|
+
console.warn(retired);
|
|
6411
|
+
notices.push(retired);
|
|
5855
6412
|
}
|
|
6413
|
+
if (this.revokedNotice) notices.push(this.revokedNotice);
|
|
6414
|
+
if (notices.length > 0) this.pendingRetiredNotice = notices.join(" ");
|
|
5856
6415
|
}
|
|
5857
6416
|
getConfig() {
|
|
5858
6417
|
return this.config;
|
|
@@ -5872,12 +6431,18 @@ var ConfigManager = class {
|
|
|
5872
6431
|
getWorkspacePath() {
|
|
5873
6432
|
return this.workspacePath;
|
|
5874
6433
|
}
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
6434
|
+
/**
|
|
6435
|
+
* @param config What to persist. Defaults to the manager's own, but a
|
|
6436
|
+
* transactional save hands in the validated copy it has NOT adopted yet —
|
|
6437
|
+
* writing before adopting is what stops a run picking up configuration that
|
|
6438
|
+
* never reached the disk (see `commitSettings`).
|
|
6439
|
+
*/
|
|
6440
|
+
async save(config = this.config) {
|
|
6441
|
+
const configPath = path35__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
6442
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(configPath), { recursive: true });
|
|
6443
|
+
await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
5879
6444
|
try {
|
|
5880
|
-
saveGlobalCredentials(this.globalDir,
|
|
6445
|
+
saveGlobalCredentials(this.globalDir, config.providers);
|
|
5881
6446
|
} catch (err) {
|
|
5882
6447
|
console.warn(`Failed to sync credentials to global store: ${err instanceof Error ? err.message : String(err)}`);
|
|
5883
6448
|
}
|
|
@@ -5908,19 +6473,35 @@ var ConfigManager = class {
|
|
|
5908
6473
|
const configProvider = this.config.providers.find((p) => p.type === provider);
|
|
5909
6474
|
return configProvider?.apiKey;
|
|
5910
6475
|
}
|
|
6476
|
+
/**
|
|
6477
|
+
* The bearer token configured for a provider, if any.
|
|
6478
|
+
*
|
|
6479
|
+
* Companion to getApiKey(). A provider can be fully configured with only
|
|
6480
|
+
* this — `cascade link` and ANTHROPIC_AUTH_TOKEN both produce it — so any
|
|
6481
|
+
* surface that asks "is this provider set up" has to consult both, or it
|
|
6482
|
+
* reports a working install as unconfigured.
|
|
6483
|
+
*/
|
|
6484
|
+
getAuthToken(provider) {
|
|
6485
|
+
return this.config.providers.find((p) => p.type === provider)?.authToken;
|
|
6486
|
+
}
|
|
5911
6487
|
async loadConfig() {
|
|
5912
6488
|
this.retiredCleanup = void 0;
|
|
5913
|
-
|
|
6489
|
+
this.revokedCredentials = 0;
|
|
6490
|
+
this.revokedPins = [];
|
|
6491
|
+
this.revokedNotice = void 0;
|
|
6492
|
+
const configPath = path35__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
5914
6493
|
try {
|
|
5915
6494
|
const raw = await fs11__default.default.readFile(configPath, "utf-8");
|
|
5916
6495
|
const parsed = JSON.parse(raw);
|
|
5917
6496
|
const cleanup = stripRetiredProviders(parsed);
|
|
5918
|
-
|
|
5919
|
-
|
|
6497
|
+
const revokedHere = stripRevokedFromConfig(parsed).removed;
|
|
6498
|
+
if (revokedHere > 0) this.revokedCredentials += revokedHere;
|
|
6499
|
+
if (didCleanupChangeAnything(cleanup) || revokedHere > 0) {
|
|
6500
|
+
if (didCleanupChangeAnything(cleanup)) this.retiredCleanup = cleanup;
|
|
5920
6501
|
try {
|
|
5921
6502
|
await fs11__default.default.writeFile(configPath, JSON.stringify(parsed, null, 2), "utf-8");
|
|
5922
6503
|
} catch (err) {
|
|
5923
|
-
console.warn(`Could not persist the
|
|
6504
|
+
console.warn(`Could not persist the config migration: ${err instanceof Error ? err.message : String(err)}`);
|
|
5924
6505
|
}
|
|
5925
6506
|
}
|
|
5926
6507
|
return validateConfig(parsed);
|
|
@@ -5931,9 +6512,102 @@ var ConfigManager = class {
|
|
|
5931
6512
|
throw err;
|
|
5932
6513
|
}
|
|
5933
6514
|
}
|
|
5934
|
-
|
|
6515
|
+
/**
|
|
6516
|
+
* Rewrite the workspace file with those tier pins removed.
|
|
6517
|
+
*
|
|
6518
|
+
* A targeted edit of the RAW file, not save(). By this point `this.config`
|
|
6519
|
+
* carries environment keys and machine-global credentials, and save()
|
|
6520
|
+
* serializes the whole object — so persisting through it would copy secrets
|
|
6521
|
+
* kept 0600 in ~/.cascade-ai into a workspace file that may be 0644. Same
|
|
6522
|
+
* reasoning as the migration write in loadConfig(); best-effort for the same
|
|
6523
|
+
* reason, since a read-only config directory must not abort startup.
|
|
6524
|
+
*/
|
|
6525
|
+
/**
|
|
6526
|
+
* The Azure entries `AZURE_OPENAI_KEY` belongs to — possibly several.
|
|
6527
|
+
*
|
|
6528
|
+
* `AZURE_OPENAI_ENDPOINT` names the resource. Without one, a single
|
|
6529
|
+
* configured resource is unambiguous and anything more is a guess — and
|
|
6530
|
+
* guessing wrong writes a key to a resource that will reject it.
|
|
6531
|
+
*
|
|
6532
|
+
* ALL the deployments on that resource, not the first. An Azure key is
|
|
6533
|
+
* resource-scoped, so every deployment there shares it, and Azure is
|
|
6534
|
+
* configured one entry per deployment — the router binds each model to its
|
|
6535
|
+
* own row, matching `deploymentName` against the model id, so filling only
|
|
6536
|
+
* the first left the rest issuing requests with no key at all.
|
|
6537
|
+
*/
|
|
6538
|
+
azureEntriesForEnv(globalProviders = []) {
|
|
6539
|
+
const workspace = this.config.providers;
|
|
6540
|
+
const routing = resolveAzureRouting(
|
|
6541
|
+
[...workspace, ...globalProviders],
|
|
6542
|
+
workspace,
|
|
6543
|
+
{
|
|
6544
|
+
endpoint: process.env["AZURE_OPENAI_ENDPOINT"],
|
|
6545
|
+
deployment: process.env["AZURE_OPENAI_DEPLOYMENT"] ?? process.env["AZURE_OPENAI_DEPLOYMENT_NAME"]
|
|
6546
|
+
}
|
|
6547
|
+
);
|
|
6548
|
+
if (!routing.ok) return [];
|
|
6549
|
+
const onResource = [...routing.rows];
|
|
6550
|
+
const claimEndpointlessRow = (name) => {
|
|
6551
|
+
const row = this.config.providers.find((p) => p.type === "azure" && !p.baseUrl?.trim() && (p.deploymentName?.trim() ?? "") === name);
|
|
6552
|
+
if (!row) return void 0;
|
|
6553
|
+
if (row.apiKey || row.authToken) {
|
|
6554
|
+
console.warn(
|
|
6555
|
+
`Ignoring the stored key on Azure deployment "${name}": it was saved without an endpoint, so there is nothing to show it belongs to ${routing.resource}. AZURE_OPENAI_KEY is being used instead.`
|
|
6556
|
+
);
|
|
6557
|
+
row.apiKey = void 0;
|
|
6558
|
+
row.authToken = void 0;
|
|
6559
|
+
}
|
|
6560
|
+
row.baseUrl = routing.resource;
|
|
6561
|
+
onResource.push(row);
|
|
6562
|
+
return row;
|
|
6563
|
+
};
|
|
6564
|
+
for (const g of globalProviders) {
|
|
6565
|
+
if (g.type !== "azure" || !g.baseUrl?.trim()) continue;
|
|
6566
|
+
if (!sameAzureEndpoint(g.baseUrl, routing.resource)) continue;
|
|
6567
|
+
const name = g.deploymentName?.trim() ?? "";
|
|
6568
|
+
if (onResource.some((p) => (p.deploymentName?.trim() ?? "") === name)) continue;
|
|
6569
|
+
if (claimEndpointlessRow(name)) continue;
|
|
6570
|
+
const row = { ...g, apiKey: void 0, authToken: void 0 };
|
|
6571
|
+
this.config.providers.push(row);
|
|
6572
|
+
onResource.push(row);
|
|
6573
|
+
}
|
|
6574
|
+
if (routing.createDeployment && !claimEndpointlessRow(routing.createDeployment)) {
|
|
6575
|
+
const row = {
|
|
6576
|
+
type: "azure",
|
|
6577
|
+
deploymentName: routing.createDeployment,
|
|
6578
|
+
baseUrl: routing.resource
|
|
6579
|
+
};
|
|
6580
|
+
this.config.providers.push(row);
|
|
6581
|
+
onResource.push(row);
|
|
6582
|
+
}
|
|
6583
|
+
return onResource;
|
|
6584
|
+
}
|
|
6585
|
+
async persistClearedPins(tiers) {
|
|
6586
|
+
const configPath = path35__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
6587
|
+
try {
|
|
6588
|
+
const raw = JSON.parse(await fs11__default.default.readFile(configPath, "utf-8"));
|
|
6589
|
+
const models = raw["models"];
|
|
6590
|
+
if (typeof models !== "object" || models === null) return;
|
|
6591
|
+
for (const tier of tiers) delete models[tier];
|
|
6592
|
+
await fs11__default.default.writeFile(configPath, JSON.stringify(raw, null, 2), "utf-8");
|
|
6593
|
+
} catch (err) {
|
|
6594
|
+
if (err.code === "ENOENT") return;
|
|
6595
|
+
console.warn(`Could not persist the cleared model pin: ${err instanceof Error ? err.message : String(err)}`);
|
|
6596
|
+
}
|
|
6597
|
+
}
|
|
6598
|
+
/**
|
|
6599
|
+
* `globalProviders` is the machine-global credential store, passed in
|
|
6600
|
+
* READ-ONLY. This runs before mergeGlobalCredentials() on purpose — an
|
|
6601
|
+
* exported key should outrank a stored one, which only holds while the
|
|
6602
|
+
* injection goes first — but a few of the questions asked here are about the
|
|
6603
|
+
* user's whole configuration rather than this workspace's file, and answering
|
|
6604
|
+
* those from the workspace alone is what produced the gateway bug this
|
|
6605
|
+
* argument fixes.
|
|
6606
|
+
*/
|
|
6607
|
+
async injectEnvKeys(globalProviders = []) {
|
|
5935
6608
|
const wasEmpty = this.config.providers.length === 0;
|
|
5936
|
-
const emptiedByRetirement = wasEmpty && !!this.retiredCleanup;
|
|
6609
|
+
const emptiedByRetirement = wasEmpty && (!!this.retiredCleanup || this.revokedCredentials > 0);
|
|
6610
|
+
const mayCreate = (type) => wasEmpty || type === "anthropic" && this.revokedCredentials > 0;
|
|
5937
6611
|
const envProviders = [
|
|
5938
6612
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
5939
6613
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
@@ -5943,11 +6617,66 @@ var ConfigManager = class {
|
|
|
5943
6617
|
for (const { env, type } of envProviders) {
|
|
5944
6618
|
const key = process.env[env];
|
|
5945
6619
|
if (!key) continue;
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
6620
|
+
if (isSubscriptionToken(key)) continue;
|
|
6621
|
+
const targets = type === "azure" ? this.azureEntriesForEnv(globalProviders) : [this.config.providers.find((p) => p.type === type)].filter((p) => !!p);
|
|
6622
|
+
const existing = targets[0];
|
|
6623
|
+
const globalMatch = type === "azure" ? void 0 : globalProviders.find((p) => p.type === type);
|
|
6624
|
+
const anthropicGateway = type === "anthropic" ? process.env["ANTHROPIC_BASE_URL"] : void 0;
|
|
6625
|
+
if (!existing && (mayCreate(type) || globalMatch)) {
|
|
6626
|
+
if (type === "azure") {
|
|
6627
|
+
const endpoint = process.env["AZURE_OPENAI_ENDPOINT"];
|
|
6628
|
+
const deploymentName = process.env["AZURE_OPENAI_DEPLOYMENT"] ?? process.env["AZURE_OPENAI_DEPLOYMENT_NAME"];
|
|
6629
|
+
if (!endpoint || !deploymentName) continue;
|
|
6630
|
+
const apiVersion = process.env["AZURE_OPENAI_API_VERSION"];
|
|
6631
|
+
this.config.providers.push({
|
|
6632
|
+
type,
|
|
6633
|
+
apiKey: key,
|
|
6634
|
+
baseUrl: endpoint,
|
|
6635
|
+
deploymentName,
|
|
6636
|
+
...apiVersion ? { apiVersion } : {}
|
|
6637
|
+
});
|
|
6638
|
+
continue;
|
|
6639
|
+
}
|
|
6640
|
+
const inherited = ENV_ENDPOINT_CHANNEL.has(type) ? anthropicGateway : anthropicGateway ?? globalMatch?.baseUrl;
|
|
6641
|
+
this.config.providers.push({
|
|
6642
|
+
type,
|
|
6643
|
+
apiKey: key,
|
|
6644
|
+
...inherited ? { baseUrl: inherited } : {}
|
|
6645
|
+
});
|
|
6646
|
+
} else if (existing) {
|
|
6647
|
+
for (const target of targets) {
|
|
6648
|
+
if (target.apiKey || target.authToken) continue;
|
|
6649
|
+
if (anthropicGateway) {
|
|
6650
|
+
target.apiKey = key;
|
|
6651
|
+
target.baseUrl = anthropicGateway;
|
|
6652
|
+
continue;
|
|
6653
|
+
}
|
|
6654
|
+
if (target.baseUrl && hasDefaultEndpoint(type) && !sameCredentialEndpoint(type, void 0, target.baseUrl)) {
|
|
6655
|
+
if (ENV_ENDPOINT_CHANNEL.has(type)) target.baseUrl = void 0;
|
|
6656
|
+
else {
|
|
6657
|
+
console.warn(
|
|
6658
|
+
`${env} will be used at the ${type} endpoint configured in this workspace (${target.baseUrl}), not at the provider's public host. Remove that \`baseUrl\` if the key was issued by ${type} directly.`
|
|
6659
|
+
);
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
target.apiKey = key;
|
|
6663
|
+
}
|
|
6664
|
+
}
|
|
6665
|
+
}
|
|
6666
|
+
const authToken = process.env["ANTHROPIC_AUTH_TOKEN"];
|
|
6667
|
+
if (authToken && !isSubscriptionToken(authToken)) {
|
|
6668
|
+
const existing = this.config.providers.find((p) => p.type === "anthropic");
|
|
6669
|
+
const globalAnthropic = globalProviders.find((p) => p.type === "anthropic");
|
|
6670
|
+
const gateway = process.env["ANTHROPIC_BASE_URL"] ?? existing?.baseUrl ?? globalAnthropic?.baseUrl;
|
|
6671
|
+
if (gateway) {
|
|
6672
|
+
if (existing) {
|
|
6673
|
+
if (!existing.apiKey && !existing.authToken) {
|
|
6674
|
+
existing.authToken = authToken;
|
|
6675
|
+
existing.baseUrl = gateway;
|
|
6676
|
+
}
|
|
6677
|
+
} else if (mayCreate("anthropic") || globalAnthropic) {
|
|
6678
|
+
this.config.providers.push({ type: "anthropic", authToken, baseUrl: gateway });
|
|
6679
|
+
}
|
|
5951
6680
|
}
|
|
5952
6681
|
}
|
|
5953
6682
|
if (wasEmpty && !emptiedByRetirement && !this.config.providers.find((p) => p.type === "ollama")) {
|
|
@@ -6299,15 +7028,15 @@ function SafeTextInput(props) {
|
|
|
6299
7028
|
const displayValue = mask ? mask.repeat(value.length) : value;
|
|
6300
7029
|
let rendered;
|
|
6301
7030
|
if (displayValue.length === 0) {
|
|
6302
|
-
rendered = showCursor && focus ? placeholder.length > 0 ?
|
|
7031
|
+
rendered = showCursor && focus ? placeholder.length > 0 ? chalk8__default.default.inverse(placeholder[0]) + chalk8__default.default.grey(placeholder.slice(1)) : chalk8__default.default.inverse(" ") : placeholder.length > 0 ? chalk8__default.default.grey(placeholder) : "";
|
|
6303
7032
|
} else if (!showCursor || !focus) {
|
|
6304
7033
|
rendered = displayValue;
|
|
6305
7034
|
} else {
|
|
6306
7035
|
let out = "";
|
|
6307
7036
|
for (let i = 0; i < displayValue.length; i++) {
|
|
6308
|
-
out += i === cursorOffset ?
|
|
7037
|
+
out += i === cursorOffset ? chalk8__default.default.inverse(displayValue[i]) : displayValue[i];
|
|
6309
7038
|
}
|
|
6310
|
-
if (cursorOffset >= displayValue.length) out +=
|
|
7039
|
+
if (cursorOffset >= displayValue.length) out += chalk8__default.default.inverse(" ");
|
|
6311
7040
|
rendered = out;
|
|
6312
7041
|
}
|
|
6313
7042
|
return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: rendered });
|
|
@@ -6323,6 +7052,7 @@ init_gemini();
|
|
|
6323
7052
|
init_ollama();
|
|
6324
7053
|
init_openai_compatible();
|
|
6325
7054
|
init_openai();
|
|
7055
|
+
init_base();
|
|
6326
7056
|
|
|
6327
7057
|
// src/core/router/selector.ts
|
|
6328
7058
|
init_constants();
|
|
@@ -6920,13 +7650,13 @@ var DeadModelStore = class {
|
|
|
6920
7650
|
function fileDeadModelPersistence(filePath) {
|
|
6921
7651
|
return {
|
|
6922
7652
|
load() {
|
|
6923
|
-
if (!
|
|
6924
|
-
const raw = JSON.parse(
|
|
7653
|
+
if (!fs32.existsSync(filePath)) return [];
|
|
7654
|
+
const raw = JSON.parse(fs32.readFileSync(filePath, "utf-8"));
|
|
6925
7655
|
return Array.isArray(raw) ? raw : [];
|
|
6926
7656
|
},
|
|
6927
7657
|
save(records) {
|
|
6928
|
-
|
|
6929
|
-
|
|
7658
|
+
fs32.mkdirSync(path35.dirname(filePath), { recursive: true });
|
|
7659
|
+
fs32.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
6930
7660
|
}
|
|
6931
7661
|
};
|
|
6932
7662
|
}
|
|
@@ -7350,7 +8080,7 @@ init_pricing();
|
|
|
7350
8080
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
7351
8081
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
7352
8082
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
7353
|
-
var DEFAULT_CACHE_FILE =
|
|
8083
|
+
var DEFAULT_CACHE_FILE = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
7354
8084
|
function normalizeModelId2(id) {
|
|
7355
8085
|
let s = id.toLowerCase();
|
|
7356
8086
|
const slash = s.lastIndexOf("/");
|
|
@@ -7497,7 +8227,7 @@ var LiveDataProvider = class {
|
|
|
7497
8227
|
}
|
|
7498
8228
|
async saveCache() {
|
|
7499
8229
|
try {
|
|
7500
|
-
await fs11__default.default.mkdir(
|
|
8230
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(this.opts.cacheFile), { recursive: true });
|
|
7501
8231
|
const cache = {
|
|
7502
8232
|
fetchedAt: this.fetchedAt,
|
|
7503
8233
|
snapshot: this.snapshot ?? void 0,
|
|
@@ -7764,9 +8494,50 @@ function guardTokens(text) {
|
|
|
7764
8494
|
var DISCOVERY_TTL_MS = 15 * 60 * 1e3;
|
|
7765
8495
|
var DISCOVERY_TIMEOUT_MS = 4e3;
|
|
7766
8496
|
var cloudDiscoveryCache = /* @__PURE__ */ new Map();
|
|
8497
|
+
var IDENTITY_SWEEP_INTERVAL_MS = 60 * 1e3;
|
|
8498
|
+
var credentialIdentities = /* @__PURE__ */ new Map();
|
|
8499
|
+
var lastIdentitySweep = 0;
|
|
8500
|
+
var identitySweepTimer;
|
|
8501
|
+
function ensureIdentitySweeper() {
|
|
8502
|
+
if (identitySweepTimer) return;
|
|
8503
|
+
identitySweepTimer = setInterval(() => {
|
|
8504
|
+
sweepExpired(Date.now());
|
|
8505
|
+
if (credentialIdentities.size === 0 && identitySweepTimer) {
|
|
8506
|
+
clearInterval(identitySweepTimer);
|
|
8507
|
+
identitySweepTimer = void 0;
|
|
8508
|
+
}
|
|
8509
|
+
}, IDENTITY_SWEEP_INTERVAL_MS);
|
|
8510
|
+
identitySweepTimer.unref?.();
|
|
8511
|
+
}
|
|
8512
|
+
function sweepExpired(now) {
|
|
8513
|
+
for (const [key, held] of credentialIdentities) {
|
|
8514
|
+
if (now - held.at > DISCOVERY_TTL_MS) credentialIdentities.delete(key);
|
|
8515
|
+
}
|
|
8516
|
+
for (const [key, entry] of cloudDiscoveryCache) {
|
|
8517
|
+
if (now - entry.at > DISCOVERY_TTL_MS) cloudDiscoveryCache.delete(key);
|
|
8518
|
+
}
|
|
8519
|
+
}
|
|
8520
|
+
function credentialIdentity(secret) {
|
|
8521
|
+
if (!secret) return "-";
|
|
8522
|
+
const now = Date.now();
|
|
8523
|
+
if (now - lastIdentitySweep > IDENTITY_SWEEP_INTERVAL_MS) {
|
|
8524
|
+
lastIdentitySweep = now;
|
|
8525
|
+
sweepExpired(now);
|
|
8526
|
+
}
|
|
8527
|
+
const held = credentialIdentities.get(secret);
|
|
8528
|
+
if (held && now - held.at <= DISCOVERY_TTL_MS) return held.id;
|
|
8529
|
+
const id = crypto__default.default.randomUUID();
|
|
8530
|
+
credentialIdentities.set(secret, { id, at: now });
|
|
8531
|
+
ensureIdentitySweeper();
|
|
8532
|
+
return id;
|
|
8533
|
+
}
|
|
7767
8534
|
function discoveryCacheKey(type, cfg) {
|
|
7768
|
-
|
|
7769
|
-
|
|
8535
|
+
return [
|
|
8536
|
+
type,
|
|
8537
|
+
credentialIdentity(cfg.apiKey),
|
|
8538
|
+
credentialIdentity(cfg.authToken),
|
|
8539
|
+
cfg.baseUrl ?? ""
|
|
8540
|
+
].join("|");
|
|
7770
8541
|
}
|
|
7771
8542
|
function applyTierLimits(options, tier, limits) {
|
|
7772
8543
|
if (!limits) return options;
|
|
@@ -8065,7 +8836,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8065
8836
|
await Promise.all(providers.map(async (type) => {
|
|
8066
8837
|
if (!this.selector.isProviderAvailable(type)) return;
|
|
8067
8838
|
const cfg = config.providers.find((p) => p.type === type);
|
|
8068
|
-
if (!cfg
|
|
8839
|
+
if (!cfg || !hasProviderCredential(cfg)) return;
|
|
8069
8840
|
const cacheKey = discoveryCacheKey(type, cfg);
|
|
8070
8841
|
let entry = cloudDiscoveryCache.get(cacheKey);
|
|
8071
8842
|
if (!entry || Date.now() - entry.at > DISCOVERY_TTL_MS) {
|
|
@@ -8074,7 +8845,11 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8074
8845
|
try {
|
|
8075
8846
|
const provider = this.createProvider(cfg, seed);
|
|
8076
8847
|
if (typeof provider.listModels !== "function") return;
|
|
8077
|
-
const models = await withTimeout(
|
|
8848
|
+
const models = await withTimeout(
|
|
8849
|
+
provider.listModels({ staticFallback: false }),
|
|
8850
|
+
DISCOVERY_TIMEOUT_MS,
|
|
8851
|
+
`${type} model discovery timed out`
|
|
8852
|
+
);
|
|
8078
8853
|
if (!models.length) return;
|
|
8079
8854
|
entry = { models, ids: models.map((m) => m.id), at: Date.now() };
|
|
8080
8855
|
cloudDiscoveryCache.set(cacheKey, entry);
|
|
@@ -8582,12 +9357,28 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8582
9357
|
return this.selector?.getAllAvailableModels() ?? [];
|
|
8583
9358
|
}
|
|
8584
9359
|
// ── Private ──────────────────────────────────
|
|
9360
|
+
/**
|
|
9361
|
+
* Why each configured provider failed its availability probe, kept so the
|
|
9362
|
+
* surfaces can say something better than "add an API key" to a user who
|
|
9363
|
+
* already has one. Cleared and refilled by each detectAvailableProviders().
|
|
9364
|
+
*/
|
|
9365
|
+
probeFailures = /* @__PURE__ */ new Map();
|
|
9366
|
+
/**
|
|
9367
|
+
* What stopped the configured providers from being usable, if anything.
|
|
9368
|
+
* Empty when every configured provider passed — or when none was configured,
|
|
9369
|
+
* which is a different problem with a different answer.
|
|
9370
|
+
*/
|
|
9371
|
+
providerProbeFailures() {
|
|
9372
|
+
return [...this.probeFailures].map(([provider, reason]) => ({ provider, reason }));
|
|
9373
|
+
}
|
|
8585
9374
|
/** Logs why a configured provider failed its availability probe. */
|
|
8586
9375
|
emitProbeFailure(type, reason) {
|
|
9376
|
+
this.probeFailures.set(type, reason);
|
|
8587
9377
|
console.warn(`[router] provider "${type}" is not available: ${reason}`);
|
|
8588
9378
|
}
|
|
8589
9379
|
async detectAvailableProviders(configs) {
|
|
8590
9380
|
const available = /* @__PURE__ */ new Set();
|
|
9381
|
+
this.probeFailures.clear();
|
|
8591
9382
|
const checks = configs.map(async (cfg) => {
|
|
8592
9383
|
try {
|
|
8593
9384
|
const testModel = this.getAnyModelForProvider(cfg.type);
|
|
@@ -8597,7 +9388,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8597
9388
|
if (ok) available.add(cfg.type);
|
|
8598
9389
|
else this.emitProbeFailure(cfg.type, "availability check returned false (bad key, wrong endpoint/deployment, or unreachable)");
|
|
8599
9390
|
} catch (err) {
|
|
8600
|
-
|
|
9391
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
9392
|
+
if (err instanceof ProviderUnreachableError) {
|
|
9393
|
+
available.add(cfg.type);
|
|
9394
|
+
console.warn(`[router] provider "${cfg.type}" probe did not complete: ${reason} \u2014 continuing with it enabled`);
|
|
9395
|
+
return;
|
|
9396
|
+
}
|
|
9397
|
+
this.emitProbeFailure(cfg.type, reason);
|
|
8601
9398
|
}
|
|
8602
9399
|
});
|
|
8603
9400
|
await Promise.allSettled(checks);
|
|
@@ -10316,8 +11113,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
10316
11113
|
// Media generation can run for a minute; without this a cancelled run
|
|
10317
11114
|
// still pays for an image nobody will see.
|
|
10318
11115
|
...this.signal ? { signal: this.signal } : {},
|
|
10319
|
-
saveSnapshot: async (
|
|
10320
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
11116
|
+
saveSnapshot: async (path40, content) => {
|
|
11117
|
+
this.store?.addFileSnapshot(this.taskId, path40, content);
|
|
10321
11118
|
},
|
|
10322
11119
|
sendPeerSync: (to, syncType, content) => {
|
|
10323
11120
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -10520,7 +11317,7 @@ ${assignment.expectedOutput}`;
|
|
|
10520
11317
|
const { promisify: promisify4 } = await import('util');
|
|
10521
11318
|
const execFileAsync2 = promisify4(execFile3);
|
|
10522
11319
|
for (const artifactPath of artifactPaths) {
|
|
10523
|
-
const absolutePath =
|
|
11320
|
+
const absolutePath = path35__default.default.resolve(this.artifactRoot(), artifactPath);
|
|
10524
11321
|
try {
|
|
10525
11322
|
const stat = await fs11__default.default.stat(absolutePath);
|
|
10526
11323
|
if (!stat.isFile()) {
|
|
@@ -10555,7 +11352,7 @@ ${assignment.expectedOutput}`;
|
|
|
10555
11352
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
10556
11353
|
continue;
|
|
10557
11354
|
}
|
|
10558
|
-
const ext =
|
|
11355
|
+
const ext = path35__default.default.extname(absolutePath).toLowerCase();
|
|
10559
11356
|
const check = ext === ".ts" || ext === ".tsx" ? { cmd: "npx", args: ["tsc", "--noEmit", absolutePath] } : ext === ".js" || ext === ".jsx" ? { cmd: process.execPath, args: ["--check", absolutePath] } : ext === ".py" ? { cmd: "python", args: ["-m", "py_compile", absolutePath] } : null;
|
|
10560
11357
|
if (check) {
|
|
10561
11358
|
try {
|
|
@@ -10647,7 +11444,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
10647
11444
|
async checkAcceptance(assignment) {
|
|
10648
11445
|
const criteria = assignment.acceptance ?? [];
|
|
10649
11446
|
if (!criteria.length) return [];
|
|
10650
|
-
const resolve2 = (target) =>
|
|
11447
|
+
const resolve2 = (target) => path35__default.default.resolve(this.artifactRoot(), target);
|
|
10651
11448
|
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
10652
11449
|
stat: async (target) => {
|
|
10653
11450
|
try {
|
|
@@ -13876,16 +14673,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
13876
14673
|
if (typeof input !== "string" || input.length === 0) {
|
|
13877
14674
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
13878
14675
|
}
|
|
13879
|
-
const root =
|
|
13880
|
-
const abs =
|
|
13881
|
-
const rel =
|
|
13882
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") ||
|
|
14676
|
+
const root = path35__default.default.resolve(workspaceRoot);
|
|
14677
|
+
const abs = path35__default.default.isAbsolute(input) ? path35__default.default.resolve(input) : path35__default.default.resolve(root, input);
|
|
14678
|
+
const rel = path35__default.default.relative(root, abs);
|
|
14679
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path35__default.default.isAbsolute(rel)) {
|
|
13883
14680
|
throw new WorkspaceSandboxError(input, root);
|
|
13884
14681
|
}
|
|
13885
14682
|
try {
|
|
13886
|
-
const real =
|
|
13887
|
-
const realRel =
|
|
13888
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") ||
|
|
14683
|
+
const real = fs32__default.default.realpathSync(abs);
|
|
14684
|
+
const realRel = path35__default.default.relative(root, real);
|
|
14685
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path35__default.default.isAbsolute(realRel))) {
|
|
13889
14686
|
throw new WorkspaceSandboxError(input, root);
|
|
13890
14687
|
}
|
|
13891
14688
|
} catch (e) {
|
|
@@ -14538,7 +15335,7 @@ var GenerateDocumentTool = class extends BaseTool {
|
|
|
14538
15335
|
}
|
|
14539
15336
|
}
|
|
14540
15337
|
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
14541
|
-
await fs11__default.default.mkdir(
|
|
15338
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(absPath), { recursive: true });
|
|
14542
15339
|
await fs11__default.default.writeFile(absPath, bytes);
|
|
14543
15340
|
const charts = countCharts(content);
|
|
14544
15341
|
return [
|
|
@@ -14759,7 +15556,7 @@ var FileWriteTool = class extends BaseTool {
|
|
|
14759
15556
|
} catch {
|
|
14760
15557
|
}
|
|
14761
15558
|
}
|
|
14762
|
-
await fs11__default.default.mkdir(
|
|
15559
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(absPath), { recursive: true });
|
|
14763
15560
|
await fs11__default.default.writeFile(absPath, content, "utf-8");
|
|
14764
15561
|
return `Written ${content.length} characters to ${filePath}`;
|
|
14765
15562
|
}
|
|
@@ -15257,7 +16054,7 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
15257
16054
|
};
|
|
15258
16055
|
async function fileToImageAttachment(filePath) {
|
|
15259
16056
|
const data = await fs11__default.default.readFile(filePath);
|
|
15260
|
-
const ext =
|
|
16057
|
+
const ext = path35__default.default.extname(filePath).toLowerCase();
|
|
15261
16058
|
const mimeMap = {
|
|
15262
16059
|
".jpg": "image/jpeg",
|
|
15263
16060
|
".jpeg": "image/jpeg",
|
|
@@ -15291,14 +16088,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
15291
16088
|
const filePath = input["path"];
|
|
15292
16089
|
const content = input["content"];
|
|
15293
16090
|
const title = input["title"];
|
|
15294
|
-
const dir =
|
|
15295
|
-
if (!
|
|
15296
|
-
|
|
16091
|
+
const dir = path35__default.default.dirname(filePath);
|
|
16092
|
+
if (!fs32__default.default.existsSync(dir)) {
|
|
16093
|
+
fs32__default.default.mkdirSync(dir, { recursive: true });
|
|
15297
16094
|
}
|
|
15298
16095
|
return new Promise((resolve2, reject) => {
|
|
15299
16096
|
try {
|
|
15300
16097
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
15301
|
-
const stream =
|
|
16098
|
+
const stream = fs32__default.default.createWriteStream(filePath);
|
|
15302
16099
|
doc.pipe(stream);
|
|
15303
16100
|
if (title) {
|
|
15304
16101
|
doc.info["Title"] = title;
|
|
@@ -15376,22 +16173,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
15376
16173
|
}
|
|
15377
16174
|
cmdPrefix = NODE_CMD;
|
|
15378
16175
|
}
|
|
15379
|
-
const tmpDir =
|
|
15380
|
-
if (!
|
|
15381
|
-
|
|
16176
|
+
const tmpDir = path35__default.default.join(this.workspaceRoot, ".cascade", "tmp");
|
|
16177
|
+
if (!fs32__default.default.existsSync(tmpDir)) {
|
|
16178
|
+
fs32__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
15382
16179
|
}
|
|
15383
16180
|
const extension = language === "python" ? "py" : "js";
|
|
15384
16181
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
15385
|
-
const filePath =
|
|
15386
|
-
|
|
16182
|
+
const filePath = path35__default.default.join(tmpDir, fileName);
|
|
16183
|
+
fs32__default.default.writeFileSync(filePath, code, "utf-8");
|
|
15387
16184
|
const execArgs = [filePath, ...args];
|
|
15388
16185
|
return new Promise((resolve2) => {
|
|
15389
16186
|
const startMs = Date.now();
|
|
15390
16187
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
15391
16188
|
const duration = Date.now() - startMs;
|
|
15392
16189
|
try {
|
|
15393
|
-
if (
|
|
15394
|
-
|
|
16190
|
+
if (fs32__default.default.existsSync(filePath)) {
|
|
16191
|
+
fs32__default.default.unlinkSync(filePath);
|
|
15395
16192
|
}
|
|
15396
16193
|
} catch (cleanupErr) {
|
|
15397
16194
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -15703,7 +16500,7 @@ var GlobTool = class extends BaseTool {
|
|
|
15703
16500
|
};
|
|
15704
16501
|
async execute(input, _options) {
|
|
15705
16502
|
const pattern = input["pattern"];
|
|
15706
|
-
const searchPath = input["path"] ?
|
|
16503
|
+
const searchPath = input["path"] ? path35__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
15707
16504
|
const matches = await glob.glob(pattern, {
|
|
15708
16505
|
cwd: searchPath,
|
|
15709
16506
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -15716,7 +16513,7 @@ var GlobTool = class extends BaseTool {
|
|
|
15716
16513
|
const withMtime = await Promise.all(
|
|
15717
16514
|
matches.map(async (rel) => {
|
|
15718
16515
|
try {
|
|
15719
|
-
const stat = await fs11__default.default.stat(
|
|
16516
|
+
const stat = await fs11__default.default.stat(path35__default.default.join(searchPath, rel));
|
|
15720
16517
|
return { rel, mtime: stat.mtimeMs };
|
|
15721
16518
|
} catch {
|
|
15722
16519
|
return { rel, mtime: 0 };
|
|
@@ -15765,7 +16562,7 @@ var GrepTool = class extends BaseTool {
|
|
|
15765
16562
|
};
|
|
15766
16563
|
async execute(input, _options) {
|
|
15767
16564
|
const pattern = input["pattern"];
|
|
15768
|
-
const searchPath = input["path"] ?
|
|
16565
|
+
const searchPath = input["path"] ? path35__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
15769
16566
|
const globPattern = input["glob"];
|
|
15770
16567
|
const outputMode = input["output_mode"] ?? "content";
|
|
15771
16568
|
const context = input["context"] ?? 0;
|
|
@@ -15819,12 +16616,12 @@ var GrepTool = class extends BaseTool {
|
|
|
15819
16616
|
nodir: true
|
|
15820
16617
|
});
|
|
15821
16618
|
} catch {
|
|
15822
|
-
files = [
|
|
16619
|
+
files = [path35__default.default.relative(searchPath, searchPath) || "."];
|
|
15823
16620
|
}
|
|
15824
16621
|
const results = [];
|
|
15825
16622
|
let totalCount = 0;
|
|
15826
16623
|
for (const rel of files) {
|
|
15827
|
-
const abs =
|
|
16624
|
+
const abs = path35__default.default.join(searchPath, rel);
|
|
15828
16625
|
let content;
|
|
15829
16626
|
try {
|
|
15830
16627
|
content = await fs11__default.default.readFile(abs, "utf-8");
|
|
@@ -16223,10 +17020,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
16223
17020
|
}
|
|
16224
17021
|
isIgnored(filePath) {
|
|
16225
17022
|
if (!filePath) return false;
|
|
16226
|
-
const abs =
|
|
16227
|
-
const rel =
|
|
16228
|
-
if (!rel || rel.startsWith("..") ||
|
|
16229
|
-
const posixRel = rel.split(
|
|
17023
|
+
const abs = path35__default.default.resolve(this.workspaceRoot, filePath);
|
|
17024
|
+
const rel = path35__default.default.relative(this.workspaceRoot, abs);
|
|
17025
|
+
if (!rel || rel.startsWith("..") || path35__default.default.isAbsolute(rel)) return true;
|
|
17026
|
+
const posixRel = rel.split(path35__default.default.sep).join("/");
|
|
16230
17027
|
return this.ignoreMatcher.ignores(posixRel);
|
|
16231
17028
|
}
|
|
16232
17029
|
};
|
|
@@ -16460,22 +17257,22 @@ var FileMcpOAuthStore = class {
|
|
|
16460
17257
|
filePath;
|
|
16461
17258
|
load() {
|
|
16462
17259
|
try {
|
|
16463
|
-
return JSON.parse(
|
|
17260
|
+
return JSON.parse(fs32__default.default.readFileSync(this.filePath, "utf-8"));
|
|
16464
17261
|
} catch {
|
|
16465
17262
|
return void 0;
|
|
16466
17263
|
}
|
|
16467
17264
|
}
|
|
16468
17265
|
save(state) {
|
|
16469
|
-
|
|
16470
|
-
|
|
17266
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
17267
|
+
fs32__default.default.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
16471
17268
|
try {
|
|
16472
|
-
|
|
17269
|
+
fs32__default.default.chmodSync(this.filePath, 384);
|
|
16473
17270
|
} catch {
|
|
16474
17271
|
}
|
|
16475
17272
|
}
|
|
16476
17273
|
clear() {
|
|
16477
17274
|
try {
|
|
16478
|
-
|
|
17275
|
+
fs32__default.default.rmSync(this.filePath);
|
|
16479
17276
|
} catch {
|
|
16480
17277
|
}
|
|
16481
17278
|
}
|
|
@@ -17123,7 +17920,7 @@ var TaskAnalyzer = class {
|
|
|
17123
17920
|
|
|
17124
17921
|
// src/core/router/model-performance-tracker.ts
|
|
17125
17922
|
init_pricing();
|
|
17126
|
-
var DEFAULT_STATS_FILE =
|
|
17923
|
+
var DEFAULT_STATS_FILE = path35__default.default.join(os10__default.default.homedir(), ".cascade", "model-perf.json");
|
|
17127
17924
|
var ModelPerformanceTracker = class {
|
|
17128
17925
|
stats = /* @__PURE__ */ new Map();
|
|
17129
17926
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -17163,7 +17960,7 @@ var ModelPerformanceTracker = class {
|
|
|
17163
17960
|
async save() {
|
|
17164
17961
|
if (this.readOnly) return;
|
|
17165
17962
|
try {
|
|
17166
|
-
await fs11__default.default.mkdir(
|
|
17963
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(this.statsFile), { recursive: true });
|
|
17167
17964
|
const modelsObj = {};
|
|
17168
17965
|
const featuresObj = {};
|
|
17169
17966
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
@@ -17711,7 +18508,7 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
17711
18508
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
17712
18509
|
async loadPersistedTools() {
|
|
17713
18510
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
17714
|
-
const file =
|
|
18511
|
+
const file = path35__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
17715
18512
|
try {
|
|
17716
18513
|
const raw = await fs11__default.default.readFile(file, "utf-8");
|
|
17717
18514
|
const specs = JSON.parse(raw);
|
|
@@ -17735,8 +18532,8 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
17735
18532
|
}
|
|
17736
18533
|
async persist() {
|
|
17737
18534
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
17738
|
-
const dir =
|
|
17739
|
-
const file =
|
|
18535
|
+
const dir = path35__default.default.join(this.workspacePath, ".cascade");
|
|
18536
|
+
const file = path35__default.default.join(dir, DYNAMIC_TOOLS_FILE);
|
|
17740
18537
|
try {
|
|
17741
18538
|
await fs11__default.default.mkdir(dir, { recursive: true });
|
|
17742
18539
|
await fs11__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
@@ -17756,12 +18553,12 @@ var WorldStateDB = class {
|
|
|
17756
18553
|
constructor(workspacePath, debugMode = false) {
|
|
17757
18554
|
this.workspacePath = workspacePath;
|
|
17758
18555
|
this.debugMode = debugMode;
|
|
17759
|
-
const cascadeDir =
|
|
17760
|
-
if (!
|
|
17761
|
-
|
|
18556
|
+
const cascadeDir = path35__default.default.join(workspacePath, ".cascade");
|
|
18557
|
+
if (!fs32__default.default.existsSync(cascadeDir)) {
|
|
18558
|
+
fs32__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
17762
18559
|
}
|
|
17763
|
-
this.keyPath =
|
|
17764
|
-
this.dbPath =
|
|
18560
|
+
this.keyPath = path35__default.default.join(cascadeDir, "world_state.key");
|
|
18561
|
+
this.dbPath = path35__default.default.join(cascadeDir, "world_state.db");
|
|
17765
18562
|
this.initEncryptionKey();
|
|
17766
18563
|
this.db = new Database2__default.default(this.dbPath);
|
|
17767
18564
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -17818,11 +18615,11 @@ var WorldStateDB = class {
|
|
|
17818
18615
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
17819
18616
|
}
|
|
17820
18617
|
initEncryptionKey() {
|
|
17821
|
-
if (
|
|
17822
|
-
this.encryptionKey =
|
|
18618
|
+
if (fs32__default.default.existsSync(this.keyPath)) {
|
|
18619
|
+
this.encryptionKey = fs32__default.default.readFileSync(this.keyPath);
|
|
17823
18620
|
} else {
|
|
17824
18621
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
17825
|
-
|
|
18622
|
+
fs32__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
17826
18623
|
}
|
|
17827
18624
|
}
|
|
17828
18625
|
encrypt(text) {
|
|
@@ -18108,9 +18905,9 @@ var WorldStateDB = class {
|
|
|
18108
18905
|
dumpDebugIfNeeded() {
|
|
18109
18906
|
if (!this.debugMode) return;
|
|
18110
18907
|
try {
|
|
18111
|
-
const dumpPath =
|
|
18908
|
+
const dumpPath = path35__default.default.join(os10__default.default.tmpdir(), "cascade_world_state_debug.json");
|
|
18112
18909
|
const entries = this.getAllEntries();
|
|
18113
|
-
|
|
18910
|
+
fs32__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
18114
18911
|
} catch (err) {
|
|
18115
18912
|
console.error("Failed to dump debug world state", err);
|
|
18116
18913
|
}
|
|
@@ -18166,11 +18963,11 @@ var ResumeStore = class {
|
|
|
18166
18963
|
version: RESUME_SCHEMA_VERSION,
|
|
18167
18964
|
createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
|
|
18168
18965
|
};
|
|
18169
|
-
await
|
|
18170
|
-
const finalPath =
|
|
18966
|
+
await fs32.promises.mkdir(this.dir, { recursive: true });
|
|
18967
|
+
const finalPath = path35__default.default.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
|
|
18171
18968
|
const tempPath = `${finalPath}.${process.pid}.tmp`;
|
|
18172
|
-
await
|
|
18173
|
-
await
|
|
18969
|
+
await fs32.promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
|
|
18970
|
+
await fs32.promises.rename(tempPath, finalPath);
|
|
18174
18971
|
await this.prune();
|
|
18175
18972
|
return true;
|
|
18176
18973
|
} catch {
|
|
@@ -18181,7 +18978,7 @@ var ResumeStore = class {
|
|
|
18181
18978
|
async list() {
|
|
18182
18979
|
let entries;
|
|
18183
18980
|
try {
|
|
18184
|
-
entries = await
|
|
18981
|
+
entries = await fs32.promises.readdir(this.dir);
|
|
18185
18982
|
} catch {
|
|
18186
18983
|
return [];
|
|
18187
18984
|
}
|
|
@@ -18190,7 +18987,7 @@ var ResumeStore = class {
|
|
|
18190
18987
|
const checkpoints = [];
|
|
18191
18988
|
for (const candidate of candidates2) {
|
|
18192
18989
|
try {
|
|
18193
|
-
const raw = await
|
|
18990
|
+
const raw = await fs32.promises.readFile(path35__default.default.join(this.dir, candidate.name), "utf-8");
|
|
18194
18991
|
const parsed = JSON.parse(raw);
|
|
18195
18992
|
if (isCheckpoint(parsed)) checkpoints.push(parsed);
|
|
18196
18993
|
} catch {
|
|
@@ -18224,12 +19021,12 @@ var ResumeStore = class {
|
|
|
18224
19021
|
const target = taskId ? all.find((c) => c.taskId === taskId) : all[0];
|
|
18225
19022
|
if (!target) return null;
|
|
18226
19023
|
try {
|
|
18227
|
-
const entries = await
|
|
19024
|
+
const entries = await fs32.promises.readdir(this.dir);
|
|
18228
19025
|
const wanted = safeId(target.taskId);
|
|
18229
19026
|
const source = entries.find((name) => FILE_PATTERN.exec(name)?.[2] === wanted);
|
|
18230
19027
|
if (!source) return null;
|
|
18231
19028
|
const claimId = `${this.now()}-${safeId(target.taskId)}`;
|
|
18232
|
-
await
|
|
19029
|
+
await fs32.promises.rename(path35__default.default.join(this.dir, source), path35__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`));
|
|
18233
19030
|
return { checkpoint: target, claimId };
|
|
18234
19031
|
} catch {
|
|
18235
19032
|
return null;
|
|
@@ -18238,9 +19035,9 @@ var ResumeStore = class {
|
|
|
18238
19035
|
/** Give a claim back, so the work stays recoverable after a failed start. */
|
|
18239
19036
|
async release(claimId) {
|
|
18240
19037
|
try {
|
|
18241
|
-
await
|
|
18242
|
-
|
|
18243
|
-
|
|
19038
|
+
await fs32.promises.rename(
|
|
19039
|
+
path35__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`),
|
|
19040
|
+
path35__default.default.join(this.dir, `${claimId}.json`)
|
|
18244
19041
|
);
|
|
18245
19042
|
} catch {
|
|
18246
19043
|
}
|
|
@@ -18248,7 +19045,7 @@ var ResumeStore = class {
|
|
|
18248
19045
|
/** Discard a claim for good — the resumed run has taken ownership. */
|
|
18249
19046
|
async settle(claimId) {
|
|
18250
19047
|
try {
|
|
18251
|
-
await
|
|
19048
|
+
await fs32.promises.rm(path35__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`), { force: true });
|
|
18252
19049
|
} catch {
|
|
18253
19050
|
}
|
|
18254
19051
|
}
|
|
@@ -18270,15 +19067,15 @@ var ResumeStore = class {
|
|
|
18270
19067
|
async reclaimStale() {
|
|
18271
19068
|
let reclaimed = 0;
|
|
18272
19069
|
try {
|
|
18273
|
-
const entries = await
|
|
19070
|
+
const entries = await fs32.promises.readdir(this.dir);
|
|
18274
19071
|
const cutoff = this.now() - this.claimTimeoutMs;
|
|
18275
19072
|
for (const name of entries) {
|
|
18276
19073
|
if (!name.endsWith(CLAIM_SUFFIX)) continue;
|
|
18277
19074
|
const stamp2 = Number(/^(\d+)-/.exec(name)?.[1]);
|
|
18278
19075
|
if (!Number.isFinite(stamp2) || stamp2 >= cutoff) continue;
|
|
18279
|
-
await
|
|
18280
|
-
|
|
18281
|
-
|
|
19076
|
+
await fs32.promises.rename(
|
|
19077
|
+
path35__default.default.join(this.dir, name),
|
|
19078
|
+
path35__default.default.join(this.dir, `${name.slice(0, -CLAIM_SUFFIX.length)}.json`)
|
|
18282
19079
|
);
|
|
18283
19080
|
reclaimed++;
|
|
18284
19081
|
}
|
|
@@ -18289,12 +19086,12 @@ var ResumeStore = class {
|
|
|
18289
19086
|
/** Remove every checkpoint for a task id. */
|
|
18290
19087
|
async delete(taskId) {
|
|
18291
19088
|
try {
|
|
18292
|
-
const entries = await
|
|
19089
|
+
const entries = await fs32.promises.readdir(this.dir);
|
|
18293
19090
|
const wanted = safeId(taskId);
|
|
18294
19091
|
await Promise.all(entries.map(async (name) => {
|
|
18295
19092
|
const match = FILE_PATTERN.exec(name);
|
|
18296
19093
|
if (match?.[2] === wanted) {
|
|
18297
|
-
await
|
|
19094
|
+
await fs32.promises.rm(path35__default.default.join(this.dir, name), { force: true });
|
|
18298
19095
|
}
|
|
18299
19096
|
}));
|
|
18300
19097
|
} catch {
|
|
@@ -18303,13 +19100,13 @@ var ResumeStore = class {
|
|
|
18303
19100
|
/** Drop expired checkpoints, then any beyond the newest `maxCheckpoints`. */
|
|
18304
19101
|
async prune() {
|
|
18305
19102
|
try {
|
|
18306
|
-
const entries = await
|
|
19103
|
+
const entries = await fs32.promises.readdir(this.dir);
|
|
18307
19104
|
const cutoff = this.now() - this.maxAgeMs;
|
|
18308
19105
|
const files = entries.map((name) => ({ name, match: FILE_PATTERN.exec(name) })).filter((e) => e.match !== null).map((e) => ({ name: e.name, timestamp: Number(e.match[1]) })).sort((a, b) => b.timestamp - a.timestamp);
|
|
18309
19106
|
const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
|
|
18310
19107
|
const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
|
|
18311
19108
|
await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
|
|
18312
|
-
(name) =>
|
|
19109
|
+
(name) => fs32.promises.rm(path35__default.default.join(this.dir, name), { force: true })
|
|
18313
19110
|
));
|
|
18314
19111
|
} catch {
|
|
18315
19112
|
}
|
|
@@ -18686,7 +19483,7 @@ var WorkspaceIndex = class {
|
|
|
18686
19483
|
isIgnored;
|
|
18687
19484
|
chunker;
|
|
18688
19485
|
constructor(opts) {
|
|
18689
|
-
this.root =
|
|
19486
|
+
this.root = path35__default.default.resolve(opts.root);
|
|
18690
19487
|
this.db = opts.db;
|
|
18691
19488
|
this.namespace = opts.namespace ?? "code";
|
|
18692
19489
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -18718,14 +19515,14 @@ var WorkspaceIndex = class {
|
|
|
18718
19515
|
return;
|
|
18719
19516
|
}
|
|
18720
19517
|
for (const entry of entries) {
|
|
18721
|
-
const abs =
|
|
19518
|
+
const abs = path35__default.default.join(dir, entry.name);
|
|
18722
19519
|
if (entry.isDirectory()) {
|
|
18723
19520
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
18724
19521
|
await walk(abs);
|
|
18725
19522
|
} else if (entry.isFile()) {
|
|
18726
|
-
const ext =
|
|
19523
|
+
const ext = path35__default.default.extname(entry.name).slice(1).toLowerCase();
|
|
18727
19524
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
18728
|
-
out.push(
|
|
19525
|
+
out.push(path35__default.default.relative(this.root, abs));
|
|
18729
19526
|
}
|
|
18730
19527
|
}
|
|
18731
19528
|
};
|
|
@@ -18755,7 +19552,7 @@ var WorkspaceIndex = class {
|
|
|
18755
19552
|
const entries = [];
|
|
18756
19553
|
for (const rel of rels) {
|
|
18757
19554
|
try {
|
|
18758
|
-
const abs =
|
|
19555
|
+
const abs = path35__default.default.join(this.root, rel);
|
|
18759
19556
|
const stat = await fs11__default.default.stat(abs);
|
|
18760
19557
|
if (stat.size > this.maxFileBytes) continue;
|
|
18761
19558
|
const buf = await fs11__default.default.readFile(abs);
|
|
@@ -19245,13 +20042,13 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
19245
20042
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
19246
20043
|
try {
|
|
19247
20044
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
19248
|
-
fileDeadModelPersistence(
|
|
20045
|
+
fileDeadModelPersistence(path35__default.default.join(workspacePath, ".cascade", "dead-models.json"))
|
|
19249
20046
|
));
|
|
19250
20047
|
} catch {
|
|
19251
20048
|
}
|
|
19252
20049
|
try {
|
|
19253
20050
|
this.resumeStore = new ResumeStore({
|
|
19254
|
-
dir:
|
|
20051
|
+
dir: path35__default.default.join(workspacePath, ".cascade", "resume")
|
|
19255
20052
|
});
|
|
19256
20053
|
} catch {
|
|
19257
20054
|
}
|
|
@@ -19339,8 +20136,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
19339
20136
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
19340
20137
|
return;
|
|
19341
20138
|
}
|
|
19342
|
-
const dbPath = ci.dbPath ||
|
|
19343
|
-
|
|
20139
|
+
const dbPath = ci.dbPath || path35__default.default.join(this.workspacePath, ".cascade", "code-index.db");
|
|
20140
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(dbPath), { recursive: true });
|
|
19344
20141
|
const db = new Database2__default.default(dbPath);
|
|
19345
20142
|
db.pragma("journal_mode = WAL");
|
|
19346
20143
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -20645,7 +21442,16 @@ ${finalOutput}` : ""
|
|
|
20645
21442
|
const selector = this.router.getSelector();
|
|
20646
21443
|
const model = options.fastAnswerModel ? selector.selectForTier(tier, options.fastAnswerModel) : selector.getCandidatesForTier(tier)[0] ?? selector.selectForTier(tier);
|
|
20647
21444
|
if (!model) {
|
|
20648
|
-
|
|
21445
|
+
const failures2 = this.router.providerProbeFailures();
|
|
21446
|
+
const context = failures2.length ? ` Configured providers: ${failures2.map((f) => `${f.provider}: ${f.reason}`).join("; ")}` : "";
|
|
21447
|
+
if (options.fastAnswerModel) {
|
|
21448
|
+
throw new Error(
|
|
21449
|
+
`No model is available for a fast answer: the pinned model "${options.fastAnswerModel}" could not be resolved \u2014 check it is spelled correctly and served by a configured provider.${context}`
|
|
21450
|
+
);
|
|
21451
|
+
}
|
|
21452
|
+
throw new Error(
|
|
21453
|
+
failures2.length ? `No model is available for a fast answer.${context}` : "No model is available for a fast answer \u2014 add a provider API key first."
|
|
21454
|
+
);
|
|
20649
21455
|
}
|
|
20650
21456
|
this.recordDecision(
|
|
20651
21457
|
"model",
|
|
@@ -20779,14 +21585,14 @@ ${finalOutput}` : ""
|
|
|
20779
21585
|
init_constants();
|
|
20780
21586
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
20781
21587
|
function globalDir(homeDir = os10__default.default.homedir()) {
|
|
20782
|
-
return
|
|
21588
|
+
return path35__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
20783
21589
|
}
|
|
20784
21590
|
function cloudSessionPath(dir = globalDir()) {
|
|
20785
|
-
return
|
|
21591
|
+
return path35__default.default.join(dir, CLOUD_SESSION_FILE);
|
|
20786
21592
|
}
|
|
20787
21593
|
function loadCloudSession(dir = globalDir()) {
|
|
20788
21594
|
try {
|
|
20789
|
-
const raw =
|
|
21595
|
+
const raw = fs32__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
20790
21596
|
const parsed = JSON.parse(raw);
|
|
20791
21597
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
20792
21598
|
return parsed;
|
|
@@ -20795,17 +21601,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
20795
21601
|
}
|
|
20796
21602
|
}
|
|
20797
21603
|
function saveCloudSession(session, dir = globalDir()) {
|
|
20798
|
-
|
|
21604
|
+
fs32__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
20799
21605
|
const file = cloudSessionPath(dir);
|
|
20800
|
-
|
|
21606
|
+
fs32__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
20801
21607
|
try {
|
|
20802
|
-
|
|
21608
|
+
fs32__default.default.chmodSync(file, 384);
|
|
20803
21609
|
} catch {
|
|
20804
21610
|
}
|
|
20805
21611
|
}
|
|
20806
21612
|
function clearCloudSession(dir = globalDir()) {
|
|
20807
21613
|
try {
|
|
20808
|
-
|
|
21614
|
+
fs32__default.default.rmSync(cloudSessionPath(dir));
|
|
20809
21615
|
} catch {
|
|
20810
21616
|
}
|
|
20811
21617
|
}
|
|
@@ -20977,16 +21783,16 @@ var CloudClient = class _CloudClient {
|
|
|
20977
21783
|
return (await this.refresh()).accessToken;
|
|
20978
21784
|
}
|
|
20979
21785
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
20980
|
-
async authed(
|
|
20981
|
-
const call = (token) => fetch(this.url(
|
|
21786
|
+
async authed(path40, init = {}) {
|
|
21787
|
+
const call = (token) => fetch(this.url(path40), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
20982
21788
|
let res = await call(await this.accessToken());
|
|
20983
21789
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
20984
21790
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
20985
21791
|
const text = await res.text();
|
|
20986
21792
|
return text ? JSON.parse(text) : {};
|
|
20987
21793
|
}
|
|
20988
|
-
authedGet(
|
|
20989
|
-
return this.authed(
|
|
21794
|
+
authedGet(path40) {
|
|
21795
|
+
return this.authed(path40);
|
|
20990
21796
|
}
|
|
20991
21797
|
async fetchMe(token) {
|
|
20992
21798
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -21847,7 +22653,7 @@ function StatusBarInternal({
|
|
|
21847
22653
|
const savedStr = savedUsd > 0 ? ` \xB7 saved $${savedUsd.toFixed(4)}` : "";
|
|
21848
22654
|
const rightStr = ` ${formatTokens(tokens)} \xB7 $${costUsd.toFixed(4)}${savedStr} ${isExecuting ? "\u26A1" : "\xB7"} `;
|
|
21849
22655
|
const gap = Math.max(0, width - leftStr.length - rightStr.length);
|
|
21850
|
-
const line =
|
|
22656
|
+
const line = chalk8__default.default.bgHex(theme.colors.primary).hex(theme.colors.background)(chalk8__default.default.bold(leftStr) + " ".repeat(gap) + rightStr);
|
|
21851
22657
|
return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: line });
|
|
21852
22658
|
}
|
|
21853
22659
|
function formatTokens(n) {
|
|
@@ -22316,7 +23122,7 @@ function renderContent(content, theme) {
|
|
|
22316
23122
|
i
|
|
22317
23123
|
);
|
|
22318
23124
|
}
|
|
22319
|
-
const rendered = part.replace(/\*\*(.*?)\*\*/g, (_, t) =>
|
|
23125
|
+
const rendered = part.replace(/\*\*(.*?)\*\*/g, (_, t) => chalk8__default.default.bold(t)).replace(/`(.*?)`/g, (_, t) => chalk8__default.default.bgHex(theme.colors.border)(t));
|
|
22320
23126
|
return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { wrap: "wrap", children: rendered }, i);
|
|
22321
23127
|
});
|
|
22322
23128
|
}
|
|
@@ -22760,9 +23566,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, s
|
|
|
22760
23566
|
}
|
|
22761
23567
|
} catch {
|
|
22762
23568
|
}
|
|
22763
|
-
const configPath =
|
|
23569
|
+
const configPath = path35__default.default.join(workspacePath, ".cascade", "config.json");
|
|
22764
23570
|
try {
|
|
22765
|
-
await fs11__default.default.mkdir(
|
|
23571
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(configPath), { recursive: true });
|
|
22766
23572
|
await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
22767
23573
|
} catch (err) {
|
|
22768
23574
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -22814,9 +23620,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, s
|
|
|
22814
23620
|
if (msg.includes("non-text parts")) return;
|
|
22815
23621
|
originalLog(...args);
|
|
22816
23622
|
};
|
|
22817
|
-
const store = new MemoryStore(
|
|
23623
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
22818
23624
|
storeRef.current = store;
|
|
22819
|
-
globalStoreRef.current = new MemoryStore(
|
|
23625
|
+
globalStoreRef.current = new MemoryStore(path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
22820
23626
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
22821
23627
|
setIdentities(identityRows);
|
|
22822
23628
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -22896,7 +23702,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, s
|
|
|
22896
23702
|
onThemeChange: (name) => setTheme(getTheme(name)),
|
|
22897
23703
|
onExport: async (fmt) => {
|
|
22898
23704
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
22899
|
-
const exportPath =
|
|
23705
|
+
const exportPath = path35__default.default.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
|
|
22900
23706
|
if (fmt === "json") {
|
|
22901
23707
|
await fs11__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
|
|
22902
23708
|
} else {
|
|
@@ -23804,7 +24610,7 @@ function stringifySlashOutput(val) {
|
|
|
23804
24610
|
}
|
|
23805
24611
|
async function searchSessionsAndMessages(query, workspacePath) {
|
|
23806
24612
|
if (!query) return "Usage: /search <query>";
|
|
23807
|
-
const dbPath =
|
|
24613
|
+
const dbPath = path35__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
23808
24614
|
try {
|
|
23809
24615
|
await fs11__default.default.access(dbPath);
|
|
23810
24616
|
} catch {
|
|
@@ -23838,7 +24644,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
|
|
|
23838
24644
|
async function diagnoseRuntime(config, workspacePath) {
|
|
23839
24645
|
const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
|
|
23840
24646
|
const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
|
|
23841
|
-
const store = new MemoryStore(
|
|
24647
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
23842
24648
|
try {
|
|
23843
24649
|
const sessions2 = store.listSessions(void 0, 3);
|
|
23844
24650
|
return [
|
|
@@ -23857,7 +24663,7 @@ async function diagnoseRuntime(config, workspacePath) {
|
|
|
23857
24663
|
}
|
|
23858
24664
|
async function showRecentLogs(args, workspacePath) {
|
|
23859
24665
|
const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
|
|
23860
|
-
const store = new MemoryStore(
|
|
24666
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
23861
24667
|
try {
|
|
23862
24668
|
const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
|
|
23863
24669
|
if (!logs.length) return "No recent runtime logs.";
|
|
@@ -23869,7 +24675,7 @@ async function showRecentLogs(args, workspacePath) {
|
|
|
23869
24675
|
async function loadSessionSnapshot(args, workspacePath) {
|
|
23870
24676
|
const sessionId = args[0];
|
|
23871
24677
|
if (!sessionId) return "Usage: /resume <sessionId>";
|
|
23872
|
-
const store = new MemoryStore(
|
|
24678
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
23873
24679
|
try {
|
|
23874
24680
|
const session = store.getSession(sessionId);
|
|
23875
24681
|
if (!session) return `Session not found: ${sessionId}`;
|
|
@@ -24289,9 +25095,9 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
24289
25095
|
...anyAuto ? { cascadeAuto: true } : {}
|
|
24290
25096
|
};
|
|
24291
25097
|
const config = CascadeConfigSchema.parse(rawConfig);
|
|
24292
|
-
const configDir =
|
|
25098
|
+
const configDir = path35__default.default.join(workspacePath, ".cascade");
|
|
24293
25099
|
await fs11__default.default.mkdir(configDir, { recursive: true });
|
|
24294
|
-
const configPath =
|
|
25100
|
+
const configPath = path35__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
24295
25101
|
await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
24296
25102
|
savedConfigRef.current = config;
|
|
24297
25103
|
dispatchRef.current({ type: "GO_DONE" });
|
|
@@ -24632,25 +25438,25 @@ function printTelemetryBanner() {
|
|
|
24632
25438
|
async function initCommand(workspacePath = process.cwd()) {
|
|
24633
25439
|
const spin = ora__default.default({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
|
|
24634
25440
|
try {
|
|
24635
|
-
const configDir =
|
|
25441
|
+
const configDir = path35__default.default.join(workspacePath, ".cascade");
|
|
24636
25442
|
await fs11__default.default.mkdir(configDir, { recursive: true });
|
|
24637
|
-
const mdPath =
|
|
25443
|
+
const mdPath = path35__default.default.join(workspacePath, "CASCADE.md");
|
|
24638
25444
|
if (!await fileExists(mdPath)) {
|
|
24639
25445
|
await createDefaultCascadeMd(workspacePath);
|
|
24640
|
-
spin.succeed(
|
|
25446
|
+
spin.succeed(chalk8__default.default.green("Created CASCADE.md"));
|
|
24641
25447
|
}
|
|
24642
|
-
const ignorePath =
|
|
25448
|
+
const ignorePath = path35__default.default.join(workspacePath, ".cascadeignore");
|
|
24643
25449
|
if (!await fileExists(ignorePath)) {
|
|
24644
25450
|
await createDefaultIgnoreFile(workspacePath);
|
|
24645
|
-
spin.succeed(
|
|
25451
|
+
spin.succeed(chalk8__default.default.green("Created .cascadeignore"));
|
|
24646
25452
|
}
|
|
24647
25453
|
spin.stop();
|
|
24648
25454
|
console.log();
|
|
24649
|
-
console.log(
|
|
25455
|
+
console.log(chalk8__default.default.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
|
|
24650
25456
|
console.log();
|
|
24651
|
-
const configPath =
|
|
25457
|
+
const configPath = path35__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
24652
25458
|
if (await fileExists(configPath)) {
|
|
24653
|
-
console.log(
|
|
25459
|
+
console.log(chalk8__default.default.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
|
|
24654
25460
|
console.log();
|
|
24655
25461
|
}
|
|
24656
25462
|
const config = await runSetupWizard(workspacePath);
|
|
@@ -24658,10 +25464,10 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
24658
25464
|
await cm.load();
|
|
24659
25465
|
await cm.updateConfig(config);
|
|
24660
25466
|
console.log();
|
|
24661
|
-
console.log(
|
|
25467
|
+
console.log(chalk8__default.default.green(" \u25C8 Setup complete! Run `cascade` to start."));
|
|
24662
25468
|
console.log();
|
|
24663
25469
|
} catch (err) {
|
|
24664
|
-
console.error(
|
|
25470
|
+
console.error(chalk8__default.default.red(`Init failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
24665
25471
|
process.exit(1);
|
|
24666
25472
|
}
|
|
24667
25473
|
}
|
|
@@ -24676,7 +25482,20 @@ async function fileExists(p) {
|
|
|
24676
25482
|
|
|
24677
25483
|
// src/cli/commands/doctor.ts
|
|
24678
25484
|
init_constants();
|
|
25485
|
+
|
|
25486
|
+
// src/config/credential-discovery.ts
|
|
25487
|
+
init_revoked_credentials();
|
|
24679
25488
|
var TOS_WARNING = "Reusing this subscription token outside its own CLI may violate the vendor\u2019s terms of service.";
|
|
25489
|
+
var ANTHROPIC_OAUTH_WARNING = "Anthropic does not permit third-party tools to route requests through Claude subscription credentials, and blocks them server-side. Use an API key from the Claude Console, or ANTHROPIC_AUTH_TOKEN with a gateway. See code.claude.com/docs/en/legal-and-compliance.";
|
|
25490
|
+
var OPENAI_COMPATIBLE_ENV = [
|
|
25491
|
+
{ id: "openrouter", env: "OPENROUTER_API_KEY", label: "OpenRouter", baseUrl: "https://openrouter.ai/api/v1" },
|
|
25492
|
+
{ id: "groq", env: "GROQ_API_KEY", label: "Groq", baseUrl: "https://api.groq.com/openai/v1" },
|
|
25493
|
+
{ id: "deepseek", env: "DEEPSEEK_API_KEY", label: "DeepSeek", baseUrl: "https://api.deepseek.com/v1" },
|
|
25494
|
+
{ id: "xai", env: "XAI_API_KEY", label: "xAI", baseUrl: "https://api.x.ai/v1" },
|
|
25495
|
+
{ id: "mistral", env: "MISTRAL_API_KEY", label: "Mistral", baseUrl: "https://api.mistral.ai/v1" },
|
|
25496
|
+
{ id: "together", env: "TOGETHER_API_KEY", label: "Together AI", baseUrl: "https://api.together.xyz/v1" },
|
|
25497
|
+
{ id: "fireworks", env: "FIREWORKS_API_KEY", label: "Fireworks AI", baseUrl: "https://api.fireworks.ai/inference/v1" }
|
|
25498
|
+
];
|
|
24680
25499
|
async function readJson(file) {
|
|
24681
25500
|
try {
|
|
24682
25501
|
const raw = await fs11__default.default.readFile(file, "utf-8");
|
|
@@ -24700,36 +25519,118 @@ function fromEnv(env) {
|
|
|
24700
25519
|
for (const { env: name, provider } of map) {
|
|
24701
25520
|
const secret = str(env[name]);
|
|
24702
25521
|
if (!secret || seen.has(provider)) continue;
|
|
25522
|
+
if (isSubscriptionToken(secret)) {
|
|
25523
|
+
out.push({
|
|
25524
|
+
provider,
|
|
25525
|
+
sourceTool: `Environment (${name})`,
|
|
25526
|
+
kind: "oauth",
|
|
25527
|
+
secret,
|
|
25528
|
+
directlyUsable: false,
|
|
25529
|
+
warning: ANTHROPIC_OAUTH_WARNING
|
|
25530
|
+
});
|
|
25531
|
+
continue;
|
|
25532
|
+
}
|
|
24703
25533
|
seen.add(provider);
|
|
24704
|
-
|
|
25534
|
+
const gateway = provider === "anthropic" ? str(env["ANTHROPIC_BASE_URL"]) : void 0;
|
|
25535
|
+
out.push({
|
|
25536
|
+
provider,
|
|
25537
|
+
sourceTool: `Environment (${name})`,
|
|
25538
|
+
kind: "api-key",
|
|
25539
|
+
secret,
|
|
25540
|
+
directlyUsable: true,
|
|
25541
|
+
...gateway ? { baseUrl: gateway } : {}
|
|
25542
|
+
});
|
|
25543
|
+
}
|
|
25544
|
+
const authToken = str(env["ANTHROPIC_AUTH_TOKEN"]);
|
|
25545
|
+
if (authToken && !seen.has("anthropic")) {
|
|
25546
|
+
seen.add("anthropic");
|
|
25547
|
+
if (isSubscriptionToken(authToken)) {
|
|
25548
|
+
out.push({
|
|
25549
|
+
provider: "anthropic",
|
|
25550
|
+
sourceTool: "Environment (ANTHROPIC_AUTH_TOKEN)",
|
|
25551
|
+
kind: "oauth",
|
|
25552
|
+
secret: authToken,
|
|
25553
|
+
directlyUsable: false,
|
|
25554
|
+
warning: ANTHROPIC_OAUTH_WARNING
|
|
25555
|
+
});
|
|
25556
|
+
} else {
|
|
25557
|
+
const gateway = str(env["ANTHROPIC_BASE_URL"]);
|
|
25558
|
+
out.push({
|
|
25559
|
+
provider: "anthropic",
|
|
25560
|
+
sourceTool: "Environment (ANTHROPIC_AUTH_TOKEN)",
|
|
25561
|
+
kind: "bearer",
|
|
25562
|
+
secret: authToken,
|
|
25563
|
+
directlyUsable: Boolean(gateway),
|
|
25564
|
+
...gateway ? { baseUrl: gateway } : {},
|
|
25565
|
+
...gateway ? {} : {
|
|
25566
|
+
warning: "A bearer token is only valid at the gateway that issued it. Set ANTHROPIC_BASE_URL to that gateway, or configure `baseUrl` for the anthropic provider, before linking it."
|
|
25567
|
+
}
|
|
25568
|
+
});
|
|
25569
|
+
}
|
|
25570
|
+
}
|
|
25571
|
+
const azureKey = str(env["AZURE_OPENAI_KEY"]) ?? str(env["AZURE_OPENAI_API_KEY"]);
|
|
25572
|
+
if (azureKey) {
|
|
25573
|
+
const endpoint = str(env["AZURE_OPENAI_ENDPOINT"]);
|
|
25574
|
+
const deployment = str(env["AZURE_OPENAI_DEPLOYMENT"]) ?? str(env["AZURE_OPENAI_DEPLOYMENT_NAME"]);
|
|
25575
|
+
const apiVersion = str(env["AZURE_OPENAI_API_VERSION"]);
|
|
25576
|
+
const missing = [
|
|
25577
|
+
!endpoint && "AZURE_OPENAI_ENDPOINT",
|
|
25578
|
+
!deployment && "AZURE_OPENAI_DEPLOYMENT"
|
|
25579
|
+
].filter((x) => typeof x === "string");
|
|
25580
|
+
out.push({
|
|
25581
|
+
provider: "azure",
|
|
25582
|
+
sourceTool: "Environment (AZURE_OPENAI_KEY)",
|
|
25583
|
+
kind: "api-key",
|
|
25584
|
+
secret: azureKey,
|
|
25585
|
+
directlyUsable: missing.length === 0,
|
|
25586
|
+
...endpoint ? { baseUrl: endpoint } : {},
|
|
25587
|
+
...deployment ? { deploymentName: deployment } : {},
|
|
25588
|
+
...apiVersion ? { apiVersion } : {},
|
|
25589
|
+
...missing.length ? { warning: `Azure also needs ${missing.join(" and ")} \u2014 a key alone cannot address a deployment.` } : {}
|
|
25590
|
+
});
|
|
25591
|
+
}
|
|
25592
|
+
for (const { id, env: name, label, baseUrl } of OPENAI_COMPATIBLE_ENV) {
|
|
25593
|
+
const secret = str(env[name]);
|
|
25594
|
+
if (!secret) continue;
|
|
25595
|
+
out.push({
|
|
25596
|
+
provider: "openai-compatible",
|
|
25597
|
+
sourceTool: `Environment (${name}) \u2014 ${label}`,
|
|
25598
|
+
kind: "api-key",
|
|
25599
|
+
secret,
|
|
25600
|
+
directlyUsable: true,
|
|
25601
|
+
baseUrl,
|
|
25602
|
+
serviceId: id
|
|
25603
|
+
});
|
|
24705
25604
|
}
|
|
24706
25605
|
return out;
|
|
24707
25606
|
}
|
|
24708
|
-
async function fromClaudeCode(home) {
|
|
24709
|
-
const
|
|
25607
|
+
async function fromClaudeCode(home, env) {
|
|
25608
|
+
const dir = str(env["CLAUDE_CONFIG_DIR"]) ?? path35__default.default.join(home, ".claude");
|
|
25609
|
+
const file = path35__default.default.join(dir, ".credentials.json");
|
|
24710
25610
|
const data = await readJson(file);
|
|
24711
25611
|
if (!data) return [];
|
|
25612
|
+
const found = [];
|
|
25613
|
+
const apiKey = str(data["apiKey"]) ?? str(data["anthropicApiKey"]);
|
|
25614
|
+
if (apiKey) {
|
|
25615
|
+
found.push({ provider: "anthropic", sourceTool: "Claude Code", kind: "api-key", secret: apiKey, directlyUsable: true, sourcePath: file });
|
|
25616
|
+
}
|
|
24712
25617
|
const oauth = data["claudeAiOauth"];
|
|
24713
25618
|
const oauthToken = str(oauth?.["accessToken"]);
|
|
24714
25619
|
if (oauthToken) {
|
|
24715
|
-
|
|
25620
|
+
found.push({
|
|
24716
25621
|
provider: "anthropic",
|
|
24717
25622
|
sourceTool: "Claude Code",
|
|
24718
25623
|
kind: "oauth",
|
|
24719
25624
|
secret: oauthToken,
|
|
24720
|
-
directlyUsable:
|
|
24721
|
-
warning:
|
|
25625
|
+
directlyUsable: false,
|
|
25626
|
+
warning: ANTHROPIC_OAUTH_WARNING,
|
|
24722
25627
|
sourcePath: file
|
|
24723
|
-
}
|
|
24724
|
-
}
|
|
24725
|
-
const apiKey = str(data["apiKey"]) ?? str(data["anthropicApiKey"]);
|
|
24726
|
-
if (apiKey) {
|
|
24727
|
-
return [{ provider: "anthropic", sourceTool: "Claude Code", kind: "api-key", secret: apiKey, directlyUsable: true, sourcePath: file }];
|
|
25628
|
+
});
|
|
24728
25629
|
}
|
|
24729
|
-
return
|
|
25630
|
+
return found;
|
|
24730
25631
|
}
|
|
24731
25632
|
async function fromCodex(home) {
|
|
24732
|
-
const file =
|
|
25633
|
+
const file = path35__default.default.join(home, ".codex", "auth.json");
|
|
24733
25634
|
const data = await readJson(file);
|
|
24734
25635
|
if (!data) return [];
|
|
24735
25636
|
const apiKey = str(data["OPENAI_API_KEY"]);
|
|
@@ -24752,7 +25653,7 @@ async function fromCodex(home) {
|
|
|
24752
25653
|
return [];
|
|
24753
25654
|
}
|
|
24754
25655
|
async function fromGemini(home) {
|
|
24755
|
-
const file =
|
|
25656
|
+
const file = path35__default.default.join(home, ".gemini", "oauth_creds.json");
|
|
24756
25657
|
const data = await readJson(file);
|
|
24757
25658
|
const accessToken = str(data?.["access_token"]);
|
|
24758
25659
|
if (!accessToken) return [];
|
|
@@ -24768,7 +25669,7 @@ async function fromGemini(home) {
|
|
|
24768
25669
|
}
|
|
24769
25670
|
async function fromCopilot(home) {
|
|
24770
25671
|
for (const name of ["apps.json", "hosts.json"]) {
|
|
24771
|
-
const file =
|
|
25672
|
+
const file = path35__default.default.join(home, ".config", "github-copilot", name);
|
|
24772
25673
|
const data = await readJson(file);
|
|
24773
25674
|
if (!data) continue;
|
|
24774
25675
|
for (const value of Object.values(data)) {
|
|
@@ -24793,7 +25694,7 @@ async function discoverCredentials(opts = {}) {
|
|
|
24793
25694
|
const env = opts.env ?? process.env;
|
|
24794
25695
|
const groups = await Promise.all([
|
|
24795
25696
|
Promise.resolve(fromEnv(env)),
|
|
24796
|
-
fromClaudeCode(home),
|
|
25697
|
+
fromClaudeCode(home, env),
|
|
24797
25698
|
fromCodex(home),
|
|
24798
25699
|
fromGemini(home),
|
|
24799
25700
|
fromCopilot(home)
|
|
@@ -24804,10 +25705,257 @@ function maskSecret(secret) {
|
|
|
24804
25705
|
if (secret.length <= 8) return "\u2022\u2022\u2022\u2022";
|
|
24805
25706
|
return `${secret.slice(0, 6)}\u2026${secret.slice(-2)}`;
|
|
24806
25707
|
}
|
|
25708
|
+
async function linkCommand(target, options = {}) {
|
|
25709
|
+
const found = await discoverCredentials();
|
|
25710
|
+
if (found.length === 0) {
|
|
25711
|
+
console.log(chalk8__default.default.yellow("\n No reusable credentials found.\n"));
|
|
25712
|
+
console.log(chalk8__default.default.gray(" Cascade looks for Claude Code, Codex, Gemini CLI, and GitHub Copilot logins,"));
|
|
25713
|
+
console.log(chalk8__default.default.gray(" plus ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY in your environment.\n"));
|
|
25714
|
+
return;
|
|
25715
|
+
}
|
|
25716
|
+
if (!target) {
|
|
25717
|
+
printDiscovered(found);
|
|
25718
|
+
return;
|
|
25719
|
+
}
|
|
25720
|
+
const resolved = normalizeProvider(target);
|
|
25721
|
+
if (!resolved) {
|
|
25722
|
+
const services2 = OPENAI_COMPATIBLE_ENV.map((s) => s.id).join(", ");
|
|
25723
|
+
console.log(chalk8__default.default.red(`
|
|
25724
|
+
Unknown provider "${target}".`));
|
|
25725
|
+
console.log(chalk8__default.default.gray(` Use one of: anthropic, openai, gemini, azure, openai-compatible`));
|
|
25726
|
+
console.log(chalk8__default.default.gray(` \u2026or name a compatible service directly: ${services2}
|
|
25727
|
+
`));
|
|
25728
|
+
return;
|
|
25729
|
+
}
|
|
25730
|
+
const { provider, serviceId } = resolved;
|
|
25731
|
+
const candidates2 = found.filter((c) => c.provider === provider && (!serviceId || c.serviceId === serviceId));
|
|
25732
|
+
const services = [...new Set(candidates2.map((c) => c.serviceId).filter((id) => !!id))];
|
|
25733
|
+
if (!serviceId && services.length > 1) {
|
|
25734
|
+
console.log(chalk8__default.default.yellow(`
|
|
25735
|
+
Keys for several OpenAI-compatible services are set, and "${target}" does not say which.`));
|
|
25736
|
+
console.log(chalk8__default.default.gray(" Name the one you mean:\n"));
|
|
25737
|
+
for (const id of services) console.log(chalk8__default.default.cyan(` cascade link ${id}`));
|
|
25738
|
+
console.log("");
|
|
25739
|
+
return;
|
|
25740
|
+
}
|
|
25741
|
+
const chosen = candidates2.find((c) => c.directlyUsable) ?? candidates2[0];
|
|
25742
|
+
if (!chosen) {
|
|
25743
|
+
console.log(chalk8__default.default.yellow(`
|
|
25744
|
+
No detected credential maps to "${provider}".
|
|
25745
|
+
`));
|
|
25746
|
+
return;
|
|
25747
|
+
}
|
|
25748
|
+
const cm = new ConfigManager(options.workspace ?? process.cwd());
|
|
25749
|
+
await cm.load();
|
|
25750
|
+
const configured = cm.getConfig().providers;
|
|
25751
|
+
const routedByConfig = isRoutedByConfig(chosen, configured);
|
|
25752
|
+
if (!chosen.directlyUsable && !routedByConfig) {
|
|
25753
|
+
console.log(chalk8__default.default.yellow(`
|
|
25754
|
+
Found a ${chosen.sourceTool} credential, but it can't be used against the standard ${provider} API.`));
|
|
25755
|
+
if (chosen.warning) console.log(chalk8__default.default.gray(` ${chosen.warning}`));
|
|
25756
|
+
console.log(chalk8__default.default.gray(" Cascade won't adopt it because it would create a non-working provider.\n"));
|
|
25757
|
+
if (options.acceptRisk && chosen.kind === "oauth") {
|
|
25758
|
+
console.log(chalk8__default.default.gray(" --accept-risk no longer applies \u2014 a subscription token is refused by the"));
|
|
25759
|
+
console.log(chalk8__default.default.gray(" provider itself, so there is no working configuration to opt into.\n"));
|
|
25760
|
+
}
|
|
25761
|
+
console.log(chalk8__default.default.gray(` Set ${chalk8__default.default.white(envKeyFor(provider))} instead, or add a key with `) + chalk8__default.default.cyan("cascade init") + chalk8__default.default.gray(".\n"));
|
|
25762
|
+
return;
|
|
25763
|
+
}
|
|
25764
|
+
const adopted = await adoptCredential(chosen, cm);
|
|
25765
|
+
if (!adopted) return;
|
|
25766
|
+
console.log(chalk8__default.default.green(`
|
|
25767
|
+
\u2713 Linked ${provider} using your ${chosen.sourceTool} credential (${maskSecret(chosen.secret)}).`));
|
|
25768
|
+
if (chosen.kind === "bearer") {
|
|
25769
|
+
console.log(chalk8__default.default.gray(" Adopted as a bearer token \u2014 set `baseUrl` to the gateway that issued it."));
|
|
25770
|
+
}
|
|
25771
|
+
console.log(chalk8__default.default.gray(" Run `cascade doctor` to verify, or `cascade` to start.\n"));
|
|
25772
|
+
}
|
|
25773
|
+
function isRoutedByConfig(cred, configured) {
|
|
25774
|
+
if (cred.provider === "azure") {
|
|
25775
|
+
return configured.some((p) => p.type === "azure" && p.deploymentName?.trim() && p.baseUrl?.trim());
|
|
25776
|
+
}
|
|
25777
|
+
return cred.kind === "bearer" && configured.some((p) => p.type === cred.provider && p.baseUrl?.trim());
|
|
25778
|
+
}
|
|
25779
|
+
function willAdoptFromConfig(cred, configured) {
|
|
25780
|
+
if (cred.provider === "azure") {
|
|
25781
|
+
const target = cred.baseUrl?.trim();
|
|
25782
|
+
const deployment = cred.deploymentName?.trim();
|
|
25783
|
+
if (target && deployment) return !azureRoutedTarget(deployment, target, configured).collision;
|
|
25784
|
+
return azureDeploymentsForCredential(cred, configured).length > 0;
|
|
25785
|
+
}
|
|
25786
|
+
return cred.directlyUsable || isRoutedByConfig(cred, configured);
|
|
25787
|
+
}
|
|
25788
|
+
function azureRoutedTarget(deployment, target, configured) {
|
|
25789
|
+
const named = (p) => p.type === "azure" && (p.deploymentName?.trim() ?? "") === deployment;
|
|
25790
|
+
const existing = configured.find((p) => named(p) && (sameAzureEndpoint(p.baseUrl, target) || !p.baseUrl?.trim()));
|
|
25791
|
+
if (existing) return { existing, collision: false };
|
|
25792
|
+
return {
|
|
25793
|
+
collision: configured.some((p) => named(p) && !!p.baseUrl?.trim() && !sameAzureEndpoint(p.baseUrl, target))
|
|
25794
|
+
};
|
|
25795
|
+
}
|
|
25796
|
+
function azureDeploymentsForCredential(cred, configured) {
|
|
25797
|
+
const routing = resolveAzureRouting(configured, configured, {
|
|
25798
|
+
endpoint: cred.baseUrl,
|
|
25799
|
+
deployment: cred.deploymentName
|
|
25800
|
+
});
|
|
25801
|
+
return routing.ok ? routing.rows : [];
|
|
25802
|
+
}
|
|
25803
|
+
function azureRoutingForCredential(cred, configured) {
|
|
25804
|
+
return resolveAzureRouting(configured, configured, {
|
|
25805
|
+
endpoint: cred.baseUrl,
|
|
25806
|
+
deployment: cred.deploymentName
|
|
25807
|
+
});
|
|
25808
|
+
}
|
|
25809
|
+
function printDiscovered(found) {
|
|
25810
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Detected credentials\n"));
|
|
25811
|
+
for (const c of found) {
|
|
25812
|
+
const usable = c.directlyUsable ? chalk8__default.default.green("usable") : chalk8__default.default.yellow("needs vendor backend");
|
|
25813
|
+
const kind = c.kind === "oauth" ? chalk8__default.default.yellow("subscription") : c.kind === "bearer" ? chalk8__default.default.cyan("bearer") : chalk8__default.default.gray("api-key");
|
|
25814
|
+
console.log(` ${chalk8__default.default.white(c.provider.padEnd(18))} ${chalk8__default.default.gray(maskSecret(c.secret).padEnd(12))} ${kind} ${usable}`);
|
|
25815
|
+
console.log(chalk8__default.default.gray(` from ${c.sourceTool}`));
|
|
25816
|
+
if (c.warning) console.log(chalk8__default.default.yellow(` \u26A0 ${c.warning}`));
|
|
25817
|
+
}
|
|
25818
|
+
console.log(chalk8__default.default.gray("\n Adopt one with: ") + chalk8__default.default.cyan("cascade link <provider>"));
|
|
25819
|
+
console.log(chalk8__default.default.gray(" Subscription tokens are listed for visibility and cannot be adopted.\n"));
|
|
25820
|
+
}
|
|
25821
|
+
function envKeyFor(provider) {
|
|
25822
|
+
if (provider === "openai") return "OPENAI_API_KEY";
|
|
25823
|
+
if (provider === "gemini") return "GEMINI_API_KEY";
|
|
25824
|
+
if (provider === "anthropic") return "ANTHROPIC_API_KEY";
|
|
25825
|
+
if (provider === "azure") return "AZURE_OPENAI_KEY";
|
|
25826
|
+
return "the provider's API key";
|
|
25827
|
+
}
|
|
25828
|
+
function normalizeProvider(target) {
|
|
25829
|
+
const t = target.toLowerCase();
|
|
25830
|
+
if (t === "anthropic" || t === "claude" || t === "claude-code") return { provider: "anthropic" };
|
|
25831
|
+
if (t === "openai" || t === "codex" || t === "gpt") return { provider: "openai" };
|
|
25832
|
+
if (t === "gemini" || t === "google") return { provider: "gemini" };
|
|
25833
|
+
if (t === "azure" || t === "azure-openai") return { provider: "azure" };
|
|
25834
|
+
if (t === "openai-compatible" || t === "compatible") return { provider: "openai-compatible" };
|
|
25835
|
+
const service = OPENAI_COMPATIBLE_ENV.find((s) => s.id === t || s.label.toLowerCase() === t);
|
|
25836
|
+
if (service) return { provider: "openai-compatible", serviceId: service.id };
|
|
25837
|
+
return null;
|
|
25838
|
+
}
|
|
25839
|
+
async function adoptCredential(cred, cm) {
|
|
25840
|
+
const config = cm.getConfig();
|
|
25841
|
+
const azureDeployments = cred.provider === "azure" ? config.providers.filter((p) => p.type === "azure" && p.deploymentName?.trim()) : [];
|
|
25842
|
+
if (azureDeployments.length > 0) {
|
|
25843
|
+
const target = cred.baseUrl?.trim();
|
|
25844
|
+
const deployment = cred.deploymentName?.trim();
|
|
25845
|
+
if (target && deployment) {
|
|
25846
|
+
const { existing: existing2, collision: claimedElsewhere } = azureRoutedTarget(deployment, target, config.providers);
|
|
25847
|
+
if (claimedElsewhere) {
|
|
25848
|
+
console.log(chalk8__default.default.yellow(`
|
|
25849
|
+
A different Azure resource already has a deployment named "${deployment}".`));
|
|
25850
|
+
console.log(chalk8__default.default.gray(" Deployment names are model ids in Cascade, so two resources cannot share one \u2014"));
|
|
25851
|
+
console.log(chalk8__default.default.gray(" the second would never be selected. Rename one deployment, or remove the entry"));
|
|
25852
|
+
console.log(chalk8__default.default.gray(" you no longer use, then run this again.\n"));
|
|
25853
|
+
return false;
|
|
25854
|
+
}
|
|
25855
|
+
const patch2 = {
|
|
25856
|
+
apiKey: cred.secret,
|
|
25857
|
+
credentialSource: cred.sourceTool,
|
|
25858
|
+
...cred.apiVersion ? { apiVersion: cred.apiVersion } : {}
|
|
25859
|
+
};
|
|
25860
|
+
const updated = config.providers.map((p) => {
|
|
25861
|
+
if (p === existing2) return { ...p, baseUrl: target, ...patch2 };
|
|
25862
|
+
const sibling = p.type === "azure" && p.deploymentName?.trim() && sameAzureEndpoint(p.baseUrl, target);
|
|
25863
|
+
return sibling ? { ...p, ...patch2 } : p;
|
|
25864
|
+
});
|
|
25865
|
+
const providers3 = existing2 ? updated : [...updated, {
|
|
25866
|
+
type: "azure",
|
|
25867
|
+
baseUrl: target,
|
|
25868
|
+
deploymentName: deployment,
|
|
25869
|
+
...patch2
|
|
25870
|
+
}];
|
|
25871
|
+
await cm.updateConfig({ providers: providers3 });
|
|
25872
|
+
return true;
|
|
25873
|
+
}
|
|
25874
|
+
const scoped = azureDeploymentsForCredential(cred, config.providers);
|
|
25875
|
+
if (scoped.length === 0) {
|
|
25876
|
+
const all = [...new Set(azureDeployments.map((p) => normalizeAzureEndpoint(p.baseUrl)))];
|
|
25877
|
+
const onTarget = target ? azureDeployments.some((p) => sameAzureEndpoint(p.baseUrl, target)) : true;
|
|
25878
|
+
const anyRouted = azureDeployments.some((p) => p.baseUrl?.trim());
|
|
25879
|
+
if (!anyRouted) {
|
|
25880
|
+
console.log(chalk8__default.default.yellow("\n The configured Azure deployments have no endpoint."));
|
|
25881
|
+
console.log(chalk8__default.default.gray(" A deployment needs its resource URL before a key can reach it \u2014"));
|
|
25882
|
+
console.log(chalk8__default.default.gray(" set AZURE_OPENAI_ENDPOINT, or add `baseUrl` to the entries in"));
|
|
25883
|
+
console.log(chalk8__default.default.gray(" .cascade/config.json, then run this again.\n"));
|
|
25884
|
+
return false;
|
|
25885
|
+
}
|
|
25886
|
+
if (target && !onTarget) {
|
|
25887
|
+
console.log(chalk8__default.default.yellow(`
|
|
25888
|
+
No configured Azure deployment is on ${target}.`));
|
|
25889
|
+
console.log(chalk8__default.default.gray(" Set AZURE_OPENAI_DEPLOYMENT as well to add one, or point"));
|
|
25890
|
+
console.log(chalk8__default.default.gray(" AZURE_OPENAI_ENDPOINT at a resource you have configured:\n"));
|
|
25891
|
+
} else {
|
|
25892
|
+
console.log(chalk8__default.default.yellow("\n Several Azure resources are configured, and an Azure key belongs to one of them."));
|
|
25893
|
+
console.log(chalk8__default.default.gray(" Set AZURE_OPENAI_ENDPOINT to the resource this key is for, then run again:\n"));
|
|
25894
|
+
}
|
|
25895
|
+
for (const r of all) console.log(chalk8__default.default.gray(` ${r || "(no endpoint set)"}`));
|
|
25896
|
+
console.log("");
|
|
25897
|
+
return false;
|
|
25898
|
+
}
|
|
25899
|
+
const patch = {
|
|
25900
|
+
apiKey: cred.secret,
|
|
25901
|
+
credentialSource: cred.sourceTool,
|
|
25902
|
+
...cred.apiVersion ? { apiVersion: cred.apiVersion } : {}
|
|
25903
|
+
};
|
|
25904
|
+
const providers2 = config.providers.map((p) => scoped.includes(p) ? { ...p, ...patch } : p);
|
|
25905
|
+
const routing = azureRoutingForCredential(cred, config.providers);
|
|
25906
|
+
if (routing.ok && routing.createDeployment) {
|
|
25907
|
+
providers2.push({
|
|
25908
|
+
type: "azure",
|
|
25909
|
+
deploymentName: routing.createDeployment,
|
|
25910
|
+
baseUrl: routing.resource,
|
|
25911
|
+
...patch
|
|
25912
|
+
});
|
|
25913
|
+
console.log(chalk8__default.default.gray(
|
|
25914
|
+
` Added deployment "${routing.createDeployment}" on ${normalizeAzureEndpoint(routing.resource)}.`
|
|
25915
|
+
));
|
|
25916
|
+
}
|
|
25917
|
+
await cm.updateConfig({ providers: providers2 });
|
|
25918
|
+
return true;
|
|
25919
|
+
}
|
|
25920
|
+
const existing = config.providers.find((p) => p.type === cred.provider);
|
|
25921
|
+
const next = {
|
|
25922
|
+
...existing,
|
|
25923
|
+
type: cred.provider,
|
|
25924
|
+
credentialSource: cred.sourceTool,
|
|
25925
|
+
// Both cleared first: adopting a credential REPLACES the old one, and
|
|
25926
|
+
// leaving a stale key beside a new token makes which one is in use depend
|
|
25927
|
+
// on provider-internal precedence.
|
|
25928
|
+
apiKey: void 0,
|
|
25929
|
+
authToken: void 0
|
|
25930
|
+
};
|
|
25931
|
+
if (cred.kind === "bearer") {
|
|
25932
|
+
next.authToken = cred.secret;
|
|
25933
|
+
} else {
|
|
25934
|
+
next.apiKey = cred.secret;
|
|
25935
|
+
}
|
|
25936
|
+
if (cred.kind !== "bearer" && !cred.baseUrl && hasDefaultEndpoint(cred.provider) && next.baseUrl) {
|
|
25937
|
+
delete next.baseUrl;
|
|
25938
|
+
delete next.local;
|
|
25939
|
+
}
|
|
25940
|
+
if (cred.baseUrl) {
|
|
25941
|
+
next.baseUrl = cred.baseUrl;
|
|
25942
|
+
delete next.local;
|
|
25943
|
+
}
|
|
25944
|
+
if (cred.deploymentName) next.deploymentName = cred.deploymentName;
|
|
25945
|
+
if (cred.apiVersion) next.apiVersion = cred.apiVersion;
|
|
25946
|
+
const providers = config.providers.filter((p) => p.type !== cred.provider);
|
|
25947
|
+
providers.push(next);
|
|
25948
|
+
await cm.updateConfig({ providers });
|
|
25949
|
+
return true;
|
|
25950
|
+
}
|
|
24807
25951
|
|
|
24808
25952
|
// src/cli/commands/doctor.ts
|
|
25953
|
+
function linkableCredentialsDetail(discovered, configured = []) {
|
|
25954
|
+
const usable = discovered.filter((d) => willAdoptFromConfig(d, configured)).length;
|
|
25955
|
+
return usable > 0 ? `${discovered.length} found (${usable} usable) \u2014 run \`cascade link\` to adopt` : `${discovered.length} found, none usable \u2014 run \`cascade link\` to see why`;
|
|
25956
|
+
}
|
|
24809
25957
|
async function doctorCommand() {
|
|
24810
|
-
console.log(
|
|
25958
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Cascade Doctor \u2014 System Diagnostics\n"));
|
|
24811
25959
|
const checks = [];
|
|
24812
25960
|
const nodeVersion = process.versions.node;
|
|
24813
25961
|
const [major] = nodeVersion.split(".").map(Number);
|
|
@@ -24822,7 +25970,7 @@ async function doctorCommand() {
|
|
|
24822
25970
|
checks.push({
|
|
24823
25971
|
label: "Cascade config",
|
|
24824
25972
|
ok: true,
|
|
24825
|
-
detail: `Loaded ${
|
|
25973
|
+
detail: `Loaded ${path35__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
|
|
24826
25974
|
});
|
|
24827
25975
|
const providers = [
|
|
24828
25976
|
{ type: "anthropic", name: "Anthropic" },
|
|
@@ -24832,10 +25980,11 @@ async function doctorCommand() {
|
|
|
24832
25980
|
];
|
|
24833
25981
|
for (const { type, name } of providers) {
|
|
24834
25982
|
const key = cm.getApiKey(type);
|
|
25983
|
+
const token = key ? void 0 : cm.getAuthToken(type);
|
|
24835
25984
|
checks.push({
|
|
24836
|
-
label: `${name}
|
|
24837
|
-
ok: Boolean(key),
|
|
24838
|
-
detail: key ? "
|
|
25985
|
+
label: `${name} credential`,
|
|
25986
|
+
ok: Boolean(key || token),
|
|
25987
|
+
detail: key ? "API key set" : token ? "Bearer token set" : "Missing"
|
|
24839
25988
|
});
|
|
24840
25989
|
}
|
|
24841
25990
|
const ollamaOk = await checkEndpoint(OLLAMA_BASE_URL + "/api/tags");
|
|
@@ -24889,32 +26038,31 @@ async function doctorCommand() {
|
|
|
24889
26038
|
try {
|
|
24890
26039
|
const discovered = await discoverCredentials();
|
|
24891
26040
|
if (discovered.length > 0) {
|
|
24892
|
-
const usable = discovered.filter((d) => d.directlyUsable).length;
|
|
24893
26041
|
checks.push({
|
|
24894
26042
|
label: "Linkable credentials",
|
|
24895
26043
|
ok: true,
|
|
24896
|
-
detail:
|
|
26044
|
+
detail: linkableCredentialsDetail(discovered, config.providers ?? [])
|
|
24897
26045
|
});
|
|
24898
26046
|
}
|
|
24899
26047
|
} catch {
|
|
24900
26048
|
}
|
|
24901
26049
|
for (const c of checks) {
|
|
24902
|
-
const icon = c.ok ?
|
|
24903
|
-
const label = c.ok ?
|
|
24904
|
-
const detail = c.detail ?
|
|
26050
|
+
const icon = c.ok ? chalk8__default.default.green(" \u2713") : chalk8__default.default.yellow(" \u25CB");
|
|
26051
|
+
const label = c.ok ? chalk8__default.default.white(c.label) : chalk8__default.default.gray(c.label);
|
|
26052
|
+
const detail = c.detail ? chalk8__default.default.gray(` \u2014 ${c.detail}`) : "";
|
|
24905
26053
|
console.log(`${icon} ${label}${detail}`);
|
|
24906
26054
|
}
|
|
24907
26055
|
const failures2 = checks.filter((c) => !c.ok);
|
|
24908
26056
|
console.log();
|
|
24909
26057
|
if (failures2.length === 0) {
|
|
24910
|
-
console.log(
|
|
26058
|
+
console.log(chalk8__default.default.green(" All checks passed!\n"));
|
|
24911
26059
|
} else {
|
|
24912
|
-
const critical = failures2.filter((c) => c.label.includes("Node") || c.label.includes("
|
|
26060
|
+
const critical = failures2.filter((c) => c.label.includes("Node") || c.label.includes("credential"));
|
|
24913
26061
|
if (critical.length) {
|
|
24914
|
-
console.log(
|
|
26062
|
+
console.log(chalk8__default.default.yellow(` ${critical.length} issue(s) need attention.
|
|
24915
26063
|
`));
|
|
24916
26064
|
} else {
|
|
24917
|
-
console.log(
|
|
26065
|
+
console.log(chalk8__default.default.gray(` ${failures2.length} optional item(s) not configured.
|
|
24918
26066
|
`));
|
|
24919
26067
|
}
|
|
24920
26068
|
}
|
|
@@ -24932,19 +26080,19 @@ async function checkEndpoint(url) {
|
|
|
24932
26080
|
}
|
|
24933
26081
|
}
|
|
24934
26082
|
async function indexCommand(dirPath) {
|
|
24935
|
-
const workspace =
|
|
26083
|
+
const workspace = path35__default.default.resolve(dirPath || process.cwd());
|
|
24936
26084
|
const cm = new ConfigManager(workspace);
|
|
24937
26085
|
await cm.load();
|
|
24938
26086
|
const config = cm.getConfig();
|
|
24939
26087
|
const embedder = embedderFromProviders(config.providers);
|
|
24940
26088
|
if (!embedder) {
|
|
24941
|
-
console.log(
|
|
24942
|
-
console.log(
|
|
26089
|
+
console.log(chalk8__default.default.red("\n No embeddings-capable provider configured."));
|
|
26090
|
+
console.log(chalk8__default.default.dim(" Add an OpenAI, OpenAI-compatible, or Ollama key, then re-run `cascade index`.\n"));
|
|
24943
26091
|
process.exitCode = 1;
|
|
24944
26092
|
return;
|
|
24945
26093
|
}
|
|
24946
|
-
const dbPath = config.codeIndex?.dbPath ||
|
|
24947
|
-
|
|
26094
|
+
const dbPath = config.codeIndex?.dbPath || path35__default.default.join(workspace, ".cascade", "code-index.db");
|
|
26095
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(dbPath), { recursive: true });
|
|
24948
26096
|
const db = new Database2__default.default(dbPath);
|
|
24949
26097
|
db.pragma("journal_mode = WAL");
|
|
24950
26098
|
const complete = chatCompleterFromProviders(config.providers);
|
|
@@ -24958,20 +26106,20 @@ async function indexCommand(dirPath) {
|
|
|
24958
26106
|
reranker,
|
|
24959
26107
|
isIgnored: (abs) => ignore4.isIgnored(abs, workspace)
|
|
24960
26108
|
});
|
|
24961
|
-
console.log(
|
|
26109
|
+
console.log(chalk8__default.default.magenta(`
|
|
24962
26110
|
\u25C8 Indexing ${workspace}
|
|
24963
26111
|
`));
|
|
24964
26112
|
let seen = 0;
|
|
24965
26113
|
const res = await index.refresh(() => {
|
|
24966
26114
|
seen++;
|
|
24967
|
-
if (seen % 10 === 0) process.stdout.write(
|
|
26115
|
+
if (seen % 10 === 0) process.stdout.write(chalk8__default.default.dim(` \u2026embedded ${seen} changed files\r`));
|
|
24968
26116
|
});
|
|
24969
26117
|
console.log(
|
|
24970
|
-
|
|
26118
|
+
chalk8__default.default.green(` \u2713 ${res.filesIndexed} file(s) indexed`) + chalk8__default.default.dim(` \xB7 ${res.chunks} chunks \xB7 ${res.filesUnchanged} unchanged \xB7 ${res.filesRemoved} removed`)
|
|
24971
26119
|
);
|
|
24972
|
-
console.log(
|
|
26120
|
+
console.log(chalk8__default.default.dim(` Index: ${dbPath}`));
|
|
24973
26121
|
if (!config.codeIndex?.enabled) {
|
|
24974
|
-
console.log(
|
|
26122
|
+
console.log(chalk8__default.default.dim(" Tip: set codeIndex.enabled = true in your config to give runs the code_search tool."));
|
|
24975
26123
|
}
|
|
24976
26124
|
console.log("");
|
|
24977
26125
|
db.close();
|
|
@@ -25020,6 +26168,7 @@ async function decryptJSON(blob, passphrase) {
|
|
|
25020
26168
|
}
|
|
25021
26169
|
|
|
25022
26170
|
// src/cloud/keysync.ts
|
|
26171
|
+
init_revoked_credentials();
|
|
25023
26172
|
function providerSig(p) {
|
|
25024
26173
|
return [p.type, p.baseUrl ?? "", p.deploymentName ?? "", p.label ?? ""].join("\0");
|
|
25025
26174
|
}
|
|
@@ -25092,6 +26241,19 @@ function gatherSyncBundle(config) {
|
|
|
25092
26241
|
function applySyncBundle(bundle, config, cleanup) {
|
|
25093
26242
|
const dropped = stripRetiredProviders(bundle);
|
|
25094
26243
|
if (cleanup) cleanup.removed = dropped.removed;
|
|
26244
|
+
let revokedFromBundle = 0;
|
|
26245
|
+
let unusableFromBundle = 0;
|
|
26246
|
+
if (Array.isArray(bundle.providers)) {
|
|
26247
|
+
const pass = stripRevokedCredentials(bundle.providers, { keepForEndpoint: false });
|
|
26248
|
+
revokedFromBundle = pass.removed;
|
|
26249
|
+
if (pass.removed > 0) bundle.providers = pass.kept;
|
|
26250
|
+
const usable = bundle.providers.filter((p) => {
|
|
26251
|
+
const orphanBearer = Boolean(p.authToken) && !p.apiKey && !p.baseUrl;
|
|
26252
|
+
if (orphanBearer) unusableFromBundle++;
|
|
26253
|
+
return !orphanBearer;
|
|
26254
|
+
});
|
|
26255
|
+
if (unusableFromBundle > 0) bundle.providers = usable;
|
|
26256
|
+
}
|
|
25095
26257
|
const next = { ...config, tools: { ...config.tools } };
|
|
25096
26258
|
if (bundle.providers) next.providers = mergeByKey(config.providers, bundle.providers, providerSig) ?? [];
|
|
25097
26259
|
if (bundle.webSearch) next.tools.webSearch = { ...config.tools?.webSearch, ...bundle.webSearch };
|
|
@@ -25109,10 +26271,15 @@ function applySyncBundle(bundle, config, cleanup) {
|
|
|
25109
26271
|
if (bundle.budget) next.budget = { ...config.budget, ...bundle.budget };
|
|
25110
26272
|
const merged = { ...next.models };
|
|
25111
26273
|
const clearedFromMerge = clearRetiredPins(merged);
|
|
25112
|
-
|
|
26274
|
+
const clearedRevoked = revokedFromBundle > 0 && !hasUsableAnthropic(next.providers ?? []) ? clearAnthropicPins(merged, next.providers ?? []) : [];
|
|
26275
|
+
if (clearedFromMerge.length > 0 || clearedRevoked.length > 0) {
|
|
26276
|
+
next.models = merged;
|
|
26277
|
+
}
|
|
25113
26278
|
if (cleanup) {
|
|
26279
|
+
cleanup.revokedCredentials = revokedFromBundle;
|
|
26280
|
+
cleanup.unusableCredentials = unusableFromBundle;
|
|
25114
26281
|
cleanup.clearedPins = ["t1", "t2", "t3"].filter(
|
|
25115
|
-
(t) => (dropped.clearedPins.includes(t) || clearedFromMerge.includes(t)) && next.models?.[t] === void 0
|
|
26282
|
+
(t) => (dropped.clearedPins.includes(t) || clearedFromMerge.includes(t) || clearedRevoked.some((c) => c.tier === t)) && next.models?.[t] === void 0
|
|
25116
26283
|
);
|
|
25117
26284
|
}
|
|
25118
26285
|
if (bundle.prefs) {
|
|
@@ -25138,43 +26305,43 @@ function openBrowser(url) {
|
|
|
25138
26305
|
}
|
|
25139
26306
|
}
|
|
25140
26307
|
function notSignedIn() {
|
|
25141
|
-
console.log(
|
|
26308
|
+
console.log(chalk8__default.default.dim("\n Not signed in. Run `cascade login` first.\n"));
|
|
25142
26309
|
process.exitCode = 1;
|
|
25143
26310
|
}
|
|
25144
26311
|
function promptHidden(question) {
|
|
25145
26312
|
return new Promise((resolve2) => {
|
|
25146
26313
|
const rl = readline__default.default.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
25147
|
-
let
|
|
26314
|
+
let masked2 = false;
|
|
25148
26315
|
rl._writeToOutput = (s) => {
|
|
25149
|
-
if (!
|
|
26316
|
+
if (!masked2 || !process.stdin.isTTY) process.stdout.write(s);
|
|
25150
26317
|
};
|
|
25151
26318
|
rl.question(question, (answer) => {
|
|
25152
26319
|
rl.close();
|
|
25153
26320
|
process.stdout.write("\n");
|
|
25154
26321
|
resolve2(answer.trim());
|
|
25155
26322
|
});
|
|
25156
|
-
|
|
26323
|
+
masked2 = true;
|
|
25157
26324
|
});
|
|
25158
26325
|
}
|
|
25159
26326
|
async function loginCommand(opts = {}) {
|
|
25160
26327
|
const serverUrl = resolveServerUrl(opts.server);
|
|
25161
26328
|
const client = new CloudClient(serverUrl);
|
|
25162
|
-
console.log(
|
|
25163
|
-
console.log(
|
|
26329
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Sign in to Cascade Cloud"));
|
|
26330
|
+
console.log(chalk8__default.default.dim(` ${serverUrl}
|
|
25164
26331
|
`));
|
|
25165
26332
|
try {
|
|
25166
26333
|
const session = await client.runDeviceLogin((d) => {
|
|
25167
|
-
console.log(` 1. Open ${
|
|
25168
|
-
console.log(` 2. Enter ${
|
|
25169
|
-
console.log(
|
|
26334
|
+
console.log(` 1. Open ${chalk8__default.default.cyan(d.verificationUri)}`);
|
|
26335
|
+
console.log(` 2. Enter ${chalk8__default.default.bold.yellow(d.userCode)}`);
|
|
26336
|
+
console.log(chalk8__default.default.dim("\n Waiting for you to approve\u2026"));
|
|
25170
26337
|
openBrowser(`${d.verificationUri}?code=${encodeURIComponent(d.userCode)}`);
|
|
25171
26338
|
});
|
|
25172
26339
|
const who = session.user.name || session.user.email || session.user.id;
|
|
25173
|
-
console.log(
|
|
26340
|
+
console.log(chalk8__default.default.green(`
|
|
25174
26341
|
\u2713 Signed in as ${who}
|
|
25175
26342
|
`));
|
|
25176
26343
|
} catch (err) {
|
|
25177
|
-
console.log(
|
|
26344
|
+
console.log(chalk8__default.default.red(`
|
|
25178
26345
|
${err instanceof Error ? err.message : String(err)}
|
|
25179
26346
|
`));
|
|
25180
26347
|
process.exitCode = 1;
|
|
@@ -25183,11 +26350,11 @@ async function loginCommand(opts = {}) {
|
|
|
25183
26350
|
async function logoutCommand() {
|
|
25184
26351
|
const client = CloudClient.fromSession();
|
|
25185
26352
|
if (!client) {
|
|
25186
|
-
console.log(
|
|
26353
|
+
console.log(chalk8__default.default.dim("\n Not signed in.\n"));
|
|
25187
26354
|
return;
|
|
25188
26355
|
}
|
|
25189
26356
|
await client.logout();
|
|
25190
|
-
console.log(
|
|
26357
|
+
console.log(chalk8__default.default.green("\n \u2713 Signed out.\n"));
|
|
25191
26358
|
}
|
|
25192
26359
|
async function whoamiCommand() {
|
|
25193
26360
|
const client = CloudClient.fromSession();
|
|
@@ -25195,10 +26362,10 @@ async function whoamiCommand() {
|
|
|
25195
26362
|
try {
|
|
25196
26363
|
const me = await client.me();
|
|
25197
26364
|
console.log(`
|
|
25198
|
-
${
|
|
26365
|
+
${chalk8__default.default.bold(me.name || "\u2014")} ${chalk8__default.default.dim(me.email || "")}${me.plan ? chalk8__default.default.dim(` \xB7 ${me.plan}`) : ""}
|
|
25199
26366
|
`);
|
|
25200
26367
|
} catch (err) {
|
|
25201
|
-
console.log(
|
|
26368
|
+
console.log(chalk8__default.default.red(`
|
|
25202
26369
|
${err instanceof Error ? err.message : String(err)}
|
|
25203
26370
|
`));
|
|
25204
26371
|
process.exitCode = 1;
|
|
@@ -25210,16 +26377,16 @@ async function sessionsCommand() {
|
|
|
25210
26377
|
try {
|
|
25211
26378
|
const convos = await client.listConversations();
|
|
25212
26379
|
if (convos.length === 0) {
|
|
25213
|
-
console.log(
|
|
26380
|
+
console.log(chalk8__default.default.dim("\n No cloud chats yet.\n"));
|
|
25214
26381
|
return;
|
|
25215
26382
|
}
|
|
25216
|
-
console.log(
|
|
26383
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Your cloud chats\n"));
|
|
25217
26384
|
for (const c of convos) {
|
|
25218
|
-
console.log(` ${
|
|
26385
|
+
console.log(` ${chalk8__default.default.dim(c.id.slice(0, 8))} ${c.title?.trim() || chalk8__default.default.dim("(untitled)")}`);
|
|
25219
26386
|
}
|
|
25220
|
-
console.log(
|
|
26387
|
+
console.log(chalk8__default.default.dim("\n cascade sessions show <id> read a chat (id or its first characters)\n"));
|
|
25221
26388
|
} catch (err) {
|
|
25222
|
-
console.log(
|
|
26389
|
+
console.log(chalk8__default.default.red(`
|
|
25223
26390
|
${err instanceof Error ? err.message : String(err)}
|
|
25224
26391
|
`));
|
|
25225
26392
|
process.exitCode = 1;
|
|
@@ -25233,9 +26400,9 @@ function formatCloudTranscript(messages) {
|
|
|
25233
26400
|
for (const m of messages) {
|
|
25234
26401
|
const n = m.siblingIds?.length ?? 1;
|
|
25235
26402
|
const i = m.siblingIds && m.id ? m.siblingIds.indexOf(m.id) + 1 : 1;
|
|
25236
|
-
const branch = n > 1 ?
|
|
25237
|
-
const tag = m.id ?
|
|
25238
|
-
const who = m.role === "user" ?
|
|
26403
|
+
const branch = n > 1 ? chalk8__default.default.yellow(` \u2039${i}/${n}\u203A`) : "";
|
|
26404
|
+
const tag = m.id ? chalk8__default.default.dim(` [${shortId2(m.id)}]`) : "";
|
|
26405
|
+
const who = m.role === "user" ? chalk8__default.default.bold.cyan("You") : chalk8__default.default.bold.magenta("Cascade");
|
|
25239
26406
|
lines.push(` ${who}${tag}${branch}`);
|
|
25240
26407
|
lines.push(" " + m.content.replace(/\n/g, "\n ") + "\n");
|
|
25241
26408
|
}
|
|
@@ -25251,23 +26418,23 @@ async function sessionShowCommand(idOrPrefix) {
|
|
|
25251
26418
|
try {
|
|
25252
26419
|
const match = await resolveConversation(client, idOrPrefix);
|
|
25253
26420
|
if (!match) {
|
|
25254
|
-
console.log(
|
|
26421
|
+
console.log(chalk8__default.default.red(`
|
|
25255
26422
|
No chat matching "${idOrPrefix}".
|
|
25256
26423
|
`));
|
|
25257
26424
|
process.exitCode = 1;
|
|
25258
26425
|
return;
|
|
25259
26426
|
}
|
|
25260
26427
|
const msgs = await client.getMessages(match.id);
|
|
25261
|
-
console.log(
|
|
26428
|
+
console.log(chalk8__default.default.magenta(`
|
|
25262
26429
|
\u25C8 ${match.title?.trim() || "(untitled)"}
|
|
25263
26430
|
`));
|
|
25264
26431
|
console.log(formatCloudTranscript(msgs));
|
|
25265
26432
|
if (msgs.some((m) => (m.siblingIds?.length ?? 1) > 1)) {
|
|
25266
|
-
console.log(
|
|
26433
|
+
console.log(chalk8__default.default.dim(" cascade sessions branch <chat> <message> switch to another version"));
|
|
25267
26434
|
}
|
|
25268
|
-
console.log(
|
|
26435
|
+
console.log(chalk8__default.default.dim(" cascade sessions rm <chat> <message> delete a message + its replies\n"));
|
|
25269
26436
|
} catch (err) {
|
|
25270
|
-
console.log(
|
|
26437
|
+
console.log(chalk8__default.default.red(`
|
|
25271
26438
|
${err instanceof Error ? err.message : String(err)}
|
|
25272
26439
|
`));
|
|
25273
26440
|
process.exitCode = 1;
|
|
@@ -25282,7 +26449,7 @@ async function sessionBranchCommand(chat, message) {
|
|
|
25282
26449
|
try {
|
|
25283
26450
|
const match = await resolveConversation(client, chat);
|
|
25284
26451
|
if (!match) {
|
|
25285
|
-
console.log(
|
|
26452
|
+
console.log(chalk8__default.default.red(`
|
|
25286
26453
|
No chat matching "${chat}".
|
|
25287
26454
|
`));
|
|
25288
26455
|
process.exitCode = 1;
|
|
@@ -25291,12 +26458,12 @@ async function sessionBranchCommand(chat, message) {
|
|
|
25291
26458
|
const active = await client.getMessages(match.id);
|
|
25292
26459
|
const targetId = resolveMessage(active, message) ?? message;
|
|
25293
26460
|
const next = await client.selectBranch(match.id, targetId);
|
|
25294
|
-
console.log(
|
|
26461
|
+
console.log(chalk8__default.default.magenta(`
|
|
25295
26462
|
\u25C8 Switched branch \u2014 ${match.title?.trim() || "(untitled)"}
|
|
25296
26463
|
`));
|
|
25297
26464
|
console.log(formatCloudTranscript(next));
|
|
25298
26465
|
} catch (err) {
|
|
25299
|
-
console.log(
|
|
26466
|
+
console.log(chalk8__default.default.red(`
|
|
25300
26467
|
${err instanceof Error ? err.message : String(err)}
|
|
25301
26468
|
`));
|
|
25302
26469
|
process.exitCode = 1;
|
|
@@ -25308,7 +26475,7 @@ async function sessionRemoveMessageCommand(chat, message) {
|
|
|
25308
26475
|
try {
|
|
25309
26476
|
const match = await resolveConversation(client, chat);
|
|
25310
26477
|
if (!match) {
|
|
25311
|
-
console.log(
|
|
26478
|
+
console.log(chalk8__default.default.red(`
|
|
25312
26479
|
No chat matching "${chat}".
|
|
25313
26480
|
`));
|
|
25314
26481
|
process.exitCode = 1;
|
|
@@ -25317,11 +26484,11 @@ async function sessionRemoveMessageCommand(chat, message) {
|
|
|
25317
26484
|
const active = await client.getMessages(match.id);
|
|
25318
26485
|
const targetId = resolveMessage(active, message) ?? message;
|
|
25319
26486
|
const next = await client.deleteMessage(match.id, targetId);
|
|
25320
|
-
console.log(
|
|
26487
|
+
console.log(chalk8__default.default.green("\n \u2713 Deleted that message and its replies.\n"));
|
|
25321
26488
|
if (next.length) console.log(formatCloudTranscript(next));
|
|
25322
|
-
else console.log(
|
|
26489
|
+
else console.log(chalk8__default.default.dim(" (the conversation is now empty)\n"));
|
|
25323
26490
|
} catch (err) {
|
|
25324
|
-
console.log(
|
|
26491
|
+
console.log(chalk8__default.default.red(`
|
|
25325
26492
|
${err instanceof Error ? err.message : String(err)}
|
|
25326
26493
|
`));
|
|
25327
26494
|
process.exitCode = 1;
|
|
@@ -25333,18 +26500,18 @@ async function sessionRenameCommand(chat, title) {
|
|
|
25333
26500
|
try {
|
|
25334
26501
|
const match = await resolveConversation(client, chat);
|
|
25335
26502
|
if (!match) {
|
|
25336
|
-
console.log(
|
|
26503
|
+
console.log(chalk8__default.default.red(`
|
|
25337
26504
|
No chat matching "${chat}".
|
|
25338
26505
|
`));
|
|
25339
26506
|
process.exitCode = 1;
|
|
25340
26507
|
return;
|
|
25341
26508
|
}
|
|
25342
26509
|
await client.renameConversation(match.id, title);
|
|
25343
|
-
console.log(
|
|
26510
|
+
console.log(chalk8__default.default.green(`
|
|
25344
26511
|
\u2713 Renamed to "${title}".
|
|
25345
26512
|
`));
|
|
25346
26513
|
} catch (err) {
|
|
25347
|
-
console.log(
|
|
26514
|
+
console.log(chalk8__default.default.red(`
|
|
25348
26515
|
${err instanceof Error ? err.message : String(err)}
|
|
25349
26516
|
`));
|
|
25350
26517
|
process.exitCode = 1;
|
|
@@ -25356,18 +26523,18 @@ async function sessionDeleteCommand(chat) {
|
|
|
25356
26523
|
try {
|
|
25357
26524
|
const match = await resolveConversation(client, chat);
|
|
25358
26525
|
if (!match) {
|
|
25359
|
-
console.log(
|
|
26526
|
+
console.log(chalk8__default.default.red(`
|
|
25360
26527
|
No chat matching "${chat}".
|
|
25361
26528
|
`));
|
|
25362
26529
|
process.exitCode = 1;
|
|
25363
26530
|
return;
|
|
25364
26531
|
}
|
|
25365
26532
|
await client.deleteConversation(match.id);
|
|
25366
|
-
console.log(
|
|
26533
|
+
console.log(chalk8__default.default.green(`
|
|
25367
26534
|
\u2713 Deleted "${match.title?.trim() || "(untitled)"}".
|
|
25368
26535
|
`));
|
|
25369
26536
|
} catch (err) {
|
|
25370
|
-
console.log(
|
|
26537
|
+
console.log(chalk8__default.default.red(`
|
|
25371
26538
|
${err instanceof Error ? err.message : String(err)}
|
|
25372
26539
|
`));
|
|
25373
26540
|
process.exitCode = 1;
|
|
@@ -25379,21 +26546,21 @@ async function syncPushCommand() {
|
|
|
25379
26546
|
const cm = new ConfigManager(process.cwd());
|
|
25380
26547
|
await cm.load();
|
|
25381
26548
|
const bundle = gatherSyncBundle(cm.getConfig());
|
|
25382
|
-
console.log(
|
|
25383
|
-
console.log(
|
|
26549
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Sync your settings to your account"));
|
|
26550
|
+
console.log(chalk8__default.default.dim(" Encrypted on this device with a passphrase we never see.\n"));
|
|
25384
26551
|
const pass = await promptHidden(" Passphrase: ");
|
|
25385
26552
|
if (!pass) {
|
|
25386
|
-
console.log(
|
|
26553
|
+
console.log(chalk8__default.default.dim("\n Cancelled.\n"));
|
|
25387
26554
|
return;
|
|
25388
26555
|
}
|
|
25389
26556
|
try {
|
|
25390
26557
|
const blob = await encryptJSON(bundle, pass);
|
|
25391
26558
|
const r = await client.pushSecrets(blob);
|
|
25392
|
-
console.log(
|
|
26559
|
+
console.log(chalk8__default.default.green(`
|
|
25393
26560
|
\u2713 Synced (v${r.version}). Run \`cascade sync pull\` on another device.
|
|
25394
26561
|
`));
|
|
25395
26562
|
} catch (err) {
|
|
25396
|
-
console.log(
|
|
26563
|
+
console.log(chalk8__default.default.red(`
|
|
25397
26564
|
${err instanceof Error ? err.message : String(err)}
|
|
25398
26565
|
`));
|
|
25399
26566
|
process.exitCode = 1;
|
|
@@ -25406,27 +26573,27 @@ async function syncPullCommand() {
|
|
|
25406
26573
|
try {
|
|
25407
26574
|
({ blob } = await client.pullSecrets());
|
|
25408
26575
|
} catch (err) {
|
|
25409
|
-
console.log(
|
|
26576
|
+
console.log(chalk8__default.default.red(`
|
|
25410
26577
|
${err instanceof Error ? err.message : String(err)}
|
|
25411
26578
|
`));
|
|
25412
26579
|
process.exitCode = 1;
|
|
25413
26580
|
return;
|
|
25414
26581
|
}
|
|
25415
26582
|
if (!blob) {
|
|
25416
|
-
console.log(
|
|
26583
|
+
console.log(chalk8__default.default.dim("\n Nothing synced to your account yet. Run `cascade sync push` first.\n"));
|
|
25417
26584
|
return;
|
|
25418
26585
|
}
|
|
25419
|
-
console.log(
|
|
26586
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Apply your synced settings to this device\n"));
|
|
25420
26587
|
const pass = await promptHidden(" Passphrase: ");
|
|
25421
26588
|
if (!pass) {
|
|
25422
|
-
console.log(
|
|
26589
|
+
console.log(chalk8__default.default.dim("\n Cancelled.\n"));
|
|
25423
26590
|
return;
|
|
25424
26591
|
}
|
|
25425
26592
|
let bundle;
|
|
25426
26593
|
try {
|
|
25427
26594
|
bundle = await decryptJSON(blob, pass);
|
|
25428
26595
|
} catch {
|
|
25429
|
-
console.log(
|
|
26596
|
+
console.log(chalk8__default.default.red("\n Could not decrypt \u2014 check your passphrase and try again.\n"));
|
|
25430
26597
|
process.exitCode = 1;
|
|
25431
26598
|
return;
|
|
25432
26599
|
}
|
|
@@ -25435,13 +26602,13 @@ async function syncPullCommand() {
|
|
|
25435
26602
|
await cm.load();
|
|
25436
26603
|
const cleanup = { removed: [], clearedPins: [] };
|
|
25437
26604
|
await cm.updateConfig(applySyncBundle(bundle, cm.getConfig(), cleanup));
|
|
25438
|
-
console.log(
|
|
26605
|
+
console.log(chalk8__default.default.green("\n \u2713 Applied your synced settings. Your keys are ready here.\n"));
|
|
25439
26606
|
if (didCleanupChangeAnything(cleanup)) {
|
|
25440
|
-
console.log(
|
|
26607
|
+
console.log(chalk8__default.default.yellow(` ${describeCleanup(cleanup)}
|
|
25441
26608
|
`));
|
|
25442
26609
|
}
|
|
25443
26610
|
} catch (err) {
|
|
25444
|
-
console.log(
|
|
26611
|
+
console.log(chalk8__default.default.red(`
|
|
25445
26612
|
Could not apply your synced settings: ${err instanceof Error ? err.message : String(err)}
|
|
25446
26613
|
`));
|
|
25447
26614
|
process.exitCode = 1;
|
|
@@ -25457,7 +26624,7 @@ function openBrowser2(url) {
|
|
|
25457
26624
|
}
|
|
25458
26625
|
function storePathFor(name) {
|
|
25459
26626
|
const safe = name.replace(/[^a-z0-9._-]/gi, "_").slice(0, 64) || "server";
|
|
25460
|
-
return
|
|
26627
|
+
return path35__default.default.join(os10__default.default.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
|
|
25461
26628
|
}
|
|
25462
26629
|
function defaultName(url) {
|
|
25463
26630
|
try {
|
|
@@ -25468,8 +26635,8 @@ function defaultName(url) {
|
|
|
25468
26635
|
}
|
|
25469
26636
|
async function mcpConnectCommand(url, opts = {}) {
|
|
25470
26637
|
const desiredName = opts.name?.trim() || defaultName(url);
|
|
25471
|
-
console.log(
|
|
25472
|
-
console.log(
|
|
26638
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Connect an MCP server via OAuth"));
|
|
26639
|
+
console.log(chalk8__default.default.dim(` ${url}
|
|
25473
26640
|
`));
|
|
25474
26641
|
try {
|
|
25475
26642
|
const cm = new ConfigManager(process.cwd());
|
|
@@ -25485,7 +26652,7 @@ async function mcpConnectCommand(url, opts = {}) {
|
|
|
25485
26652
|
store,
|
|
25486
26653
|
clientName: "Cascade AI",
|
|
25487
26654
|
openUrl: (u) => {
|
|
25488
|
-
console.log(
|
|
26655
|
+
console.log(chalk8__default.default.dim(" Opening your browser to authorize\u2026"));
|
|
25489
26656
|
openBrowser2(u);
|
|
25490
26657
|
}
|
|
25491
26658
|
});
|
|
@@ -25495,11 +26662,11 @@ async function mcpConnectCommand(url, opts = {}) {
|
|
|
25495
26662
|
config.tools.mcpServers = servers;
|
|
25496
26663
|
config.tools.mcpTrusted = Array.from(/* @__PURE__ */ new Set([...config.tools.mcpTrusted ?? [], name]));
|
|
25497
26664
|
await cm.updateConfig(config);
|
|
25498
|
-
console.log(
|
|
26665
|
+
console.log(chalk8__default.default.green(`
|
|
25499
26666
|
\u2713 Connected "${name}". Its tools are available to your runs.
|
|
25500
26667
|
`));
|
|
25501
26668
|
} catch (err) {
|
|
25502
|
-
console.log(
|
|
26669
|
+
console.log(chalk8__default.default.red(`
|
|
25503
26670
|
${err instanceof Error ? err.message : String(err)}
|
|
25504
26671
|
`));
|
|
25505
26672
|
process.exitCode = 1;
|
|
@@ -25510,13 +26677,13 @@ async function mcpListCommand() {
|
|
|
25510
26677
|
await cm.load();
|
|
25511
26678
|
const servers = cm.getConfig().tools?.mcpServers ?? [];
|
|
25512
26679
|
if (!servers.length) {
|
|
25513
|
-
console.log(
|
|
26680
|
+
console.log(chalk8__default.default.dim("\n No MCP servers configured.\n"));
|
|
25514
26681
|
return;
|
|
25515
26682
|
}
|
|
25516
|
-
console.log(
|
|
26683
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 MCP servers\n"));
|
|
25517
26684
|
for (const s of servers) {
|
|
25518
|
-
const kind = s.oauthStore ?
|
|
25519
|
-
console.log(` ${
|
|
26685
|
+
const kind = s.oauthStore ? chalk8__default.default.green("oauth") : s.headers ? chalk8__default.default.cyan("token") : s.command ? chalk8__default.default.dim("local") : chalk8__default.default.dim("open");
|
|
26686
|
+
console.log(` ${chalk8__default.default.bold(s.name)} ${chalk8__default.default.dim(s.url || s.command || "")} ${kind}`);
|
|
25520
26687
|
}
|
|
25521
26688
|
console.log();
|
|
25522
26689
|
}
|
|
@@ -25527,7 +26694,7 @@ async function mcpRemoveCommand(name) {
|
|
|
25527
26694
|
const servers = config.tools?.mcpServers ?? [];
|
|
25528
26695
|
const match = servers.find((s) => s.name === name);
|
|
25529
26696
|
if (!match) {
|
|
25530
|
-
console.log(
|
|
26697
|
+
console.log(chalk8__default.default.red(`
|
|
25531
26698
|
No MCP server named "${name}".
|
|
25532
26699
|
`));
|
|
25533
26700
|
process.exitCode = 1;
|
|
@@ -25543,7 +26710,7 @@ async function mcpRemoveCommand(name) {
|
|
|
25543
26710
|
}
|
|
25544
26711
|
}
|
|
25545
26712
|
await cm.updateConfig(config);
|
|
25546
|
-
console.log(
|
|
26713
|
+
console.log(chalk8__default.default.green(`
|
|
25547
26714
|
\u2713 Removed "${name}".
|
|
25548
26715
|
`));
|
|
25549
26716
|
}
|
|
@@ -25557,16 +26724,302 @@ async function updateCommand() {
|
|
|
25557
26724
|
const { stdout } = await execAsync2("npm show cascade-ai version", { timeout: 1e4 });
|
|
25558
26725
|
const latest = stdout.trim().split("\n").filter(Boolean).pop() ?? "";
|
|
25559
26726
|
if (latest === CASCADE_VERSION) {
|
|
25560
|
-
spin.succeed(
|
|
26727
|
+
spin.succeed(chalk8__default.default.green(`Already up to date (v${CASCADE_VERSION})`));
|
|
25561
26728
|
return;
|
|
25562
26729
|
}
|
|
25563
26730
|
spin.text = `Updating cascade-ai ${CASCADE_VERSION} \u2192 ${latest}\u2026`;
|
|
25564
26731
|
await execAsync2("npm install -g cascade-ai@latest", { timeout: 6e4 });
|
|
25565
|
-
spin.succeed(
|
|
26732
|
+
spin.succeed(chalk8__default.default.green(`Updated to v${latest}! Restart your terminal.`));
|
|
26733
|
+
} catch (err) {
|
|
26734
|
+
spin.fail(chalk8__default.default.red(`Update failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
26735
|
+
}
|
|
26736
|
+
}
|
|
26737
|
+
|
|
26738
|
+
// src/config/credential-write.ts
|
|
26739
|
+
function endpointFromSettingsPayload(endpoints, type) {
|
|
26740
|
+
if (!endpoints || !Object.hasOwn(endpoints, type)) return { kind: "preserve" };
|
|
26741
|
+
const baseUrl = endpoints[type]?.trim();
|
|
26742
|
+
return baseUrl ? { kind: "at", baseUrl } : { kind: "cleared" };
|
|
26743
|
+
}
|
|
26744
|
+
function targetEndpoint(endpoint, existing) {
|
|
26745
|
+
switch (endpoint.kind) {
|
|
26746
|
+
case "at":
|
|
26747
|
+
return endpoint.baseUrl;
|
|
26748
|
+
// An explicit clear, always. Whether the row then falls back to a public
|
|
26749
|
+
// host or becomes unroutable is the provider's business, decided before we
|
|
26750
|
+
// get here.
|
|
26751
|
+
case "cleared":
|
|
26752
|
+
return void 0;
|
|
26753
|
+
case "preserve":
|
|
26754
|
+
return existing?.baseUrl;
|
|
26755
|
+
}
|
|
26756
|
+
}
|
|
26757
|
+
function namesCustomHost(row) {
|
|
26758
|
+
if (!row?.baseUrl) return false;
|
|
26759
|
+
return !sameCredentialEndpoint(row.type, row.baseUrl, void 0);
|
|
26760
|
+
}
|
|
26761
|
+
function applyProviderCredential(providers, type, apiKey, endpoint) {
|
|
26762
|
+
const existing = providers.find((p) => p.type === type);
|
|
26763
|
+
if (endpoint.kind === "cleared" && !hasDefaultEndpoint(type)) {
|
|
26764
|
+
if (existing) {
|
|
26765
|
+
existing.baseUrl = void 0;
|
|
26766
|
+
existing.apiKey = void 0;
|
|
26767
|
+
existing.authToken = void 0;
|
|
26768
|
+
delete existing.local;
|
|
26769
|
+
}
|
|
26770
|
+
return { written: false, reason: "unroutable" };
|
|
26771
|
+
}
|
|
26772
|
+
if (endpoint.kind === "preserve" && namesCustomHost(existing)) {
|
|
26773
|
+
return { written: false, reason: "ambiguous-scope" };
|
|
26774
|
+
}
|
|
26775
|
+
const nextBaseUrl = targetEndpoint(endpoint, existing);
|
|
26776
|
+
if (existing && !sameCredentialEndpoint(type, existing.baseUrl, nextBaseUrl)) {
|
|
26777
|
+
delete existing.local;
|
|
26778
|
+
}
|
|
26779
|
+
if (existing && nextBaseUrl === void 0) existing.baseUrl = void 0;
|
|
26780
|
+
applyProviderApiKey(providers, type, apiKey, nextBaseUrl ? { baseUrl: nextBaseUrl } : {});
|
|
26781
|
+
return { written: true };
|
|
26782
|
+
}
|
|
26783
|
+
function credentialDispositionForEdit(existing, nextBaseUrl, replacementKey) {
|
|
26784
|
+
if (replacementKey) return "replaced";
|
|
26785
|
+
return sameCredentialEndpoint(existing.type, existing.baseUrl, nextBaseUrl) ? "keep" : "clear";
|
|
26786
|
+
}
|
|
26787
|
+
function applyEndpointEdit(existing, nextBaseUrl, replacementKey) {
|
|
26788
|
+
if (credentialDispositionForEdit(existing, nextBaseUrl, replacementKey) === "clear") {
|
|
26789
|
+
existing.apiKey = void 0;
|
|
26790
|
+
existing.authToken = void 0;
|
|
26791
|
+
}
|
|
26792
|
+
if (!sameCredentialEndpoint(existing.type, existing.baseUrl, nextBaseUrl)) {
|
|
26793
|
+
delete existing.local;
|
|
26794
|
+
}
|
|
26795
|
+
existing.baseUrl = nextBaseUrl || void 0;
|
|
26796
|
+
}
|
|
26797
|
+
function explainRefusal(type, reason) {
|
|
26798
|
+
return reason === "unroutable" ? `The ${type} key was not saved: its endpoint was cleared, and ${type} has no default host, so there is nowhere the key could be sent. Enter the endpoint URL alongside the key.` : `The ${type} key was not saved: this provider is configured with a custom endpoint, and Cascade cannot tell whether the new key was issued by that endpoint or by the provider directly. Set the endpoint field alongside the key \u2014 clear it if the key is for the provider itself.`;
|
|
26799
|
+
}
|
|
26800
|
+
function applySettingsCredentials(providers, data) {
|
|
26801
|
+
const refused = [];
|
|
26802
|
+
if (data.keys) {
|
|
26803
|
+
for (const [type, apiKey] of Object.entries(data.keys)) {
|
|
26804
|
+
if (!apiKey) continue;
|
|
26805
|
+
const outcome = applyProviderCredential(
|
|
26806
|
+
providers,
|
|
26807
|
+
type,
|
|
26808
|
+
apiKey,
|
|
26809
|
+
endpointFromSettingsPayload(data.endpoints, type)
|
|
26810
|
+
);
|
|
26811
|
+
if (!outcome.written) refused.push({ type, reason: outcome.reason });
|
|
26812
|
+
}
|
|
26813
|
+
}
|
|
26814
|
+
if (!data.endpoints) return { refused };
|
|
26815
|
+
for (const [type, baseUrl] of Object.entries(data.endpoints)) {
|
|
26816
|
+
if (type === "azure") continue;
|
|
26817
|
+
if (!Object.hasOwn(data.endpoints, type)) continue;
|
|
26818
|
+
const existing = providers.find((p) => p.type === type);
|
|
26819
|
+
if (!existing) {
|
|
26820
|
+
if (baseUrl) providers.push({ type, baseUrl });
|
|
26821
|
+
continue;
|
|
26822
|
+
}
|
|
26823
|
+
applyEndpointEdit(existing, baseUrl, data.keys?.[type]);
|
|
26824
|
+
}
|
|
26825
|
+
return { refused };
|
|
26826
|
+
}
|
|
26827
|
+
|
|
26828
|
+
// src/config/settings-payload.ts
|
|
26829
|
+
function priorAzureRow(prior, incoming) {
|
|
26830
|
+
if (!incoming.deploymentName) return void 0;
|
|
26831
|
+
return prior.find((p) => p.deploymentName === incoming.deploymentName && sameAzureEndpoint(p.baseUrl, incoming.baseUrl));
|
|
26832
|
+
}
|
|
26833
|
+
function applySettingsPayload(config, data) {
|
|
26834
|
+
if (!Array.isArray(config.providers)) config.providers = [];
|
|
26835
|
+
const result = applySettingsCredentials(config.providers, data);
|
|
26836
|
+
if (Array.isArray(data.azureDeployments)) {
|
|
26837
|
+
const priorAzure = config.providers.filter((p) => p.type === "azure");
|
|
26838
|
+
const nonAzure = config.providers.filter((p) => p.type !== "azure");
|
|
26839
|
+
const nextAzure = data.azureDeployments.map((d) => {
|
|
26840
|
+
const prior = priorAzureRow(priorAzure, d);
|
|
26841
|
+
const apiKey = d.apiKey ? d.apiKey : prior?.apiKey;
|
|
26842
|
+
const model = d.model ?? prior?.model;
|
|
26843
|
+
const region = d.region ?? prior?.region;
|
|
26844
|
+
return {
|
|
26845
|
+
type: "azure",
|
|
26846
|
+
...d.label ? { label: d.label } : {},
|
|
26847
|
+
...d.baseUrl ? { baseUrl: d.baseUrl } : {},
|
|
26848
|
+
...d.deploymentName ? { deploymentName: d.deploymentName } : {},
|
|
26849
|
+
...d.apiVersion ? { apiVersion: d.apiVersion } : {},
|
|
26850
|
+
...apiKey ? { apiKey } : {},
|
|
26851
|
+
...model ? { model } : {},
|
|
26852
|
+
...region ? { region } : {}
|
|
26853
|
+
};
|
|
26854
|
+
}).filter((d) => d.apiKey || d.baseUrl || d.deploymentName);
|
|
26855
|
+
config.providers = [...nonAzure, ...nextAzure];
|
|
26856
|
+
}
|
|
26857
|
+
if (data.models) {
|
|
26858
|
+
const models = config.models ?? {};
|
|
26859
|
+
for (const [tier, val] of Object.entries(data.models)) {
|
|
26860
|
+
if (val && val !== "auto") models[tier] = val;
|
|
26861
|
+
else delete models[tier];
|
|
26862
|
+
}
|
|
26863
|
+
config.models = models;
|
|
26864
|
+
}
|
|
26865
|
+
if (data.budget) {
|
|
26866
|
+
const b = data.budget;
|
|
26867
|
+
config.budget = config.budget ?? {};
|
|
26868
|
+
const cap = (key, target, accept, round = false) => {
|
|
26869
|
+
if (!Object.hasOwn(b, key)) return;
|
|
26870
|
+
const v = b[key];
|
|
26871
|
+
if (typeof v === "number" && accept(v)) config.budget[target] = round ? Math.floor(v) : v;
|
|
26872
|
+
else delete config.budget[target];
|
|
26873
|
+
};
|
|
26874
|
+
cap("maxCostPerRun", "maxCostPerRunUsd", (n) => n > 0);
|
|
26875
|
+
cap("dailyBudgetUsd", "dailyBudgetUsd", (n) => n > 0);
|
|
26876
|
+
cap("sessionBudgetUsd", "sessionBudgetUsd", (n) => n > 0);
|
|
26877
|
+
cap("maxTokensPerRun", "maxTokensPerRun", (n) => n > 0, true);
|
|
26878
|
+
cap("warnAtPct", "warnAtPct", (n) => n > 0 && n <= 100);
|
|
26879
|
+
if (b.autoBias === "balanced" || b.autoBias === "quality" || b.autoBias === "cost") {
|
|
26880
|
+
config.autoBias = b.autoBias;
|
|
26881
|
+
}
|
|
26882
|
+
}
|
|
26883
|
+
if (data.webSearch && typeof data.webSearch === "object") {
|
|
26884
|
+
config.tools = config.tools ?? {};
|
|
26885
|
+
const prior = config.tools.webSearch ?? {};
|
|
26886
|
+
const next = { ...prior };
|
|
26887
|
+
if (typeof data.webSearch.searxngUrl === "string") next.searxngUrl = data.webSearch.searxngUrl.trim() || void 0;
|
|
26888
|
+
if (data.webSearch.braveApiKey) next.braveApiKey = data.webSearch.braveApiKey;
|
|
26889
|
+
if (data.webSearch.tavilyApiKey) next.tavilyApiKey = data.webSearch.tavilyApiKey;
|
|
26890
|
+
config.tools.webSearch = next;
|
|
26891
|
+
}
|
|
26892
|
+
if (data.advanced && typeof data.advanced === "object") {
|
|
26893
|
+
const a = data.advanced;
|
|
26894
|
+
const num = (v, min, max) => {
|
|
26895
|
+
const n = Number(v);
|
|
26896
|
+
return Number.isFinite(n) && n >= min && n <= max ? n : void 0;
|
|
26897
|
+
};
|
|
26898
|
+
if (a["autonomy"] === "manual" || a["autonomy"] === "auto") config.autonomy = a["autonomy"];
|
|
26899
|
+
if (["never", "complex", "all", "always"].includes(a["planApproval"])) config.planApproval = a["planApproval"];
|
|
26900
|
+
{
|
|
26901
|
+
const n = num(a["approvalTimeoutMs"], 1e3, 864e5);
|
|
26902
|
+
if (n !== void 0) config.approvalTimeoutMs = Math.floor(n);
|
|
26903
|
+
}
|
|
26904
|
+
if (["auto", "parallel", "sequential"].includes(a["t3Execution"])) config.t3Execution = a["t3Execution"];
|
|
26905
|
+
{
|
|
26906
|
+
const n = num(a["localConcurrency"], 1, 16);
|
|
26907
|
+
if (n !== void 0) config.localConcurrency = Math.floor(n);
|
|
26908
|
+
}
|
|
26909
|
+
{
|
|
26910
|
+
const n = num(a["localInferenceTimeoutMs"], 1e3, 36e5);
|
|
26911
|
+
if (n !== void 0) config.localInferenceTimeoutMs = Math.floor(n);
|
|
26912
|
+
}
|
|
26913
|
+
{
|
|
26914
|
+
const n = num(a["cloudInferenceTimeoutMs"], 1e3, 36e5);
|
|
26915
|
+
if (n !== void 0) config.cloudInferenceTimeoutMs = Math.floor(n);
|
|
26916
|
+
}
|
|
26917
|
+
if (typeof a["reflectionEnabled"] === "boolean") config.reflection = { ...config.reflection ?? {}, enabled: a["reflectionEnabled"] };
|
|
26918
|
+
if (typeof a["cascadeAuto"] === "boolean") config.cascadeAuto = a["cascadeAuto"];
|
|
26919
|
+
if (["auto", "T1", "T2", "T3"].includes(a["forceTier"])) config.routing = { ...config.routing ?? {}, forceTier: a["forceTier"] };
|
|
26920
|
+
if (typeof a["benchmarksLive"] === "boolean") config.benchmarks = { ...config.benchmarks ?? {}, live: a["benchmarksLive"] };
|
|
26921
|
+
if (["isolate", "worker", "auto"].includes(a["dynamicToolSandbox"])) config.tools = { ...config.tools ?? {}, dynamicToolSandbox: a["dynamicToolSandbox"] };
|
|
26922
|
+
if (typeof a["factsExtraction"] === "boolean") config.knowledge = { ...config.knowledge ?? {}, factsExtraction: a["factsExtraction"] };
|
|
26923
|
+
if (typeof a["rememberSessions"] === "boolean") config.memory = { ...config.memory ?? {}, rememberSessions: a["rememberSessions"] };
|
|
26924
|
+
if (typeof a["enableToolCreation"] === "boolean") config.enableToolCreation = a["enableToolCreation"];
|
|
26925
|
+
if (typeof a["persistDynamicTools"] === "boolean") config.persistDynamicTools = a["persistDynamicTools"];
|
|
26926
|
+
if (typeof a["telemetryEnabled"] === "boolean") config.telemetry = { ...config.telemetry ?? {}, enabled: a["telemetryEnabled"] };
|
|
26927
|
+
}
|
|
26928
|
+
return result;
|
|
26929
|
+
}
|
|
26930
|
+
function settingsSnapshot(config) {
|
|
26931
|
+
const providers = config.providers ?? [];
|
|
26932
|
+
const endpoints = {};
|
|
26933
|
+
for (const p of providers) {
|
|
26934
|
+
if (p?.type && p?.baseUrl && p.type !== "azure") endpoints[p.type] = p.baseUrl;
|
|
26935
|
+
}
|
|
26936
|
+
const ws = config.tools?.webSearch ?? {};
|
|
26937
|
+
return {
|
|
26938
|
+
models: config.models ?? {},
|
|
26939
|
+
budget: {
|
|
26940
|
+
maxCostPerRun: config.budget?.maxCostPerRunUsd,
|
|
26941
|
+
autoBias: config.autoBias,
|
|
26942
|
+
dailyBudgetUsd: config.budget?.dailyBudgetUsd,
|
|
26943
|
+
sessionBudgetUsd: config.budget?.sessionBudgetUsd,
|
|
26944
|
+
maxTokensPerRun: config.budget?.maxTokensPerRun,
|
|
26945
|
+
warnAtPct: config.budget?.warnAtPct
|
|
26946
|
+
},
|
|
26947
|
+
// Through the shared predicate: `authToken` is a credential too, and
|
|
26948
|
+
// counting only `apiKey` showed a bearer-configured provider as unconfigured.
|
|
26949
|
+
providersWithKey: providers.filter((p) => hasProviderCredential(p)).map((p) => p.type),
|
|
26950
|
+
endpoints,
|
|
26951
|
+
azureDeployments: providers.filter((p) => p.type === "azure").map((p) => ({
|
|
26952
|
+
label: p.label,
|
|
26953
|
+
baseUrl: p.baseUrl,
|
|
26954
|
+
deploymentName: p.deploymentName,
|
|
26955
|
+
apiVersion: p.apiVersion,
|
|
26956
|
+
// Surfaced so a panel that round-trips the snapshot cannot drop them.
|
|
26957
|
+
model: p.model,
|
|
26958
|
+
region: p.region,
|
|
26959
|
+
hasKey: typeof p.apiKey === "string" && p.apiKey.length > 0
|
|
26960
|
+
})),
|
|
26961
|
+
webSearch: {
|
|
26962
|
+
searxngUrl: ws.searxngUrl,
|
|
26963
|
+
hasBraveKey: typeof ws.braveApiKey === "string" && ws.braveApiKey.length > 0,
|
|
26964
|
+
hasTavilyKey: typeof ws.tavilyApiKey === "string" && ws.tavilyApiKey.length > 0
|
|
26965
|
+
},
|
|
26966
|
+
advanced: {
|
|
26967
|
+
autonomy: config.autonomy,
|
|
26968
|
+
planApproval: config.planApproval,
|
|
26969
|
+
approvalTimeoutMs: config.approvalTimeoutMs,
|
|
26970
|
+
t3Execution: config.t3Execution,
|
|
26971
|
+
localConcurrency: config.localConcurrency,
|
|
26972
|
+
localInferenceTimeoutMs: config.localInferenceTimeoutMs,
|
|
26973
|
+
cloudInferenceTimeoutMs: config.cloudInferenceTimeoutMs,
|
|
26974
|
+
reflectionEnabled: config.reflection?.enabled,
|
|
26975
|
+
cascadeAuto: config.cascadeAuto,
|
|
26976
|
+
forceTier: config.routing?.forceTier,
|
|
26977
|
+
benchmarksLive: config.benchmarks?.live,
|
|
26978
|
+
dynamicToolSandbox: config.tools?.dynamicToolSandbox,
|
|
26979
|
+
factsExtraction: config.knowledge?.factsExtraction,
|
|
26980
|
+
rememberSessions: config.memory?.rememberSessions,
|
|
26981
|
+
enableToolCreation: config.enableToolCreation,
|
|
26982
|
+
persistDynamicTools: config.persistDynamicTools,
|
|
26983
|
+
telemetryEnabled: config.telemetry?.enabled
|
|
26984
|
+
}
|
|
26985
|
+
};
|
|
26986
|
+
}
|
|
26987
|
+
async function commitSettings(live, data, write) {
|
|
26988
|
+
const staged = structuredClone(live);
|
|
26989
|
+
const { refused } = applySettingsPayload(staged, data);
|
|
26990
|
+
let committed;
|
|
26991
|
+
try {
|
|
26992
|
+
committed = validateConfig(staged);
|
|
25566
26993
|
} catch (err) {
|
|
25567
|
-
|
|
26994
|
+
return {
|
|
26995
|
+
ok: false,
|
|
26996
|
+
refused,
|
|
26997
|
+
error: `Those settings are not valid: ${err instanceof Error ? err.message : String(err)}`
|
|
26998
|
+
};
|
|
26999
|
+
}
|
|
27000
|
+
const written = await write(committed);
|
|
27001
|
+
if (!written.ok) {
|
|
27002
|
+
return { ok: false, refused, error: `Could not write the config file: ${written.error}` };
|
|
25568
27003
|
}
|
|
27004
|
+
replaceInPlace(live, committed);
|
|
27005
|
+
return { ok: true, refused };
|
|
25569
27006
|
}
|
|
27007
|
+
function replaceInPlace(target, source) {
|
|
27008
|
+
const t = target;
|
|
27009
|
+
for (const key of Object.keys(t)) delete t[key];
|
|
27010
|
+
Object.assign(t, structuredClone(source));
|
|
27011
|
+
}
|
|
27012
|
+
function writeConfigFile(configPath, config) {
|
|
27013
|
+
try {
|
|
27014
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(configPath), { recursive: true });
|
|
27015
|
+
fs32__default.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
27016
|
+
return { ok: true };
|
|
27017
|
+
} catch (err) {
|
|
27018
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
27019
|
+
}
|
|
27020
|
+
}
|
|
27021
|
+
|
|
27022
|
+
// src/dashboard/server.ts
|
|
25570
27023
|
init_constants();
|
|
25571
27024
|
var JWT_ALGORITHM = "HS256";
|
|
25572
27025
|
function createToken(user, secret) {
|
|
@@ -25814,12 +27267,22 @@ var DashboardSocket = class {
|
|
|
25814
27267
|
});
|
|
25815
27268
|
});
|
|
25816
27269
|
}
|
|
27270
|
+
/**
|
|
27271
|
+
* The handler's result is ACKNOWLEDGED to the caller.
|
|
27272
|
+
*
|
|
27273
|
+
* A settings save can be partly refused — a key whose host cannot be
|
|
27274
|
+
* determined is deliberately not stored — and this path had no way to say so,
|
|
27275
|
+
* so the panel cleared the input and reported success over a key the backend
|
|
27276
|
+
* had dropped. Socket.IO's own ack channel carries it back, which needs no
|
|
27277
|
+
* new event name and cannot be missed by a client that forgot to subscribe.
|
|
27278
|
+
*/
|
|
25817
27279
|
onConfigUpdate(callback) {
|
|
25818
27280
|
this.io.on("connection", (socket) => {
|
|
25819
|
-
socket.on("config:update", (payload) => {
|
|
25820
|
-
if (typeof payload
|
|
25821
|
-
|
|
25822
|
-
|
|
27281
|
+
socket.on("config:update", (payload, ack) => {
|
|
27282
|
+
if (typeof payload !== "object" || payload === null) return;
|
|
27283
|
+
void callback(payload).then((result) => {
|
|
27284
|
+
if (typeof ack === "function") ack(result);
|
|
27285
|
+
});
|
|
25823
27286
|
});
|
|
25824
27287
|
});
|
|
25825
27288
|
}
|
|
@@ -25965,8 +27428,47 @@ function aggregateCostStats(sessions2, opts = {}) {
|
|
|
25965
27428
|
}
|
|
25966
27429
|
|
|
25967
27430
|
// src/dashboard/server.ts
|
|
25968
|
-
var __dirname$1 =
|
|
27431
|
+
var __dirname$1 = path35__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
|
|
25969
27432
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
27433
|
+
var MASK = "***";
|
|
27434
|
+
function masked(key, value) {
|
|
27435
|
+
return { [key]: value ? MASK : void 0 };
|
|
27436
|
+
}
|
|
27437
|
+
function redactProviderSecrets(config) {
|
|
27438
|
+
const safe = { ...config };
|
|
27439
|
+
safe.providers = (config.providers ?? []).map((p) => ({
|
|
27440
|
+
...p,
|
|
27441
|
+
...masked("apiKey", p.apiKey),
|
|
27442
|
+
...masked("authToken", p.authToken)
|
|
27443
|
+
}));
|
|
27444
|
+
if (config.tools) {
|
|
27445
|
+
safe.tools = { ...config.tools };
|
|
27446
|
+
if (config.tools.webSearch) {
|
|
27447
|
+
safe.tools.webSearch = {
|
|
27448
|
+
...config.tools.webSearch,
|
|
27449
|
+
...masked("braveApiKey", config.tools.webSearch.braveApiKey),
|
|
27450
|
+
...masked("tavilyApiKey", config.tools.webSearch.tavilyApiKey)
|
|
27451
|
+
};
|
|
27452
|
+
}
|
|
27453
|
+
if (config.tools.mcpServers) {
|
|
27454
|
+
safe.tools.mcpServers = config.tools.mcpServers.map((server) => ({
|
|
27455
|
+
...server,
|
|
27456
|
+
...server.headers ? { headers: Object.fromEntries(Object.keys(server.headers).map((k) => [k, MASK])) } : {},
|
|
27457
|
+
...server.env ? { env: Object.fromEntries(Object.keys(server.env).map((k) => [k, MASK])) } : {}
|
|
27458
|
+
}));
|
|
27459
|
+
}
|
|
27460
|
+
}
|
|
27461
|
+
if (config.dashboard) {
|
|
27462
|
+
safe.dashboard = { ...config.dashboard, ...masked("secret", config.dashboard.secret) };
|
|
27463
|
+
}
|
|
27464
|
+
if (config.telemetry) {
|
|
27465
|
+
safe.telemetry = {
|
|
27466
|
+
...config.telemetry,
|
|
27467
|
+
...masked("posthogApiKey", config.telemetry.posthogApiKey)
|
|
27468
|
+
};
|
|
27469
|
+
}
|
|
27470
|
+
return safe;
|
|
27471
|
+
}
|
|
25970
27472
|
var DashboardServer = class {
|
|
25971
27473
|
app;
|
|
25972
27474
|
httpServer;
|
|
@@ -26041,40 +27543,21 @@ var DashboardServer = class {
|
|
|
26041
27543
|
this.activeSessions.get(sessionId)?.rateLastRun(rating);
|
|
26042
27544
|
});
|
|
26043
27545
|
this.socket.onConfigGet((socketId) => {
|
|
26044
|
-
this.socket.emitToSocket(socketId, "config:current",
|
|
26045
|
-
models: this.config.models ?? {},
|
|
26046
|
-
budget: {
|
|
26047
|
-
maxCostPerRun: this.config.budget?.maxCostPerRunUsd,
|
|
26048
|
-
autoBias: this.config.autoBias
|
|
26049
|
-
},
|
|
26050
|
-
providersWithKey: (this.config.providers ?? []).filter((p) => typeof p.apiKey === "string" && p.apiKey.length > 0).map((p) => p.type)
|
|
26051
|
-
});
|
|
27546
|
+
this.socket.emitToSocket(socketId, "config:current", settingsSnapshot(this.config));
|
|
26052
27547
|
});
|
|
26053
|
-
this.socket.onConfigUpdate((data) => {
|
|
26054
|
-
|
|
26055
|
-
|
|
26056
|
-
|
|
26057
|
-
|
|
26058
|
-
|
|
26059
|
-
|
|
26060
|
-
|
|
26061
|
-
|
|
26062
|
-
|
|
26063
|
-
const models = this.config.models;
|
|
26064
|
-
for (const [tier, val] of Object.entries(data.models)) {
|
|
26065
|
-
if (val && val !== "auto") models[tier] = val;
|
|
26066
|
-
else delete models[tier];
|
|
26067
|
-
}
|
|
26068
|
-
}
|
|
26069
|
-
if (data.budget) {
|
|
26070
|
-
if (typeof data.budget.maxCostPerRun === "number") {
|
|
26071
|
-
this.config.budget.maxCostPerRunUsd = data.budget.maxCostPerRun;
|
|
26072
|
-
}
|
|
26073
|
-
if (data.budget.autoBias === "balanced" || data.budget.autoBias === "quality" || data.budget.autoBias === "cost") {
|
|
26074
|
-
this.config.autoBias = data.budget.autoBias;
|
|
26075
|
-
}
|
|
27548
|
+
this.socket.onConfigUpdate(async (data) => {
|
|
27549
|
+
const result = await commitSettings(this.config, data, (committed) => this.persistConfig(committed));
|
|
27550
|
+
const refused = result.refused.map((r) => ({
|
|
27551
|
+
type: r.type,
|
|
27552
|
+
reason: r.reason,
|
|
27553
|
+
message: explainRefusal(r.type, r.reason)
|
|
27554
|
+
}));
|
|
27555
|
+
for (const r of refused) console.warn(r.message);
|
|
27556
|
+
if (!result.ok) {
|
|
27557
|
+
return { refused, snapshot: settingsSnapshot(this.config), error: result.error };
|
|
26076
27558
|
}
|
|
26077
|
-
this.
|
|
27559
|
+
this.syncGlobalCredentials();
|
|
27560
|
+
return { refused, snapshot: settingsSnapshot(this.config) };
|
|
26078
27561
|
});
|
|
26079
27562
|
this.socket.onCascadeRun(async (prompt, model, socketId, requestedSessionId, forceTier) => {
|
|
26080
27563
|
const sessionId = requestedSessionId ?? crypto.randomUUID();
|
|
@@ -26243,16 +27726,47 @@ var DashboardServer = class {
|
|
|
26243
27726
|
* made over the socket (Settings → Save) persist across restarts. Best-effort:
|
|
26244
27727
|
* a write failure is logged but never crashes the running dashboard.
|
|
26245
27728
|
*/
|
|
26246
|
-
|
|
26247
|
-
|
|
26248
|
-
|
|
26249
|
-
|
|
26250
|
-
|
|
26251
|
-
|
|
26252
|
-
|
|
26253
|
-
|
|
27729
|
+
/**
|
|
27730
|
+
* Returns the durability of the WORKSPACE write, because a caller
|
|
27731
|
+
* acknowledging a save has to know it happened.
|
|
27732
|
+
*
|
|
27733
|
+
* This was `void` and swallowed every filesystem error, so a read-only
|
|
27734
|
+
* config path or a full disk produced: the live object mutated, the write
|
|
27735
|
+
* failed, the socket acknowledged success, and the panel cleared the keys the
|
|
27736
|
+
* user had typed. The changes then vanished at the next restart. "The handler
|
|
27737
|
+
* ran" is not "the save is durable".
|
|
27738
|
+
*
|
|
27739
|
+
* The global-credential sync below is genuinely best-effort — it is a
|
|
27740
|
+
* convenience copy, and failing it does not lose what the user just entered.
|
|
27741
|
+
*/
|
|
27742
|
+
/**
|
|
27743
|
+
* @param config REQUIRED, with no default. `commitSettings` writes the
|
|
27744
|
+
* validated copy BEFORE adopting it, so a writer that reached for
|
|
27745
|
+
* `this.config` instead would persist the configuration being replaced — and
|
|
27746
|
+
* a default parameter is exactly the affordance that lets that happen
|
|
27747
|
+
* silently and still typecheck.
|
|
27748
|
+
*/
|
|
27749
|
+
persistConfig(config) {
|
|
27750
|
+
const result = writeConfigFile(path35__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE), config);
|
|
27751
|
+
if (!result.ok) console.warn(`[dashboard] Failed to persist config: ${result.error}`);
|
|
27752
|
+
return result;
|
|
27753
|
+
}
|
|
27754
|
+
/**
|
|
27755
|
+
* Copy provider credentials to the machine-global store, so keys saved over
|
|
27756
|
+
* the socket survive a workspace switch — the same sync `ConfigManager.save()`
|
|
27757
|
+
* performs.
|
|
27758
|
+
*
|
|
27759
|
+
* Separate from the workspace write, and called only AFTER it succeeds. It
|
|
27760
|
+
* used to run unconditionally inside `persistConfig()`, so a failed workspace
|
|
27761
|
+
* write still pushed the provider half of a rejected save into the global
|
|
27762
|
+
* store: half the change persisted, behind an error saying none of it had.
|
|
27763
|
+
*
|
|
27764
|
+
* Best-effort on its own account: this is a convenience copy, and failing it
|
|
27765
|
+
* does not lose what the user just entered.
|
|
27766
|
+
*/
|
|
27767
|
+
syncGlobalCredentials() {
|
|
26254
27768
|
try {
|
|
26255
|
-
saveGlobalCredentials(
|
|
27769
|
+
saveGlobalCredentials(path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
26256
27770
|
} catch (err) {
|
|
26257
27771
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
26258
27772
|
}
|
|
@@ -26267,15 +27781,15 @@ var DashboardServer = class {
|
|
|
26267
27781
|
resolveDashboardSecret() {
|
|
26268
27782
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
26269
27783
|
if (fromConfig) return fromConfig;
|
|
26270
|
-
const secretPath =
|
|
27784
|
+
const secretPath = path35__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
26271
27785
|
try {
|
|
26272
|
-
if (
|
|
26273
|
-
const existing =
|
|
27786
|
+
if (fs32__default.default.existsSync(secretPath)) {
|
|
27787
|
+
const existing = fs32__default.default.readFileSync(secretPath, "utf-8").trim();
|
|
26274
27788
|
if (existing.length >= 16) return existing;
|
|
26275
27789
|
}
|
|
26276
27790
|
const generated = crypto.randomUUID();
|
|
26277
|
-
|
|
26278
|
-
|
|
27791
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(secretPath), { recursive: true });
|
|
27792
|
+
fs32__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
26279
27793
|
if (this.config.dashboard.auth) {
|
|
26280
27794
|
console.warn(
|
|
26281
27795
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -26302,7 +27816,7 @@ var DashboardServer = class {
|
|
|
26302
27816
|
// ── Setup ─────────────────────────────────────
|
|
26303
27817
|
getGlobalStore() {
|
|
26304
27818
|
if (!this.globalStore) {
|
|
26305
|
-
const globalDbPath =
|
|
27819
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
26306
27820
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
26307
27821
|
}
|
|
26308
27822
|
return this.globalStore;
|
|
@@ -26682,12 +28196,12 @@ ${prompt}`;
|
|
|
26682
28196
|
}
|
|
26683
28197
|
}
|
|
26684
28198
|
watchRuntimeChanges() {
|
|
26685
|
-
const workspaceDbPath =
|
|
26686
|
-
const globalDbPath =
|
|
28199
|
+
const workspaceDbPath = path35__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
28200
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
26687
28201
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
26688
28202
|
for (const watchPath of watchPaths) {
|
|
26689
|
-
if (!
|
|
26690
|
-
|
|
28203
|
+
if (!fs32__default.default.existsSync(watchPath)) continue;
|
|
28204
|
+
fs32__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
26691
28205
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
26692
28206
|
});
|
|
26693
28207
|
}
|
|
@@ -26809,7 +28323,7 @@ ${prompt}`;
|
|
|
26809
28323
|
const sessionId = req.params.id;
|
|
26810
28324
|
this.store.deleteSession(sessionId);
|
|
26811
28325
|
this.store.deleteRuntimeSession(sessionId);
|
|
26812
|
-
const globalDbPath =
|
|
28326
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
26813
28327
|
const globalStore = new MemoryStore(globalDbPath);
|
|
26814
28328
|
try {
|
|
26815
28329
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -26902,7 +28416,7 @@ ${prompt}`;
|
|
|
26902
28416
|
});
|
|
26903
28417
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
26904
28418
|
const body = req.body;
|
|
26905
|
-
const globalDbPath =
|
|
28419
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
26906
28420
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
26907
28421
|
const globalStore = new MemoryStore(globalDbPath);
|
|
26908
28422
|
try {
|
|
@@ -26925,7 +28439,7 @@ ${prompt}`;
|
|
|
26925
28439
|
});
|
|
26926
28440
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
26927
28441
|
this.store.deleteAllRuntimeNodes();
|
|
26928
|
-
const globalDbPath =
|
|
28442
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
26929
28443
|
const globalStore = new MemoryStore(globalDbPath);
|
|
26930
28444
|
try {
|
|
26931
28445
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -26994,8 +28508,7 @@ ${prompt}`;
|
|
|
26994
28508
|
res.json(log);
|
|
26995
28509
|
});
|
|
26996
28510
|
this.app.get("/api/config", auth2, (_req, res) => {
|
|
26997
|
-
const safe =
|
|
26998
|
-
safe.providers = safe.providers.map((p) => ({ ...p, apiKey: p.apiKey ? "***" : void 0 }));
|
|
28511
|
+
const safe = redactProviderSecrets(this.config);
|
|
26999
28512
|
res.json(safe);
|
|
27000
28513
|
});
|
|
27001
28514
|
this.app.put("/api/config", auth2, async (req, res) => {
|
|
@@ -27011,12 +28524,12 @@ ${prompt}`;
|
|
|
27011
28524
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
27012
28525
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
27013
28526
|
try {
|
|
27014
|
-
const configPath =
|
|
27015
|
-
const existing =
|
|
28527
|
+
const configPath = path35__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
28528
|
+
const existing = fs32__default.default.existsSync(configPath) ? JSON.parse(fs32__default.default.readFileSync(configPath, "utf-8")) : {};
|
|
27016
28529
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
27017
28530
|
const tmp = configPath + ".tmp";
|
|
27018
|
-
|
|
27019
|
-
|
|
28531
|
+
fs32__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
28532
|
+
fs32__default.default.renameSync(tmp, configPath);
|
|
27020
28533
|
res.json({ ok: true });
|
|
27021
28534
|
} catch (err) {
|
|
27022
28535
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -27044,7 +28557,7 @@ ${prompt}`;
|
|
|
27044
28557
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
27045
28558
|
const scope = req.query["scope"] ?? "workspace";
|
|
27046
28559
|
if (scope === "global") {
|
|
27047
|
-
const globalDbPath =
|
|
28560
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
27048
28561
|
const globalStore = new MemoryStore(globalDbPath);
|
|
27049
28562
|
try {
|
|
27050
28563
|
res.json({
|
|
@@ -27385,13 +28898,13 @@ ${prompt}`;
|
|
|
27385
28898
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
27386
28899
|
}
|
|
27387
28900
|
});
|
|
27388
|
-
const prodPath =
|
|
27389
|
-
const devPath =
|
|
27390
|
-
const webDistPath =
|
|
27391
|
-
if (
|
|
28901
|
+
const prodPath = path35__default.default.resolve(__dirname$1, "../web/dist");
|
|
28902
|
+
const devPath = path35__default.default.resolve(__dirname$1, "../../web/dist");
|
|
28903
|
+
const webDistPath = fs32__default.default.existsSync(prodPath) ? prodPath : devPath;
|
|
28904
|
+
if (fs32__default.default.existsSync(webDistPath)) {
|
|
27392
28905
|
this.app.use(express__default.default.static(webDistPath));
|
|
27393
28906
|
this.app.get("*", (_req, res) => {
|
|
27394
|
-
res.sendFile(
|
|
28907
|
+
res.sendFile(path35__default.default.join(webDistPath, "index.html"));
|
|
27395
28908
|
});
|
|
27396
28909
|
} else {
|
|
27397
28910
|
this.app.get("/", (_req, res) => {
|
|
@@ -27412,7 +28925,7 @@ init_constants();
|
|
|
27412
28925
|
async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
27413
28926
|
const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
|
|
27414
28927
|
const spin = ora__default.default({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
|
|
27415
|
-
const store = new MemoryStore(
|
|
28928
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
27416
28929
|
const server = new DashboardServer(config, store, workspacePath);
|
|
27417
28930
|
server.watchRuntimeChanges();
|
|
27418
28931
|
const gThis = globalThis;
|
|
@@ -27424,10 +28937,10 @@ async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
|
27424
28937
|
process.once("exit", onExit);
|
|
27425
28938
|
try {
|
|
27426
28939
|
await server.start();
|
|
27427
|
-
spin.succeed(
|
|
28940
|
+
spin.succeed(chalk8__default.default.green(`Dashboard running at http://localhost:${port}`));
|
|
27428
28941
|
server.refreshRuntime("workspace");
|
|
27429
28942
|
server.refreshRuntime("global");
|
|
27430
|
-
console.log(
|
|
28943
|
+
console.log(chalk8__default.default.gray(` Press Ctrl+C to stop
|
|
27431
28944
|
`));
|
|
27432
28945
|
await new Promise(() => {
|
|
27433
28946
|
});
|
|
@@ -27436,7 +28949,7 @@ async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
|
27436
28949
|
await server.stop().catch(() => {
|
|
27437
28950
|
});
|
|
27438
28951
|
onExit();
|
|
27439
|
-
spin.fail(
|
|
28952
|
+
spin.fail(chalk8__default.default.red(`Dashboard failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
27440
28953
|
process.exit(1);
|
|
27441
28954
|
}
|
|
27442
28955
|
}
|
|
@@ -27444,18 +28957,18 @@ init_constants();
|
|
|
27444
28957
|
function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
27445
28958
|
const identity = new commander.Command("identity").alias("id").description("Manage Cascade identities");
|
|
27446
28959
|
identity.command("list").description("List all available identities").action(() => {
|
|
27447
|
-
const store = new MemoryStore(
|
|
28960
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
27448
28961
|
try {
|
|
27449
28962
|
const identities = store.listIdentities();
|
|
27450
|
-
console.log(
|
|
28963
|
+
console.log(chalk8__default.default.bold("\n Identities:"));
|
|
27451
28964
|
if (identities.length === 0) {
|
|
27452
|
-
console.log(
|
|
28965
|
+
console.log(chalk8__default.default.gray(" No identities found."));
|
|
27453
28966
|
} else {
|
|
27454
28967
|
identities.forEach((id) => {
|
|
27455
|
-
const defaultLabel = id.isDefault ?
|
|
27456
|
-
console.log(` - ${
|
|
27457
|
-
console.log(
|
|
27458
|
-
if (id.description) console.log(
|
|
28968
|
+
const defaultLabel = id.isDefault ? chalk8__default.default.green(" [Default]") : "";
|
|
28969
|
+
console.log(` - ${chalk8__default.default.cyan(id.name)}${defaultLabel}`);
|
|
28970
|
+
console.log(chalk8__default.default.gray(` ID: ${id.id}`));
|
|
28971
|
+
if (id.description) console.log(chalk8__default.default.gray(` ${id.description}`));
|
|
27459
28972
|
});
|
|
27460
28973
|
}
|
|
27461
28974
|
console.log();
|
|
@@ -27464,7 +28977,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
27464
28977
|
}
|
|
27465
28978
|
});
|
|
27466
28979
|
identity.command("create <name>").description("Create a new identity").option("-d, --desc <text>", "Description of the identity").option("-s, --system <text>", "System prompt").option("--default", "Set as default identity").action((name, options) => {
|
|
27467
|
-
const store = new MemoryStore(
|
|
28980
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
27468
28981
|
try {
|
|
27469
28982
|
if (options.default) {
|
|
27470
28983
|
const existingDefault = store.getDefaultIdentity();
|
|
@@ -27481,21 +28994,21 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
27481
28994
|
isDefault: !!options.default,
|
|
27482
28995
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
27483
28996
|
});
|
|
27484
|
-
console.log(
|
|
28997
|
+
console.log(chalk8__default.default.green(`
|
|
27485
28998
|
Successfully created identity: ${name} (${id})`));
|
|
27486
|
-
if (options.default) console.log(
|
|
28999
|
+
if (options.default) console.log(chalk8__default.default.green(" Set as default."));
|
|
27487
29000
|
console.log();
|
|
27488
29001
|
} finally {
|
|
27489
29002
|
store.close();
|
|
27490
29003
|
}
|
|
27491
29004
|
});
|
|
27492
29005
|
identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
|
|
27493
|
-
const store = new MemoryStore(
|
|
29006
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
27494
29007
|
try {
|
|
27495
29008
|
const identities = store.listIdentities();
|
|
27496
29009
|
const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
|
|
27497
29010
|
if (!match) {
|
|
27498
|
-
console.error(
|
|
29011
|
+
console.error(chalk8__default.default.red(`
|
|
27499
29012
|
Identity '${query}' not found.
|
|
27500
29013
|
`));
|
|
27501
29014
|
process.exit(1);
|
|
@@ -27505,7 +29018,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
27505
29018
|
store.updateIdentity(existingDefault.id, { isDefault: false });
|
|
27506
29019
|
}
|
|
27507
29020
|
store.updateIdentity(match.id, { isDefault: true });
|
|
27508
|
-
console.log(
|
|
29021
|
+
console.log(chalk8__default.default.green(`
|
|
27509
29022
|
Identity ${match.name} is now the default.
|
|
27510
29023
|
`));
|
|
27511
29024
|
} finally {
|
|
@@ -27516,7 +29029,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
27516
29029
|
}
|
|
27517
29030
|
init_pricing();
|
|
27518
29031
|
async function modelsCommand(options = {}) {
|
|
27519
|
-
console.log(
|
|
29032
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Cascade Models\n"));
|
|
27520
29033
|
const cm = new ConfigManager(process.cwd());
|
|
27521
29034
|
await cm.load();
|
|
27522
29035
|
const config = cm.getConfig();
|
|
@@ -27524,16 +29037,16 @@ async function modelsCommand(options = {}) {
|
|
|
27524
29037
|
try {
|
|
27525
29038
|
await router.init(config);
|
|
27526
29039
|
} catch (err) {
|
|
27527
|
-
console.error(
|
|
29040
|
+
console.error(chalk8__default.default.red(` Failed to initialize router: ${err instanceof Error ? err.message : String(err)}`));
|
|
27528
29041
|
process.exit(1);
|
|
27529
29042
|
}
|
|
27530
29043
|
await withTimeout(router.refreshLiveData(), 6e3, "live data timeout").catch(() => {
|
|
27531
29044
|
});
|
|
27532
29045
|
const liveData = router.getLiveData();
|
|
27533
29046
|
const tiers = [
|
|
27534
|
-
{ tier: "T1", label: "T1 Administrator", color:
|
|
27535
|
-
{ tier: "T2", label: "T2 Manager", color:
|
|
27536
|
-
{ tier: "T3", label: "T3 Worker", color:
|
|
29047
|
+
{ tier: "T1", label: "T1 Administrator", color: chalk8__default.default.hex("#4C8DFF") },
|
|
29048
|
+
{ tier: "T2", label: "T2 Manager", color: chalk8__default.default.hex("#38B0DE") },
|
|
29049
|
+
{ tier: "T3", label: "T3 Worker", color: chalk8__default.default.hex("#2DD4BF") }
|
|
27537
29050
|
];
|
|
27538
29051
|
let anyMissing = false;
|
|
27539
29052
|
let anyUnpriced = false;
|
|
@@ -27545,76 +29058,76 @@ async function modelsCommand(options = {}) {
|
|
|
27545
29058
|
const costIn = unpriced ? "cost not tracked" : model.inputCostPer1kTokens === 0 ? "free" : `$${model.inputCostPer1kTokens.toFixed(4)}/1K in`;
|
|
27546
29059
|
const costOut = unpriced ? "" : model.outputCostPer1kTokens === 0 ? "free" : `, $${model.outputCostPer1kTokens.toFixed(4)}/1K out`;
|
|
27547
29060
|
const ctx = model.contextWindow >= 1e6 ? `${(model.contextWindow / 1e6).toFixed(1)}M ctx` : `${(model.contextWindow / 1e3).toFixed(0)}K ctx`;
|
|
27548
|
-
const local = model.isLocal ?
|
|
27549
|
-
const vision = model.isVisionCapable ?
|
|
29061
|
+
const local = model.isLocal ? chalk8__default.default.gray(" [local]") : "";
|
|
29062
|
+
const vision = model.isVisionCapable ? chalk8__default.default.gray(" \u{1F441}") : "";
|
|
27550
29063
|
const bench = Math.round(benchmarkScore01(model, "mixed") * 100);
|
|
27551
29064
|
console.log(
|
|
27552
|
-
` ${color.bold(tier)} ${
|
|
29065
|
+
` ${color.bold(tier)} ${chalk8__default.default.white(col(model.name, 24))}${chalk8__default.default.gray(col(model.provider, 16))}` + (options.verbose ? `${chalk8__default.default.gray(col(ctx, 12))}${chalk8__default.default.gray(col(`bench ${bench}/100`, 14))}${chalk8__default.default.gray(`${costIn}${costOut}`)}` : `${chalk8__default.default.gray(col(ctx, 10))}${chalk8__default.default.gray(`bench ${bench}/100`)}`) + local + vision
|
|
27553
29066
|
);
|
|
27554
29067
|
} else {
|
|
27555
|
-
console.log(` ${color.bold(tier)} ${
|
|
29068
|
+
console.log(` ${color.bold(tier)} ${chalk8__default.default.red("No model available")} ${chalk8__default.default.gray(`(check provider config for ${label})`)}`);
|
|
27556
29069
|
anyMissing = true;
|
|
27557
29070
|
}
|
|
27558
29071
|
}
|
|
27559
29072
|
console.log();
|
|
27560
29073
|
const providers = config.providers.map((p) => p.type).join(", ") || "(none)";
|
|
27561
|
-
console.log(
|
|
29074
|
+
console.log(chalk8__default.default.gray(` Configured providers: ${providers}`));
|
|
27562
29075
|
if (liveData) {
|
|
27563
29076
|
const src = liveData.getDataSource();
|
|
27564
29077
|
const gen = liveData.getGeneratedAt();
|
|
27565
29078
|
const srcLabel = src === "live" ? "live (just fetched)" : src === "cache" ? "cached" : "bundled";
|
|
27566
29079
|
const pricingMeta = pricingDatasetMeta();
|
|
27567
|
-
console.log(
|
|
29080
|
+
console.log(chalk8__default.default.gray(
|
|
27568
29081
|
` Benchmark data: ${srcLabel}` + (gen ? ` \xB7 updated ${gen.slice(0, 10)}` : "") + ` \xB7 pricing: ${liveData.hasLivePricing() ? "live (OpenRouter)" : "dataset"} (baseline ${pricingMeta.generatedAt})`
|
|
27569
29082
|
));
|
|
27570
29083
|
const drift = liveData.getPriceDisagreements();
|
|
27571
29084
|
if (drift.length) {
|
|
27572
|
-
console.log(
|
|
29085
|
+
console.log(chalk8__default.default.yellow(`
|
|
27573
29086
|
${drift.length} model${drift.length === 1 ? "" : "s"} priced differently by the live source than by the bundled dataset:`));
|
|
27574
29087
|
for (const d of drift.slice(0, 10)) {
|
|
27575
|
-
console.log(
|
|
29088
|
+
console.log(chalk8__default.default.gray(
|
|
27576
29089
|
` ${d.provider}:${d.modelId} dataset $${d.dataset.input.toFixed(5)}/$${d.dataset.output.toFixed(5)} per 1K (as of ${d.datasetAsOf}) \u2192 live $${d.live.input.toFixed(5)}/$${d.live.output.toFixed(5)} (${d.ratio}\xD7)`
|
|
27577
29090
|
));
|
|
27578
29091
|
}
|
|
27579
|
-
console.log(
|
|
29092
|
+
console.log(chalk8__default.default.gray(" Live pricing is used. Refresh src/core/router/pricing-data.json to clear this."));
|
|
27580
29093
|
}
|
|
27581
29094
|
}
|
|
27582
29095
|
if (anyUnpriced) {
|
|
27583
|
-
console.log(
|
|
29096
|
+
console.log(chalk8__default.default.yellow(
|
|
27584
29097
|
'\n Some tiers use a model with no published price \u2014 their spend is reported as "cost not tracked", never $0.00,\n and is not counted toward a cost budget. Add the model to the pricing dataset, or set `local: true` on the\n provider if the endpoint genuinely costs nothing to run.'
|
|
27585
29098
|
));
|
|
27586
29099
|
}
|
|
27587
29100
|
if (options.verbose) {
|
|
27588
29101
|
console.log();
|
|
27589
|
-
console.log(
|
|
29102
|
+
console.log(chalk8__default.default.white(" Available models by provider:\n"));
|
|
27590
29103
|
const allProviderTypes = [...new Set(config.providers.map((p) => p.type))];
|
|
27591
29104
|
for (const providerType of allProviderTypes) {
|
|
27592
29105
|
const available = router.getModelsForProvider(providerType);
|
|
27593
29106
|
if (available.length === 0) continue;
|
|
27594
|
-
console.log(
|
|
29107
|
+
console.log(chalk8__default.default.gray(` ${providerType}:`));
|
|
27595
29108
|
for (const m of available) {
|
|
27596
29109
|
const override = config.models.t1 === m.id ? " \u2190 T1" : config.models.t2 === m.id ? " \u2190 T2" : config.models.t3 === m.id ? " \u2190 T3" : "";
|
|
27597
|
-
console.log(` ${
|
|
29110
|
+
console.log(` ${chalk8__default.default.white(col(m.name, 28))}${chalk8__default.default.gray(m.id)}${chalk8__default.default.yellow(override)}`);
|
|
27598
29111
|
}
|
|
27599
29112
|
console.log();
|
|
27600
29113
|
}
|
|
27601
29114
|
}
|
|
27602
29115
|
if (anyMissing) {
|
|
27603
|
-
console.log(
|
|
29116
|
+
console.log(chalk8__default.default.yellow(" Some tiers have no available model. Run `cascade doctor` for details.\n"));
|
|
27604
29117
|
} else {
|
|
27605
|
-
console.log(
|
|
29118
|
+
console.log(chalk8__default.default.green(" All tiers are configured.\n"));
|
|
27606
29119
|
}
|
|
27607
29120
|
}
|
|
27608
29121
|
async function setModelCommand(action, tierArg, value) {
|
|
27609
29122
|
const tier = (tierArg ?? "").toLowerCase();
|
|
27610
29123
|
if (!["t1", "t2", "t3"].includes(tier)) {
|
|
27611
|
-
console.error(
|
|
27612
|
-
console.log(
|
|
29124
|
+
console.error(chalk8__default.default.red(` Invalid tier "${tierArg ?? ""}". Use t1, t2, or t3.`));
|
|
29125
|
+
console.log(chalk8__default.default.gray(" e.g. cascade models set t1 anthropic:claude-opus-4-8"));
|
|
27613
29126
|
process.exit(1);
|
|
27614
29127
|
}
|
|
27615
29128
|
const override = action === "unset" ? "auto" : (value ?? "").trim();
|
|
27616
29129
|
if (action === "set" && !override) {
|
|
27617
|
-
console.error(
|
|
29130
|
+
console.error(chalk8__default.default.red(' Missing value. e.g. cascade models set t3 openai:gpt-4o-mini (or "auto")'));
|
|
27618
29131
|
process.exit(1);
|
|
27619
29132
|
}
|
|
27620
29133
|
const cm = new ConfigManager(process.cwd());
|
|
@@ -27624,8 +29137,8 @@ async function setModelCommand(action, tierArg, value) {
|
|
|
27624
29137
|
if (override === "auto") delete models[tier];
|
|
27625
29138
|
else models[tier] = override;
|
|
27626
29139
|
await cm.updateConfig({ models });
|
|
27627
|
-
const label = override === "auto" ?
|
|
27628
|
-
console.log(
|
|
29140
|
+
const label = override === "auto" ? chalk8__default.default.gray("auto (routing decides)") : chalk8__default.default.white(override);
|
|
29141
|
+
console.log(chalk8__default.default.green(`
|
|
27629
29142
|
\u2713 ${tier.toUpperCase()} model set to ${label}
|
|
27630
29143
|
`));
|
|
27631
29144
|
}
|
|
@@ -27639,8 +29152,8 @@ async function exportCommand(options = {}) {
|
|
|
27639
29152
|
let store;
|
|
27640
29153
|
try {
|
|
27641
29154
|
const workspacePath = options.workspacePath ?? process.cwd();
|
|
27642
|
-
const workspaceDbPath =
|
|
27643
|
-
const globalDbPath =
|
|
29155
|
+
const workspaceDbPath = path35__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
29156
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
27644
29157
|
let dbPath = globalDbPath;
|
|
27645
29158
|
try {
|
|
27646
29159
|
await fs11__default.default.access(workspaceDbPath);
|
|
@@ -27649,7 +29162,7 @@ async function exportCommand(options = {}) {
|
|
|
27649
29162
|
}
|
|
27650
29163
|
store = new MemoryStore(dbPath);
|
|
27651
29164
|
} catch (err) {
|
|
27652
|
-
spin.fail(
|
|
29165
|
+
spin.fail(chalk8__default.default.red(`Cannot open memory store: ${err instanceof Error ? err.message : String(err)}`));
|
|
27653
29166
|
process.exit(1);
|
|
27654
29167
|
}
|
|
27655
29168
|
try {
|
|
@@ -27657,7 +29170,7 @@ async function exportCommand(options = {}) {
|
|
|
27657
29170
|
if (options.sessionId) {
|
|
27658
29171
|
const session2 = store.getSession(options.sessionId);
|
|
27659
29172
|
if (!session2) {
|
|
27660
|
-
spin.fail(
|
|
29173
|
+
spin.fail(chalk8__default.default.red(`Session "${options.sessionId}" not found.`));
|
|
27661
29174
|
process.exit(1);
|
|
27662
29175
|
}
|
|
27663
29176
|
sessions2 = [session2];
|
|
@@ -27665,14 +29178,14 @@ async function exportCommand(options = {}) {
|
|
|
27665
29178
|
const limit = options.last ?? 10;
|
|
27666
29179
|
sessions2 = store.listSessions(void 0, limit);
|
|
27667
29180
|
if (sessions2.length === 0) {
|
|
27668
|
-
spin.warn(
|
|
29181
|
+
spin.warn(chalk8__default.default.yellow("No sessions found."));
|
|
27669
29182
|
return;
|
|
27670
29183
|
}
|
|
27671
29184
|
const latest = sessions2[0];
|
|
27672
29185
|
const full = store.getSession(latest.id);
|
|
27673
29186
|
sessions2 = full ? [full] : [];
|
|
27674
29187
|
if (sessions2.length === 0) {
|
|
27675
|
-
spin.fail(
|
|
29188
|
+
spin.fail(chalk8__default.default.red("Could not load latest session."));
|
|
27676
29189
|
process.exit(1);
|
|
27677
29190
|
}
|
|
27678
29191
|
}
|
|
@@ -27682,17 +29195,17 @@ async function exportCommand(options = {}) {
|
|
|
27682
29195
|
const ext = format === "json" ? ".json" : ".md";
|
|
27683
29196
|
const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
|
|
27684
29197
|
const defaultFile = `cascade-export-${safeName}${ext}`;
|
|
27685
|
-
const outPath = options.output ?
|
|
29198
|
+
const outPath = options.output ? path35__default.default.resolve(options.output) : path35__default.default.join(process.cwd(), defaultFile);
|
|
27686
29199
|
await fs11__default.default.writeFile(outPath, content, "utf-8");
|
|
27687
|
-
spin.succeed(
|
|
29200
|
+
spin.succeed(chalk8__default.default.green(`Exported to ${chalk8__default.default.white(outPath)}`));
|
|
27688
29201
|
const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
|
|
27689
29202
|
console.log();
|
|
27690
|
-
console.log(
|
|
27691
|
-
console.log(
|
|
27692
|
-
console.log(
|
|
29203
|
+
console.log(chalk8__default.default.gray(` Session: ${session.title}`));
|
|
29204
|
+
console.log(chalk8__default.default.gray(` Messages: ${messageCount}`));
|
|
29205
|
+
console.log(chalk8__default.default.gray(` Format: ${format}`));
|
|
27693
29206
|
console.log();
|
|
27694
29207
|
} catch (err) {
|
|
27695
|
-
spin.fail(
|
|
29208
|
+
spin.fail(chalk8__default.default.red(`Export failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
27696
29209
|
process.exit(1);
|
|
27697
29210
|
}
|
|
27698
29211
|
}
|
|
@@ -27746,93 +29259,6 @@ function buildJsonExport(session) {
|
|
|
27746
29259
|
};
|
|
27747
29260
|
return JSON.stringify(payload, null, 2);
|
|
27748
29261
|
}
|
|
27749
|
-
async function linkCommand(target, options = {}) {
|
|
27750
|
-
const found = await discoverCredentials();
|
|
27751
|
-
if (found.length === 0) {
|
|
27752
|
-
console.log(chalk7__default.default.yellow("\n No reusable credentials found.\n"));
|
|
27753
|
-
console.log(chalk7__default.default.gray(" Cascade looks for Claude Code, Codex, Gemini CLI, and GitHub Copilot logins,"));
|
|
27754
|
-
console.log(chalk7__default.default.gray(" plus ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY in your environment.\n"));
|
|
27755
|
-
return;
|
|
27756
|
-
}
|
|
27757
|
-
if (!target) {
|
|
27758
|
-
printDiscovered(found);
|
|
27759
|
-
return;
|
|
27760
|
-
}
|
|
27761
|
-
const provider = normalizeProvider(target);
|
|
27762
|
-
if (!provider) {
|
|
27763
|
-
console.log(chalk7__default.default.red(`
|
|
27764
|
-
Unknown provider "${target}". Use one of: anthropic, openai, gemini.
|
|
27765
|
-
`));
|
|
27766
|
-
return;
|
|
27767
|
-
}
|
|
27768
|
-
const candidates2 = found.filter((c) => c.provider === provider);
|
|
27769
|
-
const chosen = candidates2.find((c) => c.directlyUsable) ?? candidates2[0];
|
|
27770
|
-
if (!chosen) {
|
|
27771
|
-
console.log(chalk7__default.default.yellow(`
|
|
27772
|
-
No detected credential maps to "${provider}".
|
|
27773
|
-
`));
|
|
27774
|
-
return;
|
|
27775
|
-
}
|
|
27776
|
-
if (!chosen.directlyUsable) {
|
|
27777
|
-
console.log(chalk7__default.default.yellow(`
|
|
27778
|
-
Found a ${chosen.sourceTool} credential, but it can't be used against the standard ${provider} API.`));
|
|
27779
|
-
if (chosen.warning) console.log(chalk7__default.default.gray(` ${chosen.warning}`));
|
|
27780
|
-
console.log(chalk7__default.default.gray(" Cascade won't adopt it because it would create a non-working provider.\n"));
|
|
27781
|
-
return;
|
|
27782
|
-
}
|
|
27783
|
-
if (chosen.kind === "oauth" && !options.acceptRisk) {
|
|
27784
|
-
console.log(chalk7__default.default.yellow(`
|
|
27785
|
-
${chosen.sourceTool} provides a subscription OAuth token, not an API key.`));
|
|
27786
|
-
if (chosen.warning) console.log(chalk7__default.default.gray(` ${chosen.warning}`));
|
|
27787
|
-
console.log(chalk7__default.default.gray(" Re-run with --accept-risk to adopt it anyway:\n"));
|
|
27788
|
-
console.log(chalk7__default.default.cyan(` cascade link ${provider} --accept-risk
|
|
27789
|
-
`));
|
|
27790
|
-
return;
|
|
27791
|
-
}
|
|
27792
|
-
await adoptCredential(chosen, options.workspace ?? process.cwd());
|
|
27793
|
-
console.log(chalk7__default.default.green(`
|
|
27794
|
-
\u2713 Linked ${provider} using your ${chosen.sourceTool} credential (${maskSecret(chosen.secret)}).`));
|
|
27795
|
-
if (chosen.kind === "oauth") {
|
|
27796
|
-
console.log(chalk7__default.default.gray(" Adopted as an OAuth bearer token \u2014 revoke it in the source tool to disable."));
|
|
27797
|
-
}
|
|
27798
|
-
console.log(chalk7__default.default.gray(" Run `cascade doctor` to verify, or `cascade` to start.\n"));
|
|
27799
|
-
}
|
|
27800
|
-
function printDiscovered(found) {
|
|
27801
|
-
console.log(chalk7__default.default.magenta("\n \u25C8 Detected credentials\n"));
|
|
27802
|
-
for (const c of found) {
|
|
27803
|
-
const usable = c.directlyUsable ? chalk7__default.default.green("usable") : chalk7__default.default.yellow("needs vendor backend");
|
|
27804
|
-
const kind = c.kind === "oauth" ? chalk7__default.default.yellow("oauth") : chalk7__default.default.gray("api-key");
|
|
27805
|
-
console.log(` ${chalk7__default.default.white(c.provider.padEnd(18))} ${chalk7__default.default.gray(maskSecret(c.secret).padEnd(12))} ${kind} ${usable}`);
|
|
27806
|
-
console.log(chalk7__default.default.gray(` from ${c.sourceTool}`));
|
|
27807
|
-
if (c.warning) console.log(chalk7__default.default.yellow(` \u26A0 ${c.warning}`));
|
|
27808
|
-
}
|
|
27809
|
-
console.log(chalk7__default.default.gray("\n Adopt one with: ") + chalk7__default.default.cyan("cascade link <provider> [--accept-risk]"));
|
|
27810
|
-
console.log(chalk7__default.default.gray(" --accept-risk is required for subscription OAuth tokens.\n"));
|
|
27811
|
-
}
|
|
27812
|
-
function normalizeProvider(target) {
|
|
27813
|
-
const t = target.toLowerCase();
|
|
27814
|
-
if (t === "anthropic" || t === "claude" || t === "claude-code") return "anthropic";
|
|
27815
|
-
if (t === "openai" || t === "codex" || t === "gpt") return "openai";
|
|
27816
|
-
if (t === "gemini" || t === "google") return "gemini";
|
|
27817
|
-
return null;
|
|
27818
|
-
}
|
|
27819
|
-
async function adoptCredential(cred, workspace) {
|
|
27820
|
-
const cm = new ConfigManager(workspace);
|
|
27821
|
-
await cm.load();
|
|
27822
|
-
const config = cm.getConfig();
|
|
27823
|
-
const next = {
|
|
27824
|
-
type: cred.provider,
|
|
27825
|
-
credentialSource: cred.sourceTool
|
|
27826
|
-
};
|
|
27827
|
-
if (cred.kind === "oauth" && cred.provider === "anthropic") {
|
|
27828
|
-
next.authToken = cred.secret;
|
|
27829
|
-
} else {
|
|
27830
|
-
next.apiKey = cred.secret;
|
|
27831
|
-
}
|
|
27832
|
-
const providers = config.providers.filter((p) => p.type !== cred.provider);
|
|
27833
|
-
providers.push(next);
|
|
27834
|
-
await cm.updateConfig({ providers });
|
|
27835
|
-
}
|
|
27836
29262
|
async function telemetryCommand(action) {
|
|
27837
29263
|
const cm = new ConfigManager(process.cwd());
|
|
27838
29264
|
await cm.load();
|
|
@@ -27840,12 +29266,12 @@ async function telemetryCommand(action) {
|
|
|
27840
29266
|
if (action === "status") {
|
|
27841
29267
|
const state = config.telemetry?.enabled ? "ON" : "OFF";
|
|
27842
29268
|
console.log();
|
|
27843
|
-
console.log(
|
|
29269
|
+
console.log(chalk8__default.default.magenta(" \u25C8 Cascade Telemetry"));
|
|
27844
29270
|
console.log();
|
|
27845
|
-
console.log(` Status: ${config.telemetry?.enabled ?
|
|
27846
|
-
console.log(
|
|
29271
|
+
console.log(` Status: ${config.telemetry?.enabled ? chalk8__default.default.green(state) : chalk8__default.default.gray(state)}`);
|
|
29272
|
+
console.log(chalk8__default.default.gray(" Scope: anonymous session metadata only (no prompts/outputs)"));
|
|
27847
29273
|
console.log();
|
|
27848
|
-
console.log(
|
|
29274
|
+
console.log(chalk8__default.default.gray(" Toggle with: cascade telemetry on | cascade telemetry off"));
|
|
27849
29275
|
console.log();
|
|
27850
29276
|
return;
|
|
27851
29277
|
}
|
|
@@ -27859,11 +29285,11 @@ async function telemetryCommand(action) {
|
|
|
27859
29285
|
});
|
|
27860
29286
|
console.log();
|
|
27861
29287
|
if (enabled) {
|
|
27862
|
-
console.log(
|
|
27863
|
-
console.log(
|
|
29288
|
+
console.log(chalk8__default.default.green(` \u2713 Telemetry enabled.`));
|
|
29289
|
+
console.log(chalk8__default.default.gray(" Anonymous session metadata (no prompts, no outputs) will be sent."));
|
|
27864
29290
|
} else {
|
|
27865
|
-
console.log(
|
|
27866
|
-
console.log(
|
|
29291
|
+
console.log(chalk8__default.default.yellow(` \u2713 Telemetry disabled.`));
|
|
29292
|
+
console.log(chalk8__default.default.gray(" No events will be transmitted from this workspace."));
|
|
27867
29293
|
}
|
|
27868
29294
|
console.log();
|
|
27869
29295
|
}
|
|
@@ -27873,11 +29299,11 @@ async function statsCommand() {
|
|
|
27873
29299
|
await tracker.load();
|
|
27874
29300
|
const all = tracker.getAll();
|
|
27875
29301
|
if (all.size === 0) {
|
|
27876
|
-
console.log(
|
|
29302
|
+
console.log(chalk8__default.default.dim("\n No routing history yet \u2014 run some tasks first.\n"));
|
|
27877
29303
|
return;
|
|
27878
29304
|
}
|
|
27879
|
-
console.log(
|
|
27880
|
-
console.log(
|
|
29305
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Auto-Routing History\n"));
|
|
29306
|
+
console.log(chalk8__default.default.dim(" Per-task-type model performance learned from past runs.\n"));
|
|
27881
29307
|
for (const taskType of TASK_TYPES) {
|
|
27882
29308
|
const entries = [];
|
|
27883
29309
|
for (const [key, stat] of all) {
|
|
@@ -27889,36 +29315,36 @@ async function statsCommand() {
|
|
|
27889
29315
|
}
|
|
27890
29316
|
if (entries.length === 0) continue;
|
|
27891
29317
|
entries.sort((a, b) => b.successRate - a.successRate || b.samples - a.samples);
|
|
27892
|
-
console.log(
|
|
29318
|
+
console.log(chalk8__default.default.bold(` ${taskType.toUpperCase()}`));
|
|
27893
29319
|
const header = ` ${"Model".padEnd(36)} ${"Success".padEnd(9)} ${"Samples".padEnd(9)} Avg cost`;
|
|
27894
|
-
console.log(
|
|
27895
|
-
console.log(
|
|
29320
|
+
console.log(chalk8__default.default.dim(header));
|
|
29321
|
+
console.log(chalk8__default.default.dim(" " + "\u2500".repeat(62)));
|
|
27896
29322
|
for (const e of entries) {
|
|
27897
29323
|
const pct = `${Math.round(e.successRate * 100)}%`;
|
|
27898
29324
|
const cost = e.avgCostUsd < 1e-4 ? "<$0.0001" : `$${e.avgCostUsd.toFixed(4)}`;
|
|
27899
|
-
const color = e.successRate >= 0.8 ?
|
|
29325
|
+
const color = e.successRate >= 0.8 ? chalk8__default.default.green : e.successRate >= 0.5 ? chalk8__default.default.yellow : chalk8__default.default.red;
|
|
27900
29326
|
console.log(
|
|
27901
|
-
` ${e.modelId.padEnd(36)} ${color(pct.padEnd(9))} ${String(e.samples).padEnd(9)} ${
|
|
29327
|
+
` ${e.modelId.padEnd(36)} ${color(pct.padEnd(9))} ${String(e.samples).padEnd(9)} ${chalk8__default.default.dim(cost)}`
|
|
27902
29328
|
);
|
|
27903
29329
|
}
|
|
27904
29330
|
console.log();
|
|
27905
29331
|
}
|
|
27906
|
-
console.log(
|
|
29332
|
+
console.log(chalk8__default.default.dim(" tip: use /rate good | bad after a task to improve these scores.\n"));
|
|
27907
29333
|
}
|
|
27908
29334
|
|
|
27909
29335
|
// src/cli/index.ts
|
|
27910
29336
|
dotenv__default.default.config();
|
|
27911
29337
|
function warnIfBuildIsStale() {
|
|
27912
29338
|
try {
|
|
27913
|
-
const here =
|
|
29339
|
+
const here = path35__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
|
|
27914
29340
|
for (const rel of ["..", "../.."]) {
|
|
27915
|
-
const pkgPath =
|
|
27916
|
-
if (!
|
|
27917
|
-
const pkg = JSON.parse(
|
|
29341
|
+
const pkgPath = path35__default.default.join(here, rel, "package.json");
|
|
29342
|
+
if (!fs32__default.default.existsSync(pkgPath)) continue;
|
|
29343
|
+
const pkg = JSON.parse(fs32__default.default.readFileSync(pkgPath, "utf-8"));
|
|
27918
29344
|
if (pkg.name !== "cascade-ai") continue;
|
|
27919
29345
|
if (pkg.version && pkg.version !== CASCADE_VERSION) {
|
|
27920
29346
|
console.error(
|
|
27921
|
-
|
|
29347
|
+
chalk8__default.default.yellow(
|
|
27922
29348
|
`\u26A0 Stale build: compiled output is v${CASCADE_VERSION} but the source tree is v${pkg.version}.
|
|
27923
29349
|
Run: npm install && npm run build`
|
|
27924
29350
|
)
|
|
@@ -28017,7 +29443,7 @@ mcp.command("list").description("List configured MCP servers").action(async () =
|
|
|
28017
29443
|
mcp.command("remove <name>").description("Remove an MCP server (and its stored OAuth tokens)").action(async (name) => {
|
|
28018
29444
|
await mcpRemoveCommand(name);
|
|
28019
29445
|
});
|
|
28020
|
-
program.command("link [provider]").description("Reuse credentials from other AI CLIs (Claude Code, Codex, Gemini CLI, Copilot)").option("--accept-risk", "
|
|
29446
|
+
program.command("link [provider]").description("Reuse credentials from other AI CLIs (Claude Code, Codex, Gemini CLI, Copilot)").option("--accept-risk", "Deprecated \u2014 subscription tokens can no longer be adopted").action(async (provider, opts) => {
|
|
28021
29447
|
await linkCommand(provider, { acceptRisk: opts.acceptRisk, workspace: process.cwd() });
|
|
28022
29448
|
});
|
|
28023
29449
|
program.command("update").description("Update Cascade to the latest version").action(async () => {
|
|
@@ -28043,7 +29469,7 @@ program.command("models [action] [tier] [value]").description("List AI models pe
|
|
|
28043
29469
|
program.command("telemetry [action]").description("Toggle anonymous usage telemetry (on | off | status). Default: status").action(async (action) => {
|
|
28044
29470
|
const normalized = (action ?? "status").toLowerCase();
|
|
28045
29471
|
if (normalized !== "on" && normalized !== "off" && normalized !== "status") {
|
|
28046
|
-
console.error(
|
|
29472
|
+
console.error(chalk8__default.default.red(`Unknown action: ${action}. Use: on | off | status`));
|
|
28047
29473
|
process.exit(1);
|
|
28048
29474
|
}
|
|
28049
29475
|
await telemetryCommand(normalized);
|
|
@@ -28065,15 +29491,15 @@ async function startRepl(options) {
|
|
|
28065
29491
|
try {
|
|
28066
29492
|
await cm.load();
|
|
28067
29493
|
} catch (err) {
|
|
28068
|
-
console.error(
|
|
28069
|
-
console.error(
|
|
29494
|
+
console.error(chalk8__default.default.red(`Config error: ${err instanceof Error ? err.message : String(err)}`));
|
|
29495
|
+
console.error(chalk8__default.default.gray("Run `cascade init` to set up this directory."));
|
|
28070
29496
|
process.exit(1);
|
|
28071
29497
|
}
|
|
28072
29498
|
let config = cm.getConfig();
|
|
28073
29499
|
const startupNotice = cm.takeRetiredNotice();
|
|
28074
29500
|
const needsSetup = !hasUsableProvider(config.providers);
|
|
28075
29501
|
if (needsSetup) {
|
|
28076
|
-
console.log(
|
|
29502
|
+
console.log(chalk8__default.default.magenta(" \u25C8 No providers configured \u2014 launching setup wizard\u2026"));
|
|
28077
29503
|
console.log();
|
|
28078
29504
|
config = await runSetupWizard(workspacePath);
|
|
28079
29505
|
await cm.updateConfig(config);
|
|
@@ -28108,24 +29534,24 @@ async function runHeadless(prompt, options) {
|
|
|
28108
29534
|
try {
|
|
28109
29535
|
await cm.load();
|
|
28110
29536
|
} catch (err) {
|
|
28111
|
-
console.error(
|
|
28112
|
-
console.error(
|
|
29537
|
+
console.error(chalk8__default.default.red(`Config error: ${err instanceof Error ? err.message : String(err)}`));
|
|
29538
|
+
console.error(chalk8__default.default.gray("Run `cascade init` to set up this directory."));
|
|
28113
29539
|
process.exit(1);
|
|
28114
29540
|
}
|
|
28115
29541
|
const config = cm.getConfig();
|
|
28116
29542
|
const needsSetup = !hasUsableProvider(config.providers);
|
|
28117
29543
|
if (needsSetup) {
|
|
28118
|
-
console.error(
|
|
29544
|
+
console.error(chalk8__default.default.red("No providers configured. Run `cascade init` first."));
|
|
28119
29545
|
process.exit(1);
|
|
28120
29546
|
}
|
|
28121
29547
|
const cascade = new Cascade(config, workspacePath);
|
|
28122
29548
|
try {
|
|
28123
29549
|
await cascade.init();
|
|
28124
29550
|
} catch (err) {
|
|
28125
|
-
console.error(
|
|
29551
|
+
console.error(chalk8__default.default.red(`Initialization failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
28126
29552
|
process.exit(1);
|
|
28127
29553
|
}
|
|
28128
|
-
console.error(
|
|
29554
|
+
console.error(chalk8__default.default.gray(" \u25C8 Running headlessly \u2014 tool approvals are auto-granted."));
|
|
28129
29555
|
let lastProgress = "";
|
|
28130
29556
|
cascade.on("tier:status", (ev) => {
|
|
28131
29557
|
const action = ev?.currentAction?.trim();
|
|
@@ -28133,7 +29559,7 @@ async function runHeadless(prompt, options) {
|
|
|
28133
29559
|
const line = ` \xB7 ${ev.role ?? ""} ${action}`.trimEnd();
|
|
28134
29560
|
if (line === lastProgress) return;
|
|
28135
29561
|
lastProgress = line;
|
|
28136
|
-
console.error(
|
|
29562
|
+
console.error(chalk8__default.default.gray(line));
|
|
28137
29563
|
});
|
|
28138
29564
|
try {
|
|
28139
29565
|
const result = await cascade.run({
|
|
@@ -28144,7 +29570,7 @@ async function runHeadless(prompt, options) {
|
|
|
28144
29570
|
});
|
|
28145
29571
|
process.stdout.write(result.output.trimEnd() + "\n");
|
|
28146
29572
|
} catch (err) {
|
|
28147
|
-
console.error(
|
|
29573
|
+
console.error(chalk8__default.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
28148
29574
|
await cascade.close().catch(() => {
|
|
28149
29575
|
});
|
|
28150
29576
|
process.exit(1);
|
|
@@ -28156,9 +29582,9 @@ async function runHeadless(prompt, options) {
|
|
|
28156
29582
|
function printBanner() {
|
|
28157
29583
|
if (process.stdout.columns < 60) return;
|
|
28158
29584
|
console.log();
|
|
28159
|
-
console.log(" " +
|
|
28160
|
-
console.log(" " +
|
|
28161
|
-
console.log(" " +
|
|
29585
|
+
console.log(" " + chalk8__default.default.hex("#4C8DFF").bold("\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
|
|
29586
|
+
console.log(" " + chalk8__default.default.hex("#38B0DE").bold(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588") + " " + chalk8__default.default.white.bold("CASCADE") + chalk8__default.default.gray(" v" + CASCADE_VERSION));
|
|
29587
|
+
console.log(" " + chalk8__default.default.hex("#2DD4BF").bold(" \u2588\u2588\u2588\u2588\u2588\u2588") + " " + chalk8__default.default.hex("#8A93A8")("multi-tier orchestration"));
|
|
28162
29588
|
console.log();
|
|
28163
29589
|
}
|
|
28164
29590
|
program.parse(process.argv);
|