diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..97c26d7 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-marketplace.json", + "name": "agent-home", + "description": "Portable agent-home skills and task operations.", + "owner": { + "name": "elix1er", + "url": "https://github.com/elix1er" + }, + "plugins": [ + { + "name": "agent-home", + "source": "./", + "description": "Operate a private GitHub Issues task ledger.", + "version": "0.1.0", + "author": { + "name": "elix1er" + }, + "homepage": "https://github.com/elix1er/agent-home#readme", + "repository": "https://github.com/elix1er/agent-home", + "license": "Apache-2.0", + "category": "productivity", + "tags": ["agent", "tasks", "github", "mcp"] + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..190be35 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "agent-home", + "version": "0.1.0", + "description": "Portable, auditable task home for one agent.", + "author": { + "name": "elix1er", + "url": "https://github.com/elix1er" + }, + "homepage": "https://github.com/elix1er/agent-home#readme", + "repository": "https://github.com/elix1er/agent-home", + "license": "Apache-2.0", + "keywords": ["agent", "tasks", "github", "mcp", "skills"] +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..200450a --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,28 @@ +{ + "name": "agent-home", + "version": "0.1.0", + "description": "Portable, auditable task home for one agent.", + "author": { + "name": "elix1er", + "url": "https://github.com/elix1er" + }, + "homepage": "https://github.com/elix1er/agent-home#readme", + "repository": "https://github.com/elix1er/agent-home", + "license": "Apache-2.0", + "keywords": ["agent", "tasks", "github", "mcp", "skills"], + "skills": "./skills/", + "interface": { + "displayName": "Agent Home", + "shortDescription": "Operate a private agent task ledger.", + "longDescription": "Create, claim, update, block, and complete auditable agent tasks in a private GitHub repository.", + "developerName": "elix1er", + "category": "Productivity", + "capabilities": ["Read", "Write"], + "websiteURL": "https://github.com/elix1er/agent-home", + "defaultPrompt": [ + "Add this task to my agent home.", + "What should you work on next?", + "Show my blocked agent tasks." + ] + } +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..80ff3fd --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +# Public URL of this deployment, without a trailing slash. +AGENT_HOME_URL=https://your-agent-home.vercel.app + +# Auth0 API settings. Keep actual values only in Vercel/local environment. +AUTH0_ISSUER=https://your-tenant.eu.auth0.com/ +AUTH0_AUDIENCE=https://your-agent-home.vercel.app/api/mcp +AUTH0_GITHUB_CLAIM=https://agent-home.dev/github_login + +# Option A: fine-grained token scoped to this one private repo. +GITHUB_TOKEN= + +# Option B (preferred for durable hosting): GitHub App credentials. Use either +# this complete group or GITHUB_TOKEN, never both. Preserve private-key newlines. +GITHUB_APP_ID= +GITHUB_APP_INSTALLATION_ID= +GITHUB_APP_PRIVATE_KEY= diff --git a/.github/ISSUE_TEMPLATE/agent-task.yml b/.github/ISSUE_TEMPLATE/agent-task.yml new file mode 100644 index 0000000..b793b9a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/agent-task.yml @@ -0,0 +1,58 @@ +name: Agent task +description: Add one outcome to the agent-home task ledger +title: "[Task] " +labels: + - "agent:inbox" +body: + - type: markdown + attributes: + value: "One issue is one reviewable outcome. Never include credentials or private exports." + - type: textarea + id: outcome + attributes: + label: Desired outcome + description: What observable result should exist? + placeholder: Prepare a reviewable release checklist for v0.2.0. + validations: + required: true + - type: textarea + id: constraints + attributes: + label: Constraints + description: Scope, safety rules, timing, and explicit exclusions. + placeholder: Do not deploy or merge. Keep it provider-neutral. + validations: + required: true + - type: textarea + id: acceptance + attributes: + label: Acceptance criteria + description: Use a checkbox for each observable completion condition. + placeholder: "- [ ] Checklist is linked from the release issue" + validations: + required: true + - type: dropdown + id: priority + attributes: + label: Priority + options: + - normal + - high + - critical + - low + default: 0 + validations: + required: true + - type: checkboxes + id: capabilities + attributes: + label: Enabled capability grants + description: These narrow the instance allowlist; they never expand agent-home.yml. + options: + - label: "`github.issues.write`" + required: true + - label: "`github.pull_requests.write`" + - label: "`github.contents.write`" + - label: "`vercel.deploy`" + - label: "`browser.use`" + - label: "`computer.use`" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..70c439e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Private security report + url: https://github.com/elix1er/agent-home/security/advisories/new + about: Report authentication, authorization, or credential issues privately. diff --git a/.github/workflows/hosted-smoke.yml b/.github/workflows/hosted-smoke.yml new file mode 100644 index 0000000..f1e9f6d --- /dev/null +++ b/.github/workflows/hosted-smoke.yml @@ -0,0 +1,51 @@ +name: Hosted MCP smoke + +on: + workflow_dispatch: + inputs: + mcp_url: + description: Full private deployment URL ending in /api/mcp + required: true + type: string + +permissions: + contents: read + +jobs: + inspect: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22.19 + - name: Verify protected resource metadata + env: + MCP_URL: ${{ inputs.mcp_url }} + run: | + base_url="${MCP_URL%/api/mcp}" + curl --fail --silent --show-error "${base_url}/.well-known/oauth-protected-resource" > /tmp/resource.json + node -e 'const m=require("/tmp/resource.json"); if (!m.authorization_servers?.length || !m.scopes_supported?.includes("tasks:write")) process.exit(1)' + - name: Reject unauthenticated MCP request + env: + MCP_URL: ${{ inputs.mcp_url }} + run: | + code=$(curl --silent --output /tmp/unauthenticated.json --dump-header /tmp/unauthenticated.headers --write-out '%{http_code}' \ + -H 'content-type: application/json' \ + --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"agent-home-smoke","version":"0.1.0"}}}' \ + "${MCP_URL}") + test "${code}" = "401" + grep -qi '^www-authenticate: Bearer' /tmp/unauthenticated.headers + - name: Inspect authenticated tool contract + env: + MCP_URL: ${{ inputs.mcp_url }} + MCP_SMOKE_BEARER_TOKEN: ${{ secrets.MCP_SMOKE_BEARER_TOKEN }} + run: | + test -n "${MCP_SMOKE_BEARER_TOKEN}" + npx --yes @modelcontextprotocol/inspector@2.2.0 --cli \ + --transport http \ + --server-url "${MCP_URL}" \ + --header "Authorization: Bearer ${MCP_SMOKE_BEARER_TOKEN}" \ + --method tools/list \ + --format json > /tmp/tools.json + node scripts/verify-inspector-output.mjs /tmp/tools.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4d07438 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm audit --audit-level=moderate + - run: npm run check + - name: Verify tag matches package version + run: test "${GITHUB_REF_NAME}" = "v$(node -p 'require("./package.json").version')" + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: gh release create "${GITHUB_REF_NAME}" --verify-tag --generate-notes --title "agent-home ${GITHUB_REF_NAME}" diff --git a/.github/workflows/setup-labels.yml b/.github/workflows/setup-labels.yml new file mode 100644 index 0000000..4b83808 --- /dev/null +++ b/.github/workflows/setup-labels.yml @@ -0,0 +1,41 @@ +name: Set up agent-home labels + +on: + workflow_dispatch: + +permissions: + issues: write + +jobs: + labels: + runs-on: ubuntu-latest + steps: + - name: Create or update task state labels + uses: actions/github-script@v7 + with: + script: | + const labels = [ + ['agent:inbox', 'D4C5F9', 'Actionable agent task'], + ['agent:active', '1D76DB', 'Task currently claimed by an agent'], + ['agent:blocked', 'D93F0B', 'Task needs a decision or capability'], + ['agent:done', '0E8A16', 'Task completed with evidence'], + ]; + + for (const [name, color, description] of labels) { + try { + await github.rest.issues.updateLabel({ + ...context.repo, + name, + color, + description, + }); + } catch (error) { + if (error.status !== 404) throw error; + await github.rest.issues.createLabel({ + ...context.repo, + name, + color, + description, + }); + } + } diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..dd62411 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,34 @@ +name: Validate + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + secrets: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm audit --audit-level=moderate + - run: npm run check diff --git a/.github/workflows/weekly-improvement.yml b/.github/workflows/weekly-improvement.yml index 9ffef2b..c33c046 100644 --- a/.github/workflows/weekly-improvement.yml +++ b/.github/workflows/weekly-improvement.yml @@ -20,6 +20,18 @@ jobs: const owner = context.repo.owner; const repo = context.repo.repo; const title = 'Weekly agent-home review'; + try { + await github.rest.issues.getLabel({ owner, repo, name: 'agent:inbox' }); + } catch (error) { + if (error.status !== 404) throw error; + await github.rest.issues.createLabel({ + owner, + repo, + name: 'agent:inbox', + color: 'D4C5F9', + description: 'Actionable agent task', + }); + } const { data: issues } = await github.rest.issues.listForRepo({ owner, repo, @@ -38,14 +50,23 @@ jobs: repo, title, body: [ - 'Run `automations/weekly-review.md` with your preferred agent.', + '## Desired outcome', + 'Run `automations/weekly-review.md` with the preferred agent.', + '', + '## Constraints', + 'Choose at most one genuinely useful improvement. Never auto-merge or deploy.', + '', + '## Acceptance criteria', + '- [ ] One useful improvement is proposed or completed, or the issue is closed with `no change`', + '- [ ] Repository changes use an `agent/*` branch and pull request', '', - '- [ ] Choose at most one useful improvement', - '- [ ] Use an `agent/*` branch and pull request for changes', - '- [ ] Close this issue when reviewed', + '## Priority', + 'low', '', - 'If nothing is worth changing, close with `no change`.', + '## Enabled capability grants', + '- `github.issues.write`', ].join('\n'), + labels: ['agent:inbox'], }); core.notice(`Created review: #${issue.number}`); diff --git a/.gitignore b/.gitignore index 2e2b885..8c162b4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ .codex/ .zcode/ node_modules/ +.next/ +*.tsbuildinfo .venv/ __pycache__/ dist/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/AGENTS.md b/AGENTS.md index f10fa47..4700345 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,13 +1,15 @@ # Agent home -Keep this repo useful, personal, and light. +Keep this repository portable, auditable, and light. -- Check what already exists before adding anything. -- Read `identity.md`, `goal.md`, and `inbox.md` before choosing work. -- Keep one clear current goal; move completed work into `archive/`. -- Save only reusable skills, automation prompts, and durable context. -- Never commit secrets, private exports, caches, or machine-local state. -- Prefer short plain files over frameworks and dependencies. +- Read `agent-home.yml`, `identity.md`, and `goal.md` before choosing work. +- Treat GitHub Issues as the canonical task ledger and closed `agent:done` issues as the task archive. +- Use exactly one state label: `agent:inbox`, `agent:active`, `agent:blocked`, or `agent:done`. +- Act only through capabilities explicitly allowed by `agent-home.yml`; missing capability means blocked. +- Log every task mutation with a concise issue comment containing action, result, and evidence. +- Save only reusable skills, durable policy, identity, and long-lived learnings in Git. +- Never commit or repeat secrets, private exports, tokens, headers, caches, or machine-local state. +- Prefer short plain files and small dependencies over frameworks inside frameworks. - Use a branch and pull request; do not push directly to `main`. -- Automations may suggest changes, but must not auto-merge, deploy, message people, rotate credentials, or delete data. +- Never auto-merge, deploy, message people, rotate credentials, or delete data unless the current task explicitly grants the named capability and the host exposes it. - If there is nothing genuinely useful to improve, say `no change`. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..35ae2f3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes follow semantic versioning. + +## 0.1.0 - 2026-08-17 + +- Publish the portable agent-home Skill and Codex/Claude/ZCode packaging. +- Make GitHub Issues the canonical task ledger with explicit state labels and audit comments. +- Add the optional self-hosted Vercel MCP with Auth0 JWT verification and owner allowlisting. +- Add schema, lifecycle, authorization, build, and no-secrets validation. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6047a68 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Contributing + +Keep changes narrow and portable. + +1. Open an issue describing the outcome and acceptance criteria. +2. Use a feature branch and pull request. +3. Run `npm run check`. +4. Update `CHANGELOG.md` for user-visible behavior. +5. Keep versions consistent across `package.json`, `.codex-plugin/plugin.json`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json`. + +Do not add credentials, private instance data, generic execution tools, or mandatory hosted infrastructure. New dependencies need a concrete reliability or interoperability benefit. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1b9d8e7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 agent-home contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 8e4a07e..b50579d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,89 @@ # agent-home -An early home for one persistent agent: who it is, what it is trying to do, what is next, and what it has learned. +`agent-home` is a portable home for one agent: durable identity and policy in Git, tasks and evidence in GitHub Issues, and an optional private MCP endpoint for clients that need shared remote access. -- [`identity.md`](identity.md) — personality, role, and boundaries -- [`goal.md`](goal.md) — the current goal -- [`inbox.md`](inbox.md) — possible next work -- `archive/` — completed work and useful history -- `skills/` — capabilities worth reusing -- `automations/` — recurring prompts +The public repository is only the template. Your real agent home should be a separate **private repository** created from it. The template author receives no tasks, credentials, telemetry, or authority over your instance. -This is V0. It may become a template, or it may become the first real agent living here. The direction is intentionally open. +## Start in five minutes -A weekly GitHub Action opens one review reminder. The matching prompt lives in [`automations/weekly-review.md`](automations/weekly-review.md) and can be used by any agent or local automation. +1. Click **Use this template** on GitHub and create a private repository. +2. Edit [`agent-home.yml`](agent-home.yml), [`identity.md`](identity.md), and [`goal.md`](goal.md). +3. Run the **Set up agent-home labels** workflow once. +4. Install the repository as a Skill/Plugin in your preferred client. +5. Create a GitHub Issue with the **Agent task** form, or say: “Use agent-home to add this task: …”. + +GitHub Issues are the task ledger. Open issues are work; closed `agent:done` issues are the archive. Repository files hold only durable identity, policy, skills, and long-lived learnings. + +## Two operating modes + +### Native mode + +Codex, Claude Code, and ZCode use the portable [`agent-home` Skill](skills/agent-home/SKILL.md) with GitHub access already available in that runtime. The Skill prefers: + +1. a configured agent-home MCP; +2. native GitHub tools/connectors; +3. local `gh` and Git access. + +It never invents access or asks you to paste a secret into chat. + +### Hosted mode (optional) + +Deploy this repository into **your own Vercel account** to expose `/api/mcp`. The MCP owns only task state and task audit history. It has no shell, browser, arbitrary HTTP, secret, or generic integration tool. + +Hosted mode is useful for ChatGPT Web/Mobile and for several clients sharing one task ledger. Auth0 is the OAuth authorization server; your deployment verifies JWTs and uses a GitHub App installation token or fine-grained token stored only in your Vercel environment. + +See [`docs/hosted-mcp.md`](docs/hosted-mcp.md) and [`.env.example`](.env.example). + +## Install the portable plugin + +### Codex / ChatGPT + +Install from this GitHub source when source plugins are enabled, or copy `skills/agent-home` into your Skills directory. Public-directory submission is intentionally not required for V1. + +### Claude Code + +```text +/plugin marketplace add elix1er/agent-home +/plugin install agent-home@agent-home +``` + +### ZCode + +Add `elix1er/agent-home` as a marketplace and install `agent-home`. ZCode consumes the Claude-compatible plugin manifest; this repository intentionally has no duplicate ZCode-only manifest. + +Client-specific notes are in [`docs/clients.md`](docs/clients.md). + +## Task lifecycle + +`agent:inbox` → `agent:active` → `agent:blocked` or `agent:done` + +Every mutation must leave a concise issue event with action, result, and evidence. Completion adds a final evidence comment, links relevant PRs/artifacts, applies `agent:done`, removes other state labels, and closes the issue. + +Capabilities default to deny. An agent may act autonomously only through capabilities explicitly allowed in `agent-home.yml`; absent or disabled capability means “blocked”, not a workaround. + +## Development + +```bash +npm install +npm run check +npm run dev +``` + +Node.js 20.9 or newer is required. Releases use semantic tags. The first release line is `0.1.x`; APIs may still change before `1.0.0`. + +## Safety + +- Never commit tokens, credentials, headers, private exports, or machine-local state. +- Never put secrets in plugin manifests, issues, comments, logs, or MCP tool output. +- Automations may propose work; they do not auto-merge, deploy, message people, rotate credentials, or delete data. +- A public template is not a suitable real agent instance. + +Apache-2.0 licensed. See [`SECURITY.md`](SECURITY.md) before reporting a vulnerability. + +## Compatibility references + +- [OpenAI plugin architecture](https://developers.openai.com/plugins/concepts/plugins) and [remote MCP authentication](https://developers.openai.com/plugins/build/auth) +- [Claude Code plugins](https://code.claude.com/docs/en/plugins), [marketplaces](https://code.claude.com/docs/en/plugin-marketplaces), and [remote MCP](https://code.claude.com/docs/en/mcp) +- [ZCode plugins](https://zcode.z.ai/en/docs/plugin) +- [Vercel MCP deployment](https://vercel.com/docs/mcp/deploy-mcp-servers-to-vercel) +- [Model Context Protocol](https://modelcontextprotocol.io/) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..c164cba --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,15 @@ +# Security policy + +## Supported versions + +Security fixes are applied to the latest release. Before `1.0.0`, upgrade to the newest `0.1.x` release rather than expecting backports. + +## Report privately + +Use GitHub's **Report a vulnerability** flow in the Security tab. Do not open a public issue for suspected credential exposure, authentication bypass, owner-check bypass, or remote code execution. + +Do not include live credentials in a report. Revoke an exposed credential first, then provide redacted evidence and reproduction steps. + +## Instance boundary + +Each hosted deployment belongs to its fork owner. The template author does not operate it and cannot retrieve its environment variables, tasks, or audit history. Security of Auth0, GitHub, Vercel, and enabled runtime capabilities remains the instance owner's responsibility. diff --git a/agent-home.yml b/agent-home.yml new file mode 100644 index 0000000..a28c24c --- /dev/null +++ b/agent-home.yml @@ -0,0 +1,25 @@ +version: 1 + +# Configure these placeholders in the private repository created from this template. +instance: + github: + owner: "replace-me" + repository: "replace-me" + allowed_logins: + - "replace-me" + +autonomy: + default: "deny" + audit: "issue-comments-required" + capabilities: + github.issues.read: "allow" + github.issues.write: "allow" + github.pull_requests.write: "deny" + github.contents.write: "deny" + vercel.deploy: "deny" + browser.use: "deny" + computer.use: "deny" + +# Declare additional host-provided MCP/tool namespaces here. This grants policy +# permission only; the client must still expose and authenticate the capability. +external_capabilities: [] diff --git a/app/.well-known/oauth-protected-resource/route.ts b/app/.well-known/oauth-protected-resource/route.ts new file mode 100644 index 0000000..b431d26 --- /dev/null +++ b/app/.well-known/oauth-protected-resource/route.ts @@ -0,0 +1,22 @@ +import { generateProtectedResourceMetadata, metadataCorsOptionsRequestHandler } from "mcp-handler"; +import { loadAuthSettings } from "../../../src/auth"; + +export function GET(request: Request): Response { + const settings = loadAuthSettings(); + const metadata = generateProtectedResourceMetadata({ + authServerUrls: [settings.issuer], + resourceUrl: settings.resourceUrl, + additionalMetadata: { + scopes_supported: ["tasks:read", "tasks:write"], + bearer_methods_supported: ["header"], + }, + }); + return Response.json(metadata, { + headers: { + "Access-Control-Allow-Origin": "*", + "Cache-Control": "public, max-age=300", + }, + }); +} + +export const OPTIONS = metadataCorsOptionsRequestHandler(); diff --git a/app/api/mcp/route.ts b/app/api/mcp/route.ts new file mode 100644 index 0000000..636395b --- /dev/null +++ b/app/api/mcp/route.ts @@ -0,0 +1,31 @@ +import { createMcpHandler, withMcpAuth } from "mcp-handler"; +import { loadAuthSettings, verifyAgentToken } from "../../../src/auth"; +import { loadManifest } from "../../../src/config"; +import { registerAgentHomeTools } from "../../../src/mcp"; + +const manifest = loadManifest(); + +const mcpHandler = createMcpHandler( + (server) => registerAgentHomeTools(server, manifest), + { + serverInfo: { name: "agent-home", version: "0.1.0" }, + instructions: "Operate only the configured GitHub task ledger and obey agent-home.yml capability grants.", + maxSubscriptions: 0, + }, +); + +const handler = withMcpAuth( + mcpHandler, + async (_request, bearerToken) => { + if (!bearerToken) return undefined; + const settings = loadAuthSettings(); + return verifyAgentToken(bearerToken, manifest, settings); + }, + { + required: true, + resourceMetadataPath: "/.well-known/oauth-protected-resource", + resourceUrl: process.env.AGENT_HOME_URL, + }, +); + +export { handler as DELETE, handler as GET, handler as POST }; diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..2759ddf --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; +import "./styles.css"; + +export const metadata: Metadata = { + title: "agent-home", + description: "Private, auditable task home for one agent.", +}; + +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..fbd567f --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,33 @@ +import { loadManifest, manifestIsConfigured } from "../src/config"; + +export const dynamic = "force-dynamic"; + +export default function Home() { + const manifest = loadManifest(); + const configured = manifestIsConfigured(manifest); + + return ( +
+

AGENT HOME

+

{configured ? `${manifest.instance.github.owner}/${manifest.instance.github.repository}` : "Configure your private instance"}

+

+ GitHub Issues are the task ledger. This deployment exposes only a narrow, authenticated task MCP at /api/mcp. +

+
+
+
Instance
+
{configured ? "configured" : "template placeholders detected"}
+
+
+
Task transport
+
Streamable HTTP
+
+
+
Authentication
+
Auth0 OAuth + GitHub allowlist
+
+
+

No task data, credentials, telemetry, or operator access is sent to the template author.

+
+ ); +} diff --git a/app/styles.css b/app/styles.css new file mode 100644 index 0000000..306071c --- /dev/null +++ b/app/styles.css @@ -0,0 +1,19 @@ +:root { + color-scheme: dark; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + background: #0b0d0f; + color: #f2f4f5; +} + +* { box-sizing: border-box; } +body { margin: 0; } +main { width: min(760px, calc(100% - 40px)); margin: 14vh auto; } +.eyebrow { color: #75e6a4; letter-spacing: .16em; font-size: .78rem; } +h1 { font-family: ui-sans-serif, system-ui, sans-serif; font-size: clamp(2rem, 7vw, 4.6rem); line-height: .98; letter-spacing: -.055em; max-width: 12ch; } +.lede { color: #b7bec4; line-height: 1.7; max-width: 62ch; } +code { color: #75e6a4; } +dl { margin-top: 3rem; border-top: 1px solid #2b3035; } +dl div { display: grid; grid-template-columns: 10rem 1fr; gap: 1rem; padding: 1rem 0; border-bottom: 1px solid #2b3035; } +dt { color: #7e8890; } +dd { margin: 0; } +.foot { margin-top: 3rem; color: #7e8890; font-size: .82rem; line-height: 1.6; } diff --git a/archive/README.md b/archive/README.md index bc7dda6..dd7904e 100644 --- a/archive/README.md +++ b/archive/README.md @@ -1,3 +1,5 @@ # Archive -Completed work and useful history live here. Keep entries short: outcome, evidence, and what changed for the agent. +Closed GitHub Issues labeled `agent:done` are the task archive. + +Use this directory only for distilled, durable learnings that remain useful after the original issue and its artifacts are gone. Never copy private exports or secrets here. diff --git a/automations/weekly-review.md b/automations/weekly-review.md index af4aa86..d8c256d 100644 --- a/automations/weekly-review.md +++ b/automations/weekly-review.md @@ -1,7 +1,7 @@ # Weekly review -Read `AGENTS.md`, `identity.md`, `goal.md`, `inbox.md`, recent changes, open pull requests and issues, and the latest archive entries. +Read `agent-home.yml`, `identity.md`, `goal.md`, open agent task issues, recent pull requests, and recent closed `agent:done` issues. -Choose at most one small action that advances the current goal. Prefer doing useful work over adding more structure. If a repository change is clearly useful, prepare it on an `agent/*` branch and open a pull request. Never auto-merge. +Choose at most one small action that materially advances the current goal. Claim an existing issue before creating a new one. Obey the capability manifest and write concise audit comments for task mutations. -Archive completed outcomes briefly. If a decision or permission is needed, leave a concise issue instead. If nothing is worth changing, report exactly `no change` and create nothing. +Prepare repository changes on an `agent/*` branch and pull request. Never auto-merge. If a required capability or decision is missing, block the issue with the exact requirement. If nothing is worth changing, report exactly `no change` and create nothing. diff --git a/docs/clients.md b/docs/clients.md new file mode 100644 index 0000000..4cdb2b5 --- /dev/null +++ b/docs/clients.md @@ -0,0 +1,34 @@ +# Client setup + +## ChatGPT Web/Mobile + +Deploy hosted mode first. In ChatGPT Developer Mode, add the deployment's `/api/mcp` URL and complete Auth0/GitHub sign-in. The portable Skill supplies the workflow; the remote MCP supplies shared task state. + +Writable private remote MCP use requires OAuth. Do not place a static API key in a prompt or Skill. + +## Codex + +Install the plugin from source or copy `skills/agent-home` into the Codex Skills directory. Native GitHub tools or local `gh` are the default. Add the hosted MCP only when shared remote state is useful. + +## Claude Code + +```text +/plugin marketplace add elix1er/agent-home +/plugin install agent-home@agent-home +``` + +The plugin contains the same canonical Skill used by Codex. Configure native GitHub access or add the hosted Streamable HTTP MCP URL using Claude Code's MCP settings. + +## ZCode + +Add `elix1er/agent-home` as a marketplace and install `agent-home`. ZCode reads the Claude-compatible `.claude-plugin/plugin.json` and Skill layout. Configure the same optional MCP URL through ZCode's MCP settings. + +## Invocation examples + +- “Use agent-home to add this task: prepare a release checklist.” +- “What should you work on next?” +- “Continue agent-home task #12.” +- “Show blocked agent work.” +- “Complete this task with the PR and test evidence.” + +In every client, missing runtime access or a denied capability produces a clear blocked result. It never produces invented success or a request to paste secrets into chat. diff --git a/docs/hosted-mcp.md b/docs/hosted-mcp.md new file mode 100644 index 0000000..89ef67e --- /dev/null +++ b/docs/hosted-mcp.md @@ -0,0 +1,42 @@ +# Hosted MCP + +Hosted mode deploys a narrow task API into the fork owner's Vercel account. It is optional for local coding agents and useful when ChatGPT Web/Mobile or several clients need one remote task ledger. + +## 1. Configure the private fork + +Replace every `replace-me` value in `agent-home.yml`. Keep the repository private. Run the **Set up agent-home labels** workflow. + +## 2. Create an Auth0 API + +Create an Auth0 API whose identifier exactly matches `AUTH0_AUDIENCE` (normally `https:///api/mcp`). Enable RBAC and add: + +- `tasks:read` +- `tasks:write` + +Use an Auth0 application type that supports Authorization Code with PKCE. Add the callback URLs supplied by each MCP client. Auth0 provides authorization-server discovery and JWKS; this repository is the protected resource server. + +GitHub social login alone does not automatically place the GitHub login in an access token. Add an Auth0 Post Login Action that copies the verified GitHub username from the linked identity profile into the custom claim named by `AUTH0_GITHUB_CLAIM`. The default claim is `https://agent-home.dev/github_login`. + +The MCP rejects a valid token when that claim is absent or not listed in `agent-home.yml`. + +## 3. Grant access to one repository + +Prefer a GitHub App installation scoped to the private fork with read/write Issues and read Metadata permission. A fine-grained personal access token scoped to that single repository is acceptable for a personal instance. + +For a GitHub App, set `GITHUB_APP_ID`, `GITHUB_APP_INSTALLATION_ID`, and `GITHUB_APP_PRIVATE_KEY`; the official Octokit auth strategy mints short-lived installation tokens. Alternatively set one fine-grained token as `GITHUB_TOKEN`. Do not configure both. Do not commit any of them. + +## 4. Deploy + +Import the private fork into Vercel and set all values from `.env.example`. Deploy a preview first, then verify: + +- `GET /.well-known/oauth-protected-resource` returns the Auth0 issuer and `tasks:read tasks:write` scopes; +- unauthenticated `/api/mcp` requests return `401` with `WWW-Authenticate` pointing to that metadata; +- a wrong GitHub login is rejected; +- MCP Inspector can initialize and list exactly eight task tools; +- a test task completes against the intended private repository. + +Promote the verified preview to production. Vercel deployment history provides rollback; agent-home itself never promotes or rolls back unless the current task explicitly grants `vercel.deploy` and the host exposes it. + +## OAuth boundary + +The service verifies JWT signature, issuer, audience, expiry, scopes, and the configured GitHub login allowlist through `jose` and Auth0 JWKS. It does not implement an authorization server, collect passwords, accept static API keys from MCP clients, or expose Vercel/GitHub credentials through tools. diff --git a/docs/release-checklist.md b/docs/release-checklist.md new file mode 100644 index 0000000..723b354 --- /dev/null +++ b/docs/release-checklist.md @@ -0,0 +1,22 @@ +# Release checklist + +## Every release + +- [ ] `npm audit --audit-level=moderate` passes. +- [ ] `npm run check` passes on Node 20. +- [ ] Skill Creator and Plugin Creator validators pass. +- [ ] Versions match across package and marketplace manifests. +- [ ] Git history contains no secrets or private instance data. +- [ ] Changelog describes user-visible changes. +- [ ] Tag is `v` and the release workflow succeeds. + +## Interoperability release gate + +- [ ] Codex source/plugin validation and invocation succeed. +- [ ] `claude plugin validate .`, local marketplace add, install, and invocation succeed. +- [ ] ZCode marketplace add, install, and invocation succeed through the Claude-compatible layout. +- [ ] Hosted MCP smoke workflow passes against a private fork preview. +- [ ] Wrong-owner Auth0 token is rejected. +- [ ] One task created from ChatGPT, Codex, Claude Code, and ZCode resolves to the same private GitHub Issues ledger. + +The final cross-client private-fork smoke requires client accounts and a user-owned Auth0/Vercel deployment. Do not mark it complete from unit tests alone. diff --git a/goal.md b/goal.md index 03cb8aa..8623c5e 100644 --- a/goal.md +++ b/goal.md @@ -1,10 +1,7 @@ # Current goal -Discover one concrete recurring responsibility for this agent and demonstrate it end to end. +Configure one durable goal for this private agent instance. -Done means: +Example: Keep the owner's small software projects healthy by triaging actionable issues, preparing reviewable pull requests, and recording evidence without merging or deploying automatically. -- the responsibility is genuinely useful; -- the agent can perform it repeatedly from this home; -- the result is reviewable; -- one completed run is recorded in `archive/`. +Active execution belongs in a GitHub Issue, not in this file. Change this file only when the long-lived goal changes. diff --git a/identity.md b/identity.md index c309411..b0cab45 100644 --- a/identity.md +++ b/identity.md @@ -1,11 +1,11 @@ # Identity -- Name: unnamed -- Role: evolving personal agent -- State: bootstrapping +Configure this file in the private instance. -This agent should grow into a useful, dependable coworker with continuity between runs. Its personality and responsibilities are not fixed yet. +- Name: Agent Home +- Role: dependable personal operator +- Style: direct, calm, concise, evidence-led -It should be direct, curious, calm, and honest about what is verified, partial, blocked, or only an idea. It earns autonomy through useful work and clear evidence. +The agent preserves continuity across runs, works toward the goal in `goal.md`, and treats issue history as its task memory. It distinguishes verified, partial, blocked, and proposed work. -It does not expose private material, silently expand its scope, or take consequential external actions without permission. +It does not invent access, silently expand scope, expose private material, or bypass a disabled capability. diff --git a/inbox.md b/inbox.md index 7698366..ed61707 100644 --- a/inbox.md +++ b/inbox.md @@ -1,8 +1,5 @@ # Inbox -Possible next moves, not commitments: +GitHub Issues labeled `agent:inbox` are the canonical inbox. -- choose the first recurring responsibility; -- run it once manually before automating it; -- decide what a useful archive entry should preserve; -- connect a local scheduled review when the responsibility is clear. +Do not keep a second task list here. This file exists only to make the rule visible to agents that begin by reading repository context. diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..ce4e94a --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,7 @@ +/// +/// +import "./.next/types/routes.d.ts"; +import "./.next/types/root-params.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..4a479ed --- /dev/null +++ b/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + poweredByHeader: false, +}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..eb9d720 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2569 @@ +{ + "name": "agent-home", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agent-home", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/server": "2.0.0", + "@octokit/auth-app": "8.3.0", + "@octokit/rest": "22.0.1", + "jose": "6.2.9", + "mcp-handler": "2.1.1", + "next": "16.3.1", + "react": "19.2.4", + "react-dom": "19.2.4", + "yaml": "2.9.0", + "zod": "4.4.3" + }, + "devDependencies": { + "@types/node": "24.10.1", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", + "typescript": "5.9.3", + "vitest": "4.1.10" + }, + "engines": { + "node": ">=20.9.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz", + "integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==", + "license": "MIT", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz", + "integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.0.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@next/env": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.1.tgz", + "integrity": "sha512-35G3xwkQUb2oETSDjFXGrVugknoayLFBh7vSE+yAcl9IP2zT9wyGwq7297AYHR11kJld807t5f8AJBs6WBzXsQ==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.1.tgz", + "integrity": "sha512-ABMIu2zQ7cnNIHm5ivKGwZwUrm0pAai3yiJ/gK/rF1c1VP9UOnj7XECbMKFdVKp9I9eMYq9NoDs1WXOoowxzJw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.1.tgz", + "integrity": "sha512-gNG21e/UnrroeScbY/QndUEdl0mF1FRibW7BBeYUz/5ABCepjqDdEdgr592vpzMtCn/m7FTjYq3TN4TpyDnutw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.1.tgz", + "integrity": "sha512-6B6Lw016iwNUQuaJoraMMTLh6TwHzFUtxipSScD1F3YyymcrRWkobodRS2ftIOkF5vrs4zNlyUrTC5YZQ9Lz5w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.1.tgz", + "integrity": "sha512-JUiPXZKK9wOhjf4MgDiH29GZLxfqOesbLtHq2pDxwH/WwscTRV2ToymnOTh1egzaZf0ueUf8T2+CeYTGHjW0Iw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.1.tgz", + "integrity": "sha512-Uog9jsrmIRIL/lfvIp9htmskSNC7JcQsMVucXL2V2YY1y/D9IUN3LPEafqy0zRJ2cIU1SQ0V6F6TlffQ+pLAGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.1.tgz", + "integrity": "sha512-6yy3FT13KgUFOj5H8bl8w/6nKiJwHIvbtwh1V+1acsu+7y4tJjnemSa6mhsh53BeoVrlozE+fMgZhXH46WmjMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.1.tgz", + "integrity": "sha512-iOoN1QecUoGNZik536U/vtK43YwgyrCsGIkth52yIkl612n+0C9MjSnJbQAikISpb+WYRooBVhaDlUW7iZoKog==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.1.tgz", + "integrity": "sha512-d/k+PpAriUPaeMJJOG7HUSdqfEX46FEPWU1p3/nm2ACmXhj9hFEWdFODUBIpkuijXYkfL90qZzTqVPRp4BW/hw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@octokit/auth-app": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.3.0.tgz", + "integrity": "sha512-/UaKmJCsOc5XBZwhnFiGNdLH/FkDF8lYtBn1QlKxtX7IpgaRB/XOXjixFtERAknyUZHxp3oDuoiG0En4VptJSg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^9.0.4", + "@octokit/auth-oauth-user": "^6.0.3", + "@octokit/request": "^10.0.13", + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0", + "toad-cache": "^3.7.0", + "universal-github-app-jwt": "^2.2.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.4.tgz", + "integrity": "sha512-Pe3du5LrC6dlv10b0RbarBlJtIT1Urq8BFoQklK8WmBw8YKnGgrANn7cQmHiA1v8AVihgi7YutV8MncP57I4og==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.4", + "@octokit/auth-oauth-user": "^6.0.3", + "@octokit/request": "^10.0.13", + "@octokit/types": "^17.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.4.tgz", + "integrity": "sha512-M/+34rmkxMvUnnTo/uqNeVRCXJoJ+5N34eNTbL1KMtIyJpedCTsu/iFL1cWdL+w5hQMlYt6xzXoyux2gn0OVnw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^6.0.3", + "@octokit/request": "^10.0.13", + "@octokit/types": "^17.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.3.tgz", + "integrity": "sha512-t4OKUhrI5britmpRiSzPAz1TJPyUN/Hw1HEE3Hz/uElxcmnf/YCRSKtFRNc5gy4yJFTlXgk34H1lvxiotQJQvQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.4", + "@octokit/oauth-methods": "^6.0.3", + "@octokit/request": "^10.0.13", + "@octokit/types": "^17.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.7.tgz", + "integrity": "sha512-DcB0M3KFgr9ECI328lhBMVsyFT2DnmNucSBTqEN3exyNKUzkkpUSCHmTRcunF41Eou2TIQKW4seewri8ON9bSA==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.4", + "@octokit/request": "^10.0.13", + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.4.tgz", + "integrity": "sha512-f1cOWoHPmxryJFknxbtDdjODWfV8A9tc8Aae6ermXPNgHFZ/x91AtHIz4gicEjL8hkJiip+u21QHJORfBv/qiA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^17.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.4.tgz", + "integrity": "sha512-5s15CCiY8XXQ+FG+b1YQcl6Z2FA++nwAz/tg2VUrTmnMncP+2nnGUEYANImdnxsA2Fnq+Mbl7hDjUTw7cFAwcg==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.13", + "@octokit/types": "^17.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", + "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.4.tgz", + "integrity": "sha512-96RsnxS7Hk/BQhUA1Qo2pmcYP6LWQRWMdo7bVbNE7ZCkFLhSUYBXVUj9tVnvhl4jzbwHYt/dcIG+ioY427b2sg==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/request": "^10.0.13", + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", + "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.13.tgz", + "integrity": "sha512-v2269YxL9Yf+x3d+gRI63FP0vFQEiWgLyBzxe/Y+0yFDg2B/Tzf5dhh9VNfccVAQnfcfwQWyk/y6Bn7rUXXs7A==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.3", + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0", + "content-type": "^2.0.0", + "json-with-bigint": "^3.5.3", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.1.tgz", + "integrity": "sha512-+eaY7G2VVpSf2pc5Gn1+mph837V/d/TYTJAgWL9Tb0ogGYcpN3IlAVFgjL+Vv93F/sevrxkvsYCedtpLdcFLzA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest": { + "version": "22.0.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", + "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", + "license": "MIT", + "dependencies": { + "@octokit/core": "^7.0.6", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-request-log": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.144.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", + "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", + "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", + "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", + "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", + "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", + "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", + "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", + "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", + "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", + "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", + "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", + "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", + "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", + "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz", + "integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "license": "Apache-2.0" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/jose": { + "version": "6.2.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.9.tgz", + "integrity": "sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-with-bigint": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.11.tgz", + "integrity": "sha512-WvkM9Hfb9kqzCcbsvpwfWDvdfGZDhYuCoaCwHRe5q3LtULKkbrI/L6JYcN8owflgA3di5dP2yVAV2NVCXkgtkA==", + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mcp-handler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mcp-handler/-/mcp-handler-2.1.1.tgz", + "integrity": "sha512-D/PAwxF9TexmixJY+FZMgYq9SSbGm3vtcUjwUHZ8tYYuOtGT3/KgAKBjO3UJZoXgW8nHDtv8ySOICMEZPD0b/Q==", + "license": "Apache-2.0", + "dependencies": { + "chalk": "^5.3.0", + "commander": "^11.1.0" + }, + "bin": { + "create-mcp-route": "dist/cli/index.js", + "mcp-adapter": "dist/cli/index.js", + "mcp-handler": "dist/cli/index.js" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/server": "^2.0.0", + "next": ">=13.0.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/server": { + "optional": false + }, + "next": { + "optional": true + } + } + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.1.tgz", + "integrity": "sha512-hsAp0i7Rh+/dhe7DGIeN2YlpLM1DP4MNxti9EtDMtqcO612X81MvvEj388/oTce9U1EcEIOWDlGq0zRwrBKvuA==", + "license": "MIT", + "dependencies": { + "@next/env": "16.3.1", + "@swc/helpers": "0.5.23", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.5.23", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.3.1", + "@next/swc-darwin-x64": "16.3.1", + "@next/swc-linux-arm64-gnu": "16.3.1", + "@next/swc-linux-arm64-musl": "16.3.1", + "@next/swc-linux-x64-gnu": "16.3.1", + "@next/swc-linux-x64-musl": "16.3.1", + "@next/swc-win32-arm64-msvc": "16.3.1", + "@next/swc-win32-x64-msvc": "16.3.1", + "sharp": "^0.35.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/rolldown": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", + "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.144.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.2.4", + "@rolldown/binding-darwin-arm64": "1.2.4", + "@rolldown/binding-darwin-x64": "1.2.4", + "@rolldown/binding-freebsd-x64": "1.2.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", + "@rolldown/binding-linux-arm64-gnu": "1.2.4", + "@rolldown/binding-linux-arm64-musl": "1.2.4", + "@rolldown/binding-linux-ppc64-gnu": "1.2.4", + "@rolldown/binding-linux-s390x-gnu": "1.2.4", + "@rolldown/binding-linux-x64-gnu": "1.2.4", + "@rolldown/binding-linux-x64-musl": "1.2.4", + "@rolldown/binding-openharmony-arm64": "1.2.4", + "@rolldown/binding-win32-arm64-msvc": "1.2.4", + "@rolldown/binding-win32-x64-msvc": "1.2.4" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.3", + "@img/sharp-darwin-x64": "0.35.3", + "@img/sharp-freebsd-wasm32": "0.35.3", + "@img/sharp-libvips-darwin-arm64": "1.3.2", + "@img/sharp-libvips-darwin-x64": "1.3.2", + "@img/sharp-libvips-linux-arm": "1.3.2", + "@img/sharp-libvips-linux-arm64": "1.3.2", + "@img/sharp-libvips-linux-ppc64": "1.3.2", + "@img/sharp-libvips-linux-riscv64": "1.3.2", + "@img/sharp-libvips-linux-s390x": "1.3.2", + "@img/sharp-libvips-linux-x64": "1.3.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", + "@img/sharp-libvips-linuxmusl-x64": "1.3.2", + "@img/sharp-linux-arm": "0.35.3", + "@img/sharp-linux-arm64": "0.35.3", + "@img/sharp-linux-ppc64": "0.35.3", + "@img/sharp-linux-riscv64": "0.35.3", + "@img/sharp-linux-s390x": "0.35.3", + "@img/sharp-linux-x64": "0.35.3", + "@img/sharp-linuxmusl-arm64": "0.35.3", + "@img/sharp-linuxmusl-x64": "0.35.3", + "@img/sharp-webcontainers-wasm32": "0.35.3", + "@img/sharp-win32-arm64": "0.35.3", + "@img/sharp-win32-ia32": "0.35.3", + "@img/sharp-win32-x64": "0.35.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz", + "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "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==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/universal-github-app-jwt": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", + "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", + "license": "MIT" + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, + "node_modules/vite": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..644b2da --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "agent-home", + "version": "0.1.0", + "private": true, + "description": "Portable, self-hosted agent task home.", + "license": "Apache-2.0", + "engines": { + "node": ">=20.9.0" + }, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "validate": "node scripts/validate.mjs", + "check": "npm run validate && npm run typecheck && npm run test && npm run build" + }, + "dependencies": { + "@modelcontextprotocol/server": "2.0.0", + "@octokit/auth-app": "8.3.0", + "@octokit/rest": "22.0.1", + "jose": "6.2.9", + "mcp-handler": "2.1.1", + "next": "16.3.1", + "react": "19.2.4", + "react-dom": "19.2.4", + "yaml": "2.9.0", + "zod": "4.4.3" + }, + "devDependencies": { + "@types/node": "24.10.1", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", + "typescript": "5.9.3", + "vitest": "4.1.10" + } +} diff --git a/roadmap.md b/roadmap.md index 77f5315..b3fd4f0 100644 --- a/roadmap.md +++ b/roadmap.md @@ -1,7 +1,5 @@ -# Roadmap — brainstorming +# Roadmap -This is direction, not a specification. +The template starts deliberately narrow: portable task operations, explicit capability grants, evidence-based completion, and an optional user-owned MCP. -The home could evolve from a few identity and goal files into an agent with a real role, recurring responsibilities, memory of completed work, reusable skills, and a reliable rhythm. Over time it may feel less like a configured tool and more like a coworker or employee with a clear purpose and history. - -The next step is not more architecture. It is finding the first useful job. +Possible later work belongs in issues and should earn its way into the product through real use. V1 intentionally excludes a central service, multi-tenant database, arbitrary executor, and npm CLI. diff --git a/scripts/validate.mjs b/scripts/validate.mjs new file mode 100644 index 0000000..9bb0229 --- /dev/null +++ b/scripts/validate.mjs @@ -0,0 +1,64 @@ +import { execFileSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { parse as parseYaml } from "yaml"; + +const read = (path) => readFileSync(path, "utf8"); +const json = (path) => JSON.parse(read(path)); +const fail = (message) => { throw new Error(message); }; + +const packageJson = json("package.json"); +const codex = json(".codex-plugin/plugin.json"); +const claude = json(".claude-plugin/plugin.json"); +const marketplace = json(".claude-plugin/marketplace.json"); +const versions = [packageJson.version, codex.version, claude.version, marketplace.plugins[0]?.version]; + +if (!versions.every((version) => version === versions[0])) { + fail(`Version mismatch: ${versions.join(", ")}`); +} +if (!/^\d+\.\d+\.\d+$/.test(versions[0])) fail(`Invalid semantic version: ${versions[0]}`); +if (codex.name !== "agent-home" || claude.name !== "agent-home" || marketplace.plugins[0]?.name !== "agent-home") { + fail("Plugin names must all be agent-home"); +} +if (codex.license !== "Apache-2.0" || claude.license !== "Apache-2.0") fail("Plugin license mismatch"); +if (marketplace.plugins[0]?.source !== "./") fail("Marketplace must install the repository root"); + +const skillText = read("skills/agent-home/SKILL.md"); +const frontmatterMatch = skillText.match(/^---\n([\s\S]*?)\n---\n/); +if (!frontmatterMatch) fail("Skill frontmatter is missing"); +const frontmatter = parseYaml(frontmatterMatch[1]); +if (Object.keys(frontmatter).sort().join(",") !== "description,name") fail("Skill frontmatter may contain only name and description"); +if (frontmatter.name !== "agent-home" || !frontmatter.description?.includes("create a task")) { + fail("Skill metadata does not cover the agent-home task triggers"); +} + +const manifest = parseYaml(read("agent-home.yml")); +if (manifest.version !== 1) fail("agent-home.yml version must be 1"); +if (manifest.autonomy?.default !== "deny") fail("Autonomy must default to deny"); +if (manifest.autonomy?.audit !== "issue-comments-required") fail("Issue comments must be required for audit"); +for (const [capability, decision] of Object.entries(manifest.autonomy?.capabilities ?? {})) { + if (!/^[a-z][a-z0-9._-]+$/.test(capability)) fail(`Invalid capability name: ${capability}`); + if (!["allow", "deny"].includes(decision)) fail(`Invalid capability decision for ${capability}`); +} + +const issueTemplate = parseYaml(read(".github/ISSUE_TEMPLATE/agent-task.yml")); +if (!issueTemplate.labels?.includes("agent:inbox")) fail("Agent task template must apply agent:inbox"); +const requiredFields = ["outcome", "constraints", "acceptance", "priority", "capabilities"]; +const actualFields = issueTemplate.body?.map((field) => field.id).filter(Boolean) ?? []; +for (const field of requiredFields) if (!actualFields.includes(field)) fail(`Issue template is missing ${field}`); + +const trackedFiles = execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard"], { encoding: "utf8" }) + .trim().split("\n").filter(Boolean); +const secretPatterns = [ + /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/, + /\bgh[pousr]_[A-Za-z0-9]{20,}\b/, + /\bAKIA[0-9A-Z]{16}\b/, + /^\s*[A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|API_KEY)[A-Z0-9_]*\s*=\s*[^\s#]+/m, +]; +for (const path of trackedFiles) { + if (path === "package-lock.json") continue; + let content; + try { content = read(path); } catch { continue; } + for (const pattern of secretPatterns) if (pattern.test(content)) fail(`Possible secret in ${path}: ${pattern}`); +} + +console.log(`Validated agent-home ${versions[0]}: manifests, Skill, task schema, capability policy, and secret patterns.`); diff --git a/scripts/verify-inspector-output.mjs b/scripts/verify-inspector-output.mjs new file mode 100644 index 0000000..341c7fc --- /dev/null +++ b/scripts/verify-inspector-output.mjs @@ -0,0 +1,35 @@ +import { readFileSync } from "node:fs"; + +const expected = [ + "block_task", + "claim_task", + "complete_task", + "create_task", + "get_home_context", + "get_task", + "list_tasks", + "update_task", +]; + +const document = JSON.parse(readFileSync(process.argv[2], "utf8")); +const arrays = []; +function visit(value) { + if (Array.isArray(value)) { + if (value.every((item) => item && typeof item === "object" && typeof item.name === "string")) arrays.push(value); + value.forEach(visit); + } else if (value && typeof value === "object") { + Object.values(value).forEach(visit); + } +} +visit(document); + +const tools = arrays.find((items) => expected.every((name) => items.some((item) => item.name === name))); +if (!tools) throw new Error("Inspector output does not contain all eight agent-home tools"); +const names = tools.map((tool) => tool.name).sort(); +if (JSON.stringify(names) !== JSON.stringify(expected)) throw new Error(`Unexpected tool surface: ${names.join(", ")}`); +for (const tool of tools) { + if (!tool.inputSchema) throw new Error(`${tool.name} is missing inputSchema`); + if (!tool.annotations) throw new Error(`${tool.name} is missing tool annotations`); + if (!tool._meta?.securitySchemes) throw new Error(`${tool.name} is missing OAuth security metadata`); +} +console.log("MCP Inspector confirmed eight narrow tools with schemas, annotations, and OAuth metadata."); diff --git a/skills/README.md b/skills/README.md index 89b48aa..0529a25 100644 --- a/skills/README.md +++ b/skills/README.md @@ -1,3 +1,3 @@ # Skills -Reusable workflows live here. Add one when a task repeats and the saved instructions make the next run meaningfully better. +Portable Skills live in this directory. Each Skill has one canonical `SKILL.md`; client manifests point here instead of duplicating instructions. diff --git a/skills/agent-home/SKILL.md b/skills/agent-home/SKILL.md new file mode 100644 index 0000000..685036e --- /dev/null +++ b/skills/agent-home/SKILL.md @@ -0,0 +1,43 @@ +--- +name: agent-home +description: Operate a private agent-home whose GitHub Issues are the canonical task ledger. Use when asked to add or create a task, choose what to work on, list inbox or blocked work, claim or continue task #N, record progress, block work, complete a task, or report agent-home context across ChatGPT, Codex, Claude Code, or ZCode. +--- + +# Agent Home + +Operate the configured private repository as an auditable task home. + +## Establish context + +1. Read `agent-home.yml`, `identity.md`, `goal.md`, and repository instructions when locally available. +2. Reject placeholder instance values. Never infer a repository from unrelated workspace state. +3. Resolve task access in this order: + - configured agent-home MCP; + - native GitHub connector/tools; + - local `gh` plus Git repository access. +4. Never request a token in chat. If no route can perform the operation, state the missing route and stop. + +Read [task-contract.md](references/task-contract.md) before mutating tasks. Read [capabilities.md](references/capabilities.md) before taking actions beyond task reads. + +## Operate tasks + +- Create one issue per outcome with desired outcome, constraints, acceptance criteria, priority, and named capability grants. Apply `agent:inbox`. +- Choose work from `agent:active` first, then the highest-priority actionable `agent:inbox` issue. Do not invent work when no useful task exists. +- Claim by applying only `agent:active` and adding an audit comment. +- Continue from the issue body, comments, linked PRs, and artifacts. Treat chat summaries as hints, not canonical state. +- Record only material progress. Include action, result, and evidence. +- Block when a decision, access route, host capability, or policy grant is missing. Apply only `agent:blocked` and name the exact unblock condition. +- Complete only when acceptance criteria are met. Add an evidence-based final comment with relevant PR/artifact links, apply only `agent:done`, and close the issue. + +## Enforce autonomy + +Treat `autonomy.default: deny` as authoritative. A capability is usable only when both conditions hold: + +1. `agent-home.yml` explicitly allows it; and +2. the active host actually exposes and authenticates it. + +Do not convert a task-level grant into broader instance permission. Do not work around a denial through shell, browser, HTTP, another plugin, or a different credential path. + +## Report + +Lead with task number and resulting state. Distinguish `verified`, `partial`, `blocked`, and `proposed`. Link the canonical issue and evidence when the runtime can resolve URLs. diff --git a/skills/agent-home/agents/openai.yaml b/skills/agent-home/agents/openai.yaml new file mode 100644 index 0000000..ddb983a --- /dev/null +++ b/skills/agent-home/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "Agent Home" + short_description: "Operate a private agent task ledger" + default_prompt: "Use $agent-home to add this task to my agent home." +policy: + allow_implicit_invocation: true diff --git a/skills/agent-home/references/capabilities.md b/skills/agent-home/references/capabilities.md new file mode 100644 index 0000000..f70ac50 --- /dev/null +++ b/skills/agent-home/references/capabilities.md @@ -0,0 +1,15 @@ +# Capability policy + +Capabilities use dotted names. The template declares common names; private instances may add host-specific MCP/tool namespaces. + +- `github.issues.read` — read issue task state +- `github.issues.write` — create, label, comment on, and close task issues +- `github.pull_requests.write` — create or update pull requests +- `github.contents.write` — modify repository content through GitHub or local Git +- `vercel.deploy` — create, promote, or roll back deployments +- `browser.use` — operate a host-provided browser +- `computer.use` — operate a host-provided computer UI + +An `allow` value is policy permission, not a credential or guarantee that the host provides the capability. `deny`, omission, a missing host tool, failed authentication, or a repository mismatch all mean unavailable. + +Never store secrets in the manifest, task body, issue comments, logs, or tool output. diff --git a/skills/agent-home/references/task-contract.md b/skills/agent-home/references/task-contract.md new file mode 100644 index 0000000..ec38b6d --- /dev/null +++ b/skills/agent-home/references/task-contract.md @@ -0,0 +1,42 @@ +# Task contract + +## Required issue body + +```markdown +## Desired outcome + + +## Constraints + + +## Acceptance criteria +- [ ] + +## Priority + + +## Enabled capability grants +- `github.issues.write` +``` + +Task grants narrow the instance-level allowlist; they never expand it. + +## State labels + +Use exactly one: + +- `agent:inbox` — actionable but unclaimed +- `agent:active` — currently owned +- `agent:blocked` — cannot proceed; comment contains the unblock condition +- `agent:done` — acceptance criteria met and issue closed + +## Audit comment + +```markdown +### Agent event +- Action: +- Result: +- Evidence: +``` + +Completion evidence must be concrete. A claim that tests passed should name the command or check; repository work should link its PR or commit; an artifact should have a stable location. diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..bd752db --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,82 @@ +import type { AuthInfo } from "@modelcontextprotocol/server"; +import { createRemoteJWKSet, jwtVerify, type JWTVerifyGetKey } from "jose"; +import type { AgentHomeManifest } from "./config"; +import { assertConfigured } from "./config"; + +export interface AuthSettings { + issuer: string; + audience: string; + githubClaim: string; + resourceUrl: string; +} + +export class OAuthScopeError extends Error { + constructor(readonly scope: "tasks:read" | "tasks:write") { + super(`OAuth scope required: ${scope}`); + } +} + +function requiredEnv(name: string): string { + const value = process.env[name]?.trim(); + if (!value) throw new Error(`${name} is not configured`); + return value; +} + +export function loadAuthSettings(): AuthSettings { + const issuer = requiredEnv("AUTH0_ISSUER").replace(/\/?$/, "/"); + const baseUrl = requiredEnv("AGENT_HOME_URL").replace(/\/$/, ""); + return { + issuer, + audience: requiredEnv("AUTH0_AUDIENCE"), + githubClaim: process.env.AUTH0_GITHUB_CLAIM?.trim() || "https://agent-home.dev/github_login", + resourceUrl: `${baseUrl}/api/mcp`, + }; +} + +export async function verifyAgentToken( + token: string, + manifest: AgentHomeManifest, + settings: AuthSettings, + getKey: JWTVerifyGetKey | CryptoKey = createRemoteJWKSet(new URL(".well-known/jwks.json", settings.issuer)), +): Promise { + assertConfigured(manifest); + const { payload } = await jwtVerify(token, getKey, { + issuer: settings.issuer, + audience: settings.audience, + }); + + const githubLogin = payload[settings.githubClaim]; + if (typeof githubLogin !== "string" || !githubLogin.trim()) { + throw new Error(`Access token is missing the ${settings.githubClaim} claim`); + } + const allowed = manifest.instance.github.allowed_logins.some( + (login) => login.toLowerCase() === githubLogin.toLowerCase(), + ); + if (!allowed) throw new Error("Authenticated GitHub identity is not allowed for this agent home"); + + const scopes = typeof payload.scope === "string" ? payload.scope.split(/\s+/).filter(Boolean) : []; + const clientId = typeof payload.azp === "string" + ? payload.azp + : typeof payload.client_id === "string" + ? payload.client_id + : String(payload.sub ?? "unknown-client"); + + return { + token, + clientId, + scopes, + expiresAt: payload.exp, + resource: new URL(settings.resourceUrl), + extra: { githubLogin }, + }; +} + +export function requireScope(authInfo: AuthInfo | undefined, scope: "tasks:read" | "tasks:write"): void { + if (!authInfo?.scopes.includes(scope)) throw new OAuthScopeError(scope); +} + +export function authenticatedLogin(authInfo: AuthInfo | undefined): string { + const login = authInfo?.extra?.githubLogin; + if (typeof login !== "string" || !login) throw new Error("Authenticated GitHub login is unavailable"); + return login; +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..8ece87d --- /dev/null +++ b/src/config.ts @@ -0,0 +1,50 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { parse } from "yaml"; +import { z } from "zod"; + +export const capabilitySchema = z.string().regex(/^[a-z][a-z0-9._-]+$/); + +export const manifestSchema = z.object({ + version: z.literal(1), + instance: z.object({ + github: z.object({ + owner: z.string().min(1), + repository: z.string().min(1), + allowed_logins: z.array(z.string().min(1)).min(1), + }), + }), + autonomy: z.object({ + default: z.literal("deny"), + audit: z.literal("issue-comments-required"), + capabilities: z.record(capabilitySchema, z.enum(["allow", "deny"])), + }), + external_capabilities: z.array(capabilitySchema).default([]), +}); + +export type AgentHomeManifest = z.infer; + +export function loadManifest(path = process.env.AGENT_HOME_MANIFEST ?? join(process.cwd(), "agent-home.yml")): AgentHomeManifest { + return manifestSchema.parse(parse(readFileSync(path, "utf8"))); +} + +export function manifestIsConfigured(manifest: AgentHomeManifest): boolean { + const github = manifest.instance.github; + return github.owner !== "replace-me" + && github.repository !== "replace-me" + && !github.allowed_logins.includes("replace-me"); +} + +export function assertConfigured(manifest: AgentHomeManifest): void { + if (!manifestIsConfigured(manifest)) { + throw new Error("agent-home.yml still contains template placeholders"); + } +} + +export function requireCapability(manifest: AgentHomeManifest, capability: string): void { + const explicitlyAllowed = manifest.autonomy.capabilities[capability] === "allow" + || manifest.external_capabilities.includes(capability); + if (!explicitlyAllowed) { + throw new Error(`Capability denied by agent-home.yml: ${capability}`); + } +} diff --git a/src/github.ts b/src/github.ts new file mode 100644 index 0000000..dc6b426 --- /dev/null +++ b/src/github.ts @@ -0,0 +1,112 @@ +import { Octokit } from "@octokit/rest"; +import { createAppAuth } from "@octokit/auth-app"; + +export type IssueState = "open" | "closed"; + +export interface TaskIssue { + number: number; + title: string; + body: string; + state: IssueState; + labels: string[]; + htmlUrl: string; + updatedAt: string; +} + +export interface IssueGateway { + create(input: { title: string; body: string; labels: string[] }): Promise; + get(number: number): Promise; + list(input: { state: "open" | "closed" | "all"; labels?: string[] }): Promise; + update(number: number, input: { state?: IssueState; labels?: string[] }): Promise; + comment(number: number, body: string): Promise; +} + +function normalizeIssue(issue: { + number: number; + title: string; + body?: string | null; + state: string; + labels: Array; + html_url: string; + updated_at: string; + pull_request?: unknown; +}): TaskIssue { + return { + number: issue.number, + title: issue.title, + body: issue.body ?? "", + state: issue.state === "closed" ? "closed" : "open", + labels: issue.labels.flatMap((label) => typeof label === "string" ? [label] : label.name ? [label.name] : []), + htmlUrl: issue.html_url, + updatedAt: issue.updated_at, + }; +} + +export class GitHubIssueGateway implements IssueGateway { + private readonly octokit: Octokit; + + constructor( + private readonly owner: string, + private readonly repo: string, + token = process.env.GITHUB_TOKEN, + ) { + const appId = process.env.GITHUB_APP_ID?.trim(); + const installationId = process.env.GITHUB_APP_INSTALLATION_ID?.trim(); + const privateKey = process.env.GITHUB_APP_PRIVATE_KEY?.replace(/\\n/g, "\n"); + const appValues = [appId, installationId, privateKey]; + if (token && appValues.some(Boolean)) { + throw new Error("Configure a GitHub App or GITHUB_TOKEN, not both"); + } + if (appValues.some(Boolean) && !appValues.every(Boolean)) { + throw new Error("GitHub App configuration is incomplete"); + } + if (appId && installationId && privateKey) { + this.octokit = new Octokit({ + authStrategy: createAppAuth, + auth: { appId, installationId, privateKey }, + }); + } else if (token) { + this.octokit = new Octokit({ auth: token }); + } else { + throw new Error("Configure either a GitHub App or GITHUB_TOKEN in this deployment"); + } + } + + async create(input: { title: string; body: string; labels: string[] }): Promise { + const { data } = await this.octokit.issues.create({ owner: this.owner, repo: this.repo, ...input }); + return normalizeIssue(data); + } + + async get(number: number): Promise { + const { data } = await this.octokit.issues.get({ owner: this.owner, repo: this.repo, issue_number: number }); + if ("pull_request" in data) throw new Error(`#${number} is a pull request, not an agent task`); + return normalizeIssue(data); + } + + async list(input: { state: "open" | "closed" | "all"; labels?: string[] }): Promise { + const items = await this.octokit.paginate(this.octokit.issues.listForRepo, { + owner: this.owner, + repo: this.repo, + state: input.state, + labels: input.labels?.join(","), + per_page: 100, + sort: "updated", + direction: "desc", + }); + return items.filter((item) => !("pull_request" in item)).map(normalizeIssue); + } + + async update(number: number, input: { state?: IssueState; labels?: string[] }): Promise { + const { data } = await this.octokit.issues.update({ + owner: this.owner, + repo: this.repo, + issue_number: number, + ...input, + }); + return normalizeIssue(data); + } + + async comment(number: number, body: string): Promise { + await this.octokit.issues.createComment({ owner: this.owner, repo: this.repo, issue_number: number, body }); + } +} diff --git a/src/mcp.ts b/src/mcp.ts new file mode 100644 index 0000000..39bde2c --- /dev/null +++ b/src/mcp.ts @@ -0,0 +1,219 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import type { McpServer, ServerContext } from "@modelcontextprotocol/server"; +import { z } from "zod"; +import { authenticatedLogin, OAuthScopeError, requireScope } from "./auth"; +import type { AgentHomeManifest } from "./config"; +import { GitHubIssueGateway } from "./github"; +import { TaskService, taskLabels } from "./tasks"; + +const readSecurity = [{ type: "oauth2", scopes: ["tasks:read"] }]; +const writeSecurity = [{ type: "oauth2", scopes: ["tasks:write"] }]; + +function text(value: unknown) { + return { content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }] }; +} + +function errorResult(error: unknown) { + const message = error instanceof Error ? error.message : "Unknown task error"; + const result: { + isError: true; + content: Array<{ type: "text"; text: string }>; + _meta?: Record; + } = { isError: true, content: [{ type: "text", text: message }] }; + if (error instanceof OAuthScopeError) { + const baseUrl = process.env.AGENT_HOME_URL?.replace(/\/$/, "") ?? ""; + result._meta = { + "mcp/www_authenticate": [ + `Bearer scope="${error.scope}", resource_metadata="${baseUrl}/.well-known/oauth-protected-resource"`, + ], + }; + } + return result; +} + +function service(manifest: AgentHomeManifest, ctx: ServerContext): TaskService { + const login = authenticatedLogin(ctx.http?.authInfo); + return new TaskService( + manifest, + new GitHubIssueGateway(manifest.instance.github.owner, manifest.instance.github.repository), + login, + ); +} + +function toolMeta(scopes: string[]) { + return { securitySchemes: [{ type: "oauth2", scopes }] }; +} + +export function registerAgentHomeTools(server: McpServer, manifest: AgentHomeManifest): void { + server.registerTool( + "get_home_context", + { + title: "Get agent-home context", + description: "Read the configured identity, goal, repository, and allowed capability names.", + inputSchema: z.object({}), + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, + _meta: toolMeta(["tasks:read"]), + }, + async (_input, ctx) => { + try { + requireScope(ctx.http?.authInfo, "tasks:read"); + return text({ + repository: `${manifest.instance.github.owner}/${manifest.instance.github.repository}`, + identity: readFileSync(join(process.cwd(), "identity.md"), "utf8"), + goal: readFileSync(join(process.cwd(), "goal.md"), "utf8"), + allowedCapabilities: Object.entries(manifest.autonomy.capabilities) + .filter(([, value]) => value === "allow") + .map(([name]) => name), + }); + } catch (error) { return errorResult(error); } + }, + ); + + server.registerTool( + "create_task", + { + title: "Create task", + description: "Create one GitHub issue in agent:inbox with an explicit task contract.", + inputSchema: z.object({ + title: z.string().min(3), + desired_outcome: z.string().min(3), + constraints: z.string().min(1), + acceptance_criteria: z.array(z.string().min(1)).min(1), + priority: z.enum(["critical", "high", "normal", "low"]).default("normal"), + capability_grants: z.array(z.string().regex(/^[a-z][a-z0-9._-]+$/)).default(["github.issues.write"]), + }), + annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, + _meta: toolMeta(["tasks:write"]), + }, + async (input, ctx) => { + try { + requireScope(ctx.http?.authInfo, "tasks:write"); + return text(await service(manifest, ctx).createTask({ + title: input.title, + desiredOutcome: input.desired_outcome, + constraints: input.constraints, + acceptanceCriteria: input.acceptance_criteria, + priority: input.priority, + capabilityGrants: input.capability_grants, + })); + } catch (error) { return errorResult(error); } + }, + ); + + server.registerTool( + "list_tasks", + { + title: "List tasks", + description: "List agent tasks, optionally filtered by one lifecycle state.", + inputSchema: z.object({ status: z.enum(taskLabels).optional() }), + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, + _meta: toolMeta(["tasks:read"]), + }, + async ({ status }, ctx) => { + try { + requireScope(ctx.http?.authInfo, "tasks:read"); + return text(await service(manifest, ctx).listTasks(status)); + } catch (error) { return errorResult(error); } + }, + ); + + server.registerTool( + "get_task", + { + title: "Get task", + description: "Read one canonical task issue.", + inputSchema: z.object({ issue_number: z.number().int().positive() }), + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, + _meta: toolMeta(["tasks:read"]), + }, + async ({ issue_number }, ctx) => { + try { + requireScope(ctx.http?.authInfo, "tasks:read"); + return text(await service(manifest, ctx).getTask(issue_number)); + } catch (error) { return errorResult(error); } + }, + ); + + server.registerTool( + "claim_task", + { + title: "Claim task", + description: "Move an open task to agent:active and add an audit event.", + inputSchema: z.object({ issue_number: z.number().int().positive() }), + annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, + _meta: toolMeta(["tasks:write"]), + }, + async ({ issue_number }, ctx) => { + try { + requireScope(ctx.http?.authInfo, "tasks:write"); + return text(await service(manifest, ctx).claimTask(issue_number)); + } catch (error) { return errorResult(error); } + }, + ); + + server.registerTool( + "update_task", + { + title: "Update task", + description: "Record material progress and evidence as an audit event.", + inputSchema: z.object({ + issue_number: z.number().int().positive(), + progress: z.string().min(1), + evidence: z.string().default(""), + }), + annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, + _meta: toolMeta(["tasks:write"]), + }, + async ({ issue_number, progress, evidence }, ctx) => { + try { + requireScope(ctx.http?.authInfo, "tasks:write"); + return text(await service(manifest, ctx).updateTask(issue_number, progress, evidence)); + } catch (error) { return errorResult(error); } + }, + ); + + server.registerTool( + "block_task", + { + title: "Block task", + description: "Move a task to agent:blocked and record the exact unblock condition.", + inputSchema: z.object({ + issue_number: z.number().int().positive(), + reason: z.string().min(1), + unblock_condition: z.string().min(1), + }), + annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, + _meta: toolMeta(["tasks:write"]), + }, + async ({ issue_number, reason, unblock_condition }, ctx) => { + try { + requireScope(ctx.http?.authInfo, "tasks:write"); + return text(await service(manifest, ctx).blockTask(issue_number, reason, unblock_condition)); + } catch (error) { return errorResult(error); } + }, + ); + + server.registerTool( + "complete_task", + { + title: "Complete task", + description: "Post final evidence, apply agent:done, and close a task.", + inputSchema: z.object({ + issue_number: z.number().int().positive(), + evidence: z.array(z.string().min(1)).min(1), + links: z.array(z.string().url()).default([]), + }), + annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }, + _meta: toolMeta(["tasks:write"]), + }, + async ({ issue_number, evidence, links }, ctx) => { + try { + requireScope(ctx.http?.authInfo, "tasks:write"); + return text(await service(manifest, ctx).completeTask(issue_number, evidence, links)); + } catch (error) { return errorResult(error); } + }, + ); +} + +export const declaredSecuritySchemes = { readSecurity, writeSecurity }; diff --git a/src/tasks.ts b/src/tasks.ts new file mode 100644 index 0000000..60e01fc --- /dev/null +++ b/src/tasks.ts @@ -0,0 +1,151 @@ +import type { AgentHomeManifest } from "./config"; +import { requireCapability } from "./config"; +import type { IssueGateway, TaskIssue } from "./github"; + +export const taskLabels = ["agent:inbox", "agent:active", "agent:blocked", "agent:done"] as const; +export type TaskStatus = typeof taskLabels[number]; +export type Priority = "critical" | "high" | "normal" | "low"; + +export interface CreateTaskInput { + title: string; + desiredOutcome: string; + constraints: string; + acceptanceCriteria: string[]; + priority: Priority; + capabilityGrants: string[]; +} + +function bodyFor(input: CreateTaskInput): string { + return [ + "## Desired outcome", + input.desiredOutcome.trim(), + "", + "## Constraints", + input.constraints.trim(), + "", + "## Acceptance criteria", + ...input.acceptanceCriteria.map((criterion) => `- [ ] ${criterion.trim()}`), + "", + "## Priority", + input.priority, + "", + "## Enabled capability grants", + ...input.capabilityGrants.map((capability) => `- \`${capability}\``), + ].join("\n"); +} + +export function capabilityGrantsFrom(body: string): string[] { + const lines = body.split("\n"); + const start = lines.findIndex((line) => /^#{2,3} Enabled capability grants\s*$/i.test(line)); + if (start === -1) return []; + const endOffset = lines.slice(start + 1).findIndex((line) => /^#{2,3} /.test(line)); + const section = lines.slice(start + 1, endOffset === -1 ? undefined : start + 1 + endOffset); + return section.flatMap((line) => { + const match = line.match(/^- (?:\[[xX]\] )?`([a-z][a-z0-9._-]+)`\s*$/); + return match ? [match[1]] : []; + }); +} + +function nextLabels(issue: TaskIssue, status: TaskStatus): string[] { + return [...issue.labels.filter((label) => !taskLabels.includes(label as TaskStatus)), status]; +} + +function event(action: string, result: "verified" | "partial" | "blocked", evidence: string, actor: string): string { + return [ + "### Agent event", + `- Action: ${action}`, + `- Result: ${result}`, + `- Evidence: ${evidence.trim() || "none"}`, + `- Actor: @${actor}`, + ].join("\n"); +} + +export class TaskService { + constructor( + private readonly manifest: AgentHomeManifest, + private readonly gateway: IssueGateway, + private readonly actor: string, + ) {} + + private requireRead(): void { + requireCapability(this.manifest, "github.issues.read"); + } + + private requireWrite(): void { + requireCapability(this.manifest, "github.issues.write"); + } + + private async writableTask(number: number): Promise { + this.requireWrite(); + const issue = await this.gateway.get(number); + if (!capabilityGrantsFrom(issue.body).includes("github.issues.write")) { + throw new Error(`Task #${number} does not grant github.issues.write`); + } + return issue; + } + + async createTask(input: CreateTaskInput): Promise { + this.requireWrite(); + if (!input.capabilityGrants.includes("github.issues.write")) { + throw new Error("A mutable task must explicitly grant github.issues.write"); + } + for (const capability of input.capabilityGrants) requireCapability(this.manifest, capability); + const issue = await this.gateway.create({ + title: input.title.trim(), + body: bodyFor(input), + labels: ["agent:inbox"], + }); + await this.gateway.comment(issue.number, event("create", "verified", "Task created in agent:inbox.", this.actor)); + return issue; + } + + async listTasks(status?: TaskStatus): Promise { + this.requireRead(); + return this.gateway.list({ + state: status === "agent:done" ? "closed" : status ? "open" : "all", + labels: status ? [status] : undefined, + }); + } + + async getTask(number: number): Promise { + this.requireRead(); + return this.gateway.get(number); + } + + async claimTask(number: number): Promise { + const issue = await this.writableTask(number); + if (issue.state === "closed") throw new Error(`Task #${number} is already closed`); + if (issue.labels.includes("agent:active")) return issue; + await this.gateway.comment(number, event("claim", "verified", "Task moved to agent:active.", this.actor)); + return this.gateway.update(number, { labels: nextLabels(issue, "agent:active") }); + } + + async updateTask(number: number, progress: string, evidence = ""): Promise { + const issue = await this.writableTask(number); + if (issue.state === "closed") throw new Error(`Task #${number} is already closed`); + await this.gateway.comment(number, event("progress", "partial", `${progress.trim()}${evidence ? ` — ${evidence.trim()}` : ""}`, this.actor)); + return this.gateway.get(number); + } + + async blockTask(number: number, reason: string, unblockCondition: string): Promise { + const issue = await this.writableTask(number); + if (issue.state === "closed") throw new Error(`Task #${number} is already closed`); + await this.gateway.comment(number, event("block", "blocked", `${reason.trim()} Unblock when: ${unblockCondition.trim()}`, this.actor)); + return this.gateway.update(number, { labels: nextLabels(issue, "agent:blocked") }); + } + + async completeTask(number: number, evidence: string[], links: string[] = []): Promise { + const issue = await this.writableTask(number); + if (issue.state === "closed" && issue.labels.includes("agent:done")) return issue; + if (issue.state === "closed") throw new Error(`Task #${number} is already closed without agent:done`); + if (evidence.length === 0 || evidence.some((item) => !item.trim())) { + throw new Error("Completion requires at least one concrete evidence item"); + } + const evidenceText = [ + ...evidence.map((item) => item.trim()), + ...links.map((link) => link.trim()), + ].join("; "); + await this.gateway.comment(number, event("complete", "verified", evidenceText, this.actor)); + return this.gateway.update(number, { state: "closed", labels: nextLabels(issue, "agent:done") }); + } +} diff --git a/tests/auth.test.ts b/tests/auth.test.ts new file mode 100644 index 0000000..15a518f --- /dev/null +++ b/tests/auth.test.ts @@ -0,0 +1,48 @@ +import { SignJWT, generateKeyPair } from "jose"; +import { describe, expect, it } from "vitest"; +import { requireScope, verifyAgentToken, type AuthSettings } from "../src/auth"; +import { manifest } from "./fixtures"; + +const settings: AuthSettings = { + issuer: "https://tenant.example.auth0.com/", + audience: "https://home.example/api/mcp", + githubClaim: "https://agent-home.dev/github_login", + resourceUrl: "https://home.example/api/mcp", +}; + +async function tokenFor(login: string, scope = "tasks:read tasks:write") { + const { privateKey, publicKey } = await generateKeyPair("RS256"); + const token = await new SignJWT({ + scope, + [settings.githubClaim]: login, + }) + .setProtectedHeader({ alg: "RS256" }) + .setIssuer(settings.issuer) + .setAudience(settings.audience) + .setSubject("github|123") + .setIssuedAt() + .setExpirationTime("5m") + .sign(privateKey); + return { token, publicKey }; +} + +describe("hosted MCP authorization", () => { + it("accepts the configured GitHub identity, audience, issuer, and scopes", async () => { + const { token, publicKey } = await tokenFor("OwNeR"); + const auth = await verifyAgentToken(token, manifest(), settings, publicKey); + expect(auth.extra?.githubLogin).toBe("OwNeR"); + expect(auth.scopes).toEqual(["tasks:read", "tasks:write"]); + expect(() => requireScope(auth, "tasks:write")).not.toThrow(); + }); + + it("rejects a valid token for the wrong GitHub owner", async () => { + const { token, publicKey } = await tokenFor("intruder"); + await expect(verifyAgentToken(token, manifest(), settings, publicKey)).rejects.toThrow("not allowed"); + }); + + it("enforces individual OAuth scopes", async () => { + const { token, publicKey } = await tokenFor("owner", "tasks:read"); + const auth = await verifyAgentToken(token, manifest(), settings, publicKey); + expect(() => requireScope(auth, "tasks:write")).toThrow("OAuth scope required"); + }); +}); diff --git a/tests/config.test.ts b/tests/config.test.ts new file mode 100644 index 0000000..0f1c5aa --- /dev/null +++ b/tests/config.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from "vitest"; +import { manifestIsConfigured, manifestSchema, requireCapability } from "../src/config"; +import { manifest } from "./fixtures"; + +describe("agent-home manifest", () => { + it("accepts a configured deny-by-default instance", () => { + const parsed = manifestSchema.parse(manifest()); + expect(manifestIsConfigured(parsed)).toBe(true); + expect(parsed.autonomy.default).toBe("deny"); + }); + + it("rejects template placeholders at runtime readiness", () => { + const value = manifest(); + value.instance.github.owner = "replace-me"; + expect(manifestIsConfigured(value)).toBe(false); + }); + + it("denies omitted and denied capabilities", () => { + expect(() => requireCapability(manifest(), "github.pull_requests.write")).toThrow("Capability denied"); + expect(() => requireCapability(manifest(), "browser.use")).toThrow("Capability denied"); + }); + + it("allows an explicitly named external host capability", () => { + const value = manifest(); + value.external_capabilities = ["linear.tasks.write"]; + expect(() => requireCapability(value, "linear.tasks.write")).not.toThrow(); + }); +}); diff --git a/tests/data/agent-home.configured.yml b/tests/data/agent-home.configured.yml new file mode 100644 index 0000000..0449e48 --- /dev/null +++ b/tests/data/agent-home.configured.yml @@ -0,0 +1,14 @@ +version: 1 +instance: + github: + owner: "example-owner" + repository: "private-agent-home" + allowed_logins: + - "example-owner" +autonomy: + default: "deny" + audit: "issue-comments-required" + capabilities: + github.issues.read: "allow" + github.issues.write: "allow" +external_capabilities: [] diff --git a/tests/fixtures.ts b/tests/fixtures.ts new file mode 100644 index 0000000..293e23a --- /dev/null +++ b/tests/fixtures.ts @@ -0,0 +1,25 @@ +import type { AgentHomeManifest } from "../src/config"; + +export function manifest(overrides: Partial = {}): AgentHomeManifest { + return { + version: 1, + instance: { + github: { + owner: "owner", + repository: "private-home", + allowed_logins: ["owner"], + }, + }, + autonomy: { + default: "deny", + audit: "issue-comments-required", + capabilities: { + "github.issues.read": "allow", + "github.issues.write": "allow", + "github.pull_requests.write": "deny", + ...overrides, + }, + }, + external_capabilities: [], + }; +} diff --git a/tests/mcp.test.ts b/tests/mcp.test.ts new file mode 100644 index 0000000..1019182 --- /dev/null +++ b/tests/mcp.test.ts @@ -0,0 +1,143 @@ +import { createServer, type Server } from "node:http"; +import { SignJWT, exportJWK, generateKeyPair, type CryptoKey } from "jose"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +const expectedTools = [ + "block_task", + "claim_task", + "complete_task", + "create_task", + "get_home_context", + "get_task", + "list_tasks", + "update_task", +]; + +let jwksServer: Server; +let privateKey: CryptoKey; +let issuer: string; + +beforeAll(async () => { + const keys = await generateKeyPair("RS256"); + privateKey = keys.privateKey; + const publicJwk = await exportJWK(keys.publicKey); + jwksServer = createServer((request, response) => { + if (request.url === "/.well-known/jwks.json") { + response.setHeader("content-type", "application/json"); + response.end(JSON.stringify({ keys: [{ ...publicJwk, kid: "test", use: "sig", alg: "RS256" }] })); + return; + } + response.statusCode = 404; + response.end(); + }); + await new Promise((resolve) => jwksServer.listen(0, "127.0.0.1", resolve)); + const address = jwksServer.address(); + if (!address || typeof address === "string") throw new Error("JWKS test server did not bind"); + issuer = `http://127.0.0.1:${address.port}/`; + + process.env.AGENT_HOME_MANIFEST = "tests/data/agent-home.configured.yml"; + process.env.AGENT_HOME_URL = "https://home.example"; + process.env.AUTH0_ISSUER = issuer; + process.env.AUTH0_AUDIENCE = "https://home.example/api/mcp"; +}); + +afterAll(async () => { + await new Promise((resolve, reject) => jwksServer.close((error) => error ? reject(error) : resolve())); +}); + +async function accessToken(scope = "tasks:read tasks:write") { + return new SignJWT({ + scope, + "https://agent-home.dev/github_login": "example-owner", + }) + .setProtectedHeader({ alg: "RS256", kid: "test" }) + .setIssuer(issuer) + .setAudience("https://home.example/api/mcp") + .setSubject("github|123") + .setIssuedAt() + .setExpirationTime("5m") + .sign(privateKey); +} + +function rpcRequest(token: string, body: object) { + return new Request("https://home.example/api/mcp", { + method: "POST", + headers: { + authorization: `Bearer ${token}`, + "content-type": "application/json", + accept: "application/json, text/event-stream", + }, + body: JSON.stringify(body), + }); +} + +async function rpcDocument(response: Response) { + const body = await response.text(); + if (!response.headers.get("content-type")?.includes("text/event-stream")) return JSON.parse(body); + const data = body.split("\n").filter((line) => line.startsWith("data: ")).at(-1)?.slice(6); + if (!data) throw new Error(`SSE response contained no data event: ${body}`); + return JSON.parse(data); +} + +describe("Streamable HTTP MCP contract", () => { + it("rejects unauthenticated initialization with an OAuth challenge", async () => { + const { POST } = await import("../app/api/mcp/route"); + const response = await POST(rpcRequest("", { + jsonrpc: "2.0", + id: 1, + method: "initialize", + params: { protocolVersion: "2026-07-28", capabilities: {}, clientInfo: { name: "test", version: "1" } }, + })); + expect(response.status).toBe(401); + expect(response.headers.get("www-authenticate")).toContain("oauth-protected-resource"); + }); + + it("lists exactly eight authenticated task tools with schemas and annotations", async () => { + const { POST } = await import("../app/api/mcp/route"); + const token = await accessToken(); + const initialize = await POST(rpcRequest(token, { + jsonrpc: "2.0", + id: 1, + method: "initialize", + params: { protocolVersion: "2026-07-28", capabilities: {}, clientInfo: { name: "test", version: "1" } }, + })); + expect(initialize.status).toBe(200); + + const response = await POST(rpcRequest(token, { jsonrpc: "2.0", id: 2, method: "tools/list", params: {} })); + expect(response.status).toBe(200); + const document = await rpcDocument(response) as { result: { tools: Array> } }; + const tools = document.result.tools; + expect(tools.map((tool) => tool.name).sort()).toEqual(expectedTools); + for (const tool of tools) { + expect(tool.inputSchema).toBeTruthy(); + expect(tool.annotations).toBeTruthy(); + expect((tool._meta as { securitySchemes?: unknown })?.securitySchemes).toBeTruthy(); + } + }); + + it("returns an MCP OAuth challenge when a tool scope is missing", async () => { + const { POST } = await import("../app/api/mcp/route"); + const token = await accessToken("tasks:read"); + const response = await POST(rpcRequest(token, { + jsonrpc: "2.0", + id: 3, + method: "tools/call", + params: { + name: "create_task", + arguments: { + title: "Test task", + desired_outcome: "Verify scope behavior", + constraints: "No mutation", + acceptance_criteria: ["Challenge is returned"], + priority: "normal", + capability_grants: ["github.issues.write"], + }, + }, + })); + const document = await rpcDocument(response) as { + result: { isError: boolean; _meta: { "mcp/www_authenticate": string[] } }; + }; + expect(document.result.isError).toBe(true); + expect(document.result._meta["mcp/www_authenticate"][0]).toContain("tasks:write"); + }); +}); diff --git a/tests/tasks.test.ts b/tests/tasks.test.ts new file mode 100644 index 0000000..9b13f3b --- /dev/null +++ b/tests/tasks.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, it } from "vitest"; +import type { IssueGateway, TaskIssue } from "../src/github"; +import { capabilityGrantsFrom, TaskService } from "../src/tasks"; +import { manifest } from "./fixtures"; + +class FakeGateway implements IssueGateway { + issues = new Map(); + comments = new Map(); + next = 1; + + async create(input: { title: string; body: string; labels: string[] }) { + const number = this.next++; + const issue: TaskIssue = { + number, + title: input.title, + body: input.body, + state: "open", + labels: input.labels, + htmlUrl: `https://github.com/owner/private-home/issues/${number}`, + updatedAt: new Date(0).toISOString(), + }; + this.issues.set(number, issue); + return structuredClone(issue); + } + + async get(number: number) { + const issue = this.issues.get(number); + if (!issue) throw new Error("Not found"); + return structuredClone(issue); + } + + async list(input: { state: "open" | "closed" | "all"; labels?: string[] }) { + return [...this.issues.values()].filter((issue) => { + const stateMatches = input.state === "all" || issue.state === input.state; + const labelsMatch = !input.labels || input.labels.every((label) => issue.labels.includes(label)); + return stateMatches && labelsMatch; + }).map((issue) => structuredClone(issue)); + } + + async update(number: number, input: { state?: "open" | "closed"; labels?: string[] }) { + const issue = await this.get(number); + const updated = { ...issue, ...input }; + this.issues.set(number, updated); + return structuredClone(updated); + } + + async comment(number: number, body: string) { + this.comments.set(number, [...(this.comments.get(number) ?? []), body]); + } +} + +function taskInput() { + return { + title: "Ship a release checklist", + desiredOutcome: "A reviewable release checklist exists.", + constraints: "Do not deploy or merge.", + acceptanceCriteria: ["Checklist is linked from a pull request"], + priority: "normal" as const, + capabilityGrants: ["github.issues.write"], + }; +} + +describe("task lifecycle", () => { + it("creates, claims, updates, blocks, and completes one canonical issue", async () => { + const gateway = new FakeGateway(); + const service = new TaskService(manifest(), gateway, "owner"); + + const created = await service.createTask(taskInput()); + expect(created.labels).toEqual(["agent:inbox"]); + expect(capabilityGrantsFrom(created.body)).toEqual(["github.issues.write"]); + + const claimed = await service.claimTask(created.number); + expect(claimed.labels).toEqual(["agent:active"]); + + await service.updateTask(created.number, "Drafted the checklist.", "npm test passed"); + const blocked = await service.blockTask(created.number, "Needs owner choice.", "Owner selects release date."); + expect(blocked.labels).toEqual(["agent:blocked"]); + + const completed = await service.completeTask( + created.number, + ["npm run check passed"], + ["https://github.com/owner/private-home/pull/7"], + ); + expect(completed.state).toBe("closed"); + expect(completed.labels).toEqual(["agent:done"]); + expect(gateway.comments.get(created.number)).toHaveLength(5); + expect(gateway.comments.get(created.number)?.at(-1)).toContain("Action: complete"); + expect(gateway.comments.get(created.number)?.at(-1)).toContain("pull/7"); + }); + + it("enforces both instance and task capability grants", async () => { + const gateway = new FakeGateway(); + const denied = new TaskService(manifest({ "github.issues.write": "deny" }), gateway, "owner"); + await expect(denied.createTask(taskInput())).rejects.toThrow("Capability denied"); + + const service = new TaskService(manifest(), gateway, "owner"); + const malformed = await gateway.create({ title: "No grant", body: "## Enabled capability grants\n", labels: ["agent:inbox"] }); + await expect(service.claimTask(malformed.number)).rejects.toThrow("does not grant github.issues.write"); + }); + + it("requires concrete completion evidence", async () => { + const gateway = new FakeGateway(); + const service = new TaskService(manifest(), gateway, "owner"); + const created = await service.createTask(taskInput()); + await expect(service.completeTask(created.number, [])).rejects.toThrow("at least one concrete evidence"); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..67c2626 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": false, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "types": [ + "node" + ] + }, + "include": [ + "next-env.d.ts", + ".next/types/**/*.ts", + "**/*.ts", + "**/*.tsx", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +}