repomix
Version:
A tool to pack repository contents to single file for AI consumption
105 lines (104 loc) • 3.55 kB
JavaScript
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import pc from 'picocolors';
import { OperationCancelledError, RepomixError } from '../../shared/errorHandle.js';
import { getDisplayPath } from '../cliReport.js';
const onCancelOperation = (cancelFn) => {
cancelFn('Skill generation cancelled.');
throw new OperationCancelledError('Skill generation cancelled');
};
export const getSkillBaseDir = (cwd, location) => {
if (location === 'personal') {
return path.join(os.homedir(), '.claude', 'skills');
}
return path.join(cwd, '.claude', 'skills');
};
const createPromptDeps = async () => {
const p = await import('@clack/prompts');
return {
select: p.select,
confirm: p.confirm,
isCancel: p.isCancel,
cancel: p.cancel,
access: fs.access,
rm: fs.rm,
};
};
export const promptSkillLocation = async (skillName, cwd, deps) => {
const resolvedDeps = deps ?? (await createPromptDeps());
const location = await resolvedDeps.select({
message: 'Where would you like to save the skill?',
options: [
{
value: 'personal',
label: 'Personal Skills',
hint: '~/.claude/skills/ - Available across all projects',
},
{
value: 'project',
label: 'Project Skills',
hint: '.claude/skills/ - Shared with team via git',
},
],
initialValue: 'personal',
});
if (resolvedDeps.isCancel(location)) {
onCancelOperation(resolvedDeps.cancel);
}
const skillDir = path.join(getSkillBaseDir(cwd, location), skillName);
let dirExists = false;
try {
await resolvedDeps.access(skillDir);
dirExists = true;
}
catch {
}
if (dirExists) {
const displayPath = getDisplayPath(skillDir, cwd);
const overwrite = await resolvedDeps.confirm({
message: `Skill directory already exists. Do you want to overwrite it?\n${pc.dim(`path: ${displayPath}`)}`,
});
if (resolvedDeps.isCancel(overwrite) || !overwrite) {
onCancelOperation(resolvedDeps.cancel);
}
await resolvedDeps.rm(skillDir, { recursive: true, force: true });
}
return {
location: location,
skillDir,
};
};
export const prepareSkillDir = async (skillDir, force, deps = {
access: fs.access,
rm: fs.rm,
stat: fs.stat,
}) => {
try {
await deps.access(skillDir);
const stats = await deps.stat(skillDir);
if (!stats.isDirectory()) {
throw new RepomixError(`Skill output path exists but is not a directory: ${skillDir}`);
}
if (force) {
await deps.rm(skillDir, { recursive: true, force: true });
}
else {
throw new RepomixError(`Skill directory already exists: ${skillDir}. Use --force to overwrite.`);
}
}
catch (error) {
if (error?.code !== 'ENOENT') {
throw error;
}
}
};
export const resolveAndPrepareSkillDir = async (skillOutput, cwd, force) => {
const skillDir = path.isAbsolute(skillOutput) ? skillOutput : path.resolve(cwd, skillOutput);
await prepareSkillDir(skillDir, force);
return skillDir;
};
export const getSkillLocation = (skillDir) => {
const personalSkillsBase = getSkillBaseDir('', 'personal');
return skillDir.startsWith(personalSkillsBase) ? 'personal' : 'project';
};