demian-cli 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1799 -364
- package/dist/index.mjs +995 -259
- package/dist/tui.mjs +21951 -20437
- package/dist/vscode-worker.mjs +1586 -290
- 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",
|
|
@@ -23582,73 +23706,102 @@ var defaultConfig = {
|
|
|
23582
23706
|
providers: {
|
|
23583
23707
|
openai: {
|
|
23584
23708
|
type: "openai-compatible",
|
|
23585
|
-
model: "openai-model-name",
|
|
23586
23709
|
baseURL: "https://api.openai.com/v1",
|
|
23587
|
-
apiKeyEnv: "OPENAI_API_KEY"
|
|
23710
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
23711
|
+
catalog: {
|
|
23712
|
+
type: "openai-models",
|
|
23713
|
+
endpoint: "https://api.openai.com/v1/models"
|
|
23714
|
+
}
|
|
23715
|
+
},
|
|
23716
|
+
anthropic: {
|
|
23717
|
+
type: "anthropic",
|
|
23718
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
23719
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
23720
|
+
catalog: {
|
|
23721
|
+
type: "anthropic-models",
|
|
23722
|
+
endpoint: "https://api.anthropic.com/v1/models"
|
|
23723
|
+
}
|
|
23588
23724
|
},
|
|
23589
23725
|
gemini: {
|
|
23590
23726
|
type: "openai-compatible",
|
|
23591
|
-
model: "gemini-model-name",
|
|
23592
23727
|
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
23593
|
-
apiKeyEnv: "
|
|
23728
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
23729
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
23730
|
+
catalog: {
|
|
23731
|
+
type: "gemini-openai-models",
|
|
23732
|
+
endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models"
|
|
23733
|
+
}
|
|
23594
23734
|
},
|
|
23595
|
-
|
|
23735
|
+
groq: {
|
|
23596
23736
|
type: "openai-compatible",
|
|
23597
|
-
|
|
23598
|
-
|
|
23599
|
-
|
|
23600
|
-
|
|
23737
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
23738
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
23739
|
+
catalog: {
|
|
23740
|
+
type: "groq-models",
|
|
23741
|
+
endpoint: "https://api.groq.com/openai/v1/models"
|
|
23742
|
+
}
|
|
23743
|
+
},
|
|
23744
|
+
azure: {
|
|
23745
|
+
type: "openai-compatible",
|
|
23746
|
+
auth: { type: "api-key", header: "api-key" },
|
|
23747
|
+
modelProfiles: [
|
|
23748
|
+
{
|
|
23749
|
+
name: "azure-example",
|
|
23750
|
+
displayName: "Azure example",
|
|
23751
|
+
model: "azure-deployment-name",
|
|
23752
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
23753
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
23754
|
+
}
|
|
23755
|
+
]
|
|
23601
23756
|
},
|
|
23602
23757
|
lmstudio: {
|
|
23603
23758
|
type: "openai-compatible",
|
|
23604
|
-
model: "local-coder-model",
|
|
23605
23759
|
baseURL: "http://localhost:1234/v1",
|
|
23606
|
-
apiKey: "lm-studio"
|
|
23760
|
+
apiKey: "lm-studio",
|
|
23761
|
+
catalog: {
|
|
23762
|
+
type: "openai-compatible-models",
|
|
23763
|
+
endpoint: "http://localhost:1234/v1/models"
|
|
23764
|
+
}
|
|
23607
23765
|
},
|
|
23608
|
-
|
|
23766
|
+
"ollama-local": {
|
|
23609
23767
|
type: "openai-compatible",
|
|
23610
|
-
|
|
23611
|
-
|
|
23612
|
-
|
|
23768
|
+
baseURL: "http://localhost:11434/v1",
|
|
23769
|
+
apiKey: "ollama",
|
|
23770
|
+
quirks: { omitTemperature: true },
|
|
23771
|
+
catalog: {
|
|
23772
|
+
type: "openai-compatible-models",
|
|
23773
|
+
endpoint: "http://localhost:11434/v1/models"
|
|
23774
|
+
}
|
|
23775
|
+
},
|
|
23776
|
+
"ollama-cloud": {
|
|
23777
|
+
type: "ollama",
|
|
23778
|
+
baseURL: "https://ollama.com/api",
|
|
23779
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
23780
|
+
catalog: {
|
|
23781
|
+
type: "ollama-tags",
|
|
23782
|
+
endpoint: "https://ollama.com/api/tags"
|
|
23783
|
+
}
|
|
23613
23784
|
},
|
|
23614
23785
|
llamacpp: {
|
|
23615
23786
|
type: "openai-compatible",
|
|
23616
|
-
model: "local-coder-model",
|
|
23617
23787
|
baseURL: "http://localhost:8080/v1",
|
|
23618
|
-
apiKey: "llama.cpp"
|
|
23619
|
-
|
|
23620
|
-
|
|
23621
|
-
|
|
23622
|
-
|
|
23623
|
-
baseURL: "https://openrouter.ai/api/v1",
|
|
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"
|
|
23788
|
+
apiKey: "llama.cpp",
|
|
23789
|
+
catalog: {
|
|
23790
|
+
type: "openai-compatible-models",
|
|
23791
|
+
endpoint: "http://localhost:8080/v1/models"
|
|
23792
|
+
}
|
|
23637
23793
|
},
|
|
23638
|
-
|
|
23794
|
+
vllm: {
|
|
23639
23795
|
type: "openai-compatible",
|
|
23640
|
-
|
|
23641
|
-
|
|
23642
|
-
|
|
23643
|
-
|
|
23644
|
-
|
|
23645
|
-
|
|
23646
|
-
model: "anthropic-model-name",
|
|
23647
|
-
apiKeyEnv: "ANTHROPIC_API_KEY"
|
|
23796
|
+
baseURL: "http://localhost:8000/v1",
|
|
23797
|
+
apiKey: "vllm",
|
|
23798
|
+
catalog: {
|
|
23799
|
+
type: "openai-compatible-models",
|
|
23800
|
+
endpoint: "http://localhost:8000/v1/models"
|
|
23801
|
+
}
|
|
23648
23802
|
},
|
|
23649
23803
|
codex: {
|
|
23650
23804
|
type: "codex",
|
|
23651
|
-
model: "gpt-5.1-codex",
|
|
23652
23805
|
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
23653
23806
|
authStore: "auto",
|
|
23654
23807
|
allowApiKeyFallback: false,
|
|
@@ -23660,6 +23813,9 @@ var defaultConfig = {
|
|
|
23660
23813
|
effort: "medium",
|
|
23661
23814
|
summary: "auto"
|
|
23662
23815
|
}
|
|
23816
|
+
},
|
|
23817
|
+
catalog: {
|
|
23818
|
+
type: "codex-oauth-models"
|
|
23663
23819
|
}
|
|
23664
23820
|
},
|
|
23665
23821
|
claudecode: {
|
|
@@ -23678,67 +23834,37 @@ var defaultConfig = {
|
|
|
23678
23834
|
useBareMode: false,
|
|
23679
23835
|
usageLedgerScope: "process",
|
|
23680
23836
|
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
23837
|
abortPolicy: "record-only",
|
|
23721
|
-
|
|
23838
|
+
catalog: {
|
|
23839
|
+
type: "claudecode-supported-models"
|
|
23840
|
+
}
|
|
23722
23841
|
}
|
|
23723
23842
|
}
|
|
23724
23843
|
};
|
|
23725
23844
|
async function loadConfig(options) {
|
|
23726
23845
|
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);
|
|
23846
|
+
const userConfigDir = path13.join(os7.homedir(), ".demian");
|
|
23847
|
+
const configPaths = [path13.join(userConfigDir, "config.json"), path13.join(userConfigDir, "config.jsond"), options.configPath].filter(Boolean);
|
|
23736
23848
|
for (const filePath of configPaths) {
|
|
23737
23849
|
if (!fs7.existsSync(filePath)) continue;
|
|
23738
|
-
|
|
23850
|
+
const parsed = parseConfigText(await readFile6(filePath, "utf8"), filePath);
|
|
23851
|
+
warnIfV1Config(parsed, filePath);
|
|
23852
|
+
configs.push(parsed);
|
|
23739
23853
|
}
|
|
23740
23854
|
return configs.reduce((acc, item) => mergeConfig(acc, item), structuredClone(defaultConfig));
|
|
23741
23855
|
}
|
|
23856
|
+
function warnIfV1Config(parsed, filePath) {
|
|
23857
|
+
if (parsed.version === 2) return;
|
|
23858
|
+
const providers = parsed.providers ?? {};
|
|
23859
|
+
const hasLegacyShape = Object.values(providers).some((provider) => {
|
|
23860
|
+
const candidate = provider;
|
|
23861
|
+
return (typeof candidate.model === "string" || Array.isArray(candidate.models)) && !Array.isArray(candidate.modelProfiles);
|
|
23862
|
+
});
|
|
23863
|
+
if (!hasLegacyShape && parsed.version === void 0 && Object.keys(providers).length === 0) return;
|
|
23864
|
+
if (!hasLegacyShape) return;
|
|
23865
|
+
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.
|
|
23866
|
+
`);
|
|
23867
|
+
}
|
|
23742
23868
|
function parseConfigText(text, filePath) {
|
|
23743
23869
|
try {
|
|
23744
23870
|
return JSON.parse(filePath.endsWith(".jsond") ? stripJsonD(text) : text);
|
|
@@ -23945,7 +24071,127 @@ function mergeConfig(base, overlay) {
|
|
|
23945
24071
|
};
|
|
23946
24072
|
}
|
|
23947
24073
|
function normalizeProviderAlias(providerName) {
|
|
23948
|
-
|
|
24074
|
+
if (providerName === "claudecode-plan" || providerName === "claudecode-subagent") return "claudecode";
|
|
24075
|
+
if (providerName === "ollama") return "ollama-local";
|
|
24076
|
+
return providerName;
|
|
24077
|
+
}
|
|
24078
|
+
function sortProviderNames(names) {
|
|
24079
|
+
const order = new Map(BUILTIN_PROVIDER_ORDER.map((name, index) => [name, index]));
|
|
24080
|
+
return [...names].sort((left, right) => {
|
|
24081
|
+
const leftIndex = order.get(left);
|
|
24082
|
+
const rightIndex = order.get(right);
|
|
24083
|
+
if (leftIndex !== void 0 && rightIndex !== void 0) return leftIndex - rightIndex;
|
|
24084
|
+
if (leftIndex !== void 0) return -1;
|
|
24085
|
+
if (rightIndex !== void 0) return 1;
|
|
24086
|
+
return left.localeCompare(right);
|
|
24087
|
+
});
|
|
24088
|
+
}
|
|
24089
|
+
function providerModelProfiles(providerName, providerConfig) {
|
|
24090
|
+
const explicit = Array.isArray(providerConfig.modelProfiles) ? providerConfig.modelProfiles.filter((profile) => typeof profile?.model === "string" && profile.model.trim()).map((profile) => ({
|
|
24091
|
+
...profile,
|
|
24092
|
+
name: profile.name?.trim() || profile.displayName?.trim() || profile.model.trim(),
|
|
24093
|
+
displayName: profile.displayName?.trim() || profile.name?.trim() || profile.model.trim(),
|
|
24094
|
+
model: profile.model.trim()
|
|
24095
|
+
})) : [];
|
|
24096
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
24097
|
+
const seenDisplayNames = /* @__PURE__ */ new Set();
|
|
24098
|
+
const seenModels = /* @__PURE__ */ new Set();
|
|
24099
|
+
const out = [];
|
|
24100
|
+
for (const profile of explicit) {
|
|
24101
|
+
if (seenNames.has(profile.name)) {
|
|
24102
|
+
process.stderr.write(`[demian] warning: provider ${providerName} has duplicate modelProfile name "${profile.name}". Later entry ignored.
|
|
24103
|
+
`);
|
|
24104
|
+
continue;
|
|
24105
|
+
}
|
|
24106
|
+
if (profile.displayName && seenDisplayNames.has(profile.displayName)) {
|
|
24107
|
+
process.stderr.write(`[demian] warning: provider ${providerName} has duplicate modelProfile displayName "${profile.displayName}". Later entry ignored.
|
|
24108
|
+
`);
|
|
24109
|
+
continue;
|
|
24110
|
+
}
|
|
24111
|
+
seenNames.add(profile.name);
|
|
24112
|
+
if (profile.displayName) seenDisplayNames.add(profile.displayName);
|
|
24113
|
+
seenModels.add(profile.model);
|
|
24114
|
+
out.push(profile);
|
|
24115
|
+
}
|
|
24116
|
+
const legacy = [...typeof providerConfig.model === "string" && providerConfig.model.trim() ? [providerConfig.model.trim()] : [], ...(providerConfig.models ?? []).filter((model) => typeof model === "string" && model.trim())];
|
|
24117
|
+
for (const model of legacy) {
|
|
24118
|
+
const trimmed = model.trim();
|
|
24119
|
+
if (!trimmed || seenModels.has(trimmed)) continue;
|
|
24120
|
+
seenModels.add(trimmed);
|
|
24121
|
+
const name = trimmed;
|
|
24122
|
+
if (!seenNames.has(name)) seenNames.add(name);
|
|
24123
|
+
out.push({ name, displayName: name, model: trimmed });
|
|
24124
|
+
}
|
|
24125
|
+
const fallback = fallbackModelForProvider(providerName, providerConfig);
|
|
24126
|
+
if (out.length === 0 && fallback) out.push({ name: fallback, displayName: fallback, model: fallback });
|
|
24127
|
+
return out;
|
|
24128
|
+
}
|
|
24129
|
+
function providerModelOptions(providerName, providerConfig) {
|
|
24130
|
+
return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => profile.displayName ?? profile.name ?? profile.model);
|
|
24131
|
+
}
|
|
24132
|
+
function defaultModelForProvider(providerName, providerConfig) {
|
|
24133
|
+
return providerModelProfiles(providerName, providerConfig)[0]?.model ?? "";
|
|
24134
|
+
}
|
|
24135
|
+
function resolveConfiguredApiKey(providerConfig) {
|
|
24136
|
+
if (providerConfig.apiKey) return { value: providerConfig.apiKey };
|
|
24137
|
+
for (const envName of [providerConfig.apiKeyEnv, ...providerConfig.apiKeyEnvAliases ?? []]) {
|
|
24138
|
+
if (!envName) continue;
|
|
24139
|
+
const value = process.env[envName];
|
|
24140
|
+
if (value) return { value, envName };
|
|
24141
|
+
}
|
|
24142
|
+
return { envName: providerConfig.apiKeyEnv ?? providerConfig.apiKeyEnvAliases?.[0] };
|
|
24143
|
+
}
|
|
24144
|
+
function resolveProviderRuntimeConfig(config, selection) {
|
|
24145
|
+
const providerName = normalizeProviderAlias(selection.providerName);
|
|
24146
|
+
const providerConfig = resolveProviderConfig(config, providerName);
|
|
24147
|
+
const profiles = providerModelProfiles(providerName, providerConfig);
|
|
24148
|
+
let profile;
|
|
24149
|
+
if (selection.modelProfileName) profile = profiles.find((item) => item.name === selection.modelProfileName);
|
|
24150
|
+
if (!profile && selection.model) profile = profiles.find((item) => item.displayName === selection.model);
|
|
24151
|
+
if (!profile && selection.model) profile = profiles.find((item) => item.model === selection.model);
|
|
24152
|
+
if (!profile) profile = profiles[0];
|
|
24153
|
+
if (!profile) {
|
|
24154
|
+
const model = selection.model ?? defaultModelForProvider(providerName, providerConfig);
|
|
24155
|
+
if (!model) throw new Error(`Provider ${providerName} has no model. Run \`demian config models ${providerName} --refresh\` or add a model profile.`);
|
|
24156
|
+
return { providerName, providerConfig, model };
|
|
24157
|
+
}
|
|
24158
|
+
const merged = mergeModelProfile(providerConfig, profile);
|
|
24159
|
+
return {
|
|
24160
|
+
providerName,
|
|
24161
|
+
providerConfig: merged,
|
|
24162
|
+
model: profile.model,
|
|
24163
|
+
modelProfileName: profile.name
|
|
24164
|
+
};
|
|
24165
|
+
}
|
|
24166
|
+
function mergeModelProfile(providerConfig, profile) {
|
|
24167
|
+
const merged = {
|
|
24168
|
+
...providerConfig,
|
|
24169
|
+
...definedOnly({
|
|
24170
|
+
baseURL: profile.baseURL,
|
|
24171
|
+
apiKey: profile.apiKey,
|
|
24172
|
+
apiKeyEnv: profile.apiKeyEnv,
|
|
24173
|
+
apiKeyEnvAliases: profile.apiKeyEnvAliases,
|
|
24174
|
+
auth: profile.auth,
|
|
24175
|
+
headers: profile.headers,
|
|
24176
|
+
quirks: profile.quirks,
|
|
24177
|
+
maxTokens: profile.maxTokens
|
|
24178
|
+
}),
|
|
24179
|
+
model: profile.model
|
|
24180
|
+
};
|
|
24181
|
+
return merged;
|
|
24182
|
+
}
|
|
24183
|
+
function definedOnly(input2) {
|
|
24184
|
+
return Object.fromEntries(Object.entries(input2).filter(([, value]) => value !== void 0));
|
|
24185
|
+
}
|
|
24186
|
+
function fallbackModelForProvider(providerName, providerConfig) {
|
|
24187
|
+
if (typeof providerConfig.model === "string" && providerConfig.model.trim()) return providerConfig.model.trim();
|
|
24188
|
+
if (providerName === "openai") return "gpt-5.5";
|
|
24189
|
+
if (providerName === "anthropic") return CLAUDE_CODE_SONNET_MODEL;
|
|
24190
|
+
if (providerName === "codex" || providerConfig.type === "codex") return "gpt-5.5";
|
|
24191
|
+
if (providerName === "claudecode") return CLAUDE_CODE_SONNET_MODEL;
|
|
24192
|
+
if (providerName === "gemini") return "gemini-2.5-pro";
|
|
24193
|
+
if (providerName === "groq") return "openai/gpt-oss-120b";
|
|
24194
|
+
return void 0;
|
|
23949
24195
|
}
|
|
23950
24196
|
function migrateProviderConfig(provider, baseProvider) {
|
|
23951
24197
|
if (isLegacyClaudeCodeShape(provider)) {
|
|
@@ -24027,12 +24273,14 @@ function resolveAgentMode(config, flagMode) {
|
|
|
24027
24273
|
return "single-agent";
|
|
24028
24274
|
}
|
|
24029
24275
|
function resolveProviderConfig(config, providerName) {
|
|
24030
|
-
const
|
|
24276
|
+
const normalized = normalizeProviderAlias(providerName);
|
|
24277
|
+
const provider = config.providers[normalized];
|
|
24031
24278
|
if (!provider) throw new Error(`Unknown provider: ${providerName}`);
|
|
24032
24279
|
return provider;
|
|
24033
24280
|
}
|
|
24034
24281
|
function resolveProvider(providerConfig, options = {}) {
|
|
24035
24282
|
const model = options.model ?? providerConfig.model;
|
|
24283
|
+
if (!model) throw new Error("Provider has no selected model. Choose a model or add a model profile.");
|
|
24036
24284
|
if (providerConfig.type === "claudecode") {
|
|
24037
24285
|
throw new Error("claudecode is an external agent runtime. Use resolveExecutionBackend().");
|
|
24038
24286
|
}
|
|
@@ -24079,12 +24327,28 @@ function resolveProvider(providerConfig, options = {}) {
|
|
|
24079
24327
|
};
|
|
24080
24328
|
}
|
|
24081
24329
|
if (providerConfig.type === "anthropic") {
|
|
24082
|
-
const apiKey2 =
|
|
24330
|
+
const apiKey2 = resolveConfiguredApiKey(providerConfig).value;
|
|
24083
24331
|
if (!apiKey2) {
|
|
24084
24332
|
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
24085
24333
|
}
|
|
24086
24334
|
return {
|
|
24087
24335
|
provider: new AnthropicProvider({
|
|
24336
|
+
apiKey: apiKey2,
|
|
24337
|
+
baseURL: providerConfig.baseURL,
|
|
24338
|
+
defaultModel: model,
|
|
24339
|
+
onRetry: options.onRetry
|
|
24340
|
+
}),
|
|
24341
|
+
model
|
|
24342
|
+
};
|
|
24343
|
+
}
|
|
24344
|
+
if (providerConfig.type === "ollama") {
|
|
24345
|
+
const apiKey2 = resolveConfiguredApiKey(providerConfig).value;
|
|
24346
|
+
if (!apiKey2) {
|
|
24347
|
+
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
24348
|
+
}
|
|
24349
|
+
return {
|
|
24350
|
+
provider: new OllamaProvider({
|
|
24351
|
+
baseURL: providerConfig.baseURL,
|
|
24088
24352
|
apiKey: apiKey2,
|
|
24089
24353
|
defaultModel: model,
|
|
24090
24354
|
onRetry: options.onRetry
|
|
@@ -24092,7 +24356,7 @@ function resolveProvider(providerConfig, options = {}) {
|
|
|
24092
24356
|
model
|
|
24093
24357
|
};
|
|
24094
24358
|
}
|
|
24095
|
-
const apiKey =
|
|
24359
|
+
const apiKey = resolveConfiguredApiKey(providerConfig).value;
|
|
24096
24360
|
if (!apiKey) {
|
|
24097
24361
|
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
24098
24362
|
}
|
|
@@ -24111,6 +24375,7 @@ function resolveProvider(providerConfig, options = {}) {
|
|
|
24111
24375
|
}
|
|
24112
24376
|
function resolveExecutionBackend(providerConfig, options = {}) {
|
|
24113
24377
|
const model = options.model ?? providerConfig.model;
|
|
24378
|
+
if (!model) throw new Error("Provider has no selected model. Choose a model or add a model profile.");
|
|
24114
24379
|
if (providerConfig.type === "claudecode") {
|
|
24115
24380
|
const runtimeConfig = options.config || providerConfig.permissionProfile ? resolveClaudeCodeRuntimeConfig(providerConfig, options.config ?? defaultConfig, options.agent) : providerConfig;
|
|
24116
24381
|
return {
|
|
@@ -24152,44 +24417,1128 @@ function claudeCodeRuntimePolicyHash(config) {
|
|
|
24152
24417
|
}
|
|
24153
24418
|
var CLAUDE_CODE_PERMISSION_PROFILE_KEYS = ["permissionMode", "defaultDecision", "allowedTools", "disallowedTools", "tools", "allowSubagents"];
|
|
24154
24419
|
|
|
24155
|
-
// src/
|
|
24156
|
-
import {
|
|
24157
|
-
import {
|
|
24420
|
+
// src/config-command.ts
|
|
24421
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
24422
|
+
import { stat as stat4 } from "node:fs/promises";
|
|
24423
|
+
import os10 from "node:os";
|
|
24424
|
+
import path16 from "node:path";
|
|
24425
|
+
import readline3 from "node:readline/promises";
|
|
24426
|
+
|
|
24427
|
+
// src/config-scaffold.ts
|
|
24428
|
+
import { chmod as chmod3, mkdir as mkdir6, readFile as readFile7, rename as rename4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
|
|
24158
24429
|
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);
|
|
24430
|
+
import path14 from "node:path";
|
|
24431
|
+
function defaultUserConfigPath() {
|
|
24432
|
+
return path14.join(os8.homedir(), ".demian", "config.json");
|
|
24166
24433
|
}
|
|
24167
|
-
|
|
24168
|
-
|
|
24169
|
-
|
|
24170
|
-
|
|
24171
|
-
|
|
24172
|
-
|
|
24173
|
-
|
|
24174
|
-
|
|
24175
|
-
|
|
24176
|
-
|
|
24177
|
-
|
|
24178
|
-
|
|
24179
|
-
|
|
24180
|
-
|
|
24181
|
-
|
|
24182
|
-
|
|
24434
|
+
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
24435
|
+
return {
|
|
24436
|
+
version: 2,
|
|
24437
|
+
defaultProvider,
|
|
24438
|
+
providers: {
|
|
24439
|
+
openai: {
|
|
24440
|
+
type: "openai-compatible",
|
|
24441
|
+
baseURL: "https://api.openai.com/v1",
|
|
24442
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
24443
|
+
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
24444
|
+
},
|
|
24445
|
+
anthropic: {
|
|
24446
|
+
type: "anthropic",
|
|
24447
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
24448
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
24449
|
+
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
24450
|
+
},
|
|
24451
|
+
gemini: {
|
|
24452
|
+
type: "openai-compatible",
|
|
24453
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
24454
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
24455
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
24456
|
+
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
24457
|
+
},
|
|
24458
|
+
groq: {
|
|
24459
|
+
type: "openai-compatible",
|
|
24460
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
24461
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
24462
|
+
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
24463
|
+
},
|
|
24464
|
+
azure: {
|
|
24465
|
+
type: "openai-compatible",
|
|
24466
|
+
auth: { type: "api-key", header: "api-key" },
|
|
24467
|
+
modelProfiles: [
|
|
24468
|
+
{
|
|
24469
|
+
name: "azure-example",
|
|
24470
|
+
displayName: "Azure example",
|
|
24471
|
+
model: "azure-deployment-name",
|
|
24472
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
24473
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
24474
|
+
}
|
|
24475
|
+
]
|
|
24476
|
+
},
|
|
24477
|
+
lmstudio: {
|
|
24478
|
+
type: "openai-compatible",
|
|
24479
|
+
baseURL: "http://localhost:1234/v1",
|
|
24480
|
+
apiKey: "lm-studio",
|
|
24481
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
24482
|
+
},
|
|
24483
|
+
"ollama-local": {
|
|
24484
|
+
type: "openai-compatible",
|
|
24485
|
+
baseURL: "http://localhost:11434/v1",
|
|
24486
|
+
apiKey: "ollama",
|
|
24487
|
+
quirks: { omitTemperature: true },
|
|
24488
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
24489
|
+
},
|
|
24490
|
+
"ollama-cloud": {
|
|
24491
|
+
type: "ollama",
|
|
24492
|
+
baseURL: "https://ollama.com/api",
|
|
24493
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
24494
|
+
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
24495
|
+
},
|
|
24496
|
+
llamacpp: {
|
|
24497
|
+
type: "openai-compatible",
|
|
24498
|
+
baseURL: "http://localhost:8080/v1",
|
|
24499
|
+
apiKey: "llama.cpp",
|
|
24500
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
24501
|
+
},
|
|
24502
|
+
vllm: {
|
|
24503
|
+
type: "openai-compatible",
|
|
24504
|
+
baseURL: "http://localhost:8000/v1",
|
|
24505
|
+
apiKey: "vllm",
|
|
24506
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
24507
|
+
},
|
|
24508
|
+
codex: {
|
|
24509
|
+
type: "codex",
|
|
24510
|
+
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
24511
|
+
authStore: "auto",
|
|
24512
|
+
allowApiKeyFallback: false,
|
|
24513
|
+
promptCacheKey: "root-session",
|
|
24514
|
+
catalog: { type: "codex-oauth-models" }
|
|
24515
|
+
},
|
|
24516
|
+
claudecode: {
|
|
24517
|
+
type: "claudecode",
|
|
24518
|
+
runtime: "agent-sdk",
|
|
24519
|
+
cliPath: "~/.local/bin/claude",
|
|
24520
|
+
cwdMode: "session",
|
|
24521
|
+
historyPolicy: "passthrough-resume",
|
|
24522
|
+
onInvalidResume: "fresh",
|
|
24523
|
+
attachmentFallback: "block",
|
|
24524
|
+
allowSubagents: false,
|
|
24525
|
+
sanitizeApiKeyEnv: true,
|
|
24526
|
+
authPreflight: true,
|
|
24527
|
+
useBareMode: false,
|
|
24528
|
+
usageLedgerScope: "process",
|
|
24529
|
+
sessionLock: true,
|
|
24530
|
+
abortPolicy: "record-only",
|
|
24531
|
+
catalog: { type: "claudecode-supported-models" }
|
|
24532
|
+
}
|
|
24533
|
+
}
|
|
24534
|
+
};
|
|
24535
|
+
}
|
|
24536
|
+
async function createUserConfig(options = {}) {
|
|
24537
|
+
const filePath = expandHome2(options.path ?? defaultUserConfigPath());
|
|
24538
|
+
const content = `${JSON.stringify(defaultUserConfig(options.defaultProvider), null, 2)}
|
|
24539
|
+
`;
|
|
24540
|
+
if (options.print) return { path: filePath, created: false, content };
|
|
24541
|
+
const existed = await exists(filePath);
|
|
24542
|
+
if (existed && !options.force) return { path: filePath, created: false, content: await readFile7(filePath, "utf8"), existed: true };
|
|
24543
|
+
await writeJsonAtomic(filePath, content);
|
|
24544
|
+
return { path: filePath, created: true, content, existed };
|
|
24545
|
+
}
|
|
24546
|
+
async function addProvider(options) {
|
|
24547
|
+
const filePath = expandHome2(options.path ?? defaultUserConfigPath());
|
|
24548
|
+
const config = await readConfigObject(filePath);
|
|
24549
|
+
const providers = objectValue(config.providers);
|
|
24550
|
+
const name = options.name;
|
|
24551
|
+
if (providers[name] && !options.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
24552
|
+
providers[name] = providerPreset(options);
|
|
24553
|
+
config.providers = providers;
|
|
24554
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
24555
|
+
`;
|
|
24556
|
+
await writeJsonAtomic(filePath, content);
|
|
24557
|
+
return { path: filePath, created: true, content };
|
|
24558
|
+
}
|
|
24559
|
+
async function addModelProfile(options) {
|
|
24560
|
+
const filePath = expandHome2(options.path ?? defaultUserConfigPath());
|
|
24561
|
+
const config = await readConfigObject(filePath);
|
|
24562
|
+
const providers = objectValue(config.providers);
|
|
24563
|
+
const provider = objectValue(providers[options.provider]);
|
|
24564
|
+
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
24565
|
+
const displayName = options.displayName ?? options.name;
|
|
24566
|
+
const existingByName = profiles.findIndex((entry) => entry.name === options.name);
|
|
24567
|
+
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
24568
|
+
if (existingByName >= 0 && !options.force) throw new Error(`Profile name "${options.name}" already exists on provider ${options.provider}. Use --force to overwrite it.`);
|
|
24569
|
+
if (existingByDisplay >= 0 && existingByDisplay !== existingByName && !options.force) {
|
|
24570
|
+
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.`);
|
|
24571
|
+
}
|
|
24572
|
+
const next = {
|
|
24573
|
+
name: options.name,
|
|
24574
|
+
displayName,
|
|
24575
|
+
model: options.model,
|
|
24576
|
+
...options.baseURL ? { baseURL: options.baseURL } : {},
|
|
24577
|
+
...options.apiKey ? { apiKey: options.apiKey } : {},
|
|
24578
|
+
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
|
|
24579
|
+
};
|
|
24580
|
+
if (existingByName >= 0) profiles[existingByName] = next;
|
|
24581
|
+
else profiles.push(next);
|
|
24582
|
+
provider.modelProfiles = profiles;
|
|
24583
|
+
providers[options.provider] = provider;
|
|
24584
|
+
config.providers = providers;
|
|
24585
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
24586
|
+
`;
|
|
24587
|
+
await writeJsonAtomic(filePath, content);
|
|
24588
|
+
return { path: filePath, created: true, content };
|
|
24589
|
+
}
|
|
24590
|
+
async function readConfigObject(filePath) {
|
|
24591
|
+
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
24592
|
+
const raw = JSON.parse(await readFile7(filePath, "utf8"));
|
|
24593
|
+
raw.version ??= 2;
|
|
24594
|
+
raw.providers = objectValue(raw.providers);
|
|
24595
|
+
return raw;
|
|
24596
|
+
}
|
|
24597
|
+
function providerPreset(options) {
|
|
24598
|
+
const preset = options.preset ?? options.name;
|
|
24599
|
+
const auth = {
|
|
24600
|
+
...options.apiKey ? { apiKey: options.apiKey } : {},
|
|
24601
|
+
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
|
|
24602
|
+
};
|
|
24603
|
+
const openAIAuth = options.authHeader ? { auth: { type: "api-key", header: options.authHeader } } : {};
|
|
24604
|
+
if (preset === "openai") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.openai.com/v1", apiKeyEnv: "OPENAI_API_KEY", ...auth, ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
|
|
24605
|
+
if (preset === "anthropic") return { type: "anthropic", baseURL: options.baseURL ?? "https://api.anthropic.com/v1", apiKeyEnv: "ANTHROPIC_API_KEY", ...auth, catalog: { type: "anthropic-models", endpoint: `${(options.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
|
|
24606
|
+
if (preset === "gemini") {
|
|
24607
|
+
const geminiBase = options.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
24608
|
+
return { type: "openai-compatible", baseURL: geminiBase, apiKeyEnv: "GEMINI_API_KEY", apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...auth, ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
|
|
24609
|
+
}
|
|
24610
|
+
if (preset === "groq") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.groq.com/openai/v1", apiKeyEnv: "GROQ_API_KEY", ...auth, ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
|
|
24611
|
+
if (preset === "azure") {
|
|
24612
|
+
return {
|
|
24613
|
+
type: "openai-compatible",
|
|
24614
|
+
auth: { type: "api-key", header: options.authHeader ?? "api-key" },
|
|
24615
|
+
...auth,
|
|
24616
|
+
modelProfiles: [
|
|
24617
|
+
{
|
|
24618
|
+
name: "azure-example",
|
|
24619
|
+
displayName: "Azure example",
|
|
24620
|
+
model: "azure-deployment-name",
|
|
24621
|
+
baseURL: options.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
24622
|
+
apiKeyEnv: options.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
24623
|
+
}
|
|
24624
|
+
]
|
|
24625
|
+
};
|
|
24626
|
+
}
|
|
24627
|
+
if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:1234/v1", apiKey: options.apiKey ?? "lm-studio", catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
|
|
24628
|
+
if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:11434/v1", apiKey: options.apiKey ?? "ollama", quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
|
|
24629
|
+
if (preset === "ollama-cloud") return { type: "ollama", baseURL: options.baseURL ?? "https://ollama.com/api", ...auth, catalog: { type: "ollama-tags", endpoint: `${(options.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
|
|
24630
|
+
if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8080/v1", apiKey: options.apiKey ?? "llama.cpp", catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
|
|
24631
|
+
if (preset === "vllm") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8000/v1", apiKey: options.apiKey ?? "vllm", catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
|
|
24632
|
+
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" } };
|
|
24633
|
+
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" } };
|
|
24634
|
+
if ((options.type ?? preset) === "openai-compatible") {
|
|
24635
|
+
const baseURL = options.baseURL;
|
|
24636
|
+
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
24637
|
+
return {
|
|
24638
|
+
type: "openai-compatible",
|
|
24639
|
+
baseURL,
|
|
24640
|
+
...options.apiKey ? { apiKey: options.apiKey } : {},
|
|
24641
|
+
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {},
|
|
24642
|
+
...openAIAuth,
|
|
24643
|
+
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
24644
|
+
};
|
|
24645
|
+
}
|
|
24646
|
+
throw new Error(`Unknown provider preset: ${preset}`);
|
|
24647
|
+
}
|
|
24648
|
+
async function writeJsonAtomic(filePath, content) {
|
|
24649
|
+
await mkdir6(path14.dirname(filePath), { recursive: true, mode: 448 });
|
|
24650
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
24651
|
+
await writeFile5(temp, content, { mode: 384 });
|
|
24652
|
+
await rename4(temp, filePath);
|
|
24653
|
+
await chmod3(filePath, 384).catch(() => void 0);
|
|
24654
|
+
}
|
|
24655
|
+
function detectDefaultProvider() {
|
|
24656
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
24657
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
24658
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
24659
|
+
if (process.env.GROQ_API_KEY) return "groq";
|
|
24660
|
+
return "openai";
|
|
24661
|
+
}
|
|
24662
|
+
function objectValue(value) {
|
|
24663
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
24664
|
+
}
|
|
24665
|
+
async function exists(filePath) {
|
|
24666
|
+
try {
|
|
24667
|
+
await stat3(filePath);
|
|
24668
|
+
return true;
|
|
24669
|
+
} catch {
|
|
24670
|
+
return false;
|
|
24671
|
+
}
|
|
24672
|
+
}
|
|
24673
|
+
function expandHome2(value) {
|
|
24674
|
+
return resolveExpandedPath(value);
|
|
24675
|
+
}
|
|
24676
|
+
|
|
24677
|
+
// src/models/catalog.ts
|
|
24678
|
+
import crypto5 from "node:crypto";
|
|
24679
|
+
import { mkdir as mkdir7, readFile as readFile8, rename as rename5, writeFile as writeFile6 } from "node:fs/promises";
|
|
24680
|
+
import os9 from "node:os";
|
|
24681
|
+
import path15 from "node:path";
|
|
24682
|
+
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
24683
|
+
var PING_TIMEOUT_MS = 1500;
|
|
24684
|
+
var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
|
|
24685
|
+
var dynamicImport2 = new Function("specifier", "return import(specifier)");
|
|
24686
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
24687
|
+
var pingCache = /* @__PURE__ */ new Map();
|
|
24688
|
+
var PING_CACHE_TTL_MS = 30 * 1e3;
|
|
24689
|
+
var packageVersionPromise;
|
|
24690
|
+
async function listProviderModelCatalog(providerName, providerConfig, options = {}) {
|
|
24691
|
+
const key = `${catalogCacheKey(providerName, providerConfig)}:${options.refresh ? "refresh" : "default"}`;
|
|
24692
|
+
const existing = inflight.get(key);
|
|
24693
|
+
if (existing) return existing;
|
|
24694
|
+
const promise = doListProviderModelCatalog(providerName, providerConfig, options).finally(() => inflight.delete(key));
|
|
24695
|
+
inflight.set(key, promise);
|
|
24696
|
+
return promise;
|
|
24697
|
+
}
|
|
24698
|
+
async function doListProviderModelCatalog(providerName, providerConfig, options) {
|
|
24699
|
+
const staticModels = staticModelEntries(providerName, providerConfig);
|
|
24700
|
+
const catalog = providerConfig.catalog;
|
|
24701
|
+
if (!catalog || catalog.type === "static") {
|
|
24702
|
+
return { status: staticModels.length ? "ready" : "unavailable", providerName, models: staticModels, source: "config" };
|
|
24703
|
+
}
|
|
24704
|
+
const missingAuthEnv = missingCatalogAuth(providerConfig);
|
|
24705
|
+
if (missingAuthEnv) {
|
|
24706
|
+
return {
|
|
24707
|
+
status: "missing-auth",
|
|
24708
|
+
providerName,
|
|
24709
|
+
models: staticModels,
|
|
24710
|
+
source: staticModels.length ? "config" : "fallback",
|
|
24711
|
+
message: `missing-auth:${missingAuthEnv}`
|
|
24712
|
+
};
|
|
24713
|
+
}
|
|
24714
|
+
const cacheKey = catalogCacheKey(providerName, providerConfig);
|
|
24715
|
+
const ttlMs = catalog.refreshTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
24716
|
+
const cached = await readCatalogCache(cacheKey, ttlMs, options.refresh !== true);
|
|
24717
|
+
if (cached && !options.refresh) return { ...cached, models: mergeCatalogWithStatic(staticModels, cached.models) };
|
|
24718
|
+
if (catalog.endpoint) {
|
|
24719
|
+
const reachable = await pingEndpoint(catalog.endpoint, options.fetch);
|
|
24720
|
+
if (!reachable) {
|
|
24721
|
+
const stale = await readCatalogCache(cacheKey, Number.POSITIVE_INFINITY, true);
|
|
24722
|
+
if (stale) return { ...stale, models: mergeCatalogWithStatic(staticModels, stale.models), message: `endpoint ${catalog.endpoint} unreachable; using cached models` };
|
|
24723
|
+
if (staticModels.length) return { status: "ready", providerName, models: staticModels, source: "config", message: `endpoint ${catalog.endpoint} unreachable; using configured profiles` };
|
|
24724
|
+
return { status: "unavailable", providerName, models: [], source: "fallback", message: `endpoint ${catalog.endpoint} unreachable` };
|
|
24725
|
+
}
|
|
24726
|
+
}
|
|
24727
|
+
try {
|
|
24728
|
+
const remote = await fetchCatalog(providerName, providerConfig, options);
|
|
24729
|
+
const merged = mergeCatalogWithStatic(staticModels, remote.models);
|
|
24730
|
+
const result = { ...remote, models: merged };
|
|
24731
|
+
if (result.status === "ready") await writeCatalogCache(cacheKey, result);
|
|
24732
|
+
return result;
|
|
24733
|
+
} catch (error) {
|
|
24734
|
+
const authMissing = isMissingAuthError(error);
|
|
24735
|
+
const stale = await readCatalogCache(cacheKey, Number.POSITIVE_INFINITY, true);
|
|
24736
|
+
if (stale) return { ...stale, status: authMissing ? "missing-auth" : stale.status, models: mergeCatalogWithStatic(staticModels, stale.models), message: errorMessage2(error) };
|
|
24737
|
+
if (staticModels.length) return { status: authMissing ? "missing-auth" : "ready", providerName, models: staticModels, source: "config", message: errorMessage2(error) };
|
|
24738
|
+
return { status: authMissing ? "missing-auth" : "unavailable", providerName, models: [], source: "fallback", message: errorMessage2(error) };
|
|
24739
|
+
}
|
|
24740
|
+
}
|
|
24741
|
+
async function pingEndpoint(endpoint, fetcher) {
|
|
24742
|
+
const now2 = Date.now();
|
|
24743
|
+
const cached = pingCache.get(endpoint);
|
|
24744
|
+
if (cached && cached.expiresAt > now2) return cached.ok;
|
|
24745
|
+
const ok2 = await tryPing(endpoint, fetcher);
|
|
24746
|
+
pingCache.set(endpoint, { ok: ok2, expiresAt: now2 + PING_CACHE_TTL_MS });
|
|
24747
|
+
return ok2;
|
|
24748
|
+
}
|
|
24749
|
+
async function tryPing(endpoint, fetcher) {
|
|
24750
|
+
const fn = fetcher ?? fetch;
|
|
24751
|
+
try {
|
|
24752
|
+
const response = await fn(endpoint, { method: "HEAD", signal: AbortSignal.timeout(PING_TIMEOUT_MS) });
|
|
24753
|
+
if (response.status < 500) return true;
|
|
24754
|
+
} catch {
|
|
24755
|
+
}
|
|
24756
|
+
try {
|
|
24757
|
+
const response = await fn(endpoint, { method: "GET", signal: AbortSignal.timeout(PING_TIMEOUT_MS), headers: { range: "bytes=0-0" } });
|
|
24758
|
+
return response.status < 500;
|
|
24759
|
+
} catch {
|
|
24760
|
+
return false;
|
|
24761
|
+
}
|
|
24762
|
+
}
|
|
24763
|
+
async function fetchCatalog(providerName, providerConfig, options) {
|
|
24764
|
+
if (providerConfig.type === "codex" || providerConfig.catalog?.type === "codex-oauth-models") return fetchCodexCatalog(providerName, providerConfig, options);
|
|
24765
|
+
if (providerConfig.type === "claudecode" || providerConfig.catalog?.type === "claudecode-supported-models") return fetchClaudeCodeCatalog(providerName, providerConfig, options);
|
|
24766
|
+
if (providerConfig.catalog?.type === "anthropic-models") return fetchAnthropicCatalog(providerName, providerConfig, options);
|
|
24767
|
+
if (providerConfig.catalog?.type === "ollama-tags") return fetchOllamaTagsCatalog(providerName, providerConfig, options);
|
|
24768
|
+
return fetchOpenAIStyleCatalog(providerName, providerConfig, options);
|
|
24769
|
+
}
|
|
24770
|
+
async function fetchOpenAIStyleCatalog(providerName, providerConfig, options) {
|
|
24771
|
+
if (providerConfig.type !== "openai-compatible") throw new Error(`Provider ${providerName} is not OpenAI-compatible.`);
|
|
24772
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
24773
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
24774
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${providerConfig.baseURL.replace(/\/+$/, "")}/models`;
|
|
24775
|
+
const response = await fetchJson(endpoint, {
|
|
24776
|
+
fetcher: options.fetch,
|
|
24777
|
+
timeoutMs: options.timeoutMs,
|
|
24778
|
+
headers: {
|
|
24779
|
+
...authHeadersForOpenAICompatible(providerConfig, apiKey.value),
|
|
24780
|
+
...providerConfig.headers ?? {}
|
|
24781
|
+
}
|
|
24782
|
+
});
|
|
24783
|
+
return {
|
|
24784
|
+
status: "ready",
|
|
24785
|
+
providerName,
|
|
24786
|
+
models: normalizeOpenAIModels(response).map((entry) => ({ ...entry, source: "api" })),
|
|
24787
|
+
source: "api"
|
|
24788
|
+
};
|
|
24789
|
+
}
|
|
24790
|
+
async function fetchAnthropicCatalog(providerName, providerConfig, options) {
|
|
24791
|
+
if (providerConfig.type !== "anthropic") throw new Error(`Provider ${providerName} is not Anthropic.`);
|
|
24792
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
24793
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
24794
|
+
const base = providerConfig.baseURL ?? "https://api.anthropic.com/v1";
|
|
24795
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${base.replace(/\/+$/, "")}/models`;
|
|
24796
|
+
const models = [];
|
|
24797
|
+
let afterId;
|
|
24798
|
+
for (; ; ) {
|
|
24799
|
+
const url = new URL(endpoint);
|
|
24800
|
+
if (afterId) url.searchParams.set("after_id", afterId);
|
|
24801
|
+
url.searchParams.set("limit", "100");
|
|
24802
|
+
const response = await fetchJson(url.toString(), {
|
|
24803
|
+
fetcher: options.fetch,
|
|
24804
|
+
timeoutMs: options.timeoutMs,
|
|
24805
|
+
headers: {
|
|
24806
|
+
"x-api-key": apiKey.value,
|
|
24807
|
+
"anthropic-version": "2023-06-01"
|
|
24808
|
+
}
|
|
24809
|
+
});
|
|
24810
|
+
const page = normalizeAnthropicModels(response);
|
|
24811
|
+
models.push(...page);
|
|
24812
|
+
if (!response.has_more || !response.last_id) break;
|
|
24813
|
+
afterId = response.last_id;
|
|
24814
|
+
}
|
|
24815
|
+
return { status: "ready", providerName, models, source: "api" };
|
|
24816
|
+
}
|
|
24817
|
+
async function fetchOllamaTagsCatalog(providerName, providerConfig, options) {
|
|
24818
|
+
if (providerConfig.type !== "ollama") throw new Error(`Provider ${providerName} is not Ollama native.`);
|
|
24819
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
24820
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
24821
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${providerConfig.baseURL.replace(/\/+$/, "")}/tags`;
|
|
24822
|
+
const response = await fetchJson(endpoint, {
|
|
24823
|
+
fetcher: options.fetch,
|
|
24824
|
+
timeoutMs: options.timeoutMs,
|
|
24825
|
+
headers: { authorization: `Bearer ${apiKey.value}` }
|
|
24826
|
+
});
|
|
24827
|
+
return {
|
|
24828
|
+
status: "ready",
|
|
24829
|
+
providerName,
|
|
24830
|
+
models: normalizeOllamaTags(response).map((entry) => ({ ...entry, source: "api" })),
|
|
24831
|
+
source: "api"
|
|
24832
|
+
};
|
|
24833
|
+
}
|
|
24834
|
+
async function fetchCodexCatalog(providerName, providerConfig, options) {
|
|
24835
|
+
if (providerConfig.type !== "codex") throw new Error(`Provider ${providerName} is not Codex.`);
|
|
24836
|
+
const fetcher = options.fetch ?? fetch;
|
|
24837
|
+
const baseURL = (providerConfig.baseURL ?? "https://chatgpt.com/backend-api/codex").replace(/\/+$/, "");
|
|
24838
|
+
const installationId = await loadOrCreateInstallationId(resolveCodexHome(providerConfig.codexHome));
|
|
24839
|
+
const auth = getSharedCodexAuthStore({
|
|
24840
|
+
codexHome: providerConfig.codexHome,
|
|
24841
|
+
authStore: providerConfig.authStore,
|
|
24842
|
+
allowApiKeyFallback: providerConfig.allowApiKeyFallback,
|
|
24843
|
+
proactiveRefreshMinutes: providerConfig.refresh?.proactiveRefreshMinutes,
|
|
24844
|
+
refreshCache: providerConfig.refresh?.cache,
|
|
24845
|
+
fetch: fetcher
|
|
24846
|
+
});
|
|
24847
|
+
const headers = await auth.requestHeaders(installationId);
|
|
24848
|
+
const url = new URL(`${baseURL}/models`);
|
|
24849
|
+
url.searchParams.set("client_version", await codexClientVersion(options.clientVersion));
|
|
24850
|
+
const response = await fetchJson(url.toString(), {
|
|
24851
|
+
fetcher,
|
|
24852
|
+
timeoutMs: options.timeoutMs,
|
|
24853
|
+
headers
|
|
24854
|
+
});
|
|
24855
|
+
return {
|
|
24856
|
+
status: "ready",
|
|
24857
|
+
providerName,
|
|
24858
|
+
models: normalizeCodexModels(response).map((entry) => ({ ...entry, source: "oauth" })),
|
|
24859
|
+
source: "oauth"
|
|
24860
|
+
};
|
|
24861
|
+
}
|
|
24862
|
+
async function fetchClaudeCodeCatalog(providerName, providerConfig, options) {
|
|
24863
|
+
if (providerConfig.type !== "claudecode") throw new Error(`Provider ${providerName} is not Claude Code.`);
|
|
24864
|
+
const fallback = staticModelEntries(providerName, providerConfig);
|
|
24865
|
+
try {
|
|
24866
|
+
const module = await dynamicImport2("@anthropic-ai/claude-agent-sdk");
|
|
24867
|
+
if (!module.query) throw new Error("Claude Agent SDK query export is unavailable.");
|
|
24868
|
+
const query = module.query({
|
|
24869
|
+
prompt: "",
|
|
24870
|
+
options: {
|
|
24871
|
+
model: defaultModelForProvider(providerName, providerConfig),
|
|
24872
|
+
maxTurns: 0,
|
|
24873
|
+
pathToClaudeCodeExecutable: resolveClaudeCodeCliPath(providerConfig.cliPath),
|
|
24874
|
+
env: sanitizedClaudeCodeEnv(providerConfig.env, providerConfig.sanitizeApiKeyEnv ?? true)
|
|
24875
|
+
}
|
|
24876
|
+
});
|
|
24877
|
+
try {
|
|
24878
|
+
if (typeof query.supportedModels !== "function") throw new Error("Claude Agent SDK supportedModels is unavailable.");
|
|
24879
|
+
const models = await withTimeout(query.supportedModels(), options.timeoutMs ?? 5e3);
|
|
24880
|
+
return {
|
|
24881
|
+
status: "ready",
|
|
24882
|
+
providerName,
|
|
24883
|
+
models: models.map((model, index) => ({
|
|
24884
|
+
name: model.displayName || model.value,
|
|
24885
|
+
displayName: model.displayName || model.value,
|
|
24886
|
+
model: model.value,
|
|
24887
|
+
description: model.description,
|
|
24888
|
+
isDefault: index === 0,
|
|
24889
|
+
source: "oauth"
|
|
24890
|
+
})),
|
|
24891
|
+
source: "oauth"
|
|
24892
|
+
};
|
|
24893
|
+
} finally {
|
|
24894
|
+
query.close?.();
|
|
24895
|
+
}
|
|
24896
|
+
} catch (error) {
|
|
24897
|
+
if (fallback.length) return { status: "ready", providerName, models: fallback, source: "fallback", message: errorMessage2(error) };
|
|
24898
|
+
throw error;
|
|
24899
|
+
}
|
|
24900
|
+
}
|
|
24901
|
+
function staticModelEntries(providerName, providerConfig) {
|
|
24902
|
+
return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile, index) => ({
|
|
24903
|
+
name: profile.displayName ?? profile.name,
|
|
24904
|
+
displayName: profile.displayName ?? profile.name,
|
|
24905
|
+
model: profile.model,
|
|
24906
|
+
description: profile.description,
|
|
24907
|
+
isDefault: index === 0,
|
|
24908
|
+
source: "config"
|
|
24909
|
+
}));
|
|
24910
|
+
}
|
|
24911
|
+
function mergeCatalogWithStatic(staticModels, remoteModels) {
|
|
24912
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
24913
|
+
for (const model of remoteModels) byModel.set(model.model, model);
|
|
24914
|
+
for (const model of staticModels) byModel.set(model.model, { ...byModel.get(model.model), ...model, source: model.source });
|
|
24915
|
+
return [...byModel.values()];
|
|
24916
|
+
}
|
|
24917
|
+
function normalizeOpenAIModels(raw) {
|
|
24918
|
+
const items = Array.isArray(raw.data) ? raw.data : Array.isArray(raw.models) ? raw.models : [];
|
|
24919
|
+
return items.map((item) => {
|
|
24920
|
+
const object2 = item;
|
|
24921
|
+
const id = stringValue4(object2.id) ?? stringValue4(object2.name) ?? stringValue4(object2.model);
|
|
24922
|
+
if (!id) return void 0;
|
|
24923
|
+
return {
|
|
24924
|
+
name: id,
|
|
24925
|
+
displayName: stringValue4(object2.display_name) ?? stringValue4(object2.displayName) ?? id,
|
|
24926
|
+
model: id,
|
|
24927
|
+
description: stringValue4(object2.description)
|
|
24928
|
+
};
|
|
24929
|
+
}).filter(Boolean);
|
|
24930
|
+
}
|
|
24931
|
+
function normalizeAnthropicModels(raw) {
|
|
24932
|
+
return (raw.data ?? []).map((item) => {
|
|
24933
|
+
const object2 = item;
|
|
24934
|
+
const id = stringValue4(object2.id);
|
|
24935
|
+
if (!id) return void 0;
|
|
24936
|
+
return {
|
|
24937
|
+
name: stringValue4(object2.display_name) ?? id,
|
|
24938
|
+
displayName: stringValue4(object2.display_name) ?? id,
|
|
24939
|
+
model: id,
|
|
24940
|
+
description: stringValue4(object2.description),
|
|
24941
|
+
source: "api"
|
|
24942
|
+
};
|
|
24943
|
+
}).filter(Boolean);
|
|
24944
|
+
}
|
|
24945
|
+
function normalizeOllamaTags(raw) {
|
|
24946
|
+
const items = Array.isArray(raw.models) ? raw.models : [];
|
|
24947
|
+
return items.map((item) => {
|
|
24948
|
+
const object2 = item;
|
|
24949
|
+
const id = stringValue4(object2.name) ?? stringValue4(object2.model);
|
|
24950
|
+
if (!id) return void 0;
|
|
24951
|
+
return { name: id, displayName: id, model: id };
|
|
24952
|
+
}).filter(Boolean);
|
|
24953
|
+
}
|
|
24954
|
+
function normalizeCodexModels(raw) {
|
|
24955
|
+
const items = Array.isArray(raw.models) ? raw.models : [];
|
|
24956
|
+
return items.map((item, index) => {
|
|
24957
|
+
const object2 = item;
|
|
24958
|
+
const id = stringValue4(object2.slug) ?? stringValue4(object2.id) ?? stringValue4(object2.model);
|
|
24959
|
+
if (!id) return void 0;
|
|
24960
|
+
return {
|
|
24961
|
+
name: stringValue4(object2.display_name) ?? id,
|
|
24962
|
+
displayName: stringValue4(object2.display_name) ?? id,
|
|
24963
|
+
model: id,
|
|
24964
|
+
description: stringValue4(object2.description),
|
|
24965
|
+
isDefault: index === 0
|
|
24966
|
+
};
|
|
24967
|
+
}).filter(Boolean);
|
|
24968
|
+
}
|
|
24969
|
+
function authHeadersForOpenAICompatible(providerConfig, apiKey) {
|
|
24970
|
+
const auth = inferOpenAICompatibleAuth(providerConfig.baseURL, providerConfig.auth);
|
|
24971
|
+
const type = auth.type ?? "bearer";
|
|
24972
|
+
const header = auth.header ?? (type === "api-key" ? "api-key" : "authorization");
|
|
24973
|
+
return { [header]: type === "bearer" ? `Bearer ${apiKey}` : apiKey };
|
|
24974
|
+
}
|
|
24975
|
+
async function fetchJson(url, options) {
|
|
24976
|
+
const response = await (options.fetcher ?? fetch)(url, {
|
|
24977
|
+
method: "GET",
|
|
24978
|
+
headers: {
|
|
24979
|
+
accept: "application/json",
|
|
24980
|
+
...options.headers ?? {}
|
|
24981
|
+
},
|
|
24982
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? 5e3)
|
|
24983
|
+
});
|
|
24984
|
+
const text = await response.text();
|
|
24985
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
|
|
24986
|
+
return text ? JSON.parse(text) : {};
|
|
24987
|
+
}
|
|
24988
|
+
function cachePath(cacheKey) {
|
|
24989
|
+
return path15.join(os9.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
|
|
24990
|
+
}
|
|
24991
|
+
async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
24992
|
+
if (!allow) return void 0;
|
|
24993
|
+
try {
|
|
24994
|
+
const raw = JSON.parse(await readFile8(cachePath(cacheKey), "utf8"));
|
|
24995
|
+
if (!raw.result) return void 0;
|
|
24996
|
+
if (ttlMs !== Number.POSITIVE_INFINITY && Date.now() - (raw.savedAt ?? 0) > ttlMs) return void 0;
|
|
24997
|
+
return { ...raw.result, source: "cache", models: raw.result.models.map((model) => ({ ...model, source: "cache" })) };
|
|
24998
|
+
} catch {
|
|
24999
|
+
return void 0;
|
|
25000
|
+
}
|
|
25001
|
+
}
|
|
25002
|
+
async function writeCatalogCache(cacheKey, result) {
|
|
25003
|
+
const filePath = cachePath(cacheKey);
|
|
25004
|
+
await mkdir7(path15.dirname(filePath), { recursive: true, mode: 448 });
|
|
25005
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
25006
|
+
await writeFile6(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
|
|
25007
|
+
await rename5(temp, filePath);
|
|
25008
|
+
}
|
|
25009
|
+
function safeCacheName(providerName) {
|
|
25010
|
+
return providerName.replace(/[^a-zA-Z0-9_.-]+/g, "_");
|
|
25011
|
+
}
|
|
25012
|
+
function catalogCacheKey(providerName, providerConfig) {
|
|
25013
|
+
const identity = {
|
|
25014
|
+
providerName,
|
|
25015
|
+
type: providerConfig.type,
|
|
25016
|
+
catalogType: providerConfig.catalog?.type,
|
|
25017
|
+
endpoint: providerConfig.catalog?.endpoint,
|
|
25018
|
+
baseURL: "baseURL" in providerConfig ? providerConfig.baseURL : void 0,
|
|
25019
|
+
apiKeyEnv: "apiKeyEnv" in providerConfig ? providerConfig.apiKeyEnv : void 0,
|
|
25020
|
+
apiKeyEnvAliases: "apiKeyEnvAliases" in providerConfig ? providerConfig.apiKeyEnvAliases : void 0,
|
|
25021
|
+
auth: "auth" in providerConfig ? providerConfig.auth : void 0
|
|
25022
|
+
};
|
|
25023
|
+
const digest = crypto5.createHash("sha256").update(JSON.stringify(identity)).digest("hex").slice(0, 16);
|
|
25024
|
+
return `${providerName}-${digest}`;
|
|
25025
|
+
}
|
|
25026
|
+
function missingCatalogAuth(providerConfig) {
|
|
25027
|
+
if (providerConfig.type !== "openai-compatible" && providerConfig.type !== "anthropic" && providerConfig.type !== "ollama") return void 0;
|
|
25028
|
+
if (providerConfig.catalog?.requiresAuth === false) return void 0;
|
|
25029
|
+
const expectsAuth = providerConfig.catalog?.requiresAuth === true || !!providerConfig.apiKey || !!providerConfig.apiKeyEnv || !!providerConfig.apiKeyEnvAliases?.length;
|
|
25030
|
+
if (!expectsAuth) return void 0;
|
|
25031
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
25032
|
+
return apiKey.value ? void 0 : apiKey.envName ?? "apiKey";
|
|
25033
|
+
}
|
|
25034
|
+
function stringValue4(value) {
|
|
25035
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
25036
|
+
}
|
|
25037
|
+
function missingAuth(envName) {
|
|
25038
|
+
const error = new Error(`missing-auth:${envName}`);
|
|
25039
|
+
return error;
|
|
25040
|
+
}
|
|
25041
|
+
function isMissingAuthError(error) {
|
|
25042
|
+
if (error instanceof CodexAuthError) {
|
|
25043
|
+
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";
|
|
25044
|
+
}
|
|
25045
|
+
return error instanceof Error && error.message.startsWith("missing-auth:");
|
|
25046
|
+
}
|
|
25047
|
+
function errorMessage2(error) {
|
|
25048
|
+
return error instanceof Error ? error.message : String(error);
|
|
25049
|
+
}
|
|
25050
|
+
async function codexClientVersion(override) {
|
|
25051
|
+
const normalized = semverLike(override);
|
|
25052
|
+
if (normalized) return normalized;
|
|
25053
|
+
packageVersionPromise ??= readPackageVersion();
|
|
25054
|
+
return packageVersionPromise;
|
|
25055
|
+
}
|
|
25056
|
+
async function readPackageVersion() {
|
|
25057
|
+
try {
|
|
25058
|
+
const raw = JSON.parse(await readFile8(new URL("../package.json", import.meta.url), "utf8"));
|
|
25059
|
+
return semverLike(raw.version) ?? DEFAULT_CODEX_CLIENT_VERSION;
|
|
25060
|
+
} catch {
|
|
25061
|
+
return DEFAULT_CODEX_CLIENT_VERSION;
|
|
25062
|
+
}
|
|
25063
|
+
}
|
|
25064
|
+
function semverLike(value) {
|
|
25065
|
+
if (typeof value !== "string") return void 0;
|
|
25066
|
+
const trimmed = value.trim();
|
|
25067
|
+
return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(trimmed) ? trimmed : void 0;
|
|
25068
|
+
}
|
|
25069
|
+
function withTimeout(promise, timeoutMs) {
|
|
25070
|
+
return new Promise((resolve, reject) => {
|
|
25071
|
+
const timer = setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
25072
|
+
promise.then(
|
|
25073
|
+
(value) => {
|
|
25074
|
+
clearTimeout(timer);
|
|
25075
|
+
resolve(value);
|
|
25076
|
+
},
|
|
25077
|
+
(error) => {
|
|
25078
|
+
clearTimeout(timer);
|
|
25079
|
+
reject(error);
|
|
25080
|
+
}
|
|
25081
|
+
);
|
|
25082
|
+
});
|
|
25083
|
+
}
|
|
25084
|
+
|
|
25085
|
+
// src/config-command.ts
|
|
25086
|
+
async function maybeRunConfigCommand(argv) {
|
|
25087
|
+
if (argv[0] === "config") return runConfigCommand(argv.slice(1));
|
|
25088
|
+
if (argv[0] === "auth") return runAuthCommand(argv.slice(1));
|
|
25089
|
+
return void 0;
|
|
25090
|
+
}
|
|
25091
|
+
async function runConfigCommand(argv) {
|
|
25092
|
+
const [command, ...rest] = argv;
|
|
25093
|
+
try {
|
|
25094
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
25095
|
+
printConfigHelp();
|
|
25096
|
+
return 0;
|
|
25097
|
+
}
|
|
25098
|
+
if (command === "init") {
|
|
25099
|
+
const flags = parseFlags(rest);
|
|
25100
|
+
const force = boolFlag(flags, "force") || await confirmOverwriteIfExists(stringFlag(flags, "path"), boolFlag(flags, "print"));
|
|
25101
|
+
const result = await createUserConfig({
|
|
25102
|
+
path: stringFlag(flags, "path"),
|
|
25103
|
+
force,
|
|
25104
|
+
print: boolFlag(flags, "print"),
|
|
25105
|
+
defaultProvider: stringFlag(flags, "provider")
|
|
25106
|
+
});
|
|
25107
|
+
if (boolFlag(flags, "print")) {
|
|
25108
|
+
process.stdout.write(result.content);
|
|
25109
|
+
} else if (result.existed && !result.created) {
|
|
25110
|
+
process.stdout.write(`Config already exists: ${result.path}
|
|
25111
|
+
`);
|
|
25112
|
+
} else {
|
|
25113
|
+
process.stdout.write(`Created config: ${result.path}
|
|
25114
|
+
`);
|
|
25115
|
+
}
|
|
25116
|
+
return 0;
|
|
25117
|
+
}
|
|
25118
|
+
if (command === "path") {
|
|
25119
|
+
process.stdout.write(`${defaultUserConfigPath()}
|
|
25120
|
+
`);
|
|
25121
|
+
return 0;
|
|
25122
|
+
}
|
|
25123
|
+
if (command === "setup") {
|
|
25124
|
+
return runSetupWizard();
|
|
25125
|
+
}
|
|
25126
|
+
if (command === "list") {
|
|
25127
|
+
const flags = parseFlags(rest);
|
|
25128
|
+
const config = await loadConfig({ cwd: process.cwd(), configPath: stringFlag(flags, "config") });
|
|
25129
|
+
process.stdout.write(`defaultProvider: ${config.defaultProvider}
|
|
25130
|
+
`);
|
|
25131
|
+
for (const name of Object.keys(config.providers)) {
|
|
25132
|
+
const provider = config.providers[name];
|
|
25133
|
+
if (provider.hidden) continue;
|
|
25134
|
+
process.stdout.write(`- ${name} (${provider.type})
|
|
25135
|
+
`);
|
|
25136
|
+
}
|
|
25137
|
+
return 0;
|
|
25138
|
+
}
|
|
25139
|
+
if (command === "models") {
|
|
25140
|
+
const providerName = rest.find((item) => !item.startsWith("-"));
|
|
25141
|
+
if (!providerName) throw new Error("config models requires a provider name.");
|
|
25142
|
+
const flags = parseFlags(rest.filter((item) => item !== providerName));
|
|
25143
|
+
const config = await loadConfig({ cwd: process.cwd(), configPath: stringFlag(flags, "config") });
|
|
25144
|
+
const provider = resolveProviderConfig(config, providerName);
|
|
25145
|
+
const result = await listProviderModelCatalog(providerName, provider, { refresh: boolFlag(flags, "refresh") });
|
|
25146
|
+
if (result.status !== "ready") {
|
|
25147
|
+
process.stdout.write(`${providerName}: ${result.status}${result.message ? ` (${result.message})` : ""}
|
|
25148
|
+
`);
|
|
25149
|
+
return result.status === "missing-auth" ? 2 : 1;
|
|
25150
|
+
}
|
|
25151
|
+
for (const model of result.models) {
|
|
25152
|
+
const label = model.displayName && model.displayName !== model.model ? `${model.displayName} (${model.model})` : model.model;
|
|
25153
|
+
process.stdout.write(`${model.isDefault ? "* " : " "}${label}
|
|
25154
|
+
`);
|
|
25155
|
+
}
|
|
25156
|
+
return 0;
|
|
25157
|
+
}
|
|
25158
|
+
if (command === "add-provider") {
|
|
25159
|
+
const [presetOrName, ...tail2] = rest;
|
|
25160
|
+
if (!presetOrName) throw new Error("config add-provider requires a provider name or preset.");
|
|
25161
|
+
const flags = parseFlags(tail2);
|
|
25162
|
+
const name = stringFlag(flags, "name") ?? presetOrName;
|
|
25163
|
+
const force = await ensureOverwriteForProvider(stringFlag(flags, "path"), name, boolFlag(flags, "force"));
|
|
25164
|
+
const result = await addProvider({
|
|
25165
|
+
path: stringFlag(flags, "path"),
|
|
25166
|
+
name,
|
|
25167
|
+
preset: presetOrName,
|
|
25168
|
+
type: stringFlag(flags, "type") ?? presetOrName,
|
|
25169
|
+
baseURL: stringFlag(flags, "base-url"),
|
|
25170
|
+
apiKey: await apiKeyFromFlags(flags),
|
|
25171
|
+
apiKeyEnv: stringFlag(flags, "api-key-env"),
|
|
25172
|
+
authHeader: stringFlag(flags, "auth-header"),
|
|
25173
|
+
force
|
|
25174
|
+
});
|
|
25175
|
+
process.stdout.write(`Updated config: ${result.path}
|
|
25176
|
+
`);
|
|
25177
|
+
return 0;
|
|
25178
|
+
}
|
|
25179
|
+
if (command === "add-model") {
|
|
25180
|
+
const [provider, ...tail2] = rest;
|
|
25181
|
+
if (!provider) throw new Error("config add-model requires a provider name.");
|
|
25182
|
+
const flags = parseFlags(tail2);
|
|
25183
|
+
const name = requiredStringFlag(flags, "name");
|
|
25184
|
+
const model = requiredStringFlag(flags, "model");
|
|
25185
|
+
const displayName = stringFlag(flags, "display-name");
|
|
25186
|
+
const force = await ensureOverwriteForModel(stringFlag(flags, "path"), provider, name, displayName, boolFlag(flags, "force"));
|
|
25187
|
+
const result = await addModelProfile({
|
|
25188
|
+
path: stringFlag(flags, "path"),
|
|
25189
|
+
provider,
|
|
25190
|
+
name,
|
|
25191
|
+
displayName,
|
|
25192
|
+
model,
|
|
25193
|
+
baseURL: stringFlag(flags, "base-url"),
|
|
25194
|
+
apiKey: await apiKeyFromFlags(flags),
|
|
25195
|
+
apiKeyEnv: stringFlag(flags, "api-key-env"),
|
|
25196
|
+
force
|
|
25197
|
+
});
|
|
25198
|
+
process.stdout.write(`Updated config: ${result.path}
|
|
25199
|
+
`);
|
|
25200
|
+
return 0;
|
|
25201
|
+
}
|
|
25202
|
+
throw new Error(`Unknown config command: ${command}`);
|
|
25203
|
+
} catch (error) {
|
|
25204
|
+
process.stderr.write(`${errorMessage3(error)}
|
|
25205
|
+
`);
|
|
25206
|
+
return 1;
|
|
25207
|
+
}
|
|
25208
|
+
}
|
|
25209
|
+
async function runAuthCommand(argv) {
|
|
25210
|
+
const [command, provider] = argv;
|
|
25211
|
+
try {
|
|
25212
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
25213
|
+
printAuthHelp();
|
|
25214
|
+
return 0;
|
|
25215
|
+
}
|
|
25216
|
+
if (command === "status") {
|
|
25217
|
+
const claude = await preflightClaudeCodeAuth({ sanitizeApiKeyEnv: true }).catch((error) => ({ ok: false, source: "unknown", warning: errorMessage3(error) }));
|
|
25218
|
+
process.stdout.write(`claudecode: ${claude.ok ? "ok" : "not ready"} (${claude.source})${claude.warning ? ` - ${claude.warning}` : ""}
|
|
25219
|
+
`);
|
|
25220
|
+
const codex = await checkCodexAuth();
|
|
25221
|
+
process.stdout.write(`codex: ${codex.ok ? "ok" : "not ready"} (${codex.source})${codex.message ? ` - ${codex.message}` : ""}
|
|
25222
|
+
`);
|
|
25223
|
+
return claude.ok && codex.ok ? 0 : 1;
|
|
25224
|
+
}
|
|
25225
|
+
if (command !== "login") throw new Error(`Unknown auth command: ${command}`);
|
|
25226
|
+
return runProviderLogin(provider);
|
|
25227
|
+
} catch (error) {
|
|
25228
|
+
process.stderr.write(`${errorMessage3(error)}
|
|
25229
|
+
`);
|
|
25230
|
+
return 1;
|
|
25231
|
+
}
|
|
25232
|
+
}
|
|
25233
|
+
async function runProviderLogin(provider) {
|
|
25234
|
+
if (provider === "codex") {
|
|
25235
|
+
if (!await commandExists("codex")) {
|
|
25236
|
+
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");
|
|
25237
|
+
return 1;
|
|
25238
|
+
}
|
|
25239
|
+
return runLogin("codex", ["login"]);
|
|
25240
|
+
}
|
|
25241
|
+
if (provider === "claudecode") {
|
|
25242
|
+
const cli = resolveClaudeCodeCliPath();
|
|
25243
|
+
if (!cli && !await commandExists("claude")) {
|
|
25244
|
+
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");
|
|
25245
|
+
return 1;
|
|
25246
|
+
}
|
|
25247
|
+
return runLogin(cli ?? "claude", ["setup-token"]);
|
|
25248
|
+
}
|
|
25249
|
+
throw new Error("auth login requires provider codex or claudecode.");
|
|
25250
|
+
}
|
|
25251
|
+
function runLogin(command, args) {
|
|
25252
|
+
process.stdout.write(`Starting ${[command, ...args].join(" ")}...
|
|
25253
|
+
`);
|
|
25254
|
+
const opened = /* @__PURE__ */ new Set();
|
|
25255
|
+
return new Promise((resolve) => {
|
|
25256
|
+
const child = spawn4(command, args, { stdio: ["inherit", "pipe", "pipe"], env: process.env });
|
|
25257
|
+
const onText = (stream) => (chunk) => {
|
|
25258
|
+
const text = chunk.toString();
|
|
25259
|
+
stream.write(text);
|
|
25260
|
+
for (const url of urlsInText(text)) {
|
|
25261
|
+
if (opened.has(url)) continue;
|
|
25262
|
+
opened.add(url);
|
|
25263
|
+
void openUrl(url);
|
|
25264
|
+
}
|
|
25265
|
+
};
|
|
25266
|
+
child.stdout.on("data", onText(process.stdout));
|
|
25267
|
+
child.stderr.on("data", onText(process.stderr));
|
|
25268
|
+
child.on("error", (error) => {
|
|
25269
|
+
process.stderr.write(`${error.message}
|
|
25270
|
+
`);
|
|
25271
|
+
resolve(1);
|
|
25272
|
+
});
|
|
25273
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
25274
|
+
});
|
|
25275
|
+
}
|
|
25276
|
+
function parseFlags(argv) {
|
|
25277
|
+
const flags = /* @__PURE__ */ new Map();
|
|
25278
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25279
|
+
const arg = argv[i];
|
|
25280
|
+
if (!arg.startsWith("--")) continue;
|
|
25281
|
+
const key = arg.slice(2);
|
|
25282
|
+
const next = argv[i + 1];
|
|
25283
|
+
if (!next || next.startsWith("--")) {
|
|
25284
|
+
flags.set(key, true);
|
|
25285
|
+
} else {
|
|
25286
|
+
flags.set(key, next);
|
|
25287
|
+
i++;
|
|
25288
|
+
}
|
|
25289
|
+
}
|
|
25290
|
+
return flags;
|
|
25291
|
+
}
|
|
25292
|
+
function stringFlag(flags, name) {
|
|
25293
|
+
const value = flags.get(name);
|
|
25294
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
25295
|
+
}
|
|
25296
|
+
function requiredStringFlag(flags, name) {
|
|
25297
|
+
const value = stringFlag(flags, name);
|
|
25298
|
+
if (!value) throw new Error(`--${name} is required.`);
|
|
25299
|
+
return value;
|
|
25300
|
+
}
|
|
25301
|
+
function boolFlag(flags, name) {
|
|
25302
|
+
return flags.get(name) === true;
|
|
25303
|
+
}
|
|
25304
|
+
async function apiKeyFromFlags(flags) {
|
|
25305
|
+
const inline = stringFlag(flags, "api-key");
|
|
25306
|
+
if (inline) return inline;
|
|
25307
|
+
if (!boolFlag(flags, "api-key-stdin")) return void 0;
|
|
25308
|
+
return (await readStdin()).trim();
|
|
25309
|
+
}
|
|
25310
|
+
async function readStdin() {
|
|
25311
|
+
let value = "";
|
|
25312
|
+
for await (const chunk of process.stdin) value += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
25313
|
+
return value;
|
|
25314
|
+
}
|
|
25315
|
+
function urlsInText(text) {
|
|
25316
|
+
return [...text.matchAll(/https?:\/\/[^\s)>"']+/g)].map((match) => match[0]);
|
|
25317
|
+
}
|
|
25318
|
+
function openUrl(url) {
|
|
25319
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
25320
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
25321
|
+
return new Promise((resolve) => {
|
|
25322
|
+
const child = spawn4(command, args, { stdio: "ignore", detached: true });
|
|
25323
|
+
child.on("error", () => resolve());
|
|
25324
|
+
child.on("spawn", () => {
|
|
25325
|
+
child.unref();
|
|
25326
|
+
resolve();
|
|
25327
|
+
});
|
|
25328
|
+
});
|
|
25329
|
+
}
|
|
25330
|
+
function printConfigHelp() {
|
|
25331
|
+
process.stdout.write(`demian config
|
|
25332
|
+
|
|
25333
|
+
Usage:
|
|
25334
|
+
demian config init [--path <path>] [--force] [--print] [--provider <name>]
|
|
25335
|
+
demian config setup interactive multi-step provider wizard
|
|
25336
|
+
demian config add-provider <preset|openai-compatible> [--name <name>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--auth-header <header>] [--force]
|
|
25337
|
+
demian config add-model <provider> --name <name> --model <model> [--display-name <label>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--force]
|
|
25338
|
+
demian config models <provider> [--refresh] [--config <path>]
|
|
25339
|
+
demian config list [--config <path>]
|
|
25340
|
+
demian config path
|
|
25341
|
+
`);
|
|
25342
|
+
}
|
|
25343
|
+
function printAuthHelp() {
|
|
25344
|
+
process.stdout.write(`demian auth
|
|
25345
|
+
|
|
25346
|
+
Usage:
|
|
25347
|
+
demian auth login codex
|
|
25348
|
+
demian auth login claudecode
|
|
25349
|
+
demian auth status
|
|
25350
|
+
`);
|
|
25351
|
+
}
|
|
25352
|
+
function errorMessage3(error) {
|
|
25353
|
+
return error instanceof Error ? error.message : String(error);
|
|
25354
|
+
}
|
|
25355
|
+
async function confirmOverwriteIfExists(customPath, isPrint) {
|
|
25356
|
+
if (isPrint) return false;
|
|
25357
|
+
const filePath = customPath ?? defaultUserConfigPath();
|
|
25358
|
+
if (!await fileExists2(filePath)) return false;
|
|
25359
|
+
return promptYesNo(`Overwrite existing config at ${filePath}? (y/N): `, false);
|
|
25360
|
+
}
|
|
25361
|
+
async function ensureOverwriteForProvider(customPath, providerName, force) {
|
|
25362
|
+
if (force) return true;
|
|
25363
|
+
const filePath = customPath ?? defaultUserConfigPath();
|
|
25364
|
+
if (!await fileExists2(filePath)) return false;
|
|
25365
|
+
const config = await readJsonSafe(filePath);
|
|
25366
|
+
const provider = config?.providers?.[providerName];
|
|
25367
|
+
if (!provider) return false;
|
|
25368
|
+
return promptYesNo(`Provider "${providerName}" already exists. Overwrite? (y/N): `, false);
|
|
25369
|
+
}
|
|
25370
|
+
async function ensureOverwriteForModel(customPath, providerName, profileName, displayName, force) {
|
|
25371
|
+
if (force) return true;
|
|
25372
|
+
const filePath = customPath ?? defaultUserConfigPath();
|
|
25373
|
+
if (!await fileExists2(filePath)) return false;
|
|
25374
|
+
const config = await readJsonSafe(filePath);
|
|
25375
|
+
const profiles = Array.isArray(config?.providers?.[providerName]?.modelProfiles) ? config.providers[providerName].modelProfiles : [];
|
|
25376
|
+
const sameName = profiles.find((entry) => entry.name === profileName);
|
|
25377
|
+
const sameDisplay = displayName ? profiles.find((entry) => entry.displayName === displayName && entry.name !== profileName) : void 0;
|
|
25378
|
+
if (!sameName && !sameDisplay) return false;
|
|
25379
|
+
const which = sameName ? `name "${profileName}"` : `displayName "${displayName}"`;
|
|
25380
|
+
return promptYesNo(`Profile with ${which} already exists on provider ${providerName}. Overwrite? (y/N): `, false);
|
|
25381
|
+
}
|
|
25382
|
+
async function fileExists2(filePath) {
|
|
25383
|
+
try {
|
|
25384
|
+
await stat4(filePath);
|
|
25385
|
+
return true;
|
|
25386
|
+
} catch {
|
|
25387
|
+
return false;
|
|
25388
|
+
}
|
|
25389
|
+
}
|
|
25390
|
+
async function readJsonSafe(filePath) {
|
|
25391
|
+
try {
|
|
25392
|
+
const { readFile: readFile18 } = await import("node:fs/promises");
|
|
25393
|
+
const text = await readFile18(filePath, "utf8");
|
|
25394
|
+
return JSON.parse(text);
|
|
25395
|
+
} catch {
|
|
25396
|
+
return void 0;
|
|
25397
|
+
}
|
|
25398
|
+
}
|
|
25399
|
+
async function promptYesNo(question, defaultYes) {
|
|
25400
|
+
if (!process.stdin.isTTY) {
|
|
25401
|
+
process.stderr.write(`${question.replace(/\(y\/N\)|\(Y\/n\)/, "").trim()} (non-interactive; pass --force to overwrite).
|
|
25402
|
+
`);
|
|
25403
|
+
return false;
|
|
25404
|
+
}
|
|
25405
|
+
const rl2 = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
25406
|
+
try {
|
|
25407
|
+
const answer = (await rl2.question(question)).trim().toLowerCase();
|
|
25408
|
+
if (!answer) return defaultYes;
|
|
25409
|
+
return answer === "y" || answer === "yes";
|
|
25410
|
+
} finally {
|
|
25411
|
+
rl2.close();
|
|
25412
|
+
}
|
|
25413
|
+
}
|
|
25414
|
+
async function commandExists(command) {
|
|
25415
|
+
return new Promise((resolve) => {
|
|
25416
|
+
const child = spawn4(process.platform === "win32" ? "where" : "which", [command], { stdio: "ignore" });
|
|
25417
|
+
child.on("close", (code) => resolve(code === 0));
|
|
25418
|
+
child.on("error", () => resolve(false));
|
|
25419
|
+
});
|
|
25420
|
+
}
|
|
25421
|
+
async function runFirstRunWizard() {
|
|
25422
|
+
const filePath = defaultUserConfigPath();
|
|
25423
|
+
if (await fileExists2(filePath)) return void 0;
|
|
25424
|
+
if (!process.stdin.isTTY) {
|
|
25425
|
+
process.stderr.write(`No Demian config at ${filePath}. Run \`demian config init\` to create one.
|
|
25426
|
+
`);
|
|
25427
|
+
return void 0;
|
|
25428
|
+
}
|
|
25429
|
+
process.stdout.write(`
|
|
25430
|
+
Welcome to Demian.
|
|
25431
|
+
No config found at ${filePath}.
|
|
25432
|
+
`);
|
|
25433
|
+
const ok2 = await promptYesNo("Create a default v2 config now? (Y/n): ", true);
|
|
25434
|
+
if (!ok2) {
|
|
25435
|
+
process.stdout.write("Skipped. Run `demian config init` later to set up.\n");
|
|
25436
|
+
return void 0;
|
|
25437
|
+
}
|
|
25438
|
+
const result = await createUserConfig({});
|
|
25439
|
+
process.stdout.write(`Created ${result.path}.
|
|
25440
|
+
`);
|
|
25441
|
+
const walkProviders = await promptYesNo("Walk through provider setup now? (y/N): ", false);
|
|
25442
|
+
if (walkProviders) await walkProviderSetup();
|
|
25443
|
+
else {
|
|
25444
|
+
process.stdout.write("Next steps:\n");
|
|
25445
|
+
process.stdout.write(" - Set API key env vars (e.g. OPENAI_API_KEY)\n");
|
|
25446
|
+
process.stdout.write(" - `demian config models <provider> --refresh` to inspect catalogs\n");
|
|
25447
|
+
process.stdout.write(" - `demian auth login codex|claudecode` for OAuth providers\n");
|
|
25448
|
+
process.stdout.write(" - `demian config setup` to re-enter this wizard\n\n");
|
|
25449
|
+
}
|
|
25450
|
+
return { created: true, configPath: result.path };
|
|
25451
|
+
}
|
|
25452
|
+
async function runSetupWizard() {
|
|
25453
|
+
if (!process.stdin.isTTY) {
|
|
25454
|
+
process.stderr.write("config setup requires an interactive terminal.\n");
|
|
25455
|
+
return 1;
|
|
25456
|
+
}
|
|
25457
|
+
const filePath = defaultUserConfigPath();
|
|
25458
|
+
if (!await fileExists2(filePath)) {
|
|
25459
|
+
const wizard = await runFirstRunWizard();
|
|
25460
|
+
return wizard?.created ? 0 : 1;
|
|
25461
|
+
}
|
|
25462
|
+
await walkProviderSetup();
|
|
25463
|
+
return 0;
|
|
25464
|
+
}
|
|
25465
|
+
async function walkProviderSetup() {
|
|
25466
|
+
const builtinPresets = [
|
|
25467
|
+
{ name: "openai", description: "OpenAI ChatGPT API", defaultEnv: "OPENAI_API_KEY" },
|
|
25468
|
+
{ name: "anthropic", description: "Anthropic Claude API", defaultEnv: "ANTHROPIC_API_KEY" },
|
|
25469
|
+
{ name: "gemini", description: "Google Gemini (OpenAI-compatible)", defaultEnv: "GEMINI_API_KEY" },
|
|
25470
|
+
{ name: "groq", description: "Groq Cloud", defaultEnv: "GROQ_API_KEY" },
|
|
25471
|
+
{ name: "codex", description: "ChatGPT Codex (OAuth)", oauth: "codex" },
|
|
25472
|
+
{ name: "claudecode", description: "Claude Code external runtime (OAuth)", oauth: "claudecode" }
|
|
25473
|
+
];
|
|
25474
|
+
process.stdout.write("\nProvider setup. Press Enter to skip a provider.\n");
|
|
25475
|
+
for (const preset of builtinPresets) {
|
|
25476
|
+
const want = await promptYesNo(`Configure ${preset.name} (${preset.description})? (y/N): `, false);
|
|
25477
|
+
if (!want) continue;
|
|
25478
|
+
if (preset.oauth) {
|
|
25479
|
+
const login = await promptYesNo(`Run \`demian auth login ${preset.oauth}\` now? (Y/n): `, true);
|
|
25480
|
+
if (login) await runProviderLogin(preset.oauth).catch(() => void 0);
|
|
25481
|
+
continue;
|
|
25482
|
+
}
|
|
25483
|
+
if (preset.defaultEnv) {
|
|
25484
|
+
const envSet = !!process.env[preset.defaultEnv];
|
|
25485
|
+
process.stdout.write(` env ${preset.defaultEnv}: ${envSet ? "set" : "not set"}
|
|
25486
|
+
`);
|
|
25487
|
+
if (!envSet) process.stdout.write(` \u2192 export ${preset.defaultEnv}=... before running demian.
|
|
25488
|
+
`);
|
|
25489
|
+
}
|
|
25490
|
+
}
|
|
25491
|
+
process.stdout.write("\nSetup complete. Run `demian` to start a session.\n\n");
|
|
25492
|
+
}
|
|
25493
|
+
async function checkCodexAuth() {
|
|
25494
|
+
const candidates = [path16.join(process.env.HOME ?? os10.homedir(), ".codex", "auth.json"), path16.join(process.env.HOME ?? os10.homedir(), ".codex", "credentials.json")];
|
|
25495
|
+
for (const candidate of candidates) {
|
|
25496
|
+
if (await fileExists2(candidate)) {
|
|
25497
|
+
return { ok: true, source: "codex-store", message: candidate };
|
|
25498
|
+
}
|
|
25499
|
+
}
|
|
25500
|
+
if (process.env.OPENAI_API_KEY) return { ok: true, source: "api-key-fallback", message: "OPENAI_API_KEY set" };
|
|
25501
|
+
return { ok: false, source: "none", message: "no codex auth file or OPENAI_API_KEY; run `demian auth login codex`" };
|
|
25502
|
+
}
|
|
25503
|
+
|
|
25504
|
+
// src/doctor/policies.ts
|
|
25505
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
25506
|
+
import { readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
|
|
25507
|
+
import os11 from "node:os";
|
|
25508
|
+
import path17 from "node:path";
|
|
25509
|
+
function isDoctorCommand(argv) {
|
|
25510
|
+
return argv[0] === "doctor";
|
|
25511
|
+
}
|
|
25512
|
+
async function maybeRunDoctorCommand(argv, options = {}) {
|
|
25513
|
+
if (!isDoctorCommand(argv)) return void 0;
|
|
25514
|
+
return runDoctorCommand(argv.slice(1), options);
|
|
25515
|
+
}
|
|
25516
|
+
async function runDoctorCommand(argv, options = {}) {
|
|
25517
|
+
const stdout = options.stdout ?? process.stdout;
|
|
25518
|
+
const stderr = options.stderr ?? process.stderr;
|
|
25519
|
+
const flags = parseDoctorFlags(argv);
|
|
25520
|
+
if (flags.help) {
|
|
25521
|
+
stdout.write(doctorHelp());
|
|
25522
|
+
return 0;
|
|
25523
|
+
}
|
|
25524
|
+
if (argv[0] !== "policies" || !argv.includes("--upgrade-namespaces")) {
|
|
25525
|
+
stderr.write("Unsupported doctor command. Run `demian doctor policies --upgrade-namespaces [--write]`.\n");
|
|
25526
|
+
return 1;
|
|
25527
|
+
}
|
|
25528
|
+
const cwd = path17.resolve(flags.cwd ?? options.cwd ?? process.cwd());
|
|
25529
|
+
const filePaths = resolvePolicyConfigPaths(cwd, flags.configPaths);
|
|
25530
|
+
if (filePaths.length === 0) {
|
|
25531
|
+
stdout.write("No demian config files found to inspect.\n");
|
|
24183
25532
|
return 0;
|
|
24184
25533
|
}
|
|
24185
25534
|
const allChanges = [];
|
|
24186
25535
|
let updatedFiles = 0;
|
|
24187
25536
|
for (const filePath of filePaths) {
|
|
24188
|
-
const original = await
|
|
25537
|
+
const original = await readFile9(filePath, "utf8");
|
|
24189
25538
|
const upgraded = upgradePolicyNamespacesInText(original, filePath);
|
|
24190
25539
|
allChanges.push(...upgraded.changes);
|
|
24191
25540
|
if (flags.write && upgraded.changes.length > 0) {
|
|
24192
|
-
await
|
|
25541
|
+
await writeFile7(filePath, upgraded.text, "utf8");
|
|
24193
25542
|
updatedFiles++;
|
|
24194
25543
|
}
|
|
24195
25544
|
}
|
|
@@ -24277,11 +25626,11 @@ function parseDoctorFlags(argv) {
|
|
|
24277
25626
|
return flags;
|
|
24278
25627
|
}
|
|
24279
25628
|
function resolvePolicyConfigPaths(cwd, explicitPaths) {
|
|
24280
|
-
const candidates = explicitPaths.length > 0 ? explicitPaths.map((item) =>
|
|
24281
|
-
|
|
24282
|
-
|
|
24283
|
-
|
|
24284
|
-
|
|
25629
|
+
const candidates = explicitPaths.length > 0 ? explicitPaths.map((item) => path17.resolve(cwd, item)) : [
|
|
25630
|
+
path17.join(os11.homedir(), ".demian", "config.json"),
|
|
25631
|
+
path17.join(os11.homedir(), ".demian", "config.jsond"),
|
|
25632
|
+
path17.join(cwd, ".demian", "config.json"),
|
|
25633
|
+
path17.join(cwd, ".demian", "config.jsond")
|
|
24285
25634
|
];
|
|
24286
25635
|
return [...new Set(candidates)].filter((item) => existsSync2(item));
|
|
24287
25636
|
}
|
|
@@ -24377,8 +25726,9 @@ Flags:
|
|
|
24377
25726
|
}
|
|
24378
25727
|
|
|
24379
25728
|
// src/transcript.ts
|
|
24380
|
-
import { mkdir as
|
|
24381
|
-
import
|
|
25729
|
+
import { mkdir as mkdir8, appendFile, writeFile as writeFile8 } from "node:fs/promises";
|
|
25730
|
+
import os12 from "node:os";
|
|
25731
|
+
import path18 from "node:path";
|
|
24382
25732
|
var TranscriptWriter = class {
|
|
24383
25733
|
filePath;
|
|
24384
25734
|
#enabled;
|
|
@@ -24386,9 +25736,9 @@ var TranscriptWriter = class {
|
|
|
24386
25736
|
#queue = Promise.resolve();
|
|
24387
25737
|
constructor(options) {
|
|
24388
25738
|
this.#enabled = options.enabled !== false;
|
|
24389
|
-
const dir =
|
|
24390
|
-
this.filePath =
|
|
24391
|
-
this.#ready = this.#enabled ?
|
|
25739
|
+
const dir = path18.join(options.storageDir ?? defaultDemianStorageDir(), "transcripts", options.sessionId);
|
|
25740
|
+
this.filePath = path18.join(dir, "session.jsonl");
|
|
25741
|
+
this.#ready = this.#enabled ? mkdir8(dir, { recursive: true }).then(() => writeFile8(this.filePath, "", { flag: "a" })) : Promise.resolve();
|
|
24392
25742
|
}
|
|
24393
25743
|
write(event) {
|
|
24394
25744
|
if (!this.#enabled) return Promise.resolve();
|
|
@@ -24403,12 +25753,15 @@ var TranscriptWriter = class {
|
|
|
24403
25753
|
await this.#queue;
|
|
24404
25754
|
}
|
|
24405
25755
|
};
|
|
25756
|
+
function defaultDemianStorageDir() {
|
|
25757
|
+
return path18.join(os12.homedir(), ".demian");
|
|
25758
|
+
}
|
|
24406
25759
|
|
|
24407
25760
|
// src/external-runtime/snapshot-diff.ts
|
|
24408
25761
|
import { createHash as createHash2 } from "node:crypto";
|
|
24409
25762
|
import { createReadStream } from "node:fs";
|
|
24410
|
-
import { readdir, readFile as
|
|
24411
|
-
import
|
|
25763
|
+
import { readdir, readFile as readFile10, stat as stat5 } from "node:fs/promises";
|
|
25764
|
+
import path19 from "node:path";
|
|
24412
25765
|
|
|
24413
25766
|
// src/workspace/diff.ts
|
|
24414
25767
|
var CONTEXT_LINES = 3;
|
|
@@ -24618,7 +25971,7 @@ async function diffWorkspaceSnapshot(before) {
|
|
|
24618
25971
|
}
|
|
24619
25972
|
async function walk(root, relativeDir, entries, options) {
|
|
24620
25973
|
if (entries.size >= options.maxFiles) return;
|
|
24621
|
-
const absoluteDir =
|
|
25974
|
+
const absoluteDir = path19.join(root, relativeDir);
|
|
24622
25975
|
let items;
|
|
24623
25976
|
try {
|
|
24624
25977
|
items = await readdir(absoluteDir, { withFileTypes: true });
|
|
@@ -24629,8 +25982,8 @@ async function walk(root, relativeDir, entries, options) {
|
|
|
24629
25982
|
if (entries.size >= options.maxFiles) return;
|
|
24630
25983
|
if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
|
|
24631
25984
|
if (SKIP_DIRS.has(item.name)) continue;
|
|
24632
|
-
const relativePath = normalizeRelativePath(
|
|
24633
|
-
const absolutePath =
|
|
25985
|
+
const relativePath = normalizeRelativePath(path19.join(relativeDir, item.name));
|
|
25986
|
+
const absolutePath = path19.join(root, relativePath);
|
|
24634
25987
|
if (item.isDirectory()) {
|
|
24635
25988
|
await walk(root, relativePath, entries, options);
|
|
24636
25989
|
continue;
|
|
@@ -24641,7 +25994,7 @@ async function walk(root, relativeDir, entries, options) {
|
|
|
24641
25994
|
}
|
|
24642
25995
|
}
|
|
24643
25996
|
async function snapshotFile(filePath, relativePath, maxTextBytes) {
|
|
24644
|
-
const info = await
|
|
25997
|
+
const info = await stat5(filePath);
|
|
24645
25998
|
const sha2562 = await sha256File(filePath);
|
|
24646
25999
|
const content = info.size <= maxTextBytes ? await readTextContent(filePath) : void 0;
|
|
24647
26000
|
return { path: relativePath, size: info.size, sha256: sha2562, content };
|
|
@@ -24652,7 +26005,7 @@ function snapshotDiffText(entry) {
|
|
|
24652
26005
|
`;
|
|
24653
26006
|
}
|
|
24654
26007
|
async function readTextContent(filePath) {
|
|
24655
|
-
const buffer = await
|
|
26008
|
+
const buffer = await readFile10(filePath);
|
|
24656
26009
|
if (buffer.includes(0)) return void 0;
|
|
24657
26010
|
return buffer.toString("utf8");
|
|
24658
26011
|
}
|
|
@@ -24666,7 +26019,7 @@ function sha256File(filePath) {
|
|
|
24666
26019
|
});
|
|
24667
26020
|
}
|
|
24668
26021
|
function normalizeRelativePath(value) {
|
|
24669
|
-
return value.split(
|
|
26022
|
+
return value.split(path19.sep).join("/");
|
|
24670
26023
|
}
|
|
24671
26024
|
|
|
24672
26025
|
// src/external-runtime/session-runner.ts
|
|
@@ -25101,15 +26454,15 @@ function safeJson(value) {
|
|
|
25101
26454
|
}
|
|
25102
26455
|
|
|
25103
26456
|
// src/hooks/dispatcher.ts
|
|
25104
|
-
import
|
|
26457
|
+
import path21 from "node:path";
|
|
25105
26458
|
|
|
25106
26459
|
// src/hooks/command.ts
|
|
25107
|
-
import { spawn as
|
|
26460
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
25108
26461
|
async function runCommandHook(hook, ctx) {
|
|
25109
26462
|
if (!hook.command) return void 0;
|
|
25110
26463
|
const timeoutMs = hook.timeoutMs ?? 1e4;
|
|
25111
26464
|
return new Promise((resolve, reject) => {
|
|
25112
|
-
const child =
|
|
26465
|
+
const child = spawn5(hook.command, {
|
|
25113
26466
|
cwd: ctx.cwd,
|
|
25114
26467
|
env: process.env,
|
|
25115
26468
|
shell: true,
|
|
@@ -25194,7 +26547,7 @@ var blockDangerousBashHook = {
|
|
|
25194
26547
|
};
|
|
25195
26548
|
|
|
25196
26549
|
// src/hooks/builtin/protect-env-files.ts
|
|
25197
|
-
import
|
|
26550
|
+
import path20 from "node:path";
|
|
25198
26551
|
var protectEnvFilesHook = {
|
|
25199
26552
|
name: "protect-env-files",
|
|
25200
26553
|
event: "PreToolUse",
|
|
@@ -25202,7 +26555,7 @@ var protectEnvFilesHook = {
|
|
|
25202
26555
|
run(ctx) {
|
|
25203
26556
|
if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
|
|
25204
26557
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
25205
|
-
const filePath = typeof input2.path === "string" ?
|
|
26558
|
+
const filePath = typeof input2.path === "string" ? path20.resolve(ctx.cwd, input2.path) : "";
|
|
25206
26559
|
if (filePath && isEnvFile(filePath)) {
|
|
25207
26560
|
return {
|
|
25208
26561
|
decision: "block",
|
|
@@ -25239,7 +26592,7 @@ var maskSecretsHook = {
|
|
|
25239
26592
|
};
|
|
25240
26593
|
|
|
25241
26594
|
// src/hooks/builtin/inject-env-info.ts
|
|
25242
|
-
import
|
|
26595
|
+
import os13 from "node:os";
|
|
25243
26596
|
var injectEnvInfoHook = {
|
|
25244
26597
|
name: "inject-env-info",
|
|
25245
26598
|
event: "SessionStart",
|
|
@@ -25248,7 +26601,7 @@ var injectEnvInfoHook = {
|
|
|
25248
26601
|
decision: "allow",
|
|
25249
26602
|
patch: {
|
|
25250
26603
|
systemNote: [
|
|
25251
|
-
`Environment: ${
|
|
26604
|
+
`Environment: ${os13.type()} ${os13.release()} (${os13.platform()}/${os13.arch()})`,
|
|
25252
26605
|
`cwd: ${ctx.cwd}`,
|
|
25253
26606
|
`provider: ${ctx.provider ?? "unknown"}`,
|
|
25254
26607
|
`model: ${ctx.model ?? "unknown"}`,
|
|
@@ -25317,14 +26670,14 @@ function matchesHook(match, ctx) {
|
|
|
25317
26670
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
25318
26671
|
const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
|
|
25319
26672
|
if (!filePath) return false;
|
|
25320
|
-
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd,
|
|
26673
|
+
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path21.resolve(ctx.cwd, filePath)))) return false;
|
|
25321
26674
|
}
|
|
25322
26675
|
return true;
|
|
25323
26676
|
}
|
|
25324
26677
|
|
|
25325
26678
|
// src/multimodal.ts
|
|
25326
|
-
import { readFile as
|
|
25327
|
-
import
|
|
26679
|
+
import { readFile as readFile11, stat as stat6 } from "node:fs/promises";
|
|
26680
|
+
import path22 from "node:path";
|
|
25328
26681
|
var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
25329
26682
|
async function buildUserContent(prompt, images = [], options) {
|
|
25330
26683
|
if (images.length === 0) return prompt;
|
|
@@ -25343,16 +26696,16 @@ async function buildUserContent(prompt, images = [], options) {
|
|
|
25343
26696
|
async function imageToUrl(input2, options) {
|
|
25344
26697
|
if (/^https?:\/\//i.test(input2) || /^data:image\//i.test(input2)) return input2;
|
|
25345
26698
|
const filePath = resolveInsideCwd(options.cwd, input2);
|
|
25346
|
-
const info = await
|
|
26699
|
+
const info = await stat6(filePath);
|
|
25347
26700
|
if (!info.isFile()) throw new Error(`Image input is not a file: ${input2}`);
|
|
25348
26701
|
const maxImageBytes = options.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
|
|
25349
26702
|
if (info.size > maxImageBytes) throw new Error(`Image is larger than ${maxImageBytes} bytes: ${input2}`);
|
|
25350
26703
|
const mime = mimeFromPath(filePath);
|
|
25351
|
-
const bytes = await
|
|
26704
|
+
const bytes = await readFile11(filePath);
|
|
25352
26705
|
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
25353
26706
|
}
|
|
25354
26707
|
function mimeFromPath(filePath) {
|
|
25355
|
-
switch (
|
|
26708
|
+
switch (path22.extname(filePath).toLowerCase()) {
|
|
25356
26709
|
case ".jpg":
|
|
25357
26710
|
case ".jpeg":
|
|
25358
26711
|
return "image/jpeg";
|
|
@@ -25363,15 +26716,15 @@ function mimeFromPath(filePath) {
|
|
|
25363
26716
|
case ".webp":
|
|
25364
26717
|
return "image/webp";
|
|
25365
26718
|
default:
|
|
25366
|
-
throw new Error(`Unsupported image extension: ${
|
|
26719
|
+
throw new Error(`Unsupported image extension: ${path22.extname(filePath) || "(none)"}`);
|
|
25367
26720
|
}
|
|
25368
26721
|
}
|
|
25369
26722
|
|
|
25370
26723
|
// src/permissions/persistent-grants.ts
|
|
25371
|
-
import { mkdir as
|
|
26724
|
+
import { mkdir as mkdir9, readFile as readFile12, writeFile as writeFile9 } from "node:fs/promises";
|
|
25372
26725
|
import fs8 from "node:fs";
|
|
25373
|
-
import
|
|
25374
|
-
import
|
|
26726
|
+
import os14 from "node:os";
|
|
26727
|
+
import path23 from "node:path";
|
|
25375
26728
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
25376
26729
|
var PersistentGrantStore = class {
|
|
25377
26730
|
filePath;
|
|
@@ -25402,22 +26755,22 @@ var PersistentGrantStore = class {
|
|
|
25402
26755
|
}
|
|
25403
26756
|
async #read() {
|
|
25404
26757
|
if (!fs8.existsSync(this.filePath)) return { version: 1, grants: [] };
|
|
25405
|
-
const data = JSON.parse(await
|
|
26758
|
+
const data = JSON.parse(await readFile12(this.filePath, "utf8"));
|
|
25406
26759
|
return {
|
|
25407
26760
|
version: 1,
|
|
25408
26761
|
grants: Array.isArray(data.grants) ? data.grants.filter(isGrantRecord) : []
|
|
25409
26762
|
};
|
|
25410
26763
|
}
|
|
25411
26764
|
async #write(file) {
|
|
25412
|
-
await
|
|
25413
|
-
await
|
|
26765
|
+
await mkdir9(path23.dirname(this.filePath), { recursive: true });
|
|
26766
|
+
await writeFile9(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
25414
26767
|
`, { mode: 384 });
|
|
25415
26768
|
}
|
|
25416
26769
|
};
|
|
25417
26770
|
function resolveGrantPath(cwd, config) {
|
|
25418
|
-
if (config.path) return
|
|
25419
|
-
if ((config.scope ?? "project") === "user") return
|
|
25420
|
-
return
|
|
26771
|
+
if (config.path) return path23.resolve(cwd, config.path);
|
|
26772
|
+
if ((config.scope ?? "project") === "user") return path23.join(os14.homedir(), ".demian", "grants.json");
|
|
26773
|
+
return path23.join(cwd, ".demian", "grants.json");
|
|
25421
26774
|
}
|
|
25422
26775
|
function isGrantRecord(value) {
|
|
25423
26776
|
if (!value || typeof value !== "object") return false;
|
|
@@ -25850,25 +27203,25 @@ function fail(content, metadata) {
|
|
|
25850
27203
|
}
|
|
25851
27204
|
|
|
25852
27205
|
// src/tools/output.ts
|
|
25853
|
-
import { mkdir as
|
|
25854
|
-
import
|
|
27206
|
+
import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
|
|
27207
|
+
import path24 from "node:path";
|
|
25855
27208
|
var DEFAULT_CAP_BYTES = 32 * 1024;
|
|
25856
27209
|
var HALF_PREVIEW_BYTES = 16 * 1024;
|
|
25857
27210
|
async function capToolOutput(result, options) {
|
|
25858
27211
|
const capBytes = options.capBytes ?? DEFAULT_CAP_BYTES;
|
|
25859
27212
|
const bytes = Buffer.byteLength(result.content, "utf8");
|
|
25860
27213
|
if (bytes <= capBytes) return result;
|
|
25861
|
-
const dir =
|
|
25862
|
-
await
|
|
25863
|
-
const outputPath =
|
|
25864
|
-
await
|
|
27214
|
+
const dir = path24.join(options.cwd, ".demian", "tmp");
|
|
27215
|
+
await mkdir10(dir, { recursive: true });
|
|
27216
|
+
const outputPath = path24.join(dir, `output-${safeCallId(options.callId)}.txt`);
|
|
27217
|
+
await writeFile10(outputPath, result.content, "utf8");
|
|
25865
27218
|
return {
|
|
25866
27219
|
...result,
|
|
25867
27220
|
content: [
|
|
25868
27221
|
sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
|
|
25869
27222
|
`
|
|
25870
27223
|
|
|
25871
|
-
[Full output saved to ${
|
|
27224
|
+
[Full output saved to ${path24.relative(options.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
|
|
25872
27225
|
|
|
25873
27226
|
`,
|
|
25874
27227
|
sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
|
|
@@ -25893,8 +27246,8 @@ function sliceUtf8(text, startByte, endByte) {
|
|
|
25893
27246
|
}
|
|
25894
27247
|
|
|
25895
27248
|
// src/tools/read-file.ts
|
|
25896
|
-
import { open as open2, stat as
|
|
25897
|
-
import
|
|
27249
|
+
import { open as open2, stat as stat7 } from "node:fs/promises";
|
|
27250
|
+
import path25 from "node:path";
|
|
25898
27251
|
|
|
25899
27252
|
// src/tools/validation.ts
|
|
25900
27253
|
function assertObject(input2, toolName) {
|
|
@@ -25964,7 +27317,7 @@ var readFileTool = {
|
|
|
25964
27317
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
25965
27318
|
const offset = positiveInteger(optionalNumberField(object2, "offset"), 1, "offset");
|
|
25966
27319
|
const limit = positiveInteger(optionalNumberField(object2, "limit"), DEFAULT_LIMIT, "limit");
|
|
25967
|
-
const info = await
|
|
27320
|
+
const info = await stat7(filePath);
|
|
25968
27321
|
if (info.isDirectory()) throw new Error(`read_file expected a file but got a directory: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
25969
27322
|
if (info.size > MAX_BYTES) throw new Error(`File is larger than ${MAX_BYTES} bytes: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
25970
27323
|
const sample = await readBytes(filePath, Math.min(SAMPLE_BYTES, info.size));
|
|
@@ -25978,7 +27331,7 @@ var readFileTool = {
|
|
|
25978
27331
|
const truncated = start + sliced.length < lines.length;
|
|
25979
27332
|
return ok(content + (truncated ? `
|
|
25980
27333
|
... (${lines.length - start - sliced.length} more lines)` : ""), {
|
|
25981
|
-
path:
|
|
27334
|
+
path: path25.relative(ctx.cwd, filePath),
|
|
25982
27335
|
lines: sliced.length,
|
|
25983
27336
|
totalLines: lines.length,
|
|
25984
27337
|
truncated
|
|
@@ -26016,8 +27369,8 @@ function looksBinary(buffer) {
|
|
|
26016
27369
|
}
|
|
26017
27370
|
|
|
26018
27371
|
// src/tools/write-file.ts
|
|
26019
|
-
import { mkdir as
|
|
26020
|
-
import
|
|
27372
|
+
import { mkdir as mkdir11, readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
|
|
27373
|
+
import path26 from "node:path";
|
|
26021
27374
|
var writeFileTool = {
|
|
26022
27375
|
name: "write_file",
|
|
26023
27376
|
description: "Create or replace a text file inside the workspace.",
|
|
@@ -26035,8 +27388,8 @@ var writeFileTool = {
|
|
|
26035
27388
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
26036
27389
|
const content = stringField(object2, "content", { allowEmpty: true });
|
|
26037
27390
|
const before = await readOptionalTextFile(filePath);
|
|
26038
|
-
await
|
|
26039
|
-
await
|
|
27391
|
+
await mkdir11(path26.dirname(filePath), { recursive: true });
|
|
27392
|
+
await writeFile11(filePath, content, "utf8");
|
|
26040
27393
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
26041
27394
|
const diff = createTextDiff(before, content, relative);
|
|
26042
27395
|
return ok(`Wrote ${relativeToCwd(ctx.cwd, filePath)} (${Buffer.byteLength(content, "utf8")} bytes).`, {
|
|
@@ -26049,7 +27402,7 @@ var writeFileTool = {
|
|
|
26049
27402
|
};
|
|
26050
27403
|
async function readOptionalTextFile(filePath) {
|
|
26051
27404
|
try {
|
|
26052
|
-
return await
|
|
27405
|
+
return await readFile13(filePath, "utf8");
|
|
26053
27406
|
} catch (error) {
|
|
26054
27407
|
if (isNotFound(error)) return void 0;
|
|
26055
27408
|
throw error;
|
|
@@ -26060,7 +27413,7 @@ function isNotFound(error) {
|
|
|
26060
27413
|
}
|
|
26061
27414
|
|
|
26062
27415
|
// src/tools/edit-file.ts
|
|
26063
|
-
import { readFile as
|
|
27416
|
+
import { readFile as readFile14, writeFile as writeFile12 } from "node:fs/promises";
|
|
26064
27417
|
var editFileTool = {
|
|
26065
27418
|
name: "edit_file",
|
|
26066
27419
|
description: "Replace an exact string in a text file inside the workspace.",
|
|
@@ -26082,12 +27435,12 @@ var editFileTool = {
|
|
|
26082
27435
|
const newString = stringField(object2, "newString", { allowEmpty: true });
|
|
26083
27436
|
const replaceAll = optionalBooleanField(object2, "replaceAll") ?? false;
|
|
26084
27437
|
if (oldString === newString) throw new Error("oldString and newString must be different");
|
|
26085
|
-
const before = await
|
|
27438
|
+
const before = await readFile14(filePath, "utf8");
|
|
26086
27439
|
const matches = countMatches(before, oldString);
|
|
26087
27440
|
if (matches === 0) throw new Error("oldString was not found");
|
|
26088
27441
|
if (matches > 1 && !replaceAll) throw new Error(`oldString matched ${matches} times; set replaceAll=true to replace all matches`);
|
|
26089
27442
|
const after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
|
|
26090
|
-
await
|
|
27443
|
+
await writeFile12(filePath, after, "utf8");
|
|
26091
27444
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
26092
27445
|
const diff = createTextDiff(before, after, relative);
|
|
26093
27446
|
return ok(`Edited ${relative} (${replaceAll ? matches : 1} replacement${matches === 1 ? "" : "s"}).`, {
|
|
@@ -26110,8 +27463,8 @@ function countMatches(text, needle) {
|
|
|
26110
27463
|
}
|
|
26111
27464
|
|
|
26112
27465
|
// src/tools/bash.ts
|
|
26113
|
-
import { spawn as
|
|
26114
|
-
import
|
|
27466
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
27467
|
+
import path28 from "node:path";
|
|
26115
27468
|
|
|
26116
27469
|
// src/sandbox/env-only.ts
|
|
26117
27470
|
function buildEnvOnlyLaunch(command, config) {
|
|
@@ -26171,7 +27524,7 @@ function bwrapPath() {
|
|
|
26171
27524
|
|
|
26172
27525
|
// src/sandbox/macos.ts
|
|
26173
27526
|
import { existsSync as existsSync4 } from "node:fs";
|
|
26174
|
-
import
|
|
27527
|
+
import path27 from "node:path";
|
|
26175
27528
|
function canUseMacOSSandbox() {
|
|
26176
27529
|
return process.platform === "darwin" && existsSync4("/usr/bin/sandbox-exec");
|
|
26177
27530
|
}
|
|
@@ -26197,7 +27550,7 @@ function macosProfile(cwd, config) {
|
|
|
26197
27550
|
}
|
|
26198
27551
|
if (mode === "workspace-write") {
|
|
26199
27552
|
lines.push("(deny file-write*)");
|
|
26200
|
-
lines.push(`(allow file-write* (subpath "${escapeProfilePath(
|
|
27553
|
+
lines.push(`(allow file-write* (subpath "${escapeProfilePath(path27.resolve(cwd))}"))`);
|
|
26201
27554
|
lines.push('(allow file-write* (subpath "/tmp"))');
|
|
26202
27555
|
lines.push('(allow file-write* (subpath "/private/tmp"))');
|
|
26203
27556
|
lines.push('(allow file-write* (subpath "/private/var/folders"))');
|
|
@@ -26259,7 +27612,7 @@ ${result.stderr}` : ""
|
|
|
26259
27612
|
].filter(Boolean).join("\n");
|
|
26260
27613
|
return ok(content, {
|
|
26261
27614
|
command,
|
|
26262
|
-
workdir:
|
|
27615
|
+
workdir: path28.relative(ctx.cwd, workdir) || ".",
|
|
26263
27616
|
exitCode: result.exitCode,
|
|
26264
27617
|
signal: result.signal,
|
|
26265
27618
|
timedOut: result.timedOut,
|
|
@@ -26270,7 +27623,7 @@ ${result.stderr}` : ""
|
|
|
26270
27623
|
function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspace-write" }) {
|
|
26271
27624
|
return new Promise((resolve, reject) => {
|
|
26272
27625
|
const launch = buildSandboxLaunch(command, cwd, sandbox);
|
|
26273
|
-
const child =
|
|
27626
|
+
const child = spawn6(launch.command, launch.args, {
|
|
26274
27627
|
cwd,
|
|
26275
27628
|
env: { ...process.env, ...launch.env },
|
|
26276
27629
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -26310,9 +27663,9 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
|
|
|
26310
27663
|
}
|
|
26311
27664
|
|
|
26312
27665
|
// src/tools/grep.ts
|
|
26313
|
-
import { spawn as
|
|
26314
|
-
import { readdir as readdir2, readFile as
|
|
26315
|
-
import
|
|
27666
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
27667
|
+
import { readdir as readdir2, readFile as readFile15, stat as stat8 } from "node:fs/promises";
|
|
27668
|
+
import path29 from "node:path";
|
|
26316
27669
|
var MAX_MATCHES = 200;
|
|
26317
27670
|
var grepTool = {
|
|
26318
27671
|
name: "grep",
|
|
@@ -26362,7 +27715,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
26362
27715
|
];
|
|
26363
27716
|
if (glob) args.push("--glob", glob);
|
|
26364
27717
|
args.push(pattern, base);
|
|
26365
|
-
const child =
|
|
27718
|
+
const child = spawn7("rg", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
26366
27719
|
const stdout = [];
|
|
26367
27720
|
const stderr = [];
|
|
26368
27721
|
const abort = () => child.kill("SIGTERM");
|
|
@@ -26387,7 +27740,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
26387
27740
|
}
|
|
26388
27741
|
function rewriteRgPath(cwd, line) {
|
|
26389
27742
|
const [file, rest] = splitFirst(line, ":");
|
|
26390
|
-
if (!
|
|
27743
|
+
if (!path29.isAbsolute(file)) return line;
|
|
26391
27744
|
return `${relativeToCwd(cwd, file)}:${rest}`;
|
|
26392
27745
|
}
|
|
26393
27746
|
function splitFirst(value, delimiter) {
|
|
@@ -26402,13 +27755,13 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
26402
27755
|
if (out.length > MAX_MATCHES) return;
|
|
26403
27756
|
const relative = relativeToCwd(cwd, item);
|
|
26404
27757
|
if (isIgnoredPath(relative)) return;
|
|
26405
|
-
const info = await
|
|
27758
|
+
const info = await stat8(item);
|
|
26406
27759
|
if (info.isDirectory()) {
|
|
26407
|
-
for (const entry of await readdir2(item)) await walk2(
|
|
27760
|
+
for (const entry of await readdir2(item)) await walk2(path29.join(item, entry));
|
|
26408
27761
|
return;
|
|
26409
27762
|
}
|
|
26410
27763
|
if (glob && !matchGlob(glob, relative)) return;
|
|
26411
|
-
const buffer = await
|
|
27764
|
+
const buffer = await readFile15(item);
|
|
26412
27765
|
if (buffer.includes(0)) return;
|
|
26413
27766
|
const lines = buffer.toString("utf8").split(/\r?\n/);
|
|
26414
27767
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -26422,8 +27775,8 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
26422
27775
|
}
|
|
26423
27776
|
|
|
26424
27777
|
// src/tools/glob.ts
|
|
26425
|
-
import { readdir as readdir3, stat as
|
|
26426
|
-
import
|
|
27778
|
+
import { readdir as readdir3, stat as stat9 } from "node:fs/promises";
|
|
27779
|
+
import path30 from "node:path";
|
|
26427
27780
|
var MAX_PATHS = 1e3;
|
|
26428
27781
|
var globTool = {
|
|
26429
27782
|
name: "glob",
|
|
@@ -26455,10 +27808,10 @@ async function collectPaths(cwd, root, pattern) {
|
|
|
26455
27808
|
async function walk2(dir) {
|
|
26456
27809
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
26457
27810
|
for (const entry of entries) {
|
|
26458
|
-
const absolute =
|
|
27811
|
+
const absolute = path30.join(dir, entry.name);
|
|
26459
27812
|
const relative = relativeToCwd(cwd, absolute);
|
|
26460
27813
|
if (isIgnoredPath(relative)) continue;
|
|
26461
|
-
const info = await
|
|
27814
|
+
const info = await stat9(absolute);
|
|
26462
27815
|
if (matchGlob(pattern, relative)) out.push({ relative, mtimeMs: info.mtimeMs });
|
|
26463
27816
|
if (entry.isDirectory()) await walk2(absolute);
|
|
26464
27817
|
}
|
|
@@ -26596,7 +27949,7 @@ async function runBraveSearch(config, options) {
|
|
|
26596
27949
|
if (config.country) url.searchParams.set("country", config.country);
|
|
26597
27950
|
if (config.searchLang) url.searchParams.set("search_lang", config.searchLang);
|
|
26598
27951
|
if (config.safeSearch) url.searchParams.set("safesearch", config.safeSearch);
|
|
26599
|
-
const json = await
|
|
27952
|
+
const json = await fetchJson2(url, {
|
|
26600
27953
|
method: "GET",
|
|
26601
27954
|
headers: {
|
|
26602
27955
|
accept: "application/json",
|
|
@@ -26615,7 +27968,7 @@ async function runTavilySearch(config, options) {
|
|
|
26615
27968
|
if (!apiKey.ok) return apiKey;
|
|
26616
27969
|
const maxResults = Math.min(positiveInteger(options.maxResults, config.maxResults ?? 5, "maxResults"), TAVILY_MAX_RESULTS);
|
|
26617
27970
|
const endpoint = config.endpoint ?? defaultConfig.webSearch.providers.tavily.endpoint ?? "https://api.tavily.com/search";
|
|
26618
|
-
const json = await
|
|
27971
|
+
const json = await fetchJson2(endpoint, {
|
|
26619
27972
|
method: "POST",
|
|
26620
27973
|
headers: {
|
|
26621
27974
|
"content-type": "application/json",
|
|
@@ -26641,7 +27994,7 @@ async function runExaSearch(config, options) {
|
|
|
26641
27994
|
if (!apiKey.ok) return apiKey;
|
|
26642
27995
|
const numResults = Math.min(positiveInteger(options.maxResults, config.numResults ?? 5, "maxResults"), EXA_MAX_RESULTS);
|
|
26643
27996
|
const endpoint = config.endpoint ?? defaultConfig.webSearch.providers.exa.endpoint ?? "https://api.exa.ai/search";
|
|
26644
|
-
const json = await
|
|
27997
|
+
const json = await fetchJson2(endpoint, {
|
|
26645
27998
|
method: "POST",
|
|
26646
27999
|
headers: {
|
|
26647
28000
|
"content-type": "application/json",
|
|
@@ -26670,7 +28023,7 @@ function resolveApiKey(provider, config) {
|
|
|
26670
28023
|
if (value) return { ok: true, value };
|
|
26671
28024
|
return toolFail(`Missing API key for web_search provider "${provider}". Set webSearch.providers.${provider}.apiKey or set ${config.apiKeyEnv ?? "the configured apiKeyEnv"}.`);
|
|
26672
28025
|
}
|
|
26673
|
-
async function
|
|
28026
|
+
async function fetchJson2(url, options) {
|
|
26674
28027
|
const { fetcher, provider, ...init } = options;
|
|
26675
28028
|
const response = await fetcher(url, init);
|
|
26676
28029
|
const text = await response.text();
|
|
@@ -26688,10 +28041,10 @@ function normalizeBraveResults(raw, limit) {
|
|
|
26688
28041
|
return (data.web?.results ?? []).slice(0, limit).map((item) => {
|
|
26689
28042
|
const result = item;
|
|
26690
28043
|
return {
|
|
26691
|
-
title:
|
|
26692
|
-
url:
|
|
26693
|
-
snippet: cleanSnippet(
|
|
26694
|
-
publishedDate:
|
|
28044
|
+
title: stringValue5(result.title) ?? "Untitled",
|
|
28045
|
+
url: stringValue5(result.url) ?? "",
|
|
28046
|
+
snippet: cleanSnippet(stringValue5(result.description) ?? stringValue5(result.snippet)),
|
|
28047
|
+
publishedDate: stringValue5(result.age)
|
|
26695
28048
|
};
|
|
26696
28049
|
}).filter((item) => item.url.length > 0);
|
|
26697
28050
|
}
|
|
@@ -26700,9 +28053,9 @@ function normalizeTavilyResults(raw, limit) {
|
|
|
26700
28053
|
return (data.results ?? []).slice(0, limit).map((item) => {
|
|
26701
28054
|
const result = item;
|
|
26702
28055
|
return {
|
|
26703
|
-
title:
|
|
26704
|
-
url:
|
|
26705
|
-
snippet: cleanSnippet(
|
|
28056
|
+
title: stringValue5(result.title) ?? "Untitled",
|
|
28057
|
+
url: stringValue5(result.url) ?? "",
|
|
28058
|
+
snippet: cleanSnippet(stringValue5(result.content) ?? stringValue5(result.raw_content)),
|
|
26706
28059
|
score: numberValue3(result.score)
|
|
26707
28060
|
};
|
|
26708
28061
|
}).filter((item) => item.url.length > 0);
|
|
@@ -26712,10 +28065,10 @@ function normalizeExaResults(raw, limit) {
|
|
|
26712
28065
|
return (data.results ?? []).slice(0, limit).map((item) => {
|
|
26713
28066
|
const result = item;
|
|
26714
28067
|
return {
|
|
26715
|
-
title:
|
|
26716
|
-
url:
|
|
28068
|
+
title: stringValue5(result.title) ?? "Untitled",
|
|
28069
|
+
url: stringValue5(result.url) ?? "",
|
|
26717
28070
|
snippet: exaSnippet(result),
|
|
26718
|
-
publishedDate:
|
|
28071
|
+
publishedDate: stringValue5(result.publishedDate),
|
|
26719
28072
|
score: numberValue3(result.score)
|
|
26720
28073
|
};
|
|
26721
28074
|
}).filter((item) => item.url.length > 0);
|
|
@@ -26726,7 +28079,7 @@ function exaSnippet(result) {
|
|
|
26726
28079
|
const joined = highlights.map((item) => typeof item === "string" ? item : "").filter(Boolean).join(" ");
|
|
26727
28080
|
if (joined) return cleanSnippet(joined);
|
|
26728
28081
|
}
|
|
26729
|
-
return cleanSnippet(
|
|
28082
|
+
return cleanSnippet(stringValue5(result.text) ?? stringValue5(result.summary));
|
|
26730
28083
|
}
|
|
26731
28084
|
function searchResult(provider, query, results, raw) {
|
|
26732
28085
|
const content = [
|
|
@@ -26750,7 +28103,7 @@ function formatResult(index, result) {
|
|
|
26750
28103
|
if (result.snippet) lines.push(` Snippet: ${result.snippet}`);
|
|
26751
28104
|
return lines.join("\n");
|
|
26752
28105
|
}
|
|
26753
|
-
function
|
|
28106
|
+
function stringValue5(value) {
|
|
26754
28107
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
26755
28108
|
}
|
|
26756
28109
|
function numberValue3(value) {
|
|
@@ -27457,14 +28810,14 @@ async function runExecutionSession(options) {
|
|
|
27457
28810
|
}
|
|
27458
28811
|
|
|
27459
28812
|
// src/goals/lock.ts
|
|
27460
|
-
import { mkdir as
|
|
27461
|
-
import
|
|
28813
|
+
import { mkdir as mkdir13, open as open3, rm as rm2 } from "node:fs/promises";
|
|
28814
|
+
import path32 from "node:path";
|
|
27462
28815
|
|
|
27463
28816
|
// src/goals/storage.ts
|
|
27464
28817
|
import { createHash as createHash3 } from "node:crypto";
|
|
27465
|
-
import { mkdir as
|
|
28818
|
+
import { mkdir as mkdir12, readFile as readFile16, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
|
|
27466
28819
|
import fs9 from "node:fs";
|
|
27467
|
-
import
|
|
28820
|
+
import path31 from "node:path";
|
|
27468
28821
|
var GoalStore = class {
|
|
27469
28822
|
cwd;
|
|
27470
28823
|
dir;
|
|
@@ -27475,12 +28828,12 @@ var GoalStore = class {
|
|
|
27475
28828
|
this.cwd = cwd;
|
|
27476
28829
|
this.scope = normalizeGoalStoreScope(options.scope);
|
|
27477
28830
|
this.dir = goalStoreDir(cwd, this.scope);
|
|
27478
|
-
this.activePath =
|
|
27479
|
-
this.archiveDir =
|
|
28831
|
+
this.activePath = path31.join(this.dir, "active.json");
|
|
28832
|
+
this.archiveDir = path31.join(this.dir, "archive");
|
|
27480
28833
|
}
|
|
27481
28834
|
async loadActive() {
|
|
27482
28835
|
if (!fs9.existsSync(this.activePath)) return void 0;
|
|
27483
|
-
const text = await
|
|
28836
|
+
const text = await readFile16(this.activePath, "utf8");
|
|
27484
28837
|
try {
|
|
27485
28838
|
return JSON.parse(text);
|
|
27486
28839
|
} catch {
|
|
@@ -27489,36 +28842,36 @@ var GoalStore = class {
|
|
|
27489
28842
|
}
|
|
27490
28843
|
}
|
|
27491
28844
|
async saveActive(state) {
|
|
27492
|
-
await
|
|
27493
|
-
const tmp =
|
|
27494
|
-
await
|
|
28845
|
+
await mkdir12(this.dir, { recursive: true });
|
|
28846
|
+
const tmp = path31.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
|
|
28847
|
+
await writeFile13(tmp, `${JSON.stringify(state, null, 2)}
|
|
27495
28848
|
`, "utf8");
|
|
27496
|
-
await
|
|
28849
|
+
await rename6(tmp, this.activePath);
|
|
27497
28850
|
}
|
|
27498
28851
|
async archiveActive(status = "cleared") {
|
|
27499
28852
|
const state = await this.loadActive();
|
|
27500
28853
|
if (!state) return void 0;
|
|
27501
|
-
await
|
|
28854
|
+
await mkdir12(this.archiveDir, { recursive: true });
|
|
27502
28855
|
const archived = {
|
|
27503
28856
|
...state,
|
|
27504
28857
|
status,
|
|
27505
28858
|
updatedAt: Date.now()
|
|
27506
28859
|
};
|
|
27507
|
-
await
|
|
28860
|
+
await writeFile13(path31.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
|
|
27508
28861
|
`, "utf8");
|
|
27509
28862
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
27510
28863
|
return archived;
|
|
27511
28864
|
}
|
|
27512
28865
|
async backupCorrupted(text) {
|
|
27513
|
-
await
|
|
27514
|
-
const backup =
|
|
27515
|
-
await
|
|
28866
|
+
await mkdir12(this.dir, { recursive: true });
|
|
28867
|
+
const backup = path31.join(this.dir, `active.corrupt.${Date.now()}.json`);
|
|
28868
|
+
await writeFile13(backup, text, "utf8");
|
|
27516
28869
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
27517
28870
|
}
|
|
27518
28871
|
};
|
|
27519
28872
|
function goalStoreDir(cwd, scope) {
|
|
27520
28873
|
const safeScope = normalizeGoalStoreScope(scope);
|
|
27521
|
-
return safeScope ?
|
|
28874
|
+
return safeScope ? path31.join(cwd, ".demian", "goals", "sessions", safeScope) : path31.join(cwd, ".demian", "goals");
|
|
27522
28875
|
}
|
|
27523
28876
|
function normalizeGoalStoreScope(scope) {
|
|
27524
28877
|
const trimmed = scope?.trim();
|
|
@@ -27532,10 +28885,10 @@ function normalizeGoalStoreScope(scope) {
|
|
|
27532
28885
|
var GoalLock = class {
|
|
27533
28886
|
path;
|
|
27534
28887
|
constructor(cwd, scope) {
|
|
27535
|
-
this.path =
|
|
28888
|
+
this.path = path32.join(goalStoreDir(cwd, scope), "active.lock");
|
|
27536
28889
|
}
|
|
27537
28890
|
async acquire() {
|
|
27538
|
-
await
|
|
28891
|
+
await mkdir13(path32.dirname(this.path), { recursive: true });
|
|
27539
28892
|
const handle = await open3(this.path, "wx").catch((error) => {
|
|
27540
28893
|
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
27541
28894
|
if (code === "EEXIST") throw new Error("Another goal is already active in this workspace.");
|
|
@@ -28065,7 +29418,7 @@ function readString(input2, key) {
|
|
|
28065
29418
|
}
|
|
28066
29419
|
|
|
28067
29420
|
// src/external-runtime/session-map.ts
|
|
28068
|
-
import
|
|
29421
|
+
import crypto6 from "node:crypto";
|
|
28069
29422
|
var ClaudeCodeSessionMap = class {
|
|
28070
29423
|
#sessions = /* @__PURE__ */ new Map();
|
|
28071
29424
|
get(key) {
|
|
@@ -28118,13 +29471,13 @@ function normalizeInstruction(value) {
|
|
|
28118
29471
|
return value.replace(/\r\n/g, "\n").trim().split("\n").map((line) => line.replace(/[ \t]+$/g, "")).join("\n");
|
|
28119
29472
|
}
|
|
28120
29473
|
function sha256(value) {
|
|
28121
|
-
return
|
|
29474
|
+
return crypto6.createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
28122
29475
|
}
|
|
28123
29476
|
|
|
28124
29477
|
// src/root-session.ts
|
|
28125
29478
|
import { cp, mkdtemp, rm as rm3 } from "node:fs/promises";
|
|
28126
|
-
import
|
|
28127
|
-
import
|
|
29479
|
+
import os15 from "node:os";
|
|
29480
|
+
import path33 from "node:path";
|
|
28128
29481
|
|
|
28129
29482
|
// src/permissions/presets.ts
|
|
28130
29483
|
var PERMISSION_PRESETS = ["deny", "ask", "auto", "full"];
|
|
@@ -28968,8 +30321,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
28968
30321
|
}
|
|
28969
30322
|
}
|
|
28970
30323
|
async createCoworkIsolatedWorkspace(groupId, memberId) {
|
|
28971
|
-
const root = await mkdtemp(
|
|
28972
|
-
const cwd =
|
|
30324
|
+
const root = await mkdtemp(path33.join(os15.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
|
|
30325
|
+
const cwd = path33.join(root, "workspace");
|
|
28973
30326
|
await cp(this.#options.cwd, cwd, {
|
|
28974
30327
|
recursive: true,
|
|
28975
30328
|
filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
|
|
@@ -29117,7 +30470,7 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
29117
30470
|
const denyTools = ["write_file", "edit_file", "bash", "claudecode.Edit", "claudecode.Write", "claudecode.MultiEdit", "claudecode.Bash"];
|
|
29118
30471
|
return this.withPermissionPreset({
|
|
29119
30472
|
...agent,
|
|
29120
|
-
provider: agent.provider?.profile === "claudecode-
|
|
30473
|
+
provider: agent.provider?.profile === "claudecode" && agent.name === "claudecode-explorer" ? { ...agent.provider, permissionProfile: "explore" } : agent.provider,
|
|
29121
30474
|
tools: agent.tools.filter((tool) => readTools.has(tool)),
|
|
29122
30475
|
permissions: [
|
|
29123
30476
|
...agent.permissions.filter((rule) => !denyTools.includes(rule.tool)),
|
|
@@ -29164,13 +30517,14 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
29164
30517
|
return lines.join("\n");
|
|
29165
30518
|
}
|
|
29166
30519
|
resolveInvocationBackend(profileName, modelOverride, agent) {
|
|
29167
|
-
const
|
|
30520
|
+
const runtime = resolveProviderRuntimeConfig(this.#options.config, { providerName: profileName, model: modelOverride });
|
|
30521
|
+
const providerConfig = runtime.providerConfig;
|
|
29168
30522
|
if (this.#options.resolveExecutionBackend) return this.#options.resolveExecutionBackend(profileName, providerConfig, modelOverride);
|
|
29169
30523
|
if (this.#options.resolveProvider) {
|
|
29170
|
-
const resolved = this.#options.resolveProvider(profileName, providerConfig,
|
|
30524
|
+
const resolved = this.#options.resolveProvider(profileName, providerConfig, runtime.model);
|
|
29171
30525
|
return { kind: "provider", ...resolved };
|
|
29172
30526
|
}
|
|
29173
|
-
return resolveExecutionBackend(providerConfig, { model:
|
|
30527
|
+
return resolveExecutionBackend(providerConfig, { model: runtime.model, config: this.#options.config, agent });
|
|
29174
30528
|
}
|
|
29175
30529
|
loadAgentSession(agent, providerProfile, model, sessionScope, cwd = this.#options.cwd) {
|
|
29176
30530
|
const key = `${this.id}:${sessionScope ?? "delegate"}:${agent.name}:${providerProfile}:${model}:${cwd}`;
|
|
@@ -29272,17 +30626,17 @@ function findDependencyCycle(group) {
|
|
|
29272
30626
|
const byId = new Map(group.map((member) => [member.memberId, member]));
|
|
29273
30627
|
const visiting = /* @__PURE__ */ new Set();
|
|
29274
30628
|
const visited = /* @__PURE__ */ new Set();
|
|
29275
|
-
const
|
|
30629
|
+
const path36 = [];
|
|
29276
30630
|
const visit = (id) => {
|
|
29277
|
-
if (visiting.has(id)) return [...
|
|
30631
|
+
if (visiting.has(id)) return [...path36.slice(path36.indexOf(id)), id];
|
|
29278
30632
|
if (visited.has(id)) return void 0;
|
|
29279
30633
|
visiting.add(id);
|
|
29280
|
-
|
|
30634
|
+
path36.push(id);
|
|
29281
30635
|
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
29282
30636
|
const cycle = visit(dep);
|
|
29283
30637
|
if (cycle) return cycle;
|
|
29284
30638
|
}
|
|
29285
|
-
|
|
30639
|
+
path36.pop();
|
|
29286
30640
|
visiting.delete(id);
|
|
29287
30641
|
visited.add(id);
|
|
29288
30642
|
return void 0;
|
|
@@ -29323,7 +30677,7 @@ function scopeStaticPrefix(pattern) {
|
|
|
29323
30677
|
return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
|
|
29324
30678
|
}
|
|
29325
30679
|
function shouldCopyIntoCoworkWorkspace(root, source) {
|
|
29326
|
-
const relative =
|
|
30680
|
+
const relative = path33.relative(root, source).split(path33.sep).join("/");
|
|
29327
30681
|
if (!relative) return true;
|
|
29328
30682
|
const parts = relative.split("/");
|
|
29329
30683
|
return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
|
|
@@ -29718,52 +31072,114 @@ function finitePositiveInteger(value) {
|
|
|
29718
31072
|
}
|
|
29719
31073
|
|
|
29720
31074
|
// src/ui/plain/interactive.ts
|
|
29721
|
-
import
|
|
31075
|
+
import readline4 from "node:readline";
|
|
29722
31076
|
|
|
29723
31077
|
// src/ui/settings.ts
|
|
29724
|
-
function providerOptions(config) {
|
|
29725
|
-
|
|
29726
|
-
|
|
29727
|
-
|
|
29728
|
-
|
|
29729
|
-
|
|
29730
|
-
|
|
29731
|
-
|
|
29732
|
-
|
|
29733
|
-
|
|
31078
|
+
function providerOptions(config, catalogs = {}) {
|
|
31079
|
+
const orderedNames = sortProviderNames(Object.keys(config.providers));
|
|
31080
|
+
const order = new Map(orderedNames.map((name, index) => [name, index]));
|
|
31081
|
+
return orderedNames.map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden && !(provider.type === "claudecode" && provider.runtime === "cli")).map(([name, provider]) => {
|
|
31082
|
+
const catalog = catalogs[name];
|
|
31083
|
+
const available = providerCatalogAvailable(catalog);
|
|
31084
|
+
const profiles = catalog?.models.length ? catalog.models.map((model) => ({
|
|
31085
|
+
name: model.name,
|
|
31086
|
+
displayName: model.displayName ?? model.name,
|
|
31087
|
+
model: model.model,
|
|
31088
|
+
description: model.description,
|
|
31089
|
+
source: model.source,
|
|
31090
|
+
isDefault: model.isDefault
|
|
31091
|
+
})) : providerModelProfiles(name, provider);
|
|
31092
|
+
const modelProfiles = profiles.map((profile) => {
|
|
31093
|
+
const displayName = profile.displayName ?? profile.name ?? profile.model;
|
|
31094
|
+
return {
|
|
31095
|
+
...profile,
|
|
31096
|
+
label: available ? displayName : unavailableLabel(displayName),
|
|
31097
|
+
available
|
|
31098
|
+
};
|
|
31099
|
+
});
|
|
31100
|
+
const models = modelProfiles.length ? [...new Set(modelProfiles.map((item) => item.model).filter((item) => typeof item === "string" && item.trim()))] : providerModelOptions(name, provider);
|
|
31101
|
+
const defaultModel = defaultModelForProvider(name, provider);
|
|
31102
|
+
return {
|
|
31103
|
+
name,
|
|
31104
|
+
label: available ? name : unavailableLabel(name),
|
|
31105
|
+
model: defaultModel,
|
|
31106
|
+
modelLabel: available || !defaultModel ? defaultModel : unavailableLabel(defaultModel),
|
|
31107
|
+
models,
|
|
31108
|
+
modelProfiles,
|
|
31109
|
+
type: provider.type,
|
|
31110
|
+
runtime: provider.type === "claudecode" ? provider.runtime : void 0,
|
|
31111
|
+
permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
|
|
31112
|
+
ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
|
|
31113
|
+
available,
|
|
31114
|
+
catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
|
|
31115
|
+
};
|
|
31116
|
+
}).sort((left, right) => {
|
|
31117
|
+
if (left.available !== right.available) return left.available ? -1 : 1;
|
|
31118
|
+
return (order.get(left.name) ?? 0) - (order.get(right.name) ?? 0);
|
|
31119
|
+
});
|
|
29734
31120
|
}
|
|
31121
|
+
var MISSING_SELECTION_PLACEHOLDER = "-";
|
|
29735
31122
|
function createInteractiveModelSelection(config, flags = {}, saved) {
|
|
29736
31123
|
const savedProviderName = saved?.providerName === "claudecode-plan" ? "claudecode" : saved?.providerName;
|
|
29737
|
-
const
|
|
29738
|
-
|
|
29739
|
-
|
|
29740
|
-
|
|
29741
|
-
|
|
31124
|
+
const savedProviderExists = savedProviderName && config.providers[savedProviderName] && !config.providers[savedProviderName]?.hidden;
|
|
31125
|
+
if (saved?.providerName && !flags.provider && !savedProviderExists) {
|
|
31126
|
+
return {
|
|
31127
|
+
providerName: saved.providerName,
|
|
31128
|
+
providerSource: "saved",
|
|
31129
|
+
model: MISSING_SELECTION_PLACEHOLDER,
|
|
31130
|
+
modelSource: "saved"
|
|
31131
|
+
};
|
|
31132
|
+
}
|
|
31133
|
+
const providerName = flags.provider ?? (savedProviderExists ? savedProviderName : config.defaultProvider);
|
|
31134
|
+
let providerConfig;
|
|
31135
|
+
try {
|
|
31136
|
+
providerConfig = resolveProviderConfig(config, providerName);
|
|
31137
|
+
} catch {
|
|
31138
|
+
return {
|
|
31139
|
+
providerName,
|
|
31140
|
+
providerSource: flags.provider ? "flag" : "config",
|
|
31141
|
+
model: MISSING_SELECTION_PLACEHOLDER,
|
|
31142
|
+
modelSource: "config"
|
|
31143
|
+
};
|
|
31144
|
+
}
|
|
31145
|
+
const usesSavedProvider = !flags.provider && savedProviderExists && providerName === savedProviderName;
|
|
29742
31146
|
const savedModel = usesSavedProvider && saved?.model ? saved.model : void 0;
|
|
31147
|
+
const model = flags.model ?? savedModel ?? defaultModelForProvider(providerName, providerConfig);
|
|
29743
31148
|
return {
|
|
29744
31149
|
providerName,
|
|
29745
31150
|
providerSource: flags.provider ? "flag" : usesSavedProvider ? "saved" : "config",
|
|
29746
|
-
model
|
|
29747
|
-
modelSource: flags.model ? "flag" : savedModel ? "saved" : "config"
|
|
31151
|
+
model,
|
|
31152
|
+
modelSource: flags.model ? "flag" : savedModel ? "saved" : "config",
|
|
31153
|
+
modelProfileName: providerModelProfiles(providerName, providerConfig).find((profile) => profile.model === model || profile.displayName === model || profile.name === model)?.name
|
|
29748
31154
|
};
|
|
29749
31155
|
}
|
|
29750
31156
|
function selectProvider(config, selection, providerName) {
|
|
29751
31157
|
const providerConfig = resolveProviderConfig(config, providerName);
|
|
31158
|
+
const profiles = providerModelProfiles(providerName, providerConfig);
|
|
31159
|
+
const profile = profiles[0];
|
|
29752
31160
|
return {
|
|
29753
31161
|
...selection,
|
|
29754
31162
|
providerName,
|
|
29755
31163
|
providerSource: "interactive",
|
|
29756
|
-
model:
|
|
29757
|
-
modelSource: "config"
|
|
31164
|
+
model: profile?.model ?? defaultModelForProvider(providerName, providerConfig),
|
|
31165
|
+
modelSource: "config",
|
|
31166
|
+
modelProfileName: profile?.name
|
|
29758
31167
|
};
|
|
29759
31168
|
}
|
|
29760
31169
|
function selectModel(selection, model) {
|
|
29761
31170
|
return {
|
|
29762
31171
|
...selection,
|
|
29763
31172
|
model,
|
|
29764
|
-
modelSource: "interactive"
|
|
31173
|
+
modelSource: "interactive",
|
|
31174
|
+
modelProfileName: void 0
|
|
29765
31175
|
};
|
|
29766
31176
|
}
|
|
31177
|
+
function providerCatalogAvailable(catalog) {
|
|
31178
|
+
return catalog ? catalog.status === "ready" && catalog.models.length > 0 : true;
|
|
31179
|
+
}
|
|
31180
|
+
function unavailableLabel(value) {
|
|
31181
|
+
return `(n/a) ${value}`;
|
|
31182
|
+
}
|
|
29767
31183
|
|
|
29768
31184
|
// src/ui/plain/interactive.ts
|
|
29769
31185
|
var PlainInteractivePrompt = class {
|
|
@@ -29775,7 +31191,7 @@ var PlainInteractivePrompt = class {
|
|
|
29775
31191
|
#exitRequested = false;
|
|
29776
31192
|
constructor(input2 = process.stdin, output2 = process.stderr) {
|
|
29777
31193
|
this.#output = output2;
|
|
29778
|
-
this.#rl =
|
|
31194
|
+
this.#rl = readline4.createInterface({ input: input2, output: output2 });
|
|
29779
31195
|
this.#rl.on("line", (line) => this.#handleLine(line));
|
|
29780
31196
|
}
|
|
29781
31197
|
get exitRequested() {
|
|
@@ -29872,13 +31288,13 @@ async function askModel(promptUi, selection) {
|
|
|
29872
31288
|
}
|
|
29873
31289
|
|
|
29874
31290
|
// src/ui/preferences.ts
|
|
29875
|
-
import { mkdir as
|
|
31291
|
+
import { mkdir as mkdir14, readFile as readFile17, writeFile as writeFile14 } from "node:fs/promises";
|
|
29876
31292
|
import fs10 from "node:fs";
|
|
29877
|
-
import
|
|
31293
|
+
import path34 from "node:path";
|
|
29878
31294
|
var UiPreferenceStore = class {
|
|
29879
31295
|
filePath;
|
|
29880
31296
|
constructor(cwd, filePath) {
|
|
29881
|
-
this.filePath = filePath ?
|
|
31297
|
+
this.filePath = filePath ? path34.resolve(cwd, filePath) : path34.join(cwd, ".demian", "preferences.json");
|
|
29882
31298
|
}
|
|
29883
31299
|
async load() {
|
|
29884
31300
|
if (!fs10.existsSync(this.filePath)) return void 0;
|
|
@@ -29896,13 +31312,13 @@ var UiPreferenceStore = class {
|
|
|
29896
31312
|
model: selection.model,
|
|
29897
31313
|
updatedAt: Date.now()
|
|
29898
31314
|
};
|
|
29899
|
-
await
|
|
29900
|
-
await
|
|
31315
|
+
await mkdir14(path34.dirname(this.filePath), { recursive: true });
|
|
31316
|
+
await writeFile14(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
29901
31317
|
`, { mode: 384 });
|
|
29902
31318
|
}
|
|
29903
31319
|
async #read() {
|
|
29904
31320
|
try {
|
|
29905
|
-
return JSON.parse(await
|
|
31321
|
+
return JSON.parse(await readFile17(this.filePath, "utf8"));
|
|
29906
31322
|
} catch {
|
|
29907
31323
|
return void 0;
|
|
29908
31324
|
}
|
|
@@ -29918,6 +31334,8 @@ function preferenceKey(selection) {
|
|
|
29918
31334
|
|
|
29919
31335
|
// src/cli.ts
|
|
29920
31336
|
async function main(argv = process.argv.slice(2)) {
|
|
31337
|
+
const configCommandResult = await maybeRunConfigCommand(argv);
|
|
31338
|
+
if (configCommandResult !== void 0) return configCommandResult;
|
|
29921
31339
|
const doctorResult = await maybeRunDoctorCommand(argv);
|
|
29922
31340
|
if (doctorResult !== void 0) return doctorResult;
|
|
29923
31341
|
const flags = parseArgs(argv);
|
|
@@ -29925,7 +31343,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
29925
31343
|
printHelp();
|
|
29926
31344
|
return 0;
|
|
29927
31345
|
}
|
|
29928
|
-
|
|
31346
|
+
if (!flags.noWizard) await runFirstRunWizard().catch(() => void 0);
|
|
31347
|
+
const cwd = path35.resolve(flags.cwd ?? process.cwd());
|
|
29929
31348
|
const config = await loadConfig({ cwd, configPath: flags.configPath });
|
|
29930
31349
|
const agentName = flags.agent ?? config.defaultAgent;
|
|
29931
31350
|
const preferenceStore = new UiPreferenceStore(cwd);
|
|
@@ -30014,9 +31433,9 @@ async function runPlainTask(options) {
|
|
|
30014
31433
|
config: config.goals,
|
|
30015
31434
|
eventBus,
|
|
30016
31435
|
titleGenerator: async (input2) => {
|
|
30017
|
-
const
|
|
30018
|
-
const resolved = resolveExecutionBackend(
|
|
30019
|
-
model:
|
|
31436
|
+
const runtime2 = resolveProviderRuntimeConfig(config, selection);
|
|
31437
|
+
const resolved = resolveExecutionBackend(runtime2.providerConfig, {
|
|
31438
|
+
model: runtime2.model,
|
|
30020
31439
|
onRetry: (event) => eventBus.emit({
|
|
30021
31440
|
type: "provider.retry",
|
|
30022
31441
|
sessionId: "goal-title",
|
|
@@ -30106,10 +31525,10 @@ async function runPlainTask(options) {
|
|
|
30106
31525
|
`);
|
|
30107
31526
|
return { history: interactiveHistoryFromRunMessages(result2.messages), lastExternalSessionKey: options.lastExternalSessionKey };
|
|
30108
31527
|
}
|
|
30109
|
-
const providerConfig = resolveProviderConfig(config, providerName);
|
|
30110
31528
|
const runtimeAgent = activeGoal ? withGoalTools(agent) : agent;
|
|
30111
|
-
const
|
|
30112
|
-
|
|
31529
|
+
const runtime = resolveProviderRuntimeConfig(config, selection);
|
|
31530
|
+
const backend = resolveExecutionBackend(runtime.providerConfig, {
|
|
31531
|
+
model: runtime.model,
|
|
30113
31532
|
config,
|
|
30114
31533
|
agent: runtimeAgent,
|
|
30115
31534
|
onRetry: (event) => eventBus.emit({
|
|
@@ -30213,6 +31632,10 @@ function parseArgs(argv) {
|
|
|
30213
31632
|
out.transcript = false;
|
|
30214
31633
|
continue;
|
|
30215
31634
|
}
|
|
31635
|
+
if (arg === "--no-wizard") {
|
|
31636
|
+
out.noWizard = true;
|
|
31637
|
+
continue;
|
|
31638
|
+
}
|
|
30216
31639
|
if (arg === "--goal") {
|
|
30217
31640
|
out.goal = true;
|
|
30218
31641
|
continue;
|
|
@@ -30287,6 +31710,10 @@ function printHelp() {
|
|
|
30287
31710
|
|
|
30288
31711
|
Usage:
|
|
30289
31712
|
demian-plain [flags]
|
|
31713
|
+
demian-plain config init [--force]
|
|
31714
|
+
demian-plain config add-provider <preset|openai-compatible> [--name <name>]
|
|
31715
|
+
demian-plain config add-model <provider> --name <name> --model <model>
|
|
31716
|
+
demian-plain auth login <codex|claudecode>
|
|
30290
31717
|
demian-plain doctor policies --upgrade-namespaces [--write]
|
|
30291
31718
|
|
|
30292
31719
|
Default:
|
|
@@ -30305,7 +31732,7 @@ Flags:
|
|
|
30305
31732
|
--single-agent force the current single-agent runtime
|
|
30306
31733
|
--multi-agent enable root-owned delegate_agent runtime
|
|
30307
31734
|
--agent <name> general, build, plan, execute, or a configured custom agent
|
|
30308
|
-
--provider <name> openai,
|
|
31735
|
+
--provider <name> openai, anthropic, gemini, groq, azure, lmstudio, ollama-local, ollama-cloud, llamacpp, vllm, codex, claudecode
|
|
30309
31736
|
--model <name> override configured model
|
|
30310
31737
|
--max-turns <n> maximum model/tool loop turns
|
|
30311
31738
|
--cwd <path> workspace directory
|
|
@@ -30318,7 +31745,8 @@ Flags:
|
|
|
30318
31745
|
--persistent-grants persist "always" permission grants
|
|
30319
31746
|
--no-persistent-grants
|
|
30320
31747
|
keep "always" grants session-local
|
|
30321
|
-
--no-transcript disable
|
|
31748
|
+
--no-transcript disable ~/.demian transcript writes
|
|
31749
|
+
--no-wizard skip the first-run config wizard
|
|
30322
31750
|
--goal run the prompt as an explicit /goal objective
|
|
30323
31751
|
/ralph-loop --fresh-context is single-agent only
|
|
30324
31752
|
--config <path> load an additional config file
|
|
@@ -30327,6 +31755,13 @@ Flags:
|
|
|
30327
31755
|
Doctor:
|
|
30328
31756
|
doctor policies --upgrade-namespaces
|
|
30329
31757
|
rewrite prefix-less permission rules as demian.<tool>
|
|
31758
|
+
|
|
31759
|
+
Config:
|
|
31760
|
+
config init create ~/.demian/config.json
|
|
31761
|
+
config models <provider> [--refresh]
|
|
31762
|
+
list configured or remote provider models
|
|
31763
|
+
auth login codex|claudecode
|
|
31764
|
+
start the external OAuth login flow
|
|
30330
31765
|
`);
|
|
30331
31766
|
}
|
|
30332
31767
|
if (import.meta.url === `file://${process.argv[1]}`) {
|