Skip to content

Repository files navigation

Unpin

Unpin is a Rust CLI with a terminal TUI and a macOS desktop workbench for local AI-agent configuration discovery, derived Agent Plugin packages, safe mutation, reusable inventory groups, snapshots, restore, and MCP-backed agent workflows.

Distribution status and quick start

Unpin 1.0.0 is the first unified stable CLI, terminal TUI, MCP, and macOS desktop release. The public stdio MCP server supports the stateless 2026-07-28 protocol edition while retaining legacy MCP host compatibility; this does not expand its plan-first mutation authority.

GitHub Releases provides provenance-attested CLI archives for Apple Silicon macOS, Intel macOS, and 64-bit GNU/Linux, plus separate native desktop archives for Apple Silicon and Intel macOS. Each archive has a CycloneDX SBOM attestation and is covered by SHA256SUMS. The GNU/Linux CLI is built on Ubuntu 22.04 and supports glibc 2.35 or newer, including Debian 12.

The 1.0.0 and 1.0.1 desktop archives are ad-hoc signed. Releases from 1.0.2 through 1.3.0 used rebuilt executables signed by a consistent personal certificate, but those rebuilt bytes could still cause a new Keychain prompt. Current macOS archives instead include a separately signed, create-once Unpin credential broker. The first verified broker installation requires one authorization; ordinary CLI updates leave its exact installed bytes unchanged. The active certificate is Unpin-specific but still not Developer ID signing or Apple notarization, so macOS can show an unidentified-developer Gatekeeper warning. This is an explicit maintainer- approved GA exception, not a trusted-distribution claim. The personal certificate is used with timestamp mode none; secure timestamping is not claimed for it. See the desktop installation instructions below before opening the app. crates.io and package-manager distribution are deferred.

After publication, download the archive for your platform from GitHub Releases, verify it against SHA256SUMS, then verify its GitHub build provenance:

gh attestation verify unpin-v1.4.2-TARGET.tar.gz \
  --repo IgorArkhipov/unpin

Extract the archive, install both included executables together on your user PATH, and start with read-only inspection. The CLI verifies the companion before installing the stable broker under the app-state root:

cd unpin-v1.4.2-TARGET
install -d "$HOME/.local/bin"
install -m 0755 unpin unpin-credential-broker "$HOME/.local/bin/"
export PATH="$HOME/.local/bin:$PATH"

unpin --help
unpin providers
unpin doctor
unpin list --json

The CLI can check the latest stable GitHub release without changing files, then install the exact version you confirm:

unpin update check --target cli
unpin update apply --target cli --confirm VERSION_FROM_CHECK

Updates download only the matching platform archive and SHA256SUMS, enforce bounded HTTPS downloads, verify the archive checksum, inspect the candidate version, and replace the installed binary atomically. On macOS, the candidate must also retain the dev.unpin.cli identifier and exactly match the installed designated requirement.

Add $HOME/.local/bin to your shell's startup configuration if it is not already on PATH.

To build from source, use Rust 1.96 or newer:

cargo build --release --locked
./target/release/unpin --help

Release users can continue with the five-minute setup below. To connect an agent, use the MCP setup guide. Contributors should start with the onboarding guide for the architecture, scope precedence, guided code tour, and fixture-backed first mutation.

Desktop workbench (macOS, first phase)

The native desktop workbench is the preferred first-phase human surface on macOS. It organizes high-volume configuration work around Discover and Organize, Change Safely, and Recover and Audit instead of mirroring every terminal view. It can inspect cross-provider inventory and derived Agent Plugin packages, maintain explicit groups, review and apply a locally approved aggregate change, and inspect backup and operation evidence before a reviewed restore.

The app bundles and supervises a matching unpin executable over a local stdio bridge. SwiftUI only renders redacted bridge state and starts one bundled child; the Rust process remains responsible for discovery, plan fingerprints, local approval, locks, provider writes, backups, drift checks, and recovery. The app does not receive secret keys, raw provider payloads, absolute provider paths, or MCP approval artifacts.

At launch, choose the repository workspace you want to manage. The workbench passes that exact folder as the bridge's project root; it never infers a project from the app bundle or silently falls back to your home directory.

CLI, MCP, and unpin tui remain supported. The terminal TUI is the compatibility surface for Profiles, Gateways, Sessions, and Hooks until those workflows receive their own desktop workspaces. The desktop's local-human approval is not an MCP approval: agent-created MCP handoffs retain the CLI/TUI approval contract described in the MCP guide.

1.0.0 ships separate desktop archives for Apple Silicon (aarch64-apple-darwin) and Intel (x86_64-apple-darwin). Verify the selected archive against SHA256SUMS and its GitHub attestation before extracting it:

gh attestation verify \
  unpin-desktop-v1.4.2-TARGET.tar.gz \
  --repo IgorArkhipov/unpin
tar -xzf unpin-desktop-v1.4.2-TARGET.tar.gz

Quit any running copy, then move UnpinDesktop.app from the extracted folder to /Applications or ~/Applications. The app checks for a newer stable release at launch; choose Check for Updates… from the application menu to check on demand. Before replacement, Unpin verifies the release checksum, app and bridge signatures, exact identifiers, versions, and equality with the installed designated requirements. It relaunches only after a successful atomic bundle swap. Certificate rotation is intentionally rejected by automatic update and requires a separately documented manual installation plus an explicit broker replacement. Designated-requirement equality is an update trust check; the unchanged stable broker is what preserves Keychain approval.

To uninstall the desktop app, quit it and move UnpinDesktop.app to Trash. This leaves the CLI and shared Unpin state under ~/.config/unpin untouched; that state can contain backup and recovery evidence and should not be removed as part of an ordinary app uninstall.

Because the desktop app is not Developer ID signed or notarized, the first launch can be blocked by Gatekeeper. After verifying both the checksum and GitHub attestation, use Finder to Control-click the app, choose Open, then confirm Open. If macOS instead reports that the app is damaged or its signature is invalid, stop and re-download it; do not disable Gatekeeper or strip quarantine metadata. See the desktop guide for the full install, update, uninstall, and trust boundary.

Workflow mode routing

Workflows let one confirmed gateway session expose a narrow capability set for its current task without toggling provider-native configuration. A workflow references a baseline profile plus named mode profiles such as planning, implementation, and review; the effective exposure is always baseline plus exactly one mode. Mode changes replace the prior mode, never accumulate it.

The confirmed maximum envelope is the session privilege boundary. An agent or user may explicitly enter another mode already inside that envelope without a new provider mutation or approval. Expansions remain inactive for separately authenticated local-operator review. Every routed exposure also includes the fixed typed Unpin status, mode-list, enter-mode, and cancel-transition controls.

Routing covers only gateway-owned skill search and body loading, projected MCP leaf tools, and gateway-dispatched hooks. Native skills, MCP registrations, plugins, hooks, shell, editor, and browser actions outside the gateway remain native-unmanaged and are not changed by a transition. Hosts that do not confirm a current-session re-list report reload-required, refresh-unconfirmed, or next-session-only; the last state leaves the current observed exposure callable. Notification alone never proves activation.

Start with read-only definition and proposal inspection:

unpin workflow list --json
unpin workflow validate --id delivery --provider codex --json
unpin workflow propose --prompt "plan the next change" --provider codex --json
unpin session status --id SESSION_ID --json

Workflow definition writes are reviewed plan/apply operations. Confirmed launch binds the proposal, workflow/profile/catalog revisions, process, workspace, and connection into one authenticated lease. Use unpin session enter-mode and unpin session cancel-transition with operation fields from status or the controlling gateway. Desktop is the primary workflow workbench; TUI is a compact inspection and CLI-handoff surface. See MCP routing details, desktop workflow guidance, and matrix evidence.

Five-minute local setup

Run Unpin from the Git repository whose agent configuration you want to inspect:

cd /path/to/repository
PROJECT_ROOT="$(git rev-parse --show-toplevel)"

unpin --version
unpin doctor --project-root "$PROJECT_ROOT"
unpin list --project-root "$PROJECT_ROOT" --json

These commands are read-only. Before the first persistent toggle, restore, or protected session, initialize Unpin's purpose-separated keychain keys once:

unpin auth backup init
unpin auth approval init
unpin auth session init

Toggle one project skill or MCP server

First discover the exact item ID. Narrow the inventory by provider, kind, and project layer:

unpin list \
  --project-root "$PROJECT_ROOT" \
  --provider claude \
  --kind skill \
  --layer project \
  --json

Plan the one-item toggle without writing anything:

unpin toggle \
  --project-root "$PROJECT_ROOT" \
  --provider claude \
  --kind skill \
  --layer project \
  --id EXACT_ITEM_ID \
  --json

Review the target state, effects, and fingerprint. Apply that exact selection only by repeating it with the returned fingerprint:

unpin toggle \
  --project-root "$PROJECT_ROOT" \
  --provider claude \
  --kind skill \
  --layer project \
  --id EXACT_ITEM_ID \
  --apply \
  --confirm \
  --plan-fingerprint PLAN_FINGERPRINT_FROM_DRY_RUN

toggle flips the item's current enabled state. To manage a configured MCP server instead, use --kind mcp and the exact MCP item ID returned by list. Change --provider for another supported agent. Unpin refuses unsupported or read-only combinations rather than inventing provider state.

Cursor can store a configured MCP server's disabled state in its workspace database. Close Cursor before applying or restoring an item whose statePath ends in state.vscdb, then reopen it after the change. Unpin never creates the database or its schema and reports cursor-host-busy if Cursor keeps it locked.

Connect Unpin to an agent

Agents connect by launching Unpin's local stdio server:

unpin mcp --provider zed --project-root "$PROJECT_ROOT"

The process stays attached to stdio while the host is connected; normally put this command in the host's MCP configuration instead of running it by hand. Replace zed with the agent host (claude, codex, cursor, or opencode). The provider pin is a hard boundary for every MCP tool. Use --provider all only when the connection intentionally administers every supported provider. Reach-aware item, bulk, inventory-group, and named-profile mutations use schema-v2 plans with an explicit Selected provider or All providers reach. Review the returned provider coverage and preserve the exact operation ID and plan fingerprint through the CLI/TUI handoff and status lookup; partial, blocked/no-targets, and recovery-required results remain distinct.

The MCP setup guide has copy-ready configuration for Codex, Claude Code, Cursor, OpenCode, and Zed; explains Pi's native-MCP limitation; and includes a prompt you can give an agent to configure and verify the connection automatically. The MCP capability-control prompt library covers project allowlists, inventory groups, profiles, capability locks, hooks, sessions, and restore. Most MCP tools only inspect state and prepare plans. The three handoff-sealing tools unpin_apply_toggle_item, unpin_apply_toggle_items, and unpin_plan_profile_provider also persist transaction/payload metadata and coordination locks under Unpin app state, but do not mutate provider configuration. A persistent server started with --enable-approved-group-apply can additionally apply one exact inventory-group plan only after a human approves its challenge through the CLI or TUI; every other persistent MCP operation remains handoff-only.

Command Surface

  • unpin auth backup|approval|session|cursor-dashboard manages purpose-separated OS-keychain state.
  • unpin providers prints the provider capability matrix.
  • unpin doctor validates discovery inputs, configured vault integrity, fixture capability-matrix drift, and provider fixture shape drift.
  • unpin snapshot writes a discovery snapshot into Unpin app state.
  • unpin list lists discovered provider items.
  • unpin toggle plans a supported item toggle, then applies only the exact confirmed fingerprint.
  • unpin agent-plugins list|show|plan|handoff|apply|status inspects and controls derived Agent Plugin packages through explicit provider reach and the existing reviewed bulk-operation lifecycle.
  • unpin restore plans a backup restore, then applies only the exact confirmed fingerprint.
  • unpin group maintains explicit mixed-type inventory groups, their revisions, history, previews, MCP approvals, and operation evidence.
  • unpin catalog, profile, gateway, session, and hook manage normalized capabilities, reusable policy, optional routing, isolated leases, and reviewed hook trust.
  • unpin mcp runs a newline-delimited stdio MCP control plane. Default item, bulk, restore, profile, policy, gateway, session, and hook writes return human-action handoffs; approved group apply is an explicit persistent-mode exception.
  • unpin update check|apply checks and installs a confirmed stable CLI or macOS desktop release through the verified updater.
  • unpin tui opens the terminal compatibility UI, including item, Packages, and Groups views. Use it for Profiles, Gateways, Sessions, and Hooks until their desktop workspaces arrive; unpin dashboard is an alias for the same command.

Provider Coverage

Unpin currently discovers Claude Code, Codex, Cursor, Pi, OpenCode, and Zed skills, configured MCPs, agents, hooks, provider settings, and selected plugin surfaces from fixture-backed or explicitly provided roots. Provider-owned Claude skills under $HOME/.claude/skills and repository-scoped .claude/skills, recursively nested Cursor skills under $HOME/.cursor/skills and repository-scoped .cursor/skills, Pi skills under $HOME/.pi/agent/skills and .pi/skills, OpenCode skills under $HOME/.config/opencode/skills and .opencode/skills, Cursor local plugin directories under $HOME/.cursor/plugins/local, and agent files are writable through guarded Unpin vault toggles with backup and restore evidence. Pi direct Markdown skills use a file vault; skill directories use a directory vault. Cursor-compatible skills, Codex shared .agents/skills, Pi shared .agents/skills, OpenCode shared .agents/skills and .claude/skills, plus Zed .agents/skills, use the same guarded cross-provider flow. Disabling one shared source records its original path and keeps every loading provider visible as disabled; re-enable through any provider view or backup restore returns it to that path. OpenCode MCPs use native mcp.<id>.enabled state in global or project JSON/JSONC while preserving comments and trailing commas. OpenCode npm plugin toggles remove and restore only config references through guarded Unpin vault state with authenticated backup evidence; Bun cache files remain installed. Pi intentionally has no native MCP core; MCP connectors belong to Pi extensions/packages. Pi package extension toggles set native packages[].extensions filters to [], retain package references and every non-extension resource, then restore the exact original package entry through guarded Unpin vault state with authenticated backup evidence. Pi 0.81.1 and OpenCode 1.18.4 global/project config compatibility was live-validated in disposable, explicitly rooted environments for this beta. OpenCode auto-loaded local plugin files are read-only because current host docs expose no local-file disable setting. Existing Claude, Codex, Cursor, and Zed mutation contracts remain unchanged. Hooks, settings, instructions, permissions, and sandbox files remain non-writable inventory. IDE extensions unrelated to agent harnesses remain outside scope.

Unpin prefers provider-native enable state. Claude, Codex, and OpenCode plugin toggles edit supported settings references and leave installed bundles or caches untouched. Cursor local plugin directories are path-discovered and have no documented local disable reference; Unpin therefore relocates the intact bundle into authenticated Unpin vault state instead of deleting it, then restores it to its recorded origin on re-enable or backup restore.

Plugin scope support is explicit:

Provider Global/user plugins Project/repository plugins
Claude Code Writable through enabledPlugins Writable through project/local enabledPlugins
Codex Writable through user plugins.<id>.enabled Unsupported by current Codex plugin host contract
Cursor Local bundles writable through intact vault/restore; marketplace installs read-only Marketplace installs inventoried read-only
Pi Package extension filters fixture- and live-host-verified Package extension filters fixture- and live-host-verified
OpenCode npm config toggles fixture- and live-host-verified; local files read-only npm config toggles fixture- and live-host-verified; local files read-only
Zed Out of scope; Zed uses standard Agent Skills Out of scope; Zed uses standard Agent Skills

Unpin does not invent repository plugin state for Codex or write Cursor marketplace caches/SQLite rows as if they were authoritative settings.

Skill discovery follows current provider layouts. Claude scans .claude/skills; Codex scans shared .agents/skills and administrator-managed /etc/codex/skills; Cursor recursively scans native and compatibility roots. Pi recursively scans native .pi and shared .agents roots and also inventories direct Markdown skills in native roots. OpenCode scans native .opencode plus shared .agents and .claude roots from selected directory to repository root. Zed uses global and selected-project .agents/skills. Reserved @compat/... and @file/... namespaces prevent native, shared, and direct-file item-id collisions. Vaulted skills remain filtered to currently resolved roots, so disabled items from another home or repository do not leak into inventory. Unreadable nested directories produce path-safe warnings while readable scopes remain available. Provider-owned skill links preserve link identity; skills under symlinked provider roots remain read-only.

Zed context_servers and OpenCode mcp.<id>.enabled mutation are JSONC-aware: comments, trailing commas, and surrounding formatting survive toggles and backup restore.

OpenCode is the supported harness in this provider family. OpenRouter is a model/API router with per-request plugins, not a standard local global/project agent-configuration host, so it has no Unpin provider adapter.

Agent Plugins packages

Unpin recognizes the Agent Plugins 1.0.0 package layout already installed by a supported host. A root plugin.json provides safe package metadata, immediate skills/*/SKILL.md entries and root mcp.json describe component coverage, and the host's native activation setting remains the only writable authority. Unpin does not install, update, import, delete, or persist desired state for packages, and it never creates missing Skill or MCP inventory rows.

Packages are a derived workbench projection. Each scan groups matching existing inventory under one logical package, reports on, off, mixed, or unknown state, and distinguishes actionable, diagnostics-only, and unsupported instances. A toggle expands to the exact existing native activation identities, requires explicit selected-provider or all-provider reach, and reuses Unpin's review, fingerprint, drift, backup, audit, recovery, and restore boundaries. Visibility filters never grant mutation reach.

Current package-root support is explicit:

Provider Global/user Project/repository
Claude Code Actionable through native enabledPlugins Actionable through project/local enabledPlugins
Codex Actionable through native plugins.<id>.enabled Unsupported; current plugin activation is user-scoped
Cursor Unsupported; current adapter recognizes provider-specific manifests Unsupported; no fixture-backed standard package root
Pi Unsupported; package references do not expose standard package roots Unsupported
OpenCode Unsupported; no fixture-backed installed standard package root Unsupported
Zed Unsupported; Zed uses standard Agent Skills Unsupported

Use unpin agent-plugins list --json to obtain a safe logical package ID. Its inventoryComplete marker is false whenever an installed package cache could not be fully read; inspect diagnostics and correct access before planning. show exposes coverage and blockers, while plan previews exact dispositions. handoff seals a durable CLI operation, and apply requires the same operation ID, fingerprint, explicit reach, and human confirmation. The TUI and desktop workbench expose the same Packages projection. MCP offers list, inspect, and plan/handoff tools only; it cannot apply a package toggle.

Inventory groups

Inventory groups are named, explicit collections of any individually toggleable inventory items Unpin already supports. Skills, configured MCP servers, plugins, and agents can coexist when their provider-native item is writable. Each member stores its full provider:layer:kind:category:id identity. A group does not create an implicit selector or broaden to future matching items.

Create definitions through the desktop workbench, CLI, or terminal TUI. Definition writes use the same preview, exact-fingerprint, and confirmation pattern as other Unpin writes:

unpin group create \
  --project-root "$PROJECT_ROOT" \
  --scope personal \
  --name brainstorming \
  --member 'claude:project:skill:skill:EXACT_SKILL_ITEM_ID' \
  --member 'codex:global:mcp:configured-mcp:EXACT_MCP_ITEM_ID' \
  --json

unpin group create \
  --project-root "$PROJECT_ROOT" \
  --scope personal \
  --name brainstorming \
  --member 'claude:project:skill:skill:EXACT_SKILL_ITEM_ID' \
  --member 'codex:global:mcp:configured-mcp:EXACT_MCP_ITEM_ID' \
  --apply \
  --confirm \
  --plan-fingerprint PLAN_FINGERPRINT_FROM_PREVIEW \
  --json

Use the exact category and item ID returned by unpin list --json. Personal definitions live in Unpin app state; repository definitions are workspace-bound project content. Both scopes retain revisioned history and support reviewed definition restore. If the same name exists in both scopes, use personal:name or repository:name; an unqualified ambiguous name is rejected.

Repository-definition CAS rejects revisions changed before publication and all Unpin writers share the same lock. Concurrent uncooperative edits to the repository group document during the final platform rename are outside the supported race boundary; do not edit that document with Git or an editor while an Unpin definition write is being confirmed.

The desktop Discover and Organize workspace is the preferred human path for a large inventory: it can create or edit a definition from staged full inventory identities, then pass the group into Change Safely. The terminal Groups view retains the same capability. Group state is On, Off, or Mixed. Planning shows each member, connected resource cohort, provider fan-out, unresolved identity, and blocked outcome before approval. Unresolved or read-only members are never silently skipped. If a multi-resource operation partially fails, preserve its operation and backup evidence, repair or restore as directed, and build a fresh plan instead of resuming provider writes from the old process.

Groups and profiles serve different purposes. A group is an explicit set of provider inventory identities used to change native enabled state now. A profile is a normalized capability allowlist used for policy, exposure, and future session selection; profile members are capability IDs rather than provider item identities.

Profiles and optional gateway

Native provider behavior remains default. Profiles are immutable allowlists resolved by replacement: session, workspace/worktree, repository, global, then native default. Provider-specific policy wins before generic policy at each scope. Global provider capability locks are applied after that selection: hard-enabled restores a capability omitted by a narrower profile, while hard-disabled removes it. Active sessions pin profile, lock, and exposure revisions, so another process, worktree, branch change, or later policy edit cannot mutate their capability set.

Inspect locks with unpin profile locks --provider codex --json. Change one with a plan-first unpin profile lock --provider codex --capability <id> --state hard-enabled|hard-disabled|clear --json, then re-run with the emitted fingerprint plus --apply --confirm. Lock status includes repository/worktree identity, the effective gateway source, conservative enforcement quality, and next-session-only activation; native mode is never reported as strict when the provider cannot prove it.

Workspace policy is machine-private. Inspect its authenticated binding with unpin profile policy status --json. Migrate the fixed local source .unpin/policy.json with unpin profile policy migrate --json, review the plan, then repeat it with --apply --confirm --plan-fingerprint <fingerprint>. Moved, deleted, or recreated worktrees are never silently rebound: status reports the classification and offers explicit reattach, discard, or cleanup plans. restore accepts only an authenticated policy backup ID. MCP can inspect this status and return the exact CLI handoff, but cannot mutate policy-maintenance state.

Gateway lifecycle separates installation, routing, and detachment:

  • gateway install records dormant Unpin-owned lifecycle state.
  • gateway on selects intended gateway policy for future sessions; it does not prove a live host attachment.
  • gateway off restores ledger-owned adopted skill views, selects native policy, and closes admission; active matching leases block unless explicitly drained.
  • gateway detach restores ledger-owned adopted skill views, selects native policy, and removes managed lifecycle state.

Managed native MCP configuration references are not yet part of gateway lifecycle effects. Status, dry-run, apply, MCP, and TUI output report this as nativeMcpReferences=not-managed; live provider attachment remains blocked, so Unpin never claims duplicate-free MCP routing before that effect exists.

Every persistent profile, capability-lock, gateway, session-end, adoption, trust, toggle, restore, and inventory-group definition action is plan-first. Apply requires --confirm plus the exact --plan-fingerprint from the current dry-run. MCP callers cannot mint human approval or expose keychain material. Outside the explicitly enabled approved-group flow, they receive a structured handoff for persistent work.

Exact terminal retries revalidate live policy, provider, adopted-view, and restored-target state before returning cached success. Divergence reports recovery-required. Catalog adoption reports rolled-back and needs-repair outcomes explicitly and exits nonzero for both.

If a native toggle fails after its backup is created, Unpin returns recovery-required, preserves the backup ID, and warns that provider writes may already have occurred. Treat that result as a recovery handoff: inspect or restore the reported backup instead of retrying the mutation blindly.

profile propose --prompt ... ranks profile metadata locally and returns a confirmation-required session proposal. It emits only the prompt digest, never prompt text, and does not mutate policy or start a session.

Fixture-backed session launch creates one private lease, Unix-socket gateway, and overlay per child harness. Lease protection binds every applicable global, repository, and workspace gateway-mode and policy target, current mutable native-item transitions, authenticated restorable backups, and adopted-view resources, including global views shared by another worktree. Gateway, restore, and native-toggle apply acquire the same transition conflict guard before mutation. All live launches require backup and session authentication so adopted and restorable resources cannot be silently omitted from the lease. Live profile-scoped launch currently fails closed until each provider adapter can prove strict native masking and gateway attachment. Native launches remain available after those authentication prerequisites. Repository identity groups shared blast radius; physical worktree identity isolates workspace policy; opaque session identity isolates exposure. Separate worktrees remain required when agents also need source-file edit isolation.

Diagnose protected sessions

Run diagnostics from the same repository and physical worktree as the session:

unpin auth session status
unpin session list --json
unpin gateway status --scope workspace --json

session list is intentionally scoped to the current repository and worktree. Its JSON keeps secret material out while reporting the provider, pinned profile, desired and observed exposure revisions, live exposure status, isolation, coverage, lifecycle, and in-flight call count.

  • Matching desired and observed revisions describe the last authenticated observation; they do not prove native provider attachment beyond the reported coverage.
  • external-degraded coverage is not strict isolation. Read its reasons before relying on the session boundary.
  • A non-active lifecycle or a desired/observed mismatch means callers should stop admitting new work and establish a fresh session after resolving the cause.
  • inFlightCalls greater than zero explains why reviewed shutdown or gateway drain may still be waiting.

End a session through the reviewed workflow; do not delete lease, overlay, transaction, backup, or audit files manually:

unpin session end --id SESSION_ID --json
unpin session end \
  --id SESSION_ID \
  --apply \
  --confirm \
  --plan-fingerprint FINGERPRINT \
  --json

The owner process remains responsible for cleanup after fencing. If a command returns recovery-required, preserve the reported state and evidence and stop before retrying or editing Unpin-owned runtime files.

Hook inventory records individual handlers and honest provider coverage. Trust receipts bind handler invocation fingerprint, compiled profile digest, provider, workspace, and session. Gateway-routed MCP hook policy is fixture-contract verified. Native dispatcher/managed-bridge host activation still requires live-provider wiring and verification; Zed built-in hooks remain unsupported.

Examples

Fixture-backed discovery:

cargo run -p unpin-cli -- list --fixture-root crates/unpin-core/tests/fixtures
cargo run -p unpin-cli -- doctor --fixture-root crates/unpin-core/tests/fixtures
cargo run -p unpin-cli -- tui --fixture-root crates/unpin-core/tests/fixtures --headless
cargo run -p unpin-cli -- dashboard --fixture-root crates/unpin-core/tests/fixtures --headless

When --fixture-root is supplied, doctor validates capability-matrix.json and required provider fixture shapes before returning OK, so stale provider contracts fail deterministically.

Explicit live-style roots:

cargo run -p unpin-cli -- list \
  --home-root "$HOME" \
  --project-root "$PWD" \
  --codex-root "${CODEX_HOME:-$HOME/.codex}" \
  --cursor-config-root "$HOME/.cursor" \
  --cursor-root "$HOME/Library/Application Support/Cursor/User"

On GNU/Linux, Cursor's default app-support root is $HOME/.config/Cursor/User; omit --cursor-root to use the platform default. Provider-specific configuration flags take precedence over host environment signals. Without those flags, Unpin follows CLAUDE_CONFIG_DIR, CODEX_HOME, PI_CODING_AGENT_DIR, OPENCODE_CONFIG_DIR, and XDG_CONFIG_HOME where the provider supports them. Cursor does not document a configuration-root environment override, so use --cursor-config-root when $HOME/.cursor is not the active location. --zed-root names the directory containing Zed's global settings.json.

Configuration

Unpin resolves command roots in this order:

  1. Defaults and supported host environment signals: current directory for the project root, ~/.config/unpin for Unpin-owned state, active provider configuration roots such as $CODEX_HOME, $HOME/.cursor/mcp.json for Cursor global MCP config, <project>/.cursor/mcp.json for Cursor project MCP config, and the platform Cursor user-data directory for Cursor app-support state (~/Library/Application Support/Cursor/User on macOS or ~/.config/Cursor/User on GNU/Linux).
  2. User config: ~/.config/unpin/config.json.
  3. CLI flags such as --project-root, --app-state-root, --cursor-root, and the provider-specific --*-root flags.

Repository-owned <projectRoot>/.unpin.json is untrusted. It cannot set projectRoot, appStateRoot, or cursorRoot; put command roots in the user config or pass the corresponding CLI flags explicitly. Unpin rejects these fields in project config so repositories cannot redirect discovery, provider mutation, approval, policy, backup, session, or audit state.

The user config is JSON and may contain:

{
  "version": 1,
  "projectRoot": "~/work/my-project",
  "appStateRoot": "~/.config/unpin",
  "cursorRoot": "~/Library/Application Support/Cursor/User"
}

Path fields must be non-empty strings or null. Invalid types and blank paths fail configuration loading instead of falling back to another root.

cursorRoot points at Cursor app-support state such as profiles and workspace storage. Cursor MCP config discovery uses the resolved home root for $HOME/.cursor/mcp.json and the resolved project root for <project>/.cursor/mcp.json.

Live doctor, list, snapshot, toggle, restore, mcp, tui, and dashboard commands use the resolved app-state root when --app-state-root is omitted. Vault discovery reports malformed metadata, mismatched provider paths, and missing payloads as invalid-vault-entry warnings; doctor fails when any such warning exists.

Backup Authentication

Live applies and protected session launches require a 32-byte backup authentication key stored in OS keychain. Initialize it once, then inspect its non-secret fingerprint:

cargo run -p unpin-cli -- auth backup init
cargo run -p unpin-cli -- auth backup status

Approval signing uses a separate key purpose:

cargo run -p unpin-cli -- auth approval init
cargo run -p unpin-cli -- auth approval status

Session leases, child launch controls, and transition conflict checks use a dedicated authentication key. Initialize it before live session, gateway, profile-policy, restore, TUI, or native apply workflows:

cargo run -p unpin-cli -- auth session init
cargo run -p unpin-cli -- auth session status

Session state uses HMAC-SHA256 over complete bootstrap and lease records. Launch controls bind signed payload to unique control path, preventing cross-session or cross-workspace replay. Key remains in OS keychain; session documents contain only non-secret key fingerprint and authentication tag.

Optional Cursor dashboard cookie storage reads secret bytes only from stdin, binds them to Cursor marketplace mutation purpose, and never prints them:

printf '%s' "$CURSOR_DASHBOARD_COOKIE" \
  | cargo run -p unpin-cli -- auth cursor-dashboard store
cargo run -p unpin-cli -- auth cursor-dashboard status
cargo run -p unpin-cli -- auth cursor-dashboard remove

Cookie presence does not make unsupported Cursor marketplace entries writable; each operation still reports current provider support and required human action.

New backup manifests use version 3 with purpose-separated HMAC-SHA256 authentication over manifest fields and deterministic payload-tree SHA-256 digests. Restore verifies both before acquiring mutation lock or writing provider state. Missing, mismatched, or tampered authentication blocks restore. Older authenticated manifests use version-specific verification. Legacy version 1 backups remain visible as legacy-unauthenticated but are not restorable unless a trusted caller explicitly authenticates current contents through the core migration API; Unpin never signs them automatically.

TUI header reports backup-auth readiness. MCP inventory summary exposes writeSafety.backupAuthentication and writeSafety.writesEnabled, allowing agents to preflight write availability without attempting mutation.

Fixture commands derive deterministic, domain-separated credentials from the fixture state root and never access the OS keychain. Keep --fixture-root and the same --app-state-root on fixture-backed restore, TUI, and MCP checks so they verify evidence created for that isolated fixture run.

Plan a no-write skill toggle:

cargo run -p unpin-cli -- toggle \
  --fixture-root crates/unpin-core/tests/fixtures \
  --provider claude \
  --kind skill \
  --layer project \
  --id claude:project:skill:example-claude-skill

Apply a Claude project configured MCP approval toggle against disposable fixtures:

tmp_fixture="$(mktemp -d)"
tmp_state="$(mktemp -d)"
cp -R crates/unpin-core/tests/fixtures/. "$tmp_fixture/"
cargo run -p unpin-cli -- toggle \
  --fixture-root "$tmp_fixture" \
  --app-state-root "$tmp_state" \
  --provider claude \
  --kind mcp \
  --layer project \
  --id claude:project:configured-mcp:github \
  --apply \
  --confirm \
  --plan-fingerprint PLAN_FINGERPRINT_FROM_DRY_RUN

Apply a Zed configured MCP context_servers vault toggle against disposable fixtures:

tmp_fixture="$(mktemp -d)"
tmp_state="$(mktemp -d)"
cp -R crates/unpin-core/tests/fixtures/. "$tmp_fixture/"
cargo run -p unpin-cli -- toggle \
  --fixture-root "$tmp_fixture" \
  --app-state-root "$tmp_state" \
  --provider zed \
  --kind mcp \
  --layer global \
  --id zed:global:configured-mcp:github \
  --apply \
  --confirm \
  --plan-fingerprint PLAN_FINGERPRINT_FROM_DRY_RUN

Apply an agent-file vault toggle against disposable fixtures:

tmp_fixture="$(mktemp -d)"
tmp_state="$(mktemp -d)"
cp -R crates/unpin-core/tests/fixtures/. "$tmp_fixture/"
cargo run -p unpin-cli -- toggle \
  --fixture-root "$tmp_fixture" \
  --app-state-root "$tmp_state" \
  --provider claude \
  --kind agent \
  --layer global \
  --id claude:global:agent:claude-global-reviewer \
  --apply \
  --confirm \
  --plan-fingerprint PLAN_FINGERPRINT_FROM_DRY_RUN

Terminal UI

The TUI lists the same discovered inventory as list, with provider/layer/category filters, / search, selected-item details, dry-run plan preview, discovery warnings, and recent backups. Writable items can be staged with space, confirmed with enter, and applied with a. After a successful staged apply, Unpin rediscovers live provider state, reloads backups, and writes a fresh latest/history snapshot.

TUI keeps last apply outcome visible. Blocked entries remain staged with confirmation reset; refresh failures skip stale snapshots, and snapshot write failures are reported after mutation succeeds.

Run the MCP stdio loop with one shell-provided request:

request='{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
printf '%s\n' "$request" \
  | cargo run -p unpin-cli -- mcp --fixture-root crates/unpin-core/tests/fixtures

Use --once for a one-request smoke check that exits immediately.

MCP tool IDs, titles, descriptions, and server identity use Unpin branding.

Malformed or empty JSON lines return a JSON-RPC -32700 parse error with id: null; the long-running stdio loop continues with later messages. Messages larger than 8 MiB remain fatal transport errors.

Plan a bulk MCP toggle:

request='{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"unpin_plan_toggle_items","arguments":{"selector":{"providers":["claude"],"kinds":["plugin"]},"targetEnabled":false}}}'
printf '%s\n' "$request" \
  | cargo run -p unpin-cli -- mcp --fixture-root crates/unpin-core/tests/fixtures --once

MCP apply tools require exact reviewed fingerprints and return structured human-action handoffs without writing provider state. Handoff sealing may still persist internal transaction/payload metadata and coordination locks under Unpin app state. Bulk requests also require maxItems. Configured MCP entries named unpin remain protected from disable attempts through their own control plane.

Development

Contributor and reviewer guidance:

Local CI-equivalent gates:

cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
cargo test --workspace --all-features --locked
cargo run -p unpin-cli --locked -- --help
cargo audit --deny warnings
cargo deny check
cargo machete

CI also checks the declared Rust 1.96 MSRV separately from the pinned Rust 1.97.1 development toolchain.

About

Local AI-agent configuration discovery, policy, safe mutation, backup, restore, and MCP workflows

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages