@tekmidian/pai 0.14.1 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"pick-BPlB38eB.mjs","names":["dim","bold","getProject","dim","bold","cmdList","getProject","bold","dim","cmdHealth","bold","dim","dim","cmdMigrate","dim","slugify","cmdStats","bold","dim","dim","bold","dim","bold","tierColor","dim","bold","bold","dim","CONFIG_FILE","cmdInstall","dim","cmdStatus","bold","HOME","cmdStatus","bold","dim","HOME","REGISTRY_DB","CONFIG_FILE","BACKUPS_DIR","DOCKER_CONTAINER","PG_DATABASE","PG_USER","dim","bold","dim","bold","line","CONFIG_FILE","getTemplatesDir","getTemplatesDir","getTemplatesDir","getTemplatesDir","useSymlinks","CONFIG_FILE","normalize","line","CONFIG_FILE","findNotesDir","findNotesDir","CONFIG_FILE","dim","cmdStatus","bold","dim","bold","dim","bold","dim","bold","dim","bold","dim","bold","dim","bold","ipcCall","fmtTs","dim","trunc","cmdList","bold","cmdStats","fmtTs","dim","statusColor","bold","dim","run","makeClient","bold","dim","STATE_FILE","loadState","saveState","CONFIG_FILE","bold","dim","getPool","dim","shorten","bold","cmdQuery","cmdList","dim","bold","dim","probeResume","dim","dim","dim","bold","dim","dim"],"sources":["../src/session/promote.ts","../src/cli/commands/project/helpers.ts","../src/cli/commands/project/commands.ts","../src/cli/commands/project/session-config.ts","../src/cli/commands/project/health.ts","../src/cli/commands/project/projects-index.ts","../src/cli/commands/registry/migrate.ts","../src/cli/commands/registry/index.ts","../src/cli/commands/memory/embed.ts","../src/cli/commands/memory/index-cmd.ts","../src/cli/commands/memory/search.ts","../src/cli/commands/memory/stats.ts","../src/cli/commands/memory/index.ts","../src/config/claude-json.ts","../src/cli/commands/mcp.ts","../src/cli/commands/daemon.ts","../src/cli/commands/backup.ts","../src/cli/commands/restore.ts","../src/config/json-store.ts","../src/cli/commands/setup/utils.ts","../src/cli/commands/setup/steps/01-welcome.ts","../src/cli/commands/setup/steps/02-storage.ts","../src/cli/commands/setup/steps/03-embedding.ts","../src/cli/commands/setup/steps/04-claude-md.ts","../src/cli/commands/setup/steps/05-pai-skill.ts","../src/cli/commands/setup/steps/06-steering-rules.ts","../src/cli/commands/setup/steps/07-skill-stubs.ts","../src/cli/commands/setup/steps/08-hooks.ts","../src/cli/commands/setup/steps/09-ts-hooks.ts","../src/cli/commands/settings-manager.ts","../src/cli/commands/setup/steps/10-settings.ts","../src/cli/commands/setup/steps/11-daemon.ts","../src/cli/commands/setup/steps/12-mcp.ts","../src/cli/commands/setup/steps/13-directories.ts","../src/cli/commands/setup/steps/14-initial-index.ts","../src/cli/commands/setup/steps/15-verify.ts","../src/tasks/resolver.ts","../src/tasks/providers/todoist.ts","../src/cli/commands/setup/steps/16-task-bus.ts","../src/cli/commands/setup/index.ts","../src/obsidian/sync/symlinks.ts","../src/obsidian/sync/generate.ts","../src/obsidian/sync/walk.ts","../src/obsidian/sync/master.ts","../src/obsidian/status.ts","../src/cli/commands/obsidian.ts","../src/cli/commands/zettel/utils.ts","../src/cli/commands/zettel/explore.ts","../src/cli/commands/zettel/health.ts","../src/cli/commands/zettel/surprise.ts","../src/cli/commands/zettel/suggest.ts","../src/cli/commands/zettel/converse.ts","../src/cli/commands/zettel/themes.ts","../src/cli/commands/zettel/index.ts","../src/cli/commands/observation.ts","../src/cli/commands/skill.ts","../src/cli/commands/update.ts","../src/cli/commands/notify.ts","../src/tasks/dispatch.ts","../src/tasks/transport/aibroker.ts","../src/tasks/scheduler.ts","../src/tasks/poller.ts","../src/tasks/schedule-install.ts","../src/cli/commands/task.ts","../src/cli/commands/topic.ts","../src/memory/kg-backfill.ts","../src/cli/commands/kg.ts","../src/cli/commands/db.ts","../src/cli/commands/help.ts","../src/cli/commands/session/pause.ts","../src/cli/commands/session/end.ts","../src/cli/commands/session/goto.ts","../src/cli/commands/session/pause-all.ts","../src/cli/commands/session/clear-names.ts","../src/cli/commands/session/helpers.ts","../src/cli/commands/session/commands.ts","../src/cli/commands/find.ts","../src/cli/lib/launch.ts","../src/cli/commands/pick.ts"],"sourcesContent":["/**\n * pai project promote --from-session <path> --to <new-project-path> [--name \"Name\"]\n *\n * Promotes a session note into a new standalone project:\n * 1. Validates inputs\n * 2. Scaffolds the new project directory\n * 3. Copies the session note and creates a MEMORY.md\n * 4. Registers the new project in the PAI registry\n * 5. Optionally backlinks from the source project's TODO.md\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n mkdirSync,\n copyFileSync,\n readFileSync,\n appendFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join, basename, dirname, resolve } from \"node:path\";\nimport {\n ok,\n warn,\n err,\n dim,\n bold,\n slugify,\n encodeDir,\n resolvePath,\n scaffoldProjectDirs,\n now,\n} from \"../cli/utils.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface PromoteOptions {\n fromSession: string;\n to: string;\n name?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Derive a human-readable project name from a session note filename.\n * \"0042 - 2026-02-24 - Dark Mode Implementation.md\" → \"Dark Mode Implementation\"\n */\nfunction deriveNameFromFilename(filename: string): string {\n const base = basename(filename, \".md\");\n // Strip leading \"NNNN - YYYY-MM-DD - \" prefix if present\n const match = base.match(/^\\d+ - \\d{4}-\\d{2}-\\d{2} - (.+)$/);\n return match ? match[1] : base;\n}\n\n/**\n * Extract the first N H2 (##) section headings from markdown content.\n */\nfunction extractH2Headings(content: string, limit = 2): string[] {\n const headings: string[] = [];\n for (const m of content.matchAll(/^## .+$/gm)) {\n headings.push(m[0]);\n if (headings.length >= limit) break;\n }\n return headings;\n}\n\n/**\n * Find the source project root from the session note path.\n * Convention: session note lives at {project-root}/Notes/{filename}\n * So: dirname(dirname(sessionPath)) = project root.\n */\nfunction findSourceProjectRoot(sessionPath: string): string {\n const notesDir = dirname(resolve(sessionPath));\n return dirname(notesDir);\n}\n\n// ---------------------------------------------------------------------------\n// Main implementation\n// ---------------------------------------------------------------------------\n\nexport function cmdPromote(db: Database, opts: PromoteOptions): void {\n const sessionPath = resolvePath(opts.fromSession);\n const targetPath = resolvePath(opts.to);\n\n // ---- Validate inputs ----\n\n if (!existsSync(sessionPath)) {\n console.error(err(`Session note not found: ${sessionPath}`));\n process.exit(1);\n }\n if (!sessionPath.endsWith(\".md\")) {\n console.error(err(`--from-session must be a markdown (.md) file: ${sessionPath}`));\n process.exit(1);\n }\n if (existsSync(targetPath)) {\n console.error(err(`Target path already exists: ${targetPath}`));\n process.exit(1);\n }\n\n // ---- Derive project name and slug ----\n\n const displayName = opts.name ?? deriveNameFromFilename(sessionPath);\n const slug = slugify(displayName);\n\n if (!slug) {\n console.error(err(`Could not derive a valid slug from name: \"${displayName}\"`));\n process.exit(1);\n }\n\n // ---- Check registry for conflicts ----\n\n const encodedDir = encodeDir(targetPath);\n const existing = db\n .prepare(\"SELECT id FROM projects WHERE slug = ? OR root_path = ? OR encoded_dir = ?\")\n .get(slug, targetPath, encodedDir);\n if (existing) {\n console.error(\n err(`A project with slug \"${slug}\" or path \"${targetPath}\" is already registered.`)\n );\n process.exit(1);\n }\n\n // ---- Scaffold new project ----\n\n scaffoldProjectDirs(targetPath);\n\n // ---- Copy session note ----\n\n const today = new Date().toISOString().slice(0, 10);\n const sourceBasename = basename(sessionPath);\n const sourceProjectRoot = findSourceProjectRoot(sessionPath);\n const sourceProjectName = basename(sourceProjectRoot);\n\n const destNoteName = `0001 - ${today} - Promoted from ${sourceProjectName}.md`;\n const destNotePath = join(targetPath, \"Notes\", destNoteName);\n\n copyFileSync(sessionPath, destNotePath);\n\n // ---- Create MEMORY.md ----\n\n const sessionContent = readFileSync(sessionPath, \"utf-8\");\n const headings = extractH2Headings(sessionContent);\n\n const memoryContent = [\n `# Project Memory`,\n ``,\n `## Origin`,\n ``,\n `- **Promoted from:** ${sessionPath}`,\n `- **Source project:** ${sourceProjectRoot}`,\n `- **Date of promotion:** ${today}`,\n `- **Original session note:** ${sourceBasename}`,\n ``,\n `## Initial Context`,\n ``,\n headings.length > 0\n ? `Key topics from the source session:\\n\\n${headings.map((h) => `- ${h.replace(/^## /, \"\")}`).join(\"\\n\")}`\n : `(Extracted from session note — see ${destNoteName})`,\n ``,\n ].join(\"\\n\");\n\n writeFileSync(join(targetPath, \"Notes\", \"MEMORY.md\"), memoryContent, \"utf-8\");\n\n // ---- Register in PAI registry ----\n\n const ts = now();\n db.prepare(\n `INSERT INTO projects\n (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)\n VALUES (?, ?, ?, ?, 'local', 'active', ?, ?)`\n ).run(slug, displayName, targetPath, encodedDir, ts, ts);\n\n // ---- Backlink in source TODO.md ----\n\n const sourceTodoPath = join(sourceProjectRoot, \"Notes\", \"TODO.md\");\n if (existsSync(sourceTodoPath)) {\n const backlink = `\\n- Promoted to project: [${slug}](${targetPath})\\n`;\n appendFileSync(sourceTodoPath, backlink, \"utf-8\");\n console.log(dim(` Backlink added to: ${sourceTodoPath}`));\n }\n\n // ---- Success output ----\n\n console.log();\n console.log(ok(`Project promoted: ${bold(slug)}`));\n console.log(dim(` Display name: ${displayName}`));\n console.log(dim(` Path: ${targetPath}`));\n console.log(dim(` Slug: ${slug}`));\n console.log(dim(` Session note: ${destNoteName}`));\n console.log(dim(` Memory: Notes/MEMORY.md created`));\n console.log();\n console.log(dim(` Next: cd ${targetPath} && pai session list ${slug}`));\n}\n","/**\n * Shared database helper functions for project sub-commands.\n * All functions accept a Database instance and are pure query wrappers.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { ProjectRow } from \"./types.js\";\nimport { err } from \"../../utils.js\";\n\nexport function getProject(db: Database, slug: string): ProjectRow | undefined {\n const direct = db\n .prepare(\"SELECT * FROM projects WHERE slug = ?\")\n .get(slug) as ProjectRow | undefined;\n if (direct) return direct;\n\n const alias = db\n .prepare(\n `SELECT p.* FROM projects p\n JOIN aliases a ON a.project_id = p.id\n WHERE a.alias = ?`\n )\n .get(slug) as ProjectRow | undefined;\n return alias;\n}\n\nexport function requireProject(db: Database, slug: string): ProjectRow {\n const project = getProject(db, slug);\n if (!project) {\n console.error(err(`Project not found: ${slug}`));\n process.exit(1);\n }\n return project;\n}\n\n/**\n * Resolve an identifier that may be a list index number or a slug.\n */\nexport function resolveIdentifier(db: Database, identifier: string): ProjectRow | undefined {\n const num = parseInt(identifier, 10);\n if (!isNaN(num) && num > 0 && String(num) === identifier) {\n const rows = db.prepare(\n \"SELECT * FROM projects ORDER BY status ASC, updated_at DESC\"\n ).all() as ProjectRow[];\n if (num <= rows.length) return rows[num - 1];\n }\n return getProject(db, identifier);\n}\n\nexport function getProjectTags(db: Database, projectId: number): string[] {\n const rows = db\n .prepare(\n `SELECT t.name FROM tags t\n JOIN project_tags pt ON pt.tag_id = t.id\n WHERE pt.project_id = ?\n ORDER BY t.name`\n )\n .all(projectId) as { name: string }[];\n return rows.map((r) => r.name);\n}\n\nexport function getProjectAliases(db: Database, projectId: number): string[] {\n const rows = db\n .prepare(\"SELECT alias FROM aliases WHERE project_id = ? ORDER BY alias\")\n .all(projectId) as { alias: string }[];\n return rows.map((r) => r.alias);\n}\n\nexport function getSessionCount(db: Database, projectId: number): number {\n const row = db\n .prepare(\"SELECT COUNT(*) AS cnt FROM sessions WHERE project_id = ?\")\n .get(projectId) as { cnt: number };\n return row.cnt;\n}\n\nexport function getLastSessionDate(db: Database, projectId: number): number | null {\n const row = db\n .prepare(\n `SELECT created_at FROM sessions WHERE project_id = ?\n ORDER BY created_at DESC LIMIT 1`\n )\n .get(projectId) as { created_at: number } | undefined;\n return row ? row.created_at : null;\n}\n\nexport function upsertTag(db: Database, tagName: string): number {\n db.prepare(\"INSERT OR IGNORE INTO tags (name) VALUES (?)\").run(tagName);\n const row = db.prepare(\"SELECT id FROM tags WHERE name = ?\").get(tagName) as { id: number };\n return row.id;\n}\n","/**\n * Core project CRUD commands: add, list, info, archive, unarchive, move, tag,\n * alias, edit, detect, consolidate, go, rebind — plus private helpers levenshtein,\n * containsIgnoreCase, findProjectNotesDirs, and findMovedPath.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n readdirSync,\n statSync,\n mkdirSync,\n renameSync,\n} from \"node:fs\";\nimport { join, basename, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport {\n ok,\n warn,\n err,\n dim,\n bold,\n header,\n slugFromPath,\n encodeDir,\n resolvePath,\n scaffoldProjectDirs,\n renderTable,\n shortenPath,\n fmtDate,\n now,\n} from \"../../utils.js\";\nimport {\n detectProject,\n formatDetection,\n formatDetectionJson,\n} from \"../detect.js\";\nimport { ensurePaiMarker } from \"../../../registry/pai-marker.js\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\nimport {\n requireProject,\n resolveIdentifier,\n getProject,\n getProjectTags,\n getProjectAliases,\n getSessionCount,\n getLastSessionDate,\n upsertTag,\n} from \"./helpers.js\";\nimport { loadScanConfig, resolveHome } from \"../registry/scan.js\";\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp: number[][] = Array.from({ length: m + 1 }, (_, i) =>\n Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))\n );\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n dp[i][j] =\n a[i - 1] === b[j - 1]\n ? dp[i - 1][j - 1]\n : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);\n }\n }\n return dp[m][n];\n}\n\nfunction containsIgnoreCase(haystack: string, needle: string): boolean {\n return haystack.toLowerCase().includes(needle.toLowerCase());\n}\n\nfunction findProjectNotesDirs(project: ProjectRow): {\n encodedDir: string;\n fullPath: string;\n notesPath: string;\n noteCount: number;\n}[] {\n const claudeProjects = join(homedir(), \".claude\", \"projects\");\n if (!existsSync(claudeProjects)) return [];\n\n const results: {\n encodedDir: string;\n fullPath: string;\n notesPath: string;\n noteCount: number;\n }[] = [];\n const rootEncoded = encodeDir(project.root_path);\n\n try {\n for (const entry of readdirSync(claudeProjects)) {\n const full = join(claudeProjects, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n\n if (entry !== rootEncoded && !entry.startsWith(rootEncoded)) continue;\n\n const notesPath = join(full, \"Notes\");\n if (!existsSync(notesPath)) continue;\n\n let noteCount = 0;\n try {\n noteCount = readdirSync(notesPath).filter(\n (f) => f.endsWith(\".md\") || f.endsWith(\".txt\")\n ).length;\n } catch {\n // count stays 0\n }\n\n results.push({ encodedDir: entry, fullPath: full, notesPath, noteCount });\n }\n } catch {\n // Unreadable — ignore\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Moved-project detection\n// ---------------------------------------------------------------------------\n\n// Directories to skip during filesystem walk\nconst SKIP_DIRS = new Set([\n \".git\", \"node_modules\", \".next\", \".nuxt\", \"dist\", \"build\", \"coverage\",\n \".cache\", \"__pycache__\", \"vendor\", \".svn\", \".hg\", \"venv\", \".venv\",\n \"target\", // Rust/Java build dir\n \"Notes\", // PAI session notes — not project roots\n]);\n\n/**\n * Walk `dir` up to `maxDepth` levels deep, collecting all subdirectory paths\n * whose basename matches `targetBasename` (case-insensitive on macOS/case-sensitive\n * on Linux, whichever is appropriate — we use exact case matching to keep it\n * correct and fast).\n *\n * Skips hidden directories and known build/tool dirs.\n * Stops walking a branch if the deadline is exceeded (returns partial results).\n */\nfunction walkForBasename(\n dir: string,\n targetBasename: string,\n maxDepth: number,\n deadline: number,\n results: string[]\n): void {\n if (Date.now() > deadline) return;\n if (maxDepth <= 0) return;\n\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (Date.now() > deadline) return;\n if (entry.startsWith(\".\")) continue;\n if (SKIP_DIRS.has(entry)) continue;\n\n const full = join(dir, entry);\n let isDir = false;\n try {\n isDir = statSync(full).isDirectory();\n } catch {\n continue;\n }\n if (!isDir) continue;\n\n // Case-insensitive comparison for the target basename (handles macOS APFS)\n if (entry.toLowerCase() === targetBasename.toLowerCase()) {\n results.push(full);\n // Don't recurse into the match itself — we want the directory, not children\n continue;\n }\n\n walkForBasename(full, targetBasename, maxDepth - 1, deadline, results);\n }\n}\n\n/**\n * Try to find where a project has moved.\n *\n * Searches scan_dirs from config (plus a set of common fallback dirs) for a\n * directory whose basename matches the basename of the registered root_path.\n *\n * Returns:\n * { found: string } — exactly one match found\n * { ambiguous: string[] } — multiple matches found\n * { found: undefined } — no match found (or timed out)\n */\nexport function findMovedPath(registeredPath: string): { found?: string; ambiguous?: string[] } {\n const targetBasename = basename(registeredPath);\n const config = loadScanConfig();\n const deadline = Date.now() + 5_000; // 5-second max\n\n // Build the list of top-level directories to search\n const home = homedir();\n const configDirs = (config.scan_dirs ?? []).map((d) => resolveHome(d));\n\n // Walk UP the registered path to find the deepest existing ancestor.\n // This is the best search root when a project has moved within the same tree.\n // e.g. ~/Cloud/Ablage/2025/Project → ~/Cloud/Ablage/2026/Project\n // Deepest existing ancestor: ~/Cloud/Ablage (if 2025 is gone but Ablage exists)\n function deepestExistingAncestor(p: string): string | null {\n let current = p;\n while (true) {\n const parent = join(current, \"..\");\n if (parent === current) return null; // filesystem root\n if (parent === home) return null; // don't use home itself as search root\n if (existsSync(parent)) return parent;\n current = parent;\n }\n }\n\n const ancestorRoot = deepestExistingAncestor(registeredPath);\n\n const fallbackDirs = [\n join(home, \"dev\"),\n join(home, \"Desktop\"),\n join(home, \"Projects\"),\n join(home, \"Documents\"),\n // Common cloud-storage root folders (Synology Drive, iCloud, etc.)\n join(home, \"Cloud\"),\n join(home, \"Daten\", \"Cloud\"),\n join(home, \"Daten\"),\n join(home, \"Library\", \"Mobile Documents\", \"com~apple~CloudDocs\"), // iCloud Drive\n // The deepest ancestor of the registered path that still exists on disk\n ...(ancestorRoot ? [ancestorRoot] : []),\n ];\n\n // Deduplicate and filter to existing directories\n const searchRoots = [...new Set([...configDirs, ...fallbackDirs])]\n .map((d) => {\n try { return resolve(d); } catch { return null; }\n })\n .filter((d): d is string => d !== null && existsSync(d));\n\n const results: string[] = [];\n for (const root of searchRoots) {\n if (Date.now() > deadline) break;\n walkForBasename(root, targetBasename, 6, deadline, results);\n }\n\n // Deduplicate results (different roots might overlap)\n const unique = [...new Set(results)];\n\n if (unique.length === 0) return {};\n if (unique.length === 1) return { found: unique[0] };\n return { ambiguous: unique };\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nexport function cmdAdd(\n db: Database,\n rawPath: string,\n opts: { slug?: string; type?: string; displayName?: string }\n): void {\n const rootPath = resolvePath(rawPath);\n const slug = opts.slug ?? slugFromPath(rootPath);\n const encodedDir = encodeDir(rootPath);\n const displayName = opts.displayName ?? slug;\n const type = opts.type ?? \"local\";\n\n const validTypes = [\"local\", \"central\", \"obsidian-linked\", \"external\"];\n if (!validTypes.includes(type)) {\n console.error(err(`Invalid type \"${type}\". Valid: ${validTypes.join(\", \")}`));\n process.exit(1);\n }\n\n const existing = db\n .prepare(\"SELECT id FROM projects WHERE slug = ? OR root_path = ?\")\n .get(slug, rootPath);\n if (existing) {\n console.error(\n err(`Project already registered (slug: ${slug} or path: ${rootPath})`)\n );\n process.exit(1);\n }\n\n const dirName = basename(rootPath).toLowerCase();\n const similar = db\n .prepare(\n `SELECT slug, root_path FROM projects WHERE status = 'active' AND slug != ?`\n )\n .all(slug) as { slug: string; root_path: string }[];\n const matches = similar.filter(\n (s) =>\n basename(s.root_path).toLowerCase() === dirName ||\n s.slug.replace(/-\\d+$/, \"\") === slug.replace(/-\\d+$/, \"\")\n );\n if (matches.length > 0) {\n console.log(warn(`Similar project(s) already registered:`));\n for (const m of matches) {\n console.log(dim(` ${bold(m.slug)} ${shortenPath(m.root_path, 50)}`));\n }\n console.log(\n dim(\n ` Consider: pai project alias ${matches[0].slug} <name> (to link them)`\n )\n );\n console.log(\n dim(` Or: pai project archive ${slug} (if this is a duplicate)`)\n );\n console.log();\n }\n\n const ts = now();\n db.prepare(\n `INSERT INTO projects\n (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, 'active', ?, ?)`\n ).run(slug, displayName, rootPath, encodedDir, type, ts, ts);\n\n scaffoldProjectDirs(rootPath);\n\n try {\n ensurePaiMarker(rootPath, slug, displayName);\n } catch {\n // Non-fatal — warn but don't fail the add command.\n }\n\n console.log(ok(`Project added: ${bold(slug)}`));\n console.log(dim(` Path: ${rootPath}`));\n console.log(dim(` Encoded dir: ${encodedDir}`));\n console.log(dim(` Type: ${type}`));\n}\n\nexport function cmdList(\n db: Database,\n opts: { status?: string; tag?: string; type?: string; all?: boolean }\n): void {\n let query = `\n SELECT p.*,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n FROM projects p\n `;\n const params: unknown[] = [];\n const where: string[] = [];\n\n // Default: active only, unless --all or explicit --status given\n if (opts.status) {\n where.push(\"p.status = ?\");\n params.push(opts.status);\n } else if (!opts.all) {\n where.push(\"p.status = 'active'\");\n }\n if (opts.type) {\n where.push(\"p.type = ?\");\n params.push(opts.type);\n }\n if (opts.tag) {\n where.push(`p.id IN (\n SELECT pt.project_id FROM project_tags pt\n JOIN tags t ON t.id = pt.tag_id WHERE t.name = ?\n )`);\n params.push(opts.tag);\n }\n\n if (where.length) query += \" WHERE \" + where.join(\" AND \");\n query += \" ORDER BY p.status ASC, p.updated_at DESC\";\n\n const rows = db.prepare(query).all(...params) as (ProjectRow & {\n session_count: number;\n last_active: number | null;\n })[];\n\n if (!rows.length) {\n console.log(warn(\"No projects found.\"));\n return;\n }\n\n const tableRows = rows.map((r, i) => [\n dim(String(i + 1)),\n bold(r.display_name ?? r.slug),\n dim(r.slug),\n dim(shortenPath(r.root_path, 44)),\n r.status === \"active\" ? chalk.green(r.status) : chalk.yellow(r.status),\n dim(r.type),\n String(r.session_count),\n fmtDate(r.last_active),\n ]);\n\n console.log(\n renderTable(\n [\"#\", \"Name\", \"Slug\", \"Path\", \"Status\", \"Type\", \"Sessions\", \"Last Active\"],\n tableRows\n )\n );\n console.log();\n\n // When showing active-only, inform the user about hidden archived projects\n const totalRow = db.prepare(\"SELECT COUNT(*) AS cnt FROM projects\").get() as { cnt: number };\n const hiddenCount = totalRow.cnt - rows.length;\n if (!opts.all && !opts.status && hiddenCount > 0) {\n console.log(dim(` ${rows.length} active project(s) (${hiddenCount} archived — use --all to show)`));\n } else {\n console.log(dim(` ${rows.length} project(s)`));\n }\n}\n\nexport function cmdInfo(db: Database, identifier: string): void {\n const project =\n resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\n const tags = getProjectTags(db, project.id);\n const aliases = getProjectAliases(db, project.id);\n const sessionCount = getSessionCount(db, project.id);\n const lastSession = getLastSessionDate(db, project.id);\n\n const recentSessions = db\n .prepare(\n `SELECT * FROM sessions WHERE project_id = ?\n ORDER BY created_at DESC LIMIT 5`\n )\n .all(project.id) as SessionRow[];\n\n console.log();\n console.log(header(` ${project.display_name}`));\n console.log();\n console.log(` ${bold(\"Slug:\")} ${project.slug}`);\n console.log(` ${bold(\"Path:\")} ${project.root_path}`);\n console.log(` ${bold(\"Encoded dir:\")} ${project.encoded_dir}`);\n console.log(` ${bold(\"Type:\")} ${project.type}`);\n console.log(\n ` ${bold(\"Status:\")} ${\n project.status === \"active\"\n ? chalk.green(project.status)\n : chalk.yellow(project.status)\n }`\n );\n console.log(\n ` ${bold(\"Tags:\")} ${\n tags.length ? tags.map((t) => chalk.cyan(t)).join(\", \") : dim(\"none\")\n }`\n );\n console.log(\n ` ${bold(\"Aliases:\")} ${aliases.length ? aliases.join(\", \") : dim(\"none\")}`\n );\n console.log(` ${bold(\"Sessions:\")} ${sessionCount}`);\n console.log(` ${bold(\"Last active:\")} ${fmtDate(lastSession)}`);\n console.log(` ${bold(\"Created:\")} ${fmtDate(project.created_at)}`);\n if (project.archived_at) {\n console.log(` ${bold(\"Archived:\")} ${fmtDate(project.archived_at)}`);\n }\n\n if (recentSessions.length) {\n console.log();\n console.log(` ${bold(\"Recent sessions:\")}`);\n const sessionRows = recentSessions.map((s) => [\n dim(`#${s.number}`),\n s.date,\n s.title.length > 50 ? s.title.slice(0, 47) + \"...\" : s.title,\n s.status === \"completed\"\n ? chalk.green(s.status)\n : chalk.yellow(s.status),\n ]);\n console.log(\n renderTable([\"#\", \"Date\", \"Title\", \"Status\"], sessionRows)\n .split(\"\\n\")\n .map((l) => \" \" + l)\n .join(\"\\n\")\n );\n }\n console.log();\n}\n\nexport function cmdArchive(db: Database, slug: string): void {\n const project = requireProject(db, slug);\n if (project.status === \"archived\") {\n console.log(warn(`Project ${slug} is already archived.`));\n return;\n }\n const ts = now();\n db.prepare(\n \"UPDATE projects SET status = 'archived', archived_at = ?, updated_at = ? WHERE id = ?\"\n ).run(ts, ts, project.id);\n console.log(ok(`Archived: ${bold(slug)}`));\n}\n\nexport function cmdUnarchive(db: Database, slug: string): void {\n const project = requireProject(db, slug);\n if (project.status !== \"archived\") {\n console.log(\n warn(`Project ${slug} is not archived (status: ${project.status}).`)\n );\n return;\n }\n const ts = now();\n db.prepare(\n \"UPDATE projects SET status = 'active', archived_at = NULL, updated_at = ? WHERE id = ?\"\n ).run(ts, project.id);\n console.log(ok(`Unarchived: ${bold(slug)}`));\n}\n\nexport function cmdMove(db: Database, slug: string, newPath: string): void {\n const project = requireProject(db, slug);\n const resolvedNew = resolvePath(newPath);\n const newEncoded = encodeDir(resolvedNew);\n const ts = now();\n\n db.prepare(\n \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n ).run(resolvedNew, newEncoded, ts, project.id);\n\n console.log(ok(`Moved: ${bold(slug)}`));\n console.log(dim(` Old path: ${project.root_path}`));\n console.log(dim(` New path: ${resolvedNew}`));\n}\n\nexport function cmdTag(\n db: Database,\n slug: string,\n tags: string[]\n): void {\n const project = requireProject(db, slug);\n const added: string[] = [];\n const skipped: string[] = [];\n\n for (const tagName of tags) {\n const tagId = upsertTag(db, tagName);\n const exists = db\n .prepare(\n \"SELECT 1 FROM project_tags WHERE project_id = ? AND tag_id = ?\"\n )\n .get(project.id, tagId);\n if (exists) {\n skipped.push(tagName);\n } else {\n db.prepare(\n \"INSERT INTO project_tags (project_id, tag_id) VALUES (?, ?)\"\n ).run(project.id, tagId);\n added.push(tagName);\n }\n }\n\n if (added.length) {\n console.log(\n ok(\n `Tagged ${bold(slug)}: ${added.map((t) => chalk.cyan(t)).join(\", \")}`\n )\n );\n }\n if (skipped.length) {\n console.log(dim(` Already present: ${skipped.join(\", \")}`));\n }\n}\n\nexport function cmdAlias(\n db: Database,\n slug: string,\n alias: string\n): void {\n requireProject(db, slug);\n\n const conflict = db\n .prepare(\"SELECT id FROM projects WHERE slug = ?\")\n .get(alias);\n if (conflict) {\n console.error(\n err(`\"${alias}\" is already a project slug — cannot use as alias.`)\n );\n process.exit(1);\n }\n\n const project = getProject(db, slug)!;\n try {\n db.prepare(\n \"INSERT INTO aliases (alias, project_id) VALUES (?, ?)\"\n ).run(alias, project.id);\n console.log(ok(`Alias added: ${bold(alias)} → ${slug}`));\n } catch {\n console.error(err(`Alias \"${alias}\" is already registered.`));\n process.exit(1);\n }\n}\n\nexport function cmdEdit(\n db: Database,\n slug: string,\n opts: { displayName?: string; type?: string }\n): void {\n const project = requireProject(db, slug);\n\n if (!opts.displayName && !opts.type) {\n console.log(warn(\"Nothing to update. Use --display-name or --type.\"));\n return;\n }\n\n const validTypes = [\"local\", \"central\", \"obsidian-linked\", \"external\"];\n if (opts.type && !validTypes.includes(opts.type)) {\n console.error(\n err(`Invalid type \"${opts.type}\". Valid: ${validTypes.join(\", \")}`)\n );\n process.exit(1);\n }\n\n const ts = now();\n if (opts.displayName) {\n db.prepare(\n \"UPDATE projects SET display_name = ?, updated_at = ? WHERE id = ?\"\n ).run(opts.displayName, ts, project.id);\n console.log(ok(`Display name updated: ${bold(opts.displayName)}`));\n }\n if (opts.type) {\n db.prepare(\n \"UPDATE projects SET type = ?, updated_at = ? WHERE id = ?\"\n ).run(opts.type, ts, project.id);\n console.log(ok(`Type updated: ${bold(opts.type)}`));\n }\n}\n\nexport function cmdDetect(\n db: Database,\n pathArg: string | undefined,\n opts: { json?: boolean }\n): void {\n const cwd = pathArg ? resolvePath(pathArg) : process.cwd();\n const detection = detectProject(db, cwd);\n\n if (!detection) {\n if (opts.json) {\n console.log(JSON.stringify({ error: \"no_match\", cwd }, null, 2));\n } else {\n console.log(warn(`No registered project found for: ${cwd}`));\n console.log(dim(\" Run 'pai project add .' to register this directory.\"));\n }\n process.exit(0);\n return;\n }\n\n if (opts.json) {\n console.log(formatDetectionJson(detection));\n return;\n }\n\n console.log();\n console.log(header(\" Project Detection Result\"));\n console.log();\n console.log(\n formatDetection(detection)\n .split(\"\\n\")\n .map((l) => \" \" + l)\n .join(\"\\n\")\n );\n console.log();\n}\n\nexport function cmdConsolidate(\n db: Database,\n identifier: string,\n opts: { yes?: boolean; dryRun?: boolean }\n): void {\n const project =\n resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\n\n console.log();\n console.log(header(` Consolidate: ${project.slug}`));\n console.log(` Target: ${project.root_path}`);\n console.log();\n\n const dirs = findProjectNotesDirs(project);\n\n if (dirs.length === 0) {\n console.log(warn(\" No scattered notes directories found for this project.\"));\n return;\n }\n\n const canonicalNotes = join(project.root_path, \"Notes\");\n const toMerge = dirs.filter((d) => d.notesPath !== canonicalNotes);\n\n if (toMerge.length === 0) {\n console.log(ok(\" All notes are already in the canonical location.\"));\n console.log(dim(` ${canonicalNotes}`));\n return;\n }\n\n console.log(\n ` Found ${toMerge.length} scattered Notes directory(ies) to consolidate:`\n );\n console.log();\n\n for (const d of toMerge) {\n console.log(` ${bold(d.encodedDir)}`);\n console.log(dim(` Notes: ${d.notesPath} (${d.noteCount} file(s))`));\n }\n\n console.log();\n console.log(` Destination: ${canonicalNotes}`);\n console.log();\n\n if (opts.dryRun) {\n console.log(warn(\" Dry run — no changes made. Remove --dry-run to proceed.\"));\n return;\n }\n\n if (!opts.yes) {\n console.log(\n warn(\n \" Run with --yes to perform consolidation, or --dry-run to preview changes.\"\n )\n );\n return;\n }\n\n mkdirSync(canonicalNotes, { recursive: true });\n\n let movedCount = 0;\n for (const d of toMerge) {\n try {\n const files = readdirSync(d.notesPath);\n for (const f of files) {\n if (!f.endsWith(\".md\") && !f.endsWith(\".txt\")) continue;\n const src = join(d.notesPath, f);\n const dest = join(canonicalNotes, f);\n if (!existsSync(dest)) {\n renameSync(src, dest);\n console.log(ok(` Moved: ${f}`));\n movedCount++;\n } else {\n console.log(warn(` Skipped (exists): ${f}`));\n }\n }\n } catch (e) {\n console.error(err(` Error reading ${d.notesPath}: ${e}`));\n }\n }\n\n console.log();\n console.log(ok(` Consolidated ${movedCount} file(s) into ${canonicalNotes}`));\n}\n\n/**\n * Attempt moved-path recovery for a project whose registered root_path is\n * missing. Updates the DB if exactly one candidate is found.\n *\n * Returns the new path on success, or undefined if ambiguous/not found.\n * Prints all messages to stderr (safe for shell-wrapper stdout capture).\n */\nfunction tryRecoverMovedProject(db: Database, project: ProjectRow): string | undefined {\n process.stderr.write(\n warn(`Path not found: ${project.root_path}\\n`) +\n dim(\" Searching for moved location...\\n\")\n );\n\n const result = findMovedPath(project.root_path);\n\n if (result.found) {\n const newPath = result.found;\n const newEncoded = encodeDir(newPath);\n const ts = now();\n db.prepare(\n \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n ).run(newPath, newEncoded, ts, project.id);\n process.stderr.write(\n ok(`Project moved: ${shortenPath(project.root_path, 50)}\\n`) +\n dim(` → ${newPath}\\n`) +\n ok(\"Registry updated.\\n\")\n );\n return newPath;\n }\n\n if (result.ambiguous) {\n process.stderr.write(\n warn(`Multiple directories named \"${basename(project.root_path)}\" found:\\n`)\n );\n for (const candidate of result.ambiguous) {\n process.stderr.write(dim(` ${candidate}\\n`));\n }\n process.stderr.write(\n dim(`\\n Disambiguate with: pai projects rebind ${project.slug} <path>\\n`)\n );\n return undefined;\n }\n\n process.stderr.write(\n err(\n `Project \"${project.slug}\" root_path \"${project.root_path}\" does not exist on disk\\n` +\n ` and no folder named \"${basename(project.root_path)}\" was found in scan dirs.\\n`\n ) +\n dim(` Fix with: pai projects rebind ${project.slug} <new-path>\\n`)\n );\n return undefined;\n}\n\nexport function cmdGo(db: Database, query: string): void {\n const all = db\n .prepare(\n \"SELECT * FROM projects WHERE status = 'active' ORDER BY updated_at DESC\"\n )\n .all() as ProjectRow[];\n\n if (!all.length) {\n console.error(\n err(\"No active projects registered. Run: pai project add <path>\")\n );\n process.exit(1);\n }\n\n const q = query.trim().toLowerCase();\n\n // 1. Exact slug or alias match\n const exact = getProject(db, query);\n if (exact) {\n if (!existsSync(exact.root_path)) {\n const recovered = tryRecoverMovedProject(db, exact);\n if (!recovered) { process.exitCode = 1; return; }\n process.stdout.write(recovered + \"\\n\");\n return;\n }\n process.stdout.write(exact.root_path + \"\\n\");\n return;\n }\n\n // 2. Substring match against slug, display_name, or root_path basename\n const partial = all.filter(\n (p) =>\n containsIgnoreCase(p.slug, q) ||\n containsIgnoreCase(p.display_name, q) ||\n containsIgnoreCase(basename(p.root_path), q)\n );\n\n if (partial.length === 1) {\n const p = partial[0];\n if (!existsSync(p.root_path)) {\n const recovered = tryRecoverMovedProject(db, p);\n if (!recovered) { process.exitCode = 1; return; }\n process.stdout.write(recovered + \"\\n\");\n return;\n }\n process.stdout.write(p.root_path + \"\\n\");\n return;\n }\n\n if (partial.length > 1) {\n console.error(\n err(`Ambiguous: \"${query}\" matches ${partial.length} projects:\\n`)\n );\n partial.forEach((p, i) => {\n console.error(\n ` ${dim(String(i + 1).padStart(2))} ${bold(p.slug.padEnd(30))} ${dim(\n shortenPath(p.root_path, 50)\n )}`\n );\n });\n console.error();\n console.error(dim(\" Use a more specific name or the exact slug.\"));\n process.exit(1);\n }\n\n // 3. No match — Levenshtein suggestions\n const scored = all\n .map((p) => {\n const distSlug = levenshtein(q, p.slug.toLowerCase());\n const distName = levenshtein(q, p.display_name.toLowerCase());\n return { project: p, dist: Math.min(distSlug, distName) };\n })\n .sort((a, b) => a.dist - b.dist);\n\n const threshold = 4;\n const suggestions =\n scored.filter((s) => s.dist <= threshold).length > 0\n ? scored.filter((s) => s.dist <= threshold).slice(0, 3)\n : scored.slice(0, 3);\n\n console.error(err(`Project not found: \"${query}\"\\n`));\n if (suggestions.length) {\n console.error(warn(\" Did you mean?\"));\n for (const s of suggestions) {\n console.error(\n ` ${bold(s.project.slug.padEnd(30))} ${dim(\n shortenPath(s.project.root_path, 50)\n )}`\n );\n }\n console.error();\n console.error(dim(\" Run: pai project list (to see all projects)\"));\n }\n process.exit(1);\n}\n\nexport function cmdRebind(\n db: Database,\n slug: string,\n newPath: string\n): void {\n const project = requireProject(db, slug);\n const resolved = resolve(newPath.startsWith(\"~/\") ? join(homedir(), newPath.slice(2)) : newPath);\n\n if (!existsSync(resolved)) {\n console.error(err(`Path does not exist: ${resolved}`));\n process.exit(1);\n }\n\n let isDir = false;\n try {\n isDir = statSync(resolved).isDirectory();\n } catch {\n // stat failed\n }\n if (!isDir) {\n console.error(err(`Path is not a directory: ${resolved}`));\n process.exit(1);\n }\n\n const newEncoded = encodeDir(resolved);\n\n // Check if another project already owns this path\n const conflict = db\n .prepare(\"SELECT slug FROM projects WHERE encoded_dir = ? AND id != ?\")\n .get(newEncoded, project.id) as { slug: string } | undefined;\n if (conflict) {\n console.error(\n err(`Path is already registered to project: ${bold(conflict.slug)}\\n`) +\n dim(` ${resolved}\\n`) +\n dim(` Archive or move that project first, or choose a different path.`)\n );\n process.exit(1);\n }\n\n const ts = now();\n db.prepare(\n \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n ).run(resolved, newEncoded, ts, project.id);\n\n console.log(ok(`Rebound: ${bold(slug)}`));\n console.log(dim(` Old path: ${project.root_path}`));\n console.log(dim(` New path: ${resolved}`));\n console.log(dim(` Encoded: ${newEncoded}`));\n}\n","/**\n * Session launch configuration for projects.\n * Implements per-project and global config CRUD (pai project config),\n * and the name/unname/names commands for curated project shortlists.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header, shortenPath, fmtDate, now, renderTable } from \"../../utils.js\";\nimport type { ProjectRow, SessionConfig, ConfigOption } from \"./types.js\";\nimport { resolveIdentifier, requireProject, getProjectAliases } from \"./helpers.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nexport const CONFIG_OPTIONS: ConfigOption[] = [\n { key: 'permission', type: 'string', description: 'Permission preset: full | trusted | default', examples: ['full', 'trusted', 'default'] },\n { key: 'flags', type: 'string', description: 'Raw Claude CLI flags', examples: ['--dangerously-skip-permissions', '--allowedTools Edit,Read'] },\n { key: 'env', type: 'object', description: 'Environment variables as key=value pairs', examples: ['IS_SANDBOX=1', 'CLAUDE_MODEL=opus'] },\n { key: 'autoStart', type: 'boolean', description: 'Auto-start session (skip interactive prompt)', examples: ['true', 'false'] },\n { key: 'prompt', type: 'string', description: 'Initial prompt sent to Claude on launch', examples: ['go', 'continue', 'run tests'] },\n { key: 'model', type: 'string', description: 'Model override for the session', examples: ['opus', 'sonnet', 'haiku'] },\n];\n\nexport const PERMISSION_PRESETS: Record<string, Partial<SessionConfig>> = {\n full: { permission: 'full', flags: '--dangerously-skip-permissions', env: { IS_SANDBOX: '1' }, autoStart: true, prompt: 'go' },\n trusted: { permission: 'trusted', flags: '', env: {}, autoStart: true, prompt: 'go' },\n default: { permission: 'default', flags: '', env: {}, autoStart: false },\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nexport function getSessionConfig(project: ProjectRow): SessionConfig {\n return project.session_config ? JSON.parse(project.session_config) : {};\n}\n\nexport function getGlobalDefaults(): SessionConfig {\n try {\n const configPath = join(homedir(), '.config', 'pai', 'config.json');\n if (existsSync(configPath)) {\n const config = JSON.parse(readFileSync(configPath, 'utf-8'));\n return config.sessionDefaults ?? {};\n }\n } catch { /* ignore */ }\n return {};\n}\n\nexport function saveGlobalDefaults(defaults: SessionConfig): void {\n const configPath = join(homedir(), '.config', 'pai', 'config.json');\n let config: Record<string, unknown> = {};\n try {\n if (existsSync(configPath)) {\n config = JSON.parse(readFileSync(configPath, 'utf-8'));\n }\n } catch { /* ignore */ }\n config.sessionDefaults = defaults;\n mkdirSync(join(homedir(), '.config', 'pai'), { recursive: true });\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n}\n\nfunction applyPreset(config: SessionConfig, preset: string): SessionConfig {\n const presetConfig = PERMISSION_PRESETS[preset];\n if (!presetConfig) {\n return { ...config, permission: 'custom', flags: preset };\n }\n return { ...config, ...presetConfig };\n}\n\nfunction parseConfigValue(key: string, value: string): unknown {\n const option = CONFIG_OPTIONS.find(o => o.key === key);\n if (!option) return value;\n\n switch (option.type) {\n case 'boolean':\n return value === 'true' || value === '1' || value === 'yes';\n case 'object':\n if (key === 'env') {\n const [k, ...vParts] = value.split('=');\n return { [k]: vParts.join('=') || '1' };\n }\n return value;\n default:\n return value;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commands\n// ---------------------------------------------------------------------------\n\nexport function cmdName(\n db: Database,\n identifier: string,\n shortname: string,\n opts: { permission?: string }\n): void {\n const project = resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\n\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(shortname)) {\n console.error(err(`Invalid name \"${shortname}\". Use letters, digits, hyphens, underscores. Must start with a letter.`));\n process.exit(1);\n }\n\n const conflictProject = db\n .prepare(\"SELECT id FROM projects WHERE slug = ? AND id != ?\")\n .get(shortname, project.id) as { id: number } | undefined;\n if (conflictProject) {\n console.error(err(`\"${shortname}\" is already a project slug.`));\n process.exit(1);\n }\n\n const conflictAlias = db\n .prepare(\"SELECT project_id FROM aliases WHERE alias = ?\")\n .get(shortname) as { project_id: number } | undefined;\n if (conflictAlias && conflictAlias.project_id !== project.id) {\n console.error(err(`\"${shortname}\" is already used by another project.`));\n process.exit(1);\n }\n\n if (!conflictAlias) {\n db.prepare(\"INSERT INTO aliases (alias, project_id) VALUES (?, ?)\").run(shortname, project.id);\n }\n\n if (opts.permission) {\n const existing = getSessionConfig(project);\n const config = applyPreset(existing, opts.permission);\n db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n .run(JSON.stringify(config), now(), project.id);\n }\n\n console.log(ok(`Named: ${bold(shortname)} → ${project.slug} (${shortenPath(project.root_path, 50)})`));\n if (opts.permission) {\n console.log(dim(` Permission: ${opts.permission}`));\n }\n}\n\nexport function cmdUnname(db: Database, shortname: string): void {\n const alias = db\n .prepare(\"SELECT project_id FROM aliases WHERE alias = ?\")\n .get(shortname) as { project_id: number } | undefined;\n\n if (!alias) {\n console.error(err(`No named project found: \"${shortname}\"`));\n process.exit(1);\n }\n\n db.prepare(\"DELETE FROM aliases WHERE alias = ?\").run(shortname);\n\n const remaining = db\n .prepare(\"SELECT COUNT(*) AS cnt FROM aliases WHERE project_id = ?\")\n .get(alias.project_id) as { cnt: number };\n\n console.log(ok(`Removed name: ${bold(shortname)}`));\n if (remaining.cnt === 0) {\n console.log(dim(\" Project has no remaining names.\"));\n }\n}\n\nexport function cmdNames(db: Database, opts: { json?: boolean; all?: boolean }): void {\n // --all: LEFT JOIN so projects WITHOUT a curated alias are included too (for\n // AIBroker's picker to search everything). Unnamed projects get their\n // slug as the launch key (pai project config resolves by slug).\n const rows = db.prepare(`\n SELECT p.*, a.alias AS name,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n FROM projects p\n ${opts.all ? \"LEFT JOIN\" : \"JOIN\"} aliases a ON a.project_id = p.id\n WHERE p.status = 'active'\n ORDER BY p.updated_at DESC\n `).all() as (ProjectRow & { name: string | null; session_count: number; last_active: number | null; session_config: string | null })[];\n\n if (opts.json) {\n const grouped = new Map<number, unknown>();\n for (const row of rows) {\n if (!grouped.has(row.id)) {\n grouped.set(row.id, {\n name: row.name ?? row.slug, // launch key: alias if named, else slug\n names: row.name ? [row.name] : [],\n slug: row.slug,\n display_name: row.display_name,\n root_path: row.root_path,\n session_count: row.session_count,\n last_active: row.last_active ? new Date(row.last_active).toISOString() : null,\n session_config: row.session_config ? JSON.parse(row.session_config) : null,\n });\n } else if (row.name) {\n (grouped.get(row.id) as { names: string[] }).names.push(row.name);\n }\n }\n console.log(JSON.stringify([...grouped.values()], null, 2));\n return;\n }\n\n if (!rows.length) {\n console.log(warn(\"No named projects. Use: pai project name <slug-or-number> <shortname>\"));\n return;\n }\n\n const seen = new Set<number>();\n const tableRows: string[][] = [];\n for (const row of rows) {\n if (seen.has(row.id)) continue;\n seen.add(row.id);\n\n const allNames = rows.filter(r => r.id === row.id).map(r => r.name).filter(Boolean) as string[];\n const config = row.session_config ? JSON.parse(row.session_config) as SessionConfig : null;\n const perm = config?.permission ?? dim('default');\n\n tableRows.push([\n bold(allNames.length ? allNames.join(', ') : row.slug),\n row.slug,\n dim(shortenPath(row.root_path, 40)),\n typeof perm === 'string' ? perm : dim('default'),\n String(row.session_count),\n fmtDate(row.last_active),\n ]);\n }\n\n console.log();\n console.log(header(\" Named Projects\"));\n console.log();\n console.log(renderTable([\"Name(s)\", \"Slug\", \"Path\", \"Permission\", \"Sessions\", \"Last Active\"], tableRows));\n console.log();\n console.log(dim(` ${tableRows.length} named project(s)`));\n}\n\nexport function cmdConfig(\n db: Database,\n identifier: string | undefined,\n opts: {\n set?: string[];\n unset?: string[];\n preset?: string;\n defaults?: boolean;\n options?: boolean;\n json?: boolean;\n reset?: boolean;\n }\n): void {\n // Discovery: list available options\n if (opts.options) {\n if (opts.json) {\n console.log(JSON.stringify({\n options: CONFIG_OPTIONS,\n presets: Object.entries(PERMISSION_PRESETS).map(([name, config]) => ({ name, ...config })),\n }, null, 2));\n return;\n }\n\n console.log();\n console.log(header(\" Session Config Options\"));\n console.log();\n console.log(bold(\" Available keys:\"));\n console.log();\n for (const opt of CONFIG_OPTIONS) {\n console.log(` ${bold(opt.key.padEnd(14))} ${dim(`(${opt.type})`)} ${opt.description}`);\n console.log(` ${' '.repeat(14)} ${dim('e.g.')} ${opt.examples.map(e => chalk.cyan(e)).join(', ')}`);\n }\n console.log();\n console.log(bold(\" Permission presets:\"));\n console.log();\n for (const [name, config] of Object.entries(PERMISSION_PRESETS)) {\n const parts = [];\n if (config.flags) parts.push(`flags: ${config.flags}`);\n if (config.env && Object.keys(config.env).length) parts.push(`env: ${Object.entries(config.env).map(([k, v]) => `${k}=${v}`).join(' ')}`);\n if (config.autoStart) parts.push('autoStart');\n if (config.prompt) parts.push(`prompt: \"${config.prompt}\"`);\n console.log(` ${bold(name.padEnd(10))} ${dim(parts.join(', ') || '(vanilla)')}`);\n }\n console.log();\n return;\n }\n\n // Global defaults mode\n if (opts.defaults) {\n let defaults = getGlobalDefaults();\n\n if (opts.reset) {\n saveGlobalDefaults({});\n console.log(ok(\"Global session defaults reset.\"));\n return;\n }\n\n if (opts.preset) {\n defaults = applyPreset(defaults, opts.preset);\n saveGlobalDefaults(defaults);\n console.log(ok(`Global defaults set to preset: ${bold(opts.preset)}`));\n return;\n }\n\n if (opts.set?.length) {\n for (const pair of opts.set) {\n const eqIdx = pair.indexOf('=');\n if (eqIdx === -1) { console.error(err(`Invalid format: \"${pair}\". Use key=value.`)); continue; }\n const key = pair.substring(0, eqIdx);\n const value = pair.substring(eqIdx + 1);\n if (!CONFIG_OPTIONS.find(o => o.key === key)) { console.error(err(`Unknown key: \"${key}\". Run: pai project config --options`)); continue; }\n if (key === 'env') {\n defaults.env = { ...(defaults.env ?? {}), ...(parseConfigValue('env', value) as Record<string, string>) };\n } else {\n (defaults as Record<string, unknown>)[key] = parseConfigValue(key, value);\n }\n }\n saveGlobalDefaults(defaults);\n console.log(ok(\"Global defaults updated.\"));\n }\n\n if (opts.unset?.length) {\n for (const key of opts.unset) {\n if (key.startsWith('env.')) {\n const envKey = key.substring(4);\n if (defaults.env) delete defaults.env[envKey];\n } else {\n delete (defaults as Record<string, unknown>)[key];\n }\n }\n saveGlobalDefaults(defaults);\n console.log(ok(\"Global defaults updated.\"));\n }\n\n if (opts.json) {\n console.log(JSON.stringify(defaults, null, 2));\n } else {\n console.log();\n console.log(header(\" Global Session Defaults\"));\n console.log();\n if (Object.keys(defaults).length === 0) {\n console.log(dim(\" No defaults set. New sessions use vanilla Claude.\"));\n console.log(dim(\" Set with: pai project config --defaults --preset full\"));\n } else {\n for (const [key, value] of Object.entries(defaults)) {\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${bold(key.padEnd(14))} ${display}`);\n }\n }\n console.log();\n }\n return;\n }\n\n // Per-project config\n if (!identifier) {\n console.error(err(\"Specify a project: pai project config <name-or-slug>\"));\n console.error(dim(\" Or use --defaults for global defaults, --options for available keys.\"));\n process.exit(1);\n }\n\n const project = resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\n let config = getSessionConfig(project);\n\n if (opts.reset) {\n db.prepare(\"UPDATE projects SET session_config = NULL, updated_at = ? WHERE id = ?\").run(now(), project.id);\n console.log(ok(`Config reset for ${bold(project.slug)}. Will use global defaults.`));\n return;\n }\n\n if (opts.preset) {\n config = applyPreset(config, opts.preset);\n db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n .run(JSON.stringify(config), now(), project.id);\n console.log(ok(`Applied preset ${bold(opts.preset)} to ${bold(project.slug)}`));\n }\n\n if (opts.set?.length) {\n for (const pair of opts.set) {\n const eqIdx = pair.indexOf('=');\n if (eqIdx === -1) { console.error(err(`Invalid format: \"${pair}\". Use key=value.`)); continue; }\n const key = pair.substring(0, eqIdx);\n const value = pair.substring(eqIdx + 1);\n if (!CONFIG_OPTIONS.find(o => o.key === key)) { console.error(err(`Unknown key: \"${key}\". Run: pai project config --options`)); continue; }\n if (key === 'env') {\n config.env = { ...(config.env ?? {}), ...(parseConfigValue('env', value) as Record<string, string>) };\n } else {\n (config as Record<string, unknown>)[key] = parseConfigValue(key, value);\n }\n }\n db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n .run(JSON.stringify(config), now(), project.id);\n console.log(ok(`Config updated for ${bold(project.slug)}`));\n }\n\n if (opts.unset?.length) {\n for (const key of opts.unset) {\n if (key.startsWith('env.')) {\n const envKey = key.substring(4);\n if (config.env) delete config.env[envKey];\n } else {\n delete (config as Record<string, unknown>)[key];\n }\n }\n db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n .run(JSON.stringify(config), now(), project.id);\n console.log(ok(`Config updated for ${bold(project.slug)}`));\n }\n\n const effective = { ...getGlobalDefaults(), ...config };\n const aliases = getProjectAliases(db, project.id);\n\n if (opts.json) {\n console.log(JSON.stringify({\n project: project.slug,\n names: aliases,\n root_path: project.root_path,\n config,\n global_defaults: getGlobalDefaults(),\n effective,\n }, null, 2));\n return;\n }\n\n console.log();\n console.log(header(` Config: ${project.slug}`));\n if (aliases.length) console.log(dim(` Names: ${aliases.join(', ')}`));\n console.log(dim(` Path: ${project.root_path}`));\n console.log();\n\n if (Object.keys(config).length === 0) {\n console.log(dim(\" No project-specific config. Using global defaults.\"));\n } else {\n console.log(bold(\" Project config:\"));\n for (const [key, value] of Object.entries(config)) {\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${bold(key.padEnd(14))} ${display}`);\n }\n }\n\n const globalDefaults = getGlobalDefaults();\n if (Object.keys(globalDefaults).length > 0) {\n console.log();\n console.log(dim(\" Global defaults (overridden by project config):\"));\n for (const [key, value] of Object.entries(globalDefaults)) {\n const overridden = key in config;\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${dim(key.padEnd(14))} ${overridden ? chalk.strikethrough(display) + ' ' + dim('(overridden)') : display}`);\n }\n }\n\n console.log();\n console.log(bold(\" Effective (what AIBroker uses):\"));\n for (const [key, value] of Object.entries(effective)) {\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${bold(key.padEnd(14))} ${display}`);\n }\n console.log();\n}\n","/**\n * Project health check command — audits registered projects for missing paths,\n * moved directories, and orphaned note directories.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header, shortenPath, now, renderTable, encodeDir } from \"../../utils.js\";\nimport type { HealthRow, HealthCategory, ProjectHealth, ProjectRow } from \"./types.js\";\n\nfunction findOrphanedNotesDirs(project: ProjectRow): string[] {\n const claudeProjects = join(homedir(), \".claude\", \"projects\");\n if (!existsSync(claudeProjects)) return [];\n\n const expected = encodeDir(project.root_path);\n const results: string[] = [];\n\n try {\n for (const entry of readdirSync(claudeProjects)) {\n const full = join(claudeProjects, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n if (entry === expected || entry === project.encoded_dir) {\n const notesDir = join(full, \"Notes\");\n if (existsSync(notesDir)) {\n results.push(notesDir);\n }\n }\n }\n } catch {\n // Unreadable — ignore\n }\n return results;\n}\n\nfunction suggestMovedPath(project: ProjectRow): string | undefined {\n const name = basename(project.root_path);\n const candidates = [\n join(homedir(), \"dev\", name),\n join(homedir(), \"dev\", \"ai\", name),\n join(homedir(), \"Desktop\", name),\n join(homedir(), \"Projects\", name),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n return undefined;\n}\n\nexport function cmdHealth(\n db: Database,\n opts: { fix?: boolean; json?: boolean; status?: string }\n): void {\n const rows = db\n .prepare(\n `SELECT p.*,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count\n FROM projects p\n ORDER BY p.status ASC, p.updated_at DESC`\n )\n .all() as HealthRow[];\n\n const results: ProjectHealth[] = rows.map((project) => {\n const pathExists = existsSync(project.root_path);\n const orphaned = findOrphanedNotesDirs(project);\n\n let category: HealthCategory;\n let suggestedPath: string | undefined;\n\n if (pathExists) {\n category = \"active\";\n } else {\n suggestedPath = suggestMovedPath(project);\n category = suggestedPath ? \"stale\" : \"dead\";\n }\n\n return {\n project,\n category,\n suggestedPath,\n claudeNotesExists: orphaned.length > 0,\n orphanedNotesDirs: orphaned,\n };\n });\n\n const filtered = opts.status ? results.filter((r) => r.category === opts.status) : results;\n\n if (opts.json) {\n console.log(JSON.stringify(\n filtered.map((r) => ({\n slug: r.project.slug,\n root_path: r.project.root_path,\n status: r.project.status,\n health: r.category,\n session_count: r.project.session_count,\n suggested_path: r.suggestedPath ?? null,\n claude_notes_exists: r.claudeNotesExists,\n orphaned_notes_dirs: r.orphanedNotesDirs,\n })),\n null,\n 2\n ));\n return;\n }\n\n const active = filtered.filter((r) => r.category === \"active\");\n const stale = filtered.filter((r) => r.category === \"stale\");\n const dead = filtered.filter((r) => r.category === \"dead\");\n\n console.log();\n console.log(header(\" PAI Project Health Report\"));\n console.log();\n console.log(\n ` ${chalk.green(\"Active:\")} ${active.length} ${chalk.yellow(\"Stale (moved?):\")} ${stale.length} ${chalk.red(\"Dead (missing):\")} ${dead.length}`\n );\n console.log();\n\n if (active.length) {\n console.log(bold(\" Active projects (path exists):\"));\n const tableRows = active.map((r) => [\n bold(r.project.slug),\n dim(shortenPath(r.project.root_path, 50)),\n String(r.project.session_count),\n r.claudeNotesExists ? chalk.green(\"yes\") : dim(\"no\"),\n ]);\n console.log(\n renderTable([\"Slug\", \"Path\", \"Sessions\", \"Claude Notes\"], tableRows)\n .split(\"\\n\").map((l) => \" \" + l).join(\"\\n\")\n );\n console.log();\n }\n\n if (stale.length) {\n console.log(warn(\" Stale projects (path missing, possible new location found):\"));\n for (const r of stale) {\n console.log(` ${bold(r.project.slug)}`);\n console.log(dim(` Old path: ${r.project.root_path}`));\n console.log(chalk.cyan(` Found at: ${r.suggestedPath}`));\n if (r.claudeNotesExists) {\n console.log(chalk.green(` Notes: ${r.orphanedNotesDirs.join(\", \")}`));\n }\n if (opts.fix && r.suggestedPath) {\n const ts = now();\n const newEncoded = encodeDir(r.suggestedPath);\n db.prepare(\"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\")\n .run(r.suggestedPath, newEncoded, ts, r.project.id);\n console.log(ok(` Auto-fixed: updated path to ${r.suggestedPath}`));\n } else if (r.suggestedPath) {\n console.log(dim(` Fix: pai project move ${r.project.slug} ${r.suggestedPath}`));\n }\n }\n console.log();\n }\n\n if (dead.length) {\n console.log(err(\" Dead projects (path missing, no match found):\"));\n for (const r of dead) {\n console.log(` ${bold(r.project.slug)} ${dim(r.project.root_path)}`);\n if (r.claudeNotesExists) {\n console.log(chalk.yellow(` Notes: ${r.orphanedNotesDirs.join(\", \")}`));\n }\n if (r.project.session_count === 0 && opts.fix) {\n db.prepare(\"UPDATE projects SET status = 'archived', archived_at = ?, updated_at = ? WHERE id = ?\")\n .run(now(), now(), r.project.id);\n console.log(ok(\" Auto-fixed: archived (0 sessions, path gone)\"));\n } else {\n console.log(dim(` Fix: pai project archive ${r.project.slug} (or pai project move ...)`));\n }\n }\n console.log();\n }\n\n console.log(dim(` ${rows.length} total: ${active.length} active, ${stale.length} stale, ${dead.length} dead`));\n\n if (!opts.fix && (stale.length > 0 || dead.length > 0)) {\n console.log();\n console.log(warn(\" Run with --fix to auto-remediate where possible.\"));\n }\n}\n","/**\n * Commander registration for all `pai projects` sub-commands (plural namespace).\n *\n * This is the canonical namespace. `pai project <subcmd>` (singular) is a\n * deprecated alias that forwards here with a stderr notice.\n */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { cmdPromote } from \"../../../session/promote.js\";\nimport {\n cmdAdd,\n cmdList,\n cmdInfo,\n cmdArchive,\n cmdUnarchive,\n cmdMove,\n cmdTag,\n cmdAlias,\n cmdEdit,\n cmdDetect,\n cmdConsolidate,\n cmdGo,\n cmdRebind,\n findMovedPath,\n} from \"./commands.js\";\nimport { existsSync } from \"node:fs\";\nimport { ok, warn, err, dim, bold, shortenPath, encodeDir, now } from \"../../utils.js\";\nimport { basename } from \"node:path\";\nimport { cmdName, cmdUnname, cmdNames, cmdConfig } from \"./session-config.js\";\nimport { cmdHealth } from \"./health.js\";\nimport { resolveIdentifier } from \"./helpers.js\";\n\nexport { cmdGo };\n\nexport function registerProjectsCommands(\n projectsCmd: Command,\n getDb: () => Database\n): void {\n // pai projects (bare) — show the unified deduped listing, same as `pai`.\n // The old project-only listing is still available as `pai projects list-raw`.\n projectsCmd\n .command(\"list\", { isDefault: true })\n .description(\n \"Show the unified deduped listing (same as `pai`).\\n\" +\n \"Use --all to include cold / zero-session / archived projects.\"\n )\n .option(\"--all\", \"Show all entries including cold / zero-session / archived projects\")\n .option(\"-n, --n <count>\", \"Max rows to show\", \"20\")\n .action(async (opts: { all?: boolean; n?: string }) => {\n const { cmdMain } = await import(\"../main-resolver.js\");\n await cmdMain(getDb(), undefined, undefined, opts);\n });\n\n // Raw legacy project listing (registry rows only, with slug + path)\n projectsCmd\n .command(\"list-raw\")\n .description(\"Raw registry listing with slug, path, status, last_active columns\")\n .option(\"--all\", \"Include archived projects (default: active only)\")\n .option(\"--status <status>\", \"Filter by status: active | archived\")\n .option(\"--tag <tag>\", \"Filter by tag\")\n .option(\"--type <type>\", \"Filter by type\")\n .action((opts: { all?: boolean; status?: string; tag?: string; type?: string }) => {\n cmdList(getDb(), opts);\n });\n\n // pai projects cd <identifier>\n projectsCmd\n .command(\"cd <identifier>\")\n .description(\n \"cd to a project directory. Short form: pai cd <name>\\n\" +\n \"(The shell wrapper handles the actual cd; pure output here.)\\n\" +\n \"Auto-detects moved projects when the registered path no longer exists.\"\n )\n .action((identifier: string) => {\n const db = getDb();\n const project = resolveIdentifier(db, identifier);\n if (!project) {\n console.error(`Project not found: ${identifier}`);\n process.exit(1);\n return;\n }\n\n if (existsSync(project.root_path)) {\n process.stdout.write(project.root_path + \"\\n\");\n return;\n }\n\n // Path missing — search for moved location\n process.stderr.write(\n warn(`Path not found: ${project.root_path}\\n`) +\n dim(\" Searching for moved location...\\n\")\n );\n\n const result = findMovedPath(project.root_path);\n\n if (result.found) {\n const newPath = result.found;\n const newEncoded = encodeDir(newPath);\n const ts = now();\n db.prepare(\n \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n ).run(newPath, newEncoded, ts, project.id);\n process.stderr.write(\n ok(`Project moved: ${shortenPath(project.root_path, 50)}\\n`) +\n dim(` → ${newPath}\\n`) +\n ok(\"Registry updated.\\n\")\n );\n process.stdout.write(newPath + \"\\n\");\n return;\n }\n\n if (result.ambiguous) {\n process.stderr.write(\n warn(`Multiple directories named \"${basename(project.root_path)}\" found:\\n`)\n );\n for (const candidate of result.ambiguous) {\n process.stderr.write(dim(` ${candidate}\\n`));\n }\n process.stderr.write(\n dim(`\\n Disambiguate with: pai projects rebind ${project.slug} <path>\\n`)\n );\n process.exitCode = 1;\n return;\n }\n\n process.stderr.write(\n err(\n `Project \"${project.slug}\" root_path \"${project.root_path}\" does not exist on disk\\n` +\n ` and no folder named \"${basename(project.root_path)}\" was found in scan dirs.\\n`\n ) +\n dim(` Fix with: pai projects rebind ${project.slug} <new-path>\\n`)\n );\n process.exitCode = 1;\n });\n\n // pai projects add <path>\n projectsCmd\n .command(\"add <path>\")\n .description(\"Register a project directory in the PAI registry\")\n .option(\"--slug <slug>\", \"Override auto-generated slug\")\n .option(\"--type <type>\", \"Project type: local | central | obsidian-linked | external\", \"local\")\n .option(\"--display-name <name>\", \"Human-readable display name\")\n .action(\n (\n rawPath: string,\n opts: { slug?: string; type?: string; displayName?: string }\n ) => {\n cmdAdd(getDb(), rawPath, opts);\n }\n );\n\n // pai projects info <slug>\n projectsCmd\n .command(\"info <slug>\")\n .description(\"Show full details for a project\")\n .action((slug: string) => {\n cmdInfo(getDb(), slug);\n });\n\n // pai projects archive <slug>\n projectsCmd\n .command(\"archive <slug>\")\n .description(\"Archive a project\")\n .action((slug: string) => {\n cmdArchive(getDb(), slug);\n });\n\n // pai projects unarchive <slug>\n projectsCmd\n .command(\"unarchive <slug>\")\n .description(\"Restore an archived project to active status\")\n .action((slug: string) => {\n cmdUnarchive(getDb(), slug);\n });\n\n // pai projects move <slug> <new-path>\n projectsCmd\n .command(\"move <slug> <new-path>\")\n .description(\"Update the root path for a project\")\n .action((slug: string, newPath: string) => {\n cmdMove(getDb(), slug, newPath);\n });\n\n // pai projects rebind <slug> <new-path>\n projectsCmd\n .command(\"rebind <slug> <new-path>\")\n .description(\n \"Manually update the root_path for a project (for when auto-detect found multiple matches).\\n\" +\n \"Validates the new path exists and is a directory, then updates the registry.\"\n )\n .action((slug: string, newPath: string) => {\n cmdRebind(getDb(), slug, newPath);\n });\n\n // pai projects tag <slug> <tags...>\n projectsCmd\n .command(\"tag <slug> <tags...>\")\n .description(\"Add one or more tags to a project\")\n .action((slug: string, tags: string[]) => {\n cmdTag(getDb(), slug, tags);\n });\n\n // pai projects alias <slug> <alias>\n projectsCmd\n .command(\"alias <slug> <alias>\")\n .description(\"Register an alternative slug for a project\")\n .action((slug: string, alias: string) => {\n cmdAlias(getDb(), slug, alias);\n });\n\n // pai projects edit <slug>\n projectsCmd\n .command(\"edit <slug>\")\n .description(\"Edit project metadata\")\n .option(\"--display-name <name>\", \"New display name\")\n .option(\"--type <type>\", \"New type\")\n .action(\n (slug: string, opts: { displayName?: string; type?: string }) => {\n cmdEdit(getDb(), slug, opts);\n }\n );\n\n // pai projects detect [path]\n projectsCmd\n .command(\"detect [path]\")\n .description(\"Detect which registered project the given path (or CWD) belongs to\")\n .option(\"--json\", \"Output raw JSON instead of human-readable text\")\n .action((pathArg: string | undefined, opts: { json?: boolean }) => {\n cmdDetect(getDb(), pathArg, opts);\n });\n\n // pai projects health\n projectsCmd\n .command(\"health\")\n .description(\n \"Audit all registered projects: check which paths still exist, find moved/dead projects\"\n )\n .option(\"--fix\", \"Auto-remediate where possible\")\n .option(\"--json\", \"Output raw JSON report\")\n .option(\"--status <category>\", \"Filter output to: active | stale | dead\")\n .action((opts: { fix?: boolean; json?: boolean; status?: string }) => {\n cmdHealth(getDb(), opts);\n });\n\n // pai projects consolidate <identifier>\n projectsCmd\n .command(\"consolidate <identifier>\")\n .description(\n \"Consolidate scattered ~/.claude/projects/.../Notes/ directories for a project into its canonical Notes/ location\"\n )\n .option(\"--yes\", \"Perform consolidation without confirmation prompt\")\n .option(\"--dry-run\", \"Preview what would be moved without making changes\")\n .action(\n (identifier: string, opts: { yes?: boolean; dryRun?: boolean }) => {\n cmdConsolidate(getDb(), identifier, opts);\n }\n );\n\n // pai projects promote\n projectsCmd\n .command(\"promote\")\n .description(\"Promote a session note into a new standalone project\")\n .requiredOption(\"--from-session <path>\", \"Path to the session note markdown file\")\n .requiredOption(\"--to <path>\", \"Directory path for the new project (must not exist)\")\n .option(\"--name <name>\", \"Display name for the new project (derived from filename if omitted)\")\n .action((opts: { fromSession: string; to: string; name?: string }) => {\n cmdPromote(getDb(), opts);\n });\n\n // pai projects go <query>\n projectsCmd\n .command(\"go <query>\")\n .description(\n \"Print the root path for a project by slug, partial name, or fuzzy match.\\n\" +\n \"Designed for shell integration: cd $(pai projects go <query>)\"\n )\n .action((query: string) => {\n cmdGo(getDb(), query);\n });\n\n // pai projects name <identifier> <shortname>\n projectsCmd\n .command(\"name <identifier> <shortname>\")\n .description(\"Give a project a short name for quick access\")\n .option(\"--permission <level>\", \"Permission level: full | trusted | default\")\n .action(\n (\n identifier: string,\n shortname: string,\n opts: { permission?: string }\n ) => {\n cmdName(getDb(), identifier, shortname, opts);\n }\n );\n\n // pai projects unname <shortname>\n projectsCmd\n .command(\"unname <shortname>\")\n .description(\"Remove a project's short name\")\n .action((shortname: string) => {\n cmdUnname(getDb(), shortname);\n });\n\n // pai projects names\n projectsCmd\n .command(\"names\")\n .description(\"List named projects (your curated shortlist)\")\n .option(\"--json\", \"Output JSON for AIBroker consumption\")\n .option(\"--all\", \"Include ALL active registered projects, not just the named shortlist\")\n .action((opts: { json?: boolean; all?: boolean }) => {\n cmdNames(getDb(), opts);\n });\n\n // pai projects config [identifier]\n projectsCmd\n .command(\"config [identifier]\")\n .description(\n \"View or modify session launch config for a project.\\n\" +\n \"Use --options to discover available keys and presets.\"\n )\n .option(\n \"--set <key=value...>\",\n \"Set config values (repeatable)\",\n (v: string, prev: string[]) => [...prev, v],\n [] as string[]\n )\n .option(\n \"--unset <key...>\",\n \"Remove config keys (repeatable, use env.KEY for env vars)\",\n (v: string, prev: string[]) => [...prev, v],\n [] as string[]\n )\n .option(\"--preset <name>\", \"Apply a permission preset: full | trusted | default\")\n .option(\"--defaults\", \"Manage global session defaults instead of a project\")\n .option(\"--options\", \"List available config keys and presets\")\n .option(\"--json\", \"Output JSON\")\n .option(\"--reset\", \"Reset config to empty (inherit global defaults)\")\n .action(\n (\n identifier: string | undefined,\n opts: {\n set?: string[];\n unset?: string[];\n preset?: string;\n defaults?: boolean;\n options?: boolean;\n json?: boolean;\n reset?: boolean;\n }\n ) => {\n cmdConfig(getDb(), identifier, opts);\n }\n );\n}\n","/** Registry migrate command: import data from ~/.claude/session-registry.json. */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { ok, warn, err, dim } from \"../../utils.js\";\nimport { slugify, parseSessionFilename } from \"../../../registry/migrate.js\";\nimport { upsertProject, upsertSession } from \"./utils.js\";\nimport type { Database } from \"better-sqlite3\";\n\n// ---------------------------------------------------------------------------\n// Legacy JSON registry types\n// ---------------------------------------------------------------------------\n\ninterface LegacyProjectEntry {\n encoded_dir: string;\n original_path: string;\n notes?: string[];\n session_count?: number;\n note_count?: number;\n todo_exists?: boolean;\n last_modified?: string;\n path_exists?: boolean;\n}\n\ninterface LegacyJsonRegistry {\n version?: number;\n projects?: LegacyProjectEntry[];\n [key: string]: unknown;\n}\n\nconst SESSION_REGISTRY_PATH = join(homedir(), \".claude\", \"session-registry.json\");\n\n// ---------------------------------------------------------------------------\n// cmdMigrate\n// ---------------------------------------------------------------------------\n\nexport function cmdMigrate(db: Database): void {\n if (!existsSync(SESSION_REGISTRY_PATH)) {\n console.error(err(`session-registry.json not found: ${SESSION_REGISTRY_PATH}`));\n process.exit(1);\n }\n\n let registry: LegacyJsonRegistry;\n try {\n const raw = readFileSync(SESSION_REGISTRY_PATH, \"utf8\");\n registry = JSON.parse(raw) as LegacyJsonRegistry;\n } catch (e) {\n console.error(err(`Failed to parse session-registry.json: ${e}`));\n process.exit(1);\n }\n\n const projects = registry.projects ?? [];\n if (!projects.length) {\n console.log(warn(\"No projects found in session-registry.json.\"));\n return;\n }\n\n console.log(dim(`Migrating ${projects.length} project(s) from session-registry.json ...`));\n\n let projectsNew = 0;\n let projectsSkipped = 0;\n let sessionsNew = 0;\n const errors: string[] = [];\n\n for (const entry of projects) {\n if (!entry.original_path || !entry.encoded_dir) {\n errors.push(`Skipping entry with missing path: ${JSON.stringify(entry)}`);\n continue;\n }\n\n const rootPath = entry.original_path;\n const encodedDir = entry.encoded_dir;\n const slug = slugify(rootPath);\n\n try {\n const { isNew, id } = upsertProject(db, slug, rootPath, encodedDir);\n if (isNew) projectsNew++;\n else projectsSkipped++;\n\n const notes = entry.notes ?? [];\n for (const filename of notes) {\n const parsed = parseSessionFilename(filename);\n if (!parsed) continue;\n\n const isNewSession = upsertSession(db, id, parsed.number, parsed.date, parsed.slug, parsed.title, parsed.filename);\n if (isNewSession) sessionsNew++;\n }\n } catch (e) {\n errors.push(`Error processing ${entry.encoded_dir}: ${String(e)}`);\n }\n }\n\n console.log(ok(\"Migration complete.\"));\n console.log(dim(` Projects: ${projectsNew} new, ${projectsSkipped} already existed`));\n console.log(dim(` Sessions: ${sessionsNew} new`));\n\n if (errors.length) {\n console.log();\n console.log(warn(` ${errors.length} error(s) during migration:`));\n for (const e of errors.slice(0, 5)) {\n console.log(dim(` ${e}`));\n }\n if (errors.length > 5) {\n console.log(dim(` ... and ${errors.length - 5} more`));\n }\n }\n}\n","/** Registry command registration and simple sub-commands (stats, rebuild, lookup). */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { ok, warn, err, dim, bold, fmtDate } from \"../../utils.js\";\nimport { homedir } from \"node:os\";\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { cmdScan, loadScanConfig, saveScanConfig, resolveHome } from \"./scan.js\";\nimport { cmdMigrate } from \"./migrate.js\";\n\n// ---------------------------------------------------------------------------\n// stats\n// ---------------------------------------------------------------------------\n\nfunction cmdStats(db: Database): void {\n const totalProjects = (db.prepare(\"SELECT COUNT(*) AS n FROM projects\").get() as { n: number }).n;\n const activeProjects = (db.prepare(\"SELECT COUNT(*) AS n FROM projects WHERE status = 'active'\").get() as { n: number }).n;\n const archivedProjects = (db.prepare(\"SELECT COUNT(*) AS n FROM projects WHERE status = 'archived'\").get() as { n: number }).n;\n const totalSessions = (db.prepare(\"SELECT COUNT(*) AS n FROM sessions\").get() as { n: number }).n;\n const totalTags = (db.prepare(\"SELECT COUNT(*) AS n FROM tags\").get() as { n: number }).n;\n\n const lastProject = db\n .prepare(\"SELECT updated_at FROM projects ORDER BY updated_at DESC LIMIT 1\")\n .get() as { updated_at: number } | undefined;\n\n const lastSession = db\n .prepare(\"SELECT created_at FROM sessions ORDER BY created_at DESC LIMIT 1\")\n .get() as { created_at: number } | undefined;\n\n console.log();\n console.log(bold(\" PAI Registry Stats\"));\n console.log();\n console.log(` ${bold(\"Projects:\")} ${totalProjects}`);\n console.log(` ${bold(\" Active:\")} ${activeProjects}`);\n console.log(` ${bold(\" Archived:\")} ${archivedProjects}`);\n console.log(` ${bold(\"Sessions:\")} ${totalSessions}`);\n console.log(` ${bold(\"Tags:\")} ${totalTags}`);\n if (lastProject) {\n console.log(` ${bold(\"Last updated:\")} ${fmtDate(lastProject.updated_at)}`);\n }\n if (lastSession) {\n console.log(` ${bold(\"Last session:\")} ${fmtDate(lastSession.created_at)}`);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// rebuild\n// ---------------------------------------------------------------------------\n\nfunction cmdRebuild(db: Database): void {\n console.log(warn(\"Rebuilding registry — all existing data will be erased.\"));\n console.log(dim(\"Clearing all tables ...\"));\n\n db.exec(`\n DELETE FROM compaction_log;\n DELETE FROM session_tags;\n DELETE FROM project_tags;\n DELETE FROM aliases;\n DELETE FROM sessions;\n DELETE FROM projects;\n DELETE FROM tags;\n DELETE FROM schema_version;\n `);\n\n console.log(dim(\"Registry cleared. Re-scanning ...\"));\n cmdScan(db);\n}\n\n// ---------------------------------------------------------------------------\n// lookup\n// ---------------------------------------------------------------------------\n\nfunction cmdLookup(db: Database, fsPath: string): void {\n const resolved = resolve(fsPath);\n\n const row = db\n .prepare(\"SELECT slug FROM projects WHERE root_path = ?\")\n .get(resolved) as { slug: string } | undefined;\n\n if (!row) {\n process.exit(1);\n }\n\n process.stdout.write(row.slug + \"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerRegistryCommands(\n registryCmd: Command,\n getDb: () => Database\n): void {\n // pai registry scan\n registryCmd\n .command(\"scan\")\n .description(\"Walk ~/.claude/projects/ and configured scan_dirs, upsert all projects\")\n .option(\"--add-dir <path>\", \"Add a directory to scan_dirs config\")\n .option(\"--remove-dir <path>\", \"Remove a directory from scan_dirs config\")\n .option(\"--show-dirs\", \"Show currently configured scan directories\")\n .option(\"--quick\", \"Minimal output mode (used by hooks and daemon)\")\n .action((opts: { addDir?: string; removeDir?: string; showDirs?: boolean; quick?: boolean }) => {\n if (opts.showDirs) {\n const config = loadScanConfig();\n if (!config.scan_dirs.length) {\n console.log(dim(\" No extra scan directories configured.\"));\n console.log(dim(\" Use --add-dir <path> to add one.\"));\n } else {\n console.log(bold(\" Configured scan directories:\"));\n for (const d of config.scan_dirs) {\n console.log(` ${d}`);\n }\n }\n return;\n }\n if (opts.addDir) {\n const config = loadScanConfig();\n const resolved = resolveHome(opts.addDir);\n if (!existsSync(resolved)) {\n console.error(err(`Directory not found: ${resolved}`));\n process.exit(1);\n }\n const display = resolved.startsWith(homedir())\n ? \"~\" + resolved.slice(homedir().length)\n : resolved;\n if (config.scan_dirs.includes(display) || config.scan_dirs.includes(resolved)) {\n console.log(warn(`Already configured: ${display}`));\n } else {\n config.scan_dirs.push(display);\n saveScanConfig(config);\n console.log(ok(`Added scan directory: ${bold(display)}`));\n }\n }\n if (opts.removeDir) {\n const config = loadScanConfig();\n const resolved = resolveHome(opts.removeDir);\n const display = resolved.startsWith(homedir())\n ? \"~\" + resolved.slice(homedir().length)\n : resolved;\n const before = config.scan_dirs.length;\n config.scan_dirs = config.scan_dirs.filter((d) => resolveHome(d) !== resolved);\n if (config.scan_dirs.length < before) {\n saveScanConfig(config);\n console.log(ok(`Removed scan directory: ${bold(display)}`));\n } else {\n console.log(warn(`Not found in config: ${display}`));\n }\n }\n if (!opts.addDir && !opts.removeDir) {\n cmdScan(getDb(), { quick: opts.quick });\n }\n });\n\n // pai registry migrate\n registryCmd\n .command(\"migrate\")\n .description(\"Import data from ~/.claude/session-registry.json\")\n .action(() => {\n cmdMigrate(getDb());\n });\n\n // pai registry stats\n registryCmd\n .command(\"stats\")\n .description(\"Show summary statistics for the registry\")\n .action(() => {\n cmdStats(getDb());\n });\n\n // pai registry rebuild\n registryCmd\n .command(\"rebuild\")\n .description(\"Erase all registry data and rebuild from the filesystem (destructive)\")\n .action(() => {\n cmdRebuild(getDb());\n });\n\n // pai registry lookup --path <path>\n registryCmd\n .command(\"lookup\")\n .description(\"Find the project slug for a filesystem path (for use in scripts)\")\n .requiredOption(\"--path <path>\", \"Filesystem path to look up\")\n .action((opts: { path: string }) => {\n cmdLookup(getDb(), opts.path);\n });\n}\n","/** Memory embed command: generate embeddings for un-embedded chunks. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { embedChunks } from \"../../../memory/indexer.js\";\nimport { dim, bold, ok, err } from \"../../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Shared embed runner (used by both index --embed and embed sub-command)\n// ---------------------------------------------------------------------------\n\nexport async function runEmbed(\n federation: Database,\n projectId?: number,\n projectSlug?: string,\n batchSize = 50,\n): Promise<void> {\n const label = projectSlug ? `project ${projectSlug}` : \"all projects\";\n console.log(dim(`Generating embeddings for ${label} (this may take a while on first run)...`));\n\n const { chunksEmbedded } = await embedChunks(\n federation,\n projectId,\n batchSize,\n (done, total) => {\n process.stdout.write(`\\r ${done} / ${total} chunks embedded...`);\n },\n );\n\n process.stdout.write(\"\\r\");\n console.log(ok(`Done.`) + ` ${bold(String(chunksEmbedded))} chunks embedded`);\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerEmbedCommand(\n memoryCmd: Command,\n getDb: () => Database,\n): void {\n memoryCmd\n .command(\"embed [project-slug]\")\n .description(\"Generate embeddings for un-embedded chunks (Phase 2.5)\")\n .option(\"--batch-size <n>\", \"Chunks to embed per batch\", \"50\")\n .action(async (projectSlug: string | undefined, opts: { batchSize?: string }) => {\n const registryDb = getDb();\n\n let federation: Database;\n try {\n federation = openFederation();\n } catch (e) {\n console.error(err(`Failed to open federation database: ${e}`));\n process.exit(1);\n }\n\n if (projectSlug) {\n const project = registryDb\n .prepare(\"SELECT id, slug FROM projects WHERE slug = ?\")\n .get(projectSlug) as { id: number; slug: string } | undefined;\n\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n await runEmbed(federation, project.id, project.slug, parseInt(opts.batchSize ?? \"50\", 10));\n } else {\n await runEmbed(federation, undefined, undefined, parseInt(opts.batchSize ?? \"50\", 10));\n }\n });\n}\n","/** Memory index command: index one or all projects into the memory store. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { indexProject, indexAll } from \"../../../memory/indexer.js\";\nimport { dim, bold, ok, err } from \"../../utils.js\";\nimport { PaiClient } from \"../../../daemon/ipc-client.js\";\nimport { loadConfig } from \"../../../daemon/config.js\";\nimport { runEmbed } from \"./embed.js\";\n\nexport function registerIndexCommand(\n memoryCmd: Command,\n getDb: () => Database,\n): void {\n memoryCmd\n .command(\"index [project-slug]\")\n .description(\"Index memory files for one project or all projects\")\n .option(\"--all\", \"Index all active projects (default when no slug given)\")\n .option(\"--embed\", \"Also generate embeddings for newly indexed chunks (Phase 2.5)\")\n .option(\"--direct\", \"Skip daemon IPC and run index directly (for debugging)\")\n .action(async (projectSlug: string | undefined, opts: { all?: boolean; embed?: boolean; direct?: boolean }) => {\n const registryDb = getDb();\n\n // If daemon is running and no --direct flag, trigger via IPC (non-blocking)\n if (!opts.direct && !projectSlug) {\n try {\n const config = loadConfig();\n const client = new PaiClient(config.socketPath);\n await client.triggerIndex();\n console.log(ok(\"Index triggered in daemon.\") + dim(\" Check daemon logs for progress.\"));\n console.log(dim(\" Run `pai daemon logs` to watch progress.\"));\n return;\n } catch {\n console.log(dim(\"Daemon not running. Running direct index...\"));\n }\n }\n\n let federation: Database;\n try {\n federation = openFederation();\n } catch (e) {\n console.error(err(`Failed to open federation database: ${e}`));\n process.exit(1);\n }\n\n if (projectSlug) {\n const project = registryDb\n .prepare(\"SELECT id, slug, display_name, root_path FROM projects WHERE slug = ? AND status = 'active'\")\n .get(projectSlug) as\n | { id: number; slug: string; display_name: string; root_path: string }\n | undefined;\n\n if (!project) {\n console.error(err(`Project not found or not active: ${projectSlug}`));\n process.exit(1);\n }\n\n console.log(dim(`Indexing ${project.display_name} (${project.slug})...`));\n const result = await indexProject(federation, project.id, project.root_path);\n\n console.log(\n ok(`Done.`) +\n ` ${bold(String(result.filesProcessed))} files indexed` +\n `, ${bold(String(result.chunksCreated))} chunks created` +\n `, ${dim(String(result.filesSkipped) + \" skipped (unchanged)\")}`,\n );\n\n if (opts.embed) {\n await runEmbed(federation, project.id, project.slug);\n }\n\n } else if (opts.all || !projectSlug) {\n console.log(dim(\"Indexing all active projects...\"));\n\n const { projects, result } = await indexAll(federation, registryDb);\n\n console.log(\n ok(`Done.`) +\n ` ${bold(String(projects))} projects` +\n `, ${bold(String(result.filesProcessed))} files indexed` +\n `, ${bold(String(result.chunksCreated))} chunks created` +\n `, ${dim(String(result.filesSkipped) + \" skipped (unchanged)\")}`,\n );\n\n if (opts.embed) {\n await runEmbed(federation);\n }\n }\n });\n}\n","/** Memory search command: BM25/semantic/hybrid search across federation.db. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport chalk from \"chalk\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { searchMemory, populateSlugs, type SearchResult } from \"../../../memory/search.js\";\nimport { dim, bold, ok, warn, err } from \"../../utils.js\";\nimport { loadConfig } from \"../../../daemon/config.js\";\nimport { createStorageBackend } from \"../../../storage/factory.js\";\n\n// ---------------------------------------------------------------------------\n// Helper\n// ---------------------------------------------------------------------------\n\nfunction tierColor(tier: string): string {\n switch (tier) {\n case \"evergreen\": return chalk.green(tier);\n case \"daily\": return chalk.yellow(tier);\n case \"topic\": return chalk.blue(tier);\n case \"session\": return chalk.dim(tier);\n default: return tier;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerSearchCommand(\n memoryCmd: Command,\n getDb: () => Database,\n): void {\n memoryCmd\n .command(\"search <query>\")\n .description(\"Search indexed memory (BM25 keyword, semantic, or hybrid)\")\n .option(\"--project <slug>\", \"Restrict search to a specific project\")\n .option(\"--source <source>\", \"Restrict to 'memory' or 'notes'\")\n .option(\"--limit <n>\", \"Maximum results to return\")\n .option(\"--mode <mode>\", \"Search mode: keyword (default), semantic, hybrid\")\n .option(\"--no-rerank\", \"Skip cross-encoder reranking (reranking is on by default)\")\n .option(\"--recency <days>\", \"Apply recency boost: score halves every N days. 0 = off\")\n .action(\n async (\n query: string,\n opts: { project?: string; source?: string; limit?: string; mode?: string; rerank: boolean; recency?: string },\n ) => {\n const registryDb = getDb();\n\n let federation: Database;\n try {\n federation = openFederation();\n } catch (e) {\n console.error(err(`Failed to open federation database: ${e}`));\n process.exit(1);\n }\n\n const config = loadConfig();\n const searchConfig = config.search;\n\n const maxResults = parseInt(opts.limit ?? String(searchConfig.defaultLimit), 10);\n const mode = (opts.mode ?? searchConfig.mode) as \"keyword\" | \"semantic\" | \"hybrid\";\n\n if (![\"keyword\", \"semantic\", \"hybrid\"].includes(mode)) {\n console.error(err(`Invalid mode: ${mode}. Use keyword, semantic, or hybrid.`));\n process.exit(1);\n }\n\n let projectIds: number[] | undefined;\n if (opts.project) {\n const project = registryDb\n .prepare(\"SELECT id FROM projects WHERE slug = ?\")\n .get(opts.project) as { id: number } | undefined;\n\n if (!project) {\n console.error(warn(`Project not found: ${opts.project} — searching all projects`));\n } else {\n projectIds = [project.id];\n }\n }\n\n const sources = opts.source ? [opts.source] : undefined;\n const searchOpts = { projectIds, sources, maxResults };\n\n let results: SearchResult[];\n\n if (mode === \"keyword\") {\n results = searchMemory(federation, query, searchOpts);\n\n } else if (mode === \"semantic\" || mode === \"hybrid\") {\n const backend = await createStorageBackend(config);\n\n try {\n const { generateEmbedding } = await import(\"../../../memory/embeddings.js\");\n\n console.log(dim(\"Generating query embedding...\"));\n const queryEmbedding = await generateEmbedding(query, true);\n\n if (mode === \"semantic\") {\n results = await backend.searchSemantic(queryEmbedding, searchOpts);\n } else {\n // Hybrid: combine keyword (BM25) and semantic results with min-max normalization\n const [keywordResults, semanticResults] = await Promise.all([\n backend.searchKeyword(query, { ...searchOpts, maxResults: 500 }),\n backend.searchSemantic(queryEmbedding, { ...searchOpts, maxResults: 500 }),\n ]);\n\n if (keywordResults.length === 0 && semanticResults.length === 0) {\n results = [];\n } else {\n const keyFor = (r: SearchResult) =>\n `${r.projectId}:${r.path}:${r.startLine}:${r.endLine}`;\n\n function minMaxNormalize(items: SearchResult[]): Map<string, number> {\n if (items.length === 0) return new Map();\n const min = Math.min(...items.map((r) => r.score));\n const max = Math.max(...items.map((r) => r.score));\n const range = max - min;\n const m = new Map<string, number>();\n for (const r of items) {\n m.set(keyFor(r), range === 0 ? 1 : (r.score - min) / range);\n }\n return m;\n }\n\n const kwNorm = minMaxNormalize(keywordResults);\n const semNorm = minMaxNormalize(semanticResults);\n const allKeys = new Set<string>([\n ...keywordResults.map(keyFor),\n ...semanticResults.map(keyFor),\n ]);\n const metaMap = new Map<string, SearchResult>();\n for (const r of [...keywordResults, ...semanticResults]) {\n metaMap.set(keyFor(r), r);\n }\n\n const combined: SearchResult[] = [];\n for (const key of allKeys) {\n const meta = metaMap.get(key)!;\n const combinedScore = 0.5 * (kwNorm.get(key) ?? 0) + 0.5 * (semNorm.get(key) ?? 0);\n combined.push({ ...meta, score: combinedScore });\n }\n\n results = combined.sort((a, b) => b.score - a.score).slice(0, maxResults);\n }\n }\n } finally {\n await backend.close();\n }\n } else {\n results = [];\n }\n\n if (!results || results.length === 0) {\n console.log(dim(`No results found for: \"${query}\" (mode: ${mode})`));\n return;\n }\n\n // Cross-encoder reranking (on by default, skip with --no-rerank)\n if (opts.rerank !== false) {\n const { rerankResults } = await import(\"../../../memory/reranker.js\");\n console.log(dim(\"Reranking with cross-encoder...\"));\n results = await rerankResults(query, results, { topK: maxResults });\n }\n\n // Recency boost (applied after reranking)\n const recencyDays = parseInt(opts.recency ?? String(searchConfig.recencyBoostDays), 10);\n if (recencyDays > 0) {\n const { applyRecencyBoost } = await import(\"../../../memory/search.js\");\n console.log(dim(`Applying recency boost (half-life: ${recencyDays} days)...`));\n results = applyRecencyBoost(results, recencyDays);\n }\n\n const withSlugs = populateSlugs(results, registryDb);\n const rerankLabel = opts.rerank !== false ? \" +rerank\" : \"\";\n const modeLabel = mode !== \"keyword\" ? ` [${mode}${rerankLabel}]` : (opts.rerank !== false ? ` [rerank]` : \"\");\n\n console.log(\n `\\n ${bold(`Search results for: \"${query}\"`)}${modeLabel} ${dim(`(${withSlugs.length} found)`)}\\n`,\n );\n\n for (const result of withSlugs) {\n const projectLabel = result.projectSlug\n ? chalk.cyan(result.projectSlug)\n : chalk.cyan(String(result.projectId));\n\n const tierLabel = tierColor(result.tier);\n const scoreLabel = dim(`score: ${result.score.toFixed(4)}`);\n const locationLabel = dim(`${result.path}:${result.startLine}-${result.endLine}`);\n\n console.log(` ${projectLabel} ${tierLabel} ${locationLabel} ${scoreLabel}`);\n\n const snippetLines = result.snippet\n .split(\"\\n\")\n .slice(0, 3)\n .map((l) => ` ${l}`);\n console.log(snippetLines.join(\"\\n\"));\n console.log();\n }\n },\n );\n}\n","/** Memory status and settings commands for the PAI memory index. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { renderTable, dim, bold, ok, err, warn, fmtDate } from \"../../utils.js\";\nimport { loadConfig, CONFIG_FILE, ensureConfigDir } from \"../../../daemon/config.js\";\n\nfunction tierColor(tier: string): string {\n switch (tier) {\n case \"evergreen\": return chalk.green(tier);\n case \"daily\": return chalk.yellow(tier);\n case \"topic\": return chalk.blue(tier);\n case \"session\": return chalk.dim(tier);\n default: return tier;\n }\n}\n\nexport function registerStatsCommands(\n memoryCmd: Command,\n getDb: () => Database,\n): void {\n\n // -------------------------------------------------------------------------\n // pai memory status [project-slug]\n // -------------------------------------------------------------------------\n\n memoryCmd\n .command(\"status [project-slug]\")\n .description(\"Show memory index statistics\")\n .action((projectSlug: string | undefined) => {\n const registryDb = getDb();\n\n let federation: Database;\n try {\n federation = openFederation();\n } catch (e) {\n console.error(err(`Failed to open federation database: ${e}`));\n process.exit(1);\n }\n\n if (projectSlug) {\n const project = registryDb\n .prepare(\"SELECT id, slug, display_name FROM projects WHERE slug = ?\")\n .get(projectSlug) as\n | { id: number; slug: string; display_name: string }\n | undefined;\n\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const fileStats = federation\n .prepare(\"SELECT COUNT(*) as files FROM memory_files WHERE project_id = ?\")\n .get(project.id) as { files: number };\n\n const chunkStats = federation\n .prepare(\"SELECT COUNT(*) as chunks FROM memory_chunks WHERE project_id = ?\")\n .get(project.id) as { chunks: number };\n\n const lastUpdate = federation\n .prepare(\"SELECT MAX(updated_at) as last_at FROM memory_chunks WHERE project_id = ?\")\n .get(project.id) as { last_at: number | null };\n\n const tierBreakdown = federation\n .prepare(\"SELECT tier, COUNT(*) as n FROM memory_chunks WHERE project_id = ? GROUP BY tier ORDER BY n DESC\")\n .all(project.id) as Array<{ tier: string; n: number }>;\n\n console.log(`\\n ${bold(project.display_name)} ${dim(`(${project.slug})`)}\\n`);\n console.log(` ${bold(\"Files indexed:\")} ${fileStats.files}`);\n console.log(` ${bold(\"Chunks:\")} ${chunkStats.chunks}`);\n console.log(` ${bold(\"Last indexed:\")} ${lastUpdate.last_at ? fmtDate(lastUpdate.last_at) : dim(\"never\")}`);\n\n if (tierBreakdown.length > 0) {\n console.log(`\\n ${bold(\"By tier:\")}`);\n for (const row of tierBreakdown) {\n console.log(` ${tierColor(row.tier).padEnd(20)} ${row.n} chunks`);\n }\n }\n console.log();\n\n } else {\n const globalFiles = federation.prepare(\"SELECT COUNT(*) as n FROM memory_files\").get() as { n: number };\n const globalChunks = federation.prepare(\"SELECT COUNT(*) as n FROM memory_chunks\").get() as { n: number };\n const lastUpdate = federation.prepare(\"SELECT MAX(updated_at) as last_at FROM memory_chunks\").get() as { last_at: number | null };\n\n const projectStats = federation\n .prepare(\n `SELECT\n mf.project_id,\n COUNT(DISTINCT mf.path) as files,\n COUNT(mc.id) as chunks,\n MAX(mc.updated_at) as last_at\n FROM memory_files mf\n LEFT JOIN memory_chunks mc ON mc.project_id = mf.project_id AND mc.path = mf.path\n GROUP BY mf.project_id\n ORDER BY chunks DESC`,\n )\n .all() as Array<{ project_id: number; files: number; chunks: number; last_at: number | null }>;\n\n const projectIds = projectStats.map((p) => p.project_id);\n const slugMap = new Map<number, string>();\n if (projectIds.length > 0) {\n const placeholders = projectIds.map(() => \"?\").join(\", \");\n const slugRows = registryDb\n .prepare(`SELECT id, slug, display_name FROM projects WHERE id IN (${placeholders})`)\n .all(...projectIds) as Array<{ id: number; slug: string; display_name: string }>;\n for (const row of slugRows) {\n slugMap.set(row.id, row.slug);\n }\n }\n\n console.log(`\\n ${bold(\"PAI Memory Index — Global Status\")}\\n`);\n console.log(` ${bold(\"Total files:\")} ${globalFiles.n} ${bold(\"Total chunks:\")} ${globalChunks.n}`);\n console.log(` ${bold(\"Last indexed:\")} ${lastUpdate.last_at ? fmtDate(lastUpdate.last_at) : dim(\"never\")}`);\n\n if (projectStats.length > 0) {\n console.log();\n const rows = projectStats.map((p) => [\n chalk.cyan(slugMap.get(p.project_id) ?? String(p.project_id)),\n String(p.files),\n String(p.chunks),\n p.last_at ? fmtDate(p.last_at) : dim(\"never\"),\n ]);\n console.log(renderTable([\"Project\", \"Files\", \"Chunks\", \"Last Indexed\"], rows));\n } else {\n console.log(dim(\"\\n No projects indexed yet. Run `pai memory index --all` to start.\"));\n }\n console.log();\n }\n });\n\n // -------------------------------------------------------------------------\n // pai memory settings [key] [value]\n // -------------------------------------------------------------------------\n\n memoryCmd\n .command(\"settings [key] [value]\")\n .description(\"View or modify search settings in ~/.config/pai/config.json\")\n .action((key: string | undefined, value: string | undefined) => {\n const config = loadConfig();\n const search = config.search;\n\n if (!key) {\n console.log(`\\n ${bold(\"PAI Memory — Search Settings\")}\\n`);\n console.log(` ${bold(\"mode:\")} ${search.mode}`);\n console.log(` ${bold(\"rerank:\")} ${search.rerank}`);\n console.log(` ${bold(\"recencyBoostDays:\")} ${search.recencyBoostDays}`);\n console.log(` ${bold(\"defaultLimit:\")} ${search.defaultLimit}`);\n console.log(` ${bold(\"snippetLength:\")} ${search.snippetLength}`);\n console.log();\n console.log(dim(` Config file: ${CONFIG_FILE}`));\n console.log(dim(` Edit directly or use: pai memory settings <key> <value>`));\n console.log();\n return;\n }\n\n if (!value) {\n const val = (search as unknown as Record<string, unknown>)[key];\n if (val === undefined) {\n console.error(err(`Unknown setting: ${key}`));\n console.log(dim(` Valid keys: mode, rerank, recencyBoostDays, defaultLimit, snippetLength`));\n process.exit(1);\n }\n console.log(String(val));\n return;\n }\n\n const validKeys = new Set([\"mode\", \"rerank\", \"recencyBoostDays\", \"defaultLimit\", \"snippetLength\"]);\n if (!validKeys.has(key)) {\n console.error(err(`Unknown setting: ${key}`));\n console.log(dim(` Valid keys: ${[...validKeys].join(\", \")}`));\n process.exit(1);\n }\n\n let fileConfig: Record<string, unknown> = {};\n if (existsSync(CONFIG_FILE)) {\n try {\n fileConfig = JSON.parse(readFileSync(CONFIG_FILE, \"utf-8\")) as Record<string, unknown>;\n } catch {\n console.error(err(`Could not parse ${CONFIG_FILE}`));\n process.exit(1);\n }\n }\n\n if (!fileConfig.search || typeof fileConfig.search !== \"object\") {\n fileConfig.search = {};\n }\n\n let parsed: string | number | boolean;\n if (key === \"mode\") {\n if (![\"keyword\", \"semantic\", \"hybrid\"].includes(value)) {\n console.error(err(`Invalid mode: ${value}. Must be keyword, semantic, or hybrid.`));\n process.exit(1);\n }\n parsed = value;\n } else if (key === \"rerank\") {\n parsed = value === \"true\" || value === \"1\" || value === \"on\";\n } else {\n parsed = parseInt(value, 10);\n if (isNaN(parsed)) {\n console.error(err(`Invalid number: ${value}`));\n process.exit(1);\n }\n }\n\n (fileConfig.search as Record<string, unknown>)[key] = parsed;\n\n try {\n ensureConfigDir();\n writeFileSync(CONFIG_FILE, JSON.stringify(fileConfig, null, 2) + \"\\n\", \"utf-8\");\n console.log(ok(`Set search.${key} = ${parsed}`));\n console.log(dim(` Restart daemon to apply: pai daemon restart`));\n } catch (e) {\n console.error(err(`Could not write config: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** Barrel: registers all memory sub-commands and re-exports registerMemoryCommands. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { registerIndexCommand } from \"./index-cmd.js\";\nimport { registerEmbedCommand } from \"./embed.js\";\nimport { registerSearchCommand } from \"./search.js\";\nimport { registerStatsCommands } from \"./stats.js\";\n\nexport function registerMemoryCommands(\n memoryCmd: Command,\n getDb: () => Database,\n): void {\n registerIndexCommand(memoryCmd, getDb);\n registerEmbedCommand(memoryCmd, getDb);\n registerSearchCommand(memoryCmd, getDb);\n registerStatsCommands(memoryCmd, getDb);\n}\n","/**\n * claude-json.ts — safe read/write for ~/.claude.json\n *\n * This file is not ours. It holds every MCP server registration plus Claude\n * Code's own per-project state — on a working machine, hundreds of kilobytes\n * that no one can reconstruct by hand. PAI only ever adds one key to it.\n *\n * The previous implementation (duplicated in mcp.ts and daemon.ts) did:\n *\n * try { return JSON.parse(read(path)); } catch { return {}; }\n *\n * followed by a full-file write. A malformed or transiently unreadable file\n * therefore became an empty object, and the next write replaced the user's\n * entire config with just PAI's entry. No error, no backup, exit code 0.\n *\n * The precondition is unlikely, which is exactly the problem: `pai setup` is\n * advertised as idempotent and safe to re-run, so it is what people run when\n * something is already broken — the state in which the file is most likely to\n * be malformed.\n */\n\nimport { existsSync, readFileSync, writeFileSync, copyFileSync, renameSync, unlinkSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const CLAUDE_JSON_PATH = join(homedir(), \".claude.json\");\nconst BACKUP_PATH = `${CLAUDE_JSON_PATH}.bak-pai`;\n\n/**\n * Read ~/.claude.json.\n *\n * A missing file is a legitimate first-run state and yields {}. Anything else\n * — malformed JSON, permissions, a half-written file — throws. Returning {}\n * for an unreadable file is what turns a read problem into data loss on the\n * next write.\n */\nexport function readClaudeJson(): Record<string, unknown> {\n if (!existsSync(CLAUDE_JSON_PATH)) return {};\n\n let raw: string;\n try {\n raw = readFileSync(CLAUDE_JSON_PATH, \"utf8\");\n } catch (e) {\n throw new Error(\n `Could not read ${CLAUDE_JSON_PATH}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — writing now would replace your MCP registrations with PAI's alone.`\n );\n }\n\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n throw new Error(\n `${CLAUDE_JSON_PATH} is not valid JSON: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — overwriting it would destroy every MCP server registration it holds.\\n` +\n `Fix the file, or move it aside and let Claude Code recreate it, then re-run this command.`\n );\n }\n}\n\n/**\n * Write ~/.claude.json, keeping a backup and never leaving it half-written.\n *\n * Writes to a temp file and renames: rename is atomic within a filesystem, so\n * a crash mid-write leaves the original intact rather than truncated. The\n * previous direct writeFileSync could truncate the file and then fail.\n */\nexport function writeClaudeJson(data: Record<string, unknown>): void {\n const serialized = JSON.stringify(data, null, 2) + \"\\n\";\n\n if (existsSync(CLAUDE_JSON_PATH)) {\n try {\n copyFileSync(CLAUDE_JSON_PATH, BACKUP_PATH);\n } catch (e) {\n throw new Error(\n `Could not back up ${CLAUDE_JSON_PATH} to ${BACKUP_PATH}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to write without a backup.`\n );\n }\n }\n\n const tmp = `${CLAUDE_JSON_PATH}.tmp-pai-${process.pid}`;\n try {\n writeFileSync(tmp, serialized, \"utf8\");\n renameSync(tmp, CLAUDE_JSON_PATH);\n } catch (e) {\n try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* best effort */ }\n throw new Error(\n `Failed to write ${CLAUDE_JSON_PATH}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `The original is unchanged${existsSync(BACKUP_PATH) ? `; a backup is at ${BACKUP_PATH}` : \"\"}.`\n );\n }\n}\n\n/** Where the pre-write backup is kept, for messages to the user. */\nexport function claudeJsonBackupPath(): string {\n return BACKUP_PATH;\n}\n","/**\n * pai mcp <sub-command>\n *\n * install — Register the PAI MCP server in ~/.claude.json\n * status — Show whether the PAI MCP server is registered and the binary exists\n */\n\nimport type { Command } from \"commander\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { readClaudeJson, writeClaudeJson, CLAUDE_JSON_PATH } from \"../../config/claude-json.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\n// CLAUDE_JSON_PATH now imported from src/config/claude-json.ts\n\n/**\n * Resolve the absolute path to the built MCP entry point.\n *\n * tsdown bundles all CLI commands into a single dist/cli/index.mjs file, so\n * import.meta.url always resolves to dist/cli/index.mjs at runtime.\n * From dist/cli/ we go up one level to dist/ and then into mcp/index.mjs.\n */\nfunction getMcpBinPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // dist/cli/index.mjs → dist/ → dist/mcp/index.mjs\n return join(__dirname, \"../mcp/index.mjs\");\n}\n\n// ---------------------------------------------------------------------------\n// Read / write ~/.claude.json safely\n// ---------------------------------------------------------------------------\n\n// Moved to src/config/claude-json.ts — these were duplicated here and in\n// daemon.ts, and both copies silently turned an unreadable file into {},\n// which the following write then made permanent.\n\n// ---------------------------------------------------------------------------\n// install\n// ---------------------------------------------------------------------------\n\nfunction cmdInstall(): void {\n const mcpBin = getMcpBinPath();\n\n const config = readClaudeJson();\n\n // Ensure mcpServers key exists\n if (typeof config.mcpServers !== \"object\" || config.mcpServers === null) {\n config.mcpServers = {};\n }\n\n const servers = config.mcpServers as Record<string, unknown>;\n\n if (\"pai\" in servers) {\n console.log(warn(\"PAI MCP server is already registered in ~/.claude.json.\"));\n console.log(dim(` Entry: ${JSON.stringify(servers[\"pai\"])}`));\n console.log(dim(\" Use `pai mcp status` to verify the configuration.\"));\n return;\n }\n\n servers[\"pai\"] = {\n command: \"node\",\n args: [mcpBin],\n };\n\n try {\n writeClaudeJson(config);\n } catch (e) {\n console.error(err(`Failed to write ~/.claude.json: ${e}`));\n process.exit(1);\n }\n\n console.log(ok(\"PAI MCP server registered in ~/.claude.json.\"));\n console.log(dim(` Binary: ${mcpBin}`));\n console.log(dim(\"\"));\n console.log(dim(\" Restart Claude Code to activate the PAI MCP tools:\"));\n console.log(dim(\" memory_search, memory_get, project_info,\"));\n console.log(dim(\" project_list, session_list, registry_search\"));\n\n if (!existsSync(mcpBin)) {\n console.log();\n console.log(\n warn(` Note: MCP binary not found at ${mcpBin}`)\n );\n console.log(dim(\" Run `bun run build` to compile it first.\"));\n }\n}\n\n// ---------------------------------------------------------------------------\n// status\n// ---------------------------------------------------------------------------\n\nfunction cmdStatus(): void {\n const mcpBin = getMcpBinPath();\n const config = readClaudeJson();\n\n const servers =\n typeof config.mcpServers === \"object\" && config.mcpServers !== null\n ? (config.mcpServers as Record<string, unknown>)\n : {};\n\n const registered = \"pai\" in servers;\n const binExists = existsSync(mcpBin);\n\n console.log();\n console.log(bold(\" PAI MCP Server Status\"));\n console.log();\n\n if (registered) {\n const entry = servers[\"pai\"];\n console.log(ok(` Registered in ~/.claude.json`));\n console.log(dim(` Config: ${JSON.stringify(entry)}`));\n } else {\n console.log(warn(` NOT registered in ~/.claude.json`));\n console.log(dim(` Run: pai mcp install`));\n }\n\n console.log();\n\n if (binExists) {\n console.log(ok(` MCP binary found: ${mcpBin}`));\n } else {\n console.log(warn(` MCP binary NOT found: ${mcpBin}`));\n console.log(dim(\" Run: bun run build\"));\n }\n\n console.log();\n\n if (registered && binExists) {\n console.log(dim(\" Status: READY — restart Claude Code to use PAI tools\"));\n } else if (registered && !binExists) {\n console.log(warn(\" Status: NEEDS BUILD — run `bun run build`\"));\n } else {\n console.log(dim(\" Status: NOT INSTALLED — run `pai mcp install`\"));\n }\n\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerMcpCommands(mcpCmd: Command): void {\n mcpCmd\n .command(\"install\")\n .description(\n \"Register the PAI MCP server in ~/.claude.json (restart Claude Code to activate)\"\n )\n .action(() => {\n cmdInstall();\n });\n\n mcpCmd\n .command(\"status\")\n .description(\n \"Show whether the PAI MCP server is registered and the binary exists\"\n )\n .action(() => {\n cmdStatus();\n });\n}\n","/**\n * pai daemon <sub-command>\n *\n * serve — Start the PAI daemon in the foreground\n * status — Query daemon status via IPC\n * restart — Send SIGTERM to running daemon (launchd will restart it)\n * install — Write launchd plist + update ~/.claude.json to use the shim\n * uninstall — Remove launchd plist + revert ~/.claude.json to direct MCP\n * logs — Tail the daemon log file\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n unlinkSync,\n mkdirSync,\n} from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { execSync, spawnSync } from \"node:child_process\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { PaiClient } from \"../../daemon/ipc-client.js\";\nimport { readClaudeJson, writeClaudeJson, CLAUDE_JSON_PATH } from \"../../config/claude-json.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\n// CLAUDE_JSON_PATH now imported from src/config/claude-json.ts\nconst PLIST_LABEL = \"com.pai.pai-daemon\";\nconst LAUNCH_AGENTS_DIR = join(HOME, \"Library\", \"LaunchAgents\");\nconst PLIST_PATH = join(LAUNCH_AGENTS_DIR, `${PLIST_LABEL}.plist`);\nconst DAEMON_LOG = \"/tmp/pai-daemon.log\";\n\n/**\n * Resolve the absolute path to the built daemon entry point.\n * tsdown bundles into dist/daemon/index.mjs (or similar).\n * From dist/cli/index.mjs → dist/ → dist/daemon/index.mjs\n */\nfunction getDaemonBinPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return join(__dirname, \"../daemon/index.mjs\");\n}\n\n/**\n * Resolve the absolute path to the built MCP shim entry point.\n * dist/cli/index.mjs → dist/ → dist/daemon-mcp/index.mjs\n */\nfunction getShimBinPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return join(__dirname, \"../daemon-mcp/index.mjs\");\n}\n\n// ---------------------------------------------------------------------------\n// claude.json helpers\n// ---------------------------------------------------------------------------\n\n// Moved to src/config/claude-json.ts — see the note there on why returning {}\n// from a failed read was a data-loss bug rather than a convenience.\n\n// ---------------------------------------------------------------------------\n// launchd plist generation\n// ---------------------------------------------------------------------------\n\nfunction generatePlist(daemonBin: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>/usr/local/bin/node</string>\n <string>${daemonBin}</string>\n <string>serve</string>\n </array>\n\n <key>KeepAlive</key>\n <true/>\n\n <key>ThrottleInterval</key>\n <integer>3</integer>\n\n <key>StandardOutPath</key>\n <string>${DAEMON_LOG}</string>\n\n <key>StandardErrorPath</key>\n <string>${DAEMON_LOG}</string>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>EnvironmentVariables</key>\n <dict>\n <key>PATH</key>\n <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>\n </dict>\n</dict>\n</plist>\n`;\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nasync function cmdStatus(): Promise<void> {\n const config = loadConfig();\n const client = new PaiClient(config.socketPath);\n\n try {\n const status = await client.status();\n const s = status as Record<string, unknown>;\n\n console.log();\n console.log(bold(\" PAI Daemon Status\"));\n console.log();\n console.log(ok(` Daemon running`));\n console.log(dim(` Uptime: ${s[\"uptime\"]}s`));\n console.log(dim(` Socket: ${s[\"socketPath\"]}`));\n console.log(\n dim(\n ` Index: ${s[\"indexInProgress\"] ? \"in progress\" : \"idle\"} (interval: ${s[\"indexIntervalSecs\"]}s)`\n )\n );\n if (s[\"lastIndexTime\"]) {\n console.log(dim(` Last index: ${s[\"lastIndexTime\"]}`));\n }\n if (s[\"db\"]) {\n const db = s[\"db\"] as Record<string, unknown>;\n console.log(\n dim(\n ` DB: ${db[\"projects\"]} projects, ${db[\"files\"]} files, ${db[\"chunks\"]} chunks`\n )\n );\n }\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.log();\n console.log(warn(\" PAI Daemon Status\"));\n console.log();\n console.log(warn(` Daemon not running: ${msg}`));\n console.log(dim(\" Start with: pai daemon serve\"));\n console.log(dim(\" Or install as service: pai daemon install\"));\n console.log();\n process.exit(1);\n }\n}\n\nfunction cmdRestart(): void {\n // Find and signal the running daemon\n try {\n const result = spawnSync(\"pgrep\", [\"-f\", \"pai-daemon.*serve\"], {\n encoding: \"utf8\",\n });\n\n if (result.status !== 0 || !result.stdout.trim()) {\n console.log(warn(\"No running pai-daemon process found.\"));\n\n // If launchd is managing it, kick it via launchctl\n const unloadResult = spawnSync(\n \"launchctl\",\n [\"kickstart\", \"-k\", `gui/${process.getuid?.() ?? 501}/${PLIST_LABEL}`],\n { encoding: \"utf8\" }\n );\n if (unloadResult.status === 0) {\n console.log(ok(\"Sent kickstart to launchd.\"));\n } else {\n console.log(dim(\"Not managed by launchd either. Run: pai daemon serve\"));\n }\n return;\n }\n\n const pids = result.stdout\n .trim()\n .split(\"\\n\")\n .map((p) => p.trim());\n for (const pid of pids) {\n try {\n process.kill(parseInt(pid, 10), \"SIGTERM\");\n console.log(ok(`Sent SIGTERM to pid ${pid}.`));\n } catch {\n console.log(warn(`Could not signal pid ${pid}.`));\n }\n }\n console.log(dim(\"launchd will restart the daemon automatically.\"));\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(`restart error: ${msg}`));\n process.exit(1);\n }\n}\n\nfunction cmdInstall(): void {\n const daemonBin = getDaemonBinPath();\n const shimBin = getShimBinPath();\n\n console.log();\n console.log(bold(\" PAI Daemon Install\"));\n console.log();\n\n // 1. Write launchd plist\n if (!existsSync(LAUNCH_AGENTS_DIR)) {\n mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });\n }\n\n const plistContent = generatePlist(daemonBin);\n try {\n writeFileSync(PLIST_PATH, plistContent, \"utf8\");\n console.log(ok(` Wrote launchd plist: ${PLIST_PATH}`));\n } catch (e) {\n console.error(err(` Failed to write plist: ${e}`));\n process.exit(1);\n }\n\n // 2. Load the plist (unload first in case it was already there)\n try {\n spawnSync(\"launchctl\", [\"unload\", PLIST_PATH], { encoding: \"utf8\" });\n const loadResult = spawnSync(\"launchctl\", [\"load\", PLIST_PATH], {\n encoding: \"utf8\",\n });\n if (loadResult.status === 0) {\n console.log(ok(\" Loaded plist with launchctl.\"));\n } else {\n console.log(warn(` launchctl load: ${loadResult.stderr?.trim() ?? \"unknown error\"}`));\n }\n } catch {\n console.log(warn(\" Could not run launchctl. Load manually:\"));\n console.log(dim(` launchctl load ${PLIST_PATH}`));\n }\n\n // 3. Update ~/.claude.json to use the shim\n const config = readClaudeJson();\n\n if (typeof config.mcpServers !== \"object\" || config.mcpServers === null) {\n config.mcpServers = {};\n }\n\n const servers = config.mcpServers as Record<string, unknown>;\n\n // Check if already pointing to the shim\n const existing = servers[\"pai\"] as Record<string, unknown> | undefined;\n const existingArgs = existing?.args as string[] | undefined;\n const alreadyShim = existingArgs?.includes(shimBin);\n\n if (alreadyShim) {\n console.log(ok(\" ~/.claude.json already points to the shim.\"));\n } else {\n // Back up the existing entry if any\n if (existing) {\n servers[\"pai-legacy\"] = existing;\n console.log(dim(\" Backed up existing 'pai' entry as 'pai-legacy'.\"));\n }\n\n servers[\"pai\"] = {\n command: \"node\",\n args: [shimBin],\n };\n\n try {\n writeClaudeJson(config);\n console.log(ok(\" Updated ~/.claude.json to use daemon shim.\"));\n } catch (e) {\n console.error(err(` Failed to write ~/.claude.json: ${e}`));\n process.exit(1);\n }\n }\n\n // 4. Verify binaries exist\n console.log();\n if (!existsSync(daemonBin)) {\n console.log(warn(` Daemon binary not found: ${daemonBin}`));\n console.log(dim(\" Run: bun run build\"));\n } else {\n console.log(ok(` Daemon binary: ${daemonBin}`));\n }\n\n if (!existsSync(shimBin)) {\n console.log(warn(` Shim binary not found: ${shimBin}`));\n console.log(dim(\" Run: bun run build\"));\n } else {\n console.log(ok(` Shim binary: ${shimBin}`));\n }\n\n console.log();\n console.log(dim(\" Restart Claude Code to activate the daemon-backed PAI tools.\"));\n console.log();\n}\n\nfunction cmdUninstall(): void {\n console.log();\n console.log(bold(\" PAI Daemon Uninstall\"));\n console.log();\n\n // 1. Unload and remove plist\n if (existsSync(PLIST_PATH)) {\n try {\n spawnSync(\"launchctl\", [\"unload\", PLIST_PATH], { encoding: \"utf8\" });\n console.log(ok(\" Unloaded launchd plist.\"));\n } catch {\n console.log(warn(\" Could not unload plist via launchctl.\"));\n }\n try {\n unlinkSync(PLIST_PATH);\n console.log(ok(` Removed plist: ${PLIST_PATH}`));\n } catch (e) {\n console.log(warn(` Could not remove plist: ${e}`));\n }\n } else {\n console.log(dim(\" No launchd plist found.\"));\n }\n\n // 2. Revert ~/.claude.json to legacy direct MCP\n const config = readClaudeJson();\n const servers =\n typeof config.mcpServers === \"object\" && config.mcpServers !== null\n ? (config.mcpServers as Record<string, unknown>)\n : {};\n\n const legacy = servers[\"pai-legacy\"];\n if (legacy) {\n servers[\"pai\"] = legacy;\n delete servers[\"pai-legacy\"];\n try {\n writeClaudeJson(config);\n console.log(ok(\" Reverted ~/.claude.json to legacy direct MCP.\"));\n } catch (e) {\n console.log(warn(` Could not update ~/.claude.json: ${e}`));\n }\n } else {\n console.log(dim(\" No legacy PAI entry found in ~/.claude.json.\"));\n }\n\n console.log();\n console.log(dim(\" Restart Claude Code to deactivate daemon-backed tools.\"));\n console.log();\n}\n\nasync function cmdMigrate(connectionString?: string): Promise<void> {\n console.log();\n console.log(bold(\" PAI Daemon Migrate\"));\n console.log();\n console.log(dim(\" Running SQLite → PostgreSQL migration...\"));\n console.log();\n\n // Resolve the migration script path relative to this built file\n const { fileURLToPath } = await import(\"node:url\");\n const { dirname: pathDirname, join: pathJoin } = await import(\"node:path\");\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = pathDirname(__filename);\n\n // The migration script is at docker/migrate-sqlite.ts in the source tree\n // When built, we look for it relative to the package root\n const migrationScript = pathJoin(__dirname, \"../..\", \"docker\", \"migrate-sqlite.ts\");\n const { spawnSync: spawn } = await import(\"node:child_process\");\n\n // Use npx tsx to run the TypeScript migration script (bun doesn't support better-sqlite3)\n const spawnArgs = [\"tsx\", migrationScript];\n if (connectionString) {\n spawnArgs.push(\"--connection-string\", connectionString);\n }\n\n const result = spawn(\"npx\", spawnArgs, { stdio: \"inherit\", encoding: \"utf8\" });\n\n if (result.status !== 0) {\n console.error(err(\" Migration failed. Check output above.\"));\n process.exit(result.status ?? 1);\n }\n}\n\nfunction cmdLogs(opts: { lines?: string; follow?: boolean }): void {\n const lines = opts.lines ?? \"50\";\n\n if (!existsSync(DAEMON_LOG)) {\n console.log(warn(`No daemon log found at ${DAEMON_LOG}.`));\n console.log(dim(\"The daemon may not have run yet.\"));\n return;\n }\n\n if (opts.follow) {\n // exec stays running\n try {\n execSync(`tail -f -n ${lines} \"${DAEMON_LOG}\"`, { stdio: \"inherit\" });\n } catch {\n // User pressed Ctrl+C — that's fine\n }\n } else {\n try {\n execSync(`tail -n ${lines} \"${DAEMON_LOG}\"`, { stdio: \"inherit\" });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(`Could not read log: ${msg}`));\n process.exit(1);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerDaemonCommands(daemonCmd: Command): void {\n daemonCmd\n .command(\"serve\")\n .description(\"Start the PAI daemon in the foreground\")\n .action(async () => {\n const { serve } = await import(\"../../daemon/daemon.js\");\n const { loadConfig: lc, ensureConfigDir } = await import(\n \"../../daemon/config.js\"\n );\n ensureConfigDir();\n const config = lc();\n await serve(config);\n });\n\n daemonCmd\n .command(\"migrate\")\n .description(\"Migrate federation data from SQLite to PostgreSQL\")\n .option(\n \"--connection-string <url>\",\n \"Postgres connection string (default: from config or postgresql://pai:pai@localhost:5432/pai)\"\n )\n .action(async (opts: { connectionString?: string }) => {\n await cmdMigrate(opts.connectionString);\n });\n\n daemonCmd\n .command(\"status\")\n .description(\"Query the running daemon status\")\n .action(async () => {\n await cmdStatus();\n });\n\n daemonCmd\n .command(\"restart\")\n .description(\"Send SIGTERM to the running daemon (launchd will restart it)\")\n .action(() => {\n cmdRestart();\n });\n\n daemonCmd\n .command(\"install\")\n .description(\n \"Install daemon as a launchd service and update ~/.claude.json to use the shim\"\n )\n .action(() => {\n cmdInstall();\n });\n\n daemonCmd\n .command(\"uninstall\")\n .description(\"Remove the launchd service and revert to direct MCP\")\n .action(() => {\n cmdUninstall();\n });\n\n daemonCmd\n .command(\"logs\")\n .description(`Tail the daemon log (${DAEMON_LOG})`)\n .option(\"-n, --lines <n>\", \"Number of lines to show\", \"50\")\n .option(\"-f, --follow\", \"Follow log output (like tail -f)\")\n .action((opts: { lines?: string; follow?: boolean }) => {\n cmdLogs(opts);\n });\n}\n","/**\n * pai backup — snapshot registry, config, and Postgres database.\n *\n * Creates a timestamped backup directory at:\n * ~/.pai/backups/YYYY-MM-DD-HHmmss/\n *\n * Contents:\n * registry.db — SQLite registry database\n * config.json — PAI daemon config\n * postgres-pai.sql — pg_dump of the Postgres \"pai\" database (via docker exec)\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n mkdirSync,\n copyFileSync,\n statSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execSync } from \"node:child_process\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\nconst REGISTRY_DB = join(HOME, \".pai\", \"registry.db\");\nconst CONFIG_FILE = join(HOME, \".config\", \"pai\", \"config.json\");\nconst BACKUPS_DIR = join(HOME, \".pai\", \"backups\");\nconst DOCKER_CONTAINER = \"pai-pgvector\";\nconst PG_DATABASE = \"pai\";\nconst PG_USER = \"pai\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction timestamp(): string {\n const now = new Date();\n const YYYY = now.getFullYear();\n const MM = String(now.getMonth() + 1).padStart(2, \"0\");\n const DD = String(now.getDate()).padStart(2, \"0\");\n const HH = String(now.getHours()).padStart(2, \"0\");\n const mm = String(now.getMinutes()).padStart(2, \"0\");\n const ss = String(now.getSeconds()).padStart(2, \"0\");\n return `${YYYY}-${MM}-${DD}-${HH}${mm}${ss}`;\n}\n\nfunction fmtBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction fileSize(path: string): string {\n try {\n return fmtBytes(statSync(path).size);\n } catch {\n return \"unknown\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerBackupCommands(program: Command): void {\n program\n .command(\"backup\")\n .description(\"Backup registry, config, and Postgres database to ~/.pai/backups/\")\n .option(\"--no-postgres\", \"Skip the Postgres pg_dump (faster, registry+config only)\")\n .action(async (opts: { postgres: boolean }) => {\n const ts = timestamp();\n const backupDir = join(BACKUPS_DIR, ts);\n\n console.log(dim(`Creating backup: ${backupDir}`));\n mkdirSync(backupDir, { recursive: true });\n\n const results: { label: string; path: string; size: string; status: string }[] = [];\n\n // ------------------------------------------------------------------\n // 1. Registry SQLite DB\n // ------------------------------------------------------------------\n\n if (existsSync(REGISTRY_DB)) {\n const dest = join(backupDir, \"registry.db\");\n try {\n copyFileSync(REGISTRY_DB, dest);\n results.push({ label: \"Registry DB\", path: dest, size: fileSize(dest), status: ok(\"ok\") });\n } catch (e) {\n results.push({ label: \"Registry DB\", path: dest, size: \"-\", status: err(`failed: ${e}`) });\n }\n } else {\n results.push({ label: \"Registry DB\", path: REGISTRY_DB, size: \"-\", status: warn(\"not found — skipped\") });\n }\n\n // ------------------------------------------------------------------\n // 2. Config file\n // ------------------------------------------------------------------\n\n if (existsSync(CONFIG_FILE)) {\n const dest = join(backupDir, \"config.json\");\n try {\n copyFileSync(CONFIG_FILE, dest);\n results.push({ label: \"Config\", path: dest, size: fileSize(dest), status: ok(\"ok\") });\n } catch (e) {\n results.push({ label: \"Config\", path: dest, size: \"-\", status: err(`failed: ${e}`) });\n }\n } else {\n results.push({ label: \"Config\", path: CONFIG_FILE, size: \"-\", status: warn(\"not found — skipped\") });\n }\n\n // ------------------------------------------------------------------\n // 3. Optional: Federation SQLite (legacy)\n // ------------------------------------------------------------------\n\n const federationDb = join(HOME, \".pai\", \"federation.db\");\n if (existsSync(federationDb)) {\n const dest = join(backupDir, \"federation.db\");\n try {\n copyFileSync(federationDb, dest);\n results.push({ label: \"Federation DB (legacy)\", path: dest, size: fileSize(dest), status: ok(\"ok\") });\n } catch (e) {\n results.push({ label: \"Federation DB (legacy)\", path: dest, size: \"-\", status: warn(`skipped: ${e}`) });\n }\n }\n\n // ------------------------------------------------------------------\n // 4. Postgres pg_dump via docker exec\n // ------------------------------------------------------------------\n\n if (opts.postgres) {\n const sqlDest = join(backupDir, \"postgres-pai.sql\");\n console.log(dim(` Running pg_dump on ${DOCKER_CONTAINER} (this may take a moment)...`));\n try {\n // Check Docker is running and container exists\n execSync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Status}}'`, {\n stdio: \"pipe\",\n });\n\n execSync(\n `docker exec ${DOCKER_CONTAINER} pg_dump -U ${PG_USER} ${PG_DATABASE} > \"${sqlDest}\"`,\n { stdio: [\"pipe\", \"pipe\", \"pipe\"], shell: true as unknown as string }\n );\n results.push({ label: \"Postgres DB\", path: sqlDest, size: fileSize(sqlDest), status: ok(\"ok\") });\n } catch (e) {\n const msg = e instanceof Error ? e.message.split(\"\\n\")[0] : String(e);\n results.push({ label: \"Postgres DB\", path: sqlDest, size: \"-\", status: err(`failed: ${msg}`) });\n console.log(warn(` Postgres backup failed. Is Docker running with container '${DOCKER_CONTAINER}'?`));\n }\n } else {\n results.push({ label: \"Postgres DB\", path: \"-\", size: \"-\", status: dim(\"skipped (--no-postgres)\") });\n }\n\n // ------------------------------------------------------------------\n // Summary\n // ------------------------------------------------------------------\n\n console.log(`\\n${bold(\"Backup complete:\")} ${backupDir}\\n`);\n\n const labelWidth = Math.max(...results.map((r) => r.label.length)) + 2;\n for (const r of results) {\n const label = r.label.padEnd(labelWidth);\n console.log(` ${bold(label)} ${r.status} ${dim(r.size)}`);\n }\n\n console.log(`\\n ${dim(\"Path:\")} ${backupDir}`);\n console.log(` ${dim(\"To restore:\")} pai restore ${backupDir}\\n`);\n });\n}\n","/**\n * pai restore [path] — restore from a backup created by `pai backup`.\n *\n * If no path is given, lists available backups and restores the latest one\n * after prompting for confirmation.\n *\n * Restores:\n * registry.db — SQLite registry database\n * config.json — PAI daemon config\n * postgres-pai.sql — Postgres dump (piped into psql via docker exec)\n * federation.db — Legacy SQLite federation DB (if present)\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n readdirSync,\n statSync,\n copyFileSync,\n mkdirSync,\n readFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execSync, spawnSync } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\nconst REGISTRY_DB = join(HOME, \".pai\", \"registry.db\");\nconst CONFIG_FILE = join(HOME, \".config\", \"pai\", \"config.json\");\nconst BACKUPS_DIR = join(HOME, \".pai\", \"backups\");\nconst DOCKER_CONTAINER = \"pai-pgvector\";\nconst PG_DATABASE = \"pai\";\nconst PG_USER = \"pai\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction confirm(question: string): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(`${question} [y/N] `, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === \"y\");\n });\n });\n}\n\nfunction listBackups(): string[] {\n if (!existsSync(BACKUPS_DIR)) return [];\n return readdirSync(BACKUPS_DIR)\n .filter((name) => {\n const full = join(BACKUPS_DIR, name);\n return statSync(full).isDirectory() && /^\\d{4}-\\d{2}-\\d{2}-\\d{6}$/.test(name);\n })\n .sort()\n .reverse(); // newest first\n}\n\nfunction formatBackupDate(name: string): string {\n // YYYY-MM-DD-HHmmss → \"YYYY-MM-DD HH:mm:ss\"\n const m = name.match(/^(\\d{4}-\\d{2}-\\d{2})-(\\d{2})(\\d{2})(\\d{2})$/);\n if (!m) return name;\n return `${m[1]} ${m[2]}:${m[3]}:${m[4]}`;\n}\n\nfunction backupContents(backupDir: string): string[] {\n try {\n return readdirSync(backupDir);\n } catch {\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerRestoreCommands(program: Command): void {\n program\n .command(\"restore [backup-path]\")\n .description(\"Restore from a backup directory (created by pai backup)\")\n .option(\"--no-postgres\", \"Skip restoring the Postgres database\")\n .option(\"--yes\", \"Skip confirmation prompt (non-interactive)\")\n .action(async (backupPath: string | undefined, opts: { postgres: boolean; yes: boolean }) => {\n\n // ------------------------------------------------------------------\n // 1. Determine backup directory\n // ------------------------------------------------------------------\n\n let resolvedDir: string;\n\n if (backupPath) {\n resolvedDir = backupPath.startsWith(\"~\")\n ? backupPath.replace(/^~/, HOME)\n : backupPath;\n\n if (!existsSync(resolvedDir)) {\n console.error(err(`Backup directory not found: ${resolvedDir}`));\n process.exit(1);\n }\n } else {\n // No path given — pick from available backups\n const backups = listBackups();\n if (backups.length === 0) {\n console.error(err(`No backups found in ${BACKUPS_DIR}`));\n console.log(dim(\" Run 'pai backup' to create one.\"));\n process.exit(1);\n }\n\n console.log(`\\n${bold(\"Available backups\")} (newest first):\\n`);\n backups.forEach((name, i) => {\n const dir = join(BACKUPS_DIR, name);\n const contents = backupContents(dir).join(\", \");\n const marker = i === 0 ? ok(\" ← latest\") : \"\";\n console.log(` ${bold(String(i + 1).padStart(2))}. ${formatBackupDate(name)}${marker}`);\n console.log(` ${dim(dir)}`);\n console.log(` ${dim(\"Contents:\")} ${contents}`);\n });\n\n console.log();\n resolvedDir = join(BACKUPS_DIR, backups[0]);\n console.log(dim(`Using latest backup: ${resolvedDir}\\n`));\n }\n\n // ------------------------------------------------------------------\n // 2. Inventory what's in the backup\n // ------------------------------------------------------------------\n\n const hasRegistry = existsSync(join(resolvedDir, \"registry.db\"));\n const hasConfig = existsSync(join(resolvedDir, \"config.json\"));\n const hasSql = existsSync(join(resolvedDir, \"postgres-pai.sql\"));\n const hasFed = existsSync(join(resolvedDir, \"federation.db\"));\n\n console.log(`${bold(\"Backup contents:\")}`);\n console.log(` registry.db ${hasRegistry ? ok(\"present\") : warn(\"missing\")}`);\n console.log(` config.json ${hasConfig ? ok(\"present\") : warn(\"missing\")}`);\n console.log(` postgres-pai.sql ${hasSql && opts.postgres ? ok(\"present\") : hasSql ? warn(\"present (skipped via --no-postgres)\") : warn(\"missing\")}`);\n if (hasFed) {\n console.log(` federation.db ${ok(\"present\")} ${dim(\"(legacy)\")}`);\n }\n\n // ------------------------------------------------------------------\n // 3. Confirm\n // ------------------------------------------------------------------\n\n console.log(`\\n${warn(\"WARNING:\")} This will OVERWRITE your current PAI data.`);\n if (hasSql && opts.postgres) {\n console.log(warn(\" Postgres database will be dropped and recreated from the backup.\"));\n }\n\n const proceed = opts.yes || await confirm(\"Proceed with restore?\");\n if (!proceed) {\n console.log(dim(\"Restore cancelled.\"));\n process.exit(0);\n }\n\n console.log();\n\n const results: { label: string; status: string }[] = [];\n\n // ------------------------------------------------------------------\n // 4. Restore registry.db\n // ------------------------------------------------------------------\n\n if (hasRegistry) {\n try {\n mkdirSync(join(HOME, \".pai\"), { recursive: true });\n copyFileSync(join(resolvedDir, \"registry.db\"), REGISTRY_DB);\n results.push({ label: \"Registry DB\", status: ok(\"restored\") });\n } catch (e) {\n results.push({ label: \"Registry DB\", status: err(`failed: ${e}`) });\n }\n } else {\n results.push({ label: \"Registry DB\", status: warn(\"missing in backup — skipped\") });\n }\n\n // ------------------------------------------------------------------\n // 5. Restore config.json\n // ------------------------------------------------------------------\n\n if (hasConfig) {\n try {\n mkdirSync(join(HOME, \".config\", \"pai\"), { recursive: true });\n copyFileSync(join(resolvedDir, \"config.json\"), CONFIG_FILE);\n results.push({ label: \"Config\", status: ok(\"restored\") });\n } catch (e) {\n results.push({ label: \"Config\", status: err(`failed: ${e}`) });\n }\n } else {\n results.push({ label: \"Config\", status: warn(\"missing in backup — skipped\") });\n }\n\n // ------------------------------------------------------------------\n // 6. Restore federation.db (legacy)\n // ------------------------------------------------------------------\n\n if (hasFed) {\n try {\n copyFileSync(join(resolvedDir, \"federation.db\"), join(HOME, \".pai\", \"federation.db\"));\n results.push({ label: \"Federation DB (legacy)\", status: ok(\"restored\") });\n } catch (e) {\n results.push({ label: \"Federation DB (legacy)\", status: warn(`skipped: ${e}`) });\n }\n }\n\n // ------------------------------------------------------------------\n // 7. Restore Postgres via docker exec\n // ------------------------------------------------------------------\n\n if (hasSql && opts.postgres) {\n console.log(dim(\" Restoring Postgres database (this may take a while)...\"));\n try {\n // Verify container is running\n execSync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Status}}'`, {\n stdio: \"pipe\",\n });\n\n // Drop and recreate the database, then restore\n const dropCreate = `docker exec ${DOCKER_CONTAINER} psql -U ${PG_USER} -c \"DROP DATABASE IF EXISTS ${PG_DATABASE}; CREATE DATABASE ${PG_DATABASE} OWNER ${PG_USER};\"`;\n execSync(dropCreate, { stdio: \"pipe\", shell: true as unknown as string });\n\n // Pipe the SQL dump into psql\n const sqlContent = readFileSync(join(resolvedDir, \"postgres-pai.sql\"), \"utf8\");\n const psqlResult = spawnSync(\n \"docker\",\n [\"exec\", \"-i\", DOCKER_CONTAINER, \"psql\", \"-U\", PG_USER, PG_DATABASE],\n { input: sqlContent, encoding: \"utf8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }\n );\n\n if (psqlResult.status !== 0) {\n const errMsg = psqlResult.stderr?.split(\"\\n\")[0] ?? \"unknown error\";\n results.push({ label: \"Postgres DB\", status: err(`failed: ${errMsg}`) });\n } else {\n results.push({ label: \"Postgres DB\", status: ok(\"restored\") });\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message.split(\"\\n\")[0] : String(e);\n results.push({ label: \"Postgres DB\", status: err(`failed: ${msg}`) });\n console.log(warn(` Is Docker running with container '${DOCKER_CONTAINER}'?`));\n }\n } else if (!hasSql || !opts.postgres) {\n const reason = !hasSql ? \"no SQL dump in backup\" : \"--no-postgres\";\n results.push({ label: \"Postgres DB\", status: dim(`skipped (${reason})`) });\n }\n\n // ------------------------------------------------------------------\n // Summary\n // ------------------------------------------------------------------\n\n console.log(`\\n${bold(\"Restore complete:\")}\\n`);\n const labelWidth = Math.max(...results.map((r) => r.label.length)) + 2;\n for (const r of results) {\n console.log(` ${bold(r.label.padEnd(labelWidth))} ${r.status}`);\n }\n\n const hasErrors = results.some((r) => r.status.includes(\"\\u001b[31m\"));\n if (!hasErrors) {\n console.log(`\\n ${ok(\"All done.\")} You may need to restart the PAI daemon.\\n`);\n console.log(` ${dim(\"Restart:\")} pai daemon restart\\n`);\n } else {\n console.log(`\\n ${warn(\"Some items failed — check output above.\")}\\n`);\n process.exit(1);\n }\n });\n}\n","/**\n * json-store.ts — read/write JSON config files without destroying them\n *\n * The failure this exists to prevent:\n *\n * try { return JSON.parse(read(path)); } catch { return {}; }\n * ... later ...\n * write(path, JSON.stringify(ourData));\n *\n * An unreadable file becomes an empty object, and the next write makes that\n * permanent. Silently, exit code 0. This shape appeared three times in this\n * repo against three different files, and twice in AIBroker.\n *\n * The distinction that matters is between *missing* and *unreadable*:\n *\n * missing — legitimate first run. Start fresh; writing is safe.\n * unreadable — the file exists and we could not parse it. Those bytes are\n * the only copy of something. Never overwrite them.\n *\n * Collapsing the second into the first is the bug.\n *\n * NOT everything deserves this guard. For a transient buffer — an undelivered\n * message queue, a cache — starting fresh IS the correct recovery, and\n * refusing to write would disable the feature permanently. Use `writeJsonAtomic`\n * alone there: it still prevents a crash from truncating a good file, without\n * blocking recovery. Reserve `readJsonStrict` for data a user cannot rebuild.\n */\n\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n copyFileSync,\n renameSync,\n unlinkSync,\n mkdirSync,\n} from \"node:fs\";\nimport { dirname } from \"node:path\";\n\n/**\n * Read a JSON file, distinguishing \"absent\" from \"damaged\".\n *\n * @param path file to read\n * @param label how to name it to the user, e.g. \"~/.claude.json\"\n * @throws if the file exists but cannot be read or parsed\n */\nexport function readJsonStrict(path: string, label = path): Record<string, unknown> {\n if (!existsSync(path)) return {};\n\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch (e) {\n throw new Error(\n `Could not read ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — writing now would replace its contents with ours alone.`\n );\n }\n\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n throw new Error(\n `${label} exists but is not valid JSON: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — overwriting it would destroy whatever it holds.\\n` +\n `Repair the file, or move it aside and re-run this command.`\n );\n }\n}\n\n/**\n * Write JSON without risking the existing file.\n *\n * Keeps a `.bak-pai` copy of the previous contents, then writes to a temp file\n * and renames. Rename is atomic within a filesystem, so a crash mid-write\n * leaves the original intact rather than truncated — which is how these files\n * become corrupt in the first place.\n */\nexport function writeJsonAtomic(\n path: string,\n data: Record<string, unknown>,\n opts: { backup?: boolean; label?: string } = {}\n): void {\n const { backup = true, label = path } = opts;\n const serialized = JSON.stringify(data, null, 2) + \"\\n\";\n\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n if (backup && existsSync(path)) {\n try {\n copyFileSync(path, `${path}.bak-pai`);\n } catch (e) {\n throw new Error(\n `Could not back up ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to write without a backup.`\n );\n }\n }\n\n const tmp = `${path}.tmp-pai-${process.pid}`;\n try {\n writeFileSync(tmp, serialized, \"utf8\");\n renameSync(tmp, path);\n } catch (e) {\n try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* best effort */ }\n throw new Error(\n `Failed to write ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `The original is unchanged.`\n );\n }\n}\n","/**\n * Shared helpers for the PAI setup wizard: chalk colour shortcuts,\n * readline prompts, config read/write, and filesystem path finders.\n */\n\nimport { createInterface } from \"node:readline\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readJsonStrict, writeJsonAtomic } from \"../../../config/json-store.js\";\nimport { spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { CONFIG_DIR, CONFIG_FILE } from \"../../../daemon/config.js\";\n\n// ---------------------------------------------------------------------------\n// Chalk colour helpers\n// ---------------------------------------------------------------------------\n\nexport const c = {\n bold: (s: string) => chalk.bold(s),\n dim: (s: string) => chalk.dim(s),\n green: (s: string) => chalk.green(s),\n yellow: (s: string) => chalk.yellow(s),\n cyan: (s: string) => chalk.cyan(s),\n red: (s: string) => chalk.red(s),\n blue: (s: string) => chalk.blue(s),\n ok: (s: string) => chalk.green(\" \" + s),\n warn: (s: string) => chalk.yellow(\" \" + s),\n err: (s: string) => chalk.red(\" \" + s),\n};\n\nexport function line(text = \"\"): void {\n console.log(text);\n}\n\nexport function section(title: string): void {\n line();\n console.log(chalk.bold.cyan(\" \" + title));\n console.log(chalk.dim(\" \" + \"─\".repeat(title.length)));\n}\n\n// ---------------------------------------------------------------------------\n// Readline prompt helpers\n// ---------------------------------------------------------------------------\n\nexport function createRl() {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.on(\"SIGINT\", () => {\n line();\n line(c.dim(\" Setup cancelled. Run `pai setup` again to restart.\"));\n line();\n process.exit(0);\n });\n\n return rl;\n}\n\nexport type Rl = ReturnType<typeof createRl>;\n\nexport async function prompt(rl: Rl, question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n resolve(answer.trim());\n });\n });\n}\n\n/** Prompt for a numbered menu selection. Returns 0-based index. */\nexport async function promptMenu(\n rl: Rl,\n options: Array<{ label: string; description?: string }>,\n defaultIdx = 0,\n): Promise<number> {\n for (let i = 0; i < options.length; i++) {\n const num = chalk.bold(` ${i + 1}.`);\n const label = i === defaultIdx ? chalk.cyan(options[i].label) : options[i].label;\n const marker = i === defaultIdx ? chalk.dim(\" (recommended)\") : \"\";\n console.log(`${num} ${label}${marker}`);\n if (options[i].description) {\n console.log(chalk.dim(` ${options[i].description}`));\n }\n }\n line();\n\n while (true) {\n const answer = await prompt(\n rl,\n chalk.bold(` Enter number [1-${options.length}] (default: ${defaultIdx + 1}): `),\n );\n\n if (answer === \"\") return defaultIdx;\n\n const n = parseInt(answer, 10);\n if (!isNaN(n) && n >= 1 && n <= options.length) {\n return n - 1;\n }\n\n console.log(c.warn(`Please enter a number between 1 and ${options.length}.`));\n }\n}\n\n/** Prompt for a yes/no answer. Returns true for yes. */\nexport async function promptYesNo(\n rl: Rl,\n question: string,\n defaultYes = true,\n): Promise<boolean> {\n const hint = defaultYes ? \"[Y/n]\" : \"[y/N]\";\n const answer = await prompt(rl, ` ${question} ${chalk.dim(hint)}: `);\n\n if (answer === \"\") return defaultYes;\n return answer.toLowerCase().startsWith(\"y\");\n}\n\n// ---------------------------------------------------------------------------\n// Config read/write helpers\n// ---------------------------------------------------------------------------\n\n/**\n * This file holds the Postgres connection string, the storage backend choice,\n * notification routing and any tracker API token — none of which the user can\n * reconstruct from memory. It previously returned {} on a parse failure and\n * then overwrote the file, so a damaged config was replaced by whatever the\n * current command happened to be setting.\n */\nexport function readConfigRaw(): Record<string, unknown> {\n return readJsonStrict(CONFIG_FILE, \"~/.config/pai/config.json\");\n}\n\nexport function writeConfigRaw(data: Record<string, unknown>): void {\n writeJsonAtomic(CONFIG_FILE, data, { label: \"~/.config/pai/config.json\" });\n}\n\nexport function mergeConfig(updates: Record<string, unknown>): void {\n const current = readConfigRaw();\n const merged = { ...current, ...updates };\n if (updates.postgres && typeof current.postgres === \"object\" && current.postgres !== null) {\n merged.postgres = { ...(current.postgres as object), ...(updates.postgres as object) };\n }\n writeConfigRaw(merged);\n}\n\n// ---------------------------------------------------------------------------\n// Docker and connection helpers\n// ---------------------------------------------------------------------------\n\nexport function hasDocker(): boolean {\n try {\n const result = spawnSync(\"docker\", [\"--version\"], { stdio: \"pipe\" });\n return result.status === 0;\n } catch {\n return false;\n }\n}\n\nexport function getDockerDir(): string {\n const candidates = [\n join(process.cwd(), \"docker\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"docker\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"docker\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"docker-compose.yml\"))) return candidate;\n }\n return join(process.cwd(), \"docker\");\n}\n\nexport async function testPostgresConnection(connectionString: string): Promise<boolean> {\n try {\n const pgModule = await import(\"pg\");\n const pg = pgModule.default ?? pgModule;\n const client = new pg.Client({ connectionString });\n await client.connect();\n await client.end();\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Filesystem path finders\n// ---------------------------------------------------------------------------\n\nexport function getTemplatesDir(): string {\n const candidates = [\n join(process.cwd(), \"templates\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"templates\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"templates\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"claude-md.template.md\"))) return candidate;\n }\n return join(process.cwd(), \"templates\");\n}\n\nexport function getHooksDir(): string {\n const candidates = [\n join(process.cwd(), \"src\", \"hooks\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"src\", \"hooks\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"src\", \"hooks\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"session-stop.sh\"))) return candidate;\n }\n return join(process.cwd(), \"src\", \"hooks\");\n}\n\nexport function getDistHooksDir(): string {\n const moduleDir = new URL(\".\", import.meta.url).pathname;\n const fromModule = join(moduleDir, \"..\", \"..\", \"hooks\");\n\n const candidates = [\n fromModule,\n join(process.cwd(), \"dist\", \"hooks\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"dist\", \"hooks\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"dist\", \"hooks\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"stop-hook.mjs\"))) return candidate;\n }\n return fromModule;\n}\n\nexport function getDistDir(): string {\n const moduleDir = new URL(\".\", import.meta.url).pathname;\n const fromModule = join(moduleDir, \"..\", \"..\", \"..\");\n\n const candidates = [\n fromModule,\n join(process.cwd(), \"dist\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"dist\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"dist\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"skills\"))) return candidate;\n }\n return fromModule;\n}\n\nexport function getStatuslineScript(): string | null {\n const candidates = [\n join(process.cwd(), \"statusline-command.sh\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"statusline-command.sh\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"statusline-command.sh\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n return null;\n}\n\nexport function getTabColorScript(): string | null {\n const candidates = [\n join(process.cwd(), \"tab-color-command.sh\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"tab-color-command.sh\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"tab-color-command.sh\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n return null;\n}\n","/** Step 1: Welcome banner displayed at the start of the setup wizard. */\n\nimport chalk from \"chalk\";\nimport { c, line } from \"../utils.js\";\n\nexport function stepWelcome(): void {\n line();\n line(chalk.bold.cyan(\" ╔════════════════════════════════════════╗\"));\n line(chalk.bold.cyan(\" ║ PAI Knowledge OS — Setup Wizard ║\"));\n line(chalk.bold.cyan(\" ╚════════════════════════════════════════╝\"));\n line();\n line(\" PAI is a personal knowledge system that indexes your files, generates\");\n line(\" semantic embeddings for intelligent search, and stores everything in a\");\n line(\" local database so you can search your knowledge base with natural language.\");\n line();\n line(c.dim(\" This wizard will guide you through the initial configuration.\"));\n line(c.dim(\" Press Ctrl+C at any time to cancel.\"));\n}\n","/** Step 2: Storage backend selection (SQLite or PostgreSQL) and Docker helper. */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { c, line, section, type Rl, prompt, promptMenu, promptYesNo, readConfigRaw, getDockerDir, hasDocker, testPostgresConnection } from \"../utils.js\";\n\nasync function startDocker(rl: Rl): Promise<boolean> {\n const dockerDir = getDockerDir();\n const composePath = join(dockerDir, \"docker-compose.yml\");\n\n if (!existsSync(composePath)) {\n console.log(c.warn(`docker-compose.yml not found at ${dockerDir}`));\n console.log(c.dim(\" You can start it manually later:\"));\n console.log(c.dim(\" docker compose up -d\"));\n return false;\n }\n\n console.log(c.dim(` Starting PostgreSQL container from ${dockerDir}...`));\n\n try {\n const result = spawnSync(\"docker\", [\"compose\", \"up\", \"-d\"], {\n cwd: dockerDir,\n stdio: \"inherit\",\n });\n\n if (result.status !== 0) {\n console.log(c.warn(\" Docker compose failed. You can start it manually:\"));\n console.log(c.dim(` cd ${dockerDir} && docker compose up -d`));\n return false;\n }\n\n console.log(c.ok(\"PostgreSQL container started.\"));\n return true;\n } catch (e) {\n console.log(c.warn(` Could not run docker compose: ${e}`));\n return false;\n }\n}\n\nexport async function stepStorage(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 2: Storage Backend\");\n\n const existing = readConfigRaw();\n if (existing.storageBackend) {\n const backend = String(existing.storageBackend);\n if (backend === \"postgres\") {\n try {\n const result = spawnSync(\"docker\", [\"ps\", \"--filter\", \"name=pai-pgvector\", \"--format\", \"{{.Status}}\"], { stdio: \"pipe\" });\n const status = result.stdout?.toString().trim();\n if (status && status.includes(\"Up\")) {\n console.log(c.ok(`Storage backend: PostgreSQL (container running). Skipping.`));\n return existing;\n }\n console.log(c.dim(\" PostgreSQL container found but not running. Starting...\"));\n await startDocker(rl);\n await new Promise((r) => setTimeout(r, 3000));\n console.log(c.ok(\"PostgreSQL container started.\"));\n } catch {\n console.log(c.ok(`Storage backend: PostgreSQL (configured). Skipping.`));\n }\n return existing;\n } else {\n console.log(c.ok(`Storage backend: ${backend}. Skipping.`));\n return existing;\n }\n }\n\n line();\n line(\" Choose how PAI stores your indexed knowledge:\");\n line();\n\n const choice = await promptMenu(rl, [\n {\n label: \"PostgreSQL with pgvector\",\n description: \"Best for large collections, semantic search, and production use. Requires Docker or a Postgres server.\",\n },\n {\n label: \"SQLite\",\n description: \"Simple, no dependencies, zero configuration. Good for trying PAI out. Keyword search only.\",\n },\n ]);\n\n if (choice === 1) {\n line();\n console.log(c.ok(\"SQLite selected. No additional setup needed.\"));\n return { storageBackend: \"sqlite\" };\n }\n\n // PostgreSQL\n line();\n line(\" PostgreSQL requires a running Postgres server with the pgvector extension.\");\n line();\n\n if (hasDocker()) {\n console.log(c.ok(\"Docker is installed.\"));\n line();\n\n const useDocker = await promptYesNo(rl, \"Start the PAI PostgreSQL container with Docker? (recommended)\", true);\n\n if (useDocker) {\n line();\n await startDocker(rl);\n line();\n\n console.log(c.dim(\" Waiting 3 seconds for container to be ready...\"));\n await new Promise((r) => setTimeout(r, 3000));\n\n const connStr = \"postgresql://pai:pai@localhost:5432/pai\";\n console.log(c.dim(` Testing connection to ${connStr}...`));\n\n const ok2 = await testPostgresConnection(connStr);\n if (ok2) {\n console.log(c.ok(\"Connection successful!\"));\n } else {\n console.log(c.warn(\"Connection test failed. The container may still be starting.\"));\n console.log(c.dim(\" Using default connection string — you can verify with `pai daemon status`.\"));\n }\n return { storageBackend: \"postgres\", postgres: { connectionString: connStr } };\n }\n } else {\n console.log(c.dim(\" Docker not found. Using manual connection string entry.\"));\n }\n\n // Manual entry\n line();\n line(\" Enter your PostgreSQL connection details:\");\n line();\n\n const useConnStr = await promptYesNo(rl, \"Use a full connection string? (e.g. postgresql://user:pass@host:5432/dbname)\", true);\n\n if (useConnStr) {\n const connStr = await prompt(rl, chalk.bold(\" Connection string: \"));\n if (connStr) {\n console.log(c.dim(\" Testing connection...\"));\n const connected = await testPostgresConnection(connStr);\n if (connected) {\n console.log(c.ok(\"Connection successful!\"));\n } else {\n console.log(c.warn(\"Connection test failed — check credentials and try again later.\"));\n }\n return { storageBackend: \"postgres\", postgres: { connectionString: connStr } };\n }\n }\n\n const host = await prompt(rl, chalk.bold(\" Host [localhost]: \")) || \"localhost\";\n const portStr = await prompt(rl, chalk.bold(\" Port [5432]: \")) || \"5432\";\n const database = await prompt(rl, chalk.bold(\" Database [pai]: \")) || \"pai\";\n const user = await prompt(rl, chalk.bold(\" User [pai]: \")) || \"pai\";\n const password = await prompt(rl, chalk.bold(\" Password [pai]: \")) || \"pai\";\n\n const connStr = `postgresql://${user}:${password}@${host}:${portStr}/${database}`;\n console.log(c.dim(` Connection string: ${connStr}`));\n console.log(c.dim(\" Testing connection...\"));\n\n const connected = await testPostgresConnection(connStr);\n if (connected) {\n console.log(c.ok(\"Connection successful!\"));\n } else {\n console.log(c.warn(\"Connection test failed — check credentials and try again later.\"));\n }\n\n return { storageBackend: \"postgres\", postgres: { connectionString: connStr } };\n}\n","/** Step 3: Embedding model selection for semantic search. */\n\nimport { c, line, section, type Rl, promptMenu, readConfigRaw } from \"../utils.js\";\n\nexport async function stepEmbedding(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 3: Embedding Model\");\n\n const existing = readConfigRaw();\n if (existing.embeddingModel) {\n console.log(c.ok(`Embedding model: ${existing.embeddingModel}. Skipping.`));\n return { embeddingModel: existing.embeddingModel };\n }\n\n line();\n line(\" An embedding model converts your text into vectors for semantic search.\");\n line(\" Models are downloaded from HuggingFace on first use.\");\n line();\n\n const choice = await promptMenu(rl, [\n {\n label: \"Snowflake Arctic Embed m v1.5\",\n description: \"768 dims, ~118MB download. Best retrieval quality per MB (MTEB score 55.14). Asymmetric retrieval — different handling for queries vs documents. Best for most users.\",\n },\n {\n label: \"BGE Small EN v1.5\",\n description: \"384 dims, ~32MB download. Lightweight and fast. Good for limited disk space or when faster embedding is more important than maximum quality. English only.\",\n },\n {\n label: \"Nomic Embed Text v1.5\",\n description: \"768 dims, ~100MB download. 8K token context window — excellent for long documents. Matryoshka dimensions (can truncate for speed/size tradeoffs).\",\n },\n {\n label: \"None — skip embeddings\",\n description: \"BM25/keyword search only. No model download needed. You can add embeddings later by running `pai memory embed` after selecting a model.\",\n },\n ]);\n\n const models: Record<number, string | null> = {\n 0: \"Snowflake/snowflake-arctic-embed-m-v1.5\",\n 1: \"BAAI/bge-small-en-v1.5\",\n 2: \"nomic-ai/nomic-embed-text-v1.5\",\n 3: null,\n };\n\n const selectedModel = models[choice];\n\n line();\n if (selectedModel) {\n console.log(c.ok(`Model selected: ${selectedModel}`));\n console.log(c.dim(\" The model will be downloaded on first use of `pai memory embed`.\"));\n } else {\n console.log(c.ok(\"Skipping embeddings. Keyword search will still work.\"));\n console.log(c.dim(\" Add later: update embeddingModel in ~/.config/pai/config.json\"));\n }\n\n return { embeddingModel: selectedModel ?? \"none\" };\n}\n","/** Step 4: CLAUDE.md generation from PAI template. */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { CONFIG_DIR } from \"../../../../daemon/config.js\";\nimport { c, line, section, type Rl, promptYesNo, getTemplatesDir } from \"../utils.js\";\n\nexport async function stepClaudeMd(rl: Rl): Promise<boolean> {\n section(\"Step 4: Agent Configuration (CLAUDE.md)\");\n line();\n line(\" PAI ships a CLAUDE.md template with agent orchestration patterns:\");\n line(\" swarm mode, model escalation, parallel execution, quality standards.\");\n line();\n\n const claudeDir = join(homedir(), \".claude\");\n const claudeMd = join(claudeDir, \"CLAUDE.md\");\n const agentPrefs = join(CONFIG_DIR, \"agent-prefs.md\");\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"claude-md.template.md\");\n\n if (!existsSync(templatePath)) {\n console.log(c.warn(\"Template not found: \" + templatePath));\n console.log(c.dim(\" Skipping CLAUDE.md generation. You can copy it manually from templates/.\"));\n return false;\n }\n\n if (existsSync(claudeMd)) {\n const content = readFileSync(claudeMd, \"utf-8\");\n const isGenerated = content.includes(\"Generated by PAI Setup\");\n if (isGenerated) {\n console.log(c.dim(\" Found existing PAI-generated CLAUDE.md.\"));\n } else {\n console.log(c.yellow(\" Found existing CLAUDE.md (custom, not PAI-generated).\"));\n console.log(c.dim(\" A backup will be created before overwriting.\"));\n }\n line();\n\n const overwrite = await promptYesNo(rl, \"Update ~/.claude/CLAUDE.md with the latest PAI template?\", isGenerated);\n if (!overwrite) {\n console.log(c.dim(\" Keeping existing CLAUDE.md unchanged.\"));\n return false;\n }\n\n if (!isGenerated) {\n const backupPath = claudeMd + \".backup\";\n writeFileSync(backupPath, content, \"utf-8\");\n console.log(c.ok(`Backed up existing CLAUDE.md to ${backupPath}`));\n }\n }\n\n let template = readFileSync(templatePath, \"utf-8\");\n template = template.replace(/\\$\\{HOME\\}/g, homedir());\n\n if (!existsSync(claudeDir)) {\n mkdirSync(claudeDir, { recursive: true });\n }\n writeFileSync(claudeMd, template, \"utf-8\");\n\n line();\n console.log(c.ok(\"Generated ~/.claude/CLAUDE.md from PAI template.\"));\n\n if (!existsSync(agentPrefs)) {\n line();\n line(\" For personal settings (project mappings, notification preferences),\");\n line(\" copy the example and customize:\");\n line();\n console.log(chalk.cyan(` cp ${templatesDir}/agent-prefs.example.md ${agentPrefs}`));\n line();\n\n const createPrefs = await promptYesNo(rl, \"Copy the example agent-prefs.md now?\", true);\n if (createPrefs) {\n const examplePath = join(templatesDir, \"agent-prefs.example.md\");\n if (existsSync(examplePath)) {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n writeFileSync(agentPrefs, readFileSync(examplePath, \"utf-8\"), \"utf-8\");\n console.log(c.ok(`Created ${agentPrefs}`));\n console.log(c.dim(\" Edit this file to add your personal preferences and project mappings.\"));\n } else {\n console.log(c.warn(\"Example file not found. Create agent-prefs.md manually.\"));\n }\n }\n } else {\n console.log(c.dim(\" Personal preferences: \" + agentPrefs));\n }\n\n return true;\n}\n","/** Step 5: PAI SKILL.md installation to ~/.claude/skills/PAI/. */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getTemplatesDir } from \"../utils.js\";\n\nexport async function stepPaiSkill(rl: Rl): Promise<boolean> {\n section(\"Step 5: PAI Skill Installation\");\n line();\n line(\" PAI ships a SKILL.md that tells Claude Code how to invoke PAI commands.\");\n line();\n\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"pai-skill.template.md\");\n\n if (!existsSync(templatePath)) {\n console.log(c.warn(\"Skill template not found: \" + templatePath));\n console.log(c.dim(\" Skipping PAI skill installation.\"));\n return false;\n }\n\n const skillDir = join(homedir(), \".claude\", \"skills\", \"PAI\");\n const skillFile = join(skillDir, \"SKILL.md\");\n\n if (existsSync(skillFile)) {\n const content = readFileSync(skillFile, \"utf-8\");\n const isGenerated = content.includes(\"Generated by PAI Setup\");\n if (isGenerated) {\n console.log(c.dim(\" Found existing PAI-generated SKILL.md.\"));\n } else {\n console.log(c.yellow(\" Found existing SKILL.md (not PAI-generated).\"));\n console.log(c.dim(\" A backup will be created before overwriting.\"));\n }\n line();\n\n const overwrite = await promptYesNo(rl, \"Update ~/.claude/skills/PAI/SKILL.md with the latest PAI skill?\", isGenerated);\n if (!overwrite) {\n console.log(c.dim(\" Keeping existing SKILL.md unchanged.\"));\n return false;\n }\n\n if (!isGenerated) {\n const backupPath = skillFile + \".backup\";\n writeFileSync(backupPath, content, \"utf-8\");\n console.log(c.ok(`Backed up existing SKILL.md to ${backupPath}`));\n }\n } else {\n const install = await promptYesNo(rl, \"Install PAI skill to ~/.claude/skills/PAI/SKILL.md?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping PAI skill installation.\"));\n return false;\n }\n }\n\n let template = readFileSync(templatePath, \"utf-8\");\n template = template.replace(/\\$\\{HOME\\}/g, homedir());\n\n if (!existsSync(skillDir)) {\n mkdirSync(skillDir, { recursive: true });\n }\n writeFileSync(skillFile, template, \"utf-8\");\n\n line();\n console.log(c.ok(\"Installed ~/.claude/skills/PAI/SKILL.md\"));\n return true;\n}\n","/** Step 6: AI Steering Rules installation to ~/.claude/skills/PAI/. */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getTemplatesDir } from \"../utils.js\";\n\nexport async function stepAiSteeringRules(rl: Rl): Promise<boolean> {\n section(\"Step 6: AI Steering Rules\");\n line();\n line(\" PAI ships a set of universal behavioral rules for AI assistants:\");\n line(\" surgical fixes, verification before assertion, root cause analysis,\");\n line(\" and honest failure modes. These rules load at startup as a skill.\");\n line();\n\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"ai-steering-rules.template.md\");\n\n if (!existsSync(templatePath)) {\n console.log(c.warn(\"AI steering rules template not found: \" + templatePath));\n console.log(c.dim(\" Skipping AI steering rules installation.\"));\n return false;\n }\n\n const skillDir = join(homedir(), \".claude\", \"skills\", \"PAI\");\n const skillFile = join(skillDir, \"AI-STEERING-RULES.md\");\n\n if (existsSync(skillFile)) {\n const content = readFileSync(skillFile, \"utf-8\");\n const isGenerated = content.includes(\"Generated by PAI Setup\");\n if (isGenerated) {\n console.log(c.dim(\" Found existing PAI-generated AI-STEERING-RULES.md.\"));\n } else {\n console.log(c.yellow(\" Found existing AI-STEERING-RULES.md (not PAI-generated).\"));\n console.log(c.dim(\" A backup will be created before overwriting.\"));\n }\n line();\n\n const overwrite = await promptYesNo(rl, \"Update ~/.claude/skills/PAI/AI-STEERING-RULES.md with the latest rules?\", isGenerated);\n if (!overwrite) {\n console.log(c.dim(\" Keeping existing AI-STEERING-RULES.md unchanged.\"));\n return false;\n }\n\n if (!isGenerated) {\n const backupPath = skillFile + \".backup\";\n writeFileSync(backupPath, content, \"utf-8\");\n console.log(c.ok(`Backed up existing AI-STEERING-RULES.md to ${backupPath}`));\n }\n } else {\n const install = await promptYesNo(rl, \"Install AI steering rules to ~/.claude/skills/PAI/AI-STEERING-RULES.md?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping AI steering rules installation.\"));\n return false;\n }\n }\n\n const template = readFileSync(templatePath, \"utf-8\");\n\n if (!existsSync(skillDir)) {\n mkdirSync(skillDir, { recursive: true });\n }\n writeFileSync(skillFile, template, \"utf-8\");\n\n line();\n console.log(c.ok(\"Installed ~/.claude/skills/PAI/AI-STEERING-RULES.md\"));\n return true;\n}\n","/**\n * Step 7: Install PAI MCP skill stubs to ~/.claude/skills/.\n *\n * Each PAI MCP prompt has a SKILL.md stub generated at build time\n * (dist/skills/<Name>/SKILL.md). This step symlinks (macOS/Linux) or\n * copies (Windows) them into Claude Code's skill discovery directory.\n *\n * Skills MUST live at ~/.claude/skills/<Name>/SKILL.md (top level),\n * NOT under a user/ subdirectory — Claude Code only scans one level deep.\n *\n * Symlinks keep the stubs in sync — rebuilding PAI updates them automatically.\n * Existing non-symlink directories with the same name are never overwritten.\n */\n\nimport {\n existsSync,\n readdirSync,\n symlinkSync,\n lstatSync,\n readlinkSync,\n copyFileSync,\n mkdirSync,\n unlinkSync,\n} from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getDistDir } from \"../utils.js\";\n\nexport async function stepSkillStubs(rl: Rl): Promise<boolean> {\n section(\"Step 7: MCP Skill Stubs\");\n line();\n line(\" PAI MCP prompts need SKILL.md stubs so Claude Code can discover them.\");\n line(\" Stubs are symlinked from dist/skills/ — rebuilding PAI keeps them in sync.\");\n line();\n\n const distSkills = join(getDistDir(), \"skills\");\n\n if (!existsSync(distSkills)) {\n console.log(c.warn(\"dist/skills/ not found — run `bun run build` first.\"));\n return false;\n }\n\n const skillNames = readdirSync(distSkills).filter((name) => {\n const p = join(distSkills, name);\n return existsSync(join(p, \"SKILL.md\"));\n });\n\n if (skillNames.length === 0) {\n console.log(c.warn(\"No skill stubs found in dist/skills/.\"));\n return false;\n }\n\n console.log(c.dim(` Found ${skillNames.length} skill stubs to install.`));\n line();\n\n const install = await promptYesNo(\n rl,\n `Symlink ${skillNames.length} PAI skill stubs to ~/.claude/skills/?`,\n true,\n );\n if (!install) {\n console.log(c.dim(\" Skipping skill stub installation.\"));\n return false;\n }\n\n const targetBase = join(homedir(), \".claude\", \"skills\");\n mkdirSync(targetBase, { recursive: true });\n\n // Clean up stale symlinks from old user/ location\n const oldUserBase = join(targetBase, \"user\");\n if (existsSync(oldUserBase)) {\n for (const name of skillNames) {\n const oldTarget = join(oldUserBase, name);\n if (existsSync(oldTarget) && lstatSync(oldTarget).isSymbolicLink()) {\n unlinkSync(oldTarget);\n }\n }\n }\n\n const useSymlinks = platform() !== \"win32\";\n let installed = 0;\n let skipped = 0;\n let updated = 0;\n\n for (const name of skillNames) {\n const source = resolve(join(distSkills, name));\n const target = join(targetBase, name);\n\n // If target exists and is not a symlink, never overwrite (user's own skill)\n if (existsSync(target) && !lstatSync(target).isSymbolicLink()) {\n console.log(c.dim(` SKIP ${name} (existing user skill, not a symlink)`));\n skipped++;\n continue;\n }\n\n // If target is an existing symlink, check if it already points to the right place\n if (existsSync(target) && lstatSync(target).isSymbolicLink()) {\n const currentTarget = readlinkSync(target);\n if (resolve(currentTarget) === source) {\n // Already correct\n installed++;\n continue;\n }\n // Stale symlink — remove and recreate\n unlinkSync(target);\n updated++;\n }\n\n if (useSymlinks) {\n symlinkSync(source, target);\n } else {\n // Windows: copy the SKILL.md\n mkdirSync(target, { recursive: true });\n copyFileSync(join(source, \"SKILL.md\"), join(target, \"SKILL.md\"));\n }\n installed++;\n }\n\n line();\n const method = useSymlinks ? \"symlinked\" : \"copied\";\n if (updated > 0) {\n console.log(c.ok(`${installed} stubs ${method}, ${updated} updated, ${skipped} skipped (user skills).`));\n } else {\n console.log(c.ok(`${installed} skill stubs ${method} to ~/.claude/skills/.`));\n if (skipped > 0) {\n console.log(c.dim(` ${skipped} skipped (existing user skills not managed by PAI).`));\n }\n }\n return true;\n}\n","/** Step 7: Shell lifecycle hooks installation (pre-compact, session-stop, statusline). */\n\nimport {\n existsSync, lstatSync, readlinkSync, symlinkSync, unlinkSync,\n copyFileSync, chmodSync, mkdirSync,\n} from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getHooksDir, getStatuslineScript, getTabColorScript } from \"../utils.js\";\n\nconst useSymlinks = platform() !== \"win32\";\n\nexport async function stepHooks(rl: Rl): Promise<boolean> {\n section(\"Step 7: Lifecycle Hooks\");\n line();\n line(\" PAI hooks fire on session stop and context compaction to save state,\");\n line(\" update notes, and display live statusline information.\");\n if (useSymlinks) {\n line(\" Files are symlinked so they auto-update when PAI is rebuilt.\");\n } else {\n line(\" Files are copied (Windows — re-run setup after PAI updates).\");\n }\n line();\n\n const install = await promptYesNo(rl, \"Install PAI lifecycle hooks (session stop, pre-compact, statusline)?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping hook installation.\"));\n return false;\n }\n\n const hooksDir = getHooksDir();\n const statuslineSrc = getStatuslineScript();\n const tabColorSrc = getTabColorScript();\n\n const claudeDir = join(homedir(), \".claude\");\n const hooksTarget = join(claudeDir, \"Hooks\");\n\n if (!existsSync(hooksTarget)) {\n mkdirSync(hooksTarget, { recursive: true });\n }\n\n let anyInstalled = false;\n\n function installLink(src: string, dest: string, label: string): void {\n if (!existsSync(src)) {\n console.log(c.warn(` Source not found: ${src}`));\n return;\n }\n\n const absSrc = resolve(src);\n\n // Check existing target\n if (existsSync(dest) || (lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink?.())) {\n try {\n const stat = lstatSync(dest);\n if (stat.isSymbolicLink()) {\n if (resolve(readlinkSync(dest)) === absSrc) {\n console.log(c.dim(` Current: ${label}`));\n return;\n }\n // Stale symlink — replace\n unlinkSync(dest);\n } else if (stat.isFile()) {\n // Old copy — replace with symlink\n unlinkSync(dest);\n } else {\n return; // Don't touch directories\n }\n } catch {\n // lstatSync failed — target doesn't exist, proceed\n }\n }\n\n if (useSymlinks) {\n symlinkSync(absSrc, dest);\n } else {\n copyFileSync(src, dest);\n chmodSync(dest, 0o755);\n }\n const verb = useSymlinks ? \"Linked\" : \"Installed\";\n console.log(c.ok(`${verb}: ${label}`));\n anyInstalled = true;\n }\n\n line();\n installLink(join(hooksDir, \"pre-compact.sh\"), join(hooksTarget, \"pai-pre-compact.sh\"), \"pai-pre-compact.sh\");\n installLink(join(hooksDir, \"session-stop.sh\"), join(hooksTarget, \"pai-session-stop.sh\"), \"pai-session-stop.sh\");\n\n if (statuslineSrc) {\n installLink(statuslineSrc, join(claudeDir, \"statusline-command.sh\"), \"statusline-command.sh\");\n } else {\n console.log(c.warn(\" statusline-command.sh not found — skipping statusline.\"));\n }\n\n if (tabColorSrc) {\n installLink(tabColorSrc, join(claudeDir, \"tab-color-command.sh\"), \"tab-color-command.sh\");\n } else {\n console.log(c.warn(\" tab-color-command.sh not found — skipping tab color.\"));\n }\n\n return anyInstalled;\n}\n","/** Step 7b: TypeScript (.mjs) hooks installation to ~/.claude/Hooks/. */\n\nimport {\n existsSync, readdirSync, lstatSync, readlinkSync,\n symlinkSync, unlinkSync, copyFileSync, chmodSync, mkdirSync,\n} from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getDistHooksDir } from \"../utils.js\";\n\nconst useSymlinks = platform() !== \"win32\";\n\nexport async function stepTsHooks(rl: Rl): Promise<boolean> {\n section(\"Step 7b: TypeScript Hooks Installation\");\n line();\n line(\" PAI ships compiled TypeScript hooks (.mjs) that fire on session events,\");\n line(\" tool use, and context compaction to capture context and update notes.\");\n if (useSymlinks) {\n line(\" Files are symlinked so they auto-update when PAI is rebuilt.\");\n } else {\n line(\" Files are copied (Windows — re-run setup after PAI updates).\");\n }\n line();\n\n const install = await promptYesNo(rl, \"Install PAI TypeScript hooks to ~/.claude/Hooks/?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping TypeScript hooks installation.\"));\n return false;\n }\n\n const distHooksDir = getDistHooksDir();\n\n if (!existsSync(distHooksDir)) {\n console.log(c.warn(` dist/hooks/ directory not found at: ${distHooksDir}`));\n console.log(c.dim(\" Build the package first: bun run build\"));\n return false;\n }\n\n const claudeDir = join(homedir(), \".claude\");\n const hooksTarget = join(claudeDir, \"Hooks\");\n\n if (!existsSync(hooksTarget)) {\n mkdirSync(hooksTarget, { recursive: true });\n }\n\n let allFiles: string[];\n try {\n allFiles = readdirSync(distHooksDir).filter((f) => f.endsWith(\".mjs\"));\n } catch (e) {\n console.log(c.warn(` Could not read dist/hooks/: ${e}`));\n return false;\n }\n\n if (allFiles.length === 0) {\n console.log(c.warn(\" No .mjs files found in dist/hooks/. Build first: bun run build\"));\n return false;\n }\n\n line();\n let linkedCount = 0;\n let skippedCount = 0;\n let cleanedCount = 0;\n\n for (const filename of allFiles) {\n const src = resolve(join(distHooksDir, filename));\n const dest = join(hooksTarget, filename);\n\n // Check existing target\n let needsLink = true;\n if (existsSync(dest) || lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink?.()) {\n try {\n const stat = lstatSync(dest);\n if (stat.isSymbolicLink()) {\n if (resolve(readlinkSync(dest)) === src) {\n console.log(c.dim(` Current: ${filename}`));\n skippedCount++;\n needsLink = false;\n } else {\n unlinkSync(dest); // Stale symlink\n }\n } else if (stat.isFile()) {\n unlinkSync(dest); // Old copy — replace with symlink\n }\n } catch {\n // lstatSync failed — target doesn't exist\n }\n }\n\n if (needsLink) {\n if (useSymlinks) {\n symlinkSync(src, dest);\n } else {\n copyFileSync(src, dest);\n chmodSync(dest, 0o755);\n }\n const verb = useSymlinks ? \"Linked\" : \"Installed\";\n console.log(c.ok(`${verb}: ${filename}`));\n linkedCount++;\n }\n\n // Clean up stale .ts files from pre-build era\n const staleTsPath = join(hooksTarget, filename.replace(/\\.mjs$/, \".ts\"));\n if (existsSync(staleTsPath)) {\n unlinkSync(staleTsPath);\n console.log(c.ok(`Cleaned up stale: ${filename.replace(/\\.mjs$/, \".ts\")}`));\n cleanedCount++;\n }\n }\n\n line();\n if (linkedCount > 0 || cleanedCount > 0) {\n const parts = [];\n const verb = useSymlinks ? \"linked\" : \"installed\";\n if (linkedCount > 0) parts.push(`${linkedCount} hook(s) ${verb}`);\n if (skippedCount > 0) parts.push(`${skippedCount} current`);\n if (cleanedCount > 0) parts.push(`${cleanedCount} stale .ts file(s) cleaned up`);\n console.log(c.ok(parts.join(\", \") + \".\"));\n } else {\n console.log(c.dim(` All ${skippedCount} hook(s) already current.`));\n }\n\n return linkedCount > 0 || cleanedCount > 0;\n}\n","/**\n * settings-manager — merge-not-overwrite utility for ~/.claude/settings.json\n *\n * Provides safe, idempotent writes to Claude Code's settings.json:\n * - env vars: added only if the key is absent (never overwrites)\n * - hooks: appended per hookType, deduplicated by command string\n * - statusLine: written only if the key is not already present\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { readJsonStrict, writeJsonAtomic } from \"../../config/json-store.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst CLAUDE_DIR = join(homedir(), \".claude\");\nconst SETTINGS_FILE = join(CLAUDE_DIR, \"settings.json\");\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type HookType =\n | \"PreCompact\"\n | \"Stop\"\n | \"SessionStart\"\n | \"SessionEnd\"\n | \"UserPromptSubmit\"\n | \"PreToolUse\"\n | \"PostToolUse\"\n | \"SubagentStop\";\n\nexport interface HookEntry {\n hookType: HookType;\n matcher?: string;\n command: string;\n}\n\nexport interface SettingsMergeOptions {\n env?: Record<string, string>;\n hooks?: HookEntry[];\n statusLine?: { type: string; command: string };\n permissions?: {\n allow?: string[];\n deny?: string[];\n };\n flags?: Record<string, unknown>;\n}\n\nexport interface MergeResult {\n changed: boolean;\n report: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal shape of settings.json hooks\n// ---------------------------------------------------------------------------\n\ninterface HookCommand {\n type: string;\n command: string;\n}\n\ninterface HookRule {\n matcher?: string;\n hooks: HookCommand[];\n}\n\ntype HooksSection = Partial<Record<HookType, HookRule[]>>;\n\n// ---------------------------------------------------------------------------\n// Read / write helpers\n// ---------------------------------------------------------------------------\n\n/**\n * ~/.claude/settings.json is Claude Code's own file, not ours: hooks, env,\n * permissions, statusline, enabled plugins. PAI only ever adds to it. Returning\n * {} for a damaged file and then writing meant a stray parse error could strip\n * every hook registration the user had.\n */\nexport function readSettingsJson(): Record<string, unknown> {\n return readJsonStrict(SETTINGS_FILE, \"~/.claude/settings.json\");\n}\n\nexport function writeSettingsJson(data: Record<string, unknown>): void {\n writeJsonAtomic(SETTINGS_FILE, data, { label: \"~/.claude/settings.json\" });\n}\n\n// ---------------------------------------------------------------------------\n// Merge helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Merge env vars — add keys that are absent, never overwrite existing ones.\n */\nfunction mergeEnv(\n settings: Record<string, unknown>,\n incoming: Record<string, string>,\n report: string[],\n): boolean {\n let changed = false;\n\n const existing = (\n typeof settings[\"env\"] === \"object\" && settings[\"env\"] !== null\n ? settings[\"env\"]\n : {}\n ) as Record<string, string>;\n\n for (const [key, value] of Object.entries(incoming)) {\n if (Object.prototype.hasOwnProperty.call(existing, key)) {\n report.push(chalk.dim(` Skipped: env.${key} already set`));\n } else {\n existing[key] = value;\n report.push(chalk.green(` Added env: ${key}`));\n changed = true;\n }\n }\n\n settings[\"env\"] = existing;\n return changed;\n}\n\n/**\n * Strip file extension from a command basename for extension-agnostic dedup.\n * This ensures that e.g. \"context-compression-hook.ts\" and\n * \"context-compression-hook.mjs\" are treated as the same hook.\n */\nfunction commandStem(cmd: string): string {\n const base = cmd.split(\"/\").pop() ?? cmd;\n return base.replace(/\\.(mjs|ts|js|sh)$/, \"\");\n}\n\n/**\n * Collect every command string already registered for a given hookType.\n * Stores full command, basename, AND extension-stripped stem for flexible\n * matching (handles ${PAI_DIR}/Hooks/foo.sh vs /Users/.../Hooks/foo.sh,\n * and .ts → .mjs migrations).\n */\nfunction existingCommandsForHookType(rules: HookRule[]): Set<string> {\n const cmds = new Set<string>();\n for (const rule of rules) {\n for (const entry of rule.hooks) {\n cmds.add(entry.command);\n // Also add the basename so expanded paths match template paths\n const base = entry.command.split(\"/\").pop();\n if (base) cmds.add(base);\n // Also add extension-stripped stem for cross-extension dedup\n cmds.add(commandStem(entry.command));\n }\n }\n return cmds;\n}\n\n/**\n * Find and remove an existing rule whose command has the same stem\n * (extension-agnostic) as the incoming command. Returns true if a\n * replacement was made. This handles .ts → .mjs migrations cleanly.\n */\nfunction replaceStaleHook(\n existingRules: HookRule[],\n incomingStem: string,\n incomingCommand: string,\n incomingMatcher: string | undefined,\n): boolean {\n for (let i = 0; i < existingRules.length; i++) {\n const rule = existingRules[i];\n for (let j = 0; j < rule.hooks.length; j++) {\n const existingStem = commandStem(rule.hooks[j].command);\n if (existingStem === incomingStem && rule.hooks[j].command !== incomingCommand) {\n // Replace the old command with the new one\n rule.hooks[j].command = incomingCommand;\n // Update matcher if provided\n if (incomingMatcher !== undefined) {\n rule.matcher = incomingMatcher;\n }\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Merge hooks — append entries, deduplicating by command string.\n * Extension-agnostic: a .mjs hook replaces an existing .ts hook with the\n * same stem, ensuring clean .ts → .mjs migrations without duplicates.\n */\nfunction mergeHooks(\n settings: Record<string, unknown>,\n incoming: HookEntry[],\n report: string[],\n): boolean {\n let changed = false;\n\n const hooksSection = (\n typeof settings[\"hooks\"] === \"object\" && settings[\"hooks\"] !== null\n ? settings[\"hooks\"]\n : {}\n ) as HooksSection;\n\n for (const entry of incoming) {\n const { hookType, matcher, command } = entry;\n\n const existingRules: HookRule[] = Array.isArray(hooksSection[hookType])\n ? (hooksSection[hookType] as HookRule[])\n : [];\n\n const basename = command.split(\"/\").pop() ?? command;\n const stem = commandStem(command);\n\n // Exact match — already registered, skip\n const existingCmds = existingCommandsForHookType(existingRules);\n if (existingCmds.has(command) || existingCmds.has(basename)) {\n report.push(chalk.dim(` Skipped: hook ${hookType} → ${basename} already registered`));\n continue;\n }\n\n // Stem match with different extension — replace old entry (.ts → .mjs migration)\n if (existingCmds.has(stem)) {\n if (replaceStaleHook(existingRules, stem, command, matcher)) {\n hooksSection[hookType] = existingRules;\n report.push(chalk.yellow(` Upgraded: hook ${hookType} → ${basename} (replaced stale extension)`));\n changed = true;\n continue;\n }\n }\n\n // No match at all — append new rule\n const newRule: HookRule = {\n hooks: [{ type: \"command\", command }],\n };\n if (matcher !== undefined) {\n newRule.matcher = matcher;\n }\n\n existingRules.push(newRule);\n hooksSection[hookType] = existingRules;\n\n report.push(chalk.green(` Added hook: ${hookType} → ${basename}`));\n changed = true;\n }\n\n settings[\"hooks\"] = hooksSection;\n return changed;\n}\n\n/**\n * Merge statusLine — write only if the key is not already present.\n */\nfunction mergeStatusLine(\n settings: Record<string, unknown>,\n incoming: { type: string; command: string },\n report: string[],\n): boolean {\n if (Object.prototype.hasOwnProperty.call(settings, \"statusLine\")) {\n report.push(chalk.dim(\" Skipped: statusLine already configured\"));\n return false;\n }\n\n settings[\"statusLine\"] = { ...incoming };\n report.push(chalk.green(\" Added statusLine\"));\n return true;\n}\n\n/**\n * Merge permissions — append allow/deny entries, deduplicating.\n */\nfunction mergePermissions(\n settings: Record<string, unknown>,\n incoming: { allow?: string[]; deny?: string[] },\n report: string[],\n): boolean {\n let changed = false;\n\n const perms = (\n typeof settings[\"permissions\"] === \"object\" && settings[\"permissions\"] !== null\n ? settings[\"permissions\"]\n : {}\n ) as Record<string, string[]>;\n\n for (const list of [\"allow\", \"deny\"] as const) {\n const entries = incoming[list];\n if (!entries || entries.length === 0) continue;\n\n const existing: string[] = Array.isArray(perms[list]) ? perms[list] : [];\n const existingSet = new Set(existing);\n\n for (const entry of entries) {\n if (existingSet.has(entry)) {\n report.push(chalk.dim(` Skipped: permissions.${list} \"${entry}\" already present`));\n } else {\n existing.push(entry);\n existingSet.add(entry);\n report.push(chalk.green(` Added permissions.${list}: ${entry}`));\n changed = true;\n }\n }\n\n perms[list] = existing;\n }\n\n settings[\"permissions\"] = perms;\n return changed;\n}\n\n/**\n * Merge flags — set keys only if not already present, never overwrite.\n */\nfunction mergeFlags(\n settings: Record<string, unknown>,\n incoming: Record<string, unknown>,\n report: string[],\n): boolean {\n let changed = false;\n\n for (const [key, value] of Object.entries(incoming)) {\n if (Object.prototype.hasOwnProperty.call(settings, key)) {\n report.push(chalk.dim(` Skipped: ${key} already set`));\n } else {\n settings[key] = value;\n report.push(chalk.green(` Added flag: ${key}`));\n changed = true;\n }\n }\n\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// Public orchestrator\n// ---------------------------------------------------------------------------\n\n/**\n * Merge env vars, hooks, and/or a statusLine entry into ~/.claude/settings.json.\n * Never overwrites existing values — only adds what is missing.\n *\n * Returns { changed, report } where report contains human-readable lines.\n */\nexport function mergeSettings(opts: SettingsMergeOptions): MergeResult {\n const settings = readSettingsJson();\n const report: string[] = [];\n let changed = false;\n\n if (opts.env !== undefined && Object.keys(opts.env).length > 0) {\n if (mergeEnv(settings, opts.env, report)) changed = true;\n }\n\n if (opts.hooks !== undefined && opts.hooks.length > 0) {\n if (mergeHooks(settings, opts.hooks, report)) changed = true;\n }\n\n if (opts.statusLine !== undefined) {\n if (mergeStatusLine(settings, opts.statusLine, report)) changed = true;\n }\n\n if (opts.permissions !== undefined) {\n if (mergePermissions(settings, opts.permissions, report)) changed = true;\n }\n\n if (opts.flags !== undefined && Object.keys(opts.flags).length > 0) {\n if (mergeFlags(settings, opts.flags, report)) changed = true;\n }\n\n if (changed) {\n writeSettingsJson(settings);\n }\n\n return { changed, report };\n}\n","/** Steps 8b and 8: Assistant name prompt and settings.json patch. */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { mergeSettings } from \"../../settings-manager.js\";\nimport { c, line, section, type Rl, prompt, promptYesNo } from \"../utils.js\";\n\nexport async function stepDaName(rl: Rl): Promise<string> {\n section(\"Step 8b: Assistant Name\");\n line();\n line(\" Choose a name for your AI assistant. This name appears in tab titles\");\n line(\" and session notes when hooks are active.\");\n line();\n\n const answer = await prompt(rl, chalk.bold(\" Assistant name [PAI]: \"));\n const daName = answer || \"PAI\";\n line();\n console.log(c.ok(`Assistant name set to: ${daName}`));\n return daName;\n}\n\nexport async function stepSettings(rl: Rl, daName: string): Promise<boolean> {\n section(\"Step 8: Settings Patch\");\n line();\n line(\" PAI will add env vars, all hook registrations, permissions, and flags\");\n line(\" to ~/.claude/settings.json. Existing values are never overwritten.\");\n line();\n\n const patch = await promptYesNo(rl, \"Patch ~/.claude/settings.json with PAI hooks, env vars, and settings?\", true);\n if (!patch) {\n console.log(c.dim(\" Skipping settings patch.\"));\n return false;\n }\n\n const paiDir = join(homedir(), \".claude\");\n\n const result = mergeSettings({\n env: {\n PAI_DIR: paiDir,\n CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: \"80\",\n CLAUDE_CODE_MAX_OUTPUT_TOKENS: \"64000\",\n DA: daName,\n },\n hooks: [\n { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/session-commands.mjs\" },\n { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/load-core-context.mjs\" },\n { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/load-project-context.mjs\" },\n { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/initialize-session.mjs\" },\n { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type SessionStart\" },\n { hookType: \"SessionStart\", matcher: \"compact\", command: \"${PAI_DIR}/Hooks/post-compact-inject.mjs\" },\n { hookType: \"UserPromptSubmit\", command: \"${PAI_DIR}/Hooks/cleanup-session-files.mjs\" },\n { hookType: \"UserPromptSubmit\", command: \"${PAI_DIR}/Hooks/update-tab-titles.mjs\" },\n { hookType: \"UserPromptSubmit\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type UserPromptSubmit\" },\n { hookType: \"PreToolUse\", matcher: \"Bash\", command: \"${PAI_DIR}/Hooks/security-validator.mjs\" },\n { hookType: \"PreToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type PreToolUse\" },\n { hookType: \"PostToolUse\", matcher: \"TodoWrite\", command: \"${PAI_DIR}/Hooks/sync-todo-to-md.mjs\" },\n { hookType: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type PostToolUse\" },\n { hookType: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/capture-tool-output.mjs\" },\n { hookType: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/update-tab-on-action.mjs\" },\n { hookType: \"Stop\", command: \"${PAI_DIR}/Hooks/stop-hook.mjs\" },\n { hookType: \"Stop\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type Stop\" },\n { hookType: \"Stop\", command: \"${PAI_DIR}/Hooks/pai-session-stop.sh\" },\n { hookType: \"SubagentStop\", command: \"${PAI_DIR}/Hooks/subagent-stop-hook.mjs\" },\n { hookType: \"SubagentStop\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type SubagentStop\" },\n { hookType: \"SessionEnd\", command: \"${PAI_DIR}/Hooks/capture-session-summary.mjs\" },\n { hookType: \"SessionEnd\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type SessionEnd\" },\n { hookType: \"PreCompact\", command: \"${PAI_DIR}/Hooks/context-compression-hook.mjs\" },\n { hookType: \"PreCompact\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type PreCompact\" },\n { hookType: \"PreCompact\", matcher: \"\", command: \"${PAI_DIR}/Hooks/pai-pre-compact.sh\" },\n ],\n statusLine: {\n type: \"command\",\n command: \"bash ${PAI_DIR}/statusline-command.sh\",\n },\n permissions: {\n allow: [\"Bash\", \"Read\", \"Write\", \"Edit\", \"Glob\", \"Grep\", \"WebFetch\", \"WebSearch\", \"NotebookEdit\", \"TodoWrite\", \"ExitPlanMode\", \"mcp__pai\"],\n deny: [\"Bash(rm -rf /)\", \"Bash(rm -rf /*)\", \"Bash(rm -rf ~)\", \"Bash(rm -rf $HOME)\", \"Bash(sudo rm -rf /)\", \"Bash(sudo rm -rf /*)\"],\n },\n flags: {\n enableAllProjectMcpServers: true,\n },\n });\n\n line();\n for (const r of result.report) {\n console.log(r);\n }\n\n if (!result.changed) {\n console.log(c.dim(\" Settings already up-to-date. No changes made.\"));\n }\n\n return result.changed;\n}\n","/** Step 9: PAI daemon installation via launchd plist. */\n\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { c, line, section, type Rl, promptYesNo } from \"../utils.js\";\n\nexport async function stepDaemon(rl: Rl): Promise<boolean> {\n section(\"Step 9: Daemon Install\");\n line();\n line(\" The PAI daemon indexes your projects every 5 minutes in the background.\");\n line();\n\n const plistPath = join(homedir(), \"Library\", \"LaunchAgents\", \"com.pai.pai-daemon.plist\");\n const exists = existsSync(plistPath);\n\n if (exists) {\n console.log(c.dim(\" PAI daemon plist already installed.\"));\n line();\n\n const reinstall = await promptYesNo(rl, \"Reinstall the PAI daemon launchd plist?\", false);\n if (!reinstall) {\n console.log(c.dim(\" Keeping existing daemon installation.\"));\n return false;\n }\n } else {\n const install = await promptYesNo(rl, \"Install the PAI daemon to run automatically at login?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping daemon install. Run manually: pai daemon install\"));\n return false;\n }\n }\n\n line();\n const result = spawnSync(\"pai\", [\"daemon\", \"install\"], { stdio: \"inherit\" });\n\n if (result.status !== 0) {\n console.log(c.warn(\" Daemon install failed. Run manually: pai daemon install\"));\n return false;\n }\n\n console.log(c.ok(\"Daemon installed as com.pai.pai-daemon.\"));\n return true;\n}\n","/** Step 10: PAI MCP server registration in ~/.claude.json. */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { c, line, section, type Rl, promptYesNo } from \"../utils.js\";\n\nexport async function stepMcp(rl: Rl): Promise<boolean> {\n section(\"Step 10: MCP Registration\");\n line();\n line(\" Registering the PAI MCP server lets Claude Code call PAI tools directly.\");\n line();\n\n const claudeJsonPath = join(homedir(), \".claude.json\");\n if (existsSync(claudeJsonPath)) {\n try {\n const raw = readFileSync(claudeJsonPath, \"utf-8\");\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const mcpServers = parsed[\"mcpServers\"] as Record<string, unknown> | undefined;\n if (mcpServers && Object.prototype.hasOwnProperty.call(mcpServers, \"pai\")) {\n console.log(c.ok(\"PAI MCP server already registered in ~/.claude.json.\"));\n console.log(c.dim(\" Skipping MCP registration.\"));\n return false;\n }\n } catch {\n // continue\n }\n }\n\n const register = await promptYesNo(rl, \"Register the PAI MCP server in ~/.claude.json?\", true);\n if (!register) {\n console.log(c.dim(\" Skipping MCP registration. Run manually: pai mcp install\"));\n return false;\n }\n\n line();\n const result = spawnSync(\"pai\", [\"mcp\", \"install\"], { stdio: \"inherit\" });\n\n if (result.status !== 0) {\n console.log(c.warn(\" MCP registration failed. Run manually: pai mcp install\"));\n return false;\n }\n\n console.log(c.ok(\"PAI MCP server registered in ~/.claude.json.\"));\n return true;\n}\n","/** Step 11: Directory scanning configuration and registry scan prompt. */\n\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { line, section, type Rl, promptYesNo } from \"../utils.js\";\n\nexport async function stepDirectories(rl: Rl): Promise<void> {\n section(\"Step 11: Directories to Index\");\n line();\n line(\" PAI indexes files in your registered projects. You can register projects\");\n line(\" individually with `pai project add <path>`, or let the registry scanner\");\n line(\" discover them automatically with `pai registry scan`.\");\n line();\n\n const defaults = [\n join(homedir(), \"Projects\"),\n join(homedir(), \"Documents\"),\n join(homedir(), \"dev\"),\n ].filter(existsSync);\n\n if (defaults.length > 0) {\n line(\" These directories exist on your system:\");\n for (const d of defaults) {\n console.log(chalk.dim(` ${d}`));\n }\n line();\n }\n\n const runScan = await promptYesNo(rl, \"Run `pai registry scan` to auto-detect projects after setup?\", false);\n\n if (runScan) {\n line();\n console.log(chalk.dim(\" Registry scan will run after setup completes.\"));\n } else {\n console.log(chalk.dim(\" Add projects manually: pai project add <path>\"));\n console.log(chalk.dim(\" Or discover them later: pai registry scan\"));\n }\n\n (stepDirectories as { _runScan?: boolean })._runScan = runScan;\n}\n","/** Step 12: Initial index — optionally starts the daemon and runs registry scan. */\n\nimport { spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { c, line, section, type Rl, promptYesNo } from \"../utils.js\";\nimport { stepDirectories } from \"./13-directories.js\";\n\nexport async function stepInitialIndex(rl: Rl): Promise<void> {\n section(\"Step 12: Initial Index\");\n line();\n line(\" Indexing scans your registered projects and builds the search index.\");\n line(\" The daemon runs indexing automatically every 5 minutes once started.\");\n line();\n\n const willScan = (stepDirectories as { _runScan?: boolean })._runScan;\n\n if (willScan) {\n const startDaemon = await promptYesNo(rl, \"Start the PAI daemon now? (enables background indexing)\", true);\n\n if (startDaemon) {\n line();\n console.log(c.dim(\" Starting daemon...\"));\n\n try {\n const result = spawnSync(\"pai\", [\"daemon\", \"serve\", \"--background\"], { stdio: \"pipe\", timeout: 10000 });\n if (result.status === 0) {\n console.log(c.ok(\"Daemon started in background.\"));\n } else {\n console.log(c.warn(\"Could not start daemon. Run manually: pai daemon serve\"));\n }\n } catch {\n console.log(c.warn(\"Could not start daemon. Run manually: pai daemon serve\"));\n }\n\n line();\n console.log(c.dim(\" Running registry scan to detect projects...\"));\n\n try {\n const result = spawnSync(\"pai\", [\"registry\", \"scan\"], { stdio: \"inherit\", timeout: 30000 });\n if (result.status !== 0) {\n console.log(c.warn(\"Registry scan encountered issues. Run `pai registry scan` manually.\"));\n }\n } catch {\n console.log(c.warn(\"Could not run registry scan. Run manually: pai registry scan\"));\n }\n } else {\n console.log(chalk.dim(\" Start the daemon later: pai daemon serve\"));\n console.log(chalk.dim(\" Scan projects later: pai registry scan\"));\n }\n } else {\n console.log(chalk.dim(\" Register projects with: pai project add <path>\"));\n console.log(chalk.dim(\" Then index them with: pai memory index --all\"));\n console.log(chalk.dim(\" Or start the daemon: pai daemon serve\"));\n }\n}\n","/** Step 13: Setup summary — displays all configuration choices made during setup. */\n\nimport chalk from \"chalk\";\nimport { CONFIG_FILE } from \"../../../../daemon/config.js\";\nimport { line, section } from \"../utils.js\";\n\nexport function stepSummary(\n configUpdates: Record<string, unknown>,\n claudeMdGenerated: boolean,\n paiSkillInstalled: boolean,\n aiSteeringRulesInstalled: boolean,\n skillStubsInstalled: boolean,\n hooksInstalled: boolean,\n tsHooksInstalled: boolean,\n settingsPatched: boolean,\n daName: string,\n daemonInstalled: boolean,\n mcpRegistered: boolean,\n): void {\n section(\"Setup Complete\");\n line();\n console.log(chalk.green(\" PAI Knowledge OS is configured!\"));\n line();\n\n const backend = configUpdates.storageBackend as string;\n const model = configUpdates.embeddingModel as string;\n\n line(chalk.bold(\" Configuration saved to: \") + chalk.dim(CONFIG_FILE));\n line();\n console.log(chalk.dim(\" Storage backend: \") + chalk.cyan(backend ?? \"sqlite\"));\n console.log(chalk.dim(\" Embedding model: \") + chalk.cyan(model && model !== \"none\" ? model : \"(none — keyword search only)\"));\n console.log(chalk.dim(\" CLAUDE.md: \") + chalk.cyan(claudeMdGenerated ? \"~/.claude/CLAUDE.md (generated)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" PAI skill: \") + chalk.cyan(paiSkillInstalled ? \"~/.claude/skills/PAI/SKILL.md (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Steering rules: \") + chalk.cyan(aiSteeringRulesInstalled ? \"~/.claude/skills/PAI/AI-STEERING-RULES.md (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Skill stubs: \") + chalk.cyan(skillStubsInstalled ? \"18 MCP prompt stubs symlinked to ~/.claude/skills/\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Hooks (shell): \") + chalk.cyan(hooksInstalled ? \"pai-pre-compact.sh, pai-session-stop.sh (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Hooks (TS): \") + chalk.cyan(tsHooksInstalled ? \"14 .mjs hooks installed to ~/.claude/Hooks/\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Assistant name: \") + chalk.cyan(daName));\n console.log(chalk.dim(\" Settings: \") + chalk.cyan(settingsPatched ? \"env vars, hooks, permissions, flags (patched)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Daemon: \") + chalk.cyan(daemonInstalled ? \"com.pai.pai-daemon (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" MCP: \") + chalk.cyan(mcpRegistered ? \"registered in ~/.claude.json\" : \"(unchanged)\"));\n line();\n console.log(chalk.bold.yellow(\" → RESTART Claude Code to activate all changes.\"));\n line();\n\n line(chalk.bold(\" Next steps:\"));\n line();\n console.log(chalk.dim(\" # Register a project\"));\n console.log(chalk.cyan(\" pai project add ~/your/project\"));\n line();\n console.log(chalk.dim(\" # Index your files\"));\n console.log(chalk.cyan(\" pai memory index --all\"));\n line();\n console.log(chalk.dim(\" # Search your knowledge\"));\n console.log(chalk.cyan(\" pai memory search \\\"your query\\\"\"));\n line();\n if (model && model !== \"none\") {\n console.log(chalk.dim(\" # Generate embeddings for semantic search\"));\n console.log(chalk.cyan(\" pai memory embed\"));\n line();\n console.log(chalk.dim(\" # Semantic search\"));\n console.log(chalk.cyan(\" pai memory search --mode semantic \\\"your query\\\"\"));\n line();\n }\n console.log(chalk.dim(\" # Start the background daemon\"));\n console.log(chalk.cyan(\" pai daemon serve\"));\n line();\n console.log(chalk.dim(\" # Show all commands\"));\n console.log(chalk.cyan(\" pai --help\"));\n line();\n}\n","/**\n * resolver.ts — Ownership resolution for the task bus\n *\n * Maps a tracker task onto the PAI project that owns it.\n *\n * 1. an explicit `pai:<project>` label (authoritative)\n * 2. the enclosing container name (fallback, matches the sub-project mirror)\n * 3. otherwise UNROUTED (normal state — the findings inbox)\n *\n * Label wins because labels survive a task being moved between containers, and\n * because a task parked in the findings inbox has no meaningful container yet.\n *\n * See Notes/docs/task-bus.md.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n OWNER_LABEL_PREFIX,\n UNROUTED,\n type TaskOwner,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Alias map\n// ---------------------------------------------------------------------------\n\nexport interface AliasTarget {\n /** The alias itself — what `pai <name>` and aibroker_pai_launch accept. */\n alias: string;\n slug: string;\n rootPath: string;\n}\n\n/** Normalized alias → target. Keys are the output of `normalize()`. */\nexport type AliasMap = Map<string, AliasTarget>;\n\n/**\n * Fold a tracker-supplied string onto its comparable form.\n *\n * Container names are written for humans and carry decoration the registry\n * never sees: \"Reading List 📚\", \"AcmeAPI\", \"Client Work\". Strip the decoration\n * rather than demanding the user keep two naming schemes in sync.\n *\n * Emoji and pictographs are removed, separators collapse to a single hyphen,\n * and the result is lowercased — so \"Client Work\" and \"client-work\" agree.\n */\nexport function normalize(raw: string): string {\n return raw\n .normalize(\"NFKD\")\n // Drop emoji, pictographs, dingbats and variation selectors.\n .replace(/[\\u{1F000}-\\u{1FAFF}\\u{2600}-\\u{27BF}\\u{FE0F}\\u{200D}]/gu, \"\")\n .replace(/[^\\p{L}\\p{N}]+/gu, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .toLowerCase();\n}\n\n/**\n * Load every alias registered in the PAI registry.\n *\n * Reads the curated shortlist only — the `aliases` table — deliberately. The\n * full registry commonly carries hundreds of projects with no aliases and\n * genuine ambiguity — separate entries routinely share a display name at\n * different paths — so widening this query resolves tasks to the wrong\n * directory. That failure is silent, which makes it worse than no match. Bus\n * participation is opt-in via `pai project name <identifier> <shortname>`.\n */\nexport function loadAliasMap(db: Database): AliasMap {\n const rows = db\n .prepare(\n `SELECT a.alias AS alias,\n p.slug AS slug,\n p.root_path AS rootPath\n FROM aliases a\n JOIN projects p ON p.id = a.project_id\n WHERE p.status != 'archived'`\n )\n .all() as Array<{ alias: string; slug: string; rootPath: string }>;\n\n const map: AliasMap = new Map();\n for (const row of rows) {\n const target: AliasTarget = {\n alias: row.alias,\n slug: row.slug,\n rootPath: row.rootPath,\n };\n map.set(normalize(row.alias), target);\n // The slug is also addressable, so a task may name either.\n // Aliases win: they are the curated, intentional handle.\n const slugKey = normalize(row.slug);\n if (!map.has(slugKey)) map.set(slugKey, target);\n }\n return map;\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/** Extract the project named by a `pai:<project>` label, if any. */\nexport function ownerLabel(labels: string[]): string | null {\n for (const label of labels) {\n const trimmed = label.trim();\n if (trimmed.toLowerCase().startsWith(OWNER_LABEL_PREFIX)) {\n const value = trimmed.slice(OWNER_LABEL_PREFIX.length).trim();\n if (value) return value;\n }\n }\n return null;\n}\n\nexport interface ResolveInput {\n labels: string[];\n /** Name of the enclosing tracker container (sub-project), when there is one. */\n container?: string | null;\n}\n\n/**\n * Resolve a task's owner.\n *\n * An unresolvable task is never an error: it returns UNROUTED with `rawHint`\n * set to whatever was tried, so a routine can say *why* it could not route it\n * rather than dropping it silently. A container like \"Reading List 📚\" maps to\n * no PAI project — that is expected, not a fault.\n */\nexport function resolveOwner(input: ResolveInput, aliases: AliasMap): TaskOwner {\n const label = ownerLabel(input.labels);\n if (label) {\n const hit = aliases.get(normalize(label));\n if (hit) {\n return {\n project: hit.alias,\n rootPath: hit.rootPath,\n source: \"label\",\n };\n }\n // An explicit label that resolves to nothing is worth surfacing: the user\n // meant to route this somewhere. Do not silently fall through to the\n // container, which would send it to the wrong project.\n return { ...UNROUTED, rawHint: label };\n }\n\n if (input.container) {\n const hit = aliases.get(normalize(input.container));\n if (hit) {\n return {\n project: hit.alias,\n rootPath: hit.rootPath,\n source: \"container\",\n };\n }\n return { ...UNROUTED, rawHint: input.container };\n }\n\n return { ...UNROUTED };\n}\n","/**\n * todoist.ts — Todoist provider for the task bus\n *\n * Talks to the Todoist REST API directly rather than going through the Todoist\n * MCP. The MCP is scoped to a Claude session; the daemon has no session, so an\n * MCP-only integration could never run the bus unattended.\n *\n * See Notes/docs/task-bus.md.\n */\n\nimport type {\n ListOptions,\n NewTask,\n Task,\n TaskPriority,\n TaskProvider,\n TodoistProviderConfig,\n} from \"../types.js\";\nimport { UNROUTED } from \"../types.js\";\nimport type { AliasMap } from \"../resolver.js\";\nimport { resolveOwner } from \"../resolver.js\";\n\n/**\n * Unified API v1. The older `/rest/v2` endpoints now return 410 Gone — they\n * were sunset, so anything still targeting them fails outright rather than\n * degrading.\n */\nconst API = \"https://api.todoist.com/api/v1\";\n\n// ---------------------------------------------------------------------------\n// Wire types (only the fields we rely on)\n// ---------------------------------------------------------------------------\n\n/** v1 wraps every collection and pages with an opaque cursor. */\ninterface WirePage<T> {\n results: T[];\n next_cursor?: string | null;\n}\n\ninterface WireProject {\n id: string;\n name: string;\n parent_id?: string | null;\n is_archived?: boolean;\n is_deleted?: boolean;\n}\n\ninterface WireTask {\n id: string;\n content: string;\n description?: string;\n project_id: string;\n section_id?: string | null;\n labels?: string[];\n priority?: number; // 4 = p1 … 1 = p4\n due?: { date?: string; datetime?: string } | null;\n /** v1 returns completed and tombstoned tasks inline; both must be filtered. */\n checked?: boolean;\n is_deleted?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nasync function call<T>(\n token: string,\n path: string,\n init?: { method?: string; body?: unknown },\n): Promise<T> {\n const res = await fetch(`${API}${path}`, {\n method: init?.method ?? \"GET\",\n headers: {\n Authorization: `Bearer ${token}`,\n ...(init?.body ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: init?.body ? JSON.stringify(init.body) : undefined,\n });\n\n if (!res.ok) {\n // 401 is by far the most common failure and the least self-explanatory,\n // so name the cause rather than surfacing a bare status code.\n const hint = res.status === 401 ? \" (token rejected — check it was copied in full)\" : \"\";\n throw new Error(`Todoist ${res.status} ${res.statusText}${hint}`);\n }\n\n // 204 on delete/close — nothing to parse.\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n}\n\n/**\n * Drain a paginated collection.\n *\n * Every v1 list endpoint pages. Stopping at the first page would silently\n * truncate — the account here already exceeds one page of tasks — so follow the\n * cursor to exhaustion. The guard is a safety stop against a server that keeps\n * handing back the same cursor, not an intentional cap.\n */\nasync function collect<T>(token: string, path: string): Promise<T[]> {\n const out: T[] = [];\n let cursor: string | null | undefined;\n let guard = 0;\n\n do {\n const sep = path.includes(\"?\") ? \"&\" : \"?\";\n const url = cursor ? `${path}${sep}cursor=${encodeURIComponent(cursor)}` : path;\n const page = await call<WirePage<T>>(token, url);\n out.push(...(page.results ?? []));\n cursor = page.next_cursor;\n } while (cursor && ++guard < 100);\n\n return out;\n}\n\n/**\n * List every live project on the account.\n *\n * Exported because setup uses it both to let the user pick the bus root and to\n * validate the token — a bad token fails here, at install time, rather than\n * silently returning nothing during a routine.\n */\nexport async function listProjects(token: string): Promise<Array<{ id: string; name: string }>> {\n const projects = await collect<WireProject>(token, \"/projects\");\n return projects\n .filter((p) => !p.is_archived && !p.is_deleted)\n .map((p) => ({ id: p.id, name: p.name }));\n}\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\n/** Todoist priority is inverted: 4 is urgent, 1 is lowest. */\nfunction toPriority(wire: number | undefined): TaskPriority {\n switch (wire) {\n case 4: return \"p1\";\n case 3: return \"p2\";\n case 2: return \"p3\";\n default: return \"p4\";\n }\n}\n\nfunction fromPriority(p: TaskPriority | undefined): number {\n switch (p) {\n case \"p1\": return 4;\n case \"p2\": return 3;\n case \"p3\": return 2;\n default: return 1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport class TodoistProvider implements TaskProvider {\n readonly providerId = \"todoist\" as const;\n\n constructor(\n private readonly config: TodoistProviderConfig,\n private readonly aliases: AliasMap,\n ) {}\n\n /**\n * Resolution order is config → env → unconfigured. A missing token disables\n * the provider rather than throwing: a user without a tracker still gets a\n * fully working PAI.\n */\n private token(): string | null {\n return this.config.apiKey?.trim() || process.env.TODOIST_API_KEY?.trim() || null;\n }\n\n isConfigured(): boolean {\n return Boolean(this.config.enabled && this.token() && this.config.rootProjectId);\n }\n\n async listOpen(opts: ListOptions = {}): Promise<Task[]> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) return [];\n\n // Fetch the project tree once and keep only the bus subtree. Filtering\n // client-side is deliberate: Todoist's project search returns zero results\n // for names containing emoji, so a name query would silently find nothing.\n const projects = await collect<WireProject>(token, \"/projects\");\n const root = this.config.rootProjectId;\n const inBus = new Map<string, WireProject>();\n for (const p of projects) {\n if (p.is_archived || p.is_deleted) continue;\n if (p.id === root || p.parent_id === root) inBus.set(p.id, p);\n }\n if (inBus.size === 0) return [];\n\n // Query per bus project rather than draining every task on the account.\n // The account may hold thousands; the bus holds a handful.\n const wire: WireTask[] = [];\n for (const id of inBus.keys()) {\n wire.push(...(await collect<WireTask>(token, `/tasks?project_id=${encodeURIComponent(id)}`)));\n }\n\n const out: Task[] = [];\n\n for (const w of wire) {\n if (w.checked || w.is_deleted) continue;\n\n const container = inBus.get(w.project_id);\n if (!container) continue;\n\n // The bus root itself is not an owner — only its sub-projects mirror\n // PAI projects. A task sitting at the root has no container hint.\n const containerName = container.id === root ? null : container.name;\n const owner = resolveOwner({ labels: w.labels ?? [], container: containerName }, this.aliases);\n\n if (opts.owner && owner.project !== opts.owner) continue;\n if (opts.includeUnrouted === false && owner.project === null) continue;\n\n const due = w.due?.datetime ?? w.due?.date ?? null;\n if (opts.dueBefore) {\n // No due date means no deadline to miss — such tasks are never overdue,\n // so they are excluded from a date-bounded sweep rather than always shown.\n if (!due || due.slice(0, 10) > opts.dueBefore) continue;\n }\n\n out.push({\n id: w.id,\n title: w.content,\n body: w.description ?? \"\",\n owner,\n due,\n priority: toPriority(w.priority),\n labels: w.labels ?? [],\n // v1 does not return a task URL. Build the deep link from the ID so a\n // dispatched task is still one click from the tracker.\n sourceUrl: `https://app.todoist.com/app/task/${w.id}`,\n });\n\n if (opts.limit && out.length >= opts.limit) break;\n }\n\n return out;\n }\n\n async add(task: NewTask): Promise<Task> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) {\n throw new Error(\"Todoist provider is not configured — run `pai setup`.\");\n }\n\n const labels = [...(task.labels ?? [])];\n if (task.owner && !labels.some((l) => l.toLowerCase() === `pai:${task.owner!.toLowerCase()}`)) {\n labels.push(`pai:${task.owner}`);\n }\n\n // Unowned work lands in the findings inbox when one is configured, so it is\n // triaged later rather than lost at the root.\n const sectionId = task.owner ? undefined : this.config.findingsSectionId;\n\n const created = await call<WireTask>(token, \"/tasks\", {\n method: \"POST\",\n body: {\n content: task.title,\n description: task.body,\n project_id: this.config.rootProjectId,\n ...(sectionId ? { section_id: sectionId } : {}),\n labels,\n priority: fromPriority(task.priority),\n ...(task.due ? { due_date: task.due } : {}),\n },\n });\n\n return {\n id: created.id,\n title: created.content,\n body: created.description ?? \"\",\n owner: task.owner\n ? resolveOwner({ labels, container: null }, this.aliases)\n : { ...UNROUTED },\n due: created.due?.datetime ?? created.due?.date ?? null,\n priority: toPriority(created.priority),\n labels: created.labels ?? [],\n sourceUrl: `https://app.todoist.com/app/task/${created.id}`,\n };\n }\n\n /**\n * Replace a task's labels.\n *\n * Only ever sends `labels`. Never `due_date`: writing that field destroys a\n * recurrence rule, silently turning a routine into a one-off. Measured — it\n * cost me an invalid test before it cost anyone a schedule.\n */\n async setLabels(id: string, labels: string[]): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n await call<WireTask>(token, `/tasks/${id}`, { method: \"POST\", body: { labels } });\n }\n\n /** Append a comment — used to keep run history on the task itself. */\n async comment(id: string, content: string): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n await call<unknown>(token, `/comments`, { method: \"POST\", body: { task_id: id, content } });\n }\n\n async complete(id: string): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai setup`.\");\n await call<void>(token, `/tasks/${id}/close`, { method: \"POST\" });\n }\n}\n","/** Step 16: Task bus — optional external tracker for cross-session work. */\n\nimport { c, line, section, type Rl, prompt, promptYesNo, readConfigRaw } from \"../utils.js\";\nimport { listProjects } from \"../../../../tasks/providers/todoist.js\";\nimport type { TaskConfig } from \"../../../../tasks/types.js\";\n\n/** Shape of the `tasks` block as it sits in the raw config file. */\ntype RawTaskConfig = Partial<TaskConfig> & {\n providers?: { todoist?: { apiKey?: string; rootProjectId?: string; enabled?: boolean } };\n};\n\nexport async function stepTaskBus(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 16: Task Bus (Optional)\");\n\n const existing = readConfigRaw();\n const current = (existing.tasks ?? {}) as RawTaskConfig;\n const todoist = current.providers?.todoist;\n\n if (todoist?.apiKey && todoist?.rootProjectId) {\n console.log(c.ok(\"Task bus already configured. Skipping.\"));\n return { tasks: current };\n }\n\n line();\n line(\" The task bus lets any PAI session file work into a shared tracker, so a\");\n line(\" routine can pick it up later and hand it to the session that owns it.\");\n line();\n line(\" PAI works fully without this. Skip it unless you want cross-session tasks.\");\n line();\n\n const wanted = await promptYesNo(rl, \"Enable the task bus (requires a Todoist account)?\", false);\n if (!wanted) {\n line();\n console.log(c.ok(\"Skipping the task bus.\"));\n console.log(c.dim(\" Add later by re-running `pai setup`.\"));\n return { tasks: { enabled: false } };\n }\n\n // -- Token -----------------------------------------------------------------\n // Prefer an existing env token so the user need not paste a credential twice.\n // Never read it from ~/.claude.json: the Todoist MCP keeps one there, but that\n // belongs to a different tool and PAI must not scrape another program's secrets.\n line();\n line(\" Get your API token from Todoist:\");\n line(c.dim(\" Settings -> Integrations -> Developer\"));\n line(c.dim(\" https://app.todoist.com/app/settings/integrations/developer\"));\n line();\n line(\" It grants full read/write access to your Todoist. Treat it like a password.\");\n line();\n\n const envToken = process.env.TODOIST_API_KEY?.trim();\n let apiKey = todoist?.apiKey ?? \"\";\n\n if (!apiKey && envToken) {\n const useEnv = await promptYesNo(rl, \"Found TODOIST_API_KEY in the environment. Use it?\", true);\n if (useEnv) apiKey = envToken;\n }\n\n while (!apiKey) {\n apiKey = (await prompt(rl, \" Paste your Todoist API token: \")).trim();\n if (!apiKey) console.log(c.warn(\" A token is required, or answer no to skip the task bus.\"));\n }\n\n // -- Root project ----------------------------------------------------------\n // Listing projects doubles as token validation: a bad token fails here, at\n // setup time, rather than silently returning nothing during a morning routine.\n line();\n line(\" Fetching your Todoist projects...\");\n\n let projects: Array<{ id: string; name: string }>;\n try {\n projects = await listProjects(apiKey);\n } catch (e) {\n line();\n console.log(c.warn(`Could not reach Todoist: ${e instanceof Error ? e.message : String(e)}`));\n console.log(c.dim(\" The token may be wrong, or the network is unavailable.\"));\n console.log(c.dim(\" Saved nothing. Re-run `pai setup` to try again.\"));\n return { tasks: { enabled: false } };\n }\n\n if (projects.length === 0) {\n console.log(c.warn(\" No projects found. Create one in Todoist first, then re-run setup.\"));\n return { tasks: { enabled: false } };\n }\n\n line();\n line(\" Which project should hold the bus? Create a dedicated one if unsure.\");\n line();\n projects.forEach((p, i) => line(` ${String(i + 1).padStart(2)}. ${p.name}`));\n line();\n\n let rootProjectId = \"\";\n while (!rootProjectId) {\n const answer = (await prompt(rl, ` Project number [1-${projects.length}]: `)).trim();\n const idx = Number.parseInt(answer, 10) - 1;\n if (Number.isInteger(idx) && idx >= 0 && idx < projects.length) {\n rootProjectId = projects[idx]!.id;\n } else {\n console.log(c.warn(` Enter a number between 1 and ${projects.length}.`));\n }\n }\n\n const chosen = projects.find((p) => p.id === rootProjectId)!;\n\n // The ID is stored, never the name. Todoist's project search silently returns\n // zero results for names containing emoji, so resolving by name later would\n // report \"no tasks\" instead of failing — the exact silent failure this\n // subsystem exists to surface.\n line();\n console.log(c.ok(`Task bus enabled on \"${chosen.name}\" (id ${rootProjectId}).`));\n console.log(c.dim(\" Label a task `pai:<project>` to assign it to a PAI project.\"));\n console.log(c.dim(\" Only projects with an alias can be dispatched to — see `pai project name`.\"));\n\n const autoDispatch = await promptYesNo(\n rl,\n \"Automatically hand tasks to the owning session (needs AIBroker)?\",\n false,\n );\n\n return {\n tasks: {\n enabled: true,\n autoDispatch,\n providers: {\n todoist: { enabled: true, apiKey, rootProjectId },\n },\n },\n };\n}\n","/**\n * PAI setup wizard — main entry point.\n * Orchestrates all setup steps in order and registers the Commander command.\n */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { existsSync } from \"node:fs\";\nimport { CONFIG_FILE, loadConfig } from \"../../../daemon/config.js\";\nimport { createRl, prompt, promptYesNo, line, mergeConfig } from \"./utils.js\";\nimport {\n stepWelcome,\n stepStorage,\n stepEmbedding,\n stepClaudeMd,\n stepPaiSkill,\n stepAiSteeringRules,\n stepSkillStubs,\n stepHooks,\n stepTsHooks,\n stepDaName,\n stepSettings,\n stepDaemon,\n stepMcp,\n stepDirectories,\n stepTaskBus,\n stepInitialIndex,\n stepSummary,\n} from \"./steps/index.js\";\n\nasync function runSetup(): Promise<void> {\n const rl = createRl();\n\n try {\n if (existsSync(CONFIG_FILE)) {\n const current = loadConfig();\n line();\n console.log(\n chalk.yellow(\" Note: PAI is already configured.\") +\n chalk.dim(\" Proceeding will update your existing configuration.\"),\n );\n console.log(chalk.dim(` Config: ${CONFIG_FILE}`));\n console.log(chalk.dim(` Current backend: ${current.storageBackend}`));\n line();\n\n const proceed = await promptYesNo(rl, \"Continue and update configuration?\", true);\n if (!proceed) {\n rl.close();\n line(chalk.dim(\" Setup cancelled.\"));\n line();\n return;\n }\n }\n\n // Step 1: Welcome\n stepWelcome();\n line();\n await prompt(rl, chalk.dim(\" Press Enter to begin setup...\"));\n\n // Step 2: Storage\n const storageConfig = await stepStorage(rl);\n\n // Step 3: Embeddings\n const embeddingConfig = await stepEmbedding(rl);\n\n // Step 4: Agent configuration (CLAUDE.md)\n const claudeMdGenerated = await stepClaudeMd(rl);\n\n // Step 5: PAI Skill\n const paiSkillInstalled = await stepPaiSkill(rl);\n\n // Step 6: AI Steering Rules\n const aiSteeringRulesInstalled = await stepAiSteeringRules(rl);\n\n // Step 7: Skill Stubs\n const skillStubsInstalled = await stepSkillStubs(rl);\n\n // Step 8: Hooks (shell scripts)\n const hooksInstalled = await stepHooks(rl);\n\n // Step 7b: TypeScript hooks (.mjs files)\n const tsHooksInstalled = await stepTsHooks(rl);\n\n // Step 8b: DA name\n const daName = await stepDaName(rl);\n\n // Step 8: Settings.json\n const settingsPatched = await stepSettings(rl, daName);\n\n // Step 9: Daemon\n const daemonInstalled = await stepDaemon(rl);\n\n // Step 10: MCP\n const mcpRegistered = await stepMcp(rl);\n\n // Step 11: Directories (informational — no config written)\n await stepDirectories(rl);\n\n // Step 11b: Task bus (optional external tracker)\n const taskConfig = await stepTaskBus(rl);\n\n // Write config after gathering all choices\n const allUpdates = { ...storageConfig, ...embeddingConfig, ...taskConfig };\n mergeConfig(allUpdates);\n\n line();\n console.log(chalk.green(\" Configuration saved.\"));\n\n // Step 12: Initial index\n await stepInitialIndex(rl);\n\n // Step 13: Summary\n stepSummary(\n allUpdates,\n claudeMdGenerated,\n paiSkillInstalled,\n aiSteeringRulesInstalled,\n skillStubsInstalled,\n hooksInstalled,\n tsHooksInstalled,\n settingsPatched,\n daName,\n daemonInstalled,\n mcpRegistered,\n );\n\n } finally {\n rl.close();\n }\n}\n\nexport function registerSetupCommand(program: Command): void {\n program\n .command(\"setup\")\n .alias(\"install\")\n .description(\n \"Interactive setup wizard — configure storage, embeddings, agent config, and indexing\",\n )\n .action(async () => {\n await runSetup();\n });\n}\n","/** Symlink management: create, validate, migrate, and clean vault project symlinks. */\n\nimport {\n existsSync,\n mkdirSync,\n symlinkSync,\n readdirSync,\n lstatSync,\n unlinkSync,\n readlinkSync,\n writeFileSync,\n rmdirSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport type { ProjectRow, SyncStats } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Find the project-root Notes directory for a project. */\nexport function findNotesDir(rootPath: string): string | null {\n const canonical = join(rootPath, \"Notes\");\n if (existsSync(canonical)) return canonical;\n const alt = join(rootPath, \".claude\", \"Notes\");\n if (existsSync(alt)) return alt;\n return null;\n}\n\n/**\n * Find the Claude Code session notes directory from the registry-stored value.\n * Returns null if not set, missing on disk, or identical to notesDir.\n */\nexport function findClaudeNotesDir(\n claudeNotesDirFromRegistry: string | null,\n notesDir: string | null\n): string | null {\n if (!claudeNotesDirFromRegistry) return null;\n if (!existsSync(claudeNotesDirFromRegistry)) return null;\n if (notesDir && claudeNotesDirFromRegistry === notesDir) return null;\n return claudeNotesDirFromRegistry;\n}\n\n/** Check whether a path exists via lstat (does not follow symlinks). */\nfunction lstatExists(p: string): boolean {\n try {\n lstatSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Resolve slug collisions by appending -2, -3, etc. */\nexport function uniqueSlug(base: string, taken: Set<string>): string {\n if (!taken.has(base)) return base;\n let n = 2;\n while (taken.has(`${base}-${n}`)) n++;\n return `${base}-${n}`;\n}\n\n/**\n * Remove broken symlinks from a directory (one level deep).\n * Returns count of entries removed.\n */\nexport function cleanBrokenSymlinks(dir: string): number {\n let removed = 0;\n if (!existsSync(dir)) return removed;\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry);\n try {\n const stat = lstatSync(full);\n if (stat.isSymbolicLink()) {\n const target = readlinkSync(full);\n if (!existsSync(target)) {\n unlinkSync(full);\n removed++;\n }\n }\n } catch {\n // Skip unreadable entries\n }\n }\n return removed;\n}\n\n/**\n * Ensure a sub-symlink inside a project directory is correct.\n * Returns true if a new symlink was created, false otherwise.\n */\nexport function ensureSubSymlink(\n linkPath: string,\n target: string,\n errors: string[],\n label: string\n): boolean {\n if (lstatExists(linkPath)) {\n try {\n const stat = lstatSync(linkPath);\n if (stat.isSymbolicLink()) {\n const current = readlinkSync(linkPath);\n if (current === target) {\n return false; // Already correct\n }\n unlinkSync(linkPath);\n } else {\n errors.push(`${label}: path exists but is not a symlink — skipped`);\n return false;\n }\n } catch (e) {\n errors.push(`${label}: error checking existing path — ${e}`);\n return false;\n }\n }\n\n try {\n symlinkSync(target, linkPath);\n return true;\n } catch (e) {\n errors.push(`${label}: symlink creation failed — ${e}`);\n return false;\n }\n}\n\n/**\n * Migrate a legacy flat symlink at `slugPath` to a real directory.\n * If `slugPath` is already a real directory, this is a no-op.\n * If it is a symlink (legacy), it is removed so mkdirSync can create the dir.\n */\nexport function migrateToProjectDir(\n slugPath: string,\n errors: string[],\n slug: string\n): boolean {\n if (!lstatExists(slugPath)) {\n return true; // Does not exist yet — mkdirSync will create it\n }\n\n try {\n const stat = lstatSync(slugPath);\n\n if (stat.isDirectory()) {\n return true; // Already a real directory — nothing to migrate\n }\n\n if (stat.isSymbolicLink()) {\n unlinkSync(slugPath);\n return true; // Removed old symlink — now ready for mkdirSync\n }\n\n errors.push(`${slug}: path exists as non-directory, non-symlink — skipped`);\n return false;\n } catch (e) {\n errors.push(`${slug}: error during migration — ${e}`);\n return false;\n }\n}\n\n/** Remove a project directory from the vault if it is empty. */\nfunction removeProjectDirIfEmpty(slugPath: string): void {\n try {\n const entries = readdirSync(slugPath);\n if (entries.length === 0) {\n rmdirSync(slugPath);\n }\n } catch {\n // Non-fatal\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: syncVault\n// ---------------------------------------------------------------------------\n\n/**\n * Sync all active project Notes directories into the Obsidian vault.\n *\n * For each active project with at least one Notes source, creates:\n * {vault}/{slug}/ — real directory\n * notes → {root}/Notes/\n * sessions → ~/.claude/projects/{enc}/Notes/ (if different)\n *\n * Archived projects get a stub markdown file in {vault}/_archive/.\n */\nexport function syncVault(vaultPath: string, db: Database): SyncStats {\n const stats: SyncStats = { created: 0, updated: 0, removed: 0, stubbed: 0, errors: [] };\n\n mkdirSync(vaultPath, { recursive: true });\n stats.removed += cleanBrokenSymlinks(vaultPath);\n\n const projects = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir, status, obsidian_link, claude_notes_dir\n FROM projects\n ORDER BY status ASC, slug ASC`\n )\n .all() as ProjectRow[];\n\n const takenSlugs = new Set<string>();\n\n for (const project of projects) {\n if (project.status === \"active\") {\n const notesDir = findNotesDir(project.root_path);\n const claudeNotesDir = findClaudeNotesDir(project.claude_notes_dir, notesDir);\n\n if (!notesDir && !claudeNotesDir) {\n continue;\n }\n\n const slug = uniqueSlug(project.slug, takenSlugs);\n takenSlugs.add(slug);\n const slugPath = join(vaultPath, slug);\n\n if (!migrateToProjectDir(slugPath, stats.errors, slug)) {\n continue;\n }\n\n try {\n mkdirSync(slugPath, { recursive: true });\n } catch (e) {\n stats.errors.push(`${slug}: failed to create project directory — ${e}`);\n continue;\n }\n\n if (notesDir) {\n const notesLink = join(slugPath, \"notes\");\n const created = ensureSubSymlink(notesLink, notesDir, stats.errors, `${slug}/notes`);\n if (created) stats.created++;\n else stats.updated++;\n }\n\n if (claudeNotesDir) {\n const sessionsLink = join(slugPath, \"sessions\");\n const created = ensureSubSymlink(sessionsLink, claudeNotesDir, stats.errors, `${slug}/sessions`);\n if (created) stats.created++;\n else stats.updated++;\n }\n\n try {\n db.prepare(\"UPDATE projects SET obsidian_link = ?, updated_at = ? WHERE id = ?\").run(\n slugPath,\n Date.now(),\n project.id\n );\n } catch {\n // Non-fatal\n }\n } else if (project.status === \"archived\") {\n const archiveDir = join(vaultPath, \"_archive\");\n mkdirSync(archiveDir, { recursive: true });\n const stubPath = join(archiveDir, `${project.slug}.md`);\n if (!existsSync(stubPath)) {\n const content = [\n `# ${project.display_name}`,\n \"\",\n \"> Archived project — no live notes available.\",\n \"\",\n `- **Slug:** ${project.slug}`,\n `- **Root:** ${project.root_path}`,\n \"\",\n ].join(\"\\n\");\n try {\n writeFileSync(stubPath, content, \"utf-8\");\n stats.stubbed++;\n } catch (e) {\n stats.errors.push(`${project.slug} (archive stub): ${e}`);\n }\n }\n }\n }\n\n return stats;\n}\n","/** Index and topic page generation — writes _index.md and _topics/{tag}.md. */\n\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { Database } from \"better-sqlite3\";\nimport { fmtDate } from \"../../cli/utils.js\";\nimport type { ProjectRow, SessionStats, TagRow } from \"./types.js\";\nimport { findNotesDir, findClaudeNotesDir } from \"./symlinks.js\";\n\n/**\n * Generate _index.md listing all projects with session counts, tags, and\n * indicators for which note sources are available (notes, sessions, or both).\n */\nexport function generateIndex(vaultPath: string, db: Database): void {\n mkdirSync(vaultPath, { recursive: true });\n\n const rows = db\n .prepare(\n `SELECT p.id, p.slug, p.display_name, p.status, p.root_path,\n p.encoded_dir, p.claude_notes_dir,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n FROM projects p\n ORDER BY p.status ASC, p.updated_at DESC`\n )\n .all() as (ProjectRow & SessionStats)[];\n\n const getTagsForProject = db.prepare(\n `SELECT t.name FROM tags t\n JOIN project_tags pt ON pt.tag_id = t.id\n WHERE pt.project_id = ?\n ORDER BY t.name`\n );\n\n const active = rows.filter((r) => r.status === \"active\");\n const archived = rows.filter((r) => r.status !== \"active\");\n\n const lines: string[] = [\n \"# PAI Project Index\",\n \"\",\n `> Auto-generated by PAI Knowledge OS — ${new Date().toISOString()}`,\n \"\",\n \"## Active Projects\",\n \"\",\n \"| Project | Sessions | Last Active | Sources | Tags |\",\n \"| ------- | -------- | ----------- | ------- | ---- |\",\n ];\n\n for (const row of active) {\n const tags = (getTagsForProject.all(row.id) as TagRow[]).map((t) => `\\`${t.name}\\``).join(\" \");\n const lastActive = fmtDate(row.last_active);\n\n const notesDir = findNotesDir(row.root_path);\n const claudeNotesDir = findClaudeNotesDir(row.claude_notes_dir, notesDir);\n const sources: string[] = [];\n if (notesDir) sources.push(\"notes\");\n if (claudeNotesDir) sources.push(\"sessions\");\n const sourcesLabel = sources.length > 0 ? sources.join(\", \") : \"—\";\n\n lines.push(\n `| [[${row.slug}/|${row.display_name}]] | ${row.session_count} | ${lastActive} | ${sourcesLabel} | ${tags || \"—\"} |`\n );\n }\n\n if (archived.length > 0) {\n lines.push(\n \"\",\n \"## Archived Projects\",\n \"\",\n \"| Project | Sessions | Tags |\",\n \"| ------- | -------- | ---- |\"\n );\n for (const row of archived) {\n const tags = (getTagsForProject.all(row.id) as TagRow[]).map((t) => `\\`${t.name}\\``).join(\" \");\n lines.push(\n `| [${row.display_name}](_archive/${row.slug}.md) | ${row.session_count} | ${tags || \"—\"} |`\n );\n }\n }\n\n lines.push(\n \"\",\n \"---\",\n `*${rows.length} projects total — ${active.length} active, ${archived.length} archived*`\n );\n\n writeFileSync(join(vaultPath, \"_index.md\"), lines.join(\"\\n\") + \"\\n\", \"utf-8\");\n}\n\n/**\n * Generate per-tag topic pages at _topics/{tag}.md.\n * Returns count of pages written.\n */\nexport function generateTopicPages(vaultPath: string, db: Database): number {\n const topicsDir = join(vaultPath, \"_topics\");\n mkdirSync(topicsDir, { recursive: true });\n\n const allTags = db\n .prepare(\"SELECT id, name FROM tags ORDER BY name\")\n .all() as { id: number; name: string }[];\n\n const getProjectsForTag = db.prepare(\n `SELECT p.id, p.slug, p.display_name, p.status,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n FROM projects p\n JOIN project_tags pt ON pt.project_id = p.id\n WHERE pt.tag_id = ?\n ORDER BY p.status ASC, p.updated_at DESC`\n );\n\n let written = 0;\n for (const tag of allTags) {\n const projects = getProjectsForTag.all(tag.id) as (ProjectRow & SessionStats)[];\n if (!projects.length) continue;\n\n const lines: string[] = [\n `# Topic: ${tag.name}`,\n \"\",\n `> Auto-generated by PAI Knowledge OS — ${new Date().toISOString()}`,\n \"\",\n \"## Projects\",\n \"\",\n \"| Project | Status | Sessions | Last Active |\",\n \"| ------- | ------ | -------- | ----------- |\",\n ];\n\n for (const p of projects) {\n const link =\n p.status === \"active\"\n ? `[[${p.slug}/|${p.display_name}]]`\n : `[${p.display_name}](../_archive/${p.slug}.md)`;\n lines.push(`| ${link} | ${p.status} | ${p.session_count} | ${fmtDate(p.last_active)} |`);\n }\n\n lines.push(\"\", \"---\", `*${projects.length} project(s) tagged \\`${tag.name}\\`*`);\n\n writeFileSync(join(topicsDir, `${tag.name}.md`), lines.join(\"\\n\") + \"\\n\", \"utf-8\");\n written++;\n }\n\n return written;\n}\n\n/**\n * Default vault path: ~/.pai/obsidian-vault\n */\nexport function defaultVaultPath(): string {\n return join(homedir(), \".pai\", \"obsidian-vault\");\n}\n","/** Directory walking and session file discovery for vault note generation. */\n\nimport {\n existsSync,\n readdirSync,\n lstatSync,\n readlinkSync,\n} from \"node:fs\";\nimport { join, relative } from \"node:path\";\nimport type { SessionFile } from \"./types.js\";\n\nexport const SESSION_FILENAME_RE = /^(\\d{4}) - (\\d{4}-\\d{2})-\\d{2} - .+\\.md$/;\n\n/**\n * Walk a directory (non-recursive root level, then one level of YYYY/MM subdirs).\n * Returns absolute paths to all .md files found, skipping master note files.\n */\nexport function walkNotesDir(dir: string): string[] {\n const results: string[] = [];\n if (!existsSync(dir)) return results;\n\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n if (entry.startsWith(\".\")) continue;\n // Skip any master note file (pattern: _{slug}-master.md or legacy _master.md)\n if (entry === \"_master.md\" || /^_[^/]+-master\\.md$/.test(entry)) continue;\n const full = join(dir, entry);\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(full);\n } catch {\n continue;\n }\n if (stat.isDirectory()) {\n if (/^\\d{4}$/.test(entry)) {\n let monthEntries: string[];\n try {\n monthEntries = readdirSync(full);\n } catch {\n continue;\n }\n for (const month of monthEntries) {\n const monthPath = join(full, month);\n if (/^\\d{2}$/.test(month) && existsSync(monthPath)) {\n let monthFiles: string[];\n try {\n monthFiles = readdirSync(monthPath);\n } catch {\n continue;\n }\n for (const f of monthFiles) {\n if (f.endsWith(\".md\") && !f.startsWith(\".\")) {\n results.push(join(monthPath, f));\n }\n }\n }\n }\n }\n } else if (entry.endsWith(\".md\")) {\n results.push(full);\n }\n }\n return results;\n}\n\n/**\n * Extract YYYY/MM from a session file path.\n * Tries the path first (/YYYY/MM/ pattern), then falls back to filename date.\n */\nexport function extractYearMonth(filePath: string): string {\n const pathMatch = filePath.match(/\\/(\\d{4})\\/(\\d{2})\\//);\n if (pathMatch) return `${pathMatch[1]}/${pathMatch[2]}`;\n\n const basename = filePath.split(\"/\").pop() ?? \"\";\n const nameMatch = basename.match(/^\\d{4} - (\\d{4})-(\\d{2})-\\d{2}/);\n if (nameMatch) return `${nameMatch[1]}/${nameMatch[2]}`;\n\n return \"unknown\";\n}\n\n/**\n * Collect all session .md files from the notes and sessions symlinks\n * inside a vault project directory.\n */\nexport function collectSessionFiles(slugPath: string): SessionFile[] {\n const sessionFiles: SessionFile[] = [];\n const subLinks = [\"notes\", \"sessions\"];\n\n for (const subLink of subLinks) {\n const linkPath = join(slugPath, subLink);\n if (!existsSync(linkPath)) continue;\n\n let realDir: string;\n try {\n const stat = lstatSync(linkPath);\n if (stat.isSymbolicLink()) {\n realDir = readlinkSync(linkPath);\n } else if (stat.isDirectory()) {\n realDir = linkPath;\n } else {\n continue;\n }\n } catch {\n continue;\n }\n\n const files = walkNotesDir(realDir);\n for (const absPath of files) {\n const basename = absPath.split(\"/\").pop() ?? \"\";\n if (!SESSION_FILENAME_RE.test(basename)) continue;\n\n const relFromReal = relative(realDir, absPath);\n const vaultRelPath = `${subLink}/${relFromReal}`;\n const wikilinkTarget = vaultRelPath.replace(/\\.md$/, \"\");\n\n sessionFiles.push({\n absPath,\n vaultRelPath,\n wikilinkTarget,\n yearMonth: extractYearMonth(absPath),\n basename: basename.replace(/\\.md$/, \"\"),\n });\n }\n }\n\n return sessionFiles;\n}\n","/** Master note generation and session tag fixing for vault projects. */\n\nimport {\n existsSync,\n unlinkSync,\n writeFileSync,\n readFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport type { ProjectRow } from \"./types.js\";\nimport { collectSessionFiles, walkNotesDir } from \"./walk.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Build the per-project master note filename. */\nexport function masterFilename(slug: string): string {\n return `_${slug}-master.md`;\n}\n\n/**\n * Remove any old/broken backlink footer from a session file.\n * Returns true if the file was modified.\n */\nfunction removeOldBacklink(filePath: string): boolean {\n let content: string;\n try {\n content = readFileSync(filePath, \"utf-8\");\n } catch {\n return false;\n }\n const oldPattern = /\\n---\\n\\[\\[\\.\\.\\/[^\\]]+\\]\\]\\n?$/;\n const newPattern = /\\n---\\n\\[\\[_[^\\]]*-master\\|[^\\]]*\\]\\]\\n?$/;\n const cleaned = content.replace(oldPattern, \"\").replace(newPattern, \"\");\n if (cleaned === content) return false;\n try {\n writeFileSync(filePath, cleaned, \"utf-8\");\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Return true if the note has a meaningful body — i.e. content beyond blank\n * lines, horizontal rules, and an existing master-note footer.\n *\n * Guards against stamping a \"← Master\" backlink onto an empty note, which\n * produces a misleading dangling stub (empty body + footer only). Whatever\n * emptied the note, we must not decorate it as if it were a real session.\n */\nfunction hasMeaningfulBody(content: string): boolean {\n const body = content\n .replace(/\\n---\\n\\[\\[_[^\\]]*-master\\|[^\\]]*\\]\\]\\n?$/, \"\") // drop existing master footer\n .split(\"\\n\")\n .filter((line) => {\n const t = line.trim();\n return t !== \"\" && t !== \"---\";\n })\n .join(\"\");\n return body.length > 0;\n}\n\n/**\n * Append the master note backlink footer to a session file, idempotently.\n * Only writes if the sentinel string is not already present AND the note has\n * a meaningful body (never stamp a footer onto an empty note).\n */\nfunction appendBacklinkIfMissing(\n filePath: string,\n slug: string,\n displayName: string\n): boolean {\n let content: string;\n try {\n content = readFileSync(filePath, \"utf-8\");\n } catch {\n return false;\n }\n // Never stamp a footer onto an empty note — that is exactly the dangling-stub\n // signature (empty body + master backlink) we are trying to prevent.\n if (!hasMeaningfulBody(content)) return false;\n\n const masterName = masterFilename(slug).replace(/\\.md$/, \"\");\n const sentinel = `[[${masterName}|`;\n if (content.includes(sentinel)) return false;\n\n const footer = `\\n---\\n[[${masterName}|← ${displayName} Master]]\\n`;\n try {\n writeFileSync(filePath, content + footer, \"utf-8\");\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: generateMasterNotes\n// ---------------------------------------------------------------------------\n\n/**\n * Generate _master.md files for projects that have >= threshold session files.\n *\n * For each qualifying project, writes a {vaultPath}/{slug}/_master.md containing:\n * - Project title, session count + date range\n * - Sessions grouped by YYYY/MM with Obsidian [[wikilinks]]\n *\n * Also appends a backlink footer to each session file (idempotently).\n *\n * @param vaultPath Absolute path to the PAI Obsidian vault\n * @param db Registry SQLite database\n * @param threshold Minimum session count to generate a master note (default: 5)\n * @returns Number of master notes written\n */\nexport function generateMasterNotes(\n vaultPath: string,\n db: Database,\n threshold = 5\n): number {\n if (!existsSync(vaultPath)) return 0;\n\n const projects = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir, status, obsidian_link, claude_notes_dir\n FROM projects\n WHERE status = 'active'\n ORDER BY slug ASC`\n )\n .all() as ProjectRow[];\n\n let written = 0;\n\n for (const project of projects) {\n const slugPath = join(vaultPath, project.slug);\n if (!existsSync(slugPath)) continue;\n\n // Remove legacy _master.md if present\n const legacyMaster = join(slugPath, \"_master.md\");\n if (existsSync(legacyMaster)) {\n try { unlinkSync(legacyMaster); } catch { /* Non-fatal */ }\n }\n\n const sessionFiles = collectSessionFiles(slugPath);\n if (sessionFiles.length < threshold) continue;\n\n // Sort by yearMonth then basename\n sessionFiles.sort((a, b) => {\n if (a.yearMonth !== b.yearMonth) return a.yearMonth.localeCompare(b.yearMonth);\n return a.basename.localeCompare(b.basename);\n });\n\n const byMonth = new Map<string, typeof sessionFiles>();\n for (const sf of sessionFiles) {\n if (!byMonth.has(sf.yearMonth)) byMonth.set(sf.yearMonth, []);\n byMonth.get(sf.yearMonth)!.push(sf);\n }\n\n const firstDate = sessionFiles[0]?.basename.match(/\\d{4}-\\d{2}-\\d{2}/)?.[0] ?? \"unknown\";\n const lastDate = sessionFiles[sessionFiles.length - 1]?.basename.match(/\\d{4}-\\d{2}-\\d{2}/)?.[0] ?? \"unknown\";\n\n const lines: string[] = [\n `# Project: ${project.display_name}`,\n \"\",\n `> Auto-generated by PAI Knowledge OS — ${new Date().toISOString()}`,\n \"\",\n \"## Overview\",\n \"\",\n `- **Sessions:** ${sessionFiles.length}`,\n `- **Date range:** ${firstDate} to ${lastDate}`,\n `- **Tags:** #${project.slug} #project`,\n \"\",\n \"## Sessions by Month\",\n \"\",\n ];\n\n for (const [ym, files] of byMonth) {\n lines.push(`### ${ym}`, \"\");\n for (const sf of files) {\n lines.push(`- [[${sf.wikilinkTarget}|${sf.basename}]]`);\n }\n lines.push(\"\");\n }\n\n lines.push(\"## Topics\", \"\", `#${project.slug} #project`, \"\");\n\n const masterPath = join(slugPath, masterFilename(project.slug));\n try {\n writeFileSync(masterPath, lines.join(\"\\n\"), \"utf-8\");\n written++;\n } catch {\n continue;\n }\n\n for (const sf of sessionFiles) {\n removeOldBacklink(sf.absPath);\n appendBacklinkIfMissing(sf.absPath, project.slug, project.display_name);\n }\n }\n\n return written;\n}\n\n// ---------------------------------------------------------------------------\n// Public: fixSessionTags\n// ---------------------------------------------------------------------------\n\n/**\n * Remove the generic #Session tag from session note files across all projects.\n *\n * Scans all session and notes directories for every active project and removes\n * #Session (with or without a trailing space) from any `**Tags:**` line.\n *\n * @param db Registry SQLite database\n * @returns Object with counts: { filesScanned, filesModified, errors }\n */\nexport function fixSessionTags(\n db: Database\n): { filesScanned: number; filesModified: number; errors: string[] } {\n const results = { filesScanned: 0, filesModified: 0, errors: [] as string[] };\n\n const projects = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir, status, obsidian_link, claude_notes_dir\n FROM projects\n WHERE status = 'active'\n ORDER BY slug ASC`\n )\n .all() as ProjectRow[];\n\n for (const project of projects) {\n const dirsToScan: string[] = [];\n\n // Inline findNotesDir to avoid a circular dependency with symlinks.ts\n const canonical = join(project.root_path, \"Notes\");\n const alt = join(project.root_path, \".claude\", \"Notes\");\n const notesDir = existsSync(canonical) ? canonical : existsSync(alt) ? alt : null;\n\n if (notesDir) dirsToScan.push(notesDir);\n\n if (\n project.claude_notes_dir &&\n existsSync(project.claude_notes_dir) &&\n project.claude_notes_dir !== notesDir\n ) {\n dirsToScan.push(project.claude_notes_dir);\n }\n\n for (const dir of dirsToScan) {\n const files = walkNotesDir(dir);\n for (const filePath of files) {\n results.filesScanned++;\n let content: string;\n try {\n content = readFileSync(filePath, \"utf-8\");\n } catch (e) {\n results.errors.push(`${filePath}: read error — ${e}`);\n continue;\n }\n\n if (!content.includes(\"#Session\")) continue;\n\n const updated = content.replace(\n /(\\*\\*Tags:\\*\\*[^\\n]*)#Session ?/g,\n \"$1\"\n );\n\n if (updated === content) continue;\n\n const cleaned = updated.replace(/(\\*\\*Tags:\\*\\*) *\\n/g, \"$1\\n\");\n\n try {\n writeFileSync(filePath, cleaned, \"utf-8\");\n results.filesModified++;\n } catch (e) {\n results.errors.push(`${filePath}: write error — ${e}`);\n }\n }\n }\n }\n\n return results;\n}\n","/**\n * obsidian/status.ts — Vault health checking for PAI Phase 4\n *\n * Vault structure (per project):\n *\n * {vault}/{slug}/ — real directory\n * notes → {root}/Notes/ (project root notes, optional)\n * sessions → ~/.claude/projects/{enc}/Notes/ (Claude Code session notes, optional)\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { existsSync, readdirSync, lstatSync, readlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface ProjectRow {\n id: number;\n slug: string;\n display_name: string;\n root_path: string;\n status: string;\n claude_notes_dir: string | null;\n obsidian_link: string | null;\n}\n\nexport interface SymlinkHealth {\n slug: string;\n linkPath: string;\n target: string | null;\n state: \"healthy\" | \"broken\" | \"orphaned\" | \"missing\";\n notes: string;\n}\n\nexport interface VaultHealthReport {\n vaultPath: string;\n healthy: SymlinkHealth[];\n broken: SymlinkHealth[];\n orphaned: SymlinkHealth[];\n missing: SymlinkHealth[];\n totalProjects: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check the health of a sub-symlink inside a project directory.\n * Returns true if the symlink exists and its target exists on disk.\n */\nfunction subSymlinkHealthy(dir: string, name: string): boolean {\n const linkPath = join(dir, name);\n try {\n const stat = lstatSync(linkPath);\n if (!stat.isSymbolicLink()) return false;\n const target = readlinkSync(linkPath);\n return existsSync(target);\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a sub-symlink exists (regardless of target validity).\n */\nfunction subSymlinkExists(dir: string, name: string): boolean {\n try {\n lstatSync(join(dir, name));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Find a project root Notes dir (checks canonical and .claude/Notes fallback).\n */\nfunction findNotesDir(rootPath: string): string | null {\n const canonical = join(rootPath, \"Notes\");\n if (existsSync(canonical)) return canonical;\n const alt = join(rootPath, \".claude\", \"Notes\");\n if (existsSync(alt)) return alt;\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Check health of the Obsidian vault:\n *\n * Project entries are now real directories with sub-symlinks (`notes`, `sessions`).\n *\n * - healthy: project dir exists; at least one sub-symlink is present and resolves\n * - broken: project dir exists but all sub-symlinks have missing targets\n * - orphaned: directory entry in vault has no matching registry project\n * - missing: active project with at least one Notes source but no vault dir\n */\nexport function checkHealth(vaultPath: string, db: Database): VaultHealthReport {\n const report: VaultHealthReport = {\n vaultPath,\n healthy: [],\n broken: [],\n orphaned: [],\n missing: [],\n totalProjects: 0,\n };\n\n if (!existsSync(vaultPath)) {\n return report;\n }\n\n const projects = db\n .prepare(\n `SELECT id, slug, display_name, root_path, status, claude_notes_dir, obsidian_link\n FROM projects WHERE status = 'active'`\n )\n .all() as ProjectRow[];\n\n report.totalProjects = projects.length;\n\n // Build a map of slug → project for quick lookup\n const projectsBySlug = new Map<string, ProjectRow>();\n for (const p of projects) {\n projectsBySlug.set(p.slug, p);\n }\n\n // Scan vault directory\n const seenSlugs = new Set<string>();\n let entries: string[] = [];\n try {\n entries = readdirSync(vaultPath);\n } catch {\n return report;\n }\n\n for (const entry of entries) {\n // Skip internal PAI-generated dirs/files\n if (entry.startsWith(\"_\")) continue;\n\n const fullPath = join(vaultPath, entry);\n let stat;\n try {\n stat = lstatSync(fullPath);\n } catch {\n continue;\n }\n\n const project = projectsBySlug.get(entry);\n\n if (stat.isDirectory()) {\n // New-style project directory\n if (!project) {\n // Directory in vault with no matching registry project\n report.orphaned.push({\n slug: entry,\n linkPath: fullPath,\n target: null,\n state: \"orphaned\",\n notes: \"No matching active project in registry (directory)\",\n });\n continue;\n }\n\n seenSlugs.add(entry);\n\n const notesOk = subSymlinkHealthy(fullPath, \"notes\");\n const sessionsOk = subSymlinkHealthy(fullPath, \"sessions\");\n const notesExists = subSymlinkExists(fullPath, \"notes\");\n const sessionsExists = subSymlinkExists(fullPath, \"sessions\");\n\n if (notesOk || sessionsOk) {\n const parts: string[] = [];\n if (notesOk) parts.push(\"notes\");\n if (sessionsOk) parts.push(\"sessions\");\n report.healthy.push({\n slug: entry,\n linkPath: fullPath,\n target: null,\n state: \"healthy\",\n notes: `Sources: ${parts.join(\", \")}`,\n });\n } else {\n // At least one sub-link exists but none resolves\n const broken: string[] = [];\n if (notesExists) broken.push(\"notes\");\n if (sessionsExists) broken.push(\"sessions\");\n report.broken.push({\n slug: entry,\n linkPath: fullPath,\n target: null,\n state: \"broken\",\n notes: `Broken sub-symlinks: ${broken.join(\", \") || \"(empty directory)\"}`,\n });\n }\n } else if (stat.isSymbolicLink()) {\n // Legacy flat symlink — treat same as before\n let target: string | null = null;\n try {\n target = readlinkSync(fullPath);\n } catch {\n // Can't read symlink\n }\n\n const targetExists = target !== null && existsSync(target);\n\n if (!project) {\n report.orphaned.push({\n slug: entry,\n linkPath: fullPath,\n target,\n state: \"orphaned\",\n notes: \"No matching active project in registry (legacy symlink)\",\n });\n continue;\n }\n\n seenSlugs.add(entry);\n\n if (targetExists) {\n report.healthy.push({\n slug: entry,\n linkPath: fullPath,\n target,\n state: \"healthy\",\n notes: \"(legacy flat symlink — run sync to upgrade)\",\n });\n } else {\n report.broken.push({\n slug: entry,\n linkPath: fullPath,\n target,\n state: \"broken\",\n notes: `Target missing: ${target ?? \"(unknown)\"}`,\n });\n }\n }\n // Non-symlink, non-directory entries are ignored (e.g. stray files)\n }\n\n // Find active projects with Notes/ dirs that have no vault entry\n for (const project of projects) {\n if (seenSlugs.has(project.slug)) continue;\n\n const notesCanonical = join(project.root_path, \"Notes\");\n const notesAlt = join(project.root_path, \".claude\", \"Notes\");\n const hasProjectNotes = existsSync(notesCanonical) || existsSync(notesAlt);\n const hasClaudeNotes =\n project.claude_notes_dir !== null && existsSync(project.claude_notes_dir);\n\n if (hasProjectNotes || hasClaudeNotes) {\n const sources: string[] = [];\n if (hasProjectNotes) sources.push(\"notes\");\n if (hasClaudeNotes) sources.push(\"sessions\");\n report.missing.push({\n slug: project.slug,\n linkPath: join(vaultPath, project.slug),\n target: null,\n state: \"missing\",\n notes: `Has sources (${sources.join(\", \")}) but no vault entry — run 'pai obsidian sync'`,\n });\n }\n }\n\n return report;\n}\n","/**\n * pai obsidian <sub-command>\n *\n * sync — symlink project Notes/ dirs into vault, write _index.md + _topics/\n * status — health report: healthy, broken, orphaned, missing symlinks\n * open — open vault in Obsidian app (macOS)\n */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { execSync } from \"node:child_process\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport {\n syncVault,\n generateIndex,\n generateTopicPages,\n generateMasterNotes,\n fixSessionTags,\n defaultVaultPath,\n} from \"../../obsidian/sync.js\";\nimport { checkHealth } from \"../../obsidian/status.js\";\nimport {\n loadConfig,\n expandHome,\n CONFIG_FILE,\n} from \"../../daemon/config.js\";\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport chalk from \"chalk\";\n\n// ---------------------------------------------------------------------------\n// Config helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read obsidianVaultPath from ~/.config/pai/config.json.\n * Falls back to defaultVaultPath() if not set.\n */\nfunction getVaultPath(override?: string): string {\n if (override) return expandHome(override);\n\n try {\n const raw = readFileSync(CONFIG_FILE, \"utf-8\");\n const cfg = JSON.parse(raw) as Record<string, unknown>;\n if (typeof cfg.obsidianVaultPath === \"string\" && cfg.obsidianVaultPath) {\n return expandHome(cfg.obsidianVaultPath);\n }\n } catch {\n // Config missing or unreadable — use default\n }\n return defaultVaultPath();\n}\n\n/**\n * Persist obsidianVaultPath into config.json so future syncs use the same path.\n */\nfunction saveVaultPath(vaultPath: string): void {\n let cfg: Record<string, unknown> = {};\n try {\n cfg = JSON.parse(readFileSync(CONFIG_FILE, \"utf-8\")) as Record<string, unknown>;\n } catch {\n // Start fresh if file is missing/corrupt\n }\n cfg.obsidianVaultPath = vaultPath;\n mkdirSync(dirname(CONFIG_FILE), { recursive: true });\n writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nfunction cmdSync(\n db: Database,\n opts: { vault?: string; quiet?: boolean }\n): void {\n const vaultPath = getVaultPath(opts.vault);\n const q = opts.quiet ?? false;\n\n if (!q) {\n console.log();\n console.log(header(\" PAI Obsidian Sync\"));\n console.log(dim(` Vault: ${vaultPath}`));\n console.log();\n }\n\n // --- Symlinks ---\n if (!q) process.stdout.write(\" Syncing symlinks...\");\n const stats = syncVault(vaultPath, db);\n if (!q) {\n process.stdout.write(\n `\\r ${ok(\"Symlinks:\")} created ${stats.created} updated ${stats.updated} removed ${stats.removed} stubs ${stats.stubbed}\\n`\n );\n if (stats.errors.length) {\n for (const e of stats.errors) {\n console.log(warn(` Warning: ${e}`));\n }\n }\n }\n\n // --- Index ---\n if (!q) process.stdout.write(\" Generating index...\");\n generateIndex(vaultPath, db);\n if (!q) console.log(`\\r ${ok(\"Index:\")} _index.md written `);\n\n // --- Topic pages ---\n if (!q) process.stdout.write(\" Generating topic pages...\");\n const topicCount = generateTopicPages(vaultPath, db);\n if (!q) console.log(`\\r ${ok(\"Topics:\")} ${topicCount} topic page(s) written `);\n\n // --- Fix session tags (remove generic #Session) ---\n if (!q) process.stdout.write(\" Fixing session tags...\");\n const tagStats = fixSessionTags(db);\n if (!q) {\n console.log(\n `\\r ${ok(\"Tags:\")} ${tagStats.filesModified} file(s) updated (scanned ${tagStats.filesScanned}) `\n );\n if (tagStats.errors.length) {\n for (const e of tagStats.errors) {\n console.log(warn(` Warning: ${e}`));\n }\n }\n }\n\n // --- Master notes ---\n if (!q) process.stdout.write(\" Generating master notes...\");\n const masterCount = generateMasterNotes(vaultPath, db);\n if (!q) console.log(`\\r ${ok(\"Masters:\")} ${masterCount} master note(s) written `);\n\n // Persist vault path so status/open can use it\n saveVaultPath(vaultPath);\n\n if (!q) {\n console.log();\n console.log(ok(` Done. Vault ready at: ${vaultPath}`));\n console.log();\n }\n}\n\nfunction cmdStatus(db: Database, opts: { vault?: string }): void {\n const vaultPath = getVaultPath(opts.vault);\n const report = checkHealth(vaultPath, db);\n\n console.log();\n console.log(header(\" PAI Obsidian Vault Status\"));\n console.log(dim(` Vault: ${vaultPath}`));\n console.log();\n\n if (!existsSync(vaultPath)) {\n console.log(warn(\" Vault directory does not exist. Run: pai obsidian sync\"));\n console.log();\n return;\n }\n\n // Summary line\n const healthyCount = report.healthy.length;\n const brokenCount = report.broken.length;\n const orphanedCount = report.orphaned.length;\n const missingCount = report.missing.length;\n\n console.log(\n ` ${chalk.green(\"Healthy:\")} ${healthyCount} ` +\n `${chalk.red(\"Broken:\")} ${brokenCount} ` +\n `${chalk.yellow(\"Orphaned:\")} ${orphanedCount} ` +\n `${chalk.cyan(\"Missing:\")} ${missingCount}`\n );\n console.log();\n\n if (report.healthy.length && report.healthy.length <= 10) {\n console.log(bold(\" Healthy symlinks:\"));\n for (const h of report.healthy) {\n console.log(` ${chalk.green(\"✓\")} ${bold(h.slug)} ${dim(\"→ \" + (h.target ?? \"\"))}`);\n }\n console.log();\n } else if (report.healthy.length > 10) {\n console.log(bold(` ${report.healthy.length} healthy symlinks (all good)`));\n console.log();\n }\n\n if (report.broken.length) {\n console.log(err(\" Broken symlinks (target missing):\"));\n for (const b of report.broken) {\n console.log(` ${chalk.red(\"✗\")} ${bold(b.slug)} ${dim(b.notes)}`);\n }\n console.log(dim(\" Fix: pai obsidian sync\"));\n console.log();\n }\n\n if (report.orphaned.length) {\n console.log(warn(\" Orphaned symlinks (no registry project):\"));\n for (const o of report.orphaned) {\n console.log(` ${chalk.yellow(\"?\")} ${bold(o.slug)} ${dim(o.notes)}`);\n }\n console.log();\n }\n\n if (report.missing.length) {\n console.log(warn(\" Projects with Notes/ but no vault symlink:\"));\n for (const m of report.missing) {\n console.log(` ${chalk.cyan(\"→\")} ${bold(m.slug)} ${dim(m.notes)}`);\n }\n console.log();\n }\n\n if (brokenCount === 0 && orphanedCount === 0 && missingCount === 0) {\n console.log(ok(\" Vault is healthy.\"));\n console.log();\n }\n}\n\nfunction cmdOpen(opts: { vault?: string }): void {\n const vaultPath = getVaultPath(opts.vault);\n // Derive vault name from last path component (Obsidian uses folder name as vault name)\n const parts = vaultPath.split(\"/\").filter(Boolean);\n const vaultName = parts[parts.length - 1] ?? \"obsidian-vault\";\n\n if (!existsSync(vaultPath)) {\n console.error(err(`Vault not found at: ${vaultPath}`));\n console.error(dim(\" Run: pai obsidian sync\"));\n process.exit(1);\n }\n\n const url = `obsidian://open?vault=${encodeURIComponent(vaultName)}`;\n console.log(dim(` Opening: ${url}`));\n try {\n execSync(`open \"${url}\"`, { stdio: \"ignore\" });\n console.log(ok(` Opened vault \"${vaultName}\" in Obsidian.`));\n } catch (e) {\n console.error(err(` Failed to open Obsidian: ${e}`));\n console.error(dim(\" Ensure Obsidian is installed and the vault is registered in Obsidian.\"));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerObsidianCommands(\n obsidianCmd: Command,\n getDb: () => Database\n): void {\n // pai obsidian sync\n obsidianCmd\n .command(\"sync\")\n .description(\"Sync project Notes/ dirs into vault, generate _index.md and topic pages\")\n .option(\"--vault <path>\", \"Override vault path (default: ~/.pai/obsidian-vault)\")\n .option(\"--quiet\", \"Minimal output — suitable for cron/hook use\")\n .action((opts: { vault?: string; quiet?: boolean }) => {\n cmdSync(getDb(), opts);\n });\n\n // pai obsidian status\n obsidianCmd\n .command(\"status\")\n .description(\"Show vault health: healthy, broken, orphaned, and missing symlinks\")\n .option(\"--vault <path>\", \"Override vault path\")\n .action((opts: { vault?: string }) => {\n cmdStatus(getDb(), opts);\n });\n\n // pai obsidian open\n obsidianCmd\n .command(\"open\")\n .description(\"Open the vault in Obsidian (macOS)\")\n .option(\"--vault <path>\", \"Override vault path\")\n .action((opts: { vault?: string }) => {\n cmdOpen(opts);\n });\n}\n","/** Shared utilities for zettel CLI commands. */\n\nimport type { Database } from \"better-sqlite3\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { err } from \"../../utils.js\";\n\n/** Shorten a vault path to just the last 2-3 components for display. */\nexport function shortPath(p: string, parts = 3): string {\n const segments = p.split(\"/\").filter(Boolean);\n return segments.slice(-parts).join(\"/\");\n}\n\n// Lazy-loaded federation DB singleton\nlet _fedDb: Database | null = null;\n\n/** Get (or lazily open) the PAI federation database. */\nexport function getFedDb(): Database {\n if (!_fedDb) {\n try {\n _fedDb = openFederation();\n } catch (e) {\n console.error(err(`Failed to open PAI federation DB: ${e}`));\n process.exit(1);\n }\n }\n return _fedDb;\n}\n","/** zettel explore: follow link chains from a starting note. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdExplore(\n note: string,\n opts: { depth?: string; direction?: string; mode?: string }\n): Promise<void> {\n const depth = parseInt(opts.depth ?? \"3\", 10);\n const direction = (opts.direction ?? \"both\") as \"forward\" | \"backward\" | \"both\";\n const mode = (opts.mode ?? \"all\") as \"sequential\" | \"associative\" | \"all\";\n\n const { zettelExplore } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n const result = zettelExplore(db, { startNote: note, depth, direction, mode });\n\n console.log();\n console.log(header(\" PAI Zettel Explore\"));\n console.log(dim(` Starting note: ${note}`));\n console.log(dim(` Depth: ${depth} Direction: ${direction} Mode: ${mode}`));\n console.log();\n\n if (result.nodes.length === 0) {\n console.log(warn(\" No connected notes found. Check that the note path exists in the vault index.\"));\n console.log();\n return;\n }\n\n console.log(` ${chalk.cyan(\"●\")} ${bold(shortPath(result.root))} ${dim(\"(root)\")}`);\n\n const byDepth = new Map<number, typeof result.nodes>();\n for (const node of result.nodes) {\n const list = byDepth.get(node.depth) ?? [];\n list.push(node);\n byDepth.set(node.depth, list);\n }\n\n for (let d = 1; d <= depth; d++) {\n const nodes = byDepth.get(d) ?? [];\n if (nodes.length === 0) continue;\n\n console.log();\n console.log(dim(` ${\" \".repeat(d - 1)}Depth ${d}:`));\n for (const node of nodes) {\n const indent = \" \".repeat(d);\n const isBranching = result.branchingPoints.includes(node.path);\n const typeColor = node.linkType === \"sequential\" ? chalk.blue : chalk.magenta;\n const branchMark = isBranching ? chalk.yellow(\" ⑂ branching\") : \"\";\n const title = node.title ?? shortPath(node.path);\n const stats = dim(`in:${node.inbound} out:${node.outbound}`);\n console.log(\n ` ${indent}${typeColor(\"→\")} ${bold(title)}${branchMark} ${stats} ${dim(typeColor(node.linkType))}`\n );\n }\n }\n\n console.log();\n const edgeSummary = `${result.edges.length} edges (${result.edges.filter(e => e.type === \"sequential\").length} sequential, ${result.edges.filter(e => e.type === \"associative\").length} associative)`;\n console.log(dim(` ${edgeSummary}`));\n if (result.branchingPoints.length > 0) {\n console.log(ok(` ${result.branchingPoints.length} branching point(s) found`));\n }\n if (result.maxDepthReached) {\n console.log(warn(\" Max depth reached — use --depth to explore further\"));\n }\n console.log();\n}\n\nexport function registerExploreCommand(parent: Command): void {\n parent\n .command(\"explore <note>\")\n .description(\"Follow link chains from a starting note\")\n .option(\"--depth <n>\", \"Maximum traversal depth (1-10)\", \"3\")\n .option(\"--direction <d>\", \"Link direction: forward | backward | both\", \"both\")\n .option(\"--mode <m>\", \"Edge mode: sequential | associative | all\", \"all\")\n .action(async (note: string, opts: { depth?: string; direction?: string; mode?: string }) => {\n try {\n await cmdExplore(note, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** zettel health: vault structural health audit. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdHealth(opts: {\n scope?: string;\n project?: string;\n days?: string;\n include?: string;\n}): Promise<void> {\n const scope = (opts.scope ?? \"full\") as \"full\" | \"recent\" | \"project\";\n const projectPath = opts.project;\n const recentDays = parseInt(opts.days ?? \"30\", 10);\n const includeTypes = opts.include\n ? (opts.include.split(\",\").map(s => s.trim()) as Array<\"dead_links\" | \"orphans\" | \"disconnected\" | \"low_connectivity\">)\n : undefined;\n\n const { zettelHealth } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n const result = zettelHealth(db, { scope, projectPath, recentDays, include: includeTypes });\n\n console.log();\n console.log(header(\" PAI Zettel Health\"));\n console.log(dim(` Scope: ${scope}${scope === \"project\" ? ` Path: ${projectPath ?? \"(none)\"}` : \"\"}${scope === \"recent\" ? ` Days: ${recentDays}` : \"\"}`));\n console.log();\n\n const score = result.healthScore;\n const scoreColor = score >= 80 ? chalk.green : score >= 60 ? chalk.yellow : chalk.red;\n const barWidth = 30;\n const filled = Math.round((score / 100) * barWidth);\n const bar = scoreColor(\"█\".repeat(filled)) + dim(\"░\".repeat(barWidth - filled));\n console.log(` Health Score: ${scoreColor(bold(String(score)))}% [${bar}]`);\n console.log();\n console.log(dim(` Files: ${result.totalFiles} Links: ${result.totalLinks}`));\n console.log();\n\n if (result.deadLinks.length === 0) {\n console.log(ok(\" Dead links: none\"));\n } else {\n console.log(warn(` Dead links: ${result.deadLinks.length}`));\n const preview = result.deadLinks.slice(0, 10);\n for (const dl of preview) {\n console.log(` ${chalk.red(\"✗\")} ${dim(shortPath(dl.sourcePath))} → ${bold(dl.targetRaw)} ${dim(`(line ${dl.lineNumber})`)}`);\n }\n if (result.deadLinks.length > 10) {\n console.log(dim(` ... and ${result.deadLinks.length - 10} more`));\n }\n console.log();\n }\n\n if (result.orphans.length === 0) {\n console.log(ok(\" Orphan notes: none\"));\n } else {\n console.log(warn(` Orphan notes: ${result.orphans.length}`));\n const preview = result.orphans.slice(0, 10);\n for (const o of preview) {\n console.log(` ${chalk.yellow(\"○\")} ${dim(shortPath(o))}`);\n }\n if (result.orphans.length > 10) {\n console.log(dim(` ... and ${result.orphans.length - 10} more`));\n }\n console.log();\n }\n\n if (result.disconnectedClusters <= 1) {\n console.log(ok(\" Disconnected clusters: 1 (fully connected)\"));\n } else {\n console.log(warn(` Disconnected clusters: ${result.disconnectedClusters}`));\n }\n\n if (result.lowConnectivity.length === 0) {\n console.log(ok(\" Low-connectivity: none\"));\n } else {\n console.log(warn(` Low-connectivity: ${result.lowConnectivity.length} note(s) with ≤1 link`));\n const preview = result.lowConnectivity.slice(0, 5);\n for (const lc of preview) {\n console.log(` ${chalk.dim(\"—\")} ${dim(shortPath(lc))}`);\n }\n if (result.lowConnectivity.length > 5) {\n console.log(dim(` ... and ${result.lowConnectivity.length - 5} more`));\n }\n }\n\n console.log();\n}\n\nexport function registerHealthCommand(parent: Command): void {\n parent\n .command(\"health\")\n .description(\"Vault structural health audit: dead links, orphans, connectivity\")\n .option(\"--scope <s>\", \"Scope: full | recent | project\", \"full\")\n .option(\"--project <path>\", \"Project path prefix (requires --scope project)\")\n .option(\"--days <n>\", \"Look-back window in days (requires --scope recent)\", \"30\")\n .option(\"--include <types>\", \"Comma-separated subset: dead_links,orphans,disconnected,low_connectivity\")\n .action(async (opts: { scope?: string; project?: string; days?: string; include?: string }) => {\n try {\n await cmdHealth(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** zettel surprise: find semantically similar but graph-distant notes. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdSurprise(\n note: string,\n opts: { vaultProjectId?: string; limit?: string; minSimilarity?: string; minDistance?: string }\n): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exit(1);\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const limit = parseInt(opts.limit ?? \"10\", 10);\n const minSimilarity = parseFloat(opts.minSimilarity ?? \"0.3\");\n const minGraphDistance = parseInt(opts.minDistance ?? \"3\", 10);\n\n const { zettelSurprise } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n console.log();\n console.log(header(\" PAI Zettel Surprise\"));\n console.log(dim(` Reference note: ${note}`));\n process.stdout.write(dim(\" Searching for surprising connections...\\n\"));\n\n const results = await zettelSurprise(db, {\n referencePath: note,\n vaultProjectId,\n limit,\n minSimilarity,\n minGraphDistance,\n });\n\n if (results.length === 0) {\n console.log(warn(\" No surprising connections found. Try lowering --min-similarity or --min-distance.\"));\n console.log();\n return;\n }\n\n console.log();\n console.log(bold(` Found ${results.length} surprising connection(s):`));\n console.log();\n\n for (let i = 0; i < results.length; i++) {\n const r = results[i];\n const title = r.title ?? shortPath(r.path);\n const surpriseBar = Math.round(r.surpriseScore * 10);\n console.log(` ${chalk.cyan(String(i + 1).padStart(2, \" \"))}. ${bold(title)}`);\n console.log(` ${dim(\"Surprise:\")} ${chalk.magenta(r.surpriseScore.toFixed(3))} ${\"■\".repeat(surpriseBar)}${\"□\".repeat(10 - surpriseBar)}`);\n console.log(` ${dim(\"Cosine:\")} ${r.cosineSimilarity.toFixed(3)} ${dim(\"Graph distance:\")} ${r.graphDistance}`);\n if (r.sharedSnippet) {\n console.log(` ${dim(\"Context:\")} ${r.sharedSnippet.slice(0, 120)}`);\n }\n console.log();\n }\n}\n\nexport function registerSurpriseCommand(parent: Command): void {\n parent\n .command(\"surprise <note>\")\n .description(\"Find semantically similar but graph-distant notes (surprising connections)\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--limit <n>\", \"Maximum results\", \"10\")\n .option(\"--min-similarity <f>\", \"Minimum cosine similarity (0–1)\", \"0.3\")\n .option(\"--min-distance <n>\", \"Minimum graph distance\", \"3\")\n .action(async (note: string, opts: { vaultProjectId?: string; limit?: string; minSimilarity?: string; minDistance?: string }) => {\n try {\n await cmdSurprise(note, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** zettel suggest: suggest new wikilink connections for a note. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdSuggest(\n note: string,\n opts: { vaultProjectId?: string; limit?: string; excludeLinked?: boolean }\n): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exit(1);\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const limit = parseInt(opts.limit ?? \"5\", 10);\n const excludeLinked = opts.excludeLinked !== false;\n\n const { zettelSuggest } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n console.log();\n console.log(header(\" PAI Zettel Suggest\"));\n console.log(dim(` Note: ${note}`));\n process.stdout.write(dim(\" Computing suggestions...\\n\"));\n\n const suggestions = await zettelSuggest(db, {\n notePath: note,\n vaultProjectId,\n limit,\n excludeLinked,\n });\n\n if (suggestions.length === 0) {\n console.log(warn(\" No suggestions found. The note may be well-connected already.\"));\n console.log();\n return;\n }\n\n console.log();\n console.log(bold(` ${suggestions.length} suggested connection(s):`));\n console.log();\n\n for (let i = 0; i < suggestions.length; i++) {\n const s = suggestions[i];\n const title = s.title ?? shortPath(s.path);\n console.log(` ${chalk.green(String(i + 1).padStart(2, \" \"))}. ${bold(title)}`);\n console.log(` ${dim(\"Score:\")} ${chalk.green(s.score.toFixed(3))} ${dim(\"Semantic:\")} ${s.semanticScore.toFixed(2)} ${dim(\"Tag:\")} ${s.tagScore.toFixed(2)} ${dim(\"Neighbor:\")} ${s.neighborScore.toFixed(2)}`);\n console.log(` ${dim(\"Reason:\")} ${s.reason}`);\n console.log(` ${dim(\"Wikilink:\")} ${chalk.cyan(s.suggestedWikilink)}`);\n console.log();\n }\n}\n\nexport function registerSuggestCommand(parent: Command): void {\n parent\n .command(\"suggest <note>\")\n .description(\"Suggest new wikilink connections for a note\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--limit <n>\", \"Maximum suggestions\", \"5\")\n .option(\"--no-exclude-linked\", \"Include notes already linked from this one\")\n .action(async (note: string, opts: { vaultProjectId?: string; limit?: string; excludeLinked?: boolean }) => {\n try {\n await cmdSuggest(note, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** zettel converse: ask the vault a question and get a synthesis prompt. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdConverse(\n question: string,\n opts: { vaultProjectId?: string; depth?: string; limit?: string }\n): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exit(1);\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const depth = parseInt(opts.depth ?? \"2\", 10);\n const limit = parseInt(opts.limit ?? \"15\", 10);\n\n const { zettelConverse } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n console.log();\n console.log(header(\" PAI Zettel Converse\"));\n console.log(dim(` Question: \"${question}\"`));\n process.stdout.write(dim(\" Searching vault for relevant notes...\\n\"));\n\n const result = await zettelConverse(db, { question, vaultProjectId, depth, limit });\n\n if (result.relevantNotes.length === 0) {\n console.log(warn(\" No relevant notes found. Try rephrasing your question.\"));\n console.log();\n return;\n }\n\n console.log();\n console.log(bold(` ${result.relevantNotes.length} relevant note(s) from ${result.domains.length} domain(s):`));\n console.log(dim(` Domains: ${result.domains.join(\", \")}`));\n console.log();\n\n for (const note of result.relevantNotes) {\n const title = note.title ?? shortPath(note.path);\n console.log(` ${chalk.cyan(\"◆\")} ${bold(title)} ${dim(`[${note.domain}] score: ${note.score.toFixed(3)}`)}`);\n if (note.snippet) {\n console.log(` ${dim(note.snippet.slice(0, 200))}`);\n }\n console.log();\n }\n\n if (result.connections.length > 0) {\n console.log(bold(\" Cross-domain connections:\"));\n for (const conn of result.connections.slice(0, 10)) {\n console.log(\n ` ${chalk.magenta(\"⟷\")} ${dim(conn.fromDomain)} ${chalk.dim(\"→\")} ${dim(conn.toDomain)} ` +\n `${dim(shortPath(conn.fromPath))} → ${dim(shortPath(conn.toPath))} ` +\n `${dim(`strength: ${conn.strength}`)}`\n );\n }\n console.log();\n }\n\n console.log(bold(\" Synthesis prompt (paste into your AI):\"));\n console.log();\n const promptLines = result.synthesisPrompt.split(\"\\n\");\n for (const line of promptLines) {\n console.log(` ${dim(line)}`);\n }\n console.log();\n}\n\nexport function registerConverseCommand(parent: Command): void {\n parent\n .command(\"converse <question>\")\n .description(\"Ask the vault a question and get a synthesis prompt with relevant notes\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--depth <n>\", \"Graph expansion depth around matched notes\", \"2\")\n .option(\"--limit <n>\", \"Maximum relevant notes to include\", \"15\")\n .action(async (question: string, opts: { vaultProjectId?: string; depth?: string; limit?: string }) => {\n try {\n await cmdConverse(question, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** zettel themes: detect emerging theme clusters in recently edited notes. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdThemes(opts: {\n vaultProjectId?: string;\n days?: string;\n minSize?: string;\n maxThemes?: string;\n threshold?: string;\n}): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exit(1);\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const lookbackDays = parseInt(opts.days ?? \"30\", 10);\n const minClusterSize = parseInt(opts.minSize ?? \"3\", 10);\n const maxThemes = parseInt(opts.maxThemes ?? \"10\", 10);\n const similarityThreshold = parseFloat(opts.threshold ?? \"0.65\");\n\n const { zettelThemes } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n console.log();\n console.log(header(\" PAI Zettel Themes\"));\n console.log(dim(` Lookback: ${lookbackDays}d Min cluster: ${minClusterSize} Threshold: ${similarityThreshold}`));\n process.stdout.write(dim(\" Detecting emerging themes...\\n\"));\n\n const result = await zettelThemes(db, {\n vaultProjectId,\n lookbackDays,\n minClusterSize,\n maxThemes,\n similarityThreshold,\n });\n\n if (result.themes.length === 0) {\n console.log(warn(` No themes detected in the last ${lookbackDays} days. Try --days with a larger window.`));\n console.log();\n return;\n }\n\n const fromDate = new Date(result.timeWindow.from).toISOString().slice(0, 10);\n const toDate = new Date(result.timeWindow.to).toISOString().slice(0, 10);\n\n console.log();\n console.log(bold(` ${result.themes.length} theme(s) from ${result.totalNotesAnalyzed} notes [${fromDate} → ${toDate}]:`));\n console.log();\n\n for (let i = 0; i < result.themes.length; i++) {\n const cluster = result.themes[i];\n const diversityBar = Math.round(cluster.folderDiversity * 10);\n const indexSuggestion = cluster.suggestIndexNote ? chalk.yellow(\" ⚑ suggest index note\") : \"\";\n\n console.log(` ${chalk.cyan(String(i + 1).padStart(2, \" \"))}. ${bold(cluster.label)}${indexSuggestion}`);\n console.log(\n ` ${dim(\"Notes:\")} ${cluster.size} ` +\n `${dim(\"Diversity:\")} ${\"█\".repeat(diversityBar)}${\"░\".repeat(10 - diversityBar)} ${cluster.folderDiversity.toFixed(2)} ` +\n `${dim(\"Linked:\")} ${Math.round(cluster.linkedRatio * 100)}%`\n );\n\n const preview = cluster.notes.slice(0, 5);\n for (const note of preview) {\n const title = note.title ?? shortPath(note.path);\n console.log(` ${dim(\"•\")} ${title}`);\n }\n if (cluster.notes.length > 5) {\n console.log(dim(` ... and ${cluster.notes.length - 5} more`));\n }\n console.log();\n }\n}\n\nexport function registerThemesCommand(parent: Command): void {\n parent\n .command(\"themes\")\n .description(\"Detect emerging theme clusters in recently edited notes\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--days <n>\", \"Look-back window in days\", \"30\")\n .option(\"--min-size <n>\", \"Minimum notes per cluster\", \"3\")\n .option(\"--max-themes <n>\", \"Maximum themes to return\", \"10\")\n .option(\"--threshold <f>\", \"Similarity threshold for clustering (0–1)\", \"0.65\")\n .action(async (opts: { vaultProjectId?: string; days?: string; minSize?: string; maxThemes?: string; threshold?: string }) => {\n try {\n await cmdThemes(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** Barrel: registers all zettel sub-commands and exports registerZettelCommands. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { registerExploreCommand } from \"./explore.js\";\nimport { registerHealthCommand } from \"./health.js\";\nimport { registerSurpriseCommand } from \"./surprise.js\";\nimport { registerSuggestCommand } from \"./suggest.js\";\nimport { registerConverseCommand } from \"./converse.js\";\nimport { registerThemesCommand } from \"./themes.js\";\n\nexport function registerZettelCommands(\n parent: Command,\n _getDb: () => Database // registry DB (unused — federation DB is opened directly)\n): void {\n registerExploreCommand(parent);\n registerHealthCommand(parent);\n registerSurpriseCommand(parent);\n registerSuggestCommand(parent);\n registerConverseCommand(parent);\n registerThemesCommand(parent);\n}\n","/**\n * pai observation <sub-command>\n *\n * list — List recent observations with filtering options\n * search — Search observations by title/narrative\n * stats — Show observation statistics\n */\n\nimport type { Command } from \"commander\";\nimport { createConnection } from \"net\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport chalk from \"chalk\";\n\n// ---------------------------------------------------------------------------\n// IPC helper — communicates with PAI daemon via Unix socket\n// ---------------------------------------------------------------------------\n\nfunction ipcCall(method: string, params: Record<string, unknown>): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let settled = false;\n const settle = (fn: () => void) => { if (!settled) { settled = true; fn(); } };\n\n const client = createConnection(\"/tmp/pai.sock\", () => {\n client.write(JSON.stringify({ id: 1, method, params }) + \"\\n\");\n });\n let data = \"\";\n client.on(\"data\", (chunk) => {\n data += chunk.toString();\n // Daemon sends a single JSON line then closes — try parsing eagerly\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n client.end();\n settle(() => resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\")));\n } catch {\n // Incomplete — wait for more data or end\n }\n });\n client.on(\"end\", () => {\n settle(() => {\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\"));\n } catch (e) { reject(e); }\n });\n });\n client.on(\"error\", (e) => settle(() => reject(e)));\n setTimeout(() => { client.destroy(); settle(() => reject(new Error(\"IPC timeout\"))); }, 10000);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Type colour mapping\n// ---------------------------------------------------------------------------\n\nfunction typeColor(type: string): string {\n switch (type) {\n case \"decision\": return chalk.cyan(type);\n case \"bugfix\": return chalk.red(type);\n case \"feature\": return chalk.green(type);\n case \"refactor\": return chalk.yellow(type);\n case \"discovery\": return chalk.blue(type);\n case \"change\": return chalk.magenta(type);\n default: return chalk.white(type);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Date formatting helper\n// ---------------------------------------------------------------------------\n\nfunction fmtTs(ts: string | null | undefined): string {\n if (!ts) return dim(\"—\");\n try {\n const d = new Date(ts);\n const date = d.toISOString().slice(0, 10);\n const time = d.toISOString().slice(11, 16);\n return `${date} ${time}`;\n } catch {\n return dim(\"—\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Truncate a string to maxLen visible characters\n// ---------------------------------------------------------------------------\n\nfunction trunc(s: string, maxLen: number): string {\n if (!s) return \"\";\n if (s.length <= maxLen) return s;\n return s.slice(0, maxLen - 1) + \"…\";\n}\n\n// ---------------------------------------------------------------------------\n// Observation shape (from IPC responses)\n// ---------------------------------------------------------------------------\n\ninterface Observation {\n id: number;\n type: string;\n title: string;\n project_slug?: string | null;\n session_id?: string | null;\n created_at: string;\n narrative?: string | null;\n}\n\ninterface ObservationStats {\n total: number;\n by_type: Array<{ type: string; count: number }>;\n by_project: Array<{ project_slug: string | null; count: number }>;\n most_recent: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai observation list\n// ---------------------------------------------------------------------------\n\nasync function cmdList(opts: {\n project?: string;\n type?: string;\n session?: string;\n limit?: string;\n}): Promise<void> {\n const limit = parseInt(opts.limit ?? \"20\", 10);\n\n const params: Record<string, unknown> = { limit };\n if (opts.project) params.project_slug = opts.project;\n if (opts.type) params.type = opts.type;\n if (opts.session) params.session_id = opts.session;\n\n let observations: Observation[];\n try {\n observations = (await ipcCall(\"observation_list\", params)) as Observation[];\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exit(1);\n }\n\n console.log();\n console.log(header(\" PAI Observations\"));\n\n const filterParts: string[] = [];\n if (opts.project) filterParts.push(`project: ${opts.project}`);\n if (opts.type) filterParts.push(`type: ${opts.type}`);\n if (opts.session) filterParts.push(`session: ${opts.session}`);\n filterParts.push(`limit: ${limit}`);\n console.log(dim(` ${filterParts.join(\" | \")}`));\n console.log();\n\n if (!observations || observations.length === 0) {\n console.log(warn(\" No observations found.\"));\n console.log();\n return;\n }\n\n // Column widths\n const ID_W = 4;\n const TYPE_W = 10;\n const TITLE_W = 42;\n const PROJ_W = 14;\n const TS_W = 16;\n\n // Header row\n console.log(\n \" \" +\n bold(\"id\".padEnd(ID_W)) + \" \" +\n bold(\"type\".padEnd(TYPE_W)) + \" \" +\n bold(\"title\".padEnd(TITLE_W)) + \" \" +\n bold(\"project\".padEnd(PROJ_W)) + \" \" +\n bold(\"created_at\")\n );\n console.log(\n dim(\n \" \" +\n \"-\".repeat(ID_W) + \" \" +\n \"-\".repeat(TYPE_W) + \" \" +\n \"-\".repeat(TITLE_W) + \" \" +\n \"-\".repeat(PROJ_W) + \" \" +\n \"-\".repeat(TS_W)\n )\n );\n\n for (const obs of observations) {\n const idStr = String(obs.id).padStart(ID_W, \" \");\n const typeStr = typeColor(obs.type ?? \"\").padEnd(TYPE_W + (typeColor(obs.type ?? \"\").length - (obs.type ?? \"\").length));\n const titleStr = trunc(obs.title ?? \"\", TITLE_W).padEnd(TITLE_W);\n const projStr = trunc(obs.project_slug ?? \"—\", PROJ_W).padEnd(PROJ_W);\n const tsStr = fmtTs(obs.created_at);\n\n console.log(` ${idStr} ${typeStr} ${titleStr} ${projStr} ${dim(tsStr)}`);\n }\n\n console.log();\n console.log(dim(` ${observations.length} observation(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai observation search <query>\n// ---------------------------------------------------------------------------\n\nasync function cmdSearch(\n query: string,\n opts: { project?: string; type?: string; limit?: string }\n): Promise<void> {\n const limit = parseInt(opts.limit ?? \"20\", 10);\n\n const params: Record<string, unknown> = { query, limit };\n if (opts.project) params.project_slug = opts.project;\n if (opts.type) params.type = opts.type;\n\n let allObservations: Observation[];\n try {\n // Fetch more than requested to allow client-side text filtering\n allObservations = (await ipcCall(\"observation_query\", { ...params, limit: limit * 5, query: undefined })) as Observation[];\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exit(1);\n }\n\n // Client-side text filter (observation_query doesn't support text search)\n const q = query.toLowerCase();\n const observations = allObservations\n .filter(o =>\n (o.title ?? \"\").toLowerCase().includes(q) ||\n (o.narrative ?? \"\").toLowerCase().includes(q) ||\n (o.tool_input_summary ?? \"\").toLowerCase().includes(q)\n )\n .slice(0, limit);\n\n console.log();\n console.log(header(\" PAI Observation Search\"));\n console.log(dim(` Query: \"${query}\"${opts.project ? ` project: ${opts.project}` : \"\"}${opts.type ? ` type: ${opts.type}` : \"\"} limit: ${limit}`));\n console.log();\n\n if (!observations || observations.length === 0) {\n console.log(warn(` No observations matching \"${query}\".`));\n console.log();\n return;\n }\n\n for (let i = 0; i < observations.length; i++) {\n const obs = observations[i];\n const idx = chalk.dim(String(i + 1).padStart(3, \" \"));\n const type = typeColor(obs.type ?? \"\");\n const title = bold(obs.title ?? \"(untitled)\");\n const proj = obs.project_slug ? dim(`[${obs.project_slug}]`) : dim(\"[—]\");\n const ts = dim(fmtTs(obs.created_at));\n const id = dim(`#${obs.id}`);\n\n console.log(` ${idx} ${type.padEnd(12)} ${title}`);\n console.log(` ${proj} ${ts} ${id}`);\n\n if (obs.narrative) {\n const snippet = trunc(obs.narrative.replace(/\\n/g, \" \"), 160);\n console.log(` ${dim(snippet)}`);\n }\n console.log();\n }\n\n console.log(dim(` ${observations.length} result(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai observation stats\n// ---------------------------------------------------------------------------\n\nasync function cmdStats(): Promise<void> {\n let stats: ObservationStats;\n try {\n stats = (await ipcCall(\"observation_stats\", {})) as ObservationStats;\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exit(1);\n }\n\n console.log();\n console.log(header(\" PAI Observation Statistics\"));\n console.log();\n\n console.log(` ${bold(\"Total observations:\")} ${chalk.cyan(String(stats.total ?? 0))}`);\n if (stats.most_recent) {\n console.log(` ${bold(\"Most recent:\")} ${dim(fmtTs(stats.most_recent))}`);\n }\n console.log();\n\n // By type\n if (stats.by_type && stats.by_type.length > 0) {\n console.log(bold(\" By type:\"));\n const maxCount = Math.max(...stats.by_type.map((r) => r.count));\n for (const row of stats.by_type) {\n const barWidth = 20;\n const filled = Math.round((row.count / maxCount) * barWidth);\n const bar = chalk.cyan(\"█\".repeat(filled)) + dim(\"░\".repeat(barWidth - filled));\n const label = typeColor(row.type).padEnd(12 + (typeColor(row.type).length - row.type.length));\n console.log(` ${label} ${bar} ${String(row.count).padStart(5)}`);\n }\n console.log();\n }\n\n // By project\n if (stats.by_project && stats.by_project.length > 0) {\n console.log(bold(\" By project:\"));\n const maxCount = Math.max(...stats.by_project.map((r) => r.count));\n const show = stats.by_project.slice(0, 15);\n for (const row of show) {\n const barWidth = 20;\n const filled = Math.round((row.count / maxCount) * barWidth);\n const bar = chalk.green(\"█\".repeat(filled)) + dim(\"░\".repeat(barWidth - filled));\n const label = (row.project_slug ?? \"—\").padEnd(20);\n console.log(` ${dim(label)} ${bar} ${String(row.count).padStart(5)}`);\n }\n if (stats.by_project.length > 15) {\n console.log(dim(` ... and ${stats.by_project.length - 15} more project(s)`));\n }\n console.log();\n }\n\n if ((!stats.by_type || stats.by_type.length === 0) && (!stats.by_project || stats.by_project.length === 0)) {\n console.log(warn(\" No observation data yet.\"));\n console.log();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerObservationCommands(parent: Command): void {\n // pai observation list\n parent\n .command(\"list\")\n .description(\"List recent observations\")\n .option(\"--project <slug>\", \"Filter by project slug\")\n .option(\"--type <type>\", \"Filter by type (decision, bugfix, feature, refactor, discovery, change)\")\n .option(\"--session <id>\", \"Filter by session ID\")\n .option(\"--limit <n>\", \"Maximum results\", \"20\")\n .action(async (opts: { project?: string; type?: string; session?: string; limit?: string }) => {\n try {\n await cmdList(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n\n // pai observation search <query>\n parent\n .command(\"search <query>\")\n .description(\"Search observations by title or narrative text\")\n .option(\"--project <slug>\", \"Filter by project slug\")\n .option(\"--type <type>\", \"Filter by type\")\n .option(\"--limit <n>\", \"Maximum results\", \"20\")\n .action(async (query: string, opts: { project?: string; type?: string; limit?: string }) => {\n try {\n await cmdSearch(query, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n\n // pai observation stats\n parent\n .command(\"stats\")\n .description(\"Show observation statistics: totals, by type, by project\")\n .action(async () => {\n try {\n await cmdStats();\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/**\n * pai skill <sub-command>\n *\n * telemetry — Show skill-invocation telemetry (self-educating skill system).\n *\n * The `skill` namespace is reserved for the wider self-educating loop\n * (search / install / audit / trial) — see Notes/swarm/skills-self-educating.md.\n */\n\nimport type { Command } from \"commander\";\nimport { createConnection } from \"net\";\nimport { warn, err, dim, bold, header } from \"../utils.js\";\nimport chalk from \"chalk\";\n\n// ---------------------------------------------------------------------------\n// IPC helper — communicates with PAI daemon via Unix socket\n// ---------------------------------------------------------------------------\n\nfunction ipcCall(method: string, params: Record<string, unknown>): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let settled = false;\n const settle = (fn: () => void) => { if (!settled) { settled = true; fn(); } };\n\n const client = createConnection(\"/tmp/pai.sock\", () => {\n client.write(JSON.stringify({ id: 1, method, params }) + \"\\n\");\n });\n let data = \"\";\n client.on(\"data\", (chunk) => {\n data += chunk.toString();\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n client.end();\n settle(() => resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\")));\n } catch {\n // Incomplete — wait for more data or end\n }\n });\n client.on(\"end\", () => {\n settle(() => {\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\"));\n } catch (e) { reject(e); }\n });\n });\n client.on(\"error\", (e) => settle(() => reject(e)));\n setTimeout(() => { client.destroy(); settle(() => reject(new Error(\"IPC timeout\"))); }, 10000);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fmtTs(ts: string | null | undefined): string {\n if (!ts) return dim(\"—\");\n try {\n const d = new Date(ts);\n return `${d.toISOString().slice(0, 10)} ${d.toISOString().slice(11, 16)}`;\n } catch {\n return dim(\"—\");\n }\n}\n\nfunction statusColor(status: string): string {\n switch (status) {\n case \"active\": return chalk.green(status);\n case \"trial\": return chalk.yellow(status);\n case \"archived\": return chalk.dim(status);\n default: return chalk.white(status);\n }\n}\n\nfunction trunc(s: string, maxLen: number): string {\n if (!s) return \"\";\n return s.length <= maxLen ? s : s.slice(0, maxLen - 1) + \"…\";\n}\n\ninterface SkillTelemetry {\n id: number;\n scope: string;\n skill_name: string;\n source: string;\n status: string;\n trigger_count: number;\n accept_count: number;\n first_triggered: string;\n last_triggered: string;\n context_projects: string[];\n hash: string | null;\n audit_status: string | null;\n last_audited: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai skill telemetry\n// ---------------------------------------------------------------------------\n\nasync function cmdTelemetry(opts: {\n scope?: string;\n status?: string;\n limit?: string;\n json?: boolean;\n}): Promise<void> {\n const params: Record<string, unknown> = {\n scope: opts.scope ?? \"default\",\n limit: parseInt(opts.limit ?? \"50\", 10),\n };\n if (opts.status) params.status = opts.status;\n\n let rows: SkillTelemetry[];\n try {\n rows = (await ipcCall(\"skill_telemetry_query\", params)) as SkillTelemetry[];\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exit(1);\n }\n\n if (opts.json) {\n console.log(JSON.stringify(rows ?? [], null, 2));\n return;\n }\n\n console.log();\n console.log(header(\" Skill Telemetry\"));\n console.log(dim(` scope: ${params.scope}${opts.status ? ` | status: ${opts.status}` : \"\"}`));\n console.log();\n\n if (!rows || rows.length === 0) {\n console.log(warn(\" No skill telemetry yet.\"));\n console.log(dim(\" Invoke a skill (e.g. /name) and it will be recorded.\"));\n console.log();\n return;\n }\n\n const NAME_W = 24;\n const SRC_W = 10;\n const STAT_W = 9;\n const TRIG_W = 8;\n const SEEN_W = 16;\n\n console.log(\n \" \" +\n bold(\"skill\".padEnd(NAME_W)) + \" \" +\n bold(\"source\".padEnd(SRC_W)) + \" \" +\n bold(\"status\".padEnd(STAT_W)) + \" \" +\n bold(\"triggers\".padStart(TRIG_W)) + \" \" +\n bold(\"last used\".padEnd(SEEN_W)) + \" \" +\n bold(\"projects\")\n );\n console.log(dim(\" \" + \"-\".repeat(NAME_W + SRC_W + STAT_W + TRIG_W + SEEN_W + 18)));\n\n for (const r of rows) {\n const name = trunc(r.skill_name ?? \"\", NAME_W).padEnd(NAME_W);\n const src = trunc(r.source ?? \"\", SRC_W).padEnd(SRC_W);\n const stat = statusColor(r.status ?? \"\");\n const statPad = stat.padEnd(STAT_W + (stat.length - (r.status ?? \"\").length));\n const trig = String(r.trigger_count ?? 0).padStart(TRIG_W);\n const seen = fmtTs(r.last_triggered).padEnd(SEEN_W);\n const projs = Array.isArray(r.context_projects) ? r.context_projects.join(\", \") : \"\";\n console.log(` ${chalk.white(name)} ${dim(src)} ${statPad} ${chalk.cyan(trig)} ${dim(seen)} ${dim(trunc(projs, 30))}`);\n }\n\n console.log();\n console.log(dim(` ${rows.length} skill(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerSkillCommands(parent: Command): void {\n parent\n .command(\"telemetry\")\n .description(\"Show skill-invocation telemetry (triggers, last used, projects)\")\n .option(\"--scope <scope>\", \"Governance scope\", \"default\")\n .option(\"--status <status>\", \"Filter by status: trial | active | archived\")\n .option(\"--limit <n>\", \"Maximum rows\", \"50\")\n .option(\"--json\", \"Output raw JSON\")\n .action(async (opts: { scope?: string; status?: string; limit?: string; json?: boolean }) => {\n try {\n await cmdTelemetry(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/**\n * pai update — Update PAI from the GitHub repository without losing customizations.\n *\n * Steps:\n * 1. Verify we're in a git repo with a remote (origin)\n * 2. Stash any local changes\n * 3. Pull latest from origin/main\n * 4. Pop the stash if there was one (handle conflicts gracefully)\n * 5. Rebuild: bun install && bun run build\n * 6. Restart the daemon (SIGHUP or pai daemon restart)\n * 7. Check if the CLAUDE.md template has changed and offer to refresh\n * 8. Run pai registry scan to update markers\n * 9. Report what changed\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { execSync, spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction line(text = \"\") {\n console.log(text);\n}\n\nfunction step(msg: string) {\n console.log(chalk.bold.cyan(`\\n ${msg}`));\n}\n\nfunction info(msg: string) {\n console.log(dim(` ${msg}`));\n}\n\nfunction success(msg: string) {\n console.log(ok(` ${msg}`));\n}\n\nfunction warning(msg: string) {\n console.log(warn(` ${msg}`));\n}\n\nfunction error(msg: string) {\n console.log(err(` ${msg}`));\n}\n\n/**\n * Run a shell command, streaming output to stdout.\n * Returns true on success, false on failure.\n */\nfunction run(cmd: string, cwd?: string): boolean {\n try {\n execSync(cmd, {\n stdio: \"inherit\",\n cwd: cwd ?? process.cwd(),\n env: process.env,\n });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Run a shell command silently, returning stdout or null on failure.\n */\nfunction capture(cmd: string, cwd?: string): string | null {\n try {\n return execSync(cmd, {\n stdio: \"pipe\",\n cwd: cwd ?? process.cwd(),\n env: process.env,\n }).toString().trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Get the PAI source directory: walk up from dist/cli/ or src/cli/ to the\n * package root where package.json lives.\n */\nfunction getPaiSrcDir(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // dist/cli/index.mjs → dist/ → package root\n return join(__dirname, \"../..\");\n}\n\n/**\n * Locate the templates directory relative to the installed package.\n */\nfunction getTemplatesDir(): string {\n const candidates = [\n join(getPaiSrcDir(), \"templates\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"templates\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@mnott\", \"pai\", \"templates\"),\n ];\n for (const c of candidates) {\n if (existsSync(join(c, \"claude-md.template.md\"))) return c;\n }\n return join(getPaiSrcDir(), \"templates\");\n}\n\n// ---------------------------------------------------------------------------\n// Update steps\n// ---------------------------------------------------------------------------\n\n/**\n * Step 1: Verify git repo + remote.\n * Returns the PAI source directory (repo root) or null if not a git repo.\n */\nfunction stepVerifyRepo(): string | null {\n step(\"Checking repository...\");\n\n const paiDir = getPaiSrcDir();\n\n // Check git repo\n const gitDir = capture(\"git rev-parse --show-toplevel\", paiDir);\n if (!gitDir) {\n error(\"Not a git repository. Cannot update automatically.\");\n info(\"Install PAI via npm to get automatic updates: npm install -g @tekmidian/pai\");\n return null;\n }\n\n // Check remote\n const remote = capture(\"git remote get-url origin\", gitDir);\n if (!remote) {\n error(\"No 'origin' remote configured. Cannot pull updates.\");\n info(\"Add a remote: git remote add origin https://github.com/mnott/PAI.git\");\n return null;\n }\n\n info(`Repository: ${gitDir}`);\n info(`Remote: ${remote}`);\n return gitDir;\n}\n\n/**\n * Step 2: Stash local changes.\n * Returns true if something was stashed (so we need to pop later).\n */\nfunction stepStash(repoDir: string): boolean {\n step(\"Stashing local changes...\");\n\n // Check if there are any changes to stash\n const status = capture(\"git status --porcelain\", repoDir);\n if (!status) {\n info(\"Working tree is clean — nothing to stash.\");\n return false;\n }\n\n info(\"Uncommitted changes found:\");\n const statusLines = status.split(\"\\n\").slice(0, 5);\n for (const l of statusLines) {\n info(` ${l}`);\n }\n if (status.split(\"\\n\").length > 5) {\n info(` ... and ${status.split(\"\\n\").length - 5} more`);\n }\n\n const result = capture(\"git stash push -m 'pai update: auto-stash'\", repoDir);\n if (result === null) {\n warning(\"Could not stash changes. Proceeding anyway — merge conflicts may occur.\");\n return false;\n }\n\n if (result.includes(\"No local changes to save\")) {\n info(\"Nothing to stash.\");\n return false;\n }\n\n success(\"Changes stashed.\");\n return true;\n}\n\n/**\n * Step 3: Pull latest from origin/main.\n * Returns the git log summary of new commits or null if already up to date.\n */\nfunction stepPull(repoDir: string): string | null {\n step(\"Pulling latest from origin/main...\");\n\n // Get current HEAD before pull\n const headBefore = capture(\"git rev-parse HEAD\", repoDir);\n\n const pulled = run(\"git pull origin main\", repoDir);\n if (!pulled) {\n warning(\"git pull failed. There may be merge conflicts with your stash.\");\n return null;\n }\n\n // Get HEAD after pull\n const headAfter = capture(\"git rev-parse HEAD\", repoDir);\n\n if (headBefore === headAfter) {\n success(\"Already up to date — no new commits.\");\n return null;\n }\n\n // Show what changed\n const log = capture(\n `git log --oneline ${headBefore ?? \"\"}..${headAfter ?? \"HEAD\"}`,\n repoDir,\n );\n if (log) {\n success(\"New commits pulled:\");\n for (const l of log.split(\"\\n\")) {\n info(` ${l}`);\n }\n }\n\n return log;\n}\n\n/**\n * Step 4: Pop stash (if we stashed anything).\n */\nfunction stepPopStash(repoDir: string): void {\n step(\"Restoring local changes (git stash pop)...\");\n\n const result = spawnSync(\"git\", [\"stash\", \"pop\"], {\n cwd: repoDir,\n stdio: \"pipe\",\n encoding: \"utf8\",\n });\n\n if (result.status === 0) {\n success(\"Local changes restored.\");\n } else {\n const stderr = result.stderr ?? \"\";\n if (stderr.includes(\"CONFLICT\")) {\n warning(\"Stash pop caused merge conflicts. Resolve them manually:\");\n info(\" git status\");\n info(\" # Edit conflicting files\");\n info(\" git add <files>\");\n info(\" git stash drop\");\n } else {\n warning(`Stash pop encountered an issue: ${stderr.trim() || \"unknown error\"}`);\n info(\" Run: git stash pop\");\n info(\" Or: git stash list (to see stashed changes)\");\n }\n }\n}\n\n/**\n * Step 5: Rebuild.\n */\nfunction stepBuild(repoDir: string): boolean {\n step(\"Rebuilding PAI (bun install && bun run build)...\");\n\n info(\"Installing dependencies...\");\n const installed = run(\"bun install\", repoDir);\n if (!installed) {\n error(\"bun install failed.\");\n return false;\n }\n\n info(\"Building...\");\n const built = run(\"bun run build\", repoDir);\n if (!built) {\n error(\"bun run build failed.\");\n return false;\n }\n\n success(\"Build complete.\");\n return true;\n}\n\n/**\n * Step 6: Restart the daemon.\n * Tries SIGHUP first (graceful reload); falls back to pai daemon restart.\n */\nfunction stepRestartDaemon(repoDir: string): void {\n step(\"Restarting PAI daemon...\");\n\n // Try to find the daemon PID from the socket/pid file\n const pidFile = \"/tmp/pai-daemon.pid\";\n if (existsSync(pidFile)) {\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf8\").trim(), 10);\n if (!isNaN(pid) && pid > 0) {\n const killed = capture(`kill -HUP ${pid}`);\n if (killed !== null) {\n success(`Sent SIGHUP to daemon (PID ${pid}).`);\n return;\n }\n }\n } catch {\n // fall through to pai daemon restart\n }\n }\n\n // Fall back to pai daemon restart subcommand\n const result = spawnSync(\"pai\", [\"daemon\", \"restart\"], {\n cwd: repoDir,\n stdio: \"pipe\",\n encoding: \"utf8\",\n timeout: 10_000,\n });\n\n if (result.status === 0) {\n success(\"Daemon restarted.\");\n } else {\n warning(\"Could not restart daemon automatically.\");\n info(\" Restart manually: pai daemon restart\");\n info(\" Or: pai daemon serve\");\n }\n}\n\n/**\n * Step 7: Check if CLAUDE.md template changed and offer to refresh.\n */\nfunction stepRefreshClaudeMd(repoDir: string, newCommitsLog: string | null): void {\n step(\"Checking CLAUDE.md template...\");\n\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"claude-md.template.md\");\n const claudeMd = join(homedir(), \".claude\", \"CLAUDE.md\");\n\n if (!existsSync(templatePath)) {\n info(\"Template not found — skipping CLAUDE.md check.\");\n return;\n }\n\n if (!existsSync(claudeMd)) {\n info(\"No ~/.claude/CLAUDE.md found — skipping.\");\n return;\n }\n\n const userContent = readFileSync(claudeMd, \"utf8\");\n const isPaiGenerated = userContent.includes(\"Generated by PAI Setup\");\n\n if (!isPaiGenerated) {\n warning(\"~/.claude/CLAUDE.md appears to be custom (not PAI-generated).\");\n info(\" Skipping auto-update. Review the new template manually:\");\n info(` ${templatePath}`);\n return;\n }\n\n // Check if the template was actually modified in the new commits\n let templateChanged = false;\n if (newCommitsLog) {\n // Check git diff on the template file for newly pulled commits\n const diff = capture(\n \"git diff HEAD~1..HEAD -- templates/claude-md.template.md\",\n repoDir,\n );\n templateChanged = !!(diff && diff.trim().length > 0);\n }\n\n if (!templateChanged) {\n info(\"CLAUDE.md template unchanged in this update.\");\n return;\n }\n\n info(\"CLAUDE.md template was updated in this release.\");\n info(\"Refreshing ~/.claude/CLAUDE.md (PAI-generated — safe to overwrite)...\");\n\n let template = readFileSync(templatePath, \"utf8\");\n // Substitute ${HOME} with actual home directory\n template = template.replace(/\\$\\{HOME\\}/g, homedir());\n writeFileSync(claudeMd, template, \"utf8\");\n\n success(\"~/.claude/CLAUDE.md refreshed from updated template.\");\n}\n\n/**\n * Step 8: Run pai registry scan.\n */\nfunction stepRegistryScan(repoDir: string): void {\n step(\"Running pai registry scan...\");\n\n const result = spawnSync(\"pai\", [\"registry\", \"scan\"], {\n cwd: repoDir,\n stdio: \"inherit\",\n timeout: 60_000,\n });\n\n if (result.status === 0) {\n success(\"Registry scan complete.\");\n } else {\n warning(\"Registry scan encountered issues.\");\n info(\" Run manually: pai registry scan\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main update action\n// ---------------------------------------------------------------------------\n\nasync function runUpdate(): Promise<void> {\n line();\n console.log(chalk.bold.cyan(\" ╔═══════════════════════════════════╗\"));\n console.log(chalk.bold.cyan(\" ║ PAI Knowledge OS — Update ║\"));\n console.log(chalk.bold.cyan(\" ╚═══════════════════════════════════╝\"));\n line();\n\n // Step 1: Verify repo\n const repoDir = stepVerifyRepo();\n if (!repoDir) {\n line();\n process.exit(1);\n }\n\n // Step 2: Stash local changes\n const didStash = stepStash(repoDir);\n\n // Step 3: Pull\n const newCommitsLog = stepPull(repoDir);\n\n // Step 4: Pop stash (only if we stashed something)\n if (didStash) {\n stepPopStash(repoDir);\n }\n\n // If no new commits and no stash needed, we can short-circuit after\n // confirming things are current. But we still offer rebuild in case the\n // user's build is stale.\n\n // Step 5: Build\n const buildOk = stepBuild(repoDir);\n if (!buildOk) {\n line();\n error(\"Update failed at build step. Check errors above.\");\n line();\n process.exit(1);\n }\n\n // Step 6: Restart daemon\n stepRestartDaemon(repoDir);\n\n // Step 7: CLAUDE.md refresh\n stepRefreshClaudeMd(repoDir, newCommitsLog);\n\n // Step 8: Registry scan\n stepRegistryScan(repoDir);\n\n // Summary\n line();\n console.log(chalk.bold.cyan(\" ─────────────────────────────────────\"));\n if (newCommitsLog) {\n success(\"PAI updated successfully!\");\n } else {\n success(\"PAI is up to date and rebuilt successfully!\");\n }\n console.log(dim(\" Version: \") + chalk.cyan(capture(\"pai --version\", repoDir) ?? \"unknown\"));\n line();\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerUpdateCommand(program: Command): void {\n program\n .command(\"update\")\n .description(\n \"Update PAI from GitHub (git pull + rebuild + daemon restart). Preserves local customizations.\",\n )\n .action(async () => {\n await runUpdate();\n });\n}\n","/**\n * pai notify <sub-command>\n *\n * status — Show current notification mode and active channels\n * get — Alias for status\n * set — Set notification mode or channel/routing config\n * test — Send a test notification through configured channels\n * send — Send a notification (event + message)\n *\n * All sub-commands communicate with the running daemon via IPC.\n */\n\nimport type { Command } from \"commander\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { PaiClient } from \"../../daemon/ipc-client.js\";\nimport type {\n NotificationConfig,\n NotificationMode,\n NotificationEvent,\n} from \"../../notifications/types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction makeClient(): PaiClient {\n const config = loadConfig();\n return new PaiClient(config.socketPath);\n}\n\nfunction modeColor(mode: NotificationMode): string {\n switch (mode) {\n case \"off\":\n return warn(mode);\n case \"voice\":\n return bold(ok(mode));\n case \"auto\":\n return ok(mode);\n default:\n return ok(mode);\n }\n}\n\nfunction printConfig(config: NotificationConfig, activeChannels: string[]): void {\n console.log();\n console.log(header(\" PAI Notification Config\"));\n console.log();\n console.log(` ${bold(\"Mode:\")} ${modeColor(config.mode)}`);\n console.log();\n console.log(` ${bold(\"Channels:\")}`);\n for (const [ch, cfg] of Object.entries(config.channels)) {\n const c = cfg as { enabled: boolean; url?: string; voiceName?: string };\n const status = c.enabled ? ok(\"enabled\") : dim(\"disabled\");\n let extra = \"\";\n if (ch === \"ntfy\" && c.url) extra = dim(` ${c.url}`);\n if (ch === \"voice\" && c.voiceName) extra = dim(` voice: ${c.voiceName}`);\n console.log(` ${bold(ch.padEnd(12))}${status}${extra}`);\n }\n console.log();\n console.log(` ${bold(\"Active:\")} ${activeChannels.length > 0 ? activeChannels.join(\", \") : dim(\"(none)\")}`);\n console.log();\n console.log(` ${bold(\"Routing:\")} ${dim(\"(auto mode only)\")}`);\n for (const [event, channels] of Object.entries(config.routing)) {\n const ch = (channels as string[]).join(\", \") || dim(\"(none)\");\n console.log(` ${event.padEnd(12)}→ ${ch}`);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nasync function cmdStatus(): Promise<void> {\n const client = makeClient();\n try {\n const { config, activeChannels } = await client.getNotificationConfig();\n printConfig(config, activeChannels);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.log();\n console.log(warn(\" Cannot reach PAI daemon.\"));\n console.log(dim(` ${msg}`));\n console.log(dim(\" Start it with: pai daemon serve\"));\n console.log();\n process.exit(1);\n }\n}\n\nasync function cmdSet(opts: {\n mode?: string;\n ntfyUrl?: string;\n ntfyPriority?: string;\n whatsappRecipient?: string;\n enableChannel?: string[];\n disableChannel?: string[];\n}): Promise<void> {\n const client = makeClient();\n\n // Build the patch object\n const patch: {\n mode?: NotificationMode;\n channels?: Record<string, unknown>;\n } = {};\n\n if (opts.mode) {\n const validModes: NotificationMode[] = [\n \"auto\", \"voice\", \"whatsapp\", \"ntfy\", \"macos\", \"cli\", \"off\",\n ];\n if (!validModes.includes(opts.mode as NotificationMode)) {\n console.error(err(`Invalid mode: ${opts.mode}. Valid: ${validModes.join(\", \")}`));\n process.exit(1);\n }\n patch.mode = opts.mode as NotificationMode;\n }\n\n // Channel enable/disable\n const channels: Record<string, unknown> = {};\n\n if (opts.enableChannel) {\n for (const ch of opts.enableChannel) {\n channels[ch] = { enabled: true };\n }\n }\n\n if (opts.disableChannel) {\n for (const ch of opts.disableChannel) {\n channels[ch] = { enabled: false };\n }\n }\n\n if (opts.ntfyUrl) {\n channels[\"ntfy\"] = {\n ...(channels[\"ntfy\"] as object ?? {}),\n url: opts.ntfyUrl,\n };\n }\n\n if (opts.ntfyPriority) {\n channels[\"ntfy\"] = {\n ...(channels[\"ntfy\"] as object ?? {}),\n priority: opts.ntfyPriority,\n };\n }\n\n if (opts.whatsappRecipient) {\n channels[\"whatsapp\"] = {\n ...(channels[\"whatsapp\"] as object ?? {}),\n recipient: opts.whatsappRecipient,\n };\n }\n\n if (Object.keys(channels).length > 0) {\n patch.channels = channels;\n }\n\n if (!patch.mode && !patch.channels) {\n console.error(err(\"No changes specified. Use --mode, --enable, --disable, etc.\"));\n console.log(dim(\" Example: pai notify set --mode voice\"));\n console.log(dim(\" Example: pai notify set --mode auto --enable macos --disable ntfy\"));\n process.exit(1);\n }\n\n try {\n const { config } = await client.setNotificationConfig(\n patch as Parameters<typeof client.setNotificationConfig>[0]\n );\n console.log();\n console.log(ok(\" Notification config updated.\"));\n console.log(` ${bold(\"Mode:\")} ${modeColor(config.mode)}`);\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` Failed: ${msg}`));\n process.exit(1);\n }\n}\n\nasync function cmdTest(opts: { event?: string }): Promise<void> {\n const client = makeClient();\n const event = (opts.event ?? \"info\") as NotificationEvent;\n\n console.log();\n console.log(dim(` Sending test ${event} notification...`));\n\n try {\n const result = await client.sendNotification({\n event,\n message: `PAI test notification (${event})`,\n title: \"PAI Test\",\n });\n\n if (result.channelsSucceeded.length === 0 && result.channelsAttempted.length > 0) {\n console.log(warn(\" All channels failed.\"));\n } else if (result.channelsAttempted.length === 0) {\n console.log(warn(\" No channels active for this event. Check mode and channel config.\"));\n } else {\n console.log(ok(` Sent to: ${result.channelsSucceeded.join(\", \")}`));\n }\n\n if (result.channelsFailed.length > 0) {\n console.log(warn(` Failed: ${result.channelsFailed.join(\", \")}`));\n }\n\n console.log(dim(` Mode: ${result.mode}`));\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` Failed: ${msg}`));\n process.exit(1);\n }\n}\n\nasync function cmdSend(\n event: string,\n message: string,\n opts: { title?: string }\n): Promise<void> {\n const client = makeClient();\n const validEvents: NotificationEvent[] = [\n \"error\", \"progress\", \"completion\", \"info\", \"debug\",\n ];\n\n if (!validEvents.includes(event as NotificationEvent)) {\n console.error(\n err(`Invalid event: ${event}. Valid: ${validEvents.join(\", \")}`)\n );\n process.exit(1);\n }\n\n try {\n const result = await client.sendNotification({\n event: event as NotificationEvent,\n message,\n title: opts.title,\n });\n\n if (result.channelsSucceeded.length > 0) {\n console.log(ok(` Sent to: ${result.channelsSucceeded.join(\", \")}`));\n } else {\n console.log(warn(\" No channels received the notification.\"));\n }\n\n if (result.channelsFailed.length > 0) {\n console.log(warn(` Failed: ${result.channelsFailed.join(\", \")}`));\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` Failed: ${msg}`));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerNotifyCommands(notifyCmd: Command): void {\n // pai notify status (default / alias for get)\n notifyCmd\n .command(\"status\")\n .description(\"Show current notification mode and active channels\")\n .action(async () => {\n await cmdStatus();\n });\n\n // pai notify get (alias for status)\n notifyCmd\n .command(\"get\")\n .description(\"Show current notification config (alias for status)\")\n .action(async () => {\n await cmdStatus();\n });\n\n // pai notify set\n notifyCmd\n .command(\"set\")\n .description(\"Update notification mode or channel configuration\")\n .option(\n \"--mode <mode>\",\n \"Notification mode: auto | voice | whatsapp | ntfy | macos | cli | off\"\n )\n .option(\"--enable <channel...>\", \"Enable one or more channels\")\n .option(\"--disable <channel...>\", \"Disable one or more channels\")\n .option(\"--ntfy-url <url>\", \"Set the ntfy.sh topic URL\")\n .option(\n \"--ntfy-priority <level>\",\n \"Set ntfy priority: min | low | default | high | urgent\"\n )\n .option(\"--whatsapp-recipient <contact>\", \"Set WhatsApp recipient\")\n .action(\n async (opts: {\n mode?: string;\n enable?: string[];\n disable?: string[];\n ntfyUrl?: string;\n ntfyPriority?: string;\n whatsappRecipient?: string;\n }) => {\n await cmdSet({\n mode: opts.mode,\n enableChannel: opts.enable,\n disableChannel: opts.disable,\n ntfyUrl: opts.ntfyUrl,\n ntfyPriority: opts.ntfyPriority,\n whatsappRecipient: opts.whatsappRecipient,\n });\n }\n );\n\n // pai notify test\n notifyCmd\n .command(\"test\")\n .description(\"Send a test notification through configured channels\")\n .option(\n \"--event <event>\",\n \"Event type to test: error | progress | completion | info | debug\",\n \"info\"\n )\n .action(async (opts: { event?: string }) => {\n await cmdTest(opts);\n });\n\n // pai notify send <event> <message>\n notifyCmd\n .command(\"send <event> <message>\")\n .description(\"Send a notification with an explicit event type and message\")\n .option(\"--title <title>\", \"Optional notification title\")\n .action(async (event: string, message: string, opts: { title?: string }) => {\n await cmdSend(event, message, opts);\n });\n}\n","/**\n * dispatch.ts — Hand tasks to the PAI session that owns them\n *\n * Transport is AIBroker, and it is optional. With it, a task reaches a running\n * session or spawns one. Without it, dispatch degrades to reporting who owns\n * what and leaves acting to the user — PAI must stay useful on machines that\n * do not run iTerm2.\n *\n * See Notes/docs/task-bus.md.\n */\n\nimport type { DispatchResult, Task } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Transport\n// ---------------------------------------------------------------------------\n\n/** What a transport reports back. Mirrors DispatchOutcome minus the states\n * the bus decides for itself (`unrouted`, and `skipped` when disabled). */\nexport interface TransportResult {\n outcome: \"delivered\" | \"spawned\" | \"unlaunchable\" | \"unreachable\" | \"skipped\";\n session?: string;\n reason?: string;\n}\n\n/**\n * The subset of AIBroker the bus needs.\n *\n * Deliberately one atomic call rather than liveSessions/launch/send. Deciding\n * \"is it running? no — launch it, then send\" is session-lifecycle logic, which\n * belongs to whoever owns sessions. Splitting it across a process boundary\n * would duplicate that logic here and race: a session can start or die between\n * the check and the send.\n *\n * Declared as an interface rather than imported so the module carries no hard\n * dependency on AIBroker being installed.\n */\nexport interface Transport {\n dispatch(\n project: string,\n message: string,\n opts: { spawnIfAbsent: boolean },\n ): Promise<TransportResult>;\n}\n\nexport interface DispatchOptions {\n transport?: Transport | null;\n /** When false, report ownership without contacting any session. */\n autoDispatch: boolean;\n /** Launch a session when the owner is not running. Default: true. */\n spawnIfAbsent?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Message\n// ---------------------------------------------------------------------------\n\n/**\n * Render a task as a session-to-session message.\n *\n * The body is included in full and deliberately not summarised: the whole point\n * of the description convention is that a task is actionable months later\n * without re-deriving anything, and truncating it here would undo that.\n */\nexport function renderTask(task: Task): string {\n const parts = [`Task from the bus: ${task.title}`];\n if (task.due) parts.push(`Due: ${task.due}`);\n if (task.priority !== \"p4\") parts.push(`Priority: ${task.priority}`);\n if (task.body.trim()) parts.push(\"\", task.body.trim());\n if (task.sourceUrl) parts.push(\"\", `Source: ${task.sourceUrl}`);\n parts.push(\n \"\",\n `Tracker id: ${task.id}`,\n `When done, run \\`pai task done ${task.id}\\` so it is not dispatched again.`,\n \"No reply is expected — the process that sent this has already exited.\",\n \"If you are blocked, file it back to the tracker with `pai task add` rather than replying.\",\n );\n return parts.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Dispatch\n// ---------------------------------------------------------------------------\n\nexport async function dispatchTask(\n task: Task,\n opts: DispatchOptions,\n): Promise<DispatchResult> {\n if (task.owner.project === null) {\n return {\n task,\n outcome: \"unrouted\",\n reason: task.owner.rawHint\n ? `\"${task.owner.rawHint}\" matches no PAI project`\n : \"no owner label or container\",\n };\n }\n\n const project = task.owner.project;\n\n if (!opts.autoDispatch || !opts.transport) {\n return {\n task,\n outcome: \"skipped\",\n session: project,\n reason: opts.transport\n ? \"autoDispatch is off\"\n : \"no transport available — run `pai <project>` to pick this up\",\n };\n }\n\n let result: TransportResult;\n try {\n result = await opts.transport.dispatch(project, renderTask(task), {\n spawnIfAbsent: opts.spawnIfAbsent !== false,\n });\n } catch (e) {\n // A transport that throws is an infrastructure failure, not a routing\n // decision — surface it rather than reporting the task as skipped.\n return {\n task,\n outcome: \"unlaunchable\",\n session: project,\n reason: e instanceof Error ? e.message : String(e),\n };\n }\n\n // Only projects with a curated alias can be launched. That is a setup gap,\n // not a bug — name the fix so it is actionable without digging.\n if (result.outcome === \"unlaunchable\" && !result.reason?.includes(\"pai project name\")) {\n return {\n task,\n outcome: \"unlaunchable\",\n session: result.session ?? project,\n reason: `${result.reason ?? \"no session could be launched\"} — give it an alias with \\`pai project name <identifier> ${project}\\``,\n };\n }\n\n return { task, outcome: result.outcome, session: result.session ?? project, reason: result.reason };\n}\n\n/**\n * Dispatch a batch, sequentially.\n *\n * Deliberately not parallel: dispatching spawns terminal tabs and types into\n * them. Racing that would interleave input across sessions.\n */\nexport async function dispatchAll(\n tasks: Task[],\n opts: DispatchOptions,\n): Promise<DispatchResult[]> {\n const results: DispatchResult[] = [];\n for (const task of tasks) {\n results.push(await dispatchTask(task, opts));\n }\n return results;\n}\n","/**\n * aibroker.ts — AIBroker transport for the task bus\n *\n * Shells out to the `aibroker` CLI. PAI's CLI cannot call MCP tools, so the\n * MCP-only dispatch primitives are unreachable from a shell — this is the\n * bridge.\n *\n * Optional by design: when AIBroker is not installed, `detect()` returns null\n * and the bus reports ownership instead of delivering. PAI must stay useful on\n * machines that do not run iTerm2.\n *\n * See Notes/docs/task-bus.md.\n */\n\nimport { execFile } from \"node:child_process\";\nimport type { ProbeAnswer } from \"../poller.js\";\nimport type { Transport, TransportResult } from \"../dispatch.js\";\n\n/**\n * How long AIBroker may spend on one dispatch, in seconds.\n *\n * A cold spawn measured ~10s on an idle machine, but boot time is not bounded:\n * under load a session can take considerably longer to start accepting input.\n */\nconst DEFAULT_DISPATCH_TIMEOUT_SECS = 180;\n\n/**\n * Margin between AIBroker's own deadline and when we kill the process.\n *\n * These two timeouts must never disagree. If ours fired first we would kill a\n * dispatch mid-flight and report a transport failure that AIBroker cannot\n * reproduce from its own CLI — a phantom bug, in the other repo, with no trace\n * on either side. So we always pass our budget down via `--timeout` and give\n * it room to time out first and tell us why.\n */\nconst KILL_MARGIN_MS = 15_000;\n\ninterface WireResult {\n outcome?: string;\n project?: string;\n session?: string;\n reason?: string;\n}\n\nconst VALID_OUTCOMES = new Set([\n \"delivered\",\n \"spawned\",\n \"unlaunchable\",\n \"unreachable\",\n \"skipped\",\n]);\n\n/**\n * Run `aibroker dispatch`, feeding the message over stdin.\n *\n * stdin rather than argv is deliberate: task bodies carry the full procedure\n * and reasoning, so they are long, multi-line, and full of quotes and\n * backticks. argv would mangle them or hit length limits.\n */\nfunction run(bin: string, args: string[], stdin: string, killAfterMs: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = execFile(\n bin,\n args,\n { timeout: killAfterMs, maxBuffer: 1024 * 1024 },\n (error, stdout, stderr) => {\n // A non-zero exit carrying parseable JSON is a reported outcome, not a\n // crash — let the caller interpret it rather than throwing here.\n if (error && !stdout.trim().startsWith(\"{\")) {\n reject(new Error(stderr.trim() || error.message));\n return;\n }\n resolve(stdout);\n },\n );\n child.stdin?.end(stdin);\n });\n}\n\nexport class AiBrokerTransport implements Transport {\n constructor(\n private readonly bin = \"aibroker\",\n private readonly timeoutSecs = DEFAULT_DISPATCH_TIMEOUT_SECS,\n ) {}\n\n async dispatch(\n project: string,\n message: string,\n opts: { spawnIfAbsent: boolean },\n ): Promise<TransportResult> {\n const args = [\"dispatch\", project, \"--stdin\", \"--json\", \"--timeout\", String(this.timeoutSecs)];\n if (!opts.spawnIfAbsent) args.push(\"--no-spawn\");\n\n const stdout = await run(\n this.bin,\n args,\n message,\n this.timeoutSecs * 1000 + KILL_MARGIN_MS,\n );\n\n // The CLI prints diagnostics before its JSON, so take the last JSON object\n // rather than assuming stdout is clean.\n const start = stdout.lastIndexOf(\"{\");\n if (start === -1) {\n throw new Error(`aibroker returned no JSON: ${stdout.trim().slice(0, 200)}`);\n }\n\n let wire: WireResult;\n try {\n wire = JSON.parse(stdout.slice(start)) as WireResult;\n } catch {\n throw new Error(`aibroker returned malformed JSON: ${stdout.trim().slice(0, 200)}`);\n }\n\n // Treat an unrecognised outcome as unlaunchable rather than trusting it.\n // Silently passing through an unknown string would report success for a\n // task that never arrived.\n if (!wire.outcome || !VALID_OUTCOMES.has(wire.outcome)) {\n return {\n outcome: \"unlaunchable\",\n session: wire.session ?? project,\n reason: `unexpected outcome from aibroker: ${String(wire.outcome)}`,\n };\n }\n\n return {\n outcome: wire.outcome as TransportResult[\"outcome\"],\n session: wire.session ?? wire.project ?? project,\n reason: wire.reason,\n };\n }\n}\n\n/**\n * Liveness prober backed by `aibroker ask`.\n *\n * Separate from Transport on purpose: asking must NEVER spawn. A probe that\n * creates the session it is probing for turns \"this session died\" into \"all is\n * well\", which is the failure the probe exists to catch.\n */\nexport class AiBrokerProber {\n constructor(\n private readonly bin = \"aibroker\",\n private readonly timeoutSecs = 60,\n ) {}\n\n async ask(project: string, question: string): Promise<ProbeAnswer> {\n let stdout: string;\n try {\n stdout = await run(\n this.bin,\n [\"ask\", project, \"--stdin\", \"--json\", \"--timeout\", String(this.timeoutSecs)],\n question,\n this.timeoutSecs * 1000 + KILL_MARGIN_MS,\n );\n } catch (e) {\n return { state: \"silent\", reason: e instanceof Error ? e.message : String(e) };\n }\n\n const start = stdout.lastIndexOf(\"{\");\n if (start === -1) return { state: \"silent\", reason: \"aibroker returned no JSON\" };\n\n try {\n const wire = JSON.parse(stdout.slice(start)) as {\n replied?: boolean;\n reply?: string;\n reason?: string;\n state?: string;\n };\n\n // Trust `state` when present. Fall back to `replied` for an older\n // aibroker that predates it — but never infer \"busy\" from silence, which\n // is the whole reason the fourth state exists.\n const state = wire.state ?? (wire.replied === true ? \"replied\" : \"silent\");\n if (state === \"replied\" || state === \"busy\" || state === \"silent\" || state === \"absent\") {\n return { state, reply: wire.reply, reason: wire.reason };\n }\n // An unrecognised state must not read as healthy.\n return { state: \"silent\", reason: `unexpected state from aibroker: ${String(wire.state)}` };\n } catch {\n return { state: \"silent\", reason: \"aibroker returned malformed JSON\" };\n }\n }\n}\n\n/**\n * Return a prober if the installed `aibroker` supports `ask`.\n *\n * Probes for the subcommand rather than the binary: an older AIBroker would\n * otherwise fail once per stuck task instead of the scheduler simply knowing it\n * has no liveness check and saying so.\n */\nexport async function detectProber(bin = \"aibroker\"): Promise<AiBrokerProber | null> {\n try {\n const help = await new Promise<string>((resolve, reject) => {\n execFile(bin, [\"help\"], { timeout: 10_000 }, (error, stdout, stderr) => {\n if (error && !stdout) reject(error);\n else resolve(stdout + stderr);\n });\n });\n return /\\bask\\b/.test(help) ? new AiBrokerProber(bin) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Return a transport if the `aibroker` CLI is present and supports dispatch.\n *\n * Probing for the subcommand rather than just the binary matters: AIBroker\n * shipped for a long time without `dispatch`, and an older install would\n * otherwise fail once per task instead of degrading cleanly up front.\n */\nexport async function detectAiBroker(\n bin = \"aibroker\",\n timeoutSecs = DEFAULT_DISPATCH_TIMEOUT_SECS,\n): Promise<Transport | null> {\n try {\n const help = await new Promise<string>((resolve, reject) => {\n execFile(bin, [\"help\"], { timeout: 10_000 }, (error, stdout, stderr) => {\n if (error && !stdout) reject(error);\n else resolve(stdout + stderr);\n });\n });\n return help.includes(\"dispatch\") ? new AiBrokerTransport(bin, timeoutSecs) : null;\n } catch {\n return null;\n }\n}\n","/**\n * scheduler.ts — Todoist-driven routine scheduler\n *\n * Todoist holds the schedule, the ordering, the dependencies and the run state.\n * This module is a dumb poller: it reads that state, decides what should happen\n * now, and reports. No LLM, so a tick costs nothing but an API call.\n *\n * Measured against the live Todoist API before this was written:\n *\n * - Completing a recurring task does NOT create a new one. The SAME task\n * rolls forward: same id, new due date, immediately unchecked. So \"is it\n * closed?\" never detects completion for a recurring task — it is closed for\n * an instant at most. Completion is \"the due date advanced\".\n *\n * - Labels survive that roll-forward. A running marker will therefore NOT\n * clear itself; the poller has to reconcile it, or the task looks\n * permanently in flight and silently never runs again.\n *\n * - Todoist never accumulates missed occurrences. Three days powered off\n * leaves ONE overdue task, not three, and completing it jumps to the next\n * future occurrence. There is no backlog to drain — but equally, Todoist\n * cannot tell you how many windows were missed, so overdue-by-N is computed\n * here.\n *\n * - Writing `due_date` DESTROYS a recurrence rule, silently turning a routine\n * into a one-off. This module must only ever write `due_string`.\n */\n\nimport type { Task } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Label vocabulary\n// ---------------------------------------------------------------------------\n\n/** Set when dispatched, cleared when the due date advances. */\nexport const RUNNING_LABEL = \"pai-running\";\n\n/** Run however late. The default when nothing is specified. */\nexport const CATCHUP_LABEL = \"pai-catchup\";\n\n/** `pai-skip-if-late:4h` — past that, the window is gone; wait for the next. */\nconst SKIP_IF_LATE_RE = /^pai-skip-if-late:(\\d+)([hm])$/i;\n\n/** Fallback when a task has no learned duration yet. */\nexport const DEFAULT_EXPECTED_MINUTES = 30;\n\n/** Probe once the run exceeds expected x this. */\nconst PROBE_FACTOR = 1.5;\n\n/** Consecutive unanswered probes before a run counts as stuck. */\nexport const STUCK_AFTER_FAILED_PROBES = 3;\n\n// ---------------------------------------------------------------------------\n// Decisions\n// ---------------------------------------------------------------------------\n\nexport type Decision =\n /** Not due yet. */\n | { action: \"wait\"; task: Task }\n /** Due (or overdue within policy) and not running — dispatch it. */\n | { action: \"dispatch\"; task: Task; overdueMinutes: number }\n /** Overdue past its skip-if-late window — leave for the next occurrence. */\n | { action: \"skip\"; task: Task; reason: string }\n /** Running and within its expected duration — leave alone. */\n | { action: \"running\"; task: Task; elapsedMinutes: number }\n /** Running and overrun — ask the session whether it is alive. */\n | { action: \"probe\"; task: Task; elapsedMinutes: number; expectedMinutes: number }\n /** Was running, due date has advanced — it finished. */\n | { action: \"complete\"; task: Task; durationMinutes: number | null }\n /** Marked running but nothing knows about it — reconcile. */\n | { action: \"orphaned\"; task: Task; reason: string };\n\n/** In-flight run bookkeeping. Transient: losing it costs one extra probe. */\nexport interface RunState {\n /** Task id → epoch ms when dispatched. */\n startedAt: Record<string, number>;\n /** Task id → consecutive probes with no reply. */\n failedProbes: Record<string, number>;\n}\n\nexport const EMPTY_RUN_STATE: RunState = { startedAt: {}, failedProbes: {} };\n\n// ---------------------------------------------------------------------------\n// Policy helpers\n// ---------------------------------------------------------------------------\n\nexport function isRunning(task: Task): boolean {\n return task.labels.some((l) => l.toLowerCase() === RUNNING_LABEL);\n}\n\n/** Minutes after which a missed window is abandoned, or null for \"always run\". */\nexport function skipIfLateMinutes(task: Task): number | null {\n for (const label of task.labels) {\n const m = SKIP_IF_LATE_RE.exec(label.trim());\n if (m) {\n const n = Number.parseInt(m[1]!, 10);\n return m[2]!.toLowerCase() === \"h\" ? n * 60 : n;\n }\n }\n return null;\n}\n\n/**\n * How overdue a task is, in minutes. Negative means still in the future.\n *\n * A date-only due (\"2026-08-01\") is treated as due at the start of that day,\n * which is what Todoist shows the user.\n */\nexport function overdueMinutes(task: Task, now: number): number {\n if (!task.due) return Number.NEGATIVE_INFINITY;\n const due = task.due.length <= 10 ? `${task.due}T00:00:00` : task.due;\n const dueMs = new Date(due).getTime();\n if (Number.isNaN(dueMs)) return Number.NEGATIVE_INFINITY;\n return Math.floor((now - dueMs) / 60_000);\n}\n\n// ---------------------------------------------------------------------------\n// Duration learning\n// ---------------------------------------------------------------------------\n\n/**\n * Expected runtime for a task.\n *\n * The mean rather than the max, deliberately: with a liveness probe available,\n * guessing low costs one extra probe, while guessing high leaves a dead session\n * undetected for far longer. The probe is what makes a tight estimate safe.\n *\n * Runs that ended in stuck or re-dispatch must never be fed in here — one hung\n * run would inflate the mean and every later run would inherit a too-generous\n * threshold.\n */\nexport function expectedMinutes(observed: number[], fallback = DEFAULT_EXPECTED_MINUTES): number {\n const clean = observed.filter((n) => Number.isFinite(n) && n > 0);\n if (clean.length === 0) return fallback;\n const recent = clean.slice(-5);\n return Math.max(1, Math.round(recent.reduce((a, b) => a + b, 0) / recent.length));\n}\n\n// ---------------------------------------------------------------------------\n// The decision function\n// ---------------------------------------------------------------------------\n\nexport interface DecideOptions {\n now: number;\n state: RunState;\n /** Task id → observed durations in minutes, most recent last. */\n history?: Record<string, number[]>;\n}\n\n/**\n * Decide what should happen to one task on this tick.\n *\n * Pure: no I/O, no clock reads. Everything comes in via options so the state\n * machine can be tested against a fixed clock rather than by waiting.\n */\nexport function decide(task: Task, opts: DecideOptions): Decision {\n const { now, state } = opts;\n const running = isRunning(task);\n const overdue = overdueMinutes(task, now);\n const startedAt = state.startedAt[task.id];\n\n if (running) {\n // Completion is \"the due date moved into the future\" — not \"closed\".\n // A recurring task is closed for an instant at most, so testing for closed\n // would never fire.\n if (overdue < 0) {\n const duration = startedAt ? Math.max(1, Math.round((now - startedAt) / 60_000)) : null;\n return { action: \"complete\", task, durationMinutes: duration };\n }\n\n if (startedAt === undefined) {\n // Labelled running, but we have no record of starting it. Either the\n // state file was lost, or a previous run died without cleaning up.\n // Probing is the safe response — never assume it is dead and re-dispatch.\n return {\n action: \"orphaned\",\n task,\n reason: \"marked running but no start time is known — probe before assuming anything\",\n };\n }\n\n const elapsed = Math.max(0, Math.round((now - startedAt) / 60_000));\n const expected = expectedMinutes(opts.history?.[task.id] ?? []);\n if (elapsed >= expected * PROBE_FACTOR) {\n return { action: \"probe\", task, elapsedMinutes: elapsed, expectedMinutes: expected };\n }\n return { action: \"running\", task, elapsedMinutes: elapsed };\n }\n\n if (overdue < 0) return { action: \"wait\", task };\n\n const limit = skipIfLateMinutes(task);\n if (limit !== null && overdue > limit) {\n return {\n action: \"skip\",\n task,\n reason: `overdue by ${overdue}m, past its ${limit}m window — waiting for the next occurrence`,\n };\n }\n\n return { action: \"dispatch\", task, overdueMinutes: Math.max(0, overdue) };\n}\n\n/**\n * Order tasks for dispatch.\n *\n * Subtasks of the same parent run in Todoist's own order, so a routine is\n * sequenced by dragging its steps in the UI. Everything else falls back to\n * priority then due time, so the most overdue urgent thing goes first.\n */\nexport function dispatchOrder(tasks: Task[]): Task[] {\n const rank = { p1: 0, p2: 1, p3: 2, p4: 3 } as const;\n return [...tasks].sort((a, b) => {\n const p = rank[a.priority] - rank[b.priority];\n if (p !== 0) return p;\n return (a.due ?? \"\").localeCompare(b.due ?? \"\");\n });\n}\n","/**\n * poller.ts — one scheduler tick\n *\n * Reads Todoist, decides, acts, reports. Run by launchd on an interval; carries\n * no LLM, so a tick that finds nothing to do costs one API call and no tokens.\n *\n * Run state (start times, probe counts) is deliberately local and transient.\n * Losing it costs one extra probe, not a wrong decision — `decide` returns\n * \"orphaned\" rather than assuming a task is dead. The durable state — schedule,\n * order, running flag, learned durations — all lives in Todoist, so a new\n * machine picks up where this one left off.\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readJsonStrict, writeJsonAtomic } from \"../config/json-store.js\";\nimport type { TodoistProvider } from \"./providers/todoist.js\";\nimport type { Transport } from \"./dispatch.js\";\nimport { dispatchTask } from \"./dispatch.js\";\nimport {\n decide,\n dispatchOrder,\n isRunning,\n RUNNING_LABEL,\n STUCK_AFTER_FAILED_PROBES,\n type Decision,\n expectedMinutes,\n type RunState,\n EMPTY_RUN_STATE,\n} from \"./scheduler.js\";\nimport type { Task } from \"./types.js\";\n\nconst STATE_FILE = join(homedir(), \".pai\", \"scheduler-state.json\");\n\n/** How many past durations to keep per task. */\nconst HISTORY_LIMIT = 5;\n\ninterface PersistedState extends RunState {\n /** Task id → observed durations in minutes, most recent last. */\n history: Record<string, number[]>;\n /** Task id → epoch ms of the last completion we reported. */\n lastReported: Record<string, number>;\n}\n\nconst EMPTY: PersistedState = { ...EMPTY_RUN_STATE, history: {}, lastReported: {} };\n\n/**\n * Run state is a rebuildable cache, so a damaged file must not block the\n * scheduler forever — starting fresh is the correct recovery here, which is\n * exactly the case json-store's guard is NOT for.\n */\nfunction loadState(): PersistedState {\n try {\n const raw = readJsonStrict(STATE_FILE, \"~/.pai/scheduler-state.json\");\n return { ...EMPTY, ...(raw as unknown as PersistedState) };\n } catch {\n return { ...EMPTY };\n }\n}\n\nfunction saveState(state: PersistedState): void {\n writeJsonAtomic(STATE_FILE, state as unknown as Record<string, unknown>, { backup: false });\n}\n\n// ---------------------------------------------------------------------------\n// Liveness probe\n// ---------------------------------------------------------------------------\n\n/**\n * What a probe found.\n *\n * The distinction that matters is `busy` versus `silent`. Claude Code queues\n * typed input while mid-turn and does not read it until the turn ends, so a\n * session doing exactly the work we gave it stays silent for minutes. Since we\n * probe at expected x1.5 — precisely when a slow task is most likely still\n * working — treating silence as death would have declared healthy sessions\n * stuck on ordinary days and \"recovered\" them out from under themselves.\n *\n * AIBroker therefore decides liveness by sampling the screen before sending\n * anything: a working session animates, an idle one is static. `busy` is\n * positive evidence of life and must never count toward the stuck threshold.\n */\nexport type ProbeState = \"replied\" | \"busy\" | \"silent\" | \"absent\";\n\nexport interface ProbeAnswer {\n state: ProbeState;\n reply?: string;\n reason?: string;\n}\n\nexport interface Prober {\n /** Ask the owning session whether it is still working. */\n ask(project: string, question: string): Promise<ProbeAnswer>;\n}\n\n/**\n * Multiple of the expected duration past which a task is flagged even though it\n * keeps reporting `busy`. A session can be alive and still wrong — stuck in a\n * loop, waiting on a prompt that will never come. Liveness is not progress.\n */\nconst GROSS_OVERRUN_FACTOR = 5;\n\n// ---------------------------------------------------------------------------\n// Tick\n// ---------------------------------------------------------------------------\n\nexport interface TickOptions {\n provider: TodoistProvider;\n transport: Transport | null;\n prober: Prober | null;\n autoDispatch: boolean;\n dryRun: boolean;\n now?: number;\n}\n\nexport interface TickReport {\n decisions: Array<{ decision: Decision; note: string }>;\n dispatched: number;\n completed: number;\n stuck: number;\n probed: number;\n}\n\nexport async function tick(opts: TickOptions): Promise<TickReport> {\n const now = opts.now ?? Date.now();\n const state = loadState();\n const report: TickReport = { decisions: [], dispatched: 0, completed: 0, stuck: 0, probed: 0 };\n\n const tasks = await opts.provider.listOpen({ includeUnrouted: true });\n const ordered = dispatchOrder(tasks);\n\n for (const task of ordered) {\n const d = decide(task, { now, state, history: state.history });\n let note = \"\";\n\n switch (d.action) {\n case \"wait\":\n continue; // not worth reporting\n\n case \"skip\":\n note = d.reason;\n break;\n\n case \"running\":\n note = `${d.elapsedMinutes}m elapsed`;\n break;\n\n case \"dispatch\":\n note = await handleDispatch(task, d.overdueMinutes, opts, state, now);\n if (!opts.dryRun) report.dispatched++;\n break;\n\n case \"complete\":\n note = await handleComplete(task, d.durationMinutes, opts, state);\n if (!opts.dryRun) report.completed++;\n break;\n\n case \"probe\":\n case \"orphaned\": {\n const elapsed = d.action === \"probe\" ? d.elapsedMinutes : null;\n const result = await handleProbe(task, elapsed, opts, state);\n note = result.note;\n if (!opts.dryRun) {\n report.probed++;\n if (result.stuck) report.stuck++;\n }\n break;\n }\n }\n\n report.decisions.push({ decision: d, note });\n }\n\n if (!opts.dryRun) saveState(state);\n return report;\n}\n\n// ---------------------------------------------------------------------------\n// Actions\n// ---------------------------------------------------------------------------\n\nasync function handleDispatch(\n task: Task,\n overdue: number,\n opts: TickOptions,\n state: PersistedState,\n now: number\n): Promise<string> {\n const late = overdue > 5 ? ` (${overdue}m late)` : \"\";\n if (opts.dryRun) return `would dispatch to ${task.owner.project ?? \"nobody\"}${late}`;\n\n if (!task.owner.project) return \"unrouted — cannot dispatch\";\n\n const result = await dispatchTask(task, {\n transport: opts.transport,\n autoDispatch: opts.autoDispatch,\n spawnIfAbsent: true,\n });\n\n if (result.outcome === \"delivered\" || result.outcome === \"spawned\") {\n // Mark running FIRST so a crash between here and the next tick leaves the\n // task visibly in flight rather than being dispatched twice.\n await opts.provider.setLabels(task.id, [...task.labels, RUNNING_LABEL]);\n state.startedAt[task.id] = now;\n delete state.failedProbes[task.id];\n return `${result.outcome} to ${result.session}${late}`;\n }\n\n return `not dispatched: ${result.outcome}${result.reason ? \" — \" + result.reason : \"\"}`;\n}\n\nasync function handleComplete(\n task: Task,\n durationMinutes: number | null,\n opts: TickOptions,\n state: PersistedState\n): Promise<string> {\n if (opts.dryRun) return `would clear ${RUNNING_LABEL}, ${durationMinutes ?? \"?\"}m`;\n\n await opts.provider.setLabels(\n task.id,\n task.labels.filter((l) => l.toLowerCase() !== RUNNING_LABEL)\n );\n\n const wasStuck = (state.failedProbes[task.id] ?? 0) > 0;\n delete state.startedAt[task.id];\n delete state.failedProbes[task.id];\n\n // A run that needed probing may have been stalled for most of its wall time.\n // Feeding that into the average would inflate every later threshold.\n if (durationMinutes !== null && !wasStuck) {\n const hist = state.history[task.id] ?? [];\n hist.push(durationMinutes);\n state.history[task.id] = hist.slice(-HISTORY_LIMIT);\n }\n\n return durationMinutes === null\n ? \"completed (duration unknown)\"\n : `completed in ${durationMinutes}m${wasStuck ? \" — not recorded, run was probed\" : \"\"}`;\n}\n\nasync function handleProbe(\n task: Task,\n elapsed: number | null,\n opts: TickOptions,\n state: PersistedState\n): Promise<{ note: string; stuck: boolean }> {\n const project = task.owner.project;\n const el = elapsed === null ? \"unknown\" : `${elapsed}m`;\n\n if (opts.dryRun) return { note: `would probe ${project ?? \"?\"} (${el} elapsed)`, stuck: false };\n\n if (!opts.prober || !project) {\n // No probe available. Do NOT guess — an unanswered question is not the same\n // as a dead session, and re-dispatching a live sweep would double-run it.\n return { note: `overrun (${el}) — no liveness probe available, leaving alone`, stuck: false };\n }\n\n const answer = await opts.prober.ask(\n project,\n `Are you still working on the task \"${task.title}\"? Reply in one short line.`\n );\n\n // Both of these are evidence of life. `busy` especially: it means AIBroker saw\n // the session actively producing output and deliberately sent nothing, so it\n // costs no tokens and must not count as a strike.\n if (answer.state === \"replied\" || answer.state === \"busy\") {\n state.failedProbes[task.id] = 0;\n\n const expected = expectedMinutes(state.history[task.id] ?? []);\n if (elapsed !== null && elapsed > expected * GROSS_OVERRUN_FACTOR) {\n // Alive but far past any plausible runtime. Do not recover it — killing a\n // working session is worse — but do not stay quiet either.\n return {\n note: `alive but ${el} against an expected ${expected}m — ${GROSS_OVERRUN_FACTOR}x over, worth a look`,\n stuck: true,\n };\n }\n\n return answer.state === \"busy\"\n ? { note: `busy after ${el} (mid-turn, nothing sent)`, stuck: false }\n : { note: `alive after ${el}: ${answer.reply ?? \"(no detail)\"}`, stuck: false };\n }\n\n const fails = (state.failedProbes[task.id] ?? 0) + 1;\n state.failedProbes[task.id] = fails;\n\n // `absent` is stronger evidence than `silent`, but not acted on immediately:\n // an empty session list is exactly how the hub failed on 2026-08-01, and\n // re-dispatching on a lying hub would spawn a duplicate tab per task.\n const what = answer.state === \"absent\" ? \"session gone\" : \"no reply\";\n\n if (fails >= STUCK_AFTER_FAILED_PROBES) {\n return {\n note: `STUCK after ${el} — ${what} x${fails} (${answer.reason ?? \"no detail\"})`,\n stuck: true,\n };\n }\n return { note: `${what} ${fails}/${STUCK_AFTER_FAILED_PROBES} after ${el}`, stuck: false };\n}\n\nexport { isRunning };\n","/**\n * schedule-install.ts — install the scheduler tick as a launchd agent\n *\n * Two speeds were considered and rejected in favour of one: a fixed interval.\n * A 15-minute tick is 96 runs a day, each one API call and zero tokens, and\n * daily routines do not need better than 15-minute granularity — a 09:00 sweep\n * starting at 09:12 is fine. Adaptive intervals would add a second thing that\n * can silently stop.\n *\n * StartInterval rather than StartCalendarInterval on purpose: the schedule\n * lives in Todoist, not here. This agent only decides how often to *look*.\n * That means one plist total, however many routines exist, and rescheduling a\n * routine never touches the machine.\n */\n\nimport { existsSync, mkdirSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { spawnSync } from \"node:child_process\";\n\nexport const SCHEDULE_LABEL = \"com.pai.task-scheduler\";\nconst LAUNCH_AGENTS = join(homedir(), \"Library\", \"LaunchAgents\");\nexport const SCHEDULE_PLIST = join(LAUNCH_AGENTS, `${SCHEDULE_LABEL}.plist`);\nexport const SCHEDULE_LOG = \"/tmp/pai-scheduler.log\";\n\n/** Default tick, in seconds. */\nexport const DEFAULT_INTERVAL_SECS = 900;\n\nfunction cliPath(): string {\n // dist/cli/index.mjs at runtime\n return fileURLToPath(new URL(\"index.mjs\", import.meta.url));\n}\n\nexport function generateSchedulePlist(intervalSecs: number, cli: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${SCHEDULE_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>/usr/local/bin/node</string>\n <string>${cli}</string>\n <string>task</string>\n <string>poll</string>\n </array>\n\n <key>StartInterval</key>\n <integer>${intervalSecs}</integer>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>StandardOutPath</key>\n <string>${SCHEDULE_LOG}</string>\n\n <key>StandardErrorPath</key>\n <string>${SCHEDULE_LOG}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n <key>PATH</key>\n <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>\n </dict>\n</dict>\n</plist>\n`;\n}\n\nexport interface InstallResult {\n plistPath: string;\n intervalSecs: number;\n loaded: boolean;\n message: string;\n}\n\nexport function installSchedule(intervalSecs = DEFAULT_INTERVAL_SECS): InstallResult {\n if (!existsSync(LAUNCH_AGENTS)) mkdirSync(LAUNCH_AGENTS, { recursive: true });\n\n const cli = cliPath();\n const plist = generateSchedulePlist(intervalSecs, cli);\n\n // Plain write: a plist is generated, not user data, so the json-store guard\n // would be the wrong tool — there is nothing here anyone could not regenerate.\n writeFileSync(SCHEDULE_PLIST, plist, \"utf8\");\n\n spawnSync(\"launchctl\", [\"unload\", SCHEDULE_PLIST], { encoding: \"utf8\" });\n const load = spawnSync(\"launchctl\", [\"load\", SCHEDULE_PLIST], { encoding: \"utf8\" });\n\n return {\n plistPath: SCHEDULE_PLIST,\n intervalSecs,\n loaded: load.status === 0,\n message:\n load.status === 0\n ? `Scheduler installed — ticking every ${Math.round(intervalSecs / 60)} min.`\n : `Plist written but launchctl load failed: ${(load.stderr || \"\").trim()}`,\n };\n}\n\nexport function uninstallSchedule(): string {\n if (!existsSync(SCHEDULE_PLIST)) return \"Scheduler is not installed.\";\n spawnSync(\"launchctl\", [\"unload\", SCHEDULE_PLIST], { encoding: \"utf8\" });\n unlinkSync(SCHEDULE_PLIST);\n return \"Scheduler uninstalled.\";\n}\n\nexport function scheduleStatus(): { installed: boolean; running: boolean; detail: string } {\n const installed = existsSync(SCHEDULE_PLIST);\n if (!installed) return { installed: false, running: false, detail: \"Not installed.\" };\n\n const list = spawnSync(\"launchctl\", [\"list\", SCHEDULE_LABEL], { encoding: \"utf8\" });\n const running = list.status === 0;\n return {\n installed: true,\n running,\n detail: running\n ? `Loaded. Log: ${SCHEDULE_LOG}`\n : \"Plist present but not loaded — run `pai task schedule install` again.\",\n };\n}\n","/**\n * task.ts — `pai task` CLI\n *\n * Surface for the task bus: list what is open, file new work, dispatch tasks to\n * the sessions that own them, and close them out.\n *\n * See Notes/docs/task-bus.md.\n */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { chmodSync } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { loadConfig, CONFIG_FILE } from \"../../daemon/config.js\";\nimport { readConfigRaw, writeConfigRaw } from \"./setup/utils.js\";\nimport { openRegistry } from \"../../registry/db.js\";\nimport { loadAliasMap } from \"../../tasks/resolver.js\";\nimport { TodoistProvider } from \"../../tasks/providers/todoist.js\";\nimport { dispatchAll } from \"../../tasks/dispatch.js\";\nimport { detectAiBroker, detectProber } from \"../../tasks/transport/aibroker.js\";\nimport { tick } from \"../../tasks/poller.js\";\nimport { installSchedule, uninstallSchedule, scheduleStatus, DEFAULT_INTERVAL_SECS } from \"../../tasks/schedule-install.js\";\nimport type { DispatchResult, Task, TaskProvider } from \"../../tasks/types.js\";\n\nconst dim = chalk.dim;\nconst bold = chalk.bold;\n\n// ---------------------------------------------------------------------------\n// Wiring\n// ---------------------------------------------------------------------------\n\nfunction buildProvider(): TaskProvider | null {\n const config = loadConfig();\n const tasks = config.tasks;\n if (!tasks?.enabled) return null;\n\n const aliases = loadAliasMap(openRegistry());\n const provider = new TodoistProvider(tasks.providers.todoist, aliases);\n return provider.isConfigured() ? provider : null;\n}\n\n/** Print the one message that actually helps when nothing is configured. */\nfunction reportUnconfigured(): void {\n console.error(chalk.yellow(\" Task bus is not configured.\"));\n console.error(dim(\" Run `pai setup` and complete the Task Bus step.\"));\n}\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\n// ---------------------------------------------------------------------------\n// Credentials\n// ---------------------------------------------------------------------------\n\n/**\n * Prompt for a secret without echoing it.\n *\n * Never accept a token as a command-line argument: argv is visible in `ps` to\n * every user on the machine and lands in shell history verbatim.\n */\nfunction promptSecret(question: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });\n const out = process.stdout as NodeJS.WriteStream & { muted?: boolean };\n // Swap in a writer that swallows echoed keystrokes but still lets the\n // prompt itself through.\n const write = out.write.bind(out);\n (rl as unknown as { _writeToOutput: (s: string) => void })._writeToOutput = (s: string) => {\n if (!out.muted) write(s);\n };\n write(question);\n out.muted = true;\n rl.question(\"\", (answer) => {\n out.muted = false;\n write(\"\\n\");\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\n/** Show enough of a token to recognise it, never enough to use it. */\nfunction redact(token: string | undefined): string {\n if (!token) return dim(\"(not set)\");\n return token.length <= 8 ? \"********\" : `${token.slice(0, 4)}…${token.slice(-4)}`;\n}\n\ntype RawTasks = {\n enabled?: boolean;\n autoDispatch?: boolean;\n dispatchTimeoutSecs?: number;\n providers?: { todoist?: { enabled?: boolean; apiKey?: string; rootProjectId?: string; findingsSectionId?: string } };\n};\n\n// ---------------------------------------------------------------------------\n// Rendering\n// ---------------------------------------------------------------------------\n\nfunction renderOwner(task: Task): string {\n if (task.owner.project) {\n const via = task.owner.source === \"label\" ? \"label\" : \"container\";\n return chalk.cyan(task.owner.project) + dim(` (${via})`);\n }\n return task.owner.rawHint\n ? chalk.yellow(\"unrouted\") + dim(` (${task.owner.rawHint} matches no project)`)\n : chalk.yellow(\"unrouted\");\n}\n\nfunction printTasks(tasks: Task[]): void {\n if (tasks.length === 0) {\n console.log(dim(\" Nothing open.\"));\n return;\n }\n console.log();\n for (const t of tasks) {\n const due = t.due ? dim(` due ${t.due.slice(0, 10)}`) : \"\";\n const prio = t.priority !== \"p4\" ? dim(` ${t.priority}`) : \"\";\n console.log(` ${bold(t.title)}${prio}${due}`);\n console.log(` ${renderOwner(t)} ${dim(\"· \" + t.id)}`);\n }\n console.log();\n}\n\nfunction printResults(results: DispatchResult[]): void {\n const symbol: Record<string, string> = {\n delivered: chalk.green(\"→\"),\n spawned: chalk.green(\"+\"),\n unrouted: chalk.yellow(\"?\"),\n unlaunchable: chalk.red(\"!\"),\n unreachable: chalk.red(\"✗\"),\n skipped: dim(\"·\"),\n };\n\n console.log();\n for (const r of results) {\n console.log(` ${symbol[r.outcome]} ${r.task.title}`);\n const detail = [r.session, r.reason].filter(Boolean).join(\" — \");\n if (detail) console.log(` ${dim(detail)}`);\n }\n console.log();\n\n const counts = results.reduce<Record<string, number>>((acc, r) => {\n acc[r.outcome] = (acc[r.outcome] ?? 0) + 1;\n return acc;\n }, {});\n console.log(dim(\" \" + Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(\", \")));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Commands\n// ---------------------------------------------------------------------------\n\nexport function registerTaskCommands(taskCmd: Command): void {\n taskCmd\n .command(\"list\")\n .description(\"List open tasks on the bus\")\n .option(\"--owner <project>\", \"Only tasks owned by this PAI project\")\n .option(\"--due <date>\", \"Only tasks due on or before this ISO date\")\n .option(\"--today\", \"Shorthand for --due <today>\")\n .option(\"--routed\", \"Hide tasks with no resolved owner\")\n .option(\"--limit <n>\", \"Maximum tasks to show\", (v) => Number.parseInt(v, 10))\n .action(async (opts) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n\n const tasks = await provider.listOpen({\n owner: opts.owner,\n dueBefore: opts.today ? todayIso() : opts.due,\n includeUnrouted: !opts.routed,\n limit: opts.limit,\n });\n printTasks(tasks);\n });\n\n taskCmd\n .command(\"add <title>\")\n .description(\"File a task onto the bus\")\n .option(\"--owner <project>\", \"PAI project that owns this (adds a pai: label)\")\n .option(\"--body <text>\", \"Full procedure and reasoning — not just a restatement of the title\")\n .option(\"--due <date>\", \"Due date (ISO or natural language)\")\n .option(\"--priority <p>\", \"p1 (highest) … p4 (default)\")\n .option(\"--url <url>\", \"Reference — prefer a hook:// URL over a file path\")\n .action(async (title, opts) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n\n // A title alone is not actionable months later. Warn rather than block:\n // refusing the write would just push people to file nothing at all.\n if (!opts.body) {\n console.log(dim(\" No --body given. Add the procedure and reasoning so this\"));\n console.log(dim(\" is still actionable months from now, by you alone.\"));\n }\n\n const created = await provider.add({\n title,\n body: opts.body ?? \"\",\n owner: opts.owner ?? null,\n due: opts.due,\n priority: opts.priority,\n sourceUrl: opts.url,\n });\n\n console.log(chalk.green(` Filed: ${created.title}`));\n console.log(` ${renderOwner(created)} ${dim(\"· \" + created.id)}`);\n });\n\n taskCmd\n .command(\"dispatch\")\n .description(\"Hand open tasks to the PAI sessions that own them\")\n .option(\"--owner <project>\", \"Only dispatch tasks for this project\")\n .option(\"--today\", \"Only tasks due today or overdue\")\n .option(\"--dry-run\", \"Report what would happen without contacting any session\")\n .option(\"--no-spawn\", \"Never launch a session; skip owners that are not running\")\n .action(async (opts) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n\n const config = loadConfig();\n const tasks = await provider.listOpen({\n owner: opts.owner,\n dueBefore: opts.today ? todayIso() : undefined,\n includeUnrouted: true,\n });\n\n if (tasks.length === 0) {\n console.log(dim(\" Nothing to dispatch.\"));\n return;\n }\n\n // A dry run must never contact a session, so skip detection entirely\n // rather than detecting and then declining to use it.\n const transport = opts.dryRun\n ? null\n : await detectAiBroker(undefined, config.tasks?.dispatchTimeoutSecs);\n\n if (!opts.dryRun && !transport) {\n console.log(dim(\" No aibroker CLI with `dispatch` found — reporting ownership only.\"));\n }\n\n const results = await dispatchAll(tasks, {\n transport,\n autoDispatch: opts.dryRun ? false : (config.tasks?.autoDispatch ?? false),\n spawnIfAbsent: opts.spawn !== false,\n });\n\n printResults(results);\n });\n\n taskCmd\n .command(\"poll\")\n .description(\"One scheduler tick: dispatch what is due, check what is running, report\")\n .option(\"--dry-run\", \"Show what would happen without touching anything\")\n .action(async (opts) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n\n const config = loadConfig();\n const transport = opts.dryRun ? null : await detectAiBroker(undefined, config.tasks?.dispatchTimeoutSecs);\n const prober = opts.dryRun ? null : await detectProber();\n\n const report = await tick({\n provider: provider as TodoistProvider,\n transport,\n prober,\n autoDispatch: config.tasks?.autoDispatch ?? false,\n dryRun: Boolean(opts.dryRun),\n });\n\n if (report.decisions.length === 0) {\n console.log(dim(\" Nothing due, nothing running.\"));\n return;\n }\n\n const mark: Record<string, string> = {\n dispatch: chalk.green(\"→\"),\n complete: chalk.green(\"✓\"),\n running: dim(\"·\"),\n probe: chalk.yellow(\"?\"),\n orphaned: chalk.yellow(\"!\"),\n skip: dim(\"–\"),\n wait: dim(\" \"),\n };\n\n console.log();\n for (const { decision, note } of report.decisions) {\n console.log(` ${mark[decision.action] ?? \" \"} ${decision.task.title}`);\n if (note) console.log(` ${dim(note)}`);\n }\n console.log();\n console.log(\n dim(\n ` ${report.dispatched} dispatched, ${report.completed} completed, ` +\n `${report.probed} probed, ${report.stuck} stuck`\n )\n );\n console.log();\n });\n\n const scheduleCmd = taskCmd\n .command(\"schedule\")\n .description(\"Install, remove or inspect the launchd agent that ticks the scheduler\");\n\n scheduleCmd\n .command(\"install\")\n .description(\"Install the scheduler tick (default: every 15 minutes)\")\n .option(\"--interval <secs>\", \"Seconds between ticks\", (v) => Number.parseInt(v, 10))\n .action((opts) => {\n const r = installSchedule(opts.interval || DEFAULT_INTERVAL_SECS);\n console.log(r.loaded ? chalk.green(` ${r.message}`) : chalk.yellow(` ${r.message}`));\n console.log(dim(` ${r.plistPath}`));\n console.log(dim(` The schedule itself lives in Todoist — this only sets how often PAI looks.`));\n });\n\n scheduleCmd\n .command(\"uninstall\")\n .description(\"Remove the scheduler agent\")\n .action(() => console.log(chalk.green(` ${uninstallSchedule()}`)));\n\n scheduleCmd\n .command(\"status\")\n .description(\"Show whether the scheduler agent is installed and loaded\")\n .action(() => {\n const s = scheduleStatus();\n console.log();\n console.log(` ${s.installed ? chalk.green(\"installed\") : dim(\"not installed\")}` +\n ` ${s.installed ? (s.running ? chalk.green(\"· loaded\") : chalk.yellow(\"· not loaded\")) : \"\"}`);\n console.log(dim(` ${s.detail}`));\n console.log();\n });\n\n taskCmd\n .command(\"config\")\n .description(\"View or change task bus settings without running the full setup wizard\")\n .option(\"--token\", \"Prompt for the Todoist API token (input is hidden)\")\n .option(\"--from-env\", \"Adopt the token from TODOIST_API_KEY in the environment\")\n .option(\"--project <id>\", \"Tracker project ID that roots the bus (an ID, never a name)\")\n .option(\"--findings <id>\", \"Section ID for the findings inbox\")\n .option(\"--timeout <secs>\", \"Seconds a single dispatch may take\", (v) => Number.parseInt(v, 10))\n .option(\"--auto-dispatch <bool>\", \"Hand tasks to owning sessions automatically (true/false)\")\n .option(\"--disable\", \"Turn the task bus off without discarding its settings\")\n .action(async (opts) => {\n const raw = readConfigRaw();\n const tasks = (raw.tasks ?? {}) as RawTasks;\n tasks.providers ??= {};\n tasks.providers.todoist ??= {};\n const todoist = tasks.providers.todoist;\n\n const noChanges =\n !opts.token && !opts.fromEnv && !opts.project && !opts.findings &&\n opts.timeout === undefined && opts.autoDispatch === undefined && !opts.disable;\n\n if (noChanges) {\n console.log();\n console.log(` ${bold(\"Task bus\")} ${tasks.enabled ? chalk.green(\"enabled\") : dim(\"disabled\")}`);\n console.log(` ${bold(\"Token\")} ${redact(todoist.apiKey)}${\n !todoist.apiKey && process.env.TODOIST_API_KEY ? dim(\" (TODOIST_API_KEY is set in this shell)\") : \"\"\n }`);\n console.log(` ${bold(\"Root project\")} ${todoist.rootProjectId ?? dim(\"(not set)\")}`);\n console.log(` ${bold(\"Findings\")} ${todoist.findingsSectionId ?? dim(\"(not set)\")}`);\n console.log(` ${bold(\"Auto-dispatch\")} ${tasks.autoDispatch ? chalk.green(\"on\") : dim(\"off\")}`);\n console.log(` ${bold(\"Timeout\")} ${tasks.dispatchTimeoutSecs ?? dim(\"default (180s)\")}`);\n console.log();\n console.log(dim(` ${CONFIG_FILE}`));\n console.log();\n return;\n }\n\n if (opts.token && opts.fromEnv) {\n console.error(chalk.yellow(\" Use either --token or --from-env, not both.\"));\n process.exitCode = 1;\n return;\n }\n\n if (opts.fromEnv) {\n const envToken = process.env.TODOIST_API_KEY?.trim();\n if (!envToken) {\n console.error(chalk.yellow(\" TODOIST_API_KEY is not set in this environment.\"));\n process.exitCode = 1;\n return;\n }\n todoist.apiKey = envToken;\n todoist.enabled = true;\n tasks.enabled = true;\n }\n\n if (opts.token) {\n const entered = await promptSecret(\" Todoist API token (hidden): \");\n if (!entered) {\n console.error(chalk.yellow(\" No token entered. Nothing changed.\"));\n process.exitCode = 1;\n return;\n }\n todoist.apiKey = entered;\n todoist.enabled = true;\n tasks.enabled = true;\n }\n\n if (opts.project) { todoist.rootProjectId = opts.project; tasks.enabled = true; }\n if (opts.findings) todoist.findingsSectionId = opts.findings;\n if (opts.timeout !== undefined) tasks.dispatchTimeoutSecs = opts.timeout;\n if (opts.autoDispatch !== undefined) tasks.autoDispatch = opts.autoDispatch === \"true\";\n if (opts.disable) tasks.enabled = false;\n\n raw.tasks = tasks;\n writeConfigRaw(raw);\n\n // The file now holds a credential. Narrow it before saying anything else,\n // so a crash between write and chmod cannot leave it world-readable.\n try {\n chmodSync(CONFIG_FILE, 0o600);\n } catch {\n console.log(chalk.yellow(` Could not restrict permissions on ${CONFIG_FILE} — check them by hand.`));\n }\n\n console.log(chalk.green(\" Saved.\"));\n if (todoist.apiKey && !todoist.rootProjectId) {\n console.log(dim(\" No root project set yet — run `pai task config --project <id>`.\"));\n }\n });\n\n taskCmd\n .command(\"done <id>\")\n .description(\"Mark a task complete on the tracker\")\n .action(async (id) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n await provider.complete(id);\n console.log(chalk.green(` Completed ${id}.`));\n });\n}\n","/**\n * pai topic <sub-command>\n *\n * check <context> — Check whether context text has drifted to a different project\n *\n * Communicates with the running daemon via IPC.\n * The daemon uses BM25 keyword search to match context against indexed memory.\n *\n * Examples:\n * pai topic check \"working on authentication and JWT tokens\"\n * pai topic check \"fixing the React component\" --current myapp\n * pai topic check \"database schema migration\" --threshold 0.7\n */\n\nimport type { Command } from \"commander\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { PaiClient } from \"../../daemon/ipc-client.js\";\nimport type { TopicCheckResult } from \"../../topics/detector.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction makeClient(): PaiClient {\n const config = loadConfig();\n return new PaiClient(config.socketPath);\n}\n\nfunction confidenceBar(confidence: number, width = 20): string {\n const filled = Math.round(confidence * width);\n const empty = width - filled;\n return \"[\" + \"=\".repeat(filled) + \" \".repeat(empty) + \"]\";\n}\n\nfunction printTopicResult(result: TopicCheckResult): void {\n console.log();\n console.log(header(\" PAI Topic Check\"));\n console.log();\n\n console.log(` ${bold(\"Current project:\")} ${result.currentProject ?? dim(\"(none)\")}`);\n console.log(` ${bold(\"Suggested project:\")} ${result.suggestedProject ?? dim(\"(none)\")}`);\n console.log(\n ` ${bold(\"Confidence:\")} ${confidenceBar(result.confidence)} ${(result.confidence * 100).toFixed(1)}%`\n );\n console.log(` ${bold(\"Chunks scored:\")} ${result.chunkCount}`);\n\n if (result.topProjects.length > 0) {\n console.log();\n console.log(` ${bold(\"Top matches:\")}`);\n for (const p of result.topProjects) {\n const bar = confidenceBar(p.score, 15);\n const marker = p.slug === result.currentProject ? dim(\" (current)\") : \"\";\n console.log(` ${p.slug.padEnd(30)} ${bar} ${(p.score * 100).toFixed(1)}%${marker}`);\n }\n }\n\n console.log();\n\n if (result.shifted) {\n console.log(\n warn(\" TOPIC SHIFT DETECTED\") +\n dim(` — conversation appears to be about \"${result.suggestedProject}\", not \"${result.currentProject}\"`)\n );\n console.log();\n } else if (result.suggestedProject && result.suggestedProject === result.currentProject) {\n console.log(ok(\" No shift detected\") + dim(\" — context matches current project\"));\n console.log();\n } else if (!result.currentProject) {\n if (result.suggestedProject) {\n console.log(\n ok(\" Best matching project: \") + bold(result.suggestedProject) +\n dim(` (confidence: ${(result.confidence * 100).toFixed(0)}%)`)\n );\n } else {\n console.log(dim(\" No matching project found in memory index.\"));\n }\n console.log();\n } else {\n console.log(\n dim(` No shift detected (top match \"${result.suggestedProject}\" has confidence ${(result.confidence * 100).toFixed(0)}% — below threshold)`)\n );\n console.log();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nasync function cmdCheck(\n context: string,\n opts: {\n current?: string;\n threshold?: string;\n json?: boolean;\n }\n): Promise<void> {\n const client = makeClient();\n\n const threshold = opts.threshold ? parseFloat(opts.threshold) : undefined;\n\n if (threshold !== undefined && (isNaN(threshold) || threshold < 0 || threshold > 1)) {\n console.error(err(\" --threshold must be a number between 0 and 1\"));\n process.exit(1);\n }\n\n try {\n const result = await client.topicCheck({\n context,\n currentProject: opts.current,\n threshold,\n });\n\n if (opts.json) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n printTopicResult(result);\n\n // Exit with code 1 if a shift was detected (useful for scripting / hooks)\n if (result.shifted) {\n process.exit(1);\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.log();\n console.log(warn(\" Cannot reach PAI daemon.\"));\n console.log(dim(` ${msg}`));\n console.log(dim(\" Start it with: pai daemon serve\"));\n console.log();\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerTopicCommands(topicCmd: Command): void {\n // pai topic check <context>\n topicCmd\n .command(\"check <context>\")\n .description(\n \"Check whether context text has drifted to a different project.\\n\" +\n 'Example: pai topic check \"working on JWT authentication\"\\n' +\n \"Exit code 1 if a shift is detected (useful for hooks).\"\n )\n .option(\n \"--current <slug>\",\n \"The project this session is currently routed to\"\n )\n .option(\n \"--threshold <n>\",\n \"Confidence threshold [0-1] to declare a shift (default: 0.6)\"\n )\n .option(\"--json\", \"Output raw JSON result\")\n .action(\n async (\n context: string,\n opts: { current?: string; threshold?: string; json?: boolean }\n ) => {\n await cmdCheck(context, opts);\n }\n );\n}\n","/**\n * kg-backfill.ts — Populate the temporal knowledge graph from existing\n * session notes.\n *\n * Walks `Notes/YYYY/MM/*.md` for each registered project, runs the same\n * extractor that the session-summary-worker uses on every NEW summary, and\n * stores triples in Postgres. Idempotent: a state file at\n * ~/.config/pai/kg-backfill-state.json records which note paths have been\n * processed so re-runs skip them.\n *\n * Even without the state file the operation is safe — extractAndStoreTriples\n * uses supersession logic so re-extracting a note never produces duplicates.\n */\n\nimport { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { Pool } from \"pg\";\n\nimport { openRegistry } from \"../registry/db.js\";\nimport { loadConfig } from \"../daemon/config.js\";\nimport { createStorageBackend } from \"../storage/factory.js\";\nimport { extractAndStoreTriples } from \"./kg-extraction.js\";\nimport { openFederation } from \"./db.js\";\n\n// ---------------------------------------------------------------------------\n// State file\n// ---------------------------------------------------------------------------\n\nconst STATE_FILE = join(homedir(), \".config\", \"pai\", \"kg-backfill-state.json\");\n\ninterface BackfillState {\n /** Map of absolute note path -> ISO timestamp of when it was processed. */\n processed: Record<string, string>;\n}\n\nfunction loadState(): BackfillState {\n try {\n if (existsSync(STATE_FILE)) {\n const raw = JSON.parse(readFileSync(STATE_FILE, \"utf-8\"));\n if (raw && typeof raw === \"object\" && raw.processed) return raw as BackfillState;\n }\n } catch { /* ignore */ }\n return { processed: {} };\n}\n\nfunction saveState(state: BackfillState): void {\n try {\n const dir = join(homedir(), \".config\", \"pai\");\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), \"utf-8\");\n } catch (e) {\n process.stderr.write(`[kg-backfill] failed to save state: ${e}\\n`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Note discovery\n// ---------------------------------------------------------------------------\n\ninterface ProjectRow {\n id: number;\n slug: string;\n root_path: string;\n}\n\ninterface ProjectNote {\n project: ProjectRow;\n notePath: string;\n}\n\n/**\n * Find all session notes under <project_root>/Notes/YYYY/MM/*.md.\n * Falls back to <project_root>/Notes/*.md (flat layout) when no year/month\n * subdirectories are present.\n */\nfunction findProjectNotes(project: ProjectRow): string[] {\n const notesRoot = join(project.root_path, \"Notes\");\n if (!existsSync(notesRoot)) return [];\n\n const found: Array<{ path: string; mtime: number }> = [];\n\n function walk(dir: string, depth: number): void {\n if (depth > 4) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n const full = join(dir, name);\n let st;\n try {\n st = statSync(full);\n } catch {\n continue;\n }\n if (st.isDirectory()) {\n walk(full, depth + 1);\n } else if (st.isFile() && name.endsWith(\".md\") && /^\\d{3,4}/.test(name)) {\n found.push({ path: full, mtime: st.mtimeMs });\n }\n }\n }\n\n walk(notesRoot, 0);\n // Process oldest first so newer extractions can correctly supersede older facts\n found.sort((a, b) => a.mtime - b.mtime);\n return found.map((f) => f.path);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport interface BackfillOptions {\n /** If set, only process this project's notes. */\n projectSlug?: string;\n /** Cap the total number of notes processed across all projects. */\n limit?: number;\n /** If true, do not call the extractor and do not write any state. */\n dryRun?: boolean;\n /** Progress callback fired before each note is processed. */\n onProgress?: (current: number, total: number, note: string) => void;\n}\n\nexport interface BackfillResult {\n notes_processed: number;\n triples_extracted: number;\n triples_added: number;\n triples_superseded: number;\n errors: number;\n}\n\n/**\n * Backfill the knowledge graph from existing session notes.\n *\n * Requires the Postgres backend (KG tables live there). Throws if Postgres\n * is unavailable, since this is an explicit user action.\n */\nexport async function backfillKgFromNotes(\n options: BackfillOptions = {}\n): Promise<BackfillResult> {\n const result: BackfillResult = {\n notes_processed: 0,\n triples_extracted: 0,\n triples_added: 0,\n triples_superseded: 0,\n errors: 0,\n };\n\n const config = loadConfig();\n if (config.storageBackend !== \"postgres\") {\n throw new Error(\n \"kg backfill requires the Postgres backend. \" +\n 'Set \"storageBackend\": \"postgres\" in ~/.config/pai/config.json.'\n );\n }\n\n const backend = await createStorageBackend(config);\n if (backend.backendType !== \"postgres\") {\n throw new Error(\n \"Postgres backend unavailable — fell back to SQLite. Cannot backfill KG.\"\n );\n }\n\n const pool: Pool = (backend as unknown as { getPool(): Pool }).getPool();\n\n // Open federation SQLite for entity upserts (kg_entities table)\n const federationDb = openFederation();\n\n // -- Load projects -------------------------------------------------------\n const registry = openRegistry();\n let projects: ProjectRow[];\n try {\n if (options.projectSlug) {\n const row = registry\n .prepare(\"SELECT id, slug, root_path FROM projects WHERE slug = ?\")\n .get(options.projectSlug) as ProjectRow | undefined;\n if (!row) {\n throw new Error(`Project not found: ${options.projectSlug}`);\n }\n projects = [row];\n } else {\n projects = registry\n .prepare(\n \"SELECT id, slug, root_path FROM projects WHERE status = 'active' ORDER BY slug\"\n )\n .all() as ProjectRow[];\n }\n } finally {\n registry.close();\n }\n\n // -- Build the work list -------------------------------------------------\n const state = loadState();\n const work: ProjectNote[] = [];\n for (const project of projects) {\n const notes = findProjectNotes(project);\n for (const notePath of notes) {\n if (state.processed[notePath]) continue;\n work.push({ project, notePath });\n }\n }\n\n const total = options.limit ? Math.min(options.limit, work.length) : work.length;\n\n // -- Process notes -------------------------------------------------------\n for (let i = 0; i < total; i++) {\n const { project, notePath } = work[i];\n options.onProgress?.(i + 1, total, notePath);\n\n if (options.dryRun) {\n result.notes_processed++;\n continue;\n }\n\n let noteContent: string;\n try {\n noteContent = readFileSync(notePath, \"utf-8\");\n } catch (e) {\n process.stderr.write(`[kg-backfill] read failed ${notePath}: ${e}\\n`);\n result.errors++;\n continue;\n }\n\n try {\n const stats = await extractAndStoreTriples(pool, {\n summaryText: noteContent,\n projectSlug: project.slug,\n projectId: project.id,\n sessionId: `backfill:${notePath}`,\n gitLog: \"\",\n model: \"sonnet\",\n federationDb,\n });\n\n result.notes_processed++;\n result.triples_extracted += stats.extracted;\n result.triples_added += stats.added;\n result.triples_superseded += stats.superseded;\n\n // Mark as processed (state file is the idempotency hint; the supersession\n // logic in the extractor is the actual safety net)\n state.processed[notePath] = new Date().toISOString();\n // Save incrementally so a crash mid-run doesn't lose progress\n if ((i + 1) % 5 === 0) saveState(state);\n } catch (e) {\n process.stderr.write(`[kg-backfill] extract failed ${notePath}: ${e}\\n`);\n result.errors++;\n }\n }\n\n if (!options.dryRun) saveState(state);\n federationDb.close();\n await backend.close();\n\n return result;\n}\n","/**\n * pai kg <sub-command>\n *\n * Temporal knowledge graph management:\n * pai kg backfill [--project SLUG] [--limit N] [--dry-run]\n * pai kg query [--subject S] [--predicate P] [--object O] [--as-of DATE] [--project SLUG]\n * pai kg list [--project SLUG] [--limit N]\n * pai kg stats\n *\n * All commands require the Postgres backend (KG tables live in Postgres).\n */\n\nimport type { Command } from \"commander\";\nimport type { Pool } from \"pg\";\n\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { createStorageBackend } from \"../../storage/factory.js\";\nimport { kgQuery } from \"../../memory/kg.js\";\nimport { backfillKgFromNotes } from \"../../memory/kg-backfill.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function getPool(): Promise<{ pool: Pool; close: () => Promise<void> }> {\n const config = loadConfig();\n if (config.storageBackend !== \"postgres\") {\n console.error(err(\" KG commands require Postgres backend.\"));\n console.error(dim(' Set \"storageBackend\": \"postgres\" in ~/.config/pai/config.json'));\n process.exit(1);\n }\n const backend = await createStorageBackend(config);\n if (backend.backendType !== \"postgres\") {\n console.error(err(\" Postgres backend unavailable — fell back to SQLite.\"));\n process.exit(1);\n }\n const pool = (backend as unknown as { getPool(): Pool }).getPool();\n return { pool, close: () => backend.close() };\n}\n\nfunction shorten(s: string, n: number): string {\n return s.length <= n ? s : s.slice(0, n - 1) + \"…\";\n}\n\n// ---------------------------------------------------------------------------\n// pai kg backfill\n// ---------------------------------------------------------------------------\n\nasync function cmdBackfill(opts: {\n project?: string;\n limit?: string;\n dryRun?: boolean;\n}): Promise<void> {\n const limit = opts.limit ? parseInt(opts.limit, 10) : undefined;\n if (limit !== undefined && (isNaN(limit) || limit < 1)) {\n console.error(err(\" --limit must be a positive integer\"));\n process.exit(1);\n }\n\n console.log();\n console.log(header(\" PAI KG Backfill\"));\n console.log();\n if (opts.project) console.log(` ${bold(\"Project:\")} ${opts.project}`);\n if (limit) console.log(` ${bold(\"Limit:\")} ${limit}`);\n if (opts.dryRun) console.log(` ${bold(\"Mode:\")} ${warn(\"dry-run\")}`);\n console.log();\n\n try {\n const result = await backfillKgFromNotes({\n projectSlug: opts.project,\n limit,\n dryRun: opts.dryRun,\n onProgress: (current, total, note) => {\n const short = shorten(note.replace(process.env.HOME ?? \"\", \"~\"), 70);\n process.stdout.write(` [${current}/${total}] ${dim(short)}\\n`);\n },\n });\n\n console.log();\n console.log(ok(\" Backfill complete\"));\n console.log(` Notes processed: ${bold(String(result.notes_processed))}`);\n console.log(` Triples extracted: ${bold(String(result.triples_extracted))}`);\n console.log(` Triples added: ${bold(String(result.triples_added))}`);\n console.log(` Triples superseded: ${bold(String(result.triples_superseded))}`);\n if (result.errors > 0) {\n console.log(` ${warn(\"Errors:\")} ${result.errors}`);\n }\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai kg query\n// ---------------------------------------------------------------------------\n\nasync function cmdQuery(opts: {\n subject?: string;\n predicate?: string;\n object?: string;\n asOf?: string;\n project?: string;\n json?: boolean;\n}): Promise<void> {\n const { pool, close } = await getPool();\n try {\n let projectId: number | undefined;\n if (opts.project) {\n const r = await pool.query<{ id: number }>(\n \"SELECT id FROM projects WHERE slug = $1 LIMIT 1\",\n [opts.project]\n );\n if (r.rows.length === 0) {\n console.error(warn(` Project not found in Postgres: ${opts.project}`));\n } else {\n projectId = r.rows[0].id;\n }\n }\n\n const asOf = opts.asOf ? new Date(opts.asOf) : undefined;\n if (asOf && isNaN(asOf.getTime())) {\n console.error(err(` Invalid --as-of date: ${opts.asOf}`));\n process.exit(1);\n }\n\n const triples = await kgQuery(pool, {\n subject: opts.subject,\n predicate: opts.predicate,\n object: opts.object,\n project_id: projectId,\n as_of: asOf,\n });\n\n if (opts.json) {\n console.log(JSON.stringify(triples, null, 2));\n return;\n }\n\n console.log();\n console.log(header(` ${triples.length} triple(s)`));\n console.log();\n for (const t of triples) {\n const validity = t.valid_to\n ? dim(`(invalidated ${t.valid_to.toISOString().slice(0, 10)})`)\n : dim(`(valid since ${t.valid_from.toISOString().slice(0, 10)})`);\n console.log(\n ` ${bold(t.subject)} ${dim(\"·\")} ${t.predicate} ${dim(\"·\")} ${t.object} ${validity}`\n );\n }\n console.log();\n } finally {\n await close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai kg list\n// ---------------------------------------------------------------------------\n\nasync function cmdList(opts: { project?: string; limit?: string }): Promise<void> {\n const limit = opts.limit ? parseInt(opts.limit, 10) : 50;\n const { pool, close } = await getPool();\n try {\n let projectId: number | undefined;\n if (opts.project) {\n const r = await pool.query<{ id: number }>(\n \"SELECT id FROM projects WHERE slug = $1 LIMIT 1\",\n [opts.project]\n );\n if (r.rows.length > 0) projectId = r.rows[0].id;\n }\n\n const triples = await kgQuery(pool, { project_id: projectId });\n const slice = triples.slice(0, limit);\n\n console.log();\n console.log(\n header(` ${slice.length} of ${triples.length} currently-valid triple(s)`)\n );\n console.log();\n for (const t of slice) {\n console.log(\n ` ${bold(t.subject)} ${dim(\"·\")} ${t.predicate} ${dim(\"·\")} ${t.object}`\n );\n }\n if (triples.length > slice.length) {\n console.log();\n console.log(dim(` (${triples.length - slice.length} more — increase --limit)`));\n }\n console.log();\n } finally {\n await close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai kg stats\n// ---------------------------------------------------------------------------\n\nasync function cmdStats(): Promise<void> {\n const { pool, close } = await getPool();\n try {\n const totals = await pool.query<{\n total: string;\n valid: string;\n invalidated: string;\n subjects: string;\n predicates: string;\n }>(\n `SELECT\n COUNT(*)::text AS total,\n COUNT(*) FILTER (WHERE valid_to IS NULL)::text AS valid,\n COUNT(*) FILTER (WHERE valid_to IS NOT NULL)::text AS invalidated,\n COUNT(DISTINCT subject)::text AS subjects,\n COUNT(DISTINCT predicate)::text AS predicates\n FROM kg_triples`\n );\n\n const contradictions = await pool.query<{ count: string }>(\n `SELECT COUNT(*)::text AS count FROM (\n SELECT subject, predicate\n FROM kg_triples\n WHERE valid_to IS NULL\n GROUP BY subject, predicate\n HAVING COUNT(*) > 1\n ) c`\n );\n\n const row = totals.rows[0] ?? {};\n console.log();\n console.log(header(\" PAI KG Stats\"));\n console.log();\n console.log(` ${bold(\"Total triples:\")} ${row.total ?? \"0\"}`);\n console.log(` ${bold(\"Currently valid:\")} ${row.valid ?? \"0\"}`);\n console.log(` ${bold(\"Invalidated:\")} ${row.invalidated ?? \"0\"}`);\n console.log(` ${bold(\"Distinct subjects:\")} ${row.subjects ?? \"0\"}`);\n console.log(` ${bold(\"Distinct predicates:\")} ${row.predicates ?? \"0\"}`);\n console.log(` ${bold(\"Contradictions:\")} ${contradictions.rows[0]?.count ?? \"0\"}`);\n console.log();\n } finally {\n await close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerKgCommands(kgCmd: Command): void {\n kgCmd\n .command(\"backfill\")\n .description(\"Populate the KG from existing session notes (idempotent)\")\n .option(\"--project <slug>\", \"Restrict backfill to a single project\")\n .option(\"--limit <n>\", \"Maximum number of notes to process\")\n .option(\"--dry-run\", \"List notes that would be processed without extracting\")\n .action(async (opts: { project?: string; limit?: string; dryRun?: boolean }) => {\n await cmdBackfill(opts);\n });\n\n kgCmd\n .command(\"query\")\n .description(\"Query KG triples by subject, predicate, object, time, or project\")\n .option(\"--subject <s>\", \"Filter by subject\")\n .option(\"--predicate <p>\", \"Filter by predicate\")\n .option(\"--object <o>\", \"Filter by object\")\n .option(\"--as-of <date>\", \"Point-in-time query (YYYY-MM-DD or ISO 8601)\")\n .option(\"--project <slug>\", \"Restrict to a project slug\")\n .option(\"--json\", \"Output raw JSON\")\n .action(\n async (opts: {\n subject?: string;\n predicate?: string;\n object?: string;\n asOf?: string;\n project?: string;\n json?: boolean;\n }) => {\n await cmdQuery(opts);\n }\n );\n\n kgCmd\n .command(\"list\")\n .description(\"List currently-valid triples\")\n .option(\"--project <slug>\", \"Restrict to a project slug\")\n .option(\"--limit <n>\", \"Maximum triples to print\", \"50\")\n .action(async (opts: { project?: string; limit?: string }) => {\n await cmdList(opts);\n });\n\n kgCmd\n .command(\"stats\")\n .description(\"Show triple counts and contradiction count\")\n .action(async () => {\n await cmdStats();\n });\n}\n","/**\n * pai db <sub-command>\n *\n * Quick database inspection commands for both SQLite and Postgres backends.\n *\n * pai db query <sql> [--db sqlite|postgres] [--json]\n * pai db tables [--db sqlite|postgres]\n * pai db schema <table> [--db sqlite|postgres]\n */\n\nimport type { Command } from \"commander\";\nimport { ok, warn, err, dim, bold, header, renderTable } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { openFederation } from \"../../memory/db.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype DbTarget = \"sqlite\" | \"postgres\";\n\ninterface CommonOpts {\n db?: DbTarget;\n json?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// SQLite helpers (synchronous — better-sqlite3)\n// ---------------------------------------------------------------------------\n\nfunction getSqliteDb() {\n return openFederation();\n}\n\nfunction sqliteQuery(sql: string): { columns: string[]; rows: unknown[][] } {\n const db = getSqliteDb();\n try {\n const stmt = db.prepare(sql);\n const raw = stmt.all() as Record<string, unknown>[];\n if (raw.length === 0) return { columns: [], rows: [] };\n const columns = Object.keys(raw[0]);\n const rows = raw.map((r) => columns.map((c) => r[c]));\n return { columns, rows };\n } finally {\n db.close();\n }\n}\n\nfunction sqliteTables(): string[] {\n const db = getSqliteDb();\n try {\n const rows = db\n .prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name\"\n )\n .all() as { name: string }[];\n return rows.map((r) => r.name);\n } finally {\n db.close();\n }\n}\n\nfunction sqliteSchema(table: string): { columns: string[]; rows: unknown[][] } {\n const db = getSqliteDb();\n try {\n // Validate table name — only allow alphanumeric / underscores\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {\n throw new Error(`Invalid table name: ${table}`);\n }\n const rows = db.prepare(`PRAGMA table_info(${table})`).all() as {\n cid: number;\n name: string;\n type: string;\n notnull: number;\n dflt_value: string | null;\n pk: number;\n }[];\n if (rows.length === 0) {\n throw new Error(`Table not found: ${table}`);\n }\n const columns = [\"cid\", \"name\", \"type\", \"notnull\", \"default\", \"pk\"];\n const data = rows.map((r) => [\n r.cid,\n r.name,\n r.type,\n r.notnull ? \"NOT NULL\" : \"\",\n r.dflt_value ?? \"\",\n r.pk ? \"PK\" : \"\",\n ]);\n return { columns, data: data } as unknown as { columns: string[]; rows: unknown[][] };\n } finally {\n db.close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Postgres helpers (async — pg Pool)\n// ---------------------------------------------------------------------------\n\nasync function getPool() {\n const config = loadConfig();\n const pgConfig = config.postgres ?? {};\n\n const { Pool } = await import(\"pg\");\n\n // Build connection params from config\n const poolConfig = pgConfig.connectionString\n ? { connectionString: pgConfig.connectionString }\n : {\n host: pgConfig.host ?? \"localhost\",\n port: pgConfig.port ?? 5432,\n database: pgConfig.database ?? \"pai\",\n user: pgConfig.user ?? \"pai\",\n password: pgConfig.password ?? \"pai\",\n connectionTimeoutMillis: pgConfig.connectionTimeoutMs ?? 5000,\n };\n\n const pool = new Pool(poolConfig);\n\n // Quick connectivity test\n try {\n const client = await pool.connect();\n client.release();\n } catch (e) {\n await pool.end();\n const msg = e instanceof Error ? e.message : String(e);\n throw new Error(`Cannot connect to Postgres: ${msg}`);\n }\n\n return pool;\n}\n\nasync function postgresQuery(\n sql: string\n): Promise<{ columns: string[]; rows: unknown[][] }> {\n const pool = await getPool();\n try {\n const result = await pool.query(sql);\n const columns = result.fields.map((f) => f.name);\n const rows = result.rows.map((r: Record<string, unknown>) =>\n columns.map((c) => r[c])\n );\n return { columns, rows };\n } finally {\n await pool.end();\n }\n}\n\nasync function postgresTables(): Promise<string[]> {\n const pool = await getPool();\n try {\n const result = await pool.query<{ tablename: string }>(\n \"SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename\"\n );\n return result.rows.map((r) => r.tablename);\n } finally {\n await pool.end();\n }\n}\n\nasync function postgresSchema(\n table: string\n): Promise<{ columns: string[]; rows: unknown[][] }> {\n const pool = await getPool();\n try {\n const result = await pool.query<{\n column_name: string;\n data_type: string;\n is_nullable: string;\n column_default: string | null;\n }>(\n `SELECT column_name, data_type, is_nullable, column_default\n FROM information_schema.columns\n WHERE table_schema = 'public' AND table_name = $1\n ORDER BY ordinal_position`,\n [table]\n );\n if (result.rows.length === 0) {\n throw new Error(`Table not found in public schema: ${table}`);\n }\n const columns = [\"column\", \"type\", \"nullable\", \"default\"];\n const rows = result.rows.map((r) => [\n r.column_name,\n r.data_type,\n r.is_nullable === \"YES\" ? \"YES\" : \"NO\",\n r.column_default ?? \"\",\n ]);\n return { columns, rows };\n } finally {\n await pool.end();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Output helpers\n// ---------------------------------------------------------------------------\n\nfunction printResult(\n result: { columns: string[]; rows: unknown[][] },\n json: boolean\n): void {\n if (json) {\n const objects = result.rows.map((row) => {\n const obj: Record<string, unknown> = {};\n result.columns.forEach((col, i) => {\n obj[col] = row[i];\n });\n return obj;\n });\n console.log(JSON.stringify(objects, null, 2));\n return;\n }\n\n if (result.rows.length === 0) {\n console.log();\n console.log(dim(\" (no rows)\"));\n console.log();\n return;\n }\n\n const stringRows = result.rows.map((row) =>\n row.map((cell) => {\n if (cell === null || cell === undefined) return dim(\"NULL\");\n if (typeof cell === \"object\") return JSON.stringify(cell);\n return String(cell);\n })\n );\n\n console.log();\n console.log(renderTable(result.columns, stringRows));\n console.log();\n console.log(dim(` ${result.rows.length} row(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// pai db query\n// ---------------------------------------------------------------------------\n\nasync function cmdQuery(\n sql: string,\n opts: CommonOpts\n): Promise<void> {\n const target: DbTarget = opts.db ?? \"sqlite\";\n console.log();\n console.log(\n header(` Query [${target}]`) + dim(` ${sql.slice(0, 80)}${sql.length > 80 ? \"…\" : \"\"}`)\n );\n\n try {\n let result: { columns: string[]; rows: unknown[][] };\n if (target === \"postgres\") {\n result = await postgresQuery(sql);\n } else {\n result = sqliteQuery(sql);\n }\n printResult(result, opts.json ?? false);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai db tables\n// ---------------------------------------------------------------------------\n\nasync function cmdTables(opts: CommonOpts): Promise<void> {\n const target: DbTarget = opts.db ?? \"sqlite\";\n console.log();\n console.log(header(` Tables [${target}]`));\n console.log();\n\n try {\n let tables: string[];\n if (target === \"postgres\") {\n tables = await postgresTables();\n } else {\n tables = sqliteTables();\n }\n\n if (opts.json) {\n console.log(JSON.stringify(tables, null, 2));\n return;\n }\n\n if (tables.length === 0) {\n console.log(dim(\" (no tables found)\"));\n } else {\n for (const t of tables) {\n console.log(` ${bold(t)}`);\n }\n }\n console.log();\n console.log(dim(` ${tables.length} table(s)`));\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai db schema\n// ---------------------------------------------------------------------------\n\nasync function cmdSchema(table: string, opts: CommonOpts): Promise<void> {\n const target: DbTarget = opts.db ?? \"sqlite\";\n console.log();\n console.log(header(` Schema: ${table} [${target}]`));\n\n try {\n let result: { columns: string[]; rows: unknown[][] };\n if (target === \"postgres\") {\n result = await postgresSchema(table);\n } else {\n const raw = sqliteSchema(table) as unknown as {\n columns: string[];\n data: unknown[][];\n };\n result = { columns: raw.columns, rows: raw.data };\n }\n printResult(result, opts.json ?? false);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerDbCommands(dbCmd: Command): void {\n dbCmd\n .command(\"query <sql>\")\n .description(\"Run a SQL query against the selected database\")\n .option(\"--db <target>\", \"Database target: sqlite (default) or postgres\", \"sqlite\")\n .option(\"--json\", \"Output results as JSON array\")\n .action(async (sql: string, opts: CommonOpts) => {\n await cmdQuery(sql, opts);\n });\n\n dbCmd\n .command(\"tables\")\n .description(\"List all tables in the selected database\")\n .option(\"--db <target>\", \"Database target: sqlite (default) or postgres\", \"sqlite\")\n .option(\"--json\", \"Output as JSON array\")\n .action(async (opts: CommonOpts) => {\n await cmdTables(opts);\n });\n\n dbCmd\n .command(\"schema <table>\")\n .description(\"Show column schema for a table\")\n .option(\"--db <target>\", \"Database target: sqlite (default) or postgres\", \"sqlite\")\n .option(\"--json\", \"Output as JSON array\")\n .action(async (table: string, opts: CommonOpts) => {\n await cmdSchema(table, opts);\n });\n}\n","/**\n * pai help [area]\n *\n * Rich man-page viewer for the generated command docs under docs/commands/.\n * These pages are generated from the live Commander tree by\n * scripts/build-docs.mjs, so they never drift from the actual CLI.\n *\n * pai help → list all command areas (the index / apropos)\n * pai help <area> → print the man page for that area\n *\n * Falls back to `pai <area> --help` if a page is missing.\n */\n\nimport type { Command } from \"commander\";\nimport { readFileSync, existsSync, readdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport { err, dim } from \"../utils.js\";\n\n/**\n * Locate the docs/commands directory robustly, regardless of how the bundle is\n * chunked. Walks up from this module's location (and from argv[1]) looking for a\n * directory that contains docs/commands.\n */\nfunction findDocsDir(): string | null {\n const candidates: string[] = [];\n try {\n candidates.push(dirname(fileURLToPath(import.meta.url)));\n } catch {\n /* ignore */\n }\n if (process.argv[1]) candidates.push(dirname(process.argv[1]));\n\n for (const start of candidates) {\n let dir = start;\n for (let i = 0; i < 6; i++) {\n const probe = join(dir, \"docs\", \"commands\");\n if (existsSync(probe)) return probe;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n }\n return null;\n}\n\n/**\n * Light terminal styling for the generated markdown — enough to read like a man\n * page without a full markdown renderer. Headings bold, table rules + fences\n * dimmed, blockquotes dimmed.\n */\nfunction inlineBold(s: string): string {\n return s\n .replace(/\\\\\\|/g, \"|\")\n .replace(/\\*\\*([^*]+)\\*\\*/g, (_m, t) => chalk.bold(t));\n}\n\nfunction renderMarkdown(md: string): string {\n const out: string[] = [];\n let inFence = false;\n for (const line of md.split(\"\\n\")) {\n // Drop the generator's HTML comment header.\n if (/^\\s*<!--.*-->\\s*$/.test(line)) continue;\n // Code fences: suppress the ``` markers, colourise the body.\n if (/^```/.test(line)) {\n inFence = !inFence;\n continue;\n }\n if (inFence) {\n out.push(chalk.cyan(line));\n continue;\n }\n if (/^#{1,6}\\s/.test(line)) {\n out.push(chalk.bold(line.replace(/^#{1,6}\\s/, \"\")));\n continue;\n }\n if (/^>\\s?/.test(line)) {\n out.push(dim(line.replace(/^>\\s?/, \"\")));\n continue;\n }\n // Markdown table rule rows.\n if (/^\\|?\\s*[-:]+\\s*(\\|\\s*[-:]+\\s*)+\\|?$/.test(line)) {\n out.push(dim(line));\n continue;\n }\n out.push(inlineBold(line));\n }\n // Collapse leading blank lines.\n while (out.length > 0 && out[0].trim() === \"\") out.shift();\n return out.join(\"\\n\");\n}\n\nfunction listAreas(docsDir: string): string[] {\n return readdirSync(docsDir)\n .filter((f) => f.endsWith(\".md\") && f !== \"README.md\")\n .map((f) => f.replace(/\\.md$/, \"\"))\n .sort();\n}\n\nexport function registerHelpCommand(program: Command): void {\n program\n .command(\"help [area]\")\n .description(\"Show the man page for a command area (pai help <area>), or list all areas\")\n .option(\"--raw\", \"Print raw markdown without terminal styling\")\n .action((area: string | undefined, opts: { raw?: boolean }) => {\n const docsDir = findDocsDir();\n\n if (!docsDir) {\n console.error(err(\" Command docs not found.\"));\n console.error(dim(\" Run `bun run build` to generate them, or use `pai <area> --help`.\"));\n process.exitCode = 1;\n return;\n }\n\n // No area → print the index.\n if (!area) {\n const indexPath = join(docsDir, \"README.md\");\n if (existsSync(indexPath)) {\n const md = readFileSync(indexPath, \"utf8\");\n console.log(opts.raw ? md : renderMarkdown(md));\n } else {\n console.log(chalk.bold(\"\\nPAI command areas\\n\"));\n for (const a of listAreas(docsDir)) console.log(` ${a}`);\n console.log(dim(\"\\n pai help <area> show the man page for an area\"));\n }\n return;\n }\n\n // Area → print its page, or fall back.\n const page = join(docsDir, `${area}.md`);\n if (existsSync(page)) {\n const md = readFileSync(page, \"utf8\");\n console.log(opts.raw ? md : renderMarkdown(md));\n return;\n }\n\n console.error(err(` No man page for \"${area}\".`));\n const areas = listAreas(docsDir);\n const near = areas.filter((a) => a.includes(area) || area.includes(a));\n if (near.length > 0) {\n console.error(dim(` Did you mean: ${near.join(\", \")}?`));\n } else {\n console.error(dim(` Available: ${areas.join(\", \")}`));\n }\n console.error(dim(` Or try: pai ${area} --help`));\n process.exitCode = 1;\n });\n}\n","/**\n * pai session pause [--dry-run]\n *\n * Writes a \"## Continue\" checkpoint to the project's TODO.md, then prints a\n * prominent reminder about HOW to exit Claude Code safely.\n *\n * The write logic mirrors cmdHandover exactly — it finds the project by cwd,\n * finds (or creates) TODO.md, and prepends a ## Continue block. The difference\n * is that we do NOT call process.exit() after writing, so the warning is shown.\n *\n * WHY Ctrl+C is dangerous:\n * Claude Code's stop-hook fires only on a clean exit (e.g. /exit inside the\n * Claude prompt, or claude process receiving SIGTERM). When you Ctrl+C from\n * a terminal that's running claude, the stop-hook is bypassed, which means:\n * - The top-level <project>/<uuid>.jsonl does NOT get its final system snapshot.\n * - Claude Code's --resume cannot find the session (0 system lines).\n * - The session becomes orphaned — transcript exists but is unresumable.\n *\n * If the project TODO.md gets its ## Continue checkpoint (written by this\n * command), the *content* can still be recovered. But the *session context*\n * (conversation history) is lost unless you exit cleanly via /exit.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n renameSync,\n mkdirSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// TODO.md discovery (same locations as handover.ts)\n// ---------------------------------------------------------------------------\n\nconst HANDOVER_TODO_LOCATIONS = [\n \"Notes/TODO.md\",\n \".claude/Notes/TODO.md\",\n \"tasks/todo.md\",\n \"TODO.md\",\n];\n\nfunction findProjectTodo(\n rootPath: string\n): { path: string; content: string } | null {\n for (const rel of HANDOVER_TODO_LOCATIONS) {\n const full = join(rootPath, rel);\n if (existsSync(full)) {\n try {\n return { path: full, content: readFileSync(full, \"utf8\") };\n } catch {\n // unreadable — try next\n }\n }\n }\n return null;\n}\n\nfunction stripContinueSection(content: string): string {\n const lines = content.split(\"\\n\");\n const startIdx = lines.findIndex((l) => l.trim() === \"## Continue\");\n if (startIdx === -1) return content;\n\n let endIdx = lines.length;\n for (let i = startIdx + 1; i < lines.length; i++) {\n const trimmed = lines[i].trim();\n if (\n trimmed === \"---\" ||\n (trimmed.startsWith(\"##\") && trimmed !== \"## Continue\")\n ) {\n endIdx = i;\n break;\n }\n }\n\n let trailingEnd = endIdx;\n if (trailingEnd < lines.length && lines[trailingEnd].trim() === \"---\") {\n trailingEnd += 1;\n }\n\n const before = lines.slice(0, startIdx);\n const after = lines.slice(trailingEnd);\n while (after.length > 0 && after[0].trim() === \"\") after.shift();\n\n return [...before, ...after].join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport function cmdPause(\n db: Database,\n opts: { dryRun?: boolean }\n): void {\n // ---- 1. Resolve project by cwd ----\n const cwd = process.cwd();\n const project = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir\n FROM projects\n WHERE ? LIKE root_path || '%'\n ORDER BY length(root_path) DESC\n LIMIT 1`\n )\n .get(cwd) as ProjectRow | undefined;\n\n if (!project) {\n console.error(\n chalk.red(\"pai session pause: \") +\n \"Current directory is not within a registered PAI project.\\n\" +\n ` cwd: ${cwd}`\n );\n process.exit(1);\n }\n\n // ---- 2. Resolve latest session ----\n const session = db\n .prepare(\n \"SELECT * FROM sessions WHERE project_id = ? ORDER BY number DESC LIMIT 1\"\n )\n .get(project.id) as SessionRow | undefined;\n\n // ---- 3. Find or create TODO.md ----\n const todo = findProjectTodo(project.root_path);\n let todoPath: string;\n let existingContent: string;\n\n if (todo) {\n todoPath = todo.path;\n existingContent = todo.content;\n } else {\n const notesDir = join(project.root_path, \"Notes\");\n if (!opts.dryRun) {\n try {\n if (!existsSync(notesDir)) mkdirSync(notesDir, { recursive: true });\n } catch (mkdirErr) {\n console.error(\n chalk.red(\"pai session pause: \") +\n `Could not create Notes directory: ${String(mkdirErr)}`\n );\n process.exit(1);\n }\n }\n todoPath = join(notesDir, \"TODO.md\");\n existingContent = \"\";\n }\n\n // ---- 4. Build ## Continue block ----\n const timestamp = new Date().toISOString();\n\n let sessionLine: string;\n if (session) {\n const num = String(session.number).padStart(4, \"0\");\n const titlePart = session.title || session.slug || \"Session\";\n sessionLine = `${num} - ${session.date} - ${titlePart}`;\n } else {\n sessionLine = \"Unknown session\";\n }\n\n const continueBlock = [\n \"## Continue\",\n \"\",\n `> **Last session:** ${sessionLine}`,\n `> **Paused at:** ${timestamp}`,\n \">\",\n `> Working directory: ${cwd}. Check the latest session note for details.`,\n \"\",\n \"---\",\n \"\",\n ].join(\"\\n\");\n\n // ---- 5. Prepend, stripping old ## Continue ----\n const stripped = stripContinueSection(existingContent).trimStart();\n const newContent = continueBlock + stripped;\n\n // ---- 6. Write atomically (or dry-run preview) ----\n if (opts.dryRun) {\n console.log(\"\\n\" + chalk.bold(\"Dry run — would write to:\") + \" \" + chalk.cyan(todoPath));\n console.log(chalk.dim(\"─\".repeat(60)));\n // Show just the ## Continue block that would be prepended\n console.log(chalk.dim(continueBlock));\n console.log(chalk.dim(\"─\".repeat(60)));\n } else {\n const tmpPath = `${todoPath}.pause.tmp`;\n try {\n writeFileSync(tmpPath, newContent, \"utf8\");\n renameSync(tmpPath, todoPath);\n } catch (writeErr) {\n try {\n if (existsSync(tmpPath)) renameSync(tmpPath, `${tmpPath}.dead`);\n } catch {\n /* ignore */\n }\n console.error(\n chalk.red(\"pai session pause: \") +\n `Failed to write TODO.md: ${String(writeErr)}`\n );\n process.exit(1);\n }\n console.log(\n chalk.green(\" ## Continue written to: \") + chalk.cyan(todoPath)\n );\n }\n\n // ---- 7. Print the safety warning ----\n const box = [\n \"\",\n chalk.bgRed.white.bold(\n \" IMPORTANT: How to exit safely \"\n ),\n \"\",\n chalk.yellow(\n \" Inside the Claude Code session, type: \" +\n chalk.white.bold(\"/exit\") +\n chalk.yellow(\" (then press Enter)\")\n ),\n \"\",\n chalk.red.bold(\" DO NOT press Ctrl+C.\"),\n \"\",\n chalk.dim(\n \" Ctrl+C bypasses PAI's stop-hook, which means:\"\n ),\n chalk.dim(\" - No project snapshot is written to the top-level jsonl\"),\n chalk.dim(\" - The session becomes orphaned (cannot --resume next time)\"),\n chalk.dim(\n \" - pai session goto will launch a fresh session instead of resuming\"\n ),\n \"\",\n chalk.dim(\" The ## Continue checkpoint in TODO.md is already saved.\"),\n chalk.dim(\" Use /exit to preserve full session resumability.\"),\n \"\",\n ].join(\"\\n\");\n\n console.log(box);\n}\n","/**\n * pai end [--dry-run]\n *\n * Extends `pai pause` with two additional steps:\n * 1. Marks the current session note **Status: Completed** and adds a\n * Completed timestamp — mirrors what `finalizeSessionNote()` does in the\n * hooks lib but without importing the hooks-only module.\n * 2. Prints a slightly more final safe-exit reminder.\n *\n * The TODO.md ## Continue checkpoint is written first (same as `pai pause`),\n * so if the user exits without the session note step succeeding the content\n * checkpoint is still intact.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n readdirSync,\n renameSync,\n mkdirSync,\n} from \"node:fs\";\nimport { join, basename } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { realpathSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\nimport { cmdPause } from \"./pause.js\";\n\n// ---------------------------------------------------------------------------\n// Notes-directory resolution (mirrors paths.ts without importing hooks lib)\n// ---------------------------------------------------------------------------\n\n/** Encode a path the same way Claude Code does. */\nfunction encodePath(path: string): string {\n return path\n .replace(/\\//g, \"-\")\n .replace(/\\./g, \"-\")\n .replace(/ /g, \"-\");\n}\n\n/** PAI_DIR — mirrors pai-paths.ts resolution. */\nfunction getPaiDir(): string {\n const envDir = process.env.PAI_DIR;\n if (envDir) {\n try {\n return realpathSync(envDir);\n } catch {\n return envDir;\n }\n }\n return join(homedir(), \".claude\");\n}\n\n/**\n * Find the notes directory for a project — checks local first, then central.\n * Returns null if neither exists (dry-run safe: don't create).\n */\nfunction findNotesDir(\n rootPath: string,\n encodedDir: string\n): string | null {\n // Check local paths first\n for (const rel of [\"Notes\", \"notes\", \".claude/Notes\"]) {\n const p = join(rootPath, rel);\n if (existsSync(p)) return p;\n }\n // Central fallback: ~/.claude/projects/<encoded>/Notes\n const central = join(getPaiDir(), \"projects\", encodedDir, \"Notes\");\n if (existsSync(central)) return central;\n return null;\n}\n\n/**\n * Find the current (latest) session note inside notesDir.\n * Searches YYYY/MM subdirectory (current month, then previous month),\n * then flat notesDir as legacy fallback.\n */\nfunction findLatestNote(notesDir: string): string | null {\n const findIn = (dir: string): string | null => {\n if (!existsSync(dir)) return null;\n const files = readdirSync(dir)\n .filter((f) => /^\\d{3,4}[\\s_-].*\\.md$/.test(f))\n .sort((a, b) => {\n const na = parseInt(a.match(/^(\\d+)/)?.[1] ?? \"0\", 10);\n const nb = parseInt(b.match(/^(\\d+)/)?.[1] ?? \"0\", 10);\n return na - nb;\n });\n return files.length > 0 ? join(dir, files[files.length - 1]) : null;\n };\n\n const now = new Date();\n const year = String(now.getFullYear());\n const month = String(now.getMonth() + 1).padStart(2, \"0\");\n\n const current = findIn(join(notesDir, year, month));\n if (current) return current;\n\n const prev = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n const py = String(prev.getFullYear());\n const pm = String(prev.getMonth() + 1).padStart(2, \"0\");\n const prevFound = findIn(join(notesDir, py, pm));\n if (prevFound) return prevFound;\n\n return findIn(notesDir);\n}\n\n// ---------------------------------------------------------------------------\n// Session-note finalization (mirrors finalizeSessionNote from session-notes.ts)\n// ---------------------------------------------------------------------------\n\nfunction finalizeNote(notePath: string): { finalized: boolean; path: string } {\n const content = readFileSync(notePath, \"utf-8\");\n\n if (content.includes(\"**Status:** Completed\")) {\n return { finalized: false, path: notePath }; // already done\n }\n\n let updated = content.replace(\"**Status:** In Progress\", \"**Status:** Completed\");\n\n if (!updated.includes(\"**Completed:**\")) {\n const completionTime = new Date().toISOString();\n updated = updated.replace(\n \"---\\n\\n## Work Done\",\n `**Completed:** ${completionTime}\\n\\n---\\n\\n## Work Done`\n );\n }\n\n // Write atomically\n const tmp = `${notePath}.end.tmp`;\n writeFileSync(tmp, updated, \"utf-8\");\n renameSync(tmp, notePath);\n\n return { finalized: true, path: notePath };\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport function cmdEnd(db: Database, opts: { dryRun?: boolean }): void {\n // ---- Step 1: Run pause logic (writes ## Continue, prints warning) ----\n // We call cmdPause first; it prints the ## Continue block and the initial\n // safe-exit box. We'll print the end-specific reminder afterwards.\n cmdPause(db, opts);\n\n // ---- Step 2: Locate the project ----\n const cwd = process.cwd();\n const project = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir\n FROM projects\n WHERE ? LIKE root_path || '%'\n ORDER BY length(root_path) DESC\n LIMIT 1`\n )\n .get(cwd) as ProjectRow | undefined;\n\n if (!project) {\n // cmdPause already printed an error. Nothing more to do.\n return;\n }\n\n // ---- Step 3: Find notes directory ----\n const notesDir = findNotesDir(project.root_path, project.encoded_dir ?? \"\");\n\n if (!notesDir) {\n console.log(\n chalk.dim(\" (No session note found — notes directory does not exist yet.)\")\n );\n printEndBox(opts.dryRun);\n return;\n }\n\n // ---- Step 4: Find latest session note ----\n const notePath = findLatestNote(notesDir);\n\n if (!notePath) {\n console.log(chalk.dim(\" (No session note found in notes directory.)\"));\n printEndBox(opts.dryRun);\n return;\n }\n\n // ---- Step 5: Mark as completed ----\n if (opts.dryRun) {\n console.log(\n \"\\n\" +\n chalk.bold(\"Dry run — would finalize session note:\") +\n \" \" +\n chalk.cyan(notePath)\n );\n console.log(\n chalk.dim(\n \" Would replace: **Status:** In Progress\\n\" +\n \" with: **Status:** Completed\"\n )\n );\n console.log(\n chalk.dim(\" Would add: **Completed:** <timestamp>\")\n );\n } else {\n const { finalized, path } = finalizeNote(notePath);\n if (finalized) {\n console.log(\n chalk.green(\" Session note finalized: \") + chalk.cyan(basename(path))\n );\n } else {\n console.log(\n chalk.dim(\n ` Session note already marked Completed: ${basename(notePath)}`\n )\n );\n }\n }\n\n // ---- Step 6: Print the more-final exit reminder ----\n printEndBox(opts.dryRun);\n}\n\nfunction printEndBox(dryRun?: boolean): void {\n const label = dryRun ? \" (dry-run)\" : \"\";\n const box = [\n \"\",\n chalk.bgRed.white.bold(\n ` SESSION ENDING${label}: How to exit safely `\n ),\n \"\",\n chalk.yellow(\n \" Inside the Claude Code session, type: \" +\n chalk.white.bold(\"/exit\") +\n chalk.yellow(\" (then press Enter)\")\n ),\n \"\",\n chalk.red.bold(\" DO NOT press Ctrl+C.\"),\n \"\",\n chalk.dim(\" Ctrl+C bypasses PAI's stop-hook, which means:\"),\n chalk.dim(\" - The session note is NOT written to by the stop-hook\"),\n chalk.dim(\" - The session becomes orphaned (cannot --resume next time)\"),\n chalk.dim(\" - The final session summary is never generated\"),\n \"\",\n chalk.dim(\n \" ## Continue checkpoint and session note status are already saved.\"\n ),\n chalk.dim(\" Use /exit to let PAI's stop-hook finalize the session fully.\"),\n \"\",\n ].join(\"\\n\");\n\n console.log(box);\n}\n","/**\n * pai resume <name> / pai sessions goto <name> [--dry-run]\n *\n * Smart session launcher with auto-fallback:\n *\n * 1. Resolve name → (uuid?, projectDir, friendlyName)\n * 2. chdir(projectDir)\n * 3. If uuid is set:\n * a. Probe: claude --resume <uuid> --print --output-format=json \"_\"\n * (timeout 5s; exits non-zero / logs \"No conversation found\" if invalid)\n * b. If probe succeeds:\n * exec claude --resume <uuid> --name \"<friendlyName>\" \"/Name <friendlyName>\\ngo\"\n * c. Else (probe failed):\n * print clear stderr line: \"Resume failed. Starting fresh session in same dir.\"\n * exec claude --name \"<friendlyName>\" \"/Name <friendlyName>\\ngo\"\n * 4. Else (no uuid known):\n * exec claude --name \"<friendlyName>\" \"/Name <friendlyName>\\ngo\"\n *\n * Why both --name AND /Name?\n * --name <friendlyName> → sets Claude Code's internal session label\n * \"/Name <friendlyName>\\ngo\" → runs the /Name slash command via AIBroker, which updates\n * iTerm tab title, statusline, and AIBroker session registry;\n * the \\ngo on the next line triggers PAI's ## Continue resume\n *\n * The probe adds ~300ms on the happy path. On the fallback path, total latency is the same\n * as a fresh start.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { spawnSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport { err } from \"../../utils.js\";\nimport {\n scanSessions,\n resolveSessionByNameOrId,\n fmtAge,\n type ScannedSession,\n} from \"../../lib/session-scan.js\";\nimport { printExitDir } from \"../../lib/exit-dir.js\";\n\n// ---------------------------------------------------------------------------\n// Probe helper\n// ---------------------------------------------------------------------------\n\ninterface ProbeResult {\n ok: boolean;\n reason?: string;\n}\n\n/**\n * Run claude --resume <uuid> --print --output-format=json \"_\" in the given cwd.\n * Returns ok=true if the session is resumable (exit 0 and no \"No conversation found\"\n * in stderr). Timeout: 5 seconds.\n */\nfunction probeResume(uuid: string, cwd: string): ProbeResult {\n const result = spawnSync(\n \"claude\",\n [\"--resume\", uuid, \"--print\", \"--output-format=json\", \"_\"],\n {\n cwd,\n timeout: 5_000,\n env: process.env,\n // Capture stderr for inspection; suppress stdout (tty)\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n }\n );\n\n if (result.error) {\n return { ok: false, reason: `spawn error: ${result.error.message}` };\n }\n\n const stderr = result.stderr?.toString(\"utf8\") ?? \"\";\n\n if (\n stderr.toLowerCase().includes(\"no conversation found\") ||\n stderr.toLowerCase().includes(\"session not found\")\n ) {\n return { ok: false, reason: \"No conversation found for this UUID\" };\n }\n\n if (result.status !== 0) {\n return {\n ok: false,\n reason: `claude exited ${result.status ?? \"signal\"}${stderr ? `: ${stderr.slice(0, 120).trim()}` : \"\"}`,\n };\n }\n\n return { ok: true };\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport function cmdGoto(\n db: Database,\n query: string,\n opts: { dryRun?: boolean }\n): void {\n // ---- 1. Resolve name → session ----\n const allSessions = scanSessions(db, { limit: 500, filter: \"named\" });\n\n let resolved;\n try {\n resolved = resolveSessionByNameOrId(allSessions, query);\n } catch (resolveErr) {\n let msg = String(resolveErr).replace(/^Error: /, \"\");\n // Append pai find suggestion for \"not found\" errors\n if (msg.includes(\"No session found matching\")) {\n msg += `\\n\\nTip: pai find \"${query}\" — search prompt history by keywords`;\n }\n console.error(err(msg));\n process.exit(1);\n }\n\n const { session: matchedSession, friendlyName } = resolved;\n\n // ---- 2. Find best resumable UUID for this project ----\n let resumableUuid: string | undefined;\n let resumableSession: ScannedSession | undefined;\n\n if (matchedSession.resumable) {\n resumableUuid = matchedSession.uuid;\n resumableSession = matchedSession;\n } else if (matchedSession.encodedDir) {\n // Find the most-recently-modified resumable session in the same project dir\n const sameProject = allSessions.filter(\n (s) => s.encodedDir === matchedSession.encodedDir && s.resumable\n );\n sameProject.sort((a, b) => b.mtime - a.mtime);\n if (sameProject.length > 0) {\n resumableSession = sameProject[0];\n resumableUuid = resumableSession.uuid;\n }\n }\n\n // ---- 3. Determine project dir (realpathSync for --resume cwd correctness) ----\n const rawDir =\n matchedSession.clcDirectory ??\n matchedSession.registryRootPath ??\n matchedSession.decodedPath;\n\n let projectDir: string;\n try {\n projectDir = realpathSync(rawDir);\n } catch {\n console.error(\n err(\n `session \"${query}\": directory does not exist or cannot be resolved.\\n` +\n ` Registry says: ${rawDir}\\n` +\n ` The directory may have moved or been deleted.`\n )\n );\n process.exit(1);\n }\n\n // ---- 4. Build argv components ----\n const name = friendlyName ?? query;\n // Initial prompt: /Name sets tab/statusline via AIBroker; \\ngo triggers ## Continue\n const promptArg = `/Name ${name}\\ngo`;\n\n // ---- 5. Dry-run mode ----\n if (opts.dryRun) {\n if (resumableUuid) {\n const argvResume = `claude --resume ${resumableUuid} --name \"${name}\" \"/Name ${name}\\\\ngo\"`;\n const argvFresh = `claude --name \"${name}\" \"/Name ${name}\\\\ngo\"`;\n console.log(\n \"\\n\" + chalk.bold(\"Dry run — would probe then exec (RESUME path):\") + \"\\n\"\n );\n console.log(` cwd: ${chalk.cyan(projectDir)}`);\n console.log(` probe: claude --resume ${resumableUuid} --print --output-format=json \"_\"`);\n console.log(` argv: ${chalk.white(argvResume)}`);\n console.log(` fallback: ${chalk.yellow(argvFresh)}`);\n if (resumableSession) {\n console.log(`\\n uuid: ${resumableSession.uuid}`);\n console.log(` age: ${fmtAge(resumableSession.mtime)}`);\n console.log(` status: ${resumableSession.sessionStatus}`);\n console.log(` sys: ${resumableSession.topLevelSystemLines} system lines`);\n }\n } else {\n const argvFresh = `claude --name \"${name}\" \"/Name ${name}\\\\ngo\"`;\n console.log(\n \"\\n\" + chalk.bold(\"Dry run — would exec (FRESH path, no resumable UUID):\") + \"\\n\"\n );\n console.log(` cwd: ${chalk.cyan(projectDir)}`);\n console.log(` argv: ${chalk.white(argvFresh)}`);\n }\n console.log();\n return;\n }\n\n // ---- 6. Live execution ----\n if (resumableUuid) {\n // Probe first\n const probe = probeResume(resumableUuid, projectDir);\n\n if (probe.ok) {\n // Happy path: session is resumable\n const result = spawnSync(\n \"claude\",\n [\"--resume\", resumableUuid, \"--name\", name, promptArg],\n {\n cwd: projectDir,\n stdio: \"inherit\",\n env: process.env,\n }\n );\n if (result.error) {\n console.error(err(`Failed to launch claude: ${result.error.message}`));\n process.exit(1);\n }\n printExitDir(projectDir);\n process.exit(result.status ?? 0);\n } else {\n // Fallback path\n process.stderr.write(\n chalk.yellow(\n `\\n Resume failed for ${resumableUuid.slice(0, 8)}: ${probe.reason ?? \"unknown error\"}\\n` +\n ` Starting fresh session in same directory.\\n\\n`\n )\n );\n const result = spawnSync(\n \"claude\",\n [\"--name\", name, promptArg],\n {\n cwd: projectDir,\n stdio: \"inherit\",\n env: process.env,\n }\n );\n if (result.error) {\n console.error(err(`Failed to launch claude: ${result.error.message}`));\n process.exit(1);\n }\n printExitDir(projectDir);\n process.exit(result.status ?? 0);\n }\n } else {\n // No UUID at all — fresh start\n const result = spawnSync(\n \"claude\",\n [\"--name\", name, promptArg],\n {\n cwd: projectDir,\n stdio: \"inherit\",\n env: process.env,\n }\n );\n if (result.error) {\n console.error(err(`Failed to launch claude: ${result.error.message}`));\n process.exit(1);\n }\n printExitDir(projectDir);\n process.exit(result.status ?? 0);\n }\n}\n","/**\n * pai pause all [--exit] [--dry-run]\n *\n * Pause every live Claude Code session that AIBroker knows about.\n *\n * For each live session returned by AIBroker's session_content IPC method:\n * 1. Send \"pause session\" to the iTerm2 pane via send_to_session.\n * 2. Optionally send \"\\n/exit\\n\" after a short delay (--exit flag).\n * 3. Print a summary of what was sent and to which sessions.\n *\n * If AIBroker is not running, prints a clear error and exits.\n * If no live sessions are found, reports \"nothing to pause\".\n */\n\nimport chalk from \"chalk\";\nimport {\n fetchLiveSessions,\n sendToSession,\n type AiBrokerSessionMeta,\n} from \"../../lib/aibroker-client.js\";\nimport { header, dim, ok, err, warn } from \"../../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction sessionLabel(s: AiBrokerSessionMeta): string {\n const name = s.paiName ?? s.name;\n return `${chalk.cyan(s.sessionId.slice(0, 8))} ${chalk.bold(name)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdPauseAll(opts: {\n exit?: boolean;\n dryRun?: boolean;\n wait?: number;\n}): Promise<void> {\n const waitMs = opts.wait ?? 5_000; // ms to wait after \"pause session\" before /exit\n\n // ── Fetch live sessions ────────────────────────────────────────────────────\n let liveSessions: AiBrokerSessionMeta[];\n try {\n liveSessions = await fetchLiveSessions();\n } catch (e) {\n console.error(err(\"AIBroker is not running. Cannot list live sessions.\"));\n console.error(dim(\" Start AIBroker or run `pai pause` from each session manually.\"));\n process.exitCode = 1;\n return;\n }\n\n // ── Filter: Claude sessions only (skip bare shells) ───────────────────────\n const claudeSessions = liveSessions.filter((s) => s.kind === \"claude\");\n const skipped = liveSessions.length - claudeSessions.length;\n\n if (skipped > 0) {\n process.stderr.write(\n `Skipping ${skipped} non-Claude tab${skipped === 1 ? \"\" : \"s\"} (bare shells).\\n`\n );\n }\n\n if (claudeSessions.length === 0) {\n console.log(warn(\"No live Claude sessions found via AIBroker. Nothing to pause.\"));\n return;\n }\n\n // ── Dry-run ───────────────────────────────────────────────────────────────\n if (opts.dryRun) {\n console.log(\"\\n\" + header(\"Dry Run — Would Pause These Sessions\") + \"\\n\");\n for (const s of claudeSessions) {\n console.log(\" \" + sessionLabel(s));\n console.log(dim(' → send: \"pause session\"'));\n if (opts.exit) {\n console.log(dim(` → wait ${waitMs}ms then send: \"/exit\"`));\n }\n }\n console.log();\n return;\n }\n\n // ── Live run ──────────────────────────────────────────────────────────────\n console.log(\n \"\\n\" +\n header(\"Pausing All Live Sessions\") +\n \"\\n\" +\n dim(` ${claudeSessions.length} Claude session(s) via AIBroker`) +\n \"\\n\"\n );\n\n const results: Array<{ session: AiBrokerSessionMeta; pauseOk: boolean; exitOk?: boolean; error?: string }> = [];\n\n for (const s of claudeSessions) {\n process.stdout.write(\" \" + sessionLabel(s) + \" … \");\n\n // Send \"pause session\" command\n const pauseResult = await sendToSession(s.sessionId, \"pause session\\n\");\n if (!pauseResult.ok) {\n console.log(err(\"FAILED: \" + (pauseResult.error ?? \"unknown error\")));\n results.push({ session: s, pauseOk: false, error: pauseResult.error });\n continue;\n }\n console.log(ok(\"paused\"));\n results.push({ session: s, pauseOk: true });\n }\n\n // ── Optional /exit after waiting ──────────────────────────────────────────\n if (opts.exit) {\n const pausedSessions = results.filter((r) => r.pauseOk).map((r) => r.session);\n if (pausedSessions.length > 0) {\n console.log(\n \"\\n\" +\n dim(` Waiting ${waitMs / 1000}s for sessions to save state…`)\n );\n await sleep(waitMs);\n\n console.log();\n for (const s of pausedSessions) {\n process.stdout.write(\" \" + sessionLabel(s) + \" exiting … \");\n const exitResult = await sendToSession(s.sessionId, \"/exit\\n\");\n const rec = results.find((r) => r.session.sessionId === s.sessionId)!;\n rec.exitOk = exitResult.ok;\n if (!exitResult.ok) {\n console.log(warn(\"exit failed: \" + (exitResult.error ?? \"unknown\")));\n } else {\n console.log(ok(\"exited\"));\n }\n }\n }\n }\n\n // ── Summary ───────────────────────────────────────────────────────────────\n const total = results.length;\n const succeeded = results.filter((r) => r.pauseOk).length;\n const failed = total - succeeded;\n\n console.log();\n if (failed === 0) {\n console.log(ok(`All ${total} session(s) paused successfully.`));\n } else {\n console.log(\n warn(`${succeeded}/${total} session(s) paused. `) +\n err(`${failed} failed.`)\n );\n for (const r of results.filter((r) => !r.pauseOk)) {\n const label = r.session.paiName ?? r.session.name ?? r.session.sessionId.slice(0, 8);\n console.log(err(` ${label}: ${r.error ?? \"unknown error\"}`));\n }\n }\n\n if (opts.exit) {\n const exitOk = results.filter((r) => r.exitOk).length;\n console.log(dim(` /exit sent to ${exitOk} session(s).`));\n }\n\n console.log();\n}\n","/**\n * pai sessions clear-names\n *\n * Recovery command: wipes corrupted iTerm2 session name state.\n *\n * Two-step reset:\n * 1. Clear ~/.aibroker/session-names.json (the persistent name store).\n * 2. Clear user.paiName variable from all live iTerm2 sessions.\n *\n * After this runs the user can `/Name` each tab fresh. Nothing is\n * set automatically — the user re-asserts names via the /Name skill.\n */\n\nimport { callAiBroker } from \"../../lib/aibroker-client.js\";\nimport { ok, err, dim, warn } from \"../../utils.js\";\n\nexport async function cmdClearNames(opts: { dryRun?: boolean }): Promise<void> {\n if (opts.dryRun) {\n console.log(dim(\"Dry run — would:\"));\n console.log(dim(\" 1. Call AIBroker clear_session_names (wipe session-names.json)\"));\n console.log(dim(\" 2. Call AIBroker clear_pai_names (wipe user.paiName from all iTerm sessions)\"));\n return;\n }\n\n let anyFailed = false;\n\n // Step 1: clear the persistent name store\n process.stdout.write(\"Clearing session-names.json … \");\n try {\n const r1 = await callAiBroker(\"clear_session_names\", {}, 10_000) as {\n cleared?: number;\n };\n const n = r1.cleared ?? 0;\n console.log(ok(`cleared ${n} entr${n === 1 ? \"y\" : \"ies\"}`));\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (msg.includes(\"ENOENT\") || msg.includes(\"ECONNREFUSED\")) {\n console.log(warn(\"AIBroker not running — session-names.json not cleared\"));\n anyFailed = true;\n } else {\n console.log(err(msg));\n anyFailed = true;\n }\n }\n\n // Step 2: clear user.paiName from all live iTerm2 sessions\n process.stdout.write(\"Clearing user.paiName from iTerm2 sessions … \");\n try {\n const r2 = await callAiBroker(\"clear_pai_names\", {}, 30_000) as {\n cleared?: number;\n };\n const n = r2.cleared ?? 0;\n console.log(ok(`cleared ${n} session${n === 1 ? \"\" : \"s\"}`));\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (msg.includes(\"ENOENT\") || msg.includes(\"ECONNREFUSED\")) {\n console.log(warn(\"AIBroker not running — iTerm2 paiName variables not cleared\"));\n anyFailed = true;\n } else {\n console.log(err(msg));\n anyFailed = true;\n }\n }\n\n if (anyFailed) {\n console.log(warn(\"\\nSome steps could not complete. Restart AIBroker and try again.\"));\n process.exitCode = 1;\n } else {\n console.log(ok(\"\\nDone. Use /Name to re-label each tab.\"));\n }\n}\n","/**\n * Shared DB helpers, formatting, and path utilities for session sub-commands.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { err } from \"../../utils.js\";\nimport type { SessionRow, ProjectRow } from \"./types.js\";\n\nexport function getProject(db: Database, slug: string): ProjectRow | undefined {\n return db\n .prepare(\n \"SELECT id, slug, display_name, root_path, encoded_dir FROM projects WHERE slug = ?\"\n )\n .get(slug) as ProjectRow | undefined;\n}\n\nexport function statusColor(status: string): string {\n switch (status) {\n case \"completed\":\n return chalk.green(status);\n case \"compacted\":\n return chalk.blue(status);\n default:\n return chalk.yellow(status);\n }\n}\n\n/** Convert a slug to title-cased display name: \"memory-engine\" → \"Memory Engine\" */\nexport function toTitleCase(slug: string): string {\n return slug\n .replace(/-/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/** Notes directory for a project: ~/.claude/projects/<encoded_dir>/Notes/ */\nexport function getNotesDir(project: ProjectRow): string {\n return join(homedir(), \".claude\", \"projects\", project.encoded_dir, \"Notes\");\n}\n\n/** Format a session filename: number=27, date=\"2026-02-23\" → \"0027 - 2026-02-23 - Title.md\" */\nexport function formatFilename(\n number: number,\n date: string,\n titleSlug: string\n): string {\n const n = String(number).padStart(4, \"0\");\n return `${n} - ${date} - ${titleSlug}.md`;\n}\n\n/** Resolve a session by project + number or \"latest\". Exits on failure. */\nexport function resolveSession(\n db: Database,\n project: ProjectRow,\n numberOrLatest: string\n): SessionRow {\n let session: SessionRow | undefined;\n\n if (numberOrLatest === \"latest\") {\n session = db\n .prepare(\n \"SELECT * FROM sessions WHERE project_id = ? ORDER BY number DESC LIMIT 1\"\n )\n .get(project.id) as SessionRow | undefined;\n } else {\n const num = parseInt(numberOrLatest, 10);\n if (isNaN(num)) {\n console.error(err(`Invalid session number: ${numberOrLatest}`));\n process.exit(1);\n }\n session = db\n .prepare(\"SELECT * FROM sessions WHERE project_id = ? AND number = ?\")\n .get(project.id, num) as SessionRow | undefined;\n }\n\n if (!session) {\n console.error(\n err(`Session ${numberOrLatest} not found in project ${project.slug}`)\n );\n process.exit(1);\n }\n\n return session;\n}\n\nexport function upsertTag(db: Database, tagName: string): number {\n db.prepare(\"INSERT OR IGNORE INTO tags (name) VALUES (?)\").run(tagName);\n const row = db\n .prepare(\"SELECT id FROM tags WHERE name = ?\")\n .get(tagName) as { id: number };\n return row.id;\n}\n\nexport function getSessionTags(db: Database, sessionId: number): string[] {\n const rows = db\n .prepare(\n `SELECT t.name FROM tags t\n JOIN session_tags st ON st.tag_id = t.id\n WHERE st.session_id = ?\n ORDER BY t.name`\n )\n .all(sessionId) as { name: string }[];\n return rows.map((r) => r.name);\n}\n","/**\n * Session CRUD commands: list, info, rename, slug, tag, route, active, auto-route.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n readdirSync,\n renameSync,\n readFileSync,\n writeFileSync,\n statSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport {\n ok,\n warn,\n err,\n dim,\n bold,\n header,\n renderTable,\n fmtDate,\n} from \"../../utils.js\";\nimport {\n findLatestTranscript,\n readLastMessages,\n generateSlug,\n} from \"../../../session/slug-generator.js\";\nimport type { SessionRow, ProjectRow } from \"./types.js\";\nimport {\n getProject,\n statusColor,\n toTitleCase,\n getNotesDir,\n formatFilename,\n resolveSession,\n upsertTag,\n getSessionTags,\n} from \"./helpers.js\";\n\n// ---------------------------------------------------------------------------\n// list\n// ---------------------------------------------------------------------------\n\nexport function cmdList(\n db: Database,\n projectSlug: string | undefined,\n opts: { limit?: string; status?: string }\n): void {\n const limit = parseInt(opts.limit ?? \"20\", 10);\n const params: unknown[] = [];\n\n let query = `\n SELECT s.*, p.slug AS project_slug, p.display_name AS project_name\n FROM sessions s\n JOIN projects p ON p.id = s.project_id\n `;\n\n const where: string[] = [];\n if (projectSlug) {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n where.push(\"s.project_id = ?\");\n params.push(project.id);\n }\n if (opts.status) {\n where.push(\"s.status = ?\");\n params.push(opts.status);\n }\n\n if (where.length) query += \" WHERE \" + where.join(\" AND \");\n query += \" ORDER BY s.date DESC, s.number DESC\";\n query += ` LIMIT ${limit}`;\n\n const rows = db.prepare(query).all(...params) as (SessionRow & {\n project_slug: string;\n project_name: string;\n })[];\n\n if (!rows.length) {\n console.log(warn(\"No sessions found.\"));\n return;\n }\n\n const showProject = !projectSlug;\n const headers = showProject\n ? [\"#\", \"Project\", \"Date\", \"Title\", \"Status\", \"Tokens\"]\n : [\"#\", \"Date\", \"Title\", \"Status\", \"Tokens\"];\n\n const tableRows = rows.map((r) => {\n const title = r.title.length > 45 ? r.title.slice(0, 42) + \"...\" : r.title;\n const tokens =\n r.token_count != null ? dim(r.token_count.toLocaleString()) : dim(\"—\");\n if (showProject) {\n return [\n dim(`#${r.number}`),\n r.project_slug,\n r.date,\n title,\n statusColor(r.status),\n tokens,\n ];\n }\n return [dim(`#${r.number}`), r.date, title, statusColor(r.status), tokens];\n });\n\n console.log();\n if (projectSlug) {\n const project = getProject(db, projectSlug)!;\n console.log(` ${bold(project.display_name)} sessions:`);\n console.log();\n }\n console.log(renderTable(headers, tableRows));\n console.log();\n console.log(dim(` ${rows.length} session(s) shown (limit: ${limit})`));\n}\n\n// ---------------------------------------------------------------------------\n// info\n// ---------------------------------------------------------------------------\n\nexport function cmdInfo(\n db: Database,\n projectSlug: string,\n sessionNumber: string\n): void {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const session = resolveSession(db, project, sessionNumber);\n\n console.log();\n console.log(header(` Session #${session.number}: ${session.title}`));\n console.log();\n console.log(\n ` ${bold(\"Project:\")} ${project.display_name} (${project.slug})`\n );\n console.log(` ${bold(\"Date:\")} ${session.date}`);\n console.log(` ${bold(\"Status:\")} ${statusColor(session.status)}`);\n console.log(` ${bold(\"Filename:\")} ${session.filename}`);\n console.log(` ${bold(\"Slug:\")} ${session.slug}`);\n if (session.claude_session_id) {\n console.log(\n ` ${bold(\"Claude ID:\")} ${dim(session.claude_session_id)}`\n );\n }\n if (session.token_count != null) {\n console.log(\n ` ${bold(\"Tokens:\")} ${session.token_count.toLocaleString()}`\n );\n }\n console.log(` ${bold(\"Created:\")} ${fmtDate(session.created_at)}`);\n if (session.closed_at) {\n console.log(` ${bold(\"Closed:\")} ${fmtDate(session.closed_at)}`);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// rename\n// ---------------------------------------------------------------------------\n\nexport function cmdRename(\n db: Database,\n projectSlug: string,\n numberOrLatest: string,\n newSlug: string\n): void {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const session = resolveSession(db, project, numberOrLatest);\n const notesDir = getNotesDir(project);\n\n if (!existsSync(notesDir)) {\n console.error(err(`Notes directory not found: ${notesDir}`));\n process.exit(1);\n }\n\n const titleSlug = toTitleCase(newSlug);\n const newFilename = formatFilename(session.number, session.date, titleSlug);\n const oldPath = join(notesDir, session.filename);\n const newPath = join(notesDir, newFilename);\n\n if (existsSync(oldPath)) {\n if (oldPath !== newPath) {\n try {\n renameSync(oldPath, newPath);\n } catch (e) {\n console.error(err(`Failed to rename file: ${e}`));\n process.exit(1);\n }\n }\n } else {\n console.log(\n warn(` Note: file not found at expected path: ${session.filename}`)\n );\n console.log(warn(` Skipping disk rename. Database will still be updated.`));\n }\n\n if (existsSync(newPath)) {\n try {\n const content = readFileSync(newPath, \"utf8\");\n const lines = content.split(\"\\n\");\n let h1Updated = false;\n const updated = lines.map((line) => {\n if (!h1Updated && line.startsWith(\"# \")) {\n h1Updated = true;\n return `# ${titleSlug}`;\n }\n return line;\n });\n if (!h1Updated) updated.unshift(`# ${titleSlug}`, \"\");\n writeFileSync(newPath, updated.join(\"\\n\"), \"utf8\");\n } catch (e) {\n console.error(err(`Failed to update H1 in file: ${e}`));\n }\n }\n\n const normalizedSlug = newSlug\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n\n db.prepare(\n \"UPDATE sessions SET slug = ?, title = ?, filename = ? WHERE id = ?\"\n ).run(normalizedSlug, titleSlug, newFilename, session.id);\n\n console.log();\n console.log(ok(` Session #${session.number} renamed.`));\n console.log(` ${bold(\"Old:\")} ${session.filename}`);\n console.log(` ${bold(\"New:\")} ${newFilename}`);\n console.log(` ${bold(\"Slug:\")} ${normalizedSlug}`);\n console.log(` ${bold(\"Title:\")} ${titleSlug}`);\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// slug\n// ---------------------------------------------------------------------------\n\nexport function cmdSlug(\n db: Database,\n projectSlug: string,\n numberOrLatest: string,\n opts: { apply?: boolean }\n): void {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const session = resolveSession(db, project, numberOrLatest);\n const transcriptPath = findLatestTranscript(project.encoded_dir);\n\n if (!transcriptPath) {\n console.log(warn(` No JSONL transcripts found for project ${projectSlug}`));\n console.log(\"unnamed-session\");\n return;\n }\n\n const messages = readLastMessages(transcriptPath);\n\n if (messages.length < 2) {\n console.log(\n warn(` Too few messages found (${messages.length}) in transcript`)\n );\n console.log(\"unnamed-session\");\n return;\n }\n\n const generatedSlug = generateSlug(messages);\n console.log(generatedSlug);\n\n if (opts.apply) {\n console.log();\n console.log(dim(` Applying slug to session #${session.number}...`));\n cmdRename(db, projectSlug, String(session.number), generatedSlug);\n }\n}\n\n// ---------------------------------------------------------------------------\n// tag\n// ---------------------------------------------------------------------------\n\nexport function cmdTag(\n db: Database,\n projectSlug: string,\n sessionNumber: string,\n rawTags: string[]\n): void {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const session = resolveSession(db, project, sessionNumber);\n\n if (rawTags.length === 0) {\n const current = getSessionTags(db, session.id);\n console.log();\n if (current.length === 0) {\n console.log(dim(` Session #${session.number} has no tags.`));\n } else {\n console.log(\n ` ${bold(`Session #${session.number}`)} tags: ${current\n .map((t) => chalk.cyan(t))\n .join(\", \")}`\n );\n }\n console.log();\n return;\n }\n\n const tags = rawTags\n .flatMap((t) => t.split(\",\"))\n .map((t) => t.trim().toLowerCase())\n .filter((t) => t.length > 0);\n\n if (tags.length === 0) {\n console.log(warn(\"No valid tags provided.\"));\n return;\n }\n\n const added: string[] = [];\n const skipped: string[] = [];\n\n for (const tagName of tags) {\n const tagId = upsertTag(db, tagName);\n const exists = db\n .prepare(\"SELECT 1 FROM session_tags WHERE session_id = ? AND tag_id = ?\")\n .get(session.id, tagId);\n if (exists) {\n skipped.push(tagName);\n } else {\n db.prepare(\n \"INSERT INTO session_tags (session_id, tag_id) VALUES (?, ?)\"\n ).run(session.id, tagId);\n added.push(tagName);\n }\n }\n\n console.log();\n if (added.length) {\n console.log(\n ok(\n ` Tagged session #${session.number}: ${added\n .map((t) => chalk.cyan(t))\n .join(\", \")}`\n )\n );\n }\n if (skipped.length) {\n console.log(dim(` Already present: ${skipped.join(\", \")}`));\n }\n\n const allTags = getSessionTags(db, session.id);\n console.log(\n ` ${bold(\"All tags:\")} ${allTags.map((t) => chalk.cyan(t)).join(\", \")}`\n );\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// route\n// ---------------------------------------------------------------------------\n\nexport function cmdRoute(\n db: Database,\n projectSlug: string,\n sessionNumber: string,\n targetProjectSlug: string,\n opts: { type?: string }\n): void {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const session = resolveSession(db, project, sessionNumber);\n\n const targetProject = db\n .prepare(\"SELECT id, slug, display_name FROM projects WHERE slug = ?\")\n .get(targetProjectSlug) as\n | { id: number; slug: string; display_name: string }\n | undefined;\n\n if (!targetProject) {\n console.error(err(`Target project not found: ${targetProjectSlug}`));\n process.exit(1);\n }\n\n const validTypes = [\"related\", \"follow-up\", \"reference\"];\n const linkType = opts.type ?? \"related\";\n if (!validTypes.includes(linkType)) {\n console.error(\n err(`Invalid link type \"${linkType}\". Valid: ${validTypes.join(\", \")}`)\n );\n process.exit(1);\n }\n\n try {\n db.prepare(\n `INSERT INTO links (session_id, target_project_id, link_type, created_at)\n VALUES (?, ?, ?, ?)`\n ).run(session.id, targetProject.id, linkType, Date.now());\n } catch {\n console.log(\n warn(\n ` Link already exists: session #${session.number} → ${targetProjectSlug}`\n )\n );\n return;\n }\n\n console.log();\n console.log(\n ok(\n ` Linked session #${session.number} (${project.slug}) → ${targetProject.display_name} (${targetProjectSlug})`\n )\n );\n console.log(dim(` Link type: ${linkType}`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// active\n// ---------------------------------------------------------------------------\n\nexport function cmdActive(\n db: Database,\n opts: { minutes?: string; json?: boolean }\n): void {\n const minutes = parseInt(opts.minutes ?? \"60\", 10);\n const cutoff = Date.now() - minutes * 60 * 1000;\n const claudeProjectsDir = join(homedir(), \".claude\", \"projects\");\n\n if (!existsSync(claudeProjectsDir)) {\n console.log(err(\"Claude projects directory not found.\"));\n return;\n }\n\n interface ActiveSession {\n slug: string;\n displayName: string;\n rootPath: string;\n encodedDir: string;\n lastModified: Date;\n jsonlFile: string;\n }\n\n const active: ActiveSession[] = [];\n const entries = readdirSync(claudeProjectsDir);\n\n for (const entry of entries) {\n const projectDir = join(claudeProjectsDir, entry);\n try {\n if (!statSync(projectDir).isDirectory()) continue;\n } catch {\n continue;\n }\n\n let latestJsonl: string | null = null;\n let latestMtime = 0;\n\n try {\n for (const file of readdirSync(projectDir)) {\n if (!file.endsWith(\".jsonl\")) continue;\n const filePath = join(projectDir, file);\n try {\n const mtime = statSync(filePath).mtimeMs;\n if (mtime > latestMtime) {\n latestMtime = mtime;\n latestJsonl = filePath;\n }\n } catch {\n continue;\n }\n }\n } catch {\n continue;\n }\n\n if (!latestJsonl || latestMtime < cutoff) continue;\n\n const project = db\n .prepare(\n \"SELECT slug, display_name, root_path FROM projects WHERE encoded_dir = ?\"\n )\n .get(entry) as\n | { slug: string; display_name: string; root_path: string }\n | undefined;\n\n active.push({\n slug: project?.slug ?? entry,\n displayName: project?.display_name ?? project?.slug ?? entry,\n rootPath: project?.root_path ?? \"\",\n encodedDir: entry,\n lastModified: new Date(latestMtime),\n jsonlFile: latestJsonl,\n });\n }\n\n active.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());\n\n const seen = new Set<string>();\n const deduped = active.filter((a) => {\n const key = a.slug.replace(/-\\d+$/, \"\");\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n if (opts.json) {\n console.log(\n JSON.stringify(\n deduped.map((a) => ({\n slug: a.slug,\n display_name: a.displayName,\n root_path: a.rootPath,\n last_modified: a.lastModified.toISOString(),\n })),\n null,\n 2\n )\n );\n return;\n }\n\n if (deduped.length === 0) {\n console.log(dim(`No active sessions in the last ${minutes} minutes.`));\n return;\n }\n\n console.log(\n header(`Currently Active Sessions`) +\n dim(` (modified in last ${minutes}min)`)\n );\n console.log();\n\n const rows = deduped.map((a) => {\n const time = a.lastModified.toTimeString().slice(0, 5);\n const dirName = a.rootPath\n ? a.rootPath.replace(homedir(), \"~\").split(\"/\").pop() ?? a.slug\n : a.slug;\n return [chalk.cyan(dirName), dim(a.slug), chalk.green(time)];\n });\n\n console.log(renderTable([\"Directory\", \"Project\", \"Last Active\"], rows));\n}\n\n// ---------------------------------------------------------------------------\n// auto-route\n// ---------------------------------------------------------------------------\n\nexport async function cmdAutoRoute(opts: {\n cwd?: string;\n context?: string;\n json?: boolean;\n}): Promise<void> {\n const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import(\n \"../../../session/auto-route.js\"\n );\n const { openRegistry } = await import(\"../../../registry/db.js\");\n const { createStorageBackend } = await import(\"../../../storage/factory.js\");\n const { loadConfig } = await import(\"../../../daemon/config.js\");\n\n const config = loadConfig();\n const registryDb = openRegistry();\n const federation = await createStorageBackend(config);\n\n const targetCwd = opts.cwd ?? process.cwd();\n const result = await autoRoute(\n registryDb,\n federation,\n targetCwd,\n opts.context\n );\n\n if (!result) {\n console.log();\n console.log(warn(\" No project match found for: \" + targetCwd));\n console.log();\n console.log(\n dim(\" Tried: path match, PAI.md marker walk\") +\n (opts.context ? dim(\", topic detection\") : \"\")\n );\n console.log();\n console.log(dim(\" Run 'pai project add .' to register this directory.\"));\n console.log();\n return;\n }\n\n if (opts.json) {\n console.log(formatAutoRouteJson(result));\n return;\n }\n\n console.log();\n console.log(header(\" PAI Auto-Route\"));\n console.log();\n console.log(` ${bold(\"Project:\")} ${result.display_name}`);\n console.log(` ${bold(\"Slug:\")} ${result.slug}`);\n console.log(` ${bold(\"Root path:\")} ${result.root_path}`);\n console.log(` ${bold(\"Method:\")} ${result.method}`);\n console.log(\n ` ${bold(\"Confidence:\")} ${(result.confidence * 100).toFixed(0)}%`\n );\n console.log();\n console.log(ok(\" Routed to: \") + bold(result.slug));\n console.log();\n}\n","/**\n * pai find <query> [--n=20] [--json]\n *\n * Content-based search across ~/.claude/history.jsonl.\n * Finds user prompts matching the query (case-insensitive substring),\n * groups by sessionId, sorts by most-recent match, and shows a table\n * with short UUID, date, project path, and the matching prompt snippet.\n *\n * The sessionId from history.jsonl is the same UUID used by claude --resume,\n * so rows are directly resumable via: pai resume <id>\n *\n * Note: older history.jsonl entries (before Claude Code ~2.0) may lack a\n * sessionId field. These are grouped under a synthetic \"no-session\" bucket\n * and shown in a separate note at the bottom.\n */\n\nimport { existsSync, createReadStream } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { renderTable, header, dim, warn, err } from \"../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface HistoryEntry {\n display?: string;\n timestamp?: number;\n project?: string;\n sessionId?: string;\n pastedContents?: unknown;\n}\n\ninterface SessionMatch {\n sessionId: string | null;\n /** Most-recent timestamp among matching lines */\n lastMatchTs: number;\n /** Most-recent matching prompt text */\n lastMatchDisplay: string;\n /** Project path (from the most-recent match) */\n project: string;\n /** Number of matching lines in this session */\n matchCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// History file location\n// ---------------------------------------------------------------------------\n\nconst HISTORY_FILE = join(homedir(), \".claude\", \"history.jsonl\");\n\n// ---------------------------------------------------------------------------\n// Shorten project path for display\n// ---------------------------------------------------------------------------\n\nfunction shortenProject(p: string, maxLen = 42): string {\n if (p.length <= maxLen) return p;\n // Keep tail (the meaningful part) and prefix with …\n return \"…\" + p.slice(-(maxLen - 1));\n}\n\n// ---------------------------------------------------------------------------\n// Format timestamp\n// ---------------------------------------------------------------------------\n\nfunction fmtTs(ts: number): string {\n const d = new Date(ts);\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +\n `${pad(d.getHours())}:${pad(d.getMinutes())}`\n );\n}\n\n// ---------------------------------------------------------------------------\n// Core search function (streaming to avoid loading 26k lines into memory)\n// ---------------------------------------------------------------------------\n\nexport async function searchHistory(\n query: string,\n maxResults: number\n): Promise<SessionMatch[]> {\n if (!existsSync(HISTORY_FILE)) return [];\n\n const queryLower = query.toLowerCase();\n // Escape regex special chars from query for safety — but we're doing\n // string .includes() not regex, so no escaping needed.\n\n const bySession = new Map<string, SessionMatch>();\n const noSession: SessionMatch = {\n sessionId: null,\n lastMatchTs: 0,\n lastMatchDisplay: \"\",\n project: \"\",\n matchCount: 0,\n };\n\n const rl = createInterface({\n input: createReadStream(HISTORY_FILE, { encoding: \"utf8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n const t = line.trim();\n if (!t) continue;\n\n let entry: HistoryEntry;\n try {\n entry = JSON.parse(t) as HistoryEntry;\n } catch {\n continue;\n }\n\n const display = entry.display ?? \"\";\n if (!display.toLowerCase().includes(queryLower)) continue;\n\n const ts = entry.timestamp ?? 0;\n const project = entry.project ?? \"\";\n const sessionId = entry.sessionId ?? null;\n\n if (!sessionId) {\n // Older entry without sessionId\n if (ts > noSession.lastMatchTs) {\n noSession.lastMatchTs = ts;\n noSession.lastMatchDisplay = display;\n noSession.project = project;\n }\n noSession.matchCount++;\n continue;\n }\n\n const existing = bySession.get(sessionId);\n if (!existing) {\n bySession.set(sessionId, {\n sessionId,\n lastMatchTs: ts,\n lastMatchDisplay: display,\n project,\n matchCount: 1,\n });\n } else {\n existing.matchCount++;\n if (ts > existing.lastMatchTs) {\n existing.lastMatchTs = ts;\n existing.lastMatchDisplay = display;\n existing.project = project;\n }\n }\n }\n\n // Sort by most-recent match descending\n const sorted = [...bySession.values()].sort(\n (a, b) => b.lastMatchTs - a.lastMatchTs\n );\n\n return sorted.slice(0, maxResults);\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdFind(\n query: string,\n opts: { n?: string; json?: boolean }\n): Promise<void> {\n const maxResults = parseInt(opts.n ?? \"20\", 10);\n\n if (!existsSync(HISTORY_FILE)) {\n console.error(err(`~/.claude/history.jsonl not found.`));\n process.exitCode = 1;\n return;\n }\n\n const matches = await searchHistory(query, maxResults);\n\n if (opts.json) {\n console.log(JSON.stringify(matches, null, 2));\n return;\n }\n\n if (matches.length === 0) {\n console.log(warn(`No sessions found matching \"${query}\" in ~/.claude/history.jsonl.`));\n console.log(dim(` Try a shorter or different search term.`));\n return;\n }\n\n console.log(\"\\n\" + header(\"Session Search Results\") + \"\\n\");\n console.log(dim(` Query: \"${query}\" (${matches.length} session(s) with matching prompts)\\n`));\n\n const headers = [\"#\", \"id\", \"when\", \"project\", \"last matching prompt\"];\n const rows = matches.map((m, idx) => {\n const shortId = (m.sessionId ?? \"no-session\").slice(0, 8);\n const when = m.lastMatchTs > 0 ? fmtTs(m.lastMatchTs) : dim(\"—\");\n const project = shortenProject(m.project || dim(\"—\"));\n // Truncate the prompt snippet — strip newlines, trim\n const snippet = m.lastMatchDisplay\n .replace(/\\n+/g, \" \")\n .trim()\n .slice(0, 48);\n const display = snippet.length < m.lastMatchDisplay.replace(/\\n+/g, \" \").trim().length\n ? `\"${snippet}…\"`\n : `\"${snippet}\"`;\n\n return [\n dim(String(idx + 1)),\n chalk.cyan(shortId),\n when,\n dim(project),\n chalk.dim(display),\n ];\n });\n\n console.log(renderTable(headers, rows));\n console.log();\n console.log(dim(\" Resume: \") + chalk.white(\"pai resume <id>\"));\n console.log();\n}\n","/**\n * launch.ts — Launch Claude Code in a directory, in the CURRENT terminal.\n *\n * Shared by the interactive picker (pick.ts). Deliberately does NOT switch\n * iTerm tabs (aibroker_switch): switching jumps the user to a different — and\n * sometimes wrong — terminal, which is confusing. Picking a place should start\n * a session right here, in the chosen directory.\n *\n * Behaviour:\n * resume-or-fresh → if a resumable UUID is given, probe it; on success\n * `claude --resume`, otherwise fall back to a fresh session\n * in the same dir. With no UUID, start fresh.\n *\n * The `claude` child inherits the tty (stdio: \"inherit\"), so the session runs\n * in the terminal that launched `pai`. On exit we print the working directory.\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport { err } from \"../utils.js\";\nimport { printExitDir } from \"./exit-dir.js\";\n\nexport interface ProbeResult {\n ok: boolean;\n reason?: string;\n}\n\n/**\n * Probe whether a session UUID is resumable from `cwd` (5s timeout).\n * Mirrors the probe in goto.ts / main-resolver.ts.\n */\nexport function probeResume(uuid: string, cwd: string): ProbeResult {\n const result = spawnSync(\n \"claude\",\n [\"--resume\", uuid, \"--print\", \"--output-format=json\", \"_\"],\n { cwd, timeout: 5_000, env: process.env, stdio: [\"ignore\", \"ignore\", \"pipe\"] }\n );\n\n if (result.error) return { ok: false, reason: `spawn error: ${result.error.message}` };\n\n const stderr = result.stderr?.toString(\"utf8\") ?? \"\";\n if (\n stderr.toLowerCase().includes(\"no conversation found\") ||\n stderr.toLowerCase().includes(\"session not found\")\n ) {\n return { ok: false, reason: \"No conversation found for this UUID\" };\n }\n if (result.status !== 0) {\n return {\n ok: false,\n reason: `claude exited ${result.status ?? \"signal\"}${stderr ? `: ${stderr.slice(0, 120).trim()}` : \"\"}`,\n };\n }\n return { ok: true };\n}\n\nexport interface LaunchOpts {\n /** If set, try to resume this session before falling back to fresh. */\n resumableUuid?: string;\n /** Skip the resume probe and start a brand-new session in the dir. */\n forceFresh?: boolean;\n /** Print what would happen, then return without launching. */\n dryRun?: boolean;\n}\n\n/**\n * Launch `claude` in `dir` in the current terminal. `name` is used for both the\n * Claude session label (--name) and the /Name slash command (tab/statusline).\n * Never returns on the live path — it exits the process after claude exits.\n */\nexport function launchInDir(dir: string, name: string, opts: LaunchOpts = {}): void {\n let cwd: string;\n try {\n cwd = realpathSync(dir);\n } catch {\n console.error(\n err(\n `Directory does not exist or cannot be resolved:\\n ${dir}\\n` +\n ` The folder may have moved or been deleted.`\n )\n );\n process.exit(1);\n return;\n }\n\n const promptArg = `/Name ${name}\\ngo`;\n const wantResume = !opts.forceFresh && !!opts.resumableUuid;\n\n if (opts.dryRun) {\n if (wantResume) {\n console.log(\"\\n\" + chalk.bold(\"Dry run — would probe then exec (RESUME path):\") + \"\\n\");\n console.log(` cwd: ${chalk.cyan(cwd)}`);\n console.log(` probe: claude --resume ${opts.resumableUuid} --print --output-format=json \"_\"`);\n console.log(` argv: claude --resume ${opts.resumableUuid} --name \"${name}\" \"/Name ${name}\\\\ngo\"`);\n console.log(` fallback: claude --name \"${name}\" \"/Name ${name}\\\\ngo\"`);\n } else {\n console.log(\"\\n\" + chalk.bold(\"Dry run — would exec (FRESH path):\") + \"\\n\");\n console.log(` cwd: ${chalk.cyan(cwd)}`);\n console.log(` argv: claude --name \"${name}\" \"/Name ${name}\\\\ngo\"`);\n }\n console.log();\n return;\n }\n\n const fresh = () => {\n const result = spawnSync(\"claude\", [\"--name\", name, promptArg], {\n cwd,\n stdio: \"inherit\",\n env: process.env,\n });\n if (result.error) {\n console.error(err(`Failed to launch claude: ${result.error.message}`));\n process.exit(1);\n }\n printExitDir(cwd);\n process.exit(result.status ?? 0);\n };\n\n if (wantResume) {\n const probe = probeResume(opts.resumableUuid!, cwd);\n if (probe.ok) {\n const result = spawnSync(\n \"claude\",\n [\"--resume\", opts.resumableUuid!, \"--name\", name, promptArg],\n { cwd, stdio: \"inherit\", env: process.env }\n );\n if (result.error) {\n console.error(err(`Failed to launch claude: ${result.error.message}`));\n process.exit(1);\n }\n printExitDir(cwd);\n process.exit(result.status ?? 0);\n }\n process.stderr.write(\n chalk.yellow(\n `\\n Resume failed for ${opts.resumableUuid!.slice(0, 8)}: ${probe.reason ?? \"unknown error\"}\\n` +\n ` Starting fresh session in same directory.\\n\\n`\n )\n );\n fresh();\n return;\n }\n\n fresh();\n}\n","/**\n * pick.ts — Interactive project/session picker (`pai` with no args).\n *\n * One door for \"find where I worked on something and go there.\" A self-contained\n * modal TUI (no fzf) over a UNIFIED, deduped list of:\n * - PROJECTS (places where knowledge lives — every active project, even cold)\n * - SESSIONS (past conversations — live / resumable / transcript)\n * tagged so the two stay visibly distinct.\n *\n * Two modes (see runSelector):\n * - command (default): ↑↓/j/k move · g go-to-tab · n new · c cd · f finder · q quit\n * - search (after s or /): type to filter, Enter/esc back to command\n * Action keys fire IMMEDIATELY on the highlighted row — letters are commands in\n * command mode, filter text only in the explicit, visible search mode. This is\n * why a single fzf-style \"type to filter\" box couldn't work: there, every letter\n * is filter input and can never also be an action.\n *\n * Design decisions (all driven by real pain):\n * - Filtering matches names, paths, AND folded-in note file/folder names, so\n * typing \"samba\" surfaces a project whose *name* is \"Chenarlier\".\n * - The detail panel shows context for the HIGHLIGHTED row only (recent notes),\n * computed lazily — avoids the ~8s/all-sessions live-prompt fetch.\n * - Actions launch Claude in the CURRENT terminal (g switches the iTerm tab via\n * the screenshot reveal mechanism; n starts fresh; c hands the dir to the\n * pai() shell wrapper to cd; f opens the OS file manager).\n * - Every rendered line is truncated to the window width so nothing wraps\n * (wrapping desyncs the one-row-per-line layout).\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { spawn } from \"node:child_process\";\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\nimport chalk from \"chalk\";\nimport { dim, encodeDir, now } from \"../utils.js\";\nimport { scanSessions, fmtAge, type ScannedSession } from \"../lib/session-scan.js\";\nimport {\n buildDeduped,\n normalizeName,\n type UnifiedSession,\n type RegisteredProject,\n} from \"../lib/dedup-sessions.js\";\nimport { writeFileSync } from \"node:fs\";\nimport { fetchLiveSessions, revealItermSession } from \"../lib/aibroker-client.js\";\nimport { renderDedupedSessions } from \"../lib/dedup-sessions.js\";\nimport { launchInDir } from \"../lib/launch.js\";\nimport { printExitDir } from \"../lib/exit-dir.js\";\n\n// ---------------------------------------------------------------------------\n// Dispatch record — one per selectable row.\n// ---------------------------------------------------------------------------\n\ninterface PickRecord {\n /** 'D' = has a directory to act on; 'L' = live-only, no dir. */\n t: \"D\" | \"L\";\n name: string;\n dir?: string;\n /** Best resumable session UUID for the dir, if any. */\n uuid?: string;\n /** Live AIBroker / iTerm2 session id (present whenever the row is live). */\n live?: string;\n /** Registry slug, when this row maps to a registered project (enables 'd'). */\n slug?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Project registry query (ALL active projects — cold ones included on purpose)\n// ---------------------------------------------------------------------------\n\nfunction getProjects(db: Database, includeArchived: boolean): RegisteredProject[] {\n try {\n const rows = db\n .prepare(`\n SELECT p.slug, p.display_name, p.root_path, p.status,\n COUNT(s.id) AS session_count,\n MAX(s.created_at) AS last_active\n FROM projects p\n LEFT JOIN sessions s ON s.project_id = p.id\n GROUP BY p.id\n ORDER BY last_active DESC NULLS LAST, p.updated_at DESC\n `)\n .all() as RegisteredProject[];\n return includeArchived ? rows : rows.filter((p) => p.status === \"active\");\n } catch {\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Note-keyword folding — cheap topic hints from file/folder names\n// ---------------------------------------------------------------------------\n\nconst KW_SKIP = new Set([\n \"node_modules\", \".git\", \".obsidian\", \"dist\", \"build\", \".cache\", \"vendor\",\n \".next\", \"target\", \"__pycache__\", \".venv\", \"coverage\",\n]);\nconst KW_STOP = new Set([\n \"the\", \"and\", \"for\", \"with\", \"notes\", \"note\", \"readme\", \"index\", \"src\",\n \"untitled\", \"new\", \"draft\", \"md\", \"txt\",\n]);\n\n/** Collect topic tokens from markdown file/folder names, depth-limited. */\nfunction noteKeywords(root: string, maxTokens = 14): string {\n const tokens = new Set<string>();\n const walk = (dir: string, depth: number) => {\n if (depth > 2 || tokens.size >= maxTokens * 3) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (name.startsWith(\".\") || KW_SKIP.has(name)) continue;\n const full = join(dir, name);\n let isDir = false;\n try {\n isDir = statSync(full).isDirectory();\n } catch {\n continue;\n }\n if (isDir) {\n for (const tok of tokenize(name)) tokens.add(tok);\n walk(full, depth + 1);\n } else if (name.toLowerCase().endsWith(\".md\")) {\n for (const tok of tokenize(name)) tokens.add(tok);\n }\n }\n };\n walk(root, 0);\n return Array.from(tokens).slice(0, maxTokens).join(\" \");\n}\n\nfunction tokenize(s: string): string[] {\n return s\n .replace(/\\.md$/i, \"\")\n .replace(/[0-9]+/g, \" \")\n .split(/[^a-zA-ZÀ-ſ]+/)\n .map((t) => t.toLowerCase())\n .filter((t) => t.length >= 3 && !KW_STOP.has(t));\n}\n\n// ---------------------------------------------------------------------------\n// Feed construction\n// ---------------------------------------------------------------------------\n\ninterface FeedLine {\n record: PickRecord;\n /** ANSI-colored display row. */\n display: string;\n /** Lowercased plain text (name · dir · keywords) used for filtering. */\n search: string;\n}\n\nfunction tagFor(status: UnifiedSession[\"status\"]): string {\n switch (status) {\n case \"live\":\n return chalk.green(\"live \");\n case \"resumable\":\n return chalk.cyan(\"resume \");\n case \"project\":\n return chalk.magenta(\"project\");\n default:\n return chalk.dim(\"old \");\n }\n}\n\nfunction pad(s: string, n: number): string {\n // pad based on visible length (strip ANSI)\n const visible = s.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n if (visible.length >= n) return s;\n return s + \" \".repeat(n - visible.length);\n}\n\nfunction shorten(p: string, max = 40): string {\n if (!p) return \"\";\n return p.length <= max ? p : \"…\" + p.slice(-(max - 1));\n}\n\n/**\n * Build the candidate list. buildDeduped merges live+disk+registry into one\n * tagged, deduped catalog. showAll=true so cold/idle projects are NOT filtered\n * out (finding old work is the whole point); archived filtering already handled\n * by getProjects().\n */\nfunction buildFeedFrom(\n allSessions: ScannedSession[],\n projects: RegisteredProject[],\n liveSessions: Awaited<ReturnType<typeof fetchLiveSessions>>,\n allProjects: RegisteredProject[]\n): FeedLine[] {\n const deduped = buildDeduped(liveSessions, allSessions, projects, true);\n\n // Directory-resolution maps span ALL projects (archived included). A live\n // session's only source of a cwd is the registry — an archived project like\n // \"Chenarlier\" still has a real directory, so we must not filter it out here.\n const slugRoot = new Map<string, string>();\n const nameRoot = new Map<string, string>();\n for (const p of allProjects) {\n slugRoot.set(p.slug, p.root_path);\n nameRoot.set(normalizeName(p.display_name ?? p.slug).toLowerCase(), p.root_path);\n }\n\n const bestResumable = new Map<string, ScannedSession>();\n for (const s of allSessions) {\n if (!s.resumable || !s.encodedDir) continue;\n const prev = bestResumable.get(s.encodedDir);\n if (!prev || s.mtime > prev.mtime) bestResumable.set(s.encodedDir, s);\n }\n\n const lines: FeedLine[] = [];\n for (const e of deduped) {\n // Skip nameless rows (e.g. a fresh, unnamed live Claude tab whose name\n // normalizes to \"\"). They're not searchable and just add a blank line.\n if (!e.name.trim()) continue;\n\n // Resolve directory + resumable uuid\n let dir: string | undefined;\n let uuid: string | undefined;\n\n if (e.diskSession) {\n dir =\n e.diskSession.clcDirectory ??\n e.diskSession.registryRootPath ??\n e.diskSession.decodedPath;\n uuid = e.diskSession.resumable ? e.diskSession.uuid : undefined;\n } else if (e.status === \"project\") {\n dir = e.project;\n } else if (e.slug && slugRoot.has(e.slug)) {\n dir = slugRoot.get(e.slug);\n } else {\n // Live-only entry (e.g. a running tab) — recover its directory from the\n // registry by normalized name. This is what makes \"go there\" work for a\n // live session instead of falling back to a tab switch.\n dir = nameRoot.get(e.name.toLowerCase());\n }\n\n if (dir && !uuid) {\n const enc = encodeDir(dir);\n uuid = bestResumable.get(enc)?.uuid;\n }\n\n let record: PickRecord;\n if (dir) {\n // Carry the live id too (when present) so 'g' can switch to the running tab.\n record = { t: \"D\", name: e.name, dir, uuid, live: e.liveSessionId, slug: e.slug };\n } else if (e.liveSessionId) {\n record = { t: \"L\", name: e.name, live: e.liveSessionId, slug: e.slug };\n } else {\n continue; // not actionable\n }\n\n // Keywords for filtering (only when we have a real dir to scan)\n const kw = dir ? noteKeywords(dir) : \"\";\n\n const tag = tagFor(e.status);\n const name = chalk.white(pad(e.name, 22));\n const where = dir ? dim(shorten(dir, 40)) : chalk.green(\"(running in another tab)\");\n const age =\n e.status === \"live\"\n ? chalk.green(\"now\")\n : e.lastActivity > 0\n ? dim(fmtAge(e.lastActivity))\n : dim(\"—\");\n const kwDisp = kw ? dim(\" \" + kw) : \"\";\n const display = `${tag} ${name} ${pad(age, 5)} ${where}${kwDisp}`;\n const search = `${e.name} ${dir ?? \"\"} ${kw}`.toLowerCase();\n\n lines.push({ record, display, search });\n }\n return lines;\n}\n\n// ---------------------------------------------------------------------------\n// Modal selector — our own TUI (no fzf). Two modes:\n// command mode (default): ↑↓/j/k move · g go-to-tab · n new · c cd · s search · q quit\n// search mode (after s or /): type to filter · Enter/esc back to command\n// Action keys fire IMMEDIATELY on the highlighted row — no Enter dance, because\n// in command mode letters are commands, not filter input. Search is a distinct,\n// visible mode so plain letters can be both filter text and actions without\n// colliding.\n// ---------------------------------------------------------------------------\n\ntype PickAction = \"new\" | \"cd\" | \"switch\";\n\ninterface SelectorResult {\n record: PickRecord;\n action: PickAction;\n}\n\nconst stripAnsi = (s: string): string => s.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n\n/** Open a path in the OS file manager (Finder / Explorer / xdg-open). */\nfunction openInFileManager(dir: string): void {\n const cmd =\n process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"explorer\" : \"xdg-open\";\n try {\n spawn(cmd, [dir], { stdio: \"ignore\", detached: true }).unref();\n } catch {\n /* best-effort */\n }\n}\n\n/**\n * Truncate to `max` visible columns, preserving ANSI color codes (which have\n * zero width) and appending an ellipsis when content is cut. This is what keeps\n * rows from wrapping on a narrow window — wrapping would desync the one-row-per-\n * line layout and make the cursor appear to jump.\n */\nfunction truncVisible(s: string, max: number): string {\n if (max <= 1) return \"\";\n let out = \"\";\n let vis = 0;\n let i = 0;\n while (i < s.length) {\n if (s[i] === \"\\x1b\") {\n const m = s.slice(i).match(/^\\x1b\\[[0-9;]*m/);\n if (m) {\n out += m[0];\n i += m[0].length;\n continue;\n }\n }\n if (vis >= max - 1) return out + \"…\\x1b[0m\";\n out += s[i];\n vis++;\n i++;\n }\n return out + \"\\x1b[0m\";\n}\n\nfunction filterLines(lines: FeedLine[], query: string): FeedLine[] {\n const q = query.trim().toLowerCase();\n if (!q) return lines;\n const tokens = q.split(/\\s+/);\n return lines.filter((l) => tokens.every((t) => l.search.includes(t)));\n}\n\n/** Cached per-directory context (recent notes) for the detail panel. */\nconst ctxCache = new Map<string, string[]>();\nfunction contextFor(rec: PickRecord): string[] {\n if (!rec.dir) {\n return rec.live ? [chalk.green(\"live — running in another tab\")] : [dim(\"no directory on record\")];\n }\n const cached = ctxCache.get(rec.dir);\n if (cached) return cached;\n const lines: string[] = [];\n const notes = recentMarkdown(rec.dir, 4);\n if (notes.length) {\n lines.push(dim(\"recent notes:\"));\n for (const n of notes) lines.push(\" \" + dim(n.rel.slice(0, 60)) + \" \" + dim(fmtAge(n.mtime)));\n } else {\n lines.push(dim(\"(no notes here yet)\"));\n }\n ctxCache.set(rec.dir, lines);\n return lines;\n}\n\nasync function runSelector(\n lines: FeedLine[],\n onRemove?: (rec: PickRecord) => boolean\n): Promise<SelectorResult | null> {\n const stdin = process.stdin;\n const out = process.stdout;\n\n return new Promise<SelectorResult | null>((resolve) => {\n let pool = lines; // mutable: 'd' removes rows from here\n let query = \"\";\n let mode: \"command\" | \"search\" = \"command\";\n let filtered = pool;\n let cursor = 0;\n let confirm: FeedLine | null = null; // pending 'd' delete confirmation\n let flash = \"\"; // transient status line\n\n const wasRaw = stdin.isRaw ?? false;\n try {\n stdin.setRawMode?.(true);\n } catch {\n /* not a tty */\n }\n stdin.resume();\n out.write(\"\\x1b[?1049h\\x1b[?25l\"); // alt screen, hide cursor\n\n const teardown = () => {\n out.write(\"\\x1b[?25h\\x1b[?1049l\"); // show cursor, leave alt screen\n stdin.removeListener(\"data\", onData);\n try {\n stdin.setRawMode?.(wasRaw);\n } catch {\n /* ignore */\n }\n stdin.pause();\n };\n const finish = (val: SelectorResult | null) => {\n teardown();\n resolve(val);\n };\n\n const recompute = () => {\n filtered = filterLines(pool, query);\n if (cursor >= filtered.length) cursor = Math.max(0, filtered.length - 1);\n };\n\n const render = () => {\n const rows = out.rows && out.rows > 8 ? out.rows : 24;\n const cols = out.columns && out.columns > 20 ? out.columns : 100;\n const frame: string[] = [];\n\n // Header\n const title = chalk.bold(\" pai\") + dim(\" — find a project or session\");\n frame.push(title);\n if (mode === \"search\") {\n frame.push(\" \" + chalk.cyan(\"search > \") + query + chalk.inverse(\" \"));\n } else {\n frame.push(\n \" \" + dim(`${filtered.length}/${lines.length}`) +\n (query ? dim(` filter: ${query}`) : \"\")\n );\n }\n frame.push(dim(\" \" + \"─\".repeat(Math.min(cols - 4, 80))));\n\n // List window\n const ctxHeight = 6;\n const listHeight = Math.max(3, rows - 4 - ctxHeight - 2);\n let start = 0;\n if (cursor >= listHeight) start = cursor - listHeight + 1;\n const end = Math.min(filtered.length, start + listHeight);\n if (filtered.length === 0) {\n frame.push(\" \" + dim(\"no matches — press s to change the search, esc to quit\"));\n }\n for (let i = start; i < end; i++) {\n const row = filtered[i];\n if (i === cursor) {\n frame.push(chalk.inverse(\" \" + stripAnsi(row.display).slice(0, cols - 2).padEnd(cols - 2)));\n } else {\n frame.push(\" \" + row.display);\n }\n }\n\n // Detail panel for the highlighted row\n frame.push(dim(\" \" + \"─\".repeat(Math.min(cols - 4, 80))));\n const cur = filtered[cursor];\n if (cur) {\n frame.push(\" \" + chalk.bold.white(cur.record.name) + (cur.record.dir ? dim(\" \" + shorten(cur.record.dir, cols - 30)) : \"\"));\n for (const l of contextFor(cur.record).slice(0, ctxHeight - 2)) frame.push(l);\n }\n\n // Footer (keys / confirm / flash)\n frame.push(dim(\" \" + \"─\".repeat(Math.min(cols - 4, 80))));\n if (confirm) {\n frame.push(\n \" \" + chalk.yellow(`Remove \"${confirm.record.name}\" from PAI's list?`) +\n dim(\" files stay on disk \") + chalk.cyan(\"y\") + dim(\"/\") + chalk.cyan(\"N\")\n );\n } else if (mode === \"search\") {\n frame.push(\" \" + dim(\"type to filter · \") + chalk.cyan(\"Enter\") + dim(\" done · \") + chalk.cyan(\"esc\") + dim(\" cancel\"));\n } else {\n const parts: string[] = [];\n if (cur?.record.live) parts.push(chalk.cyan(\"g\") + dim(\" go to tab\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"n\") + dim(\" new\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"c\") + dim(\" cd\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"f\") + dim(\" finder\"));\n if (cur?.record.slug) parts.push(chalk.cyan(\"d\") + dim(\" remove\"));\n parts.push(chalk.cyan(\"s\") + dim(\" search\"));\n parts.push(chalk.cyan(\"↑↓\") + dim(\" move\"));\n parts.push(chalk.cyan(\"q\") + dim(\" quit\"));\n frame.push(\" \" + parts.join(dim(\" · \")));\n if (flash) frame.push(\" \" + dim(flash));\n }\n\n // Paint: home, each line truncated to width (no wrapping) + cleared to\n // EOL, then clear everything below.\n const painted = frame.map((l) => truncVisible(l, cols)).join(\"\\x1b[K\\n\");\n out.write(\"\\x1b[H\" + painted + \"\\x1b[K\\x1b[J\");\n };\n\n const move = (delta: number) => {\n if (filtered.length === 0) return;\n cursor = (cursor + delta + filtered.length) % filtered.length;\n };\n\n const onData = (buf: Buffer) => {\n const s = buf.toString(\"utf8\");\n const cur = filtered[cursor];\n\n // Universal: Ctrl-C quits.\n if (s === \"\\x03\") return finish(null);\n\n // Pending delete confirmation captures the next key.\n if (confirm) {\n const target = confirm;\n confirm = null;\n if (s === \"y\" || s === \"Y\") {\n const okRemoved = onRemove ? onRemove(target.record) : false;\n if (okRemoved) {\n pool = pool.filter((l) => l !== target);\n ctxCache.delete(target.record.dir ?? \"\");\n recompute();\n flash = `Removed \"${target.record.name}\" from PAI's list (files untouched).`;\n } else {\n flash = `Could not remove \"${target.record.name}\".`;\n }\n } else {\n flash = \"\";\n }\n return render();\n }\n\n // Arrows (work in both modes).\n if (s === \"\\x1b[A\") { move(-1); return render(); }\n if (s === \"\\x1b[B\") { move(1); return render(); }\n if (s === \"\\x1b[5~\") { move(-5); return render(); }\n if (s === \"\\x1b[6~\") { move(5); return render(); }\n\n if (mode === \"search\") {\n if (s === \"\\r\" || s === \"\\n\") { mode = \"command\"; return render(); }\n if (s === \"\\x1b\") { mode = \"command\"; return render(); } // esc → back to command, keep filter\n if (s === \"\\x7f\" || s === \"\\b\") { query = query.slice(0, -1); recompute(); return render(); }\n // Printable → append to query.\n if (s.length === 1 && s >= \" \") { query += s; cursor = 0; recompute(); return render(); }\n return; // ignore other escapes\n }\n\n // command mode\n if (s === \"q\" || s === \"\\x1b\") return finish(null);\n if (s === \"s\" || s === \"/\") { flash = \"\"; mode = \"search\"; return render(); }\n if (s === \"j\") { move(1); return render(); }\n if (s === \"k\") { move(-1); return render(); }\n if (!cur) return;\n if (s === \"g\" && cur.record.live) return finish({ record: cur.record, action: \"switch\" });\n if (s === \"n\" && cur.record.dir) return finish({ record: cur.record, action: \"new\" });\n if (s === \"c\" && cur.record.dir) return finish({ record: cur.record, action: \"cd\" });\n // f → open in the file manager, but stay in the picker.\n if (s === \"f\" && cur.record.dir) { openInFileManager(cur.record.dir); return; }\n // d → ask to remove this row from PAI's list (only registered projects).\n if (s === \"d\") {\n if (cur.record.slug) { confirm = cur; flash = \"\"; }\n else flash = \"Only registered projects can be removed from the list.\";\n return render();\n }\n if (s === \"\\r\" || s === \"\\n\") {\n // Enter = smart default: live → go to tab, else → new session.\n if (cur.record.live) return finish({ record: cur.record, action: \"switch\" });\n if (cur.record.dir) return finish({ record: cur.record, action: \"new\" });\n }\n };\n\n stdin.on(\"data\", onData);\n render();\n });\n}\n\n// ---------------------------------------------------------------------------\n// Note discovery — recent markdown for the selector's detail panel.\n// ---------------------------------------------------------------------------\n\ninterface NoteRef {\n rel: string;\n full: string;\n mtime: number;\n}\n\nfunction recentMarkdown(root: string, limit: number): NoteRef[] {\n const found: NoteRef[] = [];\n const walk = (dir: string, depth: number) => {\n if (depth > 2 || found.length >= 200) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (name.startsWith(\".\") || KW_SKIP.has(name)) continue;\n const full = join(dir, name);\n let st;\n try {\n st = statSync(full);\n } catch {\n continue;\n }\n if (st.isDirectory()) walk(full, depth + 1);\n else if (name.toLowerCase().endsWith(\".md\")) {\n found.push({ rel: full.slice(root.length + 1), full, mtime: st.mtimeMs });\n }\n }\n };\n walk(root, 0);\n found.sort((a, b) => b.mtime - a.mtime);\n return found.slice(0, limit);\n}\n\n// ---------------------------------------------------------------------------\n// Main command (`pai` with no args, interactive)\n// ---------------------------------------------------------------------------\n\nexport interface PickOpts {\n all?: boolean;\n dryRun?: boolean;\n}\n\nexport async function cmdPick(db: Database, opts: PickOpts = {}): Promise<void> {\n const interactive = !!process.stdout.isTTY && !!process.stdin.isTTY;\n\n // Fallback to the static listing when we're not on an interactive terminal\n // (e.g. piped output). The modal selector needs a real tty.\n if (!interactive) {\n const allSessions = scanSessions(db, { limit: 500, filter: \"named\" });\n const projects = getProjects(db, opts.all ?? false);\n let live: Awaited<ReturnType<typeof fetchLiveSessions>> = [];\n try {\n live = await fetchLiveSessions();\n } catch {\n /* AIBroker not running */\n }\n const deduped = buildDeduped(live, allSessions, projects, opts.all ?? false);\n renderDedupedSessions(deduped, opts.all ? undefined : 20);\n return;\n }\n\n // Build the feed (with live sessions merged in)\n const allSessions = scanSessions(db, { limit: 500, filter: \"named\" });\n const projects = getProjects(db, opts.all ?? false);\n let live: Awaited<ReturnType<typeof fetchLiveSessions>> = [];\n try {\n live = await fetchLiveSessions();\n } catch {\n /* AIBroker not running — disk + registry still populate the picker */\n }\n\n const allProjects = getProjects(db, true);\n const lines = buildFeedFrom(allSessions, projects, live, allProjects);\n if (lines.length === 0) {\n process.stderr.write(dim(\" No projects or sessions found.\\n\"));\n return;\n }\n\n // 'd' removes a row from PAI's list: archive the project (reversible, FK-safe,\n // files stay on disk). Returns false if the row isn't a registered project.\n const onRemove = (rec: PickRecord): boolean => {\n if (!rec.slug) return false;\n try {\n const ts = now();\n const res = db\n .prepare(\n \"UPDATE projects SET status = 'archived', archived_at = ?, updated_at = ? WHERE slug = ? AND status = 'active'\"\n )\n .run(ts, ts, rec.slug);\n return res.changes > 0;\n } catch {\n return false;\n }\n };\n\n // Modal selector — navigate, then g/n/c act immediately on the highlighted row.\n const sel = await runSelector(lines, onRemove);\n if (!sel) return; // cancelled\n const { record, action } = sel;\n\n // switch → reveal the running iTerm tab (the screenshot mechanism).\n if (action === \"switch\") {\n if (record.live) {\n const r = revealItermSession(record.live);\n if (!r.ok) {\n process.stderr.write(chalk.yellow(` Could not switch: ${r.error ?? \"unknown error\"}\\n`));\n }\n }\n return;\n }\n\n if (!record.dir) {\n process.stderr.write(dim(` No directory on record for \"${record.name}\".\\n`));\n return;\n }\n\n // cd → hand the directory to the `pai()` shell wrapper via PAI_PICK_OUT; a\n // child process can't change the parent shell's cwd itself. Without the\n // wrapper, just print the path so the user can cd manually.\n if (action === \"cd\") {\n const sink = process.env.PAI_PICK_OUT;\n if (sink) {\n try {\n writeFileSync(sink, record.dir, \"utf8\");\n return;\n } catch {\n /* fall through to printing */\n }\n }\n printExitDir(record.dir);\n return;\n }\n\n // new → start a fresh session here, in the current terminal.\n launchInDir(record.dir, record.name, { forceFresh: true, dryRun: opts.dryRun });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pick-gne3kOJG.mjs","names":["dim","bold","getProject","dim","bold","cmdList","getProject","bold","dim","cmdHealth","bold","dim","dim","cmdMigrate","dim","slugify","cmdStats","bold","dim","dim","bold","dim","bold","tierColor","dim","bold","bold","dim","CONFIG_FILE","cmdInstall","dim","cmdStatus","bold","HOME","cmdStatus","bold","dim","HOME","REGISTRY_DB","CONFIG_FILE","BACKUPS_DIR","DOCKER_CONTAINER","PG_DATABASE","PG_USER","dim","bold","dim","bold","line","CONFIG_FILE","getTemplatesDir","getTemplatesDir","getTemplatesDir","getTemplatesDir","useSymlinks","CONFIG_FILE","normalize","line","CONFIG_FILE","findNotesDir","findNotesDir","CONFIG_FILE","dim","cmdStatus","bold","dim","bold","dim","bold","dim","bold","dim","bold","dim","bold","dim","bold","ipcCall","fmtTs","dim","trunc","cmdList","bold","cmdStats","fmtTs","dim","statusColor","bold","dim","run","makeClient","bold","dim","STATE_FILE","loadState","saveState","CONFIG_FILE","bold","dim","getPool","dim","shorten","bold","cmdQuery","cmdList","dim","bold","dim","probeResume","dim","dim","dim","bold","dim","dim"],"sources":["../src/session/promote.ts","../src/cli/commands/project/helpers.ts","../src/cli/commands/project/commands.ts","../src/cli/commands/project/session-config.ts","../src/cli/commands/project/health.ts","../src/cli/commands/project/projects-index.ts","../src/cli/commands/registry/migrate.ts","../src/cli/commands/registry/index.ts","../src/cli/commands/memory/embed.ts","../src/cli/commands/memory/index-cmd.ts","../src/cli/commands/memory/search.ts","../src/cli/commands/memory/stats.ts","../src/cli/commands/memory/index.ts","../src/config/claude-json.ts","../src/cli/commands/mcp.ts","../src/cli/commands/daemon.ts","../src/cli/commands/backup.ts","../src/cli/commands/restore.ts","../src/config/json-store.ts","../src/cli/commands/setup/utils.ts","../src/cli/commands/setup/steps/01-welcome.ts","../src/cli/commands/setup/steps/02-storage.ts","../src/cli/commands/setup/steps/03-embedding.ts","../src/cli/commands/setup/steps/04-claude-md.ts","../src/cli/commands/setup/steps/05-pai-skill.ts","../src/cli/commands/setup/steps/06-steering-rules.ts","../src/cli/commands/setup/steps/07-skill-stubs.ts","../src/cli/commands/setup/steps/08-hooks.ts","../src/cli/commands/setup/steps/09-ts-hooks.ts","../src/cli/commands/settings-manager.ts","../src/cli/commands/setup/steps/10-settings.ts","../src/cli/commands/setup/steps/11-daemon.ts","../src/cli/commands/setup/steps/12-mcp.ts","../src/cli/commands/setup/steps/13-directories.ts","../src/cli/commands/setup/steps/14-initial-index.ts","../src/cli/commands/setup/steps/15-verify.ts","../src/tasks/resolver.ts","../src/tasks/providers/todoist.ts","../src/cli/commands/setup/steps/16-task-bus.ts","../src/cli/commands/setup/index.ts","../src/obsidian/sync/symlinks.ts","../src/obsidian/sync/generate.ts","../src/obsidian/sync/walk.ts","../src/obsidian/sync/master.ts","../src/obsidian/status.ts","../src/cli/commands/obsidian.ts","../src/cli/commands/zettel/utils.ts","../src/cli/commands/zettel/explore.ts","../src/cli/commands/zettel/health.ts","../src/cli/commands/zettel/surprise.ts","../src/cli/commands/zettel/suggest.ts","../src/cli/commands/zettel/converse.ts","../src/cli/commands/zettel/themes.ts","../src/cli/commands/zettel/index.ts","../src/cli/commands/observation.ts","../src/cli/commands/skill.ts","../src/cli/commands/update.ts","../src/cli/commands/notify.ts","../src/tasks/dispatch.ts","../src/tasks/transport/aibroker.ts","../src/tasks/scheduler.ts","../src/tasks/poller.ts","../src/tasks/schedule-install.ts","../src/cli/commands/task.ts","../src/cli/commands/topic.ts","../src/memory/kg-backfill.ts","../src/cli/commands/kg.ts","../src/cli/commands/db.ts","../src/cli/commands/help.ts","../src/cli/commands/session/pause.ts","../src/cli/commands/session/end.ts","../src/cli/commands/session/goto.ts","../src/cli/commands/session/pause-all.ts","../src/cli/commands/session/clear-names.ts","../src/cli/commands/session/helpers.ts","../src/cli/commands/session/commands.ts","../src/cli/commands/find.ts","../src/cli/lib/launch.ts","../src/cli/commands/pick.ts"],"sourcesContent":["/**\n * pai project promote --from-session <path> --to <new-project-path> [--name \"Name\"]\n *\n * Promotes a session note into a new standalone project:\n * 1. Validates inputs\n * 2. Scaffolds the new project directory\n * 3. Copies the session note and creates a MEMORY.md\n * 4. Registers the new project in the PAI registry\n * 5. Optionally backlinks from the source project's TODO.md\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n mkdirSync,\n copyFileSync,\n readFileSync,\n appendFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join, basename, dirname, resolve } from \"node:path\";\nimport {\n ok,\n warn,\n err,\n dim,\n bold,\n slugify,\n encodeDir,\n resolvePath,\n scaffoldProjectDirs,\n now,\n} from \"../cli/utils.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface PromoteOptions {\n fromSession: string;\n to: string;\n name?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Derive a human-readable project name from a session note filename.\n * \"0042 - 2026-02-24 - Dark Mode Implementation.md\" → \"Dark Mode Implementation\"\n */\nfunction deriveNameFromFilename(filename: string): string {\n const base = basename(filename, \".md\");\n // Strip leading \"NNNN - YYYY-MM-DD - \" prefix if present\n const match = base.match(/^\\d+ - \\d{4}-\\d{2}-\\d{2} - (.+)$/);\n return match ? match[1] : base;\n}\n\n/**\n * Extract the first N H2 (##) section headings from markdown content.\n */\nfunction extractH2Headings(content: string, limit = 2): string[] {\n const headings: string[] = [];\n for (const m of content.matchAll(/^## .+$/gm)) {\n headings.push(m[0]);\n if (headings.length >= limit) break;\n }\n return headings;\n}\n\n/**\n * Find the source project root from the session note path.\n * Convention: session note lives at {project-root}/Notes/{filename}\n * So: dirname(dirname(sessionPath)) = project root.\n */\nfunction findSourceProjectRoot(sessionPath: string): string {\n const notesDir = dirname(resolve(sessionPath));\n return dirname(notesDir);\n}\n\n// ---------------------------------------------------------------------------\n// Main implementation\n// ---------------------------------------------------------------------------\n\nexport function cmdPromote(db: Database, opts: PromoteOptions): void {\n const sessionPath = resolvePath(opts.fromSession);\n const targetPath = resolvePath(opts.to);\n\n // ---- Validate inputs ----\n\n if (!existsSync(sessionPath)) {\n console.error(err(`Session note not found: ${sessionPath}`));\n process.exit(1);\n }\n if (!sessionPath.endsWith(\".md\")) {\n console.error(err(`--from-session must be a markdown (.md) file: ${sessionPath}`));\n process.exit(1);\n }\n if (existsSync(targetPath)) {\n console.error(err(`Target path already exists: ${targetPath}`));\n process.exit(1);\n }\n\n // ---- Derive project name and slug ----\n\n const displayName = opts.name ?? deriveNameFromFilename(sessionPath);\n const slug = slugify(displayName);\n\n if (!slug) {\n console.error(err(`Could not derive a valid slug from name: \"${displayName}\"`));\n process.exit(1);\n }\n\n // ---- Check registry for conflicts ----\n\n const encodedDir = encodeDir(targetPath);\n const existing = db\n .prepare(\"SELECT id FROM projects WHERE slug = ? OR root_path = ? OR encoded_dir = ?\")\n .get(slug, targetPath, encodedDir);\n if (existing) {\n console.error(\n err(`A project with slug \"${slug}\" or path \"${targetPath}\" is already registered.`)\n );\n process.exit(1);\n }\n\n // ---- Scaffold new project ----\n\n scaffoldProjectDirs(targetPath);\n\n // ---- Copy session note ----\n\n const today = new Date().toISOString().slice(0, 10);\n const sourceBasename = basename(sessionPath);\n const sourceProjectRoot = findSourceProjectRoot(sessionPath);\n const sourceProjectName = basename(sourceProjectRoot);\n\n const destNoteName = `0001 - ${today} - Promoted from ${sourceProjectName}.md`;\n const destNotePath = join(targetPath, \"Notes\", destNoteName);\n\n copyFileSync(sessionPath, destNotePath);\n\n // ---- Create MEMORY.md ----\n\n const sessionContent = readFileSync(sessionPath, \"utf-8\");\n const headings = extractH2Headings(sessionContent);\n\n const memoryContent = [\n `# Project Memory`,\n ``,\n `## Origin`,\n ``,\n `- **Promoted from:** ${sessionPath}`,\n `- **Source project:** ${sourceProjectRoot}`,\n `- **Date of promotion:** ${today}`,\n `- **Original session note:** ${sourceBasename}`,\n ``,\n `## Initial Context`,\n ``,\n headings.length > 0\n ? `Key topics from the source session:\\n\\n${headings.map((h) => `- ${h.replace(/^## /, \"\")}`).join(\"\\n\")}`\n : `(Extracted from session note — see ${destNoteName})`,\n ``,\n ].join(\"\\n\");\n\n writeFileSync(join(targetPath, \"Notes\", \"MEMORY.md\"), memoryContent, \"utf-8\");\n\n // ---- Register in PAI registry ----\n\n const ts = now();\n db.prepare(\n `INSERT INTO projects\n (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)\n VALUES (?, ?, ?, ?, 'local', 'active', ?, ?)`\n ).run(slug, displayName, targetPath, encodedDir, ts, ts);\n\n // ---- Backlink in source TODO.md ----\n\n const sourceTodoPath = join(sourceProjectRoot, \"Notes\", \"TODO.md\");\n if (existsSync(sourceTodoPath)) {\n const backlink = `\\n- Promoted to project: [${slug}](${targetPath})\\n`;\n appendFileSync(sourceTodoPath, backlink, \"utf-8\");\n console.log(dim(` Backlink added to: ${sourceTodoPath}`));\n }\n\n // ---- Success output ----\n\n console.log();\n console.log(ok(`Project promoted: ${bold(slug)}`));\n console.log(dim(` Display name: ${displayName}`));\n console.log(dim(` Path: ${targetPath}`));\n console.log(dim(` Slug: ${slug}`));\n console.log(dim(` Session note: ${destNoteName}`));\n console.log(dim(` Memory: Notes/MEMORY.md created`));\n console.log();\n console.log(dim(` Next: cd ${targetPath} && pai session list ${slug}`));\n}\n","/**\n * Shared database helper functions for project sub-commands.\n * All functions accept a Database instance and are pure query wrappers.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { ProjectRow } from \"./types.js\";\nimport { err } from \"../../utils.js\";\n\nexport function getProject(db: Database, slug: string): ProjectRow | undefined {\n const direct = db\n .prepare(\"SELECT * FROM projects WHERE slug = ?\")\n .get(slug) as ProjectRow | undefined;\n if (direct) return direct;\n\n const alias = db\n .prepare(\n `SELECT p.* FROM projects p\n JOIN aliases a ON a.project_id = p.id\n WHERE a.alias = ?`\n )\n .get(slug) as ProjectRow | undefined;\n return alias;\n}\n\nexport function requireProject(db: Database, slug: string): ProjectRow {\n const project = getProject(db, slug);\n if (!project) {\n console.error(err(`Project not found: ${slug}`));\n process.exit(1);\n }\n return project;\n}\n\n/**\n * Resolve an identifier that may be a list index number or a slug.\n */\nexport function resolveIdentifier(db: Database, identifier: string): ProjectRow | undefined {\n const num = parseInt(identifier, 10);\n if (!isNaN(num) && num > 0 && String(num) === identifier) {\n const rows = db.prepare(\n \"SELECT * FROM projects ORDER BY status ASC, updated_at DESC\"\n ).all() as ProjectRow[];\n if (num <= rows.length) return rows[num - 1];\n }\n return getProject(db, identifier);\n}\n\nexport function getProjectTags(db: Database, projectId: number): string[] {\n const rows = db\n .prepare(\n `SELECT t.name FROM tags t\n JOIN project_tags pt ON pt.tag_id = t.id\n WHERE pt.project_id = ?\n ORDER BY t.name`\n )\n .all(projectId) as { name: string }[];\n return rows.map((r) => r.name);\n}\n\nexport function getProjectAliases(db: Database, projectId: number): string[] {\n const rows = db\n .prepare(\"SELECT alias FROM aliases WHERE project_id = ? ORDER BY alias\")\n .all(projectId) as { alias: string }[];\n return rows.map((r) => r.alias);\n}\n\nexport function getSessionCount(db: Database, projectId: number): number {\n const row = db\n .prepare(\"SELECT COUNT(*) AS cnt FROM sessions WHERE project_id = ?\")\n .get(projectId) as { cnt: number };\n return row.cnt;\n}\n\nexport function getLastSessionDate(db: Database, projectId: number): number | null {\n const row = db\n .prepare(\n `SELECT created_at FROM sessions WHERE project_id = ?\n ORDER BY created_at DESC LIMIT 1`\n )\n .get(projectId) as { created_at: number } | undefined;\n return row ? row.created_at : null;\n}\n\nexport function upsertTag(db: Database, tagName: string): number {\n db.prepare(\"INSERT OR IGNORE INTO tags (name) VALUES (?)\").run(tagName);\n const row = db.prepare(\"SELECT id FROM tags WHERE name = ?\").get(tagName) as { id: number };\n return row.id;\n}\n","/**\n * Core project CRUD commands: add, list, info, archive, unarchive, move, tag,\n * alias, edit, detect, consolidate, go, rebind — plus private helpers levenshtein,\n * containsIgnoreCase, findProjectNotesDirs, and findMovedPath.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n readdirSync,\n statSync,\n mkdirSync,\n renameSync,\n} from \"node:fs\";\nimport { join, basename, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport {\n ok,\n warn,\n err,\n dim,\n bold,\n header,\n slugFromPath,\n encodeDir,\n resolvePath,\n scaffoldProjectDirs,\n renderTable,\n shortenPath,\n fmtDate,\n now,\n} from \"../../utils.js\";\nimport {\n detectProject,\n formatDetection,\n formatDetectionJson,\n} from \"../detect.js\";\nimport { ensurePaiMarker } from \"../../../registry/pai-marker.js\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\nimport {\n requireProject,\n resolveIdentifier,\n getProject,\n getProjectTags,\n getProjectAliases,\n getSessionCount,\n getLastSessionDate,\n upsertTag,\n} from \"./helpers.js\";\nimport { loadScanConfig, resolveHome } from \"../registry/scan.js\";\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp: number[][] = Array.from({ length: m + 1 }, (_, i) =>\n Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))\n );\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n dp[i][j] =\n a[i - 1] === b[j - 1]\n ? dp[i - 1][j - 1]\n : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);\n }\n }\n return dp[m][n];\n}\n\nfunction containsIgnoreCase(haystack: string, needle: string): boolean {\n return haystack.toLowerCase().includes(needle.toLowerCase());\n}\n\nfunction findProjectNotesDirs(project: ProjectRow): {\n encodedDir: string;\n fullPath: string;\n notesPath: string;\n noteCount: number;\n}[] {\n const claudeProjects = join(homedir(), \".claude\", \"projects\");\n if (!existsSync(claudeProjects)) return [];\n\n const results: {\n encodedDir: string;\n fullPath: string;\n notesPath: string;\n noteCount: number;\n }[] = [];\n const rootEncoded = encodeDir(project.root_path);\n\n try {\n for (const entry of readdirSync(claudeProjects)) {\n const full = join(claudeProjects, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n\n if (entry !== rootEncoded && !entry.startsWith(rootEncoded)) continue;\n\n const notesPath = join(full, \"Notes\");\n if (!existsSync(notesPath)) continue;\n\n let noteCount = 0;\n try {\n noteCount = readdirSync(notesPath).filter(\n (f) => f.endsWith(\".md\") || f.endsWith(\".txt\")\n ).length;\n } catch {\n // count stays 0\n }\n\n results.push({ encodedDir: entry, fullPath: full, notesPath, noteCount });\n }\n } catch {\n // Unreadable — ignore\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Moved-project detection\n// ---------------------------------------------------------------------------\n\n// Directories to skip during filesystem walk\nconst SKIP_DIRS = new Set([\n \".git\", \"node_modules\", \".next\", \".nuxt\", \"dist\", \"build\", \"coverage\",\n \".cache\", \"__pycache__\", \"vendor\", \".svn\", \".hg\", \"venv\", \".venv\",\n \"target\", // Rust/Java build dir\n \"Notes\", // PAI session notes — not project roots\n]);\n\n/**\n * Walk `dir` up to `maxDepth` levels deep, collecting all subdirectory paths\n * whose basename matches `targetBasename` (case-insensitive on macOS/case-sensitive\n * on Linux, whichever is appropriate — we use exact case matching to keep it\n * correct and fast).\n *\n * Skips hidden directories and known build/tool dirs.\n * Stops walking a branch if the deadline is exceeded (returns partial results).\n */\nfunction walkForBasename(\n dir: string,\n targetBasename: string,\n maxDepth: number,\n deadline: number,\n results: string[]\n): void {\n if (Date.now() > deadline) return;\n if (maxDepth <= 0) return;\n\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (Date.now() > deadline) return;\n if (entry.startsWith(\".\")) continue;\n if (SKIP_DIRS.has(entry)) continue;\n\n const full = join(dir, entry);\n let isDir = false;\n try {\n isDir = statSync(full).isDirectory();\n } catch {\n continue;\n }\n if (!isDir) continue;\n\n // Case-insensitive comparison for the target basename (handles macOS APFS)\n if (entry.toLowerCase() === targetBasename.toLowerCase()) {\n results.push(full);\n // Don't recurse into the match itself — we want the directory, not children\n continue;\n }\n\n walkForBasename(full, targetBasename, maxDepth - 1, deadline, results);\n }\n}\n\n/**\n * Try to find where a project has moved.\n *\n * Searches scan_dirs from config (plus a set of common fallback dirs) for a\n * directory whose basename matches the basename of the registered root_path.\n *\n * Returns:\n * { found: string } — exactly one match found\n * { ambiguous: string[] } — multiple matches found\n * { found: undefined } — no match found (or timed out)\n */\nexport function findMovedPath(registeredPath: string): { found?: string; ambiguous?: string[] } {\n const targetBasename = basename(registeredPath);\n const config = loadScanConfig();\n const deadline = Date.now() + 5_000; // 5-second max\n\n // Build the list of top-level directories to search\n const home = homedir();\n const configDirs = (config.scan_dirs ?? []).map((d) => resolveHome(d));\n\n // Walk UP the registered path to find the deepest existing ancestor.\n // This is the best search root when a project has moved within the same tree.\n // e.g. ~/Cloud/Ablage/2025/Project → ~/Cloud/Ablage/2026/Project\n // Deepest existing ancestor: ~/Cloud/Ablage (if 2025 is gone but Ablage exists)\n function deepestExistingAncestor(p: string): string | null {\n let current = p;\n while (true) {\n const parent = join(current, \"..\");\n if (parent === current) return null; // filesystem root\n if (parent === home) return null; // don't use home itself as search root\n if (existsSync(parent)) return parent;\n current = parent;\n }\n }\n\n const ancestorRoot = deepestExistingAncestor(registeredPath);\n\n const fallbackDirs = [\n join(home, \"dev\"),\n join(home, \"Desktop\"),\n join(home, \"Projects\"),\n join(home, \"Documents\"),\n // Common cloud-storage root folders (Synology Drive, iCloud, etc.)\n join(home, \"Cloud\"),\n join(home, \"Daten\", \"Cloud\"),\n join(home, \"Daten\"),\n join(home, \"Library\", \"Mobile Documents\", \"com~apple~CloudDocs\"), // iCloud Drive\n // The deepest ancestor of the registered path that still exists on disk\n ...(ancestorRoot ? [ancestorRoot] : []),\n ];\n\n // Deduplicate and filter to existing directories\n const searchRoots = [...new Set([...configDirs, ...fallbackDirs])]\n .map((d) => {\n try { return resolve(d); } catch { return null; }\n })\n .filter((d): d is string => d !== null && existsSync(d));\n\n const results: string[] = [];\n for (const root of searchRoots) {\n if (Date.now() > deadline) break;\n walkForBasename(root, targetBasename, 6, deadline, results);\n }\n\n // Deduplicate results (different roots might overlap)\n const unique = [...new Set(results)];\n\n if (unique.length === 0) return {};\n if (unique.length === 1) return { found: unique[0] };\n return { ambiguous: unique };\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nexport function cmdAdd(\n db: Database,\n rawPath: string,\n opts: { slug?: string; type?: string; displayName?: string }\n): void {\n const rootPath = resolvePath(rawPath);\n const slug = opts.slug ?? slugFromPath(rootPath);\n const encodedDir = encodeDir(rootPath);\n const displayName = opts.displayName ?? slug;\n const type = opts.type ?? \"local\";\n\n const validTypes = [\"local\", \"central\", \"obsidian-linked\", \"external\"];\n if (!validTypes.includes(type)) {\n console.error(err(`Invalid type \"${type}\". Valid: ${validTypes.join(\", \")}`));\n process.exit(1);\n }\n\n const existing = db\n .prepare(\"SELECT id FROM projects WHERE slug = ? OR root_path = ?\")\n .get(slug, rootPath);\n if (existing) {\n console.error(\n err(`Project already registered (slug: ${slug} or path: ${rootPath})`)\n );\n process.exit(1);\n }\n\n const dirName = basename(rootPath).toLowerCase();\n const similar = db\n .prepare(\n `SELECT slug, root_path FROM projects WHERE status = 'active' AND slug != ?`\n )\n .all(slug) as { slug: string; root_path: string }[];\n const matches = similar.filter(\n (s) =>\n basename(s.root_path).toLowerCase() === dirName ||\n s.slug.replace(/-\\d+$/, \"\") === slug.replace(/-\\d+$/, \"\")\n );\n if (matches.length > 0) {\n console.log(warn(`Similar project(s) already registered:`));\n for (const m of matches) {\n console.log(dim(` ${bold(m.slug)} ${shortenPath(m.root_path, 50)}`));\n }\n console.log(\n dim(\n ` Consider: pai project alias ${matches[0].slug} <name> (to link them)`\n )\n );\n console.log(\n dim(` Or: pai project archive ${slug} (if this is a duplicate)`)\n );\n console.log();\n }\n\n const ts = now();\n db.prepare(\n `INSERT INTO projects\n (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, 'active', ?, ?)`\n ).run(slug, displayName, rootPath, encodedDir, type, ts, ts);\n\n scaffoldProjectDirs(rootPath);\n\n try {\n ensurePaiMarker(rootPath, slug, displayName);\n } catch {\n // Non-fatal — warn but don't fail the add command.\n }\n\n console.log(ok(`Project added: ${bold(slug)}`));\n console.log(dim(` Path: ${rootPath}`));\n console.log(dim(` Encoded dir: ${encodedDir}`));\n console.log(dim(` Type: ${type}`));\n}\n\nexport function cmdList(\n db: Database,\n opts: { status?: string; tag?: string; type?: string; all?: boolean }\n): void {\n let query = `\n SELECT p.*,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n FROM projects p\n `;\n const params: unknown[] = [];\n const where: string[] = [];\n\n // Default: active only, unless --all or explicit --status given\n if (opts.status) {\n where.push(\"p.status = ?\");\n params.push(opts.status);\n } else if (!opts.all) {\n where.push(\"p.status = 'active'\");\n }\n if (opts.type) {\n where.push(\"p.type = ?\");\n params.push(opts.type);\n }\n if (opts.tag) {\n where.push(`p.id IN (\n SELECT pt.project_id FROM project_tags pt\n JOIN tags t ON t.id = pt.tag_id WHERE t.name = ?\n )`);\n params.push(opts.tag);\n }\n\n if (where.length) query += \" WHERE \" + where.join(\" AND \");\n query += \" ORDER BY p.status ASC, p.updated_at DESC\";\n\n const rows = db.prepare(query).all(...params) as (ProjectRow & {\n session_count: number;\n last_active: number | null;\n })[];\n\n if (!rows.length) {\n console.log(warn(\"No projects found.\"));\n return;\n }\n\n const tableRows = rows.map((r, i) => [\n dim(String(i + 1)),\n bold(r.display_name ?? r.slug),\n dim(r.slug),\n dim(shortenPath(r.root_path, 44)),\n r.status === \"active\" ? chalk.green(r.status) : chalk.yellow(r.status),\n dim(r.type),\n String(r.session_count),\n fmtDate(r.last_active),\n ]);\n\n console.log(\n renderTable(\n [\"#\", \"Name\", \"Slug\", \"Path\", \"Status\", \"Type\", \"Sessions\", \"Last Active\"],\n tableRows\n )\n );\n console.log();\n\n // When showing active-only, inform the user about hidden archived projects\n const totalRow = db.prepare(\"SELECT COUNT(*) AS cnt FROM projects\").get() as { cnt: number };\n const hiddenCount = totalRow.cnt - rows.length;\n if (!opts.all && !opts.status && hiddenCount > 0) {\n console.log(dim(` ${rows.length} active project(s) (${hiddenCount} archived — use --all to show)`));\n } else {\n console.log(dim(` ${rows.length} project(s)`));\n }\n}\n\nexport function cmdInfo(db: Database, identifier: string): void {\n const project =\n resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\n const tags = getProjectTags(db, project.id);\n const aliases = getProjectAliases(db, project.id);\n const sessionCount = getSessionCount(db, project.id);\n const lastSession = getLastSessionDate(db, project.id);\n\n const recentSessions = db\n .prepare(\n `SELECT * FROM sessions WHERE project_id = ?\n ORDER BY created_at DESC LIMIT 5`\n )\n .all(project.id) as SessionRow[];\n\n console.log();\n console.log(header(` ${project.display_name}`));\n console.log();\n console.log(` ${bold(\"Slug:\")} ${project.slug}`);\n console.log(` ${bold(\"Path:\")} ${project.root_path}`);\n console.log(` ${bold(\"Encoded dir:\")} ${project.encoded_dir}`);\n console.log(` ${bold(\"Type:\")} ${project.type}`);\n console.log(\n ` ${bold(\"Status:\")} ${\n project.status === \"active\"\n ? chalk.green(project.status)\n : chalk.yellow(project.status)\n }`\n );\n console.log(\n ` ${bold(\"Tags:\")} ${\n tags.length ? tags.map((t) => chalk.cyan(t)).join(\", \") : dim(\"none\")\n }`\n );\n console.log(\n ` ${bold(\"Aliases:\")} ${aliases.length ? aliases.join(\", \") : dim(\"none\")}`\n );\n console.log(` ${bold(\"Sessions:\")} ${sessionCount}`);\n console.log(` ${bold(\"Last active:\")} ${fmtDate(lastSession)}`);\n console.log(` ${bold(\"Created:\")} ${fmtDate(project.created_at)}`);\n if (project.archived_at) {\n console.log(` ${bold(\"Archived:\")} ${fmtDate(project.archived_at)}`);\n }\n\n if (recentSessions.length) {\n console.log();\n console.log(` ${bold(\"Recent sessions:\")}`);\n const sessionRows = recentSessions.map((s) => [\n dim(`#${s.number}`),\n s.date,\n s.title.length > 50 ? s.title.slice(0, 47) + \"...\" : s.title,\n s.status === \"completed\"\n ? chalk.green(s.status)\n : chalk.yellow(s.status),\n ]);\n console.log(\n renderTable([\"#\", \"Date\", \"Title\", \"Status\"], sessionRows)\n .split(\"\\n\")\n .map((l) => \" \" + l)\n .join(\"\\n\")\n );\n }\n console.log();\n}\n\nexport function cmdArchive(db: Database, slug: string): void {\n const project = requireProject(db, slug);\n if (project.status === \"archived\") {\n console.log(warn(`Project ${slug} is already archived.`));\n return;\n }\n const ts = now();\n db.prepare(\n \"UPDATE projects SET status = 'archived', archived_at = ?, updated_at = ? WHERE id = ?\"\n ).run(ts, ts, project.id);\n console.log(ok(`Archived: ${bold(slug)}`));\n}\n\nexport function cmdUnarchive(db: Database, slug: string): void {\n const project = requireProject(db, slug);\n if (project.status !== \"archived\") {\n console.log(\n warn(`Project ${slug} is not archived (status: ${project.status}).`)\n );\n return;\n }\n const ts = now();\n db.prepare(\n \"UPDATE projects SET status = 'active', archived_at = NULL, updated_at = ? WHERE id = ?\"\n ).run(ts, project.id);\n console.log(ok(`Unarchived: ${bold(slug)}`));\n}\n\nexport function cmdMove(db: Database, slug: string, newPath: string): void {\n const project = requireProject(db, slug);\n const resolvedNew = resolvePath(newPath);\n const newEncoded = encodeDir(resolvedNew);\n const ts = now();\n\n db.prepare(\n \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n ).run(resolvedNew, newEncoded, ts, project.id);\n\n console.log(ok(`Moved: ${bold(slug)}`));\n console.log(dim(` Old path: ${project.root_path}`));\n console.log(dim(` New path: ${resolvedNew}`));\n}\n\nexport function cmdTag(\n db: Database,\n slug: string,\n tags: string[]\n): void {\n const project = requireProject(db, slug);\n const added: string[] = [];\n const skipped: string[] = [];\n\n for (const tagName of tags) {\n const tagId = upsertTag(db, tagName);\n const exists = db\n .prepare(\n \"SELECT 1 FROM project_tags WHERE project_id = ? AND tag_id = ?\"\n )\n .get(project.id, tagId);\n if (exists) {\n skipped.push(tagName);\n } else {\n db.prepare(\n \"INSERT INTO project_tags (project_id, tag_id) VALUES (?, ?)\"\n ).run(project.id, tagId);\n added.push(tagName);\n }\n }\n\n if (added.length) {\n console.log(\n ok(\n `Tagged ${bold(slug)}: ${added.map((t) => chalk.cyan(t)).join(\", \")}`\n )\n );\n }\n if (skipped.length) {\n console.log(dim(` Already present: ${skipped.join(\", \")}`));\n }\n}\n\nexport function cmdAlias(\n db: Database,\n slug: string,\n alias: string\n): void {\n requireProject(db, slug);\n\n const conflict = db\n .prepare(\"SELECT id FROM projects WHERE slug = ?\")\n .get(alias);\n if (conflict) {\n console.error(\n err(`\"${alias}\" is already a project slug — cannot use as alias.`)\n );\n process.exit(1);\n }\n\n const project = getProject(db, slug)!;\n try {\n db.prepare(\n \"INSERT INTO aliases (alias, project_id) VALUES (?, ?)\"\n ).run(alias, project.id);\n console.log(ok(`Alias added: ${bold(alias)} → ${slug}`));\n } catch {\n console.error(err(`Alias \"${alias}\" is already registered.`));\n process.exit(1);\n }\n}\n\nexport function cmdEdit(\n db: Database,\n slug: string,\n opts: { displayName?: string; type?: string }\n): void {\n const project = requireProject(db, slug);\n\n if (!opts.displayName && !opts.type) {\n console.log(warn(\"Nothing to update. Use --display-name or --type.\"));\n return;\n }\n\n const validTypes = [\"local\", \"central\", \"obsidian-linked\", \"external\"];\n if (opts.type && !validTypes.includes(opts.type)) {\n console.error(\n err(`Invalid type \"${opts.type}\". Valid: ${validTypes.join(\", \")}`)\n );\n process.exit(1);\n }\n\n const ts = now();\n if (opts.displayName) {\n db.prepare(\n \"UPDATE projects SET display_name = ?, updated_at = ? WHERE id = ?\"\n ).run(opts.displayName, ts, project.id);\n console.log(ok(`Display name updated: ${bold(opts.displayName)}`));\n }\n if (opts.type) {\n db.prepare(\n \"UPDATE projects SET type = ?, updated_at = ? WHERE id = ?\"\n ).run(opts.type, ts, project.id);\n console.log(ok(`Type updated: ${bold(opts.type)}`));\n }\n}\n\nexport function cmdDetect(\n db: Database,\n pathArg: string | undefined,\n opts: { json?: boolean }\n): void {\n const cwd = pathArg ? resolvePath(pathArg) : process.cwd();\n const detection = detectProject(db, cwd);\n\n if (!detection) {\n if (opts.json) {\n console.log(JSON.stringify({ error: \"no_match\", cwd }, null, 2));\n } else {\n console.log(warn(`No registered project found for: ${cwd}`));\n console.log(dim(\" Run 'pai project add .' to register this directory.\"));\n }\n process.exit(0);\n return;\n }\n\n if (opts.json) {\n console.log(formatDetectionJson(detection));\n return;\n }\n\n console.log();\n console.log(header(\" Project Detection Result\"));\n console.log();\n console.log(\n formatDetection(detection)\n .split(\"\\n\")\n .map((l) => \" \" + l)\n .join(\"\\n\")\n );\n console.log();\n}\n\nexport function cmdConsolidate(\n db: Database,\n identifier: string,\n opts: { yes?: boolean; dryRun?: boolean }\n): void {\n const project =\n resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\n\n console.log();\n console.log(header(` Consolidate: ${project.slug}`));\n console.log(` Target: ${project.root_path}`);\n console.log();\n\n const dirs = findProjectNotesDirs(project);\n\n if (dirs.length === 0) {\n console.log(warn(\" No scattered notes directories found for this project.\"));\n return;\n }\n\n const canonicalNotes = join(project.root_path, \"Notes\");\n const toMerge = dirs.filter((d) => d.notesPath !== canonicalNotes);\n\n if (toMerge.length === 0) {\n console.log(ok(\" All notes are already in the canonical location.\"));\n console.log(dim(` ${canonicalNotes}`));\n return;\n }\n\n console.log(\n ` Found ${toMerge.length} scattered Notes directory(ies) to consolidate:`\n );\n console.log();\n\n for (const d of toMerge) {\n console.log(` ${bold(d.encodedDir)}`);\n console.log(dim(` Notes: ${d.notesPath} (${d.noteCount} file(s))`));\n }\n\n console.log();\n console.log(` Destination: ${canonicalNotes}`);\n console.log();\n\n if (opts.dryRun) {\n console.log(warn(\" Dry run — no changes made. Remove --dry-run to proceed.\"));\n return;\n }\n\n if (!opts.yes) {\n console.log(\n warn(\n \" Run with --yes to perform consolidation, or --dry-run to preview changes.\"\n )\n );\n return;\n }\n\n mkdirSync(canonicalNotes, { recursive: true });\n\n let movedCount = 0;\n for (const d of toMerge) {\n try {\n const files = readdirSync(d.notesPath);\n for (const f of files) {\n if (!f.endsWith(\".md\") && !f.endsWith(\".txt\")) continue;\n const src = join(d.notesPath, f);\n const dest = join(canonicalNotes, f);\n if (!existsSync(dest)) {\n renameSync(src, dest);\n console.log(ok(` Moved: ${f}`));\n movedCount++;\n } else {\n console.log(warn(` Skipped (exists): ${f}`));\n }\n }\n } catch (e) {\n console.error(err(` Error reading ${d.notesPath}: ${e}`));\n }\n }\n\n console.log();\n console.log(ok(` Consolidated ${movedCount} file(s) into ${canonicalNotes}`));\n}\n\n/**\n * Attempt moved-path recovery for a project whose registered root_path is\n * missing. Updates the DB if exactly one candidate is found.\n *\n * Returns the new path on success, or undefined if ambiguous/not found.\n * Prints all messages to stderr (safe for shell-wrapper stdout capture).\n */\nfunction tryRecoverMovedProject(db: Database, project: ProjectRow): string | undefined {\n process.stderr.write(\n warn(`Path not found: ${project.root_path}\\n`) +\n dim(\" Searching for moved location...\\n\")\n );\n\n const result = findMovedPath(project.root_path);\n\n if (result.found) {\n const newPath = result.found;\n const newEncoded = encodeDir(newPath);\n const ts = now();\n db.prepare(\n \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n ).run(newPath, newEncoded, ts, project.id);\n process.stderr.write(\n ok(`Project moved: ${shortenPath(project.root_path, 50)}\\n`) +\n dim(` → ${newPath}\\n`) +\n ok(\"Registry updated.\\n\")\n );\n return newPath;\n }\n\n if (result.ambiguous) {\n process.stderr.write(\n warn(`Multiple directories named \"${basename(project.root_path)}\" found:\\n`)\n );\n for (const candidate of result.ambiguous) {\n process.stderr.write(dim(` ${candidate}\\n`));\n }\n process.stderr.write(\n dim(`\\n Disambiguate with: pai projects rebind ${project.slug} <path>\\n`)\n );\n return undefined;\n }\n\n process.stderr.write(\n err(\n `Project \"${project.slug}\" root_path \"${project.root_path}\" does not exist on disk\\n` +\n ` and no folder named \"${basename(project.root_path)}\" was found in scan dirs.\\n`\n ) +\n dim(` Fix with: pai projects rebind ${project.slug} <new-path>\\n`)\n );\n return undefined;\n}\n\nexport function cmdGo(db: Database, query: string): void {\n const all = db\n .prepare(\n \"SELECT * FROM projects WHERE status = 'active' ORDER BY updated_at DESC\"\n )\n .all() as ProjectRow[];\n\n if (!all.length) {\n console.error(\n err(\"No active projects registered. Run: pai project add <path>\")\n );\n process.exit(1);\n }\n\n const q = query.trim().toLowerCase();\n\n // 1. Exact slug or alias match\n const exact = getProject(db, query);\n if (exact) {\n if (!existsSync(exact.root_path)) {\n const recovered = tryRecoverMovedProject(db, exact);\n if (!recovered) { process.exitCode = 1; return; }\n process.stdout.write(recovered + \"\\n\");\n return;\n }\n process.stdout.write(exact.root_path + \"\\n\");\n return;\n }\n\n // 2. Substring match against slug, display_name, or root_path basename\n const partial = all.filter(\n (p) =>\n containsIgnoreCase(p.slug, q) ||\n containsIgnoreCase(p.display_name, q) ||\n containsIgnoreCase(basename(p.root_path), q)\n );\n\n if (partial.length === 1) {\n const p = partial[0];\n if (!existsSync(p.root_path)) {\n const recovered = tryRecoverMovedProject(db, p);\n if (!recovered) { process.exitCode = 1; return; }\n process.stdout.write(recovered + \"\\n\");\n return;\n }\n process.stdout.write(p.root_path + \"\\n\");\n return;\n }\n\n if (partial.length > 1) {\n console.error(\n err(`Ambiguous: \"${query}\" matches ${partial.length} projects:\\n`)\n );\n partial.forEach((p, i) => {\n console.error(\n ` ${dim(String(i + 1).padStart(2))} ${bold(p.slug.padEnd(30))} ${dim(\n shortenPath(p.root_path, 50)\n )}`\n );\n });\n console.error();\n console.error(dim(\" Use a more specific name or the exact slug.\"));\n process.exit(1);\n }\n\n // 3. No match — Levenshtein suggestions\n const scored = all\n .map((p) => {\n const distSlug = levenshtein(q, p.slug.toLowerCase());\n const distName = levenshtein(q, p.display_name.toLowerCase());\n return { project: p, dist: Math.min(distSlug, distName) };\n })\n .sort((a, b) => a.dist - b.dist);\n\n const threshold = 4;\n const suggestions =\n scored.filter((s) => s.dist <= threshold).length > 0\n ? scored.filter((s) => s.dist <= threshold).slice(0, 3)\n : scored.slice(0, 3);\n\n console.error(err(`Project not found: \"${query}\"\\n`));\n if (suggestions.length) {\n console.error(warn(\" Did you mean?\"));\n for (const s of suggestions) {\n console.error(\n ` ${bold(s.project.slug.padEnd(30))} ${dim(\n shortenPath(s.project.root_path, 50)\n )}`\n );\n }\n console.error();\n console.error(dim(\" Run: pai project list (to see all projects)\"));\n }\n process.exit(1);\n}\n\nexport function cmdRebind(\n db: Database,\n slug: string,\n newPath: string\n): void {\n const project = requireProject(db, slug);\n const resolved = resolve(newPath.startsWith(\"~/\") ? join(homedir(), newPath.slice(2)) : newPath);\n\n if (!existsSync(resolved)) {\n console.error(err(`Path does not exist: ${resolved}`));\n process.exit(1);\n }\n\n let isDir = false;\n try {\n isDir = statSync(resolved).isDirectory();\n } catch {\n // stat failed\n }\n if (!isDir) {\n console.error(err(`Path is not a directory: ${resolved}`));\n process.exit(1);\n }\n\n const newEncoded = encodeDir(resolved);\n\n // Check if another project already owns this path\n const conflict = db\n .prepare(\"SELECT slug FROM projects WHERE encoded_dir = ? AND id != ?\")\n .get(newEncoded, project.id) as { slug: string } | undefined;\n if (conflict) {\n console.error(\n err(`Path is already registered to project: ${bold(conflict.slug)}\\n`) +\n dim(` ${resolved}\\n`) +\n dim(` Archive or move that project first, or choose a different path.`)\n );\n process.exit(1);\n }\n\n const ts = now();\n db.prepare(\n \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n ).run(resolved, newEncoded, ts, project.id);\n\n console.log(ok(`Rebound: ${bold(slug)}`));\n console.log(dim(` Old path: ${project.root_path}`));\n console.log(dim(` New path: ${resolved}`));\n console.log(dim(` Encoded: ${newEncoded}`));\n}\n","/**\n * Session launch configuration for projects.\n * Implements per-project and global config CRUD (pai project config),\n * and the name/unname/names commands for curated project shortlists.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header, shortenPath, fmtDate, now, renderTable } from \"../../utils.js\";\nimport type { ProjectRow, SessionConfig, ConfigOption } from \"./types.js\";\nimport { resolveIdentifier, requireProject, getProjectAliases } from \"./helpers.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nexport const CONFIG_OPTIONS: ConfigOption[] = [\n { key: 'permission', type: 'string', description: 'Permission preset: full | trusted | default', examples: ['full', 'trusted', 'default'] },\n { key: 'flags', type: 'string', description: 'Raw Claude CLI flags', examples: ['--dangerously-skip-permissions', '--allowedTools Edit,Read'] },\n { key: 'env', type: 'object', description: 'Environment variables as key=value pairs', examples: ['IS_SANDBOX=1', 'CLAUDE_MODEL=opus'] },\n { key: 'autoStart', type: 'boolean', description: 'Auto-start session (skip interactive prompt)', examples: ['true', 'false'] },\n { key: 'prompt', type: 'string', description: 'Initial prompt sent to Claude on launch', examples: ['go', 'continue', 'run tests'] },\n { key: 'model', type: 'string', description: 'Model override for the session', examples: ['opus', 'sonnet', 'haiku'] },\n];\n\nexport const PERMISSION_PRESETS: Record<string, Partial<SessionConfig>> = {\n full: { permission: 'full', flags: '--dangerously-skip-permissions', env: { IS_SANDBOX: '1' }, autoStart: true, prompt: 'go' },\n trusted: { permission: 'trusted', flags: '', env: {}, autoStart: true, prompt: 'go' },\n default: { permission: 'default', flags: '', env: {}, autoStart: false },\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nexport function getSessionConfig(project: ProjectRow): SessionConfig {\n return project.session_config ? JSON.parse(project.session_config) : {};\n}\n\nexport function getGlobalDefaults(): SessionConfig {\n try {\n const configPath = join(homedir(), '.config', 'pai', 'config.json');\n if (existsSync(configPath)) {\n const config = JSON.parse(readFileSync(configPath, 'utf-8'));\n return config.sessionDefaults ?? {};\n }\n } catch { /* ignore */ }\n return {};\n}\n\nexport function saveGlobalDefaults(defaults: SessionConfig): void {\n const configPath = join(homedir(), '.config', 'pai', 'config.json');\n let config: Record<string, unknown> = {};\n try {\n if (existsSync(configPath)) {\n config = JSON.parse(readFileSync(configPath, 'utf-8'));\n }\n } catch { /* ignore */ }\n config.sessionDefaults = defaults;\n mkdirSync(join(homedir(), '.config', 'pai'), { recursive: true });\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n}\n\nfunction applyPreset(config: SessionConfig, preset: string): SessionConfig {\n const presetConfig = PERMISSION_PRESETS[preset];\n if (!presetConfig) {\n return { ...config, permission: 'custom', flags: preset };\n }\n return { ...config, ...presetConfig };\n}\n\nfunction parseConfigValue(key: string, value: string): unknown {\n const option = CONFIG_OPTIONS.find(o => o.key === key);\n if (!option) return value;\n\n switch (option.type) {\n case 'boolean':\n return value === 'true' || value === '1' || value === 'yes';\n case 'object':\n if (key === 'env') {\n const [k, ...vParts] = value.split('=');\n return { [k]: vParts.join('=') || '1' };\n }\n return value;\n default:\n return value;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commands\n// ---------------------------------------------------------------------------\n\nexport function cmdName(\n db: Database,\n identifier: string,\n shortname: string,\n opts: { permission?: string }\n): void {\n const project = resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\n\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(shortname)) {\n console.error(err(`Invalid name \"${shortname}\". Use letters, digits, hyphens, underscores. Must start with a letter.`));\n process.exit(1);\n }\n\n const conflictProject = db\n .prepare(\"SELECT id FROM projects WHERE slug = ? AND id != ?\")\n .get(shortname, project.id) as { id: number } | undefined;\n if (conflictProject) {\n console.error(err(`\"${shortname}\" is already a project slug.`));\n process.exit(1);\n }\n\n const conflictAlias = db\n .prepare(\"SELECT project_id FROM aliases WHERE alias = ?\")\n .get(shortname) as { project_id: number } | undefined;\n if (conflictAlias && conflictAlias.project_id !== project.id) {\n console.error(err(`\"${shortname}\" is already used by another project.`));\n process.exit(1);\n }\n\n if (!conflictAlias) {\n db.prepare(\"INSERT INTO aliases (alias, project_id) VALUES (?, ?)\").run(shortname, project.id);\n }\n\n if (opts.permission) {\n const existing = getSessionConfig(project);\n const config = applyPreset(existing, opts.permission);\n db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n .run(JSON.stringify(config), now(), project.id);\n }\n\n console.log(ok(`Named: ${bold(shortname)} → ${project.slug} (${shortenPath(project.root_path, 50)})`));\n if (opts.permission) {\n console.log(dim(` Permission: ${opts.permission}`));\n }\n}\n\nexport function cmdUnname(db: Database, shortname: string): void {\n const alias = db\n .prepare(\"SELECT project_id FROM aliases WHERE alias = ?\")\n .get(shortname) as { project_id: number } | undefined;\n\n if (!alias) {\n console.error(err(`No named project found: \"${shortname}\"`));\n process.exit(1);\n }\n\n db.prepare(\"DELETE FROM aliases WHERE alias = ?\").run(shortname);\n\n const remaining = db\n .prepare(\"SELECT COUNT(*) AS cnt FROM aliases WHERE project_id = ?\")\n .get(alias.project_id) as { cnt: number };\n\n console.log(ok(`Removed name: ${bold(shortname)}`));\n if (remaining.cnt === 0) {\n console.log(dim(\" Project has no remaining names.\"));\n }\n}\n\nexport function cmdNames(db: Database, opts: { json?: boolean; all?: boolean }): void {\n // --all: LEFT JOIN so projects WITHOUT a curated alias are included too (for\n // AIBroker's picker to search everything). Unnamed projects get their\n // slug as the launch key (pai project config resolves by slug).\n const rows = db.prepare(`\n SELECT p.*, a.alias AS name,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n FROM projects p\n ${opts.all ? \"LEFT JOIN\" : \"JOIN\"} aliases a ON a.project_id = p.id\n WHERE p.status = 'active'\n ORDER BY p.updated_at DESC\n `).all() as (ProjectRow & { name: string | null; session_count: number; last_active: number | null; session_config: string | null })[];\n\n if (opts.json) {\n const grouped = new Map<number, unknown>();\n for (const row of rows) {\n if (!grouped.has(row.id)) {\n grouped.set(row.id, {\n name: row.name ?? row.slug, // launch key: alias if named, else slug\n names: row.name ? [row.name] : [],\n slug: row.slug,\n display_name: row.display_name,\n root_path: row.root_path,\n session_count: row.session_count,\n last_active: row.last_active ? new Date(row.last_active).toISOString() : null,\n session_config: row.session_config ? JSON.parse(row.session_config) : null,\n });\n } else if (row.name) {\n (grouped.get(row.id) as { names: string[] }).names.push(row.name);\n }\n }\n console.log(JSON.stringify([...grouped.values()], null, 2));\n return;\n }\n\n if (!rows.length) {\n console.log(warn(\"No named projects. Use: pai project name <slug-or-number> <shortname>\"));\n return;\n }\n\n const seen = new Set<number>();\n const tableRows: string[][] = [];\n for (const row of rows) {\n if (seen.has(row.id)) continue;\n seen.add(row.id);\n\n const allNames = rows.filter(r => r.id === row.id).map(r => r.name).filter(Boolean) as string[];\n const config = row.session_config ? JSON.parse(row.session_config) as SessionConfig : null;\n const perm = config?.permission ?? dim('default');\n\n tableRows.push([\n bold(allNames.length ? allNames.join(', ') : row.slug),\n row.slug,\n dim(shortenPath(row.root_path, 40)),\n typeof perm === 'string' ? perm : dim('default'),\n String(row.session_count),\n fmtDate(row.last_active),\n ]);\n }\n\n console.log();\n console.log(header(\" Named Projects\"));\n console.log();\n console.log(renderTable([\"Name(s)\", \"Slug\", \"Path\", \"Permission\", \"Sessions\", \"Last Active\"], tableRows));\n console.log();\n console.log(dim(` ${tableRows.length} named project(s)`));\n}\n\nexport function cmdConfig(\n db: Database,\n identifier: string | undefined,\n opts: {\n set?: string[];\n unset?: string[];\n preset?: string;\n defaults?: boolean;\n options?: boolean;\n json?: boolean;\n reset?: boolean;\n }\n): void {\n // Discovery: list available options\n if (opts.options) {\n if (opts.json) {\n console.log(JSON.stringify({\n options: CONFIG_OPTIONS,\n presets: Object.entries(PERMISSION_PRESETS).map(([name, config]) => ({ name, ...config })),\n }, null, 2));\n return;\n }\n\n console.log();\n console.log(header(\" Session Config Options\"));\n console.log();\n console.log(bold(\" Available keys:\"));\n console.log();\n for (const opt of CONFIG_OPTIONS) {\n console.log(` ${bold(opt.key.padEnd(14))} ${dim(`(${opt.type})`)} ${opt.description}`);\n console.log(` ${' '.repeat(14)} ${dim('e.g.')} ${opt.examples.map(e => chalk.cyan(e)).join(', ')}`);\n }\n console.log();\n console.log(bold(\" Permission presets:\"));\n console.log();\n for (const [name, config] of Object.entries(PERMISSION_PRESETS)) {\n const parts = [];\n if (config.flags) parts.push(`flags: ${config.flags}`);\n if (config.env && Object.keys(config.env).length) parts.push(`env: ${Object.entries(config.env).map(([k, v]) => `${k}=${v}`).join(' ')}`);\n if (config.autoStart) parts.push('autoStart');\n if (config.prompt) parts.push(`prompt: \"${config.prompt}\"`);\n console.log(` ${bold(name.padEnd(10))} ${dim(parts.join(', ') || '(vanilla)')}`);\n }\n console.log();\n return;\n }\n\n // Global defaults mode\n if (opts.defaults) {\n let defaults = getGlobalDefaults();\n\n if (opts.reset) {\n saveGlobalDefaults({});\n console.log(ok(\"Global session defaults reset.\"));\n return;\n }\n\n if (opts.preset) {\n defaults = applyPreset(defaults, opts.preset);\n saveGlobalDefaults(defaults);\n console.log(ok(`Global defaults set to preset: ${bold(opts.preset)}`));\n return;\n }\n\n if (opts.set?.length) {\n for (const pair of opts.set) {\n const eqIdx = pair.indexOf('=');\n if (eqIdx === -1) { console.error(err(`Invalid format: \"${pair}\". Use key=value.`)); continue; }\n const key = pair.substring(0, eqIdx);\n const value = pair.substring(eqIdx + 1);\n if (!CONFIG_OPTIONS.find(o => o.key === key)) { console.error(err(`Unknown key: \"${key}\". Run: pai project config --options`)); continue; }\n if (key === 'env') {\n defaults.env = { ...(defaults.env ?? {}), ...(parseConfigValue('env', value) as Record<string, string>) };\n } else {\n (defaults as Record<string, unknown>)[key] = parseConfigValue(key, value);\n }\n }\n saveGlobalDefaults(defaults);\n console.log(ok(\"Global defaults updated.\"));\n }\n\n if (opts.unset?.length) {\n for (const key of opts.unset) {\n if (key.startsWith('env.')) {\n const envKey = key.substring(4);\n if (defaults.env) delete defaults.env[envKey];\n } else {\n delete (defaults as Record<string, unknown>)[key];\n }\n }\n saveGlobalDefaults(defaults);\n console.log(ok(\"Global defaults updated.\"));\n }\n\n if (opts.json) {\n console.log(JSON.stringify(defaults, null, 2));\n } else {\n console.log();\n console.log(header(\" Global Session Defaults\"));\n console.log();\n if (Object.keys(defaults).length === 0) {\n console.log(dim(\" No defaults set. New sessions use vanilla Claude.\"));\n console.log(dim(\" Set with: pai project config --defaults --preset full\"));\n } else {\n for (const [key, value] of Object.entries(defaults)) {\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${bold(key.padEnd(14))} ${display}`);\n }\n }\n console.log();\n }\n return;\n }\n\n // Per-project config\n if (!identifier) {\n console.error(err(\"Specify a project: pai project config <name-or-slug>\"));\n console.error(dim(\" Or use --defaults for global defaults, --options for available keys.\"));\n process.exit(1);\n }\n\n const project = resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\n let config = getSessionConfig(project);\n\n if (opts.reset) {\n db.prepare(\"UPDATE projects SET session_config = NULL, updated_at = ? WHERE id = ?\").run(now(), project.id);\n console.log(ok(`Config reset for ${bold(project.slug)}. Will use global defaults.`));\n return;\n }\n\n if (opts.preset) {\n config = applyPreset(config, opts.preset);\n db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n .run(JSON.stringify(config), now(), project.id);\n console.log(ok(`Applied preset ${bold(opts.preset)} to ${bold(project.slug)}`));\n }\n\n if (opts.set?.length) {\n for (const pair of opts.set) {\n const eqIdx = pair.indexOf('=');\n if (eqIdx === -1) { console.error(err(`Invalid format: \"${pair}\". Use key=value.`)); continue; }\n const key = pair.substring(0, eqIdx);\n const value = pair.substring(eqIdx + 1);\n if (!CONFIG_OPTIONS.find(o => o.key === key)) { console.error(err(`Unknown key: \"${key}\". Run: pai project config --options`)); continue; }\n if (key === 'env') {\n config.env = { ...(config.env ?? {}), ...(parseConfigValue('env', value) as Record<string, string>) };\n } else {\n (config as Record<string, unknown>)[key] = parseConfigValue(key, value);\n }\n }\n db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n .run(JSON.stringify(config), now(), project.id);\n console.log(ok(`Config updated for ${bold(project.slug)}`));\n }\n\n if (opts.unset?.length) {\n for (const key of opts.unset) {\n if (key.startsWith('env.')) {\n const envKey = key.substring(4);\n if (config.env) delete config.env[envKey];\n } else {\n delete (config as Record<string, unknown>)[key];\n }\n }\n db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n .run(JSON.stringify(config), now(), project.id);\n console.log(ok(`Config updated for ${bold(project.slug)}`));\n }\n\n const effective = { ...getGlobalDefaults(), ...config };\n const aliases = getProjectAliases(db, project.id);\n\n if (opts.json) {\n console.log(JSON.stringify({\n project: project.slug,\n names: aliases,\n root_path: project.root_path,\n config,\n global_defaults: getGlobalDefaults(),\n effective,\n }, null, 2));\n return;\n }\n\n console.log();\n console.log(header(` Config: ${project.slug}`));\n if (aliases.length) console.log(dim(` Names: ${aliases.join(', ')}`));\n console.log(dim(` Path: ${project.root_path}`));\n console.log();\n\n if (Object.keys(config).length === 0) {\n console.log(dim(\" No project-specific config. Using global defaults.\"));\n } else {\n console.log(bold(\" Project config:\"));\n for (const [key, value] of Object.entries(config)) {\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${bold(key.padEnd(14))} ${display}`);\n }\n }\n\n const globalDefaults = getGlobalDefaults();\n if (Object.keys(globalDefaults).length > 0) {\n console.log();\n console.log(dim(\" Global defaults (overridden by project config):\"));\n for (const [key, value] of Object.entries(globalDefaults)) {\n const overridden = key in config;\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${dim(key.padEnd(14))} ${overridden ? chalk.strikethrough(display) + ' ' + dim('(overridden)') : display}`);\n }\n }\n\n console.log();\n console.log(bold(\" Effective (what AIBroker uses):\"));\n for (const [key, value] of Object.entries(effective)) {\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${bold(key.padEnd(14))} ${display}`);\n }\n console.log();\n}\n","/**\n * Project health check command — audits registered projects for missing paths,\n * moved directories, and orphaned note directories.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header, shortenPath, now, renderTable, encodeDir } from \"../../utils.js\";\nimport type { HealthRow, HealthCategory, ProjectHealth, ProjectRow } from \"./types.js\";\n\nfunction findOrphanedNotesDirs(project: ProjectRow): string[] {\n const claudeProjects = join(homedir(), \".claude\", \"projects\");\n if (!existsSync(claudeProjects)) return [];\n\n const expected = encodeDir(project.root_path);\n const results: string[] = [];\n\n try {\n for (const entry of readdirSync(claudeProjects)) {\n const full = join(claudeProjects, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n if (entry === expected || entry === project.encoded_dir) {\n const notesDir = join(full, \"Notes\");\n if (existsSync(notesDir)) {\n results.push(notesDir);\n }\n }\n }\n } catch {\n // Unreadable — ignore\n }\n return results;\n}\n\nfunction suggestMovedPath(project: ProjectRow): string | undefined {\n const name = basename(project.root_path);\n const candidates = [\n join(homedir(), \"dev\", name),\n join(homedir(), \"dev\", \"ai\", name),\n join(homedir(), \"Desktop\", name),\n join(homedir(), \"Projects\", name),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n return undefined;\n}\n\nexport function cmdHealth(\n db: Database,\n opts: { fix?: boolean; json?: boolean; status?: string }\n): void {\n const rows = db\n .prepare(\n `SELECT p.*,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count\n FROM projects p\n ORDER BY p.status ASC, p.updated_at DESC`\n )\n .all() as HealthRow[];\n\n const results: ProjectHealth[] = rows.map((project) => {\n const pathExists = existsSync(project.root_path);\n const orphaned = findOrphanedNotesDirs(project);\n\n let category: HealthCategory;\n let suggestedPath: string | undefined;\n\n if (pathExists) {\n category = \"active\";\n } else {\n suggestedPath = suggestMovedPath(project);\n category = suggestedPath ? \"stale\" : \"dead\";\n }\n\n return {\n project,\n category,\n suggestedPath,\n claudeNotesExists: orphaned.length > 0,\n orphanedNotesDirs: orphaned,\n };\n });\n\n const filtered = opts.status ? results.filter((r) => r.category === opts.status) : results;\n\n if (opts.json) {\n console.log(JSON.stringify(\n filtered.map((r) => ({\n slug: r.project.slug,\n root_path: r.project.root_path,\n status: r.project.status,\n health: r.category,\n session_count: r.project.session_count,\n suggested_path: r.suggestedPath ?? null,\n claude_notes_exists: r.claudeNotesExists,\n orphaned_notes_dirs: r.orphanedNotesDirs,\n })),\n null,\n 2\n ));\n return;\n }\n\n const active = filtered.filter((r) => r.category === \"active\");\n const stale = filtered.filter((r) => r.category === \"stale\");\n const dead = filtered.filter((r) => r.category === \"dead\");\n\n console.log();\n console.log(header(\" PAI Project Health Report\"));\n console.log();\n console.log(\n ` ${chalk.green(\"Active:\")} ${active.length} ${chalk.yellow(\"Stale (moved?):\")} ${stale.length} ${chalk.red(\"Dead (missing):\")} ${dead.length}`\n );\n console.log();\n\n if (active.length) {\n console.log(bold(\" Active projects (path exists):\"));\n const tableRows = active.map((r) => [\n bold(r.project.slug),\n dim(shortenPath(r.project.root_path, 50)),\n String(r.project.session_count),\n r.claudeNotesExists ? chalk.green(\"yes\") : dim(\"no\"),\n ]);\n console.log(\n renderTable([\"Slug\", \"Path\", \"Sessions\", \"Claude Notes\"], tableRows)\n .split(\"\\n\").map((l) => \" \" + l).join(\"\\n\")\n );\n console.log();\n }\n\n if (stale.length) {\n console.log(warn(\" Stale projects (path missing, possible new location found):\"));\n for (const r of stale) {\n console.log(` ${bold(r.project.slug)}`);\n console.log(dim(` Old path: ${r.project.root_path}`));\n console.log(chalk.cyan(` Found at: ${r.suggestedPath}`));\n if (r.claudeNotesExists) {\n console.log(chalk.green(` Notes: ${r.orphanedNotesDirs.join(\", \")}`));\n }\n if (opts.fix && r.suggestedPath) {\n const ts = now();\n const newEncoded = encodeDir(r.suggestedPath);\n db.prepare(\"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\")\n .run(r.suggestedPath, newEncoded, ts, r.project.id);\n console.log(ok(` Auto-fixed: updated path to ${r.suggestedPath}`));\n } else if (r.suggestedPath) {\n console.log(dim(` Fix: pai project move ${r.project.slug} ${r.suggestedPath}`));\n }\n }\n console.log();\n }\n\n if (dead.length) {\n console.log(err(\" Dead projects (path missing, no match found):\"));\n for (const r of dead) {\n console.log(` ${bold(r.project.slug)} ${dim(r.project.root_path)}`);\n if (r.claudeNotesExists) {\n console.log(chalk.yellow(` Notes: ${r.orphanedNotesDirs.join(\", \")}`));\n }\n if (r.project.session_count === 0 && opts.fix) {\n db.prepare(\"UPDATE projects SET status = 'archived', archived_at = ?, updated_at = ? WHERE id = ?\")\n .run(now(), now(), r.project.id);\n console.log(ok(\" Auto-fixed: archived (0 sessions, path gone)\"));\n } else {\n console.log(dim(` Fix: pai project archive ${r.project.slug} (or pai project move ...)`));\n }\n }\n console.log();\n }\n\n console.log(dim(` ${rows.length} total: ${active.length} active, ${stale.length} stale, ${dead.length} dead`));\n\n if (!opts.fix && (stale.length > 0 || dead.length > 0)) {\n console.log();\n console.log(warn(\" Run with --fix to auto-remediate where possible.\"));\n }\n}\n","/**\n * Commander registration for all `pai projects` sub-commands (plural namespace).\n *\n * This is the canonical namespace. `pai project <subcmd>` (singular) is a\n * deprecated alias that forwards here with a stderr notice.\n */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { cmdPromote } from \"../../../session/promote.js\";\nimport {\n cmdAdd,\n cmdList,\n cmdInfo,\n cmdArchive,\n cmdUnarchive,\n cmdMove,\n cmdTag,\n cmdAlias,\n cmdEdit,\n cmdDetect,\n cmdConsolidate,\n cmdGo,\n cmdRebind,\n findMovedPath,\n} from \"./commands.js\";\nimport { existsSync } from \"node:fs\";\nimport { ok, warn, err, dim, bold, shortenPath, encodeDir, now } from \"../../utils.js\";\nimport { basename } from \"node:path\";\nimport { cmdName, cmdUnname, cmdNames, cmdConfig } from \"./session-config.js\";\nimport { cmdHealth } from \"./health.js\";\nimport { resolveIdentifier } from \"./helpers.js\";\n\nexport { cmdGo };\n\nexport function registerProjectsCommands(\n projectsCmd: Command,\n getDb: () => Database\n): void {\n // pai projects (bare) — show the unified deduped listing, same as `pai`.\n // The old project-only listing is still available as `pai projects list-raw`.\n projectsCmd\n .command(\"list\", { isDefault: true })\n .description(\n \"Show the unified deduped listing (same as `pai`).\\n\" +\n \"Use --all to include cold / zero-session / archived projects.\"\n )\n .option(\"--all\", \"Show all entries including cold / zero-session / archived projects\")\n .option(\"-n, --n <count>\", \"Max rows to show\", \"20\")\n .action(async (opts: { all?: boolean; n?: string }) => {\n const { cmdMain } = await import(\"../main-resolver.js\");\n await cmdMain(getDb(), undefined, undefined, opts);\n });\n\n // Raw legacy project listing (registry rows only, with slug + path)\n projectsCmd\n .command(\"list-raw\")\n .description(\"Raw registry listing with slug, path, status, last_active columns\")\n .option(\"--all\", \"Include archived projects (default: active only)\")\n .option(\"--status <status>\", \"Filter by status: active | archived\")\n .option(\"--tag <tag>\", \"Filter by tag\")\n .option(\"--type <type>\", \"Filter by type\")\n .action((opts: { all?: boolean; status?: string; tag?: string; type?: string }) => {\n cmdList(getDb(), opts);\n });\n\n // pai projects cd <identifier>\n projectsCmd\n .command(\"cd <identifier>\")\n .description(\n \"cd to a project directory. Short form: pai cd <name>\\n\" +\n \"(The shell wrapper handles the actual cd; pure output here.)\\n\" +\n \"Auto-detects moved projects when the registered path no longer exists.\"\n )\n .action((identifier: string) => {\n const db = getDb();\n const project = resolveIdentifier(db, identifier);\n if (!project) {\n console.error(`Project not found: ${identifier}`);\n process.exit(1);\n return;\n }\n\n if (existsSync(project.root_path)) {\n process.stdout.write(project.root_path + \"\\n\");\n return;\n }\n\n // Path missing — search for moved location\n process.stderr.write(\n warn(`Path not found: ${project.root_path}\\n`) +\n dim(\" Searching for moved location...\\n\")\n );\n\n const result = findMovedPath(project.root_path);\n\n if (result.found) {\n const newPath = result.found;\n const newEncoded = encodeDir(newPath);\n const ts = now();\n db.prepare(\n \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n ).run(newPath, newEncoded, ts, project.id);\n process.stderr.write(\n ok(`Project moved: ${shortenPath(project.root_path, 50)}\\n`) +\n dim(` → ${newPath}\\n`) +\n ok(\"Registry updated.\\n\")\n );\n process.stdout.write(newPath + \"\\n\");\n return;\n }\n\n if (result.ambiguous) {\n process.stderr.write(\n warn(`Multiple directories named \"${basename(project.root_path)}\" found:\\n`)\n );\n for (const candidate of result.ambiguous) {\n process.stderr.write(dim(` ${candidate}\\n`));\n }\n process.stderr.write(\n dim(`\\n Disambiguate with: pai projects rebind ${project.slug} <path>\\n`)\n );\n process.exitCode = 1;\n return;\n }\n\n process.stderr.write(\n err(\n `Project \"${project.slug}\" root_path \"${project.root_path}\" does not exist on disk\\n` +\n ` and no folder named \"${basename(project.root_path)}\" was found in scan dirs.\\n`\n ) +\n dim(` Fix with: pai projects rebind ${project.slug} <new-path>\\n`)\n );\n process.exitCode = 1;\n });\n\n // pai projects add <path>\n projectsCmd\n .command(\"add <path>\")\n .description(\"Register a project directory in the PAI registry\")\n .option(\"--slug <slug>\", \"Override auto-generated slug\")\n .option(\"--type <type>\", \"Project type: local | central | obsidian-linked | external\", \"local\")\n .option(\"--display-name <name>\", \"Human-readable display name\")\n .action(\n (\n rawPath: string,\n opts: { slug?: string; type?: string; displayName?: string }\n ) => {\n cmdAdd(getDb(), rawPath, opts);\n }\n );\n\n // pai projects info <slug>\n projectsCmd\n .command(\"info <slug>\")\n .description(\"Show full details for a project\")\n .action((slug: string) => {\n cmdInfo(getDb(), slug);\n });\n\n // pai projects archive <slug>\n projectsCmd\n .command(\"archive <slug>\")\n .description(\"Archive a project\")\n .action((slug: string) => {\n cmdArchive(getDb(), slug);\n });\n\n // pai projects unarchive <slug>\n projectsCmd\n .command(\"unarchive <slug>\")\n .description(\"Restore an archived project to active status\")\n .action((slug: string) => {\n cmdUnarchive(getDb(), slug);\n });\n\n // pai projects move <slug> <new-path>\n projectsCmd\n .command(\"move <slug> <new-path>\")\n .description(\"Update the root path for a project\")\n .action((slug: string, newPath: string) => {\n cmdMove(getDb(), slug, newPath);\n });\n\n // pai projects rebind <slug> <new-path>\n projectsCmd\n .command(\"rebind <slug> <new-path>\")\n .description(\n \"Manually update the root_path for a project (for when auto-detect found multiple matches).\\n\" +\n \"Validates the new path exists and is a directory, then updates the registry.\"\n )\n .action((slug: string, newPath: string) => {\n cmdRebind(getDb(), slug, newPath);\n });\n\n // pai projects tag <slug> <tags...>\n projectsCmd\n .command(\"tag <slug> <tags...>\")\n .description(\"Add one or more tags to a project\")\n .action((slug: string, tags: string[]) => {\n cmdTag(getDb(), slug, tags);\n });\n\n // pai projects alias <slug> <alias>\n projectsCmd\n .command(\"alias <slug> <alias>\")\n .description(\"Register an alternative slug for a project\")\n .action((slug: string, alias: string) => {\n cmdAlias(getDb(), slug, alias);\n });\n\n // pai projects edit <slug>\n projectsCmd\n .command(\"edit <slug>\")\n .description(\"Edit project metadata\")\n .option(\"--display-name <name>\", \"New display name\")\n .option(\"--type <type>\", \"New type\")\n .action(\n (slug: string, opts: { displayName?: string; type?: string }) => {\n cmdEdit(getDb(), slug, opts);\n }\n );\n\n // pai projects detect [path]\n projectsCmd\n .command(\"detect [path]\")\n .description(\"Detect which registered project the given path (or CWD) belongs to\")\n .option(\"--json\", \"Output raw JSON instead of human-readable text\")\n .action((pathArg: string | undefined, opts: { json?: boolean }) => {\n cmdDetect(getDb(), pathArg, opts);\n });\n\n // pai projects health\n projectsCmd\n .command(\"health\")\n .description(\n \"Audit all registered projects: check which paths still exist, find moved/dead projects\"\n )\n .option(\"--fix\", \"Auto-remediate where possible\")\n .option(\"--json\", \"Output raw JSON report\")\n .option(\"--status <category>\", \"Filter output to: active | stale | dead\")\n .action((opts: { fix?: boolean; json?: boolean; status?: string }) => {\n cmdHealth(getDb(), opts);\n });\n\n // pai projects consolidate <identifier>\n projectsCmd\n .command(\"consolidate <identifier>\")\n .description(\n \"Consolidate scattered ~/.claude/projects/.../Notes/ directories for a project into its canonical Notes/ location\"\n )\n .option(\"--yes\", \"Perform consolidation without confirmation prompt\")\n .option(\"--dry-run\", \"Preview what would be moved without making changes\")\n .action(\n (identifier: string, opts: { yes?: boolean; dryRun?: boolean }) => {\n cmdConsolidate(getDb(), identifier, opts);\n }\n );\n\n // pai projects promote\n projectsCmd\n .command(\"promote\")\n .description(\"Promote a session note into a new standalone project\")\n .requiredOption(\"--from-session <path>\", \"Path to the session note markdown file\")\n .requiredOption(\"--to <path>\", \"Directory path for the new project (must not exist)\")\n .option(\"--name <name>\", \"Display name for the new project (derived from filename if omitted)\")\n .action((opts: { fromSession: string; to: string; name?: string }) => {\n cmdPromote(getDb(), opts);\n });\n\n // pai projects go <query>\n projectsCmd\n .command(\"go <query>\")\n .description(\n \"Print the root path for a project by slug, partial name, or fuzzy match.\\n\" +\n \"Designed for shell integration: cd $(pai projects go <query>)\"\n )\n .action((query: string) => {\n cmdGo(getDb(), query);\n });\n\n // pai projects name <identifier> <shortname>\n projectsCmd\n .command(\"name <identifier> <shortname>\")\n .description(\"Give a project a short name for quick access\")\n .option(\"--permission <level>\", \"Permission level: full | trusted | default\")\n .action(\n (\n identifier: string,\n shortname: string,\n opts: { permission?: string }\n ) => {\n cmdName(getDb(), identifier, shortname, opts);\n }\n );\n\n // pai projects unname <shortname>\n projectsCmd\n .command(\"unname <shortname>\")\n .description(\"Remove a project's short name\")\n .action((shortname: string) => {\n cmdUnname(getDb(), shortname);\n });\n\n // pai projects names\n projectsCmd\n .command(\"names\")\n .description(\"List named projects (your curated shortlist)\")\n .option(\"--json\", \"Output JSON for AIBroker consumption\")\n .option(\"--all\", \"Include ALL active registered projects, not just the named shortlist\")\n .action((opts: { json?: boolean; all?: boolean }) => {\n cmdNames(getDb(), opts);\n });\n\n // pai projects config [identifier]\n projectsCmd\n .command(\"config [identifier]\")\n .description(\n \"View or modify session launch config for a project.\\n\" +\n \"Use --options to discover available keys and presets.\"\n )\n .option(\n \"--set <key=value...>\",\n \"Set config values (repeatable)\",\n (v: string, prev: string[]) => [...prev, v],\n [] as string[]\n )\n .option(\n \"--unset <key...>\",\n \"Remove config keys (repeatable, use env.KEY for env vars)\",\n (v: string, prev: string[]) => [...prev, v],\n [] as string[]\n )\n .option(\"--preset <name>\", \"Apply a permission preset: full | trusted | default\")\n .option(\"--defaults\", \"Manage global session defaults instead of a project\")\n .option(\"--options\", \"List available config keys and presets\")\n .option(\"--json\", \"Output JSON\")\n .option(\"--reset\", \"Reset config to empty (inherit global defaults)\")\n .action(\n (\n identifier: string | undefined,\n opts: {\n set?: string[];\n unset?: string[];\n preset?: string;\n defaults?: boolean;\n options?: boolean;\n json?: boolean;\n reset?: boolean;\n }\n ) => {\n cmdConfig(getDb(), identifier, opts);\n }\n );\n}\n","/** Registry migrate command: import data from ~/.claude/session-registry.json. */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { ok, warn, err, dim } from \"../../utils.js\";\nimport { slugify, parseSessionFilename } from \"../../../registry/migrate.js\";\nimport { upsertProject, upsertSession } from \"./utils.js\";\nimport type { Database } from \"better-sqlite3\";\n\n// ---------------------------------------------------------------------------\n// Legacy JSON registry types\n// ---------------------------------------------------------------------------\n\ninterface LegacyProjectEntry {\n encoded_dir: string;\n original_path: string;\n notes?: string[];\n session_count?: number;\n note_count?: number;\n todo_exists?: boolean;\n last_modified?: string;\n path_exists?: boolean;\n}\n\ninterface LegacyJsonRegistry {\n version?: number;\n projects?: LegacyProjectEntry[];\n [key: string]: unknown;\n}\n\nconst SESSION_REGISTRY_PATH = join(homedir(), \".claude\", \"session-registry.json\");\n\n// ---------------------------------------------------------------------------\n// cmdMigrate\n// ---------------------------------------------------------------------------\n\nexport function cmdMigrate(db: Database): void {\n if (!existsSync(SESSION_REGISTRY_PATH)) {\n console.error(err(`session-registry.json not found: ${SESSION_REGISTRY_PATH}`));\n process.exit(1);\n }\n\n let registry: LegacyJsonRegistry;\n try {\n const raw = readFileSync(SESSION_REGISTRY_PATH, \"utf8\");\n registry = JSON.parse(raw) as LegacyJsonRegistry;\n } catch (e) {\n console.error(err(`Failed to parse session-registry.json: ${e}`));\n process.exit(1);\n }\n\n const projects = registry.projects ?? [];\n if (!projects.length) {\n console.log(warn(\"No projects found in session-registry.json.\"));\n return;\n }\n\n console.log(dim(`Migrating ${projects.length} project(s) from session-registry.json ...`));\n\n let projectsNew = 0;\n let projectsSkipped = 0;\n let sessionsNew = 0;\n const errors: string[] = [];\n\n for (const entry of projects) {\n if (!entry.original_path || !entry.encoded_dir) {\n errors.push(`Skipping entry with missing path: ${JSON.stringify(entry)}`);\n continue;\n }\n\n const rootPath = entry.original_path;\n const encodedDir = entry.encoded_dir;\n const slug = slugify(rootPath);\n\n try {\n const { isNew, id } = upsertProject(db, slug, rootPath, encodedDir);\n if (isNew) projectsNew++;\n else projectsSkipped++;\n\n const notes = entry.notes ?? [];\n for (const filename of notes) {\n const parsed = parseSessionFilename(filename);\n if (!parsed) continue;\n\n const isNewSession = upsertSession(db, id, parsed.number, parsed.date, parsed.slug, parsed.title, parsed.filename);\n if (isNewSession) sessionsNew++;\n }\n } catch (e) {\n errors.push(`Error processing ${entry.encoded_dir}: ${String(e)}`);\n }\n }\n\n console.log(ok(\"Migration complete.\"));\n console.log(dim(` Projects: ${projectsNew} new, ${projectsSkipped} already existed`));\n console.log(dim(` Sessions: ${sessionsNew} new`));\n\n if (errors.length) {\n console.log();\n console.log(warn(` ${errors.length} error(s) during migration:`));\n for (const e of errors.slice(0, 5)) {\n console.log(dim(` ${e}`));\n }\n if (errors.length > 5) {\n console.log(dim(` ... and ${errors.length - 5} more`));\n }\n }\n}\n","/** Registry command registration and simple sub-commands (stats, rebuild, lookup). */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { ok, warn, err, dim, bold, fmtDate } from \"../../utils.js\";\nimport { homedir } from \"node:os\";\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { cmdScan, loadScanConfig, saveScanConfig, resolveHome } from \"./scan.js\";\nimport { cmdMigrate } from \"./migrate.js\";\n\n// ---------------------------------------------------------------------------\n// stats\n// ---------------------------------------------------------------------------\n\nfunction cmdStats(db: Database): void {\n const totalProjects = (db.prepare(\"SELECT COUNT(*) AS n FROM projects\").get() as { n: number }).n;\n const activeProjects = (db.prepare(\"SELECT COUNT(*) AS n FROM projects WHERE status = 'active'\").get() as { n: number }).n;\n const archivedProjects = (db.prepare(\"SELECT COUNT(*) AS n FROM projects WHERE status = 'archived'\").get() as { n: number }).n;\n const totalSessions = (db.prepare(\"SELECT COUNT(*) AS n FROM sessions\").get() as { n: number }).n;\n const totalTags = (db.prepare(\"SELECT COUNT(*) AS n FROM tags\").get() as { n: number }).n;\n\n const lastProject = db\n .prepare(\"SELECT updated_at FROM projects ORDER BY updated_at DESC LIMIT 1\")\n .get() as { updated_at: number } | undefined;\n\n const lastSession = db\n .prepare(\"SELECT created_at FROM sessions ORDER BY created_at DESC LIMIT 1\")\n .get() as { created_at: number } | undefined;\n\n console.log();\n console.log(bold(\" PAI Registry Stats\"));\n console.log();\n console.log(` ${bold(\"Projects:\")} ${totalProjects}`);\n console.log(` ${bold(\" Active:\")} ${activeProjects}`);\n console.log(` ${bold(\" Archived:\")} ${archivedProjects}`);\n console.log(` ${bold(\"Sessions:\")} ${totalSessions}`);\n console.log(` ${bold(\"Tags:\")} ${totalTags}`);\n if (lastProject) {\n console.log(` ${bold(\"Last updated:\")} ${fmtDate(lastProject.updated_at)}`);\n }\n if (lastSession) {\n console.log(` ${bold(\"Last session:\")} ${fmtDate(lastSession.created_at)}`);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// rebuild\n// ---------------------------------------------------------------------------\n\nfunction cmdRebuild(db: Database): void {\n console.log(warn(\"Rebuilding registry — all existing data will be erased.\"));\n console.log(dim(\"Clearing all tables ...\"));\n\n db.exec(`\n DELETE FROM compaction_log;\n DELETE FROM session_tags;\n DELETE FROM project_tags;\n DELETE FROM aliases;\n DELETE FROM sessions;\n DELETE FROM projects;\n DELETE FROM tags;\n DELETE FROM schema_version;\n `);\n\n console.log(dim(\"Registry cleared. Re-scanning ...\"));\n cmdScan(db);\n}\n\n// ---------------------------------------------------------------------------\n// lookup\n// ---------------------------------------------------------------------------\n\nfunction cmdLookup(db: Database, fsPath: string): void {\n const resolved = resolve(fsPath);\n\n const row = db\n .prepare(\"SELECT slug FROM projects WHERE root_path = ?\")\n .get(resolved) as { slug: string } | undefined;\n\n if (!row) {\n process.exit(1);\n }\n\n process.stdout.write(row.slug + \"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerRegistryCommands(\n registryCmd: Command,\n getDb: () => Database\n): void {\n // pai registry scan\n registryCmd\n .command(\"scan\")\n .description(\"Walk ~/.claude/projects/ and configured scan_dirs, upsert all projects\")\n .option(\"--add-dir <path>\", \"Add a directory to scan_dirs config\")\n .option(\"--remove-dir <path>\", \"Remove a directory from scan_dirs config\")\n .option(\"--show-dirs\", \"Show currently configured scan directories\")\n .option(\"--quick\", \"Minimal output mode (used by hooks and daemon)\")\n .action((opts: { addDir?: string; removeDir?: string; showDirs?: boolean; quick?: boolean }) => {\n if (opts.showDirs) {\n const config = loadScanConfig();\n if (!config.scan_dirs.length) {\n console.log(dim(\" No extra scan directories configured.\"));\n console.log(dim(\" Use --add-dir <path> to add one.\"));\n } else {\n console.log(bold(\" Configured scan directories:\"));\n for (const d of config.scan_dirs) {\n console.log(` ${d}`);\n }\n }\n return;\n }\n if (opts.addDir) {\n const config = loadScanConfig();\n const resolved = resolveHome(opts.addDir);\n if (!existsSync(resolved)) {\n console.error(err(`Directory not found: ${resolved}`));\n process.exit(1);\n }\n const display = resolved.startsWith(homedir())\n ? \"~\" + resolved.slice(homedir().length)\n : resolved;\n if (config.scan_dirs.includes(display) || config.scan_dirs.includes(resolved)) {\n console.log(warn(`Already configured: ${display}`));\n } else {\n config.scan_dirs.push(display);\n saveScanConfig(config);\n console.log(ok(`Added scan directory: ${bold(display)}`));\n }\n }\n if (opts.removeDir) {\n const config = loadScanConfig();\n const resolved = resolveHome(opts.removeDir);\n const display = resolved.startsWith(homedir())\n ? \"~\" + resolved.slice(homedir().length)\n : resolved;\n const before = config.scan_dirs.length;\n config.scan_dirs = config.scan_dirs.filter((d) => resolveHome(d) !== resolved);\n if (config.scan_dirs.length < before) {\n saveScanConfig(config);\n console.log(ok(`Removed scan directory: ${bold(display)}`));\n } else {\n console.log(warn(`Not found in config: ${display}`));\n }\n }\n if (!opts.addDir && !opts.removeDir) {\n cmdScan(getDb(), { quick: opts.quick });\n }\n });\n\n // pai registry migrate\n registryCmd\n .command(\"migrate\")\n .description(\"Import data from ~/.claude/session-registry.json\")\n .action(() => {\n cmdMigrate(getDb());\n });\n\n // pai registry stats\n registryCmd\n .command(\"stats\")\n .description(\"Show summary statistics for the registry\")\n .action(() => {\n cmdStats(getDb());\n });\n\n // pai registry rebuild\n registryCmd\n .command(\"rebuild\")\n .description(\"Erase all registry data and rebuild from the filesystem (destructive)\")\n .action(() => {\n cmdRebuild(getDb());\n });\n\n // pai registry lookup --path <path>\n registryCmd\n .command(\"lookup\")\n .description(\"Find the project slug for a filesystem path (for use in scripts)\")\n .requiredOption(\"--path <path>\", \"Filesystem path to look up\")\n .action((opts: { path: string }) => {\n cmdLookup(getDb(), opts.path);\n });\n}\n","/** Memory embed command: generate embeddings for un-embedded chunks. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { embedChunks } from \"../../../memory/indexer.js\";\nimport { dim, bold, ok, err } from \"../../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Shared embed runner (used by both index --embed and embed sub-command)\n// ---------------------------------------------------------------------------\n\nexport async function runEmbed(\n federation: Database,\n projectId?: number,\n projectSlug?: string,\n batchSize = 50,\n): Promise<void> {\n const label = projectSlug ? `project ${projectSlug}` : \"all projects\";\n console.log(dim(`Generating embeddings for ${label} (this may take a while on first run)...`));\n\n const { chunksEmbedded } = await embedChunks(\n federation,\n projectId,\n batchSize,\n (done, total) => {\n process.stdout.write(`\\r ${done} / ${total} chunks embedded...`);\n },\n );\n\n process.stdout.write(\"\\r\");\n console.log(ok(`Done.`) + ` ${bold(String(chunksEmbedded))} chunks embedded`);\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerEmbedCommand(\n memoryCmd: Command,\n getDb: () => Database,\n): void {\n memoryCmd\n .command(\"embed [project-slug]\")\n .description(\"Generate embeddings for un-embedded chunks (Phase 2.5)\")\n .option(\"--batch-size <n>\", \"Chunks to embed per batch\", \"50\")\n .action(async (projectSlug: string | undefined, opts: { batchSize?: string }) => {\n const registryDb = getDb();\n\n let federation: Database;\n try {\n federation = openFederation();\n } catch (e) {\n console.error(err(`Failed to open federation database: ${e}`));\n process.exit(1);\n }\n\n if (projectSlug) {\n const project = registryDb\n .prepare(\"SELECT id, slug FROM projects WHERE slug = ?\")\n .get(projectSlug) as { id: number; slug: string } | undefined;\n\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n await runEmbed(federation, project.id, project.slug, parseInt(opts.batchSize ?? \"50\", 10));\n } else {\n await runEmbed(federation, undefined, undefined, parseInt(opts.batchSize ?? \"50\", 10));\n }\n });\n}\n","/** Memory index command: index one or all projects into the memory store. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { indexProject, indexAll } from \"../../../memory/indexer.js\";\nimport { dim, bold, ok, err } from \"../../utils.js\";\nimport { PaiClient } from \"../../../daemon/ipc-client.js\";\nimport { loadConfig } from \"../../../daemon/config.js\";\nimport { runEmbed } from \"./embed.js\";\n\nexport function registerIndexCommand(\n memoryCmd: Command,\n getDb: () => Database,\n): void {\n memoryCmd\n .command(\"index [project-slug]\")\n .description(\"Index memory files for one project or all projects\")\n .option(\"--all\", \"Index all active projects (default when no slug given)\")\n .option(\"--embed\", \"Also generate embeddings for newly indexed chunks (Phase 2.5)\")\n .option(\"--direct\", \"Skip daemon IPC and run index directly (for debugging)\")\n .action(async (projectSlug: string | undefined, opts: { all?: boolean; embed?: boolean; direct?: boolean }) => {\n const registryDb = getDb();\n\n // If daemon is running and no --direct flag, trigger via IPC (non-blocking)\n if (!opts.direct && !projectSlug) {\n try {\n const config = loadConfig();\n const client = new PaiClient(config.socketPath);\n await client.triggerIndex();\n console.log(ok(\"Index triggered in daemon.\") + dim(\" Check daemon logs for progress.\"));\n console.log(dim(\" Run `pai daemon logs` to watch progress.\"));\n return;\n } catch {\n console.log(dim(\"Daemon not running. Running direct index...\"));\n }\n }\n\n let federation: Database;\n try {\n federation = openFederation();\n } catch (e) {\n console.error(err(`Failed to open federation database: ${e}`));\n process.exit(1);\n }\n\n if (projectSlug) {\n const project = registryDb\n .prepare(\"SELECT id, slug, display_name, root_path FROM projects WHERE slug = ? AND status = 'active'\")\n .get(projectSlug) as\n | { id: number; slug: string; display_name: string; root_path: string }\n | undefined;\n\n if (!project) {\n console.error(err(`Project not found or not active: ${projectSlug}`));\n process.exit(1);\n }\n\n console.log(dim(`Indexing ${project.display_name} (${project.slug})...`));\n const result = await indexProject(federation, project.id, project.root_path);\n\n console.log(\n ok(`Done.`) +\n ` ${bold(String(result.filesProcessed))} files indexed` +\n `, ${bold(String(result.chunksCreated))} chunks created` +\n `, ${dim(String(result.filesSkipped) + \" skipped (unchanged)\")}`,\n );\n\n if (opts.embed) {\n await runEmbed(federation, project.id, project.slug);\n }\n\n } else if (opts.all || !projectSlug) {\n console.log(dim(\"Indexing all active projects...\"));\n\n const { projects, result } = await indexAll(federation, registryDb);\n\n console.log(\n ok(`Done.`) +\n ` ${bold(String(projects))} projects` +\n `, ${bold(String(result.filesProcessed))} files indexed` +\n `, ${bold(String(result.chunksCreated))} chunks created` +\n `, ${dim(String(result.filesSkipped) + \" skipped (unchanged)\")}`,\n );\n\n if (opts.embed) {\n await runEmbed(federation);\n }\n }\n });\n}\n","/** Memory search command: BM25/semantic/hybrid search across federation.db. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport chalk from \"chalk\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { searchMemory, populateSlugs, type SearchResult } from \"../../../memory/search.js\";\nimport { dim, bold, ok, warn, err } from \"../../utils.js\";\nimport { loadConfig } from \"../../../daemon/config.js\";\nimport { createStorageBackend } from \"../../../storage/factory.js\";\n\n// ---------------------------------------------------------------------------\n// Helper\n// ---------------------------------------------------------------------------\n\nfunction tierColor(tier: string): string {\n switch (tier) {\n case \"evergreen\": return chalk.green(tier);\n case \"daily\": return chalk.yellow(tier);\n case \"topic\": return chalk.blue(tier);\n case \"session\": return chalk.dim(tier);\n default: return tier;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerSearchCommand(\n memoryCmd: Command,\n getDb: () => Database,\n): void {\n memoryCmd\n .command(\"search <query>\")\n .description(\"Search indexed memory (BM25 keyword, semantic, or hybrid)\")\n .option(\"--project <slug>\", \"Restrict search to a specific project\")\n .option(\"--source <source>\", \"Restrict to 'memory' or 'notes'\")\n .option(\"--limit <n>\", \"Maximum results to return\")\n .option(\"--mode <mode>\", \"Search mode: keyword (default), semantic, hybrid\")\n .option(\"--no-rerank\", \"Skip cross-encoder reranking (reranking is on by default)\")\n .option(\"--recency <days>\", \"Apply recency boost: score halves every N days. 0 = off\")\n .action(\n async (\n query: string,\n opts: { project?: string; source?: string; limit?: string; mode?: string; rerank: boolean; recency?: string },\n ) => {\n const registryDb = getDb();\n\n let federation: Database;\n try {\n federation = openFederation();\n } catch (e) {\n console.error(err(`Failed to open federation database: ${e}`));\n process.exit(1);\n }\n\n const config = loadConfig();\n const searchConfig = config.search;\n\n const maxResults = parseInt(opts.limit ?? String(searchConfig.defaultLimit), 10);\n const mode = (opts.mode ?? searchConfig.mode) as \"keyword\" | \"semantic\" | \"hybrid\";\n\n if (![\"keyword\", \"semantic\", \"hybrid\"].includes(mode)) {\n console.error(err(`Invalid mode: ${mode}. Use keyword, semantic, or hybrid.`));\n process.exit(1);\n }\n\n let projectIds: number[] | undefined;\n if (opts.project) {\n const project = registryDb\n .prepare(\"SELECT id FROM projects WHERE slug = ?\")\n .get(opts.project) as { id: number } | undefined;\n\n if (!project) {\n console.error(warn(`Project not found: ${opts.project} — searching all projects`));\n } else {\n projectIds = [project.id];\n }\n }\n\n const sources = opts.source ? [opts.source] : undefined;\n const searchOpts = { projectIds, sources, maxResults };\n\n let results: SearchResult[];\n\n if (mode === \"keyword\") {\n results = searchMemory(federation, query, searchOpts);\n\n } else if (mode === \"semantic\" || mode === \"hybrid\") {\n const backend = await createStorageBackend(config);\n\n try {\n const { generateEmbedding } = await import(\"../../../memory/embeddings.js\");\n\n console.log(dim(\"Generating query embedding...\"));\n const queryEmbedding = await generateEmbedding(query, true);\n\n if (mode === \"semantic\") {\n results = await backend.searchSemantic(queryEmbedding, searchOpts);\n } else {\n // Hybrid: combine keyword (BM25) and semantic results with min-max normalization\n const [keywordResults, semanticResults] = await Promise.all([\n backend.searchKeyword(query, { ...searchOpts, maxResults: 500 }),\n backend.searchSemantic(queryEmbedding, { ...searchOpts, maxResults: 500 }),\n ]);\n\n if (keywordResults.length === 0 && semanticResults.length === 0) {\n results = [];\n } else {\n const keyFor = (r: SearchResult) =>\n `${r.projectId}:${r.path}:${r.startLine}:${r.endLine}`;\n\n function minMaxNormalize(items: SearchResult[]): Map<string, number> {\n if (items.length === 0) return new Map();\n const min = Math.min(...items.map((r) => r.score));\n const max = Math.max(...items.map((r) => r.score));\n const range = max - min;\n const m = new Map<string, number>();\n for (const r of items) {\n m.set(keyFor(r), range === 0 ? 1 : (r.score - min) / range);\n }\n return m;\n }\n\n const kwNorm = minMaxNormalize(keywordResults);\n const semNorm = minMaxNormalize(semanticResults);\n const allKeys = new Set<string>([\n ...keywordResults.map(keyFor),\n ...semanticResults.map(keyFor),\n ]);\n const metaMap = new Map<string, SearchResult>();\n for (const r of [...keywordResults, ...semanticResults]) {\n metaMap.set(keyFor(r), r);\n }\n\n const combined: SearchResult[] = [];\n for (const key of allKeys) {\n const meta = metaMap.get(key)!;\n const combinedScore = 0.5 * (kwNorm.get(key) ?? 0) + 0.5 * (semNorm.get(key) ?? 0);\n combined.push({ ...meta, score: combinedScore });\n }\n\n results = combined.sort((a, b) => b.score - a.score).slice(0, maxResults);\n }\n }\n } finally {\n await backend.close();\n }\n } else {\n results = [];\n }\n\n if (!results || results.length === 0) {\n console.log(dim(`No results found for: \"${query}\" (mode: ${mode})`));\n return;\n }\n\n // Cross-encoder reranking (on by default, skip with --no-rerank)\n if (opts.rerank !== false) {\n const { rerankResults } = await import(\"../../../memory/reranker.js\");\n console.log(dim(\"Reranking with cross-encoder...\"));\n results = await rerankResults(query, results, { topK: maxResults });\n }\n\n // Recency boost (applied after reranking)\n const recencyDays = parseInt(opts.recency ?? String(searchConfig.recencyBoostDays), 10);\n if (recencyDays > 0) {\n const { applyRecencyBoost } = await import(\"../../../memory/search.js\");\n console.log(dim(`Applying recency boost (half-life: ${recencyDays} days)...`));\n results = applyRecencyBoost(results, recencyDays);\n }\n\n const withSlugs = populateSlugs(results, registryDb);\n const rerankLabel = opts.rerank !== false ? \" +rerank\" : \"\";\n const modeLabel = mode !== \"keyword\" ? ` [${mode}${rerankLabel}]` : (opts.rerank !== false ? ` [rerank]` : \"\");\n\n console.log(\n `\\n ${bold(`Search results for: \"${query}\"`)}${modeLabel} ${dim(`(${withSlugs.length} found)`)}\\n`,\n );\n\n for (const result of withSlugs) {\n const projectLabel = result.projectSlug\n ? chalk.cyan(result.projectSlug)\n : chalk.cyan(String(result.projectId));\n\n const tierLabel = tierColor(result.tier);\n const scoreLabel = dim(`score: ${result.score.toFixed(4)}`);\n const locationLabel = dim(`${result.path}:${result.startLine}-${result.endLine}`);\n\n console.log(` ${projectLabel} ${tierLabel} ${locationLabel} ${scoreLabel}`);\n\n const snippetLines = result.snippet\n .split(\"\\n\")\n .slice(0, 3)\n .map((l) => ` ${l}`);\n console.log(snippetLines.join(\"\\n\"));\n console.log();\n }\n },\n );\n}\n","/** Memory status and settings commands for the PAI memory index. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { renderTable, dim, bold, ok, err, warn, fmtDate } from \"../../utils.js\";\nimport { loadConfig, CONFIG_FILE, ensureConfigDir } from \"../../../daemon/config.js\";\n\nfunction tierColor(tier: string): string {\n switch (tier) {\n case \"evergreen\": return chalk.green(tier);\n case \"daily\": return chalk.yellow(tier);\n case \"topic\": return chalk.blue(tier);\n case \"session\": return chalk.dim(tier);\n default: return tier;\n }\n}\n\nexport function registerStatsCommands(\n memoryCmd: Command,\n getDb: () => Database,\n): void {\n\n // -------------------------------------------------------------------------\n // pai memory status [project-slug]\n // -------------------------------------------------------------------------\n\n memoryCmd\n .command(\"status [project-slug]\")\n .description(\"Show memory index statistics\")\n .action((projectSlug: string | undefined) => {\n const registryDb = getDb();\n\n let federation: Database;\n try {\n federation = openFederation();\n } catch (e) {\n console.error(err(`Failed to open federation database: ${e}`));\n process.exit(1);\n }\n\n if (projectSlug) {\n const project = registryDb\n .prepare(\"SELECT id, slug, display_name FROM projects WHERE slug = ?\")\n .get(projectSlug) as\n | { id: number; slug: string; display_name: string }\n | undefined;\n\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const fileStats = federation\n .prepare(\"SELECT COUNT(*) as files FROM memory_files WHERE project_id = ?\")\n .get(project.id) as { files: number };\n\n const chunkStats = federation\n .prepare(\"SELECT COUNT(*) as chunks FROM memory_chunks WHERE project_id = ?\")\n .get(project.id) as { chunks: number };\n\n const lastUpdate = federation\n .prepare(\"SELECT MAX(updated_at) as last_at FROM memory_chunks WHERE project_id = ?\")\n .get(project.id) as { last_at: number | null };\n\n const tierBreakdown = federation\n .prepare(\"SELECT tier, COUNT(*) as n FROM memory_chunks WHERE project_id = ? GROUP BY tier ORDER BY n DESC\")\n .all(project.id) as Array<{ tier: string; n: number }>;\n\n console.log(`\\n ${bold(project.display_name)} ${dim(`(${project.slug})`)}\\n`);\n console.log(` ${bold(\"Files indexed:\")} ${fileStats.files}`);\n console.log(` ${bold(\"Chunks:\")} ${chunkStats.chunks}`);\n console.log(` ${bold(\"Last indexed:\")} ${lastUpdate.last_at ? fmtDate(lastUpdate.last_at) : dim(\"never\")}`);\n\n if (tierBreakdown.length > 0) {\n console.log(`\\n ${bold(\"By tier:\")}`);\n for (const row of tierBreakdown) {\n console.log(` ${tierColor(row.tier).padEnd(20)} ${row.n} chunks`);\n }\n }\n console.log();\n\n } else {\n const globalFiles = federation.prepare(\"SELECT COUNT(*) as n FROM memory_files\").get() as { n: number };\n const globalChunks = federation.prepare(\"SELECT COUNT(*) as n FROM memory_chunks\").get() as { n: number };\n const lastUpdate = federation.prepare(\"SELECT MAX(updated_at) as last_at FROM memory_chunks\").get() as { last_at: number | null };\n\n const projectStats = federation\n .prepare(\n `SELECT\n mf.project_id,\n COUNT(DISTINCT mf.path) as files,\n COUNT(mc.id) as chunks,\n MAX(mc.updated_at) as last_at\n FROM memory_files mf\n LEFT JOIN memory_chunks mc ON mc.project_id = mf.project_id AND mc.path = mf.path\n GROUP BY mf.project_id\n ORDER BY chunks DESC`,\n )\n .all() as Array<{ project_id: number; files: number; chunks: number; last_at: number | null }>;\n\n const projectIds = projectStats.map((p) => p.project_id);\n const slugMap = new Map<number, string>();\n if (projectIds.length > 0) {\n const placeholders = projectIds.map(() => \"?\").join(\", \");\n const slugRows = registryDb\n .prepare(`SELECT id, slug, display_name FROM projects WHERE id IN (${placeholders})`)\n .all(...projectIds) as Array<{ id: number; slug: string; display_name: string }>;\n for (const row of slugRows) {\n slugMap.set(row.id, row.slug);\n }\n }\n\n console.log(`\\n ${bold(\"PAI Memory Index — Global Status\")}\\n`);\n console.log(` ${bold(\"Total files:\")} ${globalFiles.n} ${bold(\"Total chunks:\")} ${globalChunks.n}`);\n console.log(` ${bold(\"Last indexed:\")} ${lastUpdate.last_at ? fmtDate(lastUpdate.last_at) : dim(\"never\")}`);\n\n if (projectStats.length > 0) {\n console.log();\n const rows = projectStats.map((p) => [\n chalk.cyan(slugMap.get(p.project_id) ?? String(p.project_id)),\n String(p.files),\n String(p.chunks),\n p.last_at ? fmtDate(p.last_at) : dim(\"never\"),\n ]);\n console.log(renderTable([\"Project\", \"Files\", \"Chunks\", \"Last Indexed\"], rows));\n } else {\n console.log(dim(\"\\n No projects indexed yet. Run `pai memory index --all` to start.\"));\n }\n console.log();\n }\n });\n\n // -------------------------------------------------------------------------\n // pai memory settings [key] [value]\n // -------------------------------------------------------------------------\n\n memoryCmd\n .command(\"settings [key] [value]\")\n .description(\"View or modify search settings in ~/.config/pai/config.json\")\n .action((key: string | undefined, value: string | undefined) => {\n const config = loadConfig();\n const search = config.search;\n\n if (!key) {\n console.log(`\\n ${bold(\"PAI Memory — Search Settings\")}\\n`);\n console.log(` ${bold(\"mode:\")} ${search.mode}`);\n console.log(` ${bold(\"rerank:\")} ${search.rerank}`);\n console.log(` ${bold(\"recencyBoostDays:\")} ${search.recencyBoostDays}`);\n console.log(` ${bold(\"defaultLimit:\")} ${search.defaultLimit}`);\n console.log(` ${bold(\"snippetLength:\")} ${search.snippetLength}`);\n console.log();\n console.log(dim(` Config file: ${CONFIG_FILE}`));\n console.log(dim(` Edit directly or use: pai memory settings <key> <value>`));\n console.log();\n return;\n }\n\n if (!value) {\n const val = (search as unknown as Record<string, unknown>)[key];\n if (val === undefined) {\n console.error(err(`Unknown setting: ${key}`));\n console.log(dim(` Valid keys: mode, rerank, recencyBoostDays, defaultLimit, snippetLength`));\n process.exit(1);\n }\n console.log(String(val));\n return;\n }\n\n const validKeys = new Set([\"mode\", \"rerank\", \"recencyBoostDays\", \"defaultLimit\", \"snippetLength\"]);\n if (!validKeys.has(key)) {\n console.error(err(`Unknown setting: ${key}`));\n console.log(dim(` Valid keys: ${[...validKeys].join(\", \")}`));\n process.exit(1);\n }\n\n let fileConfig: Record<string, unknown> = {};\n if (existsSync(CONFIG_FILE)) {\n try {\n fileConfig = JSON.parse(readFileSync(CONFIG_FILE, \"utf-8\")) as Record<string, unknown>;\n } catch {\n console.error(err(`Could not parse ${CONFIG_FILE}`));\n process.exit(1);\n }\n }\n\n if (!fileConfig.search || typeof fileConfig.search !== \"object\") {\n fileConfig.search = {};\n }\n\n let parsed: string | number | boolean;\n if (key === \"mode\") {\n if (![\"keyword\", \"semantic\", \"hybrid\"].includes(value)) {\n console.error(err(`Invalid mode: ${value}. Must be keyword, semantic, or hybrid.`));\n process.exit(1);\n }\n parsed = value;\n } else if (key === \"rerank\") {\n parsed = value === \"true\" || value === \"1\" || value === \"on\";\n } else {\n parsed = parseInt(value, 10);\n if (isNaN(parsed)) {\n console.error(err(`Invalid number: ${value}`));\n process.exit(1);\n }\n }\n\n (fileConfig.search as Record<string, unknown>)[key] = parsed;\n\n try {\n ensureConfigDir();\n writeFileSync(CONFIG_FILE, JSON.stringify(fileConfig, null, 2) + \"\\n\", \"utf-8\");\n console.log(ok(`Set search.${key} = ${parsed}`));\n console.log(dim(` Restart daemon to apply: pai daemon restart`));\n } catch (e) {\n console.error(err(`Could not write config: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** Barrel: registers all memory sub-commands and re-exports registerMemoryCommands. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { registerIndexCommand } from \"./index-cmd.js\";\nimport { registerEmbedCommand } from \"./embed.js\";\nimport { registerSearchCommand } from \"./search.js\";\nimport { registerStatsCommands } from \"./stats.js\";\n\nexport function registerMemoryCommands(\n memoryCmd: Command,\n getDb: () => Database,\n): void {\n registerIndexCommand(memoryCmd, getDb);\n registerEmbedCommand(memoryCmd, getDb);\n registerSearchCommand(memoryCmd, getDb);\n registerStatsCommands(memoryCmd, getDb);\n}\n","/**\n * claude-json.ts — safe read/write for ~/.claude.json\n *\n * This file is not ours. It holds every MCP server registration plus Claude\n * Code's own per-project state — on a working machine, hundreds of kilobytes\n * that no one can reconstruct by hand. PAI only ever adds one key to it.\n *\n * The previous implementation (duplicated in mcp.ts and daemon.ts) did:\n *\n * try { return JSON.parse(read(path)); } catch { return {}; }\n *\n * followed by a full-file write. A malformed or transiently unreadable file\n * therefore became an empty object, and the next write replaced the user's\n * entire config with just PAI's entry. No error, no backup, exit code 0.\n *\n * The precondition is unlikely, which is exactly the problem: `pai setup` is\n * advertised as idempotent and safe to re-run, so it is what people run when\n * something is already broken — the state in which the file is most likely to\n * be malformed.\n */\n\nimport { existsSync, readFileSync, writeFileSync, copyFileSync, renameSync, unlinkSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const CLAUDE_JSON_PATH = join(homedir(), \".claude.json\");\nconst BACKUP_PATH = `${CLAUDE_JSON_PATH}.bak-pai`;\n\n/**\n * Read ~/.claude.json.\n *\n * A missing file is a legitimate first-run state and yields {}. Anything else\n * — malformed JSON, permissions, a half-written file — throws. Returning {}\n * for an unreadable file is what turns a read problem into data loss on the\n * next write.\n */\nexport function readClaudeJson(): Record<string, unknown> {\n if (!existsSync(CLAUDE_JSON_PATH)) return {};\n\n let raw: string;\n try {\n raw = readFileSync(CLAUDE_JSON_PATH, \"utf8\");\n } catch (e) {\n throw new Error(\n `Could not read ${CLAUDE_JSON_PATH}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — writing now would replace your MCP registrations with PAI's alone.`\n );\n }\n\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n throw new Error(\n `${CLAUDE_JSON_PATH} is not valid JSON: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — overwriting it would destroy every MCP server registration it holds.\\n` +\n `Fix the file, or move it aside and let Claude Code recreate it, then re-run this command.`\n );\n }\n}\n\n/**\n * Write ~/.claude.json, keeping a backup and never leaving it half-written.\n *\n * Writes to a temp file and renames: rename is atomic within a filesystem, so\n * a crash mid-write leaves the original intact rather than truncated. The\n * previous direct writeFileSync could truncate the file and then fail.\n */\nexport function writeClaudeJson(data: Record<string, unknown>): void {\n const serialized = JSON.stringify(data, null, 2) + \"\\n\";\n\n if (existsSync(CLAUDE_JSON_PATH)) {\n try {\n copyFileSync(CLAUDE_JSON_PATH, BACKUP_PATH);\n } catch (e) {\n throw new Error(\n `Could not back up ${CLAUDE_JSON_PATH} to ${BACKUP_PATH}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to write without a backup.`\n );\n }\n }\n\n const tmp = `${CLAUDE_JSON_PATH}.tmp-pai-${process.pid}`;\n try {\n writeFileSync(tmp, serialized, \"utf8\");\n renameSync(tmp, CLAUDE_JSON_PATH);\n } catch (e) {\n try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* best effort */ }\n throw new Error(\n `Failed to write ${CLAUDE_JSON_PATH}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `The original is unchanged${existsSync(BACKUP_PATH) ? `; a backup is at ${BACKUP_PATH}` : \"\"}.`\n );\n }\n}\n\n/** Where the pre-write backup is kept, for messages to the user. */\nexport function claudeJsonBackupPath(): string {\n return BACKUP_PATH;\n}\n","/**\n * pai mcp <sub-command>\n *\n * install — Register the PAI MCP server in ~/.claude.json\n * status — Show whether the PAI MCP server is registered and the binary exists\n */\n\nimport type { Command } from \"commander\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { readClaudeJson, writeClaudeJson, CLAUDE_JSON_PATH } from \"../../config/claude-json.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\n// CLAUDE_JSON_PATH now imported from src/config/claude-json.ts\n\n/**\n * Resolve the absolute path to the built MCP entry point.\n *\n * tsdown bundles all CLI commands into a single dist/cli/index.mjs file, so\n * import.meta.url always resolves to dist/cli/index.mjs at runtime.\n * From dist/cli/ we go up one level to dist/ and then into mcp/index.mjs.\n */\nfunction getMcpBinPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // dist/cli/index.mjs → dist/ → dist/mcp/index.mjs\n return join(__dirname, \"../mcp/index.mjs\");\n}\n\n// ---------------------------------------------------------------------------\n// Read / write ~/.claude.json safely\n// ---------------------------------------------------------------------------\n\n// Moved to src/config/claude-json.ts — these were duplicated here and in\n// daemon.ts, and both copies silently turned an unreadable file into {},\n// which the following write then made permanent.\n\n// ---------------------------------------------------------------------------\n// install\n// ---------------------------------------------------------------------------\n\nfunction cmdInstall(): void {\n const mcpBin = getMcpBinPath();\n\n const config = readClaudeJson();\n\n // Ensure mcpServers key exists\n if (typeof config.mcpServers !== \"object\" || config.mcpServers === null) {\n config.mcpServers = {};\n }\n\n const servers = config.mcpServers as Record<string, unknown>;\n\n if (\"pai\" in servers) {\n console.log(warn(\"PAI MCP server is already registered in ~/.claude.json.\"));\n console.log(dim(` Entry: ${JSON.stringify(servers[\"pai\"])}`));\n console.log(dim(\" Use `pai mcp status` to verify the configuration.\"));\n return;\n }\n\n servers[\"pai\"] = {\n command: \"node\",\n args: [mcpBin],\n };\n\n try {\n writeClaudeJson(config);\n } catch (e) {\n console.error(err(`Failed to write ~/.claude.json: ${e}`));\n process.exit(1);\n }\n\n console.log(ok(\"PAI MCP server registered in ~/.claude.json.\"));\n console.log(dim(` Binary: ${mcpBin}`));\n console.log(dim(\"\"));\n console.log(dim(\" Restart Claude Code to activate the PAI MCP tools:\"));\n console.log(dim(\" memory_search, memory_get, project_info,\"));\n console.log(dim(\" project_list, session_list, registry_search\"));\n\n if (!existsSync(mcpBin)) {\n console.log();\n console.log(\n warn(` Note: MCP binary not found at ${mcpBin}`)\n );\n console.log(dim(\" Run `bun run build` to compile it first.\"));\n }\n}\n\n// ---------------------------------------------------------------------------\n// status\n// ---------------------------------------------------------------------------\n\nfunction cmdStatus(): void {\n const mcpBin = getMcpBinPath();\n const config = readClaudeJson();\n\n const servers =\n typeof config.mcpServers === \"object\" && config.mcpServers !== null\n ? (config.mcpServers as Record<string, unknown>)\n : {};\n\n const registered = \"pai\" in servers;\n const binExists = existsSync(mcpBin);\n\n console.log();\n console.log(bold(\" PAI MCP Server Status\"));\n console.log();\n\n if (registered) {\n const entry = servers[\"pai\"];\n console.log(ok(` Registered in ~/.claude.json`));\n console.log(dim(` Config: ${JSON.stringify(entry)}`));\n } else {\n console.log(warn(` NOT registered in ~/.claude.json`));\n console.log(dim(` Run: pai mcp install`));\n }\n\n console.log();\n\n if (binExists) {\n console.log(ok(` MCP binary found: ${mcpBin}`));\n } else {\n console.log(warn(` MCP binary NOT found: ${mcpBin}`));\n console.log(dim(\" Run: bun run build\"));\n }\n\n console.log();\n\n if (registered && binExists) {\n console.log(dim(\" Status: READY — restart Claude Code to use PAI tools\"));\n } else if (registered && !binExists) {\n console.log(warn(\" Status: NEEDS BUILD — run `bun run build`\"));\n } else {\n console.log(dim(\" Status: NOT INSTALLED — run `pai mcp install`\"));\n }\n\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerMcpCommands(mcpCmd: Command): void {\n mcpCmd\n .command(\"install\")\n .description(\n \"Register the PAI MCP server in ~/.claude.json (restart Claude Code to activate)\"\n )\n .action(() => {\n cmdInstall();\n });\n\n mcpCmd\n .command(\"status\")\n .description(\n \"Show whether the PAI MCP server is registered and the binary exists\"\n )\n .action(() => {\n cmdStatus();\n });\n}\n","/**\n * pai daemon <sub-command>\n *\n * serve — Start the PAI daemon in the foreground\n * status — Query daemon status via IPC\n * restart — Send SIGTERM to running daemon (launchd will restart it)\n * install — Write launchd plist + update ~/.claude.json to use the shim\n * uninstall — Remove launchd plist + revert ~/.claude.json to direct MCP\n * logs — Tail the daemon log file\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n unlinkSync,\n mkdirSync,\n} from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { execSync, spawnSync } from \"node:child_process\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { PaiClient } from \"../../daemon/ipc-client.js\";\nimport { readClaudeJson, writeClaudeJson, CLAUDE_JSON_PATH } from \"../../config/claude-json.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\n// CLAUDE_JSON_PATH now imported from src/config/claude-json.ts\nconst PLIST_LABEL = \"com.pai.pai-daemon\";\nconst LAUNCH_AGENTS_DIR = join(HOME, \"Library\", \"LaunchAgents\");\nconst PLIST_PATH = join(LAUNCH_AGENTS_DIR, `${PLIST_LABEL}.plist`);\nconst DAEMON_LOG = \"/tmp/pai-daemon.log\";\n\n/**\n * Resolve the absolute path to the built daemon entry point.\n * tsdown bundles into dist/daemon/index.mjs (or similar).\n * From dist/cli/index.mjs → dist/ → dist/daemon/index.mjs\n */\nfunction getDaemonBinPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return join(__dirname, \"../daemon/index.mjs\");\n}\n\n/**\n * Resolve the absolute path to the built MCP shim entry point.\n * dist/cli/index.mjs → dist/ → dist/daemon-mcp/index.mjs\n */\nfunction getShimBinPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return join(__dirname, \"../daemon-mcp/index.mjs\");\n}\n\n// ---------------------------------------------------------------------------\n// claude.json helpers\n// ---------------------------------------------------------------------------\n\n// Moved to src/config/claude-json.ts — see the note there on why returning {}\n// from a failed read was a data-loss bug rather than a convenience.\n\n// ---------------------------------------------------------------------------\n// launchd plist generation\n// ---------------------------------------------------------------------------\n\nfunction generatePlist(daemonBin: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>/usr/local/bin/node</string>\n <string>${daemonBin}</string>\n <string>serve</string>\n </array>\n\n <key>KeepAlive</key>\n <true/>\n\n <key>ThrottleInterval</key>\n <integer>3</integer>\n\n <key>StandardOutPath</key>\n <string>${DAEMON_LOG}</string>\n\n <key>StandardErrorPath</key>\n <string>${DAEMON_LOG}</string>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>EnvironmentVariables</key>\n <dict>\n <key>PATH</key>\n <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>\n </dict>\n</dict>\n</plist>\n`;\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nasync function cmdStatus(): Promise<void> {\n const config = loadConfig();\n const client = new PaiClient(config.socketPath);\n\n try {\n const status = await client.status();\n const s = status as Record<string, unknown>;\n\n console.log();\n console.log(bold(\" PAI Daemon Status\"));\n console.log();\n console.log(ok(` Daemon running`));\n console.log(dim(` Uptime: ${s[\"uptime\"]}s`));\n console.log(dim(` Socket: ${s[\"socketPath\"]}`));\n console.log(\n dim(\n ` Index: ${s[\"indexInProgress\"] ? \"in progress\" : \"idle\"} (interval: ${s[\"indexIntervalSecs\"]}s)`\n )\n );\n if (s[\"lastIndexTime\"]) {\n console.log(dim(` Last index: ${s[\"lastIndexTime\"]}`));\n }\n if (s[\"db\"]) {\n const db = s[\"db\"] as Record<string, unknown>;\n console.log(\n dim(\n ` DB: ${db[\"projects\"]} projects, ${db[\"files\"]} files, ${db[\"chunks\"]} chunks`\n )\n );\n }\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.log();\n console.log(warn(\" PAI Daemon Status\"));\n console.log();\n console.log(warn(` Daemon not running: ${msg}`));\n console.log(dim(\" Start with: pai daemon serve\"));\n console.log(dim(\" Or install as service: pai daemon install\"));\n console.log();\n process.exit(1);\n }\n}\n\nfunction cmdRestart(): void {\n // Find and signal the running daemon\n try {\n const result = spawnSync(\"pgrep\", [\"-f\", \"pai-daemon.*serve\"], {\n encoding: \"utf8\",\n });\n\n if (result.status !== 0 || !result.stdout.trim()) {\n console.log(warn(\"No running pai-daemon process found.\"));\n\n // If launchd is managing it, kick it via launchctl\n const unloadResult = spawnSync(\n \"launchctl\",\n [\"kickstart\", \"-k\", `gui/${process.getuid?.() ?? 501}/${PLIST_LABEL}`],\n { encoding: \"utf8\" }\n );\n if (unloadResult.status === 0) {\n console.log(ok(\"Sent kickstart to launchd.\"));\n } else {\n console.log(dim(\"Not managed by launchd either. Run: pai daemon serve\"));\n }\n return;\n }\n\n const pids = result.stdout\n .trim()\n .split(\"\\n\")\n .map((p) => p.trim());\n for (const pid of pids) {\n try {\n process.kill(parseInt(pid, 10), \"SIGTERM\");\n console.log(ok(`Sent SIGTERM to pid ${pid}.`));\n } catch {\n console.log(warn(`Could not signal pid ${pid}.`));\n }\n }\n console.log(dim(\"launchd will restart the daemon automatically.\"));\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(`restart error: ${msg}`));\n process.exit(1);\n }\n}\n\nfunction cmdInstall(): void {\n const daemonBin = getDaemonBinPath();\n const shimBin = getShimBinPath();\n\n console.log();\n console.log(bold(\" PAI Daemon Install\"));\n console.log();\n\n // 1. Write launchd plist\n if (!existsSync(LAUNCH_AGENTS_DIR)) {\n mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });\n }\n\n const plistContent = generatePlist(daemonBin);\n try {\n writeFileSync(PLIST_PATH, plistContent, \"utf8\");\n console.log(ok(` Wrote launchd plist: ${PLIST_PATH}`));\n } catch (e) {\n console.error(err(` Failed to write plist: ${e}`));\n process.exit(1);\n }\n\n // 2. Load the plist (unload first in case it was already there)\n try {\n spawnSync(\"launchctl\", [\"unload\", PLIST_PATH], { encoding: \"utf8\" });\n const loadResult = spawnSync(\"launchctl\", [\"load\", PLIST_PATH], {\n encoding: \"utf8\",\n });\n if (loadResult.status === 0) {\n console.log(ok(\" Loaded plist with launchctl.\"));\n } else {\n console.log(warn(` launchctl load: ${loadResult.stderr?.trim() ?? \"unknown error\"}`));\n }\n } catch {\n console.log(warn(\" Could not run launchctl. Load manually:\"));\n console.log(dim(` launchctl load ${PLIST_PATH}`));\n }\n\n // 3. Update ~/.claude.json to use the shim\n const config = readClaudeJson();\n\n if (typeof config.mcpServers !== \"object\" || config.mcpServers === null) {\n config.mcpServers = {};\n }\n\n const servers = config.mcpServers as Record<string, unknown>;\n\n // Check if already pointing to the shim\n const existing = servers[\"pai\"] as Record<string, unknown> | undefined;\n const existingArgs = existing?.args as string[] | undefined;\n const alreadyShim = existingArgs?.includes(shimBin);\n\n if (alreadyShim) {\n console.log(ok(\" ~/.claude.json already points to the shim.\"));\n } else {\n // Back up the existing entry if any\n if (existing) {\n servers[\"pai-legacy\"] = existing;\n console.log(dim(\" Backed up existing 'pai' entry as 'pai-legacy'.\"));\n }\n\n servers[\"pai\"] = {\n command: \"node\",\n args: [shimBin],\n };\n\n try {\n writeClaudeJson(config);\n console.log(ok(\" Updated ~/.claude.json to use daemon shim.\"));\n } catch (e) {\n console.error(err(` Failed to write ~/.claude.json: ${e}`));\n process.exit(1);\n }\n }\n\n // 4. Verify binaries exist\n console.log();\n if (!existsSync(daemonBin)) {\n console.log(warn(` Daemon binary not found: ${daemonBin}`));\n console.log(dim(\" Run: bun run build\"));\n } else {\n console.log(ok(` Daemon binary: ${daemonBin}`));\n }\n\n if (!existsSync(shimBin)) {\n console.log(warn(` Shim binary not found: ${shimBin}`));\n console.log(dim(\" Run: bun run build\"));\n } else {\n console.log(ok(` Shim binary: ${shimBin}`));\n }\n\n console.log();\n console.log(dim(\" Restart Claude Code to activate the daemon-backed PAI tools.\"));\n console.log();\n}\n\nfunction cmdUninstall(): void {\n console.log();\n console.log(bold(\" PAI Daemon Uninstall\"));\n console.log();\n\n // 1. Unload and remove plist\n if (existsSync(PLIST_PATH)) {\n try {\n spawnSync(\"launchctl\", [\"unload\", PLIST_PATH], { encoding: \"utf8\" });\n console.log(ok(\" Unloaded launchd plist.\"));\n } catch {\n console.log(warn(\" Could not unload plist via launchctl.\"));\n }\n try {\n unlinkSync(PLIST_PATH);\n console.log(ok(` Removed plist: ${PLIST_PATH}`));\n } catch (e) {\n console.log(warn(` Could not remove plist: ${e}`));\n }\n } else {\n console.log(dim(\" No launchd plist found.\"));\n }\n\n // 2. Revert ~/.claude.json to legacy direct MCP\n const config = readClaudeJson();\n const servers =\n typeof config.mcpServers === \"object\" && config.mcpServers !== null\n ? (config.mcpServers as Record<string, unknown>)\n : {};\n\n const legacy = servers[\"pai-legacy\"];\n if (legacy) {\n servers[\"pai\"] = legacy;\n delete servers[\"pai-legacy\"];\n try {\n writeClaudeJson(config);\n console.log(ok(\" Reverted ~/.claude.json to legacy direct MCP.\"));\n } catch (e) {\n console.log(warn(` Could not update ~/.claude.json: ${e}`));\n }\n } else {\n console.log(dim(\" No legacy PAI entry found in ~/.claude.json.\"));\n }\n\n console.log();\n console.log(dim(\" Restart Claude Code to deactivate daemon-backed tools.\"));\n console.log();\n}\n\nasync function cmdMigrate(connectionString?: string): Promise<void> {\n console.log();\n console.log(bold(\" PAI Daemon Migrate\"));\n console.log();\n console.log(dim(\" Running SQLite → PostgreSQL migration...\"));\n console.log();\n\n // Resolve the migration script path relative to this built file\n const { fileURLToPath } = await import(\"node:url\");\n const { dirname: pathDirname, join: pathJoin } = await import(\"node:path\");\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = pathDirname(__filename);\n\n // The migration script is at docker/migrate-sqlite.ts in the source tree\n // When built, we look for it relative to the package root\n const migrationScript = pathJoin(__dirname, \"../..\", \"docker\", \"migrate-sqlite.ts\");\n const { spawnSync: spawn } = await import(\"node:child_process\");\n\n // Use npx tsx to run the TypeScript migration script (bun doesn't support better-sqlite3)\n const spawnArgs = [\"tsx\", migrationScript];\n if (connectionString) {\n spawnArgs.push(\"--connection-string\", connectionString);\n }\n\n const result = spawn(\"npx\", spawnArgs, { stdio: \"inherit\", encoding: \"utf8\" });\n\n if (result.status !== 0) {\n console.error(err(\" Migration failed. Check output above.\"));\n process.exit(result.status ?? 1);\n }\n}\n\nfunction cmdLogs(opts: { lines?: string; follow?: boolean }): void {\n const lines = opts.lines ?? \"50\";\n\n if (!existsSync(DAEMON_LOG)) {\n console.log(warn(`No daemon log found at ${DAEMON_LOG}.`));\n console.log(dim(\"The daemon may not have run yet.\"));\n return;\n }\n\n if (opts.follow) {\n // exec stays running\n try {\n execSync(`tail -f -n ${lines} \"${DAEMON_LOG}\"`, { stdio: \"inherit\" });\n } catch {\n // User pressed Ctrl+C — that's fine\n }\n } else {\n try {\n execSync(`tail -n ${lines} \"${DAEMON_LOG}\"`, { stdio: \"inherit\" });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(`Could not read log: ${msg}`));\n process.exit(1);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerDaemonCommands(daemonCmd: Command): void {\n daemonCmd\n .command(\"serve\")\n .description(\"Start the PAI daemon in the foreground\")\n .action(async () => {\n const { serve } = await import(\"../../daemon/daemon.js\");\n const { loadConfig: lc, ensureConfigDir } = await import(\n \"../../daemon/config.js\"\n );\n ensureConfigDir();\n const config = lc();\n await serve(config);\n });\n\n daemonCmd\n .command(\"migrate\")\n .description(\"Migrate federation data from SQLite to PostgreSQL\")\n .option(\n \"--connection-string <url>\",\n \"Postgres connection string (default: from config or postgresql://pai:pai@localhost:5432/pai)\"\n )\n .action(async (opts: { connectionString?: string }) => {\n await cmdMigrate(opts.connectionString);\n });\n\n daemonCmd\n .command(\"status\")\n .description(\"Query the running daemon status\")\n .action(async () => {\n await cmdStatus();\n });\n\n daemonCmd\n .command(\"restart\")\n .description(\"Send SIGTERM to the running daemon (launchd will restart it)\")\n .action(() => {\n cmdRestart();\n });\n\n daemonCmd\n .command(\"install\")\n .description(\n \"Install daemon as a launchd service and update ~/.claude.json to use the shim\"\n )\n .action(() => {\n cmdInstall();\n });\n\n daemonCmd\n .command(\"uninstall\")\n .description(\"Remove the launchd service and revert to direct MCP\")\n .action(() => {\n cmdUninstall();\n });\n\n daemonCmd\n .command(\"logs\")\n .description(`Tail the daemon log (${DAEMON_LOG})`)\n .option(\"-n, --lines <n>\", \"Number of lines to show\", \"50\")\n .option(\"-f, --follow\", \"Follow log output (like tail -f)\")\n .action((opts: { lines?: string; follow?: boolean }) => {\n cmdLogs(opts);\n });\n}\n","/**\n * pai backup — snapshot registry, config, and Postgres database.\n *\n * Creates a timestamped backup directory at:\n * ~/.pai/backups/YYYY-MM-DD-HHmmss/\n *\n * Contents:\n * registry.db — SQLite registry database\n * config.json — PAI daemon config\n * postgres-pai.sql — pg_dump of the Postgres \"pai\" database (via docker exec)\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n mkdirSync,\n copyFileSync,\n statSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execSync } from \"node:child_process\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\nconst REGISTRY_DB = join(HOME, \".pai\", \"registry.db\");\nconst CONFIG_FILE = join(HOME, \".config\", \"pai\", \"config.json\");\nconst BACKUPS_DIR = join(HOME, \".pai\", \"backups\");\nconst DOCKER_CONTAINER = \"pai-pgvector\";\nconst PG_DATABASE = \"pai\";\nconst PG_USER = \"pai\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction timestamp(): string {\n const now = new Date();\n const YYYY = now.getFullYear();\n const MM = String(now.getMonth() + 1).padStart(2, \"0\");\n const DD = String(now.getDate()).padStart(2, \"0\");\n const HH = String(now.getHours()).padStart(2, \"0\");\n const mm = String(now.getMinutes()).padStart(2, \"0\");\n const ss = String(now.getSeconds()).padStart(2, \"0\");\n return `${YYYY}-${MM}-${DD}-${HH}${mm}${ss}`;\n}\n\nfunction fmtBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction fileSize(path: string): string {\n try {\n return fmtBytes(statSync(path).size);\n } catch {\n return \"unknown\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerBackupCommands(program: Command): void {\n program\n .command(\"backup\")\n .description(\"Backup registry, config, and Postgres database to ~/.pai/backups/\")\n .option(\"--no-postgres\", \"Skip the Postgres pg_dump (faster, registry+config only)\")\n .action(async (opts: { postgres: boolean }) => {\n const ts = timestamp();\n const backupDir = join(BACKUPS_DIR, ts);\n\n console.log(dim(`Creating backup: ${backupDir}`));\n mkdirSync(backupDir, { recursive: true });\n\n const results: { label: string; path: string; size: string; status: string }[] = [];\n\n // ------------------------------------------------------------------\n // 1. Registry SQLite DB\n // ------------------------------------------------------------------\n\n if (existsSync(REGISTRY_DB)) {\n const dest = join(backupDir, \"registry.db\");\n try {\n copyFileSync(REGISTRY_DB, dest);\n results.push({ label: \"Registry DB\", path: dest, size: fileSize(dest), status: ok(\"ok\") });\n } catch (e) {\n results.push({ label: \"Registry DB\", path: dest, size: \"-\", status: err(`failed: ${e}`) });\n }\n } else {\n results.push({ label: \"Registry DB\", path: REGISTRY_DB, size: \"-\", status: warn(\"not found — skipped\") });\n }\n\n // ------------------------------------------------------------------\n // 2. Config file\n // ------------------------------------------------------------------\n\n if (existsSync(CONFIG_FILE)) {\n const dest = join(backupDir, \"config.json\");\n try {\n copyFileSync(CONFIG_FILE, dest);\n results.push({ label: \"Config\", path: dest, size: fileSize(dest), status: ok(\"ok\") });\n } catch (e) {\n results.push({ label: \"Config\", path: dest, size: \"-\", status: err(`failed: ${e}`) });\n }\n } else {\n results.push({ label: \"Config\", path: CONFIG_FILE, size: \"-\", status: warn(\"not found — skipped\") });\n }\n\n // ------------------------------------------------------------------\n // 3. Optional: Federation SQLite (legacy)\n // ------------------------------------------------------------------\n\n const federationDb = join(HOME, \".pai\", \"federation.db\");\n if (existsSync(federationDb)) {\n const dest = join(backupDir, \"federation.db\");\n try {\n copyFileSync(federationDb, dest);\n results.push({ label: \"Federation DB (legacy)\", path: dest, size: fileSize(dest), status: ok(\"ok\") });\n } catch (e) {\n results.push({ label: \"Federation DB (legacy)\", path: dest, size: \"-\", status: warn(`skipped: ${e}`) });\n }\n }\n\n // ------------------------------------------------------------------\n // 4. Postgres pg_dump via docker exec\n // ------------------------------------------------------------------\n\n if (opts.postgres) {\n const sqlDest = join(backupDir, \"postgres-pai.sql\");\n console.log(dim(` Running pg_dump on ${DOCKER_CONTAINER} (this may take a moment)...`));\n try {\n // Check Docker is running and container exists\n execSync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Status}}'`, {\n stdio: \"pipe\",\n });\n\n execSync(\n `docker exec ${DOCKER_CONTAINER} pg_dump -U ${PG_USER} ${PG_DATABASE} > \"${sqlDest}\"`,\n { stdio: [\"pipe\", \"pipe\", \"pipe\"], shell: true as unknown as string }\n );\n results.push({ label: \"Postgres DB\", path: sqlDest, size: fileSize(sqlDest), status: ok(\"ok\") });\n } catch (e) {\n const msg = e instanceof Error ? e.message.split(\"\\n\")[0] : String(e);\n results.push({ label: \"Postgres DB\", path: sqlDest, size: \"-\", status: err(`failed: ${msg}`) });\n console.log(warn(` Postgres backup failed. Is Docker running with container '${DOCKER_CONTAINER}'?`));\n }\n } else {\n results.push({ label: \"Postgres DB\", path: \"-\", size: \"-\", status: dim(\"skipped (--no-postgres)\") });\n }\n\n // ------------------------------------------------------------------\n // Summary\n // ------------------------------------------------------------------\n\n console.log(`\\n${bold(\"Backup complete:\")} ${backupDir}\\n`);\n\n const labelWidth = Math.max(...results.map((r) => r.label.length)) + 2;\n for (const r of results) {\n const label = r.label.padEnd(labelWidth);\n console.log(` ${bold(label)} ${r.status} ${dim(r.size)}`);\n }\n\n console.log(`\\n ${dim(\"Path:\")} ${backupDir}`);\n console.log(` ${dim(\"To restore:\")} pai restore ${backupDir}\\n`);\n });\n}\n","/**\n * pai restore [path] — restore from a backup created by `pai backup`.\n *\n * If no path is given, lists available backups and restores the latest one\n * after prompting for confirmation.\n *\n * Restores:\n * registry.db — SQLite registry database\n * config.json — PAI daemon config\n * postgres-pai.sql — Postgres dump (piped into psql via docker exec)\n * federation.db — Legacy SQLite federation DB (if present)\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n readdirSync,\n statSync,\n copyFileSync,\n mkdirSync,\n readFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execSync, spawnSync } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\nconst REGISTRY_DB = join(HOME, \".pai\", \"registry.db\");\nconst CONFIG_FILE = join(HOME, \".config\", \"pai\", \"config.json\");\nconst BACKUPS_DIR = join(HOME, \".pai\", \"backups\");\nconst DOCKER_CONTAINER = \"pai-pgvector\";\nconst PG_DATABASE = \"pai\";\nconst PG_USER = \"pai\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction confirm(question: string): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(`${question} [y/N] `, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === \"y\");\n });\n });\n}\n\nfunction listBackups(): string[] {\n if (!existsSync(BACKUPS_DIR)) return [];\n return readdirSync(BACKUPS_DIR)\n .filter((name) => {\n const full = join(BACKUPS_DIR, name);\n return statSync(full).isDirectory() && /^\\d{4}-\\d{2}-\\d{2}-\\d{6}$/.test(name);\n })\n .sort()\n .reverse(); // newest first\n}\n\nfunction formatBackupDate(name: string): string {\n // YYYY-MM-DD-HHmmss → \"YYYY-MM-DD HH:mm:ss\"\n const m = name.match(/^(\\d{4}-\\d{2}-\\d{2})-(\\d{2})(\\d{2})(\\d{2})$/);\n if (!m) return name;\n return `${m[1]} ${m[2]}:${m[3]}:${m[4]}`;\n}\n\nfunction backupContents(backupDir: string): string[] {\n try {\n return readdirSync(backupDir);\n } catch {\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerRestoreCommands(program: Command): void {\n program\n .command(\"restore [backup-path]\")\n .description(\"Restore from a backup directory (created by pai backup)\")\n .option(\"--no-postgres\", \"Skip restoring the Postgres database\")\n .option(\"--yes\", \"Skip confirmation prompt (non-interactive)\")\n .action(async (backupPath: string | undefined, opts: { postgres: boolean; yes: boolean }) => {\n\n // ------------------------------------------------------------------\n // 1. Determine backup directory\n // ------------------------------------------------------------------\n\n let resolvedDir: string;\n\n if (backupPath) {\n resolvedDir = backupPath.startsWith(\"~\")\n ? backupPath.replace(/^~/, HOME)\n : backupPath;\n\n if (!existsSync(resolvedDir)) {\n console.error(err(`Backup directory not found: ${resolvedDir}`));\n process.exit(1);\n }\n } else {\n // No path given — pick from available backups\n const backups = listBackups();\n if (backups.length === 0) {\n console.error(err(`No backups found in ${BACKUPS_DIR}`));\n console.log(dim(\" Run 'pai backup' to create one.\"));\n process.exit(1);\n }\n\n console.log(`\\n${bold(\"Available backups\")} (newest first):\\n`);\n backups.forEach((name, i) => {\n const dir = join(BACKUPS_DIR, name);\n const contents = backupContents(dir).join(\", \");\n const marker = i === 0 ? ok(\" ← latest\") : \"\";\n console.log(` ${bold(String(i + 1).padStart(2))}. ${formatBackupDate(name)}${marker}`);\n console.log(` ${dim(dir)}`);\n console.log(` ${dim(\"Contents:\")} ${contents}`);\n });\n\n console.log();\n resolvedDir = join(BACKUPS_DIR, backups[0]);\n console.log(dim(`Using latest backup: ${resolvedDir}\\n`));\n }\n\n // ------------------------------------------------------------------\n // 2. Inventory what's in the backup\n // ------------------------------------------------------------------\n\n const hasRegistry = existsSync(join(resolvedDir, \"registry.db\"));\n const hasConfig = existsSync(join(resolvedDir, \"config.json\"));\n const hasSql = existsSync(join(resolvedDir, \"postgres-pai.sql\"));\n const hasFed = existsSync(join(resolvedDir, \"federation.db\"));\n\n console.log(`${bold(\"Backup contents:\")}`);\n console.log(` registry.db ${hasRegistry ? ok(\"present\") : warn(\"missing\")}`);\n console.log(` config.json ${hasConfig ? ok(\"present\") : warn(\"missing\")}`);\n console.log(` postgres-pai.sql ${hasSql && opts.postgres ? ok(\"present\") : hasSql ? warn(\"present (skipped via --no-postgres)\") : warn(\"missing\")}`);\n if (hasFed) {\n console.log(` federation.db ${ok(\"present\")} ${dim(\"(legacy)\")}`);\n }\n\n // ------------------------------------------------------------------\n // 3. Confirm\n // ------------------------------------------------------------------\n\n console.log(`\\n${warn(\"WARNING:\")} This will OVERWRITE your current PAI data.`);\n if (hasSql && opts.postgres) {\n console.log(warn(\" Postgres database will be dropped and recreated from the backup.\"));\n }\n\n const proceed = opts.yes || await confirm(\"Proceed with restore?\");\n if (!proceed) {\n console.log(dim(\"Restore cancelled.\"));\n process.exit(0);\n }\n\n console.log();\n\n const results: { label: string; status: string }[] = [];\n\n // ------------------------------------------------------------------\n // 4. Restore registry.db\n // ------------------------------------------------------------------\n\n if (hasRegistry) {\n try {\n mkdirSync(join(HOME, \".pai\"), { recursive: true });\n copyFileSync(join(resolvedDir, \"registry.db\"), REGISTRY_DB);\n results.push({ label: \"Registry DB\", status: ok(\"restored\") });\n } catch (e) {\n results.push({ label: \"Registry DB\", status: err(`failed: ${e}`) });\n }\n } else {\n results.push({ label: \"Registry DB\", status: warn(\"missing in backup — skipped\") });\n }\n\n // ------------------------------------------------------------------\n // 5. Restore config.json\n // ------------------------------------------------------------------\n\n if (hasConfig) {\n try {\n mkdirSync(join(HOME, \".config\", \"pai\"), { recursive: true });\n copyFileSync(join(resolvedDir, \"config.json\"), CONFIG_FILE);\n results.push({ label: \"Config\", status: ok(\"restored\") });\n } catch (e) {\n results.push({ label: \"Config\", status: err(`failed: ${e}`) });\n }\n } else {\n results.push({ label: \"Config\", status: warn(\"missing in backup — skipped\") });\n }\n\n // ------------------------------------------------------------------\n // 6. Restore federation.db (legacy)\n // ------------------------------------------------------------------\n\n if (hasFed) {\n try {\n copyFileSync(join(resolvedDir, \"federation.db\"), join(HOME, \".pai\", \"federation.db\"));\n results.push({ label: \"Federation DB (legacy)\", status: ok(\"restored\") });\n } catch (e) {\n results.push({ label: \"Federation DB (legacy)\", status: warn(`skipped: ${e}`) });\n }\n }\n\n // ------------------------------------------------------------------\n // 7. Restore Postgres via docker exec\n // ------------------------------------------------------------------\n\n if (hasSql && opts.postgres) {\n console.log(dim(\" Restoring Postgres database (this may take a while)...\"));\n try {\n // Verify container is running\n execSync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Status}}'`, {\n stdio: \"pipe\",\n });\n\n // Drop and recreate the database, then restore\n const dropCreate = `docker exec ${DOCKER_CONTAINER} psql -U ${PG_USER} -c \"DROP DATABASE IF EXISTS ${PG_DATABASE}; CREATE DATABASE ${PG_DATABASE} OWNER ${PG_USER};\"`;\n execSync(dropCreate, { stdio: \"pipe\", shell: true as unknown as string });\n\n // Pipe the SQL dump into psql\n const sqlContent = readFileSync(join(resolvedDir, \"postgres-pai.sql\"), \"utf8\");\n const psqlResult = spawnSync(\n \"docker\",\n [\"exec\", \"-i\", DOCKER_CONTAINER, \"psql\", \"-U\", PG_USER, PG_DATABASE],\n { input: sqlContent, encoding: \"utf8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }\n );\n\n if (psqlResult.status !== 0) {\n const errMsg = psqlResult.stderr?.split(\"\\n\")[0] ?? \"unknown error\";\n results.push({ label: \"Postgres DB\", status: err(`failed: ${errMsg}`) });\n } else {\n results.push({ label: \"Postgres DB\", status: ok(\"restored\") });\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message.split(\"\\n\")[0] : String(e);\n results.push({ label: \"Postgres DB\", status: err(`failed: ${msg}`) });\n console.log(warn(` Is Docker running with container '${DOCKER_CONTAINER}'?`));\n }\n } else if (!hasSql || !opts.postgres) {\n const reason = !hasSql ? \"no SQL dump in backup\" : \"--no-postgres\";\n results.push({ label: \"Postgres DB\", status: dim(`skipped (${reason})`) });\n }\n\n // ------------------------------------------------------------------\n // Summary\n // ------------------------------------------------------------------\n\n console.log(`\\n${bold(\"Restore complete:\")}\\n`);\n const labelWidth = Math.max(...results.map((r) => r.label.length)) + 2;\n for (const r of results) {\n console.log(` ${bold(r.label.padEnd(labelWidth))} ${r.status}`);\n }\n\n const hasErrors = results.some((r) => r.status.includes(\"\\u001b[31m\"));\n if (!hasErrors) {\n console.log(`\\n ${ok(\"All done.\")} You may need to restart the PAI daemon.\\n`);\n console.log(` ${dim(\"Restart:\")} pai daemon restart\\n`);\n } else {\n console.log(`\\n ${warn(\"Some items failed — check output above.\")}\\n`);\n process.exit(1);\n }\n });\n}\n","/**\n * json-store.ts — read/write JSON config files without destroying them\n *\n * The failure this exists to prevent:\n *\n * try { return JSON.parse(read(path)); } catch { return {}; }\n * ... later ...\n * write(path, JSON.stringify(ourData));\n *\n * An unreadable file becomes an empty object, and the next write makes that\n * permanent. Silently, exit code 0. This shape appeared three times in this\n * repo against three different files, and twice in AIBroker.\n *\n * The distinction that matters is between *missing* and *unreadable*:\n *\n * missing — legitimate first run. Start fresh; writing is safe.\n * unreadable — the file exists and we could not parse it. Those bytes are\n * the only copy of something. Never overwrite them.\n *\n * Collapsing the second into the first is the bug.\n *\n * NOT everything deserves this guard. For a transient buffer — an undelivered\n * message queue, a cache — starting fresh IS the correct recovery, and\n * refusing to write would disable the feature permanently. Use `writeJsonAtomic`\n * alone there: it still prevents a crash from truncating a good file, without\n * blocking recovery. Reserve `readJsonStrict` for data a user cannot rebuild.\n */\n\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n copyFileSync,\n renameSync,\n unlinkSync,\n mkdirSync,\n} from \"node:fs\";\nimport { dirname } from \"node:path\";\n\n/**\n * Read a JSON file, distinguishing \"absent\" from \"damaged\".\n *\n * @param path file to read\n * @param label how to name it to the user, e.g. \"~/.claude.json\"\n * @throws if the file exists but cannot be read or parsed\n */\nexport function readJsonStrict(path: string, label = path): Record<string, unknown> {\n if (!existsSync(path)) return {};\n\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch (e) {\n throw new Error(\n `Could not read ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — writing now would replace its contents with ours alone.`\n );\n }\n\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n throw new Error(\n `${label} exists but is not valid JSON: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — overwriting it would destroy whatever it holds.\\n` +\n `Repair the file, or move it aside and re-run this command.`\n );\n }\n}\n\n/**\n * Write JSON without risking the existing file.\n *\n * Keeps a `.bak-pai` copy of the previous contents, then writes to a temp file\n * and renames. Rename is atomic within a filesystem, so a crash mid-write\n * leaves the original intact rather than truncated — which is how these files\n * become corrupt in the first place.\n */\nexport function writeJsonAtomic(\n path: string,\n data: Record<string, unknown>,\n opts: { backup?: boolean; label?: string } = {}\n): void {\n const { backup = true, label = path } = opts;\n const serialized = JSON.stringify(data, null, 2) + \"\\n\";\n\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n if (backup && existsSync(path)) {\n try {\n copyFileSync(path, `${path}.bak-pai`);\n } catch (e) {\n throw new Error(\n `Could not back up ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to write without a backup.`\n );\n }\n }\n\n const tmp = `${path}.tmp-pai-${process.pid}`;\n try {\n writeFileSync(tmp, serialized, \"utf8\");\n renameSync(tmp, path);\n } catch (e) {\n try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* best effort */ }\n throw new Error(\n `Failed to write ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `The original is unchanged.`\n );\n }\n}\n","/**\n * Shared helpers for the PAI setup wizard: chalk colour shortcuts,\n * readline prompts, config read/write, and filesystem path finders.\n */\n\nimport { createInterface } from \"node:readline\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readJsonStrict, writeJsonAtomic } from \"../../../config/json-store.js\";\nimport { spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { CONFIG_DIR, CONFIG_FILE } from \"../../../daemon/config.js\";\n\n// ---------------------------------------------------------------------------\n// Chalk colour helpers\n// ---------------------------------------------------------------------------\n\nexport const c = {\n bold: (s: string) => chalk.bold(s),\n dim: (s: string) => chalk.dim(s),\n green: (s: string) => chalk.green(s),\n yellow: (s: string) => chalk.yellow(s),\n cyan: (s: string) => chalk.cyan(s),\n red: (s: string) => chalk.red(s),\n blue: (s: string) => chalk.blue(s),\n ok: (s: string) => chalk.green(\" \" + s),\n warn: (s: string) => chalk.yellow(\" \" + s),\n err: (s: string) => chalk.red(\" \" + s),\n};\n\nexport function line(text = \"\"): void {\n console.log(text);\n}\n\nexport function section(title: string): void {\n line();\n console.log(chalk.bold.cyan(\" \" + title));\n console.log(chalk.dim(\" \" + \"─\".repeat(title.length)));\n}\n\n// ---------------------------------------------------------------------------\n// Readline prompt helpers\n// ---------------------------------------------------------------------------\n\nexport function createRl() {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.on(\"SIGINT\", () => {\n line();\n line(c.dim(\" Setup cancelled. Run `pai setup` again to restart.\"));\n line();\n process.exit(0);\n });\n\n return rl;\n}\n\nexport type Rl = ReturnType<typeof createRl>;\n\nexport async function prompt(rl: Rl, question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n resolve(answer.trim());\n });\n });\n}\n\n/** Prompt for a numbered menu selection. Returns 0-based index. */\nexport async function promptMenu(\n rl: Rl,\n options: Array<{ label: string; description?: string }>,\n defaultIdx = 0,\n): Promise<number> {\n for (let i = 0; i < options.length; i++) {\n const num = chalk.bold(` ${i + 1}.`);\n const label = i === defaultIdx ? chalk.cyan(options[i].label) : options[i].label;\n const marker = i === defaultIdx ? chalk.dim(\" (recommended)\") : \"\";\n console.log(`${num} ${label}${marker}`);\n if (options[i].description) {\n console.log(chalk.dim(` ${options[i].description}`));\n }\n }\n line();\n\n while (true) {\n const answer = await prompt(\n rl,\n chalk.bold(` Enter number [1-${options.length}] (default: ${defaultIdx + 1}): `),\n );\n\n if (answer === \"\") return defaultIdx;\n\n const n = parseInt(answer, 10);\n if (!isNaN(n) && n >= 1 && n <= options.length) {\n return n - 1;\n }\n\n console.log(c.warn(`Please enter a number between 1 and ${options.length}.`));\n }\n}\n\n/** Prompt for a yes/no answer. Returns true for yes. */\nexport async function promptYesNo(\n rl: Rl,\n question: string,\n defaultYes = true,\n): Promise<boolean> {\n const hint = defaultYes ? \"[Y/n]\" : \"[y/N]\";\n const answer = await prompt(rl, ` ${question} ${chalk.dim(hint)}: `);\n\n if (answer === \"\") return defaultYes;\n return answer.toLowerCase().startsWith(\"y\");\n}\n\n// ---------------------------------------------------------------------------\n// Config read/write helpers\n// ---------------------------------------------------------------------------\n\n/**\n * This file holds the Postgres connection string, the storage backend choice,\n * notification routing and any tracker API token — none of which the user can\n * reconstruct from memory. It previously returned {} on a parse failure and\n * then overwrote the file, so a damaged config was replaced by whatever the\n * current command happened to be setting.\n */\nexport function readConfigRaw(): Record<string, unknown> {\n return readJsonStrict(CONFIG_FILE, \"~/.config/pai/config.json\");\n}\n\nexport function writeConfigRaw(data: Record<string, unknown>): void {\n writeJsonAtomic(CONFIG_FILE, data, { label: \"~/.config/pai/config.json\" });\n}\n\nexport function mergeConfig(updates: Record<string, unknown>): void {\n const current = readConfigRaw();\n const merged = { ...current, ...updates };\n if (updates.postgres && typeof current.postgres === \"object\" && current.postgres !== null) {\n merged.postgres = { ...(current.postgres as object), ...(updates.postgres as object) };\n }\n writeConfigRaw(merged);\n}\n\n// ---------------------------------------------------------------------------\n// Docker and connection helpers\n// ---------------------------------------------------------------------------\n\nexport function hasDocker(): boolean {\n try {\n const result = spawnSync(\"docker\", [\"--version\"], { stdio: \"pipe\" });\n return result.status === 0;\n } catch {\n return false;\n }\n}\n\nexport function getDockerDir(): string {\n const candidates = [\n join(process.cwd(), \"docker\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"docker\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"docker\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"docker-compose.yml\"))) return candidate;\n }\n return join(process.cwd(), \"docker\");\n}\n\nexport async function testPostgresConnection(connectionString: string): Promise<boolean> {\n try {\n const pgModule = await import(\"pg\");\n const pg = pgModule.default ?? pgModule;\n const client = new pg.Client({ connectionString });\n await client.connect();\n await client.end();\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Filesystem path finders\n// ---------------------------------------------------------------------------\n\nexport function getTemplatesDir(): string {\n const candidates = [\n join(process.cwd(), \"templates\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"templates\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"templates\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"claude-md.template.md\"))) return candidate;\n }\n return join(process.cwd(), \"templates\");\n}\n\nexport function getHooksDir(): string {\n const candidates = [\n join(process.cwd(), \"src\", \"hooks\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"src\", \"hooks\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"src\", \"hooks\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"session-stop.sh\"))) return candidate;\n }\n return join(process.cwd(), \"src\", \"hooks\");\n}\n\nexport function getDistHooksDir(): string {\n const moduleDir = new URL(\".\", import.meta.url).pathname;\n const fromModule = join(moduleDir, \"..\", \"..\", \"hooks\");\n\n const candidates = [\n fromModule,\n join(process.cwd(), \"dist\", \"hooks\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"dist\", \"hooks\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"dist\", \"hooks\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"stop-hook.mjs\"))) return candidate;\n }\n return fromModule;\n}\n\nexport function getDistDir(): string {\n const moduleDir = new URL(\".\", import.meta.url).pathname;\n const fromModule = join(moduleDir, \"..\", \"..\", \"..\");\n\n const candidates = [\n fromModule,\n join(process.cwd(), \"dist\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"dist\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"dist\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"skills\"))) return candidate;\n }\n return fromModule;\n}\n\nexport function getStatuslineScript(): string | null {\n const candidates = [\n join(process.cwd(), \"statusline-command.sh\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"statusline-command.sh\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"statusline-command.sh\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n return null;\n}\n\nexport function getTabColorScript(): string | null {\n const candidates = [\n join(process.cwd(), \"tab-color-command.sh\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"tab-color-command.sh\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"tab-color-command.sh\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n return null;\n}\n","/** Step 1: Welcome banner displayed at the start of the setup wizard. */\n\nimport chalk from \"chalk\";\nimport { c, line } from \"../utils.js\";\n\nexport function stepWelcome(): void {\n line();\n line(chalk.bold.cyan(\" ╔════════════════════════════════════════╗\"));\n line(chalk.bold.cyan(\" ║ PAI Knowledge OS — Setup Wizard ║\"));\n line(chalk.bold.cyan(\" ╚════════════════════════════════════════╝\"));\n line();\n line(\" PAI is a personal knowledge system that indexes your files, generates\");\n line(\" semantic embeddings for intelligent search, and stores everything in a\");\n line(\" local database so you can search your knowledge base with natural language.\");\n line();\n line(c.dim(\" This wizard will guide you through the initial configuration.\"));\n line(c.dim(\" Press Ctrl+C at any time to cancel.\"));\n}\n","/** Step 2: Storage backend selection (SQLite or PostgreSQL) and Docker helper. */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { c, line, section, type Rl, prompt, promptMenu, promptYesNo, readConfigRaw, getDockerDir, hasDocker, testPostgresConnection } from \"../utils.js\";\n\nasync function startDocker(rl: Rl): Promise<boolean> {\n const dockerDir = getDockerDir();\n const composePath = join(dockerDir, \"docker-compose.yml\");\n\n if (!existsSync(composePath)) {\n console.log(c.warn(`docker-compose.yml not found at ${dockerDir}`));\n console.log(c.dim(\" You can start it manually later:\"));\n console.log(c.dim(\" docker compose up -d\"));\n return false;\n }\n\n console.log(c.dim(` Starting PostgreSQL container from ${dockerDir}...`));\n\n try {\n const result = spawnSync(\"docker\", [\"compose\", \"up\", \"-d\"], {\n cwd: dockerDir,\n stdio: \"inherit\",\n });\n\n if (result.status !== 0) {\n console.log(c.warn(\" Docker compose failed. You can start it manually:\"));\n console.log(c.dim(` cd ${dockerDir} && docker compose up -d`));\n return false;\n }\n\n console.log(c.ok(\"PostgreSQL container started.\"));\n return true;\n } catch (e) {\n console.log(c.warn(` Could not run docker compose: ${e}`));\n return false;\n }\n}\n\nexport async function stepStorage(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 2: Storage Backend\");\n\n const existing = readConfigRaw();\n if (existing.storageBackend) {\n const backend = String(existing.storageBackend);\n if (backend === \"postgres\") {\n try {\n const result = spawnSync(\"docker\", [\"ps\", \"--filter\", \"name=pai-pgvector\", \"--format\", \"{{.Status}}\"], { stdio: \"pipe\" });\n const status = result.stdout?.toString().trim();\n if (status && status.includes(\"Up\")) {\n console.log(c.ok(`Storage backend: PostgreSQL (container running). Skipping.`));\n return existing;\n }\n console.log(c.dim(\" PostgreSQL container found but not running. Starting...\"));\n await startDocker(rl);\n await new Promise((r) => setTimeout(r, 3000));\n console.log(c.ok(\"PostgreSQL container started.\"));\n } catch {\n console.log(c.ok(`Storage backend: PostgreSQL (configured). Skipping.`));\n }\n return existing;\n } else {\n console.log(c.ok(`Storage backend: ${backend}. Skipping.`));\n return existing;\n }\n }\n\n line();\n line(\" Choose how PAI stores your indexed knowledge:\");\n line();\n\n const choice = await promptMenu(rl, [\n {\n label: \"PostgreSQL with pgvector\",\n description: \"Best for large collections, semantic search, and production use. Requires Docker or a Postgres server.\",\n },\n {\n label: \"SQLite\",\n description: \"Simple, no dependencies, zero configuration. Good for trying PAI out. Keyword search only.\",\n },\n ]);\n\n if (choice === 1) {\n line();\n console.log(c.ok(\"SQLite selected. No additional setup needed.\"));\n return { storageBackend: \"sqlite\" };\n }\n\n // PostgreSQL\n line();\n line(\" PostgreSQL requires a running Postgres server with the pgvector extension.\");\n line();\n\n if (hasDocker()) {\n console.log(c.ok(\"Docker is installed.\"));\n line();\n\n const useDocker = await promptYesNo(rl, \"Start the PAI PostgreSQL container with Docker? (recommended)\", true);\n\n if (useDocker) {\n line();\n await startDocker(rl);\n line();\n\n console.log(c.dim(\" Waiting 3 seconds for container to be ready...\"));\n await new Promise((r) => setTimeout(r, 3000));\n\n const connStr = \"postgresql://pai:pai@localhost:5432/pai\";\n console.log(c.dim(` Testing connection to ${connStr}...`));\n\n const ok2 = await testPostgresConnection(connStr);\n if (ok2) {\n console.log(c.ok(\"Connection successful!\"));\n } else {\n console.log(c.warn(\"Connection test failed. The container may still be starting.\"));\n console.log(c.dim(\" Using default connection string — you can verify with `pai daemon status`.\"));\n }\n return { storageBackend: \"postgres\", postgres: { connectionString: connStr } };\n }\n } else {\n console.log(c.dim(\" Docker not found. Using manual connection string entry.\"));\n }\n\n // Manual entry\n line();\n line(\" Enter your PostgreSQL connection details:\");\n line();\n\n const useConnStr = await promptYesNo(rl, \"Use a full connection string? (e.g. postgresql://user:pass@host:5432/dbname)\", true);\n\n if (useConnStr) {\n const connStr = await prompt(rl, chalk.bold(\" Connection string: \"));\n if (connStr) {\n console.log(c.dim(\" Testing connection...\"));\n const connected = await testPostgresConnection(connStr);\n if (connected) {\n console.log(c.ok(\"Connection successful!\"));\n } else {\n console.log(c.warn(\"Connection test failed — check credentials and try again later.\"));\n }\n return { storageBackend: \"postgres\", postgres: { connectionString: connStr } };\n }\n }\n\n const host = await prompt(rl, chalk.bold(\" Host [localhost]: \")) || \"localhost\";\n const portStr = await prompt(rl, chalk.bold(\" Port [5432]: \")) || \"5432\";\n const database = await prompt(rl, chalk.bold(\" Database [pai]: \")) || \"pai\";\n const user = await prompt(rl, chalk.bold(\" User [pai]: \")) || \"pai\";\n const password = await prompt(rl, chalk.bold(\" Password [pai]: \")) || \"pai\";\n\n const connStr = `postgresql://${user}:${password}@${host}:${portStr}/${database}`;\n console.log(c.dim(` Connection string: ${connStr}`));\n console.log(c.dim(\" Testing connection...\"));\n\n const connected = await testPostgresConnection(connStr);\n if (connected) {\n console.log(c.ok(\"Connection successful!\"));\n } else {\n console.log(c.warn(\"Connection test failed — check credentials and try again later.\"));\n }\n\n return { storageBackend: \"postgres\", postgres: { connectionString: connStr } };\n}\n","/** Step 3: Embedding model selection for semantic search. */\n\nimport { c, line, section, type Rl, promptMenu, readConfigRaw } from \"../utils.js\";\n\nexport async function stepEmbedding(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 3: Embedding Model\");\n\n const existing = readConfigRaw();\n if (existing.embeddingModel) {\n console.log(c.ok(`Embedding model: ${existing.embeddingModel}. Skipping.`));\n return { embeddingModel: existing.embeddingModel };\n }\n\n line();\n line(\" An embedding model converts your text into vectors for semantic search.\");\n line(\" Models are downloaded from HuggingFace on first use.\");\n line();\n\n const choice = await promptMenu(rl, [\n {\n label: \"Snowflake Arctic Embed m v1.5\",\n description: \"768 dims, ~118MB download. Best retrieval quality per MB (MTEB score 55.14). Asymmetric retrieval — different handling for queries vs documents. Best for most users.\",\n },\n {\n label: \"BGE Small EN v1.5\",\n description: \"384 dims, ~32MB download. Lightweight and fast. Good for limited disk space or when faster embedding is more important than maximum quality. English only.\",\n },\n {\n label: \"Nomic Embed Text v1.5\",\n description: \"768 dims, ~100MB download. 8K token context window — excellent for long documents. Matryoshka dimensions (can truncate for speed/size tradeoffs).\",\n },\n {\n label: \"None — skip embeddings\",\n description: \"BM25/keyword search only. No model download needed. You can add embeddings later by running `pai memory embed` after selecting a model.\",\n },\n ]);\n\n const models: Record<number, string | null> = {\n 0: \"Snowflake/snowflake-arctic-embed-m-v1.5\",\n 1: \"BAAI/bge-small-en-v1.5\",\n 2: \"nomic-ai/nomic-embed-text-v1.5\",\n 3: null,\n };\n\n const selectedModel = models[choice];\n\n line();\n if (selectedModel) {\n console.log(c.ok(`Model selected: ${selectedModel}`));\n console.log(c.dim(\" The model will be downloaded on first use of `pai memory embed`.\"));\n } else {\n console.log(c.ok(\"Skipping embeddings. Keyword search will still work.\"));\n console.log(c.dim(\" Add later: update embeddingModel in ~/.config/pai/config.json\"));\n }\n\n return { embeddingModel: selectedModel ?? \"none\" };\n}\n","/** Step 4: CLAUDE.md generation from PAI template. */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { CONFIG_DIR } from \"../../../../daemon/config.js\";\nimport { c, line, section, type Rl, promptYesNo, getTemplatesDir } from \"../utils.js\";\n\nexport async function stepClaudeMd(rl: Rl): Promise<boolean> {\n section(\"Step 4: Agent Configuration (CLAUDE.md)\");\n line();\n line(\" PAI ships a CLAUDE.md template with agent orchestration patterns:\");\n line(\" swarm mode, model escalation, parallel execution, quality standards.\");\n line();\n\n const claudeDir = join(homedir(), \".claude\");\n const claudeMd = join(claudeDir, \"CLAUDE.md\");\n const agentPrefs = join(CONFIG_DIR, \"agent-prefs.md\");\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"claude-md.template.md\");\n\n if (!existsSync(templatePath)) {\n console.log(c.warn(\"Template not found: \" + templatePath));\n console.log(c.dim(\" Skipping CLAUDE.md generation. You can copy it manually from templates/.\"));\n return false;\n }\n\n if (existsSync(claudeMd)) {\n const content = readFileSync(claudeMd, \"utf-8\");\n const isGenerated = content.includes(\"Generated by PAI Setup\");\n if (isGenerated) {\n console.log(c.dim(\" Found existing PAI-generated CLAUDE.md.\"));\n } else {\n console.log(c.yellow(\" Found existing CLAUDE.md (custom, not PAI-generated).\"));\n console.log(c.dim(\" A backup will be created before overwriting.\"));\n }\n line();\n\n const overwrite = await promptYesNo(rl, \"Update ~/.claude/CLAUDE.md with the latest PAI template?\", isGenerated);\n if (!overwrite) {\n console.log(c.dim(\" Keeping existing CLAUDE.md unchanged.\"));\n return false;\n }\n\n if (!isGenerated) {\n const backupPath = claudeMd + \".backup\";\n writeFileSync(backupPath, content, \"utf-8\");\n console.log(c.ok(`Backed up existing CLAUDE.md to ${backupPath}`));\n }\n }\n\n let template = readFileSync(templatePath, \"utf-8\");\n template = template.replace(/\\$\\{HOME\\}/g, homedir());\n\n if (!existsSync(claudeDir)) {\n mkdirSync(claudeDir, { recursive: true });\n }\n writeFileSync(claudeMd, template, \"utf-8\");\n\n line();\n console.log(c.ok(\"Generated ~/.claude/CLAUDE.md from PAI template.\"));\n\n if (!existsSync(agentPrefs)) {\n line();\n line(\" For personal settings (project mappings, notification preferences),\");\n line(\" copy the example and customize:\");\n line();\n console.log(chalk.cyan(` cp ${templatesDir}/agent-prefs.example.md ${agentPrefs}`));\n line();\n\n const createPrefs = await promptYesNo(rl, \"Copy the example agent-prefs.md now?\", true);\n if (createPrefs) {\n const examplePath = join(templatesDir, \"agent-prefs.example.md\");\n if (existsSync(examplePath)) {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n writeFileSync(agentPrefs, readFileSync(examplePath, \"utf-8\"), \"utf-8\");\n console.log(c.ok(`Created ${agentPrefs}`));\n console.log(c.dim(\" Edit this file to add your personal preferences and project mappings.\"));\n } else {\n console.log(c.warn(\"Example file not found. Create agent-prefs.md manually.\"));\n }\n }\n } else {\n console.log(c.dim(\" Personal preferences: \" + agentPrefs));\n }\n\n return true;\n}\n","/** Step 5: PAI SKILL.md installation to ~/.claude/skills/PAI/. */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getTemplatesDir } from \"../utils.js\";\n\nexport async function stepPaiSkill(rl: Rl): Promise<boolean> {\n section(\"Step 5: PAI Skill Installation\");\n line();\n line(\" PAI ships a SKILL.md that tells Claude Code how to invoke PAI commands.\");\n line();\n\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"pai-skill.template.md\");\n\n if (!existsSync(templatePath)) {\n console.log(c.warn(\"Skill template not found: \" + templatePath));\n console.log(c.dim(\" Skipping PAI skill installation.\"));\n return false;\n }\n\n const skillDir = join(homedir(), \".claude\", \"skills\", \"PAI\");\n const skillFile = join(skillDir, \"SKILL.md\");\n\n if (existsSync(skillFile)) {\n const content = readFileSync(skillFile, \"utf-8\");\n const isGenerated = content.includes(\"Generated by PAI Setup\");\n if (isGenerated) {\n console.log(c.dim(\" Found existing PAI-generated SKILL.md.\"));\n } else {\n console.log(c.yellow(\" Found existing SKILL.md (not PAI-generated).\"));\n console.log(c.dim(\" A backup will be created before overwriting.\"));\n }\n line();\n\n const overwrite = await promptYesNo(rl, \"Update ~/.claude/skills/PAI/SKILL.md with the latest PAI skill?\", isGenerated);\n if (!overwrite) {\n console.log(c.dim(\" Keeping existing SKILL.md unchanged.\"));\n return false;\n }\n\n if (!isGenerated) {\n const backupPath = skillFile + \".backup\";\n writeFileSync(backupPath, content, \"utf-8\");\n console.log(c.ok(`Backed up existing SKILL.md to ${backupPath}`));\n }\n } else {\n const install = await promptYesNo(rl, \"Install PAI skill to ~/.claude/skills/PAI/SKILL.md?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping PAI skill installation.\"));\n return false;\n }\n }\n\n let template = readFileSync(templatePath, \"utf-8\");\n template = template.replace(/\\$\\{HOME\\}/g, homedir());\n\n if (!existsSync(skillDir)) {\n mkdirSync(skillDir, { recursive: true });\n }\n writeFileSync(skillFile, template, \"utf-8\");\n\n line();\n console.log(c.ok(\"Installed ~/.claude/skills/PAI/SKILL.md\"));\n return true;\n}\n","/** Step 6: AI Steering Rules installation to ~/.claude/skills/PAI/. */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getTemplatesDir } from \"../utils.js\";\n\nexport async function stepAiSteeringRules(rl: Rl): Promise<boolean> {\n section(\"Step 6: AI Steering Rules\");\n line();\n line(\" PAI ships a set of universal behavioral rules for AI assistants:\");\n line(\" surgical fixes, verification before assertion, root cause analysis,\");\n line(\" and honest failure modes. These rules load at startup as a skill.\");\n line();\n\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"ai-steering-rules.template.md\");\n\n if (!existsSync(templatePath)) {\n console.log(c.warn(\"AI steering rules template not found: \" + templatePath));\n console.log(c.dim(\" Skipping AI steering rules installation.\"));\n return false;\n }\n\n const skillDir = join(homedir(), \".claude\", \"skills\", \"PAI\");\n const skillFile = join(skillDir, \"AI-STEERING-RULES.md\");\n\n if (existsSync(skillFile)) {\n const content = readFileSync(skillFile, \"utf-8\");\n const isGenerated = content.includes(\"Generated by PAI Setup\");\n if (isGenerated) {\n console.log(c.dim(\" Found existing PAI-generated AI-STEERING-RULES.md.\"));\n } else {\n console.log(c.yellow(\" Found existing AI-STEERING-RULES.md (not PAI-generated).\"));\n console.log(c.dim(\" A backup will be created before overwriting.\"));\n }\n line();\n\n const overwrite = await promptYesNo(rl, \"Update ~/.claude/skills/PAI/AI-STEERING-RULES.md with the latest rules?\", isGenerated);\n if (!overwrite) {\n console.log(c.dim(\" Keeping existing AI-STEERING-RULES.md unchanged.\"));\n return false;\n }\n\n if (!isGenerated) {\n const backupPath = skillFile + \".backup\";\n writeFileSync(backupPath, content, \"utf-8\");\n console.log(c.ok(`Backed up existing AI-STEERING-RULES.md to ${backupPath}`));\n }\n } else {\n const install = await promptYesNo(rl, \"Install AI steering rules to ~/.claude/skills/PAI/AI-STEERING-RULES.md?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping AI steering rules installation.\"));\n return false;\n }\n }\n\n const template = readFileSync(templatePath, \"utf-8\");\n\n if (!existsSync(skillDir)) {\n mkdirSync(skillDir, { recursive: true });\n }\n writeFileSync(skillFile, template, \"utf-8\");\n\n line();\n console.log(c.ok(\"Installed ~/.claude/skills/PAI/AI-STEERING-RULES.md\"));\n return true;\n}\n","/**\n * Step 7: Install PAI MCP skill stubs to ~/.claude/skills/.\n *\n * Each PAI MCP prompt has a SKILL.md stub generated at build time\n * (dist/skills/<Name>/SKILL.md). This step symlinks (macOS/Linux) or\n * copies (Windows) them into Claude Code's skill discovery directory.\n *\n * Skills MUST live at ~/.claude/skills/<Name>/SKILL.md (top level),\n * NOT under a user/ subdirectory — Claude Code only scans one level deep.\n *\n * Symlinks keep the stubs in sync — rebuilding PAI updates them automatically.\n * Existing non-symlink directories with the same name are never overwritten.\n */\n\nimport {\n existsSync,\n readdirSync,\n symlinkSync,\n lstatSync,\n readlinkSync,\n copyFileSync,\n mkdirSync,\n unlinkSync,\n} from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getDistDir } from \"../utils.js\";\n\nexport async function stepSkillStubs(rl: Rl): Promise<boolean> {\n section(\"Step 7: MCP Skill Stubs\");\n line();\n line(\" PAI MCP prompts need SKILL.md stubs so Claude Code can discover them.\");\n line(\" Stubs are symlinked from dist/skills/ — rebuilding PAI keeps them in sync.\");\n line();\n\n const distSkills = join(getDistDir(), \"skills\");\n\n if (!existsSync(distSkills)) {\n console.log(c.warn(\"dist/skills/ not found — run `bun run build` first.\"));\n return false;\n }\n\n const skillNames = readdirSync(distSkills).filter((name) => {\n const p = join(distSkills, name);\n return existsSync(join(p, \"SKILL.md\"));\n });\n\n if (skillNames.length === 0) {\n console.log(c.warn(\"No skill stubs found in dist/skills/.\"));\n return false;\n }\n\n console.log(c.dim(` Found ${skillNames.length} skill stubs to install.`));\n line();\n\n const install = await promptYesNo(\n rl,\n `Symlink ${skillNames.length} PAI skill stubs to ~/.claude/skills/?`,\n true,\n );\n if (!install) {\n console.log(c.dim(\" Skipping skill stub installation.\"));\n return false;\n }\n\n const targetBase = join(homedir(), \".claude\", \"skills\");\n mkdirSync(targetBase, { recursive: true });\n\n // Clean up stale symlinks from old user/ location\n const oldUserBase = join(targetBase, \"user\");\n if (existsSync(oldUserBase)) {\n for (const name of skillNames) {\n const oldTarget = join(oldUserBase, name);\n if (existsSync(oldTarget) && lstatSync(oldTarget).isSymbolicLink()) {\n unlinkSync(oldTarget);\n }\n }\n }\n\n const useSymlinks = platform() !== \"win32\";\n let installed = 0;\n let skipped = 0;\n let updated = 0;\n\n for (const name of skillNames) {\n const source = resolve(join(distSkills, name));\n const target = join(targetBase, name);\n\n // If target exists and is not a symlink, never overwrite (user's own skill)\n if (existsSync(target) && !lstatSync(target).isSymbolicLink()) {\n console.log(c.dim(` SKIP ${name} (existing user skill, not a symlink)`));\n skipped++;\n continue;\n }\n\n // If target is an existing symlink, check if it already points to the right place\n if (existsSync(target) && lstatSync(target).isSymbolicLink()) {\n const currentTarget = readlinkSync(target);\n if (resolve(currentTarget) === source) {\n // Already correct\n installed++;\n continue;\n }\n // Stale symlink — remove and recreate\n unlinkSync(target);\n updated++;\n }\n\n if (useSymlinks) {\n symlinkSync(source, target);\n } else {\n // Windows: copy the SKILL.md\n mkdirSync(target, { recursive: true });\n copyFileSync(join(source, \"SKILL.md\"), join(target, \"SKILL.md\"));\n }\n installed++;\n }\n\n line();\n const method = useSymlinks ? \"symlinked\" : \"copied\";\n if (updated > 0) {\n console.log(c.ok(`${installed} stubs ${method}, ${updated} updated, ${skipped} skipped (user skills).`));\n } else {\n console.log(c.ok(`${installed} skill stubs ${method} to ~/.claude/skills/.`));\n if (skipped > 0) {\n console.log(c.dim(` ${skipped} skipped (existing user skills not managed by PAI).`));\n }\n }\n return true;\n}\n","/** Step 7: Shell lifecycle hooks installation (pre-compact, session-stop, statusline). */\n\nimport {\n existsSync, lstatSync, readlinkSync, symlinkSync, unlinkSync,\n copyFileSync, chmodSync, mkdirSync,\n} from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getHooksDir, getStatuslineScript, getTabColorScript } from \"../utils.js\";\n\nconst useSymlinks = platform() !== \"win32\";\n\nexport async function stepHooks(rl: Rl): Promise<boolean> {\n section(\"Step 7: Lifecycle Hooks\");\n line();\n line(\" PAI hooks fire on session stop and context compaction to save state,\");\n line(\" update notes, and display live statusline information.\");\n if (useSymlinks) {\n line(\" Files are symlinked so they auto-update when PAI is rebuilt.\");\n } else {\n line(\" Files are copied (Windows — re-run setup after PAI updates).\");\n }\n line();\n\n const install = await promptYesNo(rl, \"Install PAI lifecycle hooks (session stop, pre-compact, statusline)?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping hook installation.\"));\n return false;\n }\n\n const hooksDir = getHooksDir();\n const statuslineSrc = getStatuslineScript();\n const tabColorSrc = getTabColorScript();\n\n const claudeDir = join(homedir(), \".claude\");\n const hooksTarget = join(claudeDir, \"Hooks\");\n\n if (!existsSync(hooksTarget)) {\n mkdirSync(hooksTarget, { recursive: true });\n }\n\n let anyInstalled = false;\n\n function installLink(src: string, dest: string, label: string): void {\n if (!existsSync(src)) {\n console.log(c.warn(` Source not found: ${src}`));\n return;\n }\n\n const absSrc = resolve(src);\n\n // Check existing target\n if (existsSync(dest) || (lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink?.())) {\n try {\n const stat = lstatSync(dest);\n if (stat.isSymbolicLink()) {\n if (resolve(readlinkSync(dest)) === absSrc) {\n console.log(c.dim(` Current: ${label}`));\n return;\n }\n // Stale symlink — replace\n unlinkSync(dest);\n } else if (stat.isFile()) {\n // Old copy — replace with symlink\n unlinkSync(dest);\n } else {\n return; // Don't touch directories\n }\n } catch {\n // lstatSync failed — target doesn't exist, proceed\n }\n }\n\n if (useSymlinks) {\n symlinkSync(absSrc, dest);\n } else {\n copyFileSync(src, dest);\n chmodSync(dest, 0o755);\n }\n const verb = useSymlinks ? \"Linked\" : \"Installed\";\n console.log(c.ok(`${verb}: ${label}`));\n anyInstalled = true;\n }\n\n line();\n installLink(join(hooksDir, \"pre-compact.sh\"), join(hooksTarget, \"pai-pre-compact.sh\"), \"pai-pre-compact.sh\");\n installLink(join(hooksDir, \"session-stop.sh\"), join(hooksTarget, \"pai-session-stop.sh\"), \"pai-session-stop.sh\");\n\n if (statuslineSrc) {\n installLink(statuslineSrc, join(claudeDir, \"statusline-command.sh\"), \"statusline-command.sh\");\n } else {\n console.log(c.warn(\" statusline-command.sh not found — skipping statusline.\"));\n }\n\n if (tabColorSrc) {\n installLink(tabColorSrc, join(claudeDir, \"tab-color-command.sh\"), \"tab-color-command.sh\");\n } else {\n console.log(c.warn(\" tab-color-command.sh not found — skipping tab color.\"));\n }\n\n return anyInstalled;\n}\n","/** Step 7b: TypeScript (.mjs) hooks installation to ~/.claude/Hooks/. */\n\nimport {\n existsSync, readdirSync, lstatSync, readlinkSync,\n symlinkSync, unlinkSync, copyFileSync, chmodSync, mkdirSync,\n} from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getDistHooksDir } from \"../utils.js\";\n\nconst useSymlinks = platform() !== \"win32\";\n\nexport async function stepTsHooks(rl: Rl): Promise<boolean> {\n section(\"Step 7b: TypeScript Hooks Installation\");\n line();\n line(\" PAI ships compiled TypeScript hooks (.mjs) that fire on session events,\");\n line(\" tool use, and context compaction to capture context and update notes.\");\n if (useSymlinks) {\n line(\" Files are symlinked so they auto-update when PAI is rebuilt.\");\n } else {\n line(\" Files are copied (Windows — re-run setup after PAI updates).\");\n }\n line();\n\n const install = await promptYesNo(rl, \"Install PAI TypeScript hooks to ~/.claude/Hooks/?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping TypeScript hooks installation.\"));\n return false;\n }\n\n const distHooksDir = getDistHooksDir();\n\n if (!existsSync(distHooksDir)) {\n console.log(c.warn(` dist/hooks/ directory not found at: ${distHooksDir}`));\n console.log(c.dim(\" Build the package first: bun run build\"));\n return false;\n }\n\n const claudeDir = join(homedir(), \".claude\");\n const hooksTarget = join(claudeDir, \"Hooks\");\n\n if (!existsSync(hooksTarget)) {\n mkdirSync(hooksTarget, { recursive: true });\n }\n\n let allFiles: string[];\n try {\n allFiles = readdirSync(distHooksDir).filter((f) => f.endsWith(\".mjs\"));\n } catch (e) {\n console.log(c.warn(` Could not read dist/hooks/: ${e}`));\n return false;\n }\n\n if (allFiles.length === 0) {\n console.log(c.warn(\" No .mjs files found in dist/hooks/. Build first: bun run build\"));\n return false;\n }\n\n line();\n let linkedCount = 0;\n let skippedCount = 0;\n let cleanedCount = 0;\n\n for (const filename of allFiles) {\n const src = resolve(join(distHooksDir, filename));\n const dest = join(hooksTarget, filename);\n\n // Check existing target\n let needsLink = true;\n if (existsSync(dest) || lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink?.()) {\n try {\n const stat = lstatSync(dest);\n if (stat.isSymbolicLink()) {\n if (resolve(readlinkSync(dest)) === src) {\n console.log(c.dim(` Current: ${filename}`));\n skippedCount++;\n needsLink = false;\n } else {\n unlinkSync(dest); // Stale symlink\n }\n } else if (stat.isFile()) {\n unlinkSync(dest); // Old copy — replace with symlink\n }\n } catch {\n // lstatSync failed — target doesn't exist\n }\n }\n\n if (needsLink) {\n if (useSymlinks) {\n symlinkSync(src, dest);\n } else {\n copyFileSync(src, dest);\n chmodSync(dest, 0o755);\n }\n const verb = useSymlinks ? \"Linked\" : \"Installed\";\n console.log(c.ok(`${verb}: ${filename}`));\n linkedCount++;\n }\n\n // Clean up stale .ts files from pre-build era\n const staleTsPath = join(hooksTarget, filename.replace(/\\.mjs$/, \".ts\"));\n if (existsSync(staleTsPath)) {\n unlinkSync(staleTsPath);\n console.log(c.ok(`Cleaned up stale: ${filename.replace(/\\.mjs$/, \".ts\")}`));\n cleanedCount++;\n }\n }\n\n line();\n if (linkedCount > 0 || cleanedCount > 0) {\n const parts = [];\n const verb = useSymlinks ? \"linked\" : \"installed\";\n if (linkedCount > 0) parts.push(`${linkedCount} hook(s) ${verb}`);\n if (skippedCount > 0) parts.push(`${skippedCount} current`);\n if (cleanedCount > 0) parts.push(`${cleanedCount} stale .ts file(s) cleaned up`);\n console.log(c.ok(parts.join(\", \") + \".\"));\n } else {\n console.log(c.dim(` All ${skippedCount} hook(s) already current.`));\n }\n\n return linkedCount > 0 || cleanedCount > 0;\n}\n","/**\n * settings-manager — merge-not-overwrite utility for ~/.claude/settings.json\n *\n * Provides safe, idempotent writes to Claude Code's settings.json:\n * - env vars: added only if the key is absent (never overwrites)\n * - hooks: appended per hookType, deduplicated by command string\n * - statusLine: written only if the key is not already present\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { readJsonStrict, writeJsonAtomic } from \"../../config/json-store.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst CLAUDE_DIR = join(homedir(), \".claude\");\nconst SETTINGS_FILE = join(CLAUDE_DIR, \"settings.json\");\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type HookType =\n | \"PreCompact\"\n | \"Stop\"\n | \"SessionStart\"\n | \"SessionEnd\"\n | \"UserPromptSubmit\"\n | \"PreToolUse\"\n | \"PostToolUse\"\n | \"SubagentStop\";\n\nexport interface HookEntry {\n hookType: HookType;\n matcher?: string;\n command: string;\n}\n\nexport interface SettingsMergeOptions {\n env?: Record<string, string>;\n hooks?: HookEntry[];\n statusLine?: { type: string; command: string };\n permissions?: {\n allow?: string[];\n deny?: string[];\n };\n flags?: Record<string, unknown>;\n}\n\nexport interface MergeResult {\n changed: boolean;\n report: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal shape of settings.json hooks\n// ---------------------------------------------------------------------------\n\ninterface HookCommand {\n type: string;\n command: string;\n}\n\ninterface HookRule {\n matcher?: string;\n hooks: HookCommand[];\n}\n\ntype HooksSection = Partial<Record<HookType, HookRule[]>>;\n\n// ---------------------------------------------------------------------------\n// Read / write helpers\n// ---------------------------------------------------------------------------\n\n/**\n * ~/.claude/settings.json is Claude Code's own file, not ours: hooks, env,\n * permissions, statusline, enabled plugins. PAI only ever adds to it. Returning\n * {} for a damaged file and then writing meant a stray parse error could strip\n * every hook registration the user had.\n */\nexport function readSettingsJson(): Record<string, unknown> {\n return readJsonStrict(SETTINGS_FILE, \"~/.claude/settings.json\");\n}\n\nexport function writeSettingsJson(data: Record<string, unknown>): void {\n writeJsonAtomic(SETTINGS_FILE, data, { label: \"~/.claude/settings.json\" });\n}\n\n// ---------------------------------------------------------------------------\n// Merge helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Merge env vars — add keys that are absent, never overwrite existing ones.\n */\nfunction mergeEnv(\n settings: Record<string, unknown>,\n incoming: Record<string, string>,\n report: string[],\n): boolean {\n let changed = false;\n\n const existing = (\n typeof settings[\"env\"] === \"object\" && settings[\"env\"] !== null\n ? settings[\"env\"]\n : {}\n ) as Record<string, string>;\n\n for (const [key, value] of Object.entries(incoming)) {\n if (Object.prototype.hasOwnProperty.call(existing, key)) {\n report.push(chalk.dim(` Skipped: env.${key} already set`));\n } else {\n existing[key] = value;\n report.push(chalk.green(` Added env: ${key}`));\n changed = true;\n }\n }\n\n settings[\"env\"] = existing;\n return changed;\n}\n\n/**\n * Strip file extension from a command basename for extension-agnostic dedup.\n * This ensures that e.g. \"context-compression-hook.ts\" and\n * \"context-compression-hook.mjs\" are treated as the same hook.\n */\nfunction commandStem(cmd: string): string {\n const base = cmd.split(\"/\").pop() ?? cmd;\n return base.replace(/\\.(mjs|ts|js|sh)$/, \"\");\n}\n\n/**\n * Collect every command string already registered for a given hookType.\n * Stores full command, basename, AND extension-stripped stem for flexible\n * matching (handles ${PAI_DIR}/Hooks/foo.sh vs /Users/.../Hooks/foo.sh,\n * and .ts → .mjs migrations).\n */\nfunction existingCommandsForHookType(rules: HookRule[]): Set<string> {\n const cmds = new Set<string>();\n for (const rule of rules) {\n for (const entry of rule.hooks) {\n cmds.add(entry.command);\n // Also add the basename so expanded paths match template paths\n const base = entry.command.split(\"/\").pop();\n if (base) cmds.add(base);\n // Also add extension-stripped stem for cross-extension dedup\n cmds.add(commandStem(entry.command));\n }\n }\n return cmds;\n}\n\n/**\n * Find and remove an existing rule whose command has the same stem\n * (extension-agnostic) as the incoming command. Returns true if a\n * replacement was made. This handles .ts → .mjs migrations cleanly.\n */\nfunction replaceStaleHook(\n existingRules: HookRule[],\n incomingStem: string,\n incomingCommand: string,\n incomingMatcher: string | undefined,\n): boolean {\n for (let i = 0; i < existingRules.length; i++) {\n const rule = existingRules[i];\n for (let j = 0; j < rule.hooks.length; j++) {\n const existingStem = commandStem(rule.hooks[j].command);\n if (existingStem === incomingStem && rule.hooks[j].command !== incomingCommand) {\n // Replace the old command with the new one\n rule.hooks[j].command = incomingCommand;\n // Update matcher if provided\n if (incomingMatcher !== undefined) {\n rule.matcher = incomingMatcher;\n }\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Merge hooks — append entries, deduplicating by command string.\n * Extension-agnostic: a .mjs hook replaces an existing .ts hook with the\n * same stem, ensuring clean .ts → .mjs migrations without duplicates.\n */\nfunction mergeHooks(\n settings: Record<string, unknown>,\n incoming: HookEntry[],\n report: string[],\n): boolean {\n let changed = false;\n\n const hooksSection = (\n typeof settings[\"hooks\"] === \"object\" && settings[\"hooks\"] !== null\n ? settings[\"hooks\"]\n : {}\n ) as HooksSection;\n\n for (const entry of incoming) {\n const { hookType, matcher, command } = entry;\n\n const existingRules: HookRule[] = Array.isArray(hooksSection[hookType])\n ? (hooksSection[hookType] as HookRule[])\n : [];\n\n const basename = command.split(\"/\").pop() ?? command;\n const stem = commandStem(command);\n\n // Exact match — already registered, skip\n const existingCmds = existingCommandsForHookType(existingRules);\n if (existingCmds.has(command) || existingCmds.has(basename)) {\n report.push(chalk.dim(` Skipped: hook ${hookType} → ${basename} already registered`));\n continue;\n }\n\n // Stem match with different extension — replace old entry (.ts → .mjs migration)\n if (existingCmds.has(stem)) {\n if (replaceStaleHook(existingRules, stem, command, matcher)) {\n hooksSection[hookType] = existingRules;\n report.push(chalk.yellow(` Upgraded: hook ${hookType} → ${basename} (replaced stale extension)`));\n changed = true;\n continue;\n }\n }\n\n // No match at all — append new rule\n const newRule: HookRule = {\n hooks: [{ type: \"command\", command }],\n };\n if (matcher !== undefined) {\n newRule.matcher = matcher;\n }\n\n existingRules.push(newRule);\n hooksSection[hookType] = existingRules;\n\n report.push(chalk.green(` Added hook: ${hookType} → ${basename}`));\n changed = true;\n }\n\n settings[\"hooks\"] = hooksSection;\n return changed;\n}\n\n/**\n * Merge statusLine — write only if the key is not already present.\n */\nfunction mergeStatusLine(\n settings: Record<string, unknown>,\n incoming: { type: string; command: string },\n report: string[],\n): boolean {\n if (Object.prototype.hasOwnProperty.call(settings, \"statusLine\")) {\n report.push(chalk.dim(\" Skipped: statusLine already configured\"));\n return false;\n }\n\n settings[\"statusLine\"] = { ...incoming };\n report.push(chalk.green(\" Added statusLine\"));\n return true;\n}\n\n/**\n * Merge permissions — append allow/deny entries, deduplicating.\n */\nfunction mergePermissions(\n settings: Record<string, unknown>,\n incoming: { allow?: string[]; deny?: string[] },\n report: string[],\n): boolean {\n let changed = false;\n\n const perms = (\n typeof settings[\"permissions\"] === \"object\" && settings[\"permissions\"] !== null\n ? settings[\"permissions\"]\n : {}\n ) as Record<string, string[]>;\n\n for (const list of [\"allow\", \"deny\"] as const) {\n const entries = incoming[list];\n if (!entries || entries.length === 0) continue;\n\n const existing: string[] = Array.isArray(perms[list]) ? perms[list] : [];\n const existingSet = new Set(existing);\n\n for (const entry of entries) {\n if (existingSet.has(entry)) {\n report.push(chalk.dim(` Skipped: permissions.${list} \"${entry}\" already present`));\n } else {\n existing.push(entry);\n existingSet.add(entry);\n report.push(chalk.green(` Added permissions.${list}: ${entry}`));\n changed = true;\n }\n }\n\n perms[list] = existing;\n }\n\n settings[\"permissions\"] = perms;\n return changed;\n}\n\n/**\n * Merge flags — set keys only if not already present, never overwrite.\n */\nfunction mergeFlags(\n settings: Record<string, unknown>,\n incoming: Record<string, unknown>,\n report: string[],\n): boolean {\n let changed = false;\n\n for (const [key, value] of Object.entries(incoming)) {\n if (Object.prototype.hasOwnProperty.call(settings, key)) {\n report.push(chalk.dim(` Skipped: ${key} already set`));\n } else {\n settings[key] = value;\n report.push(chalk.green(` Added flag: ${key}`));\n changed = true;\n }\n }\n\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// Public orchestrator\n// ---------------------------------------------------------------------------\n\n/**\n * Merge env vars, hooks, and/or a statusLine entry into ~/.claude/settings.json.\n * Never overwrites existing values — only adds what is missing.\n *\n * Returns { changed, report } where report contains human-readable lines.\n */\nexport function mergeSettings(opts: SettingsMergeOptions): MergeResult {\n const settings = readSettingsJson();\n const report: string[] = [];\n let changed = false;\n\n if (opts.env !== undefined && Object.keys(opts.env).length > 0) {\n if (mergeEnv(settings, opts.env, report)) changed = true;\n }\n\n if (opts.hooks !== undefined && opts.hooks.length > 0) {\n if (mergeHooks(settings, opts.hooks, report)) changed = true;\n }\n\n if (opts.statusLine !== undefined) {\n if (mergeStatusLine(settings, opts.statusLine, report)) changed = true;\n }\n\n if (opts.permissions !== undefined) {\n if (mergePermissions(settings, opts.permissions, report)) changed = true;\n }\n\n if (opts.flags !== undefined && Object.keys(opts.flags).length > 0) {\n if (mergeFlags(settings, opts.flags, report)) changed = true;\n }\n\n if (changed) {\n writeSettingsJson(settings);\n }\n\n return { changed, report };\n}\n","/** Steps 8b and 8: Assistant name prompt and settings.json patch. */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { mergeSettings } from \"../../settings-manager.js\";\nimport { c, line, section, type Rl, prompt, promptYesNo } from \"../utils.js\";\n\nexport async function stepDaName(rl: Rl): Promise<string> {\n section(\"Step 8b: Assistant Name\");\n line();\n line(\" Choose a name for your AI assistant. This name appears in tab titles\");\n line(\" and session notes when hooks are active.\");\n line();\n\n const answer = await prompt(rl, chalk.bold(\" Assistant name [PAI]: \"));\n const daName = answer || \"PAI\";\n line();\n console.log(c.ok(`Assistant name set to: ${daName}`));\n return daName;\n}\n\nexport async function stepSettings(rl: Rl, daName: string): Promise<boolean> {\n section(\"Step 8: Settings Patch\");\n line();\n line(\" PAI will add env vars, all hook registrations, permissions, and flags\");\n line(\" to ~/.claude/settings.json. Existing values are never overwritten.\");\n line();\n\n const patch = await promptYesNo(rl, \"Patch ~/.claude/settings.json with PAI hooks, env vars, and settings?\", true);\n if (!patch) {\n console.log(c.dim(\" Skipping settings patch.\"));\n return false;\n }\n\n const paiDir = join(homedir(), \".claude\");\n\n const result = mergeSettings({\n env: {\n PAI_DIR: paiDir,\n CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: \"80\",\n CLAUDE_CODE_MAX_OUTPUT_TOKENS: \"64000\",\n DA: daName,\n },\n hooks: [\n { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/session-commands.mjs\" },\n { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/load-core-context.mjs\" },\n { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/load-project-context.mjs\" },\n { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/initialize-session.mjs\" },\n { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type SessionStart\" },\n { hookType: \"SessionStart\", matcher: \"compact\", command: \"${PAI_DIR}/Hooks/post-compact-inject.mjs\" },\n { hookType: \"UserPromptSubmit\", command: \"${PAI_DIR}/Hooks/cleanup-session-files.mjs\" },\n { hookType: \"UserPromptSubmit\", command: \"${PAI_DIR}/Hooks/update-tab-titles.mjs\" },\n { hookType: \"UserPromptSubmit\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type UserPromptSubmit\" },\n { hookType: \"PreToolUse\", matcher: \"Bash\", command: \"${PAI_DIR}/Hooks/security-validator.mjs\" },\n { hookType: \"PreToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type PreToolUse\" },\n { hookType: \"PostToolUse\", matcher: \"TodoWrite\", command: \"${PAI_DIR}/Hooks/sync-todo-to-md.mjs\" },\n { hookType: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type PostToolUse\" },\n { hookType: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/capture-tool-output.mjs\" },\n { hookType: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/update-tab-on-action.mjs\" },\n { hookType: \"Stop\", command: \"${PAI_DIR}/Hooks/stop-hook.mjs\" },\n { hookType: \"Stop\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type Stop\" },\n { hookType: \"Stop\", command: \"${PAI_DIR}/Hooks/pai-session-stop.sh\" },\n { hookType: \"SubagentStop\", command: \"${PAI_DIR}/Hooks/subagent-stop-hook.mjs\" },\n { hookType: \"SubagentStop\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type SubagentStop\" },\n { hookType: \"SessionEnd\", command: \"${PAI_DIR}/Hooks/capture-session-summary.mjs\" },\n { hookType: \"SessionEnd\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type SessionEnd\" },\n { hookType: \"PreCompact\", command: \"${PAI_DIR}/Hooks/context-compression-hook.mjs\" },\n { hookType: \"PreCompact\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type PreCompact\" },\n { hookType: \"PreCompact\", matcher: \"\", command: \"${PAI_DIR}/Hooks/pai-pre-compact.sh\" },\n ],\n statusLine: {\n type: \"command\",\n command: \"bash ${PAI_DIR}/statusline-command.sh\",\n },\n permissions: {\n allow: [\"Bash\", \"Read\", \"Write\", \"Edit\", \"Glob\", \"Grep\", \"WebFetch\", \"WebSearch\", \"NotebookEdit\", \"TodoWrite\", \"ExitPlanMode\", \"mcp__pai\"],\n deny: [\"Bash(rm -rf /)\", \"Bash(rm -rf /*)\", \"Bash(rm -rf ~)\", \"Bash(rm -rf $HOME)\", \"Bash(sudo rm -rf /)\", \"Bash(sudo rm -rf /*)\"],\n },\n flags: {\n enableAllProjectMcpServers: true,\n },\n });\n\n line();\n for (const r of result.report) {\n console.log(r);\n }\n\n if (!result.changed) {\n console.log(c.dim(\" Settings already up-to-date. No changes made.\"));\n }\n\n return result.changed;\n}\n","/** Step 9: PAI daemon installation via launchd plist. */\n\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { c, line, section, type Rl, promptYesNo } from \"../utils.js\";\n\nexport async function stepDaemon(rl: Rl): Promise<boolean> {\n section(\"Step 9: Daemon Install\");\n line();\n line(\" The PAI daemon indexes your projects every 5 minutes in the background.\");\n line();\n\n const plistPath = join(homedir(), \"Library\", \"LaunchAgents\", \"com.pai.pai-daemon.plist\");\n const exists = existsSync(plistPath);\n\n if (exists) {\n console.log(c.dim(\" PAI daemon plist already installed.\"));\n line();\n\n const reinstall = await promptYesNo(rl, \"Reinstall the PAI daemon launchd plist?\", false);\n if (!reinstall) {\n console.log(c.dim(\" Keeping existing daemon installation.\"));\n return false;\n }\n } else {\n const install = await promptYesNo(rl, \"Install the PAI daemon to run automatically at login?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping daemon install. Run manually: pai daemon install\"));\n return false;\n }\n }\n\n line();\n const result = spawnSync(\"pai\", [\"daemon\", \"install\"], { stdio: \"inherit\" });\n\n if (result.status !== 0) {\n console.log(c.warn(\" Daemon install failed. Run manually: pai daemon install\"));\n return false;\n }\n\n console.log(c.ok(\"Daemon installed as com.pai.pai-daemon.\"));\n return true;\n}\n","/** Step 10: PAI MCP server registration in ~/.claude.json. */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { c, line, section, type Rl, promptYesNo } from \"../utils.js\";\n\nexport async function stepMcp(rl: Rl): Promise<boolean> {\n section(\"Step 10: MCP Registration\");\n line();\n line(\" Registering the PAI MCP server lets Claude Code call PAI tools directly.\");\n line();\n\n const claudeJsonPath = join(homedir(), \".claude.json\");\n if (existsSync(claudeJsonPath)) {\n try {\n const raw = readFileSync(claudeJsonPath, \"utf-8\");\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const mcpServers = parsed[\"mcpServers\"] as Record<string, unknown> | undefined;\n if (mcpServers && Object.prototype.hasOwnProperty.call(mcpServers, \"pai\")) {\n console.log(c.ok(\"PAI MCP server already registered in ~/.claude.json.\"));\n console.log(c.dim(\" Skipping MCP registration.\"));\n return false;\n }\n } catch {\n // continue\n }\n }\n\n const register = await promptYesNo(rl, \"Register the PAI MCP server in ~/.claude.json?\", true);\n if (!register) {\n console.log(c.dim(\" Skipping MCP registration. Run manually: pai mcp install\"));\n return false;\n }\n\n line();\n const result = spawnSync(\"pai\", [\"mcp\", \"install\"], { stdio: \"inherit\" });\n\n if (result.status !== 0) {\n console.log(c.warn(\" MCP registration failed. Run manually: pai mcp install\"));\n return false;\n }\n\n console.log(c.ok(\"PAI MCP server registered in ~/.claude.json.\"));\n return true;\n}\n","/** Step 11: Directory scanning configuration and registry scan prompt. */\n\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { line, section, type Rl, promptYesNo } from \"../utils.js\";\n\nexport async function stepDirectories(rl: Rl): Promise<void> {\n section(\"Step 11: Directories to Index\");\n line();\n line(\" PAI indexes files in your registered projects. You can register projects\");\n line(\" individually with `pai project add <path>`, or let the registry scanner\");\n line(\" discover them automatically with `pai registry scan`.\");\n line();\n\n const defaults = [\n join(homedir(), \"Projects\"),\n join(homedir(), \"Documents\"),\n join(homedir(), \"dev\"),\n ].filter(existsSync);\n\n if (defaults.length > 0) {\n line(\" These directories exist on your system:\");\n for (const d of defaults) {\n console.log(chalk.dim(` ${d}`));\n }\n line();\n }\n\n const runScan = await promptYesNo(rl, \"Run `pai registry scan` to auto-detect projects after setup?\", false);\n\n if (runScan) {\n line();\n console.log(chalk.dim(\" Registry scan will run after setup completes.\"));\n } else {\n console.log(chalk.dim(\" Add projects manually: pai project add <path>\"));\n console.log(chalk.dim(\" Or discover them later: pai registry scan\"));\n }\n\n (stepDirectories as { _runScan?: boolean })._runScan = runScan;\n}\n","/** Step 12: Initial index — optionally starts the daemon and runs registry scan. */\n\nimport { spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { c, line, section, type Rl, promptYesNo } from \"../utils.js\";\nimport { stepDirectories } from \"./13-directories.js\";\n\nexport async function stepInitialIndex(rl: Rl): Promise<void> {\n section(\"Step 12: Initial Index\");\n line();\n line(\" Indexing scans your registered projects and builds the search index.\");\n line(\" The daemon runs indexing automatically every 5 minutes once started.\");\n line();\n\n const willScan = (stepDirectories as { _runScan?: boolean })._runScan;\n\n if (willScan) {\n const startDaemon = await promptYesNo(rl, \"Start the PAI daemon now? (enables background indexing)\", true);\n\n if (startDaemon) {\n line();\n console.log(c.dim(\" Starting daemon...\"));\n\n try {\n const result = spawnSync(\"pai\", [\"daemon\", \"serve\", \"--background\"], { stdio: \"pipe\", timeout: 10000 });\n if (result.status === 0) {\n console.log(c.ok(\"Daemon started in background.\"));\n } else {\n console.log(c.warn(\"Could not start daemon. Run manually: pai daemon serve\"));\n }\n } catch {\n console.log(c.warn(\"Could not start daemon. Run manually: pai daemon serve\"));\n }\n\n line();\n console.log(c.dim(\" Running registry scan to detect projects...\"));\n\n try {\n const result = spawnSync(\"pai\", [\"registry\", \"scan\"], { stdio: \"inherit\", timeout: 30000 });\n if (result.status !== 0) {\n console.log(c.warn(\"Registry scan encountered issues. Run `pai registry scan` manually.\"));\n }\n } catch {\n console.log(c.warn(\"Could not run registry scan. Run manually: pai registry scan\"));\n }\n } else {\n console.log(chalk.dim(\" Start the daemon later: pai daemon serve\"));\n console.log(chalk.dim(\" Scan projects later: pai registry scan\"));\n }\n } else {\n console.log(chalk.dim(\" Register projects with: pai project add <path>\"));\n console.log(chalk.dim(\" Then index them with: pai memory index --all\"));\n console.log(chalk.dim(\" Or start the daemon: pai daemon serve\"));\n }\n}\n","/** Step 13: Setup summary — displays all configuration choices made during setup. */\n\nimport chalk from \"chalk\";\nimport { CONFIG_FILE } from \"../../../../daemon/config.js\";\nimport { line, section } from \"../utils.js\";\n\nexport function stepSummary(\n configUpdates: Record<string, unknown>,\n claudeMdGenerated: boolean,\n paiSkillInstalled: boolean,\n aiSteeringRulesInstalled: boolean,\n skillStubsInstalled: boolean,\n hooksInstalled: boolean,\n tsHooksInstalled: boolean,\n settingsPatched: boolean,\n daName: string,\n daemonInstalled: boolean,\n mcpRegistered: boolean,\n): void {\n section(\"Setup Complete\");\n line();\n console.log(chalk.green(\" PAI Knowledge OS is configured!\"));\n line();\n\n const backend = configUpdates.storageBackend as string;\n const model = configUpdates.embeddingModel as string;\n\n line(chalk.bold(\" Configuration saved to: \") + chalk.dim(CONFIG_FILE));\n line();\n console.log(chalk.dim(\" Storage backend: \") + chalk.cyan(backend ?? \"sqlite\"));\n console.log(chalk.dim(\" Embedding model: \") + chalk.cyan(model && model !== \"none\" ? model : \"(none — keyword search only)\"));\n console.log(chalk.dim(\" CLAUDE.md: \") + chalk.cyan(claudeMdGenerated ? \"~/.claude/CLAUDE.md (generated)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" PAI skill: \") + chalk.cyan(paiSkillInstalled ? \"~/.claude/skills/PAI/SKILL.md (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Steering rules: \") + chalk.cyan(aiSteeringRulesInstalled ? \"~/.claude/skills/PAI/AI-STEERING-RULES.md (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Skill stubs: \") + chalk.cyan(skillStubsInstalled ? \"18 MCP prompt stubs symlinked to ~/.claude/skills/\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Hooks (shell): \") + chalk.cyan(hooksInstalled ? \"pai-pre-compact.sh, pai-session-stop.sh (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Hooks (TS): \") + chalk.cyan(tsHooksInstalled ? \"14 .mjs hooks installed to ~/.claude/Hooks/\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Assistant name: \") + chalk.cyan(daName));\n console.log(chalk.dim(\" Settings: \") + chalk.cyan(settingsPatched ? \"env vars, hooks, permissions, flags (patched)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Daemon: \") + chalk.cyan(daemonInstalled ? \"com.pai.pai-daemon (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" MCP: \") + chalk.cyan(mcpRegistered ? \"registered in ~/.claude.json\" : \"(unchanged)\"));\n line();\n console.log(chalk.bold.yellow(\" → RESTART Claude Code to activate all changes.\"));\n line();\n\n line(chalk.bold(\" Next steps:\"));\n line();\n console.log(chalk.dim(\" # Register a project\"));\n console.log(chalk.cyan(\" pai project add ~/your/project\"));\n line();\n console.log(chalk.dim(\" # Index your files\"));\n console.log(chalk.cyan(\" pai memory index --all\"));\n line();\n console.log(chalk.dim(\" # Search your knowledge\"));\n console.log(chalk.cyan(\" pai memory search \\\"your query\\\"\"));\n line();\n if (model && model !== \"none\") {\n console.log(chalk.dim(\" # Generate embeddings for semantic search\"));\n console.log(chalk.cyan(\" pai memory embed\"));\n line();\n console.log(chalk.dim(\" # Semantic search\"));\n console.log(chalk.cyan(\" pai memory search --mode semantic \\\"your query\\\"\"));\n line();\n }\n console.log(chalk.dim(\" # Start the background daemon\"));\n console.log(chalk.cyan(\" pai daemon serve\"));\n line();\n console.log(chalk.dim(\" # Show all commands\"));\n console.log(chalk.cyan(\" pai --help\"));\n line();\n}\n","/**\n * resolver.ts — Ownership resolution for the task bus\n *\n * Maps a tracker task onto the PAI project that owns it.\n *\n * 1. an explicit `pai:<project>` label (authoritative)\n * 2. the enclosing container name (fallback, matches the sub-project mirror)\n * 3. otherwise UNROUTED (normal state — the findings inbox)\n *\n * Label wins because labels survive a task being moved between containers, and\n * because a task parked in the findings inbox has no meaningful container yet.\n *\n * See Notes/docs/task-bus.md.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n OWNER_LABEL_PREFIX,\n UNROUTED,\n type TaskOwner,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Alias map\n// ---------------------------------------------------------------------------\n\nexport interface AliasTarget {\n /** The alias itself — what `pai <name>` and aibroker_pai_launch accept. */\n alias: string;\n slug: string;\n rootPath: string;\n}\n\n/** Normalized alias → target. Keys are the output of `normalize()`. */\nexport type AliasMap = Map<string, AliasTarget>;\n\n/**\n * Fold a tracker-supplied string onto its comparable form.\n *\n * Container names are written for humans and carry decoration the registry\n * never sees: \"Reading List 📚\", \"AcmeAPI\", \"Client Work\". Strip the decoration\n * rather than demanding the user keep two naming schemes in sync.\n *\n * Emoji and pictographs are removed, separators collapse to a single hyphen,\n * and the result is lowercased — so \"Client Work\" and \"client-work\" agree.\n */\nexport function normalize(raw: string): string {\n return raw\n .normalize(\"NFKD\")\n // Drop emoji, pictographs, dingbats and variation selectors.\n .replace(/[\\u{1F000}-\\u{1FAFF}\\u{2600}-\\u{27BF}\\u{FE0F}\\u{200D}]/gu, \"\")\n .replace(/[^\\p{L}\\p{N}]+/gu, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .toLowerCase();\n}\n\n/**\n * Load every alias registered in the PAI registry.\n *\n * Reads the curated shortlist only — the `aliases` table — deliberately. The\n * full registry commonly carries hundreds of projects with no aliases and\n * genuine ambiguity — separate entries routinely share a display name at\n * different paths — so widening this query resolves tasks to the wrong\n * directory. That failure is silent, which makes it worse than no match. Bus\n * participation is opt-in via `pai project name <identifier> <shortname>`.\n */\nexport function loadAliasMap(db: Database): AliasMap {\n const rows = db\n .prepare(\n `SELECT a.alias AS alias,\n p.slug AS slug,\n p.root_path AS rootPath\n FROM aliases a\n JOIN projects p ON p.id = a.project_id\n WHERE p.status != 'archived'`\n )\n .all() as Array<{ alias: string; slug: string; rootPath: string }>;\n\n const map: AliasMap = new Map();\n for (const row of rows) {\n const target: AliasTarget = {\n alias: row.alias,\n slug: row.slug,\n rootPath: row.rootPath,\n };\n map.set(normalize(row.alias), target);\n // The slug is also addressable, so a task may name either.\n // Aliases win: they are the curated, intentional handle.\n const slugKey = normalize(row.slug);\n if (!map.has(slugKey)) map.set(slugKey, target);\n }\n return map;\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/** Extract the project named by a `pai:<project>` label, if any. */\nexport function ownerLabel(labels: string[]): string | null {\n for (const label of labels) {\n const trimmed = label.trim();\n if (trimmed.toLowerCase().startsWith(OWNER_LABEL_PREFIX)) {\n const value = trimmed.slice(OWNER_LABEL_PREFIX.length).trim();\n if (value) return value;\n }\n }\n return null;\n}\n\nexport interface ResolveInput {\n labels: string[];\n /** Name of the enclosing tracker container (sub-project), when there is one. */\n container?: string | null;\n}\n\n/**\n * Resolve a task's owner.\n *\n * An unresolvable task is never an error: it returns UNROUTED with `rawHint`\n * set to whatever was tried, so a routine can say *why* it could not route it\n * rather than dropping it silently. A container like \"Reading List 📚\" maps to\n * no PAI project — that is expected, not a fault.\n */\nexport function resolveOwner(input: ResolveInput, aliases: AliasMap): TaskOwner {\n const label = ownerLabel(input.labels);\n if (label) {\n const hit = aliases.get(normalize(label));\n if (hit) {\n return {\n project: hit.alias,\n rootPath: hit.rootPath,\n source: \"label\",\n };\n }\n // An explicit label that resolves to nothing is worth surfacing: the user\n // meant to route this somewhere. Do not silently fall through to the\n // container, which would send it to the wrong project.\n return { ...UNROUTED, rawHint: label };\n }\n\n if (input.container) {\n const hit = aliases.get(normalize(input.container));\n if (hit) {\n return {\n project: hit.alias,\n rootPath: hit.rootPath,\n source: \"container\",\n };\n }\n return { ...UNROUTED, rawHint: input.container };\n }\n\n return { ...UNROUTED };\n}\n","/**\n * todoist.ts — Todoist provider for the task bus\n *\n * Talks to the Todoist REST API directly rather than going through the Todoist\n * MCP. The MCP is scoped to a Claude session; the daemon has no session, so an\n * MCP-only integration could never run the bus unattended.\n *\n * See Notes/docs/task-bus.md.\n */\n\nimport type {\n ListOptions,\n NewTask,\n Task,\n TaskPriority,\n TaskProvider,\n TodoistProviderConfig,\n} from \"../types.js\";\nimport { UNROUTED } from \"../types.js\";\nimport type { AliasMap } from \"../resolver.js\";\nimport { resolveOwner } from \"../resolver.js\";\n\n/**\n * Unified API v1. The older `/rest/v2` endpoints now return 410 Gone — they\n * were sunset, so anything still targeting them fails outright rather than\n * degrading.\n */\nconst API = \"https://api.todoist.com/api/v1\";\n\n// ---------------------------------------------------------------------------\n// Wire types (only the fields we rely on)\n// ---------------------------------------------------------------------------\n\n/** v1 wraps every collection and pages with an opaque cursor. */\ninterface WirePage<T> {\n results: T[];\n next_cursor?: string | null;\n}\n\ninterface WireProject {\n id: string;\n name: string;\n parent_id?: string | null;\n is_archived?: boolean;\n is_deleted?: boolean;\n}\n\ninterface WireTask {\n id: string;\n content: string;\n description?: string;\n project_id: string;\n section_id?: string | null;\n labels?: string[];\n priority?: number; // 4 = p1 … 1 = p4\n due?: { date?: string; datetime?: string } | null;\n /** v1 returns completed and tombstoned tasks inline; both must be filtered. */\n checked?: boolean;\n is_deleted?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nasync function call<T>(\n token: string,\n path: string,\n init?: { method?: string; body?: unknown },\n): Promise<T> {\n const res = await fetch(`${API}${path}`, {\n method: init?.method ?? \"GET\",\n headers: {\n Authorization: `Bearer ${token}`,\n ...(init?.body ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: init?.body ? JSON.stringify(init.body) : undefined,\n });\n\n if (!res.ok) {\n // 401 is by far the most common failure and the least self-explanatory,\n // so name the cause rather than surfacing a bare status code.\n const hint = res.status === 401 ? \" (token rejected — check it was copied in full)\" : \"\";\n throw new Error(`Todoist ${res.status} ${res.statusText}${hint}`);\n }\n\n // 204 on delete/close — nothing to parse.\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n}\n\n/**\n * Drain a paginated collection.\n *\n * Every v1 list endpoint pages. Stopping at the first page would silently\n * truncate — the account here already exceeds one page of tasks — so follow the\n * cursor to exhaustion. The guard is a safety stop against a server that keeps\n * handing back the same cursor, not an intentional cap.\n */\nasync function collect<T>(token: string, path: string): Promise<T[]> {\n const out: T[] = [];\n let cursor: string | null | undefined;\n let guard = 0;\n\n do {\n const sep = path.includes(\"?\") ? \"&\" : \"?\";\n const url = cursor ? `${path}${sep}cursor=${encodeURIComponent(cursor)}` : path;\n const page = await call<WirePage<T>>(token, url);\n out.push(...(page.results ?? []));\n cursor = page.next_cursor;\n } while (cursor && ++guard < 100);\n\n return out;\n}\n\n/**\n * List every live project on the account.\n *\n * Exported because setup uses it both to let the user pick the bus root and to\n * validate the token — a bad token fails here, at install time, rather than\n * silently returning nothing during a routine.\n */\nexport async function listProjects(token: string): Promise<Array<{ id: string; name: string }>> {\n const projects = await collect<WireProject>(token, \"/projects\");\n return projects\n .filter((p) => !p.is_archived && !p.is_deleted)\n .map((p) => ({ id: p.id, name: p.name }));\n}\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\n/** Todoist priority is inverted: 4 is urgent, 1 is lowest. */\nfunction toPriority(wire: number | undefined): TaskPriority {\n switch (wire) {\n case 4: return \"p1\";\n case 3: return \"p2\";\n case 2: return \"p3\";\n default: return \"p4\";\n }\n}\n\nfunction fromPriority(p: TaskPriority | undefined): number {\n switch (p) {\n case \"p1\": return 4;\n case \"p2\": return 3;\n case \"p3\": return 2;\n default: return 1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport class TodoistProvider implements TaskProvider {\n readonly providerId = \"todoist\" as const;\n\n constructor(\n private readonly config: TodoistProviderConfig,\n private readonly aliases: AliasMap,\n ) {}\n\n /**\n * Resolution order is config → env → unconfigured. A missing token disables\n * the provider rather than throwing: a user without a tracker still gets a\n * fully working PAI.\n */\n private token(): string | null {\n return this.config.apiKey?.trim() || process.env.TODOIST_API_KEY?.trim() || null;\n }\n\n isConfigured(): boolean {\n return Boolean(this.config.enabled && this.token() && this.config.rootProjectId);\n }\n\n async listOpen(opts: ListOptions = {}): Promise<Task[]> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) return [];\n\n // Fetch the project tree once and keep only the bus subtree. Filtering\n // client-side is deliberate: Todoist's project search returns zero results\n // for names containing emoji, so a name query would silently find nothing.\n const projects = await collect<WireProject>(token, \"/projects\");\n const root = this.config.rootProjectId;\n const inBus = new Map<string, WireProject>();\n for (const p of projects) {\n if (p.is_archived || p.is_deleted) continue;\n if (p.id === root || p.parent_id === root) inBus.set(p.id, p);\n }\n if (inBus.size === 0) return [];\n\n // Query per bus project rather than draining every task on the account.\n // The account may hold thousands; the bus holds a handful.\n const wire: WireTask[] = [];\n for (const id of inBus.keys()) {\n wire.push(...(await collect<WireTask>(token, `/tasks?project_id=${encodeURIComponent(id)}`)));\n }\n\n const out: Task[] = [];\n\n for (const w of wire) {\n if (w.checked || w.is_deleted) continue;\n\n const container = inBus.get(w.project_id);\n if (!container) continue;\n\n // The bus root itself is not an owner — only its sub-projects mirror\n // PAI projects. A task sitting at the root has no container hint.\n const containerName = container.id === root ? null : container.name;\n const owner = resolveOwner({ labels: w.labels ?? [], container: containerName }, this.aliases);\n\n if (opts.owner && owner.project !== opts.owner) continue;\n if (opts.includeUnrouted === false && owner.project === null) continue;\n\n const due = w.due?.datetime ?? w.due?.date ?? null;\n if (opts.dueBefore) {\n // No due date means no deadline to miss — such tasks are never overdue,\n // so they are excluded from a date-bounded sweep rather than always shown.\n if (!due || due.slice(0, 10) > opts.dueBefore) continue;\n }\n\n out.push({\n id: w.id,\n title: w.content,\n body: w.description ?? \"\",\n owner,\n due,\n priority: toPriority(w.priority),\n labels: w.labels ?? [],\n // v1 does not return a task URL. Build the deep link from the ID so a\n // dispatched task is still one click from the tracker.\n sourceUrl: `https://app.todoist.com/app/task/${w.id}`,\n });\n\n if (opts.limit && out.length >= opts.limit) break;\n }\n\n return out;\n }\n\n async add(task: NewTask): Promise<Task> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) {\n throw new Error(\"Todoist provider is not configured — run `pai setup`.\");\n }\n\n const labels = [...(task.labels ?? [])];\n if (task.owner && !labels.some((l) => l.toLowerCase() === `pai:${task.owner!.toLowerCase()}`)) {\n labels.push(`pai:${task.owner}`);\n }\n\n // Unowned work lands in the findings inbox when one is configured, so it is\n // triaged later rather than lost at the root.\n const sectionId = task.owner ? undefined : this.config.findingsSectionId;\n\n const created = await call<WireTask>(token, \"/tasks\", {\n method: \"POST\",\n body: {\n content: task.title,\n description: task.body,\n project_id: this.config.rootProjectId,\n ...(sectionId ? { section_id: sectionId } : {}),\n labels,\n priority: fromPriority(task.priority),\n ...(task.due ? { due_date: task.due } : {}),\n },\n });\n\n return {\n id: created.id,\n title: created.content,\n body: created.description ?? \"\",\n owner: task.owner\n ? resolveOwner({ labels, container: null }, this.aliases)\n : { ...UNROUTED },\n due: created.due?.datetime ?? created.due?.date ?? null,\n priority: toPriority(created.priority),\n labels: created.labels ?? [],\n sourceUrl: `https://app.todoist.com/app/task/${created.id}`,\n };\n }\n\n /**\n * Replace a task's labels.\n *\n * Only ever sends `labels`. Never `due_date`: writing that field destroys a\n * recurrence rule, silently turning a routine into a one-off. Measured — it\n * cost me an invalid test before it cost anyone a schedule.\n */\n async setLabels(id: string, labels: string[]): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n await call<WireTask>(token, `/tasks/${id}`, { method: \"POST\", body: { labels } });\n }\n\n /** Append a comment — used to keep run history on the task itself. */\n async comment(id: string, content: string): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n await call<unknown>(token, `/comments`, { method: \"POST\", body: { task_id: id, content } });\n }\n\n async complete(id: string): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai setup`.\");\n await call<void>(token, `/tasks/${id}/close`, { method: \"POST\" });\n }\n}\n","/** Step 16: Task bus — optional external tracker for cross-session work. */\n\nimport { c, line, section, type Rl, prompt, promptYesNo, readConfigRaw } from \"../utils.js\";\nimport { listProjects } from \"../../../../tasks/providers/todoist.js\";\nimport type { TaskConfig } from \"../../../../tasks/types.js\";\n\n/** Shape of the `tasks` block as it sits in the raw config file. */\ntype RawTaskConfig = Partial<TaskConfig> & {\n providers?: { todoist?: { apiKey?: string; rootProjectId?: string; enabled?: boolean } };\n};\n\nexport async function stepTaskBus(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 16: Task Bus (Optional)\");\n\n const existing = readConfigRaw();\n const current = (existing.tasks ?? {}) as RawTaskConfig;\n const todoist = current.providers?.todoist;\n\n if (todoist?.apiKey && todoist?.rootProjectId) {\n console.log(c.ok(\"Task bus already configured. Skipping.\"));\n return { tasks: current };\n }\n\n line();\n line(\" The task bus lets any PAI session file work into a shared tracker, so a\");\n line(\" routine can pick it up later and hand it to the session that owns it.\");\n line();\n line(\" PAI works fully without this. Skip it unless you want cross-session tasks.\");\n line();\n\n const wanted = await promptYesNo(rl, \"Enable the task bus (requires a Todoist account)?\", false);\n if (!wanted) {\n line();\n console.log(c.ok(\"Skipping the task bus.\"));\n console.log(c.dim(\" Add later by re-running `pai setup`.\"));\n return { tasks: { enabled: false } };\n }\n\n // -- Token -----------------------------------------------------------------\n // Prefer an existing env token so the user need not paste a credential twice.\n // Never read it from ~/.claude.json: the Todoist MCP keeps one there, but that\n // belongs to a different tool and PAI must not scrape another program's secrets.\n line();\n line(\" Get your API token from Todoist:\");\n line(c.dim(\" Settings -> Integrations -> Developer\"));\n line(c.dim(\" https://app.todoist.com/app/settings/integrations/developer\"));\n line();\n line(\" It grants full read/write access to your Todoist. Treat it like a password.\");\n line();\n\n const envToken = process.env.TODOIST_API_KEY?.trim();\n let apiKey = todoist?.apiKey ?? \"\";\n\n if (!apiKey && envToken) {\n const useEnv = await promptYesNo(rl, \"Found TODOIST_API_KEY in the environment. Use it?\", true);\n if (useEnv) apiKey = envToken;\n }\n\n while (!apiKey) {\n apiKey = (await prompt(rl, \" Paste your Todoist API token: \")).trim();\n if (!apiKey) console.log(c.warn(\" A token is required, or answer no to skip the task bus.\"));\n }\n\n // -- Root project ----------------------------------------------------------\n // Listing projects doubles as token validation: a bad token fails here, at\n // setup time, rather than silently returning nothing during a morning routine.\n line();\n line(\" Fetching your Todoist projects...\");\n\n let projects: Array<{ id: string; name: string }>;\n try {\n projects = await listProjects(apiKey);\n } catch (e) {\n line();\n console.log(c.warn(`Could not reach Todoist: ${e instanceof Error ? e.message : String(e)}`));\n console.log(c.dim(\" The token may be wrong, or the network is unavailable.\"));\n console.log(c.dim(\" Saved nothing. Re-run `pai setup` to try again.\"));\n return { tasks: { enabled: false } };\n }\n\n if (projects.length === 0) {\n console.log(c.warn(\" No projects found. Create one in Todoist first, then re-run setup.\"));\n return { tasks: { enabled: false } };\n }\n\n line();\n line(\" Which project should hold the bus? Create a dedicated one if unsure.\");\n line();\n projects.forEach((p, i) => line(` ${String(i + 1).padStart(2)}. ${p.name}`));\n line();\n\n let rootProjectId = \"\";\n while (!rootProjectId) {\n const answer = (await prompt(rl, ` Project number [1-${projects.length}]: `)).trim();\n const idx = Number.parseInt(answer, 10) - 1;\n if (Number.isInteger(idx) && idx >= 0 && idx < projects.length) {\n rootProjectId = projects[idx]!.id;\n } else {\n console.log(c.warn(` Enter a number between 1 and ${projects.length}.`));\n }\n }\n\n const chosen = projects.find((p) => p.id === rootProjectId)!;\n\n // The ID is stored, never the name. Todoist's project search silently returns\n // zero results for names containing emoji, so resolving by name later would\n // report \"no tasks\" instead of failing — the exact silent failure this\n // subsystem exists to surface.\n line();\n console.log(c.ok(`Task bus enabled on \"${chosen.name}\" (id ${rootProjectId}).`));\n console.log(c.dim(\" Label a task `pai:<project>` to assign it to a PAI project.\"));\n console.log(c.dim(\" Only projects with an alias can be dispatched to — see `pai project name`.\"));\n\n const autoDispatch = await promptYesNo(\n rl,\n \"Automatically hand tasks to the owning session (needs AIBroker)?\",\n false,\n );\n\n return {\n tasks: {\n enabled: true,\n autoDispatch,\n providers: {\n todoist: { enabled: true, apiKey, rootProjectId },\n },\n },\n };\n}\n","/**\n * PAI setup wizard — main entry point.\n * Orchestrates all setup steps in order and registers the Commander command.\n */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { existsSync } from \"node:fs\";\nimport { CONFIG_FILE, loadConfig } from \"../../../daemon/config.js\";\nimport { createRl, prompt, promptYesNo, line, mergeConfig } from \"./utils.js\";\nimport {\n stepWelcome,\n stepStorage,\n stepEmbedding,\n stepClaudeMd,\n stepPaiSkill,\n stepAiSteeringRules,\n stepSkillStubs,\n stepHooks,\n stepTsHooks,\n stepDaName,\n stepSettings,\n stepDaemon,\n stepMcp,\n stepDirectories,\n stepTaskBus,\n stepInitialIndex,\n stepSummary,\n} from \"./steps/index.js\";\n\nasync function runSetup(): Promise<void> {\n const rl = createRl();\n\n try {\n if (existsSync(CONFIG_FILE)) {\n const current = loadConfig();\n line();\n console.log(\n chalk.yellow(\" Note: PAI is already configured.\") +\n chalk.dim(\" Proceeding will update your existing configuration.\"),\n );\n console.log(chalk.dim(` Config: ${CONFIG_FILE}`));\n console.log(chalk.dim(` Current backend: ${current.storageBackend}`));\n line();\n\n const proceed = await promptYesNo(rl, \"Continue and update configuration?\", true);\n if (!proceed) {\n rl.close();\n line(chalk.dim(\" Setup cancelled.\"));\n line();\n return;\n }\n }\n\n // Step 1: Welcome\n stepWelcome();\n line();\n await prompt(rl, chalk.dim(\" Press Enter to begin setup...\"));\n\n // Step 2: Storage\n const storageConfig = await stepStorage(rl);\n\n // Step 3: Embeddings\n const embeddingConfig = await stepEmbedding(rl);\n\n // Step 4: Agent configuration (CLAUDE.md)\n const claudeMdGenerated = await stepClaudeMd(rl);\n\n // Step 5: PAI Skill\n const paiSkillInstalled = await stepPaiSkill(rl);\n\n // Step 6: AI Steering Rules\n const aiSteeringRulesInstalled = await stepAiSteeringRules(rl);\n\n // Step 7: Skill Stubs\n const skillStubsInstalled = await stepSkillStubs(rl);\n\n // Step 8: Hooks (shell scripts)\n const hooksInstalled = await stepHooks(rl);\n\n // Step 7b: TypeScript hooks (.mjs files)\n const tsHooksInstalled = await stepTsHooks(rl);\n\n // Step 8b: DA name\n const daName = await stepDaName(rl);\n\n // Step 8: Settings.json\n const settingsPatched = await stepSettings(rl, daName);\n\n // Step 9: Daemon\n const daemonInstalled = await stepDaemon(rl);\n\n // Step 10: MCP\n const mcpRegistered = await stepMcp(rl);\n\n // Step 11: Directories (informational — no config written)\n await stepDirectories(rl);\n\n // Step 11b: Task bus (optional external tracker)\n const taskConfig = await stepTaskBus(rl);\n\n // Write config after gathering all choices\n const allUpdates = { ...storageConfig, ...embeddingConfig, ...taskConfig };\n mergeConfig(allUpdates);\n\n line();\n console.log(chalk.green(\" Configuration saved.\"));\n\n // Step 12: Initial index\n await stepInitialIndex(rl);\n\n // Step 13: Summary\n stepSummary(\n allUpdates,\n claudeMdGenerated,\n paiSkillInstalled,\n aiSteeringRulesInstalled,\n skillStubsInstalled,\n hooksInstalled,\n tsHooksInstalled,\n settingsPatched,\n daName,\n daemonInstalled,\n mcpRegistered,\n );\n\n } finally {\n rl.close();\n }\n}\n\nexport function registerSetupCommand(program: Command): void {\n program\n .command(\"setup\")\n .alias(\"install\")\n .description(\n \"Interactive setup wizard — configure storage, embeddings, agent config, and indexing\",\n )\n .action(async () => {\n await runSetup();\n });\n}\n","/** Symlink management: create, validate, migrate, and clean vault project symlinks. */\n\nimport {\n existsSync,\n mkdirSync,\n symlinkSync,\n readdirSync,\n lstatSync,\n unlinkSync,\n readlinkSync,\n writeFileSync,\n rmdirSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport type { ProjectRow, SyncStats } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Find the project-root Notes directory for a project. */\nexport function findNotesDir(rootPath: string): string | null {\n const canonical = join(rootPath, \"Notes\");\n if (existsSync(canonical)) return canonical;\n const alt = join(rootPath, \".claude\", \"Notes\");\n if (existsSync(alt)) return alt;\n return null;\n}\n\n/**\n * Find the Claude Code session notes directory from the registry-stored value.\n * Returns null if not set, missing on disk, or identical to notesDir.\n */\nexport function findClaudeNotesDir(\n claudeNotesDirFromRegistry: string | null,\n notesDir: string | null\n): string | null {\n if (!claudeNotesDirFromRegistry) return null;\n if (!existsSync(claudeNotesDirFromRegistry)) return null;\n if (notesDir && claudeNotesDirFromRegistry === notesDir) return null;\n return claudeNotesDirFromRegistry;\n}\n\n/** Check whether a path exists via lstat (does not follow symlinks). */\nfunction lstatExists(p: string): boolean {\n try {\n lstatSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Resolve slug collisions by appending -2, -3, etc. */\nexport function uniqueSlug(base: string, taken: Set<string>): string {\n if (!taken.has(base)) return base;\n let n = 2;\n while (taken.has(`${base}-${n}`)) n++;\n return `${base}-${n}`;\n}\n\n/**\n * Remove broken symlinks from a directory (one level deep).\n * Returns count of entries removed.\n */\nexport function cleanBrokenSymlinks(dir: string): number {\n let removed = 0;\n if (!existsSync(dir)) return removed;\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry);\n try {\n const stat = lstatSync(full);\n if (stat.isSymbolicLink()) {\n const target = readlinkSync(full);\n if (!existsSync(target)) {\n unlinkSync(full);\n removed++;\n }\n }\n } catch {\n // Skip unreadable entries\n }\n }\n return removed;\n}\n\n/**\n * Ensure a sub-symlink inside a project directory is correct.\n * Returns true if a new symlink was created, false otherwise.\n */\nexport function ensureSubSymlink(\n linkPath: string,\n target: string,\n errors: string[],\n label: string\n): boolean {\n if (lstatExists(linkPath)) {\n try {\n const stat = lstatSync(linkPath);\n if (stat.isSymbolicLink()) {\n const current = readlinkSync(linkPath);\n if (current === target) {\n return false; // Already correct\n }\n unlinkSync(linkPath);\n } else {\n errors.push(`${label}: path exists but is not a symlink — skipped`);\n return false;\n }\n } catch (e) {\n errors.push(`${label}: error checking existing path — ${e}`);\n return false;\n }\n }\n\n try {\n symlinkSync(target, linkPath);\n return true;\n } catch (e) {\n errors.push(`${label}: symlink creation failed — ${e}`);\n return false;\n }\n}\n\n/**\n * Migrate a legacy flat symlink at `slugPath` to a real directory.\n * If `slugPath` is already a real directory, this is a no-op.\n * If it is a symlink (legacy), it is removed so mkdirSync can create the dir.\n */\nexport function migrateToProjectDir(\n slugPath: string,\n errors: string[],\n slug: string\n): boolean {\n if (!lstatExists(slugPath)) {\n return true; // Does not exist yet — mkdirSync will create it\n }\n\n try {\n const stat = lstatSync(slugPath);\n\n if (stat.isDirectory()) {\n return true; // Already a real directory — nothing to migrate\n }\n\n if (stat.isSymbolicLink()) {\n unlinkSync(slugPath);\n return true; // Removed old symlink — now ready for mkdirSync\n }\n\n errors.push(`${slug}: path exists as non-directory, non-symlink — skipped`);\n return false;\n } catch (e) {\n errors.push(`${slug}: error during migration — ${e}`);\n return false;\n }\n}\n\n/** Remove a project directory from the vault if it is empty. */\nfunction removeProjectDirIfEmpty(slugPath: string): void {\n try {\n const entries = readdirSync(slugPath);\n if (entries.length === 0) {\n rmdirSync(slugPath);\n }\n } catch {\n // Non-fatal\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: syncVault\n// ---------------------------------------------------------------------------\n\n/**\n * Sync all active project Notes directories into the Obsidian vault.\n *\n * For each active project with at least one Notes source, creates:\n * {vault}/{slug}/ — real directory\n * notes → {root}/Notes/\n * sessions → ~/.claude/projects/{enc}/Notes/ (if different)\n *\n * Archived projects get a stub markdown file in {vault}/_archive/.\n */\nexport function syncVault(vaultPath: string, db: Database): SyncStats {\n const stats: SyncStats = { created: 0, updated: 0, removed: 0, stubbed: 0, errors: [] };\n\n mkdirSync(vaultPath, { recursive: true });\n stats.removed += cleanBrokenSymlinks(vaultPath);\n\n const projects = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir, status, obsidian_link, claude_notes_dir\n FROM projects\n ORDER BY status ASC, slug ASC`\n )\n .all() as ProjectRow[];\n\n const takenSlugs = new Set<string>();\n\n for (const project of projects) {\n if (project.status === \"active\") {\n const notesDir = findNotesDir(project.root_path);\n const claudeNotesDir = findClaudeNotesDir(project.claude_notes_dir, notesDir);\n\n if (!notesDir && !claudeNotesDir) {\n continue;\n }\n\n const slug = uniqueSlug(project.slug, takenSlugs);\n takenSlugs.add(slug);\n const slugPath = join(vaultPath, slug);\n\n if (!migrateToProjectDir(slugPath, stats.errors, slug)) {\n continue;\n }\n\n try {\n mkdirSync(slugPath, { recursive: true });\n } catch (e) {\n stats.errors.push(`${slug}: failed to create project directory — ${e}`);\n continue;\n }\n\n if (notesDir) {\n const notesLink = join(slugPath, \"notes\");\n const created = ensureSubSymlink(notesLink, notesDir, stats.errors, `${slug}/notes`);\n if (created) stats.created++;\n else stats.updated++;\n }\n\n if (claudeNotesDir) {\n const sessionsLink = join(slugPath, \"sessions\");\n const created = ensureSubSymlink(sessionsLink, claudeNotesDir, stats.errors, `${slug}/sessions`);\n if (created) stats.created++;\n else stats.updated++;\n }\n\n try {\n db.prepare(\"UPDATE projects SET obsidian_link = ?, updated_at = ? WHERE id = ?\").run(\n slugPath,\n Date.now(),\n project.id\n );\n } catch {\n // Non-fatal\n }\n } else if (project.status === \"archived\") {\n const archiveDir = join(vaultPath, \"_archive\");\n mkdirSync(archiveDir, { recursive: true });\n const stubPath = join(archiveDir, `${project.slug}.md`);\n if (!existsSync(stubPath)) {\n const content = [\n `# ${project.display_name}`,\n \"\",\n \"> Archived project — no live notes available.\",\n \"\",\n `- **Slug:** ${project.slug}`,\n `- **Root:** ${project.root_path}`,\n \"\",\n ].join(\"\\n\");\n try {\n writeFileSync(stubPath, content, \"utf-8\");\n stats.stubbed++;\n } catch (e) {\n stats.errors.push(`${project.slug} (archive stub): ${e}`);\n }\n }\n }\n }\n\n return stats;\n}\n","/** Index and topic page generation — writes _index.md and _topics/{tag}.md. */\n\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { Database } from \"better-sqlite3\";\nimport { fmtDate } from \"../../cli/utils.js\";\nimport type { ProjectRow, SessionStats, TagRow } from \"./types.js\";\nimport { findNotesDir, findClaudeNotesDir } from \"./symlinks.js\";\n\n/**\n * Generate _index.md listing all projects with session counts, tags, and\n * indicators for which note sources are available (notes, sessions, or both).\n */\nexport function generateIndex(vaultPath: string, db: Database): void {\n mkdirSync(vaultPath, { recursive: true });\n\n const rows = db\n .prepare(\n `SELECT p.id, p.slug, p.display_name, p.status, p.root_path,\n p.encoded_dir, p.claude_notes_dir,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n FROM projects p\n ORDER BY p.status ASC, p.updated_at DESC`\n )\n .all() as (ProjectRow & SessionStats)[];\n\n const getTagsForProject = db.prepare(\n `SELECT t.name FROM tags t\n JOIN project_tags pt ON pt.tag_id = t.id\n WHERE pt.project_id = ?\n ORDER BY t.name`\n );\n\n const active = rows.filter((r) => r.status === \"active\");\n const archived = rows.filter((r) => r.status !== \"active\");\n\n const lines: string[] = [\n \"# PAI Project Index\",\n \"\",\n `> Auto-generated by PAI Knowledge OS — ${new Date().toISOString()}`,\n \"\",\n \"## Active Projects\",\n \"\",\n \"| Project | Sessions | Last Active | Sources | Tags |\",\n \"| ------- | -------- | ----------- | ------- | ---- |\",\n ];\n\n for (const row of active) {\n const tags = (getTagsForProject.all(row.id) as TagRow[]).map((t) => `\\`${t.name}\\``).join(\" \");\n const lastActive = fmtDate(row.last_active);\n\n const notesDir = findNotesDir(row.root_path);\n const claudeNotesDir = findClaudeNotesDir(row.claude_notes_dir, notesDir);\n const sources: string[] = [];\n if (notesDir) sources.push(\"notes\");\n if (claudeNotesDir) sources.push(\"sessions\");\n const sourcesLabel = sources.length > 0 ? sources.join(\", \") : \"—\";\n\n lines.push(\n `| [[${row.slug}/|${row.display_name}]] | ${row.session_count} | ${lastActive} | ${sourcesLabel} | ${tags || \"—\"} |`\n );\n }\n\n if (archived.length > 0) {\n lines.push(\n \"\",\n \"## Archived Projects\",\n \"\",\n \"| Project | Sessions | Tags |\",\n \"| ------- | -------- | ---- |\"\n );\n for (const row of archived) {\n const tags = (getTagsForProject.all(row.id) as TagRow[]).map((t) => `\\`${t.name}\\``).join(\" \");\n lines.push(\n `| [${row.display_name}](_archive/${row.slug}.md) | ${row.session_count} | ${tags || \"—\"} |`\n );\n }\n }\n\n lines.push(\n \"\",\n \"---\",\n `*${rows.length} projects total — ${active.length} active, ${archived.length} archived*`\n );\n\n writeFileSync(join(vaultPath, \"_index.md\"), lines.join(\"\\n\") + \"\\n\", \"utf-8\");\n}\n\n/**\n * Generate per-tag topic pages at _topics/{tag}.md.\n * Returns count of pages written.\n */\nexport function generateTopicPages(vaultPath: string, db: Database): number {\n const topicsDir = join(vaultPath, \"_topics\");\n mkdirSync(topicsDir, { recursive: true });\n\n const allTags = db\n .prepare(\"SELECT id, name FROM tags ORDER BY name\")\n .all() as { id: number; name: string }[];\n\n const getProjectsForTag = db.prepare(\n `SELECT p.id, p.slug, p.display_name, p.status,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n FROM projects p\n JOIN project_tags pt ON pt.project_id = p.id\n WHERE pt.tag_id = ?\n ORDER BY p.status ASC, p.updated_at DESC`\n );\n\n let written = 0;\n for (const tag of allTags) {\n const projects = getProjectsForTag.all(tag.id) as (ProjectRow & SessionStats)[];\n if (!projects.length) continue;\n\n const lines: string[] = [\n `# Topic: ${tag.name}`,\n \"\",\n `> Auto-generated by PAI Knowledge OS — ${new Date().toISOString()}`,\n \"\",\n \"## Projects\",\n \"\",\n \"| Project | Status | Sessions | Last Active |\",\n \"| ------- | ------ | -------- | ----------- |\",\n ];\n\n for (const p of projects) {\n const link =\n p.status === \"active\"\n ? `[[${p.slug}/|${p.display_name}]]`\n : `[${p.display_name}](../_archive/${p.slug}.md)`;\n lines.push(`| ${link} | ${p.status} | ${p.session_count} | ${fmtDate(p.last_active)} |`);\n }\n\n lines.push(\"\", \"---\", `*${projects.length} project(s) tagged \\`${tag.name}\\`*`);\n\n writeFileSync(join(topicsDir, `${tag.name}.md`), lines.join(\"\\n\") + \"\\n\", \"utf-8\");\n written++;\n }\n\n return written;\n}\n\n/**\n * Default vault path: ~/.pai/obsidian-vault\n */\nexport function defaultVaultPath(): string {\n return join(homedir(), \".pai\", \"obsidian-vault\");\n}\n","/** Directory walking and session file discovery for vault note generation. */\n\nimport {\n existsSync,\n readdirSync,\n lstatSync,\n readlinkSync,\n} from \"node:fs\";\nimport { join, relative } from \"node:path\";\nimport type { SessionFile } from \"./types.js\";\n\nexport const SESSION_FILENAME_RE = /^(\\d{4}) - (\\d{4}-\\d{2})-\\d{2} - .+\\.md$/;\n\n/**\n * Walk a directory (non-recursive root level, then one level of YYYY/MM subdirs).\n * Returns absolute paths to all .md files found, skipping master note files.\n */\nexport function walkNotesDir(dir: string): string[] {\n const results: string[] = [];\n if (!existsSync(dir)) return results;\n\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n if (entry.startsWith(\".\")) continue;\n // Skip any master note file (pattern: _{slug}-master.md or legacy _master.md)\n if (entry === \"_master.md\" || /^_[^/]+-master\\.md$/.test(entry)) continue;\n const full = join(dir, entry);\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(full);\n } catch {\n continue;\n }\n if (stat.isDirectory()) {\n if (/^\\d{4}$/.test(entry)) {\n let monthEntries: string[];\n try {\n monthEntries = readdirSync(full);\n } catch {\n continue;\n }\n for (const month of monthEntries) {\n const monthPath = join(full, month);\n if (/^\\d{2}$/.test(month) && existsSync(monthPath)) {\n let monthFiles: string[];\n try {\n monthFiles = readdirSync(monthPath);\n } catch {\n continue;\n }\n for (const f of monthFiles) {\n if (f.endsWith(\".md\") && !f.startsWith(\".\")) {\n results.push(join(monthPath, f));\n }\n }\n }\n }\n }\n } else if (entry.endsWith(\".md\")) {\n results.push(full);\n }\n }\n return results;\n}\n\n/**\n * Extract YYYY/MM from a session file path.\n * Tries the path first (/YYYY/MM/ pattern), then falls back to filename date.\n */\nexport function extractYearMonth(filePath: string): string {\n const pathMatch = filePath.match(/\\/(\\d{4})\\/(\\d{2})\\//);\n if (pathMatch) return `${pathMatch[1]}/${pathMatch[2]}`;\n\n const basename = filePath.split(\"/\").pop() ?? \"\";\n const nameMatch = basename.match(/^\\d{4} - (\\d{4})-(\\d{2})-\\d{2}/);\n if (nameMatch) return `${nameMatch[1]}/${nameMatch[2]}`;\n\n return \"unknown\";\n}\n\n/**\n * Collect all session .md files from the notes and sessions symlinks\n * inside a vault project directory.\n */\nexport function collectSessionFiles(slugPath: string): SessionFile[] {\n const sessionFiles: SessionFile[] = [];\n const subLinks = [\"notes\", \"sessions\"];\n\n for (const subLink of subLinks) {\n const linkPath = join(slugPath, subLink);\n if (!existsSync(linkPath)) continue;\n\n let realDir: string;\n try {\n const stat = lstatSync(linkPath);\n if (stat.isSymbolicLink()) {\n realDir = readlinkSync(linkPath);\n } else if (stat.isDirectory()) {\n realDir = linkPath;\n } else {\n continue;\n }\n } catch {\n continue;\n }\n\n const files = walkNotesDir(realDir);\n for (const absPath of files) {\n const basename = absPath.split(\"/\").pop() ?? \"\";\n if (!SESSION_FILENAME_RE.test(basename)) continue;\n\n const relFromReal = relative(realDir, absPath);\n const vaultRelPath = `${subLink}/${relFromReal}`;\n const wikilinkTarget = vaultRelPath.replace(/\\.md$/, \"\");\n\n sessionFiles.push({\n absPath,\n vaultRelPath,\n wikilinkTarget,\n yearMonth: extractYearMonth(absPath),\n basename: basename.replace(/\\.md$/, \"\"),\n });\n }\n }\n\n return sessionFiles;\n}\n","/** Master note generation and session tag fixing for vault projects. */\n\nimport {\n existsSync,\n unlinkSync,\n writeFileSync,\n readFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport type { ProjectRow } from \"./types.js\";\nimport { collectSessionFiles, walkNotesDir } from \"./walk.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Build the per-project master note filename. */\nexport function masterFilename(slug: string): string {\n return `_${slug}-master.md`;\n}\n\n/**\n * Remove any old/broken backlink footer from a session file.\n * Returns true if the file was modified.\n */\nfunction removeOldBacklink(filePath: string): boolean {\n let content: string;\n try {\n content = readFileSync(filePath, \"utf-8\");\n } catch {\n return false;\n }\n const oldPattern = /\\n---\\n\\[\\[\\.\\.\\/[^\\]]+\\]\\]\\n?$/;\n const newPattern = /\\n---\\n\\[\\[_[^\\]]*-master\\|[^\\]]*\\]\\]\\n?$/;\n const cleaned = content.replace(oldPattern, \"\").replace(newPattern, \"\");\n if (cleaned === content) return false;\n try {\n writeFileSync(filePath, cleaned, \"utf-8\");\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Return true if the note has a meaningful body — i.e. content beyond blank\n * lines, horizontal rules, and an existing master-note footer.\n *\n * Guards against stamping a \"← Master\" backlink onto an empty note, which\n * produces a misleading dangling stub (empty body + footer only). Whatever\n * emptied the note, we must not decorate it as if it were a real session.\n */\nfunction hasMeaningfulBody(content: string): boolean {\n const body = content\n .replace(/\\n---\\n\\[\\[_[^\\]]*-master\\|[^\\]]*\\]\\]\\n?$/, \"\") // drop existing master footer\n .split(\"\\n\")\n .filter((line) => {\n const t = line.trim();\n return t !== \"\" && t !== \"---\";\n })\n .join(\"\");\n return body.length > 0;\n}\n\n/**\n * Append the master note backlink footer to a session file, idempotently.\n * Only writes if the sentinel string is not already present AND the note has\n * a meaningful body (never stamp a footer onto an empty note).\n */\nfunction appendBacklinkIfMissing(\n filePath: string,\n slug: string,\n displayName: string\n): boolean {\n let content: string;\n try {\n content = readFileSync(filePath, \"utf-8\");\n } catch {\n return false;\n }\n // Never stamp a footer onto an empty note — that is exactly the dangling-stub\n // signature (empty body + master backlink) we are trying to prevent.\n if (!hasMeaningfulBody(content)) return false;\n\n const masterName = masterFilename(slug).replace(/\\.md$/, \"\");\n const sentinel = `[[${masterName}|`;\n if (content.includes(sentinel)) return false;\n\n const footer = `\\n---\\n[[${masterName}|← ${displayName} Master]]\\n`;\n try {\n writeFileSync(filePath, content + footer, \"utf-8\");\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: generateMasterNotes\n// ---------------------------------------------------------------------------\n\n/**\n * Generate _master.md files for projects that have >= threshold session files.\n *\n * For each qualifying project, writes a {vaultPath}/{slug}/_master.md containing:\n * - Project title, session count + date range\n * - Sessions grouped by YYYY/MM with Obsidian [[wikilinks]]\n *\n * Also appends a backlink footer to each session file (idempotently).\n *\n * @param vaultPath Absolute path to the PAI Obsidian vault\n * @param db Registry SQLite database\n * @param threshold Minimum session count to generate a master note (default: 5)\n * @returns Number of master notes written\n */\nexport function generateMasterNotes(\n vaultPath: string,\n db: Database,\n threshold = 5\n): number {\n if (!existsSync(vaultPath)) return 0;\n\n const projects = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir, status, obsidian_link, claude_notes_dir\n FROM projects\n WHERE status = 'active'\n ORDER BY slug ASC`\n )\n .all() as ProjectRow[];\n\n let written = 0;\n\n for (const project of projects) {\n const slugPath = join(vaultPath, project.slug);\n if (!existsSync(slugPath)) continue;\n\n // Remove legacy _master.md if present\n const legacyMaster = join(slugPath, \"_master.md\");\n if (existsSync(legacyMaster)) {\n try { unlinkSync(legacyMaster); } catch { /* Non-fatal */ }\n }\n\n const sessionFiles = collectSessionFiles(slugPath);\n if (sessionFiles.length < threshold) continue;\n\n // Sort by yearMonth then basename\n sessionFiles.sort((a, b) => {\n if (a.yearMonth !== b.yearMonth) return a.yearMonth.localeCompare(b.yearMonth);\n return a.basename.localeCompare(b.basename);\n });\n\n const byMonth = new Map<string, typeof sessionFiles>();\n for (const sf of sessionFiles) {\n if (!byMonth.has(sf.yearMonth)) byMonth.set(sf.yearMonth, []);\n byMonth.get(sf.yearMonth)!.push(sf);\n }\n\n const firstDate = sessionFiles[0]?.basename.match(/\\d{4}-\\d{2}-\\d{2}/)?.[0] ?? \"unknown\";\n const lastDate = sessionFiles[sessionFiles.length - 1]?.basename.match(/\\d{4}-\\d{2}-\\d{2}/)?.[0] ?? \"unknown\";\n\n const lines: string[] = [\n `# Project: ${project.display_name}`,\n \"\",\n `> Auto-generated by PAI Knowledge OS — ${new Date().toISOString()}`,\n \"\",\n \"## Overview\",\n \"\",\n `- **Sessions:** ${sessionFiles.length}`,\n `- **Date range:** ${firstDate} to ${lastDate}`,\n `- **Tags:** #${project.slug} #project`,\n \"\",\n \"## Sessions by Month\",\n \"\",\n ];\n\n for (const [ym, files] of byMonth) {\n lines.push(`### ${ym}`, \"\");\n for (const sf of files) {\n lines.push(`- [[${sf.wikilinkTarget}|${sf.basename}]]`);\n }\n lines.push(\"\");\n }\n\n lines.push(\"## Topics\", \"\", `#${project.slug} #project`, \"\");\n\n const masterPath = join(slugPath, masterFilename(project.slug));\n try {\n writeFileSync(masterPath, lines.join(\"\\n\"), \"utf-8\");\n written++;\n } catch {\n continue;\n }\n\n for (const sf of sessionFiles) {\n removeOldBacklink(sf.absPath);\n appendBacklinkIfMissing(sf.absPath, project.slug, project.display_name);\n }\n }\n\n return written;\n}\n\n// ---------------------------------------------------------------------------\n// Public: fixSessionTags\n// ---------------------------------------------------------------------------\n\n/**\n * Remove the generic #Session tag from session note files across all projects.\n *\n * Scans all session and notes directories for every active project and removes\n * #Session (with or without a trailing space) from any `**Tags:**` line.\n *\n * @param db Registry SQLite database\n * @returns Object with counts: { filesScanned, filesModified, errors }\n */\nexport function fixSessionTags(\n db: Database\n): { filesScanned: number; filesModified: number; errors: string[] } {\n const results = { filesScanned: 0, filesModified: 0, errors: [] as string[] };\n\n const projects = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir, status, obsidian_link, claude_notes_dir\n FROM projects\n WHERE status = 'active'\n ORDER BY slug ASC`\n )\n .all() as ProjectRow[];\n\n for (const project of projects) {\n const dirsToScan: string[] = [];\n\n // Inline findNotesDir to avoid a circular dependency with symlinks.ts\n const canonical = join(project.root_path, \"Notes\");\n const alt = join(project.root_path, \".claude\", \"Notes\");\n const notesDir = existsSync(canonical) ? canonical : existsSync(alt) ? alt : null;\n\n if (notesDir) dirsToScan.push(notesDir);\n\n if (\n project.claude_notes_dir &&\n existsSync(project.claude_notes_dir) &&\n project.claude_notes_dir !== notesDir\n ) {\n dirsToScan.push(project.claude_notes_dir);\n }\n\n for (const dir of dirsToScan) {\n const files = walkNotesDir(dir);\n for (const filePath of files) {\n results.filesScanned++;\n let content: string;\n try {\n content = readFileSync(filePath, \"utf-8\");\n } catch (e) {\n results.errors.push(`${filePath}: read error — ${e}`);\n continue;\n }\n\n if (!content.includes(\"#Session\")) continue;\n\n const updated = content.replace(\n /(\\*\\*Tags:\\*\\*[^\\n]*)#Session ?/g,\n \"$1\"\n );\n\n if (updated === content) continue;\n\n const cleaned = updated.replace(/(\\*\\*Tags:\\*\\*) *\\n/g, \"$1\\n\");\n\n try {\n writeFileSync(filePath, cleaned, \"utf-8\");\n results.filesModified++;\n } catch (e) {\n results.errors.push(`${filePath}: write error — ${e}`);\n }\n }\n }\n }\n\n return results;\n}\n","/**\n * obsidian/status.ts — Vault health checking for PAI Phase 4\n *\n * Vault structure (per project):\n *\n * {vault}/{slug}/ — real directory\n * notes → {root}/Notes/ (project root notes, optional)\n * sessions → ~/.claude/projects/{enc}/Notes/ (Claude Code session notes, optional)\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { existsSync, readdirSync, lstatSync, readlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface ProjectRow {\n id: number;\n slug: string;\n display_name: string;\n root_path: string;\n status: string;\n claude_notes_dir: string | null;\n obsidian_link: string | null;\n}\n\nexport interface SymlinkHealth {\n slug: string;\n linkPath: string;\n target: string | null;\n state: \"healthy\" | \"broken\" | \"orphaned\" | \"missing\";\n notes: string;\n}\n\nexport interface VaultHealthReport {\n vaultPath: string;\n healthy: SymlinkHealth[];\n broken: SymlinkHealth[];\n orphaned: SymlinkHealth[];\n missing: SymlinkHealth[];\n totalProjects: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check the health of a sub-symlink inside a project directory.\n * Returns true if the symlink exists and its target exists on disk.\n */\nfunction subSymlinkHealthy(dir: string, name: string): boolean {\n const linkPath = join(dir, name);\n try {\n const stat = lstatSync(linkPath);\n if (!stat.isSymbolicLink()) return false;\n const target = readlinkSync(linkPath);\n return existsSync(target);\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a sub-symlink exists (regardless of target validity).\n */\nfunction subSymlinkExists(dir: string, name: string): boolean {\n try {\n lstatSync(join(dir, name));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Find a project root Notes dir (checks canonical and .claude/Notes fallback).\n */\nfunction findNotesDir(rootPath: string): string | null {\n const canonical = join(rootPath, \"Notes\");\n if (existsSync(canonical)) return canonical;\n const alt = join(rootPath, \".claude\", \"Notes\");\n if (existsSync(alt)) return alt;\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Check health of the Obsidian vault:\n *\n * Project entries are now real directories with sub-symlinks (`notes`, `sessions`).\n *\n * - healthy: project dir exists; at least one sub-symlink is present and resolves\n * - broken: project dir exists but all sub-symlinks have missing targets\n * - orphaned: directory entry in vault has no matching registry project\n * - missing: active project with at least one Notes source but no vault dir\n */\nexport function checkHealth(vaultPath: string, db: Database): VaultHealthReport {\n const report: VaultHealthReport = {\n vaultPath,\n healthy: [],\n broken: [],\n orphaned: [],\n missing: [],\n totalProjects: 0,\n };\n\n if (!existsSync(vaultPath)) {\n return report;\n }\n\n const projects = db\n .prepare(\n `SELECT id, slug, display_name, root_path, status, claude_notes_dir, obsidian_link\n FROM projects WHERE status = 'active'`\n )\n .all() as ProjectRow[];\n\n report.totalProjects = projects.length;\n\n // Build a map of slug → project for quick lookup\n const projectsBySlug = new Map<string, ProjectRow>();\n for (const p of projects) {\n projectsBySlug.set(p.slug, p);\n }\n\n // Scan vault directory\n const seenSlugs = new Set<string>();\n let entries: string[] = [];\n try {\n entries = readdirSync(vaultPath);\n } catch {\n return report;\n }\n\n for (const entry of entries) {\n // Skip internal PAI-generated dirs/files\n if (entry.startsWith(\"_\")) continue;\n\n const fullPath = join(vaultPath, entry);\n let stat;\n try {\n stat = lstatSync(fullPath);\n } catch {\n continue;\n }\n\n const project = projectsBySlug.get(entry);\n\n if (stat.isDirectory()) {\n // New-style project directory\n if (!project) {\n // Directory in vault with no matching registry project\n report.orphaned.push({\n slug: entry,\n linkPath: fullPath,\n target: null,\n state: \"orphaned\",\n notes: \"No matching active project in registry (directory)\",\n });\n continue;\n }\n\n seenSlugs.add(entry);\n\n const notesOk = subSymlinkHealthy(fullPath, \"notes\");\n const sessionsOk = subSymlinkHealthy(fullPath, \"sessions\");\n const notesExists = subSymlinkExists(fullPath, \"notes\");\n const sessionsExists = subSymlinkExists(fullPath, \"sessions\");\n\n if (notesOk || sessionsOk) {\n const parts: string[] = [];\n if (notesOk) parts.push(\"notes\");\n if (sessionsOk) parts.push(\"sessions\");\n report.healthy.push({\n slug: entry,\n linkPath: fullPath,\n target: null,\n state: \"healthy\",\n notes: `Sources: ${parts.join(\", \")}`,\n });\n } else {\n // At least one sub-link exists but none resolves\n const broken: string[] = [];\n if (notesExists) broken.push(\"notes\");\n if (sessionsExists) broken.push(\"sessions\");\n report.broken.push({\n slug: entry,\n linkPath: fullPath,\n target: null,\n state: \"broken\",\n notes: `Broken sub-symlinks: ${broken.join(\", \") || \"(empty directory)\"}`,\n });\n }\n } else if (stat.isSymbolicLink()) {\n // Legacy flat symlink — treat same as before\n let target: string | null = null;\n try {\n target = readlinkSync(fullPath);\n } catch {\n // Can't read symlink\n }\n\n const targetExists = target !== null && existsSync(target);\n\n if (!project) {\n report.orphaned.push({\n slug: entry,\n linkPath: fullPath,\n target,\n state: \"orphaned\",\n notes: \"No matching active project in registry (legacy symlink)\",\n });\n continue;\n }\n\n seenSlugs.add(entry);\n\n if (targetExists) {\n report.healthy.push({\n slug: entry,\n linkPath: fullPath,\n target,\n state: \"healthy\",\n notes: \"(legacy flat symlink — run sync to upgrade)\",\n });\n } else {\n report.broken.push({\n slug: entry,\n linkPath: fullPath,\n target,\n state: \"broken\",\n notes: `Target missing: ${target ?? \"(unknown)\"}`,\n });\n }\n }\n // Non-symlink, non-directory entries are ignored (e.g. stray files)\n }\n\n // Find active projects with Notes/ dirs that have no vault entry\n for (const project of projects) {\n if (seenSlugs.has(project.slug)) continue;\n\n const notesCanonical = join(project.root_path, \"Notes\");\n const notesAlt = join(project.root_path, \".claude\", \"Notes\");\n const hasProjectNotes = existsSync(notesCanonical) || existsSync(notesAlt);\n const hasClaudeNotes =\n project.claude_notes_dir !== null && existsSync(project.claude_notes_dir);\n\n if (hasProjectNotes || hasClaudeNotes) {\n const sources: string[] = [];\n if (hasProjectNotes) sources.push(\"notes\");\n if (hasClaudeNotes) sources.push(\"sessions\");\n report.missing.push({\n slug: project.slug,\n linkPath: join(vaultPath, project.slug),\n target: null,\n state: \"missing\",\n notes: `Has sources (${sources.join(\", \")}) but no vault entry — run 'pai obsidian sync'`,\n });\n }\n }\n\n return report;\n}\n","/**\n * pai obsidian <sub-command>\n *\n * sync — symlink project Notes/ dirs into vault, write _index.md + _topics/\n * status — health report: healthy, broken, orphaned, missing symlinks\n * open — open vault in Obsidian app (macOS)\n */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { execSync } from \"node:child_process\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport {\n syncVault,\n generateIndex,\n generateTopicPages,\n generateMasterNotes,\n fixSessionTags,\n defaultVaultPath,\n} from \"../../obsidian/sync.js\";\nimport { checkHealth } from \"../../obsidian/status.js\";\nimport {\n loadConfig,\n expandHome,\n CONFIG_FILE,\n} from \"../../daemon/config.js\";\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport chalk from \"chalk\";\n\n// ---------------------------------------------------------------------------\n// Config helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read obsidianVaultPath from ~/.config/pai/config.json.\n * Falls back to defaultVaultPath() if not set.\n */\nfunction getVaultPath(override?: string): string {\n if (override) return expandHome(override);\n\n try {\n const raw = readFileSync(CONFIG_FILE, \"utf-8\");\n const cfg = JSON.parse(raw) as Record<string, unknown>;\n if (typeof cfg.obsidianVaultPath === \"string\" && cfg.obsidianVaultPath) {\n return expandHome(cfg.obsidianVaultPath);\n }\n } catch {\n // Config missing or unreadable — use default\n }\n return defaultVaultPath();\n}\n\n/**\n * Persist obsidianVaultPath into config.json so future syncs use the same path.\n */\nfunction saveVaultPath(vaultPath: string): void {\n let cfg: Record<string, unknown> = {};\n try {\n cfg = JSON.parse(readFileSync(CONFIG_FILE, \"utf-8\")) as Record<string, unknown>;\n } catch {\n // Start fresh if file is missing/corrupt\n }\n cfg.obsidianVaultPath = vaultPath;\n mkdirSync(dirname(CONFIG_FILE), { recursive: true });\n writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nfunction cmdSync(\n db: Database,\n opts: { vault?: string; quiet?: boolean }\n): void {\n const vaultPath = getVaultPath(opts.vault);\n const q = opts.quiet ?? false;\n\n if (!q) {\n console.log();\n console.log(header(\" PAI Obsidian Sync\"));\n console.log(dim(` Vault: ${vaultPath}`));\n console.log();\n }\n\n // --- Symlinks ---\n if (!q) process.stdout.write(\" Syncing symlinks...\");\n const stats = syncVault(vaultPath, db);\n if (!q) {\n process.stdout.write(\n `\\r ${ok(\"Symlinks:\")} created ${stats.created} updated ${stats.updated} removed ${stats.removed} stubs ${stats.stubbed}\\n`\n );\n if (stats.errors.length) {\n for (const e of stats.errors) {\n console.log(warn(` Warning: ${e}`));\n }\n }\n }\n\n // --- Index ---\n if (!q) process.stdout.write(\" Generating index...\");\n generateIndex(vaultPath, db);\n if (!q) console.log(`\\r ${ok(\"Index:\")} _index.md written `);\n\n // --- Topic pages ---\n if (!q) process.stdout.write(\" Generating topic pages...\");\n const topicCount = generateTopicPages(vaultPath, db);\n if (!q) console.log(`\\r ${ok(\"Topics:\")} ${topicCount} topic page(s) written `);\n\n // --- Fix session tags (remove generic #Session) ---\n if (!q) process.stdout.write(\" Fixing session tags...\");\n const tagStats = fixSessionTags(db);\n if (!q) {\n console.log(\n `\\r ${ok(\"Tags:\")} ${tagStats.filesModified} file(s) updated (scanned ${tagStats.filesScanned}) `\n );\n if (tagStats.errors.length) {\n for (const e of tagStats.errors) {\n console.log(warn(` Warning: ${e}`));\n }\n }\n }\n\n // --- Master notes ---\n if (!q) process.stdout.write(\" Generating master notes...\");\n const masterCount = generateMasterNotes(vaultPath, db);\n if (!q) console.log(`\\r ${ok(\"Masters:\")} ${masterCount} master note(s) written `);\n\n // Persist vault path so status/open can use it\n saveVaultPath(vaultPath);\n\n if (!q) {\n console.log();\n console.log(ok(` Done. Vault ready at: ${vaultPath}`));\n console.log();\n }\n}\n\nfunction cmdStatus(db: Database, opts: { vault?: string }): void {\n const vaultPath = getVaultPath(opts.vault);\n const report = checkHealth(vaultPath, db);\n\n console.log();\n console.log(header(\" PAI Obsidian Vault Status\"));\n console.log(dim(` Vault: ${vaultPath}`));\n console.log();\n\n if (!existsSync(vaultPath)) {\n console.log(warn(\" Vault directory does not exist. Run: pai obsidian sync\"));\n console.log();\n return;\n }\n\n // Summary line\n const healthyCount = report.healthy.length;\n const brokenCount = report.broken.length;\n const orphanedCount = report.orphaned.length;\n const missingCount = report.missing.length;\n\n console.log(\n ` ${chalk.green(\"Healthy:\")} ${healthyCount} ` +\n `${chalk.red(\"Broken:\")} ${brokenCount} ` +\n `${chalk.yellow(\"Orphaned:\")} ${orphanedCount} ` +\n `${chalk.cyan(\"Missing:\")} ${missingCount}`\n );\n console.log();\n\n if (report.healthy.length && report.healthy.length <= 10) {\n console.log(bold(\" Healthy symlinks:\"));\n for (const h of report.healthy) {\n console.log(` ${chalk.green(\"✓\")} ${bold(h.slug)} ${dim(\"→ \" + (h.target ?? \"\"))}`);\n }\n console.log();\n } else if (report.healthy.length > 10) {\n console.log(bold(` ${report.healthy.length} healthy symlinks (all good)`));\n console.log();\n }\n\n if (report.broken.length) {\n console.log(err(\" Broken symlinks (target missing):\"));\n for (const b of report.broken) {\n console.log(` ${chalk.red(\"✗\")} ${bold(b.slug)} ${dim(b.notes)}`);\n }\n console.log(dim(\" Fix: pai obsidian sync\"));\n console.log();\n }\n\n if (report.orphaned.length) {\n console.log(warn(\" Orphaned symlinks (no registry project):\"));\n for (const o of report.orphaned) {\n console.log(` ${chalk.yellow(\"?\")} ${bold(o.slug)} ${dim(o.notes)}`);\n }\n console.log();\n }\n\n if (report.missing.length) {\n console.log(warn(\" Projects with Notes/ but no vault symlink:\"));\n for (const m of report.missing) {\n console.log(` ${chalk.cyan(\"→\")} ${bold(m.slug)} ${dim(m.notes)}`);\n }\n console.log();\n }\n\n if (brokenCount === 0 && orphanedCount === 0 && missingCount === 0) {\n console.log(ok(\" Vault is healthy.\"));\n console.log();\n }\n}\n\nfunction cmdOpen(opts: { vault?: string }): void {\n const vaultPath = getVaultPath(opts.vault);\n // Derive vault name from last path component (Obsidian uses folder name as vault name)\n const parts = vaultPath.split(\"/\").filter(Boolean);\n const vaultName = parts[parts.length - 1] ?? \"obsidian-vault\";\n\n if (!existsSync(vaultPath)) {\n console.error(err(`Vault not found at: ${vaultPath}`));\n console.error(dim(\" Run: pai obsidian sync\"));\n process.exit(1);\n }\n\n const url = `obsidian://open?vault=${encodeURIComponent(vaultName)}`;\n console.log(dim(` Opening: ${url}`));\n try {\n execSync(`open \"${url}\"`, { stdio: \"ignore\" });\n console.log(ok(` Opened vault \"${vaultName}\" in Obsidian.`));\n } catch (e) {\n console.error(err(` Failed to open Obsidian: ${e}`));\n console.error(dim(\" Ensure Obsidian is installed and the vault is registered in Obsidian.\"));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerObsidianCommands(\n obsidianCmd: Command,\n getDb: () => Database\n): void {\n // pai obsidian sync\n obsidianCmd\n .command(\"sync\")\n .description(\"Sync project Notes/ dirs into vault, generate _index.md and topic pages\")\n .option(\"--vault <path>\", \"Override vault path (default: ~/.pai/obsidian-vault)\")\n .option(\"--quiet\", \"Minimal output — suitable for cron/hook use\")\n .action((opts: { vault?: string; quiet?: boolean }) => {\n cmdSync(getDb(), opts);\n });\n\n // pai obsidian status\n obsidianCmd\n .command(\"status\")\n .description(\"Show vault health: healthy, broken, orphaned, and missing symlinks\")\n .option(\"--vault <path>\", \"Override vault path\")\n .action((opts: { vault?: string }) => {\n cmdStatus(getDb(), opts);\n });\n\n // pai obsidian open\n obsidianCmd\n .command(\"open\")\n .description(\"Open the vault in Obsidian (macOS)\")\n .option(\"--vault <path>\", \"Override vault path\")\n .action((opts: { vault?: string }) => {\n cmdOpen(opts);\n });\n}\n","/** Shared utilities for zettel CLI commands. */\n\nimport type { Database } from \"better-sqlite3\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { err } from \"../../utils.js\";\n\n/** Shorten a vault path to just the last 2-3 components for display. */\nexport function shortPath(p: string, parts = 3): string {\n const segments = p.split(\"/\").filter(Boolean);\n return segments.slice(-parts).join(\"/\");\n}\n\n// Lazy-loaded federation DB singleton\nlet _fedDb: Database | null = null;\n\n/** Get (or lazily open) the PAI federation database. */\nexport function getFedDb(): Database {\n if (!_fedDb) {\n try {\n _fedDb = openFederation();\n } catch (e) {\n console.error(err(`Failed to open PAI federation DB: ${e}`));\n process.exit(1);\n }\n }\n return _fedDb;\n}\n","/** zettel explore: follow link chains from a starting note. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdExplore(\n note: string,\n opts: { depth?: string; direction?: string; mode?: string }\n): Promise<void> {\n const depth = parseInt(opts.depth ?? \"3\", 10);\n const direction = (opts.direction ?? \"both\") as \"forward\" | \"backward\" | \"both\";\n const mode = (opts.mode ?? \"all\") as \"sequential\" | \"associative\" | \"all\";\n\n const { zettelExplore } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n const result = zettelExplore(db, { startNote: note, depth, direction, mode });\n\n console.log();\n console.log(header(\" PAI Zettel Explore\"));\n console.log(dim(` Starting note: ${note}`));\n console.log(dim(` Depth: ${depth} Direction: ${direction} Mode: ${mode}`));\n console.log();\n\n if (result.nodes.length === 0) {\n console.log(warn(\" No connected notes found. Check that the note path exists in the vault index.\"));\n console.log();\n return;\n }\n\n console.log(` ${chalk.cyan(\"●\")} ${bold(shortPath(result.root))} ${dim(\"(root)\")}`);\n\n const byDepth = new Map<number, typeof result.nodes>();\n for (const node of result.nodes) {\n const list = byDepth.get(node.depth) ?? [];\n list.push(node);\n byDepth.set(node.depth, list);\n }\n\n for (let d = 1; d <= depth; d++) {\n const nodes = byDepth.get(d) ?? [];\n if (nodes.length === 0) continue;\n\n console.log();\n console.log(dim(` ${\" \".repeat(d - 1)}Depth ${d}:`));\n for (const node of nodes) {\n const indent = \" \".repeat(d);\n const isBranching = result.branchingPoints.includes(node.path);\n const typeColor = node.linkType === \"sequential\" ? chalk.blue : chalk.magenta;\n const branchMark = isBranching ? chalk.yellow(\" ⑂ branching\") : \"\";\n const title = node.title ?? shortPath(node.path);\n const stats = dim(`in:${node.inbound} out:${node.outbound}`);\n console.log(\n ` ${indent}${typeColor(\"→\")} ${bold(title)}${branchMark} ${stats} ${dim(typeColor(node.linkType))}`\n );\n }\n }\n\n console.log();\n const edgeSummary = `${result.edges.length} edges (${result.edges.filter(e => e.type === \"sequential\").length} sequential, ${result.edges.filter(e => e.type === \"associative\").length} associative)`;\n console.log(dim(` ${edgeSummary}`));\n if (result.branchingPoints.length > 0) {\n console.log(ok(` ${result.branchingPoints.length} branching point(s) found`));\n }\n if (result.maxDepthReached) {\n console.log(warn(\" Max depth reached — use --depth to explore further\"));\n }\n console.log();\n}\n\nexport function registerExploreCommand(parent: Command): void {\n parent\n .command(\"explore <note>\")\n .description(\"Follow link chains from a starting note\")\n .option(\"--depth <n>\", \"Maximum traversal depth (1-10)\", \"3\")\n .option(\"--direction <d>\", \"Link direction: forward | backward | both\", \"both\")\n .option(\"--mode <m>\", \"Edge mode: sequential | associative | all\", \"all\")\n .action(async (note: string, opts: { depth?: string; direction?: string; mode?: string }) => {\n try {\n await cmdExplore(note, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** zettel health: vault structural health audit. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdHealth(opts: {\n scope?: string;\n project?: string;\n days?: string;\n include?: string;\n}): Promise<void> {\n const scope = (opts.scope ?? \"full\") as \"full\" | \"recent\" | \"project\";\n const projectPath = opts.project;\n const recentDays = parseInt(opts.days ?? \"30\", 10);\n const includeTypes = opts.include\n ? (opts.include.split(\",\").map(s => s.trim()) as Array<\"dead_links\" | \"orphans\" | \"disconnected\" | \"low_connectivity\">)\n : undefined;\n\n const { zettelHealth } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n const result = zettelHealth(db, { scope, projectPath, recentDays, include: includeTypes });\n\n console.log();\n console.log(header(\" PAI Zettel Health\"));\n console.log(dim(` Scope: ${scope}${scope === \"project\" ? ` Path: ${projectPath ?? \"(none)\"}` : \"\"}${scope === \"recent\" ? ` Days: ${recentDays}` : \"\"}`));\n console.log();\n\n const score = result.healthScore;\n const scoreColor = score >= 80 ? chalk.green : score >= 60 ? chalk.yellow : chalk.red;\n const barWidth = 30;\n const filled = Math.round((score / 100) * barWidth);\n const bar = scoreColor(\"█\".repeat(filled)) + dim(\"░\".repeat(barWidth - filled));\n console.log(` Health Score: ${scoreColor(bold(String(score)))}% [${bar}]`);\n console.log();\n console.log(dim(` Files: ${result.totalFiles} Links: ${result.totalLinks}`));\n console.log();\n\n if (result.deadLinks.length === 0) {\n console.log(ok(\" Dead links: none\"));\n } else {\n console.log(warn(` Dead links: ${result.deadLinks.length}`));\n const preview = result.deadLinks.slice(0, 10);\n for (const dl of preview) {\n console.log(` ${chalk.red(\"✗\")} ${dim(shortPath(dl.sourcePath))} → ${bold(dl.targetRaw)} ${dim(`(line ${dl.lineNumber})`)}`);\n }\n if (result.deadLinks.length > 10) {\n console.log(dim(` ... and ${result.deadLinks.length - 10} more`));\n }\n console.log();\n }\n\n if (result.orphans.length === 0) {\n console.log(ok(\" Orphan notes: none\"));\n } else {\n console.log(warn(` Orphan notes: ${result.orphans.length}`));\n const preview = result.orphans.slice(0, 10);\n for (const o of preview) {\n console.log(` ${chalk.yellow(\"○\")} ${dim(shortPath(o))}`);\n }\n if (result.orphans.length > 10) {\n console.log(dim(` ... and ${result.orphans.length - 10} more`));\n }\n console.log();\n }\n\n if (result.disconnectedClusters <= 1) {\n console.log(ok(\" Disconnected clusters: 1 (fully connected)\"));\n } else {\n console.log(warn(` Disconnected clusters: ${result.disconnectedClusters}`));\n }\n\n if (result.lowConnectivity.length === 0) {\n console.log(ok(\" Low-connectivity: none\"));\n } else {\n console.log(warn(` Low-connectivity: ${result.lowConnectivity.length} note(s) with ≤1 link`));\n const preview = result.lowConnectivity.slice(0, 5);\n for (const lc of preview) {\n console.log(` ${chalk.dim(\"—\")} ${dim(shortPath(lc))}`);\n }\n if (result.lowConnectivity.length > 5) {\n console.log(dim(` ... and ${result.lowConnectivity.length - 5} more`));\n }\n }\n\n console.log();\n}\n\nexport function registerHealthCommand(parent: Command): void {\n parent\n .command(\"health\")\n .description(\"Vault structural health audit: dead links, orphans, connectivity\")\n .option(\"--scope <s>\", \"Scope: full | recent | project\", \"full\")\n .option(\"--project <path>\", \"Project path prefix (requires --scope project)\")\n .option(\"--days <n>\", \"Look-back window in days (requires --scope recent)\", \"30\")\n .option(\"--include <types>\", \"Comma-separated subset: dead_links,orphans,disconnected,low_connectivity\")\n .action(async (opts: { scope?: string; project?: string; days?: string; include?: string }) => {\n try {\n await cmdHealth(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** zettel surprise: find semantically similar but graph-distant notes. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdSurprise(\n note: string,\n opts: { vaultProjectId?: string; limit?: string; minSimilarity?: string; minDistance?: string }\n): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exit(1);\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const limit = parseInt(opts.limit ?? \"10\", 10);\n const minSimilarity = parseFloat(opts.minSimilarity ?? \"0.3\");\n const minGraphDistance = parseInt(opts.minDistance ?? \"3\", 10);\n\n const { zettelSurprise } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n console.log();\n console.log(header(\" PAI Zettel Surprise\"));\n console.log(dim(` Reference note: ${note}`));\n process.stdout.write(dim(\" Searching for surprising connections...\\n\"));\n\n const results = await zettelSurprise(db, {\n referencePath: note,\n vaultProjectId,\n limit,\n minSimilarity,\n minGraphDistance,\n });\n\n if (results.length === 0) {\n console.log(warn(\" No surprising connections found. Try lowering --min-similarity or --min-distance.\"));\n console.log();\n return;\n }\n\n console.log();\n console.log(bold(` Found ${results.length} surprising connection(s):`));\n console.log();\n\n for (let i = 0; i < results.length; i++) {\n const r = results[i];\n const title = r.title ?? shortPath(r.path);\n const surpriseBar = Math.round(r.surpriseScore * 10);\n console.log(` ${chalk.cyan(String(i + 1).padStart(2, \" \"))}. ${bold(title)}`);\n console.log(` ${dim(\"Surprise:\")} ${chalk.magenta(r.surpriseScore.toFixed(3))} ${\"■\".repeat(surpriseBar)}${\"□\".repeat(10 - surpriseBar)}`);\n console.log(` ${dim(\"Cosine:\")} ${r.cosineSimilarity.toFixed(3)} ${dim(\"Graph distance:\")} ${r.graphDistance}`);\n if (r.sharedSnippet) {\n console.log(` ${dim(\"Context:\")} ${r.sharedSnippet.slice(0, 120)}`);\n }\n console.log();\n }\n}\n\nexport function registerSurpriseCommand(parent: Command): void {\n parent\n .command(\"surprise <note>\")\n .description(\"Find semantically similar but graph-distant notes (surprising connections)\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--limit <n>\", \"Maximum results\", \"10\")\n .option(\"--min-similarity <f>\", \"Minimum cosine similarity (0–1)\", \"0.3\")\n .option(\"--min-distance <n>\", \"Minimum graph distance\", \"3\")\n .action(async (note: string, opts: { vaultProjectId?: string; limit?: string; minSimilarity?: string; minDistance?: string }) => {\n try {\n await cmdSurprise(note, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** zettel suggest: suggest new wikilink connections for a note. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdSuggest(\n note: string,\n opts: { vaultProjectId?: string; limit?: string; excludeLinked?: boolean }\n): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exit(1);\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const limit = parseInt(opts.limit ?? \"5\", 10);\n const excludeLinked = opts.excludeLinked !== false;\n\n const { zettelSuggest } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n console.log();\n console.log(header(\" PAI Zettel Suggest\"));\n console.log(dim(` Note: ${note}`));\n process.stdout.write(dim(\" Computing suggestions...\\n\"));\n\n const suggestions = await zettelSuggest(db, {\n notePath: note,\n vaultProjectId,\n limit,\n excludeLinked,\n });\n\n if (suggestions.length === 0) {\n console.log(warn(\" No suggestions found. The note may be well-connected already.\"));\n console.log();\n return;\n }\n\n console.log();\n console.log(bold(` ${suggestions.length} suggested connection(s):`));\n console.log();\n\n for (let i = 0; i < suggestions.length; i++) {\n const s = suggestions[i];\n const title = s.title ?? shortPath(s.path);\n console.log(` ${chalk.green(String(i + 1).padStart(2, \" \"))}. ${bold(title)}`);\n console.log(` ${dim(\"Score:\")} ${chalk.green(s.score.toFixed(3))} ${dim(\"Semantic:\")} ${s.semanticScore.toFixed(2)} ${dim(\"Tag:\")} ${s.tagScore.toFixed(2)} ${dim(\"Neighbor:\")} ${s.neighborScore.toFixed(2)}`);\n console.log(` ${dim(\"Reason:\")} ${s.reason}`);\n console.log(` ${dim(\"Wikilink:\")} ${chalk.cyan(s.suggestedWikilink)}`);\n console.log();\n }\n}\n\nexport function registerSuggestCommand(parent: Command): void {\n parent\n .command(\"suggest <note>\")\n .description(\"Suggest new wikilink connections for a note\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--limit <n>\", \"Maximum suggestions\", \"5\")\n .option(\"--no-exclude-linked\", \"Include notes already linked from this one\")\n .action(async (note: string, opts: { vaultProjectId?: string; limit?: string; excludeLinked?: boolean }) => {\n try {\n await cmdSuggest(note, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** zettel converse: ask the vault a question and get a synthesis prompt. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdConverse(\n question: string,\n opts: { vaultProjectId?: string; depth?: string; limit?: string }\n): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exit(1);\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const depth = parseInt(opts.depth ?? \"2\", 10);\n const limit = parseInt(opts.limit ?? \"15\", 10);\n\n const { zettelConverse } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n console.log();\n console.log(header(\" PAI Zettel Converse\"));\n console.log(dim(` Question: \"${question}\"`));\n process.stdout.write(dim(\" Searching vault for relevant notes...\\n\"));\n\n const result = await zettelConverse(db, { question, vaultProjectId, depth, limit });\n\n if (result.relevantNotes.length === 0) {\n console.log(warn(\" No relevant notes found. Try rephrasing your question.\"));\n console.log();\n return;\n }\n\n console.log();\n console.log(bold(` ${result.relevantNotes.length} relevant note(s) from ${result.domains.length} domain(s):`));\n console.log(dim(` Domains: ${result.domains.join(\", \")}`));\n console.log();\n\n for (const note of result.relevantNotes) {\n const title = note.title ?? shortPath(note.path);\n console.log(` ${chalk.cyan(\"◆\")} ${bold(title)} ${dim(`[${note.domain}] score: ${note.score.toFixed(3)}`)}`);\n if (note.snippet) {\n console.log(` ${dim(note.snippet.slice(0, 200))}`);\n }\n console.log();\n }\n\n if (result.connections.length > 0) {\n console.log(bold(\" Cross-domain connections:\"));\n for (const conn of result.connections.slice(0, 10)) {\n console.log(\n ` ${chalk.magenta(\"⟷\")} ${dim(conn.fromDomain)} ${chalk.dim(\"→\")} ${dim(conn.toDomain)} ` +\n `${dim(shortPath(conn.fromPath))} → ${dim(shortPath(conn.toPath))} ` +\n `${dim(`strength: ${conn.strength}`)}`\n );\n }\n console.log();\n }\n\n console.log(bold(\" Synthesis prompt (paste into your AI):\"));\n console.log();\n const promptLines = result.synthesisPrompt.split(\"\\n\");\n for (const line of promptLines) {\n console.log(` ${dim(line)}`);\n }\n console.log();\n}\n\nexport function registerConverseCommand(parent: Command): void {\n parent\n .command(\"converse <question>\")\n .description(\"Ask the vault a question and get a synthesis prompt with relevant notes\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--depth <n>\", \"Graph expansion depth around matched notes\", \"2\")\n .option(\"--limit <n>\", \"Maximum relevant notes to include\", \"15\")\n .action(async (question: string, opts: { vaultProjectId?: string; depth?: string; limit?: string }) => {\n try {\n await cmdConverse(question, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** zettel themes: detect emerging theme clusters in recently edited notes. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedDb, shortPath } from \"./utils.js\";\n\nasync function cmdThemes(opts: {\n vaultProjectId?: string;\n days?: string;\n minSize?: string;\n maxThemes?: string;\n threshold?: string;\n}): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exit(1);\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const lookbackDays = parseInt(opts.days ?? \"30\", 10);\n const minClusterSize = parseInt(opts.minSize ?? \"3\", 10);\n const maxThemes = parseInt(opts.maxThemes ?? \"10\", 10);\n const similarityThreshold = parseFloat(opts.threshold ?? \"0.65\");\n\n const { zettelThemes } = await import(\"../../../zettelkasten/index.js\");\n const db = getFedDb();\n\n console.log();\n console.log(header(\" PAI Zettel Themes\"));\n console.log(dim(` Lookback: ${lookbackDays}d Min cluster: ${minClusterSize} Threshold: ${similarityThreshold}`));\n process.stdout.write(dim(\" Detecting emerging themes...\\n\"));\n\n const result = await zettelThemes(db, {\n vaultProjectId,\n lookbackDays,\n minClusterSize,\n maxThemes,\n similarityThreshold,\n });\n\n if (result.themes.length === 0) {\n console.log(warn(` No themes detected in the last ${lookbackDays} days. Try --days with a larger window.`));\n console.log();\n return;\n }\n\n const fromDate = new Date(result.timeWindow.from).toISOString().slice(0, 10);\n const toDate = new Date(result.timeWindow.to).toISOString().slice(0, 10);\n\n console.log();\n console.log(bold(` ${result.themes.length} theme(s) from ${result.totalNotesAnalyzed} notes [${fromDate} → ${toDate}]:`));\n console.log();\n\n for (let i = 0; i < result.themes.length; i++) {\n const cluster = result.themes[i];\n const diversityBar = Math.round(cluster.folderDiversity * 10);\n const indexSuggestion = cluster.suggestIndexNote ? chalk.yellow(\" ⚑ suggest index note\") : \"\";\n\n console.log(` ${chalk.cyan(String(i + 1).padStart(2, \" \"))}. ${bold(cluster.label)}${indexSuggestion}`);\n console.log(\n ` ${dim(\"Notes:\")} ${cluster.size} ` +\n `${dim(\"Diversity:\")} ${\"█\".repeat(diversityBar)}${\"░\".repeat(10 - diversityBar)} ${cluster.folderDiversity.toFixed(2)} ` +\n `${dim(\"Linked:\")} ${Math.round(cluster.linkedRatio * 100)}%`\n );\n\n const preview = cluster.notes.slice(0, 5);\n for (const note of preview) {\n const title = note.title ?? shortPath(note.path);\n console.log(` ${dim(\"•\")} ${title}`);\n }\n if (cluster.notes.length > 5) {\n console.log(dim(` ... and ${cluster.notes.length - 5} more`));\n }\n console.log();\n }\n}\n\nexport function registerThemesCommand(parent: Command): void {\n parent\n .command(\"themes\")\n .description(\"Detect emerging theme clusters in recently edited notes\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--days <n>\", \"Look-back window in days\", \"30\")\n .option(\"--min-size <n>\", \"Minimum notes per cluster\", \"3\")\n .option(\"--max-themes <n>\", \"Maximum themes to return\", \"10\")\n .option(\"--threshold <f>\", \"Similarity threshold for clustering (0–1)\", \"0.65\")\n .action(async (opts: { vaultProjectId?: string; days?: string; minSize?: string; maxThemes?: string; threshold?: string }) => {\n try {\n await cmdThemes(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/** Barrel: registers all zettel sub-commands and exports registerZettelCommands. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { registerExploreCommand } from \"./explore.js\";\nimport { registerHealthCommand } from \"./health.js\";\nimport { registerSurpriseCommand } from \"./surprise.js\";\nimport { registerSuggestCommand } from \"./suggest.js\";\nimport { registerConverseCommand } from \"./converse.js\";\nimport { registerThemesCommand } from \"./themes.js\";\n\nexport function registerZettelCommands(\n parent: Command,\n _getDb: () => Database // registry DB (unused — federation DB is opened directly)\n): void {\n registerExploreCommand(parent);\n registerHealthCommand(parent);\n registerSurpriseCommand(parent);\n registerSuggestCommand(parent);\n registerConverseCommand(parent);\n registerThemesCommand(parent);\n}\n","/**\n * pai observation <sub-command>\n *\n * list — List recent observations with filtering options\n * search — Search observations by title/narrative\n * stats — Show observation statistics\n */\n\nimport type { Command } from \"commander\";\nimport { createConnection } from \"net\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport chalk from \"chalk\";\n\n// ---------------------------------------------------------------------------\n// IPC helper — communicates with PAI daemon via Unix socket\n// ---------------------------------------------------------------------------\n\nfunction ipcCall(method: string, params: Record<string, unknown>): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let settled = false;\n const settle = (fn: () => void) => { if (!settled) { settled = true; fn(); } };\n\n const client = createConnection(\"/tmp/pai.sock\", () => {\n client.write(JSON.stringify({ id: 1, method, params }) + \"\\n\");\n });\n let data = \"\";\n client.on(\"data\", (chunk) => {\n data += chunk.toString();\n // Daemon sends a single JSON line then closes — try parsing eagerly\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n client.end();\n settle(() => resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\")));\n } catch {\n // Incomplete — wait for more data or end\n }\n });\n client.on(\"end\", () => {\n settle(() => {\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\"));\n } catch (e) { reject(e); }\n });\n });\n client.on(\"error\", (e) => settle(() => reject(e)));\n setTimeout(() => { client.destroy(); settle(() => reject(new Error(\"IPC timeout\"))); }, 10000);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Type colour mapping\n// ---------------------------------------------------------------------------\n\nfunction typeColor(type: string): string {\n switch (type) {\n case \"decision\": return chalk.cyan(type);\n case \"bugfix\": return chalk.red(type);\n case \"feature\": return chalk.green(type);\n case \"refactor\": return chalk.yellow(type);\n case \"discovery\": return chalk.blue(type);\n case \"change\": return chalk.magenta(type);\n default: return chalk.white(type);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Date formatting helper\n// ---------------------------------------------------------------------------\n\nfunction fmtTs(ts: string | null | undefined): string {\n if (!ts) return dim(\"—\");\n try {\n const d = new Date(ts);\n const date = d.toISOString().slice(0, 10);\n const time = d.toISOString().slice(11, 16);\n return `${date} ${time}`;\n } catch {\n return dim(\"—\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Truncate a string to maxLen visible characters\n// ---------------------------------------------------------------------------\n\nfunction trunc(s: string, maxLen: number): string {\n if (!s) return \"\";\n if (s.length <= maxLen) return s;\n return s.slice(0, maxLen - 1) + \"…\";\n}\n\n// ---------------------------------------------------------------------------\n// Observation shape (from IPC responses)\n// ---------------------------------------------------------------------------\n\ninterface Observation {\n id: number;\n type: string;\n title: string;\n project_slug?: string | null;\n session_id?: string | null;\n created_at: string;\n narrative?: string | null;\n}\n\ninterface ObservationStats {\n total: number;\n by_type: Array<{ type: string; count: number }>;\n by_project: Array<{ project_slug: string | null; count: number }>;\n most_recent: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai observation list\n// ---------------------------------------------------------------------------\n\nasync function cmdList(opts: {\n project?: string;\n type?: string;\n session?: string;\n limit?: string;\n}): Promise<void> {\n const limit = parseInt(opts.limit ?? \"20\", 10);\n\n const params: Record<string, unknown> = { limit };\n if (opts.project) params.project_slug = opts.project;\n if (opts.type) params.type = opts.type;\n if (opts.session) params.session_id = opts.session;\n\n let observations: Observation[];\n try {\n observations = (await ipcCall(\"observation_list\", params)) as Observation[];\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exit(1);\n }\n\n console.log();\n console.log(header(\" PAI Observations\"));\n\n const filterParts: string[] = [];\n if (opts.project) filterParts.push(`project: ${opts.project}`);\n if (opts.type) filterParts.push(`type: ${opts.type}`);\n if (opts.session) filterParts.push(`session: ${opts.session}`);\n filterParts.push(`limit: ${limit}`);\n console.log(dim(` ${filterParts.join(\" | \")}`));\n console.log();\n\n if (!observations || observations.length === 0) {\n console.log(warn(\" No observations found.\"));\n console.log();\n return;\n }\n\n // Column widths\n const ID_W = 4;\n const TYPE_W = 10;\n const TITLE_W = 42;\n const PROJ_W = 14;\n const TS_W = 16;\n\n // Header row\n console.log(\n \" \" +\n bold(\"id\".padEnd(ID_W)) + \" \" +\n bold(\"type\".padEnd(TYPE_W)) + \" \" +\n bold(\"title\".padEnd(TITLE_W)) + \" \" +\n bold(\"project\".padEnd(PROJ_W)) + \" \" +\n bold(\"created_at\")\n );\n console.log(\n dim(\n \" \" +\n \"-\".repeat(ID_W) + \" \" +\n \"-\".repeat(TYPE_W) + \" \" +\n \"-\".repeat(TITLE_W) + \" \" +\n \"-\".repeat(PROJ_W) + \" \" +\n \"-\".repeat(TS_W)\n )\n );\n\n for (const obs of observations) {\n const idStr = String(obs.id).padStart(ID_W, \" \");\n const typeStr = typeColor(obs.type ?? \"\").padEnd(TYPE_W + (typeColor(obs.type ?? \"\").length - (obs.type ?? \"\").length));\n const titleStr = trunc(obs.title ?? \"\", TITLE_W).padEnd(TITLE_W);\n const projStr = trunc(obs.project_slug ?? \"—\", PROJ_W).padEnd(PROJ_W);\n const tsStr = fmtTs(obs.created_at);\n\n console.log(` ${idStr} ${typeStr} ${titleStr} ${projStr} ${dim(tsStr)}`);\n }\n\n console.log();\n console.log(dim(` ${observations.length} observation(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai observation search <query>\n// ---------------------------------------------------------------------------\n\nasync function cmdSearch(\n query: string,\n opts: { project?: string; type?: string; limit?: string }\n): Promise<void> {\n const limit = parseInt(opts.limit ?? \"20\", 10);\n\n const params: Record<string, unknown> = { query, limit };\n if (opts.project) params.project_slug = opts.project;\n if (opts.type) params.type = opts.type;\n\n let allObservations: Observation[];\n try {\n // Fetch more than requested to allow client-side text filtering\n allObservations = (await ipcCall(\"observation_query\", { ...params, limit: limit * 5, query: undefined })) as Observation[];\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exit(1);\n }\n\n // Client-side text filter (observation_query doesn't support text search)\n const q = query.toLowerCase();\n const observations = allObservations\n .filter(o =>\n (o.title ?? \"\").toLowerCase().includes(q) ||\n (o.narrative ?? \"\").toLowerCase().includes(q) ||\n (o.tool_input_summary ?? \"\").toLowerCase().includes(q)\n )\n .slice(0, limit);\n\n console.log();\n console.log(header(\" PAI Observation Search\"));\n console.log(dim(` Query: \"${query}\"${opts.project ? ` project: ${opts.project}` : \"\"}${opts.type ? ` type: ${opts.type}` : \"\"} limit: ${limit}`));\n console.log();\n\n if (!observations || observations.length === 0) {\n console.log(warn(` No observations matching \"${query}\".`));\n console.log();\n return;\n }\n\n for (let i = 0; i < observations.length; i++) {\n const obs = observations[i];\n const idx = chalk.dim(String(i + 1).padStart(3, \" \"));\n const type = typeColor(obs.type ?? \"\");\n const title = bold(obs.title ?? \"(untitled)\");\n const proj = obs.project_slug ? dim(`[${obs.project_slug}]`) : dim(\"[—]\");\n const ts = dim(fmtTs(obs.created_at));\n const id = dim(`#${obs.id}`);\n\n console.log(` ${idx} ${type.padEnd(12)} ${title}`);\n console.log(` ${proj} ${ts} ${id}`);\n\n if (obs.narrative) {\n const snippet = trunc(obs.narrative.replace(/\\n/g, \" \"), 160);\n console.log(` ${dim(snippet)}`);\n }\n console.log();\n }\n\n console.log(dim(` ${observations.length} result(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai observation stats\n// ---------------------------------------------------------------------------\n\nasync function cmdStats(): Promise<void> {\n let stats: ObservationStats;\n try {\n stats = (await ipcCall(\"observation_stats\", {})) as ObservationStats;\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exit(1);\n }\n\n console.log();\n console.log(header(\" PAI Observation Statistics\"));\n console.log();\n\n console.log(` ${bold(\"Total observations:\")} ${chalk.cyan(String(stats.total ?? 0))}`);\n if (stats.most_recent) {\n console.log(` ${bold(\"Most recent:\")} ${dim(fmtTs(stats.most_recent))}`);\n }\n console.log();\n\n // By type\n if (stats.by_type && stats.by_type.length > 0) {\n console.log(bold(\" By type:\"));\n const maxCount = Math.max(...stats.by_type.map((r) => r.count));\n for (const row of stats.by_type) {\n const barWidth = 20;\n const filled = Math.round((row.count / maxCount) * barWidth);\n const bar = chalk.cyan(\"█\".repeat(filled)) + dim(\"░\".repeat(barWidth - filled));\n const label = typeColor(row.type).padEnd(12 + (typeColor(row.type).length - row.type.length));\n console.log(` ${label} ${bar} ${String(row.count).padStart(5)}`);\n }\n console.log();\n }\n\n // By project\n if (stats.by_project && stats.by_project.length > 0) {\n console.log(bold(\" By project:\"));\n const maxCount = Math.max(...stats.by_project.map((r) => r.count));\n const show = stats.by_project.slice(0, 15);\n for (const row of show) {\n const barWidth = 20;\n const filled = Math.round((row.count / maxCount) * barWidth);\n const bar = chalk.green(\"█\".repeat(filled)) + dim(\"░\".repeat(barWidth - filled));\n const label = (row.project_slug ?? \"—\").padEnd(20);\n console.log(` ${dim(label)} ${bar} ${String(row.count).padStart(5)}`);\n }\n if (stats.by_project.length > 15) {\n console.log(dim(` ... and ${stats.by_project.length - 15} more project(s)`));\n }\n console.log();\n }\n\n if ((!stats.by_type || stats.by_type.length === 0) && (!stats.by_project || stats.by_project.length === 0)) {\n console.log(warn(\" No observation data yet.\"));\n console.log();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerObservationCommands(parent: Command): void {\n // pai observation list\n parent\n .command(\"list\")\n .description(\"List recent observations\")\n .option(\"--project <slug>\", \"Filter by project slug\")\n .option(\"--type <type>\", \"Filter by type (decision, bugfix, feature, refactor, discovery, change)\")\n .option(\"--session <id>\", \"Filter by session ID\")\n .option(\"--limit <n>\", \"Maximum results\", \"20\")\n .action(async (opts: { project?: string; type?: string; session?: string; limit?: string }) => {\n try {\n await cmdList(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n\n // pai observation search <query>\n parent\n .command(\"search <query>\")\n .description(\"Search observations by title or narrative text\")\n .option(\"--project <slug>\", \"Filter by project slug\")\n .option(\"--type <type>\", \"Filter by type\")\n .option(\"--limit <n>\", \"Maximum results\", \"20\")\n .action(async (query: string, opts: { project?: string; type?: string; limit?: string }) => {\n try {\n await cmdSearch(query, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n\n // pai observation stats\n parent\n .command(\"stats\")\n .description(\"Show observation statistics: totals, by type, by project\")\n .action(async () => {\n try {\n await cmdStats();\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/**\n * pai skill <sub-command>\n *\n * telemetry — Show skill-invocation telemetry (self-educating skill system).\n *\n * The `skill` namespace is reserved for the wider self-educating loop\n * (search / install / audit / trial) — see Notes/swarm/skills-self-educating.md.\n */\n\nimport type { Command } from \"commander\";\nimport { createConnection } from \"net\";\nimport { warn, err, dim, bold, header } from \"../utils.js\";\nimport chalk from \"chalk\";\n\n// ---------------------------------------------------------------------------\n// IPC helper — communicates with PAI daemon via Unix socket\n// ---------------------------------------------------------------------------\n\nfunction ipcCall(method: string, params: Record<string, unknown>): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let settled = false;\n const settle = (fn: () => void) => { if (!settled) { settled = true; fn(); } };\n\n const client = createConnection(\"/tmp/pai.sock\", () => {\n client.write(JSON.stringify({ id: 1, method, params }) + \"\\n\");\n });\n let data = \"\";\n client.on(\"data\", (chunk) => {\n data += chunk.toString();\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n client.end();\n settle(() => resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\")));\n } catch {\n // Incomplete — wait for more data or end\n }\n });\n client.on(\"end\", () => {\n settle(() => {\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\"));\n } catch (e) { reject(e); }\n });\n });\n client.on(\"error\", (e) => settle(() => reject(e)));\n setTimeout(() => { client.destroy(); settle(() => reject(new Error(\"IPC timeout\"))); }, 10000);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fmtTs(ts: string | null | undefined): string {\n if (!ts) return dim(\"—\");\n try {\n const d = new Date(ts);\n return `${d.toISOString().slice(0, 10)} ${d.toISOString().slice(11, 16)}`;\n } catch {\n return dim(\"—\");\n }\n}\n\nfunction statusColor(status: string): string {\n switch (status) {\n case \"active\": return chalk.green(status);\n case \"trial\": return chalk.yellow(status);\n case \"archived\": return chalk.dim(status);\n default: return chalk.white(status);\n }\n}\n\nfunction trunc(s: string, maxLen: number): string {\n if (!s) return \"\";\n return s.length <= maxLen ? s : s.slice(0, maxLen - 1) + \"…\";\n}\n\ninterface SkillTelemetry {\n id: number;\n scope: string;\n skill_name: string;\n source: string;\n status: string;\n trigger_count: number;\n accept_count: number;\n first_triggered: string;\n last_triggered: string;\n context_projects: string[];\n hash: string | null;\n audit_status: string | null;\n last_audited: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai skill telemetry\n// ---------------------------------------------------------------------------\n\nasync function cmdTelemetry(opts: {\n scope?: string;\n status?: string;\n limit?: string;\n json?: boolean;\n}): Promise<void> {\n const params: Record<string, unknown> = {\n scope: opts.scope ?? \"default\",\n limit: parseInt(opts.limit ?? \"50\", 10),\n };\n if (opts.status) params.status = opts.status;\n\n let rows: SkillTelemetry[];\n try {\n rows = (await ipcCall(\"skill_telemetry_query\", params)) as SkillTelemetry[];\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exit(1);\n }\n\n if (opts.json) {\n console.log(JSON.stringify(rows ?? [], null, 2));\n return;\n }\n\n console.log();\n console.log(header(\" Skill Telemetry\"));\n console.log(dim(` scope: ${params.scope}${opts.status ? ` | status: ${opts.status}` : \"\"}`));\n console.log();\n\n if (!rows || rows.length === 0) {\n console.log(warn(\" No skill telemetry yet.\"));\n console.log(dim(\" Invoke a skill (e.g. /name) and it will be recorded.\"));\n console.log();\n return;\n }\n\n const NAME_W = 24;\n const SRC_W = 10;\n const STAT_W = 9;\n const TRIG_W = 8;\n const SEEN_W = 16;\n\n console.log(\n \" \" +\n bold(\"skill\".padEnd(NAME_W)) + \" \" +\n bold(\"source\".padEnd(SRC_W)) + \" \" +\n bold(\"status\".padEnd(STAT_W)) + \" \" +\n bold(\"triggers\".padStart(TRIG_W)) + \" \" +\n bold(\"last used\".padEnd(SEEN_W)) + \" \" +\n bold(\"projects\")\n );\n console.log(dim(\" \" + \"-\".repeat(NAME_W + SRC_W + STAT_W + TRIG_W + SEEN_W + 18)));\n\n for (const r of rows) {\n const name = trunc(r.skill_name ?? \"\", NAME_W).padEnd(NAME_W);\n const src = trunc(r.source ?? \"\", SRC_W).padEnd(SRC_W);\n const stat = statusColor(r.status ?? \"\");\n const statPad = stat.padEnd(STAT_W + (stat.length - (r.status ?? \"\").length));\n const trig = String(r.trigger_count ?? 0).padStart(TRIG_W);\n const seen = fmtTs(r.last_triggered).padEnd(SEEN_W);\n const projs = Array.isArray(r.context_projects) ? r.context_projects.join(\", \") : \"\";\n console.log(` ${chalk.white(name)} ${dim(src)} ${statPad} ${chalk.cyan(trig)} ${dim(seen)} ${dim(trunc(projs, 30))}`);\n }\n\n console.log();\n console.log(dim(` ${rows.length} skill(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerSkillCommands(parent: Command): void {\n parent\n .command(\"telemetry\")\n .description(\"Show skill-invocation telemetry (triggers, last used, projects)\")\n .option(\"--scope <scope>\", \"Governance scope\", \"default\")\n .option(\"--status <status>\", \"Filter by status: trial | active | archived\")\n .option(\"--limit <n>\", \"Maximum rows\", \"50\")\n .option(\"--json\", \"Output raw JSON\")\n .action(async (opts: { scope?: string; status?: string; limit?: string; json?: boolean }) => {\n try {\n await cmdTelemetry(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exit(1);\n }\n });\n}\n","/**\n * pai update — Update PAI from the GitHub repository without losing customizations.\n *\n * Steps:\n * 1. Verify we're in a git repo with a remote (origin)\n * 2. Stash any local changes\n * 3. Pull latest from origin/main\n * 4. Pop the stash if there was one (handle conflicts gracefully)\n * 5. Rebuild: bun install && bun run build\n * 6. Restart the daemon (SIGHUP or pai daemon restart)\n * 7. Check if the CLAUDE.md template has changed and offer to refresh\n * 8. Run pai registry scan to update markers\n * 9. Report what changed\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { execSync, spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction line(text = \"\") {\n console.log(text);\n}\n\nfunction step(msg: string) {\n console.log(chalk.bold.cyan(`\\n ${msg}`));\n}\n\nfunction info(msg: string) {\n console.log(dim(` ${msg}`));\n}\n\nfunction success(msg: string) {\n console.log(ok(` ${msg}`));\n}\n\nfunction warning(msg: string) {\n console.log(warn(` ${msg}`));\n}\n\nfunction error(msg: string) {\n console.log(err(` ${msg}`));\n}\n\n/**\n * Run a shell command, streaming output to stdout.\n * Returns true on success, false on failure.\n */\nfunction run(cmd: string, cwd?: string): boolean {\n try {\n execSync(cmd, {\n stdio: \"inherit\",\n cwd: cwd ?? process.cwd(),\n env: process.env,\n });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Run a shell command silently, returning stdout or null on failure.\n */\nfunction capture(cmd: string, cwd?: string): string | null {\n try {\n return execSync(cmd, {\n stdio: \"pipe\",\n cwd: cwd ?? process.cwd(),\n env: process.env,\n }).toString().trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Get the PAI source directory: walk up from dist/cli/ or src/cli/ to the\n * package root where package.json lives.\n */\nfunction getPaiSrcDir(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // dist/cli/index.mjs → dist/ → package root\n return join(__dirname, \"../..\");\n}\n\n/**\n * Locate the templates directory relative to the installed package.\n */\nfunction getTemplatesDir(): string {\n const candidates = [\n join(getPaiSrcDir(), \"templates\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"templates\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@mnott\", \"pai\", \"templates\"),\n ];\n for (const c of candidates) {\n if (existsSync(join(c, \"claude-md.template.md\"))) return c;\n }\n return join(getPaiSrcDir(), \"templates\");\n}\n\n// ---------------------------------------------------------------------------\n// Update steps\n// ---------------------------------------------------------------------------\n\n/**\n * Step 1: Verify git repo + remote.\n * Returns the PAI source directory (repo root) or null if not a git repo.\n */\nfunction stepVerifyRepo(): string | null {\n step(\"Checking repository...\");\n\n const paiDir = getPaiSrcDir();\n\n // Check git repo\n const gitDir = capture(\"git rev-parse --show-toplevel\", paiDir);\n if (!gitDir) {\n error(\"Not a git repository. Cannot update automatically.\");\n info(\"Install PAI via npm to get automatic updates: npm install -g @tekmidian/pai\");\n return null;\n }\n\n // Check remote\n const remote = capture(\"git remote get-url origin\", gitDir);\n if (!remote) {\n error(\"No 'origin' remote configured. Cannot pull updates.\");\n info(\"Add a remote: git remote add origin https://github.com/mnott/PAI.git\");\n return null;\n }\n\n info(`Repository: ${gitDir}`);\n info(`Remote: ${remote}`);\n return gitDir;\n}\n\n/**\n * Step 2: Stash local changes.\n * Returns true if something was stashed (so we need to pop later).\n */\nfunction stepStash(repoDir: string): boolean {\n step(\"Stashing local changes...\");\n\n // Check if there are any changes to stash\n const status = capture(\"git status --porcelain\", repoDir);\n if (!status) {\n info(\"Working tree is clean — nothing to stash.\");\n return false;\n }\n\n info(\"Uncommitted changes found:\");\n const statusLines = status.split(\"\\n\").slice(0, 5);\n for (const l of statusLines) {\n info(` ${l}`);\n }\n if (status.split(\"\\n\").length > 5) {\n info(` ... and ${status.split(\"\\n\").length - 5} more`);\n }\n\n const result = capture(\"git stash push -m 'pai update: auto-stash'\", repoDir);\n if (result === null) {\n warning(\"Could not stash changes. Proceeding anyway — merge conflicts may occur.\");\n return false;\n }\n\n if (result.includes(\"No local changes to save\")) {\n info(\"Nothing to stash.\");\n return false;\n }\n\n success(\"Changes stashed.\");\n return true;\n}\n\n/**\n * Step 3: Pull latest from origin/main.\n * Returns the git log summary of new commits or null if already up to date.\n */\nfunction stepPull(repoDir: string): string | null {\n step(\"Pulling latest from origin/main...\");\n\n // Get current HEAD before pull\n const headBefore = capture(\"git rev-parse HEAD\", repoDir);\n\n const pulled = run(\"git pull origin main\", repoDir);\n if (!pulled) {\n warning(\"git pull failed. There may be merge conflicts with your stash.\");\n return null;\n }\n\n // Get HEAD after pull\n const headAfter = capture(\"git rev-parse HEAD\", repoDir);\n\n if (headBefore === headAfter) {\n success(\"Already up to date — no new commits.\");\n return null;\n }\n\n // Show what changed\n const log = capture(\n `git log --oneline ${headBefore ?? \"\"}..${headAfter ?? \"HEAD\"}`,\n repoDir,\n );\n if (log) {\n success(\"New commits pulled:\");\n for (const l of log.split(\"\\n\")) {\n info(` ${l}`);\n }\n }\n\n return log;\n}\n\n/**\n * Step 4: Pop stash (if we stashed anything).\n */\nfunction stepPopStash(repoDir: string): void {\n step(\"Restoring local changes (git stash pop)...\");\n\n const result = spawnSync(\"git\", [\"stash\", \"pop\"], {\n cwd: repoDir,\n stdio: \"pipe\",\n encoding: \"utf8\",\n });\n\n if (result.status === 0) {\n success(\"Local changes restored.\");\n } else {\n const stderr = result.stderr ?? \"\";\n if (stderr.includes(\"CONFLICT\")) {\n warning(\"Stash pop caused merge conflicts. Resolve them manually:\");\n info(\" git status\");\n info(\" # Edit conflicting files\");\n info(\" git add <files>\");\n info(\" git stash drop\");\n } else {\n warning(`Stash pop encountered an issue: ${stderr.trim() || \"unknown error\"}`);\n info(\" Run: git stash pop\");\n info(\" Or: git stash list (to see stashed changes)\");\n }\n }\n}\n\n/**\n * Step 5: Rebuild.\n */\nfunction stepBuild(repoDir: string): boolean {\n step(\"Rebuilding PAI (bun install && bun run build)...\");\n\n info(\"Installing dependencies...\");\n const installed = run(\"bun install\", repoDir);\n if (!installed) {\n error(\"bun install failed.\");\n return false;\n }\n\n info(\"Building...\");\n const built = run(\"bun run build\", repoDir);\n if (!built) {\n error(\"bun run build failed.\");\n return false;\n }\n\n success(\"Build complete.\");\n return true;\n}\n\n/**\n * Step 6: Restart the daemon.\n * Tries SIGHUP first (graceful reload); falls back to pai daemon restart.\n */\nfunction stepRestartDaemon(repoDir: string): void {\n step(\"Restarting PAI daemon...\");\n\n // Try to find the daemon PID from the socket/pid file\n const pidFile = \"/tmp/pai-daemon.pid\";\n if (existsSync(pidFile)) {\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf8\").trim(), 10);\n if (!isNaN(pid) && pid > 0) {\n const killed = capture(`kill -HUP ${pid}`);\n if (killed !== null) {\n success(`Sent SIGHUP to daemon (PID ${pid}).`);\n return;\n }\n }\n } catch {\n // fall through to pai daemon restart\n }\n }\n\n // Fall back to pai daemon restart subcommand\n const result = spawnSync(\"pai\", [\"daemon\", \"restart\"], {\n cwd: repoDir,\n stdio: \"pipe\",\n encoding: \"utf8\",\n timeout: 10_000,\n });\n\n if (result.status === 0) {\n success(\"Daemon restarted.\");\n } else {\n warning(\"Could not restart daemon automatically.\");\n info(\" Restart manually: pai daemon restart\");\n info(\" Or: pai daemon serve\");\n }\n}\n\n/**\n * Step 7: Check if CLAUDE.md template changed and offer to refresh.\n */\nfunction stepRefreshClaudeMd(repoDir: string, newCommitsLog: string | null): void {\n step(\"Checking CLAUDE.md template...\");\n\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"claude-md.template.md\");\n const claudeMd = join(homedir(), \".claude\", \"CLAUDE.md\");\n\n if (!existsSync(templatePath)) {\n info(\"Template not found — skipping CLAUDE.md check.\");\n return;\n }\n\n if (!existsSync(claudeMd)) {\n info(\"No ~/.claude/CLAUDE.md found — skipping.\");\n return;\n }\n\n const userContent = readFileSync(claudeMd, \"utf8\");\n const isPaiGenerated = userContent.includes(\"Generated by PAI Setup\");\n\n if (!isPaiGenerated) {\n warning(\"~/.claude/CLAUDE.md appears to be custom (not PAI-generated).\");\n info(\" Skipping auto-update. Review the new template manually:\");\n info(` ${templatePath}`);\n return;\n }\n\n // Check if the template was actually modified in the new commits\n let templateChanged = false;\n if (newCommitsLog) {\n // Check git diff on the template file for newly pulled commits\n const diff = capture(\n \"git diff HEAD~1..HEAD -- templates/claude-md.template.md\",\n repoDir,\n );\n templateChanged = !!(diff && diff.trim().length > 0);\n }\n\n if (!templateChanged) {\n info(\"CLAUDE.md template unchanged in this update.\");\n return;\n }\n\n info(\"CLAUDE.md template was updated in this release.\");\n info(\"Refreshing ~/.claude/CLAUDE.md (PAI-generated — safe to overwrite)...\");\n\n let template = readFileSync(templatePath, \"utf8\");\n // Substitute ${HOME} with actual home directory\n template = template.replace(/\\$\\{HOME\\}/g, homedir());\n writeFileSync(claudeMd, template, \"utf8\");\n\n success(\"~/.claude/CLAUDE.md refreshed from updated template.\");\n}\n\n/**\n * Step 8: Run pai registry scan.\n */\nfunction stepRegistryScan(repoDir: string): void {\n step(\"Running pai registry scan...\");\n\n const result = spawnSync(\"pai\", [\"registry\", \"scan\"], {\n cwd: repoDir,\n stdio: \"inherit\",\n timeout: 60_000,\n });\n\n if (result.status === 0) {\n success(\"Registry scan complete.\");\n } else {\n warning(\"Registry scan encountered issues.\");\n info(\" Run manually: pai registry scan\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main update action\n// ---------------------------------------------------------------------------\n\nasync function runUpdate(): Promise<void> {\n line();\n console.log(chalk.bold.cyan(\" ╔═══════════════════════════════════╗\"));\n console.log(chalk.bold.cyan(\" ║ PAI Knowledge OS — Update ║\"));\n console.log(chalk.bold.cyan(\" ╚═══════════════════════════════════╝\"));\n line();\n\n // Step 1: Verify repo\n const repoDir = stepVerifyRepo();\n if (!repoDir) {\n line();\n process.exit(1);\n }\n\n // Step 2: Stash local changes\n const didStash = stepStash(repoDir);\n\n // Step 3: Pull\n const newCommitsLog = stepPull(repoDir);\n\n // Step 4: Pop stash (only if we stashed something)\n if (didStash) {\n stepPopStash(repoDir);\n }\n\n // If no new commits and no stash needed, we can short-circuit after\n // confirming things are current. But we still offer rebuild in case the\n // user's build is stale.\n\n // Step 5: Build\n const buildOk = stepBuild(repoDir);\n if (!buildOk) {\n line();\n error(\"Update failed at build step. Check errors above.\");\n line();\n process.exit(1);\n }\n\n // Step 6: Restart daemon\n stepRestartDaemon(repoDir);\n\n // Step 7: CLAUDE.md refresh\n stepRefreshClaudeMd(repoDir, newCommitsLog);\n\n // Step 8: Registry scan\n stepRegistryScan(repoDir);\n\n // Summary\n line();\n console.log(chalk.bold.cyan(\" ─────────────────────────────────────\"));\n if (newCommitsLog) {\n success(\"PAI updated successfully!\");\n } else {\n success(\"PAI is up to date and rebuilt successfully!\");\n }\n console.log(dim(\" Version: \") + chalk.cyan(capture(\"pai --version\", repoDir) ?? \"unknown\"));\n line();\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerUpdateCommand(program: Command): void {\n program\n .command(\"update\")\n .description(\n \"Update PAI from GitHub (git pull + rebuild + daemon restart). Preserves local customizations.\",\n )\n .action(async () => {\n await runUpdate();\n });\n}\n","/**\n * pai notify <sub-command>\n *\n * status — Show current notification mode and active channels\n * get — Alias for status\n * set — Set notification mode or channel/routing config\n * test — Send a test notification through configured channels\n * send — Send a notification (event + message)\n *\n * All sub-commands communicate with the running daemon via IPC.\n */\n\nimport type { Command } from \"commander\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { PaiClient } from \"../../daemon/ipc-client.js\";\nimport type {\n NotificationConfig,\n NotificationMode,\n NotificationEvent,\n} from \"../../notifications/types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction makeClient(): PaiClient {\n const config = loadConfig();\n return new PaiClient(config.socketPath);\n}\n\nfunction modeColor(mode: NotificationMode): string {\n switch (mode) {\n case \"off\":\n return warn(mode);\n case \"voice\":\n return bold(ok(mode));\n case \"auto\":\n return ok(mode);\n default:\n return ok(mode);\n }\n}\n\nfunction printConfig(config: NotificationConfig, activeChannels: string[]): void {\n console.log();\n console.log(header(\" PAI Notification Config\"));\n console.log();\n console.log(` ${bold(\"Mode:\")} ${modeColor(config.mode)}`);\n console.log();\n console.log(` ${bold(\"Channels:\")}`);\n for (const [ch, cfg] of Object.entries(config.channels)) {\n const c = cfg as { enabled: boolean; url?: string; voiceName?: string };\n const status = c.enabled ? ok(\"enabled\") : dim(\"disabled\");\n let extra = \"\";\n if (ch === \"ntfy\" && c.url) extra = dim(` ${c.url}`);\n if (ch === \"voice\" && c.voiceName) extra = dim(` voice: ${c.voiceName}`);\n console.log(` ${bold(ch.padEnd(12))}${status}${extra}`);\n }\n console.log();\n console.log(` ${bold(\"Active:\")} ${activeChannels.length > 0 ? activeChannels.join(\", \") : dim(\"(none)\")}`);\n console.log();\n console.log(` ${bold(\"Routing:\")} ${dim(\"(auto mode only)\")}`);\n for (const [event, channels] of Object.entries(config.routing)) {\n const ch = (channels as string[]).join(\", \") || dim(\"(none)\");\n console.log(` ${event.padEnd(12)}→ ${ch}`);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nasync function cmdStatus(): Promise<void> {\n const client = makeClient();\n try {\n const { config, activeChannels } = await client.getNotificationConfig();\n printConfig(config, activeChannels);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.log();\n console.log(warn(\" Cannot reach PAI daemon.\"));\n console.log(dim(` ${msg}`));\n console.log(dim(\" Start it with: pai daemon serve\"));\n console.log();\n process.exit(1);\n }\n}\n\nasync function cmdSet(opts: {\n mode?: string;\n ntfyUrl?: string;\n ntfyPriority?: string;\n whatsappRecipient?: string;\n enableChannel?: string[];\n disableChannel?: string[];\n}): Promise<void> {\n const client = makeClient();\n\n // Build the patch object\n const patch: {\n mode?: NotificationMode;\n channels?: Record<string, unknown>;\n } = {};\n\n if (opts.mode) {\n const validModes: NotificationMode[] = [\n \"auto\", \"voice\", \"whatsapp\", \"ntfy\", \"macos\", \"cli\", \"off\",\n ];\n if (!validModes.includes(opts.mode as NotificationMode)) {\n console.error(err(`Invalid mode: ${opts.mode}. Valid: ${validModes.join(\", \")}`));\n process.exit(1);\n }\n patch.mode = opts.mode as NotificationMode;\n }\n\n // Channel enable/disable\n const channels: Record<string, unknown> = {};\n\n if (opts.enableChannel) {\n for (const ch of opts.enableChannel) {\n channels[ch] = { enabled: true };\n }\n }\n\n if (opts.disableChannel) {\n for (const ch of opts.disableChannel) {\n channels[ch] = { enabled: false };\n }\n }\n\n if (opts.ntfyUrl) {\n channels[\"ntfy\"] = {\n ...(channels[\"ntfy\"] as object ?? {}),\n url: opts.ntfyUrl,\n };\n }\n\n if (opts.ntfyPriority) {\n channels[\"ntfy\"] = {\n ...(channels[\"ntfy\"] as object ?? {}),\n priority: opts.ntfyPriority,\n };\n }\n\n if (opts.whatsappRecipient) {\n channels[\"whatsapp\"] = {\n ...(channels[\"whatsapp\"] as object ?? {}),\n recipient: opts.whatsappRecipient,\n };\n }\n\n if (Object.keys(channels).length > 0) {\n patch.channels = channels;\n }\n\n if (!patch.mode && !patch.channels) {\n console.error(err(\"No changes specified. Use --mode, --enable, --disable, etc.\"));\n console.log(dim(\" Example: pai notify set --mode voice\"));\n console.log(dim(\" Example: pai notify set --mode auto --enable macos --disable ntfy\"));\n process.exit(1);\n }\n\n try {\n const { config } = await client.setNotificationConfig(\n patch as Parameters<typeof client.setNotificationConfig>[0]\n );\n console.log();\n console.log(ok(\" Notification config updated.\"));\n console.log(` ${bold(\"Mode:\")} ${modeColor(config.mode)}`);\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` Failed: ${msg}`));\n process.exit(1);\n }\n}\n\nasync function cmdTest(opts: { event?: string }): Promise<void> {\n const client = makeClient();\n const event = (opts.event ?? \"info\") as NotificationEvent;\n\n console.log();\n console.log(dim(` Sending test ${event} notification...`));\n\n try {\n const result = await client.sendNotification({\n event,\n message: `PAI test notification (${event})`,\n title: \"PAI Test\",\n });\n\n if (result.channelsSucceeded.length === 0 && result.channelsAttempted.length > 0) {\n console.log(warn(\" All channels failed.\"));\n } else if (result.channelsAttempted.length === 0) {\n console.log(warn(\" No channels active for this event. Check mode and channel config.\"));\n } else {\n console.log(ok(` Sent to: ${result.channelsSucceeded.join(\", \")}`));\n }\n\n if (result.channelsFailed.length > 0) {\n console.log(warn(` Failed: ${result.channelsFailed.join(\", \")}`));\n }\n\n console.log(dim(` Mode: ${result.mode}`));\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` Failed: ${msg}`));\n process.exit(1);\n }\n}\n\nasync function cmdSend(\n event: string,\n message: string,\n opts: { title?: string }\n): Promise<void> {\n const client = makeClient();\n const validEvents: NotificationEvent[] = [\n \"error\", \"progress\", \"completion\", \"info\", \"debug\",\n ];\n\n if (!validEvents.includes(event as NotificationEvent)) {\n console.error(\n err(`Invalid event: ${event}. Valid: ${validEvents.join(\", \")}`)\n );\n process.exit(1);\n }\n\n try {\n const result = await client.sendNotification({\n event: event as NotificationEvent,\n message,\n title: opts.title,\n });\n\n if (result.channelsSucceeded.length > 0) {\n console.log(ok(` Sent to: ${result.channelsSucceeded.join(\", \")}`));\n } else {\n console.log(warn(\" No channels received the notification.\"));\n }\n\n if (result.channelsFailed.length > 0) {\n console.log(warn(` Failed: ${result.channelsFailed.join(\", \")}`));\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` Failed: ${msg}`));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerNotifyCommands(notifyCmd: Command): void {\n // pai notify status (default / alias for get)\n notifyCmd\n .command(\"status\")\n .description(\"Show current notification mode and active channels\")\n .action(async () => {\n await cmdStatus();\n });\n\n // pai notify get (alias for status)\n notifyCmd\n .command(\"get\")\n .description(\"Show current notification config (alias for status)\")\n .action(async () => {\n await cmdStatus();\n });\n\n // pai notify set\n notifyCmd\n .command(\"set\")\n .description(\"Update notification mode or channel configuration\")\n .option(\n \"--mode <mode>\",\n \"Notification mode: auto | voice | whatsapp | ntfy | macos | cli | off\"\n )\n .option(\"--enable <channel...>\", \"Enable one or more channels\")\n .option(\"--disable <channel...>\", \"Disable one or more channels\")\n .option(\"--ntfy-url <url>\", \"Set the ntfy.sh topic URL\")\n .option(\n \"--ntfy-priority <level>\",\n \"Set ntfy priority: min | low | default | high | urgent\"\n )\n .option(\"--whatsapp-recipient <contact>\", \"Set WhatsApp recipient\")\n .action(\n async (opts: {\n mode?: string;\n enable?: string[];\n disable?: string[];\n ntfyUrl?: string;\n ntfyPriority?: string;\n whatsappRecipient?: string;\n }) => {\n await cmdSet({\n mode: opts.mode,\n enableChannel: opts.enable,\n disableChannel: opts.disable,\n ntfyUrl: opts.ntfyUrl,\n ntfyPriority: opts.ntfyPriority,\n whatsappRecipient: opts.whatsappRecipient,\n });\n }\n );\n\n // pai notify test\n notifyCmd\n .command(\"test\")\n .description(\"Send a test notification through configured channels\")\n .option(\n \"--event <event>\",\n \"Event type to test: error | progress | completion | info | debug\",\n \"info\"\n )\n .action(async (opts: { event?: string }) => {\n await cmdTest(opts);\n });\n\n // pai notify send <event> <message>\n notifyCmd\n .command(\"send <event> <message>\")\n .description(\"Send a notification with an explicit event type and message\")\n .option(\"--title <title>\", \"Optional notification title\")\n .action(async (event: string, message: string, opts: { title?: string }) => {\n await cmdSend(event, message, opts);\n });\n}\n","/**\n * dispatch.ts — Hand tasks to the PAI session that owns them\n *\n * Transport is AIBroker, and it is optional. With it, a task reaches a running\n * session or spawns one. Without it, dispatch degrades to reporting who owns\n * what and leaves acting to the user — PAI must stay useful on machines that\n * do not run iTerm2.\n *\n * See Notes/docs/task-bus.md.\n */\n\nimport type { DispatchResult, Task } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Transport\n// ---------------------------------------------------------------------------\n\n/** What a transport reports back. Mirrors DispatchOutcome minus the states\n * the bus decides for itself (`unrouted`, and `skipped` when disabled). */\nexport interface TransportResult {\n outcome: \"delivered\" | \"spawned\" | \"unlaunchable\" | \"unreachable\" | \"skipped\";\n session?: string;\n reason?: string;\n}\n\n/**\n * The subset of AIBroker the bus needs.\n *\n * Deliberately one atomic call rather than liveSessions/launch/send. Deciding\n * \"is it running? no — launch it, then send\" is session-lifecycle logic, which\n * belongs to whoever owns sessions. Splitting it across a process boundary\n * would duplicate that logic here and race: a session can start or die between\n * the check and the send.\n *\n * Declared as an interface rather than imported so the module carries no hard\n * dependency on AIBroker being installed.\n */\nexport interface Transport {\n dispatch(\n project: string,\n message: string,\n opts: { spawnIfAbsent: boolean },\n ): Promise<TransportResult>;\n}\n\nexport interface DispatchOptions {\n transport?: Transport | null;\n /** When false, report ownership without contacting any session. */\n autoDispatch: boolean;\n /** Launch a session when the owner is not running. Default: true. */\n spawnIfAbsent?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Message\n// ---------------------------------------------------------------------------\n\n/**\n * Render a task as a session-to-session message.\n *\n * The body is included in full and deliberately not summarised: the whole point\n * of the description convention is that a task is actionable months later\n * without re-deriving anything, and truncating it here would undo that.\n */\nexport function renderTask(task: Task): string {\n const parts = [`Task from the bus: ${task.title}`];\n if (task.due) parts.push(`Due: ${task.due}`);\n if (task.priority !== \"p4\") parts.push(`Priority: ${task.priority}`);\n if (task.body.trim()) parts.push(\"\", task.body.trim());\n if (task.sourceUrl) parts.push(\"\", `Source: ${task.sourceUrl}`);\n parts.push(\n \"\",\n `Tracker id: ${task.id}`,\n `When done, run \\`pai task done ${task.id}\\` so it is not dispatched again.`,\n \"No reply is expected — the process that sent this has already exited.\",\n \"If you are blocked, file it back to the tracker with `pai task add` rather than replying.\",\n );\n return parts.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Dispatch\n// ---------------------------------------------------------------------------\n\nexport async function dispatchTask(\n task: Task,\n opts: DispatchOptions,\n): Promise<DispatchResult> {\n if (task.owner.project === null) {\n return {\n task,\n outcome: \"unrouted\",\n reason: task.owner.rawHint\n ? `\"${task.owner.rawHint}\" matches no PAI project`\n : \"no owner label or container\",\n };\n }\n\n const project = task.owner.project;\n\n if (!opts.autoDispatch || !opts.transport) {\n return {\n task,\n outcome: \"skipped\",\n session: project,\n reason: opts.transport\n ? \"autoDispatch is off\"\n : \"no transport available — run `pai <project>` to pick this up\",\n };\n }\n\n let result: TransportResult;\n try {\n result = await opts.transport.dispatch(project, renderTask(task), {\n spawnIfAbsent: opts.spawnIfAbsent !== false,\n });\n } catch (e) {\n // A transport that throws is an infrastructure failure, not a routing\n // decision — surface it rather than reporting the task as skipped.\n return {\n task,\n outcome: \"unlaunchable\",\n session: project,\n reason: e instanceof Error ? e.message : String(e),\n };\n }\n\n // Only projects with a curated alias can be launched. That is a setup gap,\n // not a bug — name the fix so it is actionable without digging.\n if (result.outcome === \"unlaunchable\" && !result.reason?.includes(\"pai project name\")) {\n return {\n task,\n outcome: \"unlaunchable\",\n session: result.session ?? project,\n reason: `${result.reason ?? \"no session could be launched\"} — give it an alias with \\`pai project name <identifier> ${project}\\``,\n };\n }\n\n return { task, outcome: result.outcome, session: result.session ?? project, reason: result.reason };\n}\n\n/**\n * Dispatch a batch, sequentially.\n *\n * Deliberately not parallel: dispatching spawns terminal tabs and types into\n * them. Racing that would interleave input across sessions.\n */\nexport async function dispatchAll(\n tasks: Task[],\n opts: DispatchOptions,\n): Promise<DispatchResult[]> {\n const results: DispatchResult[] = [];\n for (const task of tasks) {\n results.push(await dispatchTask(task, opts));\n }\n return results;\n}\n","/**\n * aibroker.ts — AIBroker transport for the task bus\n *\n * Shells out to the `aibroker` CLI. PAI's CLI cannot call MCP tools, so the\n * MCP-only dispatch primitives are unreachable from a shell — this is the\n * bridge.\n *\n * Optional by design: when AIBroker is not installed, `detect()` returns null\n * and the bus reports ownership instead of delivering. PAI must stay useful on\n * machines that do not run iTerm2.\n *\n * See Notes/docs/task-bus.md.\n */\n\nimport { execFile } from \"node:child_process\";\nimport type { ProbeAnswer } from \"../poller.js\";\nimport type { Transport, TransportResult } from \"../dispatch.js\";\n\n/**\n * How long AIBroker may spend on one dispatch, in seconds.\n *\n * A cold spawn measured ~10s on an idle machine, but boot time is not bounded:\n * under load a session can take considerably longer to start accepting input.\n */\nconst DEFAULT_DISPATCH_TIMEOUT_SECS = 180;\n\n/**\n * Margin between AIBroker's own deadline and when we kill the process.\n *\n * These two timeouts must never disagree. If ours fired first we would kill a\n * dispatch mid-flight and report a transport failure that AIBroker cannot\n * reproduce from its own CLI — a phantom bug, in the other repo, with no trace\n * on either side. So we always pass our budget down via `--timeout` and give\n * it room to time out first and tell us why.\n */\nconst KILL_MARGIN_MS = 15_000;\n\ninterface WireResult {\n outcome?: string;\n project?: string;\n session?: string;\n reason?: string;\n}\n\nconst VALID_OUTCOMES = new Set([\n \"delivered\",\n \"spawned\",\n \"unlaunchable\",\n \"unreachable\",\n \"skipped\",\n]);\n\n/**\n * Run `aibroker dispatch`, feeding the message over stdin.\n *\n * stdin rather than argv is deliberate: task bodies carry the full procedure\n * and reasoning, so they are long, multi-line, and full of quotes and\n * backticks. argv would mangle them or hit length limits.\n */\nfunction run(bin: string, args: string[], stdin: string, killAfterMs: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = execFile(\n bin,\n args,\n { timeout: killAfterMs, maxBuffer: 1024 * 1024 },\n (error, stdout, stderr) => {\n // A non-zero exit carrying parseable JSON is a reported outcome, not a\n // crash — let the caller interpret it rather than throwing here.\n if (error && !stdout.trim().startsWith(\"{\")) {\n reject(new Error(stderr.trim() || error.message));\n return;\n }\n resolve(stdout);\n },\n );\n child.stdin?.end(stdin);\n });\n}\n\nexport class AiBrokerTransport implements Transport {\n constructor(\n private readonly bin = \"aibroker\",\n private readonly timeoutSecs = DEFAULT_DISPATCH_TIMEOUT_SECS,\n ) {}\n\n async dispatch(\n project: string,\n message: string,\n opts: { spawnIfAbsent: boolean },\n ): Promise<TransportResult> {\n const args = [\"dispatch\", project, \"--stdin\", \"--json\", \"--timeout\", String(this.timeoutSecs)];\n if (!opts.spawnIfAbsent) args.push(\"--no-spawn\");\n\n const stdout = await run(\n this.bin,\n args,\n message,\n this.timeoutSecs * 1000 + KILL_MARGIN_MS,\n );\n\n // The CLI prints diagnostics before its JSON, so take the last JSON object\n // rather than assuming stdout is clean.\n const start = stdout.lastIndexOf(\"{\");\n if (start === -1) {\n throw new Error(`aibroker returned no JSON: ${stdout.trim().slice(0, 200)}`);\n }\n\n let wire: WireResult;\n try {\n wire = JSON.parse(stdout.slice(start)) as WireResult;\n } catch {\n throw new Error(`aibroker returned malformed JSON: ${stdout.trim().slice(0, 200)}`);\n }\n\n // Treat an unrecognised outcome as unlaunchable rather than trusting it.\n // Silently passing through an unknown string would report success for a\n // task that never arrived.\n if (!wire.outcome || !VALID_OUTCOMES.has(wire.outcome)) {\n return {\n outcome: \"unlaunchable\",\n session: wire.session ?? project,\n reason: `unexpected outcome from aibroker: ${String(wire.outcome)}`,\n };\n }\n\n return {\n outcome: wire.outcome as TransportResult[\"outcome\"],\n session: wire.session ?? wire.project ?? project,\n reason: wire.reason,\n };\n }\n}\n\n/**\n * Liveness prober backed by `aibroker ask`.\n *\n * Separate from Transport on purpose: asking must NEVER spawn. A probe that\n * creates the session it is probing for turns \"this session died\" into \"all is\n * well\", which is the failure the probe exists to catch.\n */\nexport class AiBrokerProber {\n constructor(\n private readonly bin = \"aibroker\",\n private readonly timeoutSecs = 60,\n ) {}\n\n async ask(project: string, question: string): Promise<ProbeAnswer> {\n let stdout: string;\n try {\n stdout = await run(\n this.bin,\n [\"ask\", project, \"--stdin\", \"--json\", \"--timeout\", String(this.timeoutSecs)],\n question,\n this.timeoutSecs * 1000 + KILL_MARGIN_MS,\n );\n } catch (e) {\n return { state: \"silent\", reason: e instanceof Error ? e.message : String(e) };\n }\n\n const start = stdout.lastIndexOf(\"{\");\n if (start === -1) return { state: \"silent\", reason: \"aibroker returned no JSON\" };\n\n try {\n const wire = JSON.parse(stdout.slice(start)) as {\n replied?: boolean;\n reply?: string;\n reason?: string;\n state?: string;\n };\n\n // Trust `state` when present. Fall back to `replied` for an older\n // aibroker that predates it — but never infer \"busy\" from silence, which\n // is the whole reason the fourth state exists.\n const state = wire.state ?? (wire.replied === true ? \"replied\" : \"silent\");\n if (state === \"replied\" || state === \"busy\" || state === \"silent\" || state === \"absent\") {\n return { state, reply: wire.reply, reason: wire.reason };\n }\n // An unrecognised state must not read as healthy.\n return { state: \"silent\", reason: `unexpected state from aibroker: ${String(wire.state)}` };\n } catch {\n return { state: \"silent\", reason: \"aibroker returned malformed JSON\" };\n }\n }\n}\n\n/**\n * Return a prober if the installed `aibroker` supports `ask`.\n *\n * Probes for the subcommand rather than the binary: an older AIBroker would\n * otherwise fail once per stuck task instead of the scheduler simply knowing it\n * has no liveness check and saying so.\n */\nexport async function detectProber(bin = \"aibroker\"): Promise<AiBrokerProber | null> {\n try {\n const help = await new Promise<string>((resolve, reject) => {\n execFile(bin, [\"help\"], { timeout: 10_000 }, (error, stdout, stderr) => {\n if (error && !stdout) reject(error);\n else resolve(stdout + stderr);\n });\n });\n return /\\bask\\b/.test(help) ? new AiBrokerProber(bin) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Return a transport if the `aibroker` CLI is present and supports dispatch.\n *\n * Probing for the subcommand rather than just the binary matters: AIBroker\n * shipped for a long time without `dispatch`, and an older install would\n * otherwise fail once per task instead of degrading cleanly up front.\n */\nexport async function detectAiBroker(\n bin = \"aibroker\",\n timeoutSecs = DEFAULT_DISPATCH_TIMEOUT_SECS,\n): Promise<Transport | null> {\n try {\n const help = await new Promise<string>((resolve, reject) => {\n execFile(bin, [\"help\"], { timeout: 10_000 }, (error, stdout, stderr) => {\n if (error && !stdout) reject(error);\n else resolve(stdout + stderr);\n });\n });\n return help.includes(\"dispatch\") ? new AiBrokerTransport(bin, timeoutSecs) : null;\n } catch {\n return null;\n }\n}\n","/**\n * scheduler.ts — Todoist-driven routine scheduler\n *\n * Todoist holds the schedule, the ordering, the dependencies and the run state.\n * This module is a dumb poller: it reads that state, decides what should happen\n * now, and reports. No LLM, so a tick costs nothing but an API call.\n *\n * Measured against the live Todoist API before this was written:\n *\n * - Completing a recurring task does NOT create a new one. The SAME task\n * rolls forward: same id, new due date, immediately unchecked. So \"is it\n * closed?\" never detects completion for a recurring task — it is closed for\n * an instant at most. Completion is \"the due date advanced\".\n *\n * - Labels survive that roll-forward. A running marker will therefore NOT\n * clear itself; the poller has to reconcile it, or the task looks\n * permanently in flight and silently never runs again.\n *\n * - Todoist never accumulates missed occurrences. Three days powered off\n * leaves ONE overdue task, not three, and completing it jumps to the next\n * future occurrence. There is no backlog to drain — but equally, Todoist\n * cannot tell you how many windows were missed, so overdue-by-N is computed\n * here.\n *\n * - Writing `due_date` DESTROYS a recurrence rule, silently turning a routine\n * into a one-off. This module must only ever write `due_string`.\n */\n\nimport type { Task } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Label vocabulary\n// ---------------------------------------------------------------------------\n\n/** Set when dispatched, cleared when the due date advances. */\nexport const RUNNING_LABEL = \"pai-running\";\n\n/** Run however late. The default when nothing is specified. */\nexport const CATCHUP_LABEL = \"pai-catchup\";\n\n/** `pai-skip-if-late:4h` — past that, the window is gone; wait for the next. */\nconst SKIP_IF_LATE_RE = /^pai-skip-if-late:(\\d+)([hm])$/i;\n\n/** Fallback when a task has no learned duration yet. */\nexport const DEFAULT_EXPECTED_MINUTES = 30;\n\n/** Probe once the run exceeds expected x this. */\nconst PROBE_FACTOR = 1.5;\n\n/** Consecutive unanswered probes before a run counts as stuck. */\nexport const STUCK_AFTER_FAILED_PROBES = 3;\n\n// ---------------------------------------------------------------------------\n// Decisions\n// ---------------------------------------------------------------------------\n\nexport type Decision =\n /** Not due yet. */\n | { action: \"wait\"; task: Task }\n /** Due (or overdue within policy) and not running — dispatch it. */\n | { action: \"dispatch\"; task: Task; overdueMinutes: number }\n /** Overdue past its skip-if-late window — leave for the next occurrence. */\n | { action: \"skip\"; task: Task; reason: string }\n /** Running and within its expected duration — leave alone. */\n | { action: \"running\"; task: Task; elapsedMinutes: number }\n /** Running and overrun — ask the session whether it is alive. */\n | { action: \"probe\"; task: Task; elapsedMinutes: number; expectedMinutes: number }\n /** Was running, due date has advanced — it finished. */\n | { action: \"complete\"; task: Task; durationMinutes: number | null }\n /** Marked running but nothing knows about it — reconcile. */\n | { action: \"orphaned\"; task: Task; reason: string };\n\n/** In-flight run bookkeeping. Transient: losing it costs one extra probe. */\nexport interface RunState {\n /** Task id → epoch ms when dispatched. */\n startedAt: Record<string, number>;\n /** Task id → consecutive probes with no reply. */\n failedProbes: Record<string, number>;\n}\n\nexport const EMPTY_RUN_STATE: RunState = { startedAt: {}, failedProbes: {} };\n\n// ---------------------------------------------------------------------------\n// Policy helpers\n// ---------------------------------------------------------------------------\n\nexport function isRunning(task: Task): boolean {\n return task.labels.some((l) => l.toLowerCase() === RUNNING_LABEL);\n}\n\n/** Minutes after which a missed window is abandoned, or null for \"always run\". */\nexport function skipIfLateMinutes(task: Task): number | null {\n for (const label of task.labels) {\n const m = SKIP_IF_LATE_RE.exec(label.trim());\n if (m) {\n const n = Number.parseInt(m[1]!, 10);\n return m[2]!.toLowerCase() === \"h\" ? n * 60 : n;\n }\n }\n return null;\n}\n\n/**\n * How overdue a task is, in minutes. Negative means still in the future.\n *\n * A date-only due (\"2026-08-01\") is treated as due at the start of that day,\n * which is what Todoist shows the user.\n */\nexport function overdueMinutes(task: Task, now: number): number {\n if (!task.due) return Number.NEGATIVE_INFINITY;\n const due = task.due.length <= 10 ? `${task.due}T00:00:00` : task.due;\n const dueMs = new Date(due).getTime();\n if (Number.isNaN(dueMs)) return Number.NEGATIVE_INFINITY;\n return Math.floor((now - dueMs) / 60_000);\n}\n\n// ---------------------------------------------------------------------------\n// Duration learning\n// ---------------------------------------------------------------------------\n\n/**\n * Expected runtime for a task.\n *\n * The mean rather than the max, deliberately: with a liveness probe available,\n * guessing low costs one extra probe, while guessing high leaves a dead session\n * undetected for far longer. The probe is what makes a tight estimate safe.\n *\n * Runs that ended in stuck or re-dispatch must never be fed in here — one hung\n * run would inflate the mean and every later run would inherit a too-generous\n * threshold.\n */\nexport function expectedMinutes(observed: number[], fallback = DEFAULT_EXPECTED_MINUTES): number {\n const clean = observed.filter((n) => Number.isFinite(n) && n > 0);\n if (clean.length === 0) return fallback;\n const recent = clean.slice(-5);\n return Math.max(1, Math.round(recent.reduce((a, b) => a + b, 0) / recent.length));\n}\n\n// ---------------------------------------------------------------------------\n// The decision function\n// ---------------------------------------------------------------------------\n\nexport interface DecideOptions {\n now: number;\n state: RunState;\n /** Task id → observed durations in minutes, most recent last. */\n history?: Record<string, number[]>;\n}\n\n/**\n * Decide what should happen to one task on this tick.\n *\n * Pure: no I/O, no clock reads. Everything comes in via options so the state\n * machine can be tested against a fixed clock rather than by waiting.\n */\nexport function decide(task: Task, opts: DecideOptions): Decision {\n const { now, state } = opts;\n const running = isRunning(task);\n const overdue = overdueMinutes(task, now);\n const startedAt = state.startedAt[task.id];\n\n if (running) {\n // Completion is \"the due date moved into the future\" — not \"closed\".\n // A recurring task is closed for an instant at most, so testing for closed\n // would never fire.\n if (overdue < 0) {\n const duration = startedAt ? Math.max(1, Math.round((now - startedAt) / 60_000)) : null;\n return { action: \"complete\", task, durationMinutes: duration };\n }\n\n if (startedAt === undefined) {\n // Labelled running, but we have no record of starting it. Either the\n // state file was lost, or a previous run died without cleaning up.\n // Probing is the safe response — never assume it is dead and re-dispatch.\n return {\n action: \"orphaned\",\n task,\n reason: \"marked running but no start time is known — probe before assuming anything\",\n };\n }\n\n const elapsed = Math.max(0, Math.round((now - startedAt) / 60_000));\n const expected = expectedMinutes(opts.history?.[task.id] ?? []);\n if (elapsed >= expected * PROBE_FACTOR) {\n return { action: \"probe\", task, elapsedMinutes: elapsed, expectedMinutes: expected };\n }\n return { action: \"running\", task, elapsedMinutes: elapsed };\n }\n\n if (overdue < 0) return { action: \"wait\", task };\n\n const limit = skipIfLateMinutes(task);\n if (limit !== null && overdue > limit) {\n return {\n action: \"skip\",\n task,\n reason: `overdue by ${overdue}m, past its ${limit}m window — waiting for the next occurrence`,\n };\n }\n\n return { action: \"dispatch\", task, overdueMinutes: Math.max(0, overdue) };\n}\n\n/**\n * Order tasks for dispatch.\n *\n * Subtasks of the same parent run in Todoist's own order, so a routine is\n * sequenced by dragging its steps in the UI. Everything else falls back to\n * priority then due time, so the most overdue urgent thing goes first.\n */\nexport function dispatchOrder(tasks: Task[]): Task[] {\n const rank = { p1: 0, p2: 1, p3: 2, p4: 3 } as const;\n return [...tasks].sort((a, b) => {\n const p = rank[a.priority] - rank[b.priority];\n if (p !== 0) return p;\n return (a.due ?? \"\").localeCompare(b.due ?? \"\");\n });\n}\n","/**\n * poller.ts — one scheduler tick\n *\n * Reads Todoist, decides, acts, reports. Run by launchd on an interval; carries\n * no LLM, so a tick that finds nothing to do costs one API call and no tokens.\n *\n * Run state (start times, probe counts) is deliberately local and transient.\n * Losing it costs one extra probe, not a wrong decision — `decide` returns\n * \"orphaned\" rather than assuming a task is dead. The durable state — schedule,\n * order, running flag, learned durations — all lives in Todoist, so a new\n * machine picks up where this one left off.\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readJsonStrict, writeJsonAtomic } from \"../config/json-store.js\";\nimport type { TodoistProvider } from \"./providers/todoist.js\";\nimport type { Transport } from \"./dispatch.js\";\nimport { dispatchTask } from \"./dispatch.js\";\nimport {\n decide,\n dispatchOrder,\n isRunning,\n RUNNING_LABEL,\n STUCK_AFTER_FAILED_PROBES,\n type Decision,\n expectedMinutes,\n type RunState,\n EMPTY_RUN_STATE,\n} from \"./scheduler.js\";\nimport type { Task } from \"./types.js\";\n\nconst STATE_FILE = join(homedir(), \".pai\", \"scheduler-state.json\");\n\n/** How many past durations to keep per task. */\nconst HISTORY_LIMIT = 5;\n\ninterface PersistedState extends RunState {\n /** Task id → observed durations in minutes, most recent last. */\n history: Record<string, number[]>;\n /** Task id → epoch ms of the last completion we reported. */\n lastReported: Record<string, number>;\n /**\n * Task id → consecutive dispatch attempts that did not land.\n *\n * Without this a task whose session has died to a shell is retried every\n * tick, forever, reporting the same failure each time and escalating never —\n * the \"silently never runs\" failure this whole subsystem exists to avoid.\n */\n failedDispatches: Record<string, number>;\n}\n\nconst EMPTY: PersistedState = {\n ...EMPTY_RUN_STATE,\n history: {},\n lastReported: {},\n failedDispatches: {},\n};\n\n/** Consecutive failed dispatches before a task is reported as needing attention. */\nconst DISPATCH_FAILURES_BEFORE_ALARM = 3;\n\n/**\n * Run state is a rebuildable cache, so a damaged file must not block the\n * scheduler forever — starting fresh is the correct recovery here, which is\n * exactly the case json-store's guard is NOT for.\n */\nfunction loadState(): PersistedState {\n try {\n const raw = readJsonStrict(STATE_FILE, \"~/.pai/scheduler-state.json\");\n return { ...EMPTY, ...(raw as unknown as PersistedState) };\n } catch {\n return { ...EMPTY };\n }\n}\n\nfunction saveState(state: PersistedState): void {\n writeJsonAtomic(STATE_FILE, state as unknown as Record<string, unknown>, { backup: false });\n}\n\n// ---------------------------------------------------------------------------\n// Liveness probe\n// ---------------------------------------------------------------------------\n\n/**\n * What a probe found.\n *\n * The distinction that matters is `busy` versus `silent`. Claude Code queues\n * typed input while mid-turn and does not read it until the turn ends, so a\n * session doing exactly the work we gave it stays silent for minutes. Since we\n * probe at expected x1.5 — precisely when a slow task is most likely still\n * working — treating silence as death would have declared healthy sessions\n * stuck on ordinary days and \"recovered\" them out from under themselves.\n *\n * AIBroker therefore decides liveness by sampling the screen before sending\n * anything: a working session animates, an idle one is static. `busy` is\n * positive evidence of life and must never count toward the stuck threshold.\n */\nexport type ProbeState = \"replied\" | \"busy\" | \"silent\" | \"absent\";\n\nexport interface ProbeAnswer {\n state: ProbeState;\n reply?: string;\n reason?: string;\n}\n\nexport interface Prober {\n /** Ask the owning session whether it is still working. */\n ask(project: string, question: string): Promise<ProbeAnswer>;\n}\n\n/**\n * Multiple of the expected duration past which a task is flagged even though it\n * keeps reporting `busy`. A session can be alive and still wrong — stuck in a\n * loop, waiting on a prompt that will never come. Liveness is not progress.\n */\nconst GROSS_OVERRUN_FACTOR = 5;\n\n// ---------------------------------------------------------------------------\n// Tick\n// ---------------------------------------------------------------------------\n\nexport interface TickOptions {\n provider: TodoistProvider;\n transport: Transport | null;\n prober: Prober | null;\n autoDispatch: boolean;\n dryRun: boolean;\n now?: number;\n}\n\nexport interface TickReport {\n decisions: Array<{ decision: Decision; note: string }>;\n dispatched: number;\n completed: number;\n stuck: number;\n probed: number;\n}\n\nexport async function tick(opts: TickOptions): Promise<TickReport> {\n const now = opts.now ?? Date.now();\n const state = loadState();\n const report: TickReport = { decisions: [], dispatched: 0, completed: 0, stuck: 0, probed: 0 };\n\n const tasks = await opts.provider.listOpen({ includeUnrouted: true });\n const ordered = dispatchOrder(tasks);\n\n for (const task of ordered) {\n const d = decide(task, { now, state, history: state.history });\n let note = \"\";\n\n switch (d.action) {\n case \"wait\":\n continue; // not worth reporting\n\n case \"skip\":\n note = d.reason;\n break;\n\n case \"running\":\n note = `${d.elapsedMinutes}m elapsed`;\n break;\n\n case \"dispatch\": {\n const r = await handleDispatch(task, d.overdueMinutes, opts, state, now);\n note = r.note;\n if (!opts.dryRun) {\n report.dispatched++;\n if (r.alarm) report.stuck++;\n }\n break;\n }\n\n case \"complete\":\n note = await handleComplete(task, d.durationMinutes, opts, state);\n if (!opts.dryRun) report.completed++;\n break;\n\n case \"probe\":\n case \"orphaned\": {\n const elapsed = d.action === \"probe\" ? d.elapsedMinutes : null;\n const result = await handleProbe(task, elapsed, opts, state);\n note = result.note;\n if (!opts.dryRun) {\n report.probed++;\n if (result.stuck) report.stuck++;\n }\n break;\n }\n }\n\n report.decisions.push({ decision: d, note });\n }\n\n if (!opts.dryRun) saveState(state);\n return report;\n}\n\n// ---------------------------------------------------------------------------\n// Actions\n// ---------------------------------------------------------------------------\n\nasync function handleDispatch(\n task: Task,\n overdue: number,\n opts: TickOptions,\n state: PersistedState,\n now: number\n): Promise<{ note: string; alarm: boolean }> {\n const late = overdue > 5 ? ` (${overdue}m late)` : \"\";\n if (opts.dryRun) {\n return { note: `would dispatch to ${task.owner.project ?? \"nobody\"}${late}`, alarm: false };\n }\n\n if (!task.owner.project) return { note: \"unrouted — cannot dispatch\", alarm: false };\n\n const result = await dispatchTask(task, {\n transport: opts.transport,\n autoDispatch: opts.autoDispatch,\n spawnIfAbsent: true,\n });\n\n if (result.outcome === \"delivered\" || result.outcome === \"spawned\") {\n // Mark running FIRST so a crash between here and the next tick leaves the\n // task visibly in flight rather than being dispatched twice.\n await opts.provider.setLabels(task.id, [...task.labels, RUNNING_LABEL]);\n state.startedAt[task.id] = now;\n delete state.failedProbes[task.id];\n delete state.failedDispatches[task.id];\n return { note: `${result.outcome} to ${result.session}${late}`, alarm: false };\n }\n\n const fails = (state.failedDispatches[task.id] ?? 0) + 1;\n state.failedDispatches[task.id] = fails;\n const detail = `${result.outcome}${result.reason ? \" — \" + result.reason : \"\"}`;\n\n if (fails >= DISPATCH_FAILURES_BEFORE_ALARM) {\n // `unreachable` against a session that has exited to a shell will never\n // resolve on its own: aibroker correctly refuses to type into it, and\n // nothing here can close the tab. Retrying quietly forever is the failure.\n return {\n note: `NOT RUNNING — ${fails} failed dispatches: ${detail}`,\n alarm: true,\n };\n }\n return { note: `not dispatched (${fails}/${DISPATCH_FAILURES_BEFORE_ALARM}): ${detail}`, alarm: false };\n}\n\nasync function handleComplete(\n task: Task,\n durationMinutes: number | null,\n opts: TickOptions,\n state: PersistedState\n): Promise<string> {\n if (opts.dryRun) return `would clear ${RUNNING_LABEL}, ${durationMinutes ?? \"?\"}m`;\n\n await opts.provider.setLabels(\n task.id,\n task.labels.filter((l) => l.toLowerCase() !== RUNNING_LABEL)\n );\n\n const wasStuck = (state.failedProbes[task.id] ?? 0) > 0;\n delete state.startedAt[task.id];\n delete state.failedProbes[task.id];\n\n // A run that needed probing may have been stalled for most of its wall time.\n // Feeding that into the average would inflate every later threshold.\n if (durationMinutes !== null && !wasStuck) {\n const hist = state.history[task.id] ?? [];\n hist.push(durationMinutes);\n state.history[task.id] = hist.slice(-HISTORY_LIMIT);\n }\n\n return durationMinutes === null\n ? \"completed (duration unknown)\"\n : `completed in ${durationMinutes}m${wasStuck ? \" — not recorded, run was probed\" : \"\"}`;\n}\n\nasync function handleProbe(\n task: Task,\n elapsed: number | null,\n opts: TickOptions,\n state: PersistedState\n): Promise<{ note: string; stuck: boolean }> {\n const project = task.owner.project;\n const el = elapsed === null ? \"unknown\" : `${elapsed}m`;\n\n if (opts.dryRun) return { note: `would probe ${project ?? \"?\"} (${el} elapsed)`, stuck: false };\n\n if (!opts.prober || !project) {\n // No probe available. Do NOT guess — an unanswered question is not the same\n // as a dead session, and re-dispatching a live sweep would double-run it.\n return { note: `overrun (${el}) — no liveness probe available, leaving alone`, stuck: false };\n }\n\n const answer = await opts.prober.ask(\n project,\n `Are you still working on the task \"${task.title}\"? Reply in one short line.`\n );\n\n // Both of these are evidence of life. `busy` especially: it means AIBroker saw\n // the session actively producing output and deliberately sent nothing, so it\n // costs no tokens and must not count as a strike.\n if (answer.state === \"replied\" || answer.state === \"busy\") {\n state.failedProbes[task.id] = 0;\n\n const expected = expectedMinutes(state.history[task.id] ?? []);\n if (elapsed !== null && elapsed > expected * GROSS_OVERRUN_FACTOR) {\n // Alive but far past any plausible runtime. Do not recover it — killing a\n // working session is worse — but do not stay quiet either.\n return {\n note: `alive but ${el} against an expected ${expected}m — ${GROSS_OVERRUN_FACTOR}x over, worth a look`,\n stuck: true,\n };\n }\n\n return answer.state === \"busy\"\n ? { note: `busy after ${el} (mid-turn, nothing sent)`, stuck: false }\n : { note: `alive after ${el}: ${answer.reply ?? \"(no detail)\"}`, stuck: false };\n }\n\n const fails = (state.failedProbes[task.id] ?? 0) + 1;\n state.failedProbes[task.id] = fails;\n\n // `absent` is stronger evidence than `silent`, but not acted on immediately:\n // an empty session list is exactly how the hub failed on 2026-08-01, and\n // re-dispatching on a lying hub would spawn a duplicate tab per task.\n const what = answer.state === \"absent\" ? \"session gone\" : \"no reply\";\n\n if (fails >= STUCK_AFTER_FAILED_PROBES) {\n return {\n note: `STUCK after ${el} — ${what} x${fails} (${answer.reason ?? \"no detail\"})`,\n stuck: true,\n };\n }\n return { note: `${what} ${fails}/${STUCK_AFTER_FAILED_PROBES} after ${el}`, stuck: false };\n}\n\nexport { isRunning };\n","/**\n * schedule-install.ts — install the scheduler tick as a launchd agent\n *\n * Two speeds were considered and rejected in favour of one: a fixed interval.\n * A 15-minute tick is 96 runs a day, each one API call and zero tokens, and\n * daily routines do not need better than 15-minute granularity — a 09:00 sweep\n * starting at 09:12 is fine. Adaptive intervals would add a second thing that\n * can silently stop.\n *\n * StartInterval rather than StartCalendarInterval on purpose: the schedule\n * lives in Todoist, not here. This agent only decides how often to *look*.\n * That means one plist total, however many routines exist, and rescheduling a\n * routine never touches the machine.\n */\n\nimport { existsSync, mkdirSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { spawnSync } from \"node:child_process\";\n\nexport const SCHEDULE_LABEL = \"com.pai.task-scheduler\";\nconst LAUNCH_AGENTS = join(homedir(), \"Library\", \"LaunchAgents\");\nexport const SCHEDULE_PLIST = join(LAUNCH_AGENTS, `${SCHEDULE_LABEL}.plist`);\nexport const SCHEDULE_LOG = \"/tmp/pai-scheduler.log\";\n\n/** Default tick, in seconds. */\nexport const DEFAULT_INTERVAL_SECS = 900;\n\nfunction cliPath(): string {\n // dist/cli/index.mjs at runtime\n return fileURLToPath(new URL(\"index.mjs\", import.meta.url));\n}\n\nexport function generateSchedulePlist(intervalSecs: number, cli: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${SCHEDULE_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>/usr/local/bin/node</string>\n <string>${cli}</string>\n <string>task</string>\n <string>poll</string>\n </array>\n\n <key>StartInterval</key>\n <integer>${intervalSecs}</integer>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>StandardOutPath</key>\n <string>${SCHEDULE_LOG}</string>\n\n <key>StandardErrorPath</key>\n <string>${SCHEDULE_LOG}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n <key>PATH</key>\n <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>\n </dict>\n</dict>\n</plist>\n`;\n}\n\nexport interface InstallResult {\n plistPath: string;\n intervalSecs: number;\n loaded: boolean;\n message: string;\n}\n\nexport function installSchedule(intervalSecs = DEFAULT_INTERVAL_SECS): InstallResult {\n if (!existsSync(LAUNCH_AGENTS)) mkdirSync(LAUNCH_AGENTS, { recursive: true });\n\n const cli = cliPath();\n const plist = generateSchedulePlist(intervalSecs, cli);\n\n // Plain write: a plist is generated, not user data, so the json-store guard\n // would be the wrong tool — there is nothing here anyone could not regenerate.\n writeFileSync(SCHEDULE_PLIST, plist, \"utf8\");\n\n spawnSync(\"launchctl\", [\"unload\", SCHEDULE_PLIST], { encoding: \"utf8\" });\n const load = spawnSync(\"launchctl\", [\"load\", SCHEDULE_PLIST], { encoding: \"utf8\" });\n\n return {\n plistPath: SCHEDULE_PLIST,\n intervalSecs,\n loaded: load.status === 0,\n message:\n load.status === 0\n ? `Scheduler installed — ticking every ${Math.round(intervalSecs / 60)} min.`\n : `Plist written but launchctl load failed: ${(load.stderr || \"\").trim()}`,\n };\n}\n\nexport function uninstallSchedule(): string {\n if (!existsSync(SCHEDULE_PLIST)) return \"Scheduler is not installed.\";\n spawnSync(\"launchctl\", [\"unload\", SCHEDULE_PLIST], { encoding: \"utf8\" });\n unlinkSync(SCHEDULE_PLIST);\n return \"Scheduler uninstalled.\";\n}\n\nexport function scheduleStatus(): { installed: boolean; running: boolean; detail: string } {\n const installed = existsSync(SCHEDULE_PLIST);\n if (!installed) return { installed: false, running: false, detail: \"Not installed.\" };\n\n const list = spawnSync(\"launchctl\", [\"list\", SCHEDULE_LABEL], { encoding: \"utf8\" });\n const running = list.status === 0;\n return {\n installed: true,\n running,\n detail: running\n ? `Loaded. Log: ${SCHEDULE_LOG}`\n : \"Plist present but not loaded — run `pai task schedule install` again.\",\n };\n}\n","/**\n * task.ts — `pai task` CLI\n *\n * Surface for the task bus: list what is open, file new work, dispatch tasks to\n * the sessions that own them, and close them out.\n *\n * See Notes/docs/task-bus.md.\n */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { chmodSync } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { loadConfig, CONFIG_FILE } from \"../../daemon/config.js\";\nimport { readConfigRaw, writeConfigRaw } from \"./setup/utils.js\";\nimport { openRegistry } from \"../../registry/db.js\";\nimport { loadAliasMap } from \"../../tasks/resolver.js\";\nimport { TodoistProvider } from \"../../tasks/providers/todoist.js\";\nimport { dispatchAll } from \"../../tasks/dispatch.js\";\nimport { detectAiBroker, detectProber } from \"../../tasks/transport/aibroker.js\";\nimport { tick } from \"../../tasks/poller.js\";\nimport { installSchedule, uninstallSchedule, scheduleStatus, DEFAULT_INTERVAL_SECS } from \"../../tasks/schedule-install.js\";\nimport type { DispatchResult, Task, TaskProvider } from \"../../tasks/types.js\";\n\nconst dim = chalk.dim;\nconst bold = chalk.bold;\n\n// ---------------------------------------------------------------------------\n// Wiring\n// ---------------------------------------------------------------------------\n\nfunction buildProvider(): TaskProvider | null {\n const config = loadConfig();\n const tasks = config.tasks;\n if (!tasks?.enabled) return null;\n\n const aliases = loadAliasMap(openRegistry());\n const provider = new TodoistProvider(tasks.providers.todoist, aliases);\n return provider.isConfigured() ? provider : null;\n}\n\n/** Print the one message that actually helps when nothing is configured. */\nfunction reportUnconfigured(): void {\n console.error(chalk.yellow(\" Task bus is not configured.\"));\n console.error(dim(\" Run `pai setup` and complete the Task Bus step.\"));\n}\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\n// ---------------------------------------------------------------------------\n// Credentials\n// ---------------------------------------------------------------------------\n\n/**\n * Prompt for a secret without echoing it.\n *\n * Never accept a token as a command-line argument: argv is visible in `ps` to\n * every user on the machine and lands in shell history verbatim.\n */\nfunction promptSecret(question: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });\n const out = process.stdout as NodeJS.WriteStream & { muted?: boolean };\n // Swap in a writer that swallows echoed keystrokes but still lets the\n // prompt itself through.\n const write = out.write.bind(out);\n (rl as unknown as { _writeToOutput: (s: string) => void })._writeToOutput = (s: string) => {\n if (!out.muted) write(s);\n };\n write(question);\n out.muted = true;\n rl.question(\"\", (answer) => {\n out.muted = false;\n write(\"\\n\");\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\n/** Show enough of a token to recognise it, never enough to use it. */\nfunction redact(token: string | undefined): string {\n if (!token) return dim(\"(not set)\");\n return token.length <= 8 ? \"********\" : `${token.slice(0, 4)}…${token.slice(-4)}`;\n}\n\ntype RawTasks = {\n enabled?: boolean;\n autoDispatch?: boolean;\n dispatchTimeoutSecs?: number;\n providers?: { todoist?: { enabled?: boolean; apiKey?: string; rootProjectId?: string; findingsSectionId?: string } };\n};\n\n// ---------------------------------------------------------------------------\n// Rendering\n// ---------------------------------------------------------------------------\n\nfunction renderOwner(task: Task): string {\n if (task.owner.project) {\n const via = task.owner.source === \"label\" ? \"label\" : \"container\";\n return chalk.cyan(task.owner.project) + dim(` (${via})`);\n }\n return task.owner.rawHint\n ? chalk.yellow(\"unrouted\") + dim(` (${task.owner.rawHint} matches no project)`)\n : chalk.yellow(\"unrouted\");\n}\n\nfunction printTasks(tasks: Task[]): void {\n if (tasks.length === 0) {\n console.log(dim(\" Nothing open.\"));\n return;\n }\n console.log();\n for (const t of tasks) {\n const due = t.due ? dim(` due ${t.due.slice(0, 10)}`) : \"\";\n const prio = t.priority !== \"p4\" ? dim(` ${t.priority}`) : \"\";\n console.log(` ${bold(t.title)}${prio}${due}`);\n console.log(` ${renderOwner(t)} ${dim(\"· \" + t.id)}`);\n }\n console.log();\n}\n\nfunction printResults(results: DispatchResult[]): void {\n const symbol: Record<string, string> = {\n delivered: chalk.green(\"→\"),\n spawned: chalk.green(\"+\"),\n unrouted: chalk.yellow(\"?\"),\n unlaunchable: chalk.red(\"!\"),\n unreachable: chalk.red(\"✗\"),\n skipped: dim(\"·\"),\n };\n\n console.log();\n for (const r of results) {\n console.log(` ${symbol[r.outcome]} ${r.task.title}`);\n const detail = [r.session, r.reason].filter(Boolean).join(\" — \");\n if (detail) console.log(` ${dim(detail)}`);\n }\n console.log();\n\n const counts = results.reduce<Record<string, number>>((acc, r) => {\n acc[r.outcome] = (acc[r.outcome] ?? 0) + 1;\n return acc;\n }, {});\n console.log(dim(\" \" + Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(\", \")));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Commands\n// ---------------------------------------------------------------------------\n\nexport function registerTaskCommands(taskCmd: Command): void {\n taskCmd\n .command(\"list\")\n .description(\"List open tasks on the bus\")\n .option(\"--owner <project>\", \"Only tasks owned by this PAI project\")\n .option(\"--due <date>\", \"Only tasks due on or before this ISO date\")\n .option(\"--today\", \"Shorthand for --due <today>\")\n .option(\"--routed\", \"Hide tasks with no resolved owner\")\n .option(\"--limit <n>\", \"Maximum tasks to show\", (v) => Number.parseInt(v, 10))\n .action(async (opts) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n\n const tasks = await provider.listOpen({\n owner: opts.owner,\n dueBefore: opts.today ? todayIso() : opts.due,\n includeUnrouted: !opts.routed,\n limit: opts.limit,\n });\n printTasks(tasks);\n });\n\n taskCmd\n .command(\"add <title>\")\n .description(\"File a task onto the bus\")\n .option(\"--owner <project>\", \"PAI project that owns this (adds a pai: label)\")\n .option(\"--body <text>\", \"Full procedure and reasoning — not just a restatement of the title\")\n .option(\"--due <date>\", \"Due date (ISO or natural language)\")\n .option(\"--priority <p>\", \"p1 (highest) … p4 (default)\")\n .option(\"--url <url>\", \"Reference — prefer a hook:// URL over a file path\")\n .action(async (title, opts) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n\n // A title alone is not actionable months later. Warn rather than block:\n // refusing the write would just push people to file nothing at all.\n if (!opts.body) {\n console.log(dim(\" No --body given. Add the procedure and reasoning so this\"));\n console.log(dim(\" is still actionable months from now, by you alone.\"));\n }\n\n const created = await provider.add({\n title,\n body: opts.body ?? \"\",\n owner: opts.owner ?? null,\n due: opts.due,\n priority: opts.priority,\n sourceUrl: opts.url,\n });\n\n console.log(chalk.green(` Filed: ${created.title}`));\n console.log(` ${renderOwner(created)} ${dim(\"· \" + created.id)}`);\n });\n\n taskCmd\n .command(\"dispatch\")\n .description(\"Hand open tasks to the PAI sessions that own them\")\n .option(\"--owner <project>\", \"Only dispatch tasks for this project\")\n .option(\"--today\", \"Only tasks due today or overdue\")\n .option(\"--dry-run\", \"Report what would happen without contacting any session\")\n .option(\"--no-spawn\", \"Never launch a session; skip owners that are not running\")\n .action(async (opts) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n\n const config = loadConfig();\n const tasks = await provider.listOpen({\n owner: opts.owner,\n dueBefore: opts.today ? todayIso() : undefined,\n includeUnrouted: true,\n });\n\n if (tasks.length === 0) {\n console.log(dim(\" Nothing to dispatch.\"));\n return;\n }\n\n // A dry run must never contact a session, so skip detection entirely\n // rather than detecting and then declining to use it.\n const transport = opts.dryRun\n ? null\n : await detectAiBroker(undefined, config.tasks?.dispatchTimeoutSecs);\n\n if (!opts.dryRun && !transport) {\n console.log(dim(\" No aibroker CLI with `dispatch` found — reporting ownership only.\"));\n }\n\n const results = await dispatchAll(tasks, {\n transport,\n autoDispatch: opts.dryRun ? false : (config.tasks?.autoDispatch ?? false),\n spawnIfAbsent: opts.spawn !== false,\n });\n\n printResults(results);\n });\n\n taskCmd\n .command(\"poll\")\n .description(\"One scheduler tick: dispatch what is due, check what is running, report\")\n .option(\"--dry-run\", \"Show what would happen without touching anything\")\n .action(async (opts) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n\n const config = loadConfig();\n const transport = opts.dryRun ? null : await detectAiBroker(undefined, config.tasks?.dispatchTimeoutSecs);\n const prober = opts.dryRun ? null : await detectProber();\n\n const report = await tick({\n provider: provider as TodoistProvider,\n transport,\n prober,\n autoDispatch: config.tasks?.autoDispatch ?? false,\n dryRun: Boolean(opts.dryRun),\n });\n\n if (report.decisions.length === 0) {\n console.log(dim(\" Nothing due, nothing running.\"));\n return;\n }\n\n const mark: Record<string, string> = {\n dispatch: chalk.green(\"→\"),\n complete: chalk.green(\"✓\"),\n running: dim(\"·\"),\n probe: chalk.yellow(\"?\"),\n orphaned: chalk.yellow(\"!\"),\n skip: dim(\"–\"),\n wait: dim(\" \"),\n };\n\n console.log();\n for (const { decision, note } of report.decisions) {\n console.log(` ${mark[decision.action] ?? \" \"} ${decision.task.title}`);\n if (note) console.log(` ${dim(note)}`);\n }\n console.log();\n console.log(\n dim(\n ` ${report.dispatched} dispatched, ${report.completed} completed, ` +\n `${report.probed} probed, ${report.stuck} stuck`\n )\n );\n console.log();\n });\n\n const scheduleCmd = taskCmd\n .command(\"schedule\")\n .description(\"Install, remove or inspect the launchd agent that ticks the scheduler\");\n\n scheduleCmd\n .command(\"install\")\n .description(\"Install the scheduler tick (default: every 15 minutes)\")\n .option(\"--interval <secs>\", \"Seconds between ticks\", (v) => Number.parseInt(v, 10))\n .action((opts) => {\n const r = installSchedule(opts.interval || DEFAULT_INTERVAL_SECS);\n console.log(r.loaded ? chalk.green(` ${r.message}`) : chalk.yellow(` ${r.message}`));\n console.log(dim(` ${r.plistPath}`));\n console.log(dim(` The schedule itself lives in Todoist — this only sets how often PAI looks.`));\n });\n\n scheduleCmd\n .command(\"uninstall\")\n .description(\"Remove the scheduler agent\")\n .action(() => console.log(chalk.green(` ${uninstallSchedule()}`)));\n\n scheduleCmd\n .command(\"status\")\n .description(\"Show whether the scheduler agent is installed and loaded\")\n .action(() => {\n const s = scheduleStatus();\n console.log();\n console.log(` ${s.installed ? chalk.green(\"installed\") : dim(\"not installed\")}` +\n ` ${s.installed ? (s.running ? chalk.green(\"· loaded\") : chalk.yellow(\"· not loaded\")) : \"\"}`);\n console.log(dim(` ${s.detail}`));\n console.log();\n });\n\n taskCmd\n .command(\"config\")\n .description(\"View or change task bus settings without running the full setup wizard\")\n .option(\"--token\", \"Prompt for the Todoist API token (input is hidden)\")\n .option(\"--from-env\", \"Adopt the token from TODOIST_API_KEY in the environment\")\n .option(\"--project <id>\", \"Tracker project ID that roots the bus (an ID, never a name)\")\n .option(\"--findings <id>\", \"Section ID for the findings inbox\")\n .option(\"--timeout <secs>\", \"Seconds a single dispatch may take\", (v) => Number.parseInt(v, 10))\n .option(\"--auto-dispatch <bool>\", \"Hand tasks to owning sessions automatically (true/false)\")\n .option(\"--disable\", \"Turn the task bus off without discarding its settings\")\n .action(async (opts) => {\n const raw = readConfigRaw();\n const tasks = (raw.tasks ?? {}) as RawTasks;\n tasks.providers ??= {};\n tasks.providers.todoist ??= {};\n const todoist = tasks.providers.todoist;\n\n const noChanges =\n !opts.token && !opts.fromEnv && !opts.project && !opts.findings &&\n opts.timeout === undefined && opts.autoDispatch === undefined && !opts.disable;\n\n if (noChanges) {\n console.log();\n console.log(` ${bold(\"Task bus\")} ${tasks.enabled ? chalk.green(\"enabled\") : dim(\"disabled\")}`);\n console.log(` ${bold(\"Token\")} ${redact(todoist.apiKey)}${\n !todoist.apiKey && process.env.TODOIST_API_KEY ? dim(\" (TODOIST_API_KEY is set in this shell)\") : \"\"\n }`);\n console.log(` ${bold(\"Root project\")} ${todoist.rootProjectId ?? dim(\"(not set)\")}`);\n console.log(` ${bold(\"Findings\")} ${todoist.findingsSectionId ?? dim(\"(not set)\")}`);\n console.log(` ${bold(\"Auto-dispatch\")} ${tasks.autoDispatch ? chalk.green(\"on\") : dim(\"off\")}`);\n console.log(` ${bold(\"Timeout\")} ${tasks.dispatchTimeoutSecs ?? dim(\"default (180s)\")}`);\n console.log();\n console.log(dim(` ${CONFIG_FILE}`));\n console.log();\n return;\n }\n\n if (opts.token && opts.fromEnv) {\n console.error(chalk.yellow(\" Use either --token or --from-env, not both.\"));\n process.exitCode = 1;\n return;\n }\n\n if (opts.fromEnv) {\n const envToken = process.env.TODOIST_API_KEY?.trim();\n if (!envToken) {\n console.error(chalk.yellow(\" TODOIST_API_KEY is not set in this environment.\"));\n process.exitCode = 1;\n return;\n }\n todoist.apiKey = envToken;\n todoist.enabled = true;\n tasks.enabled = true;\n }\n\n if (opts.token) {\n const entered = await promptSecret(\" Todoist API token (hidden): \");\n if (!entered) {\n console.error(chalk.yellow(\" No token entered. Nothing changed.\"));\n process.exitCode = 1;\n return;\n }\n todoist.apiKey = entered;\n todoist.enabled = true;\n tasks.enabled = true;\n }\n\n if (opts.project) { todoist.rootProjectId = opts.project; tasks.enabled = true; }\n if (opts.findings) todoist.findingsSectionId = opts.findings;\n if (opts.timeout !== undefined) tasks.dispatchTimeoutSecs = opts.timeout;\n if (opts.autoDispatch !== undefined) tasks.autoDispatch = opts.autoDispatch === \"true\";\n if (opts.disable) tasks.enabled = false;\n\n raw.tasks = tasks;\n writeConfigRaw(raw);\n\n // The file now holds a credential. Narrow it before saying anything else,\n // so a crash between write and chmod cannot leave it world-readable.\n try {\n chmodSync(CONFIG_FILE, 0o600);\n } catch {\n console.log(chalk.yellow(` Could not restrict permissions on ${CONFIG_FILE} — check them by hand.`));\n }\n\n console.log(chalk.green(\" Saved.\"));\n if (todoist.apiKey && !todoist.rootProjectId) {\n console.log(dim(\" No root project set yet — run `pai task config --project <id>`.\"));\n }\n });\n\n taskCmd\n .command(\"done <id>\")\n .description(\"Mark a task complete on the tracker\")\n .action(async (id) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n await provider.complete(id);\n console.log(chalk.green(` Completed ${id}.`));\n });\n}\n","/**\n * pai topic <sub-command>\n *\n * check <context> — Check whether context text has drifted to a different project\n *\n * Communicates with the running daemon via IPC.\n * The daemon uses BM25 keyword search to match context against indexed memory.\n *\n * Examples:\n * pai topic check \"working on authentication and JWT tokens\"\n * pai topic check \"fixing the React component\" --current myapp\n * pai topic check \"database schema migration\" --threshold 0.7\n */\n\nimport type { Command } from \"commander\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { PaiClient } from \"../../daemon/ipc-client.js\";\nimport type { TopicCheckResult } from \"../../topics/detector.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction makeClient(): PaiClient {\n const config = loadConfig();\n return new PaiClient(config.socketPath);\n}\n\nfunction confidenceBar(confidence: number, width = 20): string {\n const filled = Math.round(confidence * width);\n const empty = width - filled;\n return \"[\" + \"=\".repeat(filled) + \" \".repeat(empty) + \"]\";\n}\n\nfunction printTopicResult(result: TopicCheckResult): void {\n console.log();\n console.log(header(\" PAI Topic Check\"));\n console.log();\n\n console.log(` ${bold(\"Current project:\")} ${result.currentProject ?? dim(\"(none)\")}`);\n console.log(` ${bold(\"Suggested project:\")} ${result.suggestedProject ?? dim(\"(none)\")}`);\n console.log(\n ` ${bold(\"Confidence:\")} ${confidenceBar(result.confidence)} ${(result.confidence * 100).toFixed(1)}%`\n );\n console.log(` ${bold(\"Chunks scored:\")} ${result.chunkCount}`);\n\n if (result.topProjects.length > 0) {\n console.log();\n console.log(` ${bold(\"Top matches:\")}`);\n for (const p of result.topProjects) {\n const bar = confidenceBar(p.score, 15);\n const marker = p.slug === result.currentProject ? dim(\" (current)\") : \"\";\n console.log(` ${p.slug.padEnd(30)} ${bar} ${(p.score * 100).toFixed(1)}%${marker}`);\n }\n }\n\n console.log();\n\n if (result.shifted) {\n console.log(\n warn(\" TOPIC SHIFT DETECTED\") +\n dim(` — conversation appears to be about \"${result.suggestedProject}\", not \"${result.currentProject}\"`)\n );\n console.log();\n } else if (result.suggestedProject && result.suggestedProject === result.currentProject) {\n console.log(ok(\" No shift detected\") + dim(\" — context matches current project\"));\n console.log();\n } else if (!result.currentProject) {\n if (result.suggestedProject) {\n console.log(\n ok(\" Best matching project: \") + bold(result.suggestedProject) +\n dim(` (confidence: ${(result.confidence * 100).toFixed(0)}%)`)\n );\n } else {\n console.log(dim(\" No matching project found in memory index.\"));\n }\n console.log();\n } else {\n console.log(\n dim(` No shift detected (top match \"${result.suggestedProject}\" has confidence ${(result.confidence * 100).toFixed(0)}% — below threshold)`)\n );\n console.log();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nasync function cmdCheck(\n context: string,\n opts: {\n current?: string;\n threshold?: string;\n json?: boolean;\n }\n): Promise<void> {\n const client = makeClient();\n\n const threshold = opts.threshold ? parseFloat(opts.threshold) : undefined;\n\n if (threshold !== undefined && (isNaN(threshold) || threshold < 0 || threshold > 1)) {\n console.error(err(\" --threshold must be a number between 0 and 1\"));\n process.exit(1);\n }\n\n try {\n const result = await client.topicCheck({\n context,\n currentProject: opts.current,\n threshold,\n });\n\n if (opts.json) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n printTopicResult(result);\n\n // Exit with code 1 if a shift was detected (useful for scripting / hooks)\n if (result.shifted) {\n process.exit(1);\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.log();\n console.log(warn(\" Cannot reach PAI daemon.\"));\n console.log(dim(` ${msg}`));\n console.log(dim(\" Start it with: pai daemon serve\"));\n console.log();\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerTopicCommands(topicCmd: Command): void {\n // pai topic check <context>\n topicCmd\n .command(\"check <context>\")\n .description(\n \"Check whether context text has drifted to a different project.\\n\" +\n 'Example: pai topic check \"working on JWT authentication\"\\n' +\n \"Exit code 1 if a shift is detected (useful for hooks).\"\n )\n .option(\n \"--current <slug>\",\n \"The project this session is currently routed to\"\n )\n .option(\n \"--threshold <n>\",\n \"Confidence threshold [0-1] to declare a shift (default: 0.6)\"\n )\n .option(\"--json\", \"Output raw JSON result\")\n .action(\n async (\n context: string,\n opts: { current?: string; threshold?: string; json?: boolean }\n ) => {\n await cmdCheck(context, opts);\n }\n );\n}\n","/**\n * kg-backfill.ts — Populate the temporal knowledge graph from existing\n * session notes.\n *\n * Walks `Notes/YYYY/MM/*.md` for each registered project, runs the same\n * extractor that the session-summary-worker uses on every NEW summary, and\n * stores triples in Postgres. Idempotent: a state file at\n * ~/.config/pai/kg-backfill-state.json records which note paths have been\n * processed so re-runs skip them.\n *\n * Even without the state file the operation is safe — extractAndStoreTriples\n * uses supersession logic so re-extracting a note never produces duplicates.\n */\n\nimport { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { Pool } from \"pg\";\n\nimport { openRegistry } from \"../registry/db.js\";\nimport { loadConfig } from \"../daemon/config.js\";\nimport { createStorageBackend } from \"../storage/factory.js\";\nimport { extractAndStoreTriples } from \"./kg-extraction.js\";\nimport { openFederation } from \"./db.js\";\n\n// ---------------------------------------------------------------------------\n// State file\n// ---------------------------------------------------------------------------\n\nconst STATE_FILE = join(homedir(), \".config\", \"pai\", \"kg-backfill-state.json\");\n\ninterface BackfillState {\n /** Map of absolute note path -> ISO timestamp of when it was processed. */\n processed: Record<string, string>;\n}\n\nfunction loadState(): BackfillState {\n try {\n if (existsSync(STATE_FILE)) {\n const raw = JSON.parse(readFileSync(STATE_FILE, \"utf-8\"));\n if (raw && typeof raw === \"object\" && raw.processed) return raw as BackfillState;\n }\n } catch { /* ignore */ }\n return { processed: {} };\n}\n\nfunction saveState(state: BackfillState): void {\n try {\n const dir = join(homedir(), \".config\", \"pai\");\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), \"utf-8\");\n } catch (e) {\n process.stderr.write(`[kg-backfill] failed to save state: ${e}\\n`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Note discovery\n// ---------------------------------------------------------------------------\n\ninterface ProjectRow {\n id: number;\n slug: string;\n root_path: string;\n}\n\ninterface ProjectNote {\n project: ProjectRow;\n notePath: string;\n}\n\n/**\n * Find all session notes under <project_root>/Notes/YYYY/MM/*.md.\n * Falls back to <project_root>/Notes/*.md (flat layout) when no year/month\n * subdirectories are present.\n */\nfunction findProjectNotes(project: ProjectRow): string[] {\n const notesRoot = join(project.root_path, \"Notes\");\n if (!existsSync(notesRoot)) return [];\n\n const found: Array<{ path: string; mtime: number }> = [];\n\n function walk(dir: string, depth: number): void {\n if (depth > 4) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n const full = join(dir, name);\n let st;\n try {\n st = statSync(full);\n } catch {\n continue;\n }\n if (st.isDirectory()) {\n walk(full, depth + 1);\n } else if (st.isFile() && name.endsWith(\".md\") && /^\\d{3,4}/.test(name)) {\n found.push({ path: full, mtime: st.mtimeMs });\n }\n }\n }\n\n walk(notesRoot, 0);\n // Process oldest first so newer extractions can correctly supersede older facts\n found.sort((a, b) => a.mtime - b.mtime);\n return found.map((f) => f.path);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport interface BackfillOptions {\n /** If set, only process this project's notes. */\n projectSlug?: string;\n /** Cap the total number of notes processed across all projects. */\n limit?: number;\n /** If true, do not call the extractor and do not write any state. */\n dryRun?: boolean;\n /** Progress callback fired before each note is processed. */\n onProgress?: (current: number, total: number, note: string) => void;\n}\n\nexport interface BackfillResult {\n notes_processed: number;\n triples_extracted: number;\n triples_added: number;\n triples_superseded: number;\n errors: number;\n}\n\n/**\n * Backfill the knowledge graph from existing session notes.\n *\n * Requires the Postgres backend (KG tables live there). Throws if Postgres\n * is unavailable, since this is an explicit user action.\n */\nexport async function backfillKgFromNotes(\n options: BackfillOptions = {}\n): Promise<BackfillResult> {\n const result: BackfillResult = {\n notes_processed: 0,\n triples_extracted: 0,\n triples_added: 0,\n triples_superseded: 0,\n errors: 0,\n };\n\n const config = loadConfig();\n if (config.storageBackend !== \"postgres\") {\n throw new Error(\n \"kg backfill requires the Postgres backend. \" +\n 'Set \"storageBackend\": \"postgres\" in ~/.config/pai/config.json.'\n );\n }\n\n const backend = await createStorageBackend(config);\n if (backend.backendType !== \"postgres\") {\n throw new Error(\n \"Postgres backend unavailable — fell back to SQLite. Cannot backfill KG.\"\n );\n }\n\n const pool: Pool = (backend as unknown as { getPool(): Pool }).getPool();\n\n // Open federation SQLite for entity upserts (kg_entities table)\n const federationDb = openFederation();\n\n // -- Load projects -------------------------------------------------------\n const registry = openRegistry();\n let projects: ProjectRow[];\n try {\n if (options.projectSlug) {\n const row = registry\n .prepare(\"SELECT id, slug, root_path FROM projects WHERE slug = ?\")\n .get(options.projectSlug) as ProjectRow | undefined;\n if (!row) {\n throw new Error(`Project not found: ${options.projectSlug}`);\n }\n projects = [row];\n } else {\n projects = registry\n .prepare(\n \"SELECT id, slug, root_path FROM projects WHERE status = 'active' ORDER BY slug\"\n )\n .all() as ProjectRow[];\n }\n } finally {\n registry.close();\n }\n\n // -- Build the work list -------------------------------------------------\n const state = loadState();\n const work: ProjectNote[] = [];\n for (const project of projects) {\n const notes = findProjectNotes(project);\n for (const notePath of notes) {\n if (state.processed[notePath]) continue;\n work.push({ project, notePath });\n }\n }\n\n const total = options.limit ? Math.min(options.limit, work.length) : work.length;\n\n // -- Process notes -------------------------------------------------------\n for (let i = 0; i < total; i++) {\n const { project, notePath } = work[i];\n options.onProgress?.(i + 1, total, notePath);\n\n if (options.dryRun) {\n result.notes_processed++;\n continue;\n }\n\n let noteContent: string;\n try {\n noteContent = readFileSync(notePath, \"utf-8\");\n } catch (e) {\n process.stderr.write(`[kg-backfill] read failed ${notePath}: ${e}\\n`);\n result.errors++;\n continue;\n }\n\n try {\n const stats = await extractAndStoreTriples(pool, {\n summaryText: noteContent,\n projectSlug: project.slug,\n projectId: project.id,\n sessionId: `backfill:${notePath}`,\n gitLog: \"\",\n model: \"sonnet\",\n federationDb,\n });\n\n result.notes_processed++;\n result.triples_extracted += stats.extracted;\n result.triples_added += stats.added;\n result.triples_superseded += stats.superseded;\n\n // Mark as processed (state file is the idempotency hint; the supersession\n // logic in the extractor is the actual safety net)\n state.processed[notePath] = new Date().toISOString();\n // Save incrementally so a crash mid-run doesn't lose progress\n if ((i + 1) % 5 === 0) saveState(state);\n } catch (e) {\n process.stderr.write(`[kg-backfill] extract failed ${notePath}: ${e}\\n`);\n result.errors++;\n }\n }\n\n if (!options.dryRun) saveState(state);\n federationDb.close();\n await backend.close();\n\n return result;\n}\n","/**\n * pai kg <sub-command>\n *\n * Temporal knowledge graph management:\n * pai kg backfill [--project SLUG] [--limit N] [--dry-run]\n * pai kg query [--subject S] [--predicate P] [--object O] [--as-of DATE] [--project SLUG]\n * pai kg list [--project SLUG] [--limit N]\n * pai kg stats\n *\n * All commands require the Postgres backend (KG tables live in Postgres).\n */\n\nimport type { Command } from \"commander\";\nimport type { Pool } from \"pg\";\n\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { createStorageBackend } from \"../../storage/factory.js\";\nimport { kgQuery } from \"../../memory/kg.js\";\nimport { backfillKgFromNotes } from \"../../memory/kg-backfill.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function getPool(): Promise<{ pool: Pool; close: () => Promise<void> }> {\n const config = loadConfig();\n if (config.storageBackend !== \"postgres\") {\n console.error(err(\" KG commands require Postgres backend.\"));\n console.error(dim(' Set \"storageBackend\": \"postgres\" in ~/.config/pai/config.json'));\n process.exit(1);\n }\n const backend = await createStorageBackend(config);\n if (backend.backendType !== \"postgres\") {\n console.error(err(\" Postgres backend unavailable — fell back to SQLite.\"));\n process.exit(1);\n }\n const pool = (backend as unknown as { getPool(): Pool }).getPool();\n return { pool, close: () => backend.close() };\n}\n\nfunction shorten(s: string, n: number): string {\n return s.length <= n ? s : s.slice(0, n - 1) + \"…\";\n}\n\n// ---------------------------------------------------------------------------\n// pai kg backfill\n// ---------------------------------------------------------------------------\n\nasync function cmdBackfill(opts: {\n project?: string;\n limit?: string;\n dryRun?: boolean;\n}): Promise<void> {\n const limit = opts.limit ? parseInt(opts.limit, 10) : undefined;\n if (limit !== undefined && (isNaN(limit) || limit < 1)) {\n console.error(err(\" --limit must be a positive integer\"));\n process.exit(1);\n }\n\n console.log();\n console.log(header(\" PAI KG Backfill\"));\n console.log();\n if (opts.project) console.log(` ${bold(\"Project:\")} ${opts.project}`);\n if (limit) console.log(` ${bold(\"Limit:\")} ${limit}`);\n if (opts.dryRun) console.log(` ${bold(\"Mode:\")} ${warn(\"dry-run\")}`);\n console.log();\n\n try {\n const result = await backfillKgFromNotes({\n projectSlug: opts.project,\n limit,\n dryRun: opts.dryRun,\n onProgress: (current, total, note) => {\n const short = shorten(note.replace(process.env.HOME ?? \"\", \"~\"), 70);\n process.stdout.write(` [${current}/${total}] ${dim(short)}\\n`);\n },\n });\n\n console.log();\n console.log(ok(\" Backfill complete\"));\n console.log(` Notes processed: ${bold(String(result.notes_processed))}`);\n console.log(` Triples extracted: ${bold(String(result.triples_extracted))}`);\n console.log(` Triples added: ${bold(String(result.triples_added))}`);\n console.log(` Triples superseded: ${bold(String(result.triples_superseded))}`);\n if (result.errors > 0) {\n console.log(` ${warn(\"Errors:\")} ${result.errors}`);\n }\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai kg query\n// ---------------------------------------------------------------------------\n\nasync function cmdQuery(opts: {\n subject?: string;\n predicate?: string;\n object?: string;\n asOf?: string;\n project?: string;\n json?: boolean;\n}): Promise<void> {\n const { pool, close } = await getPool();\n try {\n let projectId: number | undefined;\n if (opts.project) {\n const r = await pool.query<{ id: number }>(\n \"SELECT id FROM projects WHERE slug = $1 LIMIT 1\",\n [opts.project]\n );\n if (r.rows.length === 0) {\n console.error(warn(` Project not found in Postgres: ${opts.project}`));\n } else {\n projectId = r.rows[0].id;\n }\n }\n\n const asOf = opts.asOf ? new Date(opts.asOf) : undefined;\n if (asOf && isNaN(asOf.getTime())) {\n console.error(err(` Invalid --as-of date: ${opts.asOf}`));\n process.exit(1);\n }\n\n const triples = await kgQuery(pool, {\n subject: opts.subject,\n predicate: opts.predicate,\n object: opts.object,\n project_id: projectId,\n as_of: asOf,\n });\n\n if (opts.json) {\n console.log(JSON.stringify(triples, null, 2));\n return;\n }\n\n console.log();\n console.log(header(` ${triples.length} triple(s)`));\n console.log();\n for (const t of triples) {\n const validity = t.valid_to\n ? dim(`(invalidated ${t.valid_to.toISOString().slice(0, 10)})`)\n : dim(`(valid since ${t.valid_from.toISOString().slice(0, 10)})`);\n console.log(\n ` ${bold(t.subject)} ${dim(\"·\")} ${t.predicate} ${dim(\"·\")} ${t.object} ${validity}`\n );\n }\n console.log();\n } finally {\n await close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai kg list\n// ---------------------------------------------------------------------------\n\nasync function cmdList(opts: { project?: string; limit?: string }): Promise<void> {\n const limit = opts.limit ? parseInt(opts.limit, 10) : 50;\n const { pool, close } = await getPool();\n try {\n let projectId: number | undefined;\n if (opts.project) {\n const r = await pool.query<{ id: number }>(\n \"SELECT id FROM projects WHERE slug = $1 LIMIT 1\",\n [opts.project]\n );\n if (r.rows.length > 0) projectId = r.rows[0].id;\n }\n\n const triples = await kgQuery(pool, { project_id: projectId });\n const slice = triples.slice(0, limit);\n\n console.log();\n console.log(\n header(` ${slice.length} of ${triples.length} currently-valid triple(s)`)\n );\n console.log();\n for (const t of slice) {\n console.log(\n ` ${bold(t.subject)} ${dim(\"·\")} ${t.predicate} ${dim(\"·\")} ${t.object}`\n );\n }\n if (triples.length > slice.length) {\n console.log();\n console.log(dim(` (${triples.length - slice.length} more — increase --limit)`));\n }\n console.log();\n } finally {\n await close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai kg stats\n// ---------------------------------------------------------------------------\n\nasync function cmdStats(): Promise<void> {\n const { pool, close } = await getPool();\n try {\n const totals = await pool.query<{\n total: string;\n valid: string;\n invalidated: string;\n subjects: string;\n predicates: string;\n }>(\n `SELECT\n COUNT(*)::text AS total,\n COUNT(*) FILTER (WHERE valid_to IS NULL)::text AS valid,\n COUNT(*) FILTER (WHERE valid_to IS NOT NULL)::text AS invalidated,\n COUNT(DISTINCT subject)::text AS subjects,\n COUNT(DISTINCT predicate)::text AS predicates\n FROM kg_triples`\n );\n\n const contradictions = await pool.query<{ count: string }>(\n `SELECT COUNT(*)::text AS count FROM (\n SELECT subject, predicate\n FROM kg_triples\n WHERE valid_to IS NULL\n GROUP BY subject, predicate\n HAVING COUNT(*) > 1\n ) c`\n );\n\n const row = totals.rows[0] ?? {};\n console.log();\n console.log(header(\" PAI KG Stats\"));\n console.log();\n console.log(` ${bold(\"Total triples:\")} ${row.total ?? \"0\"}`);\n console.log(` ${bold(\"Currently valid:\")} ${row.valid ?? \"0\"}`);\n console.log(` ${bold(\"Invalidated:\")} ${row.invalidated ?? \"0\"}`);\n console.log(` ${bold(\"Distinct subjects:\")} ${row.subjects ?? \"0\"}`);\n console.log(` ${bold(\"Distinct predicates:\")} ${row.predicates ?? \"0\"}`);\n console.log(` ${bold(\"Contradictions:\")} ${contradictions.rows[0]?.count ?? \"0\"}`);\n console.log();\n } finally {\n await close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerKgCommands(kgCmd: Command): void {\n kgCmd\n .command(\"backfill\")\n .description(\"Populate the KG from existing session notes (idempotent)\")\n .option(\"--project <slug>\", \"Restrict backfill to a single project\")\n .option(\"--limit <n>\", \"Maximum number of notes to process\")\n .option(\"--dry-run\", \"List notes that would be processed without extracting\")\n .action(async (opts: { project?: string; limit?: string; dryRun?: boolean }) => {\n await cmdBackfill(opts);\n });\n\n kgCmd\n .command(\"query\")\n .description(\"Query KG triples by subject, predicate, object, time, or project\")\n .option(\"--subject <s>\", \"Filter by subject\")\n .option(\"--predicate <p>\", \"Filter by predicate\")\n .option(\"--object <o>\", \"Filter by object\")\n .option(\"--as-of <date>\", \"Point-in-time query (YYYY-MM-DD or ISO 8601)\")\n .option(\"--project <slug>\", \"Restrict to a project slug\")\n .option(\"--json\", \"Output raw JSON\")\n .action(\n async (opts: {\n subject?: string;\n predicate?: string;\n object?: string;\n asOf?: string;\n project?: string;\n json?: boolean;\n }) => {\n await cmdQuery(opts);\n }\n );\n\n kgCmd\n .command(\"list\")\n .description(\"List currently-valid triples\")\n .option(\"--project <slug>\", \"Restrict to a project slug\")\n .option(\"--limit <n>\", \"Maximum triples to print\", \"50\")\n .action(async (opts: { project?: string; limit?: string }) => {\n await cmdList(opts);\n });\n\n kgCmd\n .command(\"stats\")\n .description(\"Show triple counts and contradiction count\")\n .action(async () => {\n await cmdStats();\n });\n}\n","/**\n * pai db <sub-command>\n *\n * Quick database inspection commands for both SQLite and Postgres backends.\n *\n * pai db query <sql> [--db sqlite|postgres] [--json]\n * pai db tables [--db sqlite|postgres]\n * pai db schema <table> [--db sqlite|postgres]\n */\n\nimport type { Command } from \"commander\";\nimport { ok, warn, err, dim, bold, header, renderTable } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { openFederation } from \"../../memory/db.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype DbTarget = \"sqlite\" | \"postgres\";\n\ninterface CommonOpts {\n db?: DbTarget;\n json?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// SQLite helpers (synchronous — better-sqlite3)\n// ---------------------------------------------------------------------------\n\nfunction getSqliteDb() {\n return openFederation();\n}\n\nfunction sqliteQuery(sql: string): { columns: string[]; rows: unknown[][] } {\n const db = getSqliteDb();\n try {\n const stmt = db.prepare(sql);\n const raw = stmt.all() as Record<string, unknown>[];\n if (raw.length === 0) return { columns: [], rows: [] };\n const columns = Object.keys(raw[0]);\n const rows = raw.map((r) => columns.map((c) => r[c]));\n return { columns, rows };\n } finally {\n db.close();\n }\n}\n\nfunction sqliteTables(): string[] {\n const db = getSqliteDb();\n try {\n const rows = db\n .prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name\"\n )\n .all() as { name: string }[];\n return rows.map((r) => r.name);\n } finally {\n db.close();\n }\n}\n\nfunction sqliteSchema(table: string): { columns: string[]; rows: unknown[][] } {\n const db = getSqliteDb();\n try {\n // Validate table name — only allow alphanumeric / underscores\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {\n throw new Error(`Invalid table name: ${table}`);\n }\n const rows = db.prepare(`PRAGMA table_info(${table})`).all() as {\n cid: number;\n name: string;\n type: string;\n notnull: number;\n dflt_value: string | null;\n pk: number;\n }[];\n if (rows.length === 0) {\n throw new Error(`Table not found: ${table}`);\n }\n const columns = [\"cid\", \"name\", \"type\", \"notnull\", \"default\", \"pk\"];\n const data = rows.map((r) => [\n r.cid,\n r.name,\n r.type,\n r.notnull ? \"NOT NULL\" : \"\",\n r.dflt_value ?? \"\",\n r.pk ? \"PK\" : \"\",\n ]);\n return { columns, data: data } as unknown as { columns: string[]; rows: unknown[][] };\n } finally {\n db.close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Postgres helpers (async — pg Pool)\n// ---------------------------------------------------------------------------\n\nasync function getPool() {\n const config = loadConfig();\n const pgConfig = config.postgres ?? {};\n\n const { Pool } = await import(\"pg\");\n\n // Build connection params from config\n const poolConfig = pgConfig.connectionString\n ? { connectionString: pgConfig.connectionString }\n : {\n host: pgConfig.host ?? \"localhost\",\n port: pgConfig.port ?? 5432,\n database: pgConfig.database ?? \"pai\",\n user: pgConfig.user ?? \"pai\",\n password: pgConfig.password ?? \"pai\",\n connectionTimeoutMillis: pgConfig.connectionTimeoutMs ?? 5000,\n };\n\n const pool = new Pool(poolConfig);\n\n // Quick connectivity test\n try {\n const client = await pool.connect();\n client.release();\n } catch (e) {\n await pool.end();\n const msg = e instanceof Error ? e.message : String(e);\n throw new Error(`Cannot connect to Postgres: ${msg}`);\n }\n\n return pool;\n}\n\nasync function postgresQuery(\n sql: string\n): Promise<{ columns: string[]; rows: unknown[][] }> {\n const pool = await getPool();\n try {\n const result = await pool.query(sql);\n const columns = result.fields.map((f) => f.name);\n const rows = result.rows.map((r: Record<string, unknown>) =>\n columns.map((c) => r[c])\n );\n return { columns, rows };\n } finally {\n await pool.end();\n }\n}\n\nasync function postgresTables(): Promise<string[]> {\n const pool = await getPool();\n try {\n const result = await pool.query<{ tablename: string }>(\n \"SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename\"\n );\n return result.rows.map((r) => r.tablename);\n } finally {\n await pool.end();\n }\n}\n\nasync function postgresSchema(\n table: string\n): Promise<{ columns: string[]; rows: unknown[][] }> {\n const pool = await getPool();\n try {\n const result = await pool.query<{\n column_name: string;\n data_type: string;\n is_nullable: string;\n column_default: string | null;\n }>(\n `SELECT column_name, data_type, is_nullable, column_default\n FROM information_schema.columns\n WHERE table_schema = 'public' AND table_name = $1\n ORDER BY ordinal_position`,\n [table]\n );\n if (result.rows.length === 0) {\n throw new Error(`Table not found in public schema: ${table}`);\n }\n const columns = [\"column\", \"type\", \"nullable\", \"default\"];\n const rows = result.rows.map((r) => [\n r.column_name,\n r.data_type,\n r.is_nullable === \"YES\" ? \"YES\" : \"NO\",\n r.column_default ?? \"\",\n ]);\n return { columns, rows };\n } finally {\n await pool.end();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Output helpers\n// ---------------------------------------------------------------------------\n\nfunction printResult(\n result: { columns: string[]; rows: unknown[][] },\n json: boolean\n): void {\n if (json) {\n const objects = result.rows.map((row) => {\n const obj: Record<string, unknown> = {};\n result.columns.forEach((col, i) => {\n obj[col] = row[i];\n });\n return obj;\n });\n console.log(JSON.stringify(objects, null, 2));\n return;\n }\n\n if (result.rows.length === 0) {\n console.log();\n console.log(dim(\" (no rows)\"));\n console.log();\n return;\n }\n\n const stringRows = result.rows.map((row) =>\n row.map((cell) => {\n if (cell === null || cell === undefined) return dim(\"NULL\");\n if (typeof cell === \"object\") return JSON.stringify(cell);\n return String(cell);\n })\n );\n\n console.log();\n console.log(renderTable(result.columns, stringRows));\n console.log();\n console.log(dim(` ${result.rows.length} row(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// pai db query\n// ---------------------------------------------------------------------------\n\nasync function cmdQuery(\n sql: string,\n opts: CommonOpts\n): Promise<void> {\n const target: DbTarget = opts.db ?? \"sqlite\";\n console.log();\n console.log(\n header(` Query [${target}]`) + dim(` ${sql.slice(0, 80)}${sql.length > 80 ? \"…\" : \"\"}`)\n );\n\n try {\n let result: { columns: string[]; rows: unknown[][] };\n if (target === \"postgres\") {\n result = await postgresQuery(sql);\n } else {\n result = sqliteQuery(sql);\n }\n printResult(result, opts.json ?? false);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai db tables\n// ---------------------------------------------------------------------------\n\nasync function cmdTables(opts: CommonOpts): Promise<void> {\n const target: DbTarget = opts.db ?? \"sqlite\";\n console.log();\n console.log(header(` Tables [${target}]`));\n console.log();\n\n try {\n let tables: string[];\n if (target === \"postgres\") {\n tables = await postgresTables();\n } else {\n tables = sqliteTables();\n }\n\n if (opts.json) {\n console.log(JSON.stringify(tables, null, 2));\n return;\n }\n\n if (tables.length === 0) {\n console.log(dim(\" (no tables found)\"));\n } else {\n for (const t of tables) {\n console.log(` ${bold(t)}`);\n }\n }\n console.log();\n console.log(dim(` ${tables.length} table(s)`));\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai db schema\n// ---------------------------------------------------------------------------\n\nasync function cmdSchema(table: string, opts: CommonOpts): Promise<void> {\n const target: DbTarget = opts.db ?? \"sqlite\";\n console.log();\n console.log(header(` Schema: ${table} [${target}]`));\n\n try {\n let result: { columns: string[]; rows: unknown[][] };\n if (target === \"postgres\") {\n result = await postgresSchema(table);\n } else {\n const raw = sqliteSchema(table) as unknown as {\n columns: string[];\n data: unknown[][];\n };\n result = { columns: raw.columns, rows: raw.data };\n }\n printResult(result, opts.json ?? false);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerDbCommands(dbCmd: Command): void {\n dbCmd\n .command(\"query <sql>\")\n .description(\"Run a SQL query against the selected database\")\n .option(\"--db <target>\", \"Database target: sqlite (default) or postgres\", \"sqlite\")\n .option(\"--json\", \"Output results as JSON array\")\n .action(async (sql: string, opts: CommonOpts) => {\n await cmdQuery(sql, opts);\n });\n\n dbCmd\n .command(\"tables\")\n .description(\"List all tables in the selected database\")\n .option(\"--db <target>\", \"Database target: sqlite (default) or postgres\", \"sqlite\")\n .option(\"--json\", \"Output as JSON array\")\n .action(async (opts: CommonOpts) => {\n await cmdTables(opts);\n });\n\n dbCmd\n .command(\"schema <table>\")\n .description(\"Show column schema for a table\")\n .option(\"--db <target>\", \"Database target: sqlite (default) or postgres\", \"sqlite\")\n .option(\"--json\", \"Output as JSON array\")\n .action(async (table: string, opts: CommonOpts) => {\n await cmdSchema(table, opts);\n });\n}\n","/**\n * pai help [area]\n *\n * Rich man-page viewer for the generated command docs under docs/commands/.\n * These pages are generated from the live Commander tree by\n * scripts/build-docs.mjs, so they never drift from the actual CLI.\n *\n * pai help → list all command areas (the index / apropos)\n * pai help <area> → print the man page for that area\n *\n * Falls back to `pai <area> --help` if a page is missing.\n */\n\nimport type { Command } from \"commander\";\nimport { readFileSync, existsSync, readdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport { err, dim } from \"../utils.js\";\n\n/**\n * Locate the docs/commands directory robustly, regardless of how the bundle is\n * chunked. Walks up from this module's location (and from argv[1]) looking for a\n * directory that contains docs/commands.\n */\nfunction findDocsDir(): string | null {\n const candidates: string[] = [];\n try {\n candidates.push(dirname(fileURLToPath(import.meta.url)));\n } catch {\n /* ignore */\n }\n if (process.argv[1]) candidates.push(dirname(process.argv[1]));\n\n for (const start of candidates) {\n let dir = start;\n for (let i = 0; i < 6; i++) {\n const probe = join(dir, \"docs\", \"commands\");\n if (existsSync(probe)) return probe;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n }\n return null;\n}\n\n/**\n * Light terminal styling for the generated markdown — enough to read like a man\n * page without a full markdown renderer. Headings bold, table rules + fences\n * dimmed, blockquotes dimmed.\n */\nfunction inlineBold(s: string): string {\n return s\n .replace(/\\\\\\|/g, \"|\")\n .replace(/\\*\\*([^*]+)\\*\\*/g, (_m, t) => chalk.bold(t));\n}\n\nfunction renderMarkdown(md: string): string {\n const out: string[] = [];\n let inFence = false;\n for (const line of md.split(\"\\n\")) {\n // Drop the generator's HTML comment header.\n if (/^\\s*<!--.*-->\\s*$/.test(line)) continue;\n // Code fences: suppress the ``` markers, colourise the body.\n if (/^```/.test(line)) {\n inFence = !inFence;\n continue;\n }\n if (inFence) {\n out.push(chalk.cyan(line));\n continue;\n }\n if (/^#{1,6}\\s/.test(line)) {\n out.push(chalk.bold(line.replace(/^#{1,6}\\s/, \"\")));\n continue;\n }\n if (/^>\\s?/.test(line)) {\n out.push(dim(line.replace(/^>\\s?/, \"\")));\n continue;\n }\n // Markdown table rule rows.\n if (/^\\|?\\s*[-:]+\\s*(\\|\\s*[-:]+\\s*)+\\|?$/.test(line)) {\n out.push(dim(line));\n continue;\n }\n out.push(inlineBold(line));\n }\n // Collapse leading blank lines.\n while (out.length > 0 && out[0].trim() === \"\") out.shift();\n return out.join(\"\\n\");\n}\n\nfunction listAreas(docsDir: string): string[] {\n return readdirSync(docsDir)\n .filter((f) => f.endsWith(\".md\") && f !== \"README.md\")\n .map((f) => f.replace(/\\.md$/, \"\"))\n .sort();\n}\n\nexport function registerHelpCommand(program: Command): void {\n program\n .command(\"help [area]\")\n .description(\"Show the man page for a command area (pai help <area>), or list all areas\")\n .option(\"--raw\", \"Print raw markdown without terminal styling\")\n .action((area: string | undefined, opts: { raw?: boolean }) => {\n const docsDir = findDocsDir();\n\n if (!docsDir) {\n console.error(err(\" Command docs not found.\"));\n console.error(dim(\" Run `bun run build` to generate them, or use `pai <area> --help`.\"));\n process.exitCode = 1;\n return;\n }\n\n // No area → print the index.\n if (!area) {\n const indexPath = join(docsDir, \"README.md\");\n if (existsSync(indexPath)) {\n const md = readFileSync(indexPath, \"utf8\");\n console.log(opts.raw ? md : renderMarkdown(md));\n } else {\n console.log(chalk.bold(\"\\nPAI command areas\\n\"));\n for (const a of listAreas(docsDir)) console.log(` ${a}`);\n console.log(dim(\"\\n pai help <area> show the man page for an area\"));\n }\n return;\n }\n\n // Area → print its page, or fall back.\n const page = join(docsDir, `${area}.md`);\n if (existsSync(page)) {\n const md = readFileSync(page, \"utf8\");\n console.log(opts.raw ? md : renderMarkdown(md));\n return;\n }\n\n console.error(err(` No man page for \"${area}\".`));\n const areas = listAreas(docsDir);\n const near = areas.filter((a) => a.includes(area) || area.includes(a));\n if (near.length > 0) {\n console.error(dim(` Did you mean: ${near.join(\", \")}?`));\n } else {\n console.error(dim(` Available: ${areas.join(\", \")}`));\n }\n console.error(dim(` Or try: pai ${area} --help`));\n process.exitCode = 1;\n });\n}\n","/**\n * pai session pause [--dry-run]\n *\n * Writes a \"## Continue\" checkpoint to the project's TODO.md, then prints a\n * prominent reminder about HOW to exit Claude Code safely.\n *\n * The write logic mirrors cmdHandover exactly — it finds the project by cwd,\n * finds (or creates) TODO.md, and prepends a ## Continue block. The difference\n * is that we do NOT call process.exit() after writing, so the warning is shown.\n *\n * WHY Ctrl+C is dangerous:\n * Claude Code's stop-hook fires only on a clean exit (e.g. /exit inside the\n * Claude prompt, or claude process receiving SIGTERM). When you Ctrl+C from\n * a terminal that's running claude, the stop-hook is bypassed, which means:\n * - The top-level <project>/<uuid>.jsonl does NOT get its final system snapshot.\n * - Claude Code's --resume cannot find the session (0 system lines).\n * - The session becomes orphaned — transcript exists but is unresumable.\n *\n * If the project TODO.md gets its ## Continue checkpoint (written by this\n * command), the *content* can still be recovered. But the *session context*\n * (conversation history) is lost unless you exit cleanly via /exit.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n renameSync,\n mkdirSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// TODO.md discovery (same locations as handover.ts)\n// ---------------------------------------------------------------------------\n\nconst HANDOVER_TODO_LOCATIONS = [\n \"Notes/TODO.md\",\n \".claude/Notes/TODO.md\",\n \"tasks/todo.md\",\n \"TODO.md\",\n];\n\nfunction findProjectTodo(\n rootPath: string\n): { path: string; content: string } | null {\n for (const rel of HANDOVER_TODO_LOCATIONS) {\n const full = join(rootPath, rel);\n if (existsSync(full)) {\n try {\n return { path: full, content: readFileSync(full, \"utf8\") };\n } catch {\n // unreadable — try next\n }\n }\n }\n return null;\n}\n\nfunction stripContinueSection(content: string): string {\n const lines = content.split(\"\\n\");\n const startIdx = lines.findIndex((l) => l.trim() === \"## Continue\");\n if (startIdx === -1) return content;\n\n let endIdx = lines.length;\n for (let i = startIdx + 1; i < lines.length; i++) {\n const trimmed = lines[i].trim();\n if (\n trimmed === \"---\" ||\n (trimmed.startsWith(\"##\") && trimmed !== \"## Continue\")\n ) {\n endIdx = i;\n break;\n }\n }\n\n let trailingEnd = endIdx;\n if (trailingEnd < lines.length && lines[trailingEnd].trim() === \"---\") {\n trailingEnd += 1;\n }\n\n const before = lines.slice(0, startIdx);\n const after = lines.slice(trailingEnd);\n while (after.length > 0 && after[0].trim() === \"\") after.shift();\n\n return [...before, ...after].join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport function cmdPause(\n db: Database,\n opts: { dryRun?: boolean }\n): void {\n // ---- 1. Resolve project by cwd ----\n const cwd = process.cwd();\n const project = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir\n FROM projects\n WHERE ? LIKE root_path || '%'\n ORDER BY length(root_path) DESC\n LIMIT 1`\n )\n .get(cwd) as ProjectRow | undefined;\n\n if (!project) {\n console.error(\n chalk.red(\"pai session pause: \") +\n \"Current directory is not within a registered PAI project.\\n\" +\n ` cwd: ${cwd}`\n );\n process.exit(1);\n }\n\n // ---- 2. Resolve latest session ----\n const session = db\n .prepare(\n \"SELECT * FROM sessions WHERE project_id = ? ORDER BY number DESC LIMIT 1\"\n )\n .get(project.id) as SessionRow | undefined;\n\n // ---- 3. Find or create TODO.md ----\n const todo = findProjectTodo(project.root_path);\n let todoPath: string;\n let existingContent: string;\n\n if (todo) {\n todoPath = todo.path;\n existingContent = todo.content;\n } else {\n const notesDir = join(project.root_path, \"Notes\");\n if (!opts.dryRun) {\n try {\n if (!existsSync(notesDir)) mkdirSync(notesDir, { recursive: true });\n } catch (mkdirErr) {\n console.error(\n chalk.red(\"pai session pause: \") +\n `Could not create Notes directory: ${String(mkdirErr)}`\n );\n process.exit(1);\n }\n }\n todoPath = join(notesDir, \"TODO.md\");\n existingContent = \"\";\n }\n\n // ---- 4. Build ## Continue block ----\n const timestamp = new Date().toISOString();\n\n let sessionLine: string;\n if (session) {\n const num = String(session.number).padStart(4, \"0\");\n const titlePart = session.title || session.slug || \"Session\";\n sessionLine = `${num} - ${session.date} - ${titlePart}`;\n } else {\n sessionLine = \"Unknown session\";\n }\n\n const continueBlock = [\n \"## Continue\",\n \"\",\n `> **Last session:** ${sessionLine}`,\n `> **Paused at:** ${timestamp}`,\n \">\",\n `> Working directory: ${cwd}. Check the latest session note for details.`,\n \"\",\n \"---\",\n \"\",\n ].join(\"\\n\");\n\n // ---- 5. Prepend, stripping old ## Continue ----\n const stripped = stripContinueSection(existingContent).trimStart();\n const newContent = continueBlock + stripped;\n\n // ---- 6. Write atomically (or dry-run preview) ----\n if (opts.dryRun) {\n console.log(\"\\n\" + chalk.bold(\"Dry run — would write to:\") + \" \" + chalk.cyan(todoPath));\n console.log(chalk.dim(\"─\".repeat(60)));\n // Show just the ## Continue block that would be prepended\n console.log(chalk.dim(continueBlock));\n console.log(chalk.dim(\"─\".repeat(60)));\n } else {\n const tmpPath = `${todoPath}.pause.tmp`;\n try {\n writeFileSync(tmpPath, newContent, \"utf8\");\n renameSync(tmpPath, todoPath);\n } catch (writeErr) {\n try {\n if (existsSync(tmpPath)) renameSync(tmpPath, `${tmpPath}.dead`);\n } catch {\n /* ignore */\n }\n console.error(\n chalk.red(\"pai session pause: \") +\n `Failed to write TODO.md: ${String(writeErr)}`\n );\n process.exit(1);\n }\n console.log(\n chalk.green(\" ## Continue written to: \") + chalk.cyan(todoPath)\n );\n }\n\n // ---- 7. Print the safety warning ----\n const box = [\n \"\",\n chalk.bgRed.white.bold(\n \" IMPORTANT: How to exit safely \"\n ),\n \"\",\n chalk.yellow(\n \" Inside the Claude Code session, type: \" +\n chalk.white.bold(\"/exit\") +\n chalk.yellow(\" (then press Enter)\")\n ),\n \"\",\n chalk.red.bold(\" DO NOT press Ctrl+C.\"),\n \"\",\n chalk.dim(\n \" Ctrl+C bypasses PAI's stop-hook, which means:\"\n ),\n chalk.dim(\" - No project snapshot is written to the top-level jsonl\"),\n chalk.dim(\" - The session becomes orphaned (cannot --resume next time)\"),\n chalk.dim(\n \" - pai session goto will launch a fresh session instead of resuming\"\n ),\n \"\",\n chalk.dim(\" The ## Continue checkpoint in TODO.md is already saved.\"),\n chalk.dim(\" Use /exit to preserve full session resumability.\"),\n \"\",\n ].join(\"\\n\");\n\n console.log(box);\n}\n","/**\n * pai end [--dry-run]\n *\n * Extends `pai pause` with two additional steps:\n * 1. Marks the current session note **Status: Completed** and adds a\n * Completed timestamp — mirrors what `finalizeSessionNote()` does in the\n * hooks lib but without importing the hooks-only module.\n * 2. Prints a slightly more final safe-exit reminder.\n *\n * The TODO.md ## Continue checkpoint is written first (same as `pai pause`),\n * so if the user exits without the session note step succeeding the content\n * checkpoint is still intact.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n readdirSync,\n renameSync,\n mkdirSync,\n} from \"node:fs\";\nimport { join, basename } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { realpathSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\nimport { cmdPause } from \"./pause.js\";\n\n// ---------------------------------------------------------------------------\n// Notes-directory resolution (mirrors paths.ts without importing hooks lib)\n// ---------------------------------------------------------------------------\n\n/** Encode a path the same way Claude Code does. */\nfunction encodePath(path: string): string {\n return path\n .replace(/\\//g, \"-\")\n .replace(/\\./g, \"-\")\n .replace(/ /g, \"-\");\n}\n\n/** PAI_DIR — mirrors pai-paths.ts resolution. */\nfunction getPaiDir(): string {\n const envDir = process.env.PAI_DIR;\n if (envDir) {\n try {\n return realpathSync(envDir);\n } catch {\n return envDir;\n }\n }\n return join(homedir(), \".claude\");\n}\n\n/**\n * Find the notes directory for a project — checks local first, then central.\n * Returns null if neither exists (dry-run safe: don't create).\n */\nfunction findNotesDir(\n rootPath: string,\n encodedDir: string\n): string | null {\n // Check local paths first\n for (const rel of [\"Notes\", \"notes\", \".claude/Notes\"]) {\n const p = join(rootPath, rel);\n if (existsSync(p)) return p;\n }\n // Central fallback: ~/.claude/projects/<encoded>/Notes\n const central = join(getPaiDir(), \"projects\", encodedDir, \"Notes\");\n if (existsSync(central)) return central;\n return null;\n}\n\n/**\n * Find the current (latest) session note inside notesDir.\n * Searches YYYY/MM subdirectory (current month, then previous month),\n * then flat notesDir as legacy fallback.\n */\nfunction findLatestNote(notesDir: string): string | null {\n const findIn = (dir: string): string | null => {\n if (!existsSync(dir)) return null;\n const files = readdirSync(dir)\n .filter((f) => /^\\d{3,4}[\\s_-].*\\.md$/.test(f))\n .sort((a, b) => {\n const na = parseInt(a.match(/^(\\d+)/)?.[1] ?? \"0\", 10);\n const nb = parseInt(b.match(/^(\\d+)/)?.[1] ?? \"0\", 10);\n return na - nb;\n });\n return files.length > 0 ? join(dir, files[files.length - 1]) : null;\n };\n\n const now = new Date();\n const year = String(now.getFullYear());\n const month = String(now.getMonth() + 1).padStart(2, \"0\");\n\n const current = findIn(join(notesDir, year, month));\n if (current) return current;\n\n const prev = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n const py = String(prev.getFullYear());\n const pm = String(prev.getMonth() + 1).padStart(2, \"0\");\n const prevFound = findIn(join(notesDir, py, pm));\n if (prevFound) return prevFound;\n\n return findIn(notesDir);\n}\n\n// ---------------------------------------------------------------------------\n// Session-note finalization (mirrors finalizeSessionNote from session-notes.ts)\n// ---------------------------------------------------------------------------\n\nfunction finalizeNote(notePath: string): { finalized: boolean; path: string } {\n const content = readFileSync(notePath, \"utf-8\");\n\n if (content.includes(\"**Status:** Completed\")) {\n return { finalized: false, path: notePath }; // already done\n }\n\n let updated = content.replace(\"**Status:** In Progress\", \"**Status:** Completed\");\n\n if (!updated.includes(\"**Completed:**\")) {\n const completionTime = new Date().toISOString();\n updated = updated.replace(\n \"---\\n\\n## Work Done\",\n `**Completed:** ${completionTime}\\n\\n---\\n\\n## Work Done`\n );\n }\n\n // Write atomically\n const tmp = `${notePath}.end.tmp`;\n writeFileSync(tmp, updated, \"utf-8\");\n renameSync(tmp, notePath);\n\n return { finalized: true, path: notePath };\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport function cmdEnd(db: Database, opts: { dryRun?: boolean }): void {\n // ---- Step 1: Run pause logic (writes ## Continue, prints warning) ----\n // We call cmdPause first; it prints the ## Continue block and the initial\n // safe-exit box. We'll print the end-specific reminder afterwards.\n cmdPause(db, opts);\n\n // ---- Step 2: Locate the project ----\n const cwd = process.cwd();\n const project = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir\n FROM projects\n WHERE ? LIKE root_path || '%'\n ORDER BY length(root_path) DESC\n LIMIT 1`\n )\n .get(cwd) as ProjectRow | undefined;\n\n if (!project) {\n // cmdPause already printed an error. Nothing more to do.\n return;\n }\n\n // ---- Step 3: Find notes directory ----\n const notesDir = findNotesDir(project.root_path, project.encoded_dir ?? \"\");\n\n if (!notesDir) {\n console.log(\n chalk.dim(\" (No session note found — notes directory does not exist yet.)\")\n );\n printEndBox(opts.dryRun);\n return;\n }\n\n // ---- Step 4: Find latest session note ----\n const notePath = findLatestNote(notesDir);\n\n if (!notePath) {\n console.log(chalk.dim(\" (No session note found in notes directory.)\"));\n printEndBox(opts.dryRun);\n return;\n }\n\n // ---- Step 5: Mark as completed ----\n if (opts.dryRun) {\n console.log(\n \"\\n\" +\n chalk.bold(\"Dry run — would finalize session note:\") +\n \" \" +\n chalk.cyan(notePath)\n );\n console.log(\n chalk.dim(\n \" Would replace: **Status:** In Progress\\n\" +\n \" with: **Status:** Completed\"\n )\n );\n console.log(\n chalk.dim(\" Would add: **Completed:** <timestamp>\")\n );\n } else {\n const { finalized, path } = finalizeNote(notePath);\n if (finalized) {\n console.log(\n chalk.green(\" Session note finalized: \") + chalk.cyan(basename(path))\n );\n } else {\n console.log(\n chalk.dim(\n ` Session note already marked Completed: ${basename(notePath)}`\n )\n );\n }\n }\n\n // ---- Step 6: Print the more-final exit reminder ----\n printEndBox(opts.dryRun);\n}\n\nfunction printEndBox(dryRun?: boolean): void {\n const label = dryRun ? \" (dry-run)\" : \"\";\n const box = [\n \"\",\n chalk.bgRed.white.bold(\n ` SESSION ENDING${label}: How to exit safely `\n ),\n \"\",\n chalk.yellow(\n \" Inside the Claude Code session, type: \" +\n chalk.white.bold(\"/exit\") +\n chalk.yellow(\" (then press Enter)\")\n ),\n \"\",\n chalk.red.bold(\" DO NOT press Ctrl+C.\"),\n \"\",\n chalk.dim(\" Ctrl+C bypasses PAI's stop-hook, which means:\"),\n chalk.dim(\" - The session note is NOT written to by the stop-hook\"),\n chalk.dim(\" - The session becomes orphaned (cannot --resume next time)\"),\n chalk.dim(\" - The final session summary is never generated\"),\n \"\",\n chalk.dim(\n \" ## Continue checkpoint and session note status are already saved.\"\n ),\n chalk.dim(\" Use /exit to let PAI's stop-hook finalize the session fully.\"),\n \"\",\n ].join(\"\\n\");\n\n console.log(box);\n}\n","/**\n * pai resume <name> / pai sessions goto <name> [--dry-run]\n *\n * Smart session launcher with auto-fallback:\n *\n * 1. Resolve name → (uuid?, projectDir, friendlyName)\n * 2. chdir(projectDir)\n * 3. If uuid is set:\n * a. Probe: claude --resume <uuid> --print --output-format=json \"_\"\n * (timeout 5s; exits non-zero / logs \"No conversation found\" if invalid)\n * b. If probe succeeds:\n * exec claude --resume <uuid> --name \"<friendlyName>\" \"/Name <friendlyName>\\ngo\"\n * c. Else (probe failed):\n * print clear stderr line: \"Resume failed. Starting fresh session in same dir.\"\n * exec claude --name \"<friendlyName>\" \"/Name <friendlyName>\\ngo\"\n * 4. Else (no uuid known):\n * exec claude --name \"<friendlyName>\" \"/Name <friendlyName>\\ngo\"\n *\n * Why both --name AND /Name?\n * --name <friendlyName> → sets Claude Code's internal session label\n * \"/Name <friendlyName>\\ngo\" → runs the /Name slash command via AIBroker, which updates\n * iTerm tab title, statusline, and AIBroker session registry;\n * the \\ngo on the next line triggers PAI's ## Continue resume\n *\n * The probe adds ~300ms on the happy path. On the fallback path, total latency is the same\n * as a fresh start.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { spawnSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport { err } from \"../../utils.js\";\nimport {\n scanSessions,\n resolveSessionByNameOrId,\n fmtAge,\n type ScannedSession,\n} from \"../../lib/session-scan.js\";\nimport { printExitDir } from \"../../lib/exit-dir.js\";\n\n// ---------------------------------------------------------------------------\n// Probe helper\n// ---------------------------------------------------------------------------\n\ninterface ProbeResult {\n ok: boolean;\n reason?: string;\n}\n\n/**\n * Run claude --resume <uuid> --print --output-format=json \"_\" in the given cwd.\n * Returns ok=true if the session is resumable (exit 0 and no \"No conversation found\"\n * in stderr). Timeout: 5 seconds.\n */\nfunction probeResume(uuid: string, cwd: string): ProbeResult {\n const result = spawnSync(\n \"claude\",\n [\"--resume\", uuid, \"--print\", \"--output-format=json\", \"_\"],\n {\n cwd,\n timeout: 5_000,\n env: process.env,\n // Capture stderr for inspection; suppress stdout (tty)\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n }\n );\n\n if (result.error) {\n return { ok: false, reason: `spawn error: ${result.error.message}` };\n }\n\n const stderr = result.stderr?.toString(\"utf8\") ?? \"\";\n\n if (\n stderr.toLowerCase().includes(\"no conversation found\") ||\n stderr.toLowerCase().includes(\"session not found\")\n ) {\n return { ok: false, reason: \"No conversation found for this UUID\" };\n }\n\n if (result.status !== 0) {\n return {\n ok: false,\n reason: `claude exited ${result.status ?? \"signal\"}${stderr ? `: ${stderr.slice(0, 120).trim()}` : \"\"}`,\n };\n }\n\n return { ok: true };\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport function cmdGoto(\n db: Database,\n query: string,\n opts: { dryRun?: boolean }\n): void {\n // ---- 1. Resolve name → session ----\n const allSessions = scanSessions(db, { limit: 500, filter: \"named\" });\n\n let resolved;\n try {\n resolved = resolveSessionByNameOrId(allSessions, query);\n } catch (resolveErr) {\n let msg = String(resolveErr).replace(/^Error: /, \"\");\n // Append pai find suggestion for \"not found\" errors\n if (msg.includes(\"No session found matching\")) {\n msg += `\\n\\nTip: pai find \"${query}\" — search prompt history by keywords`;\n }\n console.error(err(msg));\n process.exit(1);\n }\n\n const { session: matchedSession, friendlyName } = resolved;\n\n // ---- 2. Find best resumable UUID for this project ----\n let resumableUuid: string | undefined;\n let resumableSession: ScannedSession | undefined;\n\n if (matchedSession.resumable) {\n resumableUuid = matchedSession.uuid;\n resumableSession = matchedSession;\n } else if (matchedSession.encodedDir) {\n // Find the most-recently-modified resumable session in the same project dir\n const sameProject = allSessions.filter(\n (s) => s.encodedDir === matchedSession.encodedDir && s.resumable\n );\n sameProject.sort((a, b) => b.mtime - a.mtime);\n if (sameProject.length > 0) {\n resumableSession = sameProject[0];\n resumableUuid = resumableSession.uuid;\n }\n }\n\n // ---- 3. Determine project dir (realpathSync for --resume cwd correctness) ----\n const rawDir =\n matchedSession.clcDirectory ??\n matchedSession.registryRootPath ??\n matchedSession.decodedPath;\n\n let projectDir: string;\n try {\n projectDir = realpathSync(rawDir);\n } catch {\n console.error(\n err(\n `session \"${query}\": directory does not exist or cannot be resolved.\\n` +\n ` Registry says: ${rawDir}\\n` +\n ` The directory may have moved or been deleted.`\n )\n );\n process.exit(1);\n }\n\n // ---- 4. Build argv components ----\n const name = friendlyName ?? query;\n // Initial prompt: /Name sets tab/statusline via AIBroker; \\ngo triggers ## Continue\n const promptArg = `/Name ${name}\\ngo`;\n\n // ---- 5. Dry-run mode ----\n if (opts.dryRun) {\n if (resumableUuid) {\n const argvResume = `claude --resume ${resumableUuid} --name \"${name}\" \"/Name ${name}\\\\ngo\"`;\n const argvFresh = `claude --name \"${name}\" \"/Name ${name}\\\\ngo\"`;\n console.log(\n \"\\n\" + chalk.bold(\"Dry run — would probe then exec (RESUME path):\") + \"\\n\"\n );\n console.log(` cwd: ${chalk.cyan(projectDir)}`);\n console.log(` probe: claude --resume ${resumableUuid} --print --output-format=json \"_\"`);\n console.log(` argv: ${chalk.white(argvResume)}`);\n console.log(` fallback: ${chalk.yellow(argvFresh)}`);\n if (resumableSession) {\n console.log(`\\n uuid: ${resumableSession.uuid}`);\n console.log(` age: ${fmtAge(resumableSession.mtime)}`);\n console.log(` status: ${resumableSession.sessionStatus}`);\n console.log(` sys: ${resumableSession.topLevelSystemLines} system lines`);\n }\n } else {\n const argvFresh = `claude --name \"${name}\" \"/Name ${name}\\\\ngo\"`;\n console.log(\n \"\\n\" + chalk.bold(\"Dry run — would exec (FRESH path, no resumable UUID):\") + \"\\n\"\n );\n console.log(` cwd: ${chalk.cyan(projectDir)}`);\n console.log(` argv: ${chalk.white(argvFresh)}`);\n }\n console.log();\n return;\n }\n\n // ---- 6. Live execution ----\n if (resumableUuid) {\n // Probe first\n const probe = probeResume(resumableUuid, projectDir);\n\n if (probe.ok) {\n // Happy path: session is resumable\n const result = spawnSync(\n \"claude\",\n [\"--resume\", resumableUuid, \"--name\", name, promptArg],\n {\n cwd: projectDir,\n stdio: \"inherit\",\n env: process.env,\n }\n );\n if (result.error) {\n console.error(err(`Failed to launch claude: ${result.error.message}`));\n process.exit(1);\n }\n printExitDir(projectDir);\n process.exit(result.status ?? 0);\n } else {\n // Fallback path\n process.stderr.write(\n chalk.yellow(\n `\\n Resume failed for ${resumableUuid.slice(0, 8)}: ${probe.reason ?? \"unknown error\"}\\n` +\n ` Starting fresh session in same directory.\\n\\n`\n )\n );\n const result = spawnSync(\n \"claude\",\n [\"--name\", name, promptArg],\n {\n cwd: projectDir,\n stdio: \"inherit\",\n env: process.env,\n }\n );\n if (result.error) {\n console.error(err(`Failed to launch claude: ${result.error.message}`));\n process.exit(1);\n }\n printExitDir(projectDir);\n process.exit(result.status ?? 0);\n }\n } else {\n // No UUID at all — fresh start\n const result = spawnSync(\n \"claude\",\n [\"--name\", name, promptArg],\n {\n cwd: projectDir,\n stdio: \"inherit\",\n env: process.env,\n }\n );\n if (result.error) {\n console.error(err(`Failed to launch claude: ${result.error.message}`));\n process.exit(1);\n }\n printExitDir(projectDir);\n process.exit(result.status ?? 0);\n }\n}\n","/**\n * pai pause all [--exit] [--dry-run]\n *\n * Pause every live Claude Code session that AIBroker knows about.\n *\n * For each live session returned by AIBroker's session_content IPC method:\n * 1. Send \"pause session\" to the iTerm2 pane via send_to_session.\n * 2. Optionally send \"\\n/exit\\n\" after a short delay (--exit flag).\n * 3. Print a summary of what was sent and to which sessions.\n *\n * If AIBroker is not running, prints a clear error and exits.\n * If no live sessions are found, reports \"nothing to pause\".\n */\n\nimport chalk from \"chalk\";\nimport {\n fetchLiveSessions,\n sendToSession,\n type AiBrokerSessionMeta,\n} from \"../../lib/aibroker-client.js\";\nimport { header, dim, ok, err, warn } from \"../../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction sessionLabel(s: AiBrokerSessionMeta): string {\n const name = s.paiName ?? s.name;\n return `${chalk.cyan(s.sessionId.slice(0, 8))} ${chalk.bold(name)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdPauseAll(opts: {\n exit?: boolean;\n dryRun?: boolean;\n wait?: number;\n}): Promise<void> {\n const waitMs = opts.wait ?? 5_000; // ms to wait after \"pause session\" before /exit\n\n // ── Fetch live sessions ────────────────────────────────────────────────────\n let liveSessions: AiBrokerSessionMeta[];\n try {\n liveSessions = await fetchLiveSessions();\n } catch (e) {\n console.error(err(\"AIBroker is not running. Cannot list live sessions.\"));\n console.error(dim(\" Start AIBroker or run `pai pause` from each session manually.\"));\n process.exitCode = 1;\n return;\n }\n\n // ── Filter: Claude sessions only (skip bare shells) ───────────────────────\n const claudeSessions = liveSessions.filter((s) => s.kind === \"claude\");\n const skipped = liveSessions.length - claudeSessions.length;\n\n if (skipped > 0) {\n process.stderr.write(\n `Skipping ${skipped} non-Claude tab${skipped === 1 ? \"\" : \"s\"} (bare shells).\\n`\n );\n }\n\n if (claudeSessions.length === 0) {\n console.log(warn(\"No live Claude sessions found via AIBroker. Nothing to pause.\"));\n return;\n }\n\n // ── Dry-run ───────────────────────────────────────────────────────────────\n if (opts.dryRun) {\n console.log(\"\\n\" + header(\"Dry Run — Would Pause These Sessions\") + \"\\n\");\n for (const s of claudeSessions) {\n console.log(\" \" + sessionLabel(s));\n console.log(dim(' → send: \"pause session\"'));\n if (opts.exit) {\n console.log(dim(` → wait ${waitMs}ms then send: \"/exit\"`));\n }\n }\n console.log();\n return;\n }\n\n // ── Live run ──────────────────────────────────────────────────────────────\n console.log(\n \"\\n\" +\n header(\"Pausing All Live Sessions\") +\n \"\\n\" +\n dim(` ${claudeSessions.length} Claude session(s) via AIBroker`) +\n \"\\n\"\n );\n\n const results: Array<{ session: AiBrokerSessionMeta; pauseOk: boolean; exitOk?: boolean; error?: string }> = [];\n\n for (const s of claudeSessions) {\n process.stdout.write(\" \" + sessionLabel(s) + \" … \");\n\n // Send \"pause session\" command\n const pauseResult = await sendToSession(s.sessionId, \"pause session\\n\");\n if (!pauseResult.ok) {\n console.log(err(\"FAILED: \" + (pauseResult.error ?? \"unknown error\")));\n results.push({ session: s, pauseOk: false, error: pauseResult.error });\n continue;\n }\n console.log(ok(\"paused\"));\n results.push({ session: s, pauseOk: true });\n }\n\n // ── Optional /exit after waiting ──────────────────────────────────────────\n if (opts.exit) {\n const pausedSessions = results.filter((r) => r.pauseOk).map((r) => r.session);\n if (pausedSessions.length > 0) {\n console.log(\n \"\\n\" +\n dim(` Waiting ${waitMs / 1000}s for sessions to save state…`)\n );\n await sleep(waitMs);\n\n console.log();\n for (const s of pausedSessions) {\n process.stdout.write(\" \" + sessionLabel(s) + \" exiting … \");\n const exitResult = await sendToSession(s.sessionId, \"/exit\\n\");\n const rec = results.find((r) => r.session.sessionId === s.sessionId)!;\n rec.exitOk = exitResult.ok;\n if (!exitResult.ok) {\n console.log(warn(\"exit failed: \" + (exitResult.error ?? \"unknown\")));\n } else {\n console.log(ok(\"exited\"));\n }\n }\n }\n }\n\n // ── Summary ───────────────────────────────────────────────────────────────\n const total = results.length;\n const succeeded = results.filter((r) => r.pauseOk).length;\n const failed = total - succeeded;\n\n console.log();\n if (failed === 0) {\n console.log(ok(`All ${total} session(s) paused successfully.`));\n } else {\n console.log(\n warn(`${succeeded}/${total} session(s) paused. `) +\n err(`${failed} failed.`)\n );\n for (const r of results.filter((r) => !r.pauseOk)) {\n const label = r.session.paiName ?? r.session.name ?? r.session.sessionId.slice(0, 8);\n console.log(err(` ${label}: ${r.error ?? \"unknown error\"}`));\n }\n }\n\n if (opts.exit) {\n const exitOk = results.filter((r) => r.exitOk).length;\n console.log(dim(` /exit sent to ${exitOk} session(s).`));\n }\n\n console.log();\n}\n","/**\n * pai sessions clear-names\n *\n * Recovery command: wipes corrupted iTerm2 session name state.\n *\n * Two-step reset:\n * 1. Clear ~/.aibroker/session-names.json (the persistent name store).\n * 2. Clear user.paiName variable from all live iTerm2 sessions.\n *\n * After this runs the user can `/Name` each tab fresh. Nothing is\n * set automatically — the user re-asserts names via the /Name skill.\n */\n\nimport { callAiBroker } from \"../../lib/aibroker-client.js\";\nimport { ok, err, dim, warn } from \"../../utils.js\";\n\nexport async function cmdClearNames(opts: { dryRun?: boolean }): Promise<void> {\n if (opts.dryRun) {\n console.log(dim(\"Dry run — would:\"));\n console.log(dim(\" 1. Call AIBroker clear_session_names (wipe session-names.json)\"));\n console.log(dim(\" 2. Call AIBroker clear_pai_names (wipe user.paiName from all iTerm sessions)\"));\n return;\n }\n\n let anyFailed = false;\n\n // Step 1: clear the persistent name store\n process.stdout.write(\"Clearing session-names.json … \");\n try {\n const r1 = await callAiBroker(\"clear_session_names\", {}, 10_000) as {\n cleared?: number;\n };\n const n = r1.cleared ?? 0;\n console.log(ok(`cleared ${n} entr${n === 1 ? \"y\" : \"ies\"}`));\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (msg.includes(\"ENOENT\") || msg.includes(\"ECONNREFUSED\")) {\n console.log(warn(\"AIBroker not running — session-names.json not cleared\"));\n anyFailed = true;\n } else {\n console.log(err(msg));\n anyFailed = true;\n }\n }\n\n // Step 2: clear user.paiName from all live iTerm2 sessions\n process.stdout.write(\"Clearing user.paiName from iTerm2 sessions … \");\n try {\n const r2 = await callAiBroker(\"clear_pai_names\", {}, 30_000) as {\n cleared?: number;\n };\n const n = r2.cleared ?? 0;\n console.log(ok(`cleared ${n} session${n === 1 ? \"\" : \"s\"}`));\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (msg.includes(\"ENOENT\") || msg.includes(\"ECONNREFUSED\")) {\n console.log(warn(\"AIBroker not running — iTerm2 paiName variables not cleared\"));\n anyFailed = true;\n } else {\n console.log(err(msg));\n anyFailed = true;\n }\n }\n\n if (anyFailed) {\n console.log(warn(\"\\nSome steps could not complete. Restart AIBroker and try again.\"));\n process.exitCode = 1;\n } else {\n console.log(ok(\"\\nDone. Use /Name to re-label each tab.\"));\n }\n}\n","/**\n * Shared DB helpers, formatting, and path utilities for session sub-commands.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { err } from \"../../utils.js\";\nimport type { SessionRow, ProjectRow } from \"./types.js\";\n\nexport function getProject(db: Database, slug: string): ProjectRow | undefined {\n return db\n .prepare(\n \"SELECT id, slug, display_name, root_path, encoded_dir FROM projects WHERE slug = ?\"\n )\n .get(slug) as ProjectRow | undefined;\n}\n\nexport function statusColor(status: string): string {\n switch (status) {\n case \"completed\":\n return chalk.green(status);\n case \"compacted\":\n return chalk.blue(status);\n default:\n return chalk.yellow(status);\n }\n}\n\n/** Convert a slug to title-cased display name: \"memory-engine\" → \"Memory Engine\" */\nexport function toTitleCase(slug: string): string {\n return slug\n .replace(/-/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/** Notes directory for a project: ~/.claude/projects/<encoded_dir>/Notes/ */\nexport function getNotesDir(project: ProjectRow): string {\n return join(homedir(), \".claude\", \"projects\", project.encoded_dir, \"Notes\");\n}\n\n/** Format a session filename: number=27, date=\"2026-02-23\" → \"0027 - 2026-02-23 - Title.md\" */\nexport function formatFilename(\n number: number,\n date: string,\n titleSlug: string\n): string {\n const n = String(number).padStart(4, \"0\");\n return `${n} - ${date} - ${titleSlug}.md`;\n}\n\n/** Resolve a session by project + number or \"latest\". Exits on failure. */\nexport function resolveSession(\n db: Database,\n project: ProjectRow,\n numberOrLatest: string\n): SessionRow {\n let session: SessionRow | undefined;\n\n if (numberOrLatest === \"latest\") {\n session = db\n .prepare(\n \"SELECT * FROM sessions WHERE project_id = ? ORDER BY number DESC LIMIT 1\"\n )\n .get(project.id) as SessionRow | undefined;\n } else {\n const num = parseInt(numberOrLatest, 10);\n if (isNaN(num)) {\n console.error(err(`Invalid session number: ${numberOrLatest}`));\n process.exit(1);\n }\n session = db\n .prepare(\"SELECT * FROM sessions WHERE project_id = ? AND number = ?\")\n .get(project.id, num) as SessionRow | undefined;\n }\n\n if (!session) {\n console.error(\n err(`Session ${numberOrLatest} not found in project ${project.slug}`)\n );\n process.exit(1);\n }\n\n return session;\n}\n\nexport function upsertTag(db: Database, tagName: string): number {\n db.prepare(\"INSERT OR IGNORE INTO tags (name) VALUES (?)\").run(tagName);\n const row = db\n .prepare(\"SELECT id FROM tags WHERE name = ?\")\n .get(tagName) as { id: number };\n return row.id;\n}\n\nexport function getSessionTags(db: Database, sessionId: number): string[] {\n const rows = db\n .prepare(\n `SELECT t.name FROM tags t\n JOIN session_tags st ON st.tag_id = t.id\n WHERE st.session_id = ?\n ORDER BY t.name`\n )\n .all(sessionId) as { name: string }[];\n return rows.map((r) => r.name);\n}\n","/**\n * Session CRUD commands: list, info, rename, slug, tag, route, active, auto-route.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n readdirSync,\n renameSync,\n readFileSync,\n writeFileSync,\n statSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport {\n ok,\n warn,\n err,\n dim,\n bold,\n header,\n renderTable,\n fmtDate,\n} from \"../../utils.js\";\nimport {\n findLatestTranscript,\n readLastMessages,\n generateSlug,\n} from \"../../../session/slug-generator.js\";\nimport type { SessionRow, ProjectRow } from \"./types.js\";\nimport {\n getProject,\n statusColor,\n toTitleCase,\n getNotesDir,\n formatFilename,\n resolveSession,\n upsertTag,\n getSessionTags,\n} from \"./helpers.js\";\n\n// ---------------------------------------------------------------------------\n// list\n// ---------------------------------------------------------------------------\n\nexport function cmdList(\n db: Database,\n projectSlug: string | undefined,\n opts: { limit?: string; status?: string }\n): void {\n const limit = parseInt(opts.limit ?? \"20\", 10);\n const params: unknown[] = [];\n\n let query = `\n SELECT s.*, p.slug AS project_slug, p.display_name AS project_name\n FROM sessions s\n JOIN projects p ON p.id = s.project_id\n `;\n\n const where: string[] = [];\n if (projectSlug) {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n where.push(\"s.project_id = ?\");\n params.push(project.id);\n }\n if (opts.status) {\n where.push(\"s.status = ?\");\n params.push(opts.status);\n }\n\n if (where.length) query += \" WHERE \" + where.join(\" AND \");\n query += \" ORDER BY s.date DESC, s.number DESC\";\n query += ` LIMIT ${limit}`;\n\n const rows = db.prepare(query).all(...params) as (SessionRow & {\n project_slug: string;\n project_name: string;\n })[];\n\n if (!rows.length) {\n console.log(warn(\"No sessions found.\"));\n return;\n }\n\n const showProject = !projectSlug;\n const headers = showProject\n ? [\"#\", \"Project\", \"Date\", \"Title\", \"Status\", \"Tokens\"]\n : [\"#\", \"Date\", \"Title\", \"Status\", \"Tokens\"];\n\n const tableRows = rows.map((r) => {\n const title = r.title.length > 45 ? r.title.slice(0, 42) + \"...\" : r.title;\n const tokens =\n r.token_count != null ? dim(r.token_count.toLocaleString()) : dim(\"—\");\n if (showProject) {\n return [\n dim(`#${r.number}`),\n r.project_slug,\n r.date,\n title,\n statusColor(r.status),\n tokens,\n ];\n }\n return [dim(`#${r.number}`), r.date, title, statusColor(r.status), tokens];\n });\n\n console.log();\n if (projectSlug) {\n const project = getProject(db, projectSlug)!;\n console.log(` ${bold(project.display_name)} sessions:`);\n console.log();\n }\n console.log(renderTable(headers, tableRows));\n console.log();\n console.log(dim(` ${rows.length} session(s) shown (limit: ${limit})`));\n}\n\n// ---------------------------------------------------------------------------\n// info\n// ---------------------------------------------------------------------------\n\nexport function cmdInfo(\n db: Database,\n projectSlug: string,\n sessionNumber: string\n): void {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const session = resolveSession(db, project, sessionNumber);\n\n console.log();\n console.log(header(` Session #${session.number}: ${session.title}`));\n console.log();\n console.log(\n ` ${bold(\"Project:\")} ${project.display_name} (${project.slug})`\n );\n console.log(` ${bold(\"Date:\")} ${session.date}`);\n console.log(` ${bold(\"Status:\")} ${statusColor(session.status)}`);\n console.log(` ${bold(\"Filename:\")} ${session.filename}`);\n console.log(` ${bold(\"Slug:\")} ${session.slug}`);\n if (session.claude_session_id) {\n console.log(\n ` ${bold(\"Claude ID:\")} ${dim(session.claude_session_id)}`\n );\n }\n if (session.token_count != null) {\n console.log(\n ` ${bold(\"Tokens:\")} ${session.token_count.toLocaleString()}`\n );\n }\n console.log(` ${bold(\"Created:\")} ${fmtDate(session.created_at)}`);\n if (session.closed_at) {\n console.log(` ${bold(\"Closed:\")} ${fmtDate(session.closed_at)}`);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// rename\n// ---------------------------------------------------------------------------\n\nexport function cmdRename(\n db: Database,\n projectSlug: string,\n numberOrLatest: string,\n newSlug: string\n): void {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const session = resolveSession(db, project, numberOrLatest);\n const notesDir = getNotesDir(project);\n\n if (!existsSync(notesDir)) {\n console.error(err(`Notes directory not found: ${notesDir}`));\n process.exit(1);\n }\n\n const titleSlug = toTitleCase(newSlug);\n const newFilename = formatFilename(session.number, session.date, titleSlug);\n const oldPath = join(notesDir, session.filename);\n const newPath = join(notesDir, newFilename);\n\n if (existsSync(oldPath)) {\n if (oldPath !== newPath) {\n try {\n renameSync(oldPath, newPath);\n } catch (e) {\n console.error(err(`Failed to rename file: ${e}`));\n process.exit(1);\n }\n }\n } else {\n console.log(\n warn(` Note: file not found at expected path: ${session.filename}`)\n );\n console.log(warn(` Skipping disk rename. Database will still be updated.`));\n }\n\n if (existsSync(newPath)) {\n try {\n const content = readFileSync(newPath, \"utf8\");\n const lines = content.split(\"\\n\");\n let h1Updated = false;\n const updated = lines.map((line) => {\n if (!h1Updated && line.startsWith(\"# \")) {\n h1Updated = true;\n return `# ${titleSlug}`;\n }\n return line;\n });\n if (!h1Updated) updated.unshift(`# ${titleSlug}`, \"\");\n writeFileSync(newPath, updated.join(\"\\n\"), \"utf8\");\n } catch (e) {\n console.error(err(`Failed to update H1 in file: ${e}`));\n }\n }\n\n const normalizedSlug = newSlug\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n\n db.prepare(\n \"UPDATE sessions SET slug = ?, title = ?, filename = ? WHERE id = ?\"\n ).run(normalizedSlug, titleSlug, newFilename, session.id);\n\n console.log();\n console.log(ok(` Session #${session.number} renamed.`));\n console.log(` ${bold(\"Old:\")} ${session.filename}`);\n console.log(` ${bold(\"New:\")} ${newFilename}`);\n console.log(` ${bold(\"Slug:\")} ${normalizedSlug}`);\n console.log(` ${bold(\"Title:\")} ${titleSlug}`);\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// slug\n// ---------------------------------------------------------------------------\n\nexport function cmdSlug(\n db: Database,\n projectSlug: string,\n numberOrLatest: string,\n opts: { apply?: boolean }\n): void {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const session = resolveSession(db, project, numberOrLatest);\n const transcriptPath = findLatestTranscript(project.encoded_dir);\n\n if (!transcriptPath) {\n console.log(warn(` No JSONL transcripts found for project ${projectSlug}`));\n console.log(\"unnamed-session\");\n return;\n }\n\n const messages = readLastMessages(transcriptPath);\n\n if (messages.length < 2) {\n console.log(\n warn(` Too few messages found (${messages.length}) in transcript`)\n );\n console.log(\"unnamed-session\");\n return;\n }\n\n const generatedSlug = generateSlug(messages);\n console.log(generatedSlug);\n\n if (opts.apply) {\n console.log();\n console.log(dim(` Applying slug to session #${session.number}...`));\n cmdRename(db, projectSlug, String(session.number), generatedSlug);\n }\n}\n\n// ---------------------------------------------------------------------------\n// tag\n// ---------------------------------------------------------------------------\n\nexport function cmdTag(\n db: Database,\n projectSlug: string,\n sessionNumber: string,\n rawTags: string[]\n): void {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const session = resolveSession(db, project, sessionNumber);\n\n if (rawTags.length === 0) {\n const current = getSessionTags(db, session.id);\n console.log();\n if (current.length === 0) {\n console.log(dim(` Session #${session.number} has no tags.`));\n } else {\n console.log(\n ` ${bold(`Session #${session.number}`)} tags: ${current\n .map((t) => chalk.cyan(t))\n .join(\", \")}`\n );\n }\n console.log();\n return;\n }\n\n const tags = rawTags\n .flatMap((t) => t.split(\",\"))\n .map((t) => t.trim().toLowerCase())\n .filter((t) => t.length > 0);\n\n if (tags.length === 0) {\n console.log(warn(\"No valid tags provided.\"));\n return;\n }\n\n const added: string[] = [];\n const skipped: string[] = [];\n\n for (const tagName of tags) {\n const tagId = upsertTag(db, tagName);\n const exists = db\n .prepare(\"SELECT 1 FROM session_tags WHERE session_id = ? AND tag_id = ?\")\n .get(session.id, tagId);\n if (exists) {\n skipped.push(tagName);\n } else {\n db.prepare(\n \"INSERT INTO session_tags (session_id, tag_id) VALUES (?, ?)\"\n ).run(session.id, tagId);\n added.push(tagName);\n }\n }\n\n console.log();\n if (added.length) {\n console.log(\n ok(\n ` Tagged session #${session.number}: ${added\n .map((t) => chalk.cyan(t))\n .join(\", \")}`\n )\n );\n }\n if (skipped.length) {\n console.log(dim(` Already present: ${skipped.join(\", \")}`));\n }\n\n const allTags = getSessionTags(db, session.id);\n console.log(\n ` ${bold(\"All tags:\")} ${allTags.map((t) => chalk.cyan(t)).join(\", \")}`\n );\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// route\n// ---------------------------------------------------------------------------\n\nexport function cmdRoute(\n db: Database,\n projectSlug: string,\n sessionNumber: string,\n targetProjectSlug: string,\n opts: { type?: string }\n): void {\n const project = getProject(db, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exit(1);\n }\n\n const session = resolveSession(db, project, sessionNumber);\n\n const targetProject = db\n .prepare(\"SELECT id, slug, display_name FROM projects WHERE slug = ?\")\n .get(targetProjectSlug) as\n | { id: number; slug: string; display_name: string }\n | undefined;\n\n if (!targetProject) {\n console.error(err(`Target project not found: ${targetProjectSlug}`));\n process.exit(1);\n }\n\n const validTypes = [\"related\", \"follow-up\", \"reference\"];\n const linkType = opts.type ?? \"related\";\n if (!validTypes.includes(linkType)) {\n console.error(\n err(`Invalid link type \"${linkType}\". Valid: ${validTypes.join(\", \")}`)\n );\n process.exit(1);\n }\n\n try {\n db.prepare(\n `INSERT INTO links (session_id, target_project_id, link_type, created_at)\n VALUES (?, ?, ?, ?)`\n ).run(session.id, targetProject.id, linkType, Date.now());\n } catch {\n console.log(\n warn(\n ` Link already exists: session #${session.number} → ${targetProjectSlug}`\n )\n );\n return;\n }\n\n console.log();\n console.log(\n ok(\n ` Linked session #${session.number} (${project.slug}) → ${targetProject.display_name} (${targetProjectSlug})`\n )\n );\n console.log(dim(` Link type: ${linkType}`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// active\n// ---------------------------------------------------------------------------\n\nexport function cmdActive(\n db: Database,\n opts: { minutes?: string; json?: boolean }\n): void {\n const minutes = parseInt(opts.minutes ?? \"60\", 10);\n const cutoff = Date.now() - minutes * 60 * 1000;\n const claudeProjectsDir = join(homedir(), \".claude\", \"projects\");\n\n if (!existsSync(claudeProjectsDir)) {\n console.log(err(\"Claude projects directory not found.\"));\n return;\n }\n\n interface ActiveSession {\n slug: string;\n displayName: string;\n rootPath: string;\n encodedDir: string;\n lastModified: Date;\n jsonlFile: string;\n }\n\n const active: ActiveSession[] = [];\n const entries = readdirSync(claudeProjectsDir);\n\n for (const entry of entries) {\n const projectDir = join(claudeProjectsDir, entry);\n try {\n if (!statSync(projectDir).isDirectory()) continue;\n } catch {\n continue;\n }\n\n let latestJsonl: string | null = null;\n let latestMtime = 0;\n\n try {\n for (const file of readdirSync(projectDir)) {\n if (!file.endsWith(\".jsonl\")) continue;\n const filePath = join(projectDir, file);\n try {\n const mtime = statSync(filePath).mtimeMs;\n if (mtime > latestMtime) {\n latestMtime = mtime;\n latestJsonl = filePath;\n }\n } catch {\n continue;\n }\n }\n } catch {\n continue;\n }\n\n if (!latestJsonl || latestMtime < cutoff) continue;\n\n const project = db\n .prepare(\n \"SELECT slug, display_name, root_path FROM projects WHERE encoded_dir = ?\"\n )\n .get(entry) as\n | { slug: string; display_name: string; root_path: string }\n | undefined;\n\n active.push({\n slug: project?.slug ?? entry,\n displayName: project?.display_name ?? project?.slug ?? entry,\n rootPath: project?.root_path ?? \"\",\n encodedDir: entry,\n lastModified: new Date(latestMtime),\n jsonlFile: latestJsonl,\n });\n }\n\n active.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());\n\n const seen = new Set<string>();\n const deduped = active.filter((a) => {\n const key = a.slug.replace(/-\\d+$/, \"\");\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n if (opts.json) {\n console.log(\n JSON.stringify(\n deduped.map((a) => ({\n slug: a.slug,\n display_name: a.displayName,\n root_path: a.rootPath,\n last_modified: a.lastModified.toISOString(),\n })),\n null,\n 2\n )\n );\n return;\n }\n\n if (deduped.length === 0) {\n console.log(dim(`No active sessions in the last ${minutes} minutes.`));\n return;\n }\n\n console.log(\n header(`Currently Active Sessions`) +\n dim(` (modified in last ${minutes}min)`)\n );\n console.log();\n\n const rows = deduped.map((a) => {\n const time = a.lastModified.toTimeString().slice(0, 5);\n const dirName = a.rootPath\n ? a.rootPath.replace(homedir(), \"~\").split(\"/\").pop() ?? a.slug\n : a.slug;\n return [chalk.cyan(dirName), dim(a.slug), chalk.green(time)];\n });\n\n console.log(renderTable([\"Directory\", \"Project\", \"Last Active\"], rows));\n}\n\n// ---------------------------------------------------------------------------\n// auto-route\n// ---------------------------------------------------------------------------\n\nexport async function cmdAutoRoute(opts: {\n cwd?: string;\n context?: string;\n json?: boolean;\n}): Promise<void> {\n const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import(\n \"../../../session/auto-route.js\"\n );\n const { openRegistry } = await import(\"../../../registry/db.js\");\n const { createStorageBackend } = await import(\"../../../storage/factory.js\");\n const { loadConfig } = await import(\"../../../daemon/config.js\");\n\n const config = loadConfig();\n const registryDb = openRegistry();\n const federation = await createStorageBackend(config);\n\n const targetCwd = opts.cwd ?? process.cwd();\n const result = await autoRoute(\n registryDb,\n federation,\n targetCwd,\n opts.context\n );\n\n if (!result) {\n console.log();\n console.log(warn(\" No project match found for: \" + targetCwd));\n console.log();\n console.log(\n dim(\" Tried: path match, PAI.md marker walk\") +\n (opts.context ? dim(\", topic detection\") : \"\")\n );\n console.log();\n console.log(dim(\" Run 'pai project add .' to register this directory.\"));\n console.log();\n return;\n }\n\n if (opts.json) {\n console.log(formatAutoRouteJson(result));\n return;\n }\n\n console.log();\n console.log(header(\" PAI Auto-Route\"));\n console.log();\n console.log(` ${bold(\"Project:\")} ${result.display_name}`);\n console.log(` ${bold(\"Slug:\")} ${result.slug}`);\n console.log(` ${bold(\"Root path:\")} ${result.root_path}`);\n console.log(` ${bold(\"Method:\")} ${result.method}`);\n console.log(\n ` ${bold(\"Confidence:\")} ${(result.confidence * 100).toFixed(0)}%`\n );\n console.log();\n console.log(ok(\" Routed to: \") + bold(result.slug));\n console.log();\n}\n","/**\n * pai find <query> [--n=20] [--json]\n *\n * Content-based search across ~/.claude/history.jsonl.\n * Finds user prompts matching the query (case-insensitive substring),\n * groups by sessionId, sorts by most-recent match, and shows a table\n * with short UUID, date, project path, and the matching prompt snippet.\n *\n * The sessionId from history.jsonl is the same UUID used by claude --resume,\n * so rows are directly resumable via: pai resume <id>\n *\n * Note: older history.jsonl entries (before Claude Code ~2.0) may lack a\n * sessionId field. These are grouped under a synthetic \"no-session\" bucket\n * and shown in a separate note at the bottom.\n */\n\nimport { existsSync, createReadStream } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { renderTable, header, dim, warn, err } from \"../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface HistoryEntry {\n display?: string;\n timestamp?: number;\n project?: string;\n sessionId?: string;\n pastedContents?: unknown;\n}\n\ninterface SessionMatch {\n sessionId: string | null;\n /** Most-recent timestamp among matching lines */\n lastMatchTs: number;\n /** Most-recent matching prompt text */\n lastMatchDisplay: string;\n /** Project path (from the most-recent match) */\n project: string;\n /** Number of matching lines in this session */\n matchCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// History file location\n// ---------------------------------------------------------------------------\n\nconst HISTORY_FILE = join(homedir(), \".claude\", \"history.jsonl\");\n\n// ---------------------------------------------------------------------------\n// Shorten project path for display\n// ---------------------------------------------------------------------------\n\nfunction shortenProject(p: string, maxLen = 42): string {\n if (p.length <= maxLen) return p;\n // Keep tail (the meaningful part) and prefix with …\n return \"…\" + p.slice(-(maxLen - 1));\n}\n\n// ---------------------------------------------------------------------------\n// Format timestamp\n// ---------------------------------------------------------------------------\n\nfunction fmtTs(ts: number): string {\n const d = new Date(ts);\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +\n `${pad(d.getHours())}:${pad(d.getMinutes())}`\n );\n}\n\n// ---------------------------------------------------------------------------\n// Core search function (streaming to avoid loading 26k lines into memory)\n// ---------------------------------------------------------------------------\n\nexport async function searchHistory(\n query: string,\n maxResults: number\n): Promise<SessionMatch[]> {\n if (!existsSync(HISTORY_FILE)) return [];\n\n const queryLower = query.toLowerCase();\n // Escape regex special chars from query for safety — but we're doing\n // string .includes() not regex, so no escaping needed.\n\n const bySession = new Map<string, SessionMatch>();\n const noSession: SessionMatch = {\n sessionId: null,\n lastMatchTs: 0,\n lastMatchDisplay: \"\",\n project: \"\",\n matchCount: 0,\n };\n\n const rl = createInterface({\n input: createReadStream(HISTORY_FILE, { encoding: \"utf8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n const t = line.trim();\n if (!t) continue;\n\n let entry: HistoryEntry;\n try {\n entry = JSON.parse(t) as HistoryEntry;\n } catch {\n continue;\n }\n\n const display = entry.display ?? \"\";\n if (!display.toLowerCase().includes(queryLower)) continue;\n\n const ts = entry.timestamp ?? 0;\n const project = entry.project ?? \"\";\n const sessionId = entry.sessionId ?? null;\n\n if (!sessionId) {\n // Older entry without sessionId\n if (ts > noSession.lastMatchTs) {\n noSession.lastMatchTs = ts;\n noSession.lastMatchDisplay = display;\n noSession.project = project;\n }\n noSession.matchCount++;\n continue;\n }\n\n const existing = bySession.get(sessionId);\n if (!existing) {\n bySession.set(sessionId, {\n sessionId,\n lastMatchTs: ts,\n lastMatchDisplay: display,\n project,\n matchCount: 1,\n });\n } else {\n existing.matchCount++;\n if (ts > existing.lastMatchTs) {\n existing.lastMatchTs = ts;\n existing.lastMatchDisplay = display;\n existing.project = project;\n }\n }\n }\n\n // Sort by most-recent match descending\n const sorted = [...bySession.values()].sort(\n (a, b) => b.lastMatchTs - a.lastMatchTs\n );\n\n return sorted.slice(0, maxResults);\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdFind(\n query: string,\n opts: { n?: string; json?: boolean }\n): Promise<void> {\n const maxResults = parseInt(opts.n ?? \"20\", 10);\n\n if (!existsSync(HISTORY_FILE)) {\n console.error(err(`~/.claude/history.jsonl not found.`));\n process.exitCode = 1;\n return;\n }\n\n const matches = await searchHistory(query, maxResults);\n\n if (opts.json) {\n console.log(JSON.stringify(matches, null, 2));\n return;\n }\n\n if (matches.length === 0) {\n console.log(warn(`No sessions found matching \"${query}\" in ~/.claude/history.jsonl.`));\n console.log(dim(` Try a shorter or different search term.`));\n return;\n }\n\n console.log(\"\\n\" + header(\"Session Search Results\") + \"\\n\");\n console.log(dim(` Query: \"${query}\" (${matches.length} session(s) with matching prompts)\\n`));\n\n const headers = [\"#\", \"id\", \"when\", \"project\", \"last matching prompt\"];\n const rows = matches.map((m, idx) => {\n const shortId = (m.sessionId ?? \"no-session\").slice(0, 8);\n const when = m.lastMatchTs > 0 ? fmtTs(m.lastMatchTs) : dim(\"—\");\n const project = shortenProject(m.project || dim(\"—\"));\n // Truncate the prompt snippet — strip newlines, trim\n const snippet = m.lastMatchDisplay\n .replace(/\\n+/g, \" \")\n .trim()\n .slice(0, 48);\n const display = snippet.length < m.lastMatchDisplay.replace(/\\n+/g, \" \").trim().length\n ? `\"${snippet}…\"`\n : `\"${snippet}\"`;\n\n return [\n dim(String(idx + 1)),\n chalk.cyan(shortId),\n when,\n dim(project),\n chalk.dim(display),\n ];\n });\n\n console.log(renderTable(headers, rows));\n console.log();\n console.log(dim(\" Resume: \") + chalk.white(\"pai resume <id>\"));\n console.log();\n}\n","/**\n * launch.ts — Launch Claude Code in a directory, in the CURRENT terminal.\n *\n * Shared by the interactive picker (pick.ts). Deliberately does NOT switch\n * iTerm tabs (aibroker_switch): switching jumps the user to a different — and\n * sometimes wrong — terminal, which is confusing. Picking a place should start\n * a session right here, in the chosen directory.\n *\n * Behaviour:\n * resume-or-fresh → if a resumable UUID is given, probe it; on success\n * `claude --resume`, otherwise fall back to a fresh session\n * in the same dir. With no UUID, start fresh.\n *\n * The `claude` child inherits the tty (stdio: \"inherit\"), so the session runs\n * in the terminal that launched `pai`. On exit we print the working directory.\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport { err } from \"../utils.js\";\nimport { printExitDir } from \"./exit-dir.js\";\n\nexport interface ProbeResult {\n ok: boolean;\n reason?: string;\n}\n\n/**\n * Probe whether a session UUID is resumable from `cwd` (5s timeout).\n * Mirrors the probe in goto.ts / main-resolver.ts.\n */\nexport function probeResume(uuid: string, cwd: string): ProbeResult {\n const result = spawnSync(\n \"claude\",\n [\"--resume\", uuid, \"--print\", \"--output-format=json\", \"_\"],\n { cwd, timeout: 5_000, env: process.env, stdio: [\"ignore\", \"ignore\", \"pipe\"] }\n );\n\n if (result.error) return { ok: false, reason: `spawn error: ${result.error.message}` };\n\n const stderr = result.stderr?.toString(\"utf8\") ?? \"\";\n if (\n stderr.toLowerCase().includes(\"no conversation found\") ||\n stderr.toLowerCase().includes(\"session not found\")\n ) {\n return { ok: false, reason: \"No conversation found for this UUID\" };\n }\n if (result.status !== 0) {\n return {\n ok: false,\n reason: `claude exited ${result.status ?? \"signal\"}${stderr ? `: ${stderr.slice(0, 120).trim()}` : \"\"}`,\n };\n }\n return { ok: true };\n}\n\nexport interface LaunchOpts {\n /** If set, try to resume this session before falling back to fresh. */\n resumableUuid?: string;\n /** Skip the resume probe and start a brand-new session in the dir. */\n forceFresh?: boolean;\n /** Print what would happen, then return without launching. */\n dryRun?: boolean;\n}\n\n/**\n * Launch `claude` in `dir` in the current terminal. `name` is used for both the\n * Claude session label (--name) and the /Name slash command (tab/statusline).\n * Never returns on the live path — it exits the process after claude exits.\n */\nexport function launchInDir(dir: string, name: string, opts: LaunchOpts = {}): void {\n let cwd: string;\n try {\n cwd = realpathSync(dir);\n } catch {\n console.error(\n err(\n `Directory does not exist or cannot be resolved:\\n ${dir}\\n` +\n ` The folder may have moved or been deleted.`\n )\n );\n process.exit(1);\n return;\n }\n\n const promptArg = `/Name ${name}\\ngo`;\n const wantResume = !opts.forceFresh && !!opts.resumableUuid;\n\n if (opts.dryRun) {\n if (wantResume) {\n console.log(\"\\n\" + chalk.bold(\"Dry run — would probe then exec (RESUME path):\") + \"\\n\");\n console.log(` cwd: ${chalk.cyan(cwd)}`);\n console.log(` probe: claude --resume ${opts.resumableUuid} --print --output-format=json \"_\"`);\n console.log(` argv: claude --resume ${opts.resumableUuid} --name \"${name}\" \"/Name ${name}\\\\ngo\"`);\n console.log(` fallback: claude --name \"${name}\" \"/Name ${name}\\\\ngo\"`);\n } else {\n console.log(\"\\n\" + chalk.bold(\"Dry run — would exec (FRESH path):\") + \"\\n\");\n console.log(` cwd: ${chalk.cyan(cwd)}`);\n console.log(` argv: claude --name \"${name}\" \"/Name ${name}\\\\ngo\"`);\n }\n console.log();\n return;\n }\n\n const fresh = () => {\n const result = spawnSync(\"claude\", [\"--name\", name, promptArg], {\n cwd,\n stdio: \"inherit\",\n env: process.env,\n });\n if (result.error) {\n console.error(err(`Failed to launch claude: ${result.error.message}`));\n process.exit(1);\n }\n printExitDir(cwd);\n process.exit(result.status ?? 0);\n };\n\n if (wantResume) {\n const probe = probeResume(opts.resumableUuid!, cwd);\n if (probe.ok) {\n const result = spawnSync(\n \"claude\",\n [\"--resume\", opts.resumableUuid!, \"--name\", name, promptArg],\n { cwd, stdio: \"inherit\", env: process.env }\n );\n if (result.error) {\n console.error(err(`Failed to launch claude: ${result.error.message}`));\n process.exit(1);\n }\n printExitDir(cwd);\n process.exit(result.status ?? 0);\n }\n process.stderr.write(\n chalk.yellow(\n `\\n Resume failed for ${opts.resumableUuid!.slice(0, 8)}: ${probe.reason ?? \"unknown error\"}\\n` +\n ` Starting fresh session in same directory.\\n\\n`\n )\n );\n fresh();\n return;\n }\n\n fresh();\n}\n","/**\n * pick.ts — Interactive project/session picker (`pai` with no args).\n *\n * One door for \"find where I worked on something and go there.\" A self-contained\n * modal TUI (no fzf) over a UNIFIED, deduped list of:\n * - PROJECTS (places where knowledge lives — every active project, even cold)\n * - SESSIONS (past conversations — live / resumable / transcript)\n * tagged so the two stay visibly distinct.\n *\n * Two modes (see runSelector):\n * - command (default): ↑↓/j/k move · g go-to-tab · n new · c cd · f finder · q quit\n * - search (after s or /): type to filter, Enter/esc back to command\n * Action keys fire IMMEDIATELY on the highlighted row — letters are commands in\n * command mode, filter text only in the explicit, visible search mode. This is\n * why a single fzf-style \"type to filter\" box couldn't work: there, every letter\n * is filter input and can never also be an action.\n *\n * Design decisions (all driven by real pain):\n * - Filtering matches names, paths, AND folded-in note file/folder names, so\n * typing \"samba\" surfaces a project whose *name* is \"Chenarlier\".\n * - The detail panel shows context for the HIGHLIGHTED row only (recent notes),\n * computed lazily — avoids the ~8s/all-sessions live-prompt fetch.\n * - Actions launch Claude in the CURRENT terminal (g switches the iTerm tab via\n * the screenshot reveal mechanism; n starts fresh; c hands the dir to the\n * pai() shell wrapper to cd; f opens the OS file manager).\n * - Every rendered line is truncated to the window width so nothing wraps\n * (wrapping desyncs the one-row-per-line layout).\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { spawn } from \"node:child_process\";\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\nimport chalk from \"chalk\";\nimport { dim, encodeDir, now } from \"../utils.js\";\nimport { scanSessions, fmtAge, type ScannedSession } from \"../lib/session-scan.js\";\nimport {\n buildDeduped,\n normalizeName,\n type UnifiedSession,\n type RegisteredProject,\n} from \"../lib/dedup-sessions.js\";\nimport { writeFileSync } from \"node:fs\";\nimport { fetchLiveSessions, revealItermSession } from \"../lib/aibroker-client.js\";\nimport { renderDedupedSessions } from \"../lib/dedup-sessions.js\";\nimport { launchInDir } from \"../lib/launch.js\";\nimport { printExitDir } from \"../lib/exit-dir.js\";\n\n// ---------------------------------------------------------------------------\n// Dispatch record — one per selectable row.\n// ---------------------------------------------------------------------------\n\ninterface PickRecord {\n /** 'D' = has a directory to act on; 'L' = live-only, no dir. */\n t: \"D\" | \"L\";\n name: string;\n dir?: string;\n /** Best resumable session UUID for the dir, if any. */\n uuid?: string;\n /** Live AIBroker / iTerm2 session id (present whenever the row is live). */\n live?: string;\n /** Registry slug, when this row maps to a registered project (enables 'd'). */\n slug?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Project registry query (ALL active projects — cold ones included on purpose)\n// ---------------------------------------------------------------------------\n\nfunction getProjects(db: Database, includeArchived: boolean): RegisteredProject[] {\n try {\n const rows = db\n .prepare(`\n SELECT p.slug, p.display_name, p.root_path, p.status,\n COUNT(s.id) AS session_count,\n MAX(s.created_at) AS last_active\n FROM projects p\n LEFT JOIN sessions s ON s.project_id = p.id\n GROUP BY p.id\n ORDER BY last_active DESC NULLS LAST, p.updated_at DESC\n `)\n .all() as RegisteredProject[];\n return includeArchived ? rows : rows.filter((p) => p.status === \"active\");\n } catch {\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Note-keyword folding — cheap topic hints from file/folder names\n// ---------------------------------------------------------------------------\n\nconst KW_SKIP = new Set([\n \"node_modules\", \".git\", \".obsidian\", \"dist\", \"build\", \".cache\", \"vendor\",\n \".next\", \"target\", \"__pycache__\", \".venv\", \"coverage\",\n]);\nconst KW_STOP = new Set([\n \"the\", \"and\", \"for\", \"with\", \"notes\", \"note\", \"readme\", \"index\", \"src\",\n \"untitled\", \"new\", \"draft\", \"md\", \"txt\",\n]);\n\n/** Collect topic tokens from markdown file/folder names, depth-limited. */\nfunction noteKeywords(root: string, maxTokens = 14): string {\n const tokens = new Set<string>();\n const walk = (dir: string, depth: number) => {\n if (depth > 2 || tokens.size >= maxTokens * 3) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (name.startsWith(\".\") || KW_SKIP.has(name)) continue;\n const full = join(dir, name);\n let isDir = false;\n try {\n isDir = statSync(full).isDirectory();\n } catch {\n continue;\n }\n if (isDir) {\n for (const tok of tokenize(name)) tokens.add(tok);\n walk(full, depth + 1);\n } else if (name.toLowerCase().endsWith(\".md\")) {\n for (const tok of tokenize(name)) tokens.add(tok);\n }\n }\n };\n walk(root, 0);\n return Array.from(tokens).slice(0, maxTokens).join(\" \");\n}\n\nfunction tokenize(s: string): string[] {\n return s\n .replace(/\\.md$/i, \"\")\n .replace(/[0-9]+/g, \" \")\n .split(/[^a-zA-ZÀ-ſ]+/)\n .map((t) => t.toLowerCase())\n .filter((t) => t.length >= 3 && !KW_STOP.has(t));\n}\n\n// ---------------------------------------------------------------------------\n// Feed construction\n// ---------------------------------------------------------------------------\n\ninterface FeedLine {\n record: PickRecord;\n /** ANSI-colored display row. */\n display: string;\n /** Lowercased plain text (name · dir · keywords) used for filtering. */\n search: string;\n}\n\nfunction tagFor(status: UnifiedSession[\"status\"]): string {\n switch (status) {\n case \"live\":\n return chalk.green(\"live \");\n case \"resumable\":\n return chalk.cyan(\"resume \");\n case \"project\":\n return chalk.magenta(\"project\");\n default:\n return chalk.dim(\"old \");\n }\n}\n\nfunction pad(s: string, n: number): string {\n // pad based on visible length (strip ANSI)\n const visible = s.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n if (visible.length >= n) return s;\n return s + \" \".repeat(n - visible.length);\n}\n\nfunction shorten(p: string, max = 40): string {\n if (!p) return \"\";\n return p.length <= max ? p : \"…\" + p.slice(-(max - 1));\n}\n\n/**\n * Build the candidate list. buildDeduped merges live+disk+registry into one\n * tagged, deduped catalog. showAll=true so cold/idle projects are NOT filtered\n * out (finding old work is the whole point); archived filtering already handled\n * by getProjects().\n */\nfunction buildFeedFrom(\n allSessions: ScannedSession[],\n projects: RegisteredProject[],\n liveSessions: Awaited<ReturnType<typeof fetchLiveSessions>>,\n allProjects: RegisteredProject[]\n): FeedLine[] {\n const deduped = buildDeduped(liveSessions, allSessions, projects, true);\n\n // Directory-resolution maps span ALL projects (archived included). A live\n // session's only source of a cwd is the registry — an archived project like\n // \"Chenarlier\" still has a real directory, so we must not filter it out here.\n const slugRoot = new Map<string, string>();\n const nameRoot = new Map<string, string>();\n for (const p of allProjects) {\n slugRoot.set(p.slug, p.root_path);\n nameRoot.set(normalizeName(p.display_name ?? p.slug).toLowerCase(), p.root_path);\n }\n\n const bestResumable = new Map<string, ScannedSession>();\n for (const s of allSessions) {\n if (!s.resumable || !s.encodedDir) continue;\n const prev = bestResumable.get(s.encodedDir);\n if (!prev || s.mtime > prev.mtime) bestResumable.set(s.encodedDir, s);\n }\n\n const lines: FeedLine[] = [];\n for (const e of deduped) {\n // Skip nameless rows (e.g. a fresh, unnamed live Claude tab whose name\n // normalizes to \"\"). They're not searchable and just add a blank line.\n if (!e.name.trim()) continue;\n\n // Resolve directory + resumable uuid\n let dir: string | undefined;\n let uuid: string | undefined;\n\n if (e.diskSession) {\n dir =\n e.diskSession.clcDirectory ??\n e.diskSession.registryRootPath ??\n e.diskSession.decodedPath;\n uuid = e.diskSession.resumable ? e.diskSession.uuid : undefined;\n } else if (e.status === \"project\") {\n dir = e.project;\n } else if (e.slug && slugRoot.has(e.slug)) {\n dir = slugRoot.get(e.slug);\n } else {\n // Live-only entry (e.g. a running tab) — recover its directory from the\n // registry by normalized name. This is what makes \"go there\" work for a\n // live session instead of falling back to a tab switch.\n dir = nameRoot.get(e.name.toLowerCase());\n }\n\n if (dir && !uuid) {\n const enc = encodeDir(dir);\n uuid = bestResumable.get(enc)?.uuid;\n }\n\n let record: PickRecord;\n if (dir) {\n // Carry the live id too (when present) so 'g' can switch to the running tab.\n record = { t: \"D\", name: e.name, dir, uuid, live: e.liveSessionId, slug: e.slug };\n } else if (e.liveSessionId) {\n record = { t: \"L\", name: e.name, live: e.liveSessionId, slug: e.slug };\n } else {\n continue; // not actionable\n }\n\n // Keywords for filtering (only when we have a real dir to scan)\n const kw = dir ? noteKeywords(dir) : \"\";\n\n const tag = tagFor(e.status);\n const name = chalk.white(pad(e.name, 22));\n const where = dir ? dim(shorten(dir, 40)) : chalk.green(\"(running in another tab)\");\n const age =\n e.status === \"live\"\n ? chalk.green(\"now\")\n : e.lastActivity > 0\n ? dim(fmtAge(e.lastActivity))\n : dim(\"—\");\n const kwDisp = kw ? dim(\" \" + kw) : \"\";\n const display = `${tag} ${name} ${pad(age, 5)} ${where}${kwDisp}`;\n const search = `${e.name} ${dir ?? \"\"} ${kw}`.toLowerCase();\n\n lines.push({ record, display, search });\n }\n return lines;\n}\n\n// ---------------------------------------------------------------------------\n// Modal selector — our own TUI (no fzf). Two modes:\n// command mode (default): ↑↓/j/k move · g go-to-tab · n new · c cd · s search · q quit\n// search mode (after s or /): type to filter · Enter/esc back to command\n// Action keys fire IMMEDIATELY on the highlighted row — no Enter dance, because\n// in command mode letters are commands, not filter input. Search is a distinct,\n// visible mode so plain letters can be both filter text and actions without\n// colliding.\n// ---------------------------------------------------------------------------\n\ntype PickAction = \"new\" | \"cd\" | \"switch\";\n\ninterface SelectorResult {\n record: PickRecord;\n action: PickAction;\n}\n\nconst stripAnsi = (s: string): string => s.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n\n/** Open a path in the OS file manager (Finder / Explorer / xdg-open). */\nfunction openInFileManager(dir: string): void {\n const cmd =\n process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"explorer\" : \"xdg-open\";\n try {\n spawn(cmd, [dir], { stdio: \"ignore\", detached: true }).unref();\n } catch {\n /* best-effort */\n }\n}\n\n/**\n * Truncate to `max` visible columns, preserving ANSI color codes (which have\n * zero width) and appending an ellipsis when content is cut. This is what keeps\n * rows from wrapping on a narrow window — wrapping would desync the one-row-per-\n * line layout and make the cursor appear to jump.\n */\nfunction truncVisible(s: string, max: number): string {\n if (max <= 1) return \"\";\n let out = \"\";\n let vis = 0;\n let i = 0;\n while (i < s.length) {\n if (s[i] === \"\\x1b\") {\n const m = s.slice(i).match(/^\\x1b\\[[0-9;]*m/);\n if (m) {\n out += m[0];\n i += m[0].length;\n continue;\n }\n }\n if (vis >= max - 1) return out + \"…\\x1b[0m\";\n out += s[i];\n vis++;\n i++;\n }\n return out + \"\\x1b[0m\";\n}\n\nfunction filterLines(lines: FeedLine[], query: string): FeedLine[] {\n const q = query.trim().toLowerCase();\n if (!q) return lines;\n const tokens = q.split(/\\s+/);\n return lines.filter((l) => tokens.every((t) => l.search.includes(t)));\n}\n\n/** Cached per-directory context (recent notes) for the detail panel. */\nconst ctxCache = new Map<string, string[]>();\nfunction contextFor(rec: PickRecord): string[] {\n if (!rec.dir) {\n return rec.live ? [chalk.green(\"live — running in another tab\")] : [dim(\"no directory on record\")];\n }\n const cached = ctxCache.get(rec.dir);\n if (cached) return cached;\n const lines: string[] = [];\n const notes = recentMarkdown(rec.dir, 4);\n if (notes.length) {\n lines.push(dim(\"recent notes:\"));\n for (const n of notes) lines.push(\" \" + dim(n.rel.slice(0, 60)) + \" \" + dim(fmtAge(n.mtime)));\n } else {\n lines.push(dim(\"(no notes here yet)\"));\n }\n ctxCache.set(rec.dir, lines);\n return lines;\n}\n\nasync function runSelector(\n lines: FeedLine[],\n onRemove?: (rec: PickRecord) => boolean\n): Promise<SelectorResult | null> {\n const stdin = process.stdin;\n const out = process.stdout;\n\n return new Promise<SelectorResult | null>((resolve) => {\n let pool = lines; // mutable: 'd' removes rows from here\n let query = \"\";\n let mode: \"command\" | \"search\" = \"command\";\n let filtered = pool;\n let cursor = 0;\n let confirm: FeedLine | null = null; // pending 'd' delete confirmation\n let flash = \"\"; // transient status line\n\n const wasRaw = stdin.isRaw ?? false;\n try {\n stdin.setRawMode?.(true);\n } catch {\n /* not a tty */\n }\n stdin.resume();\n out.write(\"\\x1b[?1049h\\x1b[?25l\"); // alt screen, hide cursor\n\n const teardown = () => {\n out.write(\"\\x1b[?25h\\x1b[?1049l\"); // show cursor, leave alt screen\n stdin.removeListener(\"data\", onData);\n try {\n stdin.setRawMode?.(wasRaw);\n } catch {\n /* ignore */\n }\n stdin.pause();\n };\n const finish = (val: SelectorResult | null) => {\n teardown();\n resolve(val);\n };\n\n const recompute = () => {\n filtered = filterLines(pool, query);\n if (cursor >= filtered.length) cursor = Math.max(0, filtered.length - 1);\n };\n\n const render = () => {\n const rows = out.rows && out.rows > 8 ? out.rows : 24;\n const cols = out.columns && out.columns > 20 ? out.columns : 100;\n const frame: string[] = [];\n\n // Header\n const title = chalk.bold(\" pai\") + dim(\" — find a project or session\");\n frame.push(title);\n if (mode === \"search\") {\n frame.push(\" \" + chalk.cyan(\"search > \") + query + chalk.inverse(\" \"));\n } else {\n frame.push(\n \" \" + dim(`${filtered.length}/${lines.length}`) +\n (query ? dim(` filter: ${query}`) : \"\")\n );\n }\n frame.push(dim(\" \" + \"─\".repeat(Math.min(cols - 4, 80))));\n\n // List window\n const ctxHeight = 6;\n const listHeight = Math.max(3, rows - 4 - ctxHeight - 2);\n let start = 0;\n if (cursor >= listHeight) start = cursor - listHeight + 1;\n const end = Math.min(filtered.length, start + listHeight);\n if (filtered.length === 0) {\n frame.push(\" \" + dim(\"no matches — press s to change the search, esc to quit\"));\n }\n for (let i = start; i < end; i++) {\n const row = filtered[i];\n if (i === cursor) {\n frame.push(chalk.inverse(\" \" + stripAnsi(row.display).slice(0, cols - 2).padEnd(cols - 2)));\n } else {\n frame.push(\" \" + row.display);\n }\n }\n\n // Detail panel for the highlighted row\n frame.push(dim(\" \" + \"─\".repeat(Math.min(cols - 4, 80))));\n const cur = filtered[cursor];\n if (cur) {\n frame.push(\" \" + chalk.bold.white(cur.record.name) + (cur.record.dir ? dim(\" \" + shorten(cur.record.dir, cols - 30)) : \"\"));\n for (const l of contextFor(cur.record).slice(0, ctxHeight - 2)) frame.push(l);\n }\n\n // Footer (keys / confirm / flash)\n frame.push(dim(\" \" + \"─\".repeat(Math.min(cols - 4, 80))));\n if (confirm) {\n frame.push(\n \" \" + chalk.yellow(`Remove \"${confirm.record.name}\" from PAI's list?`) +\n dim(\" files stay on disk \") + chalk.cyan(\"y\") + dim(\"/\") + chalk.cyan(\"N\")\n );\n } else if (mode === \"search\") {\n frame.push(\" \" + dim(\"type to filter · \") + chalk.cyan(\"Enter\") + dim(\" done · \") + chalk.cyan(\"esc\") + dim(\" cancel\"));\n } else {\n const parts: string[] = [];\n if (cur?.record.live) parts.push(chalk.cyan(\"g\") + dim(\" go to tab\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"n\") + dim(\" new\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"c\") + dim(\" cd\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"f\") + dim(\" finder\"));\n if (cur?.record.slug) parts.push(chalk.cyan(\"d\") + dim(\" remove\"));\n parts.push(chalk.cyan(\"s\") + dim(\" search\"));\n parts.push(chalk.cyan(\"↑↓\") + dim(\" move\"));\n parts.push(chalk.cyan(\"q\") + dim(\" quit\"));\n frame.push(\" \" + parts.join(dim(\" · \")));\n if (flash) frame.push(\" \" + dim(flash));\n }\n\n // Paint: home, each line truncated to width (no wrapping) + cleared to\n // EOL, then clear everything below.\n const painted = frame.map((l) => truncVisible(l, cols)).join(\"\\x1b[K\\n\");\n out.write(\"\\x1b[H\" + painted + \"\\x1b[K\\x1b[J\");\n };\n\n const move = (delta: number) => {\n if (filtered.length === 0) return;\n cursor = (cursor + delta + filtered.length) % filtered.length;\n };\n\n const onData = (buf: Buffer) => {\n const s = buf.toString(\"utf8\");\n const cur = filtered[cursor];\n\n // Universal: Ctrl-C quits.\n if (s === \"\\x03\") return finish(null);\n\n // Pending delete confirmation captures the next key.\n if (confirm) {\n const target = confirm;\n confirm = null;\n if (s === \"y\" || s === \"Y\") {\n const okRemoved = onRemove ? onRemove(target.record) : false;\n if (okRemoved) {\n pool = pool.filter((l) => l !== target);\n ctxCache.delete(target.record.dir ?? \"\");\n recompute();\n flash = `Removed \"${target.record.name}\" from PAI's list (files untouched).`;\n } else {\n flash = `Could not remove \"${target.record.name}\".`;\n }\n } else {\n flash = \"\";\n }\n return render();\n }\n\n // Arrows (work in both modes).\n if (s === \"\\x1b[A\") { move(-1); return render(); }\n if (s === \"\\x1b[B\") { move(1); return render(); }\n if (s === \"\\x1b[5~\") { move(-5); return render(); }\n if (s === \"\\x1b[6~\") { move(5); return render(); }\n\n if (mode === \"search\") {\n if (s === \"\\r\" || s === \"\\n\") { mode = \"command\"; return render(); }\n if (s === \"\\x1b\") { mode = \"command\"; return render(); } // esc → back to command, keep filter\n if (s === \"\\x7f\" || s === \"\\b\") { query = query.slice(0, -1); recompute(); return render(); }\n // Printable → append to query.\n if (s.length === 1 && s >= \" \") { query += s; cursor = 0; recompute(); return render(); }\n return; // ignore other escapes\n }\n\n // command mode\n if (s === \"q\" || s === \"\\x1b\") return finish(null);\n if (s === \"s\" || s === \"/\") { flash = \"\"; mode = \"search\"; return render(); }\n if (s === \"j\") { move(1); return render(); }\n if (s === \"k\") { move(-1); return render(); }\n if (!cur) return;\n if (s === \"g\" && cur.record.live) return finish({ record: cur.record, action: \"switch\" });\n if (s === \"n\" && cur.record.dir) return finish({ record: cur.record, action: \"new\" });\n if (s === \"c\" && cur.record.dir) return finish({ record: cur.record, action: \"cd\" });\n // f → open in the file manager, but stay in the picker.\n if (s === \"f\" && cur.record.dir) { openInFileManager(cur.record.dir); return; }\n // d → ask to remove this row from PAI's list (only registered projects).\n if (s === \"d\") {\n if (cur.record.slug) { confirm = cur; flash = \"\"; }\n else flash = \"Only registered projects can be removed from the list.\";\n return render();\n }\n if (s === \"\\r\" || s === \"\\n\") {\n // Enter = smart default: live → go to tab, else → new session.\n if (cur.record.live) return finish({ record: cur.record, action: \"switch\" });\n if (cur.record.dir) return finish({ record: cur.record, action: \"new\" });\n }\n };\n\n stdin.on(\"data\", onData);\n render();\n });\n}\n\n// ---------------------------------------------------------------------------\n// Note discovery — recent markdown for the selector's detail panel.\n// ---------------------------------------------------------------------------\n\ninterface NoteRef {\n rel: string;\n full: string;\n mtime: number;\n}\n\nfunction recentMarkdown(root: string, limit: number): NoteRef[] {\n const found: NoteRef[] = [];\n const walk = (dir: string, depth: number) => {\n if (depth > 2 || found.length >= 200) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (name.startsWith(\".\") || KW_SKIP.has(name)) continue;\n const full = join(dir, name);\n let st;\n try {\n st = statSync(full);\n } catch {\n continue;\n }\n if (st.isDirectory()) walk(full, depth + 1);\n else if (name.toLowerCase().endsWith(\".md\")) {\n found.push({ rel: full.slice(root.length + 1), full, mtime: st.mtimeMs });\n }\n }\n };\n walk(root, 0);\n found.sort((a, b) => b.mtime - a.mtime);\n return found.slice(0, limit);\n}\n\n// ---------------------------------------------------------------------------\n// Main command (`pai` with no args, interactive)\n// ---------------------------------------------------------------------------\n\nexport interface PickOpts {\n all?: boolean;\n dryRun?: boolean;\n}\n\nexport async function cmdPick(db: Database, opts: PickOpts = {}): Promise<void> {\n const interactive = !!process.stdout.isTTY && !!process.stdin.isTTY;\n\n // Fallback to the static listing when we're not on an interactive terminal\n // (e.g. piped output). The modal selector needs a real tty.\n if (!interactive) {\n const allSessions = scanSessions(db, { limit: 500, filter: \"named\" });\n const projects = getProjects(db, opts.all ?? false);\n let live: Awaited<ReturnType<typeof fetchLiveSessions>> = [];\n try {\n live = await fetchLiveSessions();\n } catch {\n /* AIBroker not running */\n }\n const deduped = buildDeduped(live, allSessions, projects, opts.all ?? false);\n renderDedupedSessions(deduped, opts.all ? undefined : 20);\n return;\n }\n\n // Build the feed (with live sessions merged in)\n const allSessions = scanSessions(db, { limit: 500, filter: \"named\" });\n const projects = getProjects(db, opts.all ?? false);\n let live: Awaited<ReturnType<typeof fetchLiveSessions>> = [];\n try {\n live = await fetchLiveSessions();\n } catch {\n /* AIBroker not running — disk + registry still populate the picker */\n }\n\n const allProjects = getProjects(db, true);\n const lines = buildFeedFrom(allSessions, projects, live, allProjects);\n if (lines.length === 0) {\n process.stderr.write(dim(\" No projects or sessions found.\\n\"));\n return;\n }\n\n // 'd' removes a row from PAI's list: archive the project (reversible, FK-safe,\n // files stay on disk). Returns false if the row isn't a registered project.\n const onRemove = (rec: PickRecord): boolean => {\n if (!rec.slug) return false;\n try {\n const ts = now();\n const res = db\n .prepare(\n \"UPDATE projects SET status = 'archived', archived_at = ?, updated_at = ? WHERE slug = ? AND status = 'active'\"\n )\n .run(ts, ts, rec.slug);\n return res.changes > 0;\n } catch {\n return false;\n }\n };\n\n // Modal selector — navigate, then g/n/c act immediately on the highlighted row.\n const sel = await runSelector(lines, onRemove);\n if (!sel) return; // cancelled\n const { record, action } = sel;\n\n // switch → reveal the running iTerm tab (the screenshot mechanism).\n if (action === \"switch\") {\n if (record.live) {\n const r = revealItermSession(record.live);\n if (!r.ok) {\n process.stderr.write(chalk.yellow(` Could not switch: ${r.error ?? \"unknown error\"}\\n`));\n }\n }\n return;\n }\n\n if (!record.dir) {\n process.stderr.write(dim(` No directory on record for \"${record.name}\".\\n`));\n return;\n }\n\n // cd → hand the directory to the `pai()` shell wrapper via PAI_PICK_OUT; a\n // child process can't change the parent shell's cwd itself. Without the\n // wrapper, just print the path so the user can cd manually.\n if (action === \"cd\") {\n const sink = process.env.PAI_PICK_OUT;\n if (sink) {\n try {\n writeFileSync(sink, record.dir, \"utf8\");\n return;\n } catch {\n /* fall through to printing */\n }\n }\n printExitDir(record.dir);\n return;\n }\n\n // new → start a fresh session here, in the current terminal.\n launchInDir(record.dir, record.name, { forceFresh: true, dryRun: opts.dryRun });\n}\n"],"mappings":"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"}