@ar-llm/pi-dir-providers

Pi extension that scopes the providers visible in /model based on the working directory.

Packages

Package details

extension

Install @ar-llm/pi-dir-providers from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@ar-llm/pi-dir-providers
Package
@ar-llm/pi-dir-providers
Version
0.2.1
Published
Sep 14, 2026
Downloads
259/mo · 28/wk
Author
arichiardi
License
Unlicense
Types
extension
Size
45.3 KB
Dependencies
0 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./src/dir-providers.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@ar-llm/pi-dir-providers

npm Unlicense

Pi extension that scopes which providers are visible in /model based on the working directory. In ~/git you may only want anthropic and openroutergithub-copilot and everything else disappear from model selection entirely.

Install

pi install 'npm:@ar-llm/pi-dir-providers'

Or try without installing:

pi -e 'npm:@ar-llm/pi-dir-providers'

Config

Create <agentDir>/ar-llm/dir-providers.json (same convention as pi-custom-compaction; <agentDir> is $PI_CODING_AGENT_DIR or ~/.config/pi/agent):

{
  "rules": [
    {
      "dirs": ["~/git"],
      "allowedProviders": ["anthropic", "openrouter"],
      "defaultModel": "anthropic/claude-sonnet-4-5"
    },
    {
      "dirs": ["~/git/acme-corp", "~/work/acme-corp"],
      "allowedProviders": ["anthropic"],
      "providers": {
        "anthropic": { "baseUrl": "https://anthropic-proxy.acme.com/v1" }
      }
    }
  ]
}

Rule ordering (important!)

Rules are applied strictly in array order — not by specificity. Every rule whose dirs match the current working directory applies; for allowedProviders and defaultModel, the last matching rule wins (its value replaces any earlier rule's value). The providers field is different: overrides from all matching rules are merged together per provider id.

You must order rules from least specific to most specific. Put the broadest directory rules first and the narrowest (most specific) directory rules last. If you reverse this order, a generic rule will silently override a specific one. For example, with rules [{dirs: ["~/git/managing-construction"], allowedProviders: ["github-copilot"]}, {dirs: ["~/git"], allowedProviders: ["openrouter"]}], running in ~/git/managing-construction/gossamer matches both rules, and the second rule's openrouter wins — github-copilot gets hidden. The correct order is to swap them so ~/git comes first and ~/git/managing-construction comes second.

  • dirs: list of directory subtrees. A leading ~ is expanded; a directory matches when the cwd equals it or is inside it (subdirectories inherit their ancestor's rules; a more-specific rule then overrides on top). Directory paths are symlink-resolved, so /tmp on macOS (which resolves to /private/tmp) matches the physical cwd. Nonexistent or inaccessible directories warn at startup and match as-is; point the rule at the real resolved path.
  • allowedProviders: replaces the effective set of visible providers. Providers outside the set are hidden from /model (their models are removed for the session; auth//login state is untouched).
  • defaultModel: "provider/model-id" applied to fresh sessions when the current model differs. Skipped when --model/--provider was passed on the command line. Note that switching the model persists the choice to settings.json, exactly like picking a model manually in /model.
  • providers: per-provider overrides with models.json override semantics (e.g. baseUrl, headers), merged across matching rules per provider id.

If no rule matches, the extension does nothing.

Disable marker

Create a .pi-dir-providers-disable file in any directory to disable the extension for that directory and all its subdirectories. This is useful when you want to temporarily bypass provider restrictions without modifying the config file.

  • The marker is checked in the current directory and all parent directories
  • When found, the extension becomes a no-op: no providers are hidden
  • The /dir-providers command reports "disabled by .pi-dir-providers-disable marker file"
  • Remove the file to re-enable the extension

How it works

Hiding happens at extension-factory time — before pi selects the initial model — by registering a models: [] overlay on each disallowed provider via pi.registerProvider(). This goes through pi's normal provider-composition path and is in-memory only: nothing on disk is touched, and /reload or a new pi process restores the default provider set.

Provider overrides (baseUrl, headers, extra fields, models) are applied the same way — as a registerProvider overlay — and are therefore also in-memory and reversible. A models field in an override replaces the provider's model set (like models.json), whereas an override object without models only tweaks connection fields and keeps the provider's existing models.

Validation

  • Missing config file, invalid JSON, or a missing rules array disable the extension (it stays a no-op).
  • Per-rule problems (empty dirs, relative dirs, duplicate dirs across rules, nonexistent dirs, malformed defaultModel, defaultModel provider not in the rule's allowedProviders) produce warnings and skip the offending part.
  • Unknown provider ids in allowedProviders warn at startup; check actual ids with pi --list-models. Builtin providers plus those declared in the agent dir's models.json and models-store.json are recognized.
  • If a later rule's directory is a parent of an earlier rule's directory (i.e. the broad rule appears after the narrow one), a warning is emitted at startup: the parent rule's allowedProviders/defaultModel will silently override the child rule's, and reordering is needed to fix it.

Commands

  • /dir-providers — print the effective profile for the current directory: matched rule indices, allowed providers, default model, and merged provider overrides. The TUI status bar also shows the active provider set (dir-providers: providers: ...) whenever the extension is active.

    Config-loading warnings (nonexistent/inaccessible dirs, unknown providers, rule shadowing) are not printed to stderr by default — they would only appear before the TUI starts, where they're invisible. Instead they are captured and surfaced as a Warning: notification in the TUI at session_start, so they are always visible when running interactively.

    For headless or non-TUI runs (or to debug startup behavior), set PI_DIR_PROVIDERS_DEBUG=1: diagnostics are written to $TMPDIR/ar-llm/dir-providers.log (never stderr).

Debug

The extension is quiet on stderr by default — diagnostics are routed to the TUI instead. To inspect raw startup behavior (e.g. for headless runs or to confirm which rules/providers resolved), enable verbose logging:

PI_DIR_PROVIDERS_DEBUG=1 pi ...

Diagnostics are written to $TMPDIR/ar-llm/dir-providers.log, including:

  • The [dir-providers] Active in <cwd>: rules ..., allowed [...], hid N providers status line.
  • All captured config warnings (EPERM, unknown providers, rule shadowing).

Development

make typecheck   # type-check source + tests
make test        # run unit tests (26 checks across match.ts and config.ts)
make test-watch  # same as `make test` but re-runs on file changes

Tests use Node.js's built-in node:test runtime — no external test runner is required (Node ≥ 22.6). The test/ directory contains a small ESM loader (loader.mjs + register.mjs) that maps NodeNext-style .js imports to their .ts source files so they can be imported without a build step.

Limitations

  • Providers registered by other extensions are not enumerated and cannot be hidden.
  • Directory globs are not supported; use explicit directory subtrees.
  • The profile is computed once per pi process from the startup cwd.

License

The Unlicense — public domain. Original work.