@mesh-tech/mesh-cli
Version:
CLI for Mesh platform development utilities
317 lines (316 loc) • 13.3 kB
JavaScript
import * as fs from "fs";
import * as path from "path";
import { execFileSync } from "child_process";
import { logInfo, logSuccess, logWarn } from "../utils/log.js";
import { MeshCliError } from "../utils/errors.js";
import { findPackageRoot } from "./local/stack.js";
const MANAGED_MARKER = "<!-- managed-by: mesh skills sync — edits are overwritten; copy content elsewhere to customize -->";
const FENCE_START = "<!-- intent-skills:start -->";
const HOOK_RELATIVE = path.join(".intent", "hooks", "intent-claude-gate.mjs");
export const INTENT_RANGE = "^0.3.6";
export function resolveTargetRoot(startDir = process.cwd()) {
try {
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
cwd: startDir,
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
}).trim();
}
catch {
return startDir;
}
}
function cliAsset(...segments) {
return path.join(findPackageRoot(), ...segments);
}
function listBaseSkills() {
const dir = cliAsset("skills");
if (!fs.existsSync(dir))
return [];
return fs
.readdirSync(dir, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && fs.existsSync(path.join(dir, entry.name, "SKILL.md")))
.map((entry) => ({ name: entry.name, source: path.join(dir, entry.name, "SKILL.md") }));
}
function renderManagedSkill(source) {
const raw = fs.readFileSync(source, "utf-8");
const frontmatter = raw.match(/^(---\n[\s\S]*?\n---\n)/)?.[1];
if (!frontmatter)
return `${MANAGED_MARKER}\n\n${raw}`;
return `${frontmatter}\n${MANAGED_MARKER}\n${raw.slice(frontmatter.length)}`;
}
const BASE_SKILL_PACKAGE = "mesh-cli";
function meshTechScopeDirs(root, maxDepth = 6) {
const scopes = [];
const walk = (dir, depth) => {
if (depth > maxDepth)
return;
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
}
catch {
return;
}
for (const entry of entries) {
if (!entry.isDirectory())
continue;
if (entry.name === "node_modules") {
const scope = path.join(dir, entry.name, "@mesh-tech");
if (fs.existsSync(scope))
scopes.push(scope);
continue;
}
if (entry.name.startsWith("."))
continue;
walk(path.join(dir, entry.name), depth + 1);
}
};
walk(root, 0);
return scopes;
}
export function listPackageSkills(root) {
const byName = new Map();
for (const scopeDir of meshTechScopeDirs(root)) {
for (const pkg of fs.readdirSync(scopeDir, { withFileTypes: true })) {
if (!pkg.isDirectory() && !pkg.isSymbolicLink())
continue;
if (pkg.name === BASE_SKILL_PACKAGE)
continue;
const skillsDir = path.join(scopeDir, pkg.name, "skills");
if (!fs.existsSync(skillsDir))
continue;
for (const domain of fs.readdirSync(skillsDir, { withFileTypes: true })) {
const source = path.join(skillsDir, domain.name, "SKILL.md");
if (!fs.existsSync(source))
continue;
const name = `mesh-${pkg.name}-${domain.name}`;
if (!byName.has(name))
byName.set(name, { name, dir: path.join(skillsDir, domain.name), source });
}
}
}
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
}
function copySkillExtras(sourceDir, targetDir) {
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
if (entry.name === "SKILL.md")
continue;
fs.cpSync(path.join(sourceDir, entry.name), path.join(targetDir, entry.name), { recursive: true });
}
}
export function seedAppSkill(root, appName, appRelPath) {
const target = path.join(root, ".claude", "skills", appName, "SKILL.md");
if (fs.existsSync(target))
return null;
const template = fs.readFileSync(cliAsset("assets", "app-skill", "SKILL.md"), "utf-8");
const body = template.replaceAll("__APP_NAME__", appName).replaceAll("__APP_PATH__", appRelPath || ".");
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, body);
return path.relative(root, target);
}
function planSync(root) {
const items = [];
for (const skill of listBaseSkills()) {
const target = path.join(root, ".claude", "skills", `mesh-${skill.name}`, "SKILL.md");
const desired = renderManagedSkill(skill.source);
const label = `.claude/skills/mesh-${skill.name}/SKILL.md (base skill)`;
if (!fs.existsSync(target)) {
items.push({
label,
state: "write",
apply: () => {
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, desired);
},
});
}
else {
const current = fs.readFileSync(target, "utf-8");
if (current === desired) {
items.push({ label, state: "ok" });
}
else if (current.includes(MANAGED_MARKER)) {
items.push({ label, state: "write", apply: () => fs.writeFileSync(target, desired) });
}
else {
items.push({ label: `${label} — exists without the managed marker, leaving as-is`, state: "skip" });
}
}
}
const packageSkills = listPackageSkills(root);
for (const skill of packageSkills) {
const targetDir = path.join(root, ".claude", "skills", skill.name);
const target = path.join(targetDir, "SKILL.md");
const desired = renderManagedSkill(skill.source);
const label = `.claude/skills/${skill.name}/SKILL.md (platform skill)`;
if (!fs.existsSync(target)) {
items.push({
label,
state: "write",
apply: () => {
fs.mkdirSync(targetDir, { recursive: true });
fs.writeFileSync(target, desired);
copySkillExtras(skill.dir, targetDir);
},
});
}
else {
const current = fs.readFileSync(target, "utf-8");
if (current === desired) {
items.push({ label, state: "ok" });
}
else if (current.includes(MANAGED_MARKER)) {
items.push({
label,
state: "write",
apply: () => {
fs.writeFileSync(target, desired);
copySkillExtras(skill.dir, targetDir);
},
});
}
else {
items.push({ label: `${label} — exists without the managed marker, leaving as-is`, state: "skip" });
}
}
}
if (packageSkills.length === 0 && meshTechScopeDirs(root).length === 0) {
logInfo("No @mesh-tech packages installed yet — run `pnpm install`, then `mesh skills sync` again for the platform skills.");
}
const hookSource = cliAsset("assets", "intent", "intent-claude-gate.mjs");
const hookTarget = path.join(root, HOOK_RELATIVE);
const hookDesired = fs.readFileSync(hookSource, "utf-8");
const hookCurrent = fs.existsSync(hookTarget) ? fs.readFileSync(hookTarget, "utf-8") : null;
items.push(hookCurrent === hookDesired
? { label: `${HOOK_RELATIVE} (Intent gate)`, state: "ok" }
: {
label: `${HOOK_RELATIVE} (Intent gate)`,
state: "write",
apply: () => {
fs.mkdirSync(path.dirname(hookTarget), { recursive: true });
fs.writeFileSync(hookTarget, hookDesired, { mode: 0o755 });
},
});
const rootPkgPath = path.join(root, "package.json");
if (fs.existsSync(rootPkgPath)) {
let rootPkg = null;
try {
rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
}
catch {
rootPkg = null;
}
const label = "package.json (@tanstack/intent devDependency)";
if (!rootPkg) {
items.push({ label: `${label} — package.json is unparseable, leaving as-is`, state: "skip" });
}
else if (rootPkg.devDependencies?.["@tanstack/intent"] || rootPkg.dependencies?.["@tanstack/intent"]) {
items.push({ label, state: "ok" });
}
else {
items.push({
label,
remediation: "Added @tanstack/intent — run `pnpm install` so `pnpm exec intent` resolves.",
state: "write",
apply: () => {
rootPkg.devDependencies = rootPkg.devDependencies ?? {};
rootPkg.devDependencies["@tanstack/intent"] = INTENT_RANGE;
rootPkg.devDependencies = Object.fromEntries(Object.entries(rootPkg.devDependencies).sort(([a], [b]) => a.localeCompare(b)));
fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2) + "\n");
},
});
}
}
const settingsPath = path.join(root, ".claude", "settings.json");
let settings = {};
try {
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
}
catch {
settings = {};
}
const sessionStart = settings?.hooks?.SessionStart ?? [];
const hasHook = sessionStart.some((entry) => (entry?.hooks ?? []).some((h) => JSON.stringify(h?.args ?? h?.command ?? "").includes("intent-claude-gate.mjs")));
items.push(hasHook
? { label: ".claude/settings.json (SessionStart Intent hook)", state: "ok" }
: {
label: ".claude/settings.json (SessionStart Intent hook)",
state: "write",
apply: () => {
settings.hooks = settings.hooks ?? {};
settings.hooks.SessionStart = settings.hooks.SessionStart ?? [];
settings.hooks.SessionStart.push({
matcher: "startup|resume|clear|compact",
hooks: [
{
type: "command",
command: "node",
args: ["${CLAUDE_PROJECT_DIR}/.intent/hooks/intent-claude-gate.mjs"],
timeout: 10,
statusMessage: "Loading Intent skill catalog",
},
],
});
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 1) + "\n");
},
});
const agentsPath = path.join(root, "AGENTS.md");
const fence = fs.readFileSync(cliAsset("assets", "intent", "agents-fence.md"), "utf-8");
const agentsCurrent = fs.existsSync(agentsPath) ? fs.readFileSync(agentsPath, "utf-8") : null;
items.push(agentsCurrent?.includes(FENCE_START)
? { label: "AGENTS.md (intent-skills fence)", state: "ok" }
: {
label: "AGENTS.md (intent-skills fence)",
state: "write",
apply: () => {
const next = agentsCurrent ? `${fence}\n${agentsCurrent}` : `${fence}\n# Agent Instructions\n`;
fs.writeFileSync(agentsPath, next);
},
});
return items;
}
export function syncSkills(root, opts = {}) {
const items = planSync(root);
let dirty = false;
for (const item of items) {
if (item.state === "ok")
continue;
if (item.state === "skip") {
logWarn(item.label);
continue;
}
dirty = true;
if (opts.check) {
logWarn(`missing/stale: ${item.label}`);
}
else {
item.apply?.();
logSuccess(`synced: ${item.label}`);
if (item.remediation)
logInfo(item.remediation);
}
}
if (!dirty) {
logInfo(`Agent skills are in sync (${root})`);
}
return !dirty;
}
export function registerSkillsCommands(program) {
const skills = program.command("skills").description("Agent-skill distribution (base skills + Intent discovery)");
skills
.command("sync")
.description("Install the base building-with-Mesh skills into .claude/skills/, fetch the platform skills shipped by the repo's installed @mesh-tech/* packages, and wire TanStack-Intent discovery (intent devDependency + hook + settings + AGENTS.md fence)")
.option("--check", "verify only (CI/doctor): exit 1 when anything is missing or stale", false)
.option("--root <path>", "target repo root (default: enclosing git root)")
.action((opts) => {
const root = path.resolve(opts.root ?? resolveTargetRoot());
const ok = syncSkills(root, { check: opts.check });
if (opts.check && !ok) {
throw new MeshCliError("Agent skills are missing or stale.", {
remediation: { command: "mesh skills sync" },
});
}
});
}