@khalilgharbaoui/opencode-claude-code-plugin 0.27.2 → 0.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -345,6 +345,7 @@ model: claude-code-work/claude-opus-5@work
345
345
  | `cwd` | string | see description | Working directory for the spawned CLI. Resolved **lazily per request**, first match winning: this explicit value, then the opencode session's own `directory` (so `opencode serve` and the web UI spawn in the right project even though one server handles many), then `process.cwd()` when it is a real directory, then the project directory captured at plugin init (this rescues macOS GUI launches, where `process.cwd()` is `/`), and finally `process.cwd()` regardless. [Startup diagnostics](#startup-diagnostics) reports which tier won. Session tier contributed by [@galvani](https://github.com/galvani). |
346
346
  | `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`. It is still passed when `proxyTools` is set: proxied calls go through opencode's permission system regardless, but unproxied CLI built-ins do not. The one case where the flag is dropped is `permissionMode: "plan"`, because the CLI lets the skip flag override plan mode outright. See [Plan mode](#plan-mode). |
347
347
  | `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to headless `claude --permission-mode`. `"plan"` also suppresses `--dangerously-skip-permissions` (see the row above). Not version-gated, so check that your installed CLI accepts the value. The [interactive transport](#interactive-transport-experimental) does not forward it. |
348
+ | `permissionPreset` | `"read-only"` | – | A named permission posture, so you set one option instead of combining five and getting one wrong. Opt-in: unset is exactly today's behaviour. `"read-only"` replaces `skipPermissions`, `permissionMode`, `controlRequestBehavior` and `controlRequestToolBehaviors`, and filters the write and command tools out of `proxyTools`. See [Read-only mode](#read-only-mode). |
348
349
  | `defaultSubagentModel` | string | – | Model that plugin-discovered `mode: subagent` agents run on when their own definition pins nothing. The caller's account is kept; only the model name changes. An agent's own `forceModel` wins over it, and an unknown id is refused rather than spawned. Unset means no implicit override at all. See [Subagents: your account, their model](#subagents-your-account-their-model). |
349
350
  | `proxyTools` | string[] | `["Bash", "Edit", "Write", "WebFetch", "Task"]` | Claude built-in tools to route through opencode's executor + permission UI. Opt-in extras: `"Question"`, `"Compress"`. See [Selective tool proxy](#selective-tool-proxy). |
350
351
  | `extraDisallowedTools` | string[] | – | Extra Claude built-ins to switch off with `--disallowedTools`, on top of what `proxyTools` implies. Claude's names, e.g. `["NotebookEdit"]`. See [Closing a tool with no proxy](#closing-a-tool-with-no-proxy). |
@@ -362,7 +363,7 @@ model: claude-code-work/claude-opus-5@work
362
363
  | `stripContextReminders` | boolean | `false` | Remove opencode-dcp's `<dcp-system-reminder>` blocks from message text when no `compress` tool is proxied, so an order the model cannot follow stops being re-sent with every message that carries it. Inert as soon as `compress` is reachable. See [Trimming unsatisfiable context reminders](#trimming-unsatisfiable-context-reminders). |
363
364
  | `webSearch` | `"claude"` \| `"disabled"` \| `<tool>` | `"claude"` | Routing for Claude's built-in `WebSearch`. See [WebSearch routing](#websearch-routing). |
364
365
  | `multiStepContinuation` | boolean | `true` | Append a system-prompt hint nudging Claude to chain tool calls within one turn instead of pausing between subtasks. Each opencode turn boundary requires the user to manually press "continue", so for multi-step tasks this reduces friction. Set `false` to disable. |
365
- | `autoContinueIncompleteTurns` | boolean \| `"smart"` | `"smart"` | Smartly continue incomplete Claude CLI results inside the same opencode turn. Reduces manual "continue" presses when Claude ends after reasoning/tool activity without a useful final answer. Set `false` to disable. |
366
+ | `autoContinueIncompleteTurns` | boolean \| `"smart"` | `"smart"` | Smartly continue incomplete Claude CLI results inside the same opencode turn. Reduces manual "continue" presses when Claude ends after reasoning/tool activity without a useful final answer. Also gates the `▌ **no reply:**` note on a turn that ends with no text and no tool call. Set `false` to disable both. |
366
367
  | `compactionModel` | string | `"claude-haiku-4-5"` | Model used when opencode invokes `/compact`. Override per-process via the `CLAUDE_CODE_COMPACTION_MODEL` env var (env wins over config). See [Compaction](#compaction). |
367
368
  | `ignoreAnthropicApiKey` | boolean | `false` | Strip `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` from every spawned `claude` process so it authenticates with your logged-in subscription instead of pay-as-you-go API billing. The plugin warns once at startup whenever an API key is detected, regardless of this setting. See [Billing](#billing). |
368
369
  | `idleProcessTimeoutMs` | number | – | Kill a retained headless Claude worker after this many idle milliseconds following a completed turn. The timer starts when a turn finishes, a new turn cancels it, a worker that is mid-turn when it fires is left alone and re-timed, and the session id is preserved for `--resume`. Values above Node's maximum timer delay (`2147483647`) are ignored. Omit or set `0` to retain workers until LRU eviction (16 processes). Interactive transport is excluded. Contributed by [@bernardofortes](https://github.com/bernardofortes). |
@@ -391,7 +392,9 @@ Every variable the plugin itself reads, in one place. Config is read once at ope
391
392
  | `OPENCODE_CLAUDE_CODE_LOG_DIR` | logger | Directory for the log file, overriding `logging.dir`. |
392
393
  | `OPENCODE_CLAUDE_CODE_LOG_LEVEL` | logger | Minimum level to emit, overriding `logging.level`. An unrecognised value falls through to config. |
393
394
  | `DEBUG` | logger | `DEBUG=opencode-claude-code` promotes the logger to `mode: "debug"`, echoing every emitted level to opencode's TUI. |
394
- | `OPENCODE_CLAUDE_CODE_PLUGIN_NO_CLEANUP` | startup cleanup | `1` skips the one-time removal of a stale **unscoped** `opencode-claude-code-plugin` install from opencode's plugin cache. That old package is a different artifact that shadows this scoped one when both are present; set this if you are deliberately keeping it. |
395
+ | `OPENCODE_CLAUDE_CODE_PLUGIN_NO_CLEANUP` | startup cleanup | `1` skips the removal of a stale **unscoped** `opencode-claude-code-plugin` install from opencode's plugin cache. That old package is a different artifact that shadows this scoped one when both are present; set this if you are deliberately keeping it. |
396
+ | `OPENCODE_CLAUDE_CODE_PLUGIN_FORCE_CLEANUP` | startup cleanup | `1` runs that cleanup even when the marker at `$XDG_STATE_HOME/opencode-claude-code-plugin/cleanup-stale.json` (default `~/.local/state/...`) records that this plugin version already swept. Without it the cleanup walks opencode's plugin cache once per installed version rather than on every launch. |
397
+ | `OPENCODE_CLAUDE_CODE_NO_TMP_SWEEP` | scratch directory | `1` skips the sweep of `<tmpdir>/opencode-claude-code-<pid>` directories left behind by plugin processes that were killed. See [Scratch files on disk](#scratch-files-on-disk). |
395
398
  | `OPENCODE_WORKTREE` | MCP bridge | Overrides worktree-root detection, which otherwise walks up from the working directory looking for a `.git` entry. |
396
399
  | `OPENCODE_CONFIG` / `OPENCODE_CONFIG_DIR` | config discovery | Where the plugin looks for your opencode config when bridging MCP and skills. See [Discovery order](#discovery-order-highest-to-lowest-priority). |
397
400
  | `OPENCODE_VERSION` | startup diagnostics | Reported as the opencode version when set, sparing the plugin a `--version` spawn. Diagnostics only. |
@@ -528,6 +531,23 @@ A patched process answers `401`. A `200` is a pre-0.13.2 process still running,
528
531
 
529
532
  Nothing to configure. If proxied tools ever stop working after a Claude Code upgrade, check the plugin log for `proxy-mcp rejected a request`, which names which guard failed.
530
533
 
534
+ ### Scratch files on disk
535
+
536
+ Everything the plugin writes for the Claude CLI to read goes into one per-process directory, `<tmpdir>/opencode-claude-code-<pid>`, created `0700`:
537
+
538
+ | File | Mode | Holds |
539
+ | --- | --- | --- |
540
+ | `mcp-<hash>.json` | `0600` | the bridged MCP config, including any `{env:VAR}` values substituted from your environment |
541
+ | `proxy-<hash>.json` | `0600` | the proxy endpoint's bearer token |
542
+ | `opencode-cc-sys-<uuid>.md` | `0600` | the full appended system prompt, forwarded opencode instructions and AGENTS.md included |
543
+ | `skills-<hash>/` | inherits | staged skill plugin dirs for the skill bridge; the `0700` parent is what keeps them private |
544
+
545
+ On a shared host the OS tmpdir is world-writable and the pid name is guessable, so the plugin refuses a path that already exists but is a symlink, is not a directory, or is not owned by you: it falls back to a fresh `mkdtemp` name and logs a warning naming both paths.
546
+
547
+ The directory is removed on normal exit. `SIGKILL` skips that, so on first use each run the plugin also sweeps `<tmpdir>/opencode-claude-code-<pid>` directories whose pid is no longer running and which you own. Anything else, another user's directory, a live process's, a symlink, a name that is not exactly that pattern, is left alone. Set `OPENCODE_CLAUDE_CODE_NO_TMP_SWEEP=1` to turn the sweep off.
548
+
549
+ **Windows is not hardened here.** Both spawn sites pass `shell: true` on `win32`, so the CLI argument list goes through `cmd.exe` unquoted. Treat Windows as unsupported until that is fixed; see the note in `docs/agents-history.md`.
550
+
531
551
  ### Closing a tool with no proxy
532
552
 
533
553
  `proxyTools` only reaches built-ins the plugin can replace. A built-in with no opencode equivalent, `NotebookEdit` today and whatever Claude Code ships next, stays enabled and unmediated no matter what you put in that list. `extraDisallowedTools` names them directly:
@@ -782,6 +802,7 @@ Four Claude Code stream events used to reach nothing but a debug log:
782
802
  - **A conversation Claude Code cleared.** Sending `/clear` as a message, or a plan-mode exit that clears context, makes Claude Code start a fresh conversation while opencode still shows the old messages. A `▌ **claude code reset:**` note says so. The plugin deliberately does not replay the earlier messages, since that would undo the clear. Start a new opencode session if you want the two to match.
783
803
  - **A `result` whose subtype is not `success`** (`error_max_turns`, `error_during_execution`, …). The subtype is named in the transcript and the turn finishes as an error instead of an ordinary reply.
784
804
  - **A CLI-executed tool that failed.** Its result is forwarded with the AI SDK's error flag, so opencode renders the row as failed rather than as a success whose output happens to be an error message.
805
+ - **A turn that finished cleanly without saying anything.** No text, no tool call, no error: opencode files it as an ordinary reply, so what you get is a blank message with nothing to distinguish it from a crash. A `▌ **no reply:**` note now says which of the two shapes it was, thinking-with-no-answer or nothing at all, and that nothing failed and nothing is pending. It is its own text part and is stripped from any transcript rebuilt for the CLI. Never on a compaction turn, a failed turn, an aborted one, or one that ended on a question, and suppressed entirely by `"autoContinueIncompleteTurns": false`. There is deliberately no automatic retry: measured across the whole retained plugin log, every finished turn carried between 940 and 5,080 characters of reply and none was silent.
785
806
 
786
807
  At session start the plugin also warns once per process for each MCP server Claude Code could not connect (its tools are simply absent otherwise) and once when the CLI's own `apiKeySource` says an API key is in effect, which is the field that tells you pay-as-you-go billing is happening. See [`ignoreAnthropicApiKey`](#options-reference).
787
808
 
@@ -915,6 +936,64 @@ Each chat keeps a long-lived `claude` subprocess so the model retains its native
915
936
 
916
937
  ---
917
938
 
939
+ ## Read-only mode
940
+
941
+ ```json
942
+ "options": {
943
+ "permissionPreset": "read-only"
944
+ }
945
+ ```
946
+
947
+ That is the whole configuration. The turn can read your code and search the
948
+ web, and it cannot write a file, run a command, or execute code.
949
+
950
+ Presets exist because read-only was previously a combination you had to get
951
+ exactly right. `permissionMode: "plan"` alone does not do it, and neither does
952
+ any single Claude Code flag, because this plugin puts a second execution path
953
+ next to the CLI's own tools: `proxyTools` defaults to `Bash`, `Edit`, `Write`,
954
+ `WebFetch` and `Task`, and each of those is an MCP tool the CLI calls and
955
+ **opencode** executes. No CLI flag reaches them. So the preset works at three
956
+ layers:
957
+
958
+ | Layer | What read-only does | Why it is needed |
959
+ | --- | --- | --- |
960
+ | Claude CLI tools | `--restricted` (CLI 2.1.258+) | Removes Bash, the REPL and the other code-running built-ins, removes WebFetch, confines the file tools to the working directories, and refuses bypass |
961
+ | Claude CLI tools, older CLIs | `--disallowedTools Bash Write Edit NotebookEdit REPL JavaScript WebFetch` | `--restricted` is version-gated; these names are not |
962
+ | The opencode proxy | `bash`, `write`, `edit`, `webfetch`, `task` and `task_batch` are dropped from `proxyTools` | These run in opencode, so the CLI flags above never see them |
963
+ | Everything else | `--permission-prompts none` (CLI 2.1.263+) and `controlRequestBehavior: "deny"` | A bridged MCP tool or a read outside the working directory is neither of the above |
964
+
965
+ `skipPermissions` is forced to `false`, and that is not a style choice:
966
+ `--restricted --dangerously-skip-permissions` is a startup error on CLI 2.1.280
967
+ (`Error: bypassPermissions not supported in restricted mode`), so a spawn
968
+ carrying both would not run at all.
969
+
970
+ **The preset replaces rather than merges.** Setting `skipPermissions`,
971
+ `permissionMode`, `controlRequestBehavior` or `controlRequestToolBehaviors`
972
+ next to it has no effect; each dropped value is logged at NOTICE at startup so
973
+ you can see it happen. `proxyTools` is the exception: it is filtered, so a list
974
+ naming `Question` keeps it. An unrecognised preset name applies **nothing** and
975
+ logs a WARN, rather than guessing at what you meant.
976
+
977
+ **What stops working.** Reads are fine (`Read`, `Grep`, `Glob`, `WebSearch`),
978
+ but anything that would raise a permission prompt is denied, and that includes
979
+ bridged MCP tools and the `question` proxy. Claude's own `AskUserQuestion`
980
+ still renders its stop-and-wait markdown, so the model can still ask you
981
+ things. If you need one specific tool allowed, do not use the preset: set the
982
+ underlying options yourself.
983
+
984
+ **On an older CLI** the preset still holds through `--disallowedTools` plus the
985
+ plugin's own denial of every permission request, and it warns naming what is
986
+ missing. Below 2.1.258 you lose the cwd confinement on reads; below 2.1.263 the
987
+ denial happens in the plugin rather than in the CLI, one layer instead of two.
988
+
989
+ Measured end to end on CLI 2.1.280 and `claude-haiku-4-5`: a turn under the
990
+ preset asked to write a file and run a command did neither, the file was never
991
+ created, and the CLI's own `permission_denials` recorded the single blocked
992
+ `Write` with no Bash attempt at all, because there was no Bash tool to attempt
993
+ with.
994
+
995
+ ---
996
+
918
997
  ## Plan mode
919
998
 
920
999
  Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The plugin handles `ExitPlanMode` specially — instead of forwarding it as a tool call, it converts it to a confirmation prompt that flows through opencode normally.
@@ -1280,6 +1359,7 @@ This plugin absorbs work from its forks directly, cherry-picked with the origina
1280
1359
  | [@CNQQC](https://github.com/CNQQC) | Cost units corrected to dollars per million tokens (PR #25). | PR #25 |
1281
1360
  | [@willmcginnis](https://github.com/willmcginnis) | The proxy endpoint authentication (PR #28, GHSA-3mxm-w7gf-3c5x). | PR #28 |
1282
1361
  | [@nic-lan](https://github.com/nic-lan) | The issue #29 diagnosis of subagent output lost across the CLI resume boundary, and the fix for unattended output replaying as one text block per delta (PR #35). | #29, PR #35 |
1362
+ | [@acastro2](https://github.com/acastro2) (Alexandre Castro) | Found and fixed CLI tool results being emitted under a different name than their call, which made opencode 2 abort every turn that used a Claude-side MCP server (PR #46). | PR #46 |
1283
1363
  | [@JWebCoder](https://github.com/JWebCoder) (joao moura) | Diagnosed that auto-continue never fires on current CLIs (PR #15). | PR #15 |
1284
1364
 
1285
1365
  Commit hashes are on the contributors' forks where the work was cherry-picked; `git log --author` on this repo shows the preserved authorship.
package/dist/index.d.ts CHANGED
@@ -376,7 +376,15 @@ interface ClaudeCodeConfig {
376
376
  accountFailover?: AccountFailoverMode;
377
377
  providerID?: string;
378
378
  skipPermissions?: boolean;
379
- permissionMode?: PermissionMode;
379
+ /**
380
+ * Widened past `PermissionMode` because a resolved `permissionPreset` puts
381
+ * its own internal token here (`READ_ONLY_PERMISSION_MODE`), which
382
+ * `buildCliArgs` translates to CLI flags. The operator-facing option on
383
+ * `ClaudeCodeProviderSettings` stays a plain `PermissionMode`.
384
+ */
385
+ permissionMode?: EffectivePermissionMode;
386
+ /** The preset this config was resolved from, for diagnostics. */
387
+ permissionPreset?: PermissionPreset;
380
388
  mcpConfig?: string | string[];
381
389
  strictMcpConfig?: boolean;
382
390
  bridgeOpencodeMcp?: boolean;
@@ -492,6 +500,23 @@ interface ClaudeCodeProviderSettings {
492
500
  defaultSubagentModel?: string;
493
501
  skipPermissions?: boolean;
494
502
  permissionMode?: PermissionMode;
503
+ /**
504
+ * Apply a named permission preset instead of hand-combining
505
+ * `permissionMode`, `skipPermissions`, `proxyTools`,
506
+ * `extraDisallowedTools` and `controlRequestBehavior`.
507
+ *
508
+ * `"read-only"` makes the turn unable to change anything: no
509
+ * `--dangerously-skip-permissions` (the CLI refuses bypass under
510
+ * `--restricted` outright), `--restricted` so the CLI has no Bash, REPL or
511
+ * WebFetch at all, the mutating built-ins on `--disallowedTools` for CLIs
512
+ * too old for that flag, the write and command tools removed from the
513
+ * opencode proxy, and every remaining permission request denied.
514
+ *
515
+ * A preset overrides those five options rather than merging with them, so
516
+ * one setting decides the posture; the plugin warns about each value it
517
+ * drops. Unset (the default) changes nothing.
518
+ */
519
+ permissionPreset?: PermissionPreset;
495
520
  mcpConfig?: string | string[];
496
521
  strictMcpConfig?: boolean;
497
522
  /**
@@ -806,6 +831,25 @@ interface ClaudeCodeProviderSettings {
806
831
  logging?: LoggingConfig;
807
832
  }
808
833
  type PermissionMode = "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan";
834
+ /**
835
+ * A named bundle of permission settings, so a safety posture is one option
836
+ * instead of a hand-rolled combination of `permissionMode`, `skipPermissions`,
837
+ * `proxyTools`, `extraDisallowedTools` and `controlRequestBehavior`. Opt-in:
838
+ * unset means today's behaviour, unchanged.
839
+ *
840
+ * `read-only` is the only preset so far. See `src/permission-presets.ts` for
841
+ * what it resolves to and why each part is needed.
842
+ */
843
+ type PermissionPreset = "read-only";
844
+ /**
845
+ * The `permissionMode` a resolved preset puts on `ClaudeCodeConfig`.
846
+ * Plugin-internal, never a value the operator sets: `buildCliArgs` translates
847
+ * `"read-only"` into `--restricted` (plus `--permission-prompts none` where
848
+ * the CLI has it) rather than passing it to `--permission-mode`, which would
849
+ * reject it.
850
+ */
851
+ declare const READ_ONLY_PERMISSION_MODE = "read-only";
852
+ type EffectivePermissionMode = PermissionMode | typeof READ_ONLY_PERMISSION_MODE;
809
853
  type ControlRequestBehavior = "allow" | "deny";
810
854
  /**
811
855
  * Claude CLI stream-json message types.
@@ -937,6 +981,61 @@ interface ClaudeStreamMessage {
937
981
  index?: number;
938
982
  }
939
983
 
984
+ /**
985
+ * Named permission presets: one option that decides a safety posture, instead
986
+ * of the operator hand-combining `permissionMode`, `skipPermissions`,
987
+ * `proxyTools`, `extraDisallowedTools` and `controlRequestBehavior` and
988
+ * getting one of them wrong.
989
+ *
990
+ * Opt-in. No preset resolves to exactly today's behaviour, byte for byte.
991
+ *
992
+ * ## Why `read-only` is not just a CLI flag
993
+ *
994
+ * Claude Code 2.1.248 added `--restricted`, which removes the built-in tools
995
+ * that run commands or code plus WebFetch, confines the file tools to the
996
+ * working directories, and refuses bypassPermissions. That covers the CLI's
997
+ * own tools and nothing else, and this plugin's whole point is that it puts a
998
+ * second execution path next to them: `proxyTools` defaults to `Bash`, `Edit`,
999
+ * `Write`, `WebFetch` and `Task`, each an MCP tool the CLI calls and opencode
1000
+ * executes. `--restricted` never sees those. So the preset has to work at
1001
+ * three layers at once:
1002
+ *
1003
+ * 1. the CLI's own tools (`--restricted`, plus `--disallowedTools` for the
1004
+ * same names so a CLI too old for `--restricted` still refuses them),
1005
+ * 2. the opencode proxy (the mutating defs are dropped before the proxy
1006
+ * server is built, so they are never offered),
1007
+ * 3. everything left that would prompt (denied, because a bridged MCP
1008
+ * server or a file tool reaching outside the cwd is neither of the
1009
+ * above).
1010
+ *
1011
+ * ## Measured, on the CLI this was written against (2.1.280)
1012
+ *
1013
+ * `--restricted --dangerously-skip-permissions` is a hard startup error,
1014
+ * `Error: bypassPermissions not supported in restricted mode`, so dropping
1015
+ * the skip flag is a correctness requirement and not hygiene. In the binary,
1016
+ * `restrictedMode` is a `bypassImmune` circuit breaker, which is the same
1017
+ * fact from the other side. A `--restricted` run asked to write a file and
1018
+ * run a command reported one `permission_denials` entry (`Write`) and no Bash
1019
+ * attempt at all: the shell tool was not in the set to begin with.
1020
+ */
1021
+
1022
+ /** The options a preset decides, resolved. */
1023
+ interface ResolvedPermissionPreset {
1024
+ preset: PermissionPreset;
1025
+ permissionMode: EffectivePermissionMode;
1026
+ skipPermissions: boolean;
1027
+ proxyTools: string[];
1028
+ extraDisallowedTools: string[];
1029
+ controlRequestBehavior: ControlRequestBehavior;
1030
+ /** Always undefined under a preset: see `overridden` below. */
1031
+ controlRequestToolBehaviors: undefined;
1032
+ /**
1033
+ * Operator-set values the preset replaced, as `option: reason` lines, so
1034
+ * the caller can warn once per option rather than silently winning.
1035
+ */
1036
+ overridden: string[];
1037
+ }
1038
+
940
1039
  interface DiagnosticsProviderEntry {
941
1040
  name?: string;
942
1041
  options?: Record<string, unknown>;
@@ -1139,6 +1238,19 @@ declare function registerSideQuestionCommand(config: OpenCodeConfig): boolean;
1139
1238
  declare function registerDoctorCommand(config: OpenCodeConfig): boolean;
1140
1239
  declare function _resetPlanModeWarningForTests(): void;
1141
1240
  declare function warnIfPlanModeCannotExit(permissionMode: string | undefined): void;
1241
+ /**
1242
+ * Resolve `permissionPreset` for this provider, reporting what it replaced.
1243
+ *
1244
+ * The result is applied over the operator's settings by `createClaudeCode`,
1245
+ * which is the one funnel both opencode majors reach the language model
1246
+ * through, so a preset needs wiring in exactly one place.
1247
+ *
1248
+ * Every override is logged at NOTICE, and an unrecognised preset name is a
1249
+ * WARN plus no preset at all. A safety option must never be approximated: a
1250
+ * typo'd `"readonly"` silently running at full permissions is worse than one
1251
+ * that says so.
1252
+ */
1253
+ declare function applyPermissionPreset(settings: ClaudeCodeProviderSettings, defaultProxyTools: readonly string[]): ResolvedPermissionPreset | null;
1142
1254
  declare function createClaudeCode(settings?: ClaudeCodeProviderSettings): ClaudeCodeProvider;
1143
1255
  /**
1144
1256
  * Build models in OpenCode's config schema format (flat properties like
@@ -1178,4 +1290,4 @@ declare const _default: {
1178
1290
  setup: (ctx: V2Context) => Promise<V2Cleanup>;
1179
1291
  };
1180
1292
 
1181
- export { type AgentRecord, type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, DEFAULT_PROXY_TOOL_NAMES, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, _resetPlanModeWarningForTests, bridgeOpencodeMcp, buildAgentRegistry, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels, extractDeletedSessionId, getAgentRegistry, getDefaultSubagentModel, registerDoctorCommand, registerSideQuestionCommand, resolveAgentModel, warnIfPlanModeCannotExit };
1293
+ export { type AgentRecord, type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, DEFAULT_PROXY_TOOL_NAMES, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, _resetPlanModeWarningForTests, applyPermissionPreset, bridgeOpencodeMcp, buildAgentRegistry, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels, extractDeletedSessionId, getAgentRegistry, getDefaultSubagentModel, registerDoctorCommand, registerSideQuestionCommand, resolveAgentModel, warnIfPlanModeCannotExit };