import { readdirSync, existsSync, readFileSync } from 'fs'; import { join } from 'path'; import { getCodexHome } from './db.js'; const CODEX_HOME = getCodexHome(); /** * Parse YAML frontmatter (--- delimited) from a file. */ function parseFrontmatter(content) { const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/); if (!match) return null; const yaml = match[1]; const meta = {}; for (const line of yaml.split('\n')) { const kv = line.match(/^(\w+):\s*(.*\S)?\s*$/); if (kv) { meta[kv[1]] = kv[2] || ''; } } return meta; } /** * Scan local tools/skills/plugins available to Codex. * This is a static scan of the .codex directory for registered tools. */ export function scanAvailableTools() { const tools = { builtin: [], skills: [], plugins: [], cache_tools: [], }; // Scan skills directory const skillsDir = join(CODEX_HOME, 'skills'); if (existsSync(skillsDir)) { try { const entries = readdirSync(skillsDir); for (const entry of entries) { const skillPath = join(skillsDir, entry); const meta = extractSkillMeta(skillPath); tools.skills.push({ name: entry, path: skillPath, description: meta?.description || meta?.name || '', is_dir: true, }); } } catch (e) { console.warn('[Tools] Skills scan error:', e.message); } } // Scan plugins directory const pluginsDir = join(CODEX_HOME, 'plugins'); if (existsSync(pluginsDir)) { try { const entries = readdirSync(pluginsDir); for (const entry of entries) { tools.plugins.push({ name: entry, path: join(pluginsDir, entry), }); } } catch (e) { console.warn('[Tools] Plugins scan error:', e.message); } } // Scan cache/tools for JSON tool definitions const cacheToolsDir = join(CODEX_HOME, 'cache', 'codex_apps_tools'); if (existsSync(cacheToolsDir)) { try { const entries = readdirSync(cacheToolsDir); for (const entry of entries) { if (entry.endsWith('.json')) { try { const content = readFileSync(join(cacheToolsDir, entry), 'utf-8'); const data = JSON.parse(content); // Flatten if it's a wrapping object like { tools: [...] } if (data && !Array.isArray(data) && data.tools && Array.isArray(data.tools)) { tools.cache_tools.push({ file: entry, description: data.description || '', tool_names: data.tools.map(t => t.name || t.function?.name || 'unnamed'), }); } else { const arr = Array.isArray(data) ? data : [data]; tools.cache_tools.push({ file: entry, description: '', tool_names: arr.map(t => t.name || t.function?.name || 'unnamed'), }); } } catch (e) { // skip unparseable } } } } catch (e) { console.warn('[Tools] Cache tools scan error:', e.message); } } return tools; } function extractSkillMeta(skillPath) { try { const files = readdirSync(skillPath); // Try SKILL.md frontmatter first const readmeFile = files.find(f => f === 'SKILL.md' || f === 'README.md'); if (readmeFile) { const content = readFileSync(join(skillPath, readmeFile), 'utf-8'); const meta = parseFrontmatter(content); if (meta) return meta; } // Fallback to JSON meta files const metaFile = files.find(f => f === 'skill.json' || f === 'meta.json' || f === 'package.json'); if (metaFile) { const content = readFileSync(join(skillPath, metaFile), 'utf-8'); return JSON.parse(content); } } catch (e) { // ignore } return null; } /** * Aggregate tool usage statistics across all cached sessions. * @param {Array} sessionsCache */ export function aggregateToolUsage(sessionsCache) { const toolMap = new Map(); for (const session of sessionsCache) { if (!session.tool_calls) continue; for (const [toolName, count] of Object.entries(session.tool_calls)) { if (!toolMap.has(toolName)) { toolMap.set(toolName, { name: toolName, total_calls: 0, session_count: 0, sessions: [], }); } const entry = toolMap.get(toolName); entry.total_calls += count; entry.session_count++; entry.sessions.push({ session_id: session.session_id, calls: count, preview: session.first_user_message?.slice(0, 80) || '', }); } } return Array.from(toolMap.values()) .sort((a, b) => b.total_calls - a.total_calls); }