Install Auto Mode Gate
Install the package only after reviewing its source. OpenCode plugins and Pi extensions run with the user’s system permissions.
Install from npm
Section titled “Install from npm”Choose the host that should load the gate. Each host keeps its own installation and policy paths.
Declare the package in the plugin array of your project or global opencode.json:
{ "$schema": "https://opencode.ai/config.json", "plugin": ["auto-mode-gate@0.4.3"]}Merge that entry into the existing array without removing other plugins or configuration. OpenCode resolves configured npm plugins when it starts.
You can also install it for the current project:
opencode plugin auto-mode-gate@0.4.3Add --global to install it for every project. Verify the resolved plugin list:
opencode debug configThe resolved plugin list must contain the Auto Mode Gate entry. This proves discovery and
configuration resolution, not pre-tool enforcement. Use the isolated host integration described
under Verify and operate for the behavioral check.
Version 0.4.3 requires Pi 0.84.2 for persistent judge controls and explicit secondary-model thinking, as first documented for 0.4.0.
Install globally:
pi install npm:auto-mode-gate@0.4.3Add -l for a project-local installation. Verify the package entry:
pi listInstall from a checkout
Section titled “Install from a checkout”The source loader flow uses one small loader file and does not edit host settings. Run the commands
from the checkout root, keep the checkout at the same path, and restart the host after installing
the loader. Set $scope to project or global; $targetProject is used only for project scope.
$scope = "project"$targetProject = "C:\path\to\project"if ($scope -notin @("project", "global")) { throw "scope must be project or global"}function Assert-SafeDirectoryPath([string]$Leaf, [string]$Root) { $candidate = $Leaf while (-not (Test-Path -LiteralPath $candidate)) { $candidate = Split-Path -Parent $candidate if ([string]::IsNullOrEmpty($candidate)) { throw "path escaped its scope root" } } $current = Get-Item -LiteralPath $candidate -Force while ($true) { if ($current.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { throw "path must not contain a symlink" } if ($current.FullName -eq $Root) { return } $current = $current.Parent if ($null -eq $current) { throw "path escaped its scope root" } }}$source = [System.Uri]::new((Resolve-Path .\src\opencode-runtime.ts).Path).AbsoluteUri$pluginRoot = if ($scope -eq "project") { $scopeRoot = (Get-Item -LiteralPath (Resolve-Path -LiteralPath $targetProject).Path -Force).FullName Join-Path $scopeRoot ".opencode\plugins"} elseif ($env:OPENCODE_CONFIG_DIR) { if (-not [System.IO.Path]::IsPathRooted($env:OPENCODE_CONFIG_DIR) -or -not (Test-Path -LiteralPath $env:OPENCODE_CONFIG_DIR -PathType Container)) { throw "OPENCODE_CONFIG_DIR must be an existing absolute directory" } $configuredRoot = Get-Item -LiteralPath $env:OPENCODE_CONFIG_DIR -Force if ($configuredRoot.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { throw "OPENCODE_CONFIG_DIR must not be a symlink" } $scopeRoot = $configuredRoot.FullName Join-Path $scopeRoot "plugins"} else { $scopeRoot = (Get-Item -LiteralPath $HOME -Force).FullName Join-Path $scopeRoot ".config\opencode\plugins"}Assert-SafeDirectoryPath $pluginRoot $scopeRootNew-Item -ItemType Directory -Force $pluginRoot | Out-NullAssert-SafeDirectoryPath $pluginRoot $scopeRoot$loaderPath = Join-Path $pluginRoot "auto-mode-gate.ts"$loaderBytes = [System.Text.UTF8Encoding]::new($false).GetBytes( "export { AutoModeGatePlugin } from `"$source`";")$loaderStream = $null$loaderCreated = $falsetry { $loaderStream = [System.IO.File]::Open( $loaderPath, [System.IO.FileMode]::CreateNew, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None ) $loaderCreated = $true $loaderStream.Write($loaderBytes, 0, $loaderBytes.Length) $loaderStream.Flush($true)} catch { if ($loaderCreated) { $loaderStream.Dispose() $loaderStream = $null Remove-Item -LiteralPath $loaderPath -Force } throw} finally { if ($null -ne $loaderStream) { $loaderStream.Dispose() }}Verify discovery:
if ($scope -eq "project") { Push-Location $targetProject try { opencode debug config } finally { Pop-Location }} else { opencode debug config}$scope = "project"$targetProject = "C:\path\to\project"if ($scope -notin @("project", "global")) { throw "scope must be project or global"}function Assert-SafeDirectoryPath([string]$Leaf, [string]$Root) { $candidate = $Leaf while (-not (Test-Path -LiteralPath $candidate)) { $candidate = Split-Path -Parent $candidate if ([string]::IsNullOrEmpty($candidate)) { throw "path escaped its scope root" } } $current = Get-Item -LiteralPath $candidate -Force while ($true) { if ($current.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { throw "path must not contain a symlink" } if ($current.FullName -eq $Root) { return } $current = $current.Parent if ($null -eq $current) { throw "path escaped its scope root" } }}$source = [System.Uri]::new((Resolve-Path .\src\pi-runtime.ts).Path).AbsoluteUri$extensionRoot = if ($scope -eq "project") { $scopeRoot = (Get-Item -LiteralPath (Resolve-Path -LiteralPath $targetProject).Path -Force).FullName Join-Path $scopeRoot ".pi\extensions\auto-mode-gate"} elseif ($env:PI_CODING_AGENT_DIR) { if (-not [System.IO.Path]::IsPathRooted($env:PI_CODING_AGENT_DIR) -or -not (Test-Path -LiteralPath $env:PI_CODING_AGENT_DIR -PathType Container)) { throw "PI_CODING_AGENT_DIR must be an existing absolute directory" } $configuredRoot = Get-Item -LiteralPath $env:PI_CODING_AGENT_DIR -Force if ($configuredRoot.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { throw "PI_CODING_AGENT_DIR must not be a symlink" } $scopeRoot = $configuredRoot.FullName Join-Path $scopeRoot "extensions\auto-mode-gate"} else { $scopeRoot = (Get-Item -LiteralPath $HOME -Force).FullName Join-Path $scopeRoot ".pi\agent\extensions\auto-mode-gate"}Assert-SafeDirectoryPath $extensionRoot $scopeRootNew-Item -ItemType Directory -Force $extensionRoot | Out-NullAssert-SafeDirectoryPath $extensionRoot $scopeRoot$loaderPath = Join-Path $extensionRoot "index.ts"$loaderBytes = [System.Text.UTF8Encoding]::new($false).GetBytes( "export { default } from `"$source`";")$loaderStream = $null$loaderCreated = $falsetry { $loaderStream = [System.IO.File]::Open( $loaderPath, [System.IO.FileMode]::CreateNew, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None ) $loaderCreated = $true $loaderStream.Write($loaderBytes, 0, $loaderBytes.Length) $loaderStream.Flush($true)} catch { if ($loaderCreated) { $loaderStream.Dispose() $loaderStream = $null Remove-Item -LiteralPath $loaderPath -Force } throw} finally { if ($null -ne $loaderStream) { $loaderStream.Dispose() }}Pi loads project extensions after the project is trusted. Check startup without contacting model providers:
if ($scope -eq "project") { Push-Location $targetProject try { pi --offline --list-models } finally { Pop-Location }} else { pi --offline --list-models}Remove an installation
Section titled “Remove an installation”OpenCode 1.18.18 has no plugin removal subcommand. Use opencode debug config to locate the
configuration that supplied the plugin, remove only the matching Auto Mode Gate entry from its
plugin array, restart OpenCode, and confirm that the plugin no longer appears.
Remove the package with the same scope used to install it:
pi remove npm:auto-mode-gatepi remove npm:auto-mode-gate -lFor a source installation, resolve the same scope as the installation block. In the same PowerShell session, delete only the loader or extension directory calculated above:
Remove-Item -LiteralPath (Join-Path $pluginRoot "auto-mode-gate.ts")Remove-Item -LiteralPath (Join-Path $extensionRoot "index.ts")if ((Get-ChildItem -LiteralPath $extensionRoot -Force).Count -eq 0) { Remove-Item -LiteralPath $extensionRoot}Run only the line for the host you installed. If those variables are no longer available, repeat the path calculation without creating directories or writing a loader, inspect the resolved path, and then remove it.