Files

637 lines
28 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const require_chunk = require("./chunk-CZWwpsFl.cjs");
const require_skill = require("./skill-vpzqNCTw.cjs");
const require_to_error_message = require("./to-error-message-DMTSr_Bl.cjs");
const require_mcp = require("./mcp-Ccqdolho.cjs");
const require_agents_md = require("./agents-md-CdwuWt7C.cjs");
let node_fs = require("node:fs");
let node_os = require("node:os");
let node_path = require("node:path");
let node_fs_promises = require("node:fs/promises");
let commander = require("commander");
let picocolors = require("picocolors");
picocolors = require_chunk.__toESM(picocolors, 1);
let prompts = require("prompts");
prompts = require_chunk.__toESM(prompts, 1);
//#region src/cli/utils/logger.ts
const logger = {
info: (message) => {
console.log(picocolors.default.cyan(""), message);
},
success: (message) => {
console.log(picocolors.default.green("✔"), message);
},
warn: (message) => {
console.log(picocolors.default.yellow("⚠"), message);
},
error: (message) => {
console.error(picocolors.default.red("✖"), message);
}
};
//#endregion
//#region src/cli/commands/doc/resolve-agent.ts
const knownAgents = new Set(require_agents_md.listAgentsMdDescriptors().map((descriptor) => descriptor.agent));
const isAgentsMdAgent = (value) => knownAgents.has(value);
const resolveAgentsMdAgent = (value) => {
if (value === void 0) return void 0;
if (!isAgentsMdAgent(value)) throw new Error(`Unknown agent "${value}"`);
return value;
};
//#endregion
//#region src/cli/commands/doc/init.ts
const TEMPLATE = `# AGENTS.md
This document is the source of truth for AI coding agents working in this repository.
## Project overview
Short description of what this project does.
## Setup commands
\`\`\`bash
pnpm install
pnpm dev
\`\`\`
## Conventions
- Describe coding style, framework, and patterns agents should follow.
- Link to any other docs that are relevant.
## Testing
\`\`\`bash
pnpm test
\`\`\`
`;
const docInitCommand = new commander.Command("init").description("Create an AGENTS.md (or agent-specific variant) in the current directory").option("-a, --agent <agent>", "Agent to target (defaults to universal AGENTS.md)").action((options) => {
try {
const filePath = require_agents_md.resolveAgentsMdFilePath({
cwd: process.cwd(),
agent: resolveAgentsMdAgent(options.agent)
});
if ((0, node_fs.existsSync)(filePath)) {
logger.warn(`${picocolors.default.cyan(filePath)} already exists`);
return;
}
(0, node_fs.writeFileSync)(filePath, TEMPLATE, "utf-8");
logger.success(`Created ${picocolors.default.bold(filePath)}`);
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/doc/read.ts
const docReadCommand = new commander.Command("read").description("Read the current AGENTS.md (or agent-specific variant) and list its sections").option("-a, --agent <agent>", "Agent variant to read (claude-code, cursor, codex, ...)").option("-f, --file <file>", "Explicit file path (overrides --agent)").option("--json", "Output as JSON").action((options) => {
try {
const document = require_agents_md.readAgentsMd({
agent: resolveAgentsMdAgent(options.agent),
file: options.file,
cwd: process.cwd()
});
if (options.json) {
console.log(JSON.stringify({
path: document.path,
sections: document.sections.map((section) => ({
heading: section.heading,
level: section.level,
body: section.body
}))
}, null, 2));
return;
}
if (!document.content) {
logger.warn(`No file at ${picocolors.default.cyan(document.path)}`);
return;
}
logger.info(picocolors.default.bold(document.path));
for (const section of document.sections) {
const prefix = "#".repeat(section.level);
console.log(` ${picocolors.default.dim(prefix)} ${section.heading}`);
}
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/doc/remove-section.ts
const docRemoveSectionCommand = new commander.Command("remove-section").alias("rm-section").description("Remove a section from an AGENTS.md file").argument("<heading>", "Section heading to remove").option("-a, --agent <agent>", "Agent variant to target").option("-f, --file <file>", "Explicit file path (overrides --agent)").action((heading, options) => {
try {
if (require_agents_md.removeAgentsMdSection({
heading,
agent: resolveAgentsMdAgent(options.agent),
file: options.file,
cwd: process.cwd()
})) logger.success(`Removed ${picocolors.default.bold(heading)}`);
else logger.warn(`Section ${picocolors.default.bold(heading)} not found`);
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/doc/set-section.ts
const MIN_HEADING_LEVEL = 1;
const MAX_HEADING_LEVEL = 6;
const isValidPlacement = (value) => value === "append" || value === "prepend" || value === "replace";
const docSetSectionCommand = new commander.Command("set-section").description("Create or replace a section in an AGENTS.md file").argument("<heading>", "Section heading (used as the Markdown heading)").option("-a, --agent <agent>", "Agent variant to target").option("-f, --file <file>", "Explicit file path (overrides --agent)").option("--body <body>", "Inline body content").option("--body-file <path>", "Read body from a file").option("--placement <mode>", "append, prepend, or replace (default: replace)").option("--level <level>", "Heading level (1-6, default: 2)").action((heading, options) => {
try {
const agent = resolveAgentsMdAgent(options.agent);
const body = options.body ?? (options.bodyFile ? (0, node_fs.readFileSync)(options.bodyFile, "utf-8") : "");
if (!body.trim()) throw new Error("Body is empty. Provide --body or --body-file with content.");
const placement = options.placement ?? "replace";
if (!isValidPlacement(placement)) throw new Error(`Invalid placement "${placement}" (expected: append, prepend, replace)`);
const level = options.level ? Number.parseInt(options.level, 10) : 2;
if (!Number.isInteger(level) || level < MIN_HEADING_LEVEL || level > MAX_HEADING_LEVEL) throw new Error(`Invalid heading level "${options.level}" (expected: ${MIN_HEADING_LEVEL}-${MAX_HEADING_LEVEL})`);
const filePath = require_agents_md.upsertAgentsMdSection({
heading,
body,
agent,
file: options.file,
placement,
level,
cwd: process.cwd()
});
logger.success(`Updated ${picocolors.default.bold(heading)} in ${picocolors.default.cyan(filePath)}`);
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/doc/symlink-claude.ts
const docSymlinkClaudeCommand = new commander.Command("symlink-claude").description("Create a CLAUDE.md → AGENTS.md symlink (migration helper)").option("--overwrite", "Replace an existing CLAUDE.md (backs it up first)").option("--backup <name>", "Backup filename if overwriting (default: CLAUDE.md.bak)").action(async (options) => {
try {
const result = await require_agents_md.symlinkClaudeToAgents({
cwd: process.cwd(),
overwrite: Boolean(options.overwrite),
backupName: options.backup
});
if (result.alreadyLinked) {
logger.info(`CLAUDE.md already points to AGENTS.md`);
return;
}
if (result.created) {
const suffix = result.backedUpTo ? ` (backed up existing to ${picocolors.default.dim(result.backedUpTo)})` : "";
logger.success(`Linked ${picocolors.default.cyan(result.claudePath)} → AGENTS.md${suffix}`);
return;
}
logger.warn(`CLAUDE.md already exists at ${picocolors.default.cyan(result.claudePath)}. Pass --overwrite to replace it.`);
process.exitCode = 1;
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/doc/index.ts
const docCommand = new commander.Command("agents-md").alias("doc").description("Manage AGENTS.md / CLAUDE.md / GEMINI.md / Cursor rules").addCommand(docInitCommand).addCommand(docReadCommand).addCommand(docSetSectionCommand).addCommand(docRemoveSectionCommand).addCommand(docSymlinkClaudeCommand);
//#endregion
//#region src/cli/utils/format-agent-list.ts
const formatAgentList = (agentList, emptyLabel = "(none)") => agentList.length === 0 ? emptyLabel : agentList.join(", ");
//#endregion
//#region src/cli/utils/parse-mcp-agent-list.ts
const parseMcpAgentList = (input) => {
if (!input || input.length === 0) return void 0;
if (input.includes("*")) return require_mcp.getMcpAgentTypes();
const resolved = [];
for (const value of input) {
const agentType = require_mcp.resolveMcpAgentAlias(value);
if (!agentType) throw new Error(`Unknown MCP agent "${value}"`);
resolved.push(agentType);
}
return resolved;
};
//#endregion
//#region src/cli/commands/mcp/add.ts
const parseKeyValueList = (entries, separator) => {
if (!entries || entries.length === 0) return {};
const result = {};
for (const entry of entries) {
const splitIndex = entry.indexOf(separator);
if (splitIndex === -1) throw new Error(`Invalid entry "${entry}": expected "${separator}" separator`);
const key = entry.slice(0, splitIndex).trim();
const value = entry.slice(splitIndex + separator.length).trim();
if (!key) throw new Error(`Invalid entry "${entry}": empty key`);
result[key] = value;
}
return result;
};
const resolveTransport = (input) => {
if (!input) return void 0;
if (input === "http" || input === "sse") return input;
throw new Error(`Unsupported transport "${input}" (expected: http, sse)`);
};
const detectCandidateAgents = (cwd, isGlobal) => isGlobal ? require_mcp.detectGloballyInstalledMcpAgents() : require_mcp.detectProjectInstalledMcpAgents(cwd);
const mcpAddCommand = new commander.Command("add").description("Add an MCP server to coding agents").argument("<source>", "Remote URL, npm package, or command line").option("-a, --agent <agents...>", "Target specific agents (use '*' for all)").option("-g, --global", "Install to user-level config instead of project").option("-t, --transport <type>", "Transport type for remote servers (http or sse)").option("--header <header...>", "HTTP header (Key: Value), repeatable").option("--env <env...>", "Env var for stdio servers (KEY=VALUE), repeatable").option("-n, --name <name>", "Server name override").option("-y, --yes", "Skip all prompts").option("--all", "Install to all supported agents").action((source, options) => {
try {
const parsed = require_mcp.parseMcpSource(source);
const cwd = process.cwd();
const isGlobal = Boolean(options.global);
let agentTypes = options.all ? require_mcp.getMcpAgentTypes() : parseMcpAgentList(options.agent);
if (!agentTypes) {
const detected = detectCandidateAgents(cwd, isGlobal);
if (detected.length === 0) {
logger.warn(`No ${isGlobal ? "global" : "project"}-installed MCP agents detected. Pass ${picocolors.default.cyan("-a <agent>")} (e.g. ${picocolors.default.cyan("-a cursor")}) or ${picocolors.default.cyan("--all")} to install.`);
process.exitCode = 1;
return;
}
agentTypes = detected;
logger.info(`Detected ${isGlobal ? "global" : "project"} agents: ${picocolors.default.cyan(formatAgentList(detected, "(none detected)"))}`);
}
const result = require_mcp.installMcpServer({
source,
name: options.name,
agents: agentTypes,
global: isGlobal,
cwd,
transport: resolveTransport(options.transport),
headers: parseKeyValueList(options.header, ":"),
env: parseKeyValueList(options.env, "=")
});
logger.info(`Installing ${picocolors.default.bold(result.serverName)} (${picocolors.default.cyan(parsed.type)}) to ${picocolors.default.cyan(String(result.results.length))} agent(s)`);
for (const record of result.results) if (record.success) logger.success(`${picocolors.default.cyan(record.agent)} ${picocolors.default.dim(record.path)}`);
else logger.error(`${picocolors.default.cyan(record.agent)}: ${record.error}`);
if (result.results.some((record) => !record.success)) process.exitCode = 1;
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/mcp/list.ts
const mcpListCommand = new commander.Command("list").alias("ls").description("List installed MCP servers across agents").option("-g, --global", "List global configs instead of project").option("-a, --agent <agents...>", "Filter by specific agents").option("--json", "Output as JSON").action((options) => {
try {
const entries = require_mcp.listInstalledMcpServers({
global: Boolean(options.global),
cwd: process.cwd(),
agents: parseMcpAgentList(options.agent)
});
if (options.json) {
console.log(JSON.stringify(entries, null, 2));
return;
}
if (entries.length === 0) {
logger.warn("No MCP servers installed");
return;
}
const grouped = /* @__PURE__ */ new Map();
for (const entry of entries) {
const existing = grouped.get(entry.serverName) ?? [];
existing.push(entry);
grouped.set(entry.serverName, existing);
}
for (const [serverName, group] of grouped) {
const agentLabels = group.map((record) => record.agent).join(", ");
console.log(` ${picocolors.default.bold(serverName)} ${picocolors.default.dim(`[${agentLabels}]`)}`);
const firstPath = group[0]?.path;
if (firstPath) console.log(` ${picocolors.default.dim(firstPath)}`);
}
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/mcp/remove.ts
const mcpRemoveCommand = new commander.Command("remove").alias("rm").description("Remove an MCP server from agent configs").argument("<name>", "Server name").option("-g, --global", "Remove from global scope").option("-a, --agent <agents...>", "Filter by specific agents (use '*' for all)").option("-y, --yes", "Skip confirmation prompts").action((name, options) => {
try {
const results = require_mcp.removeMcpServer({
name,
agents: parseMcpAgentList(options.agent),
global: Boolean(options.global),
cwd: process.cwd()
});
if (results.length === 0) {
logger.warn(`No agent config contained ${picocolors.default.bold(name)}`);
return;
}
for (const record of results) if (record.removed) logger.success(`${picocolors.default.cyan(record.agent)} removed ${picocolors.default.bold(name)} ${picocolors.default.dim(record.path)}`);
else logger.error(`${picocolors.default.cyan(record.agent)}: ${record.error ?? "not found"}`);
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/mcp/index.ts
const mcpCommand = new commander.Command("mcp").description("Install, list, and remove MCP servers across coding agents").addCommand(mcpAddCommand).addCommand(mcpListCommand).addCommand(mcpRemoveCommand);
//#endregion
//#region src/cli/utils/parse-skill-agent-list.ts
const parseSkillAgentList = (input) => {
if (!input || input.length === 0) return void 0;
if (input.includes("*")) return require_skill.getSkillAgentTypes();
const validated = [];
for (const value of input) {
if (!require_skill.isSkillAgentType(value)) throw new Error(`Unknown agent "${value}"`);
validated.push(value);
}
return validated;
};
//#endregion
//#region src/cli/commands/skill/add.ts
const resolveSkillFilter = (input) => {
if (!input || input.length === 0) return void 0;
if (input.includes("*")) return void 0;
return input;
};
const resolveTargetAgentsForPrompt = async (requested, yes) => {
if (requested) return requested;
const installed = await require_skill.detectInstalledSkillAgents();
const fallback = installed.length > 0 ? installed : require_skill.getUniversalSkillAgents();
if (yes || !process.stdin.isTTY) return fallback;
const rawSelected = (await (0, prompts.default)({
type: "multiselect",
name: "agents",
message: "Install to which agents?",
choices: Object.values(require_skill.skillAgents).map((agent) => ({
title: agent.displayName,
value: agent.name,
selected: fallback.includes(agent.name)
})),
min: 1
})).agents;
if (!Array.isArray(rawSelected) || rawSelected.length === 0) {
logger.warn("No agents selected, using detected defaults");
return fallback;
}
const selected = rawSelected.filter((value) => typeof value === "string" && require_skill.isSkillAgentType(value));
return selected.length > 0 ? selected : fallback;
};
const listSkillsInSource = async (source) => {
const parsed = require_skill.parseSkillSource(source);
let basePath = "";
let subpath;
let cleanup;
try {
if (parsed.type === "local") basePath = parsed.localPath ?? parsed.url;
else if (parsed.type === "url") {
basePath = await require_skill.fetchSkillManifestFromUrl(parsed.url);
cleanup = () => require_skill.cleanupTempDir(basePath);
} else if (parsed.type === "well-known") {
basePath = await require_skill.fetchWellKnownSkills(parsed.url);
cleanup = () => require_skill.cleanupTempDir(basePath);
} else {
basePath = await require_skill.cloneRepo(parsed.url, parsed.ref);
subpath = parsed.subpath;
cleanup = () => require_skill.cleanupTempDir(basePath);
}
const skills = await require_skill.discoverSkills(basePath, subpath);
const visible = parsed.skillFilter ? require_skill.filterSkillsByName(skills, [parsed.skillFilter]) : skills;
if (visible.length === 0) {
logger.warn("No SKILL.md files found in source");
return;
}
logger.info(`Found ${visible.length} skill${visible.length === 1 ? "" : "s"}`);
for (const skill of visible) console.log(` ${picocolors.default.bold(skill.name)} ${picocolors.default.dim("-")} ${skill.description}`);
} finally {
await cleanup?.().catch(() => {});
}
};
const skillAddCommand = new commander.Command("add").description("Install skills from a source (local path, GitHub, or URL)").argument("<source>", "Source to install skills from").option("-a, --agent <agents...>", "Target specific agents (use '*' for all)").option("-s, --skill <skills...>", "Install specific skills by name").option("-g, --global", "Install to ~/.agents/skills instead of the project").option("--copy", "Copy files instead of symlinking").option("-y, --yes", "Skip all prompts").option("-l, --list", "List available skills in the source without installing").action(async (source, options) => {
try {
if (options.list) {
await listSkillsInSource(source);
return;
}
const targetAgents = await resolveTargetAgentsForPrompt(parseSkillAgentList(options.agent), Boolean(options.yes));
const skillFilter = resolveSkillFilter(options.skill);
const mode = options.copy ? "copy" : "symlink";
logger.info(`Installing from ${picocolors.default.cyan(source)} to ${picocolors.default.cyan(formatAgentList(targetAgents))}`);
const result = await require_skill.installSkillsFromSource({
source,
agents: targetAgents,
skills: skillFilter,
global: options.global,
mode,
cwd: process.cwd()
});
if (result.skills.length === 0) {
logger.warn("No SKILL.md files found in source");
return;
}
for (const record of result.installed) {
const fallbackSuffix = record.symlinkFailed ? picocolors.default.dim(" (copied: symlink fallback)") : "";
logger.success(`${picocolors.default.bold(record.skill)}${picocolors.default.cyan(record.agent)}${fallbackSuffix} ${picocolors.default.dim(record.path)}`);
}
for (const record of result.failed) logger.error(`${picocolors.default.bold(record.skill)}${picocolors.default.cyan(record.agent)}: ${record.error}`);
if (result.failed.length > 0) process.exitCode = 1;
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/skill/init.ts
const renderTemplate = (skillName) => `---
name: ${skillName}
description: A brief description of what this skill does and when to use it.
---
# ${skillName}
Describe what this skill does, when the agent should use it, and the instructions
the agent should follow when activated.
## When to use
Describe the trigger conditions (e.g. "after finishing a feature", "when the user
asks about X", "before committing React code").
## Instructions
1. First step
2. Second step
3. Additional steps as needed
`;
const skillInitCommand = new commander.Command("init").description("Create a new SKILL.md in the current directory").argument("[name]", "Skill name (defaults to current directory name)").action(async (nameArg) => {
try {
const cwd = process.cwd();
const skillName = nameArg || (0, node_path.basename)(cwd);
const hasExplicitName = nameArg !== void 0;
const skillDir = hasExplicitName ? (0, node_path.join)(cwd, skillName) : cwd;
const skillFile = (0, node_path.join)(skillDir, "SKILL.md");
const displayPath = hasExplicitName ? `${skillName}/SKILL.md` : "SKILL.md";
if ((0, node_fs.existsSync)(skillFile)) {
logger.warn(`Skill already exists at ${picocolors.default.cyan(displayPath)}`);
return;
}
if (hasExplicitName) await (0, node_fs_promises.mkdir)(skillDir, { recursive: true });
await (0, node_fs_promises.writeFile)(skillFile, renderTemplate(skillName), "utf-8");
logger.success(`Initialized skill ${picocolors.default.bold(skillName)}`);
console.log(` ${picocolors.default.dim("Created:")} ${displayPath}`);
console.log();
console.log(picocolors.default.dim("Next steps:"));
console.log(` 1. Edit ${picocolors.default.cyan(displayPath)} to define your skill instructions`);
console.log(` 2. Update the ${picocolors.default.cyan("name")} and ${picocolors.default.cyan("description")} frontmatter`);
console.log(` 3. Install it locally with ${picocolors.default.cyan(`agent-install skill add ./${hasExplicitName ? skillName : "."}`)}`);
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/skill/list.ts
const isUsableEntry = async (entry, fullPath) => {
if (entry.isDirectory()) return true;
if (!entry.isSymbolicLink()) return false;
try {
return (await (0, node_fs_promises.stat)(fullPath)).isDirectory();
} catch {
return false;
}
};
const scanSkillsInDir = async (baseDir, agentType) => {
const entries = await (0, node_fs_promises.readdir)(baseDir, { withFileTypes: true }).catch(() => []);
const results = [];
for (const entry of entries) {
const skillDir = (0, node_path.join)(baseDir, entry.name);
if (!await isUsableEntry(entry, skillDir)) continue;
const skills = await require_skill.discoverSkills(skillDir);
for (const skill of skills) results.push({
skill: skill.name,
agent: agentType,
description: skill.description,
path: skill.path
});
}
return results;
};
const dirExists = (path) => {
try {
return (0, node_fs.existsSync)(path);
} catch {
return false;
}
};
const getCanonicalDir = (isGlobal, cwd) => isGlobal ? (0, node_path.join)((0, node_os.homedir)(), require_skill.CANONICAL_SKILLS_DIR) : (0, node_path.join)(cwd, require_skill.CANONICAL_SKILLS_DIR);
const collectListEntries = async (options) => {
const { isGlobal, cwd, filter } = options;
const allEntries = [];
const visitedDirs = /* @__PURE__ */ new Set();
const visit = async (dir, agent) => {
if (visitedDirs.has(dir)) return;
visitedDirs.add(dir);
allEntries.push(...await scanSkillsInDir(dir, agent));
};
if (filter) {
for (const agentType of filter) await visit(require_skill.getSkillAgentDir(agentType, {
global: isGlobal,
cwd
}), agentType);
return allEntries;
}
await visit(getCanonicalDir(isGlobal, cwd), "universal");
const installedAgents = new Set(await require_skill.detectInstalledSkillAgents());
for (const agentType of require_skill.getSkillAgentTypes()) {
if (agentType === "universal") continue;
if (require_skill.isUniversalSkillAgent(agentType)) continue;
const baseDir = require_skill.getSkillAgentDir(agentType, {
global: isGlobal,
cwd
});
if (!installedAgents.has(agentType) && !dirExists(baseDir)) continue;
await visit(baseDir, agentType);
}
return allEntries;
};
const skillListCommand = new commander.Command("list").alias("ls").description("List installed skills").option("-g, --global", "List global skills instead of project skills").option("-a, --agent <agents...>", "Filter by specific agents").option("--json", "Output as JSON").action(async (options) => {
try {
const cwd = process.cwd();
const allEntries = await collectListEntries({
isGlobal: Boolean(options.global),
cwd,
filter: parseSkillAgentList(options.agent)
});
if (options.json) {
console.log(JSON.stringify(allEntries, null, 2));
return;
}
if (allEntries.length === 0) {
logger.warn("No installed skills found");
return;
}
const bySkill = /* @__PURE__ */ new Map();
for (const entry of allEntries) {
const existing = bySkill.get(entry.skill) ?? [];
existing.push(entry);
bySkill.set(entry.skill, existing);
}
const formatAgent = (agent) => agent === "universal" ? "canonical" : require_skill.skillAgents[agent].displayName;
for (const [skillName, entries] of bySkill) {
const first = entries[0];
if (!first) continue;
const agentLabels = Array.from(new Set(entries.map((entry) => formatAgent(entry.agent))));
const description = first.description || (0, node_path.basename)(first.path);
console.log(` ${picocolors.default.bold(skillName)} ${picocolors.default.dim(`[${agentLabels.join(", ")}]`)}\n ${picocolors.default.dim(description)}`);
}
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/skill/remove.ts
const skillRemoveCommand = new commander.Command("remove").alias("rm").description("Remove installed skills").argument("[skills...]", "Skills to remove (omit for interactive selection)").option("-g, --global", "Remove from global scope").option("-a, --agent <agents...>", "Remove from specific agents (use '*' for all)").option("-y, --yes", "Skip confirmation prompts").action(async (skillArgs, options) => {
try {
const cwd = process.cwd();
const isGlobal = Boolean(options.global);
const agentFilter = parseSkillAgentList(options.agent) ?? require_skill.getSkillAgentTypes();
let skillNames = skillArgs;
if (skillNames.length === 0) {
if (options.yes || !process.stdin.isTTY) {
logger.warn("No skill names provided");
return;
}
const response = await (0, prompts.default)({
type: "text",
name: "skill",
message: "Skill name to remove"
});
if (!response.skill) return;
skillNames = [response.skill];
}
const canonicalBase = isGlobal ? (0, node_path.join)((0, node_os.homedir)(), require_skill.CANONICAL_SKILLS_DIR) : (0, node_path.join)(cwd, require_skill.CANONICAL_SKILLS_DIR);
for (const rawName of skillNames) {
const sanitized = require_skill.sanitizeName(rawName);
await (0, node_fs_promises.rm)((0, node_path.join)(canonicalBase, sanitized), {
recursive: true,
force: true
}).catch(() => {});
for (const agentType of agentFilter) {
if (agentType === "universal") continue;
await (0, node_fs_promises.rm)((0, node_path.join)(require_skill.getSkillAgentDir(agentType, {
global: isGlobal,
cwd
}), sanitized), {
recursive: true,
force: true
}).catch(() => {});
}
logger.success(`Removed ${picocolors.default.bold(rawName)}`);
}
} catch (error) {
logger.error(require_to_error_message.toErrorMessage(error));
process.exitCode = 1;
}
});
//#endregion
//#region src/cli/commands/skill/index.ts
const skillCommand = new commander.Command("skill").description("Manage SKILL.md files").addCommand(skillAddCommand).addCommand(skillInitCommand).addCommand(skillListCommand).addCommand(skillRemoveCommand);
//#endregion
//#region src/cli.ts
const VERSION = "0.0.6";
process.on("SIGINT", () => process.exit(0));
process.on("SIGTERM", () => process.exit(0));
const program = new commander.Command().name("agent-install").description("Install SKILL.md files, MCP servers, and AGENTS.md guidance for any coding agent").version(VERSION, "-v, --version", "display the version number");
program.addCommand(skillCommand);
program.addCommand(mcpCommand);
program.addCommand(docCommand);
const main = async () => {
await program.parseAsync();
};
main();
//#endregion