Configure SkillForge
skills__configureAdd or remove skill folders, manage blacklist, or reset configuration to default values.
Instructions
Manage configured skill folders, blacklist, and reset to defaults. Mutates persisted config under defaultConfigPath().
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | ||
| folder | No | ||
| blacklist | No |
Implementation Reference
- src/tools/configure.ts:56-155 (handler)Main handler function for the skills__configure tool. Handles six actions: add_folder, remove_folder, list_folders, set_blacklist, get_blacklist, reset. Mutates persisted config and reconciles folders in-place on deps.folders.
export async function handleConfigure( deps: ServerDeps, args: { action: ConfigureAction; folder?: string; blacklist?: string[] }, ): Promise<ConfigureResult> { const { action } = args; try { if (action === 'list_folders') { const persisted = await deps.configStore.load(); return { folders: [...deps.folders], blacklist: persisted.blacklist, totalSkills: deps.registry.size, }; } if (action === 'get_blacklist') { const persisted = await deps.configStore.load(); return { folders: [...deps.folders], blacklist: persisted.blacklist, totalSkills: deps.registry.size, }; } if (action === 'add_folder') { if (args.folder === undefined) { throw new Error(`configure: action "add_folder" requires "folder"`); } if (args.folder.trim().length === 0) { throw new Error('configure: folder path must not be empty'); } const absPath = resolve(args.folder); const persisted = await deps.configStore.load(); const alreadyPresent = persisted.folders.some((f) => resolve(f.path) === absPath); if (!alreadyPresent) { persisted.folders.push({ path: absPath, priority: 100, enabled: true, tags: [] }); } // Why: always save even on no-op — simpler than branching, atomic write is cheap. await deps.configStore.save(persisted); const finalPersisted = await reconcileFolders(deps); return { folders: [...deps.folders], blacklist: finalPersisted.blacklist, totalSkills: deps.registry.size, }; } if (action === 'remove_folder') { if (args.folder === undefined) { throw new Error(`configure: action "remove_folder" requires "folder"`); } const absPath = resolve(args.folder); const persisted = await deps.configStore.load(); persisted.folders = persisted.folders.filter((f) => resolve(f.path) !== absPath); await deps.configStore.save(persisted); const finalPersisted = await reconcileFolders(deps); return { folders: [...deps.folders], blacklist: finalPersisted.blacklist, totalSkills: deps.registry.size, }; } if (action === 'set_blacklist') { if (args.blacklist === undefined) { throw new Error(`configure: action "set_blacklist" requires "blacklist"`); } const persisted = await deps.configStore.load(); persisted.blacklist = args.blacklist; await deps.configStore.save(persisted); const finalPersisted = await reconcileFolders(deps); return { folders: [...deps.folders], blacklist: finalPersisted.blacklist, totalSkills: deps.registry.size, }; } if (action === 'reset') { const fresh = defaultConfig(); await deps.configStore.save(fresh); const finalPersisted = await reconcileFolders(deps); return { folders: [...deps.folders], blacklist: finalPersisted.blacklist, totalSkills: deps.registry.size, }; } throw new Error(`configure: unknown action "${action as string}"`); } catch (err) { const msg = err instanceof Error ? err.message : String(err); // Avoid double-prefixing if error already has the action prefix. if (msg.startsWith(`configure(${action}):`) || msg.startsWith('configure: ')) { throw err; } throw new Error(`configure(${action}): ${msg}`); } } - src/tools/configure.ts:9-20 (schema)Input schema for skills__configure using Zod. Defines action enum (add_folder, remove_folder, list_folders, set_blacklist, get_blacklist, reset) with optional folder string and optional blacklist string array.
export const configureInputSchema = { action: z.enum([ 'add_folder', 'remove_folder', 'list_folders', 'set_blacklist', 'get_blacklist', 'reset', ]), folder: z.string().optional(), blacklist: z.array(z.string()).optional(), } as const; - src/tools/configure.ts:40-54 (helper)Reconciles folders by loading persisted config, resolving env+persisted config, splicing folders in-place on deps.folders, setting blacklist, invalidating cache, updating folder watcher, and ensuring registry freshness.
async function reconcileFolders(deps: ServerDeps): Promise<PersistedConfig> { const persisted = await deps.configStore.load(); const resolved = await loadResolvedConfig(process.env, deps.configStore); // Splice in-place so all references to deps.folders see the new list. deps.folders.splice(0, deps.folders.length, ...resolved.folders); deps.blacklistFilter.setManualBlacklist(persisted.blacklist); deps.metadataCache.invalidate(); try { await deps.folderWatcher.setFolders(deps.folders); } catch (err) { console.error(`[skillforge:configure] watcher setFolders failed: ${String(err)}`); } await ensureRegistryFresh(deps); return persisted; } - src/tools/configure.ts:30-37 (helper)ConfigureResult type defining the return shape: folders (resolved paths), blacklist (manual blacklist), and totalSkills (registry size after action).
export interface ConfigureResult { /** Currently active resolved folders (post-action). */ folders: string[]; /** Currently active manual blacklist (post-action). */ blacklist: string[]; /** Skills visible in the registry after the action took effect. */ totalSkills: number; }