add /refresh-builtin

This commit is contained in:
nrslib 2026-01-27 22:42:04 +09:00
parent 4671af954a
commit 7270b29044
5 changed files with 106 additions and 17 deletions

View File

@ -30,6 +30,7 @@ import {
switchWorkflow, switchWorkflow,
switchConfig, switchConfig,
addTask, addTask,
refreshBuiltin,
} from './commands/index.js'; } from './commands/index.js';
import { listWorkflows } from './config/workflowLoader.js'; import { listWorkflows } from './config/workflowLoader.js';
import { selectOptionWithDefault } from './prompt/index.js'; import { selectOptionWithDefault } from './prompt/index.js';
@ -109,9 +110,13 @@ program
await addTask(cwd, args); await addTask(cwd, args);
return; return;
case 'refresh-builtin':
await refreshBuiltin();
return;
default: default:
error(`Unknown command: /${command}`); 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); process.exit(1);
} }
} }

View File

@ -13,18 +13,20 @@ export function showHelp(): void {
console.log(` console.log(`
Usage: Usage:
takt {task} Execute task with current workflow (continues session) takt {task} Execute task with current workflow (continues session)
takt /run-tasks Run all pending tasks from .takt/tasks/ takt /run-tasks Run all pending tasks from .takt/tasks/
takt /add-task Add a new task (interactive, YAML format) takt /add-task Add a new task (interactive, YAML format)
takt /switch Switch workflow interactively takt /switch Switch workflow interactively
takt /clear Clear agent conversation sessions (reset to initial state) takt /clear Clear agent conversation sessions (reset to initial state)
takt /help Show this help takt /refresh-builtin Overwrite builtin agents/workflows with latest version
takt /help Show this help
Examples: Examples:
takt "Fix the bug in main.ts" # Execute task (continues session) takt "Fix the bug in main.ts" # Execute task (continues session)
takt /add-task "認証機能を追加する" # Quick add task takt /add-task "認証機能を追加する" # Quick add task
takt /add-task # Interactive task creation takt /add-task # Interactive task creation
takt /clear # Clear sessions, start fresh takt /clear # Clear sessions, start fresh
takt /refresh-builtin # Update builtin resources
takt /switch takt /switch
takt /run-tasks takt /run-tasks

View File

@ -5,6 +5,7 @@
export { executeWorkflow, type WorkflowExecutionResult, type WorkflowExecutionOptions } from './workflowExecution.js'; export { executeWorkflow, type WorkflowExecutionResult, type WorkflowExecutionOptions } from './workflowExecution.js';
export { executeTask, runAllTasks } from './taskExecution.js'; export { executeTask, runAllTasks } from './taskExecution.js';
export { addTask } from './addTask.js'; export { addTask } from './addTask.js';
export { refreshBuiltin } from './refreshBuiltin.js';
export { showHelp } from './help.js'; export { showHelp } from './help.js';
export { withAgentSession } from './session.js'; export { withAgentSession } from './session.js';
export { switchWorkflow } from './workflow.js'; export { switchWorkflow } from './workflow.js';

View File

@ -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<void> {
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}`);
}
}

View File

@ -55,7 +55,7 @@ export function copyGlobalResourcesToDir(targetDir: string): void {
return; return;
} }
// Skip language directories (they are handled by copyLanguageResourcesToDir) // 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) */ /** Files to skip during resource copy (OS-generated files) */
const SKIP_FILES = ['.DS_Store', 'Thumbs.db']; 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. * Recursively copy directory contents.
* Skips files that already exist in target. * @param overwrite - If false (default), skips files that already exist in target.
* @param skipDirs - Directory names to skip at top level * 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)) { if (!existsSync(destDir)) {
mkdirSync(destDir, { recursive: true }); mkdirSync(destDir, { recursive: true });
} }
for (const entry of readdirSync(srcDir)) { for (const entry of readdirSync(srcDir)) {
// Skip OS-generated files
if (SKIP_FILES.includes(entry)) continue; if (SKIP_FILES.includes(entry)) continue;
// Skip specified directories
if (skipDirs.includes(entry)) continue; if (skipDirs.includes(entry)) continue;
const srcPath = join(srcDir, entry); const srcPath = join(srcDir, entry);
@ -131,11 +169,11 @@ function copyDirRecursive(srcDir: string, destDir: string, skipDirs: string[] =
const stat = statSync(srcPath); const stat = statSync(srcPath);
if (stat.isDirectory()) { if (stat.isDirectory()) {
copyDirRecursive(srcPath, destPath); copyDirRecursive(srcPath, destPath, { overwrite, copiedFiles });
} else if (!existsSync(destPath)) { } else if (overwrite || !existsSync(destPath)) {
// Only copy if file doesn't exist
const content = readFileSync(srcPath); const content = readFileSync(srcPath);
writeFileSync(destPath, content); writeFileSync(destPath, content);
copiedFiles?.push(destPath);
} }
} }
} }