demian-cli 1.0.7 → 1.0.9
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 +13 -2
- package/dist/cli.mjs +1838 -359
- package/dist/index.mjs +1022 -260
- package/dist/tui.mjs +27004 -25460
- package/dist/vscode-worker.mjs +1607 -295
- package/docs/ko/README.md +6 -2
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import
|
|
5
|
+
import path35 from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/agents/types.ts
|
|
8
8
|
function normalizeAgent(input2) {
|
|
@@ -96,7 +96,7 @@ var claudeCodeAgent = {
|
|
|
96
96
|
name: "claudecode",
|
|
97
97
|
description: "Claude Code external coding agent for focused delegated workspace tasks.",
|
|
98
98
|
mode: "subagent",
|
|
99
|
-
provider: { profile: "claudecode
|
|
99
|
+
provider: { profile: "claudecode", permissionProfile: "build" },
|
|
100
100
|
tools: [],
|
|
101
101
|
systemPrompt: [
|
|
102
102
|
"You are demian claudecode, a Claude Code external runtime working as a sub agent for Demian.",
|
|
@@ -141,7 +141,7 @@ var claudeCodeExplorerAgent = {
|
|
|
141
141
|
name: "claudecode-explorer",
|
|
142
142
|
description: "Claude Code external read-only explorer for cowork repository inspection.",
|
|
143
143
|
mode: "subagent",
|
|
144
|
-
provider: { profile: "claudecode
|
|
144
|
+
provider: { profile: "claudecode", permissionProfile: "explore" },
|
|
145
145
|
tools: [],
|
|
146
146
|
systemPrompt: [
|
|
147
147
|
"You are demian claudecode-explorer, a Claude Code external runtime working as a read-only cowork sub agent for Demian.",
|
|
@@ -176,7 +176,7 @@ var claudeCodeBuilderAgent = {
|
|
|
176
176
|
name: "claudecode-builder",
|
|
177
177
|
description: "Claude Code-backed builder for bounded cowork implementation tasks.",
|
|
178
178
|
mode: "subagent",
|
|
179
|
-
provider: { profile: "claudecode
|
|
179
|
+
provider: { profile: "claudecode", permissionProfile: "build" },
|
|
180
180
|
tools: [],
|
|
181
181
|
systemPrompt: [
|
|
182
182
|
"You are demian claudecode-builder, a Claude Code external runtime working as a writer cowork sub agent for Demian.",
|
|
@@ -481,7 +481,7 @@ import { readFile as readFile6 } from "node:fs/promises";
|
|
|
481
481
|
import crypto4 from "node:crypto";
|
|
482
482
|
import fs7 from "node:fs";
|
|
483
483
|
import os7 from "node:os";
|
|
484
|
-
import
|
|
484
|
+
import path13 from "node:path";
|
|
485
485
|
|
|
486
486
|
// src/providers/retry.ts
|
|
487
487
|
var RETRY_STATUS = /* @__PURE__ */ new Set([408, 409, 425, 429, 500, 502, 503, 504]);
|
|
@@ -874,12 +874,14 @@ var dynamicImport = new Function("specifier", "return import(specifier)");
|
|
|
874
874
|
var AnthropicProvider = class {
|
|
875
875
|
id = "anthropic";
|
|
876
876
|
#apiKey;
|
|
877
|
+
#baseURL;
|
|
877
878
|
#defaultModel;
|
|
878
879
|
#defaultMaxTokens;
|
|
879
880
|
#client;
|
|
880
881
|
#onRetry;
|
|
881
882
|
constructor(config) {
|
|
882
883
|
this.#apiKey = config.apiKey;
|
|
884
|
+
this.#baseURL = config.baseURL;
|
|
883
885
|
this.#defaultModel = config.defaultModel;
|
|
884
886
|
this.#defaultMaxTokens = config.defaultMaxTokens ?? 4096;
|
|
885
887
|
this.#client = config.client;
|
|
@@ -924,7 +926,7 @@ var AnthropicProvider = class {
|
|
|
924
926
|
if (this.#client) return this.#client;
|
|
925
927
|
if (!this.#apiKey) throw new Error("AnthropicProvider requires apiKey");
|
|
926
928
|
const Anthropic = await loadAnthropicConstructor();
|
|
927
|
-
this.#client = new Anthropic({ apiKey: this.#apiKey });
|
|
929
|
+
this.#client = new Anthropic({ apiKey: this.#apiKey, baseURL: this.#baseURL });
|
|
928
930
|
return this.#client;
|
|
929
931
|
}
|
|
930
932
|
};
|
|
@@ -1157,19 +1159,56 @@ import { execFile } from "node:child_process";
|
|
|
1157
1159
|
import { promisify } from "node:util";
|
|
1158
1160
|
import fs2 from "node:fs";
|
|
1159
1161
|
import { chmod, mkdir as mkdir2, readFile as readFile2, rename, writeFile as writeFile2 } from "node:fs/promises";
|
|
1160
|
-
import
|
|
1162
|
+
import path3 from "node:path";
|
|
1161
1163
|
|
|
1162
1164
|
// src/providers/codex-state.ts
|
|
1163
1165
|
import crypto from "node:crypto";
|
|
1164
1166
|
import fs from "node:fs";
|
|
1165
1167
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1168
|
+
import os2 from "node:os";
|
|
1169
|
+
import path2 from "node:path";
|
|
1170
|
+
|
|
1171
|
+
// src/path-expansion.ts
|
|
1166
1172
|
import os from "node:os";
|
|
1167
1173
|
import path from "node:path";
|
|
1174
|
+
function expandPathReferences(value) {
|
|
1175
|
+
let expanded = expandTilde(value);
|
|
1176
|
+
expanded = expanded.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, braced, bare) => envPathValue(braced ?? bare) ?? match);
|
|
1177
|
+
expanded = expanded.replace(/%([A-Za-z_][A-Za-z0-9_]*)%/g, (match, name) => envPathValue(name) ?? match);
|
|
1178
|
+
return expanded;
|
|
1179
|
+
}
|
|
1180
|
+
function resolveExpandedPath(value) {
|
|
1181
|
+
return path.resolve(expandPathReferences(value));
|
|
1182
|
+
}
|
|
1183
|
+
function expandTilde(value) {
|
|
1184
|
+
if (value === "~") return os.homedir();
|
|
1185
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) return path.join(os.homedir(), value.slice(2));
|
|
1186
|
+
return value;
|
|
1187
|
+
}
|
|
1188
|
+
function envPathValue(name) {
|
|
1189
|
+
if (!name) return void 0;
|
|
1190
|
+
const value = processEnvValue(name);
|
|
1191
|
+
if (value !== void 0) return value;
|
|
1192
|
+
const upper = name.toUpperCase();
|
|
1193
|
+
if (upper === "HOME" || upper === "USERPROFILE") return os.homedir();
|
|
1194
|
+
if (process.platform === "win32" && upper === "HOMEDRIVE") return path.win32.parse(os.homedir()).root.replace(/[\\/]$/, "");
|
|
1195
|
+
if (process.platform === "win32" && upper === "HOMEPATH") return os.homedir().replace(/^[A-Za-z]:/, "");
|
|
1196
|
+
return void 0;
|
|
1197
|
+
}
|
|
1198
|
+
function processEnvValue(name) {
|
|
1199
|
+
if (process.env[name] !== void 0) return process.env[name];
|
|
1200
|
+
if (process.platform !== "win32") return void 0;
|
|
1201
|
+
const upper = name.toUpperCase();
|
|
1202
|
+
const key = Object.keys(process.env).find((item) => item.toUpperCase() === upper);
|
|
1203
|
+
return key ? process.env[key] : void 0;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// src/providers/codex-state.ts
|
|
1168
1207
|
function resolveCodexHome(configured) {
|
|
1169
|
-
return
|
|
1208
|
+
return resolveExpandedPath(configured ?? process.env.CODEX_HOME ?? path2.join(os2.homedir(), ".codex"));
|
|
1170
1209
|
}
|
|
1171
1210
|
async function loadOrCreateInstallationId(codexHome) {
|
|
1172
|
-
const filePath =
|
|
1211
|
+
const filePath = path2.join(codexHome, "installation_id");
|
|
1173
1212
|
try {
|
|
1174
1213
|
const existing = (await readFile(filePath, "utf8")).trim();
|
|
1175
1214
|
if (isUuid(existing)) return existing;
|
|
@@ -1191,15 +1230,6 @@ function isNodeError(error, code) {
|
|
|
1191
1230
|
function fileExists(filePath) {
|
|
1192
1231
|
return fs.existsSync(filePath);
|
|
1193
1232
|
}
|
|
1194
|
-
function expandCodexPath(value) {
|
|
1195
|
-
let expanded = value;
|
|
1196
|
-
if (expanded === "~") return os.homedir();
|
|
1197
|
-
if (expanded.startsWith("~/")) expanded = path.join(os.homedir(), expanded.slice(2));
|
|
1198
|
-
return expanded.replace(/\$\{([A-Z_][A-Z0-9_]*)\}|\$([A-Z_][A-Z0-9_]*)/gi, (match, braced, bare) => {
|
|
1199
|
-
const name = braced ?? bare;
|
|
1200
|
-
return name && process.env[name] !== void 0 ? process.env[name] : match;
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
1233
|
|
|
1204
1234
|
// src/providers/codex-auth.ts
|
|
1205
1235
|
var execFileAsync = promisify(execFile);
|
|
@@ -1422,10 +1452,10 @@ var CodexAuthStore = class {
|
|
|
1422
1452
|
}
|
|
1423
1453
|
};
|
|
1424
1454
|
function authFilePath(codexHome) {
|
|
1425
|
-
return
|
|
1455
|
+
return path3.join(codexHome, "auth.json");
|
|
1426
1456
|
}
|
|
1427
1457
|
function codexKeyringAccount(codexHome) {
|
|
1428
|
-
const resolved =
|
|
1458
|
+
const resolved = path3.resolve(codexHome);
|
|
1429
1459
|
const canonical = fs2.existsSync(resolved) ? fs2.realpathSync.native(resolved) : resolved;
|
|
1430
1460
|
const hash = crypto2.createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
1431
1461
|
return `cli|${hash}`;
|
|
@@ -1854,8 +1884,8 @@ import { randomUUID } from "node:crypto";
|
|
|
1854
1884
|
import { execFile as execFile2 } from "node:child_process";
|
|
1855
1885
|
import fs3 from "node:fs";
|
|
1856
1886
|
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
|
|
1857
|
-
import
|
|
1858
|
-
import
|
|
1887
|
+
import os3 from "node:os";
|
|
1888
|
+
import path4 from "node:path";
|
|
1859
1889
|
import { promisify as promisify2 } from "node:util";
|
|
1860
1890
|
var execFileAsync2 = promisify2(execFile2);
|
|
1861
1891
|
var CLAUDE_CODE_KEYRING_SERVICE = "Claude Code-credentials";
|
|
@@ -1888,7 +1918,7 @@ function getSharedClaudeCodeAuthStore(options = {}) {
|
|
|
1888
1918
|
proactiveRefreshMinutes: options.proactiveRefreshMinutes ?? 30,
|
|
1889
1919
|
refreshCache: options.refreshCache ?? "claude-store",
|
|
1890
1920
|
refreshTokenURL: options.refreshTokenURL ?? DEFAULT_CLAUDE_CODE_REFRESH_TOKEN_URL,
|
|
1891
|
-
keychainAccount: options.keychainAccount ??
|
|
1921
|
+
keychainAccount: options.keychainAccount ?? os3.userInfo().username
|
|
1892
1922
|
});
|
|
1893
1923
|
const existing = sharedAuthStores2.get(key);
|
|
1894
1924
|
if (existing) return existing;
|
|
@@ -1917,7 +1947,7 @@ var ClaudeCodeAuthStore = class {
|
|
|
1917
1947
|
this.#proactiveRefreshMinutes = options.proactiveRefreshMinutes ?? 30;
|
|
1918
1948
|
this.#refreshCache = options.refreshCache ?? "claude-store";
|
|
1919
1949
|
this.#refreshTokenURL = options.refreshTokenURL ?? DEFAULT_CLAUDE_CODE_REFRESH_TOKEN_URL;
|
|
1920
|
-
this.#keychainAccount = options.keychainAccount ??
|
|
1950
|
+
this.#keychainAccount = options.keychainAccount ?? os3.userInfo().username;
|
|
1921
1951
|
this.#fetch = options.fetch ?? fetch;
|
|
1922
1952
|
this.#keyring = options.keyring ?? new MacOSSecurityKeyring2();
|
|
1923
1953
|
}
|
|
@@ -2118,10 +2148,10 @@ var ClaudeCodeAuthStore = class {
|
|
|
2118
2148
|
}
|
|
2119
2149
|
};
|
|
2120
2150
|
function resolveClaudeConfigDir(configured) {
|
|
2121
|
-
return
|
|
2151
|
+
return resolveExpandedPath(configured ?? process.env.CLAUDE_CONFIG_DIR ?? path4.join(os3.homedir(), ".claude"));
|
|
2122
2152
|
}
|
|
2123
2153
|
function claudeCodeAuthFilePath(claudeConfigDir) {
|
|
2124
|
-
return
|
|
2154
|
+
return path4.join(claudeConfigDir, ".credentials.json");
|
|
2125
2155
|
}
|
|
2126
2156
|
function oauthPayload(auth) {
|
|
2127
2157
|
const raw = auth.claudeAiOauth;
|
|
@@ -2180,15 +2210,6 @@ async function writeClaudeCodeAuthFileAtomic(claudeConfigDir, auth) {
|
|
|
2180
2210
|
await chmod2(tempPath, 384);
|
|
2181
2211
|
await rename2(tempPath, filePath);
|
|
2182
2212
|
}
|
|
2183
|
-
function expandClaudePath(value) {
|
|
2184
|
-
let expanded = value;
|
|
2185
|
-
if (expanded === "~") return os2.homedir();
|
|
2186
|
-
if (expanded.startsWith("~/")) expanded = path3.join(os2.homedir(), expanded.slice(2));
|
|
2187
|
-
return expanded.replace(/\$\{([A-Z_][A-Z0-9_]*)\}|\$([A-Z_][A-Z0-9_]*)/gi, (match, braced, bare) => {
|
|
2188
|
-
const name = braced ?? bare;
|
|
2189
|
-
return name && process.env[name] !== void 0 ? process.env[name] : match;
|
|
2190
|
-
});
|
|
2191
|
-
}
|
|
2192
2213
|
var MacOSSecurityKeyring2 = class {
|
|
2193
2214
|
async load(service, account) {
|
|
2194
2215
|
if (process.platform !== "darwin") return void 0;
|
|
@@ -2425,6 +2446,111 @@ function parseClaudeCodeErrorBody(body) {
|
|
|
2425
2446
|
}
|
|
2426
2447
|
}
|
|
2427
2448
|
|
|
2449
|
+
// src/providers/ollama.ts
|
|
2450
|
+
var OllamaProvider = class {
|
|
2451
|
+
id = "ollama";
|
|
2452
|
+
#baseURL;
|
|
2453
|
+
#apiKey;
|
|
2454
|
+
#defaultModel;
|
|
2455
|
+
#fetch;
|
|
2456
|
+
#onRetry;
|
|
2457
|
+
constructor(config) {
|
|
2458
|
+
this.#baseURL = config.baseURL.replace(/\/+$/, "");
|
|
2459
|
+
this.#apiKey = config.apiKey;
|
|
2460
|
+
this.#defaultModel = config.defaultModel;
|
|
2461
|
+
this.#fetch = config.fetch ?? fetch;
|
|
2462
|
+
this.#onRetry = config.onRetry;
|
|
2463
|
+
}
|
|
2464
|
+
async chat(req) {
|
|
2465
|
+
return chatWithRetry(() => this.#rawChat(req), {
|
|
2466
|
+
signal: req.signal,
|
|
2467
|
+
onRetry: this.#onRetry
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
async #rawChat(req) {
|
|
2471
|
+
const response = await this.#fetch(`${this.#baseURL}/chat`, {
|
|
2472
|
+
method: "POST",
|
|
2473
|
+
headers: {
|
|
2474
|
+
"content-type": "application/json",
|
|
2475
|
+
...this.#authHeaders()
|
|
2476
|
+
},
|
|
2477
|
+
body: JSON.stringify({
|
|
2478
|
+
model: req.model || this.#defaultModel,
|
|
2479
|
+
messages: req.messages.map(toOllamaMessage),
|
|
2480
|
+
...req.tools.length > 0 ? { tools: req.tools.map(toOpenAITool) } : {},
|
|
2481
|
+
stream: false,
|
|
2482
|
+
...req.temperature !== void 0 ? { options: { temperature: req.temperature } } : {}
|
|
2483
|
+
}),
|
|
2484
|
+
signal: req.signal
|
|
2485
|
+
});
|
|
2486
|
+
const text = await response.text();
|
|
2487
|
+
if (!response.ok) throw new ProviderHttpError(response.status, text, parseRetryAfter(response.headers.get("retry-after")));
|
|
2488
|
+
return normalizeOllamaResponse(text ? JSON.parse(text) : {});
|
|
2489
|
+
}
|
|
2490
|
+
#authHeaders() {
|
|
2491
|
+
return this.#apiKey ? { authorization: `Bearer ${this.#apiKey}` } : {};
|
|
2492
|
+
}
|
|
2493
|
+
};
|
|
2494
|
+
function toOllamaMessage(message) {
|
|
2495
|
+
if (message.role === "tool") {
|
|
2496
|
+
return {
|
|
2497
|
+
role: "tool",
|
|
2498
|
+
content: message.content,
|
|
2499
|
+
tool_name: message.name
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
const out = {
|
|
2503
|
+
role: message.role,
|
|
2504
|
+
content: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? "")
|
|
2505
|
+
};
|
|
2506
|
+
if (message.role === "assistant" && message.toolCalls?.length) {
|
|
2507
|
+
out.tool_calls = message.toolCalls.map((call) => ({
|
|
2508
|
+
function: {
|
|
2509
|
+
name: call.name,
|
|
2510
|
+
arguments: call.input ?? {}
|
|
2511
|
+
}
|
|
2512
|
+
}));
|
|
2513
|
+
}
|
|
2514
|
+
return out;
|
|
2515
|
+
}
|
|
2516
|
+
function normalizeOllamaResponse(raw) {
|
|
2517
|
+
const data = raw;
|
|
2518
|
+
const toolCalls = [];
|
|
2519
|
+
for (const call of data.message?.tool_calls ?? []) {
|
|
2520
|
+
const name = call.function?.name;
|
|
2521
|
+
if (!name) continue;
|
|
2522
|
+
toolCalls.push({
|
|
2523
|
+
id: call.id ?? `call_${toolCalls.length + 1}`,
|
|
2524
|
+
name,
|
|
2525
|
+
input: normalizeToolArguments(call.function?.arguments)
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
const message = {
|
|
2529
|
+
role: "assistant",
|
|
2530
|
+
content: data.message?.content ?? null,
|
|
2531
|
+
...toolCalls.length ? { toolCalls } : {}
|
|
2532
|
+
};
|
|
2533
|
+
return {
|
|
2534
|
+
message,
|
|
2535
|
+
toolCalls,
|
|
2536
|
+
stopReason: toolCalls.length ? "tool_use" : data.done_reason === "length" ? "max_tokens" : "end_turn",
|
|
2537
|
+
usage: data.prompt_eval_count !== void 0 || data.eval_count !== void 0 ? {
|
|
2538
|
+
inputTokens: data.prompt_eval_count,
|
|
2539
|
+
outputTokens: data.eval_count,
|
|
2540
|
+
totalTokens: (data.prompt_eval_count ?? 0) + (data.eval_count ?? 0)
|
|
2541
|
+
} : void 0,
|
|
2542
|
+
raw
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
function normalizeToolArguments(value) {
|
|
2546
|
+
if (typeof value !== "string") return value ?? {};
|
|
2547
|
+
try {
|
|
2548
|
+
return JSON.parse(value);
|
|
2549
|
+
} catch {
|
|
2550
|
+
return {};
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2428
2554
|
// src/external-runtime/claudecode-cli.ts
|
|
2429
2555
|
import { spawn as spawn3 } from "node:child_process";
|
|
2430
2556
|
import readline from "node:readline";
|
|
@@ -2432,7 +2558,7 @@ import readline from "node:readline";
|
|
|
2432
2558
|
// src/external-runtime/claudecode-attachments.ts
|
|
2433
2559
|
import crypto3 from "node:crypto";
|
|
2434
2560
|
import fs4 from "node:fs";
|
|
2435
|
-
import
|
|
2561
|
+
import path5 from "node:path";
|
|
2436
2562
|
async function resolveClaudeCodeAttachmentPrompt(runtimeLabel, config, req) {
|
|
2437
2563
|
const attachments = req.attachments ?? [];
|
|
2438
2564
|
if (attachments.length === 0) return req.prompt;
|
|
@@ -2474,7 +2600,7 @@ function unsupportedAttachmentError(runtimeLabel, count, detail) {
|
|
|
2474
2600
|
}
|
|
2475
2601
|
function formatAttachmentReference(value, cwd) {
|
|
2476
2602
|
if (isUrl(value)) return imageMimeFromPath(value) ? `[image: ${value} (${imageMimeFromPath(value)}, remote)]` : `[attachment: ${value}]`;
|
|
2477
|
-
const target =
|
|
2603
|
+
const target = path5.isAbsolute(value) ? value : path5.resolve(cwd, value);
|
|
2478
2604
|
try {
|
|
2479
2605
|
const stats = fs4.statSync(target);
|
|
2480
2606
|
if (!stats.isFile()) return `[attachment: ${value} (${stats.size} bytes)]`;
|
|
@@ -2491,7 +2617,7 @@ function isUrl(value) {
|
|
|
2491
2617
|
}
|
|
2492
2618
|
function imageMimeFromPath(value) {
|
|
2493
2619
|
const pathname = isUrl(value) ? new URL(value).pathname : value;
|
|
2494
|
-
const ext =
|
|
2620
|
+
const ext = path5.extname(pathname).toLowerCase();
|
|
2495
2621
|
if (ext === ".png") return "image/png";
|
|
2496
2622
|
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
2497
2623
|
if (ext === ".gif") return "image/gif";
|
|
@@ -2523,15 +2649,14 @@ function hasAnthropicApiKeyEnv(env = process.env) {
|
|
|
2523
2649
|
|
|
2524
2650
|
// src/external-runtime/claudecode-paths.ts
|
|
2525
2651
|
import fs5 from "node:fs";
|
|
2526
|
-
import
|
|
2527
|
-
import path5 from "node:path";
|
|
2652
|
+
import path6 from "node:path";
|
|
2528
2653
|
function resolveClaudeCodeCliPath(configured) {
|
|
2529
2654
|
if (configured) return expandHome(configured);
|
|
2530
2655
|
if (process.env.CLAUDE_CODE_CLI) return expandHome(process.env.CLAUDE_CODE_CLI);
|
|
2531
2656
|
const candidates = ["~/.local/bin/claude", "claude"];
|
|
2532
2657
|
for (const candidate of candidates) {
|
|
2533
2658
|
const expanded = expandHome(candidate);
|
|
2534
|
-
if (
|
|
2659
|
+
if (path6.isAbsolute(expanded)) {
|
|
2535
2660
|
if (isExecutableFile(expanded)) return expanded;
|
|
2536
2661
|
continue;
|
|
2537
2662
|
}
|
|
@@ -2540,9 +2665,7 @@ function resolveClaudeCodeCliPath(configured) {
|
|
|
2540
2665
|
return void 0;
|
|
2541
2666
|
}
|
|
2542
2667
|
function expandHome(value) {
|
|
2543
|
-
|
|
2544
|
-
if (value.startsWith("~/")) return path5.join(os3.homedir(), value.slice(2));
|
|
2545
|
-
return value;
|
|
2668
|
+
return expandPathReferences(value);
|
|
2546
2669
|
}
|
|
2547
2670
|
function isExecutableFile(filePath) {
|
|
2548
2671
|
try {
|
|
@@ -2808,14 +2931,14 @@ function numberValue2(value) {
|
|
|
2808
2931
|
// src/external-runtime/session-lock.ts
|
|
2809
2932
|
import { mkdir as mkdir4, open, readFile as readFile4, unlink } from "node:fs/promises";
|
|
2810
2933
|
import os4 from "node:os";
|
|
2811
|
-
import
|
|
2934
|
+
import path7 from "node:path";
|
|
2812
2935
|
var DEFAULT_STALE_MS = 30 * 60 * 1e3;
|
|
2813
2936
|
async function acquireClaudeCodeSessionLock(sessionId, options = {}) {
|
|
2814
|
-
const dir = options.dir ??
|
|
2937
|
+
const dir = options.dir ?? path7.join(os4.homedir(), ".demian", "claude-sessions");
|
|
2815
2938
|
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
2816
2939
|
const now2 = options.now ?? (() => Date.now());
|
|
2817
2940
|
await mkdir4(dir, { recursive: true });
|
|
2818
|
-
const lockPath =
|
|
2941
|
+
const lockPath = path7.join(dir, `${encodeURIComponent(sessionId)}.lock`);
|
|
2819
2942
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2820
2943
|
try {
|
|
2821
2944
|
const handle = await open(lockPath, "wx");
|
|
@@ -2879,14 +3002,14 @@ function isAlreadyExists(error) {
|
|
|
2879
3002
|
// src/external-runtime/usage-ledger.ts
|
|
2880
3003
|
import { mkdir as mkdir5, readFile as readFile5, rename as rename3, writeFile as writeFile4 } from "node:fs/promises";
|
|
2881
3004
|
import os5 from "node:os";
|
|
2882
|
-
import
|
|
3005
|
+
import path8 from "node:path";
|
|
2883
3006
|
var processLedger = /* @__PURE__ */ new Map();
|
|
2884
3007
|
var ClaudeCodeUsageLedger = class {
|
|
2885
3008
|
#scope;
|
|
2886
3009
|
#filePath;
|
|
2887
3010
|
constructor(options = {}) {
|
|
2888
3011
|
this.#scope = options.scope ?? "process";
|
|
2889
|
-
this.#filePath = options.filePath ??
|
|
3012
|
+
this.#filePath = options.filePath ?? path8.join(os5.homedir(), ".demian", "usage-ledger.json");
|
|
2890
3013
|
}
|
|
2891
3014
|
async spentUsd(key, now2 = /* @__PURE__ */ new Date()) {
|
|
2892
3015
|
if (this.#scope === "process") return processLedger.get(processKey(key)) ?? 0;
|
|
@@ -2917,7 +3040,7 @@ var ClaudeCodeUsageLedger = class {
|
|
|
2917
3040
|
return { version: 1, buckets: {} };
|
|
2918
3041
|
}
|
|
2919
3042
|
async #write(file) {
|
|
2920
|
-
await mkdir5(
|
|
3043
|
+
await mkdir5(path8.dirname(this.#filePath), { recursive: true });
|
|
2921
3044
|
const temp = `${this.#filePath}.${process.pid}.tmp`;
|
|
2922
3045
|
await writeFile4(temp, `${JSON.stringify(file, null, 2)}
|
|
2923
3046
|
`, "utf8");
|
|
@@ -22578,10 +22701,10 @@ function now() {
|
|
|
22578
22701
|
}
|
|
22579
22702
|
|
|
22580
22703
|
// src/permissions/engine.ts
|
|
22581
|
-
import
|
|
22704
|
+
import path11 from "node:path";
|
|
22582
22705
|
|
|
22583
22706
|
// src/permissions/grants.ts
|
|
22584
|
-
import
|
|
22707
|
+
import path9 from "node:path";
|
|
22585
22708
|
|
|
22586
22709
|
// src/util.ts
|
|
22587
22710
|
function stableStringify(value) {
|
|
@@ -22636,8 +22759,8 @@ function grantKeyFor(tool, input2, cwd) {
|
|
|
22636
22759
|
}
|
|
22637
22760
|
if (tool === "write_file" || tool === "edit_file") {
|
|
22638
22761
|
const filePath = typeof object2.path === "string" ? object2.path : "";
|
|
22639
|
-
const parent = filePath ?
|
|
22640
|
-
return `${tool}:${
|
|
22762
|
+
const parent = filePath ? path9.dirname(path9.resolve(cwd, filePath)) : cwd;
|
|
22763
|
+
return `${tool}:${path9.relative(cwd, parent) || "."}`;
|
|
22641
22764
|
}
|
|
22642
22765
|
return `${tool}:${stableStringify(input2)}`;
|
|
22643
22766
|
}
|
|
@@ -22664,30 +22787,30 @@ function firstCommandTokens(command, count) {
|
|
|
22664
22787
|
}
|
|
22665
22788
|
|
|
22666
22789
|
// src/workspace/paths.ts
|
|
22667
|
-
import
|
|
22790
|
+
import path10 from "node:path";
|
|
22668
22791
|
function normalizePathForMatch(value) {
|
|
22669
|
-
return value.split(
|
|
22792
|
+
return value.split(path10.sep).join("/");
|
|
22670
22793
|
}
|
|
22671
22794
|
function isInsidePath(root, candidate) {
|
|
22672
|
-
const relative =
|
|
22673
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
22795
|
+
const relative = path10.relative(path10.resolve(root), path10.resolve(candidate));
|
|
22796
|
+
return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
|
|
22674
22797
|
}
|
|
22675
22798
|
function resolveInsideCwd(cwd, inputPath) {
|
|
22676
22799
|
if (typeof inputPath !== "string" || inputPath.length === 0) {
|
|
22677
22800
|
throw new Error("path must be a non-empty string");
|
|
22678
22801
|
}
|
|
22679
|
-
const resolved =
|
|
22802
|
+
const resolved = path10.resolve(cwd, inputPath);
|
|
22680
22803
|
if (!isInsidePath(cwd, resolved)) {
|
|
22681
22804
|
throw new Error(`Path is outside the workspace: ${inputPath}`);
|
|
22682
22805
|
}
|
|
22683
22806
|
return resolved;
|
|
22684
22807
|
}
|
|
22685
22808
|
function relativeToCwd(cwd, absolutePath) {
|
|
22686
|
-
const relative =
|
|
22809
|
+
const relative = path10.relative(cwd, absolutePath);
|
|
22687
22810
|
return normalizePathForMatch(relative || ".");
|
|
22688
22811
|
}
|
|
22689
22812
|
function isEnvFile(filePath) {
|
|
22690
|
-
const base =
|
|
22813
|
+
const base = path10.basename(filePath);
|
|
22691
22814
|
if (base === ".env.example") return false;
|
|
22692
22815
|
return base === ".env" || base.startsWith(".env.");
|
|
22693
22816
|
}
|
|
@@ -22794,7 +22917,7 @@ var PermissionEngine = class {
|
|
|
22794
22917
|
#hardDeny(tool, input2, grantKey) {
|
|
22795
22918
|
const paths = inputPaths(tool, input2);
|
|
22796
22919
|
for (const item of paths) {
|
|
22797
|
-
const resolved =
|
|
22920
|
+
const resolved = path11.resolve(this.#cwd, item);
|
|
22798
22921
|
if (!isInsidePath(this.#cwd, resolved)) {
|
|
22799
22922
|
return {
|
|
22800
22923
|
decision: "deny",
|
|
@@ -22848,7 +22971,7 @@ function matchesRule(rule, tool, input2, cwd) {
|
|
|
22848
22971
|
if (rule.match.pathGlob) {
|
|
22849
22972
|
const paths = inputPaths(tool, input2);
|
|
22850
22973
|
if (paths.length === 0) return false;
|
|
22851
|
-
if (!paths.some((item) => matchesPathRule(rule.match?.pathGlob, relativeToCwd(cwd,
|
|
22974
|
+
if (!paths.some((item) => matchesPathRule(rule.match?.pathGlob, relativeToCwd(cwd, path11.resolve(cwd, item))))) {
|
|
22852
22975
|
return false;
|
|
22853
22976
|
}
|
|
22854
22977
|
}
|
|
@@ -22895,7 +23018,7 @@ function rulePriority(rule, ref) {
|
|
|
22895
23018
|
}
|
|
22896
23019
|
function matchesPathRule(pattern, relativePath) {
|
|
22897
23020
|
if (!pattern) return true;
|
|
22898
|
-
if (
|
|
23021
|
+
if (path11.basename(relativePath) === ".env.example" && pattern.includes(".env.*")) return false;
|
|
22899
23022
|
return matchGlob(pattern, relativePath);
|
|
22900
23023
|
}
|
|
22901
23024
|
function mostSpecificRule(rules, input2, cwd, ref) {
|
|
@@ -22973,13 +23096,13 @@ function permissionLabel(req) {
|
|
|
22973
23096
|
if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
|
|
22974
23097
|
return `${req.tool}: ${inputObject.path}`;
|
|
22975
23098
|
}
|
|
22976
|
-
const
|
|
22977
|
-
if (
|
|
23099
|
+
const path36 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
|
|
23100
|
+
if (path36) return `${tool}: ${path36}`;
|
|
22978
23101
|
return tool;
|
|
22979
23102
|
}
|
|
22980
23103
|
|
|
22981
23104
|
// src/workspace/write-scope.ts
|
|
22982
|
-
import
|
|
23105
|
+
import path12 from "node:path";
|
|
22983
23106
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file", "Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
22984
23107
|
var WORKSPACE_SCOPE = "**";
|
|
22985
23108
|
function normalizeWriteScope(cwd, scope) {
|
|
@@ -23001,7 +23124,7 @@ function enforceToolWriteScope(input2) {
|
|
|
23001
23124
|
if (paths.length === 0) return { ok: true, paths: [] };
|
|
23002
23125
|
const checked = [];
|
|
23003
23126
|
for (const target of paths) {
|
|
23004
|
-
const resolved =
|
|
23127
|
+
const resolved = path12.resolve(input2.cwd, target);
|
|
23005
23128
|
const relative = relativeToCwd(input2.cwd, resolved);
|
|
23006
23129
|
checked.push(relative);
|
|
23007
23130
|
const allowed = isPathAllowedByWriteScope(input2.cwd, target, input2.writeScope);
|
|
@@ -23025,7 +23148,7 @@ function outOfScopeDiffFiles(cwd, summary, writeScope) {
|
|
|
23025
23148
|
return out;
|
|
23026
23149
|
}
|
|
23027
23150
|
function isPathAllowedByWriteScope(cwd, inputPath, writeScope) {
|
|
23028
|
-
const resolved =
|
|
23151
|
+
const resolved = path12.resolve(cwd, inputPath);
|
|
23029
23152
|
if (!isInsidePath(cwd, resolved)) {
|
|
23030
23153
|
return { ok: false, error: "path is outside the workspace", paths: [inputPath] };
|
|
23031
23154
|
}
|
|
@@ -23044,9 +23167,9 @@ function normalizeScopePattern(cwd, raw) {
|
|
|
23044
23167
|
if (!value) return { ok: false, error: "writeScope entries must be non-empty." };
|
|
23045
23168
|
if (value.includes("\0")) return { ok: false, error: "writeScope entries must not contain NUL bytes." };
|
|
23046
23169
|
const hasGlob2 = /[*?[\]{}]/.test(value);
|
|
23047
|
-
if (
|
|
23170
|
+
if (path12.isAbsolute(value)) {
|
|
23048
23171
|
if (hasGlob2) return { ok: false, error: `writeScope absolute globs are not supported: ${raw}` };
|
|
23049
|
-
const resolved =
|
|
23172
|
+
const resolved = path12.resolve(value);
|
|
23050
23173
|
if (!isInsidePath(cwd, resolved)) return { ok: false, error: `writeScope path is outside the workspace: ${raw}` };
|
|
23051
23174
|
value = relativeToCwd(cwd, resolved);
|
|
23052
23175
|
}
|
|
@@ -23423,6 +23546,7 @@ function claudeCodePreviewEnabled(config = {}, env = process.env) {
|
|
|
23423
23546
|
}
|
|
23424
23547
|
|
|
23425
23548
|
// src/config.ts
|
|
23549
|
+
var BUILTIN_PROVIDER_ORDER = ["openai", "anthropic", "gemini", "groq", "azure", "lmstudio", "ollama-local", "ollama-cloud", "llamacpp", "vllm", "codex", "claudecode"];
|
|
23426
23550
|
var CLAUDE_CODE_SONNET_MODEL = "claude-sonnet-4-6";
|
|
23427
23551
|
var CLAUDE_CODE_OPUS_MODEL = "claude-opus-4-7";
|
|
23428
23552
|
var CLAUDE_CODE_MODELS = [CLAUDE_CODE_SONNET_MODEL, CLAUDE_CODE_OPUS_MODEL];
|
|
@@ -23460,7 +23584,7 @@ var defaultConfig = {
|
|
|
23460
23584
|
maxConcurrentExpensive: 1,
|
|
23461
23585
|
maxConcurrentWriters: 1,
|
|
23462
23586
|
maxConcurrentPerProvider: {
|
|
23463
|
-
|
|
23587
|
+
claudecode: 1
|
|
23464
23588
|
},
|
|
23465
23589
|
tokenBudget: null,
|
|
23466
23590
|
defaultMergeStrategy: "synthesize",
|
|
@@ -23515,6 +23639,7 @@ var defaultConfig = {
|
|
|
23515
23639
|
defaultProvider: "brave",
|
|
23516
23640
|
providers: {
|
|
23517
23641
|
brave: {
|
|
23642
|
+
apiKey: "",
|
|
23518
23643
|
apiKeyEnv: "BRAVE_SEARCH_API_KEY",
|
|
23519
23644
|
endpoint: "https://api.search.brave.com/res/v1/web/search",
|
|
23520
23645
|
count: 10,
|
|
@@ -23523,12 +23648,14 @@ var defaultConfig = {
|
|
|
23523
23648
|
safeSearch: "moderate"
|
|
23524
23649
|
},
|
|
23525
23650
|
tavily: {
|
|
23651
|
+
apiKey: "",
|
|
23526
23652
|
apiKeyEnv: "TAVILY_API_KEY",
|
|
23527
23653
|
endpoint: "https://api.tavily.com/search",
|
|
23528
23654
|
maxResults: 5,
|
|
23529
23655
|
searchDepth: "basic"
|
|
23530
23656
|
},
|
|
23531
23657
|
exa: {
|
|
23658
|
+
apiKey: "",
|
|
23532
23659
|
apiKeyEnv: "EXA_API_KEY",
|
|
23533
23660
|
endpoint: "https://api.exa.ai/search",
|
|
23534
23661
|
numResults: 5,
|
|
@@ -23582,73 +23709,113 @@ var defaultConfig = {
|
|
|
23582
23709
|
providers: {
|
|
23583
23710
|
openai: {
|
|
23584
23711
|
type: "openai-compatible",
|
|
23585
|
-
model: "openai-model-name",
|
|
23586
23712
|
baseURL: "https://api.openai.com/v1",
|
|
23587
|
-
|
|
23713
|
+
apiKey: "",
|
|
23714
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
23715
|
+
catalog: {
|
|
23716
|
+
type: "openai-models",
|
|
23717
|
+
endpoint: "https://api.openai.com/v1/models"
|
|
23718
|
+
}
|
|
23719
|
+
},
|
|
23720
|
+
anthropic: {
|
|
23721
|
+
type: "anthropic",
|
|
23722
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
23723
|
+
apiKey: "",
|
|
23724
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
23725
|
+
catalog: {
|
|
23726
|
+
type: "anthropic-models",
|
|
23727
|
+
endpoint: "https://api.anthropic.com/v1/models"
|
|
23728
|
+
}
|
|
23588
23729
|
},
|
|
23589
23730
|
gemini: {
|
|
23590
23731
|
type: "openai-compatible",
|
|
23591
|
-
model: "gemini-model-name",
|
|
23592
23732
|
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
23593
|
-
|
|
23733
|
+
apiKey: "",
|
|
23734
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
23735
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
23736
|
+
catalog: {
|
|
23737
|
+
type: "gemini-openai-models",
|
|
23738
|
+
endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models"
|
|
23739
|
+
}
|
|
23594
23740
|
},
|
|
23595
|
-
|
|
23741
|
+
groq: {
|
|
23596
23742
|
type: "openai-compatible",
|
|
23597
|
-
|
|
23598
|
-
|
|
23599
|
-
|
|
23600
|
-
|
|
23743
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
23744
|
+
apiKey: "",
|
|
23745
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
23746
|
+
catalog: {
|
|
23747
|
+
type: "groq-models",
|
|
23748
|
+
endpoint: "https://api.groq.com/openai/v1/models"
|
|
23749
|
+
}
|
|
23750
|
+
},
|
|
23751
|
+
azure: {
|
|
23752
|
+
type: "openai-compatible",
|
|
23753
|
+
auth: { type: "api-key", header: "api-key" },
|
|
23754
|
+
modelProfiles: [
|
|
23755
|
+
{
|
|
23756
|
+
name: "azure-example",
|
|
23757
|
+
displayName: "Azure example",
|
|
23758
|
+
model: "azure-deployment-name",
|
|
23759
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
23760
|
+
apiKey: "",
|
|
23761
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
23762
|
+
}
|
|
23763
|
+
]
|
|
23601
23764
|
},
|
|
23602
23765
|
lmstudio: {
|
|
23603
23766
|
type: "openai-compatible",
|
|
23604
|
-
model: "local-coder-model",
|
|
23605
23767
|
baseURL: "http://localhost:1234/v1",
|
|
23606
|
-
apiKey: "lm-studio"
|
|
23768
|
+
apiKey: "lm-studio",
|
|
23769
|
+
modelProfiles: [],
|
|
23770
|
+
catalog: {
|
|
23771
|
+
type: "openai-compatible-models",
|
|
23772
|
+
endpoint: "http://localhost:1234/v1/models"
|
|
23773
|
+
}
|
|
23607
23774
|
},
|
|
23608
|
-
|
|
23775
|
+
"ollama-local": {
|
|
23609
23776
|
type: "openai-compatible",
|
|
23610
|
-
|
|
23611
|
-
|
|
23612
|
-
|
|
23777
|
+
baseURL: "http://localhost:11434/v1",
|
|
23778
|
+
apiKey: "ollama",
|
|
23779
|
+
modelProfiles: [],
|
|
23780
|
+
quirks: { omitTemperature: true },
|
|
23781
|
+
catalog: {
|
|
23782
|
+
type: "openai-compatible-models",
|
|
23783
|
+
endpoint: "http://localhost:11434/v1/models"
|
|
23784
|
+
}
|
|
23785
|
+
},
|
|
23786
|
+
"ollama-cloud": {
|
|
23787
|
+
type: "ollama",
|
|
23788
|
+
baseURL: "https://ollama.com/api",
|
|
23789
|
+
apiKey: "",
|
|
23790
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
23791
|
+
modelProfiles: [],
|
|
23792
|
+
catalog: {
|
|
23793
|
+
type: "ollama-tags",
|
|
23794
|
+
endpoint: "https://ollama.com/api/tags"
|
|
23795
|
+
}
|
|
23613
23796
|
},
|
|
23614
23797
|
llamacpp: {
|
|
23615
23798
|
type: "openai-compatible",
|
|
23616
|
-
model: "local-coder-model",
|
|
23617
23799
|
baseURL: "http://localhost:8080/v1",
|
|
23618
|
-
apiKey: "llama.cpp"
|
|
23619
|
-
|
|
23620
|
-
|
|
23621
|
-
|
|
23622
|
-
|
|
23623
|
-
|
|
23624
|
-
apiKeyEnv: "OPENROUTER_API_KEY"
|
|
23625
|
-
},
|
|
23626
|
-
together: {
|
|
23627
|
-
type: "openai-compatible",
|
|
23628
|
-
model: "provider/model-name",
|
|
23629
|
-
baseURL: "https://api.together.xyz/v1",
|
|
23630
|
-
apiKeyEnv: "TOGETHER_API_KEY"
|
|
23631
|
-
},
|
|
23632
|
-
groq: {
|
|
23633
|
-
type: "openai-compatible",
|
|
23634
|
-
model: "provider/model-name",
|
|
23635
|
-
baseURL: "https://api.groq.com/openai/v1",
|
|
23636
|
-
apiKeyEnv: "GROQ_API_KEY"
|
|
23800
|
+
apiKey: "llama.cpp",
|
|
23801
|
+
modelProfiles: [],
|
|
23802
|
+
catalog: {
|
|
23803
|
+
type: "openai-compatible-models",
|
|
23804
|
+
endpoint: "http://localhost:8080/v1/models"
|
|
23805
|
+
}
|
|
23637
23806
|
},
|
|
23638
|
-
|
|
23807
|
+
vllm: {
|
|
23639
23808
|
type: "openai-compatible",
|
|
23640
|
-
|
|
23641
|
-
|
|
23642
|
-
|
|
23643
|
-
|
|
23644
|
-
|
|
23645
|
-
|
|
23646
|
-
|
|
23647
|
-
apiKeyEnv: "ANTHROPIC_API_KEY"
|
|
23809
|
+
baseURL: "http://localhost:8000/v1",
|
|
23810
|
+
apiKey: "vllm",
|
|
23811
|
+
modelProfiles: [],
|
|
23812
|
+
catalog: {
|
|
23813
|
+
type: "openai-compatible-models",
|
|
23814
|
+
endpoint: "http://localhost:8000/v1/models"
|
|
23815
|
+
}
|
|
23648
23816
|
},
|
|
23649
23817
|
codex: {
|
|
23650
23818
|
type: "codex",
|
|
23651
|
-
model: "gpt-5.1-codex",
|
|
23652
23819
|
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
23653
23820
|
authStore: "auto",
|
|
23654
23821
|
allowApiKeyFallback: false,
|
|
@@ -23660,6 +23827,9 @@ var defaultConfig = {
|
|
|
23660
23827
|
effort: "medium",
|
|
23661
23828
|
summary: "auto"
|
|
23662
23829
|
}
|
|
23830
|
+
},
|
|
23831
|
+
catalog: {
|
|
23832
|
+
type: "codex-oauth-models"
|
|
23663
23833
|
}
|
|
23664
23834
|
},
|
|
23665
23835
|
claudecode: {
|
|
@@ -23678,67 +23848,37 @@ var defaultConfig = {
|
|
|
23678
23848
|
useBareMode: false,
|
|
23679
23849
|
usageLedgerScope: "process",
|
|
23680
23850
|
sessionLock: true,
|
|
23681
|
-
abortPolicy: "record-only"
|
|
23682
|
-
},
|
|
23683
|
-
"claudecode-plan": {
|
|
23684
|
-
type: "claudecode",
|
|
23685
|
-
runtime: "agent-sdk",
|
|
23686
|
-
model: CLAUDE_CODE_SONNET_MODEL,
|
|
23687
|
-
models: [...CLAUDE_CODE_MODELS],
|
|
23688
|
-
permissionProfile: "plan",
|
|
23689
|
-
cliPath: "~/.local/bin/claude",
|
|
23690
|
-
cwdMode: "session",
|
|
23691
|
-
historyPolicy: "passthrough-resume",
|
|
23692
|
-
onInvalidResume: "fresh",
|
|
23693
|
-
attachmentFallback: "block",
|
|
23694
|
-
allowSubagents: false,
|
|
23695
|
-
sanitizeApiKeyEnv: true,
|
|
23696
|
-
authPreflight: true,
|
|
23697
|
-
useBareMode: false,
|
|
23698
|
-
usageLedgerScope: "process",
|
|
23699
|
-
sessionLock: true,
|
|
23700
|
-
abortPolicy: "record-only",
|
|
23701
|
-
hidden: true
|
|
23702
|
-
},
|
|
23703
|
-
"claudecode-subagent": {
|
|
23704
|
-
type: "claudecode",
|
|
23705
|
-
runtime: "agent-sdk",
|
|
23706
|
-
model: CLAUDE_CODE_SONNET_MODEL,
|
|
23707
|
-
models: [...CLAUDE_CODE_MODELS],
|
|
23708
|
-
permissionProfile: "build",
|
|
23709
|
-
cliPath: "~/.local/bin/claude",
|
|
23710
|
-
cwdMode: "session",
|
|
23711
|
-
historyPolicy: "passthrough-resume",
|
|
23712
|
-
onInvalidResume: "fresh",
|
|
23713
|
-
attachmentFallback: "block",
|
|
23714
|
-
allowSubagents: false,
|
|
23715
|
-
sanitizeApiKeyEnv: true,
|
|
23716
|
-
authPreflight: true,
|
|
23717
|
-
useBareMode: false,
|
|
23718
|
-
usageLedgerScope: "process",
|
|
23719
|
-
sessionLock: true,
|
|
23720
23851
|
abortPolicy: "record-only",
|
|
23721
|
-
|
|
23852
|
+
catalog: {
|
|
23853
|
+
type: "claudecode-supported-models"
|
|
23854
|
+
}
|
|
23722
23855
|
}
|
|
23723
23856
|
}
|
|
23724
23857
|
};
|
|
23725
23858
|
async function loadConfig(options) {
|
|
23726
23859
|
const configs = [];
|
|
23727
|
-
const userConfigDir =
|
|
23728
|
-
const
|
|
23729
|
-
const configPaths = [
|
|
23730
|
-
path12.join(userConfigDir, "config.json"),
|
|
23731
|
-
path12.join(userConfigDir, "config.jsond"),
|
|
23732
|
-
path12.join(projectConfigDir, "config.json"),
|
|
23733
|
-
path12.join(projectConfigDir, "config.jsond"),
|
|
23734
|
-
options.configPath
|
|
23735
|
-
].filter(Boolean);
|
|
23860
|
+
const userConfigDir = path13.join(os7.homedir(), ".demian");
|
|
23861
|
+
const configPaths = [path13.join(userConfigDir, "config.json"), path13.join(userConfigDir, "config.jsond"), options.configPath].filter(Boolean);
|
|
23736
23862
|
for (const filePath of configPaths) {
|
|
23737
23863
|
if (!fs7.existsSync(filePath)) continue;
|
|
23738
|
-
|
|
23864
|
+
const parsed = parseConfigText(await readFile6(filePath, "utf8"), filePath);
|
|
23865
|
+
warnIfV1Config(parsed, filePath);
|
|
23866
|
+
configs.push(parsed);
|
|
23739
23867
|
}
|
|
23740
23868
|
return configs.reduce((acc, item) => mergeConfig(acc, item), structuredClone(defaultConfig));
|
|
23741
23869
|
}
|
|
23870
|
+
function warnIfV1Config(parsed, filePath) {
|
|
23871
|
+
if (parsed.version === 2) return;
|
|
23872
|
+
const providers = parsed.providers ?? {};
|
|
23873
|
+
const hasLegacyShape = Object.values(providers).some((provider) => {
|
|
23874
|
+
const candidate = provider;
|
|
23875
|
+
return (typeof candidate.model === "string" || Array.isArray(candidate.models)) && !Array.isArray(candidate.modelProfiles);
|
|
23876
|
+
});
|
|
23877
|
+
if (!hasLegacyShape && parsed.version === void 0 && Object.keys(providers).length === 0) return;
|
|
23878
|
+
if (!hasLegacyShape) return;
|
|
23879
|
+
process.stderr.write(`[demian] warning: ${filePath} uses the v1 schema (provider.model/models). v2 expects modelProfiles. Run \`demian config init\` to scaffold a v2 template.
|
|
23880
|
+
`);
|
|
23881
|
+
}
|
|
23742
23882
|
function parseConfigText(text, filePath) {
|
|
23743
23883
|
try {
|
|
23744
23884
|
return JSON.parse(filePath.endsWith(".jsond") ? stripJsonD(text) : text);
|
|
@@ -23945,7 +24085,137 @@ function mergeConfig(base, overlay) {
|
|
|
23945
24085
|
};
|
|
23946
24086
|
}
|
|
23947
24087
|
function normalizeProviderAlias(providerName) {
|
|
23948
|
-
|
|
24088
|
+
if (providerName === "claudecode-plan" || providerName === "claudecode-subagent") return "claudecode";
|
|
24089
|
+
if (providerName === "ollama") return "ollama-local";
|
|
24090
|
+
return providerName;
|
|
24091
|
+
}
|
|
24092
|
+
function sortProviderNames(names) {
|
|
24093
|
+
const order = new Map(BUILTIN_PROVIDER_ORDER.map((name, index) => [name, index]));
|
|
24094
|
+
return [...names].sort((left, right) => {
|
|
24095
|
+
const leftIndex = order.get(left);
|
|
24096
|
+
const rightIndex = order.get(right);
|
|
24097
|
+
if (leftIndex !== void 0 && rightIndex !== void 0) return leftIndex - rightIndex;
|
|
24098
|
+
if (leftIndex !== void 0) return -1;
|
|
24099
|
+
if (rightIndex !== void 0) return 1;
|
|
24100
|
+
return left.localeCompare(right);
|
|
24101
|
+
});
|
|
24102
|
+
}
|
|
24103
|
+
function providerModelProfiles(providerName, providerConfig) {
|
|
24104
|
+
const explicit = Array.isArray(providerConfig.modelProfiles) ? providerConfig.modelProfiles.filter((profile) => typeof profile?.model === "string" && profile.model.trim()).map((profile) => ({
|
|
24105
|
+
...profile,
|
|
24106
|
+
name: profile.name?.trim() || profile.displayName?.trim() || profile.model.trim(),
|
|
24107
|
+
displayName: profile.displayName?.trim() || profile.name?.trim() || profile.model.trim(),
|
|
24108
|
+
model: profile.model.trim()
|
|
24109
|
+
})) : [];
|
|
24110
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
24111
|
+
const seenDisplayNames = /* @__PURE__ */ new Set();
|
|
24112
|
+
const seenModels = /* @__PURE__ */ new Set();
|
|
24113
|
+
const out = [];
|
|
24114
|
+
for (const profile of explicit) {
|
|
24115
|
+
if (seenNames.has(profile.name)) {
|
|
24116
|
+
process.stderr.write(`[demian] warning: provider ${providerName} has duplicate modelProfile name "${profile.name}". Later entry ignored.
|
|
24117
|
+
`);
|
|
24118
|
+
continue;
|
|
24119
|
+
}
|
|
24120
|
+
if (profile.displayName && seenDisplayNames.has(profile.displayName)) {
|
|
24121
|
+
process.stderr.write(`[demian] warning: provider ${providerName} has duplicate modelProfile displayName "${profile.displayName}". Later entry ignored.
|
|
24122
|
+
`);
|
|
24123
|
+
continue;
|
|
24124
|
+
}
|
|
24125
|
+
seenNames.add(profile.name);
|
|
24126
|
+
if (profile.displayName) seenDisplayNames.add(profile.displayName);
|
|
24127
|
+
seenModels.add(profile.model);
|
|
24128
|
+
out.push(profile);
|
|
24129
|
+
}
|
|
24130
|
+
const legacy = [...typeof providerConfig.model === "string" && providerConfig.model.trim() ? [providerConfig.model.trim()] : [], ...(providerConfig.models ?? []).filter((model) => typeof model === "string" && model.trim())];
|
|
24131
|
+
for (const model of legacy) {
|
|
24132
|
+
const trimmed = model.trim();
|
|
24133
|
+
if (!trimmed || seenModels.has(trimmed)) continue;
|
|
24134
|
+
seenModels.add(trimmed);
|
|
24135
|
+
const name = trimmed;
|
|
24136
|
+
if (!seenNames.has(name)) seenNames.add(name);
|
|
24137
|
+
out.push({ name, displayName: name, model: trimmed });
|
|
24138
|
+
}
|
|
24139
|
+
const fallback = fallbackModelForProvider(providerName, providerConfig);
|
|
24140
|
+
if (out.length === 0 && fallback) out.push({ name: fallback, displayName: fallback, model: fallback });
|
|
24141
|
+
return out;
|
|
24142
|
+
}
|
|
24143
|
+
function providerModelOptions(providerName, providerConfig) {
|
|
24144
|
+
return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => displayModelForProfile(profile));
|
|
24145
|
+
}
|
|
24146
|
+
function defaultModelForProvider(providerName, providerConfig) {
|
|
24147
|
+
return providerModelProfiles(providerName, providerConfig)[0]?.model ?? "";
|
|
24148
|
+
}
|
|
24149
|
+
function displayModelForProfile(profile) {
|
|
24150
|
+
return profile.displayName?.trim() || profile.name?.trim() || profile.model;
|
|
24151
|
+
}
|
|
24152
|
+
function defaultDisplayModelForProvider(providerName, providerConfig) {
|
|
24153
|
+
const profile = providerModelProfiles(providerName, providerConfig)[0];
|
|
24154
|
+
return profile ? displayModelForProfile(profile) : defaultModelForProvider(providerName, providerConfig);
|
|
24155
|
+
}
|
|
24156
|
+
function resolveConfiguredApiKey(providerConfig) {
|
|
24157
|
+
if (providerConfig.apiKey) return { value: providerConfig.apiKey };
|
|
24158
|
+
for (const envName of [providerConfig.apiKeyEnv, ...providerConfig.apiKeyEnvAliases ?? []]) {
|
|
24159
|
+
if (!envName) continue;
|
|
24160
|
+
const value = process.env[envName];
|
|
24161
|
+
if (value) return { value, envName };
|
|
24162
|
+
}
|
|
24163
|
+
return { envName: providerConfig.apiKeyEnv ?? providerConfig.apiKeyEnvAliases?.[0] };
|
|
24164
|
+
}
|
|
24165
|
+
function resolveProviderRuntimeConfig(config, selection) {
|
|
24166
|
+
const providerName = normalizeProviderAlias(selection.providerName);
|
|
24167
|
+
const providerConfig = resolveProviderConfig(config, providerName);
|
|
24168
|
+
const profiles = providerModelProfiles(providerName, providerConfig);
|
|
24169
|
+
let profile;
|
|
24170
|
+
if (selection.modelProfileName) profile = profiles.find((item) => item.name === selection.modelProfileName);
|
|
24171
|
+
if (!profile && selection.model) profile = profiles.find((item) => item.displayName === selection.model);
|
|
24172
|
+
if (!profile && selection.model) profile = profiles.find((item) => item.model === selection.model);
|
|
24173
|
+
if (!profile) profile = profiles[0];
|
|
24174
|
+
if (!profile) {
|
|
24175
|
+
const model = selection.model ?? defaultModelForProvider(providerName, providerConfig);
|
|
24176
|
+
if (!model) throw new Error(`Provider ${providerName} has no model. Run \`demian config models ${providerName} --refresh\` or add a model profile.`);
|
|
24177
|
+
return { providerName, providerConfig, model };
|
|
24178
|
+
}
|
|
24179
|
+
const merged = mergeModelProfile(providerConfig, profile);
|
|
24180
|
+
return {
|
|
24181
|
+
providerName,
|
|
24182
|
+
providerConfig: merged,
|
|
24183
|
+
model: profile.model,
|
|
24184
|
+
modelProfileName: profile.name
|
|
24185
|
+
};
|
|
24186
|
+
}
|
|
24187
|
+
function mergeModelProfile(providerConfig, profile) {
|
|
24188
|
+
const merged = {
|
|
24189
|
+
...providerConfig,
|
|
24190
|
+
...definedOnly({
|
|
24191
|
+
baseURL: profile.baseURL,
|
|
24192
|
+
apiKey: nonEmptyString(profile.apiKey),
|
|
24193
|
+
apiKeyEnv: profile.apiKeyEnv,
|
|
24194
|
+
apiKeyEnvAliases: profile.apiKeyEnvAliases,
|
|
24195
|
+
auth: profile.auth,
|
|
24196
|
+
headers: profile.headers,
|
|
24197
|
+
quirks: profile.quirks,
|
|
24198
|
+
maxTokens: profile.maxTokens
|
|
24199
|
+
}),
|
|
24200
|
+
model: profile.model
|
|
24201
|
+
};
|
|
24202
|
+
return merged;
|
|
24203
|
+
}
|
|
24204
|
+
function definedOnly(input2) {
|
|
24205
|
+
return Object.fromEntries(Object.entries(input2).filter(([, value]) => value !== void 0));
|
|
24206
|
+
}
|
|
24207
|
+
function nonEmptyString(value) {
|
|
24208
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
24209
|
+
}
|
|
24210
|
+
function fallbackModelForProvider(providerName, providerConfig) {
|
|
24211
|
+
if (typeof providerConfig.model === "string" && providerConfig.model.trim()) return providerConfig.model.trim();
|
|
24212
|
+
if (providerName === "openai") return "gpt-5.5";
|
|
24213
|
+
if (providerName === "anthropic") return CLAUDE_CODE_SONNET_MODEL;
|
|
24214
|
+
if (providerName === "codex" || providerConfig.type === "codex") return "gpt-5.5";
|
|
24215
|
+
if (providerName === "claudecode") return CLAUDE_CODE_SONNET_MODEL;
|
|
24216
|
+
if (providerName === "gemini") return "gemini-2.5-pro";
|
|
24217
|
+
if (providerName === "groq") return "openai/gpt-oss-120b";
|
|
24218
|
+
return void 0;
|
|
23949
24219
|
}
|
|
23950
24220
|
function migrateProviderConfig(provider, baseProvider) {
|
|
23951
24221
|
if (isLegacyClaudeCodeShape(provider)) {
|
|
@@ -24027,12 +24297,14 @@ function resolveAgentMode(config, flagMode) {
|
|
|
24027
24297
|
return "single-agent";
|
|
24028
24298
|
}
|
|
24029
24299
|
function resolveProviderConfig(config, providerName) {
|
|
24030
|
-
const
|
|
24300
|
+
const normalized = normalizeProviderAlias(providerName);
|
|
24301
|
+
const provider = config.providers[normalized];
|
|
24031
24302
|
if (!provider) throw new Error(`Unknown provider: ${providerName}`);
|
|
24032
24303
|
return provider;
|
|
24033
24304
|
}
|
|
24034
24305
|
function resolveProvider(providerConfig, options = {}) {
|
|
24035
24306
|
const model = options.model ?? providerConfig.model;
|
|
24307
|
+
if (!model) throw new Error("Provider has no selected model. Choose a model or add a model profile.");
|
|
24036
24308
|
if (providerConfig.type === "claudecode") {
|
|
24037
24309
|
throw new Error("claudecode is an external agent runtime. Use resolveExecutionBackend().");
|
|
24038
24310
|
}
|
|
@@ -24079,12 +24351,28 @@ function resolveProvider(providerConfig, options = {}) {
|
|
|
24079
24351
|
};
|
|
24080
24352
|
}
|
|
24081
24353
|
if (providerConfig.type === "anthropic") {
|
|
24082
|
-
const apiKey2 =
|
|
24354
|
+
const apiKey2 = resolveConfiguredApiKey(providerConfig).value;
|
|
24083
24355
|
if (!apiKey2) {
|
|
24084
24356
|
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
24085
24357
|
}
|
|
24086
24358
|
return {
|
|
24087
24359
|
provider: new AnthropicProvider({
|
|
24360
|
+
apiKey: apiKey2,
|
|
24361
|
+
baseURL: providerConfig.baseURL,
|
|
24362
|
+
defaultModel: model,
|
|
24363
|
+
onRetry: options.onRetry
|
|
24364
|
+
}),
|
|
24365
|
+
model
|
|
24366
|
+
};
|
|
24367
|
+
}
|
|
24368
|
+
if (providerConfig.type === "ollama") {
|
|
24369
|
+
const apiKey2 = resolveConfiguredApiKey(providerConfig).value;
|
|
24370
|
+
if (!apiKey2) {
|
|
24371
|
+
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
24372
|
+
}
|
|
24373
|
+
return {
|
|
24374
|
+
provider: new OllamaProvider({
|
|
24375
|
+
baseURL: providerConfig.baseURL,
|
|
24088
24376
|
apiKey: apiKey2,
|
|
24089
24377
|
defaultModel: model,
|
|
24090
24378
|
onRetry: options.onRetry
|
|
@@ -24092,7 +24380,7 @@ function resolveProvider(providerConfig, options = {}) {
|
|
|
24092
24380
|
model
|
|
24093
24381
|
};
|
|
24094
24382
|
}
|
|
24095
|
-
const apiKey =
|
|
24383
|
+
const apiKey = resolveConfiguredApiKey(providerConfig).value;
|
|
24096
24384
|
if (!apiKey) {
|
|
24097
24385
|
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
24098
24386
|
}
|
|
@@ -24111,6 +24399,7 @@ function resolveProvider(providerConfig, options = {}) {
|
|
|
24111
24399
|
}
|
|
24112
24400
|
function resolveExecutionBackend(providerConfig, options = {}) {
|
|
24113
24401
|
const model = options.model ?? providerConfig.model;
|
|
24402
|
+
if (!model) throw new Error("Provider has no selected model. Choose a model or add a model profile.");
|
|
24114
24403
|
if (providerConfig.type === "claudecode") {
|
|
24115
24404
|
const runtimeConfig = options.config || providerConfig.permissionProfile ? resolveClaudeCodeRuntimeConfig(providerConfig, options.config ?? defaultConfig, options.agent) : providerConfig;
|
|
24116
24405
|
return {
|
|
@@ -24152,31 +24441,1130 @@ function claudeCodeRuntimePolicyHash(config) {
|
|
|
24152
24441
|
}
|
|
24153
24442
|
var CLAUDE_CODE_PERMISSION_PROFILE_KEYS = ["permissionMode", "defaultDecision", "allowedTools", "disallowedTools", "tools", "allowSubagents"];
|
|
24154
24443
|
|
|
24155
|
-
// src/
|
|
24156
|
-
import {
|
|
24157
|
-
import {
|
|
24444
|
+
// src/config-command.ts
|
|
24445
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
24446
|
+
import { stat as stat4 } from "node:fs/promises";
|
|
24447
|
+
import os10 from "node:os";
|
|
24448
|
+
import path16 from "node:path";
|
|
24449
|
+
import readline3 from "node:readline/promises";
|
|
24450
|
+
|
|
24451
|
+
// src/config-scaffold.ts
|
|
24452
|
+
import { chmod as chmod3, mkdir as mkdir6, readFile as readFile7, rename as rename4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
|
|
24158
24453
|
import os8 from "node:os";
|
|
24159
|
-
import
|
|
24160
|
-
function
|
|
24161
|
-
return
|
|
24162
|
-
}
|
|
24163
|
-
async function maybeRunDoctorCommand(argv, options = {}) {
|
|
24164
|
-
if (!isDoctorCommand(argv)) return void 0;
|
|
24165
|
-
return runDoctorCommand(argv.slice(1), options);
|
|
24454
|
+
import path14 from "node:path";
|
|
24455
|
+
function defaultUserConfigPath() {
|
|
24456
|
+
return path14.join(os8.homedir(), ".demian", "config.json");
|
|
24166
24457
|
}
|
|
24167
|
-
|
|
24168
|
-
|
|
24169
|
-
|
|
24170
|
-
|
|
24171
|
-
|
|
24172
|
-
|
|
24173
|
-
|
|
24174
|
-
|
|
24175
|
-
|
|
24458
|
+
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
24459
|
+
return {
|
|
24460
|
+
version: 2,
|
|
24461
|
+
defaultProvider,
|
|
24462
|
+
providers: {
|
|
24463
|
+
openai: {
|
|
24464
|
+
type: "openai-compatible",
|
|
24465
|
+
baseURL: "https://api.openai.com/v1",
|
|
24466
|
+
apiKey: "",
|
|
24467
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
24468
|
+
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
24469
|
+
},
|
|
24470
|
+
anthropic: {
|
|
24471
|
+
type: "anthropic",
|
|
24472
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
24473
|
+
apiKey: "",
|
|
24474
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
24475
|
+
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
24476
|
+
},
|
|
24477
|
+
gemini: {
|
|
24478
|
+
type: "openai-compatible",
|
|
24479
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
24480
|
+
apiKey: "",
|
|
24481
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
24482
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
24483
|
+
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
24484
|
+
},
|
|
24485
|
+
groq: {
|
|
24486
|
+
type: "openai-compatible",
|
|
24487
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
24488
|
+
apiKey: "",
|
|
24489
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
24490
|
+
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
24491
|
+
},
|
|
24492
|
+
azure: {
|
|
24493
|
+
type: "openai-compatible",
|
|
24494
|
+
auth: { type: "api-key", header: "api-key" },
|
|
24495
|
+
modelProfiles: [
|
|
24496
|
+
{
|
|
24497
|
+
name: "azure-example",
|
|
24498
|
+
displayName: "Azure example",
|
|
24499
|
+
model: "azure-deployment-name",
|
|
24500
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
24501
|
+
apiKey: "",
|
|
24502
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
24503
|
+
}
|
|
24504
|
+
]
|
|
24505
|
+
},
|
|
24506
|
+
lmstudio: {
|
|
24507
|
+
type: "openai-compatible",
|
|
24508
|
+
baseURL: "http://localhost:1234/v1",
|
|
24509
|
+
apiKey: "lm-studio",
|
|
24510
|
+
modelProfiles: [],
|
|
24511
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
24512
|
+
},
|
|
24513
|
+
"ollama-local": {
|
|
24514
|
+
type: "openai-compatible",
|
|
24515
|
+
baseURL: "http://localhost:11434/v1",
|
|
24516
|
+
apiKey: "ollama",
|
|
24517
|
+
modelProfiles: [],
|
|
24518
|
+
quirks: { omitTemperature: true },
|
|
24519
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
24520
|
+
},
|
|
24521
|
+
"ollama-cloud": {
|
|
24522
|
+
type: "ollama",
|
|
24523
|
+
baseURL: "https://ollama.com/api",
|
|
24524
|
+
apiKey: "",
|
|
24525
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
24526
|
+
modelProfiles: [],
|
|
24527
|
+
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
24528
|
+
},
|
|
24529
|
+
llamacpp: {
|
|
24530
|
+
type: "openai-compatible",
|
|
24531
|
+
baseURL: "http://localhost:8080/v1",
|
|
24532
|
+
apiKey: "llama.cpp",
|
|
24533
|
+
modelProfiles: [],
|
|
24534
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
24535
|
+
},
|
|
24536
|
+
vllm: {
|
|
24537
|
+
type: "openai-compatible",
|
|
24538
|
+
baseURL: "http://localhost:8000/v1",
|
|
24539
|
+
apiKey: "vllm",
|
|
24540
|
+
modelProfiles: [],
|
|
24541
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
24542
|
+
},
|
|
24543
|
+
codex: {
|
|
24544
|
+
type: "codex",
|
|
24545
|
+
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
24546
|
+
authStore: "auto",
|
|
24547
|
+
allowApiKeyFallback: false,
|
|
24548
|
+
promptCacheKey: "root-session",
|
|
24549
|
+
catalog: { type: "codex-oauth-models" }
|
|
24550
|
+
},
|
|
24551
|
+
claudecode: {
|
|
24552
|
+
type: "claudecode",
|
|
24553
|
+
runtime: "agent-sdk",
|
|
24554
|
+
cliPath: "~/.local/bin/claude",
|
|
24555
|
+
cwdMode: "session",
|
|
24556
|
+
historyPolicy: "passthrough-resume",
|
|
24557
|
+
onInvalidResume: "fresh",
|
|
24558
|
+
attachmentFallback: "block",
|
|
24559
|
+
allowSubagents: false,
|
|
24560
|
+
sanitizeApiKeyEnv: true,
|
|
24561
|
+
authPreflight: true,
|
|
24562
|
+
useBareMode: false,
|
|
24563
|
+
usageLedgerScope: "process",
|
|
24564
|
+
sessionLock: true,
|
|
24565
|
+
abortPolicy: "record-only",
|
|
24566
|
+
catalog: { type: "claudecode-supported-models" }
|
|
24567
|
+
}
|
|
24568
|
+
}
|
|
24569
|
+
};
|
|
24570
|
+
}
|
|
24571
|
+
async function createUserConfig(options = {}) {
|
|
24572
|
+
const filePath = expandHome2(options.path ?? defaultUserConfigPath());
|
|
24573
|
+
const content = `${JSON.stringify(defaultUserConfig(options.defaultProvider), null, 2)}
|
|
24574
|
+
`;
|
|
24575
|
+
if (options.print) return { path: filePath, created: false, content };
|
|
24576
|
+
const existed = await exists(filePath);
|
|
24577
|
+
if (existed && !options.force) return { path: filePath, created: false, content: await readFile7(filePath, "utf8"), existed: true };
|
|
24578
|
+
await writeJsonAtomic(filePath, content);
|
|
24579
|
+
return { path: filePath, created: true, content, existed };
|
|
24580
|
+
}
|
|
24581
|
+
async function addProvider(options) {
|
|
24582
|
+
const filePath = expandHome2(options.path ?? defaultUserConfigPath());
|
|
24583
|
+
const config = await readConfigObject(filePath);
|
|
24584
|
+
const providers = objectValue(config.providers);
|
|
24585
|
+
const name = options.name;
|
|
24586
|
+
if (providers[name] && !options.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
24587
|
+
providers[name] = providerPreset(options);
|
|
24588
|
+
config.providers = providers;
|
|
24589
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
24590
|
+
`;
|
|
24591
|
+
await writeJsonAtomic(filePath, content);
|
|
24592
|
+
return { path: filePath, created: true, content };
|
|
24593
|
+
}
|
|
24594
|
+
async function addModelProfile(options) {
|
|
24595
|
+
const filePath = expandHome2(options.path ?? defaultUserConfigPath());
|
|
24596
|
+
const config = await readConfigObject(filePath);
|
|
24597
|
+
const providers = objectValue(config.providers);
|
|
24598
|
+
const provider = objectValue(providers[options.provider]);
|
|
24599
|
+
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
24600
|
+
const displayName = options.displayName ?? options.name;
|
|
24601
|
+
const existingByName = profiles.findIndex((entry) => entry.name === options.name);
|
|
24602
|
+
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
24603
|
+
if (existingByName >= 0 && !options.force) throw new Error(`Profile name "${options.name}" already exists on provider ${options.provider}. Use --force to overwrite it.`);
|
|
24604
|
+
if (existingByDisplay >= 0 && existingByDisplay !== existingByName && !options.force) {
|
|
24605
|
+
throw new Error(`Profile displayName "${displayName}" already exists on provider ${options.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
|
|
24606
|
+
}
|
|
24607
|
+
const next = {
|
|
24608
|
+
name: options.name,
|
|
24609
|
+
displayName,
|
|
24610
|
+
model: options.model,
|
|
24611
|
+
...options.baseURL ? { baseURL: options.baseURL } : {},
|
|
24612
|
+
...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
24613
|
+
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
|
|
24614
|
+
};
|
|
24615
|
+
if (existingByName >= 0) profiles[existingByName] = next;
|
|
24616
|
+
else profiles.push(next);
|
|
24617
|
+
provider.modelProfiles = profiles;
|
|
24618
|
+
providers[options.provider] = provider;
|
|
24619
|
+
config.providers = providers;
|
|
24620
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
24621
|
+
`;
|
|
24622
|
+
await writeJsonAtomic(filePath, content);
|
|
24623
|
+
return { path: filePath, created: true, content };
|
|
24624
|
+
}
|
|
24625
|
+
async function readConfigObject(filePath) {
|
|
24626
|
+
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
24627
|
+
const raw = JSON.parse(await readFile7(filePath, "utf8"));
|
|
24628
|
+
raw.version ??= 2;
|
|
24629
|
+
raw.providers = objectValue(raw.providers);
|
|
24630
|
+
return raw;
|
|
24631
|
+
}
|
|
24632
|
+
function providerPreset(options) {
|
|
24633
|
+
const preset = options.preset ?? options.name;
|
|
24634
|
+
const auth = apiKeyAuthFields(options);
|
|
24635
|
+
const openAIAuth = options.authHeader ? { auth: { type: "api-key", header: options.authHeader } } : {};
|
|
24636
|
+
if (preset === "openai") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
|
|
24637
|
+
if (preset === "anthropic") return { type: "anthropic", baseURL: options.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
|
|
24638
|
+
if (preset === "gemini") {
|
|
24639
|
+
const geminiBase = options.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
24640
|
+
return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
|
|
24641
|
+
}
|
|
24642
|
+
if (preset === "groq") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
|
|
24643
|
+
if (preset === "azure") {
|
|
24644
|
+
return {
|
|
24645
|
+
type: "openai-compatible",
|
|
24646
|
+
auth: { type: "api-key", header: options.authHeader ?? "api-key" },
|
|
24647
|
+
...auth,
|
|
24648
|
+
modelProfiles: [
|
|
24649
|
+
{
|
|
24650
|
+
name: "azure-example",
|
|
24651
|
+
displayName: "Azure example",
|
|
24652
|
+
model: "azure-deployment-name",
|
|
24653
|
+
baseURL: options.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
24654
|
+
...options.apiKey ? {} : { apiKey: "" },
|
|
24655
|
+
apiKeyEnv: options.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
24656
|
+
}
|
|
24657
|
+
]
|
|
24658
|
+
};
|
|
24659
|
+
}
|
|
24660
|
+
if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:1234/v1", apiKey: options.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
|
|
24661
|
+
if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:11434/v1", apiKey: options.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
|
|
24662
|
+
if (preset === "ollama-cloud") return { type: "ollama", baseURL: options.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
|
|
24663
|
+
if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8080/v1", apiKey: options.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
|
|
24664
|
+
if (preset === "vllm") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8000/v1", apiKey: options.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
|
|
24665
|
+
if (preset === "codex") return { type: "codex", baseURL: options.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
|
|
24666
|
+
if (preset === "claudecode") return { type: "claudecode", runtime: "agent-sdk", cliPath: "~/.local/bin/claude", cwdMode: "session", historyPolicy: "passthrough-resume", onInvalidResume: "fresh", attachmentFallback: "block", allowSubagents: false, sanitizeApiKeyEnv: true, authPreflight: true, useBareMode: false, usageLedgerScope: "process", sessionLock: true, abortPolicy: "record-only", catalog: { type: "claudecode-supported-models" } };
|
|
24667
|
+
if ((options.type ?? preset) === "openai-compatible") {
|
|
24668
|
+
const baseURL = options.baseURL;
|
|
24669
|
+
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
24670
|
+
return {
|
|
24671
|
+
type: "openai-compatible",
|
|
24672
|
+
baseURL,
|
|
24673
|
+
...auth,
|
|
24674
|
+
...openAIAuth,
|
|
24675
|
+
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
24676
|
+
};
|
|
24677
|
+
}
|
|
24678
|
+
throw new Error(`Unknown provider preset: ${preset}`);
|
|
24679
|
+
}
|
|
24680
|
+
function apiKeyAuthFields(options, defaultApiKeyEnv) {
|
|
24681
|
+
const apiKeyEnv = options.apiKeyEnv ?? defaultApiKeyEnv;
|
|
24682
|
+
return {
|
|
24683
|
+
...options.apiKey !== void 0 || apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
24684
|
+
...apiKeyEnv ? { apiKeyEnv } : {}
|
|
24685
|
+
};
|
|
24686
|
+
}
|
|
24687
|
+
async function writeJsonAtomic(filePath, content) {
|
|
24688
|
+
await mkdir6(path14.dirname(filePath), { recursive: true, mode: 448 });
|
|
24689
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
24690
|
+
await writeFile5(temp, content, { mode: 384 });
|
|
24691
|
+
await rename4(temp, filePath);
|
|
24692
|
+
await chmod3(filePath, 384).catch(() => void 0);
|
|
24693
|
+
}
|
|
24694
|
+
function detectDefaultProvider() {
|
|
24695
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
24696
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
24697
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
24698
|
+
if (process.env.GROQ_API_KEY) return "groq";
|
|
24699
|
+
return "openai";
|
|
24700
|
+
}
|
|
24701
|
+
function objectValue(value) {
|
|
24702
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
24703
|
+
}
|
|
24704
|
+
async function exists(filePath) {
|
|
24705
|
+
try {
|
|
24706
|
+
await stat3(filePath);
|
|
24707
|
+
return true;
|
|
24708
|
+
} catch {
|
|
24709
|
+
return false;
|
|
24710
|
+
}
|
|
24711
|
+
}
|
|
24712
|
+
function expandHome2(value) {
|
|
24713
|
+
return resolveExpandedPath(value);
|
|
24714
|
+
}
|
|
24715
|
+
|
|
24716
|
+
// src/models/catalog.ts
|
|
24717
|
+
import crypto5 from "node:crypto";
|
|
24718
|
+
import { mkdir as mkdir7, readFile as readFile8, rename as rename5, writeFile as writeFile6 } from "node:fs/promises";
|
|
24719
|
+
import os9 from "node:os";
|
|
24720
|
+
import path15 from "node:path";
|
|
24721
|
+
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
24722
|
+
var PING_TIMEOUT_MS = 1500;
|
|
24723
|
+
var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
|
|
24724
|
+
var dynamicImport2 = new Function("specifier", "return import(specifier)");
|
|
24725
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
24726
|
+
var pingCache = /* @__PURE__ */ new Map();
|
|
24727
|
+
var PING_CACHE_TTL_MS = 30 * 1e3;
|
|
24728
|
+
var packageVersionPromise;
|
|
24729
|
+
async function listProviderModelCatalog(providerName, providerConfig, options = {}) {
|
|
24730
|
+
const key = `${catalogCacheKey(providerName, providerConfig)}:${options.refresh ? "refresh" : "default"}`;
|
|
24731
|
+
const existing = inflight.get(key);
|
|
24732
|
+
if (existing) return existing;
|
|
24733
|
+
const promise = doListProviderModelCatalog(providerName, providerConfig, options).finally(() => inflight.delete(key));
|
|
24734
|
+
inflight.set(key, promise);
|
|
24735
|
+
return promise;
|
|
24736
|
+
}
|
|
24737
|
+
async function doListProviderModelCatalog(providerName, providerConfig, options) {
|
|
24738
|
+
const staticModels = staticModelEntries(providerName, providerConfig);
|
|
24739
|
+
const catalog = providerConfig.catalog;
|
|
24740
|
+
if (!catalog || catalog.type === "static") {
|
|
24741
|
+
return { status: staticModels.length ? "ready" : "unavailable", providerName, models: staticModels, source: "config" };
|
|
24742
|
+
}
|
|
24743
|
+
const missingAuthEnv = missingCatalogAuth(providerConfig);
|
|
24744
|
+
if (missingAuthEnv) {
|
|
24745
|
+
return {
|
|
24746
|
+
status: "missing-auth",
|
|
24747
|
+
providerName,
|
|
24748
|
+
models: staticModels,
|
|
24749
|
+
source: staticModels.length ? "config" : "fallback",
|
|
24750
|
+
message: `missing-auth:${missingAuthEnv}`
|
|
24751
|
+
};
|
|
24752
|
+
}
|
|
24753
|
+
const cacheKey = catalogCacheKey(providerName, providerConfig);
|
|
24754
|
+
const ttlMs = catalog.refreshTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
24755
|
+
const cached = await readCatalogCache(cacheKey, ttlMs, options.refresh !== true);
|
|
24756
|
+
if (cached && !options.refresh) return { ...cached, models: mergeCatalogWithStatic(staticModels, cached.models) };
|
|
24757
|
+
if (catalog.endpoint) {
|
|
24758
|
+
const reachable = await pingEndpoint(catalog.endpoint, options.fetch);
|
|
24759
|
+
if (!reachable) {
|
|
24760
|
+
const stale = await readCatalogCache(cacheKey, Number.POSITIVE_INFINITY, true);
|
|
24761
|
+
if (stale) return { ...stale, models: mergeCatalogWithStatic(staticModels, stale.models), message: `endpoint ${catalog.endpoint} unreachable; using cached models` };
|
|
24762
|
+
if (staticModels.length) return { status: "ready", providerName, models: staticModels, source: "config", message: `endpoint ${catalog.endpoint} unreachable; using configured profiles` };
|
|
24763
|
+
return { status: "unavailable", providerName, models: [], source: "fallback", message: `endpoint ${catalog.endpoint} unreachable` };
|
|
24764
|
+
}
|
|
24765
|
+
}
|
|
24766
|
+
try {
|
|
24767
|
+
const remote = await fetchCatalog(providerName, providerConfig, options);
|
|
24768
|
+
const merged = mergeCatalogWithStatic(staticModels, remote.models);
|
|
24769
|
+
const result = { ...remote, models: merged };
|
|
24770
|
+
if (result.status === "ready") await writeCatalogCache(cacheKey, result);
|
|
24771
|
+
return result;
|
|
24772
|
+
} catch (error) {
|
|
24773
|
+
const authMissing = isMissingAuthError(error);
|
|
24774
|
+
const stale = await readCatalogCache(cacheKey, Number.POSITIVE_INFINITY, true);
|
|
24775
|
+
if (stale) return { ...stale, status: authMissing ? "missing-auth" : stale.status, models: mergeCatalogWithStatic(staticModels, stale.models), message: errorMessage2(error) };
|
|
24776
|
+
if (staticModels.length) return { status: authMissing ? "missing-auth" : "ready", providerName, models: staticModels, source: "config", message: errorMessage2(error) };
|
|
24777
|
+
return { status: authMissing ? "missing-auth" : "unavailable", providerName, models: [], source: "fallback", message: errorMessage2(error) };
|
|
24778
|
+
}
|
|
24779
|
+
}
|
|
24780
|
+
async function pingEndpoint(endpoint, fetcher) {
|
|
24781
|
+
const now2 = Date.now();
|
|
24782
|
+
const cached = pingCache.get(endpoint);
|
|
24783
|
+
if (cached && cached.expiresAt > now2) return cached.ok;
|
|
24784
|
+
const ok2 = await tryPing(endpoint, fetcher);
|
|
24785
|
+
pingCache.set(endpoint, { ok: ok2, expiresAt: now2 + PING_CACHE_TTL_MS });
|
|
24786
|
+
return ok2;
|
|
24787
|
+
}
|
|
24788
|
+
async function tryPing(endpoint, fetcher) {
|
|
24789
|
+
const fn = fetcher ?? fetch;
|
|
24790
|
+
try {
|
|
24791
|
+
const response = await fn(endpoint, { method: "HEAD", signal: AbortSignal.timeout(PING_TIMEOUT_MS) });
|
|
24792
|
+
if (response.status < 500) return true;
|
|
24793
|
+
} catch {
|
|
24794
|
+
}
|
|
24795
|
+
try {
|
|
24796
|
+
const response = await fn(endpoint, { method: "GET", signal: AbortSignal.timeout(PING_TIMEOUT_MS), headers: { range: "bytes=0-0" } });
|
|
24797
|
+
return response.status < 500;
|
|
24798
|
+
} catch {
|
|
24799
|
+
return false;
|
|
24800
|
+
}
|
|
24801
|
+
}
|
|
24802
|
+
async function fetchCatalog(providerName, providerConfig, options) {
|
|
24803
|
+
if (providerConfig.type === "codex" || providerConfig.catalog?.type === "codex-oauth-models") return fetchCodexCatalog(providerName, providerConfig, options);
|
|
24804
|
+
if (providerConfig.type === "claudecode" || providerConfig.catalog?.type === "claudecode-supported-models") return fetchClaudeCodeCatalog(providerName, providerConfig, options);
|
|
24805
|
+
if (providerConfig.catalog?.type === "anthropic-models") return fetchAnthropicCatalog(providerName, providerConfig, options);
|
|
24806
|
+
if (providerConfig.catalog?.type === "ollama-tags") return fetchOllamaTagsCatalog(providerName, providerConfig, options);
|
|
24807
|
+
return fetchOpenAIStyleCatalog(providerName, providerConfig, options);
|
|
24808
|
+
}
|
|
24809
|
+
async function fetchOpenAIStyleCatalog(providerName, providerConfig, options) {
|
|
24810
|
+
if (providerConfig.type !== "openai-compatible") throw new Error(`Provider ${providerName} is not OpenAI-compatible.`);
|
|
24811
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
24812
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
24813
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${providerConfig.baseURL.replace(/\/+$/, "")}/models`;
|
|
24814
|
+
const response = await fetchJson(endpoint, {
|
|
24815
|
+
fetcher: options.fetch,
|
|
24816
|
+
timeoutMs: options.timeoutMs,
|
|
24817
|
+
headers: {
|
|
24818
|
+
...authHeadersForOpenAICompatible(providerConfig, apiKey.value),
|
|
24819
|
+
...providerConfig.headers ?? {}
|
|
24820
|
+
}
|
|
24821
|
+
});
|
|
24822
|
+
return {
|
|
24823
|
+
status: "ready",
|
|
24824
|
+
providerName,
|
|
24825
|
+
models: normalizeOpenAIModels(response).map((entry) => ({ ...entry, source: "api" })),
|
|
24826
|
+
source: "api"
|
|
24827
|
+
};
|
|
24828
|
+
}
|
|
24829
|
+
async function fetchAnthropicCatalog(providerName, providerConfig, options) {
|
|
24830
|
+
if (providerConfig.type !== "anthropic") throw new Error(`Provider ${providerName} is not Anthropic.`);
|
|
24831
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
24832
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
24833
|
+
const base = providerConfig.baseURL ?? "https://api.anthropic.com/v1";
|
|
24834
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${base.replace(/\/+$/, "")}/models`;
|
|
24835
|
+
const models = [];
|
|
24836
|
+
let afterId;
|
|
24837
|
+
for (; ; ) {
|
|
24838
|
+
const url = new URL(endpoint);
|
|
24839
|
+
if (afterId) url.searchParams.set("after_id", afterId);
|
|
24840
|
+
url.searchParams.set("limit", "100");
|
|
24841
|
+
const response = await fetchJson(url.toString(), {
|
|
24842
|
+
fetcher: options.fetch,
|
|
24843
|
+
timeoutMs: options.timeoutMs,
|
|
24844
|
+
headers: {
|
|
24845
|
+
"x-api-key": apiKey.value,
|
|
24846
|
+
"anthropic-version": "2023-06-01"
|
|
24847
|
+
}
|
|
24848
|
+
});
|
|
24849
|
+
const page = normalizeAnthropicModels(response);
|
|
24850
|
+
models.push(...page);
|
|
24851
|
+
if (!response.has_more || !response.last_id) break;
|
|
24852
|
+
afterId = response.last_id;
|
|
24853
|
+
}
|
|
24854
|
+
return { status: "ready", providerName, models, source: "api" };
|
|
24855
|
+
}
|
|
24856
|
+
async function fetchOllamaTagsCatalog(providerName, providerConfig, options) {
|
|
24857
|
+
if (providerConfig.type !== "ollama") throw new Error(`Provider ${providerName} is not Ollama native.`);
|
|
24858
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
24859
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
24860
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${providerConfig.baseURL.replace(/\/+$/, "")}/tags`;
|
|
24861
|
+
const response = await fetchJson(endpoint, {
|
|
24862
|
+
fetcher: options.fetch,
|
|
24863
|
+
timeoutMs: options.timeoutMs,
|
|
24864
|
+
headers: { authorization: `Bearer ${apiKey.value}` }
|
|
24865
|
+
});
|
|
24866
|
+
return {
|
|
24867
|
+
status: "ready",
|
|
24868
|
+
providerName,
|
|
24869
|
+
models: normalizeOllamaTags(response).map((entry) => ({ ...entry, source: "api" })),
|
|
24870
|
+
source: "api"
|
|
24871
|
+
};
|
|
24872
|
+
}
|
|
24873
|
+
async function fetchCodexCatalog(providerName, providerConfig, options) {
|
|
24874
|
+
if (providerConfig.type !== "codex") throw new Error(`Provider ${providerName} is not Codex.`);
|
|
24875
|
+
const fetcher = options.fetch ?? fetch;
|
|
24876
|
+
const baseURL = (providerConfig.baseURL ?? "https://chatgpt.com/backend-api/codex").replace(/\/+$/, "");
|
|
24877
|
+
const installationId = await loadOrCreateInstallationId(resolveCodexHome(providerConfig.codexHome));
|
|
24878
|
+
const auth = getSharedCodexAuthStore({
|
|
24879
|
+
codexHome: providerConfig.codexHome,
|
|
24880
|
+
authStore: providerConfig.authStore,
|
|
24881
|
+
allowApiKeyFallback: providerConfig.allowApiKeyFallback,
|
|
24882
|
+
proactiveRefreshMinutes: providerConfig.refresh?.proactiveRefreshMinutes,
|
|
24883
|
+
refreshCache: providerConfig.refresh?.cache,
|
|
24884
|
+
fetch: fetcher
|
|
24885
|
+
});
|
|
24886
|
+
const headers = await auth.requestHeaders(installationId);
|
|
24887
|
+
const url = new URL(`${baseURL}/models`);
|
|
24888
|
+
url.searchParams.set("client_version", await codexClientVersion(options.clientVersion));
|
|
24889
|
+
const response = await fetchJson(url.toString(), {
|
|
24890
|
+
fetcher,
|
|
24891
|
+
timeoutMs: options.timeoutMs,
|
|
24892
|
+
headers
|
|
24893
|
+
});
|
|
24894
|
+
return {
|
|
24895
|
+
status: "ready",
|
|
24896
|
+
providerName,
|
|
24897
|
+
models: normalizeCodexModels(response).map((entry) => ({ ...entry, source: "oauth" })),
|
|
24898
|
+
source: "oauth"
|
|
24899
|
+
};
|
|
24900
|
+
}
|
|
24901
|
+
async function fetchClaudeCodeCatalog(providerName, providerConfig, options) {
|
|
24902
|
+
if (providerConfig.type !== "claudecode") throw new Error(`Provider ${providerName} is not Claude Code.`);
|
|
24903
|
+
const fallback = staticModelEntries(providerName, providerConfig);
|
|
24904
|
+
try {
|
|
24905
|
+
const module = await dynamicImport2("@anthropic-ai/claude-agent-sdk");
|
|
24906
|
+
if (!module.query) throw new Error("Claude Agent SDK query export is unavailable.");
|
|
24907
|
+
const query = module.query({
|
|
24908
|
+
prompt: "",
|
|
24909
|
+
options: {
|
|
24910
|
+
model: defaultModelForProvider(providerName, providerConfig),
|
|
24911
|
+
maxTurns: 0,
|
|
24912
|
+
pathToClaudeCodeExecutable: resolveClaudeCodeCliPath(providerConfig.cliPath),
|
|
24913
|
+
env: sanitizedClaudeCodeEnv(providerConfig.env, providerConfig.sanitizeApiKeyEnv ?? true)
|
|
24914
|
+
}
|
|
24915
|
+
});
|
|
24916
|
+
try {
|
|
24917
|
+
if (typeof query.supportedModels !== "function") throw new Error("Claude Agent SDK supportedModels is unavailable.");
|
|
24918
|
+
const models = await withTimeout(query.supportedModels(), options.timeoutMs ?? 5e3);
|
|
24919
|
+
return {
|
|
24920
|
+
status: "ready",
|
|
24921
|
+
providerName,
|
|
24922
|
+
models: models.map((model, index) => ({
|
|
24923
|
+
name: model.displayName || model.value,
|
|
24924
|
+
displayName: model.displayName || model.value,
|
|
24925
|
+
model: model.value,
|
|
24926
|
+
description: model.description,
|
|
24927
|
+
isDefault: index === 0,
|
|
24928
|
+
source: "oauth"
|
|
24929
|
+
})),
|
|
24930
|
+
source: "oauth"
|
|
24931
|
+
};
|
|
24932
|
+
} finally {
|
|
24933
|
+
query.close?.();
|
|
24934
|
+
}
|
|
24935
|
+
} catch (error) {
|
|
24936
|
+
if (fallback.length) return { status: "ready", providerName, models: fallback, source: "fallback", message: errorMessage2(error) };
|
|
24937
|
+
throw error;
|
|
24938
|
+
}
|
|
24939
|
+
}
|
|
24940
|
+
function staticModelEntries(providerName, providerConfig) {
|
|
24941
|
+
return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile, index) => ({
|
|
24942
|
+
name: profile.displayName ?? profile.name,
|
|
24943
|
+
displayName: profile.displayName ?? profile.name,
|
|
24944
|
+
model: profile.model,
|
|
24945
|
+
description: profile.description,
|
|
24946
|
+
isDefault: index === 0,
|
|
24947
|
+
source: "config"
|
|
24948
|
+
}));
|
|
24949
|
+
}
|
|
24950
|
+
function mergeCatalogWithStatic(staticModels, remoteModels) {
|
|
24951
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
24952
|
+
for (const model of remoteModels) byModel.set(model.model, model);
|
|
24953
|
+
for (const model of staticModels) byModel.set(model.model, { ...byModel.get(model.model), ...model, source: model.source });
|
|
24954
|
+
return [...byModel.values()];
|
|
24955
|
+
}
|
|
24956
|
+
function normalizeOpenAIModels(raw) {
|
|
24957
|
+
const items = Array.isArray(raw.data) ? raw.data : Array.isArray(raw.models) ? raw.models : [];
|
|
24958
|
+
return items.map((item) => {
|
|
24959
|
+
const object2 = item;
|
|
24960
|
+
const id = stringValue4(object2.id) ?? stringValue4(object2.name) ?? stringValue4(object2.model);
|
|
24961
|
+
if (!id) return void 0;
|
|
24962
|
+
return {
|
|
24963
|
+
name: id,
|
|
24964
|
+
displayName: stringValue4(object2.display_name) ?? stringValue4(object2.displayName) ?? id,
|
|
24965
|
+
model: id,
|
|
24966
|
+
description: stringValue4(object2.description)
|
|
24967
|
+
};
|
|
24968
|
+
}).filter(Boolean);
|
|
24969
|
+
}
|
|
24970
|
+
function normalizeAnthropicModels(raw) {
|
|
24971
|
+
return (raw.data ?? []).map((item) => {
|
|
24972
|
+
const object2 = item;
|
|
24973
|
+
const id = stringValue4(object2.id);
|
|
24974
|
+
if (!id) return void 0;
|
|
24975
|
+
return {
|
|
24976
|
+
name: stringValue4(object2.display_name) ?? id,
|
|
24977
|
+
displayName: stringValue4(object2.display_name) ?? id,
|
|
24978
|
+
model: id,
|
|
24979
|
+
description: stringValue4(object2.description),
|
|
24980
|
+
source: "api"
|
|
24981
|
+
};
|
|
24982
|
+
}).filter(Boolean);
|
|
24983
|
+
}
|
|
24984
|
+
function normalizeOllamaTags(raw) {
|
|
24985
|
+
const items = Array.isArray(raw.models) ? raw.models : [];
|
|
24986
|
+
return items.map((item) => {
|
|
24987
|
+
const object2 = item;
|
|
24988
|
+
const id = stringValue4(object2.name) ?? stringValue4(object2.model);
|
|
24989
|
+
if (!id) return void 0;
|
|
24990
|
+
return { name: id, displayName: id, model: id };
|
|
24991
|
+
}).filter(Boolean);
|
|
24992
|
+
}
|
|
24993
|
+
function normalizeCodexModels(raw) {
|
|
24994
|
+
const items = Array.isArray(raw.models) ? raw.models : [];
|
|
24995
|
+
return items.map((item, index) => {
|
|
24996
|
+
const object2 = item;
|
|
24997
|
+
const id = stringValue4(object2.slug) ?? stringValue4(object2.id) ?? stringValue4(object2.model);
|
|
24998
|
+
if (!id) return void 0;
|
|
24999
|
+
return {
|
|
25000
|
+
name: stringValue4(object2.display_name) ?? id,
|
|
25001
|
+
displayName: stringValue4(object2.display_name) ?? id,
|
|
25002
|
+
model: id,
|
|
25003
|
+
description: stringValue4(object2.description),
|
|
25004
|
+
isDefault: index === 0
|
|
25005
|
+
};
|
|
25006
|
+
}).filter(Boolean);
|
|
25007
|
+
}
|
|
25008
|
+
function authHeadersForOpenAICompatible(providerConfig, apiKey) {
|
|
25009
|
+
const auth = inferOpenAICompatibleAuth(providerConfig.baseURL, providerConfig.auth);
|
|
25010
|
+
const type = auth.type ?? "bearer";
|
|
25011
|
+
const header = auth.header ?? (type === "api-key" ? "api-key" : "authorization");
|
|
25012
|
+
return { [header]: type === "bearer" ? `Bearer ${apiKey}` : apiKey };
|
|
25013
|
+
}
|
|
25014
|
+
async function fetchJson(url, options) {
|
|
25015
|
+
const response = await (options.fetcher ?? fetch)(url, {
|
|
25016
|
+
method: "GET",
|
|
25017
|
+
headers: {
|
|
25018
|
+
accept: "application/json",
|
|
25019
|
+
...options.headers ?? {}
|
|
25020
|
+
},
|
|
25021
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? 5e3)
|
|
25022
|
+
});
|
|
25023
|
+
const text = await response.text();
|
|
25024
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
|
|
25025
|
+
return text ? JSON.parse(text) : {};
|
|
25026
|
+
}
|
|
25027
|
+
function cachePath(cacheKey) {
|
|
25028
|
+
return path15.join(os9.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
|
|
25029
|
+
}
|
|
25030
|
+
async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
25031
|
+
if (!allow) return void 0;
|
|
25032
|
+
try {
|
|
25033
|
+
const raw = JSON.parse(await readFile8(cachePath(cacheKey), "utf8"));
|
|
25034
|
+
if (!raw.result) return void 0;
|
|
25035
|
+
if (ttlMs !== Number.POSITIVE_INFINITY && Date.now() - (raw.savedAt ?? 0) > ttlMs) return void 0;
|
|
25036
|
+
return { ...raw.result, source: "cache", models: raw.result.models.map((model) => ({ ...model, source: "cache" })) };
|
|
25037
|
+
} catch {
|
|
25038
|
+
return void 0;
|
|
25039
|
+
}
|
|
25040
|
+
}
|
|
25041
|
+
async function writeCatalogCache(cacheKey, result) {
|
|
25042
|
+
const filePath = cachePath(cacheKey);
|
|
25043
|
+
await mkdir7(path15.dirname(filePath), { recursive: true, mode: 448 });
|
|
25044
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
25045
|
+
await writeFile6(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
|
|
25046
|
+
await rename5(temp, filePath);
|
|
25047
|
+
}
|
|
25048
|
+
function safeCacheName(providerName) {
|
|
25049
|
+
return providerName.replace(/[^a-zA-Z0-9_.-]+/g, "_");
|
|
25050
|
+
}
|
|
25051
|
+
function catalogCacheKey(providerName, providerConfig) {
|
|
25052
|
+
const identity = {
|
|
25053
|
+
providerName,
|
|
25054
|
+
type: providerConfig.type,
|
|
25055
|
+
catalogType: providerConfig.catalog?.type,
|
|
25056
|
+
endpoint: providerConfig.catalog?.endpoint,
|
|
25057
|
+
baseURL: "baseURL" in providerConfig ? providerConfig.baseURL : void 0,
|
|
25058
|
+
apiKeyEnv: "apiKeyEnv" in providerConfig ? providerConfig.apiKeyEnv : void 0,
|
|
25059
|
+
apiKeyEnvAliases: "apiKeyEnvAliases" in providerConfig ? providerConfig.apiKeyEnvAliases : void 0,
|
|
25060
|
+
auth: "auth" in providerConfig ? providerConfig.auth : void 0
|
|
25061
|
+
};
|
|
25062
|
+
const digest = crypto5.createHash("sha256").update(JSON.stringify(identity)).digest("hex").slice(0, 16);
|
|
25063
|
+
return `${providerName}-${digest}`;
|
|
25064
|
+
}
|
|
25065
|
+
function missingCatalogAuth(providerConfig) {
|
|
25066
|
+
if (providerConfig.type !== "openai-compatible" && providerConfig.type !== "anthropic" && providerConfig.type !== "ollama") return void 0;
|
|
25067
|
+
if (providerConfig.catalog?.requiresAuth === false) return void 0;
|
|
25068
|
+
const expectsAuth = providerConfig.catalog?.requiresAuth === true || !!providerConfig.apiKey || !!providerConfig.apiKeyEnv || !!providerConfig.apiKeyEnvAliases?.length;
|
|
25069
|
+
if (!expectsAuth) return void 0;
|
|
25070
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
25071
|
+
return apiKey.value ? void 0 : apiKey.envName ?? "apiKey";
|
|
25072
|
+
}
|
|
25073
|
+
function stringValue4(value) {
|
|
25074
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
25075
|
+
}
|
|
25076
|
+
function missingAuth(envName) {
|
|
25077
|
+
const error = new Error(`missing-auth:${envName}`);
|
|
25078
|
+
return error;
|
|
25079
|
+
}
|
|
25080
|
+
function isMissingAuthError(error) {
|
|
25081
|
+
if (error instanceof CodexAuthError) {
|
|
25082
|
+
return error.code === "missing_auth" || error.code === "missing_chatgpt_tokens" || error.code === "refresh_unavailable" || error.code === "keyring_write_unsupported" || error.code === "refresh_expired" || error.code === "refresh_invalidated" || error.code === "refresh_reused" || error.code === "refresh_other";
|
|
25083
|
+
}
|
|
25084
|
+
return error instanceof Error && error.message.startsWith("missing-auth:");
|
|
25085
|
+
}
|
|
25086
|
+
function errorMessage2(error) {
|
|
25087
|
+
return error instanceof Error ? error.message : String(error);
|
|
25088
|
+
}
|
|
25089
|
+
async function codexClientVersion(override) {
|
|
25090
|
+
const normalized = semverLike(override);
|
|
25091
|
+
if (normalized) return normalized;
|
|
25092
|
+
packageVersionPromise ??= readPackageVersion();
|
|
25093
|
+
return packageVersionPromise;
|
|
25094
|
+
}
|
|
25095
|
+
async function readPackageVersion() {
|
|
25096
|
+
try {
|
|
25097
|
+
const raw = JSON.parse(await readFile8(new URL("../package.json", import.meta.url), "utf8"));
|
|
25098
|
+
return semverLike(raw.version) ?? DEFAULT_CODEX_CLIENT_VERSION;
|
|
25099
|
+
} catch {
|
|
25100
|
+
return DEFAULT_CODEX_CLIENT_VERSION;
|
|
25101
|
+
}
|
|
25102
|
+
}
|
|
25103
|
+
function semverLike(value) {
|
|
25104
|
+
if (typeof value !== "string") return void 0;
|
|
25105
|
+
const trimmed = value.trim();
|
|
25106
|
+
return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(trimmed) ? trimmed : void 0;
|
|
25107
|
+
}
|
|
25108
|
+
function withTimeout(promise, timeoutMs) {
|
|
25109
|
+
return new Promise((resolve, reject) => {
|
|
25110
|
+
const timer = setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
25111
|
+
promise.then(
|
|
25112
|
+
(value) => {
|
|
25113
|
+
clearTimeout(timer);
|
|
25114
|
+
resolve(value);
|
|
25115
|
+
},
|
|
25116
|
+
(error) => {
|
|
25117
|
+
clearTimeout(timer);
|
|
25118
|
+
reject(error);
|
|
25119
|
+
}
|
|
25120
|
+
);
|
|
25121
|
+
});
|
|
25122
|
+
}
|
|
25123
|
+
|
|
25124
|
+
// src/config-command.ts
|
|
25125
|
+
async function maybeRunConfigCommand(argv) {
|
|
25126
|
+
if (argv[0] === "config") return runConfigCommand(argv.slice(1));
|
|
25127
|
+
if (argv[0] === "auth") return runAuthCommand(argv.slice(1));
|
|
25128
|
+
return void 0;
|
|
25129
|
+
}
|
|
25130
|
+
async function runConfigCommand(argv) {
|
|
25131
|
+
const [command, ...rest] = argv;
|
|
25132
|
+
try {
|
|
25133
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
25134
|
+
printConfigHelp();
|
|
25135
|
+
return 0;
|
|
25136
|
+
}
|
|
25137
|
+
if (command === "init") {
|
|
25138
|
+
const flags = parseFlags(rest);
|
|
25139
|
+
const force = boolFlag(flags, "force") || await confirmOverwriteIfExists(stringFlag(flags, "path"), boolFlag(flags, "print"));
|
|
25140
|
+
const result = await createUserConfig({
|
|
25141
|
+
path: stringFlag(flags, "path"),
|
|
25142
|
+
force,
|
|
25143
|
+
print: boolFlag(flags, "print"),
|
|
25144
|
+
defaultProvider: stringFlag(flags, "provider")
|
|
25145
|
+
});
|
|
25146
|
+
if (boolFlag(flags, "print")) {
|
|
25147
|
+
process.stdout.write(result.content);
|
|
25148
|
+
} else if (result.existed && !result.created) {
|
|
25149
|
+
process.stdout.write(`Config already exists: ${result.path}
|
|
25150
|
+
`);
|
|
25151
|
+
} else {
|
|
25152
|
+
process.stdout.write(`Created config: ${result.path}
|
|
25153
|
+
`);
|
|
25154
|
+
}
|
|
25155
|
+
return 0;
|
|
25156
|
+
}
|
|
25157
|
+
if (command === "path") {
|
|
25158
|
+
process.stdout.write(`${defaultUserConfigPath()}
|
|
25159
|
+
`);
|
|
25160
|
+
return 0;
|
|
25161
|
+
}
|
|
25162
|
+
if (command === "setup") {
|
|
25163
|
+
return runSetupWizard();
|
|
25164
|
+
}
|
|
25165
|
+
if (command === "list") {
|
|
25166
|
+
const flags = parseFlags(rest);
|
|
25167
|
+
const config = await loadConfig({ cwd: process.cwd(), configPath: stringFlag(flags, "config") });
|
|
25168
|
+
process.stdout.write(`defaultProvider: ${config.defaultProvider}
|
|
25169
|
+
`);
|
|
25170
|
+
for (const name of Object.keys(config.providers)) {
|
|
25171
|
+
const provider = config.providers[name];
|
|
25172
|
+
if (provider.hidden) continue;
|
|
25173
|
+
process.stdout.write(`- ${name} (${provider.type})
|
|
25174
|
+
`);
|
|
25175
|
+
}
|
|
25176
|
+
return 0;
|
|
25177
|
+
}
|
|
25178
|
+
if (command === "models") {
|
|
25179
|
+
const providerName = rest.find((item) => !item.startsWith("-"));
|
|
25180
|
+
if (!providerName) throw new Error("config models requires a provider name.");
|
|
25181
|
+
const flags = parseFlags(rest.filter((item) => item !== providerName));
|
|
25182
|
+
const config = await loadConfig({ cwd: process.cwd(), configPath: stringFlag(flags, "config") });
|
|
25183
|
+
const provider = resolveProviderConfig(config, providerName);
|
|
25184
|
+
const result = await listProviderModelCatalog(providerName, provider, { refresh: boolFlag(flags, "refresh") });
|
|
25185
|
+
if (result.status !== "ready") {
|
|
25186
|
+
process.stdout.write(`${providerName}: ${result.status}${result.message ? ` (${result.message})` : ""}
|
|
25187
|
+
`);
|
|
25188
|
+
return result.status === "missing-auth" ? 2 : 1;
|
|
25189
|
+
}
|
|
25190
|
+
for (const model of result.models) {
|
|
25191
|
+
const label = model.displayName && model.displayName !== model.model ? `${model.displayName} (${model.model})` : model.model;
|
|
25192
|
+
process.stdout.write(`${model.isDefault ? "* " : " "}${label}
|
|
25193
|
+
`);
|
|
25194
|
+
}
|
|
25195
|
+
return 0;
|
|
25196
|
+
}
|
|
25197
|
+
if (command === "add-provider") {
|
|
25198
|
+
const [presetOrName, ...tail2] = rest;
|
|
25199
|
+
if (!presetOrName) throw new Error("config add-provider requires a provider name or preset.");
|
|
25200
|
+
const flags = parseFlags(tail2);
|
|
25201
|
+
const name = stringFlag(flags, "name") ?? presetOrName;
|
|
25202
|
+
const force = await ensureOverwriteForProvider(stringFlag(flags, "path"), name, boolFlag(flags, "force"));
|
|
25203
|
+
const result = await addProvider({
|
|
25204
|
+
path: stringFlag(flags, "path"),
|
|
25205
|
+
name,
|
|
25206
|
+
preset: presetOrName,
|
|
25207
|
+
type: stringFlag(flags, "type") ?? presetOrName,
|
|
25208
|
+
baseURL: stringFlag(flags, "base-url"),
|
|
25209
|
+
apiKey: await apiKeyFromFlags(flags),
|
|
25210
|
+
apiKeyEnv: stringFlag(flags, "api-key-env"),
|
|
25211
|
+
authHeader: stringFlag(flags, "auth-header"),
|
|
25212
|
+
force
|
|
25213
|
+
});
|
|
25214
|
+
process.stdout.write(`Updated config: ${result.path}
|
|
25215
|
+
`);
|
|
25216
|
+
return 0;
|
|
25217
|
+
}
|
|
25218
|
+
if (command === "add-model") {
|
|
25219
|
+
const [provider, ...tail2] = rest;
|
|
25220
|
+
if (!provider) throw new Error("config add-model requires a provider name.");
|
|
25221
|
+
const flags = parseFlags(tail2);
|
|
25222
|
+
const name = requiredStringFlag(flags, "name");
|
|
25223
|
+
const model = requiredStringFlag(flags, "model");
|
|
25224
|
+
const displayName = stringFlag(flags, "display-name");
|
|
25225
|
+
const force = await ensureOverwriteForModel(stringFlag(flags, "path"), provider, name, displayName, boolFlag(flags, "force"));
|
|
25226
|
+
const result = await addModelProfile({
|
|
25227
|
+
path: stringFlag(flags, "path"),
|
|
25228
|
+
provider,
|
|
25229
|
+
name,
|
|
25230
|
+
displayName,
|
|
25231
|
+
model,
|
|
25232
|
+
baseURL: stringFlag(flags, "base-url"),
|
|
25233
|
+
apiKey: await apiKeyFromFlags(flags),
|
|
25234
|
+
apiKeyEnv: stringFlag(flags, "api-key-env"),
|
|
25235
|
+
force
|
|
25236
|
+
});
|
|
25237
|
+
process.stdout.write(`Updated config: ${result.path}
|
|
25238
|
+
`);
|
|
25239
|
+
return 0;
|
|
25240
|
+
}
|
|
25241
|
+
throw new Error(`Unknown config command: ${command}`);
|
|
25242
|
+
} catch (error) {
|
|
25243
|
+
process.stderr.write(`${errorMessage3(error)}
|
|
25244
|
+
`);
|
|
25245
|
+
return 1;
|
|
25246
|
+
}
|
|
25247
|
+
}
|
|
25248
|
+
async function runAuthCommand(argv) {
|
|
25249
|
+
const [command, provider] = argv;
|
|
25250
|
+
try {
|
|
25251
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
25252
|
+
printAuthHelp();
|
|
25253
|
+
return 0;
|
|
25254
|
+
}
|
|
25255
|
+
if (command === "status") {
|
|
25256
|
+
const claude = await preflightClaudeCodeAuth({ sanitizeApiKeyEnv: true }).catch((error) => ({ ok: false, source: "unknown", warning: errorMessage3(error) }));
|
|
25257
|
+
process.stdout.write(`claudecode: ${claude.ok ? "ok" : "not ready"} (${claude.source})${claude.warning ? ` - ${claude.warning}` : ""}
|
|
25258
|
+
`);
|
|
25259
|
+
const codex = await checkCodexAuth();
|
|
25260
|
+
process.stdout.write(`codex: ${codex.ok ? "ok" : "not ready"} (${codex.source})${codex.message ? ` - ${codex.message}` : ""}
|
|
25261
|
+
`);
|
|
25262
|
+
return claude.ok && codex.ok ? 0 : 1;
|
|
25263
|
+
}
|
|
25264
|
+
if (command !== "login") throw new Error(`Unknown auth command: ${command}`);
|
|
25265
|
+
return runProviderLogin(provider);
|
|
25266
|
+
} catch (error) {
|
|
25267
|
+
process.stderr.write(`${errorMessage3(error)}
|
|
25268
|
+
`);
|
|
25269
|
+
return 1;
|
|
25270
|
+
}
|
|
25271
|
+
}
|
|
25272
|
+
async function runProviderLogin(provider) {
|
|
25273
|
+
if (provider === "codex") {
|
|
25274
|
+
if (!await commandExists("codex")) {
|
|
25275
|
+
process.stderr.write("Codex CLI not found in PATH. Install it from https://github.com/openai/codex first, then re-run `demian auth login codex`.\n");
|
|
25276
|
+
return 1;
|
|
25277
|
+
}
|
|
25278
|
+
return runLogin("codex", ["login"]);
|
|
25279
|
+
}
|
|
25280
|
+
if (provider === "claudecode") {
|
|
25281
|
+
const cli = resolveClaudeCodeCliPath();
|
|
25282
|
+
if (!cli && !await commandExists("claude")) {
|
|
25283
|
+
process.stderr.write("Claude Code CLI not found. Install it from https://docs.claude.com/en/docs/agents-and-tools/claude-code first, then re-run `demian auth login claudecode`.\n");
|
|
25284
|
+
return 1;
|
|
25285
|
+
}
|
|
25286
|
+
return runLogin(cli ?? "claude", ["setup-token"]);
|
|
25287
|
+
}
|
|
25288
|
+
throw new Error("auth login requires provider codex or claudecode.");
|
|
25289
|
+
}
|
|
25290
|
+
function runLogin(command, args) {
|
|
25291
|
+
process.stdout.write(`Starting ${[command, ...args].join(" ")}...
|
|
25292
|
+
`);
|
|
25293
|
+
const opened = /* @__PURE__ */ new Set();
|
|
25294
|
+
return new Promise((resolve) => {
|
|
25295
|
+
const child = spawn4(command, args, { stdio: ["inherit", "pipe", "pipe"], env: process.env });
|
|
25296
|
+
const onText = (stream) => (chunk) => {
|
|
25297
|
+
const text = chunk.toString();
|
|
25298
|
+
stream.write(text);
|
|
25299
|
+
for (const url of urlsInText(text)) {
|
|
25300
|
+
if (opened.has(url)) continue;
|
|
25301
|
+
opened.add(url);
|
|
25302
|
+
void openUrl(url);
|
|
25303
|
+
}
|
|
25304
|
+
};
|
|
25305
|
+
child.stdout.on("data", onText(process.stdout));
|
|
25306
|
+
child.stderr.on("data", onText(process.stderr));
|
|
25307
|
+
child.on("error", (error) => {
|
|
25308
|
+
process.stderr.write(`${error.message}
|
|
25309
|
+
`);
|
|
25310
|
+
resolve(1);
|
|
25311
|
+
});
|
|
25312
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
25313
|
+
});
|
|
25314
|
+
}
|
|
25315
|
+
function parseFlags(argv) {
|
|
25316
|
+
const flags = /* @__PURE__ */ new Map();
|
|
25317
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25318
|
+
const arg = argv[i];
|
|
25319
|
+
if (!arg.startsWith("--")) continue;
|
|
25320
|
+
const key = arg.slice(2);
|
|
25321
|
+
const next = argv[i + 1];
|
|
25322
|
+
if (!next || next.startsWith("--")) {
|
|
25323
|
+
flags.set(key, true);
|
|
25324
|
+
} else {
|
|
25325
|
+
flags.set(key, next);
|
|
25326
|
+
i++;
|
|
25327
|
+
}
|
|
25328
|
+
}
|
|
25329
|
+
return flags;
|
|
25330
|
+
}
|
|
25331
|
+
function stringFlag(flags, name) {
|
|
25332
|
+
const value = flags.get(name);
|
|
25333
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
25334
|
+
}
|
|
25335
|
+
function requiredStringFlag(flags, name) {
|
|
25336
|
+
const value = stringFlag(flags, name);
|
|
25337
|
+
if (!value) throw new Error(`--${name} is required.`);
|
|
25338
|
+
return value;
|
|
25339
|
+
}
|
|
25340
|
+
function boolFlag(flags, name) {
|
|
25341
|
+
return flags.get(name) === true;
|
|
25342
|
+
}
|
|
25343
|
+
async function apiKeyFromFlags(flags) {
|
|
25344
|
+
const inline = stringFlag(flags, "api-key");
|
|
25345
|
+
if (inline) return inline;
|
|
25346
|
+
if (!boolFlag(flags, "api-key-stdin")) return void 0;
|
|
25347
|
+
return (await readStdin()).trim();
|
|
25348
|
+
}
|
|
25349
|
+
async function readStdin() {
|
|
25350
|
+
let value = "";
|
|
25351
|
+
for await (const chunk of process.stdin) value += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
25352
|
+
return value;
|
|
25353
|
+
}
|
|
25354
|
+
function urlsInText(text) {
|
|
25355
|
+
return [...text.matchAll(/https?:\/\/[^\s)>"']+/g)].map((match) => match[0]);
|
|
25356
|
+
}
|
|
25357
|
+
function openUrl(url) {
|
|
25358
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
25359
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
25360
|
+
return new Promise((resolve) => {
|
|
25361
|
+
const child = spawn4(command, args, { stdio: "ignore", detached: true });
|
|
25362
|
+
child.on("error", () => resolve());
|
|
25363
|
+
child.on("spawn", () => {
|
|
25364
|
+
child.unref();
|
|
25365
|
+
resolve();
|
|
25366
|
+
});
|
|
25367
|
+
});
|
|
25368
|
+
}
|
|
25369
|
+
function printConfigHelp() {
|
|
25370
|
+
process.stdout.write(`demian config
|
|
25371
|
+
|
|
25372
|
+
Usage:
|
|
25373
|
+
demian config init [--path <path>] [--force] [--print] [--provider <name>]
|
|
25374
|
+
demian config setup interactive multi-step provider wizard
|
|
25375
|
+
demian config add-provider <preset|openai-compatible> [--name <name>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--auth-header <header>] [--force]
|
|
25376
|
+
demian config add-model <provider> --name <name> --model <model> [--display-name <label>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--force]
|
|
25377
|
+
demian config models <provider> [--refresh] [--config <path>]
|
|
25378
|
+
demian config list [--config <path>]
|
|
25379
|
+
demian config path
|
|
25380
|
+
`);
|
|
25381
|
+
}
|
|
25382
|
+
function printAuthHelp() {
|
|
25383
|
+
process.stdout.write(`demian auth
|
|
25384
|
+
|
|
25385
|
+
Usage:
|
|
25386
|
+
demian auth login codex
|
|
25387
|
+
demian auth login claudecode
|
|
25388
|
+
demian auth status
|
|
25389
|
+
`);
|
|
25390
|
+
}
|
|
25391
|
+
function errorMessage3(error) {
|
|
25392
|
+
return error instanceof Error ? error.message : String(error);
|
|
25393
|
+
}
|
|
25394
|
+
async function confirmOverwriteIfExists(customPath, isPrint) {
|
|
25395
|
+
if (isPrint) return false;
|
|
25396
|
+
const filePath = customPath ?? defaultUserConfigPath();
|
|
25397
|
+
if (!await fileExists2(filePath)) return false;
|
|
25398
|
+
return promptYesNo(`Overwrite existing config at ${filePath}? (y/N): `, false);
|
|
25399
|
+
}
|
|
25400
|
+
async function ensureOverwriteForProvider(customPath, providerName, force) {
|
|
25401
|
+
if (force) return true;
|
|
25402
|
+
const filePath = customPath ?? defaultUserConfigPath();
|
|
25403
|
+
if (!await fileExists2(filePath)) return false;
|
|
25404
|
+
const config = await readJsonSafe(filePath);
|
|
25405
|
+
const provider = config?.providers?.[providerName];
|
|
25406
|
+
if (!provider) return false;
|
|
25407
|
+
return promptYesNo(`Provider "${providerName}" already exists. Overwrite? (y/N): `, false);
|
|
25408
|
+
}
|
|
25409
|
+
async function ensureOverwriteForModel(customPath, providerName, profileName, displayName, force) {
|
|
25410
|
+
if (force) return true;
|
|
25411
|
+
const filePath = customPath ?? defaultUserConfigPath();
|
|
25412
|
+
if (!await fileExists2(filePath)) return false;
|
|
25413
|
+
const config = await readJsonSafe(filePath);
|
|
25414
|
+
const profiles = Array.isArray(config?.providers?.[providerName]?.modelProfiles) ? config.providers[providerName].modelProfiles : [];
|
|
25415
|
+
const sameName = profiles.find((entry) => entry.name === profileName);
|
|
25416
|
+
const sameDisplay = displayName ? profiles.find((entry) => entry.displayName === displayName && entry.name !== profileName) : void 0;
|
|
25417
|
+
if (!sameName && !sameDisplay) return false;
|
|
25418
|
+
const which = sameName ? `name "${profileName}"` : `displayName "${displayName}"`;
|
|
25419
|
+
return promptYesNo(`Profile with ${which} already exists on provider ${providerName}. Overwrite? (y/N): `, false);
|
|
25420
|
+
}
|
|
25421
|
+
async function fileExists2(filePath) {
|
|
25422
|
+
try {
|
|
25423
|
+
await stat4(filePath);
|
|
25424
|
+
return true;
|
|
25425
|
+
} catch {
|
|
25426
|
+
return false;
|
|
25427
|
+
}
|
|
25428
|
+
}
|
|
25429
|
+
async function readJsonSafe(filePath) {
|
|
25430
|
+
try {
|
|
25431
|
+
const { readFile: readFile18 } = await import("node:fs/promises");
|
|
25432
|
+
const text = await readFile18(filePath, "utf8");
|
|
25433
|
+
return JSON.parse(text);
|
|
25434
|
+
} catch {
|
|
25435
|
+
return void 0;
|
|
25436
|
+
}
|
|
25437
|
+
}
|
|
25438
|
+
async function promptYesNo(question, defaultYes) {
|
|
25439
|
+
if (!process.stdin.isTTY) {
|
|
25440
|
+
process.stderr.write(`${question.replace(/\(y\/N\)|\(Y\/n\)/, "").trim()} (non-interactive; pass --force to overwrite).
|
|
25441
|
+
`);
|
|
25442
|
+
return false;
|
|
25443
|
+
}
|
|
25444
|
+
const rl2 = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
25445
|
+
try {
|
|
25446
|
+
const answer = (await rl2.question(question)).trim().toLowerCase();
|
|
25447
|
+
if (!answer) return defaultYes;
|
|
25448
|
+
return answer === "y" || answer === "yes";
|
|
25449
|
+
} finally {
|
|
25450
|
+
rl2.close();
|
|
25451
|
+
}
|
|
25452
|
+
}
|
|
25453
|
+
async function commandExists(command) {
|
|
25454
|
+
return new Promise((resolve) => {
|
|
25455
|
+
const child = spawn4(process.platform === "win32" ? "where" : "which", [command], { stdio: "ignore" });
|
|
25456
|
+
child.on("close", (code) => resolve(code === 0));
|
|
25457
|
+
child.on("error", () => resolve(false));
|
|
25458
|
+
});
|
|
25459
|
+
}
|
|
25460
|
+
async function runFirstRunWizard() {
|
|
25461
|
+
const filePath = defaultUserConfigPath();
|
|
25462
|
+
if (await fileExists2(filePath)) return void 0;
|
|
25463
|
+
if (!process.stdin.isTTY) {
|
|
25464
|
+
process.stderr.write(`No Demian config at ${filePath}. Run \`demian config init\` to create one.
|
|
25465
|
+
`);
|
|
25466
|
+
return void 0;
|
|
25467
|
+
}
|
|
25468
|
+
process.stdout.write(`
|
|
25469
|
+
Welcome to Demian.
|
|
25470
|
+
No config found at ${filePath}.
|
|
25471
|
+
`);
|
|
25472
|
+
const ok2 = await promptYesNo("Create a default v2 config now? (Y/n): ", true);
|
|
25473
|
+
if (!ok2) {
|
|
25474
|
+
process.stdout.write("Skipped. Run `demian config init` later to set up.\n");
|
|
25475
|
+
return void 0;
|
|
25476
|
+
}
|
|
25477
|
+
const result = await createUserConfig({});
|
|
25478
|
+
process.stdout.write(`Created ${result.path}.
|
|
25479
|
+
`);
|
|
25480
|
+
const walkProviders = await promptYesNo("Walk through provider setup now? (y/N): ", false);
|
|
25481
|
+
if (walkProviders) await walkProviderSetup();
|
|
25482
|
+
else {
|
|
25483
|
+
process.stdout.write("Next steps:\n");
|
|
25484
|
+
process.stdout.write(" - Set API key env vars (e.g. OPENAI_API_KEY)\n");
|
|
25485
|
+
process.stdout.write(" - `demian config models <provider> --refresh` to inspect catalogs\n");
|
|
25486
|
+
process.stdout.write(" - `demian auth login codex|claudecode` for OAuth providers\n");
|
|
25487
|
+
process.stdout.write(" - `demian config setup` to re-enter this wizard\n\n");
|
|
25488
|
+
}
|
|
25489
|
+
return { created: true, configPath: result.path };
|
|
25490
|
+
}
|
|
25491
|
+
async function runSetupWizard() {
|
|
25492
|
+
if (!process.stdin.isTTY) {
|
|
25493
|
+
process.stderr.write("config setup requires an interactive terminal.\n");
|
|
25494
|
+
return 1;
|
|
25495
|
+
}
|
|
25496
|
+
const filePath = defaultUserConfigPath();
|
|
25497
|
+
if (!await fileExists2(filePath)) {
|
|
25498
|
+
const wizard = await runFirstRunWizard();
|
|
25499
|
+
return wizard?.created ? 0 : 1;
|
|
25500
|
+
}
|
|
25501
|
+
await walkProviderSetup();
|
|
25502
|
+
return 0;
|
|
25503
|
+
}
|
|
25504
|
+
async function walkProviderSetup() {
|
|
25505
|
+
const builtinPresets = [
|
|
25506
|
+
{ name: "openai", description: "OpenAI ChatGPT API", defaultEnv: "OPENAI_API_KEY" },
|
|
25507
|
+
{ name: "anthropic", description: "Anthropic Claude API", defaultEnv: "ANTHROPIC_API_KEY" },
|
|
25508
|
+
{ name: "gemini", description: "Google Gemini (OpenAI-compatible)", defaultEnv: "GEMINI_API_KEY" },
|
|
25509
|
+
{ name: "groq", description: "Groq Cloud", defaultEnv: "GROQ_API_KEY" },
|
|
25510
|
+
{ name: "codex", description: "ChatGPT Codex (OAuth)", oauth: "codex" },
|
|
25511
|
+
{ name: "claudecode", description: "Claude Code external runtime (OAuth)", oauth: "claudecode" }
|
|
25512
|
+
];
|
|
25513
|
+
process.stdout.write("\nProvider setup. Press Enter to skip a provider.\n");
|
|
25514
|
+
for (const preset of builtinPresets) {
|
|
25515
|
+
const want = await promptYesNo(`Configure ${preset.name} (${preset.description})? (y/N): `, false);
|
|
25516
|
+
if (!want) continue;
|
|
25517
|
+
if (preset.oauth) {
|
|
25518
|
+
const login = await promptYesNo(`Run \`demian auth login ${preset.oauth}\` now? (Y/n): `, true);
|
|
25519
|
+
if (login) await runProviderLogin(preset.oauth).catch(() => void 0);
|
|
25520
|
+
continue;
|
|
25521
|
+
}
|
|
25522
|
+
if (preset.defaultEnv) {
|
|
25523
|
+
const envSet = !!process.env[preset.defaultEnv];
|
|
25524
|
+
process.stdout.write(` env ${preset.defaultEnv}: ${envSet ? "set" : "not set"}
|
|
25525
|
+
`);
|
|
25526
|
+
if (!envSet) process.stdout.write(` \u2192 export ${preset.defaultEnv}=... before running demian.
|
|
25527
|
+
`);
|
|
25528
|
+
}
|
|
25529
|
+
}
|
|
25530
|
+
process.stdout.write("\nSetup complete. Run `demian` to start a session.\n\n");
|
|
25531
|
+
}
|
|
25532
|
+
async function checkCodexAuth() {
|
|
25533
|
+
const candidates = [path16.join(process.env.HOME ?? os10.homedir(), ".codex", "auth.json"), path16.join(process.env.HOME ?? os10.homedir(), ".codex", "credentials.json")];
|
|
25534
|
+
for (const candidate of candidates) {
|
|
25535
|
+
if (await fileExists2(candidate)) {
|
|
25536
|
+
return { ok: true, source: "codex-store", message: candidate };
|
|
25537
|
+
}
|
|
25538
|
+
}
|
|
25539
|
+
if (process.env.OPENAI_API_KEY) return { ok: true, source: "api-key-fallback", message: "OPENAI_API_KEY set" };
|
|
25540
|
+
return { ok: false, source: "none", message: "no codex auth file or OPENAI_API_KEY; run `demian auth login codex`" };
|
|
25541
|
+
}
|
|
25542
|
+
|
|
25543
|
+
// src/doctor/policies.ts
|
|
25544
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
25545
|
+
import { readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
|
|
25546
|
+
import os11 from "node:os";
|
|
25547
|
+
import path17 from "node:path";
|
|
25548
|
+
function isDoctorCommand(argv) {
|
|
25549
|
+
return argv[0] === "doctor";
|
|
25550
|
+
}
|
|
25551
|
+
async function maybeRunDoctorCommand(argv, options = {}) {
|
|
25552
|
+
if (!isDoctorCommand(argv)) return void 0;
|
|
25553
|
+
return runDoctorCommand(argv.slice(1), options);
|
|
25554
|
+
}
|
|
25555
|
+
async function runDoctorCommand(argv, options = {}) {
|
|
25556
|
+
const stdout = options.stdout ?? process.stdout;
|
|
25557
|
+
const stderr = options.stderr ?? process.stderr;
|
|
25558
|
+
const flags = parseDoctorFlags(argv);
|
|
25559
|
+
if (flags.help) {
|
|
25560
|
+
stdout.write(doctorHelp());
|
|
25561
|
+
return 0;
|
|
25562
|
+
}
|
|
25563
|
+
if (argv[0] !== "policies" || !argv.includes("--upgrade-namespaces")) {
|
|
24176
25564
|
stderr.write("Unsupported doctor command. Run `demian doctor policies --upgrade-namespaces [--write]`.\n");
|
|
24177
25565
|
return 1;
|
|
24178
25566
|
}
|
|
24179
|
-
const cwd =
|
|
25567
|
+
const cwd = path17.resolve(flags.cwd ?? options.cwd ?? process.cwd());
|
|
24180
25568
|
const filePaths = resolvePolicyConfigPaths(cwd, flags.configPaths);
|
|
24181
25569
|
if (filePaths.length === 0) {
|
|
24182
25570
|
stdout.write("No demian config files found to inspect.\n");
|
|
@@ -24185,11 +25573,11 @@ async function runDoctorCommand(argv, options = {}) {
|
|
|
24185
25573
|
const allChanges = [];
|
|
24186
25574
|
let updatedFiles = 0;
|
|
24187
25575
|
for (const filePath of filePaths) {
|
|
24188
|
-
const original = await
|
|
25576
|
+
const original = await readFile9(filePath, "utf8");
|
|
24189
25577
|
const upgraded = upgradePolicyNamespacesInText(original, filePath);
|
|
24190
25578
|
allChanges.push(...upgraded.changes);
|
|
24191
25579
|
if (flags.write && upgraded.changes.length > 0) {
|
|
24192
|
-
await
|
|
25580
|
+
await writeFile7(filePath, upgraded.text, "utf8");
|
|
24193
25581
|
updatedFiles++;
|
|
24194
25582
|
}
|
|
24195
25583
|
}
|
|
@@ -24277,11 +25665,11 @@ function parseDoctorFlags(argv) {
|
|
|
24277
25665
|
return flags;
|
|
24278
25666
|
}
|
|
24279
25667
|
function resolvePolicyConfigPaths(cwd, explicitPaths) {
|
|
24280
|
-
const candidates = explicitPaths.length > 0 ? explicitPaths.map((item) =>
|
|
24281
|
-
|
|
24282
|
-
|
|
24283
|
-
|
|
24284
|
-
|
|
25668
|
+
const candidates = explicitPaths.length > 0 ? explicitPaths.map((item) => path17.resolve(cwd, item)) : [
|
|
25669
|
+
path17.join(os11.homedir(), ".demian", "config.json"),
|
|
25670
|
+
path17.join(os11.homedir(), ".demian", "config.jsond"),
|
|
25671
|
+
path17.join(cwd, ".demian", "config.json"),
|
|
25672
|
+
path17.join(cwd, ".demian", "config.jsond")
|
|
24285
25673
|
];
|
|
24286
25674
|
return [...new Set(candidates)].filter((item) => existsSync2(item));
|
|
24287
25675
|
}
|
|
@@ -24377,8 +25765,9 @@ Flags:
|
|
|
24377
25765
|
}
|
|
24378
25766
|
|
|
24379
25767
|
// src/transcript.ts
|
|
24380
|
-
import { mkdir as
|
|
24381
|
-
import
|
|
25768
|
+
import { mkdir as mkdir8, appendFile, writeFile as writeFile8 } from "node:fs/promises";
|
|
25769
|
+
import os12 from "node:os";
|
|
25770
|
+
import path18 from "node:path";
|
|
24382
25771
|
var TranscriptWriter = class {
|
|
24383
25772
|
filePath;
|
|
24384
25773
|
#enabled;
|
|
@@ -24386,9 +25775,9 @@ var TranscriptWriter = class {
|
|
|
24386
25775
|
#queue = Promise.resolve();
|
|
24387
25776
|
constructor(options) {
|
|
24388
25777
|
this.#enabled = options.enabled !== false;
|
|
24389
|
-
const dir =
|
|
24390
|
-
this.filePath =
|
|
24391
|
-
this.#ready = this.#enabled ?
|
|
25778
|
+
const dir = path18.join(options.storageDir ?? defaultDemianStorageDir(), "transcripts", options.sessionId);
|
|
25779
|
+
this.filePath = path18.join(dir, "session.jsonl");
|
|
25780
|
+
this.#ready = this.#enabled ? mkdir8(dir, { recursive: true }).then(() => writeFile8(this.filePath, "", { flag: "a" })) : Promise.resolve();
|
|
24392
25781
|
}
|
|
24393
25782
|
write(event) {
|
|
24394
25783
|
if (!this.#enabled) return Promise.resolve();
|
|
@@ -24403,12 +25792,15 @@ var TranscriptWriter = class {
|
|
|
24403
25792
|
await this.#queue;
|
|
24404
25793
|
}
|
|
24405
25794
|
};
|
|
25795
|
+
function defaultDemianStorageDir() {
|
|
25796
|
+
return path18.join(os12.homedir(), ".demian");
|
|
25797
|
+
}
|
|
24406
25798
|
|
|
24407
25799
|
// src/external-runtime/snapshot-diff.ts
|
|
24408
25800
|
import { createHash as createHash2 } from "node:crypto";
|
|
24409
25801
|
import { createReadStream } from "node:fs";
|
|
24410
|
-
import { readdir, readFile as
|
|
24411
|
-
import
|
|
25802
|
+
import { readdir, readFile as readFile10, stat as stat5 } from "node:fs/promises";
|
|
25803
|
+
import path19 from "node:path";
|
|
24412
25804
|
|
|
24413
25805
|
// src/workspace/diff.ts
|
|
24414
25806
|
var CONTEXT_LINES = 3;
|
|
@@ -24618,7 +26010,7 @@ async function diffWorkspaceSnapshot(before) {
|
|
|
24618
26010
|
}
|
|
24619
26011
|
async function walk(root, relativeDir, entries, options) {
|
|
24620
26012
|
if (entries.size >= options.maxFiles) return;
|
|
24621
|
-
const absoluteDir =
|
|
26013
|
+
const absoluteDir = path19.join(root, relativeDir);
|
|
24622
26014
|
let items;
|
|
24623
26015
|
try {
|
|
24624
26016
|
items = await readdir(absoluteDir, { withFileTypes: true });
|
|
@@ -24629,8 +26021,8 @@ async function walk(root, relativeDir, entries, options) {
|
|
|
24629
26021
|
if (entries.size >= options.maxFiles) return;
|
|
24630
26022
|
if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
|
|
24631
26023
|
if (SKIP_DIRS.has(item.name)) continue;
|
|
24632
|
-
const relativePath = normalizeRelativePath(
|
|
24633
|
-
const absolutePath =
|
|
26024
|
+
const relativePath = normalizeRelativePath(path19.join(relativeDir, item.name));
|
|
26025
|
+
const absolutePath = path19.join(root, relativePath);
|
|
24634
26026
|
if (item.isDirectory()) {
|
|
24635
26027
|
await walk(root, relativePath, entries, options);
|
|
24636
26028
|
continue;
|
|
@@ -24641,7 +26033,7 @@ async function walk(root, relativeDir, entries, options) {
|
|
|
24641
26033
|
}
|
|
24642
26034
|
}
|
|
24643
26035
|
async function snapshotFile(filePath, relativePath, maxTextBytes) {
|
|
24644
|
-
const info = await
|
|
26036
|
+
const info = await stat5(filePath);
|
|
24645
26037
|
const sha2562 = await sha256File(filePath);
|
|
24646
26038
|
const content = info.size <= maxTextBytes ? await readTextContent(filePath) : void 0;
|
|
24647
26039
|
return { path: relativePath, size: info.size, sha256: sha2562, content };
|
|
@@ -24652,7 +26044,7 @@ function snapshotDiffText(entry) {
|
|
|
24652
26044
|
`;
|
|
24653
26045
|
}
|
|
24654
26046
|
async function readTextContent(filePath) {
|
|
24655
|
-
const buffer = await
|
|
26047
|
+
const buffer = await readFile10(filePath);
|
|
24656
26048
|
if (buffer.includes(0)) return void 0;
|
|
24657
26049
|
return buffer.toString("utf8");
|
|
24658
26050
|
}
|
|
@@ -24666,7 +26058,7 @@ function sha256File(filePath) {
|
|
|
24666
26058
|
});
|
|
24667
26059
|
}
|
|
24668
26060
|
function normalizeRelativePath(value) {
|
|
24669
|
-
return value.split(
|
|
26061
|
+
return value.split(path19.sep).join("/");
|
|
24670
26062
|
}
|
|
24671
26063
|
|
|
24672
26064
|
// src/external-runtime/session-runner.ts
|
|
@@ -25101,15 +26493,15 @@ function safeJson(value) {
|
|
|
25101
26493
|
}
|
|
25102
26494
|
|
|
25103
26495
|
// src/hooks/dispatcher.ts
|
|
25104
|
-
import
|
|
26496
|
+
import path21 from "node:path";
|
|
25105
26497
|
|
|
25106
26498
|
// src/hooks/command.ts
|
|
25107
|
-
import { spawn as
|
|
26499
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
25108
26500
|
async function runCommandHook(hook, ctx) {
|
|
25109
26501
|
if (!hook.command) return void 0;
|
|
25110
26502
|
const timeoutMs = hook.timeoutMs ?? 1e4;
|
|
25111
26503
|
return new Promise((resolve, reject) => {
|
|
25112
|
-
const child =
|
|
26504
|
+
const child = spawn5(hook.command, {
|
|
25113
26505
|
cwd: ctx.cwd,
|
|
25114
26506
|
env: process.env,
|
|
25115
26507
|
shell: true,
|
|
@@ -25194,7 +26586,7 @@ var blockDangerousBashHook = {
|
|
|
25194
26586
|
};
|
|
25195
26587
|
|
|
25196
26588
|
// src/hooks/builtin/protect-env-files.ts
|
|
25197
|
-
import
|
|
26589
|
+
import path20 from "node:path";
|
|
25198
26590
|
var protectEnvFilesHook = {
|
|
25199
26591
|
name: "protect-env-files",
|
|
25200
26592
|
event: "PreToolUse",
|
|
@@ -25202,7 +26594,7 @@ var protectEnvFilesHook = {
|
|
|
25202
26594
|
run(ctx) {
|
|
25203
26595
|
if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
|
|
25204
26596
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
25205
|
-
const filePath = typeof input2.path === "string" ?
|
|
26597
|
+
const filePath = typeof input2.path === "string" ? path20.resolve(ctx.cwd, input2.path) : "";
|
|
25206
26598
|
if (filePath && isEnvFile(filePath)) {
|
|
25207
26599
|
return {
|
|
25208
26600
|
decision: "block",
|
|
@@ -25239,7 +26631,7 @@ var maskSecretsHook = {
|
|
|
25239
26631
|
};
|
|
25240
26632
|
|
|
25241
26633
|
// src/hooks/builtin/inject-env-info.ts
|
|
25242
|
-
import
|
|
26634
|
+
import os13 from "node:os";
|
|
25243
26635
|
var injectEnvInfoHook = {
|
|
25244
26636
|
name: "inject-env-info",
|
|
25245
26637
|
event: "SessionStart",
|
|
@@ -25248,7 +26640,7 @@ var injectEnvInfoHook = {
|
|
|
25248
26640
|
decision: "allow",
|
|
25249
26641
|
patch: {
|
|
25250
26642
|
systemNote: [
|
|
25251
|
-
`Environment: ${
|
|
26643
|
+
`Environment: ${os13.type()} ${os13.release()} (${os13.platform()}/${os13.arch()})`,
|
|
25252
26644
|
`cwd: ${ctx.cwd}`,
|
|
25253
26645
|
`provider: ${ctx.provider ?? "unknown"}`,
|
|
25254
26646
|
`model: ${ctx.model ?? "unknown"}`,
|
|
@@ -25317,14 +26709,14 @@ function matchesHook(match, ctx) {
|
|
|
25317
26709
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
25318
26710
|
const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
|
|
25319
26711
|
if (!filePath) return false;
|
|
25320
|
-
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd,
|
|
26712
|
+
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path21.resolve(ctx.cwd, filePath)))) return false;
|
|
25321
26713
|
}
|
|
25322
26714
|
return true;
|
|
25323
26715
|
}
|
|
25324
26716
|
|
|
25325
26717
|
// src/multimodal.ts
|
|
25326
|
-
import { readFile as
|
|
25327
|
-
import
|
|
26718
|
+
import { readFile as readFile11, stat as stat6 } from "node:fs/promises";
|
|
26719
|
+
import path22 from "node:path";
|
|
25328
26720
|
var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
25329
26721
|
async function buildUserContent(prompt, images = [], options) {
|
|
25330
26722
|
if (images.length === 0) return prompt;
|
|
@@ -25343,16 +26735,16 @@ async function buildUserContent(prompt, images = [], options) {
|
|
|
25343
26735
|
async function imageToUrl(input2, options) {
|
|
25344
26736
|
if (/^https?:\/\//i.test(input2) || /^data:image\//i.test(input2)) return input2;
|
|
25345
26737
|
const filePath = resolveInsideCwd(options.cwd, input2);
|
|
25346
|
-
const info = await
|
|
26738
|
+
const info = await stat6(filePath);
|
|
25347
26739
|
if (!info.isFile()) throw new Error(`Image input is not a file: ${input2}`);
|
|
25348
26740
|
const maxImageBytes = options.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
|
|
25349
26741
|
if (info.size > maxImageBytes) throw new Error(`Image is larger than ${maxImageBytes} bytes: ${input2}`);
|
|
25350
26742
|
const mime = mimeFromPath(filePath);
|
|
25351
|
-
const bytes = await
|
|
26743
|
+
const bytes = await readFile11(filePath);
|
|
25352
26744
|
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
25353
26745
|
}
|
|
25354
26746
|
function mimeFromPath(filePath) {
|
|
25355
|
-
switch (
|
|
26747
|
+
switch (path22.extname(filePath).toLowerCase()) {
|
|
25356
26748
|
case ".jpg":
|
|
25357
26749
|
case ".jpeg":
|
|
25358
26750
|
return "image/jpeg";
|
|
@@ -25363,15 +26755,15 @@ function mimeFromPath(filePath) {
|
|
|
25363
26755
|
case ".webp":
|
|
25364
26756
|
return "image/webp";
|
|
25365
26757
|
default:
|
|
25366
|
-
throw new Error(`Unsupported image extension: ${
|
|
26758
|
+
throw new Error(`Unsupported image extension: ${path22.extname(filePath) || "(none)"}`);
|
|
25367
26759
|
}
|
|
25368
26760
|
}
|
|
25369
26761
|
|
|
25370
26762
|
// src/permissions/persistent-grants.ts
|
|
25371
|
-
import { mkdir as
|
|
26763
|
+
import { mkdir as mkdir9, readFile as readFile12, writeFile as writeFile9 } from "node:fs/promises";
|
|
25372
26764
|
import fs8 from "node:fs";
|
|
25373
|
-
import
|
|
25374
|
-
import
|
|
26765
|
+
import os14 from "node:os";
|
|
26766
|
+
import path23 from "node:path";
|
|
25375
26767
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
25376
26768
|
var PersistentGrantStore = class {
|
|
25377
26769
|
filePath;
|
|
@@ -25402,22 +26794,22 @@ var PersistentGrantStore = class {
|
|
|
25402
26794
|
}
|
|
25403
26795
|
async #read() {
|
|
25404
26796
|
if (!fs8.existsSync(this.filePath)) return { version: 1, grants: [] };
|
|
25405
|
-
const data = JSON.parse(await
|
|
26797
|
+
const data = JSON.parse(await readFile12(this.filePath, "utf8"));
|
|
25406
26798
|
return {
|
|
25407
26799
|
version: 1,
|
|
25408
26800
|
grants: Array.isArray(data.grants) ? data.grants.filter(isGrantRecord) : []
|
|
25409
26801
|
};
|
|
25410
26802
|
}
|
|
25411
26803
|
async #write(file) {
|
|
25412
|
-
await
|
|
25413
|
-
await
|
|
26804
|
+
await mkdir9(path23.dirname(this.filePath), { recursive: true });
|
|
26805
|
+
await writeFile9(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
25414
26806
|
`, { mode: 384 });
|
|
25415
26807
|
}
|
|
25416
26808
|
};
|
|
25417
26809
|
function resolveGrantPath(cwd, config) {
|
|
25418
|
-
if (config.path) return
|
|
25419
|
-
if ((config.scope ?? "project") === "user") return
|
|
25420
|
-
return
|
|
26810
|
+
if (config.path) return path23.resolve(cwd, config.path);
|
|
26811
|
+
if ((config.scope ?? "project") === "user") return path23.join(os14.homedir(), ".demian", "grants.json");
|
|
26812
|
+
return path23.join(cwd, ".demian", "grants.json");
|
|
25421
26813
|
}
|
|
25422
26814
|
function isGrantRecord(value) {
|
|
25423
26815
|
if (!value || typeof value !== "object") return false;
|
|
@@ -25850,25 +27242,25 @@ function fail(content, metadata) {
|
|
|
25850
27242
|
}
|
|
25851
27243
|
|
|
25852
27244
|
// src/tools/output.ts
|
|
25853
|
-
import { mkdir as
|
|
25854
|
-
import
|
|
27245
|
+
import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
|
|
27246
|
+
import path24 from "node:path";
|
|
25855
27247
|
var DEFAULT_CAP_BYTES = 32 * 1024;
|
|
25856
27248
|
var HALF_PREVIEW_BYTES = 16 * 1024;
|
|
25857
27249
|
async function capToolOutput(result, options) {
|
|
25858
27250
|
const capBytes = options.capBytes ?? DEFAULT_CAP_BYTES;
|
|
25859
27251
|
const bytes = Buffer.byteLength(result.content, "utf8");
|
|
25860
27252
|
if (bytes <= capBytes) return result;
|
|
25861
|
-
const dir =
|
|
25862
|
-
await
|
|
25863
|
-
const outputPath =
|
|
25864
|
-
await
|
|
27253
|
+
const dir = path24.join(options.cwd, ".demian", "tmp");
|
|
27254
|
+
await mkdir10(dir, { recursive: true });
|
|
27255
|
+
const outputPath = path24.join(dir, `output-${safeCallId(options.callId)}.txt`);
|
|
27256
|
+
await writeFile10(outputPath, result.content, "utf8");
|
|
25865
27257
|
return {
|
|
25866
27258
|
...result,
|
|
25867
27259
|
content: [
|
|
25868
27260
|
sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
|
|
25869
27261
|
`
|
|
25870
27262
|
|
|
25871
|
-
[Full output saved to ${
|
|
27263
|
+
[Full output saved to ${path24.relative(options.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
|
|
25872
27264
|
|
|
25873
27265
|
`,
|
|
25874
27266
|
sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
|
|
@@ -25893,8 +27285,8 @@ function sliceUtf8(text, startByte, endByte) {
|
|
|
25893
27285
|
}
|
|
25894
27286
|
|
|
25895
27287
|
// src/tools/read-file.ts
|
|
25896
|
-
import { open as open2, stat as
|
|
25897
|
-
import
|
|
27288
|
+
import { open as open2, stat as stat7 } from "node:fs/promises";
|
|
27289
|
+
import path25 from "node:path";
|
|
25898
27290
|
|
|
25899
27291
|
// src/tools/validation.ts
|
|
25900
27292
|
function assertObject(input2, toolName) {
|
|
@@ -25964,7 +27356,7 @@ var readFileTool = {
|
|
|
25964
27356
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
25965
27357
|
const offset = positiveInteger(optionalNumberField(object2, "offset"), 1, "offset");
|
|
25966
27358
|
const limit = positiveInteger(optionalNumberField(object2, "limit"), DEFAULT_LIMIT, "limit");
|
|
25967
|
-
const info = await
|
|
27359
|
+
const info = await stat7(filePath);
|
|
25968
27360
|
if (info.isDirectory()) throw new Error(`read_file expected a file but got a directory: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
25969
27361
|
if (info.size > MAX_BYTES) throw new Error(`File is larger than ${MAX_BYTES} bytes: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
25970
27362
|
const sample = await readBytes(filePath, Math.min(SAMPLE_BYTES, info.size));
|
|
@@ -25978,7 +27370,7 @@ var readFileTool = {
|
|
|
25978
27370
|
const truncated = start + sliced.length < lines.length;
|
|
25979
27371
|
return ok(content + (truncated ? `
|
|
25980
27372
|
... (${lines.length - start - sliced.length} more lines)` : ""), {
|
|
25981
|
-
path:
|
|
27373
|
+
path: path25.relative(ctx.cwd, filePath),
|
|
25982
27374
|
lines: sliced.length,
|
|
25983
27375
|
totalLines: lines.length,
|
|
25984
27376
|
truncated
|
|
@@ -26016,8 +27408,8 @@ function looksBinary(buffer) {
|
|
|
26016
27408
|
}
|
|
26017
27409
|
|
|
26018
27410
|
// src/tools/write-file.ts
|
|
26019
|
-
import { mkdir as
|
|
26020
|
-
import
|
|
27411
|
+
import { mkdir as mkdir11, readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
|
|
27412
|
+
import path26 from "node:path";
|
|
26021
27413
|
var writeFileTool = {
|
|
26022
27414
|
name: "write_file",
|
|
26023
27415
|
description: "Create or replace a text file inside the workspace.",
|
|
@@ -26035,8 +27427,8 @@ var writeFileTool = {
|
|
|
26035
27427
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
26036
27428
|
const content = stringField(object2, "content", { allowEmpty: true });
|
|
26037
27429
|
const before = await readOptionalTextFile(filePath);
|
|
26038
|
-
await
|
|
26039
|
-
await
|
|
27430
|
+
await mkdir11(path26.dirname(filePath), { recursive: true });
|
|
27431
|
+
await writeFile11(filePath, content, "utf8");
|
|
26040
27432
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
26041
27433
|
const diff = createTextDiff(before, content, relative);
|
|
26042
27434
|
return ok(`Wrote ${relativeToCwd(ctx.cwd, filePath)} (${Buffer.byteLength(content, "utf8")} bytes).`, {
|
|
@@ -26049,7 +27441,7 @@ var writeFileTool = {
|
|
|
26049
27441
|
};
|
|
26050
27442
|
async function readOptionalTextFile(filePath) {
|
|
26051
27443
|
try {
|
|
26052
|
-
return await
|
|
27444
|
+
return await readFile13(filePath, "utf8");
|
|
26053
27445
|
} catch (error) {
|
|
26054
27446
|
if (isNotFound(error)) return void 0;
|
|
26055
27447
|
throw error;
|
|
@@ -26060,7 +27452,7 @@ function isNotFound(error) {
|
|
|
26060
27452
|
}
|
|
26061
27453
|
|
|
26062
27454
|
// src/tools/edit-file.ts
|
|
26063
|
-
import { readFile as
|
|
27455
|
+
import { readFile as readFile14, writeFile as writeFile12 } from "node:fs/promises";
|
|
26064
27456
|
var editFileTool = {
|
|
26065
27457
|
name: "edit_file",
|
|
26066
27458
|
description: "Replace an exact string in a text file inside the workspace.",
|
|
@@ -26082,12 +27474,12 @@ var editFileTool = {
|
|
|
26082
27474
|
const newString = stringField(object2, "newString", { allowEmpty: true });
|
|
26083
27475
|
const replaceAll = optionalBooleanField(object2, "replaceAll") ?? false;
|
|
26084
27476
|
if (oldString === newString) throw new Error("oldString and newString must be different");
|
|
26085
|
-
const before = await
|
|
27477
|
+
const before = await readFile14(filePath, "utf8");
|
|
26086
27478
|
const matches = countMatches(before, oldString);
|
|
26087
27479
|
if (matches === 0) throw new Error("oldString was not found");
|
|
26088
27480
|
if (matches > 1 && !replaceAll) throw new Error(`oldString matched ${matches} times; set replaceAll=true to replace all matches`);
|
|
26089
27481
|
const after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
|
|
26090
|
-
await
|
|
27482
|
+
await writeFile12(filePath, after, "utf8");
|
|
26091
27483
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
26092
27484
|
const diff = createTextDiff(before, after, relative);
|
|
26093
27485
|
return ok(`Edited ${relative} (${replaceAll ? matches : 1} replacement${matches === 1 ? "" : "s"}).`, {
|
|
@@ -26110,8 +27502,8 @@ function countMatches(text, needle) {
|
|
|
26110
27502
|
}
|
|
26111
27503
|
|
|
26112
27504
|
// src/tools/bash.ts
|
|
26113
|
-
import { spawn as
|
|
26114
|
-
import
|
|
27505
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
27506
|
+
import path28 from "node:path";
|
|
26115
27507
|
|
|
26116
27508
|
// src/sandbox/env-only.ts
|
|
26117
27509
|
function buildEnvOnlyLaunch(command, config) {
|
|
@@ -26171,7 +27563,7 @@ function bwrapPath() {
|
|
|
26171
27563
|
|
|
26172
27564
|
// src/sandbox/macos.ts
|
|
26173
27565
|
import { existsSync as existsSync4 } from "node:fs";
|
|
26174
|
-
import
|
|
27566
|
+
import path27 from "node:path";
|
|
26175
27567
|
function canUseMacOSSandbox() {
|
|
26176
27568
|
return process.platform === "darwin" && existsSync4("/usr/bin/sandbox-exec");
|
|
26177
27569
|
}
|
|
@@ -26197,7 +27589,7 @@ function macosProfile(cwd, config) {
|
|
|
26197
27589
|
}
|
|
26198
27590
|
if (mode === "workspace-write") {
|
|
26199
27591
|
lines.push("(deny file-write*)");
|
|
26200
|
-
lines.push(`(allow file-write* (subpath "${escapeProfilePath(
|
|
27592
|
+
lines.push(`(allow file-write* (subpath "${escapeProfilePath(path27.resolve(cwd))}"))`);
|
|
26201
27593
|
lines.push('(allow file-write* (subpath "/tmp"))');
|
|
26202
27594
|
lines.push('(allow file-write* (subpath "/private/tmp"))');
|
|
26203
27595
|
lines.push('(allow file-write* (subpath "/private/var/folders"))');
|
|
@@ -26259,7 +27651,7 @@ ${result.stderr}` : ""
|
|
|
26259
27651
|
].filter(Boolean).join("\n");
|
|
26260
27652
|
return ok(content, {
|
|
26261
27653
|
command,
|
|
26262
|
-
workdir:
|
|
27654
|
+
workdir: path28.relative(ctx.cwd, workdir) || ".",
|
|
26263
27655
|
exitCode: result.exitCode,
|
|
26264
27656
|
signal: result.signal,
|
|
26265
27657
|
timedOut: result.timedOut,
|
|
@@ -26270,7 +27662,7 @@ ${result.stderr}` : ""
|
|
|
26270
27662
|
function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspace-write" }) {
|
|
26271
27663
|
return new Promise((resolve, reject) => {
|
|
26272
27664
|
const launch = buildSandboxLaunch(command, cwd, sandbox);
|
|
26273
|
-
const child =
|
|
27665
|
+
const child = spawn6(launch.command, launch.args, {
|
|
26274
27666
|
cwd,
|
|
26275
27667
|
env: { ...process.env, ...launch.env },
|
|
26276
27668
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -26310,9 +27702,9 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
|
|
|
26310
27702
|
}
|
|
26311
27703
|
|
|
26312
27704
|
// src/tools/grep.ts
|
|
26313
|
-
import { spawn as
|
|
26314
|
-
import { readdir as readdir2, readFile as
|
|
26315
|
-
import
|
|
27705
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
27706
|
+
import { readdir as readdir2, readFile as readFile15, stat as stat8 } from "node:fs/promises";
|
|
27707
|
+
import path29 from "node:path";
|
|
26316
27708
|
var MAX_MATCHES = 200;
|
|
26317
27709
|
var grepTool = {
|
|
26318
27710
|
name: "grep",
|
|
@@ -26362,7 +27754,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
26362
27754
|
];
|
|
26363
27755
|
if (glob) args.push("--glob", glob);
|
|
26364
27756
|
args.push(pattern, base);
|
|
26365
|
-
const child =
|
|
27757
|
+
const child = spawn7("rg", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
26366
27758
|
const stdout = [];
|
|
26367
27759
|
const stderr = [];
|
|
26368
27760
|
const abort = () => child.kill("SIGTERM");
|
|
@@ -26387,7 +27779,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
26387
27779
|
}
|
|
26388
27780
|
function rewriteRgPath(cwd, line) {
|
|
26389
27781
|
const [file, rest] = splitFirst(line, ":");
|
|
26390
|
-
if (!
|
|
27782
|
+
if (!path29.isAbsolute(file)) return line;
|
|
26391
27783
|
return `${relativeToCwd(cwd, file)}:${rest}`;
|
|
26392
27784
|
}
|
|
26393
27785
|
function splitFirst(value, delimiter) {
|
|
@@ -26402,13 +27794,13 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
26402
27794
|
if (out.length > MAX_MATCHES) return;
|
|
26403
27795
|
const relative = relativeToCwd(cwd, item);
|
|
26404
27796
|
if (isIgnoredPath(relative)) return;
|
|
26405
|
-
const info = await
|
|
27797
|
+
const info = await stat8(item);
|
|
26406
27798
|
if (info.isDirectory()) {
|
|
26407
|
-
for (const entry of await readdir2(item)) await walk2(
|
|
27799
|
+
for (const entry of await readdir2(item)) await walk2(path29.join(item, entry));
|
|
26408
27800
|
return;
|
|
26409
27801
|
}
|
|
26410
27802
|
if (glob && !matchGlob(glob, relative)) return;
|
|
26411
|
-
const buffer = await
|
|
27803
|
+
const buffer = await readFile15(item);
|
|
26412
27804
|
if (buffer.includes(0)) return;
|
|
26413
27805
|
const lines = buffer.toString("utf8").split(/\r?\n/);
|
|
26414
27806
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -26422,8 +27814,8 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
26422
27814
|
}
|
|
26423
27815
|
|
|
26424
27816
|
// src/tools/glob.ts
|
|
26425
|
-
import { readdir as readdir3, stat as
|
|
26426
|
-
import
|
|
27817
|
+
import { readdir as readdir3, stat as stat9 } from "node:fs/promises";
|
|
27818
|
+
import path30 from "node:path";
|
|
26427
27819
|
var MAX_PATHS = 1e3;
|
|
26428
27820
|
var globTool = {
|
|
26429
27821
|
name: "glob",
|
|
@@ -26455,10 +27847,10 @@ async function collectPaths(cwd, root, pattern) {
|
|
|
26455
27847
|
async function walk2(dir) {
|
|
26456
27848
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
26457
27849
|
for (const entry of entries) {
|
|
26458
|
-
const absolute =
|
|
27850
|
+
const absolute = path30.join(dir, entry.name);
|
|
26459
27851
|
const relative = relativeToCwd(cwd, absolute);
|
|
26460
27852
|
if (isIgnoredPath(relative)) continue;
|
|
26461
|
-
const info = await
|
|
27853
|
+
const info = await stat9(absolute);
|
|
26462
27854
|
if (matchGlob(pattern, relative)) out.push({ relative, mtimeMs: info.mtimeMs });
|
|
26463
27855
|
if (entry.isDirectory()) await walk2(absolute);
|
|
26464
27856
|
}
|
|
@@ -26596,7 +27988,7 @@ async function runBraveSearch(config, options) {
|
|
|
26596
27988
|
if (config.country) url.searchParams.set("country", config.country);
|
|
26597
27989
|
if (config.searchLang) url.searchParams.set("search_lang", config.searchLang);
|
|
26598
27990
|
if (config.safeSearch) url.searchParams.set("safesearch", config.safeSearch);
|
|
26599
|
-
const json = await
|
|
27991
|
+
const json = await fetchJson2(url, {
|
|
26600
27992
|
method: "GET",
|
|
26601
27993
|
headers: {
|
|
26602
27994
|
accept: "application/json",
|
|
@@ -26615,7 +28007,7 @@ async function runTavilySearch(config, options) {
|
|
|
26615
28007
|
if (!apiKey.ok) return apiKey;
|
|
26616
28008
|
const maxResults = Math.min(positiveInteger(options.maxResults, config.maxResults ?? 5, "maxResults"), TAVILY_MAX_RESULTS);
|
|
26617
28009
|
const endpoint = config.endpoint ?? defaultConfig.webSearch.providers.tavily.endpoint ?? "https://api.tavily.com/search";
|
|
26618
|
-
const json = await
|
|
28010
|
+
const json = await fetchJson2(endpoint, {
|
|
26619
28011
|
method: "POST",
|
|
26620
28012
|
headers: {
|
|
26621
28013
|
"content-type": "application/json",
|
|
@@ -26641,7 +28033,7 @@ async function runExaSearch(config, options) {
|
|
|
26641
28033
|
if (!apiKey.ok) return apiKey;
|
|
26642
28034
|
const numResults = Math.min(positiveInteger(options.maxResults, config.numResults ?? 5, "maxResults"), EXA_MAX_RESULTS);
|
|
26643
28035
|
const endpoint = config.endpoint ?? defaultConfig.webSearch.providers.exa.endpoint ?? "https://api.exa.ai/search";
|
|
26644
|
-
const json = await
|
|
28036
|
+
const json = await fetchJson2(endpoint, {
|
|
26645
28037
|
method: "POST",
|
|
26646
28038
|
headers: {
|
|
26647
28039
|
"content-type": "application/json",
|
|
@@ -26666,11 +28058,11 @@ async function runExaSearch(config, options) {
|
|
|
26666
28058
|
return searchResult("exa", options.query, results, json.value);
|
|
26667
28059
|
}
|
|
26668
28060
|
function resolveApiKey(provider, config) {
|
|
26669
|
-
const value = config.apiKey
|
|
28061
|
+
const value = config.apiKey || (config.apiKeyEnv ? process.env[config.apiKeyEnv] : void 0);
|
|
26670
28062
|
if (value) return { ok: true, value };
|
|
26671
28063
|
return toolFail(`Missing API key for web_search provider "${provider}". Set webSearch.providers.${provider}.apiKey or set ${config.apiKeyEnv ?? "the configured apiKeyEnv"}.`);
|
|
26672
28064
|
}
|
|
26673
|
-
async function
|
|
28065
|
+
async function fetchJson2(url, options) {
|
|
26674
28066
|
const { fetcher, provider, ...init } = options;
|
|
26675
28067
|
const response = await fetcher(url, init);
|
|
26676
28068
|
const text = await response.text();
|
|
@@ -26688,10 +28080,10 @@ function normalizeBraveResults(raw, limit) {
|
|
|
26688
28080
|
return (data.web?.results ?? []).slice(0, limit).map((item) => {
|
|
26689
28081
|
const result = item;
|
|
26690
28082
|
return {
|
|
26691
|
-
title:
|
|
26692
|
-
url:
|
|
26693
|
-
snippet: cleanSnippet(
|
|
26694
|
-
publishedDate:
|
|
28083
|
+
title: stringValue5(result.title) ?? "Untitled",
|
|
28084
|
+
url: stringValue5(result.url) ?? "",
|
|
28085
|
+
snippet: cleanSnippet(stringValue5(result.description) ?? stringValue5(result.snippet)),
|
|
28086
|
+
publishedDate: stringValue5(result.age)
|
|
26695
28087
|
};
|
|
26696
28088
|
}).filter((item) => item.url.length > 0);
|
|
26697
28089
|
}
|
|
@@ -26700,9 +28092,9 @@ function normalizeTavilyResults(raw, limit) {
|
|
|
26700
28092
|
return (data.results ?? []).slice(0, limit).map((item) => {
|
|
26701
28093
|
const result = item;
|
|
26702
28094
|
return {
|
|
26703
|
-
title:
|
|
26704
|
-
url:
|
|
26705
|
-
snippet: cleanSnippet(
|
|
28095
|
+
title: stringValue5(result.title) ?? "Untitled",
|
|
28096
|
+
url: stringValue5(result.url) ?? "",
|
|
28097
|
+
snippet: cleanSnippet(stringValue5(result.content) ?? stringValue5(result.raw_content)),
|
|
26706
28098
|
score: numberValue3(result.score)
|
|
26707
28099
|
};
|
|
26708
28100
|
}).filter((item) => item.url.length > 0);
|
|
@@ -26712,10 +28104,10 @@ function normalizeExaResults(raw, limit) {
|
|
|
26712
28104
|
return (data.results ?? []).slice(0, limit).map((item) => {
|
|
26713
28105
|
const result = item;
|
|
26714
28106
|
return {
|
|
26715
|
-
title:
|
|
26716
|
-
url:
|
|
28107
|
+
title: stringValue5(result.title) ?? "Untitled",
|
|
28108
|
+
url: stringValue5(result.url) ?? "",
|
|
26717
28109
|
snippet: exaSnippet(result),
|
|
26718
|
-
publishedDate:
|
|
28110
|
+
publishedDate: stringValue5(result.publishedDate),
|
|
26719
28111
|
score: numberValue3(result.score)
|
|
26720
28112
|
};
|
|
26721
28113
|
}).filter((item) => item.url.length > 0);
|
|
@@ -26726,7 +28118,7 @@ function exaSnippet(result) {
|
|
|
26726
28118
|
const joined = highlights.map((item) => typeof item === "string" ? item : "").filter(Boolean).join(" ");
|
|
26727
28119
|
if (joined) return cleanSnippet(joined);
|
|
26728
28120
|
}
|
|
26729
|
-
return cleanSnippet(
|
|
28121
|
+
return cleanSnippet(stringValue5(result.text) ?? stringValue5(result.summary));
|
|
26730
28122
|
}
|
|
26731
28123
|
function searchResult(provider, query, results, raw) {
|
|
26732
28124
|
const content = [
|
|
@@ -26750,7 +28142,7 @@ function formatResult(index, result) {
|
|
|
26750
28142
|
if (result.snippet) lines.push(` Snippet: ${result.snippet}`);
|
|
26751
28143
|
return lines.join("\n");
|
|
26752
28144
|
}
|
|
26753
|
-
function
|
|
28145
|
+
function stringValue5(value) {
|
|
26754
28146
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
26755
28147
|
}
|
|
26756
28148
|
function numberValue3(value) {
|
|
@@ -27457,14 +28849,14 @@ async function runExecutionSession(options) {
|
|
|
27457
28849
|
}
|
|
27458
28850
|
|
|
27459
28851
|
// src/goals/lock.ts
|
|
27460
|
-
import { mkdir as
|
|
27461
|
-
import
|
|
28852
|
+
import { mkdir as mkdir13, open as open3, rm as rm2 } from "node:fs/promises";
|
|
28853
|
+
import path32 from "node:path";
|
|
27462
28854
|
|
|
27463
28855
|
// src/goals/storage.ts
|
|
27464
28856
|
import { createHash as createHash3 } from "node:crypto";
|
|
27465
|
-
import { mkdir as
|
|
28857
|
+
import { mkdir as mkdir12, readFile as readFile16, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
|
|
27466
28858
|
import fs9 from "node:fs";
|
|
27467
|
-
import
|
|
28859
|
+
import path31 from "node:path";
|
|
27468
28860
|
var GoalStore = class {
|
|
27469
28861
|
cwd;
|
|
27470
28862
|
dir;
|
|
@@ -27475,12 +28867,12 @@ var GoalStore = class {
|
|
|
27475
28867
|
this.cwd = cwd;
|
|
27476
28868
|
this.scope = normalizeGoalStoreScope(options.scope);
|
|
27477
28869
|
this.dir = goalStoreDir(cwd, this.scope);
|
|
27478
|
-
this.activePath =
|
|
27479
|
-
this.archiveDir =
|
|
28870
|
+
this.activePath = path31.join(this.dir, "active.json");
|
|
28871
|
+
this.archiveDir = path31.join(this.dir, "archive");
|
|
27480
28872
|
}
|
|
27481
28873
|
async loadActive() {
|
|
27482
28874
|
if (!fs9.existsSync(this.activePath)) return void 0;
|
|
27483
|
-
const text = await
|
|
28875
|
+
const text = await readFile16(this.activePath, "utf8");
|
|
27484
28876
|
try {
|
|
27485
28877
|
return JSON.parse(text);
|
|
27486
28878
|
} catch {
|
|
@@ -27489,36 +28881,36 @@ var GoalStore = class {
|
|
|
27489
28881
|
}
|
|
27490
28882
|
}
|
|
27491
28883
|
async saveActive(state) {
|
|
27492
|
-
await
|
|
27493
|
-
const tmp =
|
|
27494
|
-
await
|
|
28884
|
+
await mkdir12(this.dir, { recursive: true });
|
|
28885
|
+
const tmp = path31.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
|
|
28886
|
+
await writeFile13(tmp, `${JSON.stringify(state, null, 2)}
|
|
27495
28887
|
`, "utf8");
|
|
27496
|
-
await
|
|
28888
|
+
await rename6(tmp, this.activePath);
|
|
27497
28889
|
}
|
|
27498
28890
|
async archiveActive(status = "cleared") {
|
|
27499
28891
|
const state = await this.loadActive();
|
|
27500
28892
|
if (!state) return void 0;
|
|
27501
|
-
await
|
|
28893
|
+
await mkdir12(this.archiveDir, { recursive: true });
|
|
27502
28894
|
const archived = {
|
|
27503
28895
|
...state,
|
|
27504
28896
|
status,
|
|
27505
28897
|
updatedAt: Date.now()
|
|
27506
28898
|
};
|
|
27507
|
-
await
|
|
28899
|
+
await writeFile13(path31.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
|
|
27508
28900
|
`, "utf8");
|
|
27509
28901
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
27510
28902
|
return archived;
|
|
27511
28903
|
}
|
|
27512
28904
|
async backupCorrupted(text) {
|
|
27513
|
-
await
|
|
27514
|
-
const backup =
|
|
27515
|
-
await
|
|
28905
|
+
await mkdir12(this.dir, { recursive: true });
|
|
28906
|
+
const backup = path31.join(this.dir, `active.corrupt.${Date.now()}.json`);
|
|
28907
|
+
await writeFile13(backup, text, "utf8");
|
|
27516
28908
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
27517
28909
|
}
|
|
27518
28910
|
};
|
|
27519
28911
|
function goalStoreDir(cwd, scope) {
|
|
27520
28912
|
const safeScope = normalizeGoalStoreScope(scope);
|
|
27521
|
-
return safeScope ?
|
|
28913
|
+
return safeScope ? path31.join(cwd, ".demian", "goals", "sessions", safeScope) : path31.join(cwd, ".demian", "goals");
|
|
27522
28914
|
}
|
|
27523
28915
|
function normalizeGoalStoreScope(scope) {
|
|
27524
28916
|
const trimmed = scope?.trim();
|
|
@@ -27532,10 +28924,10 @@ function normalizeGoalStoreScope(scope) {
|
|
|
27532
28924
|
var GoalLock = class {
|
|
27533
28925
|
path;
|
|
27534
28926
|
constructor(cwd, scope) {
|
|
27535
|
-
this.path =
|
|
28927
|
+
this.path = path32.join(goalStoreDir(cwd, scope), "active.lock");
|
|
27536
28928
|
}
|
|
27537
28929
|
async acquire() {
|
|
27538
|
-
await
|
|
28930
|
+
await mkdir13(path32.dirname(this.path), { recursive: true });
|
|
27539
28931
|
const handle = await open3(this.path, "wx").catch((error) => {
|
|
27540
28932
|
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
27541
28933
|
if (code === "EEXIST") throw new Error("Another goal is already active in this workspace.");
|
|
@@ -28065,7 +29457,7 @@ function readString(input2, key) {
|
|
|
28065
29457
|
}
|
|
28066
29458
|
|
|
28067
29459
|
// src/external-runtime/session-map.ts
|
|
28068
|
-
import
|
|
29460
|
+
import crypto6 from "node:crypto";
|
|
28069
29461
|
var ClaudeCodeSessionMap = class {
|
|
28070
29462
|
#sessions = /* @__PURE__ */ new Map();
|
|
28071
29463
|
get(key) {
|
|
@@ -28118,13 +29510,13 @@ function normalizeInstruction(value) {
|
|
|
28118
29510
|
return value.replace(/\r\n/g, "\n").trim().split("\n").map((line) => line.replace(/[ \t]+$/g, "")).join("\n");
|
|
28119
29511
|
}
|
|
28120
29512
|
function sha256(value) {
|
|
28121
|
-
return
|
|
29513
|
+
return crypto6.createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
28122
29514
|
}
|
|
28123
29515
|
|
|
28124
29516
|
// src/root-session.ts
|
|
28125
29517
|
import { cp, mkdtemp, rm as rm3 } from "node:fs/promises";
|
|
28126
|
-
import
|
|
28127
|
-
import
|
|
29518
|
+
import os15 from "node:os";
|
|
29519
|
+
import path33 from "node:path";
|
|
28128
29520
|
|
|
28129
29521
|
// src/permissions/presets.ts
|
|
28130
29522
|
var PERMISSION_PRESETS = ["deny", "ask", "auto", "full"];
|
|
@@ -28968,8 +30360,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
28968
30360
|
}
|
|
28969
30361
|
}
|
|
28970
30362
|
async createCoworkIsolatedWorkspace(groupId, memberId) {
|
|
28971
|
-
const root = await mkdtemp(
|
|
28972
|
-
const cwd =
|
|
30363
|
+
const root = await mkdtemp(path33.join(os15.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
|
|
30364
|
+
const cwd = path33.join(root, "workspace");
|
|
28973
30365
|
await cp(this.#options.cwd, cwd, {
|
|
28974
30366
|
recursive: true,
|
|
28975
30367
|
filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
|
|
@@ -29117,7 +30509,7 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
29117
30509
|
const denyTools = ["write_file", "edit_file", "bash", "claudecode.Edit", "claudecode.Write", "claudecode.MultiEdit", "claudecode.Bash"];
|
|
29118
30510
|
return this.withPermissionPreset({
|
|
29119
30511
|
...agent,
|
|
29120
|
-
provider: agent.provider?.profile === "claudecode-
|
|
30512
|
+
provider: agent.provider?.profile === "claudecode" && agent.name === "claudecode-explorer" ? { ...agent.provider, permissionProfile: "explore" } : agent.provider,
|
|
29121
30513
|
tools: agent.tools.filter((tool) => readTools.has(tool)),
|
|
29122
30514
|
permissions: [
|
|
29123
30515
|
...agent.permissions.filter((rule) => !denyTools.includes(rule.tool)),
|
|
@@ -29164,13 +30556,14 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
29164
30556
|
return lines.join("\n");
|
|
29165
30557
|
}
|
|
29166
30558
|
resolveInvocationBackend(profileName, modelOverride, agent) {
|
|
29167
|
-
const
|
|
30559
|
+
const runtime = resolveProviderRuntimeConfig(this.#options.config, { providerName: profileName, model: modelOverride });
|
|
30560
|
+
const providerConfig = runtime.providerConfig;
|
|
29168
30561
|
if (this.#options.resolveExecutionBackend) return this.#options.resolveExecutionBackend(profileName, providerConfig, modelOverride);
|
|
29169
30562
|
if (this.#options.resolveProvider) {
|
|
29170
|
-
const resolved = this.#options.resolveProvider(profileName, providerConfig,
|
|
30563
|
+
const resolved = this.#options.resolveProvider(profileName, providerConfig, runtime.model);
|
|
29171
30564
|
return { kind: "provider", ...resolved };
|
|
29172
30565
|
}
|
|
29173
|
-
return resolveExecutionBackend(providerConfig, { model:
|
|
30566
|
+
return resolveExecutionBackend(providerConfig, { model: runtime.model, config: this.#options.config, agent });
|
|
29174
30567
|
}
|
|
29175
30568
|
loadAgentSession(agent, providerProfile, model, sessionScope, cwd = this.#options.cwd) {
|
|
29176
30569
|
const key = `${this.id}:${sessionScope ?? "delegate"}:${agent.name}:${providerProfile}:${model}:${cwd}`;
|
|
@@ -29272,17 +30665,17 @@ function findDependencyCycle(group) {
|
|
|
29272
30665
|
const byId = new Map(group.map((member) => [member.memberId, member]));
|
|
29273
30666
|
const visiting = /* @__PURE__ */ new Set();
|
|
29274
30667
|
const visited = /* @__PURE__ */ new Set();
|
|
29275
|
-
const
|
|
30668
|
+
const path36 = [];
|
|
29276
30669
|
const visit = (id) => {
|
|
29277
|
-
if (visiting.has(id)) return [...
|
|
30670
|
+
if (visiting.has(id)) return [...path36.slice(path36.indexOf(id)), id];
|
|
29278
30671
|
if (visited.has(id)) return void 0;
|
|
29279
30672
|
visiting.add(id);
|
|
29280
|
-
|
|
30673
|
+
path36.push(id);
|
|
29281
30674
|
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
29282
30675
|
const cycle = visit(dep);
|
|
29283
30676
|
if (cycle) return cycle;
|
|
29284
30677
|
}
|
|
29285
|
-
|
|
30678
|
+
path36.pop();
|
|
29286
30679
|
visiting.delete(id);
|
|
29287
30680
|
visited.add(id);
|
|
29288
30681
|
return void 0;
|
|
@@ -29323,7 +30716,7 @@ function scopeStaticPrefix(pattern) {
|
|
|
29323
30716
|
return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
|
|
29324
30717
|
}
|
|
29325
30718
|
function shouldCopyIntoCoworkWorkspace(root, source) {
|
|
29326
|
-
const relative =
|
|
30719
|
+
const relative = path33.relative(root, source).split(path33.sep).join("/");
|
|
29327
30720
|
if (!relative) return true;
|
|
29328
30721
|
const parts = relative.split("/");
|
|
29329
30722
|
return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
|
|
@@ -29718,52 +31111,119 @@ function finitePositiveInteger(value) {
|
|
|
29718
31111
|
}
|
|
29719
31112
|
|
|
29720
31113
|
// src/ui/plain/interactive.ts
|
|
29721
|
-
import
|
|
31114
|
+
import readline4 from "node:readline";
|
|
29722
31115
|
|
|
29723
31116
|
// src/ui/settings.ts
|
|
29724
|
-
function providerOptions(config) {
|
|
29725
|
-
|
|
29726
|
-
|
|
29727
|
-
|
|
29728
|
-
|
|
29729
|
-
|
|
29730
|
-
|
|
29731
|
-
|
|
29732
|
-
|
|
29733
|
-
|
|
31117
|
+
function providerOptions(config, catalogs = {}) {
|
|
31118
|
+
const orderedNames = sortProviderNames(Object.keys(config.providers));
|
|
31119
|
+
const order = new Map(orderedNames.map((name, index) => [name, index]));
|
|
31120
|
+
return orderedNames.map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden && !(provider.type === "claudecode" && provider.runtime === "cli")).map(([name, provider]) => {
|
|
31121
|
+
const catalog = catalogs[name];
|
|
31122
|
+
const available = providerCatalogAvailable(catalog);
|
|
31123
|
+
const profiles = catalog?.models.length ? catalog.models.map((model) => ({
|
|
31124
|
+
name: model.name,
|
|
31125
|
+
displayName: model.displayName ?? model.name,
|
|
31126
|
+
model: model.model,
|
|
31127
|
+
description: model.description,
|
|
31128
|
+
source: model.source,
|
|
31129
|
+
isDefault: model.isDefault
|
|
31130
|
+
})) : providerModelProfiles(name, provider);
|
|
31131
|
+
const modelProfiles = profiles.map((profile) => {
|
|
31132
|
+
const displayName = displayModelForProfile(profile);
|
|
31133
|
+
return {
|
|
31134
|
+
...profile,
|
|
31135
|
+
label: available ? displayName : unavailableLabel(displayName),
|
|
31136
|
+
available
|
|
31137
|
+
};
|
|
31138
|
+
});
|
|
31139
|
+
const models = modelProfiles.length ? [...new Set(modelProfiles.map((item) => item.model).filter((item) => typeof item === "string" && item.trim()))] : providerModelOptions(name, provider);
|
|
31140
|
+
const defaultModel = defaultDisplayModelForProvider(name, provider);
|
|
31141
|
+
return {
|
|
31142
|
+
name,
|
|
31143
|
+
label: available ? name : unavailableLabel(name),
|
|
31144
|
+
model: defaultModel,
|
|
31145
|
+
modelLabel: available || !defaultModel ? defaultModel : unavailableLabel(defaultModel),
|
|
31146
|
+
models,
|
|
31147
|
+
modelProfiles,
|
|
31148
|
+
type: provider.type,
|
|
31149
|
+
runtime: provider.type === "claudecode" ? provider.runtime : void 0,
|
|
31150
|
+
permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
|
|
31151
|
+
ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
|
|
31152
|
+
available,
|
|
31153
|
+
catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
|
|
31154
|
+
};
|
|
31155
|
+
}).sort((left, right) => {
|
|
31156
|
+
if (left.available !== right.available) return left.available ? -1 : 1;
|
|
31157
|
+
return (order.get(left.name) ?? 0) - (order.get(right.name) ?? 0);
|
|
31158
|
+
});
|
|
29734
31159
|
}
|
|
31160
|
+
var MISSING_SELECTION_PLACEHOLDER = "-";
|
|
29735
31161
|
function createInteractiveModelSelection(config, flags = {}, saved) {
|
|
29736
31162
|
const savedProviderName = saved?.providerName === "claudecode-plan" ? "claudecode" : saved?.providerName;
|
|
29737
|
-
const
|
|
29738
|
-
|
|
29739
|
-
|
|
29740
|
-
|
|
29741
|
-
|
|
31163
|
+
const savedProviderExists = savedProviderName && config.providers[savedProviderName] && !config.providers[savedProviderName]?.hidden;
|
|
31164
|
+
if (saved?.providerName && !flags.provider && !savedProviderExists) {
|
|
31165
|
+
return {
|
|
31166
|
+
providerName: saved.providerName,
|
|
31167
|
+
providerSource: "saved",
|
|
31168
|
+
model: MISSING_SELECTION_PLACEHOLDER,
|
|
31169
|
+
modelSource: "saved"
|
|
31170
|
+
};
|
|
31171
|
+
}
|
|
31172
|
+
const providerName = flags.provider ?? (savedProviderExists ? savedProviderName : config.defaultProvider);
|
|
31173
|
+
let providerConfig;
|
|
31174
|
+
try {
|
|
31175
|
+
providerConfig = resolveProviderConfig(config, providerName);
|
|
31176
|
+
} catch {
|
|
31177
|
+
return {
|
|
31178
|
+
providerName,
|
|
31179
|
+
providerSource: flags.provider ? "flag" : "config",
|
|
31180
|
+
model: MISSING_SELECTION_PLACEHOLDER,
|
|
31181
|
+
modelSource: "config"
|
|
31182
|
+
};
|
|
31183
|
+
}
|
|
31184
|
+
const usesSavedProvider = !flags.provider && savedProviderExists && providerName === savedProviderName;
|
|
29742
31185
|
const savedModel = usesSavedProvider && saved?.model ? saved.model : void 0;
|
|
31186
|
+
const profiles = providerModelProfiles(providerName, providerConfig);
|
|
31187
|
+
const savedProfile = savedModel ? profiles.find((profile) => profile.model === savedModel || profile.displayName === savedModel || profile.name === savedModel) : void 0;
|
|
31188
|
+
const defaultModel = displayModelForProfile(profiles[0] ?? { name: "", displayName: "", model: "" }) || defaultDisplayModelForProvider(providerName, providerConfig);
|
|
31189
|
+
const flagProfile = flags.model ? profiles.find((profile) => profile.model === flags.model || profile.displayName === flags.model || profile.name === flags.model) : void 0;
|
|
31190
|
+
const selectedProfile = flags.model ? flagProfile : savedModel ? savedProfile : profiles[0];
|
|
31191
|
+
const model = flags.model ?? (savedProfile ? displayModelForProfile(savedProfile) : savedModel) ?? defaultModel;
|
|
29743
31192
|
return {
|
|
29744
31193
|
providerName,
|
|
29745
31194
|
providerSource: flags.provider ? "flag" : usesSavedProvider ? "saved" : "config",
|
|
29746
|
-
model
|
|
29747
|
-
modelSource: flags.model ? "flag" : savedModel ? "saved" : "config"
|
|
31195
|
+
model,
|
|
31196
|
+
modelSource: flags.model ? "flag" : savedModel ? "saved" : "config",
|
|
31197
|
+
modelProfileName: selectedProfile?.name
|
|
29748
31198
|
};
|
|
29749
31199
|
}
|
|
29750
31200
|
function selectProvider(config, selection, providerName) {
|
|
29751
31201
|
const providerConfig = resolveProviderConfig(config, providerName);
|
|
31202
|
+
const profiles = providerModelProfiles(providerName, providerConfig);
|
|
31203
|
+
const profile = profiles[0];
|
|
29752
31204
|
return {
|
|
29753
31205
|
...selection,
|
|
29754
31206
|
providerName,
|
|
29755
31207
|
providerSource: "interactive",
|
|
29756
|
-
model: providerConfig
|
|
29757
|
-
modelSource: "config"
|
|
31208
|
+
model: profile ? displayModelForProfile(profile) : defaultDisplayModelForProvider(providerName, providerConfig),
|
|
31209
|
+
modelSource: "config",
|
|
31210
|
+
modelProfileName: profile?.name
|
|
29758
31211
|
};
|
|
29759
31212
|
}
|
|
29760
31213
|
function selectModel(selection, model) {
|
|
29761
31214
|
return {
|
|
29762
31215
|
...selection,
|
|
29763
31216
|
model,
|
|
29764
|
-
modelSource: "interactive"
|
|
31217
|
+
modelSource: "interactive",
|
|
31218
|
+
modelProfileName: void 0
|
|
29765
31219
|
};
|
|
29766
31220
|
}
|
|
31221
|
+
function providerCatalogAvailable(catalog) {
|
|
31222
|
+
return catalog ? catalog.status === "ready" && catalog.models.length > 0 : true;
|
|
31223
|
+
}
|
|
31224
|
+
function unavailableLabel(value) {
|
|
31225
|
+
return `(n/a) ${value}`;
|
|
31226
|
+
}
|
|
29767
31227
|
|
|
29768
31228
|
// src/ui/plain/interactive.ts
|
|
29769
31229
|
var PlainInteractivePrompt = class {
|
|
@@ -29775,7 +31235,7 @@ var PlainInteractivePrompt = class {
|
|
|
29775
31235
|
#exitRequested = false;
|
|
29776
31236
|
constructor(input2 = process.stdin, output2 = process.stderr) {
|
|
29777
31237
|
this.#output = output2;
|
|
29778
|
-
this.#rl =
|
|
31238
|
+
this.#rl = readline4.createInterface({ input: input2, output: output2 });
|
|
29779
31239
|
this.#rl.on("line", (line) => this.#handleLine(line));
|
|
29780
31240
|
}
|
|
29781
31241
|
get exitRequested() {
|
|
@@ -29872,13 +31332,13 @@ async function askModel(promptUi, selection) {
|
|
|
29872
31332
|
}
|
|
29873
31333
|
|
|
29874
31334
|
// src/ui/preferences.ts
|
|
29875
|
-
import { mkdir as
|
|
31335
|
+
import { mkdir as mkdir14, readFile as readFile17, writeFile as writeFile14 } from "node:fs/promises";
|
|
29876
31336
|
import fs10 from "node:fs";
|
|
29877
|
-
import
|
|
31337
|
+
import path34 from "node:path";
|
|
29878
31338
|
var UiPreferenceStore = class {
|
|
29879
31339
|
filePath;
|
|
29880
31340
|
constructor(cwd, filePath) {
|
|
29881
|
-
this.filePath = filePath ?
|
|
31341
|
+
this.filePath = filePath ? path34.resolve(cwd, filePath) : path34.join(cwd, ".demian", "preferences.json");
|
|
29882
31342
|
}
|
|
29883
31343
|
async load() {
|
|
29884
31344
|
if (!fs10.existsSync(this.filePath)) return void 0;
|
|
@@ -29896,13 +31356,13 @@ var UiPreferenceStore = class {
|
|
|
29896
31356
|
model: selection.model,
|
|
29897
31357
|
updatedAt: Date.now()
|
|
29898
31358
|
};
|
|
29899
|
-
await
|
|
29900
|
-
await
|
|
31359
|
+
await mkdir14(path34.dirname(this.filePath), { recursive: true });
|
|
31360
|
+
await writeFile14(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
29901
31361
|
`, { mode: 384 });
|
|
29902
31362
|
}
|
|
29903
31363
|
async #read() {
|
|
29904
31364
|
try {
|
|
29905
|
-
return JSON.parse(await
|
|
31365
|
+
return JSON.parse(await readFile17(this.filePath, "utf8"));
|
|
29906
31366
|
} catch {
|
|
29907
31367
|
return void 0;
|
|
29908
31368
|
}
|
|
@@ -29918,6 +31378,8 @@ function preferenceKey(selection) {
|
|
|
29918
31378
|
|
|
29919
31379
|
// src/cli.ts
|
|
29920
31380
|
async function main(argv = process.argv.slice(2)) {
|
|
31381
|
+
const configCommandResult = await maybeRunConfigCommand(argv);
|
|
31382
|
+
if (configCommandResult !== void 0) return configCommandResult;
|
|
29921
31383
|
const doctorResult = await maybeRunDoctorCommand(argv);
|
|
29922
31384
|
if (doctorResult !== void 0) return doctorResult;
|
|
29923
31385
|
const flags = parseArgs(argv);
|
|
@@ -29925,7 +31387,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
29925
31387
|
printHelp();
|
|
29926
31388
|
return 0;
|
|
29927
31389
|
}
|
|
29928
|
-
|
|
31390
|
+
if (!flags.noWizard) await runFirstRunWizard().catch(() => void 0);
|
|
31391
|
+
const cwd = path35.resolve(flags.cwd ?? process.cwd());
|
|
29929
31392
|
const config = await loadConfig({ cwd, configPath: flags.configPath });
|
|
29930
31393
|
const agentName = flags.agent ?? config.defaultAgent;
|
|
29931
31394
|
const preferenceStore = new UiPreferenceStore(cwd);
|
|
@@ -30014,9 +31477,9 @@ async function runPlainTask(options) {
|
|
|
30014
31477
|
config: config.goals,
|
|
30015
31478
|
eventBus,
|
|
30016
31479
|
titleGenerator: async (input2) => {
|
|
30017
|
-
const
|
|
30018
|
-
const resolved = resolveExecutionBackend(
|
|
30019
|
-
model:
|
|
31480
|
+
const runtime2 = resolveProviderRuntimeConfig(config, selection);
|
|
31481
|
+
const resolved = resolveExecutionBackend(runtime2.providerConfig, {
|
|
31482
|
+
model: runtime2.model,
|
|
30020
31483
|
onRetry: (event) => eventBus.emit({
|
|
30021
31484
|
type: "provider.retry",
|
|
30022
31485
|
sessionId: "goal-title",
|
|
@@ -30106,10 +31569,10 @@ async function runPlainTask(options) {
|
|
|
30106
31569
|
`);
|
|
30107
31570
|
return { history: interactiveHistoryFromRunMessages(result2.messages), lastExternalSessionKey: options.lastExternalSessionKey };
|
|
30108
31571
|
}
|
|
30109
|
-
const providerConfig = resolveProviderConfig(config, providerName);
|
|
30110
31572
|
const runtimeAgent = activeGoal ? withGoalTools(agent) : agent;
|
|
30111
|
-
const
|
|
30112
|
-
|
|
31573
|
+
const runtime = resolveProviderRuntimeConfig(config, selection);
|
|
31574
|
+
const backend = resolveExecutionBackend(runtime.providerConfig, {
|
|
31575
|
+
model: runtime.model,
|
|
30113
31576
|
config,
|
|
30114
31577
|
agent: runtimeAgent,
|
|
30115
31578
|
onRetry: (event) => eventBus.emit({
|
|
@@ -30213,6 +31676,10 @@ function parseArgs(argv) {
|
|
|
30213
31676
|
out.transcript = false;
|
|
30214
31677
|
continue;
|
|
30215
31678
|
}
|
|
31679
|
+
if (arg === "--no-wizard") {
|
|
31680
|
+
out.noWizard = true;
|
|
31681
|
+
continue;
|
|
31682
|
+
}
|
|
30216
31683
|
if (arg === "--goal") {
|
|
30217
31684
|
out.goal = true;
|
|
30218
31685
|
continue;
|
|
@@ -30287,6 +31754,10 @@ function printHelp() {
|
|
|
30287
31754
|
|
|
30288
31755
|
Usage:
|
|
30289
31756
|
demian-plain [flags]
|
|
31757
|
+
demian-plain config init [--force]
|
|
31758
|
+
demian-plain config add-provider <preset|openai-compatible> [--name <name>]
|
|
31759
|
+
demian-plain config add-model <provider> --name <name> --model <model>
|
|
31760
|
+
demian-plain auth login <codex|claudecode>
|
|
30290
31761
|
demian-plain doctor policies --upgrade-namespaces [--write]
|
|
30291
31762
|
|
|
30292
31763
|
Default:
|
|
@@ -30305,7 +31776,7 @@ Flags:
|
|
|
30305
31776
|
--single-agent force the current single-agent runtime
|
|
30306
31777
|
--multi-agent enable root-owned delegate_agent runtime
|
|
30307
31778
|
--agent <name> general, build, plan, execute, or a configured custom agent
|
|
30308
|
-
--provider <name> openai,
|
|
31779
|
+
--provider <name> openai, anthropic, gemini, groq, azure, lmstudio, ollama-local, ollama-cloud, llamacpp, vllm, codex, claudecode
|
|
30309
31780
|
--model <name> override configured model
|
|
30310
31781
|
--max-turns <n> maximum model/tool loop turns
|
|
30311
31782
|
--cwd <path> workspace directory
|
|
@@ -30318,7 +31789,8 @@ Flags:
|
|
|
30318
31789
|
--persistent-grants persist "always" permission grants
|
|
30319
31790
|
--no-persistent-grants
|
|
30320
31791
|
keep "always" grants session-local
|
|
30321
|
-
--no-transcript disable
|
|
31792
|
+
--no-transcript disable ~/.demian transcript writes
|
|
31793
|
+
--no-wizard skip the first-run config wizard
|
|
30322
31794
|
--goal run the prompt as an explicit /goal objective
|
|
30323
31795
|
/ralph-loop --fresh-context is single-agent only
|
|
30324
31796
|
--config <path> load an additional config file
|
|
@@ -30327,6 +31799,13 @@ Flags:
|
|
|
30327
31799
|
Doctor:
|
|
30328
31800
|
doctor policies --upgrade-namespaces
|
|
30329
31801
|
rewrite prefix-less permission rules as demian.<tool>
|
|
31802
|
+
|
|
31803
|
+
Config:
|
|
31804
|
+
config init create ~/.demian/config.json
|
|
31805
|
+
config models <provider> [--refresh]
|
|
31806
|
+
list configured or remote provider models
|
|
31807
|
+
auth login codex|claudecode
|
|
31808
|
+
start the external OAuth login flow
|
|
30330
31809
|
`);
|
|
30331
31810
|
}
|
|
30332
31811
|
if (import.meta.url === `file://${process.argv[1]}`) {
|