diff --git a/README.md b/README.md index d658f43..28f922c 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,7 @@ agents = ["claude", "cursor"] | `codex` | `.codex` | `.codex/config.toml` | -- | | `vscode` | `.vscode` | `.vscode/mcp.json` | `.claude/settings.json` | | `opencode` | `.claude` | `opencode.json` | -- | - -[Pi](https://github.com/badlogic/pi-mono) reads `.agents/skills/` natively and needs no configuration. +| `pi` | `.pi` | -- | -- | ## Documentation diff --git a/docs/public/llms.txt b/docs/public/llms.txt index cca0b9b..55cc792 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -126,7 +126,7 @@ command = "notify-done" |-------|------|----------|---------|-------------| | `version` | integer | Yes | -- | Schema version. Always `1`. | | `defaultRepositorySource` | string | No | `github` | Host used for shorthand `owner/repo` skill sources. Valid values: `github`, `gitlab`. | -| `agents` | string[] | No | `[]` | Agent tool IDs: `claude`, `cursor`, `codex`, `vscode`, `opencode`. Creates symlinks and config files for each. | +| `agents` | string[] | No | `[]` | Agent tool IDs: `claude`, `cursor`, `codex`, `vscode`, `opencode`, `pi`. Creates symlinks and config files for each. | ### Skills @@ -385,6 +385,7 @@ Check project health: gitignore setup, installed skills, symlinks, and legacy co | `codex` | Codex | `.codex` | (reads `.agents/skills/` natively) | `.codex/config.toml` | Not supported | | `vscode` | VS Code Copilot | `.vscode` | (reads `.agents/skills/` natively) | `.vscode/mcp.json` | `.claude/settings.json` | | `opencode` | OpenCode | `.claude` | (reads `.agents/skills/` natively) | `opencode.json` | Not supported | +| `pi` | Pi | `.pi` | (reads `.agents/skills/` natively) | Not supported | Not supported | Claude and Cursor use symlinks from their config directory to `.agents/skills/`. Codex, VS Code, OpenCode, and Pi read `.agents/skills/` directly. diff --git a/specs/SPEC.md b/specs/SPEC.md index 1b639d1..95eb7d9 100644 --- a/specs/SPEC.md +++ b/specs/SPEC.md @@ -79,7 +79,7 @@ headers = { X-Api-Key = "${API_KEY}" } |-------|----------|-------------| | `version` | Yes | Schema version. Always `1`. | | `defaultRepositorySource` | No | Host used for shorthand `owner/repo` skill sources. Valid values: `github`, `gitlab`. Defaults to `github`. | -| `agents` | No | Array of agent tool IDs. Valid: `claude`, `cursor`, `codex`, `vscode`, `opencode`. Defaults to `[]`. When set, dotagents creates skills symlinks and MCP config files for each agent. | +| `agents` | No | Array of agent tool IDs. Valid: `claude`, `cursor`, `codex`, `vscode`, `opencode`, `pi`. Defaults to `[]`. When set, dotagents creates skills symlinks and MCP config files for each agent. | | `project` | No | Project metadata. | | `symlinks` | No | Symlink configuration (legacy — prefer `agents` for new projects). | | `skills` | No | Skill dependencies (array of tables). | @@ -184,6 +184,7 @@ Hook declarations. Each entry defines a hook that dotagents will configure for a | `codex` | Codex | `.codex` | `.codex/config.toml` | TOML (shared) | | `vscode` | VS Code Copilot | `.vscode` | `.vscode/mcp.json` | JSON | | `opencode` | OpenCode | `.claude` | `opencode.json` | JSON (shared) | +| `pi` | Pi | `.pi` | -- | -- | Each agent has its own MCP config format. dotagents translates the universal `[[mcp]]` declarations into the format each tool expects during `install` and `sync`. @@ -671,7 +672,7 @@ dotagents/ mcp.ts agents/ types.ts # McpDeclaration, AgentDefinition interfaces - registry.ts # Agent registry (claude, cursor, codex, vscode, opencode) + registry.ts # Agent registry (claude, cursor, codex, vscode, opencode, pi) definitions/ # Per-agent definitions (claude.ts, cursor.ts, etc.) mcp-writer.ts # MCP config file generation per agent hook-writer.ts # Hook config file generation per agent diff --git a/src/agents/definitions/pi.ts b/src/agents/definitions/pi.ts new file mode 100644 index 0000000..76421ed --- /dev/null +++ b/src/agents/definitions/pi.ts @@ -0,0 +1,21 @@ +import type { AgentDefinition } from "../types.js"; +import { UnsupportedFeature } from "../errors.js"; + +const pi: AgentDefinition = { + id: "pi", + displayName: "Pi", + configDir: ".pi", + // reads .agents/skills/ natively at both project and user scope + skillsParentDir: undefined, + userSkillsParentDirs: undefined, + mcp: undefined, + serializeServer() { + throw new UnsupportedFeature("pi", "MCP"); + }, + hooks: undefined, + serializeHooks() { + throw new UnsupportedFeature("pi", "hooks"); + }, +}; + +export default pi; diff --git a/src/agents/mcp-writer.ts b/src/agents/mcp-writer.ts index dd04848..fa4eedc 100644 --- a/src/agents/mcp-writer.ts +++ b/src/agents/mcp-writer.ts @@ -57,6 +57,8 @@ export async function writeMcpConfigs( if (!agent) {continue;} const { mcp } = agent; + if (!mcp) {continue;} + const { filePath, shared } = resolveTarget(id, mcp); if (seen.has(filePath)) {continue;} seen.add(filePath); @@ -96,6 +98,8 @@ export async function verifyMcpConfigs( if (!agent) {continue;} const { mcp } = agent; + if (!mcp) {continue;} + const { filePath } = resolveTarget(id, mcp); if (seen.has(filePath)) {continue;} seen.add(filePath); diff --git a/src/agents/paths.test.ts b/src/agents/paths.test.ts index e2a03ab..b03855c 100644 --- a/src/agents/paths.test.ts +++ b/src/agents/paths.test.ts @@ -41,6 +41,10 @@ describe("getUserMcpTarget", () => { it("throws for unknown agent", () => { expect(() => getUserMcpTarget("emacs")).toThrow("Unknown agent"); }); + + it("throws for pi (no MCP support)", () => { + expect(() => getUserMcpTarget("pi")).toThrow("Unknown agent"); + }); }); describe("skill discovery paths", () => { @@ -77,4 +81,10 @@ describe("skill discovery paths", () => { expect(agent.skillsParentDir).toBeUndefined(); expect(agent.userSkillsParentDirs).toBeUndefined(); }); + + it("pi reads .agents/skills/ natively", () => { + const agent = getAgent("pi")!; + expect(agent.skillsParentDir).toBeUndefined(); + expect(agent.userSkillsParentDirs).toBeUndefined(); + }); }); diff --git a/src/agents/registry.test.ts b/src/agents/registry.test.ts index 6801b23..fef6c5f 100644 --- a/src/agents/registry.test.ts +++ b/src/agents/registry.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from "vitest"; import { getAgent, allAgentIds } from "./registry.js"; +import { UnsupportedFeature } from "./errors.js"; import type { McpDeclaration } from "./types.js"; const STDIO_SERVER: McpDeclaration = { @@ -29,7 +30,8 @@ describe("allAgentIds", () => { expect(ids).toContain("codex"); expect(ids).toContain("vscode"); expect(ids).toContain("opencode"); - expect(ids).toHaveLength(5); + expect(ids).toContain("pi"); + expect(ids).toHaveLength(6); }); }); @@ -110,8 +112,8 @@ describe("codex serializer", () => { }); it("has toml format and shared flag", () => { - expect(agent.mcp.format).toBe("toml"); - expect(agent.mcp.shared).toBe(true); + expect(agent.mcp!.format).toBe("toml"); + expect(agent.mcp!.shared).toBe(true); }); }); @@ -179,7 +181,30 @@ describe("opencode serializer", () => { }); it("shares config and reads .agents/ natively", () => { - expect(agent.mcp.shared).toBe(true); + expect(agent.mcp!.shared).toBe(true); expect(agent.skillsParentDir).toBeUndefined(); }); }); + +describe("pi agent", () => { + const agent = getAgent("pi")!; + + it("does not support MCP", () => { + expect(agent.mcp).toBeUndefined(); + expect(() => agent.serializeServer(STDIO_SERVER)).toThrow(UnsupportedFeature); + }); + + it("does not support hooks", () => { + expect(agent.hooks).toBeUndefined(); + expect(() => agent.serializeHooks([])).toThrow(UnsupportedFeature); + }); + + it("reads .agents/skills/ natively", () => { + expect(agent.skillsParentDir).toBeUndefined(); + expect(agent.userSkillsParentDirs).toBeUndefined(); + }); + + it("uses .pi config directory", () => { + expect(agent.configDir).toBe(".pi"); + }); +}); diff --git a/src/agents/registry.ts b/src/agents/registry.ts index 8bffe05..25cdee9 100644 --- a/src/agents/registry.ts +++ b/src/agents/registry.ts @@ -4,8 +4,9 @@ import cursor from "./definitions/cursor.js"; import codex from "./definitions/codex.js"; import vscode from "./definitions/vscode.js"; import opencode from "./definitions/opencode.js"; +import pi from "./definitions/pi.js"; -const ALL_AGENTS: AgentDefinition[] = [claude, cursor, codex, vscode, opencode]; +const ALL_AGENTS: AgentDefinition[] = [claude, cursor, codex, vscode, opencode, pi]; const AGENT_REGISTRY = new Map( ALL_AGENTS.map((a) => [a.id, a]), diff --git a/src/agents/types.ts b/src/agents/types.ts index 0b3b908..3acb56a 100644 --- a/src/agents/types.ts +++ b/src/agents/types.ts @@ -91,8 +91,8 @@ export interface AgentDefinition { * Undefined if the agent reads ~/.agents/skills/ natively (no symlink needed). */ userSkillsParentDirs?: string[]; - /** MCP config file specification */ - mcp: McpConfigSpec; + /** MCP config file specification (undefined if agent doesn't support MCP) */ + mcp?: McpConfigSpec; /** Transforms universal MCP declaration to agent-specific format */ serializeServer: McpSerializer; /** Hook config file specification (undefined if agent doesn't support hooks) */