proxitor 0.9.0-beta.2 → 0.9.0-beta.3
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 +16 -4
- package/dist/cli.mjs +696 -340
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -2
package/dist/cli.mjs
CHANGED
|
@@ -19924,10 +19924,6 @@ function withReq(id, message) {
|
|
|
19924
19924
|
}
|
|
19925
19925
|
//#endregion
|
|
19926
19926
|
//#region src/commands/config/config.ts
|
|
19927
|
-
/**
|
|
19928
|
-
* Throws {@link MissingConfigError} via `findConfigFile` if no config is found.
|
|
19929
|
-
* Pass an explicit path to skip discovery and use that file directly.
|
|
19930
|
-
*/
|
|
19931
19927
|
function requireConfigPath(explicitPath) {
|
|
19932
19928
|
return findConfigFile(explicitPath);
|
|
19933
19929
|
}
|
|
@@ -19937,6 +19933,22 @@ function readConfigRaw(path) {
|
|
|
19937
19933
|
function writeConfigRaw(path, content) {
|
|
19938
19934
|
writeFileSync(path, content, "utf-8");
|
|
19939
19935
|
}
|
|
19936
|
+
function setGlobalConfigField(configPath, field, value) {
|
|
19937
|
+
const doc = (0, import_dist.parseDocument)(readConfigRaw(configPath));
|
|
19938
|
+
if (value === void 0) doc.delete(field);
|
|
19939
|
+
else doc.set(field, value);
|
|
19940
|
+
writeConfigRaw(configPath, doc.toString());
|
|
19941
|
+
}
|
|
19942
|
+
/**
|
|
19943
|
+
* Atomic batch write — updates multiple fields in a single read-parse-write
|
|
19944
|
+
* cycle, eliminating the risk of partial state on crash or Ctrl-C.
|
|
19945
|
+
*/
|
|
19946
|
+
function setGlobalConfigFields(configPath, fields) {
|
|
19947
|
+
const doc = (0, import_dist.parseDocument)(readConfigRaw(configPath));
|
|
19948
|
+
for (const [key, value] of Object.entries(fields)) if (value === void 0) doc.delete(key);
|
|
19949
|
+
else doc.set(key, value);
|
|
19950
|
+
writeConfigRaw(configPath, doc.toString());
|
|
19951
|
+
}
|
|
19940
19952
|
function setModelOverride(configPath, modelKey, override) {
|
|
19941
19953
|
const doc = (0, import_dist.parseDocument)(readConfigRaw(configPath));
|
|
19942
19954
|
let overrides = doc.get("modelOverrides");
|
|
@@ -20173,6 +20185,206 @@ async function selectIgnoreProviders(providerOptions) {
|
|
|
20173
20185
|
return { provider: { ignore: values.length === 1 ? values[0] : values } };
|
|
20174
20186
|
}
|
|
20175
20187
|
//#endregion
|
|
20188
|
+
//#region src/commands/config/prompts.ts
|
|
20189
|
+
function maskKey(key) {
|
|
20190
|
+
if (!key) return "(none)";
|
|
20191
|
+
if (key.length <= 11) return "****";
|
|
20192
|
+
return `${key.slice(0, 7)}…${key.slice(-4)}`;
|
|
20193
|
+
}
|
|
20194
|
+
async function askApiKey(currentKey) {
|
|
20195
|
+
if (currentKey) log.info(`Current key: ${maskKey(currentKey)}`);
|
|
20196
|
+
const apiKey = await text({
|
|
20197
|
+
message: "OpenRouter API key",
|
|
20198
|
+
placeholder: currentKey ? "Press Enter to keep current key" : "sk-or-v1-...",
|
|
20199
|
+
validate: (v) => {
|
|
20200
|
+
if (!v?.trim() && !currentKey) return "API key is required";
|
|
20201
|
+
}
|
|
20202
|
+
});
|
|
20203
|
+
if (isCancel(apiKey)) return null;
|
|
20204
|
+
note("You can also set the OPENROUTER_API_KEY environment variable\nto avoid storing the key in the config file.", "Tip");
|
|
20205
|
+
return apiKey.trim() || currentKey;
|
|
20206
|
+
}
|
|
20207
|
+
async function askPort(current) {
|
|
20208
|
+
const input = await text({
|
|
20209
|
+
message: "Proxy port",
|
|
20210
|
+
initialValue: String(current),
|
|
20211
|
+
placeholder: String(DEFAULTS.port),
|
|
20212
|
+
validate: (v) => {
|
|
20213
|
+
if (!v?.trim()) return void 0;
|
|
20214
|
+
const n = Number.parseInt(v, 10);
|
|
20215
|
+
if (Number.isNaN(n) || n < 1 || n > 65535) return "Port must be 1–65535";
|
|
20216
|
+
}
|
|
20217
|
+
});
|
|
20218
|
+
if (isCancel(input)) return null;
|
|
20219
|
+
return input.trim() ? Number.parseInt(input, 10) : DEFAULTS.port;
|
|
20220
|
+
}
|
|
20221
|
+
async function askBaseUrl(current) {
|
|
20222
|
+
const url = await text({
|
|
20223
|
+
message: "OpenRouter API base URL",
|
|
20224
|
+
placeholder: DEFAULTS.openrouterBaseUrl,
|
|
20225
|
+
initialValue: current === DEFAULTS.openrouterBaseUrl ? "" : current,
|
|
20226
|
+
validate: (v) => {
|
|
20227
|
+
if (!v?.trim()) return void 0;
|
|
20228
|
+
try {
|
|
20229
|
+
if (!new URL(v.trim()).protocol.startsWith("http")) return "URL must start with http:// or https://";
|
|
20230
|
+
} catch {
|
|
20231
|
+
return "Invalid URL";
|
|
20232
|
+
}
|
|
20233
|
+
}
|
|
20234
|
+
});
|
|
20235
|
+
if (isCancel(url)) return null;
|
|
20236
|
+
return url.trim() || DEFAULTS.openrouterBaseUrl;
|
|
20237
|
+
}
|
|
20238
|
+
async function askAuthType(current) {
|
|
20239
|
+
const authType = await select({
|
|
20240
|
+
message: "Authentication type",
|
|
20241
|
+
initialValue: current,
|
|
20242
|
+
options: [{
|
|
20243
|
+
value: "bearer",
|
|
20244
|
+
label: "Bearer token",
|
|
20245
|
+
hint: "Standard OpenRouter"
|
|
20246
|
+
}, {
|
|
20247
|
+
value: "oauth",
|
|
20248
|
+
label: "OAuth token",
|
|
20249
|
+
hint: "Custom proxy providers"
|
|
20250
|
+
}]
|
|
20251
|
+
});
|
|
20252
|
+
if (isCancel(authType)) return null;
|
|
20253
|
+
return authType;
|
|
20254
|
+
}
|
|
20255
|
+
async function askHost(current) {
|
|
20256
|
+
const isPreset = (v) => v === "0.0.0.0" || v === "127.0.0.1";
|
|
20257
|
+
const host = await select({
|
|
20258
|
+
message: "Listen address",
|
|
20259
|
+
initialValue: isPreset(current) ? current : "__custom__",
|
|
20260
|
+
options: [
|
|
20261
|
+
{
|
|
20262
|
+
value: "0.0.0.0",
|
|
20263
|
+
label: "All interfaces (0.0.0.0)",
|
|
20264
|
+
hint: "Default"
|
|
20265
|
+
},
|
|
20266
|
+
{
|
|
20267
|
+
value: "127.0.0.1",
|
|
20268
|
+
label: "Localhost only (127.0.0.1)",
|
|
20269
|
+
hint: "More secure"
|
|
20270
|
+
},
|
|
20271
|
+
{
|
|
20272
|
+
value: "__custom__",
|
|
20273
|
+
label: "Custom address…",
|
|
20274
|
+
hint: "Specific IP, hostname, or unix:/path"
|
|
20275
|
+
}
|
|
20276
|
+
]
|
|
20277
|
+
});
|
|
20278
|
+
if (isCancel(host)) return null;
|
|
20279
|
+
if (host === "__custom__") {
|
|
20280
|
+
const custom = await text({
|
|
20281
|
+
message: "Custom listen address",
|
|
20282
|
+
placeholder: "192.168.1.1",
|
|
20283
|
+
initialValue: isPreset(current) ? "" : current,
|
|
20284
|
+
validate: (v) => {
|
|
20285
|
+
const t = v?.trim();
|
|
20286
|
+
if (!t) return "Address is required";
|
|
20287
|
+
if (t.startsWith("unix:")) return void 0;
|
|
20288
|
+
if (!/^[\w.\-:]+$/.test(t)) return "Invalid host (allowed: IP, hostname, or unix:…)";
|
|
20289
|
+
}
|
|
20290
|
+
});
|
|
20291
|
+
if (isCancel(custom)) return null;
|
|
20292
|
+
return custom.trim();
|
|
20293
|
+
}
|
|
20294
|
+
return host;
|
|
20295
|
+
}
|
|
20296
|
+
/** Shared hint texts for session routing tri-state — used in add, edit, session-routing. */
|
|
20297
|
+
const SESSION_HINTS = {
|
|
20298
|
+
auto: "Passthrough client ID, generate if missing",
|
|
20299
|
+
always: "Always generate proxy session ID",
|
|
20300
|
+
never: "Don't manage session headers"
|
|
20301
|
+
};
|
|
20302
|
+
/** Shared hint texts for cache control tri-state — used in add, edit, cache-control. */
|
|
20303
|
+
const CACHE_HINTS = {
|
|
20304
|
+
auto: "Anthropic models only",
|
|
20305
|
+
always: "All models",
|
|
20306
|
+
never: "Off"
|
|
20307
|
+
};
|
|
20308
|
+
async function askTriState(message, current, hints) {
|
|
20309
|
+
const result = await select({
|
|
20310
|
+
message,
|
|
20311
|
+
initialValue: current ?? "auto",
|
|
20312
|
+
options: [
|
|
20313
|
+
{
|
|
20314
|
+
value: "auto",
|
|
20315
|
+
label: "auto",
|
|
20316
|
+
hint: hints.auto
|
|
20317
|
+
},
|
|
20318
|
+
{
|
|
20319
|
+
value: "always",
|
|
20320
|
+
label: "always",
|
|
20321
|
+
hint: hints.always
|
|
20322
|
+
},
|
|
20323
|
+
{
|
|
20324
|
+
value: "never",
|
|
20325
|
+
label: "never",
|
|
20326
|
+
hint: hints.never
|
|
20327
|
+
}
|
|
20328
|
+
]
|
|
20329
|
+
});
|
|
20330
|
+
if (isCancel(result)) return null;
|
|
20331
|
+
return result;
|
|
20332
|
+
}
|
|
20333
|
+
async function askCacheControlTtl(current) {
|
|
20334
|
+
const options = [{
|
|
20335
|
+
value: "5m",
|
|
20336
|
+
label: "5 minutes",
|
|
20337
|
+
hint: "Anthropic default"
|
|
20338
|
+
}, {
|
|
20339
|
+
value: "1h",
|
|
20340
|
+
label: "1 hour",
|
|
20341
|
+
hint: "Higher write cost"
|
|
20342
|
+
}];
|
|
20343
|
+
if (current) options.push({
|
|
20344
|
+
value: "reset",
|
|
20345
|
+
label: "Reset to default",
|
|
20346
|
+
hint: "Remove TTL override"
|
|
20347
|
+
});
|
|
20348
|
+
const result = await select({
|
|
20349
|
+
message: "Cache TTL",
|
|
20350
|
+
initialValue: current ?? "5m",
|
|
20351
|
+
options
|
|
20352
|
+
});
|
|
20353
|
+
if (isCancel(result)) return null;
|
|
20354
|
+
if (result === "reset") return "reset";
|
|
20355
|
+
return result;
|
|
20356
|
+
}
|
|
20357
|
+
//#endregion
|
|
20358
|
+
//#region src/commands/config/tri-state.ts
|
|
20359
|
+
/**
|
|
20360
|
+
* Prompt the user for a session routing mode.
|
|
20361
|
+
* Returns the updated override (with `sessionId` set) or `null` on cancel.
|
|
20362
|
+
*/
|
|
20363
|
+
async function collectSessionTriState(currentSid) {
|
|
20364
|
+
const sid = await askTriState("Session ID mode", currentSid ?? "auto", SESSION_HINTS);
|
|
20365
|
+
if (sid === null) return null;
|
|
20366
|
+
return { sessionId: sid };
|
|
20367
|
+
}
|
|
20368
|
+
/**
|
|
20369
|
+
* Prompt the user for a cache control mode and optional TTL.
|
|
20370
|
+
* Returns the updated fields or `null` on tri-state cancel.
|
|
20371
|
+
*
|
|
20372
|
+
* **TTL cancel behaviour**: when the user presses Escape on the TTL prompt
|
|
20373
|
+
* the existing TTL is preserved (not silently dropped).
|
|
20374
|
+
*/
|
|
20375
|
+
async function collectCacheTriState(currentCc, currentTtl) {
|
|
20376
|
+
const cc = await askTriState("Cache control mode", currentCc ?? "auto", CACHE_HINTS);
|
|
20377
|
+
if (cc === null) return null;
|
|
20378
|
+
const result = { cacheControl: cc };
|
|
20379
|
+
if (cc !== "never") {
|
|
20380
|
+
const ttl = await askCacheControlTtl(currentTtl ?? void 0);
|
|
20381
|
+
if (ttl === null) {
|
|
20382
|
+
if (currentTtl) result.cacheControlTtl = currentTtl;
|
|
20383
|
+
} else if (ttl !== "reset") result.cacheControlTtl = ttl;
|
|
20384
|
+
}
|
|
20385
|
+
return result;
|
|
20386
|
+
}
|
|
20387
|
+
//#endregion
|
|
20176
20388
|
//#region src/commands/config/add.ts
|
|
20177
20389
|
const CUSTOM_PATTERN_VALUE = "__proxitor_custom_pattern__";
|
|
20178
20390
|
/** Run the interactive "Add model override" flow. */
|
|
@@ -20262,18 +20474,49 @@ async function enterPattern(models) {
|
|
|
20262
20474
|
async function configureProviderAndSave(configPath, client, modelKey, isPattern) {
|
|
20263
20475
|
const mode = await selectRoutingMode("Configure provider routing");
|
|
20264
20476
|
if (isCancel(mode)) return;
|
|
20477
|
+
let override = {};
|
|
20265
20478
|
if (mode === "skip") {
|
|
20266
|
-
if (!await confirmAndSave(configPath, modelKey,
|
|
20267
|
-
outro("✓
|
|
20479
|
+
if (!await confirmAndSave(configPath, modelKey, override, client)) return;
|
|
20480
|
+
outro("✓ Model override saved (no provider routing)");
|
|
20268
20481
|
return;
|
|
20269
20482
|
}
|
|
20270
20483
|
const providerOptions = await fetchProvidersForModel(client, modelKey, isPattern);
|
|
20271
20484
|
if (!providerOptions) return;
|
|
20272
|
-
const
|
|
20273
|
-
if (!
|
|
20485
|
+
const providerResult = await selectProvidersByMode(mode, providerOptions);
|
|
20486
|
+
if (!providerResult) return;
|
|
20487
|
+
override = providerResult;
|
|
20488
|
+
override = await collectSessionAndCache(override);
|
|
20274
20489
|
if (!await confirmAndSave(configPath, modelKey, override, client)) return;
|
|
20275
20490
|
outro("✓ Model override saved");
|
|
20276
20491
|
}
|
|
20492
|
+
async function collectSessionAndCache(override) {
|
|
20493
|
+
override = await collectSession(override);
|
|
20494
|
+
override = await collectCache(override);
|
|
20495
|
+
return override;
|
|
20496
|
+
}
|
|
20497
|
+
async function collectSession(override) {
|
|
20498
|
+
const want = await confirm({
|
|
20499
|
+
message: "Configure session routing for this model?",
|
|
20500
|
+
initialValue: false
|
|
20501
|
+
});
|
|
20502
|
+
if (isCancel(want) || !want) return override;
|
|
20503
|
+
const result = await collectSessionTriState();
|
|
20504
|
+
if (result) override.sessionId = result.sessionId;
|
|
20505
|
+
return override;
|
|
20506
|
+
}
|
|
20507
|
+
async function collectCache(override) {
|
|
20508
|
+
const want = await confirm({
|
|
20509
|
+
message: "Configure cache control for this model?",
|
|
20510
|
+
initialValue: false
|
|
20511
|
+
});
|
|
20512
|
+
if (isCancel(want) || !want) return override;
|
|
20513
|
+
const result = await collectCacheTriState();
|
|
20514
|
+
if (result) {
|
|
20515
|
+
override.cacheControl = result.cacheControl;
|
|
20516
|
+
if (result.cacheControlTtl) override.cacheControlTtl = result.cacheControlTtl;
|
|
20517
|
+
}
|
|
20518
|
+
return override;
|
|
20519
|
+
}
|
|
20277
20520
|
/**
|
|
20278
20521
|
* Show the proposed override and let the user Save / Test (dry-run) / Cancel.
|
|
20279
20522
|
* Returns true if the override was saved.
|
|
@@ -20330,6 +20573,9 @@ function formatOverrideYaml(override) {
|
|
|
20330
20573
|
const p = override.provider;
|
|
20331
20574
|
for (const [key, value] of Object.entries(p)) parts.push(`provider.${key}: ${JSON.stringify(value)}`);
|
|
20332
20575
|
}
|
|
20576
|
+
if (override.sessionId) parts.push(`sessionId: ${override.sessionId}`);
|
|
20577
|
+
if (override.cacheControl) parts.push(`cacheControl: ${override.cacheControl}`);
|
|
20578
|
+
if (override.cacheControlTtl) parts.push(`cacheControlTtl: ${override.cacheControlTtl}`);
|
|
20333
20579
|
return parts.join("\n ") || "(empty)";
|
|
20334
20580
|
}
|
|
20335
20581
|
//#endregion
|
|
@@ -20416,39 +20662,59 @@ async function browseModelsCommand(client) {
|
|
|
20416
20662
|
//#endregion
|
|
20417
20663
|
//#region src/commands/config/edit.ts
|
|
20418
20664
|
function formatOverrideHint(override) {
|
|
20419
|
-
if (!override
|
|
20420
|
-
|
|
20665
|
+
if (!override) return "(empty)";
|
|
20666
|
+
const parts = [];
|
|
20667
|
+
if (override.provider) {
|
|
20668
|
+
const keys = Object.keys(override.provider);
|
|
20669
|
+
parts.push(`provider: ${keys.join(", ")}`);
|
|
20670
|
+
}
|
|
20671
|
+
if (override.sessionId) parts.push(`session: ${override.sessionId}`);
|
|
20672
|
+
if (override.cacheControl) parts.push(`cache: ${override.cacheControl}`);
|
|
20673
|
+
if (override.headers) parts.push(`${Object.keys(override.headers).length} header(s)`);
|
|
20674
|
+
return parts.join(", ") || "(empty)";
|
|
20421
20675
|
}
|
|
20422
20676
|
function showCurrentConfig(modelKey, current) {
|
|
20423
20677
|
log.info(`Current config for "${modelKey}":`);
|
|
20424
20678
|
if (current.provider) for (const [field, value] of Object.entries(current.provider)) log.info(` provider.${field}: ${JSON.stringify(value)}`);
|
|
20679
|
+
if (current.sessionId) log.info(` sessionId: ${current.sessionId}`);
|
|
20680
|
+
if (current.cacheControl) log.info(` cacheControl: ${current.cacheControl}`);
|
|
20681
|
+
if (current.cacheControlTtl) log.info(` cacheControlTtl: ${current.cacheControlTtl}`);
|
|
20425
20682
|
if (current.headers) for (const [name, value] of Object.entries(current.headers)) log.info(` headers.${name}: ${value}`);
|
|
20426
20683
|
}
|
|
20427
|
-
function
|
|
20428
|
-
return current.headers ? { headers: current.headers } : {};
|
|
20429
|
-
}
|
|
20430
|
-
async function updateProviderRouting(configPath, modelKey, current, client) {
|
|
20684
|
+
async function editProvider(modelKey, current, client) {
|
|
20431
20685
|
const isPattern = modelKey.includes("*");
|
|
20432
20686
|
const mode = await selectRoutingMode("Routing mode");
|
|
20433
|
-
if (isCancel(mode)) return;
|
|
20687
|
+
if (isCancel(mode)) return current;
|
|
20434
20688
|
if (mode === "skip") {
|
|
20435
|
-
|
|
20436
|
-
|
|
20437
|
-
return;
|
|
20689
|
+
const { provider: _, ...rest } = current;
|
|
20690
|
+
return rest;
|
|
20438
20691
|
}
|
|
20439
20692
|
const providerOptions = await fetchProvidersForModel(client, modelKey, isPattern);
|
|
20440
|
-
if (!providerOptions) return;
|
|
20441
|
-
const
|
|
20442
|
-
if (
|
|
20443
|
-
|
|
20444
|
-
|
|
20445
|
-
|
|
20446
|
-
|
|
20447
|
-
|
|
20448
|
-
|
|
20449
|
-
|
|
20450
|
-
|
|
20451
|
-
|
|
20693
|
+
if (!providerOptions) return current;
|
|
20694
|
+
const result = await selectProvidersByMode(mode, providerOptions);
|
|
20695
|
+
if (!result) return current;
|
|
20696
|
+
return {
|
|
20697
|
+
...current,
|
|
20698
|
+
provider: result.provider
|
|
20699
|
+
};
|
|
20700
|
+
}
|
|
20701
|
+
async function editSessionId(current) {
|
|
20702
|
+
const result = await collectSessionTriState(current.sessionId);
|
|
20703
|
+
if (result === null) return current;
|
|
20704
|
+
const { sessionId: _, ...rest } = current;
|
|
20705
|
+
return {
|
|
20706
|
+
...rest,
|
|
20707
|
+
sessionId: result.sessionId
|
|
20708
|
+
};
|
|
20709
|
+
}
|
|
20710
|
+
async function editCacheControl(current) {
|
|
20711
|
+
const result = await collectCacheTriState(current.cacheControl, current.cacheControlTtl);
|
|
20712
|
+
if (result === null) return current;
|
|
20713
|
+
const { cacheControl: _cc, cacheControlTtl: _ttl, ...rest } = current;
|
|
20714
|
+
return {
|
|
20715
|
+
...rest,
|
|
20716
|
+
...result
|
|
20717
|
+
};
|
|
20452
20718
|
}
|
|
20453
20719
|
/** Run the interactive "Edit model override" flow. */
|
|
20454
20720
|
async function editOverrideCommand(client, configPath) {
|
|
@@ -20471,9 +20737,53 @@ async function editOverrideCommand(client, configPath) {
|
|
|
20471
20737
|
});
|
|
20472
20738
|
if (isCancel(selected)) return;
|
|
20473
20739
|
const modelKey = selected;
|
|
20474
|
-
|
|
20740
|
+
let current = overrides[modelKey] ?? {};
|
|
20475
20741
|
showCurrentConfig(modelKey, current);
|
|
20476
|
-
|
|
20742
|
+
while (true) {
|
|
20743
|
+
const field = await select({
|
|
20744
|
+
message: "Edit which field?",
|
|
20745
|
+
options: [
|
|
20746
|
+
{
|
|
20747
|
+
value: "provider",
|
|
20748
|
+
label: "Provider routing",
|
|
20749
|
+
hint: formatOverrideHint({ provider: current.provider })
|
|
20750
|
+
},
|
|
20751
|
+
{
|
|
20752
|
+
value: "sessionId",
|
|
20753
|
+
label: "Session ID",
|
|
20754
|
+
hint: current.sessionId ?? "(inherit)"
|
|
20755
|
+
},
|
|
20756
|
+
{
|
|
20757
|
+
value: "cacheControl",
|
|
20758
|
+
label: "Cache control",
|
|
20759
|
+
hint: [current.cacheControl, current.cacheControlTtl].filter(Boolean).join(", ") || "(inherit)"
|
|
20760
|
+
},
|
|
20761
|
+
{
|
|
20762
|
+
value: "done",
|
|
20763
|
+
label: "✓ Done"
|
|
20764
|
+
}
|
|
20765
|
+
]
|
|
20766
|
+
});
|
|
20767
|
+
if (isCancel(field) || field === "done") break;
|
|
20768
|
+
switch (field) {
|
|
20769
|
+
case "provider":
|
|
20770
|
+
current = await editProvider(modelKey, current, client);
|
|
20771
|
+
break;
|
|
20772
|
+
case "sessionId":
|
|
20773
|
+
current = await editSessionId(current);
|
|
20774
|
+
break;
|
|
20775
|
+
case "cacheControl":
|
|
20776
|
+
current = await editCacheControl(current);
|
|
20777
|
+
break;
|
|
20778
|
+
}
|
|
20779
|
+
}
|
|
20780
|
+
const save = await confirm({ message: "Save changes?" });
|
|
20781
|
+
if (isCancel(save) || !save) {
|
|
20782
|
+
outro("Cancelled");
|
|
20783
|
+
return;
|
|
20784
|
+
}
|
|
20785
|
+
setModelOverride(resolvedConfigPath, modelKey, current);
|
|
20786
|
+
outro("✓ Override updated");
|
|
20477
20787
|
}
|
|
20478
20788
|
//#endregion
|
|
20479
20789
|
//#region src/commands/config/list.ts
|
|
@@ -20483,6 +20793,9 @@ function formatOverrideSummary(override) {
|
|
|
20483
20793
|
for (const [field, value] of Object.entries(override.provider)) if (value !== void 0) parts.push(`${field}: ${JSON.stringify(value)}`);
|
|
20484
20794
|
}
|
|
20485
20795
|
if (override.headers) for (const [name, value] of Object.entries(override.headers)) parts.push(`header ${name}: ${value}`);
|
|
20796
|
+
if (override.sessionId) parts.push(`session: ${override.sessionId}`);
|
|
20797
|
+
if (override.cacheControl) parts.push(`cache: ${override.cacheControl}`);
|
|
20798
|
+
if (override.cacheControlTtl) parts.push(`ttl: ${override.cacheControlTtl}`);
|
|
20486
20799
|
return parts.join(", ") || "(empty)";
|
|
20487
20800
|
}
|
|
20488
20801
|
async function listOverridesCommand(args = {}) {
|
|
@@ -20547,40 +20860,166 @@ async function removeOverrideCommand(args = {}) {
|
|
|
20547
20860
|
outro(`✓ ${toRemove.length} override(s) removed`);
|
|
20548
20861
|
}
|
|
20549
20862
|
//#endregion
|
|
20550
|
-
//#region src/
|
|
20551
|
-
|
|
20552
|
-
|
|
20553
|
-
|
|
20554
|
-
|
|
20555
|
-
|
|
20556
|
-
|
|
20863
|
+
//#region src/commands/config/show.ts
|
|
20864
|
+
const SEPARATE_KEYS = new Set([
|
|
20865
|
+
...new Set(["openrouterKey"]),
|
|
20866
|
+
"modelOverrides",
|
|
20867
|
+
"provider",
|
|
20868
|
+
"headers"
|
|
20869
|
+
]);
|
|
20870
|
+
function logResolved(cfg, source) {
|
|
20871
|
+
intro("Resolved configuration");
|
|
20872
|
+
if (source) log.info(`Source: ${source}`);
|
|
20873
|
+
else log.info("Source: defaults only");
|
|
20874
|
+
for (const [key, value] of Object.entries(cfg)) {
|
|
20875
|
+
if (SEPARATE_KEYS.has(key)) continue;
|
|
20876
|
+
const label = key.padEnd(20);
|
|
20877
|
+
if (value !== void 0 && value !== "") log.info(` ${label} ${value}`);
|
|
20557
20878
|
}
|
|
20558
|
-
};
|
|
20559
|
-
|
|
20560
|
-
|
|
20561
|
-
|
|
20562
|
-
|
|
20563
|
-
|
|
20564
|
-
|
|
20565
|
-
if (baseUrl !== void 0) {
|
|
20566
|
-
this.apiKey = apiKeyOrUrl;
|
|
20567
|
-
this.baseUrl = baseUrl;
|
|
20568
|
-
this.authType = authType;
|
|
20569
|
-
} else {
|
|
20570
|
-
this.apiKey = void 0;
|
|
20571
|
-
this.baseUrl = apiKeyOrUrl;
|
|
20572
|
-
this.authType = void 0;
|
|
20573
|
-
}
|
|
20879
|
+
log.info(` ${"openrouterKey".padEnd(20)} ${maskKey(cfg.openrouterKey)}`);
|
|
20880
|
+
if (cfg.provider) log.info(` ${"provider".padEnd(20)} ${JSON.stringify(cfg.provider)}`);
|
|
20881
|
+
if (cfg.headers && Object.keys(cfg.headers).length > 0) log.info(` ${"headers".padEnd(20)} ${JSON.stringify(cfg.headers)}`);
|
|
20882
|
+
if (cfg.modelOverrides && Object.keys(cfg.modelOverrides).length > 0) {
|
|
20883
|
+
const count = Object.keys(cfg.modelOverrides).length;
|
|
20884
|
+
log.info(` ${"modelOverrides".padEnd(20)} ${count} model(s)`);
|
|
20885
|
+
for (const key of Object.keys(cfg.modelOverrides)) log.info(` ${key}`);
|
|
20574
20886
|
}
|
|
20575
|
-
|
|
20576
|
-
|
|
20577
|
-
|
|
20578
|
-
|
|
20579
|
-
|
|
20580
|
-
if (
|
|
20581
|
-
|
|
20582
|
-
|
|
20583
|
-
|
|
20887
|
+
outro(`Defaults loaded from schema: ${Object.keys(DEFAULTS).length} keys`);
|
|
20888
|
+
}
|
|
20889
|
+
async function showConfigCommand(args) {
|
|
20890
|
+
let discoveredPath = null;
|
|
20891
|
+
if (args.configPath) {
|
|
20892
|
+
if (existsSync(args.configPath)) discoveredPath = resolve(args.configPath);
|
|
20893
|
+
} else discoveredPath = tryFindConfigFile();
|
|
20894
|
+
const configExists = discoveredPath !== null;
|
|
20895
|
+
if (!configExists && !args.openrouterKey) {
|
|
20896
|
+
log.warn("No config file and no --openrouter-key — cannot show a complete resolved view.");
|
|
20897
|
+
if (args.json) {
|
|
20898
|
+
process.stdout.write(`${JSON.stringify({
|
|
20899
|
+
ok: false,
|
|
20900
|
+
error: "No config available"
|
|
20901
|
+
})}\n`);
|
|
20902
|
+
return;
|
|
20903
|
+
}
|
|
20904
|
+
outro("Run `proxitor config wizard` to create one.");
|
|
20905
|
+
return;
|
|
20906
|
+
}
|
|
20907
|
+
const cfg = await loadConfig({
|
|
20908
|
+
configPath: args.configPath,
|
|
20909
|
+
noConfig: !configExists && !args.configPath,
|
|
20910
|
+
openrouterKey: args.openrouterKey
|
|
20911
|
+
});
|
|
20912
|
+
if (args.json) {
|
|
20913
|
+
const masked = {
|
|
20914
|
+
...cfg,
|
|
20915
|
+
openrouterKey: maskKey(cfg.openrouterKey)
|
|
20916
|
+
};
|
|
20917
|
+
process.stdout.write(`${JSON.stringify(masked, null, 2)}\n`);
|
|
20918
|
+
return;
|
|
20919
|
+
}
|
|
20920
|
+
logResolved(cfg, discoveredPath);
|
|
20921
|
+
}
|
|
20922
|
+
//#endregion
|
|
20923
|
+
//#region src/commands/config/validate.ts
|
|
20924
|
+
function validate(configPath) {
|
|
20925
|
+
if (!configPath) return {
|
|
20926
|
+
ok: false,
|
|
20927
|
+
configPath: null,
|
|
20928
|
+
error: "No config file found"
|
|
20929
|
+
};
|
|
20930
|
+
try {
|
|
20931
|
+
const cfg = readConfigFile(configPath);
|
|
20932
|
+
return {
|
|
20933
|
+
ok: true,
|
|
20934
|
+
configPath,
|
|
20935
|
+
keyCount: Object.keys(cfg).length,
|
|
20936
|
+
overrideCount: cfg.modelOverrides ? Object.keys(cfg.modelOverrides).length : 0
|
|
20937
|
+
};
|
|
20938
|
+
} catch (error) {
|
|
20939
|
+
if (error instanceof ConfigValidationError) {
|
|
20940
|
+
const issues = error.zodError.issues.map((issue) => ({
|
|
20941
|
+
path: issue.path.length > 0 ? issue.path.join(".") : "(root)",
|
|
20942
|
+
message: issue.message
|
|
20943
|
+
}));
|
|
20944
|
+
return {
|
|
20945
|
+
ok: false,
|
|
20946
|
+
configPath,
|
|
20947
|
+
error: error.message,
|
|
20948
|
+
issues
|
|
20949
|
+
};
|
|
20950
|
+
}
|
|
20951
|
+
return {
|
|
20952
|
+
ok: false,
|
|
20953
|
+
configPath,
|
|
20954
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20955
|
+
};
|
|
20956
|
+
}
|
|
20957
|
+
}
|
|
20958
|
+
async function validateConfigCommand(args = {}) {
|
|
20959
|
+
const result = validate(tryFindConfigFile(args.configPath));
|
|
20960
|
+
if (args.json) {
|
|
20961
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
20962
|
+
return result.ok ? 0 : 1;
|
|
20963
|
+
}
|
|
20964
|
+
intro("Validate Config");
|
|
20965
|
+
if (result.ok) {
|
|
20966
|
+
log.success(`Config is valid: ${result.configPath}`);
|
|
20967
|
+
log.info(` ${result.keyCount} top-level keys, ${result.overrideCount} model override(s)`);
|
|
20968
|
+
outro("Ready to run `proxitor start`.");
|
|
20969
|
+
return 0;
|
|
20970
|
+
}
|
|
20971
|
+
if (!result.configPath) {
|
|
20972
|
+
log.warn("No config file found — nothing to validate.");
|
|
20973
|
+
log.info("Searched:");
|
|
20974
|
+
outro("Run `proxitor config wizard` to create one.");
|
|
20975
|
+
return 1;
|
|
20976
|
+
}
|
|
20977
|
+
log.error(`Invalid config: ${result.configPath}`);
|
|
20978
|
+
if (result.issues && result.issues.length > 0) {
|
|
20979
|
+
note(result.issues.map((i) => ` ${i.path}: ${i.message}`).join("\n"), "Validation issues");
|
|
20980
|
+
log.info("Tips:");
|
|
20981
|
+
log.info(` • Open the file: \`$EDITOR ${result.configPath}\``);
|
|
20982
|
+
log.info(" • Run `proxitor config wizard` to start fresh");
|
|
20983
|
+
log.info(" • Or run `proxitor doctor` for a fuller diagnostic");
|
|
20984
|
+
} else log.error(result.error);
|
|
20985
|
+
outro("Fix the issues and re-run `proxitor config validate`.");
|
|
20986
|
+
return 1;
|
|
20987
|
+
}
|
|
20988
|
+
//#endregion
|
|
20989
|
+
//#region src/openrouter/client.ts
|
|
20990
|
+
var OpenRouterClientError = class extends Error {
|
|
20991
|
+
status;
|
|
20992
|
+
constructor(status, message) {
|
|
20993
|
+
super(`OpenRouter API error (${status}): ${message}`);
|
|
20994
|
+
this.name = "OpenRouterClientError";
|
|
20995
|
+
this.status = status;
|
|
20996
|
+
}
|
|
20997
|
+
};
|
|
20998
|
+
/** HTTP client for OpenRouter REST endpoints. */
|
|
20999
|
+
var OpenRouterClient = class {
|
|
21000
|
+
apiKey;
|
|
21001
|
+
baseUrl;
|
|
21002
|
+
authType;
|
|
21003
|
+
constructor(apiKeyOrUrl, baseUrl, authType) {
|
|
21004
|
+
if (baseUrl !== void 0) {
|
|
21005
|
+
this.apiKey = apiKeyOrUrl;
|
|
21006
|
+
this.baseUrl = baseUrl;
|
|
21007
|
+
this.authType = authType;
|
|
21008
|
+
} else {
|
|
21009
|
+
this.apiKey = void 0;
|
|
21010
|
+
this.baseUrl = apiKeyOrUrl;
|
|
21011
|
+
this.authType = void 0;
|
|
21012
|
+
}
|
|
21013
|
+
}
|
|
21014
|
+
async get(path) {
|
|
21015
|
+
const url = `${this.baseUrl}${path}`;
|
|
21016
|
+
const headers = {};
|
|
21017
|
+
if (this.apiKey !== void 0 && this.authType !== void 0) headers.Authorization = formatAuthHeader(this.apiKey, this.authType);
|
|
21018
|
+
const res = await fetch(url, { headers });
|
|
21019
|
+
if (!res.ok) {
|
|
21020
|
+
const body = await res.text().catch(() => "");
|
|
21021
|
+
throw new OpenRouterClientError(res.status, body || res.statusText);
|
|
21022
|
+
}
|
|
20584
21023
|
return res.json();
|
|
20585
21024
|
}
|
|
20586
21025
|
};
|
|
@@ -20722,11 +21161,6 @@ async function probeUpstream(baseUrl, apiKey, authType, timeoutMs = 3e3) {
|
|
|
20722
21161
|
}
|
|
20723
21162
|
//#endregion
|
|
20724
21163
|
//#region src/commands/config/wizard.ts
|
|
20725
|
-
function maskKey(key) {
|
|
20726
|
-
if (!key) return "(none)";
|
|
20727
|
-
if (key.length <= 11) return "****";
|
|
20728
|
-
return `${key.slice(0, 7)}…${key.slice(-4)}`;
|
|
20729
|
-
}
|
|
20730
21164
|
function resolveSavePath(location) {
|
|
20731
21165
|
switch (location) {
|
|
20732
21166
|
case "local": return resolve("proxitor.config.yaml");
|
|
@@ -20782,108 +21216,6 @@ function buildYaml(apiKey, port, host, baseUrl, authType, existingRaw) {
|
|
|
20782
21216
|
if (authType !== DEFAULTS.authType) config.authType = authType;
|
|
20783
21217
|
return (0, import_dist.stringify)(config);
|
|
20784
21218
|
}
|
|
20785
|
-
async function askApiKey(currentKey) {
|
|
20786
|
-
if (currentKey) log.info(`Current key: ${maskKey(currentKey)}`);
|
|
20787
|
-
const apiKey = await text({
|
|
20788
|
-
message: "OpenRouter API key",
|
|
20789
|
-
placeholder: currentKey ? "Press Enter to keep current key" : "sk-or-v1-...",
|
|
20790
|
-
validate: (v) => {
|
|
20791
|
-
if (!v?.trim() && !currentKey) return "API key is required";
|
|
20792
|
-
}
|
|
20793
|
-
});
|
|
20794
|
-
if (isCancel(apiKey)) return null;
|
|
20795
|
-
note("You can also set the OPENROUTER_API_KEY environment variable\nto avoid storing the key in the config file.", "Tip");
|
|
20796
|
-
return apiKey.trim() || currentKey;
|
|
20797
|
-
}
|
|
20798
|
-
async function askPort(current) {
|
|
20799
|
-
const input = await text({
|
|
20800
|
-
message: "Proxy port",
|
|
20801
|
-
initialValue: String(current),
|
|
20802
|
-
placeholder: String(DEFAULTS.port),
|
|
20803
|
-
validate: (v) => {
|
|
20804
|
-
if (!v?.trim()) return void 0;
|
|
20805
|
-
const n = Number.parseInt(v, 10);
|
|
20806
|
-
if (Number.isNaN(n) || n < 1 || n > 65535) return "Port must be 1–65535";
|
|
20807
|
-
}
|
|
20808
|
-
});
|
|
20809
|
-
if (isCancel(input)) return null;
|
|
20810
|
-
return input.trim() ? Number.parseInt(input, 10) : DEFAULTS.port;
|
|
20811
|
-
}
|
|
20812
|
-
async function askBaseUrl(current) {
|
|
20813
|
-
const url = await text({
|
|
20814
|
-
message: "OpenRouter API base URL",
|
|
20815
|
-
placeholder: DEFAULTS.openrouterBaseUrl,
|
|
20816
|
-
initialValue: current === DEFAULTS.openrouterBaseUrl ? "" : current,
|
|
20817
|
-
validate: (v) => {
|
|
20818
|
-
if (!v?.trim()) return void 0;
|
|
20819
|
-
try {
|
|
20820
|
-
if (!new URL(v.trim()).protocol.startsWith("http")) return "URL must start with http:// or https://";
|
|
20821
|
-
} catch {
|
|
20822
|
-
return "Invalid URL";
|
|
20823
|
-
}
|
|
20824
|
-
}
|
|
20825
|
-
});
|
|
20826
|
-
if (isCancel(url)) return null;
|
|
20827
|
-
return url.trim() || DEFAULTS.openrouterBaseUrl;
|
|
20828
|
-
}
|
|
20829
|
-
async function askAuthType(current) {
|
|
20830
|
-
const authType = await select({
|
|
20831
|
-
message: "Authentication type",
|
|
20832
|
-
initialValue: current,
|
|
20833
|
-
options: [{
|
|
20834
|
-
value: "bearer",
|
|
20835
|
-
label: "Bearer token",
|
|
20836
|
-
hint: "Standard OpenRouter"
|
|
20837
|
-
}, {
|
|
20838
|
-
value: "oauth",
|
|
20839
|
-
label: "OAuth token",
|
|
20840
|
-
hint: "Custom proxy providers"
|
|
20841
|
-
}]
|
|
20842
|
-
});
|
|
20843
|
-
if (isCancel(authType)) return null;
|
|
20844
|
-
return authType;
|
|
20845
|
-
}
|
|
20846
|
-
async function askHost(current) {
|
|
20847
|
-
const isPreset = (v) => v === "0.0.0.0" || v === "127.0.0.1";
|
|
20848
|
-
const host = await select({
|
|
20849
|
-
message: "Listen address",
|
|
20850
|
-
initialValue: isPreset(current) ? current : "__custom__",
|
|
20851
|
-
options: [
|
|
20852
|
-
{
|
|
20853
|
-
value: "0.0.0.0",
|
|
20854
|
-
label: "All interfaces (0.0.0.0)",
|
|
20855
|
-
hint: "Default"
|
|
20856
|
-
},
|
|
20857
|
-
{
|
|
20858
|
-
value: "127.0.0.1",
|
|
20859
|
-
label: "Localhost only (127.0.0.1)",
|
|
20860
|
-
hint: "More secure"
|
|
20861
|
-
},
|
|
20862
|
-
{
|
|
20863
|
-
value: "__custom__",
|
|
20864
|
-
label: "Custom address…",
|
|
20865
|
-
hint: "Specific IP, hostname, or unix:/path"
|
|
20866
|
-
}
|
|
20867
|
-
]
|
|
20868
|
-
});
|
|
20869
|
-
if (isCancel(host)) return null;
|
|
20870
|
-
if (host === "__custom__") {
|
|
20871
|
-
const custom = await text({
|
|
20872
|
-
message: "Custom listen address",
|
|
20873
|
-
placeholder: "192.168.1.1",
|
|
20874
|
-
initialValue: isPreset(current) ? "" : current,
|
|
20875
|
-
validate: (v) => {
|
|
20876
|
-
const t = v?.trim();
|
|
20877
|
-
if (!t) return "Address is required";
|
|
20878
|
-
if (t.startsWith("unix:")) return void 0;
|
|
20879
|
-
if (!/^[\w.\-:]+$/.test(t)) return "Invalid host (allowed: IP, hostname, or unix:…)";
|
|
20880
|
-
}
|
|
20881
|
-
});
|
|
20882
|
-
if (isCancel(custom)) return null;
|
|
20883
|
-
return custom.trim();
|
|
20884
|
-
}
|
|
20885
|
-
return host;
|
|
20886
|
-
}
|
|
20887
21219
|
async function askSaveLocation(existingPath) {
|
|
20888
21220
|
const options = getSaveLocationOptions(existingPath);
|
|
20889
21221
|
const location = await select({
|
|
@@ -20998,197 +21330,221 @@ async function runWizard(opts = {}) {
|
|
|
20998
21330
|
}
|
|
20999
21331
|
}
|
|
21000
21332
|
//#endregion
|
|
21001
|
-
//#region src/commands/config/
|
|
21002
|
-
|
|
21003
|
-
|
|
21004
|
-
|
|
21005
|
-
|
|
21006
|
-
|
|
21007
|
-
|
|
21008
|
-
|
|
21009
|
-
|
|
21010
|
-
if (
|
|
21011
|
-
|
|
21012
|
-
|
|
21013
|
-
|
|
21014
|
-
|
|
21015
|
-
if (
|
|
21016
|
-
|
|
21017
|
-
|
|
21018
|
-
|
|
21019
|
-
|
|
21020
|
-
|
|
21021
|
-
const count = Object.keys(cfg.modelOverrides).length;
|
|
21022
|
-
log.info(` ${"modelOverrides".padEnd(20)} ${count} model(s)`);
|
|
21023
|
-
for (const key of Object.keys(cfg.modelOverrides)) log.info(` ${key}`);
|
|
21024
|
-
}
|
|
21025
|
-
outro(`Defaults loaded from schema: ${Object.keys(DEFAULTS).length} keys`);
|
|
21026
|
-
}
|
|
21027
|
-
async function showConfigCommand(args) {
|
|
21028
|
-
let discoveredPath = null;
|
|
21029
|
-
if (args.configPath) {
|
|
21030
|
-
if (existsSync(args.configPath)) discoveredPath = resolve(args.configPath);
|
|
21031
|
-
} else discoveredPath = tryFindConfigFile();
|
|
21032
|
-
const configExists = discoveredPath !== null;
|
|
21033
|
-
if (!configExists && !args.openrouterKey) {
|
|
21034
|
-
log.warn("No config file and no --openrouter-key — cannot show a complete resolved view.");
|
|
21035
|
-
if (args.json) {
|
|
21036
|
-
process.stdout.write(`${JSON.stringify({
|
|
21037
|
-
ok: false,
|
|
21038
|
-
error: "No config available"
|
|
21039
|
-
})}\n`);
|
|
21040
|
-
return;
|
|
21041
|
-
}
|
|
21042
|
-
outro("Run `proxitor config wizard` to create one.");
|
|
21043
|
-
return;
|
|
21044
|
-
}
|
|
21045
|
-
const cfg = await loadConfig({
|
|
21046
|
-
configPath: args.configPath,
|
|
21047
|
-
noConfig: !configExists && !args.configPath,
|
|
21048
|
-
openrouterKey: args.openrouterKey
|
|
21049
|
-
});
|
|
21050
|
-
if (args.json) {
|
|
21051
|
-
const masked = {
|
|
21052
|
-
...cfg,
|
|
21053
|
-
openrouterKey: maskKey(cfg.openrouterKey)
|
|
21054
|
-
};
|
|
21055
|
-
process.stdout.write(`${JSON.stringify(masked, null, 2)}\n`);
|
|
21056
|
-
return;
|
|
21057
|
-
}
|
|
21058
|
-
logResolved(cfg, discoveredPath);
|
|
21333
|
+
//#region src/commands/config/cache-control.ts
|
|
21334
|
+
async function cacheControlCommand(opts) {
|
|
21335
|
+
const configPath = requireConfigPath(opts?.configPath);
|
|
21336
|
+
const cfg = readConfigFile(configPath);
|
|
21337
|
+
const currentCc = cfg.cacheControl ?? DEFAULTS.cacheControl;
|
|
21338
|
+
const currentTtl = cfg.cacheControlTtl;
|
|
21339
|
+
log.info(`Current: cacheControl = ${currentCc}`);
|
|
21340
|
+
if (currentTtl) log.info(`Current: cacheControlTtl = ${currentTtl}`);
|
|
21341
|
+
const cc = await askTriState("Cache control mode", currentCc, CACHE_HINTS);
|
|
21342
|
+
if (cc === null) return;
|
|
21343
|
+
const fields = { cacheControl: cc };
|
|
21344
|
+
if (cc !== "never") {
|
|
21345
|
+
const ttlResult = await askCacheControlTtl(currentTtl);
|
|
21346
|
+
if (ttlResult === null) return;
|
|
21347
|
+
if (ttlResult === "reset") fields.cacheControlTtl = void 0;
|
|
21348
|
+
else fields.cacheControlTtl = ttlResult;
|
|
21349
|
+
}
|
|
21350
|
+
setGlobalConfigFields(configPath, fields);
|
|
21351
|
+
const ttlPart = fields.cacheControlTtl ? `, TTL = ${fields.cacheControlTtl}` : "";
|
|
21352
|
+
log.success(`cacheControl set to ${cc}${ttlPart}`);
|
|
21059
21353
|
}
|
|
21060
21354
|
//#endregion
|
|
21061
|
-
//#region src/commands/config/
|
|
21062
|
-
|
|
21063
|
-
|
|
21064
|
-
|
|
21065
|
-
|
|
21066
|
-
|
|
21067
|
-
}
|
|
21068
|
-
|
|
21069
|
-
|
|
21070
|
-
|
|
21071
|
-
|
|
21072
|
-
|
|
21073
|
-
|
|
21074
|
-
|
|
21075
|
-
|
|
21076
|
-
|
|
21077
|
-
|
|
21078
|
-
|
|
21079
|
-
|
|
21080
|
-
|
|
21081
|
-
|
|
21082
|
-
|
|
21083
|
-
|
|
21084
|
-
|
|
21085
|
-
|
|
21086
|
-
|
|
21087
|
-
};
|
|
21088
|
-
}
|
|
21089
|
-
return {
|
|
21090
|
-
ok: false,
|
|
21091
|
-
configPath,
|
|
21092
|
-
error: error instanceof Error ? error.message : String(error)
|
|
21093
|
-
};
|
|
21094
|
-
}
|
|
21095
|
-
}
|
|
21096
|
-
async function validateConfigCommand(args = {}) {
|
|
21097
|
-
const result = validate(tryFindConfigFile(args.configPath));
|
|
21098
|
-
if (args.json) {
|
|
21099
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
21100
|
-
return result.ok ? 0 : 1;
|
|
21101
|
-
}
|
|
21102
|
-
intro("Validate Config");
|
|
21103
|
-
if (result.ok) {
|
|
21104
|
-
log.success(`Config is valid: ${result.configPath}`);
|
|
21105
|
-
log.info(` ${result.keyCount} top-level keys, ${result.overrideCount} model override(s)`);
|
|
21106
|
-
outro("Ready to run `proxitor start`.");
|
|
21107
|
-
return 0;
|
|
21108
|
-
}
|
|
21109
|
-
if (!result.configPath) {
|
|
21110
|
-
log.warn("No config file found — nothing to validate.");
|
|
21111
|
-
log.info("Searched:");
|
|
21112
|
-
outro("Run `proxitor config wizard` to create one.");
|
|
21113
|
-
return 1;
|
|
21355
|
+
//#region src/commands/config/connection.ts
|
|
21356
|
+
const FIELD_MAP = {
|
|
21357
|
+
apiKey: {
|
|
21358
|
+
configKey: "openrouterKey",
|
|
21359
|
+
label: "API key",
|
|
21360
|
+
ask: (cfg) => askApiKey(cfg.openrouterKey ?? DEFAULTS.openrouterKey)
|
|
21361
|
+
},
|
|
21362
|
+
port: {
|
|
21363
|
+
configKey: "port",
|
|
21364
|
+
label: "Proxy port",
|
|
21365
|
+
ask: (cfg) => askPort(cfg.port ?? DEFAULTS.port)
|
|
21366
|
+
},
|
|
21367
|
+
host: {
|
|
21368
|
+
configKey: "host",
|
|
21369
|
+
label: "Listen address",
|
|
21370
|
+
ask: (cfg) => askHost(cfg.host ?? DEFAULTS.host)
|
|
21371
|
+
},
|
|
21372
|
+
baseUrl: {
|
|
21373
|
+
configKey: "openrouterBaseUrl",
|
|
21374
|
+
label: "Base URL",
|
|
21375
|
+
ask: (cfg) => askBaseUrl(cfg.openrouterBaseUrl ?? DEFAULTS.openrouterBaseUrl)
|
|
21376
|
+
},
|
|
21377
|
+
authType: {
|
|
21378
|
+
configKey: "authType",
|
|
21379
|
+
label: "Auth type",
|
|
21380
|
+
ask: (cfg) => askAuthType(cfg.authType ?? DEFAULTS.authType)
|
|
21114
21381
|
}
|
|
21115
|
-
|
|
21116
|
-
|
|
21117
|
-
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
log.info(" • Or run `proxitor doctor` for a fuller diagnostic");
|
|
21122
|
-
} else log.error(result.error);
|
|
21123
|
-
outro("Fix the issues and re-run `proxitor config validate`.");
|
|
21124
|
-
return 1;
|
|
21125
|
-
}
|
|
21126
|
-
//#endregion
|
|
21127
|
-
//#region src/commands/config.ts
|
|
21128
|
-
/** Run the interactive config manager menu. */
|
|
21129
|
-
async function runConfigMenu(client) {
|
|
21130
|
-
intro("Proxitor Config Manager");
|
|
21131
|
-
const action = await select({
|
|
21132
|
-
message: "What would you like to do?",
|
|
21382
|
+
};
|
|
21383
|
+
async function connectionMenuCommand(opts) {
|
|
21384
|
+
const configPath = requireConfigPath(opts?.configPath);
|
|
21385
|
+
const cfg = readConfigFile(configPath);
|
|
21386
|
+
const field = await select({
|
|
21387
|
+
message: "Change which field?",
|
|
21133
21388
|
options: [
|
|
21134
21389
|
{
|
|
21135
|
-
value: "
|
|
21136
|
-
label: "
|
|
21390
|
+
value: "apiKey",
|
|
21391
|
+
label: "API key",
|
|
21392
|
+
hint: maskKey(cfg.openrouterKey ?? "")
|
|
21137
21393
|
},
|
|
21138
21394
|
{
|
|
21139
|
-
value: "
|
|
21140
|
-
label: "
|
|
21395
|
+
value: "port",
|
|
21396
|
+
label: "Proxy port",
|
|
21397
|
+
hint: String(cfg.port ?? DEFAULTS.port)
|
|
21141
21398
|
},
|
|
21142
21399
|
{
|
|
21143
|
-
value: "
|
|
21144
|
-
label: "
|
|
21400
|
+
value: "host",
|
|
21401
|
+
label: "Listen address",
|
|
21402
|
+
hint: cfg.host ?? DEFAULTS.host
|
|
21145
21403
|
},
|
|
21146
21404
|
{
|
|
21147
|
-
value: "
|
|
21148
|
-
label: "
|
|
21405
|
+
value: "baseUrl",
|
|
21406
|
+
label: "Base URL"
|
|
21149
21407
|
},
|
|
21150
21408
|
{
|
|
21151
|
-
value: "
|
|
21152
|
-
label: "
|
|
21409
|
+
value: "authType",
|
|
21410
|
+
label: "Auth type",
|
|
21411
|
+
hint: cfg.authType ?? DEFAULTS.authType
|
|
21153
21412
|
},
|
|
21154
21413
|
{
|
|
21155
|
-
value: "
|
|
21156
|
-
label: "
|
|
21157
|
-
},
|
|
21158
|
-
{
|
|
21159
|
-
value: "exit",
|
|
21160
|
-
label: "❌ Exit"
|
|
21414
|
+
value: "back",
|
|
21415
|
+
label: "← Back"
|
|
21161
21416
|
}
|
|
21162
21417
|
]
|
|
21163
21418
|
});
|
|
21164
|
-
if (isCancel(
|
|
21165
|
-
|
|
21166
|
-
|
|
21167
|
-
|
|
21168
|
-
|
|
21169
|
-
|
|
21170
|
-
|
|
21171
|
-
|
|
21172
|
-
|
|
21173
|
-
|
|
21174
|
-
|
|
21175
|
-
|
|
21176
|
-
|
|
21177
|
-
|
|
21178
|
-
|
|
21179
|
-
|
|
21180
|
-
|
|
21181
|
-
|
|
21182
|
-
|
|
21183
|
-
|
|
21184
|
-
|
|
21185
|
-
|
|
21186
|
-
|
|
21419
|
+
if (isCancel(field) || field === "back") return;
|
|
21420
|
+
const entry = FIELD_MAP[field];
|
|
21421
|
+
if (!entry) return;
|
|
21422
|
+
const result = await entry.ask(cfg);
|
|
21423
|
+
if (result === null) return;
|
|
21424
|
+
setGlobalConfigField(configPath, entry.configKey, result);
|
|
21425
|
+
log.success(`${entry.label} updated`);
|
|
21426
|
+
}
|
|
21427
|
+
//#endregion
|
|
21428
|
+
//#region src/commands/config/session-routing.ts
|
|
21429
|
+
async function sessionRoutingCommand(opts) {
|
|
21430
|
+
const configPath = requireConfigPath(opts?.configPath);
|
|
21431
|
+
const current = readConfigFile(configPath).sessionId ?? DEFAULTS.sessionId;
|
|
21432
|
+
log.info(`Current: sessionId = ${current}`);
|
|
21433
|
+
const result = await askTriState("Session routing mode", current, SESSION_HINTS);
|
|
21434
|
+
if (result === null) return;
|
|
21435
|
+
setGlobalConfigField(configPath, "sessionId", result);
|
|
21436
|
+
log.success(`sessionId set to ${result}`);
|
|
21437
|
+
}
|
|
21438
|
+
//#endregion
|
|
21439
|
+
//#region src/commands/config.ts
|
|
21440
|
+
async function runConfigMenu(client) {
|
|
21441
|
+
intro("Proxitor Config Manager");
|
|
21442
|
+
while (true) {
|
|
21443
|
+
const action = await select({
|
|
21444
|
+
message: "What would you like to do?",
|
|
21445
|
+
options: [
|
|
21446
|
+
{
|
|
21447
|
+
value: "show",
|
|
21448
|
+
label: "📋 Show current config"
|
|
21449
|
+
},
|
|
21450
|
+
{
|
|
21451
|
+
value: "_sep1",
|
|
21452
|
+
label: "── Global Settings ──",
|
|
21453
|
+
disabled: true
|
|
21454
|
+
},
|
|
21455
|
+
{
|
|
21456
|
+
value: "connection",
|
|
21457
|
+
label: "🔑 API key & connection"
|
|
21458
|
+
},
|
|
21459
|
+
{
|
|
21460
|
+
value: "session",
|
|
21461
|
+
label: "🔗 Session routing"
|
|
21462
|
+
},
|
|
21463
|
+
{
|
|
21464
|
+
value: "cache",
|
|
21465
|
+
label: "💾 Cache control"
|
|
21466
|
+
},
|
|
21467
|
+
{
|
|
21468
|
+
value: "_sep2",
|
|
21469
|
+
label: "── Model Overrides ──",
|
|
21470
|
+
disabled: true
|
|
21471
|
+
},
|
|
21472
|
+
{
|
|
21473
|
+
value: "add",
|
|
21474
|
+
label: "➕ Add model override"
|
|
21475
|
+
},
|
|
21476
|
+
{
|
|
21477
|
+
value: "edit",
|
|
21478
|
+
label: "✏️ Edit model override"
|
|
21479
|
+
},
|
|
21480
|
+
{
|
|
21481
|
+
value: "remove",
|
|
21482
|
+
label: "🗑 Remove model override"
|
|
21483
|
+
},
|
|
21484
|
+
{
|
|
21485
|
+
value: "list",
|
|
21486
|
+
label: "📄 List current overrides"
|
|
21487
|
+
},
|
|
21488
|
+
{
|
|
21489
|
+
value: "browse",
|
|
21490
|
+
label: "🔍 Browse models"
|
|
21491
|
+
},
|
|
21492
|
+
{
|
|
21493
|
+
value: "_sep3",
|
|
21494
|
+
label: "── Tools ──",
|
|
21495
|
+
disabled: true
|
|
21496
|
+
},
|
|
21497
|
+
{
|
|
21498
|
+
value: "validate",
|
|
21499
|
+
label: "✅ Validate config"
|
|
21500
|
+
},
|
|
21501
|
+
{
|
|
21502
|
+
value: "exit",
|
|
21503
|
+
label: "❌ Exit"
|
|
21504
|
+
}
|
|
21505
|
+
]
|
|
21506
|
+
});
|
|
21507
|
+
if (isCancel(action) || action === "exit") {
|
|
21508
|
+
outro("Bye!");
|
|
21509
|
+
return;
|
|
21510
|
+
}
|
|
21511
|
+
switch (action) {
|
|
21512
|
+
case "show":
|
|
21513
|
+
await showConfigCommand({});
|
|
21514
|
+
break;
|
|
21515
|
+
case "connection":
|
|
21516
|
+
await connectionMenuCommand();
|
|
21517
|
+
break;
|
|
21518
|
+
case "session":
|
|
21519
|
+
await sessionRoutingCommand();
|
|
21520
|
+
break;
|
|
21521
|
+
case "cache":
|
|
21522
|
+
await cacheControlCommand();
|
|
21523
|
+
break;
|
|
21524
|
+
case "add":
|
|
21525
|
+
await addOverrideCommand({ client });
|
|
21526
|
+
break;
|
|
21527
|
+
case "edit":
|
|
21528
|
+
await editOverrideCommand(client);
|
|
21529
|
+
break;
|
|
21530
|
+
case "remove":
|
|
21531
|
+
await removeOverrideCommand();
|
|
21532
|
+
break;
|
|
21533
|
+
case "list":
|
|
21534
|
+
await listOverridesCommand();
|
|
21535
|
+
break;
|
|
21536
|
+
case "browse":
|
|
21537
|
+
await browseModelsCommand(client);
|
|
21538
|
+
break;
|
|
21539
|
+
case "validate":
|
|
21540
|
+
await validateConfigCommand();
|
|
21541
|
+
break;
|
|
21542
|
+
}
|
|
21187
21543
|
}
|
|
21188
21544
|
}
|
|
21189
21545
|
//#endregion
|
|
21190
21546
|
//#region src/version.ts
|
|
21191
|
-
const version = "0.9.0-beta.
|
|
21547
|
+
const version = "0.9.0-beta.3";
|
|
21192
21548
|
//#endregion
|
|
21193
21549
|
//#region src/commands/doctor.ts
|
|
21194
21550
|
const DEFAULT_TIMEOUT_MS = 3e3;
|