Skip to content
Closed
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
4 changes: 2 additions & 2 deletions packages/coding-agent/src/discovery/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as os from "node:os";
import * as path from "node:path";
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import { FileType, glob } from "@oh-my-pi/pi-natives";
import { CONFIG_DIR_NAME, getConfigDirName, getProjectDir, parseFrontmatter, tryParseJson } from "@oh-my-pi/pi-utils";
import { CONFIG_DIR_NAME, getConfigDirName, getProjectDir, getUserPluginsRegistryDir, parseFrontmatter, tryParseJson } from "@oh-my-pi/pi-utils";
import type { ExtensionModule } from "../capability/extension-module";
import { invalidate as invalidateFsCache, readDirEntries, readFile } from "../capability/fs";
import { parseRuleConditionAndScope, type Rule, type RuleFrontmatter } from "../capability/rule";
Expand Down Expand Up @@ -805,7 +805,7 @@ export async function listClaudePluginRoots(
// ── OMP installed plugins registry ───────────────────────────────────────
// OMP registry is authoritative: its entries replace Claude's entries for the same plugin ID.
// Path derived from `home` (not os.homedir()) so test isolation works when home is overridden.
const ompRegistryPath = path.join(home, getConfigDirName(), "plugins", "installed_plugins.json");
const ompRegistryPath = path.join(getUserPluginsRegistryDir(home));
const ompContent = await readFile(ompRegistryPath);
if (ompContent) {
const ompRegistry = parseClaudePluginsRegistry(ompContent);
Expand Down
15 changes: 13 additions & 2 deletions packages/coding-agent/test/discovery/claude-plugins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { discoverAgents } from "@oh-my-pi/pi-coding-agent/task/discovery";
import "@oh-my-pi/pi-coding-agent/discovery/claude-plugins";
import type { Skill } from "@oh-my-pi/pi-coding-agent/capability/skill";
import type { SlashCommand } from "@oh-my-pi/pi-coding-agent/capability/slash-command";

import { getAgentDir, getConfigDirName, setAgentDir } from "@oh-my-pi/pi-utils";
describe("parseClaudePluginsRegistry", () => {
test("parses valid registry", () => {
const content = JSON.stringify({
Expand Down Expand Up @@ -60,25 +60,36 @@ describe("parseClaudePluginsRegistry", () => {
describe("listClaudePluginRoots", () => {
let tempDir: string;
let originalHome: string | undefined;

let originalAgentDir: string;
let originalXdgDataHome: string | undefined;
beforeEach(async () => {
clearClaudePluginRootsCache();
clearFsCache();
originalHome = process.env.HOME;
originalAgentDir = getAgentDir();
originalXdgDataHome = process.env.XDG_DATA_HOME;
delete process.env.XDG_DATA_HOME;
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "claude-plugins-test-"));
process.env.HOME = tempDir;
vi.spyOn(os, "homedir").mockReturnValue(tempDir);
setAgentDir(path.join(tempDir, getConfigDirName(), "agent"));
});

afterEach(async () => {
clearClaudePluginRootsCache();
clearFsCache();
vi.restoreAllMocks();
setAgentDir(originalAgentDir);
if (originalHome === undefined) {
delete process.env.HOME;
} else {
process.env.HOME = originalHome;
}
if (originalXdgDataHome === undefined) {
delete process.env.XDG_DATA_HOME;
} else {
process.env.XDG_DATA_HOME = originalXdgDataHome;
}
await fs.rm(tempDir, { recursive: true, force: true });
});

Expand Down
9 changes: 9 additions & 0 deletions packages/utils/src/dirs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,15 @@ export function getPluginsDir(): string {
return dirs.rootSubdir("plugins", "data");
}

/** Get the users plugins directory (~/.omp/plugins or ~/.local/share/omp/plugins). */
export function getUserPluginsRegistryDir(home: string): string {
if (home != os.homedir()) {
// If a custom home directory is provided, assume plugins are stored under it without XDG redirection.
return path.join(home, getConfigDirName(), "plugins", "installed_plugins.json");
}
return path.join(getPluginsDir(), "installed_plugins.json")
}

/** Where npm installs packages (~/.omp/plugins/node_modules). */
export function getPluginsNodeModules(): string {
return path.join(getPluginsDir(), "node_modules");
Expand Down
Loading