From a16df666d17becf99c958dcbd07a466b512a08c5 Mon Sep 17 00:00:00 2001 From: Martin Bens Date: Fri, 2 Oct 2026 12:09:52 +0200 Subject: [PATCH 01/11] feat(github-mcp): add pi as a third host github-mcp now runs in pi 1.0.0 as the npm package @shopware-ag/github-agent-tools, rooted at the repository. The extension in plugins/github-mcp/pi/ registers the existing gh-tooling and gh-tooling-write servers with deferred exposure, injects the SessionStart directive as the github_mcp system-prompt section, and runs check-gh-tools.sh and check-api-tools.sh on pi's tool_call event. gate.ts spawns each script in its own process group with GITHUB_MCP_HOST=pi and without CLAUDE_PROJECT_DIR, kills the group on timeout, and fails open on anything but exit 2, so the shell scripts stay the single implementation of the gates and the directive. The hooks and servers gain a .pi/.mcp-gh-tooling.json location. One rule in shared/config-dirs.sh now produces the config order for every host: the active host's directory, then the rest of .claude/, .codex/, .pi/, then the project root. _load_gh_config moves into mcp-server-gh/lib/common.sh instead of being duplicated in both servers. CLAUDE_PROJECT_DIR still selects Claude Code ahead of GITHUB_MCP_HOST, so an exported host variable cannot misroute Claude Code's hooks. Tests cover the pi host and the new config location in BATS, gate.ts with node:test unit tests, and the whole package in pi_e2e.bats, which drives the real pi binary with a scripted faux model and a stubbed gh against both the git-clone and npm-tarball install layouts. CI gains Node 24, npm ci, the type-check, and the unit tests. npm-publish.yml publishes on v* tags through npm trusted publishing after checking the tag against all three version files. The version is 4.4.0 in package.json and both plugin manifests. Co-Authored-By: Claude --- .github/ISSUE_TEMPLATE/config.yml | 4 + .github/workflows/ci.yml | 24 + .github/workflows/npm-publish.yml | 71 + .gitignore | 3 + AGENTS.md | 71 +- README.md | 24 +- package-lock.json | 3683 +++++++++++++++++ package.json | 32 + plugin-tests/github-mcp/check_api_tools.bats | 40 + plugin-tests/github-mcp/fixtures/pi/driver.ts | 102 + .../github-mcp/fixtures/pi/gh-stub.sh | 18 + plugin-tests/github-mcp/gh_tools.bats | 86 + plugin-tests/github-mcp/package_contents.bats | 79 + plugin-tests/github-mcp/package_manifest.bats | 79 + plugin-tests/github-mcp/pi/gate.test.ts | 105 + plugin-tests/github-mcp/pi_e2e.bats | 348 ++ plugin-tests/github-mcp/session_start.bats | 89 + .../github-mcp/write_server_gating.bats | 82 + plugin-tests/test_helper/common_setup.bash | 10 + plugins/github-mcp/.claude-plugin/plugin.json | 2 +- plugins/github-mcp/.codex-plugin/plugin.json | 2 +- plugins/github-mcp/AGENTS.md | 44 +- plugins/github-mcp/CHANGELOG.md | 19 + plugins/github-mcp/README.md | 36 +- plugins/github-mcp/hooks/prompts/host-pi.md | 1 + .../hooks/scripts/check-api-tools.sh | 2 +- .../hooks/scripts/check-gh-tools.sh | 2 +- .../github-mcp/hooks/scripts/lib/common.sh | 57 +- .../github-mcp/hooks/scripts/session-start.sh | 8 +- .../github-mcp/mcp-server-gh/lib/common.sh | 58 + .../github-mcp/mcp-server-gh/server-read.sh | 53 +- .../github-mcp/mcp-server-gh/server-write.sh | 51 +- plugins/github-mcp/pi/gate.ts | 67 + plugins/github-mcp/pi/index.ts | 70 + plugins/github-mcp/shared/config-dirs.sh | 28 + renovate.json | 11 +- tsconfig.json | 19 + 37 files changed, 5316 insertions(+), 164 deletions(-) create mode 100644 .github/workflows/npm-publish.yml create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 plugin-tests/github-mcp/fixtures/pi/driver.ts create mode 100755 plugin-tests/github-mcp/fixtures/pi/gh-stub.sh create mode 100644 plugin-tests/github-mcp/package_contents.bats create mode 100644 plugin-tests/github-mcp/package_manifest.bats create mode 100644 plugin-tests/github-mcp/pi/gate.test.ts create mode 100644 plugin-tests/github-mcp/pi_e2e.bats create mode 100644 plugins/github-mcp/hooks/prompts/host-pi.md create mode 100644 plugins/github-mcp/pi/gate.ts create mode 100644 plugins/github-mcp/pi/index.ts create mode 100644 plugins/github-mcp/shared/config-dirs.sh create mode 100644 tsconfig.json diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 58ee42b..5e44eb0 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -16,3 +16,7 @@ contact_links: - name: 🤖 Codex Plugin Guide url: https://learn.chatgpt.com/docs/build-plugins about: "Learn how to install and manage Codex plugins" + + - name: 📦 pi Package Guide + url: https://pi.dev/packages + about: "Learn how to install and manage pi packages" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ad77b6..a14a7c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,16 +8,26 @@ on: - 'plugin-tests/**' - '.mcp-sdk.lock' - '.shellcheckrc' + - '.gitignore' - '.github/workflows/ci.yml' - '.github/scripts/**' + - 'package.json' + - 'package-lock.json' + - 'tsconfig.json' + - '.github/workflows/npm-publish.yml' pull_request: paths: - 'plugins/**' - 'plugin-tests/**' - '.mcp-sdk.lock' - '.shellcheckrc' + - '.gitignore' - '.github/workflows/ci.yml' - '.github/scripts/**' + - 'package.json' + - 'package-lock.json' + - 'tsconfig.json' + - '.github/workflows/npm-publish.yml' jobs: bash: @@ -28,6 +38,20 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + + - name: Install npm dependencies + run: npm ci + + - name: Type-check + run: npx tsc --noEmit -p . + + - name: Node unit tests + run: node --test 'plugin-tests/github-mcp/pi/*.test.ts' + - name: Install jq run: sudo apt-get update && sudo apt-get install -y jq diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..524c571 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,71 @@ +name: npm Publish + +on: + push: + tags: + - 'v*' + +jobs: + publish: + name: Publish to npm + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + + # Trusted publishing needs npm 11.5.1 or later. + - name: Update npm + run: npm install -g npm@11.21.0 + + - name: Install jq + run: sudo apt-get update && sudo apt-get install -y jq + + - name: Setup BATS + uses: bats-core/bats-action@77d6fb60505b4d0d1d73e48bd035b55074bbfb43 # 4.0.0 + with: + bats-version: 1.13.0 + support-install: true + assert-install: true + assert-version: 2.2.4 + detik-install: false + file-install: false + support-path: ${{ github.workspace }}/.bats/bats-support + assert-path: ${{ github.workspace }}/.bats/bats-assert + + - name: Install npm dependencies + run: npm ci + + - name: Type-check + run: npx tsc --noEmit -p . + + - name: Node unit tests + run: node --test 'plugin-tests/github-mcp/pi/*.test.ts' + + - name: BATS + run: | + echo "Running BATS tests with timing..." + bats --timing -r plugin-tests/ + + - name: Verify tag matches package and plugin manifest versions + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + PACKAGE_VERSION=$(jq -r '.version' package.json) + CLAUDE_VERSION=$(jq -r '.version' plugins/github-mcp/.claude-plugin/plugin.json) + CODEX_VERSION=$(jq -r '.version' plugins/github-mcp/.codex-plugin/plugin.json) + if ! { [[ "${TAG_VERSION}" == "${PACKAGE_VERSION}" ]] && [[ "${TAG_VERSION}" == "${CLAUDE_VERSION}" ]] && [[ "${TAG_VERSION}" == "${CODEX_VERSION}" ]]; }; then + echo "Version mismatch: tag=${TAG_VERSION} package.json=${PACKAGE_VERSION} claude-plugin=${CLAUDE_VERSION} codex-plugin=${CODEX_VERSION}" >&2 + exit 1 + fi + + - name: Publish to npm + run: npm publish diff --git a/.gitignore b/.gitignore index 7e1fef0..b136b3d 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ AGENTS.local.md # BATS testing framework (installed locally via .github/scripts/setup-bats.sh) .bats/ + +# Node (pi package development install) +node_modules/ diff --git a/AGENTS.md b/AGENTS.md index f5b809c..1758258 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,16 +9,20 @@ from [shopwareLabs/ai-coding-tools](https://github.com/shopwareLabs/ai-coding-to > **Plugin name vs. server names.** The installable plugin is `github-mcp`, but its two MCP > servers keep their original IDs `gh-tooling` (read) and `gh-tooling-write` (write). So you > install `github-mcp@github-agent-tools`, while `/mcp` and `.mcp-gh-tooling.json` use the raw -> server IDs. Claude Code exposes names such as `mcp__plugin_github-mcp_gh-tooling__…`; Codex -> exposes the same tools as `mcp__gh_tooling__…` after sanitizing the server ID. +> server IDs. Claude Code exposes names such as `mcp__plugin_github-mcp_gh-tooling__…`; Codex and +> pi expose the same tools as `mcp__gh_tooling__…` after sanitizing the server ID — pi's tool +> names match Codex's. -## Dual-target: Claude Code and Codex +## Three hosts: Claude Code, Codex, and pi The plugin's core is assistant-neutral: `plugins/github-mcp/mcp-server-gh/server-{read,write}.sh` -are plain stdio [MCP](https://modelcontextprotocol.io/) servers that both hosts spawn. Claude Code -uses `.claude-plugin/marketplace.json`, `.claude-plugin/plugin.json`, and `.mcp.json`; Codex uses -`.agents/plugins/marketplace.json` and `.codex-plugin/plugin.json`. The hook definition and scripts -are shared. Keep the MCP server scripts and hook behavior as the single core across both hosts. +are plain stdio [MCP](https://modelcontextprotocol.io/) servers that all three hosts spawn. Claude +Code uses `.claude-plugin/marketplace.json`, `.claude-plugin/plugin.json`, and `.mcp.json`; Codex +uses `.agents/plugins/marketplace.json` and `.codex-plugin/plugin.json`; pi installs the repository +root as an npm package and loads its extension from the package entry point +`plugins/github-mcp/pi/index.ts`, declared in root `package.json`'s `pi.extensions`. The hook +definition and scripts are shared. Keep the MCP server scripts and hook behavior as the single core +across all three hosts. The separate `plugin-setup` plugin remains Claude Code-only because its skill uses Claude Code interaction and permission-setting features. Do not list it in the Codex marketplace unless that @@ -31,17 +35,21 @@ directly — treat them as you would any source file. For `github-mcp` these are - `.mcp.json` — Claude Code MCP server registration - `.codex-plugin/plugin.json` — Codex manifest and MCP server registration +- `plugins/github-mcp/pi/` — pi extension (server registration, directive, and gates) +- root `package.json` — pi npm package manifest, including `pi.extensions` - `mcp-server-gh/` — the read/write servers, tool definitions (`tools-*.json`), and `lib/*.sh` - `shared/mcpserver_core.sh` — JSON-RPC protocol handler +- `shared/config-dirs.sh` — host config directory order, used by the hooks and the servers (not vendored) - `hooks/` — `hooks.json` plus the SessionStart/PreToolUse scripts -The `github-mcp` plugin ships only MCP servers and hooks — no skills, slash commands, or agents. +The `github-mcp` plugin ships MCP servers, hooks, and (for pi) the extension that adapts them — +no skills, slash commands, or agents. The separate `plugin-setup` plugin ships one skill (`github-mcp-setting-up`); its `skills/*/SKILL.md` is a runtime file. If `github-mcp` gains its own `skills/*/SKILL.md`, `commands/*.md`, or `agents/*.md` later, those are runtime files too. **Developer documentation is not runtime.** Repository and plugin `README.md`, `AGENTS.md`, -`CLAUDE.md` (where present), and `CHANGELOG.md` files are read by maintainers; neither host loads +`CLAUDE.md` (where present), and `CHANGELOG.md` files are read by maintainers; no host loads them as installed plugin runtime. When changing runtime behavior, edit runtime files; when updating guides or architecture notes, edit the docs. @@ -130,8 +138,8 @@ convention. Update `plugins/github-mcp/README.md` and `REFERENCE.md` when tool b ### Version bumps -Edit the version in both `plugins/github-mcp/.claude-plugin/plugin.json` and -`plugins/github-mcp/.codex-plugin/plugin.json`, then add a matching +Edit the version in `plugins/github-mcp/.claude-plugin/plugin.json`, +`plugins/github-mcp/.codex-plugin/plugin.json`, and root `package.json`, then add a matching `plugins/github-mcp/CHANGELOG.md` entry. `plugin-setup` is versioned independently in its own Claude Code manifest and `CHANGELOG.md`; keep the `version` in each of its skills' SKILL.md frontmatter equal to that manifest version. @@ -171,6 +179,19 @@ codex plugin list --available --json # inspect the resolved Codex marketplac codex plugin add github-mcp@github-agent-tools ``` +### Node and pi + +```bash +npm ci # installs the pi devDependencies +npx tsc --noEmit -p . # type-checks the extension and its tests +node --test 'plugin-tests/github-mcp/pi/*.test.ts' # gate.ts unit tests +``` + +`plugin-tests/github-mcp/pi_e2e.bats` drives the real `pi` binary against a scripted model and a +stubbed `gh`; `plugin-tests/github-mcp/package_contents.bats` and `package_manifest.bats` check the +npm package. All three run as part of the BATS suite below. `pi_e2e.bats` needs GNU coreutils' +`timeout` (or `gtimeout`) on PATH; on macOS, install it with `brew install coreutils`. + ### BATS ```bash @@ -180,16 +201,21 @@ codex plugin add github-mcp@github-agent-tools Tests live in `plugin-tests//` mirroring the plugin structure and load the shared helper at `plugin-tests/test_helper/common_setup.bash` (it resolves the repo root by walking up to `.bats/`). -CI (`.github/workflows/ci.yml`) runs ShellCheck over `plugins plugin-tests .github/scripts`, -`vendor-mcp-sdk.sh --check` for the vendored SDK copy, and BATS over `plugin-tests/`; a separate -`validate.yml` checks the issue-template dropdowns. +CI (`.github/workflows/ci.yml`) runs `npm ci`, the type-check, and the Node unit tests, then +ShellCheck over `plugins plugin-tests .github/scripts`, `vendor-mcp-sdk.sh --check` for the +vendored SDK copy, and BATS over `plugin-tests/` (including `pi_e2e.bats` and +`package_contents.bats`); a separate `validate.yml` checks the issue-template dropdowns. ### Pre-release checklist - [ ] `claude plugin validate .` passes - [ ] Codex marketplace add, list, and plugin install smoke test passes -- [ ] Plugin version bumped in both host manifests with a CHANGELOG entry -- [ ] BATS green (`.bats/bats-core/bin/bats -r plugin-tests/`) +- [ ] Plugin version bumped in the Claude Code and Codex manifests and root `package.json`, with a + CHANGELOG entry +- [ ] `npx tsc --noEmit -p .` passes +- [ ] `node --test 'plugin-tests/github-mcp/pi/*.test.ts'` passes +- [ ] BATS green (`.bats/bats-core/bin/bats -r plugin-tests/`), including `pi_e2e.bats` and + `package_contents.bats` - [ ] ShellCheck clean - [ ] Issue-template dropdowns up to date (`.github/scripts/validate-issue-templates.sh`) - [ ] Vendored SDK matches its lock (`.github/scripts/vendor-mcp-sdk.sh --check`) @@ -203,6 +229,19 @@ then `/plugin install github-mcp@github-agent-tools`. Codex installs with `codex plugin marketplace add shopwareLabs/github-agent-tools` then `codex plugin add github-mcp@github-agent-tools`. +pi installs the repository as the npm package `@shopware-ag/github-agent-tools`, with +`pi install npm:@shopware-ag/github-agent-tools` or `pi install git:github.com/shopwareLabs/github-agent-tools`. +`.github/workflows/npm-publish.yml` publishes on a pushed tag matching `v*`: it type-checks, runs +the Node unit tests and the full BATS suite, checks the tag against `package.json` and both plugin +manifests' versions, then runs `npm publish` using npm trusted publishing (no stored npm token). +Releasing a version bumps `package.json`, both plugin manifests, and +`plugins/github-mcp/CHANGELOG.md` in one commit, then tags `v` on that commit and pushes +the tag. Bootstrap, once: a maintainer with publish rights in the `@shopware-ag` npm organization +publishes the first version by hand from a clean checkout of its release tag with `npm publish`, +then runs +`npm trust github @shopware-ag/github-agent-tools --file npm-publish.yml --repository shopwareLabs/github-agent-tools --allow-publish` +(npm 11.15.0+). Every later version publishes from the workflow. + ## Using Anthropic dev plugins When developing here with Anthropic's official `plugin-dev`/`feature-dev` plugins, invoke their diff --git a/README.md b/README.md index ce25a41..f9c043c 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ GitHub CLI tools for AI coding agents. Wraps the GitHub CLI (`gh`) behind MCP servers — pull requests, issues, CI runs, jobs, commits, search, labels, projects, and repository file browsing — as first-class MCP tools, with hook-based enforcement that keeps the agent on the tools instead of raw `gh` bash calls. -The MCP servers use the assistant-neutral [Model Context Protocol](https://modelcontextprotocol.io/) and can be integrated with MCP-capable coding agents. This repository provides tested `github-mcp` plugin packaging for both [Claude Code](https://docs.claude.com/en/docs/claude-code/plugins) and [Codex](https://learn.chatgpt.com/docs/build-plugins). +The MCP servers use the assistant-neutral [Model Context Protocol](https://modelcontextprotocol.io/) and can be integrated with MCP-capable coding agents. This repository provides tested `github-mcp` plugin packaging for [Claude Code](https://docs.claude.com/en/docs/claude-code/plugins), [Codex](https://learn.chatgpt.com/docs/build-plugins), and [pi](https://pi.dev). > **Origin**: Extracted from its sibling project [shopwareLabs/ai-coding-tools](https://github.com/shopwareLabs/ai-coding-tools) — a broader AI-coding-tools marketplace — into this standalone repository. The GitHub tooling was split out so it can evolve and be installed independently. ## ⚡ Quick Start -**Packaged plugin requirements:** `gh` CLI authenticated (`gh auth login`), `jq`, and either [Claude Code](https://docs.claude.com/en/docs/claude-code) or [Codex](https://developers.openai.com/codex/cli/). +**Packaged plugin requirements:** `gh` CLI authenticated (`gh auth login`), `jq`, and [Claude Code](https://docs.claude.com/en/docs/claude-code), [Codex](https://developers.openai.com/codex/cli/), or [pi](https://pi.dev). ### Claude Code @@ -30,12 +30,32 @@ codex plugin add github-mcp@github-agent-tools Start a new Codex task after installation. Open `/hooks` to review and trust the bundled enforcement hooks; Codex skips untrusted plugin hooks. +### pi + +Install from npm: + +```bash +pi install npm:@shopware-ag/github-agent-tools +``` + +Or install from git: + +```bash +pi install git:github.com/shopwareLabs/github-agent-tools +``` + +Run one of the two, never both: installing from both sources loads the extension twice. + +pi needs no restart beyond a new session or `/reload`. The read server works without configuration. + The read server (`gh-tooling`) is always active. The write server (`gh-tooling-write`) is opt-in via `enable_write_server: true` in a `.mcp-gh-tooling.json` config file. Configuration is optional — the read server works out of the box when `gh` is authenticated. **Optional Claude Code setup:** the companion `plugin-setup` plugin remains Claude Code-only because it uses Claude Code interaction and permission settings. Install it with `/plugin install plugin-setup@github-agent-tools`, ask Claude to *"set up github-mcp"*, then uninstall it when setup is complete. Codex users configure `.mcp-gh-tooling.json` manually as described in the plugin guide. ## 🧩 What's Inside +Supported hosts: Claude Code, Codex, and pi. + | Component | Description | |------------|------------------------------------------------------------------------------------------------------| | 🔌 MCP | Two servers — `gh-tooling` (31 read tools) and `gh-tooling-write` (25 write tools, gated) | diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2058044 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3683 @@ +{ + "name": "@shopware-ag/github-agent-tools", + "version": "4.4.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@shopware-ag/github-agent-tools", + "version": "4.4.0", + "license": "MIT", + "devDependencies": { + "@earendil-works/pi-ai": "1.0.0", + "@earendil-works/pi-coding-agent": "1.0.0", + "@types/node": "24.19.1", + "typescript": "7.0.2" + }, + "engines": { + "node": ">=22.19.0" + }, + "peerDependencies": { + "@earendil-works/pi-coding-agent": "*" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.124.0.tgz", + "integrity": "sha512-cN5O8i9UVxHeOQAzj/XjshWXG8KiibJDw9OGpH2Z/eR3n/RBxdoLxDJOcfqAJWvjaMDFfHTBADU04hWRJVkDyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1127.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1127.0.tgz", + "integrity": "sha512-IDl/lrPb90aH+pZFHGNDmgH9nAUQj5PlZH1sJ3w7RikctyjHSnY3oNjZhrLoaBoQn/rNK0zsP6OHEqEhj2tdLA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/credential-provider-node": "^3.972.82", + "@aws-sdk/eventstream-handler-node": "^3.972.34", + "@aws-sdk/middleware-eventstream": "^3.972.29", + "@aws-sdk/middleware-websocket": "^3.972.52", + "@aws-sdk/token-providers": "3.1127.0", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.978.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.1.tgz", + "integrity": "sha512-LbY9aGsEiznDWmUc30Nwv3aIX/+dbwTx8KfS0yOC3NPYMO+O91e6jkT1azf34FwjOndq8/Q+RcVVZz5xnerwdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.6", + "@aws-sdk/xml-builder": "^3.972.41", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.35.0", + "@smithy/signature-v4": "^5.7.3", + "@smithy/types": "^4.19.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.72.tgz", + "integrity": "sha512-xTKO/FWJPozTIXbozVnVGoNBhaGba8TBcx+KyUjRVeOlXE+dUc7GTR1cLvu0uTdIdmemzaFbqqCshXeZA1fZew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.74", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.74.tgz", + "integrity": "sha512-u91E/hT8f4d1xy0Jl7VG4nVKJ3lxbrZkoBTeSVoJdWBiSEUMwMS/9+e0H/aJVQV//Lt5wuzP+E69v4aRSsNTmw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.17.tgz", + "integrity": "sha512-ged4KXdBkvIC81bLvNHHuQKdKak/VXhQTR1NWYTTqW0474nlmsxy9O/vlgTIohDDWH3xpBdtVMZRyjb+DnocDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/credential-provider-env": "^3.972.72", + "@aws-sdk/credential-provider-http": "^3.972.74", + "@aws-sdk/credential-provider-login": "^3.972.79", + "@aws-sdk/credential-provider-process": "^3.972.72", + "@aws-sdk/credential-provider-sso": "^3.973.16", + "@aws-sdk/credential-provider-web-identity": "^3.972.78", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.79", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.79.tgz", + "integrity": "sha512-L+Z85anONJd8MaiuraO4wRxATCdEejBZ3K3eymzWI5JPXa9sOS9CkIm72PBKqXKX+Z9p9NGMX5AIMXm0LEflgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.84", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.84.tgz", + "integrity": "sha512-oHt854odINVwzwsh+c5x69j0ajm4DbqqqVJ+O1ECsCIZeMDAbzFpXItaqP7UZstJj/ATdTk/KFSH0LaNAgV+kA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.72", + "@aws-sdk/credential-provider-http": "^3.972.74", + "@aws-sdk/credential-provider-ini": "^3.973.17", + "@aws-sdk/credential-provider-process": "^3.972.72", + "@aws-sdk/credential-provider-sso": "^3.973.16", + "@aws-sdk/credential-provider-web-identity": "^3.972.78", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.72.tgz", + "integrity": "sha512-rLIp2xbMjX/k9/od7APpqq1ZgXXnV0pOL1Th3ZsL8Wu0TRtBsDTVS8iPqcfRFcHakFxPvR04OSTv2ka2qOb/2A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.16.tgz", + "integrity": "sha512-IGihaJfFZYacJJr/odqILCoK7W/mvrZ7cuK7ECn3sAu4vLC6u0V8bS7mCGbdugJ8Aum2tnvqmx0F2MRFp2rn9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/token-providers": "3.1138.0", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1138.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1138.0.tgz", + "integrity": "sha512-GpyAr0DD63YOEmYFM6Df+gJuIgC92MMTiBK4FTKfxii5MJ9ge20epR7LyroulscYlG89J+ZB2ivFDPjvfQhzdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.78", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.78.tgz", + "integrity": "sha512-/y9WvNtlcPBGLR0qc1a+9J/xtYZfVczvLUOuXaVWylzttH7ewsxwHtjmiJSolNrVSDorIxHGHMU61CbonRkmwA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.35", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.35.tgz", + "integrity": "sha512-a8xilRoRaalvSPZdfrs0VY3/BPc0uYhXW56Z/k6nmZ5Vk430LHeowf9APIPq8utM3tMJSNGSSuJMlB9YrPsN+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.30.tgz", + "integrity": "sha512-B6gvZlcnRBNWraKgyEjKsbhv+VjtbyHmpU7hmtTHzXpDSxHPpCJnYLpglEOOsF+SFne20DbZC5IVC1aNr2Pb4A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.54.tgz", + "integrity": "sha512-bhESdru8u8KosziH8jcVta/NM/dNreeRz2+2aU86si5bfBYOiJ6IiqD+9U4URUcU5WJO3Yw+jO3GLDIHlscXMQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/signature-v4": "^5.7.3", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.46.tgz", + "integrity": "sha512-oRxtBcka/JGHGs9l9p9IVajGoTP8vTPmoAzdHGy4Qcy9P5vPnDf6nhIeM/COQNY9k/OahImTRaLkHftoXvfcmQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.47", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.47", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.47.tgz", + "integrity": "sha512-Zk08macMvQTHzQJCLJVkOlviVoqwYMrpXv4lmLN7b7sAbiMoOK7Go0NYdR5UeF+MW8LIbRmwrNy9u/5VvX1U5g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.6", + "@smithy/signature-v4": "^5.7.3", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1127.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1127.0.tgz", + "integrity": "sha512-Dv2TMWBshJ+tF6ahs2Sy5bh4Iabsd4GAQqVvE9XZmYmnoaVbpS2QKIKE/HRacc7bTtbjEEvP+laGzHvHlf1CiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.6.tgz", + "integrity": "sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.41.tgz", + "integrity": "sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@earendil-works/pi-ai": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-1.0.0.tgz", + "integrity": "sha512-3/W1vdDaVtpeMd23ElvJC12HLA5yS/BGqqcXF+0SK082dN7cbgNcCwguTBRBC258Ke8SzSvUW1B75iAf8w8IxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "0.124.0", + "@aws-sdk/client-bedrock-runtime": "3.1127.0", + "@earendil-works/pi-telemetry": "^1.0.0", + "@google/genai": "2.21.0", + "@smithy/node-http-handler": "4.12.1", + "http-proxy-agent": "9.1.0", + "https-proxy-agent": "9.1.0", + "openai": "7.19.0", + "partial-json": "0.1.7", + "typebox": "1.3.27" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-1.0.0.tgz", + "integrity": "sha512-/FtbxoSQU/mEv1QnichJjRjqteqaIaMWxmhB4G367+MwZfX7/DI5B9YAg5lqbN7nztFskBEtUSZ+FlmMBECtMw==", + "dev": true, + "hasShrinkwrap": true, + "license": "MIT", + "dependencies": { + "@earendil-works/chord": "^1.0.0", + "@earendil-works/pi-agent-core": "^1.0.0", + "@earendil-works/pi-ai": "^1.0.0", + "@earendil-works/pi-codemode": "^1.0.0", + "@earendil-works/pi-mcp": "^1.0.0", + "@earendil-works/pi-tui": "^1.0.0", + "@silvia-odwyer/photon-node": "0.3.4", + "chalk": "6.0.0", + "cross-spawn": "7.0.6", + "diff": "8.0.4", + "grok-mermaid": "0.2.3", + "highlight.js": "10.7.3", + "hosted-git-info": "9.0.3", + "ignore": "7.0.8", + "jiti": "2.7.0", + "minimatch": "10.2.6", + "proper-lockfile": "4.1.2", + "quickjs-wasi": "3.6.2", + "semver": "7.8.5", + "typebox": "1.3.27", + "undici": "8.10.2", + "yaml": "2.9.0" + }, + "bin": { + "pi": "dist/bundle/cli.js" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@anthropic-ai/sdk": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.124.0.tgz", + "integrity": "sha512-cN5O8i9UVxHeOQAzj/XjshWXG8KiibJDw9OGpH2Z/eR3n/RBxdoLxDJOcfqAJWvjaMDFfHTBADU04hWRJVkDyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1127.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1127.0.tgz", + "integrity": "sha512-IDl/lrPb90aH+pZFHGNDmgH9nAUQj5PlZH1sJ3w7RikctyjHSnY3oNjZhrLoaBoQn/rNK0zsP6OHEqEhj2tdLA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/credential-provider-node": "^3.972.82", + "@aws-sdk/eventstream-handler-node": "^3.972.34", + "@aws-sdk/middleware-eventstream": "^3.972.29", + "@aws-sdk/middleware-websocket": "^3.972.52", + "@aws-sdk/token-providers": "3.1127.0", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/core": { + "version": "3.977.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.9.tgz", + "integrity": "sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@aws-sdk/xml-builder": "^3.972.40", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.33.3", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.70.tgz", + "integrity": "sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.72.tgz", + "integrity": "sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.15.tgz", + "integrity": "sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/credential-provider-env": "^3.972.70", + "@aws-sdk/credential-provider-http": "^3.972.72", + "@aws-sdk/credential-provider-login": "^3.972.77", + "@aws-sdk/credential-provider-process": "^3.972.70", + "@aws-sdk/credential-provider-sso": "^3.973.14", + "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.77", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.77.tgz", + "integrity": "sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.82", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.82.tgz", + "integrity": "sha512-znDkEOGXB8W3kG1LJUKP3foBZY/9qLM0eil/DxWXSp37XsdsRLQHE/d/OaCGGVgKpA6znR38h/+INk8do1FjiA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.70", + "@aws-sdk/credential-provider-http": "^3.972.72", + "@aws-sdk/credential-provider-ini": "^3.973.15", + "@aws-sdk/credential-provider-process": "^3.972.70", + "@aws-sdk/credential-provider-sso": "^3.973.14", + "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.70.tgz", + "integrity": "sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.14.tgz", + "integrity": "sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/token-providers": "3.1116.0", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1116.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1116.0.tgz", + "integrity": "sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.76", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.76.tgz", + "integrity": "sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.34.tgz", + "integrity": "sha512-cTeVzpu1xEAkryTZBYhGwnQ6gOGyp8ZYZvmn0Sg/nI/ABmy/CRHHxPDJDUi9PxwxUtGGaatvfRUB3FCgT/rSWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.29.tgz", + "integrity": "sha512-dlRzHCgyB8W6hLuDC5pcT5q+ziPt00n4QGgGBE17ucLVU4zMa6lsbuUdQ2Pm75Z5VA8GF+R/+SgrRcaTdIzSIQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.52", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.52.tgz", + "integrity": "sha512-vsPPM+nMbKJlUCFU+eoGZbdxdxDIAX9LbpjSXaR5Ufpmqgp8TdYQnoExhLu4T3umW/JIIPny1ydbhWidZZYokQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/nested-clients": { + "version": "3.997.44", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.44.tgz", + "integrity": "sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/signature-v4-multi-region": "^3.996.46", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.46.tgz", + "integrity": "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/token-providers": { + "version": "3.1127.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1127.0.tgz", + "integrity": "sha512-Dv2TMWBshJ+tF6ahs2Sy5bh4Iabsd4GAQqVvE9XZmYmnoaVbpS2QKIKE/HRacc7bTtbjEEvP+laGzHvHlf1CiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/types": { + "version": "3.974.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.5.tgz", + "integrity": "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.40", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.40.tgz", + "integrity": "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/chord": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@earendil-works/chord/-/chord-1.0.0.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "0.28.2" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-1.0.0.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-ai": "^1.0.0", + "typebox": "1.3.27" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-1.0.0.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "0.124.0", + "@aws-sdk/client-bedrock-runtime": "3.1127.0", + "@earendil-works/pi-telemetry": "^1.0.0", + "@google/genai": "2.21.0", + "@smithy/node-http-handler": "4.12.1", + "http-proxy-agent": "9.1.0", + "https-proxy-agent": "9.1.0", + "openai": "7.19.0", + "partial-json": "0.1.7", + "typebox": "1.3.27" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai/node_modules/agent-base": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", + "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai/node_modules/https-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai/node_modules/openai": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.19.0.tgz", + "integrity": "sha512-MX2s3u2L5racTO0CC/SWpCOasJQBCJrqLKXK+l82cAhdeF8mPMBEe/gxMm0ZFa2xpKpOFLRjxv5afYEZbBXmbQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-codemode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-codemode/-/pi-codemode-1.0.0.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "quickjs-wasi": "3.6.2" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-mcp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-mcp/-/pi-mcp-1.0.0.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "7.0.6" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-telemetry": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-1.0.0.tgz", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-1.0.0.tgz", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "1.6.0", + "marked": "18.0.11" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@google/genai": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.21.0.tgz", + "integrity": "sha512-+PDtco2/Z0ONdzCGekCoCT+O1VJS9xJQNN4XzQpXG/t3El/SWWMkCWlFRO1KmivOHPa4Q0VjUYu1HBKCZ/v33Q==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/core": { + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.33.3.tgz", + "integrity": "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/node-http-handler": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/signature-v4": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.3.tgz", + "integrity": "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/types": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.18.0.tgz", + "integrity": "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/chalk": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.0.tgz", + "integrity": "sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/grok-mermaid": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/grok-mermaid/-/grok-mermaid-0.2.3.tgz", + "integrity": "sha512-/4KopAbsjvuRP9MdPtlDjOHUmUVEohOX73JNcsWpzAtFxh+bq5+Dhb6gzvRieLDwIPQIR3/vy8V1NNTuz4Zsmg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/http-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", + "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ignore": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.8.tgz", + "integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/lru-cache": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", + "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/marked": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.11.tgz", + "integrity": "sha512-HnslJfsZkRPBDJRHvVtAaWlZHEpSu7u8LgQuJCELjRKuWR+hpq4A7sLq3p8HaI9ypVoXDXxV34CsQJEe1+J5Aw==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry/node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", + "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/quickjs-wasi": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-3.6.2.tgz", + "integrity": "sha512-FCqGtGOrMgzUiIrMNMA2YnsOxCNwo31dzqXvclXUC6xeT35NJLKXQJsvbeCTjvoFAwZgEAPg8U6+KAPDGXn8Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/standardwebhooks": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.1.1.tgz", + "integrity": "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": { + "version": "1.3.27", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.27.tgz", + "integrity": "sha512-zu+jc1pcy4UiNThxikUr36f0Rybk9PEeCg/NE6adeWr/SKsdNO4EzZHYRDlv2YCVAfj3Odq3dESSo/jNyoBXzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.2.tgz", + "integrity": "sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/@earendil-works/pi-telemetry": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-1.0.0.tgz", + "integrity": "sha512-WjNBj5TYIiPZFQEz2WlULcDwPLaKwIlmsKjVeYM+LJSbnSp38kWsHUJysIDGnu23IcLbKoPtywsvYfUJCeZePA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@google/genai": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.21.0.tgz", + "integrity": "sha512-+PDtco2/Z0ONdzCGekCoCT+O1VJS9xJQNN4XzQpXG/t3El/SWWMkCWlFRO1KmivOHPa4Q0VjUYu1HBKCZ/v33Q==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@smithy/core": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.35.1.tgz", + "integrity": "sha512-i4YPS4B6ts7bjn7UwLnGjiZdprOvHvgGobFZsYK3GIY3E5hIqtj0rReU69BcTpGp+fvtraSNXeG1l+jtJvF55w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.4.tgz", + "integrity": "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.19.0.tgz", + "integrity": "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz", + "integrity": "sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/agent-base": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", + "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/gaxios": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", + "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.1.tgz", + "integrity": "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/http-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/https-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/openai": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.19.0.tgz", + "integrity": "sha512-MX2s3u2L5racTO0CC/SWpCOasJQBCJrqLKXK+l82cAhdeF8mPMBEe/gxMm0ZFa2xpKpOFLRjxv5afYEZbBXmbQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", + "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true + } + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/standardwebhooks": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.1.1.tgz", + "integrity": "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typebox": { + "version": "1.3.27", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.27.tgz", + "integrity": "sha512-zu+jc1pcy4UiNThxikUr36f0Rybk9PEeCg/NE6adeWr/SKsdNO4EzZHYRDlv2YCVAfj3Odq3dESSo/jNyoBXzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fac46b5 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "@shopware-ag/github-agent-tools", + "version": "4.4.0", + "description": "GitHub CLI MCP servers for pull requests, issues, CI, and repositories, with enforcement that keeps the agent on the MCP tools.", + "type": "module", + "license": "MIT", + "author": "Shopware Labs", + "homepage": "https://github.com/shopwareLabs/github-agent-tools/tree/main/plugins/github-mcp", + "repository": { "type": "git", "url": "git+https://github.com/shopwareLabs/github-agent-tools.git" }, + "bugs": "https://github.com/shopwareLabs/github-agent-tools/issues", + "keywords": ["pi-package", "github", "gh", "mcp"], + "pi": { "extensions": ["./plugins/github-mcp/pi/index.ts"] }, + "files": [ + "plugins/github-mcp/pi/", + "plugins/github-mcp/hooks/", + "plugins/github-mcp/mcp-server-gh/", + "plugins/github-mcp/shared/", + "plugins/github-mcp/README.md", + "plugins/github-mcp/REFERENCE.md", + "plugins/github-mcp/CHANGELOG.md", + "!**/*.log" + ], + "engines": { "node": ">=22.19.0" }, + "publishConfig": { "access": "public" }, + "peerDependencies": { "@earendil-works/pi-coding-agent": "*" }, + "devDependencies": { + "@earendil-works/pi-ai": "1.0.0", + "@earendil-works/pi-coding-agent": "1.0.0", + "@types/node": "24.19.1", + "typescript": "7.0.2" + } +} diff --git a/plugin-tests/github-mcp/check_api_tools.bats b/plugin-tests/github-mcp/check_api_tools.bats index d39e4de..03d5be4 100644 --- a/plugin-tests/github-mcp/check_api_tools.bats +++ b/plugin-tests/github-mcp/check_api_tools.bats @@ -229,3 +229,43 @@ setup_write_blocking() { assert_failure 2 assert_output --partial "pr_create" } + +# ============================================================================ +# Host pi (GITHUB_MCP_HOST=pi): same tool names as Codex, .pi/ config +# ============================================================================ + +# Write a config into the .pi/ directory of a pi project passed as payload cwd. +setup_pi_config() { + local content="$1" + unset CLAUDE_PROJECT_DIR + export GITHUB_MCP_HOST=pi + export HOOK_CWD="${BATS_TEST_TMPDIR}/pi-project" + mkdir -p "${HOOK_CWD}/.pi" + printf '%s\n' "$content" > "${HOOK_CWD}/.pi/.mcp-gh-tooling.json" +} + +@test "pi read api: block_api_tool_read in .pi blocks the read tool" { + setup_pi_config '{"block_api_tool_read": true}' + run_api_hook "$CODEX_READ_TOOL" "repos/shopware/shopware/pulls/123/comments" + assert_failure 2 + assert_output --partial "pr_comments" +} + +@test "pi read api: block_api_tool_write alone leaves the read tool allowed" { + setup_pi_config '{"block_api_tool_write": true}' + run_api_hook "$CODEX_READ_TOOL" "repos/shopware/shopware/pulls/123/comments" + assert_success +} + +@test "pi write api: block_api_tool_write in .pi blocks the write tool" { + setup_pi_config '{"block_api_tool_write": true}' + run_api_hook "$CODEX_WRITE_TOOL" "repos/shopware/shopware/pulls" "POST" + assert_failure 2 + assert_output --partial "pr_create" +} + +@test "pi write api: block_api_tool_read alone leaves the write tool allowed" { + setup_pi_config '{"block_api_tool_read": true}' + run_api_hook "$CODEX_WRITE_TOOL" "repos/shopware/shopware/pulls/123/comments" "GET" + assert_success +} diff --git a/plugin-tests/github-mcp/fixtures/pi/driver.ts b/plugin-tests/github-mcp/fixtures/pi/driver.ts new file mode 100644 index 0000000..5599017 --- /dev/null +++ b/plugin-tests/github-mcp/fixtures/pi/driver.ts @@ -0,0 +1,102 @@ +// Scripted model for pi_e2e.bats. Registers the faux provider (model faux/faux-1), answers each +// model request with the next turn of the $SCENARIO script, and records what the package +// extension does: every request's messages go to $TRACE, and every tool call's view of the +// gh-tooling tools goes to $PROBE. Loaded with -e, so it sees each call before the package's gate. +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import { + fauxAssistantMessage, + fauxProvider, + fauxText, + fauxToolCall, + type FauxResponseFactory, + type ToolCall, +} from "@earendil-works/pi-ai"; +import { appendFileSync } from "node:fs"; + +function requiredEnv(name: string): string { + const value = process.env[name]; + if (value === undefined || value === "") throw new Error(`driver.ts: ${name} must be set`); + return value; +} + +const SCENARIO = requiredEnv("SCENARIO"); +const TRACE = requiredEnv("TRACE"); +const PROBE = requiredEnv("PROBE"); + +const REPO = "shopwareLabs/github-agent-tools"; +const FILES_ENDPOINT = `repos/${REPO}/pulls/8/files`; + +function prView(number: number): ToolCall { + return fauxToolCall( + "mcp__gh_tooling__pr_view", + { repo: REPO, number, fields: "number,title" }, + { id: `pr-view-${number}` }, + ); +} + +const ghPrViewInBash = fauxToolCall("bash", { command: "gh pr view 8" }, { id: "bash-gh-pr-view" }); + +/** One assistant turn per model request; the last turn ends the run with plain text. */ +const SCENARIOS: Record = { + deferred: [ + fauxToolCall("tool_search", { query: "pr_view" }, { id: "search-pr-view" }), + prView(8), + ghPrViewInBash, + fauxToolCall("tool_search", { query: "api_read" }, { id: "search-api-read" }), + fauxToolCall("mcp__gh_tooling__api_read", { endpoint: FILES_ENDPOINT }, { id: "api-read-files" }), + [prView(11), prView(12), prView(13)], + "done", + ], + codemode: [ + fauxToolCall( + "codemode", + { + code: ` +const results = await Promise.all([11, 12, 13].map((number) => + tools.mcp__gh_tooling__pr_view({ repo: "${REPO}", number, fields: "number,title" }))); +let caught = null; +try { + await tools.mcp__gh_tooling__api_read({ endpoint: "${FILES_ENDPOINT}" }); +} catch (error) { + caught = error.message; +} +return JSON.stringify({ results, caught }); +`, + }, + { id: "codemode-script" }, + ), + "done", + ], + "environment-leak": [ghPrViewInBash, "done"], + "mcp-override": [prView(8), ghPrViewInBash, "done"], +}; + +const turns = SCENARIOS[SCENARIO]; +if (turns === undefined) throw new Error(`driver.ts: unknown SCENARIO ${SCENARIO}`); + +const responses: FauxResponseFactory[] = turns.map((turn) => (context) => { + appendFileSync(TRACE, `${JSON.stringify(context.messages)}\n`); + return fauxAssistantMessage(typeof turn === "string" ? fauxText(turn) : turn); +}); + +const isGhTooling = (name: string): boolean => name.startsWith("mcp__gh_tooling"); + +export default function driver(pi: ExtensionAPI): void { + const faux = fauxProvider(); + faux.setResponses(responses); + pi.registerProvider(faux.provider); + + pi.on("tool_call", (event) => { + const ghTools = pi.getAllTools().filter((tool) => isGhTooling(tool.name)); + appendFileSync( + PROBE, + `${JSON.stringify({ + tool: event.toolName, + all: ghTools.length, + exposures: [...new Set(ghTools.map((tool) => tool.exposure))], + active: pi.getActiveTools().filter(isGhTooling).length, + })}\n`, + ); + return undefined; + }); +} diff --git a/plugin-tests/github-mcp/fixtures/pi/gh-stub.sh b/plugin-tests/github-mcp/fixtures/pi/gh-stub.sh new file mode 100755 index 0000000..3fd07b5 --- /dev/null +++ b/plugin-tests/github-mcp/fixtures/pi/gh-stub.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Stand-in for the gh CLI in pi_e2e.bats, linked as `gh` first on PATH so no +# call reaches GitHub. Logs every invocation to $GH_LOG. Answers only +# `pr view --repo --json `, with {"number":,"title":"stub"} +# restricted to the requested fields; anything else fails. + +set -euo pipefail + +printf '%s\n' "$*" >> "${GH_LOG:?GH_LOG must point at the invocation log}" + +if [[ $# -eq 7 && "$1" == "pr" && "$2" == "view" && "$4" == "--repo" && "$6" == "--json" ]]; then + jq -cn --argjson number "$3" --arg fields "$7" \ + '{number: $number, title: "stub"} | with_entries(select(.key as $key | $fields | split(",") | index($key)))' + exit 0 +fi + +printf 'gh-stub: unsupported invocation: %s\n' "$*" >&2 +exit 1 diff --git a/plugin-tests/github-mcp/gh_tools.bats b/plugin-tests/github-mcp/gh_tools.bats index 2644cd0..1c0b3ea 100644 --- a/plugin-tests/github-mcp/gh_tools.bats +++ b/plugin-tests/github-mcp/gh_tools.bats @@ -140,6 +140,7 @@ bats_test_function --description "blocks gh run view in ; chain → suggests run } @test "Claude hook suggestions use the plugin-qualified tool namespace" { + unset GITHUB_MCP_HOST run_hook "check-gh-tools.sh" "gh pr view 14642" assert_failure 2 assert_output --partial "mcp__plugin_github-mcp_gh-tooling__pr_view" @@ -152,6 +153,91 @@ bats_test_function --description "blocks gh run view in ; chain → suggests run assert_output --partial "mcp__gh_tooling__pr_view" } +# ============================================================================ +# Host pi (GITHUB_MCP_HOST=pi) and the .pi/ config location +# ============================================================================ + +# Set the hook environment. Empty arguments leave the variable unset. +# Args: $1=GITHUB_MCP_HOST, $2=payload cwd, $3=CLAUDE_PROJECT_DIR +set_hook_env() { + if [[ -n "$1" ]]; then export GITHUB_MCP_HOST="$1"; else unset GITHUB_MCP_HOST; fi + if [[ -n "$2" ]]; then export HOOK_CWD="$2"; mkdir -p "$2"; else unset HOOK_CWD; fi + if [[ -n "$3" ]]; then export CLAUDE_PROJECT_DIR="$3"; mkdir -p "$3"; else unset CLAUDE_PROJECT_DIR; fi +} + +# Args: $1=project dir, $2=config dir relative to it ("" for the root), $3=JSON +write_project_config() { + mkdir -p "${1}/${2}" + printf '%s\n' "$3" > "${1}/${2}/.mcp-gh-tooling.json" +} + +# bats test_tags=host,pi +@test "pi suggestions use the sanitized tool namespace" { + set_hook_env "pi" "${BATS_TEST_TMPDIR}/pi-project" "" + + run_hook "check-gh-tools.sh" "gh pr view 14642" + + assert_failure 2 + assert_output --partial "mcp__gh_tooling__pr_view" +} + +@test "pi prefers a .pi config over .claude and .codex configs" { + local project="${BATS_TEST_TMPDIR}/pi-project" + write_project_config "$project" ".pi" '{"enforce_mcp_tools": false}' + write_project_config "$project" ".claude" '{"enforce_mcp_tools": true}' + write_project_config "$project" ".codex" '{"enforce_mcp_tools": true}' + set_hook_env "pi" "$project" "" + + run_hook "check-gh-tools.sh" "gh pr view 14642" + + assert_success +} + +# A GITHUB_MCP_HOST exported in the shell does not override Claude Code: the +# payload cwd disables enforcement, CLAUDE_PROJECT_DIR opts into API blocking. +# Args: $1=GITHUB_MCP_HOST +claude_project_dir_wins_over_host() { + local claude_project="${BATS_TEST_TMPDIR}/claude-project" + local payload_project="${BATS_TEST_TMPDIR}/payload-project" + write_project_config "$claude_project" ".claude" '{"enforce_mcp_tools": true, "block_api_commands": true}' + write_project_config "$payload_project" ".${1}" '{"enforce_mcp_tools": false}' + set_hook_env "$1" "$payload_project" "$claude_project" + + run_hook "check-gh-tools.sh" "gh api repos/shopware/shopware/pulls/14642/files" + + assert_failure 2 + assert_output --partial "mcp__plugin_github-mcp_gh-tooling__pr_files" +} + +bats_test_function \ + --description "CLAUDE_PROJECT_DIR resolves as Claude Code even with GITHUB_MCP_HOST=pi" \ + -- claude_project_dir_wins_over_host "pi" +bats_test_function \ + --description "CLAUDE_PROJECT_DIR resolves as Claude Code even with GITHUB_MCP_HOST=codex" \ + -- claude_project_dir_wins_over_host "codex" + +@test "Claude prefers a .pi config over the project-root config" { + local project="${BATS_TEST_TMPDIR}/claude-project" + write_project_config "$project" ".pi" '{"enforce_mcp_tools": false}' + write_project_config "$project" "" '{"enforce_mcp_tools": true}' + set_hook_env "" "" "$project" + + run_hook "check-gh-tools.sh" "gh pr view 14642" + + assert_success +} + +@test "Codex prefers a .pi config over the project-root config" { + local project="${BATS_TEST_TMPDIR}/codex-project" + write_project_config "$project" ".pi" '{"enforce_mcp_tools": false}' + write_project_config "$project" "" '{"enforce_mcp_tools": true}' + set_hook_env "" "$project" "" + + run_hook "check-gh-tools.sh" "gh pr view 14642" + + assert_success +} + # ============================================================================ # block_api_commands: true — blocks gh api endpoints with dedicated MCP tools # ============================================================================ diff --git a/plugin-tests/github-mcp/package_contents.bats b/plugin-tests/github-mcp/package_contents.bats new file mode 100644 index 0000000..578bf35 --- /dev/null +++ b/plugin-tests/github-mcp/package_contents.bats @@ -0,0 +1,79 @@ +#!/usr/bin/env bats +# bats file_tags=github-mcp,pi,package +# The files `npm pack` puts into the pi package tarball: everything the +# extension, the gates, and the MCP servers need at runtime, nothing from the +# tests, CI, or the other hosts' manifests, and executable server entry points. +bats_require_minimum_version 1.11.0 + +load 'test_helper/common_setup' + +setup_file() { + (cd "${REPO_ROOT}" && npm pack --dry-run --json) >"${BATS_FILE_TMPDIR}/pack.json" +} + +# Prints every tarball path, one per line. +packed_paths() { + jq -r '.[0].files[].path' "${BATS_FILE_TMPDIR}/pack.json" +} + +# Prints, relative to the repository root, the plugin files matching a find +# expression below a plugin subdirectory. +# Args: $1=subdirectory of the plugin, remaining=find expression +plugin_files() { + local subdirectory="$1" + shift + (cd "${REPO_ROOT}" && find "plugins/${PLUGIN_NAME}/${subdirectory}" "$@" -type f) +} + +@test "the tarball contains package.json and every runtime file of the plugin" { + local prefix="plugins/${PLUGIN_NAME}" + local -a expected=( + "package.json" + "${prefix}/pi/index.ts" + "${prefix}/pi/gate.ts" + "${prefix}/mcp-server-gh/server-read.sh" + "${prefix}/mcp-server-gh/server-write.sh" + "${prefix}/mcp-server-gh/tools-read.json" + "${prefix}/mcp-server-gh/tools-write.json" + "${prefix}/mcp-server-gh/tools-empty.json" + "${prefix}/mcp-server-gh/config-read.json" + "${prefix}/mcp-server-gh/config-write.json" + "${prefix}/shared/mcpserver_core.sh" + "${prefix}/shared/config-dirs.sh" + ) + local -a globbed + mapfile -t globbed < <( + plugin_files mcp-server-gh/lib -maxdepth 1 -name '*.sh' + plugin_files hooks/scripts -name '*.sh' + plugin_files hooks/prompts -maxdepth 1 -name '*.md' + ) + assert [ "${#globbed[@]}" -gt 0 ] + + run comm -23 <(printf '%s\n' "${expected[@]}" "${globbed[@]}" | sort -u) <(packed_paths | sort -u) + assert_success + assert_output "" +} + +@test "the tarball contains no logs, tests, CI files, plugin-setup, or host manifests" { + run jq -r ' + .[0].files[].path + | select(test("\\.log$") or test("^(plugin-tests|\\.github|plugins/plugin-setup)/") + or test("(^|/)\\.(claude|codex)-plugin/")) + ' "${BATS_FILE_TMPDIR}/pack.json" + assert_success + assert_output "" +} + +@test "the server entry points and the session-start hook are packed with mode 0755" { + # npm reports modes in decimal: 493 is 0755. + run jq -c --arg prefix "plugins/${PLUGIN_NAME}" ' + [.[0].files[] + | select(.path == "\($prefix)/mcp-server-gh/server-read.sh" + or .path == "\($prefix)/mcp-server-gh/server-write.sh" + or .path == "\($prefix)/hooks/scripts/session-start.sh") + | [.path, .mode]] + | sort + ' "${BATS_FILE_TMPDIR}/pack.json" + assert_success + assert_output "[[\"plugins/${PLUGIN_NAME}/hooks/scripts/session-start.sh\",493],[\"plugins/${PLUGIN_NAME}/mcp-server-gh/server-read.sh\",493],[\"plugins/${PLUGIN_NAME}/mcp-server-gh/server-write.sh\",493]]" +} diff --git a/plugin-tests/github-mcp/package_manifest.bats b/plugin-tests/github-mcp/package_manifest.bats new file mode 100644 index 0000000..42b542f --- /dev/null +++ b/plugin-tests/github-mcp/package_manifest.bats @@ -0,0 +1,79 @@ +#!/usr/bin/env bats +# bats file_tags=github-mcp,pi,package +# The root package.json that makes the repository a pi package: its version +# tracks the plugin manifests, it installs nothing at runtime, and the pi +# packages it builds against stay host-provided. +bats_require_minimum_version 1.11.0 + +load 'test_helper/common_setup' + +setup() { + PACKAGE_JSON="${REPO_ROOT}/package.json" +} + +@test "package.json version equals both plugin manifest versions" { + local package_version claude_version codex_version + package_version=$(jq -er '.version' "${PACKAGE_JSON}") + claude_version=$(jq -er '.version' "${PLUGIN_DIR}/.claude-plugin/plugin.json") + codex_version=$(jq -er '.version' "${PLUGIN_DIR}/.codex-plugin/plugin.json") + + assert_equal "${claude_version}" "${package_version}" + assert_equal "${codex_version}" "${package_version}" +} + +@test "every pi extension entry points at an existing file" { + local -a extensions + mapfile -t extensions < <(jq -r '.pi.extensions[]' "${PACKAGE_JSON}") + + assert [ "${#extensions[@]}" -gt 0 ] + local extension + for extension in "${extensions[@]}"; do + assert [ -f "${REPO_ROOT}/${extension}" ] + done +} + +@test "package.json declares no runtime dependencies" { + run jq 'has("dependencies")' "${PACKAGE_JSON}" + assert_output "false" +} + +@test "package.json declares no scripts" { + run jq 'has("scripts")' "${PACKAGE_JSON}" + assert_output "false" +} + +@test "every @earendil-works package is a \"*\" peer dependency or only a dev dependency" { + run jq -c ' + def earendil: to_entries[] | select(.key | startswith("@earendil-works/")); + [ (.dependencies // {}, .optionalDependencies // {} | earendil | .key), + (.peerDependencies // {} | earendil | select(.value != "*") | .key) ] + ' "${PACKAGE_JSON}" + assert_success + assert_output "[]" +} + +@test "pi-coding-agent and pi-ai are pinned to the same dev version" { + local agent_version ai_version + agent_version=$(jq -er '.devDependencies["@earendil-works/pi-coding-agent"]' "${PACKAGE_JSON}") + ai_version=$(jq -er '.devDependencies["@earendil-works/pi-ai"]' "${PACKAGE_JSON}") + + assert_equal "${ai_version}" "${agent_version}" +} + +@test "the pi extension imports @earendil-works packages only as types" { + local -a ts_files + mapfile -t ts_files < <(find "${PLUGIN_DIR}/pi" -type f -name '*.ts') + assert [ "${#ts_files[@]}" -gt 0 ] + + # Matches whole statements, so multi-line imports are covered; dynamic + # import() is never type-only. + run perl -0777 -ne ' + while (/^\s*((?:import|export)\b[^;]*?["\x27]\@earendil-works\/[^;]*)/mg) { + my $statement = $1; + print "$ARGV: $statement\n" unless $statement =~ /^(?:import|export)\s+type\s/; + } + print "$ARGV: $1\n" while /(\bimport\s*\(\s*["\x27]\@earendil-works\/[^)]*\))/g; + ' "${ts_files[@]}" + assert_success + assert_output "" +} diff --git a/plugin-tests/github-mcp/pi/gate.test.ts b/plugin-tests/github-mcp/pi/gate.test.ts new file mode 100644 index 0000000..24b92f3 --- /dev/null +++ b/plugin-tests/github-mcp/pi/gate.test.ts @@ -0,0 +1,105 @@ +import test, { type TestContext } from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { setTimeout as delay } from "node:timers/promises"; +import { runGate, runScript } from "../../../plugins/github-mcp/pi/gate.ts"; + +function makeTempDir(t: TestContext): string { + const dir = mkdtempSync(join(tmpdir(), "github-mcp-pi-gate-")); + t.after(() => rmSync(dir, { recursive: true, force: true })); + return dir; +} + +function writeScript(dir: string, body: string): string { + const script = join(dir, "script.sh"); + writeFileSync(script, body); + return script; +} + +function processExists(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ESRCH") return false; + throw error; + } +} + +test("runScript returns the script's stdout and exit code 0 unchanged", async (t) => { + const script = writeScript(makeTempDir(t), "printf ' {\"ok\": true}\\n'\nexit 0\n"); + + const result = await runScript(script, {}, 5000); + + assert.deepEqual(result, { code: 0, stdout: ' {"ok": true}\n', stderr: "" }); +}); + +test("a gate that exits 2 blocks with its trimmed stderr as the reason", async (t) => { + const script = writeScript(makeTempDir(t), "printf '\\n Use mcp__gh_tooling__pr_view instead. \\n' >&2\nexit 2\n"); + + const result = await runGate(script, { tool_input: { command: "gh pr view 8" } }, 5000); + + assert.deepEqual(result, { block: true, reason: "Use mcp__gh_tooling__pr_view instead." }); +}); + +test("the script runs with GITHUB_MCP_HOST=pi and without the caller's CLAUDE_PROJECT_DIR", async (t) => { + const previous = process.env.CLAUDE_PROJECT_DIR; + process.env.CLAUDE_PROJECT_DIR = "/claude/project"; + t.after(() => { + if (previous === undefined) delete process.env.CLAUDE_PROJECT_DIR; + else process.env.CLAUDE_PROJECT_DIR = previous; + }); + const script = writeScript( + makeTempDir(t), + 'printf "host=%s claude=%s" "${GITHUB_MCP_HOST-unset}" "${CLAUDE_PROJECT_DIR-unset}"\n', + ); + + const { stdout } = await runScript(script, {}, 5000); + + assert.equal(stdout, "host=pi claude=unset"); +}); + +test("a nonexistent gate script does not block", async (t) => { + const script = join(makeTempDir(t), "missing.sh"); + + const result = await runGate(script, {}, 5000); + + assert.deepEqual(result, { block: false }); +}); + +test("a gate that closes its stdin before reading a 1 MB input does not block and raises no unhandled error", async (t) => { + const unhandled: unknown[] = []; + const record = (error: unknown): void => { + unhandled.push(error); + }; + process.on("uncaughtException", record); + process.on("unhandledRejection", record); + t.after(() => { + process.off("uncaughtException", record); + process.off("unhandledRejection", record); + }); + const script = writeScript(makeTempDir(t), "exec 0<&-\nexit 0\n"); + + const result = await runGate(script, { payload: "x".repeat(1024 * 1024) }, 5000); + // A late EPIPE would surface after settling; give it a few event-loop turns to arrive. + await delay(100); + + assert.deepEqual(result, { block: false }); + assert.deepEqual(unhandled, []); +}); + +test("a gate that outlives its timeout does not block and its background children are killed", { timeout: 10_000 }, async (t) => { + const dir = makeTempDir(t); + const pidFile = join(dir, "sleep.pid"); + const script = writeScript(dir, `sleep 37 &\nprintf '%s' "$!" > '${pidFile}'\nwait\n`); + + const result = await runGate(script, {}, 500); + + assert.deepEqual(result, { block: false }); + const sleepPid = Number(readFileSync(pidFile, "utf8")); + // SIGKILL delivery and reaping of the orphaned sleep are asynchronous; poll briefly. + for (let attempt = 0; attempt < 40 && processExists(sleepPid); attempt++) await delay(50); + assert.equal(processExists(sleepPid), false); +}); diff --git a/plugin-tests/github-mcp/pi_e2e.bats b/plugin-tests/github-mcp/pi_e2e.bats new file mode 100644 index 0000000..920c4ae --- /dev/null +++ b/plugin-tests/github-mcp/pi_e2e.bats @@ -0,0 +1,348 @@ +#!/usr/bin/env bats +# bats file_tags=github-mcp,pi,e2e +# Drives the real pi binary against the installed package with a scripted model +# (fixtures/pi/driver.ts) and a stubbed gh (fixtures/pi/gh-stub.sh), offline. +# setup_file installs each package variant, runs pi once per scenario, and keeps +# the outputs; every test asserts over those outputs. +bats_require_minimum_version 1.11.0 + +load 'test_helper/common_setup' + +PI_BIN="${REPO_ROOT}/node_modules/.bin/pi" +FIXTURES_DIR="${BATS_TEST_DIRNAME}/fixtures/pi" +REPO_SLUG="shopwareLabs/github-agent-tools" +FILES_ENDPOINT="repos/${REPO_SLUG}/pulls/8/files" +GH_TOOLING_DESCRIPTION="GitHub via the gh CLI: pull requests, issues, CI runs and logs, commits, search, labels, projects, repository files." + +# Resolves the GNU `timeout` binary this suite bounds each pi run with. +# Stock macOS ships neither name under PATH's `timeout`; Homebrew's coreutils +# provides it as `gtimeout`. Fails the suite outright rather than skipping. +resolve_timeout_bin() { + if command -v timeout >/dev/null 2>&1; then + command -v timeout + elif command -v gtimeout >/dev/null 2>&1; then + command -v gtimeout + else + printf 'pi_e2e.bats: no "timeout" or "gtimeout" on PATH. Install GNU coreutils (e.g. "brew install coreutils").\n' >&2 + return 1 + fi +} + +# Reproduces offline what `pi install git:…` runs: a clone, then npm install in it. +# The cloned repository is a one-commit snapshot of the working tree (tracked and +# non-ignored untracked files that exist on disk, modes and symlinks kept), so the +# layout ships uncommitted changes exactly as the next commit would. +install_git_layout() { + local source="${BATS_FILE_TMPDIR}/git-source" layout="${BATS_FILE_TMPDIR}/git-layout" + local listed="${BATS_FILE_TMPDIR}/git-source-listed" present="${BATS_FILE_TMPDIR}/git-source-present" + local path + git -C "${REPO_ROOT}" ls-files -z --cached --others --exclude-standard >"${listed}" + while IFS= read -r -d '' path; do + if [[ -e "${REPO_ROOT}/${path}" || -L "${REPO_ROOT}/${path}" ]]; then + printf '%s\0' "${path}" + fi + done <"${listed}" >"${present}" + mkdir -p "${source}" + tar -C "${REPO_ROOT}" --null -T "${present}" -cf "${BATS_FILE_TMPDIR}/git-source.tar" + tar -C "${source}" -xf "${BATS_FILE_TMPDIR}/git-source.tar" + git -C "${source}" init --quiet + git -C "${source}" add --all --force + git -C "${source}" -c user.name=pi-e2e -c user.email=pi-e2e@localhost -c commit.gpgsign=false \ + commit --quiet --no-verify -m "working tree snapshot" + git clone --quiet "${source}" "${layout}" + (cd "${layout}" && npm install --omit=dev --legacy-peer-deps --offline --no-audit --no-fund) + git -C "${layout}" status --porcelain >"${BATS_FILE_TMPDIR}/git-layout-status" +} + +# Reproduces offline what `pi install npm:…` runs: npm install of the tarball under a prefix. +install_npm_layout() { + local pack_dir="${BATS_FILE_TMPDIR}/npm-pack" prefix="${BATS_FILE_TMPDIR}/npm-prefix" tarball + mkdir -p "${pack_dir}" "${prefix}" + tarball=$(cd "${REPO_ROOT}" && npm pack --json --pack-destination "${pack_dir}" | jq -er '.[0].filename') + npm install "${pack_dir}/${tarball}" --prefix "${prefix}" --legacy-peer-deps --offline --no-audit --no-fund +} + +# Runs pi once for a scenario against a package variant; outputs land in +# ${BATS_FILE_TMPDIR}/-/. +# Args: $1=variant (git|npm), $2=scenario (a driver.ts SCENARIO) +run_pi_scenario() { + local variant="$1" scenario="$2" + local run_dir="${BATS_FILE_TMPDIR}/${variant}-${scenario}" package_dir + case "${variant}" in + git) package_dir="${BATS_FILE_TMPDIR}/git-layout" ;; + npm) package_dir="${BATS_FILE_TMPDIR}/npm-prefix/node_modules/@shopware-ag/github-agent-tools" ;; + esac + + mkdir -p "${run_dir}/project/.pi" "${run_dir}/bin" "${run_dir}/agent" + git -C "${run_dir}/project" init --quiet + git -C "${run_dir}/project" remote add origin "https://github.com/${REPO_SLUG}.git" + printf '%s\n' '{"block_api_tool_read": true}' >"${run_dir}/project/.pi/.mcp-gh-tooling.json" + ln -s "${FIXTURES_DIR}/gh-stub.sh" "${run_dir}/bin/gh" + : >"${run_dir}/trace.jsonl" + : >"${run_dir}/probe.jsonl" + : >"${run_dir}/gh.log" + + PI_CODING_AGENT_DIR="${run_dir}/agent" "${PI_BIN}" install "${package_dir}" "${run_dir}/settings.json" + mv "${run_dir}/settings.json" "${run_dir}/agent/settings.json" + ;; + environment-leak) + mkdir -p "${run_dir}/leak/.claude" + printf '%s\n' '{"enforce_mcp_tools": false}' >"${run_dir}/leak/.claude/.mcp-gh-tooling.json" + scenario_env=("CLAUDE_PROJECT_DIR=${run_dir}/leak") + ;; + mcp-override) + printf '%s\n' '{"mcpServers": {"gh-tooling": {"command": "false", "enabled": false}}}' \ + >"${run_dir}/agent/mcp.json" + ;; + esac + + local status=0 + ( + cd "${run_dir}/project" + env -u MCP_GH_TOOLING_CONFIG -u PROJECT_ROOT "${scenario_env[@]}" \ + PATH="${run_dir}/bin:${PATH}" \ + PI_CODING_AGENT_DIR="${run_dir}/agent" \ + SCENARIO="${scenario}" \ + TRACE="${run_dir}/trace.jsonl" \ + PROBE="${run_dir}/probe.jsonl" \ + GH_LOG="${run_dir}/gh.log" \ + "${TIMEOUT_BIN}" 60 "${PI_BIN}" --offline --no-session -e "${FIXTURES_DIR}/driver.ts" \ + --model faux/faux-1 --mode json -p go \ + "${run_dir}/stdout.jsonl" 2>"${run_dir}/stderr.log" + ) || status=$? + printf '%s\n' "${status}" >"${run_dir}/exit-code" +} + +setup_file() { + if [[ ! -x "${PI_BIN}" ]]; then + printf 'pi_e2e.bats: %s not found. Run "npm ci" in %s first.\n' "${PI_BIN}" "${REPO_ROOT}" >&2 + return 1 + fi + TIMEOUT_BIN=$(resolve_timeout_bin) || return 1 + # The environment-leak scenario sets it deliberately; no other run may inherit it. + unset CLAUDE_PROJECT_DIR + + install_git_layout + install_npm_layout + run_pi_scenario git deferred + run_pi_scenario npm deferred + run_pi_scenario git codemode + run_pi_scenario git environment-leak + run_pi_scenario git mcp-override +} + +# Prints {"isError": …, "text": …} for one finished tool call, text being the +# concatenated text parts of its result. +# Args: $1=run (-), $2=toolCallId +tool_result() { + jq -c --arg id "$2" ' + select(.type == "tool_execution_end" and .toolCallId == $id) + | {isError, text: ([.result.content[] | select(.type == "text") | .text] | join(""))} + ' "${BATS_FILE_TMPDIR}/$1/stdout.jsonl" +} + +# Prints {"isError": …, "json": …} for a tool call whose result text is JSON. +# Args: $1=run, $2=toolCallId +tool_result_json() { + tool_result "$1" "$2" | jq -c '{isError, json: (.text | fromjson)}' +} + +# Fails unless the tool call ended as an error whose text contains the fragment. +# Args: $1=run, $2=toolCallId, $3=fragment +assert_tool_error_contains() { + local result + result=$(tool_result "$1" "$2") + run jq -r '.isError' <<<"${result}" + assert_output "true" + run jq -r '.text' <<<"${result}" + assert_output --partial "$3" +} + +# Fails if the gh stub logged an invocation matching the regex. +# Args: $1=run, $2=regex over one logged argv line +refute_gh_invoked() { + run grep -E "$2" "${BATS_FILE_TMPDIR}/$1/gh.log" + assert_failure 1 +} + +# ============================================================================ +# Scenario `deferred` — asserted for both package variants +# ============================================================================ + +assert_tools_deferred_until_searched() { + local probe="${BATS_FILE_TMPDIR}/$1/probe.jsonl" expected_count + expected_count=$(jq '.tools | length' "${GH_SERVER_DIR}/tools-read.json") + + run jq -nc 'first(inputs | select(.tool == "tool_search")) | .active' "${probe}" + assert_output "0" + run jq -nc 'first(inputs | select(.tool == "mcp__gh_tooling__pr_view")) | {all, exposures}' "${probe}" + assert_output "{\"all\":${expected_count},\"exposures\":[\"deferred\"]}" +} + +assert_tool_search_loads_pr_view() { + local result + result=$(tool_result "$1" search-pr-view) + run jq -r '.isError' <<<"${result}" + assert_output "false" + run jq -r '.text' <<<"${result}" + assert_output --partial "mcp__gh_tooling__pr_view" +} + +assert_pr_view_returns_stub() { + run tool_result_json "$1" pr-view-8 + assert_output '{"isError":false,"json":{"number":8,"title":"stub"}}' +} + +assert_bash_gh_blocked() { + assert_tool_error_contains "$1" bash-gh-pr-view "mcp__gh_tooling__pr_view" + refute_gh_invoked "$1" '^pr view 8$' +} + +assert_api_read_blocked() { + assert_tool_error_contains "$1" api-read-files "pr_files" + refute_gh_invoked "$1" '^api( |$)' +} + +assert_parallel_pr_views_succeed() { + local number + for number in 11 12 13; do + run tool_result_json "$1" "pr-view-${number}" + assert_output "{\"isError\":false,\"json\":{\"number\":${number},\"title\":\"stub\"}}" + done +} + +assert_first_request_carries_sections() { + local host_pi_text + host_pi_text=$(<"${PLUGIN_DIR}/hooks/prompts/host-pi.md") + local first_system + first_system=$(jq -nc 'first(inputs) | .[0]' "${BATS_FILE_TMPDIR}/$1/trace.jsonl") + + run jq -r '.role' <<<"${first_system}" + assert_output "system" + run jq -r '.sections.github_mcp' <<<"${first_system}" + assert_output --partial "${host_pi_text}" + run jq -r '.sections.mcp_servers' <<<"${first_system}" + assert_output --partial "mcp__gh_tooling (tool_search): ${GH_TOOLING_DESCRIPTION}" +} + +assert_pi_exits_cleanly() { + run cat "${BATS_FILE_TMPDIR}/$1/exit-code" + assert_output "0" +} + +@test "git layout: npm install in the clone leaves the working tree clean" { + run cat "${BATS_FILE_TMPDIR}/git-layout-status" + assert_output "" +} + +@test "git layout: gh-tooling tools stay inactive until tool_search, and all are deferred" { + assert_tools_deferred_until_searched git-deferred +} + +@test "git layout: tool_search for pr_view loads mcp__gh_tooling__pr_view" { + assert_tool_search_loads_pr_view git-deferred +} + +@test "git layout: pr_view returns the stub's JSON" { + assert_pr_view_returns_stub git-deferred +} + +@test "git layout: bash gh pr view is blocked before gh runs" { + assert_bash_gh_blocked git-deferred +} + +@test "git layout: api_read on the PR files endpoint is blocked in favor of pr_files" { + assert_api_read_blocked git-deferred +} + +@test "git layout: three pr_view calls in one turn all succeed" { + assert_parallel_pr_views_succeed git-deferred +} + +@test "git layout: the first request carries the github_mcp and mcp_servers sections" { + assert_first_request_carries_sections git-deferred +} + +@test "git layout: pi exits 0" { + assert_pi_exits_cleanly git-deferred +} + +@test "npm layout: gh-tooling tools stay inactive until tool_search, and all are deferred" { + assert_tools_deferred_until_searched npm-deferred +} + +@test "npm layout: tool_search for pr_view loads mcp__gh_tooling__pr_view" { + assert_tool_search_loads_pr_view npm-deferred +} + +@test "npm layout: pr_view returns the stub's JSON" { + assert_pr_view_returns_stub npm-deferred +} + +@test "npm layout: bash gh pr view is blocked before gh runs" { + assert_bash_gh_blocked npm-deferred +} + +@test "npm layout: api_read on the PR files endpoint is blocked in favor of pr_files" { + assert_api_read_blocked npm-deferred +} + +@test "npm layout: three pr_view calls in one turn all succeed" { + assert_parallel_pr_views_succeed npm-deferred +} + +@test "npm layout: the first request carries the github_mcp and mcp_servers sections" { + assert_first_request_carries_sections npm-deferred +} + +@test "npm layout: pi exits 0" { + assert_pi_exits_cleanly npm-deferred +} + +# ============================================================================ +# Additional scenarios — git layout only +# ============================================================================ + +# Prints the value the codemode script returned: the JSON after its "Output:" line. +codemode_output() { + tool_result git-codemode codemode-script | jq -c '.text | split("\nOutput:\n")[1] | fromjson' +} + +@test "codemode: pr_view calls through Promise.all return the stub's JSON" { + run jq -c '[.results[] | .isError, (.content[0].text | fromjson)]' < <(codemode_output) + assert_output '[false,{"number":11,"title":"stub"},false,{"number":12,"title":"stub"},false,{"number":13,"title":"stub"}]' +} + +@test "codemode: a blocked api_read rejects with the gate's message" { + local payload gate_message + payload=$(jq -cn --arg endpoint "${FILES_ENDPOINT}" --arg cwd "${BATS_FILE_TMPDIR}/git-codemode/project" \ + '{tool_name: "mcp__gh_tooling__api_read", tool_input: {endpoint: $endpoint}, cwd: $cwd}') + # The environment pi's extension gives a gate: setup() exports CLAUDE_PROJECT_DIR, + # which would otherwise resolve the hook as Claude Code. + run --separate-stderr env -u CLAUDE_PROJECT_DIR GITHUB_MCP_HOST=pi bash "${SCRIPTS_DIR}/check-api-tools.sh" <<<"${payload}" + assert_failure 2 + gate_message="${stderr}" + + run jq -r '.caught' < <(codemode_output) + assert_output "${gate_message}" +} + +@test "environment leak: CLAUDE_PROJECT_DIR disabling enforcement does not unblock bash gh" { + assert_tool_error_contains git-environment-leak bash-gh-pr-view "mcp__gh_tooling__pr_view" + refute_gh_invoked git-environment-leak '^pr view 8$' +} + +@test "mcp.json override: pr_view of the disabled server is not found" { + run tool_result git-mcp-override pr-view-8 + assert_output '{"isError":true,"text":"Tool mcp__gh_tooling__pr_view not found"}' +} + +@test "mcp.json override: bash gh pr view is still blocked" { + assert_tool_error_contains git-mcp-override bash-gh-pr-view "mcp__gh_tooling__pr_view" + refute_gh_invoked git-mcp-override '^pr view 8$' +} diff --git a/plugin-tests/github-mcp/session_start.bats b/plugin-tests/github-mcp/session_start.bats index 2cd6f2f..acb028a 100644 --- a/plugin-tests/github-mcp/session_start.bats +++ b/plugin-tests/github-mcp/session_start.bats @@ -58,3 +58,92 @@ run_session_start() { assert_success assert_output "" } + +# ============================================================================ +# Host pi (GITHUB_MCP_HOST=pi): tool-naming note and .pi/ config +# ============================================================================ + +PI_HOST_NOTE="In pi, these tools are named \`mcp__gh_tooling__\` (read) and \`mcp__gh_tooling_write__\` (write), for example \`mcp__gh_tooling__pr_view\`. A tool that \`tool_search\` loads is callable from your next call." + +# Args: $1=project dir, $2=config dir relative to it ("" for the root), $3=JSON +write_project_config() { + mkdir -p "${1}/${2}" + printf '%s\n' "$3" > "${1}/${2}/.mcp-gh-tooling.json" +} + +# Run session-start.sh in a fresh hook environment; $output is the parsed +# additionalContext. Empty host or Claude project dir leaves that variable unset. +# Args: $1=GITHUB_MCP_HOST, $2=CLAUDE_PROJECT_DIR, $3=payload cwd +run_session_start_context() { + local -a hook_env=(-u GITHUB_MCP_HOST -u CLAUDE_PROJECT_DIR) + [[ -n "$1" ]] && hook_env+=("GITHUB_MCP_HOST=$1") + [[ -n "$2" ]] && hook_env+=("CLAUDE_PROJECT_DIR=$2") + local payload + mkdir -p "$3" + payload=$(jq -cn --arg cwd "$3" '{cwd: $cwd}') + run bash -c \ + 'set -o pipefail; printf "%s" "$1" | env "${@:3}" bash "$2" | jq -r ".hookSpecificOutput.additionalContext"' \ + _ "$payload" "$SESSION_SCRIPT" "${hook_env[@]}" +} + +# bats test_tags=host,pi +@test "pi directive ends with the pi tool-naming note after a blank line" { + run_session_start_context "pi" "" "${BATS_TEST_TMPDIR}/pi-project" + + assert_success + [[ "$output" == *$'\n\n'"${PI_HOST_NOTE}" ]] +} + +@test "Claude directive omits the pi tool-naming note" { + local project="${BATS_TEST_TMPDIR}/claude-project" + + run_session_start_context "" "$project" "$project" + + assert_success + refute_output --partial "In pi, these tools are named" +} + +@test "Codex directive omits the pi tool-naming note" { + run_session_start_context "" "" "${BATS_TEST_TMPDIR}/codex-project" + + assert_success + refute_output --partial "In pi, these tools are named" +} + +@test "pi takes enable_write_server from .pi over .claude and .codex" { + local project="${BATS_TEST_TMPDIR}/pi-project" + write_project_config "$project" ".pi" '{"enable_write_server": true}' + write_project_config "$project" ".claude" '{"enable_write_server": false}' + write_project_config "$project" ".codex" '{"enable_write_server": false}' + + run_session_start_context "pi" "" "$project" + + assert_success + assert_output --partial "## Write (gh-tooling-write)" +} + +@test "pi takes labels from .pi over .claude and .codex" { + local project="${BATS_TEST_TMPDIR}/pi-project" + write_project_config "$project" ".pi" '{"labels": {"needs-triage": "New and not yet reviewed"}}' + write_project_config "$project" ".claude" '{"labels": {"claude-label": "From .claude"}}' + write_project_config "$project" ".codex" '{"labels": {"codex-label": "From .codex"}}' + + run_session_start_context "pi" "" "$project" + + assert_success + assert_output --partial "- needs-triage: New and not yet reviewed" + refute_output --partial "claude-label" + refute_output --partial "codex-label" +} + +@test "Claude takes labels from .pi over the project-root config" { + local project="${BATS_TEST_TMPDIR}/claude-project" + write_project_config "$project" ".pi" '{"labels": {"needs-triage": "New and not yet reviewed"}}' + write_project_config "$project" "" '{"labels": {"root-label": "From the project root"}}' + + run_session_start_context "" "$project" "$project" + + assert_success + assert_output --partial "- needs-triage: New and not yet reviewed" + refute_output --partial "root-label" +} diff --git a/plugin-tests/github-mcp/write_server_gating.bats b/plugin-tests/github-mcp/write_server_gating.bats index fb68a1f..fd59bc2 100644 --- a/plugin-tests/github-mcp/write_server_gating.bats +++ b/plugin-tests/github-mcp/write_server_gating.bats @@ -98,3 +98,85 @@ run_codex_server_request() { tool_count=$(echo "$output" | jq '.result.tools | length') [[ "$tool_count" -gt 0 ]] } + +# ============================================================================ +# Per-host config precedence with the .pi/ location +# ============================================================================ + +# Run the write server for a host and print the tools/list response. +# Args: $1=GITHUB_MCP_HOST value ("" for none), $2=project root +run_host_server_tools_list() { + local host="$1" + local project_root="$2" + local requests + requests=$(send_jsonrpc "initialize" 1) + requests+=$'\n' + requests+=$(send_jsonrpc "tools/list" 2) + + local -a host_env=(-u GITHUB_MCP_HOST) + [[ -n "$host" ]] && host_env=("GITHUB_MCP_HOST=${host}") + printf '%s' "$requests" | env "${host_env[@]}" PROJECT_ROOT="$project_root" \ + bash "$SERVER_SCRIPT" 2>/dev/null | tail -1 +} + +# Args: $1=project root, $2=config dir relative to it ("" for the root), $3=JSON +write_server_config() { + mkdir -p "${1}/${2}" + printf '%s\n' "$3" > "${1}/${2}/.mcp-gh-tooling.json" +} + +@test "pi server prefers .pi config over .claude and .codex configs" { + local project_root="${BATS_TEST_TMPDIR}/pi-priority" + write_server_config "$project_root" ".pi" '{"enable_write_server": true}' + write_server_config "$project_root" ".claude" '{"enable_write_server": false}' + write_server_config "$project_root" ".codex" '{"enable_write_server": false}' + + run run_host_server_tools_list "pi" "$project_root" + + assert_success + [[ "$(jq '.result.tools | length' <<<"$output")" -gt 0 ]] +} + +@test "Codex server prefers .codex config over .pi config" { + local project_root="${BATS_TEST_TMPDIR}/codex-over-pi" + write_server_config "$project_root" ".codex" '{"enable_write_server": true}' + write_server_config "$project_root" ".pi" '{"enable_write_server": false}' + + run run_host_server_tools_list "codex" "$project_root" + + assert_success + [[ "$(jq '.result.tools | length' <<<"$output")" -gt 0 ]] +} + +@test "Claude server prefers .claude config over .pi config" { + local project_root="${BATS_TEST_TMPDIR}/claude-over-pi" + write_server_config "$project_root" ".claude" '{"enable_write_server": true}' + write_server_config "$project_root" ".pi" '{"enable_write_server": false}' + + run run_host_server_tools_list "" "$project_root" + + assert_success + [[ "$(jq '.result.tools | length' <<<"$output")" -gt 0 ]] +} + +@test "Codex server prefers .pi config over the project-root config" { + local project_root="${BATS_TEST_TMPDIR}/codex-pi-over-root" + write_server_config "$project_root" ".pi" '{"enable_write_server": true}' + write_server_config "$project_root" "" '{"enable_write_server": false}' + + run run_host_server_tools_list "codex" "$project_root" + + assert_success + [[ "$(jq '.result.tools | length' <<<"$output")" -gt 0 ]] +} + +@test "Claude server prefers .pi config over the project-root config" { + local project_root="${BATS_TEST_TMPDIR}/claude-pi-over-root" + write_server_config "$project_root" ".pi" '{"enable_write_server": true}' + write_server_config "$project_root" "" '{"enable_write_server": false}' + + run run_host_server_tools_list "" "$project_root" + + assert_success + [[ "$(jq '.result.tools | length' <<<"$output")" -gt 0 ]] +} diff --git a/plugin-tests/test_helper/common_setup.bash b/plugin-tests/test_helper/common_setup.bash index 2bcd07f..7a6e94d 100644 --- a/plugin-tests/test_helper/common_setup.bash +++ b/plugin-tests/test_helper/common_setup.bash @@ -12,6 +12,16 @@ _get_repo_root() { REPO_ROOT="$(_get_repo_root)" +# The hooks select their host from GITHUB_MCP_HOST; a value from the developer's +# shell would change their behavior. Tests that need a host set it explicitly. +unset GITHUB_MCP_HOST + +# The servers honor MCP_GH_TOOLING_CONFIG and PROJECT_ROOT ahead of project +# config locations; values from the developer's shell would redirect config +# lookup. Tests that need either set them explicitly. +unset MCP_GH_TOOLING_CONFIG +unset PROJECT_ROOT + # Load BATS helper libraries load "${REPO_ROOT}/.bats/bats-support/load" load "${REPO_ROOT}/.bats/bats-assert/load" diff --git a/plugins/github-mcp/.claude-plugin/plugin.json b/plugins/github-mcp/.claude-plugin/plugin.json index 69fd8ae..7491a55 100644 --- a/plugins/github-mcp/.claude-plugin/plugin.json +++ b/plugins/github-mcp/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "github-mcp", - "version": "4.3.0", + "version": "4.4.0", "description": "GitHub CLI MCP servers wrapping gh for pull requests, issues, CI runs, jobs, commits, search, labels, projects, and reviews. Read server (always active) with 31 tools and write server (opt-in) with 25 tools. Includes SessionStart hook that injects MCP tool directives and PreToolUse hooks that enforce MCP tool usage. Configuration-optional: works without config when gh is authenticated.", "author": { "name": "Shopware Labs" diff --git a/plugins/github-mcp/.codex-plugin/plugin.json b/plugins/github-mcp/.codex-plugin/plugin.json index 07b5879..afd2c37 100644 --- a/plugins/github-mcp/.codex-plugin/plugin.json +++ b/plugins/github-mcp/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "github-mcp", - "version": "4.3.0", + "version": "4.4.0", "description": "GitHub CLI MCP servers for pull requests, issues, CI, repositories, and other GitHub workflows, with shared lifecycle hooks that steer agents to the MCP tools.", "author": { "name": "Shopware Labs" diff --git a/plugins/github-mcp/AGENTS.md b/plugins/github-mcp/AGENTS.md index a4fe6ab..bd62515 100644 --- a/plugins/github-mcp/AGENTS.md +++ b/plugins/github-mcp/AGENTS.md @@ -13,6 +13,10 @@ plugins/github-mcp/ ├── .codex-plugin/plugin.json # Codex plugin manifest + inline MCP registrations ├── .mcp.json # Claude Code MCP registrations │ +├── pi/ # PI EXTENSION +│ ├── index.ts # Extension factory: server registration, directive, gates +│ └── gate.ts # runScript()/runGate(): spawns a hook script, parses its result +│ ├── hooks/ # HOOKS (MCP tool enforcement) │ ├── hooks.json # Hook configuration (SessionStart + PreToolUse x3) │ ├── prompts/ @@ -22,10 +26,11 @@ plugins/github-mcp/ │ ├── check-gh-tools.sh # Blocks common gh CLI bash commands (read + write) │ ├── check-api-tools.sh # Blocks MCP api_read/api tool bypass of dedicated tools │ └── lib/ -│ └── common.sh # Shared: parse_hook_input(), load_mcp_config(), block_tool() +│ └── common.sh # Shared: resolve_hook_context(), find_mcp_config(), parse_hook_input(), load_mcp_config(), block_tool() │ ├── shared/ # SHARED FRAMEWORK (language-agnostic) -│ └── mcpserver_core.sh # JSON-RPC 2.0 protocol handler +│ ├── mcpserver_core.sh # JSON-RPC 2.0 protocol handler (vendored) +│ └── config-dirs.sh # github_mcp_config_dirs(): host config directory order for hooks and servers │ └── mcp-server-gh/ # GITHUB CLI MCP SERVERS ├── server-read.sh # Read server entry point - loads optional .mcp-gh-tooling.json @@ -36,7 +41,7 @@ plugins/github-mcp/ ├── tools-write.json # 25 write tools (PR lifecycle, reviews, issues, issue types/fields, labels, assignees, sub-issues, projects, api) ├── mcp-gh-tooling.schema.json # JSON Schema for .mcp-gh-tooling.json └── lib/ - ├── common.sh # _gh_validate_number/repo/sha(), _gh_resolve_repo(), _gh_validate_jq_filter(), _gh_post_process(), _gh_parse_github_url(), _gh_validate_path(), _gh_download_file(), _gh_resolve_owner_repo() + ├── common.sh # _load_gh_config(), _gh_validate_number/repo/sha(), _gh_resolve_repo(), _gh_validate_jq_filter(), _gh_post_process(), _gh_parse_github_url(), _gh_validate_path(), _gh_download_file(), _gh_resolve_owner_repo() ├── pr.sh # tool_pr_view/diff/list/checks/comments/reviews/files/commits() ├── pr_write.sh # tool_pr_create/edit/ready/merge/close/reopen() ├── issue.sh # tool_issue_view(), tool_issue_list() @@ -72,17 +77,29 @@ This plugin provides: - `check-gh-tools.sh` - Blocks bash commands that should use MCP tools instead (both read and write commands) - `check-api-tools.sh` - Blocks `api_read` and `api` MCP tools when targeting endpoints with dedicated tools (opt-in via `block_api_tool_read`/`block_api_tool_write`) - All hook types configurable via `enforce_mcp_tools: false` in `.mcp-gh-tooling.json` +- **pi Extension** via `pi/index.ts` (the package entry point declared in root `package.json`'s + `pi.extensions`), loaded only by pi: + - Registers both MCP servers with `exposure: "deferred"`, same command scripts as Claude Code and + Codex + - Runs `session-start.sh` on `session_start` and stores its directive for `before_agent_start` + - Runs `check-gh-tools.sh`/`check-api-tools.sh` on `tool_call` through `pi/gate.ts`'s + `runGate()`/`runScript()` in place of hooks.json's PreToolUse matchers ## Architecture ### Config Loading -The gh-tooling servers have their own config loading logic independent of any shared config framework: +The gh-tooling servers load their config with `_load_gh_config()` in `mcp-server-gh/lib/common.sh`: - Config is **optional** (works without any config file if `gh` is authenticated) - Provides a default repo so `repo` doesn't need to be passed to every tool call -- Config discovery checks standard locations, including the project root, `.claude/`, and `.codex/` -- When both host-specific files exist, the active host's file has the highest priority and the - other host's file remains a fallback +- Config discovery checks standard locations, including the project root, `.claude/`, `.codex/`, + and `.pi/` +- The host directory order comes from `github_mcp_config_dirs()` in `shared/config-dirs.sh`, which + the hooks' `find_mcp_config()` also uses: the active host's directory first, then the others in the + fixed order `.claude/`, `.codex/`, `.pi/`. The servers take the host from `GITHUB_MCP_HOST`; any + value other than `pi` or `codex` uses the Claude Code order +- The hooks' `resolve_hook_context()` selects Claude Code whenever `CLAUDE_PROJECT_DIR` is set, then + `GITHUB_MCP_HOST` (`pi` or `codex`), and falls back to Codex - Write server checks `enable_write_server` flag and returns empty tools list when disabled ### Protocol Flow @@ -120,6 +137,7 @@ Captures `__raw` and `__exit` separately; branches on `suppress_errors` for `2>/ | Add blocked gh command | `hooks/scripts/check-gh-tools.sh` | - | `block_tool()`, grep pattern | | Add blocked API endpoint | `hooks/scripts/check-api-tools.sh` | - | Endpoint pattern matching | | Modify shared hook logic | `hooks/scripts/lib/common.sh` | - | `parse_hook_input()`, `load_mcp_config()`, `block_tool()` | +| Change the host config directory order | `shared/config-dirs.sh` | - | `github_mcp_config_dirs()`, used by hooks and servers | | Modify Claude Code registration | `.mcp.json` | `.claude-plugin/plugin.json` | `${CLAUDE_PLUGIN_ROOT}` | | Modify Codex registration | `.codex-plugin/plugin.json` | - | Inline `mcpServers`, inherited project cwd | | Disable hook enforcement | `.mcp-gh-tooling.json` | - | `enforce_mcp_tools: false` | @@ -149,6 +167,15 @@ Captures `__raw` and `__exit` separately; branches on `suppress_errors` for `2>/ 5. Add bash command blocking in `hooks/scripts/check-gh-tools.sh` 6. Update README.md and REFERENCE.md +**Modifying the pi extension:** +1. `pi/index.ts` registers the servers, runs the SessionStart directive, and wires the two gates — + it calls the same scripts as `hooks/hooks.json`, so a new blocked command or a directive change + needs no change here +2. `pi/gate.ts` only spawns a script and interprets its exit code; change it only to change how a + gate script's result is turned into a block/allow decision +3. Both files import pi's own packages with `import type` only, so the extension keeps no runtime + dependency on them + **Key design decisions:** - No environment wrapping (gh always runs natively on host) - Config is optional (no config = works with no default repo) @@ -168,6 +195,9 @@ the host: |------|------------|-------------| | Claude Code | `mcp__plugin_github-mcp_gh-tooling__` | `mcp__plugin_github-mcp_gh-tooling-write__` | | Codex | `mcp__gh_tooling__` | `mcp__gh_tooling_write__` | +| pi | `mcp__gh_tooling__` | `mcp__gh_tooling_write__` | + +pi's tool names match Codex's: both sanitize the server ID the same way. ```yaml # Codex read tools diff --git a/plugins/github-mcp/CHANGELOG.md b/plugins/github-mcp/CHANGELOG.md index 8f2e4e8..916550e 100644 --- a/plugins/github-mcp/CHANGELOG.md +++ b/plugins/github-mcp/CHANGELOG.md @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.4.0] - 2026-10-02 + +### Added + +- github-mcp runs in [pi](https://pi.dev) as a pi package. `plugins/github-mcp/pi/index.ts` is a pi + extension that registers the same two MCP servers, runs the same SessionStart directive script, + and runs the same `check-gh-tools.sh`/`check-api-tools.sh` gates through `pi/gate.ts` in place of + Claude Code's and Codex's PreToolUse hooks. The repository root is now also the npm package + `@shopware-ag/github-agent-tools`, installable with `pi install npm:@shopware-ag/github-agent-tools` + or `pi install git:github.com/shopwareLabs/github-agent-tools`, and published to the npm registry + by `.github/workflows/npm-publish.yml` on a pushed release tag. + +### Changed + +- `.pi/.mcp-gh-tooling.json` is a new config location, checked by the MCP servers and the + enforcement hooks on all three hosts, ahead of the project root. Claude Code and Codex now also + consult it: when it exists alongside a `.claude/` or `.codex/` file, the active host's own + directory still wins, but `.pi/` now ranks ahead of the project-root fallback for every host. + ## [4.3.0] - 2026-10-02 ### Fixed diff --git a/plugins/github-mcp/README.md b/plugins/github-mcp/README.md index 529dc51..8f4ce60 100644 --- a/plugins/github-mcp/README.md +++ b/plugins/github-mcp/README.md @@ -48,6 +48,11 @@ codex plugin add github-mcp@github-agent-tools Start a new Codex task after installation. Open `/hooks` to review and trust the bundled hooks; Codex skips plugin hooks until they are trusted. +### pi + +See the root [README](../../README.md#pi) for the install commands. pi needs no restart beyond a +new session or `/reload`. + ### Interactive Setup (Claude Code only) Install the `plugin-setup` plugin, then ask Claude to help you set up github-mcp: @@ -143,7 +148,7 @@ Configuration is loaded in the following priority order: - `.kiro/.mcp-gh-tooling.json` (Kiro) - `.windsurf/.mcp-gh-tooling.json` (Windsurf/Codeium) - `.zed/.mcp-gh-tooling.json` (Zed editor) - - Host override directories: `.claude/.mcp-gh-tooling.json` and `.codex/.mcp-gh-tooling.json`. When both exist, the active host's directory has highest priority; the other host's file remains a fallback. + - Host override directories: `.claude/.mcp-gh-tooling.json`, `.codex/.mcp-gh-tooling.json`, and `.pi/.mcp-gh-tooling.json`. The active host's directory has the highest priority, followed by the other two in the fixed order `.claude/`, `.codex/`, `.pi/`: Claude Code prefers `.claude/`, then `.codex/`, then `.pi/`; Codex prefers `.codex/`, then `.claude/`, then `.pi/`; pi prefers `.pi/`, then `.claude/`, then `.codex/`. The servers take the host from `GITHUB_MCP_HOST` (`pi` or `codex`; any other value or none means Claude Code). The enforcement hooks use the same order, then the project root, and take the first match. **Prerequisites:** - `gh` CLI installed: `brew install gh` (macOS) or see [GitHub CLI installation](https://cli.github.com/) @@ -235,6 +240,11 @@ Blocks bash commands that match known `gh` subcommands and redirects to the corr Optionally blocks the `api_read` and `api` tools when they target endpoints that have dedicated MCP tools. Configured separately via `block_api_tool_read` and `block_api_tool_write`. Implemented in `hooks/scripts/check-api-tools.sh`. +On pi, there is no PreToolUse hook; the extension's `tool_call` handler runs the same +`check-gh-tools.sh` and `check-api-tools.sh` scripts and blocks the call on the same output. A +gate script that fails any other way than exiting `2` — a crash, a timeout, a missing script — fails +open: the call goes through rather than being blocked. + All hook layers respect the `enforce_mcp_tools` setting and are disabled when set to `false`. ### Disabling Enforcement @@ -327,15 +337,35 @@ The raw server IDs stay `gh-tooling` and `gh-tooling-write`, but each host rende |---|---|---| | Claude Code | `mcp__plugin_github-mcp_gh-tooling__pr_view` | `mcp__plugin_github-mcp_gh-tooling-write__pr_create` | | Codex | `mcp__gh_tooling__pr_view` | `mcp__gh_tooling_write__pr_create` | +| pi | `mcp__gh_tooling__pr_view` | `mcp__gh_tooling_write__pr_create` | Use the identifiers exposed by the active host rather than copying the other host's spelling. +## pi + +- Both servers are registered with `exposure: "deferred"`: pi lists `gh-tooling` and + `gh-tooling-write` in its system prompt's `mcp_servers` section, and a tool is undeclared — so + the model does not see it as a direct tool call option — until `tool_search` loads it. A codemode + script can call a deferred tool whether or not it has been declared. +- An MCP result over 20 KB reaches the model with the middle cut out. Pass `max_lines`, `fields`, + or `jq_filter` to keep a result under that limit. +- A `gh-tooling` entry in pi's `mcp.json` (`~/.pi/agent/mcp.json` or a trusted project's + `.pi/mcp.json`) overrides the extension's registration — this is how to change the exposure pi + gives the server. The entry needs its own `command`, since it does not inherit the extension's, + and must set `env: {"GITHUB_MCP_HOST": "pi"}`; without it the server uses the Claude Code config + order while the hooks use pi's. +- The servers need bash 4.1+ and jq 1.7+ on the `PATH` pi runs with, same as under Claude Code and + Codex. +- pi support covers macOS and Linux. + ## Troubleshooting ### MCP Server Not Starting -1. Restart Claude Code or start a new Codex task after plugin installation -2. Check `/mcp` for connection status +1. Restart Claude Code or start a new Codex task, or start a new pi session or run `/reload`, after + plugin installation +2. Check `/mcp` for connection status; under pi, `gh-tooling` and `gh-tooling-write` show the + extension as their source 3. In Codex, open `/hooks` and confirm the plugin hooks are trusted if enforcement is missing 4. Verify `jq` is installed: `which jq` 5. Verify `gh` is installed and authenticated: `gh auth status` diff --git a/plugins/github-mcp/hooks/prompts/host-pi.md b/plugins/github-mcp/hooks/prompts/host-pi.md new file mode 100644 index 0000000..dc6ebf2 --- /dev/null +++ b/plugins/github-mcp/hooks/prompts/host-pi.md @@ -0,0 +1 @@ +In pi, these tools are named `mcp__gh_tooling__` (read) and `mcp__gh_tooling_write__` (write), for example `mcp__gh_tooling__pr_view`. A tool that `tool_search` loads is callable from your next call. diff --git a/plugins/github-mcp/hooks/scripts/check-api-tools.sh b/plugins/github-mcp/hooks/scripts/check-api-tools.sh index ca3830d..1898b62 100755 --- a/plugins/github-mcp/hooks/scripts/check-api-tools.sh +++ b/plugins/github-mcp/hooks/scripts/check-api-tools.sh @@ -1,5 +1,5 @@ #!/bin/bash -# MCP API Tool Enforcer (Claude Code and Codex) +# MCP API Tool Enforcer (Claude Code, Codex, and pi) # ========================================================= # Blocks MCP api/api_read tool calls when a dedicated tool exists. # Controlled by block_api_tool_read and block_api_tool_write in .mcp-gh-tooling.json. diff --git a/plugins/github-mcp/hooks/scripts/check-gh-tools.sh b/plugins/github-mcp/hooks/scripts/check-gh-tools.sh index 1b6bbb4..cc887f0 100644 --- a/plugins/github-mcp/hooks/scripts/check-gh-tools.sh +++ b/plugins/github-mcp/hooks/scripts/check-gh-tools.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Dev Tooling MCP Enforcer (GitHub CLI, Claude Code and Codex) +# Dev Tooling MCP Enforcer (GitHub CLI, Claude Code, Codex, and pi) # ========================================================= # Blocks common gh CLI bash commands in favor of gh-tooling MCP tools. # Controlled by two fields in .mcp-gh-tooling.json: diff --git a/plugins/github-mcp/hooks/scripts/lib/common.sh b/plugins/github-mcp/hooks/scripts/lib/common.sh index a4bdc01..79b8047 100644 --- a/plugins/github-mcp/hooks/scripts/lib/common.sh +++ b/plugins/github-mcp/hooks/scripts/lib/common.sh @@ -1,7 +1,7 @@ #!/bin/bash # Shared functions for MCP tool enforcement hooks # ================================================ -# This library provides common functionality for Claude Code and Codex hooks +# This library provides common functionality for Claude Code, Codex, and pi hooks # that block bash commands in favor of MCP tools. # # Usage: @@ -15,21 +15,32 @@ # HOOK_INPUT - Raw hook input read from stdin # COMMAND - The bash command being checked # PROJECT_DIR - Project directory reported by the active host -# HOOK_HOST - Host inferred from the hook environment (claude/codex) +# HOOK_HOST - Host inferred from the hook environment (claude/codex/pi) # CONFIG_FILE - Path to loaded config file (or empty) # ENVIRONMENT - Environment from config (native/docker/vagrant/ddev) # ENFORCE_MCP_TOOLS - Whether to enforce MCP tools (true/false) +source "$(dirname "${BASH_SOURCE[0]}")/../../../shared/config-dirs.sh" + # Resolve the active host and project directory from hook input. -# Claude Code provides CLAUDE_PROJECT_DIR; Codex provides cwd in the JSON payload. +# Claude Code provides CLAUDE_PROJECT_DIR, which selects claude even when +# GITHUB_MCP_HOST is exported in the shell. Otherwise GITHUB_MCP_HOST=pi or +# codex names the host, and codex is assumed. Codex and pi provide cwd in the +# JSON payload. resolve_hook_context() { local input="${1:-}" if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]]; then HOOK_HOST="claude" - PROJECT_DIR="${CLAUDE_PROJECT_DIR}" + elif [[ "${GITHUB_MCP_HOST:-}" == "pi" || "${GITHUB_MCP_HOST:-}" == "codex" ]]; then + HOOK_HOST="${GITHUB_MCP_HOST}" else HOOK_HOST="codex" + fi + + if [[ "$HOOK_HOST" == "claude" ]]; then + PROJECT_DIR="${CLAUDE_PROJECT_DIR}" + else PROJECT_DIR="" if command -v jq &>/dev/null; then PROJECT_DIR=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null || true) @@ -37,38 +48,28 @@ resolve_hook_context() { fi } -# Find a project config, preferring the active host's directory and then the -# other supported host's directory before the project-root fallback. +# Find a project config in the host config directories, in the order +# github_mcp_config_dirs gives for the active host, then in the project root. +# First match wins. # Args: $1 = config prefix # Sets: CONFIG_FILE (global) find_mcp_config() { local config_prefix="$1" - local -a locations CONFIG_FILE="" [[ -z "${PROJECT_DIR:-}" ]] && return 0 - if [[ "${HOOK_HOST:-claude}" == "codex" ]]; then - locations=( - ".codex/.mcp-${config_prefix}.json" - ".claude/.mcp-${config_prefix}.json" - ".mcp-${config_prefix}.json" - ) - else - locations=( - ".claude/.mcp-${config_prefix}.json" - ".codex/.mcp-${config_prefix}.json" - ".mcp-${config_prefix}.json" - ) - fi - - local location - for location in "${locations[@]}"; do - if [[ -f "${PROJECT_DIR}/${location}" ]]; then - CONFIG_FILE="${PROJECT_DIR}/${location}" - break + local dir + while IFS= read -r dir; do + if [[ -f "${PROJECT_DIR}/${dir}/.mcp-${config_prefix}.json" ]]; then + CONFIG_FILE="${PROJECT_DIR}/${dir}/.mcp-${config_prefix}.json" + return 0 fi - done + done < <(github_mcp_config_dirs "${HOOK_HOST:-claude}") + + if [[ -f "${PROJECT_DIR}/.mcp-${config_prefix}.json" ]]; then + CONFIG_FILE="${PROJECT_DIR}/.mcp-${config_prefix}.json" + fi } # Parse hook input from stdin @@ -119,7 +120,7 @@ block_tool() { local description="$2" local display_tool="$tool" - if [[ "${HOOK_HOST:-claude}" == "codex" ]]; then + if [[ "${HOOK_HOST:-claude}" == "codex" || "${HOOK_HOST:-claude}" == "pi" ]]; then display_tool="${display_tool//gh-tooling-write/gh_tooling_write}" display_tool="${display_tool//gh-tooling/gh_tooling}" else diff --git a/plugins/github-mcp/hooks/scripts/session-start.sh b/plugins/github-mcp/hooks/scripts/session-start.sh index 900ff9c..fc66809 100755 --- a/plugins/github-mcp/hooks/scripts/session-start.sh +++ b/plugins/github-mcp/hooks/scripts/session-start.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # SessionStart hook: inject gh-tooling MCP tool usage directives into conversation context. # Reads template from hooks/prompts/mcp-tool-directives.md, assembles dynamic sections -# from .mcp-gh-tooling.json config, and outputs to stdout as JSON additionalContext. +# from .mcp-gh-tooling.json config, appends hooks/prompts/host-pi.md under pi, and +# outputs to stdout as JSON additionalContext. set -euo pipefail HOOK_DIR="$(cd "$(dirname "$0")/.." && pwd)" @@ -49,6 +50,11 @@ fi assembled="${template/\{\{WRITE_SECTION\}\}/$write_section}" assembled="${assembled/\{\{LABEL_SECTION\}\}/$label_section}" +# pi: append the host's tool-naming note after one blank line +if [[ "$HOOK_HOST" == "pi" ]]; then + assembled="${assembled%$'\n'}"$'\n\n'"$(<"${PROMPTS_DIR}/host-pi.md")" +fi + # Output as JSON additionalContext context=$(printf '%s' "$assembled" | jq -Rs '.') cat <= 0; i-- )); do + locations+=("${project_root}/${host_dirs[i]}/${config_name}") + done + + local loc + for loc in "${locations[@]}"; do + if [[ -f "${loc}" ]]; then + GH_TOOLING_CONFIG_FILE="${loc}" + log "INFO" "Found config: ${loc}" + fi + done + + if [[ -z "${GH_TOOLING_CONFIG_FILE}" ]]; then + log "INFO" "${no_config_message}" + fi +} + # Validate a GitHub number (PR, issue, run, job ID - positive integer) # Args: $1 = value, $2 = field name for error message # Outputs error message to stdout and returns 1 on failure diff --git a/plugins/github-mcp/mcp-server-gh/server-read.sh b/plugins/github-mcp/mcp-server-gh/server-read.sh index 073f527..43ab989 100755 --- a/plugins/github-mcp/mcp-server-gh/server-read.sh +++ b/plugins/github-mcp/mcp-server-gh/server-read.sh @@ -23,57 +23,6 @@ source "${SHARED_DIR}/mcpserver_core.sh" GH_TOOLING_CONFIG_FILE="" GH_DEFAULT_REPO="" -_load_gh_config() { - local project_root="$1" - local config_name=".mcp-gh-tooling.json" - - if [[ -n "${MCP_GH_TOOLING_CONFIG:-}" ]]; then - if [[ -f "${MCP_GH_TOOLING_CONFIG}" ]]; then - GH_TOOLING_CONFIG_FILE="${MCP_GH_TOOLING_CONFIG}" - log "INFO" "Config from MCP_GH_TOOLING_CONFIG: ${GH_TOOLING_CONFIG_FILE}" - else - log "WARN" "MCP_GH_TOOLING_CONFIG set but file not found: ${MCP_GH_TOOLING_CONFIG}" - fi - return 0 - fi - - # Check standard config locations (last found wins). If both supported-host - # overrides exist, prefer the active host's directory. - local -a locations=( - "${project_root}/${config_name}" - "${project_root}/.aiassistant/${config_name}" - "${project_root}/.amazonq/${config_name}" - "${project_root}/.cline/${config_name}" - "${project_root}/.cursor/${config_name}" - "${project_root}/.kiro/${config_name}" - "${project_root}/.windsurf/${config_name}" - "${project_root}/.zed/${config_name}" - ) - - if [[ "${GITHUB_MCP_HOST:-claude}" == "codex" ]]; then - locations+=( - "${project_root}/.claude/${config_name}" - "${project_root}/.codex/${config_name}" - ) - else - locations+=( - "${project_root}/.codex/${config_name}" - "${project_root}/.claude/${config_name}" - ) - fi - - for loc in "${locations[@]}"; do - if [[ -f "${loc}" ]]; then - GH_TOOLING_CONFIG_FILE="${loc}" - log "INFO" "Found config: ${loc}" - fi - done - - if [[ -z "${GH_TOOLING_CONFIG_FILE}" ]]; then - log "INFO" "No .mcp-gh-tooling.json found - using defaults (no default repo)" - fi -} - _read_gh_config() { if [[ -z "${GH_TOOLING_CONFIG_FILE}" ]] || [[ ! -f "${GH_TOOLING_CONFIG_FILE}" ]]; then return 0 @@ -104,7 +53,7 @@ source "${SCRIPT_DIR}/lib/project.sh" trap 'log "ERROR" "Unexpected error on line ${LINENO}"' ERR -_load_gh_config "${PROJECT_ROOT}" +_load_gh_config "${PROJECT_ROOT}" "No .mcp-gh-tooling.json found - using defaults (no default repo)" _read_gh_config _gh_require_tools_list diff --git a/plugins/github-mcp/mcp-server-gh/server-write.sh b/plugins/github-mcp/mcp-server-gh/server-write.sh index 98c9644..fcc0907 100755 --- a/plugins/github-mcp/mcp-server-gh/server-write.sh +++ b/plugins/github-mcp/mcp-server-gh/server-write.sh @@ -22,55 +22,6 @@ source "${SHARED_DIR}/mcpserver_core.sh" GH_TOOLING_CONFIG_FILE="" GH_DEFAULT_REPO="" -_load_gh_config() { - local project_root="$1" - local config_name=".mcp-gh-tooling.json" - - if [[ -n "${MCP_GH_TOOLING_CONFIG:-}" ]]; then - if [[ -f "${MCP_GH_TOOLING_CONFIG}" ]]; then - GH_TOOLING_CONFIG_FILE="${MCP_GH_TOOLING_CONFIG}" - log "INFO" "Config from MCP_GH_TOOLING_CONFIG: ${GH_TOOLING_CONFIG_FILE}" - else - log "WARN" "MCP_GH_TOOLING_CONFIG set but file not found: ${MCP_GH_TOOLING_CONFIG}" - fi - return 0 - fi - - local -a locations=( - "${project_root}/${config_name}" - "${project_root}/.aiassistant/${config_name}" - "${project_root}/.amazonq/${config_name}" - "${project_root}/.cline/${config_name}" - "${project_root}/.cursor/${config_name}" - "${project_root}/.kiro/${config_name}" - "${project_root}/.windsurf/${config_name}" - "${project_root}/.zed/${config_name}" - ) - - if [[ "${GITHUB_MCP_HOST:-claude}" == "codex" ]]; then - locations+=( - "${project_root}/.claude/${config_name}" - "${project_root}/.codex/${config_name}" - ) - else - locations+=( - "${project_root}/.codex/${config_name}" - "${project_root}/.claude/${config_name}" - ) - fi - - for loc in "${locations[@]}"; do - if [[ -f "${loc}" ]]; then - GH_TOOLING_CONFIG_FILE="${loc}" - log "INFO" "Found config: ${loc}" - fi - done - - if [[ -z "${GH_TOOLING_CONFIG_FILE}" ]]; then - log "INFO" "No .mcp-gh-tooling.json found - write server disabled (no config)" - fi -} - _read_gh_config() { if [[ -z "${GH_TOOLING_CONFIG_FILE}" ]] || [[ ! -f "${GH_TOOLING_CONFIG_FILE}" ]]; then return 0 @@ -117,7 +68,7 @@ source "${SCRIPT_DIR}/lib/project.sh" trap 'log "ERROR" "Unexpected error on line ${LINENO}"' ERR -_load_gh_config "${PROJECT_ROOT}" +_load_gh_config "${PROJECT_ROOT}" "No .mcp-gh-tooling.json found - write server disabled (no config)" _read_gh_config _check_write_enabled _gh_require_tools_list diff --git a/plugins/github-mcp/pi/gate.ts b/plugins/github-mcp/pi/gate.ts new file mode 100644 index 0000000..731df79 --- /dev/null +++ b/plugins/github-mcp/pi/gate.ts @@ -0,0 +1,67 @@ +import { spawn } from "node:child_process"; + +export interface ScriptResult { + code: number | null; + stdout: string; + stderr: string; +} + +export type GateResult = { block: true; reason: string } | { block: false }; + +/** + * Runs `bash