192 lines
6.7 KiB
TypeScript
192 lines
6.7 KiB
TypeScript
/**
|
|
* takt export-cc — Deploy takt skill files to Claude Code.
|
|
*
|
|
* Copies the following to ~/.claude/skills/takt/:
|
|
* SKILL.md — Engine overview (user-invocable as /takt)
|
|
* references/ — Engine logic + YAML schema
|
|
* pieces/ — Builtin piece YAML files
|
|
* personas/ — Builtin persona .md files
|
|
* stances/ — Builtin stance files
|
|
* instructions/ — Builtin instruction files
|
|
* knowledge/ — Builtin knowledge files
|
|
* report-formats/ — Builtin report format files
|
|
* templates/ — Builtin template files
|
|
*
|
|
* Piece YAML persona paths (../personas/...) work as-is because
|
|
* the directory structure is mirrored.
|
|
*/
|
|
|
|
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
|
import { homedir } from 'node:os';
|
|
import { join, dirname, relative } from 'node:path';
|
|
|
|
import { getLanguage } from '../../infra/config/index.js';
|
|
import { getResourcesDir, getLanguageResourcesDir } from '../../infra/resources/index.js';
|
|
import { confirm } from '../../shared/prompt/index.js';
|
|
import { header, success, info, warn, blankLine } from '../../shared/ui/index.js';
|
|
|
|
/** Files to skip during directory copy */
|
|
const SKIP_FILES = new Set(['.DS_Store', 'Thumbs.db']);
|
|
|
|
/** Target paths under ~/.claude/ */
|
|
function getSkillDir(): string {
|
|
return join(homedir(), '.claude', 'skills', 'takt');
|
|
}
|
|
|
|
/** Directories within builtins/{lang}/ to copy as resource types */
|
|
const RESOURCE_DIRS = [
|
|
'pieces',
|
|
'personas',
|
|
'stances',
|
|
'instructions',
|
|
'knowledge',
|
|
'report-formats',
|
|
'templates',
|
|
] as const;
|
|
|
|
/**
|
|
* Deploy takt skill to Claude Code (~/.claude/).
|
|
*/
|
|
export async function deploySkill(): Promise<void> {
|
|
header('takt export-cc — Deploy to Claude Code');
|
|
|
|
const lang = getLanguage();
|
|
const skillResourcesDir = join(getResourcesDir(), 'skill');
|
|
const langResourcesDir = getLanguageResourcesDir(lang);
|
|
const skillDir = getSkillDir();
|
|
|
|
// Verify source directories exist
|
|
if (!existsSync(skillResourcesDir)) {
|
|
warn('Skill resources not found. Ensure takt is installed correctly.');
|
|
return;
|
|
}
|
|
|
|
// Check if skill already exists and ask for confirmation
|
|
const skillExists = existsSync(join(skillDir, 'SKILL.md'));
|
|
if (skillExists) {
|
|
info('Claude Code Skill が既にインストールされています。');
|
|
const overwrite = await confirm(
|
|
'既存のスキルファイルをすべて削除し、最新版に置き換えます。続行しますか?',
|
|
false,
|
|
);
|
|
if (!overwrite) {
|
|
info('キャンセルしました。');
|
|
return;
|
|
}
|
|
blankLine();
|
|
}
|
|
|
|
const copiedFiles: string[] = [];
|
|
|
|
// 1. Deploy SKILL.md
|
|
const skillSrc = join(skillResourcesDir, 'SKILL.md');
|
|
const skillDest = join(skillDir, 'SKILL.md');
|
|
copyFile(skillSrc, skillDest, copiedFiles);
|
|
|
|
// 2. Deploy references/ (engine.md, yaml-schema.md)
|
|
const refsSrcDir = join(skillResourcesDir, 'references');
|
|
const refsDestDir = join(skillDir, 'references');
|
|
cleanDir(refsDestDir);
|
|
copyDirRecursive(refsSrcDir, refsDestDir, copiedFiles);
|
|
|
|
// 3. Deploy all resource directories from builtins/{lang}/
|
|
for (const resourceDir of RESOURCE_DIRS) {
|
|
const srcDir = join(langResourcesDir, resourceDir);
|
|
const destDir = join(skillDir, resourceDir);
|
|
cleanDir(destDir);
|
|
copyDirRecursive(srcDir, destDir, copiedFiles);
|
|
}
|
|
|
|
// Report results
|
|
blankLine();
|
|
if (copiedFiles.length > 0) {
|
|
success(`${copiedFiles.length} ファイルをデプロイしました。`);
|
|
blankLine();
|
|
|
|
// Show summary by category
|
|
const skillBase = join(homedir(), '.claude');
|
|
const skillFiles = copiedFiles.filter(
|
|
(f) =>
|
|
f.startsWith(skillDir) &&
|
|
!RESOURCE_DIRS.some((dir) => f.includes(`/${dir}/`)),
|
|
);
|
|
const pieceFiles = copiedFiles.filter((f) => f.includes('/pieces/'));
|
|
const personaFiles = copiedFiles.filter((f) => f.includes('/personas/'));
|
|
const stanceFiles = copiedFiles.filter((f) => f.includes('/stances/'));
|
|
const instructionFiles = copiedFiles.filter((f) => f.includes('/instructions/'));
|
|
const knowledgeFiles = copiedFiles.filter((f) => f.includes('/knowledge/'));
|
|
const reportFormatFiles = copiedFiles.filter((f) => f.includes('/report-formats/'));
|
|
const templateFiles = copiedFiles.filter((f) => f.includes('/templates/'));
|
|
|
|
if (skillFiles.length > 0) {
|
|
info(` スキル: ${skillFiles.length} ファイル`);
|
|
for (const f of skillFiles) {
|
|
info(` ${relative(skillBase, f)}`);
|
|
}
|
|
}
|
|
if (pieceFiles.length > 0) {
|
|
info(` ピース: ${pieceFiles.length} ファイル`);
|
|
}
|
|
if (personaFiles.length > 0) {
|
|
info(` ペルソナ: ${personaFiles.length} ファイル`);
|
|
}
|
|
if (stanceFiles.length > 0) {
|
|
info(` スタンス: ${stanceFiles.length} ファイル`);
|
|
}
|
|
if (instructionFiles.length > 0) {
|
|
info(` インストラクション: ${instructionFiles.length} ファイル`);
|
|
}
|
|
if (knowledgeFiles.length > 0) {
|
|
info(` ナレッジ: ${knowledgeFiles.length} ファイル`);
|
|
}
|
|
if (reportFormatFiles.length > 0) {
|
|
info(` レポート形式: ${reportFormatFiles.length} ファイル`);
|
|
}
|
|
if (templateFiles.length > 0) {
|
|
info(` テンプレート: ${templateFiles.length} ファイル`);
|
|
}
|
|
|
|
blankLine();
|
|
info('使い方: /takt <piece-name> <task>');
|
|
info('例: /takt passthrough "Hello World テスト"');
|
|
} else {
|
|
info('デプロイするファイルがありませんでした。');
|
|
}
|
|
}
|
|
|
|
/** Remove a directory and all its contents so stale files don't persist across deploys. */
|
|
function cleanDir(dir: string): void {
|
|
if (existsSync(dir)) {
|
|
rmSync(dir, { recursive: true });
|
|
}
|
|
}
|
|
|
|
/** Copy a single file, creating parent directories as needed. */
|
|
function copyFile(src: string, dest: string, copiedFiles: string[]): void {
|
|
if (!existsSync(src)) return;
|
|
mkdirSync(dirname(dest), { recursive: true });
|
|
writeFileSync(dest, readFileSync(src));
|
|
copiedFiles.push(dest);
|
|
}
|
|
|
|
/** Recursively copy directory contents, always overwriting. */
|
|
function copyDirRecursive(srcDir: string, destDir: string, copiedFiles: string[]): void {
|
|
if (!existsSync(srcDir)) return;
|
|
mkdirSync(destDir, { recursive: true });
|
|
|
|
for (const entry of readdirSync(srcDir)) {
|
|
if (SKIP_FILES.has(entry)) continue;
|
|
|
|
const srcPath = join(srcDir, entry);
|
|
const destPath = join(destDir, entry);
|
|
const stat = statSync(srcPath);
|
|
|
|
if (stat.isDirectory()) {
|
|
copyDirRecursive(srcPath, destPath, copiedFiles);
|
|
} else {
|
|
writeFileSync(destPath, readFileSync(srcPath));
|
|
copiedFiles.push(destPath);
|
|
}
|
|
}
|
|
}
|