@steipete/summarize 0.4.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.
- package/CHANGELOG.md +73 -5
- package/README.md +116 -18
- package/dist/cli.cjs +8136 -4368
- package/dist/cli.cjs.map +4 -4
- package/dist/esm/cli-main.js +47 -2
- package/dist/esm/cli-main.js.map +1 -1
- package/dist/esm/config.js +368 -3
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/content/link-preview/content/index.js +13 -0
- package/dist/esm/content/link-preview/content/index.js.map +1 -1
- package/dist/esm/content/link-preview/content/utils.js +3 -1
- package/dist/esm/content/link-preview/content/utils.js.map +1 -1
- package/dist/esm/content/link-preview/content/video.js +96 -0
- package/dist/esm/content/link-preview/content/video.js.map +1 -0
- package/dist/esm/content/link-preview/transcript/providers/youtube/captions.js +21 -21
- package/dist/esm/content/link-preview/transcript/providers/youtube/captions.js.map +1 -1
- package/dist/esm/costs.js.map +1 -1
- package/dist/esm/flags.js +23 -0
- package/dist/esm/flags.js.map +1 -1
- package/dist/esm/generate-free.js +616 -0
- package/dist/esm/generate-free.js.map +1 -0
- package/dist/esm/llm/cli.js +290 -0
- package/dist/esm/llm/cli.js.map +1 -0
- package/dist/esm/llm/generate-text.js +159 -105
- package/dist/esm/llm/generate-text.js.map +1 -1
- package/dist/esm/llm/html-to-markdown.js +4 -2
- package/dist/esm/llm/html-to-markdown.js.map +1 -1
- package/dist/esm/model-auto.js +353 -0
- package/dist/esm/model-auto.js.map +1 -0
- package/dist/esm/model-spec.js +82 -0
- package/dist/esm/model-spec.js.map +1 -0
- package/dist/esm/prompts/cli.js +18 -0
- package/dist/esm/prompts/cli.js.map +1 -0
- package/dist/esm/prompts/file.js +4 -4
- package/dist/esm/prompts/file.js.map +1 -1
- package/dist/esm/prompts/index.js +1 -0
- package/dist/esm/prompts/index.js.map +1 -1
- package/dist/esm/prompts/link-summary.js +3 -8
- package/dist/esm/prompts/link-summary.js.map +1 -1
- package/dist/esm/refresh-free.js +667 -0
- package/dist/esm/refresh-free.js.map +1 -0
- package/dist/esm/run.js +1384 -532
- package/dist/esm/run.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/types/config.d.ts +58 -5
- package/dist/types/content/link-preview/content/types.d.ts +10 -0
- package/dist/types/content/link-preview/content/utils.d.ts +1 -1
- package/dist/types/content/link-preview/content/video.d.ts +5 -0
- package/dist/types/costs.d.ts +2 -1
- package/dist/types/flags.d.ts +3 -0
- package/dist/types/generate-free.d.ts +17 -0
- package/dist/types/llm/cli.d.ts +24 -0
- package/dist/types/llm/generate-text.d.ts +13 -4
- package/dist/types/llm/html-to-markdown.d.ts +9 -3
- package/dist/types/model-auto.d.ts +23 -0
- package/dist/types/model-spec.d.ts +33 -0
- package/dist/types/prompts/cli.d.ts +8 -0
- package/dist/types/prompts/index.d.ts +1 -0
- package/dist/types/refresh-free.d.ts +19 -0
- package/dist/types/version.d.ts +1 -1
- package/docs/README.md +3 -0
- package/docs/cli.md +95 -0
- package/docs/config.md +123 -1
- package/docs/llm.md +24 -4
- package/docs/manual-tests.md +40 -0
- package/docs/model-auto.md +92 -0
- package/docs/site/assets/site.js +20 -17
- package/docs/smoketest.md +58 -0
- package/docs/website.md +3 -1
- package/package.json +8 -4
- package/dist/esm/content/link-preview/transcript/providers/twitter.js +0 -12
- package/dist/esm/content/link-preview/transcript/providers/twitter.js.map +0 -1
- package/dist/esm/content/link-preview/transcript/providers/youtube/ytdlp.js +0 -114
- package/dist/esm/content/link-preview/transcript/providers/youtube/ytdlp.js.map +0 -1
- package/dist/esm/summarizeHome.js +0 -20
- package/dist/esm/summarizeHome.js.map +0 -1
- package/dist/esm/tty/live-markdown.js +0 -52
- package/dist/esm/tty/live-markdown.js.map +0 -1
- package/dist/types/content/link-preview/transcript/providers/twitter.d.ts +0 -3
- package/dist/types/content/link-preview/transcript/providers/youtube/ytdlp.d.ts +0 -3
- package/dist/types/summarizeHome.d.ts +0 -6
- package/dist/types/tty/live-markdown.d.ts +0 -10
|
@@ -14,7 +14,13 @@ export type LlmTokenUsage = {
|
|
|
14
14
|
completionTokens: number | null;
|
|
15
15
|
totalTokens: number | null;
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
type RetryNotice = {
|
|
18
|
+
attempt: number;
|
|
19
|
+
maxRetries: number;
|
|
20
|
+
delayMs: number;
|
|
21
|
+
error: unknown;
|
|
22
|
+
};
|
|
23
|
+
export declare function generateTextWithModelId({ modelId, apiKeys, system, prompt, temperature, maxOutputTokens, timeoutMs, fetchImpl, forceOpenRouter, retries, onRetry, }: {
|
|
18
24
|
modelId: string;
|
|
19
25
|
apiKeys: LlmApiKeys;
|
|
20
26
|
system?: string;
|
|
@@ -23,14 +29,16 @@ export declare function generateTextWithModelId({ modelId, apiKeys, system, prom
|
|
|
23
29
|
maxOutputTokens?: number;
|
|
24
30
|
timeoutMs: number;
|
|
25
31
|
fetchImpl: typeof fetch;
|
|
26
|
-
|
|
32
|
+
forceOpenRouter?: boolean;
|
|
33
|
+
retries?: number;
|
|
34
|
+
onRetry?: (notice: RetryNotice) => void;
|
|
27
35
|
}): Promise<{
|
|
28
36
|
text: string;
|
|
29
37
|
canonicalModelId: string;
|
|
30
38
|
provider: 'xai' | 'openai' | 'google' | 'anthropic';
|
|
31
39
|
usage: LlmTokenUsage | null;
|
|
32
40
|
}>;
|
|
33
|
-
export declare function streamTextWithModelId({ modelId, apiKeys, system, prompt, temperature, maxOutputTokens, timeoutMs, fetchImpl,
|
|
41
|
+
export declare function streamTextWithModelId({ modelId, apiKeys, system, prompt, temperature, maxOutputTokens, timeoutMs, fetchImpl, forceOpenRouter, }: {
|
|
34
42
|
modelId: string;
|
|
35
43
|
apiKeys: LlmApiKeys;
|
|
36
44
|
system?: string;
|
|
@@ -39,7 +47,7 @@ export declare function streamTextWithModelId({ modelId, apiKeys, system, prompt
|
|
|
39
47
|
maxOutputTokens?: number;
|
|
40
48
|
timeoutMs: number;
|
|
41
49
|
fetchImpl: typeof fetch;
|
|
42
|
-
|
|
50
|
+
forceOpenRouter?: boolean;
|
|
43
51
|
}): Promise<{
|
|
44
52
|
textStream: AsyncIterable<string>;
|
|
45
53
|
canonicalModelId: string;
|
|
@@ -47,3 +55,4 @@ export declare function streamTextWithModelId({ modelId, apiKeys, system, prompt
|
|
|
47
55
|
usage: Promise<LlmTokenUsage | null>;
|
|
48
56
|
lastError: () => unknown;
|
|
49
57
|
}>;
|
|
58
|
+
export {};
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import type { ConvertHtmlToMarkdown } from '../content/link-preview/deps.js';
|
|
2
2
|
import type { LlmTokenUsage } from './generate-text.js';
|
|
3
|
-
|
|
4
|
-
export declare function createHtmlToMarkdownConverter({ modelId, xaiApiKey, googleApiKey, openaiApiKey, anthropicApiKey, openrouterApiKey, openrouter, fetchImpl, onUsage, }: {
|
|
3
|
+
export declare function createHtmlToMarkdownConverter({ modelId, forceOpenRouter, xaiApiKey, googleApiKey, openaiApiKey, anthropicApiKey, openrouterApiKey, fetchImpl, retries, onRetry, onUsage, }: {
|
|
5
4
|
modelId: string;
|
|
5
|
+
forceOpenRouter?: boolean;
|
|
6
6
|
xaiApiKey: string | null;
|
|
7
7
|
googleApiKey: string | null;
|
|
8
8
|
openaiApiKey: string | null;
|
|
9
9
|
fetchImpl: typeof fetch;
|
|
10
10
|
anthropicApiKey: string | null;
|
|
11
11
|
openrouterApiKey: string | null;
|
|
12
|
-
|
|
12
|
+
retries?: number;
|
|
13
|
+
onRetry?: (notice: {
|
|
14
|
+
attempt: number;
|
|
15
|
+
maxRetries: number;
|
|
16
|
+
delayMs: number;
|
|
17
|
+
error: unknown;
|
|
18
|
+
}) => void;
|
|
13
19
|
onUsage?: (usage: {
|
|
14
20
|
model: string;
|
|
15
21
|
provider: 'xai' | 'openai' | 'google' | 'anthropic';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AutoRuleKind, CliProvider, SummarizeConfig } from './config.js';
|
|
2
|
+
import type { LiteLlmCatalog } from './pricing/litellm.js';
|
|
3
|
+
export type AutoSelectionInput = {
|
|
4
|
+
kind: AutoRuleKind;
|
|
5
|
+
promptTokens: number | null;
|
|
6
|
+
desiredOutputTokens: number | null;
|
|
7
|
+
requiresVideoUnderstanding: boolean;
|
|
8
|
+
env: Record<string, string | undefined>;
|
|
9
|
+
config: SummarizeConfig | null;
|
|
10
|
+
catalog: LiteLlmCatalog | null;
|
|
11
|
+
openrouterProvidersFromEnv: string[] | null;
|
|
12
|
+
cliAvailability?: Partial<Record<CliProvider, boolean>>;
|
|
13
|
+
};
|
|
14
|
+
export type AutoModelAttempt = {
|
|
15
|
+
transport: 'native' | 'openrouter' | 'cli';
|
|
16
|
+
userModelId: string;
|
|
17
|
+
llmModelId: string | null;
|
|
18
|
+
openrouterProviders: string[] | null;
|
|
19
|
+
forceOpenRouter: boolean;
|
|
20
|
+
requiredEnv: 'XAI_API_KEY' | 'OPENAI_API_KEY' | 'GEMINI_API_KEY' | 'ANTHROPIC_API_KEY' | 'OPENROUTER_API_KEY' | 'CLI_CLAUDE' | 'CLI_CODEX' | 'CLI_GEMINI';
|
|
21
|
+
debug: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function buildAutoModelAttempts(input: AutoSelectionInput): AutoModelAttempt[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CliProvider } from './config.js';
|
|
2
|
+
export type FixedModelSpec = {
|
|
3
|
+
transport: 'native';
|
|
4
|
+
userModelId: string;
|
|
5
|
+
llmModelId: string;
|
|
6
|
+
provider: 'xai' | 'openai' | 'google' | 'anthropic';
|
|
7
|
+
openrouterProviders: string[] | null;
|
|
8
|
+
forceOpenRouter: false;
|
|
9
|
+
requiredEnv: 'XAI_API_KEY' | 'OPENAI_API_KEY' | 'GEMINI_API_KEY' | 'ANTHROPIC_API_KEY';
|
|
10
|
+
} | {
|
|
11
|
+
transport: 'openrouter';
|
|
12
|
+
userModelId: string;
|
|
13
|
+
openrouterModelId: string;
|
|
14
|
+
llmModelId: string;
|
|
15
|
+
openrouterProviders: string[] | null;
|
|
16
|
+
forceOpenRouter: true;
|
|
17
|
+
requiredEnv: 'OPENROUTER_API_KEY';
|
|
18
|
+
} | {
|
|
19
|
+
transport: 'cli';
|
|
20
|
+
userModelId: string;
|
|
21
|
+
llmModelId: null;
|
|
22
|
+
openrouterProviders: null;
|
|
23
|
+
forceOpenRouter: false;
|
|
24
|
+
requiredEnv: 'CLI_CLAUDE' | 'CLI_CODEX' | 'CLI_GEMINI';
|
|
25
|
+
cliProvider: CliProvider;
|
|
26
|
+
cliModel: string | null;
|
|
27
|
+
};
|
|
28
|
+
export type RequestedModel = {
|
|
29
|
+
kind: 'auto';
|
|
30
|
+
} | ({
|
|
31
|
+
kind: 'fixed';
|
|
32
|
+
} & FixedModelSpec);
|
|
33
|
+
export declare function parseRequestedModelId(raw: string): RequestedModel;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SummaryLengthTarget } from './link-summary.js';
|
|
2
|
+
export declare function buildPathSummaryPrompt({ kindLabel, filePath, filename, mediaType, summaryLength, }: {
|
|
3
|
+
kindLabel: 'file' | 'image';
|
|
4
|
+
filePath: string;
|
|
5
|
+
filename: string | null;
|
|
6
|
+
mediaType: string | null;
|
|
7
|
+
summaryLength: SummaryLengthTarget;
|
|
8
|
+
}): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export type { SummaryLength } from '../shared/contracts.js';
|
|
2
|
+
export { buildPathSummaryPrompt } from './cli.js';
|
|
2
3
|
export { buildFileSummaryPrompt, buildFileTextSummaryPrompt } from './file.js';
|
|
3
4
|
export { buildLinkSummaryPrompt, estimateMaxCompletionTokensForCharacters, pickSummaryLengthForCharacters, type ShareContextEntry, SUMMARY_LENGTH_TO_TOKENS, type SummaryLengthTarget, } from './link-summary.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type GenerateFreeOptions = {
|
|
2
|
+
runs: number;
|
|
3
|
+
smart: number;
|
|
4
|
+
maxCandidates: number;
|
|
5
|
+
concurrency: number;
|
|
6
|
+
timeoutMs: number;
|
|
7
|
+
minParamB: number;
|
|
8
|
+
maxAgeDays: number;
|
|
9
|
+
setDefault: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function refreshFree({ env, fetchImpl, stdout, stderr, verbose, options, }: {
|
|
12
|
+
env: Record<string, string | undefined>;
|
|
13
|
+
fetchImpl: typeof fetch;
|
|
14
|
+
stdout: NodeJS.WritableStream;
|
|
15
|
+
stderr: NodeJS.WritableStream;
|
|
16
|
+
verbose?: boolean;
|
|
17
|
+
options?: Partial<GenerateFreeOptions>;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
export {};
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const FALLBACK_VERSION = "0.
|
|
1
|
+
export declare const FALLBACK_VERSION = "0.5.0";
|
|
2
2
|
export declare function resolvePackageVersion(importMetaUrl?: string): string;
|
package/docs/README.md
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
- `docs/youtube.md` — YouTube transcript extraction (youtubei / captionTracks / Apify)
|
|
5
5
|
- `docs/firecrawl.md` — Firecrawl mode + API key
|
|
6
6
|
- `docs/llm.md` — LLM summarization + model config (Gateway/OpenAI)
|
|
7
|
+
- `docs/cli.md` — CLI models (Claude/Codex/Gemini)
|
|
8
|
+
- `docs/model-auto.md` — automatic model selection (`--model auto`)
|
|
9
|
+
- `docs/manual-tests.md` — manual end-to-end test checklist
|
|
7
10
|
- `docs/extract-only.md` — extract mode (no summary LLM call)
|
|
8
11
|
|
|
9
12
|
## Website
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# CLI models
|
|
2
|
+
|
|
3
|
+
Summarize can use installed CLIs (Claude, Codex, Gemini) as local model backends.
|
|
4
|
+
|
|
5
|
+
## Model ids
|
|
6
|
+
|
|
7
|
+
- `cli/claude/<model>` (e.g. `cli/claude/sonnet`)
|
|
8
|
+
- `cli/codex/<model>` (e.g. `cli/codex/gpt-5.2`)
|
|
9
|
+
- `cli/gemini/<model>` (e.g. `cli/gemini/gemini-3-flash-preview`)
|
|
10
|
+
|
|
11
|
+
Use `--cli [provider]` (case-insensitive) for the provider default, or `--model cli/<provider>/<model>` to pin a model.
|
|
12
|
+
If `--cli` is provided without a provider, auto selection is used with CLI enabled.
|
|
13
|
+
|
|
14
|
+
## Auto mode
|
|
15
|
+
|
|
16
|
+
Auto mode does **not** use CLIs unless you set `cli.enabled` in config.
|
|
17
|
+
|
|
18
|
+
Why: CLI adds ~4s latency per attempt and higher variance.
|
|
19
|
+
Recommendation: enable only Gemini unless you have a reason to add others.
|
|
20
|
+
|
|
21
|
+
Gemini CLI performance: summarize sets `GEMINI_CLI_NO_RELAUNCH=true` for Gemini CLI runs to avoid a costly self-relaunch (can be overridden by setting it yourself).
|
|
22
|
+
|
|
23
|
+
When enabled, auto prepends CLI attempts in the order listed in `cli.enabled`
|
|
24
|
+
(recommended: `["gemini"]`).
|
|
25
|
+
|
|
26
|
+
Enable CLI attempts:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"cli": { "enabled": ["gemini"] }
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Disable CLI attempts:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"cli": { "enabled": [] }
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Note: when `cli.enabled` is set, it also acts as an allowlist for explicit `--cli` / `--model cli/...`.
|
|
43
|
+
|
|
44
|
+
## CLI discovery
|
|
45
|
+
|
|
46
|
+
Binary lookup:
|
|
47
|
+
|
|
48
|
+
- `CLAUDE_PATH`, `CODEX_PATH`, `GEMINI_PATH` (optional overrides)
|
|
49
|
+
- Otherwise uses `PATH`
|
|
50
|
+
|
|
51
|
+
## Attachments (images/files)
|
|
52
|
+
|
|
53
|
+
When a CLI attempt is used for an image or non-text file, Summarize switches to a
|
|
54
|
+
path-based prompt and enables the required tool flags:
|
|
55
|
+
|
|
56
|
+
- Claude: `--tools Read --dangerously-skip-permissions`
|
|
57
|
+
- Gemini: `--yolo` and `--include-directories <dir>`
|
|
58
|
+
- Codex: `codex exec --output-last-message ...` and `-i <image>` for images
|
|
59
|
+
|
|
60
|
+
## Config
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"cli": {
|
|
65
|
+
"enabled": ["claude", "gemini", "codex"],
|
|
66
|
+
"codex": { "model": "gpt-5.2" },
|
|
67
|
+
"gemini": { "model": "gemini-3-flash-preview", "extraArgs": ["--verbose"] },
|
|
68
|
+
"claude": {
|
|
69
|
+
"model": "sonnet",
|
|
70
|
+
"binary": "/usr/local/bin/claude",
|
|
71
|
+
"extraArgs": ["--verbose"]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Notes:
|
|
78
|
+
|
|
79
|
+
- CLI output is treated as text only (no token accounting).
|
|
80
|
+
- If a CLI call fails, auto mode falls back to the next candidate.
|
|
81
|
+
|
|
82
|
+
## Generate free preset (OpenRouter)
|
|
83
|
+
|
|
84
|
+
`summarize` ships with a built-in preset `free`, backed by OpenRouter `:free` models.
|
|
85
|
+
To regenerate the candidate list (and persist it in your config):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
summarize refresh-free
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Options:
|
|
92
|
+
|
|
93
|
+
- `--runs 2` (default): extra timing runs per selected model (total runs = 1 + runs)
|
|
94
|
+
- `--smart 3` (default): number of “smart-first” picks (rest filled by fastest)
|
|
95
|
+
- `--set-default`: also sets `"model": "free"` in `~/.summarize/config.json`
|
package/docs/config.md
CHANGED
|
@@ -15,14 +15,136 @@ For `model`:
|
|
|
15
15
|
1. CLI flag `--model`
|
|
16
16
|
2. Env `SUMMARIZE_MODEL`
|
|
17
17
|
3. Config file `model`
|
|
18
|
-
4. Built-in default (`
|
|
18
|
+
4. Built-in default (`auto`)
|
|
19
19
|
|
|
20
20
|
## Format
|
|
21
21
|
|
|
22
22
|
`~/.summarize/config.json`:
|
|
23
23
|
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"model": { "id": "google/gemini-3-flash-preview" }
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Shorthand (equivalent):
|
|
31
|
+
|
|
24
32
|
```json
|
|
25
33
|
{
|
|
26
34
|
"model": "google/gemini-3-flash-preview"
|
|
27
35
|
}
|
|
28
36
|
```
|
|
37
|
+
|
|
38
|
+
`model` can also be auto:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"model": { "mode": "auto" }
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Shorthand (equivalent):
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"model": "auto"
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Presets
|
|
55
|
+
|
|
56
|
+
Define presets you can select via `--model <preset>`:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"models": {
|
|
61
|
+
"fast": { "id": "openai/gpt-5-mini" },
|
|
62
|
+
"or-free": {
|
|
63
|
+
"rules": [
|
|
64
|
+
{
|
|
65
|
+
"candidates": [
|
|
66
|
+
"openrouter/google/gemini-2.0-flash-exp:free",
|
|
67
|
+
"openrouter/meta-llama/llama-3.3-70b-instruct:free"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Notes:
|
|
77
|
+
|
|
78
|
+
- `auto` is reserved and can’t be defined as a preset.
|
|
79
|
+
- `free` is built-in (OpenRouter `:free` candidates). Override it by defining `models.free` in your config, or regenerate it via `summarize refresh-free`.
|
|
80
|
+
|
|
81
|
+
Use a preset as your default `model`:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"model": "fast"
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Notes:
|
|
90
|
+
|
|
91
|
+
- For presets, `"mode": "auto"` is optional when `"rules"` is present.
|
|
92
|
+
|
|
93
|
+
For auto selection with rules:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"model": {
|
|
98
|
+
"mode": "auto",
|
|
99
|
+
"rules": [
|
|
100
|
+
{
|
|
101
|
+
"when": ["video"],
|
|
102
|
+
"candidates": ["google/gemini-3-flash-preview"]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"when": ["website", "youtube"],
|
|
106
|
+
"bands": [
|
|
107
|
+
{
|
|
108
|
+
"token": { "max": 8000 },
|
|
109
|
+
"candidates": ["openai/gpt-5-mini"]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"candidates": ["xai/grok-4-fast-non-reasoning"]
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"candidates": ["openai/gpt-5-mini", "openrouter/openai/gpt-5-mini"]
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
"media": { "videoMode": "auto" }
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Notes:
|
|
126
|
+
|
|
127
|
+
- Parsed leniently (JSON5), but **comments are not allowed**.
|
|
128
|
+
- Unknown keys are ignored.
|
|
129
|
+
- `model.rules` is optional. If omitted, built-in defaults apply.
|
|
130
|
+
- `model.rules[].when` (optional) must be an array (e.g. `["video","youtube"]`).
|
|
131
|
+
- `model.rules[]` must use either `candidates` or `bands`.
|
|
132
|
+
|
|
133
|
+
## CLI config
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"cli": {
|
|
138
|
+
"enabled": ["gemini"],
|
|
139
|
+
"codex": { "model": "gpt-5.2" },
|
|
140
|
+
"claude": { "binary": "/usr/local/bin/claude", "extraArgs": ["--verbose"] }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Notes:
|
|
146
|
+
|
|
147
|
+
- `cli.enabled` is an allowlist (auto uses CLIs only when set; explicit `--cli` / `--model cli/...` must be included).
|
|
148
|
+
- Recommendation: keep `cli.enabled` to `["gemini"]` unless you have a reason to add others (extra latency/variance).
|
|
149
|
+
- `cli.<provider>.binary` overrides CLI binary discovery.
|
|
150
|
+
- `cli.<provider>.extraArgs` appends extra CLI args.
|
package/docs/llm.md
CHANGED
|
@@ -1,37 +1,57 @@
|
|
|
1
1
|
# LLM / summarization mode
|
|
2
2
|
|
|
3
|
-
By default `summarize` will call an LLM using **direct provider API keys**.
|
|
3
|
+
By default `summarize` will call an LLM using **direct provider API keys**. When CLI tools are
|
|
4
|
+
installed, auto mode can use local CLI models when `cli.enabled` is set (see `docs/cli.md`).
|
|
4
5
|
|
|
5
6
|
## Defaults
|
|
6
7
|
|
|
7
|
-
- Default model: `
|
|
8
|
+
- Default model: `auto`
|
|
8
9
|
- Override with `SUMMARIZE_MODEL`, config file (`model`), or `--model`.
|
|
9
10
|
|
|
10
11
|
## Env
|
|
11
12
|
|
|
13
|
+
- `.env` (optional): when running the CLI, `summarize` also reads `.env` in the current working directory and merges it into the environment (real env vars win).
|
|
12
14
|
- `XAI_API_KEY` (required for `xai/...` models)
|
|
13
15
|
- `OPENAI_API_KEY` (required for `openai/...` models)
|
|
14
16
|
- `OPENAI_BASE_URL` (optional; OpenAI-compatible API endpoint, e.g. OpenRouter)
|
|
15
|
-
- `OPENROUTER_API_KEY` (optional; used when `OPENAI_BASE_URL` points to OpenRouter)
|
|
17
|
+
- `OPENROUTER_API_KEY` (optional; required for `openrouter/...` models; also used when `OPENAI_BASE_URL` points to OpenRouter)
|
|
16
18
|
- `GEMINI_API_KEY` (required for `google/...` models; also accepts `GOOGLE_GENERATIVE_AI_API_KEY` / `GOOGLE_API_KEY`)
|
|
17
19
|
- `ANTHROPIC_API_KEY` (required for `anthropic/...` models)
|
|
18
20
|
- `SUMMARIZE_MODEL` (optional; overrides default model selection)
|
|
21
|
+
- `CLAUDE_PATH` / `CODEX_PATH` / `GEMINI_PATH` (optional; override CLI binary paths)
|
|
19
22
|
|
|
20
23
|
## Flags
|
|
21
24
|
|
|
22
25
|
- `--model <model>`
|
|
23
26
|
- Examples:
|
|
27
|
+
- `cli/codex/gpt-5.2`
|
|
28
|
+
- `cli/claude/sonnet`
|
|
29
|
+
- `cli/gemini/gemini-3-flash-preview`
|
|
24
30
|
- `google/gemini-3-flash-preview`
|
|
25
|
-
- `openai/gpt-5
|
|
31
|
+
- `openai/gpt-5-mini`
|
|
26
32
|
- `xai/grok-4-fast-non-reasoning`
|
|
27
33
|
- `google/gemini-2.0-flash`
|
|
28
34
|
- `anthropic/claude-sonnet-4-5`
|
|
35
|
+
- `openrouter/meta-llama/llama-3.3-70b-instruct:free` (force OpenRouter)
|
|
36
|
+
- `--cli [provider]`
|
|
37
|
+
- Examples: `--cli claude`, `--cli Gemini`, `--cli codex` (equivalent to `--model cli/<provider>`); `--cli` alone uses auto selection with CLI enabled.
|
|
38
|
+
- `--model auto`
|
|
39
|
+
- See `docs/model-auto.md`
|
|
40
|
+
- `--model <preset>`
|
|
41
|
+
- Uses a config-defined preset (see `docs/config.md` → “Presets”).
|
|
42
|
+
- `--video-mode auto|transcript|understand`
|
|
43
|
+
- Only relevant for video inputs / video-only pages.
|
|
29
44
|
- `--length short|medium|long|xl|xxl|<chars>`
|
|
30
45
|
- This is *soft guidance* to the model (no hard truncation).
|
|
31
46
|
- Minimum numeric value: 50 chars.
|
|
47
|
+
- Default: `long`.
|
|
32
48
|
- `--max-output-tokens <count>`
|
|
33
49
|
- Hard cap for output tokens (optional).
|
|
50
|
+
- If omitted, no max token parameter is sent (provider default).
|
|
34
51
|
- Minimum numeric value: 16.
|
|
52
|
+
- Recommendation: prefer `--length` unless you need a hard cap (some providers count “reasoning” into the cap).
|
|
53
|
+
- `--retries <count>`
|
|
54
|
+
- LLM retry attempts on timeout (default: 1).
|
|
35
55
|
- `--json` (includes prompt + summary in one JSON object)
|
|
36
56
|
|
|
37
57
|
## Input limits
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Manual tests
|
|
2
|
+
|
|
3
|
+
Goal: sanity-check auto selection + presets end-to-end.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
- `OPENAI_API_KEY=...` (optional)
|
|
8
|
+
- `GEMINI_API_KEY=...` (optional)
|
|
9
|
+
- `ANTHROPIC_API_KEY=...` (optional)
|
|
10
|
+
- `XAI_API_KEY=...` (optional)
|
|
11
|
+
- `OPENROUTER_API_KEY=...` (optional)
|
|
12
|
+
|
|
13
|
+
Tip: use `--verbose` to see model attempts + the chosen model.
|
|
14
|
+
|
|
15
|
+
## Auto (default)
|
|
16
|
+
|
|
17
|
+
- Website summary (should pick a model, show it in spinner):
|
|
18
|
+
- `summarize --max-output-tokens 200 https://example.com`
|
|
19
|
+
- No-model-needed shortcut (should print extracted text; no footer “no model needed”):
|
|
20
|
+
- `summarize --max-output-tokens 99999 https://example.com`
|
|
21
|
+
- Missing-key skip (configure only one key; should skip other providers, still succeed):
|
|
22
|
+
- Set only `OPENAI_API_KEY`, then run a website summary; should not try Gemini/Anthropic/XAI.
|
|
23
|
+
|
|
24
|
+
## Presets
|
|
25
|
+
|
|
26
|
+
- Define a preset in `~/.summarize/config.json` (see `docs/config.md` → “Presets”), then:
|
|
27
|
+
- `summarize --model <preset> --max-output-tokens 200 https://example.com`
|
|
28
|
+
- If the preset contains OpenRouter models, ensure `OPENROUTER_API_KEY` is set.
|
|
29
|
+
|
|
30
|
+
## Images
|
|
31
|
+
|
|
32
|
+
- Local image (auto uses API models by default; enable CLI via `cli.enabled` to test CLIs):
|
|
33
|
+
- `summarize ./path/to/image.png --max-output-tokens 200`
|
|
34
|
+
|
|
35
|
+
## Video
|
|
36
|
+
|
|
37
|
+
- YouTube:
|
|
38
|
+
- `summarize https://www.youtube.com/watch?v=dQw4w9WgXcQ --max-output-tokens 200`
|
|
39
|
+
- Local video understanding (requires Gemini video-capable model; otherwise expect an error or transcript-only behavior depending on input):
|
|
40
|
+
- `summarize ./path/to/video.mp4 --max-output-tokens 200`
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Auto model selection (`--model auto`)
|
|
2
|
+
|
|
3
|
+
`--model auto` picks a model based on input kind + token size, and retries with fallbacks when something fails.
|
|
4
|
+
|
|
5
|
+
This is also the built-in default when you don’t specify a model.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Builds an ordered list of model “attempts” from `candidates[]` (native first, optional OpenRouter fallback).
|
|
10
|
+
- Skips attempts that don’t have the required API key configured.
|
|
11
|
+
- On any request error, tries the next attempt.
|
|
12
|
+
- If no model is usable, prints the extracted text (no LLM summary). Use `--extract` if you want the raw extracted content even when models are available.
|
|
13
|
+
- Auto prepends CLI attempts only when `cli.enabled` is set (see `docs/cli.md`).
|
|
14
|
+
- Order follows `cli.enabled`.
|
|
15
|
+
|
|
16
|
+
## OpenRouter vs native
|
|
17
|
+
|
|
18
|
+
Model ids:
|
|
19
|
+
|
|
20
|
+
- Native: `<provider>/<model>` (e.g. `openai/gpt-5-mini`, `google/gemini-3-flash-preview`)
|
|
21
|
+
- Forced OpenRouter: `openrouter/<author>/<slug>` (e.g. `openrouter/meta-llama/llama-3.3-70b-instruct:free`)
|
|
22
|
+
|
|
23
|
+
Behavior:
|
|
24
|
+
|
|
25
|
+
- If you pass an `openrouter/...` model id, the request uses OpenRouter (and requires `OPENROUTER_API_KEY`).
|
|
26
|
+
- If you pass a native model id, the CLI prefers the native provider SDK when its key is available, and can fall back to OpenRouter when no native key exists (and `OPENROUTER_API_KEY` is set).
|
|
27
|
+
|
|
28
|
+
## How selection works
|
|
29
|
+
|
|
30
|
+
- Uses the order you provide in `model.rules[].candidates[]` (or `bands[].candidates[]`).
|
|
31
|
+
- Filters out candidates that can’t fit the prompt (max input tokens, LiteLLM catalog).
|
|
32
|
+
- For a native candidate, auto mode may add an OpenRouter fallback attempt right after it (when `OPENROUTER_API_KEY` is set and video understanding isn’t required).
|
|
33
|
+
|
|
34
|
+
Notes:
|
|
35
|
+
|
|
36
|
+
- Auto mode is non-streaming (so a failed attempt won’t partially print output).
|
|
37
|
+
- Video understanding is only attempted when `--video-mode` is `auto` or `understand`, and a video-capable model is selected.
|
|
38
|
+
|
|
39
|
+
## Config
|
|
40
|
+
|
|
41
|
+
Default config file: `~/.summarize/config.json`
|
|
42
|
+
|
|
43
|
+
This file is parsed leniently (JSON5), but **comments are not allowed**.
|
|
44
|
+
|
|
45
|
+
`model.rules` is optional; when omitted, built-in defaults apply.
|
|
46
|
+
|
|
47
|
+
`model.rules[].when` is optional, and when present must be an array (e.g. `["video","youtube"]`).
|
|
48
|
+
|
|
49
|
+
Rules can be either:
|
|
50
|
+
|
|
51
|
+
- `candidates: string[]`
|
|
52
|
+
- `bands: [{ token?: { min?: number; max?: number }, candidates: string[] }]`
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"model": {
|
|
59
|
+
"mode": "auto",
|
|
60
|
+
"rules": [
|
|
61
|
+
{
|
|
62
|
+
"when": ["video"],
|
|
63
|
+
"candidates": ["google/gemini-3-flash-preview"]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"when": ["website", "youtube"],
|
|
67
|
+
"bands": [
|
|
68
|
+
{
|
|
69
|
+
"token": { "max": 8000 },
|
|
70
|
+
"candidates": ["openai/gpt-5-mini"]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"candidates": ["xai/grok-4-fast-non-reasoning"]
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"candidates": ["openai/gpt-5-mini", "openrouter/openai/gpt-5-mini"]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
"media": { "videoMode": "auto" }
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Minimal shorthand:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"model": "auto"
|
|
91
|
+
}
|
|
92
|
+
```
|
package/docs/site/assets/site.js
CHANGED
|
@@ -31,24 +31,27 @@ const highlightNav = () => {
|
|
|
31
31
|
|
|
32
32
|
const wireCopyButtons = () => {
|
|
33
33
|
const buttons = document.querySelectorAll('[data-copy]')
|
|
34
|
+
const handleCopyClick = async (button) => {
|
|
35
|
+
const selector = button.getAttribute('data-copy')
|
|
36
|
+
const target = selector ? document.querySelector(selector) : null
|
|
37
|
+
const text = target?.textContent?.trim() ?? ''
|
|
38
|
+
if (!text) return
|
|
39
|
+
try {
|
|
40
|
+
await navigator.clipboard.writeText(text)
|
|
41
|
+
const prev = button.textContent ?? ''
|
|
42
|
+
button.textContent = 'Copied'
|
|
43
|
+
button.setAttribute('data-copied', '1')
|
|
44
|
+
window.setTimeout(() => {
|
|
45
|
+
button.textContent = prev
|
|
46
|
+
button.removeAttribute('data-copied')
|
|
47
|
+
}, 900)
|
|
48
|
+
} catch {
|
|
49
|
+
// ignore
|
|
50
|
+
}
|
|
51
|
+
}
|
|
34
52
|
for (const button of buttons) {
|
|
35
|
-
button.addEventListener('click',
|
|
36
|
-
|
|
37
|
-
const target = selector ? document.querySelector(selector) : null
|
|
38
|
-
const text = target?.textContent?.trim() ?? ''
|
|
39
|
-
if (!text) return
|
|
40
|
-
try {
|
|
41
|
-
await navigator.clipboard.writeText(text)
|
|
42
|
-
const prev = button.textContent ?? ''
|
|
43
|
-
button.textContent = 'Copied'
|
|
44
|
-
button.setAttribute('data-copied', '1')
|
|
45
|
-
window.setTimeout(() => {
|
|
46
|
-
button.textContent = prev
|
|
47
|
-
button.removeAttribute('data-copied')
|
|
48
|
-
}, 900)
|
|
49
|
-
} catch {
|
|
50
|
-
// ignore
|
|
51
|
-
}
|
|
53
|
+
button.addEventListener('click', () => {
|
|
54
|
+
void handleCopyClick(button)
|
|
52
55
|
})
|
|
53
56
|
}
|
|
54
57
|
}
|