UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

83 lines (82 loc) • 4.31 kB
/** Refresh existing managed context destinations after a store pull, without relinking the installation. */ import * as fs from "fs"; import * as path from "path"; import { homePath } from "../phren-paths.js"; import { getProjectDirs } from "../shared.js"; import { readProjectConfig, getProjectSourcePath, getProjectOwnershipMode } from "../project-config.js"; import { findProjectDir } from "../project-locator.js"; import { resolveManagementCapabilities } from "../init/management-preset.js"; import { syncScopeSkillsToDir } from "../skill/files.js"; import { getNonPrimaryStores, getStoreProjectDirs } from "../store-registry.js"; /** * Projects from attached team stores whose checkout is on this machine. Profiles * list only the primary store's projects, so link and refresh reach these here; * a project the primary store also has is left to the primary. */ export function teamStoreProjectCheckouts(phrenPath) { const found = []; const seen = new Set(); for (const store of getNonPrimaryStores(phrenPath)) { if (!fs.existsSync(store.path)) continue; for (const dir of getStoreProjectDirs(store)) { const project = path.basename(dir); if (project === "global" || seen.has(project) || fs.existsSync(path.join(phrenPath, project))) continue; seen.add(project); const config = readProjectConfig(store.path, project); if (getProjectOwnershipMode(store.path, project, config) !== "phren-managed") continue; // The store is shared between computers, so its sourcePath may be another machine's. const configured = getProjectSourcePath(store.path, project, config); const target = configured && fs.existsSync(configured) ? configured : findProjectDir(project); if (target && fs.existsSync(target)) found.push({ project, target, skills: config.skills !== false }); } } return found; } export function refreshLinkedContext(phrenPath, profile) { const caps = resolveManagementCapabilities(phrenPath); if (caps.installSkillLinks) { for (const tool of [".claude", ".agents", ".copilot"]) { const dir = homePath(tool, "skills"); if (fs.existsSync(path.join(path.dirname(dir), "skill-manifest.json"))) syncScopeSkillsToDir(phrenPath, "global", dir); } } if (!caps.repoMirroring) return; for (const source of getProjectDirs(phrenPath, profile)) { const project = path.basename(source); if (project === "global") continue; const config = readProjectConfig(phrenPath, project); if (getProjectOwnershipMode(phrenPath, project, config) !== "phren-managed") continue; const target = getProjectSourcePath(phrenPath, project, config) ?? findProjectDir(project); if (!target || !fs.existsSync(target)) continue; const skillsDir = path.join(target, ".claude", "skills"); if (config.skills !== false && fs.existsSync(path.join(target, ".claude", "skill-manifest.json"))) { syncScopeSkillsToDir(phrenPath, project, skillsDir); } const agentsPath = path.join(target, "AGENTS.md"); const claudePath = path.join(source, "AGENTS.md"); // Older releases wrote AGENTS.md as a generated snapshot with a skills table; // link and doctor --fix replace that with a symlink to the store's AGENTS.md. // Do the same here instead of regenerating the snapshot, or a pull and a // doctor run keep flipping the file between the two shapes. if (!fs.existsSync(agentsPath) || !fs.existsSync(claudePath) || fs.lstatSync(agentsPath).isSymbolicLink()) continue; if (!fs.readFileSync(agentsPath, "utf8").includes("<!-- phren:generated-agents -->")) continue; fs.unlinkSync(agentsPath); fs.symlinkSync(claudePath, agentsPath); } for (const { project, target, skills } of teamStoreProjectCheckouts(phrenPath)) { if (skills && fs.existsSync(path.join(target, ".claude", "skill-manifest.json"))) { syncScopeSkillsToDir(phrenPath, project, path.join(target, ".claude", "skills")); } } }