@steipete/summarize 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 (99) hide show
  1. package/CHANGELOG.md +80 -5
  2. package/README.md +122 -20
  3. package/dist/cli.cjs +8446 -4360
  4. package/dist/cli.cjs.map +4 -4
  5. package/dist/esm/cli-main.js +47 -2
  6. package/dist/esm/cli-main.js.map +1 -1
  7. package/dist/esm/config.js +368 -3
  8. package/dist/esm/config.js.map +1 -1
  9. package/dist/esm/content/link-preview/content/index.js +13 -0
  10. package/dist/esm/content/link-preview/content/index.js.map +1 -1
  11. package/dist/esm/content/link-preview/content/utils.js +3 -1
  12. package/dist/esm/content/link-preview/content/utils.js.map +1 -1
  13. package/dist/esm/content/link-preview/content/video.js +96 -0
  14. package/dist/esm/content/link-preview/content/video.js.map +1 -0
  15. package/dist/esm/content/link-preview/transcript/providers/youtube/captions.js +21 -21
  16. package/dist/esm/content/link-preview/transcript/providers/youtube/captions.js.map +1 -1
  17. package/dist/esm/costs.js.map +1 -1
  18. package/dist/esm/flags.js +41 -1
  19. package/dist/esm/flags.js.map +1 -1
  20. package/dist/esm/generate-free.js +616 -0
  21. package/dist/esm/generate-free.js.map +1 -0
  22. package/dist/esm/llm/cli.js +290 -0
  23. package/dist/esm/llm/cli.js.map +1 -0
  24. package/dist/esm/llm/generate-text.js +159 -105
  25. package/dist/esm/llm/generate-text.js.map +1 -1
  26. package/dist/esm/llm/html-to-markdown.js +4 -2
  27. package/dist/esm/llm/html-to-markdown.js.map +1 -1
  28. package/dist/esm/markitdown.js +54 -0
  29. package/dist/esm/markitdown.js.map +1 -0
  30. package/dist/esm/model-auto.js +353 -0
  31. package/dist/esm/model-auto.js.map +1 -0
  32. package/dist/esm/model-spec.js +82 -0
  33. package/dist/esm/model-spec.js.map +1 -0
  34. package/dist/esm/prompts/cli.js +18 -0
  35. package/dist/esm/prompts/cli.js.map +1 -0
  36. package/dist/esm/prompts/file.js +21 -2
  37. package/dist/esm/prompts/file.js.map +1 -1
  38. package/dist/esm/prompts/index.js +2 -1
  39. package/dist/esm/prompts/index.js.map +1 -1
  40. package/dist/esm/prompts/link-summary.js +3 -8
  41. package/dist/esm/prompts/link-summary.js.map +1 -1
  42. package/dist/esm/refresh-free.js +667 -0
  43. package/dist/esm/refresh-free.js.map +1 -0
  44. package/dist/esm/run.js +1612 -533
  45. package/dist/esm/run.js.map +1 -1
  46. package/dist/esm/version.js +1 -1
  47. package/dist/types/config.d.ts +58 -5
  48. package/dist/types/content/link-preview/content/types.d.ts +10 -0
  49. package/dist/types/content/link-preview/content/utils.d.ts +1 -1
  50. package/dist/types/content/link-preview/content/video.d.ts +5 -0
  51. package/dist/types/costs.d.ts +2 -1
  52. package/dist/types/flags.d.ts +7 -0
  53. package/dist/types/generate-free.d.ts +17 -0
  54. package/dist/types/llm/cli.d.ts +24 -0
  55. package/dist/types/llm/generate-text.d.ts +13 -4
  56. package/dist/types/llm/html-to-markdown.d.ts +9 -3
  57. package/dist/types/markitdown.d.ts +10 -0
  58. package/dist/types/model-auto.d.ts +23 -0
  59. package/dist/types/model-spec.d.ts +33 -0
  60. package/dist/types/prompts/cli.d.ts +8 -0
  61. package/dist/types/prompts/file.d.ts +7 -0
  62. package/dist/types/prompts/index.d.ts +2 -1
  63. package/dist/types/refresh-free.d.ts +19 -0
  64. package/dist/types/run.d.ts +3 -1
  65. package/dist/types/version.d.ts +1 -1
  66. package/docs/README.md +4 -1
  67. package/docs/cli.md +95 -0
  68. package/docs/config.md +123 -1
  69. package/docs/extract-only.md +10 -7
  70. package/docs/firecrawl.md +2 -2
  71. package/docs/llm.md +24 -4
  72. package/docs/manual-tests.md +40 -0
  73. package/docs/model-auto.md +92 -0
  74. package/docs/site/assets/site.js +20 -17
  75. package/docs/site/docs/config.html +3 -3
  76. package/docs/site/docs/extract-only.html +7 -5
  77. package/docs/site/docs/firecrawl.html +6 -6
  78. package/docs/site/docs/index.html +2 -2
  79. package/docs/site/docs/llm.html +2 -2
  80. package/docs/site/docs/openai.html +2 -2
  81. package/docs/site/docs/website.html +7 -4
  82. package/docs/site/docs/youtube.html +2 -2
  83. package/docs/site/index.html +1 -1
  84. package/docs/smoketest.md +58 -0
  85. package/docs/website.md +13 -8
  86. package/docs/youtube.md +1 -1
  87. package/package.json +8 -4
  88. package/dist/esm/content/link-preview/transcript/providers/twitter.js +0 -12
  89. package/dist/esm/content/link-preview/transcript/providers/twitter.js.map +0 -1
  90. package/dist/esm/content/link-preview/transcript/providers/youtube/ytdlp.js +0 -114
  91. package/dist/esm/content/link-preview/transcript/providers/youtube/ytdlp.js.map +0 -1
  92. package/dist/esm/summarizeHome.js +0 -20
  93. package/dist/esm/summarizeHome.js.map +0 -1
  94. package/dist/esm/tty/live-markdown.js +0 -52
  95. package/dist/esm/tty/live-markdown.js.map +0 -1
  96. package/dist/types/content/link-preview/transcript/providers/twitter.d.ts +0 -3
  97. package/dist/types/content/link-preview/transcript/providers/youtube/ytdlp.d.ts +0 -3
  98. package/dist/types/summarizeHome.d.ts +0 -6
  99. package/dist/types/tty/live-markdown.d.ts +0 -10
package/CHANGELOG.md CHANGED
@@ -1,7 +1,83 @@
1
1
  # Changelog
2
2
 
3
- ## 0.3.0 - 2025-12-20
3
+ ## 0.5.0 - 2025-12-24
4
+
5
+ ### Features
6
+
7
+ - **Model selection & presets**
8
+ - Automatic model selection (`--model auto`, now the default):
9
+ - Chooses models based on input kind (website/YouTube/file/image/video/text) and prompt size.
10
+ - Skips candidates without API keys; retries next model on request errors.
11
+ - Adds OpenRouter fallback attempts when `OPENROUTER_API_KEY` is present.
12
+ - Shows the chosen model in the progress UI.
13
+ - Named model presets via config (`~/.summarize/config.json` → `models`), selectable as `--model <preset>`.
14
+ - Built-in preset: `--model free` (OpenRouter `:free` candidates; override via `models.free`).
15
+ - **OpenRouter free preset maintenance**
16
+ - `summarize refresh-free` regenerates `models.free` by scanning OpenRouter `:free` models and testing availability + latency.
17
+ - `summarize refresh-free --set-default` also sets `"model": "free"` in `~/.summarize/config.json` (so free becomes your default).
18
+ - **CLI models**
19
+ - Add `--cli <provider>` flag (equivalent to `--model cli/<provider>`).
20
+ - `--cli` accepts case-insensitive providers and can be used without a provider to enable CLI auto selection.
21
+ - **Content extraction**
22
+ - Website extraction detects video-only pages:
23
+ - YouTube embeds switch to transcript extraction automatically.
24
+ - Direct video URLs can be downloaded + summarized when `--video-mode auto|understand` and a Gemini key is available.
25
+ - **Env**
26
+ - `.env` in the current directory is loaded automatically (so API keys work without exporting env vars).
27
+
28
+ ### Changes
29
+
30
+ - **CLI config**
31
+ - Auto mode uses CLI models only when `cli.enabled` is set; order follows the list.
32
+ - `cli.enabled` is an allowlist for CLI usage.
33
+ - **OpenRouter**
34
+ - Stop sending extra routing headers.
35
+ - `--model free`: when OpenRouter rejects routing with “No allowed providers”, print the exact provider names to allow and suggest running `summarize refresh-free`.
36
+ - `--max-output-tokens`: when explicitly set, it is also forwarded to OpenRouter calls.
37
+ - **Refresh Free**
38
+ - Default extra runs reduced to 2 (total runs = 1 + runs) to reduce rate-limit pressure.
39
+ - Filter `:free` candidates by recency (default: last 180 days; configurable via `--max-age-days`).
40
+ - Print `ctx`/`out` in `k` units for readability.
41
+ - **Defaults**
42
+ - Default summary length is now `xl`.
43
+
44
+ ### Fixes
45
+
46
+ - **LLM / OpenRouter**
47
+ - LLM request retries (`--retries`) and clearer timeout errors.
48
+ - `summarize refresh-free`: detect OpenRouter free-model rate limits and back off + retry.
49
+ - **Streaming**
50
+ - Normalize + de-dupe overlapping chunks to prevent repeated sections in live Markdown output.
51
+ - **YouTube**
52
+ - Prefer manual captions over auto-generated when both exist. Thanks @dougvk.
53
+ - Always summarize YouTube transcripts in auto mode (instead of printing the transcript).
54
+ - **Prompting & metrics**
55
+ - Don’t “pad” beyond input length when asking for longer summaries.
56
+ - `--metrics detailed`: fold metrics into finish line and make labels less cryptic.
57
+
58
+ ### Docs
4
59
 
60
+ - Add documentation for presets and Refresh Free.
61
+ - Add a “make free the default” quick start for `summarize refresh-free --set-default`.
62
+ - Add a manual end-to-end checklist (`docs/manual-tests.md`).
63
+ - Add a quick CLI smoke checklist (`docs/smoketest.md`).
64
+ - Document CLI ordering and model selection behavior.
65
+
66
+ ### Tests
67
+
68
+ - Add coverage for presets and Refresh Free regeneration.
69
+ - Add live coverage for the `free` preset.
70
+ - Add regression coverage for YouTube transcript handling and metrics formatting.
71
+
72
+ ## 0.4.0 - 2025-12-21
73
+
74
+ ### Changes
75
+
76
+ - Add URL extraction mode via `--extract` with `--format md|text`.
77
+ - Rename HTML→Markdown conversion flag to `--markdown-mode`.
78
+ - Add `--preprocess off|auto|always` and a `uvx markitdown` fallback for Markdown extraction and unsupported file attachments (when `--format md` is used).
79
+
80
+ ## 0.3.0 - 2025-12-20
5
81
  ### Changes
6
82
 
7
83
  - Add yt-dlp audio transcription fallback for YouTube; prefer OpenAI Whisper with FAL fallback. Thanks @dougvk.
@@ -9,7 +85,6 @@
9
85
  - Run yt-dlp after web + Apify in `--youtube auto`, and error early for missing keys in `--youtube yt-dlp`.
10
86
  - Require Node 22+.
11
87
  - Respect `OPENAI_BASE_URL` when set, even with OpenRouter keys.
12
- - Apply OpenRouter provider ordering headers to HTML→Markdown conversion.
13
88
  - Add OpenRouter configuration tests. Thanks @dougvk for the initial OpenRouter support.
14
89
  - Build and ship a Bun bytecode arm64 binary for Homebrew.
15
90
 
@@ -26,7 +101,7 @@
26
101
 
27
102
  ### Changes
28
103
 
29
- - Add native OpenRouter support via `OPENROUTER_API_KEY` with optional provider ordering (`OPENROUTER_PROVIDERS`).
104
+ - Add native OpenRouter support via `OPENROUTER_API_KEY`.
30
105
  - Remove map-reduce summarization; reject inputs that exceed the model's context window.
31
106
  - Preflight text prompts with the GPT tokenizer and the model’s max input tokens.
32
107
  - Reject text files over 10 MB before tokenization.
@@ -94,7 +169,7 @@ First public release.
94
169
  - `--max-output-tokens <count>` (optional hard cap)
95
170
  - `--timeout <duration>` (default `2m`)
96
171
  - `--stream auto|on|off`, `--render auto|md-live|md|plain`
97
- - `--extract-only` (URLs only; no summary)
172
+ - `--extract` (URLs only; no summary)
98
173
  - `--json` (structured output incl. input config, prompt, extracted content, LLM metadata, and metrics)
99
174
  - `--metrics off|on|detailed` (default `on`)
100
175
  - `--verbose`
@@ -103,7 +178,7 @@ First public release.
103
178
 
104
179
  - Websites: fetch + extract “article-ish” content + normalization for prompts.
105
180
  - Firecrawl fallback for blocked/thin sites (`--firecrawl off|auto|always`, via `FIRECRAWL_API_KEY`).
106
- - Markdown extraction for websites in `--extract-only` mode (`--markdown off|auto|llm`).
181
+ - Markdown extraction for websites in `--extract` mode (`--format md|text`, `--markdown-mode off|auto|llm`).
107
182
  - YouTube (`--youtube auto|web|apify`):
108
183
  - best-effort transcript endpoints
109
184
  - optional Apify fallback (requires `APIFY_API_TOKEN`; single actor `faVsWy9VTSNVIhWpR`)
package/README.md CHANGED
@@ -16,7 +16,13 @@ Requires Node 22+.
16
16
  - npx (no install):
17
17
 
18
18
  ```bash
19
- npx -y @steipete/summarize "https://example.com" --model google/gemini-3-flash-preview
19
+ npx -y @steipete/summarize "https://example.com"
20
+ ```
21
+
22
+ - npm (global install):
23
+
24
+ ```bash
25
+ npm i -g @steipete/summarize
20
26
  ```
21
27
 
22
28
  - Homebrew (custom tap):
@@ -30,7 +36,7 @@ Apple Silicon only (arm64).
30
36
  ## Quickstart
31
37
 
32
38
  ```bash
33
- summarize "https://example.com" --model google/gemini-3-flash-preview
39
+ summarize "https://example.com"
34
40
  ```
35
41
 
36
42
  Input can be a URL or a local file path:
@@ -73,10 +79,11 @@ Use “gateway-style” ids: `<provider>/<model>`.
73
79
 
74
80
  Examples:
75
81
 
76
- - `openai/gpt-5.2`
77
- - `anthropic/claude-opus-4-5`
82
+ - `openai/gpt-5-mini`
83
+ - `anthropic/claude-sonnet-4-5`
78
84
  - `xai/grok-4-fast-non-reasoning`
79
85
  - `google/gemini-3-flash-preview`
86
+ - `openrouter/openai/gpt-5-mini` (force OpenRouter)
80
87
 
81
88
  Note: some models/providers don’t support streaming or certain file media types. When that happens, the CLI prints a friendly error (or auto-disables streaming for that model when supported by the provider).
82
89
 
@@ -93,6 +100,8 @@ npx -y @steipete/summarize "https://example.com" --length 20k
93
100
  - Character targets: `1500`, `20k`, `20000`
94
101
  - Optional hard cap: `--max-output-tokens <count>` (e.g. `2000`, `2k`)
95
102
  - Provider/model APIs still enforce their own maximum output limits.
103
+ - If omitted, no max token parameter is sent (provider default).
104
+ - Prefer `--length` unless you need a hard cap (some providers count “reasoning” into the cap).
96
105
  - Minimums: `--length` numeric values must be ≥ 50 chars; `--max-output-tokens` must be ≥ 16.
97
106
 
98
107
  ## Limits
@@ -106,24 +115,65 @@ npx -y @steipete/summarize "https://example.com" --length 20k
106
115
  npx -y @steipete/summarize <input> [flags]
107
116
  ```
108
117
 
109
- - `--model <provider/model>`: which model to use (defaults to `google/gemini-3-flash-preview`)
118
+ - `--model <provider/model>`: which model to use (defaults to `auto`)
119
+ - `--model auto`: automatic model selection + fallback (default)
120
+ - `--model <name>`: use a config-defined model (see “Configuration”)
110
121
  - `--timeout <duration>`: `30s`, `2m`, `5000ms` (default `2m`)
122
+ - `--retries <count>`: LLM retry attempts on timeout (default `1`)
111
123
  - `--length short|medium|long|xl|xxl|<chars>`
112
- - `--max-output-tokens <count>`: hard cap for LLM output tokens (optional)
124
+ - `--max-output-tokens <count>`: hard cap for LLM output tokens (optional; only sent when set)
125
+ - `--cli [provider]`: use a CLI provider (case-insensitive; equivalent to `--model cli/<provider>`). If omitted, uses auto selection with CLI enabled.
113
126
  - `--stream auto|on|off`: stream LLM output (`auto` = TTY only; disabled in `--json` mode)
114
127
  - `--render auto|md-live|md|plain`: Markdown rendering (`auto` = best default for TTY)
115
- - `--extract-only`: print extracted content and exit (no summary) — only for URLs
128
+ - `--format md|text`: website/file content format (default `text`)
129
+ - `--preprocess off|auto|always`: controls `uvx markitdown` usage (default `auto`; `always` forces file preprocessing)
130
+ - Install `uvx`: `brew install uv` (or https://astral.sh/uv/)
131
+ - `--extract`: print extracted content and exit (no summary) — only for URLs
132
+ - Deprecated alias: `--extract-only`
116
133
  - `--json`: machine-readable output with diagnostics, prompt, `metrics`, and optional summary
117
134
  - `--verbose`: debug/diagnostics on stderr
118
- - `--metrics off|on|detailed`: metrics output (default `on`; `detailed` prints a breakdown to stderr)
135
+ - `--metrics off|on|detailed`: metrics output (default `on`; `detailed` adds a compact 2nd-line breakdown on stderr)
136
+
137
+ ## Auto model ordering
138
+
139
+ `--model auto` builds candidate attempts from built-in rules (or your `model.rules` overrides).
140
+ CLI tools are **not** used in auto mode unless you explicitly enable them via `cli.enabled` in config.
141
+ Why: CLI adds ~4s latency per attempt and higher variance.
142
+ Shortcut: `--cli` (with no provider) uses auto selection with CLI enabled.
143
+
144
+ When enabled, auto prepends CLI attempts in the order listed in `cli.enabled`
145
+ (recommended: `["gemini"]`), then tries the native provider candidates
146
+ (with OpenRouter fallbacks when configured).
147
+
148
+ Enable CLI attempts:
149
+
150
+ ```json
151
+ {
152
+ "cli": { "enabled": ["gemini"] }
153
+ }
154
+ ```
155
+
156
+ Disable CLI attempts:
157
+
158
+ ```json
159
+ {
160
+ "cli": { "enabled": [] }
161
+ }
162
+ ```
163
+
164
+ Note: when `cli.enabled` is set, it’s also an allowlist for explicit `--cli` / `--model cli/...`.
119
165
 
120
166
  ## Website extraction (Firecrawl + Markdown)
121
167
 
122
168
  Non-YouTube URLs go through a “fetch → extract” pipeline. When the direct fetch/extraction is blocked or too thin, `--firecrawl auto` can fall back to Firecrawl (if configured).
123
169
 
124
170
  - `--firecrawl off|auto|always` (default `auto`)
125
- - `--markdown off|auto|llm` (default `auto`; only affects `--extract-only` for non-YouTube URLs)
126
- - Plain-text mode: use `--firecrawl off --markdown off`.
171
+ - `--extract --format md|text` (default `text`)
172
+ - `--markdown-mode off|auto|llm` (default `auto`; only affects `--format md` for non-YouTube URLs)
173
+ - `auto`: use an LLM converter when configured; may fall back to `uvx markitdown`
174
+ - `llm`: force LLM conversion (requires a configured model key)
175
+ - `off`: disable LLM conversion (still may return Firecrawl Markdown when configured)
176
+ - Plain-text mode: use `--format text`.
127
177
 
128
178
  ## YouTube transcripts
129
179
 
@@ -149,16 +199,34 @@ Supported keys today:
149
199
 
150
200
  ```json
151
201
  {
152
- "model": "openai/gpt-5.2"
202
+ "model": { "id": "openai/gpt-5-mini" }
153
203
  }
154
204
  ```
155
205
 
206
+ Shorthand (equivalent):
207
+
208
+ ```json
209
+ {
210
+ "model": "openai/gpt-5-mini"
211
+ }
212
+ ```
213
+
214
+ Also supported:
215
+
216
+ - `model: { "mode": "auto" }` (automatic model selection + fallback; see `docs/model-auto.md`)
217
+ - `model.rules` (customize candidates / ordering)
218
+ - `models` (define presets selectable via `--model <preset>`)
219
+ - `media.videoMode: "auto"|"transcript"|"understand"`
220
+
221
+ Note: the config is parsed leniently (JSON5), but **comments are not allowed**.
222
+ Unknown keys are ignored.
223
+
156
224
  Precedence:
157
225
 
158
226
  1) `--model`
159
227
  2) `SUMMARIZE_MODEL`
160
228
  3) `~/.summarize/config.json`
161
- 4) default
229
+ 4) default (`auto`)
162
230
 
163
231
  ## Environment variables
164
232
 
@@ -172,23 +240,57 @@ Set the key matching your chosen `--model`:
172
240
 
173
241
  OpenRouter (OpenAI-compatible):
174
242
 
175
- - Set `OPENROUTER_API_KEY=...` to route `openai/...` models through OpenRouter
176
- - Use OpenRouter models via the `openai/...` prefix, e.g. `--model openai/openai/gpt-oss-20b`
177
- - Optional: `OPENROUTER_PROVIDERS=...` to specify provider fallback order (e.g. `groq,google-vertex`)
243
+ - Set `OPENROUTER_API_KEY=...`
244
+ - Prefer forcing OpenRouter per model id: `--model openrouter/<author>/<slug>` (e.g. `openrouter/meta-llama/llama-3.1-8b-instruct:free`)
245
+ - Built-in preset: `--model free` (uses a default set of OpenRouter `:free` models).
178
246
 
179
- Example:
247
+ ### `summarize refresh-free`
248
+
249
+ Quick start: make free the default (keep `auto` available)
180
250
 
181
251
  ```bash
182
- OPENROUTER_API_KEY=sk-or-... summarize "https://example.com" --model openai/openai/gpt-oss-20b
252
+ # writes ~/.summarize/config.json (models.free) and sets model="free"
253
+ summarize refresh-free --set-default
254
+
255
+ # now this defaults to free models
256
+ summarize "https://example.com"
257
+
258
+ # whenever you want best quality instead
259
+ summarize "https://example.com" --model auto
183
260
  ```
184
261
 
185
- With provider ordering (falls back through providers in order):
262
+ Regenerates the `free` preset (writes `models.free` into `~/.summarize/config.json`) by:
263
+
264
+ - Fetching OpenRouter `/models`, filtering `:free`
265
+ - Skipping models that look very small (<27B by default) based on the model id/name (best-effort heuristic)
266
+ - Testing which ones return non-empty text (concurrency 4, timeout 10s)
267
+ - Picking a mix of “smart-ish” (bigger `context_length` / output cap) and fast models
268
+ - Refining timings for the final selection and writing the sorted list back
269
+
270
+ If `--model free` stops working (rate limits, allowed-provider restrictions, models removed), run:
271
+
272
+ ```bash
273
+ summarize refresh-free
274
+ ```
275
+
276
+ Flags:
277
+
278
+ - `--runs 2` (default): extra timing runs per selected model (total runs = 1 + runs)
279
+ - `--smart 3` (default): how many “smart-first” picks (rest filled by fastest)
280
+ - `--min-params 27b` (default): ignore models with inferred size smaller than N billion parameters
281
+ - `--max-age-days 180` (default): ignore models older than N days (set 0 to disable)
282
+ - `--set-default`: also sets `"model": "free"` in `~/.summarize/config.json`
283
+
284
+ Example:
186
285
 
187
286
  ```bash
188
- OPENROUTER_API_KEY=sk-or-... OPENROUTER_PROVIDERS="groq,google-vertex" summarize "https://example.com"
287
+ OPENROUTER_API_KEY=sk-or-... summarize "https://example.com" --model openrouter/meta-llama/llama-3.1-8b-instruct:free
189
288
  ```
190
289
 
191
- Legacy: `OPENAI_BASE_URL=https://openrouter.ai/api/v1` with `OPENAI_API_KEY` also works.
290
+ If your OpenRouter account enforces an allowed-provider list, make sure at least one provider
291
+ is allowed for the selected model. (When routing fails, `summarize` prints the exact providers to allow.)
292
+
293
+ Legacy: `OPENAI_BASE_URL=https://openrouter.ai/api/v1` (and either `OPENAI_API_KEY` or `OPENROUTER_API_KEY`) also works.
192
294
 
193
295
  Optional services:
194
296