cascade-ai 0.74.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 +2030 -696
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2008 -674
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +50734 -49654
- package/dist/index.cjs +16114 -15034
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +554 -34
- package/dist/index.d.ts +554 -34
- package/dist/index.js +16115 -15053
- 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 = {}) {
|
|
@@ -2400,6 +2638,8 @@ var init_model_filter = __esm({
|
|
|
2400
2638
|
var anthropic_exports = {};
|
|
2401
2639
|
__export(anthropic_exports, {
|
|
2402
2640
|
AnthropicProvider: () => AnthropicProvider,
|
|
2641
|
+
anthropicApiRoot: () => anthropicApiRoot,
|
|
2642
|
+
anthropicAuth: () => anthropicAuth,
|
|
2403
2643
|
toAnthropicToolUse: () => toAnthropicToolUse,
|
|
2404
2644
|
toAnthropicTools: () => toAnthropicTools
|
|
2405
2645
|
});
|
|
@@ -2424,25 +2664,56 @@ function toAnthropicToolUse(toolCalls) {
|
|
|
2424
2664
|
input: tc.input
|
|
2425
2665
|
}));
|
|
2426
2666
|
}
|
|
2427
|
-
|
|
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;
|
|
2428
2692
|
var init_anthropic = __esm({
|
|
2429
2693
|
"src/providers/anthropic.ts"() {
|
|
2430
2694
|
init_constants();
|
|
2431
2695
|
init_base();
|
|
2432
2696
|
init_pricing();
|
|
2433
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
|
+
});
|
|
2434
2704
|
AnthropicProvider = class extends BaseProvider {
|
|
2435
2705
|
client;
|
|
2436
2706
|
constructor(config, model) {
|
|
2437
2707
|
super(config, model);
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
});
|
|
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 });
|
|
2443
2712
|
} else {
|
|
2444
2713
|
this.client = new Anthropic__default.default({
|
|
2445
|
-
apiKey:
|
|
2714
|
+
apiKey: auth2.mode === "apiKey" ? auth2.key : void 0,
|
|
2715
|
+
fetch: sameOriginFetch,
|
|
2716
|
+
...baseURL ? { baseURL } : {}
|
|
2446
2717
|
});
|
|
2447
2718
|
}
|
|
2448
2719
|
}
|
|
@@ -2518,21 +2789,24 @@ var init_anthropic = __esm({
|
|
|
2518
2789
|
async countTokens(text) {
|
|
2519
2790
|
return Math.ceil(text.length / 4);
|
|
2520
2791
|
}
|
|
2521
|
-
async listModels() {
|
|
2792
|
+
async listModels(options = {}) {
|
|
2793
|
+
const staticCatalog = () => options.staticFallback === false ? [] : Object.values(MODELS).filter((m) => m.provider === "anthropic");
|
|
2522
2794
|
try {
|
|
2523
|
-
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, {
|
|
2524
2799
|
headers: {
|
|
2525
|
-
|
|
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 : "" },
|
|
2526
2804
|
"anthropic-version": "2023-06-01"
|
|
2527
2805
|
}
|
|
2528
2806
|
});
|
|
2529
|
-
if (!resp.ok)
|
|
2530
|
-
return Object.values(MODELS).filter((m) => m.provider === "anthropic");
|
|
2531
|
-
}
|
|
2807
|
+
if (!resp.ok) return staticCatalog();
|
|
2532
2808
|
const data = await resp.json();
|
|
2533
|
-
if (!Array.isArray(data?.data))
|
|
2534
|
-
return Object.values(MODELS).filter((m) => m.provider === "anthropic");
|
|
2535
|
-
}
|
|
2809
|
+
if (!Array.isArray(data?.data)) return staticCatalog();
|
|
2536
2810
|
return data.data.filter((m) => isChatModel(m.id)).map((m) => {
|
|
2537
2811
|
const known = Object.values(MODELS).find((km) => km.id === m.id && km.provider === "anthropic");
|
|
2538
2812
|
if (known) return known;
|
|
@@ -2550,7 +2824,7 @@ var init_anthropic = __esm({
|
|
|
2550
2824
|
});
|
|
2551
2825
|
});
|
|
2552
2826
|
} catch {
|
|
2553
|
-
return
|
|
2827
|
+
return staticCatalog();
|
|
2554
2828
|
}
|
|
2555
2829
|
}
|
|
2556
2830
|
async isAvailable() {
|
|
@@ -2770,7 +3044,8 @@ var init_openai = __esm({
|
|
|
2770
3044
|
async countTokens(text) {
|
|
2771
3045
|
return Math.ceil(text.length / 4);
|
|
2772
3046
|
}
|
|
2773
|
-
async listModels() {
|
|
3047
|
+
async listModels(options = {}) {
|
|
3048
|
+
const staticCatalog = () => options.staticFallback === false ? [] : Object.values(MODELS).filter((m) => m.provider === "openai");
|
|
2774
3049
|
try {
|
|
2775
3050
|
const response = await this.client.models.list();
|
|
2776
3051
|
return response.data.filter((m) => isChatModel(m.id)).map((m) => {
|
|
@@ -2790,7 +3065,7 @@ var init_openai = __esm({
|
|
|
2790
3065
|
});
|
|
2791
3066
|
});
|
|
2792
3067
|
} catch {
|
|
2793
|
-
return
|
|
3068
|
+
return staticCatalog();
|
|
2794
3069
|
}
|
|
2795
3070
|
}
|
|
2796
3071
|
async isAvailable() {
|
|
@@ -3092,9 +3367,46 @@ var init_gemini_schema = __esm({
|
|
|
3092
3367
|
var gemini_exports = {};
|
|
3093
3368
|
__export(gemini_exports, {
|
|
3094
3369
|
GeminiProvider: () => GeminiProvider,
|
|
3370
|
+
geminiApiRoot: () => geminiApiRoot,
|
|
3095
3371
|
toGeminiFunctionCalls: () => toGeminiFunctionCalls,
|
|
3096
3372
|
toGeminiTools: () => toGeminiTools
|
|
3097
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
|
+
}
|
|
3098
3410
|
function toGeminiTools(tools) {
|
|
3099
3411
|
return tools.map((tool) => ({
|
|
3100
3412
|
name: tool.name,
|
|
@@ -3127,7 +3439,7 @@ async function describeGeminiError(resp) {
|
|
|
3127
3439
|
return "";
|
|
3128
3440
|
}
|
|
3129
3441
|
}
|
|
3130
|
-
var GeminiProvider;
|
|
3442
|
+
var GEMINI_API_VERSION, GEMINI_PUBLIC_ROOT, SAFETY_SETTINGS, GeminiProvider;
|
|
3131
3443
|
var init_gemini = __esm({
|
|
3132
3444
|
"src/providers/gemini.ts"() {
|
|
3133
3445
|
init_constants();
|
|
@@ -3135,11 +3447,21 @@ var init_gemini = __esm({
|
|
|
3135
3447
|
init_pricing();
|
|
3136
3448
|
init_model_filter();
|
|
3137
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
|
+
];
|
|
3138
3457
|
GeminiProvider = class extends BaseProvider {
|
|
3139
3458
|
client;
|
|
3140
3459
|
constructor(config, model) {
|
|
3141
3460
|
super(config, model);
|
|
3142
|
-
this.client = new genai.GoogleGenAI({
|
|
3461
|
+
this.client = new genai.GoogleGenAI({
|
|
3462
|
+
apiKey: config.apiKey ?? "",
|
|
3463
|
+
...config.baseUrl ? { httpOptions: { baseUrl: geminiApiRoot(config.baseUrl) } } : {}
|
|
3464
|
+
});
|
|
3143
3465
|
}
|
|
3144
3466
|
async generate(options) {
|
|
3145
3467
|
const chunks = [];
|
|
@@ -3147,15 +3469,12 @@ var init_gemini = __esm({
|
|
|
3147
3469
|
}
|
|
3148
3470
|
async generateStream(options, onChunk) {
|
|
3149
3471
|
const contents = this.buildContents(options.messages, options.images);
|
|
3150
|
-
const stream = await this.client.models.generateContentStream({
|
|
3472
|
+
const stream = this.config.baseUrl ? await this.streamOverRest(contents, options) : await this.client.models.generateContentStream({
|
|
3151
3473
|
model: this.model.id,
|
|
3152
3474
|
contents,
|
|
3153
3475
|
config: {
|
|
3154
3476
|
systemInstruction: options.systemPrompt,
|
|
3155
|
-
safetySettings:
|
|
3156
|
-
{ category: genai.HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: genai.HarmBlockThreshold.BLOCK_NONE },
|
|
3157
|
-
{ category: genai.HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: genai.HarmBlockThreshold.BLOCK_NONE }
|
|
3158
|
-
],
|
|
3477
|
+
safetySettings: SAFETY_SETTINGS,
|
|
3159
3478
|
tools: options.tools?.length ? [{ functionDeclarations: toGeminiTools(options.tools) }] : void 0,
|
|
3160
3479
|
abortSignal: options.signal
|
|
3161
3480
|
}
|
|
@@ -3206,25 +3525,40 @@ var init_gemini = __esm({
|
|
|
3206
3525
|
};
|
|
3207
3526
|
}
|
|
3208
3527
|
async countTokens(text) {
|
|
3528
|
+
const contents = [{ role: "user", parts: [{ text }] }];
|
|
3209
3529
|
try {
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
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 });
|
|
3214
3547
|
return result.totalTokens ?? 0;
|
|
3215
3548
|
} catch {
|
|
3216
3549
|
return Math.ceil(text.length / 4);
|
|
3217
3550
|
}
|
|
3218
3551
|
}
|
|
3219
|
-
async listModels() {
|
|
3552
|
+
async listModels(options = {}) {
|
|
3553
|
+
const staticCatalog = () => options.staticFallback === false ? [] : Object.values(MODELS).filter((m) => m.provider === "gemini");
|
|
3220
3554
|
try {
|
|
3221
3555
|
const resp = await this.fetchModelList();
|
|
3222
3556
|
if (!resp.ok) {
|
|
3223
|
-
return
|
|
3557
|
+
return staticCatalog();
|
|
3224
3558
|
}
|
|
3225
3559
|
const data = await resp.json();
|
|
3226
3560
|
if (!Array.isArray(data?.models)) {
|
|
3227
|
-
return
|
|
3561
|
+
return staticCatalog();
|
|
3228
3562
|
}
|
|
3229
3563
|
return data.models.filter((m) => isChatModel(m.name.replace("models/", ""), m.supportedGenerationMethods)).map((m) => {
|
|
3230
3564
|
const id = m.name.replace("models/", "");
|
|
@@ -3246,7 +3580,7 @@ var init_gemini = __esm({
|
|
|
3246
3580
|
});
|
|
3247
3581
|
});
|
|
3248
3582
|
} catch {
|
|
3249
|
-
return
|
|
3583
|
+
return staticCatalog();
|
|
3250
3584
|
}
|
|
3251
3585
|
}
|
|
3252
3586
|
/**
|
|
@@ -3299,8 +3633,43 @@ var init_gemini = __esm({
|
|
|
3299
3633
|
* logs, error reports and shell history, and this one would carry the key
|
|
3300
3634
|
* with it.
|
|
3301
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
|
+
}
|
|
3302
3671
|
fetchModelList() {
|
|
3303
|
-
return
|
|
3672
|
+
return fetchSameOrigin(`${geminiApiRoot(this.config.baseUrl)}/${GEMINI_API_VERSION}/models`, {
|
|
3304
3673
|
headers: { "x-goog-api-key": this.config.apiKey ?? "" }
|
|
3305
3674
|
});
|
|
3306
3675
|
}
|
|
@@ -3388,84 +3757,6 @@ var init_gemini = __esm({
|
|
|
3388
3757
|
};
|
|
3389
3758
|
}
|
|
3390
3759
|
});
|
|
3391
|
-
function preferIpv4Host(url) {
|
|
3392
|
-
return url;
|
|
3393
|
-
}
|
|
3394
|
-
async function nodeHttpFetch(input, init = {}, redirectCount = 0, opts = {}) {
|
|
3395
|
-
const urlStr = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
3396
|
-
const u = new URL(preferIpv4Host(urlStr) ?? urlStr);
|
|
3397
|
-
const lib = u.protocol === "https:" ? https__default.default : http__default.default;
|
|
3398
|
-
const method = (init.method ?? "GET").toUpperCase();
|
|
3399
|
-
const headers = {};
|
|
3400
|
-
const h = init.headers;
|
|
3401
|
-
if (h instanceof Headers) h.forEach((v, k) => {
|
|
3402
|
-
headers[k] = v;
|
|
3403
|
-
});
|
|
3404
|
-
else if (Array.isArray(h)) for (const [k, v] of h) headers[k] = v;
|
|
3405
|
-
else if (h) Object.assign(headers, h);
|
|
3406
|
-
if (!Object.keys(headers).some((k) => k.toLowerCase() === "accept-encoding")) {
|
|
3407
|
-
headers["accept-encoding"] = "gzip, deflate, br";
|
|
3408
|
-
}
|
|
3409
|
-
const body = init.body == null ? void 0 : typeof init.body === "string" ? init.body : Buffer.from(init.body);
|
|
3410
|
-
return new Promise((resolve2, reject) => {
|
|
3411
|
-
const req = lib.request(
|
|
3412
|
-
{
|
|
3413
|
-
hostname: u.hostname,
|
|
3414
|
-
port: u.port || (u.protocol === "https:" ? 443 : 80),
|
|
3415
|
-
path: u.pathname + u.search,
|
|
3416
|
-
method,
|
|
3417
|
-
headers
|
|
3418
|
-
},
|
|
3419
|
-
(res) => {
|
|
3420
|
-
const status = res.statusCode ?? 200;
|
|
3421
|
-
const location = res.headers.location;
|
|
3422
|
-
if (status >= 300 && status < 400 && location && redirectCount < MAX_REDIRECTS) {
|
|
3423
|
-
res.resume();
|
|
3424
|
-
const nextUrl = new URL(location, u).href;
|
|
3425
|
-
if (opts.allowedRedirectOrigin && new URL(nextUrl).origin !== opts.allowedRedirectOrigin) {
|
|
3426
|
-
reject(new Error(`Refusing cross-origin redirect to ${new URL(nextUrl).origin}`));
|
|
3427
|
-
return;
|
|
3428
|
-
}
|
|
3429
|
-
const downgrade = status === 303 || (status === 301 || status === 302) && method !== "GET" && method !== "HEAD";
|
|
3430
|
-
const nextInit = downgrade ? { ...init, method: "GET", body: void 0 } : init;
|
|
3431
|
-
resolve2(nodeHttpFetch(nextUrl, nextInit, redirectCount + 1, opts));
|
|
3432
|
-
return;
|
|
3433
|
-
}
|
|
3434
|
-
const encoding = (res.headers["content-encoding"] ?? "").toLowerCase();
|
|
3435
|
-
let bodyStream = res;
|
|
3436
|
-
if (encoding === "gzip" || encoding === "x-gzip") bodyStream = res.pipe(zlib__default.default.createGunzip());
|
|
3437
|
-
else if (encoding === "deflate") bodyStream = res.pipe(zlib__default.default.createInflate());
|
|
3438
|
-
else if (encoding === "br") bodyStream = res.pipe(zlib__default.default.createBrotliDecompress());
|
|
3439
|
-
const stream$1 = stream.Readable.toWeb(bodyStream);
|
|
3440
|
-
const respHeaders = new Headers();
|
|
3441
|
-
for (const [k, v] of Object.entries(res.headers)) {
|
|
3442
|
-
if (k === "content-encoding" || k === "content-length") continue;
|
|
3443
|
-
if (Array.isArray(v)) respHeaders.set(k, v.join(", "));
|
|
3444
|
-
else if (typeof v === "string") respHeaders.set(k, v);
|
|
3445
|
-
}
|
|
3446
|
-
resolve2(new Response(stream$1, {
|
|
3447
|
-
status,
|
|
3448
|
-
statusText: res.statusMessage ?? "",
|
|
3449
|
-
headers: respHeaders
|
|
3450
|
-
}));
|
|
3451
|
-
}
|
|
3452
|
-
);
|
|
3453
|
-
req.on("error", reject);
|
|
3454
|
-
req.setTimeout(15e3, () => req.destroy(new Error("request timed out")));
|
|
3455
|
-
if (body !== void 0) req.write(body);
|
|
3456
|
-
req.end();
|
|
3457
|
-
});
|
|
3458
|
-
}
|
|
3459
|
-
var MAX_REDIRECTS;
|
|
3460
|
-
var init_net = __esm({
|
|
3461
|
-
"src/utils/net.ts"() {
|
|
3462
|
-
try {
|
|
3463
|
-
dns__default.default.setDefaultResultOrder("ipv4first");
|
|
3464
|
-
} catch {
|
|
3465
|
-
}
|
|
3466
|
-
MAX_REDIRECTS = 5;
|
|
3467
|
-
}
|
|
3468
|
-
});
|
|
3469
3760
|
|
|
3470
3761
|
// src/providers/ollama.ts
|
|
3471
3762
|
var ollama_exports = {};
|
|
@@ -3754,23 +4045,49 @@ var init_openai_compatible = __esm({
|
|
|
3754
4045
|
OpenAICompatibleProvider = class extends OpenAIProvider {
|
|
3755
4046
|
constructor(config, model) {
|
|
3756
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
|
+
}
|
|
3757
4053
|
this.client = new OpenAI__default.default({
|
|
3758
4054
|
apiKey: config.apiKey ?? "not-required",
|
|
3759
4055
|
baseURL: preferIpv4Host(config.baseUrl),
|
|
3760
|
-
|
|
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()))
|
|
3761
4064
|
});
|
|
3762
4065
|
}
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
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
|
+
}
|
|
4080
|
+
modelsUrl() {
|
|
4081
|
+
const base = (preferIpv4Host(this.config.baseUrl) ?? "").replace(/\/+$/, "");
|
|
4082
|
+
return base + "/models";
|
|
4083
|
+
}
|
|
4084
|
+
authHeaders() {
|
|
3768
4085
|
const h = { Accept: "application/json" };
|
|
3769
4086
|
if (this.config.apiKey) h["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
3770
4087
|
return h;
|
|
3771
4088
|
}
|
|
3772
4089
|
async listModels() {
|
|
3773
|
-
const res = await nodeHttpFetch(this.modelsUrl(), { headers: this.authHeaders() });
|
|
4090
|
+
const res = await nodeHttpFetch(this.modelsUrl(), { headers: this.authHeaders() }, 0, this.redirectPolicy());
|
|
3774
4091
|
if (!res.ok) throw new Error(`models endpoint ${this.modelsUrl()} returned HTTP ${res.status}`);
|
|
3775
4092
|
const body = await res.json();
|
|
3776
4093
|
const raw = Array.isArray(body?.data) ? body.data : Array.isArray(body?.models) ? body.models : [];
|
|
@@ -3800,7 +4117,7 @@ var init_openai_compatible = __esm({
|
|
|
3800
4117
|
}
|
|
3801
4118
|
async isAvailable() {
|
|
3802
4119
|
try {
|
|
3803
|
-
const res = await nodeHttpFetch(this.modelsUrl(), { headers: this.authHeaders() });
|
|
4120
|
+
const res = await nodeHttpFetch(this.modelsUrl(), { headers: this.authHeaders() }, 0, this.redirectPolicy());
|
|
3804
4121
|
return res.ok;
|
|
3805
4122
|
} catch {
|
|
3806
4123
|
return false;
|
|
@@ -3822,12 +4139,12 @@ var init_audit_logger = __esm({
|
|
|
3822
4139
|
constructor(workspacePath, debugMode = false) {
|
|
3823
4140
|
this.workspacePath = workspacePath;
|
|
3824
4141
|
this.debugMode = debugMode;
|
|
3825
|
-
const cascadeDir =
|
|
3826
|
-
if (!
|
|
3827
|
-
|
|
4142
|
+
const cascadeDir = path35__default.default.join(workspacePath, ".cascade");
|
|
4143
|
+
if (!fs32__default.default.existsSync(cascadeDir)) {
|
|
4144
|
+
fs32__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
3828
4145
|
}
|
|
3829
|
-
this.keyPath =
|
|
3830
|
-
this.dbPath =
|
|
4146
|
+
this.keyPath = path35__default.default.join(cascadeDir, "audit_log.key");
|
|
4147
|
+
this.dbPath = path35__default.default.join(cascadeDir, "audit_log.db");
|
|
3831
4148
|
this.initEncryptionKey();
|
|
3832
4149
|
this.db = new Database2__default.default(this.dbPath);
|
|
3833
4150
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -3857,11 +4174,11 @@ var init_audit_logger = __esm({
|
|
|
3857
4174
|
return crypto__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
3858
4175
|
}
|
|
3859
4176
|
initEncryptionKey() {
|
|
3860
|
-
if (
|
|
3861
|
-
this.encryptionKey =
|
|
4177
|
+
if (fs32__default.default.existsSync(this.keyPath)) {
|
|
4178
|
+
this.encryptionKey = fs32__default.default.readFileSync(this.keyPath);
|
|
3862
4179
|
} else {
|
|
3863
4180
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
3864
|
-
|
|
4181
|
+
fs32__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
3865
4182
|
}
|
|
3866
4183
|
}
|
|
3867
4184
|
encrypt(text) {
|
|
@@ -3939,9 +4256,9 @@ var init_audit_logger = __esm({
|
|
|
3939
4256
|
dumpDebugIfNeeded() {
|
|
3940
4257
|
if (!this.debugMode) return;
|
|
3941
4258
|
try {
|
|
3942
|
-
const dumpPath =
|
|
4259
|
+
const dumpPath = path35__default.default.join(os10__default.default.tmpdir(), "cascade_audit_logs_debug.json");
|
|
3943
4260
|
const entries = this.getAllLogs();
|
|
3944
|
-
|
|
4261
|
+
fs32__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
3945
4262
|
} catch (err) {
|
|
3946
4263
|
console.error("Failed to dump debug audit logs", err);
|
|
3947
4264
|
}
|
|
@@ -3996,7 +4313,7 @@ var Keystore = class {
|
|
|
3996
4313
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
3997
4314
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
3998
4315
|
this.backend = "keytar";
|
|
3999
|
-
if (password &&
|
|
4316
|
+
if (password && fs32__default.default.existsSync(this.storePath)) {
|
|
4000
4317
|
try {
|
|
4001
4318
|
const fileEntries = this.decryptFile(password);
|
|
4002
4319
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -4015,7 +4332,7 @@ var Keystore = class {
|
|
|
4015
4332
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
4016
4333
|
);
|
|
4017
4334
|
}
|
|
4018
|
-
if (!
|
|
4335
|
+
if (!fs32__default.default.existsSync(this.storePath)) {
|
|
4019
4336
|
const salt = crypto__default.default.randomBytes(SALT_LEN);
|
|
4020
4337
|
this.masterKey = this.deriveKey(password, salt);
|
|
4021
4338
|
this.writeWithSalt({}, salt);
|
|
@@ -4029,7 +4346,7 @@ var Keystore = class {
|
|
|
4029
4346
|
}
|
|
4030
4347
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
4031
4348
|
unlockSync(password) {
|
|
4032
|
-
if (!
|
|
4349
|
+
if (!fs32__default.default.existsSync(this.storePath)) {
|
|
4033
4350
|
const salt = crypto__default.default.randomBytes(SALT_LEN);
|
|
4034
4351
|
this.masterKey = this.deriveKey(password, salt);
|
|
4035
4352
|
this.writeWithSalt({}, salt);
|
|
@@ -4087,7 +4404,7 @@ var Keystore = class {
|
|
|
4087
4404
|
}
|
|
4088
4405
|
}
|
|
4089
4406
|
decryptFile(password, knownSalt) {
|
|
4090
|
-
if (!
|
|
4407
|
+
if (!fs32__default.default.existsSync(this.storePath)) return {};
|
|
4091
4408
|
try {
|
|
4092
4409
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
4093
4410
|
const useSalt = knownSalt ?? salt;
|
|
@@ -4109,8 +4426,8 @@ var Keystore = class {
|
|
|
4109
4426
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
4110
4427
|
const tag = cipher.getAuthTag();
|
|
4111
4428
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
4112
|
-
|
|
4113
|
-
|
|
4429
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(this.storePath), { recursive: true });
|
|
4430
|
+
fs32__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
4114
4431
|
}
|
|
4115
4432
|
writeWithSalt(data, salt) {
|
|
4116
4433
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -4120,11 +4437,11 @@ var Keystore = class {
|
|
|
4120
4437
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
4121
4438
|
const tag = cipher.getAuthTag();
|
|
4122
4439
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
4123
|
-
|
|
4124
|
-
|
|
4440
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(this.storePath), { recursive: true });
|
|
4441
|
+
fs32__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
4125
4442
|
}
|
|
4126
4443
|
readRaw() {
|
|
4127
|
-
const buf =
|
|
4444
|
+
const buf = fs32__default.default.readFileSync(this.storePath);
|
|
4128
4445
|
let offset = 0;
|
|
4129
4446
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
4130
4447
|
offset += SALT_LEN;
|
|
@@ -4157,7 +4474,7 @@ var CascadeIgnore = class {
|
|
|
4157
4474
|
]);
|
|
4158
4475
|
}
|
|
4159
4476
|
async load(workspacePath) {
|
|
4160
|
-
const filePath =
|
|
4477
|
+
const filePath = path35__default.default.join(workspacePath, ".cascadeignore");
|
|
4161
4478
|
try {
|
|
4162
4479
|
const content = await fs11__default.default.readFile(filePath, "utf-8");
|
|
4163
4480
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
@@ -4168,7 +4485,7 @@ var CascadeIgnore = class {
|
|
|
4168
4485
|
}
|
|
4169
4486
|
isIgnored(filePath, workspacePath) {
|
|
4170
4487
|
try {
|
|
4171
|
-
const relative = workspacePath ?
|
|
4488
|
+
const relative = workspacePath ? path35__default.default.relative(workspacePath, filePath) : filePath;
|
|
4172
4489
|
return this.ig.ignores(relative);
|
|
4173
4490
|
} catch {
|
|
4174
4491
|
return false;
|
|
@@ -4179,7 +4496,7 @@ var CascadeIgnore = class {
|
|
|
4179
4496
|
}
|
|
4180
4497
|
};
|
|
4181
4498
|
async function createDefaultIgnoreFile(workspacePath) {
|
|
4182
|
-
const filePath =
|
|
4499
|
+
const filePath = path35__default.default.join(workspacePath, ".cascadeignore");
|
|
4183
4500
|
const content = `# .cascadeignore \u2014 Files Cascade agents cannot read or modify
|
|
4184
4501
|
# Syntax identical to .gitignore
|
|
4185
4502
|
|
|
@@ -4209,7 +4526,7 @@ Thumbs.db
|
|
|
4209
4526
|
await fs11__default.default.writeFile(filePath, content, "utf-8");
|
|
4210
4527
|
}
|
|
4211
4528
|
async function loadCascadeMd(workspacePath) {
|
|
4212
|
-
const filePath =
|
|
4529
|
+
const filePath = path35__default.default.join(workspacePath, "CASCADE.md");
|
|
4213
4530
|
try {
|
|
4214
4531
|
const raw = await fs11__default.default.readFile(filePath, "utf-8");
|
|
4215
4532
|
return parseCascadeMd(raw);
|
|
@@ -4238,7 +4555,7 @@ ${raw.trim()}`;
|
|
|
4238
4555
|
return { raw, sections, systemPrompt };
|
|
4239
4556
|
}
|
|
4240
4557
|
async function createDefaultCascadeMd(workspacePath) {
|
|
4241
|
-
const filePath =
|
|
4558
|
+
const filePath = path35__default.default.join(workspacePath, "CASCADE.md");
|
|
4242
4559
|
const content = `# Cascade Project Instructions
|
|
4243
4560
|
|
|
4244
4561
|
This file contains project-specific instructions for the Cascade AI agent.
|
|
@@ -4273,7 +4590,7 @@ List any areas the agent should not touch.
|
|
|
4273
4590
|
var MemoryStore = class _MemoryStore {
|
|
4274
4591
|
db;
|
|
4275
4592
|
constructor(dbPath) {
|
|
4276
|
-
|
|
4593
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(dbPath), { recursive: true });
|
|
4277
4594
|
try {
|
|
4278
4595
|
this.db = new Database2__default.default(dbPath, { timeout: 5e3 });
|
|
4279
4596
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -5537,21 +5854,80 @@ function validateConfig(raw) {
|
|
|
5537
5854
|
|
|
5538
5855
|
// src/config/global-credentials.ts
|
|
5539
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
|
|
5540
5899
|
function credentialsPath(globalDir2) {
|
|
5541
|
-
return
|
|
5900
|
+
return path35__default.default.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
5542
5901
|
}
|
|
5543
5902
|
function providerKey(p) {
|
|
5544
|
-
if (p.type === "azure") {
|
|
5545
|
-
return `azure:${p.deploymentName ?? p.baseUrl ?? p.label ?? ""}`;
|
|
5546
|
-
}
|
|
5547
5903
|
return p.type;
|
|
5548
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
|
+
}
|
|
5549
5925
|
function isPersistable(p) {
|
|
5550
5926
|
return Boolean(p.apiKey || p.authToken || p.type === "azure" || p.baseUrl);
|
|
5551
5927
|
}
|
|
5552
5928
|
function loadGlobalCredentials(globalDir2) {
|
|
5553
5929
|
try {
|
|
5554
|
-
const raw =
|
|
5930
|
+
const raw = fs32__default.default.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
5555
5931
|
const parsed = JSON.parse(raw);
|
|
5556
5932
|
if (!Array.isArray(parsed.providers)) return [];
|
|
5557
5933
|
return parsed.providers.filter(
|
|
@@ -5563,34 +5939,93 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
5563
5939
|
}
|
|
5564
5940
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
5565
5941
|
const filePath = credentialsPath(globalDir2);
|
|
5566
|
-
|
|
5942
|
+
fs32__default.default.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
5567
5943
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
5568
5944
|
const tmp = `${filePath}.tmp`;
|
|
5569
|
-
|
|
5570
|
-
|
|
5945
|
+
fs32__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
5946
|
+
fs32__default.default.renameSync(tmp, filePath);
|
|
5571
5947
|
try {
|
|
5572
|
-
|
|
5948
|
+
fs32__default.default.chmodSync(filePath, 384);
|
|
5573
5949
|
} catch {
|
|
5574
5950
|
}
|
|
5575
5951
|
}
|
|
5576
5952
|
function mergeGlobalCredentials(workspaceProviders, globalProviders) {
|
|
5577
5953
|
const merged = [...workspaceProviders];
|
|
5578
|
-
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]));
|
|
5579
5955
|
for (const g of globalProviders) {
|
|
5580
|
-
|
|
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));
|
|
5581
5964
|
if (!existing) {
|
|
5582
5965
|
merged.push({ ...g });
|
|
5583
|
-
byKey.set(providerKey(g), merged[merged.length - 1]);
|
|
5966
|
+
if (g.type !== "azure") byKey.set(providerKey(g), merged[merged.length - 1]);
|
|
5584
5967
|
continue;
|
|
5585
5968
|
}
|
|
5586
|
-
|
|
5587
|
-
if (!existing.authToken && g.authToken) existing.authToken = g.authToken;
|
|
5588
|
-
if (!existing.baseUrl && g.baseUrl) existing.baseUrl = g.baseUrl;
|
|
5589
|
-
if (!existing.apiVersion && g.apiVersion) existing.apiVersion = g.apiVersion;
|
|
5590
|
-
if (!existing.label && g.label) existing.label = g.label;
|
|
5969
|
+
fillFrom(existing, g);
|
|
5591
5970
|
}
|
|
5592
5971
|
return merged;
|
|
5593
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
|
+
}
|
|
5594
6029
|
|
|
5595
6030
|
// src/config/retired-providers.ts
|
|
5596
6031
|
var RETIRED_PROVIDER_TYPES = {
|
|
@@ -5600,7 +6035,7 @@ function isRetiredProviderType(type) {
|
|
|
5600
6035
|
return typeof type === "string" && Object.hasOwn(RETIRED_PROVIDER_TYPES, type);
|
|
5601
6036
|
}
|
|
5602
6037
|
function didCleanupChangeAnything(c) {
|
|
5603
|
-
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;
|
|
5604
6039
|
}
|
|
5605
6040
|
function stripRetiredProviders(raw) {
|
|
5606
6041
|
const cleanup = { removed: [], clearedPins: [] };
|
|
@@ -5647,11 +6082,24 @@ function describeCleanup(c) {
|
|
|
5647
6082
|
for (const type of c.removed) {
|
|
5648
6083
|
parts.push(`removed the retired "${type}" provider (${RETIRED_PROVIDER_TYPES[type]})`);
|
|
5649
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
|
+
}
|
|
5650
6094
|
if (c.clearedPins.length > 0) {
|
|
5651
6095
|
parts.push(`reset ${c.clearedPins.map((t) => t.toUpperCase()).join("/")} to Auto, since the pin named it`);
|
|
5652
6096
|
}
|
|
6097
|
+
if (parts.length === 0) return "";
|
|
5653
6098
|
return `Cascade config migration: ${parts.join("; ")}.`;
|
|
5654
6099
|
}
|
|
6100
|
+
|
|
6101
|
+
// src/config/index.ts
|
|
6102
|
+
init_revoked_credentials();
|
|
5655
6103
|
var MCP_TOOL_PREFIX = "mcp__";
|
|
5656
6104
|
var MAX_SEGMENT_LENGTH = 24;
|
|
5657
6105
|
var SEGMENT_HASH_LENGTH = 8;
|
|
@@ -5833,10 +6281,31 @@ function removeMcpServerDenials(disabledTools, serverName) {
|
|
|
5833
6281
|
// src/config/index.ts
|
|
5834
6282
|
init_constants();
|
|
5835
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
|
+
}
|
|
5836
6294
|
function hasUsableProvider(providers) {
|
|
5837
6295
|
if (!providers?.length) return false;
|
|
5838
|
-
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 } : {} });
|
|
5839
6307
|
}
|
|
6308
|
+
var ENV_ENDPOINT_CHANNEL = /* @__PURE__ */ new Set(["anthropic"]);
|
|
5840
6309
|
var ConfigManager = class {
|
|
5841
6310
|
config;
|
|
5842
6311
|
keystore;
|
|
@@ -5856,6 +6325,16 @@ var ConfigManager = class {
|
|
|
5856
6325
|
* fallback for a list that is empty for some unrelated reason.
|
|
5857
6326
|
*/
|
|
5858
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;
|
|
5859
6338
|
/**
|
|
5860
6339
|
* Human-readable migration notice held for a UI to display. `console.warn`
|
|
5861
6340
|
* is not sufficient: the REPL clears the TTY immediately after load(), and
|
|
@@ -5871,7 +6350,7 @@ var ConfigManager = class {
|
|
|
5871
6350
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
5872
6351
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
5873
6352
|
this.workspacePath = workspacePath;
|
|
5874
|
-
this.globalDir = globalDirOverride ??
|
|
6353
|
+
this.globalDir = globalDirOverride ?? path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
5875
6354
|
}
|
|
5876
6355
|
async load() {
|
|
5877
6356
|
this.config = await this.loadConfig();
|
|
@@ -5885,22 +6364,38 @@ var ConfigManager = class {
|
|
|
5885
6364
|
this.ignore = new CascadeIgnore();
|
|
5886
6365
|
await this.ignore.load(this.workspacePath);
|
|
5887
6366
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
5888
|
-
this.keystore = new Keystore(
|
|
5889
|
-
this.store = new MemoryStore(
|
|
5890
|
-
const
|
|
5891
|
-
|
|
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;
|
|
5892
6374
|
try {
|
|
5893
6375
|
saveGlobalCredentials(this.globalDir, globalCreds.kept);
|
|
5894
6376
|
} catch (err) {
|
|
5895
6377
|
console.warn(`Could not rewrite the global credential store: ${err instanceof Error ? err.message : String(err)}`);
|
|
5896
6378
|
}
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
6379
|
+
if (retiredHere) {
|
|
6380
|
+
this.retiredCleanup = {
|
|
6381
|
+
removed: [.../* @__PURE__ */ new Set([...this.retiredCleanup?.removed ?? [], ...globalCreds.removed])],
|
|
6382
|
+
clearedPins: this.retiredCleanup?.clearedPins ?? []
|
|
6383
|
+
};
|
|
6384
|
+
}
|
|
5901
6385
|
}
|
|
5902
|
-
await this.injectEnvKeys();
|
|
6386
|
+
await this.injectEnvKeys(globalCreds.kept);
|
|
5903
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
|
+
}
|
|
5904
6399
|
for (const type of Object.keys(RETIRED_PROVIDER_TYPES)) {
|
|
5905
6400
|
try {
|
|
5906
6401
|
this.store.purgeCachedModelsForRetiredProvider(type);
|
|
@@ -5909,10 +6404,14 @@ var ConfigManager = class {
|
|
|
5909
6404
|
}
|
|
5910
6405
|
await this.ensureDefaultIdentity();
|
|
5911
6406
|
if (mcpNamesChanged) await this.save();
|
|
6407
|
+
const notices = [];
|
|
5912
6408
|
if (this.retiredCleanup) {
|
|
5913
|
-
|
|
5914
|
-
|
|
6409
|
+
const retired = describeCleanup(this.retiredCleanup);
|
|
6410
|
+
console.warn(retired);
|
|
6411
|
+
notices.push(retired);
|
|
5915
6412
|
}
|
|
6413
|
+
if (this.revokedNotice) notices.push(this.revokedNotice);
|
|
6414
|
+
if (notices.length > 0) this.pendingRetiredNotice = notices.join(" ");
|
|
5916
6415
|
}
|
|
5917
6416
|
getConfig() {
|
|
5918
6417
|
return this.config;
|
|
@@ -5932,12 +6431,18 @@ var ConfigManager = class {
|
|
|
5932
6431
|
getWorkspacePath() {
|
|
5933
6432
|
return this.workspacePath;
|
|
5934
6433
|
}
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
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");
|
|
5939
6444
|
try {
|
|
5940
|
-
saveGlobalCredentials(this.globalDir,
|
|
6445
|
+
saveGlobalCredentials(this.globalDir, config.providers);
|
|
5941
6446
|
} catch (err) {
|
|
5942
6447
|
console.warn(`Failed to sync credentials to global store: ${err instanceof Error ? err.message : String(err)}`);
|
|
5943
6448
|
}
|
|
@@ -5968,19 +6473,35 @@ var ConfigManager = class {
|
|
|
5968
6473
|
const configProvider = this.config.providers.find((p) => p.type === provider);
|
|
5969
6474
|
return configProvider?.apiKey;
|
|
5970
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
|
+
}
|
|
5971
6487
|
async loadConfig() {
|
|
5972
6488
|
this.retiredCleanup = void 0;
|
|
5973
|
-
|
|
6489
|
+
this.revokedCredentials = 0;
|
|
6490
|
+
this.revokedPins = [];
|
|
6491
|
+
this.revokedNotice = void 0;
|
|
6492
|
+
const configPath = path35__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
5974
6493
|
try {
|
|
5975
6494
|
const raw = await fs11__default.default.readFile(configPath, "utf-8");
|
|
5976
6495
|
const parsed = JSON.parse(raw);
|
|
5977
6496
|
const cleanup = stripRetiredProviders(parsed);
|
|
5978
|
-
|
|
5979
|
-
|
|
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;
|
|
5980
6501
|
try {
|
|
5981
6502
|
await fs11__default.default.writeFile(configPath, JSON.stringify(parsed, null, 2), "utf-8");
|
|
5982
6503
|
} catch (err) {
|
|
5983
|
-
console.warn(`Could not persist the
|
|
6504
|
+
console.warn(`Could not persist the config migration: ${err instanceof Error ? err.message : String(err)}`);
|
|
5984
6505
|
}
|
|
5985
6506
|
}
|
|
5986
6507
|
return validateConfig(parsed);
|
|
@@ -5991,9 +6512,102 @@ var ConfigManager = class {
|
|
|
5991
6512
|
throw err;
|
|
5992
6513
|
}
|
|
5993
6514
|
}
|
|
5994
|
-
|
|
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 = []) {
|
|
5995
6608
|
const wasEmpty = this.config.providers.length === 0;
|
|
5996
|
-
const emptiedByRetirement = wasEmpty && !!this.retiredCleanup;
|
|
6609
|
+
const emptiedByRetirement = wasEmpty && (!!this.retiredCleanup || this.revokedCredentials > 0);
|
|
6610
|
+
const mayCreate = (type) => wasEmpty || type === "anthropic" && this.revokedCredentials > 0;
|
|
5997
6611
|
const envProviders = [
|
|
5998
6612
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
5999
6613
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
@@ -6003,11 +6617,66 @@ var ConfigManager = class {
|
|
|
6003
6617
|
for (const { env, type } of envProviders) {
|
|
6004
6618
|
const key = process.env[env];
|
|
6005
6619
|
if (!key) continue;
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
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
|
+
}
|
|
6011
6680
|
}
|
|
6012
6681
|
}
|
|
6013
6682
|
if (wasEmpty && !emptiedByRetirement && !this.config.providers.find((p) => p.type === "ollama")) {
|
|
@@ -6359,15 +7028,15 @@ function SafeTextInput(props) {
|
|
|
6359
7028
|
const displayValue = mask ? mask.repeat(value.length) : value;
|
|
6360
7029
|
let rendered;
|
|
6361
7030
|
if (displayValue.length === 0) {
|
|
6362
|
-
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) : "";
|
|
6363
7032
|
} else if (!showCursor || !focus) {
|
|
6364
7033
|
rendered = displayValue;
|
|
6365
7034
|
} else {
|
|
6366
7035
|
let out = "";
|
|
6367
7036
|
for (let i = 0; i < displayValue.length; i++) {
|
|
6368
|
-
out += i === cursorOffset ?
|
|
7037
|
+
out += i === cursorOffset ? chalk8__default.default.inverse(displayValue[i]) : displayValue[i];
|
|
6369
7038
|
}
|
|
6370
|
-
if (cursorOffset >= displayValue.length) out +=
|
|
7039
|
+
if (cursorOffset >= displayValue.length) out += chalk8__default.default.inverse(" ");
|
|
6371
7040
|
rendered = out;
|
|
6372
7041
|
}
|
|
6373
7042
|
return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: rendered });
|
|
@@ -6981,13 +7650,13 @@ var DeadModelStore = class {
|
|
|
6981
7650
|
function fileDeadModelPersistence(filePath) {
|
|
6982
7651
|
return {
|
|
6983
7652
|
load() {
|
|
6984
|
-
if (!
|
|
6985
|
-
const raw = JSON.parse(
|
|
7653
|
+
if (!fs32.existsSync(filePath)) return [];
|
|
7654
|
+
const raw = JSON.parse(fs32.readFileSync(filePath, "utf-8"));
|
|
6986
7655
|
return Array.isArray(raw) ? raw : [];
|
|
6987
7656
|
},
|
|
6988
7657
|
save(records) {
|
|
6989
|
-
|
|
6990
|
-
|
|
7658
|
+
fs32.mkdirSync(path35.dirname(filePath), { recursive: true });
|
|
7659
|
+
fs32.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
6991
7660
|
}
|
|
6992
7661
|
};
|
|
6993
7662
|
}
|
|
@@ -7411,7 +8080,7 @@ init_pricing();
|
|
|
7411
8080
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
7412
8081
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
7413
8082
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
7414
|
-
var DEFAULT_CACHE_FILE =
|
|
8083
|
+
var DEFAULT_CACHE_FILE = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
7415
8084
|
function normalizeModelId2(id) {
|
|
7416
8085
|
let s = id.toLowerCase();
|
|
7417
8086
|
const slash = s.lastIndexOf("/");
|
|
@@ -7558,7 +8227,7 @@ var LiveDataProvider = class {
|
|
|
7558
8227
|
}
|
|
7559
8228
|
async saveCache() {
|
|
7560
8229
|
try {
|
|
7561
|
-
await fs11__default.default.mkdir(
|
|
8230
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(this.opts.cacheFile), { recursive: true });
|
|
7562
8231
|
const cache = {
|
|
7563
8232
|
fetchedAt: this.fetchedAt,
|
|
7564
8233
|
snapshot: this.snapshot ?? void 0,
|
|
@@ -7825,9 +8494,50 @@ function guardTokens(text) {
|
|
|
7825
8494
|
var DISCOVERY_TTL_MS = 15 * 60 * 1e3;
|
|
7826
8495
|
var DISCOVERY_TIMEOUT_MS = 4e3;
|
|
7827
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
|
+
}
|
|
7828
8534
|
function discoveryCacheKey(type, cfg) {
|
|
7829
|
-
|
|
7830
|
-
|
|
8535
|
+
return [
|
|
8536
|
+
type,
|
|
8537
|
+
credentialIdentity(cfg.apiKey),
|
|
8538
|
+
credentialIdentity(cfg.authToken),
|
|
8539
|
+
cfg.baseUrl ?? ""
|
|
8540
|
+
].join("|");
|
|
7831
8541
|
}
|
|
7832
8542
|
function applyTierLimits(options, tier, limits) {
|
|
7833
8543
|
if (!limits) return options;
|
|
@@ -8126,7 +8836,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8126
8836
|
await Promise.all(providers.map(async (type) => {
|
|
8127
8837
|
if (!this.selector.isProviderAvailable(type)) return;
|
|
8128
8838
|
const cfg = config.providers.find((p) => p.type === type);
|
|
8129
|
-
if (!cfg
|
|
8839
|
+
if (!cfg || !hasProviderCredential(cfg)) return;
|
|
8130
8840
|
const cacheKey = discoveryCacheKey(type, cfg);
|
|
8131
8841
|
let entry = cloudDiscoveryCache.get(cacheKey);
|
|
8132
8842
|
if (!entry || Date.now() - entry.at > DISCOVERY_TTL_MS) {
|
|
@@ -8135,7 +8845,11 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8135
8845
|
try {
|
|
8136
8846
|
const provider = this.createProvider(cfg, seed);
|
|
8137
8847
|
if (typeof provider.listModels !== "function") return;
|
|
8138
|
-
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
|
+
);
|
|
8139
8853
|
if (!models.length) return;
|
|
8140
8854
|
entry = { models, ids: models.map((m) => m.id), at: Date.now() };
|
|
8141
8855
|
cloudDiscoveryCache.set(cacheKey, entry);
|
|
@@ -10399,8 +11113,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
10399
11113
|
// Media generation can run for a minute; without this a cancelled run
|
|
10400
11114
|
// still pays for an image nobody will see.
|
|
10401
11115
|
...this.signal ? { signal: this.signal } : {},
|
|
10402
|
-
saveSnapshot: async (
|
|
10403
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
11116
|
+
saveSnapshot: async (path40, content) => {
|
|
11117
|
+
this.store?.addFileSnapshot(this.taskId, path40, content);
|
|
10404
11118
|
},
|
|
10405
11119
|
sendPeerSync: (to, syncType, content) => {
|
|
10406
11120
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -10603,7 +11317,7 @@ ${assignment.expectedOutput}`;
|
|
|
10603
11317
|
const { promisify: promisify4 } = await import('util');
|
|
10604
11318
|
const execFileAsync2 = promisify4(execFile3);
|
|
10605
11319
|
for (const artifactPath of artifactPaths) {
|
|
10606
|
-
const absolutePath =
|
|
11320
|
+
const absolutePath = path35__default.default.resolve(this.artifactRoot(), artifactPath);
|
|
10607
11321
|
try {
|
|
10608
11322
|
const stat = await fs11__default.default.stat(absolutePath);
|
|
10609
11323
|
if (!stat.isFile()) {
|
|
@@ -10638,7 +11352,7 @@ ${assignment.expectedOutput}`;
|
|
|
10638
11352
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
10639
11353
|
continue;
|
|
10640
11354
|
}
|
|
10641
|
-
const ext =
|
|
11355
|
+
const ext = path35__default.default.extname(absolutePath).toLowerCase();
|
|
10642
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;
|
|
10643
11357
|
if (check) {
|
|
10644
11358
|
try {
|
|
@@ -10730,7 +11444,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
10730
11444
|
async checkAcceptance(assignment) {
|
|
10731
11445
|
const criteria = assignment.acceptance ?? [];
|
|
10732
11446
|
if (!criteria.length) return [];
|
|
10733
|
-
const resolve2 = (target) =>
|
|
11447
|
+
const resolve2 = (target) => path35__default.default.resolve(this.artifactRoot(), target);
|
|
10734
11448
|
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
10735
11449
|
stat: async (target) => {
|
|
10736
11450
|
try {
|
|
@@ -13959,16 +14673,16 @@ function resolveInWorkspace(workspaceRoot, input) {
|
|
|
13959
14673
|
if (typeof input !== "string" || input.length === 0) {
|
|
13960
14674
|
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
13961
14675
|
}
|
|
13962
|
-
const root =
|
|
13963
|
-
const abs =
|
|
13964
|
-
const rel =
|
|
13965
|
-
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)) {
|
|
13966
14680
|
throw new WorkspaceSandboxError(input, root);
|
|
13967
14681
|
}
|
|
13968
14682
|
try {
|
|
13969
|
-
const real =
|
|
13970
|
-
const realRel =
|
|
13971
|
-
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))) {
|
|
13972
14686
|
throw new WorkspaceSandboxError(input, root);
|
|
13973
14687
|
}
|
|
13974
14688
|
} catch (e) {
|
|
@@ -14621,7 +15335,7 @@ var GenerateDocumentTool = class extends BaseTool {
|
|
|
14621
15335
|
}
|
|
14622
15336
|
}
|
|
14623
15337
|
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
14624
|
-
await fs11__default.default.mkdir(
|
|
15338
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(absPath), { recursive: true });
|
|
14625
15339
|
await fs11__default.default.writeFile(absPath, bytes);
|
|
14626
15340
|
const charts = countCharts(content);
|
|
14627
15341
|
return [
|
|
@@ -14842,7 +15556,7 @@ var FileWriteTool = class extends BaseTool {
|
|
|
14842
15556
|
} catch {
|
|
14843
15557
|
}
|
|
14844
15558
|
}
|
|
14845
|
-
await fs11__default.default.mkdir(
|
|
15559
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(absPath), { recursive: true });
|
|
14846
15560
|
await fs11__default.default.writeFile(absPath, content, "utf-8");
|
|
14847
15561
|
return `Written ${content.length} characters to ${filePath}`;
|
|
14848
15562
|
}
|
|
@@ -15340,7 +16054,7 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
15340
16054
|
};
|
|
15341
16055
|
async function fileToImageAttachment(filePath) {
|
|
15342
16056
|
const data = await fs11__default.default.readFile(filePath);
|
|
15343
|
-
const ext =
|
|
16057
|
+
const ext = path35__default.default.extname(filePath).toLowerCase();
|
|
15344
16058
|
const mimeMap = {
|
|
15345
16059
|
".jpg": "image/jpeg",
|
|
15346
16060
|
".jpeg": "image/jpeg",
|
|
@@ -15374,14 +16088,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
15374
16088
|
const filePath = input["path"];
|
|
15375
16089
|
const content = input["content"];
|
|
15376
16090
|
const title = input["title"];
|
|
15377
|
-
const dir =
|
|
15378
|
-
if (!
|
|
15379
|
-
|
|
16091
|
+
const dir = path35__default.default.dirname(filePath);
|
|
16092
|
+
if (!fs32__default.default.existsSync(dir)) {
|
|
16093
|
+
fs32__default.default.mkdirSync(dir, { recursive: true });
|
|
15380
16094
|
}
|
|
15381
16095
|
return new Promise((resolve2, reject) => {
|
|
15382
16096
|
try {
|
|
15383
16097
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
15384
|
-
const stream =
|
|
16098
|
+
const stream = fs32__default.default.createWriteStream(filePath);
|
|
15385
16099
|
doc.pipe(stream);
|
|
15386
16100
|
if (title) {
|
|
15387
16101
|
doc.info["Title"] = title;
|
|
@@ -15459,22 +16173,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
15459
16173
|
}
|
|
15460
16174
|
cmdPrefix = NODE_CMD;
|
|
15461
16175
|
}
|
|
15462
|
-
const tmpDir =
|
|
15463
|
-
if (!
|
|
15464
|
-
|
|
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 });
|
|
15465
16179
|
}
|
|
15466
16180
|
const extension = language === "python" ? "py" : "js";
|
|
15467
16181
|
const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
|
|
15468
|
-
const filePath =
|
|
15469
|
-
|
|
16182
|
+
const filePath = path35__default.default.join(tmpDir, fileName);
|
|
16183
|
+
fs32__default.default.writeFileSync(filePath, code, "utf-8");
|
|
15470
16184
|
const execArgs = [filePath, ...args];
|
|
15471
16185
|
return new Promise((resolve2) => {
|
|
15472
16186
|
const startMs = Date.now();
|
|
15473
16187
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
15474
16188
|
const duration = Date.now() - startMs;
|
|
15475
16189
|
try {
|
|
15476
|
-
if (
|
|
15477
|
-
|
|
16190
|
+
if (fs32__default.default.existsSync(filePath)) {
|
|
16191
|
+
fs32__default.default.unlinkSync(filePath);
|
|
15478
16192
|
}
|
|
15479
16193
|
} catch (cleanupErr) {
|
|
15480
16194
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -15786,7 +16500,7 @@ var GlobTool = class extends BaseTool {
|
|
|
15786
16500
|
};
|
|
15787
16501
|
async execute(input, _options) {
|
|
15788
16502
|
const pattern = input["pattern"];
|
|
15789
|
-
const searchPath = input["path"] ?
|
|
16503
|
+
const searchPath = input["path"] ? path35__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
15790
16504
|
const matches = await glob.glob(pattern, {
|
|
15791
16505
|
cwd: searchPath,
|
|
15792
16506
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -15799,7 +16513,7 @@ var GlobTool = class extends BaseTool {
|
|
|
15799
16513
|
const withMtime = await Promise.all(
|
|
15800
16514
|
matches.map(async (rel) => {
|
|
15801
16515
|
try {
|
|
15802
|
-
const stat = await fs11__default.default.stat(
|
|
16516
|
+
const stat = await fs11__default.default.stat(path35__default.default.join(searchPath, rel));
|
|
15803
16517
|
return { rel, mtime: stat.mtimeMs };
|
|
15804
16518
|
} catch {
|
|
15805
16519
|
return { rel, mtime: 0 };
|
|
@@ -15848,7 +16562,7 @@ var GrepTool = class extends BaseTool {
|
|
|
15848
16562
|
};
|
|
15849
16563
|
async execute(input, _options) {
|
|
15850
16564
|
const pattern = input["pattern"];
|
|
15851
|
-
const searchPath = input["path"] ?
|
|
16565
|
+
const searchPath = input["path"] ? path35__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
15852
16566
|
const globPattern = input["glob"];
|
|
15853
16567
|
const outputMode = input["output_mode"] ?? "content";
|
|
15854
16568
|
const context = input["context"] ?? 0;
|
|
@@ -15902,12 +16616,12 @@ var GrepTool = class extends BaseTool {
|
|
|
15902
16616
|
nodir: true
|
|
15903
16617
|
});
|
|
15904
16618
|
} catch {
|
|
15905
|
-
files = [
|
|
16619
|
+
files = [path35__default.default.relative(searchPath, searchPath) || "."];
|
|
15906
16620
|
}
|
|
15907
16621
|
const results = [];
|
|
15908
16622
|
let totalCount = 0;
|
|
15909
16623
|
for (const rel of files) {
|
|
15910
|
-
const abs =
|
|
16624
|
+
const abs = path35__default.default.join(searchPath, rel);
|
|
15911
16625
|
let content;
|
|
15912
16626
|
try {
|
|
15913
16627
|
content = await fs11__default.default.readFile(abs, "utf-8");
|
|
@@ -16306,10 +17020,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
16306
17020
|
}
|
|
16307
17021
|
isIgnored(filePath) {
|
|
16308
17022
|
if (!filePath) return false;
|
|
16309
|
-
const abs =
|
|
16310
|
-
const rel =
|
|
16311
|
-
if (!rel || rel.startsWith("..") ||
|
|
16312
|
-
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("/");
|
|
16313
17027
|
return this.ignoreMatcher.ignores(posixRel);
|
|
16314
17028
|
}
|
|
16315
17029
|
};
|
|
@@ -16543,22 +17257,22 @@ var FileMcpOAuthStore = class {
|
|
|
16543
17257
|
filePath;
|
|
16544
17258
|
load() {
|
|
16545
17259
|
try {
|
|
16546
|
-
return JSON.parse(
|
|
17260
|
+
return JSON.parse(fs32__default.default.readFileSync(this.filePath, "utf-8"));
|
|
16547
17261
|
} catch {
|
|
16548
17262
|
return void 0;
|
|
16549
17263
|
}
|
|
16550
17264
|
}
|
|
16551
17265
|
save(state) {
|
|
16552
|
-
|
|
16553
|
-
|
|
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 });
|
|
16554
17268
|
try {
|
|
16555
|
-
|
|
17269
|
+
fs32__default.default.chmodSync(this.filePath, 384);
|
|
16556
17270
|
} catch {
|
|
16557
17271
|
}
|
|
16558
17272
|
}
|
|
16559
17273
|
clear() {
|
|
16560
17274
|
try {
|
|
16561
|
-
|
|
17275
|
+
fs32__default.default.rmSync(this.filePath);
|
|
16562
17276
|
} catch {
|
|
16563
17277
|
}
|
|
16564
17278
|
}
|
|
@@ -17206,7 +17920,7 @@ var TaskAnalyzer = class {
|
|
|
17206
17920
|
|
|
17207
17921
|
// src/core/router/model-performance-tracker.ts
|
|
17208
17922
|
init_pricing();
|
|
17209
|
-
var DEFAULT_STATS_FILE =
|
|
17923
|
+
var DEFAULT_STATS_FILE = path35__default.default.join(os10__default.default.homedir(), ".cascade", "model-perf.json");
|
|
17210
17924
|
var ModelPerformanceTracker = class {
|
|
17211
17925
|
stats = /* @__PURE__ */ new Map();
|
|
17212
17926
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -17246,7 +17960,7 @@ var ModelPerformanceTracker = class {
|
|
|
17246
17960
|
async save() {
|
|
17247
17961
|
if (this.readOnly) return;
|
|
17248
17962
|
try {
|
|
17249
|
-
await fs11__default.default.mkdir(
|
|
17963
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(this.statsFile), { recursive: true });
|
|
17250
17964
|
const modelsObj = {};
|
|
17251
17965
|
const featuresObj = {};
|
|
17252
17966
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
@@ -17794,7 +18508,7 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
17794
18508
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
17795
18509
|
async loadPersistedTools() {
|
|
17796
18510
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
17797
|
-
const file =
|
|
18511
|
+
const file = path35__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
17798
18512
|
try {
|
|
17799
18513
|
const raw = await fs11__default.default.readFile(file, "utf-8");
|
|
17800
18514
|
const specs = JSON.parse(raw);
|
|
@@ -17818,8 +18532,8 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
17818
18532
|
}
|
|
17819
18533
|
async persist() {
|
|
17820
18534
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
17821
|
-
const dir =
|
|
17822
|
-
const file =
|
|
18535
|
+
const dir = path35__default.default.join(this.workspacePath, ".cascade");
|
|
18536
|
+
const file = path35__default.default.join(dir, DYNAMIC_TOOLS_FILE);
|
|
17823
18537
|
try {
|
|
17824
18538
|
await fs11__default.default.mkdir(dir, { recursive: true });
|
|
17825
18539
|
await fs11__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
@@ -17839,12 +18553,12 @@ var WorldStateDB = class {
|
|
|
17839
18553
|
constructor(workspacePath, debugMode = false) {
|
|
17840
18554
|
this.workspacePath = workspacePath;
|
|
17841
18555
|
this.debugMode = debugMode;
|
|
17842
|
-
const cascadeDir =
|
|
17843
|
-
if (!
|
|
17844
|
-
|
|
18556
|
+
const cascadeDir = path35__default.default.join(workspacePath, ".cascade");
|
|
18557
|
+
if (!fs32__default.default.existsSync(cascadeDir)) {
|
|
18558
|
+
fs32__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
17845
18559
|
}
|
|
17846
|
-
this.keyPath =
|
|
17847
|
-
this.dbPath =
|
|
18560
|
+
this.keyPath = path35__default.default.join(cascadeDir, "world_state.key");
|
|
18561
|
+
this.dbPath = path35__default.default.join(cascadeDir, "world_state.db");
|
|
17848
18562
|
this.initEncryptionKey();
|
|
17849
18563
|
this.db = new Database2__default.default(this.dbPath);
|
|
17850
18564
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -17901,11 +18615,11 @@ var WorldStateDB = class {
|
|
|
17901
18615
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
17902
18616
|
}
|
|
17903
18617
|
initEncryptionKey() {
|
|
17904
|
-
if (
|
|
17905
|
-
this.encryptionKey =
|
|
18618
|
+
if (fs32__default.default.existsSync(this.keyPath)) {
|
|
18619
|
+
this.encryptionKey = fs32__default.default.readFileSync(this.keyPath);
|
|
17906
18620
|
} else {
|
|
17907
18621
|
this.encryptionKey = crypto__default.default.randomBytes(32);
|
|
17908
|
-
|
|
18622
|
+
fs32__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
17909
18623
|
}
|
|
17910
18624
|
}
|
|
17911
18625
|
encrypt(text) {
|
|
@@ -18191,9 +18905,9 @@ var WorldStateDB = class {
|
|
|
18191
18905
|
dumpDebugIfNeeded() {
|
|
18192
18906
|
if (!this.debugMode) return;
|
|
18193
18907
|
try {
|
|
18194
|
-
const dumpPath =
|
|
18908
|
+
const dumpPath = path35__default.default.join(os10__default.default.tmpdir(), "cascade_world_state_debug.json");
|
|
18195
18909
|
const entries = this.getAllEntries();
|
|
18196
|
-
|
|
18910
|
+
fs32__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
18197
18911
|
} catch (err) {
|
|
18198
18912
|
console.error("Failed to dump debug world state", err);
|
|
18199
18913
|
}
|
|
@@ -18249,11 +18963,11 @@ var ResumeStore = class {
|
|
|
18249
18963
|
version: RESUME_SCHEMA_VERSION,
|
|
18250
18964
|
createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
|
|
18251
18965
|
};
|
|
18252
|
-
await
|
|
18253
|
-
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`);
|
|
18254
18968
|
const tempPath = `${finalPath}.${process.pid}.tmp`;
|
|
18255
|
-
await
|
|
18256
|
-
await
|
|
18969
|
+
await fs32.promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
|
|
18970
|
+
await fs32.promises.rename(tempPath, finalPath);
|
|
18257
18971
|
await this.prune();
|
|
18258
18972
|
return true;
|
|
18259
18973
|
} catch {
|
|
@@ -18264,7 +18978,7 @@ var ResumeStore = class {
|
|
|
18264
18978
|
async list() {
|
|
18265
18979
|
let entries;
|
|
18266
18980
|
try {
|
|
18267
|
-
entries = await
|
|
18981
|
+
entries = await fs32.promises.readdir(this.dir);
|
|
18268
18982
|
} catch {
|
|
18269
18983
|
return [];
|
|
18270
18984
|
}
|
|
@@ -18273,7 +18987,7 @@ var ResumeStore = class {
|
|
|
18273
18987
|
const checkpoints = [];
|
|
18274
18988
|
for (const candidate of candidates2) {
|
|
18275
18989
|
try {
|
|
18276
|
-
const raw = await
|
|
18990
|
+
const raw = await fs32.promises.readFile(path35__default.default.join(this.dir, candidate.name), "utf-8");
|
|
18277
18991
|
const parsed = JSON.parse(raw);
|
|
18278
18992
|
if (isCheckpoint(parsed)) checkpoints.push(parsed);
|
|
18279
18993
|
} catch {
|
|
@@ -18307,12 +19021,12 @@ var ResumeStore = class {
|
|
|
18307
19021
|
const target = taskId ? all.find((c) => c.taskId === taskId) : all[0];
|
|
18308
19022
|
if (!target) return null;
|
|
18309
19023
|
try {
|
|
18310
|
-
const entries = await
|
|
19024
|
+
const entries = await fs32.promises.readdir(this.dir);
|
|
18311
19025
|
const wanted = safeId(target.taskId);
|
|
18312
19026
|
const source = entries.find((name) => FILE_PATTERN.exec(name)?.[2] === wanted);
|
|
18313
19027
|
if (!source) return null;
|
|
18314
19028
|
const claimId = `${this.now()}-${safeId(target.taskId)}`;
|
|
18315
|
-
await
|
|
19029
|
+
await fs32.promises.rename(path35__default.default.join(this.dir, source), path35__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`));
|
|
18316
19030
|
return { checkpoint: target, claimId };
|
|
18317
19031
|
} catch {
|
|
18318
19032
|
return null;
|
|
@@ -18321,9 +19035,9 @@ var ResumeStore = class {
|
|
|
18321
19035
|
/** Give a claim back, so the work stays recoverable after a failed start. */
|
|
18322
19036
|
async release(claimId) {
|
|
18323
19037
|
try {
|
|
18324
|
-
await
|
|
18325
|
-
|
|
18326
|
-
|
|
19038
|
+
await fs32.promises.rename(
|
|
19039
|
+
path35__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`),
|
|
19040
|
+
path35__default.default.join(this.dir, `${claimId}.json`)
|
|
18327
19041
|
);
|
|
18328
19042
|
} catch {
|
|
18329
19043
|
}
|
|
@@ -18331,7 +19045,7 @@ var ResumeStore = class {
|
|
|
18331
19045
|
/** Discard a claim for good — the resumed run has taken ownership. */
|
|
18332
19046
|
async settle(claimId) {
|
|
18333
19047
|
try {
|
|
18334
|
-
await
|
|
19048
|
+
await fs32.promises.rm(path35__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`), { force: true });
|
|
18335
19049
|
} catch {
|
|
18336
19050
|
}
|
|
18337
19051
|
}
|
|
@@ -18353,15 +19067,15 @@ var ResumeStore = class {
|
|
|
18353
19067
|
async reclaimStale() {
|
|
18354
19068
|
let reclaimed = 0;
|
|
18355
19069
|
try {
|
|
18356
|
-
const entries = await
|
|
19070
|
+
const entries = await fs32.promises.readdir(this.dir);
|
|
18357
19071
|
const cutoff = this.now() - this.claimTimeoutMs;
|
|
18358
19072
|
for (const name of entries) {
|
|
18359
19073
|
if (!name.endsWith(CLAIM_SUFFIX)) continue;
|
|
18360
19074
|
const stamp2 = Number(/^(\d+)-/.exec(name)?.[1]);
|
|
18361
19075
|
if (!Number.isFinite(stamp2) || stamp2 >= cutoff) continue;
|
|
18362
|
-
await
|
|
18363
|
-
|
|
18364
|
-
|
|
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`)
|
|
18365
19079
|
);
|
|
18366
19080
|
reclaimed++;
|
|
18367
19081
|
}
|
|
@@ -18372,12 +19086,12 @@ var ResumeStore = class {
|
|
|
18372
19086
|
/** Remove every checkpoint for a task id. */
|
|
18373
19087
|
async delete(taskId) {
|
|
18374
19088
|
try {
|
|
18375
|
-
const entries = await
|
|
19089
|
+
const entries = await fs32.promises.readdir(this.dir);
|
|
18376
19090
|
const wanted = safeId(taskId);
|
|
18377
19091
|
await Promise.all(entries.map(async (name) => {
|
|
18378
19092
|
const match = FILE_PATTERN.exec(name);
|
|
18379
19093
|
if (match?.[2] === wanted) {
|
|
18380
|
-
await
|
|
19094
|
+
await fs32.promises.rm(path35__default.default.join(this.dir, name), { force: true });
|
|
18381
19095
|
}
|
|
18382
19096
|
}));
|
|
18383
19097
|
} catch {
|
|
@@ -18386,13 +19100,13 @@ var ResumeStore = class {
|
|
|
18386
19100
|
/** Drop expired checkpoints, then any beyond the newest `maxCheckpoints`. */
|
|
18387
19101
|
async prune() {
|
|
18388
19102
|
try {
|
|
18389
|
-
const entries = await
|
|
19103
|
+
const entries = await fs32.promises.readdir(this.dir);
|
|
18390
19104
|
const cutoff = this.now() - this.maxAgeMs;
|
|
18391
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);
|
|
18392
19106
|
const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
|
|
18393
19107
|
const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
|
|
18394
19108
|
await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
|
|
18395
|
-
(name) =>
|
|
19109
|
+
(name) => fs32.promises.rm(path35__default.default.join(this.dir, name), { force: true })
|
|
18396
19110
|
));
|
|
18397
19111
|
} catch {
|
|
18398
19112
|
}
|
|
@@ -18769,7 +19483,7 @@ var WorkspaceIndex = class {
|
|
|
18769
19483
|
isIgnored;
|
|
18770
19484
|
chunker;
|
|
18771
19485
|
constructor(opts) {
|
|
18772
|
-
this.root =
|
|
19486
|
+
this.root = path35__default.default.resolve(opts.root);
|
|
18773
19487
|
this.db = opts.db;
|
|
18774
19488
|
this.namespace = opts.namespace ?? "code";
|
|
18775
19489
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -18801,14 +19515,14 @@ var WorkspaceIndex = class {
|
|
|
18801
19515
|
return;
|
|
18802
19516
|
}
|
|
18803
19517
|
for (const entry of entries) {
|
|
18804
|
-
const abs =
|
|
19518
|
+
const abs = path35__default.default.join(dir, entry.name);
|
|
18805
19519
|
if (entry.isDirectory()) {
|
|
18806
19520
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
18807
19521
|
await walk(abs);
|
|
18808
19522
|
} else if (entry.isFile()) {
|
|
18809
|
-
const ext =
|
|
19523
|
+
const ext = path35__default.default.extname(entry.name).slice(1).toLowerCase();
|
|
18810
19524
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
18811
|
-
out.push(
|
|
19525
|
+
out.push(path35__default.default.relative(this.root, abs));
|
|
18812
19526
|
}
|
|
18813
19527
|
}
|
|
18814
19528
|
};
|
|
@@ -18838,7 +19552,7 @@ var WorkspaceIndex = class {
|
|
|
18838
19552
|
const entries = [];
|
|
18839
19553
|
for (const rel of rels) {
|
|
18840
19554
|
try {
|
|
18841
|
-
const abs =
|
|
19555
|
+
const abs = path35__default.default.join(this.root, rel);
|
|
18842
19556
|
const stat = await fs11__default.default.stat(abs);
|
|
18843
19557
|
if (stat.size > this.maxFileBytes) continue;
|
|
18844
19558
|
const buf = await fs11__default.default.readFile(abs);
|
|
@@ -19328,13 +20042,13 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
19328
20042
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
19329
20043
|
try {
|
|
19330
20044
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
19331
|
-
fileDeadModelPersistence(
|
|
20045
|
+
fileDeadModelPersistence(path35__default.default.join(workspacePath, ".cascade", "dead-models.json"))
|
|
19332
20046
|
));
|
|
19333
20047
|
} catch {
|
|
19334
20048
|
}
|
|
19335
20049
|
try {
|
|
19336
20050
|
this.resumeStore = new ResumeStore({
|
|
19337
|
-
dir:
|
|
20051
|
+
dir: path35__default.default.join(workspacePath, ".cascade", "resume")
|
|
19338
20052
|
});
|
|
19339
20053
|
} catch {
|
|
19340
20054
|
}
|
|
@@ -19422,8 +20136,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
19422
20136
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
19423
20137
|
return;
|
|
19424
20138
|
}
|
|
19425
|
-
const dbPath = ci.dbPath ||
|
|
19426
|
-
|
|
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 });
|
|
19427
20141
|
const db = new Database2__default.default(dbPath);
|
|
19428
20142
|
db.pragma("journal_mode = WAL");
|
|
19429
20143
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -20871,14 +21585,14 @@ ${finalOutput}` : ""
|
|
|
20871
21585
|
init_constants();
|
|
20872
21586
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
20873
21587
|
function globalDir(homeDir = os10__default.default.homedir()) {
|
|
20874
|
-
return
|
|
21588
|
+
return path35__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
20875
21589
|
}
|
|
20876
21590
|
function cloudSessionPath(dir = globalDir()) {
|
|
20877
|
-
return
|
|
21591
|
+
return path35__default.default.join(dir, CLOUD_SESSION_FILE);
|
|
20878
21592
|
}
|
|
20879
21593
|
function loadCloudSession(dir = globalDir()) {
|
|
20880
21594
|
try {
|
|
20881
|
-
const raw =
|
|
21595
|
+
const raw = fs32__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
20882
21596
|
const parsed = JSON.parse(raw);
|
|
20883
21597
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
20884
21598
|
return parsed;
|
|
@@ -20887,17 +21601,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
20887
21601
|
}
|
|
20888
21602
|
}
|
|
20889
21603
|
function saveCloudSession(session, dir = globalDir()) {
|
|
20890
|
-
|
|
21604
|
+
fs32__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
20891
21605
|
const file = cloudSessionPath(dir);
|
|
20892
|
-
|
|
21606
|
+
fs32__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
20893
21607
|
try {
|
|
20894
|
-
|
|
21608
|
+
fs32__default.default.chmodSync(file, 384);
|
|
20895
21609
|
} catch {
|
|
20896
21610
|
}
|
|
20897
21611
|
}
|
|
20898
21612
|
function clearCloudSession(dir = globalDir()) {
|
|
20899
21613
|
try {
|
|
20900
|
-
|
|
21614
|
+
fs32__default.default.rmSync(cloudSessionPath(dir));
|
|
20901
21615
|
} catch {
|
|
20902
21616
|
}
|
|
20903
21617
|
}
|
|
@@ -21069,16 +21783,16 @@ var CloudClient = class _CloudClient {
|
|
|
21069
21783
|
return (await this.refresh()).accessToken;
|
|
21070
21784
|
}
|
|
21071
21785
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
21072
|
-
async authed(
|
|
21073
|
-
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}` } });
|
|
21074
21788
|
let res = await call(await this.accessToken());
|
|
21075
21789
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
21076
21790
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
21077
21791
|
const text = await res.text();
|
|
21078
21792
|
return text ? JSON.parse(text) : {};
|
|
21079
21793
|
}
|
|
21080
|
-
authedGet(
|
|
21081
|
-
return this.authed(
|
|
21794
|
+
authedGet(path40) {
|
|
21795
|
+
return this.authed(path40);
|
|
21082
21796
|
}
|
|
21083
21797
|
async fetchMe(token) {
|
|
21084
21798
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -21939,7 +22653,7 @@ function StatusBarInternal({
|
|
|
21939
22653
|
const savedStr = savedUsd > 0 ? ` \xB7 saved $${savedUsd.toFixed(4)}` : "";
|
|
21940
22654
|
const rightStr = ` ${formatTokens(tokens)} \xB7 $${costUsd.toFixed(4)}${savedStr} ${isExecuting ? "\u26A1" : "\xB7"} `;
|
|
21941
22655
|
const gap = Math.max(0, width - leftStr.length - rightStr.length);
|
|
21942
|
-
const line =
|
|
22656
|
+
const line = chalk8__default.default.bgHex(theme.colors.primary).hex(theme.colors.background)(chalk8__default.default.bold(leftStr) + " ".repeat(gap) + rightStr);
|
|
21943
22657
|
return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: line });
|
|
21944
22658
|
}
|
|
21945
22659
|
function formatTokens(n) {
|
|
@@ -22408,7 +23122,7 @@ function renderContent(content, theme) {
|
|
|
22408
23122
|
i
|
|
22409
23123
|
);
|
|
22410
23124
|
}
|
|
22411
|
-
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));
|
|
22412
23126
|
return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { wrap: "wrap", children: rendered }, i);
|
|
22413
23127
|
});
|
|
22414
23128
|
}
|
|
@@ -22852,9 +23566,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, s
|
|
|
22852
23566
|
}
|
|
22853
23567
|
} catch {
|
|
22854
23568
|
}
|
|
22855
|
-
const configPath =
|
|
23569
|
+
const configPath = path35__default.default.join(workspacePath, ".cascade", "config.json");
|
|
22856
23570
|
try {
|
|
22857
|
-
await fs11__default.default.mkdir(
|
|
23571
|
+
await fs11__default.default.mkdir(path35__default.default.dirname(configPath), { recursive: true });
|
|
22858
23572
|
await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
22859
23573
|
} catch (err) {
|
|
22860
23574
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -22906,9 +23620,9 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, s
|
|
|
22906
23620
|
if (msg.includes("non-text parts")) return;
|
|
22907
23621
|
originalLog(...args);
|
|
22908
23622
|
};
|
|
22909
|
-
const store = new MemoryStore(
|
|
23623
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
22910
23624
|
storeRef.current = store;
|
|
22911
|
-
globalStoreRef.current = new MemoryStore(
|
|
23625
|
+
globalStoreRef.current = new MemoryStore(path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
22912
23626
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
22913
23627
|
setIdentities(identityRows);
|
|
22914
23628
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -22988,7 +23702,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, s
|
|
|
22988
23702
|
onThemeChange: (name) => setTheme(getTheme(name)),
|
|
22989
23703
|
onExport: async (fmt) => {
|
|
22990
23704
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
22991
|
-
const exportPath =
|
|
23705
|
+
const exportPath = path35__default.default.join(workspacePath, `cascade-export-${stamp2}.${fmt === "json" ? "json" : "md"}`);
|
|
22992
23706
|
if (fmt === "json") {
|
|
22993
23707
|
await fs11__default.default.writeFile(exportPath, JSON.stringify({ sessionId: sessionIdRef.current, messages: state.messages }, null, 2), "utf-8");
|
|
22994
23708
|
} else {
|
|
@@ -23896,7 +24610,7 @@ function stringifySlashOutput(val) {
|
|
|
23896
24610
|
}
|
|
23897
24611
|
async function searchSessionsAndMessages(query, workspacePath) {
|
|
23898
24612
|
if (!query) return "Usage: /search <query>";
|
|
23899
|
-
const dbPath =
|
|
24613
|
+
const dbPath = path35__default.default.join(workspacePath, CASCADE_DB_FILE);
|
|
23900
24614
|
try {
|
|
23901
24615
|
await fs11__default.default.access(dbPath);
|
|
23902
24616
|
} catch {
|
|
@@ -23930,7 +24644,7 @@ async function searchSessionsAndMessages(query, workspacePath) {
|
|
|
23930
24644
|
async function diagnoseRuntime(config, workspacePath) {
|
|
23931
24645
|
const providers = config.providers.map((p) => `${p.type}${p.apiKey ? " (key set)" : " (no key)"}`).join("\n");
|
|
23932
24646
|
const models = [`T1: ${config.models.t1 ?? "default"}`, `T2: ${config.models.t2 ?? "default"}`, `T3: ${config.models.t3 ?? "default"}`].join("\n");
|
|
23933
|
-
const store = new MemoryStore(
|
|
24647
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
23934
24648
|
try {
|
|
23935
24649
|
const sessions2 = store.listSessions(void 0, 3);
|
|
23936
24650
|
return [
|
|
@@ -23949,7 +24663,7 @@ async function diagnoseRuntime(config, workspacePath) {
|
|
|
23949
24663
|
}
|
|
23950
24664
|
async function showRecentLogs(args, workspacePath) {
|
|
23951
24665
|
const limit = Number.parseInt(args[0] ?? "10", 10) || 10;
|
|
23952
|
-
const store = new MemoryStore(
|
|
24666
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
23953
24667
|
try {
|
|
23954
24668
|
const logs = store.listRuntimeNodeLogs(void 0, void 0, limit);
|
|
23955
24669
|
if (!logs.length) return "No recent runtime logs.";
|
|
@@ -23961,7 +24675,7 @@ async function showRecentLogs(args, workspacePath) {
|
|
|
23961
24675
|
async function loadSessionSnapshot(args, workspacePath) {
|
|
23962
24676
|
const sessionId = args[0];
|
|
23963
24677
|
if (!sessionId) return "Usage: /resume <sessionId>";
|
|
23964
|
-
const store = new MemoryStore(
|
|
24678
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
23965
24679
|
try {
|
|
23966
24680
|
const session = store.getSession(sessionId);
|
|
23967
24681
|
if (!session) return `Session not found: ${sessionId}`;
|
|
@@ -24381,9 +25095,9 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
24381
25095
|
...anyAuto ? { cascadeAuto: true } : {}
|
|
24382
25096
|
};
|
|
24383
25097
|
const config = CascadeConfigSchema.parse(rawConfig);
|
|
24384
|
-
const configDir =
|
|
25098
|
+
const configDir = path35__default.default.join(workspacePath, ".cascade");
|
|
24385
25099
|
await fs11__default.default.mkdir(configDir, { recursive: true });
|
|
24386
|
-
const configPath =
|
|
25100
|
+
const configPath = path35__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
24387
25101
|
await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
24388
25102
|
savedConfigRef.current = config;
|
|
24389
25103
|
dispatchRef.current({ type: "GO_DONE" });
|
|
@@ -24724,25 +25438,25 @@ function printTelemetryBanner() {
|
|
|
24724
25438
|
async function initCommand(workspacePath = process.cwd()) {
|
|
24725
25439
|
const spin = ora__default.default({ text: "Initializing Cascade project\u2026", color: "magenta" }).start();
|
|
24726
25440
|
try {
|
|
24727
|
-
const configDir =
|
|
25441
|
+
const configDir = path35__default.default.join(workspacePath, ".cascade");
|
|
24728
25442
|
await fs11__default.default.mkdir(configDir, { recursive: true });
|
|
24729
|
-
const mdPath =
|
|
25443
|
+
const mdPath = path35__default.default.join(workspacePath, "CASCADE.md");
|
|
24730
25444
|
if (!await fileExists(mdPath)) {
|
|
24731
25445
|
await createDefaultCascadeMd(workspacePath);
|
|
24732
|
-
spin.succeed(
|
|
25446
|
+
spin.succeed(chalk8__default.default.green("Created CASCADE.md"));
|
|
24733
25447
|
}
|
|
24734
|
-
const ignorePath =
|
|
25448
|
+
const ignorePath = path35__default.default.join(workspacePath, ".cascadeignore");
|
|
24735
25449
|
if (!await fileExists(ignorePath)) {
|
|
24736
25450
|
await createDefaultIgnoreFile(workspacePath);
|
|
24737
|
-
spin.succeed(
|
|
25451
|
+
spin.succeed(chalk8__default.default.green("Created .cascadeignore"));
|
|
24738
25452
|
}
|
|
24739
25453
|
spin.stop();
|
|
24740
25454
|
console.log();
|
|
24741
|
-
console.log(
|
|
25455
|
+
console.log(chalk8__default.default.magenta(" \u25C8 Cascade AI \u2014 Project initialized"));
|
|
24742
25456
|
console.log();
|
|
24743
|
-
const configPath =
|
|
25457
|
+
const configPath = path35__default.default.join(workspacePath, CASCADE_CONFIG_FILE);
|
|
24744
25458
|
if (await fileExists(configPath)) {
|
|
24745
|
-
console.log(
|
|
25459
|
+
console.log(chalk8__default.default.yellow(" .cascade/config.json already exists \u2014 launching wizard to reconfigure."));
|
|
24746
25460
|
console.log();
|
|
24747
25461
|
}
|
|
24748
25462
|
const config = await runSetupWizard(workspacePath);
|
|
@@ -24750,10 +25464,10 @@ async function initCommand(workspacePath = process.cwd()) {
|
|
|
24750
25464
|
await cm.load();
|
|
24751
25465
|
await cm.updateConfig(config);
|
|
24752
25466
|
console.log();
|
|
24753
|
-
console.log(
|
|
25467
|
+
console.log(chalk8__default.default.green(" \u25C8 Setup complete! Run `cascade` to start."));
|
|
24754
25468
|
console.log();
|
|
24755
25469
|
} catch (err) {
|
|
24756
|
-
console.error(
|
|
25470
|
+
console.error(chalk8__default.default.red(`Init failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
24757
25471
|
process.exit(1);
|
|
24758
25472
|
}
|
|
24759
25473
|
}
|
|
@@ -24768,7 +25482,20 @@ async function fileExists(p) {
|
|
|
24768
25482
|
|
|
24769
25483
|
// src/cli/commands/doctor.ts
|
|
24770
25484
|
init_constants();
|
|
25485
|
+
|
|
25486
|
+
// src/config/credential-discovery.ts
|
|
25487
|
+
init_revoked_credentials();
|
|
24771
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
|
+
];
|
|
24772
25499
|
async function readJson(file) {
|
|
24773
25500
|
try {
|
|
24774
25501
|
const raw = await fs11__default.default.readFile(file, "utf-8");
|
|
@@ -24792,36 +25519,118 @@ function fromEnv(env) {
|
|
|
24792
25519
|
for (const { env: name, provider } of map) {
|
|
24793
25520
|
const secret = str(env[name]);
|
|
24794
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
|
+
}
|
|
24795
25533
|
seen.add(provider);
|
|
24796
|
-
|
|
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
|
+
});
|
|
24797
25604
|
}
|
|
24798
25605
|
return out;
|
|
24799
25606
|
}
|
|
24800
|
-
async function fromClaudeCode(home) {
|
|
24801
|
-
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");
|
|
24802
25610
|
const data = await readJson(file);
|
|
24803
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
|
+
}
|
|
24804
25617
|
const oauth = data["claudeAiOauth"];
|
|
24805
25618
|
const oauthToken = str(oauth?.["accessToken"]);
|
|
24806
25619
|
if (oauthToken) {
|
|
24807
|
-
|
|
25620
|
+
found.push({
|
|
24808
25621
|
provider: "anthropic",
|
|
24809
25622
|
sourceTool: "Claude Code",
|
|
24810
25623
|
kind: "oauth",
|
|
24811
25624
|
secret: oauthToken,
|
|
24812
|
-
directlyUsable:
|
|
24813
|
-
warning:
|
|
25625
|
+
directlyUsable: false,
|
|
25626
|
+
warning: ANTHROPIC_OAUTH_WARNING,
|
|
24814
25627
|
sourcePath: file
|
|
24815
|
-
}
|
|
24816
|
-
}
|
|
24817
|
-
const apiKey = str(data["apiKey"]) ?? str(data["anthropicApiKey"]);
|
|
24818
|
-
if (apiKey) {
|
|
24819
|
-
return [{ provider: "anthropic", sourceTool: "Claude Code", kind: "api-key", secret: apiKey, directlyUsable: true, sourcePath: file }];
|
|
25628
|
+
});
|
|
24820
25629
|
}
|
|
24821
|
-
return
|
|
25630
|
+
return found;
|
|
24822
25631
|
}
|
|
24823
25632
|
async function fromCodex(home) {
|
|
24824
|
-
const file =
|
|
25633
|
+
const file = path35__default.default.join(home, ".codex", "auth.json");
|
|
24825
25634
|
const data = await readJson(file);
|
|
24826
25635
|
if (!data) return [];
|
|
24827
25636
|
const apiKey = str(data["OPENAI_API_KEY"]);
|
|
@@ -24844,7 +25653,7 @@ async function fromCodex(home) {
|
|
|
24844
25653
|
return [];
|
|
24845
25654
|
}
|
|
24846
25655
|
async function fromGemini(home) {
|
|
24847
|
-
const file =
|
|
25656
|
+
const file = path35__default.default.join(home, ".gemini", "oauth_creds.json");
|
|
24848
25657
|
const data = await readJson(file);
|
|
24849
25658
|
const accessToken = str(data?.["access_token"]);
|
|
24850
25659
|
if (!accessToken) return [];
|
|
@@ -24860,7 +25669,7 @@ async function fromGemini(home) {
|
|
|
24860
25669
|
}
|
|
24861
25670
|
async function fromCopilot(home) {
|
|
24862
25671
|
for (const name of ["apps.json", "hosts.json"]) {
|
|
24863
|
-
const file =
|
|
25672
|
+
const file = path35__default.default.join(home, ".config", "github-copilot", name);
|
|
24864
25673
|
const data = await readJson(file);
|
|
24865
25674
|
if (!data) continue;
|
|
24866
25675
|
for (const value of Object.values(data)) {
|
|
@@ -24885,7 +25694,7 @@ async function discoverCredentials(opts = {}) {
|
|
|
24885
25694
|
const env = opts.env ?? process.env;
|
|
24886
25695
|
const groups = await Promise.all([
|
|
24887
25696
|
Promise.resolve(fromEnv(env)),
|
|
24888
|
-
fromClaudeCode(home),
|
|
25697
|
+
fromClaudeCode(home, env),
|
|
24889
25698
|
fromCodex(home),
|
|
24890
25699
|
fromGemini(home),
|
|
24891
25700
|
fromCopilot(home)
|
|
@@ -24896,10 +25705,257 @@ function maskSecret(secret) {
|
|
|
24896
25705
|
if (secret.length <= 8) return "\u2022\u2022\u2022\u2022";
|
|
24897
25706
|
return `${secret.slice(0, 6)}\u2026${secret.slice(-2)}`;
|
|
24898
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
|
+
}
|
|
24899
25951
|
|
|
24900
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
|
+
}
|
|
24901
25957
|
async function doctorCommand() {
|
|
24902
|
-
console.log(
|
|
25958
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Cascade Doctor \u2014 System Diagnostics\n"));
|
|
24903
25959
|
const checks = [];
|
|
24904
25960
|
const nodeVersion = process.versions.node;
|
|
24905
25961
|
const [major] = nodeVersion.split(".").map(Number);
|
|
@@ -24914,7 +25970,7 @@ async function doctorCommand() {
|
|
|
24914
25970
|
checks.push({
|
|
24915
25971
|
label: "Cascade config",
|
|
24916
25972
|
ok: true,
|
|
24917
|
-
detail: `Loaded ${
|
|
25973
|
+
detail: `Loaded ${path35__default.default.join(process.cwd(), CASCADE_CONFIG_FILE)}`
|
|
24918
25974
|
});
|
|
24919
25975
|
const providers = [
|
|
24920
25976
|
{ type: "anthropic", name: "Anthropic" },
|
|
@@ -24924,10 +25980,11 @@ async function doctorCommand() {
|
|
|
24924
25980
|
];
|
|
24925
25981
|
for (const { type, name } of providers) {
|
|
24926
25982
|
const key = cm.getApiKey(type);
|
|
25983
|
+
const token = key ? void 0 : cm.getAuthToken(type);
|
|
24927
25984
|
checks.push({
|
|
24928
|
-
label: `${name}
|
|
24929
|
-
ok: Boolean(key),
|
|
24930
|
-
detail: key ? "
|
|
25985
|
+
label: `${name} credential`,
|
|
25986
|
+
ok: Boolean(key || token),
|
|
25987
|
+
detail: key ? "API key set" : token ? "Bearer token set" : "Missing"
|
|
24931
25988
|
});
|
|
24932
25989
|
}
|
|
24933
25990
|
const ollamaOk = await checkEndpoint(OLLAMA_BASE_URL + "/api/tags");
|
|
@@ -24981,32 +26038,31 @@ async function doctorCommand() {
|
|
|
24981
26038
|
try {
|
|
24982
26039
|
const discovered = await discoverCredentials();
|
|
24983
26040
|
if (discovered.length > 0) {
|
|
24984
|
-
const usable = discovered.filter((d) => d.directlyUsable).length;
|
|
24985
26041
|
checks.push({
|
|
24986
26042
|
label: "Linkable credentials",
|
|
24987
26043
|
ok: true,
|
|
24988
|
-
detail:
|
|
26044
|
+
detail: linkableCredentialsDetail(discovered, config.providers ?? [])
|
|
24989
26045
|
});
|
|
24990
26046
|
}
|
|
24991
26047
|
} catch {
|
|
24992
26048
|
}
|
|
24993
26049
|
for (const c of checks) {
|
|
24994
|
-
const icon = c.ok ?
|
|
24995
|
-
const label = c.ok ?
|
|
24996
|
-
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}`) : "";
|
|
24997
26053
|
console.log(`${icon} ${label}${detail}`);
|
|
24998
26054
|
}
|
|
24999
26055
|
const failures2 = checks.filter((c) => !c.ok);
|
|
25000
26056
|
console.log();
|
|
25001
26057
|
if (failures2.length === 0) {
|
|
25002
|
-
console.log(
|
|
26058
|
+
console.log(chalk8__default.default.green(" All checks passed!\n"));
|
|
25003
26059
|
} else {
|
|
25004
|
-
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"));
|
|
25005
26061
|
if (critical.length) {
|
|
25006
|
-
console.log(
|
|
26062
|
+
console.log(chalk8__default.default.yellow(` ${critical.length} issue(s) need attention.
|
|
25007
26063
|
`));
|
|
25008
26064
|
} else {
|
|
25009
|
-
console.log(
|
|
26065
|
+
console.log(chalk8__default.default.gray(` ${failures2.length} optional item(s) not configured.
|
|
25010
26066
|
`));
|
|
25011
26067
|
}
|
|
25012
26068
|
}
|
|
@@ -25024,19 +26080,19 @@ async function checkEndpoint(url) {
|
|
|
25024
26080
|
}
|
|
25025
26081
|
}
|
|
25026
26082
|
async function indexCommand(dirPath) {
|
|
25027
|
-
const workspace =
|
|
26083
|
+
const workspace = path35__default.default.resolve(dirPath || process.cwd());
|
|
25028
26084
|
const cm = new ConfigManager(workspace);
|
|
25029
26085
|
await cm.load();
|
|
25030
26086
|
const config = cm.getConfig();
|
|
25031
26087
|
const embedder = embedderFromProviders(config.providers);
|
|
25032
26088
|
if (!embedder) {
|
|
25033
|
-
console.log(
|
|
25034
|
-
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"));
|
|
25035
26091
|
process.exitCode = 1;
|
|
25036
26092
|
return;
|
|
25037
26093
|
}
|
|
25038
|
-
const dbPath = config.codeIndex?.dbPath ||
|
|
25039
|
-
|
|
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 });
|
|
25040
26096
|
const db = new Database2__default.default(dbPath);
|
|
25041
26097
|
db.pragma("journal_mode = WAL");
|
|
25042
26098
|
const complete = chatCompleterFromProviders(config.providers);
|
|
@@ -25050,20 +26106,20 @@ async function indexCommand(dirPath) {
|
|
|
25050
26106
|
reranker,
|
|
25051
26107
|
isIgnored: (abs) => ignore4.isIgnored(abs, workspace)
|
|
25052
26108
|
});
|
|
25053
|
-
console.log(
|
|
26109
|
+
console.log(chalk8__default.default.magenta(`
|
|
25054
26110
|
\u25C8 Indexing ${workspace}
|
|
25055
26111
|
`));
|
|
25056
26112
|
let seen = 0;
|
|
25057
26113
|
const res = await index.refresh(() => {
|
|
25058
26114
|
seen++;
|
|
25059
|
-
if (seen % 10 === 0) process.stdout.write(
|
|
26115
|
+
if (seen % 10 === 0) process.stdout.write(chalk8__default.default.dim(` \u2026embedded ${seen} changed files\r`));
|
|
25060
26116
|
});
|
|
25061
26117
|
console.log(
|
|
25062
|
-
|
|
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`)
|
|
25063
26119
|
);
|
|
25064
|
-
console.log(
|
|
26120
|
+
console.log(chalk8__default.default.dim(` Index: ${dbPath}`));
|
|
25065
26121
|
if (!config.codeIndex?.enabled) {
|
|
25066
|
-
console.log(
|
|
26122
|
+
console.log(chalk8__default.default.dim(" Tip: set codeIndex.enabled = true in your config to give runs the code_search tool."));
|
|
25067
26123
|
}
|
|
25068
26124
|
console.log("");
|
|
25069
26125
|
db.close();
|
|
@@ -25112,6 +26168,7 @@ async function decryptJSON(blob, passphrase) {
|
|
|
25112
26168
|
}
|
|
25113
26169
|
|
|
25114
26170
|
// src/cloud/keysync.ts
|
|
26171
|
+
init_revoked_credentials();
|
|
25115
26172
|
function providerSig(p) {
|
|
25116
26173
|
return [p.type, p.baseUrl ?? "", p.deploymentName ?? "", p.label ?? ""].join("\0");
|
|
25117
26174
|
}
|
|
@@ -25184,6 +26241,19 @@ function gatherSyncBundle(config) {
|
|
|
25184
26241
|
function applySyncBundle(bundle, config, cleanup) {
|
|
25185
26242
|
const dropped = stripRetiredProviders(bundle);
|
|
25186
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
|
+
}
|
|
25187
26257
|
const next = { ...config, tools: { ...config.tools } };
|
|
25188
26258
|
if (bundle.providers) next.providers = mergeByKey(config.providers, bundle.providers, providerSig) ?? [];
|
|
25189
26259
|
if (bundle.webSearch) next.tools.webSearch = { ...config.tools?.webSearch, ...bundle.webSearch };
|
|
@@ -25201,10 +26271,15 @@ function applySyncBundle(bundle, config, cleanup) {
|
|
|
25201
26271
|
if (bundle.budget) next.budget = { ...config.budget, ...bundle.budget };
|
|
25202
26272
|
const merged = { ...next.models };
|
|
25203
26273
|
const clearedFromMerge = clearRetiredPins(merged);
|
|
25204
|
-
|
|
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
|
+
}
|
|
25205
26278
|
if (cleanup) {
|
|
26279
|
+
cleanup.revokedCredentials = revokedFromBundle;
|
|
26280
|
+
cleanup.unusableCredentials = unusableFromBundle;
|
|
25206
26281
|
cleanup.clearedPins = ["t1", "t2", "t3"].filter(
|
|
25207
|
-
(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
|
|
25208
26283
|
);
|
|
25209
26284
|
}
|
|
25210
26285
|
if (bundle.prefs) {
|
|
@@ -25230,43 +26305,43 @@ function openBrowser(url) {
|
|
|
25230
26305
|
}
|
|
25231
26306
|
}
|
|
25232
26307
|
function notSignedIn() {
|
|
25233
|
-
console.log(
|
|
26308
|
+
console.log(chalk8__default.default.dim("\n Not signed in. Run `cascade login` first.\n"));
|
|
25234
26309
|
process.exitCode = 1;
|
|
25235
26310
|
}
|
|
25236
26311
|
function promptHidden(question) {
|
|
25237
26312
|
return new Promise((resolve2) => {
|
|
25238
26313
|
const rl = readline__default.default.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
25239
|
-
let
|
|
26314
|
+
let masked2 = false;
|
|
25240
26315
|
rl._writeToOutput = (s) => {
|
|
25241
|
-
if (!
|
|
26316
|
+
if (!masked2 || !process.stdin.isTTY) process.stdout.write(s);
|
|
25242
26317
|
};
|
|
25243
26318
|
rl.question(question, (answer) => {
|
|
25244
26319
|
rl.close();
|
|
25245
26320
|
process.stdout.write("\n");
|
|
25246
26321
|
resolve2(answer.trim());
|
|
25247
26322
|
});
|
|
25248
|
-
|
|
26323
|
+
masked2 = true;
|
|
25249
26324
|
});
|
|
25250
26325
|
}
|
|
25251
26326
|
async function loginCommand(opts = {}) {
|
|
25252
26327
|
const serverUrl = resolveServerUrl(opts.server);
|
|
25253
26328
|
const client = new CloudClient(serverUrl);
|
|
25254
|
-
console.log(
|
|
25255
|
-
console.log(
|
|
26329
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Sign in to Cascade Cloud"));
|
|
26330
|
+
console.log(chalk8__default.default.dim(` ${serverUrl}
|
|
25256
26331
|
`));
|
|
25257
26332
|
try {
|
|
25258
26333
|
const session = await client.runDeviceLogin((d) => {
|
|
25259
|
-
console.log(` 1. Open ${
|
|
25260
|
-
console.log(` 2. Enter ${
|
|
25261
|
-
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"));
|
|
25262
26337
|
openBrowser(`${d.verificationUri}?code=${encodeURIComponent(d.userCode)}`);
|
|
25263
26338
|
});
|
|
25264
26339
|
const who = session.user.name || session.user.email || session.user.id;
|
|
25265
|
-
console.log(
|
|
26340
|
+
console.log(chalk8__default.default.green(`
|
|
25266
26341
|
\u2713 Signed in as ${who}
|
|
25267
26342
|
`));
|
|
25268
26343
|
} catch (err) {
|
|
25269
|
-
console.log(
|
|
26344
|
+
console.log(chalk8__default.default.red(`
|
|
25270
26345
|
${err instanceof Error ? err.message : String(err)}
|
|
25271
26346
|
`));
|
|
25272
26347
|
process.exitCode = 1;
|
|
@@ -25275,11 +26350,11 @@ async function loginCommand(opts = {}) {
|
|
|
25275
26350
|
async function logoutCommand() {
|
|
25276
26351
|
const client = CloudClient.fromSession();
|
|
25277
26352
|
if (!client) {
|
|
25278
|
-
console.log(
|
|
26353
|
+
console.log(chalk8__default.default.dim("\n Not signed in.\n"));
|
|
25279
26354
|
return;
|
|
25280
26355
|
}
|
|
25281
26356
|
await client.logout();
|
|
25282
|
-
console.log(
|
|
26357
|
+
console.log(chalk8__default.default.green("\n \u2713 Signed out.\n"));
|
|
25283
26358
|
}
|
|
25284
26359
|
async function whoamiCommand() {
|
|
25285
26360
|
const client = CloudClient.fromSession();
|
|
@@ -25287,10 +26362,10 @@ async function whoamiCommand() {
|
|
|
25287
26362
|
try {
|
|
25288
26363
|
const me = await client.me();
|
|
25289
26364
|
console.log(`
|
|
25290
|
-
${
|
|
26365
|
+
${chalk8__default.default.bold(me.name || "\u2014")} ${chalk8__default.default.dim(me.email || "")}${me.plan ? chalk8__default.default.dim(` \xB7 ${me.plan}`) : ""}
|
|
25291
26366
|
`);
|
|
25292
26367
|
} catch (err) {
|
|
25293
|
-
console.log(
|
|
26368
|
+
console.log(chalk8__default.default.red(`
|
|
25294
26369
|
${err instanceof Error ? err.message : String(err)}
|
|
25295
26370
|
`));
|
|
25296
26371
|
process.exitCode = 1;
|
|
@@ -25302,16 +26377,16 @@ async function sessionsCommand() {
|
|
|
25302
26377
|
try {
|
|
25303
26378
|
const convos = await client.listConversations();
|
|
25304
26379
|
if (convos.length === 0) {
|
|
25305
|
-
console.log(
|
|
26380
|
+
console.log(chalk8__default.default.dim("\n No cloud chats yet.\n"));
|
|
25306
26381
|
return;
|
|
25307
26382
|
}
|
|
25308
|
-
console.log(
|
|
26383
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Your cloud chats\n"));
|
|
25309
26384
|
for (const c of convos) {
|
|
25310
|
-
console.log(` ${
|
|
26385
|
+
console.log(` ${chalk8__default.default.dim(c.id.slice(0, 8))} ${c.title?.trim() || chalk8__default.default.dim("(untitled)")}`);
|
|
25311
26386
|
}
|
|
25312
|
-
console.log(
|
|
26387
|
+
console.log(chalk8__default.default.dim("\n cascade sessions show <id> read a chat (id or its first characters)\n"));
|
|
25313
26388
|
} catch (err) {
|
|
25314
|
-
console.log(
|
|
26389
|
+
console.log(chalk8__default.default.red(`
|
|
25315
26390
|
${err instanceof Error ? err.message : String(err)}
|
|
25316
26391
|
`));
|
|
25317
26392
|
process.exitCode = 1;
|
|
@@ -25325,9 +26400,9 @@ function formatCloudTranscript(messages) {
|
|
|
25325
26400
|
for (const m of messages) {
|
|
25326
26401
|
const n = m.siblingIds?.length ?? 1;
|
|
25327
26402
|
const i = m.siblingIds && m.id ? m.siblingIds.indexOf(m.id) + 1 : 1;
|
|
25328
|
-
const branch = n > 1 ?
|
|
25329
|
-
const tag = m.id ?
|
|
25330
|
-
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");
|
|
25331
26406
|
lines.push(` ${who}${tag}${branch}`);
|
|
25332
26407
|
lines.push(" " + m.content.replace(/\n/g, "\n ") + "\n");
|
|
25333
26408
|
}
|
|
@@ -25343,23 +26418,23 @@ async function sessionShowCommand(idOrPrefix) {
|
|
|
25343
26418
|
try {
|
|
25344
26419
|
const match = await resolveConversation(client, idOrPrefix);
|
|
25345
26420
|
if (!match) {
|
|
25346
|
-
console.log(
|
|
26421
|
+
console.log(chalk8__default.default.red(`
|
|
25347
26422
|
No chat matching "${idOrPrefix}".
|
|
25348
26423
|
`));
|
|
25349
26424
|
process.exitCode = 1;
|
|
25350
26425
|
return;
|
|
25351
26426
|
}
|
|
25352
26427
|
const msgs = await client.getMessages(match.id);
|
|
25353
|
-
console.log(
|
|
26428
|
+
console.log(chalk8__default.default.magenta(`
|
|
25354
26429
|
\u25C8 ${match.title?.trim() || "(untitled)"}
|
|
25355
26430
|
`));
|
|
25356
26431
|
console.log(formatCloudTranscript(msgs));
|
|
25357
26432
|
if (msgs.some((m) => (m.siblingIds?.length ?? 1) > 1)) {
|
|
25358
|
-
console.log(
|
|
26433
|
+
console.log(chalk8__default.default.dim(" cascade sessions branch <chat> <message> switch to another version"));
|
|
25359
26434
|
}
|
|
25360
|
-
console.log(
|
|
26435
|
+
console.log(chalk8__default.default.dim(" cascade sessions rm <chat> <message> delete a message + its replies\n"));
|
|
25361
26436
|
} catch (err) {
|
|
25362
|
-
console.log(
|
|
26437
|
+
console.log(chalk8__default.default.red(`
|
|
25363
26438
|
${err instanceof Error ? err.message : String(err)}
|
|
25364
26439
|
`));
|
|
25365
26440
|
process.exitCode = 1;
|
|
@@ -25374,7 +26449,7 @@ async function sessionBranchCommand(chat, message) {
|
|
|
25374
26449
|
try {
|
|
25375
26450
|
const match = await resolveConversation(client, chat);
|
|
25376
26451
|
if (!match) {
|
|
25377
|
-
console.log(
|
|
26452
|
+
console.log(chalk8__default.default.red(`
|
|
25378
26453
|
No chat matching "${chat}".
|
|
25379
26454
|
`));
|
|
25380
26455
|
process.exitCode = 1;
|
|
@@ -25383,12 +26458,12 @@ async function sessionBranchCommand(chat, message) {
|
|
|
25383
26458
|
const active = await client.getMessages(match.id);
|
|
25384
26459
|
const targetId = resolveMessage(active, message) ?? message;
|
|
25385
26460
|
const next = await client.selectBranch(match.id, targetId);
|
|
25386
|
-
console.log(
|
|
26461
|
+
console.log(chalk8__default.default.magenta(`
|
|
25387
26462
|
\u25C8 Switched branch \u2014 ${match.title?.trim() || "(untitled)"}
|
|
25388
26463
|
`));
|
|
25389
26464
|
console.log(formatCloudTranscript(next));
|
|
25390
26465
|
} catch (err) {
|
|
25391
|
-
console.log(
|
|
26466
|
+
console.log(chalk8__default.default.red(`
|
|
25392
26467
|
${err instanceof Error ? err.message : String(err)}
|
|
25393
26468
|
`));
|
|
25394
26469
|
process.exitCode = 1;
|
|
@@ -25400,7 +26475,7 @@ async function sessionRemoveMessageCommand(chat, message) {
|
|
|
25400
26475
|
try {
|
|
25401
26476
|
const match = await resolveConversation(client, chat);
|
|
25402
26477
|
if (!match) {
|
|
25403
|
-
console.log(
|
|
26478
|
+
console.log(chalk8__default.default.red(`
|
|
25404
26479
|
No chat matching "${chat}".
|
|
25405
26480
|
`));
|
|
25406
26481
|
process.exitCode = 1;
|
|
@@ -25409,11 +26484,11 @@ async function sessionRemoveMessageCommand(chat, message) {
|
|
|
25409
26484
|
const active = await client.getMessages(match.id);
|
|
25410
26485
|
const targetId = resolveMessage(active, message) ?? message;
|
|
25411
26486
|
const next = await client.deleteMessage(match.id, targetId);
|
|
25412
|
-
console.log(
|
|
26487
|
+
console.log(chalk8__default.default.green("\n \u2713 Deleted that message and its replies.\n"));
|
|
25413
26488
|
if (next.length) console.log(formatCloudTranscript(next));
|
|
25414
|
-
else console.log(
|
|
26489
|
+
else console.log(chalk8__default.default.dim(" (the conversation is now empty)\n"));
|
|
25415
26490
|
} catch (err) {
|
|
25416
|
-
console.log(
|
|
26491
|
+
console.log(chalk8__default.default.red(`
|
|
25417
26492
|
${err instanceof Error ? err.message : String(err)}
|
|
25418
26493
|
`));
|
|
25419
26494
|
process.exitCode = 1;
|
|
@@ -25425,18 +26500,18 @@ async function sessionRenameCommand(chat, title) {
|
|
|
25425
26500
|
try {
|
|
25426
26501
|
const match = await resolveConversation(client, chat);
|
|
25427
26502
|
if (!match) {
|
|
25428
|
-
console.log(
|
|
26503
|
+
console.log(chalk8__default.default.red(`
|
|
25429
26504
|
No chat matching "${chat}".
|
|
25430
26505
|
`));
|
|
25431
26506
|
process.exitCode = 1;
|
|
25432
26507
|
return;
|
|
25433
26508
|
}
|
|
25434
26509
|
await client.renameConversation(match.id, title);
|
|
25435
|
-
console.log(
|
|
26510
|
+
console.log(chalk8__default.default.green(`
|
|
25436
26511
|
\u2713 Renamed to "${title}".
|
|
25437
26512
|
`));
|
|
25438
26513
|
} catch (err) {
|
|
25439
|
-
console.log(
|
|
26514
|
+
console.log(chalk8__default.default.red(`
|
|
25440
26515
|
${err instanceof Error ? err.message : String(err)}
|
|
25441
26516
|
`));
|
|
25442
26517
|
process.exitCode = 1;
|
|
@@ -25448,18 +26523,18 @@ async function sessionDeleteCommand(chat) {
|
|
|
25448
26523
|
try {
|
|
25449
26524
|
const match = await resolveConversation(client, chat);
|
|
25450
26525
|
if (!match) {
|
|
25451
|
-
console.log(
|
|
26526
|
+
console.log(chalk8__default.default.red(`
|
|
25452
26527
|
No chat matching "${chat}".
|
|
25453
26528
|
`));
|
|
25454
26529
|
process.exitCode = 1;
|
|
25455
26530
|
return;
|
|
25456
26531
|
}
|
|
25457
26532
|
await client.deleteConversation(match.id);
|
|
25458
|
-
console.log(
|
|
26533
|
+
console.log(chalk8__default.default.green(`
|
|
25459
26534
|
\u2713 Deleted "${match.title?.trim() || "(untitled)"}".
|
|
25460
26535
|
`));
|
|
25461
26536
|
} catch (err) {
|
|
25462
|
-
console.log(
|
|
26537
|
+
console.log(chalk8__default.default.red(`
|
|
25463
26538
|
${err instanceof Error ? err.message : String(err)}
|
|
25464
26539
|
`));
|
|
25465
26540
|
process.exitCode = 1;
|
|
@@ -25471,21 +26546,21 @@ async function syncPushCommand() {
|
|
|
25471
26546
|
const cm = new ConfigManager(process.cwd());
|
|
25472
26547
|
await cm.load();
|
|
25473
26548
|
const bundle = gatherSyncBundle(cm.getConfig());
|
|
25474
|
-
console.log(
|
|
25475
|
-
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"));
|
|
25476
26551
|
const pass = await promptHidden(" Passphrase: ");
|
|
25477
26552
|
if (!pass) {
|
|
25478
|
-
console.log(
|
|
26553
|
+
console.log(chalk8__default.default.dim("\n Cancelled.\n"));
|
|
25479
26554
|
return;
|
|
25480
26555
|
}
|
|
25481
26556
|
try {
|
|
25482
26557
|
const blob = await encryptJSON(bundle, pass);
|
|
25483
26558
|
const r = await client.pushSecrets(blob);
|
|
25484
|
-
console.log(
|
|
26559
|
+
console.log(chalk8__default.default.green(`
|
|
25485
26560
|
\u2713 Synced (v${r.version}). Run \`cascade sync pull\` on another device.
|
|
25486
26561
|
`));
|
|
25487
26562
|
} catch (err) {
|
|
25488
|
-
console.log(
|
|
26563
|
+
console.log(chalk8__default.default.red(`
|
|
25489
26564
|
${err instanceof Error ? err.message : String(err)}
|
|
25490
26565
|
`));
|
|
25491
26566
|
process.exitCode = 1;
|
|
@@ -25498,27 +26573,27 @@ async function syncPullCommand() {
|
|
|
25498
26573
|
try {
|
|
25499
26574
|
({ blob } = await client.pullSecrets());
|
|
25500
26575
|
} catch (err) {
|
|
25501
|
-
console.log(
|
|
26576
|
+
console.log(chalk8__default.default.red(`
|
|
25502
26577
|
${err instanceof Error ? err.message : String(err)}
|
|
25503
26578
|
`));
|
|
25504
26579
|
process.exitCode = 1;
|
|
25505
26580
|
return;
|
|
25506
26581
|
}
|
|
25507
26582
|
if (!blob) {
|
|
25508
|
-
console.log(
|
|
26583
|
+
console.log(chalk8__default.default.dim("\n Nothing synced to your account yet. Run `cascade sync push` first.\n"));
|
|
25509
26584
|
return;
|
|
25510
26585
|
}
|
|
25511
|
-
console.log(
|
|
26586
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Apply your synced settings to this device\n"));
|
|
25512
26587
|
const pass = await promptHidden(" Passphrase: ");
|
|
25513
26588
|
if (!pass) {
|
|
25514
|
-
console.log(
|
|
26589
|
+
console.log(chalk8__default.default.dim("\n Cancelled.\n"));
|
|
25515
26590
|
return;
|
|
25516
26591
|
}
|
|
25517
26592
|
let bundle;
|
|
25518
26593
|
try {
|
|
25519
26594
|
bundle = await decryptJSON(blob, pass);
|
|
25520
26595
|
} catch {
|
|
25521
|
-
console.log(
|
|
26596
|
+
console.log(chalk8__default.default.red("\n Could not decrypt \u2014 check your passphrase and try again.\n"));
|
|
25522
26597
|
process.exitCode = 1;
|
|
25523
26598
|
return;
|
|
25524
26599
|
}
|
|
@@ -25527,13 +26602,13 @@ async function syncPullCommand() {
|
|
|
25527
26602
|
await cm.load();
|
|
25528
26603
|
const cleanup = { removed: [], clearedPins: [] };
|
|
25529
26604
|
await cm.updateConfig(applySyncBundle(bundle, cm.getConfig(), cleanup));
|
|
25530
|
-
console.log(
|
|
26605
|
+
console.log(chalk8__default.default.green("\n \u2713 Applied your synced settings. Your keys are ready here.\n"));
|
|
25531
26606
|
if (didCleanupChangeAnything(cleanup)) {
|
|
25532
|
-
console.log(
|
|
26607
|
+
console.log(chalk8__default.default.yellow(` ${describeCleanup(cleanup)}
|
|
25533
26608
|
`));
|
|
25534
26609
|
}
|
|
25535
26610
|
} catch (err) {
|
|
25536
|
-
console.log(
|
|
26611
|
+
console.log(chalk8__default.default.red(`
|
|
25537
26612
|
Could not apply your synced settings: ${err instanceof Error ? err.message : String(err)}
|
|
25538
26613
|
`));
|
|
25539
26614
|
process.exitCode = 1;
|
|
@@ -25549,7 +26624,7 @@ function openBrowser2(url) {
|
|
|
25549
26624
|
}
|
|
25550
26625
|
function storePathFor(name) {
|
|
25551
26626
|
const safe = name.replace(/[^a-z0-9._-]/gi, "_").slice(0, 64) || "server";
|
|
25552
|
-
return
|
|
26627
|
+
return path35__default.default.join(os10__default.default.homedir(), ".cascade-ai", "mcp-oauth", `${safe}.json`);
|
|
25553
26628
|
}
|
|
25554
26629
|
function defaultName(url) {
|
|
25555
26630
|
try {
|
|
@@ -25560,8 +26635,8 @@ function defaultName(url) {
|
|
|
25560
26635
|
}
|
|
25561
26636
|
async function mcpConnectCommand(url, opts = {}) {
|
|
25562
26637
|
const desiredName = opts.name?.trim() || defaultName(url);
|
|
25563
|
-
console.log(
|
|
25564
|
-
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}
|
|
25565
26640
|
`));
|
|
25566
26641
|
try {
|
|
25567
26642
|
const cm = new ConfigManager(process.cwd());
|
|
@@ -25577,7 +26652,7 @@ async function mcpConnectCommand(url, opts = {}) {
|
|
|
25577
26652
|
store,
|
|
25578
26653
|
clientName: "Cascade AI",
|
|
25579
26654
|
openUrl: (u) => {
|
|
25580
|
-
console.log(
|
|
26655
|
+
console.log(chalk8__default.default.dim(" Opening your browser to authorize\u2026"));
|
|
25581
26656
|
openBrowser2(u);
|
|
25582
26657
|
}
|
|
25583
26658
|
});
|
|
@@ -25587,11 +26662,11 @@ async function mcpConnectCommand(url, opts = {}) {
|
|
|
25587
26662
|
config.tools.mcpServers = servers;
|
|
25588
26663
|
config.tools.mcpTrusted = Array.from(/* @__PURE__ */ new Set([...config.tools.mcpTrusted ?? [], name]));
|
|
25589
26664
|
await cm.updateConfig(config);
|
|
25590
|
-
console.log(
|
|
26665
|
+
console.log(chalk8__default.default.green(`
|
|
25591
26666
|
\u2713 Connected "${name}". Its tools are available to your runs.
|
|
25592
26667
|
`));
|
|
25593
26668
|
} catch (err) {
|
|
25594
|
-
console.log(
|
|
26669
|
+
console.log(chalk8__default.default.red(`
|
|
25595
26670
|
${err instanceof Error ? err.message : String(err)}
|
|
25596
26671
|
`));
|
|
25597
26672
|
process.exitCode = 1;
|
|
@@ -25602,13 +26677,13 @@ async function mcpListCommand() {
|
|
|
25602
26677
|
await cm.load();
|
|
25603
26678
|
const servers = cm.getConfig().tools?.mcpServers ?? [];
|
|
25604
26679
|
if (!servers.length) {
|
|
25605
|
-
console.log(
|
|
26680
|
+
console.log(chalk8__default.default.dim("\n No MCP servers configured.\n"));
|
|
25606
26681
|
return;
|
|
25607
26682
|
}
|
|
25608
|
-
console.log(
|
|
26683
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 MCP servers\n"));
|
|
25609
26684
|
for (const s of servers) {
|
|
25610
|
-
const kind = s.oauthStore ?
|
|
25611
|
-
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}`);
|
|
25612
26687
|
}
|
|
25613
26688
|
console.log();
|
|
25614
26689
|
}
|
|
@@ -25619,7 +26694,7 @@ async function mcpRemoveCommand(name) {
|
|
|
25619
26694
|
const servers = config.tools?.mcpServers ?? [];
|
|
25620
26695
|
const match = servers.find((s) => s.name === name);
|
|
25621
26696
|
if (!match) {
|
|
25622
|
-
console.log(
|
|
26697
|
+
console.log(chalk8__default.default.red(`
|
|
25623
26698
|
No MCP server named "${name}".
|
|
25624
26699
|
`));
|
|
25625
26700
|
process.exitCode = 1;
|
|
@@ -25635,7 +26710,7 @@ async function mcpRemoveCommand(name) {
|
|
|
25635
26710
|
}
|
|
25636
26711
|
}
|
|
25637
26712
|
await cm.updateConfig(config);
|
|
25638
|
-
console.log(
|
|
26713
|
+
console.log(chalk8__default.default.green(`
|
|
25639
26714
|
\u2713 Removed "${name}".
|
|
25640
26715
|
`));
|
|
25641
26716
|
}
|
|
@@ -25649,16 +26724,302 @@ async function updateCommand() {
|
|
|
25649
26724
|
const { stdout } = await execAsync2("npm show cascade-ai version", { timeout: 1e4 });
|
|
25650
26725
|
const latest = stdout.trim().split("\n").filter(Boolean).pop() ?? "";
|
|
25651
26726
|
if (latest === CASCADE_VERSION) {
|
|
25652
|
-
spin.succeed(
|
|
26727
|
+
spin.succeed(chalk8__default.default.green(`Already up to date (v${CASCADE_VERSION})`));
|
|
25653
26728
|
return;
|
|
25654
26729
|
}
|
|
25655
26730
|
spin.text = `Updating cascade-ai ${CASCADE_VERSION} \u2192 ${latest}\u2026`;
|
|
25656
26731
|
await execAsync2("npm install -g cascade-ai@latest", { timeout: 6e4 });
|
|
25657
|
-
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);
|
|
26993
|
+
} catch (err) {
|
|
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}` };
|
|
27003
|
+
}
|
|
27004
|
+
replaceInPlace(live, committed);
|
|
27005
|
+
return { ok: true, refused };
|
|
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 };
|
|
25658
27017
|
} catch (err) {
|
|
25659
|
-
|
|
27018
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
25660
27019
|
}
|
|
25661
27020
|
}
|
|
27021
|
+
|
|
27022
|
+
// src/dashboard/server.ts
|
|
25662
27023
|
init_constants();
|
|
25663
27024
|
var JWT_ALGORITHM = "HS256";
|
|
25664
27025
|
function createToken(user, secret) {
|
|
@@ -25906,12 +27267,22 @@ var DashboardSocket = class {
|
|
|
25906
27267
|
});
|
|
25907
27268
|
});
|
|
25908
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
|
+
*/
|
|
25909
27279
|
onConfigUpdate(callback) {
|
|
25910
27280
|
this.io.on("connection", (socket) => {
|
|
25911
|
-
socket.on("config:update", (payload) => {
|
|
25912
|
-
if (typeof payload
|
|
25913
|
-
|
|
25914
|
-
|
|
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
|
+
});
|
|
25915
27286
|
});
|
|
25916
27287
|
});
|
|
25917
27288
|
}
|
|
@@ -26057,8 +27428,47 @@ function aggregateCostStats(sessions2, opts = {}) {
|
|
|
26057
27428
|
}
|
|
26058
27429
|
|
|
26059
27430
|
// src/dashboard/server.ts
|
|
26060
|
-
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))));
|
|
26061
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
|
+
}
|
|
26062
27472
|
var DashboardServer = class {
|
|
26063
27473
|
app;
|
|
26064
27474
|
httpServer;
|
|
@@ -26133,40 +27543,21 @@ var DashboardServer = class {
|
|
|
26133
27543
|
this.activeSessions.get(sessionId)?.rateLastRun(rating);
|
|
26134
27544
|
});
|
|
26135
27545
|
this.socket.onConfigGet((socketId) => {
|
|
26136
|
-
this.socket.emitToSocket(socketId, "config:current",
|
|
26137
|
-
models: this.config.models ?? {},
|
|
26138
|
-
budget: {
|
|
26139
|
-
maxCostPerRun: this.config.budget?.maxCostPerRunUsd,
|
|
26140
|
-
autoBias: this.config.autoBias
|
|
26141
|
-
},
|
|
26142
|
-
providersWithKey: (this.config.providers ?? []).filter((p) => typeof p.apiKey === "string" && p.apiKey.length > 0).map((p) => p.type)
|
|
26143
|
-
});
|
|
27546
|
+
this.socket.emitToSocket(socketId, "config:current", settingsSnapshot(this.config));
|
|
26144
27547
|
});
|
|
26145
|
-
this.socket.onConfigUpdate((data) => {
|
|
26146
|
-
|
|
26147
|
-
|
|
26148
|
-
|
|
26149
|
-
|
|
26150
|
-
|
|
26151
|
-
|
|
26152
|
-
|
|
26153
|
-
|
|
26154
|
-
|
|
26155
|
-
const models = this.config.models;
|
|
26156
|
-
for (const [tier, val] of Object.entries(data.models)) {
|
|
26157
|
-
if (val && val !== "auto") models[tier] = val;
|
|
26158
|
-
else delete models[tier];
|
|
26159
|
-
}
|
|
26160
|
-
}
|
|
26161
|
-
if (data.budget) {
|
|
26162
|
-
if (typeof data.budget.maxCostPerRun === "number") {
|
|
26163
|
-
this.config.budget.maxCostPerRunUsd = data.budget.maxCostPerRun;
|
|
26164
|
-
}
|
|
26165
|
-
if (data.budget.autoBias === "balanced" || data.budget.autoBias === "quality" || data.budget.autoBias === "cost") {
|
|
26166
|
-
this.config.autoBias = data.budget.autoBias;
|
|
26167
|
-
}
|
|
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 };
|
|
26168
27558
|
}
|
|
26169
|
-
this.
|
|
27559
|
+
this.syncGlobalCredentials();
|
|
27560
|
+
return { refused, snapshot: settingsSnapshot(this.config) };
|
|
26170
27561
|
});
|
|
26171
27562
|
this.socket.onCascadeRun(async (prompt, model, socketId, requestedSessionId, forceTier) => {
|
|
26172
27563
|
const sessionId = requestedSessionId ?? crypto.randomUUID();
|
|
@@ -26335,16 +27726,47 @@ var DashboardServer = class {
|
|
|
26335
27726
|
* made over the socket (Settings → Save) persist across restarts. Best-effort:
|
|
26336
27727
|
* a write failure is logged but never crashes the running dashboard.
|
|
26337
27728
|
*/
|
|
26338
|
-
|
|
26339
|
-
|
|
26340
|
-
|
|
26341
|
-
|
|
26342
|
-
|
|
26343
|
-
|
|
26344
|
-
|
|
26345
|
-
|
|
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() {
|
|
26346
27768
|
try {
|
|
26347
|
-
saveGlobalCredentials(
|
|
27769
|
+
saveGlobalCredentials(path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
26348
27770
|
} catch (err) {
|
|
26349
27771
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
26350
27772
|
}
|
|
@@ -26359,15 +27781,15 @@ var DashboardServer = class {
|
|
|
26359
27781
|
resolveDashboardSecret() {
|
|
26360
27782
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
26361
27783
|
if (fromConfig) return fromConfig;
|
|
26362
|
-
const secretPath =
|
|
27784
|
+
const secretPath = path35__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
26363
27785
|
try {
|
|
26364
|
-
if (
|
|
26365
|
-
const existing =
|
|
27786
|
+
if (fs32__default.default.existsSync(secretPath)) {
|
|
27787
|
+
const existing = fs32__default.default.readFileSync(secretPath, "utf-8").trim();
|
|
26366
27788
|
if (existing.length >= 16) return existing;
|
|
26367
27789
|
}
|
|
26368
27790
|
const generated = crypto.randomUUID();
|
|
26369
|
-
|
|
26370
|
-
|
|
27791
|
+
fs32__default.default.mkdirSync(path35__default.default.dirname(secretPath), { recursive: true });
|
|
27792
|
+
fs32__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
26371
27793
|
if (this.config.dashboard.auth) {
|
|
26372
27794
|
console.warn(
|
|
26373
27795
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -26394,7 +27816,7 @@ var DashboardServer = class {
|
|
|
26394
27816
|
// ── Setup ─────────────────────────────────────
|
|
26395
27817
|
getGlobalStore() {
|
|
26396
27818
|
if (!this.globalStore) {
|
|
26397
|
-
const globalDbPath =
|
|
27819
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
26398
27820
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
26399
27821
|
}
|
|
26400
27822
|
return this.globalStore;
|
|
@@ -26774,12 +28196,12 @@ ${prompt}`;
|
|
|
26774
28196
|
}
|
|
26775
28197
|
}
|
|
26776
28198
|
watchRuntimeChanges() {
|
|
26777
|
-
const workspaceDbPath =
|
|
26778
|
-
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);
|
|
26779
28201
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
26780
28202
|
for (const watchPath of watchPaths) {
|
|
26781
|
-
if (!
|
|
26782
|
-
|
|
28203
|
+
if (!fs32__default.default.existsSync(watchPath)) continue;
|
|
28204
|
+
fs32__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
26783
28205
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
26784
28206
|
});
|
|
26785
28207
|
}
|
|
@@ -26901,7 +28323,7 @@ ${prompt}`;
|
|
|
26901
28323
|
const sessionId = req.params.id;
|
|
26902
28324
|
this.store.deleteSession(sessionId);
|
|
26903
28325
|
this.store.deleteRuntimeSession(sessionId);
|
|
26904
|
-
const globalDbPath =
|
|
28326
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
26905
28327
|
const globalStore = new MemoryStore(globalDbPath);
|
|
26906
28328
|
try {
|
|
26907
28329
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -26994,7 +28416,7 @@ ${prompt}`;
|
|
|
26994
28416
|
});
|
|
26995
28417
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
26996
28418
|
const body = req.body;
|
|
26997
|
-
const globalDbPath =
|
|
28419
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
26998
28420
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
26999
28421
|
const globalStore = new MemoryStore(globalDbPath);
|
|
27000
28422
|
try {
|
|
@@ -27017,7 +28439,7 @@ ${prompt}`;
|
|
|
27017
28439
|
});
|
|
27018
28440
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
27019
28441
|
this.store.deleteAllRuntimeNodes();
|
|
27020
|
-
const globalDbPath =
|
|
28442
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
27021
28443
|
const globalStore = new MemoryStore(globalDbPath);
|
|
27022
28444
|
try {
|
|
27023
28445
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -27086,8 +28508,7 @@ ${prompt}`;
|
|
|
27086
28508
|
res.json(log);
|
|
27087
28509
|
});
|
|
27088
28510
|
this.app.get("/api/config", auth2, (_req, res) => {
|
|
27089
|
-
const safe =
|
|
27090
|
-
safe.providers = safe.providers.map((p) => ({ ...p, apiKey: p.apiKey ? "***" : void 0 }));
|
|
28511
|
+
const safe = redactProviderSecrets(this.config);
|
|
27091
28512
|
res.json(safe);
|
|
27092
28513
|
});
|
|
27093
28514
|
this.app.put("/api/config", auth2, async (req, res) => {
|
|
@@ -27103,12 +28524,12 @@ ${prompt}`;
|
|
|
27103
28524
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
27104
28525
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
27105
28526
|
try {
|
|
27106
|
-
const configPath =
|
|
27107
|
-
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")) : {};
|
|
27108
28529
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
27109
28530
|
const tmp = configPath + ".tmp";
|
|
27110
|
-
|
|
27111
|
-
|
|
28531
|
+
fs32__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
28532
|
+
fs32__default.default.renameSync(tmp, configPath);
|
|
27112
28533
|
res.json({ ok: true });
|
|
27113
28534
|
} catch (err) {
|
|
27114
28535
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -27136,7 +28557,7 @@ ${prompt}`;
|
|
|
27136
28557
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
27137
28558
|
const scope = req.query["scope"] ?? "workspace";
|
|
27138
28559
|
if (scope === "global") {
|
|
27139
|
-
const globalDbPath =
|
|
28560
|
+
const globalDbPath = path35__default.default.join(os10__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
27140
28561
|
const globalStore = new MemoryStore(globalDbPath);
|
|
27141
28562
|
try {
|
|
27142
28563
|
res.json({
|
|
@@ -27477,13 +28898,13 @@ ${prompt}`;
|
|
|
27477
28898
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
27478
28899
|
}
|
|
27479
28900
|
});
|
|
27480
|
-
const prodPath =
|
|
27481
|
-
const devPath =
|
|
27482
|
-
const webDistPath =
|
|
27483
|
-
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)) {
|
|
27484
28905
|
this.app.use(express__default.default.static(webDistPath));
|
|
27485
28906
|
this.app.get("*", (_req, res) => {
|
|
27486
|
-
res.sendFile(
|
|
28907
|
+
res.sendFile(path35__default.default.join(webDistPath, "index.html"));
|
|
27487
28908
|
});
|
|
27488
28909
|
} else {
|
|
27489
28910
|
this.app.get("/", (_req, res) => {
|
|
@@ -27504,7 +28925,7 @@ init_constants();
|
|
|
27504
28925
|
async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
27505
28926
|
const port = config.dashboard.port ?? DEFAULT_DASHBOARD_PORT;
|
|
27506
28927
|
const spin = ora__default.default({ text: `Starting dashboard on port ${port}\u2026`, color: "magenta" }).start();
|
|
27507
|
-
const store = new MemoryStore(
|
|
28928
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
27508
28929
|
const server = new DashboardServer(config, store, workspacePath);
|
|
27509
28930
|
server.watchRuntimeChanges();
|
|
27510
28931
|
const gThis = globalThis;
|
|
@@ -27516,10 +28937,10 @@ async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
|
27516
28937
|
process.once("exit", onExit);
|
|
27517
28938
|
try {
|
|
27518
28939
|
await server.start();
|
|
27519
|
-
spin.succeed(
|
|
28940
|
+
spin.succeed(chalk8__default.default.green(`Dashboard running at http://localhost:${port}`));
|
|
27520
28941
|
server.refreshRuntime("workspace");
|
|
27521
28942
|
server.refreshRuntime("global");
|
|
27522
|
-
console.log(
|
|
28943
|
+
console.log(chalk8__default.default.gray(` Press Ctrl+C to stop
|
|
27523
28944
|
`));
|
|
27524
28945
|
await new Promise(() => {
|
|
27525
28946
|
});
|
|
@@ -27528,7 +28949,7 @@ async function dashboardCommand(config, workspacePath = process.cwd()) {
|
|
|
27528
28949
|
await server.stop().catch(() => {
|
|
27529
28950
|
});
|
|
27530
28951
|
onExit();
|
|
27531
|
-
spin.fail(
|
|
28952
|
+
spin.fail(chalk8__default.default.red(`Dashboard failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
27532
28953
|
process.exit(1);
|
|
27533
28954
|
}
|
|
27534
28955
|
}
|
|
@@ -27536,18 +28957,18 @@ init_constants();
|
|
|
27536
28957
|
function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
27537
28958
|
const identity = new commander.Command("identity").alias("id").description("Manage Cascade identities");
|
|
27538
28959
|
identity.command("list").description("List all available identities").action(() => {
|
|
27539
|
-
const store = new MemoryStore(
|
|
28960
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
27540
28961
|
try {
|
|
27541
28962
|
const identities = store.listIdentities();
|
|
27542
|
-
console.log(
|
|
28963
|
+
console.log(chalk8__default.default.bold("\n Identities:"));
|
|
27543
28964
|
if (identities.length === 0) {
|
|
27544
|
-
console.log(
|
|
28965
|
+
console.log(chalk8__default.default.gray(" No identities found."));
|
|
27545
28966
|
} else {
|
|
27546
28967
|
identities.forEach((id) => {
|
|
27547
|
-
const defaultLabel = id.isDefault ?
|
|
27548
|
-
console.log(` - ${
|
|
27549
|
-
console.log(
|
|
27550
|
-
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}`));
|
|
27551
28972
|
});
|
|
27552
28973
|
}
|
|
27553
28974
|
console.log();
|
|
@@ -27556,7 +28977,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
27556
28977
|
}
|
|
27557
28978
|
});
|
|
27558
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) => {
|
|
27559
|
-
const store = new MemoryStore(
|
|
28980
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
27560
28981
|
try {
|
|
27561
28982
|
if (options.default) {
|
|
27562
28983
|
const existingDefault = store.getDefaultIdentity();
|
|
@@ -27573,21 +28994,21 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
27573
28994
|
isDefault: !!options.default,
|
|
27574
28995
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
27575
28996
|
});
|
|
27576
|
-
console.log(
|
|
28997
|
+
console.log(chalk8__default.default.green(`
|
|
27577
28998
|
Successfully created identity: ${name} (${id})`));
|
|
27578
|
-
if (options.default) console.log(
|
|
28999
|
+
if (options.default) console.log(chalk8__default.default.green(" Set as default."));
|
|
27579
29000
|
console.log();
|
|
27580
29001
|
} finally {
|
|
27581
29002
|
store.close();
|
|
27582
29003
|
}
|
|
27583
29004
|
});
|
|
27584
29005
|
identity.command("set-default <name>").description("Set an identity as default by name or ID").action((query) => {
|
|
27585
|
-
const store = new MemoryStore(
|
|
29006
|
+
const store = new MemoryStore(path35__default.default.join(workspacePath, CASCADE_DB_FILE));
|
|
27586
29007
|
try {
|
|
27587
29008
|
const identities = store.listIdentities();
|
|
27588
29009
|
const match = identities.find((i) => i.id === query || i.name.toLowerCase() === query.toLowerCase());
|
|
27589
29010
|
if (!match) {
|
|
27590
|
-
console.error(
|
|
29011
|
+
console.error(chalk8__default.default.red(`
|
|
27591
29012
|
Identity '${query}' not found.
|
|
27592
29013
|
`));
|
|
27593
29014
|
process.exit(1);
|
|
@@ -27597,7 +29018,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
27597
29018
|
store.updateIdentity(existingDefault.id, { isDefault: false });
|
|
27598
29019
|
}
|
|
27599
29020
|
store.updateIdentity(match.id, { isDefault: true });
|
|
27600
|
-
console.log(
|
|
29021
|
+
console.log(chalk8__default.default.green(`
|
|
27601
29022
|
Identity ${match.name} is now the default.
|
|
27602
29023
|
`));
|
|
27603
29024
|
} finally {
|
|
@@ -27608,7 +29029,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
27608
29029
|
}
|
|
27609
29030
|
init_pricing();
|
|
27610
29031
|
async function modelsCommand(options = {}) {
|
|
27611
|
-
console.log(
|
|
29032
|
+
console.log(chalk8__default.default.magenta("\n \u25C8 Cascade Models\n"));
|
|
27612
29033
|
const cm = new ConfigManager(process.cwd());
|
|
27613
29034
|
await cm.load();
|
|
27614
29035
|
const config = cm.getConfig();
|
|
@@ -27616,16 +29037,16 @@ async function modelsCommand(options = {}) {
|
|
|
27616
29037
|
try {
|
|
27617
29038
|
await router.init(config);
|
|
27618
29039
|
} catch (err) {
|
|
27619
|
-
console.error(
|
|
29040
|
+
console.error(chalk8__default.default.red(` Failed to initialize router: ${err instanceof Error ? err.message : String(err)}`));
|
|
27620
29041
|
process.exit(1);
|
|
27621
29042
|
}
|
|
27622
29043
|
await withTimeout(router.refreshLiveData(), 6e3, "live data timeout").catch(() => {
|
|
27623
29044
|
});
|
|
27624
29045
|
const liveData = router.getLiveData();
|
|
27625
29046
|
const tiers = [
|
|
27626
|
-
{ tier: "T1", label: "T1 Administrator", color:
|
|
27627
|
-
{ tier: "T2", label: "T2 Manager", color:
|
|
27628
|
-
{ 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") }
|
|
27629
29050
|
];
|
|
27630
29051
|
let anyMissing = false;
|
|
27631
29052
|
let anyUnpriced = false;
|
|
@@ -27637,76 +29058,76 @@ async function modelsCommand(options = {}) {
|
|
|
27637
29058
|
const costIn = unpriced ? "cost not tracked" : model.inputCostPer1kTokens === 0 ? "free" : `$${model.inputCostPer1kTokens.toFixed(4)}/1K in`;
|
|
27638
29059
|
const costOut = unpriced ? "" : model.outputCostPer1kTokens === 0 ? "free" : `, $${model.outputCostPer1kTokens.toFixed(4)}/1K out`;
|
|
27639
29060
|
const ctx = model.contextWindow >= 1e6 ? `${(model.contextWindow / 1e6).toFixed(1)}M ctx` : `${(model.contextWindow / 1e3).toFixed(0)}K ctx`;
|
|
27640
|
-
const local = model.isLocal ?
|
|
27641
|
-
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}") : "";
|
|
27642
29063
|
const bench = Math.round(benchmarkScore01(model, "mixed") * 100);
|
|
27643
29064
|
console.log(
|
|
27644
|
-
` ${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
|
|
27645
29066
|
);
|
|
27646
29067
|
} else {
|
|
27647
|
-
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})`)}`);
|
|
27648
29069
|
anyMissing = true;
|
|
27649
29070
|
}
|
|
27650
29071
|
}
|
|
27651
29072
|
console.log();
|
|
27652
29073
|
const providers = config.providers.map((p) => p.type).join(", ") || "(none)";
|
|
27653
|
-
console.log(
|
|
29074
|
+
console.log(chalk8__default.default.gray(` Configured providers: ${providers}`));
|
|
27654
29075
|
if (liveData) {
|
|
27655
29076
|
const src = liveData.getDataSource();
|
|
27656
29077
|
const gen = liveData.getGeneratedAt();
|
|
27657
29078
|
const srcLabel = src === "live" ? "live (just fetched)" : src === "cache" ? "cached" : "bundled";
|
|
27658
29079
|
const pricingMeta = pricingDatasetMeta();
|
|
27659
|
-
console.log(
|
|
29080
|
+
console.log(chalk8__default.default.gray(
|
|
27660
29081
|
` Benchmark data: ${srcLabel}` + (gen ? ` \xB7 updated ${gen.slice(0, 10)}` : "") + ` \xB7 pricing: ${liveData.hasLivePricing() ? "live (OpenRouter)" : "dataset"} (baseline ${pricingMeta.generatedAt})`
|
|
27661
29082
|
));
|
|
27662
29083
|
const drift = liveData.getPriceDisagreements();
|
|
27663
29084
|
if (drift.length) {
|
|
27664
|
-
console.log(
|
|
29085
|
+
console.log(chalk8__default.default.yellow(`
|
|
27665
29086
|
${drift.length} model${drift.length === 1 ? "" : "s"} priced differently by the live source than by the bundled dataset:`));
|
|
27666
29087
|
for (const d of drift.slice(0, 10)) {
|
|
27667
|
-
console.log(
|
|
29088
|
+
console.log(chalk8__default.default.gray(
|
|
27668
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)`
|
|
27669
29090
|
));
|
|
27670
29091
|
}
|
|
27671
|
-
console.log(
|
|
29092
|
+
console.log(chalk8__default.default.gray(" Live pricing is used. Refresh src/core/router/pricing-data.json to clear this."));
|
|
27672
29093
|
}
|
|
27673
29094
|
}
|
|
27674
29095
|
if (anyUnpriced) {
|
|
27675
|
-
console.log(
|
|
29096
|
+
console.log(chalk8__default.default.yellow(
|
|
27676
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.'
|
|
27677
29098
|
));
|
|
27678
29099
|
}
|
|
27679
29100
|
if (options.verbose) {
|
|
27680
29101
|
console.log();
|
|
27681
|
-
console.log(
|
|
29102
|
+
console.log(chalk8__default.default.white(" Available models by provider:\n"));
|
|
27682
29103
|
const allProviderTypes = [...new Set(config.providers.map((p) => p.type))];
|
|
27683
29104
|
for (const providerType of allProviderTypes) {
|
|
27684
29105
|
const available = router.getModelsForProvider(providerType);
|
|
27685
29106
|
if (available.length === 0) continue;
|
|
27686
|
-
console.log(
|
|
29107
|
+
console.log(chalk8__default.default.gray(` ${providerType}:`));
|
|
27687
29108
|
for (const m of available) {
|
|
27688
29109
|
const override = config.models.t1 === m.id ? " \u2190 T1" : config.models.t2 === m.id ? " \u2190 T2" : config.models.t3 === m.id ? " \u2190 T3" : "";
|
|
27689
|
-
console.log(` ${
|
|
29110
|
+
console.log(` ${chalk8__default.default.white(col(m.name, 28))}${chalk8__default.default.gray(m.id)}${chalk8__default.default.yellow(override)}`);
|
|
27690
29111
|
}
|
|
27691
29112
|
console.log();
|
|
27692
29113
|
}
|
|
27693
29114
|
}
|
|
27694
29115
|
if (anyMissing) {
|
|
27695
|
-
console.log(
|
|
29116
|
+
console.log(chalk8__default.default.yellow(" Some tiers have no available model. Run `cascade doctor` for details.\n"));
|
|
27696
29117
|
} else {
|
|
27697
|
-
console.log(
|
|
29118
|
+
console.log(chalk8__default.default.green(" All tiers are configured.\n"));
|
|
27698
29119
|
}
|
|
27699
29120
|
}
|
|
27700
29121
|
async function setModelCommand(action, tierArg, value) {
|
|
27701
29122
|
const tier = (tierArg ?? "").toLowerCase();
|
|
27702
29123
|
if (!["t1", "t2", "t3"].includes(tier)) {
|
|
27703
|
-
console.error(
|
|
27704
|
-
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"));
|
|
27705
29126
|
process.exit(1);
|
|
27706
29127
|
}
|
|
27707
29128
|
const override = action === "unset" ? "auto" : (value ?? "").trim();
|
|
27708
29129
|
if (action === "set" && !override) {
|
|
27709
|
-
console.error(
|
|
29130
|
+
console.error(chalk8__default.default.red(' Missing value. e.g. cascade models set t3 openai:gpt-4o-mini (or "auto")'));
|
|
27710
29131
|
process.exit(1);
|
|
27711
29132
|
}
|
|
27712
29133
|
const cm = new ConfigManager(process.cwd());
|
|
@@ -27716,8 +29137,8 @@ async function setModelCommand(action, tierArg, value) {
|
|
|
27716
29137
|
if (override === "auto") delete models[tier];
|
|
27717
29138
|
else models[tier] = override;
|
|
27718
29139
|
await cm.updateConfig({ models });
|
|
27719
|
-
const label = override === "auto" ?
|
|
27720
|
-
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(`
|
|
27721
29142
|
\u2713 ${tier.toUpperCase()} model set to ${label}
|
|
27722
29143
|
`));
|
|
27723
29144
|
}
|
|
@@ -27731,8 +29152,8 @@ async function exportCommand(options = {}) {
|
|
|
27731
29152
|
let store;
|
|
27732
29153
|
try {
|
|
27733
29154
|
const workspacePath = options.workspacePath ?? process.cwd();
|
|
27734
|
-
const workspaceDbPath =
|
|
27735
|
-
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);
|
|
27736
29157
|
let dbPath = globalDbPath;
|
|
27737
29158
|
try {
|
|
27738
29159
|
await fs11__default.default.access(workspaceDbPath);
|
|
@@ -27741,7 +29162,7 @@ async function exportCommand(options = {}) {
|
|
|
27741
29162
|
}
|
|
27742
29163
|
store = new MemoryStore(dbPath);
|
|
27743
29164
|
} catch (err) {
|
|
27744
|
-
spin.fail(
|
|
29165
|
+
spin.fail(chalk8__default.default.red(`Cannot open memory store: ${err instanceof Error ? err.message : String(err)}`));
|
|
27745
29166
|
process.exit(1);
|
|
27746
29167
|
}
|
|
27747
29168
|
try {
|
|
@@ -27749,7 +29170,7 @@ async function exportCommand(options = {}) {
|
|
|
27749
29170
|
if (options.sessionId) {
|
|
27750
29171
|
const session2 = store.getSession(options.sessionId);
|
|
27751
29172
|
if (!session2) {
|
|
27752
|
-
spin.fail(
|
|
29173
|
+
spin.fail(chalk8__default.default.red(`Session "${options.sessionId}" not found.`));
|
|
27753
29174
|
process.exit(1);
|
|
27754
29175
|
}
|
|
27755
29176
|
sessions2 = [session2];
|
|
@@ -27757,14 +29178,14 @@ async function exportCommand(options = {}) {
|
|
|
27757
29178
|
const limit = options.last ?? 10;
|
|
27758
29179
|
sessions2 = store.listSessions(void 0, limit);
|
|
27759
29180
|
if (sessions2.length === 0) {
|
|
27760
|
-
spin.warn(
|
|
29181
|
+
spin.warn(chalk8__default.default.yellow("No sessions found."));
|
|
27761
29182
|
return;
|
|
27762
29183
|
}
|
|
27763
29184
|
const latest = sessions2[0];
|
|
27764
29185
|
const full = store.getSession(latest.id);
|
|
27765
29186
|
sessions2 = full ? [full] : [];
|
|
27766
29187
|
if (sessions2.length === 0) {
|
|
27767
|
-
spin.fail(
|
|
29188
|
+
spin.fail(chalk8__default.default.red("Could not load latest session."));
|
|
27768
29189
|
process.exit(1);
|
|
27769
29190
|
}
|
|
27770
29191
|
}
|
|
@@ -27774,17 +29195,17 @@ async function exportCommand(options = {}) {
|
|
|
27774
29195
|
const ext = format === "json" ? ".json" : ".md";
|
|
27775
29196
|
const safeName = session.title.replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").slice(0, 60);
|
|
27776
29197
|
const defaultFile = `cascade-export-${safeName}${ext}`;
|
|
27777
|
-
const outPath = options.output ?
|
|
29198
|
+
const outPath = options.output ? path35__default.default.resolve(options.output) : path35__default.default.join(process.cwd(), defaultFile);
|
|
27778
29199
|
await fs11__default.default.writeFile(outPath, content, "utf-8");
|
|
27779
|
-
spin.succeed(
|
|
29200
|
+
spin.succeed(chalk8__default.default.green(`Exported to ${chalk8__default.default.white(outPath)}`));
|
|
27780
29201
|
const messageCount = Array.isArray(session.messages) ? session.messages.length : 0;
|
|
27781
29202
|
console.log();
|
|
27782
|
-
console.log(
|
|
27783
|
-
console.log(
|
|
27784
|
-
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}`));
|
|
27785
29206
|
console.log();
|
|
27786
29207
|
} catch (err) {
|
|
27787
|
-
spin.fail(
|
|
29208
|
+
spin.fail(chalk8__default.default.red(`Export failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
27788
29209
|
process.exit(1);
|
|
27789
29210
|
}
|
|
27790
29211
|
}
|
|
@@ -27838,93 +29259,6 @@ function buildJsonExport(session) {
|
|
|
27838
29259
|
};
|
|
27839
29260
|
return JSON.stringify(payload, null, 2);
|
|
27840
29261
|
}
|
|
27841
|
-
async function linkCommand(target, options = {}) {
|
|
27842
|
-
const found = await discoverCredentials();
|
|
27843
|
-
if (found.length === 0) {
|
|
27844
|
-
console.log(chalk7__default.default.yellow("\n No reusable credentials found.\n"));
|
|
27845
|
-
console.log(chalk7__default.default.gray(" Cascade looks for Claude Code, Codex, Gemini CLI, and GitHub Copilot logins,"));
|
|
27846
|
-
console.log(chalk7__default.default.gray(" plus ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY in your environment.\n"));
|
|
27847
|
-
return;
|
|
27848
|
-
}
|
|
27849
|
-
if (!target) {
|
|
27850
|
-
printDiscovered(found);
|
|
27851
|
-
return;
|
|
27852
|
-
}
|
|
27853
|
-
const provider = normalizeProvider(target);
|
|
27854
|
-
if (!provider) {
|
|
27855
|
-
console.log(chalk7__default.default.red(`
|
|
27856
|
-
Unknown provider "${target}". Use one of: anthropic, openai, gemini.
|
|
27857
|
-
`));
|
|
27858
|
-
return;
|
|
27859
|
-
}
|
|
27860
|
-
const candidates2 = found.filter((c) => c.provider === provider);
|
|
27861
|
-
const chosen = candidates2.find((c) => c.directlyUsable) ?? candidates2[0];
|
|
27862
|
-
if (!chosen) {
|
|
27863
|
-
console.log(chalk7__default.default.yellow(`
|
|
27864
|
-
No detected credential maps to "${provider}".
|
|
27865
|
-
`));
|
|
27866
|
-
return;
|
|
27867
|
-
}
|
|
27868
|
-
if (!chosen.directlyUsable) {
|
|
27869
|
-
console.log(chalk7__default.default.yellow(`
|
|
27870
|
-
Found a ${chosen.sourceTool} credential, but it can't be used against the standard ${provider} API.`));
|
|
27871
|
-
if (chosen.warning) console.log(chalk7__default.default.gray(` ${chosen.warning}`));
|
|
27872
|
-
console.log(chalk7__default.default.gray(" Cascade won't adopt it because it would create a non-working provider.\n"));
|
|
27873
|
-
return;
|
|
27874
|
-
}
|
|
27875
|
-
if (chosen.kind === "oauth" && !options.acceptRisk) {
|
|
27876
|
-
console.log(chalk7__default.default.yellow(`
|
|
27877
|
-
${chosen.sourceTool} provides a subscription OAuth token, not an API key.`));
|
|
27878
|
-
if (chosen.warning) console.log(chalk7__default.default.gray(` ${chosen.warning}`));
|
|
27879
|
-
console.log(chalk7__default.default.gray(" Re-run with --accept-risk to adopt it anyway:\n"));
|
|
27880
|
-
console.log(chalk7__default.default.cyan(` cascade link ${provider} --accept-risk
|
|
27881
|
-
`));
|
|
27882
|
-
return;
|
|
27883
|
-
}
|
|
27884
|
-
await adoptCredential(chosen, options.workspace ?? process.cwd());
|
|
27885
|
-
console.log(chalk7__default.default.green(`
|
|
27886
|
-
\u2713 Linked ${provider} using your ${chosen.sourceTool} credential (${maskSecret(chosen.secret)}).`));
|
|
27887
|
-
if (chosen.kind === "oauth") {
|
|
27888
|
-
console.log(chalk7__default.default.gray(" Adopted as an OAuth bearer token \u2014 revoke it in the source tool to disable."));
|
|
27889
|
-
}
|
|
27890
|
-
console.log(chalk7__default.default.gray(" Run `cascade doctor` to verify, or `cascade` to start.\n"));
|
|
27891
|
-
}
|
|
27892
|
-
function printDiscovered(found) {
|
|
27893
|
-
console.log(chalk7__default.default.magenta("\n \u25C8 Detected credentials\n"));
|
|
27894
|
-
for (const c of found) {
|
|
27895
|
-
const usable = c.directlyUsable ? chalk7__default.default.green("usable") : chalk7__default.default.yellow("needs vendor backend");
|
|
27896
|
-
const kind = c.kind === "oauth" ? chalk7__default.default.yellow("oauth") : chalk7__default.default.gray("api-key");
|
|
27897
|
-
console.log(` ${chalk7__default.default.white(c.provider.padEnd(18))} ${chalk7__default.default.gray(maskSecret(c.secret).padEnd(12))} ${kind} ${usable}`);
|
|
27898
|
-
console.log(chalk7__default.default.gray(` from ${c.sourceTool}`));
|
|
27899
|
-
if (c.warning) console.log(chalk7__default.default.yellow(` \u26A0 ${c.warning}`));
|
|
27900
|
-
}
|
|
27901
|
-
console.log(chalk7__default.default.gray("\n Adopt one with: ") + chalk7__default.default.cyan("cascade link <provider> [--accept-risk]"));
|
|
27902
|
-
console.log(chalk7__default.default.gray(" --accept-risk is required for subscription OAuth tokens.\n"));
|
|
27903
|
-
}
|
|
27904
|
-
function normalizeProvider(target) {
|
|
27905
|
-
const t = target.toLowerCase();
|
|
27906
|
-
if (t === "anthropic" || t === "claude" || t === "claude-code") return "anthropic";
|
|
27907
|
-
if (t === "openai" || t === "codex" || t === "gpt") return "openai";
|
|
27908
|
-
if (t === "gemini" || t === "google") return "gemini";
|
|
27909
|
-
return null;
|
|
27910
|
-
}
|
|
27911
|
-
async function adoptCredential(cred, workspace) {
|
|
27912
|
-
const cm = new ConfigManager(workspace);
|
|
27913
|
-
await cm.load();
|
|
27914
|
-
const config = cm.getConfig();
|
|
27915
|
-
const next = {
|
|
27916
|
-
type: cred.provider,
|
|
27917
|
-
credentialSource: cred.sourceTool
|
|
27918
|
-
};
|
|
27919
|
-
if (cred.kind === "oauth" && cred.provider === "anthropic") {
|
|
27920
|
-
next.authToken = cred.secret;
|
|
27921
|
-
} else {
|
|
27922
|
-
next.apiKey = cred.secret;
|
|
27923
|
-
}
|
|
27924
|
-
const providers = config.providers.filter((p) => p.type !== cred.provider);
|
|
27925
|
-
providers.push(next);
|
|
27926
|
-
await cm.updateConfig({ providers });
|
|
27927
|
-
}
|
|
27928
29262
|
async function telemetryCommand(action) {
|
|
27929
29263
|
const cm = new ConfigManager(process.cwd());
|
|
27930
29264
|
await cm.load();
|
|
@@ -27932,12 +29266,12 @@ async function telemetryCommand(action) {
|
|
|
27932
29266
|
if (action === "status") {
|
|
27933
29267
|
const state = config.telemetry?.enabled ? "ON" : "OFF";
|
|
27934
29268
|
console.log();
|
|
27935
|
-
console.log(
|
|
29269
|
+
console.log(chalk8__default.default.magenta(" \u25C8 Cascade Telemetry"));
|
|
27936
29270
|
console.log();
|
|
27937
|
-
console.log(` Status: ${config.telemetry?.enabled ?
|
|
27938
|
-
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)"));
|
|
27939
29273
|
console.log();
|
|
27940
|
-
console.log(
|
|
29274
|
+
console.log(chalk8__default.default.gray(" Toggle with: cascade telemetry on | cascade telemetry off"));
|
|
27941
29275
|
console.log();
|
|
27942
29276
|
return;
|
|
27943
29277
|
}
|
|
@@ -27951,11 +29285,11 @@ async function telemetryCommand(action) {
|
|
|
27951
29285
|
});
|
|
27952
29286
|
console.log();
|
|
27953
29287
|
if (enabled) {
|
|
27954
|
-
console.log(
|
|
27955
|
-
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."));
|
|
27956
29290
|
} else {
|
|
27957
|
-
console.log(
|
|
27958
|
-
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."));
|
|
27959
29293
|
}
|
|
27960
29294
|
console.log();
|
|
27961
29295
|
}
|
|
@@ -27965,11 +29299,11 @@ async function statsCommand() {
|
|
|
27965
29299
|
await tracker.load();
|
|
27966
29300
|
const all = tracker.getAll();
|
|
27967
29301
|
if (all.size === 0) {
|
|
27968
|
-
console.log(
|
|
29302
|
+
console.log(chalk8__default.default.dim("\n No routing history yet \u2014 run some tasks first.\n"));
|
|
27969
29303
|
return;
|
|
27970
29304
|
}
|
|
27971
|
-
console.log(
|
|
27972
|
-
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"));
|
|
27973
29307
|
for (const taskType of TASK_TYPES) {
|
|
27974
29308
|
const entries = [];
|
|
27975
29309
|
for (const [key, stat] of all) {
|
|
@@ -27981,36 +29315,36 @@ async function statsCommand() {
|
|
|
27981
29315
|
}
|
|
27982
29316
|
if (entries.length === 0) continue;
|
|
27983
29317
|
entries.sort((a, b) => b.successRate - a.successRate || b.samples - a.samples);
|
|
27984
|
-
console.log(
|
|
29318
|
+
console.log(chalk8__default.default.bold(` ${taskType.toUpperCase()}`));
|
|
27985
29319
|
const header = ` ${"Model".padEnd(36)} ${"Success".padEnd(9)} ${"Samples".padEnd(9)} Avg cost`;
|
|
27986
|
-
console.log(
|
|
27987
|
-
console.log(
|
|
29320
|
+
console.log(chalk8__default.default.dim(header));
|
|
29321
|
+
console.log(chalk8__default.default.dim(" " + "\u2500".repeat(62)));
|
|
27988
29322
|
for (const e of entries) {
|
|
27989
29323
|
const pct = `${Math.round(e.successRate * 100)}%`;
|
|
27990
29324
|
const cost = e.avgCostUsd < 1e-4 ? "<$0.0001" : `$${e.avgCostUsd.toFixed(4)}`;
|
|
27991
|
-
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;
|
|
27992
29326
|
console.log(
|
|
27993
|
-
` ${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)}`
|
|
27994
29328
|
);
|
|
27995
29329
|
}
|
|
27996
29330
|
console.log();
|
|
27997
29331
|
}
|
|
27998
|
-
console.log(
|
|
29332
|
+
console.log(chalk8__default.default.dim(" tip: use /rate good | bad after a task to improve these scores.\n"));
|
|
27999
29333
|
}
|
|
28000
29334
|
|
|
28001
29335
|
// src/cli/index.ts
|
|
28002
29336
|
dotenv__default.default.config();
|
|
28003
29337
|
function warnIfBuildIsStale() {
|
|
28004
29338
|
try {
|
|
28005
|
-
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))));
|
|
28006
29340
|
for (const rel of ["..", "../.."]) {
|
|
28007
|
-
const pkgPath =
|
|
28008
|
-
if (!
|
|
28009
|
-
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"));
|
|
28010
29344
|
if (pkg.name !== "cascade-ai") continue;
|
|
28011
29345
|
if (pkg.version && pkg.version !== CASCADE_VERSION) {
|
|
28012
29346
|
console.error(
|
|
28013
|
-
|
|
29347
|
+
chalk8__default.default.yellow(
|
|
28014
29348
|
`\u26A0 Stale build: compiled output is v${CASCADE_VERSION} but the source tree is v${pkg.version}.
|
|
28015
29349
|
Run: npm install && npm run build`
|
|
28016
29350
|
)
|
|
@@ -28109,7 +29443,7 @@ mcp.command("list").description("List configured MCP servers").action(async () =
|
|
|
28109
29443
|
mcp.command("remove <name>").description("Remove an MCP server (and its stored OAuth tokens)").action(async (name) => {
|
|
28110
29444
|
await mcpRemoveCommand(name);
|
|
28111
29445
|
});
|
|
28112
|
-
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) => {
|
|
28113
29447
|
await linkCommand(provider, { acceptRisk: opts.acceptRisk, workspace: process.cwd() });
|
|
28114
29448
|
});
|
|
28115
29449
|
program.command("update").description("Update Cascade to the latest version").action(async () => {
|
|
@@ -28135,7 +29469,7 @@ program.command("models [action] [tier] [value]").description("List AI models pe
|
|
|
28135
29469
|
program.command("telemetry [action]").description("Toggle anonymous usage telemetry (on | off | status). Default: status").action(async (action) => {
|
|
28136
29470
|
const normalized = (action ?? "status").toLowerCase();
|
|
28137
29471
|
if (normalized !== "on" && normalized !== "off" && normalized !== "status") {
|
|
28138
|
-
console.error(
|
|
29472
|
+
console.error(chalk8__default.default.red(`Unknown action: ${action}. Use: on | off | status`));
|
|
28139
29473
|
process.exit(1);
|
|
28140
29474
|
}
|
|
28141
29475
|
await telemetryCommand(normalized);
|
|
@@ -28157,15 +29491,15 @@ async function startRepl(options) {
|
|
|
28157
29491
|
try {
|
|
28158
29492
|
await cm.load();
|
|
28159
29493
|
} catch (err) {
|
|
28160
|
-
console.error(
|
|
28161
|
-
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."));
|
|
28162
29496
|
process.exit(1);
|
|
28163
29497
|
}
|
|
28164
29498
|
let config = cm.getConfig();
|
|
28165
29499
|
const startupNotice = cm.takeRetiredNotice();
|
|
28166
29500
|
const needsSetup = !hasUsableProvider(config.providers);
|
|
28167
29501
|
if (needsSetup) {
|
|
28168
|
-
console.log(
|
|
29502
|
+
console.log(chalk8__default.default.magenta(" \u25C8 No providers configured \u2014 launching setup wizard\u2026"));
|
|
28169
29503
|
console.log();
|
|
28170
29504
|
config = await runSetupWizard(workspacePath);
|
|
28171
29505
|
await cm.updateConfig(config);
|
|
@@ -28200,24 +29534,24 @@ async function runHeadless(prompt, options) {
|
|
|
28200
29534
|
try {
|
|
28201
29535
|
await cm.load();
|
|
28202
29536
|
} catch (err) {
|
|
28203
|
-
console.error(
|
|
28204
|
-
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."));
|
|
28205
29539
|
process.exit(1);
|
|
28206
29540
|
}
|
|
28207
29541
|
const config = cm.getConfig();
|
|
28208
29542
|
const needsSetup = !hasUsableProvider(config.providers);
|
|
28209
29543
|
if (needsSetup) {
|
|
28210
|
-
console.error(
|
|
29544
|
+
console.error(chalk8__default.default.red("No providers configured. Run `cascade init` first."));
|
|
28211
29545
|
process.exit(1);
|
|
28212
29546
|
}
|
|
28213
29547
|
const cascade = new Cascade(config, workspacePath);
|
|
28214
29548
|
try {
|
|
28215
29549
|
await cascade.init();
|
|
28216
29550
|
} catch (err) {
|
|
28217
|
-
console.error(
|
|
29551
|
+
console.error(chalk8__default.default.red(`Initialization failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
28218
29552
|
process.exit(1);
|
|
28219
29553
|
}
|
|
28220
|
-
console.error(
|
|
29554
|
+
console.error(chalk8__default.default.gray(" \u25C8 Running headlessly \u2014 tool approvals are auto-granted."));
|
|
28221
29555
|
let lastProgress = "";
|
|
28222
29556
|
cascade.on("tier:status", (ev) => {
|
|
28223
29557
|
const action = ev?.currentAction?.trim();
|
|
@@ -28225,7 +29559,7 @@ async function runHeadless(prompt, options) {
|
|
|
28225
29559
|
const line = ` \xB7 ${ev.role ?? ""} ${action}`.trimEnd();
|
|
28226
29560
|
if (line === lastProgress) return;
|
|
28227
29561
|
lastProgress = line;
|
|
28228
|
-
console.error(
|
|
29562
|
+
console.error(chalk8__default.default.gray(line));
|
|
28229
29563
|
});
|
|
28230
29564
|
try {
|
|
28231
29565
|
const result = await cascade.run({
|
|
@@ -28236,7 +29570,7 @@ async function runHeadless(prompt, options) {
|
|
|
28236
29570
|
});
|
|
28237
29571
|
process.stdout.write(result.output.trimEnd() + "\n");
|
|
28238
29572
|
} catch (err) {
|
|
28239
|
-
console.error(
|
|
29573
|
+
console.error(chalk8__default.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
28240
29574
|
await cascade.close().catch(() => {
|
|
28241
29575
|
});
|
|
28242
29576
|
process.exit(1);
|
|
@@ -28248,9 +29582,9 @@ async function runHeadless(prompt, options) {
|
|
|
28248
29582
|
function printBanner() {
|
|
28249
29583
|
if (process.stdout.columns < 60) return;
|
|
28250
29584
|
console.log();
|
|
28251
|
-
console.log(" " +
|
|
28252
|
-
console.log(" " +
|
|
28253
|
-
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"));
|
|
28254
29588
|
console.log();
|
|
28255
29589
|
}
|
|
28256
29590
|
program.parse(process.argv);
|