From 7270b29044724b374fc8fcb4b0f15188c7fca509 Mon Sep 17 00:00:00 2001 From: nrslib <38722970+nrslib@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:42:04 +0900 Subject: [PATCH] add /refresh-builtin --- src/cli.ts | 7 +++- src/commands/help.ts | 14 ++++---- src/commands/index.ts | 1 + src/commands/refreshBuiltin.ts | 43 +++++++++++++++++++++++++ src/resources/index.ts | 58 ++++++++++++++++++++++++++++------ 5 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 src/commands/refreshBuiltin.ts diff --git a/src/cli.ts b/src/cli.ts index df5d2e0..291ea44 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -30,6 +30,7 @@ import { switchWorkflow, switchConfig, addTask, + refreshBuiltin, } from './commands/index.js'; import { listWorkflows } from './config/workflowLoader.js'; import { selectOptionWithDefault } from './prompt/index.js'; @@ -109,9 +110,13 @@ program await addTask(cwd, args); return; + case 'refresh-builtin': + await refreshBuiltin(); + return; + default: error(`Unknown command: /${command}`); - info('Available: /run-tasks, /add-task, /switch, /clear, /help, /config'); + info('Available: /run-tasks, /add-task, /switch, /clear, /refresh-builtin, /help, /config'); process.exit(1); } } diff --git a/src/commands/help.ts b/src/commands/help.ts index 0a385ca..c5cf42e 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -13,18 +13,20 @@ export function showHelp(): void { console.log(` Usage: - takt {task} Execute task with current workflow (continues session) - takt /run-tasks Run all pending tasks from .takt/tasks/ - takt /add-task Add a new task (interactive, YAML format) - takt /switch Switch workflow interactively - takt /clear Clear agent conversation sessions (reset to initial state) - takt /help Show this help + takt {task} Execute task with current workflow (continues session) + takt /run-tasks Run all pending tasks from .takt/tasks/ + takt /add-task Add a new task (interactive, YAML format) + takt /switch Switch workflow interactively + takt /clear Clear agent conversation sessions (reset to initial state) + takt /refresh-builtin Overwrite builtin agents/workflows with latest version + takt /help Show this help Examples: takt "Fix the bug in main.ts" # Execute task (continues session) takt /add-task "認証機能を追加する" # Quick add task takt /add-task # Interactive task creation takt /clear # Clear sessions, start fresh + takt /refresh-builtin # Update builtin resources takt /switch takt /run-tasks diff --git a/src/commands/index.ts b/src/commands/index.ts index 5b9d6aa..208b865 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -5,6 +5,7 @@ export { executeWorkflow, type WorkflowExecutionResult, type WorkflowExecutionOptions } from './workflowExecution.js'; export { executeTask, runAllTasks } from './taskExecution.js'; export { addTask } from './addTask.js'; +export { refreshBuiltin } from './refreshBuiltin.js'; export { showHelp } from './help.js'; export { withAgentSession } from './session.js'; export { switchWorkflow } from './workflow.js'; diff --git a/src/commands/refreshBuiltin.ts b/src/commands/refreshBuiltin.ts new file mode 100644 index 0000000..16be721 --- /dev/null +++ b/src/commands/refreshBuiltin.ts @@ -0,0 +1,43 @@ +/** + * /refresh-builtin command implementation + * + * Overwrites builtin workflow and agent files in ~/.takt/ with the latest + * embedded resources. Does NOT touch config.yaml or user-added files. + */ + +import { getGlobalConfigDir } from '../config/paths.js'; +import { getLanguage } from '../config/globalConfig.js'; +import { forceRefreshLanguageResources } from '../resources/index.js'; +import { header, success, info, error } from '../utils/ui.js'; +import { createLogger } from '../utils/debug.js'; + +const log = createLogger('refresh-builtin'); + +/** + * Refresh builtin agents and workflows to latest version. + */ +export async function refreshBuiltin(): Promise { + const globalDir = getGlobalConfigDir(); + const lang = getLanguage(); + + header('Refresh Builtin Resources'); + info(`Language: ${lang}`); + info(`Target: ${globalDir}`); + + try { + const overwritten = forceRefreshLanguageResources(globalDir, lang); + + log.info('Builtin resources refreshed', { count: overwritten.length, lang }); + + console.log(); + success(`${overwritten.length} files refreshed.`); + + for (const filePath of overwritten) { + info(` ✓ ${filePath}`); + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + log.error('Failed to refresh builtin resources', { error: message }); + error(`Failed to refresh: ${message}`); + } +} diff --git a/src/resources/index.ts b/src/resources/index.ts index a0ee09f..d5bd0da 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -55,7 +55,7 @@ export function copyGlobalResourcesToDir(targetDir: string): void { return; } // Skip language directories (they are handled by copyLanguageResourcesToDir) - copyDirRecursive(resourcesDir, targetDir, ['en', 'ja']); + copyDirRecursive(resourcesDir, targetDir, { skipDirs: ['en', 'ja'] }); } /** @@ -106,24 +106,62 @@ export function copyLanguageResourcesToDir(targetDir: string, lang: Language): v } } +/** + * Force-refresh language-specific resources (agents and workflows) to ~/.takt. + * Overwrites existing builtin files. Does NOT touch config.yaml. + */ +export function forceRefreshLanguageResources(targetDir: string, lang: Language): string[] { + const langDir = getLanguageResourcesDir(lang); + if (!existsSync(langDir)) { + throw new Error(`Language resources not found: ${langDir}`); + } + + const copiedFiles: string[] = []; + const forceOptions = { overwrite: true, copiedFiles }; + + // Overwrite agents directory + const langAgentsDir = join(langDir, 'agents'); + const targetAgentsDir = join(targetDir, 'agents'); + if (existsSync(langAgentsDir)) { + copyDirRecursive(langAgentsDir, targetAgentsDir, forceOptions); + } + + // Overwrite workflows directory + const langWorkflowsDir = join(langDir, 'workflows'); + const targetWorkflowsDir = join(targetDir, 'workflows'); + if (existsSync(langWorkflowsDir)) { + copyDirRecursive(langWorkflowsDir, targetWorkflowsDir, forceOptions); + } + + return copiedFiles; +} + /** Files to skip during resource copy (OS-generated files) */ const SKIP_FILES = ['.DS_Store', 'Thumbs.db']; +interface CopyOptions { + /** Directory names to skip at the top level */ + skipDirs?: string[]; + /** Overwrite existing files (default: false) */ + overwrite?: boolean; + /** Collect copied file paths into this array */ + copiedFiles?: string[]; +} + /** * Recursively copy directory contents. - * Skips files that already exist in target. - * @param skipDirs - Directory names to skip at top level + * @param overwrite - If false (default), skips files that already exist in target. + * If true, overwrites existing files. */ -function copyDirRecursive(srcDir: string, destDir: string, skipDirs: string[] = []): void { +function copyDirRecursive(srcDir: string, destDir: string, options: CopyOptions = {}): void { + const { skipDirs = [], overwrite = false, copiedFiles } = options; + if (!existsSync(destDir)) { mkdirSync(destDir, { recursive: true }); } for (const entry of readdirSync(srcDir)) { - // Skip OS-generated files if (SKIP_FILES.includes(entry)) continue; - - // Skip specified directories if (skipDirs.includes(entry)) continue; const srcPath = join(srcDir, entry); @@ -131,11 +169,11 @@ function copyDirRecursive(srcDir: string, destDir: string, skipDirs: string[] = const stat = statSync(srcPath); if (stat.isDirectory()) { - copyDirRecursive(srcPath, destPath); - } else if (!existsSync(destPath)) { - // Only copy if file doesn't exist + copyDirRecursive(srcPath, destPath, { overwrite, copiedFiles }); + } else if (overwrite || !existsSync(destPath)) { const content = readFileSync(srcPath); writeFileSync(destPath, content); + copiedFiles?.push(destPath); } } }