@h1d3rone/claude-proxy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +338 -0
- package/config_example.toml +11 -0
- package/package.json +38 -0
- package/src/cli.js +458 -0
- package/src/config.js +494 -0
- package/src/proxy/converters.js +533 -0
- package/src/proxy/server.js +271 -0
- package/src/services/client-config-manager.js +302 -0
- package/src/services/host-manager.js +369 -0
- package/src/utils.js +224 -0
package/src/config.js
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
const fs = require("fs/promises");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const readline = require("readline/promises");
|
|
5
|
+
const toml = require("@iarna/toml");
|
|
6
|
+
|
|
7
|
+
const DEFAULT_CLIENT_API_KEY = "arbitrary value";
|
|
8
|
+
const DEFAULT_BIG_MODEL = "gpt-5.4";
|
|
9
|
+
const DEFAULT_MIDDLE_MODEL = "gpt-5.3-codex";
|
|
10
|
+
const DEFAULT_SMALL_MODEL = "gpt-5.2-codex";
|
|
11
|
+
const DEFAULT_CLAUDE_MODEL = "opus[1m]";
|
|
12
|
+
const LEGACY_TOP_LEVEL_FIELDS = ["hosts", "type", "name", "enabled", "host", "user", "sync_project"];
|
|
13
|
+
const CONFIG_SECTION_FIELDS = {
|
|
14
|
+
claude: ["server_port", "big_model", "middle_model", "small_model", "default_claude_model", "claude_dir"],
|
|
15
|
+
openai: ["base_url", "api_key", "codex_dir", "codex_provider"]
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function expandHome(inputPath) {
|
|
19
|
+
if (!inputPath || typeof inputPath !== "string") {
|
|
20
|
+
return inputPath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (inputPath === "~") {
|
|
24
|
+
return os.homedir();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (inputPath.startsWith("~/")) {
|
|
28
|
+
return path.join(os.homedir(), inputPath.slice(2));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return inputPath;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeLocalPath(inputPath, baseDir) {
|
|
35
|
+
const expanded = expandHome(inputPath);
|
|
36
|
+
if (!expanded) {
|
|
37
|
+
return expanded;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (path.isAbsolute(expanded)) {
|
|
41
|
+
return expanded;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return path.resolve(baseDir, expanded);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getDefaultConfigDir() {
|
|
48
|
+
return path.join(os.homedir(), ".claude-proxy");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getDefaultConfigPath() {
|
|
52
|
+
return path.join(getDefaultConfigDir(), "config.toml");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getDefaultConfigDisplayPath() {
|
|
56
|
+
return "<home>/.claude-proxy/config.toml";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createDefaultConfigDocument() {
|
|
60
|
+
return {
|
|
61
|
+
server_host: "127.0.0.1",
|
|
62
|
+
server_port: 8082,
|
|
63
|
+
base_url: "",
|
|
64
|
+
api_key: "",
|
|
65
|
+
big_model: DEFAULT_BIG_MODEL,
|
|
66
|
+
middle_model: DEFAULT_MIDDLE_MODEL,
|
|
67
|
+
small_model: DEFAULT_SMALL_MODEL,
|
|
68
|
+
default_claude_model: DEFAULT_CLAUDE_MODEL,
|
|
69
|
+
home_dir: "~",
|
|
70
|
+
claude_dir: "~/.claude",
|
|
71
|
+
codex_dir: "~/.codex"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isPlaceholderValue(value) {
|
|
76
|
+
if (value == null) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const normalized = String(value).trim();
|
|
81
|
+
if (!normalized) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const lowered = normalized.toLowerCase();
|
|
86
|
+
return (
|
|
87
|
+
lowered.startsWith("replace-with-") ||
|
|
88
|
+
lowered.startsWith("your-") ||
|
|
89
|
+
lowered === "changeme" ||
|
|
90
|
+
lowered === "todo"
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function applyHostRuntimePaths(host, runtime = {}) {
|
|
95
|
+
const pathApi = runtime.pathApi || path;
|
|
96
|
+
const projectRoot = runtime.projectRoot || host.project_root;
|
|
97
|
+
const claudeDir = runtime.claudeDir || host.claude_dir;
|
|
98
|
+
const codexDir = runtime.codexDir || host.codex_dir;
|
|
99
|
+
const configPath = runtime.configPath || host.config_path || pathApi.join(projectRoot, "config.toml");
|
|
100
|
+
const runtimeDir = pathApi.join(claudeDir, "claude-proxy");
|
|
101
|
+
const stateDir = pathApi.join(runtimeDir, "state");
|
|
102
|
+
const logsDir = pathApi.join(runtimeDir, "logs");
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
...host,
|
|
106
|
+
project_root: projectRoot,
|
|
107
|
+
claude_dir: claudeDir,
|
|
108
|
+
codex_dir: codexDir,
|
|
109
|
+
runtime_dir: runtimeDir,
|
|
110
|
+
state_dir: stateDir,
|
|
111
|
+
logs_dir: logsDir,
|
|
112
|
+
settings_path: pathApi.join(claudeDir, "settings.json"),
|
|
113
|
+
backups_dir: pathApi.join(runtimeDir, "backups"),
|
|
114
|
+
sessions_file: pathApi.join(stateDir, "sessions.txt"),
|
|
115
|
+
pid_file: pathApi.join(stateDir, "proxy.pid"),
|
|
116
|
+
managed_state_file: pathApi.join(stateDir, "managed-files.json"),
|
|
117
|
+
server_log_file: pathApi.join(logsDir, "server.log"),
|
|
118
|
+
codex_config_path: pathApi.join(codexDir, "config.toml"),
|
|
119
|
+
codex_auth_path: pathApi.join(codexDir, "auth.json"),
|
|
120
|
+
config_path: configPath
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function validateConfigDocument(document) {
|
|
125
|
+
const legacyFields = LEGACY_TOP_LEVEL_FIELDS.filter((key) =>
|
|
126
|
+
Object.prototype.hasOwnProperty.call(document, key)
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (legacyFields.length > 0) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
"Only the single-machine top-level config format is supported now. " +
|
|
132
|
+
`Remove legacy fields: ${legacyFields.join(", ")}.`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getPromptValue(document, key, fallback) {
|
|
138
|
+
const value = document[key];
|
|
139
|
+
if (value == null || value === "") {
|
|
140
|
+
return fallback;
|
|
141
|
+
}
|
|
142
|
+
return String(value);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function collectConfigPrompts(document = {}) {
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
target: "server_host",
|
|
149
|
+
question: "Server host",
|
|
150
|
+
defaultValue: getPromptValue(document, "server_host", "127.0.0.1"),
|
|
151
|
+
required: true
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
target: "server_port",
|
|
155
|
+
question: "Server port",
|
|
156
|
+
defaultValue: String(document.server_port || 8082),
|
|
157
|
+
required: true
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
target: "base_url",
|
|
161
|
+
question: "Upstream OpenAI-compatible base_url (provider root or explicit /v1)",
|
|
162
|
+
defaultValue: getPromptValue(document, "base_url", ""),
|
|
163
|
+
required: true
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
target: "api_key",
|
|
167
|
+
question: "Upstream API key",
|
|
168
|
+
defaultValue: getPromptValue(document, "api_key", ""),
|
|
169
|
+
required: true
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
target: "big_model",
|
|
173
|
+
question: "Large model mapping",
|
|
174
|
+
defaultValue: getPromptValue(document, "big_model", DEFAULT_BIG_MODEL),
|
|
175
|
+
required: true
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
target: "middle_model",
|
|
179
|
+
question: "Middle model mapping",
|
|
180
|
+
defaultValue: getPromptValue(document, "middle_model", DEFAULT_MIDDLE_MODEL),
|
|
181
|
+
required: true
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
target: "small_model",
|
|
185
|
+
question: "Small model mapping",
|
|
186
|
+
defaultValue: getPromptValue(document, "small_model", DEFAULT_SMALL_MODEL),
|
|
187
|
+
required: true
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
target: "default_claude_model",
|
|
191
|
+
question: "Default Claude model",
|
|
192
|
+
defaultValue: getPromptValue(document, "default_claude_model", DEFAULT_CLAUDE_MODEL),
|
|
193
|
+
required: true
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
target: "home_dir",
|
|
197
|
+
question: "Home directory",
|
|
198
|
+
defaultValue: getPromptValue(document, "home_dir", "~"),
|
|
199
|
+
required: true
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
target: "claude_dir",
|
|
203
|
+
question: "Claude config directory",
|
|
204
|
+
defaultValue: getPromptValue(document, "claude_dir", "~/.claude"),
|
|
205
|
+
required: true
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
target: "codex_dir",
|
|
209
|
+
question: "Codex config directory",
|
|
210
|
+
defaultValue: getPromptValue(document, "codex_dir", "~/.codex"),
|
|
211
|
+
required: true
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
target: "codex_provider",
|
|
215
|
+
question: "Codex provider name (optional)",
|
|
216
|
+
defaultValue: getPromptValue(document, "codex_provider", ""),
|
|
217
|
+
required: false
|
|
218
|
+
}
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function collectConfigPromptsForSection(document = {}, section) {
|
|
223
|
+
if (!section || section === "all") {
|
|
224
|
+
return collectConfigPrompts(document);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const targets = new Set(CONFIG_SECTION_FIELDS[section] || []);
|
|
228
|
+
if (targets.size === 0) {
|
|
229
|
+
throw new Error(`Unknown config section: ${section}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return collectConfigPrompts(document).filter((entry) => targets.has(entry.target));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function normalizeConfigDocument(document, resolvedConfigPath, options = {}) {
|
|
236
|
+
const configDir = path.dirname(resolvedConfigPath);
|
|
237
|
+
const runtimeProjectRoot = options.runtimeProjectRoot || configDir;
|
|
238
|
+
const homeDir = normalizeLocalPath(document.home_dir || "~", configDir);
|
|
239
|
+
const claudeDir = normalizeLocalPath(document.claude_dir || path.join(homeDir, ".claude"), configDir);
|
|
240
|
+
const codexDir = normalizeLocalPath(document.codex_dir || path.join(homeDir, ".codex"), configDir);
|
|
241
|
+
const localConfig = applyHostRuntimePaths(
|
|
242
|
+
{
|
|
243
|
+
name: "local",
|
|
244
|
+
type: "local",
|
|
245
|
+
base_url: document.base_url || "",
|
|
246
|
+
api_key: document.api_key || "",
|
|
247
|
+
client_api_key: DEFAULT_CLIENT_API_KEY,
|
|
248
|
+
big_model: document.big_model || DEFAULT_BIG_MODEL,
|
|
249
|
+
middle_model: document.middle_model || DEFAULT_MIDDLE_MODEL,
|
|
250
|
+
small_model: document.small_model || DEFAULT_SMALL_MODEL,
|
|
251
|
+
default_claude_model: document.default_claude_model || DEFAULT_CLAUDE_MODEL,
|
|
252
|
+
codex_provider: document.codex_provider || null,
|
|
253
|
+
home_dir: homeDir,
|
|
254
|
+
project_root: runtimeProjectRoot,
|
|
255
|
+
claude_dir: claudeDir,
|
|
256
|
+
codex_dir: codexDir,
|
|
257
|
+
config_path: resolvedConfigPath
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
projectRoot: runtimeProjectRoot,
|
|
261
|
+
claudeDir,
|
|
262
|
+
codexDir,
|
|
263
|
+
configPath: resolvedConfigPath
|
|
264
|
+
}
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
server_host: document.server_host || "127.0.0.1",
|
|
269
|
+
server_port: Number(document.server_port || 8082),
|
|
270
|
+
__configPath: resolvedConfigPath,
|
|
271
|
+
__projectRoot: runtimeProjectRoot,
|
|
272
|
+
...localConfig
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function readConfigDocument(configPath, options = {}) {
|
|
277
|
+
const resolvedConfigPath = path.resolve(configPath || getDefaultConfigPath());
|
|
278
|
+
let raw;
|
|
279
|
+
try {
|
|
280
|
+
raw = await fs.readFile(resolvedConfigPath, "utf8");
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (error.code === "ENOENT" && options.allowMissing) {
|
|
283
|
+
return {
|
|
284
|
+
resolvedConfigPath,
|
|
285
|
+
document: createDefaultConfigDocument()
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (error.code === "ENOENT") {
|
|
289
|
+
const defaultConfigPath = getDefaultConfigPath();
|
|
290
|
+
const defaultConfigDisplayPath = getDefaultConfigDisplayPath();
|
|
291
|
+
const usesDefaultConfigPath = !configPath || resolvedConfigPath === defaultConfigPath;
|
|
292
|
+
const displayPath = usesDefaultConfigPath ? defaultConfigDisplayPath : resolvedConfigPath;
|
|
293
|
+
const hint = usesDefaultConfigPath
|
|
294
|
+
? `Run "claude-proxy config" first to create the default config at ${defaultConfigDisplayPath}.`
|
|
295
|
+
: `Please check whether the file exists, or rerun with the same --config path used during "claude-proxy config".`;
|
|
296
|
+
throw new Error(
|
|
297
|
+
`Config file not found: ${displayPath}. ${hint} You can use "config_example.toml" in the project root as a reference.`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
resolvedConfigPath,
|
|
305
|
+
document: toml.parse(raw)
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function writeConfigDocument(configPath, document) {
|
|
310
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
311
|
+
await fs.writeFile(configPath, toml.stringify(document), "utf8");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function clearConfigDocumentSection(document, section) {
|
|
315
|
+
const fields = CONFIG_SECTION_FIELDS[section];
|
|
316
|
+
if (!fields) {
|
|
317
|
+
throw new Error(`Unknown config section: ${section}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const cleaned = { ...document };
|
|
321
|
+
for (const field of fields) {
|
|
322
|
+
delete cleaned[field];
|
|
323
|
+
}
|
|
324
|
+
return cleaned;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function clearConfigSection(configPath, section) {
|
|
328
|
+
const resolvedConfigPath = path.resolve(configPath || getDefaultConfigPath());
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
await fs.access(resolvedConfigPath);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (error.code === "ENOENT") {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const { document } = await readConfigDocument(resolvedConfigPath);
|
|
340
|
+
validateConfigDocument(document);
|
|
341
|
+
await writeConfigDocument(resolvedConfigPath, clearConfigDocumentSection(document, section));
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function loadConfig(configPath, options = {}) {
|
|
346
|
+
const { resolvedConfigPath, document } = await readConfigDocument(configPath, {
|
|
347
|
+
allowMissing: options.allowMissing
|
|
348
|
+
});
|
|
349
|
+
validateConfigDocument(document);
|
|
350
|
+
const config = normalizeConfigDocument(document, resolvedConfigPath, {
|
|
351
|
+
runtimeProjectRoot: options.runtimeProjectRoot
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (
|
|
355
|
+
!options.allowIncomplete &&
|
|
356
|
+
(isPlaceholderValue(config.base_url) || isPlaceholderValue(config.api_key))
|
|
357
|
+
) {
|
|
358
|
+
throw new Error("Config must include non-empty base_url and api_key.");
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return config;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function setDocumentValue(document, target, value) {
|
|
365
|
+
if (target === "server_port") {
|
|
366
|
+
document.server_port = Number(value);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (target === "codex_provider") {
|
|
371
|
+
if (value) {
|
|
372
|
+
document.codex_provider = value;
|
|
373
|
+
} else {
|
|
374
|
+
delete document.codex_provider;
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
document[target] = value;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function isValidPromptValue(target, value) {
|
|
383
|
+
if (target === "codex_provider") {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (!value) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (target === "server_port") {
|
|
392
|
+
const port = Number(value);
|
|
393
|
+
return Number.isInteger(port) && port > 0;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function promptForConfig(configPath, options = {}) {
|
|
400
|
+
const { resolvedConfigPath, document } = await readConfigDocument(configPath, {
|
|
401
|
+
allowMissing: true
|
|
402
|
+
});
|
|
403
|
+
validateConfigDocument(document);
|
|
404
|
+
const prompts = collectConfigPromptsForSection(document, "all");
|
|
405
|
+
const rl = readline.createInterface({
|
|
406
|
+
input: process.stdin,
|
|
407
|
+
output: process.stdout
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
for (const entry of prompts) {
|
|
412
|
+
for (;;) {
|
|
413
|
+
const suffix = entry.defaultValue ? ` [${entry.defaultValue}]` : "";
|
|
414
|
+
const answer = await rl.question(`${entry.question}${suffix}: `);
|
|
415
|
+
const value = answer.trim() || entry.defaultValue;
|
|
416
|
+
|
|
417
|
+
if (!isValidPromptValue(entry.target, value)) {
|
|
418
|
+
console.log(`Invalid value for ${entry.target}. Please try again.`);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
setDocumentValue(document, entry.target, value);
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
} finally {
|
|
427
|
+
rl.close();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
await writeConfigDocument(resolvedConfigPath, document);
|
|
431
|
+
return normalizeConfigDocument(document, resolvedConfigPath, {
|
|
432
|
+
runtimeProjectRoot: options.runtimeProjectRoot
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function promptForConfigSection(configPath, section, options = {}) {
|
|
437
|
+
const { resolvedConfigPath, document } = await readConfigDocument(configPath, {
|
|
438
|
+
allowMissing: true
|
|
439
|
+
});
|
|
440
|
+
validateConfigDocument(document);
|
|
441
|
+
const prompts = collectConfigPromptsForSection(document, section);
|
|
442
|
+
const rl = readline.createInterface({
|
|
443
|
+
input: process.stdin,
|
|
444
|
+
output: process.stdout
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
for (const entry of prompts) {
|
|
449
|
+
for (;;) {
|
|
450
|
+
const suffix = entry.defaultValue ? ` [${entry.defaultValue}]` : "";
|
|
451
|
+
const answer = await rl.question(`${entry.question}${suffix}: `);
|
|
452
|
+
const value = answer.trim() || entry.defaultValue;
|
|
453
|
+
|
|
454
|
+
if (!isValidPromptValue(entry.target, value)) {
|
|
455
|
+
console.log(`Invalid value for ${entry.target}. Please try again.`);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
setDocumentValue(document, entry.target, value);
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
} finally {
|
|
464
|
+
rl.close();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
await writeConfigDocument(resolvedConfigPath, document);
|
|
468
|
+
return normalizeConfigDocument(document, resolvedConfigPath, {
|
|
469
|
+
runtimeProjectRoot: options.runtimeProjectRoot
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
module.exports = {
|
|
474
|
+
DEFAULT_BIG_MODEL,
|
|
475
|
+
DEFAULT_CLAUDE_MODEL,
|
|
476
|
+
DEFAULT_CLIENT_API_KEY,
|
|
477
|
+
DEFAULT_MIDDLE_MODEL,
|
|
478
|
+
DEFAULT_SMALL_MODEL,
|
|
479
|
+
applyHostRuntimePaths,
|
|
480
|
+
clearConfigSection,
|
|
481
|
+
collectConfigPrompts,
|
|
482
|
+
collectConfigPromptsForSection,
|
|
483
|
+
expandHome,
|
|
484
|
+
getDefaultConfigDir,
|
|
485
|
+
getDefaultConfigDisplayPath,
|
|
486
|
+
getDefaultConfigPath,
|
|
487
|
+
isPlaceholderValue,
|
|
488
|
+
loadConfig,
|
|
489
|
+
normalizeLocalPath,
|
|
490
|
+
promptForConfig,
|
|
491
|
+
promptForConfigSection,
|
|
492
|
+
readConfigDocument,
|
|
493
|
+
writeConfigDocument
|
|
494
|
+
};
|