diff --git a/README.md b/README.md index f412a64..4b92517 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ npx @gaffa-dev/cli doctor npx @gaffa-dev/cli doctor --json ``` -Supported tools: Claude Code, Codex, GitHub Copilot, Cursor and Antigravity. +Supported tools: Claude Code, Codex, GitHub Copilot, Cursor, Antigravity and Pi. Each is detected by its config directory rather than a binary on the path, since an IDE may put nothing on the path. `--json` prints the same result as structured output for scripts. diff --git a/src/tools.ts b/src/tools.ts index 70d7927..2e6ad86 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -171,6 +171,23 @@ export const TOOLS: Tool[] = [ urlKey: "serverUrl", }, }, + { + id: "pi", + label: "Pi", + configEnv: "PI_CODING_AGENT_DIR", + configSegments: [".pi", "agent"], + // Pi reads .pi/skills and .agents/skills as project dirs, and personal + // skills from the config dir (~/.pi/agent/skills by default, moved by + // PI_CODING_AGENT_DIR when set) plus ~/.agents/skills. .pi/skills is + // first so install writes there. No MCP entry yet, Pi's MCP config + // location is not verified. + skillDirs: [ + { scope: "project", segments: [".pi", "skills"] }, + { scope: "project", segments: [".agents", "skills"] }, + { scope: "personal", fromConfig: true, segments: ["skills"] }, + { scope: "personal", segments: [".agents", "skills"] }, + ], + }, ]; export interface DoctorContext { diff --git a/test/doctor.test.js b/test/doctor.test.js index 413a8b9..e80c7a1 100644 --- a/test/doctor.test.js +++ b/test/doctor.test.js @@ -33,7 +33,7 @@ test("nothing installed: every tool reports not installed and no skills", () => const cwd = tmp(); try { const reports = inspectTools({ home, cwd, env: {} }); - assert.equal(reports.length, 5); + assert.equal(reports.length, 6); for (const r of reports) { assert.equal(r.installed, false); for (const loc of r.skillLocations) assert.deepEqual(loc.skills, []); @@ -152,7 +152,7 @@ test("json output has a tools array with an entry per tool", async () => { const cwd = tmp(); try { const parsed = JSON.parse(await runDoctor({ home, cwd, env: {} }, true)); - assert.equal(parsed.tools.length, 5); + assert.equal(parsed.tools.length, 6); assert.ok(parsed.tools.every((t) => "installed" in t && "configPath" in t)); } finally { rmSync(home, { recursive: true, force: true }); @@ -165,7 +165,7 @@ test("human output names every tool", async () => { const cwd = tmp(); try { const out = await runDoctor({ home, cwd, env: {} }, false); - for (const label of ["Claude Code", "Codex", "GitHub Copilot", "Cursor", "Antigravity"]) { + for (const label of ["Claude Code", "Codex", "GitHub Copilot", "Cursor", "Antigravity", "Pi"]) { assert.match(out, new RegExp(label)); } } finally { @@ -274,10 +274,10 @@ test("doctor runs and lists the tools", () => { assert.match(stdout, /Antigravity/); }); -test("doctor --json emits parseable json with five tools", () => { +test("doctor --json emits parseable json with six tools", () => { const { stdout, code } = run(["doctor", "--json"]); assert.equal(code, 0); - assert.equal(JSON.parse(stdout).tools.length, 5); + assert.equal(JSON.parse(stdout).tools.length, 6); }); test("help lists the doctor command", () => { diff --git a/test/install.test.js b/test/install.test.js index eaee32e..50b43c4 100644 --- a/test/install.test.js +++ b/test/install.test.js @@ -84,6 +84,26 @@ test("claude-code and codex resolve to different project dirs", () => { } }); +test("pi resolves project to .pi/skills and personal to the config dir skills", () => { + const home = tmp(), cwd = tmp(), override = tmp(); + try { + assert.deepEqual( + writeDirs(["pi"], "project", { home, cwd, env: {} }), + [join(cwd, ".pi", "skills")], + ); + assert.deepEqual( + writeDirs(["pi"], "personal", { home, cwd, env: {} }), + [join(home, ".pi", "agent", "skills")], + ); + assert.deepEqual( + writeDirs(["pi"], "personal", { home, cwd, env: { PI_CODING_AGENT_DIR: override } }), + [join(override, "skills")], + ); + } finally { + cleanup(home, cwd, override); + } +}); + test("a refresh drops a skill the source no longer has", () => { const home = tmp(), cwd = tmp(), src = tmp(); try {