proxitor 0.3.0 → 0.5.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.
Files changed (62) hide show
  1. package/README.md +27 -8
  2. package/dist/add.mjs +26 -27
  3. package/dist/add.mjs.map +1 -1
  4. package/dist/browse.mjs +20 -21
  5. package/dist/browse.mjs.map +1 -1
  6. package/dist/cli.mjs +14774 -33
  7. package/dist/cli.mjs.map +1 -1
  8. package/dist/config.mjs +6 -5
  9. package/dist/config.mjs.map +1 -1
  10. package/dist/config2.mjs +5 -6
  11. package/dist/config2.mjs.map +1 -1
  12. package/dist/dist.mjs +1325 -0
  13. package/dist/dist.mjs.map +1 -0
  14. package/dist/dist2.mjs +6617 -0
  15. package/dist/dist2.mjs.map +1 -0
  16. package/dist/edit.mjs +17 -18
  17. package/dist/edit.mjs.map +1 -1
  18. package/dist/list.mjs +4 -4
  19. package/dist/list.mjs.map +1 -1
  20. package/dist/prompt.mjs +849 -0
  21. package/dist/prompt.mjs.map +1 -0
  22. package/dist/providers.mjs +16 -16
  23. package/dist/providers.mjs.map +1 -1
  24. package/dist/remove.mjs +10 -11
  25. package/dist/remove.mjs.map +1 -1
  26. package/dist/validate.mjs +9 -9
  27. package/dist/validate.mjs.map +1 -1
  28. package/dist/wizard.mjs +222 -0
  29. package/dist/wizard.mjs.map +1 -0
  30. package/package.json +1 -16
  31. package/dist/add.cjs +0 -139
  32. package/dist/add.cjs.map +0 -1
  33. package/dist/browse.cjs +0 -88
  34. package/dist/browse.cjs.map +0 -1
  35. package/dist/cli.cjs +0 -159
  36. package/dist/cli.cjs.map +0 -1
  37. package/dist/cli.d.cts +0 -1
  38. package/dist/cli.d.mts +0 -1
  39. package/dist/config.cjs +0 -68
  40. package/dist/config.cjs.map +0 -1
  41. package/dist/config2.cjs +0 -75
  42. package/dist/config2.cjs.map +0 -1
  43. package/dist/edit.cjs +0 -82
  44. package/dist/edit.cjs.map +0 -1
  45. package/dist/index.cjs +0 -12
  46. package/dist/index.d.cts +0 -261
  47. package/dist/index.d.cts.map +0 -1
  48. package/dist/index.d.mts +0 -261
  49. package/dist/index.d.mts.map +0 -1
  50. package/dist/index.mjs +0 -2
  51. package/dist/list.cjs +0 -33
  52. package/dist/list.cjs.map +0 -1
  53. package/dist/providers.cjs +0 -376
  54. package/dist/providers.cjs.map +0 -1
  55. package/dist/proxy.cjs +0 -656
  56. package/dist/proxy.cjs.map +0 -1
  57. package/dist/proxy.mjs +0 -544
  58. package/dist/proxy.mjs.map +0 -1
  59. package/dist/remove.cjs +0 -38
  60. package/dist/remove.cjs.map +0 -1
  61. package/dist/validate.cjs +0 -26
  62. package/dist/validate.cjs.map +0 -1
package/README.md CHANGED
@@ -17,11 +17,11 @@
17
17
  ```
18
18
  Claude Code / Codex
19
19
 
20
- │ ANTHROPIC_BASE_URL=http://localhost:8080/v1
20
+ │ ANTHROPIC_BASE_URL=http://localhost:8828/v1
21
21
 
22
22
  ┌───────────────┐
23
23
  │ proxitor │ ← injects provider routing
24
- │ :8080 │ ← streams SSE back unchanged
24
+ │ :8828 │ ← streams SSE back unchanged
25
25
  └───────────────┘
26
26
 
27
27
  │ + X-OpenRouter-* headers
@@ -81,17 +81,17 @@ npx proxitor
81
81
 
82
82
  ```sh
83
83
  OPENROUTER_API_KEY=sk-or-... proxitor
84
- # Listening on http://0.0.0.0:8080
84
+ # Listening on http://0.0.0.0:8828
85
85
  ```
86
86
 
87
87
  **2. Point your tools at it**
88
88
 
89
89
  ```sh
90
90
  # Claude Code
91
- ANTHROPIC_BASE_URL=http://localhost:8080/v1 claude
91
+ ANTHROPIC_BASE_URL=http://localhost:8828/v1 claude
92
92
 
93
93
  # Codex
94
- OPENAI_BASE_URL=http://localhost:8080/v1 codex
94
+ OPENAI_BASE_URL=http://localhost:8828/v1 codex
95
95
  ```
96
96
 
97
97
  That's it. Requests flow through proxitor to OpenRouter, SSE streams pass through unchanged.
@@ -224,7 +224,7 @@ provider:
224
224
  ### Health check
225
225
 
226
226
  ```sh
227
- curl http://localhost:8080/health
227
+ curl http://localhost:8828/health
228
228
  ```
229
229
 
230
230
  ---
@@ -233,6 +233,24 @@ curl http://localhost:8080/health
233
233
 
234
234
  Proxitor includes an interactive CLI for managing model overrides — search models, pick providers, and write to config without editing YAML by hand.
235
235
 
236
+ ### Setup wizard
237
+
238
+ Run the wizard to create or update your config interactively. If no config exists, any command will offer to launch it automatically.
239
+
240
+ ```sh
241
+ proxitor config wizard
242
+ ```
243
+
244
+ The wizard asks for:
245
+
246
+ - **OpenRouter API key** — stored in config or set as `OPENROUTER_API_KEY` env var
247
+ - **Port** — default `8828` (avoids conflicts with common dev servers on 8080)
248
+ - **API base URL** — default `https://openrouter.ai/api/v1`; change for self-hosted or custom endpoints
249
+ - **Host** — all interfaces (`0.0.0.0`) or localhost only (`127.0.0.1`)
250
+ - **Save location** — project directory, `~/.config/proxitor/`, or `$XDG_CONFIG_HOME/proxitor/`
251
+
252
+ If a config already exists, the wizard shows its location and asks whether to reconfigure. Existing `modelOverrides`, `provider`, and other fields are preserved — only the wizard fields are updated.
253
+
236
254
  ```sh
237
255
  proxitor config menu # interactive menu
238
256
  proxitor config add # add a model override
@@ -240,6 +258,7 @@ proxitor config edit # edit existing override
240
258
  proxitor config remove # remove override(s)
241
259
  proxitor config list # show current overrides
242
260
  proxitor config browse # explore models with pricing info
261
+ proxitor config wizard # interactive setup wizard
243
262
  proxitor config validate # validate config file
244
263
  ```
245
264
 
@@ -309,7 +328,7 @@ The interface uses live data from the OpenRouter API — model search with type-
309
328
 
310
329
  | Flag | Default | Description |
311
330
  |---|---|---|
312
- | `-p, --port <port>` | `8080` | Server port |
331
+ | `-p, --port <port>` | `8828` | Server port |
313
332
  | `-h, --host <host>` | `0.0.0.0` | Server host |
314
333
  | `-c, --config <path>` | auto-discovered | Path to config file |
315
334
  | `--openrouter-key <key>` | `$OPENROUTER_API_KEY` | OpenRouter API key |
@@ -337,4 +356,4 @@ pnpm check # typecheck + biome + test (full CI)
337
356
 
338
357
  ## License
339
358
 
340
- [MIT](./LICENSE)
359
+ [MIT](./LICENSE)
package/dist/add.mjs CHANGED
@@ -1,12 +1,11 @@
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";
1
2
  import { c as formatModelHint, f as fetchModels, g as OpenRouterClient, l as formatModelLabel, n as selectProvidersByMode, o as formatContextLength, p as formatPrice, r as selectRoutingMode, t as fetchProvidersForModel, u as formatPricing } from "./providers.mjs";
2
3
  import { i as setModelOverride, r as requireConfigPath, t as getModelOverrides } from "./config.mjs";
3
- import * as clack from "@clack/prompts";
4
- import { isCancel } from "@clack/prompts";
5
4
  //#region src/commands/config/add.ts
6
5
  const CUSTOM_PATTERN = "__custom_pattern__";
7
6
  /** Run the interactive "Add model override" flow. */
8
7
  async function addOverrideCommand(apiKey) {
9
- clack.intro("Add Model Override");
8
+ ye("Add Model Override");
10
9
  const configPath = requireConfigPath();
11
10
  const client = new OpenRouterClient(apiKey);
12
11
  const models = await loadModelsWithSpinner(client);
@@ -18,7 +17,7 @@ async function addOverrideCommand(apiKey) {
18
17
  const pattern = await enterPattern(models);
19
18
  if (!pattern) return;
20
19
  if (getModelOverrides(configPath)[pattern]) {
21
- clack.log.warn(`Override for "${pattern}" already exists. Use Edit instead.`);
20
+ R.warn(`Override for "${pattern}" already exists. Use Edit instead.`);
22
21
  return;
23
22
  }
24
23
  await configureProviderAndSave(configPath, client, pattern, true);
@@ -27,13 +26,13 @@ async function addOverrideCommand(apiKey) {
27
26
  const selected = models.find((m) => m.id === modelId);
28
27
  if (selected) displayModelInfo(selected);
29
28
  if (getModelOverrides(configPath)[modelId]) {
30
- clack.log.warn(`Override for "${modelId}" already exists. Use Edit instead.`);
29
+ R.warn(`Override for "${modelId}" already exists. Use Edit instead.`);
31
30
  return;
32
31
  }
33
32
  await configureProviderAndSave(configPath, client, modelId, false);
34
33
  }
35
34
  async function loadModelsWithSpinner(client) {
36
- const s = clack.spinner();
35
+ const s = vt();
37
36
  s.start("Loading models from OpenRouter...");
38
37
  try {
39
38
  const models = await fetchModels(client);
@@ -41,12 +40,12 @@ async function loadModelsWithSpinner(client) {
41
40
  return models;
42
41
  } catch (error) {
43
42
  s.stop("Failed to load models");
44
- clack.log.error(String(error));
43
+ R.error(String(error));
45
44
  return null;
46
45
  }
47
46
  }
48
47
  async function searchModel(models) {
49
- const result = await clack.autocomplete({
48
+ const result = await Vt({
50
49
  message: "Search for a model",
51
50
  placeholder: "Type to search (e.g. \"claude\", \"gpt-4o\", \"qwen\")",
52
51
  maxItems: 15,
@@ -69,53 +68,53 @@ async function searchModel(models) {
69
68
  },
70
69
  filter: (_search, _option) => true
71
70
  });
72
- if (isCancel(result)) return null;
71
+ if (R$1(result)) return null;
73
72
  return result;
74
73
  }
75
74
  async function enterPattern(models) {
76
- const pattern = await clack.text({
75
+ const pattern = await Re({
77
76
  message: "Enter model pattern",
78
77
  placeholder: "e.g. claude-*, gpt-4*, anthropic/*",
79
78
  validate: (v) => {
80
79
  if (!v?.trim()) return "Pattern cannot be empty";
81
80
  }
82
81
  });
83
- if (isCancel(pattern)) return null;
82
+ if (R$1(pattern)) return null;
84
83
  const pat = pattern.trim();
85
84
  const matches = countPatternMatches(pat, models);
86
- if (matches > 0) clack.log.info(`Pattern "${pat}" matches ${matches} model(s)`);
87
- else clack.log.warn(`Pattern "${pat}" does not match any current models — it will still be saved`);
85
+ if (matches > 0) R.info(`Pattern "${pat}" matches ${matches} model(s)`);
86
+ else R.warn(`Pattern "${pat}" does not match any current models — it will still be saved`);
88
87
  return pat;
89
88
  }
90
89
  async function configureProviderAndSave(configPath, client, modelKey, isPattern) {
91
90
  const mode = await selectRoutingMode("Configure provider routing");
92
- if (isCancel(mode)) return;
91
+ if (R$1(mode)) return;
93
92
  if (mode === "skip") {
94
93
  setModelOverride(configPath, modelKey, {});
95
- clack.outro("Done — override saved without provider routing");
94
+ fe("Done — override saved without provider routing");
96
95
  return;
97
96
  }
98
97
  const providerOptions = await fetchProvidersForModel(client, modelKey, isPattern);
99
98
  if (!providerOptions) return;
100
99
  const override = await selectProvidersByMode(mode, providerOptions);
101
100
  if (!override) return;
102
- clack.log.info(`Proposed override:\n ${modelKey}:\n ${formatOverrideYaml(override)}`);
103
- const save = await clack.confirm({ message: "Save to config?" });
104
- if (isCancel(save) || !save) {
105
- clack.outro("Cancelled");
101
+ R.info(`Proposed override:\n ${modelKey}:\n ${formatOverrideYaml(override)}`);
102
+ const save = await le({ message: "Save to config?" });
103
+ if (R$1(save) || !save) {
104
+ fe("Cancelled");
106
105
  return;
107
106
  }
108
107
  setModelOverride(configPath, modelKey, override);
109
- clack.outro("✓ Model override saved");
108
+ fe("✓ Model override saved");
110
109
  }
111
110
  function displayModelInfo(model) {
112
- clack.log.info(`${model.name || model.id}`);
113
- clack.log.info(` Context: ${formatContextLength(model.context_length)} tokens`);
114
- clack.log.info(` Pricing: ${formatPricing(model.pricing.prompt, model.pricing.completion)}`);
115
- if (model.pricing.input_cache_read && model.pricing.input_cache_read !== "0") clack.log.info(` Cache read: ${formatPrice(model.pricing.input_cache_read)}`);
116
- if (model.pricing.input_cache_write && model.pricing.input_cache_write !== "0") clack.log.info(` Cache write: ${formatPrice(model.pricing.input_cache_write)}`);
117
- if (model.top_provider?.max_completion_tokens) clack.log.info(` Max output: ${formatContextLength(model.top_provider.max_completion_tokens)} tokens`);
118
- if (model.architecture?.modality) clack.log.info(` Modality: ${model.architecture.modality}`);
111
+ R.info(`${model.name || model.id}`);
112
+ R.info(` Context: ${formatContextLength(model.context_length)} tokens`);
113
+ R.info(` Pricing: ${formatPricing(model.pricing.prompt, model.pricing.completion)}`);
114
+ if (model.pricing.input_cache_read && model.pricing.input_cache_read !== "0") R.info(` Cache read: ${formatPrice(model.pricing.input_cache_read)}`);
115
+ if (model.pricing.input_cache_write && model.pricing.input_cache_write !== "0") R.info(` Cache write: ${formatPrice(model.pricing.input_cache_write)}`);
116
+ if (model.top_provider?.max_completion_tokens) R.info(` Max output: ${formatContextLength(model.top_provider.max_completion_tokens)} tokens`);
117
+ if (model.architecture?.modality) R.info(` Modality: ${model.architecture.modality}`);
119
118
  }
120
119
  function countPatternMatches(pattern, models) {
121
120
  if (pattern.endsWith("*")) {
package/dist/add.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"add.mjs","names":[],"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 { 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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"}
package/dist/browse.mjs CHANGED
@@ -1,7 +1,6 @@
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";
1
2
  import { a as getUniqueProviders, c as formatModelHint, d as formatThroughput, f as fetchModels, g as OpenRouterClient, 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";
2
3
  import { addOverrideCommand } from "./add.mjs";
3
- import * as clack from "@clack/prompts";
4
- import { isCancel } from "@clack/prompts";
5
4
  //#region src/commands/config/browse.ts
6
5
  function toOption(m) {
7
6
  return {
@@ -11,23 +10,23 @@ function toOption(m) {
11
10
  };
12
11
  }
13
12
  function displayModelDetails(model) {
14
- clack.log.success(`${model.name || model.id}`);
13
+ R.success(`${model.name || model.id}`);
15
14
  if (model.description) {
16
15
  const desc = model.description.length > 200 ? `${model.description.slice(0, 200)}...` : model.description;
17
- clack.log.info(` ${desc}`);
16
+ R.info(` ${desc}`);
18
17
  }
19
- clack.log.info(` Context: ${formatContextLength(model.context_length)} tokens`);
20
- if (model.top_provider?.max_completion_tokens) clack.log.info(` Max output: ${formatContextLength(model.top_provider.max_completion_tokens)} tokens`);
21
- clack.log.info(` Pricing: ${formatPricing(model.pricing.prompt, model.pricing.completion)}`);
22
- if (model.pricing.input_cache_read && model.pricing.input_cache_read !== "0") clack.log.info(` Cache read: ${formatPrice(model.pricing.input_cache_read)}`);
23
- if (model.pricing.input_cache_write && model.pricing.input_cache_write !== "0") clack.log.info(` Cache write: ${formatPrice(model.pricing.input_cache_write)}`);
24
- if (model.architecture?.modality) clack.log.info(` Modality: ${model.architecture.modality}`);
25
- if (model.supported_parameters?.length) clack.log.info(` Parameters: ${model.supported_parameters.join(", ")}`);
18
+ R.info(` Context: ${formatContextLength(model.context_length)} tokens`);
19
+ if (model.top_provider?.max_completion_tokens) R.info(` Max output: ${formatContextLength(model.top_provider.max_completion_tokens)} tokens`);
20
+ R.info(` Pricing: ${formatPricing(model.pricing.prompt, model.pricing.completion)}`);
21
+ if (model.pricing.input_cache_read && model.pricing.input_cache_read !== "0") R.info(` Cache read: ${formatPrice(model.pricing.input_cache_read)}`);
22
+ if (model.pricing.input_cache_write && model.pricing.input_cache_write !== "0") R.info(` Cache write: ${formatPrice(model.pricing.input_cache_write)}`);
23
+ if (model.architecture?.modality) R.info(` Modality: ${model.architecture.modality}`);
24
+ if (model.supported_parameters?.length) R.info(` Parameters: ${model.supported_parameters.join(", ")}`);
26
25
  }
27
26
  async function displayProviders(client, model) {
28
27
  const author = parseModelAuthor(model.id);
29
28
  const slug = parseModelSlug(model.id);
30
- const s = clack.spinner();
29
+ const s = vt();
31
30
  s.start("Checking providers...");
32
31
  try {
33
32
  const endpoints = await fetchModelEndpoints(client, author, slug);
@@ -37,7 +36,7 @@ async function displayProviders(client, model) {
37
36
  const ep = endpoints.find((e) => e.tag === p.tag);
38
37
  const latency = formatLatency(ep?.latency_last_30m?.p50 ?? null);
39
38
  const throughput = formatThroughput(ep?.throughput_last_30m?.p50 ?? null);
40
- clack.log.info(` ${p.providerName} (${p.tag}) — ${latency} · ${throughput}`);
39
+ R.info(` ${p.providerName} (${p.tag}) — ${latency} · ${throughput}`);
41
40
  }
42
41
  } catch {
43
42
  s.stop("Could not fetch providers");
@@ -45,9 +44,9 @@ async function displayProviders(client, model) {
45
44
  }
46
45
  /** Run the interactive "Browse models" flow. */
47
46
  async function browseModelsCommand(apiKey) {
48
- clack.intro("Browse Models");
47
+ ye("Browse Models");
49
48
  const client = new OpenRouterClient(apiKey);
50
- const s = clack.spinner();
49
+ const s = vt();
51
50
  s.start("Loading models...");
52
51
  let models;
53
52
  try {
@@ -55,10 +54,10 @@ async function browseModelsCommand(apiKey) {
55
54
  s.stop(`${models.length} models available`);
56
55
  } catch (error) {
57
56
  s.stop("Failed to load models");
58
- clack.log.error(String(error));
57
+ R.error(String(error));
59
58
  return;
60
59
  }
61
- const modelId = await clack.autocomplete({
60
+ const modelId = await Vt({
62
61
  message: "Search for a model",
63
62
  placeholder: "Type to search...",
64
63
  maxItems: 15,
@@ -69,14 +68,14 @@ async function browseModelsCommand(apiKey) {
69
68
  },
70
69
  filter: (_search, _option) => true
71
70
  });
72
- if (isCancel(modelId)) return;
71
+ if (R$1(modelId)) return;
73
72
  const model = models.find((m) => m.id === modelId);
74
73
  if (!model) return;
75
74
  displayModelDetails(model);
76
75
  await displayProviders(client, model);
77
- const configure = await clack.confirm({ message: `Configure routing for ${model.id}?` });
78
- if (isCancel(configure) || !configure) {
79
- clack.outro("Bye!");
76
+ const configure = await le({ message: `Configure routing for ${model.id}?` });
77
+ if (R$1(configure) || !configure) {
78
+ fe("Bye!");
80
79
  return;
81
80
  }
82
81
  await addOverrideCommand(apiKey);
@@ -1 +1 @@
1
- {"version":3,"file":"browse.mjs","names":[],"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(apiKey: string): Promise<void> {\n clack.intro('Browse Models')\n\n const client = new OpenRouterClient(apiKey)\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(apiKey)\n}\n"],"mappings":"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"}
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(apiKey: string): Promise<void> {\n clack.intro('Browse Models')\n\n const client = new OpenRouterClient(apiKey)\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(apiKey)\n}\n"],"mappings":"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"}