npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

opencode-permission-reviewer

v1.3.3

Published

Policy-aware permission reviewer for OpenCode V1

Readme

OpenCode Permission Reviewer

[!NOTE] This is an unofficial community plugin for OpenCode. It is not affiliated with or endorsed by Anomaly.

A tool-free AI reviewer for every ask permission. It reads the request, your policy, and the session context, then allows once, denies with feedback, or escalates to you — so safe actions don't wait for a keystroke, and genuinely risky ones still get blocked or surfaced.

OpenCode Bun npm Downloads License Checks Open issues

OpenCode pauses on every ask permission and waits for a keystroke — even for safe, routine actions. This plugin adds a Codex-Guardian-style reviewer: a dedicated, tool-free model session reads the pending request, bounded transcript evidence, recovered user intent, and a tenant policy you control, then allows once, denies with rationale, or escalates to you. Critical risk is never allowed, and anything broken or uncertain fails safe to manual review.

  • Preserves your policyallow continues, deny stays blocked; neither ever reaches the reviewer.
  • Isolated, tool-free reviewer session — the reviewer runs in a scratch directory outside your project (no AGENTS.md, project instructions, or project MCP servers) with every tool denied through a wildcard session permission rule, so it can neither call tools (MCP included) nor request permissions recursively.
  • Read-only enrichment — bounded, sanitized SSH / local-script / Git evidence for the reviewer; the filesystem is never modified.
  • Auditable — one JSONL record per review, with remote commands stored as SHA-256, not plaintext.
  • Optional TUI overlay — shows review state and gets out of the way of your native approval controls.

Policy adapted from OpenAI Codex Guardian — the reviewer policy text in this project derives from Codex's auto-review policy (Apache-2.0). The implementation is independent. See NOTICE.


Quickstart

Requirements

  • Bun ≥ 1.3.0 (CI runs 1.3.0 and 1.3.5)
  • OpenCode ≥ 1.18.11 and < 2 (tested with 1.18.25)
  • git on PATH (only used for read-only Git-state enrichment; missing git degrades gracefully)
  • A model provider configured in OpenCode, exposing a model that follows JSON schemas reliably (see Choosing the reviewer model)
  • A permission policy with at least one ask rule — if nothing is ask, the plugin never activates (everything is already allow/deny).

See Supported versions for the full matrix.

Install

The package is published to npm. Install it as a dependency, or clone and build when you want to run from a checkout:

# From npm
bun add opencode-permission-reviewer   # or: npm install opencode-permission-reviewer

# From a checkout (development)
git clone https://github.com/Warc0s/opencode-permission-reviewer.git
cd opencode-permission-reviewer
bun install && bun run build

What ships in dist/:

  • Servermain / ./serverdist/index.js (bundled).
  • TUI overlay./tuidist/tui/tui.tsx (raw TSX, not a JS bundle). OpenCode's host compiles that entry with its own Solid/OpenTUI pipeline and rewrites solid-js / @opentui/* onto the host runtime. A prebundled dist/tui.js loads but never paints the overlay.
  • CLI./cli / bindist/explain.js.

The CLI can register the plugin for you (--tui also writes tui.json; --npm emits an npm package name instead of a path; it never clobbers an existing entry):

bunx opencode-permission-reviewer init --npm --tui --yes

Configure

Register the plugin in your opencode.json (project or ~/.config/opencode/opencode.json). Use an absolute path to a checkout, or the npm package name after bun add / npm install:

// opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    [
      "/absolute/path/to/opencode-permission-reviewer",
      // or: "opencode-permission-reviewer"
      {
        "model": "openai/gpt-5.6-luna", // default reviewer; override with any provider/model
        "variant": "max",
        "timeoutMs": 120000,
      },
    ],
  ],
  "permission": {
    "bash": "ask", // at least one ask rule, or the plugin is a no-op
  },
}

For the optional TUI overlay, register the same plugin block in your tui.json (~/.config/opencode/tui.json). Keep model, variant, and timeoutMs identical in both files so the watchdog and server agree:

// tui.json
{
  "$schema": "https://opencode.ai/tui.json",
  "plugin": [
    [
      "/absolute/path/to/opencode-permission-reviewer",
      { "model": "openai/gpt-5.6-luna", "variant": "max", "timeoutMs": 120000 },
    ],
  ],
}

Restart OpenCode fully after install or rebuild. The host imports the plugin once at startup; a live session keeps the previous code in memory and will not show a rebuilt overlay.

Then ask the agent to run something safe, e.g. printf hello. An auto-approved ask resolves itself with once and the tool runs normally — without injecting rationale into the agent context. Denials still return a short reason the agent can act on.

Cost note: every ask action now spawns one extra child-session model call (up to timeoutMs). Your model spend scales with how much your policy asks. Lower the reasoning variant or raise confidenceThreshold to taste.

Choosing the reviewer model

The reviewer is a normal OpenCode model invocation (every tool denied at the session-permission level), so it can be any model from any provider you have configured. Three options, identical in both config files:

  • model — in provider/model form. Must match a configured provider and a model that provider exposes.
  • variant — reasoning effort the model supports (max, high, medium, low, none). Passed straight through to OpenCode.
  • outputFormat — how the reviewer returns its decision: json_schema (default; uses OpenCode's structured output, needs provider support) or text (ask the model to emit JSON in plain text and parse it locally). Use text for models that reject the json_schema format, e.g. opencode-go/deepseek-v4-flash.
  • timeoutMs — review timeout; must match across files.

The default reviewer is openai/gpt-5.6-luna (max reasoning) — a real model that follows JSON schemas well. Override model to use any other provider/model you have configured; whichever you pick should follow structured output reliably, because weaker models just produce more escalations (safe, but noisier). Higher reasoning variants give better safety judgments at higher cost/latency.

Reviewer models without structured-output support

Some models (for example opencode-go/deepseek-v4-flash) do not support OpenCode's json_schema structured-output format and fail with a format error when it is requested. For those, set "outputFormat": "text" so the reviewer asks the model to emit its decision as plain JSON and parses it locally. This needs the flag set identically in both opencode.json and tui.json:

{
  "model": "opencode-go/deepseek-v4-flash",
  "variant": "high",
  "outputFormat": "text",
  "timeoutMs": 120000,
}

Text mode is safe but noisier: without host-side schema enforcement the plugin re-prompts the reviewer once if the response is unparseable (mirroring the auto-retry that json_schema mode gets from OpenCode), and a response that is still invalid escalates to a human rather than being auto-approved. Parsing is deliberately strict and fail-closed: the entire response must be exactly one JSON object (optionally wrapped in a single Markdown code fence). Prose around the object, multiple objects, multiple fences, or any other ambiguity escalates to a human — the parser never guesses which candidate the model meant. Every parsed decision still passes the same strict parseDecision validation and enforceDecision invariants (critical risk is never approved, etc.), so text mode cannot approve anything that structured mode would not.

One caveat applies to any output format: the deterministic gates check the decision's consistency, not its semantic correctness. A reviewer model that misclassifies an unsafe action as low risk can produce an unsafe allow in either mode, so pick as strong a reviewer model as your budget allows.

All configuration options

Every option is optional. Numeric/string options are clamped to safe bounds.

| Option | Default | Bounds / type | Description | | ---------------------- | --------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------- | | model | openai/gpt-5.6-luna | provider/model | Reviewer model (override with any provider/model) | | variant | max | non-empty string | Reasoning variant passed to OpenCode | | outputFormat | json_schema | json_schema / text | How the reviewer returns its decision (text for models without structured output) | | timeoutMs | 120000 | 5000600000 | Review timeout (match in both files) | | confidenceThreshold | 0.7 | 0.51 | Minimum confidence to auto-act; below it escalates | | maxContextChars | 32000 | 4000200000 | Total transcript evidence budget | | maxPartChars | 8000 | 50050000 | Per-message-part budget | | maxEnrichmentChars | 24000 | 1000100000 | SSH / script / Git enrichment budget | | maxIntentChars | 8000 | 100050000 | User-intent history budget | | transcriptMessages | 12 | 1100 | Recent messages shown to the reviewer | | intentMessages | 8 | 150 | Genuine user intents kept | | historyMessages | 200 | 20500 | Messages fetched to recover intent | | retainReviewSessions | false | boolean | Keep reviewer child sessions (debug only; see below) | | audit | true | boolean | Append one JSONL audit record per review | | auditPath | ~/.local/share/opencode/permission-reviewer-audit.jsonl | path | Audit file location | | policy | built-in default | string | Full local override of the tenant policy text | | debug | false | boolean | Verbose logs to stderr | | enforcementMode | observe | observe / enforce | enforce applies declarative policy routes; observe audits them only | | escalationMode | manual | manual / deny | How final escalations are disposed (manual = human; deny = fail-closed reject) | | maxSessionDepth | 8 | 132 | Parent-session lineage walk depth | | maxParentSessions | 8 | 032 | Max parent sessions resolved for actor context | | actorProfiles | {} | name → profile map | Trusted agent name → profile (read-only, validation, workspace, …) | | riskPolicy | built-in conservative matrix | object | Override allow cells per risk level and failure modes (onInvalidDecision, …) | | repositoryTrust | unknown | trusted / untrusted / unknown | Repository trust level used by the policy engine | | policyRules | [] | array | Declarative rules (most-restrictive wins); project rules combine with trusted ones | | askDecisions | true | boolean | Show the reviewer what the user answered in agent ask dialogs (scoped authorization evidence) |

Config is layered: built-in defaults ← global ~/.config/opencode/permission-reviewer.jsonc ← project .opencode/permission-reviewer.jsonc ← inline plugin options (later wins). The project layer crosses a trust boundary: it can only tighten security-sensitive fields, and its hardening survives even when a trusted layer set the same field. The project layer cannot choose the reviewer model or replace the policy text (both decide where code/context travels and what the reviewer enforces), cannot redirect auditPath, grant actorProfiles, set repositoryTrust: "trusted", downgrade a global enforcementMode: "enforce", or relax a trusted escalationMode: "deny" / failure-mode deny knob / confidenceThreshold / riskPolicy. Project values of the wrong type (including null) are ignored, never normalized back to defaults.

A config file that exists but cannot be honored fails CLOSED on the trusted side: a malformed or unreadable global config, or trusted policyRules dropped by validation, marks the run degraded — reviews still run, but automatic approval stays off (everything escalates) until the file is fixed, and the degradation is reported on stderr. A malformed project file is reported and ignored (the untrusted layer adds nothing anyway).

In declarative policyRules, a when condition with an unknown key (a typo), a false flag, or an empty object drops the whole rule — a mistyped rule must never degrade into a universal match. Catch-all rules are spelled explicitly: omit when entirely, or use "when": { "always": true } (valid only alone). When a catch-all comes from the trusted global config it simply matches everything; project-sourced allow rules are still rejected outright.

Interactive vs autonomous

| Mode | Config | Behavior | | --------------------------- | -------------------------- | ----------------------------------------------------------------------- | | Interactive (default / 1.0) | escalationMode: "manual" | Uncertainty escalates to you; OpenCode's native approval UI takes over | | Autonomous / fail-closed | escalationMode: "deny" | Every final escalation becomes a reject with rationale; no human prompt |

For unattended agents, set fail-closed in global config (not in the repo):

// ~/.config/opencode/permission-reviewer.jsonc
{
  "escalationMode": "deny",
}

Optional fine-grained hardening under interactive mode (only their own cases):

{
  "riskPolicy": {
    "onInvalidDecision": "deny", // invalid structured output → reject
    "onReviewerFailure": "deny", // timeout / transport failure → reject
  },
}

escalationMode: "deny" hardens every escalate path globally. Restrictive settings can only block more, never relax security.

audit defaults to true. Each completed review appends one JSON object to the audit path with mode 0600 (schemaVersion: 2): outcome, decision source, rationale, risk, authorization, confidence, per-phase latency, reviewer model, optional reviewerOutcome / escalationDisposition (to distinguish an explicit deny from fail-closed escalate→deny), and a bounded SSH summary. Remote commands are stored as SHA-256, never in clear text. Set audit: false to disable.

What you'll see

✓ Review approved · bash · rm -rf /tmp/scratch-cache
Narrowly scoped temp cleanup; matches user intent.

While reviewing, the optional TUI overlay covers the native approval controls with Reviewing this permission and No action needed, plus the action, reviewer model, and elapsed time. Once resolved, the overlay becomes a compact status strip below the session: one line for the result and a second for its rationale, with long text truncated. The review keymap is released immediately so you can resume typing while the result stays visible for 5 s. OpenCode still hides its editor while a permission is pending. On a technical failure or escalation, the overlay is removed and OpenCode's native approval controls become available with a manual review required warning. A broken TUI transport never changes the safety decision.

How it works

  1. OpenCode emits permission.asked for any ask-classified action.
  2. A deterministic emergency brake rejects unmistakable root destruction and direct credential export before any model call. It is wrapper-aware (sudo, doas, env, command, nice, nohup, …), so sudo rm -rf /, env VAR=x rm -rf /, /bin/rm -rf /, sh -c 'rm -rf /', ssh host rm -rf /, and busybox rm -rf / are all caught.
  3. The plugin builds bounded evidence: recent transcript, recovered user intent, and optional read-only enrichment for SSH commands, local interpreter scripts, and Git state. Intent attribution uses a single origin rule: synthetic/host-flagged parts are never human intent, and in a delegated (subagent) session no user-role message counts as human authorization — the initial briefing and every later task_id follow-up are agent-authored and surface only as labeled delegation context. Common credential formats are always redacted from this evidence (Bearer, AWS / GitHub / OpenAI / Anthropic / Slack / Google / Stripe / GitLab keys, JWTs, private keys, URL userinfo, cookies, and credential-bearing assignments) so a secret you once pasted into the session never travels to the reviewer's provider.
  4. An isolated, tool-free reviewer session runs the reviewer model with a strict JSON schema and returns { outcome, risk_level, user_authorization, rationale, confidence }. The session is created in a scratch directory outside your project so the host does not prepend repository instructions (AGENTS.md, project config instructions, project MCP context) to the reviewer's system prompt — only your trusted global instructions remain. Tool denial is a wildcard session permission rule, which takes precedence over agent-config allows and therefore also covers MCP tools and MCP resource tools. If the host refuses the isolated directory, the review is not run in the project directory: it escalates to the human as a reviewer failure instead, so isolation is never silently degraded.
  5. Decisions are enforced with invariants: critical risk is never approved, high risk with low/unknown authorization is escalated, medium risk with unknown authorization is escalated, low confidence is escalated, invalid output is escalated, errors and timeouts are escalated. Two deterministic blocks also apply regardless of model confidence: a degraded trusted config (see above) and evidence where a material part of the action itself was elided or truncated — neither can auto-approve. A single enforcement boundary then disposes every internal escalate according to escalationMode (manual → human; deny → reject with the original reason).
  6. Approved actions get once (never always) and execute silently — the tool output is not annotated, so approval rationale never contaminates the primary agent context (rationale still lands in audit, TUI, and debug logs). Denials return a short actionable rationale as tool feedback. A manual reply that arrives mid-review supersedes the automatic one (no double reply).

By default everything fails safe to manual review. With escalationMode: "deny", uncertainty fails closed to a reject with reason instead — suitable for non-interactive agents.

Evidence enrichment

The reviewer never sees the raw filesystem — only bounded, sanitized evidence. Enrichment is deliberately conservative and never makes an approval decision by itself (one narrow deterministic exception exists for SSH, below).

  • SSH commands are parsed into destination, options, remote command, environment/mutation/secret/stdin signals, and bounded stdin content for the common cat script | ssh ... python - pattern. Sensitive paths, credential-like literal content, binary files, unresolved shell expressions, and symlinks escaping approved roots are excluded.
  • Local interpreter commands (Python, Node, Bun, shell, Ruby, Perl, and compound commands that first activate an environment) get the same bounded inspection when they name an explicit script. Inline code, modules, stdin programs, dynamic paths, and remote-only SSH arguments are not misidentified as local files.
  • Git operations (add, commit, checkout, restore, rm) get a read-only pre-command snapshot: current branch, files already staged before the command, unstaged/untracked files, planned targets, unresolved shell-expanded paths, and a bounded numstat for changes that would be discarded. Snapshots use fixed non-interactive Git queries with locking and hooks disabled, a two-second timeout, and bounded output. The repository is never modified. Repository-configured conversion filters (clean, smudge, process) and diff textconv drivers are enumerated before every snapshot and neutralized with config overrides (including dotted names); if the configuration cannot be fully verified — too many filters, or the config scan itself fails — the snapshot is withheld rather than risk executing repository-configured commands. Verification and inspection are still two distinct moments: a filter configured between them is a residual race the snapshot does not claim to eliminate.

Only regular text files inside the working directory, the worktree, or /tmp/opencode can be included. Missing, blocked, and truncated executable stdin is explicitly identified so the reviewer fails safe.

The only deterministic SSH preflight rejection is an executable stdin file that still does not exist after a 100 ms recheck — the primary agent gets an actionable instruction to create it and retry. Every other SSH case (sensitive, binary, blocked, or truncated evidence) remains a reviewer decision.

Safety properties

  • Responds only to permission.asked.
  • Critical-risk actions cannot be approved, even if model output says allow.
  • High-risk actions with low or unknown authorization, and medium-risk actions with unknown authorization, are deterministically escalated — the model cannot auto-approve them by labeling a contradictory combination.
  • Invalid, low-confidence, or inconsistent output is escalated to the user.
  • Common credential formats are always redacted from the evidence before reaching the reviewer, so credentials never leak to the reviewer's provider.
  • Reviewer sessions cannot request permissions recursively; every tool is denied by a wildcard session permission rule that also covers MCP tools and takes precedence over agent-config allows.
  • The reviewer session runs outside the project directory, so repository instructions (AGENTS.md and project-config instructions) are not part of its system prompt; if the isolated directory cannot be established, the review escalates to the human as a reviewer failure rather than running with degraded isolation.
  • A narrow deterministic emergency brake rejects unmistakable root destruction (including privilege-prefixed and command-string forms such as sudo rm -rf /, sh -c 'rm -rf /', ssh host rm -rf /) and direct credential-file export before any model call.
  • A manual reply that arrives while a review is in flight supersedes it: the reviewer stops without replying or resurrecting a UI state.
  • Approvals are silent to the primary agent (no tool-result annotation); denials return the rationale as feedback. Rationale remains in audit/TUI/debug.
  • SSH commands and executable stdin receive bounded, untrusted action enrichment; enrichment never makes an approval decision on its own.
  • Long-session user intent is recovered separately from recent operational context; later explicit requests supersede conflicting older ones.
  • Synthetic compaction/control messages are excluded from authorization evidence.
  • Audit failures never affect or relax the safety decision.
  • UI status messages are versioned, request-scoped, bounded, and transported through OpenCode's own workspace TUI event channel.

Supported versions

| Component | Supported | Notes | | --------------------- | -------------- | ------------------------------------------------------------ | | OpenCode | >=1.18.11 <2 | Declared in engines.opencode; verified with 1.18.25 | | @opencode-ai/plugin | >=1.18.11 <2 | Peer dependency for the server transport | | Bun | >=1.3.0 | Declared in engines.bun; CI runs 1.3.0 and 1.3.5 | | TUI overlay | OpenCode V1 | Needs the host Solid/OpenTUI plugin pipeline (raw TSX entry) | | OS | macOS / Linux | On Windows, SSH/Git enrichment degrade to fail-safe manual |

  • TUI overlay ships as raw TSX (dist/tui/tui.tsx). The host compiles it against its embedded Solid/OpenTUI runtime. A prebundled TUI entry loads but never paints. The server half does not depend on the overlay.
  • Server half replies through an isolated transport chosen once at startup: public SDK reply with feedback message → public reply plus a separate feedback channel → authenticated raw HTTP (/permission/{requestID}/reply via input.client._client.post) → refuse startup. On OpenCode 1.18.x the message-bearing reply is only reachable via the raw transport, so the chain resolves there. That raw field is not part of OpenCode's public plugin API and can change without notice. If startup fails with "authenticated SDK transport is unavailable", file an issue rather than downgrading.
  • OpenCode v2-generation hosts are detected at startup and refused until their reply contract is verified.
  • Full enrichment assumes a Unix-like system (macOS/Linux). On Windows, SSH and Git enrichment degrade gracefully toward fail-safe manual review.
  • retainReviewSessions: keep it false in normal use. Set true only to debug the known json_schema structured-output serialization bug in OpenCode 1.18.11 — it keeps the child session on disk so you can inspect the malformed response; it does not fix the bug.
  • Run opencode-permission-reviewer doctor to compare installed versions against the ranges above.

Troubleshooting

| Symptom | Likely cause | Fix | | --------------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Every ask escalates after a long wait | Reviewer model not found / provider not configured | Ensure the model's provider is set up in OpenCode and the model ID is valid in both config files | | Plugin does nothing | No ask rule in your permission policy | Add e.g. "bash": "ask" | | TUI overlay never appears | Not in tui.json; mismatched timeoutMs; stale process; or host without Solid/OpenTUI pipeline | Register the same block in tui.json with matching timeoutMs. Overlay is raw TSX (dist/tui/tui.tsx); a prebundled dist/tui.js does not render. Fully restart OpenCode after rebuilds | | Startup error: "authenticated SDK transport…" | OpenCode outside >=1.18.11 <2, or an SDK change that hides the raw transport | Upgrade OpenCode and @opencode-ai/plugin into the supported range; report the version in an issue | | Startup error: "Detected an OpenCode v2…" | OpenCode v2-generation host | Run an OpenCode 1.18.x host (v2 is not supported yet) | | Reviews always time out | timeoutMs too low for the model | Raise timeoutMs (up to 600000) | | GIT_STATE_ANALYSIS shows spawn git ENOENT | git not on PATH | Install git; Git enrichment degrades safely until then | | Want a version check | Host/SDK outside the supported range | Run opencode --version and opencode-permission-reviewer doctor | | Want to turn it off | — | Remove the plugin entry from both opencode.json and tui.json |

Enable "debug": true for verbose stderr logs while investigating. TUI load errors ([tui.plugin] …) are printed on the TUI process console, not in ~/.local/share/opencode/log/opencode.log.

Development

bun install
bun run check          # format + lint + typecheck + tests + build (must pass before any push)
bun run test:stress    # stress suite only
bun run test:package   # npm pack ship-set smoke (raw TUI + server bundle)

bun run build bundles the server/CLI with tsup, then copies the slim TUI source graph into dist/tui/ as raw TSX (scripts/copy-tui.ts). Do not add a prebundled TUI entry — it will not render on the host.

The live end-to-end harness in tests/live-harness.ts runs against a real OpenCode server + model and is not part of bun test; see CONTRIBUTING.md.

Attribution

The reviewer policy and prompt text in src/policy.ts are adapted from OpenAI Codex Guardian (Apache-2.0). See NOTICE for full attribution and license details.

License

Apache License 2.0 © 2026 Warc0s