Skip to content
Open
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
67 changes: 66 additions & 1 deletion src/add.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as p from '@clack/prompts';
import pc from 'picocolors';
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
import { homedir } from 'os';
import { sep } from 'path';
import { sep, basename, join, resolve } from 'path';
import { parseSource, getOwnerRepo, parseOwnerRepo, isRepoPrivate } from './source-parser.ts';
import { searchMultiselect, cancelSymbol } from './prompts/search-multiselect.ts';

Expand Down Expand Up @@ -418,6 +419,7 @@ export interface AddOptions {
all?: boolean;
fullDepth?: boolean;
copy?: boolean;
dir?: string;
}

/**
Expand Down Expand Up @@ -524,6 +526,64 @@ async function handleWellKnownSkills(
selectedSkills = selected as WellKnownSkill[];
}

// --dir: install directly to user-specified directory, skip agent detection
if (options.dir) {
const customDir = resolve(options.dir);
p.log.info(`Installing to custom directory: ${customDir}`);

const spinner = p.spinner();
spinner.start('Installing skills...');

const results: {
skill: string;
success: boolean;
path: string;
error?: string;
}[] = [];

for (const skill of selectedSkills) {
const skillName = skill.name || basename(skill.path);
const targetDir = join(customDir, skillName);
try {
await mkdir(targetDir, { recursive: true });
await copyDirectory(skill.path, targetDir);
results.push({ skill: getSkillDisplayName(skill), success: true, path: targetDir });
} catch (err) {
results.push({
skill: getSkillDisplayName(skill),
success: false,
path: targetDir,
error: err instanceof Error ? err.message : String(err),
});
}
}

spinner.stop('Installation complete');
console.log();

const successful = results.filter((r) => r.success);
const failed = results.filter((r) => !r.success);

if (successful.length > 0) {
for (const r of successful) {
console.log(` ${pc.green('✓')} ${r.skill} → ${pc.dim(r.path)}`);
}
}

if (failed.length > 0) {
for (const r of failed) {
console.log(` ${pc.red('✗')} ${r.skill}: ${r.error}`);
}
}

// Clean up temp dir if needed
if (tempDir) {
await cleanupTempDir(tempDir);
}

return;
}

// Detect agents
let targetAgents: AgentType[];
const validAgents = Object.keys(agents);
Expand Down Expand Up @@ -1801,6 +1861,11 @@ export function parseAddOptions(args: string[]): { source: string[]; options: Ad
options.fullDepth = true;
} else if (arg === '--copy') {
options.copy = true;
} else if (arg === '-d' || arg === '--dir') {
i++;
if (i < args.length) {
options.dir = args[i];
}
} else if (arg && !arg.startsWith('-')) {
source.push(arg);
}
Expand Down
1 change: 1 addition & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ ${BOLD}Project:${RESET}
${BOLD}Add Options:${RESET}
-g, --global Install skill globally (user-level) instead of project-level
-a, --agent <agents> Specify agents to install to (use '*' for all agents)
-d, --dir <path> Install skills to a custom directory (skips agent detection)
-s, --skill <skills> Specify skill names to install (use '*' for all skills)
-l, --list List available skills in the repository without installing
-y, --yes Skip confirmation prompts
Expand Down