From 6367c2765e93e13d4326b146dcf64d5bde336e2a Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Sun, 28 Jun 2026 14:05:04 +0200 Subject: [PATCH 01/90] feat(contract): add model and fallbackModels to AgentDefinitionSchema Adds two optional fields to AgentDefinitionSchema (issue #44). model: primary model identifier; when unset, harness uses its default. fallbackModels: ordered list resolved at setup time in roster plugins. JSON schema regenerated. Tests assert optional contract on both fields. --- packages/contract/__tests__/schema.test.ts | 38 +++++++++++++++++++++- packages/contract/manifest.schema.json | 9 +++++ packages/contract/src/schema.ts | 2 ++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/contract/__tests__/schema.test.ts b/packages/contract/__tests__/schema.test.ts index 6e1e6a0..eac5a9a 100644 --- a/packages/contract/__tests__/schema.test.ts +++ b/packages/contract/__tests__/schema.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { PluginManifestSchema, TargetPlatformSchema, ALL_TARGETS } from '../src/index.js'; +import { PluginManifestSchema, TargetPlatformSchema, ALL_TARGETS, AgentDefinitionSchema } from '../src/index.js'; describe('PluginManifestSchema (single source of truth)', () => { it('accepts a minimal valid manifest', () => { @@ -35,3 +35,39 @@ describe('PluginManifestSchema (single source of truth)', () => { expect(parsed.sidecar?.command).toBe('node server.js'); }); }); + +describe('AgentDefinitionSchema — model fields', () => { + it('accepts agent with no model (optional)', () => { + const parsed = AgentDefinitionSchema.parse({ name: 'explorer' }); + expect(parsed.model).toBeUndefined(); + expect(parsed.fallbackModels).toBeUndefined(); + }); + + it('accepts agent with model set', () => { + const parsed = AgentDefinitionSchema.parse({ name: 'oracle', model: 'claude-opus-4-8' }); + expect(parsed.model).toBe('claude-opus-4-8'); + }); + + it('accepts agent with fallbackModels', () => { + const parsed = AgentDefinitionSchema.parse({ + name: 'fixer', + model: 'claude-sonnet-4-6', + fallbackModels: ['glm-5.2', 'kimi-k2'], + }); + expect(parsed.fallbackModels).toEqual(['glm-5.2', 'kimi-k2']); + }); + + it('accepts agents[] on PluginManifest with model', () => { + const parsed = PluginManifestSchema.parse({ + name: 'teams-roster', + version: '0.1.0', + description: 'test', + agents: [ + { name: 'orchestrator', model: 'claude-opus-4-8', fallbackModels: ['glm-5.2'] }, + { name: 'explorer' }, + ], + }); + expect(parsed.agents?.[0].model).toBe('claude-opus-4-8'); + expect(parsed.agents?.[1].model).toBeUndefined(); + }); +}); diff --git a/packages/contract/manifest.schema.json b/packages/contract/manifest.schema.json index 11d42fb..a292fb8 100644 --- a/packages/contract/manifest.schema.json +++ b/packages/contract/manifest.schema.json @@ -1793,6 +1793,15 @@ }, "filePath": { "type": "string" + }, + "model": { + "type": "string" + }, + "fallbackModels": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ diff --git a/packages/contract/src/schema.ts b/packages/contract/src/schema.ts index a5aa7e7..42401f9 100644 --- a/packages/contract/src/schema.ts +++ b/packages/contract/src/schema.ts @@ -73,6 +73,8 @@ export const AgentDefinitionSchema = z.object({ prompt: z.string().optional(), tools: z.array(z.string()).optional(), filePath: z.string().optional(), + model: z.string().optional(), + fallbackModels: z.array(z.string()).optional(), }); export type AgentDefinition = z.infer; From 2f9700b3164e6cf0bb4d3637da784770a8962709 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Sun, 28 Jun 2026 14:05:10 +0200 Subject: [PATCH 02/90] feat(adapter-claude): emit agents/*.md with model frontmatter Claude previously dropped agents[] into metadata._dropped (issue #45). Adds buildAgentFile() mirroring buildSkillFile() and wires it into compile() after the skills step. Emits agents/.md with name, description, tools (if set), and model (only when agent.model is set). Adds vitest test suite asserting model present/absent per contract. --- .../__tests__/agent-emission.test.ts | 72 +++++++++++++++++++ packages/adapter-claude/package.json | 6 +- packages/adapter-claude/src/index.ts | 26 +++++++ packages/adapter-claude/vitest.config.ts | 8 +++ pnpm-lock.yaml | 3 + 5 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 packages/adapter-claude/__tests__/agent-emission.test.ts create mode 100644 packages/adapter-claude/vitest.config.ts diff --git a/packages/adapter-claude/__tests__/agent-emission.test.ts b/packages/adapter-claude/__tests__/agent-emission.test.ts new file mode 100644 index 0000000..4715864 --- /dev/null +++ b/packages/adapter-claude/__tests__/agent-emission.test.ts @@ -0,0 +1,72 @@ +import { describe, it, expect } from 'vitest'; +import { createClaudeAdapter } from '../src/index.js'; +import type { PluginManifest } from '@agentplugins/core'; + +const adapter = createClaudeAdapter(); + +describe('Claude adapter — agent file emission', () => { + it('emits agents/.md when agents[] is present', () => { + const manifest: PluginManifest = { + name: 'test-plugin', + version: '0.1.0', + description: 'test', + agents: [{ name: 'explorer', description: 'Fast codebase recon', prompt: 'You are explorer.' }], + }; + const { files } = adapter.compile(manifest); + const agentFile = files.find((f) => f.path === 'agents/explorer.md'); + expect(agentFile).toBeDefined(); + expect(agentFile!.content).toContain('name: explorer'); + expect(agentFile!.content).toContain('You are explorer.'); + }); + + it('includes model: in frontmatter when agent.model is set', () => { + const manifest: PluginManifest = { + name: 'test-plugin', + version: '0.1.0', + description: 'test', + agents: [{ name: 'oracle', model: 'claude-opus-4-8', prompt: 'You are oracle.' }], + }; + const { files } = adapter.compile(manifest); + const agentFile = files.find((f) => f.path === 'agents/oracle.md'); + expect(agentFile).toBeDefined(); + expect(agentFile!.content).toContain('model: claude-opus-4-8'); + }); + + it('omits model: from frontmatter when agent.model is unset', () => { + const manifest: PluginManifest = { + name: 'test-plugin', + version: '0.1.0', + description: 'test', + agents: [{ name: 'explorer', prompt: 'You are explorer.' }], + }; + const { files } = adapter.compile(manifest); + const agentFile = files.find((f) => f.path === 'agents/explorer.md'); + expect(agentFile).toBeDefined(); + expect(agentFile!.content).not.toContain('model:'); + }); + + it('includes tools: when agent.tools is set', () => { + const manifest: PluginManifest = { + name: 'test-plugin', + version: '0.1.0', + description: 'test', + agents: [{ name: 'fixer', tools: ['Bash', 'Edit'], model: 'claude-sonnet-4-6' }], + }; + const { files } = adapter.compile(manifest); + const agentFile = files.find((f) => f.path === 'agents/fixer.md'); + expect(agentFile).toBeDefined(); + expect(agentFile!.content).toContain('tools: Bash, Edit'); + expect(agentFile!.content).toContain('model: claude-sonnet-4-6'); + }); + + it('emits no agent files when agents[] is absent', () => { + const manifest: PluginManifest = { + name: 'test-plugin', + version: '0.1.0', + description: 'test', + }; + const { files } = adapter.compile(manifest); + const agentFiles = files.filter((f) => f.path.startsWith('agents/')); + expect(agentFiles).toHaveLength(0); + }); +}); diff --git a/packages/adapter-claude/package.json b/packages/adapter-claude/package.json index 3a57c26..de27f45 100644 --- a/packages/adapter-claude/package.json +++ b/packages/adapter-claude/package.json @@ -15,7 +15,8 @@ "scripts": { "build": "tsup src/index.ts --format cjs,esm --dts", "dev": "tsup src/index.ts --format cjs,esm --dts --watch", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "test": "vitest run --passWithNoTests" }, "dependencies": { "@agentplugins/core": "workspace:*" @@ -23,7 +24,8 @@ "devDependencies": { "@types/node": "^20.0.0", "tsup": "^8.0.0", - "typescript": "^5.5.0" + "typescript": "^5.5.0", + "vitest": "^1.0.0" }, "files": [ "dist", diff --git a/packages/adapter-claude/src/index.ts b/packages/adapter-claude/src/index.ts index c84b895..12fbbd9 100644 --- a/packages/adapter-claude/src/index.ts +++ b/packages/adapter-claude/src/index.ts @@ -379,6 +379,17 @@ class ClaudePlatformAdapter implements PlatformAdapter { } } + // ─── 4b. Generate agent files ─────────────────────────────────────────── + if (plugin.agents && plugin.agents.length > 0) { + for (const agent of plugin.agents) { + const agentContent = this.buildAgentFile(agent); + files.push({ + path: `agents/${agent.name}.md`, + content: agentContent, + }); + } + } + // ─── 5. Generate .mcp.json ────────────────────────────────────────────── if (plugin.mcpServers && Object.keys(plugin.mcpServers).length > 0) { const mcpConfig = this.buildMCPConfig(plugin); @@ -642,6 +653,21 @@ class ClaudePlatformAdapter implements PlatformAdapter { return frontmatterBlock + `\n# ${skill.name}\n\n${skill.description}\n`; } + private buildAgentFile(agent: { name: string; description?: string; prompt?: string; tools?: string[]; model?: string; fallbackModels?: string[] }): string { + const lines: string[] = ['---', `name: ${agent.name}`]; + if (agent.description) { + lines.push(`description: ${agent.description}`); + } + if (agent.tools && agent.tools.length > 0) { + lines.push(`tools: ${agent.tools.join(', ')}`); + } + if (agent.model) { + lines.push(`model: ${agent.model}`); + } + lines.push('---', '', agent.prompt ?? '', ''); + return lines.join('\n'); + } + /** * Build the .mcp.json configuration from universal MCP server definitions. */ diff --git a/packages/adapter-claude/vitest.config.ts b/packages/adapter-claude/vitest.config.ts new file mode 100644 index 0000000..5288c8d --- /dev/null +++ b/packages/adapter-claude/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['__tests__/**/*.test.ts'], + environment: 'node', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef69bf4..686958d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,6 +60,9 @@ importers: typescript: specifier: ^5.5.0 version: 5.9.3 + vitest: + specifier: ^1.0.0 + version: 1.6.1(@types/node@20.19.41) packages/adapter-codex: dependencies: From 9be09d32d761bc0701f1b2391dc3553920aea4f5 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Sun, 28 Jun 2026 14:05:16 +0200 Subject: [PATCH 03/90] feat(adapter-opencode): add model frontmatter to generateAgentFiles Pushes model: into agent/.md frontmatter only when agent.model is set; omitted when unset so the harness uses its default. Adds agent-emission tests asserting present/absent model contract. --- .../__tests__/agent-emission.test.ts | 51 +++++++++++++++++++ .../adapter-opencode/src/output-generators.ts | 3 ++ 2 files changed, 54 insertions(+) create mode 100644 packages/adapter-opencode/__tests__/agent-emission.test.ts diff --git a/packages/adapter-opencode/__tests__/agent-emission.test.ts b/packages/adapter-opencode/__tests__/agent-emission.test.ts new file mode 100644 index 0000000..1e47546 --- /dev/null +++ b/packages/adapter-opencode/__tests__/agent-emission.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from 'vitest'; +import { generateAgentFiles } from '../src/output-generators.js'; +import type { PluginManifest } from '@agentplugins/core'; + +describe('OpenCode adapter — agent file emission', () => { + it('emits agent/.md when agents[] is present', () => { + const manifest: PluginManifest = { + name: 'test-plugin', + version: '0.1.0', + description: 'test', + agents: [{ name: 'explorer', description: 'Fast recon', prompt: 'You are explorer.' }], + }; + const files = generateAgentFiles(manifest); + expect(files).toHaveLength(1); + expect(files[0].path).toBe('agent/explorer.md'); + expect(files[0].content).toContain('description: Fast recon'); + expect(files[0].content).toContain('You are explorer.'); + }); + + it('includes model: in frontmatter when agent.model is set', () => { + const manifest: PluginManifest = { + name: 'test-plugin', + version: '0.1.0', + description: 'test', + agents: [{ name: 'oracle', model: 'claude-opus-4-8', prompt: 'You are oracle.' }], + }; + const files = generateAgentFiles(manifest); + expect(files[0].content).toContain('model: claude-opus-4-8'); + }); + + it('omits model: from frontmatter when agent.model is unset', () => { + const manifest: PluginManifest = { + name: 'test-plugin', + version: '0.1.0', + description: 'test', + agents: [{ name: 'explorer', prompt: 'You are explorer.' }], + }; + const files = generateAgentFiles(manifest); + expect(files[0].content).not.toContain('model:'); + }); + + it('returns empty array when agents[] is absent', () => { + const manifest: PluginManifest = { + name: 'test-plugin', + version: '0.1.0', + description: 'test', + }; + const files = generateAgentFiles(manifest); + expect(files).toHaveLength(0); + }); +}); diff --git a/packages/adapter-opencode/src/output-generators.ts b/packages/adapter-opencode/src/output-generators.ts index a5fae3e..169c954 100644 --- a/packages/adapter-opencode/src/output-generators.ts +++ b/packages/adapter-opencode/src/output-generators.ts @@ -212,6 +212,9 @@ export function generateAgentFiles(manifest: PluginManifest): FileOutput[] { if (agent.tools && agent.tools.length > 0) { lines.push(`tools: [${agent.tools.join(", ")}]`); } + if (agent.model) { + lines.push(`model: ${agent.model}`); + } lines.push("---", "", agent.prompt ?? "", ""); const content = lines.join("\n"); return { path: `agent/${agent.name}.md`, content }; From 99c37c25ed4ac3cbf8d3e01bd46b66b6ac8bae8b Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Sun, 28 Jun 2026 14:05:23 +0200 Subject: [PATCH 04/90] docs(compat-matrix): add per-agent model row Audit showed codex and pimono adapters do not emit agent files despite the matrix listing agents[] as universal. New row documents the actual state: Claude and OpenCode emit model: frontmatter (issue #47); Codex and Pi Mono have no per-agent file concept and fall back to harness default. --- docs/reference/compat-matrix.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reference/compat-matrix.md b/docs/reference/compat-matrix.md index 0d13ad4..ef5c585 100644 --- a/docs/reference/compat-matrix.md +++ b/docs/reference/compat-matrix.md @@ -24,6 +24,7 @@ Legend: | `commands` | ✅ | ✅ | ✅ | ✅ | Universal codegen | | `mcpServers` | ✅ | ✅ | ✅ | ✅ | **Recommended universal tool path** | | `agents[]` | ✅ | ✅ | ✅ | ✅ | Universal codegen | +| `agents[].model` | ✅ | ⚠️ | ✅ | ⚠️ | Claude + OpenCode: emits `model:` frontmatter when set. Codex/Pi Mono: no per-agent file concept; model unset → harness default | | `subagentStart` | ✅ | ✅ | ⚠️ | ✅ | OpenCode: no native event; emits WARN; guided path: intercept via `preToolUse` for subagent tool | | `subagentStop` | ✅ | ✅ | ⚠️ | ✅ | Same as above; Pi `stop`↔`subagentStop` collision fixed in v0.3.0 | | `tools[]` (first-class) | ⚠️ | ⚠️ | ✅ | ✅ | WARN emitted; use `mcpServers` for Claude/Codex (Tier-1 universal tool path) | From 2d98ae6dedd73e378cda2809b9eaa0eef296a889 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Sun, 28 Jun 2026 14:43:47 +0200 Subject: [PATCH 05/90] fix(adapter-copilot): handle universal CommandHookHandler format Command handlers in the universal contract store the command string directly on the handler object (handler.command), not inside handler.config. The copilot adapter was crashing with 'Cannot read properties of undefined (reading script)' when compiling hooks that used the universal format. Fix: fall back to the handler object itself when handler.config is not present, and default shell to 'bash' when not explicitly specified. --- packages/adapter-copilot/src/index.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/adapter-copilot/src/index.ts b/packages/adapter-copilot/src/index.ts index f255d82..6b1b497 100644 --- a/packages/adapter-copilot/src/index.ts +++ b/packages/adapter-copilot/src/index.ts @@ -325,20 +325,14 @@ function validateHandler( switch (handler.type as string) { case "command": { - const cmd = handler.config as { + // Support both copilot-specific format (handler.config.script/shell) and + // universal contract format (handler.command directly on the handler object). + const cmd = (handler.config ?? handler) as { shell?: string; script?: string; command?: string; }; - if (!cmd.shell) { - issues.push( - issue( - Severity.ERROR, - `Command handler must specify "shell" (e.g. "bash" or "powershell").`, - `${handlerPath}.config.shell` - ) - ); - } else if (!["bash", "powershell"].includes(cmd.shell)) { + if (cmd.shell && !["bash", "powershell"].includes(cmd.shell)) { issues.push( issue( Severity.WARNING, @@ -571,8 +565,8 @@ function compileHookEntry( // Resolve the handler path / URL / template switch (handler.type as string) { case "command": { - const cfg = handler.config as { - shell: string; + const cfg = (handler.config ?? handler) as { + shell?: string; script?: string; command?: string; }; @@ -853,11 +847,12 @@ class CopilotAdapter implements PlatformAdapter { const entry = compileHookEntry(uHook, hookDef); if ((handler.type as string) === "command") { - const cfg = handler.config as { - shell: string; + const rawCfg = (handler.config ?? handler) as { + shell?: string; script?: string; command?: string; }; + const cfg = { shell: "bash", ...rawCfg }; // Generate a wrapper script for this hook const scriptName = `hook-${hookName}.${ From 500229a2b973318953df6c6286e000339b3a86bf Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Sun, 28 Jun 2026 16:06:40 +0200 Subject: [PATCH 06/90] feat(docs): add sponsor section to landing page heroAlt slot with Apache 2.0 copy and Polar.sh checkout CTA. --- docs/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/index.md b/docs/index.md index 2e850e3..8209d78 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,6 +14,14 @@ hero: text: GitHub link: https://github.com/sigilco/agentplugins +heroAlt: + title: "Free & open source" + tagline: "AgentPlugins is Apache 2.0 licensed and will always be free and open source. Sponsorship keeps it that way." + actions: + - theme: brand + text: Become a Sponsor + link: https://buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj + features: - title: "Manage any AI harness plugin" details: "Install any GitHub plugin with `agentplugins add`. Symlinks to all detected agents automatically." From 0edc0af2822532f0ea7eeb907149b34fe819079b Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Sun, 28 Jun 2026 16:06:40 +0200 Subject: [PATCH 07/90] docs(readme): add Sponsor badge above the fold and near License Polar.sh checkout link, brand color #3c82f6, shields.io for-the-badge style. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 3bdee0c..caf7ad3 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ AgentPlugins is a plugin manager for AI agent harnesses. Install a plugin once — it's symlinked into Claude, Codex, Copilot, Gemini, Kimi, OpenCode, and Pi Mono automatically. Includes a codegen toolkit for authors who want to write once and compile to all platforms. +

+ + Sponsor AgentPlugins on Polar.sh + +

+ ```bash agentplugins add user/awesome-plugin # → Clones, parses manifest, symlinks to every detected agent @@ -218,6 +224,12 @@ For codegen (power users): Manifest → Core (validate + compile) → Adapters → Platform-native output ``` +

+ + Sponsor AgentPlugins on Polar.sh + +

+ ## License Apache-2.0 \ No newline at end of file From 1767d258e18a2e34b9a40ab147b7e6bc2c3ef0e3 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Sun, 28 Jun 2026 23:17:17 +0200 Subject: [PATCH 08/90] fix(docs): render sponsor section as markdown body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plan's heroAlt slot does not exist in VitePress — VitePress exposes home-hero-after, home-features-before, etc., but not heroAlt. Place the sponsor block as markdown content below the frontmatter divider, which the home layout renders below the features grid. No Vue component or theme override needed. VPButton scoped-class on inline anchor does not style as expected; link renders as default underlined link. Acceptable for v1; revisit if user wants the button styled like the hero CTA. --- docs/index.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/index.md b/docs/index.md index 8209d78..044a491 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,14 +14,6 @@ hero: text: GitHub link: https://github.com/sigilco/agentplugins -heroAlt: - title: "Free & open source" - tagline: "AgentPlugins is Apache 2.0 licensed and will always be free and open source. Sponsorship keeps it that way." - actions: - - theme: brand - text: Become a Sponsor - link: https://buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj - features: - title: "Manage any AI harness plugin" details: "Install any GitHub plugin with `agentplugins add`. Symlinks to all detected agents automatically." @@ -32,3 +24,11 @@ features: - title: "Zero config" details: "Install via npm, Homebrew, curl, or mise. No setup required." --- + +## Free & open source + +AgentPlugins is Apache 2.0 licensed and will always be free and open source. Sponsorship keeps it that way. + +

+ Become a Sponsor +

From a9c11196d3d82b34c088f1abe3fa69a46ce3adad Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 11:06:11 +0200 Subject: [PATCH 09/90] feat(docs): add SEO metadata, sitemap, favicon, and OG assets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #50 - sitemap.xml via VitePress built-in `sitemap.hostname` - robots.txt with Sitemap directive - Favicon (SVG + ICO), og.png 1200×630 brand image - Head expanded: keywords, OG, Twitter card, JSON-LD SoftwareApplication - Page-level title + description frontmatter on index, introduction, quick-start, creating-plugins - .gitignore: allow static public assets while keeping install.sh ignored --- .gitignore | 6 +++++- docs/.vitepress/config.ts | 28 ++++++++++++++++++++++++++++ docs/guide/creating-plugins.md | 3 ++- docs/guide/introduction.md | 3 ++- docs/guide/quick-start.md | 3 ++- docs/index.md | 2 ++ docs/public/favicon.ico | Bin 0 -> 993 bytes docs/public/favicon.svg | 4 ++++ docs/public/og.png | Bin 0 -> 29531 bytes docs/public/robots.txt | 4 ++++ 10 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 docs/public/favicon.ico create mode 100644 docs/public/favicon.svg create mode 100644 docs/public/og.png create mode 100644 docs/public/robots.txt diff --git a/.gitignore b/.gitignore index b065e29..802a98a 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,11 @@ coverage/ storybook-static/ docs/.vitepress/cache/ docs/.vitepress/dist/ -docs/public/ +docs/public/* +!docs/public/robots.txt +!docs/public/favicon.svg +!docs/public/favicon.ico +!docs/public/og.png # Misc *.local diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index e57cfaf..182a3eb 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -28,8 +28,36 @@ export default withMermaid(defineConfig({ cleanUrls: true, lastUpdated: true, + sitemap: { + hostname: DOCS_SITE, + }, + head: [ ['meta', { name: 'theme-color', content: '#3c82f6' }], + ['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }], + ['link', { rel: 'alternate icon', href: '/favicon.ico' }], + ['meta', { name: 'keywords', content: 'ai agent plugin, claude code plugin, codex plugin, opencode plugin, universal agent manifest, ai harness plugin manager' }], + ['meta', { property: 'og:type', content: 'website' }], + ['meta', { property: 'og:url', content: DOCS_SITE }], + ['meta', { property: 'og:title', content: 'AgentPlugins – Universal Plugin Standard for AI Agents' }], + ['meta', { property: 'og:description', content: 'Write AI agent plugins once, ship to Claude Code, Codex, OpenCode, Copilot, Gemini, Kimi, and Pi Mono. One manifest. Zero lock-in.' }], + ['meta', { property: 'og:image', content: `${DOCS_SITE}/og.png` }], + ['meta', { name: 'twitter:card', content: 'summary_large_image' }], + ['meta', { name: 'twitter:title', content: 'AgentPlugins – Universal Plugin Standard for AI Agents' }], + ['meta', { name: 'twitter:description', content: 'Write AI agent plugins once, ship to Claude Code, Codex, OpenCode, Copilot, Gemini, Kimi, and Pi Mono.' }], + ['meta', { name: 'twitter:image', content: `${DOCS_SITE}/og.png` }], + ['script', { type: 'application/ld+json' }, JSON.stringify({ + "@context": "https://schema.org", + "@type": "SoftwareApplication", + "name": "AgentPlugins", + "description": "Universal plugin standard for AI agents. Write once, ship to any harness.", + "url": DOCS_SITE, + "applicationCategory": "DeveloperApplication", + "operatingSystem": "All", + "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }, + "license": "https://www.apache.org/licenses/LICENSE-2.0", + "codeRepository": "https://github.com/sigilco/agentplugins" + })], ], markdown: { diff --git a/docs/guide/creating-plugins.md b/docs/guide/creating-plugins.md index 64fdbf0..df26e65 100644 --- a/docs/guide/creating-plugins.md +++ b/docs/guide/creating-plugins.md @@ -1,5 +1,6 @@ --- -description: Build and publish a universal AgentPlugin +title: Creating Plugins – AgentPlugins +description: Step-by-step guide to authoring an AgentPlugins manifest and shipping to seven AI agent harnesses. --- # Creating Plugins diff --git a/docs/guide/introduction.md b/docs/guide/introduction.md index 24f058b..e03917b 100644 --- a/docs/guide/introduction.md +++ b/docs/guide/introduction.md @@ -1,5 +1,6 @@ --- -description: Learn how AgentPlugins solves plugin fragmentation across AI agent frameworks +title: Introduction – AgentPlugins +description: Learn how AgentPlugins unifies AI agent plugins across Claude Code, Codex, OpenCode, and more with a single manifest file. --- # Introduction diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md index e150046..b3a0f4f 100644 --- a/docs/guide/quick-start.md +++ b/docs/guide/quick-start.md @@ -1,5 +1,6 @@ --- -description: Get a working AgentPlugin in five commands +title: Quick Start – AgentPlugins +description: Install AgentPlugins and create your first universal AI agent plugin in under 2 minutes. --- # Quick Start diff --git a/docs/index.md b/docs/index.md index 044a491..7e3b4b7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,6 @@ --- +title: AgentPlugins – Universal Plugin Standard for AI Agents +description: Write AI agent plugins once, ship to Claude Code, Codex, OpenCode, Gemini, Copilot, Kimi, and Pi Mono. One manifest. Seven platforms. Zero lock-in. layout: home hero: diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..eaabf64418dd068246704ab56f52bf0dbbd80b5e GIT binary patch literal 993 zcmZQzU}Ruo5D;Jh0tJR6j0_BFAeI7z{~gGG1;m{Je(tkhkMv3B%51Y!K zUK2l|@@Gt+LoLZu|37Z>*h7uS_%X zX-@XAKi5+s^Yi&6oie1KQXQ_3_marZArhoijzWdGd7qnFqi_MqqS1J+lT>hzu zW0L(-mAb(28f})=oTcAa?_Hr8+qk5B(OZ`*OOhimHSG!Lv^!~QaXsP=OM(B>g{rP= zAMq;vTW>4Hv!{F)a=d#Wzp$SVIDgdPwz;ReyRrLiJhx;IL_;#A5 zH?yNe+x?lIPD+gex;Bp*H6;2~lot60I4b8QzdgS}Sm*8ABl3yKuXVecl9c5xc8D>u z3I^^`KKbs{nVbypv(wY<0)Z{N?Y zDnA4y-z6llIxYB_C-E|6gK@%3jn>=hvdgba*@&{#>aMY2JLbeQi*JpL_n+6x-K2J% zN?5SaOYBZ!%;CG6=RA-O43&74S!n5%6ue~jXxq6o7D%(xpr!S9@Os<-}at8k#uZx@-Z2#=z-BPBD%xyT$ z$g|COiAh3@S>yC0=k-IJcrHKYb7A7){(awc(p8h93jx-`u4M&F(`@Ese|KtL#rQ9( z>zGzh46~OPq8)nM7wqm-TFGR+@jh=s?)>NWQ!=(SY*Y}| z-BG^s(c91OZ@itM^^dnSu4&z`JFjL(Ob?o&lO@?Om1(l8F0(;oJhNfdH}&=oP2^|? E0HtWD{r~^~ literal 0 HcmV?d00001 diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg new file mode 100644 index 0000000..edbabf2 --- /dev/null +++ b/docs/public/favicon.svg @@ -0,0 +1,4 @@ + + + A + diff --git a/docs/public/og.png b/docs/public/og.png new file mode 100644 index 0000000000000000000000000000000000000000..9b477f9f4bae72e7de46c548e974bd4daaf50ae3 GIT binary patch literal 29531 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 literal 0 HcmV?d00001 diff --git a/docs/public/robots.txt b/docs/public/robots.txt new file mode 100644 index 0000000..e55e71a --- /dev/null +++ b/docs/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://agentplugins.pages.dev/sitemap.xml From 2283124c850c2731512b93aa9a5a2d7c0745a96e Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 12:36:29 +0200 Subject: [PATCH 10/90] feat(pipeline): add @agentplugins/pipeline middleware kernel New zero-wiring package that defines the Plugin interface, middleware contexts (BuildCtx, TargetCtx, InstallCtx), and the createApp() runner. Every pipeline stage and adapter will register as a Plugin in later phases. Closes part of #51. --- packages/pipeline/package.json | 44 ++++++ packages/pipeline/src/app.ts | 231 ++++++++++++++++++++++++++++++++ packages/pipeline/src/index.ts | 21 +++ packages/pipeline/src/types.ts | 116 ++++++++++++++++ packages/pipeline/tsconfig.json | 9 ++ pnpm-lock.yaml | 19 +++ 6 files changed, 440 insertions(+) create mode 100644 packages/pipeline/package.json create mode 100644 packages/pipeline/src/app.ts create mode 100644 packages/pipeline/src/index.ts create mode 100644 packages/pipeline/src/types.ts create mode 100644 packages/pipeline/tsconfig.json diff --git a/packages/pipeline/package.json b/packages/pipeline/package.json new file mode 100644 index 0000000..a23b1a2 --- /dev/null +++ b/packages/pipeline/package.json @@ -0,0 +1,44 @@ +{ + "name": "@agentplugins/pipeline", + "version": "0.5.0", + "description": "AgentPlugins middleware kernel — composable plugin bus for the build and install pipelines", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts", + "dev": "tsup src/index.ts --format cjs,esm --dts --watch", + "typecheck": "tsc --noEmit", + "test": "vitest run --passWithNoTests" + }, + "dependencies": { + "@agentplugins/contract": "workspace:*" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsup": "^8.0.0", + "typescript": "^5.5.0", + "vitest": "^1.0.0" + }, + "files": [ + "dist", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/sigilco/agentplugins.git", + "directory": "packages/pipeline" + }, + "homepage": "https://agentplugins.pages.dev/" +} diff --git a/packages/pipeline/src/app.ts b/packages/pipeline/src/app.ts new file mode 100644 index 0000000..a0cc10c --- /dev/null +++ b/packages/pipeline/src/app.ts @@ -0,0 +1,231 @@ +import type { + Plugin, + App, + Middleware, + BuildCtx, + TargetCtx, + InstallCtx, + LintRule, + CodeEmitter, +} from './types.js'; +import type { PlatformAdapter } from '@agentplugins/contract'; + +export class AbortError extends Error { + constructor(reason?: string) { + super(reason ?? 'Pipeline aborted'); + this.name = 'AbortError'; + } +} + +async function runChain( + chain: Middleware[], + ctx: Ctx +): Promise { + let i = 0; + const next = async (): Promise => { + if (i < chain.length) { + await chain[i++](ctx, next); + } + }; + await next(); +} + +class PipelineApp implements App { + private readonly plugins: Plugin[] = []; + private readonly _adapters = new Map(); + private readonly _lintRules: LintRule[] = []; + private readonly _emitters = new Map(); + + get adapters(): ReadonlyMap { + return this._adapters; + } + + get lintRules(): readonly LintRule[] { + return this._lintRules; + } + + get emitters(): ReadonlyMap { + return this._emitters; + } + + use(plugin: Plugin): App { + this.plugins.push(plugin); + + if (plugin.adapter) { + this._adapters.set(plugin.adapter.name, plugin.adapter); + } + + if (plugin.lintRules) { + this._lintRules.push(...plugin.lintRules); + } + + if (plugin.emitters) { + for (const [lang, emitter] of Object.entries(plugin.emitters)) { + this._emitters.set(lang, emitter); + } + } + + return this; + } + + async runBuild(ctx: BuildCtx): Promise { + const preValidateChain = this.plugins + .map(p => p.preValidate) + .filter((m): m is Middleware => m != null); + + const transformIRChain = this.plugins + .map(p => p.transformIR) + .filter((m): m is Middleware => m != null); + + try { + await runChain(preValidateChain, ctx); + await runChain(transformIRChain, ctx); + } catch (err) { + if (err instanceof AbortError) throw err; + throw err; + } + } + + async runTarget(ctx: TargetCtx): Promise { + const postEmitChain = this.plugins + .map(p => p.postEmit) + .filter((m): m is Middleware => m != null); + + try { + await runChain(postEmitChain, ctx); + } catch (err) { + if (err instanceof AbortError) throw err; + throw err; + } + } + + async runInstall(ctx: InstallCtx): Promise { + const onInstallChain = this.plugins + .map(p => p.onInstall) + .filter((m): m is Middleware => m != null); + + try { + await runChain(onInstallChain, ctx); + } catch (err) { + if (err instanceof AbortError) throw err; + throw err; + } + } + + async runAudit(ctx: InstallCtx): Promise { + const onAuditChain = this.plugins + .map(p => p.onAudit) + .filter((m): m is Middleware => m != null); + + await runChain(onAuditChain, ctx); + } +} + +export function createApp(): PipelineApp { + return new PipelineApp(); +} + +// ─── Context factories ──────────────────────────────────────────────────────── + +import type { + PluginManifest, + FileOutput, + NativeCopy, + ValidationIssue, +} from '@agentplugins/contract'; + +export function createBuildCtx(options: { + manifest: PluginManifest; + targets: string[]; + outDir?: string; + pluginRoot?: string; +}): BuildCtx { + const issues: ValidationIssue[] = []; + const warnings: string[] = []; + const files = new Map(); + const nativeCopies = new Map(); + const postInstall = new Map(); + + const abort = (reason?: string): never => { + throw new AbortError(reason); + }; + + return { + manifest: options.manifest, + targets: options.targets, + outDir: options.outDir, + pluginRoot: options.pluginRoot, + issues, + warnings, + files, + nativeCopies, + postInstall, + abort, + addIssue(issue) { issues.push(issue); }, + addWarning(msg) { warnings.push(msg); }, + addFile(target, file) { + const list = files.get(target) ?? []; + list.push(file); + files.set(target, list); + }, + addNativeCopy(target, copy) { + const list = nativeCopies.get(target) ?? []; + list.push(copy); + nativeCopies.set(target, list); + }, + addPostInstall(target, step) { + const list = postInstall.get(target) ?? []; + list.push(step); + postInstall.set(target, list); + }, + }; +} + +export function createTargetCtx(options: { + manifest: PluginManifest; + target: string; + pluginRoot?: string; +}): TargetCtx { + const files: FileOutput[] = []; + const warnings: string[] = []; + const nativeCopies: NativeCopy[] = []; + const postInstall: string[] = []; + + const abort = (reason?: string): never => { + throw new AbortError(reason); + }; + + return { + manifest: options.manifest, + target: options.target, + pluginRoot: options.pluginRoot, + files, + warnings, + nativeCopies, + postInstall, + abort, + addFile(file) { files.push(file); }, + addWarning(msg) { warnings.push(msg); }, + addNativeCopy(copy) { nativeCopies.push(copy); }, + addPostInstall(step) { postInstall.push(step); }, + }; +} + +export function createInstallCtx(options: { + pluginName: string; + installDir: string; + manifest: PluginManifest; + meta?: Record; +}): InstallCtx { + const abort = (reason?: string): never => { + throw new AbortError(reason); + }; + + return { + pluginName: options.pluginName, + installDir: options.installDir, + manifest: options.manifest, + meta: options.meta ?? {}, + abort, + }; +} diff --git a/packages/pipeline/src/index.ts b/packages/pipeline/src/index.ts new file mode 100644 index 0000000..8345b54 --- /dev/null +++ b/packages/pipeline/src/index.ts @@ -0,0 +1,21 @@ +export type { + LintIssue, + LintContext, + LintRule, + CodeEmitter, + Next, + Middleware, + BuildCtx, + TargetCtx, + InstallCtx, + Plugin, + App, +} from './types.js'; + +export { + AbortError, + createApp, + createBuildCtx, + createTargetCtx, + createInstallCtx, +} from './app.js'; diff --git a/packages/pipeline/src/types.ts b/packages/pipeline/src/types.ts new file mode 100644 index 0000000..999babb --- /dev/null +++ b/packages/pipeline/src/types.ts @@ -0,0 +1,116 @@ +import type { + PluginManifest, + PlatformAdapter, + FileOutput, + NativeCopy, + ValidationIssue, +} from '@agentplugins/contract'; + +// ─── Opaque extension interfaces ───────────────────────────────────────────── +// Kept structurally compatible with @agentplugins/compile exports. + +export interface LintIssue { + rule: string; + severity: 'error' | 'warning'; + field?: string; + message: string; + suggestion?: string; +} + +export interface LintContext { + manifest: PluginManifest; + inlineHandlerSource?: string[]; +} + +export interface LintRule { + id: string; + description: string; + run: (ctx: LintContext) => LintIssue[]; +} + +export interface CodeEmitter { + readonly language: string; + emit(manifest: PluginManifest, hooks: unknown[]): FileOutput[]; +} + +// ─── Middleware ─────────────────────────────────────────────────────────────── + +export type Next = () => Promise; +export type Middleware = (ctx: Ctx, next: Next) => Promise; + +// ─── Contexts ───────────────────────────────────────────────────────────────── + +export interface BuildCtx { + manifest: PluginManifest; + targets: string[]; + outDir?: string; + pluginRoot?: string; + issues: ValidationIssue[]; + warnings: string[]; + /** Accumulated output files per target. */ + files: Map; + nativeCopies: Map; + postInstall: Map; + abort(reason?: string): never; + addIssue(issue: ValidationIssue): void; + addWarning(message: string): void; + addFile(target: string, file: FileOutput): void; + addNativeCopy(target: string, copy: NativeCopy): void; + addPostInstall(target: string, step: string): void; +} + +export interface TargetCtx { + manifest: PluginManifest; + target: string; + pluginRoot?: string; + files: FileOutput[]; + warnings: string[]; + nativeCopies: NativeCopy[]; + postInstall: string[]; + abort(reason?: string): never; + addFile(file: FileOutput): void; + addWarning(message: string): void; + addNativeCopy(copy: NativeCopy): void; + addPostInstall(step: string): void; +} + +export interface InstallCtx { + pluginName: string; + installDir: string; + manifest: PluginManifest; + meta: Record; + abort(reason?: string): never; +} + +// ─── Plugin ─────────────────────────────────────────────────────────────────── + +export interface Plugin { + readonly name: string; + /** Contributes a compile target. */ + adapter?: PlatformAdapter; + /** Contributes build-time checks. */ + lintRules?: LintRule[]; + /** Contributes code emitters. */ + emitters?: Record; + // Build-side lifecycle + preValidate?: Middleware; + transformIR?: Middleware; + postEmit?: Middleware; + // Install-side lifecycle + onAudit?: Middleware; + onInstall?: Middleware; +} + +// ─── App ───────────────────────────────────────────────────────────────────── + +export interface App { + use(plugin: Plugin): App; + /** Registered adapters, keyed by target name. */ + readonly adapters: ReadonlyMap; + /** All registered lint rules from plugins. */ + readonly lintRules: readonly LintRule[]; + /** All registered emitters from plugins. */ + readonly emitters: ReadonlyMap; + runBuild(ctx: BuildCtx): Promise; + runInstall(ctx: InstallCtx): Promise; +} diff --git a/packages/pipeline/tsconfig.json b/packages/pipeline/tsconfig.json new file mode 100644 index 0000000..b813d42 --- /dev/null +++ b/packages/pipeline/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 686958d..63496b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,6 +343,25 @@ importers: specifier: ^3.1.4 version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + packages/pipeline: + dependencies: + '@agentplugins/contract': + specifier: workspace:* + version: link:../contract + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.19.41 + tsup: + specifier: ^8.0.0 + version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) + typescript: + specifier: ^5.5.0 + version: 5.9.3 + vitest: + specifier: ^1.0.0 + version: 1.6.1(@types/node@20.19.41) + packages/schema: dependencies: ajv: From ca02a30f32220f8c2a36afaa3a715b338ceb04aa Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 12:40:22 +0200 Subject: [PATCH 11/90] refactor(compile): extract validate/lint/compile/write as middleware factories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds validateMiddleware, lintMiddleware, compileMiddleware, and writeMiddleware to @agentplugins/compile. Extends LintContext with extraRules for per-run rule injection. No changes to existing behavior — builds and tests remain green. --- packages/compile/package.json | 3 +- packages/compile/src/index.ts | 1 + packages/compile/src/lint.ts | 5 +- packages/compile/src/middleware.ts | 206 +++++++++++++++++++++++++++++ pnpm-lock.yaml | 3 + 5 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 packages/compile/src/middleware.ts diff --git a/packages/compile/package.json b/packages/compile/package.json index 67ab94e..0fbe170 100644 --- a/packages/compile/package.json +++ b/packages/compile/package.json @@ -33,7 +33,8 @@ }, "homepage": "https://agentplugins.pages.dev/", "dependencies": { - "@agentplugins/contract": "workspace:*" + "@agentplugins/contract": "workspace:*", + "@agentplugins/pipeline": "workspace:*" }, "devDependencies": { "@types/node": "^20.0.0", diff --git a/packages/compile/src/index.ts b/packages/compile/src/index.ts index ed30e78..ae58dd7 100644 --- a/packages/compile/src/index.ts +++ b/packages/compile/src/index.ts @@ -4,3 +4,4 @@ export * from './codegen.js'; export * from './hook-wrapper.js'; export * from './validation.js'; export * from './lint.js'; +export * from './middleware.js'; diff --git a/packages/compile/src/lint.ts b/packages/compile/src/lint.ts index 672cdde..eb6e8a8 100644 --- a/packages/compile/src/lint.ts +++ b/packages/compile/src/lint.ts @@ -18,6 +18,8 @@ export interface LintIssue { export interface LintContext { manifest: PluginManifest; inlineHandlerSource?: string[]; + /** Extra rules appended to the global registry for this lint run only. */ + extraRules?: LintRule[]; } export interface LintRule { @@ -340,7 +342,8 @@ export function getLintRules(): LintRule[] { export function lint(ctx: LintContext): LintIssue[] { const issues: LintIssue[] = []; - for (const rule of registry) { + const rules = ctx.extraRules ? [...registry, ...ctx.extraRules] : registry; + for (const rule of rules) { issues.push(...rule.run(ctx)); } return issues; diff --git a/packages/compile/src/middleware.ts b/packages/compile/src/middleware.ts new file mode 100644 index 0000000..d75755e --- /dev/null +++ b/packages/compile/src/middleware.ts @@ -0,0 +1,206 @@ +import { mkdir, rm, readFile, writeFile } from 'node:fs/promises'; +import { resolve, join } from 'node:path'; +import type { PlatformAdapter } from '@agentplugins/contract'; +import type { Middleware, BuildCtx, TargetCtx } from '@agentplugins/pipeline'; +import { validateUniversal, validateForPlatform } from './validation.js'; +import { lint } from './lint.js'; +import type { LintRule } from './lint.js'; +import { sanitizeJoin } from './sanitize.js'; + +// ─── validateMiddleware ─────────────────────────────────────────────────────── + +export interface ValidateOptions { + /** If true, abort on validation errors. Defaults to true. */ + abortOnError?: boolean; +} + +export function validateMiddleware(options: ValidateOptions = {}): Middleware { + const { abortOnError = true } = options; + + return async (ctx, next) => { + const issues = validateUniversal(ctx.manifest); + for (const issue of issues) ctx.addIssue(issue); + + if (abortOnError && issues.some(i => i.severity === 'error')) { + ctx.abort('Universal validation failed. Fix errors before building.'); + } + + await next(); + }; +} + +// ─── lintMiddleware ─────────────────────────────────────────────────────────── + +export interface LintOptions { + /** Extra lint rules in addition to the global registry. */ + extraRules?: LintRule[]; + /** If true and in strict mode, abort on lint errors. */ + strict?: boolean; +} + +export function lintMiddleware(options: LintOptions = {}): Middleware { + const { extraRules = [], strict = false } = options; + + return async (ctx, next) => { + const inlineSources = await collectInlineSources(ctx); + const issues = lint({ manifest: ctx.manifest, inlineHandlerSource: inlineSources, extraRules }); + + for (const issue of issues) { + ctx.addWarning(`[lint:${issue.rule}] ${issue.message}`); + } + + if (strict && issues.some(i => i.severity === 'error')) { + ctx.abort(`Strict mode: ${issues.filter(i => i.severity === 'error').length} lint error(s) found.`); + } + + await next(); + }; +} + +// ─── compileMiddleware ──────────────────────────────────────────────────────── + +export interface CompileMiddlewareOptions { + adapters: ReadonlyMap; + /** If true, skip targets with platform validation errors. Defaults to true. */ + skipOnPlatformError?: boolean; +} + +export function compileMiddleware(options: CompileMiddlewareOptions): Middleware { + const { adapters, skipOnPlatformError = true } = options; + + return async (ctx, next) => { + for (const target of ctx.targets) { + const adapter = adapters.get(target); + if (!adapter) { + ctx.addWarning(`No adapter registered for target "${target}" — skipping.`); + continue; + } + + const platformIssues = validateForPlatform(ctx.manifest, target as never); + const platformErrors = platformIssues.filter(i => i.severity === 'error'); + + if (skipOnPlatformError && platformErrors.length > 0) { + ctx.addWarning( + `Build failed for ${target}: ${platformErrors.length} validation error(s) — ${platformErrors.map(e => e.message).join('; ')}` + ); + continue; + } + + try { + const output = adapter.compile(ctx.manifest, { pluginRoot: ctx.pluginRoot }); + + for (const file of output.files) { + ctx.addFile(target, file); + } + for (const w of output.warnings) { + ctx.addWarning(`[${target}] ${w}`); + } + if (output.nativeCopies) { + for (const copy of output.nativeCopies) { + ctx.addNativeCopy(target, copy); + } + } + if (output.postInstall) { + for (const step of output.postInstall) { + ctx.addPostInstall(target, step); + } + } + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + ctx.addWarning(`Build failed for ${target}: ${msg}`); + } + } + + await next(); + }; +} + +// ─── writeMiddleware ────────────────────────────────────────────────────────── + +export function writeMiddleware(): Middleware { + return async (ctx, next) => { + await next(); + + if (!ctx.outDir) return; + + const resolvedOut = resolve(ctx.outDir); + + for (const [target, files] of ctx.files) { + const targetDir = join(resolvedOut, target); + await rm(targetDir, { recursive: true, force: true }); + await mkdir(targetDir, { recursive: true }); + + for (const file of files) { + const filePath = join(targetDir, file.path); + await mkdir(resolve(filePath, '..'), { recursive: true }); + await writeFile(filePath, file.content, 'utf-8'); + } + + const nativeCopies = ctx.nativeCopies.get(target) ?? []; + if (nativeCopies.length > 0 && ctx.pluginRoot) { + const resolvedRoot = resolve(ctx.pluginRoot); + for (const copy of nativeCopies) { + const srcPath = sanitizeJoin(resolvedRoot, copy.from); + const dstPath = sanitizeJoin(targetDir, copy.to); + await mkdir(resolve(dstPath, '..'), { recursive: true }); + const content = await readFile(srcPath, 'utf-8'); + await writeFile(dstPath, content, 'utf-8'); + } + } + } + }; +} + +// ─── targetWriteMiddleware ──────────────────────────────────────────────────── + +export function targetWriteMiddleware(outDir: string): Middleware { + return async (ctx, next) => { + await next(); + + const resolvedOut = resolve(outDir); + const targetDir = join(resolvedOut, ctx.target); + await rm(targetDir, { recursive: true, force: true }); + await mkdir(targetDir, { recursive: true }); + + for (const file of ctx.files) { + const filePath = join(targetDir, file.path); + await mkdir(resolve(filePath, '..'), { recursive: true }); + await writeFile(filePath, file.content, 'utf-8'); + } + + if (ctx.nativeCopies.length > 0 && ctx.pluginRoot) { + const resolvedRoot = resolve(ctx.pluginRoot); + for (const copy of ctx.nativeCopies) { + const srcPath = sanitizeJoin(resolvedRoot, copy.from); + const dstPath = sanitizeJoin(targetDir, copy.to); + await mkdir(resolve(dstPath, '..'), { recursive: true }); + const content = await readFile(srcPath, 'utf-8'); + await writeFile(dstPath, content, 'utf-8'); + } + } + }; +} + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +async function collectInlineSources(ctx: BuildCtx): Promise { + const sources: string[] = []; + if (!ctx.manifest.hooks) return sources; + for (const def of Object.values(ctx.manifest.hooks)) { + if (!def) continue; + const handler = def.handler as { type: string; code?: string; source?: string }; + if (handler.type === 'inline') { + if (handler.code) { + sources.push(handler.code); + } else if (handler.source && ctx.pluginRoot) { + try { + const content = await readFile(sanitizeJoin(resolve(ctx.pluginRoot), handler.source), 'utf-8'); + sources.push(content); + } catch { + // skip unreadable sources + } + } + } + } + return sources; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 63496b2..322d7d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -238,6 +238,9 @@ importers: '@agentplugins/contract': specifier: workspace:* version: link:../contract + '@agentplugins/pipeline': + specifier: workspace:* + version: link:../pipeline devDependencies: '@types/node': specifier: ^20.0.0 From 9ff837976017cc5714b113f5ad5f2dca178db4f5 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 12:43:13 +0200 Subject: [PATCH 12/90] refactor(cli): replace getAdapterFactory switch with pipeline app registry Drops the 7-arm switch in favour of buildBuiltinApp() which registers each adapter as a Plugin via createApp().use(). Extracts resolveTargets() to eliminate the duplicated targets-fallback on lines 84 and 171. Build output is byte-identical (verified against example-logger baseline). --- packages/cli/package.json | 1 + packages/cli/src/commands/build.ts | 91 +++++++++++++++++------------- pnpm-lock.yaml | 3 + 3 files changed, 57 insertions(+), 38 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ff08407..c6d4a23 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -24,6 +24,7 @@ "@agentplugins/contract": "workspace:*", "@agentplugins/core": "workspace:*", "@agentplugins/compile": "workspace:*", + "@agentplugins/pipeline": "workspace:*", "@agentplugins/ingest": "workspace:*", "@agentplugins/schema": "workspace:*", "@agentplugins/security": "workspace:*", diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index 52e1c5b..043eb8a 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -16,8 +16,54 @@ import { type CompileOptions as AdapterCompileOptions, } from '@agentplugins/core'; import { sanitizeJoin, lint, type LintIssue } from '@agentplugins/compile'; +import { createApp } from '@agentplugins/pipeline'; +import type { App } from '@agentplugins/pipeline'; import type { LoadedConfig } from '../config.js'; +// ─── Target resolution ──────────────────────────────────────────────────────── + +function resolveTargets( + cliTargets: string[] | undefined, + manifestTargets: string[] | undefined +): TargetPlatform[] { + return (cliTargets ?? manifestTargets ?? ALL_TARGETS) as TargetPlatform[]; +} + +// ─── Builtin adapter app ────────────────────────────────────────────────────── + +interface AdapterSpec { + platform: TargetPlatform; + pkg: string; + exportName: string; +} + +const BUILTIN_ADAPTER_SPECS: AdapterSpec[] = [ + { platform: 'claude', pkg: '@agentplugins/adapter-claude', exportName: 'createClaudeAdapter' }, + { platform: 'codex', pkg: '@agentplugins/adapter-codex', exportName: 'createCodexAdapter' }, + { platform: 'copilot', pkg: '@agentplugins/adapter-copilot', exportName: 'createCopilotAdapter' }, + { platform: 'gemini', pkg: '@agentplugins/adapter-gemini', exportName: 'createGeminiAdapter' }, + { platform: 'kimi', pkg: '@agentplugins/adapter-kimi', exportName: 'createKimiAdapter' }, + { platform: 'opencode', pkg: '@agentplugins/adapter-opencode', exportName: 'createOpenCodeAdapter' }, + { platform: 'pimono', pkg: '@agentplugins/adapter-pimono', exportName: 'createPiMonoAdapter' }, +]; + +async function buildBuiltinApp(): Promise { + const app = createApp(); + for (const { platform, pkg, exportName } of BUILTIN_ADAPTER_SPECS) { + try { + // @ts-ignore — loaded dynamically at runtime + const mod = await import(pkg); + const factory = mod[exportName] as (() => ReturnType) | undefined; + if (typeof factory === 'function') { + app.use({ name: platform, adapter: factory() }); + } + } catch { + // Adapter package not installed — skip silently + } + } + return app; +} + // ─── Compile (extracted for reuse by preview) ────────────────────────────── export interface CompileFile { @@ -44,36 +90,6 @@ export interface CompileOptions { pluginRoot?: string; } -type AdapterFactory = () => { compile: (manifest: any, options?: AdapterCompileOptions) => any }; - -async function getAdapterFactory(target: TargetPlatform): Promise { - switch (target) { - case 'claude': - // @ts-ignore - adapter loaded dynamically at runtime - return (await import('@agentplugins/adapter-claude')).createClaudeAdapter; - case 'codex': - // @ts-ignore - adapter loaded dynamically at runtime - return (await import('@agentplugins/adapter-codex')).createCodexAdapter; - case 'copilot': - // @ts-ignore - adapter loaded dynamically at runtime - return (await import('@agentplugins/adapter-copilot')).createCopilotAdapter; - case 'gemini': - // @ts-ignore - adapter loaded dynamically at runtime - return (await import('@agentplugins/adapter-gemini')).createGeminiAdapter; - case 'kimi': - // @ts-ignore - adapter loaded dynamically at runtime - return (await import('@agentplugins/adapter-kimi')).createKimiAdapter; - case 'opencode': - // @ts-ignore - adapter loaded dynamically at runtime - return (await import('@agentplugins/adapter-opencode')).createOpenCodeAdapter; - case 'pimono': - // @ts-ignore - adapter loaded dynamically at runtime - return (await import('@agentplugins/adapter-pimono')).createPiMonoAdapter; - default: - throw new Error(`Unknown target: ${target}`); - } -} - /** * Run the compilation pipeline for one or more targets. * If `write` is true, files are written to `outDir//`. @@ -81,14 +97,14 @@ async function getAdapterFactory(target: TargetPlatform): Promise { const { manifest, write = false, outDir, silent = false, pluginRoot } = options; - const targetList = (options.targets || manifest.targets || ALL_TARGETS) as TargetPlatform[]; + const targetList = resolveTargets(options.targets, manifest.targets); const results: CompileResult[] = []; + const app = await buildBuiltinApp(); + for (const target of targetList) { - let factory: AdapterFactory; - try { - factory = await getAdapterFactory(target); - } catch { + const adapter = app.adapters.get(target); + if (!adapter) { results.push({ target, files: [], warnings: [], skipped: true }); continue; } @@ -105,8 +121,7 @@ export async function compile(options: CompileOptions): Promise } try { - const adapter = factory(); - const output = adapter.compile(manifest, { pluginRoot }); + const output = adapter.compile(manifest, { pluginRoot } as AdapterCompileOptions); if (write && outDir) { const targetDir = join(resolve(outDir), target); @@ -168,7 +183,7 @@ export interface BuildOptions { export async function build(options: BuildOptions): Promise { const { config, outDir } = options; const manifest = config.manifest; - const targetList = (options.targets || manifest.targets || ALL_TARGETS) as TargetPlatform[]; + const targetList = resolveTargets(options.targets as TargetPlatform[] | undefined, manifest.targets); console.log(chalk.bold('\n🌉 AgentPlugins Build\n')); console.log(chalk.gray(`Plugin: ${manifest.name} v${manifest.version}`)); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 322d7d8..f37f06e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -204,6 +204,9 @@ importers: '@agentplugins/ingest': specifier: workspace:* version: link:../ingest + '@agentplugins/pipeline': + specifier: workspace:* + version: link:../pipeline '@agentplugins/schema': specifier: workspace:* version: link:../schema From a337915395cb2ac410c6f47eb4803549e3d4ec80 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 12:45:19 +0200 Subject: [PATCH 13/90] refactor(core): replace registry with pipeline-app-backed implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrites registry.ts to use a shared pipeline App as the authoritative adapter store. Fixes the createAdapter-only bug: registerBuiltinAdapters now uses the per-adapter factory name (createClaudeAdapter, createCodexAdapter, …) instead of the deprecated createAdapter alias that only adapter-claude exports. Adds getRegistryApp() to the public adapter API. --- packages/core/package.json | 1 + packages/core/src/adapter.ts | 1 + packages/core/src/registry.ts | 113 +++++++++++++++++++--------------- pnpm-lock.yaml | 3 + 4 files changed, 67 insertions(+), 51 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index a357cb9..37c7660 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -45,6 +45,7 @@ "dependencies": { "@agentplugins/contract": "workspace:*", "@agentplugins/compile": "workspace:*", + "@agentplugins/pipeline": "workspace:*", "@agentplugins/store": "workspace:*", "zod": "^3.25.76" }, diff --git a/packages/core/src/adapter.ts b/packages/core/src/adapter.ts index 2ac9f1d..ed8d0ac 100644 --- a/packages/core/src/adapter.ts +++ b/packages/core/src/adapter.ts @@ -34,6 +34,7 @@ export { loadAllAdapters, getRegisteredPlatforms, registerBuiltinAdapters, + getRegistryApp, } from './registry.js'; // Hook Wrapper Generation diff --git a/packages/core/src/registry.ts b/packages/core/src/registry.ts index b2b724d..0d3219a 100644 --- a/packages/core/src/registry.ts +++ b/packages/core/src/registry.ts @@ -1,89 +1,100 @@ /** * AgentPlugins Adapter Registry * - * Central registry for all platform adapters. - * Adapters register themselves; the CLI and build system look them up here. + * Backed by the pipeline App. Each registered adapter becomes a Plugin + * on the shared App instance, fixing the stale `createAdapter`-only bug + * in the previous implementation. */ +import { createApp } from '@agentplugins/pipeline'; +import type { App } from '@agentplugins/pipeline'; import type { PlatformAdapter, TargetPlatform } from './types.js'; -const adapterMap = new Map Promise>(); +const _app = createApp(); + +const _asyncFactories = new Map Promise>(); + +/** Access the shared pipeline App that backs this registry. */ +export function getRegistryApp(): App { + return _app; +} + +const BUILTIN_ADAPTER_SPECS: Array<{ platform: TargetPlatform; pkg: string; exportName: string }> = [ + { platform: 'claude', pkg: '@agentplugins/adapter-claude', exportName: 'createClaudeAdapter' }, + { platform: 'codex', pkg: '@agentplugins/adapter-codex', exportName: 'createCodexAdapter' }, + { platform: 'copilot', pkg: '@agentplugins/adapter-copilot', exportName: 'createCopilotAdapter' }, + { platform: 'gemini', pkg: '@agentplugins/adapter-gemini', exportName: 'createGeminiAdapter' }, + { platform: 'kimi', pkg: '@agentplugins/adapter-kimi', exportName: 'createKimiAdapter' }, + { platform: 'opencode', pkg: '@agentplugins/adapter-opencode', exportName: 'createOpenCodeAdapter' }, + { platform: 'pimono', pkg: '@agentplugins/adapter-pimono', exportName: 'createPiMonoAdapter' }, +]; /** * Register a platform adapter factory. - * Called by each adapter package during initialization. + * The factory is called immediately and the adapter is registered synchronously. */ export function registerAdapter( platform: TargetPlatform, factory: () => Promise ): void { - adapterMap.set(platform, factory); + _asyncFactories.set(platform, factory); + factory() + .then(adapter => _app.use({ name: platform, adapter })) + .catch(() => { + // adapter init failed — do not register + }); } -/** - * Check if an adapter is registered for a platform. - */ export function hasAdapter(platform: TargetPlatform): boolean { - return adapterMap.has(platform); + return _app.adapters.has(platform) || _asyncFactories.has(platform); } -/** - * Load a platform adapter asynchronously. - */ export async function loadAdapter(platform: TargetPlatform): Promise { - const factory = adapterMap.get(platform); - if (!factory) { - throw new Error( - `No adapter registered for platform "${platform}". ` + - `Install the corresponding adapter package: npm install @agentplugins/adapter-${platform}` - ); + const adapter = _app.adapters.get(platform); + if (adapter) return adapter; + + const factory = _asyncFactories.get(platform); + if (factory) { + const resolved = await factory(); + _app.use({ name: platform, adapter: resolved }); + return resolved; } - return factory(); + + throw new Error( + `No adapter registered for platform "${platform}". ` + + `Install the corresponding adapter package: npm install @agentplugins/adapter-${platform}` + ); } -/** - * Load all registered adapters. - */ export async function loadAllAdapters(): Promise> { - const result = new Map(); - for (const [platform, factory] of adapterMap) { - result.set(platform, await factory()); + for (const [platform, factory] of _asyncFactories) { + if (!_app.adapters.has(platform)) { + try { + const adapter = await factory(); + _app.use({ name: platform, adapter }); + } catch { + // skip failed adapters + } + } } - return result; + return new Map(_app.adapters) as Map; } -/** - * Get list of registered platforms. - */ export function getRegisteredPlatforms(): TargetPlatform[] { - return Array.from(adapterMap.keys()); + return Array.from(_app.adapters.keys()) as TargetPlatform[]; } -/** - * Import and register all built-in adapters. - * This is a convenience function that auto-registers all official adapters. - */ export async function registerBuiltinAdapters(): Promise { - // Dynamic imports to avoid loading adapters that aren't installed - const builtinAdapters: { platform: TargetPlatform; pkg: string }[] = [ - { platform: 'claude', pkg: '@agentplugins/adapter-claude' }, - { platform: 'codex', pkg: '@agentplugins/adapter-codex' }, - { platform: 'copilot', pkg: '@agentplugins/adapter-copilot' }, - { platform: 'gemini', pkg: '@agentplugins/adapter-gemini' }, - { platform: 'kimi', pkg: '@agentplugins/adapter-kimi' }, - { platform: 'opencode', pkg: '@agentplugins/adapter-opencode' }, - { platform: 'pimono', pkg: '@agentplugins/adapter-pimono' }, - ]; - - for (const { platform, pkg } of builtinAdapters) { + for (const { platform, pkg, exportName } of BUILTIN_ADAPTER_SPECS) { try { - // Use dynamic import that works in both ESM and bundled contexts - const mod = await import(/* @vite-ignore */ pkg); - if (mod.createAdapter) { - registerAdapter(platform, mod.createAdapter); + // @ts-ignore — dynamic import; package may not be installed + const mod = await import(pkg); + const factory = mod[exportName] as (() => PlatformAdapter) | undefined; + if (typeof factory === 'function') { + _app.use({ name: platform, adapter: factory() }); } } catch { - // Adapter package not installed — that's fine, skip it + // Adapter package not installed — skip silently } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f37f06e..34dc079 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -282,6 +282,9 @@ importers: '@agentplugins/contract': specifier: workspace:* version: link:../contract + '@agentplugins/pipeline': + specifier: workspace:* + version: link:../pipeline '@agentplugins/store': specifier: workspace:* version: link:../store From ad2eeb156eff2d504bcd1346dc6fd85f61855e1f Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 12:48:49 +0200 Subject: [PATCH 14/90] feat(contract): widen TargetPlatform to allow custom adapter targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TargetPlatformSchema now accepts any non-empty string instead of a closed enum, enabling custom/private adapters without schema rejections. Built-in ids are preserved as BuiltinTarget for autocomplete. Unknown target check in validateUniversal is downgraded from error to warning — the compile middleware enforces the "no adapter → skip" gate at build time. --- packages/compile/src/validation.ts | 8 ++++---- packages/contract/manifest.schema.json | 20 ++------------------ packages/contract/src/schema.ts | 21 +++++++++++++++------ packages/core/src/validation.ts | 8 ++++---- 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/packages/compile/src/validation.ts b/packages/compile/src/validation.ts index f2eb1fb..0a9fb49 100644 --- a/packages/compile/src/validation.ts +++ b/packages/compile/src/validation.ts @@ -71,12 +71,12 @@ export function validateUniversal(plugin: PluginManifest): ValidationIssue[] { // ─── targets ────────────────────────────────────────────────────────────── if (plugin.targets) { for (const target of plugin.targets) { - if (!ALL_TARGETS.includes(target)) { + if (!(ALL_TARGETS as string[]).includes(target)) { issues.push({ - severity: Severity.ERROR, + severity: Severity.WARNING, field: 'targets', - message: `Unknown target platform: "${target}"`, - suggestion: `Supported: ${ALL_TARGETS.join(', ')}`, + message: `"${target}" is not a built-in target — ensure a matching adapter is registered or compilation will be skipped`, + suggestion: `Built-in targets: ${ALL_TARGETS.join(', ')}`, }); } } diff --git a/packages/contract/manifest.schema.json b/packages/contract/manifest.schema.json index a292fb8..5e7d4b5 100644 --- a/packages/contract/manifest.schema.json +++ b/packages/contract/manifest.schema.json @@ -1898,15 +1898,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "claude", - "codex", - "copilot", - "gemini", - "kimi", - "opencode", - "pimono" - ] + "minLength": 1 } }, "nativeEntry": { @@ -1927,15 +1919,7 @@ "type": "string" }, "propertyNames": { - "enum": [ - "claude", - "codex", - "copilot", - "gemini", - "kimi", - "opencode", - "pimono" - ] + "minLength": 1 } }, "dependencies": { diff --git a/packages/contract/src/schema.ts b/packages/contract/src/schema.ts index 42401f9..947f8ae 100644 --- a/packages/contract/src/schema.ts +++ b/packages/contract/src/schema.ts @@ -12,7 +12,8 @@ import { z } from 'zod'; // ─── Target platform ────────────────────────────────────────────────────────── -export const TargetPlatformSchema = z.enum([ +/** Known built-in target ids — custom adapters may use any non-empty string. */ +export const BUILTIN_TARGETS = [ 'claude', 'codex', 'copilot', @@ -20,12 +21,20 @@ export const TargetPlatformSchema = z.enum([ 'kimi', 'opencode', 'pimono', -]); -export type TargetPlatform = z.infer; +] as const; + +export type BuiltinTarget = (typeof BUILTIN_TARGETS)[number]; + +/** + * Accepts any non-empty string so custom adapters can declare private targets. + * Known built-in ids have autocomplete via the `BuiltinTarget` union. + */ +export const TargetPlatformSchema = z.string().min(1); + +/** Union preserving autocomplete for built-in ids while allowing custom ones. */ +export type TargetPlatform = BuiltinTarget | (string & {}); -export const ALL_TARGETS: TargetPlatform[] = [ - 'claude', 'codex', 'copilot', 'gemini', 'kimi', 'opencode', 'pimono', -]; +export const ALL_TARGETS: BuiltinTarget[] = [...BUILTIN_TARGETS]; // ─── Dependency & Sidecar ──────────────────────────────────────────────────── diff --git a/packages/core/src/validation.ts b/packages/core/src/validation.ts index 737ad29..031a366 100644 --- a/packages/core/src/validation.ts +++ b/packages/core/src/validation.ts @@ -71,12 +71,12 @@ export function validateUniversal(plugin: PluginManifest): ValidationIssue[] { // ─── targets ────────────────────────────────────────────────────────────── if (plugin.targets) { for (const target of plugin.targets) { - if (!ALL_TARGETS.includes(target)) { + if (!(ALL_TARGETS as string[]).includes(target)) { issues.push({ - severity: Severity.ERROR, + severity: Severity.WARNING, field: 'targets', - message: `Unknown target platform: "${target}"`, - suggestion: `Supported: ${ALL_TARGETS.join(', ')}`, + message: `"${target}" is not a built-in target — ensure a matching adapter is registered or compilation will be skipped`, + suggestion: `Built-in targets: ${ALL_TARGETS.join(', ')}`, }); } } From 6e7a3e20d1b95632c2af38db25910001a45e3a34 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 12:51:49 +0200 Subject: [PATCH 15/90] feat(core): add defineConfig API with plugin bus and target overrides Adds defineConfig({ manifest, plugins, targets }) to @agentplugins/core as a power-user companion to definePlugin. The config loader detects the defineConfig shape and threads plugins and target overrides into the build pipeline. User plugins are registered after builtins so they can override any built-in adapter or stage. Bare manifests remain backward-compatible. --- packages/cli/src/cli.ts | 2 +- packages/cli/src/commands/build.ts | 26 +++++++++++++++---- packages/cli/src/config.ts | 40 ++++++++++++++++++++++++------ packages/core/src/index.ts | 27 +++++++++++++++++++- 4 files changed, 80 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index b5d2466..352d5be 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -180,7 +180,7 @@ cli const config = await loadConfig(options.config); const targets = options.target ? options.target.split(',').map((t: string) => t.trim()) - : config.manifest.targets; + : undefined; await build({ config, diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index 043eb8a..80a8ab2 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -17,7 +17,7 @@ import { } from '@agentplugins/core'; import { sanitizeJoin, lint, type LintIssue } from '@agentplugins/compile'; import { createApp } from '@agentplugins/pipeline'; -import type { App } from '@agentplugins/pipeline'; +import type { App, Plugin } from '@agentplugins/pipeline'; import type { LoadedConfig } from '../config.js'; // ─── Target resolution ──────────────────────────────────────────────────────── @@ -47,8 +47,10 @@ const BUILTIN_ADAPTER_SPECS: AdapterSpec[] = [ { platform: 'pimono', pkg: '@agentplugins/adapter-pimono', exportName: 'createPiMonoAdapter' }, ]; -async function buildBuiltinApp(): Promise { +async function buildApp(userPlugins: Plugin[] = []): Promise { const app = createApp(); + + // Register builtin adapters first (lower precedence) for (const { platform, pkg, exportName } of BUILTIN_ADAPTER_SPECS) { try { // @ts-ignore — loaded dynamically at runtime @@ -61,6 +63,12 @@ async function buildBuiltinApp(): Promise { // Adapter package not installed — skip silently } } + + // Register user plugins after builtins so they can override any builtin + for (const plugin of userPlugins) { + app.use(plugin); + } + return app; } @@ -88,6 +96,8 @@ export interface CompileOptions { silent?: boolean; /** Plugin root directory — required to resolve nativeEntry source paths. */ pluginRoot?: string; + /** User-provided pipeline plugins from defineConfig. */ + plugins?: Plugin[]; } /** @@ -96,11 +106,11 @@ export interface CompileOptions { * Returns per-target results. */ export async function compile(options: CompileOptions): Promise { - const { manifest, write = false, outDir, silent = false, pluginRoot } = options; + const { manifest, write = false, outDir, silent = false, pluginRoot, plugins = [] } = options; const targetList = resolveTargets(options.targets, manifest.targets); const results: CompileResult[] = []; - const app = await buildBuiltinApp(); + const app = await buildApp(plugins); for (const target of targetList) { const adapter = app.adapters.get(target); @@ -183,7 +193,12 @@ export interface BuildOptions { export async function build(options: BuildOptions): Promise { const { config, outDir } = options; const manifest = config.manifest; - const targetList = resolveTargets(options.targets as TargetPlatform[] | undefined, manifest.targets); + // CLI --target flag > defineConfig targets > manifest.targets > ALL_TARGETS + const targetList = resolveTargets( + options.targets as TargetPlatform[] | undefined + ?? config.configTargets as TargetPlatform[] | undefined, + manifest.targets + ); console.log(chalk.bold('\n🌉 AgentPlugins Build\n')); console.log(chalk.gray(`Plugin: ${manifest.name} v${manifest.version}`)); @@ -216,6 +231,7 @@ export async function build(options: BuildOptions): Promise { write: true, outDir, pluginRoot: config.root, + plugins: config.plugins, }); // Strict mode: fail on warnings diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts index 951534e..c68bf72 100644 --- a/packages/cli/src/config.ts +++ b/packages/cli/src/config.ts @@ -3,6 +3,11 @@ * * Loads plugin configuration from agentplugins.config.ts/js/mjs/json files. * Uses jiti for TypeScript support without pre-compilation. + * + * Supports three export shapes: + * 1. defineConfig({ manifest, plugins, targets }) — power user + * 2. definePlugin(manifest) — bare manifest object + * 3. () => PluginManifest — factory function */ import { existsSync } from 'node:fs'; @@ -13,7 +18,8 @@ import jiti from 'jiti'; const createJITI = jiti as unknown as (filename: string, opts?: Record) => { import: (id: string, opts?: Record) => Promise; }; -import type { PluginManifest } from '@agentplugins/core'; +import type { PluginManifest, AgentPluginsConfig } from '@agentplugins/core'; +import type { Plugin } from '@agentplugins/pipeline'; export interface LoadedConfig { /** Resolved manifest */ @@ -22,6 +28,19 @@ export interface LoadedConfig { root: string; /** Path to the config file */ configPath: string; + /** User-provided pipeline plugins from defineConfig (empty if bare manifest). */ + plugins: Plugin[]; + /** User-provided target override from defineConfig (undefined if not set). */ + configTargets?: string[]; +} + +function isDefineConfig(obj: unknown): obj is AgentPluginsConfig { + return ( + typeof obj === 'object' && + obj !== null && + 'manifest' in obj && + typeof (obj as Record).manifest === 'object' + ); } /** @@ -37,24 +56,30 @@ export async function loadConfig(configPath: string): Promise { const ext = extname(resolvedPath); let manifest: PluginManifest; + let plugins: Plugin[] = []; + let configTargets: string[] | undefined; if (ext === '.json') { - // JSON config + // JSON config — always a bare manifest const { readFile } = await import('node:fs/promises'); const content = await readFile(resolvedPath, 'utf-8'); manifest = JSON.parse(content) as PluginManifest; } else { // TypeScript/JavaScript config — use jiti - const jiti = createJITI(resolvedPath, { + const jitiLoader = createJITI(resolvedPath, { interopDefault: true, esmResolve: true, }); - const mod = await jiti.import(resolvedPath, { default: true }); - const exported = (mod as any)?.default ?? mod; + const mod = await jitiLoader.import(resolvedPath, { default: true }); + const exported = (mod as Record)?.default ?? mod; if (typeof exported === 'function') { - manifest = await exported(); + manifest = await (exported as () => Promise)(); + } else if (isDefineConfig(exported)) { + manifest = exported.manifest; + plugins = exported.plugins ?? []; + configTargets = exported.targets; } else { manifest = exported as PluginManifest; } @@ -71,10 +96,9 @@ export async function loadConfig(configPath: string): Promise { throw new Error('Plugin manifest must have a "description" field'); } - // Resolve root directory const root = resolve(resolvedPath, '..'); - return { manifest, root, configPath: resolvedPath }; + return { manifest, root, configPath: resolvedPath, plugins, configTargets }; } /** diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 382d6c0..46e7cef 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -122,10 +122,35 @@ export type { RunSetupResult, } from '@agentplugins/store'; -// ─── definePlugin helper ────────────────────────────────────────────────────── +// ─── definePlugin / defineConfig ───────────────────────────────────────────── import type { PluginManifest } from '@agentplugins/contract'; +import type { Plugin } from '@agentplugins/pipeline'; +export type { Plugin } from '@agentplugins/pipeline'; + +export interface AgentPluginsConfig { + /** The universal plugin manifest. */ + manifest: PluginManifest; + /** + * Pipeline plugins that contribute adapters, lint rules, emitters, + * and lifecycle middleware. Registered before the builtin adapters so + * they can override or extend any stage. + */ + plugins?: Plugin[]; + /** + * Override the target list for this build. Falls back to + * `manifest.targets` and then to all built-in targets. + */ + targets?: string[]; +} + +/** Identity helper — provides TypeScript inference for the manifest. */ export function definePlugin(manifest: PluginManifest): PluginManifest { return manifest; } + +/** Power-user config with plugins and target overrides. Backward-compatible. */ +export function defineConfig(config: AgentPluginsConfig): AgentPluginsConfig { + return config; +} From efa2e6ce010fc9bba0f80d883425786430d369d1 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 12:53:21 +0200 Subject: [PATCH 16/90] feat(compile): add registerEmitter and mutable emitter registry EMITTERS is now a Map instead of a Record, with registerEmitter() and getRegisteredEmitters() exposed from both @agentplugins/compile and @agentplugins/core/adapter. Plugins can contribute code emitters for custom target languages without modifying core internals. --- packages/compile/src/codegen.ts | 22 +++++++++++++++------- packages/core/src/adapter.ts | 2 ++ packages/core/src/codegen.ts | 22 +++++++++++++++------- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/compile/src/codegen.ts b/packages/compile/src/codegen.ts index 8b03d79..617842d 100644 --- a/packages/compile/src/codegen.ts +++ b/packages/compile/src/codegen.ts @@ -212,16 +212,24 @@ export const GoEmitter: CodeEmitter = { }, }; -const EMITTERS: Record = { - typescript: TypeScriptEmitter, - javascript: JavaScriptEmitter, - go: GoEmitter, -}; +const EMITTERS: Map = new Map([ + ['typescript', TypeScriptEmitter], + ['javascript', JavaScriptEmitter], + ['go', GoEmitter], +]); + +export function registerEmitter(emitter: CodeEmitter): void { + EMITTERS.set(emitter.language, emitter); +} -export function getEmitter(language: EmitLanguage): CodeEmitter { - const emitter = EMITTERS[language]; +export function getEmitter(language: EmitLanguage | string): CodeEmitter { + const emitter = EMITTERS.get(language); if (!emitter) { throw new Error(`Unknown emit language: ${String(language)}`); } return emitter; } + +export function getRegisteredEmitters(): ReadonlyMap { + return EMITTERS; +} diff --git a/packages/core/src/adapter.ts b/packages/core/src/adapter.ts index ed8d0ac..89fef42 100644 --- a/packages/core/src/adapter.ts +++ b/packages/core/src/adapter.ts @@ -49,6 +49,8 @@ export type { WrapperOptions } from './hook-wrapper.js'; export { extractCompiledHooks, getEmitter, + registerEmitter, + getRegisteredEmitters, TypeScriptEmitter, JavaScriptEmitter, GoEmitter, diff --git a/packages/core/src/codegen.ts b/packages/core/src/codegen.ts index 6ce3bf7..1759938 100644 --- a/packages/core/src/codegen.ts +++ b/packages/core/src/codegen.ts @@ -212,16 +212,24 @@ export const GoEmitter: CodeEmitter = { }, }; -const EMITTERS: Record = { - typescript: TypeScriptEmitter, - javascript: JavaScriptEmitter, - go: GoEmitter, -}; +const EMITTERS: Map = new Map([ + ['typescript', TypeScriptEmitter], + ['javascript', JavaScriptEmitter], + ['go', GoEmitter], +]); + +export function registerEmitter(emitter: CodeEmitter): void { + EMITTERS.set(emitter.language, emitter); +} -export function getEmitter(language: EmitLanguage): CodeEmitter { - const emitter = EMITTERS[language]; +export function getEmitter(language: EmitLanguage | string): CodeEmitter { + const emitter = EMITTERS.get(language); if (!emitter) { throw new Error(`Unknown emit language: ${String(language)}`); } return emitter; } + +export function getRegisteredEmitters(): ReadonlyMap { + return EMITTERS; +} From 3e76611d15408902d7a7dccf6a42ea31b71382e1 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 12:56:39 +0200 Subject: [PATCH 17/90] refactor(store): expose security gate as install-pipeline Plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds securityPlugin to @agentplugins/store backed by evaluateScriptPolicy, gateSetupCommand, and hashDirectory. Three onInstall middleware stages — integrity hashing, setup-command policy gate, and dependency-lifecycle policy gate — run in sequence and abort the install on any denied decision. Transparent, reorderable, and removable via defineConfig. --- packages/store/package.json | 1 + packages/store/src/index.ts | 1 + packages/store/src/plugin.ts | 103 +++++++++++++++++++++++++++++++++++ pnpm-lock.yaml | 3 + 4 files changed, 108 insertions(+) create mode 100644 packages/store/src/plugin.ts diff --git a/packages/store/package.json b/packages/store/package.json index b01e1cf..74515ae 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -40,6 +40,7 @@ "homepage": "https://agentplugins.pages.dev/", "dependencies": { "@agentplugins/compile": "workspace:*", + "@agentplugins/pipeline": "workspace:*", "@agentplugins/security": "workspace:*" }, "devDependencies": { diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts index 930ef04..8723188 100644 --- a/packages/store/src/index.ts +++ b/packages/store/src/index.ts @@ -9,3 +9,4 @@ export * from './store.js'; export * from './setup.js'; +export * from './plugin.js'; diff --git a/packages/store/src/plugin.ts b/packages/store/src/plugin.ts new file mode 100644 index 0000000..98be59a --- /dev/null +++ b/packages/store/src/plugin.ts @@ -0,0 +1,103 @@ +/** + * Built-in security Plugin for the install pipeline. + * + * Wraps evaluateScriptPolicy (via gateSetupCommand) and integrity checking + * as explicit, reorderable middleware. Registered by default in the install + * pipeline — power users may remove or reorder it via defineConfig. + */ + +import type { Plugin, InstallCtx } from '@agentplugins/pipeline'; +import { hashDirectory, evaluateScriptPolicy, DEFAULT_POLICY } from '@agentplugins/security'; +import { resolveSetupCommand, gateSetupCommand } from './setup.js'; + +/** Abort keys written to InstallCtx.meta by securityPlugin middleware. */ +export const SECURITY_META_KEYS = { + integrity: 'security:integrity', + scriptDecision: 'security:scriptDecision', +} as const; + +/** + * Middleware: hashes the install directory and stores the result in ctx.meta. + * Does not abort — callers may inspect ctx.meta[SECURITY_META_KEYS.integrity] + * to decide whether to block. + */ +async function integrityMiddleware(ctx: InstallCtx, next: () => Promise): Promise { + if (ctx.installDir) { + const hash = hashDirectory(ctx.installDir); + ctx.meta[SECURITY_META_KEYS.integrity] = hash; + } + await next(); +} + +/** + * Middleware: evaluates the setup-script policy for the plugin's setup command. + * Aborts the install if the policy returns 'deny'. + */ +async function scriptPolicyMiddleware(ctx: InstallCtx, next: () => Promise): Promise { + const manifest = ctx.manifest as unknown as Record; + const resolved = resolveSetupCommand(ctx.installDir, manifest); + + if (resolved) { + const gate = gateSetupCommand(resolved.command, ctx.pluginName); + ctx.meta[SECURITY_META_KEYS.scriptDecision] = gate; + + if (gate.decision === 'deny') { + ctx.abort( + `Setup command blocked by policy for "${ctx.pluginName}": ${gate.reasons.join('; ')}` + ); + } + } + + await next(); +} + +/** + * Middleware: evaluates script policy for all dependency lifecycle commands + * declared in the manifest. Aborts if any are denied. + */ +async function dependencyScriptMiddleware(ctx: InstallCtx, next: () => Promise): Promise { + const manifest = ctx.manifest as unknown as Record; + const deps = Array.isArray(manifest.dependencies) ? manifest.dependencies as Array> : []; + + for (const dep of deps) { + if (typeof dep.lifecycle === 'string' && typeof dep.command === 'string') { + const result = evaluateScriptPolicy( + { + dependency: String(dep.name ?? '?'), + phase: dep.lifecycle as 'preinstall' | 'install' | 'postinstall', + command: dep.command, + pluginName: ctx.pluginName, + }, + DEFAULT_POLICY, + ); + + if (result.decision === 'deny') { + ctx.abort( + `Dependency lifecycle script blocked by policy: ${dep.name} (${dep.lifecycle}): ${result.reasons.join('; ')}` + ); + } + } + } + + await next(); +} + +/** + * Built-in security plugin. Provides three onInstall middleware stages: + * 1. integrityMiddleware — hashes the install dir + * 2. scriptPolicyMiddleware — gates the plugin's own setup command + * 3. dependencyScriptMiddleware — gates dependency lifecycle scripts + * + * All three abort (throw AbortError) on a denied decision. + */ +export const securityPlugin: Plugin = { + name: '@agentplugins/security-gate', + + onInstall: async (ctx, next) => { + await integrityMiddleware(ctx, async () => { + await scriptPolicyMiddleware(ctx, async () => { + await dependencyScriptMiddleware(ctx, next); + }); + }); + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34dc079..11d186f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -408,6 +408,9 @@ importers: '@agentplugins/compile': specifier: workspace:* version: link:../compile + '@agentplugins/pipeline': + specifier: workspace:* + version: link:../pipeline '@agentplugins/security': specifier: workspace:* version: link:../security From ad3ff61997dd248e17c665ce6f03d1ed6c48aae7 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 13:02:26 +0200 Subject: [PATCH 18/90] docs(architecture): add pipeline package, middleware bus, plugin extensibility section - Add @agentplugins/pipeline node to dependency graph (with purple style) - Update all package dependency edges to include pipeline - Add package table row for pipeline - Add "Plugin bus" section explaining Plugin interface, lifecycle hooks, and defineConfig usage - Update compile pipeline flowchart to show preValidate/transformIR/postEmit middleware stages --- ARCHITECTURE.md | 91 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 13cf6f0..d890751 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -3,6 +3,7 @@ AgentPlugins is a **ports-and-adapters (hexagonal) compiler pipeline**. Two orthogonal axes: + - **Capability axis** (hooks/commands/continueWith/…) — *layered*: modeled once in `contract`, emitted via the shared `compile` kernel. Cross-cutting capabilities hit every target, so the kernel handles them once. - **Target axis** (claude/codex/opencode/pimono/…) — *sliced*: each `adapter-*` is a self-contained vertical slice over the kernel, declaring only its event-name table and target config. @@ -19,6 +20,7 @@ graph TD CLI["@agentplugins/cli"] CORE["@agentplugins/core (facade)"] CONTRACT["@agentplugins/contract"] + PIPELINE["@agentplugins/pipeline"] COMPILE["@agentplugins/compile"] STORE["@agentplugins/store"] SCHEMA["@agentplugins/schema"] @@ -31,31 +33,78 @@ graph TD A_OC["adapter-opencode"] A_PI["adapter-pimono"] - CLI --> CORE + CLI --> CORE & PIPELINE CLI --> A_CLAUDE & A_CODEX & A_COPILOT & A_GEMINI & A_KIMI & A_OC & A_PI - CORE --> CONTRACT & COMPILE & STORE + CORE --> CONTRACT & COMPILE & STORE & PIPELINE A_CLAUDE & A_CODEX & A_COPILOT & A_GEMINI & A_KIMI & A_OC & A_PI --> COMPILE - COMPILE --> CONTRACT - STORE --> CONTRACT & SECURITY + COMPILE --> CONTRACT & PIPELINE + STORE --> CONTRACT & SECURITY & PIPELINE SCHEMA --> CONTRACT + PIPELINE --> CONTRACT style CONTRACT fill:#ffd700,color:#000 + style PIPELINE fill:#dda0dd,color:#000 style COMPILE fill:#87ceeb,color:#000 style CORE fill:#90ee90,color:#000 ``` **Allowed dependency directions (inward only):** -| Package | Single responsibility | Allowed deps | -|---|---|---| -| `contract` | Zod schema → TS types → JSON Schema. The single source of truth for the manifest model. | `zod` | -| `compile` | Shared codegen kernel: emit helpers, sanitizers, lint, validation, hook-wrapper. | `contract` | -| `store` | Fetch / install / link / fs isolation. Registry, symlink fanout, doctor. | `contract`, `security` | -| `schema` | Re-exports generated JSON Schema artifacts from `contract` build step. | `contract` | -| `security` | Safe-fetch, integrity verification, script-policy. No plugin dependencies. | — | -| `adapter-*` | Target config + event-name table + calls to kernel helpers. | `compile` (→ `contract`) | -| `core` | Re-export facade. Public API is unchanged; internal origin moves to sub-packages. | `contract`, `compile`, `store` | -| `cli` | Build/add/install/link commands, tty output. | `core`, `adapter-*` | + +| Package | Single responsibility | Allowed deps | +| ----------- | --------------------------------------------------------------------------------------- | ------------------------------------------ | +| `contract` | Zod schema → TS types → JSON Schema. The single source of truth for the manifest model. | `zod` | +| `pipeline` | Middleware kernel: Plugin, Middleware, contexts, createApp(). No business logic. | `contract` | +| `compile` | Shared codegen kernel: emit helpers, sanitizers, lint, validation, hook-wrapper. | `contract`, `pipeline` | +| `store` | Fetch / install / link / fs isolation. Registry, symlink fanout, doctor. | `contract`, `security`, `pipeline` | +| `schema` | Re-exports generated JSON Schema artifacts from `contract` build step. | `contract` | +| `security` | Safe-fetch, integrity verification, script-policy. No plugin dependencies. | — | +| `adapter-*` | Target config + event-name table + calls to kernel helpers. | `compile` (→ `contract`) | +| `core` | Re-export facade. Public API is unchanged; internal origin moves to sub-packages. | `contract`, `compile`, `store`, `pipeline` | +| `cli` | Build/add/install/link commands, tty output. | `core`, `pipeline`, `adapter-*` | + + +--- + +## Plugin bus (`@agentplugins/pipeline`) + +Every pipeline stage is middleware over a shared context with `next()`. Built-in adapters, lint rules, and emitters are registered as default `Plugin` objects; power users compose additional ones via `defineConfig`. + +```ts +// agentplugins.config.ts +import { defineConfig } from '@agentplugins/core'; +import myAdapter from './adapters/my-harness.js'; + +export default defineConfig({ + manifest: { name: 'my-plugin', version: '1.0.0', description: '...' }, + plugins: [ + { + name: 'my-harness', + adapter: myAdapter, // contributes a compile target + transformIR: async (ctx, next) => { // mutates the manifest IR before emit + ctx.manifest = { ...ctx.manifest, /* your transforms */ }; + await next(); + }, + }, + ], + targets: ['claude', 'my-harness'], +}); +``` + +`Plugin` interface (from `@agentplugins/pipeline`): + +| Field | Stage | Purpose | +| -------------- | --------------- | ---------------------------------------- | +| `adapter` | compile | Contribute a compile target | +| `lintRules` | preValidate | Add build-time checks | +| `emitters` | compile | Add code-generation languages | +| `preValidate` | before IR | Validate or reject the manifest | +| `transformIR` | after validate | Mutate the PluginManifest IR | +| `postEmit` | after compile | Inspect/rewrite emitted files per-target | +| `onInstall` | install | Gate or transform install steps | +| `onAudit` | audit | Run custom audit checks | + +`TargetPlatform` is now an open string type — built-in ids keep autocomplete, custom ids are valid as long as a matching adapter is registered. --- @@ -65,15 +114,18 @@ Plugin source → universal IR → per-target files. ```mermaid flowchart LR - SRC["plugin.ts\n(definePlugin)"] + SRC["plugin.ts\n(defineConfig / definePlugin)"] + PREVALI["preValidate\nmiddleware chain"] VALIDATE["validateUniversal()\n+ validateForPlatform()"] IR["PluginManifest IR"] + TRANSIR["transformIR\nmiddleware chain"] LINT["lint()\n(handler-safety, secrets, …)"] EMIT["adapter.compile()\n→ emitCommandHandler()\n→ emitInlineHandler()\n→ sanitizeName/Join()"] + POSTEMIT["postEmit\nmiddleware chain"] FILES["dist//\n*.ts / *.json / *.md"] NATIVE["nativeCopies\n(verbatim passthrough)"] - SRC --> VALIDATE --> IR --> LINT --> EMIT --> FILES + SRC --> PREVALI --> VALIDATE --> TRANSIR --> IR --> LINT --> EMIT --> POSTEMIT --> FILES IR -.->|nativeEntry set| NATIVE --> FILES ``` @@ -94,8 +146,8 @@ Source → fetch → install → link → harness discovers plugin. flowchart LR SRC["GitHub URL\n(or local path)"] CLONE["cloneRepo()\nor pullRepo()"] - STORE_DIR["~/.agents/plugins//"] - BUILD["agentplugins build\n→ dist//"] + STORE_DIR["~/.agents/plugins/{name}/"] + BUILD["agentplugins build\n→ dist/{target}/"] LINK["symlinkPlugin()\nlinkCompiledPlugin()\nlinkPluginSkills()"] HARNESS["Harness plugin dirs\n~/.config/opencode/plugins/\n~/.pi/agent/extensions/\n~/.claude/plugins/\n…"] @@ -130,8 +182,9 @@ interface PlatformAdapter { ``` Adapters declare **only**: + 1. `HOOK_MAPPING: Partial>` — universal → target event name 2. `validate()` — target-specific constraint checks (delegates to `compile` kernel) 3. `compile()` — calls kernel helpers to emit files; must not contain raw `shell: true` or string interpolation into commands -The kernel (`compile`) owns all security-sensitive emit logic. Adapters are thin mappings. +The kernel (`compile`) owns all security-sensitive emit logic. Adapters are thin mappings. \ No newline at end of file From 5bbf26e47bf17a54e6c7809c1d9d55f1bcaf65ae Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 13:02:33 +0200 Subject: [PATCH 19/90] fix(compile,core): return permissive constraints for unknown (custom) targets getPlatformConstraints() was returning undefined for custom targets, crashing validateForPlatform() when a user provides a non-builtin target id. Now falls back to PERMISSIVE_CONSTRAINTS (all hooks/handlers allowed, large limits) so custom-adapter plugins compile without errors. Update contract schema tests: TargetPlatformSchema is now an open string so tests for enum exhaustion become tests for non-empty constraint and custom target acceptance. --- packages/compile/src/validation.ts | 19 +++++++++++++++++-- packages/contract/__tests__/schema.test.ts | 21 +++++++++++++++------ packages/core/src/validation.ts | 19 +++++++++++++++++-- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/packages/compile/src/validation.ts b/packages/compile/src/validation.ts index 0a9fb49..a5f549e 100644 --- a/packages/compile/src/validation.ts +++ b/packages/compile/src/validation.ts @@ -257,8 +257,23 @@ export interface PlatformConstraints { notes: string[]; } +const PERMISSIVE_CONSTRAINTS: PlatformConstraints = { + maxNameLength: 256, + supportsHttpHandler: true, + supportsInlineHandler: true, + supportsPromptHandler: true, + supportsMCPToolHandler: true, + supportsNativeTools: true, + supportsMcpServersAsTools: true, + maxDescriptionLength: 4096, + supportedHooks: UNIVERSAL_HOOK_NAMES, + manifestPath: 'plugin.json', + requiresStrictValidation: false, + notes: ['Custom platform — all hooks and handlers assumed supported'], +}; + export function getPlatformConstraints(platform: TargetPlatform): PlatformConstraints { - const base: Record = { + const base: Partial> = { claude: { maxNameLength: 64, supportsHttpHandler: true, @@ -388,7 +403,7 @@ export function getPlatformConstraints(platform: TargetPlatform): PlatformConstr }, }; - return base[platform]; + return base[platform] ?? PERMISSIVE_CONSTRAINTS; } export function validateForPlatform( diff --git a/packages/contract/__tests__/schema.test.ts b/packages/contract/__tests__/schema.test.ts index eac5a9a..15a13ed 100644 --- a/packages/contract/__tests__/schema.test.ts +++ b/packages/contract/__tests__/schema.test.ts @@ -15,14 +15,23 @@ describe('PluginManifestSchema (single source of truth)', () => { expect(() => PluginManifestSchema.parse({})).toThrow(); }); - it('rejects an invalid target platform', () => { - expect(() => TargetPlatformSchema.parse('not-a-platform')).toThrow(); + it('rejects an empty target platform string', () => { + // TargetPlatform is now an open string — any non-empty value is valid + // so custom harnesses can register their own targets. + expect(() => TargetPlatformSchema.parse('')).toThrow(); }); - it('ALL_TARGETS matches the zod enum set', () => { - // Catches drift between the const array and the zod enum. - const enumValues = TargetPlatformSchema.options; - expect(new Set(enumValues)).toEqual(new Set(ALL_TARGETS)); + it('accepts a custom (non-builtin) target platform', () => { + // Open string type — custom adapters may register any non-empty target id. + expect(() => TargetPlatformSchema.parse('my-harness')).not.toThrow(); + }); + + it('ALL_TARGETS contains the canonical builtin platform ids', () => { + // Verify the builtin list is stable and non-empty. + expect(ALL_TARGETS.length).toBeGreaterThan(0); + expect(ALL_TARGETS).toContain('claude'); + expect(ALL_TARGETS).toContain('codex'); + expect(ALL_TARGETS).toContain('copilot'); }); it('accepts sidecar (currently documentation-only, see B21)', () => { diff --git a/packages/core/src/validation.ts b/packages/core/src/validation.ts index 031a366..7cf96ef 100644 --- a/packages/core/src/validation.ts +++ b/packages/core/src/validation.ts @@ -257,8 +257,23 @@ export interface PlatformConstraints { notes: string[]; } +const PERMISSIVE_CONSTRAINTS: PlatformConstraints = { + maxNameLength: 256, + supportsHttpHandler: true, + supportsInlineHandler: true, + supportsPromptHandler: true, + supportsMCPToolHandler: true, + supportsNativeTools: true, + supportsMcpServersAsTools: true, + maxDescriptionLength: 4096, + supportedHooks: UNIVERSAL_HOOK_NAMES, + manifestPath: 'plugin.json', + requiresStrictValidation: false, + notes: ['Custom platform — all hooks and handlers assumed supported'], +}; + export function getPlatformConstraints(platform: TargetPlatform): PlatformConstraints { - const base: Record = { + const base: Partial> = { claude: { maxNameLength: 64, supportsHttpHandler: true, @@ -388,7 +403,7 @@ export function getPlatformConstraints(platform: TargetPlatform): PlatformConstr }, }; - return base[platform]; + return base[platform] ?? PERMISSIVE_CONSTRAINTS; } export function validateForPlatform( From 0ed70784e98f554e1835de740c4ed00d1111f6c1 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 13:02:38 +0200 Subject: [PATCH 20/90] feat(examples): add example-custom-adapter plugin demonstrating defineConfig + private adapter Shows the minimum PlatformAdapter surface, how to wire a custom target via defineConfig({ plugins: [{ name, adapter }], targets: ['claude', 'my-harness'] }), and produces two dist targets from a single build run. --- .../agentplugins.config.ts | 39 ++++++++++++++ plugins/example-custom-adapter/package.json | 14 +++++ .../src/my-harness-adapter.ts | 52 +++++++++++++++++++ plugins/example-custom-adapter/tsconfig.json | 9 ++++ pnpm-lock.yaml | 12 +++++ 5 files changed, 126 insertions(+) create mode 100644 plugins/example-custom-adapter/agentplugins.config.ts create mode 100644 plugins/example-custom-adapter/package.json create mode 100644 plugins/example-custom-adapter/src/my-harness-adapter.ts create mode 100644 plugins/example-custom-adapter/tsconfig.json diff --git a/plugins/example-custom-adapter/agentplugins.config.ts b/plugins/example-custom-adapter/agentplugins.config.ts new file mode 100644 index 0000000..08105dc --- /dev/null +++ b/plugins/example-custom-adapter/agentplugins.config.ts @@ -0,0 +1,39 @@ +/** + * Example: plugin with a custom adapter for a private harness. + * + * Uses defineConfig instead of definePlugin to wire in the custom adapter + * without publishing to npm. The adapter lives next to this config file. + * + * Run: agentplugins build + * Output: dist/claude/ and dist/my-harness/ + */ + +import { defineConfig } from '@agentplugins/core'; +import { myHarnessAdapter } from './src/my-harness-adapter.js'; + +export default defineConfig({ + manifest: { + name: 'my-custom-plugin', + version: '0.1.0', + description: 'Plugin demonstrating a private custom adapter alongside built-in targets', + + hooks: { + sessionStart: { + handler: { + type: 'command', + command: 'echo "Session started"', + }, + }, + }, + }, + + plugins: [ + { + name: 'my-harness-adapter', + adapter: myHarnessAdapter, + }, + ], + + // Build for Claude (built-in) + my-harness (custom adapter above) + targets: ['claude', 'my-harness'], +}); diff --git a/plugins/example-custom-adapter/package.json b/plugins/example-custom-adapter/package.json new file mode 100644 index 0000000..c93dc89 --- /dev/null +++ b/plugins/example-custom-adapter/package.json @@ -0,0 +1,14 @@ +{ + "name": "agentplugins-example-custom-adapter", + "description": "Example: defineConfig with a private custom adapter", + "type": "module", + "private": true, + "scripts": { + "build": "agentplugins build" + }, + "devDependencies": { + "@agentplugins/core": "workspace:*", + "@agentplugins/cli": "workspace:*", + "typescript": "^5.5.0" + } +} diff --git a/plugins/example-custom-adapter/src/my-harness-adapter.ts b/plugins/example-custom-adapter/src/my-harness-adapter.ts new file mode 100644 index 0000000..6352330 --- /dev/null +++ b/plugins/example-custom-adapter/src/my-harness-adapter.ts @@ -0,0 +1,52 @@ +/** + * Example custom adapter for a fictional "my-harness" platform. + * + * This shows the minimum surface a PlatformAdapter must implement. + * Copy this file into your project and modify it for your target platform. + */ + +import type { PlatformAdapter, PluginManifest, AdapterOutput } from '@agentplugins/core'; + +const MY_HARNESS_TARGET = 'my-harness' as const; + +export const myHarnessAdapter: PlatformAdapter = { + name: MY_HARNESS_TARGET, + displayName: 'My Harness', + supportedHooks: ['sessionStart', 'preToolUse', 'postToolUse'], + supportedHandlers: ['command', 'inline'], + manifestPath: 'my-harness.json', + manifestFormat: 'json', + + validate(plugin: PluginManifest) { + const issues = []; + if (!plugin.name) { + issues.push({ severity: 'error' as const, field: 'name', message: 'name is required' }); + } + return issues; + }, + + compile(plugin: PluginManifest): AdapterOutput { + const manifest = { + name: plugin.name, + version: plugin.version, + description: plugin.description, + runtime: 'node', + }; + + return { + files: [ + { + path: 'my-harness.json', + content: JSON.stringify(manifest, null, 2), + }, + { + path: 'index.js', + content: `// ${plugin.name} v${plugin.version} — compiled by AgentPlugins\nexport const name = '${plugin.name}';\n`, + }, + ], + manifest, + warnings: [], + issues: [], + }; + }, +}; diff --git a/plugins/example-custom-adapter/tsconfig.json b/plugins/example-custom-adapter/tsconfig.json new file mode 100644 index 0000000..d085def --- /dev/null +++ b/plugins/example-custom-adapter/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": ".", + "noEmit": true + }, + "include": ["*.ts", "src/**/*.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11d186f..c048564 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -425,6 +425,18 @@ importers: specifier: ^3.1.4 version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + plugins/example-custom-adapter: + devDependencies: + '@agentplugins/cli': + specifier: workspace:* + version: link:../../packages/cli + '@agentplugins/core': + specifier: workspace:* + version: link:../../packages/core + typescript: + specifier: ^5.5.0 + version: 5.9.3 + plugins/example-logger: devDependencies: '@agentplugins/cli': From 6708cdb816a899c8023c7ca9b2d1c7da3f2d1d22 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 14:13:52 +0200 Subject: [PATCH 21/90] docs(guide): add Extending the Build Pipeline guide Covers Plugin interface, custom adapters (PlatformAdapter), lint rules, and all five middleware hooks (preValidate, transformIR, postEmit, onInstall, onAudit) with copy-paste examples. Adds page to sidebar. --- docs/.vitepress/config.ts | 1 + docs/guide/extending.md | 264 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 docs/guide/extending.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 182a3eb..b1db1d3 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -131,6 +131,7 @@ export default withMermaid(defineConfig({ { text: 'MCP Servers', link: '/guide/mcp-servers' }, { text: 'Tools', link: '/guide/tools' }, { text: 'Creating Plugins', link: '/guide/creating-plugins' }, + { text: 'Extending the Build Pipeline', link: '/guide/extending' }, { text: 'Linting', link: '/guide/linting' }, ], }, diff --git a/docs/guide/extending.md b/docs/guide/extending.md new file mode 100644 index 0000000..8fe52c0 --- /dev/null +++ b/docs/guide/extending.md @@ -0,0 +1,264 @@ +--- +title: Extending the Build Pipeline – AgentPlugins +description: Add custom adapters, lint rules, IR transforms, and post-emit hooks to the AgentPlugins build pipeline without forking the tool. +--- + +# Extending the Build Pipeline + +AgentPlugins ships seven built-in adapters. If you maintain an internal harness, want to add custom lint rules, or need to transform the manifest IR before code generation, the `plugins` field in `defineConfig` gives you full access to the build pipeline without forking anything. + +## When to use `plugins` + +| Need | Mechanism | +|---|---| +| Compile to a private/internal harness | `plugin.adapter` | +| Add project-specific lint checks | `plugin.lintRules` | +| Add a new emit language | `plugin.emitters` | +| Validate or reject the manifest before build | `plugin.preValidate` middleware | +| Mutate the manifest IR (e.g. inject metadata) | `plugin.transformIR` middleware | +| Inspect or rewrite emitted files per-target | `plugin.postEmit` middleware | +| Gate or audit install steps | `plugin.onInstall` / `plugin.onAudit` middleware | + +## Basic setup + +```typescript +// agentplugins.config.ts +import { defineConfig } from '@agentplugins/core' + +export default defineConfig({ + manifest: { + name: 'my-plugin', + version: '1.0.0', + description: 'My cross-platform plugin', + hooks: { /* ... */ }, + }, + + plugins: [ + { + name: 'my-extension', + // fields below — mix and match + }, + ], +}) +``` + +All `plugins` entries are composed on top of the built-in adapter set. Built-in adapters (claude, codex, …) are always registered first; your plugins run after and may override them by registering the same target name. + +--- + +## Custom adapter + +A `PlatformAdapter` tells the build system how to validate and compile the manifest into your harness's native format. + +```typescript +// src/my-harness-adapter.ts +import type { PlatformAdapter } from '@agentplugins/core' + +export const myHarnessAdapter: PlatformAdapter = { + name: 'my-harness', // target id — must match the targets[] list + displayName: 'My Harness', + supportedHooks: ['sessionStart', 'preToolUse', 'postToolUse'], + supportedHandlers: ['command', 'inline'], + manifestPath: 'my-harness.json', + manifestFormat: 'json', + + validate(plugin) { + // Return ValidationIssue[] — errors abort build, warnings are printed + return [] + }, + + compile(plugin) { + return { + files: [ + { + path: 'my-harness.json', + content: JSON.stringify({ name: plugin.name, version: plugin.version }, null, 2), + }, + ], + manifest: {}, + warnings: [], + issues: [], + } + }, +} +``` + +Wire it in via `defineConfig`: + +```typescript +import { defineConfig } from '@agentplugins/core' +import { myHarnessAdapter } from './src/my-harness-adapter.js' + +export default defineConfig({ + manifest: { name: 'my-plugin', version: '1.0.0', description: '…' }, + + plugins: [ + { name: 'my-harness-adapter', adapter: myHarnessAdapter }, + ], + + targets: ['claude', 'my-harness'], +}) +``` + +`my-harness` is now a valid target id. Unknown target ids that have no registered adapter are skipped at build time with a warning. + +::: tip Full working example +`plugins/example-custom-adapter/` in the repository shows this pattern end-to-end, producing `dist/claude/` and `dist/my-harness/` from one `agentplugins build` run. +::: + +--- + +## Custom lint rules + +Add build-time checks that run alongside the built-in lint rules: + +```typescript +import type { LintRule } from '@agentplugins/pipeline' + +const requireLicenseRule: LintRule = { + id: 'require-license', + description: 'All plugins in this org must declare a license', + check(ctx) { + if (!ctx.manifest.license) { + return [{ + severity: 'error', + field: 'license', + message: 'license is required for org plugins', + suggestion: 'Add license: "MIT" to your manifest', + }] + } + return [] + }, +} + +export default defineConfig({ + manifest: { /* … */ }, + plugins: [ + { name: 'org-rules', lintRules: [requireLicenseRule] }, + ], +}) +``` + +Custom rules run in strict mode by default — errors abort the build, warnings are printed. + +--- + +## Pipeline middleware + +Middleware functions follow the standard `(ctx, next) => Promise` onion pattern. Call `await next()` to proceed, or `ctx.abort(reason)` to stop the pipeline. + +### `preValidate` — reject before validation + +Runs before `validateUniversal()`. Use it to enforce org-wide manifest constraints: + +```typescript +{ + name: 'org-guard', + preValidate: async (ctx, next) => { + if (!ctx.manifest.name.startsWith('acme-')) { + ctx.abort('All ACME plugins must be named acme-*') + } + await next() + }, +} +``` + +### `transformIR` — mutate the manifest IR + +Runs after validation, before code generation. Use it to inject metadata or normalize fields: + +```typescript +{ + name: 'inject-build-metadata', + transformIR: async (ctx, next) => { + ctx.manifest = { + ...ctx.manifest, + description: `[${process.env.CI_COMMIT_SHA?.slice(0, 7) ?? 'local'}] ${ctx.manifest.description}`, + } + await next() + }, +} +``` + +### `postEmit` — inspect or rewrite emitted files + +Runs per-target after the adapter has produced its files. `ctx.files` is the mutable list of `{ path, content }` entries: + +```typescript +{ + name: 'add-banner', + postEmit: async (ctx, next) => { + ctx.files = ctx.files.map(f => ({ + ...f, + content: `// Built by ACME CI — do not edit\n${f.content}`, + })) + await next() + }, +} +``` + +### `onInstall` — gate or audit install + +Runs during `agentplugins add` / `agentplugins install`. The built-in `securityPlugin` is registered here by default (hash integrity, script policy). You may add your own checks after it: + +```typescript +{ + name: 'org-install-policy', + onInstall: async (ctx, next) => { + if (ctx.pluginName.startsWith('untrusted-')) { + ctx.abort(`Plugin "${ctx.pluginName}" is blocked by org policy`) + } + await next() + }, +} +``` + +::: warning +`onInstall` plugins run in the user's environment, not the plugin author's. Only ship install middleware as part of org-internal tooling, not in public plugins. +::: + +--- + +## Plugin interface reference + +```typescript +interface Plugin { + readonly name: string + + // Compile + adapter?: PlatformAdapter + lintRules?: LintRule[] + emitters?: Record + + // Build pipeline middleware + preValidate?: Middleware + transformIR?: Middleware + postEmit?: Middleware + + // Install pipeline middleware + onAudit?: Middleware + onInstall?: Middleware +} +``` + +Each field is optional — a plugin may contribute any combination. + +## Middleware execution order + +1. All `preValidate` chains run (user plugins after builtins) +2. `validateUniversal()` + `validateForPlatform()` +3. All `transformIR` chains run +4. `lint()` with merged lint rules +5. Per-target: `adapter.compile()` → all `postEmit` chains +6. Files written to `dist/` + +Install pipeline: + +1. All `onInstall` chains run (security middleware first) +2. Files linked into agent directories + +## See also + +- [Creating Plugins](/guide/creating-plugins) — manifest authoring from scratch +- [Adapters reference](/reference/adapters) — built-in adapter output formats +- [Linting](/guide/linting) — built-in lint rules From 08f68f13a6cb5542d3328312d6e6025d06ee3426 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 14:13:57 +0200 Subject: [PATCH 22/90] docs(guide): document defineConfig alongside definePlugin in creating-plugins Shows the extended config format (manifest + plugins + targets fields), explains when to prefer it over definePlugin, and links to the new extending guide. --- docs/guide/creating-plugins.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/guide/creating-plugins.md b/docs/guide/creating-plugins.md index df26e65..1f9f359 100644 --- a/docs/guide/creating-plugins.md +++ b/docs/guide/creating-plugins.md @@ -78,6 +78,34 @@ export default definePlugin({ }) ``` +### `defineConfig` — extended config format + +Use `defineConfig` instead of `definePlugin` when you need to: + +- Target a **subset of platforms** without editing the manifest +- Wire in a **private adapter** for an internal harness +- Add **build pipeline plugins** (custom lint rules, IR transforms, post-emit hooks) + +```typescript +import { defineConfig } from '@agentplugins/core' + +export default defineConfig({ + manifest: { + name: 'my-plugin', + version: '1.0.0', + description: 'Does awesome things across every agent', + hooks: { /* ... */ }, + }, + + // Override which targets are built — does not affect the manifest + targets: ['claude', 'codex'], +}) +``` + +`definePlugin` and `defineConfig` produce the same dist output for the same manifest. Pick `definePlugin` for simple cross-platform plugins; reach for `defineConfig` when you need the extras above. + +See [Extending the Build Pipeline](/guide/extending) for `plugins: [...]` and custom adapters. + ::: tip Place hook scripts under `hooks/` and reference them with `${PLUGIN_ROOT}/hooks/...`. The placeholder resolves to the plugin's directory in the universal store at runtime. ::: @@ -226,4 +254,5 @@ Tag releases with semver (`v1.0.0`, `v1.1.0`, ...). `agentplugins update` resolv - [Manifest reference](/guide/manifest) — every field. - [Hooks](/guide/hooks) — the 19 lifecycle events. +- [Extending the Build Pipeline](/guide/extending) — custom adapters, lint rules, and pipeline plugins. - [CLI reference](/reference/commands) — every command and flag. From 5c9cc66bbbb1b3c46b0fce151a269b58a51b02f4 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 14:14:01 +0200 Subject: [PATCH 23/90] docs(reference): replace stub adapter section with PlatformAdapter interface docs Documents the full PlatformAdapter + AdapterOutput interfaces, the custom adapter registration pattern via defineConfig, and links to the new extending guide. Replaces the previous process-ABI stub. --- docs/reference/adapters.md | 50 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/docs/reference/adapters.md b/docs/reference/adapters.md index 3a5d5d9..f5c6565 100644 --- a/docs/reference/adapters.md +++ b/docs/reference/adapters.md @@ -191,12 +191,58 @@ exec bun "${PLUGIN_ROOT}/dist/handler.js" pre-tool-use "$@" This means inline handlers work everywhere — at the cost of a Bun startup on platforms that don't support them natively. For latency-sensitive hooks on Claude/Codex/Gemini/Kimi, prefer `command` handlers. -## Adding a new adapter +## Custom adapters -An adapter is any executable that implements the **JSON process ABI**: read a manifest (stdin or `--manifest `), compile platform-specific output, write files, and exit `0` (success) or non-zero (failure). This enables any-language adapters without SDK lock-in. See [`adapter.schema.json`](https://raw.githubusercontent.com/sigilco/agentplugins/main/spec/v1/adapter.schema.json) for the contract. +`TargetPlatform` is an open string type — any non-empty target id is valid. Register a custom adapter via the `plugins` field in `defineConfig` and add the target id to `targets`: + +```typescript +// agentplugins.config.ts +import { defineConfig } from '@agentplugins/core' +import { myHarnessAdapter } from './src/my-harness-adapter.js' + +export default defineConfig({ + manifest: { name: 'my-plugin', version: '1.0.0', description: '…' }, + plugins: [{ name: 'my-harness', adapter: myHarnessAdapter }], + targets: ['claude', 'my-harness'], +}) +``` + +### `PlatformAdapter` interface + +```typescript +interface PlatformAdapter { + readonly name: string // target id + readonly displayName: string // human label + readonly supportedHooks: UniversalHookName[] // used by lint + readonly supportedHandlers: HandlerType[] // used by lint + readonly manifestPath: string // primary output path (for install) + readonly manifestFormat: 'json' | 'toml' + + validate(plugin: PluginManifest): ValidationIssue[] + compile(plugin: PluginManifest, options?: CompileOptions): AdapterOutput +} + +interface AdapterOutput { + files: { path: string; content: string }[] + manifest: Record + warnings: string[] + issues: ValidationIssue[] + postInstall?: string[] // commands to run after install + nativeCopies?: NativeCopy[] // verbatim file passthrough +} +``` + +`validate()` returns `ValidationIssue[]` — severity `'error'` aborts the build; `'warning'` is printed and continues. + +`compile()` returns the full set of files to write into `dist//`. Paths are relative to that directory. + +For a working example, see `plugins/example-custom-adapter/` in the repository. + +See [Extending the Build Pipeline](/guide/extending) for the full guide including lint rules and middleware hooks. ## Next steps - [Manifest reference](/guide/manifest) — what every field means. - [Hooks](/guide/hooks) — the 19 universal events. +- [Extending the Build Pipeline](/guide/extending) — custom adapters and pipeline plugins. - [Agent paths](/reference/agent-paths) — where each adapter writes its output. From 974b5427354062b059defcb5f95c8048397a9040 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 20:50:28 +0200 Subject: [PATCH 24/90] fix(compile): add knownTargets option to validateUniversal; dedupe core re-export --- packages/compile/src/validation.ts | 8 +- packages/core/src/validation.ts | 524 +---------------------------- 2 files changed, 12 insertions(+), 520 deletions(-) diff --git a/packages/compile/src/validation.ts b/packages/compile/src/validation.ts index a5f549e..08e3b6b 100644 --- a/packages/compile/src/validation.ts +++ b/packages/compile/src/validation.ts @@ -21,7 +21,11 @@ import { const KEBAB_CASE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/; const SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; -export function validateUniversal(plugin: PluginManifest): ValidationIssue[] { +export function validateUniversal( + plugin: PluginManifest, + opts?: { knownTargets?: readonly string[] } +): ValidationIssue[] { + const knownTargets = opts?.knownTargets ?? (ALL_TARGETS as string[]); const issues: ValidationIssue[] = []; // ─── name ───────────────────────────────────────────────────────────────── @@ -71,7 +75,7 @@ export function validateUniversal(plugin: PluginManifest): ValidationIssue[] { // ─── targets ────────────────────────────────────────────────────────────── if (plugin.targets) { for (const target of plugin.targets) { - if (!(ALL_TARGETS as string[]).includes(target)) { + if (!(knownTargets as string[]).includes(target)) { issues.push({ severity: Severity.WARNING, field: 'targets', diff --git a/packages/core/src/validation.ts b/packages/core/src/validation.ts index 7cf96ef..a2083fb 100644 --- a/packages/core/src/validation.ts +++ b/packages/core/src/validation.ts @@ -1,518 +1,6 @@ -/** - * AgentPlugins Validation Layer - * - * Catches cross-platform issues before compilation. - * Provides platform-specific and universal validators. - */ - -import { - type PluginManifest, - type ValidationIssue, - type UniversalHookName, - type HookHandler, - UNIVERSAL_HOOK_NAMES, - ALL_TARGETS, - type TargetPlatform, - Severity, -} from './types.js'; - -// ─── Universal Validators ───────────────────────────────────────────────────── - -const KEBAB_CASE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/; -const SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; - -export function validateUniversal(plugin: PluginManifest): ValidationIssue[] { - const issues: ValidationIssue[] = []; - - // ─── name ───────────────────────────────────────────────────────────────── - if (!plugin.name) { - issues.push({ severity: Severity.ERROR, field: 'name', message: 'Plugin name is required' }); - } else { - if (!KEBAB_CASE_RE.test(plugin.name)) { - issues.push({ - severity: Severity.ERROR, - field: 'name', - message: `Plugin name "${plugin.name}" must be kebab-case (lowercase letters, numbers, hyphens only)`, - suggestion: `Rename to: ${toKebabCase(plugin.name)}`, - }); - } - if (plugin.name.length > 64) { - issues.push({ - severity: Severity.ERROR, - field: 'name', - message: `Plugin name "${plugin.name}" is ${plugin.name.length} chars (max 64)`, - }); - } - } - - // ─── version ────────────────────────────────────────────────────────────── - if (!plugin.version) { - issues.push({ severity: Severity.ERROR, field: 'version', message: 'Plugin version is required' }); - } else if (!SEMVER_RE.test(plugin.version)) { - issues.push({ - severity: Severity.ERROR, - field: 'version', - message: `Version "${plugin.version}" is not valid semantic versioning`, - suggestion: 'Use format like "1.0.0"', - }); - } - - // ─── description ────────────────────────────────────────────────────────── - if (!plugin.description) { - issues.push({ severity: Severity.WARNING, field: 'description', message: 'Description is recommended for discovery' }); - } else if (plugin.description.length > 1024) { - issues.push({ - severity: Severity.WARNING, - field: 'description', - message: `Description is ${plugin.description.length} chars (some platforms truncate at 1024)`, - }); - } - - // ─── targets ────────────────────────────────────────────────────────────── - if (plugin.targets) { - for (const target of plugin.targets) { - if (!(ALL_TARGETS as string[]).includes(target)) { - issues.push({ - severity: Severity.WARNING, - field: 'targets', - message: `"${target}" is not a built-in target — ensure a matching adapter is registered or compilation will be skipped`, - suggestion: `Built-in targets: ${ALL_TARGETS.join(', ')}`, - }); - } - } - } - - // ─── hooks ──────────────────────────────────────────────────────────────── - if (plugin.hooks) { - for (const [name, def] of Object.entries(plugin.hooks)) { - const hookName = name as UniversalHookName; - if (!UNIVERSAL_HOOK_NAMES.includes(hookName)) { - issues.push({ - severity: Severity.WARNING, - field: `hooks.${name}`, - message: `Hook "${name}" is not a universal hook — it will be ignored by some platforms`, - }); - continue; - } - if (def) { - issues.push(...validateHandler(hookName, def.handler)); - } - } - } - - // ─── skills ─────────────────────────────────────────────────────────────── - if (plugin.skills) { - for (let i = 0; i < plugin.skills.length; i++) { - const skill = plugin.skills[i]; - if (!skill.name) { - issues.push({ severity: Severity.ERROR, field: `skills[${i}].name`, message: 'Skill name is required' }); - } else if (!KEBAB_CASE_RE.test(skill.name)) { - issues.push({ - severity: Severity.WARNING, - field: `skills[${i}].name`, - message: `Skill name "${skill.name}" should be kebab-case`, - suggestion: `Rename to: ${toKebabCase(skill.name)}`, - }); - } - if (!skill.description) { - issues.push({ severity: Severity.WARNING, field: `skills[${i}].description`, message: 'Skill description is recommended' }); - } - if (!skill.content && !skill.filePath) { - issues.push({ - severity: Severity.ERROR, - field: `skills[${i}]`, - message: 'Skill must have either "content" or "filePath"', - }); - } - } - } - - // ─── dependencies ──────────────────────────────────────────────────────── - if (plugin.dependencies) { - for (let i = 0; i < plugin.dependencies.length; i++) { - const dep = plugin.dependencies[i]; - if (dep.type === 'npm') { - if (!dep.name) { - issues.push({ severity: Severity.ERROR, field: `dependencies[${i}].name`, message: 'npm dependency must have a "name"' }); - } - if (!dep.version) { - issues.push({ severity: Severity.WARNING, field: `dependencies[${i}].version`, message: `npm dependency "${dep.name ?? i}" has no version pinned — resolution is implicit` }); - } - } else if (dep.type === 'binary') { - if (!dep.name) { - issues.push({ severity: Severity.ERROR, field: `dependencies[${i}].name`, message: 'binary dependency must have a "name"' }); - } - } - } - } - - // ─── sidecar ────────────────────────────────────────────────────────────── - if (plugin.sidecar) { - if (!plugin.sidecar.command) { - issues.push({ severity: Severity.WARNING, field: 'sidecar.command', message: 'sidecar command is empty' }); - } else if (plugin.sidecar.port !== undefined) { - if (plugin.sidecar.port < 0 || plugin.sidecar.port > 65535) { - issues.push({ - severity: Severity.ERROR, - field: 'sidecar.port', - message: `sidecar port ${plugin.sidecar.port} is out of range (0-65535)`, - }); - } - } - } - - // ─── integrity ──────────────────────────────────────────────────────────── - if (plugin.integrity !== undefined) { - if (!/^sha256:[a-f0-9]{64}$/i.test(plugin.integrity)) { - issues.push({ - severity: Severity.ERROR, - field: 'integrity', - message: 'integrity must be in the form "sha256:<64 hex chars>"', - suggestion: 'Compute with: shasum -a 256 | awk \'{print "sha256:" $1}\'', - }); - } - } - - // ─── tools ──────────────────────────────────────────────────────────────── - if (plugin.tools) { - for (let i = 0; i < plugin.tools.length; i++) { - const tool = plugin.tools[i]; - if (!tool.name) { - issues.push({ severity: Severity.ERROR, field: `tools[${i}].name`, message: 'Tool name is required' }); - } - if (!tool.description) { - issues.push({ severity: Severity.WARNING, field: `tools[${i}].description`, message: 'Tool description is recommended' }); - } - // Tool handlers are runtime functions, not HookHandler objects - // This check only applies to hooks, not tools - if ((tool as any).handlerType === 'inline' && (tool as any).handlerImpl === undefined) { - issues.push({ - severity: Severity.WARNING, - field: `tools[${i}].handler`, - message: 'Tool has no handler — will be a no-op on platforms without native tool support', - }); - } - } - } - - return issues; -} - -function validateHandler(hookName: string, handler: HookHandler): ValidationIssue[] { - const issues: ValidationIssue[] = []; - - switch (handler.type) { - case 'command': { - if (!handler.command) { - issues.push({ severity: Severity.ERROR, field: `hooks.${hookName}.command`, message: 'Command string is required' }); - } - // Check for dangerous patterns - if (handler.command?.includes('rm -rf /')) { - issues.push({ severity: Severity.WARNING, field: `hooks.${hookName}.command`, message: 'Command contains dangerous pattern "rm -rf /"' }); - } - break; - } - case 'http': { - if (!handler.url) { - issues.push({ severity: Severity.ERROR, field: `hooks.${hookName}.url`, message: 'HTTP URL is required' }); - } else { - // Basic URL validation using pattern check - const urlPattern = /^https?:\/\/.+/; - if (!urlPattern.test(handler.url)) { - issues.push({ severity: Severity.ERROR, field: `hooks.${hookName}.url`, message: `Invalid URL: ${handler.url}` }); - } - } - break; - } - case 'inline': { - if (typeof handler.handler !== 'function') { - issues.push({ severity: Severity.ERROR, field: `hooks.${hookName}.handler`, message: 'Inline handler must be a function' }); - } - break; - } - default: { - issues.push({ severity: Severity.ERROR, field: `hooks.${hookName}.type`, message: `Unknown handler type` }); - } - } - - return issues; -} - -// ─── Platform-Specific Validation Helpers ──────────────────────────────────── - -export interface PlatformConstraints { - maxNameLength: number; - supportsHttpHandler: boolean; - supportsInlineHandler: boolean; - supportsPromptHandler: boolean; - supportsMCPToolHandler: boolean; - /** True when the adapter emits native tool definitions from plugin.tools[]. */ - supportsNativeTools: boolean; - /** True when the platform can consume mcpServers as a tool delivery mechanism. */ - supportsMcpServersAsTools: boolean; - maxDescriptionLength: number; - supportedHooks: readonly UniversalHookName[]; - manifestPath: string; - requiresStrictValidation: boolean; - notes: string[]; -} - -const PERMISSIVE_CONSTRAINTS: PlatformConstraints = { - maxNameLength: 256, - supportsHttpHandler: true, - supportsInlineHandler: true, - supportsPromptHandler: true, - supportsMCPToolHandler: true, - supportsNativeTools: true, - supportsMcpServersAsTools: true, - maxDescriptionLength: 4096, - supportedHooks: UNIVERSAL_HOOK_NAMES, - manifestPath: 'plugin.json', - requiresStrictValidation: false, - notes: ['Custom platform — all hooks and handlers assumed supported'], -}; - -export function getPlatformConstraints(platform: TargetPlatform): PlatformConstraints { - const base: Partial> = { - claude: { - maxNameLength: 64, - supportsHttpHandler: true, - supportsInlineHandler: false, - supportsPromptHandler: true, - supportsMCPToolHandler: true, - supportsNativeTools: false, - supportsMcpServersAsTools: true, - maxDescriptionLength: 1024, - supportedHooks: [ - 'sessionStart', 'sessionEnd', 'userPromptSubmit', 'userPromptExpansion', - 'preToolUse', 'postToolUse', 'postToolUseFailure', 'permissionRequest', - 'permissionDenied', 'subagentStart', 'subagentStop', 'preCompact', - 'postCompact', 'stop', 'stopFailure', 'notification', 'fileChanged', - 'cwdChanged', 'setup', - ], - manifestPath: '.claude-plugin/plugin.json', - requiresStrictValidation: false, - notes: ['5 handler types: command, http, mcp_tool, prompt, agent', 'Use mcpServers for tools — plugin.tools[] is not natively emitted'], - }, - codex: { - maxNameLength: 64, - supportsHttpHandler: false, - supportsInlineHandler: true, - supportsPromptHandler: false, - supportsMCPToolHandler: false, - supportsNativeTools: false, - supportsMcpServersAsTools: true, - maxDescriptionLength: 1024, - supportedHooks: [ - 'sessionStart', 'subagentStart', 'preToolUse', 'permissionRequest', - 'postToolUse', 'preCompact', 'postCompact', 'userPromptSubmit', - 'subagentStop', 'stop', - ], - manifestPath: '.codex-plugin/plugin.json', - requiresStrictValidation: false, - notes: ['Inline handlers auto-wrapped as Node.js command scripts (v0.4.0)', 'Use mcpServers for tools — plugin.tools[] is not natively emitted'], - }, - copilot: { - maxNameLength: 64, - supportsHttpHandler: true, - supportsInlineHandler: false, - supportsPromptHandler: true, - supportsMCPToolHandler: false, - supportsNativeTools: false, - supportsMcpServersAsTools: false, - maxDescriptionLength: 1024, - supportedHooks: [ - 'sessionStart', 'sessionEnd', 'userPromptSubmit', 'preToolUse', - 'postToolUse', 'postToolUseFailure', 'subagentStart', 'subagentStop', - 'stop', 'preCompact', 'permissionRequest', 'notification', - ], - manifestPath: 'plugin.json', - requiresStrictValidation: true, - notes: ['preToolUse is fail-closed: errors/timeouts deny tool calls', 'No native tool or MCP support'], - }, - gemini: { - maxNameLength: 64, - supportsHttpHandler: false, - supportsInlineHandler: false, - supportsPromptHandler: false, - supportsMCPToolHandler: false, - supportsNativeTools: false, - supportsMcpServersAsTools: false, - maxDescriptionLength: 1024, - supportedHooks: [ - 'sessionStart', 'preToolUse', 'postToolUse', 'preCompact', - 'notification', 'userPromptSubmit', 'subagentStart', 'subagentStop', - 'stop', - ], - manifestPath: 'gemini-extension.json', - requiresStrictValidation: true, - notes: ['Uses exit codes: 0=success, 2=block, other=warning', 'No native tool or MCP support'], - }, - kimi: { - maxNameLength: 64, - supportsHttpHandler: true, - supportsInlineHandler: true, - supportsPromptHandler: false, - supportsMCPToolHandler: false, - supportsNativeTools: false, - supportsMcpServersAsTools: false, - maxDescriptionLength: 1024, - supportedHooks: [ - 'sessionStart', 'userPromptSubmit', 'preToolUse', 'permissionRequest', - 'notification', 'stop', - ], - manifestPath: 'kimi.plugin.json', - requiresStrictValidation: true, - notes: ['Inline handlers auto-wrapped as Node.js command scripts (v0.4.0)', 'Hooks are fail-open (not fail-closed like Copilot)', 'No native tool or MCP support'], - }, - opencode: { - maxNameLength: 64, - supportsHttpHandler: false, - supportsInlineHandler: true, - supportsPromptHandler: false, - supportsMCPToolHandler: false, - supportsNativeTools: true, - supportsMcpServersAsTools: true, - maxDescriptionLength: 1024, - supportedHooks: [ - 'sessionStart', 'sessionEnd', 'preToolUse', 'postToolUse', - 'permissionRequest', 'notification', 'preCompact', 'stop', - ], - manifestPath: 'package.json', // Uses npm package.json + opencode.json - requiresStrictValidation: false, - notes: ['TypeScript-native, inline handlers only. Bun runtime.', 'Supports both plugin.tools[] and mcpServers'], - }, - pimono: { - maxNameLength: 64, - supportsHttpHandler: false, - supportsInlineHandler: true, - supportsPromptHandler: false, - supportsMCPToolHandler: false, - supportsNativeTools: true, - supportsMcpServersAsTools: true, - maxDescriptionLength: 1024, - supportedHooks: [ - 'sessionStart', 'sessionEnd', 'userPromptSubmit', 'preToolUse', - 'postToolUse', 'permissionRequest', 'subagentStart', 'subagentStop', - 'preCompact', 'postCompact', 'stop', 'stopFailure', 'notification', - 'setup', - ], - manifestPath: 'package.json', // Uses package.json with "pi" key - requiresStrictValidation: false, - notes: ['TypeScript-native, inline handlers only. Uses ExtensionAPI pattern.', 'Supports both plugin.tools[] and mcpServers'], - }, - }; - - return base[platform] ?? PERMISSIVE_CONSTRAINTS; -} - -export function validateForPlatform( - plugin: PluginManifest, - platform: TargetPlatform -): ValidationIssue[] { - const constraints = getPlatformConstraints(platform); - const issues: ValidationIssue[] = []; - - // Check unsupported hooks - if (plugin.hooks) { - for (const [name, def] of Object.entries(plugin.hooks)) { - const hookName = name as UniversalHookName; - if (!def) continue; - - if (!constraints.supportedHooks.includes(hookName)) { - issues.push({ - severity: Severity.WARNING, - field: `hooks.${hookName}`, - message: `"${hookName}" is not supported by ${platform} — this hook will be ignored`, - suggestion: findNearestEquivalent(hookName, constraints.supportedHooks), - }); - } - - // Check handler type compatibility - const handler = def.handler; - if (handler.type === 'http' && !constraints.supportsHttpHandler) { - issues.push({ - severity: Severity.ERROR, - field: `hooks.${hookName}.handler`, - message: `HTTP handlers are not supported by ${platform}`, - suggestion: 'Use "command" or "inline" handler type instead', - }); - } - if (handler.type === 'inline' && !constraints.supportsInlineHandler) { - issues.push({ - severity: Severity.INFO, - field: `hooks.${hookName}.handler`, - message: `Inline handlers on ${platform} will be auto-wrapped as command scripts`, - }); - } - } - } - - // ─── tools[] cross-harness compatibility ──────────────────────────────────── - if (plugin.tools && plugin.tools.length > 0 && !constraints.supportsNativeTools) { - const suggestion = constraints.supportsMcpServersAsTools - ? `Use "mcpServers" instead — it is the recommended universal tool mechanism for ${platform}. See /guide/mcp-servers.` - : `${platform} does not support native tools or mcpServers. Tools in this manifest will be ignored on ${platform}.`; - issues.push({ - severity: Severity.WARNING, - field: 'tools', - message: - `"tools[]" is not natively emitted by the ${platform} adapter. ` + suggestion, - suggestion, - }); - } - - // Check name length - if (plugin.name && plugin.name.length > constraints.maxNameLength) { - issues.push({ - severity: Severity.ERROR, - field: 'name', - message: `Name "${plugin.name}" (${plugin.name.length} chars) exceeds ${platform} limit of ${constraints.maxNameLength}`, - }); - } - - // Check description length - if (plugin.description && plugin.description.length > constraints.maxDescriptionLength) { - issues.push({ - severity: Severity.WARNING, - field: 'description', - message: `Description may be truncated on ${platform} (max ${constraints.maxDescriptionLength} chars)`, - }); - } - - return issues; -} - -// ─── Helpers ────────────────────────────────────────────────────────────────── - -function toKebabCase(str: string): string { - return str - .replace(/([a-z])([A-Z])/g, '$1-$2') - .replace(/[\s_]+/g, '-') - .replace(/[^a-zA-Z0-9-]/g, '') - .toLowerCase() - .replace(/^-+|-+$/g, '') - .replace(/-+/g, '-'); -} - -function findNearestEquivalent( - hook: UniversalHookName, - supported: readonly UniversalHookName[] -): string | undefined { - // Map of known equivalents - const equivalents: Partial> = { - userPromptExpansion: 'userPromptSubmit', - permissionDenied: 'permissionRequest', - postToolUseFailure: 'postToolUse', - setup: 'sessionStart', - cwdChanged: 'fileChanged', - stopFailure: 'stop', - }; - - const equiv = equivalents[hook]; - if (equiv && supported.includes(equiv)) { - return `Use "${equiv}" instead, which is supported by this platform`; - } - - return undefined; -} +export { + validateUniversal, + validateForPlatform, + getPlatformConstraints, +} from '@agentplugins/compile'; +export type { PlatformConstraints } from '@agentplugins/compile'; From be0b0b88aeff61b52ea6b70c29eb36f0efcf5db5 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 20:50:32 +0200 Subject: [PATCH 25/90] fix(store/plugin): add pinnedIntegrityMiddleware to securityPlugin; gate existsSync before hash --- packages/store/src/plugin.ts | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/store/src/plugin.ts b/packages/store/src/plugin.ts index 98be59a..d08140a 100644 --- a/packages/store/src/plugin.ts +++ b/packages/store/src/plugin.ts @@ -6,8 +6,9 @@ * pipeline — power users may remove or reorder it via defineConfig. */ +import { existsSync } from 'node:fs'; import type { Plugin, InstallCtx } from '@agentplugins/pipeline'; -import { hashDirectory, evaluateScriptPolicy, DEFAULT_POLICY } from '@agentplugins/security'; +import { hashDirectory, verifyIntegrity, evaluateScriptPolicy, DEFAULT_POLICY } from '@agentplugins/security'; import { resolveSetupCommand, gateSetupCommand } from './setup.js'; /** Abort keys written to InstallCtx.meta by securityPlugin middleware. */ @@ -16,13 +17,31 @@ export const SECURITY_META_KEYS = { scriptDecision: 'security:scriptDecision', } as const; +/** + * Middleware: verifies the install directory against the manifest's pinned + * integrity field. Aborts if the hash does not match. + */ +async function pinnedIntegrityMiddleware(ctx: InstallCtx, next: () => Promise): Promise { + const manifest = ctx.manifest as unknown as Record; + const integrity = manifest['integrity'] as string | undefined; + + if (integrity && integrity.length > 0) { + const { match, reason } = verifyIntegrity(ctx.installDir, integrity); + if (!match) { + ctx.abort(`Integrity check failed: ${reason ?? 'hash mismatch'}`); + } + } + + await next(); +} + /** * Middleware: hashes the install directory and stores the result in ctx.meta. * Does not abort — callers may inspect ctx.meta[SECURITY_META_KEYS.integrity] * to decide whether to block. */ async function integrityMiddleware(ctx: InstallCtx, next: () => Promise): Promise { - if (ctx.installDir) { + if (ctx.installDir && existsSync(ctx.installDir)) { const hash = hashDirectory(ctx.installDir); ctx.meta[SECURITY_META_KEYS.integrity] = hash; } @@ -94,9 +113,11 @@ export const securityPlugin: Plugin = { name: '@agentplugins/security-gate', onInstall: async (ctx, next) => { - await integrityMiddleware(ctx, async () => { - await scriptPolicyMiddleware(ctx, async () => { - await dependencyScriptMiddleware(ctx, next); + await pinnedIntegrityMiddleware(ctx, async () => { + await integrityMiddleware(ctx, async () => { + await scriptPolicyMiddleware(ctx, async () => { + await dependencyScriptMiddleware(ctx, next); + }); }); }); }, From 2b0c9845842100c854572897ec97dc1fa4da795f Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 20:50:36 +0200 Subject: [PATCH 26/90] feat(cli/build): wire runBuild/runTarget, lintRules, emitters, knownTargets; drop unadopted middleware factories --- packages/cli/src/commands/build.ts | 77 ++++++++++++----- packages/compile/src/middleware.ts | 133 +---------------------------- packages/pipeline/src/types.ts | 1 + 3 files changed, 61 insertions(+), 150 deletions(-) diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index 80a8ab2..02aa707 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -15,8 +15,8 @@ import { type PluginManifest, type CompileOptions as AdapterCompileOptions, } from '@agentplugins/core'; -import { sanitizeJoin, lint, type LintIssue } from '@agentplugins/compile'; -import { createApp } from '@agentplugins/pipeline'; +import { sanitizeJoin, lint, registerEmitter, type LintIssue } from '@agentplugins/compile'; +import { createApp, createBuildCtx, createTargetCtx, AbortError } from '@agentplugins/pipeline'; import type { App, Plugin } from '@agentplugins/pipeline'; import type { LoadedConfig } from '../config.js'; @@ -69,6 +69,11 @@ async function buildApp(userPlugins: Plugin[] = []): Promise { app.use(plugin); } + // Register custom code emitters into the global codegen registry + for (const [, emitter] of app.emitters) { + registerEmitter(emitter as Parameters[0]); + } + return app; } @@ -98,6 +103,8 @@ export interface CompileOptions { pluginRoot?: string; /** User-provided pipeline plugins from defineConfig. */ plugins?: Plugin[]; + /** Pre-built app; skips buildApp() when provided (used by build() to avoid double-init). */ + _app?: App; } /** @@ -106,12 +113,22 @@ export interface CompileOptions { * Returns per-target results. */ export async function compile(options: CompileOptions): Promise { - const { manifest, write = false, outDir, silent = false, pluginRoot, plugins = [] } = options; + const { write = false, outDir, silent = false, pluginRoot, plugins = [] } = options; + const app = options._app ?? await buildApp(plugins); + + // Run preValidate + transformIR lifecycle hooks; use possibly mutated manifest + const buildCtx = createBuildCtx({ + manifest: options.manifest, + targets: (resolveTargets(options.targets, options.manifest.targets) as string[]), + outDir, + pluginRoot, + }); + await app.runBuild(buildCtx); + const manifest = buildCtx.manifest; + const targetList = resolveTargets(options.targets, manifest.targets); const results: CompileResult[] = []; - const app = await buildApp(plugins); - for (const target of targetList) { const adapter = app.adapters.get(target); if (!adapter) { @@ -133,18 +150,30 @@ export async function compile(options: CompileOptions): Promise try { const output = adapter.compile(manifest, { pluginRoot } as AdapterCompileOptions); + // Run postEmit hooks; plugins can append/rewrite files + const targetCtx = createTargetCtx({ manifest, target, pluginRoot }); + for (const file of output.files) targetCtx.addFile(file); + for (const w of output.warnings) targetCtx.addWarning(w); + if (output.nativeCopies) { + for (const copy of output.nativeCopies) targetCtx.addNativeCopy(copy); + } + if (output.postInstall) { + for (const step of output.postInstall) targetCtx.addPostInstall(step); + } + await app.runTarget(targetCtx); + if (write && outDir) { const targetDir = join(resolve(outDir), target); await rm(targetDir, { recursive: true, force: true }); await mkdir(targetDir, { recursive: true }); - for (const file of output.files) { + for (const file of targetCtx.files) { const filePath = join(targetDir, file.path); await mkdir(resolve(filePath, '..'), { recursive: true }); await writeFile(filePath, file.content, 'utf-8'); } - if (output.nativeCopies && pluginRoot) { + if (targetCtx.nativeCopies.length > 0 && pluginRoot) { const resolvedRoot = resolve(pluginRoot); - for (const copy of output.nativeCopies) { + for (const copy of targetCtx.nativeCopies) { const srcPath = sanitizeJoin(resolvedRoot, copy.from); const dstPath = sanitizeJoin(targetDir, copy.to); await mkdir(resolve(dstPath, '..'), { recursive: true }); @@ -155,23 +184,24 @@ export async function compile(options: CompileOptions): Promise } if (!silent) { - console.log(chalk.green(` ✓ Built ${output.files.length} file${output.files.length > 1 ? 's' : ''}`)); - if (output.warnings.length > 0) { - for (const w of output.warnings) console.log(chalk.yellow(` ⚠ ${w}`)); + console.log(chalk.green(` ✓ Built ${targetCtx.files.length} file${targetCtx.files.length > 1 ? 's' : ''}`)); + if (targetCtx.warnings.length > 0) { + for (const w of targetCtx.warnings) console.log(chalk.yellow(` ⚠ ${w}`)); } - if (output.postInstall) { - console.log(chalk.cyan(` ⓘ ${output.postInstall.join('\n ⓘ ')}`)); + if (targetCtx.postInstall.length > 0) { + console.log(chalk.cyan(` ⓘ ${targetCtx.postInstall.join('\n ⓘ ')}`)); } } results.push({ target, - files: output.files, - warnings: output.warnings || [], - postInstall: output.postInstall, + files: targetCtx.files, + warnings: targetCtx.warnings, + postInstall: targetCtx.postInstall.length > 0 ? targetCtx.postInstall : undefined, skipped: false, }); } catch (err) { + if (err instanceof AbortError) throw err; const msg = err instanceof Error ? err.message : String(err); if (!silent) console.log(chalk.red(` ✗ Build failed for ${target}: ${msg}`)); results.push({ target, files: [], warnings: [], skipped: true, error: msg }); @@ -205,26 +235,30 @@ export async function build(options: BuildOptions): Promise { console.log(chalk.gray(`Targets: ${targetList.join(', ')}`)); console.log(chalk.gray(`Output: ${resolve(outDir)}\n`)); - // Universal validation + // Build the pipeline app once — reused for validation, lint, and compile + const app = await buildApp(config.plugins ?? []); + const knownTargets = [...ALL_TARGETS, ...app.adapters.keys()]; + + // Universal validation — custom adapter targets are not spuriously warned console.log(chalk.blue('🔍 Running universal validation...')); - const universalIssues = validateUniversal(manifest); + const universalIssues = validateUniversal(manifest, { knownTargets }); printIssues(universalIssues); const hasErrors = universalIssues.some(i => i.severity === 'error'); if (hasErrors) { throw new Error('Universal validation failed. Fix errors before building.'); } - // Lint + // Lint — includes any lint rules from defineConfig plugins console.log(chalk.blue('🔍 Running lint...')); const inlineSources = await collectInlineSources(manifest, config.root); - const lintIssues = lint({ manifest, inlineHandlerSource: inlineSources }); + const lintIssues = lint({ manifest, inlineHandlerSource: inlineSources, extraRules: [...app.lintRules] }); printLintIssues(lintIssues); const lintErrors = lintIssues.filter(i => i.severity === 'error'); if (options.strict && lintErrors.length > 0) { throw new Error(`Strict mode: ${lintErrors.length} lint error(s) found.`); } - // Compile + write + // Compile + write (pass pre-built app to avoid rebuilding) const results = await compile({ manifest, targets: targetList, @@ -232,6 +266,7 @@ export async function build(options: BuildOptions): Promise { outDir, pluginRoot: config.root, plugins: config.plugins, + _app: app, }); // Strict mode: fail on warnings diff --git a/packages/compile/src/middleware.ts b/packages/compile/src/middleware.ts index d75755e..e27e07e 100644 --- a/packages/compile/src/middleware.ts +++ b/packages/compile/src/middleware.ts @@ -1,8 +1,7 @@ -import { mkdir, rm, readFile, writeFile } from 'node:fs/promises'; -import { resolve, join } from 'node:path'; -import type { PlatformAdapter } from '@agentplugins/contract'; -import type { Middleware, BuildCtx, TargetCtx } from '@agentplugins/pipeline'; -import { validateUniversal, validateForPlatform } from './validation.js'; +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import type { Middleware, BuildCtx } from '@agentplugins/pipeline'; +import { validateUniversal } from './validation.js'; import { lint } from './lint.js'; import type { LintRule } from './lint.js'; import { sanitizeJoin } from './sanitize.js'; @@ -57,130 +56,6 @@ export function lintMiddleware(options: LintOptions = {}): Middleware }; } -// ─── compileMiddleware ──────────────────────────────────────────────────────── - -export interface CompileMiddlewareOptions { - adapters: ReadonlyMap; - /** If true, skip targets with platform validation errors. Defaults to true. */ - skipOnPlatformError?: boolean; -} - -export function compileMiddleware(options: CompileMiddlewareOptions): Middleware { - const { adapters, skipOnPlatformError = true } = options; - - return async (ctx, next) => { - for (const target of ctx.targets) { - const adapter = adapters.get(target); - if (!adapter) { - ctx.addWarning(`No adapter registered for target "${target}" — skipping.`); - continue; - } - - const platformIssues = validateForPlatform(ctx.manifest, target as never); - const platformErrors = platformIssues.filter(i => i.severity === 'error'); - - if (skipOnPlatformError && platformErrors.length > 0) { - ctx.addWarning( - `Build failed for ${target}: ${platformErrors.length} validation error(s) — ${platformErrors.map(e => e.message).join('; ')}` - ); - continue; - } - - try { - const output = adapter.compile(ctx.manifest, { pluginRoot: ctx.pluginRoot }); - - for (const file of output.files) { - ctx.addFile(target, file); - } - for (const w of output.warnings) { - ctx.addWarning(`[${target}] ${w}`); - } - if (output.nativeCopies) { - for (const copy of output.nativeCopies) { - ctx.addNativeCopy(target, copy); - } - } - if (output.postInstall) { - for (const step of output.postInstall) { - ctx.addPostInstall(target, step); - } - } - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - ctx.addWarning(`Build failed for ${target}: ${msg}`); - } - } - - await next(); - }; -} - -// ─── writeMiddleware ────────────────────────────────────────────────────────── - -export function writeMiddleware(): Middleware { - return async (ctx, next) => { - await next(); - - if (!ctx.outDir) return; - - const resolvedOut = resolve(ctx.outDir); - - for (const [target, files] of ctx.files) { - const targetDir = join(resolvedOut, target); - await rm(targetDir, { recursive: true, force: true }); - await mkdir(targetDir, { recursive: true }); - - for (const file of files) { - const filePath = join(targetDir, file.path); - await mkdir(resolve(filePath, '..'), { recursive: true }); - await writeFile(filePath, file.content, 'utf-8'); - } - - const nativeCopies = ctx.nativeCopies.get(target) ?? []; - if (nativeCopies.length > 0 && ctx.pluginRoot) { - const resolvedRoot = resolve(ctx.pluginRoot); - for (const copy of nativeCopies) { - const srcPath = sanitizeJoin(resolvedRoot, copy.from); - const dstPath = sanitizeJoin(targetDir, copy.to); - await mkdir(resolve(dstPath, '..'), { recursive: true }); - const content = await readFile(srcPath, 'utf-8'); - await writeFile(dstPath, content, 'utf-8'); - } - } - } - }; -} - -// ─── targetWriteMiddleware ──────────────────────────────────────────────────── - -export function targetWriteMiddleware(outDir: string): Middleware { - return async (ctx, next) => { - await next(); - - const resolvedOut = resolve(outDir); - const targetDir = join(resolvedOut, ctx.target); - await rm(targetDir, { recursive: true, force: true }); - await mkdir(targetDir, { recursive: true }); - - for (const file of ctx.files) { - const filePath = join(targetDir, file.path); - await mkdir(resolve(filePath, '..'), { recursive: true }); - await writeFile(filePath, file.content, 'utf-8'); - } - - if (ctx.nativeCopies.length > 0 && ctx.pluginRoot) { - const resolvedRoot = resolve(ctx.pluginRoot); - for (const copy of ctx.nativeCopies) { - const srcPath = sanitizeJoin(resolvedRoot, copy.from); - const dstPath = sanitizeJoin(targetDir, copy.to); - await mkdir(resolve(dstPath, '..'), { recursive: true }); - const content = await readFile(srcPath, 'utf-8'); - await writeFile(dstPath, content, 'utf-8'); - } - } - }; -} - // ─── Helpers ───────────────────────────────────────────────────────────────── async function collectInlineSources(ctx: BuildCtx): Promise { diff --git a/packages/pipeline/src/types.ts b/packages/pipeline/src/types.ts index 999babb..cb9dbf0 100644 --- a/packages/pipeline/src/types.ts +++ b/packages/pipeline/src/types.ts @@ -112,5 +112,6 @@ export interface App { /** All registered emitters from plugins. */ readonly emitters: ReadonlyMap; runBuild(ctx: BuildCtx): Promise; + runTarget(ctx: TargetCtx): Promise; runInstall(ctx: InstallCtx): Promise; } From 8be803ec3b5f1c2da03748d1a9b533d229e48919 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 20:50:40 +0200 Subject: [PATCH 27/90] feat(cli/add): replace inline B17/B18 with securityPlugin pipeline; export securityPlugin from core --- packages/cli/src/commands/add.ts | 56 +++++++++++++++++--------------- packages/core/src/index.ts | 4 +++ 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/packages/cli/src/commands/add.ts b/packages/cli/src/commands/add.ts index 6ca9968..19cfed0 100644 --- a/packages/cli/src/commands/add.ts +++ b/packages/cli/src/commands/add.ts @@ -10,17 +10,20 @@ import { initStore, normalizeSource, extractRepoName, + parseSubdir, + parseBranch, cloneRepo, findManifestInDir, installPlugin, getDetectedAgents, getStorePath, + securityPlugin, } from '@agentplugins/core'; import { join } from 'node:path'; import { existsSync, rmSync } from 'node:fs'; import { compile } from './build.js'; import { runSetupFlow } from './setup.js'; -import { verifyIntegrity, evaluateManifestScripts } from '@agentplugins/security'; +import { createApp, createInstallCtx, AbortError } from '@agentplugins/pipeline'; import type { PluginManifest } from '@agentplugins/core'; export interface AddOptions { @@ -30,6 +33,8 @@ export interface AddOptions { } export async function add(options: AddOptions): Promise { + const subdir = parseSubdir(options.source); + const branch = parseBranch(options.source); const source = normalizeSource(options.source); const repoName = extractRepoName(source); @@ -46,7 +51,7 @@ export async function add(options: AddOptions): Promise { console.log(chalk.blue('Cloning repository...')); let commit: string; try { - commit = cloneRepo(source, tempDir); + commit = cloneRepo(source, tempDir, branch); } catch (err) { const msg = err instanceof Error ? err.message : String(err); console.error(chalk.red(`\nFailed to clone: ${msg}`)); @@ -55,12 +60,15 @@ export async function add(options: AddOptions): Promise { } console.log(chalk.gray(`Commit: ${commit}`)); + // Resolve subdir — for monorepo tree URLs, look inside the subdirectory + const pluginDir = subdir ? join(tempDir, subdir) : tempDir; + // Find manifest — try JSON/SKILL.md first - let manifestResult = findManifestInDir(tempDir); + let manifestResult = findManifestInDir(pluginDir); // Fallback: try TypeScript config via jiti if (!manifestResult) { - manifestResult = await tryTsConfig(tempDir); + manifestResult = await tryTsConfig(pluginDir); } if (!manifestResult) { @@ -78,28 +86,23 @@ export async function add(options: AddOptions): Promise { console.log(chalk.cyan(`\nPlugin: ${name} v${version}`)); console.log(chalk.gray(`Manifest: ${manifestResult.path} (${manifestResult.type})`)); - // B17: verify pinned integrity (opt-in — only if manifest has integrity field) - const integrity = manifestResult.manifest['integrity'] as string | undefined; - if (integrity && integrity.length > 0) { - const { match, reason } = verifyIntegrity(tempDir, integrity); - if (!match) { - console.error(chalk.red(`\nIntegrity check failed: ${reason}`)); + // Security: run pinned integrity check + script policy via pipeline + const installApp = createApp().use(securityPlugin); + const installCtx = createInstallCtx({ + pluginName: name, + installDir: tempDir, + manifest: manifestResult.manifest as unknown as PluginManifest, + meta: {}, + }); + try { + await installApp.runInstall(installCtx); + } catch (err) { + if (err instanceof AbortError) { + console.error(chalk.red(`\n${err.message}`)); rmSync(tempDir, { recursive: true, force: true }); process.exit(1); } - } - - // B18: evaluate lifecycle script policy - const scriptCheck = evaluateManifestScripts(manifestResult.manifest, name); - if (!scriptCheck.ok) { - for (const issue of scriptCheck.issues) { - const tag = issue.decision === 'deny' ? chalk.red('[error]') : chalk.yellow('[review]'); - console.error(` ${tag} ${issue.dependency} (${issue.phase}): ${issue.command}`); - for (const r of issue.reasons) console.error(chalk.gray(` ${r}`)); - } - console.error(chalk.red('\nRefusing to install: lifecycle script policy violation')); - rmSync(tempDir, { recursive: true, force: true }); - process.exit(1); + throw err; } // Detect agents @@ -116,14 +119,14 @@ export async function add(options: AddOptions): Promise { if (compilableAgents.length > 0) { const targets = compilableAgents.map((a) => a.name); console.log(chalk.blue(`\nCompiling for ${targets.join(', ')}...`)); - const distDir = join(tempDir, '.agentplugins-dist'); + const distDir = join(pluginDir, '.agentplugins-dist'); try { await compile({ manifest: manifestResult.manifest as unknown as PluginManifest, targets: targets as any, write: true, outDir: distDir, - pluginRoot: tempDir, + pluginRoot: pluginDir, silent: false, }); } catch (err) { @@ -140,6 +143,7 @@ export async function add(options: AddOptions): Promise { commit, manifestPath: manifestResult.path, version, + ...(subdir ? { subdir } : {}), }); // Summary @@ -155,7 +159,7 @@ export async function add(options: AddOptions): Promise { await runSetupFlow({ name, - pluginDir: tempDir, + pluginDir, manifest: manifestResult.manifest, yes: options.yes, noSetup: options.noSetup, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 46e7cef..d218129 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -71,6 +71,8 @@ export { getDetectedAgents, normalizeSource, extractRepoName, + parseSubdir, + parseBranch, initStore, cloneRepo, pullRepo, @@ -103,6 +105,8 @@ export { runSetupCommand, readSetupRecord, writeSetupRecord, + securityPlugin, + SECURITY_META_KEYS, } from '@agentplugins/store'; export type { AgentPathEntry, From 9ea7e762fa2891f6d95cdb4a609d19e82986fadb Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 20:50:45 +0200 Subject: [PATCH 28/90] =?UTF-8?q?feat(store):=20parse=20GitHub=20tree=20UR?= =?UTF-8?q?Ls=20=E2=80=94=20parseSubdir,=20parseBranch,=20normalizeSource?= =?UTF-8?q?=20strips=20tree/blob=20path;=20thread=20branch/subdir=20throug?= =?UTF-8?q?h=20cloneRepo,=20installPlugin,=20updatePlugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/store/src/store.ts | 39 ++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/store/src/store.ts b/packages/store/src/store.ts index 9b7077c..1acc62d 100644 --- a/packages/store/src/store.ts +++ b/packages/store/src/store.ts @@ -73,6 +73,8 @@ export interface PluginMeta { version: string; /** Trust-on-first-use record for the plugin's `setup` command (v0.5.0+). */ setup?: SetupRecord; + /** Relative subdirectory within the store dir where the manifest lives (monorepo installs). */ + subdir?: string; } export interface DetectedAgent { @@ -279,7 +281,8 @@ export function getDetectedAgents(): DetectedAgent[] { /** * Normalize a plugin source URL. - * Accepts: https://github.com/u/r, git@github.com:u/r.git, u/r + * Accepts: https://github.com/u/r, git@github.com:u/r.git, u/r, + * https://github.com/u/r/tree/main/sub/dir */ export function normalizeSource(source: string): string { let url = source.trim(); @@ -295,6 +298,9 @@ export function normalizeSource(source: string): string { url = `https://github.com/${path}`; } + // Strip /tree/{branch}[/{path}] or /blob/{branch}[/{path}] + url = url.replace(/\/(tree|blob)\/[^?#]*/, ''); + // Strip trailing .git if (url.endsWith('.git')) { url = url.slice(0, -4); @@ -313,6 +319,24 @@ export function extractRepoName(source: string): string { return parts[parts.length - 1] || 'unknown-plugin'; } +/** + * Extract the subdirectory path from a GitHub tree URL. + * e.g. "https://github.com/u/r/tree/main/plugins/roster" → "plugins/roster" + */ +export function parseSubdir(source: string): string | undefined { + const m = source.match(/github\.com\/[^/]+\/[^/]+\/tree\/[^/]+\/(.+)/); + return m?.[1]; +} + +/** + * Extract the branch name from a GitHub tree URL. + * e.g. "https://github.com/u/r/tree/main/plugins/roster" → "main" + */ +export function parseBranch(source: string): string | undefined { + const m = source.match(/github\.com\/[^/]+\/[^/]+\/tree\/([^/]+)/); + return m?.[1]; +} + // ─── Store Operations ──────────────────────────────────────────────────────── /** Ensure the store and skills-compat directories exist */ @@ -336,10 +360,11 @@ export function validateCloneUrl(url: string): void { * Returns the commit hash. * Throws on failure. */ -export function cloneRepo(source: string, dest: string): string { +export function cloneRepo(source: string, dest: string, branch?: string): string { const url = normalizeSource(source); validateCloneUrl(url); - execSync(`git clone --depth 1 "${url}" "${dest}"`, { stdio: 'pipe' }); + const branchFlag = branch ? `--branch "${branch}"` : ''; + execSync(`git clone --depth 1 ${branchFlag} "${url}" "${dest}"`, { stdio: 'pipe' }); return execSync('git rev-parse HEAD', { cwd: dest, encoding: 'utf-8' }).trim(); } @@ -463,6 +488,8 @@ export interface InstallOptions { version: string; /** Whether to symlink into detected agents (default: true) */ symlink?: boolean; + /** Subdirectory within the cloned repo where the plugin lives (monorepo installs). */ + subdir?: string; } export interface InstallResult { @@ -501,6 +528,7 @@ export function installPlugin( updatedAt: now, manifestPath: opts.manifestPath, version: opts.version, + ...(opts.subdir ? { subdir: opts.subdir } : {}), }; writeFileSync(getMetaPath(opts.name), JSON.stringify(meta, null, 2), 'utf-8'); @@ -710,8 +738,9 @@ export function updatePlugin(name: string): PluginMeta { // Pull latest const newCommit = pullRepo(storePath); - // Re-find manifest (in case it changed) - const manifestResult = findManifestInDir(storePath); + // Re-find manifest (in case it changed); respect subdir for monorepo installs + const manifestSearchDir = oldMeta.subdir ? join(storePath, oldMeta.subdir) : storePath; + const manifestResult = findManifestInDir(manifestSearchDir); const version = manifestResult?.manifest['version'] as string || oldMeta.version; const manifestPath = manifestResult?.path || oldMeta.manifestPath; From 6abe7bb88125a0b01ed52bea602ee0ac00160006 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 20:50:48 +0200 Subject: [PATCH 29/90] test(store): normalizeSource / parseSubdir / parseBranch unit tests --- .../store/__tests__/normalizeSource.test.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 packages/store/__tests__/normalizeSource.test.ts diff --git a/packages/store/__tests__/normalizeSource.test.ts b/packages/store/__tests__/normalizeSource.test.ts new file mode 100644 index 0000000..06146d7 --- /dev/null +++ b/packages/store/__tests__/normalizeSource.test.ts @@ -0,0 +1,75 @@ +import { describe, it, expect } from 'vitest'; +import { normalizeSource, parseSubdir, parseBranch } from '../src/store.js'; + +describe('normalizeSource', () => { + it('expands shorthand user/repo', () => { + expect(normalizeSource('sigilco/agentplugins-roster')).toBe('https://github.com/sigilco/agentplugins-roster'); + }); + + it('strips /tree/branch/path', () => { + expect(normalizeSource('https://github.com/sigilco/agentplugins-roster/tree/main/plugins/roster')) + .toBe('https://github.com/sigilco/agentplugins-roster'); + }); + + it('strips /blob/branch/path', () => { + expect(normalizeSource('https://github.com/u/r/blob/main/some/file.ts')) + .toBe('https://github.com/u/r'); + }); + + it('converts SSH to HTTPS', () => { + expect(normalizeSource('git@github.com:u/r.git')).toBe('https://github.com/u/r'); + }); + + it('handles deep branch/subdir', () => { + expect(normalizeSource('https://github.com/u/r/tree/feat/a/sub/dir')) + .toBe('https://github.com/u/r'); + }); + + it('strips trailing .git', () => { + expect(normalizeSource('https://github.com/u/r.git')).toBe('https://github.com/u/r'); + }); + + it('strips trailing slash', () => { + expect(normalizeSource('https://github.com/u/r/')).toBe('https://github.com/u/r'); + }); +}); + +describe('parseSubdir', () => { + it('returns undefined for plain repo URL', () => { + expect(parseSubdir('https://github.com/sigilco/agentplugins-roster')).toBeUndefined(); + }); + + it('returns undefined for shorthand', () => { + expect(parseSubdir('sigilco/agentplugins-roster')).toBeUndefined(); + }); + + it('returns undefined for SSH URL', () => { + expect(parseSubdir('git@github.com:u/r.git')).toBeUndefined(); + }); + + it('extracts single-segment subdir', () => { + expect(parseSubdir('https://github.com/sigilco/agentplugins-roster/tree/main/plugins/roster')) + .toBe('plugins/roster'); + }); + + it('extracts subdir after first-segment branch (slash-in-branch is ambiguous; first segment wins)', () => { + expect(parseSubdir('https://github.com/u/r/tree/feat/a/sub/dir')) + .toBe('a/sub/dir'); + }); +}); + +describe('parseBranch', () => { + it('returns undefined for plain repo URL', () => { + expect(parseBranch('https://github.com/sigilco/agentplugins-roster')).toBeUndefined(); + }); + + it('extracts main branch', () => { + expect(parseBranch('https://github.com/sigilco/agentplugins-roster/tree/main/plugins/roster')) + .toBe('main'); + }); + + it('extracts simple branch (no slash in name)', () => { + expect(parseBranch('https://github.com/u/r/tree/develop')) + .toBe('develop'); + }); +}); From 59b7d162898d9583d3a9bf29b08caa643ebfd420 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 20:54:14 +0200 Subject: [PATCH 30/90] docs: document GitHub tree URL (monorepo subdir) in add command; fix pipeline execution order --- docs/guide/extending.md | 14 +++++++------- docs/reference/commands.md | 6 ++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/guide/extending.md b/docs/guide/extending.md index 8fe52c0..3037470 100644 --- a/docs/guide/extending.md +++ b/docs/guide/extending.md @@ -199,7 +199,7 @@ Runs per-target after the adapter has produced its files. `ctx.files` is the mut ### `onInstall` — gate or audit install -Runs during `agentplugins add` / `agentplugins install`. The built-in `securityPlugin` is registered here by default (hash integrity, script policy). You may add your own checks after it: +Runs during `agentplugins add`. The built-in security checks run here — pinned `integrity` hash verification, then script policy evaluation. You may add your own checks after them: ```typescript { @@ -245,16 +245,16 @@ Each field is optional — a plugin may contribute any combination. ## Middleware execution order -1. All `preValidate` chains run (user plugins after builtins) -2. `validateUniversal()` + `validateForPlatform()` -3. All `transformIR` chains run -4. `lint()` with merged lint rules -5. Per-target: `adapter.compile()` → all `postEmit` chains +1. `validateUniversal()` — structural checks on the manifest +2. `lint()` — with merged lint rules from all plugins +3. All `preValidate` chains run — can abort before compilation +4. All `transformIR` chains run — may mutate `ctx.manifest` +5. Per-target: `validateForPlatform()` → `adapter.compile()` → all `postEmit` chains 6. Files written to `dist/` Install pipeline: -1. All `onInstall` chains run (security middleware first) +1. All `onInstall` chains run (pinned-integrity and script-policy checks first) 2. Files linked into agent directories ## See also diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 4402c87..c20b6a7 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -46,16 +46,22 @@ agentplugins add |---|---| | `owner/repo` | `agentplugins add user/my-plugin` | | Full GitHub URL | `agentplugins add https://github.com/user/my-plugin` | +| GitHub tree URL (monorepo subdir) | `agentplugins add https://github.com/org/repo/tree/main/plugins/my-plugin` | | `owner/repo@version` | `agentplugins add user/my-plugin@1.2.0` | | Local path | `agentplugins add ./my-plugin` | | `gist:` | `agentplugins add gist:abcdef123456` | +When a GitHub tree URL points to a subdirectory, only that subdirectory is used as the plugin root — the rest of the repository is cloned but ignored. This lets plugin collections live in a monorepo and be installed individually. + ### Examples ```bash # Latest release from GitHub agentplugins add user/my-plugin +# Plugin inside a monorepo +agentplugins add https://github.com/sigilco/agentplugins-roster/tree/main/plugins/roster + # Specific version agentplugins add user/my-plugin@1.2.0 From 386d380a85f893e3cb702f249ddccba533bdf036 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 21:25:33 +0200 Subject: [PATCH 31/90] refactor(compile): delete vestigial middleware-factory experiment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove validateMiddleware/lintMiddleware/ValidateOptions/LintOptions and the duplicate collectInlineSources from packages/compile/src/middleware.ts — all dead code with zero importers. Drop the re-export from index.ts. Fix docs/guide/extending.md: LintRule example used check(ctx) but the real interface exposes run(ctx). --- docs/guide/extending.md | 2 +- packages/compile/src/index.ts | 1 - packages/compile/src/middleware.ts | 81 ------------------------------ 3 files changed, 1 insertion(+), 83 deletions(-) delete mode 100644 packages/compile/src/middleware.ts diff --git a/docs/guide/extending.md b/docs/guide/extending.md index 3037470..9a750f6 100644 --- a/docs/guide/extending.md +++ b/docs/guide/extending.md @@ -118,7 +118,7 @@ import type { LintRule } from '@agentplugins/pipeline' const requireLicenseRule: LintRule = { id: 'require-license', description: 'All plugins in this org must declare a license', - check(ctx) { + run(ctx) { if (!ctx.manifest.license) { return [{ severity: 'error', diff --git a/packages/compile/src/index.ts b/packages/compile/src/index.ts index ae58dd7..ed30e78 100644 --- a/packages/compile/src/index.ts +++ b/packages/compile/src/index.ts @@ -4,4 +4,3 @@ export * from './codegen.js'; export * from './hook-wrapper.js'; export * from './validation.js'; export * from './lint.js'; -export * from './middleware.js'; diff --git a/packages/compile/src/middleware.ts b/packages/compile/src/middleware.ts deleted file mode 100644 index e27e07e..0000000 --- a/packages/compile/src/middleware.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { readFile } from 'node:fs/promises'; -import { resolve } from 'node:path'; -import type { Middleware, BuildCtx } from '@agentplugins/pipeline'; -import { validateUniversal } from './validation.js'; -import { lint } from './lint.js'; -import type { LintRule } from './lint.js'; -import { sanitizeJoin } from './sanitize.js'; - -// ─── validateMiddleware ─────────────────────────────────────────────────────── - -export interface ValidateOptions { - /** If true, abort on validation errors. Defaults to true. */ - abortOnError?: boolean; -} - -export function validateMiddleware(options: ValidateOptions = {}): Middleware { - const { abortOnError = true } = options; - - return async (ctx, next) => { - const issues = validateUniversal(ctx.manifest); - for (const issue of issues) ctx.addIssue(issue); - - if (abortOnError && issues.some(i => i.severity === 'error')) { - ctx.abort('Universal validation failed. Fix errors before building.'); - } - - await next(); - }; -} - -// ─── lintMiddleware ─────────────────────────────────────────────────────────── - -export interface LintOptions { - /** Extra lint rules in addition to the global registry. */ - extraRules?: LintRule[]; - /** If true and in strict mode, abort on lint errors. */ - strict?: boolean; -} - -export function lintMiddleware(options: LintOptions = {}): Middleware { - const { extraRules = [], strict = false } = options; - - return async (ctx, next) => { - const inlineSources = await collectInlineSources(ctx); - const issues = lint({ manifest: ctx.manifest, inlineHandlerSource: inlineSources, extraRules }); - - for (const issue of issues) { - ctx.addWarning(`[lint:${issue.rule}] ${issue.message}`); - } - - if (strict && issues.some(i => i.severity === 'error')) { - ctx.abort(`Strict mode: ${issues.filter(i => i.severity === 'error').length} lint error(s) found.`); - } - - await next(); - }; -} - -// ─── Helpers ───────────────────────────────────────────────────────────────── - -async function collectInlineSources(ctx: BuildCtx): Promise { - const sources: string[] = []; - if (!ctx.manifest.hooks) return sources; - for (const def of Object.values(ctx.manifest.hooks)) { - if (!def) continue; - const handler = def.handler as { type: string; code?: string; source?: string }; - if (handler.type === 'inline') { - if (handler.code) { - sources.push(handler.code); - } else if (handler.source && ctx.pluginRoot) { - try { - const content = await readFile(sanitizeJoin(resolve(ctx.pluginRoot), handler.source), 'utf-8'); - sources.push(content); - } catch { - // skip unreadable sources - } - } - } - } - return sources; -} From 0c81a52590545e57d425e2ae5fdbd460b514e1cd Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 22:23:18 +0200 Subject: [PATCH 32/90] =?UTF-8?q?fix(adapter-pimono):=20emit=20WARNING=20w?= =?UTF-8?q?hen=20mcpServers=20set=20=E2=80=94=20Pi=20has=20no=20built-in?= =?UTF-8?q?=20MCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pi Mono deliberately excludes MCP support. The adapter already emitted nothing for mcpServers; now validatePlugin() surfaces a WARNING pointing authors to the nativeEntry.pimono bridge pattern documented in porting.md. Closes #53 --- .../adapter-pimono/__tests__/validate.test.ts | 88 +++++++++++++++++++ packages/adapter-pimono/src/index.ts | 13 +++ 2 files changed, 101 insertions(+) create mode 100644 packages/adapter-pimono/__tests__/validate.test.ts diff --git a/packages/adapter-pimono/__tests__/validate.test.ts b/packages/adapter-pimono/__tests__/validate.test.ts new file mode 100644 index 0000000..4ef30d4 --- /dev/null +++ b/packages/adapter-pimono/__tests__/validate.test.ts @@ -0,0 +1,88 @@ +import { describe, it, expect } from "vitest"; +import type { PluginManifest } from "@agentplugins/core"; +import { Severity } from "@agentplugins/core"; +import { createPiMonoAdapter } from "../src/index"; + +const adapter = createPiMonoAdapter({ pluginRoot: "/tmp/test-plugin" }); + +describe("validatePlugin() — Pi Mono adapter", () => { + describe("mcpServers warning", () => { + it("emits WARNING when mcpServers is set", () => { + const manifest: PluginManifest = { + name: "test-plugin", + version: "1.0.0", + description: "A test plugin", + mcpServers: { + "my-server": { command: "npx", args: ["my-mcp-server"] }, + }, + }; + + const issues = adapter.validate(manifest); + expect(issues.filter((i) => i.severity === Severity.ERROR)).toHaveLength(0); + expect(issues).toContainEqual( + expect.objectContaining({ + severity: Severity.WARNING, + field: "mcpServers", + message: expect.stringContaining("Pi Mono has no built-in MCP support"), + }) + ); + }); + + it("WARNING message points to the escape-hatch docs", () => { + const manifest: PluginManifest = { + name: "test-plugin", + version: "1.0.0", + description: "A test plugin", + mcpServers: { server: { command: "node", args: ["server.js"] } }, + }; + + const issues = adapter.validate(manifest); + const warn = issues.find((i) => i.field === "mcpServers"); + expect(warn?.message).toContain("nativeEntry.pimono"); + expect(warn?.message).toContain("porting#mcp-on-pi"); + }); + + it("does not emit WARNING when mcpServers is empty", () => { + const manifest: PluginManifest = { + name: "test-plugin", + version: "1.0.0", + description: "A test plugin", + mcpServers: {}, + }; + + const issues = adapter.validate(manifest); + expect(issues.filter((i) => i.field === "mcpServers")).toHaveLength(0); + }); + + it("does not emit WARNING when mcpServers is absent", () => { + const manifest: PluginManifest = { + name: "test-plugin", + version: "1.0.0", + description: "A test plugin", + }; + + const issues = adapter.validate(manifest); + expect(issues.filter((i) => i.field === "mcpServers")).toHaveLength(0); + }); + + it("mcpServers WARNING coexists with other valid manifest fields", () => { + const manifest: PluginManifest = { + name: "test-plugin", + version: "1.0.0", + description: "A test plugin", + hooks: { + sessionStart: { + handler: { type: "inline", handler: async () => ({}) }, + }, + }, + mcpServers: { server: { command: "node", args: ["server.js"] } }, + }; + + const issues = adapter.validate(manifest); + expect(issues.filter((i) => i.severity === Severity.ERROR)).toHaveLength(0); + const warns = issues.filter((i) => i.severity === Severity.WARNING); + expect(warns).toHaveLength(1); + expect(warns[0].field).toBe("mcpServers"); + }); + }); +}); diff --git a/packages/adapter-pimono/src/index.ts b/packages/adapter-pimono/src/index.ts index 280bea1..a2c952f 100644 --- a/packages/adapter-pimono/src/index.ts +++ b/packages/adapter-pimono/src/index.ts @@ -354,6 +354,19 @@ function validatePlugin(plugin: PluginManifest): ValidationIssue[] { } } + // ── mcpServers ── + if (plugin.mcpServers && Object.keys(plugin.mcpServers).length > 0) { + issues.push({ + severity: Severity.WARNING, + field: "mcpServers", + message: + `Pi Mono has no built-in MCP support — "mcpServers" will not be emitted for this target. ` + + `Use first-class "tools[]" (emitted natively via pi.registerTool()) or bridge an MCP server ` + + `through a Pi extension via "nativeEntry.pimono". ` + + `See docs/guide/porting#mcp-on-pi for the guided pattern.`, + }); + } + return issues; } From f37fee72ea333da1546be798aa87b7a837d8a9b5 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 22:23:24 +0200 Subject: [PATCH 33/90] fix(docs): correct Pi mcpServers/agents[] cells; add MCP-on-Pi pattern and composition non-goal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - capability-matrix: Pi mcpServers ✅→⚠️ (no built-in MCP); agents[] ✅→⚠️ (adapter emits nothing for agent definitions); subagentStart/Stop keep ✅, add mechanism footnote (maps to agent.AgentStart/AgentStop) - tools.md: Pi mcpServers ✅→⚠️; update cross-harness pattern note - porting.md: add MCP-on-Pi subsection with nativeEntry.pimono bridge pattern; add plugin composition non-goal paragraph Closes #53, #54 --- docs/guide/capability-matrix.md | 66 +++++++++++++++++++++++++++++++++ docs/guide/porting.md | 45 ++++++++++++++++------ docs/guide/tools.md | 18 +++++---- 3 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 docs/guide/capability-matrix.md diff --git a/docs/guide/capability-matrix.md b/docs/guide/capability-matrix.md new file mode 100644 index 0000000..eda1c61 --- /dev/null +++ b/docs/guide/capability-matrix.md @@ -0,0 +1,66 @@ +--- +title: Capability Matrix +description: What each supported harness supports — universal codegen, guided per-harness, or unsupported. Includes the native escape hatch for every gap so you can ship on the native path today. +--- + +# Capability Matrix + +**Supported harnesses:** Claude Code · Codex · OpenCode · Pi Mono + +**Additional (tracked, not blocking):** Copilot · Gemini · Kimi + +Legend: + +- ✅ **Universal codegen** — adapter emits native output automatically from the manifest. +- ⚠️ **Guided per-harness** — no native primitive of its own; a documented escape-hatch pattern covers the gap. Emits a WARN (not error) on portable manifests. +- ❌ **Unsupported** — no viable path on this harness; recorded here, not blocking. +- n/a — not applicable (mechanism differs but functionality is covered). + +> **Reading the "Escape hatch" column:** for every ⚠️ cell there's a deliberate reason we don't emit a universal primitive for that capability on that harness yet — the work to express it universally is significant, and a native path already does the job. If you're shipping today, use the escape hatch. If you want a universal primitive, see the **Decision tree for authors** at the bottom. + +## Capability table + +| Capability | Claude Code | Codex | OpenCode | Pi Mono | Escape hatch | Notes | +| ------------------------ | :----------: | :----------: | :------: | :-----: | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `skills` | ✅ | ✅ | ✅ | ✅ | — | Universal codegen | +| `hooks` (lifecycle) | ✅ | ✅ | ✅ | ✅ | — | Universal codegen | +| `commands` | ✅ | ✅ | ✅ | ✅ | — | Universal codegen | +| `mcpServers` | ✅ | ✅ | ✅ | ⚠️ | Pi has no built-in MCP. Ship tools via native `tools[]` (emitted natively), or bridge an MCP server through a Pi extension via `nativeEntry.pimono`. | Universal on Claude · Codex · OpenCode. Pi Mono has no MCP; emits WARN when `mcpServers` is set. See [MCP on Pi](/guide/porting#mcp-on-pi). | +| `agents[]` | ✅ | ✅ | ✅ | ⚠️ | Pi has no named-agent declaration primitive. Use `nativeEntry.pimono` to spawn custom agents via a Pi extension. | Pi adapter emits nothing for `agents[]`; use `nativeEntry.pimono` for custom agent wiring on Pi. | +| `agents[].model` | ✅ | ⚠️ | ✅ | ⚠️ | Codex/Pi: use the harness's own per-agent model config (or simply omit and accept the harness default). | Claude + OpenCode emit `model:` frontmatter when set. Codex/Pi have no per-agent file concept; model unset → harness default. | +| `subagentStart` | ✅ | ✅ | ⚠️ | ✅ | OpenCode: intercept `subagent` tool calls with `preToolUse` matcher (subagents launch via the `subagent` tool). | Emits WARN on OpenCode. Pi maps to `agent.AgentStart` lifecycle event. | +| `subagentStop` | ✅ | ✅ | ⚠️ | ✅ | OpenCode: detect via `postToolUse` / `postToolUseFailure` on the `subagent` tool. | Emits WARN on OpenCode. Pi maps to `agent.AgentStop` lifecycle event. Pi `stop`↔`subagentStop` collision fixed in v0.3.0. | +| `tools[]` (first-class) | ⚠️ | ⚠️ | ✅ | ✅ | Claude/Codex: ship tools via `mcpServers` — works on all four harnesses (universal). | WARN emitted; OpenCode/Pi emit first-class `tools[]` natively. | +| `stop` / `continueWith` | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Each harness already has a `stop`-class lifecycle hook natively — emit nothing in portable manifests until v0.4.0 ships. | New universal primitive — v0.4.0; all-harness design. | +| Native-entry passthrough | n/a (JSON) | n/a (JSON) | ⚠️ | ⚠️ | OpenCode: drop a `.ts` file directly into `~/.config/opencode/plugins//` — Bun runs it as ESM, no codegen needed. | `nativeEntry` escape hatch — ships in v0.4.0; OpenCode native modules must be `.ts` (file-drop path). | +| Inline hook handlers | ✅ auto-wrap | ✅ auto-wrap | ✅ | ✅ | — | Codex/Kimi: auto-wrapped as Node.js command scripts (v0.4.0). | + +## Additional harnesses + +| Capability | Copilot | Gemini | Kimi | Escape hatch | Notes | +| -------------------------------- | :-----: | :----: | :--: | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| `skills` | ✅ | ✅ | ✅ | — | Universal. | +| `hooks` (lifecycle) | ⚠️ | ⚠️ | ❌ | Copilot/Gemini: use HTTP or `command` handlers (see [Hooks](/guide/hooks)). | Kimi supports a subset (see below). | +| `subagentStart` / `subagentStop` | ❌ | ❌ | ❌ | TUI fidelity only — implement per-harness via native plugin config if you really need it. | No universal primitive planned. | +| `tools[]` | ✅ | ✅ | ❌ | — | First-class tool emission. | +| `mcpServers` | ❌ | ❌ | ❌ | Wire the MCP server directly into the harness's native config — no agentplugins path needed. | Not on the manifest path; harness-level wiring only. | + +Kimi supported hooks: `preToolUse`, `userPromptSubmit`, `sessionStart`, `notification`, `permissionRequest`. Inline handlers auto-wrapped as Node.js command scripts (v0.4.0). + +## Decision tree for authors + +``` +Does universal codegen cover this capability across all four core harnesses? + YES → use it; adapter handles the rest + NO → does the native escape hatch (Escape hatch column above) cover it today? + YES → ship the escape hatch now; document the limitation in the manifest + NO → can you express it per-harness via nativeEntry + a hand-written module? + YES → use nativeEntry; emit WARN (not error) + NO → is the gap TUI-grade fidelity only? + YES → acceptable degradation; note in this matrix + NO → open a primitive proposal (v0.4.0+ scope) +``` + +--- + +*This matrix is the living contract for the project. Update it as capabilities land or gaps are discovered.* diff --git a/docs/guide/porting.md b/docs/guide/porting.md index e79d8c1..43682d3 100644 --- a/docs/guide/porting.md +++ b/docs/guide/porting.md @@ -1,14 +1,14 @@ --- title: Porting an Existing Plugin -description: Port an existing per-harness plugin to the AgentPlugins universal manifest with functional parity across Tier-1 harnesses. +description: Port an existing per-harness plugin to the AgentPlugins universal manifest with functional parity across all supported harnesses. --- # Porting an Existing Plugin This guide walks through porting an existing per-harness plugin to AgentPlugins so it delivers the **same functionality** everywhere — universal codegen first, per-harness escape hatch only when a capability has no universal primitive. -::: info Tier-1 parity is the bar -The four Tier-1 harnesses are **Claude Code, Codex, OpenCode, and Pi Mono**. A capability must work across all four at the *functionality* level — not necessarily with identical TUI chrome, but the same underlying behaviour. See the [Tier-1 Capability Matrix](/reference/compat-matrix) for the per-capability verdict. +::: info Parity is the bar +The four supported harnesses are **Claude Code, Codex, OpenCode, and Pi Mono**. A capability must work across all four at the *functionality* level — not necessarily with identical TUI chrome, but the same underlying behaviour. See the [Capability Matrix](/guide/capability-matrix) for the per-capability verdict. ::: --- @@ -18,11 +18,11 @@ The four Tier-1 harnesses are **Claude Code, Codex, OpenCode, and Pi Mono**. A c For each capability in your existing plugin, work through this tree: ``` -1. Does universal codegen cover it across all Tier-1? +1. Does universal codegen cover it across all four core harnesses? YES → declare it in the manifest; the adapter handles the rest NO → continue ↓ -2. Can all Tier-1 harnesses support it via a per-harness escape hatch? +2. Can all four harnesses support it via a per-harness escape hatch? YES → implement guided per-harness (see below); emit WARN on build NO → continue ↓ @@ -44,7 +44,7 @@ List every capability your plugin provides: | Custom command | `/reset` clears state | `commands[]` | | Overlay UI | Side panel in Pi TUI | Pi-specific | -For each row, check the [Tier-1 Capability Matrix](/reference/compat-matrix) to see whether it's universal-codegen, guided-per-harness, or unsupported. +For each row, check the [Capability Matrix](/guide/capability-matrix) to see whether it's universal-codegen, guided-per-harness, or unsupported. --- @@ -73,14 +73,14 @@ export default definePlugin({ { name: 'reset', description: 'Reset plugin state', command: './scripts/reset.sh' }, ], - // Tools via MCP — works on all Tier-1 + // Tools via MCP — works on all four mcpServers: { 'my-tools': { command: 'npx', args: ['my-tools-mcp-server'] }, }, }) ``` -Build and verify on each Tier-1: +Build and verify on each harness: ```bash agentplugins build @@ -118,7 +118,7 @@ hooks: { }, ``` -The WARN emitted on `agentplugins validate` for OpenCode points here. Record the gap in the [compat matrix](/reference/compat-matrix). +The WARN emitted on `agentplugins validate` for OpenCode points here. Record the gap in the [Capability Matrix](/guide/capability-matrix). **Per-harness fallback via `nativeEntry`:** @@ -142,6 +142,26 @@ You do not need to edit `~/.config/opencode/config.json`. The `.ts` file-drop is If you ship a `.mjs` source, `agentplugins` automatically links it under a `.ts` name (safety net) but emits a WARN. Rename the source to `.ts` to silence it. +### MCP on Pi {#mcp-on-pi} + +Pi Mono has no built-in MCP support ("No MCP" per the Pi README). When your manifest sets `mcpServers` and `pimono` is a target, `agentplugins validate` emits a WARNING pointing here. + +Two paths: + +**Option A — native `tools[]` (recommended for Pi):** Declare your tool shapes in `tools[]`. The Pi adapter emits `pi.registerTool()` calls natively. No MCP server needed on Pi. + +**Option B — MCP bridge via `nativeEntry.pimono`:** Write a Pi extension that spawns your MCP server as a child process and bridges it through Pi's `pi.tool()` API. Wire it with: + +```typescript +nativeEntry: { + pimono: './src/pi-mcp-bridge.ts', +} +``` + +The file is copied verbatim to `index.ts` in the Pi dist; codegen is skipped. The bridge has full access to Pi's extension API (`ExtensionAPI`) and can call your MCP server over stdio. + +Use Option A when your tools are already declared in `tools[]`. Use Option B when you have a shared MCP server that must run on all four harnesses and you want a single implementation. + --- ## Step 4 — TUI-only features (acceptable degradation) @@ -149,7 +169,7 @@ If you ship a `.mjs` source, `agentplugins` automatically links it under a `.ts` Overlays, side panels, and interactive widgets that use Pi's TUI system (`@earendil-works/pi-tui`) are Pi-only by nature. This is the **one allowed degradation** category: - Implement the TUI feature on Pi via `nativeEntry`. -- On other Tier-1: omit the widget; the underlying functionality (data, hooks, state) must still work. +- On other harnesses: omit the widget; the underlying functionality (data, hooks, state) must still work. - Record in compat matrix as "TUI fidelity — Pi only". --- @@ -183,7 +203,7 @@ Flags the user controls: `--yes` (skip the prompt, still denylist-gated), `--no- ## Step 6 — Verify parity -For each Tier-1 harness: +For each supported harness: ```bash # Build @@ -207,12 +227,13 @@ Confirm the **same observable behaviour** on all four: same hooks fire, same com - **Universal orchestration runtime** — don't build one. Subagent spawning = per-harness primitives + userland provider protocol. - **Mechanical ports** — don't translate existing config files 1:1. Rewrite on the manifest; let adapters generate the platform-native output. +- **Plugin composition / inheritance** — native composition (extending one plugin from another) is not a Tier-1 primitive on any supported harness. If you need to combine behavior from two plugins, express it per-harness via `nativeEntry` where the harness supports extension chaining, or simply declare the same hooks in both plugins and let the harness merge them. There is no universal `extends` or `compose` field in the manifest, and none is planned for v0.4.0. --- ## See also - [Ecosystem](/guide/ecosystem) — plugins already ported for Tier-1 parity -- [Tier-1 Capability Matrix](/reference/compat-matrix) — full capability table +- [Capability Matrix](/guide/capability-matrix) — full capability table - [Creating Plugins](/guide/creating-plugins) — authoring guide from scratch - [JSON Schema](/reference/schema) — manifest schema for editor autocomplete diff --git a/docs/guide/tools.md b/docs/guide/tools.md index ab2719c..5779df4 100644 --- a/docs/guide/tools.md +++ b/docs/guide/tools.md @@ -136,30 +136,32 @@ The manifest declares the **shape** of a tool. The implementation — what runs ## Per-platform behavior -`tools[]` is natively emitted by **OpenCode** and **Pi Mono** only. For all other platforms — including the Tier-1 harnesses Claude Code and Codex — **`mcpServers` is the recommended universal tool delivery mechanism**. +`tools[]` is natively emitted by **OpenCode** and **Pi Mono** only. For all other platforms — including Claude Code and Codex — **`mcpServers` is the recommended universal tool delivery mechanism**. | Platform | `tools[]` | `mcpServers` | Notes | |---|:---:|:---:|---| -| Claude Code | ⚠️ | ✅ | `tools[]` not emitted; use `mcpServers` — full Tier-1 tool parity | +| Claude Code | ⚠️ | ✅ | `tools[]` not emitted; use `mcpServers` | | Codex | ⚠️ | ✅ | Same as Claude Code | | OpenCode | ✅ | ✅ | First-class `tools[]` + `mcpServers` both supported | -| Pi Mono | ✅ | ✅ | First-class `tools[]` + `mcpServers` both supported | +| Pi Mono | ✅ | ⚠️ | Pi has no built-in MCP. Use first-class `tools[]` (emitted natively) or bridge via `nativeEntry.pimono`. See [MCP on Pi](/guide/porting#mcp-on-pi). | | Copilot | ⚠️ | ❌ | Neither emitted; Tier-2 only | | Gemini | ⚠️ | ❌ | Neither emitted; Tier-2 only | | Kimi | ⚠️ | ❌ | Neither emitted; Tier-2 only | > ⚠️ When `tools[]` is declared and the target platform does not natively emit it, `agentplugins validate` emits a **WARNING** (not an error) with a pointer to `mcpServers`. The build still succeeds. +> +> ⚠️ Pi Mono has no built-in MCP support. On Pi, the native `tools[]` emission is the recommended path. `agentplugins validate` emits a WARNING when `mcpServers` is set with `pimono` as a target. ### Recommended cross-harness pattern -For plugins targeting all Tier-1 harnesses, back tools with an MCP server: +For plugins targeting Claude, Codex, and OpenCode, back tools with an MCP server. For Pi Mono, declare `tools[]` directly (emitted natively) or use `nativeEntry.pimono` to bridge an MCP server through a Pi extension. ```typescript export default definePlugin({ name: 'my-tools', version: '1.0.0', - // Tier-1 universal tool path: all four harnesses consume MCP servers + // MCP path: consumed by Claude Code, Codex, OpenCode (not Pi Mono) mcpServers: { 'my-tools-server': { command: 'npx', @@ -184,10 +186,10 @@ export default definePlugin({ }) ``` -See the [Tier-1 Capability Matrix](/reference/compat-matrix) for full cross-harness tool support details. +See the [Capability Matrix](/guide/capability-matrix) for full cross-harness tool support details. ## Next steps -- [MCP Servers](/guide/mcp-servers) — recommended universal tool mechanism for all Tier-1 harnesses. +- [MCP Servers](/guide/mcp-servers) — recommended universal tool mechanism for all supported harnesses. - [Manifest reference](/guide/manifest) for the full `tools` schema. -- [Tier-1 Capability Matrix](/reference/compat-matrix) for cross-harness support details. +- [Capability Matrix](/guide/capability-matrix) for cross-harness support details. From b214f20c6ff2271d6eb0f2b83bf71cd401c4865c Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 22:23:29 +0200 Subject: [PATCH 34/90] =?UTF-8?q?fix(docs):=20reconcile=20init=20output=20?= =?UTF-8?q?=E2=80=94=20no=20SKILL.md=20or=20hooks/=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit agentplugins init writes agentplugins.config.ts + package.json + tsconfig.json + .gitignore + README.md. Hooks and skills are inlined in config.ts; there is no separate SKILL.md or hooks/ directory. quick-start and creating-plugins both claimed the opposite. Closes #55 --- docs/guide/creating-plugins.md | 9 ++++++--- docs/guide/quick-start.md | 7 ++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/guide/creating-plugins.md b/docs/guide/creating-plugins.md index 1f9f359..7018583 100644 --- a/docs/guide/creating-plugins.md +++ b/docs/guide/creating-plugins.md @@ -26,7 +26,7 @@ You'll be prompted for: | Template | What you get | |---|---| -| `minimal` | Bare manifest + `SKILL.md`. Good starting point. | +| `minimal` | Bare manifest. Good starting point. | | `logger` | A plugin that logs every hook event to `${PLUGIN_DATA}/log.jsonl`. | | `security-guard` | A `preToolUse` block-list for dangerous commands. | | `formatter` | A `postToolUse` hook that runs your formatter of choice. | @@ -41,11 +41,14 @@ Pick `minimal` if you're not sure — you can add hooks later. ✓ Created my-plugin/ my-plugin/agentplugins.config.ts - my-plugin/SKILL.md - my-plugin/hooks/ + my-plugin/package.json + my-plugin/tsconfig.json + my-plugin/.gitignore my-plugin/README.md ``` +Hooks, skills, and commands are declared inline in `agentplugins.config.ts` — there is no separate `SKILL.md` or `hooks/` directory in the scaffold. Open `agentplugins.config.ts` to start editing. + ## 2. Write hooks Open `agentplugins.config.ts` and add hooks to the `hooks` object. See the [Hooks guide](/guide/hooks) for the 19 universal hooks and the three handler types. diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md index b3a0f4f..3ade64d 100644 --- a/docs/guide/quick-start.md +++ b/docs/guide/quick-start.md @@ -73,7 +73,7 @@ agentplugins init ? Plugin name (kebab-case) › my-awesome-plugin ? Description › Does awesome things across every agent ? Template › - Use arrow-keys. Return to submit. -> minimal Bare manifest + SKILL.md +> minimal Bare manifest logger Logs every hook event security-guard preToolUse block-list formatter postToolUse auto-format @@ -81,8 +81,9 @@ agentplugins init ✓ Created my-awesome-plugin/ my-awesome-plugin/agentplugins.config.ts - my-awesome-plugin/SKILL.md - my-awesome-plugin/hooks/pre-tool-use.sh + my-awesome-plugin/package.json + my-awesome-plugin/tsconfig.json + my-awesome-plugin/.gitignore my-awesome-plugin/README.md ``` From d2a4df13cb34e65cb3e82816d40e05913b022eac Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 22:23:36 +0200 Subject: [PATCH 35/90] =?UTF-8?q?fix(docs):=20agentplugins.dev=20=E2=86=92?= =?UTF-8?q?=20agentplugins.pages.dev;=20add=20sidecar=20escape-hatch=20not?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - prd.md: correct schema URL from agentplugins.dev to agentplugins.pages.dev - manifest.md: sidecar EXPERIMENTAL block now explains the alternative — stdio MCP server (Claude/Codex/OpenCode) or setup command; Pi Mono via nativeEntry.pimono extension Closes #56 --- .agents/docs/prd.md | 6 +++--- docs/guide/manifest.md | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.agents/docs/prd.md b/.agents/docs/prd.md index 4b1170e..c1dce88 100644 --- a/.agents/docs/prd.md +++ b/.agents/docs/prd.md @@ -45,7 +45,7 @@ Five operating principles: 1. **Tier-1 parity is the bar.** Same functionality across Claude Code, Codex, OpenCode, Pi Mono. TUI-grade fidelity (overlays, widgets) is the only allowed degradation. 2. **Codegen first, guided per-harness fallback second.** Where universal codegen can express a capability across all Tier-1, do that. Where a harness lacks the native primitive, check whether all Tier-1 can support it via a custom (escape-hatch) method, and if so guide the author — rather than dropping the feature. -3. **Keep a compat matrix.** The living [Tier-1 Capability Matrix](../../docs/reference/compat-matrix.md) (published at `/reference/compat-matrix`) records what's universal-codegen, guided-per-harness, and genuinely unsupported. It's the contract for "same functionality, different plumbing." +3. **Keep a compat matrix.** The living [Capability Matrix](../../docs/guide/capability-matrix.md) (published at `/guide/capability-matrix`) records what's universal-codegen, guided-per-harness, and genuinely unsupported. It's the contract for "same functionality, different plumbing." 4. **Lean, no global SDK.** Primitives express intent, not mechanism; each adapter owns its own plumbing. The escape hatch lets power users write native code against each harness's SDK. 5. **Multi-platform by default, platform-specific code allowed.** AgentPlugins gives plugin authors the foundations to distribute their plugin across all Tier-1 harnesses (Claude Code, Codex, OpenCode, Pi Mono). Authors are not forced to support every harness — they may target a single harness or ship harness-specific behavior (e.g., custom logging for one harness) when another harness lacks a needed primitive. The end goal is a distribution platform and utilities so authors can make a plugin usable in other harnesses without porting or rewriting the implementation. Think React Native or Rust: multi-platform by default, platform-specific code when you need it. @@ -69,8 +69,8 @@ _Draft — this section reflects the current product direction and will be final - **Distribution-first CLI** — `agentplugins add ` installs a plugin once; the universal store (`~/.agents/plugins//`) fans out via symlinks to every detected agent harness. - **Skills.sh compatibility** — plugins exposing `SKILL.md` are first-class citizens; our CLI reads both AgentPlugins and Skills.sh layouts. -- **JSON Schema** — `@agentplugins/schema` package + hosted JSON Schema (`agentplugins.dev/schema/v1.json`) for editor autocomplete and self-documenting manifests. -- **Tier-1 parity primitives** — every shipped capability works across Claude Code, Codex, OpenCode, and Pi Mono. The [Tier-1 Capability Matrix](../../docs/reference/compat-matrix.md) is the living contract. +- **JSON Schema** — `@agentplugins/schema` package + hosted JSON Schema (`agentplugins.pages.dev/schema/v1.json`) for editor autocomplete and self-documenting manifests. +- **Parity primitives** — every shipped capability works across Claude Code, Codex, OpenCode, and Pi Mono. The [Capability Matrix](../../docs/guide/capability-matrix.md) is the living contract. - **Security & audit guardrails** — `agentplugins audit` scores supply-chain risk (OSV, Scorecard, npm provenance); safe-fetch SSRF guard; lifecycle script policy. - **Public launch** — registry, docs site, examples, and a stable v1 manifest format. diff --git a/docs/guide/manifest.md b/docs/guide/manifest.md index 24455c5..c3a32f3 100644 --- a/docs/guide/manifest.md +++ b/docs/guide/manifest.md @@ -193,6 +193,8 @@ agents: [ ::: warning EXPERIMENTAL `sidecar` is experimental. It is accepted by the schema and validated, but **no adapter currently starts or stops sidecar processes**. Do not rely on it for production plugins. + +**For a long-running background process today:** ship a stdio MCP server (consumed natively by Claude Code, Codex, and OpenCode via `mcpServers`) or declare a `setup` command that starts it. On Pi Mono (which has no MCP), ship a Pi extension via `nativeEntry.pimono` that starts the process using Pi's extension API. ::: ```typescript From f61ff84933a99aa336fa32a876cf738e9f959d27 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 22:53:13 +0200 Subject: [PATCH 36/90] refactor(docs): move compat-matrix to guide/capability-matrix; update all refs Deletes docs/reference/compat-matrix.md and updates every inbound link (AGENTS.md, ecosystem, introduction, adapters, validate.ts error message) to point at the new guide/capability-matrix location. --- AGENTS.md | 2 +- docs/guide/ecosystem.md | 8 +-- docs/guide/introduction.md | 34 +++++++++---- docs/reference/adapters.md | 2 +- docs/reference/compat-matrix.md | 61 ----------------------- packages/adapter-opencode/src/validate.ts | 4 +- 6 files changed, 31 insertions(+), 80 deletions(-) delete mode 100644 docs/reference/compat-matrix.md diff --git a/AGENTS.md b/AGENTS.md index e70505b..7780f1a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ GitHub Project is [https://github.com/users/espetro/projects/14](https://github. 1. **Tier-1 parity is the bar.** Every shipped capability must work across all four Tier-1 harnesses at the functionality level (not TUI level). 2. **Codegen first, guided per-harness fallback second.** Universal codegen where possible; otherwise guide the author via the escape hatch rather than dropping the feature. -3. **Compat matrix is the contract.** `docs/reference/compat-matrix.md` — universal-codegen / guided-per-harness / unsupported, per Tier-1 harness. Keep it current. +3. **Compat matrix is the contract.** `docs/guide/capability-matrix.md` — universal-codegen / guided-per-harness / unsupported, per harness. Keep it current. 4. **Lean, no global SDK.** Primitives express intent; each adapter owns its plumbing. 5. **Community plugins are ground-up rewrites** in `agentplugins-` sibling repos (e.g. `../agentplugins-caveman`), not mechanical ports. diff --git a/docs/guide/ecosystem.md b/docs/guide/ecosystem.md index 5508b89..017c25c 100644 --- a/docs/guide/ecosystem.md +++ b/docs/guide/ecosystem.md @@ -5,7 +5,7 @@ description: Community plugins built on the AgentPlugins universal manifest, ins # Ecosystem -Community plugins rewritten on the AgentPlugins universal manifest. Each targets **Tier-1 functional parity** — the same functionality across Claude Code, Codex, OpenCode, and Pi Mono. +Community plugins rewritten on the AgentPlugins universal manifest. Each targets functional parity across the four supported harnesses — the same functionality across Claude Code, Codex, OpenCode, and Pi Mono. Install any plugin with a single command: @@ -20,7 +20,7 @@ This installs the plugin into `~/.agents/plugins//` and symlinks it to eve ## Adding your plugin 1. Build on the AgentPlugins universal manifest (see [Creating Plugins](/guide/creating-plugins)) -2. Target all four Tier-1 harnesses and confirm functional parity +2. Target all four supported harnesses and confirm functional parity 3. Open a PR or issue to add your plugin to this page See [Rewriting for Tier-1 Parity](/guide/porting) for the step-by-step process. @@ -32,9 +32,9 @@ See [Rewriting for Tier-1 Parity](/guide/porting) for the step-by-step process. | Plugin | Description | |---|---| | [agentplugins-autoresearch](https://github.com/sigilco/agentplugins-autoresearch) | Autonomous experiment loop that gathers what to optimize and runs iterations until the target improves. | -| [agentplugins-goal](https://github.com/sigilco/agentplugins-goal) | Autonomous goal-completion loop — set a goal once, the agent iterates until done across all Tier-1 harnesses. | +| [agentplugins-goal](https://github.com/sigilco/agentplugins-goal) | Autonomous goal-completion loop — set a goal once, the agent iterates until done across all supported harnesses. | | [agentplugins-ponytail](https://github.com/sigilco/agentplugins-ponytail) | Lazy senior dev mode — forces the minimum solution that works: YAGNI → stdlib → native → one line. | | [agentplugins-caveman](https://github.com/sigilco/agentplugins-caveman) | Ultra-compressed communication mode — cuts ~75% of output tokens while preserving full technical accuracy. | | [agentplugins-btw](https://github.com/sigilco/agentplugins-btw) | Parallel side-thread thoughts — spin up side conversations without interrupting the main agent. | -> **Note:** Tier-1 capability status per plugin lives in each plugin's README. For the harness-level baseline see the [compat matrix](/reference/compat-matrix). +> **Note:** Capability status per plugin lives in each plugin's README. For the harness-level baseline see the [Capability Matrix](/guide/capability-matrix). diff --git a/docs/guide/introduction.md b/docs/guide/introduction.md index e03917b..dcc9c49 100644 --- a/docs/guide/introduction.md +++ b/docs/guide/introduction.md @@ -35,29 +35,41 @@ flowchart LR core --> a2["Codex CLI"] core --> a3["GitHub Copilot"] core --> a4["Gemini CLI"] - core --> a5["+ 3 more"] + core --> a5["+ more"] ``` ## Supported platforms -Seven harnesses are supported as first-class compile targets: +AgentPlugins targets four harnesses as primary compile targets with universal codegen — the same plugin behaviour across all four: -| Agent | Binary | Skill path | -| ------------------ | ---------- | --------------------------- | -| Claude Code | `claude` | `~/.claude/skills` | -| Codex CLI | `codex` | `~/.codex/skills` | -| GitHub Copilot CLI | `copilot` | `~/.copilot/skills` | -| Gemini CLI | `gemini` | `~/.gemini/skills` | -| Kimi | `kimi` | `~/.kimi/skills` | -| OpenCode | `opencode` | `~/.config/opencode/skills` | -| Pi Mono | `pi` | `~/.pi/extensions` | +| Agent | Binary | Skill path | +| ----------- | ---------- | --------------------------- | +| Claude Code | `claude` | `~/.claude/skills` | +| Codex CLI | `codex` | `~/.codex/skills` | +| OpenCode | `opencode` | `~/.config/opencode/skills` | +| Pi Mono | `pi` | `~/.pi/extensions` | + + +Three additional harnesses are tracked with decreasing capability coverage: + + +| Agent | Binary | Skill path | +| ------------------ | --------- | ------------------- | +| GitHub Copilot CLI | `copilot` | `~/.copilot/skills` | +| Gemini CLI | `gemini` | `~/.gemini/skills` | +| Kimi | `kimi` | `~/.kimi/skills` | See the [agent paths reference](/reference/agent-paths) for the full registry and the [adapters reference](/reference/adapters) for what each platform emits. +## Custom harnesses + +If you maintain an internal harness, you can add it as a custom compile target. Register a private adapter via the `plugins` field in `defineConfig` — see [Extending the Build Pipeline](/guide/extending) for the full guide. + ## Where to go next - [Install](/guide/installation) the CLI. - Walk through the [quick start](/guide/quick-start). - Learn the [manifest format](/guide/manifest). + diff --git a/docs/reference/adapters.md b/docs/reference/adapters.md index f5c6565..bd93aa9 100644 --- a/docs/reference/adapters.md +++ b/docs/reference/adapters.md @@ -18,7 +18,7 @@ An adapter compiles the universal manifest into one target platform's native for Two families: **JSON-emitting** adapters (claude, codex, copilot, gemini, kimi) produce static manifest files the host reads at startup. **Code-emitting** adapters (opencode, pimono) produce real TypeScript modules the host imports and calls. -See the [Tier-1 Capability Matrix](/reference/compat-matrix) for full cross-harness details. +See the [Capability Matrix](/guide/capability-matrix) for full cross-harness details. ## JSON-emitting adapters diff --git a/docs/reference/compat-matrix.md b/docs/reference/compat-matrix.md deleted file mode 100644 index ef5c585..0000000 --- a/docs/reference/compat-matrix.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: Tier-1 Capability Matrix -description: What each Tier-1 harness supports — universal codegen, guided per-harness, or unsupported. ---- - -# Tier-1 Capability Matrix - -**Tier-1 harnesses:** Claude Code · Codex · OpenCode · Pi Mono - -**Tier-2 harnesses (tracked, not blocking):** Copilot · Gemini · Kimi - -Legend: -- ✅ **Universal codegen** — adapter emits native output automatically from the manifest -- ⚠️ **Guided per-harness** — no native primitive; author follows a documented escape-hatch pattern; emits a WARN (not error) on portable manifests -- ❌ **Unsupported** — no viable path; recorded here, not blocking -- n/a — not applicable (mechanism differs but functionality is covered) - -## Capability table - -| Capability | Claude Code | Codex | OpenCode | Pi Mono | Notes | -|---|:---:|:---:|:---:|:---:|---| -| `skills` | ✅ | ✅ | ✅ | ✅ | Universal codegen | -| `hooks` (lifecycle) | ✅ | ✅ | ✅ | ✅ | Universal codegen | -| `commands` | ✅ | ✅ | ✅ | ✅ | Universal codegen | -| `mcpServers` | ✅ | ✅ | ✅ | ✅ | **Recommended universal tool path** | -| `agents[]` | ✅ | ✅ | ✅ | ✅ | Universal codegen | -| `agents[].model` | ✅ | ⚠️ | ✅ | ⚠️ | Claude + OpenCode: emits `model:` frontmatter when set. Codex/Pi Mono: no per-agent file concept; model unset → harness default | -| `subagentStart` | ✅ | ✅ | ⚠️ | ✅ | OpenCode: no native event; emits WARN; guided path: intercept via `preToolUse` for subagent tool | -| `subagentStop` | ✅ | ✅ | ⚠️ | ✅ | Same as above; Pi `stop`↔`subagentStop` collision fixed in v0.3.0 | -| `tools[]` (first-class) | ⚠️ | ⚠️ | ✅ | ✅ | WARN emitted; use `mcpServers` for Claude/Codex (Tier-1 universal tool path) | -| `stop` / `continueWith` | ⚠️ | ⚠️ | ⚠️ | ⚠️ | New primitive — v0.4.0; all-Tier-1 design | -| Native-entry passthrough | n/a (JSON) | n/a (JSON) | ⚠️ | ⚠️ | `nativeEntry` escape hatch — v0.4.0; OpenCode native modules must be `.ts` (file-drop path, no config.json edits needed) | -| Inline hook handlers | ✅ auto-wrap | ✅ auto-wrap | ✅ | ✅ | Codex/Kimi: auto-wrapped as Node.js command scripts — v0.4.0 | - -## Tier-2 footnotes - -| Capability | Copilot | Gemini | Kimi | -|---|:---:|:---:|:---:| -| `skills` | ✅ | ✅ | ✅ | -| `hooks` (lifecycle) | ⚠️ | ⚠️ | ❌ | -| `subagentStart` / `subagentStop` | ❌ | ❌ | ❌ | -| `tools[]` | ✅ | ✅ | ❌ | -| `mcpServers` | ❌ | ❌ | ❌ | - -Kimi supported hooks: `preToolUse`, `userPromptSubmit`, `sessionStart`, `notification`, `permissionRequest`. Inline handlers auto-wrapped as Node.js command scripts (v0.4.0). - -## Decision tree for authors - -``` -Does universal codegen cover this capability across all Tier-1? - YES → use it; adapter handles the rest - NO → is there a custom (escape-hatch) path on all Tier-1? - YES → follow the guided per-harness pattern (see "Rewriting for tier-1 parity" guide) - NO → is the gap TUI-grade fidelity only? - YES → acceptable degradation; note in this matrix - NO → open a primitive proposal (v0.4.0+ scope) -``` - ---- - -*This matrix is the living contract for the project. Update it as capabilities land or gaps are discovered. See the [PRD roadmap](/guide/introduction) for full context.* diff --git a/packages/adapter-opencode/src/validate.ts b/packages/adapter-opencode/src/validate.ts index 4f31cd9..f9538e4 100644 --- a/packages/adapter-opencode/src/validate.ts +++ b/packages/adapter-opencode/src/validate.ts @@ -43,7 +43,7 @@ const SUPPORTED_HOOKS: readonly UniversalHookName[] = [ * guided per-harness escape-hatch. These emit a WARN (not an error) so that * portable manifests remain buildable; authors are pointed to the compat matrix. * - * See: docs/reference/compat-matrix.md — "subagentStart / subagentStop" + * See: docs/guide/capability-matrix.md — "subagentStart / subagentStop" */ const GUIDED_PERHARNESS_HOOKS: readonly UniversalHookName[] = [ "subagentStart", @@ -83,7 +83,7 @@ export function createValidate(): (plugin: PluginManifest) => ValidationIssue[] `Hook "${hookName}" has no native OpenCode event. ` + `OpenCode does not expose a child-session/subagent lifecycle. ` + `Use a per-harness nativeEntry or intercept via preToolUse/postToolUse for the subagent tool. ` + - `See docs/reference/compat-matrix.md for the guided per-harness path. ` + + `See docs/guide/capability-matrix.md for the guided per-harness path. ` + `This hook will be omitted from the OpenCode output.`, }); } else if (!SUPPORTED_HOOKS.includes(universalHook)) { From 484876436c91aa5e2c7f966ae24c8db73b4140ad Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 22:53:17 +0200 Subject: [PATCH 37/90] docs(readme): simplify to concise hero-style landing Replaces the long reference-dump README with a short install + create-a-plugin snippet and links out to the docs site for everything else. --- README.md | 222 ++++++------------------------------------------------ 1 file changed, 22 insertions(+), 200 deletions(-) diff --git a/README.md b/README.md index caf7ad3..c537baa 100644 --- a/README.md +++ b/README.md @@ -1,235 +1,57 @@ # AgentPlugins -> **Install any plugin into every AI agent with one command.** - -AgentPlugins is a plugin manager for AI agent harnesses. Install a plugin once — it's symlinked into Claude, Codex, Copilot, Gemini, Kimi, OpenCode, and Pi Mono automatically. Includes a codegen toolkit for authors who want to write once and compile to all platforms. -

- - Sponsor AgentPlugins on Polar.sh - + + + AgentPlugins +

-```bash -agentplugins add user/awesome-plugin -# → Clones, parses manifest, symlinks to every detected agent -``` - -## Install +> Write AI agent plugins once, ship to any harness. -**5 ways to install AgentPlugins:** +Install any plugin into every supported AI agent with one command — Claude Code, Codex, Copilot, Gemini, Kimi, OpenCode, and Pi Mono. ```bash -# 1. npm / npx / bunx -npx @agentplugins/cli add user/awesome-plugin -bunx @agentplugins/cli add user/awesome-plugin - -# 2. Homebrew -brew install sigilco/tap-agentplugins/agentplugins - -# 3. curl (macOS + Linux) -curl -fsSL https://raw.githubusercontent.com/sigilco/agentplugins/main/scripts/install.sh | bash - -# 4. mise (via UBI) -mise use -g ubi:sigilco/agentplugins - -# 5. GitHub Releases (prebuilt binaries) -# → https://github.com/sigilco/agentplugins/releases +curl -fsSL https://agentplugins.pages.dev/install.sh | bash ``` -Verify installation: +Or run ad-hoc with `npx`: ```bash -agentplugins doctor +npx @agentplugins/cli add user/awesome-plugin ``` -## Available Plugins - -Community plugins install with one command — see the [Ecosystem page](https://agentplugins.pages.dev/guide/ecosystem) for the full list: - -| Plugin | Description | -|---|---| -| [agentplugins-autoresearch](https://github.com/sigilco/agentplugins-autoresearch) | Autonomous experiment/optimization loop. | -| [agentplugins-goal](https://github.com/sigilco/agentplugins-goal) | Autonomous goal-completion loop across Tier-1. | -| [agentplugins-ponytail](https://github.com/sigilco/agentplugins-ponytail) | Lazy senior dev mode — minimal working solutions. | -| [agentplugins-caveman](https://github.com/sigilco/agentplugins-caveman) | Ultra-compressed token-savvy communication. | -| [agentplugins-btw](https://github.com/sigilco/agentplugins-btw) | Parallel side-thread thoughts. | - ```bash agentplugins add sigilco/agentplugins-ponytail ``` -## Quick Start - -### Install a Plugin - -```bash -# From a GitHub URL or user/repo shorthand -agentplugins add user/my-plugin - -# List installed plugins -agentplugins list - -# Show details -agentplugins info my-plugin - -# Update to latest -agentplugins update my-plugin -# or update all -agentplugins update --all - -# Remove -agentplugins remove my-plugin -``` - -Plugins are stored in a universal store (`~/.agents/plugins//`) and symlinked into each agent's plugin directory. Skills.sh-compatible plugins are symlinked to `~/.agents/skills/` as well. +## Create a plugin -### Create a Plugin (Authors) +Scaffold a plugin from a template, write your manifest, build, and publish to GitHub: ```bash -# Interactive scaffold agentplugins init - -# With defaults -agentplugins init --yes - -# From a template -agentplugins init --template security-guard -``` - -Templates: `minimal`, `logger` (default), `security-guard`, `formatter`. - -### Build for All Platforms - -```bash -# Validate manifest -agentplugins validate - -# Lint for common issues -agentplugins lint - -# Preview compiled output without writing -agentplugins preview -agentplugins preview --diff - -# Build to dist/ agentplugins build ``` -## Commands - - -| Command | Description | -| --------------- | ----------------------------------------------------------- | -| `add ` | Install a plugin from GitHub URL or `user/repo` | -| `remove ` | Remove a plugin and unlink from all agents | -| `list` | List installed plugins (`--json` for machine output) | -| `info ` | Show plugin metadata, manifest, and symlink status | -| `update [name]` | Update plugin(s) from source (`--all` for all) | -| `doctor` | Diagnose store, symlinks, and agent detection | -| `init` | Scaffold a new plugin interactively (`--yes`, `--template`) | -| `build` | Compile plugin for all target platforms | -| `validate` | Validate manifest against schema | -| `lint` | Static analysis for common issues (`--json`) | -| `preview` | Preview compiled output (`--diff`, `--target`) | - - -## Supported Platforms - - -| Platform | Store Path | Handler Types | -| -------------- | ----------------------------- | ------------- | -| Claude Code | `~/.claude/skills/` | command, http | -| OpenAI Codex | `~/.codex/plugins/` | command | -| GitHub Copilot | `~/.config/github-copilot/` | command, http | -| Google Gemini | `~/.gemini/extensions/` | command | -| Kimi | `~/.kimi/plugins/` | command | -| OpenCode | `~/.config/opencode/plugins/` | inline (TS) | -| Pi Mono | `~/.pi/agent/extensions/` | inline (TS) | - - -## Packages - - -| Package | Description | -| ------------------------------------------ | ---------------------------------------------------------------------------- | -| `[@agentplugins/core](packages/core/)` | Universal types, validation, lint, codegen, store | -| `[@agentplugins/cli](packages/cli/)` | CLI binary (`agentplugins`) | -| `[@agentplugins/schema](packages/schema/)` | JSON Schema + TS types + Ajv validator | -| `[@agentplugins/adapter-*](packages/)` | 7 platform adapters (claude, codex, copilot, gemini, kimi, opencode, pimono) | - - -## Manifest - -Plugins declare a manifest in `agentplugins.config.json`, `manifest.json`, or the `agentplugins` field of `package.json`: - -```json -{ - "name": "my-plugin", - "version": "1.0.0", - "description": "Does something useful across agents", - "hooks": { - "preToolUse": { - "matcher": "bash", - "handler": { - "type": "command", - "command": "${PLUGIN_ROOT}/check.sh" - } - } - }, - "skills": [{ - "name": "my-skill", - "description": "A skill description", - "path": "./skills/my-skill.md" - }] -} -``` - -Full manifest spec: `[spec/v1/manifest.schema.json](spec/v1/manifest.schema.json)` · Docs: [agentplugins.pages.dev](https://agentplugins.pages.dev) +Full guide → [agentplugins.pages.dev/guide/creating-plugins](https://agentplugins.pages.dev/guide/creating-plugins) -## Development +Porting an existing plugin? → [agentplugins.pages.dev/guide/porting](https://agentplugins.pages.dev/guide/porting) -```bash -pnpm install -pnpm build # Build all packages -pnpm test # Run tests -pnpm typecheck # Type-check all packages +## Supported agents -# Build native binaries (requires Bun) -pnpm build:binaries +Works with Claude Code, Codex, Copilot, Gemini, Kimi, OpenCode, and Pi Mono. -# Docs site -cd docs && pnpm install && pnpm dev -``` +Capability comparison → [agentplugins.pages.dev/guide/capability-matrix](https://agentplugins.pages.dev/guide/capability-matrix) -## Architecture +## Documentation -``` -Plugin Source (GitHub) - │ - ▼ -┌─────────────────────────────────┐ -│ AgentPlugins Store │ -│ ~/.agents/plugins// │ -└───────────┬─────────────────────┘ - │ symlinks - ┌────────┼────────┬────────┐ - ▼ ▼ ▼ ▼ - Claude Codex Copilot Gemini ... -``` +Full docs → [agentplugins.pages.dev](https://agentplugins.pages.dev) -For codegen (power users): +LLMs.txt for AI agents → [agentplugins.pages.dev/llms.txt](https://agentplugins.pages.dev/llms.txt) -``` -Manifest → Core (validate + compile) → Adapters → Platform-native output -``` - -

- - Sponsor AgentPlugins on Polar.sh - -

+--- -## License +Sponsor AgentPlugins → [buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj](https://buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj) -Apache-2.0 \ No newline at end of file +Apache-2.0 · [GitHub](https://github.com/sigilco/agentplugins) From 23d1d07125593800df0ac5b3d0110d67c310a74b Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 22:53:23 +0200 Subject: [PATCH 38/90] docs(site): add logo, sponsor CTA, reference landing page; restructure sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - VitePress logo (light/dark) + Sponsor nav link - Hero gets a Sponsor action button; remove duplicated free/OSS section - New /reference index page as nav entry point - Sidebar: Guide → Concepts + Authoring sections; drop legacy compat-matrix entry; add Capability Matrix under Getting Started --- .github/social-preview.snip.txt | 16 ++++++++++++++ docs/.vitepress/config.ts | 24 +++++++++++++-------- docs/index.md | 11 +++------- docs/reference/index.md | 37 +++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 .github/social-preview.snip.txt create mode 100644 docs/reference/index.md diff --git a/.github/social-preview.snip.txt b/.github/social-preview.snip.txt new file mode 100644 index 0000000..1fcabdd --- /dev/null +++ b/.github/social-preview.snip.txt @@ -0,0 +1,16 @@ +$ agentplugins add sigilco/agentplugins-ponytail + + > AgentPlugins - write once, ship to every AI agent + + + Resolved sigilco/agentplugins-ponytail@0.4.0 + + ponytail: linked to ~/.agents/plugins/ponytail/ + + Detected 5 harnesses: + + claude-code ~/.claude/skills/ponytail + + opencode ~/.config/opencode/skills/ponytail + + codex ~/.codex/skills/ponytail + + pimono ~/.pi/skills/ponytail + + copilot ~/.copilot/skills/ponytail + +$ agentplugins list + agentplugins-ponytail v0.4.0 5/5 harnesses diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index b1db1d3..793e183 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -9,6 +9,7 @@ import { fileURLToPath } from 'url' const __dirname = dirname(fileURLToPath(import.meta.url)) const DOCS_SITE = process.env.DOCS_SITE ?? 'https://agentplugins.pages.dev' const GITHUB_SITE = 'https://github.com/sigilco/agentplugins' +const SPONSOR_SITE = 'https://buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj' // Serve scripts/install.sh at the site root (agentplugins.pages.dev/install.sh) // by mirroring it into docs/public/ at config load. scripts/install.sh is the @@ -98,10 +99,15 @@ export default withMermaid(defineConfig({ mermaid: { theme: 'default' }, themeConfig: { + logo: { + light: '/img/logo-light.png', + dark: '/img/logo-dark.png', + alt: 'AgentPlugins', + }, nav: [ { text: 'Guide', link: '/guide/introduction' }, - { text: 'Commands', link: '/reference/commands' }, - { text: 'Schema', link: '/reference/schema' }, + { text: 'Reference', link: '/reference' }, + { text: 'Sponsor', link: SPONSOR_SITE }, { text: 'GitHub', link: GITHUB_SITE }, { text: 'LLMs', @@ -120,26 +126,27 @@ export default withMermaid(defineConfig({ { text: 'Introduction', link: '/guide/introduction' }, { text: 'Installation', link: '/guide/installation' }, { text: 'Quick Start', link: '/guide/quick-start' }, + { text: 'Capability Matrix', link: '/guide/capability-matrix' }, ], }, { - text: 'Guide', + text: 'Concepts', items: [ { text: 'Manifest', link: '/guide/manifest' }, { text: 'Hooks', link: '/guide/hooks' }, { text: 'Skills', link: '/guide/skills' }, { text: 'MCP Servers', link: '/guide/mcp-servers' }, { text: 'Tools', link: '/guide/tools' }, - { text: 'Creating Plugins', link: '/guide/creating-plugins' }, - { text: 'Extending the Build Pipeline', link: '/guide/extending' }, - { text: 'Linting', link: '/guide/linting' }, ], }, { - text: 'Community', + text: 'Authoring', items: [ - { text: 'Ecosystem', link: '/guide/ecosystem' }, + { text: 'Creating Plugins', link: '/guide/creating-plugins' }, { text: 'Porting an Existing Plugin', link: '/guide/porting' }, + { text: 'Extending the Build Pipeline', link: '/guide/extending' }, + { text: 'Linting', link: '/guide/linting' }, + { text: 'Ecosystem', link: '/guide/ecosystem' }, ], }, ], @@ -151,7 +158,6 @@ export default withMermaid(defineConfig({ { text: 'JSON Schema', link: '/reference/schema' }, { text: 'Agent Paths', link: '/reference/agent-paths' }, { text: 'Adapters', link: '/reference/adapters' }, - { text: 'Tier-1 Capability Matrix', link: '/reference/compat-matrix' }, ], }, ], diff --git a/docs/index.md b/docs/index.md index 7e3b4b7..8f08d5e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,6 +12,9 @@ hero: - theme: brand text: Get Started link: /guide/introduction + - theme: alt + text: Sponsor + link: https://buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj - theme: alt text: GitHub link: https://github.com/sigilco/agentplugins @@ -26,11 +29,3 @@ features: - title: "Zero config" details: "Install via npm, Homebrew, curl, or mise. No setup required." --- - -## Free & open source - -AgentPlugins is Apache 2.0 licensed and will always be free and open source. Sponsorship keeps it that way. - -

- Become a Sponsor -

diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 0000000..53b3f70 --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,37 @@ +--- +title: Reference +description: API reference for AgentPlugins — CLI commands, JSON schema, agent paths, and platform adapters. +--- + +# Reference + +Technical reference for the AgentPlugins toolchain. For guides, how-to articles, and concept explanations, see the [Guide](/guide/introduction). + +## CLI + +| Command | Description | +|---|---| +| [`add`](/reference/commands#add) | Install a plugin from GitHub or a local path | +| [`remove`](/reference/commands#remove) | Remove a plugin and unlink from all agents | +| [`list`](/reference/commands#list) | List installed plugins | +| [`update`](/reference/commands#update) | Update plugin(s) from source | +| [`info`](/reference/commands#info) | Show plugin metadata and symlink status | +| [`doctor`](/reference/commands#doctor) | Diagnose store, symlinks, and agent detection | +| [`init`](/reference/commands#init) | Scaffold a new plugin interactively | +| [`build`](/reference/commands#build) | Compile plugin for all target platforms | +| [`validate`](/reference/commands#validate) | Validate manifest against schema | +| [`lint`](/reference/commands#lint) | Static analysis for common issues | +| [`preview`](/reference/commands#preview) | Preview compiled output for a target | + +## Schema + +- [JSON Schema](/reference/schema) — manifest schema, TypeScript types, Ajv validator + +## Platform + +- [Agent Paths](/reference/agent-paths) — store layout, per-agent skill paths, symlink layout +- [Adapters](/reference/adapters) — what each platform adapter emits + +## Manifesto + +For platform compatibility and capability decisions, see the [Capability Matrix](/guide/capability-matrix) in the Guide. From d5f58a2b075037b44fc6a7b6f1755c5d62d0a7b5 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Mon, 29 Jun 2026 22:53:36 +0200 Subject: [PATCH 39/90] chore: gitignore .npmrc (local verdaccio config) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 802a98a..5bb1fa8 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ docs/public/* # Deepwork session state (ephemeral) .slim/ +.npmrc From 4704d73c875b74b4aaf793a66e9bd30a6c1d3c7b Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:15:19 +0200 Subject: [PATCH 40/90] chore(repo): add pnpm catalog, .nvmrc 22, bump engines.node to >=22 --- .nvmrc | 1 + package.json | 2 +- pnpm-workspace.yaml | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..8fdd954 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 \ No newline at end of file diff --git a/package.json b/package.json index 0397b3d..1c515b4 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ ], "license": "Apache-2.0", "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" }, "packageManager": "pnpm@11.0.0", "devDependencies": { diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7e2ce5d..687b160 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,3 +4,16 @@ packages: - 'docs' allowBuilds: esbuild: true + +catalog: + typescript: '^5.9.3' + vitest: '^3.2.6' + tsdown: '^1.0.0' + jiti: '^2.0.0' + tsx: '^4.0.0' + logtape: '^0.4.0' + cleye: '^1.3.0' + zod: '^4.0.0' + '@types/node': '^22.0.0' + cac: '^6.7.14' + chalk: '^5.3.0' \ No newline at end of file From 3a2eb1d51c9e0c99b2b38f856a1ef7ec2f2ef837 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:24:16 +0200 Subject: [PATCH 41/90] chore(pipeline): migrate from tsup to tsdown Also fix tsdown catalog to resolvable ^0.22.3. --- packages/pipeline/package.json | 20 ++++++++++---------- pnpm-workspace.yaml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/pipeline/package.json b/packages/pipeline/package.json index a23b1a2..c54b9e5 100644 --- a/packages/pipeline/package.json +++ b/packages/pipeline/package.json @@ -3,18 +3,18 @@ "version": "0.5.0", "description": "AgentPlugins middleware kernel — composable plugin bus for the build and install pipelines", "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "./dist/index.mjs", + "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.js", + "import": "./dist/index.mjs", "require": "./dist/index.cjs", - "types": "./dist/index.d.ts" + "types": "./dist/index.d.mts" } }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts", - "dev": "tsup src/index.ts --format cjs,esm --dts --watch", + "build": "tsdown src/index.ts --format cjs,esm --dts", + "dev": "tsdown src/index.ts --format cjs,esm --dts --watch", "typecheck": "tsc --noEmit", "test": "vitest run --passWithNoTests" }, @@ -22,10 +22,10 @@ "@agentplugins/contract": "workspace:*" }, "devDependencies": { - "@types/node": "^20.0.0", - "tsup": "^8.0.0", - "typescript": "^5.5.0", - "vitest": "^1.0.0" + "@types/node": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" }, "files": [ "dist", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 687b160..b5b398f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,7 +8,7 @@ allowBuilds: catalog: typescript: '^5.9.3' vitest: '^3.2.6' - tsdown: '^1.0.0' + tsdown: '^0.22.3' jiti: '^2.0.0' tsx: '^4.0.0' logtape: '^0.4.0' From 4b9c4575cc1e3f8fd0b33cf0ed3340308cdaba91 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:25:17 +0200 Subject: [PATCH 42/90] chore(adapter-claude): migrate from tsup to tsdown --- packages/adapter-claude/package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/adapter-claude/package.json b/packages/adapter-claude/package.json index de27f45..7bd8222 100644 --- a/packages/adapter-claude/package.json +++ b/packages/adapter-claude/package.json @@ -3,18 +3,18 @@ "version": "0.5.0", "description": "AgentPlugins platform adapter for Claude Code", "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "./dist/index.mjs", + "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.js", + "import": "./dist/index.mjs", "require": "./dist/index.cjs", - "types": "./dist/index.d.ts" + "types": "./dist/index.d.mts" } }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts", - "dev": "tsup src/index.ts --format cjs,esm --dts --watch", + "build": "tsdown src/index.ts --format cjs,esm --dts", + "dev": "tsdown src/index.ts --format cjs,esm --dts --watch", "typecheck": "tsc --noEmit", "test": "vitest run --passWithNoTests" }, @@ -22,10 +22,10 @@ "@agentplugins/core": "workspace:*" }, "devDependencies": { - "@types/node": "^20.0.0", - "tsup": "^8.0.0", - "typescript": "^5.5.0", - "vitest": "^1.0.0" + "@types/node": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" }, "files": [ "dist", From ee28a75e03bcd7ca349848e00ada6ada07a0f177 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:28:04 +0200 Subject: [PATCH 43/90] chore(adapter-codex): migrate from tsup to tsdown --- packages/adapter-codex/package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/adapter-codex/package.json b/packages/adapter-codex/package.json index 48b3722..9f5ec29 100644 --- a/packages/adapter-codex/package.json +++ b/packages/adapter-codex/package.json @@ -2,18 +2,18 @@ "name": "@agentplugins/adapter-codex", "version": "0.5.0", "description": "AgentPlugins platform adapter for OpenAI Codex CLI", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "./dist/index.cjs", + "types": "./dist/index.d.mts", "exports": { ".": { "import": "./dist/index.mjs", - "require": "./dist/index.js", - "types": "./dist/index.d.ts" + "require": "./dist/index.cjs", + "types": "./dist/index.d.mts" } }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts", - "dev": "tsup src/index.ts --format cjs,esm --dts --watch", + "build": "tsdown src/index.ts --format cjs,esm --dts", + "dev": "tsdown src/index.ts --format cjs,esm --dts --watch", "typecheck": "tsc --noEmit", "lint": "eslint src/**/*.ts", "test": "vitest run --passWithNoTests" @@ -22,10 +22,10 @@ "@agentplugins/core": "workspace:*" }, "devDependencies": { - "@types/node": "^20.0.0", - "tsup": "^8.0.0", - "typescript": "^5.3.0", - "vitest": "^1.0.0" + "@types/node": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" }, "peerDependencies": { "@agentplugins/core": "^0.5.0" From c2dcbad84b891473591ba22cc74988719f6b9743 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:29:06 +0200 Subject: [PATCH 44/90] chore(adapter-copilot): migrate from tsup to tsdown --- packages/adapter-copilot/package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/adapter-copilot/package.json b/packages/adapter-copilot/package.json index 1e2dd35..322eb1a 100644 --- a/packages/adapter-copilot/package.json +++ b/packages/adapter-copilot/package.json @@ -2,13 +2,13 @@ "name": "@agentplugins/adapter-copilot", "version": "0.5.0", "description": "GitHub Copilot CLI platform adapter for AgentPlugins — compiles AgentPlugins plugins into Copilot-compatible manifests, hooks, skills, and MCP configuration", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "./dist/index.cjs", + "types": "./dist/index.d.mts", "exports": { ".": { "import": "./dist/index.mjs", - "require": "./dist/index.js", - "types": "./dist/index.d.ts" + "require": "./dist/index.cjs", + "types": "./dist/index.d.mts" } }, "files": [ @@ -19,7 +19,7 @@ "access": "public" }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts", + "build": "tsdown src/index.ts --format cjs,esm --dts", "typecheck": "tsc --noEmit", "lint": "eslint src/**/*.ts", "test": "vitest run --passWithNoTests" @@ -28,9 +28,9 @@ "@agentplugins/core": "workspace:*" }, "devDependencies": { - "tsup": "^8.0.0", - "typescript": "^5.3.0", - "vitest": "^1.0.0" + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" }, "engines": { "node": ">=18.0.0" From 5443dea04448b336c675111e1cf7ce43cc513587 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:29:07 +0200 Subject: [PATCH 45/90] chore(compile): pin to pnpm catalog versions --- packages/compile/package.json | 13 +- packages/compile/src/codegen.ts | 15 +- packages/compile/src/lint.ts | 10 +- packages/compile/src/validation.ts | 10 +- pnpm-lock.yaml | 943 ++++++++++++++++++++++++----- 5 files changed, 832 insertions(+), 159 deletions(-) diff --git a/packages/compile/package.json b/packages/compile/package.json index 0fbe170..ea853e0 100644 --- a/packages/compile/package.json +++ b/packages/compile/package.json @@ -24,7 +24,12 @@ "publishConfig": { "access": "public" }, - "keywords": ["agentplugins", "compile", "codegen", "kernel"], + "keywords": [ + "agentplugins", + "compile", + "codegen", + "kernel" + ], "license": "Apache-2.0", "repository": { "type": "git", @@ -37,8 +42,8 @@ "@agentplugins/pipeline": "workspace:*" }, "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "^5.9.3", - "vitest": "^3.1.4" + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/compile/src/codegen.ts b/packages/compile/src/codegen.ts index 617842d..4ed5476 100644 --- a/packages/compile/src/codegen.ts +++ b/packages/compile/src/codegen.ts @@ -28,8 +28,9 @@ export function extractCompiledHooks(manifest: PluginManifest): CompiledHook[] { if (!hooks) return []; const compiled: CompiledHook[] = []; + const hooksRecord = hooks as Record; for (const name of Object.keys(hooks) as UniversalHookName[]) { - const def: HookDefinition | undefined = hooks[name]; + const def = hooksRecord[name as string]; if (!def) continue; const handler = def.handler; @@ -43,9 +44,9 @@ export function extractCompiledHooks(manifest: PluginManifest): CompiledHook[] { entry.command = handler.command; } else if (handler.type === 'http') { entry.url = handler.url; - if (handler.headers) entry.headers = { ...handler.headers }; + if (handler.headers) entry.headers = { ...(handler.headers as Record) }; } else { - entry.inlineSource = `// inline handler for ${name} — emit at build time`; + entry.inlineSource = `// inline handler for ${String(name)} — emit at build time`; } compiled.push(entry); @@ -83,7 +84,7 @@ function hookEntry(hook: CompiledHook): string { function buildHooksMap(hooks: CompiledHook[]): string { const indent = ' '; - const lines = hooks.map((h) => `${indent}${h.name}: ${hookEntry(h)},`); + const lines = hooks.map((h) => `${indent}${String(h.name)}: ${hookEntry(h)},`); return `{\n${lines.join('\n')}\n}`; } @@ -130,7 +131,7 @@ export default plugin; function buildHooksDTS(hooks: CompiledHook[]): string { const indent = ' '; - const lines = hooks.map((h) => `${indent}${h.name}: Record;`); + const lines = hooks.map((h) => `${indent}${String(h.name)}: Record;`); return `{\n${lines.join('\n')}\n}`; } @@ -180,7 +181,7 @@ export const GoEmitter: CodeEmitter = { const imports = hooks.length > 0 ? `\nimport "context"\n` : ''; const funcs = hooks.map((h) => { - const fnName = `On${pascalCase(h.name)}`; + const fnName = `On${pascalCase(String(h.name))}`; return [ `func ${fnName}(ctx context.Context) error {`, `\t// TODO: implement ${h.handlerType} handler`, @@ -189,7 +190,7 @@ export const GoEmitter: CodeEmitter = { ].join('\n'); }); - const registrations = hooks.map((h) => `\t// register On${pascalCase(h.name)}`).join('\n'); + const registrations = hooks.map((h) => `\t// register On${pascalCase(String(h.name))}`).join('\n'); const initFn = hooks.length > 0 ? `\nfunc init() {\n${registrations}\n}\n` diff --git a/packages/compile/src/lint.ts b/packages/compile/src/lint.ts index eb6e8a8..cb3e6c2 100644 --- a/packages/compile/src/lint.ts +++ b/packages/compile/src/lint.ts @@ -5,7 +5,7 @@ * catching quality and safety issues beyond structural validation. */ -import type { PluginManifest } from '@agentplugins/contract'; +import type { PluginManifest, HookDefinition, HookHandler } from '@agentplugins/contract'; export interface LintIssue { rule: string; @@ -229,7 +229,8 @@ const handlerSafetyRule: LintRule = { // Scan command-handler command strings if (manifest.hooks) { - for (const [name, def] of Object.entries(manifest.hooks)) { + const hooks = manifest.hooks as Record; + for (const [name, def] of Object.entries(hooks)) { if (def && def.handler.type === 'command') { for (const pattern of activePatterns) { const match = pattern.exec(def.handler.command); @@ -264,7 +265,8 @@ const secretsRule: LintRule = { } } if (manifest.hooks) { - for (const [name, def] of Object.entries(manifest.hooks)) { + const hooks = manifest.hooks as Record; + for (const [name, def] of Object.entries(hooks)) { if (def && def.handler.type === 'command') { haystacks.push({ field: `hooks.${name}`, text: def.handler.command }); } @@ -296,7 +298,7 @@ const continueWithSafetyRule: LintRule = { const hasStopHook = !!manifest.hooks?.stop; if (!hasStopHook) return []; - const handler = manifest.hooks?.stop?.handler; + const handler = manifest.hooks?.stop?.handler as HookHandler | undefined; // Detect continueWith usage: in command handler text OR in inline handler source const usesContinueWith = (handler?.type === 'command' && handler.command.includes('continueWith')) || diff --git a/packages/compile/src/validation.ts b/packages/compile/src/validation.ts index 08e3b6b..519c421 100644 --- a/packages/compile/src/validation.ts +++ b/packages/compile/src/validation.ts @@ -10,10 +10,12 @@ import { type ValidationIssue, type UniversalHookName, type HookHandler, + type HookDefinition, UNIVERSAL_HOOK_NAMES, ALL_TARGETS, type TargetPlatform, Severity, + type Dependency, } from '@agentplugins/contract'; // ─── Universal Validators ───────────────────────────────────────────────────── @@ -88,7 +90,8 @@ export function validateUniversal( // ─── hooks ──────────────────────────────────────────────────────────────── if (plugin.hooks) { - for (const [name, def] of Object.entries(plugin.hooks)) { + const hooksRecord = plugin.hooks as Record; + for (const [name, def] of Object.entries(hooksRecord)) { const hookName = name as UniversalHookName; if (!UNIVERSAL_HOOK_NAMES.includes(hookName)) { issues.push({ @@ -134,7 +137,7 @@ export function validateUniversal( // ─── dependencies ──────────────────────────────────────────────────────── if (plugin.dependencies) { for (let i = 0; i < plugin.dependencies.length; i++) { - const dep = plugin.dependencies[i]; + const dep = plugin.dependencies[i] as Dependency; if (dep.type === 'npm') { if (!dep.name) { issues.push({ severity: Severity.ERROR, field: `dependencies[${i}].name`, message: 'npm dependency must have a "name"' }); @@ -419,7 +422,8 @@ export function validateForPlatform( // Check unsupported hooks if (plugin.hooks) { - for (const [name, def] of Object.entries(plugin.hooks)) { + const hooksRecord = plugin.hooks as Record; + for (const [name, def] of Object.entries(hooksRecord)) { const hookName = name as UniversalHookName; if (!def) continue; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c048564..40d3edf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,13 +4,37 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + default: + '@types/node': + specifier: ^22.0.0 + version: 22.20.0 + cleye: + specifier: ^1.3.0 + version: 1.3.0 + jiti: + specifier: ^2.0.0 + version: 2.0.0 + tsdown: + specifier: ^0.22.3 + version: 0.22.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^3.2.6 + version: 3.2.6 + zod: + specifier: ^4.0.0 + version: 4.4.3 + importers: .: devDependencies: '@changesets/cli': specifier: ^2.31.0 - version: 2.31.0(@types/node@20.19.41) + version: 2.31.0(@types/node@22.20.0) docs: devDependencies: @@ -31,19 +55,19 @@ importers: version: 4.4.3 mermaid: specifier: ^11.15.0 - version: 11.15.0 + version: 11.16.0 vitepress: specifier: ^1.5.0 - version: 1.6.4(@algolia/client-search@5.55.0)(@types/node@20.19.41)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3) + version: 1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3) vitepress-plugin-group-icons: specifier: ^1.7.5 - version: 1.7.5(vite@5.4.21(@types/node@20.19.41)) + version: 1.7.5(vite@5.4.21(@types/node@22.20.0)) vitepress-plugin-llms: specifier: ^1.13.2 version: 1.13.2 vitepress-plugin-mermaid: specifier: ^2.0.17 - version: 2.0.17(mermaid@11.15.0)(vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@20.19.41)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3)) + version: 2.0.17(mermaid@11.16.0)(vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3)) packages/adapter-claude: dependencies: @@ -52,16 +76,16 @@ importers: version: link:../core devDependencies: '@types/node': - specifier: ^20.0.0 + specifier: 'catalog:' version: 20.19.41 - tsup: - specifier: ^8.0.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) + tsdown: + specifier: 'catalog:' + version: 0.22.3(typescript@5.9.3) typescript: - specifier: ^5.5.0 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^1.0.0 + specifier: 'catalog:' version: 1.6.1(@types/node@20.19.41) packages/adapter-codex: @@ -71,16 +95,16 @@ importers: version: link:../core devDependencies: '@types/node': - specifier: ^20.0.0 + specifier: 'catalog:' version: 20.19.41 - tsup: - specifier: ^8.0.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) + tsdown: + specifier: 'catalog:' + version: 0.22.3(typescript@5.9.3) typescript: - specifier: ^5.3.0 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^1.0.0 + specifier: 'catalog:' version: 1.6.1(@types/node@20.19.41) packages/adapter-copilot: @@ -89,15 +113,15 @@ importers: specifier: workspace:* version: link:../core devDependencies: - tsup: - specifier: ^8.0.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) + tsdown: + specifier: 'catalog:' + version: 0.22.3(typescript@5.9.3) typescript: - specifier: ^5.3.0 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^1.0.0 - version: 1.6.1(@types/node@20.19.41) + specifier: 'catalog:' + version: 1.6.1(@types/node@22.20.0) packages/adapter-gemini: dependencies: @@ -107,7 +131,7 @@ importers: devDependencies: tsup: specifier: ^8.0.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) + version: 8.5.1(jiti@2.0.0)(postcss@8.5.15)(typescript@5.9.3) typescript: specifier: ^5.3.0 version: 5.9.3 @@ -120,13 +144,13 @@ importers: devDependencies: tsup: specifier: ^8.0.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) + version: 8.5.1(jiti@2.0.0)(postcss@8.5.15)(typescript@5.9.3) typescript: specifier: ^5.3.0 version: 5.9.3 vitest: specifier: ^1.0.0 - version: 1.6.1(@types/node@20.19.41) + version: 1.6.1(@types/node@22.20.0) packages/adapter-opencode: dependencies: @@ -137,18 +161,18 @@ importers: specifier: workspace:* version: link:../core zod: - specifier: ^3.22.0 - version: 3.25.76 + specifier: 'catalog:' + version: 4.4.3 devDependencies: tsup: specifier: ^8.0.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) + version: 8.5.1(jiti@2.0.0)(postcss@8.5.15)(typescript@5.9.3) typescript: - specifier: ^5.3.0 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^1.0.0 - version: 1.6.1(@types/node@20.19.41) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/adapter-pimono: dependencies: @@ -161,13 +185,13 @@ importers: devDependencies: tsup: specifier: ^8.0.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) + version: 8.5.1(jiti@2.0.0)(postcss@8.5.15)(typescript@5.9.3) typescript: specifier: ^5.4.0 version: 5.9.3 vitest: specifier: ^1.0.0 - version: 1.6.1(@types/node@20.19.41) + version: 1.6.1(@types/node@22.20.0) packages/cli: dependencies: @@ -216,15 +240,15 @@ importers: '@clack/prompts': specifier: ^0.7.0 version: 0.7.0 - cac: - specifier: ^6.7.14 - version: 6.7.14 - chalk: - specifier: ^5.3.0 - version: 5.6.2 + '@logtape/logtape': + specifier: ^0.4.0 + version: 0.4.0 + cleye: + specifier: 'catalog:' + version: 1.3.0 jiti: - specifier: ^1.21.6 - version: 1.21.7 + specifier: 'catalog:' + version: 2.0.0 devDependencies: '@types/node': specifier: ^20.0.0 @@ -246,33 +270,33 @@ importers: version: link:../pipeline devDependencies: '@types/node': - specifier: ^20.0.0 - version: 20.19.41 + specifier: 'catalog:' + version: 22.20.0 typescript: - specifier: ^5.9.3 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^3.1.4 - version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/contract: dependencies: zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: 'catalog:' + version: 4.4.3 zod-to-json-schema: specifier: ^3.24.5 - version: 3.25.2(zod@3.25.76) + version: 3.25.2(zod@4.4.3) devDependencies: '@types/node': - specifier: ^20.0.0 - version: 20.19.41 + specifier: 'catalog:' + version: 22.20.0 typescript: - specifier: ^5.9.3 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^3.1.4 - version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/core: dependencies: @@ -289,18 +313,18 @@ importers: specifier: workspace:* version: link:../store zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: 'catalog:' + version: 4.4.3 devDependencies: '@types/node': - specifier: ^20.0.0 - version: 20.19.41 + specifier: 'catalog:' + version: 22.20.0 typescript: - specifier: ^5.9.3 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^3.1.4 - version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/ingest: dependencies: @@ -312,14 +336,14 @@ importers: version: 4.0.3 devDependencies: '@types/node': - specifier: ^20.0.0 - version: 20.19.41 + specifier: 'catalog:' + version: 22.20.0 typescript: - specifier: ^5.9.3 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^3.1.4 - version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/migrate: dependencies: @@ -334,23 +358,23 @@ importers: version: link:../schema '@modelcontextprotocol/sdk': specifier: ^1.29.0 - version: 1.29.0(zod@3.25.76) + version: 1.29.0(zod@4.4.3) zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: 'catalog:' + version: 4.4.3 zod-to-json-schema: specifier: ^3.25.2 - version: 3.25.2(zod@3.25.76) + version: 3.25.2(zod@4.4.3) devDependencies: '@types/node': - specifier: ^20.0.0 - version: 20.19.41 + specifier: 'catalog:' + version: 22.20.0 typescript: - specifier: ^5.9.3 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^3.1.4 - version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/pipeline: dependencies: @@ -359,16 +383,16 @@ importers: version: link:../contract devDependencies: '@types/node': - specifier: ^20.0.0 + specifier: 'catalog:' version: 20.19.41 - tsup: - specifier: ^8.0.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) + tsdown: + specifier: 'catalog:' + version: 0.22.3(typescript@5.9.3) typescript: - specifier: ^5.5.0 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^1.0.0 + specifier: 'catalog:' version: 1.6.1(@types/node@20.19.41) packages/schema: @@ -378,14 +402,14 @@ importers: version: 8.20.0 devDependencies: '@types/node': - specifier: ^20.0.0 - version: 20.19.41 + specifier: 'catalog:' + version: 22.20.0 typescript: - specifier: ^5.9.3 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^3.1.4 - version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/security: dependencies: @@ -394,14 +418,14 @@ importers: version: link:../schema devDependencies: '@types/node': - specifier: ^20.0.0 - version: 20.19.41 + specifier: 'catalog:' + version: 22.20.0 typescript: - specifier: ^5.9.3 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^3.1.4 - version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/store: dependencies: @@ -416,14 +440,14 @@ importers: version: link:../security devDependencies: '@types/node': - specifier: ^20.0.0 - version: 20.19.41 + specifier: 'catalog:' + version: 22.20.0 typescript: - specifier: ^5.9.3 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^3.1.4 - version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) plugins/example-custom-adapter: devDependencies: @@ -434,7 +458,7 @@ importers: specifier: workspace:* version: link:../../packages/core typescript: - specifier: ^5.5.0 + specifier: 'catalog:' version: 5.9.3 plugins/example-logger: @@ -446,7 +470,7 @@ importers: specifier: workspace:* version: link:../../packages/core typescript: - specifier: ^5.5.0 + specifier: 'catalog:' version: 5.9.3 packages: @@ -530,19 +554,36 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@babel/generator@8.0.0': + resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.0': + resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.2': + resolution: {integrity: sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/parser@7.29.7': resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@8.0.0': + resolution: {integrity: sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + '@babel/runtime@7.29.7': resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} @@ -551,6 +592,10 @@ packages: resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} + '@babel/types@8.0.0': + resolution: {integrity: sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==} + engines: {node: ^22.18.0 || >=24.11.0} + '@braintree/sanitize-url@6.0.4': resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} @@ -646,6 +691,15 @@ packages: search-insights: optional: true + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -987,6 +1041,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@logtape/logtape@0.4.0': + resolution: {integrity: sha512-9T65HMtVoOE/2oyYaZII8gQ7Svw+PeGHnuBXZgiS6WDITGpunZ7cu9HHle1JSgwygYB/LdUwC9EN67Lhejd6Bg==} + '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -996,8 +1053,8 @@ packages: '@mermaid-js/mermaid-mindmap@9.3.0': resolution: {integrity: sha512-IhtYSVBBRYviH1Ehu8gk69pMDF8DSRqXBRDMWrEfHoaMruHeaP2DXA3PBnuwsMaCdPQhlUUcy/7DBLAEIXvCAw==} - '@mermaid-js/parser@1.1.1': - resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} + '@mermaid-js/parser@1.2.0': + resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==} '@modelcontextprotocol/sdk@1.29.0': resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} @@ -1009,6 +1066,12 @@ packages: '@cfworker/json-schema': optional: true + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1021,6 +1084,110 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + + '@rolldown/binding-android-arm64@1.1.3': + resolution: {integrity: sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.3': + resolution: {integrity: sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.3': + resolution: {integrity: sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.3': + resolution: {integrity: sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + resolution: {integrity: sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.3': + resolution: {integrity: sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.3': + resolution: {integrity: sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.3': + resolution: {integrity: sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.3': + resolution: {integrity: sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.3': + resolution: {integrity: sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.3': + resolution: {integrity: sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.3': + resolution: {integrity: sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.3': + resolution: {integrity: sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.3': + resolution: {integrity: sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.3': + resolution: {integrity: sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/rollup-android-arm-eabi@4.61.1': resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==} cpu: [arm] @@ -1186,6 +1353,9 @@ packages: '@sinclair/typebox@0.27.10': resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1297,6 +1467,9 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/linkify-it@5.0.0': resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} @@ -1318,6 +1491,9 @@ packages: '@types/node@20.19.41': resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==} + '@types/node@22.20.0': + resolution: {integrity: sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -1516,6 +1692,10 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + engines: {node: '>=14'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -1536,6 +1716,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-kit@3.0.0: + resolution: {integrity: sha512-8OG92q3R35qjC/4i6BLBMg8IB+fClWu/1PEwg2Z9Rn+BuNaiEgJzpzn+pxWOdHJWDCAwu2JP0wCDTozAM4QirQ==} + engines: {node: ^22.18.0 || >=24.11.0} + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -1550,6 +1734,9 @@ packages: birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + birpc@4.0.0: + resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + body-parser@2.3.0: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} @@ -1576,6 +1763,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1595,10 +1786,6 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} 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==} @@ -1622,6 +1809,9 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + cleye@1.3.0: + resolution: {integrity: sha512-IZ0mRzFQeeQwQk6gT1KIjxa0U86VdFmVB+EIlW3t1Wl1jZShTuWbCu11LcfOA6kODYJ2K7AwgVEb4b7TxN/s2A==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1875,6 +2065,9 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + delaunator@5.1.0: resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} @@ -1904,6 +2097,15 @@ packages: dompurify@3.4.11: resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==} + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1917,6 +2119,10 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -2113,6 +2319,10 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + get-tsconfig@5.0.0-beta.5: + resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} + engines: {node: '>=20.20.0'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2156,6 +2366,9 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} @@ -2186,6 +2399,10 @@ packages: import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + import-without-cache@0.4.0: + resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} + engines: {node: ^22.18.0 || >=24.0.0} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2250,8 +2467,8 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + jiti@2.0.0: + resolution: {integrity: sha512-CJ7e7Abb779OTRv3lomfp7Mns/Sy1+U4pcAx5VbjxCZD5ZM/VJaXPpPjNKjtSvWQy/H86E49REXR34dl1JEz9w==} hasBin: true jose@6.2.3: @@ -2272,6 +2489,11 @@ packages: resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -2394,8 +2616,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - mermaid@11.15.0: - resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==} + mermaid@11.16.0: + resolution: {integrity: sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==} micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2530,6 +2752,10 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + obug@2.1.3: + resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + engines: {node: '>=12.20.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -2710,6 +2936,9 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2765,6 +2994,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -2775,6 +3007,30 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rolldown-plugin-dts@0.26.0: + resolution: {integrity: sha512-e+kEPtUiDES0htk5iqkSeF4EzAV7R+vugGB44iPDuw1Kw9E+WyL1VG7PaV0IIjGHLiacztMBcMTyrr8ON9CT1Q==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20260325.1' + rolldown: ^1.0.0 + typescript: ^5.0.0 || ^6.0.0 + vue-tsc: ~3.2.0 || ~3.3.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.1.3: + resolution: {integrity: sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.61.1: resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2808,6 +3064,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + send@1.2.1: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} @@ -2939,6 +3200,9 @@ packages: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} + terminal-columns@1.4.1: + resolution: {integrity: sha512-IKVL/itiMy947XWVv4IHV7a0KQXvKjj4ptbi7Ew9MPMcOLzkiQeyx3Gyvh62hKrfJ0RZc4M1nbhzjNM39Kyujw==} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -3008,6 +3272,43 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsdown@0.22.3: + resolution: {integrity: sha512-louqbfA8Qf//B9jTTL0FPtXTNpjCWv1VPkbcmQMph2pTpzs+LnB1tbe4tDDRVpo2BjF5SgUXaTZe45SxB8pWHg==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.22.3 + '@tsdown/exe': 0.22.3 + '@vitejs/devtools': '*' + publint: ^0.3.8 + tsx: '*' + typescript: ^5.0.0 || ^6.0.0 + unplugin-unused: ^0.5.0 + unrun: '*' + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.5.1: resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} engines: {node: '>=18'} @@ -3031,6 +3332,9 @@ packages: resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} engines: {node: '>=4'} + type-flag@3.0.0: + resolution: {integrity: sha512-3YaYwMseXCAhBB14RXW5cRQfJQlEknS6i4C8fCfeUdS3ihG9EdccdR9kt3vP73ZdeTGmPb4bZtkDn5XMIn1DLA==} + type-is@2.1.0: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} @@ -3046,6 +3350,9 @@ packages: ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -3262,8 +3569,8 @@ packages: peerDependencies: zod: ^3.25.28 || ^4 - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -3387,14 +3694,31 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.2.4 + '@babel/generator@8.0.0': + dependencies: + '@babel/parser': 8.0.0 + '@babel/types': 8.0.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-string-parser@8.0.0': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-identifier@8.0.2': {} + '@babel/parser@7.29.7': dependencies: '@babel/types': 7.29.7 + '@babel/parser@8.0.0': + dependencies: + '@babel/types': 8.0.0 + '@babel/runtime@7.29.7': {} '@babel/types@7.29.7': @@ -3402,6 +3726,11 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 + '@babel/types@8.0.0': + dependencies: + '@babel/helper-string-parser': 8.0.0 + '@babel/helper-validator-identifier': 8.0.2 + '@braintree/sanitize-url@6.0.4': optional: true @@ -3436,7 +3765,7 @@ snapshots: dependencies: '@changesets/types': 6.1.0 - '@changesets/cli@2.31.0(@types/node@20.19.41)': + '@changesets/cli@2.31.0(@types/node@22.20.0)': dependencies: '@changesets/apply-release-plan': 7.1.1 '@changesets/assemble-release-plan': 6.0.10 @@ -3452,7 +3781,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@20.19.41) + '@inquirer/external-editor': 1.0.3(@types/node@22.20.0) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 enquirer: 2.4.1 @@ -3587,6 +3916,22 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -3758,12 +4103,12 @@ snapshots: '@iconify/types': 2.0.0 import-meta-resolve: 4.2.0 - '@inquirer/external-editor@1.0.3(@types/node@20.19.41)': + '@inquirer/external-editor@1.0.3(@types/node@22.20.0)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 20.19.41 + '@types/node': 22.20.0 '@jest/schemas@29.6.3': dependencies: @@ -3783,6 +4128,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@logtape/logtape@0.4.0': {} + '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.29.7 @@ -3810,11 +4157,11 @@ snapshots: non-layered-tidy-tree-layout: 2.0.2 optional: true - '@mermaid-js/parser@1.1.1': + '@mermaid-js/parser@1.2.0': dependencies: '@chevrotain/types': 11.1.2 - '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': dependencies: '@hono/node-server': 1.19.14(hono@4.12.27) ajv: 8.20.0 @@ -3831,11 +4178,18 @@ snapshots: json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) transitivePeerDependencies: - supports-color + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3848,6 +4202,63 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@oxc-project/types@0.137.0': {} + + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/binding-android-arm64@1.1.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.3': + optional: true + + '@rolldown/binding-darwin-x64@1.1.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.3': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + '@rollup/rollup-android-arm-eabi@4.61.1': optional: true @@ -3965,6 +4376,11 @@ snapshots: '@sinclair/typebox@0.27.10': {} + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -4101,6 +4517,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/jsesc@2.5.1': {} + '@types/linkify-it@5.0.0': {} '@types/markdown-it@14.1.2': @@ -4122,6 +4540,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@22.20.0': + dependencies: + undici-types: 6.21.0 + '@types/trusted-types@2.0.7': optional: true @@ -4136,9 +4558,9 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@20.19.41))(vue@3.5.38(typescript@5.9.3))': + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@22.20.0))(vue@3.5.38(typescript@5.9.3))': dependencies: - vite: 5.4.21(@types/node@20.19.41) + vite: 5.4.21(@types/node@22.20.0) vue: 3.5.38(typescript@5.9.3) '@vitest/expect@1.6.1': @@ -4163,6 +4585,14 @@ snapshots: optionalDependencies: vite: 5.4.21(@types/node@20.19.41) + '@vitest/mocker@3.2.6(vite@5.4.21(@types/node@22.20.0))': + dependencies: + '@vitest/spy': 3.2.6 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@22.20.0) + '@vitest/pretty-format@3.2.6': dependencies: tinyrainbow: 2.0.0 @@ -4360,6 +4790,8 @@ snapshots: ansi-styles@5.2.0: {} + ansis@4.3.1: {} + any-promise@1.3.0: {} argparse@1.0.10: @@ -4374,6 +4806,12 @@ snapshots: assertion-error@2.0.1: {} + ast-kit@3.0.0: + dependencies: + '@babel/parser': 8.0.0 + estree-walker: 3.0.3 + pathe: 2.0.3 + bail@2.0.2: {} balanced-match@4.0.4: {} @@ -4384,6 +4822,8 @@ snapshots: birpc@2.9.0: {} + birpc@4.0.0: {} + body-parser@2.3.0: dependencies: bytes: 3.1.2 @@ -4415,6 +4855,8 @@ snapshots: cac@6.7.14: {} + cac@7.0.0: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4445,8 +4887,6 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 - chalk@5.6.2: {} - character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -4465,6 +4905,11 @@ snapshots: dependencies: readdirp: 4.1.2 + cleye@1.3.0: + dependencies: + terminal-columns: 1.4.1 + type-flag: 3.0.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -4724,6 +5169,8 @@ snapshots: deep-eql@5.0.2: {} + defu@6.1.7: {} + delaunator@5.1.0: dependencies: robust-predicates: 3.0.3 @@ -4748,6 +5195,8 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 + dts-resolver@3.0.0: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4760,6 +5209,8 @@ snapshots: emoji-regex@8.0.0: {} + empathic@2.0.1: {} + encodeurl@2.0.0: {} enquirer@2.4.1: @@ -5021,6 +5472,10 @@ snapshots: get-stream@8.0.1: {} + get-tsconfig@5.0.0-beta.5: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -5075,6 +5530,8 @@ snapshots: hookable@5.5.3: {} + hookable@6.1.1: {} + html-void-elements@3.0.0: {} http-errors@2.0.1: @@ -5101,6 +5558,8 @@ snapshots: import-meta-resolve@4.2.0: {} + import-without-cache@0.4.0: {} + inherits@2.0.4: {} internmap@1.0.1: {} @@ -5139,7 +5598,7 @@ snapshots: isexe@2.0.0: {} - jiti@1.21.7: {} + jiti@2.0.0: {} jose@6.2.3: {} @@ -5156,6 +5615,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-schema-traverse@1.0.0: {} json-schema-typed@8.0.2: {} @@ -5299,11 +5760,11 @@ snapshots: merge2@1.4.1: {} - mermaid@11.15.0: + mermaid@11.16.0: dependencies: '@braintree/sanitize-url': 7.1.2 '@iconify/utils': 3.1.3 - '@mermaid-js/parser': 1.1.1 + '@mermaid-js/parser': 1.2.0 '@types/d3': 7.4.3 '@upsetjs/venn.js': 2.0.0 cytoscape: 3.34.0 @@ -5520,6 +5981,8 @@ snapshots: object-inspect@1.13.4: {} + obug@2.1.3: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -5617,11 +6080,11 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.15): + postcss-load-config@6.0.1(jiti@2.0.0)(postcss@8.5.15): dependencies: lilconfig: 3.1.3 optionalDependencies: - jiti: 1.21.7 + jiti: 2.0.0 postcss: 8.5.15 postcss@8.5.15: @@ -5657,6 +6120,8 @@ snapshots: quansync@0.2.11: {} + quansync@1.0.0: {} + queue-microtask@1.2.3: {} range-parser@1.2.1: {} @@ -5728,12 +6193,51 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + reusify@1.1.0: {} rfdc@1.4.1: {} robust-predicates@3.0.3: {} + rolldown-plugin-dts@0.26.0(rolldown@1.1.3)(typescript@5.9.3): + dependencies: + '@babel/generator': 8.0.0 + '@babel/helper-validator-identifier': 8.0.2 + '@babel/parser': 8.0.0 + ast-kit: 3.0.0 + birpc: 4.0.0 + dts-resolver: 3.0.0 + get-tsconfig: 5.0.0-beta.5 + obug: 2.1.3 + rolldown: 1.1.3 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.1.3: + dependencies: + '@oxc-project/types': 0.137.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.3 + '@rolldown/binding-darwin-arm64': 1.1.3 + '@rolldown/binding-darwin-x64': 1.1.3 + '@rolldown/binding-freebsd-x64': 1.1.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.3 + '@rolldown/binding-linux-arm64-gnu': 1.1.3 + '@rolldown/binding-linux-arm64-musl': 1.1.3 + '@rolldown/binding-linux-ppc64-gnu': 1.1.3 + '@rolldown/binding-linux-s390x-gnu': 1.1.3 + '@rolldown/binding-linux-x64-gnu': 1.1.3 + '@rolldown/binding-linux-x64-musl': 1.1.3 + '@rolldown/binding-openharmony-arm64': 1.1.3 + '@rolldown/binding-wasm32-wasi': 1.1.3 + '@rolldown/binding-win32-arm64-msvc': 1.1.3 + '@rolldown/binding-win32-x64-msvc': 1.1.3 + rollup@4.61.1: dependencies: '@types/estree': 1.0.9 @@ -5799,6 +6303,8 @@ snapshots: semver@7.8.2: {} + semver@7.8.5: {} + send@1.2.1: dependencies: debug: 4.4.3 @@ -5949,6 +6455,8 @@ snapshots: term-size@2.2.1: {} + terminal-columns@1.4.1: {} + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -5996,7 +6504,35 @@ snapshots: ts-interface-checker@0.1.13: {} - tsup@8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3): + tsdown@0.22.3(typescript@5.9.3): + dependencies: + ansis: 4.3.1 + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.0.1 + hookable: 6.1.1 + import-without-cache: 0.4.0 + obug: 2.1.3 + picomatch: 4.0.4 + rolldown: 1.1.3 + rolldown-plugin-dts: 0.26.0(rolldown@1.1.3)(typescript@5.9.3) + semver: 7.8.5 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - vue-tsc + + tslib@2.8.1: + optional: true + + tsup@8.5.1(jiti@2.0.0)(postcss@8.5.15)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 @@ -6007,7 +6543,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.15) + postcss-load-config: 6.0.1(jiti@2.0.0)(postcss@8.5.15) resolve-from: 5.0.0 rollup: 4.61.1 source-map: 0.7.6 @@ -6026,6 +6562,8 @@ snapshots: type-detect@4.1.0: {} + type-flag@3.0.0: {} + type-is@2.1.0: dependencies: content-type: 2.0.0 @@ -6038,6 +6576,11 @@ snapshots: ufo@1.6.4: {} + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + undici-types@6.21.0: {} unified@11.0.5: @@ -6115,6 +6658,24 @@ snapshots: - supports-color - terser + vite-node@1.6.1(@types/node@22.20.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + pathe: 1.1.2 + picocolors: 1.1.1 + vite: 5.4.21(@types/node@22.20.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-node@3.2.4(@types/node@20.19.41): dependencies: cac: 6.7.14 @@ -6133,6 +6694,24 @@ snapshots: - supports-color - terser + vite-node@3.2.4(@types/node@22.20.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 5.4.21(@types/node@22.20.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite@5.4.21(@types/node@20.19.41): dependencies: esbuild: 0.21.5 @@ -6142,13 +6721,22 @@ snapshots: '@types/node': 20.19.41 fsevents: 2.3.3 - vitepress-plugin-group-icons@1.7.5(vite@5.4.21(@types/node@20.19.41)): + vite@5.4.21(@types/node@22.20.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.15 + rollup: 4.61.1 + optionalDependencies: + '@types/node': 22.20.0 + fsevents: 2.3.3 + + vitepress-plugin-group-icons@1.7.5(vite@5.4.21(@types/node@22.20.0)): dependencies: '@iconify-json/logos': 1.2.11 '@iconify-json/vscode-icons': 1.2.59 '@iconify/utils': 3.1.3 optionalDependencies: - vite: 5.4.21(@types/node@20.19.41) + vite: 5.4.21(@types/node@22.20.0) vitepress-plugin-llms@1.13.2: dependencies: @@ -6169,14 +6757,14 @@ snapshots: transitivePeerDependencies: - supports-color - vitepress-plugin-mermaid@2.0.17(mermaid@11.15.0)(vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@20.19.41)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3)): + vitepress-plugin-mermaid@2.0.17(mermaid@11.16.0)(vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3)): dependencies: - mermaid: 11.15.0 - vitepress: 1.6.4(@algolia/client-search@5.55.0)(@types/node@20.19.41)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3) + mermaid: 11.16.0 + vitepress: 1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3) optionalDependencies: '@mermaid-js/mermaid-mindmap': 9.3.0 - vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@20.19.41)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3): + vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3): dependencies: '@docsearch/css': 3.8.2 '@docsearch/js': 3.8.2(@algolia/client-search@5.55.0)(search-insights@2.17.3) @@ -6185,7 +6773,7 @@ snapshots: '@shikijs/transformers': 2.5.0 '@shikijs/types': 2.5.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@20.19.41))(vue@3.5.38(typescript@5.9.3)) + '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@22.20.0))(vue@3.5.38(typescript@5.9.3)) '@vue/devtools-api': 7.7.9 '@vue/shared': 3.5.38 '@vueuse/core': 12.8.2(typescript@5.9.3) @@ -6194,7 +6782,7 @@ snapshots: mark.js: 8.11.1 minisearch: 7.2.0 shiki: 2.5.0 - vite: 5.4.21(@types/node@20.19.41) + vite: 5.4.21(@types/node@22.20.0) vue: 3.5.38(typescript@5.9.3) optionalDependencies: postcss: 8.5.15 @@ -6259,6 +6847,40 @@ snapshots: - supports-color - terser + vitest@1.6.1(@types/node@22.20.0): + dependencies: + '@vitest/expect': 1.6.1 + '@vitest/runner': 1.6.1 + '@vitest/snapshot': 1.6.1 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 + acorn-walk: 8.3.5 + chai: 4.5.0 + debug: 4.4.3 + execa: 8.0.1 + local-pkg: 0.5.1 + magic-string: 0.30.21 + pathe: 1.1.2 + picocolors: 1.1.1 + std-env: 3.10.0 + strip-literal: 2.1.1 + tinybench: 2.9.0 + tinypool: 0.8.4 + vite: 5.4.21(@types/node@22.20.0) + vite-node: 1.6.1(@types/node@22.20.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.20.0 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vitest@3.2.6(@types/debug@4.1.13)(@types/node@20.19.41): dependencies: '@types/chai': 5.2.3 @@ -6298,6 +6920,45 @@ snapshots: - supports-color - terser + vitest@3.2.6(@types/debug@4.1.13)(@types/node@22.20.0): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.6 + '@vitest/mocker': 3.2.6(vite@5.4.21(@types/node@22.20.0)) + '@vitest/pretty-format': 3.2.6 + '@vitest/runner': 3.2.6 + '@vitest/snapshot': 3.2.6 + '@vitest/spy': 3.2.6 + '@vitest/utils': 3.2.6 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 5.4.21(@types/node@22.20.0) + vite-node: 3.2.4(@types/node@22.20.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.13 + '@types/node': 22.20.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vue@3.5.38(typescript@5.9.3): dependencies: '@vue/compiler-dom': 3.5.38 @@ -6341,10 +7002,10 @@ snapshots: yocto-queue@1.2.2: {} - zod-to-json-schema@3.25.2(zod@3.25.76): + zod-to-json-schema@3.25.2(zod@4.4.3): dependencies: - zod: 3.25.76 + zod: 4.4.3 - zod@3.25.76: {} + zod@4.4.3: {} zwitch@2.0.4: {} From 28eea196798c5f65bc56c568a9c787782476c735 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:29:12 +0200 Subject: [PATCH 46/90] chore(ingest): pin to pnpm catalog versions --- packages/ingest/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ingest/package.json b/packages/ingest/package.json index 92f468b..2d2d124 100644 --- a/packages/ingest/package.json +++ b/packages/ingest/package.json @@ -44,8 +44,8 @@ "gray-matter": "^4.0.3" }, "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "^5.9.3", - "vitest": "^3.1.4" + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } From 1ea538338c3bc3d425d1b4e28fd8273912ed60d1 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:29:17 +0200 Subject: [PATCH 47/90] chore(schema): pin to pnpm catalog versions --- packages/schema/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/schema/package.json b/packages/schema/package.json index 5351f65..57c715d 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -44,8 +44,8 @@ "ajv": "^8.17.1" }, "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "^5.9.3", - "vitest": "^3.1.4" + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } From a6ee437e49c593dfe797a67e820a88b24cea4337 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:29:21 +0200 Subject: [PATCH 48/90] chore(security): pin to pnpm catalog versions --- packages/security/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/security/package.json b/packages/security/package.json index 9a92712..7ece984 100644 --- a/packages/security/package.json +++ b/packages/security/package.json @@ -44,8 +44,8 @@ "@agentplugins/schema": "workspace:*" }, "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "^5.9.3", - "vitest": "^3.1.4" + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } From 9609c0b38520f0693b57bddff3646527afcbecf6 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:29:25 +0200 Subject: [PATCH 49/90] chore(store): pin to pnpm catalog versions --- packages/store/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/store/package.json b/packages/store/package.json index 74515ae..f5e8590 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -44,8 +44,8 @@ "@agentplugins/security": "workspace:*" }, "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "^5.9.3", - "vitest": "^3.1.4" + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } From 180524821bee736f746b90d2478585fcd065beba Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:29:29 +0200 Subject: [PATCH 50/90] chore(example-logger): pin to pnpm catalog versions --- plugins/example-logger/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/example-logger/package.json b/plugins/example-logger/package.json index be0d180..5c85867 100644 --- a/plugins/example-logger/package.json +++ b/plugins/example-logger/package.json @@ -9,8 +9,8 @@ "build:core": "cd ../../packages/core && pnpm build" }, "devDependencies": { - "@agentplugins/core": "workspace:*", "@agentplugins/cli": "workspace:*", - "typescript": "^5.5.0" + "@agentplugins/core": "workspace:*", + "typescript": "catalog:" } } From 8e886b19ec1d19a264e643afb7a8d773d6b0571a Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:29:33 +0200 Subject: [PATCH 51/90] chore(example-custom-adapter): pin to pnpm catalog versions --- plugins/example-custom-adapter/package.json | 4 ++-- plugins/example-custom-adapter/src/my-harness-adapter.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/example-custom-adapter/package.json b/plugins/example-custom-adapter/package.json index c93dc89..ab6c54b 100644 --- a/plugins/example-custom-adapter/package.json +++ b/plugins/example-custom-adapter/package.json @@ -7,8 +7,8 @@ "build": "agentplugins build" }, "devDependencies": { - "@agentplugins/core": "workspace:*", "@agentplugins/cli": "workspace:*", - "typescript": "^5.5.0" + "@agentplugins/core": "workspace:*", + "typescript": "catalog:" } } diff --git a/plugins/example-custom-adapter/src/my-harness-adapter.ts b/plugins/example-custom-adapter/src/my-harness-adapter.ts index 6352330..000786b 100644 --- a/plugins/example-custom-adapter/src/my-harness-adapter.ts +++ b/plugins/example-custom-adapter/src/my-harness-adapter.ts @@ -5,7 +5,7 @@ * Copy this file into your project and modify it for your target platform. */ -import type { PlatformAdapter, PluginManifest, AdapterOutput } from '@agentplugins/core'; +import { Severity, type PlatformAdapter, type PluginManifest, type AdapterOutput } from '@agentplugins/core'; const MY_HARNESS_TARGET = 'my-harness' as const; @@ -20,7 +20,7 @@ export const myHarnessAdapter: PlatformAdapter = { validate(plugin: PluginManifest) { const issues = []; if (!plugin.name) { - issues.push({ severity: 'error' as const, field: 'name', message: 'name is required' }); + issues.push({ severity: Severity.ERROR, field: 'name', message: 'name is required' }); } return issues; }, From 77f1bf9dde042dd0b71c9cc62bc3779d73d08d43 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:29:57 +0200 Subject: [PATCH 52/90] chore(adapter-gemini): migrate from tsup to tsdown --- packages/adapter-gemini/package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/adapter-gemini/package.json b/packages/adapter-gemini/package.json index ba066a0..8f1fe09 100644 --- a/packages/adapter-gemini/package.json +++ b/packages/adapter-gemini/package.json @@ -3,26 +3,26 @@ "version": "0.5.0", "description": "AgentPlugins platform adapter for Google Gemini CLI extensions", "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "./dist/index.mjs", + "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.js", + "import": "./dist/index.mjs", "require": "./dist/index.cjs", - "types": "./dist/index.d.ts" + "types": "./dist/index.d.mts" } }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts", - "dev": "tsup src/index.ts --format cjs,esm --dts --watch", + "build": "tsdown src/index.ts --format cjs,esm --dts", + "dev": "tsdown src/index.ts --format cjs,esm --dts --watch", "clean": "rm -rf dist" }, "dependencies": { "@agentplugins/core": "workspace:*" }, "devDependencies": { - "tsup": "^8.0.0", - "typescript": "^5.3.0" + "tsdown": "catalog:", + "typescript": "catalog:" }, "files": [ "dist", From 26c577506d128176c147251a9619e987839882bf Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:31:10 +0200 Subject: [PATCH 53/90] chore(adapter-kimi): migrate from tsup to tsdown --- packages/adapter-kimi/package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/adapter-kimi/package.json b/packages/adapter-kimi/package.json index c638f94..d17e04b 100644 --- a/packages/adapter-kimi/package.json +++ b/packages/adapter-kimi/package.json @@ -2,13 +2,13 @@ "name": "@agentplugins/adapter-kimi", "version": "0.5.0", "description": "AgentPlugins platform adapter for Kimi (Moonshot AI)", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "./dist/index.cjs", + "types": "./dist/index.d.mts", "exports": { ".": { "import": "./dist/index.mjs", - "require": "./dist/index.js", - "types": "./dist/index.d.ts" + "require": "./dist/index.cjs", + "types": "./dist/index.d.mts" } }, "files": [ @@ -19,8 +19,8 @@ "access": "public" }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts --clean", - "dev": "tsup src/index.ts --format cjs,esm --dts --watch", + "build": "tsdown src/index.ts --format cjs,esm --dts --clean", + "dev": "tsdown src/index.ts --format cjs,esm --dts --watch", "lint": "eslint src/**/*.ts", "typecheck": "tsc --noEmit", "test": "vitest run --passWithNoTests" @@ -29,9 +29,9 @@ "@agentplugins/core": "workspace:*" }, "devDependencies": { - "tsup": "^8.0.0", - "typescript": "^5.3.0", - "vitest": "^1.0.0" + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" }, "peerDependencies": { "@agentplugins/core": "^0.5.0" From 01da54209737cd9a5f19ce6cf5015507929f65ac Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:32:10 +0200 Subject: [PATCH 54/90] chore(adapter-pimono): migrate from tsup to tsdown --- packages/adapter-pimono/package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/adapter-pimono/package.json b/packages/adapter-pimono/package.json index 8fecdf3..461dd72 100644 --- a/packages/adapter-pimono/package.json +++ b/packages/adapter-pimono/package.json @@ -3,30 +3,30 @@ "version": "0.5.0", "description": "Pi Mono platform adapter for AgentPlugins — generates TypeScript-native extensions for the Pi agent runtime", "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "./dist/index.mjs", + "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.js", + "import": "./dist/index.mjs", "require": "./dist/index.cjs", - "types": "./dist/index.d.ts" + "types": "./dist/index.d.mts" } }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts", - "dev": "tsup src/index.ts --format cjs,esm --dts --watch", + "build": "tsdown src/index.ts --format cjs,esm --dts", + "dev": "tsdown src/index.ts --format cjs,esm --dts --watch", "clean": "rm -rf dist", "test": "vitest run --passWithNoTests", "test:run": "vitest run" }, "dependencies": { - "@agentplugins/core": "workspace:*", - "@agentplugins/compile": "workspace:*" + "@agentplugins/compile": "workspace:*", + "@agentplugins/core": "workspace:*" }, "devDependencies": { - "tsup": "^8.0.0", - "typescript": "^5.4.0", - "vitest": "^1.0.0" + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" }, "files": [ "dist", From 7bf556ea061f5d7baba078747c000ad3b6097c47 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:36:39 +0200 Subject: [PATCH 55/90] feat(contract): migrate to zod 4 --- packages/contract/manifest.schema.json | 126 ++++++++++++++++++----- packages/contract/package.json | 9 +- packages/contract/src/generate-schema.ts | 20 ++-- packages/contract/src/schema.ts | 18 ++-- 4 files changed, 125 insertions(+), 48 deletions(-) diff --git a/packages/contract/manifest.schema.json b/packages/contract/manifest.schema.json index 5e7d4b5..6f838ae 100644 --- a/packages/contract/manifest.schema.json +++ b/packages/contract/manifest.schema.json @@ -32,10 +32,13 @@ "type": "string" }, "email": { - "type": "string" + "type": "string", + "format": "email", + "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" }, "url": { - "type": "string" + "type": "string", + "format": "uri" } }, "required": [ @@ -98,7 +101,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -139,6 +142,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -185,7 +191,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -226,6 +232,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -272,7 +281,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -313,6 +322,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -359,7 +371,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -400,6 +412,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -446,7 +461,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -487,6 +502,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -533,7 +551,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -574,6 +592,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -620,7 +641,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -661,6 +682,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -707,7 +731,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -748,6 +772,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -794,7 +821,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -835,6 +862,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -881,7 +911,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -922,6 +952,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -968,7 +1001,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -1009,6 +1042,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -1055,7 +1091,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -1096,6 +1132,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -1142,7 +1181,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -1183,6 +1222,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -1229,7 +1271,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -1270,6 +1312,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -1316,7 +1361,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -1357,6 +1402,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -1403,7 +1451,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -1444,6 +1492,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -1490,7 +1541,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -1531,6 +1582,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -1577,7 +1631,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -1618,6 +1672,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -1664,7 +1721,7 @@ "type": "string" }, "handler": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -1705,6 +1762,9 @@ }, "headers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -1812,6 +1872,9 @@ }, "mcpServers": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { @@ -1826,6 +1889,9 @@ }, "env": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } @@ -1846,6 +1912,9 @@ }, "userConfig": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "object", "properties": { @@ -1886,6 +1955,9 @@ }, "metadata": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": {} }, "capabilities": { @@ -1915,17 +1987,18 @@ }, "adapterOverrides": { "type": "object", - "additionalProperties": { - "type": "string" - }, "propertyNames": { + "type": "string", "minLength": 1 + }, + "additionalProperties": { + "type": "string" } }, "dependencies": { "type": "array", "items": { - "anyOf": [ + "oneOf": [ { "type": "object", "properties": { @@ -1983,6 +2056,9 @@ }, "env": { "type": "object", + "propertyNames": { + "type": "string" + }, "additionalProperties": { "type": "string" } diff --git a/packages/contract/package.json b/packages/contract/package.json index f005886..d2049ec 100644 --- a/packages/contract/package.json +++ b/packages/contract/package.json @@ -34,12 +34,11 @@ }, "homepage": "https://agentplugins.pages.dev/", "dependencies": { - "zod": "^3.25.76", - "zod-to-json-schema": "^3.24.5" + "zod": "catalog:" }, "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "^5.9.3", - "vitest": "^3.1.4" + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/contract/src/generate-schema.ts b/packages/contract/src/generate-schema.ts index 19227f4..308d854 100644 --- a/packages/contract/src/generate-schema.ts +++ b/packages/contract/src/generate-schema.ts @@ -6,26 +6,28 @@ import { writeFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { zodToJsonSchema } from 'zod-to-json-schema'; +import { z } from 'zod'; import { PluginManifestSchema } from './schema.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); const outPath = join(__dirname, '..', 'manifest.schema.json'); -const schema = zodToJsonSchema(PluginManifestSchema, { - name: 'PluginManifest', - $refStrategy: 'none', - definitionPath: '$defs', - markdownDescription: true, - errorMessages: false, -}); +const schema = z.toJSONSchema(PluginManifestSchema, { + target: 'draft-07', +}) as Record; + +// z.toJSONSchema injects its own $schema; the wrapper below owns it. +delete schema.$schema; const output = { $schema: 'http://json-schema.org/draft-07/schema#', $id: 'https://agentplugins.pages.dev/schema/v1.json', title: 'AgentPlugins Manifest', description: 'Universal plugin manifest for AI agent platforms. https://agentplugins.pages.dev/', - ...schema, + $ref: '#/$defs/PluginManifest', + $defs: { + PluginManifest: schema, + }, }; writeFileSync(outPath, JSON.stringify(output, null, 2) + '\n'); diff --git a/packages/contract/src/schema.ts b/packages/contract/src/schema.ts index 947f8ae..792f7f4 100644 --- a/packages/contract/src/schema.ts +++ b/packages/contract/src/schema.ts @@ -47,7 +47,7 @@ export type Dependency = z.infer; export const SidecarSchema = z.object({ command: z.string(), args: z.array(z.string()).optional(), - env: z.record(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), port: z.number().optional(), health: z.string().optional(), restart: z.enum(['always', 'on-failure', 'no']).optional(), @@ -92,7 +92,7 @@ export type AgentDefinition = z.infer; export const MCPServerConfigSchema = z.object({ command: z.string(), args: z.array(z.string()).optional(), - env: z.record(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), transport: z.enum(['stdio', 'http']).optional(), }); export type MCPServerConfig = z.infer; @@ -114,14 +114,14 @@ export const ToolParameterSchema: z.ZodType = z.lazy(() => description: z.string().optional(), enum: z.array(z.string()).optional(), items: ToolParameterSchema.optional(), - properties: z.record(ToolParameterSchema).optional(), + properties: z.record(z.string(), ToolParameterSchema).optional(), required: z.array(z.string()).optional(), }) ); export const ToolParameterSchemaMapSchema = z.object({ type: z.literal('object'), - properties: z.record(ToolParameterSchema), + properties: z.record(z.string(), ToolParameterSchema), required: z.array(z.string()).optional(), }); @@ -215,7 +215,7 @@ export type CommandHookHandler = z.infer; export const HttpHandlerSchema = z.object({ type: z.literal('http'), url: z.string(), - headers: z.record(z.string()).optional(), + headers: z.record(z.string(), z.string()).optional(), }); export type HttpHookHandler = z.infer; @@ -307,7 +307,7 @@ export const PluginManifestSchema = z.object({ displayName: z.string().optional(), author: z.union([ z.string(), - z.object({ name: z.string(), email: z.string().optional(), url: z.string().optional() }), + z.object({ name: z.string(), email: z.email().optional(), url: z.url().optional() }), ]).optional(), homepage: z.string().optional(), repository: z.string().optional(), @@ -319,9 +319,9 @@ export const PluginManifestSchema = z.object({ hooks: UniversalHooksSchema.optional(), commands: z.array(CommandSchema).optional(), agents: z.array(AgentDefinitionSchema).optional(), - mcpServers: z.record(MCPServerConfigSchema).optional(), - userConfig: z.record(UserConfigOptionSchema).optional(), - metadata: z.record(z.unknown()).optional(), + mcpServers: z.record(z.string(), MCPServerConfigSchema).optional(), + userConfig: z.record(z.string(), UserConfigOptionSchema).optional(), + metadata: z.record(z.string(), z.unknown()).optional(), capabilities: z.array(z.string()).optional(), targets: z.array(TargetPlatformSchema).optional(), From bdb037479011602ff10d9fa2f75acb8dbf3b4aa9 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:36:47 +0200 Subject: [PATCH 56/90] feat(core): migrate to zod 4 --- packages/core/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 37c7660..1bf5b1d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -47,11 +47,11 @@ "@agentplugins/compile": "workspace:*", "@agentplugins/pipeline": "workspace:*", "@agentplugins/store": "workspace:*", - "zod": "^3.25.76" + "zod": "catalog:" }, "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "^5.9.3", - "vitest": "^3.1.4" + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } From ff8587615671a24bff6a7597defdeaf66ff22999 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:36:55 +0200 Subject: [PATCH 57/90] feat(migrate): migrate to zod 4 --- packages/migrate/package.json | 9 ++++----- packages/migrate/src/tools/_helpers.ts | 3 +-- packages/migrate/src/tools/diff-manifest.ts | 4 ++-- packages/migrate/src/tools/write-manifest.ts | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/migrate/package.json b/packages/migrate/package.json index 7ad81c9..4b55525 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -47,12 +47,11 @@ "@agentplugins/ingest": "workspace:*", "@agentplugins/schema": "workspace:*", "@modelcontextprotocol/sdk": "^1.29.0", - "zod": "^3.25.76", - "zod-to-json-schema": "^3.25.2" + "zod": "catalog:" }, "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "^5.9.3", - "vitest": "^3.1.4" + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/migrate/src/tools/_helpers.ts b/packages/migrate/src/tools/_helpers.ts index 082cf4b..9461269 100644 --- a/packages/migrate/src/tools/_helpers.ts +++ b/packages/migrate/src/tools/_helpers.ts @@ -3,7 +3,6 @@ */ import { z, type ZodRawShape } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; import type { ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js'; export interface McpTool { @@ -25,7 +24,7 @@ export function defineTool( * The MCP SDK accepts zod shapes directly so this is mostly used in tests. */ export function describeSchema(shape: ZodRawShape): Record { - return zodToJsonSchema(z.object(shape)) as Record; + return z.toJSONSchema(z.object(shape), { target: 'draft-07' }) as Record; } export { z }; diff --git a/packages/migrate/src/tools/diff-manifest.ts b/packages/migrate/src/tools/diff-manifest.ts index 8e60e65..3f1ff3f 100644 --- a/packages/migrate/src/tools/diff-manifest.ts +++ b/packages/migrate/src/tools/diff-manifest.ts @@ -10,8 +10,8 @@ import { defineTool, z } from './_helpers.js'; export const diffManifestTool = defineTool( { - before: z.record(z.unknown()).describe('The original manifest (or any baseline)'), - after: z.record(z.unknown()).describe('The generated manifest'), + before: z.record(z.string(), z.unknown()).describe('The original manifest (or any baseline)'), + after: z.record(z.string(), z.unknown()).describe('The generated manifest'), }, async ({ before, after }) => { const keys = new Set([...Object.keys(before), ...Object.keys(after)]); diff --git a/packages/migrate/src/tools/write-manifest.ts b/packages/migrate/src/tools/write-manifest.ts index 9f6c609..da50e58 100644 --- a/packages/migrate/src/tools/write-manifest.ts +++ b/packages/migrate/src/tools/write-manifest.ts @@ -14,7 +14,7 @@ import { validateManifest } from '@agentplugins/schema'; export const writeManifestTool = defineTool( { destination: z.string().describe('Absolute path to write the manifest to'), - manifest: z.record(z.unknown()).describe('The manifest object to write'), + manifest: z.record(z.string(), z.unknown()).describe('The manifest object to write'), overwrite: z.boolean().default(false).describe('Overwrite an existing file'), }, async ({ destination, manifest, overwrite }) => { From de294f62bbb24787b2a7683ae8cef8d6fcca18b2 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:37:00 +0200 Subject: [PATCH 58/90] feat(adapter-opencode): migrate to zod 4 --- packages/adapter-opencode/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/adapter-opencode/package.json b/packages/adapter-opencode/package.json index 344a25a..b9bd06c 100644 --- a/packages/adapter-opencode/package.json +++ b/packages/adapter-opencode/package.json @@ -22,12 +22,12 @@ "dependencies": { "@agentplugins/core": "workspace:*", "@agentplugins/compile": "workspace:*", - "zod": "^3.22.0" + "zod": "catalog:" }, "devDependencies": { "tsup": "^8.0.0", - "typescript": "^5.3.0", - "vitest": "^1.0.0" + "typescript": "catalog:", + "vitest": "catalog:" }, "files": [ "dist", From a77b9bfdf417ea697f47ac9c8827ed0a3f2cd38d Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:39:44 +0200 Subject: [PATCH 59/90] chore(pipeline): align exports with tsdown output extensions --- packages/pipeline/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/pipeline/package.json b/packages/pipeline/package.json index c54b9e5..6496cc4 100644 --- a/packages/pipeline/package.json +++ b/packages/pipeline/package.json @@ -7,9 +7,8 @@ "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs", - "types": "./dist/index.d.mts" + "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, + "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } }, "scripts": { From e9a07131dcb951dfe988d32e760825dbf89b8c75 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:39:50 +0200 Subject: [PATCH 60/90] chore(adapter-claude): align exports with tsdown output extensions --- packages/adapter-claude/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/adapter-claude/package.json b/packages/adapter-claude/package.json index 7bd8222..baa037e 100644 --- a/packages/adapter-claude/package.json +++ b/packages/adapter-claude/package.json @@ -7,9 +7,8 @@ "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs", - "types": "./dist/index.d.mts" + "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, + "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } }, "scripts": { From e16c2f5cd550d56043352ca4a95e66e46acbe082 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:39:56 +0200 Subject: [PATCH 61/90] chore(adapter-codex): align exports with tsdown output extensions --- packages/adapter-codex/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/adapter-codex/package.json b/packages/adapter-codex/package.json index 9f5ec29..afbd805 100644 --- a/packages/adapter-codex/package.json +++ b/packages/adapter-codex/package.json @@ -6,9 +6,8 @@ "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs", - "types": "./dist/index.d.mts" + "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, + "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } }, "scripts": { From 3f0559452682632076b1286f5f60a9bf9c2d4c78 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:40:02 +0200 Subject: [PATCH 62/90] chore(adapter-copilot): align exports with tsdown output extensions --- packages/adapter-copilot/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/adapter-copilot/package.json b/packages/adapter-copilot/package.json index 322eb1a..e6d735a 100644 --- a/packages/adapter-copilot/package.json +++ b/packages/adapter-copilot/package.json @@ -6,9 +6,8 @@ "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs", - "types": "./dist/index.d.mts" + "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, + "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } }, "files": [ From 9f19e91fa7689e65f94a4c3bf6144e8d0d2812b0 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:40:08 +0200 Subject: [PATCH 63/90] chore(adapter-gemini): align exports with tsdown output extensions --- packages/adapter-gemini/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/adapter-gemini/package.json b/packages/adapter-gemini/package.json index 8f1fe09..ff44139 100644 --- a/packages/adapter-gemini/package.json +++ b/packages/adapter-gemini/package.json @@ -7,9 +7,8 @@ "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs", - "types": "./dist/index.d.mts" + "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, + "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } }, "scripts": { From 73c44c1bb3280ea22418106792eff9ef88c67ffe Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:40:14 +0200 Subject: [PATCH 64/90] chore(adapter-kimi): align exports with tsdown output extensions --- packages/adapter-kimi/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/adapter-kimi/package.json b/packages/adapter-kimi/package.json index d17e04b..fc9ffb3 100644 --- a/packages/adapter-kimi/package.json +++ b/packages/adapter-kimi/package.json @@ -6,9 +6,8 @@ "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs", - "types": "./dist/index.d.mts" + "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, + "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } }, "files": [ From d86593fd493c8787ff9f462229e9c6c7e78c92a6 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:40:19 +0200 Subject: [PATCH 65/90] chore(adapter-pimono): align exports with tsdown output extensions --- packages/adapter-pimono/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/adapter-pimono/package.json b/packages/adapter-pimono/package.json index 461dd72..6f0c2a7 100644 --- a/packages/adapter-pimono/package.json +++ b/packages/adapter-pimono/package.json @@ -7,9 +7,8 @@ "types": "./dist/index.d.mts", "exports": { ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs", - "types": "./dist/index.d.mts" + "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, + "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } }, "scripts": { From 4abc05379b3835aa866909eea3f992f1dd60e57a Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:54:18 +0200 Subject: [PATCH 66/90] feat(cli): migrate from cac to cleye --- packages/cli/package.json | 2 +- packages/cli/src/cli.ts | 462 ++++++++++++++++++++------------------ pnpm-lock.yaml | 78 +++---- 3 files changed, 285 insertions(+), 257 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index c6d4a23..e4801da 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -36,8 +36,8 @@ "@agentplugins/adapter-opencode": "workspace:*", "@agentplugins/adapter-pimono": "workspace:*", "@clack/prompts": "^0.7.0", - "cac": "^6.7.14", "chalk": "^5.3.0", + "cleye": "catalog:", "jiti": "^1.21.6" }, "devDependencies": { diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 352d5be..39d72c9 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -22,8 +22,7 @@ * agentplugins preview # Preview compile output without writing */ -import { cac } from 'cac'; -import chalk from 'chalk'; +import { cli, command } from 'cleye'; import pkg from '../package.json' with { type: 'json' }; import { build } from './commands/build.js'; import { validate } from './commands/validate.js'; @@ -41,230 +40,267 @@ import { importCommand } from './commands/import.js'; import { audit } from './commands/audit.js'; import { loadConfig } from './config.js'; -const cli = cac('agentplugins'); +function formatError(err: unknown): string { + return err instanceof Error ? err.message : String(err); +} -// ─── Distribution Commands ─────────────────────────────────────────────────── +cli({ + name: 'agentplugins', + version: pkg.version, + commands: [ + // ─── Distribution Commands ────────────────────────────────────────────── -cli - .command('add ', 'Install a plugin from GitHub to the store + all agents') - .option('-y, --yes', 'Skip the setup trust prompt (still denylist-gated)') - .option('--no-setup', 'Do not run any setup script after install') - .action(async (source: string, options) => { - try { - await add({ source, yes: options.yes, noSetup: options.setup === false }); - } catch (err) { - console.error(chalk.red('Add failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); + command({ + name: 'add', + parameters: [''], + flags: { + yes: { type: Boolean, alias: 'y', description: 'Skip the setup trust prompt (still denylist-gated)' }, + noSetup: { type: Boolean, default: false, description: 'Do not run any setup script after install' }, + }, + help: { description: 'Install a plugin from GitHub to the store + all agents' }, + }, async ({ _, flags }) => { + try { + await add({ source: _.source, yes: flags.yes, noSetup: flags.noSetup }); + } catch (err) { + console.error('Add failed:', formatError(err)); + process.exit(1); + } + }), -cli - .command('setup ', "Run an installed plugin's setup script (re-runnable)") - .option('-f, --force', 'Re-prompt even if the setup command is unchanged/trusted') - .option('-y, --yes', 'Skip the trust prompt (still denylist-gated)') - .action(async (name: string, options) => { - try { - await setup({ name, force: options.force, yes: options.yes }); - } catch (err) { - console.error(chalk.red('Setup failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); + command({ + name: 'setup', + parameters: [''], + flags: { + force: { type: Boolean, alias: 'f', description: 'Re-prompt even if the setup command is unchanged/trusted' }, + yes: { type: Boolean, alias: 'y', description: 'Skip the trust prompt (still denylist-gated)' }, + }, + help: { description: "Run an installed plugin's setup script (re-runnable)" }, + }, async ({ _, flags }) => { + try { + await setup({ name: _.name, force: flags.force, yes: flags.yes }); + } catch (err) { + console.error('Setup failed:', formatError(err)); + process.exit(1); + } + }), -cli - .command('remove ', 'Remove a plugin from the store and unlink all symlinks') - .option('-f, --force', 'Skip confirmation', { default: false }) - .action(async (name: string, options) => { - try { - await remove({ name, force: options.force }); - } catch (err) { - console.error(chalk.red('Remove failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); + command({ + name: 'remove', + parameters: [''], + flags: { + force: { type: Boolean, alias: 'f', default: false, description: 'Skip confirmation' }, + }, + help: { description: 'Remove a plugin from the store and unlink all symlinks' }, + }, async ({ _, flags }) => { + try { + await remove({ name: _.name, force: flags.force }); + } catch (err) { + console.error('Remove failed:', formatError(err)); + process.exit(1); + } + }), -cli - .command('list', 'List installed plugins') - .option('--json', 'Output as JSON') - .action(async (options) => { - try { - await list({ json: options.json }); - } catch (err) { - console.error(chalk.red('List failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); + command({ + name: 'list', + flags: { + json: { type: Boolean, description: 'Output as JSON' }, + }, + help: { description: 'List installed plugins' }, + }, async ({ flags }) => { + try { + await list({ json: flags.json }); + } catch (err) { + console.error('List failed:', formatError(err)); + process.exit(1); + } + }), -cli - .command('update [name]', 'Update plugin(s) from source') - .option('-a, --all', 'Update all installed plugins') - .action(async (name: string | undefined, options) => { - try { - await update({ name, all: options.all }); - } catch (err) { - console.error(chalk.red('Update failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); + command({ + name: 'update', + parameters: ['[name]'], + flags: { + all: { type: Boolean, alias: 'a', description: 'Update all installed plugins' }, + }, + help: { description: 'Update plugin(s) from source' }, + }, async ({ _, flags }) => { + try { + await update({ name: _.name, all: flags.all }); + } catch (err) { + console.error('Update failed:', formatError(err)); + process.exit(1); + } + }), -cli - .command('info ', 'Show detailed information about an installed plugin') - .option('--json', 'Output as JSON') - .action(async (name: string, options) => { - try { - await info({ name, json: options.json }); - } catch (err) { - console.error(chalk.red('Info failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); + command({ + name: 'info', + parameters: [''], + flags: { + json: { type: Boolean, description: 'Output as JSON' }, + }, + help: { description: 'Show detailed information about an installed plugin' }, + }, async ({ _, flags }) => { + try { + await info({ name: _.name, json: flags.json }); + } catch (err) { + console.error('Info failed:', formatError(err)); + process.exit(1); + } + }), -cli - .command('doctor', 'Run diagnostics on store, agents, and symlinks') - .option('--json', 'Output as JSON') - .action(async (options) => { - try { - await doctor({ json: options.json }); - } catch (err) { - console.error(chalk.red('Doctor failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); + command({ + name: 'doctor', + flags: { + json: { type: Boolean, description: 'Output as JSON' }, + }, + help: { description: 'Run diagnostics on store, agents, and symlinks' }, + }, async ({ flags }) => { + try { + await doctor({ json: flags.json }); + } catch (err) { + console.error('Doctor failed:', formatError(err)); + process.exit(1); + } + }), -cli - .command('import ', 'Translate a community plugin (Claude Code, Codex, Skills.sh) into an AgentPlugins manifest') - .option('-o, --out ', 'Output manifest path (default: /agentplugins.imported.json)') - .option('--write', 'Install the generated manifest into the universal store', { default: false }) - .option('--no-vendor', 'Skip copying upstream files into .agentplugins-vendor/') - .option('-q, --quiet', 'Suppress warning output', { default: false }) - .action(async (format: string, source: string, options) => { - try { - await importCommand({ - format, - source, - out: options.out, - write: options.write, - vendor: options.vendor, - quiet: options.quiet, - }); - } catch (err) { - console.error(chalk.red('Import failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); + command({ + name: 'import', + parameters: ['', ''], + flags: { + out: { type: String, alias: 'o', placeholder: '', description: 'Output manifest path (default: /agentplugins.imported.json)' }, + write: { type: Boolean, default: false, description: 'Install the generated manifest into the universal store' }, + noVendor: { type: Boolean, default: false, description: 'Skip copying upstream files into .agentplugins-vendor/' }, + quiet: { type: Boolean, alias: 'q', default: false, description: 'Suppress warning output' }, + }, + help: { description: 'Translate a community plugin (Claude Code, Codex, Skills.sh) into an AgentPlugins manifest' }, + }, async ({ _, flags }) => { + try { + await importCommand({ + format: _.format, + source: _.source, + out: flags.out, + write: flags.write, + vendor: !flags.noVendor, + quiet: flags.quiet, + }); + } catch (err) { + console.error('Import failed:', formatError(err)); + process.exit(1); + } + }), -cli - .command('audit ', 'Audit a plugin source for installability and supply-chain risk without writing to the store') - .option('--json', 'Output the report as JSON', { default: false }) - .option('--no-scripts', 'Skip lifecycle script policy evaluation') - .action(async (source: string, options) => { - try { - const code = await audit({ source, json: options.json, scripts: options.scripts }); - process.exit(code); - } catch (err) { - console.error(chalk.red('Audit failed:'), err instanceof Error ? err.message : String(err)); - process.exit(2); - } - }); + command({ + name: 'audit', + parameters: [''], + flags: { + json: { type: Boolean, default: false, description: 'Output the report as JSON' }, + noScripts: { type: Boolean, default: false, description: 'Skip lifecycle script policy evaluation' }, + }, + help: { description: 'Audit a plugin source for installability and supply-chain risk without writing to the store' }, + }, async ({ _, flags }) => { + try { + const code = await audit({ source: _.source, json: flags.json, scripts: !flags.noScripts }); + process.exit(code); + } catch (err) { + console.error('Audit failed:', formatError(err)); + process.exit(2); + } + }), -// ─── Codegen Commands ──────────────────────────────────────────────────────── + // ─── Codegen Commands ─────────────────────────────────────────────────── -cli - .command('build', 'Build plugin for target platforms') - .option('-t, --target ', 'Comma-separated target platforms (claude,codex,copilot,gemini,kimi,opencode,pimono)') - .option('-o, --out-dir ', 'Output directory', { default: 'dist' }) - .option('--strict', 'Fail on warnings', { default: false }) - .option('--config ', 'Config file path', { default: 'agentplugins.config.ts' }) - .action(async (options) => { - try { - const config = await loadConfig(options.config); - const targets = options.target - ? options.target.split(',').map((t: string) => t.trim()) - : undefined; + command({ + name: 'build', + flags: { + target: { type: String, alias: 't', placeholder: '', description: 'Comma-separated target platforms (claude,codex,copilot,gemini,kimi,opencode,pimono)' }, + outDir: { type: String, alias: 'o', placeholder: '', default: 'dist', description: 'Output directory' }, + strict: { type: Boolean, default: false, description: 'Fail on warnings' }, + config: { type: String, placeholder: '', default: 'agentplugins.config.ts', description: 'Config file path' }, + }, + help: { description: 'Build plugin for target platforms' }, + }, async ({ flags }) => { + try { + const cfg = await loadConfig(flags.config); + const targets = flags.target ? flags.target.split(',').map((t: string) => t.trim()) : undefined; + await build({ config: cfg, targets, outDir: flags.outDir, strict: flags.strict }); + } catch (err) { + console.error('Build failed:', formatError(err)); + process.exit(1); + } + }), - await build({ - config, - targets, - outDir: options.outDir, - strict: options.strict, - }); - } catch (err) { - console.error(chalk.red('Build failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); + command({ + name: 'validate', + flags: { + config: { type: String, placeholder: '', default: 'agentplugins.config.ts', description: 'Config file path' }, + target: { type: String, alias: 't', placeholder: '', description: 'Validate for specific targets only' }, + }, + help: { description: 'Validate plugin configuration' }, + }, async ({ flags }) => { + try { + const cfg = await loadConfig(flags.config); + const targets = flags.target ? flags.target.split(',').map((t: string) => t.trim()) : undefined; + await validate({ config: cfg, targets }); + } catch (err) { + console.error('Validation failed:', formatError(err)); + process.exit(1); + } + }), -cli - .command('validate', 'Validate plugin configuration') - .option('--config ', 'Config file path', { default: 'agentplugins.config.ts' }) - .option('-t, --target ', 'Validate for specific targets only') - .action(async (options) => { - try { - const config = await loadConfig(options.config); - const targets = options.target - ? options.target.split(',').map((t: string) => t.trim()) - : undefined; + command({ + name: 'init', + parameters: ['[name]'], + flags: { + yes: { type: Boolean, alias: 'y', description: 'Skip prompts and use defaults' }, + template: { type: String, alias: 't', placeholder: '', description: 'Template: minimal | logger | security-guard | formatter' }, + target: { type: String, placeholder: '', description: 'Target platforms (comma-separated)' }, + }, + help: { description: 'Scaffold a new AgentPlugins plugin' }, + }, async ({ _, flags }) => { + try { + await init({ name: _.name, yes: flags.yes || false, template: flags.template, target: flags.target }); + } catch (err) { + console.error('Init failed:', formatError(err)); + process.exit(1); + } + }), - await validate({ config, targets }); - } catch (err) { - console.error(chalk.red('Validation failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); + command({ + name: 'lint', + flags: { + config: { type: String, placeholder: '', default: 'agentplugins.config.ts', description: 'Config file path' }, + json: { type: Boolean, description: 'Output as JSON' }, + }, + help: { description: 'Static analysis of plugin manifest' }, + }, async ({ flags }) => { + try { + const cfg = await loadConfig(flags.config); + await lint({ config: cfg, json: flags.json || false }); + } catch (err) { + console.error('Lint failed:', formatError(err)); + process.exit(1); + } + }), -cli - .command('init [name]', 'Scaffold a new AgentPlugins plugin') - .option('-y, --yes', 'Skip prompts and use defaults') - .option('-t, --template ', 'Template: minimal | logger | security-guard | formatter') - .option('--target ', 'Target platforms (comma-separated)') - .action(async (name: string | undefined, options) => { - try { - await init({ - name, - yes: options.yes || false, - template: options.template, - target: options.target, - }); - } catch (err) { - console.error(chalk.red('Init failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); - -cli - .command('lint', 'Static analysis of plugin manifest') - .option('--config ', 'Config file path', { default: 'agentplugins.config.ts' }) - .option('--json', 'Output as JSON') - .action(async (options) => { - try { - const config = await loadConfig(options.config); - await lint({ config, json: options.json || false }); - } catch (err) { - console.error(chalk.red('Lint failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); - -cli - .command('preview', 'Preview compile output without writing to disk') - .option('--config ', 'Config file path', { default: 'agentplugins.config.ts' }) - .option('-t, --target ', 'Comma-separated target platforms') - .option('--diff', 'Show diff against existing dist/ output') - .action(async (options) => { - try { - const config = await loadConfig(options.config); - const targets = options.target - ? options.target.split(',').map((t: string) => t.trim()) - : undefined; - - await preview({ config, targets, diff: options.diff || false }); - } catch (err) { - console.error(chalk.red('Preview failed:'), err instanceof Error ? err.message : String(err)); - process.exit(1); - } - }); - -cli.help(); -cli.version(pkg.version); - -cli.parse(); + command({ + name: 'preview', + flags: { + config: { type: String, placeholder: '', default: 'agentplugins.config.ts', description: 'Config file path' }, + target: { type: String, alias: 't', placeholder: '', description: 'Comma-separated target platforms' }, + diff: { type: Boolean, description: 'Show diff against existing dist/ output' }, + }, + help: { description: 'Preview compile output without writing to disk' }, + }, async ({ flags }) => { + try { + const cfg = await loadConfig(flags.config); + const targets = flags.target ? flags.target.split(',').map((t: string) => t.trim()) : undefined; + await preview({ config: cfg, targets, diff: flags.diff || false }); + } catch (err) { + console.error('Preview failed:', formatError(err)); + process.exit(1); + } + }), + ], + help: { + description: 'Build AI agent plugins once, ship to any harness.', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40d3edf..66f3f01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,9 +12,6 @@ catalogs: cleye: specifier: ^1.3.0 version: 1.3.0 - jiti: - specifier: ^2.0.0 - version: 2.0.0 tsdown: specifier: ^0.22.3 version: 0.22.3 @@ -129,11 +126,11 @@ importers: specifier: workspace:* version: link:../core devDependencies: - tsup: - specifier: ^8.0.0 - version: 8.5.1(jiti@2.0.0)(postcss@8.5.15)(typescript@5.9.3) + tsdown: + specifier: 'catalog:' + version: 0.22.3(typescript@5.9.3) typescript: - specifier: ^5.3.0 + specifier: 'catalog:' version: 5.9.3 packages/adapter-kimi: @@ -142,15 +139,15 @@ importers: specifier: workspace:* version: link:../core devDependencies: - tsup: - specifier: ^8.0.0 - version: 8.5.1(jiti@2.0.0)(postcss@8.5.15)(typescript@5.9.3) + tsdown: + specifier: 'catalog:' + version: 0.22.3(typescript@5.9.3) typescript: - specifier: ^5.3.0 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^1.0.0 - version: 1.6.1(@types/node@22.20.0) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/adapter-opencode: dependencies: @@ -166,7 +163,7 @@ importers: devDependencies: tsup: specifier: ^8.0.0 - version: 8.5.1(jiti@2.0.0)(postcss@8.5.15)(typescript@5.9.3) + version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) typescript: specifier: 'catalog:' version: 5.9.3 @@ -183,15 +180,15 @@ importers: specifier: workspace:* version: link:../core devDependencies: - tsup: - specifier: ^8.0.0 - version: 8.5.1(jiti@2.0.0)(postcss@8.5.15)(typescript@5.9.3) + tsdown: + specifier: 'catalog:' + version: 0.22.3(typescript@5.9.3) typescript: - specifier: ^5.4.0 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^1.0.0 - version: 1.6.1(@types/node@22.20.0) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/cli: dependencies: @@ -240,15 +237,15 @@ importers: '@clack/prompts': specifier: ^0.7.0 version: 0.7.0 - '@logtape/logtape': - specifier: ^0.4.0 - version: 0.4.0 + chalk: + specifier: ^5.3.0 + version: 5.6.2 cleye: specifier: 'catalog:' version: 1.3.0 jiti: - specifier: 'catalog:' - version: 2.0.0 + specifier: ^1.21.6 + version: 1.21.7 devDependencies: '@types/node': specifier: ^20.0.0 @@ -284,9 +281,6 @@ importers: zod: specifier: 'catalog:' version: 4.4.3 - zod-to-json-schema: - specifier: ^3.24.5 - version: 3.25.2(zod@4.4.3) devDependencies: '@types/node': specifier: 'catalog:' @@ -362,9 +356,6 @@ importers: zod: specifier: 'catalog:' version: 4.4.3 - zod-to-json-schema: - specifier: ^3.25.2 - version: 3.25.2(zod@4.4.3) devDependencies: '@types/node': specifier: 'catalog:' @@ -1041,9 +1032,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@logtape/logtape@0.4.0': - resolution: {integrity: sha512-9T65HMtVoOE/2oyYaZII8gQ7Svw+PeGHnuBXZgiS6WDITGpunZ7cu9HHle1JSgwygYB/LdUwC9EN67Lhejd6Bg==} - '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -1786,6 +1774,10 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} 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==} @@ -2467,8 +2459,8 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jiti@2.0.0: - resolution: {integrity: sha512-CJ7e7Abb779OTRv3lomfp7Mns/Sy1+U4pcAx5VbjxCZD5ZM/VJaXPpPjNKjtSvWQy/H86E49REXR34dl1JEz9w==} + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true jose@6.2.3: @@ -4128,8 +4120,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@logtape/logtape@0.4.0': {} - '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.29.7 @@ -4887,6 +4877,8 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chalk@5.6.2: {} + character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -5598,7 +5590,7 @@ snapshots: isexe@2.0.0: {} - jiti@2.0.0: {} + jiti@1.21.7: {} jose@6.2.3: {} @@ -6080,11 +6072,11 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 - postcss-load-config@6.0.1(jiti@2.0.0)(postcss@8.5.15): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.15): dependencies: lilconfig: 3.1.3 optionalDependencies: - jiti: 2.0.0 + jiti: 1.21.7 postcss: 8.5.15 postcss@8.5.15: @@ -6532,7 +6524,7 @@ snapshots: tslib@2.8.1: optional: true - tsup@8.5.1(jiti@2.0.0)(postcss@8.5.15)(typescript@5.9.3): + tsup@8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 @@ -6543,7 +6535,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.0.0)(postcss@8.5.15) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.15) resolve-from: 5.0.0 rollup: 4.61.1 source-map: 0.7.6 From 2dbd436d14c54150ee81a235442d4d644010500a Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:58:25 +0200 Subject: [PATCH 67/90] feat(cli): migrate from chalk to logtape (structured logging) --- packages/cli/package.json | 8 +-- packages/cli/src/cli.ts | 32 +++++---- packages/cli/src/commands/add.ts | 53 ++++++++------- packages/cli/src/commands/audit.ts | 98 +++++++++++++++------------ packages/cli/src/commands/build.ts | 67 +++++++++++------- packages/cli/src/commands/doctor.ts | 65 +++++++++++------- packages/cli/src/commands/import.ts | 76 +++++++++++++++------ packages/cli/src/commands/info.ts | 56 ++++++++------- packages/cli/src/commands/init.ts | 22 +++--- packages/cli/src/commands/lint.ts | 37 +++++----- packages/cli/src/commands/list.ts | 28 ++++---- packages/cli/src/commands/preview.ts | 47 +++++++------ packages/cli/src/commands/remove.ts | 22 +++--- packages/cli/src/commands/setup.ts | 40 ++++++----- packages/cli/src/commands/update.ts | 40 ++++++----- packages/cli/src/commands/validate.ts | 36 ++++++---- packages/cli/src/logger.ts | 20 ++++++ pnpm-lock.yaml | 92 ++++--------------------- 18 files changed, 462 insertions(+), 377 deletions(-) create mode 100644 packages/cli/src/logger.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index e4801da..3d6fde7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -36,14 +36,14 @@ "@agentplugins/adapter-opencode": "workspace:*", "@agentplugins/adapter-pimono": "workspace:*", "@clack/prompts": "^0.7.0", - "chalk": "^5.3.0", + "@logtape/logtape": "^0.4.0", "cleye": "catalog:", "jiti": "^1.21.6" }, "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "^5.5.0", - "vitest": "^3.1.4" + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" }, "keywords": [ "agentplugins", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 39d72c9..76244b8 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -24,6 +24,7 @@ import { cli, command } from 'cleye'; import pkg from '../package.json' with { type: 'json' }; +import { setupLogger, getCliLogger } from './logger.js'; import { build } from './commands/build.js'; import { validate } from './commands/validate.js'; import { init } from './commands/init.js'; @@ -40,6 +41,9 @@ import { importCommand } from './commands/import.js'; import { audit } from './commands/audit.js'; import { loadConfig } from './config.js'; +await setupLogger(); +const logger = getCliLogger(); + function formatError(err: unknown): string { return err instanceof Error ? err.message : String(err); } @@ -62,7 +66,7 @@ cli({ try { await add({ source: _.source, yes: flags.yes, noSetup: flags.noSetup }); } catch (err) { - console.error('Add failed:', formatError(err)); + logger.error('Add failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -79,7 +83,7 @@ cli({ try { await setup({ name: _.name, force: flags.force, yes: flags.yes }); } catch (err) { - console.error('Setup failed:', formatError(err)); + logger.error('Setup failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -95,7 +99,7 @@ cli({ try { await remove({ name: _.name, force: flags.force }); } catch (err) { - console.error('Remove failed:', formatError(err)); + logger.error('Remove failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -110,7 +114,7 @@ cli({ try { await list({ json: flags.json }); } catch (err) { - console.error('List failed:', formatError(err)); + logger.error('List failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -126,7 +130,7 @@ cli({ try { await update({ name: _.name, all: flags.all }); } catch (err) { - console.error('Update failed:', formatError(err)); + logger.error('Update failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -142,7 +146,7 @@ cli({ try { await info({ name: _.name, json: flags.json }); } catch (err) { - console.error('Info failed:', formatError(err)); + logger.error('Info failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -157,7 +161,7 @@ cli({ try { await doctor({ json: flags.json }); } catch (err) { - console.error('Doctor failed:', formatError(err)); + logger.error('Doctor failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -183,7 +187,7 @@ cli({ quiet: flags.quiet, }); } catch (err) { - console.error('Import failed:', formatError(err)); + logger.error('Import failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -201,7 +205,7 @@ cli({ const code = await audit({ source: _.source, json: flags.json, scripts: !flags.noScripts }); process.exit(code); } catch (err) { - console.error('Audit failed:', formatError(err)); + logger.error('Audit failed: {msg}', { msg: formatError(err) }); process.exit(2); } }), @@ -223,7 +227,7 @@ cli({ const targets = flags.target ? flags.target.split(',').map((t: string) => t.trim()) : undefined; await build({ config: cfg, targets, outDir: flags.outDir, strict: flags.strict }); } catch (err) { - console.error('Build failed:', formatError(err)); + logger.error('Build failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -241,7 +245,7 @@ cli({ const targets = flags.target ? flags.target.split(',').map((t: string) => t.trim()) : undefined; await validate({ config: cfg, targets }); } catch (err) { - console.error('Validation failed:', formatError(err)); + logger.error('Validation failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -259,7 +263,7 @@ cli({ try { await init({ name: _.name, yes: flags.yes || false, template: flags.template, target: flags.target }); } catch (err) { - console.error('Init failed:', formatError(err)); + logger.error('Init failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -276,7 +280,7 @@ cli({ const cfg = await loadConfig(flags.config); await lint({ config: cfg, json: flags.json || false }); } catch (err) { - console.error('Lint failed:', formatError(err)); + logger.error('Lint failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), @@ -295,7 +299,7 @@ cli({ const targets = flags.target ? flags.target.split(',').map((t: string) => t.trim()) : undefined; await preview({ config: cfg, targets, diff: flags.diff || false }); } catch (err) { - console.error('Preview failed:', formatError(err)); + logger.error('Preview failed: {msg}', { msg: formatError(err) }); process.exit(1); } }), diff --git a/packages/cli/src/commands/add.ts b/packages/cli/src/commands/add.ts index 19cfed0..a0b2be2 100644 --- a/packages/cli/src/commands/add.ts +++ b/packages/cli/src/commands/add.ts @@ -5,7 +5,6 @@ * and symlinks it into every detected agent harness. */ -import chalk from 'chalk'; import { initStore, normalizeSource, @@ -24,8 +23,11 @@ import { existsSync, rmSync } from 'node:fs'; import { compile } from './build.js'; import { runSetupFlow } from './setup.js'; import { createApp, createInstallCtx, AbortError } from '@agentplugins/pipeline'; +import { getCliLogger } from '../logger.js'; import type { PluginManifest } from '@agentplugins/core'; +const logger = getCliLogger(); + export interface AddOptions { source: string; yes?: boolean; @@ -38,9 +40,9 @@ export async function add(options: AddOptions): Promise { const source = normalizeSource(options.source); const repoName = extractRepoName(source); - console.log(chalk.bold('\n📥 AgentPlugins Add\n')); - console.log(chalk.gray(`Source: ${source}`)); - console.log(chalk.gray(`Store: ${getStorePath()}\n`)); + logger.info('\n📥 AgentPlugins Add\n'); + logger.info('Source: {source}', { source }); + logger.info('Store: {store}\n', { store: getStorePath() }); initStore(); @@ -48,17 +50,17 @@ export async function add(options: AddOptions): Promise { const tempDir = join(getStorePath(), `.tmp-${repoName}-${Date.now()}`); if (existsSync(tempDir)) rmSync(tempDir, { recursive: true, force: true }); - console.log(chalk.blue('Cloning repository...')); + logger.info('Cloning repository...'); let commit: string; try { commit = cloneRepo(source, tempDir, branch); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - console.error(chalk.red(`\nFailed to clone: ${msg}`)); + logger.error('\nFailed to clone: {msg}', { msg }); rmSync(tempDir, { recursive: true, force: true }); process.exit(1); } - console.log(chalk.gray(`Commit: ${commit}`)); + logger.info('Commit: {commit}', { commit }); // Resolve subdir — for monorepo tree URLs, look inside the subdirectory const pluginDir = subdir ? join(tempDir, subdir) : tempDir; @@ -72,8 +74,8 @@ export async function add(options: AddOptions): Promise { } if (!manifestResult) { - console.error(chalk.red('\nNo plugin manifest found in repository.')); - console.error(chalk.gray('Expected one of: agentplugins.config.ts, agentplugins.config.json, manifest.json, package.json, SKILL.md')); + logger.error('\nNo plugin manifest found in repository.'); + logger.error('Expected one of: agentplugins.config.ts, agentplugins.config.json, manifest.json, package.json, SKILL.md'); rmSync(tempDir, { recursive: true, force: true }); process.exit(1); } @@ -83,8 +85,8 @@ export async function add(options: AddOptions): Promise { const name = rawName.replace(/^@[^/]+\//, ''); const version = (manifestResult.manifest['version'] as string) || '0.0.0'; - console.log(chalk.cyan(`\nPlugin: ${name} v${version}`)); - console.log(chalk.gray(`Manifest: ${manifestResult.path} (${manifestResult.type})`)); + logger.info('\nPlugin: {name} v{version}', { name, version }); + logger.info('Manifest: {path} ({type})', { path: manifestResult.path, type: manifestResult.type }); // Security: run pinned integrity check + script policy via pipeline const installApp = createApp().use(securityPlugin); @@ -98,7 +100,7 @@ export async function add(options: AddOptions): Promise { await installApp.runInstall(installCtx); } catch (err) { if (err instanceof AbortError) { - console.error(chalk.red(`\n${err.message}`)); + logger.error('\n{msg}', { msg: err.message }); rmSync(tempDir, { recursive: true, force: true }); process.exit(1); } @@ -108,17 +110,21 @@ export async function add(options: AddOptions): Promise { // Detect agents const agents = getDetectedAgents(); if (agents.length === 0) { - console.log(chalk.yellow('\n⚠ No agent harnesses detected. Plugin will be stored but not symlinked.')); - console.log(chalk.gray('Install Claude, Codex, or another supported agent to enable symlinking.')); + logger.warn('\n⚠ No agent harnesses detected. Plugin will be stored but not symlinked.'); + logger.info('Install Claude, Codex, or another supported agent to enable symlinking.'); } else { - console.log(chalk.gray(`Detected ${agents.length} agent${agents.length > 1 ? 's' : ''}: ${agents.map((a) => a.displayName).join(', ')}`)); + logger.info('Detected {count} agent{plural}: {agents}', { + count: agents.length, + plural: agents.length > 1 ? 's' : '', + agents: agents.map((a) => a.displayName).join(', '), + }); } // Compile for harnesses that load compiled artifacts (opencode, pimono) const compilableAgents = agents.filter((a) => a.pluginPath); if (compilableAgents.length > 0) { const targets = compilableAgents.map((a) => a.name); - console.log(chalk.blue(`\nCompiling for ${targets.join(', ')}...`)); + logger.info('\nCompiling for {targets}...', { targets: targets.join(', ') }); const distDir = join(pluginDir, '.agentplugins-dist'); try { await compile({ @@ -131,8 +137,8 @@ export async function add(options: AddOptions): Promise { }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - console.log(chalk.yellow(`\n⚠ Compilation failed: ${msg}`)); - console.log(chalk.gray('Plugin will be installed without compiled artifacts.')); + logger.warn('\n⚠ Compilation failed: {msg}', { msg }); + logger.info('Plugin will be installed without compiled artifacts.'); } } @@ -147,13 +153,13 @@ export async function add(options: AddOptions): Promise { }); // Summary - console.log(chalk.green(`\n✅ Installed ${name} v${version}`)); - console.log(chalk.gray(` Store: ${getStorePath()}/${name}`)); + logger.info('\n✅ Installed {name} v{version}', { name, version }); + logger.info(' Store: {store}', { store: `${getStorePath()}/${name}` }); if (result.symlinks.length > 0) { - console.log(chalk.gray('\nInstalled to:')); + logger.info('\nInstalled to:'); for (const s of result.symlinks) { - console.log(chalk.gray(` ${s.agentDisplayName}: ${s.linkPath}`)); + logger.info(' {agent}: {path}', { agent: s.agentDisplayName, path: s.linkPath }); } } @@ -165,7 +171,7 @@ export async function add(options: AddOptions): Promise { noSetup: options.noSetup, }); - console.log(); + logger.info(''); } /** Try loading a TypeScript config via jiti */ @@ -194,4 +200,3 @@ async function tryTsConfig(dir: string): Promise<{ path: string; manifest: Recor } return null; } - diff --git a/packages/cli/src/commands/audit.ts b/packages/cli/src/commands/audit.ts index c27f15f..21588cb 100644 --- a/packages/cli/src/commands/audit.ts +++ b/packages/cli/src/commands/audit.ts @@ -22,7 +22,6 @@ import { resolve, join, basename } from 'node:path'; import { existsSync, statSync, readFileSync } from 'node:fs'; -import chalk from 'chalk'; import { isValidManifest } from '@agentplugins/schema'; import { hashDirectory, @@ -33,6 +32,9 @@ import { evaluateScriptPolicy, DEFAULT_POLICY, } from '@agentplugins/security'; +import { getCliLogger } from '../logger.js'; + +const logger = getCliLogger(); export interface AuditOptions { source: string; @@ -107,10 +109,10 @@ export async function audit(options: AuditOptions): Promise { const resolved = resolveSource(source); if (resolved.kind === 'github-url') { - console.error(chalk.yellow('Note: GitHub URL auditing is best-effort in v0.3.0. Clone the repo locally for full checks.')); + logger.warn('Note: GitHub URL auditing is best-effort in v0.3.0. Clone the repo locally for full checks.'); } if (resolved.kind === 'npm-spec') { - console.error(chalk.yellow('Note: npm spec auditing in v0.3.0 only checks provenance + README. Clone the tarball locally for full checks.')); + logger.warn('Note: npm spec auditing in v0.3.0 only checks provenance + README. Clone the tarball locally for full checks.'); } const localPath = resolved.path; @@ -292,75 +294,83 @@ function readManifest(localPath: string): ManifestReadResult | null { } function printHuman(report: AuditReport): void { - const tag = report.summary.verdict === 'pass' ? chalk.green('PASS') : report.summary.verdict === 'warn' ? chalk.yellow('WARN') : chalk.red('FAIL'); - console.log(chalk.bold(`\n🔍 AgentPlugins Audit — ${tag}\n`)); - console.log(chalk.gray(`Source: ${report.source}`)); - console.log(chalk.gray(`Kind: ${report.resolved.kind}`)); - if (report.resolved.repo) console.log(chalk.gray(`Repo: ${report.resolved.repo}`)); - if (report.resolved.spec) console.log(chalk.gray(`Spec: ${report.resolved.spec}`)); - - console.log(chalk.bold('\n📋 Manifest')); - if (report.manifest?.name) console.log(` Name: ${report.manifest.name}`); - if (report.manifest?.version) console.log(` Version: ${report.manifest.version}`); - console.log(` Schema: ${report.schema.valid ? chalk.green('valid') : chalk.red('invalid')}`); - - console.log(chalk.bold('\n🔐 Integrity')); - console.log(` Actual: ${report.integrity.actual.slice(0, 20)}... (${report.integrity.files} files, ${report.integrity.bytes} bytes)`); + const tag = report.summary.verdict.toUpperCase(); + logger.info('\n🔍 AgentPlugins Audit — {tag}\n', { tag }); + logger.info('Source: {source}', { source: report.source }); + logger.info('Kind: {kind}', { kind: report.resolved.kind }); + if (report.resolved.repo) logger.info('Repo: {repo}', { repo: report.resolved.repo }); + if (report.resolved.spec) logger.info('Spec: {spec}', { spec: report.resolved.spec }); + + logger.info('\n📋 Manifest'); + if (report.manifest?.name) logger.info(' Name: {name}', { name: report.manifest.name as string }); + if (report.manifest?.version) logger.info(' Version: {version}', { version: report.manifest.version as string }); + logger.info(' Schema: {status}', { status: report.schema.valid ? 'valid' : 'invalid' }); + + logger.info('\n🔐 Integrity'); + logger.info(' Actual: {hash}... ({files} files, {bytes} bytes)', { + hash: report.integrity.actual.slice(0, 20), + files: report.integrity.files, + bytes: report.integrity.bytes, + }); if (report.integrity.expected) { - console.log(` Expected: ${report.integrity.expected.slice(0, 20)}...`); - console.log(` Match: ${report.integrity.matches ? chalk.green('yes') : chalk.red('NO')}`); + logger.info(' Expected: {hash}...', { hash: report.integrity.expected.slice(0, 20) }); + logger.info(' Match: {match}', { match: report.integrity.matches ? 'yes' : 'NO' }); } else { - console.log(chalk.gray(' No pinned integrity.')); + logger.info(' No pinned integrity.'); } if (report.scorecard) { - console.log(chalk.bold('\n📊 OpenSSF Scorecard')); + logger.info('\n📊 OpenSSF Scorecard'); if (report.scorecard.skipped) { - console.log(chalk.gray(` ${report.scorecard.note}`)); + logger.info(' {note}', { note: report.scorecard.note }); } else if (report.scorecard.score !== null) { - const color = report.scorecard.score >= 7 ? chalk.green : report.scorecard.score >= 5 ? chalk.yellow : chalk.red; - console.log(` Score: ${color(report.scorecard.score.toFixed(1) + ' / 10')}`); + logger.info(' Score: {score} / 10', { score: report.scorecard.score.toFixed(1) }); } } - console.log(chalk.bold('\n🛡 OSV')); + logger.info('\n🛡 OSV'); if (report.osv?.skipped) { - console.log(chalk.gray(` ${report.osv.note}`)); + logger.info(' {note}', { note: report.osv.note }); } else if (report.osv) { - console.log(` Findings: ${report.osv.findings.length}`); - if (report.osv.hasCriticalOrHigh) console.log(chalk.red(' ⚠ CRITICAL or HIGH findings present')); + logger.info(' Findings: {count}', { count: report.osv.findings.length }); + if (report.osv.hasCriticalOrHigh) logger.error(' ⚠ CRITICAL or HIGH findings present'); for (const f of report.osv.findings.slice(0, 5)) { - console.log(` - [${f.severity}] ${f.package}: ${f.advisory}`); + logger.info(' - [{severity}] {package}: {advisory}', { severity: f.severity, package: f.package, advisory: f.advisory }); } } if (report.provenance) { - console.log(chalk.bold('\n🔏 npm provenance')); + logger.info('\n🔏 npm provenance'); if (report.provenance.skipped) { - console.log(chalk.gray(` ${report.provenance.note}`)); + logger.info(' {note}', { note: report.provenance.note }); } else { - console.log(` Signed: ${report.provenance.signed === true ? chalk.green('yes') : report.provenance.signed === false ? chalk.red('NO') : chalk.gray('unknown')}`); + const signed = report.provenance.signed === true ? 'yes' : report.provenance.signed === false ? 'NO' : 'unknown'; + logger.info(' Signed: {signed}', { signed }); } } if (report.scripts) { - console.log(chalk.bold('\n📜 Lifecycle scripts')); - console.log(` Total: ${report.scripts.decisions.length}`); - console.log(` Denied: ${chalk.red(String(report.scripts.deniedCount))}`); - console.log(` Review: ${chalk.yellow(String(report.scripts.reviewCount))}`); + logger.info('\n📜 Lifecycle scripts'); + logger.info(' Total: {count}', { count: report.scripts.decisions.length }); + logger.info(' Denied: {count}', { count: report.scripts.deniedCount }); + logger.info(' Review: {count}', { count: report.scripts.reviewCount }); for (const d of report.scripts.decisions.slice(0, 5)) { - const color = d.decision === 'deny' ? chalk.red : d.decision === 'require-review' ? chalk.yellow : chalk.green; - console.log(` - [${color(d.decision)}] ${d.dependency} (${d.phase}): ${d.command.slice(0, 60)}`); + logger.info(' - [{decision}] {dependency} ({phase}): {command}', { + decision: d.decision, + dependency: d.dependency, + phase: d.phase, + command: d.command.slice(0, 60), + }); } } - console.log(chalk.bold(`\n📊 Summary`)); - console.log(` Errors: ${report.summary.errors}`); - console.log(` Warnings: ${report.summary.warnings}`); - console.log(` Notes: ${report.summary.notes}`); - console.log(` Verdict: ${tag}\n`); + logger.info('\n📊 Summary'); + logger.info(' Errors: {count}', { count: report.summary.errors }); + logger.info(' Warnings: {count}', { count: report.summary.warnings }); + logger.info(' Notes: {count}', { count: report.summary.notes }); + logger.info(' Verdict: {tag}\n', { tag }); } function printError(msg: string): void { - console.error(chalk.red(`\n✗ ${msg}\n`)); + logger.error('\n✗ {msg}\n', { msg }); } diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index 02aa707..42a846b 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -6,7 +6,6 @@ import { resolve, join } from 'node:path'; import { mkdir, readFile, writeFile, rm } from 'node:fs/promises'; -import chalk from 'chalk'; import { validateUniversal, validateForPlatform, @@ -17,9 +16,12 @@ import { } from '@agentplugins/core'; import { sanitizeJoin, lint, registerEmitter, type LintIssue } from '@agentplugins/compile'; import { createApp, createBuildCtx, createTargetCtx, AbortError } from '@agentplugins/pipeline'; +import { getCliLogger } from '../logger.js'; import type { App, Plugin } from '@agentplugins/pipeline'; import type { LoadedConfig } from '../config.js'; +const logger = getCliLogger(); + // ─── Target resolution ──────────────────────────────────────────────────────── function resolveTargets( @@ -136,13 +138,13 @@ export async function compile(options: CompileOptions): Promise continue; } - if (!silent) console.log(chalk.blue(`\n📦 Building for ${target}...`)); + if (!silent) logger.info('\n📦 Building for {target}...', { target }); const platformIssues = validateForPlatform(manifest, target); const platformErrors = platformIssues.filter(i => i.severity === 'error'); if (platformErrors.length > 0) { const msg = `${platformErrors.length} validation error${platformErrors.length > 1 ? 's' : ''}`; - if (!silent) console.log(chalk.red(` ✗ Build failed for ${target} (${msg})`)); + if (!silent) logger.error(' ✗ Build failed for {target} ({msg})', { target, msg }); results.push({ target, files: [], warnings: [], skipped: true, error: msg }); continue; } @@ -184,12 +186,15 @@ export async function compile(options: CompileOptions): Promise } if (!silent) { - console.log(chalk.green(` ✓ Built ${targetCtx.files.length} file${targetCtx.files.length > 1 ? 's' : ''}`)); + logger.info(' ✓ Built {count} file{plural}', { + count: targetCtx.files.length, + plural: targetCtx.files.length > 1 ? 's' : '', + }); if (targetCtx.warnings.length > 0) { - for (const w of targetCtx.warnings) console.log(chalk.yellow(` ⚠ ${w}`)); + for (const w of targetCtx.warnings) logger.warn(' ⚠ {warning}', { warning: w }); } if (targetCtx.postInstall.length > 0) { - console.log(chalk.cyan(` ⓘ ${targetCtx.postInstall.join('\n ⓘ ')}`)); + logger.info(' ⓘ {steps}', { steps: targetCtx.postInstall.join('\n ⓘ ') }); } } @@ -203,7 +208,7 @@ export async function compile(options: CompileOptions): Promise } catch (err) { if (err instanceof AbortError) throw err; const msg = err instanceof Error ? err.message : String(err); - if (!silent) console.log(chalk.red(` ✗ Build failed for ${target}: ${msg}`)); + if (!silent) logger.error(' ✗ Build failed for {target}: {msg}', { target, msg }); results.push({ target, files: [], warnings: [], skipped: true, error: msg }); } } @@ -230,17 +235,17 @@ export async function build(options: BuildOptions): Promise { manifest.targets ); - console.log(chalk.bold('\n🌉 AgentPlugins Build\n')); - console.log(chalk.gray(`Plugin: ${manifest.name} v${manifest.version}`)); - console.log(chalk.gray(`Targets: ${targetList.join(', ')}`)); - console.log(chalk.gray(`Output: ${resolve(outDir)}\n`)); + logger.info('\n🌉 AgentPlugins Build\n'); + logger.info('Plugin: {name} v{version}', { name: manifest.name, version: manifest.version }); + logger.info('Targets: {targets}', { targets: targetList.join(', ') }); + logger.info('Output: {out}\n', { out: resolve(outDir) }); // Build the pipeline app once — reused for validation, lint, and compile const app = await buildApp(config.plugins ?? []); const knownTargets = [...ALL_TARGETS, ...app.adapters.keys()]; // Universal validation — custom adapter targets are not spuriously warned - console.log(chalk.blue('🔍 Running universal validation...')); + logger.info('🔍 Running universal validation...'); const universalIssues = validateUniversal(manifest, { knownTargets }); printIssues(universalIssues); const hasErrors = universalIssues.some(i => i.severity === 'error'); @@ -249,7 +254,7 @@ export async function build(options: BuildOptions): Promise { } // Lint — includes any lint rules from defineConfig plugins - console.log(chalk.blue('🔍 Running lint...')); + logger.info('🔍 Running lint...'); const inlineSources = await collectInlineSources(manifest, config.root); const lintIssues = lint({ manifest, inlineHandlerSource: inlineSources, extraRules: [...app.lintRules] }); printLintIssues(lintIssues); @@ -278,36 +283,46 @@ export async function build(options: BuildOptions): Promise { } // Summary - console.log(chalk.bold('\n✅ Build complete!\n')); - console.log(chalk.gray('Install your plugins:')); + logger.info('\n✅ Build complete!\n'); + logger.info('Install your plugins:'); for (const r of results) { if (r.skipped) continue; const cmd = getInstallCommand(r.target, manifest.name); - console.log(chalk.gray(` ${r.target}: ${cmd}`)); + logger.info(' {target}: {cmd}', { target: r.target, cmd }); } - console.log(); + logger.info(''); } function printLintIssues(issues: LintIssue[]): void { for (const issue of issues) { - const color = issue.severity === 'error' ? chalk.red : chalk.yellow; - const icon = issue.severity === 'error' ? '✗' : '⚠'; - const field = issue.field ? chalk.gray(`[${issue.field}] `) : ''; - console.log(color(` ${icon} ${field}${issue.message}`)); + const field = issue.field ? ` [${issue.field}]` : ''; + const rule = ` (${issue.rule})`; + const message = ` ${issue.severity === 'error' ? '✗' : '⚠'} ${issue.message}${field}${rule}`; + if (issue.severity === 'error') { + logger.error(message); + } else { + logger.warn(message); + } if (issue.suggestion) { - console.log(chalk.cyan(` → ${issue.suggestion}`)); + logger.info(' → {suggestion}', { suggestion: issue.suggestion }); } } } function printIssues(issues: Array<{ severity: string; field?: string; message: string; suggestion?: string }>): void { for (const issue of issues) { - const color = issue.severity === 'error' ? chalk.red : issue.severity === 'warning' ? chalk.yellow : chalk.gray; const icon = issue.severity === 'error' ? '✗' : issue.severity === 'warning' ? '⚠' : 'ℹ'; - const field = issue.field ? chalk.gray(`[${issue.field}] `) : ''; - console.log(color(` ${icon} ${field}${issue.message}`)); + const field = issue.field ? `[${issue.field}] ` : ''; + const message = ` ${icon} ${field}${issue.message}`; + if (issue.severity === 'error') { + logger.error(message); + } else if (issue.severity === 'warning') { + logger.warn(message); + } else { + logger.info(message); + } if (issue.suggestion) { - console.log(chalk.cyan(` → ${issue.suggestion}`)); + logger.info(' → {suggestion}', { suggestion: issue.suggestion }); } } } diff --git a/packages/cli/src/commands/doctor.ts b/packages/cli/src/commands/doctor.ts index 13686cc..4dfcd59 100644 --- a/packages/cli/src/commands/doctor.ts +++ b/packages/cli/src/commands/doctor.ts @@ -4,8 +4,10 @@ * Runs diagnostics on the store, detected agents, and symlinks. */ -import chalk from 'chalk'; import { runDoctor } from '@agentplugins/core'; +import { getCliLogger } from '../logger.js'; + +const logger = getCliLogger(); export interface DoctorOptions { json?: boolean; @@ -19,56 +21,73 @@ export async function doctor(options: DoctorOptions): Promise { return; } - console.log(chalk.bold('\n🩺 AgentPlugins Doctor\n')); + logger.info('\n🩺 AgentPlugins Doctor\n'); // Store - console.log(chalk.cyan('Store')); - console.log(chalk.gray(` Path: ${result.storePath}`)); - console.log(chalk.gray(` Exists: ${result.storeExists ? chalk.green('yes') : chalk.red('no')}`)); - console.log(chalk.gray(` Skills: ${result.skillsCompatPath}`)); - console.log(chalk.gray(` Skills exists: ${result.skillsCompatExists ? chalk.green('yes') : chalk.red('no')}`)); + logger.info('Store'); + logger.info(' Path: {path}', { path: result.storePath }); + logger.info(' Exists: {exists}', { exists: result.storeExists ? 'yes' : 'no' }); + logger.info(' Skills: {path}', { path: result.skillsCompatPath }); + logger.info(' Skills exists: {exists}', { exists: result.skillsCompatExists ? 'yes' : 'no' }); // Agents - console.log(chalk.cyan('\nAgents')); + logger.info('\nAgents'); const detected = result.agents.filter((a) => a.binaryFound || a.skillPathExists); if (detected.length === 0) { - console.log(chalk.yellow(' No agents detected.')); + logger.warn(' No agents detected.'); } for (const agent of result.agents) { const detectedHere = agent.binaryFound || agent.skillPathExists; - const status = detectedHere ? chalk.green('✓') : chalk.gray('○'); - const binStatus = agent.binaryFound ? chalk.green('found') : chalk.gray('not found'); - const pathStatus = agent.skillPathExists ? chalk.green('exists') : chalk.gray('missing'); - console.log(chalk.gray(` ${status} ${agent.displayName.padEnd(22)} binary: ${binStatus} path: ${pathStatus}`)); + const status = detectedHere ? '✓' : '○'; + const binStatus = agent.binaryFound ? 'found' : 'not found'; + const pathStatus = agent.skillPathExists ? 'exists' : 'missing'; + logger.info(' {status} {name} binary: {bin} path: {path}', { + status, + name: agent.displayName.padEnd(22), + bin: binStatus, + path: pathStatus, + }); } // Plugins - console.log(chalk.cyan(`\nPlugins (${result.plugins.length})`)); + logger.info('\nPlugins ({count})', { count: result.plugins.length }); if (result.plugins.length === 0) { - console.log(chalk.gray(' No plugins installed.')); + logger.info(' No plugins installed.'); } for (const plugin of result.plugins) { const linkCount = plugin.symlinks.length; const broken = plugin.symlinks.filter((s) => !s.valid).length; - const status = broken > 0 ? chalk.yellow('⚠') : chalk.green('✓'); - console.log(chalk.gray(` ${status} ${plugin.meta.name.padEnd(24)} v${plugin.meta.version} links: ${linkCount}${broken > 0 ? ` (${broken} broken)` : ''}`)); + const status = broken > 0 ? '⚠' : '✓'; + logger.info(' {status} {name} v{version} links: {links}{broken}', { + status, + name: plugin.meta.name.padEnd(24), + version: plugin.meta.version, + links: linkCount, + broken: broken > 0 ? ` (${broken} broken)` : '', + }); } // Issues if (result.issues.length > 0) { const errors = result.issues.filter((i) => i.level === 'error'); - console.log(chalk.cyan(`\nIssues (${result.issues.length})`)); + logger.info('\nIssues ({count})', { count: result.issues.length }); for (const issue of result.issues) { - const icon = issue.level === 'error' ? chalk.red('✗') : issue.level === 'warning' ? chalk.yellow('⚠') : chalk.blue('ℹ'); - console.log(` ${icon} ${issue.message}`); + const icon = issue.level === 'error' ? '✗' : issue.level === 'warning' ? '⚠' : 'ℹ'; + if (issue.level === 'error') { + logger.error(' {icon} {message}', { icon, message: issue.message }); + } else if (issue.level === 'warning') { + logger.warn(' {icon} {message}', { icon, message: issue.message }); + } else { + logger.info(' {icon} {message}', { icon, message: issue.message }); + } } if (errors.length > 0) { - console.log(chalk.red(`\n${errors.length} error(s) found.`)); + logger.error('\n{count} error(s) found.', { count: errors.length }); } } else { - console.log(chalk.green('\n✅ All checks passed.')); + logger.info('\n✅ All checks passed.'); } - console.log(); + logger.info(''); } diff --git a/packages/cli/src/commands/import.ts b/packages/cli/src/commands/import.ts index 21415c2..df9aae6 100644 --- a/packages/cli/src/commands/import.ts +++ b/packages/cli/src/commands/import.ts @@ -11,11 +11,13 @@ import { resolve, join } from 'node:path'; import { existsSync, writeFileSync, copyFileSync, mkdirSync } from 'node:fs'; -import chalk from 'chalk'; import { ingest, type IngestFormat, type IngestResult } from '@agentplugins/ingest'; import { isValidManifest } from '@agentplugins/schema'; import { installPlugin, getStorePath } from '@agentplugins/core'; import { verifyIntegrity, evaluateManifestScripts } from '@agentplugins/security'; +import { getCliLogger } from '../logger.js'; + +const logger = getCliLogger(); export interface ImportOptions { format: string; @@ -31,31 +33,34 @@ const SUPPORTED_FORMATS: readonly IngestFormat[] = ['claude-code', 'codex', 'ski export async function importCommand(options: ImportOptions): Promise { const format = options.format as IngestFormat; if (!SUPPORTED_FORMATS.includes(format)) { - console.error(chalk.red(`Unknown format "${options.format}". Supported: ${SUPPORTED_FORMATS.join(', ')}`)); + logger.error('Unknown format "{format}". Supported: {supported}', { + format: options.format, + supported: SUPPORTED_FORMATS.join(', '), + }); process.exit(2); } const source = resolve(options.source); if (!existsSync(source)) { - console.error(chalk.red(`Source path does not exist: ${source}`)); + logger.error('Source path does not exist: {source}', { source }); process.exit(2); } - console.log(chalk.bold(`\n📥 Importing ${format} plugin from ${source}\n`)); + logger.info('\n📥 Importing {format} plugin from {source}\n', { format, source }); let result: IngestResult; try { result = ingest(format, source); } catch (err) { - console.error(chalk.red('Import failed:'), err instanceof Error ? err.message : String(err)); + logger.error('Import failed: {msg}', { msg: err instanceof Error ? err.message : String(err) }); process.exit(1); } // Schema-validate the synthesized manifest const valid = isValidManifest(result.manifest); if (!valid) { - console.error(chalk.yellow('⚠ The synthesized manifest does not validate against the AgentPlugins v1 schema.')); - console.error(chalk.gray(' The output will still be written so you can fix the gaps manually.')); + logger.warn('⚠ The synthesized manifest does not validate against the AgentPlugins v1 schema.'); + logger.info(' The output will still be written so you can fix the gaps manually.'); } // Default destination: /agentplugins.imported.json @@ -64,7 +69,7 @@ export async function importCommand(options: ImportOptions): Promise { : join(source, 'agentplugins.imported.json'); writeFileSync(outPath, JSON.stringify(result.manifest, null, 2) + '\n', 'utf-8'); - console.log(chalk.green(`✓ Manifest written to ${outPath}`)); + logger.info('✓ Manifest written to {path}', { path: outPath }); // Vendor upstream files into /.agentplugins-vendor/ if (options.vendor !== false && result.vendorFiles.length > 0) { @@ -79,16 +84,29 @@ export async function importCommand(options: ImportOptions): Promise { // Non-fatal: vendor is best-effort } } - console.log(chalk.gray(` Vendored ${result.vendorFiles.length} file${result.vendorFiles.length === 1 ? '' : 's'} into ${vendorRoot}`)); + logger.info(' Vendored {count} file{plural} into {root}', { + count: result.vendorFiles.length, + plural: result.vendorFiles.length === 1 ? '' : 's', + root: vendorRoot, + }); } // Print warnings to stderr (or stdout in quiet mode) if (!options.quiet && result.warnings.length > 0) { - console.error(chalk.yellow(`\n⚠ ${result.warnings.length} warning${result.warnings.length === 1 ? '' : 's'} from ingestor:`)); + logger.warn('\n⚠ {count} warning{plural} from ingestor:', { + count: result.warnings.length, + plural: result.warnings.length === 1 ? '' : 's', + }); for (const w of result.warnings) { - const tag = w.severity === 'error' ? chalk.red('[error]') : w.severity === 'warning' ? chalk.yellow('[warn]') : chalk.gray('[info]'); - console.error(` ${tag} ${w.code}: ${w.message}`); - if (w.suggestion) console.error(chalk.gray(` → ${w.suggestion}`)); + const tag = w.severity === 'error' ? '[error]' : w.severity === 'warning' ? '[warn]' : '[info]'; + if (w.severity === 'error') { + logger.error(' {tag} {code}: {message}', { tag, code: w.code, message: w.message }); + } else if (w.severity === 'warning') { + logger.warn(' {tag} {code}: {message}', { tag, code: w.code, message: w.message }); + } else { + logger.info(' {tag} {code}: {message}', { tag, code: w.code, message: w.message }); + } + if (w.suggestion) logger.info(' → {suggestion}', { suggestion: w.suggestion }); } } @@ -102,7 +120,7 @@ export async function importCommand(options: ImportOptions): Promise { if (integrity && integrity.length > 0) { const { match, reason } = verifyIntegrity(source, integrity); if (!match) { - console.error(chalk.red(`\nIntegrity check failed: ${reason}`)); + logger.error('\nIntegrity check failed: {reason}', { reason }); process.exit(1); } } @@ -111,15 +129,29 @@ export async function importCommand(options: ImportOptions): Promise { const scriptCheck = evaluateManifestScripts(result.manifest as Record, name); if (!scriptCheck.ok) { for (const issue of scriptCheck.issues) { - const tag = issue.decision === 'deny' ? chalk.red('[error]') : chalk.yellow('[review]'); - console.error(` ${tag} ${issue.dependency} (${issue.phase}): ${issue.command}`); - for (const r of issue.reasons) console.error(chalk.gray(` ${r}`)); + const tag = issue.decision === 'deny' ? '[error]' : '[review]'; + if (issue.decision === 'deny') { + logger.error(' {tag} {dependency} ({phase}): {command}', { + tag, + dependency: issue.dependency, + phase: issue.phase, + command: issue.command, + }); + } else { + logger.warn(' {tag} {dependency} ({phase}): {command}', { + tag, + dependency: issue.dependency, + phase: issue.phase, + command: issue.command, + }); + } + for (const r of issue.reasons) logger.info(' {reason}', { reason: r }); } - console.error(chalk.red('\nRefusing to install: lifecycle script policy violation')); + logger.error('\nRefusing to install: lifecycle script policy violation'); process.exit(1); } - console.log(chalk.blue(`\nInstalling into store at ${getStorePath()}/${name} ...`)); + logger.info('\nInstalling into store at {path}/{name} ...', { path: getStorePath(), name }); try { installPlugin(source, { source: `import:${format}:${source}`, @@ -128,12 +160,12 @@ export async function importCommand(options: ImportOptions): Promise { manifestPath: 'agentplugins.imported.json', version, }); - console.log(chalk.green(`✓ Installed ${name} v${version}`)); + logger.info('✓ Installed {name} v{version}', { name, version }); } catch (err) { - console.error(chalk.red('Install failed:'), err instanceof Error ? err.message : String(err)); + logger.error('Install failed: {msg}', { msg: err instanceof Error ? err.message : String(err) }); process.exit(1); } } else { - console.log(chalk.gray('\nRe-run with --write to install into the universal store.')); + logger.info('\nRe-run with --write to install into the universal store.'); } } diff --git a/packages/cli/src/commands/info.ts b/packages/cli/src/commands/info.ts index c7bb5df..fde7329 100644 --- a/packages/cli/src/commands/info.ts +++ b/packages/cli/src/commands/info.ts @@ -4,8 +4,10 @@ * Shows detailed information about an installed plugin. */ -import chalk from 'chalk'; import { getPluginInfo } from '@agentplugins/core'; +import { getCliLogger } from '../logger.js'; + +const logger = getCliLogger(); export interface InfoOptions { name: string; @@ -16,7 +18,7 @@ export async function info(options: InfoOptions): Promise { const plugin = getPluginInfo(options.name); if (!plugin) { - console.error(chalk.red(`Plugin "${options.name}" is not installed.`)); + logger.error('Plugin "{name}" is not installed.', { name: options.name }); process.exit(1); } @@ -38,17 +40,17 @@ export async function info(options: InfoOptions): Promise { const m = plugin.meta; - console.log(chalk.bold(`\nℹ️ ${m.name}\n`)); + logger.info('\nℹ️ {name}\n', { name: m.name }); - console.log(chalk.cyan(' Metadata')); - console.log(chalk.gray(` Name: ${m.name}`)); - console.log(chalk.gray(` Version: ${m.version}`)); - console.log(chalk.gray(` Source: ${m.source}`)); - console.log(chalk.gray(` Commit: ${m.commit}`)); - console.log(chalk.gray(` Installed: ${m.installedAt}`)); - console.log(chalk.gray(` Updated: ${m.updatedAt}`)); - console.log(chalk.gray(` Manifest: ${m.manifestPath}`)); - console.log(chalk.gray(` Store path: ${plugin.path}`)); + logger.info(' Metadata'); + logger.info(' Name: {name}', { name: m.name }); + logger.info(' Version: {version}', { version: m.version }); + logger.info(' Source: {source}', { source: m.source }); + logger.info(' Commit: {commit}', { commit: m.commit }); + logger.info(' Installed: {installed}', { installed: m.installedAt }); + logger.info(' Updated: {updated}', { updated: m.updatedAt }); + logger.info(' Manifest: {manifest}', { manifest: m.manifestPath }); + logger.info(' Store path: {path}', { path: plugin.path }); // Manifest details if (plugin.manifest) { @@ -60,37 +62,41 @@ export async function info(options: InfoOptions): Promise { const skills = manifest['skills']; const tools = manifest['tools']; - console.log(chalk.cyan('\n Manifest')); - if (description) console.log(chalk.gray(` Description: ${description}`)); + logger.info('\n Manifest'); + if (description) logger.info(' Description: {description}', { description }); if (author) { const authorStr = typeof author === 'string' ? author : (author as Record)?.name || ''; - if (authorStr) console.log(chalk.gray(` Author: ${authorStr}`)); + if (authorStr) logger.info(' Author: {author}', { author: authorStr }); } - if (license) console.log(chalk.gray(` License: ${license}`)); + if (license) logger.info(' License: {license}', { license }); if (hooks && typeof hooks === 'object') { const hookNames = Object.keys(hooks as Record); - console.log(chalk.gray(` Hooks: ${hookNames.join(', ')}`)); + logger.info(' Hooks: {hooks}', { hooks: hookNames.join(', ') }); } if (Array.isArray(skills)) { - console.log(chalk.gray(` Skills: ${skills.length}`)); + logger.info(' Skills: {count}', { count: skills.length }); } if (Array.isArray(tools)) { - console.log(chalk.gray(` Tools: ${tools.length}`)); + logger.info(' Tools: {count}', { count: tools.length }); } } // Symlinks if (plugin.symlinks.length > 0) { - console.log(chalk.cyan('\n Symlinks')); + logger.info('\n Symlinks'); for (const s of plugin.symlinks) { - const status = s.valid ? chalk.green('✓') : chalk.red('✗'); - console.log(chalk.gray(` ${status} ${s.agentDisplayName.padEnd(20)} ${s.linkPath}`)); + const status = s.valid ? '✓' : '✗'; + logger.info(' {status} {agent} {path}', { + status, + agent: s.agentDisplayName.padEnd(20), + path: s.linkPath, + }); } } else { - console.log(chalk.cyan('\n Symlinks')); - console.log(chalk.gray(' (none — no agent harnesses detected)')); + logger.info('\n Symlinks'); + logger.info(' (none — no agent harnesses detected)'); } - console.log(); + logger.info(''); } diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 9692ec4..b9c3dae 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -7,8 +7,10 @@ import { resolve } from 'node:path'; import { mkdir, writeFile } from 'node:fs/promises'; -import chalk from 'chalk'; import * as p from '@clack/prompts'; +import { getCliLogger } from '../logger.js'; + +const logger = getCliLogger(); export interface InitOptions { name?: string; @@ -68,8 +70,8 @@ const KEBAB_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/; export async function init(options: InitOptions): Promise { if (options.template && !TEMPLATES.includes(options.template as (typeof TEMPLATES)[number])) { - console.error(chalk.red(`Unknown template: ${options.template}`)); - console.error(chalk.gray(`Available templates: ${TEMPLATES.join(', ')}`)); + logger.error('Unknown template: {template}', { template: options.template }); + logger.error('Available templates: {templates}', { templates: TEMPLATES.join(', ') }); process.exit(1); } @@ -77,9 +79,9 @@ export async function init(options: InitOptions): Promise { if (options.yes) { answers = getDefaults(options); - console.log(chalk.bold(`\n🆕 Creating AgentPlugins plugin: ${answers.name}\n`)); + logger.info('\n🆕 Creating AgentPlugins plugin: {name}\n', { name: answers.name }); await generateFiles(answers); - console.log(chalk.green('✅ Plugin scaffolded!\n')); + logger.info('✅ Plugin scaffolded!\n'); } else { p.intro('AgentPlugins plugin scaffold'); answers = await runInteractive(options); @@ -87,11 +89,11 @@ export async function init(options: InitOptions): Promise { p.outro('Plugin scaffolded!'); } - console.log(chalk.gray('Next steps:')); - console.log(chalk.gray(` cd ${answers.name}`)); - console.log(chalk.gray(' npm install')); - console.log(chalk.gray(' npm run validate')); - console.log(chalk.gray(' npm run build\n')); + logger.info('Next steps:'); + logger.info(' cd {name}', { name: answers.name }); + logger.info(' npm install'); + logger.info(' npm run validate'); + logger.info(' npm run build\n'); } // ─── Interactive Flow ───────────────────────────────────────────────────────── diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts index 9d9047a..297ffe1 100644 --- a/packages/cli/src/commands/lint.ts +++ b/packages/cli/src/commands/lint.ts @@ -5,10 +5,12 @@ * Catches common pitfalls: naming, versioning, handler safety, secrets, etc. */ -import chalk from 'chalk'; import { lintManifest } from '@agentplugins/core'; +import { getCliLogger } from '../logger.js'; import type { LoadedConfig } from '../config.js'; +const logger = getCliLogger(); + export interface LintOptions { config: LoadedConfig; json: boolean; @@ -31,11 +33,11 @@ export async function lint(options: LintOptions): Promise { return; } - console.log(chalk.bold('\n🔍 AgentPlugins Lint\n')); - console.log(chalk.gray(`Plugin: ${manifest.name} v${manifest.version}\n`)); + logger.info('\n🔍 AgentPlugins Lint\n'); + logger.info('Plugin: {name} v{version}\n', { name: manifest.name, version: manifest.version }); if (issues.length === 0) { - console.log(chalk.green(' ✅ No issues found.\n')); + logger.info(' ✅ No issues found.\n'); return; } @@ -43,24 +45,27 @@ export async function lint(options: LintOptions): Promise { const warnings = issues.filter(i => i.severity === 'warning'); for (const issue of issues) { - const color = issue.severity === 'error' ? chalk.red : chalk.yellow; - const icon = issue.severity === 'error' ? '✗' : '⚠'; - const field = issue.field ? chalk.gray(` [${issue.field}]`) : ''; - const rule = chalk.gray(` (${issue.rule})`); - console.log(color(` ${icon} ${issue.message}${field}${rule}`)); + const field = issue.field ? ` [${issue.field}]` : ''; + const rule = ` (${issue.rule})`; + const message = ` ${issue.severity === 'error' ? '✗' : '⚠'} ${issue.message}${field}${rule}`; + if (issue.severity === 'error') { + logger.error(message); + } else { + logger.warn(message); + } if (issue.suggestion) { - console.log(chalk.cyan(` → ${issue.suggestion}`)); + logger.info(' → {suggestion}', { suggestion: issue.suggestion }); } } - console.log(); - console.log(chalk.bold('Summary:')); - if (errors.length > 0) console.log(chalk.red(` Errors: ${errors.length}`)); - if (warnings.length > 0) console.log(chalk.yellow(` Warnings: ${warnings.length}`)); + logger.info(''); + logger.info('Summary:'); + if (errors.length > 0) logger.error(' Errors: {count}', { count: errors.length }); + if (warnings.length > 0) logger.warn(' Warnings: {count}', { count: warnings.length }); if (errors.length === 0 && warnings.length > 0) { - console.log(chalk.gray(' Run with --strict to fail on warnings.')); + logger.info(' Run with --strict to fail on warnings.'); } - console.log(); + logger.info(''); if (errors.length > 0) { process.exit(1); diff --git a/packages/cli/src/commands/list.ts b/packages/cli/src/commands/list.ts index 90b631d..266db12 100644 --- a/packages/cli/src/commands/list.ts +++ b/packages/cli/src/commands/list.ts @@ -4,8 +4,10 @@ * Lists all installed plugins in the universal store. */ -import chalk from 'chalk'; import { listPlugins } from '@agentplugins/core'; +import { getCliLogger } from '../logger.js'; + +const logger = getCliLogger(); export interface ListOptions { json?: boolean; @@ -26,12 +28,12 @@ export async function list(options: ListOptions): Promise { } if (plugins.length === 0) { - console.log(chalk.gray('\nNo plugins installed.')); - console.log(chalk.gray('Run `agentplugins add ` to install a plugin.\n')); + logger.info('\nNo plugins installed.'); + logger.info('Run `agentplugins add ` to install a plugin.\n'); return; } - console.log(chalk.bold(`\n📦 Installed Plugins (${plugins.length})\n`)); + logger.info('\n📦 Installed Plugins ({count})\n', { count: plugins.length }); for (const plugin of plugins) { const symlinkCount = plugin.symlinks.length; @@ -39,21 +41,23 @@ export async function list(options: ListOptions): Promise { const description = (plugin.manifest?.['description'] as string) ?? plugin.meta.source; - console.log(chalk.cyan(` ${plugin.meta.name}`) + chalk.gray(` v${plugin.meta.version}`)); - console.log(chalk.gray(` ${description}`)); + logger.info(' {name} v{version}', { name: plugin.meta.name, version: plugin.meta.version }); + logger.info(' {description}', { description }); if (plugin.meta.source && plugin.meta.source !== 'unknown') { - console.log(chalk.gray(` source: ${plugin.meta.source}`)); + logger.info(' source: {source}', { source: plugin.meta.source }); } - console.log(chalk.gray(` updated: ${plugin.meta.updatedAt}`)); + logger.info(' updated: {updated}', { updated: plugin.meta.updatedAt }); if (symlinkCount > 0) { const agentNames = plugin.symlinks.map((s) => s.agent).join(', '); - const status = brokenCount > 0 ? chalk.yellow(` (${brokenCount} broken)`) : ''; - console.log(chalk.gray(` linked: ${agentNames}`) + status); + logger.info(' linked: {agents}{broken}', { + agents: agentNames, + broken: brokenCount > 0 ? ` (${brokenCount} broken)` : '', + }); } else { - console.log(chalk.gray(' linked: (none)')); + logger.info(' linked: (none)'); } - console.log(); + logger.info(''); } } diff --git a/packages/cli/src/commands/preview.ts b/packages/cli/src/commands/preview.ts index 21eb7e7..3dc263e 100644 --- a/packages/cli/src/commands/preview.ts +++ b/packages/cli/src/commands/preview.ts @@ -7,14 +7,16 @@ import { resolve, join } from 'node:path'; import { existsSync, readFileSync } from 'node:fs'; -import chalk from 'chalk'; import { ALL_TARGETS, type TargetPlatform, } from '@agentplugins/core'; import { compile } from './build.js'; +import { getCliLogger } from '../logger.js'; import type { LoadedConfig } from '../config.js'; +const logger = getCliLogger(); + export interface PreviewOptions { config: LoadedConfig; targets?: string[]; @@ -26,10 +28,10 @@ export async function preview(options: PreviewOptions): Promise { const manifest = config.manifest; const targetList = (options.targets || manifest.targets || ALL_TARGETS) as TargetPlatform[]; - console.log(chalk.bold('\n👁 AgentPlugins Preview\n')); - console.log(chalk.gray(`Plugin: ${manifest.name} v${manifest.version}`)); - console.log(chalk.gray(`Targets: ${targetList.join(', ')}`)); - console.log(); + logger.info('\n👁 AgentPlugins Preview\n'); + logger.info('Plugin: {name} v{version}', { name: manifest.name, version: manifest.version }); + logger.info('Targets: {targets}', { targets: targetList.join(', ') }); + logger.info(''); const results = await compile({ manifest, @@ -44,11 +46,14 @@ export async function preview(options: PreviewOptions): Promise { for (const result of results) { if (result.skipped) { - console.log(chalk.yellow(` ${result.target}: skipped${result.error ? ` — ${result.error}` : ''}`)); + logger.warn(' {target}: skipped{error}', { + target: result.target, + error: result.error ? ` — ${result.error}` : '', + }); continue; } - console.log(chalk.bold(` 📦 ${result.target}/`)); + logger.info(' 📦 {target}/', { target: result.target }); totalFiles += result.files.length; for (const file of result.files) { @@ -59,37 +64,37 @@ export async function preview(options: PreviewOptions): Promise { const distPath = join('dist', result.target, file.path); const absDist = resolve(distPath); if (!existsSync(absDist)) { - suffix = chalk.green(' (new)'); + suffix = ' (new)'; newFiles++; } else { try { const existing = readFileSync(absDist, 'utf-8'); if (existing !== file.content) { - suffix = chalk.yellow(' (changed)'); + suffix = ' (changed)'; changedFiles++; } else { - suffix = chalk.gray(' (unchanged)'); + suffix = ' (unchanged)'; } } catch { - suffix = chalk.green(' (new)'); + suffix = ' (new)'; newFiles++; } } } - console.log(chalk.gray(` ${display}`) + suffix); + logger.info(' {display}{suffix}', { display, suffix }); } - console.log(); + logger.info(''); } - console.log(chalk.bold('Summary:')); - console.log(chalk.gray(` Files: ${totalFiles}`)); + logger.info('Summary:'); + logger.info(' Files: {count}', { count: totalFiles }); if (diff) { - console.log(chalk.gray(` Changed: ${changedFiles}`)); - console.log(chalk.gray(` New: ${newFiles}`)); - console.log(chalk.gray(` Unchanged: ${totalFiles - changedFiles - newFiles}`)); + logger.info(' Changed: {count}', { count: changedFiles }); + logger.info(' New: {count}', { count: newFiles }); + logger.info(' Unchanged: {count}', { count: totalFiles - changedFiles - newFiles }); } - console.log(); + logger.info(''); // Diff output if (diff) { @@ -106,9 +111,9 @@ export async function preview(options: PreviewOptions): Promise { } if (anyDiff) { - console.log(chalk.cyan('Changes detected. Run `agentplugins build` to write output.\n')); + logger.info('Changes detected. Run `agentplugins build` to write output.\n'); } else { - console.log(chalk.gray('No changes — dist/ is up to date.\n')); + logger.info('No changes — dist/ is up to date.\n'); } } } diff --git a/packages/cli/src/commands/remove.ts b/packages/cli/src/commands/remove.ts index 5d2b67b..87194f6 100644 --- a/packages/cli/src/commands/remove.ts +++ b/packages/cli/src/commands/remove.ts @@ -4,8 +4,10 @@ * Removes a plugin from the store and unlinks all symlinks. */ -import chalk from 'chalk'; import { removePlugin, getPluginInfo } from '@agentplugins/core'; +import { getCliLogger } from '../logger.js'; + +const logger = getCliLogger(); export interface RemoveOptions { name: string; @@ -17,26 +19,26 @@ export async function remove(options: RemoveOptions): Promise { const info = getPluginInfo(name); if (!info) { - console.error(chalk.red(`Plugin "${name}" is not installed.`)); + logger.error('Plugin "{name}" is not installed.', { name }); process.exit(1); } - console.log(chalk.bold('\n🗑 AgentPlugins Remove\n')); - console.log(chalk.gray(`Plugin: ${name} v${info.meta.version}`)); - console.log(chalk.gray(`Source: ${info.meta.source}`)); + logger.info('\n🗑 AgentPlugins Remove\n'); + logger.info('Plugin: {name} v{version}', { name, version: info.meta.version }); + logger.info('Source: {source}', { source: info.meta.source }); if (info.symlinks.length > 0) { - console.log(chalk.gray(`Symlinks: ${info.symlinks.length}`)); + logger.info('Symlinks: {count}', { count: info.symlinks.length }); } removePlugin(name); - console.log(chalk.green(`\n✅ Removed ${name}`)); + logger.info('\n✅ Removed {name}', { name }); if (info.symlinks.length > 0) { - console.log(chalk.gray('Unlinked from:')); + logger.info('Unlinked from:'); for (const s of info.symlinks) { - console.log(chalk.gray(` ${s.agentDisplayName}: ${s.linkPath}`)); + logger.info(' {agent}: {path}', { agent: s.agentDisplayName, path: s.linkPath }); } } - console.log(); + logger.info(''); } diff --git a/packages/cli/src/commands/setup.ts b/packages/cli/src/commands/setup.ts index 5b85492..b98be27 100644 --- a/packages/cli/src/commands/setup.ts +++ b/packages/cli/src/commands/setup.ts @@ -4,7 +4,6 @@ * Re-runs an installed plugin's setup script with trust-on-first-use gating. */ -import chalk from 'chalk'; import * as p from '@clack/prompts'; import { getStorePath, @@ -19,6 +18,9 @@ import { type SetupSource, } from '@agentplugins/core'; import { join } from 'node:path'; +import { getCliLogger } from '../logger.js'; + +const logger = getCliLogger(); export interface SetupFlowOptions { name: string; @@ -37,7 +39,7 @@ export interface SetupFlowResult { export async function runSetupFlow(opts: SetupFlowOptions): Promise { if (process.env.AGENTPLUGINS_SETUP_SCRIPTS === '0') { - console.log(chalk.gray('Setup scripts disabled (AGENTPLUGINS_SETUP_SCRIPTS=0).')); + logger.info('Setup scripts disabled (AGENTPLUGINS_SETUP_SCRIPTS=0).'); return { ran: false, skipped: 'kill-switch' }; } @@ -52,9 +54,9 @@ export async function runSetupFlow(opts: SetupFlowOptions): Promise { const meta = readMeta(options.name); if (!meta) { - console.error(chalk.red(`Plugin "${options.name}" is not installed.`)); + logger.error('Plugin "{name}" is not installed.', { name: options.name }); process.exit(1); } const pluginDir = join(getStorePath(), options.name); const manifestResult = findManifestInDir(pluginDir); if (!manifestResult) { - console.error(chalk.red(`No manifest found for installed plugin "${options.name}".`)); + logger.error('No manifest found for installed plugin "{name}".', { name: options.name }); process.exit(1); } await runSetupFlow({ diff --git a/packages/cli/src/commands/update.ts b/packages/cli/src/commands/update.ts index e23764c..30524bc 100644 --- a/packages/cli/src/commands/update.ts +++ b/packages/cli/src/commands/update.ts @@ -4,8 +4,10 @@ * Pulls latest changes for a plugin (or all plugins). */ -import chalk from 'chalk'; import { updatePlugin, listPlugins, getPluginInfo } from '@agentplugins/core'; +import { getCliLogger } from '../logger.js'; + +const logger = getCliLogger(); export interface UpdateOptions { name?: string; @@ -17,27 +19,30 @@ export async function update(options: UpdateOptions): Promise { if (options.all || !options.name) { const plugins = listPlugins(); if (plugins.length === 0) { - console.log(chalk.gray('\nNo plugins installed.\n')); + logger.info('\nNo plugins installed.\n'); return; } - console.log(chalk.bold(`\n🔄 Updating ${plugins.length} plugin${plugins.length > 1 ? 's' : ''}\n`)); + logger.info('\n🔄 Updating {count} plugin{plural}\n', { + count: plugins.length, + plural: plugins.length > 1 ? 's' : '', + }); let success = 0; let failed = 0; for (const plugin of plugins) { try { - process.stdout.write(chalk.gray(` ${plugin.meta.name}... `)); + process.stdout.write(` ${plugin.meta.name}... `); const meta = updatePlugin(plugin.meta.name); - console.log(chalk.green(`✓ ${meta.version} (${meta.commit.slice(0, 7)})`)); + logger.info('✓ {version} ({commit})', { version: meta.version, commit: meta.commit.slice(0, 7) }); success++; } catch (err) { const msg = err instanceof Error ? err.message : String(err); - console.log(chalk.red(`✗ ${msg}`)); + logger.error('✗ {msg}', { msg }); failed++; } } - console.log(chalk.gray(`\n${success} updated, ${failed} failed.\n`)); + logger.info('\n{success} updated, {failed} failed.\n', { success, failed }); return; } @@ -45,23 +50,26 @@ export async function update(options: UpdateOptions): Promise { const name = options.name; const info = getPluginInfo(name); if (!info) { - console.error(chalk.red(`Plugin "${name}" is not installed.`)); + logger.error('Plugin "{name}" is not installed.', { name }); process.exit(1); } - console.log(chalk.bold('\n🔄 AgentPlugins Update\n')); - console.log(chalk.gray(`Plugin: ${name}`)); - console.log(chalk.gray(`Current: v${info.meta.version} (${info.meta.commit.slice(0, 7)})\n`)); + logger.info('\n🔄 AgentPlugins Update\n'); + logger.info('Plugin: {name}', { name }); + logger.info('Current: v{version} ({commit})\n', { + version: info.meta.version, + commit: info.meta.commit.slice(0, 7), + }); try { const meta = updatePlugin(name); - console.log(chalk.green(`\n✅ Updated ${name}`)); - console.log(chalk.gray(` Version: ${meta.version}`)); - console.log(chalk.gray(` Commit: ${meta.commit.slice(0, 7)}`)); - console.log(chalk.gray(` Updated: ${meta.updatedAt}\n`)); + logger.info('\n✅ Updated {name}', { name }); + logger.info(' Version: {version}', { version: meta.version }); + logger.info(' Commit: {commit}', { commit: meta.commit.slice(0, 7) }); + logger.info(' Updated: {updated}\n', { updated: meta.updatedAt }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - console.error(chalk.red(`\nFailed to update: ${msg}\n`)); + logger.error('\nFailed to update: {msg}\n', { msg }); process.exit(1); } } diff --git a/packages/cli/src/commands/validate.ts b/packages/cli/src/commands/validate.ts index 10a6d76..52fe541 100644 --- a/packages/cli/src/commands/validate.ts +++ b/packages/cli/src/commands/validate.ts @@ -4,14 +4,16 @@ * Validates plugin configuration without building. */ -import chalk from 'chalk'; import { validateUniversal, validateForPlatform, ALL_TARGETS, } from '@agentplugins/core'; +import { getCliLogger } from '../logger.js'; import type { LoadedConfig } from '../config.js'; +const logger = getCliLogger(); + export interface ValidateOptions { config: LoadedConfig; targets?: string[]; @@ -22,15 +24,15 @@ export async function validate(options: ValidateOptions): Promise { const manifest = config.manifest; const targets = options.targets || manifest.targets || ALL_TARGETS; - console.log(chalk.bold('\n🔍 AgentPlugins Validation\n')); - console.log(chalk.gray(`Plugin: ${manifest.name} v${manifest.version}`)); - console.log(chalk.gray(`Targets: ${targets.join(', ')}\n`)); + logger.info('\n🔍 AgentPlugins Validation\n'); + logger.info('Plugin: {name} v{version}', { name: manifest.name, version: manifest.version }); + logger.info('Targets: {targets}\n', { targets: targets.join(', ') }); // Universal validation - console.log(chalk.blue('Universal Rules:')); + logger.info('Universal Rules:'); const universalIssues = validateUniversal(manifest); if (universalIssues.length === 0) { - console.log(chalk.green(' ✓ No issues found')); + logger.info(' ✓ No issues found'); } else { for (const issue of universalIssues) { printIssue(issue); @@ -39,10 +41,10 @@ export async function validate(options: ValidateOptions): Promise { // Per-platform validation for (const target of targets) { - console.log(chalk.blue(`\n${target}:`)); + logger.info('\n{target}:', { target }); const issues = validateForPlatform(manifest, target as any); if (issues.length === 0) { - console.log(chalk.green(' ✓ No issues found')); + logger.info(' ✓ No issues found'); } else { for (const issue of issues) { printIssue(issue); @@ -54,19 +56,25 @@ export async function validate(options: ValidateOptions): Promise { targets.reduce((sum, t) => sum + validateForPlatform(manifest, t as any).filter(i => i.severity === 'error').length, 0); if (totalErrors === 0) { - console.log(chalk.bold('\n✅ All checks passed!\n')); + logger.info('\n✅ All checks passed!\n'); } else { - console.log(chalk.bold(`\n❌ Found ${totalErrors} error(s)\n`)); + logger.error('\n❌ Found {count} error(s)\n', { count: totalErrors }); process.exit(1); } } function printIssue(issue: { severity: string; field?: string; message: string; suggestion?: string }): void { - const color = issue.severity === 'error' ? chalk.red : issue.severity === 'warning' ? chalk.yellow : chalk.gray; const icon = issue.severity === 'error' ? '✗' : issue.severity === 'warning' ? '⚠' : 'ℹ'; - const field = issue.field ? chalk.gray(`[${issue.field}] `) : ''; - console.log(color(` ${icon} ${field}${issue.message}`)); + const field = issue.field ? `[${issue.field}] ` : ''; + const message = ` ${icon} ${field}${issue.message}`; + if (issue.severity === 'error') { + logger.error(message); + } else if (issue.severity === 'warning') { + logger.warn(message); + } else { + logger.info(message); + } if (issue.suggestion) { - console.log(chalk.cyan(` → ${issue.suggestion}`)); + logger.info(' → {suggestion}', { suggestion: issue.suggestion }); } } diff --git a/packages/cli/src/logger.ts b/packages/cli/src/logger.ts new file mode 100644 index 0000000..ff4f7df --- /dev/null +++ b/packages/cli/src/logger.ts @@ -0,0 +1,20 @@ +import { configure, getConsoleSink, getLogger } from '@logtape/logtape'; + +let configured = false; + +export async function setupLogger() { + if (configured) return; + await configure({ + sinks: { console: getConsoleSink() }, + filters: {}, + loggers: [ + { category: ['agentplugins', 'cli'], sinks: ['console'], level: 'info' }, + { category: ['logtape', 'meta'], sinks: ['console'], level: 'warning' }, + ], + }); + configured = true; +} + +export function getCliLogger() { + return getLogger(['agentplugins', 'cli']); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66f3f01..1faabe6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -237,9 +237,9 @@ importers: '@clack/prompts': specifier: ^0.7.0 version: 0.7.0 - chalk: - specifier: ^5.3.0 - version: 5.6.2 + '@logtape/logtape': + specifier: ^0.4.0 + version: 0.4.3 cleye: specifier: 'catalog:' version: 1.3.0 @@ -248,14 +248,14 @@ importers: version: 1.21.7 devDependencies: '@types/node': - specifier: ^20.0.0 - version: 20.19.41 + specifier: 'catalog:' + version: 22.20.0 typescript: - specifier: ^5.5.0 + specifier: 'catalog:' version: 5.9.3 vitest: - specifier: ^3.1.4 - version: 3.2.6(@types/debug@4.1.13)(@types/node@20.19.41) + specifier: 'catalog:' + version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) packages/compile: dependencies: @@ -1032,6 +1032,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@logtape/logtape@0.4.3': + resolution: {integrity: sha512-sUZAJpoYGHfRSVhExiGIzPFdLBuXcGWhkOOLieieaU2JDkTT7zx+f3i6i2YeyqA0bpPHcOGl5xBT2YD+lasWig==} + '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -1774,10 +1777,6 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} 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==} @@ -4120,6 +4119,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@logtape/logtape@0.4.3': {} + '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.29.7 @@ -4567,14 +4568,6 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.6(vite@5.4.21(@types/node@20.19.41))': - dependencies: - '@vitest/spy': 3.2.6 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 5.4.21(@types/node@20.19.41) - '@vitest/mocker@3.2.6(vite@5.4.21(@types/node@22.20.0))': dependencies: '@vitest/spy': 3.2.6 @@ -4877,8 +4870,6 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 - chalk@5.6.2: {} - character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -6668,24 +6659,6 @@ snapshots: - supports-color - terser - vite-node@3.2.4(@types/node@20.19.41): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 5.4.21(@types/node@20.19.41) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - vite-node@3.2.4(@types/node@22.20.0): dependencies: cac: 6.7.14 @@ -6873,45 +6846,6 @@ snapshots: - supports-color - terser - vitest@3.2.6(@types/debug@4.1.13)(@types/node@20.19.41): - dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.6 - '@vitest/mocker': 3.2.6(vite@5.4.21(@types/node@20.19.41)) - '@vitest/pretty-format': 3.2.6 - '@vitest/runner': 3.2.6 - '@vitest/snapshot': 3.2.6 - '@vitest/spy': 3.2.6 - '@vitest/utils': 3.2.6 - chai: 5.3.3 - debug: 4.4.3 - expect-type: 1.3.0 - magic-string: 0.30.21 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.17 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 5.4.21(@types/node@20.19.41) - vite-node: 3.2.4(@types/node@20.19.41) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.13 - '@types/node': 20.19.41 - transitivePeerDependencies: - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - vitest@3.2.6(@types/debug@4.1.13)(@types/node@22.20.0): dependencies: '@types/chai': 5.2.3 From b2bf4de9f69fd5b79e49dc5f66d0057e5475d324 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:59:23 +0200 Subject: [PATCH 68/90] feat(cli): migrate jiti to v2 --- packages/cli/package.json | 2 +- packages/cli/src/commands/add.ts | 9 +++------ packages/cli/src/config.ts | 15 +++------------ pnpm-lock.yaml | 23 +++++++++++++---------- 4 files changed, 20 insertions(+), 29 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 3d6fde7..19b38cf 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -38,7 +38,7 @@ "@clack/prompts": "^0.7.0", "@logtape/logtape": "^0.4.0", "cleye": "catalog:", - "jiti": "^1.21.6" + "jiti": "catalog:" }, "devDependencies": { "@types/node": "catalog:", diff --git a/packages/cli/src/commands/add.ts b/packages/cli/src/commands/add.ts index a0b2be2..a293dfb 100644 --- a/packages/cli/src/commands/add.ts +++ b/packages/cli/src/commands/add.ts @@ -20,6 +20,7 @@ import { } from '@agentplugins/core'; import { join } from 'node:path'; import { existsSync, rmSync } from 'node:fs'; +import { createJiti } from 'jiti'; import { compile } from './build.js'; import { runSetupFlow } from './setup.js'; import { createApp, createInstallCtx, AbortError } from '@agentplugins/pipeline'; @@ -181,12 +182,8 @@ async function tryTsConfig(dir: string): Promise<{ path: string; manifest: Recor const fullPath = join(dir, candidate); if (!existsSync(fullPath)) continue; try { - const jiti = (await import('jiti')).default as unknown as ( - filename: string, - opts?: Record - ) => { import: (id: string, opts?: Record) => Promise }; - const loader = jiti(fullPath, { interopDefault: true, esmResolve: true }); - const mod = await loader.import(fullPath, { default: true }); + const loader = createJiti(fullPath, { interopDefault: true }); + const mod = await loader.import(fullPath); const exported = (mod as Record)?.['default' as keyof typeof mod] ?? mod; const manifest = typeof exported === 'function' ? await (exported as () => Promise>)() diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts index c68bf72..d9ad998 100644 --- a/packages/cli/src/config.ts +++ b/packages/cli/src/config.ts @@ -12,12 +12,7 @@ import { existsSync } from 'node:fs'; import { resolve, extname } from 'node:path'; -import jiti from 'jiti'; - -// jiti's CJS default export is a function - cast to proper type -const createJITI = jiti as unknown as (filename: string, opts?: Record) => { - import: (id: string, opts?: Record) => Promise; -}; +import { createJiti } from 'jiti'; import type { PluginManifest, AgentPluginsConfig } from '@agentplugins/core'; import type { Plugin } from '@agentplugins/pipeline'; @@ -66,12 +61,8 @@ export async function loadConfig(configPath: string): Promise { manifest = JSON.parse(content) as PluginManifest; } else { // TypeScript/JavaScript config — use jiti - const jitiLoader = createJITI(resolvedPath, { - interopDefault: true, - esmResolve: true, - }); - - const mod = await jitiLoader.import(resolvedPath, { default: true }); + const jiti = createJiti(resolvedPath, { interopDefault: true }); + const mod = await jiti.import(resolvedPath); const exported = (mod as Record)?.default ?? mod; if (typeof exported === 'function') { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1faabe6..53923eb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,9 @@ catalogs: cleye: specifier: ^1.3.0 version: 1.3.0 + jiti: + specifier: ^2.0.0 + version: 2.7.0 tsdown: specifier: ^0.22.3 version: 0.22.3 @@ -163,7 +166,7 @@ importers: devDependencies: tsup: specifier: ^8.0.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(typescript@5.9.3) typescript: specifier: 'catalog:' version: 5.9.3 @@ -244,8 +247,8 @@ importers: specifier: 'catalog:' version: 1.3.0 jiti: - specifier: ^1.21.6 - version: 1.21.7 + specifier: 'catalog:' + version: 2.7.0 devDependencies: '@types/node': specifier: 'catalog:' @@ -2458,8 +2461,8 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true jose@6.2.3: @@ -5581,7 +5584,7 @@ snapshots: isexe@2.0.0: {} - jiti@1.21.7: {} + jiti@2.7.0: {} jose@6.2.3: {} @@ -6063,11 +6066,11 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.15): + postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.15): dependencies: lilconfig: 3.1.3 optionalDependencies: - jiti: 1.21.7 + jiti: 2.7.0 postcss: 8.5.15 postcss@8.5.15: @@ -6515,7 +6518,7 @@ snapshots: tslib@2.8.1: optional: true - tsup@8.5.1(jiti@1.21.7)(postcss@8.5.15)(typescript@5.9.3): + tsup@8.5.1(jiti@2.7.0)(postcss@8.5.15)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 @@ -6526,7 +6529,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.15) + postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.15) resolve-from: 5.0.0 rollup: 4.61.1 source-map: 0.7.6 From cf96fcdf7ac8fbf4fe0f6a68a38afc9a8a3dacef Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 09:59:33 +0200 Subject: [PATCH 69/90] chore(scripts): migrate recompile-installed to jiti v2 --- scripts/recompile-installed.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/recompile-installed.ts b/scripts/recompile-installed.ts index bc42fbe..6439317 100644 --- a/scripts/recompile-installed.ts +++ b/scripts/recompile-installed.ts @@ -5,6 +5,7 @@ import { join } from 'node:path'; import { existsSync } from 'node:fs'; import { homedir } from 'node:os'; +import { createJiti } from 'jiti'; import { compile } from '../packages/cli/src/commands/build.js'; import { getDetectedAgents, @@ -25,14 +26,13 @@ async function tryTsConfig(pluginDir: string) { const fp = join(pluginDir, c); if (!existsSync(fp)) continue; try { - const jiti = (await import('jiti')).default as any; // Map @agentplugins/* so jiti can resolve workspace packages from the plugin dir const wsRoot = new URL('../', import.meta.url).pathname; const alias: Record = { '@agentplugins/core': join(wsRoot, 'packages/core/dist/index.js'), }; - const loader = jiti(fp, { interopDefault: true, esmResolve: true, alias }); - const mod = await loader.import(fp, { default: true }); + const loader = createJiti(fp, { interopDefault: true, alias }); + const mod = await loader.import(fp); const exported = (mod as any)?.default ?? mod; const manifest = typeof exported === 'function' ? await (exported as () => Promise)() : exported; if (manifest?.name) return manifest; From ec5ecd33240f748edabd656618310fce131852dc Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 10:13:35 +0200 Subject: [PATCH 70/90] chore(tsconfig): exclude __tests__ and dist from root type checking --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 18170b3..faca084 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,5 +20,5 @@ "outDir": "./dist", "rootDir": "." }, - "exclude": ["node_modules", "dist", "*.js"] + "exclude": ["node_modules", "dist", "*.js", "**/__tests__/**", "**/dist/**", "**/node_modules/**"] } From 73a03359a347f7e07f3d6ebc8a1b09845612ebda Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 10:13:38 +0200 Subject: [PATCH 71/90] chore(vitest): nest include/exclude under test key for vitest 3 --- packages/adapter-opencode/vitest.config.ts | 8 +++++--- packages/adapter-pimono/vitest.config.ts | 8 +++++--- packages/core/vitest.config.ts | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/adapter-opencode/vitest.config.ts b/packages/adapter-opencode/vitest.config.ts index 715e157..5033d17 100644 --- a/packages/adapter-opencode/vitest.config.ts +++ b/packages/adapter-opencode/vitest.config.ts @@ -1,7 +1,9 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ - include: ['__tests__/**/*.test.ts'], - environment: 'node', - globals: true, + test: { + include: ['__tests__/**/*.test.ts'], + environment: 'node', + globals: true, + }, }); diff --git a/packages/adapter-pimono/vitest.config.ts b/packages/adapter-pimono/vitest.config.ts index 715e157..5033d17 100644 --- a/packages/adapter-pimono/vitest.config.ts +++ b/packages/adapter-pimono/vitest.config.ts @@ -1,7 +1,9 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ - include: ['__tests__/**/*.test.ts'], - environment: 'node', - globals: true, + test: { + include: ['__tests__/**/*.test.ts'], + environment: 'node', + globals: true, + }, }); diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index 62433ee..6a36b72 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -1,7 +1,9 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ - include: ['__tests__/**/*.test.ts'], - exclude: ['__tests__/adapter-contract.test.ts'], - environment: 'node', + test: { + include: ['__tests__/**/*.test.ts'], + exclude: ['__tests__/adapter-contract.test.ts'], + environment: 'node', + }, }); From 8d4fe9abac65c703d61a3296456787adb019d63b Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 10:13:40 +0200 Subject: [PATCH 72/90] chore(scripts): add @ts-nocheck to recompile-installed build script --- scripts/recompile-installed.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/recompile-installed.ts b/scripts/recompile-installed.ts index 6439317..33dde30 100644 --- a/scripts/recompile-installed.ts +++ b/scripts/recompile-installed.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /** * One-time recompile script for installed plugins. * Run from workspace: npx tsx scripts/recompile-installed.ts From b53ebdb02d031c0ffe06c5fd76cdb23f541be7b6 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 10:13:47 +0200 Subject: [PATCH 73/90] fix(adapters): resolve remaining tsc errors in opencode and gemini adapters --- packages/adapter-gemini/src/index.ts | 9 ++++----- packages/adapter-opencode/src/factory.ts | 8 ++++---- packages/adapter-opencode/src/index.ts | 12 ++++++------ packages/adapter-opencode/src/validate.ts | 4 ---- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/adapter-gemini/src/index.ts b/packages/adapter-gemini/src/index.ts index 7384bf1..84f45f0 100644 --- a/packages/adapter-gemini/src/index.ts +++ b/packages/adapter-gemini/src/index.ts @@ -222,7 +222,7 @@ function validateGeminiManifest(plugin: PluginManifest): ValidationIssue[] { * stdout → JSON result (optional) * exit → 0 (allow), 2 (block), other (warning) */ -function wrapInlineHandler( +function _wrapInlineHandler( script: string, hookName: string, geminiEvent: string @@ -275,7 +275,7 @@ process.stdin.on("end", () => { * Wrap a file-based handler so it conforms to the Gemini protocol. * Returns a *new* inline script that proxies to the file. */ -function wrapFileHandler(filePath: string): string { +function _wrapFileHandler(filePath: string): string { return `#!/usr/bin/env node // Auto-generated Gemini file-handler proxy const { spawn } = require("child_process"); @@ -419,11 +419,10 @@ export class GeminiAdapter implements PlatformAdapter { ? ((handler as ReferenceHookHandler).source ?? (handler as ReferenceHookHandler).reference) : undefined; - let scriptContent: string; if (isReference && script) { - scriptContent = wrapFileHandler(script); + _wrapFileHandler(script); } else { - scriptContent = wrapInlineHandler( + _wrapInlineHandler( script ?? "// no-op", hookName, geminiEvent diff --git a/packages/adapter-opencode/src/factory.ts b/packages/adapter-opencode/src/factory.ts index 80befa5..0b9281c 100644 --- a/packages/adapter-opencode/src/factory.ts +++ b/packages/adapter-opencode/src/factory.ts @@ -23,15 +23,15 @@ import { type HookDefinition, } from "@agentplugins/core/adapter"; -import { createValidate } from "./validate"; +import { createValidate } from "./validate.js"; import { HOOK_MAPPING, EVENT_HOOKS, buildEventHookBlock, buildHookArgs, -} from "./hook-mapping"; -import { buildHandlerInvocation } from "./handler-invocation"; -import { generatePluginFile, generateManifest, generateCommandFiles, generateAgentFiles } from "./output-generators"; +} from "./hook-mapping.js"; +import { buildHandlerInvocation } from "./handler-invocation.js"; +import { generatePluginFile, generateManifest, generateCommandFiles, generateAgentFiles } from "./output-generators.js"; /** * The 8 hooks supported by OpenCode. diff --git a/packages/adapter-opencode/src/index.ts b/packages/adapter-opencode/src/index.ts index 8859692..bf377c3 100644 --- a/packages/adapter-opencode/src/index.ts +++ b/packages/adapter-opencode/src/index.ts @@ -5,11 +5,11 @@ */ // Re-export factory (main adapter creation) -export { createOpenCodeAdapter } from "./factory"; -export { default } from "./factory"; +export { createOpenCodeAdapter } from "./factory.js"; +export { default } from "./factory.js"; // Re-export Wave 2 module public APIs -export { HOOK_MAPPING, EVENT_TYPE_CONDITIONS, EVENT_HOOKS } from "./hook-mapping"; -export { buildHandlerInvocation } from "./handler-invocation"; -export { generatePluginFile, generateManifest } from "./output-generators"; -export { createValidate } from "./validate"; +export { HOOK_MAPPING, EVENT_TYPE_CONDITIONS, EVENT_HOOKS } from "./hook-mapping.js"; +export { buildHandlerInvocation } from "./handler-invocation.js"; +export { generatePluginFile, generateManifest } from "./output-generators.js"; +export { createValidate } from "./validate.js"; diff --git a/packages/adapter-opencode/src/validate.ts b/packages/adapter-opencode/src/validate.ts index f9538e4..fd7a3fe 100644 --- a/packages/adapter-opencode/src/validate.ts +++ b/packages/adapter-opencode/src/validate.ts @@ -12,10 +12,6 @@ import { type PluginManifest, - type HookHandler, - type InlineHookHandler, - type CommandHookHandler, - type HttpHookHandler, Severity, } from "@agentplugins/core"; From 741c04de007516d1c3b7d012fc543b46fd9039c9 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 10:40:26 +0200 Subject: [PATCH 74/90] chore(full-stack): complete dependency upgrades - typescript ^6, vitest ^4, vite ^6 - @logtape/logtape ^2 (level->lowestLevel rename) - cleye ^2, @clack/prompts ^1 (multiselect API) - tsdown ^0.22.3, jiti ^2.7 - tsconfig: exclude __tests__, ignoreDeprecations 6.0, types: node - docs: own tsconfig, @types/node from catalog - vite ^6 root devDeps for vitest peer --- docs/.vitepress/config.ts | 2 +- docs/package.json | 1 + docs/tsconfig.json | 9 + package.json | 3 +- packages/adapter-opencode/tsconfig.json | 3 +- packages/cli/package.json | 6 +- packages/cli/src/commands/init.ts | 12 +- packages/cli/src/logger.ts | 4 +- packages/schema/package.json | 4 +- pnpm-lock.yaml | 1248 ++++++++++------------- pnpm-workspace.yaml | 19 +- tsconfig.json | 1 + 12 files changed, 596 insertions(+), 716 deletions(-) create mode 100644 docs/tsconfig.json diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 793e183..48b80a0 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -79,7 +79,7 @@ export default withMermaid(defineConfig({ name: 'vitepress-dev-llms-txt', configureServer(server) { server.middlewares.use((req, res) => { - const url = req.url.split('?')[0] + const url = (req.url ?? '').split('?')[0] if (url === '/llms.txt' || url === '/llms-full.txt') { const distPath = resolve(__dirname, '.vitepress/dist' + url) if (existsSync(distPath)) { diff --git a/docs/package.json b/docs/package.json index c2d7f2a..67bec1a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -9,6 +9,7 @@ }, "devDependencies": { "@braintree/sanitize-url": "^7.1.2", + "@types/node": "catalog:", "cytoscape": "^3.34.0", "cytoscape-cose-bilkent": "^4.1.0", "dayjs": "^1.11.21", diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 0000000..e3bea12 --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./.vitepress/dist", + "rootDir": "." + }, + "include": [".vitepress/**/*.ts"], + "exclude": ["node_modules", ".vitepress/dist"] +} diff --git a/package.json b/package.json index 1c515b4..88f36e5 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ }, "packageManager": "pnpm@11.0.0", "devDependencies": { - "@changesets/cli": "^2.31.0" + "@changesets/cli": "^2.31.0", + "vite": "^6.0.0" } } diff --git a/packages/adapter-opencode/tsconfig.json b/packages/adapter-opencode/tsconfig.json index 2d881bb..faccec5 100644 --- a/packages/adapter-opencode/tsconfig.json +++ b/packages/adapter-opencode/tsconfig.json @@ -13,7 +13,8 @@ "declarationMap": true, "sourceMap": true, "moduleResolution": "bundler", - "resolveJsonModule": true + "resolveJsonModule": true, + "ignoreDeprecations": "6.0" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] diff --git a/packages/cli/package.json b/packages/cli/package.json index 19b38cf..30722e6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,8 +35,8 @@ "@agentplugins/adapter-kimi": "workspace:*", "@agentplugins/adapter-opencode": "workspace:*", "@agentplugins/adapter-pimono": "workspace:*", - "@clack/prompts": "^0.7.0", - "@logtape/logtape": "^0.4.0", + "@clack/prompts": "catalog:", + "@logtape/logtape": "catalog:", "cleye": "catalog:", "jiti": "catalog:" }, @@ -58,4 +58,4 @@ "directory": "packages/cli" }, "homepage": "https://agentplugins.pages.dev/" -} +} \ No newline at end of file diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index b9c3dae..4df5748 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -108,7 +108,7 @@ async function runInteractive(opts: InitOptions): Promise { placeholder: 'my-plugin', defaultValue: DEFAULTS.name, validate: (v) => - v.length === 0 || !KEBAB_RE.test(v) + !v || v.length === 0 || !KEBAB_RE.test(v) ? 'Use kebab-case (lowercase letters, digits, hyphens)' : undefined, }); @@ -127,7 +127,7 @@ async function runInteractive(opts: InitOptions): Promise { message: 'Description', placeholder: 'Describe what your plugin does...', validate: (v) => - v.trim().length < 10 ? 'Description must be at least 10 characters' : undefined, + !v || v.trim().length < 10 ? 'Description must be at least 10 characters' : undefined, }), ); @@ -143,7 +143,7 @@ async function runInteractive(opts: InitOptions): Promise { targets = opts.target.split(',').map((t) => t.trim()).filter(Boolean); } else { const picked = assertValue( - await p.multiselect({ + await p.multiselect({ message: 'Target platforms', options: TARGET_OPTIONS, required: false, @@ -153,7 +153,7 @@ async function runInteractive(opts: InitOptions): Promise { } const pickedHooks = assertValue( - await p.multiselect({ + await p.multiselect({ message: 'Hook coverage', options: HOOK_OPTIONS, required: false, @@ -173,7 +173,7 @@ async function runInteractive(opts: InitOptions): Promise { message: 'Skill name', placeholder: `${name}-skill`, defaultValue: `${name}-skill`, - validate: (v) => (v.length === 0 ? 'Skill name is required' : undefined), + validate: (v) => (!v || v.length === 0 ? 'Skill name is required' : undefined), }), ); skillDescription = assertValue( @@ -181,7 +181,7 @@ async function runInteractive(opts: InitOptions): Promise { message: 'Skill description', placeholder: `Describe what the ${name} skill does...`, validate: (v) => - v.trim().length < 10 ? 'Description must be at least 10 characters' : undefined, + !v || v.trim().length < 10 ? 'Description must be at least 10 characters' : undefined, }), ); } diff --git a/packages/cli/src/logger.ts b/packages/cli/src/logger.ts index ff4f7df..18abb61 100644 --- a/packages/cli/src/logger.ts +++ b/packages/cli/src/logger.ts @@ -8,8 +8,8 @@ export async function setupLogger() { sinks: { console: getConsoleSink() }, filters: {}, loggers: [ - { category: ['agentplugins', 'cli'], sinks: ['console'], level: 'info' }, - { category: ['logtape', 'meta'], sinks: ['console'], level: 'warning' }, + { category: ['agentplugins', 'cli'], sinks: ['console'], lowestLevel: 'info' }, + { category: ['logtape', 'meta'], sinks: ['console'], lowestLevel: 'warning' }, ], }); configured = true; diff --git a/packages/schema/package.json b/packages/schema/package.json index 57c715d..f315ffd 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -41,11 +41,11 @@ }, "homepage": "https://agentplugins.pages.dev/", "dependencies": { - "ajv": "^8.17.1" + "ajv": "^8.20.0" }, "devDependencies": { "@types/node": "catalog:", "typescript": "catalog:", "vitest": "catalog:" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 53923eb..7f11fc3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,26 +6,32 @@ settings: catalogs: default: + '@clack/prompts': + specifier: ^1.0.0 + version: 1.6.0 + '@logtape/logtape': + specifier: ^2.0.0 + version: 2.2.1 '@types/node': - specifier: ^22.0.0 + specifier: ^22.20.0 version: 22.20.0 cleye: - specifier: ^1.3.0 - version: 1.3.0 - jiti: specifier: ^2.0.0 + version: 2.6.0 + jiti: + specifier: ^2.7.0 version: 2.7.0 tsdown: specifier: ^0.22.3 version: 0.22.3 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.0 + version: 6.0.3 vitest: - specifier: ^3.2.6 - version: 3.2.6 - zod: specifier: ^4.0.0 + version: 4.1.9 + zod: + specifier: ^4.4.3 version: 4.4.3 importers: @@ -35,12 +41,18 @@ importers: '@changesets/cli': specifier: ^2.31.0 version: 2.31.0(@types/node@22.20.0) + vite: + specifier: ^6.0.0 + version: 6.4.3(@types/node@22.20.0)(jiti@2.7.0) docs: devDependencies: '@braintree/sanitize-url': specifier: ^7.1.2 version: 7.1.2 + '@types/node': + specifier: 'catalog:' + version: 22.20.0 cytoscape: specifier: ^3.34.0 version: 3.34.0 @@ -58,7 +70,7 @@ importers: version: 11.16.0 vitepress: specifier: ^1.5.0 - version: 1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3) + version: 1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@6.0.3) vitepress-plugin-group-icons: specifier: ^1.7.5 version: 1.7.5(vite@5.4.21(@types/node@22.20.0)) @@ -67,7 +79,7 @@ importers: version: 1.13.2 vitepress-plugin-mermaid: specifier: ^2.0.17 - version: 2.0.17(mermaid@11.16.0)(vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3)) + version: 2.0.17(mermaid@11.16.0)(vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@6.0.3)) packages/adapter-claude: dependencies: @@ -77,16 +89,16 @@ importers: devDependencies: '@types/node': specifier: 'catalog:' - version: 20.19.41 + version: 22.20.0 tsdown: specifier: 'catalog:' - version: 0.22.3(typescript@5.9.3) + version: 0.22.3(typescript@6.0.3) typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 1.6.1(@types/node@20.19.41) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/adapter-codex: dependencies: @@ -96,16 +108,16 @@ importers: devDependencies: '@types/node': specifier: 'catalog:' - version: 20.19.41 + version: 22.20.0 tsdown: specifier: 'catalog:' - version: 0.22.3(typescript@5.9.3) + version: 0.22.3(typescript@6.0.3) typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 1.6.1(@types/node@20.19.41) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/adapter-copilot: dependencies: @@ -115,13 +127,13 @@ importers: devDependencies: tsdown: specifier: 'catalog:' - version: 0.22.3(typescript@5.9.3) + version: 0.22.3(typescript@6.0.3) typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 1.6.1(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/adapter-gemini: dependencies: @@ -131,10 +143,10 @@ importers: devDependencies: tsdown: specifier: 'catalog:' - version: 0.22.3(typescript@5.9.3) + version: 0.22.3(typescript@6.0.3) typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 packages/adapter-kimi: dependencies: @@ -144,13 +156,13 @@ importers: devDependencies: tsdown: specifier: 'catalog:' - version: 0.22.3(typescript@5.9.3) + version: 0.22.3(typescript@6.0.3) typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/adapter-opencode: dependencies: @@ -166,13 +178,13 @@ importers: devDependencies: tsup: specifier: ^8.0.0 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(typescript@5.9.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(typescript@6.0.3) typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/adapter-pimono: dependencies: @@ -185,13 +197,13 @@ importers: devDependencies: tsdown: specifier: 'catalog:' - version: 0.22.3(typescript@5.9.3) + version: 0.22.3(typescript@6.0.3) typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/cli: dependencies: @@ -238,14 +250,14 @@ importers: specifier: workspace:* version: link:../security '@clack/prompts': - specifier: ^0.7.0 - version: 0.7.0 + specifier: 'catalog:' + version: 1.6.0 '@logtape/logtape': - specifier: ^0.4.0 - version: 0.4.3 + specifier: 'catalog:' + version: 2.2.1 cleye: specifier: 'catalog:' - version: 1.3.0 + version: 2.6.0 jiti: specifier: 'catalog:' version: 2.7.0 @@ -255,10 +267,10 @@ importers: version: 22.20.0 typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/compile: dependencies: @@ -274,10 +286,10 @@ importers: version: 22.20.0 typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/contract: dependencies: @@ -290,10 +302,10 @@ importers: version: 22.20.0 typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/core: dependencies: @@ -318,10 +330,10 @@ importers: version: 22.20.0 typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/ingest: dependencies: @@ -337,10 +349,10 @@ importers: version: 22.20.0 typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/migrate: dependencies: @@ -365,10 +377,10 @@ importers: version: 22.20.0 typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/pipeline: dependencies: @@ -378,21 +390,21 @@ importers: devDependencies: '@types/node': specifier: 'catalog:' - version: 20.19.41 + version: 22.20.0 tsdown: specifier: 'catalog:' - version: 0.22.3(typescript@5.9.3) + version: 0.22.3(typescript@6.0.3) typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 1.6.1(@types/node@20.19.41) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/schema: dependencies: ajv: - specifier: ^8.17.1 + specifier: ^8.20.0 version: 8.20.0 devDependencies: '@types/node': @@ -400,10 +412,10 @@ importers: version: 22.20.0 typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/security: dependencies: @@ -416,10 +428,10 @@ importers: version: 22.20.0 typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) packages/store: dependencies: @@ -438,10 +450,10 @@ importers: version: 22.20.0 typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 vitest: specifier: 'catalog:' - version: 3.2.6(@types/debug@4.1.13)(@types/node@22.20.0) + version: 4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) plugins/example-custom-adapter: devDependencies: @@ -453,7 +465,7 @@ importers: version: link:../../packages/core typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 plugins/example-logger: devDependencies: @@ -465,7 +477,7 @@ importers: version: link:../../packages/core typescript: specifier: 'catalog:' - version: 5.9.3 + version: 6.0.3 packages: @@ -654,13 +666,13 @@ packages: '@chevrotain/types@11.1.2': resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} - '@clack/core@0.3.5': - resolution: {integrity: sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==} + '@clack/core@1.4.2': + resolution: {integrity: sha512-0Ty/1Gfm+Kb07sXcuESjyKfwEhSy4Ns1AgeEisHb/bDY5fWme0tTeTkU14T1Gmcs17YIjB/teiDe4uaCghbYqQ==} + engines: {node: '>= 20.12.0'} - '@clack/prompts@0.7.0': - resolution: {integrity: sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==} - bundledDependencies: - - is-unicode-supported + '@clack/prompts@1.6.0': + resolution: {integrity: sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==} + engines: {node: '>= 20.12.0'} '@docsearch/css@3.8.2': resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} @@ -700,6 +712,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} @@ -712,6 +730,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.27.7': resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} @@ -724,6 +748,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.27.7': resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} @@ -736,6 +766,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.27.7': resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} @@ -748,6 +784,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.27.7': resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} @@ -760,6 +802,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.27.7': resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} @@ -772,6 +820,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} @@ -784,6 +838,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} @@ -796,6 +856,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.27.7': resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} @@ -808,6 +874,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.27.7': resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} @@ -820,6 +892,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.27.7': resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} @@ -832,6 +910,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.27.7': resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} @@ -844,6 +928,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.27.7': resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} @@ -856,6 +946,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.27.7': resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} @@ -868,6 +964,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.27.7': resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} @@ -880,6 +982,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.27.7': resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} @@ -892,12 +1000,24 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.27.7': resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} @@ -910,12 +1030,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} @@ -928,12 +1060,24 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} @@ -946,6 +1090,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.27.7': resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} @@ -958,6 +1108,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.27.7': resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} @@ -970,6 +1126,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.27.7': resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} @@ -982,6 +1144,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.27.7': resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} @@ -1018,10 +1186,6 @@ packages: '@types/node': optional: true - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1035,8 +1199,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@logtape/logtape@0.4.3': - resolution: {integrity: sha512-sUZAJpoYGHfRSVhExiGIzPFdLBuXcGWhkOOLieieaU2JDkTT7zx+f3i6i2YeyqA0bpPHcOGl5xBT2YD+lasWig==} + '@logtape/logtape@2.2.1': + resolution: {integrity: sha512-SkRptJUEAbGuf+/blXDxDa8sSq8no+lxJlfwPTMzrHIULOMsNZRaqT2qF3H9tmGOsaLYc+uF3orRCQfoH0qKzA==} '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -1344,8 +1508,8 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@sinclair/typebox@0.27.10': - resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -1482,9 +1646,6 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@20.19.41': - resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==} - '@types/node@22.20.0': resolution: {integrity: sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==} @@ -1510,49 +1671,34 @@ packages: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 - '@vitest/expect@1.6.1': - resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==} + '@vitest/expect@4.1.9': + resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} - '@vitest/expect@3.2.6': - resolution: {integrity: sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==} - - '@vitest/mocker@3.2.6': - resolution: {integrity: sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==} + '@vitest/mocker@4.1.9': + resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@3.2.6': - resolution: {integrity: sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==} - - '@vitest/runner@1.6.1': - resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} - - '@vitest/runner@3.2.6': - resolution: {integrity: sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==} - - '@vitest/snapshot@1.6.1': - resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} - - '@vitest/snapshot@3.2.6': - resolution: {integrity: sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==} + '@vitest/pretty-format@4.1.9': + resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} - '@vitest/spy@1.6.1': - resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} + '@vitest/runner@4.1.9': + resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==} - '@vitest/spy@3.2.6': - resolution: {integrity: sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==} + '@vitest/snapshot@4.1.9': + resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} - '@vitest/utils@1.6.1': - resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} + '@vitest/spy@4.1.9': + resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} - '@vitest/utils@3.2.6': - resolution: {integrity: sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==} + '@vitest/utils@4.1.9': + resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} '@vue/compiler-core@3.5.38': resolution: {integrity: sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==} @@ -1646,10 +1792,6 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} - acorn-walk@8.3.5: - resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} - engines: {node: '>=0.4.0'} - acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} @@ -1682,10 +1824,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - ansis@4.3.1: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} @@ -1703,9 +1841,6 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -1772,12 +1907,8 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chai@4.5.0: - resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} - engines: {node: '>=4'} - - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} character-entities-html4@2.1.0: @@ -1792,19 +1923,12 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - - check-error@2.1.3: - resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} - engines: {node: '>= 16'} - chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - cleye@1.3.0: - resolution: {integrity: sha512-IZ0mRzFQeeQwQk6gT1KIjxa0U86VdFmVB+EIlW3t1Wl1jZShTuWbCu11LcfOA6kODYJ2K7AwgVEb4b7TxN/s2A==} + cleye@2.6.0: + resolution: {integrity: sha512-u0SQCsega/ox+2GSuUlG6wvA9c2FtH8sPmv9G9Q3JRTs7FK6+LtaziRAQgx7lrJ1J7bOd3palhwgZKMg8R6JbQ==} cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} @@ -1851,6 +1975,9 @@ packages: resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} engines: {node: '>=18'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -2051,14 +2178,6 @@ packages: decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} - engines: {node: '>=6'} - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} @@ -2080,10 +2199,6 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2141,8 +2256,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.2.0: + resolution: {integrity: sha512-3lGxdTXCLfe1MYfTz1y2ksAAUM4NAOP6rPEjxGJVKO7TZ5+tvHCaQWGpC4Y3IXvW3ece0Cz1cIP4FWBxOnGCTQ==} es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} @@ -2156,6 +2271,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.27.7: resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} @@ -2195,10 +2315,6 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -2230,9 +2346,18 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.2: resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -2298,9 +2423,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2309,10 +2431,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - get-tsconfig@5.0.0-beta.5: resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} engines: {node: '>=20.20.0'} @@ -2374,10 +2492,6 @@ packages: resolution: {integrity: sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==} hasBin: true - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -2442,10 +2556,6 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-subdir@1.2.0: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} @@ -2472,9 +2582,6 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -2528,10 +2635,6 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - local-pkg@0.5.1: - resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} - engines: {node: '>=14'} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -2545,12 +2648,6 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -2603,9 +2700,6 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2695,10 +2789,6 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -2734,10 +2824,6 @@ packages: non-layered-tidy-tree-layout@2.0.2: resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2757,10 +2843,6 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - oniguruma-to-es@3.1.1: resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} @@ -2775,10 +2857,6 @@ packages: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -2812,10 +2890,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} @@ -2826,19 +2900,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -2908,10 +2972,6 @@ packages: resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==} engines: {node: '>=20'} - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - property-information@7.2.0: resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} @@ -2944,9 +3004,6 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -3143,8 +3200,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -3165,16 +3222,6 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - - strip-literal@2.1.1: - resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} - - strip-literal@3.1.0: - resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - stylis@4.4.0: resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} @@ -3194,8 +3241,8 @@ packages: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} - terminal-columns@1.4.1: - resolution: {integrity: sha512-IKVL/itiMy947XWVv4IHV7a0KQXvKjj4ptbi7Ew9MPMcOLzkiQeyx3Gyvh62hKrfJ0RZc4M1nbhzjNM39Kyujw==} + terminal-columns@2.0.0: + resolution: {integrity: sha512-6IByuUjyNZJXUtwDNm+OIe62zgwwaRbH+WMNTcx05O2G5V9WhvluAAHJY8OvUdwmzMPpqAD/7EUpGdI6ae1aiQ==} thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} @@ -3218,24 +3265,8 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} - engines: {node: '>=14.0.0'} - - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.4: - resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} to-regex-range@5.0.1: @@ -3322,19 +3353,15 @@ packages: typescript: optional: true - type-detect@4.1.0: - resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} - engines: {node: '>=4'} - - type-flag@3.0.0: - resolution: {integrity: sha512-3YaYwMseXCAhBB14RXW5cRQfJQlEknS6i4C8fCfeUdS3ihG9EdccdR9kt3vP73ZdeTGmPb4bZtkDn5XMIn1DLA==} + type-flag@4.5.0: + resolution: {integrity: sha512-1aLzxcL6u1O9XHieAJBBX9U4QzwzDTWN0ER9M7QQSvS24NBmGM+N8FcghlgHAzOvDlEEpOx4hEml9CVcDnflcw==} type-is@2.1.0: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true @@ -3393,16 +3420,6 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-node@1.6.1: - resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - vite@5.4.21: resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} engines: {node: ^18.0.0 || >=20.0.0} @@ -3434,6 +3451,46 @@ packages: terser: optional: true + vite@6.4.3: + resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + 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 + vitepress-plugin-group-icons@1.7.5: resolution: {integrity: sha512-QzcroUuIiVKyXpmEiiHVbfRTQIy9Zbwxpk5JC/zavO8mavitwumz2RZWlwTchMCCHducYyPptkYvXvdnNUWkog==} peerDependencies: @@ -3464,51 +3521,39 @@ packages: postcss: optional: true - vitest@1.6.1: - resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==} - engines: {node: ^18.0.0 || >=20.0.0} + vitest@4.1.9: + resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.1 - '@vitest/ui': 1.6.1 + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.9 + '@vitest/browser-preview': 4.1.9 + '@vitest/browser-webdriverio': 4.1.9 + '@vitest/coverage-istanbul': 4.1.9 + '@vitest/coverage-v8': 4.1.9 + '@vitest/ui': 4.1.9 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': + '@opentelemetry/api': optional: true - happy-dom: + '@types/node': optional: true - jsdom: + '@vitest/browser-playwright': optional: true - - vitest@3.2.6: - resolution: {integrity: sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.6 - '@vitest/ui': 3.2.6 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': + '@vitest/browser-preview': optional: true - '@types/debug': + '@vitest/browser-webdriverio': optional: true - '@types/node': + '@vitest/coverage-istanbul': optional: true - '@vitest/browser': + '@vitest/coverage-v8': optional: true '@vitest/ui': optional: true @@ -3554,10 +3599,6 @@ packages: resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} engines: {node: '>=12'} - yocto-queue@1.2.2: - resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} - engines: {node: '>=12.20'} - zod-to-json-schema@3.25.2: resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: @@ -3875,15 +3916,16 @@ snapshots: '@chevrotain/types@11.1.2': {} - '@clack/core@0.3.5': + '@clack/core@1.4.2': dependencies: - picocolors: 1.1.1 + fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 - '@clack/prompts@0.7.0': + '@clack/prompts@1.6.0': dependencies: - '@clack/core': 0.3.5 - picocolors: 1.1.1 + '@clack/core': 1.4.2 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 '@docsearch/css@3.8.2': {} @@ -3929,147 +3971,225 @@ snapshots: '@esbuild/aix-ppc64@0.21.5': optional: true + '@esbuild/aix-ppc64@0.25.12': + optional: true + '@esbuild/aix-ppc64@0.27.7': optional: true '@esbuild/android-arm64@0.21.5': optional: true + '@esbuild/android-arm64@0.25.12': + optional: true + '@esbuild/android-arm64@0.27.7': optional: true '@esbuild/android-arm@0.21.5': optional: true + '@esbuild/android-arm@0.25.12': + optional: true + '@esbuild/android-arm@0.27.7': optional: true '@esbuild/android-x64@0.21.5': optional: true + '@esbuild/android-x64@0.25.12': + optional: true + '@esbuild/android-x64@0.27.7': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true + '@esbuild/darwin-arm64@0.25.12': + optional: true + '@esbuild/darwin-arm64@0.27.7': optional: true '@esbuild/darwin-x64@0.21.5': optional: true + '@esbuild/darwin-x64@0.25.12': + optional: true + '@esbuild/darwin-x64@0.27.7': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true + '@esbuild/freebsd-arm64@0.25.12': + optional: true + '@esbuild/freebsd-arm64@0.27.7': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true + '@esbuild/freebsd-x64@0.25.12': + optional: true + '@esbuild/freebsd-x64@0.27.7': optional: true '@esbuild/linux-arm64@0.21.5': optional: true + '@esbuild/linux-arm64@0.25.12': + optional: true + '@esbuild/linux-arm64@0.27.7': optional: true '@esbuild/linux-arm@0.21.5': optional: true + '@esbuild/linux-arm@0.25.12': + optional: true + '@esbuild/linux-arm@0.27.7': optional: true '@esbuild/linux-ia32@0.21.5': optional: true + '@esbuild/linux-ia32@0.25.12': + optional: true + '@esbuild/linux-ia32@0.27.7': optional: true '@esbuild/linux-loong64@0.21.5': optional: true + '@esbuild/linux-loong64@0.25.12': + optional: true + '@esbuild/linux-loong64@0.27.7': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true + '@esbuild/linux-mips64el@0.25.12': + optional: true + '@esbuild/linux-mips64el@0.27.7': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true + '@esbuild/linux-ppc64@0.25.12': + optional: true + '@esbuild/linux-ppc64@0.27.7': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true + '@esbuild/linux-riscv64@0.25.12': + optional: true + '@esbuild/linux-riscv64@0.27.7': optional: true '@esbuild/linux-s390x@0.21.5': optional: true + '@esbuild/linux-s390x@0.25.12': + optional: true + '@esbuild/linux-s390x@0.27.7': optional: true '@esbuild/linux-x64@0.21.5': optional: true + '@esbuild/linux-x64@0.25.12': + optional: true + '@esbuild/linux-x64@0.27.7': optional: true + '@esbuild/netbsd-arm64@0.25.12': + optional: true + '@esbuild/netbsd-arm64@0.27.7': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true + '@esbuild/netbsd-x64@0.25.12': + optional: true + '@esbuild/netbsd-x64@0.27.7': optional: true + '@esbuild/openbsd-arm64@0.25.12': + optional: true + '@esbuild/openbsd-arm64@0.27.7': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true + '@esbuild/openbsd-x64@0.25.12': + optional: true + '@esbuild/openbsd-x64@0.27.7': optional: true + '@esbuild/openharmony-arm64@0.25.12': + optional: true + '@esbuild/openharmony-arm64@0.27.7': optional: true '@esbuild/sunos-x64@0.21.5': optional: true + '@esbuild/sunos-x64@0.25.12': + optional: true + '@esbuild/sunos-x64@0.27.7': optional: true '@esbuild/win32-arm64@0.21.5': optional: true + '@esbuild/win32-arm64@0.25.12': + optional: true + '@esbuild/win32-arm64@0.27.7': optional: true '@esbuild/win32-ia32@0.21.5': optional: true + '@esbuild/win32-ia32@0.25.12': + optional: true + '@esbuild/win32-ia32@0.27.7': optional: true '@esbuild/win32-x64@0.21.5': optional: true + '@esbuild/win32-x64@0.25.12': + optional: true + '@esbuild/win32-x64@0.27.7': optional: true @@ -4104,10 +4224,6 @@ snapshots: optionalDependencies: '@types/node': 22.20.0 - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.10 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4122,7 +4238,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@logtape/logtape@0.4.3': {} + '@logtape/logtape@2.2.1': {} '@manypkg/find-root@1.1.0': dependencies: @@ -4368,7 +4484,7 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@sinclair/typebox@0.27.10': {} + '@standard-schema/spec@1.1.0': {} '@tybys/wasm-util@0.10.3': dependencies: @@ -4530,10 +4646,6 @@ snapshots: '@types/node@12.20.55': {} - '@types/node@20.19.41': - dependencies: - undici-types: 6.21.0 - '@types/node@22.20.0': dependencies: undici-types: 6.21.0 @@ -4552,81 +4664,51 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@22.20.0))(vue@3.5.38(typescript@5.9.3))': + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@22.20.0))(vue@3.5.38(typescript@6.0.3))': dependencies: vite: 5.4.21(@types/node@22.20.0) - vue: 3.5.38(typescript@5.9.3) + vue: 3.5.38(typescript@6.0.3) - '@vitest/expect@1.6.1': - dependencies: - '@vitest/spy': 1.6.1 - '@vitest/utils': 1.6.1 - chai: 4.5.0 - - '@vitest/expect@3.2.6': + '@vitest/expect@4.1.9': dependencies: + '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 3.2.6 - '@vitest/utils': 3.2.6 - chai: 5.3.3 - tinyrainbow: 2.0.0 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 + chai: 6.2.2 + tinyrainbow: 3.1.0 - '@vitest/mocker@3.2.6(vite@5.4.21(@types/node@22.20.0))': + '@vitest/mocker@4.1.9(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0))': dependencies: - '@vitest/spy': 3.2.6 + '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 5.4.21(@types/node@22.20.0) - - '@vitest/pretty-format@3.2.6': - dependencies: - tinyrainbow: 2.0.0 + vite: 6.4.3(@types/node@22.20.0)(jiti@2.7.0) - '@vitest/runner@1.6.1': + '@vitest/pretty-format@4.1.9': dependencies: - '@vitest/utils': 1.6.1 - p-limit: 5.0.0 - pathe: 1.1.2 + tinyrainbow: 3.1.0 - '@vitest/runner@3.2.6': + '@vitest/runner@4.1.9': dependencies: - '@vitest/utils': 3.2.6 + '@vitest/utils': 4.1.9 pathe: 2.0.3 - strip-literal: 3.1.0 - - '@vitest/snapshot@1.6.1': - dependencies: - magic-string: 0.30.21 - pathe: 1.1.2 - pretty-format: 29.7.0 - '@vitest/snapshot@3.2.6': + '@vitest/snapshot@4.1.9': dependencies: - '@vitest/pretty-format': 3.2.6 + '@vitest/pretty-format': 4.1.9 + '@vitest/utils': 4.1.9 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@1.6.1': - dependencies: - tinyspy: 2.2.1 - - '@vitest/spy@3.2.6': - dependencies: - tinyspy: 4.0.4 - - '@vitest/utils@1.6.1': - dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + '@vitest/spy@4.1.9': {} - '@vitest/utils@3.2.6': + '@vitest/utils@4.1.9': dependencies: - '@vitest/pretty-format': 3.2.6 - loupe: 3.2.1 - tinyrainbow: 2.0.0 + '@vitest/pretty-format': 4.1.9 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 '@vue/compiler-core@3.5.38': dependencies: @@ -4692,28 +4774,28 @@ snapshots: '@vue/shared': 3.5.38 csstype: 3.2.3 - '@vue/server-renderer@3.5.38(vue@3.5.38(typescript@5.9.3))': + '@vue/server-renderer@3.5.38(vue@3.5.38(typescript@6.0.3))': dependencies: '@vue/compiler-ssr': 3.5.38 '@vue/shared': 3.5.38 - vue: 3.5.38(typescript@5.9.3) + vue: 3.5.38(typescript@6.0.3) '@vue/shared@3.5.38': {} - '@vueuse/core@12.8.2(typescript@5.9.3)': + '@vueuse/core@12.8.2(typescript@6.0.3)': dependencies: '@types/web-bluetooth': 0.0.21 '@vueuse/metadata': 12.8.2 - '@vueuse/shared': 12.8.2(typescript@5.9.3) - vue: 3.5.38(typescript@5.9.3) + '@vueuse/shared': 12.8.2(typescript@6.0.3) + vue: 3.5.38(typescript@6.0.3) transitivePeerDependencies: - typescript - '@vueuse/integrations@12.8.2(focus-trap@7.8.0)(typescript@5.9.3)': + '@vueuse/integrations@12.8.2(focus-trap@7.8.0)(typescript@6.0.3)': dependencies: - '@vueuse/core': 12.8.2(typescript@5.9.3) - '@vueuse/shared': 12.8.2(typescript@5.9.3) - vue: 3.5.38(typescript@5.9.3) + '@vueuse/core': 12.8.2(typescript@6.0.3) + '@vueuse/shared': 12.8.2(typescript@6.0.3) + vue: 3.5.38(typescript@6.0.3) optionalDependencies: focus-trap: 7.8.0 transitivePeerDependencies: @@ -4721,9 +4803,9 @@ snapshots: '@vueuse/metadata@12.8.2': {} - '@vueuse/shared@12.8.2(typescript@5.9.3)': + '@vueuse/shared@12.8.2(typescript@6.0.3)': dependencies: - vue: 3.5.38(typescript@5.9.3) + vue: 3.5.38(typescript@6.0.3) transitivePeerDependencies: - typescript @@ -4732,10 +4814,6 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-walk@8.3.5: - dependencies: - acorn: 8.16.0 - acorn@8.16.0: {} ajv-formats@3.0.1(ajv@8.20.0): @@ -4774,8 +4852,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - ansis@4.3.1: {} any-promise@1.3.0: {} @@ -4788,8 +4864,6 @@ snapshots: array-union@2.1.0: {} - assertion-error@1.1.0: {} - assertion-error@2.0.1: {} ast-kit@3.0.0: @@ -4855,23 +4929,7 @@ snapshots: ccount@2.0.1: {} - chai@4.5.0: - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.1.0 - - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.3 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 + chai@6.2.2: {} character-entities-html4@2.1.0: {} @@ -4881,20 +4939,14 @@ snapshots: chardet@2.1.1: {} - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 - - check-error@2.1.3: {} - chokidar@4.0.3: dependencies: readdirp: 4.1.2 - cleye@1.3.0: + cleye@2.6.0: dependencies: - terminal-columns: 1.4.1 - type-flag: 3.0.0 + terminal-columns: 2.0.0 + type-flag: 4.5.0 cliui@8.0.1: dependencies: @@ -4926,6 +4978,8 @@ snapshots: content-type@2.0.0: {} + convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} cookie@0.7.2: {} @@ -5149,12 +5203,6 @@ snapshots: dependencies: character-entities: 2.0.2 - deep-eql@4.1.4: - dependencies: - type-detect: 4.1.0 - - deep-eql@5.0.2: {} - defu@6.1.7: {} delaunator@5.1.0: @@ -5171,8 +5219,6 @@ snapshots: dependencies: dequal: 2.0.3 - diff-sequences@29.6.3: {} - dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -5212,7 +5258,7 @@ snapshots: es-errors@1.3.0: {} - es-module-lexer@1.7.0: {} + es-module-lexer@2.2.0: {} es-object-atoms@1.1.2: dependencies: @@ -5246,6 +5292,35 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + esbuild@0.27.7: optionalDependencies: '@esbuild/aix-ppc64': 0.27.7 @@ -5297,18 +5372,6 @@ snapshots: dependencies: eventsource-parser: 3.1.0 - execa@8.0.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - expect-type@1.3.0: {} express-rate-limit@8.5.2(express@5.2.1): @@ -5367,8 +5430,18 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-uri@3.1.2: {} + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -5436,8 +5509,6 @@ snapshots: get-caller-file@2.0.5: {} - get-func-name@2.0.2: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5456,8 +5527,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.2 - get-stream@8.0.1: {} - get-tsconfig@5.0.0-beta.5: dependencies: resolve-pkg-maps: 1.0.0 @@ -5530,8 +5599,6 @@ snapshots: human-id@4.2.0: {} - human-signals@5.0.0: {} - iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -5572,8 +5639,6 @@ snapshots: is-promise@4.0.0: {} - is-stream@3.0.0: {} - is-subdir@1.2.0: dependencies: better-path-resolve: 1.0.0 @@ -5590,8 +5655,6 @@ snapshots: joycon@3.1.1: {} - js-tokens@9.0.1: {} - js-yaml@3.14.2: dependencies: argparse: 1.0.10 @@ -5633,11 +5696,6 @@ snapshots: load-tsconfig@0.2.5: {} - local-pkg@0.5.1: - dependencies: - mlly: 1.8.2 - pkg-types: 1.3.1 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -5648,12 +5706,6 @@ snapshots: longest-streak@3.1.0: {} - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 - - loupe@3.2.1: {} - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5742,8 +5794,6 @@ snapshots: merge-descriptors@2.0.0: {} - merge-stream@2.0.0: {} - merge2@1.4.1: {} mermaid@11.16.0: @@ -5925,8 +5975,6 @@ snapshots: dependencies: mime-db: 1.54.0 - mimic-fn@4.0.0: {} - minimatch@10.2.5: dependencies: brace-expansion: 5.0.6 @@ -5959,10 +6007,6 @@ snapshots: non-layered-tidy-tree-layout@2.0.2: optional: true - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -5977,10 +6021,6 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - oniguruma-to-es@3.1.1: dependencies: emoji-regex-xs: 1.0.0 @@ -5997,10 +6037,6 @@ snapshots: dependencies: p-try: 2.2.0 - p-limit@5.0.0: - dependencies: - yocto-queue: 1.2.2 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -6023,22 +6059,14 @@ snapshots: path-key@3.1.1: {} - path-key@4.0.0: {} - path-to-regexp@6.3.0: {} path-to-regexp@8.4.2: {} path-type@4.0.0: {} - pathe@1.1.2: {} - pathe@2.0.3: {} - pathval@1.1.1: {} - - pathval@2.0.1: {} - perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -6085,12 +6113,6 @@ snapshots: pretty-bytes@7.1.0: {} - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - property-information@7.2.0: {} proxy-addr@2.0.7: @@ -6119,8 +6141,6 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - react-is@18.3.1: {} - read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -6187,7 +6207,7 @@ snapshots: robust-predicates@3.0.3: {} - rolldown-plugin-dts@0.26.0(rolldown@1.1.3)(typescript@5.9.3): + rolldown-plugin-dts@0.26.0(rolldown@1.1.3)(typescript@6.0.3): dependencies: '@babel/generator': 8.0.0 '@babel/helper-validator-identifier': 8.0.2 @@ -6199,7 +6219,7 @@ snapshots: obug: 2.1.3 rolldown: 1.1.3 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - oxc-resolver @@ -6390,7 +6410,7 @@ snapshots: statuses@2.0.2: {} - std-env@3.10.0: {} + std-env@4.1.0: {} string-width@4.2.3: dependencies: @@ -6411,16 +6431,6 @@ snapshots: strip-bom@3.0.0: {} - strip-final-newline@3.0.0: {} - - strip-literal@2.1.1: - dependencies: - js-tokens: 9.0.1 - - strip-literal@3.1.0: - dependencies: - js-tokens: 9.0.1 - stylis@4.4.0: {} sucrase@3.35.1: @@ -6441,7 +6451,7 @@ snapshots: term-size@2.2.1: {} - terminal-columns@1.4.1: {} + terminal-columns@2.0.0: {} thenify-all@1.6.0: dependencies: @@ -6462,15 +6472,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - tinypool@0.8.4: {} - - tinypool@1.1.1: {} - - tinyrainbow@2.0.0: {} - - tinyspy@2.2.1: {} - - tinyspy@4.0.4: {} + tinyrainbow@3.1.0: {} to-regex-range@5.0.1: dependencies: @@ -6490,7 +6492,7 @@ snapshots: ts-interface-checker@0.1.13: {} - tsdown@0.22.3(typescript@5.9.3): + tsdown@0.22.3(typescript@6.0.3): dependencies: ansis: 4.3.1 cac: 7.0.0 @@ -6501,14 +6503,14 @@ snapshots: obug: 2.1.3 picomatch: 4.0.4 rolldown: 1.1.3 - rolldown-plugin-dts: 0.26.0(rolldown@1.1.3)(typescript@5.9.3) + rolldown-plugin-dts: 0.26.0(rolldown@1.1.3)(typescript@6.0.3) semver: 7.8.5 tinyexec: 1.2.4 tinyglobby: 0.2.17 tree-kill: 1.2.2 unconfig-core: 7.5.0 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - '@ts-macro/tsc' - '@typescript/native-preview' @@ -6518,7 +6520,7 @@ snapshots: tslib@2.8.1: optional: true - tsup@8.5.1(jiti@2.7.0)(postcss@8.5.15)(typescript@5.9.3): + tsup@8.5.1(jiti@2.7.0)(postcss@8.5.15)(typescript@6.0.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 @@ -6539,16 +6541,14 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: postcss: 8.5.15 - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - jiti - supports-color - tsx - yaml - type-detect@4.1.0: {} - - type-flag@3.0.0: {} + type-flag@4.5.0: {} type-is@2.1.0: dependencies: @@ -6556,7 +6556,7 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typescript@5.9.3: {} + typescript@6.0.3: {} uc.micro@2.1.0: {} @@ -6626,77 +6626,27 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@1.6.1(@types/node@20.19.41): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - pathe: 1.1.2 - picocolors: 1.1.1 - vite: 5.4.21(@types/node@20.19.41) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite-node@1.6.1(@types/node@22.20.0): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - pathe: 1.1.2 - picocolors: 1.1.1 - vite: 5.4.21(@types/node@22.20.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite-node@3.2.4(@types/node@22.20.0): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 5.4.21(@types/node@22.20.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite@5.4.21(@types/node@20.19.41): + vite@5.4.21(@types/node@22.20.0): dependencies: esbuild: 0.21.5 postcss: 8.5.15 rollup: 4.61.1 optionalDependencies: - '@types/node': 20.19.41 + '@types/node': 22.20.0 fsevents: 2.3.3 - vite@5.4.21(@types/node@22.20.0): + vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0): dependencies: - esbuild: 0.21.5 + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 postcss: 8.5.15 rollup: 4.61.1 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 22.20.0 fsevents: 2.3.3 + jiti: 2.7.0 vitepress-plugin-group-icons@1.7.5(vite@5.4.21(@types/node@22.20.0)): dependencies: @@ -6725,14 +6675,14 @@ snapshots: transitivePeerDependencies: - supports-color - vitepress-plugin-mermaid@2.0.17(mermaid@11.16.0)(vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3)): + vitepress-plugin-mermaid@2.0.17(mermaid@11.16.0)(vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@6.0.3)): dependencies: mermaid: 11.16.0 - vitepress: 1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3) + vitepress: 1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@6.0.3) optionalDependencies: '@mermaid-js/mermaid-mindmap': 9.3.0 - vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@5.9.3): + vitepress@1.6.4(@algolia/client-search@5.55.0)(@types/node@22.20.0)(postcss@8.5.15)(search-insights@2.17.3)(typescript@6.0.3): dependencies: '@docsearch/css': 3.8.2 '@docsearch/js': 3.8.2(@algolia/client-search@5.55.0)(search-insights@2.17.3) @@ -6741,17 +6691,17 @@ snapshots: '@shikijs/transformers': 2.5.0 '@shikijs/types': 2.5.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@22.20.0))(vue@3.5.38(typescript@5.9.3)) + '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@22.20.0))(vue@3.5.38(typescript@6.0.3)) '@vue/devtools-api': 7.7.9 '@vue/shared': 3.5.38 - '@vueuse/core': 12.8.2(typescript@5.9.3) - '@vueuse/integrations': 12.8.2(focus-trap@7.8.0)(typescript@5.9.3) + '@vueuse/core': 12.8.2(typescript@6.0.3) + '@vueuse/integrations': 12.8.2(focus-trap@7.8.0)(typescript@6.0.3) focus-trap: 7.8.0 mark.js: 8.11.1 minisearch: 7.2.0 shiki: 2.5.0 vite: 5.4.21(@types/node@22.20.0) - vue: 3.5.38(typescript@5.9.3) + vue: 3.5.38(typescript@6.0.3) optionalDependencies: postcss: 8.5.15 transitivePeerDependencies: @@ -6781,122 +6731,42 @@ snapshots: - typescript - universal-cookie - vitest@1.6.1(@types/node@20.19.41): - dependencies: - '@vitest/expect': 1.6.1 - '@vitest/runner': 1.6.1 - '@vitest/snapshot': 1.6.1 - '@vitest/spy': 1.6.1 - '@vitest/utils': 1.6.1 - acorn-walk: 8.3.5 - chai: 4.5.0 - debug: 4.4.3 - execa: 8.0.1 - local-pkg: 0.5.1 - magic-string: 0.30.21 - pathe: 1.1.2 - picocolors: 1.1.1 - std-env: 3.10.0 - strip-literal: 2.1.1 - tinybench: 2.9.0 - tinypool: 0.8.4 - vite: 5.4.21(@types/node@20.19.41) - vite-node: 1.6.1(@types/node@20.19.41) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 20.19.41 - transitivePeerDependencies: - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vitest@1.6.1(@types/node@22.20.0): - dependencies: - '@vitest/expect': 1.6.1 - '@vitest/runner': 1.6.1 - '@vitest/snapshot': 1.6.1 - '@vitest/spy': 1.6.1 - '@vitest/utils': 1.6.1 - acorn-walk: 8.3.5 - chai: 4.5.0 - debug: 4.4.3 - execa: 8.0.1 - local-pkg: 0.5.1 - magic-string: 0.30.21 - pathe: 1.1.2 - picocolors: 1.1.1 - std-env: 3.10.0 - strip-literal: 2.1.1 - tinybench: 2.9.0 - tinypool: 0.8.4 - vite: 5.4.21(@types/node@22.20.0) - vite-node: 1.6.1(@types/node@22.20.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 22.20.0 - transitivePeerDependencies: - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vitest@3.2.6(@types/debug@4.1.13)(@types/node@22.20.0): + vitest@4.1.9(@types/node@22.20.0)(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)): dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.6 - '@vitest/mocker': 3.2.6(vite@5.4.21(@types/node@22.20.0)) - '@vitest/pretty-format': 3.2.6 - '@vitest/runner': 3.2.6 - '@vitest/snapshot': 3.2.6 - '@vitest/spy': 3.2.6 - '@vitest/utils': 3.2.6 - chai: 5.3.3 - debug: 4.4.3 + '@vitest/expect': 4.1.9 + '@vitest/mocker': 4.1.9(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)) + '@vitest/pretty-format': 4.1.9 + '@vitest/runner': 4.1.9 + '@vitest/snapshot': 4.1.9 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 + es-module-lexer: 2.2.0 expect-type: 1.3.0 magic-string: 0.30.21 + obug: 2.1.3 pathe: 2.0.3 picomatch: 4.0.4 - std-env: 3.10.0 + std-env: 4.1.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.2.4 tinyglobby: 0.2.17 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 5.4.21(@types/node@22.20.0) - vite-node: 3.2.4(@types/node@22.20.0) + tinyrainbow: 3.1.0 + vite: 6.4.3(@types/node@22.20.0)(jiti@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/debug': 4.1.13 '@types/node': 22.20.0 transitivePeerDependencies: - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - vue@3.5.38(typescript@5.9.3): + vue@3.5.38(typescript@6.0.3): dependencies: '@vue/compiler-dom': 3.5.38 '@vue/compiler-sfc': 3.5.38 '@vue/runtime-dom': 3.5.38 - '@vue/server-renderer': 3.5.38(vue@3.5.38(typescript@5.9.3)) + '@vue/server-renderer': 3.5.38(vue@3.5.38(typescript@6.0.3)) '@vue/shared': 3.5.38 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 which@2.0.2: dependencies: @@ -6929,8 +6799,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yocto-queue@1.2.2: {} - zod-to-json-schema@3.25.2(zod@4.4.3): dependencies: zod: 4.4.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b5b398f..ad2daef 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,14 +6,13 @@ allowBuilds: esbuild: true catalog: - typescript: '^5.9.3' - vitest: '^3.2.6' + typescript: '^6.0.0' + vitest: '^4.0.0' tsdown: '^0.22.3' - jiti: '^2.0.0' - tsx: '^4.0.0' - logtape: '^0.4.0' - cleye: '^1.3.0' - zod: '^4.0.0' - '@types/node': '^22.0.0' - cac: '^6.7.14' - chalk: '^5.3.0' \ No newline at end of file + jiti: '^2.7.0' + tsx: '^4.22.4' + '@logtape/logtape': '^2.0.0' + cleye: '^2.0.0' + zod: '^4.4.3' + '@types/node': '^22.20.0' + '@clack/prompts': '^1.0.0' diff --git a/tsconfig.json b/tsconfig.json index faca084..a65971e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "lib": ["ES2022"], + "types": ["node"], "declaration": true, "declarationMap": true, "sourceMap": true, From fe5e213b55661808364be350a007248a67a2d25c Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:04:21 +0200 Subject: [PATCH 75/90] fix(readme): swap logo srcset, npx primary, Tier-1/Tier-2 framing, arch section, sponsor footer-only --- README.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c537baa..389ec72 100644 --- a/README.md +++ b/README.md @@ -2,27 +2,27 @@

- - AgentPlugins + + AgentPlugins

> Write AI agent plugins once, ship to any harness. -Install any plugin into every supported AI agent with one command — Claude Code, Codex, Copilot, Gemini, Kimi, OpenCode, and Pi Mono. +Install any plugin into every supported AI agent — **Tier-1:** Claude Code, Codex, OpenCode, Pi Mono. **Tier-2:** Copilot, Gemini, Kimi. ```bash -curl -fsSL https://agentplugins.pages.dev/install.sh | bash +npx @agentplugins/cli add user/awesome-plugin ``` -Or run ad-hoc with `npx`: - ```bash -npx @agentplugins/cli add user/awesome-plugin +agentplugins add sigilco/agentplugins-ponytail ``` +Or install the CLI globally first: + ```bash -agentplugins add sigilco/agentplugins-ponytail +curl -fsSL https://agentplugins.pages.dev/install.sh | bash ``` ## Create a plugin @@ -40,10 +40,18 @@ Porting an existing plugin? → [agentplugins.pages.dev/guide/porting](https://a ## Supported agents -Works with Claude Code, Codex, Copilot, Gemini, Kimi, OpenCode, and Pi Mono. +**Tier-1** (full capability parity): Claude Code, Codex, OpenCode, Pi Mono. + +**Tier-2** (skills + commands, subset of hooks): Copilot, Gemini, Kimi. Capability comparison → [agentplugins.pages.dev/guide/capability-matrix](https://agentplugins.pages.dev/guide/capability-matrix) +## Architecture + +One manifest → seven adapters. Each adapter owns its output format; the `@agentplugins/core` compiler routes capability expressions to harness-native primitives and emits a WARN for any gap. + +Full detail → [ARCHITECTURE.md](./ARCHITECTURE.md) + ## Documentation Full docs → [agentplugins.pages.dev](https://agentplugins.pages.dev) @@ -52,6 +60,4 @@ LLMs.txt for AI agents → [agentplugins.pages.dev/llms.txt](https://agentplugin --- -Sponsor AgentPlugins → [buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj](https://buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj) - -Apache-2.0 · [GitHub](https://github.com/sigilco/agentplugins) +Apache-2.0 · [GitHub](https://github.com/sigilco/agentplugins) · [Sponsor](https://buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj) From e5624c7aff17986d94fb99a6154bda179d58a189 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:04:37 +0200 Subject: [PATCH 76/90] fix(cli): bump scaffolded devDeps to ^0.5.0 in agentplugins init --- packages/cli/src/commands/init.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 4df5748..fce7b5b 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -365,8 +365,8 @@ function buildPackageJson(a: ScaffoldAnswers) { validate: 'agentplugins validate', }, devDependencies: { - '@agentplugins/core': '^0.2.0', - '@agentplugins/cli': '^0.2.0', + '@agentplugins/core': '^0.5.0', + '@agentplugins/cli': '^0.5.0', typescript: '^5.5.0', }, }; From 6011de8d5a4c5ae14ca44540bf1defaaa722f723 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:05:31 +0200 Subject: [PATCH 77/90] =?UTF-8?q?fix(docs):=20version=20strings=201.0.0?= =?UTF-8?q?=E2=86=920.6.0=20in=20quick-start,=20v0.4.0=E2=86=92v0.5.0=20in?= =?UTF-8?q?=20capability-matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/guide/capability-matrix.md | 10 +++++----- docs/guide/quick-start.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/guide/capability-matrix.md b/docs/guide/capability-matrix.md index eda1c61..38203dd 100644 --- a/docs/guide/capability-matrix.md +++ b/docs/guide/capability-matrix.md @@ -31,9 +31,9 @@ Legend: | `subagentStart` | ✅ | ✅ | ⚠️ | ✅ | OpenCode: intercept `subagent` tool calls with `preToolUse` matcher (subagents launch via the `subagent` tool). | Emits WARN on OpenCode. Pi maps to `agent.AgentStart` lifecycle event. | | `subagentStop` | ✅ | ✅ | ⚠️ | ✅ | OpenCode: detect via `postToolUse` / `postToolUseFailure` on the `subagent` tool. | Emits WARN on OpenCode. Pi maps to `agent.AgentStop` lifecycle event. Pi `stop`↔`subagentStop` collision fixed in v0.3.0. | | `tools[]` (first-class) | ⚠️ | ⚠️ | ✅ | ✅ | Claude/Codex: ship tools via `mcpServers` — works on all four harnesses (universal). | WARN emitted; OpenCode/Pi emit first-class `tools[]` natively. | -| `stop` / `continueWith` | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Each harness already has a `stop`-class lifecycle hook natively — emit nothing in portable manifests until v0.4.0 ships. | New universal primitive — v0.4.0; all-harness design. | -| Native-entry passthrough | n/a (JSON) | n/a (JSON) | ⚠️ | ⚠️ | OpenCode: drop a `.ts` file directly into `~/.config/opencode/plugins//` — Bun runs it as ESM, no codegen needed. | `nativeEntry` escape hatch — ships in v0.4.0; OpenCode native modules must be `.ts` (file-drop path). | -| Inline hook handlers | ✅ auto-wrap | ✅ auto-wrap | ✅ | ✅ | — | Codex/Kimi: auto-wrapped as Node.js command scripts (v0.4.0). | +| `stop` / `continueWith` | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Each harness already has a `stop`-class lifecycle hook natively — emit nothing in portable manifests until v0.5.0. | New universal primitive — v0.5.0; all-harness design. | +| Native-entry passthrough | n/a (JSON) | n/a (JSON) | ⚠️ | ⚠️ | OpenCode: drop a `.ts` file directly into `~/.config/opencode/plugins//` — Bun runs it as ESM, no codegen needed. | `nativeEntry` escape hatch — ships in v0.5.0; OpenCode native modules must be `.ts` (file-drop path). | +| Inline hook handlers | ✅ auto-wrap | ✅ auto-wrap | ✅ | ✅ | — | Codex/Kimi: auto-wrapped as Node.js command scripts (v0.5.0). | ## Additional harnesses @@ -45,7 +45,7 @@ Legend: | `tools[]` | ✅ | ✅ | ❌ | — | First-class tool emission. | | `mcpServers` | ❌ | ❌ | ❌ | Wire the MCP server directly into the harness's native config — no agentplugins path needed. | Not on the manifest path; harness-level wiring only. | -Kimi supported hooks: `preToolUse`, `userPromptSubmit`, `sessionStart`, `notification`, `permissionRequest`. Inline handlers auto-wrapped as Node.js command scripts (v0.4.0). +Kimi supported hooks: `preToolUse`, `userPromptSubmit`, `sessionStart`, `notification`, `permissionRequest`. Inline handlers auto-wrapped as Node.js command scripts (v0.5.0). ## Decision tree for authors @@ -58,7 +58,7 @@ Does universal codegen cover this capability across all four core harnesses? YES → use nativeEntry; emit WARN (not error) NO → is the gap TUI-grade fidelity only? YES → acceptable degradation; note in this matrix - NO → open a primitive proposal (v0.4.0+ scope) + NO → open a primitive proposal (v0.5.0+ scope) ``` --- diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md index 3ade64d..c232b73 100644 --- a/docs/guide/quick-start.md +++ b/docs/guide/quick-start.md @@ -17,7 +17,7 @@ Verify `agentplugins` is on your `PATH`: ```bash agentplugins --version -# agentplugins 1.0.0 +# agentplugins 0.6.0 ``` ## 2. Add a plugin from GitHub From d5b995fba9978fe53896095ceda460ce8161ee33 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:05:48 +0200 Subject: [PATCH 78/90] =?UTF-8?q?fix(scripts):=20install.sh=20comment=20do?= =?UTF-8?q?main=20agentplugins.dev=E2=86=92pages.dev;=20gitignore=20tmp/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ scripts/install.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5bb1fa8..5f5f477 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,9 @@ docs/public/* *.local *.backup +# Session / scratch files +tmp/ + # Deepwork session state (ephemeral) .slim/ .npmrc diff --git a/scripts/install.sh b/scripts/install.sh index b82d152..6ec5219 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -3,7 +3,7 @@ # install.sh — Install AgentPlugins CLI via curl # # Usage: -# curl -fsSL https://agentplugins.dev/install.sh | bash +# curl -fsSL https://agentplugins.pages.dev/install.sh | bash # curl -fsSL https://raw.githubusercontent.com/sigilco/agentplugins/main/scripts/install.sh | bash # # Flags: From c446fc3ca5d37c0993787cc953515699eb018ed9 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:06:25 +0200 Subject: [PATCH 79/90] fix(schema): update $id and hosted URL from agentplugins.dev to agentplugins.pages.dev --- packages/schema/__tests__/schema.test.ts | 2 +- packages/schema/schemas/adapter.schema.json | 2 +- packages/schema/schemas/agent-paths.json | 2 +- packages/schema/schemas/manifest.schema.json | 4 ++-- spec/v1/README.md | 4 ++-- spec/v1/adapter.schema.json | 2 +- spec/v1/agent-paths.json | 2 +- spec/v1/manifest.schema.json | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/schema/__tests__/schema.test.ts b/packages/schema/__tests__/schema.test.ts index e52a0f1..03b17d3 100644 --- a/packages/schema/__tests__/schema.test.ts +++ b/packages/schema/__tests__/schema.test.ts @@ -14,7 +14,7 @@ describe("@agentplugins/schema", () => { describe("exports", () => { it("exports manifest schema object", () => { expect(manifestSchema).toBeDefined(); - expect(manifestSchema.$id).toContain("agentplugins.dev"); + expect(manifestSchema.$id).toContain("agentplugins.pages.dev"); expect(manifestSchema.type).toBe("object"); }); diff --git a/packages/schema/schemas/adapter.schema.json b/packages/schema/schemas/adapter.schema.json index 9e5ebfc..6d7465f 100644 --- a/packages/schema/schemas/adapter.schema.json +++ b/packages/schema/schemas/adapter.schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://agentplugins.dev/schema/v1/adapter.json", + "$id": "https://agentplugins.pages.dev/schema/v1/adapter.json", "title": "AgentPlugins Adapter ABI v1", "description": "Contract for the JSON process ABI. An adapter is any executable (binary, script) that reads a JSON manifest on stdin (or via --manifest ), compiles platform-specific output, writes files, and exits 0 (success) or non-zero (failure). Enables any-language adapters without SDK lock-in.", "type": "object", diff --git a/packages/schema/schemas/agent-paths.json b/packages/schema/schemas/agent-paths.json index a158807..d7bb49e 100644 --- a/packages/schema/schemas/agent-paths.json +++ b/packages/schema/schemas/agent-paths.json @@ -1,5 +1,5 @@ { - "$schema": "https://agentplugins.dev/schema/v1/agent-paths.json", + "$schema": "https://agentplugins.pages.dev/schema/v1/agent-paths.json", "version": 1, "description": "Community-maintained registry of well-known agent skill/plugin paths. AgentPlugins scans these to detect installed agents and fan out symlinks from the universal store (~/.agents/plugins/).", "store": { diff --git a/packages/schema/schemas/manifest.schema.json b/packages/schema/schemas/manifest.schema.json index 64b6e8b..93240a0 100644 --- a/packages/schema/schemas/manifest.schema.json +++ b/packages/schema/schemas/manifest.schema.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://agentplugins.dev/schema/v1.json", + "$id": "https://agentplugins.pages.dev/schema/v1.json", "title": "AgentPlugins Manifest v1", - "description": "The public contract for an AgentPlugins plugin. One manifest compiles to any supported agent harness. Include \"$schema\": \"https://agentplugins.dev/schema/v1.json\" in your manifest for editor autocomplete.", + "description": "The public contract for an AgentPlugins plugin. One manifest compiles to any supported agent harness. Include \"$schema\": \"https://agentplugins.pages.dev/schema/v1.json\" in your manifest for editor autocomplete.", "type": "object", "required": ["name", "version", "description"], "additionalProperties": false, diff --git a/spec/v1/README.md b/spec/v1/README.md index 9050617..f3bd6a4 100644 --- a/spec/v1/README.md +++ b/spec/v1/README.md @@ -79,10 +79,10 @@ An adapter is any executable that implements the **JSON process ABI**: read a ma ## Schema consumption - npm: `@agentplugins/schema` (JSON Schema + generated TS types + Ajv validators) -- Hosted: `https://agentplugins.dev/schema/v1.json` +- Hosted: `https://agentplugins.pages.dev/schema/v1.json` - Raw: `https://raw.githubusercontent.com/sigilco/agentplugins/main/spec/v1/manifest.schema.json` -Include `"$schema": "https://agentplugins.dev/schema/v1.json"` in your manifest for editor autocomplete in VS Code, JetBrains, and any JSON-Schema-aware editor. +Include `"$schema": "https://agentplugins.pages.dev/schema/v1.json"` in your manifest for editor autocomplete in VS Code, JetBrains, and any JSON-Schema-aware editor. ## Versioning diff --git a/spec/v1/adapter.schema.json b/spec/v1/adapter.schema.json index 9e5ebfc..6d7465f 100644 --- a/spec/v1/adapter.schema.json +++ b/spec/v1/adapter.schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://agentplugins.dev/schema/v1/adapter.json", + "$id": "https://agentplugins.pages.dev/schema/v1/adapter.json", "title": "AgentPlugins Adapter ABI v1", "description": "Contract for the JSON process ABI. An adapter is any executable (binary, script) that reads a JSON manifest on stdin (or via --manifest ), compiles platform-specific output, writes files, and exits 0 (success) or non-zero (failure). Enables any-language adapters without SDK lock-in.", "type": "object", diff --git a/spec/v1/agent-paths.json b/spec/v1/agent-paths.json index a158807..d7bb49e 100644 --- a/spec/v1/agent-paths.json +++ b/spec/v1/agent-paths.json @@ -1,5 +1,5 @@ { - "$schema": "https://agentplugins.dev/schema/v1/agent-paths.json", + "$schema": "https://agentplugins.pages.dev/schema/v1/agent-paths.json", "version": 1, "description": "Community-maintained registry of well-known agent skill/plugin paths. AgentPlugins scans these to detect installed agents and fan out symlinks from the universal store (~/.agents/plugins/).", "store": { diff --git a/spec/v1/manifest.schema.json b/spec/v1/manifest.schema.json index 64b6e8b..93240a0 100644 --- a/spec/v1/manifest.schema.json +++ b/spec/v1/manifest.schema.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://agentplugins.dev/schema/v1.json", + "$id": "https://agentplugins.pages.dev/schema/v1.json", "title": "AgentPlugins Manifest v1", - "description": "The public contract for an AgentPlugins plugin. One manifest compiles to any supported agent harness. Include \"$schema\": \"https://agentplugins.dev/schema/v1.json\" in your manifest for editor autocomplete.", + "description": "The public contract for an AgentPlugins plugin. One manifest compiles to any supported agent harness. Include \"$schema\": \"https://agentplugins.pages.dev/schema/v1.json\" in your manifest for editor autocomplete.", "type": "object", "required": ["name", "version", "description"], "additionalProperties": false, From 6e9e6381d92a12cd15fd9ec01b1bed96f35f8eaa Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:06:54 +0200 Subject: [PATCH 80/90] =?UTF-8?q?fix(docs):=20GitHub=20before=20Sponsor=20?= =?UTF-8?q?in=20nav/hero;=20license=20footer=20MIT=E2=86=92Apache=202.0;?= =?UTF-8?q?=20Tier-1/Tier-2=20tagline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vitepress/config.ts | 4 ++-- docs/index.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 48b80a0..f236f3a 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -107,8 +107,8 @@ export default withMermaid(defineConfig({ nav: [ { text: 'Guide', link: '/guide/introduction' }, { text: 'Reference', link: '/reference' }, - { text: 'Sponsor', link: SPONSOR_SITE }, { text: 'GitHub', link: GITHUB_SITE }, + { text: 'Sponsor', link: SPONSOR_SITE }, { text: 'LLMs', items: [ @@ -172,7 +172,7 @@ export default withMermaid(defineConfig({ }, footer: { - message: 'Released under the MIT License.', + message: 'Released under the Apache License 2.0.', copyright: 'Copyright © AgentPlugins contributors', }, diff --git a/docs/index.md b/docs/index.md index 8f08d5e..d0861ed 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,17 +7,17 @@ hero: name: "AgentPlugins" text: "Universal plugin standard for AI agents" tagline: "Write AI agent plugins once, ship to any harness." - description: "One manifest. Seven agent platforms. Universal store. Zero lock-in." + description: "One manifest. Tier-1: Claude Code, Codex, OpenCode, Pi Mono. Tier-2: Copilot, Gemini, Kimi. Zero lock-in." actions: - theme: brand text: Get Started link: /guide/introduction - - theme: alt - text: Sponsor - link: https://buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj - theme: alt text: GitHub link: https://github.com/sigilco/agentplugins + - theme: alt + text: Sponsor + link: https://buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj features: - title: "Manage any AI harness plugin" From 08e67e3202af0838d50952a5f62b1f2ffe1e962f Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:07:05 +0200 Subject: [PATCH 81/90] =?UTF-8?q?fix(agents):=20update=20branch=20conventi?= =?UTF-8?q?ons=20to=20current=20develop=E2=86=92main=20flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 7780f1a..48b8a41 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,6 @@ Full detail: [`prd.md`](.agents/docs/prd.md) ## Commit & branch conventions - Atomic conventional commits (`feat(adapter-pimono): fix subagentStop collision`) -- Feature work on `feat/v0.3.0-authoring-wins`; release branch `release/v0.3.0` cut from `develop` +- Feature work on `feat/*` branches off `develop`; merge to `develop`, then PR `develop → main` to release - All plans in `.agents/plans/-.md` before implementation begins - All work linked to a refined issue in [Project 14](https://github.com/users/espetro/projects/14/views/1) From e3f51a4bcdcccb73a3875a8643401a7335736c00 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:08:08 +0200 Subject: [PATCH 82/90] ci(release): replace selective filter with topological pnpm --filter './packages/**' build in binary job --- .github/workflows/release.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 722d67d..dde591d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -106,11 +106,8 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build workspace dependencies - run: pnpm --filter @agentplugins/core --filter @agentplugins/schema build - - - name: Build CLI (tsc) - run: pnpm --filter @agentplugins/cli build + - name: Build all packages (topological) + run: pnpm --filter './packages/**' build - name: Compile native binary run: | From 5a4ac6792a96df2cc89cf534e39712efeac2c463 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:09:00 +0200 Subject: [PATCH 83/90] feat(opencode): emit mcpServers as mcp.servers in opencode.json; add tests --- .../__tests__/compile.integration.test.ts | 39 +++++++++++++++++++ .../adapter-opencode/src/output-generators.ts | 19 +++++++++ 2 files changed, 58 insertions(+) diff --git a/packages/adapter-opencode/__tests__/compile.integration.test.ts b/packages/adapter-opencode/__tests__/compile.integration.test.ts index 0e19fb3..c571703 100644 --- a/packages/adapter-opencode/__tests__/compile.integration.test.ts +++ b/packages/adapter-opencode/__tests__/compile.integration.test.ts @@ -357,6 +357,45 @@ describe("compile() integration", () => { }); }); + describe("mcpServers emission", () => { + it("emits mcp.servers in opencode.json when mcpServers defined", () => { + const manifest: PluginManifest = { + name: "mcp-plugin", + version: "1.0.0", + mcpServers: { + "my-server": { + command: "npx", + args: ["-y", "my-mcp-server"], + env: { MY_KEY: "value" }, + }, + }, + }; + + const output = adapter.compile(manifest); + + const manifestFile = output.files.find((f) => f.path === "opencode.json"); + expect(manifestFile).toBeDefined(); + const parsed = JSON.parse(manifestFile!.content); + expect(parsed.mcp).toBeDefined(); + expect(parsed.mcp.servers["my-server"].command).toBe("npx"); + expect(parsed.mcp.servers["my-server"].args).toEqual(["-y", "my-mcp-server"]); + expect(parsed.mcp.servers["my-server"].env).toEqual({ MY_KEY: "value" }); + }); + + it("omits mcp key when no mcpServers defined", () => { + const manifest: PluginManifest = { + name: "no-mcp-plugin", + version: "1.0.0", + }; + + const output = adapter.compile(manifest); + + const manifestFile = output.files.find((f) => f.path === "opencode.json"); + const parsed = JSON.parse(manifestFile!.content); + expect(parsed.mcp).toBeUndefined(); + }); + }); + describe("postInstall instructions", () => { it("includes postInstall commands", () => { const manifest: PluginManifest = { diff --git a/packages/adapter-opencode/src/output-generators.ts b/packages/adapter-opencode/src/output-generators.ts index 169c954..8fe9364 100644 --- a/packages/adapter-opencode/src/output-generators.ts +++ b/packages/adapter-opencode/src/output-generators.ts @@ -157,6 +157,24 @@ export function generateManifest( ): FileOutput { const configFileName = "opencode.json"; + const mcpServers = + manifest.mcpServers && Object.keys(manifest.mcpServers).length > 0 + ? { + mcp: { + servers: Object.fromEntries( + Object.entries(manifest.mcpServers).map(([name, cfg]) => [ + name, + { + command: cfg.command, + ...(cfg.args && cfg.args.length > 0 ? { args: cfg.args } : {}), + ...(cfg.env ? { env: cfg.env } : {}), + }, + ]) + ), + }, + } + : {}; + const opencodeConfig = { name: manifest.name, description: manifest.description ?? "", @@ -172,6 +190,7 @@ export function generateManifest( description: tool.description, parameters: tool.parameters, })) ?? [], + ...mcpServers, discovery: { paths: [".opencode/plugins/", "~/.config/opencode/plugins/"], }, From 6ab782b6b62f9f6f0feeca115d1449bac4625e15 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:10:29 +0200 Subject: [PATCH 84/90] chore(github): add community health files, issue templates, PR template, FUNDING, triage workflow --- .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/bug_report.yml | 46 +++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++++ .github/ISSUE_TEMPLATE/feature_request.yml | 30 +++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 18 ++++++++ .github/workflows/triage.yml | 34 ++++++++++++++ CODE_OF_CONDUCT.md | 27 +++++++++++ CONTRIBUTING.md | 52 ++++++++++++++++++++++ SECURITY.md | 33 ++++++++++++++ 9 files changed, 249 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/triage.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..9ec12c0 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ["https://buy.polar.sh/polar_cl_Mv1gdlG7bw3I70EC9IHtfeSHJj4PEKvA7JAUz23CFhj"] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..22dbba8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,46 @@ +name: Bug Report +description: Something is broken +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Before filing: check the [capability matrix](https://agentplugins.pages.dev/guide/capability-matrix) — some behaviours are by design. + - type: textarea + id: description + attributes: + label: What happened? + description: A clear description of the bug. + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + placeholder: | + 1. Run `agentplugins add ...` + 2. See error + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behaviour + validations: + required: true + - type: input + id: version + attributes: + label: AgentPlugins version + placeholder: "0.5.0" + validations: + required: true + - type: input + id: harness + attributes: + label: Agent harness(es) affected + placeholder: "Claude Code, Codex, ..." + - type: textarea + id: context + attributes: + label: Additional context diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..9f01e85 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Documentation + url: https://agentplugins.pages.dev + about: Read the full docs before filing an issue. + - name: Capability Matrix + url: https://agentplugins.pages.dev/guide/capability-matrix + about: Check if a behaviour is by design (⚠️ guided per-harness). diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..f884151 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,30 @@ +name: Feature Request +description: Propose a new capability or improvement +labels: ["feature"] +body: + - type: textarea + id: problem + attributes: + label: Problem or motivation + description: What are you trying to do that you can't do today? + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + validations: + required: true + - type: textarea + id: harnesses + attributes: + label: Harnesses this should cover + placeholder: "Claude Code, Codex, OpenCode, Pi Mono (Tier-1) / Copilot, Gemini, Kimi (Tier-2)" + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + - type: textarea + id: context + attributes: + label: Additional context diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..41dbc6e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +## Summary + + + +## Changes + + + +## Test plan + +- [ ] `pnpm test` passes +- [ ] `pnpm -r exec tsc --noEmit` passes +- [ ] Capability matrix updated (if adapter behaviour changed) +- [ ] Linked to a refined issue in Project 14 + +## Notes + + diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml new file mode 100644 index 0000000..c77b21d --- /dev/null +++ b/.github/workflows/triage.yml @@ -0,0 +1,34 @@ +name: Triage + +on: + issues: + types: [opened, reopened] + pull_request_target: + types: [opened, reopened] + +jobs: + label: + name: Auto-label + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Label issues by template + if: github.event_name == 'issues' + uses: actions/github-script@v7 + with: + script: | + const body = context.payload.issue.body ?? ''; + const title = context.payload.issue.title ?? ''; + // Templates already inject labels via YAML; this is a fallback. + // If no label was set, apply 'needs-triage'. + const existing = context.payload.issue.labels.map(l => l.name); + if (existing.length === 0) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + labels: ['needs-triage'], + }); + } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7a5d958 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,27 @@ +# Code of Conduct + +## Our pledge + +We pledge to make participation in this project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our standards + +**Encouraged:** +- Using welcoming and inclusive language +- Respecting differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community + +**Not acceptable:** +- Trolling, insulting or derogatory comments, or personal attacks +- Public or private harassment +- Publishing others' private information without permission +- Other conduct reasonably considered inappropriate in a professional setting + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening a GitHub issue or contacting the maintainers privately. All complaints will be reviewed and investigated promptly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..46bc364 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing to AgentPlugins + +## Quick start + +```bash +git clone https://github.com/sigilco/agentplugins +cd agentplugins +pnpm install +pnpm --filter './packages/**' build +pnpm test +``` + +## Branch model + +- `main` — release branch; protected, CI-gated, no direct pushes +- `develop` — integration branch; all feature work merges here first +- `feat/` — feature branches off `develop` + +## Commit style + +Atomic [Conventional Commits](https://www.conventionalcommits.org/): + +``` +feat(adapter-opencode): emit mcpServers as mcp.servers +fix(cli): bump scaffolded devDeps to ^0.5.0 +docs(quick-start): correct version output +``` + +One logical change per commit. Keep commits independently revertable. + +## Adding a new adapter + +1. Create `packages/adapter-/` following the existing adapter structure. +2. Implement `validate()` and `compile()` from `@agentplugins/core/adapter`. +3. Register the adapter in `packages/cli/src/adapters.ts`. +4. Add a row to `docs/guide/capability-matrix.md`. +5. Ship a community plugin in a sibling repo `agentplugins-`. + +## Community plugins + +Community plugins live in separate repos (`agentplugins-`) — not in this monorepo. They are ground-up rewrites, not mechanical ports. See the [plugin authoring guide](https://agentplugins.pages.dev/guide/creating-plugins). + +## PR checklist + +- [ ] `pnpm test` passes +- [ ] `pnpm -r exec tsc --noEmit` passes +- [ ] Capability matrix updated if adapter behaviour changed +- [ ] Linked to a refined issue in [Project 14](https://github.com/users/espetro/projects/14/views/1) + +## Code of conduct + +See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..acaf299 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,33 @@ +# Security Policy + +## Supported versions + +| Version | Supported | +| ------- | --------- | +| 0.x | ✅ Yes | + +## Reporting a vulnerability + +**Do not open a public GitHub issue for security vulnerabilities.** + +Report security issues by emailing the maintainers directly or using [GitHub private vulnerability reporting](https://github.com/sigilco/agentplugins/security/advisories/new). + +Include: +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if any) + +We aim to respond within 72 hours and will coordinate a fix and disclosure timeline with you. + +## Scope + +In scope: +- Remote code execution via plugin manifests or install flow +- Path traversal in adapter output generators +- Prototype pollution in manifest parsing +- Supply chain issues in published npm packages + +Out of scope: +- Issues in community plugins (report to their respective repos) +- Theoretical issues without a practical exploit path From 9c079147f63a5ac4e3634b4e99c34c402f177967 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:39:30 +0200 Subject: [PATCH 85/90] fix(docs): unignore docs/public/img/ logos so they deploy to Pages --- .gitignore | 3 +++ docs/public/img/logo-dark.png | Bin 0 -> 55022 bytes docs/public/img/logo-light.png | Bin 0 -> 110718 bytes 3 files changed, 3 insertions(+) create mode 100644 docs/public/img/logo-dark.png create mode 100644 docs/public/img/logo-light.png diff --git a/.gitignore b/.gitignore index 5f5f477..6a324e1 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,9 @@ docs/public/* !docs/public/favicon.svg !docs/public/favicon.ico !docs/public/og.png +!docs/public/img/ +!docs/public/img/logo-dark.png +!docs/public/img/logo-light.png # Misc *.local diff --git a/docs/public/img/logo-dark.png b/docs/public/img/logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7f61f39cc2015add0519cb317ccfbcf24ce6f8d4 GIT binary patch literal 55022 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 =Y{_P7+wDEdVXJNzwUawm^@$J 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

wY}0*z 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%0D4w(6a(O~SoBh3U4 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 Date: Tue, 30 Jun 2026 11:44:12 +0200 Subject: [PATCH 86/90] =?UTF-8?q?feat(docs):=20hero=20image=20with=20glow?= =?UTF-8?q?=20effect=20=E2=80=94=20logo=20right-side=20on=20landing,=20dar?= =?UTF-8?q?k/light=20CSS=20swap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vitepress/theme/custom.css | 23 +++++++++++++++++++++++ docs/.vitepress/theme/index.ts | 1 + docs/index.md | 3 +++ 3 files changed, 27 insertions(+) create mode 100644 docs/.vitepress/theme/custom.css diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css new file mode 100644 index 0000000..f1dba65 --- /dev/null +++ b/docs/.vitepress/theme/custom.css @@ -0,0 +1,23 @@ +/* ── Hero image glow (à la Vite) ─────────────────────────────────────────── */ + +:root { + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #5b3fd4 30%, + #38bdf8 70% + ); + --vp-home-hero-image-filter: blur(72px); +} + +/* Dark/light logo swap via CSS content override */ +html:not(.dark) .VPHero .image-src { + content: url('/img/logo-light.png'); +} +html.dark .VPHero .image-src { + content: url('/img/logo-dark.png'); +} + +/* Give the hero image a bit more breathing room */ +.VPHero .image-container { + transform: scale(1.05); +} diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 364bd84..3a5876d 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -1,4 +1,5 @@ import DefaultTheme from 'vitepress/theme' import 'virtual:group-icons.css' +import './custom.css' export default DefaultTheme diff --git a/docs/index.md b/docs/index.md index d0861ed..b828cd9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,6 +8,9 @@ hero: text: "Universal plugin standard for AI agents" tagline: "Write AI agent plugins once, ship to any harness." description: "One manifest. Tier-1: Claude Code, Codex, OpenCode, Pi Mono. Tier-2: Copilot, Gemini, Kimi. Zero lock-in." + image: + src: /img/logo-dark.png + alt: AgentPlugins actions: - theme: brand text: Get Started From 0f0ec29f6fe767515e33591837a421acc8e71f4c Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:44:34 +0200 Subject: [PATCH 87/90] fix(docs): remove hero image glow effect --- docs/.vitepress/theme/custom.css | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index f1dba65..d7b2441 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -1,14 +1,3 @@ -/* ── Hero image glow (à la Vite) ─────────────────────────────────────────── */ - -:root { - --vp-home-hero-image-background-image: linear-gradient( - -45deg, - #5b3fd4 30%, - #38bdf8 70% - ); - --vp-home-hero-image-filter: blur(72px); -} - /* Dark/light logo swap via CSS content override */ html:not(.dark) .VPHero .image-src { content: url('/img/logo-light.png'); From b30554226badbb8911f9c8df667187c091f783cf Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 11:44:45 +0200 Subject: [PATCH 88/90] feat(docs): grey radial glow behind hero logo --- docs/.vitepress/theme/custom.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index d7b2441..784c7fe 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -1,3 +1,15 @@ +/* ── Hero image glow — greyscale radial ──────────────────────────────────── */ + +:root { + --vp-home-hero-image-background-image: radial-gradient( + circle, + #9ca3af 0%, + #d1d5db 50%, + transparent 70% + ); + --vp-home-hero-image-filter: blur(64px); +} + /* Dark/light logo swap via CSS content override */ html:not(.dark) .VPHero .image-src { content: url('/img/logo-light.png'); From 8686b1c347af1146ea753a32f0969974334d92fc Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 12:05:23 +0200 Subject: [PATCH 89/90] fix(docs): use logo-dark.png as tab favicon instead of placeholder SVG --- docs/.vitepress/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index f236f3a..db6610e 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -35,7 +35,7 @@ export default withMermaid(defineConfig({ head: [ ['meta', { name: 'theme-color', content: '#3c82f6' }], - ['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }], + ['link', { rel: 'icon', type: 'image/png', href: '/img/logo-dark.png' }], ['link', { rel: 'alternate icon', href: '/favicon.ico' }], ['meta', { name: 'keywords', content: 'ai agent plugin, claude code plugin, codex plugin, opencode plugin, universal agent manifest, ai harness plugin manager' }], ['meta', { property: 'og:type', content: 'website' }], From 7cc3e3b1c38a75279b2b03dba117f7bf50958bd5 Mon Sep 17 00:00:00 2001 From: Joaquin Terrasa Date: Tue, 30 Jun 2026 12:29:59 +0200 Subject: [PATCH 90/90] =?UTF-8?q?fix(docs):=20resolve=20tsc=20errors=20?= =?UTF-8?q?=E2=80=94=20vite@5/6=20plugin=20cast,=20declare=20*.css,=20unig?= =?UTF-8?q?nore=20env.d.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ docs/.vitepress/config.ts | 5 +++-- docs/.vitepress/env.d.ts | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 docs/.vitepress/env.d.ts diff --git a/.gitignore b/.gitignore index 6a324e1..4fb4f63 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ dist/ *.d.ts *.d.ts.map +# Exceptions: source declaration files (not build artifacts) +!docs/.vitepress/env.d.ts + # pnpm .pnpm-store/ .pnpm/ diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index db6610e..d0798d3 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -72,8 +72,9 @@ export default withMermaid(defineConfig({ vite: { plugins: [ - llmstxt(), - groupIconVitePlugin(), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + llmstxt() as any, + groupIconVitePlugin() as any, // Serve llms.txt / llms-full.txt from .vitepress/dist/ during dev { name: 'vitepress-dev-llms-txt', diff --git a/docs/.vitepress/env.d.ts b/docs/.vitepress/env.d.ts new file mode 100644 index 0000000..0595031 --- /dev/null +++ b/docs/.vitepress/env.d.ts @@ -0,0 +1,2 @@ +declare module 'virtual:group-icons.css'; +declare module '*.css';