Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,19 +304,20 @@ Use `summarize --help` or `summarize help` for the full help text.
- `--verbose`: debug/diagnostics on stderr
- `--metrics off|on|detailed`: metrics output (default `on`)

### Coding CLIs (Codex, Claude, Gemini, Agent)
### Coding CLIs (Codex, Claude, Gemini, Agent, OpenCode)

Summarize can use common coding CLIs as local model backends:

- `codex` -> `--cli codex` / `--model cli/codex/<model>`
- `claude` -> `--cli claude` / `--model cli/claude/<model>`
- `gemini` -> `--cli gemini` / `--model cli/gemini/<model>`
- `agent` (Cursor Agent CLI) -> `--cli agent` / `--model cli/agent/<model>`
- `opencode` -> `--cli opencode` / `--model cli/opencode/<provider/model>` (or `cli/opencode` for `cli.opencode.model` / the OpenCode default model)

Requirements:

- Binary installed and on `PATH` (or set `CODEX_PATH`, `CLAUDE_PATH`, `GEMINI_PATH`, `AGENT_PATH`)
- Provider authenticated (`codex login`, `claude auth`, `gemini` login flow, `agent login` or `CURSOR_API_KEY`)
- Binary installed and on `PATH` (or set `CODEX_PATH`, `CLAUDE_PATH`, `GEMINI_PATH`, `AGENT_PATH`, `OPENCODE_PATH`)
- Provider authenticated (`codex login`, `claude auth`, `gemini` login flow, `agent login` or `CURSOR_API_KEY`, `opencode auth login`)

Quick smoke test:

Expand All @@ -327,13 +328,14 @@ summarize --cli codex --plain --timeout 2m /tmp/summarize-cli-smoke.txt
summarize --cli claude --plain --timeout 2m /tmp/summarize-cli-smoke.txt
summarize --cli gemini --plain --timeout 2m /tmp/summarize-cli-smoke.txt
summarize --cli agent --plain --timeout 2m /tmp/summarize-cli-smoke.txt
summarize --cli opencode --plain --timeout 2m /tmp/summarize-cli-smoke.txt
```

Set explicit CLI allowlist/order:

```json
{
"cli": { "enabled": ["codex", "claude", "gemini", "agent"] }
"cli": { "enabled": ["codex", "claude", "gemini", "agent", "opencode"] }
}
```

Expand All @@ -345,7 +347,7 @@ Configure implicit auto CLI fallback:
"autoFallback": {
"enabled": true,
"onlyWhenNoApiKeys": true,
"order": ["claude", "gemini", "codex", "agent"]
"order": ["claude", "gemini", "codex", "agent", "opencode"]
}
}
}
Expand All @@ -361,7 +363,7 @@ CLI attempts are prepended when:
- `cli.enabled` is set (explicit allowlist/order), or
- implicit auto selection is active and `cli.autoFallback` is enabled.

Default fallback behavior: only when no API keys are configured, order `claude, gemini, codex, agent`, and remember/prioritize last successful provider (`~/.summarize/cli-state.json`).
Default fallback behavior: only when no API keys are configured, order `claude, gemini, codex, agent, opencode`, and remember/prioritize last successful provider (`~/.summarize/cli-state.json`).

Set explicit CLI attempts:

Expand Down
24 changes: 14 additions & 10 deletions apps/chrome-extension/src/entrypoints/background/panel-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ export function resolveOptionsUrl(): string {
return chrome.runtime.getURL(page);
}

function isContentTabUrl(url: string | null | undefined): url is string {
if (!url) return false;
return !(
url.startsWith("chrome-extension://") ||
url.startsWith("chrome://") ||
url.startsWith("moz-extension://") ||
url.startsWith("edge://") ||
url.startsWith("about:")
);
}

export async function openOptionsWindow() {
const url = resolveOptionsUrl();
try {
Expand Down Expand Up @@ -86,11 +97,7 @@ export async function getActiveTab(windowId?: number): Promise<chrome.tabs.Tab |
? { active: true, windowId }
: { active: true, currentWindow: true };
const [activeTab] = await chrome.tabs.query(query);
if (
activeTab?.url &&
!activeTab.url.startsWith("chrome-extension://") &&
!activeTab.url.startsWith("chrome://")
) {
if (isContentTabUrl(activeTab?.url)) {
return activeTab;
}

Expand All @@ -99,12 +106,9 @@ export async function getActiveTab(windowId?: number): Promise<chrome.tabs.Tab |
);
const contentTab =
fallbackTabs.find(
(tab) =>
typeof tab.url === "string" &&
!tab.url.startsWith("chrome-extension://") &&
!tab.url.startsWith("chrome://"),
(tab) => isContentTabUrl(tab.url),
) ?? null;
return contentTab ?? activeTab ?? null;
return contentTab;
}

export function normalizeUrl(value: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export function createModelPresetsController({
if (p.cliGemini === true) hints.push("cli/gemini");
if (p.cliCodex === true) hints.push("cli/codex");
if (p.cliAgent === true) hints.push("cli/agent");
if (p.cliOpencode === true) hints.push("cli/opencode");
}
if (discovery.localModelsSource && typeof discovery.localModelsSource === "object") {
hints.push("local: openai/<id>");
Expand Down
12 changes: 10 additions & 2 deletions apps/chrome-extension/src/lib/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,15 @@ function normalizeAutoCliOrder(value: unknown): string {
.filter(Boolean);
const out: string[] = [];
for (const item of items) {
if (item !== "claude" && item !== "gemini" && item !== "codex" && item !== "agent") continue;
if (
item !== "claude" &&
item !== "gemini" &&
item !== "codex" &&
item !== "agent" &&
item !== "opencode"
) {
continue;
}
if (!out.includes(item)) out.push(item);
}
return out.length > 0 ? out.join(",") : defaultSettings.autoCliOrder;
Expand Down Expand Up @@ -230,7 +238,7 @@ export const defaultSettings: Settings = {
summaryTimestamps: true,
extendedLogging: false,
autoCliFallback: true,
autoCliOrder: "claude,gemini,codex,agent",
autoCliOrder: "claude,gemini,codex,agent,opencode",
hoverPrompt:
"Plain text only (no Markdown). Summarize the linked page concisely in 1-2 sentences; aim for 100-200 characters.",
transcriber: "",
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ summary: "Docs index for summarize behaviors and modes."

- `docs/chrome-extension.md` — Chrome side panel extension + daemon setup/troubleshooting
- `docs/cache.md` — cache design + config (SQLite)
- `docs/cli.md` — CLI models (Claude/Codex/Gemini)
- `docs/cli.md` — CLI models (Claude/Codex/Gemini/OpenCode)
- `docs/config.md` — config file location, precedence, and schema
- `docs/extract-only.md` — extract mode (no summary LLM call)
- `docs/firecrawl.md` — Firecrawl mode + API key
Expand Down
2 changes: 1 addition & 1 deletion docs/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Returns cached chat history for the same cache key as `/v1/agent`.
- **Auto model**: uses existing auto-selection logic (`buildAutoModelAttempts`), preferring API-key transports and then CLI fallback when available.
- **Synthetic models**: created for OpenAI-compatible base URLs (local/openrouter).
- `maxOutputTokens` defaults to 2048 or `maxOutputTokens` override.
- CLI models are supported as auto fallback and via explicit `cli/<provider>/<model>` overrides.
- CLI models are supported as auto fallback and via explicit `cli/<provider>` or `cli/<provider>/<model>` overrides.
- If the daemon still says no model is available after key/install changes, restart or reinstall it so the saved environment snapshot refreshes.

## Page Content Payload
Expand Down
25 changes: 17 additions & 8 deletions docs/cli.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
---
summary: "CLI model providers and config for Claude, Codex, Gemini, and Cursor Agent."
summary: "CLI model providers and config for Claude, Codex, Gemini, Cursor Agent, and OpenCode."
read_when:
- "When changing CLI model integration."
---

# CLI models

Summarize can use installed CLIs (Claude, Codex, Gemini, Cursor Agent) as local model backends.
Summarize can use installed CLIs (Claude, Codex, Gemini, Cursor Agent, OpenCode) as local model backends.

## Model ids

- `cli/claude/<model>` (e.g. `cli/claude/sonnet`)
- `cli/codex/<model>` (e.g. `cli/codex/gpt-5.2`)
- `cli/gemini/<model>` (e.g. `cli/gemini/gemini-3-flash`)
- `cli/agent/<model>` (e.g. `cli/agent/gpt-5.2`)
- `cli/opencode` (uses `cli.opencode.model` when configured, otherwise the OpenCode runtime default model)
- `cli/opencode/<provider/model>` (e.g. `cli/opencode/openai/gpt-5.4`)

Use `--cli [provider]` (case-insensitive) for the provider default, or `--model cli/<provider>/<model>` to pin a model.
If `--cli` is provided without a provider, auto selection is used with CLI enabled.
Expand All @@ -28,7 +30,7 @@ Auto mode can prepend CLI attempts in two ways:
- Auto CLI fallback (`cli.autoFallback`, default enabled):
- Applies only to **implicit** auto (when no model is set via flag/env/config).
- Default behavior: only when no API key is configured.
- Default order: `claude, gemini, codex, agent`.
- Default order: `claude, gemini, codex, agent, opencode`.
- Remembers + prioritizes the last successful CLI provider (`~/.summarize/cli-state.json`).

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).
Expand All @@ -49,7 +51,7 @@ Configure auto CLI fallback:
"autoFallback": {
"enabled": true,
"onlyWhenNoApiKeys": true,
"order": ["claude", "gemini", "codex", "agent"]
"order": ["claude", "gemini", "codex", "agent", "opencode"]
}
}
}
Expand All @@ -70,7 +72,7 @@ Note: `--model auto` (explicit) does not trigger auto CLI fallback unless `cli.e
Binary lookup:

- `CLAUDE_PATH`, `CODEX_PATH`, `GEMINI_PATH` (optional overrides)
- `AGENT_PATH` (optional override)
- `AGENT_PATH`, `OPENCODE_PATH` (optional overrides)
- Otherwise uses `PATH`

## Attachments (images/files)
Expand All @@ -82,17 +84,18 @@ path-based prompt and enables the required tool flags:
- Gemini: `--yolo` and `--include-directories <dir>`
- Codex: `codex exec --output-last-message ...` and `-i <image>` for images
- Agent: uses built-in file tools in `agent --print` mode (no extra flags)
- OpenCode: `opencode run --format json --file <path>`

## Config

```json
{
"cli": {
"enabled": ["claude", "gemini", "codex", "agent"],
"enabled": ["claude", "gemini", "codex", "agent", "opencode"],
"autoFallback": {
"enabled": true,
"onlyWhenNoApiKeys": true,
"order": ["claude", "gemini", "codex", "agent"]
"order": ["claude", "gemini", "codex", "agent", "opencode"]
},
"codex": { "model": "gpt-5.2" },
"gemini": { "model": "gemini-3-flash", "extraArgs": ["--verbose"] },
Expand All @@ -104,17 +107,21 @@ path-based prompt and enables the required tool flags:
"agent": {
"model": "gpt-5.2",
"binary": "/usr/local/bin/agent"
},
"opencode": {
"binary": "/usr/local/bin/opencode"
}
}
}
```

Notes:

- CLI output is treated as text only (no token accounting).
- CLI output is treated as text; usage and cost are recorded when the CLI exposes them.
- If a CLI call fails, auto mode falls back to the next candidate.
- Cursor Agent CLI uses the `agent` binary and relies on Cursor CLI auth (login or `CURSOR_API_KEY`).
- Gemini CLI is invoked in headless mode with `--prompt` for compatibility with current Gemini CLI releases.
- OpenCode CLI is invoked as `opencode run --format json` and reads the summary prompt from stdin.

## Quick smoke test (all CLI providers)

Expand All @@ -127,9 +134,11 @@ summarize --cli codex --plain --timeout 2m /tmp/summarize-cli-smoke.txt
summarize --cli claude --plain --timeout 2m /tmp/summarize-cli-smoke.txt
summarize --cli gemini --plain --timeout 2m /tmp/summarize-cli-smoke.txt
summarize --cli agent --plain --timeout 2m /tmp/summarize-cli-smoke.txt
summarize --cli opencode --plain --timeout 2m /tmp/summarize-cli-smoke.txt
```

If Agent fails with auth, run `agent login` (interactive) or set `CURSOR_API_KEY`.
If OpenCode fails with auth, run `opencode auth login` for the provider you want to use.

## Generate free preset (OpenRouter)

Expand Down
9 changes: 5 additions & 4 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,15 +316,16 @@ Examples:
```json
{
"cli": {
"enabled": ["gemini", "agent"],
"enabled": ["gemini", "agent", "opencode"],
"autoFallback": {
"enabled": true,
"onlyWhenNoApiKeys": true,
"order": ["claude", "gemini", "codex", "agent"]
"order": ["claude", "gemini", "codex", "agent", "opencode"]
},
"codex": { "model": "gpt-5.2" },
"claude": { "binary": "/usr/local/bin/claude", "extraArgs": ["--verbose"] },
"agent": { "binary": "/usr/local/bin/agent", "model": "gpt-5.2" }
"agent": { "binary": "/usr/local/bin/agent", "model": "gpt-5.2" },
"opencode": { "binary": "/usr/local/bin/opencode" }
}
}
```
Expand All @@ -333,7 +334,7 @@ Notes:

- `cli.enabled` is an allowlist (and order) for auto + explicit CLI model ids.
- `cli.autoFallback` controls implicit-auto CLI fallback when `cli.enabled` is not set.
- Default auto fallback order: `claude, gemini, codex, agent`.
- Default auto fallback order: `claude, gemini, codex, agent, opencode`.
- Auto fallback stores the last successful provider in `~/.summarize/cli-state.json` and prioritizes it on the next run.
- `cli.<provider>.binary` overrides CLI binary discovery.
- `cli.<provider>.extraArgs` appends extra CLI args.
Expand Down
6 changes: 4 additions & 2 deletions docs/llm.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ installed, auto mode can use local CLI models via `cli.enabled` or implicit auto
- `ANTHROPIC_API_KEY` (required for `anthropic/...` models)
- `ANTHROPIC_BASE_URL` (optional; override Anthropic API endpoint)
- `SUMMARIZE_MODEL` (optional; overrides default model selection)
- `CLAUDE_PATH` / `CODEX_PATH` / `GEMINI_PATH` / `AGENT_PATH` (optional; override CLI binary paths)
- `CLAUDE_PATH` / `CODEX_PATH` / `GEMINI_PATH` / `AGENT_PATH` / `OPENCODE_PATH` (optional; override CLI binary paths)

## Flags

Expand All @@ -44,6 +44,8 @@ installed, auto mode can use local CLI models via `cli.enabled` or implicit auto
- `cli/claude/sonnet`
- `cli/gemini/gemini-3-flash`
- `cli/agent/gpt-5.2`
- `cli/opencode`
- `cli/opencode/openai/gpt-5.4`
- `google/gemini-3-flash`
- `openai/gpt-5-mini`
- `nvidia/z-ai/glm5`
Expand All @@ -53,7 +55,7 @@ installed, auto mode can use local CLI models via `cli.enabled` or implicit auto
- `anthropic/claude-sonnet-4-5`
- `openrouter/meta-llama/llama-3.3-70b-instruct:free` (force OpenRouter)
- `--cli [provider]`
- Examples: `--cli claude`, `--cli Gemini`, `--cli codex`, `--cli agent` (equivalent to `--model cli/<provider>`); `--cli` alone uses auto selection with CLI enabled.
- Examples: `--cli claude`, `--cli Gemini`, `--cli codex`, `--cli agent`, `--cli opencode` (equivalent to `--model cli/<provider>`); `--cli` alone uses auto selection with CLI enabled.
- `--model auto`
- See `docs/model-auto.md`
- `--model <preset>`
Expand Down
8 changes: 7 additions & 1 deletion src/config/parse-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ export function parseOptionalBaseUrl(raw: unknown): string | undefined {

export function parseCliProvider(value: unknown, path: string): CliProvider {
const trimmed = typeof value === "string" ? value.trim().toLowerCase() : "";
if (trimmed === "claude" || trimmed === "codex" || trimmed === "gemini" || trimmed === "agent") {
if (
trimmed === "claude" ||
trimmed === "codex" ||
trimmed === "gemini" ||
trimmed === "agent" ||
trimmed === "opencode"
) {
return trimmed as CliProvider;
}
throw new Error(`Invalid config file ${path}: unknown CLI provider "${String(value)}".`);
Expand Down
5 changes: 5 additions & 0 deletions src/config/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@ export function parseCliConfig(root: Record<string, unknown>, path: string): Cli
const codex = value.codex ? parseCliProviderConfig(value.codex, path, "codex") : undefined;
const gemini = value.gemini ? parseCliProviderConfig(value.gemini, path, "gemini") : undefined;
const agent = value.agent ? parseCliProviderConfig(value.agent, path, "agent") : undefined;
const opencode = value.opencode
? parseCliProviderConfig(value.opencode, path, "opencode")
: undefined;
if (typeof value.autoFallback !== "undefined" && typeof value.magicAuto !== "undefined") {
throw new Error(
`Invalid config file ${path}: use only one of "cli.autoFallback" or legacy "cli.magicAuto".`,
Expand Down Expand Up @@ -334,6 +337,7 @@ export function parseCliConfig(root: Record<string, unknown>, path: string): Cli
codex ||
gemini ||
agent ||
opencode ||
autoFallback ||
promptOverride ||
typeof allowTools === "boolean" ||
Expand All @@ -345,6 +349,7 @@ export function parseCliConfig(root: Record<string, unknown>, path: string): Cli
...(codex ? { codex } : {}),
...(gemini ? { gemini } : {}),
...(agent ? { agent } : {}),
...(opencode ? { opencode } : {}),
...(autoFallback ? { autoFallback } : {}),
...(promptOverride ? { promptOverride } : {}),
...(typeof allowTools === "boolean" ? { allowTools } : {}),
Expand Down
3 changes: 2 additions & 1 deletion src/config/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type AutoRuleKind = "text" | "website" | "youtube" | "image" | "video" | "file";
export type VideoMode = "auto" | "transcript" | "understand";
export type CliProvider = "claude" | "codex" | "gemini" | "agent";
export type CliProvider = "claude" | "codex" | "gemini" | "agent" | "opencode";
export type CliProviderConfig = {
binary?: string;
extraArgs?: string[];
Expand All @@ -18,6 +18,7 @@ export type CliConfig = {
codex?: CliProviderConfig;
gemini?: CliProviderConfig;
agent?: CliProviderConfig;
opencode?: CliProviderConfig;
autoFallback?: CliAutoFallbackConfig;
magicAuto?: CliAutoFallbackConfig;
promptOverride?: string;
Expand Down
4 changes: 3 additions & 1 deletion src/daemon/agent-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ function buildNoAgentModelAvailableError({
codex?: boolean;
gemini?: boolean;
agent?: boolean;
opencode?: boolean;
};
}): Error {
const checked = attempts.map((attempt) => attempt.userModelId);
Expand All @@ -152,7 +153,8 @@ function buildNoAgentModelAvailableError({
if (attempt.requiredEnv === "CLI_CLAUDE") return "claude";
if (attempt.requiredEnv === "CLI_CODEX") return "codex";
if (attempt.requiredEnv === "CLI_GEMINI") return "gemini";
return "agent";
if (attempt.requiredEnv === "CLI_AGENT") return "agent";
return "opencode";
})
.filter((provider) => !cliAvailability[provider as keyof typeof cliAvailability]),
),
Expand Down
Loading