import { existsSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const moduleDir = dirname(fileURLToPath(import.meta.url)); function firstExisting(candidates: string[], fallback: string): string { for (const candidate of candidates) { if (existsSync(candidate)) return candidate; } return fallback; } /** * Directory holding the imported knowledge base JSON. * - dev (tsx): `/src/knowledge/data` * - build (node): `/dist/knowledge/data` (copied by `copy-assets.mjs`) */ export function knowledgeDataDir(): string { return firstExisting( [resolve(moduleDir, "../knowledge/data"), resolve(moduleDir, "knowledge/data")], resolve(moduleDir, "../knowledge/data"), ); } /** * Directory holding recipe JSON files. * - dev: `/recipes` * - build: `/dist/recipes` */ export function recipesDir(): string { return firstExisting( [resolve(moduleDir, "../../recipes"), resolve(moduleDir, "recipes")], resolve(moduleDir, "../../recipes"), ); } /** * Directory holding the TouchDesigner-side Python bridge modules that ship with * the package (the `td/modules` tree). Used by the `install-bridge` CLI to copy * them somewhere TouchDesigner can import from. * - dev (tsx): `/td/modules` * - installed package (node): `/td/modules` */ export function bridgeModulesDir(): string { return firstExisting( [resolve(moduleDir, "../td/modules"), resolve(moduleDir, "../../td/modules")], resolve(moduleDir, "../td/modules"), ); } /** * Resolves the installed `@bottobot/td-mcp` package root (the source of operator * data), if present. Used as a fallback when local knowledge data is missing. */ export function bottobotPackageDir(): string | undefined { const candidates = [ resolve(moduleDir, "../../node_modules/@bottobot/td-mcp"), resolve(moduleDir, "../node_modules/@bottobot/td-mcp"), resolve(process.cwd(), "node_modules/@bottobot/td-mcp"), ]; for (const candidate of candidates) { if (existsSync(resolve(candidate, "wiki/data/processed"))) return candidate; } return undefined; }