{"_id":"@bramburn/pi-model-council","_rev":"4-bfa74f7e17a7d8f08d57dd1958841f46","name":"@bramburn/pi-model-council","dist-tags":{"latest":"1.6.11"},"versions":{"1.6.1":{"name":"@bramburn/pi-model-council","version":"1.6.1","_id":"@bramburn/pi-model-council@1.6.1","maintainers":[{"name":"bramburn","email":"nitrogen@gmail.com"}],"dist":{"shasum":"e9a7da79dda89b72952d59a4aa69711136768e09","tarball":"https://registry.npmjs.org/@bramburn/pi-model-council/-/pi-model-council-1.6.1.tgz","fileCount":1,"integrity":"sha512-um0qbWbsywf52Wfe0llla9oXEdTwc/WfuYKDaRENH8q20I/3mBDzBiVhLhNfWHvuZaUrtbBfyiyzJFDjDnQUmA==","signatures":[{"sig":"MEUCIDy4//0tZZVmLeni3YjmV3zkDq4D1pVVIvqTS+hGzFo8AiEAsAiD2MGT7r424g3W19uEToNVf8liYeQvzoYtQ4Ygs14=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":113},"_from":"file:bramburn-pi-model-council-1.6.1.tgz","_npmUser":{"name":"bramburn","email":"nitrogen@gmail.com"},"_resolved":"C:\\Users\\bramburn\\AppData\\Local\\Temp\\2\\pkg-tarball\\bramburn-pi-model-council-1.6.1.tgz","_integrity":"sha512-um0qbWbsywf52Wfe0llla9oXEdTwc/WfuYKDaRENH8q20I/3mBDzBiVhLhNfWHvuZaUrtbBfyiyzJFDjDnQUmA==","_npmVersion":"11.6.1","description":"test","directories":{},"_nodeVersion":"24.11.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/pi-model-council_1.6.1_1782836474147_0.11850289691620786","host":"s3://npm-registry-packages-npm-production"}},"1.6.2":{"name":"@bramburn/pi-model-council","version":"1.6.2","_id":"@bramburn/pi-model-council@1.6.2","maintainers":[{"name":"bramburn","email":"nitrogen@gmail.com"}],"dist":{"shasum":"6ad95c9da79d479cc0af44b43dafd010638bc74e","tarball":"https://registry.npmjs.org/@bramburn/pi-model-council/-/pi-model-council-1.6.2.tgz","fileCount":2,"integrity":"sha512-ZEqIx7FMmXN4CH0/SwCjYTyZQK5iAUei8KsbZV9CNOG/ckjHO/H3aT4aRxt+iZpC9Wj7v79UHxIadnEWA0CYvA==","signatures":[{"sig":"MEQCIH/RRSkXh/xK8wFooqKrwBohtYBdr7qpAEiweWL5MlJ+AiAmIRHNjz25JQPEUfBZV/Kl0w8Jkm/iibffBcY06bLgUA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":137},"main":"index.js","_npmUser":{"name":"bramburn","email":"nitrogen@gmail.com"},"_npmVersion":"11.6.1","description":"test","directories":{},"_nodeVersion":"24.11.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/pi-model-council_1.6.2_1782837156629_0.44789081151023713","host":"s3://npm-registry-packages-npm-production"}},"1.6.3":{"name":"@bramburn/pi-model-council","version":"1.6.3","_id":"@bramburn/pi-model-council@1.6.3","maintainers":[{"name":"bramburn","email":"nitrogen@gmail.com"}],"dist":{"shasum":"4f963e4c0c80131de3803c9d9adae0e0f571d137","tarball":"https://registry.npmjs.org/@bramburn/pi-model-council/-/pi-model-council-1.6.3.tgz","fileCount":2,"integrity":"sha512-4m30s07tUT304tLtHN82e8iXPKrhfzpKUNpWGgjVtIhGXCsVQqpB3xrsEPNPKN78S/fHEzihHPLRm1Ax8hIdkQ==","signatures":[{"sig":"MEUCIQCpkQ6XQXFe/rkoaJg33WmyM8JZ6q9SBHj48ACVgXzB7QIgLxcB7mY5vR1dAPibrzhTPQF1JdBRXRYvgBZfgdlrpgA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":137},"main":"index.js","_npmUser":{"name":"bramburn","email":"nitrogen@gmail.com"},"_npmVersion":"11.6.1","description":"test","directories":{},"_nodeVersion":"24.11.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/pi-model-council_1.6.3_1782837159273_0.7529825169792401","host":"s3://npm-registry-packages-npm-production"}},"1.6.11":{"name":"@bramburn/pi-model-council","version":"1.6.11","description":"Pi extension: multi-model coding decisions via OpenRouter","type":"module","main":"index.ts","keywords":["pi-package","pi-extension","openrouter","ai-council"],"repository":{"type":"git","url":"git+https://github.com/bramburn/pi-model-council.git"},"bugs":{"url":"https://github.com/bramburn/pi-model-council/issues"},"homepage":"https://github.com/bramburn/pi-model-council#readme","engines":{"node":">=22.14.0","npm":">=11.5.1"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"scripts":{"lint":"eslint . --max-warnings 0","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","audit":"npm audit --audit-level=high","prepublishOnly":"npm run lint && npm run typecheck && npm test","pack:dry":"npm pack --dry-run"},"dependencies":{"@sinclair/typebox":"^0.34.0"},"devDependencies":{"@earendil-works/pi-coding-agent":"*","@earendil-works/pi-tui":"*","@eslint/js":"^9.0.0","@types/node":"^22.0.0","eslint":"^9.0.0","typescript":"^5.7.0","typescript-eslint":"^8.0.0","vitest":"^3.2.6"},"peerDependencies":{"@earendil-works/pi-coding-agent":"*","@earendil-works/pi-tui":"*"},"pi":{"extensions":["./"]},"_id":"@bramburn/pi-model-council@1.6.11","gitHead":"b9c77b85be1a7f7de2afadfc27a5e316a67603c9","_nodeVersion":"22.14.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-tyo0LfstsoeexKgExITPirKUEw6qx9jF97rK1rFGBRnoZXSICfg+YVFZPRrICSE/ju7g44tuEe4BtS3BYdybSw==","shasum":"5f57c20ed214d2ca627ff8938eab1670e1ba30c2","tarball":"https://registry.npmjs.org/@bramburn/pi-model-council/-/pi-model-council-1.6.11.tgz","fileCount":24,"unpackedSize":189048,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bramburn%2fpi-model-council@1.6.11","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDm2OkiMYJAQrOL4lS8jrh5+1kfeVQnlx6dMoetMpX1dAiEAlwC6stSAdab1P6SDhrb8b7/3j5l20l1BqNIL4GHT2sM="}]},"_npmUser":{"name":"bramburn","email":"nitrogen@gmail.com"},"directories":{},"maintainers":[{"name":"bramburn","email":"nitrogen@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pi-model-council_1.6.11_1782838174079_0.11889253100522845"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-30T16:21:13.915Z","modified":"2026-06-30T16:49:34.496Z","1.6.1":"2026-06-30T16:21:14.280Z","1.6.2":"2026-06-30T16:32:36.796Z","1.6.3":"2026-06-30T16:32:39.410Z","1.6.11":"2026-06-30T16:49:34.215Z"},"description":"Pi extension: multi-model coding decisions via OpenRouter","maintainers":[{"name":"bramburn","email":"nitrogen@gmail.com"}],"readme":"# model-council\n\nPi extension for multi-model coding decisions via OpenRouter.\n\nAsk three independent AI models for a second opinion, then have a fourth model synthesise them into a single actionable plan. Use the fast `/opinion` command for quick checks, and `/council` for higher-stakes architectural decisions.\n\n## Features\n\n- **Multi-model council** — Three OpenRouter models deliberate independently, then a fourth model (you pick) synthesises the final decision. Returned as a structured plan you (or Pi) can implement.\n- **Single-model opinions** — Get a quick second opinion from any model with valid auth, using Pi's built-in model registry.\n- **Pi-native auth** — Re-uses `OPENROUTER_API_KEY` (or `/login openrouter`) so the API key lives in one place. No separate key prompt required when Pi already knows OpenRouter.\n- **Persistent settings** — Stored in `~/.pi/agent/council-settings.json` (or project-scoped when trusted). Includes the three council members, the synthesis model, and the opinion model.\n- **Secure** — Settings are gitignored; keys never logged.\n\n---\n\n## Installation\n\n`pi install` accepts three source types: **npm**, **git**, and **local path**. Pick the one that matches how you want to consume the extension.\n\n### Prerequisites\n\n- **Pi** installed (`pi --version` should print ≥ 1.0)\n- **Node.js 22+** (matches the extension's CI runner)\n- An **OpenRouter API key** — get one at [openrouter.ai/keys](https://openrouter.ai/keys)\n\n### Pick the install method\n\n| Use case | Command | Where it lands |\n|---|---|---|\n| **npm** (recommended once published) | `pi install npm:pi-model-council` | `~/.pi/agent/npm/pi-model-council` |\n| **GitHub via SSH** (most common) | `pi install git:github.com/bramburn/pi-model-council` | `~/.pi/agent/git/github.com/bramburn/pi-model-council` |\n| **GitHub via HTTPS + PAT** (private repos, CI) | `pi install https://x-access-token:<TOKEN>@github.com/bramburn/pi-model-council.git` | `~/.pi/agent/git/github.com/bramburn/pi-model-council` |\n| **Local clone** (development) | `pi install ./pi-model-council` | registered in-place, not copied |\n| **Try without installing** (one-shot) | `pi -e git:github.com/bramburn/pi-model-council` | temp directory |\n\nPin to a specific tag for reproducibility (recommended for shared / CI use):\n\n```bash\npi install git:github.com/bramburn/pi-model-council@v1.5.0\n```\n\n### Detailed walkthroughs\n\n#### Option 1 — npm (once the package is published)\n\n```bash\npi install npm:pi-model-council\n```\n\nUpdates use the regular npm flow: `pi update --extensions`.\n\n#### Option 2 — GitHub via SSH\n\nIf you've already added an SSH key to your GitHub account (the same key you'd use for `git push` to this repo), Pi uses it automatically:\n\n```bash\npi install git:github.com/bramburn/pi-model-council\n# Or pinned to a tag:\npi install git:github.com/bramburn/pi-model-council@v1.5.0\n```\n\nThe clone is placed under `~/.pi/agent/git/github.com/bramburn/pi-model-council/`.\n\n#### Option 3 — GitHub via HTTPS with a Personal Access Token\n\nUse this when SSH isn't an option. Create a **fine-grained** token at <https://github.com/settings/tokens?type=beta> with **read** access scoped to just `bramburn/pi-model-council`:\n\n```bash\npi install https://x-access-token:<TOKEN>@github.com/bramburn/pi-model-council.git\n```\n\n> The PAT will be embedded in `~/.pi/agent/settings.json`. The settings file is created with `0600` permissions, but treat it as a secret and don't commit it.\n\n#### Option 4 — Local clone (recommended for development)\n\n```bash\n# Clone once anywhere\ngit clone https://github.com/bramburn/pi-model-council.git ~/code/pi-model-council\ncd ~/code/pi-model-council\nnpm install\n\n# Register the local path with pi (no copy — edits take effect immediately)\npi install ./pi-model-council\n```\n\nTo update later:\n\n```bash\ncd ~/code/pi-model-council\ngit pull && npm install\n# The next pi launch picks up the new code automatically; no reinstall needed.\n# If you changed pi-extension metadata, force a refresh:\npi update --self --force\n```\n\n#### Option 5 — Try without installing (one-shot smoke test)\n\n```bash\npi -e git:github.com/bramburn/pi-model-council\n```\n\nPi clones to a temp directory and uses the extension for the current run only. Nothing is written to your settings.\n\n### Project-scoped install (team sharing)\n\nBy default, `pi install` writes to **user** settings (`~/.pi/agent/settings.json`). To write to **project** settings (`.pi/settings.json`) so the install is checked into your team's repo and auto-applied when a teammate opens the project:\n\n```bash\npi install -l git:github.com/bramburn/pi-model-council@v1.5.0\n```\n\nAfter this, commit `.pi/settings.json` to your project. When a teammate clones and Pi trusts the project, the extension installs automatically.\n\n### Verify the install\n\n```bash\npi list                # shows installed packages and their sources\npi config              # toggle the extension on/off\n```\n\nYou should see `pi-model-council` listed and enabled. Then launch Pi and try:\n\n```bash\npi\n> /council-settings\n```\n\nIf the council UI opens, the extension is loaded correctly.\n\n### Updating an installed version\n\n```bash\n# Update every installed extension to the ref pinned in settings\npi update --extensions\n\n# Update only this one to a new tag/commit\npi install git:github.com/bramburn/pi-model-council@v1.6.0   # overwrites\n```\n\n### Uninstalling\n\n```bash\npi remove pi-model-council\n```\n\nOr manually delete the entry from `~/.pi/agent/settings.json` and remove the clone under `~/.pi/agent/git/github.com/bramburn/pi-model-council/`. Your settings file (`council-settings.json`) is kept — re-installing will pick them back up.\n\n---\n\n## Workflow: Getting Started in 3 Steps\n\nThe whole setup takes about a minute. Follow this order — each step builds on the previous one.\n\n### Step 1 — Authenticate with OpenRouter (one-time)\n\nPi already ships with an OpenRouter provider. Give it your API key once and Pi will list every supported OpenRouter model under `provider === \"openrouter\"`.\n\n```bash\n# Option A — environment variable (recommended for CI / dotfiles)\nexport OPENROUTER_API_KEY=\"sk-or-v1-...\"\n\n# Option B — interactive login (stores in ~/.pi/agent/auth.json with 0600 perms)\n# Inside Pi, run:\n/login openrouter\n```\n\nGet a key from [openrouter.ai/keys](https://openrouter.ai/keys).\n\nVerify Pi sees the models:\n\n```bash\npi --list-models\n# Look for entries like:\n#   openrouter/anthropic/claude-3.5-sonnet\n#   openrouter/openai/gpt-4o\n#   openrouter/qwen/qwen3.7-max\n```\n\n### Step 2 — Configure the extension\n\nRun the council settings UI. Pi will detect OpenRouter in your model registry and skip the API-key prompt entirely.\n\n```bash\n/council-settings\n```\n\nThe UI uses a **typeahead-searchable, scrollable picker** (same UX as `/model`), so you can filter through hundreds of OpenRouter models by typing a few characters — fuzzy-matched across model name and id. Up/Down to navigate, Enter to select, Esc to cancel.\n\nThe UI walks you through:\n\n1. **Council Model 1 of 3** — first dissenting voice\n2. **Council Model 2 of 3** — second dissenting voice (already-picked models are filtered out)\n3. **Council Model 3 of 3** — third dissenting voice\n4. **Synthesis Model** — reads all three opinions and writes the final plan. Defaults to \"Council Model 1\" since you already trust it; pick any OpenRouter model you like.\n5. **Second Opinion Model** — the model used by `/opinion` for quick checks\n6. **Structured Output** — JSON schema for faster parsing (recommended: yes)\n\nIf Pi doesn't see OpenRouter yet (because you skipped Step 1), the UI falls back to a manual flow: it asks for an API key, pings OpenRouter to verify, fetches the live model list, and proceeds the same way.\n\nRe-run anytime to swap models:\n\n```bash\n/council-settings list    # show current config\n/council-settings reset   # wipe and reconfigure\n```\n\nFor just the opinion model:\n\n```bash\n/opinion-settings\n```\n\n### Step 3 — Use the council\n\n```bash\n# Three deliberating models + one synthesis model\n/council fix \"The login fails on mobile devices\"\n/council ask \"Should we use hooks or context for state?\"\n/council architecture \"Where should auth state live?\"\n\n# Single-model quick check (uses your /opinion model)\n/opinion \"How should I refactor this function?\"\n/opinion fix \"Lesson progress resets after navigation\"\n```\n\nThe full report is saved to `.pi/council/last-decision.md` (and `.pi/council/last-opinion.md` for `/opinion`). The Pi agent can read this file when continuing the conversation.\n\n---\n\n## How Model Selection Works\n\nThe extension uses two layers:\n\n| Layer | Source | Purpose |\n|---|---|---|\n| **Discovery** | `ctx.modelRegistry.getAvailable()` | Lists every OpenRouter model Pi knows about (the curated list ships with Pi). Filtered to `provider === \"openrouter\"`. |\n| **Validation** | Same registry, then OpenRouter `/models` fallback | Confirms each chosen model ID still exists at runtime. |\n| **Inference** | Direct OpenRouter REST call (`openrouterClient.ts`) | Sends the council prompts to each model and the synthesis prompt to the chosen synthesiser. |\n\nThis means:\n\n- If Pi already knows about OpenRouter (Step 1 above), `ctx.modelRegistry.getAvailable()` provides the model list — no extra HTTP call.\n- If the registry is empty (e.g. fresh install, no API key yet), the UI prompts for a key and fetches the live model list directly from OpenRouter.\n- The chosen model IDs are stored in `council-settings.json` and used to make the actual inference calls. They are not changed when you swap Pi providers.\n\n### Why a separate Synthesis Model?\n\nThe three council members argue from different angles; the synthesis model is the judge that weighs them and produces a single plan. By default it's \"Council Model 1\" because you already trust that model. For higher-stakes decisions, pick a model known for careful reasoning (e.g. `anthropic/claude-3.5-sonnet`, `openai/o1`) — it's a 4th API call on top of the three council calls.\n\n### The 4-Model Orchestration Flow\n\nWhen you run `/council`, a single `council_decide` tool call internally fans out to four models. Pi sees only the final synthesised report — all sub-calls are hidden behind one logical tool:\n\n```\n                       /council fix \"...\"         (slash command)\n                              │\n                              ▼\n              ┌───────────────────────────────┐\n              │   council_decide tool         │   (single Pi tool call)\n              │   councilRunner.ts            │\n              └───────────────────────────────┘\n                              │\n            ┌─────────────────┼─────────────────┐\n            │                 │                 │\n            ▼                 ▼                 ▼\n      ┌──────────┐       ┌──────────┐       ┌──────────┐\n      │ Model 1  │       │ Model 2  │       │ Model 3  │   ← Promise.all\n      │ (council)│       │ (council)│       │ (council)│     parallel\n      └────┬─────┘       └────┬─────┘       └────┬─────┘     fan-out\n           │                  │                  │\n           ▼                  ▼                  ▼\n       opinion A          opinion B          opinion C        (JSON)\n           │                  │                  │\n           └──────────────────┼──────────────────┘\n                              ▼\n                       ┌──────────────┐\n                       │  Model 4     │                         ← synthesis step\n                       │  (synthesis) │                            reads 3 opinions\n                       └──────┬───────┘                            writes 1 decision\n                              ▼\n                       ┌──────────────┐\n                       │  Pi receives │   ← tool result returned to agent\n                       │  Markdown    │\n                       │  report      │\n                       └──────────────┘\n```\n\n**Key properties:**\n\n- All four calls happen **inside one tool execution**, so the agent sees a single tool call in its reasoning graph — no multi-turn coordination overhead.\n- The three council calls run **in parallel** via `Promise.all`. Each call has its own `withTimeout` + `retry` wrapper, so a slow or failing model doesn't block the others.\n- If 1 of 3 council models fails, the synthesis step still proceeds with the 2 successful opinions. If all 3 fail, the runner surfaces a clear error.\n- The synthesis call has a longer timeout (`synthesisTimeoutMs`) because the synthesis prompt includes all three opinions.\n- `ctx.signal` is threaded through every downstream `fetch()`, so pressing Esc during a long council cancels all in-flight calls (Pi's `withTimeout` helper combines the parent signal with per-call timeouts).\n\n### Where the API Key Comes From\n\nResolution order:\n\n1. `council-settings.json` → `openRouter.apiKey` (the legacy explicit-prompt flow)\n2. Pi's auth storage → `modelRegistry.getApiKeyForProvider(\"openrouter\")` (when you've set `OPENROUTER_API_KEY` or run `/login openrouter`)\n3. `process.env.OPENROUTER_API_KEY`\n\nIf none of these resolve, `/council` and `/opinion` both fail fast with a setup error pointing you at `/council-settings`.\n\n---\n\n## Commands\n\n### `/council`\n\nThree OpenRouter models + one synthesis model.\n\n```bash\n/council fix \"The login fails on mobile devices\"\n/council ask \"Should we use hooks or context for state?\"\n/council architecture \"Where should auth state live?\"\n```\n\nModes:\n\n- `fix` — debug a known problem\n- `ask` — open technical question\n- `architecture` — design-level decision\n- (default) — generic second opinion\n\n### `/opinion`\n\nSingle-model quick check.\n\n```bash\n/opinion \"How should I refactor this function?\"\n/opinion fix \"The lesson progress resets after navigation\"\n```\n\nModes: `fix`, `ask`, `architecture`, `general`.\n\n### `/council-settings`\n\nConfigure the OpenRouter setup.\n\n```bash\n/council-settings          # Open the settings UI (3 council + 1 synthesis + opinion)\n/council-settings list     # Show current settings (API key redacted)\n/council-settings reset    # Reset all council settings\n```\n\n### `/opinion-settings`\n\nConfigure just the `/opinion` model.\n\n```bash\n/opinion-settings          # Open the settings UI\n/opinion-settings list     # Show current model\n/opinion-settings reset    # Reset to default\n```\n\n---\n\n## Models\n\n### Council (3 Members)\n\nThe three models you pick in `/council-settings` for independent deliberation. Recommended starting set (all available on OpenRouter):\n\n- `anthropic/claude-3.5-sonnet` — careful reasoning, code quality\n- `openai/gpt-4o` — broad knowledge, multimodal\n- `qwen/qwen3.7-max` — strong code model, cost-effective\n\nPick models that disagree productively. If they all share a training cutoff, you get correlated blind spots.\n\n### Synthesis Model\n\nThe fourth model that reads the three opinions and writes the single recommendation. Defaults to \"Council Model 1\". For high-stakes decisions, consider a reasoning-tuned model:\n\n- `openai/o1` — strong step-by-step reasoning\n- `anthropic/claude-3.5-sonnet` — careful, balanced\n- `deepseek/deepseek-r1` — reasoning model with low cost\n\n### Second Opinion (`/opinion`)\n\nAny model with valid auth works. Pi's `modelRegistry.getAvailable()` is the source of truth, so any provider you've configured (Anthropic, OpenAI, Google, OpenRouter, etc.) is selectable.\n\n---\n\n## Architecture\n\n```\npi-model-council/\n├── index.ts                # Extension entry point\n├── councilRunner.ts        # 3-model + synthesis logic\n├── secondOpinionRunner.ts  # Single-model logic\n├── settings.ts             # Settings persistence\n├── settings-ui.ts          # Settings TUI components (registry-aware)\n├── openrouterClient.ts     # OpenRouter REST client + JSON repair\n├── prompts.ts              # Proposal + synthesis prompts (blind-label)\n├── structuredOutput.ts     # JSON schemas + repair\n├── markdown.ts             # Decision report formatter\n├── searchSelector.ts       # Searchable model picker (custom TUI)\n├── retry.ts                # Timeout + retry helpers\n├── schemas.ts              # TypeBox tool parameter schemas\n├── commandParser.ts        # CLI argument parsers\n└── types.ts                # Shared TypeScript types\n```\n\n---\n\n## Development\n\n```bash\n# Install dependencies\nnpm install\n\n# Type check\nnpm run typecheck\n\n# Lint\nnpm run lint\n\n# Run tests\nnpm test\n\n# Watch mode\nnpm run test:watch\n\n# Coverage\nnpm run test:coverage\n\n# Audit dependencies\nnpm run audit\n```\n\n---\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) for the security policy and reporting vulnerabilities.\n\nAPI keys are stored in `council-settings.json` (when set explicitly) or in Pi's `~/.pi/agent/auth.json` (when set via env var or `/login`). Both files use `0600` permissions and are gitignored. The extension never logs keys, but does echo a redacted preview when listing settings.\n\n## Disclaimer\n\nThis extension sends your prompts to OpenRouter and the model providers behind it. AI-generated suggestions can be wrong, outdated, or unsafe — always review before applying. See [DISCLAIMER.md](DISCLAIMER.md) for the full text.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for setup, code style, and the PR process.\n\n## Code of Conduct\n\nBy participating, you agree to the Contributor Covenant in [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).\n\n## Support\n\nOpen an issue using the templates in [`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/). See [SUPPORT.md](SUPPORT.md) for where to ask questions.\n\n## License\n\nMIT — see [LICENSE](LICENSE).","readmeFilename":"README.md","homepage":"https://github.com/bramburn/pi-model-council#readme","keywords":["pi-package","pi-extension","openrouter","ai-council"],"repository":{"type":"git","url":"git+https://github.com/bramburn/pi-model-council.git"},"bugs":{"url":"https://github.com/bramburn/pi-model-council/issues"}}