diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json deleted file mode 100644 index 63e2b8b..0000000 --- a/.agents/plugins/marketplace.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "ai-plugin-marketplace", - "owner": { - "name": "AI Plugin Marketplace Template" - }, - "metadata": { - "version": "0.0.1", - "description": "Universal AI Plugin Marketplace — author once, distribute to all platforms" - }, - "plugins": [ - { - "name": "skill-evaluator", - "source": { - "source": "local", - "path": "./plugins/skill-evaluator" - }, - "description": "Evaluate AI skills across model tiers with blind testing and refinement recommendations", - "tags": [ - "evaluation", - "testing", - "skills", - "model-tiers" - ], - "policy": { - "installation": "AVAILABLE", - "authentication": "ON_INSTALL" - } - } - ] -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bca8fb..0728567 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,45 +7,35 @@ on: branches: [main] jobs: - ci: + CI: runs-on: ubuntu-latest + env: + CI: "true" steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 + # version is read from package.json#packageManager — do not duplicate here - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: pnpm - - run: pnpm install --frozen-lockfile + - name: Install dependencies + run: pnpm install --frozen-lockfile - - name: Typecheck - run: pnpm run typecheck + - name: Build plugins + run: pnpm exec aipm build - - name: Lint TypeScript - run: pnpm run lint - - - name: Lint markdown - run: pnpm run lint:md - - - name: Test - run: pnpm run test - - - name: Build hooks - run: pnpm run build:hooks - - - name: Validate plugins - run: pnpm run validate - - - name: Build standalone exports - run: pnpm run build:standalone - - - name: Verify dist/ is up to date + - name: Verify the tree is clean after build (freshness) run: | - if [ -n "$(git status --porcelain -- dist/)" ]; then - echo "::error::dist/ is out of date. Run 'pnpm run build' and commit the changes." - git status -- dist/ + if [ -n "$(git status --porcelain)" ]; then + echo "::error::Working tree is dirty after 'aipm build'. Run 'aipm build' locally and commit the regenerated artifacts." + git status --porcelain + git --no-pager diff exit 1 fi + + - name: Validate plugins + run: pnpm exec aipm validate diff --git a/.gitignore b/.gitignore index 5856bed..bb5dd41 100644 --- a/.gitignore +++ b/.gitignore @@ -146,9 +146,10 @@ vite.config.ts.timestamp-* .claude/**/*.local.* *.local.* -# Build output (dist/ is committed for consumer install — see .gitattributes) +# Build output. Toolkit-generated artifacts (dist/ bundles and per-plugin +# hooks/*.json) are committed for consumer install and the freshness check — +# see .gitattributes and the @ai-plugin-marketplace build contract. dist-ts/ -plugins/*/hooks/*.json # Scratch & manual-test artifacts scratch/ diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index da39e4f..0000000 --- a/.mcp.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mcpServers": {} -} diff --git a/.remarkrc.mjs b/.remarkrc.mjs deleted file mode 100644 index 9ab56a1..0000000 --- a/.remarkrc.mjs +++ /dev/null @@ -1,7 +0,0 @@ -export default { - plugins: [ - "remark-frontmatter", - "remark-preset-lint-consistent", - "remark-preset-lint-recommended", - ], -}; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..70e49a4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing + +## Prerequisites + +This repository consumes the `@ai-plugin-marketplace/*` toolkit from npm. After +cloning, run: + +```bash +pnpm install +``` + +This requires `@ai-plugin-marketplace/cli` and `@ai-plugin-marketplace/core` to +be **published to npm**. A fresh `pnpm install` regenerates `pnpm-lock.yaml` +against the published versions. + +## Workflow + +1. Add or edit a plugin under `plugins//`. Use `aipm scaffold ` to + create a new one or `aipm add-target ` to expand an + existing plugin's support envelope. +2. Run `aipm build` to regenerate toolkit-owned artifacts (hook JSON and the + `dist/` bundles). Commit both your authored sources and the regenerated + output — CI enforces that the tree is clean after a build (freshness). +3. Run `aipm validate` and resolve any hard findings. + +## Upgrading the toolkit + +```bash +pnpm up @ai-plugin-marketplace/cli @ai-plugin-marketplace/core +aipm build +``` + +Commit the result. Minor and patch upgrades never require manifest changes; +major upgrades may ask you to run `aipm migrate`. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..08eb851 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Mike North + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index d0177be..a2f2af9 100644 --- a/README.md +++ b/README.md @@ -1,228 +1,128 @@ -# Universal AI Plugin Marketplace Template +# AI Plugin Marketplace Template -Author AI assistant plugins once and distribute them to all major platforms at the +Author AI assistant plugins once and ship them to multiple host platforms — +**Claude Code, Cursor, Gemini CLI, Kiro, and Vercel Skills CLI** — at the highest fidelity each one accepts. -## Supported Platforms +This repository is a **thin consumer** of the +[`@ai-plugin-marketplace`](https://www.npmjs.com/package/@ai-plugin-marketplace/cli) +toolkit. It contains your plugin sources and a dependency on the toolkit — +nothing else. All validation, scaffolding, and build logic lives in the +versioned `@ai-plugin-marketplace/*` npm packages, so you upgrade your tooling +with a single `pnpm up` and never get stranded on a forked copy of the build +scripts. -| Platform | Fidelity | Status | -|----------|----------|--------| -| [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | Tier 1 (rich plugin) | Supported | -| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Tier 1 (rich extension) | Supported | -| [OpenAI Codex](https://developers.openai.com/codex/plugins/build) | Tier 1 (rich plugin) | Supported | -| [Cursor](https://www.cursor.com/) | Tier 1 (rich plugin) | Supported | -| [Kiro](https://kiro.dev/) | Tier 2 (standalone export) | Supported | -| [Vercel Skills CLI](https://sdk.vercel.ai/docs/ai-sdk-core/agents#skills) | Tier 3 (skills only) | Supported | -| [`npx plugins`](https://www.npmjs.com/package/plugins) | Universal installer | Compatible | +## Getting started -## Installation - -Users can install plugins from this marketplace into Claude Code, Cursor, and Codex with a single command: +This repo is a GitHub template. Click **"Use this template"** (or run +`aipm init` in an empty directory to generate the same layout from scratch), +then: ```bash -npx plugins add owner/repo +pnpm install ``` -This uses the [open plugin format](https://www.npmjs.com/package/plugins) to auto-detect and install all plugins in the repository. +> **Note:** `pnpm install` requires the `@ai-plugin-marketplace/*` packages to be +> published to npm. See [CONTRIBUTING.md](CONTRIBUTING.md). -## Quick Start +## Authoring plugins -### Create a new plugin +### Add a new plugin ```bash -pnpm run scaffold my-plugin +aipm scaffold my-plugin ``` -### Validate all plugins +This creates `plugins/my-plugin/` with an `aipm.config.ts` support envelope and +skeleton manifests for each declared target, and registers the plugin in the +repo-root marketplace registries. + +### Build generated artifacts ```bash -pnpm run validate +aipm build ``` -### Build standalone exports +`aipm build` regenerates every toolkit-owned artifact — per-plugin hook JSON +(`hooks/claude.json`, `hooks/hooks.json`) and the standalone bundles under +`dist/gemini/` and `dist/kiro/`. Both authored sources and generated outputs are +committed, so plugins stay browsable on GitHub and pull-request diffs stay +honest. + +### Validate ```bash -pnpm run build:standalone +aipm validate +``` + +Validation checks the support envelope, every target manifest's schema, +cross-target name consistency, MCP server-key sync, marketplace registration, +and freshness (that the committed generated artifacts match what `aipm build` +would produce). + +`package.json` exposes these as `pnpm build`, `pnpm check`, and +`pnpm scaffold` if you prefer the npm-script entry points. + +## The support envelope + +Each plugin declares the targets it supports in `plugins//aipm.config.ts`: + +```ts +import { defineConfig } from '@ai-plugin-marketplace/core'; + +export default defineConfig({ + version: '0.1.0', + targets: ['claude', 'cursor', 'gemini', 'kiro', 'vercel'], +}); ``` -This generates standalone directories in `dist/` for platforms that require -repo-root manifests (Gemini CLI and Kiro). Claude Code, Cursor, and Codex read -plugins directly from `plugins//` via their marketplace registries. +The toolkit emits artifacts only for declared targets and refuses to validate a +plugin that carries files for a target outside its envelope. To expand a +plugin's envelope, run `aipm add-target ` to scaffold the +skeleton files for a new target, then fill in the manifest fields. -## Repository Structure +## Repository structure ``` -ai-plugin-marketplace-template/ -├── marketplace.json # Open plugin format registry (npx plugins) +. ├── .claude-plugin/ -│ └── marketplace.json # Claude Code marketplace registry +│ └── marketplace.json # Claude Code marketplace registry ├── .cursor-plugin/ -│ └── marketplace.json # Cursor marketplace registry -├── .agents/ -│ └── plugins/ -│ └── marketplace.json # OpenAI Codex marketplace registry +│ └── marketplace.json # Cursor marketplace registry ├── plugins/ -│ └── / # One directory per plugin -│ ├── .claude-plugin/ -│ │ └── plugin.json # Claude Code manifest -│ ├── .cursor-plugin/ -│ │ └── plugin.json # Cursor manifest -│ ├── .codex-plugin/ -│ │ └── plugin.json # OpenAI Codex manifest -│ ├── gemini-extension.json # Gemini CLI manifest -│ ├── POWER.md # Kiro power entry point -│ ├── GEMINI.md # Gemini CLI context file -│ ├── .mcp.json # MCP config (Claude Code / Cursor / Codex) -│ ├── mcp.json # MCP config (Kiro) -│ ├── skills/ # SKILL.md files (universal) -│ ├── agents/ # Agent definitions (.md) -│ ├── rules/ # Rules (.md for Claude, .mdc for Cursor) -│ ├── steering/ # Kiro steering files -│ ├── commands/ # Commands (.md for Claude/Cursor, .toml for Gemini) -│ ├── hooks/ # Hook definitions (claude.yaml source) -│ ├── README.md -│ └── LICENSE -├── src/ -│ ├── validate.ts # Validate plugins and manifests -│ ├── scaffold.ts # Create new plugin from template -│ ├── build-hooks.ts # Convert hooks YAML → per-target JSON -│ └── build-standalone.ts # Generate standalone exports -├── schemas/ # JSON Schemas for plugin and marketplace manifests -├── templates/ # Templates for scaffolding -├── dist/ # Generated standalone repos +│ └── / # One directory per plugin (authored sources) +│ ├── aipm.config.ts # Support envelope + plugin version +│ └── ... # Manifests, agents, skills, commands, hooks, rules +├── dist/ # Generated standalone bundles (committed) │ ├── gemini/ │ └── kiro/ +├── package.json # Depends on @ai-plugin-marketplace/cli + core +├── LICENSE └── README.md ``` -## Platform Compatibility Matrix - -| Feature | Claude Code | Cursor | Gemini CLI | Codex | Kiro | Skills CLI | -|---------|:-----------:|:------:|:----------:|:-----:|:----:|:----------:| -| Fidelity tier | 1 | 1 | 1 | 1 | 2 | 3 | -| Skills (SKILL.md) | native | native | native | native | via steering | native | -| Agents (.md) | native | native | native | — | derived (.kiro/agents) | — | -| Rules | .md | .mdc | — | — | steering/ | — | -| Hooks | claude.json | claude.json | hooks.json | — | .kiro/hooks/ | — | -| Commands | .md | .md/.mdc | .toml | — | — | — | -| MCP Servers | .mcp.json | .mcp.json | gemini-extension.json | .mcp.json | mcp.json | — | -| Interface metadata | — | — | — | interface block | POWER.md | — | -| Marketplace | marketplace.json | marketplace.json | distributed per extension | .agents/plugins/marketplace.json | powers panel | `npx skills find` | -| Multi-plugin repo | yes | yes | no (per-extension repo) | yes | no | yes | - -## How It Works - -> **Platform terminology note:** Claude Code, Cursor, and Codex call these -> *plugins*; Kiro calls them *powers*; Gemini CLI calls them *extensions*; -> Vercel calls them *skills*. This template uses "plugin" as the generic term -> throughout. - -Each plugin is authored once under `plugins//` as a superset of every -component type — `agents/`, `skills/`, `commands/`, `hooks/`, `rules/`, -`steering/`, and MCP config. The build pipeline emits the **richest native -representation** each target tool accepts. Components a given target cannot -express natively are simply absent from its output — they are **not** flattened -into SKILL.md as a fallback. - -### Tier 1: Rich plugins (Claude Code, Cursor, Gemini CLI, OpenAI Codex) - -These platforms accept full plugin packages bundling multiple component types. - -- **Claude Code and Cursor** share the same internal layout — `skills/`, - `agents/`, `hooks/`, `commands/`, `.mcp.json` are identical. Only the - manifest wrapper differs (`.claude-plugin/plugin.json` vs - `.cursor-plugin/plugin.json`). Plugins are referenced directly from the - per-tool root `marketplace.json`. -- **Gemini CLI** ships as an extension with a richer - `gemini-extension.json` (MCP servers, `contextFileName`, `excludeTools`, - `settings`) and native auto-discovered directories for `skills/`, - `agents/*.md`, `commands/*.toml`, `hooks/hooks.json`, and `policies/*.toml`. - `pnpm run build:standalone` writes the self-contained extension to - `dist/gemini//`, with Claude-flavored agent tool names rewritten to - Gemini equivalents (`Read` → `read_file`, `Write` → `write_file`, etc.) and - the hook YAML converted to Gemini's `hooks.json`. -- **OpenAI Codex** reads plugins directly from this repo via - `./.agents/plugins/marketplace.json`, with each plugin's manifest at - `plugins//.codex-plugin/plugin.json`. The Codex manifest carries an - `interface` block (`displayName`, `shortDescription`, `longDescription`, - `category`, `capabilities`, `brandColor`, etc.) used to render the plugin in - the Codex UI. Codex natively supports skills, MCP, apps, and interface - metadata — sub-agents, commands, and hooks are absent from Codex output - (they remain available to the Claude/Cursor/Gemini targets). - -### Tier 2: Standalone export (Kiro) - -Kiro expects its manifest at the repository root. `pnpm run build:standalone` -copies each plugin into `dist/kiro//` with POWER.md, `steering/`, -`skills/`, and auto-converted `.kiro/agents/*.json` configs (generated from the -Claude agent `.md` files). Kiro reads skills via steering context files rather -than natively parsing SKILL.md frontmatter, and has no commands concept. - -### Tier 3: Lossy fallback (Vercel Skills CLI) - -`npx skills add /` auto-discovers every `SKILL.md` in the -repository. Nothing else — sub-agents, commands, hooks, rules, and MCP configs -are **not** surfaced to Skills CLI consumers. Use this tier when a plugin's -value is carried entirely by its skills. - -## OpenAI Codex - -Codex plugins live alongside the other platforms' manifests: - -- Per-plugin manifest: `plugins//.codex-plugin/plugin.json`. -- Repo-root marketplace: `.agents/plugins/marketplace.json` with entries of the - form - ```json - { - "name": "", - "source": { "source": "local", "path": "./plugins/" }, - "policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL" } - } - ``` -- The `interface` block on the plugin manifest drives Codex UI rendering - (`displayName`, `shortDescription`, `longDescription`, `category`, - `capabilities`, `brandColor`, `composerIcon`, `logo`, `screenshots`, - `defaultPrompt`, `websiteURL`, `privacyPolicyURL`, `termsOfServiceURL`). - -After installing a Codex user source pointing at this repo, `codex /plugins` -will list the plugin with its interface metadata. - -## Example Plugin: skill-evaluator +## Upgrading the toolkit + +Because the build logic lives in npm packages, upgrading is a routine +dependency bump — no copied scripts to reconcile: + +```bash +pnpm up @ai-plugin-marketplace/cli @ai-plugin-marketplace/core +aipm build +git commit -am "chore: upgrade @ai-plugin-marketplace" +``` + +A minor or patch toolkit upgrade never requires you to change your plugin +manifests. Major upgrades may ask you to run `aipm migrate`; the CLI tells you +when. + +## Example plugin: skill-evaluator The included `skill-evaluator` plugin demonstrates the full multi-platform -pattern end-to-end. It evaluates AI skills across model tiers (opus → sonnet → -haiku) using blind sub-agent testing. - -See [plugins/skill-evaluator/README.md](plugins/skill-evaluator/README.md) for -details. - -## Creating a Plugin Manually - -If you prefer to create a plugin without the scaffold script: - -1. Create a directory under `plugins/`. -2. Add platform manifests (see the `skill-evaluator` plugin for the Tier 1 - shape). -3. Add your skills, agents, rules, commands, and hooks. -4. Update all root marketplace files: - - `marketplace.json` (open plugin format / `npx plugins`) - - `.claude-plugin/marketplace.json` - - `.cursor-plugin/marketplace.json` - - `.agents/plugins/marketplace.json` -5. Run `pnpm run validate` to verify everything is correct. -6. Run `pnpm run build:standalone` to generate Gemini and Kiro exports. - -## npm Scripts - -| Script | Description | -|--------|-------------| -| `pnpm run validate` | Validate all plugins and manifests | -| `pnpm run scaffold ` | Create a new plugin from templates | -| `pnpm run build:hooks` | Convert `hooks/*.yaml` sources to per-target JSON | -| `pnpm run build:standalone` | Generate standalone Gemini/Kiro exports | -| `pnpm run build` | Typecheck + build hooks + validate + build standalone | -| `pnpm run clean` | Remove dist/ directory | +pattern end-to-end — it evaluates AI skills across model tiers (opus → sonnet → +haiku) using blind sub-agent testing. See +[plugins/skill-evaluator/README.md](plugins/skill-evaluator/README.md). ## License -MIT +MIT — see [LICENSE](LICENSE). diff --git a/dist/gemini/skill-evaluator/gemini-extension.json b/dist/gemini/skill-evaluator/gemini-extension.json index ba9dac7..72a094b 100644 --- a/dist/gemini/skill-evaluator/gemini-extension.json +++ b/dist/gemini/skill-evaluator/gemini-extension.json @@ -1,4 +1,5 @@ { + "schemaVersion": "0.1.0", "name": "skill-evaluator", "version": "0.0.1", "description": "Evaluate AI skills across model tiers with blind testing and refinement recommendations", diff --git a/dist/kiro/skill-evaluator/POWER.md b/dist/kiro/skill-evaluator/POWER.md index fb43abb..fc9f2ae 100644 --- a/dist/kiro/skill-evaluator/POWER.md +++ b/dist/kiro/skill-evaluator/POWER.md @@ -1,4 +1,5 @@ --- +schemaVersion: 0.1.0 name: skill-evaluator description: Evaluate AI skills across model tiers with blind testing and refinement recommendations version: 0.0.1 diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 590dd7d..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import eslint from "@eslint/js"; -import tseslint from "typescript-eslint"; -import prettier from "eslint-config-prettier"; - -export default tseslint.config( - eslint.configs.recommended, - tseslint.configs.strictTypeChecked, - tseslint.configs.stylisticTypeChecked, - prettier, - { - languageOptions: { - parserOptions: { - projectService: true, - tsconfigRootDir: import.meta.dirname, - }, - }, - rules: { - "@typescript-eslint/no-unused-vars": [ - "error", - { - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", - }, - ], - "no-plusplus": "off", - "@typescript-eslint/restrict-template-expressions": [ - "error", - { allowNumber: true }, - ], - }, - }, - { - ignores: ["dist/", "dist-ts/"], - }, -); diff --git a/marketplace.json b/marketplace.json deleted file mode 100644 index 83b3c34..0000000 --- a/marketplace.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "ai-plugin-marketplace", - "owner": { - "name": "AI Plugin Marketplace Template" - }, - "metadata": { - "version": "0.0.1", - "description": "Universal AI Plugin Marketplace — author once, distribute to all platforms" - }, - "plugins": [ - { - "name": "skill-evaluator", - "source": "./plugins/skill-evaluator", - "description": "Evaluate AI skills across model tiers with blind testing and refinement recommendations", - "tags": ["evaluation", "testing", "skills", "model-tiers"] - } - ] -} diff --git a/package.json b/package.json index 4d238d5..f1780df 100644 --- a/package.json +++ b/package.json @@ -1,54 +1,15 @@ { "name": "ai-plugin-marketplace-template", - "version": "0.0.0", - "description": "Author AI assistant plugins once and distribute them to Claude Code, Cursor, Gemini CLI, Kiro, and Vercel Skills CLI", "private": true, "type": "module", + "packageManager": "pnpm@10.30.3", "scripts": { - "validate": "pnpm run build:hooks && tsx src/validate.ts", - "scaffold": "tsx src/scaffold.ts", - "build:standalone": "tsx src/build-standalone.ts", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist dist-ts", - "build:hooks": "tsx src/build-hooks.ts", - "build": "pnpm run typecheck && pnpm run build:hooks && pnpm run validate && pnpm run build:standalone", - "test:codex": "tsx scripts/codex-acceptance.ts preflight", - "test:codex:verify": "tsx scripts/codex-acceptance.ts verify", - "test:codex:cleanup": "tsx scripts/codex-acceptance.ts cleanup", - "lint": "pnpm run lint:ts && pnpm run lint:md", - "lint:ts": "eslint src/", - "lint:md": "remark plugins/ --ext md,mdc --frail --quiet", - "test": "vitest run" + "build": "aipm build", + "check": "aipm validate", + "scaffold": "aipm scaffold" }, - "keywords": [ - "ai", - "plugin", - "marketplace", - "claude-code", - "cursor", - "gemini", - "kiro", - "skills" - ], - "author": "", - "license": "MIT", - "packageManager": "pnpm@10.30.3", "devDependencies": { - "@eslint/js": "^10.0.1", - "@types/node": "^25.3.3", - "eslint": "^10.0.2", - "eslint-config-prettier": "^10.1.8", - "prettier": "^3.8.1", - "remark-cli": "^12.0.1", - "remark-frontmatter": "^5.0.0", - "remark-preset-lint-consistent": "^6.0.1", - "remark-preset-lint-recommended": "^7.0.1", - "rimraf": "^6.1.3", - "tsx": "^4.19.0", - "typescript": "^5.9.3", - "typescript-eslint": "^8.56.1", - "vitest": "^4.0.18", - "yaml": "^2.8.2", - "zod": "^4.3.6" + "@ai-plugin-marketplace/cli": "^0.1.0", + "@ai-plugin-marketplace/core": "^0.1.0" } } diff --git a/plugins/skill-evaluator/.claude-plugin/plugin.json b/plugins/skill-evaluator/.claude-plugin/plugin.json index 486b358..b85ead7 100644 --- a/plugins/skill-evaluator/.claude-plugin/plugin.json +++ b/plugins/skill-evaluator/.claude-plugin/plugin.json @@ -1,4 +1,5 @@ { + "schemaVersion": "0.1.0", "name": "skill-evaluator", "version": "0.0.1", "description": "Evaluate AI skills across model tiers with blind testing and refinement recommendations", diff --git a/plugins/skill-evaluator/.codex-plugin/plugin.json b/plugins/skill-evaluator/.codex-plugin/plugin.json deleted file mode 100644 index 066fb72..0000000 --- a/plugins/skill-evaluator/.codex-plugin/plugin.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "skill-evaluator", - "version": "0.0.1", - "description": "Evaluate AI skills across model tiers with blind testing and refinement recommendations", - "author": { - "name": "AI Plugin Marketplace Template" - }, - "keywords": ["evaluation", "testing", "skills", "model-tiers"], - "skills": "./skills", - "mcpServers": "./.mcp.json", - "interface": { - "displayName": "Skill Evaluator", - "shortDescription": "Blind-test AI skills across opus/sonnet/haiku tiers and produce a refinement report.", - "longDescription": "Skill Evaluator orchestrates blind evaluations of an AI skill across multiple model tiers. It runs each test case through the skill at opus, sonnet, and haiku, compares outputs against expected outcomes, and emits a structured refinement report calling out the lowest-capability tier that passes (the 'clarity floor') along with targeted improvement recommendations.", - "category": "developer-tools", - "capabilities": [ - "Blind evaluation of SKILL.md files across model tiers", - "Automatic pass/fail scoring against expected outcomes", - "Refinement report with root-cause analysis and recommendations" - ], - "defaultPrompt": "Evaluate the skill in ./skills/evaluate-skill against the test cases in ./test-cases." - } -} diff --git a/plugins/skill-evaluator/.cursor-plugin/plugin.json b/plugins/skill-evaluator/.cursor-plugin/plugin.json index 486b358..b85ead7 100644 --- a/plugins/skill-evaluator/.cursor-plugin/plugin.json +++ b/plugins/skill-evaluator/.cursor-plugin/plugin.json @@ -1,4 +1,5 @@ { + "schemaVersion": "0.1.0", "name": "skill-evaluator", "version": "0.0.1", "description": "Evaluate AI skills across model tiers with blind testing and refinement recommendations", diff --git a/plugins/skill-evaluator/POWER.md b/plugins/skill-evaluator/POWER.md index fb43abb..fc9f2ae 100644 --- a/plugins/skill-evaluator/POWER.md +++ b/plugins/skill-evaluator/POWER.md @@ -1,4 +1,5 @@ --- +schemaVersion: 0.1.0 name: skill-evaluator description: Evaluate AI skills across model tiers with blind testing and refinement recommendations version: 0.0.1 diff --git a/plugins/skill-evaluator/aipm.config.ts b/plugins/skill-evaluator/aipm.config.ts new file mode 100644 index 0000000..30f5608 --- /dev/null +++ b/plugins/skill-evaluator/aipm.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from '@ai-plugin-marketplace/core'; + +export default defineConfig({ + version: '0.1.0', + targets: ['claude', 'cursor', 'gemini', 'kiro', 'vercel'], +}); diff --git a/plugins/skill-evaluator/gemini-extension.json b/plugins/skill-evaluator/gemini-extension.json index ba9dac7..72a094b 100644 --- a/plugins/skill-evaluator/gemini-extension.json +++ b/plugins/skill-evaluator/gemini-extension.json @@ -1,4 +1,5 @@ { + "schemaVersion": "0.1.0", "name": "skill-evaluator", "version": "0.0.1", "description": "Evaluate AI skills across model tiers with blind testing and refinement recommendations", diff --git a/plugins/skill-evaluator/hooks/claude.json b/plugins/skill-evaluator/hooks/claude.json new file mode 100644 index 0000000..1716693 --- /dev/null +++ b/plugins/skill-evaluator/hooks/claude.json @@ -0,0 +1,20 @@ +{ + "_generated": { + "by": "@ai-plugin-marketplace/cli", + "source": "hooks/claude.yaml" + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Write", + "description": "Log evaluation report writes to a structured log file", + "hooks": [ + { + "type": "command", + "command": "if echo \"$TOOL_INPUT\" | grep -q 'evaluation-report'; then echo \"[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Evaluation report written\" >> .evaluation-log.jsonl; fi" + } + ] + } + ] + } +} diff --git a/plugins/skill-evaluator/hooks/hooks.json b/plugins/skill-evaluator/hooks/hooks.json new file mode 100644 index 0000000..b011acb --- /dev/null +++ b/plugins/skill-evaluator/hooks/hooks.json @@ -0,0 +1,20 @@ +{ + "_generated": { + "by": "@ai-plugin-marketplace/cli", + "source": "hooks/claude.yaml" + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "write_file", + "description": "Log evaluation report writes to a structured log file", + "hooks": [ + { + "type": "command", + "command": "if echo \"$TOOL_INPUT\" | grep -q 'evaluation-report'; then echo \"[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Evaluation report written\" >> .evaluation-log.jsonl; fi" + } + ] + } + ] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4fc911d..3933624 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,3714 +8,50 @@ importers: .: devDependencies: - '@eslint/js': - specifier: ^10.0.1 - version: 10.0.1(eslint@10.0.2) - '@types/node': - specifier: ^25.3.3 - version: 25.3.3 - eslint: - specifier: ^10.0.2 - version: 10.0.2 - eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@10.0.2) - prettier: - specifier: ^3.8.1 - version: 3.8.1 - remark-cli: - specifier: ^12.0.1 - version: 12.0.1 - remark-frontmatter: - specifier: ^5.0.0 - version: 5.0.0 - remark-preset-lint-consistent: - specifier: ^6.0.1 - version: 6.0.1 - remark-preset-lint-recommended: - specifier: ^7.0.1 - version: 7.0.1 - rimraf: - specifier: ^6.1.3 - version: 6.1.3 - tsx: - specifier: ^4.19.0 - version: 4.21.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - typescript-eslint: - specifier: ^8.56.1 - version: 8.56.1(eslint@10.0.2)(typescript@5.9.3) - vitest: - specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.3)(tsx@4.21.0)(yaml@2.8.2) - yaml: - specifier: ^2.8.2 - version: 2.8.2 - zod: - specifier: ^4.3.6 - version: 4.3.6 + '@ai-plugin-marketplace/cli': + specifier: ^0.1.0 + version: 0.1.0 + '@ai-plugin-marketplace/core': + specifier: ^0.1.0 + version: 0.1.0 packages: - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.23.2': - resolution: {integrity: sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/config-helpers@0.5.2': - resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/core@1.1.0': - resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/js@10.0.1': - resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - peerDependencies: - eslint: ^10.0.0 - peerDependenciesMeta: - eslint: - optional: true - - '@eslint/object-schema@3.0.2': - resolution: {integrity: sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/plugin-kit@0.6.0': - resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@npmcli/config@8.3.4': - resolution: {integrity: sha512-01rtHedemDNhUXdicU7s+QYz/3JyV5Naj84cvdXGH4mgCdL+agmSYaLF4LUG4vMCLzhBO8YtS0gPpH1FGvbgAw==} - engines: {node: ^16.14.0 || >=18.0.0} - - '@npmcli/git@5.0.8': - resolution: {integrity: sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==} - engines: {node: ^16.14.0 || >=18.0.0} - - '@npmcli/map-workspaces@3.0.6': - resolution: {integrity: sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@npmcli/name-from-folder@2.0.0': - resolution: {integrity: sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@npmcli/package-json@5.2.1': - resolution: {integrity: sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==} - engines: {node: ^16.14.0 || >=18.0.0} - - '@npmcli/promise-spawn@7.0.2': - resolution: {integrity: sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==} - engines: {node: ^16.14.0 || >=18.0.0} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@rollup/rollup-android-arm-eabi@4.59.0': - resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.59.0': - resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.59.0': - resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.59.0': - resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.59.0': - resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.59.0': - resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.59.0': - resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-musl@4.59.0': - resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-loong64-gnu@4.59.0': - resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.59.0': - resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.59.0': - resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.59.0': - resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.59.0': - resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-gnu@4.59.0': - resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-musl@4.59.0': - resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.59.0': - resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.59.0': - resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.59.0': - resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.59.0': - resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.59.0': - resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.59.0': - resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} - cpu: [x64] - os: [win32] - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/concat-stream@2.0.3': - resolution: {integrity: sha512-3qe4oQAPNwVNwK4C9c8u+VJqv9kez+2MR4qJpoPFfXtgxxif1QbFusvXzK0/Wra2VX07smostI2VMmJNSpZjuQ==} - - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/esrecurse@4.3.1': - resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} - - '@types/estree-jsx@1.0.5': - resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/hast@3.0.4': - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - - '@types/is-empty@1.2.3': - resolution: {integrity: sha512-4J1l5d79hoIvsrKh5VUKVRA1aIdsOb10Hu5j3J2VfP/msDnfTdGPmNp2E1Wg+vs97Bktzo+MZePFFXSGoykYJw==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/mdast@4.0.4': - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@22.19.13': - resolution: {integrity: sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==} - - '@types/node@25.3.3': - resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==} - - '@types/supports-color@8.1.3': - resolution: {integrity: sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==} - - '@types/text-table@0.2.5': - resolution: {integrity: sha512-hcZhlNvMkQG/k1vcZ6yHOl6WAYftQ2MLfTHcYRZ2xYZFD8tGVnE3qFV0lj1smQeDSR7/yY0PyuUalauf33bJeA==} - - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - - '@typescript-eslint/eslint-plugin@8.56.1': - resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.56.1 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/parser@8.56.1': - resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/project-service@8.56.1': - resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/scope-manager@8.56.1': - resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.56.1': - resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/type-utils@8.56.1': - resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/types@8.56.1': - resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.56.1': - resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/utils@8.56.1': - resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/visitor-keys@8.56.1': - resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@vitest/expect@4.0.18': - resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} - - '@vitest/mocker@4.0.18': - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@4.0.18': - resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} - - '@vitest/runner@4.0.18': - resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} - - '@vitest/snapshot@4.0.18': - resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} - - '@vitest/spy@4.0.18': - resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} - - '@vitest/utils@4.0.18': - resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} - - abbrev@2.0.0: - resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - brace-expansion@5.0.4: - resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} - engines: {node: 18 || 20 || >=22} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - - character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - ci-info@4.4.0: - resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} - engines: {node: '>=8'} - - collapse-white-space@2.1.0: - resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - - concat-stream@2.0.0: - resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} - engines: {'0': node >= 6.0} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decode-named-character-reference@1.3.0: - resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} - engines: {node: '>=18'} - hasBin: true - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + '@ai-plugin-marketplace/cli@0.1.0': + resolution: {integrity: sha512-DOoDFaDSZqsGTvxjiByIgQdTFbYBOeet+dee6FDavW8FtDBWKUs9xYc+FuGQPu1G9+ztT5uS9dIcQpdGZbEGaQ==} + engines: {node: '>=20.0.0'} hasBin: true - peerDependencies: - eslint: '>=7.0.0' - eslint-scope@9.1.1: - resolution: {integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@ai-plugin-marketplace/core@0.1.0': + resolution: {integrity: sha512-nUlTv2M/PIq8HtyLgeX9K8boLZJslS/4XrwHyamrgxINAxJOXyljelzYUFP8KqLypZPxfdlsuLILG8wW9LSeiQ==} + engines: {node: '>=20.0.0'} - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - eslint@10.0.2: - resolution: {integrity: sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@11.1.1: - resolution: {integrity: sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fault@2.0.1: - resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.3.4: - resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - get-tsconfig@4.13.6: - resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} - - hosted-git-info@7.0.2: - resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} - engines: {node: ^16.14.0 || >=18.0.0} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - ignore@6.0.2: - resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==} - engines: {node: '>= 4'} - - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - - import-meta-resolve@4.2.0: - resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@4.1.3: - resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - - is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - - is-empty@1.2.0: - resolution: {integrity: sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isexe@3.1.5: - resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} - engines: {node: '>=18'} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-parse-even-better-errors@3.0.2: - resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} hasBin: true - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lines-and-columns@2.0.4: - resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - load-plugin@6.0.3: - resolution: {integrity: sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w==} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - lru-cache@11.2.6: - resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} - engines: {node: 20 || >=22} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - markdown-extensions@2.0.0: - resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} - engines: {node: '>=16'} - - mdast-comment-marker@3.0.0: - resolution: {integrity: sha512-bt08sLmTNg00/UtVDiqZKocxqvQqqyQZAg1uaRuO/4ysXV5motg7RolF5o5yy/sY1rG0v2XgZEqFWho1+2UquA==} - - mdast-util-directive@3.1.0: - resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} - - mdast-util-from-markdown@2.0.3: - resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} - - mdast-util-frontmatter@2.0.1: - resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} - - mdast-util-heading-style@3.0.0: - resolution: {integrity: sha512-tsUfM9Kj9msjlemA/38Z3pvraQay880E3zP2NgIthMoGcpU9bcPX9oSM6QC/+eFXGGB4ba+VCB1dKAPHB7Veug==} - - mdast-util-mdx-expression@2.0.1: - resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} - - mdast-util-phrasing@4.1.0: - resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - - mdast-util-to-markdown@2.1.2: - resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} - - mdast-util-to-string@4.0.0: - resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - - micromark-core-commonmark@2.0.3: - resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} - - micromark-extension-frontmatter@2.0.0: - resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} - - micromark-factory-destination@2.0.1: - resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - - micromark-factory-label@2.0.1: - resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - - micromark-factory-space@2.0.1: - resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - - micromark-factory-title@2.0.1: - resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - - micromark-factory-whitespace@2.0.1: - resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} - - micromark-util-character@2.1.1: - resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - - micromark-util-chunked@2.0.1: - resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - - micromark-util-classify-character@2.0.1: - resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - - micromark-util-combine-extensions@2.0.1: - resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - - micromark-util-decode-numeric-character-reference@2.0.2: - resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - - micromark-util-decode-string@2.0.1: - resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - - micromark-util-encode@2.0.1: - resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - - micromark-util-html-tag-name@2.0.1: - resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - - micromark-util-normalize-identifier@2.0.1: - resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - - micromark-util-resolve-all@2.0.1: - resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - - micromark-util-sanitize-uri@2.0.1: - resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - - micromark-util-subtokenize@2.1.0: - resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} +snapshots: - micromark-util-symbol@2.0.1: - resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + '@ai-plugin-marketplace/cli@0.1.0': + dependencies: + '@ai-plugin-marketplace/core': 0.1.0 - micromark-util-types@2.0.2: - resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - - micromark@4.0.2: - resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - - minimatch@10.2.4: - resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} - engines: {node: 18 || 20 || >=22} - - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - nopt@7.2.1: - resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - - normalize-package-data@6.0.2: - resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} - engines: {node: ^16.14.0 || >=18.0.0} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-install-checks@6.3.0: - resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-normalize-package-bin@3.0.1: - resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-package-arg@11.0.3: - resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} - engines: {node: ^16.14.0 || >=18.0.0} - - npm-pick-manifest@9.1.0: - resolution: {integrity: sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==} - engines: {node: ^16.14.0 || >=18.0.0} - - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - parse-entities@4.0.2: - resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} - - parse-json@7.1.1: - resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} - engines: {node: '>=16'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - postcss@8.5.8: - resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} - engines: {node: ^10 || ^12 || >=14} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} - engines: {node: '>=14'} - hasBin: true - - proc-log@4.2.0: - resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - promise-inflight@1.0.1: - resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} - peerDependencies: - bluebird: '*' - peerDependenciesMeta: - bluebird: - optional: true - - promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - read-package-json-fast@3.0.2: - resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - remark-cli@12.0.1: - resolution: {integrity: sha512-2NAEOACoTgo+e+YAaCTODqbrWyhMVmlUyjxNCkTrDRHHQvH6+NbrnqVvQaLH/Q8Ket3v90A43dgAJmXv8y5Tkw==} - hasBin: true - - remark-frontmatter@5.0.0: - resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==} - - remark-lint-blockquote-indentation@4.0.1: - resolution: {integrity: sha512-7BhOsImFgTD7IIliu2tt+yJbx5gbMbXCOspc3VdYf/87iLJdWKqJoMy2V6DZG7kBjBlBsIZi38fDDngJttXt4w==} - - remark-lint-checkbox-character-style@5.0.1: - resolution: {integrity: sha512-6qilm7XQXOcTvjFEqqNY57Ki7md9rkSdpMIfIzVXdEnI4Npl2BnUff6ANrGRM7qTgJTrloaf8H0eQ91urcU6Og==} - - remark-lint-code-block-style@4.0.1: - resolution: {integrity: sha512-d4mHsEpv1yqXWl2dd+28tGRX0Lzk5qw7cfxAQVkOXPUONhsMFwXJEBeeqZokeG4lOKtkKdIJR7ezScDfWR0X4w==} - - remark-lint-emphasis-marker@4.0.1: - resolution: {integrity: sha512-BF1WWsAxai3XoKk48sfiqT3L8m02AZLj3BnipWkHDRXuLfz6VwsHVaHWyNvvE0p6b2B3A5dSYbcfJu5RmPx4tQ==} - - remark-lint-fenced-code-marker@4.0.1: - resolution: {integrity: sha512-uI91OcVPKjNxV+vpjDW9T64hkE0a/CRn3JhwdMxUAJYpVsKnA7PFPSFJOx/abNsVZHNSe7ZFGgGdaH/lqgSizA==} - - remark-lint-final-newline@3.0.1: - resolution: {integrity: sha512-q5diKHD6BMbzqWqgvYPOB8AJgLrMzEMBAprNXjcpKoZ/uCRqly+gxjco+qVUMtMWSd+P+KXZZEqoa7Y6QiOudw==} - - remark-lint-hard-break-spaces@4.1.1: - resolution: {integrity: sha512-AKDPDt39fvmr3yk38OKZEWJxxCOOUBE+96AsBfs+ExS5LW6oLa9041X5ahFDQHvHGzdoremEIaaElursaPEkNg==} - - remark-lint-heading-style@4.0.1: - resolution: {integrity: sha512-+rUpJ/N2CGC5xPgZ18XgsCsUBtadgEhdTi0BJPrsFmHPzL22BUHajeg9im8Y7zphUcbi1qFiKuxZd2nzDgZSXQ==} - - remark-lint-link-title-style@4.0.1: - resolution: {integrity: sha512-MtmnYrhjhRXR0zeiyYf/7GBlUF5KAPypJb345KjyDluOhI4Wj4VAXvVQuov/MFc3y8p/1yVwv3QDYv6yue8/wQ==} - - remark-lint-list-item-bullet-indent@5.0.1: - resolution: {integrity: sha512-LKuTxkw5aYChzZoF3BkfaBheSCHs0T8n8dPHLQEuOLo6iC5wy98iyryz0KZ61GD8stlZgQO2KdWSdnP6vr40Iw==} - - remark-lint-list-item-content-indent@4.0.1: - resolution: {integrity: sha512-KSopxxp64O6dLuTQ2sWaTqgjKWr1+AoB1QCTektMJ3mfHfn0QyZzC2CZbBU22KGzBhiYXv9cIxlJlxUtq2NqHg==} - - remark-lint-list-item-indent@4.0.1: - resolution: {integrity: sha512-gJd1Q+jOAeTgmGRsdMpnRh01DUrAm0O5PCQxE8ttv1QZOV015p/qJH+B4N6QSmcUuPokHLAh9USuq05C73qpiA==} - - remark-lint-no-blockquote-without-marker@6.0.1: - resolution: {integrity: sha512-b4IOkNcG7C16HYAdKUeAhO7qPt45m+v7SeYbVrqvbSFtlD3EUBL8fgHRgLK1mdujFXDP1VguOEMx+Txv8JOT4w==} - - remark-lint-no-duplicate-definitions@4.0.1: - resolution: {integrity: sha512-Ek+A/xDkv5Nn+BXCFmf+uOrFSajCHj6CjhsHjtROgVUeEPj726yYekDBoDRA0Y3+z+U30AsJoHgf/9Jj1IFSug==} - - remark-lint-no-heading-content-indent@5.0.1: - resolution: {integrity: sha512-YIWktnZo7M9aw7PGnHdshvetSH3Y0qW+Fm143R66zsk5lLzn1XA5NEd/MtDzP8tSxxV+gcv+bDd5St1QUI4oSQ==} - - remark-lint-no-literal-urls@4.0.1: - resolution: {integrity: sha512-RhTANFkFFXE6bM+WxWcPo2TTPEfkWG3lJZU50ycW7tJJmxUzDNzRed/z80EVJIdGwFa0NntVooLUJp3xrogalQ==} - - remark-lint-no-shortcut-reference-image@4.0.1: - resolution: {integrity: sha512-hQhJ3Dr8ZWRdj7qm6+9vcPpqtGchhENA2UHOmcTraLf6dN1cFATCgY/HbTbRIN6NkG/EEClTgRC1QCokWR2Mmw==} - - remark-lint-no-shortcut-reference-link@4.0.1: - resolution: {integrity: sha512-YxciuUZc90QaJYhayGO80lS3zxEOBgwwLW1MKYB7AfUdkrLcLVlS+DFloiq0MZ7EDVXuuGUEnIzyjyLSbI5BUA==} - - remark-lint-no-undefined-references@5.0.2: - resolution: {integrity: sha512-5prkVb1tKwJwr5+kct/UjsLjvMdEDO7uClPeGfrxfAcN59+pWU8OUSYiqYmpSKWJPIdyxPRS8Oyf1HtaYvg8VQ==} - - remark-lint-no-unused-definitions@4.0.2: - resolution: {integrity: sha512-KRzPmvfq6b3LSEcAQZobAn+5eDfPTle0dPyDEywgPSc3E7MIdRZQenL9UL8iIqHQWK4FvdUD0GX8FXGqu5EuCw==} - - remark-lint-ordered-list-marker-style@4.0.1: - resolution: {integrity: sha512-vZTAbstcBPbGwJacwldGzdGmKwy5/4r29SZ9nQkME4alEl5B1ReSBlYa8t7QnTSW7+tqvA9Sg71RPadgAKWa4w==} - - remark-lint-ordered-list-marker-value@4.0.1: - resolution: {integrity: sha512-HQb1MrArvApREC1/I6bkiFlZVDjngsuII29n8E8StnAaHOMN3hVYy6wJ9Uk+O3+X9O8v7fDsZPqFUHSfJhERXQ==} - - remark-lint-rule-style@4.0.1: - resolution: {integrity: sha512-gl1Ft13oTS3dJUCsWZzxD/5dAwI1HON67KU7uNfODD5gXJ8Y11deOWbun190ma7XbYdD7P0l8VT2HeRtEQzrWg==} - - remark-lint-strong-marker@4.0.1: - resolution: {integrity: sha512-KaGtj/OWEP4eoafevnlp3NsEVwC7yGEjBJ6uFMzfjNoXyjATdfZ2euB/AfKVt/A/FdZeeMeVoAUFH4DL+hScLQ==} - - remark-lint-table-cell-padding@5.1.1: - resolution: {integrity: sha512-6fgVA1iINBoAJaZMOnSsxrF9Qj9+hmCqrsrqZqgJJETjT1ODGH64iAN1/6vHR7dIwmy73d6ysB2WrGyKhVlK3A==} - - remark-lint@10.0.1: - resolution: {integrity: sha512-1+PYGFziOg4pH7DDf1uMd4AR3YuO2EMnds/SdIWMPGT7CAfDRSnAmpxPsJD0Ds3IKpn97h3d5KPGf1WFOg6hXQ==} - - remark-message-control@8.0.0: - resolution: {integrity: sha512-brpzOO+jdyE/mLqvqqvbogmhGxKygjpCUCG/PwSCU43+JZQ+RM+sSzkCWBcYvgF3KIAVNIoPsvXjBkzO7EdsYQ==} - - remark-parse@11.0.0: - resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - - remark-preset-lint-consistent@6.0.1: - resolution: {integrity: sha512-SOLdA36UOU1hiGFm6HAqN9+DORGJPVWxU/EvPVkknTr9V4ULhlzHEJ8OVRMVX3jqoy4lrwb4IqiboVz0YLA7+Q==} - - remark-preset-lint-recommended@7.0.1: - resolution: {integrity: sha512-j1CY5u48PtZl872BQ40uWSQMT3R4gXKp0FUgevMu5gW7hFMtvaCiDq+BfhzeR8XKKiW9nIMZGfIMZHostz5X4g==} - - remark-stringify@11.0.0: - resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - - remark@15.0.1: - resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - - rimraf@6.1.3: - resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} - engines: {node: 20 || >=22} - hasBin: true - - rollup@4.59.0: - resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.23: - resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string-width@6.1.0: - resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} - engines: {node: '>=16'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - supports-color@9.4.0: - resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} - engines: {node: '>=12'} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} - engines: {node: '>=18'} - - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - - tinyrainbow@3.0.3: - resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} - engines: {node: '>=14.0.0'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - - ts-api-utils@2.4.0: - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} - engines: {node: '>=18.0.0'} - hasBin: true - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-fest@3.13.1: - resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} - engines: {node: '>=14.16'} - - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - - typescript-eslint@8.56.1: - resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - - unified-args@11.0.1: - resolution: {integrity: sha512-WEQghE91+0s3xPVs0YW6a5zUduNLjmANswX7YbBfksHNDGMjHxaWCql4SR7c9q0yov/XiIEdk6r/LqfPjaYGcw==} - - unified-engine@11.2.2: - resolution: {integrity: sha512-15g/gWE7qQl9tQ3nAEbMd5h9HV1EACtFs6N9xaRBZICoCwnNGbal1kOs++ICf4aiTdItZxU2s/kYWhW7htlqJg==} - - unified-lint-rule@3.0.1: - resolution: {integrity: sha512-HxIeQOmwL19DGsxHXbeyzKHBsoSCFO7UtRVUvT2v61ptw/G+GbysWcrpHdfs5jqbIFDA11MoKngIhQK0BeTVjA==} - - unified-message-control@5.0.0: - resolution: {integrity: sha512-B2cSAkpuMVVmPP90KCfKdBhm1e9KYJ+zK3x5BCa0N65zpq1Ybkc9C77+M5qwR8FWO7RF3LM5QRRPZtgjW6DUCw==} - - unified@11.0.5: - resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - - unist-util-inspect@8.1.0: - resolution: {integrity: sha512-mOlg8Mp33pR0eeFpo5d2902ojqFFOKMMG2hF8bmH7ZlhnmjFgh0NI3/ZDwdaBJNbvrS7LZFVrBVtIE9KZ9s7vQ==} - - unist-util-is@6.0.1: - resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} - - unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - - unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - - unist-util-visit-parents@6.0.2: - resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - - unist-util-visit@5.1.0: - resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - - validate-npm-package-name@5.0.1: - resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - vfile-location@5.0.3: - resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} - - vfile-message@4.0.3: - resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - - vfile-reporter@8.1.1: - resolution: {integrity: sha512-qxRZcnFSQt6pWKn3PAk81yLK2rO2i7CDXpy8v8ZquiEOMLSnPw6BMSi9Y1sUCwGGl7a9b3CJT1CKpnRF7pp66g==} - - vfile-sort@4.0.0: - resolution: {integrity: sha512-lffPI1JrbHDTToJwcq0rl6rBmkjQmMuXkAxsZPRS9DXbaJQvc642eCg6EGxcX2i1L+esbuhq+2l9tBll5v8AeQ==} - - vfile-statistics@3.0.0: - resolution: {integrity: sha512-/qlwqwWBWFOmpXujL/20P+Iuydil0rZZNglR+VNm6J0gpLHwuVM5s7g2TfVoswbXjZ4HuIhLMySEyIw5i7/D8w==} - - vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - - vite@7.3.1: - resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.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 - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@4.0.18: - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.18 - '@vitest/browser-preview': 4.0.18 - '@vitest/browser-webdriverio': 4.0.18 - '@vitest/ui': 4.0.18 - happy-dom: '*' - jsdom: '*' - 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/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - walk-up-path@3.0.1: - resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - which@4.0.0: - resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} - engines: {node: ^16.13.0 || >=18.0.0} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - yaml@2.8.2: - resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} - engines: {node: '>= 14.6'} - hasBin: true - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} - - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -snapshots: - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/helper-validator-identifier@7.28.5': {} - - '@esbuild/aix-ppc64@0.27.3': - optional: true - - '@esbuild/android-arm64@0.27.3': - optional: true - - '@esbuild/android-arm@0.27.3': - optional: true - - '@esbuild/android-x64@0.27.3': - optional: true - - '@esbuild/darwin-arm64@0.27.3': - optional: true - - '@esbuild/darwin-x64@0.27.3': - optional: true - - '@esbuild/freebsd-arm64@0.27.3': - optional: true - - '@esbuild/freebsd-x64@0.27.3': - optional: true - - '@esbuild/linux-arm64@0.27.3': - optional: true - - '@esbuild/linux-arm@0.27.3': - optional: true - - '@esbuild/linux-ia32@0.27.3': - optional: true - - '@esbuild/linux-loong64@0.27.3': - optional: true - - '@esbuild/linux-mips64el@0.27.3': - optional: true - - '@esbuild/linux-ppc64@0.27.3': - optional: true - - '@esbuild/linux-riscv64@0.27.3': - optional: true - - '@esbuild/linux-s390x@0.27.3': - optional: true - - '@esbuild/linux-x64@0.27.3': - optional: true - - '@esbuild/netbsd-arm64@0.27.3': - optional: true - - '@esbuild/netbsd-x64@0.27.3': - optional: true - - '@esbuild/openbsd-arm64@0.27.3': - optional: true - - '@esbuild/openbsd-x64@0.27.3': - optional: true - - '@esbuild/openharmony-arm64@0.27.3': - optional: true - - '@esbuild/sunos-x64@0.27.3': - optional: true - - '@esbuild/win32-arm64@0.27.3': - optional: true - - '@esbuild/win32-ia32@0.27.3': - optional: true - - '@esbuild/win32-x64@0.27.3': - optional: true - - '@eslint-community/eslint-utils@4.9.1(eslint@10.0.2)': - dependencies: - eslint: 10.0.2 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} - - '@eslint/config-array@0.23.2': - dependencies: - '@eslint/object-schema': 3.0.2 - debug: 4.4.3 - minimatch: 10.2.4 - transitivePeerDependencies: - - supports-color - - '@eslint/config-helpers@0.5.2': - dependencies: - '@eslint/core': 1.1.0 - - '@eslint/core@1.1.0': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/js@10.0.1(eslint@10.0.2)': - optionalDependencies: - eslint: 10.0.2 - - '@eslint/object-schema@3.0.2': {} - - '@eslint/plugin-kit@0.6.0': - dependencies: - '@eslint/core': 1.1.0 - levn: 0.4.1 - - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.7': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.4.3 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.4.3': {} - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.2.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@npmcli/config@8.3.4': - dependencies: - '@npmcli/map-workspaces': 3.0.6 - '@npmcli/package-json': 5.2.1 - ci-info: 4.4.0 - ini: 4.1.3 - nopt: 7.2.1 - proc-log: 4.2.0 - semver: 7.7.4 - walk-up-path: 3.0.1 - transitivePeerDependencies: - - bluebird - - '@npmcli/git@5.0.8': - dependencies: - '@npmcli/promise-spawn': 7.0.2 - ini: 4.1.3 - lru-cache: 10.4.3 - npm-pick-manifest: 9.1.0 - proc-log: 4.2.0 - promise-inflight: 1.0.1 - promise-retry: 2.0.1 - semver: 7.7.4 - which: 4.0.0 - transitivePeerDependencies: - - bluebird - - '@npmcli/map-workspaces@3.0.6': - dependencies: - '@npmcli/name-from-folder': 2.0.0 - glob: 10.5.0 - minimatch: 9.0.9 - read-package-json-fast: 3.0.2 - - '@npmcli/name-from-folder@2.0.0': {} - - '@npmcli/package-json@5.2.1': - dependencies: - '@npmcli/git': 5.0.8 - glob: 10.5.0 - hosted-git-info: 7.0.2 - json-parse-even-better-errors: 3.0.2 - normalize-package-data: 6.0.2 - proc-log: 4.2.0 - semver: 7.7.4 - transitivePeerDependencies: - - bluebird - - '@npmcli/promise-spawn@7.0.2': - dependencies: - which: 4.0.0 - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@rollup/rollup-android-arm-eabi@4.59.0': - optional: true - - '@rollup/rollup-android-arm64@4.59.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.59.0': - optional: true - - '@rollup/rollup-darwin-x64@4.59.0': - optional: true - - '@rollup/rollup-freebsd-arm64@4.59.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.59.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.59.0': - optional: true - - '@rollup/rollup-openbsd-x64@4.59.0': - optional: true - - '@rollup/rollup-openharmony-arm64@4.59.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.59.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.59.0': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.59.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.59.0': - optional: true - - '@standard-schema/spec@1.1.0': {} - - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - - '@types/concat-stream@2.0.3': - dependencies: - '@types/node': 25.3.3 - - '@types/debug@4.1.12': - dependencies: - '@types/ms': 2.1.0 - - '@types/deep-eql@4.0.2': {} - - '@types/esrecurse@4.3.1': {} - - '@types/estree-jsx@1.0.5': - dependencies: - '@types/estree': 1.0.8 - - '@types/estree@1.0.8': {} - - '@types/hast@3.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/is-empty@1.2.3': {} - - '@types/json-schema@7.0.15': {} - - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/ms@2.1.0': {} - - '@types/node@22.19.13': - dependencies: - undici-types: 6.21.0 - - '@types/node@25.3.3': - dependencies: - undici-types: 7.18.2 - - '@types/supports-color@8.1.3': {} - - '@types/text-table@0.2.5': {} - - '@types/unist@2.0.11': {} - - '@types/unist@3.0.3': {} - - '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(typescript@5.9.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.56.1(eslint@10.0.2)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/type-utils': 8.56.1(eslint@10.0.2)(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@10.0.2)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.56.1 - eslint: 10.0.2 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.56.1(eslint@10.0.2)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.56.1 - debug: 4.4.3 - eslint: 10.0.2 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) - '@typescript-eslint/types': 8.56.1 - debug: 4.4.3 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.56.1': - dependencies: - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/visitor-keys': 8.56.1 - - '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': - dependencies: - typescript: 5.9.3 - - '@typescript-eslint/type-utils@8.56.1(eslint@10.0.2)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@10.0.2)(typescript@5.9.3) - debug: 4.4.3 - eslint: 10.0.2 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.56.1': {} - - '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': - dependencies: - '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/visitor-keys': 8.56.1 - debug: 4.4.3 - minimatch: 10.2.4 - semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.56.1(eslint@10.0.2)(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2) - '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - eslint: 10.0.2 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.56.1': - dependencies: - '@typescript-eslint/types': 8.56.1 - eslint-visitor-keys: 5.0.1 - - '@vitest/expect@4.0.18': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - chai: 6.2.2 - tinyrainbow: 3.0.3 - - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.3)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - '@vitest/spy': 4.0.18 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.1(@types/node@25.3.3)(tsx@4.21.0)(yaml@2.8.2) - - '@vitest/pretty-format@4.0.18': - dependencies: - tinyrainbow: 3.0.3 - - '@vitest/runner@4.0.18': - dependencies: - '@vitest/utils': 4.0.18 - pathe: 2.0.3 - - '@vitest/snapshot@4.0.18': - dependencies: - '@vitest/pretty-format': 4.0.18 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.0.18': {} - - '@vitest/utils@4.0.18': - dependencies: - '@vitest/pretty-format': 4.0.18 - tinyrainbow: 3.0.3 - - abbrev@2.0.0: {} - - acorn-jsx@5.3.2(acorn@8.16.0): - dependencies: - acorn: 8.16.0 - - acorn@8.16.0: {} - - ajv@6.14.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - assertion-error@2.0.1: {} - - bail@2.0.2: {} - - balanced-match@1.0.2: {} - - balanced-match@4.0.4: {} - - binary-extensions@2.3.0: {} - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - brace-expansion@5.0.4: - dependencies: - balanced-match: 4.0.4 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - buffer-from@1.1.2: {} - - ccount@2.0.1: {} - - chai@6.2.2: {} - - chalk@5.6.2: {} - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - character-entities@2.0.2: {} - - character-reference-invalid@2.0.1: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - ci-info@4.4.0: {} - - collapse-white-space@2.1.0: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - comma-separated-tokens@2.0.3: {} - - concat-stream@2.0.0: - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.2 - typedarray: 0.0.6 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decode-named-character-reference@1.3.0: - dependencies: - character-entities: 2.0.2 - - deep-is@0.1.4: {} - - dequal@2.0.3: {} - - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - - eastasianwidth@0.2.0: {} - - emoji-regex@10.6.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - err-code@2.0.3: {} - - error-ex@1.3.4: - dependencies: - is-arrayish: 0.2.1 - - es-module-lexer@1.7.0: {} - - esbuild@0.27.3: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 - - escape-string-regexp@4.0.0: {} - - escape-string-regexp@5.0.0: {} - - eslint-config-prettier@10.1.8(eslint@10.0.2): - dependencies: - eslint: 10.0.2 - - eslint-scope@9.1.1: - dependencies: - '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.8 - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@5.0.1: {} - - eslint@10.0.2: - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.2 - '@eslint/config-helpers': 0.5.2 - '@eslint/core': 1.1.0 - '@eslint/plugin-kit': 0.6.0 - '@humanfs/node': 0.16.7 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.14.0 - cross-spawn: 7.0.6 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - eslint-scope: 9.1.1 - eslint-visitor-keys: 5.0.1 - espree: 11.1.1 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - minimatch: 10.2.4 - natural-compare: 1.4.0 - optionator: 0.9.4 - transitivePeerDependencies: - - supports-color - - espree@11.1.1: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 5.0.1 - - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - esutils@2.0.3: {} - - expect-type@1.3.0: {} - - extend@3.0.2: {} - - fast-deep-equal@3.1.3: {} - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fault@2.0.1: - dependencies: - format: 0.2.2 - - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.3.4 - keyv: 4.5.4 - - flatted@3.3.4: {} - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - format@0.2.2: {} - - fsevents@2.3.3: - optional: true - - get-tsconfig@4.13.6: - dependencies: - resolve-pkg-maps: 1.0.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.9 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - glob@13.0.6: - dependencies: - minimatch: 10.2.4 - minipass: 7.1.3 - path-scurry: 2.0.2 - - hosted-git-info@7.0.2: - dependencies: - lru-cache: 10.4.3 - - ignore@5.3.2: {} - - ignore@6.0.2: {} - - ignore@7.0.5: {} - - import-meta-resolve@4.2.0: {} - - imurmurhash@0.1.4: {} - - inherits@2.0.4: {} - - ini@4.1.3: {} - - is-alphabetical@2.0.1: {} - - is-alphanumerical@2.0.1: - dependencies: - is-alphabetical: 2.0.1 - is-decimal: 2.0.1 - - is-arrayish@0.2.1: {} - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-decimal@2.0.1: {} - - is-empty@1.2.0: {} - - is-extglob@2.1.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-hexadecimal@2.0.1: {} - - is-number@7.0.0: {} - - is-plain-obj@4.1.0: {} - - isexe@2.0.0: {} - - isexe@3.1.5: {} - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - js-tokens@4.0.0: {} - - json-buffer@3.0.1: {} - - json-parse-even-better-errors@3.0.2: {} - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - json5@2.2.3: {} - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - lines-and-columns@2.0.4: {} - - load-plugin@6.0.3: - dependencies: - '@npmcli/config': 8.3.4 - import-meta-resolve: 4.2.0 - transitivePeerDependencies: - - bluebird - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - longest-streak@3.1.0: {} - - lru-cache@10.4.3: {} - - lru-cache@11.2.6: {} - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - markdown-extensions@2.0.0: {} - - mdast-comment-marker@3.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-mdx-expression: 2.0.1 - transitivePeerDependencies: - - supports-color - - mdast-util-directive@3.1.0: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - parse-entities: 4.0.2 - stringify-entities: 4.0.4 - unist-util-visit-parents: 6.0.2 - transitivePeerDependencies: - - supports-color - - mdast-util-from-markdown@2.0.3: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.2 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-frontmatter@2.0.1: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - escape-string-regexp: 5.0.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - micromark-extension-frontmatter: 2.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-heading-style@3.0.0: - dependencies: - '@types/mdast': 4.0.4 - - mdast-util-mdx-expression@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-phrasing@4.1.0: - dependencies: - '@types/mdast': 4.0.4 - unist-util-is: 6.0.1 - - mdast-util-to-markdown@2.1.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - longest-streak: 3.1.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-string: 4.0.0 - micromark-util-classify-character: 2.0.1 - micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.1.0 - zwitch: 2.0.4 - - mdast-util-to-string@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - - micromark-core-commonmark@2.0.3: - dependencies: - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-factory-destination: 2.0.1 - micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 - micromark-factory-title: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-html-tag-name: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-frontmatter@2.0.0: - dependencies: - fault: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-destination@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-label@2.0.1: - dependencies: - devlop: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-space@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-types: 2.0.2 - - micromark-factory-title@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-whitespace@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-character@2.1.1: - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-chunked@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-classify-character@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-combine-extensions@2.0.1: - dependencies: - micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-decode-numeric-character-reference@2.0.2: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-decode-string@2.0.1: - dependencies: - decode-named-character-reference: 1.3.0 - micromark-util-character: 2.1.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-symbol: 2.0.1 - - micromark-util-encode@2.0.1: {} - - micromark-util-html-tag-name@2.0.1: {} - - micromark-util-normalize-identifier@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-resolve-all@2.0.1: - dependencies: - micromark-util-types: 2.0.2 - - micromark-util-sanitize-uri@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - - micromark-util-subtokenize@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-symbol@2.0.1: {} - - micromark-util-types@2.0.2: {} - - micromark@4.0.2: - dependencies: - '@types/debug': 4.1.12 - debug: 4.4.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-combine-extensions: 2.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-encode: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - transitivePeerDependencies: - - supports-color - - minimatch@10.2.4: - dependencies: - brace-expansion: 5.0.4 - - minimatch@9.0.9: - dependencies: - brace-expansion: 2.0.2 - - minimist@1.2.8: {} - - minipass@7.1.3: {} - - ms@2.1.3: {} - - nanoid@3.3.11: {} - - natural-compare@1.4.0: {} - - nopt@7.2.1: - dependencies: - abbrev: 2.0.0 - - normalize-package-data@6.0.2: - dependencies: - hosted-git-info: 7.0.2 - semver: 7.7.4 - validate-npm-package-license: 3.0.4 - - normalize-path@3.0.0: {} - - npm-install-checks@6.3.0: - dependencies: - semver: 7.7.4 - - npm-normalize-package-bin@3.0.1: {} - - npm-package-arg@11.0.3: - dependencies: - hosted-git-info: 7.0.2 - proc-log: 4.2.0 - semver: 7.7.4 - validate-npm-package-name: 5.0.1 - - npm-pick-manifest@9.1.0: - dependencies: - npm-install-checks: 6.3.0 - npm-normalize-package-bin: 3.0.1 - npm-package-arg: 11.0.3 - semver: 7.7.4 - - obug@2.1.1: {} - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - package-json-from-dist@1.0.1: {} - - parse-entities@4.0.2: - dependencies: - '@types/unist': 2.0.11 - character-entities-legacy: 3.0.0 - character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.3.0 - is-alphanumerical: 2.0.1 - is-decimal: 2.0.1 - is-hexadecimal: 2.0.1 - - parse-json@7.1.1: - dependencies: - '@babel/code-frame': 7.29.0 - error-ex: 1.3.4 - json-parse-even-better-errors: 3.0.2 - lines-and-columns: 2.0.4 - type-fest: 3.13.1 - - path-exists@4.0.0: {} - - path-key@3.1.1: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.3 - - path-scurry@2.0.2: - dependencies: - lru-cache: 11.2.6 - minipass: 7.1.3 - - pathe@2.0.3: {} - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - picomatch@4.0.3: {} - - pluralize@8.0.0: {} - - postcss@8.5.8: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prelude-ls@1.2.1: {} - - prettier@3.8.1: {} - - proc-log@4.2.0: {} - - promise-inflight@1.0.1: {} - - promise-retry@2.0.1: - dependencies: - err-code: 2.0.3 - retry: 0.12.0 - - punycode@2.3.1: {} - - read-package-json-fast@3.0.2: - dependencies: - json-parse-even-better-errors: 3.0.2 - npm-normalize-package-bin: 3.0.1 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - remark-cli@12.0.1: - dependencies: - import-meta-resolve: 4.2.0 - markdown-extensions: 2.0.0 - remark: 15.0.1 - unified-args: 11.0.1 - transitivePeerDependencies: - - bluebird - - supports-color - - remark-frontmatter@5.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-frontmatter: 2.0.1 - micromark-extension-frontmatter: 2.0.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-lint-blockquote-indentation@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-phrasing: 4.1.0 - pluralize: 8.0.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - - remark-lint-checkbox-character-style@5.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-phrasing: 4.1.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-code-block-style@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-phrasing: 4.1.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-emphasis-marker@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-fenced-code-marker@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-phrasing: 4.1.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-final-newline@3.0.1: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - unified-lint-rule: 3.0.1 - vfile-location: 5.0.3 - - remark-lint-hard-break-spaces@4.1.1: - dependencies: - '@types/mdast': 4.0.4 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 - - remark-lint-heading-style@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-heading-style: 3.0.0 - mdast-util-phrasing: 4.1.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-link-title-style@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-list-item-bullet-indent@5.0.1: - dependencies: - '@types/mdast': 4.0.4 - pluralize: 8.0.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - - remark-lint-list-item-content-indent@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-phrasing: 4.1.0 - pluralize: 8.0.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-list-item-indent@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-phrasing: 4.1.0 - pluralize: 8.0.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - - remark-lint-no-blockquote-without-marker@6.0.1: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-directive: 3.1.0 - mdast-util-phrasing: 4.1.0 - pluralize: 8.0.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-location: 5.0.3 - transitivePeerDependencies: - - supports-color - - remark-lint-no-duplicate-definitions@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-phrasing: 4.1.0 - unified-lint-rule: 3.0.1 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-no-heading-content-indent@5.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-phrasing: 4.1.0 - pluralize: 8.0.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - - remark-lint-no-literal-urls@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-to-string: 4.0.0 - micromark-util-character: 2.1.1 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - - remark-lint-no-shortcut-reference-image@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - unified-lint-rule: 3.0.1 - unist-util-visit-parents: 6.0.2 - - remark-lint-no-shortcut-reference-link@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - unified-lint-rule: 3.0.1 - unist-util-visit-parents: 6.0.2 - - remark-lint-no-undefined-references@5.0.2: - dependencies: - '@types/mdast': 4.0.4 - collapse-white-space: 2.1.0 - devlop: 1.1.0 - micromark-util-normalize-identifier: 2.0.1 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-location: 5.0.3 - - remark-lint-no-unused-definitions@4.0.2: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - unified-lint-rule: 3.0.1 - unist-util-visit-parents: 6.0.2 - - remark-lint-ordered-list-marker-style@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-phrasing: 4.1.0 - micromark-util-character: 2.1.1 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-ordered-list-marker-value@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-phrasing: 4.1.0 - micromark-util-character: 2.1.1 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-rule-style@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-phrasing: 4.1.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-strong-marker@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint-table-cell-padding@5.1.1: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - devlop: 1.1.0 - mdast-util-phrasing: 4.1.0 - pluralize: 8.0.0 - unified-lint-rule: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit-parents: 6.0.2 - vfile-message: 4.0.3 - - remark-lint@10.0.1: - dependencies: - '@types/mdast': 4.0.4 - remark-message-control: 8.0.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-message-control@8.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-comment-marker: 3.0.0 - unified-message-control: 5.0.0 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - remark-parse@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3 - micromark-util-types: 2.0.2 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-preset-lint-consistent@6.0.1: - dependencies: - remark-lint: 10.0.1 - remark-lint-blockquote-indentation: 4.0.1 - remark-lint-checkbox-character-style: 5.0.1 - remark-lint-code-block-style: 4.0.1 - remark-lint-emphasis-marker: 4.0.1 - remark-lint-fenced-code-marker: 4.0.1 - remark-lint-heading-style: 4.0.1 - remark-lint-link-title-style: 4.0.1 - remark-lint-list-item-content-indent: 4.0.1 - remark-lint-ordered-list-marker-style: 4.0.1 - remark-lint-ordered-list-marker-value: 4.0.1 - remark-lint-rule-style: 4.0.1 - remark-lint-strong-marker: 4.0.1 - remark-lint-table-cell-padding: 5.1.1 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-preset-lint-recommended@7.0.1: - dependencies: - remark-lint: 10.0.1 - remark-lint-final-newline: 3.0.1 - remark-lint-hard-break-spaces: 4.1.1 - remark-lint-list-item-bullet-indent: 5.0.1 - remark-lint-list-item-indent: 4.0.1 - remark-lint-no-blockquote-without-marker: 6.0.1 - remark-lint-no-duplicate-definitions: 4.0.1 - remark-lint-no-heading-content-indent: 5.0.1 - remark-lint-no-literal-urls: 4.0.1 - remark-lint-no-shortcut-reference-image: 4.0.1 - remark-lint-no-shortcut-reference-link: 4.0.1 - remark-lint-no-undefined-references: 5.0.2 - remark-lint-no-unused-definitions: 4.0.2 - remark-lint-ordered-list-marker-style: 4.0.1 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-stringify@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-to-markdown: 2.1.2 - unified: 11.0.5 - - remark@15.0.1: - dependencies: - '@types/mdast': 4.0.4 - remark-parse: 11.0.0 - remark-stringify: 11.0.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - resolve-pkg-maps@1.0.0: {} - - retry@0.12.0: {} - - rimraf@6.1.3: - dependencies: - glob: 13.0.6 - package-json-from-dist: 1.0.1 - - rollup@4.59.0: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.59.0 - '@rollup/rollup-android-arm64': 4.59.0 - '@rollup/rollup-darwin-arm64': 4.59.0 - '@rollup/rollup-darwin-x64': 4.59.0 - '@rollup/rollup-freebsd-arm64': 4.59.0 - '@rollup/rollup-freebsd-x64': 4.59.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 - '@rollup/rollup-linux-arm-musleabihf': 4.59.0 - '@rollup/rollup-linux-arm64-gnu': 4.59.0 - '@rollup/rollup-linux-arm64-musl': 4.59.0 - '@rollup/rollup-linux-loong64-gnu': 4.59.0 - '@rollup/rollup-linux-loong64-musl': 4.59.0 - '@rollup/rollup-linux-ppc64-gnu': 4.59.0 - '@rollup/rollup-linux-ppc64-musl': 4.59.0 - '@rollup/rollup-linux-riscv64-gnu': 4.59.0 - '@rollup/rollup-linux-riscv64-musl': 4.59.0 - '@rollup/rollup-linux-s390x-gnu': 4.59.0 - '@rollup/rollup-linux-x64-gnu': 4.59.0 - '@rollup/rollup-linux-x64-musl': 4.59.0 - '@rollup/rollup-openbsd-x64': 4.59.0 - '@rollup/rollup-openharmony-arm64': 4.59.0 - '@rollup/rollup-win32-arm64-msvc': 4.59.0 - '@rollup/rollup-win32-ia32-msvc': 4.59.0 - '@rollup/rollup-win32-x64-gnu': 4.59.0 - '@rollup/rollup-win32-x64-msvc': 4.59.0 - fsevents: 2.3.3 - - safe-buffer@5.2.1: {} - - semver@7.7.4: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - siginfo@2.0.0: {} - - signal-exit@4.1.0: {} - - source-map-js@1.2.1: {} - - space-separated-tokens@2.0.2: {} - - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.23 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.23 - - spdx-license-ids@3.0.23: {} - - stackback@0.0.2: {} - - std-env@3.10.0: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.2.0 - - string-width@6.1.0: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 10.6.0 - strip-ansi: 7.2.0 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - - supports-color@9.4.0: {} - - text-table@0.2.0: {} - - tinybench@2.9.0: {} - - tinyexec@1.0.2: {} - - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - - tinyrainbow@3.0.3: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - trough@2.2.0: {} - - ts-api-utils@2.4.0(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - - tsx@4.21.0: - dependencies: - esbuild: 0.27.3 - get-tsconfig: 4.13.6 - optionalDependencies: - fsevents: 2.3.3 - - type-check@0.4.0: + '@ai-plugin-marketplace/core@0.1.0': dependencies: - prelude-ls: 1.2.1 - - type-fest@3.13.1: {} - - typedarray@0.0.6: {} - - typescript-eslint@8.56.1(eslint@10.0.2)(typescript@5.9.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2)(typescript@5.9.3))(eslint@10.0.2)(typescript@5.9.3) - '@typescript-eslint/parser': 8.56.1(eslint@10.0.2)(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@10.0.2)(typescript@5.9.3) - eslint: 10.0.2 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - typescript@5.9.3: {} - - undici-types@6.21.0: {} - - undici-types@7.18.2: {} - - unified-args@11.0.1: - dependencies: - '@types/text-table': 0.2.5 - chalk: 5.6.2 - chokidar: 3.6.0 - comma-separated-tokens: 2.0.3 - json5: 2.2.3 - minimist: 1.2.8 - strip-ansi: 7.2.0 - text-table: 0.2.0 - unified-engine: 11.2.2 - transitivePeerDependencies: - - bluebird - - supports-color - - unified-engine@11.2.2: - dependencies: - '@types/concat-stream': 2.0.3 - '@types/debug': 4.1.12 - '@types/is-empty': 1.2.3 - '@types/node': 22.19.13 - '@types/unist': 3.0.3 - concat-stream: 2.0.0 - debug: 4.4.3 - extend: 3.0.2 - glob: 10.5.0 - ignore: 6.0.2 - is-empty: 1.2.0 - is-plain-obj: 4.1.0 - load-plugin: 6.0.3 - parse-json: 7.1.1 - trough: 2.2.0 - unist-util-inspect: 8.1.0 - vfile: 6.0.3 - vfile-message: 4.0.3 - vfile-reporter: 8.1.1 - vfile-statistics: 3.0.0 - yaml: 2.8.2 - transitivePeerDependencies: - - bluebird - - supports-color - - unified-lint-rule@3.0.1: - dependencies: - '@types/unist': 3.0.3 - trough: 2.2.0 - unified: 11.0.5 - vfile: 6.0.3 - - unified-message-control@5.0.0: - dependencies: - '@types/unist': 3.0.3 - devlop: 1.1.0 - space-separated-tokens: 2.0.2 - unist-util-is: 6.0.1 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - vfile-location: 5.0.3 - vfile-message: 4.0.3 - - unified@11.0.5: - dependencies: - '@types/unist': 3.0.3 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.3 - - unist-util-inspect@8.1.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-is@6.0.1: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-stringify-position@4.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-visit-parents@6.0.2: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-visit@5.1.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - util-deprecate@1.0.2: {} - - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - - validate-npm-package-name@5.0.1: {} - - vfile-location@5.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile: 6.0.3 - - vfile-message@4.0.3: - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - - vfile-reporter@8.1.1: - dependencies: - '@types/supports-color': 8.1.3 - string-width: 6.1.0 - supports-color: 9.4.0 - unist-util-stringify-position: 4.0.0 - vfile: 6.0.3 - vfile-message: 4.0.3 - vfile-sort: 4.0.0 - vfile-statistics: 3.0.0 - - vfile-sort@4.0.0: - dependencies: - vfile: 6.0.3 - vfile-message: 4.0.3 - - vfile-statistics@3.0.0: - dependencies: - vfile: 6.0.3 - vfile-message: 4.0.3 - - vfile@6.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.3 - - vite@7.3.1(@types/node@25.3.3)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.8 - rollup: 4.59.0 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 25.3.3 - fsevents: 2.3.3 - tsx: 4.21.0 - yaml: 2.8.2 - - vitest@4.0.18(@types/node@25.3.3)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.3)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.0.18 - '@vitest/runner': 4.0.18 - '@vitest/snapshot': 4.0.18 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - es-module-lexer: 1.7.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: 3.10.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.3.3)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.3.3 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - - walk-up-path@3.0.1: {} - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - which@4.0.0: - dependencies: - isexe: 3.1.5 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - word-wrap@1.2.5: {} - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.2.0 - - yaml@2.8.2: {} + jiti: 2.7.0 + yaml: 2.9.0 + zod: 4.4.3 - yocto-queue@0.1.0: {} + jiti@2.7.0: {} - zod@4.3.6: {} + yaml@2.9.0: {} - zwitch@2.0.4: {} + zod@4.4.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml deleted file mode 100644 index c5739b7..0000000 --- a/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -ignoredBuiltDependencies: - - esbuild diff --git a/schemas/codex-marketplace.json b/schemas/codex-marketplace.json deleted file mode 100644 index 6df27c9..0000000 --- a/schemas/codex-marketplace.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://raw.githubusercontent.com/ai-plugin-marketplace-template/main/schemas/codex-marketplace.json", - "title": "OpenAI Codex Plugin Marketplace", - "description": "Codex plugin marketplace registry (repo-root .agents/plugins/marketplace.json). Lists plugins for discovery by `codex /plugins` via a local source.", - "type": "object", - "required": ["plugins"], - "properties": { - "name": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "description": "Marketplace name (lowercase, hyphens, no spaces)" - }, - "owner": { - "type": "object", - "required": ["name"], - "properties": { - "name": { "type": "string" }, - "email": { "type": "string", "format": "email" }, - "url": { "type": "string", "format": "uri" } - }, - "additionalProperties": false, - "description": "Marketplace owner information" - }, - "metadata": { - "type": "object", - "properties": { - "pluginRoot": { "type": "string" }, - "version": { "type": "string" }, - "description": { "type": "string" } - }, - "additionalProperties": false, - "description": "Optional metadata about the marketplace" - }, - "plugins": { - "type": "array", - "items": { - "type": "object", - "required": ["name", "source"], - "properties": { - "name": { - "type": "string", - "description": "Plugin name (must match .codex-plugin/plugin.json name)" - }, - "source": { - "type": "object", - "required": ["source", "path"], - "properties": { - "source": { - "type": "string", - "enum": ["local"], - "description": "Source kind — only 'local' is supported for repo-root marketplaces" - }, - "path": { - "type": "string", - "pattern": "^\\./", - "description": "Relative path to the plugin directory (must start with ./)" - } - }, - "additionalProperties": false, - "description": "Plugin source descriptor — { source: \"local\", path: \"./plugins/\" }" - }, - "description": { - "type": "string", - "description": "Plugin description" - }, - "tags": { - "type": "array", - "items": { "type": "string" }, - "description": "Discovery tags" - }, - "policy": { - "type": "object", - "properties": { - "installation": { - "type": "string", - "enum": ["AVAILABLE", "BLOCKED"], - "description": "Whether the plugin may be installed" - }, - "authentication": { - "type": "string", - "enum": ["ON_INSTALL", "NEVER", "ALWAYS"], - "description": "When authentication is requested" - } - }, - "additionalProperties": false, - "description": "Installation and authentication policy" - } - }, - "additionalProperties": false - }, - "description": "List of plugins in this Codex marketplace" - } - }, - "additionalProperties": false -} diff --git a/schemas/codex-plugin.json b/schemas/codex-plugin.json deleted file mode 100644 index 44d5ebd..0000000 --- a/schemas/codex-plugin.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://raw.githubusercontent.com/ai-plugin-marketplace-template/main/schemas/codex-plugin.json", - "title": "OpenAI Codex Plugin Manifest", - "description": "Plugin manifest for OpenAI Codex (.codex-plugin/plugin.json). Mirrors developers.openai.com/codex/plugins/build. Uses strict validation — no extra fields beyond those defined here.", - "type": "object", - "required": ["name", "version", "description", "interface"], - "properties": { - "name": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "maxLength": 64, - "description": "Plugin name (lowercase, hyphens, no spaces) — must match directory name and marketplace entry" - }, - "version": { - "type": "string", - "pattern": "^\\d+\\.\\d+\\.\\d+", - "description": "Semantic version" - }, - "description": { - "type": "string", - "minLength": 1, - "maxLength": 1024, - "description": "Short description of what the plugin does" - }, - "author": { - "type": "object", - "required": ["name"], - "properties": { - "name": { "type": "string" }, - "email": { "type": "string", "format": "email" }, - "url": { "type": "string", "format": "uri" } - }, - "additionalProperties": false, - "description": "Plugin author information" - }, - "homepage": { - "type": "string", - "format": "uri", - "description": "Plugin homepage URL" - }, - "repository": { - "type": "string", - "description": "Source repository URL or shorthand" - }, - "license": { - "type": "string", - "description": "SPDX license identifier" - }, - "keywords": { - "type": "array", - "items": { "type": "string" }, - "description": "Discovery keywords" - }, - "skills": { - "type": "string", - "pattern": "^\\./", - "description": "Relative path to skills directory (e.g. ./skills). Codex auto-discovers SKILL.md files within." - }, - "mcpServers": { - "type": "string", - "pattern": "^\\./.*\\.json$", - "description": "Relative path to an MCP config JSON file (e.g. ./.mcp.json)" - }, - "apps": { - "type": "string", - "pattern": "^\\./.*\\.json$", - "description": "Relative path to an apps config JSON file (e.g. ./.app.json)" - }, - "interface": { - "type": "object", - "required": ["displayName", "shortDescription"], - "properties": { - "displayName": { - "type": "string", - "minLength": 1, - "description": "Human-readable name shown in Codex UI" - }, - "shortDescription": { - "type": "string", - "minLength": 1, - "description": "One-line description for plugin listings" - }, - "longDescription": { - "type": "string", - "description": "Multi-line description shown on the plugin detail page" - }, - "category": { - "type": "string", - "description": "Plugin category (e.g. 'developer-tools', 'productivity')" - }, - "capabilities": { - "type": "array", - "items": { "type": "string" }, - "description": "Human-readable list of plugin capabilities" - }, - "brandColor": { - "type": "string", - "pattern": "^#[0-9a-fA-F]{6}$", - "description": "Brand color as a 6-digit hex code (e.g. #1a73e8)" - }, - "composerIcon": { - "type": "string", - "description": "Path (relative to plugin root) or URL to the composer icon" - }, - "logo": { - "type": "string", - "description": "Path (relative to plugin root) or URL to the plugin logo" - }, - "screenshots": { - "type": "array", - "items": { "type": "string" }, - "description": "List of paths or URLs to screenshot images" - }, - "defaultPrompt": { - "type": "string", - "description": "Default user-facing prompt suggestion" - }, - "websiteURL": { - "type": "string", - "format": "uri", - "description": "Plugin website URL" - }, - "privacyPolicyURL": { - "type": "string", - "format": "uri", - "description": "Privacy policy URL" - }, - "termsOfServiceURL": { - "type": "string", - "format": "uri", - "description": "Terms of service URL" - } - }, - "additionalProperties": false, - "description": "User-facing interface metadata rendered by Codex" - } - }, - "additionalProperties": false -} diff --git a/schemas/marketplace.json b/schemas/marketplace.json deleted file mode 100644 index bc5fa24..0000000 --- a/schemas/marketplace.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://raw.githubusercontent.com/ai-plugin-marketplace-template/main/schemas/marketplace.json", - "title": "AI Plugin Marketplace", - "description": "Marketplace registry listing available plugins", - "type": "object", - "required": ["name", "owner", "plugins"], - "properties": { - "name": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "description": "Marketplace name (lowercase, hyphens, no spaces)" - }, - "owner": { - "type": "object", - "required": ["name"], - "properties": { - "name": { "type": "string" }, - "email": { "type": "string", "format": "email" }, - "url": { "type": "string", "format": "uri" } - }, - "additionalProperties": false, - "description": "Marketplace owner information" - }, - "metadata": { - "type": "object", - "properties": { - "pluginRoot": { "type": "string" }, - "version": { "type": "string" }, - "description": { "type": "string" } - }, - "additionalProperties": false, - "description": "Optional metadata about the marketplace" - }, - "plugins": { - "type": "array", - "items": { - "type": "object", - "required": ["name", "source"], - "properties": { - "name": { - "type": "string", - "description": "Plugin name (must match plugin manifest name)" - }, - "source": { - "oneOf": [ - { - "type": "string", - "pattern": "^\\./", - "description": "Relative path to plugin directory (must start with ./)" - }, - { - "type": "object", - "description": "GitHub, npm, or other source specification" - } - ], - "description": "Plugin source — relative path or source specification object" - }, - "description": { - "type": "string", - "description": "Plugin description" - }, - "category": { - "type": "string", - "description": "Plugin category" - }, - "tags": { - "type": "array", - "items": { "type": "string" }, - "description": "Discovery tags (use 'tags', not 'keywords')" - }, - "strict": { - "type": "boolean", - "description": "Whether to apply strict validation (default: true)" - } - }, - "additionalProperties": false - }, - "description": "List of plugins in this marketplace" - }, - "forceRemoveDeletedPlugins": { - "type": "boolean", - "description": "Whether to remove plugins that no longer appear in the manifest" - } - }, - "additionalProperties": false -} diff --git a/schemas/plugin.json b/schemas/plugin.json deleted file mode 100644 index b833940..0000000 --- a/schemas/plugin.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://raw.githubusercontent.com/ai-plugin-marketplace-template/main/schemas/plugin.json", - "title": "AI Plugin Manifest", - "description": "Plugin manifest for Claude Code (.claude-plugin/plugin.json) or Cursor (.cursor-plugin/plugin.json). Uses strict validation — no extra fields beyond those defined here.", - "type": "object", - "required": ["name"], - "properties": { - "name": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$", - "maxLength": 64, - "description": "Plugin name (lowercase, hyphens, no spaces)" - }, - "version": { - "type": "string", - "pattern": "^\\d+\\.\\d+\\.\\d+", - "description": "Semantic version (optional)" - }, - "description": { - "type": "string", - "minLength": 1, - "maxLength": 1024, - "description": "What this plugin does (optional)" - }, - "author": { - "type": "object", - "required": ["name"], - "properties": { - "name": { "type": "string" }, - "email": { "type": "string", "format": "email" }, - "url": { "type": "string", "format": "uri" } - }, - "additionalProperties": false, - "description": "Plugin author information" - }, - "homepage": { - "type": "string", - "format": "uri", - "description": "Plugin homepage URL" - }, - "repository": { - "type": "string", - "description": "Source repository URL or shorthand" - }, - "license": { - "type": "string", - "description": "SPDX license identifier" - }, - "keywords": { - "type": "array", - "items": { "type": "string" }, - "description": "Discovery keywords" - }, - "hooks": { - "oneOf": [ - { - "type": "string", - "pattern": "^\\./.*\\.json$", - "description": "Relative path to a hooks JSON file (must start with ./ and end with .json)" - }, - { - "type": "object", - "description": "Inline hooks configuration object" - } - ], - "description": "Hooks configuration — either a path string to a .json file or an inline hooks object" - }, - "commands": { - "oneOf": [ - { - "type": "string", - "pattern": "^\\./", - "description": "Relative path to a commands .md file or directory (must start with ./)" - }, - { - "type": "array", - "items": { - "type": "string", - "pattern": "^\\./", - "description": "Relative path to a commands .md file or directory" - }, - "description": "Array of relative paths to command files or directories" - }, - { - "type": "object", - "description": "Record of command name to command object" - } - ], - "description": "Commands — path string, array of paths, or record of command objects" - }, - "agents": { - "oneOf": [ - { - "type": "string", - "pattern": "^\\./.+\\.md$", - "description": "Relative path to an agent .md file (must start with ./ and end with .md)" - }, - { - "type": "array", - "items": { - "type": "string", - "pattern": "^\\./.+\\.md$", - "description": "Relative path to an agent .md file" - }, - "description": "Array of relative paths to agent .md files" - } - ], - "description": "Agent definitions — path string or array of paths to .md files" - }, - "skills": { - "oneOf": [ - { - "type": "string", - "pattern": "^\\./", - "description": "Relative path to a skill directory (must start with ./)" - }, - { - "type": "array", - "items": { - "type": "string", - "pattern": "^\\./", - "description": "Relative path to a skill directory" - }, - "description": "Array of relative paths to skill directories" - } - ], - "description": "Skill definitions — directory path or array of directory paths (not SKILL.md file paths)" - }, - "outputStyles": { - "type": "object", - "description": "Output style configuration" - }, - "mcpServers": { - "type": "object", - "description": "MCP server configuration" - }, - "lspServers": { - "type": "object", - "description": "LSP server configuration" - }, - "settings": { - "type": "object", - "description": "Plugin settings configuration" - } - }, - "additionalProperties": false -} diff --git a/scripts/codex-acceptance.ts b/scripts/codex-acceptance.ts deleted file mode 100644 index 669a07a..0000000 --- a/scripts/codex-acceptance.ts +++ /dev/null @@ -1,1100 +0,0 @@ -#!/usr/bin/env node -/** - * Codex acceptance-test driver. - * - * Codex's CLI ships no `plugin validate` / `plugin list` command today (the - * `plugins` feature is flagged `under development`), so end-to-end validation - * requires the macOS Codex desktop app. This script minimizes the human's - * involvement to one click (Install in the Codex app) + one paste (the - * introspection prompt) — the marketplace itself is registered by writing - * a `[marketplaces.]` section into `~/.codex/config.toml`. - * - * Phases: - * preflight — schema, path, and asset validation of the local plugin + - * marketplace; writes the `[marketplaces.]` entry to - * ~/.codex/config.toml (idempotent, with a dated backup); - * snapshots pre-install state; emits the copy-ready prompt. - * verify — after Install + prompt paste, reads the agent-written - * report + the on-disk cache + config.toml and prints a - * pass/fail matrix. - * cleanup — removes the marketplace entry, any plugin entries, and - * the cache directory so the machine is left as-found. - * - * Usage: - * pnpm run test:codex # preflight (writes marketplace entry) - * pnpm run test:codex:verify # run after the in-app steps - * pnpm run test:codex:cleanup # tear down - */ - -import * as fs from "node:fs"; -import * as os from "node:os"; -import * as path from "node:path"; -import { fileURLToPath } from "node:url"; -import { z } from "zod"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.resolve(__dirname, ".."); -const SCRATCH = path.join(ROOT, "scratch"); -const SNAPSHOT_FILE = path.join(SCRATCH, "codex-acceptance-snapshot.json"); -const REPORT_FILE = path.join(SCRATCH, "codex-introspection-report.json"); -const PROMPT_FILE = path.join(SCRATCH, "codex-introspection-prompt.md"); -const CODEX_HOME = path.join(os.homedir(), ".codex"); -const CACHE_ROOT = path.join(CODEX_HOME, "plugins", "cache"); -const CONFIG_TOML = path.join(CODEX_HOME, "config.toml"); - -/** - * The marketplace manifest is the single source of truth for the marketplace - * name and the plugin(s) under test. Nothing below is hardcoded to the example - * plugin, so removing it (delete its directory + marketplace entry) needs no - * edits here — `preflight` simply no-ops when no plugins are listed. - */ -const MARKETPLACE_FILE = path.join( - ROOT, - ".agents", - "plugins", - "marketplace.json", -); - -// ────────────────────────────────────────────────────────────────────────── -// Zod schemas — documented shape per developers.openai.com/codex/plugins/build -// ────────────────────────────────────────────────────────────────────────── - -const codexAuthor = z.object({ - name: z.string(), - email: z.string().optional(), - url: z.string().optional(), -}); - -const codexInterface = z.object({ - displayName: z.string().min(1), - shortDescription: z.string().min(1), - longDescription: z.string().optional(), - developerName: z.string().optional(), - category: z.string().optional(), - capabilities: z.array(z.string()).optional(), - websiteURL: z.string().optional(), - privacyPolicyURL: z.string().optional(), - termsOfServiceURL: z.string().optional(), - defaultPrompt: z.union([z.string(), z.array(z.string())]).optional(), - brandColor: z.string().optional(), - composerIcon: z.string().optional(), - logo: z.string().optional(), - screenshots: z.array(z.string()).optional(), -}); - -const codexPlugin = z - .object({ - name: z.string().regex(/^[a-z0-9][a-z0-9-]*$/), - version: z.string().regex(/^\d+\.\d+\.\d+/), - description: z.string().min(1), - author: codexAuthor.optional(), - homepage: z.string().optional(), - repository: z.string().optional(), - license: z.string().optional(), - keywords: z.array(z.string()).optional(), - skills: z.string().optional(), - mcpServers: z.string().optional(), - apps: z.string().optional(), - interface: codexInterface.optional(), - }) - .strict(); - -const codexMarketplaceEntry = z.object({ - name: z.string(), - source: z.object({ - source: z.literal("local"), - path: z.string().regex(/^\.\//, "paths must start with ./"), - }), - description: z.string().optional(), - tags: z.array(z.string()).optional(), - category: z.string().optional(), - policy: z - .object({ - installation: z.enum([ - "AVAILABLE", - "INSTALLED_BY_DEFAULT", - "NOT_AVAILABLE", - ]), - authentication: z.enum(["ON_INSTALL", "ON_FIRST_USE"]).optional(), - }) - .optional(), -}); - -const codexMarketplace = z.object({ - name: z.string(), - owner: z.object({ name: z.string() }).optional(), - metadata: z - .object({ - version: z.string().optional(), - description: z.string().optional(), - }) - .optional(), - plugins: z.array(codexMarketplaceEntry), -}); - -export type CodexMarketplace = z.infer; - -// ────────────────────────────────────────────────────────────────────────── -// Target resolution — derive marketplace + plugin from the manifest -// ────────────────────────────────────────────────────────────────────────── - -/** A fully-resolved plugin to act on. */ -export interface ResolvedTarget { - marketplaceName: string; - pluginName: string; - /** Absolute path to the plugin directory. */ - pluginDir: string; -} - -export type Resolution = - | { kind: "ok"; target: ResolvedTarget } - | { kind: "empty" } - | { kind: "ambiguous"; available: string[] } - | { kind: "not-found"; requested: string; available: string[] }; - -export type LoadResult = - | { kind: "ok"; data: CodexMarketplace } - | { kind: "missing" } - | { kind: "invalid"; issues: string }; - -/** Read + schema-validate the marketplace manifest. No selection logic. */ -export function loadMarketplace(file: string): LoadResult { - if (!fs.existsSync(file)) return { kind: "missing" }; - let text: string; - try { - text = fs.readFileSync(file, "utf8"); - } catch (e) { - return { kind: "invalid", issues: `could not read file: ${String(e)}` }; - } - let raw: unknown; - try { - raw = JSON.parse(text); - } catch (e) { - return { kind: "invalid", issues: `not valid JSON: ${String(e)}` }; - } - const parsed = codexMarketplace.safeParse(raw); - if (!parsed.success) { - return { - kind: "invalid", - issues: parsed.error.issues - .map((i) => `${i.path.join(".")}: ${i.message}`) - .join("; "), - }; - } - return { kind: "ok", data: parsed.data }; -} - -/** - * Choose which plugin to act on, given the parsed manifest and an optional - * `--plugin ` override. Pure (no filesystem access). With a single listed - * plugin and no override, that plugin is chosen automatically; with several, an - * override is required. - */ -export function resolvePluginSelection( - marketplace: CodexMarketplace, - opts: { pluginArg: string | undefined; root: string }, -): Resolution { - const available = marketplace.plugins.map((p) => p.name); - const toTarget = ( - entry: CodexMarketplace["plugins"][number], - ): ResolvedTarget => ({ - marketplaceName: marketplace.name, - pluginName: entry.name, - pluginDir: path.join(opts.root, entry.source.path), - }); - - if (opts.pluginArg !== undefined) { - const entry = marketplace.plugins.find((p) => p.name === opts.pluginArg); - return entry - ? { kind: "ok", target: toTarget(entry) } - : { kind: "not-found", requested: opts.pluginArg, available }; - } - - const [only] = marketplace.plugins; - if (marketplace.plugins.length === 0) return { kind: "empty" }; - if (marketplace.plugins.length === 1 && only) - return { kind: "ok", target: toTarget(only) }; - return { kind: "ambiguous", available }; -} - -/** - * Extract `--plugin ` / `--plugin=` from argv. Returns undefined - * when the value is missing or empty, or looks like another option (starts with - * `-`) — plugin names always start with an alphanumeric, so such a value is a - * missing argument, not a plugin. - */ -export function parsePluginArg(argv: readonly string[]): string | undefined { - for (let i = 0; i < argv.length; i++) { - const a = argv[i]; - if (a === "--plugin") { - const next = argv[i + 1]; - return next && !next.startsWith("-") ? next : undefined; - } - if (a?.startsWith("--plugin=")) { - return a.slice("--plugin=".length) || undefined; - } - } - return undefined; -} - -function formatPluginChoice(headline: string, available: string[]): string { - const list = available.map((n) => ` - ${n}`).join("\n"); - return ( - `${headline}\n` + - `Pick one with --plugin :\n${list || " (none listed)"}\n\n` + - ` e.g. pnpm run test:codex -- --plugin ${available[0] ?? ""}\n` - ); -} - -// ────────────────────────────────────────────────────────────────────────── -// Check harness -// ────────────────────────────────────────────────────────────────────────── - -interface Check { - id: string; - label: string; - status: "pass" | "fail" | "warn" | "info"; - detail?: string | undefined; -} - -const checks: Check[] = []; -const pass = (id: string, label: string, detail?: string) => - checks.push({ id, label, status: "pass", detail }); -const fail = (id: string, label: string, detail: string) => - checks.push({ id, label, status: "fail", detail }); -const warn = (id: string, label: string, detail: string) => - checks.push({ id, label, status: "warn", detail }); -const info = (id: string, label: string, detail: string) => - checks.push({ id, label, status: "info", detail }); - -function printChecks(): boolean { - let failed = 0; - for (const c of checks) { - const glyph = - c.status === "pass" - ? "✓" - : c.status === "fail" - ? "✗" - : c.status === "warn" - ? "!" - : "·"; - const line = ` ${glyph} ${c.label}`; - process.stdout.write(line + "\n"); - if (c.detail) process.stdout.write(` ${c.detail}\n`); - if (c.status === "fail") failed++; - } - return failed === 0; -} - -function readJson(file: string): T { - return JSON.parse(fs.readFileSync(file, "utf8")) as T; -} - -function ensureScratch() { - if (!fs.existsSync(SCRATCH)) fs.mkdirSync(SCRATCH, { recursive: true }); -} - -// ────────────────────────────────────────────────────────────────────────── -// Preflight -// ────────────────────────────────────────────────────────────────────────── - -function preflight(pluginArg: string | undefined): number { - process.stdout.write("── Codex acceptance: preflight ──\n\n"); - - // Resolve the target from the marketplace manifest (single source of truth). - const loaded = loadMarketplace(MARKETPLACE_FILE); - if (loaded.kind === "missing") { - fail( - "marketplace-exists", - ".agents/plugins/marketplace.json present", - MARKETPLACE_FILE, - ); - printChecks(); - return 1; - } - if (loaded.kind === "invalid") { - pass("marketplace-exists", ".agents/plugins/marketplace.json present"); - fail( - "marketplace-schema", - "marketplace.json matches documented schema", - loaded.issues, - ); - printChecks(); - return 1; - } - - const resolution = resolvePluginSelection(loaded.data, { - pluginArg, - root: ROOT, - }); - if (resolution.kind === "empty") { - process.stdout.write( - "No plugins listed in .agents/plugins/marketplace.json — nothing to test.\n" + - "Add one with `pnpm run scaffold`, then re-run `pnpm run test:codex`.\n", - ); - return 0; - } - if (resolution.kind === "ambiguous") { - process.stdout.write( - formatPluginChoice( - "This marketplace lists multiple plugins.", - resolution.available, - ), - ); - return 2; - } - if (resolution.kind === "not-found") { - process.stdout.write( - formatPluginChoice( - `No plugin named "${resolution.requested}" in the marketplace.`, - resolution.available, - ), - ); - return 2; - } - const { marketplaceName, pluginName, pluginDir } = resolution.target; - info("target", "target", `${marketplaceName} → ${pluginName}`); - - // 1. Plugin manifest schema - const manifestPath = path.join(pluginDir, ".codex-plugin", "plugin.json"); - if (!fs.existsSync(manifestPath)) { - fail("manifest-exists", "plugin.json present", manifestPath); - } else { - pass("manifest-exists", "plugin.json present"); - const parsed = codexPlugin.safeParse(readJson(manifestPath)); - if (!parsed.success) { - fail( - "manifest-schema", - "plugin.json matches documented schema", - parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "), - ); - } else { - pass("manifest-schema", "plugin.json matches documented schema"); - const m = parsed.data; - - // Name matches the marketplace entry - if (m.name !== pluginName) { - fail( - "manifest-name", - "plugin.name matches marketplace entry", - `got "${m.name}", expected "${pluginName}"`, - ); - } else pass("manifest-name", "plugin.name matches marketplace entry"); - - // Referenced paths exist - const pathRefs: Array<[string, string | undefined]> = [ - ["skills", m.skills], - ["mcpServers", m.mcpServers], - ["apps", m.apps], - ]; - for (const [field, ref] of pathRefs) { - if (!ref) continue; - const resolved = path.join(pluginDir, ref); - if (fs.existsSync(resolved)) { - pass(`path-${field}`, `${field} → ${ref} resolves`); - } else { - fail( - `path-${field}`, - `${field} → ${ref} resolves`, - `missing: ${resolved}`, - ); - } - } - - // Interface completeness - if (!m.interface) { - warn( - "interface-present", - "interface metadata present", - "desktop app will show fallback UI without it", - ); - } else { - pass("interface-present", "interface metadata present"); - - // Assets referenced by interface must exist - const assetFields = [ - ["composerIcon", m.interface.composerIcon], - ["logo", m.interface.logo], - ] as const; - for (const [field, ref] of assetFields) { - if (!ref) continue; - const resolved = path.join(pluginDir, ref); - if (fs.existsSync(resolved)) { - pass(`asset-${field}`, `interface.${field} asset exists`); - } else { - warn( - `asset-${field}`, - `interface.${field} asset exists`, - `missing: ${resolved} — desktop app will render a placeholder`, - ); - } - } - for (const shot of m.interface.screenshots ?? []) { - const resolved = path.join(pluginDir, shot); - if (!fs.existsSync(resolved)) { - warn( - `asset-shot-${shot}`, - `screenshot ${shot} exists`, - `missing: ${resolved}`, - ); - } - } - } - } - } - - // 2. Marketplace lists this plugin at the right path - pass("marketplace-exists", ".agents/plugins/marketplace.json present"); - pass("marketplace-schema", "marketplace.json matches documented schema"); - const entry = loaded.data.plugins.find((p) => p.name === pluginName); - if (!entry) { - fail( - "marketplace-lists-plugin", - `marketplace lists "${pluginName}"`, - "add a plugins[] entry in .agents/plugins/marketplace.json", - ); - } else { - pass("marketplace-lists-plugin", `marketplace lists "${pluginName}"`); - const resolved = path.join(ROOT, entry.source.path); - if (resolved !== pluginDir) { - fail( - "marketplace-path", - "source.path resolves to plugin directory", - `expected ${pluginDir}, got ${resolved}`, - ); - } else pass("marketplace-path", "source.path resolves to plugin directory"); - } - - // 3. Pre-install snapshot - ensureScratch(); - const snapshot = { - takenAt: new Date().toISOString(), - marketplaceName, - pluginName, - pluginDir: path.relative(ROOT, pluginDir), - repoRoot: ROOT, - cacheBefore: safeListDir(path.join(CACHE_ROOT, marketplaceName)), - configTomlBefore: tomlHasPluginEntry(CONFIG_TOML, pluginName, marketplaceName), - marketplaceEntryBefore: tomlHasMarketplaceEntry(CONFIG_TOML, marketplaceName), - }; - fs.writeFileSync(SNAPSHOT_FILE, JSON.stringify(snapshot, null, 2)); - info( - "snapshot", - "pre-install snapshot written", - path.relative(ROOT, SNAPSHOT_FILE), - ); - - // 4. Register the marketplace in ~/.codex/config.toml - try { - const result = registerMarketplaceInConfig(CONFIG_TOML, { - name: marketplaceName, - source: ROOT, - }); - if (result.action === "added") - pass("register", `added [marketplaces.${marketplaceName}] → config.toml`); - else if (result.action === "updated") - pass( - "register", - `updated [marketplaces.${marketplaceName}] → config.toml`, - `(was: ${result.previousSource ?? "unknown"})`, - ); - else - pass( - "register", - `[marketplaces.${marketplaceName}] already points at this repo`, - ); - if (result.backupPath) - info("register-backup", "config.toml backup", result.backupPath); - } catch (e) { - fail( - "register", - `register [marketplaces.${marketplaceName}] in config.toml`, - String(e), - ); - } - - // 5. Emit the introspection prompt - const promptBody = renderIntrospectionPrompt({ - marketplaceName, - pluginName, - reportFile: REPORT_FILE, - }); - fs.writeFileSync(PROMPT_FILE, promptBody); - info( - "prompt", - "introspection prompt written", - path.relative(ROOT, PROMPT_FILE), - ); - - const ok = printChecks(); - printManualSteps(marketplaceName, pluginName); - return ok ? 0 : 1; -} - -function safeListDir(p: string): string[] | null { - if (!fs.existsSync(p)) return null; - try { - return fs.readdirSync(p); - } catch { - return null; - } -} - -function tomlHasPluginEntry( - file: string, - pluginName: string, - marketplaceName: string, -): boolean { - if (!fs.existsSync(file)) return false; - const text = fs.readFileSync(file, "utf8"); - const needle = `[plugins."${pluginName}@${marketplaceName}"]`; - return text.includes(needle); -} - -function tomlHasMarketplaceEntry(file: string, name: string): boolean { - if (!fs.existsSync(file)) return false; - const text = fs.readFileSync(file, "utf8"); - return text.includes(`[marketplaces.${name}]`); -} - -/** - * Extract the value of a TOML string key inside a known section. Tolerates - * optional surrounding whitespace. Returns null if section/key not found. - */ -function readTomlSectionString( - text: string, - section: string, - key: string, -): string | null { - const lines = text.split("\n"); - const header = `[${section}]`; - const i = lines.findIndex((l) => l.trim() === header); - if (i === -1) return null; - for (let j = i + 1; j < lines.length; j++) { - const line = lines[j]; - if (line === undefined) break; - if (/^\[/.test(line.trim())) break; - const m = line.match( - new RegExp(`^\\s*${escapeRegex(key)}\\s*=\\s*"([^"]*)"`), - ); - if (m) return m[1] ?? null; - } - return null; -} - -function escapeRegex(s: string): string { - return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} - -/** - * Idempotently register a local marketplace in ~/.codex/config.toml. - * Writes a dated backup before the first modification, then atomically - * rewrites the file. - */ -function registerMarketplaceInConfig( - file: string, - opts: { name: string; source: string }, -): { - action: "added" | "updated" | "unchanged"; - previousSource?: string | undefined; - backupPath?: string | undefined; -} { - const header = `[marketplaces.${opts.name}]`; - const block = [ - header, - `last_updated = "${new Date().toISOString()}"`, - `source_type = "local"`, - `source = "${opts.source.replace(/"/g, '\\"')}"`, - "", - ].join("\n"); - - const exists = fs.existsSync(file); - const original = exists ? fs.readFileSync(file, "utf8") : ""; - - const sectionRegex = new RegExp( - `(^|\\n)${escapeRegex(header)}\\n(?:(?!\\[).*\\n?)*`, - "s", - ); - let next: string; - let action: "added" | "updated" | "unchanged"; - let previousSource: string | undefined; - - if (sectionRegex.test(original)) { - previousSource = - readTomlSectionString(original, `marketplaces.${opts.name}`, "source") ?? - undefined; - if (previousSource === opts.source) { - // Still rewrite to refresh `last_updated`, but mark unchanged. - next = original.replace(sectionRegex, (match) => - match.startsWith("\n") ? "\n" + block : block, - ); - action = "unchanged"; - } else { - next = original.replace(sectionRegex, (match) => - match.startsWith("\n") ? "\n" + block : block, - ); - action = "updated"; - } - } else { - const suffix = original.endsWith("\n") || original === "" ? "" : "\n"; - next = original + suffix + (original === "" ? "" : "\n") + block; - action = "added"; - } - - if (next === original) return { action: "unchanged", previousSource }; - - // Backup before modifying. - let backupPath: string | undefined; - if (exists) { - const stamp = new Date() - .toISOString() - .replace(/[-:T]/g, "") - .replace(/\..+$/, ""); - backupPath = `${file}.bak-${stamp}`; - fs.copyFileSync(file, backupPath); - } - - // Atomic write. - const tmp = `${file}.tmp-${process.pid}`; - fs.writeFileSync(tmp, next); - fs.renameSync(tmp, file); - return { action, previousSource, backupPath }; -} - -/** - * Remove the `[marketplaces.]` section, any - * `[plugins."@"]` sections, and optionally the cache dir. - */ -function unregisterMarketplaceFromConfig( - file: string, - opts: { name: string }, -): { removed: string[]; backupPath?: string } { - if (!fs.existsSync(file)) return { removed: [] }; - const original = fs.readFileSync(file, "utf8"); - const removed: string[] = []; - let next = original; - - const marketplaceRegex = new RegExp( - `(^|\\n)${escapeRegex(`[marketplaces.${opts.name}]`)}\\n(?:(?!\\[).*\\n?)*`, - "s", - ); - if (marketplaceRegex.test(next)) { - next = next.replace(marketplaceRegex, "\n"); - removed.push(`[marketplaces.${opts.name}]`); - } - - const pluginRegex = new RegExp( - `(^|\\n)\\[plugins\\."[^"]+@${escapeRegex(opts.name)}"\\]\\n(?:(?!\\[).*\\n?)*`, - "sg", - ); - const pluginMatches = next.match(pluginRegex) ?? []; - for (const m of pluginMatches) - removed.push(m.match(/\[plugins\."[^"]+@[^"]+"\]/)?.[0] ?? m.trim()); - next = next.replace(pluginRegex, "\n"); - - // Collapse runs of 3+ newlines introduced by removals. - next = next.replace(/\n{3,}/g, "\n\n"); - - if (next === original) return { removed }; - - const stamp = new Date() - .toISOString() - .replace(/[-:T]/g, "") - .replace(/\..+$/, ""); - const backupPath = `${file}.bak-${stamp}`; - fs.copyFileSync(file, backupPath); - const tmp = `${file}.tmp-${process.pid}`; - fs.writeFileSync(tmp, next); - fs.renameSync(tmp, file); - return { removed, backupPath }; -} - -function printManualSteps(marketplaceName: string, pluginName: string) { - const steps = ` -── Manual steps in the Codex desktop app ── - - The marketplace is already registered in ~/.codex/config.toml — you do - not need to click "Add marketplace". - - 1. Open (or restart) Codex.app and go to the Plugins panel. - Find the marketplace "${marketplaceName}" and click Install on - "${pluginName}". Wait until the status shows "Installed". - - 2. In the Codex composer (a new session in this repo is fine), paste - the entire contents of: - - scratch/codex-introspection-prompt.md - - and send. The agent writes its report to - scratch/codex-introspection-report.json, then replies "report - written". - - 3. Back in this terminal, run: - - pnpm run test:codex:verify - - When you're done, tear everything down with: - - pnpm run test:codex:cleanup -`; - process.stdout.write(steps + "\n"); -} - -function renderIntrospectionPrompt(opts: { - marketplaceName: string; - pluginName: string; - reportFile: string; -}): string { - const { marketplaceName, pluginName, reportFile } = opts; - return `# Codex plugin acceptance — introspection prompt - -You are being asked to self-report the state of a specific plugin installation -so an external verifier can confirm Codex accepted it correctly. Do not install -or modify anything. Just observe and write a report. - -## Target - -- Marketplace: \`${marketplaceName}\` -- Plugin: \`${pluginName}\` - -## Steps - -1. Inspect the plugin cache under \`~/.codex/plugins/cache/${marketplaceName}/${pluginName}/\`. - Resolve the single version directory that exists (it will be a commit-hash - or \`local\`). Call that \`$VERSION\`. -2. Read \`~/.codex/plugins/cache/${marketplaceName}/${pluginName}/$VERSION/.codex-plugin/plugin.json\`. -3. Read \`~/.codex/config.toml\` and find the \`[plugins."${pluginName}@${marketplaceName}"]\` - section (if present). -4. From your own runtime state, enumerate: - - Skills currently loaded from this plugin (name + absolute location). - - MCP servers currently running from this plugin (name + command + status). - - Apps/connectors attached from this plugin. -5. For each skill in the plugin's \`skills\` directory, confirm Codex's skill - loader has picked it up — compare your loaded skills list against the - filesystem. - -## Output - -Write the report as JSON to: - - ${reportFile} - -Schema: - -\`\`\`json -{ - "pluginName": "${pluginName}", - "marketplaceName": "${marketplaceName}", - "version": "", - "cachePath": "", - "configTomlEntry": true | false, - "manifest": { /* contents of .codex-plugin/plugin.json */ }, - "loadedSkills": [ { "name": "...", "location": "..." } ], - "expectedSkills": [ { "name": "...", "location": "..." } ], - "mcpServers": [ { "name": "...", "status": "...", "command": "..." } ], - "apps": [ { "name": "...", "status": "..." } ], - "notes": "" -} -\`\`\` - -After writing the file, reply with just \`report written\`. Do not summarize. -`; -} - -// ────────────────────────────────────────────────────────────────────────── -// Verify -// ────────────────────────────────────────────────────────────────────────── - -function verify(): number { - process.stdout.write("── Codex acceptance: verify ──\n\n"); - - if (!fs.existsSync(SNAPSHOT_FILE)) { - fail( - "snapshot-missing", - "pre-install snapshot exists", - "run `pnpm run test:codex` first", - ); - printChecks(); - return 1; - } - - const snapshot = readJson<{ - marketplaceName: string; - pluginName: string; - pluginDir: string; - cacheBefore: string[] | null; - configTomlBefore: boolean; - }>(SNAPSHOT_FILE); - // The snapshot records exactly which plugin preflight targeted, so verify - // stays consistent even if marketplace.json changed in between. - const { marketplaceName, pluginName } = snapshot; - const pluginDir = path.join(ROOT, snapshot.pluginDir); - - // 1. Cache directory populated - const cacheMarketplace = path.join(CACHE_ROOT, marketplaceName); - const versionDirs = - safeListDir(path.join(cacheMarketplace, pluginName)) ?? []; - const versionDir = versionDirs[0]; - if (!versionDir) { - fail( - "cache-populated", - `cache dir populated at ~/.codex/plugins/cache/${marketplaceName}/${pluginName}/`, - "did you click Install in the Codex app?", - ); - } else { - pass( - "cache-populated", - `cache dir populated (version: ${versionDirs.join(", ")})`, - ); - - // 2. Manifest in cache matches source - const cachedManifest = path.join( - cacheMarketplace, - pluginName, - versionDir, - ".codex-plugin", - "plugin.json", - ); - const sourceManifest = path.join( - pluginDir, - ".codex-plugin", - "plugin.json", - ); - if (!fs.existsSync(cachedManifest)) { - fail("cache-manifest", "cached plugin.json exists", cachedManifest); - } else { - pass("cache-manifest", "cached plugin.json exists"); - try { - const a = JSON.stringify(readJson(cachedManifest)); - const b = JSON.stringify(readJson(sourceManifest)); - if (a === b) pass("cache-manifest-matches", "cached manifest === source manifest"); - else - warn( - "cache-manifest-matches", - "cached manifest === source manifest", - "Codex may have normalized/augmented — diff manually if concerned", - ); - } catch (e) { - warn( - "cache-manifest-matches", - "cached manifest === source manifest", - String(e), - ); - } - } - } - - // 3. config.toml updated - const cfgHas = tomlHasPluginEntry(CONFIG_TOML, pluginName, marketplaceName); - const cfgLabel = `[plugins."${pluginName}@${marketplaceName}"] in config.toml`; - if (!cfgHas && snapshot.configTomlBefore) { - warn("config-entry", cfgLabel, "pre-existing entry removed — double-check"); - } else if (!cfgHas) { - fail( - "config-entry", - cfgLabel, - "no entry found — installation may not have persisted", - ); - } else { - pass("config-entry", cfgLabel); - } - - // 4. Agent introspection report - if (!fs.existsSync(REPORT_FILE)) { - fail( - "report-exists", - "agent report written", - `missing: ${path.relative(ROOT, REPORT_FILE)} — did you paste the prompt into the Codex app?`, - ); - } else { - pass("report-exists", "agent report written"); - try { - const r = readJson<{ - configTomlEntry?: boolean; - loadedSkills?: Array<{ name: string; location: string }>; - expectedSkills?: Array<{ name: string; location: string }>; - mcpServers?: Array<{ name: string; status: string }>; - apps?: Array<{ name: string; status: string }>; - notes?: string; - }>(REPORT_FILE); - - if (r.configTomlEntry === false && cfgHas) { - warn( - "report-config", - "agent saw config.toml entry", - "filesystem has it but agent reports missing — stale session?", - ); - } else if (r.configTomlEntry) { - pass("report-config", "agent confirms config.toml entry"); - } - - const loaded = r.loadedSkills ?? []; - const expected = r.expectedSkills ?? []; - if (expected.length === 0) { - info("skills-expected", "agent enumerated plugin skills", "none reported"); - } else { - const missing = expected.filter( - (e) => !loaded.some((l) => l.name === e.name), - ); - if (missing.length === 0) { - pass( - "skills-loaded", - `all ${expected.length} plugin skill(s) loaded by Codex`, - ); - } else { - fail( - "skills-loaded", - "all plugin skills loaded by Codex", - `not loaded: ${missing.map((m) => m.name).join(", ")}`, - ); - } - } - - if (r.mcpServers && r.mcpServers.length > 0) { - const ok = r.mcpServers.filter((m) => /run|start|ok/i.test(m.status)); - if (ok.length === r.mcpServers.length) { - pass( - "mcp-servers", - `all ${r.mcpServers.length} MCP server(s) running`, - ); - } else { - warn( - "mcp-servers", - "MCP servers running", - r.mcpServers.map((m) => `${m.name}: ${m.status}`).join("; "), - ); - } - } - - if (r.notes) { - info("agent-notes", "agent notes", r.notes); - } - } catch (e) { - fail( - "report-parse", - "agent report is valid JSON", - String(e), - ); - } - } - - const ok = printChecks(); - process.stdout.write( - "\n" + - (ok - ? "All automated Codex acceptance checks passed.\n" - : "One or more checks failed — see detail above.\n"), - ); - return ok ? 0 : 1; -} - -// ────────────────────────────────────────────────────────────────────────── -// Cleanup -// ────────────────────────────────────────────────────────────────────────── - -/** - * Resolve the marketplace name to clean up. Prefer the snapshot (records what - * preflight registered) so cleanup still works after the plugin/marketplace has - * been removed from the manifest; fall back to the manifest itself. - */ -function resolveMarketplaceNameForCleanup(): string | undefined { - if (fs.existsSync(SNAPSHOT_FILE)) { - try { - const s = readJson<{ marketplaceName?: string }>(SNAPSHOT_FILE); - if (s.marketplaceName) return s.marketplaceName; - } catch { - // fall through to the manifest - } - } - const loaded = loadMarketplace(MARKETPLACE_FILE); - return loaded.kind === "ok" ? loaded.data.name : undefined; -} - -function cleanup(): number { - process.stdout.write("── Codex acceptance: cleanup ──\n\n"); - - const marketplaceName = resolveMarketplaceNameForCleanup(); - - if (!marketplaceName) { - info( - "unregister", - "config.toml", - "no marketplace name found (no snapshot, no manifest) — skipping", - ); - } else { - try { - const { removed, backupPath } = unregisterMarketplaceFromConfig( - CONFIG_TOML, - { name: marketplaceName }, - ); - if (removed.length === 0) { - info("unregister", "config.toml", "no matching sections found"); - } else { - pass( - "unregister", - `removed ${removed.length} section(s) from config.toml`, - removed.join(", "), - ); - if (backupPath) - info("unregister-backup", "config.toml backup", backupPath); - } - } catch (e) { - fail("unregister", "remove config.toml sections", String(e)); - } - - const cacheDir = path.join(CACHE_ROOT, marketplaceName); - if (fs.existsSync(cacheDir)) { - try { - fs.rmSync(cacheDir, { recursive: true, force: true }); - pass("cache-cleared", `removed cache dir ${cacheDir}`); - } catch (e) { - fail("cache-cleared", "remove cache dir", String(e)); - } - } else { - info("cache-cleared", "cache dir", "already absent"); - } - } - - for (const f of [SNAPSHOT_FILE, REPORT_FILE, PROMPT_FILE]) { - if (fs.existsSync(f)) fs.rmSync(f); - } - info("scratch-cleared", "scratch artifacts removed", ""); - - const ok = printChecks(); - process.stdout.write( - "\nYou may need to restart Codex.app for the change to take effect.\n", - ); - return ok ? 0 : 1; -} - -// ────────────────────────────────────────────────────────────────────────── -// Entry -// ────────────────────────────────────────────────────────────────────────── - -function main(argv: readonly string[]): number { - const cmd = argv[2] ?? "preflight"; - const pluginArg = parsePluginArg(argv.slice(3)); - switch (cmd) { - case "preflight": - return preflight(pluginArg); - case "verify": - return verify(); - case "cleanup": - return cleanup(); - default: - process.stderr.write(`unknown subcommand: ${cmd}\n`); - process.stderr.write( - "usage: codex-acceptance.ts [preflight|verify|cleanup] [--plugin ]\n", - ); - return 2; - } -} - -// Run the CLI only when executed directly — importing this module (e.g. from -// tests) must not dispatch or call process.exit. -if ( - process.argv[1] && - path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) -) { - process.exit(main(process.argv)); -} diff --git a/src/build-hooks.ts b/src/build-hooks.ts deleted file mode 100644 index b72b7af..0000000 --- a/src/build-hooks.ts +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env node -/** - * Converts hooks YAML source files to JSON for platforms that require JSON configuration. - * - * For each plugin in plugins/, finds hooks/*.yaml files and writes corresponding hooks/*.json files. - * Only YAML sources are committed to git; JSON files are generated and gitignored. - * - * A Claude-source YAML (typically hooks/claude.yaml) is emitted once for each of the - * supported target formats: - * - * - `claude` target → hooks/claude.json (Claude Code's native tool names, e.g. "Write") - * - `gemini` target → hooks/hooks.json (Gemini CLI native tool names, e.g. "write_file") - * - * Usage: pnpm run build:hooks - * - * @see https://geminicli.com/docs/extensions/reference/ — Gemini CLI hooks format - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import { fileURLToPath } from "node:url"; -import { parse as parseYaml } from "yaml"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.resolve(__dirname, ".."); -const PLUGINS_DIR = path.join(ROOT, "plugins"); - -export type HookTarget = "claude" | "gemini"; - -/** - * Claude Code → Gemini CLI tool-name translations. - * When emitting a Gemini-format hooks file, any `matcher: ` entry - * is rewritten to the corresponding Gemini tool name. - */ -const CLAUDE_TO_GEMINI_TOOL_MATCHERS: Record = { - Read: "read_file", - Write: "write_file", - Edit: "replace", - Glob: "glob", - Grep: "search_file_content", - Bash: "run_shell_command", - Agent: "activate_skill", -}; - -interface HookEntry { - type?: string; - command?: string; - [key: string]: unknown; -} - -interface HookMatcher { - matcher?: string; - description?: string; - hooks?: HookEntry[]; - [key: string]: unknown; -} - -interface HooksFile { - hooks?: Record; - [key: string]: unknown; -} - -function isHooksFile(value: unknown): value is HooksFile { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - -/** - * Deep-clone a hooks object and translate `matcher` tool names from Claude to Gemini. - * Entries whose matcher has no Gemini equivalent are preserved unchanged (the - * matcher string may be a glob pattern or non-tool identifier). - */ -function translateHooksForGemini(source: HooksFile): HooksFile { - const cloned = JSON.parse(JSON.stringify(source)) as HooksFile; - const hooks = cloned.hooks; - if (!hooks) return cloned; - for (const event of Object.keys(hooks)) { - const matchers = hooks[event]; - if (!Array.isArray(matchers)) continue; - for (const m of matchers) { - if (typeof m.matcher === "string") { - const translated = CLAUDE_TO_GEMINI_TOOL_MATCHERS[m.matcher]; - if (translated !== undefined) { - m.matcher = translated; - } - } - } - } - return cloned; -} - -/** - * Convert a single hooks YAML file for the given plugin to the requested target format. - * Returns the output file basename (e.g. "claude.json" or "hooks.json") on success. - */ -export function convertHookFile( - hooksDir: string, - yamlFile: string, - target: HookTarget, -): string { - const yamlPath = path.join(hooksDir, yamlFile); - const content = fs.readFileSync(yamlPath, "utf-8"); - const parsed: unknown = parseYaml(content); - if (!isHooksFile(parsed)) { - throw new Error(`${yamlPath}: expected top-level object with optional "hooks" key`); - } - - if (target === "claude") { - const outputName = yamlFile.replace(/\.ya?ml$/, ".json"); - const outputPath = path.join(hooksDir, outputName); - fs.writeFileSync(outputPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8"); - return outputName; - } - - // Gemini CLI canonically looks for `hooks/hooks.json`, regardless of source filename. - const geminiShape = translateHooksForGemini(parsed); - const outputName = "hooks.json"; - const outputPath = path.join(hooksDir, outputName); - fs.writeFileSync(outputPath, JSON.stringify(geminiShape, null, 2) + "\n", "utf-8"); - return outputName; -} - -/** - * Build all hook JSON files for a single plugin. Emits one JSON file per target format - * for each YAML source found. Returns the number of output files produced. - */ -export function buildHooksForPlugin( - pluginDir: string, - pluginName: string, - targets: HookTarget[] = ["claude", "gemini"], -): number { - const hooksDir = path.join(pluginDir, "hooks"); - if (!fs.existsSync(hooksDir)) return 0; - - const yamlFiles = fs - .readdirSync(hooksDir) - .filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")); - - let count = 0; - for (const yamlFile of yamlFiles) { - for (const target of targets) { - const outputName = convertHookFile(hooksDir, yamlFile, target); - console.log(` ${pluginName}/hooks/${yamlFile} → ${outputName} (${target})`); - count++; - } - } - - return count; -} - -function main(): void { - if (!fs.existsSync(PLUGINS_DIR)) { - console.error(`plugins/ directory not found at ${PLUGINS_DIR}`); - process.exit(1); - } - - const plugins = fs - .readdirSync(PLUGINS_DIR, { withFileTypes: true }) - .filter((e) => e.isDirectory()) - .map((e) => e.name); - - if (plugins.length === 0) { - console.log("No plugins found in plugins/"); - return; - } - - console.log("Building hooks (YAML → JSON):\n"); - - let totalCount = 0; - for (const plugin of plugins) { - const pluginDir = path.join(PLUGINS_DIR, plugin); - totalCount += buildHooksForPlugin(pluginDir, plugin); - } - - if (totalCount === 0) { - console.log(" No YAML hook files found."); - } - - console.log(`\nConverted ${totalCount} hook file(s).`); -} - -if (process.argv[1] === fileURLToPath(import.meta.url)) { - main(); -} diff --git a/src/build-standalone.ts b/src/build-standalone.ts deleted file mode 100644 index f4c394f..0000000 --- a/src/build-standalone.ts +++ /dev/null @@ -1,362 +0,0 @@ -/** - * Builds standalone distribution exports for each plugin. - * - * For each plugin in plugins/, produces two platform-specific bundles: - * dist/gemini// — Gemini CLI extension (gemini-extension.json, GEMINI.md, skills/, agents/, commands/*.toml) - * dist/kiro// — Kiro plugin (POWER.md, mcp.json, steering/, skills/) - * - * Usage: pnpm run build - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.resolve(__dirname, ".."); - -/** - * `dist/` subdirectories owned and regenerated by this builder. They are wiped - * at the start of every run so the output mirrors `plugins/` exactly — removing - * or renaming a plugin leaves no stale artifacts behind (no manual cleanup). - */ -export const TARGETS = ["gemini", "kiro"] as const; - -function copyFile(src: string, dest: string): void { - fs.mkdirSync(path.dirname(dest), { recursive: true }); - fs.copyFileSync(src, dest); -} - -function copyDir(src: string, dest: string, filter?: (name: string) => boolean): void { - if (!fs.existsSync(src)) return; - fs.mkdirSync(dest, { recursive: true }); - for (const entry of fs.readdirSync(src, { withFileTypes: true })) { - if (filter && !filter(entry.name)) continue; - const srcPath = path.join(src, entry.name); - const destPath = path.join(dest, entry.name); - if (entry.isDirectory()) { - copyDir(srcPath, destPath, filter); - } else { - copyFile(srcPath, destPath); - } - } -} - -function copyIfExists(src: string, dest: string): boolean { - if (!fs.existsSync(src)) return false; - if (fs.statSync(src).isDirectory()) { - copyDir(src, dest); - } else { - copyFile(src, dest); - } - return true; -} - -function cleanDir(dir: string): void { - if (fs.existsSync(dir)) { - fs.rmSync(dir, { recursive: true }); - } -} - -interface BuildResult { - plugin: string; - gemini: string[]; - kiro: string[]; -} - -/** - * Maps Claude Code tool names (PascalCase) to Gemini CLI tool names (snake_case). - * Gemini CLI validates tool names in agent frontmatter and rejects unknown names. - */ -const CLAUDE_TO_GEMINI_TOOLS: Record = { - Read: "read_file", - Write: "write_file", - Edit: "replace", - Glob: "glob", - Grep: "search_file_content", - Bash: "run_shell_command", - Agent: "activate_skill", -}; - -/** - * Maps Claude Code tool names (PascalCase) to Kiro CLI tool names (lowercase). - * Kiro CLI agent configs use JSON with a `tools` array of lowercase names. - * - * @see ~/.kiro/agents/agent_config.json.example for reference - */ -const CLAUDE_TO_KIRO_TOOLS: Record = { - Read: "read", - Write: "write", - Edit: "write", - Glob: "glob", - Grep: "grep", - Bash: "shell", - Agent: "delegate", -}; - -/** - * Rewrites agent .md files in the Gemini standalone export, translating - * Claude Code tool names in the YAML frontmatter `tools:` list to Gemini CLI equivalents. - * Tools with no known mapping are dropped with a warning. - */ -function rewriteGeminiAgentTools(agentsDir: string): void { - if (!fs.existsSync(agentsDir)) return; - for (const file of fs.readdirSync(agentsDir)) { - if (!file.endsWith(".md")) continue; - const filePath = path.join(agentsDir, file); - let content = fs.readFileSync(filePath, "utf-8"); - - // Match YAML frontmatter tools list - content = content.replace( - /^(---\n[\s\S]*?)(tools:\n)((?:\s+-\s+\S+\n)+)([\s\S]*?---)/m, - (_match, before: string, toolsKey: string, toolsList: string, after: string) => { - const translatedLines: string[] = []; - for (const line of toolsList.split("\n")) { - const toolMatch = /^\s+-\s+(\S+)/.exec(line); - if (!toolMatch?.[1]) continue; - const claudeTool = toolMatch[1]; - const geminiTool = CLAUDE_TO_GEMINI_TOOLS[claudeTool]; - if (geminiTool) { - translatedLines.push(` - ${geminiTool}`); - } else { - console.warn(` ⚠ Agent ${file}: no Gemini equivalent for tool "${claudeTool}" — skipped`); - } - } - if (translatedLines.length === 0) { - return `${before}tools: []\n${after}`; - } - return `${before}${toolsKey}${translatedLines.join("\n")}\n${after}`; - }, - ); - - fs.writeFileSync(filePath, content); - } -} - -function buildGeminiStandalone(pluginDir: string, destDir: string): string[] { - cleanDir(destDir); - const copied: string[] = []; - - const files: [string, string][] = [ - ["gemini-extension.json", "gemini-extension.json"], - ["GEMINI.md", "GEMINI.md"], - ["README.md", "README.md"], - ["LICENSE", "LICENSE"], - ]; - - for (const [src, dest] of files) { - if (copyIfExists(path.join(pluginDir, src), path.join(destDir, dest))) { - copied.push(dest); - } - } - - const dirs: [string, string][] = [ - ["skills", "skills"], - ["agents", "agents"], - ]; - - for (const [src, dest] of dirs) { - if (copyIfExists(path.join(pluginDir, src), path.join(destDir, dest))) { - copied.push(`${dest}/`); - } - } - - // Rewrite agent tool names from Claude Code → Gemini CLI equivalents - rewriteGeminiAgentTools(path.join(destDir, "agents")); - - // commands: only .toml files (Gemini uses TOML for commands) - const commandsSrc = path.join(pluginDir, "commands"); - const commandsDest = path.join(destDir, "commands"); - if (fs.existsSync(commandsSrc)) { - const tomlFiles = fs.readdirSync(commandsSrc).filter((f) => f.endsWith(".toml")); - if (tomlFiles.length > 0) { - fs.mkdirSync(commandsDest, { recursive: true }); - for (const file of tomlFiles) { - copyFile(path.join(commandsSrc, file), path.join(commandsDest, file)); - } - copied.push("commands/ (.toml only)"); - } - } - - // hooks/hooks.json: Gemini CLI native hooks format - const hooksJsonSrc = path.join(pluginDir, "hooks", "hooks.json"); - const hooksJsonDest = path.join(destDir, "hooks", "hooks.json"); - if (fs.existsSync(hooksJsonSrc)) { - copyFile(hooksJsonSrc, hooksJsonDest); - copied.push("hooks/hooks.json"); - } - - // policies/: Gemini CLI policy TOML files (if present) - const policiesSrc = path.join(pluginDir, "policies"); - const policiesDest = path.join(destDir, "policies"); - if (fs.existsSync(policiesSrc) && fs.statSync(policiesSrc).isDirectory()) { - copyDir(policiesSrc, policiesDest); - copied.push("policies/"); - } - - return copied; -} - -/** - * Parses YAML frontmatter from a markdown agent file and generates - * a Kiro CLI agent JSON config. - */ -function buildKiroAgentJson(agentMdPath: string): Record | null { - const content = fs.readFileSync(agentMdPath, "utf-8"); - const fmMatch = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(content); - if (!fmMatch) return null; - - const frontmatter = fmMatch[1] ?? ""; - const body = fmMatch[2] ?? ""; - - const nameMatch = /^name:\s*(.+)$/m.exec(frontmatter); - const descMatch = /^description:\s*(.+)$/m.exec(frontmatter); - - const tools: string[] = []; - const toolsBlockMatch = /^tools:\n((?:\s+-\s+\S+\n?)+)/m.exec(frontmatter); - if (toolsBlockMatch?.[1]) { - for (const line of toolsBlockMatch[1].split("\n")) { - const toolMatch = /^\s+-\s+(\S+)/.exec(line); - if (!toolMatch?.[1]) continue; - const kiroTool = CLAUDE_TO_KIRO_TOOLS[toolMatch[1]]; - if (kiroTool && !tools.includes(kiroTool)) { - tools.push(kiroTool); - } - } - } - - return { - name: nameMatch?.[1] ?? path.basename(agentMdPath, ".md"), - description: descMatch?.[1] ?? "", - prompt: body.trim(), - mcpServers: {}, - tools, - toolAliases: {}, - allowedTools: [], - resources: [], - hooks: {}, - toolsSettings: {}, - includeMcpJson: true, - model: null, - }; -} - -/** - * Converts Claude Code agent .md files to Kiro CLI agent JSON configs - * under .kiro/agents/ in the destination directory. - */ -function buildKiroAgents(agentsDir: string, destDir: string): boolean { - if (!fs.existsSync(agentsDir)) return false; - const kiroAgentsDir = path.join(destDir, ".kiro", "agents"); - fs.mkdirSync(kiroAgentsDir, { recursive: true }); - - let count = 0; - for (const file of fs.readdirSync(agentsDir)) { - if (!file.endsWith(".md")) continue; - const config = buildKiroAgentJson(path.join(agentsDir, file)); - if (!config) continue; - const jsonName = file.replace(/\.md$/, ".json"); - fs.writeFileSync(path.join(kiroAgentsDir, jsonName), JSON.stringify(config, null, 2) + "\n"); - count++; - } - - return count > 0; -} - -function buildKiroStandalone(pluginDir: string, destDir: string): string[] { - cleanDir(destDir); - const copied: string[] = []; - - const files: [string, string][] = [ - ["POWER.md", "POWER.md"], - ["mcp.json", "mcp.json"], - ["README.md", "README.md"], - ["LICENSE", "LICENSE"], - ]; - - for (const [src, dest] of files) { - if (copyIfExists(path.join(pluginDir, src), path.join(destDir, dest))) { - copied.push(dest); - } - } - - const dirs: [string, string][] = [ - ["steering", "steering"], - ["skills", "skills"], - ]; - - for (const [src, dest] of dirs) { - if (copyIfExists(path.join(pluginDir, src), path.join(destDir, dest))) { - copied.push(`${dest}/`); - } - } - - // Generate .kiro/agents/ JSON configs from Claude Code agent .md files - if (buildKiroAgents(path.join(pluginDir, "agents"), destDir)) { - copied.push(".kiro/agents/"); - } - - return copied; -} - -export function buildAll(root: string = ROOT): void { - const pluginsDir = path.join(root, "plugins"); - const distDir = path.join(root, "dist"); - - if (!fs.existsSync(pluginsDir)) { - console.error(`plugins/ directory not found at ${pluginsDir}`); - process.exit(1); - } - - // Wipe the managed target roots up front so dist/ is a pure function of - // plugins/ — stale exports from removed/renamed plugins are cleared without - // any manual `rm`. - for (const target of TARGETS) { - cleanDir(path.join(distDir, target)); - } - - const plugins = fs - .readdirSync(pluginsDir, { withFileTypes: true }) - .filter((e) => e.isDirectory()) - .map((e) => e.name); - - if (plugins.length === 0) { - console.log("No plugins found in plugins/ — dist/ targets cleaned."); - return; - } - - const results: BuildResult[] = []; - - for (const plugin of plugins) { - const pluginDir = path.join(pluginsDir, plugin); - - const geminiDest = path.join(distDir, "gemini", plugin); - const kiroDest = path.join(distDir, "kiro", plugin); - - const geminiCopied = buildGeminiStandalone(pluginDir, geminiDest); - const kiroCopied = buildKiroStandalone(pluginDir, kiroDest); - - results.push({ plugin, gemini: geminiCopied, kiro: kiroCopied }); - } - - console.log("\nBuild complete — standalone exports generated:\n"); - - for (const { plugin, gemini, kiro } of results) { - console.log(` ${plugin}`); - console.log(` dist/gemini/${plugin}/`); - for (const item of gemini) { - console.log(` ${item}`); - } - console.log(` dist/kiro/${plugin}/`); - for (const item of kiro) { - console.log(` ${item}`); - } - console.log(); - } - - console.log(`Generated ${results.length} plugin(s) × 2 platforms = ${results.length * 2} standalone exports.`); -} - -if (process.argv[1] === fileURLToPath(import.meta.url)) { - buildAll(); -} diff --git a/src/scaffold.ts b/src/scaffold.ts deleted file mode 100644 index e277f7c..0000000 --- a/src/scaffold.ts +++ /dev/null @@ -1,383 +0,0 @@ -#!/usr/bin/env node -/** - * Scaffolds a new plugin directory from templates. - * - * Usage: pnpm run scaffold - * - * Plugin names must be lowercase with hyphens only (no spaces or uppercase). - * Templates are read from the templates/ directory at the repo root. - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.resolve(__dirname, ".."); -const PLUGINS_DIR = path.join(ROOT, "plugins"); -const TEMPLATES_DIR = path.join(ROOT, "templates"); -const ROOT_MARKETPLACE = path.join(ROOT, "marketplace.json"); -const CLAUDE_MARKETPLACE = path.join(ROOT, ".claude-plugin", "marketplace.json"); -const CURSOR_MARKETPLACE = path.join(ROOT, ".cursor-plugin", "marketplace.json"); -const CODEX_MARKETPLACE = path.join(ROOT, ".agents", "plugins", "marketplace.json"); - -interface TemplateVars { - name: string; - description: string; - title: string; - keywords: string; -} - -function toTitleCase(str: string): string { - return str - .split("-") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" "); -} - -function applyTemplate(template: string, vars: TemplateVars): string { - return template - .replace(/\{\{name\}\}/g, vars.name) - .replace(/\{\{description\}\}/g, vars.description) - .replace(/\{\{title\}\}/g, vars.title) - .replace(/\{\{keywords\}\}/g, vars.keywords); -} - -function readTemplate(filename: string): string { - const templatePath = path.join(TEMPLATES_DIR, filename); - if (!fs.existsSync(templatePath)) { - throw new Error(`Template not found: ${filename}`); - } - return fs.readFileSync(templatePath, "utf-8"); -} - -function writeFile(filePath: string, content: string, createdFiles: string[]): void { - const dir = path.dirname(filePath); - fs.mkdirSync(dir, { recursive: true }); - fs.writeFileSync(filePath, content, "utf-8"); - createdFiles.push(filePath); -} - -function writeGitkeep(dirPath: string, createdFiles: string[]): void { - fs.mkdirSync(dirPath, { recursive: true }); - const gitkeepPath = path.join(dirPath, ".gitkeep"); - fs.writeFileSync(gitkeepPath, "", "utf-8"); - createdFiles.push(gitkeepPath); -} - -interface MarketplaceOwner { - name: string; - email?: string; - url?: string; -} - -interface MarketplaceEntry { - name: string; - source: string; - description: string; - tags: string[]; -} - -interface CodexMarketplaceEntry { - name: string; - source: { source: "local"; path: string }; - description: string; - tags: string[]; - policy: { installation: "AVAILABLE"; authentication: "ON_INSTALL" }; -} - -interface MarketplaceJson { - name?: string; - owner?: MarketplaceOwner; - metadata?: { - version?: string; - description?: string; - pluginRoot?: string; - }; - plugins?: MarketplaceEntry[]; - [key: string]: unknown; -} - -interface CodexMarketplaceJson { - name?: string; - owner?: MarketplaceOwner; - metadata?: { - version?: string; - description?: string; - }; - plugins?: CodexMarketplaceEntry[]; - [key: string]: unknown; -} - -function updateMarketplace(marketplacePath: string, entry: MarketplaceEntry): void { - if (!fs.existsSync(marketplacePath)) { - throw new Error(`Marketplace file not found: ${marketplacePath}`); - } - - const content = fs.readFileSync(marketplacePath, "utf-8"); - const marketplace = JSON.parse(content) as MarketplaceJson; - - if (!Array.isArray(marketplace.plugins)) { - marketplace.plugins = []; - } - - // Check for duplicate - const exists = marketplace.plugins.some((p) => p.name === entry.name || p.source === entry.source); - if (!exists) { - marketplace.plugins.push(entry); - } - - fs.writeFileSync(marketplacePath, JSON.stringify(marketplace, null, 2) + "\n", "utf-8"); -} - -function updateCodexMarketplace(marketplacePath: string, entry: CodexMarketplaceEntry): void { - if (!fs.existsSync(marketplacePath)) { - throw new Error(`Marketplace file not found: ${marketplacePath}`); - } - - const content = fs.readFileSync(marketplacePath, "utf-8"); - const marketplace = JSON.parse(content) as CodexMarketplaceJson; - - if (!Array.isArray(marketplace.plugins)) { - marketplace.plugins = []; - } - - const exists = marketplace.plugins.some( - (p) => p.name === entry.name || p.source.path === entry.source.path, - ); - if (!exists) { - marketplace.plugins.push(entry); - } - - fs.writeFileSync(marketplacePath, JSON.stringify(marketplace, null, 2) + "\n", "utf-8"); -} - -function validatePluginName(name: string): void { - if (!name) { - throw new Error("Plugin name is required."); - } - if (!/^[a-z][a-z0-9-]*$/.test(name)) { - throw new Error( - `Invalid plugin name: "${name}". ` + - "Names must be lowercase, start with a letter, and contain only letters, digits, and hyphens.", - ); - } - if (name.includes("--")) { - throw new Error(`Invalid plugin name: "${name}". Names must not contain consecutive hyphens.`); - } - if (name.endsWith("-")) { - throw new Error(`Invalid plugin name: "${name}". Names must not end with a hyphen.`); - } -} - -function scaffoldPlugin(pluginName: string): void { - const pluginDir = path.join(PLUGINS_DIR, pluginName); - - if (fs.existsSync(pluginDir)) { - throw new Error(`Plugin directory already exists: ${pluginDir}`); - } - - const vars: TemplateVars = { - name: pluginName, - description: `A plugin for ${pluginName}`, - title: toTitleCase(pluginName), - keywords: "", - }; - - const createdFiles: string[] = []; - - console.log(`\nScaffolding plugin: ${pluginName}`); - console.log(`Directory: ${pluginDir}\n`); - - // .claude-plugin/plugin.json - writeFile( - path.join(pluginDir, ".claude-plugin", "plugin.json"), - applyTemplate(readTemplate("plugin.json.tmpl"), vars), - createdFiles, - ); - - // .cursor-plugin/plugin.json - writeFile( - path.join(pluginDir, ".cursor-plugin", "plugin.json"), - applyTemplate(readTemplate("cursor-plugin.json.tmpl"), vars), - createdFiles, - ); - - // .codex-plugin/plugin.json - writeFile( - path.join(pluginDir, ".codex-plugin", "plugin.json"), - applyTemplate(readTemplate("codex-plugin.json.tmpl"), vars), - createdFiles, - ); - - // gemini-extension.json - writeFile( - path.join(pluginDir, "gemini-extension.json"), - applyTemplate(readTemplate("gemini-extension.json.tmpl"), vars), - createdFiles, - ); - - // POWER.md - writeFile( - path.join(pluginDir, "POWER.md"), - applyTemplate(readTemplate("POWER.md.tmpl"), vars), - createdFiles, - ); - - // GEMINI.md - const md = String.raw; - writeFile( - path.join(pluginDir, "GEMINI.md"), - md`# ${vars.title} - -${vars.description} - -## Overview - -Describe what this plugin does for Gemini CLI users. -`, - createdFiles, - ); - - // .mcp.json - writeFile( - path.join(pluginDir, ".mcp.json"), - JSON.stringify({ mcpServers: {} }, null, 2) + "\n", - createdFiles, - ); - - // mcp.json - writeFile( - path.join(pluginDir, "mcp.json"), - JSON.stringify({ mcpServers: {} }, null, 2) + "\n", - createdFiles, - ); - - // skills//SKILL.md - writeFile( - path.join(pluginDir, "skills", pluginName, "SKILL.md"), - applyTemplate(readTemplate("SKILL.md.tmpl"), vars), - createdFiles, - ); - - // Empty dirs with .gitkeep - writeGitkeep(path.join(pluginDir, "agents"), createdFiles); - writeGitkeep(path.join(pluginDir, "rules"), createdFiles); - writeGitkeep(path.join(pluginDir, "steering"), createdFiles); - writeGitkeep(path.join(pluginDir, "commands"), createdFiles); - - // hooks/claude.yaml — YAML source for Claude Code hooks (build:hooks converts to JSON) - const yaml = String.raw; - writeFile( - path.join(pluginDir, "hooks", "claude.yaml"), - yaml`hooks: {} -`, - createdFiles, - ); - - // README.md - const readme = String.raw; - writeFile( - path.join(pluginDir, "README.md"), - readme`# ${vars.title} - -${vars.description} - -## Installation - -Install this plugin by copying it into your AI assistant's plugin directory. - -## Usage - -See \`skills/${pluginName}/SKILL.md\` for available skills. - -## License - -ISC -`, - createdFiles, - ); - - // LICENSE - const currentYear = new Date().getFullYear(); - writeFile( - path.join(pluginDir, "LICENSE"), - `ISC License - -Copyright (c) ${currentYear} - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -`, - createdFiles, - ); - - // Update marketplace manifests - const marketplaceEntry: MarketplaceEntry = { - name: pluginName, - source: `./plugins/${pluginName}`, - description: vars.description, - tags: [], - }; - - updateMarketplace(ROOT_MARKETPLACE, marketplaceEntry); - updateMarketplace(CLAUDE_MARKETPLACE, marketplaceEntry); - updateMarketplace(CURSOR_MARKETPLACE, marketplaceEntry); - - const codexMarketplaceEntry: CodexMarketplaceEntry = { - name: pluginName, - source: { source: "local", path: `./plugins/${pluginName}` }, - description: vars.description, - tags: [], - policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, - }; - updateCodexMarketplace(CODEX_MARKETPLACE, codexMarketplaceEntry); - console.log(`Updated marketplace manifests.`); - - // Print summary - console.log(`\nCreated ${createdFiles.length} files:`); - for (const file of createdFiles) { - const relPath = path.relative(ROOT, file); - console.log(` ${relPath}`); - } - - console.log(`\n✓ Plugin "${pluginName}" scaffolded successfully!`); - console.log(`\nNext steps:`); - console.log(` 1. Edit plugins/${pluginName}/POWER.md to describe your plugin`); - console.log(` 2. Edit plugins/${pluginName}/GEMINI.md to describe your plugin for Gemini CLI users`); - console.log(` 3. Edit plugins/${pluginName}/skills/${pluginName}/SKILL.md to define your skill`); - console.log(` 4. Run "pnpm run build" to build hooks, validate, and generate standalone exports`); -} - -function main(): void { - const args = process.argv.slice(2); - const pluginName = args[0]; - - if (!pluginName) { - console.error("Usage: pnpm run scaffold "); - console.error("Example: pnpm run scaffold my-plugin"); - process.exit(1); - } - - try { - validatePluginName(pluginName); - scaffoldPlugin(pluginName); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error(`\nError: ${message}`); - process.exit(1); - } -} - -if (process.argv[1] === fileURLToPath(import.meta.url)) { - main(); -} diff --git a/src/validate.ts b/src/validate.ts deleted file mode 100644 index 4aabba2..0000000 --- a/src/validate.ts +++ /dev/null @@ -1,969 +0,0 @@ -#!/usr/bin/env node -/** - * Validates all plugins and marketplace manifests are in sync. - * - * Checks: - * - Each plugin directory has all required files - * - Name fields are consistent across plugin.json, gemini-extension.json, POWER.md, and directory name - * - .mcp.json and mcp.json have the same mcpServers keys - * - Root marketplace.json files list all plugin directories - * - Marketplace entry path fields point to actual plugin directories - * - Each plugin has a README.md - * - SKILL.md frontmatter is valid per agentskills.io spec (name, description, body length) - * - Files referenced in plugin.json manifests (skills, agents, commands, hooks) exist on disk - * - Agent .md files have name and description in frontmatter - * - hooks/claude.json has a valid Claude Code hooks object - * - POWER.md frontmatter has name, description, and version - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import { fileURLToPath } from "node:url"; -import { z } from "zod"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.resolve(__dirname, ".."); -const PLUGINS_DIR = path.join(ROOT, "plugins"); -const ROOT_MARKETPLACE = path.join(ROOT, "marketplace.json"); -const CLAUDE_MARKETPLACE = path.join(ROOT, ".claude-plugin", "marketplace.json"); -const CURSOR_MARKETPLACE = path.join(ROOT, ".cursor-plugin", "marketplace.json"); -const CODEX_MARKETPLACE = path.join(ROOT, ".agents", "plugins", "marketplace.json"); - -export const REQUIRED_FILES = [ - ".claude-plugin/plugin.json", - ".cursor-plugin/plugin.json", - ".codex-plugin/plugin.json", - "gemini-extension.json", - "POWER.md", - "GEMINI.md", - ".mcp.json", - "mcp.json", -]; - -export interface ValidationResult { - passed: string[]; - failed: string[]; -} - -function pass(results: ValidationResult, msg: string): void { - results.passed.push(msg); -} - -function fail(results: ValidationResult, msg: string): void { - results.failed.push(msg); -} - -function fileExists(filePath: string): boolean { - return fs.existsSync(filePath); -} - -function readJson(filePath: string): unknown { - const content = fs.readFileSync(filePath, "utf-8"); - return JSON.parse(content) as unknown; -} - -/** - * Extract a named field from YAML frontmatter (between --- markers). - * Returns undefined if not found. - */ -export function parseFrontmatterField(content: string, field: string): string | undefined { - const fmMatch = /^---\s*\n([\s\S]*?)\n---/m.exec(content); - if (!fmMatch) return undefined; - const frontmatter = fmMatch[1] ?? ""; - const fieldMatch = new RegExp(`^${field}:\\s*(.+)$`, "m").exec(frontmatter); - return fieldMatch ? (fieldMatch[1]?.trim() ?? undefined) : undefined; -} - -function getPluginDirectories(): string[] { - if (!fs.existsSync(PLUGINS_DIR)) { - return []; - } - return fs - .readdirSync(PLUGINS_DIR, { withFileTypes: true }) - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name); -} - -export function validatePluginFiles(pluginDir: string, pluginName: string, results: ValidationResult): void { - for (const relPath of REQUIRED_FILES) { - const fullPath = path.join(pluginDir, relPath); - if (fileExists(fullPath)) { - pass(results, `[${pluginName}] Required file present: ${relPath}`); - } else { - fail(results, `[${pluginName}] Missing required file: ${relPath}`); - } - } - - // Check for at least one SKILL.md in skills/ - const skillsDir = path.join(pluginDir, "skills"); - if (fs.existsSync(skillsDir)) { - const skillFiles = findFilesRecursive(skillsDir, "SKILL.md"); - if (skillFiles.length > 0) { - pass(results, `[${pluginName}] At least one SKILL.md found in skills/ (${skillFiles.length})`); - } else { - fail(results, `[${pluginName}] No SKILL.md found in skills/`); - } - } else { - fail(results, `[${pluginName}] Missing skills/ directory`); - } -} - -function findFilesRecursive(dir: string, filename: string): string[] { - const results: string[] = []; - if (!fs.existsSync(dir)) return results; - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - results.push(...findFilesRecursive(fullPath, filename)); - } else if (entry.name === filename) { - results.push(fullPath); - } - } - return results; -} - -const PluginJsonSchema = z.object({ - name: z.string().optional(), - version: z.string().optional(), - description: z.string().optional(), - skills: z.union([z.string(), z.array(z.string())]).optional(), - agents: z.union([z.string(), z.array(z.string())]).optional(), - commands: z.union([z.string(), z.array(z.string()), z.record(z.string(), z.unknown())]).optional(), - hooks: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(), - keywords: z.array(z.string()).optional(), -}).loose(); - -const GeminiExtensionSchema = z.object({ - name: z.string(), - version: z.string().optional(), - description: z.string().optional(), -}).loose(); - -const McpJsonSchema = z.object({ - mcpServers: z.record(z.string(), z.object({ - command: z.string(), - args: z.array(z.string()).optional(), - env: z.record(z.string(), z.string()).optional(), - }).loose()).optional(), -}).loose(); - -export function validateNameConsistency(pluginDir: string, pluginName: string, results: ValidationResult): void { - const names: Record = { - "directory": pluginName, - }; - - const claudePluginPath = path.join(pluginDir, ".claude-plugin", "plugin.json"); - if (fileExists(claudePluginPath)) { - try { - const json = PluginJsonSchema.parse(readJson(claudePluginPath)); - names[".claude-plugin/plugin.json"] = json.name; - } catch { - fail(results, `[${pluginName}] Failed to parse .claude-plugin/plugin.json`); - } - } - - const cursorPluginPath = path.join(pluginDir, ".cursor-plugin", "plugin.json"); - if (fileExists(cursorPluginPath)) { - try { - const json = PluginJsonSchema.parse(readJson(cursorPluginPath)); - names[".cursor-plugin/plugin.json"] = json.name; - } catch { - fail(results, `[${pluginName}] Failed to parse .cursor-plugin/plugin.json`); - } - } - - const codexPluginPath = path.join(pluginDir, ".codex-plugin", "plugin.json"); - if (fileExists(codexPluginPath)) { - try { - const json = PluginJsonSchema.parse(readJson(codexPluginPath)); - names[".codex-plugin/plugin.json"] = json.name; - } catch { - fail(results, `[${pluginName}] Failed to parse .codex-plugin/plugin.json`); - } - } - - const geminiPath = path.join(pluginDir, "gemini-extension.json"); - if (fileExists(geminiPath)) { - try { - const json = GeminiExtensionSchema.parse(readJson(geminiPath)); - names["gemini-extension.json"] = json.name; - } catch { - fail(results, `[${pluginName}] Failed to parse gemini-extension.json`); - } - } - - const powerMdPath = path.join(pluginDir, "POWER.md"); - if (fileExists(powerMdPath)) { - try { - const content = fs.readFileSync(powerMdPath, "utf-8"); - const name = parseFrontmatterField(content, "name"); - names["POWER.md frontmatter"] = name; - } catch { - fail(results, `[${pluginName}] Failed to read POWER.md`); - } - } - - // Compare all found names against the directory name. - // Only emit a pass if at least one non-directory source was checked. - const nonDirectorySources = Object.keys(names).filter((k) => k !== "directory"); - if (nonDirectorySources.length === 0) { - fail(results, `[${pluginName}] No plugin manifest files found to validate name consistency`); - return; - } - - let allConsistent = true; - for (const source of nonDirectorySources) { - const name = names[source]; - if (name === undefined) { - fail(results, `[${pluginName}] Missing 'name' field in ${source}`); - allConsistent = false; - } else if (name !== pluginName) { - fail(results, `[${pluginName}] Name mismatch in ${source}: expected "${pluginName}", got "${name}"`); - allConsistent = false; - } - } - - if (allConsistent) { - pass(results, `[${pluginName}] Name is consistent across all files`); - } -} - -export function validateMcpSync(pluginDir: string, pluginName: string, results: ValidationResult): void { - const hiddenMcpPath = path.join(pluginDir, ".mcp.json"); - const mcpPath = path.join(pluginDir, "mcp.json"); - - if (!fileExists(hiddenMcpPath) || !fileExists(mcpPath)) { - // Missing files are reported by file presence check — skip sync check - return; - } - - try { - const hiddenMcp = McpJsonSchema.parse(readJson(hiddenMcpPath)); - const mcp = McpJsonSchema.parse(readJson(mcpPath)); - - const hiddenKeys = Object.keys(hiddenMcp.mcpServers ?? {}).sort(); - const mcpKeys = Object.keys(mcp.mcpServers ?? {}).sort(); - - const hiddenKeysStr = JSON.stringify(hiddenKeys); - const mcpKeysStr = JSON.stringify(mcpKeys); - - if (hiddenKeysStr === mcpKeysStr) { - pass(results, `[${pluginName}] .mcp.json and mcp.json have consistent mcpServers keys`); - } else { - fail( - results, - `[${pluginName}] mcpServers key mismatch — .mcp.json: [${hiddenKeys.join(", ")}], mcp.json: [${mcpKeys.join(", ")}]`, - ); - } - } catch { - fail(results, `[${pluginName}] Failed to parse .mcp.json or mcp.json for key comparison`); - } -} - -const MarketplaceEntrySchema = z.object({ - name: z.string(), - source: z.union([z.string(), z.record(z.string(), z.unknown())]), - description: z.string().optional(), - tags: z.array(z.string()).optional(), -}).loose(); - -const MarketplaceOwnerSchema = z.object({ - name: z.string(), - email: z.string().optional(), - url: z.string().optional(), -}).loose(); - -const MarketplaceJsonSchema = z.object({ - name: z.string(), - owner: MarketplaceOwnerSchema, - plugins: z.array(MarketplaceEntrySchema), - metadata: z.object({ - version: z.string().optional(), - description: z.string().optional(), - }).loose().optional(), -}).loose(); - -/** - * Resolve the source field of a marketplace entry to a filesystem path string. - * Only string sources are resolved against ROOT; object sources (GitHub, npm, etc.) are skipped. - */ -export function resolveMarketplaceSource(source: string | Record | undefined): string | undefined { - if (typeof source !== "string") return undefined; - // Strip leading "./" so we can join with ROOT cleanly - const normalized = source.startsWith("./") ? source.slice(2) : source; - return normalized; -} - -export function validateMarketplace( - marketplacePath: string, - pluginNames: string[], - label: string, - results: ValidationResult, -): void { - if (!fileExists(marketplacePath)) { - fail(results, `[marketplace] Missing ${label}`); - return; - } - - let marketplace: z.infer; - try { - marketplace = MarketplaceJsonSchema.parse(readJson(marketplacePath)); - } catch { - fail(results, `[marketplace] Failed to parse ${label}`); - return; - } - - const plugins = marketplace.plugins; - - // Check every plugin directory is listed. A single entry must match both name and source. - for (const pluginName of pluginNames) { - const expectedPath = `plugins/${pluginName}`; - const found = plugins.some((entry) => { - const resolved = resolveMarketplaceSource(entry.source); - return entry.name === pluginName && resolved === expectedPath; - }); - if (found) { - pass(results, `[marketplace] ${label} lists plugin: ${pluginName}`); - } else { - fail(results, `[marketplace] ${label} is missing plugin: ${pluginName}`); - } - } - - // Check every marketplace entry's source (if a string path) points to a real directory - for (const entry of plugins) { - if (typeof entry.source !== "string") { - // Object sources (GitHub, npm, etc.) — skip local filesystem check - pass(results, `[marketplace] ${label} entry "${entry.name}" uses object source (skipping path check)`); - continue; - } - - const normalizedSource = resolveMarketplaceSource(entry.source); - if (normalizedSource === undefined) continue; - - const resolvedPath = path.join(ROOT, normalizedSource); - if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) { - pass(results, `[marketplace] ${label} source exists: ${entry.source}`); - } else { - fail(results, `[marketplace] ${label} source does not exist or is not a directory: ${entry.source}`); - } - } -} - -export function validateReadme(pluginDir: string, pluginName: string, results: ValidationResult): void { - const readmePath = path.join(pluginDir, "README.md"); - if (fileExists(readmePath)) { - pass(results, `[${pluginName}] README.md present`); - } else { - fail(results, `[${pluginName}] Missing README.md`); - } -} - -export function validateSkillFrontmatter(pluginDir: string, pluginName: string, results: ValidationResult): void { - const skillsDir = path.join(pluginDir, "skills"); - if (!fs.existsSync(skillsDir)) return; - - const skillFiles = findFilesRecursive(skillsDir, "SKILL.md"); - - for (const skillPath of skillFiles) { - const content = fs.readFileSync(skillPath, "utf-8"); - const relPath = path.relative(pluginDir, skillPath); - // Derive expected skill name from parent directory - const parentDir = path.basename(path.dirname(skillPath)); - - // Check name field (required per agentskills.io) - const name = parseFrontmatterField(content, "name"); - if (name === undefined) { - fail(results, `[${pluginName}] ${relPath}: missing required 'name' in frontmatter`); - } else { - // Validate name constraints per agentskills.io spec - if (name.length > 64) { - fail(results, `[${pluginName}] ${relPath}: name exceeds 64 characters`); - } else if (!/^[a-z][a-z0-9-]*$/.test(name)) { - fail(results, `[${pluginName}] ${relPath}: name must be lowercase alphanumeric with hyphens, starting with a letter`); - } else if (name.includes("--")) { - fail(results, `[${pluginName}] ${relPath}: name must not contain consecutive hyphens`); - } else if (name.endsWith("-")) { - fail(results, `[${pluginName}] ${relPath}: name must not end with a hyphen`); - } else if (name !== parentDir) { - fail(results, `[${pluginName}] ${relPath}: name "${name}" does not match parent directory "${parentDir}"`); - } else { - pass(results, `[${pluginName}] ${relPath}: name "${name}" is valid and matches directory`); - } - } - - // Check description field (required per agentskills.io) - const description = parseFrontmatterField(content, "description"); - if (description === undefined || description.length === 0) { - fail(results, `[${pluginName}] ${relPath}: missing required 'description' in frontmatter`); - } else if (description.length > 1024) { - fail(results, `[${pluginName}] ${relPath}: description exceeds 1024 characters`); - } else { - pass(results, `[${pluginName}] ${relPath}: description present and valid`); - } - - // Check body length recommendation (500 lines max) - const bodyStart = content.indexOf("---", content.indexOf("---") + 3); - if (bodyStart !== -1) { - const body = content.slice(bodyStart + 3).trim(); - const bodyLines = body.split("\n").length; - if (bodyLines > 500) { - fail(results, `[${pluginName}] ${relPath}: body is ${bodyLines} lines (recommended max 500)`); - } - } - } -} - -/** - * Plugin manifest schema reflecting the real Claude Code schema. - * - * - `skills`: directory path string or array of directory path strings (not SKILL.md paths) - * - `agents`: .md file path string or array of .md file path strings - * - `commands`: path string, array of path strings, or record of command objects - * - `hooks`: path string to .json file, or inline hooks object - * - * All path strings must start with "./". - */ -const PluginManifestSchema = z.object({ - name: z.string().optional(), - skills: z.union([z.string(), z.array(z.string())]).optional(), - agents: z.union([z.string(), z.array(z.string())]).optional(), - commands: z.union([z.string(), z.array(z.string()), z.record(z.string(), z.unknown())]).optional(), - hooks: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(), -}).loose(); - -/** - * Normalize a manifest field that can be a string path, an array of string paths, - * or a non-path value (object/undefined). Returns only the string path entries. - */ -export function normalizePathField(value: string | string[] | Record | undefined): string[] { - if (value === undefined) return []; - if (typeof value === "string") return [value]; - if (Array.isArray(value)) return value.filter((v): v is string => typeof v === "string"); - // Object value (inline hooks object, record of commands) — no paths to check - return []; -} - -export function validateManifestFileRefs(pluginDir: string, pluginName: string, results: ValidationResult): void { - const manifests = [ - { relPath: ".claude-plugin/plugin.json", label: "claude-plugin" }, - { relPath: ".cursor-plugin/plugin.json", label: "cursor-plugin" }, - ]; - - for (const { relPath, label } of manifests) { - const fullPath = path.join(pluginDir, relPath); - if (!fileExists(fullPath)) continue; - - let manifest: z.infer; - try { - manifest = PluginManifestSchema.parse(readJson(fullPath)); - } catch { - continue; // Parse errors reported elsewhere - } - - // skills: directory paths — check that the directory exists - const skillPaths = normalizePathField(manifest.skills); - // agents: .md file paths — check that the file exists - const agentPaths = normalizePathField(manifest.agents); - // commands: path string(s) — check that file or directory exists - const commandPaths = normalizePathField(manifest.commands); - // hooks: path string to .json file — check that the file exists (skip inline objects) - const hookPaths = typeof manifest.hooks === "string" ? [manifest.hooks] : []; - - const refGroups: { field: string; paths: string[] }[] = [ - { field: "skills", paths: skillPaths }, - { field: "agents", paths: agentPaths }, - { field: "commands", paths: commandPaths }, - { field: "hooks", paths: hookPaths }, - ]; - - let allRefsValid = true; - for (const { field, paths: refPaths } of refGroups) { - for (const refPath of refPaths) { - // All manifest paths must start with "./" and must not contain ".." segments - if (!refPath.startsWith("./")) { - fail(results, `[${pluginName}] ${label} ${field} path must start with "./": ${refPath}`); - allRefsValid = false; - continue; - } - if (refPath.includes("..")) { - fail(results, `[${pluginName}] ${label} ${field} path must not contain "..": ${refPath}`); - allRefsValid = false; - continue; - } - const normalized = refPath.slice(2); - const resolvedPath = path.join(pluginDir, normalized); - if (!fs.existsSync(resolvedPath)) { - fail(results, `[${pluginName}] ${label} ${field} references non-existent path: ${refPath}`); - allRefsValid = false; - } - } - } - - if (allRefsValid) { - pass(results, `[${pluginName}] ${label} manifest file references are all valid`); - } - } -} - -export function validateAgentFrontmatter(pluginDir: string, pluginName: string, results: ValidationResult): void { - const agentsDir = path.join(pluginDir, "agents"); - if (!fs.existsSync(agentsDir)) return; - - const entries = fs.readdirSync(agentsDir, { withFileTypes: true }); - for (const entry of entries) { - if (!entry.isFile() || !entry.name.endsWith(".md")) continue; - const agentPath = path.join(agentsDir, entry.name); - const content = fs.readFileSync(agentPath, "utf-8"); - - const name = parseFrontmatterField(content, "name"); - const description = parseFrontmatterField(content, "description"); - - if (name === undefined) { - fail(results, `[${pluginName}] agents/${entry.name}: missing 'name' in frontmatter`); - } - if (description === undefined) { - fail(results, `[${pluginName}] agents/${entry.name}: missing 'description' in frontmatter`); - } - if (name !== undefined && description !== undefined) { - pass(results, `[${pluginName}] agents/${entry.name}: frontmatter valid (name + description)`); - } - } -} - -export function validateClaudeHooks(pluginDir: string, pluginName: string, results: ValidationResult): void { - const hooksDir = path.join(pluginDir, "hooks"); - if (!fs.existsSync(hooksDir)) return; - - const hooksPath = path.join(hooksDir, "claude.json"); - if (!fileExists(hooksPath)) return; - - try { - const HookEntrySchema = z.object({ - type: z.enum(["command"]), - command: z.string(), - }).loose(); - - const HookMatcherSchema = z.object({ - matcher: z.string().optional(), - description: z.string().optional(), - hooks: z.array(HookEntrySchema), - }).loose(); - - const HooksFileSchema = z.object({ - hooks: z.record(z.string(), z.array(HookMatcherSchema)), - }).loose(); - const parseResult = HooksFileSchema.safeParse(readJson(hooksPath)); - if (parseResult.success) { - const validEvents = new Set(["PreToolUse", "PostToolUse", "Stop", "UserPromptSubmit"]); - const hookEvents = Object.keys(parseResult.data.hooks); - const invalidEvents = hookEvents.filter((e) => !validEvents.has(e)); - if (invalidEvents.length > 0) { - fail(results, `[${pluginName}] hooks/claude.json has unknown event types: ${invalidEvents.join(", ")}`); - } else { - pass(results, `[${pluginName}] hooks/claude.json has valid hooks object`); - } - } else { - fail(results, `[${pluginName}] hooks/claude.json 'hooks' must be an object keyed by event type (PreToolUse, PostToolUse, Stop, UserPromptSubmit)`); - } - } catch { - fail(results, `[${pluginName}] hooks/claude.json is not valid JSON`); - } -} - -/** - * Schema for the Codex plugin manifest (.codex-plugin/plugin.json). - * Mirrors schemas/codex-plugin.json. `skills`, `mcpServers`, and `apps` are - * string paths (unlike Claude's skills/agents which can also be arrays). - */ -const CodexInterfaceSchema = z.object({ - displayName: z.string().min(1), - shortDescription: z.string().min(1), - longDescription: z.string().optional(), - category: z.string().optional(), - capabilities: z.array(z.string()).optional(), - brandColor: z.string().optional(), - composerIcon: z.string().optional(), - logo: z.string().optional(), - screenshots: z.array(z.string()).optional(), - defaultPrompt: z.string().optional(), - websiteURL: z.string().optional(), - privacyPolicyURL: z.string().optional(), - termsOfServiceURL: z.string().optional(), -}).loose(); - -const CodexPluginManifestSchema = z.object({ - name: z.string(), - version: z.string(), - description: z.string(), - author: z.object({ - name: z.string(), - email: z.string().optional(), - url: z.string().optional(), - }).loose().optional(), - homepage: z.string().optional(), - repository: z.string().optional(), - license: z.string().optional(), - keywords: z.array(z.string()).optional(), - skills: z.string().optional(), - mcpServers: z.string().optional(), - apps: z.string().optional(), - interface: CodexInterfaceSchema, -}).loose(); - -/** - * Required fields on the Codex interface object — these must be non-empty - * even when the rest of the interface block is minimally populated. - */ -const CODEX_INTERFACE_REQUIRED: readonly (keyof z.infer)[] = [ - "displayName", - "shortDescription", -]; - -export function validateCodexManifest(pluginDir: string, pluginName: string, results: ValidationResult): void { - const codexPath = path.join(pluginDir, ".codex-plugin", "plugin.json"); - if (!fileExists(codexPath)) { - // Missing file already reported by validatePluginFiles — skip deeper checks. - return; - } - - const parseResult = CodexPluginManifestSchema.safeParse(readJson(codexPath)); - if (!parseResult.success) { - const messages = parseResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`); - fail(results, `[${pluginName}] .codex-plugin/plugin.json schema errors: ${messages.join("; ")}`); - return; - } - const manifest = parseResult.data; - - // Required interface fields - let interfaceValid = true; - for (const field of CODEX_INTERFACE_REQUIRED) { - const value = manifest.interface[field]; - if (typeof value !== "string" || value.length === 0) { - fail(results, `[${pluginName}] .codex-plugin/plugin.json interface.${field} is required and must be a non-empty string`); - interfaceValid = false; - } - } - if (interfaceValid) { - pass(results, `[${pluginName}] .codex-plugin/plugin.json interface has required fields`); - } - - // Path fields must start with "./" and referenced files/dirs must exist. - const pathFields: { field: "skills" | "mcpServers" | "apps"; kind: "dir" | "file" }[] = [ - { field: "skills", kind: "dir" }, - { field: "mcpServers", kind: "file" }, - { field: "apps", kind: "file" }, - ]; - - let allPathsValid = true; - for (const { field, kind } of pathFields) { - const value = manifest[field]; - if (value === undefined) continue; - if (!value.startsWith("./")) { - fail(results, `[${pluginName}] .codex-plugin/plugin.json ${field} path must start with "./": ${value}`); - allPathsValid = false; - continue; - } - if (value.includes("..")) { - fail(results, `[${pluginName}] .codex-plugin/plugin.json ${field} path must not contain "..": ${value}`); - allPathsValid = false; - continue; - } - const resolved = path.join(pluginDir, value.slice(2)); - if (!fs.existsSync(resolved)) { - fail(results, `[${pluginName}] .codex-plugin/plugin.json ${field} references non-existent path: ${value}`); - allPathsValid = false; - continue; - } - const stat = fs.statSync(resolved); - if (kind === "dir" && !stat.isDirectory()) { - fail(results, `[${pluginName}] .codex-plugin/plugin.json ${field} must reference a directory: ${value}`); - allPathsValid = false; - } else if (kind === "file" && !stat.isFile()) { - fail(results, `[${pluginName}] .codex-plugin/plugin.json ${field} must reference a file: ${value}`); - allPathsValid = false; - } - } - - // MCP cross-reference: if `mcpServers` points at a file, sanity-check that it parses. - if (manifest.mcpServers !== undefined) { - const mcpResolved = path.join(pluginDir, manifest.mcpServers.replace(/^\.\//, "")); - if (fs.existsSync(mcpResolved)) { - try { - McpJsonSchema.parse(readJson(mcpResolved)); - } catch { - fail(results, `[${pluginName}] .codex-plugin/plugin.json mcpServers points to a file that is not a valid MCP config: ${manifest.mcpServers}`); - allPathsValid = false; - } - } - } - - if (allPathsValid) { - pass(results, `[${pluginName}] .codex-plugin/plugin.json paths resolve to real files/directories`); - } -} - -const CodexMarketplaceEntrySchema = z.object({ - name: z.string(), - source: z.object({ - source: z.literal("local"), - path: z.string(), - }).loose(), - description: z.string().optional(), - tags: z.array(z.string()).optional(), - policy: z.object({ - installation: z.string().optional(), - authentication: z.string().optional(), - }).loose().optional(), -}).loose(); - -const CodexMarketplaceJsonSchema = z.object({ - name: z.string().optional(), - owner: MarketplaceOwnerSchema.optional(), - metadata: z.object({ - version: z.string().optional(), - description: z.string().optional(), - }).loose().optional(), - plugins: z.array(CodexMarketplaceEntrySchema), -}).loose(); - -export function validateCodexMarketplace( - marketplacePath: string, - pluginNames: string[], - label: string, - results: ValidationResult, -): void { - if (!fileExists(marketplacePath)) { - fail(results, `[marketplace] Missing ${label}`); - return; - } - - const parseResult = CodexMarketplaceJsonSchema.safeParse(readJson(marketplacePath)); - if (!parseResult.success) { - const messages = parseResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`); - fail(results, `[marketplace] Failed to parse ${label}: ${messages.join("; ")}`); - return; - } - const marketplace = parseResult.data; - - for (const pluginName of pluginNames) { - const expectedPath = `./plugins/${pluginName}`; - const entry = marketplace.plugins.find((e) => e.name === pluginName); - if (!entry) { - fail(results, `[marketplace] ${label} is missing plugin: ${pluginName}`); - continue; - } - if (entry.source.path !== expectedPath) { - fail(results, `[marketplace] ${label} plugin "${pluginName}" expected source.path "${expectedPath}", got "${entry.source.path}"`); - continue; - } - if (entry.policy?.installation !== "AVAILABLE" || entry.policy.authentication !== "ON_INSTALL") { - fail(results, `[marketplace] ${label} plugin "${pluginName}" should declare policy { installation: "AVAILABLE", authentication: "ON_INSTALL" }`); - continue; - } - const resolvedPath = path.join(ROOT, entry.source.path.replace(/^\.\//, "")); - if (!fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isDirectory()) { - fail(results, `[marketplace] ${label} plugin "${pluginName}" source.path does not exist or is not a directory: ${entry.source.path}`); - continue; - } - pass(results, `[marketplace] ${label} lists plugin: ${pluginName}`); - } -} - -/** - * Schema for the Gemini CLI extension manifest (gemini-extension.json). - * Per geminicli.com/docs/extensions/reference: supports name/version/description - * plus mcpServers, contextFileName, excludeTools, and settings. - */ -const GeminiExtensionManifestSchema = z.object({ - name: z.string(), - version: z.string().optional(), - description: z.string().optional(), - contextFileName: z.string().optional(), - excludeTools: z.array(z.string()).optional(), - settings: z.record(z.string(), z.unknown()).optional(), - mcpServers: z.record(z.string(), z.object({ - command: z.string().optional(), - args: z.array(z.string()).optional(), - env: z.record(z.string(), z.string()).optional(), - url: z.string().optional(), - }).loose()).optional(), -}).loose(); - -/** - * Validate that the Gemini extension manifest cross-references (contextFileName, - * referenced command/agent/hook/skill directories) actually exist on disk. - * - * Gemini CLI auto-discovers the `commands/`, `agents/`, `hooks/`, and `skills/` - * directories next to the manifest — we verify that directories advertised in - * the manifest (or auto-discovered on disk) contain syntactically valid files. - */ -export function validateGeminiExtension(pluginDir: string, pluginName: string, results: ValidationResult): void { - const geminiPath = path.join(pluginDir, "gemini-extension.json"); - if (!fileExists(geminiPath)) return; - - const parseResult = GeminiExtensionManifestSchema.safeParse(readJson(geminiPath)); - if (!parseResult.success) { - const messages = parseResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`); - fail(results, `[${pluginName}] gemini-extension.json schema errors: ${messages.join("; ")}`); - return; - } - const manifest = parseResult.data; - - let allValid = true; - - // contextFileName: if declared, the file must exist on disk. - if (manifest.contextFileName !== undefined) { - const ctxPath = path.join(pluginDir, manifest.contextFileName); - if (!fs.existsSync(ctxPath) || !fs.statSync(ctxPath).isFile()) { - fail(results, `[${pluginName}] gemini-extension.json contextFileName references missing file: ${manifest.contextFileName}`); - allValid = false; - } - } - - // Auto-discovered components — if the dir exists, spot-check files are syntactically - // reasonable. If the dir is missing AND referenced by disk, fine; we don't demand - // every plugin have every component. - const commandsDir = path.join(pluginDir, "commands"); - if (fs.existsSync(commandsDir) && fs.statSync(commandsDir).isDirectory()) { - const tomlFiles = fs.readdirSync(commandsDir).filter((f) => f.endsWith(".toml")); - if (tomlFiles.length === 0) { - // No .toml is fine — Gemini simply has no commands to load. - } else { - for (const f of tomlFiles) { - const content = fs.readFileSync(path.join(commandsDir, f), "utf-8"); - if (!/^\s*(description|prompt)\s*=/m.test(content)) { - fail(results, `[${pluginName}] gemini commands/${f} is missing required TOML keys (description or prompt)`); - allValid = false; - } - } - } - } - - const agentsDir = path.join(pluginDir, "agents"); - if (fs.existsSync(agentsDir) && fs.statSync(agentsDir).isDirectory()) { - const mdFiles = fs.readdirSync(agentsDir).filter((f) => f.endsWith(".md")); - for (const f of mdFiles) { - const content = fs.readFileSync(path.join(agentsDir, f), "utf-8"); - const name = parseFrontmatterField(content, "name"); - const description = parseFrontmatterField(content, "description"); - if (name === undefined || description === undefined) { - fail(results, `[${pluginName}] gemini agents/${f} must have name and description in frontmatter`); - allValid = false; - } - } - } - - const hooksJson = path.join(pluginDir, "hooks", "hooks.json"); - // If a Gemini hooks.json exists, sanity-check it's at least valid JSON with a `hooks` root. - if (fs.existsSync(hooksJson)) { - let parsed: unknown; - try { - parsed = readJson(hooksJson); - } catch { - fail(results, `[${pluginName}] hooks/hooks.json is not valid JSON`); - allValid = false; - parsed = undefined; - } - if (parsed !== undefined) { - if (typeof parsed !== "object" || parsed === null || !("hooks" in parsed)) { - fail(results, `[${pluginName}] hooks/hooks.json must have a top-level "hooks" key`); - allValid = false; - } - } - } - - const skillsDir = path.join(pluginDir, "skills"); - if (!fs.existsSync(skillsDir)) { - fail(results, `[${pluginName}] gemini-extension.json expects skills/ directory on disk (missing)`); - allValid = false; - } - - if (allValid) { - pass(results, `[${pluginName}] gemini-extension.json cross-references are valid`); - } -} - -export function validatePowerMdFrontmatter(pluginDir: string, pluginName: string, results: ValidationResult): void { - const powerPath = path.join(pluginDir, "POWER.md"); - if (!fileExists(powerPath)) return; - - const content = fs.readFileSync(powerPath, "utf-8"); - const name = parseFrontmatterField(content, "name"); - const description = parseFrontmatterField(content, "description"); - const version = parseFrontmatterField(content, "version"); - - const missing: string[] = []; - if (name === undefined) missing.push("name"); - if (description === undefined) missing.push("description"); - if (version === undefined) missing.push("version"); - - if (missing.length === 0) { - pass(results, `[${pluginName}] POWER.md frontmatter valid (name, description, version)`); - } else { - fail(results, `[${pluginName}] POWER.md frontmatter missing: ${missing.join(", ")}`); - } -} - -function printResults(results: ValidationResult): void { - if (results.passed.length > 0) { - console.log("\n✓ Passed:"); - for (const msg of results.passed) { - console.log(` ✓ ${msg}`); - } - } - - if (results.failed.length > 0) { - console.log("\n✗ Failed:"); - for (const msg of results.failed) { - console.error(` ✗ ${msg}`); - } - } - - console.log( - `\nResults: ${results.passed.length} passed, ${results.failed.length} failed`, - ); -} - -function main(): void { - console.log("Validating AI Plugin Marketplace...\n"); - - const results: ValidationResult = { passed: [], failed: [] }; - const pluginNames = getPluginDirectories(); - - if (pluginNames.length === 0) { - console.log("No plugin directories found in plugins/"); - } else { - console.log(`Found ${pluginNames.length} plugin(s): ${pluginNames.join(", ")}`); - } - - for (const pluginName of pluginNames) { - const pluginDir = path.join(PLUGINS_DIR, pluginName); - validatePluginFiles(pluginDir, pluginName, results); - validateNameConsistency(pluginDir, pluginName, results); - validateMcpSync(pluginDir, pluginName, results); - validateReadme(pluginDir, pluginName, results); - validateSkillFrontmatter(pluginDir, pluginName, results); - validateManifestFileRefs(pluginDir, pluginName, results); - validateAgentFrontmatter(pluginDir, pluginName, results); - validateClaudeHooks(pluginDir, pluginName, results); - validateCodexManifest(pluginDir, pluginName, results); - validateGeminiExtension(pluginDir, pluginName, results); - validatePowerMdFrontmatter(pluginDir, pluginName, results); - } - - validateMarketplace(ROOT_MARKETPLACE, pluginNames, "marketplace.json", results); - validateMarketplace(CLAUDE_MARKETPLACE, pluginNames, ".claude-plugin/marketplace.json", results); - validateMarketplace(CURSOR_MARKETPLACE, pluginNames, ".cursor-plugin/marketplace.json", results); - validateCodexMarketplace(CODEX_MARKETPLACE, pluginNames, ".agents/plugins/marketplace.json", results); - - printResults(results); - - if (results.failed.length > 0) { - process.exit(1); - } -} - -if (process.argv[1] === fileURLToPath(import.meta.url)) { - main(); -} diff --git a/templates/POWER.md.tmpl b/templates/POWER.md.tmpl deleted file mode 100644 index 634c6fc..0000000 --- a/templates/POWER.md.tmpl +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: {{name}} -description: {{description}} -version: 0.0.1 ---- - -# {{title}} - -{{description}} - -## Capabilities - -- **[Capability name]**: [Description of what this capability does] -- **[Another capability]**: [Description] - -## Related Files - -- `steering/` — Steering files for Kiro -- `mcp.json` — MCP server configuration diff --git a/templates/SKILL.md.tmpl b/templates/SKILL.md.tmpl deleted file mode 100644 index 6aad17f..0000000 --- a/templates/SKILL.md.tmpl +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: {{name}} -description: {{description}} -arguments: - # - name: example-arg - # description: Description of this argument - # required: true ---- - -# {{title}} - -{{description}} - -## Procedure - -1. [Step 1] -2. [Step 2] -3. [Step 3] diff --git a/templates/agent.md.tmpl b/templates/agent.md.tmpl deleted file mode 100644 index 10f3425..0000000 --- a/templates/agent.md.tmpl +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: {{name}} -description: {{description}} -tools: [] ---- - -# {{title}} - -{{description}} - -## Principles - -- [Principle 1] -- [Principle 2] - -## Workflow - -1. [Step 1] -2. [Step 2] -3. [Step 3] diff --git a/templates/codex-plugin.json.tmpl b/templates/codex-plugin.json.tmpl deleted file mode 100644 index 7b2611a..0000000 --- a/templates/codex-plugin.json.tmpl +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "{{name}}", - "version": "0.0.1", - "description": "{{description}}", - "keywords": [{{keywords}}], - "skills": "./skills", - "mcpServers": "./.mcp.json", - "interface": { - "displayName": "{{title}}", - "shortDescription": "{{description}}", - "longDescription": "{{description}}", - "category": "developer-tools", - "capabilities": [], - "defaultPrompt": "" - } -} diff --git a/templates/cursor-plugin.json.tmpl b/templates/cursor-plugin.json.tmpl deleted file mode 100644 index 6960eac..0000000 --- a/templates/cursor-plugin.json.tmpl +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "{{name}}", - "version": "0.0.1", - "description": "{{description}}", - "keywords": [{{keywords}}], - "skills": ["./skills"], - "agents": [], - "commands": [], - "hooks": "./hooks/claude.json", - "mcpServers": "./.mcp.json" -} diff --git a/templates/gemini-extension.json.tmpl b/templates/gemini-extension.json.tmpl deleted file mode 100644 index bda14e1..0000000 --- a/templates/gemini-extension.json.tmpl +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "{{name}}", - "version": "0.0.1", - "description": "{{description}}", - "contextFileName": "GEMINI.md", - "mcpServers": {} -} diff --git a/templates/plugin.json.tmpl b/templates/plugin.json.tmpl deleted file mode 100644 index 6960eac..0000000 --- a/templates/plugin.json.tmpl +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "{{name}}", - "version": "0.0.1", - "description": "{{description}}", - "keywords": [{{keywords}}], - "skills": ["./skills"], - "agents": [], - "commands": [], - "hooks": "./hooks/claude.json", - "mcpServers": "./.mcp.json" -} diff --git a/tests/validate.test.ts b/tests/validate.test.ts deleted file mode 100644 index c91e35b..0000000 --- a/tests/validate.test.ts +++ /dev/null @@ -1,1008 +0,0 @@ -/** - * Tests for src/validate.ts - * - * @see https://agentskills.io — SKILL.md frontmatter spec - */ - -import * as fs from "node:fs"; -import * as os from "node:os"; -import * as path from "node:path"; -import { afterEach, describe, expect, it } from "vitest"; -import { - normalizePathField, - parseFrontmatterField, - REQUIRED_FILES, - resolveMarketplaceSource, - validateAgentFrontmatter, - validateClaudeHooks, - validateCodexManifest, - validateCodexMarketplace, - validateGeminiExtension, - validateManifestFileRefs, - validateMarketplace, - validateMcpSync, - validateNameConsistency, - validatePluginFiles, - validatePowerMdFrontmatter, - validateReadme, - validateSkillFrontmatter, - type ValidationResult, -} from "../src/validate.js"; - -function freshResult(): ValidationResult { - return { passed: [], failed: [] }; -} - -let tmpDir: string | undefined; - -function makeTmpDir(): string { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "validate-test-")); - return tmpDir; -} - -afterEach(() => { - if (tmpDir !== undefined) { - fs.rmSync(tmpDir, { recursive: true, force: true }); - tmpDir = undefined; - } -}); - -/** - * Write a file inside the given directory, creating intermediate dirs. - */ -function writeFile(dir: string, relPath: string, content: string): void { - const fullPath = path.join(dir, relPath); - fs.mkdirSync(path.dirname(fullPath), { recursive: true }); - fs.writeFileSync(fullPath, content, "utf-8"); -} - -// --------------------------------------------------------------------------- -// parseFrontmatterField -// --------------------------------------------------------------------------- - -describe("parseFrontmatterField", () => { - it("extracts a field from valid frontmatter", () => { - const content = "---\nname: my-skill\ndescription: A skill\n---\n# Body"; - expect(parseFrontmatterField(content, "name")).toBe("my-skill"); - expect(parseFrontmatterField(content, "description")).toBe("A skill"); - }); - - it("returns undefined for a missing field", () => { - const content = "---\nname: my-skill\n---\n# Body"; - expect(parseFrontmatterField(content, "description")).toBeUndefined(); - }); - - it("returns undefined when no frontmatter is present", () => { - const content = "# Just a heading\nSome text."; - expect(parseFrontmatterField(content, "name")).toBeUndefined(); - }); - - it("handles frontmatter with multiple fields", () => { - const content = "---\nname: test\nversion: 1.0.0\nauthor: Alice\n---\n"; - expect(parseFrontmatterField(content, "version")).toBe("1.0.0"); - expect(parseFrontmatterField(content, "author")).toBe("Alice"); - }); -}); - -// --------------------------------------------------------------------------- -// normalizePathField -// --------------------------------------------------------------------------- - -describe("normalizePathField", () => { - it("returns empty array for undefined", () => { - expect(normalizePathField(undefined)).toEqual([]); - }); - - it("wraps a string in an array", () => { - expect(normalizePathField("./skills/foo")).toEqual(["./skills/foo"]); - }); - - it("filters non-string entries from arrays", () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- testing runtime behavior with mixed types - const mixed = ["./a.md", 42, "./b.md", null] as any; - expect(normalizePathField(mixed)).toEqual(["./a.md", "./b.md"]); - }); - - it("returns empty array for objects", () => { - expect(normalizePathField({ key: "value" })).toEqual([]); - }); -}); - -// --------------------------------------------------------------------------- -// resolveMarketplaceSource -// --------------------------------------------------------------------------- - -describe("resolveMarketplaceSource", () => { - it("strips leading ./ from string sources", () => { - expect(resolveMarketplaceSource("./plugins/foo")).toBe("plugins/foo"); - }); - - it("returns string as-is when no ./ prefix", () => { - expect(resolveMarketplaceSource("plugins/foo")).toBe("plugins/foo"); - }); - - it("returns undefined for object sources", () => { - expect(resolveMarketplaceSource({ github: "owner/repo" })).toBeUndefined(); - }); - - it("returns undefined for undefined", () => { - expect(resolveMarketplaceSource(undefined)).toBeUndefined(); - }); -}); - -// --------------------------------------------------------------------------- -// validatePluginFiles -// --------------------------------------------------------------------------- - -describe("validatePluginFiles", () => { - it("passes when all required files and skills/ are present", () => { - const dir = makeTmpDir(); - for (const f of REQUIRED_FILES) { - writeFile(dir, f, "{}"); - } - writeFile(dir, "skills/my-skill/SKILL.md", "---\nname: my-skill\n---\n"); - - const results = freshResult(); - validatePluginFiles(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.length).toBeGreaterThan(0); - }); - - it("fails for each missing required file", () => { - const dir = makeTmpDir(); - // Create skills/ with a SKILL.md but no required files - writeFile(dir, "skills/my-skill/SKILL.md", "---\nname: my-skill\n---\n"); - - const results = freshResult(); - validatePluginFiles(dir, "test-plugin", results); - - expect(results.failed.length).toBe(REQUIRED_FILES.length); - for (const f of REQUIRED_FILES) { - expect(results.failed.some((msg) => msg.includes(f))).toBe(true); - } - }); - - it("fails when skills/ directory is missing", () => { - const dir = makeTmpDir(); - for (const f of REQUIRED_FILES) { - writeFile(dir, f, "{}"); - } - - const results = freshResult(); - validatePluginFiles(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("Missing skills/ directory"))).toBe(true); - }); -}); - -// --------------------------------------------------------------------------- -// validateNameConsistency -// --------------------------------------------------------------------------- - -describe("validateNameConsistency", () => { - it("passes when all manifest names match directory name", () => { - const dir = makeTmpDir(); - const name = "my-plugin"; - writeFile(dir, ".claude-plugin/plugin.json", JSON.stringify({ name })); - writeFile(dir, ".cursor-plugin/plugin.json", JSON.stringify({ name })); - writeFile(dir, "gemini-extension.json", JSON.stringify({ name })); - writeFile(dir, "POWER.md", `---\nname: ${name}\n---\n`); - - const results = freshResult(); - validateNameConsistency(dir, name, results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((msg) => msg.includes("consistent"))).toBe(true); - }); - - it("fails when a manifest name mismatches", () => { - const dir = makeTmpDir(); - writeFile(dir, ".claude-plugin/plugin.json", JSON.stringify({ name: "my-plugin" })); - writeFile(dir, "gemini-extension.json", JSON.stringify({ name: "wrong-name" })); - - const results = freshResult(); - validateNameConsistency(dir, "my-plugin", results); - - expect(results.failed.some((msg) => msg.includes("Name mismatch"))).toBe(true); - }); - - it("fails when a manifest is missing the name field", () => { - const dir = makeTmpDir(); - writeFile(dir, ".claude-plugin/plugin.json", JSON.stringify({ version: "1.0" })); - - const results = freshResult(); - validateNameConsistency(dir, "my-plugin", results); - - expect(results.failed.some((msg) => msg.includes("Missing 'name'"))).toBe(true); - }); -}); - -// --------------------------------------------------------------------------- -// validateSkillFrontmatter -// --------------------------------------------------------------------------- - -describe("validateSkillFrontmatter", () => { - it("passes for valid agentskills.io frontmatter", () => { - const dir = makeTmpDir(); - writeFile( - dir, - "skills/my-skill/SKILL.md", - "---\nname: my-skill\ndescription: A valid skill description\n---\n# Skill\nBody content here.", - ); - - const results = freshResult(); - validateSkillFrontmatter(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((msg) => msg.includes("name") && msg.includes("valid"))).toBe(true); - expect(results.passed.some((msg) => msg.includes("description"))).toBe(true); - }); - - it("fails for name exceeding 64 characters", () => { - const dir = makeTmpDir(); - const longName = "a".repeat(65); - writeFile(dir, `skills/${longName}/SKILL.md`, `---\nname: ${longName}\ndescription: Test\n---\n`); - - const results = freshResult(); - validateSkillFrontmatter(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("exceeds 64 characters"))).toBe(true); - }); - - it("fails for name with uppercase characters", () => { - const dir = makeTmpDir(); - writeFile(dir, "skills/MySkill/SKILL.md", "---\nname: MySkill\ndescription: Test\n---\n"); - - const results = freshResult(); - validateSkillFrontmatter(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("lowercase"))).toBe(true); - }); - - it("fails for name not matching parent directory", () => { - const dir = makeTmpDir(); - writeFile(dir, "skills/actual-dir/SKILL.md", "---\nname: different-name\ndescription: Test\n---\n"); - - const results = freshResult(); - validateSkillFrontmatter(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("does not match parent directory"))).toBe(true); - }); - - it("fails for missing description", () => { - const dir = makeTmpDir(); - writeFile(dir, "skills/my-skill/SKILL.md", "---\nname: my-skill\n---\n"); - - const results = freshResult(); - validateSkillFrontmatter(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("missing required 'description'"))).toBe(true); - }); -}); - -// --------------------------------------------------------------------------- -// validateClaudeHooks -// --------------------------------------------------------------------------- - -describe("validateClaudeHooks", () => { - it("passes for valid hooks object with known event types", () => { - const dir = makeTmpDir(); - writeFile( - dir, - "hooks/claude.json", - JSON.stringify({ hooks: { PostToolUse: [], PreToolUse: [] } }), - ); - - const results = freshResult(); - validateClaudeHooks(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((msg) => msg.includes("valid hooks object"))).toBe(true); - }); - - it("fails for unknown event types", () => { - const dir = makeTmpDir(); - writeFile( - dir, - "hooks/claude.json", - JSON.stringify({ hooks: { PostToolUse: [], InvalidEvent: [] } }), - ); - - const results = freshResult(); - validateClaudeHooks(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("unknown event types") && msg.includes("InvalidEvent"))).toBe( - true, - ); - }); - - it("fails for hooks as array instead of object", () => { - const dir = makeTmpDir(); - writeFile(dir, "hooks/claude.json", JSON.stringify({ hooks: [{ event: "PostToolUse" }] })); - - const results = freshResult(); - validateClaudeHooks(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("must be an object"))).toBe(true); - }); - - it("fails for invalid JSON", () => { - const dir = makeTmpDir(); - writeFile(dir, "hooks/claude.json", "{ not valid json"); - - const results = freshResult(); - validateClaudeHooks(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("not valid JSON"))).toBe(true); - }); -}); - -// --------------------------------------------------------------------------- -// validateAgentFrontmatter -// --------------------------------------------------------------------------- - -describe("validateAgentFrontmatter", () => { - it("passes when name and description are present", () => { - const dir = makeTmpDir(); - writeFile(dir, "agents/my-agent.md", "---\nname: my-agent\ndescription: Does things\n---\n# Agent"); - - const results = freshResult(); - validateAgentFrontmatter(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((msg) => msg.includes("frontmatter valid"))).toBe(true); - }); - - it("fails when name is missing", () => { - const dir = makeTmpDir(); - writeFile(dir, "agents/my-agent.md", "---\ndescription: Does things\n---\n"); - - const results = freshResult(); - validateAgentFrontmatter(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("missing 'name'"))).toBe(true); - }); - - it("fails when description is missing", () => { - const dir = makeTmpDir(); - writeFile(dir, "agents/my-agent.md", "---\nname: my-agent\n---\n"); - - const results = freshResult(); - validateAgentFrontmatter(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("missing 'description'"))).toBe(true); - }); -}); - -// --------------------------------------------------------------------------- -// validateManifestFileRefs -// --------------------------------------------------------------------------- - -describe("validateManifestFileRefs", () => { - it("passes when all referenced paths exist", () => { - const dir = makeTmpDir(); - writeFile(dir, "skills/evaluate-skill/SKILL.md", "---\nname: evaluate-skill\n---\n"); - writeFile(dir, "agents/experimenter.md", "---\nname: experimenter\n---\n"); - writeFile(dir, "commands/evaluate.md", "# Command"); - writeFile(dir, "hooks/claude.json", "{}"); - writeFile( - dir, - ".claude-plugin/plugin.json", - JSON.stringify({ - name: "test", - skills: ["./skills/evaluate-skill"], - agents: ["./agents/experimenter.md"], - commands: ["./commands/evaluate.md"], - hooks: "./hooks/claude.json", - }), - ); - - const results = freshResult(); - validateManifestFileRefs(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((msg) => msg.includes("references are all valid"))).toBe(true); - }); - - it("fails when a referenced skill directory is missing", () => { - const dir = makeTmpDir(); - writeFile( - dir, - ".claude-plugin/plugin.json", - JSON.stringify({ - name: "test", - skills: ["./skills/nonexistent"], - }), - ); - - const results = freshResult(); - validateManifestFileRefs(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("non-existent path") && msg.includes("skills/nonexistent"))).toBe( - true, - ); - }); - - it("fails when a referenced agent file is missing", () => { - const dir = makeTmpDir(); - writeFile( - dir, - ".claude-plugin/plugin.json", - JSON.stringify({ - name: "test", - agents: ["./agents/missing.md"], - }), - ); - - const results = freshResult(); - validateManifestFileRefs(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("non-existent path") && msg.includes("agents/missing.md"))).toBe( - true, - ); - }); -}); - -// --------------------------------------------------------------------------- -// validatePowerMdFrontmatter -// --------------------------------------------------------------------------- - -describe("validatePowerMdFrontmatter", () => { - it("passes when all fields are present", () => { - const dir = makeTmpDir(); - writeFile(dir, "POWER.md", "---\nname: my-plugin\ndescription: A plugin\nversion: 1.0.0\n---\n"); - - const results = freshResult(); - validatePowerMdFrontmatter(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((msg) => msg.includes("POWER.md frontmatter valid"))).toBe(true); - }); - - it("fails when fields are missing", () => { - const dir = makeTmpDir(); - writeFile(dir, "POWER.md", "---\nname: my-plugin\n---\n"); - - const results = freshResult(); - validatePowerMdFrontmatter(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("description") && msg.includes("version"))).toBe(true); - }); -}); - -// --------------------------------------------------------------------------- -// validateReadme -// --------------------------------------------------------------------------- - -describe("validateReadme", () => { - it("passes when README.md is present", () => { - const dir = makeTmpDir(); - writeFile(dir, "README.md", "# Plugin"); - - const results = freshResult(); - validateReadme(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((msg) => msg.includes("README.md present"))).toBe(true); - }); - - it("fails when README.md is missing", () => { - const dir = makeTmpDir(); - - const results = freshResult(); - validateReadme(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("Missing README.md"))).toBe(true); - }); -}); - -// --------------------------------------------------------------------------- -// validateMcpSync -// --------------------------------------------------------------------------- - -describe("validateMcpSync", () => { - it("passes when .mcp.json and mcp.json have the same keys", () => { - const dir = makeTmpDir(); - writeFile(dir, ".mcp.json", JSON.stringify({ mcpServers: { "my-server": { command: "node" } } })); - writeFile(dir, "mcp.json", JSON.stringify({ mcpServers: { "my-server": { command: "node" } } })); - - const results = freshResult(); - validateMcpSync(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((msg) => msg.includes("consistent mcpServers keys"))).toBe(true); - }); - - it("passes when both have empty mcpServers", () => { - const dir = makeTmpDir(); - writeFile(dir, ".mcp.json", JSON.stringify({ mcpServers: {} })); - writeFile(dir, "mcp.json", JSON.stringify({ mcpServers: {} })); - - const results = freshResult(); - validateMcpSync(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((msg) => msg.includes("consistent"))).toBe(true); - }); - - it("fails when mcpServers keys differ", () => { - const dir = makeTmpDir(); - writeFile(dir, ".mcp.json", JSON.stringify({ mcpServers: { "server-a": { command: "node" } } })); - writeFile(dir, "mcp.json", JSON.stringify({ mcpServers: { "server-b": { command: "node" } } })); - - const results = freshResult(); - validateMcpSync(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes("key mismatch"))).toBe(true); - }); - - it("skips when one of the files is missing", () => { - const dir = makeTmpDir(); - writeFile(dir, ".mcp.json", JSON.stringify({ mcpServers: {} })); - // mcp.json missing - - const results = freshResult(); - validateMcpSync(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed).toEqual([]); - }); -}); - -// --------------------------------------------------------------------------- -// validateMarketplace -// --------------------------------------------------------------------------- - -/** - * Helper to create a minimal valid marketplace JSON file. - */ -function writeMarketplace( - dir: string, - filename: string, - plugins: Array<{ name: string; source: string }>, -): string { - const filePath = path.join(dir, filename); - const content = { - name: "test-marketplace", - owner: { name: "Test" }, - plugins: plugins.map((p) => ({ ...p, description: "", tags: [] })), - }; - writeFile(dir, filename, JSON.stringify(content)); - return filePath; -} - -describe("validateMarketplace", () => { - it("validates root marketplace.json alongside tool-specific variants", () => { - const dir = makeTmpDir(); - const mpPath = writeMarketplace(dir, "marketplace.json", [ - { name: "my-plugin", source: "./plugins/my-plugin" }, - ]); - - const results = freshResult(); - validateMarketplace(mpPath, ["my-plugin"], "marketplace.json", results); - - expect(results.passed.some((msg) => msg.includes("lists plugin: my-plugin"))).toBe(true); - expect(results.failed.every((msg) => !msg.includes("missing plugin"))).toBe(true); - }); - - it("passes the listing check when plugin name and source both match in the same entry", () => { - const dir = makeTmpDir(); - const mpPath = writeMarketplace(dir, "marketplace.json", [ - { name: "my-plugin", source: "./plugins/my-plugin" }, - ]); - - const results = freshResult(); - validateMarketplace(mpPath, ["my-plugin"], "marketplace.json", results); - - // The listing check should pass (name + source match in one entry). - // The source existence check may fail since ROOT isn't our temp dir — that's fine. - expect(results.passed.some((msg) => msg.includes("lists plugin: my-plugin"))).toBe(true); - expect(results.failed.every((msg) => !msg.includes("missing plugin"))).toBe(true); - }); - - it("fails when name matches one entry but source matches a different entry", () => { - const dir = makeTmpDir(); - fs.mkdirSync(path.join(dir, "plugins", "alpha"), { recursive: true }); - fs.mkdirSync(path.join(dir, "plugins", "beta"), { recursive: true }); - // Name "alpha" appears in one entry, source "plugins/alpha" appears in another - const mpPath = writeMarketplace(dir, "marketplace.json", [ - { name: "alpha", source: "./plugins/beta" }, - { name: "beta", source: "./plugins/alpha" }, - ]); - - const results = freshResult(); - validateMarketplace(mpPath, ["alpha"], "marketplace.json", results); - - expect(results.failed.some((msg) => msg.includes("missing plugin: alpha"))).toBe(true); - }); - - it("fails when plugin is not listed at all", () => { - const dir = makeTmpDir(); - const mpPath = writeMarketplace(dir, "marketplace.json", []); - - const results = freshResult(); - validateMarketplace(mpPath, ["my-plugin"], "marketplace.json", results); - - expect(results.failed.some((msg) => msg.includes("missing plugin: my-plugin"))).toBe(true); - }); - - it("fails when marketplace file is missing", () => { - const dir = makeTmpDir(); - - const results = freshResult(); - validateMarketplace(path.join(dir, "nonexistent.json"), ["my-plugin"], "marketplace.json", results); - - expect(results.failed.some((msg) => msg.includes("Missing"))).toBe(true); - }); -}); - -// --------------------------------------------------------------------------- -// validateManifestFileRefs — path validation -// --------------------------------------------------------------------------- - -// --------------------------------------------------------------------------- -// validateCodexManifest -// --------------------------------------------------------------------------- - -/** - * Minimal valid Codex plugin.json payload — used as a starting point for negative-path tests. - */ -function baseCodexManifest(overrides: Record = {}): Record { - return { - name: "test-plugin", - version: "0.0.1", - description: "Test", - skills: "./skills", - mcpServers: "./.mcp.json", - interface: { - displayName: "Test Plugin", - shortDescription: "A test plugin", - }, - ...overrides, - }; -} - -describe("validateCodexManifest", () => { - it("passes for a valid manifest with existing paths", () => { - const dir = makeTmpDir(); - writeFile(dir, "skills/test-skill/SKILL.md", "---\nname: test-skill\n---\n"); - writeFile(dir, ".mcp.json", JSON.stringify({ mcpServers: {} })); - writeFile( - dir, - ".codex-plugin/plugin.json", - JSON.stringify(baseCodexManifest()), - ); - - const results = freshResult(); - validateCodexManifest(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((m) => m.includes("interface has required fields"))).toBe(true); - expect(results.passed.some((m) => m.includes("paths resolve"))).toBe(true); - }); - - it("skips deep checks when the manifest file is absent", () => { - const dir = makeTmpDir(); - const results = freshResult(); - validateCodexManifest(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed).toEqual([]); - }); - - it("fails when a required top-level field is missing", () => { - const dir = makeTmpDir(); - writeFile( - dir, - ".codex-plugin/plugin.json", - JSON.stringify({ - name: "test-plugin", - version: "0.0.1", - interface: { displayName: "X", shortDescription: "Y" }, - }), - ); - - const results = freshResult(); - validateCodexManifest(dir, "test-plugin", results); - - expect(results.failed.some((m) => m.includes("schema errors") && m.includes("description"))).toBe(true); - }); - - it("fails when interface.displayName is empty", () => { - const dir = makeTmpDir(); - writeFile(dir, "skills/.gitkeep", ""); - writeFile(dir, ".mcp.json", JSON.stringify({ mcpServers: {} })); - writeFile( - dir, - ".codex-plugin/plugin.json", - JSON.stringify( - baseCodexManifest({ - interface: { displayName: "", shortDescription: "ok" }, - }), - ), - ); - - const results = freshResult(); - validateCodexManifest(dir, "test-plugin", results); - - expect(results.failed.some((m) => m.includes("displayName"))).toBe(true); - }); - - it("fails when interface.shortDescription is missing", () => { - const dir = makeTmpDir(); - writeFile( - dir, - ".codex-plugin/plugin.json", - JSON.stringify( - baseCodexManifest({ - interface: { displayName: "OK" }, - }), - ), - ); - - const results = freshResult(); - validateCodexManifest(dir, "test-plugin", results); - - expect(results.failed.some((m) => m.includes("shortDescription"))).toBe(true); - }); - - it("fails when a path field is not ./-prefixed", () => { - const dir = makeTmpDir(); - writeFile( - dir, - ".codex-plugin/plugin.json", - JSON.stringify( - baseCodexManifest({ - skills: "skills", - }), - ), - ); - - const results = freshResult(); - validateCodexManifest(dir, "test-plugin", results); - - expect(results.failed.some((m) => m.includes('must start with "./"'))).toBe(true); - }); - - it("fails when a referenced skills directory does not exist", () => { - const dir = makeTmpDir(); - writeFile( - dir, - ".codex-plugin/plugin.json", - JSON.stringify( - baseCodexManifest({ - skills: "./skills-missing", - }), - ), - ); - - const results = freshResult(); - validateCodexManifest(dir, "test-plugin", results); - - expect( - results.failed.some( - (m) => m.includes("non-existent path") && m.includes("./skills-missing"), - ), - ).toBe(true); - }); -}); - -// --------------------------------------------------------------------------- -// validateCodexMarketplace -// --------------------------------------------------------------------------- - -/** - * Writes a Codex-shaped marketplace JSON to the given directory and returns the full path. - */ -function writeCodexMarketplace( - dir: string, - filename: string, - plugins: Array>, -): string { - const content = { - name: "test-marketplace", - owner: { name: "Test" }, - plugins, - }; - writeFile(dir, filename, JSON.stringify(content)); - return path.join(dir, filename); -} - -describe("validateCodexMarketplace", () => { - it("passes when a plugin is listed with the expected source and policy", () => { - const dir = makeTmpDir(); - // Create the plugin directory under ROOT — codex validator resolves ./plugins/ against ROOT, - // so we can't easily satisfy that from a tmp dir. The listing+policy checks still run before the - // path existence check, and expectation here is only that listing passes even when ROOT path doesn't match. - const mpPath = writeCodexMarketplace(dir, "marketplace.json", [ - { - name: "skill-evaluator", - source: { source: "local", path: "./plugins/skill-evaluator" }, - description: "", - tags: [], - policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, - }, - ]); - - const results = freshResult(); - validateCodexMarketplace(mpPath, ["skill-evaluator"], "marketplace.json", results); - - // Either the path-existence check succeeds (if ROOT/plugins/skill-evaluator exists from the worktree) - // or it fails with a specific message — policy and listing checks should NOT fail either way. - expect(results.failed.every((m) => !m.includes("must use source.source"))).toBe(true); - expect(results.failed.every((m) => !m.includes("expected source.path"))).toBe(true); - expect(results.failed.every((m) => !m.includes("should declare policy"))).toBe(true); - expect(results.failed.every((m) => !m.includes("is missing plugin"))).toBe(true); - }); - - it("fails when plugin is missing from the marketplace", () => { - const dir = makeTmpDir(); - const mpPath = writeCodexMarketplace(dir, "marketplace.json", []); - - const results = freshResult(); - validateCodexMarketplace(mpPath, ["skill-evaluator"], "marketplace.json", results); - - expect(results.failed.some((m) => m.includes("missing plugin: skill-evaluator"))).toBe(true); - }); - - it("fails when source.path does not match expected ./plugins/", () => { - const dir = makeTmpDir(); - const mpPath = writeCodexMarketplace(dir, "marketplace.json", [ - { - name: "foo", - source: { source: "local", path: "./somewhere/foo" }, - policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, - }, - ]); - - const results = freshResult(); - validateCodexMarketplace(mpPath, ["foo"], "marketplace.json", results); - - expect(results.failed.some((m) => m.includes("expected source.path"))).toBe(true); - }); - - it("fails when policy is missing or wrong", () => { - const dir = makeTmpDir(); - const mpPath = writeCodexMarketplace(dir, "marketplace.json", [ - { - name: "foo", - source: { source: "local", path: "./plugins/foo" }, - // policy intentionally absent - }, - ]); - - const results = freshResult(); - validateCodexMarketplace(mpPath, ["foo"], "marketplace.json", results); - - expect(results.failed.some((m) => m.includes("should declare policy"))).toBe(true); - }); - - it("fails when the marketplace file is missing", () => { - const dir = makeTmpDir(); - const results = freshResult(); - validateCodexMarketplace(path.join(dir, "nonexistent.json"), ["foo"], "marketplace.json", results); - - expect(results.failed.some((m) => m.includes("Missing"))).toBe(true); - }); -}); - -// --------------------------------------------------------------------------- -// validateGeminiExtension -// --------------------------------------------------------------------------- - -describe("validateGeminiExtension", () => { - it("passes when all referenced components exist on disk", () => { - const dir = makeTmpDir(); - writeFile(dir, "gemini-extension.json", JSON.stringify({ - name: "test", - contextFileName: "GEMINI.md", - })); - writeFile(dir, "GEMINI.md", "# context"); - writeFile(dir, "skills/.gitkeep", ""); - writeFile(dir, "commands/evaluate.toml", 'description = "x"\nprompt = "y"'); - writeFile(dir, "agents/foo.md", "---\nname: foo\ndescription: bar\n---\n# Foo"); - - const results = freshResult(); - validateGeminiExtension(dir, "test-plugin", results); - - expect(results.failed).toEqual([]); - expect(results.passed.some((m) => m.includes("cross-references are valid"))).toBe(true); - }); - - it("fails when contextFileName points to a missing file", () => { - const dir = makeTmpDir(); - writeFile(dir, "gemini-extension.json", JSON.stringify({ - name: "test", - contextFileName: "MISSING.md", - })); - writeFile(dir, "skills/.gitkeep", ""); - - const results = freshResult(); - validateGeminiExtension(dir, "test-plugin", results); - - expect(results.failed.some((m) => m.includes("contextFileName") && m.includes("MISSING.md"))).toBe(true); - }); - - it("fails when a command TOML is missing required keys", () => { - const dir = makeTmpDir(); - writeFile(dir, "gemini-extension.json", JSON.stringify({ name: "test" })); - writeFile(dir, "skills/.gitkeep", ""); - writeFile(dir, "commands/bad.toml", "# no description or prompt"); - - const results = freshResult(); - validateGeminiExtension(dir, "test-plugin", results); - - expect(results.failed.some((m) => m.includes("commands/bad.toml"))).toBe(true); - }); - - it("fails when an agent md is missing name/description frontmatter", () => { - const dir = makeTmpDir(); - writeFile(dir, "gemini-extension.json", JSON.stringify({ name: "test" })); - writeFile(dir, "skills/.gitkeep", ""); - writeFile(dir, "agents/broken.md", "---\nname: broken\n---\n# no description"); - - const results = freshResult(); - validateGeminiExtension(dir, "test-plugin", results); - - expect(results.failed.some((m) => m.includes("agents/broken.md"))).toBe(true); - }); - - it("fails when skills/ directory is missing", () => { - const dir = makeTmpDir(); - writeFile(dir, "gemini-extension.json", JSON.stringify({ name: "test" })); - - const results = freshResult(); - validateGeminiExtension(dir, "test-plugin", results); - - expect(results.failed.some((m) => m.includes("skills/"))).toBe(true); - }); - - it("fails when hooks/hooks.json is malformed", () => { - const dir = makeTmpDir(); - writeFile(dir, "gemini-extension.json", JSON.stringify({ name: "test" })); - writeFile(dir, "skills/.gitkeep", ""); - writeFile(dir, "hooks/hooks.json", "{ not valid json"); - - const results = freshResult(); - validateGeminiExtension(dir, "test-plugin", results); - - expect(results.failed.some((m) => m.includes("hooks.json"))).toBe(true); - }); -}); - -describe("validateManifestFileRefs path validation", () => { - it("fails when a path does not start with ./", () => { - const dir = makeTmpDir(); - writeFile( - dir, - ".claude-plugin/plugin.json", - JSON.stringify({ - name: "test", - skills: ["skills/no-dot-slash"], - }), - ); - - const results = freshResult(); - validateManifestFileRefs(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes('must start with "./"'))).toBe(true); - }); - - it("fails when a path contains ..", () => { - const dir = makeTmpDir(); - writeFile( - dir, - ".claude-plugin/plugin.json", - JSON.stringify({ - name: "test", - agents: ["./../escape/agent.md"], - }), - ); - - const results = freshResult(); - validateManifestFileRefs(dir, "test-plugin", results); - - expect(results.failed.some((msg) => msg.includes('must not contain ".."'))).toBe(true); - }); -}); diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 1514e50..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "strict": true, - "noEmitOnError": true, - "noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": true, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noPropertyAccessFromIndexSignature": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["src", "tests"] -}