proxitor 0.4.0 → 0.5.1
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 -1
- package/dist/add.mjs +2 -3
- package/dist/add.mjs.map +1 -1
- package/dist/browse.mjs +3 -4
- package/dist/browse.mjs.map +1 -1
- package/dist/cli.mjs +80 -58
- package/dist/cli.mjs.map +1 -1
- package/dist/config2.mjs +4 -4
- package/dist/config2.mjs.map +1 -1
- package/dist/dist.mjs +1 -1
- package/dist/dist2.mjs +1 -1
- package/dist/edit.mjs +4 -5
- package/dist/edit.mjs.map +1 -1
- package/dist/providers.mjs +1 -30
- package/dist/providers.mjs.map +1 -1
- package/dist/wizard.mjs +92 -28
- package/dist/wizard.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,8 +109,20 @@ proxitor.config.yaml → proxitor.config.yml → proxitor.config.json
|
|
|
109
109
|
|
|
110
110
|
**Priority:** CLI flags > config file > environment variables > defaults
|
|
111
111
|
|
|
112
|
+
All defaults are derived from a single Zod schema (`DEFAULTS`) — no hardcoded constants scattered across modules. Config values are validated through Zod on load, including the final merged result.
|
|
113
|
+
|
|
112
114
|
See [`proxitor.config.example.yaml`](./proxitor.config.example.yaml) for the complete reference.
|
|
113
115
|
|
|
116
|
+
### Authentication type
|
|
117
|
+
|
|
118
|
+
By default, proxitor sends the API key as a `Bearer` token (`Authorization: Bearer sk-...`). If you're using a custom proxy provider that expects an `OAuth` header instead, set `authType` to `oauth`:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
authType: oauth # "bearer" (default) or "oauth"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
This changes the header to `Authorization: OAuth sk-...`.
|
|
125
|
+
|
|
114
126
|
### Provider routing
|
|
115
127
|
|
|
116
128
|
Control which provider handles your requests. All three options accept a string or an array:
|
|
@@ -245,6 +257,8 @@ The wizard asks for:
|
|
|
245
257
|
|
|
246
258
|
- **OpenRouter API key** — stored in config or set as `OPENROUTER_API_KEY` env var
|
|
247
259
|
- **Port** — default `8828` (avoids conflicts with common dev servers on 8080)
|
|
260
|
+
- **API base URL** — default `https://openrouter.ai/api/v1`; change for self-hosted or custom endpoints
|
|
261
|
+
- **Authentication type** — `bearer` (default) or `oauth`; use `oauth` for custom proxy providers that pass tokens in the `Authorization: OAuth ...` header
|
|
248
262
|
- **Host** — all interfaces (`0.0.0.0`) or localhost only (`127.0.0.1`)
|
|
249
263
|
- **Save location** — project directory, `~/.config/proxitor/`, or `$XDG_CONFIG_HOME/proxitor/`
|
|
250
264
|
|
|
@@ -332,6 +346,7 @@ The interface uses live data from the OpenRouter API — model search with type-
|
|
|
332
346
|
| `-c, --config <path>` | auto-discovered | Path to config file |
|
|
333
347
|
| `--openrouter-key <key>` | `$OPENROUTER_API_KEY` | OpenRouter API key |
|
|
334
348
|
| `--verbose` | `false` | Enable verbose logging |
|
|
349
|
+
| `--no-config` | | Skip config file discovery |
|
|
335
350
|
| `-v, --version` | | Print version |
|
|
336
351
|
| `--help` | | Print help |
|
|
337
352
|
|
|
@@ -355,4 +370,4 @@ pnpm check # typecheck + biome + test (full CI)
|
|
|
355
370
|
|
|
356
371
|
## License
|
|
357
372
|
|
|
358
|
-
[MIT](./LICENSE)
|
|
373
|
+
[MIT](./LICENSE)
|
package/dist/add.mjs
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { autocomplete as Vt, confirm as le, intro as ye, isCancel as R$1, log as R, outro as fe, spinner as vt, text as Re } from "./dist.mjs";
|
|
2
|
-
import { c as formatModelHint, f as fetchModels,
|
|
2
|
+
import { c as formatModelHint, f as fetchModels, l as formatModelLabel, n as selectProvidersByMode, o as formatContextLength, p as formatPrice, r as selectRoutingMode, t as fetchProvidersForModel, u as formatPricing } from "./providers.mjs";
|
|
3
3
|
import { i as setModelOverride, r as requireConfigPath, t as getModelOverrides } from "./config.mjs";
|
|
4
4
|
//#region src/commands/config/add.ts
|
|
5
5
|
const CUSTOM_PATTERN = "__custom_pattern__";
|
|
6
6
|
/** Run the interactive "Add model override" flow. */
|
|
7
|
-
async function addOverrideCommand(
|
|
7
|
+
async function addOverrideCommand(client) {
|
|
8
8
|
ye("Add Model Override");
|
|
9
9
|
const configPath = requireConfigPath();
|
|
10
|
-
const client = new OpenRouterClient(apiKey);
|
|
11
10
|
const models = await loadModelsWithSpinner(client);
|
|
12
11
|
if (!models) return;
|
|
13
12
|
const modelId = await searchModel(models);
|
package/dist/add.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add.mjs","names":["clack.spinner","clack.autocomplete","isCancel","clack.text","clack.confirm"],"sources":["../src/commands/config/add.ts"],"sourcesContent":["import * as clack from '@clack/prompts'\nimport { isCancel } from '@clack/prompts'\nimport { OpenRouterClient } from '../../openrouter/client.js'\nimport { fetchModels, formatPrice } from '../../openrouter/models.js'\nimport type { OpenRouterModel } from '../../openrouter/types.js'\nimport { getModelOverrides, requireConfigPath, setModelOverride } from './config.js'\nimport {\n formatContextLength,\n formatModelHint,\n formatModelLabel,\n formatPricing,\n} from './format.js'\nimport {\n fetchProvidersForModel,\n selectProvidersByMode,\n selectRoutingMode,\n} from './providers.js'\n\nconst CUSTOM_PATTERN = '__custom_pattern__'\n\n/** Run the interactive \"Add model override\" flow. */\nexport async function addOverrideCommand(apiKey: string): Promise<void> {\n clack.intro('Add Model Override')\n\n const configPath = requireConfigPath()\n const client = new OpenRouterClient(apiKey)\n\n const models = await loadModelsWithSpinner(client)\n if (!models) return\n\n const modelId = await searchModel(models)\n if (!modelId) return\n\n if (typeof modelId !== 'string') return\n\n if (modelId === CUSTOM_PATTERN) {\n const pattern = await enterPattern(models)\n if (!pattern) return\n\n const existing = getModelOverrides(configPath)\n if (existing[pattern]) {\n clack.log.warn(`Override for \"${pattern}\" already exists. Use Edit instead.`)\n return\n }\n\n await configureProviderAndSave(configPath, client, pattern, true)\n return\n }\n\n const selected = models.find(m => m.id === modelId)\n if (selected) displayModelInfo(selected)\n\n const existing = getModelOverrides(configPath)\n if (existing[modelId]) {\n clack.log.warn(`Override for \"${modelId}\" already exists. Use Edit instead.`)\n return\n }\n\n await configureProviderAndSave(configPath, client, modelId, false)\n}\n\nasync function loadModelsWithSpinner(\n client: OpenRouterClient,\n): Promise<OpenRouterModel[] | null> {\n const s = clack.spinner()\n s.start('Loading models from OpenRouter...')\n try {\n const models = await fetchModels(client)\n s.stop(`${models.length} models available`)\n return models\n } catch (error) {\n s.stop('Failed to load models')\n clack.log.error(String(error))\n return null\n }\n}\n\nasync function searchModel(models: OpenRouterModel[]): Promise<string | symbol | null> {\n const result = await clack.autocomplete({\n message: 'Search for a model',\n placeholder: 'Type to search (e.g. \"claude\", \"gpt-4o\", \"qwen\")',\n maxItems: 15,\n options(this: { userInput: string }) {\n const query = this.userInput.trim().toLowerCase()\n\n if (!query) {\n return [\n {\n value: CUSTOM_PATTERN,\n label: '✏️ Enter custom pattern (e.g. \"claude-*\")',\n },\n ]\n }\n\n const filtered = models\n .filter(m => {\n const text = `${m.id} ${m.name}`.toLowerCase()\n return text.includes(query)\n })\n .slice(0, 14)\n .map(m => ({\n value: m.id,\n label: formatModelLabel(m),\n hint: formatModelHint(m),\n }))\n\n return [\n ...filtered,\n { value: CUSTOM_PATTERN, label: '✏️ Enter custom pattern (e.g. \"claude-*\")' },\n ]\n },\n filter: (_search: string, _option: { value: string }) => true,\n })\n\n if (isCancel(result)) return null\n return result as string\n}\n\nasync function enterPattern(models: OpenRouterModel[]): Promise<string | null> {\n const pattern = await clack.text({\n message: 'Enter model pattern',\n placeholder: 'e.g. claude-*, gpt-4*, anthropic/*',\n validate: v => {\n if (!v?.trim()) return 'Pattern cannot be empty'\n return undefined\n },\n })\n\n if (isCancel(pattern)) return null\n\n const pat = (pattern as string).trim()\n const matches = countPatternMatches(pat, models)\n if (matches > 0) {\n clack.log.info(`Pattern \"${pat}\" matches ${matches} model(s)`)\n } else {\n clack.log.warn(\n `Pattern \"${pat}\" does not match any current models — it will still be saved`,\n )\n }\n\n return pat\n}\n\nasync function configureProviderAndSave(\n configPath: string,\n client: OpenRouterClient,\n modelKey: string,\n isPattern: boolean,\n): Promise<void> {\n const mode = await selectRoutingMode('Configure provider routing')\n if (isCancel(mode)) return\n\n if (mode === 'skip') {\n setModelOverride(configPath, modelKey, {})\n clack.outro('Done — override saved without provider routing')\n return\n }\n\n const providerOptions = await fetchProvidersForModel(client, modelKey, isPattern)\n if (!providerOptions) return\n\n const override = await selectProvidersByMode(mode as string, providerOptions)\n if (!override) return\n\n clack.log.info(\n `Proposed override:\\n ${modelKey}:\\n ${formatOverrideYaml(override)}`,\n )\n\n const save = await clack.confirm({ message: 'Save to config?' })\n if (isCancel(save) || !save) {\n clack.outro('Cancelled')\n return\n }\n\n setModelOverride(configPath, modelKey, override)\n clack.outro('✓ Model override saved')\n}\n\nfunction displayModelInfo(model: OpenRouterModel): void {\n clack.log.info(`${model.name || model.id}`)\n clack.log.info(` Context: ${formatContextLength(model.context_length)} tokens`)\n clack.log.info(\n ` Pricing: ${formatPricing(model.pricing.prompt, model.pricing.completion)}`,\n )\n if (model.pricing.input_cache_read && model.pricing.input_cache_read !== '0') {\n clack.log.info(` Cache read: ${formatPrice(model.pricing.input_cache_read)}`)\n }\n if (model.pricing.input_cache_write && model.pricing.input_cache_write !== '0') {\n clack.log.info(` Cache write: ${formatPrice(model.pricing.input_cache_write)}`)\n }\n if (model.top_provider?.max_completion_tokens) {\n clack.log.info(\n ` Max output: ${formatContextLength(model.top_provider.max_completion_tokens)} tokens`,\n )\n }\n if (model.architecture?.modality) {\n clack.log.info(` Modality: ${model.architecture.modality}`)\n }\n}\n\nfunction countPatternMatches(pattern: string, models: OpenRouterModel[]): number {\n if (pattern.endsWith('*')) {\n const prefix = pattern.slice(0, -1)\n return models.filter(m => m.id.startsWith(prefix)).length\n }\n return models.filter(m => m.id === pattern).length\n}\n\nfunction formatOverrideYaml(override: Record<string, unknown>): string {\n const parts: string[] = []\n if (override.provider && typeof override.provider === 'object') {\n const p = override.provider as Record<string, unknown>\n for (const [key, value] of Object.entries(p)) {\n parts.push(`provider.${key}: ${JSON.stringify(value)}`)\n }\n }\n return parts.join('\\n ') || '(empty)'\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"add.mjs","names":["clack.spinner","clack.autocomplete","isCancel","clack.text","clack.confirm"],"sources":["../src/commands/config/add.ts"],"sourcesContent":["import * as clack from '@clack/prompts'\nimport { isCancel } from '@clack/prompts'\nimport type { OpenRouterClient } from '../../openrouter/client.js'\nimport { fetchModels, formatPrice } from '../../openrouter/models.js'\nimport type { OpenRouterModel } from '../../openrouter/types.js'\nimport { getModelOverrides, requireConfigPath, setModelOverride } from './config.js'\nimport {\n formatContextLength,\n formatModelHint,\n formatModelLabel,\n formatPricing,\n} from './format.js'\nimport {\n fetchProvidersForModel,\n selectProvidersByMode,\n selectRoutingMode,\n} from './providers.js'\n\nconst CUSTOM_PATTERN = '__custom_pattern__'\n\n/** Run the interactive \"Add model override\" flow. */\nexport async function addOverrideCommand(client: OpenRouterClient): Promise<void> {\n clack.intro('Add Model Override')\n\n const configPath = requireConfigPath()\n\n const models = await loadModelsWithSpinner(client)\n if (!models) return\n\n const modelId = await searchModel(models)\n if (!modelId) return\n\n if (typeof modelId !== 'string') return\n\n if (modelId === CUSTOM_PATTERN) {\n const pattern = await enterPattern(models)\n if (!pattern) return\n\n const existing = getModelOverrides(configPath)\n if (existing[pattern]) {\n clack.log.warn(`Override for \"${pattern}\" already exists. Use Edit instead.`)\n return\n }\n\n await configureProviderAndSave(configPath, client, pattern, true)\n return\n }\n\n const selected = models.find(m => m.id === modelId)\n if (selected) displayModelInfo(selected)\n\n const existing = getModelOverrides(configPath)\n if (existing[modelId]) {\n clack.log.warn(`Override for \"${modelId}\" already exists. Use Edit instead.`)\n return\n }\n\n await configureProviderAndSave(configPath, client, modelId, false)\n}\n\nasync function loadModelsWithSpinner(\n client: OpenRouterClient,\n): Promise<OpenRouterModel[] | null> {\n const s = clack.spinner()\n s.start('Loading models from OpenRouter...')\n try {\n const models = await fetchModels(client)\n s.stop(`${models.length} models available`)\n return models\n } catch (error) {\n s.stop('Failed to load models')\n clack.log.error(String(error))\n return null\n }\n}\n\nasync function searchModel(models: OpenRouterModel[]): Promise<string | symbol | null> {\n const result = await clack.autocomplete({\n message: 'Search for a model',\n placeholder: 'Type to search (e.g. \"claude\", \"gpt-4o\", \"qwen\")',\n maxItems: 15,\n options(this: { userInput: string }) {\n const query = this.userInput.trim().toLowerCase()\n\n if (!query) {\n return [\n {\n value: CUSTOM_PATTERN,\n label: '✏️ Enter custom pattern (e.g. \"claude-*\")',\n },\n ]\n }\n\n const filtered = models\n .filter(m => {\n const text = `${m.id} ${m.name}`.toLowerCase()\n return text.includes(query)\n })\n .slice(0, 14)\n .map(m => ({\n value: m.id,\n label: formatModelLabel(m),\n hint: formatModelHint(m),\n }))\n\n return [\n ...filtered,\n { value: CUSTOM_PATTERN, label: '✏️ Enter custom pattern (e.g. \"claude-*\")' },\n ]\n },\n filter: (_search: string, _option: { value: string }) => true,\n })\n\n if (isCancel(result)) return null\n return result as string\n}\n\nasync function enterPattern(models: OpenRouterModel[]): Promise<string | null> {\n const pattern = await clack.text({\n message: 'Enter model pattern',\n placeholder: 'e.g. claude-*, gpt-4*, anthropic/*',\n validate: v => {\n if (!v?.trim()) return 'Pattern cannot be empty'\n return undefined\n },\n })\n\n if (isCancel(pattern)) return null\n\n const pat = (pattern as string).trim()\n const matches = countPatternMatches(pat, models)\n if (matches > 0) {\n clack.log.info(`Pattern \"${pat}\" matches ${matches} model(s)`)\n } else {\n clack.log.warn(\n `Pattern \"${pat}\" does not match any current models — it will still be saved`,\n )\n }\n\n return pat\n}\n\nasync function configureProviderAndSave(\n configPath: string,\n client: OpenRouterClient,\n modelKey: string,\n isPattern: boolean,\n): Promise<void> {\n const mode = await selectRoutingMode('Configure provider routing')\n if (isCancel(mode)) return\n\n if (mode === 'skip') {\n setModelOverride(configPath, modelKey, {})\n clack.outro('Done — override saved without provider routing')\n return\n }\n\n const providerOptions = await fetchProvidersForModel(client, modelKey, isPattern)\n if (!providerOptions) return\n\n const override = await selectProvidersByMode(mode as string, providerOptions)\n if (!override) return\n\n clack.log.info(\n `Proposed override:\\n ${modelKey}:\\n ${formatOverrideYaml(override)}`,\n )\n\n const save = await clack.confirm({ message: 'Save to config?' })\n if (isCancel(save) || !save) {\n clack.outro('Cancelled')\n return\n }\n\n setModelOverride(configPath, modelKey, override)\n clack.outro('✓ Model override saved')\n}\n\nfunction displayModelInfo(model: OpenRouterModel): void {\n clack.log.info(`${model.name || model.id}`)\n clack.log.info(` Context: ${formatContextLength(model.context_length)} tokens`)\n clack.log.info(\n ` Pricing: ${formatPricing(model.pricing.prompt, model.pricing.completion)}`,\n )\n if (model.pricing.input_cache_read && model.pricing.input_cache_read !== '0') {\n clack.log.info(` Cache read: ${formatPrice(model.pricing.input_cache_read)}`)\n }\n if (model.pricing.input_cache_write && model.pricing.input_cache_write !== '0') {\n clack.log.info(` Cache write: ${formatPrice(model.pricing.input_cache_write)}`)\n }\n if (model.top_provider?.max_completion_tokens) {\n clack.log.info(\n ` Max output: ${formatContextLength(model.top_provider.max_completion_tokens)} tokens`,\n )\n }\n if (model.architecture?.modality) {\n clack.log.info(` Modality: ${model.architecture.modality}`)\n }\n}\n\nfunction countPatternMatches(pattern: string, models: OpenRouterModel[]): number {\n if (pattern.endsWith('*')) {\n const prefix = pattern.slice(0, -1)\n return models.filter(m => m.id.startsWith(prefix)).length\n }\n return models.filter(m => m.id === pattern).length\n}\n\nfunction formatOverrideYaml(override: Record<string, unknown>): string {\n const parts: string[] = []\n if (override.provider && typeof override.provider === 'object') {\n const p = override.provider as Record<string, unknown>\n for (const [key, value] of Object.entries(p)) {\n parts.push(`provider.${key}: ${JSON.stringify(value)}`)\n }\n }\n return parts.join('\\n ') || '(empty)'\n}\n"],"mappings":"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"}
|
package/dist/browse.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { autocomplete as Vt, confirm as le, intro as ye, isCancel as R$1, log as R, outro as fe, spinner as vt } from "./dist.mjs";
|
|
2
|
-
import { a as getUniqueProviders, c as formatModelHint, d as formatThroughput, f as fetchModels,
|
|
2
|
+
import { a as getUniqueProviders, c as formatModelHint, d as formatThroughput, f as fetchModels, h as parseModelSlug, i as fetchModelEndpoints, l as formatModelLabel, m as parseModelAuthor, o as formatContextLength, p as formatPrice, s as formatLatency, u as formatPricing } from "./providers.mjs";
|
|
3
3
|
import { addOverrideCommand } from "./add.mjs";
|
|
4
4
|
//#region src/commands/config/browse.ts
|
|
5
5
|
function toOption(m) {
|
|
@@ -43,9 +43,8 @@ async function displayProviders(client, model) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
/** Run the interactive "Browse models" flow. */
|
|
46
|
-
async function browseModelsCommand(
|
|
46
|
+
async function browseModelsCommand(client) {
|
|
47
47
|
ye("Browse Models");
|
|
48
|
-
const client = new OpenRouterClient(apiKey);
|
|
49
48
|
const s = vt();
|
|
50
49
|
s.start("Loading models...");
|
|
51
50
|
let models;
|
|
@@ -78,7 +77,7 @@ async function browseModelsCommand(apiKey) {
|
|
|
78
77
|
fe("Bye!");
|
|
79
78
|
return;
|
|
80
79
|
}
|
|
81
|
-
await addOverrideCommand(
|
|
80
|
+
await addOverrideCommand(client);
|
|
82
81
|
}
|
|
83
82
|
//#endregion
|
|
84
83
|
export { browseModelsCommand };
|
package/dist/browse.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browse.mjs","names":["clack.spinner","clack.autocomplete","isCancel","clack.confirm"],"sources":["../src/commands/config/browse.ts"],"sourcesContent":["import * as clack from '@clack/prompts'\nimport { isCancel } from '@clack/prompts'\nimport { OpenRouterClient } from '../../openrouter/client.js'\nimport { fetchModelEndpoints, getUniqueProviders } from '../../openrouter/endpoints.js'\nimport {\n fetchModels,\n formatPrice,\n parseModelAuthor,\n parseModelSlug,\n} from '../../openrouter/models.js'\nimport type { OpenRouterModel } from '../../openrouter/types.js'\nimport { addOverrideCommand } from './add.js'\nimport {\n formatContextLength,\n formatLatency,\n formatModelHint,\n formatModelLabel,\n formatPricing,\n formatThroughput,\n} from './format.js'\n\nfunction toOption(m: OpenRouterModel) {\n return { value: m.id, label: formatModelLabel(m), hint: formatModelHint(m) }\n}\n\nfunction displayModelDetails(model: OpenRouterModel): void {\n clack.log.success(`${model.name || model.id}`)\n if (model.description) {\n const desc =\n model.description.length > 200\n ? `${model.description.slice(0, 200)}...`\n : model.description\n clack.log.info(` ${desc}`)\n }\n clack.log.info(` Context: ${formatContextLength(model.context_length)} tokens`)\n if (model.top_provider?.max_completion_tokens) {\n clack.log.info(\n ` Max output: ${formatContextLength(model.top_provider.max_completion_tokens)} tokens`,\n )\n }\n clack.log.info(\n ` Pricing: ${formatPricing(model.pricing.prompt, model.pricing.completion)}`,\n )\n if (model.pricing.input_cache_read && model.pricing.input_cache_read !== '0') {\n clack.log.info(` Cache read: ${formatPrice(model.pricing.input_cache_read)}`)\n }\n if (model.pricing.input_cache_write && model.pricing.input_cache_write !== '0') {\n clack.log.info(` Cache write: ${formatPrice(model.pricing.input_cache_write)}`)\n }\n if (model.architecture?.modality) {\n clack.log.info(` Modality: ${model.architecture.modality}`)\n }\n if (model.supported_parameters?.length) {\n clack.log.info(` Parameters: ${model.supported_parameters.join(', ')}`)\n }\n}\n\nasync function displayProviders(\n client: OpenRouterClient,\n model: OpenRouterModel,\n): Promise<void> {\n const author = parseModelAuthor(model.id)\n const slug = parseModelSlug(model.id)\n const s = clack.spinner()\n s.start('Checking providers...')\n try {\n const endpoints = await fetchModelEndpoints(client, author, slug)\n const unique = getUniqueProviders(endpoints)\n s.stop(`${unique.length} providers available`)\n\n for (const p of unique) {\n const ep = endpoints.find(e => e.tag === p.tag)\n const latency = formatLatency(ep?.latency_last_30m?.p50 ?? null)\n const throughput = formatThroughput(ep?.throughput_last_30m?.p50 ?? null)\n clack.log.info(` ${p.providerName} (${p.tag}) — ${latency} · ${throughput}`)\n }\n } catch {\n s.stop('Could not fetch providers')\n }\n}\n\n/** Run the interactive \"Browse models\" flow. */\nexport async function browseModelsCommand(
|
|
1
|
+
{"version":3,"file":"browse.mjs","names":["clack.spinner","clack.autocomplete","isCancel","clack.confirm"],"sources":["../src/commands/config/browse.ts"],"sourcesContent":["import * as clack from '@clack/prompts'\nimport { isCancel } from '@clack/prompts'\nimport type { OpenRouterClient } from '../../openrouter/client.js'\nimport { fetchModelEndpoints, getUniqueProviders } from '../../openrouter/endpoints.js'\nimport {\n fetchModels,\n formatPrice,\n parseModelAuthor,\n parseModelSlug,\n} from '../../openrouter/models.js'\nimport type { OpenRouterModel } from '../../openrouter/types.js'\nimport { addOverrideCommand } from './add.js'\nimport {\n formatContextLength,\n formatLatency,\n formatModelHint,\n formatModelLabel,\n formatPricing,\n formatThroughput,\n} from './format.js'\n\nfunction toOption(m: OpenRouterModel) {\n return { value: m.id, label: formatModelLabel(m), hint: formatModelHint(m) }\n}\n\nfunction displayModelDetails(model: OpenRouterModel): void {\n clack.log.success(`${model.name || model.id}`)\n if (model.description) {\n const desc =\n model.description.length > 200\n ? `${model.description.slice(0, 200)}...`\n : model.description\n clack.log.info(` ${desc}`)\n }\n clack.log.info(` Context: ${formatContextLength(model.context_length)} tokens`)\n if (model.top_provider?.max_completion_tokens) {\n clack.log.info(\n ` Max output: ${formatContextLength(model.top_provider.max_completion_tokens)} tokens`,\n )\n }\n clack.log.info(\n ` Pricing: ${formatPricing(model.pricing.prompt, model.pricing.completion)}`,\n )\n if (model.pricing.input_cache_read && model.pricing.input_cache_read !== '0') {\n clack.log.info(` Cache read: ${formatPrice(model.pricing.input_cache_read)}`)\n }\n if (model.pricing.input_cache_write && model.pricing.input_cache_write !== '0') {\n clack.log.info(` Cache write: ${formatPrice(model.pricing.input_cache_write)}`)\n }\n if (model.architecture?.modality) {\n clack.log.info(` Modality: ${model.architecture.modality}`)\n }\n if (model.supported_parameters?.length) {\n clack.log.info(` Parameters: ${model.supported_parameters.join(', ')}`)\n }\n}\n\nasync function displayProviders(\n client: OpenRouterClient,\n model: OpenRouterModel,\n): Promise<void> {\n const author = parseModelAuthor(model.id)\n const slug = parseModelSlug(model.id)\n const s = clack.spinner()\n s.start('Checking providers...')\n try {\n const endpoints = await fetchModelEndpoints(client, author, slug)\n const unique = getUniqueProviders(endpoints)\n s.stop(`${unique.length} providers available`)\n\n for (const p of unique) {\n const ep = endpoints.find(e => e.tag === p.tag)\n const latency = formatLatency(ep?.latency_last_30m?.p50 ?? null)\n const throughput = formatThroughput(ep?.throughput_last_30m?.p50 ?? null)\n clack.log.info(` ${p.providerName} (${p.tag}) — ${latency} · ${throughput}`)\n }\n } catch {\n s.stop('Could not fetch providers')\n }\n}\n\n/** Run the interactive \"Browse models\" flow. */\nexport async function browseModelsCommand(client: OpenRouterClient): Promise<void> {\n clack.intro('Browse Models')\n\n const s = clack.spinner()\n s.start('Loading models...')\n let models: OpenRouterModel[]\n try {\n models = await fetchModels(client)\n s.stop(`${models.length} models available`)\n } catch (error) {\n s.stop('Failed to load models')\n clack.log.error(String(error))\n return\n }\n\n const modelId = await clack.autocomplete({\n message: 'Search for a model',\n placeholder: 'Type to search...',\n maxItems: 15,\n options(this: { userInput: string }) {\n const query = this.userInput.trim().toLowerCase()\n if (!query) return models.slice(0, 15).map(toOption)\n\n return models\n .filter(m => `${m.id} ${m.name}`.toLowerCase().includes(query))\n .slice(0, 15)\n .map(toOption)\n },\n filter: (_search: string, _option: { value: string }) => true,\n })\n\n if (isCancel(modelId)) return\n\n const model = models.find(m => m.id === modelId)\n if (!model) return\n\n displayModelDetails(model)\n await displayProviders(client, model)\n\n const configure = await clack.confirm({\n message: `Configure routing for ${model.id}?`,\n })\n\n if (isCancel(configure) || !configure) {\n clack.outro('Bye!')\n return\n }\n\n await addOverrideCommand(client)\n}\n"],"mappings":"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"}
|
package/dist/cli.mjs
CHANGED
|
@@ -10541,14 +10541,12 @@ function superRefine(fn, params) {
|
|
|
10541
10541
|
}
|
|
10542
10542
|
//#endregion
|
|
10543
10543
|
//#region src/config-schema.ts
|
|
10544
|
-
/** Percentile cutoffs for performance thresholds */
|
|
10545
10544
|
const percentileCutoffsSchema = object({
|
|
10546
10545
|
p50: number$1().positive().optional(),
|
|
10547
10546
|
p75: number$1().positive().optional(),
|
|
10548
10547
|
p90: number$1().positive().optional(),
|
|
10549
10548
|
p99: number$1().positive().optional()
|
|
10550
10549
|
}).strict();
|
|
10551
|
-
/** Provider sorting options */
|
|
10552
10550
|
const providerSortSchema = union([_enum([
|
|
10553
10551
|
"price",
|
|
10554
10552
|
"throughput",
|
|
@@ -10561,14 +10559,12 @@ const providerSortSchema = union([_enum([
|
|
|
10561
10559
|
]),
|
|
10562
10560
|
partition: _enum(["model", "none"]).optional()
|
|
10563
10561
|
}).strict()]);
|
|
10564
|
-
/** Maximum pricing for a request */
|
|
10565
10562
|
const maxPriceSchema = object({
|
|
10566
10563
|
prompt: number$1().nonnegative().optional(),
|
|
10567
10564
|
completion: number$1().nonnegative().optional(),
|
|
10568
10565
|
request: number$1().nonnegative().optional(),
|
|
10569
10566
|
image: number$1().nonnegative().optional()
|
|
10570
10567
|
}).strict();
|
|
10571
|
-
/** Provider routing configuration */
|
|
10572
10568
|
const providerConfigSchema = object({
|
|
10573
10569
|
only: union([string$1(), array(string$1())]).optional(),
|
|
10574
10570
|
order: union([string$1(), array(string$1())]).optional(),
|
|
@@ -10584,33 +10580,32 @@ const providerConfigSchema = object({
|
|
|
10584
10580
|
preferredMinThroughput: union([number$1().positive(), percentileCutoffsSchema]).optional(),
|
|
10585
10581
|
preferredMaxLatency: union([number$1().positive(), percentileCutoffsSchema]).optional()
|
|
10586
10582
|
}).strict();
|
|
10587
|
-
/** Per-model override: layers on top of global config */
|
|
10588
10583
|
const modelOverrideSchema = object({
|
|
10589
10584
|
provider: providerConfigSchema.optional(),
|
|
10590
10585
|
headers: record(string$1(), string$1()).optional()
|
|
10591
10586
|
}).strict();
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
verbose: boolean(),
|
|
10599
|
-
bodyLimit: string$1().min(1),
|
|
10587
|
+
const proxyConfigSchema = object({
|
|
10588
|
+
host: string$1().min(1).default("0.0.0.0"),
|
|
10589
|
+
port: number$1().int().min(1).max(65535).default(8828),
|
|
10590
|
+
openrouterKey: string$1().default(""),
|
|
10591
|
+
openrouterBaseUrl: string$1().url().default("https://openrouter.ai/api/v1"),
|
|
10592
|
+
authType: _enum(["bearer", "oauth"]).default("bearer"),
|
|
10593
|
+
verbose: boolean().default(false),
|
|
10594
|
+
bodyLimit: string$1().min(1).default("50mb"),
|
|
10600
10595
|
provider: providerConfigSchema.optional(),
|
|
10601
|
-
attributionReferer: string$1().min(1),
|
|
10602
|
-
attributionTitle: string$1().min(1),
|
|
10596
|
+
attributionReferer: string$1().min(1).default("http://localhost"),
|
|
10597
|
+
attributionTitle: string$1().min(1).default("proxitor"),
|
|
10603
10598
|
headers: record(string$1(), string$1()).optional(),
|
|
10604
10599
|
modelOverrides: record(string$1().min(1), modelOverrideSchema).optional()
|
|
10605
|
-
}).strict()
|
|
10606
|
-
|
|
10600
|
+
}).strict();
|
|
10601
|
+
const DEFAULTS = proxyConfigSchema.parse({});
|
|
10602
|
+
const proxyConfigFileSchema = proxyConfigSchema.partial();
|
|
10607
10603
|
var ConfigParseError = class extends Error {
|
|
10608
10604
|
constructor(filePath, cause) {
|
|
10609
10605
|
super(`Failed to parse config file ${filePath}: ${cause?.message ?? "unknown error"}`, { cause });
|
|
10610
10606
|
this.name = "ConfigParseError";
|
|
10611
10607
|
}
|
|
10612
10608
|
};
|
|
10613
|
-
/** Formats zod validation issues into a readable multi-line message */
|
|
10614
10609
|
var ConfigValidationError = class extends Error {
|
|
10615
10610
|
constructor(filePath, zodError) {
|
|
10616
10611
|
const lines = zodError.issues.map((issue) => {
|
|
@@ -10622,6 +10617,10 @@ var ConfigValidationError = class extends Error {
|
|
|
10622
10617
|
};
|
|
10623
10618
|
//#endregion
|
|
10624
10619
|
//#region src/utils.ts
|
|
10620
|
+
/** Format an API key with the appropriate auth prefix based on authType. Defaults to Bearer. */
|
|
10621
|
+
function formatAuthHeader(key, authType) {
|
|
10622
|
+
return `${authType === "oauth" ? "OAuth" : "Bearer"} ${key}`;
|
|
10623
|
+
}
|
|
10625
10624
|
/** Normalize a single string or array of strings to an array. Returns undefined for empty arrays. */
|
|
10626
10625
|
function toArray(value) {
|
|
10627
10626
|
if (value === void 0) return void 0;
|
|
@@ -10639,17 +10638,6 @@ function tryParseBody(raw) {
|
|
|
10639
10638
|
}
|
|
10640
10639
|
//#endregion
|
|
10641
10640
|
//#region src/config.ts
|
|
10642
|
-
const DEFAULT_CONFIG = {
|
|
10643
|
-
host: "0.0.0.0",
|
|
10644
|
-
port: 8828,
|
|
10645
|
-
openrouterKey: "",
|
|
10646
|
-
openrouterBaseUrl: "https://openrouter.ai/api/v1",
|
|
10647
|
-
verbose: false,
|
|
10648
|
-
bodyLimit: "50mb",
|
|
10649
|
-
attributionReferer: "http://localhost",
|
|
10650
|
-
attributionTitle: "proxitor"
|
|
10651
|
-
};
|
|
10652
|
-
/** Fields that need toArray normalization (string | string[] → string[] | undefined) */
|
|
10653
10641
|
const ARRAY_FIELDS = [
|
|
10654
10642
|
{
|
|
10655
10643
|
key: "only",
|
|
@@ -10668,7 +10656,6 @@ const ARRAY_FIELDS = [
|
|
|
10668
10656
|
apiName: "quantizations"
|
|
10669
10657
|
}
|
|
10670
10658
|
];
|
|
10671
|
-
/** Direct camelCase → snake_case field mappings */
|
|
10672
10659
|
const DIRECT_FIELDS = [
|
|
10673
10660
|
{
|
|
10674
10661
|
key: "sort",
|
|
@@ -10703,7 +10690,6 @@ const DIRECT_FIELDS = [
|
|
|
10703
10690
|
apiName: "preferred_max_latency"
|
|
10704
10691
|
}
|
|
10705
10692
|
];
|
|
10706
|
-
/** Build the provider routing object for OpenRouter request body injection */
|
|
10707
10693
|
function buildProviderRouting(provider) {
|
|
10708
10694
|
if (!provider) return void 0;
|
|
10709
10695
|
const result = {};
|
|
@@ -10721,13 +10707,11 @@ function buildProviderRouting(provider) {
|
|
|
10721
10707
|
if (result.order) result.allow_fallbacks = provider.allowFallbacks ?? true;
|
|
10722
10708
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
10723
10709
|
}
|
|
10724
|
-
/** Score a pattern against a model name. Higher = better match. -1 = no match. */
|
|
10725
10710
|
function matchScore(pattern, modelName) {
|
|
10726
10711
|
if (pattern === modelName) return modelName.length + 1e3;
|
|
10727
10712
|
if (pattern.endsWith("*") && modelName.startsWith(pattern.slice(0, -1))) return pattern.length;
|
|
10728
10713
|
return -1;
|
|
10729
10714
|
}
|
|
10730
|
-
/** Resolve the effective config for a given model by merging global defaults with the best-matching override */
|
|
10731
10715
|
function resolveModelConfig(config, modelName) {
|
|
10732
10716
|
const result = {
|
|
10733
10717
|
provider: config.provider,
|
|
@@ -10754,23 +10738,25 @@ function resolveModelConfig(config, modelName) {
|
|
|
10754
10738
|
return result;
|
|
10755
10739
|
}
|
|
10756
10740
|
async function loadConfig(options) {
|
|
10757
|
-
|
|
10741
|
+
let fileConfig = {};
|
|
10758
10742
|
if (!options.noConfig) {
|
|
10759
10743
|
const configPath = findConfigFile(options.configPath);
|
|
10760
|
-
if (configPath)
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
if (!
|
|
10771
|
-
|
|
10744
|
+
if (configPath) fileConfig = readConfigFile(configPath);
|
|
10745
|
+
}
|
|
10746
|
+
const merged = {
|
|
10747
|
+
...DEFAULTS,
|
|
10748
|
+
...fileConfig,
|
|
10749
|
+
...options.host ? { host: options.host } : {},
|
|
10750
|
+
...options.port ? { port: options.port } : {},
|
|
10751
|
+
...options.verbose !== void 0 ? { verbose: options.verbose } : {},
|
|
10752
|
+
...options.openrouterKey ? { openrouterKey: options.openrouterKey } : {}
|
|
10753
|
+
};
|
|
10754
|
+
if (!merged.openrouterKey) merged.openrouterKey = process.env.OPENROUTER_API_KEY ?? "";
|
|
10755
|
+
const result = proxyConfigSchema.safeParse(merged);
|
|
10756
|
+
if (!result.success) throw new ConfigValidationError("(merged config)", result.error);
|
|
10757
|
+
if (!result.data.openrouterKey) throw new Error("OpenRouter API key is required. Set OPENROUTER_API_KEY env var, pass --openrouter-key flag, or set it in config file.");
|
|
10758
|
+
return result.data;
|
|
10772
10759
|
}
|
|
10773
|
-
/** Resolve XDG config directory: $XDG_CONFIG_HOME/proxitor or ~/.config/proxitor */
|
|
10774
10760
|
function getXdgConfigDir() {
|
|
10775
10761
|
const xdgHome = process.env.XDG_CONFIG_HOME;
|
|
10776
10762
|
return xdgHome ? resolve(xdgHome, "proxitor") : join(homedir(), ".config", "proxitor");
|
|
@@ -11764,6 +11750,36 @@ function _getDefaultLogLevel() {
|
|
|
11764
11750
|
//#region src/logger.ts
|
|
11765
11751
|
const logger = createConsola().withTag("proxitor");
|
|
11766
11752
|
//#endregion
|
|
11753
|
+
//#region src/openrouter/client.ts
|
|
11754
|
+
var OpenRouterClientError = class extends Error {
|
|
11755
|
+
status;
|
|
11756
|
+
constructor(status, message) {
|
|
11757
|
+
super(`OpenRouter API error (${status}): ${message}`);
|
|
11758
|
+
this.name = "OpenRouterClientError";
|
|
11759
|
+
this.status = status;
|
|
11760
|
+
}
|
|
11761
|
+
};
|
|
11762
|
+
/** HTTP client for OpenRouter REST endpoints. */
|
|
11763
|
+
var OpenRouterClient = class {
|
|
11764
|
+
apiKey;
|
|
11765
|
+
baseUrl;
|
|
11766
|
+
authType;
|
|
11767
|
+
constructor(apiKey, baseUrl, authType) {
|
|
11768
|
+
this.apiKey = apiKey;
|
|
11769
|
+
this.baseUrl = baseUrl;
|
|
11770
|
+
this.authType = authType;
|
|
11771
|
+
}
|
|
11772
|
+
async get(path) {
|
|
11773
|
+
const url = `${this.baseUrl}${path}`;
|
|
11774
|
+
const res = await fetch(url, { headers: { Authorization: formatAuthHeader(this.apiKey, this.authType) } });
|
|
11775
|
+
if (!res.ok) {
|
|
11776
|
+
const body = await res.text().catch(() => "");
|
|
11777
|
+
throw new OpenRouterClientError(res.status, body || res.statusText);
|
|
11778
|
+
}
|
|
11779
|
+
return res.json();
|
|
11780
|
+
}
|
|
11781
|
+
};
|
|
11782
|
+
//#endregion
|
|
11767
11783
|
//#region node_modules/.pnpm/@hono+node-server@2.0.4_hono@4.12.23/node_modules/@hono/node-server/dist/index.mjs
|
|
11768
11784
|
var RequestError = class extends Error {
|
|
11769
11785
|
constructor(message, options) {
|
|
@@ -14497,7 +14513,7 @@ function filterHeaders(incoming, blocklist) {
|
|
|
14497
14513
|
/** Build request headers for upstream fetch */
|
|
14498
14514
|
function buildRequestHeaders(incoming, config, inject, extraHeaders) {
|
|
14499
14515
|
const headers = filterHeaders(incoming, STRIP_REQUEST);
|
|
14500
|
-
headers.Authorization =
|
|
14516
|
+
headers.Authorization = formatAuthHeader(config.openrouterKey, config.authType);
|
|
14501
14517
|
headers["HTTP-Referer"] = config.attributionReferer;
|
|
14502
14518
|
headers["X-Title"] = config.attributionTitle;
|
|
14503
14519
|
headers["Accept-Encoding"] = "identity";
|
|
@@ -14719,7 +14735,7 @@ function startProxyServer(config, onReady) {
|
|
|
14719
14735
|
}
|
|
14720
14736
|
//#endregion
|
|
14721
14737
|
//#region src/version.ts
|
|
14722
|
-
const version = "0.
|
|
14738
|
+
const version = "0.5.1";
|
|
14723
14739
|
//#endregion
|
|
14724
14740
|
//#region src/cli.ts
|
|
14725
14741
|
const argv = process.argv.slice(2);
|
|
@@ -14758,14 +14774,14 @@ const startCommand = (0, import_cjs.command)({
|
|
|
14758
14774
|
short: "p",
|
|
14759
14775
|
type: import_cjs.number,
|
|
14760
14776
|
description: "Proxy server port",
|
|
14761
|
-
defaultValue: () =>
|
|
14777
|
+
defaultValue: () => DEFAULTS.port,
|
|
14762
14778
|
defaultValueIsSerializable: true
|
|
14763
14779
|
}),
|
|
14764
14780
|
host: (0, import_cjs.option)({
|
|
14765
14781
|
long: "host",
|
|
14766
14782
|
type: import_cjs.string,
|
|
14767
14783
|
description: "Proxy server host",
|
|
14768
|
-
defaultValue: () =>
|
|
14784
|
+
defaultValue: () => DEFAULTS.host,
|
|
14769
14785
|
defaultValueIsSerializable: true
|
|
14770
14786
|
}),
|
|
14771
14787
|
config: (0, import_cjs.option)({
|
|
@@ -14808,9 +14824,15 @@ const startCommand = (0, import_cjs.command)({
|
|
|
14808
14824
|
}
|
|
14809
14825
|
}
|
|
14810
14826
|
});
|
|
14811
|
-
const
|
|
14827
|
+
const withClient = (fn) => async (args) => {
|
|
14812
14828
|
const apiKey = await resolveApiKey(args.config ?? void 0, args.openrouterKey ?? void 0);
|
|
14813
|
-
if (apiKey)
|
|
14829
|
+
if (!apiKey) return;
|
|
14830
|
+
try {
|
|
14831
|
+
const cfg = await loadConfig({ configPath: args.config ?? void 0 });
|
|
14832
|
+
await fn(new OpenRouterClient(apiKey, cfg.openrouterBaseUrl, cfg.authType));
|
|
14833
|
+
} catch (error) {
|
|
14834
|
+
logger.error("Failed to load config:", error);
|
|
14835
|
+
}
|
|
14814
14836
|
};
|
|
14815
14837
|
const rootCli = (0, import_cjs.subcommands)({
|
|
14816
14838
|
name: "proxitor",
|
|
@@ -14826,13 +14848,13 @@ const rootCli = (0, import_cjs.subcommands)({
|
|
|
14826
14848
|
name: "add",
|
|
14827
14849
|
description: "Add model override",
|
|
14828
14850
|
args: configOptionArgs,
|
|
14829
|
-
handler:
|
|
14851
|
+
handler: withClient(async (client) => (await import("./add.mjs")).addOverrideCommand(client))
|
|
14830
14852
|
}),
|
|
14831
14853
|
edit: (0, import_cjs.command)({
|
|
14832
14854
|
name: "edit",
|
|
14833
14855
|
description: "Edit model override",
|
|
14834
14856
|
args: configOptionArgs,
|
|
14835
|
-
handler:
|
|
14857
|
+
handler: withClient(async (client) => (await import("./edit.mjs")).editOverrideCommand(client))
|
|
14836
14858
|
}),
|
|
14837
14859
|
remove: (0, import_cjs.command)({
|
|
14838
14860
|
name: "remove",
|
|
@@ -14850,7 +14872,7 @@ const rootCli = (0, import_cjs.subcommands)({
|
|
|
14850
14872
|
name: "browse",
|
|
14851
14873
|
description: "Browse models",
|
|
14852
14874
|
args: configOptionArgs,
|
|
14853
|
-
handler:
|
|
14875
|
+
handler: withClient(async (client) => (await import("./browse.mjs")).browseModelsCommand(client))
|
|
14854
14876
|
}),
|
|
14855
14877
|
validate: (0, import_cjs.command)({
|
|
14856
14878
|
name: "validate",
|
|
@@ -14862,7 +14884,7 @@ const rootCli = (0, import_cjs.subcommands)({
|
|
|
14862
14884
|
name: "menu",
|
|
14863
14885
|
description: "Interactive configuration menu",
|
|
14864
14886
|
args: configOptionArgs,
|
|
14865
|
-
handler:
|
|
14887
|
+
handler: withClient(async (client) => (await import("./config2.mjs")).runConfigMenu(client))
|
|
14866
14888
|
}),
|
|
14867
14889
|
wizard: (0, import_cjs.command)({
|
|
14868
14890
|
name: "wizard",
|
|
@@ -14896,6 +14918,6 @@ const handleError = async (err) => {
|
|
|
14896
14918
|
if (argv.some((a) => !a.startsWith("-")) || isInfo) (0, import_cjs.run)(rootCli, argv).catch((err) => void handleError(err));
|
|
14897
14919
|
else (0, import_cjs.run)(startCommand, argv).catch((err) => void handleError(err));
|
|
14898
14920
|
//#endregion
|
|
14899
|
-
export {
|
|
14921
|
+
export { DEFAULTS as a, readConfigFile as i, findConfigFile as n, __commonJSMin$1 as o, getXdgConfigDir as r, __require as s, logger as t };
|
|
14900
14922
|
|
|
14901
14923
|
//# sourceMappingURL=cli.mjs.map
|