Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 17 additions & 0 deletions src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 5 additions & 5 deletions test/doctor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, []);
Expand Down Expand Up @@ -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 });
Expand All @@ -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 {
Expand Down Expand Up @@ -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", () => {
Expand Down
20 changes: 20 additions & 0 deletions test/install.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading