@tekmidian/pai 0.40.0 → 0.41.0
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.
- package/README.md +1 -0
- package/dist/browser-mcp/index.d.mts +1 -0
- package/dist/browser-mcp/index.mjs +352 -0
- package/dist/browser-mcp/index.mjs.map +1 -0
- package/dist/{chain-DhVHVnmT.mjs → chain-zMjCDW-R.mjs} +40 -8
- package/dist/chain-zMjCDW-R.mjs.map +1 -0
- package/dist/cli/index.mjs +3 -3
- package/dist/cli/program.mjs +3 -3
- package/dist/daemon-mcp/index.mjs +12 -2
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{fallback-CdXv-Np0.mjs → fallback-CgQ_x4I7.mjs} +2 -2
- package/dist/{fallback-CdXv-Np0.mjs.map → fallback-CgQ_x4I7.mjs.map} +1 -1
- package/dist/hooks/worker-status-line.mjs.map +2 -2
- package/dist/{planner-Cm3g6fWH.mjs → planner-DsmMRIZR.mjs} +2 -2
- package/dist/{planner-Cm3g6fWH.mjs.map → planner-DsmMRIZR.mjs.map} +1 -1
- package/dist/{program-AR0qlYRg.mjs → program-Bed-Pd-8.mjs} +67 -55
- package/dist/program-Bed-Pd-8.mjs.map +1 -0
- package/dist/skills/Worker/SKILL.md +10 -0
- package/docs/commands/README.md +2 -2
- package/docs/commands/mcp.md +4 -4
- package/docs/commands/worker.md +3 -0
- package/docs/provider-independence.md +8 -21
- package/package.json +1 -1
- package/statusline-command.sh +65 -73
- package/dist/chain-DhVHVnmT.mjs.map +0 -1
- package/dist/program-AR0qlYRg.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"program-AR0qlYRg.mjs","names":["dim","ok","bold","getProject","upsertTag","dim","warn","bold","ok","cmdList","cmdInfo","cmdTag","upsertTag","getProject","ok","bold","dim","warn","norm","cmdHealth","bold","dim","warn","ok","bold","dim","warn","ok","dim","bold","ok","bold","dim","warn","ok","warn","dim","ok","cmdMigrate","warn","dim","slugify","ok","bold","ok","dim","warn","ok","dim","warn","cmdStats","bold","warn","dim","ok","dim","ok","bold","ok","dim","bold","tierColor","warn","dim","bold","warn","dim","bold","CONFIG_FILE","ok","dim","CONFIG_FILE","warn","bold","dim","ok","cmdInstall","warn","dim","ok","cmdStatus","bold","HOME","cmdStatus","bold","ok","dim","warn","HOME","REGISTRY_DB","CONFIG_FILE","BACKUPS_DIR","DOCKER_CONTAINER","PG_DATABASE","PG_USER","dim","ok","warn","bold","dim","bold","ok","warn","line","CONFIG_FILE","getTemplatesDir","getTemplatesDir","getTemplatesDir","getTemplatesDir","useSymlinks","CONFIG_FILE","normalize","normalizeName","line","CONFIG_FILE","findClaudeNotesDir","findClaudeNotesDir","CONFIG_FILE","dim","ok","warn","cmdStatus","bold","dim","warn","bold","ok","dim","bold","ok","warn","dim","warn","bold","dim","warn","bold","dim","warn","bold","dim","warn","bold","ipcCall","fmtTs","dim","trunc","cmdList","warn","bold","cmdStats","fmtTs","dim","statusColor","warn","bold","dim","ok","warn","run","makeClient","warn","bold","ok","dim","run","STATE_FILE","loadState","saveState","CONFIG_FILE","loadState","STATE_FILE","bold","dim","warn","ok","getPool","dim","shorten","bold","warn","ok","cmdQuery","cmdList","dim","bold","dim","tokenize","getProject","toTitleCase","getProject","warn","dim","bold","toTitleCase","ok","findNotesDir","dim","bold","ok","dim","warn","fail","parseIntArg","dim","ok","fail","ok","dim","sleep","dim","dim","bold","warn","ok","dim","ok","findNotesDir","dim","warn","ok","dim","ok","warn","warn","dim","normalizeName","dim","warn","dim","ok"],"sources":["../src/registry/registrable.ts","../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/relocate.ts","../src/cli/commands/project/health.ts","../src/registry/merge.ts","../src/cli/commands/project/merge.ts","../src/cli/commands/project/here.ts","../src/cli/commands/project/unregister.ts","../src/cli/commands/project/projects-index.ts","../src/cli/commands/registry/migrate.ts","../src/cli/commands/registry/dedupe.ts","../src/cli/commands/registry/reconnect.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/sources-cmd.ts","../src/cli/commands/memory/index.ts","../src/identity/index.ts","../src/cli/commands/identity.ts","../src/config/claude-json.ts","../src/cli/commands/mcp.ts","../src/cli/commands/daemon-status.ts","../src/cli/commands/daemon.ts","../src/cli/commands/backup.ts","../src/cli/commands/restore.ts","../src/cli/lib/exit.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/workers/install.ts","../src/cli/commands/setup/steps/17-workers.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/tasks/archive.ts","../src/tasks/projects.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/session/slug-generator.ts","../src/cli/commands/session/helpers.ts","../src/cli/commands/session/commands.ts","../src/cli/commands/session/checkpoint.ts","../src/cli/commands/session/pause.ts","../src/session/autosave.ts","../src/cli/commands/session/handover.ts","../src/hooks/ts/lib/context-fill.ts","../src/hooks/ts/lib/worker-session.ts","../src/session/context-handover-trigger.ts","../src/cli/commands/session/autosave.ts","../src/cli/commands/session/recent.ts","../src/cli/commands/session/goto.ts","../src/cli/commands/session/restore.ts","../src/cli/commands/session/index.ts","../src/workers/agents.ts","../src/cli/commands/worker/providers.ts","../src/cli/commands/worker/model.ts","../src/workers/wait.ts","../src/cli/commands/worker/index.ts","../src/cli/commands/session-cleanup/types.ts","../src/cli/commands/session-cleanup/rename.ts","../src/cli/commands/session-cleanup/scanner.ts","../src/cli/commands/session-cleanup/executor.ts","../src/cli/commands/session-cleanup/index.ts","../src/cli/commands/session/end.ts","../src/cli/commands/session/pause-all.ts","../src/cli/commands/session/clear-names.ts","../src/cli/commands/find.ts","../src/cli/commands/pick.ts","../src/cli/program.ts"],"sourcesContent":["/**\n * registrable.ts — directories that must never become registered projects.\n *\n * A project is a durable thing. The registry, however, has been accepting\n * whatever directory a session happened to start in, and some of those\n * directories are disposable by construction. Measured on the real registry,\n * 2026-08-04:\n *\n * 08 - Others/MDF/Infrastruktur/.claude/worktrees/cool-haibt 1 session\n * 08 - Others/MDF/Infrastruktur/.claude/worktrees/strange-haibt 7 sessions\n * /private/tmp/ops-webui dead\n * /private/tmp/claude-501/-Users-…-AIBroker/aae854c6-… dead\n *\n * The worktrees are agent isolation directories, created to be removed. The temp\n * paths are exactly what their name says. Registering them attributes session\n * history to a location with no future, and lets `pai <name>` route someone into\n * a directory a cleanup can delete underneath them.\n *\n * So this refuses at the point of registration rather than reclassifying\n * afterwards. It is a guard, not a policy about what health should call things —\n * it stops the set growing while the vocabulary question (dead vs duplicate vs\n * misnamed vs ephemeral) is decided separately.\n *\n * Found by the AIBroker session while we were dividing up the dead-path work.\n */\n\nimport { sep } from \"node:path\";\n\n/** Path fragments that mark a location as disposable, with why. */\nconst EPHEMERAL: ReadonlyArray<{ fragment: string; because: string }> = [\n {\n // Agent worktrees. `.claude/worktrees/<name>` is created for isolation during\n // one task and removed afterwards.\n fragment: `${sep}.claude${sep}worktrees${sep}`,\n because: \"a git worktree created for agent isolation — it is meant to be removed\",\n },\n {\n fragment: `${sep}private${sep}tmp${sep}`,\n because: \"a system temp directory\",\n },\n {\n fragment: `${sep}var${sep}folders${sep}`,\n because: \"a macOS per-user temp directory\",\n },\n];\n\n/**\n * Why this path cannot be a project, or undefined if it can.\n *\n * Returns the reason rather than a boolean so the caller can tell the user which\n * rule caught them — \"refused\" without a reason invites someone to work around it\n * rather than move their project somewhere durable.\n */\nexport function unregistrableReason(rootPath: string): string | undefined {\n // Compare with trailing separators so a fragment cannot match a partial\n // directory name: `/tmp/` must not match `/tmpdir/`.\n const padded = rootPath.endsWith(sep) ? rootPath : rootPath + sep;\n\n for (const { fragment, because } of EPHEMERAL) {\n if (padded.includes(fragment)) return because;\n }\n\n // A bare `/tmp/...` too. Kept separate from the list above because on macOS\n // /tmp is a symlink to /private/tmp, so both spellings reach the same place and\n // only one of them contains \"private\".\n if (padded.startsWith(`${sep}tmp${sep}`)) return \"a system temp directory\";\n\n return undefined;\n}\n","/**\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\";\nimport { unregistrableReason } from \"../registry/registrable.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 // ---- Refuse disposable locations ----\n //\n // Same guard as `pai project add`. Promoting a session INTO a worktree or a\n // temp directory would create a project whose home is designed to be deleted.\n const ephemeral = unregistrableReason(targetPath);\n if (ephemeral) {\n console.error(err(`Refusing to promote into ${targetPath}`));\n console.error(dim(` That is ${ephemeral}.`));\n console.error(dim(` Pick a durable location for the new project.`));\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\";\n\n/**\n * Find a project by slug or alias, case-insensitively.\n *\n * Slugs are conventionally lowercase, so exact matching went unnoticed until a\n * project was deliberately named `Solar` — after which `pai project info solar`\n * reported \"Project not found\" for a project sitting right there. Nobody types\n * a name back with its capitalisation intact, and a lookup that demands it will\n * be wrong more often than the user is.\n *\n * Exact match is still tried first: where two projects differ only by case, the\n * one asked for by its exact name wins rather than whichever the database\n * returns first.\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 if (alias) return alias;\n\n const insensitive = db\n .prepare(\"SELECT * FROM projects WHERE lower(slug) = lower(?)\")\n .get(slug) as ProjectRow | undefined;\n if (insensitive) return insensitive;\n\n return db\n .prepare(\n `SELECT p.* FROM projects p\n JOIN aliases a ON a.project_id = p.id\n WHERE lower(a.alias) = lower(?)`\n )\n .get(slug) as ProjectRow | undefined;\n}\n\nexport function requireProject(db: Database, slug: string): ProjectRow {\n const project = getProject(db, slug);\n if (!project) {\n // Throw rather than process.exit(1): it propagates to the top-level\n // catch in cli/index.ts, which prints the message and sets\n // process.exitCode, letting the process exit naturally so piped\n // stdout/stderr is flushed (process.exit() does not guarantee that).\n throw new Error(`Project not found: ${slug}`);\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 { unregistrableReason } from \"../../../registry/registrable.js\";\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.exitCode = 1;\n return;\n }\n\n // Disposable directories must not become projects. Two agent worktrees and two\n // /private/tmp paths got in this way and between them carried 8 sessions\n // attributed to locations with no future — and `pai <name>` will happily route\n // someone into a worktree a cleanup can delete underneath them.\n const ephemeral = unregistrableReason(rootPath);\n if (ephemeral) {\n console.error(err(`Refusing to register ${shortenPath(rootPath, 60)}`));\n console.error(dim(` That is ${ephemeral}.`));\n console.error(dim(` A project needs a durable location — move it, then register.`));\n process.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 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.exitCode = 1;\n return;\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 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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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 * relocate.ts — find a registered project whose ancestor directory was renamed.\n *\n * The registry stores absolute root paths. Rename any directory ABOVE a project\n * and every project underneath it goes missing at once — `existsSync` fails, and\n * the health check reported them as dead and offered to archive them. Renaming\n * `Ideaverse` to `🧠 Ideaverse` orphaned a whole subtree that way: 32 entries, all\n * still on disk, none of them findable.\n *\n * The old suggestion logic could not see this. It took the project's BASENAME and\n * looked for it in four hardcoded directories (`~/dev`, `~/dev/ai`, `~/Desktop`,\n * `~/Projects`), so it could only recognise \"the leaf moved into a place I already\n * know about\". A renamed ancestor leaves the leaf exactly where it was.\n *\n * So walk down from the root while the path still exists — that lands on the\n * deepest surviving ancestor, which is precisely where the rename happened — then\n * match the remaining segments by NORMALISED name. Normalising strips the emoji,\n * the space and the case, so `🧠 Ideaverse` and `Ideaverse` both reduce to\n * \"ideaverse\". The general case is \"an ancestor was renamed decoratively\", which\n * is the whole class of failure rather than this one instance of it.\n *\n * Algorithm and the measurements behind it come from the AIBroker session, which\n * probed it and handed it over rather than implementing it in a file that was not\n * theirs.\n */\n\nimport { existsSync, readdirSync, statSync, realpathSync } from \"node:fs\";\nimport { join, sep, basename } from \"node:path\";\nimport { homedir } from \"node:os\";\n\n/**\n * A directory name reduced to what a rename is unlikely to have changed.\n *\n * NFC first because macOS hands back decomposed Unicode: an \"ä\" typed in one\n * place and read from a directory listing in another are different byte\n * sequences, and comparing them raw fails for reasons no one can see. Then case\n * and every non-alphanumeric are dropped, which is what makes `🧠 Ideaverse`\n * match `Ideaverse` — and, unavoidably, what makes `my-project` match\n * `my project`. That looseness is the point, and the uniqueness rule below is\n * what keeps it safe.\n */\nexport function norm(s: string): string {\n return s\n .normalize(\"NFC\")\n .toLowerCase()\n .replace(/[^\\p{Letter}\\p{Number}]+/gu, \"\");\n}\n\n/** Hidden by Unix convention — tool state rather than a project directory. */\nfunction isHidden(name: string): boolean {\n return name.startsWith(\".\");\n}\n\n/**\n * Drop a leading ordering prefix: `20 - Webseiten` -> `Webseiten`.\n *\n * This vault numbers directories to force sort order — `04 - Ablage`,\n * `08 - Others`, `01 - Base Setup`, `20 - Webseiten` — and adding or removing that\n * prefix is a rename of exactly the same kind as adding an emoji. `norm()` alone\n * cannot see it, because the digits survive normalisation: \"webseiten\" against\n * \"20webseiten\".\n *\n * Found because `MDF.md` links to `Infrastruktur/20 - Webseiten` while the\n * registry has a dead entry for plain `Infrastruktur/Webseiten`. The note files\n * knew where it went.\n *\n * Deliberately NOT a general suffix match, and the reason is worth spelling out\n * because \"why not just match on the suffix\" is the obvious next question.\n *\n * `MDF/Infrastruktur/` is wall-to-wall numbered: `00 - Migration Analysis`,\n * `01 - Base Setup`, `02 - Storage Setup`, `03 - Network Setup`, and so on. A\n * suffix rule on a wanted `Setup` hits eight of them at once — so the uniqueness\n * rule below would veto it, and the danger would never show itself. The one that\n * bites is a wanted `Analysis` against `00 - Migration Analysis`: a single hit,\n * therefore silently relocated, therefore wrong, and uniqueness cannot save you\n * because there is nothing ambiguous about it.\n *\n * That is a failure mode this file's own safety rule would have HIDDEN rather than\n * caught. Only a leading run of digits with optional separator comes off.\n */\nfunction stripOrderingPrefix(name: string): string {\n return name.replace(/^\\d+\\s*[-._)]?\\s*/, \"\");\n}\n\n/**\n * The names under which a directory can be recognised.\n *\n * Both spellings are offered from both sides, so the prefix can have been added\n * OR removed since the path was registered.\n */\nfunction matchKeys(name: string): Set<string> {\n const keys = new Set<string>();\n const bare = norm(name);\n if (bare) keys.add(bare);\n const stripped = norm(stripOrderingPrefix(name));\n if (stripped) keys.add(stripped);\n return keys;\n}\n\nfunction shareAKey(a: Set<string>, b: Set<string>): boolean {\n for (const k of a) if (b.has(k)) return true;\n return false;\n}\n\n/**\n * Resolve symlinks so two spellings of one directory compare equal.\n *\n * Falls back to the input when it cannot resolve — a path that does not exist has\n * no real path, and for the duplicate check below \"unresolvable\" must not\n * accidentally equal some other unresolvable path.\n */\nfunction realOrSelf(path: string): string {\n try {\n return realpathSync(path);\n } catch {\n return path;\n }\n}\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Where a project went when a directory above it was renamed.\n *\n * Returns undefined rather than a guess whenever it cannot be certain — see the\n * uniqueness rule inside. undefined means \"still dead as far as this can tell\",\n * which leaves the entry exactly as it was.\n */\nexport function relocateRenamedAncestor(rootPath: string): string | undefined {\n // Nothing to relocate. Worth an explicit early return: a caller that asks\n // about a live path must not be told it \"moved\" to itself.\n if (existsSync(rootPath)) return undefined;\n\n const segments = rootPath.split(sep).filter((s) => s.length > 0);\n if (segments.length === 0) return undefined;\n\n // Walk DOWN while the path still exists. What survives is the deepest existing\n // ancestor, and the first segment that does not is the renamed one.\n const absolute = rootPath.startsWith(sep);\n let current = absolute ? sep : \"\";\n let i = 0;\n for (; i < segments.length; i++) {\n const next = join(current, segments[i]!);\n if (!existsSync(next)) break;\n current = next;\n }\n\n // Every segment resolved, yet the whole path does not exist. Should be\n // unreachable, and returning undefined is the safe reading of a contradiction.\n if (i === segments.length) return undefined;\n\n for (; i < segments.length; i++) {\n const wanted = matchKeys(segments[i]!);\n if (wanted.size === 0) return undefined; // punctuation only — nothing to match on\n\n let children: string[];\n try {\n children = readdirSync(current);\n } catch {\n return undefined; // unreadable directory — cannot claim to know\n }\n\n const hits = children.filter(\n (c) =>\n isDir(join(current, c)) &&\n shareAKey(matchKeys(c), wanted) &&\n // A leading dot is not decoration, so it must not normalise away.\n //\n // Caught on the first real run: `~/PAI` was \"relocated\" to `~/.pai`,\n // because norm() strips the dot and both reduce to \"pai\". `~/.pai` is\n // PAI's own registry and state directory — not a project — and the\n // uniqueness rule below could not save us, since there was exactly one\n // match. A hidden sibling is a category difference (`.git`, `.claude`,\n // `.pai` are tool state), not a rename of the visible one.\n isHidden(c) === isHidden(segments[i]!)\n );\n\n // THE SAFETY PROPERTY. Two directories that normalise the same means this\n // cannot tell which one the project was, and repointing a registered slug at\n // the wrong project would silently attach it to someone else's notes — worse\n // than leaving the entry dead, because it looks fixed. Ambiguity is not a\n // relocation.\n if (hits.length !== 1) return undefined;\n\n current = join(current, hits[0]!);\n }\n\n return current;\n}\n\n/**\n * The pre-existing guess: the project's leaf name in one of a few usual places.\n *\n * Kept, and kept SECOND, because it answers a different question — \"the project\n * itself was moved somewhere I know about\" rather than \"an ancestor was renamed\".\n * It is the weaker of the two: matching on basename alone can point at an\n * unrelated directory that happens to share a name, so it only gets to answer\n * when the ancestor walk has declined.\n */\nfunction suggestByBasename(rootPath: string): string | undefined {\n const name = basename(rootPath);\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\n/**\n * Where a missing project probably is, or undefined.\n *\n * Feeds the health check's `suggestedPath ? \"stale\" : \"dead\"` classification, so\n * an answer here moves an entry out of the dead list and into the one `--fix`\n * repairs.\n *\n * `otherRoots` is every OTHER registered project's root path, and it is what\n * stops a repair from becoming a duplicate. Caught by the AIBroker session on the\n * first version of this: `~/PAI` was \"recovered\" to `~/dev/ai/PAI`, which is a\n * second spelling of `~/Daten/Cloud/Development/ai/PAI` — a path an ACTIVE project\n * already owns. Two registry entries for one directory is the exact mess that was\n * merged out of this registry earlier the same day.\n *\n * The comparison must be by realpath, not string. String equality is precisely\n * what missed it: the two paths share not one character after `/Users/owner/`.\n */\nexport function suggestMovedPath(\n rootPath: string,\n otherRoots: readonly string[] = []\n): string | undefined {\n const candidate = relocateRenamedAncestor(rootPath) ?? suggestByBasename(rootPath);\n if (!candidate) return undefined;\n\n const taken = new Set(otherRoots.map(realOrSelf));\n if (taken.has(realOrSelf(candidate))) return undefined;\n\n return candidate;\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, realpathSync } 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, now, renderTable, encodeDir } from \"../../utils.js\";\nimport type {\n HealthRow,\n HealthCategory,\n HealthReason,\n ProjectHealth,\n ProjectRow,\n} from \"./types.js\";\nimport { suggestMovedPath } from \"./relocate.js\";\nimport { unregistrableReason } from \"../../../registry/registrable.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\n/*\n * suggestMovedPath now lives in relocate.ts, which walks to the deepest surviving\n * ancestor and re-matches the rest by normalised name.\n *\n * What was here looked for the project's BASENAME in four hardcoded directories,\n * so it could only recognise \"the leaf moved somewhere I already know about\". It\n * could not see the failure that actually happened: renaming `Ideaverse` to\n * `🧠 Ideaverse` left 32 registered projects exactly where they were and made\n * every one of them unreachable, and this function reported them dead and offered\n * to archive them.\n *\n * The basename guess is kept, second, since it answers the other question.\n */\n\n/**\n * Why a row is unhealthy, and what to do about it.\n *\n * `category` said only dead / stale / active, and \"archive\" was offered as the\n * remedy for everything. Four different situations were collapsed into that, and\n * archiving is right for exactly one of them:\n *\n * EPHEMERAL the path is a worktree or a temp dir — it should never have been\n * registered. Checked FIRST, deliberately: a temp path whose\n * directory has also vanished is both ephemeral and dead, and\n * \"should never have been registered\" is the stronger and more\n * actionable statement. Without a stated precedence the same row\n * gets different labels depending on evaluation order.\n * DUPLICATE the path is gone and another project owns where it went. Its\n * sessions are the only thing of value on it, so the remedy is\n * merge — archiving strands them.\n * MISNAMED the path EXISTS and another project owns a subtree of it, under a\n * slug that has nothing to do with it. `pferde` on `08 - Others/MDF`.\n * health never reported this at all, because existsSync says yes.\n * DEAD the path is gone and nothing claims it. Archive is correct here.\n *\n * The action must never name a command that destroys a row holding sessions —\n * a wrong command in an action field reads as vetted.\n */\nfunction diagnose(\n project: HealthRow,\n pathExists: boolean,\n others: HealthRow[]\n): { reason?: HealthReason; owner?: string; action?: string } {\n const ephemeral = unregistrableReason(project.root_path);\n if (ephemeral) {\n return {\n reason: \"ephemeral\",\n action:\n project.session_count > 0\n ? `holds ${project.session_count} session(s) — pai project merge ${project.slug} <durable-project> --execute, then unregister`\n : `pai project unregister ${project.slug} --execute (${ephemeral})`,\n };\n }\n\n if (pathExists) {\n // A live directory that another project owns a piece of, under an unrelated\n // slug. Only reported when the other project sits BENEATH this one, which is\n // the shape actually observed; two unrelated roots are not each other's problem.\n const nested = others.find(\n (o) =>\n o.status === \"active\" &&\n o.root_path.startsWith(project.root_path + \"/\") &&\n existsSync(o.root_path)\n );\n if (nested && project.status !== \"active\") {\n return {\n reason: \"misnamed\",\n owner: nested.slug,\n action: `live directory, but ${nested.slug} owns a subtree of it — rename, or pai project merge ${project.slug} ${nested.slug}`,\n };\n }\n return {};\n }\n\n // Path is gone. Does an active project already own where this one would go?\n const suggestion = suggestMovedPath(project.root_path, []);\n if (suggestion) {\n const owner = others.find(\n (o) => o.root_path === suggestion || realpathEq(o.root_path, suggestion)\n );\n if (owner) {\n return {\n reason: \"duplicate\",\n owner: owner.slug,\n action:\n project.session_count > 0\n ? `pai project merge ${project.slug} ${owner.slug} (moves ${project.session_count} session(s))`\n : `pai project merge ${project.slug} ${owner.slug} (no sessions — a plain drop)`,\n };\n }\n }\n\n return {\n reason: \"dead\",\n action:\n project.session_count > 0\n ? `holds ${project.session_count} session(s) — merge before archiving, or they become unreachable`\n : `pai project archive ${project.slug}`,\n };\n}\n\n/** Same directory under two spellings — symlinks, not string equality. */\nfunction realpathEq(a: string, b: string): boolean {\n try {\n return realpathSync(a) === realpathSync(b);\n } catch {\n return false;\n }\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 const others = rows.filter((r) => r.id !== project.id);\n\n let category: HealthCategory;\n let suggestedPath: string | undefined;\n\n if (pathExists) {\n category = \"active\";\n } else {\n // Every OTHER project's root, so a repair cannot point this entry at a\n // directory another project already owns.\n suggestedPath = suggestMovedPath(\n project.root_path,\n others.map((r) => r.root_path)\n );\n category = suggestedPath ? \"stale\" : \"dead\";\n }\n\n const { reason, owner, action } = diagnose(project, pathExists, others);\n\n return {\n project,\n category,\n suggestedPath,\n claudeNotesExists: orphaned.length > 0,\n orphanedNotesDirs: orphaned,\n reason,\n owner,\n action,\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 // Added fields, not renamed ones: `health` keeps its old values so any\n // existing consumer keeps working.\n reason: r.reason ?? null,\n owner: r.owner ?? null,\n action: r.action ?? null,\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(\" Unreachable projects (path missing):\"));\n for (const r of dead) {\n const label = r.reason && r.reason !== \"dead\" ? warn(` [${r.reason}]`) : \"\";\n console.log(` ${bold(r.project.slug)} ${dim(r.project.root_path)}${label}`);\n if (r.claudeNotesExists) {\n console.log(chalk.yellow(` Notes: ${r.orphanedNotesDirs.join(\", \")}`));\n }\n // Only archive rows that are genuinely dead. A duplicate's sessions are the\n // only thing of value on it, and an ephemeral row wants unregistering, not\n // filing away — auto-archiving either was the bug behind this whole item.\n if (r.project.session_count === 0 && opts.fix && r.reason === \"dead\") {\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, nothing claims it)\"));\n } else if (r.action) {\n console.log(dim(` Do: ${r.action}`));\n }\n }\n console.log();\n }\n\n // \"active\" is the one word in this output that means something else elsewhere,\n // so say which thing it is.\n //\n // This command's \"active\" is \"the path exists on disk\". The registry's `status`\n // column also says active, meaning \"not archived\". They are different\n // predicates over the same rows and they disagree by a lot: measured\n // 2026-08-04, 124 paths present against 99 registry-active, because 29 ARCHIVED\n // projects still have their directory. A careful reader took the 124 as a\n // registry figure today and drew a wrong conclusion from it, which is a fair\n // reading of a line that just said \"124 active\".\n const archivedButPresent = results.filter(\n (r) => r.category === \"active\" && r.project.status !== \"active\"\n ).length;\n\n console.log(\n dim(\n ` ${rows.length} total: ${active.length} with the path present, ` +\n `${stale.length} stale, ${dead.length} dead`\n )\n );\n if (archivedButPresent > 0) {\n console.log(\n dim(\n ` ${archivedButPresent} of those ${active.length} are archived in the registry — ` +\n `\"present\" here is about the directory, not the registry status.`\n )\n );\n }\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 * merge.ts — fold one registered project into another without losing anything.\n *\n * The registry accumulates rows that describe the same work twice: a project\n * registered under two spellings of one directory, a vault-notes twin of a code\n * project, a slug that collided and got a `-1`. Measured 2026-08-04 there were\n * six such rows, and between them they carried ten sessions.\n *\n * Those sessions are the only thing of value on the losing row, which is why\n * `archive` is the wrong remedy and a plain DELETE is worse. `pai project health`\n * suggested archiving for every unhealthy row, and archiving a duplicate leaves\n * the sessions attached to a row nobody will look at again.\n *\n * FIVE tables reference a project, and `PRAGMA foreign_keys` is 0 on this\n * database — so deleting a project row does not fail, it silently orphans rows in\n * sessions, project_tags, aliases, compaction_log and links. Anything that merges\n * by hand will miss at least one of them. Hence one function.\n *\n * Two constraints shape the implementation:\n *\n * sessions UNIQUE (project_id, number) — incoming sessions must be renumbered,\n * or the insert collides with the\n * target's own numbering\n * links UNIQUE (session_id, target_project_id)\n * project_tags PRIMARY KEY (project_id, tag_id)\n * — both can already hold the row we are\n * about to point at the target\n */\n\nimport type { Database } from \"better-sqlite3\";\n\nexport interface MergePlan {\n fromId: number;\n fromSlug: string;\n intoId: number;\n intoSlug: string;\n /** Sessions to move, with the numbers they will be given. */\n sessions: { id: number; from: number; to: number }[];\n tags: number;\n aliases: number;\n compactions: number;\n links: number;\n /** The losing slug is kept as an alias, so `pai <old-name>` still resolves. */\n aliasToAdd?: string;\n}\n\nexport class MergeError extends Error {}\n\n/**\n * What a merge would do, without doing it.\n *\n * Built as a plan first because the session renumbering is the part a reader will\n * not predict, and printing \"0001 -> 0016\" is the difference between a command\n * that can be trusted and one that has to be taken on faith.\n */\nexport function planMerge(db: Database, fromSlug: string, intoSlug: string): MergePlan {\n if (fromSlug === intoSlug) {\n throw new MergeError(`Cannot merge ${fromSlug} into itself.`);\n }\n\n const from = db\n .prepare(\"SELECT id, slug FROM projects WHERE slug = ?\")\n .get(fromSlug) as { id: number; slug: string } | undefined;\n const into = db\n .prepare(\"SELECT id, slug FROM projects WHERE slug = ?\")\n .get(intoSlug) as { id: number; slug: string } | undefined;\n\n if (!from) throw new MergeError(`No project with slug \"${fromSlug}\".`);\n if (!into) throw new MergeError(`No project with slug \"${intoSlug}\".`);\n\n // Where the target's numbering currently ends. Incoming sessions continue from\n // there rather than keeping their own numbers, which would collide.\n const maxRow = db\n .prepare(\"SELECT COALESCE(MAX(number), 0) AS n FROM sessions WHERE project_id = ?\")\n .get(into.id) as { n: number };\n let next = maxRow.n;\n\n const incoming = db\n .prepare(\"SELECT id, number FROM sessions WHERE project_id = ? ORDER BY number ASC\")\n .all(from.id) as { id: number; number: number }[];\n\n const sessions = incoming.map((s) => ({ id: s.id, from: s.number, to: ++next }));\n\n const count = (sql: string, ...args: unknown[]): number =>\n (db.prepare(sql).get(...args) as { n: number }).n;\n\n // The losing slug becomes an alias of the winner, so anything that resolved by\n // that name keeps working — including a human who remembers the old name.\n const aliasTaken =\n count(\"SELECT COUNT(*) AS n FROM aliases WHERE alias = ?\", from.slug) > 0 ||\n count(\"SELECT COUNT(*) AS n FROM projects WHERE slug = ?\", from.slug) > 1;\n\n return {\n fromId: from.id,\n fromSlug: from.slug,\n intoId: into.id,\n intoSlug: into.slug,\n sessions,\n tags: count(\"SELECT COUNT(*) AS n FROM project_tags WHERE project_id = ?\", from.id),\n aliases: count(\"SELECT COUNT(*) AS n FROM aliases WHERE project_id = ?\", from.id),\n compactions: count(\n \"SELECT COUNT(*) AS n FROM compaction_log WHERE project_id = ?\",\n from.id\n ),\n links: count(\"SELECT COUNT(*) AS n FROM links WHERE target_project_id = ?\", from.id),\n aliasToAdd: aliasTaken ? undefined : from.slug,\n };\n}\n\n/**\n * Apply a plan. One transaction: either the whole row is folded in or nothing is.\n *\n * A half-merged project is the worst outcome available here — sessions moved but\n * the row still present, or the row gone and its tags orphaned — and with foreign\n * keys off nothing would complain.\n */\nexport function applyMerge(db: Database, plan: MergePlan): void {\n const run = db.transaction(() => {\n // Sessions, one at a time because each gets its own new number.\n const move = db.prepare(\n \"UPDATE sessions SET project_id = ?, number = ? WHERE id = ?\"\n );\n for (const s of plan.sessions) move.run(plan.intoId, s.to, s.id);\n\n // Tags: OR IGNORE because (project_id, tag_id) is the primary key and the\n // target may already carry the same tag.\n db.prepare(\n `INSERT OR IGNORE INTO project_tags (project_id, tag_id)\n SELECT ?, tag_id FROM project_tags WHERE project_id = ?`\n ).run(plan.intoId, plan.fromId);\n db.prepare(\"DELETE FROM project_tags WHERE project_id = ?\").run(plan.fromId);\n\n // Existing aliases of the loser now point at the winner.\n db.prepare(\"UPDATE aliases SET project_id = ? WHERE project_id = ?\").run(\n plan.intoId,\n plan.fromId\n );\n\n db.prepare(\"UPDATE compaction_log SET project_id = ? WHERE project_id = ?\").run(\n plan.intoId,\n plan.fromId\n );\n\n // Links: same collision shape as tags, plus one extra case — a session that\n // linked to the loser may now be linking to its own project, which is not a\n // relationship worth keeping.\n db.prepare(\n `UPDATE OR IGNORE links SET target_project_id = ? WHERE target_project_id = ?`\n ).run(plan.intoId, plan.fromId);\n db.prepare(\"DELETE FROM links WHERE target_project_id = ?\").run(plan.fromId);\n db.prepare(\n `DELETE FROM links WHERE target_project_id = ?\n AND session_id IN (SELECT id FROM sessions WHERE project_id = ?)`\n ).run(plan.intoId, plan.intoId);\n\n // And keep the old name resolvable.\n if (plan.aliasToAdd) {\n db.prepare(\"INSERT OR IGNORE INTO aliases (alias, project_id) VALUES (?, ?)\").run(\n plan.aliasToAdd,\n plan.intoId\n );\n }\n\n db.prepare(\"DELETE FROM projects WHERE id = ?\").run(plan.fromId);\n });\n\n run();\n}\n","/**\n * pai project merge <from> <into> — fold a duplicate registry row into the real one.\n *\n * Exists because `health` had one remedy for every unhealthy row — archive — and\n * archiving a duplicate strands its sessions on a row nobody will open again. The\n * sessions are the only thing of value on a duplicate.\n *\n * Preview by default. This deletes a registry row, and the five-table fan-out\n * behind it is not something a caller can eyeball afterwards, so the plan is\n * printed first and `--execute` is what touches the database.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { ok, warn, err, dim, bold, shortenPath } from \"../../utils.js\";\nimport { planMerge, applyMerge, MergeError } from \"../../../registry/merge.js\";\n\nexport function cmdMerge(\n db: Database,\n fromSlug: string,\n intoSlug: string,\n opts: { execute?: boolean } = {}\n): void {\n let plan;\n try {\n plan = planMerge(db, fromSlug, intoSlug);\n } catch (e) {\n if (e instanceof MergeError) {\n console.error(err(` ${e.message}`));\n process.exitCode = 1;\n return;\n }\n throw e;\n }\n\n const path = (id: number): string => {\n const row = db.prepare(\"SELECT root_path FROM projects WHERE id = ?\").get(id) as\n | { root_path: string }\n | undefined;\n return row ? shortenPath(row.root_path, 54) : \"(unknown)\";\n };\n\n console.log();\n console.log(bold(` Merge ${plan.fromSlug} into ${plan.intoSlug}`));\n console.log();\n console.log(dim(` from ${plan.fromSlug.padEnd(22)} ${path(plan.fromId)}`));\n console.log(dim(` into ${plan.intoSlug.padEnd(22)} ${path(plan.intoId)}`));\n console.log();\n\n if (plan.sessions.length > 0) {\n // The renumbering is the part a reader cannot predict — sessions are UNIQUE\n // on (project_id, number), so incoming ones cannot keep their numbers.\n console.log(` ${plan.sessions.length} session(s) move and are renumbered:`);\n for (const s of plan.sessions) {\n console.log(dim(` ${String(s.from).padStart(4)} -> ${String(s.to).padStart(4)}`));\n }\n } else {\n console.log(dim(` No sessions to move.`));\n }\n\n const extras: string[] = [];\n if (plan.tags) extras.push(`${plan.tags} tag(s)`);\n if (plan.aliases) extras.push(`${plan.aliases} existing alias(es)`);\n if (plan.compactions) extras.push(`${plan.compactions} compaction record(s)`);\n if (plan.links) extras.push(`${plan.links} inbound link(s)`);\n if (extras.length > 0) {\n console.log(dim(` Also repointed: ${extras.join(\", \")}.`));\n }\n\n if (plan.aliasToAdd) {\n console.log(\n dim(` \"${plan.aliasToAdd}\" is kept as an alias, so the old name still resolves.`)\n );\n } else {\n console.log(\n warn(` \"${plan.fromSlug}\" cannot be kept as an alias — the name is already taken.`)\n );\n }\n\n console.log(dim(` Then the ${plan.fromSlug} row is deleted.`));\n console.log();\n\n if (!opts.execute) {\n console.log(dim(\" Preview — nothing was changed. Re-run with --execute to merge.\"));\n console.log();\n return;\n }\n\n applyMerge(db, plan);\n console.log(\n ok(\n ` Merged. ${plan.sessions.length} session(s) now belong to ${plan.intoSlug}.`\n )\n );\n console.log();\n}\n","/**\n * `pai project here <name>` — \"this directory is that project\".\n *\n * Directories move. They get renamed, reorganised into an archive tree, reached\n * through a symlinked parent. Every existing way to fix the registry afterwards\n * asks for something the user has to look up — `rebind` and `move` want a slug\n * AND a path, `add` wants a path — which means the moment you are standing in\n * the directory and know perfectly well what it is, you still cannot just say so.\n *\n * This command is that sentence. Run it in the directory, name the project, and\n * the registry points here. If no project by that name exists, it is created.\n *\n * Two details that matter more than they look:\n *\n * 1. The path is canonicalised with realpath. `resolve()` keeps whichever\n * spelling was typed, so a directory reachable through a symlinked parent\n * encodes two different ways and can be registered TWICE as two unrelated\n * projects — with sessions split across both. Storing the canonical path is\n * what makes \"this directory\" mean one row.\n *\n * 2. Matching accepts the name a human uses. The display name, the slug, and\n * all-query-words-present, in that order, so a project whose directory has\n * since gained or lost a word is still reachable by the name its owner\n * thinks of it by.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { realpathSync } from \"node:fs\";\nimport { ok, err, dim, bold, encodeDir, now } from \"../../utils.js\";\n\ninterface ProjectRow {\n id: number;\n slug: string;\n display_name: string;\n root_path: string;\n status: string;\n}\n\n/** Normalise for name comparison: lowercase, collapse whitespace and hyphens. */\nfunction norm(s: string): string {\n return s.toLowerCase().replace(/[-_]+/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * Find the project a human means by `name`.\n *\n * Exact display-name or slug first. Only if that finds nothing do we fall back\n * to requiring every query word to appear, which is deliberately narrower than\n * a substring match: it reaches \"Jobs Search Beta\" from \"jobs beta\" without\n * also reaching a sibling project that merely shares the first word.\n */\nexport function findProjectsByName(db: Database, name: string): ProjectRow[] {\n const rows = db\n .prepare(\"SELECT id, slug, display_name, root_path, status FROM projects\")\n .all() as ProjectRow[];\n\n const q = norm(name);\n const exact = rows.filter((r) => norm(r.display_name) === q || norm(r.slug) === q);\n if (exact.length > 0) return exact;\n\n const words = q.split(\" \").filter(Boolean);\n if (words.length === 0) return [];\n return rows.filter((r) => {\n const hay = `${norm(r.display_name)} ${norm(r.slug)}`;\n return words.every((w) => hay.includes(w));\n });\n}\n\n/** Slug from a display name: lowercase, non-alphanumerics to hyphens. */\nexport function slugFromName(name: string): string {\n return (\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\") || \"project\"\n );\n}\n\nexport function cmdHere(\n db: Database,\n name: string,\n opts: { cwd?: string; dryRun?: boolean } = {}\n): void {\n // Canonicalise: see note 1 above. A symlinked parent must not create a second\n // identity for the same directory.\n let target: string;\n try {\n target = realpathSync(opts.cwd ?? process.cwd());\n } catch {\n console.error(err(`Cannot resolve the current directory.`));\n process.exitCode = 1;\n return;\n }\n\n const encoded = encodeDir(target);\n const matches = findProjectsByName(db, name);\n\n if (matches.length > 1) {\n console.error(\n err(`\"${name}\" matches ${matches.length} projects — name one exactly:\\n`) +\n matches.map((m) => dim(` ${m.slug} (${m.display_name})\\n`)).join(\"\")\n );\n process.exitCode = 1;\n return;\n }\n\n // Another project already owns this directory: refuse rather than create a\n // duplicate row for a path that is already spoken for.\n const owner = db\n .prepare(\"SELECT id, slug, display_name FROM projects WHERE encoded_dir = ?\")\n .get(encoded) as Pick<ProjectRow, \"id\" | \"slug\" | \"display_name\"> | undefined;\n\n if (matches.length === 1) {\n const p = matches[0];\n\n if (owner && owner.id !== p.id) {\n console.error(\n err(`This directory is already registered to ${bold(owner.slug)}.\\n`) +\n dim(` ${target}\\n`) +\n dim(` Merge them: pai project merge ${owner.slug} ${p.slug} --execute`)\n );\n process.exitCode = 1;\n return;\n }\n\n // Already correct — say so instead of writing an identical row. Compared by\n // realpath, so a differently-spelled but equivalent path counts as correct.\n let sameAlready = false;\n try {\n sameAlready = realpathSync(p.root_path) === target;\n } catch {\n sameAlready = false;\n }\n if (sameAlready) {\n console.log(ok(`Already set: ${bold(p.display_name)}`));\n console.log(dim(` ${target}`));\n return;\n }\n\n if (opts.dryRun) {\n console.log(dim(`Would repoint ${p.slug}:`));\n console.log(dim(` from ${p.root_path}`));\n console.log(dim(` to ${target}`));\n return;\n }\n\n db.prepare(\n \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n ).run(target, encoded, now(), p.id);\n\n console.log(ok(`${bold(p.display_name)} is now here`));\n console.log(dim(` was: ${p.root_path}`));\n console.log(dim(` now: ${target}`));\n return;\n }\n\n // No project by that name — create it pointing here.\n if (owner) {\n console.error(\n err(`This directory is already registered as ${bold(owner.slug)} (${owner.display_name}).\\n`) +\n dim(` Rename it instead: pai project edit ${owner.slug} --display-name \"${name}\"`)\n );\n process.exitCode = 1;\n return;\n }\n\n const slug = slugFromName(name);\n const taken = db.prepare(\"SELECT 1 FROM projects WHERE slug = ?\").get(slug);\n const finalSlug = taken ? `${slug}-${encoded.slice(-6)}` : slug;\n\n if (opts.dryRun) {\n console.log(dim(`Would create ${finalSlug} (${name}) at ${target}`));\n return;\n }\n\n const ts = now();\n db.prepare(\n `INSERT INTO projects (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)\n VALUES (?, ?, ?, ?, 'local', 'active', ?, ?)`\n ).run(finalSlug, name, target, encoded, ts, ts);\n\n console.log(ok(`Created ${bold(name)}`));\n console.log(dim(` slug: ${finalSlug}`));\n console.log(dim(` path: ${target}`));\n}\n","/**\n * pai project unregister <slug> — remove a row that should never have existed.\n *\n * The remedy for an EPHEMERAL project: one rooted in a git worktree or a temp\n * directory. Archiving is wrong for those — archiving means \"this was real and is\n * finished\", and these were never projects. Measured 2026-08-04 there were five:\n * two agent worktrees, `/private/tmp/ops-webui`, a scratchpad under\n * `/private/tmp/claude-501/…`, and `tmp` rooted at `/private/tmp` itself, active\n * since February.\n *\n * Written because `health` started printing \"unregister\" as the suggested action\n * and the command did not exist. An action field naming something unavailable is\n * worse than no action field: it reads as vetted.\n *\n * Refuses by default when the row holds sessions. Those sessions are the only\n * thing of value on it, and deleting the row would strand them — merge is the\n * right verb there, so this points at it rather than offering --force as the\n * obvious path.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { ok, warn, err, dim, bold } from \"../../utils.js\";\n\nexport function cmdUnregister(\n db: Database,\n slug: string,\n opts: { execute?: boolean; force?: boolean } = {}\n): void {\n const row = db\n .prepare(\n `SELECT p.id, p.slug, p.root_path, p.status,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count\n FROM projects p WHERE p.slug = ?`\n )\n .get(slug) as\n | { id: number; slug: string; root_path: string; status: string; session_count: number }\n | undefined;\n\n if (!row) {\n console.error(err(` No project with slug \"${slug}\".`));\n process.exitCode = 1;\n return;\n }\n\n console.log();\n console.log(bold(` Unregister ${row.slug}`));\n console.log(dim(` ${row.root_path}`));\n console.log(dim(` status ${row.status}, ${row.session_count} session(s)`));\n console.log();\n\n if (row.session_count > 0 && !opts.force) {\n console.log(warn(` Refusing: ${row.session_count} session(s) would be stranded.`));\n console.log(\n dim(` Those sessions are the only thing of value on this row. Move them first:`)\n );\n console.log(dim(` pai project merge ${row.slug} <into> --execute`));\n console.log(dim(` Or pass --force if the sessions are genuinely worthless.`));\n console.log();\n process.exitCode = 1;\n return;\n }\n\n if (!opts.execute) {\n console.log(\n dim(\" Preview — nothing was changed. Re-run with --execute to unregister.\")\n );\n if (row.session_count > 0) {\n console.log(warn(` --force is set: ${row.session_count} session(s) WILL be deleted.`));\n }\n console.log();\n return;\n }\n\n // Same five tables as merge. With foreign keys off, deleting only the project\n // row would silently orphan the rest.\n const run = db.transaction(() => {\n db.prepare(\"DELETE FROM links WHERE target_project_id = ?\").run(row.id);\n db.prepare(\n \"DELETE FROM links WHERE session_id IN (SELECT id FROM sessions WHERE project_id = ?)\"\n ).run(row.id);\n db.prepare(\"DELETE FROM compaction_log WHERE project_id = ?\").run(row.id);\n db.prepare(\"DELETE FROM project_tags WHERE project_id = ?\").run(row.id);\n db.prepare(\"DELETE FROM aliases WHERE project_id = ?\").run(row.id);\n db.prepare(\"DELETE FROM sessions WHERE project_id = ?\").run(row.id);\n db.prepare(\"DELETE FROM projects WHERE id = ?\").run(row.id);\n });\n run();\n\n console.log(ok(` Unregistered ${row.slug}.`));\n console.log(dim(` The directory itself was not touched.`));\n console.log();\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 { cmdMerge } from \"./merge.js\";\nimport { cmdHere } from \"./here.js\";\nimport { cmdUnregister } from \"./unregister.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.exitCode = 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 merge <from> <into> [--execute]\n //\n // The remedy for a DUPLICATE row, which archiving does not serve: archiving a\n // duplicate strands its sessions on a row nobody opens again. Preview by\n // default — this deletes a registry row and repoints five tables.\n projectsCmd\n .command(\"merge <from> <into>\")\n .description(\n \"Fold a duplicate project into another: move its sessions (renumbered), repoint its \" +\n \"tags, aliases, compaction records and links, keep the old slug as an alias, then \" +\n \"delete the row. Preview unless --execute is given.\"\n )\n .option(\"--execute\", \"Actually perform the merge\")\n .action((from: string, into: string, opts: { execute?: boolean }) => {\n cmdMerge(getDb(), from, into, opts);\n });\n\n // pai projects unregister <slug> [--execute] [--force]\n //\n // The remedy for an EPHEMERAL row — a worktree or temp path that should never\n // have been registered. Archiving claims \"this was real and is finished\", which\n // is the wrong thing to say about a directory designed to be deleted.\n projectsCmd\n .command(\"unregister <slug>\")\n .description(\n \"Remove a project row entirely, for paths that should never have been registered \" +\n \"(worktrees, temp dirs). Refuses when the row holds sessions — merge those first. \" +\n \"Preview unless --execute is given. The directory itself is never touched.\"\n )\n .option(\"--execute\", \"Actually remove the row\")\n .option(\"--force\", \"Remove even though sessions would be deleted with it\")\n .action((slug: string, opts: { execute?: boolean; force?: boolean }) => {\n cmdUnregister(getDb(), slug, opts);\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 here <name>\n projectsCmd\n .command(\"here <name>\")\n .description(\n \"Declare that the CURRENT directory is this project — no slug, no path.\\n\" +\n \"Repoints an existing project of that name, or creates it. Stores the\\n\" +\n \"canonical (realpath) location, so a symlinked parent cannot register the\\n\" +\n \"same directory twice.\"\n )\n .option(\"--dry-run\", \"Show what would change without writing\")\n .action((name: string, opts: { dryRun?: boolean }) => {\n cmdHere(getDb(), name, { dryRun: opts.dryRun });\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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","/**\n * pai registry dedupe [--execute]\n *\n * Merges registry rows that describe the same project.\n *\n * WHY THIS HAPPENS\n * ----------------\n * `projects.root_path` is UNIQUE, so two spellings of the same directory\n * register as two projects. On a machine with symlinked path prefixes\n * (`~/dev -> Cloud/Development`, say) the same repo is reachable as\n * `~/dev/ai/PAI` and `~/Daten/Cloud/Development/ai/PAI`, and the scanner\n * registers both. Every session then lands under whichever spelling the shell\n * happened to be using, so history splits: one row accumulates 95 sessions and\n * the other has none, while both claim to be the same project.\n *\n * The symptom that surfaces it is a checkpoint labelled \"Unknown session\" —\n * the row matching the current directory genuinely has no session rows.\n *\n * WHY realpath IS THE RIGHT KEY\n * -----------------------------\n * It removes the judgement call. Two rows whose `root_path` resolves to the\n * same inode are the same project — not \"probably\", definitively — so the\n * merge is mechanical. Rows that merely look similar (same display name, one\n * path a prefix of another) are left alone: deciding those is the user's call,\n * not this command's.\n *\n * NO DATA LOSS\n * ------------\n * Everything referencing a merged row is repointed, never deleted:\n * sessions (renumbered only on collision), compaction_log, aliases, links,\n * project_tags, and any child project's parent_id. The DB is backed up before\n * anything is written, and the whole merge runs in one transaction.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { realpathSync, copyFileSync, existsSync } from \"node:fs\";\nimport { ok, warn, err, dim, bold } from \"../../utils.js\";\n\ninterface ProjectRow {\n id: number;\n slug: string;\n display_name: string;\n root_path: string;\n encoded_dir: string;\n status: string;\n claude_notes_dir: string | null;\n session_config: string | null;\n created_at: number;\n}\n\ninterface MergeGroup {\n realPath: string;\n canonical: ProjectRow;\n duplicates: ProjectRow[];\n}\n\ninterface Counts {\n sessions: number;\n compaction: number;\n aliases: number;\n links: number;\n tags: number;\n children: number;\n}\n\n// ---------------------------------------------------------------------------\n// Analysis\n// ---------------------------------------------------------------------------\n\n/** Resolve a path to its canonical form, or null when it no longer exists. */\nfunction safeRealPath(p: string): string | null {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n}\n\nfunction countRefs(db: Database, projectId: number): Counts {\n const one = (sql: string): number =>\n (db.prepare(sql).get(projectId) as { n: number }).n;\n\n return {\n sessions: one(\"SELECT COUNT(*) AS n FROM sessions WHERE project_id = ?\"),\n compaction: one(\"SELECT COUNT(*) AS n FROM compaction_log WHERE project_id = ?\"),\n aliases: one(\"SELECT COUNT(*) AS n FROM aliases WHERE project_id = ?\"),\n links: one(\"SELECT COUNT(*) AS n FROM links WHERE target_project_id = ?\"),\n tags: one(\"SELECT COUNT(*) AS n FROM project_tags WHERE project_id = ?\"),\n children: one(\"SELECT COUNT(*) AS n FROM projects WHERE parent_id = ?\"),\n };\n}\n\n/**\n * Choose which row survives.\n *\n * The row whose stored path already IS the canonical filesystem path wins:\n * keeping it means `root_path` ends up correct without rewriting it, and any\n * external reference to that path keeps resolving. Failing that, the row with\n * the most sessions wins, then the oldest.\n */\nfunction pickCanonical(rows: ProjectRow[], realPath: string, db: Database): ProjectRow {\n const exact = rows.filter((r) => r.root_path === realPath);\n const pool = exact.length > 0 ? exact : rows;\n\n return [...pool].sort((a, b) => {\n const sa = countRefs(db, a.id).sessions;\n const sb = countRefs(db, b.id).sessions;\n if (sa !== sb) return sb - sa;\n return a.created_at - b.created_at;\n })[0];\n}\n\nexport function analyzeDuplicates(db: Database): {\n groups: MergeGroup[];\n stalePaths: ProjectRow[];\n} {\n const rows = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir, status,\n claude_notes_dir, session_config, created_at\n FROM projects ORDER BY id`\n )\n .all() as ProjectRow[];\n\n const byRealPath = new Map<string, ProjectRow[]>();\n const stalePaths: ProjectRow[] = [];\n\n for (const row of rows) {\n const rp = safeRealPath(row.root_path);\n if (!rp) {\n // The directory is gone. It cannot be proven identical to anything, so\n // it is never merged automatically — only reported.\n stalePaths.push(row);\n continue;\n }\n const bucket = byRealPath.get(rp);\n if (bucket) bucket.push(row);\n else byRealPath.set(rp, [row]);\n }\n\n const groups: MergeGroup[] = [];\n for (const [realPath, members] of byRealPath) {\n if (members.length < 2) continue;\n const canonical = pickCanonical(members, realPath, db);\n groups.push({\n realPath,\n canonical,\n duplicates: members.filter((m) => m.id !== canonical.id),\n });\n }\n\n return { groups, stalePaths };\n}\n\n// ---------------------------------------------------------------------------\n// Merge\n// ---------------------------------------------------------------------------\n\ninterface MergeReport {\n movedSessions: number;\n foldedSessions: number;\n renumbered: Array<{ from: number; to: number; title: string }>;\n movedCompaction: number;\n movedAliases: number;\n movedLinks: number;\n movedTags: number;\n movedChildren: number;\n deletedRows: number;\n reclaimedSlugs: Array<{ from: string; to: string }>;\n}\n\n/**\n * Fold a duplicate session row into the one the canonical project already has\n * for the same note file.\n *\n * Both rows describe one file on disk — the double registration produced two\n * registry rows for it, often identical and created milliseconds apart. Moving\n * both would leave the merged project with two entries per note and force one\n * of them to a number that no longer matches its own filename.\n *\n * Nothing is discarded: tags and links are repointed at the surviving row, and\n * any field the survivor is missing is backfilled from the duplicate before it\n * is deleted.\n */\nfunction foldSession(db: Database, keepId: number, dropId: number): void {\n // session_tags PK is (session_id, tag_id) — skip tags the survivor has.\n const tags = db\n .prepare(\"SELECT tag_id FROM session_tags WHERE session_id = ?\")\n .all(dropId) as Array<{ tag_id: number }>;\n for (const t of tags) {\n const clash = db\n .prepare(\n \"SELECT 1 FROM session_tags WHERE session_id = ? AND tag_id = ? LIMIT 1\"\n )\n .get(keepId, t.tag_id);\n if (clash) {\n db.prepare(\n \"DELETE FROM session_tags WHERE session_id = ? AND tag_id = ?\"\n ).run(dropId, t.tag_id);\n } else {\n db.prepare(\n \"UPDATE session_tags SET session_id = ? WHERE session_id = ? AND tag_id = ?\"\n ).run(keepId, dropId, t.tag_id);\n }\n }\n\n // links UNIQUE(session_id, target_project_id)\n const links = db\n .prepare(\"SELECT id, target_project_id FROM links WHERE session_id = ?\")\n .all(dropId) as Array<{ id: number; target_project_id: number }>;\n for (const l of links) {\n const clash = db\n .prepare(\n \"SELECT 1 FROM links WHERE session_id = ? AND target_project_id = ? LIMIT 1\"\n )\n .get(keepId, l.target_project_id);\n if (clash) db.prepare(\"DELETE FROM links WHERE id = ?\").run(l.id);\n else\n db.prepare(\"UPDATE links SET session_id = ? WHERE id = ?\").run(\n keepId,\n l.id\n );\n }\n\n db.prepare(\"UPDATE compaction_log SET session_id = ? WHERE session_id = ?\").run(\n keepId,\n dropId\n );\n\n // Backfill anything the survivor lacks, and prefer a finished status.\n db.prepare(\n `UPDATE sessions SET\n claude_session_id = COALESCE(claude_session_id,\n (SELECT claude_session_id FROM sessions WHERE id = ?)),\n token_count = COALESCE(token_count,\n (SELECT token_count FROM sessions WHERE id = ?)),\n closed_at = COALESCE(closed_at,\n (SELECT closed_at FROM sessions WHERE id = ?)),\n status = CASE WHEN status = 'open'\n AND (SELECT status FROM sessions WHERE id = ?) != 'open'\n THEN (SELECT status FROM sessions WHERE id = ?)\n ELSE status END\n WHERE id = ?`\n ).run(dropId, dropId, dropId, dropId, dropId, keepId);\n\n db.prepare(\"DELETE FROM sessions WHERE id = ?\").run(dropId);\n}\n\nfunction mergeGroup(db: Database, group: MergeGroup): MergeReport {\n const report: MergeReport = {\n movedSessions: 0,\n foldedSessions: 0,\n renumbered: [],\n movedCompaction: 0,\n movedAliases: 0,\n movedLinks: 0,\n movedTags: 0,\n movedChildren: 0,\n deletedRows: 0,\n reclaimedSlugs: [],\n };\n\n const canonicalId = group.canonical.id;\n\n // Highest session number already used on the canonical row — collisions are\n // appended above it so no existing number ever has to change.\n let nextNumber =\n ((\n db\n .prepare(\"SELECT MAX(number) AS n FROM sessions WHERE project_id = ?\")\n .get(canonicalId) as { n: number | null }\n ).n ?? 0) + 1;\n\n for (const dup of group.duplicates) {\n const sessions = db\n .prepare(\n \"SELECT id, number, title, filename FROM sessions WHERE project_id = ? ORDER BY number\"\n )\n .all(dup.id) as Array<{\n id: number;\n number: number;\n title: string;\n filename: string;\n }>;\n\n for (const s of sessions) {\n // Same file already registered on the canonical row? Then this is the\n // same session recorded twice, not a second session. Fold, do not move.\n const twin = db\n .prepare(\n \"SELECT id FROM sessions WHERE project_id = ? AND filename = ? LIMIT 1\"\n )\n .get(canonicalId, s.filename) as { id: number } | undefined;\n\n if (twin) {\n foldSession(db, twin.id, s.id);\n report.foldedSessions++;\n continue;\n }\n\n const clash = db\n .prepare(\n \"SELECT 1 FROM sessions WHERE project_id = ? AND number = ? LIMIT 1\"\n )\n .get(canonicalId, s.number);\n\n if (clash) {\n const assigned = nextNumber++;\n db.prepare(\n \"UPDATE sessions SET project_id = ?, number = ? WHERE id = ?\"\n ).run(canonicalId, assigned, s.id);\n report.renumbered.push({\n from: s.number,\n to: assigned,\n title: s.title,\n });\n } else {\n db.prepare(\"UPDATE sessions SET project_id = ? WHERE id = ?\").run(\n canonicalId,\n s.id\n );\n if (s.number >= nextNumber) nextNumber = s.number + 1;\n }\n report.movedSessions++;\n }\n\n report.movedCompaction += db\n .prepare(\"UPDATE compaction_log SET project_id = ? WHERE project_id = ?\")\n .run(canonicalId, dup.id).changes;\n\n // aliases.alias is the PK, so an alias already pointing at the canonical\n // row would collide. Repoint what can move, drop only exact duplicates.\n const dupAliases = db\n .prepare(\"SELECT alias FROM aliases WHERE project_id = ?\")\n .all(dup.id) as Array<{ alias: string }>;\n for (const a of dupAliases) {\n const existing = db\n .prepare(\"SELECT project_id FROM aliases WHERE alias = ?\")\n .get(a.alias) as { project_id: number } | undefined;\n if (existing && existing.project_id === canonicalId) continue;\n db.prepare(\"UPDATE aliases SET project_id = ? WHERE alias = ?\").run(\n canonicalId,\n a.alias\n );\n report.movedAliases++;\n }\n\n // links has UNIQUE(session_id, target_project_id).\n const dupLinks = db\n .prepare(\"SELECT id, session_id FROM links WHERE target_project_id = ?\")\n .all(dup.id) as Array<{ id: number; session_id: number }>;\n for (const l of dupLinks) {\n const clash = db\n .prepare(\n \"SELECT 1 FROM links WHERE session_id = ? AND target_project_id = ? LIMIT 1\"\n )\n .get(l.session_id, canonicalId);\n if (clash) {\n db.prepare(\"DELETE FROM links WHERE id = ?\").run(l.id);\n continue;\n }\n db.prepare(\"UPDATE links SET target_project_id = ? WHERE id = ?\").run(\n canonicalId,\n l.id\n );\n report.movedLinks++;\n }\n\n // project_tags has PRIMARY KEY(project_id, tag_id).\n const dupTags = db\n .prepare(\"SELECT tag_id FROM project_tags WHERE project_id = ?\")\n .all(dup.id) as Array<{ tag_id: number }>;\n for (const t of dupTags) {\n const clash = db\n .prepare(\n \"SELECT 1 FROM project_tags WHERE project_id = ? AND tag_id = ? LIMIT 1\"\n )\n .get(canonicalId, t.tag_id);\n if (clash) {\n db.prepare(\n \"DELETE FROM project_tags WHERE project_id = ? AND tag_id = ?\"\n ).run(dup.id, t.tag_id);\n continue;\n }\n db.prepare(\n \"UPDATE project_tags SET project_id = ? WHERE project_id = ? AND tag_id = ?\"\n ).run(canonicalId, dup.id, t.tag_id);\n report.movedTags++;\n }\n\n report.movedChildren += db\n .prepare(\"UPDATE projects SET parent_id = ? WHERE parent_id = ?\")\n .run(canonicalId, dup.id).changes;\n\n // Carry over settings the canonical row is missing rather than losing them.\n if (!group.canonical.claude_notes_dir && dup.claude_notes_dir) {\n db.prepare(\"UPDATE projects SET claude_notes_dir = ? WHERE id = ?\").run(\n dup.claude_notes_dir,\n canonicalId\n );\n }\n if (!group.canonical.session_config && dup.session_config) {\n db.prepare(\"UPDATE projects SET session_config = ? WHERE id = ?\").run(\n dup.session_config,\n canonicalId\n );\n }\n\n db.prepare(\"DELETE FROM projects WHERE id = ?\").run(dup.id);\n report.deletedRows++;\n }\n\n // An active duplicate means the project is active, whatever the canonical\n // row happened to say.\n const anyActive =\n group.canonical.status === \"active\" ||\n group.duplicates.some((d) => d.status === \"active\");\n if (anyActive && group.canonical.status !== \"active\") {\n db.prepare(\"UPDATE projects SET status = 'active' WHERE id = ?\").run(\n canonicalId\n );\n }\n\n // Reclaim the good slug.\n //\n // The duplicate rows were created in path order, so the plain slug (\"pai\")\n // usually went to whichever spelling was seen first and the canonical row —\n // chosen by filesystem path, not by name — ended up with the suffixed one\n // (\"pai-2\"). Now that the duplicates are gone their names are free, so take\n // the best one back rather than leaving the merged project called \"pai-2\".\n const suffixed = /^(.*)-\\d+$/.exec(group.canonical.slug);\n if (suffixed) {\n const candidates = [\n suffixed[1],\n ...group.duplicates.map((d) => d.slug).filter((s) => !/-\\d+$/.test(s)),\n ];\n for (const candidate of candidates) {\n const taken = db\n .prepare(\"SELECT 1 FROM projects WHERE slug = ? LIMIT 1\")\n .get(candidate);\n if (taken) continue;\n db.prepare(\"UPDATE projects SET slug = ?, updated_at = ? WHERE id = ?\").run(\n candidate,\n Date.now(),\n canonicalId\n );\n report.reclaimedSlugs.push({\n from: group.canonical.slug,\n to: candidate,\n });\n break;\n }\n }\n\n // Point the surviving row at the canonical filesystem path.\n if (group.canonical.root_path !== group.realPath) {\n const taken = db\n .prepare(\"SELECT id FROM projects WHERE root_path = ? AND id != ?\")\n .get(group.realPath, canonicalId);\n if (!taken) {\n db.prepare(\n \"UPDATE projects SET root_path = ?, updated_at = ? WHERE id = ?\"\n ).run(group.realPath, Date.now(), canonicalId);\n }\n }\n\n return report;\n}\n\n/**\n * Rewrite any remaining root_path that is not already its canonical form.\n *\n * Merging fixes the rows that already split. This stops the rest from\n * splitting later: a project registered only as `~/dev/ai/PAILot` still has a\n * non-canonical path, so the first session started from the other spelling\n * would create a second row and the whole problem recurs.\n *\n * Only rows whose canonical path is free are touched — a collision would mean\n * a duplicate, and those have already been merged.\n */\nfunction canonicalizePaths(\n db: Database,\n dryRun: boolean\n): Array<{ slug: string; from: string; to: string }> {\n const rows = db\n .prepare(\"SELECT id, slug, root_path FROM projects\")\n .all() as Array<{ id: number; slug: string; root_path: string }>;\n\n const changed: Array<{ slug: string; from: string; to: string }> = [];\n\n for (const row of rows) {\n const rp = safeRealPath(row.root_path);\n if (!rp || rp === row.root_path) continue;\n\n const taken = db\n .prepare(\"SELECT 1 FROM projects WHERE root_path = ? AND id != ? LIMIT 1\")\n .get(rp, row.id);\n if (taken) continue;\n\n changed.push({ slug: row.slug, from: row.root_path, to: rp });\n if (!dryRun) {\n db.prepare(\n \"UPDATE projects SET root_path = ?, updated_at = ? WHERE id = ?\"\n ).run(rp, Date.now(), row.id);\n }\n }\n\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nfunction backupDb(dbPath: string): string | null {\n if (!existsSync(dbPath)) return null;\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const dest = `${dbPath}.bak-${stamp}`;\n try {\n copyFileSync(dbPath, dest);\n // Copy the WAL too — without it the backup can miss committed pages.\n for (const suffix of [\"-wal\", \"-shm\"]) {\n if (existsSync(dbPath + suffix)) {\n copyFileSync(dbPath + suffix, dest + suffix);\n }\n }\n return dest;\n } catch {\n return null;\n }\n}\n\nexport function cmdDedupe(\n db: Database,\n opts: { execute?: boolean; dbPath?: string }\n): void {\n const { groups, stalePaths } = analyzeDuplicates(db);\n\n console.log();\n console.log(\n bold(\n opts.execute\n ? \" pai registry dedupe\"\n : \" pai registry dedupe — DRY RUN (no changes will be made)\"\n )\n );\n console.log();\n\n if (groups.length === 0) {\n console.log(ok(\"No duplicate projects found — every root_path resolves uniquely.\"));\n }\n\n for (const g of groups) {\n console.log(` ${bold(g.realPath)}`);\n const cCounts = countRefs(db, g.canonical.id);\n console.log(\n ` ${ok(\"keep\")} #${g.canonical.id} ${bold(g.canonical.slug)} ` +\n dim(`(${g.canonical.root_path}) — ${cCounts.sessions} sessions, ${g.canonical.status}`)\n );\n for (const d of g.duplicates) {\n const dc = countRefs(db, d.id);\n const moving = [\n dc.sessions ? `${dc.sessions} sessions` : null,\n dc.compaction ? `${dc.compaction} compaction rows` : null,\n dc.aliases ? `${dc.aliases} aliases` : null,\n dc.links ? `${dc.links} links` : null,\n dc.tags ? `${dc.tags} tags` : null,\n dc.children ? `${dc.children} child projects` : null,\n ]\n .filter(Boolean)\n .join(\", \");\n console.log(\n ` ${warn(\"merge\")} #${d.id} ${bold(d.slug)} ` +\n dim(`(${d.root_path}) — ${moving || \"nothing to move\"}`)\n );\n }\n console.log();\n }\n\n if (stalePaths.length > 0) {\n console.log(\n dim(\n ` ${stalePaths.length} project(s) point at a path that no longer exists.`\n )\n );\n console.log(\n dim(\n \" These are never merged automatically — a missing directory cannot be\"\n )\n );\n console.log(dim(\" proven identical to anything else. Listed for review:\"));\n for (const s of stalePaths) {\n const sc = countRefs(db, s.id);\n if (sc.sessions === 0 && sc.compaction === 0) continue;\n console.log(\n dim(\n ` #${s.id} ${s.slug} (${s.root_path}) — ${sc.sessions} sessions, ${sc.compaction} compaction rows`\n )\n );\n }\n console.log();\n }\n\n const pathFixes = canonicalizePaths(db, true);\n if (pathFixes.length > 0) {\n console.log(\n dim(\n ` ${pathFixes.length} project(s) stored under a non-canonical path — these would`\n )\n );\n console.log(\n dim(\n \" split into a second row the first time a session runs from the other\"\n )\n );\n console.log(dim(\" spelling. They will be rewritten to their resolved path:\"));\n for (const p of pathFixes.slice(0, 8)) {\n console.log(dim(` ${p.slug}: ${p.from} -> ${p.to}`));\n }\n if (pathFixes.length > 8) {\n console.log(dim(` ... and ${pathFixes.length - 8} more`));\n }\n console.log();\n }\n\n if (!opts.execute) {\n if (groups.length > 0 || pathFixes.length > 0) {\n console.log(dim(\" Re-run with --execute to apply. The registry is backed up first.\"));\n console.log();\n }\n return;\n }\n\n if (groups.length === 0 && pathFixes.length === 0) return;\n\n // ---- Back up before touching anything ----\n const dbPath = opts.dbPath;\n if (dbPath) {\n const backup = backupDb(dbPath);\n if (backup) {\n console.log(ok(`Registry backed up to ${bold(backup)}`));\n } else {\n console.error(\n err(\"Could not back up the registry — refusing to merge without one.\")\n );\n process.exitCode = 1;\n return;\n }\n }\n\n // ---- Merge, all-or-nothing ----\n const totals: MergeReport = {\n movedSessions: 0,\n foldedSessions: 0,\n renumbered: [],\n movedCompaction: 0,\n movedAliases: 0,\n movedLinks: 0,\n movedTags: 0,\n movedChildren: 0,\n deletedRows: 0,\n reclaimedSlugs: [],\n };\n\n let canonicalized = 0;\n\n const run = db.transaction(() => {\n for (const g of groups) {\n const r = mergeGroup(db, g);\n totals.movedSessions += r.movedSessions;\n totals.foldedSessions += r.foldedSessions;\n totals.renumbered.push(...r.renumbered);\n totals.movedCompaction += r.movedCompaction;\n totals.movedAliases += r.movedAliases;\n totals.movedLinks += r.movedLinks;\n totals.movedTags += r.movedTags;\n totals.movedChildren += r.movedChildren;\n totals.deletedRows += r.deletedRows;\n totals.reclaimedSlugs.push(...r.reclaimedSlugs);\n }\n // After merging, the remaining collisions are gone, so the rest of the\n // registry can be moved onto canonical paths safely.\n canonicalized = canonicalizePaths(db, false).length;\n });\n\n try {\n run();\n } catch (e) {\n console.error(err(`Merge failed and was rolled back: ${String(e)}`));\n process.exitCode = 1;\n return;\n }\n\n console.log();\n console.log(ok(`Merged ${totals.deletedRows} duplicate row(s).`));\n console.log(` sessions moved: ${totals.movedSessions}`);\n console.log(\n ` sessions folded: ${totals.foldedSessions} ` +\n dim(\"(same note file registered twice)\")\n );\n console.log(` compaction rows moved: ${totals.movedCompaction}`);\n console.log(` aliases moved: ${totals.movedAliases}`);\n console.log(` links moved: ${totals.movedLinks}`);\n console.log(` tags moved: ${totals.movedTags}`);\n console.log(` child projects moved: ${totals.movedChildren}`);\n console.log(` paths canonicalized: ${canonicalized}`);\n\n if (totals.reclaimedSlugs.length > 0) {\n console.log();\n console.log(ok(\"Slugs reclaimed from the merged rows:\"));\n for (const s of totals.reclaimedSlugs) {\n console.log(dim(` ${s.from} -> ${s.to}`));\n }\n }\n\n if (totals.renumbered.length > 0) {\n console.log();\n console.log(\n warn(`${totals.renumbered.length} session(s) renumbered to avoid collisions:`)\n );\n for (const r of totals.renumbered) {\n console.log(dim(` ${r.from} -> ${r.to} ${r.title}`));\n }\n console.log(\n dim(\" Session note filenames on disk are unchanged — only the registry number moved.\")\n );\n console.log();\n console.log(\n warn(\n \" A renumbered session's registry number no longer matches the number in its\"\n )\n );\n console.log(\n dim(\n \" filename. The scanner keys sessions by (project, number), so a note whose\\n\" +\n \" number is now occupied cannot be re-registered until numbering is reconciled.\\n\" +\n \" Run `pai session cleanup <slug> --execute` on the affected projects to renumber\\n\" +\n \" notes and registry together.\"\n )\n );\n }\n console.log();\n}\n","/**\n * `pai registry reconnect` — point projects back at the transcripts they lost.\n *\n * A project's `encoded_dir` is written once and never updated when the project\n * moves, so every transcript lookup for a moved project quietly returns\n * nothing: no handover, no session digest, no checkpoint. The failure is\n * invisible because an empty result and an unasked question look identical.\n *\n * Repairs are derived from the transcripts themselves — each records the `cwd`\n * it ran in — rather than from a naming rule, because the naming rule is what\n * broke. Dry-run by default: this rewrites registry rows, and a repair that\n * guesses wrong reattaches a project to someone else's history.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport {\n scanTranscriptFolders,\n findMovedProjects,\n transcriptFiles,\n claudeProjectsDir,\n type RegistryProjectRow,\n} from \"../../../registry/moved.js\";\nimport { encodeDir } from \"../../utils.js\";\nimport { dim, ok, warn } from \"../../utils.js\";\n\nexport function cmdReconnect(db: Database, opts: { execute?: boolean }): void {\n const rows = db\n .prepare(\n `SELECT p.id, p.slug, p.root_path, p.encoded_dir,\n (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS sessions\n FROM projects p\n WHERE p.status = 'active'`\n )\n .all() as RegistryProjectRow[];\n\n const base = claudeProjectsDir();\n const has = (name: string | null): boolean =>\n Boolean(name) && transcriptFiles(join(base, name!)).length > 0;\n\n // Mirrors resolveTranscriptDir: stored value first, derived from the current\n // root path second. A project either of those already handles is not broken,\n // and rewriting it would be churn dressed as a repair.\n const resolvesNow = (r: RegistryProjectRow): boolean =>\n has(r.encoded_dir) || has(encodeDir(r.root_path));\n\n const moved = findMovedProjects(rows, scanTranscriptFolders(base), resolvesNow);\n\n console.log();\n if (moved.length === 0) {\n console.log(ok(\" Nothing to reconnect. \"));\n console.log(dim(\" Every active project resolves to a folder holding transcripts.\"));\n console.log();\n return;\n }\n\n console.log(\n chalk.bold(` ${moved.length} project(s) point at the wrong transcript folder`)\n );\n console.log();\n for (const m of moved) {\n const cost = m.sessions > 0 ? chalk.yellow(`${m.sessions} sessions unreachable`) : dim(\"no sessions recorded\");\n console.log(` ${chalk.cyan(m.slug)} ${cost}`);\n console.log(dim(` stored: ${m.storedDir ?? \"(none)\"}`));\n console.log(dim(` correct: ${m.correctDir} (${m.transcripts} transcripts)`));\n }\n console.log();\n\n if (!opts.execute) {\n console.log(dim(\" Dry run. Re-run with --execute to write these.\"));\n console.log();\n return;\n }\n\n // One transaction: a partial repair would leave the registry in a state\n // nobody chose, and the whole point here is that stale rows are silent.\n const update = db.prepare(\"UPDATE projects SET encoded_dir = ? WHERE id = ?\");\n const apply = db.transaction((list: typeof moved) => {\n for (const m of list) update.run(m.correctDir, m.id);\n });\n\n try {\n apply(moved);\n console.log(ok(` Reconnected ${moved.length} project(s).`));\n const recovered = moved.reduce((n, m) => n + m.sessions, 0);\n if (recovered > 0) {\n console.log(dim(` ${recovered} session(s) are reachable again.`));\n }\n } catch (e) {\n console.error(warn(\" Nothing was written: \") + (e instanceof Error ? e.message : String(e)));\n }\n console.log();\n}\n\n/** Path to the registry, for callers that want to back it up first. */\nexport const REGISTRY_PATH = join(homedir(), \".pai\", \"registry.db\");\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\";\nimport { cmdDedupe } from \"./dedupe.js\";\nimport { cmdReconnect } from \"./reconnect.js\";\nimport { join } from \"node:path\";\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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 dedupe [--execute]\n registryCmd\n .command(\"dedupe\")\n .description(\n \"Merge registry rows that describe the same project.\\n\" +\n \"Two spellings of one directory (e.g. via a symlinked path prefix) register\\n\" +\n \"as separate projects and split session history between them. Rows are grouped\\n\" +\n \"by resolved path, so a merge only happens when the paths are provably identical.\\n\" +\n \"Dry-run by default; --execute backs up the registry first and merges in one transaction.\"\n )\n .option(\"--execute\", \"Actually perform the merge (default is dry-run)\")\n .action((opts: { execute?: boolean }) => {\n cmdDedupe(getDb(), {\n execute: opts.execute,\n dbPath: join(homedir(), \".pai\", \"registry.db\"),\n });\n });\n\n // pai registry reconnect\n registryCmd\n .command(\"reconnect\")\n .description(\n \"Point projects back at the transcripts they lost.\\n\" +\n \"A project's encoded_dir is written once and never updated when the project\\n\" +\n \"moves, so handovers, session digests and checkpoints silently find nothing.\\n\" +\n \"Repairs are read from the transcripts themselves — each records the cwd it ran\\n\" +\n \"in — rather than re-derived from the naming rule that broke.\\n\" +\n \"Dry-run by default; --execute writes the corrected rows in one transaction.\"\n )\n .option(\"--execute\", \"Actually write the corrections (default is dry-run)\")\n .action((opts: { execute?: boolean }) => {\n cmdReconnect(getDb(), { execute: opts.execute });\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\n }\n\n // Never wait indefinitely on a lock. These are unbounded COUNT(*) scans\n // over a table the daemon writes continuously, so without a timeout this\n // command does not fail — it hangs, with no output and no explanation,\n // while `pai memory search` against the same data returns normally.\n // Measured 2026-08-02: killed at 60s having produced zero bytes.\n try {\n federation.pragma(\"busy_timeout = 4000\");\n } catch {\n /* older driver — the queries below still run, just without the guard */\n }\n\n // Say which database these numbers came from. With storageBackend set to\n // postgres the real index lives there, and this file holds whatever\n // SQLite still has — often far less. Reporting a small number as if it\n // were the whole index is worse than reporting nothing, because it looks\n // like an answer.\n // Stop here when the live index is not in this file.\n //\n // These queries read the SQLite federation database. With storageBackend\n // set to postgres that file holds almost nothing, so the figures would be\n // wrong — and wrong in the worst direction, because a small number reads\n // as an answer rather than as an absence. Reporting where the data\n // actually is beats reporting a confident zero.\n const backend = loadConfig().storageBackend;\n if (backend !== \"sqlite\") {\n console.log();\n console.log(warn(` Nothing to report from here.`));\n console.log(\n dim(` storageBackend is \"${backend}\", so the live index is not in the`)\n );\n console.log(dim(` SQLite federation database this command reads.`));\n console.log();\n console.log(` Authoritative counts: ${bold(\"pai daemon status\")}`);\n console.log();\n try {\n federation.close();\n } catch {\n /* already closed */\n }\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n }\n });\n}\n","/** Registers `pai memory sources` — what the indexer has taken in. */\n\nimport type { Command } from \"commander\";\nimport { err, dim } from \"../../utils.js\";\n\nexport function registerSourcesCommand(memoryCmd: Command): void {\n memoryCmd\n .command(\"sources\")\n .description(\n \"Show what the indexer has taken in: composition by source, which roots\\n\" +\n \"content enters from, the heaviest single files, and how many chunks are\\n\" +\n \"rewritten per day (which distinguishes a finite backlog from a treadmill).\"\n )\n .option(\"--limit <n>\", \"Rows per section (default 8)\", \"8\")\n .action(async (opts: { limit?: string }) => {\n // Reads whichever backend is configured — unlike `memory status`, which\n // reads the SQLite federation file directly and therefore has nothing to\n // say when the live index lives in Postgres.\n const { createStorageBackend } = await import(\"../../../storage/factory.js\");\n const { loadConfig } = await import(\"../../../daemon/config.js\");\n const { cmdMemorySources } = await import(\"./sources.js\");\n\n let backend;\n try {\n backend = await createStorageBackend(loadConfig());\n } catch (e) {\n console.error(err(`Cannot reach the storage backend.`));\n console.error(dim(` ${e instanceof Error ? e.message : String(e)}`));\n process.exitCode = 1;\n return;\n }\n\n try {\n await cmdMemorySources(backend, {\n limit: Math.max(1, parseInt(opts.limit ?? \"8\", 10) || 8),\n });\n } finally {\n await backend.close?.().catch?.(() => {});\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\";\nimport { registerSourcesCommand } from \"./sources-cmd.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 registerSourcesCommand(memoryCmd);\n}\n","/**\n * Who \"me\" is.\n *\n * One question is asked of this module: **is this address the user's own?**\n * It exists because the alternative is an assistant deciding that per message,\n * from context, at the moment it is about to act — and \"that looks like his\n * address\" is not a judgement worth re-making every time. Here it is a lookup\n * against a list the user maintains.\n *\n * The rule the list serves: a mail whose recipients are all the user's own may\n * be sent; anything else is a draft for review. So a false positive here means\n * mail leaving without review. Everything below is written to fail closed —\n * an empty list means nothing is self-addressed, an unparseable address is not\n * self-addressed, and no pattern is ever inferred.\n *\n * Aliases are NOT matched by pattern, deliberately. `matthias.nott+x@gmail.com`\n * and `owner.name@example.com` are the same mailbox, and Gmail also ignores\n * dots — but the general forms of those rules (\"same local part before a plus\",\n * \"same domain\") also match addresses belonging to other people. Gmail's own\n * normalisation is Gmail's; other providers treat `+` and `.` as ordinary\n * characters, and `first.last@company.com` is a different person from\n * `firstlast@company.com`. Anything that should count gets listed.\n */\n\n// ---------------------------------------------------------------------------\n// Normalisation\n// ---------------------------------------------------------------------------\n\n/**\n * Reduce an address to a comparable form: trimmed, unwrapped, lowercased.\n *\n * Handles `Display Name <addr@example.com>` because recipient fields carry that\n * form routinely. Returns null when there is nothing usable, which callers\n * treat as \"not the user's\" rather than as an error — an address that cannot be\n * parsed is one whose ownership cannot be established.\n */\nexport function normalizeEmail(raw: string | null | undefined): string | null {\n if (!raw) return null;\n\n let value = raw.trim();\n if (!value) return null;\n\n const angled = value.match(/<([^>]*)>/);\n if (angled) value = angled[1].trim();\n\n value = value.toLowerCase();\n\n // Must look like an address at all: exactly one @, with something either side\n // and a dot in the domain. This is a sanity check, not validation — its job\n // is to reject junk before it can be compared, not to police correctness.\n if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(value)) return null;\n\n return value;\n}\n\n// ---------------------------------------------------------------------------\n// Ownership\n// ---------------------------------------------------------------------------\n\nexport interface IdentityLike {\n selfEmails?: string[];\n deliverTo?: string;\n sendingAccount?: string;\n}\n\n/** Is this address one the user has declared as their own? */\nexport function isSelfAddress(address: string, identity: IdentityLike | undefined): boolean {\n const candidate = normalizeEmail(address);\n if (!candidate) return false;\n\n const own = (identity?.selfEmails ?? [])\n .map(normalizeEmail)\n .filter((e): e is string => e !== null);\n\n return own.includes(candidate);\n}\n\n/**\n * May a mail to these recipients be sent without review?\n *\n * Every recipient must be the user's own — To, Cc and Bcc together, since a\n * single outsider on Bcc is still an outsider. An empty recipient list is not\n * permission: there is nothing to check, so it is refused.\n *\n * Returns the offending addresses rather than a bare false, so the caller can\n * say which recipient forced the draft instead of reporting a flat refusal.\n */\nexport interface SendDecision {\n allowed: boolean;\n /** Recipients that are not the user's own. Empty when allowed. */\n foreign: string[];\n reason: string;\n}\n\nexport function maySendWithoutReview(\n recipients: Array<string | null | undefined>,\n identity: IdentityLike | undefined\n): SendDecision {\n const listed = recipients.filter((r): r is string => Boolean(r && r.trim()));\n\n if (listed.length === 0) {\n return { allowed: false, foreign: [], reason: \"No recipients to check\" };\n }\n\n if (!identity?.selfEmails?.length) {\n return {\n allowed: false,\n foreign: listed,\n reason:\n \"No identity.selfEmails configured — nothing counts as your own address yet. \" +\n \"Run `pai identity add <email>`.\",\n };\n }\n\n const foreign = listed.filter((r) => !isSelfAddress(r, identity));\n\n if (foreign.length > 0) {\n return {\n allowed: false,\n foreign,\n reason: `Not your own address: ${foreign.join(\", \")} — draft for review instead`,\n };\n }\n\n return { allowed: true, foreign: [], reason: \"All recipients are your own address\" };\n}\n\n// ---------------------------------------------------------------------------\n// Delivery reachability\n// ---------------------------------------------------------------------------\n\n/**\n * Would a mail from `sendingAccount` to `deliverTo` actually reach an inbox?\n *\n * Gmail files a message sent from an account to itself, or to one of its own\n * domain aliases, under Sent only — it never appears in the inbox. The send\n * reports success, so the failure is invisible and indistinguishable from\n * delivery. This cost a real digest on 2026-08-01: owner@example.ch →\n * owner@example.de, sent cleanly, never seen.\n *\n * Detecting it in general is not possible from the addresses alone — whether\n * two addresses are the same Google account is a fact about the account, not\n * about their spelling. So this reports what it can see and says so plainly:\n * an exact match is certain, a shared domain is suspicious and worth warning\n * about, and anything else is unknown rather than fine.\n */\nexport type Reachability = \"unreachable\" | \"suspect\" | \"unknown\";\n\nexport interface ReachabilityVerdict {\n verdict: Reachability;\n reason: string;\n}\n\nexport function checkDeliveryReachability(\n deliverTo: string | undefined,\n sendingAccount: string | undefined,\n sendingAccountAliases: string[] = []\n): ReachabilityVerdict {\n const to = normalizeEmail(deliverTo);\n const from = normalizeEmail(sendingAccount);\n\n if (!to || !from) {\n return { verdict: \"unknown\", reason: \"Delivery address or sending account not configured\" };\n }\n\n if (to === from) {\n return {\n verdict: \"unreachable\",\n reason:\n `${to} is the sending account itself. Gmail files self-sends under Sent only — ` +\n `the send will report success and never reach the inbox.`,\n };\n }\n\n // The declared alias list is the only reliable signal, and it is why the list\n // exists. The case that actually cost a digest was owner@example.ch sending to\n // owner@example.de — one Google account behind two DIFFERENT domains, which no\n // comparison of the addresses themselves can reveal. Whether two addresses\n // are one mailbox is a fact about the account, not about their spelling.\n const aliases = sendingAccountAliases\n .map(normalizeEmail)\n .filter((e): e is string => e !== null);\n\n if (aliases.includes(to)) {\n return {\n verdict: \"unreachable\",\n reason:\n `${to} is a declared alias of the sending account (${from}). The send will report ` +\n `success and never reach the inbox. Deliver by adding the INBOX label to the ` +\n `written message instead.`,\n };\n }\n\n const toDomain = to.slice(to.indexOf(\"@\") + 1);\n const fromDomain = from.slice(from.indexOf(\"@\") + 1);\n\n if (toDomain === fromDomain) {\n return {\n verdict: \"suspect\",\n reason:\n `${to} shares a domain with the sending account (${from}). If it is an alias of the ` +\n `same account the send will silently not arrive. Deliver by adding the INBOX label ` +\n `to the written message rather than relying on the send path.`,\n };\n }\n\n // Deliberately not \"fine\". Cross-domain aliases of one account look exactly\n // like this and are undetectable here — which is why label-based delivery is\n // the safe default even when this returns unknown.\n return {\n verdict: \"unknown\",\n reason:\n \"Separate domains and not a declared alias — no suppression predictable from the \" +\n \"addresses alone, which is not the same as delivery being guaranteed.\",\n };\n}\n","/**\n * `pai identity` — declare who \"me\" is.\n *\n * The list this edits is an allowlist for actions that reach the outside world\n * without review, outbound mail being the case it was built for. Adding an\n * address widens what can be sent unreviewed, so every mutation here says what\n * it changed and the command warns rather than staying quiet when a setting\n * looks like it will not work.\n *\n * Edits the same `~/.config/pai/config.json` the daemon reads, preserving\n * everything else in the file — including the `_comment` keys a user may have\n * left for themselves.\n */\n\nimport type { Command } from \"commander\";\nimport { readFileSync, writeFileSync, existsSync, renameSync } from \"node:fs\";\nimport { CONFIG_FILE } from \"../../daemon/config.js\";\nimport {\n normalizeEmail,\n checkDeliveryReachability,\n type IdentityLike,\n} from \"../../identity/index.js\";\nimport { ok, err, warn, dim, bold } from \"../utils.js\";\n\ninterface ConfigShape {\n identity?: IdentityLike & { sendingAccountAliases?: string[] };\n [key: string]: unknown;\n}\n\nfunction readConfig(): ConfigShape {\n if (!existsSync(CONFIG_FILE)) return {};\n try {\n return JSON.parse(readFileSync(CONFIG_FILE, \"utf-8\")) as ConfigShape;\n } catch (e) {\n console.error(err(\"pai identity: \") + `Could not parse ${CONFIG_FILE}: ${String(e)}`);\n process.exit(1);\n }\n}\n\n/**\n * Write via a temp file and rename, so an interrupted write cannot leave the\n * config truncated. The daemon reads this file; a half-written one takes the\n * whole system down rather than just this command.\n */\nfunction writeConfig(config: ConfigShape): void {\n const tmp = `${CONFIG_FILE}.identity.tmp`;\n writeFileSync(tmp, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n renameSync(tmp, CONFIG_FILE);\n}\n\nfunction identityOf(config: ConfigShape) {\n return config.identity ?? { selfEmails: [] };\n}\n\nfunction reportReachability(id: IdentityLike & { sendingAccountAliases?: string[] }): void {\n const v = checkDeliveryReachability(\n id.deliverTo,\n id.sendingAccount,\n id.sendingAccountAliases ?? []\n );\n if (v.verdict === \"unreachable\") {\n console.log(err(\" Unreachable: \") + v.reason);\n } else if (v.verdict === \"suspect\") {\n console.log(warn(\" Suspect: \") + v.reason);\n }\n}\n\nexport function registerIdentityCommands(identityCmd: Command): void {\n identityCmd\n .command(\"show\")\n .description(\"Show the configured identity\")\n .action(() => {\n const id = identityOf(readConfig());\n const emails = id.selfEmails ?? [];\n\n console.log();\n console.log(bold(\" Identity\"));\n console.log();\n console.log(` Deliver to: ${id.deliverTo ?? dim(\"not set\")}`);\n console.log(` Sending account: ${id.sendingAccount ?? dim(\"not set\")}`);\n console.log();\n\n if (emails.length === 0) {\n console.log(warn(\" No self addresses configured.\"));\n console.log(\n dim(\" Nothing counts as your own address, so every mail is drafts-only.\")\n );\n console.log(dim(\" Add one with: pai identity add <email>\"));\n } else {\n console.log(bold(` Your own addresses (${emails.length}):`));\n for (const e of emails) console.log(` ${e}`);\n console.log();\n console.log(\n dim(\" Mail addressed only to these may be sent; anything else is a draft.\")\n );\n }\n console.log();\n reportReachability(id);\n console.log();\n });\n\n identityCmd\n .command(\"add <email>\")\n .description(\"Declare an address as your own (widens what can be sent unreviewed)\")\n .action((email: string) => {\n const normalized = normalizeEmail(email);\n if (!normalized) {\n console.error(err(\"pai identity: \") + `Not a usable email address: ${email}`);\n process.exitCode = 1;\n return;\n }\n\n const config = readConfig();\n const id = identityOf(config);\n const emails = (id.selfEmails ?? []).slice();\n\n if (emails.map(normalizeEmail).includes(normalized)) {\n console.log(dim(` ${normalized} is already listed. Nothing changed.`));\n return;\n }\n\n emails.push(normalized);\n config.identity = { ...id, selfEmails: emails };\n writeConfig(config);\n\n console.log(ok(\"Added. \") + `${normalized} now counts as your own address.`);\n console.log(dim(` Mail addressed only to your own addresses may now be sent unreviewed.`));\n });\n\n identityCmd\n .command(\"remove <email>\")\n .description(\"Stop treating an address as your own\")\n .action((email: string) => {\n const normalized = normalizeEmail(email);\n const config = readConfig();\n const id = identityOf(config);\n const before = id.selfEmails ?? [];\n const after = before.filter((e) => normalizeEmail(e) !== normalized);\n\n if (after.length === before.length) {\n console.log(dim(` ${email} was not listed. Nothing changed.`));\n return;\n }\n\n config.identity = { ...id, selfEmails: after };\n writeConfig(config);\n console.log(ok(\"Removed. \") + `${email} is no longer treated as your own.`);\n });\n\n identityCmd\n .command(\"set-delivery <email>\")\n .description(\"Set where digests and 'mail me X' are delivered\")\n .action((email: string) => {\n const normalized = normalizeEmail(email);\n if (!normalized) {\n console.error(err(\"pai identity: \") + `Not a usable email address: ${email}`);\n process.exitCode = 1;\n return;\n }\n\n const config = readConfig();\n const id = identityOf(config);\n config.identity = { ...id, deliverTo: normalized };\n writeConfig(config);\n\n console.log(ok(\"Delivery address set. \") + normalized);\n\n // Warn immediately rather than at send time. A delivery address that\n // cannot arrive fails silently — the send reports success — so the only\n // useful moment to say so is when it is being configured.\n reportReachability(config.identity);\n\n if (!(id.selfEmails ?? []).map(normalizeEmail).includes(normalized)) {\n console.log(\n warn(\" Note: \") +\n `${normalized} is not in your self-address list, so mail to it would still be ` +\n `drafted rather than sent. Run: pai identity add ${normalized}`\n );\n }\n });\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.exitCode = 1;\n return;\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 * daemon-status.ts — the health part of `pai daemon status`, as data.\n *\n * The daemon has always sent its work-queue depth in the status payload and the\n * command has never printed it. That is the remaining half of the July finding:\n * the queue backing up is what an outage actually looks like from outside —\n * `session-end`, `session-summary` and `registry-scan` enqueued and never\n * drained, session notes for two days never written — and it was the one number\n * that would have said so.\n *\n * Kept separate from the printing, and pure, because \"does status report the\n * outage\" is exactly the assertion that was missing when status reported\n * \"Index: idle\" through a two-day outage. A function that only writes to stdout\n * cannot be asked that question.\n */\n\nimport type { BackendOutage } from \"../../storage/outage.js\";\nimport { humanDuration } from \"../../storage/outage.js\";\n\nexport type HealthSeverity = \"error\" | \"warn\" | \"info\";\n\nexport interface HealthLine {\n severity: HealthSeverity;\n text: string;\n}\n\n/** The subset of the daemon status payload that describes storage health. */\nexport interface StorageHealthStatus {\n backendOutage?: BackendOutage | null;\n workQueue?: {\n pending?: number;\n processing?: number;\n completed?: number;\n failed?: number;\n total?: number;\n } | null;\n}\n\n/**\n * Health lines to print above the index/DB figures, worst first.\n *\n * Empty when there is nothing wrong: a healthy daemon should not grow a\n * paragraph of reassurance, or the one time it says something real gets read as\n * more of the same.\n */\nexport function formatStorageHealth(\n s: StorageHealthStatus,\n now = Date.now()\n): HealthLine[] {\n const lines: HealthLine[] = [];\n const outage = s.backendOutage ?? null;\n\n if (outage) {\n lines.push({\n severity: \"error\",\n text: `BACKEND DOWN: ${outage.backend} unreachable for ${humanDuration(now - outage.since)}`,\n });\n lines.push({\n severity: \"warn\",\n text: `${outage.attempts} attempts — ${outage.lastError}`,\n });\n }\n\n const queue = queueLine(s.workQueue, outage !== null);\n if (queue) lines.push(queue);\n\n if (outage) {\n lines.push({\n severity: \"info\",\n text: \"Indexing, session notes and the work queue are stalled until it returns.\",\n });\n }\n\n return lines;\n}\n\n/**\n * The queue depth, when it means something.\n *\n * During an outage it always means something — including a depth of zero, which\n * says the backlog is not the reason nothing is happening. Otherwise it is only\n * worth a line when work is actually sitting there: a queue that is empty\n * because everything drained is the normal case, and printing it every time\n * teaches the reader to skip the line.\n *\n * `failed` is reported whenever it is non-zero, outage or not. Items that\n * exhausted their retries are the silent-loss case this whole status block\n * exists for — they will never be retried and nothing else surfaces them.\n */\nfunction queueLine(\n q: StorageHealthStatus[\"workQueue\"],\n duringOutage: boolean\n): HealthLine | null {\n if (!q) return null;\n\n const pending = q.pending ?? 0;\n const processing = q.processing ?? 0;\n const failed = q.failed ?? 0;\n const waiting = pending + processing;\n\n if (!duringOutage && waiting === 0 && failed === 0) return null;\n\n const parts = [`${pending} pending`];\n if (processing > 0) parts.push(`${processing} processing`);\n if (failed > 0) parts.push(`${failed} failed`);\n\n return {\n // A backlog while the backend is down is a symptom, not a fault of its own;\n // failures that will never retry are a fault whether or not anything is down.\n severity: failed > 0 ? \"error\" : duringOutage || waiting > 0 ? \"warn\" : \"info\",\n text: `Work queue: ${parts.join(\", \")}`,\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\";\nimport { daemonLogPath } from \"../../runtime-paths.js\";\nimport { formatStorageHealth, type StorageHealthStatus } from \"./daemon-status.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 = daemonLogPath();\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\n // Lead with the outage and the backlog. Everything below them — index\n // state, totals — describes a system that is not moving, and printing those\n // first is how \"Index: idle\" came to mean \"all is well\" during a two-day\n // outage.\n const health = formatStorageHealth(s as StorageHealthStatus);\n if (health.length > 0) {\n console.log();\n for (const line of health) {\n const paint = line.severity === \"error\" ? err : line.severity === \"warn\" ? warn : dim;\n console.log(paint(` ${line.text}`));\n }\n console.log();\n }\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.exitCode = 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.exitCode = 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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 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.exitCode = 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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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 return;\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.exitCode = 1;\n return;\n }\n });\n}\n","/**\n * Safe process termination for the CLI.\n *\n * Node's stdout/stderr are ASYNCHRONOUS when they point at a pipe (and\n * synchronous when they point at a TTY or a regular file). `process.exit()`\n * tears the process down immediately, before any still-in-flight async\n * write completes — so a command whose output is piped (`pai ... | less`,\n * `| wc -c`, a subshell capture, etc.) can have its output silently\n * truncated or dropped entirely, while the same command run against a TTY\n * or redirected to a file prints correctly. Exit code is still 0, so the\n * failure is invisible.\n *\n * The robust fix, used everywhere it is possible, is to never call\n * `process.exit()` at all: set `process.exitCode` and let the command\n * return normally. Once the event loop has nothing left to do, Node exits\n * on its own — and a natural exit always flushes pending writes first.\n *\n * `exitAfterFlush` exists for the rare call site where control flow\n * genuinely cannot return (e.g. deep inside a callback with no path back\n * to the caller). It waits for both stdout and stderr to drain before\n * calling `process.exit()`.\n */\n\nfunction streamDrained(stream: NodeJS.WriteStream): Promise<void> {\n // `writableLength` is the number of bytes still buffered and not yet\n // handed to the OS. If it's already 0, there is nothing to wait for.\n if (!stream.writableLength) return Promise.resolve();\n return new Promise((resolve) => {\n stream.once(\"drain\", () => resolve());\n // Belt-and-braces: also resolve on the next tick after a short poll,\n // in case `drain` was already emitted before we attached the listener\n // (rare, but writableLength can hit 0 without a fresh `drain` event on\n // some platforms/versions).\n const check = () => {\n if (!stream.writableLength) resolve();\n };\n setImmediate(check);\n });\n}\n\n/**\n * Wait for stdout and stderr to finish flushing, then exit with `code`.\n * Never returns (the return type documents that; callers should not\n * expect code after this call to run).\n */\nexport async function exitAfterFlush(code: number): Promise<never> {\n await drainStdio();\n process.exit(code);\n}\n\n/**\n * Wait for stdout and stderr to finish flushing, without exiting.\n *\n * Belt-and-suspenders backstop for cli/index.ts: every command path avoids\n * process.exit() so a natural process exit — which always flushes pending\n * writes — can do the flushing. This makes that explicit and awaited at the\n * one place every command path funnels through, instead of trusting that\n * nothing between here and process shutdown (a dependency's keep-alive\n * socket being unref'd, a timer, anything else that can make the event loop\n * look \"empty\") lets Node begin tearing down before a write that's still\n * asynchronously in flight to a pipe has actually reached the OS.\n */\nexport async function drainStdio(): Promise<void> {\n await Promise.all([streamDrained(process.stdout), streamDrained(process.stderr)]);\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\";\nimport { exitAfterFlush } from \"../../lib/exit.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 // Control can't return here — readline is still blocking on input, so\n // there is no natural exit to fall through to. exitAfterFlush() waits\n // for the lines above to actually reach the OS before terminating.\n void exitAfterFlush(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 installLink(join(hooksDir, \"session-autosave.sh\"), join(hooksTarget, \"pai-session-autosave.sh\"), \"pai-session-autosave.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 // Rolling checkpoint. The model is not invoked on /exit and never on\n // Ctrl+C, so a handover written at exit time is impossible — it has to\n // already exist. Rate-limited internally; safe on high-frequency events.\n { hookType: \"UserPromptSubmit\", command: \"${PAI_DIR}/Hooks/pai-session-autosave.sh\" },\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: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/pai-session-autosave.sh\" },\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 * Where a task marked only with the bare `pai` marker goes. Omit to leave\n * such a task unrouted, which sends it to the findings inbox for triage.\n */\n defaultOwner?: string | null;\n}\n\n/**\n * Does this task carry the bare `pai` marker?\n *\n * The marker means \"an AI should take this, and I do not know which one yet\" —\n * the single thing a task's location cannot express, and the reason to keep any\n * label mechanism at all. Everything else is answered by the project it sits\n * in, which is single-valued, visible, and changed by the act of moving it.\n *\n * Deliberately distinct from `pai:<name>`: that names a specific owner, this\n * names none. Previously a bare `pai` was a near miss — it looked like an\n * address and resolved to nothing — which meant the most natural thing to type\n * was the one thing that silently did nothing.\n */\nexport function hasDefaultMarker(labels: string[]): boolean {\n return labels.some((l) => l.trim().toLowerCase() === \"pai\");\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 // CONTAINER FIRST. A task sits in exactly one project; it can carry many\n // labels. A multi-valued field cannot be the authoritative owner — with two\n // `pai:` labels there is no principled winner, only whichever the code\n // happens to see first.\n //\n // Labels also survive a move. On 2026-08-02 a task was moved from Clickr to\n // AIBroker in the tracker UI and a comment on it was delivered to Clickr,\n // because a label from its old home outranked the project it now sat in. The\n // user had not seen the label and had not used labels in months. A field\n // nobody is looking at should not outrank the one they just changed\n // deliberately.\n //\n // So: the container decides whenever it resolves. Moving a task is then the\n // whole act of re-assigning it, which is what someone dragging it between\n // projects already believes they are doing.\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 }\n\n // Labels remain the way to address a task whose container says nothing —\n // sitting in the Inbox, at the bus root, or in a project that mirrors no PAI\n // project. That is the case they are good at, and the only one where nothing\n // else can express the intent.\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: someone\n // meant to route this somewhere and named a project that does not exist.\n return { ...UNROUTED, rawHint: label };\n }\n\n // The bare marker: an AI should take this, nobody said which. Last, because\n // anything more specific is a better answer — this is the Inbox case, where\n // there is no location to read and no owner named.\n if (input.defaultOwner && hasDefaultMarker(input.labels)) {\n const hit = aliases.get(normalize(input.defaultOwner));\n if (hit) {\n return { project: hit.alias, rootPath: hit.rootPath, source: \"label\" };\n }\n return { ...UNROUTED, rawHint: input.defaultOwner };\n }\n\n // Container present but unrecognised, and nothing else to go on. Report what\n // was tried — \"Reading List 📚\" maps to no PAI project, which is expected\n // rather than a fault.\n if (input.container) return { ...UNROUTED, rawHint: input.container };\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 * True when the project has collaborators — i.e. someone other than the user\n * can create tasks in it. See ownerContainers for why that matters here.\n */\n is_shared?: 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; string?: string; is_recurring?: boolean } | 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 */\n/** A bare calendar date — the only shape `due_date` accepts. */\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** A project inside the bus, and the sub-project whose name decides its owner. */\nexport interface OwnerContainer {\n project: WireProject;\n /** Name of the nearest ancestor directly under the root. Null at the root. */\n ownerName: string | null;\n}\n\n/**\n * Every project in the bus subtree, each mapped to the name that owns it.\n *\n * Ownership belongs to the sub-projects directly under the root — they are the\n * ones mirroring PAI projects. Anything deeper is a FOLDER: a way to group work\n * inside one owner's inbox, not a new owner. So `Claude / Jobs Alpha /\n * Executive Search` is eighteen tasks belonging to Jobs Alpha, filed under a\n * heading.\n *\n * This used to take the root and its direct children only. A grandchild was not\n * merely unrouted, it was never QUERIED — its tasks did not exist as far as the\n * bus was concerned. Which is the worst possible failure for this: the user\n * organises work into a sub-project, it looks tidy, and nothing is ever\n * dispatched. Observed 2026-08-02 with exactly the tree above.\n *\n * Descends from the root rather than walking up from each project, so a cycle\n * in the parent chain — which the API should never produce, but a bad write\n * could — cannot hang the poller.\n */\nexport function ownerContainers(\n projects: WireProject[],\n root: string\n): Map<string, OwnerContainer> {\n const live = projects.filter((p) => !p.is_archived && !p.is_deleted);\n const childrenOf = new Map<string, WireProject[]>();\n for (const p of live) {\n if (!p.parent_id) continue;\n const siblings = childrenOf.get(p.parent_id);\n if (siblings) siblings.push(p);\n else childrenOf.set(p.parent_id, [p]);\n }\n\n const out = new Map<string, OwnerContainer>();\n const rootProject = live.find((p) => p.id === root);\n if (!rootProject) return out;\n\n // The root carries no owner name: a task sitting there is unrouted, which is\n // how the findings inbox is meant to work.\n out.set(root, { project: rootProject, ownerName: null });\n\n const walk = (project: WireProject, ownerName: string): void => {\n if (out.has(project.id)) return; // cycle guard\n\n // A shared project is excluded, along with everything under it.\n //\n // Inheriting ownership down a subtree means a project only has to be MOVED\n // under the root to gain the right to dispatch work into a session. For a\n // project the user owns that is the entire point. For a shared one it is an\n // escalation: a collaborator writes the task, and the task is a work order\n // that spawns a terminal and types into it. Nobody reparents a project\n // thinking about that.\n //\n // Excluded rather than merely unrouted, and announced rather than dropped\n // quietly — a boundary that narrows in silence is the same defect as one\n // that widens in silence. AIBroker reached the same conclusion from the\n // other side on 2026-08-02 and made subtree grants opt-in; this is the\n // equivalent for a poller that has no grant to opt into.\n if (project.is_shared) {\n process.stderr.write(\n `pai: skipping shared project \"${project.name}\" (${project.id}) and anything under it — ` +\n `tasks there can be written by a collaborator, and a dispatched task runs as you.\\n`\n );\n return;\n }\n\n out.set(project.id, { project, ownerName });\n for (const child of childrenOf.get(project.id) ?? []) walk(child, ownerName);\n };\n\n for (const top of childrenOf.get(root) ?? []) walk(top, top.name);\n\n return out;\n}\n\n/**\n * Report a write that the server silently shortened.\n *\n * Todoist caps a task description at 16,383 characters and enforces it by\n * TRUNCATION, not by rejection: the request returns 200 and the response says\n * success. Measured 2026-08-02 — a 19,457-character runbook stored as 16,383,\n * losing 3,074 characters off the end with no error anywhere. What went missing\n * was the close-out section, so a session working from it would have done the\n * job correctly and then never marked the task done.\n *\n * The general form is worth more than the specific limit, which is why this\n * compares what came back rather than checking a number: an API that reports\n * success for an operation that did not fully happen is invisible until someone\n * inspects the artifact instead of the return value. Any sink with an unknown\n * limit deserves the same treatment.\n *\n * Warns rather than throws. The task exists and is usable; failing the call\n * would discard work that mostly succeeded. But it must not pass silently.\n *\n * THIS DOES NOT PREVENT THE LOSS. The truncation still happens and the tail is\n * still gone — all this does is turn a silent data-loss bug into a loud one.\n * That is a large improvement and it is not a resolution, and the difference\n * matters because the obvious wrong conclusion from reading this function is\n * \"handled, so long content is fine now\". It is not: whether anything is done\n * about the warning depends on someone reading stderr. Long content belongs in\n * a file, with the field pointing at it. Visible failure is not absence of\n * failure.\n */\nexport function warnIfTruncated(field: string, sent: string | undefined, stored: string | undefined): void {\n const before = sent?.length ?? 0;\n const after = stored?.length ?? 0;\n if (before === 0 || after >= before) return;\n\n process.stderr.write(\n `\\npai: WARNING — Todoist truncated the ${field}.\\n` +\n ` sent ${before} characters, stored ${after} — ${before - after} lost, silently.\\n` +\n ` The API reported success. Anything at the end of that text is gone.\\n` +\n ` Put long content in a file and reference it from the ${field} instead.\\n\\n`\n );\n}\n\n/**\n * Build the due portion of a task write.\n *\n * Exported for testing: the whole defect was a one-word field choice, so the\n * choice itself is what needs pinning.\n */\nexport function dueField(\n due: string | null | undefined\n): Record<string, string> {\n const value = due?.trim();\n if (!value) return {};\n return ISO_DATE.test(value) ? { due_date: value } : { due_string: value };\n}\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/** Strip emoji and case so \"Whazaa 🐝\" and \"whazaa\" compare equal. */\nfunction normalizeName(raw: string): string {\n return raw\n .replace(/[\\u{1F000}-\\u{1FAFF}\\u{2600}-\\u{27BF}\\u{FE0F}\\u{200D}]/gu, \"\")\n .trim()\n .toLowerCase();\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 /** Bus-level settings. Optional so existing callers keep working. */\n private readonly taskConfig?: { defaultOwner?: string },\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 /** Owner for a task carrying only the bare `pai` marker. See ResolveInput. */\n private get defaultOwner(): string | null {\n return this.taskConfig?.defaultOwner ?? null;\n }\n\n /**\n * Fetch one task by id, open or completed.\n *\n * `listOpen` cannot serve this: a completed task is gone from that list, and\n * the whole point of archiving is to run at or after completion. Ownership is\n * resolved the same way as in listOpen — label first, then the container\n * walked up to the sub-project directly under the bus root.\n */\n async getTask(id: string): Promise<Task | null> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) return null;\n\n let w: WireTask;\n try {\n w = await call<WireTask>(token, `/tasks/${encodeURIComponent(id)}`);\n } catch {\n return null;\n }\n if (!w?.id) return null;\n\n const projects = await collect<WireProject>(token, \"/projects\");\n const owners = ownerContainers(projects, this.config.rootProjectId);\n const entry = owners.get(w.project_id ?? \"\");\n\n const owner = resolveOwner(\n { labels: w.labels ?? [], container: entry?.ownerName ?? null, defaultOwner: this.defaultOwner },\n this.aliases\n );\n\n return {\n id: w.id,\n title: w.content,\n body: w.description ?? \"\",\n owner,\n due: w.due?.datetime ?? w.due?.date ?? null,\n recurrence: w.due?.is_recurring ? (w.due.string ?? null) : null,\n priority: toPriority(w.priority),\n labels: w.labels ?? [],\n sourceUrl: `https://app.todoist.com/app/task/${w.id}`,\n };\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 owners = ownerContainers(projects, root);\n if (owners.size === 0) return [];\n const inBus = new Map([...owners].map(([id, o]) => [id, o.project]));\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 entry = owners.get(w.project_id);\n if (!entry) continue;\n\n // The owning name is the nearest ancestor directly under the root, NOT\n // the project the task literally sits in. The bus root itself is not an\n // owner — a task at the root has no container hint.\n const owner = resolveOwner(\n { labels: w.labels ?? [], container: entry.ownerName, defaultOwner: this.defaultOwner },\n this.aliases\n );\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 recurrence: w.due?.is_recurring ? (w.due.string ?? null) : null,\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 /**\n * Choose the wire field for a due value.\n *\n * `due_date` takes an ISO date and nothing else — \"tomorrow\" or \"every day\"\n * come back as HTTP 400. Recurrence in particular is only expressible through\n * `due_string`, which is the field Todoist runs its natural-language parser\n * over. Sending everything as a date is what made `pai task add --repeat`\n * impossible to write, and a repeating task is the whole mechanism behind a\n * self-rescheduling routine on the bus.\n *\n * Strict ISO still goes to `due_date` rather than through the parser. The\n * parser is locale-sensitive and there is no reason to hand it a value that\n * is already unambiguous — this keeps existing ISO callers on exactly the\n * path they were on.\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 // Prefer LOCATION over a label. Ownership resolves from the project a task\n // sits in, so filing it into the owner's sub-project says the same thing\n // the label would — visibly, in one place, and in the field someone changes\n // when they re-assign it. A label added here would only be a second, quieter\n // answer to the same question, and one that survives a move: on 2026-08-02 a\n // task moved between projects was still routed by its old label.\n //\n // Only when no sub-project mirrors this owner does the label do real work,\n // and then it is the sole way to express the intent.\n let ownerProjectId: string | null = null;\n if (task.owner && !task.into) {\n ownerProjectId = await this.subProjectForOwner(task.owner);\n }\n\n const labels = [...(task.labels ?? [])];\n const needsLabel = task.owner && !task.into && !ownerProjectId;\n if (\n needsLabel &&\n !labels.some((l) => l.toLowerCase() === `pai:${task.owner!.toLowerCase()}`)\n ) {\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 || task.into ? undefined : this.config.findingsSectionId;\n\n // Filing into a per-project sub-project keeps findings from piling up flat\n // in the root, where they bury each other across projects.\n const projectId = task.into\n ? (await this.findOrCreateSubProject(task.into)).id\n : (ownerProjectId ?? this.config.rootProjectId);\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: projectId,\n ...(sectionId ? { section_id: sectionId } : {}),\n labels,\n priority: fromPriority(task.priority),\n ...dueField(task.due),\n },\n });\n\n // The POST already returns what was stored, so this costs nothing.\n warnIfTruncated(\"task description\", task.body, created.description ?? \"\");\n\n return {\n id: created.id,\n title: created.content,\n body: created.description ?? \"\",\n owner: task.owner\n ? resolveOwner({ labels, container: null, defaultOwner: this.defaultOwner }, 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 /**\n * Move a task's due date, expressed in Todoist's natural language.\n *\n * Always `due_string`, never `due_date`: writing a bare date to a recurring\n * task drops the recurrence and turns a routine into a one-off, silently. To\n * keep both, the caller passes the rule and the date together — Todoist\n * accepts \"every day at 08:00 starting 2026-08-02\" and honours each half.\n */\n async setDue(id: string, dueString: 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: { due_string: dueString } });\n }\n\n /**\n * Find the sub-project named `name` under the bus root, creating it if absent.\n *\n * Per-project sub-projects are the filing convention: a flat pile in the root\n * buries findings across projects. This exists so the convention can be\n * executed rather than re-derived — a session that has to infer structure\n * from the existing project list will sometimes infer cautiously and file\n * flat, which is the mess the convention prevents.\n *\n * Matching is case-insensitive and ignores decoration, so \"Whazaa\" finds an\n * existing \"Whazaa 🐝\" rather than creating a near-duplicate.\n */\n /**\n * The sub-projects under the bus root — i.e. every address a task can be\n * filed against. Archived and deleted ones are excluded: they cannot receive\n * work, so listing them would overstate what is reachable.\n */\n /**\n * The existing sub-project that resolves to this owner, if there is one.\n *\n * Matched by running the ordinary ownership resolution over each sub-project\n * name rather than comparing strings, because a display name is rarely its\n * PAI slug — \"AIBroker\" is `broker`, \"SL\" is `seriousletter`. Reusing the\n * resolver means filing and reading cannot disagree about where a task\n * belongs, which is a class of bug we have already paid for.\n *\n * Deliberately does NOT create anything. Filing a task should not bring a new\n * address into existence as a side effect; that is `--into`'s job, asked for\n * explicitly.\n */\n private async subProjectForOwner(owner: string): Promise<string | null> {\n try {\n for (const p of await this.listSubProjects()) {\n const resolved = resolveOwner({ labels: [], container: p.name }, this.aliases);\n if (resolved.project === owner) return p.id;\n }\n } catch {\n // Unreachable tracker — fall back to the label, which still routes.\n }\n return null;\n }\n\n async listSubProjects(): Promise<Array<{ id: string; name: string }>> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) {\n throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n }\n\n const root = this.config.rootProjectId;\n const projects = await collect<WireProject>(token, \"/projects\");\n return projects\n .filter((p) => !p.is_archived && !p.is_deleted && p.parent_id === root)\n .map((p) => ({ id: p.id, name: p.name }));\n }\n\n async findOrCreateSubProject(name: string): Promise<{ id: string; created: boolean }> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) {\n throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n }\n\n const root = this.config.rootProjectId;\n const projects = await collect<WireProject>(token, \"/projects\");\n const want = normalizeName(name);\n\n for (const p of projects) {\n if (p.is_archived || p.is_deleted) continue;\n if (p.parent_id === root && normalizeName(p.name) === want) {\n return { id: p.id, created: false };\n }\n }\n\n const created = await call<WireProject>(token, \"/projects\", {\n method: \"POST\",\n body: { name, parent_id: root },\n });\n return { id: created.id, created: true };\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 const created = await call<{ content?: string }>(token, `/comments`, {\n method: \"POST\",\n body: { task_id: id, content },\n });\n // Comments have their own cap, and the same truncate-and-report-success\n // behaviour. A run history that quietly loses its tail is worse than none,\n // because it reads as complete.\n warnIfTruncated(\"comment\", content, created?.content ?? \"\");\n }\n\n /** Comments on one task, oldest first. */\n async listComments(\n taskId: string\n ): Promise<Array<{ id: string; content: string; postedAt?: string }>> {\n const token = this.token();\n if (!token) return [];\n const wire = await collect<{ id: string; content?: string; posted_at?: string }>(\n token,\n `/comments?task_id=${encodeURIComponent(taskId)}`\n );\n // posted_at is carried because the archive is a record: a discussion with\n // no dates cannot be reconciled against anything that happened around it.\n return wire.map((c) => ({ id: c.id, content: c.content ?? \"\", postedAt: c.posted_at }));\n }\n\n async deleteComment(commentId: 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/${commentId}`, { method: \"DELETE\" });\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 * install.ts — `pai worker install`: one command, whole migration.\n *\n * Three things:\n *\n * 1. ~/.claude/settings.json: point the PreToolUse \"Agent\" hook at\n * ${PAI_DIR}/Hooks/route-agents-to-worker.mjs, replacing the old\n * route-agents-to-glm.sh registration if present.\n * 2. ~/.local/bin compatibility shims (glm, glm-run, glm-ps, glm-log) that\n * exec the pai equivalents, so every existing habit, alias and script\n * keeps working. Anything already at those paths is moved aside to\n * <name>.pre-pai once — never deleted.\n * 3. Remove the old route-agents-to-glm.sh from ~/.claude/Hooks (the\n * settings.json entry is gone, so the file is dead).\n *\n * Idempotent: running it twice changes nothing the second time.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, renameSync, unlinkSync, writeFileSync, chmodSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readSettingsJson, writeSettingsJson } from \"../cli/commands/settings-manager.js\";\n\nconst CLAUDE_DIR = join(homedir(), \".claude\");\nconst HOOKS_DIR = join(CLAUDE_DIR, \"Hooks\");\nconst LOCAL_BIN = join(homedir(), \".local\", \"bin\");\n\nexport const NEW_AGENT_HOOK = \"${PAI_DIR}/Hooks/route-agents-to-worker.mjs\";\nconst OLD_AGENT_HOOK_STEM = \"route-agents-to-glm\";\n\nexport interface InstallResult {\n lines: string[];\n changed: boolean;\n}\n\n/** Absolute path of the pai executable to hard-wire into the shims. */\nfunction paiPath(): string {\n try {\n return execFileSync(\"/usr/bin/which\", [\"pai\"], { encoding: \"utf8\" }).trim() || \"pai\";\n } catch {\n return \"pai\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// 1. settings.json: Agent hook migration\n// ---------------------------------------------------------------------------\n\nfunction patchAgentHook(lines: string[]): boolean {\n const settings = readSettingsJson();\n const hooks = (typeof settings[\"hooks\"] === \"object\" && settings[\"hooks\"] !== null\n ? settings[\"hooks\"]\n : {}) as Record<string, unknown>;\n\n const preToolUse = Array.isArray(hooks[\"PreToolUse\"])\n ? (hooks[\"PreToolUse\"] as Array<Record<string, unknown>>)\n : [];\n\n let changed = false;\n let replaced = false;\n\n for (const rule of preToolUse) {\n if (rule[\"matcher\"] !== \"Agent\") continue;\n const entries = Array.isArray(rule[\"hooks\"]) ? (rule[\"hooks\"] as Array<Record<string, unknown>>) : [];\n for (const entry of entries) {\n const cmd = typeof entry[\"command\"] === \"string\" ? entry[\"command\"] : \"\";\n if (cmd === NEW_AGENT_HOOK) replaced = true;\n if (cmd.includes(OLD_AGENT_HOOK_STEM)) {\n entry[\"command\"] = NEW_AGENT_HOOK;\n lines.push(`settings.json: Agent hook migrated to ${NEW_AGENT_HOOK}`);\n changed = true;\n replaced = true;\n }\n }\n // rules whose matcher only existed for the old hook entry stay as-is;\n // the new command reuses the rule\n }\n\n if (!replaced) {\n preToolUse.push({\n matcher: \"Agent\",\n hooks: [{ type: \"command\", command: NEW_AGENT_HOOK }],\n });\n lines.push(`settings.json: Agent hook added → ${NEW_AGENT_HOOK}`);\n changed = true;\n }\n\n if (changed) {\n hooks[\"PreToolUse\"] = preToolUse;\n settings[\"hooks\"] = hooks;\n writeSettingsJson(settings);\n } else {\n lines.push(`settings.json: Agent hook already current`);\n }\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// 2. ~/.local/bin shims\n// ---------------------------------------------------------------------------\n\nconst GLM_PS_SHIM = (pai: string) => `# pai worker shim — replaces the python glm-ps of the same name\ncase \"\\${1-}\" in\n \"\"|\"ps\") exec ${pai} worker ps ;;\n follow|watch|pane|log) exec ${pai} worker \"$@\" ;;\n --status) shift; exec ${pai} worker status-line \"\\${ITERM_SESSION_ID:-}\" \"\\${PWD:-$HOME}\" ;;\n -*) exec ${pai} worker ps \"$@\" ;;\n *) exec ${pai} worker replay \"$@\" ;;\nesac\n`;\n\nfunction shim(name: string, pai: string): string {\n if (name === \"glm-ps\") return GLM_PS_SHIM(pai);\n if (name === \"glm-log\") return `#!/bin/sh\\n# pai worker shim — replaces the shell glm-log of the same name\\nexec ${pai} worker log \"$@\"\\n`;\n if (name === \"worker-say\") return `#!/bin/sh\\n# pai worker shim — say one line to a running worker\\nexec ${pai} worker say \"$@\"\\n`;\n // glm / glm-run: pass everything through; `pai worker run` forwards unknown\n // options to claude and drops --output-format/--verbose itself\n return `#!/bin/sh\\n# pai worker shim — replaces the previous ${name} of the same name\\nexec ${pai} worker run \"$@\"\\n`;\n}\n\nfunction installShims(lines: string[]): boolean {\n const pai = paiPath();\n let changed = false;\n for (const name of [\"glm\", \"glm-run\", \"glm-ps\", \"glm-log\", \"worker-say\"]) {\n const path = join(LOCAL_BIN, name);\n const ours = `# pai worker shim`;\n let current: string | null = null;\n if (existsSync(path)) {\n try {\n current = execFileSync(\"head\", [\"-c\", \"64\", path], { encoding: \"utf8\" });\n } catch {\n current = null;\n }\n }\n if (current !== null && !current.includes(ours)) {\n // one-time move-aside; never clobber what a later run moved there\n const aside = join(LOCAL_BIN, `${name}.pre-pai`);\n if (!existsSync(aside)) {\n renameSync(path, aside);\n lines.push(`${name}: previous version kept as ${name}.pre-pai`);\n } else {\n unlinkSync(path);\n lines.push(`${name}: replaced (${name}.pre-pai already exists)`);\n }\n changed = true;\n } else if (current === null) {\n changed = true;\n } else {\n lines.push(`${name}: shim already current`);\n continue;\n }\n const body = shim(name, pai);\n writeFileSync(path, body, { encoding: \"utf8\", mode: 0o755 });\n try {\n chmodSync(path, 0o755);\n } catch {\n /* mode set on create where supported */\n }\n lines.push(`${name}: shim installed → ${pai} worker …`);\n }\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// 3. old hook file\n// ---------------------------------------------------------------------------\n\nfunction removeOldHook(lines: string[]): boolean {\n const old = join(HOOKS_DIR, \"route-agents-to-glm.sh\");\n if (!existsSync(old)) return false;\n unlinkSync(old);\n lines.push(`removed ${OLD_AGENT_HOOK_STEM}.sh (registration migrated above)`);\n return true;\n}\n\n// ---------------------------------------------------------------------------\n\nexport function installWorkers(): InstallResult {\n const lines: string[] = [];\n let changed = patchAgentHook(lines);\n if (installShims(lines)) changed = true;\n if (removeOldHook(lines)) changed = true;\n if (!existsSync(LOCAL_BIN)) {\n lines.push(`note: ${LOCAL_BIN} did not exist; make sure it is on PATH`);\n }\n return { lines, changed };\n}\n","/** Step 17: Worker providers — subagents on configured providers. */\n\nimport { c, line, section, type Rl, promptYesNo, readConfigRaw } from \"../utils.js\";\nimport { installWorkers } from \"../../../../workers/install.js\";\n\nexport async function stepWorkers(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 17: Worker Providers (Optional)\");\n\n const existing = readConfigRaw();\n const current = existing.workers as { enabled?: boolean; providers?: Record<string, unknown> } | undefined;\n\n if (current?.providers && Object.keys(current.providers).length > 0) {\n if (current.enabled !== false) {\n console.log(c.ok(\"Worker providers already configured. Running install to refresh shims…\"));\n for (const l of installWorkers().lines) line(` ${l}`);\n }\n return { workers: current };\n }\n\n line();\n line(\" PAI can run every subagent (worker) on a provider you configure —\");\n line(\" a non-Anthropic endpoint via its Anthropic-compatible API.\");\n line();\n line(\" `pai worker run` is the runner; ps / follow / replay watch the runs;\");\n line(\" the Agent tool is denied and rewritten to it once providers exist.\");\n line();\n line(\" PAI works fully without this — Agent subagents then just run as before.\");\n line();\n\n const wanted = await promptYesNo(rl, \"Configure a worker provider now?\", false);\n if (!wanted) {\n line();\n console.log(c.ok(\"Skipping worker providers.\"));\n console.log(c.dim(\" Add later: pai worker providers add <name> … — see docs/worker.md\"));\n return { workers: { enabled: false, providers: {}, classes: {} } };\n }\n\n line();\n line(\" Add the provider after setup finishes (this step only prepares the\");\n line(\" wiring — shims, hook registration, log dir):\");\n line();\n line(c.dim(\" pai worker providers add glm \\\\\"));\n line(c.dim(\" --base-url https://api.example.com/api/anthropic \\\\\"));\n line(c.dim(\" --key-file ~/.config/example/api_key \\\\\"));\n line(c.dim(\" --model example-4.7 --fast-model example-4.7-flash\"));\n line();\n line(\" The first provider turns workers on and seeds the classes.\");\n line();\n\n const r = installWorkers();\n for (const l of r.lines) line(` ${l}`);\n return { workers: { enabled: false, providers: {}, classes: {} } };\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 stepWorkers,\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 // Step 11c: Worker providers (optional subagent routing)\n const workersConfig = await stepWorkers(rl);\n\n // Write config after gathering all choices\n const allUpdates = { ...storageConfig, ...embeddingConfig, ...taskConfig, ...workersConfig };\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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\";\nimport { paiSocketPath } from \"../../runtime-paths.js\";\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(paiSocketPath(), () => {\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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\";\nimport { paiSocketPath } from \"../../runtime-paths.js\";\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(paiSocketPath(), () => {\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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\";\nimport { daemonPidPath } from \"../../runtime-paths.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 = daemonPidPath();\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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\" | \"queued\" | \"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 * Reporting only — no session is contacted. Distinguishes \"no transport was\n * looked for\" from \"a transport was looked for and not found\", which read\n * identically without it and made a working setup look broken.\n */\n dryRun?: 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 // A dry run deliberately passes no transport, so the absence means nothing\n // here — reporting it as \"no transport available\" made a healthy system\n // look broken, and sent at least one investigation after a fault that did\n // not exist. Routing has already succeeded by this point; say so.\n const reason = opts.dryRun\n ? `would dispatch to ${project} (dry run — no session contacted)`\n : opts.transport\n ? \"autoDispatch is off\"\n : \"no transport available — run `pai <project>` to pick this up\";\n\n return { task, outcome: \"skipped\", session: project, reason };\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 \"queued\",\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 * Multiples of the expected duration after which a claim is released outright.\n *\n * The running label became an interlock once AIBroker's webhook started skipping\n * any task carrying it (0.17.1). That closed the duplicate-dispatch hole and\n * opened a worse one: nothing here ever took the label off except a completion,\n * so a session that died mid-turn left the task claimed forever — the webhook\n * skipping it, the poller probing it, and neither ever running it again. Silent\n * permanent non-execution is the exact failure this subsystem exists to prevent,\n * and it is worse than a duplicate run.\n *\n * Deliberately far beyond the probe threshold and NOT conditional on a probe\n * saying the session is gone. An empty session list is how the hub itself failed\n * on 2026-08-01, so \"absent\" is not trustworthy evidence; elapsed time is. A\n * sweep that has been in flight for five hours is not in flight.\n */\nexport const ABANDON_FACTOR = 10;\n\n/** Floor for the same, so a task with a short learned duration is not released early. */\nexport const ABANDON_FLOOR_MINUTES = 120;\n\n/** When a claim is old enough to be released regardless of what probes say. */\nexport function abandonAfterMinutes(observed: number[]): number {\n return Math.max(expectedMinutes(observed) * ABANDON_FACTOR, ABANDON_FLOOR_MINUTES);\n}\n\n// ---------------------------------------------------------------------------\n// Decisions\n// ---------------------------------------------------------------------------\n\nexport type Decision =\n /** Not due yet. */\n | { action: \"wait\"; task: Task }\n /**\n * Ticked off in the tracker by hand — run it now, and put the schedule back.\n *\n * `restoreTo` is the due_string to write afterwards, or null to leave the\n * advanced date alone (the occurrence that was just consumed is already past,\n * so restoring it would only produce an immediately-overdue task).\n */\n | { action: \"triggered\"; task: Task; restoreTo: string | null }\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 /**\n * Claimed so long ago that no run is plausibly still going — release it.\n *\n * Not a re-dispatch: the claim comes off and nothing else happens this tick.\n * The task is still overdue, so the next tick dispatches it through the normal\n * path, claim and all. Splitting it that way keeps one dispatcher.\n */\n | { action: \"abandoned\"; task: Task; elapsedMinutes: number; thresholdMinutes: number }\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\n/**\n * Did THIS poller start the run currently in flight?\n *\n * The distinction from {@link isClaimedByAnyone} has now caused three separate\n * defects in three functions across two days, so it is named rather than\n * re-derived from `startedAt` at each site.\n *\n * `startedAt` is set only when this poller dispatches. A run triggered through\n * the tracker's webhook is claimed by AIBroker before this poller ever sees the\n * task, so it has no `startedAt` here — and every piece of evidence that is\n * sound for our own runs is worthless for those. Twice that produced a\n * confident wrong answer: `overdue < 0` reported a webhook run finished on the\n * tick after it started, and an orphan sweep keyed on `startedAt` would have\n * discarded exactly the entries the webhook path depends on.\n *\n * Use this when the question is \"can I trust state I recorded myself\".\n */\nexport function isOurRun(state: RunState, taskId: string): boolean {\n return state.startedAt[taskId] !== undefined;\n}\n\n/**\n * Is anyone running this task — us, or a session claimed through the tracker?\n *\n * The RUNNING label is the shared interlock: whoever claims a task sets it,\n * whichever side they are. Use this when the question is \"is a run in flight at\n * all\", which is what liveness means for anything scoped to a run.\n */\nexport function isClaimedByAnyone(task: Task, state: RunState): boolean {\n return isRunning(task) || isOurRun(state, task.id);\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// Manual trigger (the checkbox as a Run Now button)\n// ---------------------------------------------------------------------------\n\n/**\n * Why this is inferred from the due date rather than observed directly:\n *\n * Completing a recurring task leaves no completion event anywhere the API will\n * show us. Measured 2026-08-01 against the live account — a recurring task was\n * created, completed, and then queried through the completed-by-completion-date\n * endpoint: it is absent. The task itself comes straight back as open with the\n * due date advanced and the same id. So \"the due date jumped forward while we\n * were not running it\" is the ONLY evidence a human ticked the box.\n *\n * That evidence is ambiguous — dragging the date forward looks identical — so\n * the jump is checked against the recurrence period before it is believed. A\n * daily task that moves by exactly one day was ticked; one that moves by three\n * was rescheduled, and firing a sweep on that would both waste a run and undo\n * the user's edit.\n */\n\nconst MS_PER_DAY = 86_400_000;\n\n/**\n * Day-deltas consistent with one occurrence of `recurrence` elapsing.\n *\n * A set rather than a number because real rules are not fixed-length: a monthly\n * task advances by 28 to 31 days, and \"every weekday\" by one to three. Null\n * means the rule was not recognised, which the caller treats as permissive —\n * an unrecognised recurrence is still a recurrence, and refusing to fire is the\n * failure mode this whole mechanism exists to remove.\n */\nexport function expectedAdvanceDays(recurrence: string | null | undefined): number[] | null {\n if (!recurrence) return null;\n const r = recurrence.trim().toLowerCase();\n\n if (/\\bevery\\s+(work ?day|weekday)/.test(r)) return [1, 2, 3];\n if (/\\bevery\\s+other\\s+day\\b/.test(r)) return [2];\n if (/\\bevery\\s+(\\d+)\\s+days?\\b/.test(r)) {\n const n = Number.parseInt(/\\bevery\\s+(\\d+)\\s+days?\\b/.exec(r)![1]!, 10);\n return n > 0 ? [n] : null;\n }\n if (/\\bevery\\s+day\\b/.test(r) || /\\bdaily\\b/.test(r)) return [1];\n if (/\\bevery\\s+(\\d+)\\s+weeks?\\b/.test(r)) {\n const n = Number.parseInt(/\\bevery\\s+(\\d+)\\s+weeks?\\b/.exec(r)![1]!, 10);\n return n > 0 ? [n * 7] : null;\n }\n // Before the weekday rule: an unanchored \"mon\" alternative also matches the\n // \"mon\" in \"month\", which classified a monthly routine as weekly.\n if (/\\bevery\\s+(month|\\d+\\s+months?)\\b/.test(r) || /\\bmonthly\\b/.test(r)) return [28, 29, 30, 31];\n if (/\\bevery\\s+year\\b/.test(r) || /\\byearly\\b/.test(r) || /\\bannually\\b/.test(r)) return [365, 366];\n if (\n /\\bevery\\s+(?:week|mondays?|tuesdays?|wednesdays?|thursdays?|fridays?|saturdays?|sundays?|mon|tue|tues|wed|thu|thur|thurs|fri|sat|sun)\\b/.test(r) ||\n /\\bweekly\\b/.test(r)\n ) {\n return [7];\n }\n\n return null;\n}\n\n/** Whole days between two due values, rounded — times of day are preserved by\n * both a completion and a drag, so they carry no signal either way. */\nfunction advanceDays(prevDue: string, newDue: string): number {\n const a = new Date(prevDue.length <= 10 ? `${prevDue}T00:00:00` : prevDue).getTime();\n const b = new Date(newDue.length <= 10 ? `${newDue}T00:00:00` : newDue).getTime();\n if (Number.isNaN(a) || Number.isNaN(b)) return Number.NaN;\n return Math.round((b - a) / MS_PER_DAY);\n}\n\n/**\n * Did this task advance because someone ticked it off?\n *\n * A non-recurring task is never a trigger: completing one closes it, and it\n * drops out of the open list rather than reappearing with a later date. So a\n * forward jump there is unambiguously a reschedule.\n */\nexport function wasTicked(task: Task, prevDue: string | null | undefined): boolean {\n if (!prevDue || !task.due || !task.recurrence) return false;\n if (task.due <= prevDue) return false;\n\n const moved = advanceDays(prevDue, task.due);\n if (!Number.isFinite(moved) || moved <= 0) return false;\n\n const expected = expectedAdvanceDays(task.recurrence);\n return expected === null ? true : expected.includes(moved);\n}\n\n/**\n * The due_string that puts a ticked task back where it was.\n *\n * Rule and date in one string on purpose. Writing the date alone through the\n * tracker's date field drops the recurrence and quietly demotes a routine to a\n * one-off — the failure the provider's `setDue` comment warns about. Todoist\n * parses \"every day at 08:00 starting 2026-08-02\" and honours both halves;\n * verified against the live API on 2026-08-01.\n */\nexport function restoreDueString(recurrence: string, prevDue: string): string {\n const date = prevDue.slice(0, 10);\n const hasTime = prevDue.length > 10;\n const ruleCarriesTime = /\\bat\\s+\\d/.test(recurrence);\n\n // \"every day\" plus a datetime due would come back as a date-only occurrence,\n // losing the time of day. Carry it explicitly when the rule does not.\n const time = hasTime && !ruleCarriesTime ? ` at ${prevDue.slice(11, 16)}` : \"\";\n return `${recurrence}${time} starting ${date}`;\n}\n\n/**\n * When a restored slot actually falls due — the inverse of `restoreDueString`.\n *\n * Callers need this to decide whether putting the slot back is still useful.\n * Restoring one that has already passed writes a task that is overdue the\n * instant it lands, and the next tick dispatches it, which advances the date,\n * which restores it again: an unbounded loop that fires every tick for the rest\n * of the day. That is not hypothetical — it happened on 2026-08-04 with \"every\n * day at 9am\", which re-dispatched roughly every 30 minutes (two 900s ticks)\n * because the guard compared against the end of the day rather than 09:00.\n *\n * Both shapes `restoreDueString` can emit put the time after \"at\", so one match\n * covers \"every day at 9am starting …\" and \"every week at 08:00 starting …\".\n * A rule carrying no time at all is a genuine date-only occurrence, and for\n * those the slot really does run until the end of the day.\n */\nexport function triggeredSlotMs(keep: string, date: string): number {\n const at = /\\bat\\s+(\\d{1,2})(?::(\\d{2}))?\\s*([ap]m)?/i.exec(keep);\n if (!at) return new Date(`${date}T23:59:59`).getTime();\n\n let hour = Number(at[1]);\n const suffix = at[3]?.toLowerCase();\n if (suffix === \"pm\" && hour < 12) hour += 12;\n if (suffix === \"am\" && hour === 12) hour = 0;\n\n const hh = String(hour).padStart(2, \"0\");\n const mm = String(Number(at[2] ?? 0)).padStart(2, \"0\");\n return new Date(`${date}T${hh}:${mm}:00`).getTime();\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 * Task id → the due date seen on the previous tick.\n *\n * The only way to notice a task was ticked off by hand. Absent on the first\n * tick after install, which is why a missing entry can never mean \"it moved\".\n */\n lastSeenDue?: Record<string, string>;\n /**\n * True when a tracker webhook is delivering completion events.\n *\n * When set, the due-date trigger inference is switched off: the webhook sees\n * the real event, so the guess can only add false positives. Defaults to\n * false so a machine with no webhook keeps the fallback — which is the case\n * the inference was written for.\n */\n webhookActive?: boolean;\n /**\n * Task id → when this poller FIRST saw the task carrying a running claim.\n *\n * Needed because the claim is no longer only ours to make: AIBroker's webhook\n * claims and dispatches on its own, so a claimed task frequently has no start\n * time here. This is the clock for those runs — both to age them out and to\n * tell a claim we have been watching from one we are seeing for the first time.\n */\n claimSeenAt?: 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 const claimSeenAt = opts.claimSeenAt?.[task.id];\n const prevDue = opts.lastSeenDue?.[task.id];\n const dueAdvanced = prevDue !== undefined && task.due !== null && task.due > prevDue;\n const ourRun = isOurRun(state, task.id);\n\n // Completion is \"the due date moved into the future\" — not \"closed\". A\n // recurring task is closed for an instant at most, so testing for closed\n // would never fire.\n //\n // But `overdue < 0` alone is only evidence for a run WE started. A run\n // dispatched by AIBroker's webhook is triggered by the user ticking the box,\n // which advances the due date before the run even begins — so the due sits\n // in the future for the whole run, and this branch reported it finished on\n // the very next tick and stripped the interlock mid-flight. Measured against\n // a live probe on 2026-08-01: \"✓ would clear pai-running\" while the sweep\n // was still going.\n //\n // For someone else's run the completion signal is the due date advancing\n // AGAIN — and only once we have watched the claim across a tick, since the\n // first advance we see is the trigger itself, not an ending.\n const finishedElsewhere = dueAdvanced && claimSeenAt !== undefined && claimSeenAt < now;\n\n if ((ourRun && overdue < 0) || finishedElsewhere) {\n const duration = startedAt ? Math.max(1, Math.round((now - startedAt) / 60_000)) : null;\n return { action: \"complete\", task, durationMinutes: duration };\n }\n\n // Clock for everything below. A claim we did not make still has to age out,\n // or a webhook run whose session died would hold the interlock forever.\n const clock = startedAt ?? claimSeenAt;\n if (clock === undefined) {\n // First sighting of a claim with nothing to date it by. Either the state\n // file was lost or another dispatcher just claimed it. Probing is the safe\n // response — never assume it is dead and re-dispatch. The next tick has a\n // claim time and can age it properly.\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 - clock) / 60_000));\n const expected = expectedMinutes(opts.history?.[task.id] ?? []);\n\n // Checked before probing: past this point the probe's answer no longer\n // changes what should happen. Even a session replying \"yes, still working\"\n // has been working ten times its usual run, and leaving the claim on is what\n // turns one dead session into a routine that never runs again.\n const abandonAfter = abandonAfterMinutes(opts.history?.[task.id] ?? []);\n if (elapsed >= abandonAfter) {\n return { action: \"abandoned\", task, elapsedMinutes: elapsed, thresholdMinutes: abandonAfter };\n }\n\n // Only interrogate our own runs. Asking after a session another dispatcher\n // started tells us nothing actionable — we would not re-dispatch it either\n // way — and the ageing above already bounds it.\n if (!ourRun) return { action: \"running\", task, elapsedMinutes: elapsed };\n\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 // Checked before the wait branch, because a task that was just ticked always\n // lands in the future — it would otherwise be indistinguishable from one that\n // is simply not due yet, which is exactly how the checkbox came to look like\n // a button that silently pushed the sweep out by a day.\n const prevDue = opts.lastSeenDue?.[task.id];\n\n // A claim we were watching until a moment ago means this advance is a run\n // ENDING, not a box being ticked. Since aibroker 0.17.4 the finishing session\n // drops the claim itself and then advances the recurrence with `pai task\n // done` — so by the next tick the task is unclaimed with its due date exactly\n // one period on, which is indistinguishable from a hand-tick and would have\n // dispatched the same work a second time.\n //\n // The entry is still here because tick() clears it AFTER deciding, which is\n // precisely the window this needs. When a whole run fits between two ticks we\n // never see the claim at all — but then the due has advanced twice, once for\n // the trigger and once for the completion, and the period check rejects it.\n const wasClaimed = opts.claimSeenAt?.[task.id] !== undefined;\n\n // Suppressed entirely where a webhook is live, and that is a deliberate\n // precedence decision rather than a tuning knob.\n //\n // The inference reads \"due advanced by exactly one period, unclaimed\" as a\n // tick. For a DAILY recurrence that is byte-identical to a human dragging the\n // task forward one day, and to this poller repairing a due date it wrote\n // wrongly itself. There is no local signal that separates them — it is an\n // irreducible false positive, not something a tighter check can remove.\n //\n // A webhook has the actual item:completed event and no ambiguity at all. So\n // where one is running, guessing is strictly worse than not guessing, and\n // leaving both active means the fallback keeps firing on reschedules the\n // webhook correctly ignored — which is how repairing one duplicate sweep\n // produced a second on 2026-08-02.\n //\n // The costs are asymmetric, which is what settles it. A false positive\n // dispatches real work nobody asked for — a sweep that scrapes, writes and\n // sends mail. A false negative merely delays a hand-triggered run until the\n // next tick, which then dispatches it on the ordinary overdue path. Guessing\n // wrong in the expensive direction to save minutes in the cheap one is a bad\n // trade wherever the unambiguous signal exists.\n if (!opts.webhookActive && !wasClaimed && wasTicked(task, prevDue)) {\n // Restore only a slot that has not passed. Putting back an occurrence that\n // is already overdue leaves a task that reads as late the instant it is\n // written, for a run that is starting right now.\n const prevMs = new Date(prevDue!.length <= 10 ? `${prevDue}T00:00:00` : prevDue!).getTime();\n const restoreTo =\n task.recurrence && prevMs > now ? restoreDueString(task.recurrence, prevDue!) : null;\n return { action: \"triggered\", task, restoreTo };\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 { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { execFileSync } from \"node:child_process\";\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 isClaimedByAnyone,\n RUNNING_LABEL,\n STUCK_AFTER_FAILED_PROBES,\n type Decision,\n expectedMinutes,\n type RunState,\n EMPTY_RUN_STATE,\n wasTicked,\n restoreDueString,\n triggeredSlotMs,\n} from \"./scheduler.js\";\nimport type { Task } from \"./types.js\";\n\nexport const STATE_FILE = join(homedir(), \".pai\", \"scheduler-state.json\");\n\n/** How many past durations to keep per task. */\nconst HISTORY_LIMIT = 5;\n\nexport interface 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 * Task id → epoch ms of the last time an alarm about it reached the user.\n *\n * Every tick that raised an alarm before this existed raised it into\n * /tmp/pai-scheduler.log and nowhere else. Measured 2026-08-03: the daily\n * check had logged \"unrouted — cannot dispatch\" 151 times across two days,\n * on a task whose whole purpose was to notice things failing silently.\n *\n * Kept per task, and honoured for a day, because the two wrong answers are\n * symmetric: notify every tick and the alarm is muted as noise within the\n * hour, notify once ever and a fault fixed-then-reintroduced is silent the\n * second time.\n */\n alarmedAt: Record<string, number>;\n /**\n * Task id → the due date this tick saw.\n *\n * Durable rather than transient, unlike the rest of this file: it is the only\n * record that a recurring task's due date moved, and a lost entry means the\n * next tick cannot tell a hand-ticked task from one that was never due. That\n * costs a missed run, not a wrong one — the first tick after a reset simply\n * re-learns every date and triggers nothing.\n */\n lastSeenDue: Record<string, string>;\n /**\n * Task id → the due_string a hand-triggered run has to get back.\n *\n * Restoring once at trigger time is not enough. The session ends its run with\n * `pai task done`, which advances the recurrence a second time — so a sweep\n * run by hand on Saturday evening silently ate Sunday morning's scheduled\n * one. Held here until the completion is seen, then re-applied.\n */\n triggeredRestore: Record<string, string>;\n /**\n * Task id → when this poller first saw a running claim on it.\n *\n * The claim is no longer only ours to make — AIBroker's webhook claims and\n * dispatches on its own — so a claimed task often has no start time here.\n * This dates those runs, which is what lets them age out instead of holding\n * the interlock forever, and what separates a claim we have been watching\n * from one appearing for the first time.\n */\n claimSeenAt: Record<string, number>;\n /**\n * Task id → the failure that made this task stop being dispatched.\n *\n * `failedDispatches` counts attempts and gates the ALARM. It never gated the\n * RETRY, so a task whose dispatch could not possibly succeed was re-attempted\n * every tick for as long as it stayed due. On 2026-08-04 that meant a fresh\n * terminal window every fifteen minutes for nine hours, against a project\n * directory that had been renamed months earlier: each attempt opened a tab,\n * failed its `cd`, waited out the full 90s readiness timeout and gave up,\n * changing nothing before the next one.\n *\n * Parking is the missing state between \"failed\" and \"gave up\". A parked task\n * is still visible, still reported, and still escalates once — it simply\n * stops being launched at something that cannot accept it. It unparks when\n * the task's due date moves (the user rescheduled it) or when its parked\n * reason no longer applies, which is what keeps this from being a one-way\n * door that quietly retires a task nobody notices is gone.\n */\n parked: Record<string, { reason: string; at: number; due?: string }>;\n}\n\n/**\n * A fresh blank state, built per call rather than shared.\n *\n * This was a module-level constant spread into every load. A spread is shallow,\n * so any field the persisted file did not carry — a fresh install, or a file\n * written before that field existed — aliased the constant's own object, and\n * writes to it leaked into every later load in the process. Harmless for the\n * launchd tick, which loads once and exits, and quietly wrong everywhere else:\n * it made test runs contaminate each other and would do the same to any caller\n * that ticked twice.\n */\nfunction emptyState(): PersistedState {\n return {\n startedAt: {},\n failedProbes: {},\n history: {},\n lastReported: {},\n failedDispatches: {},\n alarmedAt: {},\n lastSeenDue: {},\n triggeredRestore: {},\n claimSeenAt: {},\n parked: {},\n };\n}\n\n/** Consecutive failed dispatches before a task is reported as needing attention. */\nconst DISPATCH_FAILURES_BEFORE_ALARM = 3;\n\n/** How long an alarm about one task stays quiet before it is repeated. */\nconst ALARM_REPEAT_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Tell the user about a task that is scheduled and not running.\n *\n * The counterpart of the storage-backend escalation: a subsystem whose entire\n * job is noticing silent failure has to be the last thing that fails silently.\n * `report.stuck` counted these accurately and showed the count in a CLI line\n * that only appears if somebody runs a tick by hand — which, for a job launchd\n * runs every 15 minutes, is nobody.\n *\n * Never allowed to throw: a notification that cannot be delivered must not take\n * down the tick, because the tick is what dispatches everything else.\n */\nasync function escalate(\n task: Task,\n headline: string,\n detail: string,\n state: PersistedState,\n now: number,\n opts?: TickOptions\n): Promise<void> {\n const last = state.alarmedAt[task.id];\n if (last !== undefined && now - last < ALARM_REPEAT_MS) return;\n state.alarmedAt[task.id] = now;\n\n let delivered = false;\n try {\n const { routeNotification } = await import(\"../notifications/router.js\");\n const { loadConfig } = await import(\"../daemon/config.js\");\n const result = await routeNotification(\n {\n event: \"error\",\n title: `PAI: ${headline}`,\n // `title`, not `content`. Todoist's own API calls this field `content`\n // and the provider renames it on the way in; writing the wire name here\n // compiled to `undefined` and shipped in v0.27.0, so every alarm this\n // path has ever sent read `\"undefined\" is due but has no owner`. The\n // test below asserts the title appears, because the one that existed\n // asserted only the tail of the sentence and passed all the way through.\n message: `\"${task.title}\" ${detail}`,\n },\n loadConfig().notifications\n );\n delivered = result.channelsSucceeded.length > 0;\n } catch {\n /* see above — the tick matters more than the notification */\n }\n\n // Last resort: say it on the task itself.\n //\n // routeNotification returns which channels worked, and this used to throw\n // that away. On 2026-08-04 the WhatsApp provider was dialling a socket that\n // had stopped existing when Whazaa became a thin adapter, so `error` events\n // silently failed — four escalations about a job that had not run in nine\n // hours reached /tmp/pai-scheduler.log and nowhere else, and the user found\n // out by looking.\n //\n // The tracker is the one channel that cannot be misconfigured out of\n // existence, because it is where the task already lives and where the user\n // already looks. A comment is durable, reaches the phone, and survives every\n // notification channel being down at once.\n if (delivered || !opts?.provider.comment) return;\n try {\n await opts.provider.comment(\n task.id,\n `${AGENT_MARK} **${headline}** — ${detail}\\n\\n` +\n `_Posted here because no notification channel accepted this alert. ` +\n `Check \\`pai notify status\\`._`\n );\n } catch {\n /* nothing left to try; the tick still matters more */\n }\n}\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 */\nexport function loadState(file: string): PersistedState {\n try {\n const raw = readJsonStrict(file, \"~/.pai/scheduler-state.json\");\n return { ...emptyState(), ...(raw as unknown as PersistedState) };\n } catch {\n return emptyState();\n }\n}\n\nexport function saveState(state: PersistedState, file: string): void {\n writeJsonAtomic(file, state as unknown as Record<string, unknown>, { backup: false });\n}\n\n/**\n * Fold in any completion a concurrent `pai task done` wrote while this tick\n * was busy with the rest of the list.\n *\n * A tick holds one in-memory `state` for its whole run and saves it exactly\n * once, at the end. For a morning routine with several due tasks that run is\n * not instantaneous — each dispatch waits out AIBroker's spawn-readiness\n * window in turn, so the tick can still be working through task 4 minutes\n * after task 1 was launched. `pai task done` has no such window: it loads,\n * releases the claim, stamps `lastSeenDue` to the next occurrence, and saves,\n * all in well under a second. If the session for task 1 finishes and closes\n * it while the tick is still on task 4, the CLI's write lands on disk first —\n * and the tick's blind save at the end, built from the snapshot it took\n * before any of this happened, overwrites it: `lastSeenDue` reverts to the\n * occurrence that was just completed, and `startedAt`/`claimSeenAt` come back\n * because this tick never learned they had been released. The next tick then\n * reads that reverted date as \"due advanced by one period, unclaimed\" — which\n * is `wasTicked`'s exact definition of a hand-tick — and redispatches a task\n * that already finished. Reproduced against a live poller run; see the\n * negative control referenced in Notes/TODO.md.\n *\n * `lastSeenDue` only ever moves forward in normal operation, so a disk value\n * strictly ahead of what this tick computed can only mean a concurrent writer\n * — there is exactly one, `pai task done` — saw a later completion than this\n * tick's stale snapshot knew about. Whenever that happens, this tick's own\n * value is the stale one: defer to disk for that task's date, and carry the\n * claim release forward too, since a more-advanced date with no such release\n * is not a state `pai task done` ever produces.\n *\n * Scoped to exactly the fields `pai task done` touches (`releaseClaim` plus\n * the `lastSeenDue` stamp) — the two writers race over nothing else, so a\n * broader merge would only add risk without removing any.\n */\nfunction reconcileWithDisk(state: PersistedState, stateFile: string): void {\n const onDisk = loadState(stateFile);\n for (const [id, oursDue] of Object.entries(state.lastSeenDue)) {\n const diskDue = onDisk.lastSeenDue[id];\n if (diskDue !== undefined && diskDue > oursDue) {\n state.lastSeenDue[id] = diskDue;\n delete state.startedAt[id];\n delete state.claimSeenAt[id];\n delete state.failedProbes[id];\n }\n }\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 * Where run state is persisted. Overridable so the dispatch path can be\n * tested at all: a real tick writes to disk, and the alternative — asserting\n * only through `dryRun` — skips the transport entirely, which is precisely\n * the code that misreported a delivered task as failed.\n */\n stateFile?: string;\n /**\n * Override webhook detection. Left unset, the tick asks AIBroker once.\n *\n * Present so tests can pin both sides of the precedence rule without a live\n * daemon — the whole point of the flag is which of two subsystems decides,\n * and that is not testable if it can only be discovered by shelling out.\n */\n webhookActive?: boolean;\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\n/**\n * Is a tracker webhook delivering completion events on this machine?\n *\n * Asked of AIBroker, which owns the receiver: `todoist status` reports whether\n * an OAuth grant is on file, and without a grant no completion events arrive.\n * Deliberately conservative — anything unexpected reads as \"no webhook\", which\n * leaves the due-date inference running. That is the pre-existing behaviour and\n * degrades to guessing rather than to silence.\n *\n * Cheap enough to run per tick (one short-lived process, once), and read live\n * rather than cached because a grant can lapse at any moment — that happened\n * mid-afternoon on 2026-08-01, and a cached \"yes\" would have suppressed the\n * fallback exactly when it was needed.\n */\nfunction detectWebhook(bin = \"aibroker\"): boolean {\n try {\n const out = execFileSync(bin, [\"todoist\", \"status\"], {\n timeout: 10_000,\n encoding: \"utf-8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n // A grant on file is the only thing that matters; without one the receiver\n // can verify webhooks but cannot resolve them, so no completion lands.\n return /authoris|authoriz|token on file|grant/i.test(out) && !/no (token|grant)/i.test(out);\n } catch {\n return false;\n }\n}\n\n/**\n * Discard restore entries whose run is no longer in flight.\n *\n * `triggeredRestore` belongs to one run: it records the occurrence that run\n * consumed, to be handed back when the run completes. It was only ever consumed\n * on the completion path, so a run that ended any other way — abandoned, or\n * released by hand — left the entry behind, and it then fired on that task's\n * NEXT completion, whenever that came.\n *\n * Observed 2026-08-02: runs ended by hand the previous night left entries that\n * fired on the first unattended 08:00 completion and wrote the due date from\n * 08-03 back to 08-02. That date was already past, so the task was instantly\n * overdue and the following tick would have dispatched a duplicate sweep.\n *\n * Clearing it at each release site would work, and would have to be remembered\n * at every site added later. Deriving it instead makes the class unreachable:\n * an entry with no claim behind it describes a run that is over, whatever ended\n * it. The claim is the only thing that says a run is still in flight.\n */\nfunction dropOrphanedRestores(tasks: Task[], state: PersistedState): void {\n const byId = new Map(tasks.map((t) => [t.id, t]));\n\n for (const id of Object.keys(state.triggeredRestore)) {\n const task = byId.get(id);\n\n // Gone from the open list entirely — nothing will complete it again.\n if (!task) {\n delete state.triggeredRestore[id];\n continue;\n }\n\n // Liveness means \"a run is in flight at all\", not \"a run we started\" —\n // a webhook-triggered run is claimed before this poller sees the task, so\n // keying on our own startedAt would discard exactly the entries the webhook\n // path depends on, one tick after recording them.\n if (!isClaimedByAnyone(task, state)) {\n delete state.triggeredRestore[id];\n }\n }\n}\n\nexport async function tick(opts: TickOptions): Promise<TickReport> {\n const now = opts.now ?? Date.now();\n const stateFile = opts.stateFile ?? STATE_FILE;\n const state = loadState(stateFile);\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 if (!opts.dryRun) dropOrphanedRestores(tasks, state);\n\n // Resolved once per tick, not per task: it is a property of the machine.\n const webhookActive = opts.webhookActive ?? detectWebhook();\n\n for (const task of ordered) {\n // Captured before lastSeenDue is overwritten below — both the trigger\n // detection and the webhook-trigger signature need the PREVIOUS tick's value.\n const prevDue = state.lastSeenDue[task.id];\n\n const d = decide(task, {\n now,\n state,\n history: state.history,\n lastSeenDue: state.lastSeenDue,\n claimSeenAt: state.claimSeenAt,\n webhookActive,\n });\n let note = \"\";\n\n // Recorded for every task on every tick, whatever was decided — the next\n // tick's ability to spot a hand-ticked task depends on it, and skipping the\n // uninteresting cases is what would leave that blind.\n if (!opts.dryRun && task.due) state.lastSeenDue[task.id] = task.due;\n\n // Dated on first sight and dropped the moment the claim clears, so the next\n // claim on the same task is timed from when IT appeared rather than from a\n // stale entry belonging to a previous run.\n if (!opts.dryRun) {\n if (isRunning(task)) {\n // A claim appearing at the same moment the due date advanced by exactly\n // one period is the signature of a webhook-triggered run: the user\n // ticked the box, AIBroker claimed it and dispatched, all before this\n // tick. Recorded so the occurrence the tick consumed is given back after\n // the run, the same as for a trigger this poller handled itself.\n //\n // Recorded ONLY — the date is not written back now. AIBroker decides\n // whether the session may release its claim by checking that the due\n // date has advanced past the occurrence it noted when claiming, so\n // moving that date mid-run would make a finished run look unfinished\n // and strand the claim.\n if (state.claimSeenAt[task.id] === undefined && task.recurrence && prevDue !== undefined) {\n if (wasTicked(task, prevDue)) {\n state.triggeredRestore[task.id] = restoreDueString(task.recurrence, prevDue);\n }\n }\n state.claimSeenAt[task.id] ??= now;\n } else {\n delete state.claimSeenAt[task.id];\n }\n }\n\n switch (d.action) {\n case \"wait\":\n continue; // not worth reporting\n\n case \"triggered\": {\n const r = await handleDispatch(task, 0, opts, state, now);\n note = `ticked off by hand — ${r.note}`;\n if (!opts.dryRun) {\n if (!r.parked) report.dispatched++;\n if (r.alarm) report.stuck++;\n note += await restoreSchedule(task, d.restoreTo, opts, state);\n // Held for the completion, which advances the recurrence again.\n if (d.restoreTo) state.triggeredRestore[task.id] = d.restoreTo;\n }\n break;\n }\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 // A parked task is not dispatched, so it must not be counted as one.\n // `dispatched: 1, stuck: 1` for a task nothing tried to launch reads\n // as a dispatch that then went wrong — the opposite of what happened.\n if (!r.parked) 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, now);\n if (!opts.dryRun) report.completed++;\n break;\n\n case \"abandoned\": {\n note = `claimed ${d.elapsedMinutes}m ago, past the ${d.thresholdMinutes}m limit — releasing the claim`;\n if (!opts.dryRun) {\n await releaseClaim(task, opts.provider, state);\n // Reported as needing attention: a run that had to be released this\n // way did not finish, and the next tick re-dispatching it is a repair,\n // not business as usual.\n report.stuck++;\n }\n break;\n }\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) {\n reconcileWithDisk(state, stateFile);\n saveState(state, stateFile);\n }\n return report;\n}\n\n// ---------------------------------------------------------------------------\n// Actions\n// ---------------------------------------------------------------------------\n\n/**\n * Is this failure a verdict rather than a setback?\n *\n * Retrying is the right default — a session that is briefly busy, an iTerm that\n * has not finished launching, a machine under load all resolve on their own.\n * A filesystem that does not have the directory is not one of those.\n *\n * A backstop, NOT the main guard. The missing-root case that motivated all of\n * this is caught before dispatch by asking the disk directly (see\n * `handleDispatch`), because AIBroker never phrases it this way: its report for\n * a vanished root is `unreachable — Launched a session in <path> but it did not\n * become ready within 90s`, which is indistinguishable in text from a machine\n * under load. This function exists for reasons that DO name the fault — a\n * transport throwing ENOENT, a future AIBroker that says so — and it is\n * deliberately narrow, since anything vaguer would park transient failures and\n * stop tasks that were only briefly unlucky.\n */\nfunction isPermanentDispatchFailure(result: { outcome: string; reason?: string }): boolean {\n const reason = result.reason ?? \"\";\n return (\n /project_root_missing/i.test(reason) ||\n /which does not exist/i.test(reason) ||\n /no such file or directory/i.test(reason)\n );\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; parked?: 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 // A parked task is not dispatched, but it is still reported every tick — the\n // failure mode being avoided here is silence, not noise. Moving the due date\n // is the unpark gesture: it is what a user does after fixing the cause, and\n // it needs no command to remember.\n const parked = state.parked[task.id];\n if (parked) {\n if (parked.due === (task.due ?? undefined)) {\n // Re-escalated, not merely noted. `escalate` is idempotent inside its 24h\n // window, so this costs one notification a day and buys the thing the\n // whole v0.27.0 alarm existed for: a task that is not running cannot go\n // quiet. Alarming once at the moment of parking and never again would\n // rebuild the original failure in a new shape — a task scheduled every\n // day, executing never, and saying so only in a log nobody tails.\n await escalate(\n task,\n \"task is parked\",\n `is still parked (${parked.reason}) and is not being retried. ` +\n `Fix the cause and move its due date to release it.`,\n state,\n now,\n opts\n );\n return {\n note: `PARKED since ${new Date(parked.at).toISOString().slice(0, 16).replace(\"T\", \" \")} — ${parked.reason}`,\n alarm: true,\n parked: true,\n };\n }\n // Rescheduled — the user has acted. Try again.\n delete state.parked[task.id];\n delete state.failedDispatches[task.id];\n }\n\n // Reaching here at all means the task is DUE: an undated task scores\n // NEGATIVE_INFINITY overdue and is decided as `wait`, so it never arrives.\n // That is what separates this from the findings inbox, where UNROUTED is the\n // normal resting state and alarming would be pure noise — a finding carries\n // no date and makes no promise. A task with a due date does: it claims it\n // will run, and unrouted means it never can, by itself, ever.\n //\n // So this is the one dispatch failure that retrying cannot fix, and it used\n // to be the only one that did not alarm. The daily check sat here from\n // 2026-08-01 to 2026-08-03 reading \"recurring, every day at 9am\" while doing\n // nothing, and was found by a human reading the tracker, not by this code.\n if (!task.owner.project) {\n const hint = task.owner.rawHint ? ` (${task.owner.rawHint} matches no project)` : \"\";\n await escalate(\n task,\n \"scheduled task cannot run\",\n `is due${late} but has no owner${hint}, so nothing will ever pick it up. ` +\n `Move it into a session's sub-project, or give that container an alias.`,\n state,\n now,\n opts\n );\n return { note: `unrouted — cannot dispatch${hint}${late}`, alarm: true };\n }\n\n // A registered root that is not on disk is checked HERE, before anything is\n // spawned, because the transport cannot tell us about it.\n //\n // This is what the 2026-08-04 incident actually looked like from AIBroker's\n // side: it opened the tab, the shell's `cd` failed, Claude never started, and\n // it reported `unreachable — Launched a session in <path> but it did not\n // become ready within 90s`. That sentence names the path and says nothing\n // about it missing, so matching failure text for \"does not exist\" — the first\n // shape of this guard — would have parked nothing at all while reading, in\n // the diff, exactly like a fix. `/tmp/pai-scheduler.log` has the wording.\n //\n // Asked locally it is not a guess: the registry says where the project is,\n // and either the directory is there or it is not. Nine hours of fifteen-minute\n // retries, each costing a terminal window and a 90-second timeout, come down\n // to one `existsSync`.\n if (task.owner.rootPath && !existsSync(task.owner.rootPath)) {\n const reason = `project root missing: ${task.owner.rootPath}`;\n state.parked[task.id] = { reason, at: now, due: task.due ?? undefined };\n await escalate(\n task,\n \"task is not running\",\n `cannot be dispatched: its project directory ${task.owner.rootPath} does not exist ` +\n `(renamed or moved?). It is now PARKED and will not be retried. Repoint the ` +\n `project with \\`pai project\\` and move the task's due date to release it.`,\n state,\n now,\n opts\n );\n return { note: `PARKED — ${reason}`, alarm: true, parked: true };\n }\n\n // Claim the task BEFORE dispatching, not after.\n //\n // This label is now the interlock between two independent dispatchers: this\n // poller, and AIBroker's Todoist webhook, which fires on item:completed and\n // skips any task already carrying it (aibroker 0.17.0). Setting it after the\n // dispatch returned would leave a window — the whole length of a spawn — in\n // which the webhook sees an unclaimed task and sends the same work order a\n // second time. It was previously written only for crash visibility, where\n // \"after\" was harmless; it is load-bearing now, so it has to come first.\n //\n // The cost is a label to undo when the dispatch does not land, which is the\n // cheaper failure: a task wrongly marked running is visible and self-clears\n // below, whereas a double-dispatched sweep reads Gmail twice and mails twice.\n const claimed = [...task.labels, RUNNING_LABEL];\n await opts.provider.setLabels(task.id, claimed);\n\n const result = await dispatchTask(task, {\n transport: opts.transport,\n autoDispatch: opts.autoDispatch,\n spawnIfAbsent: true,\n });\n\n // `queued` counts as delivery. The message is sitting in a live session's\n // input box; Claude Code simply will not read it until the current turn ends.\n // Treating it as a failure marked a sweep NOT RUNNING while it was running,\n // and left the task without its running label so a later tick could send it\n // again — the same reasoning that makes `busy` positive evidence on the probe\n // path, where a working session is exactly the one that cannot answer.\n if (result.outcome === \"delivered\" || result.outcome === \"queued\" || result.outcome === \"spawned\") {\n state.startedAt[task.id] = now;\n delete state.failedProbes[task.id];\n delete state.failedDispatches[task.id];\n delete state.parked[task.id];\n // Dropped on success so the day-long quiet window never outlives the fault\n // it described: a task fixed this morning and broken again tonight alarms\n // tonight, rather than staying muted until tomorrow.\n delete state.alarmedAt[task.id];\n await markRunning(task, opts, now);\n return { note: `${result.outcome} to ${result.session}${late}`, alarm: false };\n }\n\n // Nothing is running, so release the claim — otherwise the task looks in\n // flight forever and the next tick reports it orphaned rather than retrying.\n // Not `releaseClaim`: the claim never made it into `state` on this path (it\n // is undone before `startedAt` would have been set), so there is nothing\n // there to clear, and `task.labels` is already the pre-claim array.\n await opts.provider.setLabels(task.id, task.labels);\n await clearRunningMark(task, opts.provider);\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 // Some failures are verdicts, not setbacks. A project whose root directory\n // does not exist cannot be launched by trying harder, so it parks on the\n // first occurrence rather than burning three 90-second spawn timeouts and\n // then retrying anyway.\n const permanent = isPermanentDispatchFailure(result);\n\n if (permanent || 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 // so this now stops retrying as well as reporting.\n state.parked[task.id] = {\n reason: detail,\n at: now,\n due: task.due ?? undefined,\n };\n await escalate(\n task,\n \"task is not running\",\n `failed to dispatch ${permanent ? \"\" : `${fails} times `}(${detail}). ` +\n `It is scheduled and nothing is executing it. It is now PARKED and will not ` +\n `be retried until its due date moves — reschedule it once the cause is fixed.`,\n state,\n now,\n opts\n );\n return {\n note: `PARKED — ${permanent ? \"permanent failure\" : `${fails} failed dispatches`}: ${detail}`,\n alarm: true,\n };\n }\n return { note: `not dispatched (${fails}/${DISPATCH_FAILURES_BEFORE_ALARM}): ${detail}`, alarm: false };\n}\n\n/**\n * First line of the progress comment, and the only way it is ever found again.\n *\n * Deliberately matched by content rather than by a stored comment id. An id in\n * the state file is one more thing that goes stale — lose the file and the\n * comment is orphaned with nothing able to remove it. A sentinel is\n * self-healing: any later release finds and clears it, whatever happened in\n * between, and clearing is idempotent so doing it twice costs nothing.\n */\nconst RUNNING_COMMENT_MARK = \"**RUNNING**\";\n\n/**\n * The cross-repo \"this was written by a machine\" prefix.\n *\n * AIBroker mirrors Todoist comments to the session that owns a task — which is\n * wanted, and is how a human follow-up reaches the running work. Its echo guard\n * (`AGENT_MARK` in `daemon/todoist-webhook.ts`) is what stops that mirror from\n * feeding an agent its own writing, and it keys on this character.\n *\n * The progress marker below did not carry it. So: the poller dispatched the\n * sweep, posted \"RUNNING\" to Todoist, Todoist fired a `note:added` webhook, and\n * AIBroker mirrored the poller's own status line back as a work order — a\n * SECOND session for the same task, 19 seconds after the first. Observed\n * 2026-08-04 07:42:19 → 07:42:38 in `~/.aibroker/audit.jsonl`.\n *\n * Duplicated rather than imported because PAI must not depend on AIBroker. The\n * value is a protocol constant between them; if it ever changes, it changes in\n * both, and the mirror's own tests pin it on that side.\n */\nconst AGENT_MARK = \"🤖\";\n\n/**\n * Post the human-facing progress marker.\n *\n * This is NOT an interlock and nothing may ever decide from it. `pai-running`\n * is the machine-readable claim and both dispatchers read that alone. Two\n * mechanisms tracking one piece of state and disagreeing is the shape of every\n * defect found on 2026-08-01; this stays purely informational so it cannot\n * become the sixth.\n *\n * Failure is swallowed on purpose. A comment that could not be posted must not\n * cost the run — the sweep matters, the annotation does not.\n */\nasync function markRunning(task: Task, opts: TickOptions, startedAt: number): Promise<void> {\n if (!opts.provider.comment) return;\n const when = new Date(startedAt).toISOString().replace(\"T\", \" \").slice(0, 16);\n try {\n // Kept to one line on purpose. The first version explained the interlock\n // design here and read as noise to the person it was written for — a\n // progress marker's job is to say it is running, not to document why.\n await opts.provider.comment(\n task.id,\n `${AGENT_MARK} ${RUNNING_COMMENT_MARK} — started ${when} UTC, ${task.owner.project}. ` +\n `Disappears when it finishes.`\n );\n } catch {\n // Deliberately silent: see above.\n }\n}\n\n/**\n * Remove any progress marker left on a task.\n *\n * Called from every path that releases the claim, including the ones that\n * release it because something went wrong. A marker outliving its run is\n * exactly the stale-state problem this whole subsystem keeps producing, so the\n * cleanup is attached to the release rather than to the happy path.\n */\nasync function clearRunningMark(task: Task, provider: TodoistProvider): Promise<void> {\n const { listComments, deleteComment } = provider;\n if (!listComments || !deleteComment) return;\n try {\n const comments = await listComments.call(provider, task.id);\n for (const c of comments) {\n // `includes`, not `startsWith`: the marker gained an AGENT_MARK prefix,\n // and markers posted before that change must still be found and cleared.\n // The sentinel is meant to be self-healing — a matcher that only knows\n // the current spelling orphans every comment written by the last one.\n if (c.content.includes(RUNNING_COMMENT_MARK)) {\n await deleteComment.call(provider, c.id);\n }\n }\n } catch {\n // Same reasoning as posting it: never let the annotation break the run.\n }\n}\n\n/**\n * Release a claim on a task: strip the interlock label, remove the progress\n * marker, and forget the transient run state that made this poller believe\n * something was still working on it.\n *\n * This is every effect of ending a run except the tracker-side completion\n * itself — factored out so `handleComplete` and the \"abandoned\" release below\n * cannot drift apart, and so anything else that ends a claim (`pai task done`\n * included) gets the identical cleanup rather than a hand-rolled subset of it.\n * A partial version of this — closing the task but skipping this call — is\n * exactly what left `pai-running` and the transient state behind on every\n * `pai task done` before this existed: the next occurrence of a recurring task\n * inherited a live-looking claim it never made, and the poller spent the\n * morning asking a session whether it was still working on a task it had\n * already finished.\n *\n * Only ever removes `RUNNING_LABEL` from the label array — never replaces it —\n * so any other label the user put on the task survives. Only ever deletes this\n * task's own entries from `startedAt` / `claimSeenAt` / `failedProbes` —\n * `lastSeenDue` and `history` are untouched, exactly as `handleComplete` always\n * left them.\n */\nexport async function releaseClaim(\n task: Task,\n provider: TodoistProvider,\n state: Pick<PersistedState, \"startedAt\" | \"claimSeenAt\" | \"failedProbes\">\n): Promise<void> {\n await provider.setLabels(\n task.id,\n task.labels.filter((l) => l.toLowerCase() !== RUNNING_LABEL)\n );\n await clearRunningMark(task, provider);\n delete state.startedAt[task.id];\n delete state.failedProbes[task.id];\n delete state.claimSeenAt[task.id];\n}\n\n/**\n * Put a hand-ticked task's schedule back.\n *\n * Deliberately after the dispatch and never in its place: the run is the point,\n * and a tracker that refuses the write must not cost the user the sweep they\n * asked for. A failure here is reported and nothing else — the task simply\n * keeps the advanced date, which is where it would have stayed anyway.\n */\nasync function restoreSchedule(\n task: Task,\n restoreTo: string | null,\n opts: TickOptions,\n state: PersistedState\n): Promise<string> {\n if (!restoreTo) return \", schedule left at the next occurrence\";\n if (!opts.provider.setDue) return \", schedule unchanged (provider cannot rewrite due dates)\";\n\n try {\n await opts.provider.setDue(task.id, restoreTo);\n // Record what we wrote, not what we read — the next tick must compare\n // against the restored date or it would read the restore as a fresh jump.\n const restoredDate = /starting (\\d{4}-\\d{2}-\\d{2})/.exec(restoreTo)?.[1];\n if (restoredDate) state.lastSeenDue[task.id] = restoredDate;\n return `, schedule restored (${restoreTo})`;\n } catch (e) {\n return `, could NOT restore the schedule: ${e instanceof Error ? e.message : String(e)}`;\n }\n}\n\nasync function handleComplete(\n task: Task,\n durationMinutes: number | null,\n opts: TickOptions,\n state: PersistedState,\n now: number\n): Promise<string> {\n if (opts.dryRun) return `would clear ${RUNNING_LABEL}, ${durationMinutes ?? \"?\"}m`;\n\n const wasStuck = (state.failedProbes[task.id] ?? 0) > 0;\n await releaseClaim(task, opts.provider, state);\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 const base =\n durationMinutes === null\n ? \"completed (duration unknown)\"\n : `completed in ${durationMinutes}m${wasStuck ? \" — not recorded, run was probed\" : \"\"}`;\n\n return base + (await keepTriggeredSchedule(task, opts, state, now));\n}\n\n/**\n * Give a hand-triggered run its schedule back, a second time.\n *\n * `pai task done` is the correct way to end a recurring run — the roll-forward\n * is how completion is detected at all — but it advances the date on top of the\n * advance the hand-tick already caused. Without this, using the checkbox as a\n * Run Now button quietly cancelled the next scheduled occurrence, which is the\n * exact behaviour the restore was added to prevent.\n *\n * Skipped when the slot has already passed: re-applying it would produce a task\n * that is overdue the moment it is written, and the next tick would dispatch it.\n */\nasync function keepTriggeredSchedule(\n task: Task,\n opts: TickOptions,\n state: PersistedState,\n now: number\n): Promise<string> {\n const keep = state.triggeredRestore[task.id];\n if (!keep) return \"\";\n delete state.triggeredRestore[task.id];\n\n const date = /starting (\\d{4}-\\d{2}-\\d{2})/.exec(keep)?.[1];\n if (!date || !opts.provider.setDue) return \"\";\n if (triggeredSlotMs(keep, date) <= now) {\n return \", triggered-run slot already past — schedule left as it is\";\n }\n\n try {\n await opts.provider.setDue(task.id, keep);\n state.lastSeenDue[task.id] = date;\n return `, schedule kept at ${date} (run was hand-triggered)`;\n } catch (e) {\n return `, could NOT keep the schedule: ${e instanceof Error ? e.message : String(e)}`;\n }\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 { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { spawnSync } from \"node:child_process\";\nimport { schedulerLogPath } from \"../runtime-paths.js\";\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 = schedulerLogPath();\n\n/** Default tick, in seconds. */\nexport const DEFAULT_INTERVAL_SECS = 900;\n\n/**\n * Absolute path to the CLI entrypoint the agent should run.\n *\n * Resolving \"index.mjs\" next to this module was wrong. The bundler emits shared\n * code into chunks at dist/ root, so import.meta.url pointed there and the plist\n * was written against dist/index.mjs — the library barrel, which parses no argv,\n * prints nothing and exits 0. The agent ticked every 15 minutes and did nothing\n * at all, which looks identical to \"nothing was due\". Anchor on the package root\n * and refuse to install rather than write a plist around a file that is not a CLI.\n */\nfunction cliPath(): string {\n let dir = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 6; i++) {\n for (const candidate of [join(dir, \"dist\", \"cli\", \"index.mjs\"), join(dir, \"cli\", \"index.mjs\")]) {\n if (existsSync(candidate)) return candidate;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new Error(\n \"cannot locate the PAI CLI entrypoint (dist/cli/index.mjs) — refusing to install a scheduler that would silently do nothing\"\n );\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 * archive.ts — keep the conversation that happened on a task.\n *\n * A task's comment thread is where the actual thinking ends up: a question,\n * the answer, the correction, the reason a decision went the way it did. When\n * the task is completed that thread stops being visible — it is not deleted,\n * but it leaves every list anyone looks at, and it lives in a tracker rather\n * than in the project it was about. Six months later the decision is\n * unrecoverable from the place you would look for it.\n *\n * So on completion the thread is written into the owning project's notes, where\n * the rest of that project's knowledge already is and where the memory indexer\n * will pick it up.\n *\n * One file per task, rewritten in full each time rather than appended to. The\n * whole thread is fetched on every archive, so rewriting is naturally\n * idempotent: archiving twice produces the same file, and a recurring task\n * accumulates its history in one place instead of scattering a note per\n * occurrence. Appending would need duplicate detection, and duplicate detection\n * is where this would grow a bug.\n */\n\nimport { mkdirSync, writeFileSync, existsSync, readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport type { Task } from \"./types.js\";\n\nexport interface ArchiveComment {\n id: string;\n content: string;\n postedAt?: string;\n}\n\nexport interface ArchiveResult {\n path: string;\n /** False when the file already held exactly this content. */\n written: boolean;\n commentCount: number;\n /** Set when nothing was written and the reason was not \"unchanged\". */\n skipped?: \"no-discussion\";\n}\n\n/**\n * Filesystem-safe stem for a task, stable across re-archives.\n *\n * The id is included because titles change and two tasks can share one — the\n * id is what makes the path stable, and stability is what makes rewriting\n * idempotent rather than accumulating near-duplicate files.\n */\nexport function archiveSlug(task: Pick<Task, \"id\" | \"title\">): string {\n const title = task.title\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}]+/gu, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60)\n .replace(/-+$/g, \"\");\n return title ? `${title} - ${task.id}` : task.id;\n}\n\n/** Where a task's thread belongs, given the project root that owns it. */\nexport function archivePath(projectRoot: string, task: Pick<Task, \"id\" | \"title\">): string {\n return join(projectRoot, \"Notes\", \"tasks\", `${archiveSlug(task)}.md`);\n}\n\n/**\n * Render the task and its thread as a note.\n *\n * The description is included in full. It is usually the runbook or the\n * reasoning, and it is the half of the record that explains why the comments\n * say what they say.\n */\nexport function renderArchive(\n task: Task,\n comments: ArchiveComment[],\n completedAt: string\n): string {\n const out: string[] = [];\n\n out.push(\"---\");\n out.push(`task_id: ${task.id}`);\n out.push(`title: ${JSON.stringify(task.title)}`);\n if (task.owner.project) out.push(`owner: ${task.owner.project}`);\n if (task.due) out.push(`due: ${task.due}`);\n if (task.recurrence) out.push(`recurrence: ${JSON.stringify(task.recurrence)}`);\n out.push(`completed: ${completedAt}`);\n if (task.sourceUrl) out.push(`source: ${task.sourceUrl}`);\n out.push(\"---\");\n out.push(\"\");\n out.push(`# ${task.title}`);\n out.push(\"\");\n\n if (task.body.trim()) {\n out.push(\"## Task\");\n out.push(\"\");\n out.push(task.body.trim());\n out.push(\"\");\n }\n\n out.push(`## Discussion (${comments.length})`);\n out.push(\"\");\n\n if (comments.length === 0) {\n // Stated rather than omitted: an empty section says the thread was checked\n // and was empty, where a missing section says nothing at all.\n out.push(\"_No comments were posted on this task._\");\n out.push(\"\");\n } else {\n for (const c of comments) {\n const when = c.postedAt ? c.postedAt.slice(0, 19).replace(\"T\", \" \") : \"date unknown\";\n out.push(`### ${when}`);\n out.push(\"\");\n out.push(c.content.trim());\n out.push(\"\");\n }\n }\n\n return out.join(\"\\n\");\n}\n\n/** Drop the `completed:` frontmatter line, so two archives are comparable. */\nfunction withoutStamp(body: string): string {\n return body\n .split(\"\\n\")\n .filter((l) => !l.startsWith(\"completed: \"))\n .join(\"\\n\");\n}\n\n/**\n * Write the archive, skipping the write when nothing changed.\n *\n * Returning `written: false` for an unchanged file matters for a recurring\n * task: it is archived on every completion, and rewriting an identical file\n * daily would churn mtimes and make the indexer re-read it for nothing.\n */\nexport function writeArchive(\n projectRoot: string,\n task: Task,\n comments: ArchiveComment[],\n completedAt: string\n): ArchiveResult {\n const path = archivePath(projectRoot, task);\n\n // Nothing was discussed, so there is nothing to keep. The point of this is to\n // preserve a conversation that completing the task would otherwise bury —\n // where no conversation happened, a file saying so is noise, and enough of it\n // buries the notes that DO carry something. The task itself is not at risk:\n // it stays in the tracker either way.\n if (comments.length === 0) {\n return { path, written: false, commentCount: 0, skipped: \"no-discussion\" };\n }\n\n const body = renderArchive(task, comments, completedAt);\n\n if (existsSync(path)) {\n try {\n // Compare without the completion stamp. It is `new Date()` at call time,\n // so including it makes every re-archive differ from the last by\n // construction — the unchanged case could never fire, the file would be\n // rewritten on every duplicate delivery, and the owning session would be\n // notified again each time about a discussion it has already seen. The\n // thread is what is being preserved; when to notice it is not part of it.\n if (withoutStamp(readFileSync(path, \"utf-8\")) === withoutStamp(body)) {\n return { path, written: false, commentCount: comments.length };\n }\n } catch {\n // Unreadable for any reason — fall through and rewrite rather than\n // treating a read failure as \"already up to date\".\n }\n }\n\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, body, \"utf-8\");\n return { path, written: true, commentCount: comments.length };\n}\n","/**\n * projects.ts — reconcile sessions against the tracker projects that address them.\n *\n * A session can only be given work from a phone if something in the tracker\n * names it. That mapping drifts constantly and invisibly: sessions get renamed,\n * new ones appear, old ones stop being used, and nothing anywhere says \"these\n * five can receive work and these thirteen cannot\". The failure is silent in the\n * worst direction — filing a task for a session with no project looks exactly\n * like filing one that will be picked up.\n *\n * So this reports the mapping in both directions rather than just listing what\n * exists: sessions with no project (cannot be addressed) and projects with no\n * session (will queue until one launches, which is legitimate and worth\n * distinguishing from a mistake).\n */\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst run = promisify(execFile);\n\nexport interface SessionEntry {\n name: string;\n directory: string;\n}\n\nexport interface ProjectEntry {\n id: string;\n name: string;\n}\n\nexport type MappingState = \"mapped\" | \"session-only\" | \"project-only\";\n\nexport interface MappingRow {\n name: string;\n state: MappingState;\n directory?: string;\n projectId?: string;\n}\n\n/**\n * Read the session manifest from AIBroker.\n *\n * The manifest rather than a live enumeration, deliberately: it is the set of\n * sessions the user actually works with, which is the right unit for \"should\n * this have an inbox\". A live list would drop everything not open at this\n * instant and churn a permanent decision on a transient signal.\n *\n * Returns an empty list when AIBroker is absent — this command degrades to\n * \"here are your tracker projects\" rather than failing, in keeping with the\n * rest of the bus being optional.\n */\nexport async function readSessionManifest(bin = \"aibroker\"): Promise<SessionEntry[]> {\n let stdout: string;\n try {\n ({ stdout } = await run(bin, [\"sessions\", \"list\"], { timeout: 20_000 }));\n } catch {\n return [];\n }\n\n const entries: SessionEntry[] = [];\n for (const line of stdout.split(\"\\n\")) {\n // Manifest rows are indented \" Name<spaces>/absolute/path\". Anything\n // without a leading absolute path is a header or a log line.\n const m = line.match(/^\\s{2}(\\S.*?)\\s{2,}(\\/.*)$/);\n if (!m) continue;\n const name = m[1].trim();\n const directory = m[2].trim();\n if (!name || !directory) continue;\n entries.push({ name, directory });\n }\n return entries;\n}\n\n/** Compare names the way a person scanning two lists would. */\nexport function normalizeName(name: string): string {\n return name.trim().toLowerCase().replace(/\\s+/g, \" \");\n}\n\n/**\n * Join sessions and tracker projects into one ordered view.\n *\n * Sorted mapped-first then by name, so the actionable rows — sessions that\n * cannot yet be addressed — sit together rather than scattered through a list\n * of things that already work.\n */\nexport function reconcile(\n sessions: SessionEntry[],\n projects: ProjectEntry[]\n): MappingRow[] {\n const byName = new Map<string, ProjectEntry>();\n for (const p of projects) byName.set(normalizeName(p.name), p);\n\n const seen = new Set<string>();\n const rows: MappingRow[] = [];\n\n for (const s of sessions) {\n const key = normalizeName(s.name);\n const project = byName.get(key);\n if (project) seen.add(key);\n rows.push({\n name: s.name,\n state: project ? \"mapped\" : \"session-only\",\n directory: s.directory,\n projectId: project?.id,\n });\n }\n\n for (const p of projects) {\n const key = normalizeName(p.name);\n if (seen.has(key)) continue;\n rows.push({ name: p.name, state: \"project-only\", projectId: p.id });\n }\n\n const rank: Record<MappingState, number> = {\n mapped: 0,\n \"session-only\": 1,\n \"project-only\": 2,\n };\n return rows.sort(\n (a, b) => rank[a.state] - rank[b.state] || a.name.localeCompare(b.name)\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 type { Database } from \"better-sqlite3\";\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, releaseClaim, loadState, saveState, STATE_FILE } 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\";\nimport { writeArchive } from \"../../tasks/archive.js\";\nimport {\n readSessionManifest,\n reconcile,\n normalizeName,\n type MappingRow,\n} from \"../../tasks/projects.js\";\n\n/**\n * Make a tracker sub-project name resolve to the PAI project it belongs to.\n *\n * Ownership is decided by matching the container name against a curated alias.\n * A session's display name is almost never its PAI slug — \"SL\" is\n * `seriousletter`, \"Home\" is `owner` — so a freshly created sub-project\n * routes nowhere until someone connects the two. That gap bit twice on\n * 2026-08-02: the project existed, looked addressable in every listing, and\n * tasks filed into it came back unrouted.\n *\n * The session's working directory is what links them: the manifest gives the\n * directory, the registry gives the project at that path. Returns a line worth\n * printing, or null when nothing needed doing.\n */\nfunction ensureAlias(db: Database, name: string, directory?: string): string | null {\n if (!directory) return `no directory for \"${name}\" — set the alias by hand`;\n\n const project = db\n .prepare(\"SELECT id, slug FROM projects WHERE root_path = ? AND status = 'active'\")\n .get(directory) as { id: number; slug: string } | undefined;\n\n if (!project) return `no PAI project at ${directory} — tasks here will not route`;\n\n const existing = db\n .prepare(\"SELECT project_id FROM aliases WHERE lower(alias) = lower(?)\")\n .get(name) as { project_id: number } | undefined;\n\n if (existing) {\n // Repointing someone else's alias would silently steal their address.\n return existing.project_id === project.id\n ? null\n : `alias \"${name}\" already points elsewhere — left alone`;\n }\n\n db.prepare(\"INSERT INTO aliases (alias, project_id) VALUES (?, ?)\").run(name, project.id);\n return `aliased → ${project.slug}`;\n}\n\n/**\n * Render the session/project mapping.\n *\n * The three states are printed with different weight on purpose. \"Cannot be\n * addressed\" is the only one that needs action, and it is the one that is\n * otherwise invisible — filing a task for a session with no project looks\n * exactly like filing one that works.\n */\nfunction printProjectMapping(rows: MappingRow[], noBroker: boolean): void {\n const mapped = rows.filter((r) => r.state === \"mapped\");\n const unmapped = rows.filter((r) => r.state === \"session-only\");\n const orphan = rows.filter((r) => r.state === \"project-only\");\n\n console.log();\n console.log(chalk.bold(` Can receive work (${mapped.length})`));\n for (const r of mapped) console.log(chalk.green(\" ✓ \") + r.name);\n if (mapped.length === 0) console.log(dim(\" none\"));\n\n if (unmapped.length > 0) {\n console.log();\n console.log(chalk.bold(` No project — cannot be addressed (${unmapped.length})`));\n for (const r of unmapped) console.log(chalk.yellow(\" · \") + r.name);\n console.log();\n console.log(dim(\" pai task projects --create <name>... or --create-all\"));\n }\n\n if (orphan.length > 0) {\n console.log();\n console.log(chalk.bold(` Project with no session (${orphan.length})`));\n for (const r of orphan) console.log(dim(\" · \" + r.name));\n console.log(dim(\" Tasks filed here queue until that session launches — not a fault.\"));\n }\n\n if (noBroker) {\n console.log();\n console.log(dim(\" AIBroker not reachable — showing tracker projects only.\"));\n }\n console.log();\n}\n\nconst dim = chalk.dim;\nconst bold = chalk.bold;\nconst warn = chalk.yellow;\nconst ok = chalk.green;\n\n// ---------------------------------------------------------------------------\n// Wiring\n// ---------------------------------------------------------------------------\n\nfunction buildProvider(): TodoistProvider | 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 defaultOwner: tasks.defaultOwner,\n });\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(\"projects\")\n .description(\"Show which sessions can be given work from the tracker, and create the missing projects\")\n .option(\"--create <names...>\", \"Create tracker projects for these session names\")\n .option(\"--create-all\", \"Create a project for every session that has none\")\n .action(async (opts) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n\n if (!provider.listSubProjects || !provider.findOrCreateSubProject) {\n console.log();\n console.log(\n chalk.yellow(` ${provider.providerId} does not address work by sub-project.`)\n );\n console.log(dim(\" Session-scoped inboxes need a tracker with nested projects.\"));\n console.log();\n return;\n }\n\n const [sessions, projects] = await Promise.all([\n readSessionManifest(),\n provider.listSubProjects(),\n ]);\n\n const rows = reconcile(sessions, projects);\n\n const wanted = opts.createAll\n ? rows.filter((r) => r.state === \"session-only\").map((r) => r.name)\n : ((opts.create as string[] | undefined) ?? []);\n\n if (wanted.length > 0) {\n const addressable = new Set(\n rows.filter((r) => r.state === \"session-only\").map((r) => normalizeName(r.name))\n );\n console.log();\n for (const name of wanted) {\n // Refuse to create a project for a session nobody has: it would look\n // addressable and never be picked up, which is the failure this\n // command exists to surface.\n if (!addressable.has(normalizeName(name))) {\n console.log(chalk.yellow(` skipped ${name}`) + dim(\" — no such session, or it already has a project\"));\n continue;\n }\n const { id, created } = await provider.findOrCreateSubProject(name);\n console.log(\n (created ? chalk.green(\" created \") : dim(\" exists \")) + name + dim(` ${id}`)\n );\n\n // A project alone does not route. Ownership resolves by matching the\n // container name against a curated alias, and a session's display\n // name is rarely its PAI slug — \"SL\" is `seriousletter`, \"Home\" is\n // `owner`. Creating the project and stopping produced something\n // that looked addressable and silently was not, twice, before this\n // was wired in. The alias is not a separate step a user should have\n // to know about; it is what makes the project mean anything.\n const row = rows.find((r) => normalizeName(r.name) === normalizeName(name));\n const note = ensureAlias(openRegistry(), name, row?.directory);\n if (note) console.log(dim(` ${note}`));\n }\n console.log();\n return;\n }\n\n printProjectMapping(rows, sessions.length === 0);\n });\n\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 <when>\", \"Due date: ISO, natural language, or a recurrence (\\\"every monday at 9\\\")\")\n .option(\"--priority <p>\", \"p1 (highest) … p4 (default)\")\n .option(\"--url <url>\", \"Reference — prefer a hook:// URL over a file path\")\n .option(\"--into <sub-project>\", \"File into this sub-project under the bus root, creating it if absent\")\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 into: opts.into,\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 dryRun: Boolean(opts.dryRun),\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 triggered: chalk.green(\"▶\"),\n complete: chalk.green(\"✓\"),\n running: dim(\"·\"),\n probe: chalk.yellow(\"?\"),\n orphaned: chalk.yellow(\"!\"),\n abandoned: chalk.red(\"\\u2a2f\"),\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(\"archive <id>\")\n .description(\"Save a task's discussion into the owning project's notes, without completing it\")\n .option(\"--quiet\", \"Print nothing (for hook and webhook callers)\")\n .option(\"--no-notify\", \"Do not tell the owning session where the file landed\")\n .action(async (id, opts: { quiet?: boolean; notify?: boolean }) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n // Notifies by default: this command is the path something OUTSIDE the\n // session took — a checkbox ticked in the tracker — so the session has no\n // idea it happened. `task done` deliberately does not notify, because\n // there the session is the one that just did it.\n const saved = await cmdArchiveTask(provider, id, {\n quiet: opts.quiet,\n notify: opts.notify !== false,\n });\n // Non-zero on failure so a webhook caller can tell nothing was saved\n // rather than assuming success — the whole point is not losing content.\n if (!saved) process.exitCode = 1;\n });\n\n taskCmd\n .command(\"done <id>\")\n .description(\"Mark a task complete on the tracker, keeping its discussion\")\n .option(\"--no-archive\", \"Complete without saving the comment thread\")\n .action(async (id, opts) => {\n const provider = buildProvider();\n if (!provider) return reportUnconfigured();\n\n // Archive BEFORE completing. A completed task is harder to read back on\n // some providers, and losing the thread is the failure this exists to\n // prevent — so the copy is taken while the task is unambiguously there.\n let archived: { path: string; written: boolean; commentCount: number } | null = null;\n if (opts.archive !== false) {\n archived = await archiveTaskThread(provider, id);\n }\n\n await provider.complete(id);\n console.log(chalk.green(` Completed ${id}.`));\n\n // Release the claim left behind by whichever run finished this task.\n // For a recurring task, `complete` above only advanced the due date to\n // the next occurrence — the `pai-running` label, the progress-marker\n // comment, and this poller's transient run state (`startedAt` /\n // `claimSeenAt` / `failedProbes`) all outlive it unless something\n // clears them. Left alone, the next occurrence looks already claimed,\n // and the scheduler classifies it as overrunning and starts probing a\n // session that finished hours ago.\n //\n // Best-effort throughout: closing the task above is the action that\n // has to succeed, and this cleanup must never turn a completed task\n // into a failed command.\n try {\n const task = await provider.getTask(id);\n if (task) {\n const state = loadState(STATE_FILE);\n await releaseClaim(task, provider, state);\n // Record the due date this advanced to, the same way `tick()`\n // records it every cycle. Without this, the next `pai task poll`\n // has no way to tell this advance apart from a human dragging the\n // task forward by hand — both look like \"due moved exactly one\n // recurrence period, unclaimed\" — and would dispatch the task a\n // second time for a run that just finished.\n if (task.due) state.lastSeenDue[id] = task.due;\n saveState(state, STATE_FILE);\n }\n } catch {\n // Cleanup is best-effort — see comment above.\n }\n\n if (archived) {\n const what = `${archived.commentCount} comment${archived.commentCount === 1 ? \"\" : \"s\"}`;\n console.log(\n archived.written\n ? dim(` Discussion saved (${what}) → ${archived.path}`)\n : dim(` Discussion already saved (${what}), unchanged.`)\n );\n }\n });\n}\n\n/**\n * `pai task archive <id>` — save a task's discussion without completing it.\n *\n * Split out from `done` so the webhook has something to call. Ticking the\n * checkbox in the tracker is the completion path people actually use, and it\n * never runs `pai task done` — so an archive wired only into that command\n * fires on the route nobody takes and not on the route everybody does.\n *\n * Safe to call repeatedly: the archive rewrites one file per task from the full\n * thread, so a duplicate webhook or a poller backstop covering the same\n * completion costs one no-op.\n */\nexport async function cmdArchiveTask(\n provider: TaskProvider,\n id: string,\n opts: { quiet?: boolean; notify?: boolean } = {}\n): Promise<boolean> {\n if (!provider.getTask || !provider.listComments) {\n if (!opts.quiet) console.log(warn(` ${provider.providerId} cannot read a task's discussion.`));\n return false;\n }\n\n const task = await provider.getTask(id);\n if (!task) {\n if (!opts.quiet) console.log(warn(` No task ${id}.`));\n return false;\n }\n\n const root = task.owner.rootPath;\n if (!task.owner.project || !root) {\n if (!opts.quiet) {\n console.log(\n warn(\" No owning project — nothing archived. \") +\n dim(\"Label it `pai:<project>` to give it a home.\")\n );\n }\n return false;\n }\n\n const comments = await provider.listComments(id);\n const r = writeArchive(root, task, comments, new Date().toISOString());\n\n if (r.written && opts.notify) {\n await tellOwningSession(task, r.path, r.commentCount, opts.quiet);\n }\n\n if (!opts.quiet) {\n const what = `${r.commentCount} comment${r.commentCount === 1 ? \"\" : \"s\"}`;\n if (r.skipped === \"no-discussion\") {\n console.log(dim(\" No discussion on this task — nothing to archive.\"));\n } else {\n console.log(\n r.written\n ? ok(` Saved (${what}) → `) + r.path\n : dim(` Already saved (${what}), unchanged.`)\n );\n }\n }\n return true;\n}\n\n/**\n * Tell the owning session that a discussion was filed, if it is running.\n *\n * `Notes/tasks/` is a default, not a decision. The session that owns a project\n * knows where things actually belong there — a dated triage folder, a ledger\n * row, an existing note the discussion continues. It can move or link the file;\n * this side cannot, because the convention lives in that project's head and not\n * in PAI.\n *\n * **Never spawns.** Launching a session to tell it a file exists inverts the\n * cost: the archive is already safely on disk, and a terminal opening by itself\n * because someone ticked a checkbox is worse than a note filed one directory\n * away from ideal. If nothing is running, the file simply waits.\n *\n * Best-effort throughout. The archive has already succeeded by the time this\n * runs, and a notification that cannot be delivered must never turn a saved\n * discussion into a reported failure.\n */\nasync function tellOwningSession(\n task: Task,\n path: string,\n comments: number,\n quiet?: boolean\n): Promise<void> {\n const project = task.owner.project;\n if (!project) return;\n\n try {\n const transport = await detectAiBroker();\n if (!transport) return;\n\n const message = [\n `A discussion was archived from the tracker — ${comments} comment${comments === 1 ? \"\" : \"s\"} on \"${task.title}\".`,\n \"\",\n `Filed provisionally at: ${path}`,\n `Tracker id: ${task.id}`,\n \"\",\n \"This is a notification, not a work order. Nothing is waiting on you and the\",\n \"content is already safe on disk.\",\n \"\",\n \"Notes/tasks/ is PAI's default, chosen without knowing this project's\",\n \"conventions. If it belongs somewhere else here — a dated folder, a ledger\",\n \"row, an existing note the discussion continues — move or link it, then say\",\n \"nothing. If the default is fine, leave it.\",\n ].join(\"\\n\");\n\n // spawnIfAbsent: false is the whole point — see the note above.\n const result = await transport.dispatch(project, message, { spawnIfAbsent: false });\n\n if (!quiet && result.outcome === \"delivered\") {\n console.log(dim(` Told the ${project} session where it landed.`));\n }\n } catch {\n // Deliberately silent: the discussion is saved, which is what mattered.\n }\n}\n\n/**\n * Copy a task's thread into the notes of the project that owns it.\n *\n * Best-effort by design: this runs as a side effect of completing a task, and\n * a failure to archive must never stop the task being completed. It reports\n * what went wrong rather than throwing, because a silent skip here would lose\n * exactly the content it exists to keep.\n */\nasync function archiveTaskThread(\n provider: TaskProvider,\n id: string\n): Promise<{ path: string; written: boolean; commentCount: number } | null> {\n try {\n if (!provider.listComments) return null;\n\n const tasks = await provider.listOpen({ includeUnrouted: true });\n const task = tasks.find((t) => t.id === id);\n if (!task) {\n console.log(dim(` Not on the open list — nothing archived.`));\n return null;\n }\n\n const project = task.owner.project;\n const root = task.owner.rootPath;\n if (!project || !root) {\n console.log(\n warn(` No owning project — discussion not archived. `) +\n dim(`Label it \\`pai:<project>\\` to give it a home.`)\n );\n return null;\n }\n\n const comments = await provider.listComments(id);\n return writeArchive(root, task, comments, new Date().toISOString());\n } catch (e) {\n console.log(\n warn(` Could not archive the discussion: `) +\n (e instanceof Error ? e.message : String(e))\n );\n return null;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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 * Slug generator for PAI session notes.\n *\n * Extracts a 1-3 word descriptive slug from Claude Code JSONL transcripts\n * using keyword frequency analysis — no LLM required.\n *\n * JSONL format (one JSON object per line):\n * - type \"user\": { type: \"user\", message: { role: \"user\", content: string | [{ type, text }] } }\n * - type \"assistant\": { type: \"assistant\", message: { role: \"assistant\", content: [{ type, text }] } }\n */\n\nimport { readFileSync, existsSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { STOP_WORDS } from \"../utils/stop-words.js\";\n\n// ---------------------------------------------------------------------------\n// Public API types\n// ---------------------------------------------------------------------------\n\nexport interface SlugOptions {\n /** Maximum number of words in the generated slug (default: 3) */\n maxWords?: number;\n /** Maximum character length of the generated slug (default: 30) */\n maxLength?: number;\n}\n\n// STOP_WORDS imported from utils/stop-words.ts\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extract plain text from a parsed JSONL message object.\n */\nfunction extractText(obj: Record<string, unknown>): string | null {\n const msg = obj.message as Record<string, unknown> | undefined;\n if (!msg) return null;\n\n const content = msg.content;\n\n // Array of content blocks (modern assistant messages)\n if (Array.isArray(content)) {\n const texts: string[] = [];\n for (const block of content) {\n const b = block as Record<string, unknown>;\n if (b.type === \"text\" && typeof b.text === \"string\") {\n texts.push(b.text);\n }\n }\n return texts.join(\" \") || null;\n }\n\n // Plain string content (user messages)\n if (typeof content === \"string\") {\n return content || null;\n }\n\n return null;\n}\n\n/**\n * Tokenize a string into lowercase words, filtering stop words and short tokens.\n */\nfunction tokenize(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \" \")\n .split(/\\s+/)\n .filter((w) => {\n if (w.length < 3) return false;\n if (/^\\d+$/.test(w)) return false;\n if (STOP_WORDS.has(w)) return false;\n return true;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public functions\n// ---------------------------------------------------------------------------\n\n/**\n * Find the latest JSONL transcript for a project's Claude encoded directory.\n *\n * @param encodedDir Claude-encoded project directory name,\n * e.g. \"-Users-alice-dev-ai-PAI\"\n * @returns Absolute path to the most recently modified .jsonl file, or null.\n */\nexport function findLatestTranscript(encodedDir: string): string | null {\n const sessionsDir = join(\n homedir(),\n \".claude\",\n \"projects\",\n encodedDir,\n \"sessions\"\n );\n\n if (!existsSync(sessionsDir)) return null;\n\n let entries: string[];\n try {\n entries = readdirSync(sessionsDir).filter((f) => f.endsWith(\".jsonl\"));\n } catch {\n return null;\n }\n\n if (!entries.length) return null;\n\n // Sort by mtime descending, return the newest\n const sorted = entries\n .map((f) => {\n const fullPath = join(sessionsDir, f);\n try {\n return { path: fullPath, mtime: statSync(fullPath).mtimeMs };\n } catch {\n return { path: fullPath, mtime: 0 };\n }\n })\n .sort((a, b) => b.mtime - a.mtime);\n\n return sorted[0].path;\n}\n\n/**\n * Read the last `count` human/assistant message pairs from a JSONL file.\n *\n * Synchronous — reads the whole file in memory. JSONL transcripts are\n * bounded by context window size so this is safe even for large sessions.\n *\n * @param jsonlPath Absolute path to a Claude Code .jsonl transcript file.\n * @param count Number of conversation pairs to read (default: 15).\n * @returns Array of plain-text message strings (up to count*2 entries).\n */\nexport function readLastMessages(\n jsonlPath: string,\n count: number = 15\n): string[] {\n if (!existsSync(jsonlPath)) return [];\n\n let raw: string;\n try {\n raw = readFileSync(jsonlPath, \"utf8\");\n } catch {\n return [];\n }\n\n const lines = raw.split(\"\\n\").filter((l) => l.trim().length > 0);\n const messages: string[] = [];\n const limit = count * 2;\n\n // Walk from the end to collect the most recent messages first\n for (let i = lines.length - 1; i >= 0 && messages.length < limit; i--) {\n let obj: Record<string, unknown>;\n try {\n obj = JSON.parse(lines[i]) as Record<string, unknown>;\n } catch {\n continue;\n }\n\n const type = obj.type as string | undefined;\n if (type !== \"assistant\" && type !== \"user\") continue;\n\n const text = extractText(obj);\n if (text && text.trim().length > 0) {\n // Prepend to maintain chronological order\n messages.unshift(text);\n }\n }\n\n return messages;\n}\n\n/**\n * Generate a descriptive 1-3 word slug from conversation message strings.\n *\n * Algorithm:\n * 1. Tokenize all messages, remove stop words and short tokens\n * 2. Count word frequency\n * 3. Pick the top `maxWords` most frequent tokens\n * 4. Join with hyphen, lowercase\n *\n * @param messages Array of plain-text conversation messages.\n * @param opts Optional slug configuration.\n * @returns A lowercase hyphen-joined slug, e.g. \"memory-engine-refactor\",\n * or \"unnamed-session\" if no meaningful words are found.\n */\nexport function generateSlug(messages: string[], opts?: SlugOptions): string {\n const maxWords = opts?.maxWords ?? 3;\n const maxLength = opts?.maxLength ?? 30;\n\n if (!messages.length) return \"unnamed-session\";\n\n // Count word frequencies across all messages\n const freq = new Map<string, number>();\n for (const msg of messages) {\n for (const token of tokenize(msg)) {\n freq.set(token, (freq.get(token) ?? 0) + 1);\n }\n }\n\n if (!freq.size) return \"unnamed-session\";\n\n // Sort by frequency descending, take top maxWords\n const topWords = Array.from(freq.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, maxWords)\n .map(([word]) => word);\n\n if (!topWords.length) return \"unnamed-session\";\n\n // Join and trim to maxLength (truncate at last hyphen boundary)\n let slug = topWords.join(\"-\");\n if (slug.length > maxLength) {\n slug = slug.slice(0, maxLength).replace(/-[^-]*$/, \"\");\n }\n\n return slug || \"unnamed-session\";\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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 * Session checkpoint command — appends a timestamped block to the active\n * session note. Designed for use in hooks; fast, silent, rate-limited.\n */\n\nimport { existsSync, readdirSync, statSync, readFileSync, writeFileSync, renameSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir, tmpdir } from \"node:os\";\n\nfunction findNotesDirForCwd(): string | null {\n const cwd = process.cwd();\n const claudeProjectsDir = join(homedir(), \".claude\", \"projects\");\n\n if (!existsSync(claudeProjectsDir)) return null;\n\n const expectedEncoded = cwd.replace(/[/\\s.\\-]/g, \"-\");\n let encodedDir: string | null = null;\n\n try {\n const entries = readdirSync(claudeProjectsDir);\n\n if (entries.includes(expectedEncoded)) {\n encodedDir = expectedEncoded;\n } else {\n for (const entry of entries) {\n const full = join(claudeProjectsDir, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n const candidate = entry.replace(/-+$/, \"\");\n const expected = expectedEncoded.replace(/-+$/, \"\");\n if (candidate === expected) {\n encodedDir = entry;\n break;\n }\n }\n }\n } catch {\n return null;\n }\n\n if (!encodedDir) return null;\n const notesDir = join(claudeProjectsDir, encodedDir, \"Notes\");\n return existsSync(notesDir) ? notesDir : null;\n}\n\nfunction findLatestNoteFile(notesDir: string): string | null {\n let entries: string[];\n try {\n entries = readdirSync(notesDir);\n } catch {\n return null;\n }\n\n const mdFiles = entries.filter((e) => e.endsWith(\".md\"));\n if (mdFiles.length === 0) return null;\n\n let latestPath: string | null = null;\n let latestMtime = 0;\n\n for (const file of mdFiles) {\n const full = join(notesDir, file);\n try {\n const { mtimeMs } = statSync(full);\n if (mtimeMs > latestMtime) {\n latestMtime = mtimeMs;\n latestPath = full;\n }\n } catch {\n // skip unreadable files\n }\n }\n\n return latestPath;\n}\n\nfunction checkpointTooRecent(notesDir: string, minGapSeconds: number): boolean {\n const safeKey = notesDir.replace(/[^a-zA-Z0-9]/g, \"-\").slice(-80);\n const tmpFile = join(tmpdir(), `pai-checkpoint-${safeKey}`);\n if (!existsSync(tmpFile)) return false;\n try {\n const { mtimeMs } = statSync(tmpFile);\n return Date.now() - mtimeMs < minGapSeconds * 1000;\n } catch {\n return false;\n }\n}\n\nfunction touchCheckpointSentinel(notesDir: string): void {\n const safeKey = notesDir.replace(/[^a-zA-Z0-9]/g, \"-\").slice(-80);\n const tmpFile = join(tmpdir(), `pai-checkpoint-${safeKey}`);\n try {\n writeFileSync(tmpFile, String(Date.now()), \"utf8\");\n } catch {\n // Non-fatal — rate limiting is best-effort\n }\n}\n\nexport function cmdCheckpoint(\n message: string,\n opts: { minGap?: string }\n): void {\n const minGapSeconds = parseInt(opts.minGap ?? \"300\", 10);\n\n const notesDir = findNotesDirForCwd();\n if (!notesDir) return;\n\n if (checkpointTooRecent(notesDir, minGapSeconds)) return;\n\n const notePath = findLatestNoteFile(notesDir);\n if (!notePath) return;\n\n const timestamp = new Date().toISOString();\n const block = `\\n## Checkpoint — ${timestamp}\\n${message}\\n`;\n\n const tmpPath = `${notePath}.checkpoint.tmp`;\n try {\n const existing = readFileSync(notePath, \"utf8\");\n writeFileSync(tmpPath, existing + block, \"utf8\");\n renameSync(tmpPath, notePath);\n } catch {\n try {\n if (existsSync(tmpPath)) renameSync(tmpPath, tmpPath + \".dead\");\n } catch {\n /* ignore */\n }\n return;\n }\n\n touchCheckpointSentinel(notesDir);\n}\n","/**\n * pai pause [--body-file <path>] [--session-id <uuid>] [--no-body] [--dry-run]\n *\n * Writes a \"## Continue\" checkpoint to the project's TODO.md and appends the\n * same checkpoint to the current session note, then prints a prominent\n * reminder about HOW to exit Claude Code safely.\n *\n * THE BODY IS THE POINT\n * ---------------------\n * This command used to emit a fixed four-line block: session name, timestamp,\n * working directory. That is metadata, not a checkpoint — it tells the next\n * session nothing about what was decided, what is blocked, or what to do next.\n * The actual checkpoint was written by the model and printed to the terminal,\n * where it died. Recovering it required the user to copy it off the screen by\n * hand.\n *\n * So `--body-file` is now mandatory. Omitting it is an error rather than a\n * silent downgrade, because a silent downgrade is precisely the failure this\n * fixes: everything looked like it worked, and the state was gone.\n * `--no-body` remains available for deliberate metadata-only use.\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\nimport type { Database } from \"better-sqlite3\";\nimport { basename } from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\nimport {\n applyContinue,\n appendCheckpointToNote,\n findNotesDir,\n findLatestNote,\n readBodyFile,\n} from \"../../../session/checkpoint-block.js\";\n\nexport interface PauseOptions {\n dryRun?: boolean;\n /** Path to the model-authored checkpoint markdown, or \"-\" for stdin. */\n bodyFile?: string;\n /** Claude Code session UUID — becomes the `claude --resume` handle. */\n sessionId?: string;\n /** Deliberately write a metadata-only checkpoint. */\n noBody?: boolean;\n}\n\n/** Format a session row as \"0003 - 2026-08-01 - Title\". */\nexport function formatSessionLine(session: SessionRow | undefined): string {\n if (!session) return \"Unknown session\";\n const num = String(session.number).padStart(4, \"0\");\n const titlePart = session.title || session.slug || \"Session\";\n return `${num} - ${session.date} - ${titlePart}`;\n}\n\n/**\n * Resolve the string that identifies a session in a ## Continue block.\n *\n * THIS IS THE PRESERVATION KEY. It decides whether an authored checkpoint\n * belongs to the current session and must be left alone, so every writer must\n * derive it identically — `pai pause`, `pai session handover`, and the hooks'\n * updateTodoContinue. Deriving it in more than one place is how a checkpoint\n * gets clobbered: one writer says \"0006 - …\" and the other says\n * \"Unknown session\", the keys do not match, and the unattended writer replaces\n * work it should have preserved.\n *\n * The registry row is preferred. When the project has no session rows — which\n * happens whenever one codebase is registered under several paths — it falls\n * back to the session note's filename, which is what the hooks use.\n */\nexport function resolveSessionLine(\n project: ProjectRow,\n session: SessionRow | undefined,\n notePath: string | null\n): string {\n // The session note filename wins.\n //\n // It is what the unattended writers use, and agreement is the entire point —\n // a mismatch means the hooks replace a checkpoint they should have preserved.\n //\n // The registry row is the weaker source: it is resolved with\n // `ORDER BY number DESC`, which stops meaning \"current\" once a project has\n // been merged. Numbers from both merged rows then coexist, so the highest\n // number can be a session from months earlier while the note on disk is\n // today's. Observed live: the registry offered \"0184 - 2026-02-22\" while the\n // current note was \"0007 - 2026-08-01\".\n if (notePath) return basename(notePath).replace(/\\.md$/, \"\");\n if (session) return formatSessionLine(session);\n return \"Unknown session\";\n}\n\n/** Locate the latest session note for a project, or null. */\nexport function resolveNotePath(project: ProjectRow): string | null {\n const notesDir = findNotesDir(project.root_path, project.encoded_dir ?? \"\");\n return notesDir ? findLatestNote(notesDir) : null;\n}\n\n/**\n * Resolve the project containing cwd, or undefined.\n *\n * Tries the literal path first, then its resolved form. A symlinked path\n * prefix (`~/dev -> Cloud/Development`) makes one directory reachable under\n * two spellings; matching only the literal string means a session started from\n * the other spelling finds nothing, and the scanner then registers a second\n * project for the same directory — which splits session history in half.\n */\nexport function resolveProjectByCwd(\n db: Database,\n cwd: string\n): ProjectRow | undefined {\n const stmt = db.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\n const direct = stmt.get(cwd) as ProjectRow | undefined;\n if (direct) return direct;\n\n let resolved: string;\n try {\n resolved = realpathSync(cwd);\n } catch {\n return undefined;\n }\n if (resolved === cwd) return undefined;\n\n return stmt.get(resolved) as ProjectRow | undefined;\n}\n\nfunction printMissingBodyError(): void {\n console.error(\n \"\\n\" +\n chalk.bgRed.white.bold(\n \" CHECKPOINT BODY MISSING — nothing was written \"\n ) +\n \"\\n\\n\" +\n chalk.yellow(\n \" A checkpoint without a body is just a timestamp. The next session\\n\" +\n \" would learn nothing about what was decided or what is blocked.\\n\"\n ) +\n \"\\n\" +\n chalk.white(\" Write your checkpoint markdown to a file, then re-run:\\n\") +\n \"\\n\" +\n chalk.cyan(\" pai pause --body-file /path/to/checkpoint.md \\\\\\n\") +\n chalk.cyan(\" --session-id <claude-session-uuid>\\n\") +\n \"\\n\" +\n chalk.dim(\" Or pipe it: \") +\n chalk.cyan(\"cat checkpoint.md | pai pause --body-file -\\n\") +\n \"\\n\" +\n chalk.dim(\n \" If you genuinely want a metadata-only checkpoint, pass --no-body.\\n\"\n )\n );\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport function cmdPause(db: Database, opts: PauseOptions): void {\n // ---- 1. Body first — fail before touching anything ----\n let body = \"\";\n\n if (opts.bodyFile) {\n try {\n body = readBodyFile(opts.bodyFile).trim();\n } catch (err) {\n console.error(\n chalk.red(\"pai pause: \") +\n `Could not read checkpoint body from ${opts.bodyFile}: ${String(err)}`\n );\n process.exitCode = 1;\n return;\n }\n if (!body) {\n console.error(\n chalk.red(\"pai pause: \") +\n `Checkpoint body at ${opts.bodyFile} is empty.`\n );\n process.exitCode = 1;\n return;\n }\n } else if (!opts.noBody) {\n printMissingBodyError();\n process.exitCode = 1;\n return;\n }\n\n // ---- 2. Resolve project by cwd ----\n const cwd = process.cwd();\n const project = resolveProjectByCwd(db, cwd);\n\n if (!project) {\n console.error(\n chalk.red(\"pai pause: \") +\n \"Current directory is not within a registered PAI project.\\n\" +\n ` cwd: ${cwd}`\n );\n process.exitCode = 1;\n return;\n }\n\n // ---- 3. 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 // ---- 3b. Agree with the hooks on how a session is identified ----\n //\n // The unattended writers (pre-compact hook, daemon work-queue worker) key a\n // session by its note filename. This command keys it by the registry row.\n // That string is the preservation key — it is what decides whether an\n // authored checkpoint belongs to the current session and must be left alone —\n // so the two must produce the same value or the hooks will clobber this\n // command's work.\n //\n // They diverge whenever the registry has no session row for the resolved\n // project, which happens when one codebase is registered under several paths.\n // Falling back to the note filename makes them agree, and incidentally\n // replaces a useless \"Unknown session\" label with the real one.\n const notePath = resolveNotePath(project);\n const sessionLine = resolveSessionLine(project, session, notePath);\n\n // A registered project with zero sessions almost always means split identity:\n // the same codebase registered under several paths (a dev copy and a synced\n // copy, say), with the session history filed under a different row. The\n // checkpoint still writes correctly, but \"Unknown session\" in TODO.md is a\n // symptom worth naming rather than shipping silently.\n if (!session) {\n const siblings = db\n .prepare(\n `SELECT p.slug, p.root_path, COUNT(s.id) AS n\n FROM projects p LEFT JOIN sessions s ON s.project_id = p.id\n WHERE p.id != ? AND (p.slug = ? OR p.slug LIKE ? || '-%' OR ? LIKE p.slug || '-%')\n GROUP BY p.id HAVING n > 0\n ORDER BY n DESC LIMIT 3`\n )\n .all(project.id, project.slug, project.slug, project.slug) as Array<{\n slug: string;\n root_path: string;\n n: number;\n }>;\n\n console.log(\n chalk.yellow(\n ` Note: project '${project.slug}' has no registered sessions; the ` +\n `checkpoint is labelled from the session note instead (\"${sessionLine}\").`\n )\n );\n for (const s of siblings) {\n console.log(\n chalk.dim(\n ` '${s.slug}' (${s.root_path}) has ${s.n} — likely the same project under another path.`\n )\n );\n }\n }\n\n // ---- 4. Write the ## Continue block ----\n const timestamp = new Date().toISOString();\n\n // `authored` records WHO initiated the checkpoint, not whether it turned out\n // to have anything in it. A pause is model-initiated by definition, so a\n // bodyless one used to mislabel itself \"auto\" and silently forfeit the\n // preservation rules that exist for exactly this kind of write. Substance is\n // a separate question and is now asked separately (`hasSubstance`); an empty\n // model block is still boilerplate-only, so it cannot freeze TODO.md — the\n // `isBoilerplateOnly` guards on cases 1b and 2b already see to that.\n const result = applyContinue({\n rootPath: project.root_path,\n authored: \"model\",\n sessionLine,\n sessionId: opts.sessionId,\n cwd,\n body,\n timestamp,\n dryRun: opts.dryRun,\n });\n\n if (result.action === \"failed\") {\n console.error(\n chalk.red(\"pai pause: \") + `Failed to write TODO.md: ${result.error}`\n );\n process.exitCode = 1;\n return;\n }\n\n if (opts.dryRun) {\n console.log(\n \"\\n\" + chalk.bold(\"Dry run — would write to:\") + \" \" + chalk.cyan(result.path ?? \"?\")\n );\n console.log(chalk.dim(\"─\".repeat(60)));\n console.log(chalk.dim(result.block));\n console.log(chalk.dim(\"─\".repeat(60)));\n } else {\n console.log(\n chalk.green(\" ## Continue written to: \") + chalk.cyan(result.path ?? \"?\")\n );\n }\n\n // ---- 5. Mirror the body into the session note ----\n //\n // TODO.md's ## Continue is a single slot that the next checkpoint overwrites.\n // The session note is the durable record, so the body goes to both.\n if (body) {\n if (!notePath) {\n console.log(\n chalk.yellow(\n \" (No session note found — checkpoint saved to TODO.md only.)\"\n )\n );\n } else if (opts.dryRun) {\n console.log(\n chalk.dim(` Would append checkpoint to: ${basename(notePath)}`)\n );\n } else {\n const { appended, error } = appendCheckpointToNote(\n notePath,\n body,\n timestamp\n );\n if (appended) {\n console.log(\n chalk.green(\" Checkpoint appended to note: \") +\n chalk.cyan(basename(notePath))\n );\n } else if (error) {\n console.log(\n chalk.yellow(` (Could not append to session note: ${error})`)\n );\n }\n }\n }\n\n // ---- 6. 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(\" Ctrl+C bypasses PAI's stop-hook, which means:\"),\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 * Rolling auto-checkpoint — the floor under every session.\n *\n * WHY THIS EXISTS\n * ---------------\n * A model-authored checkpoint is the best handover there is: it knows what is\n * unfinished, what is load-bearing, and what to do next. It has one fatal\n * property — it only exists if the model was asked to write one.\n *\n * The model is not invoked on `/exit`, and certainly not on Ctrl+C. No hook can\n * make it write prose at exit time. So a good checkpoint at exit can only exist\n * if something wrote it *before* exit. Exit-time is the wrong moment by\n * construction, and building the guarantee there is why sessions kept ending\n * with nothing to hand over.\n *\n * This module builds a mechanical digest instead — recent prompts, the state of\n * the working tree — cheap enough to run on a timer during the session. It is\n * strictly worse than what the model would write, and that is the point: it is\n * the floor, not the ceiling. `applyContinue` writes it in \"auto\" mode, so it\n * can never overwrite a model-authored checkpoint for the same session.\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execFileSync } from \"node:child_process\";\nimport { encodeDir } from \"../cli/utils.js\";\n\n/** How many recent user prompts to carry. */\nconst MAX_PROMPTS = 6;\n/** Longest a single quoted prompt may be before it is elided. */\nconst MAX_PROMPT_CHARS = 220;\n/** Cap on listed working-tree entries, so a big refactor cannot flood TODO.md. */\nconst MAX_FILES = 20;\n\n// ---------------------------------------------------------------------------\n// Transcript\n// ---------------------------------------------------------------------------\n\n/**\n * Text of a JSONL message, or null when the entry carries none.\n * Mirrors the shape handled by slug-generator: content is either a plain\n * string (user turns) or an array of typed blocks (assistant turns).\n */\nfunction messageText(obj: Record<string, unknown>): string | null {\n const msg = obj.message as Record<string, unknown> | undefined;\n if (!msg) return null;\n\n const content = msg.content;\n if (typeof content === \"string\") return content || null;\n\n if (Array.isArray(content)) {\n const texts: string[] = [];\n for (const block of content) {\n const b = block as Record<string, unknown>;\n if (b.type === \"text\" && typeof b.text === \"string\") texts.push(b.text);\n }\n return texts.join(\" \") || null;\n }\n\n return null;\n}\n\n/**\n * Strip injected machinery from a user turn, leaving what the user typed.\n *\n * Hooks do not get their own turn — `UserPromptSubmit` output is folded into\n * the same user message as the prompt itself. So this cannot be a predicate\n * that accepts or rejects whole messages: doing that discarded every prompt in\n * a session where a reminder happened to sort first, which is every session\n * here. The markup has to be cut out and the remainder kept.\n */\nfunction stripInjected(text: string): string {\n return text\n .replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/g, \"\")\n .replace(/<local-command-[a-z]*>[\\s\\S]*?<\\/local-command-[a-z]*>/g, \"\")\n .replace(/<command-(name|message|args)>[\\s\\S]*?<\\/command-\\1>/g, \"\")\n .replace(/<persisted-output>[\\s\\S]*?<\\/persisted-output>/g, \"\")\n .trim();\n}\n\n/**\n * True for a turn that is entirely machinery — no user intent to recover.\n */\nfunction isMachinery(text: string): boolean {\n const t = text.trimStart();\n return (\n t.startsWith(\"Caveat:\") ||\n t.startsWith(\"[Request interrupted\") ||\n t.startsWith(\"This session is being continued from a previous\")\n );\n}\n\n/**\n * Every transcript file belonging to a session, oldest part first.\n *\n * A session's transcript is not one file. `load-project-context` archives all\n * but the newest `.jsonl` into `sessions/`, after which Claude Code opens a\n * fresh file under the same UUID — so the same session routinely has a large\n * archived half and a small live one. Reading only the newest file yields just\n * the current turn, which is how the first cut of this digest reported a\n * six-turn session as a single prompt.\n *\n * With no session id, falls back to the newest file in the project directory.\n */\n/**\n * Locate the Claude Code transcript directory for a project.\n *\n * The registry records an `encoded_dir`, but it records whatever was true when\n * the project was added and is not updated when the project moves. Audited on\n * 2026-08-01: of 114 active projects only 19 had an `encoded_dir` containing\n * any transcript at all — 20 named a directory that no longer exists and 75\n * named one holding no `.jsonl` files. AIBroker's still pointed at a path last\n * used in March, which silently disabled checkpoint capture for that project:\n * every lookup returned no transcript, so every digest came out empty and every\n * handover degraded to \"see the latest session note\".\n *\n * So the stored value is treated as a hint, not an answer. If it yields no\n * transcripts, the directory is re-derived from the project's current root\n * path — which is maintained — and that is used instead. This self-heals a\n * moved project on the next capture without a migration.\n */\nexport function resolveTranscriptDir(project: {\n encoded_dir?: string | null;\n root_path: string;\n}): string {\n const base = join(homedir(), \".claude\", \"projects\");\n\n const recorded = project.encoded_dir ? join(base, project.encoded_dir) : null;\n if (recorded && hasTranscripts(recorded)) return recorded;\n\n const derived = join(base, encodeDir(project.root_path));\n if (hasTranscripts(derived)) return derived;\n\n // Neither has anything. Return the recorded one so behaviour and any error\n // message still reference what the registry actually claims.\n return recorded ?? derived;\n}\n\nfunction hasTranscripts(dir: string): boolean {\n if (!existsSync(dir)) return false;\n try {\n if (readdirSync(dir).some((e) => e.endsWith(\".jsonl\"))) return true;\n const archived = join(dir, \"sessions\");\n return existsSync(archived) && readdirSync(archived).some((e) => e.endsWith(\".jsonl\"));\n } catch {\n return false;\n }\n}\n\nexport function findTranscripts(\n projectDir: string,\n sessionId?: string\n): string[] {\n if (!existsSync(projectDir)) return [];\n\n if (sessionId) {\n const archived = join(projectDir, \"sessions\", `${sessionId}.jsonl`);\n const live = join(projectDir, `${sessionId}.jsonl`);\n const parts = [archived, live].filter((p) => existsSync(p));\n if (parts.length > 0) return parts;\n }\n\n let newest: { path: string; mtime: number } | null = null;\n let entries: string[];\n try {\n entries = readdirSync(projectDir);\n } catch {\n return [];\n }\n\n for (const entry of entries) {\n if (!entry.endsWith(\".jsonl\")) continue;\n const full = join(projectDir, entry);\n try {\n const { mtimeMs } = statSync(full);\n if (!newest || mtimeMs > newest.mtime) newest = { path: full, mtime: mtimeMs };\n } catch {\n // Unreadable — skip.\n }\n }\n\n return newest ? [newest.path] : [];\n}\n\n/**\n * The last few things the user actually asked for, oldest first.\n *\n * Claude Code writes a dedicated `last-prompt` entry per turn, holding the\n * prompt already separated from the hook output that shares its user message.\n * That is used when present; parsing user messages is the fallback for\n * transcripts written before those entries existed.\n */\nexport function recentPrompts(\n transcriptPaths: string[],\n limit = MAX_PROMPTS\n): string[] {\n const fromEntries: string[] = [];\n const fromMessages: string[] = [];\n\n const take = (text: string, into: string[]): void => {\n const cleaned = stripInjected(text);\n if (!cleaned || isMachinery(cleaned)) return;\n into.push(\n cleaned.length > MAX_PROMPT_CHARS\n ? cleaned.slice(0, MAX_PROMPT_CHARS).trimEnd() + \" …\"\n : cleaned\n );\n };\n\n for (const path of transcriptPaths) {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n continue;\n }\n\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue;\n let obj: Record<string, unknown>;\n try {\n obj = JSON.parse(line) as Record<string, unknown>;\n } catch {\n continue;\n }\n\n if (obj.type === \"last-prompt\") {\n if (typeof obj.lastPrompt === \"string\") take(obj.lastPrompt, fromEntries);\n continue;\n }\n\n if (obj.type === \"user\") {\n const text = messageText(obj);\n if (text) take(text, fromMessages);\n }\n }\n }\n\n const source = fromEntries.length > 0 ? fromEntries : fromMessages;\n\n // Consecutive duplicates are common: a prompt is recorded both when it is\n // submitted and again when the turn it belongs to is finalised.\n const deduped = source.filter((p, i) => i === 0 || p !== source[i - 1]);\n return deduped.slice(-limit);\n}\n\n// ---------------------------------------------------------------------------\n// Working tree\n// ---------------------------------------------------------------------------\n\nexport interface WorkingTree {\n branch: string | null;\n head: string | null;\n /** Porcelain entries, capped. */\n changes: string[];\n /** How many entries were dropped by the cap. */\n overflow: number;\n}\n\nexport function readWorkingTree(cwd: string): WorkingTree | null {\n const git = (args: string[]): string | null => {\n try {\n return execFileSync(\"git\", args, {\n cwd,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n return null;\n }\n };\n\n const branch = git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n if (branch === null) return null; // Not a git repo — nothing to report.\n\n const status = git([\"status\", \"--porcelain\"]) ?? \"\";\n const all = status.split(\"\\n\").filter((l) => l.trim().length > 0);\n\n return {\n branch,\n head: git([\"log\", \"-1\", \"--pretty=%h %s\"]),\n changes: all.slice(0, MAX_FILES),\n overflow: Math.max(0, all.length - MAX_FILES),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Digest\n// ---------------------------------------------------------------------------\n\nexport interface DigestInput {\n cwd: string;\n /** Every transcript file for this session, oldest part first. */\n transcriptPaths?: string[];\n timestamp?: string;\n}\n\n/**\n * Build the auto-checkpoint body, or \"\" when there is nothing worth recording.\n *\n * REQUIRING A PROMPT IS THE POINT\n * -------------------------------\n * A checkpoint that replaces an earlier one had better be worth more than what\n * it displaced. Until the user has said something, this session has produced no\n * state — and an uncommitted working tree is not this session's doing; it was\n * most likely already there when the session opened.\n *\n * Without this guard, opening a session in a dirty repo and touching one tool\n * is enough to overwrite the previous session's rich handover with \"clean tree,\n * nothing asked\". The handover survives in the session note either way, but\n * `## Continue` is what the next session is shown, so what sits there matters.\n */\n/** Cap on `@n=` file-declaration lines, so the whole message stays ≤25 lines. */\nconst MAX_AT_LINES = 17;\n\n/** Flatten a prompt (or any field value) to a single trimmed line. */\nfunction flattenField(text: string): string {\n return text.replace(/\\s*\\n\\s*/g, \" \").trim();\n}\n\n/**\n * Strip a git-porcelain status prefix off a `changes` entry, leaving the path.\n *\n * Porcelain lines are a 1-2 char status field, a space, then the path (e.g.\n * `\"M src/foo.ts\"`, `\" M src/foo.ts\"`, `\"?? x.ts\"`). The stripped result falls\n * back to the trimmed original whenever the pattern does not match, so a\n * surprising entry still produces a usable `@n=` line instead of an empty one.\n */\nfunction pathOf(entry: string): string {\n const stripped = entry.replace(/^\\s*\\S{1,2}\\s+/, \"\").trim();\n return stripped || entry.trim();\n}\n\n/**\n * Build the auto-checkpoint body as a single AG2 (Agentish v2) message of kind\n * `T` (task/handover). Pure function of its inputs so it can be unit-tested\n * without touching the filesystem or git.\n *\n * Field layout — see the AG2 validator for the authoritative rules:\n * i = ckpt-<sanitised timestamp>\n * g = the most recent prompt (goal)\n * d = the earlier prompts, joined by \" | \" (\"?\" when there are none)\n * t = \"?\" always — a mechanical handover runs no tests\n * @n = one declaration per changed file, capped at MAX_AT_LINES so the\n * whole message stays within AG2's 25-line limit\n * z = a note summarising the working tree, capped at 200 chars\n */\nexport function ag2Checkpoint(\n prompts: string[],\n tree: WorkingTree | null,\n ts: string\n): string {\n const id = `ckpt-${ts.replace(/[^A-Za-z0-9._-]/g, \"-\")}`;\n const g = flattenField(prompts[0]);\n const d =\n prompts.length > 1 ? prompts.slice(1).map(flattenField).join(\" | \") : \"?\";\n\n const lines: string[] = [\"T\", `i=${id}`, `g=${g}`, `d=${d}`, \"t=?\"];\n\n let extra = 0;\n if (tree) {\n const shown = tree.changes.slice(0, MAX_AT_LINES);\n extra = Math.max(0, tree.changes.length - MAX_AT_LINES) + tree.overflow;\n shown.forEach((entry, i) => lines.push(`@${i + 1}=${pathOf(entry)}`));\n }\n\n let z: string;\n if (!tree) {\n z = \"auto ckpt (no model)\";\n } else {\n const dirty = tree.changes.length;\n const head = tree.head ?? \"nohead\";\n z = `auto ckpt (no model); ${tree.branch}@${head}; ${dirty} dirty`;\n if (extra > 0) z += `; +${extra} paths`;\n z += \"; git status for detail\";\n }\n lines.push(`z=${z.length > 200 ? z.slice(0, 200) : z}`);\n\n return lines.join(\"\\n\");\n}\n\nexport function buildAutosaveBody(input: DigestInput): string {\n const ts = input.timestamp ?? new Date().toISOString();\n const prompts = recentPrompts(input.transcriptPaths ?? []);\n if (prompts.length === 0) return \"\";\n\n const tree = readWorkingTree(input.cwd);\n return ag2Checkpoint(prompts, tree, ts);\n}\n","/**\n * Session handover command — writes a ## Continue section to the project's\n * TODO.md. Called from the session-stop and pre-compact hooks.\n *\n * PRESERVATION\n * ------------\n * This runs on every clean exit and before every compaction, unattended. It\n * used to regenerate a generic four-line block and strip whatever ## Continue\n * was already there — which meant a model-authored checkpoint written seconds\n * earlier by `pai pause` was destroyed by /exit.\n *\n * It now writes in \"auto\" mode: an authored checkpoint describing the *same*\n * session is left alone. One from an earlier session is stale and gets\n * replaced, so TODO.md never points at the wrong session.\n *\n * Still exits 0 on every path — this must never interrupt Claude Code.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { SessionRow, ProjectRow } from \"./types.js\";\nimport { applyContinue } from \"../../../session/checkpoint-block.js\";\nimport {\n resolveSessionLine,\n resolveNotePath,\n resolveProjectByCwd,\n} from \"./pause.js\";\nimport {\n findTranscripts,\n buildAutosaveBody,\n resolveTranscriptDir,\n} from \"../../../session/autosave.js\";\n\n/**\n * Reconstruct a resumable digest for a session that is stopping.\n *\n * Best-effort by construction: this runs from a hook on every exit, so a failure\n * to read the transcript must degrade to an empty body — which the checkpoint\n * writer now treats as \"do not overwrite what is already there\" — rather than\n * throw and interrupt Claude Code.\n */\nfunction buildStopBody(project: ProjectRow, sessionId?: string): string {\n try {\n const transcriptPaths = findTranscripts(resolveTranscriptDir(project), sessionId);\n return buildAutosaveBody({ cwd: process.cwd(), transcriptPaths });\n } catch {\n return \"\";\n }\n}\n\nexport function cmdHandover(\n db: Database,\n projectSlug: string | undefined,\n numberOrLatest: string | undefined,\n sessionId?: string\n): void {\n // ---- 1. Resolve project ----\n let project: ProjectRow | undefined;\n\n if (projectSlug) {\n project = db\n .prepare(\n \"SELECT id, slug, display_name, root_path, encoded_dir FROM projects WHERE slug = ?\"\n )\n .get(projectSlug) as ProjectRow | undefined;\n if (!project) return;\n } else {\n // Same resolver as `pai pause` — including the realpath fallback, so a\n // session started via a symlinked path prefix resolves to the same project\n // rather than silently finding nothing.\n const row = resolveProjectByCwd(db, process.cwd());\n if (!row) return;\n project = row;\n }\n\n // ---- 2. Resolve session ----\n let session: SessionRow | undefined;\n const nol = numberOrLatest ?? \"latest\";\n\n if (nol === \"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(nol, 10);\n if (!isNaN(num)) {\n session = db\n .prepare(\"SELECT * FROM sessions WHERE project_id = ? AND number = ?\")\n .get(project!.id, num) as SessionRow | undefined;\n }\n }\n\n // ---- 3. Write, preserving any authored checkpoint for this session ----\n //\n // The session line must be derived exactly as `pai pause` derives it — it is\n // the fallback preservation key, and deriving it independently here is what\n // let this command clobber a checkpoint written seconds earlier.\n //\n // The line alone is not enough. By the time this runs, `session-stop.sh` has\n // already executed `session slug --apply` and `session cleanup --execute`,\n // both of which rewrite the session note filename the line is derived from.\n // The UUID is passed through precisely because it survives that.\n // A body, not just a pointer. This used to write metadata only, on the\n // assumption that the session note holds the real content — but the note is\n // written by a different path that can fail or never run at all, and when it\n // does the checkpoint names a file that does not exist. Reconstructing the\n // same digest the autosave writes (recent prompts + working tree) costs one\n // transcript read and means a stop-hook handover always carries something\n // resumable on its own.\n applyContinue({\n rootPath: project!.root_path,\n authored: \"auto\",\n sessionLine: resolveSessionLine(project!, session, resolveNotePath(project!)),\n sessionId,\n cwd: process.cwd(),\n body: buildStopBody(project!, sessionId),\n });\n}\n","/**\n * context-fill.ts — \"how full is this session's context window, right now?\"\n *\n * No hook payload carries this number directly (verified: PreCompact,\n * UserPromptSubmit and PostToolUse stdin never include `context_window`).\n * Two sources can reconstruct it, in order of preference:\n *\n * 1. STATUSLINE STATE FILE — statusline-command.sh receives the exact\n * figure from Claude Code (`.context_window.used_percentage` /\n * `.context_window.context_window_size`) on every render and persists\n * it to `${TMPDIR}/pai-context-<session_id>.json`. This is authoritative\n * but depends on the status line having rendered recently — a session\n * whose terminal isn't drawing a status line (headless, backgrounded)\n * leaves this file missing or stale.\n *\n * 2. TRANSCRIPT USAGE — every `message.usage` entry in the session's own\n * .jsonl transcript already reports the token accounting for that one\n * API call: `input_tokens + cache_read_input_tokens +\n * cache_creation_input_tokens` on the MOST RECENT such entry *is* the\n * current context fill, because each request resends the full context.\n * This is why it must be the last entry's fields summed, never a sum\n * across entries — summing across entries is a running token-spend\n * total, not a fill reading (see calculateSessionTokens, a different\n * metric answering a different question).\n *\n * When neither source is available, the answer is UNKNOWN, not zero. A zero\n * reads as \"plenty of room\" and would silently suppress every downstream\n * decision that depends on this number (the compaction handover chief among\n * them) for the entire session.\n */\n\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n DEFAULT_CONTEXT_WINDOW,\n contextWindowFromModelId,\n stripModelVariant,\n} from \"../../../utils/model-window.js\";\nimport { readWorkersSection } from \"../../../workers/config.js\";\n\nexport { DEFAULT_CONTEXT_WINDOW };\n\n/** How many trailing lines of the transcript to scan for a usage entry.\n * A single turn is rarely more than a handful of JSONL lines (assistant\n * text + tool_use/tool_result pairs), so 40 comfortably covers one turn\n * even a busy one, without reading the whole file on every check. */\nconst TRANSCRIPT_TAIL_LINES = 40;\n\n/** A statusline reading older than this is treated as not there at all —\n * a stale number is worse than none, because it looks confident. */\nconst STATUSLINE_STALE_MS = 5 * 60 * 1000; // 5 minutes\n\nexport type ContextFillSource = \"statusline\" | \"transcript\" | \"unknown\";\n\nexport interface ContextFillReading {\n status: \"ok\" | \"unknown\";\n /** Tokens currently occupying the context window, or null when unknown. */\n usedTokens: number | null;\n windowSize: number;\n /** usedTokens / windowSize, or null when unknown. Not clamped to [0,1] —\n * callers that need a clamped display value do that themselves so the\n * raw reading (which can legitimately exceed 1 for a moment) is never\n * silently rewritten here. */\n fraction: number | null;\n source: ContextFillSource;\n}\n\nfunction unknownReading(windowSize: number): ContextFillReading {\n return { status: \"unknown\", usedTokens: null, windowSize, fraction: null, source: \"unknown\" };\n}\n\n// ---------------------------------------------------------------------------\n// Source 1 — statusline state file\n// ---------------------------------------------------------------------------\n\nexport function statuslineStateFilePath(sessionId: string): string {\n return join(tmpdir(), `pai-context-${sessionId}.json`);\n}\n\ninterface StatuslineState {\n used_percentage?: number;\n context_window_size?: number;\n session_id?: string;\n timestamp?: number;\n}\n\n/**\n * Read the state file statusline-command.sh writes on every render.\n * Returns null when the file is missing, unparsable, or older than\n * STATUSLINE_STALE_MS — all three mean \"not a source right now\".\n */\nexport function readStatuslineFill(sessionId: string, now = Date.now()): ContextFillReading | null {\n if (!sessionId) return null;\n const path = statuslineStateFilePath(sessionId);\n if (!existsSync(path)) return null;\n\n let raw: StatuslineState;\n try {\n raw = JSON.parse(readFileSync(path, \"utf-8\"));\n } catch {\n return null;\n }\n\n if (typeof raw.timestamp !== \"number\" || now - raw.timestamp > STATUSLINE_STALE_MS) {\n return null; // stale — fall through to the transcript source\n }\n if (typeof raw.used_percentage !== \"number\" || typeof raw.context_window_size !== \"number\") {\n return null;\n }\n\n const windowSize = raw.context_window_size;\n const usedTokens = Math.round((raw.used_percentage / 100) * windowSize);\n return {\n status: \"ok\",\n usedTokens,\n windowSize,\n fraction: raw.used_percentage / 100,\n source: \"statusline\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// Source 2 — transcript usage (fallback)\n// ---------------------------------------------------------------------------\n\ninterface UsageEntry {\n input_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n}\n\n/**\n * Sum the three context-carrying fields of ONE usage object — never across\n * usage objects. A single `message.usage` already reports what that one API\n * call sent as context; adding another entry's numbers to it produces a\n * cumulative spend figure, not a fill reading, and can exceed the window\n * size many times over on a long session (the exact defect this helper\n * exists to not repeat — see the PreCompact header fix in the same change).\n */\nfunction usageTotal(usage: UsageEntry): number {\n return (\n (usage.input_tokens || 0) +\n (usage.cache_read_input_tokens || 0) +\n (usage.cache_creation_input_tokens || 0)\n );\n}\n\n/**\n * Derive context fill from the tail of a .jsonl transcript: the most recent\n * `message.usage` entry, read from the end backwards so a trailing line with\n * no usage field (a plain text turn, a tool_result) doesn't hide one just\n * before it.\n *\n * The window size, when the caller has none, is derived from the transcript's\n * own last assistant model id (\"glm-5.3[1m]\" → 1,000,000) before falling back\n * to the assumed default — a session whose model declares its window should\n * not be measured against a guess.\n */\nexport function contextFillFromTranscript(\n transcriptPath: string,\n windowSize?: number\n): ContextFillReading {\n if (!transcriptPath || !existsSync(transcriptPath)) {\n return unknownReading(windowSize ?? DEFAULT_CONTEXT_WINDOW);\n }\n\n let raw: string;\n try {\n raw = readFileSync(transcriptPath, \"utf-8\");\n } catch {\n return unknownReading(windowSize ?? DEFAULT_CONTEXT_WINDOW);\n }\n\n const lines = raw.trim().split(\"\\n\").filter((l) => l.trim());\n const tail = lines.slice(-TRANSCRIPT_TAIL_LINES);\n\n // one pass from the end: the first usage entry is the fill reading, and the\n // last assistant model seen (before or at that point) declares the window\n let lastModel: string | null = null;\n for (let i = tail.length - 1; i >= 0; i--) {\n let entry: { type?: string; message?: { usage?: UsageEntry; model?: unknown } };\n try {\n entry = JSON.parse(tail[i]);\n } catch {\n continue;\n }\n if (entry?.type === \"assistant\" && lastModel === null) {\n const model = entry.message?.model;\n if (typeof model === \"string\" && model !== \"\") lastModel = model;\n }\n const usage = entry?.message?.usage;\n if (usage && typeof usage === \"object\") {\n const window = windowSize ?? contextWindowFromModelId(lastModel) ?? DEFAULT_CONTEXT_WINDOW;\n const usedTokens = usageTotal(usage);\n return {\n status: \"ok\",\n usedTokens,\n windowSize: window,\n fraction: usedTokens / window,\n source: \"transcript\",\n };\n }\n }\n\n const window = windowSize ?? contextWindowFromModelId(lastModel) ?? DEFAULT_CONTEXT_WINDOW;\n return unknownReading(window);\n}\n\n// ---------------------------------------------------------------------------\n// Precedence — statusline (fresh) > transcript > unknown\n// ---------------------------------------------------------------------------\n\nexport function getContextFill(\n input: { sessionId?: string; transcriptPath?: string; windowSize?: number },\n now = Date.now()\n): ContextFillReading {\n if (input.sessionId) {\n const fromStatusline = readStatuslineFill(input.sessionId, now);\n if (fromStatusline) return fromStatusline;\n }\n\n if (input.transcriptPath) {\n // windowSize passes through only when the caller knows it; otherwise the\n // transcript's own model id is asked first (see contextFillFromTranscript)\n return contextFillFromTranscript(input.transcriptPath, input.windowSize);\n }\n\n return unknownReading(input.windowSize ?? DEFAULT_CONTEXT_WINDOW);\n}\n\n// ---------------------------------------------------------------------------\n// Display helper — clamp-and-flag rather than ever print an absurd number\n// ---------------------------------------------------------------------------\n\nexport interface FillDisplay {\n /** e.g. \"63k\" or \"unknown\". Never a number larger than the window. */\n text: string;\n /** True when the raw reading exceeded the window size and had to be\n * clamped — that reading was a bug, not a fill, and callers may want to\n * log it even though the displayed text is already safe. */\n flagged: boolean;\n}\n\nexport function formatContextFill(reading: ContextFillReading): FillDisplay {\n if (reading.status !== \"ok\" || reading.usedTokens === null) {\n return { text: \"unknown\", flagged: false };\n }\n\n let used = reading.usedTokens;\n let flagged = false;\n if (used > reading.windowSize) {\n flagged = true;\n used = reading.windowSize;\n }\n\n const text = used > 1000 ? `${Math.round(used / 1000)}k` : String(used);\n return { text: flagged ? `${text}+ (clamped — reading exceeded window)` : text, flagged };\n}\n\n// ---------------------------------------------------------------------------\n// Threshold-triggered handover — WHEN to warm up / refresh the model-written\n// summary ahead of a compaction.\n// ---------------------------------------------------------------------------\n//\n// THE CONFIGURED VALUE DOES NOT PREDICT THE TRIGGER. CLAUDE_AUTOCOMPACT_\n// PCT_OVERRIDE was 80 throughout, on this machine, across BOTH of the\n// following regimes — the same configured number, two different truths:\n//\n// 2026-08-16 .. 2026-09-10 preTokens ~ 993,096 – 1,002,500 (~100% of a 1M window)\n// 2026-09-12 .. 2026-09-15 preTokens ~ 782,981 – 791,995 (~78-79% of a 1M window)\n//\n// A threshold derived only from the configured override was wrong for the\n// second regime and would be wrong again the next time it moves — the\n// config is not ground truth, it is what Claude Code claims it will honor.\n//\n// GROUND TRUTH IS ON DISK ALREADY: every real compaction writes a\n// `compact_boundary` system event to the transcript with\n// `compactMetadata.preTokens` — the platform's own count of context tokens\n// immediately before IT compacted. `measureCompactionTrigger` scans a\n// project's own transcripts for these and takes the MINIMUM of the most\n// recent three (minimum, not mean: warm-up must fire before the earliest\n// plausible boundary, not the average one). A project that has compacted\n// before learns its own trigger and needs no code change when the regime\n// moves again — the same derivation produced 998,508 pre-09-12 and ~784k\n// after, from one unchanged formula.\n//\n// The configured chain — env override, then a default — is the fallback,\n// used ONLY when a project has no compaction history yet:\n//\n// effectiveTrigger = measured ?? (windowSize * (override ?? 80) / 100)\n//\n// DEFAULT IS 80, NOT 100, WHEN NOTHING IS KNOWN. 100 was considered — it\n// matches the FIRST regime above — and rejected: the costs are asymmetric.\n// Warming up too early wastes one summary; cheap, invisible. Warming up too\n// late produces exactly the degraded successor session this feature exists\n// to prevent. 80 is the conservative default until a project has its own\n// measured history to correct it.\n//\n// Margins below the effective trigger are absolute tokens, sized against the\n// largest single-turn context jump measured: 62,383 tokens (517,952 →\n// 580,335, about 27 seconds). warmup sits 100k below the trigger —\n// comfortably more than that jump, so a session cannot leap clean over\n// warmup straight into a compaction in one turn; refresh at 40k below\n// catches a session that sat above warmup for a while; fireNow at 15k below\n// is \"no time left for a clean crossing\" and fires immediately rather than\n// waiting for one.\nexport const DEFAULT_AUTOCOMPACT_PCT = 80;\n\n/** How many of the most recent compact_boundary events to consider, and to\n * take the minimum of. */\nconst MEASURED_TRIGGER_SAMPLE_SIZE = 3;\n\nexport const THRESHOLD_MARGIN_TOKENS = {\n warmup: 100_000,\n refresh: 40_000,\n immediate: 15_000,\n} as const;\n\n// ---------------------------------------------------------------------------\n// Measured trigger — scan a project's own transcript history for the\n// platform's own compact_boundary events, ground truth over configuration.\n// ---------------------------------------------------------------------------\n//\n// Deliberately re-implements the tiny bit of path encoding it needs (below)\n// rather than importing project-utils/paths.ts's encodePath: that module\n// chain ends in pai-paths.ts, which calls process.exit(1) if PAI_DIR does\n// not resolve to an existing directory. context-fill.ts is specifically the\n// module a hook falls back on when its environment is unreliable — pulling\n// in a dependency that can kill the process on import would defeat that.\n// Claude Code's transcript directory is a fixed, home-relative convention\n// (~/.claude/projects/<encoded-cwd>/), not something PAI_DIR governs, so\n// resolving it directly here is also just the more correct dependency, not\n// only the safer one.\n\n/** Real default — overridable per-call so tests never touch the user's\n * actual ~/.claude/projects/ (a live directory this very session writes to). */\nexport const CLAUDE_PROJECTS_DIR = join(homedir(), \".claude\", \"projects\");\n\nfunction encodeProjectPath(cwd: string): string {\n return cwd.replace(/[/\\s.-]/g, \"-\");\n}\n\nexport interface CompactBoundarySample {\n preTokens: number;\n timestampMs: number;\n /** ISO string, kept alongside timestampMs so callers can print it. */\n timestamp: string;\n /** The event's own uuid when present — the dedup key. Claude Code's\n * `sessions/` archive directory mirrors the live project transcript, so\n * the SAME compact_boundary event can legitimately appear in two files;\n * without deduping by identity, \"most recent three\" can silently become\n * three copies of one event, which is a stale reading wearing a\n * plausible-looking sample size. */\n uuid?: string;\n}\n\n/** Every `.jsonl` transcript belonging to a project — top-level (the live\n * file) and `sessions/` (Claude Code's archive, which mirrors it). No file\n * is excluded and no ordering is applied here: ordering by EVENT\n * timestamp, not by file mtime, is the whole point (see\n * readCompactBoundarySamples) — a file's mtime does not reliably track\n * which events inside it are recent, and pre-filtering by mtime is exactly\n * what caused this function to return a stale, pre-regime-change trigger\n * on real data. */\nfunction listProjectTranscripts(cwd: string, projectsDir: string): string[] {\n const projectDir = join(projectsDir, encodeProjectPath(cwd));\n if (!existsSync(projectDir)) return [];\n\n const paths: string[] = [];\n const collect = (dir: string): void => {\n if (!existsSync(dir)) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.endsWith(\".jsonl\")) paths.push(join(dir, entry));\n }\n };\n collect(projectDir);\n collect(join(projectDir, \"sessions\"));\n return paths;\n}\n\n/** Placeholder model id the platform writes on synthetic assistant turns. */\nconst SYNTHETIC_MODEL = \"<synthetic>\";\n\n/**\n * Which family of model wrote a transcript: a stable key derived from the id\n * itself, never from an Anthropic allowlist. \"claude\" is the platform's own;\n * any other id reduces to its base form before any bracketed variant suffix\n * (\"glm-5.3[1m]\" → \"glm-5.3\"), so the same model family always produces the\n * same key; \"unknown\" is no usable model field at all. Whether a family is\n * foreign is decided by comparison (see isForeignModelFamily), not by the\n * key — keying \"foreign\" here is what made non-Anthropic primary models lose\n * their own compaction history.\n */\nexport type TranscriptModelFamily = \"claude\" | \"unknown\" | (string & {});\n\nexport function modelFamily(model: string | null | undefined): TranscriptModelFamily {\n if (typeof model !== \"string\" || model === \"\" || model === SYNTHETIC_MODEL) return \"unknown\";\n const base = stripModelVariant(model);\n return base.startsWith(\"claude-\") ? \"claude\" : base;\n}\n\n/**\n * Is a model id foreign to this machine's sessions? A model is native when it\n * is a Claude model or one the worker registry configures (any provider's\n * default or fast alias — the models this stack actually runs on). Anything\n * else, with a readable model id, is foreign. Doubt (no id, unreadable\n * registry) reads as native: the callers that skip work on \"foreign\" must not\n * skip it on doubt.\n */\nexport function isForeignModelFamily(\n family: TranscriptModelFamily,\n nativeFamilies?: Set<string>\n): boolean {\n if (family === \"unknown\" || family === \"claude\") return false;\n return !(nativeFamilies ?? nativeModelFamilies()).has(family);\n}\n\n/** Families of every model id the worker registry configures. Never throws:\n * an unreadable or missing registry means \"nothing configured\", which leaves\n * every non-Claude family foreign — the historical behaviour. */\nexport function nativeModelFamilies(configPath?: string): Set<string> {\n const families = new Set<string>();\n try {\n for (const p of Object.values(readWorkersSection(configPath).workers.providers)) {\n families.add(modelFamily(p.models.default));\n if (p.models.fast) families.add(modelFamily(p.models.fast));\n }\n } catch {\n return families;\n }\n return families;\n}\n\n/**\n * The family of the LAST assistant model in a transcript — scanned from the\n * end so a long transcript costs one read and a few lines of parsing.\n * Unreadable or model-less transcripts are \"unknown\", never foreign:\n * the callers that skip work on foreign models must not skip it on doubt.\n * Pair with isForeignModelFamily to judge the key.\n */\nexport function transcriptModelFamily(path: string): TranscriptModelFamily {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf-8\");\n } catch {\n return \"unknown\";\n }\n const lines = raw.split(\"\\n\");\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i];\n if (!line.includes('\"model\"')) continue;\n try {\n const entry = JSON.parse(line) as { type?: string; message?: { model?: unknown } };\n if (entry.type !== \"assistant\") continue;\n const model = entry.message?.model;\n if (typeof model === \"string\" && model !== SYNTHETIC_MODEL) return modelFamily(model);\n } catch {\n continue;\n }\n }\n return \"unknown\";\n}\n\n/**\n * Every DISTINCT compact_boundary sample found across ALL of a project's\n * transcripts (live + archived), newest first by the event's OWN timestamp\n * — never by which file it came from or that file's mtime. Deduplicated by\n * the event's uuid (falling back to a timestamp+preTokens key for the rare\n * line with no uuid) so an event mirrored into `sessions/` is counted once.\n */\nfunction readCompactBoundarySamples(\n cwd: string,\n projectsDir: string,\n nativeFamilies: Set<string>\n): CompactBoundarySample[] {\n const byKey = new Map<string, CompactBoundarySample>();\n\n for (const path of listProjectTranscripts(cwd, projectsDir)) {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf-8\");\n } catch {\n continue;\n }\n\n // The model governing each sample: the most recent assistant\n // `message.model` seen in this file before the compact_boundary. A\n // transcript written by a model foreign to this machine (a headless\n // worker on another provider, with a different context window) compacts\n // at a different size and must not shape THIS project's trigger — two\n // such workers compacting at ~151k pulled a real project's trigger from\n // ~784k to ~151k. Native is Claude plus every model the registry\n // configures, so a non-Anthropic PRIMARY model keeps its own history.\n // Samples with no model seen yet are kept: older transcripts may lack\n // the field.\n let lastModel: string | null = null;\n let foreignDiscards = 0;\n\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue;\n let entry: {\n type?: string;\n subtype?: string;\n timestamp?: string;\n uuid?: string;\n compactMetadata?: { preTokens?: number };\n message?: { model?: unknown };\n };\n try {\n entry = JSON.parse(line);\n } catch {\n continue;\n }\n if (entry.type === \"assistant\") {\n const model = entry.message?.model;\n if (typeof model === \"string\" && model !== SYNTHETIC_MODEL) lastModel = model;\n continue;\n }\n if (entry.type !== \"system\" || entry.subtype !== \"compact_boundary\") continue;\n const preTokens = entry.compactMetadata?.preTokens;\n if (typeof preTokens !== \"number\" || !Number.isFinite(preTokens)) continue;\n const timestampMs = entry.timestamp ? Date.parse(entry.timestamp) : NaN;\n if (!Number.isFinite(timestampMs)) continue;\n if (isForeignModelFamily(modelFamily(lastModel), nativeFamilies)) {\n foreignDiscards++;\n continue;\n }\n\n const key = entry.uuid ?? `${timestampMs}:${preTokens}`;\n if (!byKey.has(key)) {\n byKey.set(key, { preTokens, timestampMs, timestamp: entry.timestamp!, uuid: entry.uuid });\n }\n }\n\n if (foreignDiscards > 0) {\n console.error(\n `[context-fill] ignored ${foreignDiscards} compaction sample(s) from a ` +\n `foreign-model transcript (model=${lastModel}): ${path}`\n );\n }\n }\n\n return [...byKey.values()].sort((a, b) => b.timestampMs - a.timestampMs);\n}\n\n/**\n * The most recent MEASURED_TRIGGER_SAMPLE_SIZE distinct compact_boundary\n * events for a project, newest first — exposed on its own (not just the\n * derived minimum) so the number `measureCompactionTrigger` returns is\n * checkable: print these and the timestamps prove which three events\n * produced it, rather than asking for trust.\n */\nexport function selectedCompactionSamples(\n cwd: string,\n projectsDir: string = CLAUDE_PROJECTS_DIR,\n configPath?: string\n): CompactBoundarySample[] {\n if (!cwd) return [];\n return readCompactBoundarySamples(cwd, projectsDir, nativeModelFamilies(configPath))\n .slice(0, MEASURED_TRIGGER_SAMPLE_SIZE);\n}\n\n/**\n * The measured compaction trigger for a project, or null when it has no\n * compaction history yet (a brand-new project, or one whose transcripts\n * this process cannot read). Minimum of the most recent\n * MEASURED_TRIGGER_SAMPLE_SIZE DISTINCT compact_boundary events, ordered by\n * the events' own timestamps across every transcript the project has\n * (live and archived) — see the module comment above for why minimum, not\n * mean, and readCompactBoundarySamples for why \"distinct\" and \"own\n * timestamp\" both matter (a file-mtime-ordered, non-deduplicated version of\n * this returned a stale pre-regime-change trigger on real project data).\n */\nexport function measureCompactionTrigger(\n cwd: string,\n projectsDir: string = CLAUDE_PROJECTS_DIR,\n configPath?: string\n): number | null {\n const samples = selectedCompactionSamples(cwd, projectsDir, configPath);\n if (samples.length === 0) return null;\n const trigger = Math.min(...samples.map((s) => s.preTokens));\n console.error(\n `[context-fill] measured trigger for ${cwd}: ${trigger} (minimum of ` +\n samples.map((s) => `${s.preTokens}@${s.timestamp}`).join(\", \") + \")\"\n );\n return trigger;\n}\n\n/**\n * Read CLAUDE_AUTOCOMPACT_PCT_OVERRIDE from the environment. Absent →\n * DEFAULT_AUTOCOMPACT_PCT. Present but not a finite number in (0, 100] →\n * also DEFAULT_AUTOCOMPACT_PCT, logged, so a typo in the override degrades\n * to the documented default instead of silently producing nonsense\n * thresholds (0, negative, or a fraction so large no session ever reaches\n * it).\n */\nexport function resolveAutocompactPct(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE;\n if (raw === undefined || raw === \"\") return DEFAULT_AUTOCOMPACT_PCT;\n\n const parsed = Number(raw);\n if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 100) {\n console.error(\n `[context-fill] CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=\"${raw}\" is not a usable percentage — ` +\n `falling back to the default ${DEFAULT_AUTOCOMPACT_PCT}.`\n );\n return DEFAULT_AUTOCOMPACT_PCT;\n }\n return parsed;\n}\n\n/**\n * The two thresholds that actually fire a handover job. \"immediate\" is not a\n * third job — see `isImmediate` below — it only changes how urgently a\n * crossing of these two is treated.\n */\nexport type ThresholdName = \"warmup\" | \"refresh\";\n\n/** Which basis actually produced effectiveTriggerTokens — reported so the\n * number is checkable rather than trusted. */\nexport type TriggerSource = \"measured\" | \"configured\" | \"measured-clamped\";\n\nexport interface ContextFillThresholds {\n warmupTokens: number;\n refreshTokens: number;\n immediateTokens: number;\n /** The derived compaction trigger these were measured back from —\n * min(measured, configured) when a measured value exists. */\n effectiveTriggerTokens: number;\n /** The raw measured value from this project's own compact_boundary\n * history, before any clamping — null when the project has no history.\n * Kept alongside effectiveTriggerTokens so a clamp is visible rather\n * than silent: a caller can see both what was measured and what was\n * actually used. */\n measuredTriggerTokens: number | null;\n /** The raw configured-chain value (env override or default, as a\n * fraction of the window) — always computed and reported, even when it\n * wasn't what ended up being used. */\n configuredTriggerTokens: number;\n /** The autocompact percentage from the configured chain. */\n autocompactPct: number;\n /** \"measured\": a measured value existed and was <= configured, so it was\n * used directly — the better estimate, since it reflects reality the\n * configured percentage cannot know.\n * \"measured-clamped\": a measured value existed but was HIGHER than\n * configured — it reflects a regime that may no longer apply (this\n * project's last compaction predates a since-changed trigger), so the\n * lower, safer configured value was used instead.\n * \"configured\": no measured value exists yet (no compaction history for\n * this project) — the configured chain is all there is. */\n triggerSource: TriggerSource;\n /** True when these were computed against a window size Claude Code itself\n * reported (the statusline source). False when the window is only an\n * assumed default (the transcript-fallback source never learns the real\n * window size) — callers should log this: a threshold silently computed\n * against the wrong basis is the same class of fault as printing a token\n * count larger than the window. */\n windowConfirmed: boolean;\n}\n\nexport interface ContextFillThresholdOpts {\n /** The session's project directory, used to look up its own compaction\n * history for the measured trigger. Omit when unknown — falls back to\n * the configured chain, same as a project with no history yet. */\n cwd?: string;\n /** Override the measured-trigger lookup instead of scanning transcripts —\n * primarily for tests. `null` forces the configured fallback even when\n * `cwd` is given; `undefined` (the default) does the real lookup. */\n measuredTrigger?: number | null;\n}\n\n/**\n * Derive warmup/refresh/immediate thresholds from a fill reading.\n *\n * effectiveTrigger = min(measured, configuredChainValue) when a measured\n * value exists — NOT the measured value outright. A project whose newest\n * compaction predates a regime change measures a stale-HIGH trigger: a real\n * case on this machine measured 998,267 for a project whose ACTUAL current\n * boundary (from a different project's fresher history, cross-checked\n * independently) is ~784,000 — using 998,267 directly would compute a\n * warm-up of 898,267, above the real boundary, so the handover would never\n * fire there. The measurement is honest; it is just old.\n *\n * The minimum is correct in both directions: the measured value is the\n * better estimate when it is LOWER than configured (it reflects reality the\n * configured percentage cannot know — see the module comment above, the\n * 100%-to-78% regime change this project itself lived through); it is\n * unsafe when it is HIGHER (it reflects a regime that no longer applies).\n * Taking the minimum costs nothing in the safe direction — one wasted\n * summary if the project's regime actually did move up — and prevents the\n * unsafe direction, where a stale-high measurement suppresses the handover\n * past the real boundary. That asymmetry is the same one the 80-not-100\n * default was chosen for.\n *\n * Margins below effectiveTrigger are clamped at 0 (and logged) for a window\n * small enough that a margin would otherwise go negative — a pathological\n * input should degrade to \"fire immediately\", never to a threshold below\n * zero.\n */\nexport function contextFillThresholds(\n reading: ContextFillReading,\n env: NodeJS.ProcessEnv = process.env,\n opts: ContextFillThresholdOpts = {}\n): ContextFillThresholds {\n const windowConfirmed = reading.source === \"statusline\";\n const autocompactPct = resolveAutocompactPct(env);\n const configuredTriggerTokens = Math.round(reading.windowSize * (autocompactPct / 100));\n\n const measuredTriggerTokens = opts.measuredTrigger !== undefined\n ? opts.measuredTrigger\n : opts.cwd\n ? measureCompactionTrigger(opts.cwd)\n : null;\n\n let effectiveTriggerTokens: number;\n let triggerSource: TriggerSource;\n\n if (measuredTriggerTokens === null) {\n effectiveTriggerTokens = configuredTriggerTokens;\n triggerSource = \"configured\";\n console.error(\n `[context-fill] trigger source: CONFIGURED — no compaction history for this project yet. ` +\n `measured=none, configured=${configuredTriggerTokens} (${autocompactPct}% of a ${reading.windowSize}-token ` +\n `window) -> using ${effectiveTriggerTokens}.`\n );\n } else if (measuredTriggerTokens <= configuredTriggerTokens) {\n effectiveTriggerTokens = measuredTriggerTokens;\n triggerSource = \"measured\";\n console.error(\n `[context-fill] trigger source: MEASURED — measured=${measuredTriggerTokens} ` +\n `(minimum of the most recent ${MEASURED_TRIGGER_SAMPLE_SIZE} compact_boundary events), ` +\n `configured=${configuredTriggerTokens} -> using ${effectiveTriggerTokens} (measured, ≤ configured).`\n );\n } else {\n effectiveTriggerTokens = configuredTriggerTokens;\n triggerSource = \"measured-clamped\";\n console.error(\n `[context-fill] trigger source: MEASURED-CLAMPED — measured=${measuredTriggerTokens} is HIGHER than ` +\n `configured=${configuredTriggerTokens} (a stale regime this project's history predates) -> ` +\n `using ${effectiveTriggerTokens} (configured, the safer bound).`\n );\n }\n\n const clamp = (name: string, value: number): number => {\n if (value >= 0) return value;\n console.error(\n `[context-fill] ${name} threshold went negative (${value}) for a ` +\n `${reading.windowSize}-token window — clamping to 0.`\n );\n return 0;\n };\n\n return {\n warmupTokens: clamp(\"warmup\", effectiveTriggerTokens - THRESHOLD_MARGIN_TOKENS.warmup),\n refreshTokens: clamp(\"refresh\", effectiveTriggerTokens - THRESHOLD_MARGIN_TOKENS.refresh),\n immediateTokens: clamp(\"immediate\", effectiveTriggerTokens - THRESHOLD_MARGIN_TOKENS.immediate),\n effectiveTriggerTokens,\n measuredTriggerTokens,\n configuredTriggerTokens,\n autocompactPct,\n triggerSource,\n windowConfirmed,\n };\n}\n\n/**\n * Which named thresholds `usedTokens` has newly crossed, given the ones that\n * have already fired this session. Ascending order (warmup before refresh).\n *\n * A single check can return more than one name — e.g. a session first\n * observed at 99% of its window crosses warmup and refresh in the same tick,\n * because there was no earlier clean crossing to catch it at. That is the\n * \"fire immediately\" case: the caller enqueues one handover and marks every\n * newly-crossed name fired, rather than waiting for a crossing that already\n * happened.\n */\nexport function crossedThresholds(\n usedTokens: number,\n thresholds: ContextFillThresholds,\n alreadyFired: ThresholdName[]\n): ThresholdName[] {\n const fired = new Set(alreadyFired);\n const ordered: Array<[ThresholdName, number]> = [\n [\"warmup\", thresholds.warmupTokens],\n [\"refresh\", thresholds.refreshTokens],\n ];\n return ordered.filter(([name, tokens]) => !fired.has(name) && usedTokens >= tokens).map(([name]) => name);\n}\n\n/**\n * True once a session is at or above the \"no time left for a clean crossing\"\n * floor (0.985 of the window). Purely informational for callers — it never\n * gates whether `crossedThresholds` fires (a poll-based check already fires\n * any unfired threshold the moment `usedTokens` reaches it, on whatever tick\n * observes it) — but it distinguishes \"this crossed on schedule\" from \"this\n * was first observed already almost out of room\", which is worth a different\n * log line and, for a caller that queues work, a higher priority.\n */\nexport function isImmediate(usedTokens: number, thresholds: ContextFillThresholds): boolean {\n return usedTokens >= thresholds.immediateTokens;\n}\n","/**\n * Worker-session detection.\n *\n * A disposable headless worker (a `claude -p` run started by an orchestrating\n * session, possibly against a different model provider with a different\n * context window) shares the project directory and the hook configuration\n * with the real session that spawned it. Left alone, the hooks treat it as a\n * session in its own right: they inject project context into it, create and\n * rename a numbered session note for it, autosave it, and enqueue a\n * model-written handover for it. Its compactions also land in the project's\n * transcript folder, where they are indistinguishable from a real session's\n * and drag the measured compaction trigger down (observed 2026-09-17: two\n * workers compacting at ~151k tokens pulled a project's trigger from ~784k\n * to ~151k, and the real session's handover fired at ~50k tokens).\n *\n * The launcher marks such sessions with `PAI_WORKER=1`. Every hook that does\n * per-session bookkeeping returns immediately when this predicate is true.\n * Deliberately NOT guarded: the security validator (a worker's shell\n * commands must still be checked) and observability capture.\n */\nexport function isWorkerSession(env: NodeJS.ProcessEnv = process.env): boolean {\n return env.PAI_WORKER === \"1\";\n}\n","/**\n * context-handover-trigger.ts — decide whether THIS check is the moment a\n * session should get its threshold-triggered pre-compaction handover, and\n * enqueue it with the daemon if so.\n *\n * Called from the same live hooks that already drive the AG2 rolling\n * autosave (UserPromptSubmit, PostToolUse) — see\n * cli/commands/session/autosave.ts. Reuses the context-fill reading and the\n * derived thresholds from hooks/ts/lib/context-fill.ts; this module only\n * adds the \"have we already fired this one\" bookkeeping and the enqueue.\n *\n * BUG FIXED (found in live use, session 77084e72-...): a threshold used to\n * be marked fired the moment the ENQUEUE call succeeded — recording intent,\n * not outcome. When the daemon worker then failed or was restarted mid-spawn\n * (observed: a daemon restart 90s after the enqueue), no handover cache was\n * ever written, but the marker already said \"done\" — so the session\n * compacted with the mechanical scrape only, and would never have retried,\n * ever, for that session. A threshold is now marked CONFIRMED only after a\n * handover cache file actually appears; an enqueue with no cache to show for\n * it within PENDING_TIMEOUT_MS is treated as failed and retried.\n *\n * Never throws. A daemon that isn't running, a stale reading, an unknown\n * fill — every one of those is a reason to do nothing this check and try\n * again next time, not a reason to interrupt the hook that called this.\n */\n\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport {\n getContextFill,\n contextFillThresholds,\n crossedThresholds,\n isForeignModelFamily,\n isImmediate,\n transcriptModelFamily,\n type ContextFillReading,\n type ThresholdName,\n} from \"../hooks/ts/lib/context-fill.js\";\nimport { isWorkerSession } from \"../hooks/ts/lib/worker-session.js\";\nimport { readContextHandoverCache, type ContextHandoverCache } from \"../hooks/ts/lib/context-handover-cache.js\";\nimport { PaiClient } from \"../daemon/ipc-client.js\";\n\n/** The enqueue only needs to hand the item to the daemon's queue file — the\n * actual LLM call happens later, asynchronously, in the worker loop. A\n * short bound keeps a rare (at most twice per session) daemon hiccup from\n * making the calling hook wait anywhere near as long as a normal IPC call\n * is allowed to. */\nconst ENQUEUE_TIMEOUT_MS = 2_000;\n\n/**\n * How long to wait for a handover cache to appear after an enqueue before\n * treating it as failed and retrying. Generous over the worker's own sonnet\n * timeout (120s, see session-summary-worker.ts's CLAUDE_TIMEOUT_MS) plus\n * queue latency, so a slow-but-working run isn't retried out from under\n * itself — and short enough that a genuinely dead attempt (a daemon restart\n * mid-spawn, exactly what happened in the live failure this fixes) is\n * retried well before the next compaction, not \"never\".\n */\nconst PENDING_TIMEOUT_MS = 5 * 60 * 1000;\n\n/** Both thresholds confirmed — nothing left to check for the rest of the session. */\nconst ALL_THRESHOLDS: ThresholdName[] = [\"warmup\", \"refresh\"];\n\n// ---------------------------------------------------------------------------\n// Persisted state — outcome-based, not intent-based\n// ---------------------------------------------------------------------------\n\n/**\n * `confirmed`: thresholds whose handover was actually verified written.\n * `pending`: an enqueue attempt awaiting outcome confirmation — cleared\n * either when a fresher cache appears (success) or when PENDING_TIMEOUT_MS\n * elapses with nothing to show for it (treated as failed; retried).\n *\n * A file in the OLD shape (`{fired: [...]}`, no `confirmed` key — exactly\n * what the live failure left behind) parses with `confirmed` defaulting to\n * `[]` and `pending` to `null`: it is treated as \"nothing confirmed yet\",\n * which self-heals the stuck session that bug left behind rather than\n * requiring a manual cleanup step.\n */\nexport interface HandoverTriggerState {\n confirmed: ThresholdName[];\n pending: { thresholds: ThresholdName[]; enqueuedAt: string } | null;\n}\n\nexport function triggerStatePath(sessionId: string): string {\n return join(tmpdir(), `pai-context-handover-fired-${sessionId}.json`);\n}\n\nexport function loadTriggerState(sessionId: string): HandoverTriggerState {\n const path = triggerStatePath(sessionId);\n if (!existsSync(path)) return { confirmed: [], pending: null };\n try {\n const raw = JSON.parse(readFileSync(path, \"utf-8\")) as Partial<HandoverTriggerState>;\n return {\n confirmed: Array.isArray(raw.confirmed) ? (raw.confirmed as ThresholdName[]) : [],\n pending: raw.pending && Array.isArray(raw.pending.thresholds) && typeof raw.pending.enqueuedAt === \"string\"\n ? raw.pending\n : null,\n };\n } catch {\n return { confirmed: [], pending: null };\n }\n}\n\nfunction saveTriggerState(sessionId: string, state: HandoverTriggerState): void {\n try {\n writeFileSync(triggerStatePath(sessionId), JSON.stringify(state), \"utf-8\");\n } catch {\n // Best-effort — a missing write only costs a possible re-attempt later.\n }\n}\n\n/**\n * Reset a session's fired/confirmed marker after a compaction, so the next\n * fill cycle gets its own handover instead of being judged against\n * thresholds this session already used up. Deliberately touches ONLY the\n * marker file (`triggerStatePath`) — never the handover cache\n * (`context-handover-cache.ts`), which is what actually gets injected at\n * the compaction boundary and must survive until that injection reads it.\n *\n * WHY THIS EXISTS (do not \"simplify\" it away): without this reset,\n * `confirmed` was permanent for the lifetime of a session id — nothing\n * anywhere ever cleared it. A session observed live on 2026-09-17\n * compacted once, both thresholds got confirmed, and every check for the\n * rest of that session's life then hit the `confirmed` short-circuit at\n * the top of `checkAndEnqueueContextHandover` and returned immediately —\n * even after a SECOND compaction, ~15 hours and a full context refill\n * later, whose injected \"handover\" was still the first one, generated\n * long before, and which said in its own text that nothing had changed\n * since it. A long-lived session compacts more than once; each compaction\n * starts a new fill cycle and deserves its own handover attempt.\n *\n * Call this exactly once per compaction, from the post-compact SessionStart\n * hook that already runs at that moment for other cleanup — see\n * post-compact-inject.ts.\n *\n * If a `pending` enqueue was outstanding at reset time, it is simply\n * dropped along with the rest of the marker — the in-flight daemon job\n * itself is NOT cancelled and will still write its cache entry when it\n * finishes (harmless: cache files are read by generation time, not by\n * whether a marker was watching for them). Because the marker is gone,\n * the next check has no pending entry to resolve, so it does not confirm\n * against that stray cache — it simply runs a fresh crossing check. So\n * this cannot double-enqueue: the only thing that ever calls `enqueue`\n * again is a genuinely fresh threshold crossing after the reset, of which\n * there is exactly one code path.\n *\n * Never throws: a session with no marker file (fresh session, or one\n * already reset) is a plain no-op, not an error.\n */\nexport function resetHandoverTriggerState(sessionId: string): void {\n const path = triggerStatePath(sessionId);\n try {\n if (existsSync(path)) unlinkSync(path);\n } catch {\n // Best-effort — see saveTriggerState above. Worst case on failure: the\n // next compaction re-serves one stale handover, exactly the bug this\n // function fixes, not a corrupted or crashed session.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Enqueue\n// ---------------------------------------------------------------------------\n\nexport interface HandoverTriggerInput {\n sessionId: string;\n cwd: string;\n transcriptPath?: string;\n}\n\nasync function enqueueContextHandover(payload: {\n sessionId: string;\n cwd: string;\n transcriptPath?: string;\n threshold: ThresholdName;\n urgent: boolean;\n}): Promise<void> {\n const client = new PaiClient();\n await client.call(\n \"work_queue_enqueue\",\n {\n type: \"context-handover\",\n // Below session-end (2), above session-summary (4) — this is rarer\n // than session-summary (at most twice per session) and time-sensitive\n // (it exists specifically to beat the compaction it is racing).\n priority: 3,\n payload: {\n sessionId: payload.sessionId,\n cwd: payload.cwd,\n transcriptPath: payload.transcriptPath,\n threshold: payload.threshold,\n urgent: payload.urgent,\n },\n },\n ENQUEUE_TIMEOUT_MS\n );\n}\n\n// ---------------------------------------------------------------------------\n// Dependency injection — for testing without a real daemon or filesystem\n// ---------------------------------------------------------------------------\n\nexport interface HandoverTriggerDeps {\n getReading: (input: HandoverTriggerInput) => ContextFillReading;\n loadState: (sessionId: string) => HandoverTriggerState;\n saveState: (sessionId: string, state: HandoverTriggerState) => void;\n readCache: (sessionId: string) => ContextHandoverCache | null;\n enqueue: typeof enqueueContextHandover;\n now: () => number;\n}\n\nconst defaultDeps: HandoverTriggerDeps = {\n getReading: (input) =>\n getContextFill({ sessionId: input.sessionId, transcriptPath: input.transcriptPath }),\n loadState: loadTriggerState,\n saveState: saveTriggerState,\n readCache: readContextHandoverCache,\n enqueue: enqueueContextHandover,\n now: () => Date.now(),\n};\n\n// ---------------------------------------------------------------------------\n// Main entry point\n// ---------------------------------------------------------------------------\n\nexport interface HandoverTriggerResult {\n /** Newly enqueued this check — an attempt, not yet a confirmed outcome. */\n attempted: ThresholdName[];\n /** Newly CONFIRMED this check — a handover cache was actually verified. */\n confirmed: ThresholdName[];\n}\n\nconst NOTHING: HandoverTriggerResult = { attempted: [], confirmed: [] };\n\n/**\n * Check this session's current context fill against the derived thresholds,\n * confirm or retry any in-flight attempt, and enqueue a context-handover job\n * for any newly-crossed threshold. Never marks a threshold done on the\n * strength of the enqueue call alone — only a verified cache file does that.\n *\n * A single check that crosses both warmup and refresh in one jump (a\n * session first observed already close to compaction) enqueues ONE job,\n * tagged with the more urgent of the two, and — once confirmed — marks both\n * done together, since one model-written handover already covers what\n * either alone would have.\n */\nexport async function checkAndEnqueueContextHandover(\n input: HandoverTriggerInput,\n deps: HandoverTriggerDeps = defaultDeps\n): Promise<HandoverTriggerResult> {\n // A disposable worker session gets no handover: nobody resumes it, and its\n // model may not even be the platform's (a different window, a different\n // provider's bill). Two signals, either suffices: the launcher's env mark,\n // or a transcript whose last assistant turn came from a non-Claude model.\n if (isWorkerSession()) {\n console.error(`[context-handover-trigger] session ${input.sessionId}: worker session (PAI_WORKER=1) — no handover.`);\n return NOTHING;\n }\n // Native is Claude plus the registry's provider models: a primary session\n // on a non-Anthropic provider still gets handovers; only transcripts from\n // models this machine does not run are skipped.\n if (\n input.transcriptPath &&\n isForeignModelFamily(transcriptModelFamily(input.transcriptPath))\n ) {\n console.error(`[context-handover-trigger] session ${input.sessionId}: transcript written by a model this machine does not run — no handover.`);\n return NOTHING;\n }\n\n let state = deps.loadState(input.sessionId);\n if (ALL_THRESHOLDS.every((t) => state.confirmed.includes(t))) return NOTHING;\n\n // Thresholds confirmed DURING this check (from the pending-resolution step\n // below) — carried through every return path so a confirm that falls\n // through to a fresh crossing-check doesn't get silently dropped.\n let confirmedThisCheck: ThresholdName[] = [];\n\n // -------------------------------------------------------------------\n // Resolve any in-flight attempt first — confirm, keep waiting, or\n // time it out and clear it for retry. This is the outcome check: a\n // handover cache generated AFTER the enqueue is proof the worker ran\n // and actually wrote something, not just that the enqueue call itself\n // succeeded.\n // -------------------------------------------------------------------\n if (state.pending) {\n const cache = deps.readCache(input.sessionId);\n const cacheIsFromThisAttempt =\n cache !== null && Date.parse(cache.generatedAt) > Date.parse(state.pending.enqueuedAt);\n\n if (cacheIsFromThisAttempt) {\n const newlyConfirmed = state.pending.thresholds.filter((t) => !state.confirmed.includes(t));\n state = { confirmed: [...state.confirmed, ...newlyConfirmed], pending: null };\n deps.saveState(input.sessionId, state);\n confirmedThisCheck = newlyConfirmed;\n console.error(\n `[context-handover-trigger] session ${input.sessionId}: confirmed [${newlyConfirmed.join(\", \")}] ` +\n `— handover cache verified (generated ${cache!.generatedAt}).`\n );\n if (ALL_THRESHOLDS.every((t) => state.confirmed.includes(t))) {\n return { attempted: [], confirmed: confirmedThisCheck };\n }\n // Fall through — a session that jumped straight to refresh-confirmed\n // territory may already have crossed further; keep checking below.\n } else if (deps.now() - Date.parse(state.pending.enqueuedAt) < PENDING_TIMEOUT_MS) {\n // Still within the window — no news is not yet failure.\n return NOTHING;\n } else {\n console.error(\n `[context-handover-trigger] session ${input.sessionId}: pending [${state.pending.thresholds.join(\", \")}] ` +\n `timed out after ${PENDING_TIMEOUT_MS}ms with no handover cache written — ` +\n `treating as FAILED and retrying (daemon worker likely died or was restarted mid-run).`\n );\n state = { ...state, pending: null };\n deps.saveState(input.sessionId, state);\n }\n }\n\n const reading = deps.getReading(input);\n if (reading.status !== \"ok\" || reading.usedTokens === null) {\n return { attempted: [], confirmed: confirmedThisCheck };\n }\n\n const thresholds = contextFillThresholds(reading, process.env, { cwd: input.cwd });\n if (!thresholds.windowConfirmed) {\n console.error(\n `[context-handover-trigger] session ${input.sessionId}: window size not confirmed — ` +\n `using the assumed default (${reading.windowSize} tokens) rather than a reading Claude Code reported.`\n );\n }\n\n const newlyCrossed = crossedThresholds(reading.usedTokens, thresholds, state.confirmed);\n if (newlyCrossed.length === 0) return { attempted: [], confirmed: confirmedThisCheck };\n\n const urgent = isImmediate(reading.usedTokens, thresholds);\n const mostUrgent = newlyCrossed[newlyCrossed.length - 1];\n\n try {\n await deps.enqueue({\n sessionId: input.sessionId,\n cwd: input.cwd,\n transcriptPath: input.transcriptPath,\n threshold: mostUrgent,\n urgent,\n });\n } catch (err) {\n console.error(\n `[context-handover-trigger] session ${input.sessionId}: enqueue FAILED — will retry next check: ${err}`\n );\n // Nothing persisted — next check sees no pending and tries again.\n return { attempted: [], confirmed: confirmedThisCheck };\n }\n\n state = { ...state, pending: { thresholds: newlyCrossed, enqueuedAt: new Date(deps.now()).toISOString() } };\n deps.saveState(input.sessionId, state);\n console.error(\n `[context-handover-trigger] session ${input.sessionId}: enqueued [${newlyCrossed.join(\", \")}] ` +\n `at ${reading.usedTokens} tokens (trigger=${thresholds.effectiveTriggerTokens}, ` +\n `triggerSource=${thresholds.triggerSource}, autocompactPct=${thresholds.autocompactPct}, urgent=${urgent}) ` +\n `— awaiting outcome confirmation before marking done.`\n );\n\n return { attempted: newlyCrossed, confirmed: confirmedThisCheck };\n}\n","/**\n * pai session autosave [--session-id <uuid>] [--min-gap <seconds>] [--dry-run]\n *\n * The rolling floor. Runs unattended from live hooks during a session, so an\n * interrupted session still leaves a usable `## Continue` behind.\n *\n * It writes in \"auto\" mode, which is what makes it safe to run often: a\n * model-authored checkpoint for the same session is preserved untouched, so\n * this can never trade a good handover for a mechanical one. When there is no\n * authored checkpoint — the Ctrl+C case, the crash case, the \"never got round\n * to it\" case — this is what the next session finds instead of nothing.\n *\n * Silent and exit-0 on every path. It is wired to hooks that fire constantly;\n * anything it printed would be noise, and anything it threw would interrupt\n * Claude Code.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { join } from \"node:path\";\nimport { existsSync, statSync, writeFileSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { applyContinue } from \"../../../session/checkpoint-block.js\";\nimport {\n buildAutosaveBody,\n findTranscripts,\n resolveTranscriptDir,\n} from \"../../../session/autosave.js\";\nimport { checkAndEnqueueContextHandover } from \"../../../session/context-handover-trigger.js\";\nimport { resolveSessionLine, resolveNotePath, resolveProjectByCwd } from \"./pause.js\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\n\nexport interface AutosaveOptions {\n sessionId?: string;\n minGap?: string;\n dryRun?: boolean;\n}\n\n/** Default seconds between autosaves. Long enough to be free, short enough to matter. */\nconst DEFAULT_MIN_GAP = 240;\n\n// ---------------------------------------------------------------------------\n// Rate limiting\n// ---------------------------------------------------------------------------\n\n/**\n * One sentinel per project, shared by every trigger.\n *\n * The command is wired to more than one hook event, and they fire at wildly\n * different rates. Keying the gap on the project rather than the event means\n * \"at most once per N seconds\" holds across all of them, instead of once per\n * N seconds *each*.\n */\nfunction sentinelPath(rootPath: string): string {\n const key = rootPath.replace(/[^a-zA-Z0-9]/g, \"-\").slice(-80);\n return join(tmpdir(), `pai-autosave-${key}`);\n}\n\nfunction tooRecent(rootPath: string, minGapSeconds: number): boolean {\n const p = sentinelPath(rootPath);\n if (!existsSync(p)) return false;\n try {\n return Date.now() - statSync(p).mtimeMs < minGapSeconds * 1000;\n } catch {\n return false;\n }\n}\n\nfunction touchSentinel(rootPath: string): void {\n try {\n writeFileSync(sentinelPath(rootPath), String(Date.now()), \"utf8\");\n } catch {\n // Best-effort — a missing sentinel only costs an extra write.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdAutosave(db: Database, opts: AutosaveOptions): Promise<void> {\n const minGap = parseInt(opts.minGap ?? String(DEFAULT_MIN_GAP), 10);\n const cwd = process.cwd();\n\n const project = resolveProjectByCwd(db, cwd) as ProjectRow | undefined;\n if (!project) return;\n\n if (!opts.dryRun && tooRecent(project.root_path, minGap)) return;\n\n // The transcript lives in the Claude Code project directory, which the\n // registry already records as encoded_dir. Passing the session id matters:\n // it selects this session's transcript — both halves of it — rather than\n // whichever file happens to be newest.\n // The registry's encoded_dir is a hint, not an answer — it goes stale when a\n // project moves and silently disables capture. See resolveTranscriptDir.\n const transcriptPaths = findTranscripts(\n resolveTranscriptDir(project),\n opts.sessionId\n );\n\n // Threshold-triggered pre-compaction handover — runs on the same cadence\n // as the rolling checkpoint below (same rate-limit gate above), but is\n // otherwise independent of it: it must still check even on a tick where\n // there is no new autosave body to write, because a session can sit idle\n // on the CLI side while its token count keeps climbing from tool output.\n // Best-effort and bounded (see context-handover-trigger.ts) — never lets a\n // daemon hiccup slow this hook down or throw out of it.\n if (opts.sessionId && !opts.dryRun) {\n const liveTranscript = transcriptPaths[transcriptPaths.length - 1];\n try {\n await checkAndEnqueueContextHandover({\n sessionId: opts.sessionId,\n cwd,\n transcriptPath: liveTranscript,\n });\n } catch {\n // checkAndEnqueueContextHandover already catches its own errors; this\n // is only a backstop against a bug in that catching.\n }\n }\n\n const body = buildAutosaveBody({ cwd, transcriptPaths });\n // Nothing worth recording — leave whatever is already there alone.\n if (!body) return;\n\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 const result = applyContinue({\n rootPath: project.root_path,\n authored: \"auto\",\n sessionLine: resolveSessionLine(project, session, resolveNotePath(project)),\n sessionId: opts.sessionId,\n cwd,\n body,\n dryRun: opts.dryRun,\n });\n\n if (opts.dryRun) {\n console.log(result.block);\n console.log(`\\n[dry-run] action would be: ${result.action}`);\n return;\n }\n\n touchSentinel(project.root_path);\n}\n","/**\n * pai session recent [-n N] [--all] [--json]\n *\n * List named sessions — resumable OR in the clc registry.\n * Default: \"named\" filter (resumable + stubs + transcript-only from clc registry).\n * --all: also include unnamed orphan sessions (only in sessions/ subdir, not in registry).\n *\n * Status column (always shown):\n * resumable — top-level jsonl with system snapshot (claude --resume works) — green\n * stub — in registry, top-level exists but no system lines (Ctrl+C exit) — yellow\n * transcript-only — in registry but no top-level jsonl — yellow\n * orphan — not in registry, only transcript exists — dim (--all only)\n *\n * When AIBroker is running, a \"Live Sessions\" section is shown first,\n * listing currently-active iTerm2 panes that have an active Claude session.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport chalk from \"chalk\";\nimport { renderTable, err, dim, header } from \"../../utils.js\";\nimport { scanSessions, fmtAge, type SessionStatus } from \"../../lib/session-scan.js\";\nimport { fetchLiveSessions, type AiBrokerSessionMeta } from \"../../lib/aibroker-client.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fmtSize(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`;\n if (bytes < 1_048_576) return `${Math.round(bytes / 1024)}K`;\n return `${(bytes / 1_048_576).toFixed(1)}M`;\n}\n\nfunction fmtStatus(status: SessionStatus): string {\n switch (status) {\n case \"resumable\":\n return chalk.green(\"resumable\");\n case \"stub\":\n return chalk.yellow(\"stub\");\n case \"transcript-only\":\n return chalk.yellow(\"transcript-only\");\n case \"orphan\":\n return chalk.dim(\"orphan\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Live-sessions rendering (AIBroker integration)\n// ---------------------------------------------------------------------------\n\nfunction renderLiveSessions(allLiveSessions: AiBrokerSessionMeta[], allTabs: boolean): void {\n // Default: Claude sessions only (kind === \"claude\"). --all-tabs includes shells.\n const displayed = allTabs\n ? allLiveSessions\n : allLiveSessions.filter((s) => s.kind !== \"shell\");\n const skippedCount = allLiveSessions.length - displayed.length;\n\n if (displayed.length === 0) return;\n\n console.log(\"\\n\" + header(\"Live Sessions\") + \"\\n\");\n\n const liveHeaders = [\"#\", \"id\", \"name\", \"at prompt\", \"kind\"];\n const liveRows = displayed.map((s, idx) => {\n const shortId = s.sessionId.slice(0, 8);\n // Prefer paiName for Claude sessions, fall back to iTerm2 tab name\n const rawName = s.paiName ?? s.name;\n const name = rawName.length > 36 ? rawName.slice(0, 35) + \"…\" : rawName;\n const atPrompt = s.atPrompt ? chalk.green(\"yes\") : chalk.yellow(\"busy\");\n const kind = s.kind === \"claude\"\n ? chalk.cyan(s.kind)\n : chalk.dim(s.kind);\n\n return [\n chalk.dim(String(idx + 1)),\n chalk.cyan(shortId),\n name,\n atPrompt,\n kind,\n ];\n });\n\n console.log(renderTable(liveHeaders, liveRows));\n\n if (skippedCount > 0) {\n console.log(\n dim(` (${skippedCount} shell tab${skippedCount === 1 ? \"\" : \"s\"} hidden — use --all-tabs to show)`)\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdRecent(\n db: Database,\n opts: { n?: string; all?: boolean; allTabs?: boolean; json?: boolean }\n): Promise<void> {\n const limit = parseInt(opts.n ?? \"20\", 10);\n const includeAll = opts.all === true;\n const allTabs = opts.allTabs === true;\n\n // Fetch live sessions from AIBroker (silently no-ops if not running).\n const liveSessions = await fetchLiveSessions();\n\n const sessions = scanSessions(db, {\n limit,\n filter: includeAll ? \"all\" : \"named\",\n });\n\n if (opts.json) {\n const output = {\n live: liveSessions.map((s) => ({\n index: s.index,\n sessionId: s.sessionId,\n name: s.name,\n paiName: s.paiName,\n atPrompt: s.atPrompt,\n kind: s.kind,\n active: s.active,\n })),\n paused: sessions.map((s, idx) => ({\n idx: idx + 1,\n uuid: s.uuid,\n shortId: s.shortId,\n resumable: s.resumable,\n sessionStatus: s.sessionStatus,\n age: fmtAge(s.mtime),\n mtime: s.mtime,\n name: s.friendlyName,\n lastUserPrompt: s.lastUserPrompt,\n userLines: s.userLines,\n msgCount: s.msgCount,\n topLevelSize: s.topLevelSize,\n decodedPath: s.decodedPath,\n })),\n };\n console.log(JSON.stringify(output, null, 2));\n return;\n }\n\n // ── Live section ──────────────────────────────────────────────────────────\n const hasClaudeLive = liveSessions.some((s) => s.kind === \"claude\");\n if (liveSessions.length > 0) {\n renderLiveSessions(liveSessions, allTabs);\n }\n\n // ── Paused / disk-scan section ────────────────────────────────────────────\n if (sessions.length === 0) {\n if (!hasClaudeLive) {\n // Nothing at all\n if (includeAll) {\n console.log(err(\"No sessions found in ~/.claude/projects/.\"));\n } else {\n console.log(\n err(\n \"No named sessions found.\\n\\n\" +\n \" Named sessions appear when you have entries in ~/.claude/session.json\\n\" +\n \" (set via /Name inside Claude Code) or resumable top-level jsonl files.\\n\" +\n \" Run: pai session recent --all to list all sessions including unnamed orphans.\"\n )\n );\n }\n }\n // If we had live Claude sessions, nothing more to print.\n return;\n }\n\n const title = includeAll\n ? \"Paused / All Sessions (named + orphans)\"\n : \"Paused / Resumable Sessions\";\n console.log(\"\\n\" + header(title) + \"\\n\");\n\n const headers = [\"#\", \"id\", \"age\", \"name / project\", \"last prompt\", \"msgs\", \"size\", \"status\"];\n\n const rows = sessions.map((s, idx) => {\n const name = s.friendlyName ?? s.decodedPath;\n const prompt = s.lastUserPrompt\n ? chalk.dim(\n s.lastUserPrompt.length > 40\n ? s.lastUserPrompt.slice(0, 39) + \"…\"\n : s.lastUserPrompt\n )\n : dim(\"—\");\n\n return [\n chalk.dim(String(idx + 1)),\n chalk.cyan(s.shortId),\n dim(fmtAge(s.mtime)),\n name.length > 26 ? name.slice(0, 25) + \"…\" : name,\n prompt,\n s.userLines > 0 ? String(s.userLines) : dim(\"—\"),\n dim(fmtSize(s.topLevelSize)),\n fmtStatus(s.sessionStatus),\n ];\n });\n\n console.log(renderTable(headers, rows));\n\n console.log(\n \"\\n\" +\n dim(\"Go to a session: \") +\n chalk.white(\"pai resume <name>\") +\n \"\\n\" +\n (hasClaudeLive\n ? dim(\"Pause all live: \") + chalk.white(\"pai pause all\") + \"\\n\"\n : \"\") +\n (includeAll\n ? \"\"\n : dim(\"Show all sessions: \") +\n chalk.white(\"pai sessions --all\") +\n \"\\n\")\n );\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: is a transcript for <uuid> on disk under this cwd?\n * (see probeResume in lib/launch.ts — the filesystem answers,\n * and only an unrecognised layout falls back to spawning claude)\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 is two `existsSync` calls on the happy path. On the fallback path, total latency\n * is the same 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\";\nimport { probeResume } from \"../../lib/launch.js\";\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\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.exitCode = 1;\n return;\n }\n printExitDir(projectDir);\n process.exitCode = result.status ?? 0;\n return;\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.exitCode = 1;\n return;\n }\n printExitDir(projectDir);\n process.exitCode = result.status ?? 0;\n return;\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.exitCode = 1;\n return;\n }\n printExitDir(projectDir);\n process.exitCode = result.status ?? 0;\n }\n}\n","/**\n * pai session restore — put displaced transcripts back where resume looks.\n *\n * PAI moved session transcripts out of `~/.claude/projects/<dir>/` into\n * `<dir>/sessions/`, and `claude --resume <uuid>` reads only the former. The\n * archiver no longer does that (it hardlinks), but the sessions already moved\n * stayed moved — and every checkpoint PAI ever wrote ends with\n * \"Resume with: claude --resume <uuid>\". For a displaced session that line is an\n * instruction that cannot work, and nothing says so.\n *\n * One project measured 1 transcript at the project root against 52 underneath.\n *\n * So this is a repair, and it is deliberately a COMMAND rather than another hook.\n * Silently relinking dozens of a user's files during an unrelated session start is\n * the same class of mistake as silently moving them was. Dry run is the default;\n * `--execute` is the only thing that touches disk.\n *\n * The link logic itself is imported, not rewritten. Today's other bug was a fix\n * that landed in one of three copies of `probeResume`, so `restoreTopLevel` stays\n * the single implementation.\n */\n\nimport { existsSync, readdirSync, statSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { restoreTopLevel, hasConversation } from \"../../lib/launch.js\";\nimport { claudeProjectsDir } from \"../../../registry/moved.js\";\nimport { smartDecodeDir, ok, warn, err, dim, bold } from \"../../utils.js\";\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport interface DisplacedSession {\n uuid: string;\n /** The `~/.claude/projects/<encoded>` directory holding it. */\n projectDir: string;\n encodedDir: string;\n /** Decoded working directory, when it can be recovered. */\n cwd: string | null;\n bytes: number;\n mtime: number;\n /** Files that tell the user to resume this id — the lie being repaired. */\n promisedBy: string[];\n /**\n * Whether the transcript contains an actual exchange.\n *\n * False means restoring it changes nothing: `claude --resume` answers\n * \"No conversation found\" for a file with no conversation in it, wherever it\n * sits. Measured 2026-08-04 on 046bb712 — 537 bytes of `last-prompt`,\n * `custom-title`, `agent-name`, `mode`, `permission-mode` and nothing else. It\n * was restored, correctly hardlinked, and still would not resume.\n *\n * Worth reporting rather than hiding, because that id is named in a checkpoint\n * telling the user to resume it. The honest answer is that the session was\n * never there, not that the repair failed.\n */\n hasConversation: boolean;\n}\n\n/*\n * Stub detection lives in launch.ts, imported above, and this is the second time\n * today that duplicating a helper cost real work — so it stays imported.\n *\n * The version that was here read a bounded 256 KB head and looked only for an\n * assistant marker. AIBroker refuted it with measurements and both causes are\n * confirmed on this machine:\n *\n * b3462801, 867 KB, a session we had BOTH verified claude --resume accepts:\n * line 1 is 762,976 bytes — one hook-context attachment blob\n * first \"type\":\"user\" at byte 766,830\n * so any head shorter than 766 KB reports a working session as an empty stub.\n *\n * b8cd4a5d, 2,626 bytes, 3 user lines and ZERO assistant lines: restored and\n * probed, claude FINDS it. A user-only transcript is resumable, so requiring an\n * assistant marker is simply the wrong test.\n *\n * Measured over this project's 52 archived transcripts: head+assistant-only said\n * 20 real / 32 stubs, chunked-full-scan + user-OR-assistant says 33 real / 19\n * stubs. Thirteen disagreements, every one of them head=stub / full=real — a 41%\n * false-stub rate, all in the direction that talks the user out of a recovery.\n * The \"745 KB stub\" I reported was one of these.\n *\n * Cost is not the tradeoff it looked like: a full scan short-circuits at the\n * first marker, so a real session stops early, and a genuine stub is small\n * enough to read whole. The head read paid 256 KB on every file including the\n * ones it then got wrong.\n */\n\n/**\n * Transcripts that exist under `sessions/` with no twin at the project root.\n *\n * A twin means nothing needs doing: the archiver hardlinks now, so the normal\n * state is both names pointing at one inode. Only the ones missing upstairs were\n * displaced by the old rename.\n */\nexport function findDisplaced(projectsDir = claudeProjectsDir()): DisplacedSession[] {\n const out: DisplacedSession[] = [];\n\n let encodedDirs: string[];\n try {\n encodedDirs = readdirSync(projectsDir);\n } catch {\n return out; // no projects dir at all — nothing to repair, not an error\n }\n\n for (const encodedDir of encodedDirs) {\n const projectDir = join(projectsDir, encodedDir);\n const sessionsDir = join(projectDir, \"sessions\");\n if (!existsSync(sessionsDir)) continue;\n\n let files: string[];\n try {\n files = readdirSync(sessionsDir);\n } catch {\n continue;\n }\n\n for (const file of files) {\n if (!file.endsWith(\".jsonl\")) continue;\n const uuid = file.slice(0, -6);\n if (!UUID_RE.test(uuid)) continue;\n if (existsSync(join(projectDir, file))) continue; // already has its twin\n\n let bytes = 0;\n let mtime = 0;\n try {\n const st = statSync(join(sessionsDir, file));\n bytes = st.size;\n mtime = st.mtimeMs;\n } catch {\n continue; // vanished under us — nothing to restore\n }\n\n out.push({\n uuid,\n projectDir,\n encodedDir,\n cwd: smartDecodeDir(encodedDir),\n bytes,\n mtime,\n promisedBy: [],\n hasConversation: hasConversation(join(sessionsDir, file)),\n });\n }\n }\n\n // Biggest first: transcript size is the best available proxy for how much work\n // is currently unreachable, and it is what makes the report worth reading.\n return out.sort((a, b) => b.bytes - a.bytes);\n}\n\n/**\n * Notes that instruct the user to resume this id.\n *\n * Bounded on purpose — the project's own TODO.md and note files, never a search\n * from the home directory. The point is to name the specific promise each\n * restore makes true, so a count of files is not enough.\n */\nexport function findResumePromises(uuid: string, cwd: string | null): string[] {\n if (!cwd) return [];\n\n const candidates: string[] = [];\n for (const notesDir of [cwd, join(cwd, \"Notes\"), join(cwd, \"notes\")]) {\n try {\n for (const entry of readdirSync(notesDir)) {\n if (entry.endsWith(\".md\")) candidates.push(join(notesDir, entry));\n }\n } catch {\n /* not a directory here — try the next */\n }\n }\n\n // Deduplicate by INODE, not by path text and not by realpath.\n //\n // macOS is case-insensitive, so `Notes` and `notes` above are the same\n // directory and every note was reported twice, once per spelling. The obvious\n // fix — resolve with realpathSync and compare — works under Bun and does NOT\n // work under Node, whose non-native realpathSync leaves case alone; the tests\n // caught exactly that split. Identity by (device, inode) is filesystem truth\n // in either runtime, and it collapses symlinks for free, which matters because\n // PAI symlinks note directories into the Obsidian vault.\n const seen = new Set<string>();\n const hits: string[] = [];\n for (const file of candidates) {\n let key: string;\n try {\n const st = statSync(file);\n key = `${st.dev}:${st.ino}`;\n } catch {\n key = file.toLowerCase(); // cannot stat — path text is all that is left\n }\n if (seen.has(key)) continue;\n seen.add(key);\n\n try {\n if (readFileSync(file, \"utf8\").includes(uuid)) hits.push(file);\n } catch {\n /* unreadable — silence is right, this is a report not a gate */\n }\n }\n return hits;\n}\n\nfunction human(bytes: number): string {\n if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n if (bytes >= 1024) return `${Math.round(bytes / 1024)} KB`;\n return `${bytes} B`;\n}\n\nexport interface RestoreOptions {\n execute?: boolean;\n json?: boolean;\n /** Only sessions a checkpoint tells the user to resume. */\n promised?: boolean;\n /** Only sessions belonging to this working directory. */\n cwd?: string;\n /** List every displaced session instead of the largest few. */\n all?: boolean;\n /**\n * Also restore transcripts with no conversation in them.\n *\n * Off by default because it is pointless work, and because the raw count\n * misrepresents the damage: of 2869 displaced transcripts measured 2026-08-04,\n * 2240 were stubs and only 629 held an actual conversation. 1493 of those stubs\n * came from one probe tool spawning sessions that were never used. Skipping\n * them is what turns \"450 MB displaced\" into the honest \"417 MB recoverable\"\n * without a special case for anybody's tooling.\n */\n includeStubs?: boolean;\n}\n\n/** How many to list before the report stops being readable. */\nconst LIST_LIMIT = 20;\n\n/**\n * Which of the displaced sessions a run is about, and which of those to link.\n *\n * Pure, and separate from the printing, for two reasons. The obvious one is that\n * it can be tested without pointing a test at the developer's real\n * `~/.claude/projects` — an earlier draft of the test did exactly that and would\n * have relinked live files. The other is that the report and the action must\n * agree: `--json` originally restored everything in scope while the formatted\n * path skipped stubs, so the two modes did different things with the same flags.\n */\nexport function selectTargets(\n everything: DisplacedSession[],\n opts: RestoreOptions\n): { inScope: DisplacedSession[]; toRestore: DisplacedSession[] } {\n const inScope = everything.filter((d) => {\n if (opts.promised && d.promisedBy.length === 0) return false;\n if (opts.cwd && d.cwd !== opts.cwd) return false;\n return true;\n });\n\n const toRestore = opts.includeStubs\n ? inScope\n : inScope.filter((d) => d.hasConversation);\n\n return { inScope, toRestore };\n}\n\nexport function cmdRestore(opts: RestoreOptions = {}): void {\n const everything = findDisplaced();\n for (const d of everything) {\n d.promisedBy = findResumePromises(d.uuid, d.cwd);\n }\n\n // Scoping exists because the first real dry run reported 2874 transcripts and\n // 450 MB, machine-wide — not the ~52 of one project we had been reasoning\n // about. Restoring is non-destructive and costs no disk (hardlinks), but\n // relinking 2874 files across every project a user has ever opened is not\n // something to do because they typed a command once, and an unscoped list of\n // 2874 lines is not a report anyone can act on.\n const { inScope: displaced, toRestore } = selectTargets(everything, opts);\n\n if (opts.json) {\n console.log(\n JSON.stringify(\n {\n displaced,\n // Named so a script can see that stubs were held back, rather than\n // inferring it from a count that does not match.\n wouldRestore: toRestore.map((d) => d.uuid),\n executed: Boolean(opts.execute),\n },\n null,\n 2\n )\n );\n if (opts.execute) for (const d of toRestore) restoreTopLevel(d.uuid, d.projectDir);\n return;\n }\n\n console.log();\n console.log(bold(\" Displaced session transcripts\"));\n console.log();\n\n if (displaced.length === 0) {\n if (everything.length > 0) {\n console.log(ok(` Nothing displaced in this scope.`));\n console.log(dim(` ${everything.length} displaced elsewhere — drop the filters to see them.`));\n } else {\n console.log(ok(\" Nothing displaced — every archived transcript has its twin at the\"));\n console.log(dim(\" project root, which is where claude --resume looks.\"));\n }\n console.log();\n return;\n }\n\n const promised = displaced.filter((d) => d.promisedBy.length > 0);\n const totalBytes = displaced.reduce((n, d) => n + d.bytes, 0);\n\n console.log(\n warn(\n ` ${displaced.length} transcript(s), ${human(totalBytes)}, currently unresumable.`\n )\n );\n if (displaced.length !== everything.length) {\n console.log(dim(` (filtered from ${everything.length} machine-wide)`));\n }\n if (promised.length > 0) {\n console.log(\n err(\n ` ${promised.length} named in a checkpoint that tells you to resume them — those lines are lies today.`\n )\n );\n }\n const stubs = displaced.filter((d) => !d.hasConversation);\n if (stubs.length > 0) {\n // Restoring these is harmless and pointless. Saying so is the difference\n // between a report and a sales pitch: `claude --resume` answers\n // \"No conversation found\" for an empty transcript wherever it sits.\n console.log(\n dim(\n ` ${stubs.length} of them hold no conversation at all — restoring those changes nothing.`\n )\n );\n }\n console.log();\n\n // Promised ones first regardless of size: a checkpoint pointing at a dead id is\n // the concrete harm, and it is what --promised restores on its own.\n const ordered = [...promised, ...displaced.filter((d) => d.promisedBy.length === 0)];\n const shown = opts.all ? ordered : ordered.slice(0, LIST_LIMIT);\n\n for (const d of shown) {\n const where = d.cwd ?? dim(`(undecodable: ${d.encodedDir})`);\n const stub = d.hasConversation ? \"\" : warn(\" [stub — no conversation]\");\n console.log(\n ` ${bold(d.uuid.slice(0, 8))} ${human(d.bytes).padStart(8)} ${where}${stub}`\n );\n for (const file of d.promisedBy) {\n // This is the line the repair makes true again — unless there is nothing\n // in the file, in which case say so rather than implying a recovery.\n console.log(\n d.hasConversation\n ? err(` promised by ${file}`)\n : dim(` promised by ${file} (promise cannot be kept: empty transcript)`)\n );\n }\n }\n\n if (shown.length < ordered.length) {\n console.log();\n console.log(\n dim(` … and ${ordered.length - shown.length} more. --all lists every one.`)\n );\n }\n\n console.log();\n\n if (!opts.execute) {\n console.log(dim(\" Dry run — nothing was touched.\"));\n console.log(dim(\" Restore hardlinks each transcript back to the project root: same inode,\"));\n console.log(dim(\" no copy, and the sessions/ archive is left exactly where it is.\"));\n console.log();\n console.log(dim(\" --promised --execute just the ones a checkpoint promises (start here)\"));\n console.log(dim(\" --cwd <path> --execute one project\"));\n console.log(dim(\" --execute everything in scope\"));\n console.log();\n return;\n }\n\n // Report everything in scope, restore only what restoring can help. A stub is\n // listed above with its label so the picture stays complete, but linking it\n // would be work with a guaranteed null result.\n const target = toRestore;\n const skipped = displaced.length - target.length;\n\n let restored = 0;\n const failed: string[] = [];\n for (const d of target) {\n if (restoreTopLevel(d.uuid, d.projectDir)) restored++;\n else failed.push(d.uuid);\n }\n\n console.log(ok(` Restored ${restored} of ${target.length} recoverable.`));\n if (skipped > 0) {\n console.log(\n dim(` Skipped ${skipped} stub(s) — nothing in them to resume. --include-stubs overrides.`)\n );\n }\n if (failed.length > 0) {\n console.log(warn(` Could not restore ${failed.length}:`));\n for (const uuid of failed) console.log(dim(` ${uuid}`));\n }\n console.log();\n}\n","/**\n * Commander registration for all `pai session` sub-commands.\n */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport {\n cmdList,\n cmdInfo,\n cmdRename,\n cmdSlug,\n cmdTag,\n cmdRoute,\n cmdActive,\n cmdAutoRoute,\n} from \"./commands.js\";\nimport { cmdCheckpoint } from \"./checkpoint.js\";\nimport { cmdHandover } from \"./handover.js\";\nimport { cmdAutosave } from \"./autosave.js\";\nimport { cmdRecent } from \"./recent.js\";\nimport { cmdGoto } from \"./goto.js\";\nimport { cmdPause } from \"./pause.js\";\nimport { cmdRestore } from \"./restore.js\";\n\nexport function registerSessionCommands(\n sessionCmd: Command,\n getDb: () => Database\n): void {\n // pai session list [-n N] [--all] [--json]\n // NEW: resumable sessions catalog (was \"recent\"). Short form: pai sessions.\n sessionCmd\n .command(\"list\")\n .description(\n \"Resumable sessions catalog — named sessions with resume status.\\n\" +\n \"Short form: pai sessions\\n\" +\n \"Default shows all sessions you've named or that are resumable.\\n\" +\n \"Use --all to also show unnamed orphan sessions.\"\n )\n .option(\"-n <count>\", \"Maximum sessions to show (default: 20)\", \"20\")\n .option(\"--all\", \"Include unnamed orphan sessions (not in clc registry)\")\n .option(\"--json\", \"Output raw JSON instead of formatted table\")\n .action(async (opts: { n?: string; all?: boolean; json?: boolean }) => {\n await cmdRecent(getDb(), opts);\n });\n\n // pai session restore [--execute]\n //\n // Repair, not routine. PAI's old archiver moved transcripts out of the project\n // root, and claude --resume reads only that root, so those sessions became\n // unresumable while every checkpoint kept telling the user to resume them.\n // Dry run by default: relinking dozens of a user's files unasked is the same\n // class of mistake as moving them was.\n sessionCmd\n .command(\"restore\")\n // One line, no embedded newlines: the docs generator turns them into\n // paragraph breaks, which split this sentence mid-clause in\n // docs/commands/session.md.\n .description(\n \"Restore transcripts PAI displaced into sessions/ so claude --resume can find them again. \" +\n \"Reports what is unresumable, which checkpoints promise it, and which transcripts are empty. \" +\n \"Dry run unless --execute is given.\"\n )\n .option(\"--execute\", \"Actually restore (hardlink back to the project root)\")\n .option(\"--promised\", \"Only sessions a checkpoint tells you to resume\")\n .option(\"--cwd <path>\", \"Only sessions belonging to this working directory\")\n .option(\"--all\", \"List every displaced session, not just the largest few\")\n .option(\"--include-stubs\", \"Also restore transcripts that hold no conversation\")\n .option(\"--json\", \"Output raw JSON instead of a formatted report\")\n .action(\n (opts: {\n execute?: boolean;\n promised?: boolean;\n cwd?: string;\n all?: boolean;\n includeStubs?: boolean;\n json?: boolean;\n }) => {\n cmdRestore(opts);\n }\n );\n\n // pai session info <project-slug> <number>\n sessionCmd\n .command(\"info <project-slug> <number>\")\n .description(\"Show full details for a specific session\")\n .action((projectSlug: string, number: string) => {\n cmdInfo(getDb(), projectSlug, number);\n });\n\n // pai session rename <project-slug> <number> <new-slug>\n sessionCmd\n .command(\"rename <project-slug> <number> <new-slug>\")\n .description(\n \"Rename a session note — updates file on disk, H1 title, and registry\"\n )\n .action((projectSlug: string, number: string, newSlug: string) => {\n cmdRename(getDb(), projectSlug, number, newSlug);\n });\n\n // pai session slug <project-slug> <number|latest>\n sessionCmd\n .command(\"slug <project-slug> <number>\")\n .description(\n \"Generate a descriptive slug from the session JSONL transcript\"\n )\n .option(\"--apply\", \"Rename the session note using the generated slug\")\n .action(\n (projectSlug: string, number: string, opts: { apply?: boolean }) => {\n cmdSlug(getDb(), projectSlug, number, opts);\n }\n );\n\n // pai session tag <project-slug> <number> [tags...]\n sessionCmd\n .command(\"tag <project-slug> <number> [tags...]\")\n .description(\n \"Set or show tags on a session. Tags can be space-separated or comma-separated.\"\n )\n .action((projectSlug: string, number: string, tags: string[]) => {\n cmdTag(getDb(), projectSlug, number, tags);\n });\n\n // pai session route <project-slug> <number> <target-project>\n sessionCmd\n .command(\"route <project-slug> <number> <target-project>\")\n .description(\n \"Create a cross-reference link from a session to a target project\"\n )\n .option(\"--type <type>\", \"Link type: related | follow-up | reference\", \"related\")\n .action(\n (\n projectSlug: string,\n number: string,\n targetProject: string,\n opts: { type?: string }\n ) => {\n cmdRoute(getDb(), projectSlug, number, targetProject, opts);\n }\n );\n\n // pai session handover [project-slug] [number-or-latest]\n sessionCmd\n .command(\"handover [project-slug] [number-or-latest]\")\n .description(\n \"Write a ## Continue section to the project's TODO.md.\\n\" +\n \"Called automatically from session-stop and pre-compact hooks.\\n\" +\n \"Records the last session identifier, timestamp, and working directory\\n\" +\n \"so the next session can resume from the correct context.\"\n )\n .option(\n \"--session-id <uuid>\",\n \"Claude session UUID. Used to decide whether an existing authored\\n\" +\n \"checkpoint belongs to this session and must be preserved. Without it\\n\" +\n \"the comparison falls back to the session note filename, which the stop\\n\" +\n \"hook itself renames — so pass it whenever it is known.\"\n )\n .action(\n (\n projectSlug: string | undefined,\n numberOrLatest: string | undefined,\n opts: { sessionId?: string }\n ) => {\n cmdHandover(getDb(), projectSlug, numberOrLatest, opts.sessionId);\n }\n );\n\n // pai session checkpoint <message>\n sessionCmd\n .command(\"checkpoint <message>\")\n .description(\n \"Append a timestamped checkpoint to the active session note.\\n\" +\n \"Designed for hooks (PostToolUse, UserPromptSubmit) — fast and silent.\\n\" +\n \"Rate-limited: skips silently if last checkpoint was < --min-gap seconds ago.\"\n )\n .option(\n \"--min-gap <seconds>\",\n \"Minimum seconds between checkpoints (default: 300 = 5 minutes)\",\n \"300\"\n )\n .action((message: string, opts: { minGap?: string }) => {\n cmdCheckpoint(message, opts);\n });\n\n // pai session autosave [--session-id <uuid>] [--min-gap <seconds>]\n sessionCmd\n .command(\"autosave\")\n .description(\n \"Refresh the ## Continue checkpoint from the transcript and working tree.\\n\" +\n \"Runs unattended from live hooks so an interrupted session still leaves\\n\" +\n \"a usable handover. Writes in auto mode, so a model-authored checkpoint\\n\" +\n \"for the same session is preserved untouched. Silent; always exits 0.\"\n )\n .option(\n \"--session-id <uuid>\",\n \"Claude session UUID — the key that decides whether an existing authored\\n\" +\n \"checkpoint belongs to this session and must be left alone.\"\n )\n .option(\n \"--min-gap <seconds>\",\n \"Minimum seconds between autosaves, shared across all triggers (default: 240)\",\n \"240\"\n )\n .option(\"--dry-run\", \"Print the block that would be written and exit\")\n .action(async (opts: { sessionId?: string; minGap?: string; dryRun?: boolean }) => {\n await cmdAutosave(getDb(), opts);\n });\n\n // pai session active [--minutes N] [--json]\n sessionCmd\n .command(\"active\")\n .description(\n \"Show currently active Claude Code sessions.\\n\" +\n \"Detects live sessions by checking which JSONL transcript files\\n\" +\n \"were recently modified in ~/.claude/projects/.\"\n )\n .option(\n \"--minutes <n>\",\n \"Consider sessions active if modified within N minutes (default: 60)\",\n \"60\"\n )\n .option(\"--json\", \"Output raw JSON instead of formatted display\")\n .action((opts: { minutes?: string; json?: boolean }) => {\n cmdActive(getDb(), opts);\n });\n\n // pai session recent — DEPRECATED alias for `pai session list`\n sessionCmd\n .command(\"recent\")\n .description(\"[Deprecated] Use `pai session list` or `pai sessions` instead.\")\n .option(\"-n <count>\", \"Maximum sessions to show (default: 20)\", \"20\")\n .option(\"--all\", \"Include unnamed orphan sessions (not in clc registry)\")\n .option(\"--json\", \"Output raw JSON instead of formatted table\")\n .action(async (opts: { n?: string; all?: boolean; json?: boolean }) => {\n process.stderr.write(\n \"Deprecated: `pai session recent` — use `pai session list` or `pai sessions` instead.\\n\"\n );\n await cmdRecent(getDb(), opts);\n });\n\n // pai session goto <name-or-id-or-prefix> [--skip-name] [--skip-go] [--dry-run]\n sessionCmd\n .command(\"goto <name-or-id>\")\n .description(\n \"Go to a session: resume if a resumable snapshot exists, start fresh otherwise.\\n\" +\n \"Recommended short form: pai resume <name>\\n\" +\n \"Resolves by clc/registry name (case-insensitive) or UUID prefix.\\n\" +\n \"Sends '/Name <name>\\\\ngo' as the initial prompt so the PAI ## Continue hook fires.\"\n )\n .option(\"--skip-name\", \"Do not prepend /Name to set the session name in chrome\")\n .option(\"--skip-go\", \"Do not append \\\\ngo to trigger PAI auto-resume (## Continue)\")\n .option(\"--dry-run\", \"Print the exact argv and cwd, then exit without launching\")\n .action(\n (nameOrId: string, opts: { skipName?: boolean; skipGo?: boolean; dryRun?: boolean }) => {\n process.stderr.write(\n \"Note: `pai session goto` works but `pai resume` is the new shorter form.\\n\"\n );\n cmdGoto(getDb(), nameOrId, { noName: opts.skipName, noGo: opts.skipGo, dryRun: opts.dryRun });\n }\n );\n\n // pai session pause [--dry-run]\n sessionCmd\n .command(\"pause\")\n .description(\n \"Write a ## Continue checkpoint to the project's TODO.md.\\n\" +\n \"Recommended short form: pai pause\\n\" +\n \"Ctrl+C bypasses the stop-hook, orphaning the session (cannot --resume).\\n\" +\n \"Use /exit inside Claude Code to preserve full session resumability.\"\n )\n .option(\"--dry-run\", \"Preview the ## Continue block without writing it\")\n .option(\n \"--body-file <path>\",\n \"File holding the checkpoint markdown to persist ('-' reads stdin). Required unless --no-body.\"\n )\n .option(\n \"--session-id <uuid>\",\n \"Claude Code session UUID — recorded as the `claude --resume` handle\"\n )\n .option(\n \"--no-body\",\n \"Deliberately write a metadata-only checkpoint (no content)\"\n )\n .action((opts: { dryRun?: boolean; bodyFile?: string; sessionId?: string; body?: boolean }) => {\n process.stderr.write(\n \"Note: `pai session pause` works but `pai pause` is the new shorter form.\\n\"\n );\n cmdPause(getDb(), {\n dryRun: opts.dryRun,\n bodyFile: opts.bodyFile,\n sessionId: opts.sessionId,\n noBody: opts.body === false,\n });\n });\n\n // pai session auto-route [--cwd path] [--context \"text\"] [--json]\n sessionCmd\n .command(\"auto-route\")\n .description(\n \"Auto-detect which project this session belongs to.\\n\" +\n \"Tries: (1) path match in registry, (2) Notes/PAI.md marker walk, (3) topic detection.\\n\" +\n \"Designed for use in CLAUDE.md session-start hooks.\"\n )\n .option(\n \"--cwd <path>\",\n \"Working directory to detect from (default: process.cwd())\"\n )\n .option(\n \"--context <text>\",\n \"Conversation context for topic-based fallback routing\"\n )\n .option(\"--json\", \"Output raw JSON instead of formatted display\")\n .action(\n async (opts: { cwd?: string; context?: string; json?: boolean }) => {\n await cmdAutoRoute(opts);\n }\n );\n}\n","/**\n * agents.ts — run agent definitions (~/.claude/agents/<name>.md) as workers.\n *\n * The file format is the Claude Code agent one: YAML front matter (`model`,\n * `tools`, `description`) followed by a Markdown body that is the agent's\n * system prompt. `pai worker run --agent <name>` loads it and maps it onto\n * the worker runner:\n *\n * - body → --append-system-prompt (prepended, so an explicit caller\n * flag still wins);\n * - tools → --allowedTools;\n * - model → a task class via its tier (haiku→simple, sonnet→implement,\n * opus→complex). Non-Anthropic ids map through the provider\n * registry (models.fast → haiku tier, models.default → sonnet\n * tier, a provider's modelTiers override if set), unless\n * --class is given.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { ModelTier, WorkerProvider } from \"./config.js\";\n\nexport type { ModelTier } from \"./config.js\";\n\nimport { UNLABELED } from \"./status.js\";\n\nexport interface AgentDef {\n name: string;\n /** Front-matter `model`, when present (any string; mapped via modelToClass). */\n model?: string;\n /** Front-matter `tools`, comma-separated. */\n tools?: string[];\n /** Front-matter `description`. */\n description?: string;\n /** Everything after the front matter — the agent's system prompt. */\n body: string;\n /** The file the definition was read from. */\n path: string;\n}\n\n/** Directory the agent library lives in. */\nexport function agentsDir(): string {\n return join(homedir(), \".claude\", \"agents\");\n}\n\nexport function agentPath(name: string): string {\n return join(agentsDir(), `${name}.md`);\n}\n\n/** `key: value` (bare) or `key: [a, b]` / `key:\\n - a\\n - b` (list). */\nfunction parseFrontMatter(text: string): Record<string, string | string[]> {\n const out: Record<string, string | string[]> = {};\n let key = \"\";\n for (const raw of text.split(\"\\n\")) {\n const listItem = /^\\s+-\\s+(.*)$/.exec(raw);\n if (listItem && key) {\n const cur = out[key];\n const item = listItem[1].trim().replace(/^[\"']|[\"']$/g, \"\");\n out[key] = Array.isArray(cur) ? [...cur, item] : [item];\n continue;\n }\n const kv = /^([A-Za-z0-9_-]+):\\s*(.*)$/.exec(raw);\n if (!kv) continue;\n key = kv[1];\n const val = kv[2].trim();\n if (!val) {\n out[key] = [];\n } else if (val.startsWith(\"[\") && val.endsWith(\"]\")) {\n out[key] = val\n .slice(1, -1)\n .split(\",\")\n .map((s) => s.trim().replace(/^[\"']|[\"']$/g, \"\"))\n .filter(Boolean);\n } else {\n out[key] = val.replace(/^[\"']|[\"']$/g, \"\");\n }\n }\n return out;\n}\n\n/** Parse an agent file's text (front matter + body). Throws on empty body. */\nexport function parseAgentFile(name: string, text: string, path: string): AgentDef {\n const m = /^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/.exec(text);\n if (!m) {\n throw new Error(\n `${path}: expected YAML front matter (--- … ---) before the agent body`\n );\n }\n const fm = parseFrontMatter(m[1]);\n const body = m[2].trim();\n if (!body) throw new Error(`${path}: the agent body (after the front matter) is empty`);\n const def: AgentDef = { name, body, path };\n if (typeof fm.model === \"string\" && fm.model) def.model = fm.model;\n if (Array.isArray(fm.tools) && fm.tools.length) def.tools = fm.tools;\n if (typeof fm.description === \"string\" && fm.description) def.description = fm.description;\n return def;\n}\n\n/** Load ~/.claude/agents/<name>.md; the error names the library when missing. */\nexport function loadAgent(name: string): AgentDef {\n const path = agentPath(name);\n let text: string;\n try {\n text = readFileSync(path, \"utf8\");\n } catch {\n throw new Error(\n `no agent named \"${name}\" at ${path}. ` +\n `The agent library lives in ~/.claude/agents/<name>.md and runs on workers.`\n );\n }\n return parseAgentFile(name, text, path);\n}\n\n/** Tier → task class: the cheap tier runs as simple, the middle as\n * implement, the top as complex. */\nconst TIER_CLASS: Record<ModelTier, string> = {\n haiku: \"simple\",\n sonnet: \"implement\",\n opus: \"complex\",\n};\n\n/** Which tier a model id belongs to: one of the CLI's tier aliases, or an\n * exact match against a configured provider's models (a modelTiers override\n * first, then fast → haiku tier, default → sonnet tier). null = no match. */\nexport function modelTier(\n model: string,\n providers?: Record<string, WorkerProvider>\n): ModelTier | null {\n const m = model.toLowerCase();\n if (m.includes(\"haiku\")) return \"haiku\";\n if (m.includes(\"sonnet\")) return \"sonnet\";\n if (m.includes(\"opus\")) return \"opus\";\n if (providers) {\n for (const p of Object.values(providers)) {\n const override = p.modelTiers?.[model];\n if (override) return override;\n if (p.models.fast === model) return \"haiku\";\n if (p.models.default === model) return \"sonnet\";\n }\n }\n return null;\n}\n\n/** Model ids already warned about — one line per id, not one per run. */\nconst unmatchedTierLogged = new Set<string>();\n\n/** Map an agent's front-matter model to a worker class. An id that matches\n * no tier is logged once and falls back to the middle tier's class rather\n * than dropping the hint silently. */\nexport function modelToClass(\n model: string | undefined,\n providers?: Record<string, WorkerProvider>\n): string | undefined {\n if (!model) return undefined;\n const tier = modelTier(model, providers);\n if (tier) return TIER_CLASS[tier];\n if (!unmatchedTierLogged.has(model)) {\n unmatchedTierLogged.add(model);\n console.error(\n `[agents] model \"${model}\" matches no known tier — ` +\n `falling back to the ${TIER_CLASS.sonnet} class.`\n );\n }\n return TIER_CLASS.sonnet;\n}\n\n/** Extra claude args the definition contributes (before the caller's own). */\nexport function agentClaudeArgs(def: AgentDef): string[] {\n const args: string[] = [];\n if (def.tools?.length) args.push(\"--allowedTools\", def.tools.join(\",\"));\n args.push(\"--append-system-prompt\", def.body);\n return args;\n}\n\n/** `<agent>: <first 50 chars of prompt>` — the default label for a run. */\nexport function agentLabel(name: string, prompt: string | null | undefined): string {\n return `${name}: ${prompt ?? UNLABELED}`.slice(0, name.length + 2 + 50);\n}\n","/**\n * `pai worker providers` and `pai worker classes` — configuration commands.\n *\n * The actual mutations live in src/workers/providers.ts so the MCP tools run\n * the same code. The CLI only parses options (and deliberately accepts no\n * inline API keys — keys enter via --key-file; the MCP `add` tool is the one\n * place a raw key is accepted, and it parks it in ~/.config/pai/keys itself).\n *\n * `pai worker roles` stays as an alias of `classes` over the same data (the\n * config key was renamed; old configs migrate on first write).\n */\n\nimport type { Command } from \"commander\";\nimport {\n PROVIDER_TAGS,\n WorkersConfigError,\n readWorkersSection,\n type ClassTarget,\n} from \"../../../workers/config.js\";\nimport { workersLogDir } from \"../../../workers/paths.js\";\nimport { testProvider } from \"../../../workers/run.js\";\nimport {\n addProvider,\n classTargetText,\n describeProviders,\n removeProvider,\n setClass,\n setProviderEnabled,\n unsetClass,\n updateProvider,\n useProvider,\n} from \"../../../workers/providers.js\";\nimport { ok, err, dim } from \"../../utils.js\";\n\nfunction fail(e: unknown): void {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(\"pai worker: \") + msg);\n process.exitCode = 1;\n}\n\n/** `--env K=V` (repeatable) → record. Throws on a value without \"=\". */\nfunction collectEnv(pairs: string[] | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n for (const p of pairs ?? []) {\n const eq = p.indexOf(\"=\");\n if (eq <= 0) {\n throw new WorkersConfigError(`--env expects NAME=VALUE, got \"${p}\"`);\n }\n out[p.slice(0, eq)] = p.slice(eq + 1);\n }\n return out;\n}\n\nfunction parseIntArg(v: string): number {\n const n = parseInt(v, 10);\n if (Number.isNaN(n) || n <= 0) {\n throw new WorkersConfigError(`expected a positive number, got \"${v}\"`);\n }\n return n;\n}\n\nfunction parseCostTier(v: string): number {\n const n = parseInt(v, 10);\n if (!Number.isInteger(n) || n < 1 || n > 5) {\n throw new WorkersConfigError(`cost tier must be an integer 1 (cheapest) … 5 (most expensive), got \"${v}\"`);\n }\n return n;\n}\n\n/** `--tags a,b` (repeatable) → validated tag list. */\nfunction collectTags(vals: string[] | undefined): string[] {\n const out: string[] = [];\n for (const v of vals ?? []) {\n for (const t of v.split(\",\").map((s) => s.trim()).filter(Boolean)) {\n if (!(PROVIDER_TAGS as readonly string[]).includes(t)) {\n throw new WorkersConfigError(`\"${t}\" is not a tag (from: ${PROVIDER_TAGS.join(\", \")})`);\n }\n if (!out.includes(t)) out.push(t);\n }\n }\n return out;\n}\n\ninterface ClassSetOpts {\n provider?: string;\n mcp?: string;\n maxCostTier?: number;\n requireTags?: string[];\n order?: string;\n}\n\n/** Positional target + constraint flags → a ClassTarget for setClass. */\nfunction buildClassTarget(target: string | undefined, opts: ClassSetOpts): ClassTarget {\n const hasConstraints =\n opts.provider !== undefined ||\n opts.mcp !== undefined ||\n opts.maxCostTier !== undefined ||\n opts.requireTags !== undefined ||\n opts.order !== undefined;\n if (target !== undefined && !hasConstraints) return target;\n const obj: Exclude<ClassTarget, string> = {};\n if (target !== undefined) obj.provider = target.split(\"/\")[0];\n if (opts.provider !== undefined) obj.provider = opts.provider;\n if (opts.mcp !== undefined) obj.mcp = opts.mcp.split(\",\").map((s) => s.trim()).filter(Boolean);\n if (opts.maxCostTier !== undefined) obj.maxCostTier = opts.maxCostTier;\n if (opts.requireTags !== undefined) obj.requireTags = opts.requireTags;\n if (opts.order !== undefined) obj.order = opts.order.split(\",\").map((s) => s.trim()).filter(Boolean);\n if (target !== undefined && target.includes(\"/\") && obj.provider) {\n // provider/alias in string form only; keep it out of the object\n const alias = target.split(\"/\")[1];\n if (alias && alias !== \"default\") {\n throw new WorkersConfigError(\n `a provider alias (\"${alias}\") cannot be combined with constraint flags — ` +\n `use the plain \"<target>\" form for pinning`\n );\n }\n }\n if (!Object.keys(obj).length) {\n throw new WorkersConfigError(\"give a target (provider, provider/fast) or a constraint (--max-cost-tier, --require-tags, …)\");\n }\n return obj;\n}\n\nexport function registerWorkerProviderCommands(providersCmd: Command): void {\n providersCmd\n .description(\"Providers: list (default), add, remove, use, enable, disable, test\")\n .action(() => {\n const { workers } = readWorkersSection();\n console.log();\n for (const line of describeProviders(workers)) console.log(` ${line}`);\n console.log();\n console.log(dim(` workers are ${workers.enabled ? \"on\" : \"off\"} — pai worker ${workers.enabled ? \"off\" : \"on\"}`));\n console.log();\n });\n\n providersCmd\n .command(\"add <name>\")\n .description(\n \"Add a provider; the first one also turns workers on and seeds classes.\\n\" +\n \"Example: pai worker providers add glm --base-url https://…/anthropic \\\\\\n\" +\n \" --key-file ~/.config/zai/api_key --model glm-5.3 --fast-model glm-5.3-flash\\n\" +\n \"OpenAI-protocol: --protocol openai --upstream-url https://…/v1 (runs via the PAI proxy).\\n\" +\n \"Codex (ChatGPT plan): --engine codex — runs through the Codex CLI.\"\n )\n .option(\"--base-url <url>\", \"Anthropic-compatible API base URL (required unless --protocol openai)\")\n .requiredOption(\"--model <model>\", \"Default model id for this provider\")\n .option(\"--key-file <path>\", \"File holding the API token (0600); omit for token \\\"local\\\"\")\n .option(\"--fast-model <model>\", \"Cheaper model for spotchecks and routing\")\n .option(\"--env <name=value>\", \"Extra env for runs (repeatable)\", (v: string, acc: string[]) => [...acc, v], [] as string[])\n .option(\"--note <text>\", \"Human note shown in `providers list`\")\n .option(\"--protocol <proto>\", \"anthropic (default) or openai — openai runs through the local PAI proxy\")\n .option(\"--upstream-url <url>\", \"Chat Completions base URL (required for --protocol openai)\")\n .option(\"--engine <engine>\", \"claude (default) or codex — codex runs `codex exec --json`\")\n .option(\"--context-window <tokens>\", \"Context window for the meter (default 200000; init event overrides)\", parseIntArg)\n .option(\"--quota-probe <url>\", \"URL whose JSON first number is the quota percent (0-100)\")\n .option(\"--cost-tier <1-5>\", \"Cost tier 1 (cheapest) … 5 (most expensive; default 3)\", parseCostTier)\n .option(\"--tags <tags>\", \"Capability tags, comma-separated (from: \" + PROVIDER_TAGS.join(\", \") + \")\", (v: string, acc: string[]) => [...acc, v], [] as string[])\n .action(\n (\n name: string,\n opts: {\n baseUrl?: string;\n model: string;\n keyFile?: string;\n fastModel?: string;\n env?: string[];\n note?: string;\n protocol?: string;\n upstreamUrl?: string;\n engine?: string;\n contextWindow?: number;\n quotaProbe?: string;\n costTier?: number;\n tags?: string[];\n }\n ) => {\n try {\n const protocol = opts.protocol as \"anthropic\" | \"openai\" | undefined;\n const engine = opts.engine as \"claude\" | \"codex\" | undefined;\n if (protocol && protocol !== \"anthropic\" && protocol !== \"openai\") {\n throw new WorkersConfigError(`--protocol must be anthropic or openai, got \"${protocol}\"`);\n }\n if (engine && engine !== \"claude\" && engine !== \"codex\") {\n throw new WorkersConfigError(`--engine must be claude or codex, got \"${engine}\"`);\n }\n if (protocol !== \"openai\" && !opts.baseUrl) {\n throw new WorkersConfigError(\"--base-url is required (only --protocol openai goes without it)\");\n }\n const tags = collectTags(opts.tags);\n addProvider({\n name,\n baseUrl: opts.baseUrl ?? \"\",\n keyFile: opts.keyFile ?? null,\n model: opts.model,\n fastModel: opts.fastModel,\n env: collectEnv(opts.env),\n note: opts.note,\n ...(protocol ? { protocol } : {}),\n ...(opts.upstreamUrl ? { upstreamUrl: opts.upstreamUrl } : {}),\n ...(engine ? { engine } : {}),\n ...(opts.contextWindow ? { contextWindow: opts.contextWindow } : {}),\n quotaProbe: opts.quotaProbe,\n ...(opts.costTier !== undefined ? { costTier: opts.costTier } : {}),\n ...(tags.length ? { tags } : {}),\n });\n const { workers } = readWorkersSection();\n console.log(ok(`provider ${name} added`));\n if (workers.active === name) console.log(` active: ${name} — workers ${workers.enabled ? \"on\" : \"off\"}`);\n for (const line of describeProviders(workers)) console.log(dim(` ${line}`));\n } catch (e) {\n fail(e);\n }\n }\n );\n\n providersCmd\n .command(\"update <name>\")\n .description(\"Change cost tier and tags of a provider (routing constraints use these)\")\n .option(\"--cost-tier <1-5>\", \"Cost tier 1 (cheapest) … 5 (most expensive)\", parseCostTier)\n .option(\"--tags <tags>\", \"Capability tags, comma-separated (from: \" + PROVIDER_TAGS.join(\", \") + \"); --tags '' clears\", (v: string, acc: string[]) => [...acc, v], [] as string[])\n .action(\n (\n name: string,\n opts: { costTier?: number; tags?: string[] }\n ) => {\n try {\n if (opts.costTier === undefined && opts.tags === undefined) {\n throw new WorkersConfigError(\"nothing to update — pass --cost-tier and/or --tags\");\n }\n const tags = opts.tags === undefined ? undefined : collectTags(opts.tags);\n updateProvider(name, {\n ...(opts.costTier !== undefined ? { costTier: opts.costTier } : {}),\n ...(tags !== undefined ? { tags } : {}),\n });\n console.log(ok(`provider ${name} updated`));\n const { workers } = readWorkersSection();\n for (const line of describeProviders(workers)) console.log(dim(` ${line}`));\n } catch (e) {\n fail(e);\n }\n }\n );\n\n providersCmd\n .command(\"remove <name>\")\n .description(\"Remove a provider and any classes pointing at it\")\n .action((name: string) => {\n try {\n removeProvider(name);\n console.log(ok(`provider ${name} removed`));\n } catch (e) {\n fail(e);\n }\n });\n\n providersCmd\n .command(\"use <name>\")\n .description(\"Make this provider the active one for runs without --provider/--class\")\n .action((name: string) => {\n try {\n useProvider(name);\n console.log(ok(`active provider: ${name}`));\n } catch (e) {\n fail(e);\n }\n });\n\n providersCmd\n .command(\"enable <name>\")\n .description(\"Enable a provider (also clears its cooldown)\")\n .action((name: string) => {\n try {\n setProviderEnabled(name, true);\n console.log(ok(`provider ${name} enabled`));\n } catch (e) {\n fail(e);\n }\n });\n\n providersCmd\n .command(\"disable <name>\")\n .description(\"Disable a provider (auto-routing skips it; --provider still works)\")\n .action((name: string) => {\n try {\n setProviderEnabled(name, false);\n console.log(ok(`provider ${name} disabled`));\n } catch (e) {\n fail(e);\n }\n });\n\n providersCmd\n .command(\"test [name]\")\n .description(\"One-word pong probe through a provider (default: the active one)\")\n .action(async (name: string | undefined) => {\n try {\n const { workers } = readWorkersSection();\n const pname = name ?? workers.active ?? \"\";\n if (!pname || !workers.providers[pname]) {\n throw new WorkersConfigError(\n name ? `no provider named \"${name}\"` : \"no active provider — name one explicitly\"\n );\n }\n console.log(dim(` probing ${pname} (${workers.providers[pname].models.default}) …`));\n const r = await testProvider(pname, workers.providers[pname], workersLogDir(workers));\n const head = `${r.provider} ${r.model} ${(r.latencyMs / 1000).toFixed(1)}s`;\n if (r.skipped) {\n // e.g. engine codex without the CLI installed: report, do not fail\n console.log(dim(` ${head} ${r.skipped}`));\n console.log(` reply: ${r.result.slice(0, 200)}`);\n return;\n }\n console.log(r.ok ? ok(` ${head}`) : err(` ${head}`));\n console.log(` reply: ${r.result.slice(0, 200)}`);\n process.exitCode = r.ok ? 0 : 1;\n } catch (e) {\n fail(e);\n }\n });\n\n // no classes here — they hang directly off `pai worker classes`\n}\n\nfunction printClasses(): void {\n const { workers } = readWorkersSection();\n const entries = Object.entries(workers.classes);\n if (!entries.length) {\n console.log(dim(\" no classes set — runs use the active provider (or auto routing)\"));\n return;\n }\n for (const [cls, target] of entries) console.log(` ${cls.padEnd(12)} ${classTargetText(target)}`);\n}\n\nfunction registerClassSubcommands(classesCmd: Command): void {\n classesCmd\n .command(\"list\")\n .description(\"List classes and their targets (default action)\")\n .action(() => printClasses());\n\n classesCmd\n .command(\"set <class> [target]\")\n .description(\n \"Point a class at a provider (or provider/fast), or give only constraints:\\n\" +\n \"classes set research --max-cost-tier 2 --require-tags long-context,reasoning\"\n )\n .option(\"--provider <name>\", \"Pin the class to this provider (object form)\")\n .option(\"--mcp <names>\", \"MCP servers/sets for runs of this class (comma-separated)\")\n .option(\"--max-cost-tier <1-5>\", \"Auto-routing considers only providers up to this cost tier\", parseCostTier)\n .option(\"--require-tags <tags>\", \"Auto-routing needs these tags (comma-separated)\", (v: string) =>\n v.split(\",\").map((s) => s.trim()).filter(Boolean)\n )\n .option(\"--order <providers>\", \"Per-class routing order overriding workers.routing.order (comma-separated)\")\n .action((cls: string, target: string | undefined, opts: ClassSetOpts) => {\n try {\n const built = buildClassTarget(target, opts);\n setClass(cls, built);\n console.log(ok(`class ${cls} → ${classTargetText(built)}`));\n } catch (e) {\n fail(e);\n }\n });\n\n classesCmd\n .command(\"unset <class>\")\n .description(\"Remove a class (runs then use the active provider)\")\n .action((cls: string) => {\n try {\n unsetClass(cls);\n console.log(ok(`class ${cls} removed`));\n } catch (e) {\n fail(e);\n }\n });\n}\n\n/** `pai worker classes` — class → target assignment (roles is the old name). */\nexport function registerWorkerClassCommands(workerCmd: Command): void {\n const classesCmd = workerCmd\n .command(\"classes\")\n .description(\"Classes: which provider serves draft / implement / review / …\")\n .action(() => printClasses());\n registerClassSubcommands(classesCmd);\n\n // `roles` — the pre-classes name; same data, kept for muscle memory\n const rolesCmd = workerCmd\n .command(\"roles\", { hidden: true })\n .description(\"Alias of `classes` (roles was renamed to classes)\")\n .action(() => printClasses());\n registerClassSubcommands(rolesCmd);\n}\n","/**\n * `pai worker model` — the model ids a provider runs on, per capability.\n *\n * Reads and writes workers.providers.<name>.models.<capability> (default,\n * fast, image, …) through the same functions the MCP worker_model tool uses,\n * so CLI and chat agree on what a set means.\n */\n\nimport type { Command } from \"commander\";\nimport {\n WorkersConfigError,\n isModelCapability,\n readWorkersSection,\n type ModelCapability,\n} from \"../../../workers/config.js\";\nimport {\n describeModels,\n modelPrefsText,\n resolveProviderName,\n setProviderModel,\n} from \"../../../workers/providers.js\";\nimport { ok, err, dim } from \"../../utils.js\";\n\nfunction fail(e: unknown): void {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(\"pai worker: \") + msg);\n process.exitCode = 1;\n}\n\nexport function registerWorkerModelCommand(workerCmd: Command): void {\n workerCmd\n .command(\"model [what] [model]\")\n .description(\n \"Model ids per provider: no args lists them,\\n\" +\n \"`model <model-id>` sets the active provider's default model (back-compat),\\n\" +\n \"`model <capability>` shows one capability, `model <capability> <model-id>` sets it.\\n\" +\n `Capabilities: default, fast, image. --provider targets another provider.`\n )\n .option(\"--provider <name>\", \"Provider to read or change (default: the active one)\")\n .action((what: string | undefined, model: string | undefined, opts: { provider?: string }) => {\n try {\n const { workers } = readWorkersSection();\n if (what === undefined) {\n for (const line of describeModels(workers)) console.log(` ${line}`);\n return;\n }\n let capability: ModelCapability = \"default\";\n let id = what;\n if (isModelCapability(what)) {\n capability = what;\n if (model === undefined) {\n const name = resolveProviderName(workers, opts.provider);\n const p = workers.providers[name];\n if (p) console.log(` ${name} ${capability} ${p.models[capability] ?? \"(none)\"}`);\n return;\n }\n id = model;\n } else if (model !== undefined) {\n throw new WorkersConfigError(\n `unexpected second argument \"${model}\" — usage: model [<capability>] <model-id>`\n );\n }\n const name = resolveProviderName(workers, opts.provider);\n const fresh = setProviderModel(name, capability, id);\n const p = fresh.providers[name];\n if (p) {\n console.log(ok(`${name} ${capability} model: ${p.models[capability]}`));\n console.log(dim(` ${modelPrefsText(p)}`));\n }\n } catch (e) {\n fail(e);\n }\n });\n}\n","/**\n * wait.ts — `pai worker wait <id...>`: the deterministic poller.\n *\n * A script, not guidance: polls each worker's status file until every one is\n * terminal (done/failed/killed/lost), then returns their result texts. The\n * orchestrator's Bash call stays synchronous and gets the JSON on stdout,\n * with exit 1 naming whoever failed or timed out.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { eventsPath } from \"./paths.js\";\nimport { parseWorkerReport, type WorkerReport } from \"./report.js\";\nimport { loadStatus, type WorkerStatus } from \"./status.js\";\n\nexport interface WaitResult {\n id: string;\n label: string;\n state: WorkerStatus[\"state\"];\n rc: number | null;\n secs: number | null;\n ok: boolean;\n /** The `result` text of the last result event in the worker's transcript. */\n result: string | null;\n report: WorkerReport | null;\n}\n\nexport interface WaitOptions {\n /** Give up after this long; default 15 min. */\n timeoutMs?: number;\n /** Poll interval; default 2 s. */\n intervalMs?: number;\n}\n\nconst TERMINAL: readonly WorkerStatus[\"state\"][] = [\"done\", \"failed\", \"killed\", \"lost\"];\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * The `result` text of the last result event in the worker's `<id>.jsonl`,\n * scanning the file backwards; null when the file or the event is absent.\n */\nfunction lastResultText(logDir: string, id: string): string | null {\n let raw: string;\n try {\n raw = readFileSync(eventsPath(logDir, id), \"utf8\");\n } catch {\n return null;\n }\n const lines = raw.split(\"\\n\");\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i].trim();\n if (!line.startsWith(\"{\")) continue;\n try {\n const e = JSON.parse(line) as { type?: string; result?: unknown };\n if (e.type === \"result\" && typeof e.result === \"string\") return e.result;\n } catch {\n // a torn line is skipped, not fatal\n }\n }\n return null;\n}\n\nfunction toWaitResult(logDir: string, st: WorkerStatus): WaitResult {\n const result = lastResultText(logDir, st.id);\n return {\n id: st.id,\n label: st.label,\n state: st.state,\n rc: st.rc,\n secs: st.secs,\n ok: st.state === \"done\" && (st.rc ?? 0) === 0,\n result,\n report: parseWorkerReport(result ?? \"\"),\n };\n}\n\n/**\n * Wait until every named worker reaches a terminal state. Unknown ids throw\n * immediately. On timeout nothing is thrown: the return carries what is known\n * and `timedOut` names the still-pending ids.\n */\nexport async function waitWorkers(\n logDir: string,\n ids: string[],\n opts: WaitOptions = {}\n): Promise<{ results: WaitResult[]; timedOut: string[] }> {\n const timeoutMs = opts.timeoutMs ?? 900_000;\n const intervalMs = opts.intervalMs ?? 2_000;\n const known = new Map<string, WorkerStatus>();\n const pending = new Set<string>();\n // the pre-sleep check: already-finished workers return without waiting\n for (const id of ids) {\n const st = loadStatus(logDir, id);\n if (!st) throw new Error(`no worker named \"${id}\"`);\n known.set(id, st);\n if (!TERMINAL.includes(st.state)) pending.add(id);\n }\n const deadline = Date.now() + timeoutMs;\n while (pending.size > 0 && Date.now() < deadline) {\n await sleep(Math.min(intervalMs, Math.max(1, deadline - Date.now())));\n for (const id of [...pending]) {\n const st = loadStatus(logDir, id);\n if (st) known.set(id, st); // a vanished file keeps the last known status\n if (!st || TERMINAL.includes(known.get(id)!.state)) pending.delete(id);\n }\n }\n return { results: ids.map((id) => toWaitResult(logDir, known.get(id)!)), timedOut: [...pending] };\n}\n","/**\n * `pai worker` — the CLI face of the worker system.\n *\n * Thin: every command here is a wrapper over src/workers/*, which is also\n * what the MCP tools and the Agent hook call. Nothing in this file decides\n * routing, providers or rendering on its own.\n *\n * Layout mirrors the old glm tooling so the habits transfer:\n *\n * pai worker run … (glm / glm-run)\n * pai worker ps (glm-ps)\n * pai worker follow (glm-ps follow / watch)\n * pai worker replay <id> (glm-ps <id>)\n * pai worker pane (glm-ps pane)\n * pai worker log (glm-log)\n *\n * Plus the operator surface: say/resume for talking to workers, proxy for the\n * local Anthropic↔OpenAI translation, mcp for the allowlist workers may load.\n */\n\nimport type { Command } from \"commander\";\nimport { spawn } from \"node:child_process\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { readWorkersSection, type WorkerProvider } from \"../../../workers/config.js\";\nimport { workersLogDir, eventsPath, ledgerPath } from \"../../../workers/paths.js\";\nimport { parseRunnerArgs } from \"../../../workers/args.js\";\nimport { runWorker } from \"../../../workers/run.js\";\nimport { runChain } from \"../../../workers/chain.js\";\nimport { agentClaudeArgs, agentLabel, loadAgent, modelToClass } from \"../../../workers/agents.js\";\nimport { followWorkers, psOutput, replayOutput, statusLineOutput } from \"../../../workers/viewer.js\";\nimport { openFollowPane, openPaneForWorker, checkPaneForWorker } from \"../../../workers/pane.js\";\nimport { installWorkers } from \"../../../workers/install.js\";\nimport { setWorkersEnabled } from \"../../../workers/providers.js\";\nimport { fallbackOn, fallbackOff, fallbackStatus, fallbackStatusText } from \"../../../workers/fallback.js\";\nimport { registerWorkerProviderCommands, registerWorkerClassCommands } from \"./providers.js\";\nimport { registerWorkerModelCommand } from \"./model.js\";\nimport { loadStatus } from \"../../../workers/status.js\";\nimport { sayToWorker } from \"../../../workers/operator.js\";\nimport { handoffFromInside } from \"../../../workers/handoff.js\";\nimport { discardWorker, mergeWorker } from \"../../../workers/worktree.js\";\nimport { waitWorkers } from \"../../../workers/wait.js\";\nimport { describeMcp } from \"../../../workers/mcp.js\";\nimport { DEFAULT_PROXY_PORT, ensureProxyRunning, stopProxy } from \"../../../workers/proxy/server.js\";\nimport { err, dim } from \"../../utils.js\";\n\nfunction currentLogDir(): string {\n const { workers } = readWorkersSection();\n return workersLogDir(workers);\n}\n\nfunction fail(e: unknown): void {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(\"pai worker: \") + msg);\n process.exitCode = 1;\n}\n\nexport function registerWorkerCommands(workerCmd: Command): void {\n workerCmd\n .command(\"run\")\n .description(\n \"Run one claude-code worker through the configured provider.\\n\" +\n \"Unknown options are passed to claude verbatim (e.g. -p, --allowedTools);\\n\" +\n \"--output-format/--verbose are handled here.\\n\" +\n \"--chain draft,implement[,review] runs a spec-first pipeline;\\n\" +\n \"--agent <name> runs an agent definition from ~/.claude/agents.\"\n )\n .allowUnknownOption(true)\n .option(\"--provider <name>\", \"Provider to run on (default: active, else routing order)\")\n .option(\"--class <name>\", \"Use the provider of this class (draft, implement, review, research, spotcheck, simple, complex, image)\")\n .option(\"--role <name>\", \"Alias of --class (roles were renamed to classes)\")\n .option(\"--chain <stages>\", \"Comma-separated stage classes, e.g. draft,implement or draft,implement,review\")\n .option(\"--agent <name>\", \"Run the agent definition ~/.claude/agents/<name>.md on a worker\")\n .option(\"--model <model>\", \"Override the provider's model for this run\")\n .option(\"--label <text>\", \"Short task label shown in ps / follow / status line\")\n .option(\"--mcp <names>\", \"MCP servers/sets this worker may use (comma-separated; see `pai worker mcp`)\")\n .option(\"--no-pane\", \"Do not open a follow pane for this worker\")\n .option(\"--worktree\", \"Run in a git worktree on branch worker/<id> (default for implement/complex/plan in a git repo)\")\n .option(\"--no-worktree\", \"Run in place, no worktree\")\n .argument(\"[args...]\", \"claude arguments, e.g. -p '<task>' --allowedTools 'Read,Edit,Bash'\")\n .action(\n async (\n args: string[],\n opts: {\n provider?: string;\n class?: string;\n role?: string;\n chain?: string;\n agent?: string;\n model?: string;\n label?: string;\n mcp?: string;\n pane?: boolean;\n worktree?: boolean;\n }\n ) => {\n try {\n const className = opts.class ?? opts.role;\n let claudeArgs = args;\n let label = opts.label;\n let agentClass: string | undefined;\n if (opts.agent) {\n // agent definition: system prompt + tools come from the file, the\n // model maps to a class, the label defaults to \"<agent>: <prompt>\"\n const def = loadAgent(opts.agent);\n claudeArgs = [...agentClaudeArgs(def), ...args];\n // provider models map into the tier table so routing survives\n // non-Anthropic ids; an unreadable config still resolves aliases\n let agentProviders: Record<string, WorkerProvider> | undefined;\n try {\n agentProviders = readWorkersSection().workers.providers;\n } catch {\n agentProviders = undefined;\n }\n agentClass = modelToClass(def.model, agentProviders);\n if (!label) label = agentLabel(opts.agent, parseRunnerArgs(args).prompt);\n }\n if (opts.chain) {\n const brief = parseRunnerArgs(claudeArgs).prompt;\n if (!brief) {\n throw new Error(\"--chain needs the task as -p '<brief>'\");\n }\n const rc = await runChain({\n stages: opts.chain.split(\",\"),\n className: className ?? agentClass,\n providerFlag: opts.provider,\n modelFlag: opts.model,\n label,\n noPane: opts.pane === false,\n mcpFlag: opts.mcp,\n brief,\n claudeArgs,\n });\n process.exitCode = rc;\n return;\n }\n const rc = await runWorker({\n providerFlag: opts.provider,\n className: className ?? agentClass,\n modelFlag: opts.model,\n label,\n mcpFlag: opts.mcp,\n noPane: opts.pane === false,\n worktreeFlag: opts.worktree,\n claudeArgs,\n });\n process.exitCode = rc;\n } catch (e) {\n fail(e);\n }\n }\n );\n\n workerCmd\n .command(\"ps\")\n .description(\"List workers of this session (RUNNING + FINISHED); --all for every worker\")\n .option(\"--all\", \"Show workers of all sessions, not just this terminal's\")\n .action((opts: { all?: boolean }) => {\n try {\n console.log(psOutput(currentLogDir(), opts.all === true));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"follow [id]\")\n .description(\"Live transcript of one worker, or of this session's running workers\")\n .option(\"--all\", \"Follow workers of all sessions\")\n .option(\"--auto-exit [secs]\", \"Exit secs after the workers end (pane mode; default from config)\")\n .action(async (id: string | undefined, opts: { all?: boolean; autoExit?: string | boolean }) => {\n try {\n const { workers } = readWorkersSection();\n const autoExit =\n opts.autoExit === undefined || opts.autoExit === false\n ? 0\n : opts.autoExit === true\n ? workers.pane.autoExitSecs\n : Number(opts.autoExit);\n await followWorkers(currentLogDir(), id ?? null, opts.all === true, autoExit);\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"replay <id>\")\n .description(\"Print the transcript of one finished or running worker\")\n .option(\"--tail <n>\", \"Only the last n rendered lines\", parseIntArg)\n .action((id: string, opts: { tail?: number }) => {\n try {\n console.log(replayOutput(currentLogDir(), id, process.stdout.isTTY === true, opts.tail));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"watch\")\n .description(\"ps refreshed every 2 seconds (plain `watch`, colors kept)\")\n .action(() => {\n const proc = spawn(\"watch\", [\"-n\", \"2\", \"pai worker ps --all\"], { stdio: \"inherit\" });\n proc.on(\"error\", (e) => fail(new Error(`cannot run watch: ${e.message}`)));\n proc.on(\"close\", (code) => {\n process.exitCode = code ?? 1;\n });\n });\n\n workerCmd\n .command(\"pane [id]\")\n .description(\"Open the follow pane for a worker (or one shared pane for this session)\")\n .option(\"--check\", \"Only report whether the pane is open, plus the profile file's path, font, and the hosting window's bounds\")\n .action(async (id: string | undefined, opts: { check?: boolean }) => {\n try {\n const { workers } = readWorkersSection();\n const logDir = currentLogDir();\n const term = process.env.ITERM_SESSION_ID ?? \"\";\n if (id) {\n const msg = opts.check\n ? await checkPaneForWorker(id, workers.pane.fontSize, term)\n : await openPaneForWorker(logDir, workers, id, term);\n console.log(msg);\n } else {\n console.log(await openFollowPane(logDir, workers, term, opts.check === true));\n }\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"log [what]\")\n .description(\"all = ledger, tail = last ledger lines, <id> = raw event stream, none = list\")\n .action((what: string | undefined) => {\n try {\n const logDir = currentLogDir();\n const ledger = ledgerPath(logDir);\n if (what === \"all\") {\n console.log(existsSync(ledger) ? readFileSync(ledger, \"utf8\") : dim(\"(no ledger yet)\"));\n return;\n }\n if (what === \"tail\") {\n if (!existsSync(ledger)) {\n console.log(dim(\"(no ledger yet)\"));\n return;\n }\n const lines = readFileSync(ledger, \"utf8\").trim().split(\"\\n\");\n console.log(lines.slice(-20).join(\"\\n\"));\n return;\n }\n if (what) {\n const path = eventsPath(logDir, what);\n if (!existsSync(path)) {\n fail(new Error(`no event log for ${what}`));\n return;\n }\n const lines = readFileSync(path, \"utf8\").trim().split(\"\\n\");\n console.log(lines.slice(-30).join(\"\\n\"));\n return;\n }\n if (!existsSync(logDir)) {\n console.log(dim(\"(no workers yet)\"));\n return;\n }\n for (const f of readdirSync(logDir).filter((x) => x.endsWith(\".jsonl\")).sort()) {\n const st = statSync(`${logDir}/${f}`);\n const mb = (st.size / 1024 / 1024).toFixed(1);\n console.log(` ${f.replace(\".jsonl\", \"\")} ${mb} MB ${st.mtime.toISOString().slice(0, 19).replace(\"T\", \" \")}`);\n }\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"say <id> <text>\")\n .description(\"Send one message to a running worker (forwarded to its open stdin)\")\n .action(async (id: string, text: string) => {\n try {\n await sayToWorker(currentLogDir(), id, text);\n console.log(\"ok\");\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"handoff <json>\")\n .description(\n \"From inside a worker: append a handoff to the parent's inbox and (when it runs) say it to the parent.\\n\" +\n 'Payload: {\"kind\":\"proposal|question|blocker\",\"text\":\"…\",\"data\":{…}} — from/to come from the environment.'\n )\n .action(async (json: string) => {\n try {\n let payload: unknown;\n try {\n payload = JSON.parse(json);\n } catch {\n fail(new Error(`payload is not JSON: ${json}`));\n return;\n }\n const h = await handoffFromInside(currentLogDir(), process.env, payload);\n console.log(`ok → ${h.to} (${h.kind}); inbox ${h.to}.inbox.jsonl`);\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"merge <id>\")\n .description(\"Merge a worker's worktree branch (worker/<id>) into the original checkout, then remove the worktree and delete the branch\")\n .action((id: string) => {\n try {\n console.log(mergeWorker(currentLogDir(), id));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"wait <ids...>\")\n .description(\"Poll workers until they finish; prints each result as one JSON line, exit 1 on failure or timeout\")\n .option(\"--timeout <secs>\", \"Give up after this many seconds (default 900)\", parseIntArg)\n .action(async (ids: string[], opts: { timeout?: number }) => {\n try {\n const { results, timedOut } = await waitWorkers(currentLogDir(), ids, {\n timeoutMs: (opts.timeout ?? 900) * 1000,\n });\n for (const r of results) console.log(JSON.stringify(r));\n if (timedOut.length) {\n console.error(err(`pai worker wait: timed out waiting for ${timedOut.join(\", \")}`));\n process.exitCode = 1;\n return;\n }\n const failed = results.filter((r) => !r.ok).map((r) => r.id);\n if (failed.length) {\n console.error(err(`pai worker wait: failed: ${failed.join(\", \")}`));\n process.exitCode = 1;\n }\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"discard <id>\")\n .description(\"Drop a worker's worktree and branch, keeping nothing\")\n .action((id: string) => {\n try {\n console.log(discardWorker(currentLogDir(), id));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"controls <id> <who>\")\n .description(\n \"Hand the desktop controls (clickr) to a worker or take them back.\\n\" +\n \"<who> is `you` (the worker may actuate) or `me` (the operator keeps them);\\n\" +\n \"inside a worker's pane, typing \\\"your controls\\\" does the same.\"\n )\n .action((id: string, who: string) => {\n try {\n if (who !== \"you\" && who !== \"me\") {\n fail(new Error(`<who> must be \"you\" or \"me\"`));\n return;\n }\n if (!loadStatus(currentLogDir(), id)) {\n fail(new Error(`no worker named \"${id}\"`));\n return;\n }\n const proc = spawn(\"clickr\", [\"controls\", who], { stdio: \"inherit\" });\n proc.on(\"error\", (e) =>\n fail(new Error(`cannot run clickr controls: ${e.message} (is clickr installed?)`))\n );\n proc.on(\"close\", (code) => {\n if (code === 0) console.log(`controls → ${who === \"you\" ? id : \"operator\"}`);\n process.exitCode = code ?? 1;\n });\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"resume <id> <text>\")\n .description(\"Continue a finished worker on the same provider: claude --resume <session>\")\n .option(\"--print-id\", \"Print the new worker id on its own line (pane follow handoff)\")\n .option(\"--no-pane\", \"Do not open a follow pane for the resumed worker\")\n .action(async (id: string, text: string, opts: { printId?: boolean; pane?: boolean }) => {\n try {\n const logDir = currentLogDir();\n const old = loadStatus(logDir, id);\n if (!old) {\n fail(new Error(`no worker named \"${id}\"`));\n return;\n }\n if (!old.claudeSession) {\n fail(\n new Error(\n `worker ${id} recorded no Claude session id — it predates resume support ` +\n `or ran through an engine that does not expose one`\n )\n );\n return;\n }\n let newId = \"\";\n const rc = await runWorker({\n providerFlag: old.provider,\n modelFlag: old.model,\n label: `↩ ${old.label}`,\n noPane: opts.pane === false,\n claudeArgs: [\"--resume\", old.claudeSession, \"-p\", text],\n onWorkerStart: (wid) => {\n newId = wid;\n },\n });\n if (opts.printId === true && newId) console.log(newId);\n process.exitCode = rc;\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"proxy [stop]\")\n .description(\n \"The local Anthropic↔OpenAI proxy (loopback only); started on demand by `run`,\\n\" +\n \"`proxy` starts/verifies it, `proxy stop` stops it again\"\n )\n .option(\"--port <n>\", `Port to listen on (default ${DEFAULT_PROXY_PORT})`, parseIntArg)\n .action(async (stop: string | undefined, opts: { port?: number }) => {\n try {\n const logDir = currentLogDir();\n if (stop === \"stop\") {\n console.log(stopProxy(logDir));\n return;\n }\n if (stop) {\n fail(new Error(`unknown proxy argument \"${stop}\" (expected: stop)`));\n return;\n }\n const url = await ensureProxyRunning(opts.port ?? DEFAULT_PROXY_PORT, logDir);\n console.log(`proxy listening on ${url} — point openai providers at ${url}/<provider>`);\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"mcp [list]\")\n .description(\"MCP servers workers may load via --mcp / roles, and the configured sets\")\n .action((what: string | undefined) => {\n if (what && what !== \"list\") {\n fail(new Error(`unknown mcp argument \"${what}\" (expected: list)`));\n return;\n }\n try {\n const { workers } = readWorkersSection();\n console.log(describeMcp(workers));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"status-line [term] [cwd] [session]\")\n .description(\n \"One-line worker summary for a status bar (empty when none in scope).\\n\" +\n \"Called by statusline-command.sh with ITERM_SESSION_ID, the pane cwd and\\n\" +\n \"the claude session id (claims workers spawned by that session's Bash).\"\n )\n .action((term: string | undefined, cwd: string | undefined, session: string | undefined) => {\n try {\n const out = statusLineOutput(\n currentLogDir(),\n term ?? process.env.ITERM_SESSION_ID ?? \"\",\n cwd ?? process.cwd(),\n session ?? \"\"\n );\n if (out) console.log(out);\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"on\")\n .description(\"Route Agent-tool subagents to workers (default when a provider exists)\")\n .action(() => {\n try {\n setWorkersEnabled(true);\n console.log(\"workers on — the Agent hook now denies and suggests `pai worker run`\");\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"off\")\n .description(\"Stop routing: Agent tool runs on Anthropic again\")\n .action(() => {\n try {\n setWorkersEnabled(false);\n console.log(\"workers off — Agent-tool subagents run on Anthropic\");\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"fallback [action] [provider]\")\n .description(\n \"Machine-wide fallback: every NEW Claude Code process runs on a worker\\n\" +\n \"provider (settings.json env + model pin) until switched back.\\n\" +\n \"on [provider] switches (default: active), off restores settings.json\\n\" +\n \"exactly, status shows state and running sessions. CLAUDE_SETTINGS_PATH\\n\" +\n \"points at another settings.json for dry runs.\"\n )\n .action((action: string | undefined, provider: string | undefined) => {\n try {\n if (action === undefined || action === \"status\") {\n console.log(fallbackStatusText(fallbackStatus()).join(\"\\n\"));\n return;\n }\n if (action === \"on\") {\n const r = fallbackOn(provider);\n console.log(\n r.alreadyOn\n ? `fallback already on — provider ${r.provider}, env re-applied`\n : `fallback on — provider ${r.provider}; every new Claude Code process uses it`\n );\n console.log(` settings.json env: ${r.envKeys.join(\", \")}`);\n console.log(` model pin: ${r.model}`);\n console.log(\"running sessions keep their current provider until restarted (see: pai worker fallback status)\");\n return;\n }\n if (action === \"off\") {\n const r = fallbackOff();\n console.log(`fallback off — provider ${r.provider} released, settings.json restored`);\n console.log(` restored keys: ${r.envKeys.join(\", \")}`);\n return;\n }\n fail(new Error(`unknown fallback action \"${action}\" (expected: on, off, status)`));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"install\")\n .description(\"Migrate: Agent hook in settings.json, ~/.local/bin glm* shims, old script cleanup\")\n .action(() => {\n try {\n const r = installWorkers();\n for (const line of r.lines) console.log(` ${line}`);\n } catch (e) {\n fail(e);\n }\n });\n\n const providersCmd = workerCmd\n .command(\"providers\")\n .description(\"Providers: list, add, remove, use, enable, disable, test\");\n\n registerWorkerProviderCommands(providersCmd);\n registerWorkerClassCommands(workerCmd);\n registerWorkerModelCommand(workerCmd);\n}\n\nfunction parseIntArg(v: string): number {\n const n = parseInt(v, 10);\n return Number.isNaN(n) ? 0 : n;\n}\n","/**\n * Shared types and constants for the session-cleanup command.\n */\n\nexport interface ProjectRow {\n id: number;\n slug: string;\n display_name: string;\n root_path: string;\n encoded_dir: string;\n claude_notes_dir: string | null;\n}\n\nexport interface SessionRow {\n id: number;\n project_id: number;\n number: number;\n date: string;\n slug: string;\n title: string;\n filename: string;\n status: string;\n}\n\nexport type SessionClassification = \"EMPTY\" | \"UNNAMED\" | \"NAMED\" | \"LEGACY_FORMAT\";\n\nexport interface SessionCandidate {\n session: SessionRow | null; // null if file exists on disk but not in DB\n filename: string;\n filepath: string;\n sizeBytes: number;\n classification: SessionClassification;\n autoName?: string; // proposed name for UNNAMED sessions\n date: string;\n number: number;\n}\n\nexport interface NotesDirPlan {\n notesDir: string;\n toDelete: SessionCandidate[];\n toRename: SessionCandidate[];\n toMove: SessionCandidate[]; // survivors that need moving to YYYY/MM/ within this dir\n}\n\nexport interface CleanupPlan {\n project: ProjectRow;\n notesDirs: NotesDirPlan[]; // one entry per discovered Notes/ directory (up to 2)\n renumberMap: Map<number, number>; // old number → new number (global across both dirs)\n}\n\n// Template content indicators — if the file only contains these patterns, delete it.\nexport const TEMPLATE_INDICATORS = [\n \"<!-- PAI will add completed work here during session -->\",\n \"<!-- PAI will add completed work here -->\",\n \"Session completed.\",\n \"Session started and ready for your instructions\",\n];\n\n// Session filename patterns\nexport const MODERN_PATTERN = /^(\\d{4}) - (\\d{4}-\\d{2}-\\d{2}) - (.+)\\.md$/;\nexport const LEGACY_PATTERN = /^(\\d{4})_(\\d{4}-\\d{2}-\\d{2})_(.+)\\.md$/;\n","/**\n * Auto-name extraction and string helpers for session-cleanup.\n * Derives a meaningful session title from Markdown content.\n */\n\n// Meta-phrases indicating template/status text rather than real work.\nconst META_PHRASE_PATTERNS: RegExp[] = [\n /session initialized and ready for your instructions/i,\n /fresh session with no pending tasks/i,\n /starting new session.*checking for pending work/i,\n /fresh session with empty todo/i,\n /session started and ready/i,\n /^session\\b.*\\bready\\b/i,\n /^session\\b.*\\binitialized\\b/i,\n /^session\\b.*\\bno pending\\b/i,\n /^session\\b.*\\bno prior work\\b/i,\n /^no pending tasks/i,\n /^no prior work/i,\n];\n\nconst TITLE_CASE_MINOR_WORDS = new Set([\n \"a\", \"an\", \"the\", \"in\", \"on\", \"at\", \"for\", \"to\", \"of\", \"and\", \"or\",\n \"but\", \"via\", \"with\", \"from\", \"by\", \"as\", \"nor\",\n]);\n\nfunction cleanMarkdownLine(raw: string): string | null {\n let s = raw.trim();\n s = s.replace(/^[-*+]\\s+\\[[ xX]\\]\\s*/, \"\");\n s = s.replace(/^[-*+]\\s+/, \"\");\n s = s.replace(/^\\[[ xX]\\]\\s*/, \"\");\n s = s.replace(/\\*\\*\\s*([^*]+?)\\s*\\*\\*/g, \"$1\");\n s = s.replace(/\\*\\s*([^*]+?)\\s*\\*/g, \"$1\");\n s = s.replace(/`([^`]+)`/g, \"$1\");\n s = s.replace(/^\\*+\\s*/, \"\");\n s = s.replace(/^[.,;:]+/, \"\").replace(/[.,;:]+$/, \"\");\n s = s.replace(/\\s+/g, \" \").trim();\n return s.length >= 4 ? s : null;\n}\n\nfunction isMetaPhrase(text: string): boolean {\n return META_PHRASE_PATTERNS.some((re) => re.test(text));\n}\n\nfunction toTitleCase(text: string): string {\n const words = text.split(\" \");\n return words\n .map((word, i) => {\n const lower = word.toLowerCase();\n if (i !== 0 && TITLE_CASE_MINOR_WORDS.has(lower)) return lower;\n return word.charAt(0).toUpperCase() + word.slice(1);\n })\n .join(\" \");\n}\n\nexport function sanitizeName(raw: string): string {\n let s = raw.replace(/[\\/\\\\:*?\"<>|#`]/g, \"\");\n s = s.replace(/\\s+/g, \" \").trim();\n if (s.length > 60) {\n const truncated = s.slice(0, 60);\n const lastSpace = truncated.lastIndexOf(\" \");\n s = lastSpace > 20 ? truncated.slice(0, lastSpace) : truncated;\n }\n s = s.trim();\n return toTitleCase(s);\n}\n\nexport function extractAutoName(content: string): string {\n const lines = content.split(\"\\n\");\n\n const CONTENT_SECTION_HEADINGS = new Set([\n \"Work Done\", \"Summary\", \"Completed\", \"What Was Done\",\n \"Results\", \"Outcomes\", \"Changes\", \"Progress\",\n ]);\n const SKIP_SECTION_HEADINGS = new Set([\n \"Next Steps\", \"Tags\", \"TODO\", \"Blockers\", \"Notes\",\n \"Metadata\", \"Context\", \"Background\",\n ]);\n\n let pastH1 = false;\n let pastFirstHr = false;\n let currentSection: string | null = null;\n const contentSectionLines: string[] = [];\n const otherH2Headings: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (trimmed.startsWith(\"# \")) {\n pastH1 = true;\n continue;\n }\n if (!pastH1) continue;\n\n if (!pastFirstHr) {\n if (trimmed === \"---\") pastFirstHr = true;\n continue;\n }\n\n if (trimmed.startsWith(\"## \")) {\n const headingText = trimmed.slice(3).trim();\n if (CONTENT_SECTION_HEADINGS.has(headingText)) {\n currentSection = \"content\";\n } else if (SKIP_SECTION_HEADINGS.has(headingText)) {\n currentSection = \"skip\";\n } else {\n currentSection = null;\n otherH2Headings.push(headingText);\n }\n continue;\n }\n\n if (trimmed.startsWith(\"#\")) continue;\n if (trimmed === \"-->\") continue;\n if (trimmed.startsWith(\"<!--\") && /^<!--.*-->$/.test(trimmed)) continue;\n\n if (currentSection === \"content\" && trimmed.length > 0) {\n const withoutComment = trimmed.replace(/^<!--.*?-->\\s*/, \"\");\n const effective = withoutComment.length > 0 ? withoutComment : trimmed;\n if (effective.length === 0) continue;\n contentSectionLines.push(effective);\n }\n }\n\n for (const raw of contentSectionLines) {\n const cleaned = cleanMarkdownLine(raw);\n if (!cleaned) continue;\n if (isMetaPhrase(cleaned)) continue;\n if (cleaned.startsWith(\"<!--\") || cleaned.includes(\"PAI will add\")) continue;\n if (cleaned.length < 5) continue;\n return sanitizeName(cleaned);\n }\n\n for (const heading of otherH2Headings) {\n const cleaned = cleanMarkdownLine(heading);\n if (!cleaned) continue;\n if (isMetaPhrase(cleaned)) continue;\n if (cleaned.length > 3 && cleaned.length < 80) {\n return sanitizeName(cleaned);\n }\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.startsWith(\"# \")) {\n const title = trimmed.slice(2).trim();\n if (/^Session \\d{4}/i.test(title)) continue;\n const cleaned = cleanMarkdownLine(title);\n if (cleaned && !isMetaPhrase(cleaned) && cleaned.length > 3) {\n return sanitizeName(cleaned);\n }\n }\n }\n\n return \"Unnamed Session\";\n}\n\n/** Format a 4-digit padded session number. */\nexport function padNum(n: number): string {\n return String(n).padStart(4, \"0\");\n}\n","/**\n * Analysis phase: scan Notes/ directories and build CleanupPlans.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n readdirSync,\n readFileSync,\n statSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type {\n ProjectRow,\n SessionRow,\n SessionCandidate,\n SessionClassification,\n NotesDirPlan,\n CleanupPlan,\n} from \"./types.js\";\nimport {\n TEMPLATE_INDICATORS,\n MODERN_PATTERN,\n LEGACY_PATTERN,\n} from \"./types.js\";\nimport { extractAutoName, padNum } from \"./rename.js\";\n\n// ---------------------------------------------------------------------------\n// DB helpers\n// ---------------------------------------------------------------------------\n\nexport function getAllProjects(db: Database): ProjectRow[] {\n return db\n .prepare(\n \"SELECT id, slug, display_name, root_path, encoded_dir, claude_notes_dir FROM projects WHERE status = 'active' ORDER BY slug\"\n )\n .all() as ProjectRow[];\n}\n\nexport function getProject(\n db: Database,\n slug: string\n): ProjectRow | undefined {\n return db\n .prepare(\n \"SELECT id, slug, display_name, root_path, encoded_dir, claude_notes_dir FROM projects WHERE slug = ?\"\n )\n .get(slug) as ProjectRow | undefined;\n}\n\nfunction getProjectSessions(\n db: Database,\n projectId: number\n): SessionRow[] {\n return db\n .prepare(\"SELECT * FROM sessions WHERE project_id = ? ORDER BY number ASC\")\n .all(projectId) as SessionRow[];\n}\n\n// ---------------------------------------------------------------------------\n// Notes directory discovery\n// ---------------------------------------------------------------------------\n\nfunction findRootNotesDir(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\nfunction findClaudeNotesDir(\n project: ProjectRow,\n rootNotesDir: string | null\n): string | null {\n const candidate =\n project.claude_notes_dir ??\n join(homedir(), \".claude\", \"projects\", project.encoded_dir, \"Notes\");\n\n if (!existsSync(candidate)) return null;\n if (rootNotesDir && candidate === rootNotesDir) return null;\n return candidate;\n}\n\nexport function findAllNotesDirs(project: ProjectRow): string[] {\n const rootDir = findRootNotesDir(project.root_path);\n const claudeDir = findClaudeNotesDir(project, rootDir);\n const dirs: string[] = [];\n if (rootDir) dirs.push(rootDir);\n if (claudeDir) dirs.push(claudeDir);\n return dirs;\n}\n\n// ---------------------------------------------------------------------------\n// Content analysis\n// ---------------------------------------------------------------------------\n\nfunction isTemplateOnly(content: string): boolean {\n const hasTemplateMarker = TEMPLATE_INDICATORS.some((ind) =>\n content.includes(ind)\n );\n if (!hasTemplateMarker) return false;\n\n const lines = content.split(\"\\n\");\n let inWorkDone = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === \"## Work Done\") {\n inWorkDone = true;\n continue;\n }\n if (trimmed.startsWith(\"## \") && inWorkDone) break;\n if (!inWorkDone) continue;\n if (!trimmed) continue;\n if (trimmed.startsWith(\"<!--\") && trimmed.endsWith(\"-->\")) continue;\n if (trimmed.startsWith(\"<!--\")) continue;\n if (trimmed === \"-->\") continue;\n if (trimmed === \"Session completed.\") continue;\n if (trimmed === \"#Session\" || trimmed === \"**Tags:** #Session\") continue;\n return false;\n }\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Scan + analysis\n// ---------------------------------------------------------------------------\n\nexport function scanNotesDir(\n notesDir: string,\n dbByFilename: Map<string, SessionRow>\n): SessionCandidate[] {\n const candidates: SessionCandidate[] = [];\n\n let flatFiles: string[] = [];\n try {\n flatFiles = readdirSync(notesDir, { withFileTypes: true })\n .filter((d) => d.isFile() && d.name.endsWith(\".md\"))\n .map((d) => d.name);\n } catch {\n // Directory unreadable\n }\n\n const subDirFiles: { filename: string; filepath: string }[] = [];\n try {\n const topEntries = readdirSync(notesDir, { withFileTypes: true });\n for (const entry of topEntries) {\n if (!entry.isDirectory()) continue;\n if (!/^\\d{4}$/.test(entry.name)) continue;\n const yearDir = join(notesDir, entry.name);\n const monthDirs = readdirSync(yearDir, { withFileTypes: true });\n for (const mEntry of monthDirs) {\n if (!mEntry.isDirectory()) continue;\n const monthDir = join(yearDir, mEntry.name);\n const files = readdirSync(monthDir).filter((f) => f.endsWith(\".md\"));\n for (const f of files) {\n subDirFiles.push({ filename: f, filepath: join(monthDir, f) });\n }\n }\n }\n } catch {\n // Ignore errors scanning sub-dirs\n }\n\n const allFiles: { filename: string; filepath: string }[] = [\n ...flatFiles.map((f) => ({ filename: f, filepath: join(notesDir, f) })),\n ...subDirFiles,\n ];\n\n for (const { filename, filepath } of allFiles) {\n let num: number;\n let date: string;\n let namepart: string;\n let classification: SessionClassification;\n\n const modernMatch = MODERN_PATTERN.exec(filename);\n const legacyMatch = LEGACY_PATTERN.exec(filename);\n\n if (modernMatch) {\n num = parseInt(modernMatch[1], 10);\n date = modernMatch[2];\n namepart = modernMatch[3];\n classification = \"NAMED\";\n } else if (legacyMatch) {\n num = parseInt(legacyMatch[1], 10);\n date = legacyMatch[2];\n namepart = legacyMatch[3];\n classification = \"LEGACY_FORMAT\";\n } else {\n continue; // not a session file\n }\n\n let sizeBytes = 0;\n let content = \"\";\n try {\n sizeBytes = statSync(filepath).size;\n content = readFileSync(filepath, \"utf8\");\n } catch {\n continue;\n }\n\n const dbSession =\n dbByFilename.get(filename) ??\n dbByFilename.get(filepath.split(`${notesDir}/`)[1] ?? \"\") ??\n null;\n\n if (classification !== \"LEGACY_FORMAT\") {\n if (sizeBytes < 400 || isTemplateOnly(content)) {\n classification = \"EMPTY\";\n } else if (\n namepart === \"New Session\" ||\n namepart === (process.env.USER ?? \"\") ||\n namepart === \"session-started-and-ready-for-your-instructions\"\n ) {\n classification = \"UNNAMED\";\n }\n }\n\n const candidate: SessionCandidate = {\n session: dbSession,\n filename,\n filepath,\n sizeBytes,\n classification,\n date,\n number: num,\n };\n\n if (\n classification === \"UNNAMED\" ||\n classification === \"LEGACY_FORMAT\"\n ) {\n candidate.autoName = extractAutoName(content);\n }\n\n candidates.push(candidate);\n }\n\n return candidates;\n}\n\n/**\n * Renumbering is disabled. This always returns an empty map.\n *\n * It used to reassign every surviving note to its position in the sorted list\n * (`newNum = idx + 1`), which treats the number as a position. The number is\n * also the note's identity — handovers and other notes cite notes by number —\n * and a value that is both a position and an identity parts company with\n * itself the moment the set changes.\n *\n * What that cost, measured on a real corpus: deleting or adding one note\n * shifted the whole series, and because the number is also written into the H1\n * (`# Session 0006: ...`), the rewrite changed file *contents* as well as\n * names. Git could not pair them as renames — 0 of 262 rewritten notes were\n * byte-identical to anything in HEAD — so a single shift produced 261 deletions\n * plus 262 additions. The operator changed one source file and their prompt\n * reported 262 changes, which hides any real uncommitted work in the noise.\n *\n * Numbers are now minted once, at creation, and never reassigned. Gaps are\n * expected and harmless: a gap is information (a note was removed) and costs\n * nothing, whereas a renumber invalidates every existing reference silently.\n *\n * Kept as a function rather than deleted so the call site and the CleanupPlan\n * shape stay intact; collisions should be reported by the caller, never\n * resolved by shifting.\n */\nfunction buildRenumberMap(\n _survivors: SessionCandidate[]\n): Map<number, number> {\n return new Map();\n}\n\nexport function analyzeProject(\n db: Database,\n project: ProjectRow\n): CleanupPlan | null {\n const notesDirPaths = findAllNotesDirs(project);\n if (notesDirPaths.length === 0) return null;\n\n const dbSessions = getProjectSessions(db, project.id);\n const dbByFilename = new Map<string, SessionRow>();\n for (const s of dbSessions) dbByFilename.set(s.filename, s);\n\n const notesDirPlans: NotesDirPlan[] = [];\n const allSurvivors: SessionCandidate[] = [];\n\n for (const notesDir of notesDirPaths) {\n const candidates = scanNotesDir(notesDir, dbByFilename);\n if (candidates.length === 0) continue;\n\n const toDelete = candidates.filter((c) => c.classification === \"EMPTY\");\n const toRename = candidates.filter(\n (c) =>\n c.classification === \"UNNAMED\" || c.classification === \"LEGACY_FORMAT\"\n );\n const survivors = candidates.filter((c) => c.classification !== \"EMPTY\");\n\n notesDirPlans.push({ notesDir, toDelete, toRename, toMove: survivors });\n allSurvivors.push(...survivors);\n }\n\n if (notesDirPlans.length === 0) return null;\n\n const renumberMap = buildRenumberMap(allSurvivors);\n\n return { project, notesDirs: notesDirPlans, renumberMap };\n}\n","/**\n * Dry-run display, Postgres vector DB path updates, and cleanup execution.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport {\n existsSync,\n readdirSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n renameSync,\n statSync,\n} from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header } from \"../../utils.js\";\nimport type { CleanupPlan, SessionCandidate } from \"./types.js\";\nimport { padNum } from \"./rename.js\";\n\n// ---------------------------------------------------------------------------\n// Postgres helpers\n// ---------------------------------------------------------------------------\n\nasync function countVectorDbPaths(oldPaths: string[]): Promise<number> {\n if (oldPaths.length === 0) return 0;\n try {\n const { loadConfig } = await import(\"../../../daemon/config.js\");\n const { PostgresBackend } = await import(\"../../../storage/postgres.js\");\n const config = loadConfig();\n if (config.storageBackend !== \"postgres\") return 0;\n const pgBackend = new PostgresBackend(config.postgres ?? {});\n const connErr = await pgBackend.testConnection();\n if (connErr) { await pgBackend.close(); return 0; }\n const pool = (pgBackend as unknown as {\n pool: { query: (sql: string, params: string[]) => Promise<{ rows: Array<{ n: string }> }> };\n }).pool;\n const placeholders = oldPaths.map((_, i) => `$${i + 1}`).join(\", \");\n const result = await pool.query(\n `SELECT COUNT(*)::text AS n FROM pai_files WHERE path IN (${placeholders})`,\n oldPaths\n );\n await pgBackend.close();\n return parseInt(result.rows[0]?.n ?? \"0\", 10);\n } catch {\n return 0;\n }\n}\n\nasync function updateVectorDbPaths(\n moves: Array<{ oldPath: string; newPath: string }>\n): Promise<number> {\n if (moves.length === 0) return 0;\n try {\n const { loadConfig } = await import(\"../../../daemon/config.js\");\n const { PostgresBackend } = await import(\"../../../storage/postgres.js\");\n const config = loadConfig();\n if (config.storageBackend !== \"postgres\") return 0;\n const pgBackend = new PostgresBackend(config.postgres ?? {});\n const connErr = await pgBackend.testConnection();\n if (connErr) {\n process.stderr.write(`[session-cleanup] Postgres unavailable (${connErr}). Skipping vector DB path update.\\n`);\n await pgBackend.close();\n return 0;\n }\n const pool = (pgBackend as unknown as {\n pool: { connect: () => Promise<{\n query: (sql: string, params: string[]) => Promise<{ rowCount: number | null }>;\n release: () => void;\n }> };\n }).pool;\n const client = await pool.connect();\n let filesUpdated = 0;\n try {\n await client.query(\"BEGIN\", []);\n for (const { oldPath, newPath } of moves) {\n const r = await client.query(\"UPDATE pai_files SET path = $1 WHERE path = $2\", [newPath, oldPath]);\n filesUpdated += r.rowCount ?? 0;\n await client.query(\"UPDATE pai_chunks SET path = $1 WHERE path = $2\", [newPath, oldPath]);\n }\n await client.query(\"COMMIT\", []);\n } catch (e) {\n await client.query(\"ROLLBACK\", []);\n throw e;\n } finally {\n client.release();\n }\n await pgBackend.close();\n return filesUpdated;\n } catch (e) {\n process.stderr.write(`[session-cleanup] Failed to update vector DB paths: ${e}\\n`);\n return -1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Dry-run display\n// ---------------------------------------------------------------------------\n\nexport async function displayDryRun(plans: CleanupPlan[]): Promise<void> {\n let totalDelete = 0;\n let totalRename = 0;\n let totalMove = 0;\n let totalRenumber = 0;\n\n for (const plan of plans) {\n const hasWork =\n plan.notesDirs.some(\n (d) => d.toDelete.length > 0 || d.toRename.length > 0 || d.toMove.length > 0\n ) || plan.renumberMap.size > 0;\n if (!hasWork) continue;\n\n console.log();\n console.log(\n header(` Project: ${plan.project.display_name} (${plan.project.slug})`)\n );\n\n for (const dirPlan of plan.notesDirs) {\n console.log(dim(` Notes: ${dirPlan.notesDir}`));\n\n if (dirPlan.toDelete.length > 0) {\n console.log(bold(\" ARCHIVE (empty/template-only sessions → .archive/):\"));\n for (const c of dirPlan.toDelete) {\n console.log(\n ` ${chalk.yellow(\"ARCH\")} ${dim(padNum(c.number))} - ${c.date} - ${\n c.filename.split(\" - \").slice(2).join(\" - \")\n } ${dim(`(${c.sizeBytes}b)`)}`\n );\n totalDelete++;\n }\n console.log();\n }\n\n if (dirPlan.toRename.length > 0) {\n console.log(bold(\" RENAME (unnamed or legacy-format sessions):\"));\n for (const c of dirPlan.toRename) {\n const autoName = c.autoName ?? \"Unnamed Session\";\n console.log(` ${chalk.yellow(\"REN\")} ${c.filename}`);\n console.log(` → ${padNum(c.number)} - ${c.date} - ${autoName}.md`);\n totalRename++;\n }\n console.log();\n }\n\n if (dirPlan.toMove.length > 0) {\n console.log(bold(\" MOVE TO YYYY/MM/ hierarchy:\"));\n for (const c of dirPlan.toMove) {\n const [year, month] = c.date.split(\"-\");\n console.log(` ${chalk.cyan(\"MOV\")} ${c.filename}`);\n console.log(` → ${year}/${month}/${c.filename}`);\n totalMove++;\n }\n console.log();\n }\n }\n\n if (plan.renumberMap.size > 0) {\n console.log(\n bold(\" RENUMBER (after deletions, global across all Notes/ dirs):\")\n );\n for (const [oldN, newN] of plan.renumberMap) {\n console.log(\n ` ${chalk.blue(\"NUM\")} #${padNum(oldN)} → #${padNum(newN)}`\n );\n totalRenumber++;\n }\n console.log();\n }\n }\n\n const wouldMovePaths: string[] = [];\n for (const plan of plans) {\n for (const dirPlan of plan.notesDirs) {\n for (const c of dirPlan.toMove) {\n const [year, month] = c.date.split(\"-\");\n const targetPath = join(dirPlan.notesDir, year, month, c.filename);\n if (c.filepath !== targetPath) wouldMovePaths.push(c.filepath);\n }\n }\n }\n\n const vectorDbCount = await countVectorDbPaths(wouldMovePaths);\n\n console.log();\n console.log(bold(\" Summary (dry-run):\"));\n console.log(` ${chalk.yellow(\"ARCH\")} ${totalDelete} empty sessions to archive (moved, not deleted)`);\n console.log(` ${chalk.yellow(\"REN\")} ${totalRename} unnamed sessions to rename`);\n console.log(` ${chalk.blue(\"NUM\")} ${totalRenumber} sessions to renumber`);\n console.log(` ${chalk.cyan(\"MOV\")} ${totalMove} sessions to move into YYYY/MM/ dirs`);\n if (vectorDbCount > 0) {\n console.log(\n ` ${chalk.magenta(\"VEC\")} ${vectorDbCount} file path(s) will be updated in the vector DB (embeddings preserved)`\n );\n } else if (wouldMovePaths.length > 0) {\n console.log(\n ` ${chalk.magenta(\"VEC\")} 0 file path(s) found in vector DB for moved files (no embeddings to preserve)`\n );\n }\n console.log();\n console.log(warn(\" This is a dry-run. Add --execute to apply changes.\"));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Execution\n// ---------------------------------------------------------------------------\n\nexport async function executeCleanup(\n db: Database,\n plans: CleanupPlan[],\n skipReindex: boolean\n): Promise<void> {\n let deleted = 0;\n let renamed = 0;\n let moved = 0;\n let renumbered = 0;\n let dbUpdated = 0;\n const vectorDbMoves: Array<{ oldPath: string; newPath: string }> = [];\n\n for (const plan of plans) {\n console.log();\n console.log(\n header(` Project: ${plan.project.display_name} (${plan.project.slug})`)\n );\n\n for (const dirPlan of plan.notesDirs) {\n const { notesDir } = dirPlan;\n if (plan.notesDirs.length > 1) console.log(dim(` Directory: ${notesDir}`));\n\n // Step 1: Retire \"empty\" sessions — by moving them, never by unlinking.\n //\n // This deleted four titled notes from Youdrill on 2026-08-03 (\"Cocoapods\n // Bootstrap\", \"Youdrill Tools Pipeline Review\", and two more), and they\n // were only recoverable because that session inspected the diff before\n // committing. Anywhere this ran and was committed, the notes are gone.\n //\n // The classifier is the root problem: EMPTY is `sizeBytes < 400 ||\n // isTemplateOnly`. Size is a proxy for worthlessness and a bad one — a\n // real session that produced three lines and a good title is under 400\n // bytes. But no threshold is worth trusting with an irreversible\n // operation, so the fix is not a better number: it is that a cleanup tool\n // has no business destroying anything a human might want back.\n //\n // Moving to .archive/ still achieves the actual goal (the note leaves the\n // working listing and the numbering respreads) while keeping the file.\n for (const c of dirPlan.toDelete) {\n try {\n const archiveDir = join(dirname(c.filepath), \".archive\");\n mkdirSync(archiveDir, { recursive: true });\n let dest = join(archiveDir, c.filename);\n // Never clobber an earlier archived note with the same name.\n if (existsSync(dest)) {\n const stamp = statSync(c.filepath).mtimeMs.toString(36);\n dest = join(archiveDir, `${c.filename.replace(/\\.md$/, \"\")} (${stamp}).md`);\n }\n renameSync(c.filepath, dest);\n console.log(ok(` ARCH ${c.filename}`) + dim(\" → .archive/\"));\n deleted++;\n } catch (e) {\n console.log(err(` FAIL to archive ${c.filename}: ${e}`));\n }\n if (c.session) {\n try {\n db.prepare(\"DELETE FROM sessions WHERE id = ?\").run(c.session.id);\n dbUpdated++;\n } catch (e) {\n console.log(err(` FAIL to remove session #${c.number} from DB: ${e}`));\n }\n }\n }\n\n // Step 2: Rename unnamed/legacy sessions\n for (const c of dirPlan.toRename) {\n const autoName = c.autoName ?? \"Unnamed Session\";\n const newFilename = `${padNum(c.number)} - ${c.date} - ${autoName}.md`;\n const newPath = join(notesDir, newFilename);\n\n if (c.filepath !== newPath) {\n try {\n renameSync(c.filepath, newPath);\n console.log(ok(` REN ${c.filename}`));\n console.log(dim(` → ${newFilename}`));\n renamed++;\n (c as { filename: string }).filename = newFilename;\n (c as { filepath: string }).filepath = newPath;\n } catch (e) {\n console.log(err(` FAIL rename ${c.filename}: ${e}`));\n continue;\n }\n }\n\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 `# ${autoName}`;\n }\n return line;\n });\n if (!h1Updated) updated.unshift(`# ${autoName}`, \"\");\n writeFileSync(newPath, updated.join(\"\\n\"), \"utf8\");\n } catch {\n // Non-fatal\n }\n\n if (c.session) {\n const normalizedSlug = autoName\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n try {\n db.prepare(\n \"UPDATE sessions SET slug = ?, title = ?, filename = ? WHERE id = ?\"\n ).run(normalizedSlug, autoName, newFilename, c.session.id);\n dbUpdated++;\n } catch (e) {\n console.log(err(` FAIL DB update for session #${c.number}: ${e}`));\n }\n }\n }\n\n // Step 3a: Renumber survivors\n if (plan.renumberMap.size > 0) {\n const toRenumber = dirPlan.toMove.filter((c) =>\n plan.renumberMap.has(c.number)\n );\n\n const tempFiles: {\n candidate: SessionCandidate;\n tempPath: string;\n newNum: number;\n }[] = [];\n for (const c of toRenumber) {\n const newNum = plan.renumberMap.get(c.number)!;\n const tempFilename = `__tmp_${padNum(c.number)}_${c.filename}`;\n const tempPath = join(notesDir, tempFilename);\n try {\n if (existsSync(c.filepath)) {\n renameSync(c.filepath, tempPath);\n tempFiles.push({ candidate: c, tempPath, newNum });\n }\n } catch (e) {\n console.log(err(` FAIL temp-rename #${c.number}: ${e}`));\n }\n }\n\n for (const { candidate: c, tempPath, newNum } of tempFiles) {\n const newFilename = c.filename.replace(/^\\d{4}/, padNum(newNum));\n const newPath = join(notesDir, newFilename);\n try {\n renameSync(tempPath, newPath);\n console.log(\n ok(` NUM #${padNum(c.number)} → #${padNum(newNum)}: ${newFilename}`)\n );\n renumbered++;\n (c as { filename: string }).filename = newFilename;\n (c as { filepath: string }).filepath = newPath;\n (c as { number: number }).number = newNum;\n } catch (e) {\n console.log(err(` FAIL final-rename #${newNum}: ${e}`));\n }\n\n if (existsSync(newPath)) {\n try {\n const content = readFileSync(newPath, \"utf8\");\n const lines = content.split(\"\\n\");\n const updated = lines.map((line) => {\n if (line.match(/^# Session \\d{4}:/)) {\n return line.replace(\n /^# Session \\d{4}:/,\n `# Session ${padNum(newNum)}:`\n );\n }\n return line;\n });\n writeFileSync(newPath, updated.join(\"\\n\"), \"utf8\");\n } catch {\n // Non-fatal\n }\n }\n }\n\n const dbRenumbers = tempFiles\n .filter(({ candidate: c }) => c.session != null)\n .map(({ candidate: c, newNum }) => ({\n session: c.session!,\n newNum,\n newFilename: c.filename,\n }));\n\n if (dbRenumbers.length > 0) {\n const renumberDb = db.transaction(() => {\n for (const { session, newNum } of dbRenumbers) {\n db.prepare(\"UPDATE sessions SET number = ? WHERE id = ?\").run(\n -newNum,\n session.id\n );\n }\n for (const { session, newNum, newFilename } of dbRenumbers) {\n db.prepare(\n \"UPDATE sessions SET number = ?, filename = ? WHERE id = ?\"\n ).run(newNum, newFilename, session.id);\n }\n });\n try {\n renumberDb();\n dbUpdated += dbRenumbers.length;\n } catch (e) {\n console.log(err(` FAIL DB renumber transaction: ${e}`));\n }\n }\n }\n\n // Step 3b: Move to YYYY/MM/\n for (const c of dirPlan.toMove) {\n const [year, month] = c.date.split(\"-\");\n const targetDir = join(notesDir, year, month);\n const targetPath = join(targetDir, c.filename);\n if (c.filepath === targetPath) continue;\n\n try {\n mkdirSync(targetDir, { recursive: true });\n } catch (e) {\n console.log(err(` FAIL mkdir ${targetDir}: ${e}`));\n continue;\n }\n\n const oldAbsPath = c.filepath;\n try {\n if (existsSync(c.filepath)) {\n renameSync(c.filepath, targetPath);\n console.log(ok(` MOV ${c.filename}`));\n console.log(dim(` → ${year}/${month}/${c.filename}`));\n moved++;\n vectorDbMoves.push({ oldPath: oldAbsPath, newPath: targetPath });\n }\n } catch (e) {\n console.log(err(` FAIL move ${c.filename}: ${e}`));\n continue;\n }\n\n const newFilenameInDb = `${year}/${month}/${c.filename}`;\n if (c.session) {\n try {\n db.prepare(\"UPDATE sessions SET filename = ? WHERE id = ?\").run(\n newFilenameInDb,\n c.session.id\n );\n dbUpdated++;\n } catch (e) {\n console.log(err(` FAIL DB update path for ${c.filename}: ${e}`));\n }\n }\n }\n }\n }\n\n // Step 5: Update Postgres vector DB paths\n let vectorDbUpdated = 0;\n if (vectorDbMoves.length > 0) {\n console.log();\n console.log(\n dim(\n ` Updating ${vectorDbMoves.length} file path(s) in vector DB to preserve embeddings...`\n )\n );\n const result = await updateVectorDbPaths(vectorDbMoves);\n if (result >= 0) {\n vectorDbUpdated = result;\n console.log(\n ok(` Updated ${vectorDbUpdated} file path(s) in Postgres (embeddings preserved)`)\n );\n } else {\n console.log(\n warn(\n \" Vector DB path update failed — embeddings may be orphaned (check logs)\"\n )\n );\n }\n }\n\n console.log();\n console.log(bold(\" Cleanup complete:\"));\n console.log(ok(` ${deleted} session(s) deleted`));\n console.log(ok(` ${renamed} session(s) renamed`));\n console.log(ok(` ${renumbered} session(s) renumbered`));\n console.log(ok(` ${moved} session(s) moved to YYYY/MM/ hierarchy`));\n console.log(ok(` ${dbUpdated} registry DB record(s) updated`));\n if (vectorDbMoves.length > 0) {\n console.log(\n ok(\n ` ${vectorDbUpdated} vector DB file path(s) updated (embeddings preserved)`\n )\n );\n }\n\n if (!skipReindex) {\n console.log();\n console.log(\n dim(\" Memory re-index: the PAI daemon will pick up changes within 5 minutes.\")\n );\n console.log(dim(\" To force immediate re-index: pai memory index --all\"));\n }\n\n console.log();\n}\n","/**\n * Commander registration for `pai session cleanup`.\n */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { err, dim, ok, header } from \"../../utils.js\";\nimport { getAllProjects, getProject, analyzeProject } from \"./scanner.js\";\nimport { displayDryRun, executeCleanup } from \"./executor.js\";\n\nexport function registerSessionCleanupCommand(\n sessionCmd: Command,\n getDb: () => Database\n): void {\n sessionCmd\n .command(\"cleanup [project-slug]\")\n .description(\n \"Clean up session notes: delete empties, auto-name unnamed, move into YYYY/MM/ hierarchy, renumber\"\n )\n .option(\"--execute\", \"Actually perform the cleanup (default is dry-run)\")\n .option(\"--no-renumber\", \"Skip renumbering sessions after deletions\")\n .option(\"--no-reindex\", \"Skip triggering memory re-index after moves\")\n .action(\n async (\n projectSlug: string | undefined,\n opts: { execute?: boolean; renumber?: boolean; reindex?: boolean }\n ) => {\n const db = getDb();\n const dryRun = !opts.execute;\n const skipReindex = opts.reindex === false;\n\n let projects;\n if (projectSlug) {\n const p = getProject(db, projectSlug);\n if (!p) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n projects = [p];\n } else {\n projects = getAllProjects(db);\n }\n\n console.log();\n console.log(\n header(\n dryRun\n ? \" pai session cleanup — DRY RUN (no changes will be made)\"\n : \" pai session cleanup — EXECUTING\"\n )\n );\n console.log(dim(` Analyzing ${projects.length} project(s)...`));\n\n const plans = [];\n for (const project of projects) {\n const plan = analyzeProject(db, project);\n if (plan) plans.push(plan);\n }\n\n const activePlans = plans.filter(\n (p) =>\n p.notesDirs.some(\n (d) =>\n d.toDelete.length > 0 ||\n d.toRename.length > 0 ||\n d.toMove.length > 0\n ) || p.renumberMap.size > 0\n );\n\n if (activePlans.length === 0) {\n console.log();\n console.log(ok(\" Nothing to do — all session notes are clean!\"));\n console.log();\n return;\n }\n\n if (dryRun) {\n await displayDryRun(activePlans);\n } else {\n await executeCleanup(db, activePlans, skipReindex);\n }\n }\n );\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 { readFileSync, writeFileSync, renameSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport chalk from \"chalk\";\nimport type { ProjectRow } from \"./types.js\";\nimport { cmdPause, type PauseOptions } from \"./pause.js\";\nimport {\n findNotesDir,\n findLatestNote,\n} from \"../../../session/checkpoint-block.js\";\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: PauseOptions): 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 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/** Give each session a moment to pick up the prompt before judging it idle. */\nconst SETTLE_MS = 4_000;\n/** How often to re-ask AIBroker which sessions are back at the prompt. */\nconst POLL_MS = 2_000;\n\n/**\n * Wait until each session is back at its prompt, i.e. has finished writing its\n * checkpoint.\n *\n * This used to be a flat 5-second sleep before sending /exit. Authoring a real\n * handover — reading state, composing it, writing the file — takes far longer\n * than five seconds, so the exit routinely arrived mid-write and killed the\n * session while it was saving the very thing the command exists to save.\n *\n * `atPrompt` is the signal that a session is idle again. Sessions still busy\n * when the deadline passes are returned as stragglers and deliberately NOT\n * exited: leaving a session open costs nothing, and killing one mid-write costs\n * the checkpoint.\n */\nasync function waitForSessionsIdle(\n sessionIds: string[],\n timeoutMs: number\n): Promise<{ idle: Set<string>; stragglers: Set<string> }> {\n const pending = new Set(sessionIds);\n const idle = new Set<string>();\n\n await sleep(Math.min(SETTLE_MS, timeoutMs));\n\n const deadline = Date.now() + timeoutMs;\n while (pending.size > 0 && Date.now() < deadline) {\n let current: AiBrokerSessionMeta[];\n try {\n current = await fetchLiveSessions();\n } catch {\n // AIBroker blipped — try again on the next tick rather than assuming\n // anything about sessions we can no longer see.\n await sleep(POLL_MS);\n continue;\n }\n\n for (const s of current) {\n if (pending.has(s.sessionId) && s.atPrompt) {\n pending.delete(s.sessionId);\n idle.add(s.sessionId);\n }\n }\n\n // A session that vanished from the list has already gone; stop waiting.\n const visible = new Set(current.map((s) => s.sessionId));\n for (const id of [...pending]) {\n if (!visible.has(id)) pending.delete(id);\n }\n\n if (pending.size > 0) await sleep(POLL_MS);\n }\n\n return { idle, stragglers: pending };\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\n/** Case-insensitive substring match on the session's name or id. */\nexport function matchesOnly(s: AiBrokerSessionMeta, only: string): boolean {\n const needle = only.trim().toLowerCase();\n if (!needle) return true;\n const name = (s.paiName ?? s.name ?? \"\").toLowerCase();\n return name.includes(needle) || s.sessionId.toLowerCase().includes(needle);\n}\n\nexport async function cmdPauseAll(opts: {\n exit?: boolean;\n dryRun?: boolean;\n only?: string;\n wait?: number;\n}): Promise<void> {\n // Upper bound on how long to wait for a session to finish its checkpoint —\n // not a fixed delay. Generous, because the cost of waiting is nothing and the\n // cost of exiting too early is the checkpoint.\n const waitMs = opts.wait ?? 180_000;\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 allClaude = liveSessions.filter((s) => s.kind === \"claude\");\n const claudeSessions = opts.only\n ? allClaude.filter((s) => matchesOnly(s, opts.only!))\n : allClaude;\n const skipped = liveSessions.length - allClaude.length;\n\n if (opts.only) {\n // Said out loud, because a filter that silently matches nothing looks\n // identical to a filter that matched and did its job quietly.\n console.log(\n dim(\n ` --only ${opts.only}: ${claudeSessions.length} of ${allClaude.length} ` +\n `Claude session(s) matched.`\n )\n );\n if (claudeSessions.length === 0) {\n console.log(warn(`No live Claude session matches \"${opts.only}\". Nothing to pause.`));\n return;\n }\n }\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<{\n session: AiBrokerSessionMeta;\n pauseOk: boolean;\n unconfirmed?: boolean;\n exitOk?: boolean;\n error?: string;\n }> = [];\n\n for (const s of claudeSessions) {\n process.stdout.write(\" \" + sessionLabel(s) + \" … \");\n\n // Send \"pause session\" command\n // No trailing newline: the transport appends Enter itself.\n const pauseResult = await sendToSession(s.sessionId, \"pause session\");\n\n // Three outcomes, not two. A timeout means the message reached the target's\n // mailbox — the handler deposits before it waits for the submit ack — so the\n // pause is under way and only the confirmation is missing.\n //\n // Printing that as FAILED is worse than useless: the obvious response is to\n // run the command again, and a second \"pause session\" into a session that\n // already paused nests a fresh \"carried forward\" block inside the one it\n // just wrote. Measured across 15 sessions: 9 reported failed, 8 verifiably\n // paused, and one refused the duplicate specifically to protect its own\n // checkpoint.\n if (pauseResult.timedOut) {\n console.log(warn(\"sent — checkpoint still running (not confirmed)\"));\n results.push({ session: s, pauseOk: true, unconfirmed: true });\n continue;\n }\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(\n ` Waiting for sessions to finish writing their checkpoints ` +\n `(up to ${Math.round(waitMs / 1000)}s)…`\n )\n );\n\n const { stragglers } = await waitForSessionsIdle(\n pausedSessions.map((s) => s.sessionId),\n waitMs\n );\n\n console.log();\n for (const s of pausedSessions) {\n // Never exit a session that is still working — it is most likely still\n // writing the checkpoint, and /exit would destroy it mid-write.\n if (stragglers.has(s.sessionId)) {\n console.log(\n \" \" +\n sessionLabel(s) +\n \" \" +\n warn(\"still busy — left open, not exited\")\n );\n continue;\n }\n\n process.stdout.write(\" \" + sessionLabel(s) + \" exiting … \");\n const exitResult = await sendToSession(s.sessionId, \"/exit\");\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 if (stragglers.size > 0) {\n console.log(\n \"\\n\" +\n dim(\n ` ${stragglers.size} session(s) were still working. Leaving a ` +\n `session open is harmless; exiting one mid-write is not.`\n )\n );\n }\n }\n }\n\n // ── Summary ───────────────────────────────────────────────────────────────\n const total = results.length;\n const succeeded = results.filter((r) => r.pauseOk).length;\n const unconfirmed = results.filter((r) => r.unconfirmed).length;\n const confirmed = succeeded - unconfirmed;\n const failed = total - succeeded;\n\n console.log();\n if (failed === 0 && unconfirmed === 0) {\n console.log(ok(`All ${total} session(s) paused successfully.`));\n } else {\n console.log(\n ok(`${confirmed} confirmed`) +\n (unconfirmed > 0 ? warn(`, ${unconfirmed} still writing`) : \"\") +\n (failed > 0 ? err(`, ${failed} not delivered`) : \"\") +\n dim(` — of ${total} session(s).`)\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 if (unconfirmed > 0) {\n // Said explicitly, because the whole cost of the old wording was that it\n // read as \"try again\" when trying again is the one harmful move.\n console.log(\n dim(\n `\\n \"Still writing\" means delivered — the session is composing its\\n` +\n ` checkpoint, which routinely outlasts the confirmation window.\\n` +\n ` Do NOT re-run to catch them: a second pause nests a new\\n` +\n ` \"carried forward\" block inside the one being written. Check with\\n` +\n ` \\`pai session list\\` in a minute instead.`\n )\n );\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 * 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 * 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 · w worker · a claude · 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 · w worker · a claude · 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\" | \"worker\" | \"anthropic\";\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(\"w\") + dim(\" worker\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"a\") + dim(\" claude\"));\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 // w/a force one engine for the launch, overriding the workers config.\n if (s === \"w\" && cur.record.dir) return finish({ record: cur.record, action: \"worker\" });\n if (s === \"a\" && cur.record.dir) return finish({ record: cur.record, action: \"anthropic\" });\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/w/a 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. The engine\n // follows the workers config; w/a above forced one explicitly.\n const engine = action === \"worker\" ? \"worker\" : action === \"anthropic\" ? \"claude\" : undefined;\n launchInDir(record.dir, record.name, { forceFresh: true, engine, dryRun: opts.dryRun });\n}\n","/**\n * PAI CLI — Commander program builder.\n *\n * Constructs the full `pai` command tree WITHOUT parsing argv. This separation\n * lets the documentation generator (scripts/build-docs.mjs) import and introspect\n * the live program object — the single source of truth for `--help`, the\n * generated man pages, and `pai help <area>`.\n *\n * The thin entry point (cli/index.ts) imports buildProgram() and calls parse().\n */\n\nimport { Command } from \"commander\";\nimport { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { openRegistry } from \"../registry/db.js\";\nimport type { Database } from \"better-sqlite3\";\nimport { registerProjectsCommands } from \"./commands/project/projects-index.js\";\nimport { findMovedPath } from \"./commands/project/commands.js\";\nimport { existsSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { registerRegistryCommands } from \"./commands/registry.js\";\nimport { registerMemoryCommands } from \"./commands/memory.js\";\nimport { registerIdentityCommands } from \"./commands/identity.js\";\nimport { registerMcpCommands } from \"./commands/mcp.js\";\nimport { registerDaemonCommands } from \"./commands/daemon.js\";\nimport { registerBackupCommands } from \"./commands/backup.js\";\nimport { registerRestoreCommands } from \"./commands/restore.js\";\nimport { registerSetupCommand } from \"./commands/setup.js\";\nimport { registerObsidianCommands } from \"./commands/obsidian.js\";\nimport { registerZettelCommands } from \"./commands/zettel.js\";\nimport { registerObservationCommands } from \"./commands/observation.js\";\nimport { registerSkillCommands } from \"./commands/skill.js\";\nimport { registerUpdateCommand } from \"./commands/update.js\";\nimport { registerNotifyCommands } from \"./commands/notify.js\";\nimport { registerTaskCommands } from \"./commands/task.js\";\nimport { registerTopicCommands } from \"./commands/topic.js\";\nimport { registerKgCommands } from \"./commands/kg.js\";\nimport { registerDbCommands } from \"./commands/db.js\";\nimport { registerHelpCommand } from \"./commands/help.js\";\nimport { registerSessionCommands } from \"./commands/session/index.js\";\nimport { registerWorkerCommands } from \"./commands/worker/index.js\";\nimport { registerSessionCleanupCommand } from \"./commands/session-cleanup/index.js\";\nimport { err, warn, ok, dim, shortenPath, encodeDir, now } from \"./utils.js\";\nimport { cmdPause } from \"./commands/session/pause.js\";\nimport { cmdEnd } from \"./commands/session/end.js\";\nimport { cmdGoto } from \"./commands/session/goto.js\";\nimport { cmdPauseAll } from \"./commands/session/pause-all.js\";\nimport { cmdClearNames } from \"./commands/session/clear-names.js\";\nimport { cmdList as cmdNotesList } from \"./commands/session/commands.js\";\nimport { resolveIdentifier } from \"./commands/project/helpers.js\";\nimport { cmdFind } from \"./commands/find.js\";\nimport { cmdMain } from \"./commands/main-resolver.js\";\nimport { cmdPick } from \"./commands/pick.js\";\n\n// ---------------------------------------------------------------------------\n// Version resolution\n// ---------------------------------------------------------------------------\n\nfunction getVersion(): string {\n try {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkgPath = join(__dirname, \"../../package.json\");\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { version?: string };\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Lazy database singleton\n// ---------------------------------------------------------------------------\n\nlet _db: Database | null = null;\n\nfunction getDb(): Database {\n if (!_db) {\n try {\n _db = openRegistry();\n } catch (e) {\n // Throw instead of process.exit(1): the throw propagates up through\n // the calling action and parseAsync() to the top-level catch in\n // cli/index.ts, which prints the message and sets process.exitCode.\n // That lets the process exit naturally, which flushes stdout/stderr\n // even when either is a pipe (process.exit() does not — see\n // src/cli/lib/exit.ts).\n throw new Error(`Failed to open PAI registry: ${e}`);\n }\n }\n return _db;\n}\n\n// ---------------------------------------------------------------------------\n// Program builder\n// ---------------------------------------------------------------------------\n\n/**\n * Build and return the fully configured `pai` Commander program.\n * Does NOT call parse — the caller (cli/index.ts) does that.\n */\nexport function buildProgram(): Command {\n const program = new Command();\n\n program\n .name(\"pai\")\n .description(\"PAI Knowledge OS — Personal AI Infrastructure CLI\")\n .version(getVersion(), \"-V, --version\", \"Print version and exit\")\n .addHelpText(\n \"after\",\n `\nDaily commands:\n pai Show your work: live tabs + recent sessions + recent projects\n pai <name> Switch / resume / fresh — universal\n pai pause [all] Pause current (or all live) sessions\n pai end Finalize current session\n\nSubcommands (rarely needed):\n pai projects ... Project registry management (cd, rebind, info, ...)\n pai registry ... Registry maintenance (scan, ...)\n pai memory ... Memory engine (index, search, ...)\n pai shell-init Shell integration (eval \"$(pai shell-init)\")\n\nRecovery:\n pai clear-names Wipe corrupt iTerm tab-name store\n\nExamples:\n pai aibroker Switch to the live AIBroker tab in iTerm\n pai mdf Find sessions where you worked on MDF\n pai 81c5c3dc Resume session by UUID prefix\n pai --all Show everything including cold and zero-session projects`\n );\n\n // -------------------------------------------------------------------------\n // pai projects (canonical plural namespace)\n // -------------------------------------------------------------------------\n\n const projectsCmd = program\n .command(\"projects\")\n .description(\"Manage registered projects (list, cd, add, info, ...)\");\n\n registerProjectsCommands(projectsCmd, getDb);\n\n // Singular alias: `pai project` → `pai projects`\n const projectCmd = program\n .command(\"project\")\n .description(\"Alias for `pai projects`\");\n\n registerProjectsCommands(projectCmd, getDb);\n\n // -------------------------------------------------------------------------\n // pai sessions (alias for the unified `pai` listing)\n // -------------------------------------------------------------------------\n\n // -------------------------------------------------------------------------\n // pai session <subcommand>\n //\n // These were written, exported and never wired: neither\n // registerSessionCommands nor registerSessionCleanupCommand was called from\n // anywhere, so every `pai session <x>` fell through to the default `query`\n // resolver and was interpreted as a prompt-history search.\n //\n // That is why session-stop.sh has been a near no-op — all four of its calls\n // (`session cleanup --execute`, `session checkpoint`, `session slug --apply`,\n // `session handover`) failed silently behind `2>/dev/null || true`.\n // -------------------------------------------------------------------------\n\n const sessionCmd = program\n .command(\"session\")\n .description(\n \"Session management: list, info, checkpoint, handover, cleanup, slug, tag, route.\\n\" +\n \"Short forms: `pai pause`, `pai end`, `pai resume <name>`.\"\n );\n\n registerSessionCommands(sessionCmd, getDb);\n registerSessionCleanupCommand(sessionCmd, getDb);\n\n // -------------------------------------------------------------------------\n // pai worker — subagent runs on configured providers (glm et al.)\n // -------------------------------------------------------------------------\n\n const workerCmd = program\n .command(\"worker\")\n .description(\n \"Run subagents on configured worker providers: run, ps, follow, replay,\\n\" +\n \"pane, log, status-line, providers, roles, on, off, install\"\n );\n\n registerWorkerCommands(workerCmd);\n\n program\n .command(\"sessions\")\n .description(\"Alias for `pai` — show the unified deduped listing.\")\n .option(\"--all\", \"Show all entries including cold / zero-session / archived projects\")\n .option(\"-n, --n <count>\", \"Max candidates for history search\", \"20\")\n .action(async (opts: { all?: boolean; n?: string }) => {\n await cmdMain(getDb(), undefined, undefined, opts);\n });\n\n // -------------------------------------------------------------------------\n // pai registry\n // -------------------------------------------------------------------------\n\n const registryCmd = program\n .command(\"registry\")\n .description(\"Registry maintenance: scan, migrate, stats, rebuild\");\n\n registerRegistryCommands(registryCmd, getDb);\n\n // -------------------------------------------------------------------------\n // pai memory\n // -------------------------------------------------------------------------\n\n const memoryCmd = program\n .command(\"memory\")\n .description(\"Memory engine: index, search, and status\");\n\n registerMemoryCommands(memoryCmd, getDb);\n\n // -------------------------------------------------------------------------\n // pai identity\n // -------------------------------------------------------------------------\n\n const identityCmd = program\n .command(\"identity\")\n .description(\"Declare who you are: self addresses and where mail is delivered\");\n\n registerIdentityCommands(identityCmd);\n\n // -------------------------------------------------------------------------\n // pai mcp\n // -------------------------------------------------------------------------\n\n const mcpCmd = program\n .command(\"mcp\")\n .description(\"MCP server management: install and status\");\n\n registerMcpCommands(mcpCmd);\n\n // -------------------------------------------------------------------------\n // pai daemon\n // -------------------------------------------------------------------------\n\n const daemonCmd = program\n .command(\"daemon\")\n .description(\"PAI daemon management: serve, status, restart, install, uninstall, logs\");\n\n registerDaemonCommands(daemonCmd);\n\n // -------------------------------------------------------------------------\n // pai backup / pai restore\n // -------------------------------------------------------------------------\n\n registerBackupCommands(program);\n registerRestoreCommands(program);\n\n // -------------------------------------------------------------------------\n // pai setup / pai update\n // -------------------------------------------------------------------------\n\n registerSetupCommand(program);\n registerUpdateCommand(program);\n\n // -------------------------------------------------------------------------\n // pai notify\n // -------------------------------------------------------------------------\n\n const notifyCmd = program\n .command(\"notify\")\n .description(\"Notification config: status, get, set, test, send\");\n\n registerNotifyCommands(notifyCmd);\n\n // -------------------------------------------------------------------------\n // pai task\n // -------------------------------------------------------------------------\n\n const taskCmd = program\n .command(\"task\")\n .alias(\"tasks\")\n .description(\"Task bus: list, add, dispatch, and complete cross-session work\");\n\n registerTaskCommands(taskCmd);\n\n // -------------------------------------------------------------------------\n // pai topic\n // -------------------------------------------------------------------------\n\n const topicCmd = program\n .command(\"topic\")\n .description(\"Topic shift detection: check whether context has drifted to a different project\");\n\n registerTopicCommands(topicCmd);\n\n // -------------------------------------------------------------------------\n // pai kg\n // -------------------------------------------------------------------------\n\n const kgCmd = program\n .command(\"kg\")\n .description(\"Temporal knowledge graph: backfill, query, list, stats\");\n\n registerKgCommands(kgCmd);\n\n // -------------------------------------------------------------------------\n // pai db\n // -------------------------------------------------------------------------\n\n const dbCmd = program\n .command(\"db\")\n .description(\"Database inspection: query, tables, schema (sqlite or postgres)\");\n\n registerDbCommands(dbCmd);\n\n // -------------------------------------------------------------------------\n // pai obsidian\n // -------------------------------------------------------------------------\n\n const obsidianCmd = program\n .command(\"obsidian\")\n .description(\"Obsidian vault: sync project notes, view status, open in Obsidian\");\n\n registerObsidianCommands(obsidianCmd, getDb);\n\n // -------------------------------------------------------------------------\n // pai zettel\n // -------------------------------------------------------------------------\n\n const zettelCmd = program\n .command(\"zettel\")\n .description(\"Zettelkasten intelligence: explore, surprise, converse, themes, health, suggest\");\n\n registerZettelCommands(zettelCmd, getDb);\n\n // -------------------------------------------------------------------------\n // pai observation\n // -------------------------------------------------------------------------\n\n const observationCmd = program\n .command(\"observation\")\n .description(\"Observation capture: list, search, and stats\");\n\n registerObservationCommands(observationCmd);\n\n // -------------------------------------------------------------------------\n // pai skill\n // -------------------------------------------------------------------------\n\n const skillCmd = program\n .command(\"skill\")\n .description(\"Skill telemetry and (future) discovery for the self-educating skill system\");\n\n registerSkillCommands(skillCmd);\n\n // -------------------------------------------------------------------------\n // pai help [area] — rich man-page viewer for the generated docs\n // -------------------------------------------------------------------------\n\n registerHelpCommand(program);\n\n // -------------------------------------------------------------------------\n // DAILY VERBS — top-level\n // -------------------------------------------------------------------------\n\n // pai pause [all] [--dry-run] [--exit] [--wait <ms>]\n program\n .command(\"pause [target]\")\n .description(\n \"Save state and display safe-exit instructions for the current session.\\n\" +\n \"Writes a ## Continue checkpoint to the project's TODO.md.\\n\" +\n \"Use `pai pause all` to pause every live session via AIBroker.\"\n )\n .option(\"--dry-run\", \"Preview changes without writing them\")\n .option(\n \"--body-file <path>\",\n \"File holding the checkpoint markdown to persist ('-' reads stdin). Required unless --no-body.\"\n )\n .option(\n \"--session-id <uuid>\",\n \"Claude Code session UUID — recorded as the `claude --resume` handle\"\n )\n .option(\n \"--no-body\",\n \"Deliberately write a metadata-only checkpoint (no content)\"\n )\n .option(\"--exit\", \"(pause all only) Also send /exit to each session after pausing\")\n .option(\n \"--only <name>\",\n \"(pause all only) Pause just the sessions whose name or id contains <name>.\\n\" +\n \"For rehearsing the real thing against one session before trusting it with\\n\" +\n \"every open tab — a dry run exercises enumeration but never sends anything,\\n\" +\n \"which is exactly how a broken send survived until it was needed.\"\n )\n .option(\n \"--wait <ms>\",\n \"(pause all only) Max milliseconds to wait for a session to finish its\\n\" +\n \"checkpoint before /exit. Not a fixed delay — sessions are exited as soon\\n\" +\n \"as they return to the prompt, and any still busy at the deadline are left\\n\" +\n \"open rather than killed mid-write. (default: 180000)\",\n \"180000\"\n )\n .action(async (target: string | undefined, opts: { dryRun?: boolean; exit?: boolean; wait?: string; only?: string; bodyFile?: string; sessionId?: string; body?: boolean }) => {\n if (target === \"all\") {\n await cmdPauseAll({\n exit: opts.exit,\n dryRun: opts.dryRun,\n only: opts.only,\n wait: opts.wait !== undefined ? parseInt(opts.wait, 10) : undefined,\n });\n } else {\n if (target !== undefined) {\n console.error(err(`Unknown target: ${target}. Did you mean 'pai pause all'?`));\n process.exitCode = 1;\n return;\n }\n // Commander maps `--no-body` to opts.body === false.\n cmdPause(getDb(), {\n dryRun: opts.dryRun,\n bodyFile: opts.bodyFile,\n sessionId: opts.sessionId,\n noBody: opts.body === false,\n });\n }\n });\n\n // pai end [--body-file <path>] [--session-id <uuid>] [--no-body] [--dry-run]\n program\n .command(\"end\")\n .description(\n \"Finalize a session: save state, mark note Completed, display safe-exit instructions.\"\n )\n .option(\"--dry-run\", \"Preview all changes without writing them\")\n .option(\n \"--body-file <path>\",\n \"File holding the checkpoint markdown to persist ('-' reads stdin). Required unless --no-body.\"\n )\n .option(\n \"--session-id <uuid>\",\n \"Claude Code session UUID — recorded as the `claude --resume` handle\"\n )\n .option(\n \"--no-body\",\n \"Deliberately write a metadata-only checkpoint (no content)\"\n )\n .action((opts: { dryRun?: boolean; bodyFile?: string; sessionId?: string; body?: boolean }) => {\n cmdEnd(getDb(), {\n dryRun: opts.dryRun,\n bodyFile: opts.bodyFile,\n sessionId: opts.sessionId,\n noBody: opts.body === false,\n });\n });\n\n // pai clear-names [--dry-run] — recovery: wipe corrupt iTerm/JSON name store\n program\n .command(\"clear-names\")\n .description(\n \"Recovery: wipe corrupted iTerm2 session name state.\\n\" +\n \"Clears ~/.aibroker/session-names.json AND user.paiName from all live iTerm2 sessions.\\n\" +\n \"After running this, use /Name to re-label each tab.\"\n )\n .option(\"--dry-run\", \"Preview what would be cleared without making changes\")\n .action(async (opts: { dryRun?: boolean }) => {\n await cmdClearNames(opts);\n });\n\n // -------------------------------------------------------------------------\n // HIDDEN COMPAT ALIASES\n // -------------------------------------------------------------------------\n\n // pai resume <name> [--dry-run] — hidden; prefer `pai <name>`\n program\n .command(\"resume <name>\", { hidden: true })\n .description(\"Go to a session by name or UUID (prefer: pai <name>)\")\n .option(\"--dry-run\", \"Print the exact argv and cwd, then exit without launching\")\n .action((name: string, opts: { dryRun?: boolean }) => {\n cmdGoto(getDb(), name, { dryRun: opts.dryRun });\n });\n\n // pai cd <identifier> — hidden; shell wrapper uses this\n program\n .command(\"cd <identifier>\", { hidden: true })\n .description(\"cd to a project directory (shell wrapper handles the actual cd)\")\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.exitCode = 1;\n return;\n }\n\n if (existsSync(project.root_path)) {\n process.stdout.write(project.root_path + \"\\n\");\n return;\n }\n\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 // -------------------------------------------------------------------------\n // pai notes — hidden power-user alias; accessible but not in main help\n // -------------------------------------------------------------------------\n\n const notesCmd = program\n .command(\"notes [project-slug]\", { hidden: true })\n .description(\"Markdown session notes — list notes, optionally filtered to a project.\")\n .option(\"--limit <n>\", \"Maximum number of notes to show\", \"20\")\n .option(\"--status <status>\", \"Filter by status: open | completed | compacted\")\n .action(\n (projectSlug: string | undefined, opts: { limit?: string; status?: string }) => {\n cmdNotesList(getDb(), projectSlug, opts);\n }\n );\n\n notesCmd\n .command(\"list [project-slug]\")\n .description(\"List markdown session notes (same as: pai notes)\")\n .option(\"--limit <n>\", \"Maximum number of notes to show\", \"20\")\n .option(\"--status <status>\", \"Filter by status: open | completed | compacted\")\n .action(\n (projectSlug: string | undefined, opts: { limit?: string; status?: string }) => {\n cmdNotesList(getDb(), projectSlug, opts);\n }\n );\n\n // -------------------------------------------------------------------------\n // pai find <query> — hidden power-user alias for history search\n // -------------------------------------------------------------------------\n\n program\n .command(\"find <query>\", { hidden: true })\n .description(\"Search prompt history for matching sessions.\")\n .option(\"-n, --n <count>\", \"Maximum number of sessions to show\", \"20\")\n .option(\"--json\", \"Output as JSON array\")\n .action(async (query: string, opts: { n?: string; json?: boolean }) => {\n await cmdFind(query, opts);\n });\n\n // -------------------------------------------------------------------------\n // pai shell-init — emit shell function for eval \"$(pai shell-init)\"\n // -------------------------------------------------------------------------\n\n program\n .command(\"shell-init\")\n .description('Emit shell integration code. Add to ~/.zshrc: eval \"$(pai shell-init)\"')\n .action(() => {\n process.stdout.write(\n `# PAI shell integration — generated by: pai shell-init\npai() {\n if [[ \"$1\" == \"cd\" ]]; then\n local dir=$(command pai cd \"$2\" 2>/dev/null)\n if [[ -n \"$dir\" && -d \"$dir\" ]]; then\n builtin cd \"$dir\"\n echo \"-> $dir\"\n else\n echo \"Project not found: $2\" >&2\n return 1\n fi\n elif [[ \"$1\" == \"projects\" && \"$2\" == \"cd\" ]]; then\n local dir=$(command pai projects cd \"$3\" 2>/dev/null)\n if [[ -n \"$dir\" && -d \"$dir\" ]]; then\n builtin cd \"$dir\"\n echo \"-> $dir\"\n else\n echo \"Project not found: $3\" >&2\n return 1\n fi\n elif [[ $# -eq 0 ]]; then\n # Bare \\`pai\\` → interactive picker. fzf draws on /dev/tty; the picker writes\n # a chosen directory to PAI_PICK_OUT when you press Ctrl-G (cd only), which we\n # then cd into here (a child process can't change this shell's cwd itself).\n local __pai_f\n __pai_f=$(mktemp -t pai-pick 2>/dev/null) || { command pai; return $?; }\n PAI_PICK_OUT=\"$__pai_f\" command pai\n local __pai_rc=$?\n local __pai_dir=\"\"\n [[ -s \"$__pai_f\" ]] && __pai_dir=$(cat \"$__pai_f\")\n command rm -f \"$__pai_f\"\n if [[ -n \"$__pai_dir\" && -d \"$__pai_dir\" ]]; then\n builtin cd \"$__pai_dir\"\n echo \"-> $__pai_dir\"\n fi\n return $__pai_rc\n else\n command pai \"$@\"\n fi\n}\n\n# Print the working directory after an interactive Claude Code session exits.\n# A SessionEnd hook can't do this reliably: Claude Code does not fire SessionEnd\n# on /exit (anthropics/claude-code#17885) and may reap the hook mid-run (#41577).\n# Running it from the shell, after \\`claude\\` returns, sidesteps both.\nclaude() {\n local __pai_skip=0 __pai_arg\n for __pai_arg in \"$@\"; do\n case \"$__pai_arg\" in\n -p|--print) __pai_skip=1 ;;\n esac\n done\n command claude \"$@\"\n local __pai_rc=$?\n if [[ -t 1 && $__pai_skip -eq 0 ]]; then\n printf '\\\\n\\\\033[2m📂 Working directory:\\\\033[0m %s\\\\n\\\\033[2m cd \"%s\"\\\\033[0m\\\\n' \"$PWD\" \"$PWD\"\n fi\n return $__pai_rc\n}\n`\n );\n });\n\n // -------------------------------------------------------------------------\n // pai [query] [pick] — DEFAULT COMMAND (must be registered LAST)\n // -------------------------------------------------------------------------\n\n program\n .command(\"query [query] [pick]\", { isDefault: true, hidden: true })\n .description(\n \"Find and launch a session by topic, name, or UUID.\\n\" +\n \"No arg → deduped session listing\\n\" +\n \"Name → switch live tab, or resume, or start fresh\\n\" +\n \"UUID → direct resume via filesystem scan\\n\" +\n \"Topic → history search → candidate list → pick #\"\n )\n .option(\"-y, --auto\", \"Auto-pick #1 without prompting\")\n .option(\"--dry-run\", \"Print what would happen without launching\")\n .option(\"-n, --n <count>\", \"Max candidates for history search\", \"20\")\n .option(\"--all\", \"Show all entries including cold / zero-session / archived projects\")\n .option(\"--list\", \"Skip the interactive picker; print the static session listing\")\n .action(async (query: string | undefined, pick: string | undefined, opts: { auto?: boolean; dryRun?: boolean; n?: string; all?: boolean; list?: boolean }) => {\n // No query → interactive fzf picker (projects + sessions, go where you pick).\n // `--list` (or a non-TTY pipe, handled inside cmdPick) falls back to the\n // static listing. Any query still goes through the name/UUID/topic resolver.\n if (query === undefined && !opts.list) {\n await cmdPick(getDb(), { all: opts.all, dryRun: opts.dryRun });\n return;\n }\n const pickN = pick !== undefined ? parseInt(pick, 10) : undefined;\n await cmdMain(getDb(), query, pickN, opts);\n });\n\n // -------------------------------------------------------------------------\n // Error handling\n // -------------------------------------------------------------------------\n\n program.configureOutput({\n writeErr: (str) => process.stderr.write(err(str)),\n });\n\n // Throw (the default exitOverride behaviour) instead of calling\n // process.exit() directly. Commander has already written the help text\n // or the formatted error message via configureOutput/writeErr above by\n // the time this runs; throwing lets that CommanderError propagate up\n // through parseAsync() to the top-level catch in cli/index.ts, which\n // sets process.exitCode and returns instead of exiting immediately.\n // A natural process exit always flushes stdout/stderr, even when either\n // is a pipe — process.exit() does not (see src/cli/lib/exit.ts).\n program.exitOverride();\n\n return program;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAM,YAAkE;CACtE;EAGE,UAAU,GAAG,IAAI,SAAS,IAAI,WAAW;EACzC,SAAS;EACV;CACD;EACE,UAAU,GAAG,IAAI,SAAS,IAAI,KAAK;EACnC,SAAS;EACV;CACD;EACE,UAAU,GAAG,IAAI,KAAK,IAAI,SAAS;EACnC,SAAS;EACV;CACF;;;;;;;;AASD,SAAgB,oBAAoB,UAAsC;CAGxE,MAAM,SAAS,SAAS,SAAS,IAAI,GAAG,WAAW,WAAW;AAE9D,MAAK,MAAM,EAAE,UAAU,aAAa,UAClC,KAAI,OAAO,SAAS,SAAS,CAAE,QAAO;AAMxC,KAAI,OAAO,WAAW,GAAG,IAAI,KAAK,MAAM,CAAE,QAAO;;;;;;;;;ACZnD,SAAS,uBAAuB,UAA0B;CACxD,MAAM,OAAO,SAAS,UAAU,MAAM;CAEtC,MAAM,QAAQ,KAAK,MAAM,mCAAmC;AAC5D,QAAO,QAAQ,MAAM,KAAK;;;;;AAM5B,SAAS,kBAAkB,SAAiB,QAAQ,GAAa;CAC/D,MAAM,WAAqB,EAAE;AAC7B,MAAK,MAAM,KAAK,QAAQ,SAAS,YAAY,EAAE;AAC7C,WAAS,KAAK,EAAE,GAAG;AACnB,MAAI,SAAS,UAAU,MAAO;;AAEhC,QAAO;;;;;;;AAQT,SAAS,sBAAsB,aAA6B;AAE1D,QAAO,QADU,QAAQ,QAAQ,YAAY,CAAC,CACtB;;AAO1B,SAAgB,WAAW,IAAc,MAA4B;CACnE,MAAM,cAAc,YAAY,KAAK,YAAY;CACjD,MAAM,aAAa,YAAY,KAAK,GAAG;AAIvC,KAAI,CAAC,WAAW,YAAY,EAAE;AAC5B,UAAQ,MAAM,IAAI,2BAA2B,cAAc,CAAC;AAC5D,UAAQ,KAAK,EAAE;;AAEjB,KAAI,CAAC,YAAY,SAAS,MAAM,EAAE;AAChC,UAAQ,MAAM,IAAI,iDAAiD,cAAc,CAAC;AAClF,UAAQ,KAAK,EAAE;;AAEjB,KAAI,WAAW,WAAW,EAAE;AAC1B,UAAQ,MAAM,IAAI,+BAA+B,aAAa,CAAC;AAC/D,UAAQ,KAAK,EAAE;;CAKjB,MAAM,cAAc,KAAK,QAAQ,uBAAuB,YAAY;CACpE,MAAM,OAAO,QAAQ,YAAY;AAEjC,KAAI,CAAC,MAAM;AACT,UAAQ,MAAM,IAAI,6CAA6C,YAAY,GAAG,CAAC;AAC/E,UAAQ,KAAK,EAAE;;CAOjB,MAAM,YAAY,oBAAoB,WAAW;AACjD,KAAI,WAAW;AACb,UAAQ,MAAM,IAAI,4BAA4B,aAAa,CAAC;AAC5D,UAAQ,MAAMA,MAAI,aAAa,UAAU,GAAG,CAAC;AAC7C,UAAQ,MAAMA,MAAI,iDAAiD,CAAC;AACpE,UAAQ,KAAK,EAAE;;CAKjB,MAAM,aAAa,UAAU,WAAW;AAIxC,KAHiB,GACd,QAAQ,6EAA6E,CACrF,IAAI,MAAM,YAAY,WAAW,EACtB;AACZ,UAAQ,MACN,IAAI,wBAAwB,KAAK,aAAa,WAAW,0BAA0B,CACpF;AACD,UAAQ,KAAK,EAAE;;AAKjB,qBAAoB,WAAW;CAI/B,MAAM,yBAAQ,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;CACnD,MAAM,iBAAiB,SAAS,YAAY;CAC5C,MAAM,oBAAoB,sBAAsB,YAAY;CAG5D,MAAM,eAAe,UAAU,MAAM,mBAFX,SAAS,kBAAkB,CAEqB;AAG1E,cAAa,aAFQ,KAAK,YAAY,SAAS,aAAa,CAErB;CAKvC,MAAM,WAAW,kBADM,aAAa,aAAa,QAAQ,CACP;CAElD,MAAM,gBAAgB;EACpB;EACA;EACA;EACA;EACA,wBAAwB;EACxB,yBAAyB;EACzB,4BAA4B;EAC5B,gCAAgC;EAChC;EACA;EACA;EACA,SAAS,SAAS,IACd,0CAA0C,SAAS,KAAK,MAAM,KAAK,EAAE,QAAQ,QAAQ,GAAG,GAAG,CAAC,KAAK,KAAK,KACtG,sCAAsC,aAAa;EACvD;EACD,CAAC,KAAK,KAAK;AAEZ,eAAc,KAAK,YAAY,SAAS,YAAY,EAAE,eAAe,QAAQ;CAI7E,MAAM,KAAK,KAAK;AAChB,IAAG,QACD;;mDAGD,CAAC,IAAI,MAAM,aAAa,YAAY,YAAY,IAAI,GAAG;CAIxD,MAAM,iBAAiB,KAAK,mBAAmB,SAAS,UAAU;AAClE,KAAI,WAAW,eAAe,EAAE;AAE9B,iBAAe,gBADE,6BAA6B,KAAK,IAAI,WAAW,MACzB,QAAQ;AACjD,UAAQ,IAAIA,MAAI,wBAAwB,iBAAiB,CAAC;;AAK5D,SAAQ,KAAK;AACb,SAAQ,IAAIC,KAAG,qBAAqBC,OAAK,KAAK,GAAG,CAAC;AAClD,SAAQ,IAAIF,MAAI,mBAAmB,cAAc,CAAC;AAClD,SAAQ,IAAIA,MAAI,mBAAmB,aAAa,CAAC;AACjD,SAAQ,IAAIA,MAAI,mBAAmB,OAAO,CAAC;AAC3C,SAAQ,IAAIA,MAAI,mBAAmB,eAAe,CAAC;AACnD,SAAQ,IAAIA,MAAI,0CAA0C,CAAC;AAC3D,SAAQ,KAAK;AACb,SAAQ,IAAIA,MAAI,cAAc,WAAW,uBAAuB,OAAO,CAAC;;;;;;;;;;;;;;;;;;AC5L1E,SAAgBG,aAAW,IAAc,MAAsC;CAC7E,MAAM,SAAS,GACZ,QAAQ,wCAAwC,CAChD,IAAI,KAAK;AACZ,KAAI,OAAQ,QAAO;CAEnB,MAAM,QAAQ,GACX,QACC;;0BAGD,CACA,IAAI,KAAK;AACZ,KAAI,MAAO,QAAO;CAElB,MAAM,cAAc,GACjB,QAAQ,sDAAsD,CAC9D,IAAI,KAAK;AACZ,KAAI,YAAa,QAAO;AAExB,QAAO,GACJ,QACC;;wCAGD,CACA,IAAI,KAAK;;AAGd,SAAgB,eAAe,IAAc,MAA0B;CACrE,MAAM,UAAUA,aAAW,IAAI,KAAK;AACpC,KAAI,CAAC,QAKH,OAAM,IAAI,MAAM,sBAAsB,OAAO;AAE/C,QAAO;;;;;AAMT,SAAgB,kBAAkB,IAAc,YAA4C;CAC1F,MAAM,MAAM,SAAS,YAAY,GAAG;AACpC,KAAI,CAAC,MAAM,IAAI,IAAI,MAAM,KAAK,OAAO,IAAI,KAAK,YAAY;EACxD,MAAM,OAAO,GAAG,QACd,8DACD,CAAC,KAAK;AACP,MAAI,OAAO,KAAK,OAAQ,QAAO,KAAK,MAAM;;AAE5C,QAAOA,aAAW,IAAI,WAAW;;AAGnC,SAAgB,eAAe,IAAc,WAA6B;AASxE,QARa,GACV,QACC;;;wBAID,CACA,IAAI,UAAU,CACL,KAAK,MAAM,EAAE,KAAK;;AAGhC,SAAgB,kBAAkB,IAAc,WAA6B;AAI3E,QAHa,GACV,QAAQ,gEAAgE,CACxE,IAAI,UAAU,CACL,KAAK,MAAM,EAAE,MAAM;;AAGjC,SAAgB,gBAAgB,IAAc,WAA2B;AAIvE,QAHY,GACT,QAAQ,4DAA4D,CACpE,IAAI,UAAU,CACN;;AAGb,SAAgB,mBAAmB,IAAc,WAAkC;CACjF,MAAM,MAAM,GACT,QACC;yCAED,CACA,IAAI,UAAU;AACjB,QAAO,MAAM,IAAI,aAAa;;AAGhC,SAAgBC,YAAU,IAAc,SAAyB;AAC/D,IAAG,QAAQ,+CAA+C,CAAC,IAAI,QAAQ;AAEvE,QADY,GAAG,QAAQ,qCAAqC,CAAC,IAAI,QAAQ,CAC9D;;;;;AC1Db,SAAS,YAAY,GAAW,GAAmB;CACjD,MAAM,IAAI,EAAE;CACZ,MAAM,IAAI,EAAE;CACZ,MAAM,KAAiB,MAAM,KAAK,EAAE,QAAQ,IAAI,GAAG,GAAG,GAAG,MACvD,MAAM,KAAK,EAAE,QAAQ,IAAI,GAAG,GAAG,GAAG,MAAO,MAAM,IAAI,IAAI,MAAM,IAAI,IAAI,EAAG,CACzE;AACD,MAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,IAAG,GAAG,KACJ,EAAE,IAAI,OAAO,EAAE,IAAI,KACf,GAAG,IAAI,GAAG,IAAI,KACd,IAAI,KAAK,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG;AAGpE,QAAO,GAAG,GAAG;;AAGf,SAAS,mBAAmB,UAAkB,QAAyB;AACrE,QAAO,SAAS,aAAa,CAAC,SAAS,OAAO,aAAa,CAAC;;AAG9D,SAAS,qBAAqB,SAK1B;CACF,MAAM,iBAAiB,KAAK,SAAS,EAAE,WAAW,WAAW;AAC7D,KAAI,CAAC,WAAW,eAAe,CAAE,QAAO,EAAE;CAE1C,MAAM,UAKA,EAAE;CACR,MAAM,cAAc,UAAU,QAAQ,UAAU;AAEhD,KAAI;AACF,OAAK,MAAM,SAAS,YAAY,eAAe,EAAE;GAC/C,MAAM,OAAO,KAAK,gBAAgB,MAAM;AACxC,OAAI;AACF,QAAI,CAAC,SAAS,KAAK,CAAC,aAAa,CAAE;WAC7B;AACN;;AAGF,OAAI,UAAU,eAAe,CAAC,MAAM,WAAW,YAAY,CAAE;GAE7D,MAAM,YAAY,KAAK,MAAM,QAAQ;AACrC,OAAI,CAAC,WAAW,UAAU,CAAE;GAE5B,IAAI,YAAY;AAChB,OAAI;AACF,gBAAY,YAAY,UAAU,CAAC,QAChC,MAAM,EAAE,SAAS,MAAM,IAAI,EAAE,SAAS,OAAO,CAC/C,CAAC;WACI;AAIR,WAAQ,KAAK;IAAE,YAAY;IAAO,UAAU;IAAM;IAAW;IAAW,CAAC;;SAErE;AAIR,QAAO;;AAQT,MAAM,YAAY,IAAI,IAAI;CACxB;CAAQ;CAAgB;CAAS;CAAS;CAAQ;CAAS;CAC3D;CAAU;CAAe;CAAU;CAAQ;CAAO;CAAQ;CAC1D;CACA;CACD,CAAC;;;;;;;;;;AAWF,SAAS,gBACP,KACA,gBACA,UACA,UACA,SACM;AACN,KAAI,KAAK,KAAK,GAAG,SAAU;AAC3B,KAAI,YAAY,EAAG;CAEnB,IAAI;AACJ,KAAI;AACF,YAAU,YAAY,IAAI;SACpB;AACN;;AAGF,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,KAAK,KAAK,GAAG,SAAU;AAC3B,MAAI,MAAM,WAAW,IAAI,CAAE;AAC3B,MAAI,UAAU,IAAI,MAAM,CAAE;EAE1B,MAAM,OAAO,KAAK,KAAK,MAAM;EAC7B,IAAI,QAAQ;AACZ,MAAI;AACF,WAAQ,SAAS,KAAK,CAAC,aAAa;UAC9B;AACN;;AAEF,MAAI,CAAC,MAAO;AAGZ,MAAI,MAAM,aAAa,KAAK,eAAe,aAAa,EAAE;AACxD,WAAQ,KAAK,KAAK;AAElB;;AAGF,kBAAgB,MAAM,gBAAgB,WAAW,GAAG,UAAU,QAAQ;;;;;;;;;;;;;;AAe1E,SAAgB,cAAc,gBAAkE;CAC9F,MAAM,iBAAiB,SAAS,eAAe;CAC/C,MAAM,SAAS,gBAAgB;CAC/B,MAAM,WAAW,KAAK,KAAK,GAAG;CAG9B,MAAM,OAAO,SAAS;CACtB,MAAM,cAAc,OAAO,aAAa,EAAE,EAAE,KAAK,MAAM,YAAY,EAAE,CAAC;CAMtE,SAAS,wBAAwB,GAA0B;EACzD,IAAI,UAAU;AACd,SAAO,MAAM;GACX,MAAM,SAAS,KAAK,SAAS,KAAK;AAClC,OAAI,WAAW,QAAS,QAAO;AAC/B,OAAI,WAAW,KAAM,QAAO;AAC5B,OAAI,WAAW,OAAO,CAAE,QAAO;AAC/B,aAAU;;;CAId,MAAM,eAAe,wBAAwB,eAAe;CAE5D,MAAM,eAAe;EACnB,KAAK,MAAM,MAAM;EACjB,KAAK,MAAM,UAAU;EACrB,KAAK,MAAM,WAAW;EACtB,KAAK,MAAM,YAAY;EAEvB,KAAK,MAAM,QAAQ;EACnB,KAAK,MAAM,SAAS,QAAQ;EAC5B,KAAK,MAAM,QAAQ;EACnB,KAAK,MAAM,WAAW,oBAAoB,sBAAsB;EAEhE,GAAI,eAAe,CAAC,aAAa,GAAG,EAAE;EACvC;CAGD,MAAM,cAAc,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,YAAY,GAAG,aAAa,CAAC,CAAC,CAC/D,KAAK,MAAM;AACV,MAAI;AAAE,UAAO,QAAQ,EAAE;UAAU;AAAE,UAAO;;GAC1C,CACD,QAAQ,MAAmB,MAAM,QAAQ,WAAW,EAAE,CAAC;CAE1D,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,QAAQ,aAAa;AAC9B,MAAI,KAAK,KAAK,GAAG,SAAU;AAC3B,kBAAgB,MAAM,gBAAgB,GAAG,UAAU,QAAQ;;CAI7D,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;AAEpC,KAAI,OAAO,WAAW,EAAG,QAAO,EAAE;AAClC,KAAI,OAAO,WAAW,EAAG,QAAO,EAAE,OAAO,OAAO,IAAI;AACpD,QAAO,EAAE,WAAW,QAAQ;;AAO9B,SAAgB,OACd,IACA,SACA,MACM;CACN,MAAM,WAAW,YAAY,QAAQ;CACrC,MAAM,OAAO,KAAK,QAAQ,aAAa,SAAS;CAChD,MAAM,aAAa,UAAU,SAAS;CACtC,MAAM,cAAc,KAAK,eAAe;CACxC,MAAM,OAAO,KAAK,QAAQ;CAE1B,MAAM,aAAa;EAAC;EAAS;EAAW;EAAmB;EAAW;AACtE,KAAI,CAAC,WAAW,SAAS,KAAK,EAAE;AAC9B,UAAQ,MAAM,IAAI,iBAAiB,KAAK,YAAY,WAAW,KAAK,KAAK,GAAG,CAAC;AAC7E,UAAQ,WAAW;AACnB;;CAOF,MAAM,YAAY,oBAAoB,SAAS;AAC/C,KAAI,WAAW;AACb,UAAQ,MAAM,IAAI,wBAAwB,YAAY,UAAU,GAAG,GAAG,CAAC;AACvE,UAAQ,MAAMC,MAAI,aAAa,UAAU,GAAG,CAAC;AAC7C,UAAQ,MAAMA,MAAI,iEAAiE,CAAC;AACpF,UAAQ,WAAW;AACnB;;AAMF,KAHiB,GACd,QAAQ,0DAA0D,CAClE,IAAI,MAAM,SAAS,EACR;AACZ,UAAQ,MACN,IAAI,qCAAqC,KAAK,YAAY,SAAS,GAAG,CACvE;AACD,UAAQ,WAAW;AACnB;;CAGF,MAAM,UAAU,SAAS,SAAS,CAAC,aAAa;CAMhD,MAAM,UALU,GACb,QACC,6EACD,CACA,IAAI,KAAK,CACY,QACrB,MACC,SAAS,EAAE,UAAU,CAAC,aAAa,KAAK,WACxC,EAAE,KAAK,QAAQ,SAAS,GAAG,KAAK,KAAK,QAAQ,SAAS,GAAG,CAC5D;AACD,KAAI,QAAQ,SAAS,GAAG;AACtB,UAAQ,IAAIC,OAAK,yCAAyC,CAAC;AAC3D,OAAK,MAAM,KAAK,QACd,SAAQ,IAAID,MAAI,KAAKE,OAAK,EAAE,KAAK,CAAC,IAAI,YAAY,EAAE,WAAW,GAAG,GAAG,CAAC;AAExE,UAAQ,IACNF,MACE,iCAAiC,QAAQ,GAAG,KAAK,wBAClD,CACF;AACD,UAAQ,IACNA,MAAI,6BAA6B,KAAK,2BAA2B,CAClE;AACD,UAAQ,KAAK;;CAGf,MAAM,KAAK,KAAK;AAChB,IAAG,QACD;;6CAGD,CAAC,IAAI,MAAM,aAAa,UAAU,YAAY,MAAM,IAAI,GAAG;AAE5D,qBAAoB,SAAS;AAE7B,KAAI;AACF,kBAAgB,UAAU,MAAM,YAAY;SACtC;AAIR,SAAQ,IAAIG,KAAG,kBAAkBD,OAAK,KAAK,GAAG,CAAC;AAC/C,SAAQ,IAAIF,MAAI,mBAAmB,WAAW,CAAC;AAC/C,SAAQ,IAAIA,MAAI,mBAAmB,aAAa,CAAC;AACjD,SAAQ,IAAIA,MAAI,mBAAmB,OAAO,CAAC;;AAG7C,SAAgBI,UACd,IACA,MACM;CACN,IAAI,QAAQ;;;;;;CAMZ,MAAM,SAAoB,EAAE;CAC5B,MAAM,QAAkB,EAAE;AAG1B,KAAI,KAAK,QAAQ;AACf,QAAM,KAAK,eAAe;AAC1B,SAAO,KAAK,KAAK,OAAO;YACf,CAAC,KAAK,IACf,OAAM,KAAK,sBAAsB;AAEnC,KAAI,KAAK,MAAM;AACb,QAAM,KAAK,aAAa;AACxB,SAAO,KAAK,KAAK,KAAK;;AAExB,KAAI,KAAK,KAAK;AACZ,QAAM,KAAK;;;OAGR;AACH,SAAO,KAAK,KAAK,IAAI;;AAGvB,KAAI,MAAM,OAAQ,UAAS,YAAY,MAAM,KAAK,QAAQ;AAC1D,UAAS;CAET,MAAM,OAAO,GAAG,QAAQ,MAAM,CAAC,IAAI,GAAG,OAAO;AAK7C,KAAI,CAAC,KAAK,QAAQ;AAChB,UAAQ,IAAIH,OAAK,qBAAqB,CAAC;AACvC;;CAGF,MAAM,YAAY,KAAK,KAAK,GAAG,MAAM;EACnCD,MAAI,OAAO,IAAI,EAAE,CAAC;EAClBE,OAAK,EAAE,gBAAgB,EAAE,KAAK;EAC9BF,MAAI,EAAE,KAAK;EACXA,MAAI,YAAY,EAAE,WAAW,GAAG,CAAC;EACjC,EAAE,WAAW,WAAW,MAAM,MAAM,EAAE,OAAO,GAAG,MAAM,OAAO,EAAE,OAAO;EACtEA,MAAI,EAAE,KAAK;EACX,OAAO,EAAE,cAAc;EACvB,QAAQ,EAAE,YAAY;EACvB,CAAC;AAEF,SAAQ,IACN,YACE;EAAC;EAAK;EAAQ;EAAQ;EAAQ;EAAU;EAAQ;EAAY;EAAc,EAC1E,UACD,CACF;AACD,SAAQ,KAAK;CAIb,MAAM,cADW,GAAG,QAAQ,uCAAuC,CAAC,KAAK,CAC5C,MAAM,KAAK;AACxC,KAAI,CAAC,KAAK,OAAO,CAAC,KAAK,UAAU,cAAc,EAC7C,SAAQ,IAAIA,MAAI,KAAK,KAAK,OAAO,uBAAuB,YAAY,gCAAgC,CAAC;KAErG,SAAQ,IAAIA,MAAI,KAAK,KAAK,OAAO,aAAa,CAAC;;AAInD,SAAgBK,UAAQ,IAAc,YAA0B;CAC9D,MAAM,UACJ,kBAAkB,IAAI,WAAW,IAAI,eAAe,IAAI,WAAW;CACrE,MAAM,OAAO,eAAe,IAAI,QAAQ,GAAG;CAC3C,MAAM,UAAU,kBAAkB,IAAI,QAAQ,GAAG;CACjD,MAAM,eAAe,gBAAgB,IAAI,QAAQ,GAAG;CACpD,MAAM,cAAc,mBAAmB,IAAI,QAAQ,GAAG;CAEtD,MAAM,iBAAiB,GACpB,QACC;yCAED,CACA,IAAI,QAAQ,GAAG;AAElB,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,KAAK,QAAQ,eAAe,CAAC;AAChD,SAAQ,KAAK;AACb,SAAQ,IAAI,KAAKH,OAAK,QAAQ,CAAC,WAAW,QAAQ,OAAO;AACzD,SAAQ,IAAI,KAAKA,OAAK,QAAQ,CAAC,WAAW,QAAQ,YAAY;AAC9D,SAAQ,IAAI,KAAKA,OAAK,eAAe,CAAC,IAAI,QAAQ,cAAc;AAChE,SAAQ,IAAI,KAAKA,OAAK,QAAQ,CAAC,WAAW,QAAQ,OAAO;AACzD,SAAQ,IACN,KAAKA,OAAK,UAAU,CAAC,SACnB,QAAQ,WAAW,WACf,MAAM,MAAM,QAAQ,OAAO,GAC3B,MAAM,OAAO,QAAQ,OAAO,GAEnC;AACD,SAAQ,IACN,KAAKA,OAAK,QAAQ,CAAC,WACjB,KAAK,SAAS,KAAK,KAAK,MAAM,MAAM,KAAK,EAAE,CAAC,CAAC,KAAK,KAAK,GAAGF,MAAI,OAAO,GAExE;AACD,SAAQ,IACN,KAAKE,OAAK,WAAW,CAAC,QAAQ,QAAQ,SAAS,QAAQ,KAAK,KAAK,GAAGF,MAAI,OAAO,GAChF;AACD,SAAQ,IAAI,KAAKE,OAAK,YAAY,CAAC,OAAO,eAAe;AACzD,SAAQ,IAAI,KAAKA,OAAK,eAAe,CAAC,IAAI,QAAQ,YAAY,GAAG;AACjE,SAAQ,IAAI,KAAKA,OAAK,WAAW,CAAC,QAAQ,QAAQ,QAAQ,WAAW,GAAG;AACxE,KAAI,QAAQ,YACV,SAAQ,IAAI,KAAKA,OAAK,YAAY,CAAC,OAAO,QAAQ,QAAQ,YAAY,GAAG;AAG3E,KAAI,eAAe,QAAQ;AACzB,UAAQ,KAAK;AACb,UAAQ,IAAI,KAAKA,OAAK,mBAAmB,GAAG;EAC5C,MAAM,cAAc,eAAe,KAAK,MAAM;GAC5CF,MAAI,IAAI,EAAE,SAAS;GACnB,EAAE;GACF,EAAE,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,GAAG,GAAG,GAAG,QAAQ,EAAE;GACvD,EAAE,WAAW,cACT,MAAM,MAAM,EAAE,OAAO,GACrB,MAAM,OAAO,EAAE,OAAO;GAC3B,CAAC;AACF,UAAQ,IACN,YAAY;GAAC;GAAK;GAAQ;GAAS;GAAS,EAAE,YAAY,CACvD,MAAM,KAAK,CACX,KAAK,MAAM,OAAO,EAAE,CACpB,KAAK,KAAK,CACd;;AAEH,SAAQ,KAAK;;AAGf,SAAgB,WAAW,IAAc,MAAoB;CAC3D,MAAM,UAAU,eAAe,IAAI,KAAK;AACxC,KAAI,QAAQ,WAAW,YAAY;AACjC,UAAQ,IAAIC,OAAK,WAAW,KAAK,uBAAuB,CAAC;AACzD;;CAEF,MAAM,KAAK,KAAK;AAChB,IAAG,QACD,wFACD,CAAC,IAAI,IAAI,IAAI,QAAQ,GAAG;AACzB,SAAQ,IAAIE,KAAG,aAAaD,OAAK,KAAK,GAAG,CAAC;;AAG5C,SAAgB,aAAa,IAAc,MAAoB;CAC7D,MAAM,UAAU,eAAe,IAAI,KAAK;AACxC,KAAI,QAAQ,WAAW,YAAY;AACjC,UAAQ,IACND,OAAK,WAAW,KAAK,4BAA4B,QAAQ,OAAO,IAAI,CACrE;AACD;;CAEF,MAAM,KAAK,KAAK;AAChB,IAAG,QACD,yFACD,CAAC,IAAI,IAAI,QAAQ,GAAG;AACrB,SAAQ,IAAIE,KAAG,eAAeD,OAAK,KAAK,GAAG,CAAC;;AAG9C,SAAgB,QAAQ,IAAc,MAAc,SAAuB;CACzE,MAAM,UAAU,eAAe,IAAI,KAAK;CACxC,MAAM,cAAc,YAAY,QAAQ;CACxC,MAAM,aAAa,UAAU,YAAY;CACzC,MAAM,KAAK,KAAK;AAEhB,IAAG,QACD,kFACD,CAAC,IAAI,aAAa,YAAY,IAAI,QAAQ,GAAG;AAE9C,SAAQ,IAAIC,KAAG,UAAUD,OAAK,KAAK,GAAG,CAAC;AACvC,SAAQ,IAAIF,MAAI,eAAe,QAAQ,YAAY,CAAC;AACpD,SAAQ,IAAIA,MAAI,eAAe,cAAc,CAAC;;AAGhD,SAAgBM,SACd,IACA,MACA,MACM;CACN,MAAM,UAAU,eAAe,IAAI,KAAK;CACxC,MAAM,QAAkB,EAAE;CAC1B,MAAM,UAAoB,EAAE;AAE5B,MAAK,MAAM,WAAW,MAAM;EAC1B,MAAM,QAAQC,YAAU,IAAI,QAAQ;AAMpC,MALe,GACZ,QACC,iEACD,CACA,IAAI,QAAQ,IAAI,MAAM,CAEvB,SAAQ,KAAK,QAAQ;OAChB;AACL,MAAG,QACD,8DACD,CAAC,IAAI,QAAQ,IAAI,MAAM;AACxB,SAAM,KAAK,QAAQ;;;AAIvB,KAAI,MAAM,OACR,SAAQ,IACNJ,KACE,UAAUD,OAAK,KAAK,CAAC,IAAI,MAAM,KAAK,MAAM,MAAM,KAAK,EAAE,CAAC,CAAC,KAAK,KAAK,GACpE,CACF;AAEH,KAAI,QAAQ,OACV,SAAQ,IAAIF,MAAI,sBAAsB,QAAQ,KAAK,KAAK,GAAG,CAAC;;AAIhE,SAAgB,SACd,IACA,MACA,OACM;AACN,gBAAe,IAAI,KAAK;AAKxB,KAHiB,GACd,QAAQ,yCAAyC,CACjD,IAAI,MAAM,EACC;AACZ,UAAQ,MACN,IAAI,IAAI,MAAM,oDAAoD,CACnE;AACD,UAAQ,WAAW;AACnB;;CAGF,MAAM,UAAUQ,aAAW,IAAI,KAAK;AACpC,KAAI;AACF,KAAG,QACD,wDACD,CAAC,IAAI,OAAO,QAAQ,GAAG;AACxB,UAAQ,IAAIL,KAAG,gBAAgBD,OAAK,MAAM,CAAC,KAAK,OAAO,CAAC;SAClD;AACN,UAAQ,MAAM,IAAI,UAAU,MAAM,0BAA0B,CAAC;AAC7D,UAAQ,WAAW;;;AAIvB,SAAgB,QACd,IACA,MACA,MACM;CACN,MAAM,UAAU,eAAe,IAAI,KAAK;AAExC,KAAI,CAAC,KAAK,eAAe,CAAC,KAAK,MAAM;AACnC,UAAQ,IAAID,OAAK,mDAAmD,CAAC;AACrE;;CAGF,MAAM,aAAa;EAAC;EAAS;EAAW;EAAmB;EAAW;AACtE,KAAI,KAAK,QAAQ,CAAC,WAAW,SAAS,KAAK,KAAK,EAAE;AAChD,UAAQ,MACN,IAAI,iBAAiB,KAAK,KAAK,YAAY,WAAW,KAAK,KAAK,GAAG,CACpE;AACD,UAAQ,WAAW;AACnB;;CAGF,MAAM,KAAK,KAAK;AAChB,KAAI,KAAK,aAAa;AACpB,KAAG,QACD,oEACD,CAAC,IAAI,KAAK,aAAa,IAAI,QAAQ,GAAG;AACvC,UAAQ,IAAIE,KAAG,yBAAyBD,OAAK,KAAK,YAAY,GAAG,CAAC;;AAEpE,KAAI,KAAK,MAAM;AACb,KAAG,QACD,4DACD,CAAC,IAAI,KAAK,MAAM,IAAI,QAAQ,GAAG;AAChC,UAAQ,IAAIC,KAAG,iBAAiBD,OAAK,KAAK,KAAK,GAAG,CAAC;;;AAIvD,SAAgB,UACd,IACA,SACA,MACM;CACN,MAAM,MAAM,UAAU,YAAY,QAAQ,GAAG,QAAQ,KAAK;CAC1D,MAAM,YAAY,cAAc,IAAI,IAAI;AAExC,KAAI,CAAC,WAAW;AACd,MAAI,KAAK,KACP,SAAQ,IAAI,KAAK,UAAU;GAAE,OAAO;GAAY;GAAK,EAAE,MAAM,EAAE,CAAC;OAC3D;AACL,WAAQ,IAAID,OAAK,oCAAoC,MAAM,CAAC;AAC5D,WAAQ,IAAID,MAAI,wDAAwD,CAAC;;AAE3E;;AAGF,KAAI,KAAK,MAAM;AACb,UAAQ,IAAI,oBAAoB,UAAU,CAAC;AAC3C;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,6BAA6B,CAAC;AACjD,SAAQ,KAAK;AACb,SAAQ,IACN,gBAAgB,UAAU,CACvB,MAAM,KAAK,CACX,KAAK,MAAM,OAAO,EAAE,CACpB,KAAK,KAAK,CACd;AACD,SAAQ,KAAK;;AAGf,SAAgB,eACd,IACA,YACA,MACM;CACN,MAAM,UACJ,kBAAkB,IAAI,WAAW,IAAI,eAAe,IAAI,WAAW;AAErE,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,kBAAkB,QAAQ,OAAO,CAAC;AACrD,SAAQ,IAAI,cAAc,QAAQ,YAAY;AAC9C,SAAQ,KAAK;CAEb,MAAM,OAAO,qBAAqB,QAAQ;AAE1C,KAAI,KAAK,WAAW,GAAG;AACrB,UAAQ,IAAIC,OAAK,2DAA2D,CAAC;AAC7E;;CAGF,MAAM,iBAAiB,KAAK,QAAQ,WAAW,QAAQ;CACvD,MAAM,UAAU,KAAK,QAAQ,MAAM,EAAE,cAAc,eAAe;AAElE,KAAI,QAAQ,WAAW,GAAG;AACxB,UAAQ,IAAIE,KAAG,qDAAqD,CAAC;AACrE,UAAQ,IAAIH,MAAI,KAAK,iBAAiB,CAAC;AACvC;;AAGF,SAAQ,IACN,WAAW,QAAQ,OAAO,iDAC3B;AACD,SAAQ,KAAK;AAEb,MAAK,MAAM,KAAK,SAAS;AACvB,UAAQ,IAAI,OAAOE,OAAK,EAAE,WAAW,GAAG;AACxC,UAAQ,IAAIF,MAAI,gBAAgB,EAAE,UAAU,IAAI,EAAE,UAAU,WAAW,CAAC;;AAG1E,SAAQ,KAAK;AACb,SAAQ,IAAI,kBAAkB,iBAAiB;AAC/C,SAAQ,KAAK;AAEb,KAAI,KAAK,QAAQ;AACf,UAAQ,IAAIC,OAAK,4DAA4D,CAAC;AAC9E;;AAGF,KAAI,CAAC,KAAK,KAAK;AACb,UAAQ,IACNA,OACE,8EACD,CACF;AACD;;AAGF,WAAU,gBAAgB,EAAE,WAAW,MAAM,CAAC;CAE9C,IAAI,aAAa;AACjB,MAAK,MAAM,KAAK,QACd,KAAI;EACF,MAAM,QAAQ,YAAY,EAAE,UAAU;AACtC,OAAK,MAAM,KAAK,OAAO;AACrB,OAAI,CAAC,EAAE,SAAS,MAAM,IAAI,CAAC,EAAE,SAAS,OAAO,CAAE;GAC/C,MAAM,MAAM,KAAK,EAAE,WAAW,EAAE;GAChC,MAAM,OAAO,KAAK,gBAAgB,EAAE;AACpC,OAAI,CAAC,WAAW,KAAK,EAAE;AACrB,eAAW,KAAK,KAAK;AACrB,YAAQ,IAAIE,KAAG,cAAc,IAAI,CAAC;AAClC;SAEA,SAAQ,IAAIF,OAAK,yBAAyB,IAAI,CAAC;;UAG5C,GAAG;AACV,UAAQ,MAAM,IAAI,qBAAqB,EAAE,UAAU,IAAI,IAAI,CAAC;;AAIhE,SAAQ,KAAK;AACb,SAAQ,IAAIE,KAAG,kBAAkB,WAAW,gBAAgB,iBAAiB,CAAC;;;;;;;;;AAUhF,SAAS,uBAAuB,IAAc,SAAyC;AACrF,SAAQ,OAAO,MACbF,OAAK,mBAAmB,QAAQ,UAAU,IAAI,GAC9CD,MAAI,sCAAsC,CAC3C;CAED,MAAM,SAAS,cAAc,QAAQ,UAAU;AAE/C,KAAI,OAAO,OAAO;EAChB,MAAM,UAAU,OAAO;EACvB,MAAM,aAAa,UAAU,QAAQ;EACrC,MAAM,KAAK,KAAK;AAChB,KAAG,QACD,kFACD,CAAC,IAAI,SAAS,YAAY,IAAI,QAAQ,GAAG;AAC1C,UAAQ,OAAO,MACbG,KAAG,kBAAkB,YAAY,QAAQ,WAAW,GAAG,CAAC,IAAI,GAC5DH,MAAI,OAAO,QAAQ,IAAI,GACvBG,KAAG,sBAAsB,CAC1B;AACD,SAAO;;AAGT,KAAI,OAAO,WAAW;AACpB,UAAQ,OAAO,MACbF,OAAK,+BAA+B,SAAS,QAAQ,UAAU,CAAC,YAAY,CAC7E;AACD,OAAK,MAAM,aAAa,OAAO,UAC7B,SAAQ,OAAO,MAAMD,MAAI,KAAK,UAAU,IAAI,CAAC;AAE/C,UAAQ,OAAO,MACbA,MAAI,8CAA8C,QAAQ,KAAK,WAAW,CAC3E;AACD;;AAGF,SAAQ,OAAO,MACb,IACE,YAAY,QAAQ,KAAK,eAAe,QAAQ,UAAU,mDAChC,SAAS,QAAQ,UAAU,CAAC,6BACvD,GACDA,MAAI,mCAAmC,QAAQ,KAAK,eAAe,CACpE;;AAIH,SAAgB,MAAM,IAAc,OAAqB;CACvD,MAAM,MAAM,GACT,QACC,0EACD,CACA,KAAK;AAER,KAAI,CAAC,IAAI,QAAQ;AACf,UAAQ,MACN,IAAI,6DAA6D,CAClE;AACD,UAAQ,WAAW;AACnB;;CAGF,MAAM,IAAI,MAAM,MAAM,CAAC,aAAa;CAGpC,MAAM,QAAQQ,aAAW,IAAI,MAAM;AACnC,KAAI,OAAO;AACT,MAAI,CAAC,WAAW,MAAM,UAAU,EAAE;GAChC,MAAM,YAAY,uBAAuB,IAAI,MAAM;AACnD,OAAI,CAAC,WAAW;AAAE,YAAQ,WAAW;AAAG;;AACxC,WAAQ,OAAO,MAAM,YAAY,KAAK;AACtC;;AAEF,UAAQ,OAAO,MAAM,MAAM,YAAY,KAAK;AAC5C;;CAIF,MAAM,UAAU,IAAI,QACjB,MACC,mBAAmB,EAAE,MAAM,EAAE,IAC7B,mBAAmB,EAAE,cAAc,EAAE,IACrC,mBAAmB,SAAS,EAAE,UAAU,EAAE,EAAE,CAC/C;AAED,KAAI,QAAQ,WAAW,GAAG;EACxB,MAAM,IAAI,QAAQ;AAClB,MAAI,CAAC,WAAW,EAAE,UAAU,EAAE;GAC5B,MAAM,YAAY,uBAAuB,IAAI,EAAE;AAC/C,OAAI,CAAC,WAAW;AAAE,YAAQ,WAAW;AAAG;;AACxC,WAAQ,OAAO,MAAM,YAAY,KAAK;AACtC;;AAEF,UAAQ,OAAO,MAAM,EAAE,YAAY,KAAK;AACxC;;AAGF,KAAI,QAAQ,SAAS,GAAG;AACtB,UAAQ,MACN,IAAI,eAAe,MAAM,YAAY,QAAQ,OAAO,cAAc,CACnE;AACD,UAAQ,SAAS,GAAG,MAAM;AACxB,WAAQ,MACN,KAAKR,MAAI,OAAO,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,IAAIE,OAAK,EAAE,KAAK,OAAO,GAAG,CAAC,CAAC,IAAIF,MAClE,YAAY,EAAE,WAAW,GAAG,CAC7B,GACF;IACD;AACF,UAAQ,OAAO;AACf,UAAQ,MAAMA,MAAI,gDAAgD,CAAC;AACnE,UAAQ,WAAW;AACnB;;CAIF,MAAM,SAAS,IACZ,KAAK,MAAM;EACV,MAAM,WAAW,YAAY,GAAG,EAAE,KAAK,aAAa,CAAC;EACrD,MAAM,WAAW,YAAY,GAAG,EAAE,aAAa,aAAa,CAAC;AAC7D,SAAO;GAAE,SAAS;GAAG,MAAM,KAAK,IAAI,UAAU,SAAS;GAAE;GACzD,CACD,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK;CAElC,MAAM,YAAY;CAClB,MAAM,cACJ,OAAO,QAAQ,MAAM,EAAE,QAAQ,UAAU,CAAC,SAAS,IAC/C,OAAO,QAAQ,MAAM,EAAE,QAAQ,UAAU,CAAC,MAAM,GAAG,EAAE,GACrD,OAAO,MAAM,GAAG,EAAE;AAExB,SAAQ,MAAM,IAAI,uBAAuB,MAAM,KAAK,CAAC;AACrD,KAAI,YAAY,QAAQ;AACtB,UAAQ,MAAMC,OAAK,kBAAkB,CAAC;AACtC,OAAK,MAAM,KAAK,YACd,SAAQ,MACN,OAAOC,OAAK,EAAE,QAAQ,KAAK,OAAO,GAAG,CAAC,CAAC,IAAIF,MACzC,YAAY,EAAE,QAAQ,WAAW,GAAG,CACrC,GACF;AAEH,UAAQ,OAAO;AACf,UAAQ,MAAMA,MAAI,iDAAiD,CAAC;;AAEtE,SAAQ,WAAW;;AAGrB,SAAgB,UACd,IACA,MACA,SACM;CACN,MAAM,UAAU,eAAe,IAAI,KAAK;CACxC,MAAM,WAAW,QAAQ,QAAQ,WAAW,KAAK,GAAG,KAAK,SAAS,EAAE,QAAQ,MAAM,EAAE,CAAC,GAAG,QAAQ;AAEhG,KAAI,CAAC,WAAW,SAAS,EAAE;AACzB,UAAQ,MAAM,IAAI,wBAAwB,WAAW,CAAC;AACtD,UAAQ,WAAW;AACnB;;CAGF,IAAI,QAAQ;AACZ,KAAI;AACF,UAAQ,SAAS,SAAS,CAAC,aAAa;SAClC;AAGR,KAAI,CAAC,OAAO;AACV,UAAQ,MAAM,IAAI,4BAA4B,WAAW,CAAC;AAC1D,UAAQ,WAAW;AACnB;;CAGF,MAAM,aAAa,UAAU,SAAS;CAGtC,MAAM,WAAW,GACd,QAAQ,8DAA8D,CACtE,IAAI,YAAY,QAAQ,GAAG;AAC9B,KAAI,UAAU;AACZ,UAAQ,MACN,IAAI,0CAA0CE,OAAK,SAAS,KAAK,CAAC,IAAI,GACtEF,MAAI,KAAK,SAAS,IAAI,GACtBA,MAAI,oEAAoE,CACzE;AACD,UAAQ,WAAW;AACnB;;CAGF,MAAM,KAAK,KAAK;AAChB,IAAG,QACD,kFACD,CAAC,IAAI,UAAU,YAAY,IAAI,QAAQ,GAAG;AAE3C,SAAQ,IAAIG,KAAG,YAAYD,OAAK,KAAK,GAAG,CAAC;AACzC,SAAQ,IAAIF,MAAI,eAAe,QAAQ,YAAY,CAAC;AACpD,SAAQ,IAAIA,MAAI,eAAe,WAAW,CAAC;AAC3C,SAAQ,IAAIA,MAAI,eAAe,aAAa,CAAC;;;;;AC/6B/C,MAAa,iBAAiC;CAC5C;EAAE,KAAK;EAAc,MAAM;EAAU,aAAa;EAA+C,UAAU;GAAC;GAAQ;GAAW;GAAU;EAAE;CAC3I;EAAE,KAAK;EAAS,MAAM;EAAU,aAAa;EAAwB,UAAU,CAAC,kCAAkC,2BAA2B;EAAE;CAC/I;EAAE,KAAK;EAAO,MAAM;EAAU,aAAa;EAA4C,UAAU,CAAC,gBAAgB,oBAAoB;EAAE;CACxI;EAAE,KAAK;EAAa,MAAM;EAAW,aAAa;EAAgD,UAAU,CAAC,QAAQ,QAAQ;EAAE;CAC/H;EAAE,KAAK;EAAU,MAAM;EAAU,aAAa;EAA2C,UAAU;GAAC;GAAM;GAAY;GAAY;EAAE;CACpI;EAAE,KAAK;EAAS,MAAM;EAAU,aAAa;EAAkC,UAAU;GAAC;GAAQ;GAAU;GAAQ;EAAE;CACvH;AAED,MAAa,qBAA6D;CACxE,MAAM;EAAE,YAAY;EAAQ,OAAO;EAAkC,KAAK,EAAE,YAAY,KAAK;EAAE,WAAW;EAAM,QAAQ;EAAM;CAC9H,SAAS;EAAE,YAAY;EAAW,OAAO;EAAI,KAAK,EAAE;EAAE,WAAW;EAAM,QAAQ;EAAM;CACrF,SAAS;EAAE,YAAY;EAAW,OAAO;EAAI,KAAK,EAAE;EAAE,WAAW;EAAO;CACzE;AAMD,SAAgB,iBAAiB,SAAoC;AACnE,QAAO,QAAQ,iBAAiB,KAAK,MAAM,QAAQ,eAAe,GAAG,EAAE;;AAGzE,SAAgB,oBAAmC;AACjD,KAAI;EACF,MAAM,aAAa,KAAK,SAAS,EAAE,WAAW,OAAO,cAAc;AACnE,MAAI,WAAW,WAAW,CAExB,QADe,KAAK,MAAM,aAAa,YAAY,QAAQ,CAAC,CAC9C,mBAAmB,EAAE;SAE/B;AACR,QAAO,EAAE;;AAGX,SAAgB,mBAAmB,UAA+B;CAChE,MAAM,aAAa,KAAK,SAAS,EAAE,WAAW,OAAO,cAAc;CACnE,IAAI,SAAkC,EAAE;AACxC,KAAI;AACF,MAAI,WAAW,WAAW,CACxB,UAAS,KAAK,MAAM,aAAa,YAAY,QAAQ,CAAC;SAElD;AACR,QAAO,kBAAkB;AACzB,WAAU,KAAK,SAAS,EAAE,WAAW,MAAM,EAAE,EAAE,WAAW,MAAM,CAAC;AACjE,eAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,EAAE,GAAG,KAAK;;AAGnE,SAAS,YAAY,QAAuB,QAA+B;CACzE,MAAM,eAAe,mBAAmB;AACxC,KAAI,CAAC,aACH,QAAO;EAAE,GAAG;EAAQ,YAAY;EAAU,OAAO;EAAQ;AAE3D,QAAO;EAAE,GAAG;EAAQ,GAAG;EAAc;;AAGvC,SAAS,iBAAiB,KAAa,OAAwB;CAC7D,MAAM,SAAS,eAAe,MAAK,MAAK,EAAE,QAAQ,IAAI;AACtD,KAAI,CAAC,OAAQ,QAAO;AAEpB,SAAQ,OAAO,MAAf;EACE,KAAK,UACH,QAAO,UAAU,UAAU,UAAU,OAAO,UAAU;EACxD,KAAK;AACH,OAAI,QAAQ,OAAO;IACjB,MAAM,CAAC,GAAG,GAAG,UAAU,MAAM,MAAM,IAAI;AACvC,WAAO,GAAG,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK;;AAEzC,UAAO;EACT,QACE,QAAO;;;AAQb,SAAgB,QACd,IACA,YACA,WACA,MACM;CACN,MAAM,UAAU,kBAAkB,IAAI,WAAW,IAAI,eAAe,IAAI,WAAW;AAEnF,KAAI,CAAC,2BAA2B,KAAK,UAAU,EAAE;AAC/C,UAAQ,MAAM,IAAI,iBAAiB,UAAU,yEAAyE,CAAC;AACvH,UAAQ,WAAW;AACnB;;AAMF,KAHwB,GACrB,QAAQ,qDAAqD,CAC7D,IAAI,WAAW,QAAQ,GAAG,EACR;AACnB,UAAQ,MAAM,IAAI,IAAI,UAAU,8BAA8B,CAAC;AAC/D,UAAQ,WAAW;AACnB;;CAGF,MAAM,gBAAgB,GACnB,QAAQ,iDAAiD,CACzD,IAAI,UAAU;AACjB,KAAI,iBAAiB,cAAc,eAAe,QAAQ,IAAI;AAC5D,UAAQ,MAAM,IAAI,IAAI,UAAU,uCAAuC,CAAC;AACxE,UAAQ,WAAW;AACnB;;AAGF,KAAI,CAAC,cACH,IAAG,QAAQ,wDAAwD,CAAC,IAAI,WAAW,QAAQ,GAAG;AAGhG,KAAI,KAAK,YAAY;EAEnB,MAAM,SAAS,YADE,iBAAiB,QAAQ,EACL,KAAK,WAAW;AACrD,KAAG,QAAQ,sEAAsE,CAC9E,IAAI,KAAK,UAAU,OAAO,EAAE,KAAK,EAAE,QAAQ,GAAG;;AAGnD,SAAQ,IAAIS,KAAG,UAAUC,OAAK,UAAU,CAAC,KAAK,QAAQ,KAAK,IAAI,YAAY,QAAQ,WAAW,GAAG,CAAC,GAAG,CAAC;AACtG,KAAI,KAAK,WACP,SAAQ,IAAIC,MAAI,iBAAiB,KAAK,aAAa,CAAC;;AAIxD,SAAgB,UAAU,IAAc,WAAyB;CAC/D,MAAM,QAAQ,GACX,QAAQ,iDAAiD,CACzD,IAAI,UAAU;AAEjB,KAAI,CAAC,OAAO;AACV,UAAQ,MAAM,IAAI,4BAA4B,UAAU,GAAG,CAAC;AAC5D,UAAQ,WAAW;AACnB;;AAGF,IAAG,QAAQ,sCAAsC,CAAC,IAAI,UAAU;CAEhE,MAAM,YAAY,GACf,QAAQ,2DAA2D,CACnE,IAAI,MAAM,WAAW;AAExB,SAAQ,IAAIF,KAAG,iBAAiBC,OAAK,UAAU,GAAG,CAAC;AACnD,KAAI,UAAU,QAAQ,EACpB,SAAQ,IAAIC,MAAI,oCAAoC,CAAC;;AAIzD,SAAgB,SAAS,IAAc,MAA+C;CAIpF,MAAM,OAAO,GAAG,QAAQ;;;;;MAKpB,KAAK,MAAM,cAAc,OAAO;;;IAGlC,CAAC,KAAK;AAER,KAAI,KAAK,MAAM;EACb,MAAM,0BAAU,IAAI,KAAsB;AAC1C,OAAK,MAAM,OAAO,KAChB,KAAI,CAAC,QAAQ,IAAI,IAAI,GAAG,CACtB,SAAQ,IAAI,IAAI,IAAI;GAClB,MAAM,IAAI,QAAQ,IAAI;GACtB,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,GAAG,EAAE;GACjC,MAAM,IAAI;GACV,cAAc,IAAI;GAClB,WAAW,IAAI;GACf,eAAe,IAAI;GACnB,aAAa,IAAI,cAAc,IAAI,KAAK,IAAI,YAAY,CAAC,aAAa,GAAG;GACzE,gBAAgB,IAAI,iBAAiB,KAAK,MAAM,IAAI,eAAe,GAAG;GACvE,CAAC;WACO,IAAI,KACb,CAAC,QAAQ,IAAI,IAAI,GAAG,CAAyB,MAAM,KAAK,IAAI,KAAK;AAGrE,UAAQ,IAAI,KAAK,UAAU,CAAC,GAAG,QAAQ,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;AAC3D;;AAGF,KAAI,CAAC,KAAK,QAAQ;AAChB,UAAQ,IAAIC,OAAK,wEAAwE,CAAC;AAC1F;;CAGF,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,YAAwB,EAAE;AAChC,MAAK,MAAM,OAAO,MAAM;AACtB,MAAI,KAAK,IAAI,IAAI,GAAG,CAAE;AACtB,OAAK,IAAI,IAAI,GAAG;EAEhB,MAAM,WAAW,KAAK,QAAO,MAAK,EAAE,OAAO,IAAI,GAAG,CAAC,KAAI,MAAK,EAAE,KAAK,CAAC,OAAO,QAAQ;EAEnF,MAAM,QADS,IAAI,iBAAiB,KAAK,MAAM,IAAI,eAAe,GAAoB,OACjE,cAAcD,MAAI,UAAU;AAEjD,YAAU,KAAK;GACbD,OAAK,SAAS,SAAS,SAAS,KAAK,KAAK,GAAG,IAAI,KAAK;GACtD,IAAI;GACJC,MAAI,YAAY,IAAI,WAAW,GAAG,CAAC;GACnC,OAAO,SAAS,WAAW,OAAOA,MAAI,UAAU;GAChD,OAAO,IAAI,cAAc;GACzB,QAAQ,IAAI,YAAY;GACzB,CAAC;;AAGJ,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,mBAAmB,CAAC;AACvC,SAAQ,KAAK;AACb,SAAQ,IAAI,YAAY;EAAC;EAAW;EAAQ;EAAQ;EAAc;EAAY;EAAc,EAAE,UAAU,CAAC;AACzG,SAAQ,KAAK;AACb,SAAQ,IAAIA,MAAI,KAAK,UAAU,OAAO,mBAAmB,CAAC;;AAG5D,SAAgB,UACd,IACA,YACA,MASM;AAEN,KAAI,KAAK,SAAS;AAChB,MAAI,KAAK,MAAM;AACb,WAAQ,IAAI,KAAK,UAAU;IACzB,SAAS;IACT,SAAS,OAAO,QAAQ,mBAAmB,CAAC,KAAK,CAAC,MAAM,aAAa;KAAE;KAAM,GAAG;KAAQ,EAAE;IAC3F,EAAE,MAAM,EAAE,CAAC;AACZ;;AAGF,UAAQ,KAAK;AACb,UAAQ,IAAI,OAAO,2BAA2B,CAAC;AAC/C,UAAQ,KAAK;AACb,UAAQ,IAAID,OAAK,oBAAoB,CAAC;AACtC,UAAQ,KAAK;AACb,OAAK,MAAM,OAAO,gBAAgB;AAChC,WAAQ,IAAI,OAAOA,OAAK,IAAI,IAAI,OAAO,GAAG,CAAC,CAAC,GAAGC,MAAI,IAAI,IAAI,KAAK,GAAG,CAAC,IAAI,IAAI,cAAc;AAC1F,WAAQ,IAAI,OAAO,IAAI,OAAO,GAAG,CAAC,UAAUA,MAAI,OAAO,CAAC,GAAG,IAAI,SAAS,KAAI,MAAK,MAAM,KAAK,EAAE,CAAC,CAAC,KAAK,KAAK,GAAG;;AAE/G,UAAQ,KAAK;AACb,UAAQ,IAAID,OAAK,wBAAwB,CAAC;AAC1C,UAAQ,KAAK;AACb,OAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,mBAAmB,EAAE;GAC/D,MAAM,QAAQ,EAAE;AAChB,OAAI,OAAO,MAAO,OAAM,KAAK,UAAU,OAAO,QAAQ;AACtD,OAAI,OAAO,OAAO,OAAO,KAAK,OAAO,IAAI,CAAC,OAAQ,OAAM,KAAK,QAAQ,OAAO,QAAQ,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG;AACzI,OAAI,OAAO,UAAW,OAAM,KAAK,YAAY;AAC7C,OAAI,OAAO,OAAQ,OAAM,KAAK,YAAY,OAAO,OAAO,GAAG;AAC3D,WAAQ,IAAI,OAAOA,OAAK,KAAK,OAAO,GAAG,CAAC,CAAC,GAAGC,MAAI,MAAM,KAAK,KAAK,IAAI,YAAY,GAAG;;AAErF,UAAQ,KAAK;AACb;;AAIF,KAAI,KAAK,UAAU;EACjB,IAAI,WAAW,mBAAmB;AAElC,MAAI,KAAK,OAAO;AACd,sBAAmB,EAAE,CAAC;AACtB,WAAQ,IAAIF,KAAG,iCAAiC,CAAC;AACjD;;AAGF,MAAI,KAAK,QAAQ;AACf,cAAW,YAAY,UAAU,KAAK,OAAO;AAC7C,sBAAmB,SAAS;AAC5B,WAAQ,IAAIA,KAAG,kCAAkCC,OAAK,KAAK,OAAO,GAAG,CAAC;AACtE;;AAGF,MAAI,KAAK,KAAK,QAAQ;AACpB,QAAK,MAAM,QAAQ,KAAK,KAAK;IAC3B,MAAM,QAAQ,KAAK,QAAQ,IAAI;AAC/B,QAAI,UAAU,IAAI;AAAE,aAAQ,MAAM,IAAI,oBAAoB,KAAK,mBAAmB,CAAC;AAAE;;IACrF,MAAM,MAAM,KAAK,UAAU,GAAG,MAAM;IACpC,MAAM,QAAQ,KAAK,UAAU,QAAQ,EAAE;AACvC,QAAI,CAAC,eAAe,MAAK,MAAK,EAAE,QAAQ,IAAI,EAAE;AAAE,aAAQ,MAAM,IAAI,iBAAiB,IAAI,sCAAsC,CAAC;AAAE;;AAChI,QAAI,QAAQ,MACV,UAAS,MAAM;KAAE,GAAI,SAAS,OAAO,EAAE;KAAG,GAAI,iBAAiB,OAAO,MAAM;KAA6B;QAEzG,CAAC,SAAqC,OAAO,iBAAiB,KAAK,MAAM;;AAG7E,sBAAmB,SAAS;AAC5B,WAAQ,IAAID,KAAG,2BAA2B,CAAC;;AAG7C,MAAI,KAAK,OAAO,QAAQ;AACtB,QAAK,MAAM,OAAO,KAAK,MACrB,KAAI,IAAI,WAAW,OAAO,EAAE;IAC1B,MAAM,SAAS,IAAI,UAAU,EAAE;AAC/B,QAAI,SAAS,IAAK,QAAO,SAAS,IAAI;SAEtC,QAAQ,SAAqC;AAGjD,sBAAmB,SAAS;AAC5B,WAAQ,IAAIA,KAAG,2BAA2B,CAAC;;AAG7C,MAAI,KAAK,KACP,SAAQ,IAAI,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC;OACzC;AACL,WAAQ,KAAK;AACb,WAAQ,IAAI,OAAO,4BAA4B,CAAC;AAChD,WAAQ,KAAK;AACb,OAAI,OAAO,KAAK,SAAS,CAAC,WAAW,GAAG;AACtC,YAAQ,IAAIE,MAAI,sDAAsD,CAAC;AACvE,YAAQ,IAAIA,MAAI,0DAA0D,CAAC;SAE3E,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,EAAE;IACnD,MAAM,UAAU,OAAO,UAAU,WAAW,KAAK,UAAU,MAAM,GAAG,OAAO,MAAM;AACjF,YAAQ,IAAI,OAAOD,OAAK,IAAI,OAAO,GAAG,CAAC,CAAC,GAAG,UAAU;;AAGzD,WAAQ,KAAK;;AAEf;;AAIF,KAAI,CAAC,YAAY;AACf,UAAQ,MAAM,IAAI,uDAAuD,CAAC;AAC1E,UAAQ,MAAMC,MAAI,yEAAyE,CAAC;AAC5F,UAAQ,WAAW;AACnB;;CAGF,MAAM,UAAU,kBAAkB,IAAI,WAAW,IAAI,eAAe,IAAI,WAAW;CACnF,IAAI,SAAS,iBAAiB,QAAQ;AAEtC,KAAI,KAAK,OAAO;AACd,KAAG,QAAQ,yEAAyE,CAAC,IAAI,KAAK,EAAE,QAAQ,GAAG;AAC3G,UAAQ,IAAIF,KAAG,oBAAoBC,OAAK,QAAQ,KAAK,CAAC,6BAA6B,CAAC;AACpF;;AAGF,KAAI,KAAK,QAAQ;AACf,WAAS,YAAY,QAAQ,KAAK,OAAO;AACzC,KAAG,QAAQ,sEAAsE,CAC9E,IAAI,KAAK,UAAU,OAAO,EAAE,KAAK,EAAE,QAAQ,GAAG;AACjD,UAAQ,IAAID,KAAG,kBAAkBC,OAAK,KAAK,OAAO,CAAC,MAAMA,OAAK,QAAQ,KAAK,GAAG,CAAC;;AAGjF,KAAI,KAAK,KAAK,QAAQ;AACpB,OAAK,MAAM,QAAQ,KAAK,KAAK;GAC3B,MAAM,QAAQ,KAAK,QAAQ,IAAI;AAC/B,OAAI,UAAU,IAAI;AAAE,YAAQ,MAAM,IAAI,oBAAoB,KAAK,mBAAmB,CAAC;AAAE;;GACrF,MAAM,MAAM,KAAK,UAAU,GAAG,MAAM;GACpC,MAAM,QAAQ,KAAK,UAAU,QAAQ,EAAE;AACvC,OAAI,CAAC,eAAe,MAAK,MAAK,EAAE,QAAQ,IAAI,EAAE;AAAE,YAAQ,MAAM,IAAI,iBAAiB,IAAI,sCAAsC,CAAC;AAAE;;AAChI,OAAI,QAAQ,MACV,QAAO,MAAM;IAAE,GAAI,OAAO,OAAO,EAAE;IAAG,GAAI,iBAAiB,OAAO,MAAM;IAA6B;OAErG,CAAC,OAAmC,OAAO,iBAAiB,KAAK,MAAM;;AAG3E,KAAG,QAAQ,sEAAsE,CAC9E,IAAI,KAAK,UAAU,OAAO,EAAE,KAAK,EAAE,QAAQ,GAAG;AACjD,UAAQ,IAAID,KAAG,sBAAsBC,OAAK,QAAQ,KAAK,GAAG,CAAC;;AAG7D,KAAI,KAAK,OAAO,QAAQ;AACtB,OAAK,MAAM,OAAO,KAAK,MACrB,KAAI,IAAI,WAAW,OAAO,EAAE;GAC1B,MAAM,SAAS,IAAI,UAAU,EAAE;AAC/B,OAAI,OAAO,IAAK,QAAO,OAAO,IAAI;QAElC,QAAQ,OAAmC;AAG/C,KAAG,QAAQ,sEAAsE,CAC9E,IAAI,KAAK,UAAU,OAAO,EAAE,KAAK,EAAE,QAAQ,GAAG;AACjD,UAAQ,IAAID,KAAG,sBAAsBC,OAAK,QAAQ,KAAK,GAAG,CAAC;;CAG7D,MAAM,YAAY;EAAE,GAAG,mBAAmB;EAAE,GAAG;EAAQ;CACvD,MAAM,UAAU,kBAAkB,IAAI,QAAQ,GAAG;AAEjD,KAAI,KAAK,MAAM;AACb,UAAQ,IAAI,KAAK,UAAU;GACzB,SAAS,QAAQ;GACjB,OAAO;GACP,WAAW,QAAQ;GACnB;GACA,iBAAiB,mBAAmB;GACpC;GACD,EAAE,MAAM,EAAE,CAAC;AACZ;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,aAAa,QAAQ,OAAO,CAAC;AAChD,KAAI,QAAQ,OAAQ,SAAQ,IAAIC,MAAI,YAAY,QAAQ,KAAK,KAAK,GAAG,CAAC;AACtE,SAAQ,IAAIA,MAAI,WAAW,QAAQ,YAAY,CAAC;AAChD,SAAQ,KAAK;AAEb,KAAI,OAAO,KAAK,OAAO,CAAC,WAAW,EACjC,SAAQ,IAAIA,MAAI,uDAAuD,CAAC;MACnE;AACL,UAAQ,IAAID,OAAK,oBAAoB,CAAC;AACtC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,EAAE;GACjD,MAAM,UAAU,OAAO,UAAU,WAAW,KAAK,UAAU,MAAM,GAAG,OAAO,MAAM;AACjF,WAAQ,IAAI,OAAOA,OAAK,IAAI,OAAO,GAAG,CAAC,CAAC,GAAG,UAAU;;;CAIzD,MAAM,iBAAiB,mBAAmB;AAC1C,KAAI,OAAO,KAAK,eAAe,CAAC,SAAS,GAAG;AAC1C,UAAQ,KAAK;AACb,UAAQ,IAAIC,MAAI,oDAAoD,CAAC;AACrE,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,eAAe,EAAE;GACzD,MAAM,aAAa,OAAO;GAC1B,MAAM,UAAU,OAAO,UAAU,WAAW,KAAK,UAAU,MAAM,GAAG,OAAO,MAAM;AACjF,WAAQ,IAAI,OAAOA,MAAI,IAAI,OAAO,GAAG,CAAC,CAAC,GAAG,aAAa,MAAM,cAAc,QAAQ,GAAG,MAAMA,MAAI,eAAe,GAAG,UAAU;;;AAIhI,SAAQ,KAAK;AACb,SAAQ,IAAID,OAAK,oCAAoC,CAAC;AACtD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,EAAE;EACpD,MAAM,UAAU,OAAO,UAAU,WAAW,KAAK,UAAU,MAAM,GAAG,OAAO,MAAM;AACjF,UAAQ,IAAI,OAAOA,OAAK,IAAI,OAAO,GAAG,CAAC,CAAC,GAAG,UAAU;;AAEvD,SAAQ,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9Zf,SAAgBG,OAAK,GAAmB;AACtC,QAAO,EACJ,UAAU,MAAM,CAChB,aAAa,CACb,QAAQ,8BAA8B,GAAG;;;AAI9C,SAAS,SAAS,MAAuB;AACvC,QAAO,KAAK,WAAW,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8B7B,SAAS,oBAAoB,MAAsB;AACjD,QAAO,KAAK,QAAQ,qBAAqB,GAAG;;;;;;;;AAS9C,SAAS,UAAU,MAA2B;CAC5C,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,OAAOA,OAAK,KAAK;AACvB,KAAI,KAAM,MAAK,IAAI,KAAK;CACxB,MAAM,WAAWA,OAAK,oBAAoB,KAAK,CAAC;AAChD,KAAI,SAAU,MAAK,IAAI,SAAS;AAChC,QAAO;;AAGT,SAAS,UAAU,GAAgB,GAAyB;AAC1D,MAAK,MAAM,KAAK,EAAG,KAAI,EAAE,IAAI,EAAE,CAAE,QAAO;AACxC,QAAO;;;;;;;;;AAUT,SAAS,WAAW,MAAsB;AACxC,KAAI;AACF,SAAO,aAAa,KAAK;SACnB;AACN,SAAO;;;AAIX,SAAS,MAAM,MAAuB;AACpC,KAAI;AACF,SAAO,SAAS,KAAK,CAAC,aAAa;SAC7B;AACN,SAAO;;;;;;;;;;AAWX,SAAgB,wBAAwB,UAAsC;AAG5E,KAAI,WAAW,SAAS,CAAE,QAAO;CAEjC,MAAM,WAAW,SAAS,MAAM,IAAI,CAAC,QAAQ,MAAM,EAAE,SAAS,EAAE;AAChE,KAAI,SAAS,WAAW,EAAG,QAAO;CAKlC,IAAI,UADa,SAAS,WAAW,IAAI,GAChB,MAAM;CAC/B,IAAI,IAAI;AACR,QAAO,IAAI,SAAS,QAAQ,KAAK;EAC/B,MAAM,OAAO,KAAK,SAAS,SAAS,GAAI;AACxC,MAAI,CAAC,WAAW,KAAK,CAAE;AACvB,YAAU;;AAKZ,KAAI,MAAM,SAAS,OAAQ,QAAO;AAElC,QAAO,IAAI,SAAS,QAAQ,KAAK;EAC/B,MAAM,SAAS,UAAU,SAAS,GAAI;AACtC,MAAI,OAAO,SAAS,EAAG,QAAO;EAE9B,IAAI;AACJ,MAAI;AACF,cAAW,YAAY,QAAQ;UACzB;AACN;;EAGF,MAAM,OAAO,SAAS,QACnB,MACC,MAAM,KAAK,SAAS,EAAE,CAAC,IACvB,UAAU,UAAU,EAAE,EAAE,OAAO,IAS/B,SAAS,EAAE,KAAK,SAAS,SAAS,GAAI,CACzC;AAOD,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,YAAU,KAAK,SAAS,KAAK,GAAI;;AAGnC,QAAO;;;;;;;;;;;AAYT,SAAS,kBAAkB,UAAsC;CAC/D,MAAM,OAAO,SAAS,SAAS;CAC/B,MAAM,aAAa;EACjB,KAAK,SAAS,EAAE,OAAO,KAAK;EAC5B,KAAK,SAAS,EAAE,OAAO,MAAM,KAAK;EAClC,KAAK,SAAS,EAAE,WAAW,KAAK;EAChC,KAAK,SAAS,EAAE,YAAY,KAAK;EAClC;AACD,MAAK,MAAM,aAAa,WACtB,KAAI,WAAW,UAAU,CAAE,QAAO;;;;;;;;;;;;;;;;;;;AAsBtC,SAAgB,iBACd,UACA,aAAgC,EAAE,EACd;CACpB,MAAM,YAAY,wBAAwB,SAAS,IAAI,kBAAkB,SAAS;AAClF,KAAI,CAAC,UAAW,QAAO;AAGvB,KADc,IAAI,IAAI,WAAW,IAAI,WAAW,CAAC,CACvC,IAAI,WAAW,UAAU,CAAC,CAAE,QAAO;AAE7C,QAAO;;;;;ACjOT,SAAS,sBAAsB,SAA+B;CAC5D,MAAM,iBAAiB,KAAK,SAAS,EAAE,WAAW,WAAW;AAC7D,KAAI,CAAC,WAAW,eAAe,CAAE,QAAO,EAAE;CAE1C,MAAM,WAAW,UAAU,QAAQ,UAAU;CAC7C,MAAM,UAAoB,EAAE;AAE5B,KAAI;AACF,OAAK,MAAM,SAAS,YAAY,eAAe,EAAE;GAC/C,MAAM,OAAO,KAAK,gBAAgB,MAAM;AACxC,OAAI;AACF,QAAI,CAAC,SAAS,KAAK,CAAC,aAAa,CAAE;WAC7B;AACN;;AAEF,OAAI,UAAU,YAAY,UAAU,QAAQ,aAAa;IACvD,MAAM,WAAW,KAAK,MAAM,QAAQ;AACpC,QAAI,WAAW,SAAS,CACtB,SAAQ,KAAK,SAAS;;;SAItB;AAGR,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCT,SAAS,SACP,SACA,YACA,QAC4D;CAC5D,MAAM,YAAY,oBAAoB,QAAQ,UAAU;AACxD,KAAI,UACF,QAAO;EACL,QAAQ;EACR,QACE,QAAQ,gBAAgB,IACpB,SAAS,QAAQ,cAAc,kCAAkC,QAAQ,KAAK,iDAC9E,0BAA0B,QAAQ,KAAK,eAAe,UAAU;EACvE;AAGH,KAAI,YAAY;EAId,MAAM,SAAS,OAAO,MACnB,MACC,EAAE,WAAW,YACb,EAAE,UAAU,WAAW,QAAQ,YAAY,IAAI,IAC/C,WAAW,EAAE,UAAU,CAC1B;AACD,MAAI,UAAU,QAAQ,WAAW,SAC/B,QAAO;GACL,QAAQ;GACR,OAAO,OAAO;GACd,QAAQ,uBAAuB,OAAO,KAAK,uDAAuD,QAAQ,KAAK,GAAG,OAAO;GAC1H;AAEH,SAAO,EAAE;;CAIX,MAAM,aAAa,iBAAiB,QAAQ,WAAW,EAAE,CAAC;AAC1D,KAAI,YAAY;EACd,MAAM,QAAQ,OAAO,MAClB,MAAM,EAAE,cAAc,cAAc,WAAW,EAAE,WAAW,WAAW,CACzE;AACD,MAAI,MACF,QAAO;GACL,QAAQ;GACR,OAAO,MAAM;GACb,QACE,QAAQ,gBAAgB,IACpB,qBAAqB,QAAQ,KAAK,GAAG,MAAM,KAAK,WAAW,QAAQ,cAAc,gBACjF,qBAAqB,QAAQ,KAAK,GAAG,MAAM,KAAK;GACvD;;AAIL,QAAO;EACL,QAAQ;EACR,QACE,QAAQ,gBAAgB,IACpB,SAAS,QAAQ,cAAc,oEAC/B,uBAAuB,QAAQ;EACtC;;;AAIH,SAAS,WAAW,GAAW,GAAoB;AACjD,KAAI;AACF,SAAO,aAAa,EAAE,KAAK,aAAa,EAAE;SACpC;AACN,SAAO;;;AAIX,SAAgBC,YACd,IACA,MACM;CACN,MAAM,OAAO,GACV,QACC;;;iDAID,CACA,KAAK;CAER,MAAM,UAA2B,KAAK,KAAK,YAAY;EACrD,MAAM,aAAa,WAAW,QAAQ,UAAU;EAChD,MAAM,WAAW,sBAAsB,QAAQ;EAE/C,MAAM,SAAS,KAAK,QAAQ,MAAM,EAAE,OAAO,QAAQ,GAAG;EAEtD,IAAI;EACJ,IAAI;AAEJ,MAAI,WACF,YAAW;OACN;AAGL,mBAAgB,iBACd,QAAQ,WACR,OAAO,KAAK,MAAM,EAAE,UAAU,CAC/B;AACD,cAAW,gBAAgB,UAAU;;EAGvC,MAAM,EAAE,QAAQ,OAAO,WAAW,SAAS,SAAS,YAAY,OAAO;AAEvE,SAAO;GACL;GACA;GACA;GACA,mBAAmB,SAAS,SAAS;GACrC,mBAAmB;GACnB;GACA;GACA;GACD;GACD;CAEF,MAAM,WAAW,KAAK,SAAS,QAAQ,QAAQ,MAAM,EAAE,aAAa,KAAK,OAAO,GAAG;AAEnF,KAAI,KAAK,MAAM;AACb,UAAQ,IAAI,KAAK,UACf,SAAS,KAAK,OAAO;GACnB,MAAM,EAAE,QAAQ;GAChB,WAAW,EAAE,QAAQ;GACrB,QAAQ,EAAE,QAAQ;GAClB,QAAQ,EAAE;GACV,eAAe,EAAE,QAAQ;GACzB,gBAAgB,EAAE,iBAAiB;GACnC,qBAAqB,EAAE;GACvB,qBAAqB,EAAE;GAGvB,QAAQ,EAAE,UAAU;GACpB,OAAO,EAAE,SAAS;GAClB,QAAQ,EAAE,UAAU;GACrB,EAAE,EACH,MACA,EACD,CAAC;AACF;;CAGF,MAAM,SAAS,SAAS,QAAQ,MAAM,EAAE,aAAa,SAAS;CAC9D,MAAM,QAAQ,SAAS,QAAQ,MAAM,EAAE,aAAa,QAAQ;CAC5D,MAAM,OAAO,SAAS,QAAQ,MAAM,EAAE,aAAa,OAAO;AAE1D,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,8BAA8B,CAAC;AAClD,SAAQ,KAAK;AACb,SAAQ,IACN,KAAK,MAAM,MAAM,UAAU,CAAC,GAAG,OAAO,OAAO,KAAK,MAAM,OAAO,kBAAkB,CAAC,GAAG,MAAM,OAAO,KAAK,MAAM,IAAI,kBAAkB,CAAC,GAAG,KAAK,SAC7I;AACD,SAAQ,KAAK;AAEb,KAAI,OAAO,QAAQ;AACjB,UAAQ,IAAIC,OAAK,mCAAmC,CAAC;EACrD,MAAM,YAAY,OAAO,KAAK,MAAM;GAClCA,OAAK,EAAE,QAAQ,KAAK;GACpBC,MAAI,YAAY,EAAE,QAAQ,WAAW,GAAG,CAAC;GACzC,OAAO,EAAE,QAAQ,cAAc;GAC/B,EAAE,oBAAoB,MAAM,MAAM,MAAM,GAAGA,MAAI,KAAK;GACrD,CAAC;AACF,UAAQ,IACN,YAAY;GAAC;GAAQ;GAAQ;GAAY;GAAe,EAAE,UAAU,CACjE,MAAM,KAAK,CAAC,KAAK,MAAM,OAAO,EAAE,CAAC,KAAK,KAAK,CAC/C;AACD,UAAQ,KAAK;;AAGf,KAAI,MAAM,QAAQ;AAChB,UAAQ,IAAIC,OAAK,gEAAgE,CAAC;AAClF,OAAK,MAAM,KAAK,OAAO;AACrB,WAAQ,IAAI,OAAOF,OAAK,EAAE,QAAQ,KAAK,GAAG;AAC1C,WAAQ,IAAIC,MAAI,qBAAqB,EAAE,QAAQ,YAAY,CAAC;AAC5D,WAAQ,IAAI,MAAM,KAAK,qBAAqB,EAAE,gBAAgB,CAAC;AAC/D,OAAI,EAAE,kBACJ,SAAQ,IAAI,MAAM,MAAM,qBAAqB,EAAE,kBAAkB,KAAK,KAAK,GAAG,CAAC;AAEjF,OAAI,KAAK,OAAO,EAAE,eAAe;IAC/B,MAAM,KAAK,KAAK;IAChB,MAAM,aAAa,UAAU,EAAE,cAAc;AAC7C,OAAG,QAAQ,kFAAkF,CAC1F,IAAI,EAAE,eAAe,YAAY,IAAI,EAAE,QAAQ,GAAG;AACrD,YAAQ,IAAIE,KAAG,qCAAqC,EAAE,gBAAgB,CAAC;cAC9D,EAAE,cACX,SAAQ,IAAIF,MAAI,sCAAsC,EAAE,QAAQ,KAAK,GAAG,EAAE,gBAAgB,CAAC;;AAG/F,UAAQ,KAAK;;AAGf,KAAI,KAAK,QAAQ;AACf,UAAQ,IAAI,IAAI,yCAAyC,CAAC;AAC1D,OAAK,MAAM,KAAK,MAAM;GACpB,MAAM,QAAQ,EAAE,UAAU,EAAE,WAAW,SAASC,OAAK,MAAM,EAAE,OAAO,GAAG,GAAG;AAC1E,WAAQ,IAAI,OAAOF,OAAK,EAAE,QAAQ,KAAK,CAAC,KAAKC,MAAI,EAAE,QAAQ,UAAU,GAAG,QAAQ;AAChF,OAAI,EAAE,kBACJ,SAAQ,IAAI,MAAM,OAAO,iBAAiB,EAAE,kBAAkB,KAAK,KAAK,GAAG,CAAC;AAK9E,OAAI,EAAE,QAAQ,kBAAkB,KAAK,KAAK,OAAO,EAAE,WAAW,QAAQ;AACpE,OAAG,QAAQ,wFAAwF,CAChG,IAAI,KAAK,EAAE,KAAK,EAAE,EAAE,QAAQ,GAAG;AAClC,YAAQ,IAAIE,KAAG,wEAAwE,CAAC;cAC/E,EAAE,OACX,SAAQ,IAAIF,MAAI,iBAAiB,EAAE,SAAS,CAAC;;AAGjD,UAAQ,KAAK;;CAaf,MAAM,qBAAqB,QAAQ,QAChC,MAAM,EAAE,aAAa,YAAY,EAAE,QAAQ,WAAW,SACxD,CAAC;AAEF,SAAQ,IACNA,MACE,KAAK,KAAK,OAAO,UAAU,OAAO,OAAO,0BACpC,MAAM,OAAO,UAAU,KAAK,OAAO,OACzC,CACF;AACD,KAAI,qBAAqB,EACvB,SAAQ,IACNA,MACE,KAAK,mBAAmB,YAAY,OAAO,OAAO,iGAEnD,CACF;AAGH,KAAI,CAAC,KAAK,QAAQ,MAAM,SAAS,KAAK,KAAK,SAAS,IAAI;AACtD,UAAQ,KAAK;AACb,UAAQ,IAAIC,OAAK,qDAAqD,CAAC;;;;;;AChS3E,IAAa,aAAb,cAAgC,MAAM;;;;;;;;AAStC,SAAgB,UAAU,IAAc,UAAkB,UAA6B;AACrF,KAAI,aAAa,SACf,OAAM,IAAI,WAAW,gBAAgB,SAAS,eAAe;CAG/D,MAAM,OAAO,GACV,QAAQ,+CAA+C,CACvD,IAAI,SAAS;CAChB,MAAM,OAAO,GACV,QAAQ,+CAA+C,CACvD,IAAI,SAAS;AAEhB,KAAI,CAAC,KAAM,OAAM,IAAI,WAAW,yBAAyB,SAAS,IAAI;AACtE,KAAI,CAAC,KAAM,OAAM,IAAI,WAAW,yBAAyB,SAAS,IAAI;CAOtE,IAAI,OAHW,GACZ,QAAQ,0EAA0E,CAClF,IAAI,KAAK,GAAG,CACG;CAMlB,MAAM,WAJW,GACd,QAAQ,2EAA2E,CACnF,IAAI,KAAK,GAAG,CAEW,KAAK,OAAO;EAAE,IAAI,EAAE;EAAI,MAAM,EAAE;EAAQ,IAAI,EAAE;EAAM,EAAE;CAEhF,MAAM,SAAS,KAAa,GAAG,SAC5B,GAAG,QAAQ,IAAI,CAAC,IAAI,GAAG,KAAK,CAAmB;CAIlD,MAAM,aACJ,MAAM,qDAAqD,KAAK,KAAK,GAAG,KACxE,MAAM,qDAAqD,KAAK,KAAK,GAAG;AAE1E,QAAO;EACL,QAAQ,KAAK;EACb,UAAU,KAAK;EACf,QAAQ,KAAK;EACb,UAAU,KAAK;EACf;EACA,MAAM,MAAM,+DAA+D,KAAK,GAAG;EACnF,SAAS,MAAM,0DAA0D,KAAK,GAAG;EACjF,aAAa,MACX,iEACA,KAAK,GACN;EACD,OAAO,MAAM,+DAA+D,KAAK,GAAG;EACpF,YAAY,aAAa,SAAY,KAAK;EAC3C;;;;;;;;;AAUH,SAAgB,WAAW,IAAc,MAAuB;AAkD9D,CAjDY,GAAG,kBAAkB;EAE/B,MAAM,OAAO,GAAG,QACd,8DACD;AACD,OAAK,MAAM,KAAK,KAAK,SAAU,MAAK,IAAI,KAAK,QAAQ,EAAE,IAAI,EAAE,GAAG;AAIhE,KAAG,QACD;gEAED,CAAC,IAAI,KAAK,QAAQ,KAAK,OAAO;AAC/B,KAAG,QAAQ,gDAAgD,CAAC,IAAI,KAAK,OAAO;AAG5E,KAAG,QAAQ,yDAAyD,CAAC,IACnE,KAAK,QACL,KAAK,OACN;AAED,KAAG,QAAQ,gEAAgE,CAAC,IAC1E,KAAK,QACL,KAAK,OACN;AAKD,KAAG,QACD,+EACD,CAAC,IAAI,KAAK,QAAQ,KAAK,OAAO;AAC/B,KAAG,QAAQ,gDAAgD,CAAC,IAAI,KAAK,OAAO;AAC5E,KAAG,QACD;2EAED,CAAC,IAAI,KAAK,QAAQ,KAAK,OAAO;AAG/B,MAAI,KAAK,WACP,IAAG,QAAQ,kEAAkE,CAAC,IAC5E,KAAK,YACL,KAAK,OACN;AAGH,KAAG,QAAQ,oCAAoC,CAAC,IAAI,KAAK,OAAO;GAChE,EAEG;;;;;ACtJP,SAAgB,SACd,IACA,UACA,UACA,OAA8B,EAAE,EAC1B;CACN,IAAI;AACJ,KAAI;AACF,SAAO,UAAU,IAAI,UAAU,SAAS;UACjC,GAAG;AACV,MAAI,aAAa,YAAY;AAC3B,WAAQ,MAAM,IAAI,KAAK,EAAE,UAAU,CAAC;AACpC,WAAQ,WAAW;AACnB;;AAEF,QAAM;;CAGR,MAAM,QAAQ,OAAuB;EACnC,MAAM,MAAM,GAAG,QAAQ,8CAA8C,CAAC,IAAI,GAAG;AAG7E,SAAO,MAAM,YAAY,IAAI,WAAW,GAAG,GAAG;;AAGhD,SAAQ,KAAK;AACb,SAAQ,IAAIE,OAAK,WAAW,KAAK,SAAS,QAAQ,KAAK,WAAW,CAAC;AACnE,SAAQ,KAAK;AACb,SAAQ,IAAIC,MAAI,aAAa,KAAK,SAAS,OAAO,GAAG,CAAC,GAAG,KAAK,KAAK,OAAO,GAAG,CAAC;AAC9E,SAAQ,IAAIA,MAAI,aAAa,KAAK,SAAS,OAAO,GAAG,CAAC,GAAG,KAAK,KAAK,OAAO,GAAG,CAAC;AAC9E,SAAQ,KAAK;AAEb,KAAI,KAAK,SAAS,SAAS,GAAG;AAG5B,UAAQ,IAAI,OAAO,KAAK,SAAS,OAAO,sCAAsC;AAC9E,OAAK,MAAM,KAAK,KAAK,SACnB,SAAQ,IAAIA,MAAI,SAAS,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,QAAQ,OAAO,EAAE,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC;OAG1F,SAAQ,IAAIA,MAAI,2BAA2B,CAAC;CAG9C,MAAM,SAAmB,EAAE;AAC3B,KAAI,KAAK,KAAM,QAAO,KAAK,GAAG,KAAK,KAAK,SAAS;AACjD,KAAI,KAAK,QAAS,QAAO,KAAK,GAAG,KAAK,QAAQ,qBAAqB;AACnE,KAAI,KAAK,YAAa,QAAO,KAAK,GAAG,KAAK,YAAY,uBAAuB;AAC7E,KAAI,KAAK,MAAO,QAAO,KAAK,GAAG,KAAK,MAAM,kBAAkB;AAC5D,KAAI,OAAO,SAAS,EAClB,SAAQ,IAAIA,MAAI,uBAAuB,OAAO,KAAK,KAAK,CAAC,GAAG,CAAC;AAG/D,KAAI,KAAK,WACP,SAAQ,IACNA,MAAI,QAAQ,KAAK,WAAW,wDAAwD,CACrF;KAED,SAAQ,IACNC,OAAK,QAAQ,KAAK,SAAS,2DAA2D,CACvF;AAGH,SAAQ,IAAID,MAAI,gBAAgB,KAAK,SAAS,kBAAkB,CAAC;AACjE,SAAQ,KAAK;AAEb,KAAI,CAAC,KAAK,SAAS;AACjB,UAAQ,IAAIA,MAAI,mEAAmE,CAAC;AACpF,UAAQ,KAAK;AACb;;AAGF,YAAW,IAAI,KAAK;AACpB,SAAQ,IACNE,KACE,aAAa,KAAK,SAAS,OAAO,4BAA4B,KAAK,SAAS,GAC7E,CACF;AACD,SAAQ,KAAK;;;;;;ACtDf,SAAS,KAAK,GAAmB;AAC/B,QAAO,EAAE,aAAa,CAAC,QAAQ,UAAU,IAAI,CAAC,QAAQ,QAAQ,IAAI,CAAC,MAAM;;;;;;;;;;AAW3E,SAAgB,mBAAmB,IAAc,MAA4B;CAC3E,MAAM,OAAO,GACV,QAAQ,iEAAiE,CACzE,KAAK;CAER,MAAM,IAAI,KAAK,KAAK;CACpB,MAAM,QAAQ,KAAK,QAAQ,MAAM,KAAK,EAAE,aAAa,KAAK,KAAK,KAAK,EAAE,KAAK,KAAK,EAAE;AAClF,KAAI,MAAM,SAAS,EAAG,QAAO;CAE7B,MAAM,QAAQ,EAAE,MAAM,IAAI,CAAC,OAAO,QAAQ;AAC1C,KAAI,MAAM,WAAW,EAAG,QAAO,EAAE;AACjC,QAAO,KAAK,QAAQ,MAAM;EACxB,MAAM,MAAM,GAAG,KAAK,EAAE,aAAa,CAAC,GAAG,KAAK,EAAE,KAAK;AACnD,SAAO,MAAM,OAAO,MAAM,IAAI,SAAS,EAAE,CAAC;GAC1C;;;AAIJ,SAAgB,aAAa,MAAsB;AACjD,QACE,KACG,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG,IAAI;;AAIlC,SAAgB,QACd,IACA,MACA,OAA2C,EAAE,EACvC;CAGN,IAAI;AACJ,KAAI;AACF,WAAS,aAAa,KAAK,OAAO,QAAQ,KAAK,CAAC;SAC1C;AACN,UAAQ,MAAM,IAAI,wCAAwC,CAAC;AAC3D,UAAQ,WAAW;AACnB;;CAGF,MAAM,UAAU,UAAU,OAAO;CACjC,MAAM,UAAU,mBAAmB,IAAI,KAAK;AAE5C,KAAI,QAAQ,SAAS,GAAG;AACtB,UAAQ,MACN,IAAI,IAAI,KAAK,YAAY,QAAQ,OAAO,iCAAiC,GACvE,QAAQ,KAAK,MAAMC,MAAI,KAAK,EAAE,KAAK,KAAK,EAAE,aAAa,KAAK,CAAC,CAAC,KAAK,GAAG,CACzE;AACD,UAAQ,WAAW;AACnB;;CAKF,MAAM,QAAQ,GACX,QAAQ,oEAAoE,CAC5E,IAAI,QAAQ;AAEf,KAAI,QAAQ,WAAW,GAAG;EACxB,MAAM,IAAI,QAAQ;AAElB,MAAI,SAAS,MAAM,OAAO,EAAE,IAAI;AAC9B,WAAQ,MACN,IAAI,2CAA2CC,OAAK,MAAM,KAAK,CAAC,KAAK,GACnED,MAAI,KAAK,OAAO,IAAI,GACpBA,MAAI,mCAAmC,MAAM,KAAK,GAAG,EAAE,KAAK,YAAY,CAC3E;AACD,WAAQ,WAAW;AACnB;;EAKF,IAAI,cAAc;AAClB,MAAI;AACF,iBAAc,aAAa,EAAE,UAAU,KAAK;UACtC;AACN,iBAAc;;AAEhB,MAAI,aAAa;AACf,WAAQ,IAAIE,KAAG,gBAAgBD,OAAK,EAAE,aAAa,GAAG,CAAC;AACvD,WAAQ,IAAID,MAAI,KAAK,SAAS,CAAC;AAC/B;;AAGF,MAAI,KAAK,QAAQ;AACf,WAAQ,IAAIA,MAAI,iBAAiB,EAAE,KAAK,GAAG,CAAC;AAC5C,WAAQ,IAAIA,MAAI,UAAU,EAAE,YAAY,CAAC;AACzC,WAAQ,IAAIA,MAAI,UAAU,SAAS,CAAC;AACpC;;AAGF,KAAG,QACD,kFACD,CAAC,IAAI,QAAQ,SAAS,KAAK,EAAE,EAAE,GAAG;AAEnC,UAAQ,IAAIE,KAAG,GAAGD,OAAK,EAAE,aAAa,CAAC,cAAc,CAAC;AACtD,UAAQ,IAAID,MAAI,UAAU,EAAE,YAAY,CAAC;AACzC,UAAQ,IAAIA,MAAI,UAAU,SAAS,CAAC;AACpC;;AAIF,KAAI,OAAO;AACT,UAAQ,MACN,IAAI,2CAA2CC,OAAK,MAAM,KAAK,CAAC,IAAI,MAAM,aAAa,MAAM,GAC3FD,MAAI,yCAAyC,MAAM,KAAK,mBAAmB,KAAK,GAAG,CACtF;AACD,UAAQ,WAAW;AACnB;;CAGF,MAAM,OAAO,aAAa,KAAK;CAE/B,MAAM,YADQ,GAAG,QAAQ,wCAAwC,CAAC,IAAI,KAAK,GACjD,GAAG,KAAK,GAAG,QAAQ,MAAM,GAAG,KAAK;AAE3D,KAAI,KAAK,QAAQ;AACf,UAAQ,IAAIA,MAAI,gBAAgB,UAAU,IAAI,KAAK,OAAO,SAAS,CAAC;AACpE;;CAGF,MAAM,KAAK,KAAK;AAChB,IAAG,QACD;mDAED,CAAC,IAAI,WAAW,MAAM,QAAQ,SAAS,IAAI,GAAG;AAE/C,SAAQ,IAAIE,KAAG,WAAWD,OAAK,KAAK,GAAG,CAAC;AACxC,SAAQ,IAAID,MAAI,WAAW,YAAY,CAAC;AACxC,SAAQ,IAAIA,MAAI,WAAW,SAAS,CAAC;;;;;AChKvC,SAAgB,cACd,IACA,MACA,OAA+C,EAAE,EAC3C;CACN,MAAM,MAAM,GACT,QACC;;yCAGD,CACA,IAAI,KAAK;AAIZ,KAAI,CAAC,KAAK;AACR,UAAQ,MAAM,IAAI,2BAA2B,KAAK,IAAI,CAAC;AACvD,UAAQ,WAAW;AACnB;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAIG,OAAK,gBAAgB,IAAI,OAAO,CAAC;AAC7C,SAAQ,IAAIC,MAAI,OAAO,IAAI,YAAY,CAAC;AACxC,SAAQ,IAAIA,MAAI,cAAc,IAAI,OAAO,IAAI,IAAI,cAAc,aAAa,CAAC;AAC7E,SAAQ,KAAK;AAEb,KAAI,IAAI,gBAAgB,KAAK,CAAC,KAAK,OAAO;AACxC,UAAQ,IAAIC,OAAK,eAAe,IAAI,cAAc,gCAAgC,CAAC;AACnF,UAAQ,IACND,MAAI,6EAA6E,CAClF;AACD,UAAQ,IAAIA,MAAI,2BAA2B,IAAI,KAAK,mBAAmB,CAAC;AACxE,UAAQ,IAAIA,MAAI,6DAA6D,CAAC;AAC9E,UAAQ,KAAK;AACb,UAAQ,WAAW;AACnB;;AAGF,KAAI,CAAC,KAAK,SAAS;AACjB,UAAQ,IACNA,MAAI,wEAAwE,CAC7E;AACD,MAAI,IAAI,gBAAgB,EACtB,SAAQ,IAAIC,OAAK,qBAAqB,IAAI,cAAc,8BAA8B,CAAC;AAEzF,UAAQ,KAAK;AACb;;AAgBF,CAXY,GAAG,kBAAkB;AAC/B,KAAG,QAAQ,gDAAgD,CAAC,IAAI,IAAI,GAAG;AACvE,KAAG,QACD,uFACD,CAAC,IAAI,IAAI,GAAG;AACb,KAAG,QAAQ,kDAAkD,CAAC,IAAI,IAAI,GAAG;AACzE,KAAG,QAAQ,gDAAgD,CAAC,IAAI,IAAI,GAAG;AACvE,KAAG,QAAQ,2CAA2C,CAAC,IAAI,IAAI,GAAG;AAClE,KAAG,QAAQ,4CAA4C,CAAC,IAAI,IAAI,GAAG;AACnE,KAAG,QAAQ,oCAAoC,CAAC,IAAI,IAAI,GAAG;GAC3D,EACG;AAEL,SAAQ,IAAIC,KAAG,kBAAkB,IAAI,KAAK,GAAG,CAAC;AAC9C,SAAQ,IAAIF,MAAI,0CAA0C,CAAC;AAC3D,SAAQ,KAAK;;;;;ACpDf,SAAgB,yBACd,aACA,OACM;AAGN,aACG,QAAQ,QAAQ,EAAE,WAAW,MAAM,CAAC,CACpC,YACC,mHAED,CACA,OAAO,SAAS,qEAAqE,CACrF,OAAO,mBAAmB,oBAAoB,KAAK,CACnD,OAAO,OAAO,SAAwC;EACrD,MAAM,EAAE,YAAY,MAAM,OAAO;AACjC,QAAM,QAAQ,OAAO,EAAE,QAAW,QAAW,KAAK;GAClD;AAGJ,aACG,QAAQ,WAAW,CACnB,YAAY,oEAAoE,CAChF,OAAO,SAAS,mDAAmD,CACnE,OAAO,qBAAqB,sCAAsC,CAClE,OAAO,eAAe,gBAAgB,CACtC,OAAO,iBAAiB,iBAAiB,CACzC,QAAQ,SAA0E;AACjF,YAAQ,OAAO,EAAE,KAAK;GACtB;AAGJ,aACG,QAAQ,kBAAkB,CAC1B,YACC,6LAGD,CACA,QAAQ,eAAuB;EAC9B,MAAM,KAAK,OAAO;EAClB,MAAM,UAAU,kBAAkB,IAAI,WAAW;AACjD,MAAI,CAAC,SAAS;AACZ,WAAQ,MAAM,sBAAsB,aAAa;AACjD,WAAQ,WAAW;AACnB;;AAGF,MAAI,WAAW,QAAQ,UAAU,EAAE;AACjC,WAAQ,OAAO,MAAM,QAAQ,YAAY,KAAK;AAC9C;;AAIF,UAAQ,OAAO,MACbG,OAAK,mBAAmB,QAAQ,UAAU,IAAI,GAC9CC,MAAI,sCAAsC,CAC3C;EAED,MAAM,SAAS,cAAc,QAAQ,UAAU;AAE/C,MAAI,OAAO,OAAO;GAChB,MAAM,UAAU,OAAO;GACvB,MAAM,aAAa,UAAU,QAAQ;GACrC,MAAM,KAAK,KAAK;AAChB,MAAG,QACD,kFACD,CAAC,IAAI,SAAS,YAAY,IAAI,QAAQ,GAAG;AAC1C,WAAQ,OAAO,MACbC,KAAG,kBAAkB,YAAY,QAAQ,WAAW,GAAG,CAAC,IAAI,GAC5DD,MAAI,OAAO,QAAQ,IAAI,GACvBC,KAAG,sBAAsB,CAC1B;AACD,WAAQ,OAAO,MAAM,UAAU,KAAK;AACpC;;AAGF,MAAI,OAAO,WAAW;AACpB,WAAQ,OAAO,MACbF,OAAK,+BAA+B,SAAS,QAAQ,UAAU,CAAC,YAAY,CAC7E;AACD,QAAK,MAAM,aAAa,OAAO,UAC7B,SAAQ,OAAO,MAAMC,MAAI,KAAK,UAAU,IAAI,CAAC;AAE/C,WAAQ,OAAO,MACbA,MAAI,8CAA8C,QAAQ,KAAK,WAAW,CAC3E;AACD,WAAQ,WAAW;AACnB;;AAGF,UAAQ,OAAO,MACb,IACE,YAAY,QAAQ,KAAK,eAAe,QAAQ,UAAU,mDAChC,SAAS,QAAQ,UAAU,CAAC,6BACvD,GACDA,MAAI,mCAAmC,QAAQ,KAAK,eAAe,CACpE;AACD,UAAQ,WAAW;GACnB;AAGJ,aACG,QAAQ,aAAa,CACrB,YAAY,mDAAmD,CAC/D,OAAO,iBAAiB,+BAA+B,CACvD,OAAO,iBAAiB,8DAA8D,QAAQ,CAC9F,OAAO,yBAAyB,8BAA8B,CAC9D,QAEG,SACA,SACG;AACH,SAAO,OAAO,EAAE,SAAS,KAAK;GAEjC;AAGH,aACG,QAAQ,cAAc,CACtB,YAAY,kCAAkC,CAC9C,QAAQ,SAAiB;AACxB,YAAQ,OAAO,EAAE,KAAK;GACtB;AAGJ,aACG,QAAQ,iBAAiB,CACzB,YAAY,oBAAoB,CAChC,QAAQ,SAAiB;AACxB,aAAW,OAAO,EAAE,KAAK;GACzB;AAOJ,aACG,QAAQ,sBAAsB,CAC9B,YACC,yNAGD,CACA,OAAO,aAAa,6BAA6B,CACjD,QAAQ,MAAc,MAAc,SAAgC;AACnE,WAAS,OAAO,EAAE,MAAM,MAAM,KAAK;GACnC;AAOJ,aACG,QAAQ,oBAAoB,CAC5B,YACC,6OAGD,CACA,OAAO,aAAa,0BAA0B,CAC9C,OAAO,WAAW,uDAAuD,CACzE,QAAQ,MAAc,SAAiD;AACtE,gBAAc,OAAO,EAAE,MAAM,KAAK;GAClC;AAGJ,aACG,QAAQ,mBAAmB,CAC3B,YAAY,+CAA+C,CAC3D,QAAQ,SAAiB;AACxB,eAAa,OAAO,EAAE,KAAK;GAC3B;AAGJ,aACG,QAAQ,yBAAyB,CACjC,YAAY,qCAAqC,CACjD,QAAQ,MAAc,YAAoB;AACzC,UAAQ,OAAO,EAAE,MAAM,QAAQ;GAC/B;AAGJ,aACG,QAAQ,2BAA2B,CACnC,YACC,2KAED,CACA,QAAQ,MAAc,YAAoB;AACzC,YAAU,OAAO,EAAE,MAAM,QAAQ;GACjC;AAGJ,aACG,QAAQ,cAAc,CACtB,YACC,gPAID,CACA,OAAO,aAAa,yCAAyC,CAC7D,QAAQ,MAAc,SAA+B;AACpD,UAAQ,OAAO,EAAE,MAAM,EAAE,QAAQ,KAAK,QAAQ,CAAC;GAC/C;AAGJ,aACG,QAAQ,uBAAuB,CAC/B,YAAY,oCAAoC,CAChD,QAAQ,MAAc,SAAmB;AACxC,WAAO,OAAO,EAAE,MAAM,KAAK;GAC3B;AAGJ,aACG,QAAQ,uBAAuB,CAC/B,YAAY,6CAA6C,CACzD,QAAQ,MAAc,UAAkB;AACvC,WAAS,OAAO,EAAE,MAAM,MAAM;GAC9B;AAGJ,aACG,QAAQ,cAAc,CACtB,YAAY,wBAAwB,CACpC,OAAO,yBAAyB,mBAAmB,CACnD,OAAO,iBAAiB,WAAW,CACnC,QACE,MAAc,SAAkD;AAC/D,UAAQ,OAAO,EAAE,MAAM,KAAK;GAE/B;AAGH,aACG,QAAQ,gBAAgB,CACxB,YAAY,qEAAqE,CACjF,OAAO,UAAU,iDAAiD,CAClE,QAAQ,SAA6B,SAA6B;AACjE,YAAU,OAAO,EAAE,SAAS,KAAK;GACjC;AAGJ,aACG,QAAQ,SAAS,CACjB,YACC,yFACD,CACA,OAAO,SAAS,gCAAgC,CAChD,OAAO,UAAU,yBAAyB,CAC1C,OAAO,uBAAuB,0CAA0C,CACxE,QAAQ,SAA6D;AACpE,cAAU,OAAO,EAAE,KAAK;GACxB;AAGJ,aACG,QAAQ,2BAA2B,CACnC,YACC,mHACD,CACA,OAAO,SAAS,oDAAoD,CACpE,OAAO,aAAa,qDAAqD,CACzE,QACE,YAAoB,SAA8C;AACjE,iBAAe,OAAO,EAAE,YAAY,KAAK;GAE5C;AAGH,aACG,QAAQ,UAAU,CAClB,YAAY,uDAAuD,CACnE,eAAe,yBAAyB,yCAAyC,CACjF,eAAe,eAAe,sDAAsD,CACpF,OAAO,iBAAiB,sEAAsE,CAC9F,QAAQ,SAA6D;AACpE,aAAW,OAAO,EAAE,KAAK;GACzB;AAGJ,aACG,QAAQ,aAAa,CACrB,YACC,0IAED,CACA,QAAQ,UAAkB;AACzB,QAAM,OAAO,EAAE,MAAM;GACrB;AAGJ,aACG,QAAQ,gCAAgC,CACxC,YAAY,+CAA+C,CAC3D,OAAO,wBAAwB,6CAA6C,CAC5E,QAEG,YACA,WACA,SACG;AACH,UAAQ,OAAO,EAAE,YAAY,WAAW,KAAK;GAEhD;AAGH,aACG,QAAQ,qBAAqB,CAC7B,YAAY,gCAAgC,CAC5C,QAAQ,cAAsB;AAC7B,YAAU,OAAO,EAAE,UAAU;GAC7B;AAGJ,aACG,QAAQ,QAAQ,CAChB,YAAY,+CAA+C,CAC3D,OAAO,UAAU,uCAAuC,CACxD,OAAO,SAAS,uEAAuE,CACvF,QAAQ,SAA4C;AACnD,WAAS,OAAO,EAAE,KAAK;GACvB;AAGJ,aACG,QAAQ,sBAAsB,CAC9B,YACC,6GAED,CACA,OACC,wBACA,mCACC,GAAW,SAAmB,CAAC,GAAG,MAAM,EAAE,EAC3C,EAAE,CACH,CACA,OACC,oBACA,8DACC,GAAW,SAAmB,CAAC,GAAG,MAAM,EAAE,EAC3C,EAAE,CACH,CACA,OAAO,mBAAmB,sDAAsD,CAChF,OAAO,cAAc,sDAAsD,CAC3E,OAAO,aAAa,yCAAyC,CAC7D,OAAO,UAAU,cAAc,CAC/B,OAAO,WAAW,kDAAkD,CACpE,QAEG,YACA,SASG;AACH,YAAU,OAAO,EAAE,YAAY,KAAK;GAEvC;;;;;;ACtXL,MAAM,wBAAwB,KAAK,SAAS,EAAE,WAAW,wBAAwB;AAMjF,SAAgBE,aAAW,IAAoB;AAC7C,KAAI,CAAC,WAAW,sBAAsB,EAAE;AACtC,UAAQ,MAAM,IAAI,oCAAoC,wBAAwB,CAAC;AAC/E,UAAQ,WAAW;AACnB;;CAGF,IAAI;AACJ,KAAI;EACF,MAAM,MAAM,aAAa,uBAAuB,OAAO;AACvD,aAAW,KAAK,MAAM,IAAI;UACnB,GAAG;AACV,UAAQ,MAAM,IAAI,0CAA0C,IAAI,CAAC;AACjE,UAAQ,WAAW;AACnB;;CAGF,MAAM,WAAW,SAAS,YAAY,EAAE;AACxC,KAAI,CAAC,SAAS,QAAQ;AACpB,UAAQ,IAAIC,OAAK,8CAA8C,CAAC;AAChE;;AAGF,SAAQ,IAAIC,MAAI,aAAa,SAAS,OAAO,4CAA4C,CAAC;CAE1F,IAAI,cAAc;CAClB,IAAI,kBAAkB;CACtB,IAAI,cAAc;CAClB,MAAM,SAAmB,EAAE;AAE3B,MAAK,MAAM,SAAS,UAAU;AAC5B,MAAI,CAAC,MAAM,iBAAiB,CAAC,MAAM,aAAa;AAC9C,UAAO,KAAK,qCAAqC,KAAK,UAAU,MAAM,GAAG;AACzE;;EAGF,MAAM,WAAW,MAAM;EACvB,MAAM,aAAa,MAAM;EACzB,MAAM,OAAOC,UAAQ,SAAS;AAE9B,MAAI;GACF,MAAM,EAAE,OAAO,OAAO,cAAc,IAAI,MAAM,UAAU,WAAW;AACnE,OAAI,MAAO;OACN;GAEL,MAAM,QAAQ,MAAM,SAAS,EAAE;AAC/B,QAAK,MAAM,YAAY,OAAO;IAC5B,MAAM,SAAS,qBAAqB,SAAS;AAC7C,QAAI,CAAC,OAAQ;AAGb,QADqB,cAAc,IAAI,IAAI,OAAO,QAAQ,OAAO,MAAM,OAAO,MAAM,OAAO,OAAO,OAAO,SAAS,CAChG;;WAEb,GAAG;AACV,UAAO,KAAK,oBAAoB,MAAM,YAAY,IAAI,OAAO,EAAE,GAAG;;;AAItE,SAAQ,IAAIC,KAAG,sBAAsB,CAAC;AACtC,SAAQ,IAAIF,MAAI,eAAe,YAAY,QAAQ,gBAAgB,kBAAkB,CAAC;AACtF,SAAQ,IAAIA,MAAI,eAAe,YAAY,MAAM,CAAC;AAElD,KAAI,OAAO,QAAQ;AACjB,UAAQ,KAAK;AACb,UAAQ,IAAID,OAAK,KAAK,OAAO,OAAO,6BAA6B,CAAC;AAClE,OAAK,MAAM,KAAK,OAAO,MAAM,GAAG,EAAE,CAChC,SAAQ,IAAIC,MAAI,OAAO,IAAI,CAAC;AAE9B,MAAI,OAAO,SAAS,EAClB,SAAQ,IAAIA,MAAI,eAAe,OAAO,SAAS,EAAE,OAAO,CAAC;;;;;;;ACpC/D,SAAS,aAAa,GAA0B;AAC9C,KAAI;AACF,SAAO,aAAa,EAAE;SAChB;AACN,SAAO;;;AAIX,SAAS,UAAU,IAAc,WAA2B;CAC1D,MAAM,OAAO,QACV,GAAG,QAAQ,IAAI,CAAC,IAAI,UAAU,CAAmB;AAEpD,QAAO;EACL,UAAU,IAAI,0DAA0D;EACxE,YAAY,IAAI,gEAAgE;EAChF,SAAS,IAAI,yDAAyD;EACtE,OAAO,IAAI,8DAA8D;EACzE,MAAM,IAAI,8DAA8D;EACxE,UAAU,IAAI,yDAAyD;EACxE;;;;;;;;;;AAWH,SAAS,cAAc,MAAoB,UAAkB,IAA0B;CACrF,MAAM,QAAQ,KAAK,QAAQ,MAAM,EAAE,cAAc,SAAS;AAG1D,QAAO,CAAC,GAFK,MAAM,SAAS,IAAI,QAAQ,KAExB,CAAC,MAAM,GAAG,MAAM;EAC9B,MAAM,KAAK,UAAU,IAAI,EAAE,GAAG,CAAC;EAC/B,MAAM,KAAK,UAAU,IAAI,EAAE,GAAG,CAAC;AAC/B,MAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,SAAO,EAAE,aAAa,EAAE;GACxB,CAAC;;AAGL,SAAgB,kBAAkB,IAGhC;CACA,MAAM,OAAO,GACV,QACC;;oCAGD,CACA,KAAK;CAER,MAAM,6BAAa,IAAI,KAA2B;CAClD,MAAM,aAA2B,EAAE;AAEnC,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,KAAK,aAAa,IAAI,UAAU;AACtC,MAAI,CAAC,IAAI;AAGP,cAAW,KAAK,IAAI;AACpB;;EAEF,MAAM,SAAS,WAAW,IAAI,GAAG;AACjC,MAAI,OAAQ,QAAO,KAAK,IAAI;MACvB,YAAW,IAAI,IAAI,CAAC,IAAI,CAAC;;CAGhC,MAAM,SAAuB,EAAE;AAC/B,MAAK,MAAM,CAAC,UAAU,YAAY,YAAY;AAC5C,MAAI,QAAQ,SAAS,EAAG;EACxB,MAAM,YAAY,cAAc,SAAS,UAAU,GAAG;AACtD,SAAO,KAAK;GACV;GACA;GACA,YAAY,QAAQ,QAAQ,MAAM,EAAE,OAAO,UAAU,GAAG;GACzD,CAAC;;AAGJ,QAAO;EAAE;EAAQ;EAAY;;;;;;;;;;;;;;;AAiC/B,SAAS,YAAY,IAAc,QAAgB,QAAsB;CAEvE,MAAM,OAAO,GACV,QAAQ,uDAAuD,CAC/D,IAAI,OAAO;AACd,MAAK,MAAM,KAAK,KAMd,KALc,GACX,QACC,yEACD,CACA,IAAI,QAAQ,EAAE,OAAO,CAEtB,IAAG,QACD,+DACD,CAAC,IAAI,QAAQ,EAAE,OAAO;KAEvB,IAAG,QACD,6EACD,CAAC,IAAI,QAAQ,QAAQ,EAAE,OAAO;CAKnC,MAAM,QAAQ,GACX,QAAQ,+DAA+D,CACvE,IAAI,OAAO;AACd,MAAK,MAAM,KAAK,MAMd,KALc,GACX,QACC,6EACD,CACA,IAAI,QAAQ,EAAE,kBAAkB,CACxB,IAAG,QAAQ,iCAAiC,CAAC,IAAI,EAAE,GAAG;KAE/D,IAAG,QAAQ,+CAA+C,CAAC,IACzD,QACA,EAAE,GACH;AAGL,IAAG,QAAQ,gEAAgE,CAAC,IAC1E,QACA,OACD;AAGD,IAAG,QACD;;;;;;;;;;;mBAYD,CAAC,IAAI,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAErD,IAAG,QAAQ,oCAAoC,CAAC,IAAI,OAAO;;AAG7D,SAAS,WAAW,IAAc,OAAgC;CAChE,MAAM,SAAsB;EAC1B,eAAe;EACf,gBAAgB;EAChB,YAAY,EAAE;EACd,iBAAiB;EACjB,cAAc;EACd,YAAY;EACZ,WAAW;EACX,eAAe;EACf,aAAa;EACb,gBAAgB,EAAE;EACnB;CAED,MAAM,cAAc,MAAM,UAAU;CAIpC,IAAI,cAEA,GACG,QAAQ,6DAA6D,CACrE,IAAI,YAAY,CACnB,KAAK,KAAK;AAEd,MAAK,MAAM,OAAO,MAAM,YAAY;EAClC,MAAM,WAAW,GACd,QACC,wFACD,CACA,IAAI,IAAI,GAAG;AAOd,OAAK,MAAM,KAAK,UAAU;GAGxB,MAAM,OAAO,GACV,QACC,wEACD,CACA,IAAI,aAAa,EAAE,SAAS;AAE/B,OAAI,MAAM;AACR,gBAAY,IAAI,KAAK,IAAI,EAAE,GAAG;AAC9B,WAAO;AACP;;AASF,OANc,GACX,QACC,qEACD,CACA,IAAI,aAAa,EAAE,OAAO,EAElB;IACT,MAAM,WAAW;AACjB,OAAG,QACD,8DACD,CAAC,IAAI,aAAa,UAAU,EAAE,GAAG;AAClC,WAAO,WAAW,KAAK;KACrB,MAAM,EAAE;KACR,IAAI;KACJ,OAAO,EAAE;KACV,CAAC;UACG;AACL,OAAG,QAAQ,kDAAkD,CAAC,IAC5D,aACA,EAAE,GACH;AACD,QAAI,EAAE,UAAU,WAAY,cAAa,EAAE,SAAS;;AAEtD,UAAO;;AAGT,SAAO,mBAAmB,GACvB,QAAQ,gEAAgE,CACxE,IAAI,aAAa,IAAI,GAAG,CAAC;EAI5B,MAAM,aAAa,GAChB,QAAQ,iDAAiD,CACzD,IAAI,IAAI,GAAG;AACd,OAAK,MAAM,KAAK,YAAY;GAC1B,MAAM,WAAW,GACd,QAAQ,iDAAiD,CACzD,IAAI,EAAE,MAAM;AACf,OAAI,YAAY,SAAS,eAAe,YAAa;AACrD,MAAG,QAAQ,oDAAoD,CAAC,IAC9D,aACA,EAAE,MACH;AACD,UAAO;;EAIT,MAAM,WAAW,GACd,QAAQ,+DAA+D,CACvE,IAAI,IAAI,GAAG;AACd,OAAK,MAAM,KAAK,UAAU;AAMxB,OALc,GACX,QACC,6EACD,CACA,IAAI,EAAE,YAAY,YAAY,EACtB;AACT,OAAG,QAAQ,iCAAiC,CAAC,IAAI,EAAE,GAAG;AACtD;;AAEF,MAAG,QAAQ,sDAAsD,CAAC,IAChE,aACA,EAAE,GACH;AACD,UAAO;;EAIT,MAAM,UAAU,GACb,QAAQ,uDAAuD,CAC/D,IAAI,IAAI,GAAG;AACd,OAAK,MAAM,KAAK,SAAS;AAMvB,OALc,GACX,QACC,yEACD,CACA,IAAI,aAAa,EAAE,OAAO,EAClB;AACT,OAAG,QACD,+DACD,CAAC,IAAI,IAAI,IAAI,EAAE,OAAO;AACvB;;AAEF,MAAG,QACD,6EACD,CAAC,IAAI,aAAa,IAAI,IAAI,EAAE,OAAO;AACpC,UAAO;;AAGT,SAAO,iBAAiB,GACrB,QAAQ,wDAAwD,CAChE,IAAI,aAAa,IAAI,GAAG,CAAC;AAG5B,MAAI,CAAC,MAAM,UAAU,oBAAoB,IAAI,iBAC3C,IAAG,QAAQ,wDAAwD,CAAC,IAClE,IAAI,kBACJ,YACD;AAEH,MAAI,CAAC,MAAM,UAAU,kBAAkB,IAAI,eACzC,IAAG,QAAQ,sDAAsD,CAAC,IAChE,IAAI,gBACJ,YACD;AAGH,KAAG,QAAQ,oCAAoC,CAAC,IAAI,IAAI,GAAG;AAC3D,SAAO;;AAQT,MAFE,MAAM,UAAU,WAAW,YAC3B,MAAM,WAAW,MAAM,MAAM,EAAE,WAAW,SAAS,KACpC,MAAM,UAAU,WAAW,SAC1C,IAAG,QAAQ,qDAAqD,CAAC,IAC/D,YACD;CAUH,MAAM,WAAW,aAAa,KAAK,MAAM,UAAU,KAAK;AACxD,KAAI,UAAU;EACZ,MAAM,aAAa,CACjB,SAAS,IACT,GAAG,MAAM,WAAW,KAAK,MAAM,EAAE,KAAK,CAAC,QAAQ,MAAM,CAAC,QAAQ,KAAK,EAAE,CAAC,CACvE;AACD,OAAK,MAAM,aAAa,YAAY;AAIlC,OAHc,GACX,QAAQ,gDAAgD,CACxD,IAAI,UAAU,CACN;AACX,MAAG,QAAQ,4DAA4D,CAAC,IACtE,WACA,KAAK,KAAK,EACV,YACD;AACD,UAAO,eAAe,KAAK;IACzB,MAAM,MAAM,UAAU;IACtB,IAAI;IACL,CAAC;AACF;;;AAKJ,KAAI,MAAM,UAAU,cAAc,MAAM,UAItC;MAAI,CAHU,GACX,QAAQ,0DAA0D,CAClE,IAAI,MAAM,UAAU,YAAY,CAEjC,IAAG,QACD,iEACD,CAAC,IAAI,MAAM,UAAU,KAAK,KAAK,EAAE,YAAY;;AAIlD,QAAO;;;;;;;;;;;;;AAcT,SAAS,kBACP,IACA,QACmD;CACnD,MAAM,OAAO,GACV,QAAQ,2CAA2C,CACnD,KAAK;CAER,MAAM,UAA6D,EAAE;AAErE,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,KAAK,aAAa,IAAI,UAAU;AACtC,MAAI,CAAC,MAAM,OAAO,IAAI,UAAW;AAKjC,MAHc,GACX,QAAQ,iEAAiE,CACzE,IAAI,IAAI,IAAI,GAAG,CACP;AAEX,UAAQ,KAAK;GAAE,MAAM,IAAI;GAAM,MAAM,IAAI;GAAW,IAAI;GAAI,CAAC;AAC7D,MAAI,CAAC,OACH,IAAG,QACD,iEACD,CAAC,IAAI,IAAI,KAAK,KAAK,EAAE,IAAI,GAAG;;AAIjC,QAAO;;AAOT,SAAS,SAAS,QAA+B;AAC/C,KAAI,CAAC,WAAW,OAAO,CAAE,QAAO;CAEhC,MAAM,OAAO,GAAG,OAAO,wBADT,IAAI,MAAM,EAAC,aAAa,CAAC,QAAQ,SAAS,IAAI;AAE5D,KAAI;AACF,eAAa,QAAQ,KAAK;AAE1B,OAAK,MAAM,UAAU,CAAC,QAAQ,OAAO,CACnC,KAAI,WAAW,SAAS,OAAO,CAC7B,cAAa,SAAS,QAAQ,OAAO,OAAO;AAGhD,SAAO;SACD;AACN,SAAO;;;AAIX,SAAgB,UACd,IACA,MACM;CACN,MAAM,EAAE,QAAQ,eAAe,kBAAkB,GAAG;AAEpD,SAAQ,KAAK;AACb,SAAQ,IACNG,OACE,KAAK,UACD,0BACA,4DACL,CACF;AACD,SAAQ,KAAK;AAEb,KAAI,OAAO,WAAW,EACpB,SAAQ,IAAIC,KAAG,mEAAmE,CAAC;AAGrF,MAAK,MAAM,KAAK,QAAQ;AACtB,UAAQ,IAAI,KAAKD,OAAK,EAAE,SAAS,GAAG;EACpC,MAAM,UAAU,UAAU,IAAI,EAAE,UAAU,GAAG;AAC7C,UAAQ,IACN,OAAOC,KAAG,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,GAAGD,OAAK,EAAE,UAAU,KAAK,CAAC,KAC9DE,MAAI,IAAI,EAAE,UAAU,UAAU,MAAM,QAAQ,SAAS,aAAa,EAAE,UAAU,SAAS,CAC1F;AACD,OAAK,MAAM,KAAK,EAAE,YAAY;GAC5B,MAAM,KAAK,UAAU,IAAI,EAAE,GAAG;GAC9B,MAAM,SAAS;IACb,GAAG,WAAW,GAAG,GAAG,SAAS,aAAa;IAC1C,GAAG,aAAa,GAAG,GAAG,WAAW,oBAAoB;IACrD,GAAG,UAAU,GAAG,GAAG,QAAQ,YAAY;IACvC,GAAG,QAAQ,GAAG,GAAG,MAAM,UAAU;IACjC,GAAG,OAAO,GAAG,GAAG,KAAK,SAAS;IAC9B,GAAG,WAAW,GAAG,GAAG,SAAS,mBAAmB;IACjD,CACE,OAAO,QAAQ,CACf,KAAK,KAAK;AACb,WAAQ,IACN,OAAOC,OAAK,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAGH,OAAK,EAAE,KAAK,CAAC,KAC5CE,MAAI,IAAI,EAAE,UAAU,MAAM,UAAU,oBAAoB,CAC3D;;AAEH,UAAQ,KAAK;;AAGf,KAAI,WAAW,SAAS,GAAG;AACzB,UAAQ,IACNA,MACE,KAAK,WAAW,OAAO,oDACxB,CACF;AACD,UAAQ,IACNA,MACE,yEACD,CACF;AACD,UAAQ,IAAIA,MAAI,0DAA0D,CAAC;AAC3E,OAAK,MAAM,KAAK,YAAY;GAC1B,MAAM,KAAK,UAAU,IAAI,EAAE,GAAG;AAC9B,OAAI,GAAG,aAAa,KAAK,GAAG,eAAe,EAAG;AAC9C,WAAQ,IACNA,MACE,QAAQ,EAAE,GAAG,GAAG,EAAE,KAAK,IAAI,EAAE,UAAU,MAAM,GAAG,SAAS,aAAa,GAAG,WAAW,kBACrF,CACF;;AAEH,UAAQ,KAAK;;CAGf,MAAM,YAAY,kBAAkB,IAAI,KAAK;AAC7C,KAAI,UAAU,SAAS,GAAG;AACxB,UAAQ,IACNA,MACE,KAAK,UAAU,OAAO,6DACvB,CACF;AACD,UAAQ,IACNA,MACE,yEACD,CACF;AACD,UAAQ,IAAIA,MAAI,6DAA6D,CAAC;AAC9E,OAAK,MAAM,KAAK,UAAU,MAAM,GAAG,EAAE,CACnC,SAAQ,IAAIA,MAAI,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,KAAK,CAAC;AAEzD,MAAI,UAAU,SAAS,EACrB,SAAQ,IAAIA,MAAI,eAAe,UAAU,SAAS,EAAE,OAAO,CAAC;AAE9D,UAAQ,KAAK;;AAGf,KAAI,CAAC,KAAK,SAAS;AACjB,MAAI,OAAO,SAAS,KAAK,UAAU,SAAS,GAAG;AAC7C,WAAQ,IAAIA,MAAI,qEAAqE,CAAC;AACtF,WAAQ,KAAK;;AAEf;;AAGF,KAAI,OAAO,WAAW,KAAK,UAAU,WAAW,EAAG;CAGnD,MAAM,SAAS,KAAK;AACpB,KAAI,QAAQ;EACV,MAAM,SAAS,SAAS,OAAO;AAC/B,MAAI,OACF,SAAQ,IAAID,KAAG,yBAAyBD,OAAK,OAAO,GAAG,CAAC;OACnD;AACL,WAAQ,MACN,IAAI,kEAAkE,CACvE;AACD,WAAQ,WAAW;AACnB;;;CAKJ,MAAM,SAAsB;EAC1B,eAAe;EACf,gBAAgB;EAChB,YAAY,EAAE;EACd,iBAAiB;EACjB,cAAc;EACd,YAAY;EACZ,WAAW;EACX,eAAe;EACf,aAAa;EACb,gBAAgB,EAAE;EACnB;CAED,IAAI,gBAAgB;CAEpB,MAAM,MAAM,GAAG,kBAAkB;AAC/B,OAAK,MAAM,KAAK,QAAQ;GACtB,MAAM,IAAI,WAAW,IAAI,EAAE;AAC3B,UAAO,iBAAiB,EAAE;AAC1B,UAAO,kBAAkB,EAAE;AAC3B,UAAO,WAAW,KAAK,GAAG,EAAE,WAAW;AACvC,UAAO,mBAAmB,EAAE;AAC5B,UAAO,gBAAgB,EAAE;AACzB,UAAO,cAAc,EAAE;AACvB,UAAO,aAAa,EAAE;AACtB,UAAO,iBAAiB,EAAE;AAC1B,UAAO,eAAe,EAAE;AACxB,UAAO,eAAe,KAAK,GAAG,EAAE,eAAe;;AAIjD,kBAAgB,kBAAkB,IAAI,MAAM,CAAC;GAC7C;AAEF,KAAI;AACF,OAAK;UACE,GAAG;AACV,UAAQ,MAAM,IAAI,qCAAqC,OAAO,EAAE,GAAG,CAAC;AACpE,UAAQ,WAAW;AACnB;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAIC,KAAG,UAAU,OAAO,YAAY,oBAAoB,CAAC;AACjE,SAAQ,IAAI,8BAA8B,OAAO,gBAAgB;AACjE,SAAQ,IACN,8BAA8B,OAAO,eAAe,KAClDC,MAAI,oCAAoC,CAC3C;AACD,SAAQ,IAAI,8BAA8B,OAAO,kBAAkB;AACnE,SAAQ,IAAI,8BAA8B,OAAO,eAAe;AAChE,SAAQ,IAAI,8BAA8B,OAAO,aAAa;AAC9D,SAAQ,IAAI,8BAA8B,OAAO,YAAY;AAC7D,SAAQ,IAAI,8BAA8B,OAAO,gBAAgB;AACjE,SAAQ,IAAI,8BAA8B,gBAAgB;AAE1D,KAAI,OAAO,eAAe,SAAS,GAAG;AACpC,UAAQ,KAAK;AACb,UAAQ,IAAID,KAAG,wCAAwC,CAAC;AACxD,OAAK,MAAM,KAAK,OAAO,eACrB,SAAQ,IAAIC,MAAI,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,CAAC;;AAIhD,KAAI,OAAO,WAAW,SAAS,GAAG;AAChC,UAAQ,KAAK;AACb,UAAQ,IACNC,OAAK,GAAG,OAAO,WAAW,OAAO,6CAA6C,CAC/E;AACD,OAAK,MAAM,KAAK,OAAO,WACrB,SAAQ,IAAID,MAAI,OAAO,EAAE,KAAK,MAAM,EAAE,GAAG,IAAI,EAAE,QAAQ,CAAC;AAE1D,UAAQ,IACNA,MAAI,qFAAqF,CAC1F;AACD,UAAQ,KAAK;AACb,UAAQ,IACNC,OACE,iFACD,CACF;AACD,UAAQ,IACND,MACE,0RAID,CACF;;AAEH,SAAQ,KAAK;;;;;ACzsBf,SAAgB,aAAa,IAAc,MAAmC;CAC5E,MAAM,OAAO,GACV,QACC;;;mCAID,CACA,KAAK;CAER,MAAM,OAAO,mBAAmB;CAChC,MAAM,OAAO,SACX,QAAQ,KAAK,IAAI,gBAAgB,KAAK,MAAM,KAAM,CAAC,CAAC,SAAS;CAK/D,MAAM,eAAe,MACnB,IAAI,EAAE,YAAY,IAAI,IAAI,UAAU,EAAE,UAAU,CAAC;CAEnD,MAAM,QAAQ,kBAAkB,MAAM,sBAAsB,KAAK,EAAE,YAAY;AAE/E,SAAQ,KAAK;AACb,KAAI,MAAM,WAAW,GAAG;AACtB,UAAQ,IAAIE,KAAG,2BAA2B,CAAC;AAC3C,UAAQ,IAAIC,MAAI,mEAAmE,CAAC;AACpF,UAAQ,KAAK;AACb;;AAGF,SAAQ,IACN,MAAM,KAAK,KAAK,MAAM,OAAO,kDAAkD,CAChF;AACD,SAAQ,KAAK;AACb,MAAK,MAAM,KAAK,OAAO;EACrB,MAAM,OAAO,EAAE,WAAW,IAAI,MAAM,OAAO,GAAG,EAAE,SAAS,uBAAuB,GAAGA,MAAI,uBAAuB;AAC9G,UAAQ,IAAI,KAAK,MAAM,KAAK,EAAE,KAAK,CAAC,IAAI,OAAO;AAC/C,UAAQ,IAAIA,MAAI,gBAAgB,EAAE,aAAa,WAAW,CAAC;AAC3D,UAAQ,IAAIA,MAAI,gBAAgB,EAAE,WAAW,KAAK,EAAE,YAAY,eAAe,CAAC;;AAElF,SAAQ,KAAK;AAEb,KAAI,CAAC,KAAK,SAAS;AACjB,UAAQ,IAAIA,MAAI,mDAAmD,CAAC;AACpE,UAAQ,KAAK;AACb;;CAKF,MAAM,SAAS,GAAG,QAAQ,mDAAmD;CAC7E,MAAM,QAAQ,GAAG,aAAa,SAAuB;AACnD,OAAK,MAAM,KAAK,KAAM,QAAO,IAAI,EAAE,YAAY,EAAE,GAAG;GACpD;AAEF,KAAI;AACF,QAAM,MAAM;AACZ,UAAQ,IAAID,KAAG,iBAAiB,MAAM,OAAO,cAAc,CAAC;EAC5D,MAAM,YAAY,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,UAAU,EAAE;AAC3D,MAAI,YAAY,EACd,SAAQ,IAAIC,MAAI,KAAK,UAAU,kCAAkC,CAAC;UAE7D,GAAG;AACV,UAAQ,MAAMC,OAAK,0BAA0B,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,EAAE;;AAE/F,SAAQ,KAAK;;;AAIf,MAAa,gBAAgB,KAAK,SAAS,EAAE,QAAQ,cAAc;;;;AC/EnE,SAASC,WAAS,IAAoB;CACpC,MAAM,gBAAiB,GAAG,QAAQ,qCAAqC,CAAC,KAAK,CAAmB;CAChG,MAAM,iBAAkB,GAAG,QAAQ,6DAA6D,CAAC,KAAK,CAAmB;CACzH,MAAM,mBAAoB,GAAG,QAAQ,+DAA+D,CAAC,KAAK,CAAmB;CAC7H,MAAM,gBAAiB,GAAG,QAAQ,qCAAqC,CAAC,KAAK,CAAmB;CAChG,MAAM,YAAa,GAAG,QAAQ,iCAAiC,CAAC,KAAK,CAAmB;CAExF,MAAM,cAAc,GACjB,QAAQ,mEAAmE,CAC3E,KAAK;CAER,MAAM,cAAc,GACjB,QAAQ,mEAAmE,CAC3E,KAAK;AAER,SAAQ,KAAK;AACb,SAAQ,IAAIC,OAAK,uBAAuB,CAAC;AACzC,SAAQ,KAAK;AACb,SAAQ,IAAI,KAAKA,OAAK,YAAY,CAAC,OAAO,gBAAgB;AAC1D,SAAQ,IAAI,KAAKA,OAAK,YAAY,CAAC,OAAO,iBAAiB;AAC3D,SAAQ,IAAI,KAAKA,OAAK,cAAc,CAAC,KAAK,mBAAmB;AAC7D,SAAQ,IAAI,KAAKA,OAAK,YAAY,CAAC,OAAO,gBAAgB;AAC1D,SAAQ,IAAI,KAAKA,OAAK,QAAQ,CAAC,WAAW,YAAY;AACtD,KAAI,YACF,SAAQ,IAAI,KAAKA,OAAK,gBAAgB,CAAC,GAAG,QAAQ,YAAY,WAAW,GAAG;AAE9E,KAAI,YACF,SAAQ,IAAI,KAAKA,OAAK,gBAAgB,CAAC,GAAG,QAAQ,YAAY,WAAW,GAAG;AAE9E,SAAQ,KAAK;;AAOf,SAAS,WAAW,IAAoB;AACtC,SAAQ,IAAIC,OAAK,0DAA0D,CAAC;AAC5E,SAAQ,IAAIC,MAAI,0BAA0B,CAAC;AAE3C,IAAG,KAAK;;;;;;;;;IASN;AAEF,SAAQ,IAAIA,MAAI,oCAAoC,CAAC;AACrD,SAAQ,GAAG;;AAOb,SAAS,UAAU,IAAc,QAAsB;CACrD,MAAM,WAAW,QAAQ,OAAO;CAEhC,MAAM,MAAM,GACT,QAAQ,gDAAgD,CACxD,IAAI,SAAS;AAEhB,KAAI,CAAC,KAAK;AACR,UAAQ,WAAW;AACnB;;AAGF,SAAQ,OAAO,MAAM,IAAI,OAAO,KAAK;;AAOvC,SAAgB,yBACd,aACA,OACM;AAEN,aACG,QAAQ,OAAO,CACf,YAAY,yEAAyE,CACrF,OAAO,oBAAoB,sCAAsC,CACjE,OAAO,uBAAuB,2CAA2C,CACzE,OAAO,eAAe,6CAA6C,CACnE,OAAO,WAAW,iDAAiD,CACnE,QAAQ,SAAuF;AAC9F,MAAI,KAAK,UAAU;GACjB,MAAM,SAAS,gBAAgB;AAC/B,OAAI,CAAC,OAAO,UAAU,QAAQ;AAC5B,YAAQ,IAAIA,MAAI,0CAA0C,CAAC;AAC3D,YAAQ,IAAIA,MAAI,qCAAqC,CAAC;UACjD;AACL,YAAQ,IAAIF,OAAK,iCAAiC,CAAC;AACnD,SAAK,MAAM,KAAK,OAAO,UACrB,SAAQ,IAAI,OAAO,IAAI;;AAG3B;;AAEF,MAAI,KAAK,QAAQ;GACf,MAAM,SAAS,gBAAgB;GAC/B,MAAM,WAAW,YAAY,KAAK,OAAO;AACzC,OAAI,CAAC,WAAW,SAAS,EAAE;AACzB,YAAQ,MAAM,IAAI,wBAAwB,WAAW,CAAC;AACtD,YAAQ,WAAW;AACnB;;GAEF,MAAM,UAAU,SAAS,WAAW,SAAS,CAAC,GAC1C,MAAM,SAAS,MAAM,SAAS,CAAC,OAAO,GACtC;AACJ,OAAI,OAAO,UAAU,SAAS,QAAQ,IAAI,OAAO,UAAU,SAAS,SAAS,CAC3E,SAAQ,IAAIC,OAAK,uBAAuB,UAAU,CAAC;QAC9C;AACL,WAAO,UAAU,KAAK,QAAQ;AAC9B,mBAAe,OAAO;AACtB,YAAQ,IAAIE,KAAG,yBAAyBH,OAAK,QAAQ,GAAG,CAAC;;;AAG7D,MAAI,KAAK,WAAW;GAClB,MAAM,SAAS,gBAAgB;GAC/B,MAAM,WAAW,YAAY,KAAK,UAAU;GAC5C,MAAM,UAAU,SAAS,WAAW,SAAS,CAAC,GAC1C,MAAM,SAAS,MAAM,SAAS,CAAC,OAAO,GACtC;GACJ,MAAM,SAAS,OAAO,UAAU;AAChC,UAAO,YAAY,OAAO,UAAU,QAAQ,MAAM,YAAY,EAAE,KAAK,SAAS;AAC9E,OAAI,OAAO,UAAU,SAAS,QAAQ;AACpC,mBAAe,OAAO;AACtB,YAAQ,IAAIG,KAAG,2BAA2BH,OAAK,QAAQ,GAAG,CAAC;SAE3D,SAAQ,IAAIC,OAAK,wBAAwB,UAAU,CAAC;;AAGxD,MAAI,CAAC,KAAK,UAAU,CAAC,KAAK,UACxB,SAAQ,OAAO,EAAE,EAAE,OAAO,KAAK,OAAO,CAAC;GAEzC;AAGJ,aACG,QAAQ,UAAU,CAClB,YAAY,mDAAmD,CAC/D,aAAa;AACZ,eAAW,OAAO,CAAC;GACnB;AAGJ,aACG,QAAQ,QAAQ,CAChB,YAAY,2CAA2C,CACvD,aAAa;AACZ,aAAS,OAAO,CAAC;GACjB;AAGJ,aACG,QAAQ,UAAU,CAClB,YAAY,wEAAwE,CACpF,aAAa;AACZ,aAAW,OAAO,CAAC;GACnB;AAGJ,aACG,QAAQ,SAAS,CACjB,YACC,6XAKD,CACA,OAAO,aAAa,kDAAkD,CACtE,QAAQ,SAAgC;AACvC,YAAU,OAAO,EAAE;GACjB,SAAS,KAAK;GACd,QAAQ,KAAK,SAAS,EAAE,QAAQ,cAAc;GAC/C,CAAC;GACF;AAGJ,aACG,QAAQ,YAAY,CACpB,YACC,waAMD,CACA,OAAO,aAAa,sDAAsD,CAC1E,QAAQ,SAAgC;AACvC,eAAa,OAAO,EAAE,EAAE,SAAS,KAAK,SAAS,CAAC;GAChD;AAGJ,aACG,QAAQ,SAAS,CACjB,YAAY,mEAAmE,CAC/E,eAAe,iBAAiB,6BAA6B,CAC7D,QAAQ,SAA2B;AAClC,YAAU,OAAO,EAAE,KAAK,KAAK;GAC7B;;;;;ACtNN,eAAsB,SACpB,YACA,WACA,aACA,YAAY,IACG;CACf,MAAM,QAAQ,cAAc,WAAW,gBAAgB;AACvD,SAAQ,IAAIG,MAAI,6BAA6B,MAAM,0CAA0C,CAAC;CAE9F,MAAM,EAAE,mBAAmB,MAAM,YAC/B,YACA,WACA,YACC,MAAM,UAAU;AACf,UAAQ,OAAO,MAAM,OAAO,KAAK,KAAK,MAAM,qBAAqB;GAEpE;AAED,SAAQ,OAAO,MAAM,KAAK;AAC1B,SAAQ,IAAIC,KAAG,QAAQ,GAAG,KAAKC,OAAK,OAAO,eAAe,CAAC,CAAC,kBAAkB;;AAOhF,SAAgB,qBACd,WACA,OACM;AACN,WACG,QAAQ,uBAAuB,CAC/B,YAAY,yDAAyD,CACrE,OAAO,oBAAoB,6BAA6B,KAAK,CAC7D,OAAO,OAAO,aAAiC,SAAiC;EAC/E,MAAM,aAAa,OAAO;EAE1B,IAAI;AACJ,MAAI;AACF,gBAAa,gBAAgB;WACtB,GAAG;AACV,WAAQ,MAAM,IAAI,uCAAuC,IAAI,CAAC;AAC9D,WAAQ,WAAW;AACnB;;AAGF,MAAI,aAAa;GACf,MAAM,UAAU,WACb,QAAQ,+CAA+C,CACvD,IAAI,YAAY;AAEnB,OAAI,CAAC,SAAS;AACZ,YAAQ,MAAM,IAAI,sBAAsB,cAAc,CAAC;AACvD,YAAQ,WAAW;AACnB;;AAGF,SAAM,SAAS,YAAY,QAAQ,IAAI,QAAQ,MAAM,SAAS,KAAK,aAAa,MAAM,GAAG,CAAC;QAE1F,OAAM,SAAS,YAAY,QAAW,QAAW,SAAS,KAAK,aAAa,MAAM,GAAG,CAAC;GAExF;;;;;AC9DN,SAAgB,qBACd,WACA,OACM;AACN,WACG,QAAQ,uBAAuB,CAC/B,YAAY,qDAAqD,CACjE,OAAO,SAAS,yDAAyD,CACzE,OAAO,WAAW,gEAAgE,CAClF,OAAO,YAAY,yDAAyD,CAC5E,OAAO,OAAO,aAAiC,SAA+D;EAC7G,MAAM,aAAa,OAAO;AAG1B,MAAI,CAAC,KAAK,UAAU,CAAC,YACnB,KAAI;AAGF,SADe,IAAI,UADJ,YAAY,CACS,WAAW,CAClC,cAAc;AAC3B,WAAQ,IAAIC,KAAG,6BAA6B,GAAGC,MAAI,oCAAoC,CAAC;AACxF,WAAQ,IAAIA,MAAI,6CAA6C,CAAC;AAC9D;UACM;AACN,WAAQ,IAAIA,MAAI,8CAA8C,CAAC;;EAInE,IAAI;AACJ,MAAI;AACF,gBAAa,gBAAgB;WACtB,GAAG;AACV,WAAQ,MAAM,IAAI,uCAAuC,IAAI,CAAC;AAC9D,WAAQ,WAAW;AACnB;;AAGF,MAAI,aAAa;GACf,MAAM,UAAU,WACb,QAAQ,8FAA8F,CACtG,IAAI,YAAY;AAInB,OAAI,CAAC,SAAS;AACZ,YAAQ,MAAM,IAAI,oCAAoC,cAAc,CAAC;AACrE,YAAQ,WAAW;AACnB;;AAGF,WAAQ,IAAIA,MAAI,YAAY,QAAQ,aAAa,IAAI,QAAQ,KAAK,MAAM,CAAC;GACzE,MAAM,SAAS,MAAM,aAAa,YAAY,QAAQ,IAAI,QAAQ,UAAU;AAE5E,WAAQ,IACND,KAAG,QAAQ,GACX,KAAKE,OAAK,OAAO,OAAO,eAAe,CAAC,CAAC,kBACpCA,OAAK,OAAO,OAAO,cAAc,CAAC,CAAC,mBACnCD,MAAI,OAAO,OAAO,aAAa,GAAG,uBAAuB,GAC/D;AAED,OAAI,KAAK,MACP,OAAM,SAAS,YAAY,QAAQ,IAAI,QAAQ,KAAK;aAG7C,KAAK,OAAO,CAAC,aAAa;AACnC,WAAQ,IAAIA,MAAI,kCAAkC,CAAC;GAEnD,MAAM,EAAE,UAAU,WAAW,MAAM,SAAS,YAAY,WAAW;AAEnE,WAAQ,IACND,KAAG,QAAQ,GACX,KAAKE,OAAK,OAAO,SAAS,CAAC,CAAC,aACvBA,OAAK,OAAO,OAAO,eAAe,CAAC,CAAC,kBACpCA,OAAK,OAAO,OAAO,cAAc,CAAC,CAAC,mBACnCD,MAAI,OAAO,OAAO,aAAa,GAAG,uBAAuB,GAC/D;AAED,OAAI,KAAK,MACP,OAAM,SAAS,WAAW;;GAG9B;;;;;AC5EN,SAASE,YAAU,MAAsB;AACvC,SAAQ,MAAR;EACE,KAAK,YAAa,QAAO,MAAM,MAAM,KAAK;EAC1C,KAAK,QAAa,QAAO,MAAM,OAAO,KAAK;EAC3C,KAAK,QAAa,QAAO,MAAM,KAAK,KAAK;EACzC,KAAK,UAAa,QAAO,MAAM,IAAI,KAAK;EACxC,QAAkB,QAAO;;;AAQ7B,SAAgB,sBACd,WACA,OACM;AACN,WACG,QAAQ,iBAAiB,CACzB,YAAY,4DAA4D,CACxE,OAAO,oBAAoB,wCAAwC,CACnE,OAAO,qBAAqB,kCAAkC,CAC9D,OAAO,eAAe,4BAA4B,CAClD,OAAO,iBAAiB,mDAAmD,CAC3E,OAAO,eAAe,4DAA4D,CAClF,OAAO,oBAAoB,0DAA0D,CACrF,OACC,OACE,OACA,SACG;EACH,MAAM,aAAa,OAAO;EAE1B,IAAI;AACJ,MAAI;AACF,gBAAa,gBAAgB;WACtB,GAAG;AACV,WAAQ,MAAM,IAAI,uCAAuC,IAAI,CAAC;AAC9D,WAAQ,WAAW;AACnB;;EAGF,MAAM,SAAS,YAAY;EAC3B,MAAM,eAAe,OAAO;EAE5B,MAAM,aAAa,SAAS,KAAK,SAAS,OAAO,aAAa,aAAa,EAAE,GAAG;EAChF,MAAM,OAAQ,KAAK,QAAQ,aAAa;AAExC,MAAI,CAAC;GAAC;GAAW;GAAY;GAAS,CAAC,SAAS,KAAK,EAAE;AACrD,WAAQ,MAAM,IAAI,iBAAiB,KAAK,qCAAqC,CAAC;AAC9E,WAAQ,WAAW;AACnB;;EAGF,IAAI;AACJ,MAAI,KAAK,SAAS;GAChB,MAAM,UAAU,WACb,QAAQ,yCAAyC,CACjD,IAAI,KAAK,QAAQ;AAEpB,OAAI,CAAC,QACH,SAAQ,MAAMC,OAAK,sBAAsB,KAAK,QAAQ,2BAA2B,CAAC;OAElF,cAAa,CAAC,QAAQ,GAAG;;EAI7B,MAAM,UAAU,KAAK,SAAS,CAAC,KAAK,OAAO,GAAG;EAC9C,MAAM,aAAa;GAAE;GAAY;GAAS;GAAY;EAEtD,IAAI;AAEJ,MAAI,SAAS,UACX,WAAU,aAAa,YAAY,OAAO,WAAW;WAE5C,SAAS,cAAc,SAAS,UAAU;GACnD,MAAM,UAAU,MAAM,qBAAqB,OAAO;AAElD,OAAI;IACF,MAAM,EAAE,sBAAsB,MAAM,OAAO;AAE3C,YAAQ,IAAIC,MAAI,gCAAgC,CAAC;IACjD,MAAM,iBAAiB,MAAM,kBAAkB,OAAO,KAAK;AAE3D,QAAI,SAAS,WACX,WAAU,MAAM,QAAQ,eAAe,gBAAgB,WAAW;SAC7D;KAEL,MAAM,CAAC,gBAAgB,mBAAmB,MAAM,QAAQ,IAAI,CAC1D,QAAQ,cAAc,OAAO;MAAE,GAAG;MAAY,YAAY;MAAK,CAAC,EAChE,QAAQ,eAAe,gBAAgB;MAAE,GAAG;MAAY,YAAY;MAAK,CAAC,CAC3E,CAAC;AAEF,SAAI,eAAe,WAAW,KAAK,gBAAgB,WAAW,EAC5D,WAAU,EAAE;UACP;MACL,MAAM,UAAU,MACd,GAAG,EAAE,UAAU,GAAG,EAAE,KAAK,GAAG,EAAE,UAAU,GAAG,EAAE;MAE/C,SAAS,gBAAgB,OAA4C;AACnE,WAAI,MAAM,WAAW,EAAG,wBAAO,IAAI,KAAK;OACxC,MAAM,MAAM,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,MAAM,CAAC;OAElD,MAAM,QADM,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,MAAM,CAAC,GAC9B;OACpB,MAAM,oBAAI,IAAI,KAAqB;AACnC,YAAK,MAAM,KAAK,MACd,GAAE,IAAI,OAAO,EAAE,EAAE,UAAU,IAAI,KAAK,EAAE,QAAQ,OAAO,MAAM;AAE7D,cAAO;;MAGT,MAAM,SAAS,gBAAgB,eAAe;MAC9C,MAAM,UAAU,gBAAgB,gBAAgB;MAChD,MAAM,UAAU,IAAI,IAAY,CAC9B,GAAG,eAAe,IAAI,OAAO,EAC7B,GAAG,gBAAgB,IAAI,OAAO,CAC/B,CAAC;MACF,MAAM,0BAAU,IAAI,KAA2B;AAC/C,WAAK,MAAM,KAAK,CAAC,GAAG,gBAAgB,GAAG,gBAAgB,CACrD,SAAQ,IAAI,OAAO,EAAE,EAAE,EAAE;MAG3B,MAAM,WAA2B,EAAE;AACnC,WAAK,MAAM,OAAO,SAAS;OACzB,MAAM,OAAO,QAAQ,IAAI,IAAI;OAC7B,MAAM,gBAAgB,MAAO,OAAO,IAAI,IAAI,IAAI,KAAK,MAAO,QAAQ,IAAI,IAAI,IAAI;AAChF,gBAAS,KAAK;QAAE,GAAG;QAAM,OAAO;QAAe,CAAC;;AAGlD,gBAAU,SAAS,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,GAAG,WAAW;;;aAGrE;AACR,UAAM,QAAQ,OAAO;;QAGvB,WAAU,EAAE;AAGd,MAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AACpC,WAAQ,IAAIA,MAAI,0BAA0B,MAAM,WAAW,KAAK,GAAG,CAAC;AACpE;;AAIF,MAAI,KAAK,WAAW,OAAO;GACzB,MAAM,EAAE,kBAAkB,MAAM,OAAO;AACvC,WAAQ,IAAIA,MAAI,kCAAkC,CAAC;AACnD,aAAU,MAAM,cAAc,OAAO,SAAS,EAAE,MAAM,YAAY,CAAC;;EAIrE,MAAM,cAAc,SAAS,KAAK,WAAW,OAAO,aAAa,iBAAiB,EAAE,GAAG;AACvF,MAAI,cAAc,GAAG;GACnB,MAAM,EAAE,sBAAsB,MAAM,OAAO;AAC3C,WAAQ,IAAIA,MAAI,sCAAsC,YAAY,WAAW,CAAC;AAC9E,aAAU,kBAAkB,SAAS,YAAY;;EAGnD,MAAM,YAAY,cAAc,SAAS,WAAW;EACpD,MAAM,cAAc,KAAK,WAAW,QAAQ,aAAa;EACzD,MAAM,YAAY,SAAS,YAAY,KAAK,OAAO,YAAY,KAAM,KAAK,WAAW,QAAQ,cAAc;AAE3G,UAAQ,IACN,OAAOC,OAAK,wBAAwB,MAAM,GAAG,GAAG,UAAU,IAAID,MAAI,IAAI,UAAU,OAAO,SAAS,CAAC,IAClG;AAED,OAAK,MAAM,UAAU,WAAW;GAC9B,MAAM,eAAe,OAAO,cACxB,MAAM,KAAK,OAAO,YAAY,GAC9B,MAAM,KAAK,OAAO,OAAO,UAAU,CAAC;GAExC,MAAM,YAAYF,YAAU,OAAO,KAAK;GACxC,MAAM,aAAaE,MAAI,UAAU,OAAO,MAAM,QAAQ,EAAE,GAAG;GAC3D,MAAM,gBAAgBA,MAAI,GAAG,OAAO,KAAK,GAAG,OAAO,UAAU,GAAG,OAAO,UAAU;AAEjF,WAAQ,IAAI,KAAK,aAAa,IAAI,UAAU,IAAI,cAAc,IAAI,aAAa;GAE/E,MAAM,eAAe,OAAO,QACzB,MAAM,KAAK,CACX,MAAM,GAAG,EAAE,CACX,KAAK,MAAM,OAAO,IAAI;AACzB,WAAQ,IAAI,aAAa,KAAK,KAAK,CAAC;AACpC,WAAQ,KAAK;;GAGlB;;;;;AChML,SAAS,UAAU,MAAsB;AACvC,SAAQ,MAAR;EACE,KAAK,YAAa,QAAO,MAAM,MAAM,KAAK;EAC1C,KAAK,QAAa,QAAO,MAAM,OAAO,KAAK;EAC3C,KAAK,QAAa,QAAO,MAAM,KAAK,KAAK;EACzC,KAAK,UAAa,QAAO,MAAM,IAAI,KAAK;EACxC,QAAkB,QAAO;;;AAI7B,SAAgB,sBACd,WACA,OACM;AAMN,WACG,QAAQ,wBAAwB,CAChC,YAAY,+BAA+B,CAC3C,QAAQ,gBAAoC;EAC3C,MAAM,aAAa,OAAO;EAE1B,IAAI;AACJ,MAAI;AACF,gBAAa,gBAAgB;WACtB,GAAG;AACV,WAAQ,MAAM,IAAI,uCAAuC,IAAI,CAAC;AAC9D,WAAQ,WAAW;AACnB;;AAQF,MAAI;AACF,cAAW,OAAO,sBAAsB;UAClC;EAgBR,MAAM,UAAU,YAAY,CAAC;AAC7B,MAAI,YAAY,UAAU;AACxB,WAAQ,KAAK;AACb,WAAQ,IAAIE,OAAK,iCAAiC,CAAC;AACnD,WAAQ,IACNC,MAAI,wBAAwB,QAAQ,oCAAoC,CACzE;AACD,WAAQ,IAAIA,MAAI,mDAAmD,CAAC;AACpE,WAAQ,KAAK;AACb,WAAQ,IAAI,4BAA4BC,OAAK,oBAAoB,GAAG;AACpE,WAAQ,KAAK;AACb,OAAI;AACF,eAAW,OAAO;WACZ;AAGR;;AAGF,MAAI,aAAa;GACf,MAAM,UAAU,WACb,QAAQ,6DAA6D,CACrE,IAAI,YAAY;AAInB,OAAI,CAAC,SAAS;AACZ,YAAQ,MAAM,IAAI,sBAAsB,cAAc,CAAC;AACvD,YAAQ,WAAW;AACnB;;GAGF,MAAM,YAAY,WACf,QAAQ,kEAAkE,CAC1E,IAAI,QAAQ,GAAG;GAElB,MAAM,aAAa,WAChB,QAAQ,oEAAoE,CAC5E,IAAI,QAAQ,GAAG;GAElB,MAAM,aAAa,WAChB,QAAQ,4EAA4E,CACpF,IAAI,QAAQ,GAAG;GAElB,MAAM,gBAAgB,WACnB,QAAQ,mGAAmG,CAC3G,IAAI,QAAQ,GAAG;AAElB,WAAQ,IAAI,OAAOA,OAAK,QAAQ,aAAa,CAAC,GAAGD,MAAI,IAAI,QAAQ,KAAK,GAAG,CAAC,IAAI;AAC9E,WAAQ,IAAI,KAAKC,OAAK,iBAAiB,CAAC,IAAI,UAAU,QAAQ;AAC9D,WAAQ,IAAI,KAAKA,OAAK,UAAU,CAAC,WAAW,WAAW,SAAS;AAChE,WAAQ,IAAI,KAAKA,OAAK,gBAAgB,CAAC,KAAK,WAAW,UAAU,QAAQ,WAAW,QAAQ,GAAGD,MAAI,QAAQ,GAAG;AAE9G,OAAI,cAAc,SAAS,GAAG;AAC5B,YAAQ,IAAI,OAAOC,OAAK,WAAW,GAAG;AACtC,SAAK,MAAM,OAAO,cAChB,SAAQ,IAAI,OAAO,UAAU,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,IAAI,IAAI,EAAE,SAAS;;AAGzE,WAAQ,KAAK;SAER;GACL,MAAM,cAAc,WAAW,QAAQ,yCAAyC,CAAC,KAAK;GACtF,MAAM,eAAe,WAAW,QAAQ,0CAA0C,CAAC,KAAK;GACxF,MAAM,aAAa,WAAW,QAAQ,uDAAuD,CAAC,KAAK;GAEnG,MAAM,eAAe,WAClB,QACC;;;;;;;;mCASD,CACA,KAAK;GAER,MAAM,aAAa,aAAa,KAAK,MAAM,EAAE,WAAW;GACxD,MAAM,0BAAU,IAAI,KAAqB;AACzC,OAAI,WAAW,SAAS,GAAG;IACzB,MAAM,eAAe,WAAW,UAAU,IAAI,CAAC,KAAK,KAAK;IACzD,MAAM,WAAW,WACd,QAAQ,4DAA4D,aAAa,GAAG,CACpF,IAAI,GAAG,WAAW;AACrB,SAAK,MAAM,OAAO,SAChB,SAAQ,IAAI,IAAI,IAAI,IAAI,KAAK;;AAIjC,WAAQ,IAAI,OAAOA,OAAK,mCAAmC,CAAC,IAAI;AAChE,WAAQ,IAAI,KAAKA,OAAK,eAAe,CAAC,KAAK,YAAY,EAAE,KAAKA,OAAK,gBAAgB,CAAC,IAAI,aAAa,IAAI;AACzG,WAAQ,IAAI,KAAKA,OAAK,gBAAgB,CAAC,IAAI,WAAW,UAAU,QAAQ,WAAW,QAAQ,GAAGD,MAAI,QAAQ,GAAG;AAE7G,OAAI,aAAa,SAAS,GAAG;AAC3B,YAAQ,KAAK;IACb,MAAM,OAAO,aAAa,KAAK,MAAM;KACnC,MAAM,KAAK,QAAQ,IAAI,EAAE,WAAW,IAAI,OAAO,EAAE,WAAW,CAAC;KAC7D,OAAO,EAAE,MAAM;KACf,OAAO,EAAE,OAAO;KAChB,EAAE,UAAU,QAAQ,EAAE,QAAQ,GAAGA,MAAI,QAAQ;KAC9C,CAAC;AACF,YAAQ,IAAI,YAAY;KAAC;KAAW;KAAS;KAAU;KAAe,EAAE,KAAK,CAAC;SAE9E,SAAQ,IAAIA,MAAI,sEAAsE,CAAC;AAEzF,WAAQ,KAAK;;GAEf;AAMJ,WACG,QAAQ,yBAAyB,CACjC,YAAY,8DAA8D,CAC1E,QAAQ,KAAyB,UAA8B;EAE9D,MAAM,SADS,YAAY,CACL;AAEtB,MAAI,CAAC,KAAK;AACR,WAAQ,IAAI,OAAOC,OAAK,+BAA+B,CAAC,IAAI;AAC5D,WAAQ,IAAI,KAAKA,OAAK,QAAQ,CAAC,eAAe,OAAO,OAAO;AAC5D,WAAQ,IAAI,KAAKA,OAAK,UAAU,CAAC,aAAa,OAAO,SAAS;AAC9D,WAAQ,IAAI,KAAKA,OAAK,oBAAoB,CAAC,GAAG,OAAO,mBAAmB;AACxE,WAAQ,IAAI,KAAKA,OAAK,gBAAgB,CAAC,OAAO,OAAO,eAAe;AACpE,WAAQ,IAAI,KAAKA,OAAK,iBAAiB,CAAC,MAAM,OAAO,gBAAgB;AACrE,WAAQ,KAAK;AACb,WAAQ,IAAID,MAAI,kBAAkBE,gBAAc,CAAC;AACjD,WAAQ,IAAIF,MAAI,4DAA4D,CAAC;AAC7E,WAAQ,KAAK;AACb;;AAGF,MAAI,CAAC,OAAO;GACV,MAAM,MAAO,OAA8C;AAC3D,OAAI,QAAQ,QAAW;AACrB,YAAQ,MAAM,IAAI,oBAAoB,MAAM,CAAC;AAC7C,YAAQ,IAAIA,MAAI,4EAA4E,CAAC;AAC7F,YAAQ,WAAW;AACnB;;AAEF,WAAQ,IAAI,OAAO,IAAI,CAAC;AACxB;;EAGF,MAAM,YAAY,IAAI,IAAI;GAAC;GAAQ;GAAU;GAAoB;GAAgB;GAAgB,CAAC;AAClG,MAAI,CAAC,UAAU,IAAI,IAAI,EAAE;AACvB,WAAQ,MAAM,IAAI,oBAAoB,MAAM,CAAC;AAC7C,WAAQ,IAAIA,MAAI,iBAAiB,CAAC,GAAG,UAAU,CAAC,KAAK,KAAK,GAAG,CAAC;AAC9D,WAAQ,WAAW;AACnB;;EAGF,IAAI,aAAsC,EAAE;AAC5C,MAAI,WAAWE,cAAY,CACzB,KAAI;AACF,gBAAa,KAAK,MAAM,aAAaA,eAAa,QAAQ,CAAC;UACrD;AACN,WAAQ,MAAM,IAAI,mBAAmBA,gBAAc,CAAC;AACpD,WAAQ,WAAW;AACnB;;AAIJ,MAAI,CAAC,WAAW,UAAU,OAAO,WAAW,WAAW,SACrD,YAAW,SAAS,EAAE;EAGxB,IAAI;AACJ,MAAI,QAAQ,QAAQ;AAClB,OAAI,CAAC;IAAC;IAAW;IAAY;IAAS,CAAC,SAAS,MAAM,EAAE;AACtD,YAAQ,MAAM,IAAI,iBAAiB,MAAM,yCAAyC,CAAC;AACnF,YAAQ,WAAW;AACnB;;AAEF,YAAS;aACA,QAAQ,SACjB,UAAS,UAAU,UAAU,UAAU,OAAO,UAAU;OACnD;AACL,YAAS,SAAS,OAAO,GAAG;AAC5B,OAAI,MAAM,OAAO,EAAE;AACjB,YAAQ,MAAM,IAAI,mBAAmB,QAAQ,CAAC;AAC9C,YAAQ,WAAW;AACnB;;;AAIJ,EAAC,WAAW,OAAmC,OAAO;AAEtD,MAAI;AACF,oBAAiB;AACjB,iBAAcA,eAAa,KAAK,UAAU,YAAY,MAAM,EAAE,GAAG,MAAM,QAAQ;AAC/E,WAAQ,IAAIC,KAAG,cAAc,IAAI,KAAK,SAAS,CAAC;AAChD,WAAQ,IAAIH,MAAI,gDAAgD,CAAC;WAC1D,GAAG;AACV,WAAQ,MAAM,IAAI,2BAA2B,IAAI,CAAC;AAClD,WAAQ,WAAW;;GAErB;;;;;ACxQN,SAAgB,uBAAuB,WAA0B;AAC/D,WACG,QAAQ,UAAU,CAClB,YACC,8NAGD,CACA,OAAO,eAAe,gCAAgC,IAAI,CAC1D,OAAO,OAAO,SAA6B;EAI1C,MAAM,EAAE,yBAAyB,MAAM,OAAO;EAC9C,MAAM,EAAE,eAAe,MAAM,OAAO;EACpC,MAAM,EAAE,qBAAqB,MAAM,OAAO;EAE1C,IAAI;AACJ,MAAI;AACF,aAAU,MAAM,qBAAqB,YAAY,CAAC;WAC3C,GAAG;AACV,WAAQ,MAAM,IAAI,oCAAoC,CAAC;AACvD,WAAQ,MAAMI,MAAI,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,GAAG,CAAC;AACrE,WAAQ,WAAW;AACnB;;AAGF,MAAI;AACF,SAAM,iBAAiB,SAAS,EAC9B,OAAO,KAAK,IAAI,GAAG,SAAS,KAAK,SAAS,KAAK,GAAG,IAAI,EAAE,EACzD,CAAC;YACM;AACR,SAAM,QAAQ,SAAS,CAAC,cAAc,GAAG;;GAE3C;;;;;AC7BN,SAAgB,uBACd,WACA,OACM;AACN,sBAAqB,WAAW,MAAM;AACtC,sBAAqB,WAAW,MAAM;AACtC,uBAAsB,WAAW,MAAM;AACvC,uBAAsB,WAAW,MAAM;AACvC,wBAAuB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACkBnC,SAAgB,eAAe,KAA+C;AAC5E,KAAI,CAAC,IAAK,QAAO;CAEjB,IAAI,QAAQ,IAAI,MAAM;AACtB,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,SAAS,MAAM,MAAM,YAAY;AACvC,KAAI,OAAQ,SAAQ,OAAO,GAAG,MAAM;AAEpC,SAAQ,MAAM,aAAa;AAK3B,KAAI,CAAC,6BAA6B,KAAK,MAAM,CAAE,QAAO;AAEtD,QAAO;;AAqGT,SAAgB,0BACd,WACA,gBACA,wBAAkC,EAAE,EACf;CACrB,MAAM,KAAK,eAAe,UAAU;CACpC,MAAM,OAAO,eAAe,eAAe;AAE3C,KAAI,CAAC,MAAM,CAAC,KACV,QAAO;EAAE,SAAS;EAAW,QAAQ;EAAsD;AAG7F,KAAI,OAAO,KACT,QAAO;EACL,SAAS;EACT,QACE,GAAG,GAAG;EAET;AAYH,KAJgB,sBACb,IAAI,eAAe,CACnB,QAAQ,MAAmB,MAAM,KAAK,CAE7B,SAAS,GAAG,CACtB,QAAO;EACL,SAAS;EACT,QACE,GAAG,GAAG,+CAA+C,KAAK;EAG7D;AAMH,KAHiB,GAAG,MAAM,GAAG,QAAQ,IAAI,GAAG,EAAE,KAC3B,KAAK,MAAM,KAAK,QAAQ,IAAI,GAAG,EAAE,CAGlD,QAAO;EACL,SAAS;EACT,QACE,GAAG,GAAG,6CAA6C,KAAK;EAG3D;AAMH,QAAO;EACL,SAAS;EACT,QACE;EAEH;;;;;ACzLH,SAAS,aAA0B;AACjC,KAAI,CAAC,WAAWC,cAAY,CAAE,QAAO,EAAE;AACvC,KAAI;AACF,SAAO,KAAK,MAAM,aAAaA,eAAa,QAAQ,CAAC;UAC9C,GAAG;AACV,UAAQ,MAAM,IAAI,iBAAiB,GAAG,mBAAmBA,cAAY,IAAI,OAAO,EAAE,GAAG;AACrF,UAAQ,KAAK,EAAE;;;;;;;;AASnB,SAAS,YAAY,QAA2B;CAC9C,MAAM,MAAM,GAAGA,cAAY;AAC3B,eAAc,KAAK,KAAK,UAAU,QAAQ,MAAM,EAAE,GAAG,MAAM,QAAQ;AACnE,YAAW,KAAKA,cAAY;;AAG9B,SAAS,WAAW,QAAqB;AACvC,QAAO,OAAO,YAAY,EAAE,YAAY,EAAE,EAAE;;AAG9C,SAAS,mBAAmB,IAA+D;CACzF,MAAM,IAAI,0BACR,GAAG,WACH,GAAG,gBACH,GAAG,yBAAyB,EAAE,CAC/B;AACD,KAAI,EAAE,YAAY,cAChB,SAAQ,IAAI,IAAI,kBAAkB,GAAG,EAAE,OAAO;UACrC,EAAE,YAAY,UACvB,SAAQ,IAAIC,OAAK,cAAc,GAAG,EAAE,OAAO;;AAI/C,SAAgB,yBAAyB,aAA4B;AACnE,aACG,QAAQ,OAAO,CACf,YAAY,+BAA+B,CAC3C,aAAa;EACZ,MAAM,KAAK,WAAW,YAAY,CAAC;EACnC,MAAM,SAAS,GAAG,cAAc,EAAE;AAElC,UAAQ,KAAK;AACb,UAAQ,IAAIC,OAAK,aAAa,CAAC;AAC/B,UAAQ,KAAK;AACb,UAAQ,IAAI,uBAAuB,GAAG,aAAaC,MAAI,UAAU,GAAG;AACpE,UAAQ,IAAI,uBAAuB,GAAG,kBAAkBA,MAAI,UAAU,GAAG;AACzE,UAAQ,KAAK;AAEb,MAAI,OAAO,WAAW,GAAG;AACvB,WAAQ,IAAIF,OAAK,kCAAkC,CAAC;AACpD,WAAQ,IACNE,MAAI,sEAAsE,CAC3E;AACD,WAAQ,IAAIA,MAAI,2CAA2C,CAAC;SACvD;AACL,WAAQ,IAAID,OAAK,yBAAyB,OAAO,OAAO,IAAI,CAAC;AAC7D,QAAK,MAAM,KAAK,OAAQ,SAAQ,IAAI,OAAO,IAAI;AAC/C,WAAQ,KAAK;AACb,WAAQ,IACNC,MAAI,wEAAwE,CAC7E;;AAEH,UAAQ,KAAK;AACb,qBAAmB,GAAG;AACtB,UAAQ,KAAK;GACb;AAEJ,aACG,QAAQ,cAAc,CACtB,YAAY,sEAAsE,CAClF,QAAQ,UAAkB;EACzB,MAAM,aAAa,eAAe,MAAM;AACxC,MAAI,CAAC,YAAY;AACf,WAAQ,MAAM,IAAI,iBAAiB,GAAG,+BAA+B,QAAQ;AAC7E,WAAQ,WAAW;AACnB;;EAGF,MAAM,SAAS,YAAY;EAC3B,MAAM,KAAK,WAAW,OAAO;EAC7B,MAAM,UAAU,GAAG,cAAc,EAAE,EAAE,OAAO;AAE5C,MAAI,OAAO,IAAI,eAAe,CAAC,SAAS,WAAW,EAAE;AACnD,WAAQ,IAAIA,MAAI,KAAK,WAAW,sCAAsC,CAAC;AACvE;;AAGF,SAAO,KAAK,WAAW;AACvB,SAAO,WAAW;GAAE,GAAG;GAAI,YAAY;GAAQ;AAC/C,cAAY,OAAO;AAEnB,UAAQ,IAAIC,KAAG,UAAU,GAAG,GAAG,WAAW,kCAAkC;AAC5E,UAAQ,IAAID,MAAI,0EAA0E,CAAC;GAC3F;AAEJ,aACG,QAAQ,iBAAiB,CACzB,YAAY,uCAAuC,CACnD,QAAQ,UAAkB;EACzB,MAAM,aAAa,eAAe,MAAM;EACxC,MAAM,SAAS,YAAY;EAC3B,MAAM,KAAK,WAAW,OAAO;EAC7B,MAAM,SAAS,GAAG,cAAc,EAAE;EAClC,MAAM,QAAQ,OAAO,QAAQ,MAAM,eAAe,EAAE,KAAK,WAAW;AAEpE,MAAI,MAAM,WAAW,OAAO,QAAQ;AAClC,WAAQ,IAAIA,MAAI,KAAK,MAAM,mCAAmC,CAAC;AAC/D;;AAGF,SAAO,WAAW;GAAE,GAAG;GAAI,YAAY;GAAO;AAC9C,cAAY,OAAO;AACnB,UAAQ,IAAIC,KAAG,YAAY,GAAG,GAAG,MAAM,oCAAoC;GAC3E;AAEJ,aACG,QAAQ,uBAAuB,CAC/B,YAAY,kDAAkD,CAC9D,QAAQ,UAAkB;EACzB,MAAM,aAAa,eAAe,MAAM;AACxC,MAAI,CAAC,YAAY;AACf,WAAQ,MAAM,IAAI,iBAAiB,GAAG,+BAA+B,QAAQ;AAC7E,WAAQ,WAAW;AACnB;;EAGF,MAAM,SAAS,YAAY;EAC3B,MAAM,KAAK,WAAW,OAAO;AAC7B,SAAO,WAAW;GAAE,GAAG;GAAI,WAAW;GAAY;AAClD,cAAY,OAAO;AAEnB,UAAQ,IAAIA,KAAG,yBAAyB,GAAG,WAAW;AAKtD,qBAAmB,OAAO,SAAS;AAEnC,MAAI,EAAE,GAAG,cAAc,EAAE,EAAE,IAAI,eAAe,CAAC,SAAS,WAAW,CACjE,SAAQ,IACNH,OAAK,WAAW,GACd,GAAG,WAAW,kHACqC,aACtD;GAEH;;;;;;;;;;;;;;;;;;;;;;;;;AC1JN,MAAa,mBAAmB,KAAK,SAAS,EAAE,eAAe;AAC/D,MAAM,cAAc,GAAG,iBAAiB;;;;;;;;;AAUxC,SAAgB,iBAA0C;AACxD,KAAI,CAAC,WAAW,iBAAiB,CAAE,QAAO,EAAE;CAE5C,IAAI;AACJ,KAAI;AACF,QAAM,aAAa,kBAAkB,OAAO;UACrC,GAAG;AACV,QAAM,IAAI,MACR,kBAAkB,iBAAiB,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,6FAEnF;;AAGH,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;UACf,GAAG;AACV,QAAM,IAAI,MACR,GAAG,iBAAiB,sBAAsB,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,0LAGtF;;;;;;;;;;AAWL,SAAgB,gBAAgB,MAAqC;CACnE,MAAM,aAAa,KAAK,UAAU,MAAM,MAAM,EAAE,GAAG;AAEnD,KAAI,WAAW,iBAAiB,CAC9B,KAAI;AACF,eAAa,kBAAkB,YAAY;UACpC,GAAG;AACV,QAAM,IAAI,MACR,qBAAqB,iBAAiB,MAAM,YAAY,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,uCAExG;;CAIL,MAAM,MAAM,GAAG,iBAAiB,WAAW,QAAQ;AACnD,KAAI;AACF,gBAAc,KAAK,YAAY,OAAO;AACtC,aAAW,KAAK,iBAAiB;UAC1B,GAAG;AACV,MAAI;AAAE,OAAI,WAAW,IAAI,CAAE,YAAW,IAAI;UAAU;AACpD,QAAM,IAAI,MACR,mBAAmB,iBAAiB,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,6BACvD,WAAW,YAAY,GAAG,oBAAoB,gBAAgB,GAAG,GAC9F;;;;;;;;;;;;;AC9DL,SAAS,gBAAwB;AAI/B,QAAO,KAFW,QADC,cAAc,OAAO,KAAK,IAAI,CACZ,EAEd,mBAAmB;;AAe5C,SAASI,eAAmB;CAC1B,MAAM,SAAS,eAAe;CAE9B,MAAM,SAAS,gBAAgB;AAG/B,KAAI,OAAO,OAAO,eAAe,YAAY,OAAO,eAAe,KACjE,QAAO,aAAa,EAAE;CAGxB,MAAM,UAAU,OAAO;AAEvB,KAAI,SAAS,SAAS;AACpB,UAAQ,IAAIC,OAAK,0DAA0D,CAAC;AAC5E,UAAQ,IAAIC,MAAI,YAAY,KAAK,UAAU,QAAQ,OAAO,GAAG,CAAC;AAC9D,UAAQ,IAAIA,MAAI,sDAAsD,CAAC;AACvE;;AAGF,SAAQ,SAAS;EACf,SAAS;EACT,MAAM,CAAC,OAAO;EACf;AAED,KAAI;AACF,kBAAgB,OAAO;UAChB,GAAG;AACV,UAAQ,MAAM,IAAI,mCAAmC,IAAI,CAAC;AAC1D,UAAQ,WAAW;AACnB;;AAGF,SAAQ,IAAIC,KAAG,+CAA+C,CAAC;AAC/D,SAAQ,IAAID,MAAI,aAAa,SAAS,CAAC;AACvC,SAAQ,IAAIA,MAAI,GAAG,CAAC;AACpB,SAAQ,IAAIA,MAAI,uDAAuD,CAAC;AACxE,SAAQ,IAAIA,MAAI,+CAA+C,CAAC;AAChE,SAAQ,IAAIA,MAAI,kDAAkD,CAAC;AAEnE,KAAI,CAAC,WAAW,OAAO,EAAE;AACvB,UAAQ,KAAK;AACb,UAAQ,IACND,OAAK,mCAAmC,SAAS,CAClD;AACD,UAAQ,IAAIC,MAAI,6CAA6C,CAAC;;;AAQlE,SAASE,cAAkB;CACzB,MAAM,SAAS,eAAe;CAC9B,MAAM,SAAS,gBAAgB;CAE/B,MAAM,UACJ,OAAO,OAAO,eAAe,YAAY,OAAO,eAAe,OAC1D,OAAO,aACR,EAAE;CAER,MAAM,aAAa,SAAS;CAC5B,MAAM,YAAY,WAAW,OAAO;AAEpC,SAAQ,KAAK;AACb,SAAQ,IAAIC,OAAK,0BAA0B,CAAC;AAC5C,SAAQ,KAAK;AAEb,KAAI,YAAY;EACd,MAAM,QAAQ,QAAQ;AACtB,UAAQ,IAAIF,KAAG,iCAAiC,CAAC;AACjD,UAAQ,IAAID,MAAI,eAAe,KAAK,UAAU,MAAM,GAAG,CAAC;QACnD;AACL,UAAQ,IAAID,OAAK,qCAAqC,CAAC;AACvD,UAAQ,IAAIC,MAAI,2BAA2B,CAAC;;AAG9C,SAAQ,KAAK;AAEb,KAAI,UACF,SAAQ,IAAIC,KAAG,uBAAuB,SAAS,CAAC;MAC3C;AACL,UAAQ,IAAIF,OAAK,2BAA2B,SAAS,CAAC;AACtD,UAAQ,IAAIC,MAAI,yBAAyB,CAAC;;AAG5C,SAAQ,KAAK;AAEb,KAAI,cAAc,UAChB,SAAQ,IAAIA,MAAI,yDAAyD,CAAC;UACjE,cAAc,CAAC,UACxB,SAAQ,IAAID,OAAK,8CAA8C,CAAC;KAEhE,SAAQ,IAAIC,MAAI,kDAAkD,CAAC;AAGrE,SAAQ,KAAK;;AAOf,SAAgB,oBAAoB,QAAuB;AACzD,QACG,QAAQ,UAAU,CAClB,YACC,kFACD,CACA,aAAa;AACZ,gBAAY;GACZ;AAEJ,QACG,QAAQ,SAAS,CACjB,YACC,sEACD,CACA,aAAa;AACZ,eAAW;GACX;;;;;;;;;;;;AC1HN,SAAgB,oBACd,GACA,MAAM,KAAK,KAAK,EACF;CACd,MAAM,QAAsB,EAAE;CAC9B,MAAM,SAAS,EAAE,iBAAiB;AAElC,KAAI,QAAQ;AACV,QAAM,KAAK;GACT,UAAU;GACV,MAAM,iBAAiB,OAAO,QAAQ,mBAAmB,cAAc,MAAM,OAAO,MAAM;GAC3F,CAAC;AACF,QAAM,KAAK;GACT,UAAU;GACV,MAAM,GAAG,OAAO,SAAS,cAAc,OAAO;GAC/C,CAAC;;CAGJ,MAAM,QAAQ,UAAU,EAAE,WAAW,WAAW,KAAK;AACrD,KAAI,MAAO,OAAM,KAAK,MAAM;AAE5B,KAAI,OACF,OAAM,KAAK;EACT,UAAU;EACV,MAAM;EACP,CAAC;AAGJ,QAAO;;;;;;;;;;;;;;;AAgBT,SAAS,UACP,GACA,cACmB;AACnB,KAAI,CAAC,EAAG,QAAO;CAEf,MAAM,UAAU,EAAE,WAAW;CAC7B,MAAM,aAAa,EAAE,cAAc;CACnC,MAAM,SAAS,EAAE,UAAU;CAC3B,MAAM,UAAU,UAAU;AAE1B,KAAI,CAAC,gBAAgB,YAAY,KAAK,WAAW,EAAG,QAAO;CAE3D,MAAM,QAAQ,CAAC,GAAG,QAAQ,UAAU;AACpC,KAAI,aAAa,EAAG,OAAM,KAAK,GAAG,WAAW,aAAa;AAC1D,KAAI,SAAS,EAAG,OAAM,KAAK,GAAG,OAAO,SAAS;AAE9C,QAAO;EAGL,UAAU,SAAS,IAAI,UAAU,gBAAgB,UAAU,IAAI,SAAS;EACxE,MAAM,gBAAgB,MAAM,KAAK,KAAK;EACvC;;;;;AC7EH,MAAMI,SAAO,SAAS;AAEtB,MAAM,cAAc;AACpB,MAAM,oBAAoB,KAAKA,QAAM,WAAW,eAAe;AAC/D,MAAM,aAAa,KAAK,mBAAmB,GAAG,YAAY,QAAQ;AAClE,MAAM,aAAa,eAAe;;;;;;AAOlC,SAAS,mBAA2B;AAGlC,QAAO,KADW,QADC,cAAc,OAAO,KAAK,IAAI,CACZ,EACd,sBAAsB;;;;;;AAO/C,SAAS,iBAAyB;AAGhC,QAAO,KADW,QADC,cAAc,OAAO,KAAK,IAAI,CACZ,EACd,0BAA0B;;AAcnD,SAAS,cAAc,WAA2B;AAChD,QAAO;;;;;cAKK,YAAY;;;;;kBAKR,UAAU;;;;;;;;;;;cAWd,WAAW;;;cAGX,WAAW;;;;;;;;;;;;;;AAmBzB,eAAeC,cAA2B;CAExC,MAAM,SAAS,IAAI,UADJ,YAAY,CACS,WAAW;AAE/C,KAAI;EAEF,MAAM,IADS,MAAM,OAAO,QAAQ;AAGpC,UAAQ,KAAK;AACb,UAAQ,IAAIC,OAAK,sBAAsB,CAAC;AACxC,UAAQ,KAAK;AACb,UAAQ,IAAIC,KAAG,mBAAmB,CAAC;AACnC,UAAQ,IAAIC,MAAI,oBAAoB,EAAE,UAAU,GAAG,CAAC;AACpD,UAAQ,IAAIA,MAAI,oBAAoB,EAAE,gBAAgB,CAAC;EAMvD,MAAM,SAAS,oBAAoB,EAAyB;AAC5D,MAAI,OAAO,SAAS,GAAG;AACrB,WAAQ,KAAK;AACb,QAAK,MAAM,QAAQ,QAAQ;IACzB,MAAM,QAAQ,KAAK,aAAa,UAAU,MAAM,KAAK,aAAa,SAASC,SAAOD;AAClF,YAAQ,IAAI,MAAM,OAAO,KAAK,OAAO,CAAC;;AAExC,WAAQ,KAAK;;AAEf,UAAQ,IACNA,MACE,oBAAoB,EAAE,qBAAqB,gBAAgB,OAAO,eAAe,EAAE,qBAAqB,IACzG,CACF;AACD,MAAI,EAAE,iBACJ,SAAQ,IAAIA,MAAI,oBAAoB,EAAE,mBAAmB,CAAC;AAE5D,MAAI,EAAE,OAAO;GACX,MAAM,KAAK,EAAE;AACb,WAAQ,IACNA,MACE,oBAAoB,GAAG,YAAY,aAAa,GAAG,SAAS,UAAU,GAAG,UAAU,SACpF,CACF;;AAEH,UAAQ,KAAK;UACN,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,KAAK;AACb,UAAQ,IAAIC,OAAK,sBAAsB,CAAC;AACxC,UAAQ,KAAK;AACb,UAAQ,IAAIA,OAAK,yBAAyB,MAAM,CAAC;AACjD,UAAQ,IAAID,MAAI,mCAAmC,CAAC;AACpD,UAAQ,IAAIA,MAAI,gDAAgD,CAAC;AACjE,UAAQ,KAAK;AACb,UAAQ,WAAW;;;AAIvB,SAAS,aAAmB;AAE1B,KAAI;EACF,MAAM,SAAS,UAAU,SAAS,CAAC,MAAM,oBAAoB,EAAE,EAC7D,UAAU,QACX,CAAC;AAEF,MAAI,OAAO,WAAW,KAAK,CAAC,OAAO,OAAO,MAAM,EAAE;AAChD,WAAQ,IAAIC,OAAK,uCAAuC,CAAC;AAQzD,OALqB,UACnB,aACA;IAAC;IAAa;IAAM,OAAO,QAAQ,UAAU,IAAI,IAAI,GAAG;IAAc,EACtE,EAAE,UAAU,QAAQ,CACrB,CACgB,WAAW,EAC1B,SAAQ,IAAIF,KAAG,6BAA6B,CAAC;OAE7C,SAAQ,IAAIC,MAAI,uDAAuD,CAAC;AAE1E;;EAGF,MAAM,OAAO,OAAO,OACjB,MAAM,CACN,MAAM,KAAK,CACX,KAAK,MAAM,EAAE,MAAM,CAAC;AACvB,OAAK,MAAM,OAAO,KAChB,KAAI;AACF,WAAQ,KAAK,SAAS,KAAK,GAAG,EAAE,UAAU;AAC1C,WAAQ,IAAID,KAAG,uBAAuB,IAAI,GAAG,CAAC;UACxC;AACN,WAAQ,IAAIE,OAAK,wBAAwB,IAAI,GAAG,CAAC;;AAGrD,UAAQ,IAAID,MAAI,iDAAiD,CAAC;UAC3D,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,MAAM,IAAI,kBAAkB,MAAM,CAAC;AAC3C,UAAQ,WAAW;;;AAIvB,SAAS,aAAmB;CAC1B,MAAM,YAAY,kBAAkB;CACpC,MAAM,UAAU,gBAAgB;AAEhC,SAAQ,KAAK;AACb,SAAQ,IAAIF,OAAK,uBAAuB,CAAC;AACzC,SAAQ,KAAK;AAGb,KAAI,CAAC,WAAW,kBAAkB,CAChC,WAAU,mBAAmB,EAAE,WAAW,MAAM,CAAC;CAGnD,MAAM,eAAe,cAAc,UAAU;AAC7C,KAAI;AACF,gBAAc,YAAY,cAAc,OAAO;AAC/C,UAAQ,IAAIC,KAAG,0BAA0B,aAAa,CAAC;UAChD,GAAG;AACV,UAAQ,MAAM,IAAI,4BAA4B,IAAI,CAAC;AACnD,UAAQ,WAAW;AACnB;;AAIF,KAAI;AACF,YAAU,aAAa,CAAC,UAAU,WAAW,EAAE,EAAE,UAAU,QAAQ,CAAC;EACpE,MAAM,aAAa,UAAU,aAAa,CAAC,QAAQ,WAAW,EAAE,EAC9D,UAAU,QACX,CAAC;AACF,MAAI,WAAW,WAAW,EACxB,SAAQ,IAAIA,KAAG,iCAAiC,CAAC;MAEjD,SAAQ,IAAIE,OAAK,qBAAqB,WAAW,QAAQ,MAAM,IAAI,kBAAkB,CAAC;SAElF;AACN,UAAQ,IAAIA,OAAK,4CAA4C,CAAC;AAC9D,UAAQ,IAAID,MAAI,sBAAsB,aAAa,CAAC;;CAItD,MAAM,SAAS,gBAAgB;AAE/B,KAAI,OAAO,OAAO,eAAe,YAAY,OAAO,eAAe,KACjE,QAAO,aAAa,EAAE;CAGxB,MAAM,UAAU,OAAO;CAGvB,MAAM,WAAW,QAAQ;AAIzB,MAHqB,UAAU,OACG,SAAS,QAAQ,CAGjD,SAAQ,IAAID,KAAG,+CAA+C,CAAC;MAC1D;AAEL,MAAI,UAAU;AACZ,WAAQ,gBAAgB;AACxB,WAAQ,IAAIC,MAAI,oDAAoD,CAAC;;AAGvE,UAAQ,SAAS;GACf,SAAS;GACT,MAAM,CAAC,QAAQ;GAChB;AAED,MAAI;AACF,mBAAgB,OAAO;AACvB,WAAQ,IAAID,KAAG,+CAA+C,CAAC;WACxD,GAAG;AACV,WAAQ,MAAM,IAAI,qCAAqC,IAAI,CAAC;AAC5D,WAAQ,WAAW;AACnB;;;AAKJ,SAAQ,KAAK;AACb,KAAI,CAAC,WAAW,UAAU,EAAE;AAC1B,UAAQ,IAAIE,OAAK,8BAA8B,YAAY,CAAC;AAC5D,UAAQ,IAAID,MAAI,uBAAuB,CAAC;OAExC,SAAQ,IAAID,KAAG,oBAAoB,YAAY,CAAC;AAGlD,KAAI,CAAC,WAAW,QAAQ,EAAE;AACxB,UAAQ,IAAIE,OAAK,4BAA4B,UAAU,CAAC;AACxD,UAAQ,IAAID,MAAI,uBAAuB,CAAC;OAExC,SAAQ,IAAID,KAAG,kBAAkB,UAAU,CAAC;AAG9C,SAAQ,KAAK;AACb,SAAQ,IAAIC,MAAI,iEAAiE,CAAC;AAClF,SAAQ,KAAK;;AAGf,SAAS,eAAqB;AAC5B,SAAQ,KAAK;AACb,SAAQ,IAAIF,OAAK,yBAAyB,CAAC;AAC3C,SAAQ,KAAK;AAGb,KAAI,WAAW,WAAW,EAAE;AAC1B,MAAI;AACF,aAAU,aAAa,CAAC,UAAU,WAAW,EAAE,EAAE,UAAU,QAAQ,CAAC;AACpE,WAAQ,IAAIC,KAAG,4BAA4B,CAAC;UACtC;AACN,WAAQ,IAAIE,OAAK,0CAA0C,CAAC;;AAE9D,MAAI;AACF,cAAW,WAAW;AACtB,WAAQ,IAAIF,KAAG,oBAAoB,aAAa,CAAC;WAC1C,GAAG;AACV,WAAQ,IAAIE,OAAK,6BAA6B,IAAI,CAAC;;OAGrD,SAAQ,IAAID,MAAI,4BAA4B,CAAC;CAI/C,MAAM,SAAS,gBAAgB;CAC/B,MAAM,UACJ,OAAO,OAAO,eAAe,YAAY,OAAO,eAAe,OAC1D,OAAO,aACR,EAAE;CAER,MAAM,SAAS,QAAQ;AACvB,KAAI,QAAQ;AACV,UAAQ,SAAS;AACjB,SAAO,QAAQ;AACf,MAAI;AACF,mBAAgB,OAAO;AACvB,WAAQ,IAAID,KAAG,kDAAkD,CAAC;WAC3D,GAAG;AACV,WAAQ,IAAIE,OAAK,sCAAsC,IAAI,CAAC;;OAG9D,SAAQ,IAAID,MAAI,iDAAiD,CAAC;AAGpE,SAAQ,KAAK;AACb,SAAQ,IAAIA,MAAI,2DAA2D,CAAC;AAC5E,SAAQ,KAAK;;AAGf,eAAe,WAAW,kBAA0C;AAClE,SAAQ,KAAK;AACb,SAAQ,IAAIF,OAAK,uBAAuB,CAAC;AACzC,SAAQ,KAAK;AACb,SAAQ,IAAIE,MAAI,6CAA6C,CAAC;AAC9D,SAAQ,KAAK;CAGb,MAAM,EAAE,kBAAkB,MAAM,OAAO;CACvC,MAAM,EAAE,SAAS,aAAa,MAAM,aAAa,MAAM,OAAO;CAM9D,MAAM,kBAAkB,SAJN,YADC,cAAc,OAAO,KAAK,IAAI,CACR,EAIG,SAAS,UAAU,oBAAoB;CACnF,MAAM,EAAE,WAAW,UAAU,MAAM,OAAO;CAG1C,MAAM,YAAY,CAAC,OAAO,gBAAgB;AAC1C,KAAI,iBACF,WAAU,KAAK,uBAAuB,iBAAiB;CAGzD,MAAM,SAAS,MAAM,OAAO,WAAW;EAAE,OAAO;EAAW,UAAU;EAAQ,CAAC;AAE9E,KAAI,OAAO,WAAW,GAAG;AACvB,UAAQ,MAAM,IAAI,0CAA0C,CAAC;AAC7D,UAAQ,WAAW,OAAO,UAAU;;;AAIxC,SAAS,QAAQ,MAAkD;CACjE,MAAM,QAAQ,KAAK,SAAS;AAE5B,KAAI,CAAC,WAAW,WAAW,EAAE;AAC3B,UAAQ,IAAIC,OAAK,0BAA0B,WAAW,GAAG,CAAC;AAC1D,UAAQ,IAAID,MAAI,mCAAmC,CAAC;AACpD;;AAGF,KAAI,KAAK,OAEP,KAAI;AACF,WAAS,cAAc,MAAM,IAAI,WAAW,IAAI,EAAE,OAAO,WAAW,CAAC;SAC/D;KAIR,KAAI;AACF,WAAS,WAAW,MAAM,IAAI,WAAW,IAAI,EAAE,OAAO,WAAW,CAAC;UAC3D,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,MAAM,IAAI,uBAAuB,MAAM,CAAC;AAChD,UAAQ,WAAW;;;AASzB,SAAgB,uBAAuB,WAA0B;AAC/D,WACG,QAAQ,QAAQ,CAChB,YAAY,yCAAyC,CACrD,OAAO,YAAY;EAClB,MAAM,EAAE,UAAU,MAAM,OAAO;EAC/B,MAAM,EAAE,YAAY,IAAI,oBAAoB,MAAM,OAChD;AAEF,mBAAiB;AAEjB,QAAM,MADS,IAAI,CACA;GACnB;AAEJ,WACG,QAAQ,UAAU,CAClB,YAAY,oDAAoD,CAChE,OACC,6BACA,+FACD,CACA,OAAO,OAAO,SAAwC;AACrD,QAAM,WAAW,KAAK,iBAAiB;GACvC;AAEJ,WACG,QAAQ,SAAS,CACjB,YAAY,kCAAkC,CAC9C,OAAO,YAAY;AAClB,QAAMH,aAAW;GACjB;AAEJ,WACG,QAAQ,UAAU,CAClB,YAAY,+DAA+D,CAC3E,aAAa;AACZ,cAAY;GACZ;AAEJ,WACG,QAAQ,UAAU,CAClB,YACC,gFACD,CACA,aAAa;AACZ,cAAY;GACZ;AAEJ,WACG,QAAQ,YAAY,CACpB,YAAY,sDAAsD,CAClE,aAAa;AACZ,gBAAc;GACd;AAEJ,WACG,QAAQ,OAAO,CACf,YAAY,wBAAwB,WAAW,GAAG,CAClD,OAAO,mBAAmB,2BAA2B,KAAK,CAC1D,OAAO,gBAAgB,mCAAmC,CAC1D,QAAQ,SAA+C;AACtD,UAAQ,KAAK;GACb;;;;;AC9cN,MAAMK,SAAO,SAAS;AACtB,MAAMC,gBAAc,KAAKD,QAAM,QAAQ,cAAc;AACrD,MAAME,gBAAc,KAAKF,QAAM,WAAW,OAAO,cAAc;AAC/D,MAAMG,gBAAc,KAAKH,QAAM,QAAQ,UAAU;AACjD,MAAMI,qBAAmB;AACzB,MAAMC,gBAAc;AACpB,MAAMC,YAAU;AAMhB,SAAS,YAAoB;CAC3B,MAAM,sBAAM,IAAI,MAAM;AAOtB,QAAO,GANM,IAAI,aAAa,CAMf,GALJ,OAAO,IAAI,UAAU,GAAG,EAAE,CAAC,SAAS,GAAG,IAAI,CAKjC,GAJV,OAAO,IAAI,SAAS,CAAC,CAAC,SAAS,GAAG,IAAI,CAItB,GAHhB,OAAO,IAAI,UAAU,CAAC,CAAC,SAAS,GAAG,IAAI,GACvC,OAAO,IAAI,YAAY,CAAC,CAAC,SAAS,GAAG,IAAI,GACzC,OAAO,IAAI,YAAY,CAAC,CAAC,SAAS,GAAG,IAAI;;AAItD,SAAS,SAAS,OAAuB;AACvC,KAAI,QAAQ,KAAM,QAAO,GAAG,MAAM;AAClC,KAAI,QAAQ,OAAO,KAAM,QAAO,IAAI,QAAQ,MAAM,QAAQ,EAAE,CAAC;AAC7D,QAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,EAAE,CAAC;;AAG/C,SAAS,SAAS,MAAsB;AACtC,KAAI;AACF,SAAO,SAAS,SAAS,KAAK,CAAC,KAAK;SAC9B;AACN,SAAO;;;AAQX,SAAgB,uBAAuB,SAAwB;AAC7D,SACG,QAAQ,SAAS,CACjB,YAAY,oEAAoE,CAChF,OAAO,iBAAiB,2DAA2D,CACnF,OAAO,OAAO,SAAgC;EAE7C,MAAM,YAAY,KAAKH,eADZ,WAAW,CACiB;AAEvC,UAAQ,IAAII,MAAI,oBAAoB,YAAY,CAAC;AACjD,YAAU,WAAW,EAAE,WAAW,MAAM,CAAC;EAEzC,MAAM,UAA2E,EAAE;AAMnF,MAAI,WAAWN,cAAY,EAAE;GAC3B,MAAM,OAAO,KAAK,WAAW,cAAc;AAC3C,OAAI;AACF,iBAAaA,eAAa,KAAK;AAC/B,YAAQ,KAAK;KAAE,OAAO;KAAe,MAAM;KAAM,MAAM,SAAS,KAAK;KAAE,QAAQO,KAAG,KAAK;KAAE,CAAC;YACnF,GAAG;AACV,YAAQ,KAAK;KAAE,OAAO;KAAe,MAAM;KAAM,MAAM;KAAK,QAAQ,IAAI,WAAW,IAAI;KAAE,CAAC;;QAG5F,SAAQ,KAAK;GAAE,OAAO;GAAe,MAAMP;GAAa,MAAM;GAAK,QAAQQ,OAAK,sBAAsB;GAAE,CAAC;AAO3G,MAAI,WAAWP,cAAY,EAAE;GAC3B,MAAM,OAAO,KAAK,WAAW,cAAc;AAC3C,OAAI;AACF,iBAAaA,eAAa,KAAK;AAC/B,YAAQ,KAAK;KAAE,OAAO;KAAU,MAAM;KAAM,MAAM,SAAS,KAAK;KAAE,QAAQM,KAAG,KAAK;KAAE,CAAC;YAC9E,GAAG;AACV,YAAQ,KAAK;KAAE,OAAO;KAAU,MAAM;KAAM,MAAM;KAAK,QAAQ,IAAI,WAAW,IAAI;KAAE,CAAC;;QAGvF,SAAQ,KAAK;GAAE,OAAO;GAAU,MAAMN;GAAa,MAAM;GAAK,QAAQO,OAAK,sBAAsB;GAAE,CAAC;EAOtG,MAAM,eAAe,KAAKT,QAAM,QAAQ,gBAAgB;AACxD,MAAI,WAAW,aAAa,EAAE;GAC5B,MAAM,OAAO,KAAK,WAAW,gBAAgB;AAC7C,OAAI;AACF,iBAAa,cAAc,KAAK;AAChC,YAAQ,KAAK;KAAE,OAAO;KAA0B,MAAM;KAAM,MAAM,SAAS,KAAK;KAAE,QAAQQ,KAAG,KAAK;KAAE,CAAC;YAC9F,GAAG;AACV,YAAQ,KAAK;KAAE,OAAO;KAA0B,MAAM;KAAM,MAAM;KAAK,QAAQC,OAAK,YAAY,IAAI;KAAE,CAAC;;;AAQ3G,MAAI,KAAK,UAAU;GACjB,MAAM,UAAU,KAAK,WAAW,mBAAmB;AACnD,WAAQ,IAAIF,MAAI,wBAAwBH,mBAAiB,8BAA8B,CAAC;AACxF,OAAI;AAEF,aAAS,kBAAkBA,mBAAiB,gCAAgC,EAC1E,OAAO,QACR,CAAC;AAEF,aACE,eAAeA,mBAAiB,cAAcE,UAAQ,GAAGD,cAAY,MAAM,QAAQ,IACnF;KAAE,OAAO;MAAC;MAAQ;MAAQ;MAAO;KAAE,OAAO;KAA2B,CACtE;AACD,YAAQ,KAAK;KAAE,OAAO;KAAe,MAAM;KAAS,MAAM,SAAS,QAAQ;KAAE,QAAQG,KAAG,KAAK;KAAE,CAAC;YACzF,GAAG;IACV,MAAM,MAAM,aAAa,QAAQ,EAAE,QAAQ,MAAM,KAAK,CAAC,KAAK,OAAO,EAAE;AACrE,YAAQ,KAAK;KAAE,OAAO;KAAe,MAAM;KAAS,MAAM;KAAK,QAAQ,IAAI,WAAW,MAAM;KAAE,CAAC;AAC/F,YAAQ,IAAIC,OAAK,+DAA+DL,mBAAiB,IAAI,CAAC;;QAGxG,SAAQ,KAAK;GAAE,OAAO;GAAe,MAAM;GAAK,MAAM;GAAK,QAAQG,MAAI,0BAA0B;GAAE,CAAC;AAOtG,UAAQ,IAAI,KAAKG,OAAK,mBAAmB,CAAC,GAAG,UAAU,IAAI;EAE3D,MAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,KAAK,MAAM,EAAE,MAAM,OAAO,CAAC,GAAG;AACrE,OAAK,MAAM,KAAK,SAAS;GACvB,MAAM,QAAQ,EAAE,MAAM,OAAO,WAAW;AACxC,WAAQ,IAAI,KAAKA,OAAK,MAAM,CAAC,GAAG,EAAE,OAAO,IAAIH,MAAI,EAAE,KAAK,GAAG;;AAG7D,UAAQ,IAAI,OAAOA,MAAI,QAAQ,CAAC,GAAG,YAAY;AAC/C,UAAQ,IAAI,KAAKA,MAAI,cAAc,CAAC,eAAe,UAAU,IAAI;GACjE;;;;;AC5IN,MAAM,OAAO,SAAS;AACtB,MAAM,cAAc,KAAK,MAAM,QAAQ,cAAc;AACrD,MAAM,cAAc,KAAK,MAAM,WAAW,OAAO,cAAc;AAC/D,MAAM,cAAc,KAAK,MAAM,QAAQ,UAAU;AACjD,MAAM,mBAAmB;AACzB,MAAM,cAAc;AACpB,MAAM,UAAU;AAMhB,SAAS,QAAQ,UAAoC;AACnD,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,KAAK,gBAAgB;GAAE,OAAO,QAAQ;GAAO,QAAQ,QAAQ;GAAQ,CAAC;AAC5E,KAAG,SAAS,GAAG,SAAS,WAAW,WAAW;AAC5C,MAAG,OAAO;AACV,WAAQ,OAAO,MAAM,CAAC,aAAa,KAAK,IAAI;IAC5C;GACF;;AAGJ,SAAS,cAAwB;AAC/B,KAAI,CAAC,WAAW,YAAY,CAAE,QAAO,EAAE;AACvC,QAAO,YAAY,YAAY,CAC5B,QAAQ,SAAS;AAEhB,SAAO,SADM,KAAK,aAAa,KAAK,CACf,CAAC,aAAa,IAAI,4BAA4B,KAAK,KAAK;GAC7E,CACD,MAAM,CACN,SAAS;;AAGd,SAAS,iBAAiB,MAAsB;CAE9C,MAAM,IAAI,KAAK,MAAM,8CAA8C;AACnE,KAAI,CAAC,EAAG,QAAO;AACf,QAAO,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE;;AAGtC,SAAS,eAAe,WAA6B;AACnD,KAAI;AACF,SAAO,YAAY,UAAU;SACvB;AACN,SAAO,EAAE;;;AAQb,SAAgB,wBAAwB,SAAwB;AAC9D,SACG,QAAQ,wBAAwB,CAChC,YAAY,0DAA0D,CACtE,OAAO,iBAAiB,uCAAuC,CAC/D,OAAO,SAAS,6CAA6C,CAC7D,OAAO,OAAO,YAAgC,SAA8C;EAM3F,IAAI;AAEJ,MAAI,YAAY;AACd,iBAAc,WAAW,WAAW,IAAI,GACpC,WAAW,QAAQ,MAAM,KAAK,GAC9B;AAEJ,OAAI,CAAC,WAAW,YAAY,EAAE;AAC5B,YAAQ,MAAM,IAAI,+BAA+B,cAAc,CAAC;AAChE,YAAQ,WAAW;AACnB;;SAEG;GAEL,MAAM,UAAU,aAAa;AAC7B,OAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,MAAM,IAAI,uBAAuB,cAAc,CAAC;AACxD,YAAQ,IAAII,MAAI,oCAAoC,CAAC;AACrD,YAAQ,WAAW;AACnB;;AAGF,WAAQ,IAAI,KAAKC,OAAK,oBAAoB,CAAC,oBAAoB;AAC/D,WAAQ,SAAS,MAAM,MAAM;IAC3B,MAAM,MAAM,KAAK,aAAa,KAAK;IACnC,MAAM,WAAW,eAAe,IAAI,CAAC,KAAK,KAAK;IAC/C,MAAM,SAAS,MAAM,IAAIC,KAAG,YAAY,GAAG;AAC3C,YAAQ,IAAI,KAAKD,OAAK,OAAO,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,iBAAiB,KAAK,GAAG,SAAS;AACvF,YAAQ,IAAI,SAASD,MAAI,IAAI,GAAG;AAChC,YAAQ,IAAI,SAASA,MAAI,YAAY,CAAC,GAAG,WAAW;KACpD;AAEF,WAAQ,KAAK;AACb,iBAAc,KAAK,aAAa,QAAQ,GAAG;AAC3C,WAAQ,IAAIA,MAAI,wBAAwB,YAAY,IAAI,CAAC;;EAO3D,MAAM,cAAc,WAAW,KAAK,aAAa,cAAc,CAAC;EAChE,MAAM,YAAc,WAAW,KAAK,aAAa,cAAc,CAAC;EAChE,MAAM,SAAc,WAAW,KAAK,aAAa,mBAAmB,CAAC;EACrE,MAAM,SAAc,WAAW,KAAK,aAAa,gBAAgB,CAAC;AAElE,UAAQ,IAAI,GAAGC,OAAK,mBAAmB,GAAG;AAC1C,UAAQ,IAAI,0BAA0B,cAAcC,KAAG,UAAU,GAAGC,OAAK,UAAU,GAAG;AACtF,UAAQ,IAAI,0BAA0B,YAAcD,KAAG,UAAU,GAAGC,OAAK,UAAU,GAAG;AACtF,UAAQ,IAAI,0BAA0B,UAAU,KAAK,WAAWD,KAAG,UAAU,GAAG,SAASC,OAAK,sCAAsC,GAAGA,OAAK,UAAU,GAAG;AACzJ,MAAI,OACF,SAAQ,IAAI,0BAA0BD,KAAG,UAAU,CAAC,GAAGF,MAAI,WAAW,GAAG;AAO3E,UAAQ,IAAI,KAAKG,OAAK,WAAW,CAAC,6CAA6C;AAC/E,MAAI,UAAU,KAAK,SACjB,SAAQ,IAAIA,OAAK,qEAAqE,CAAC;AAIzF,MAAI,EADY,KAAK,OAAO,MAAM,QAAQ,wBAAwB,GACpD;AACZ,WAAQ,IAAIH,MAAI,qBAAqB,CAAC;AACtC;;AAGF,UAAQ,KAAK;EAEb,MAAM,UAA+C,EAAE;AAMvD,MAAI,YACF,KAAI;AACF,aAAU,KAAK,MAAM,OAAO,EAAE,EAAE,WAAW,MAAM,CAAC;AAClD,gBAAa,KAAK,aAAa,cAAc,EAAE,YAAY;AAC3D,WAAQ,KAAK;IAAE,OAAO;IAAe,QAAQE,KAAG,WAAW;IAAE,CAAC;WACvD,GAAG;AACV,WAAQ,KAAK;IAAE,OAAO;IAAe,QAAQ,IAAI,WAAW,IAAI;IAAE,CAAC;;MAGrE,SAAQ,KAAK;GAAE,OAAO;GAAe,QAAQC,OAAK,8BAA8B;GAAE,CAAC;AAOrF,MAAI,UACF,KAAI;AACF,aAAU,KAAK,MAAM,WAAW,MAAM,EAAE,EAAE,WAAW,MAAM,CAAC;AAC5D,gBAAa,KAAK,aAAa,cAAc,EAAE,YAAY;AAC3D,WAAQ,KAAK;IAAE,OAAO;IAAU,QAAQD,KAAG,WAAW;IAAE,CAAC;WAClD,GAAG;AACV,WAAQ,KAAK;IAAE,OAAO;IAAU,QAAQ,IAAI,WAAW,IAAI;IAAE,CAAC;;MAGhE,SAAQ,KAAK;GAAE,OAAO;GAAU,QAAQC,OAAK,8BAA8B;GAAE,CAAC;AAOhF,MAAI,OACF,KAAI;AACF,gBAAa,KAAK,aAAa,gBAAgB,EAAE,KAAK,MAAM,QAAQ,gBAAgB,CAAC;AACrF,WAAQ,KAAK;IAAE,OAAO;IAA0B,QAAQD,KAAG,WAAW;IAAE,CAAC;WAClE,GAAG;AACV,WAAQ,KAAK;IAAE,OAAO;IAA0B,QAAQC,OAAK,YAAY,IAAI;IAAE,CAAC;;AAQpF,MAAI,UAAU,KAAK,UAAU;AAC3B,WAAQ,IAAIH,MAAI,2DAA2D,CAAC;AAC5E,OAAI;AAEF,aAAS,kBAAkB,iBAAiB,gCAAgC,EAC1E,OAAO,QACR,CAAC;AAIF,aADmB,eAAe,iBAAiB,WAAW,QAAQ,+BAA+B,YAAY,oBAAoB,YAAY,SAAS,QAAQ,KAC7I;KAAE,OAAO;KAAQ,OAAO;KAA2B,CAAC;IAGzE,MAAM,aAAa,aAAa,KAAK,aAAa,mBAAmB,EAAE,OAAO;IAC9E,MAAM,aAAa,UACjB,UACA;KAAC;KAAQ;KAAM;KAAkB;KAAQ;KAAM;KAAS;KAAY,EACpE;KAAE,OAAO;KAAY,UAAU;KAAQ,OAAO;MAAC;MAAQ;MAAQ;MAAO;KAAE,CACzE;AAED,QAAI,WAAW,WAAW,GAAG;KAC3B,MAAM,SAAS,WAAW,QAAQ,MAAM,KAAK,CAAC,MAAM;AACpD,aAAQ,KAAK;MAAE,OAAO;MAAe,QAAQ,IAAI,WAAW,SAAS;MAAE,CAAC;UAExE,SAAQ,KAAK;KAAE,OAAO;KAAe,QAAQE,KAAG,WAAW;KAAE,CAAC;YAEzD,GAAG;IACV,MAAM,MAAM,aAAa,QAAQ,EAAE,QAAQ,MAAM,KAAK,CAAC,KAAK,OAAO,EAAE;AACrE,YAAQ,KAAK;KAAE,OAAO;KAAe,QAAQ,IAAI,WAAW,MAAM;KAAE,CAAC;AACrE,YAAQ,IAAIC,OAAK,uCAAuC,iBAAiB,IAAI,CAAC;;aAEvE,CAAC,UAAU,CAAC,KAAK,UAAU;GACpC,MAAM,SAAS,CAAC,SAAS,0BAA0B;AACnD,WAAQ,KAAK;IAAE,OAAO;IAAe,QAAQH,MAAI,YAAY,OAAO,GAAG;IAAE,CAAC;;AAO5E,UAAQ,IAAI,KAAKC,OAAK,oBAAoB,CAAC,IAAI;EAC/C,MAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,KAAK,MAAM,EAAE,MAAM,OAAO,CAAC,GAAG;AACrE,OAAK,MAAM,KAAK,QACd,SAAQ,IAAI,KAAKA,OAAK,EAAE,MAAM,OAAO,WAAW,CAAC,CAAC,GAAG,EAAE,SAAS;AAIlE,MAAI,CADc,QAAQ,MAAM,MAAM,EAAE,OAAO,SAAS,WAAa,CAAC,EACtD;AACd,WAAQ,IAAI,OAAOC,KAAG,YAAY,CAAC,4CAA4C;AAC/E,WAAQ,IAAI,KAAKF,MAAI,WAAW,CAAC,uBAAuB;SACnD;AACL,WAAQ,IAAI,OAAOG,OAAK,0CAA0C,CAAC,IAAI;AACvE,WAAQ,WAAW;AACnB;;GAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;AC1PN,SAAS,cAAc,QAA2C;AAGhE,KAAI,CAAC,OAAO,eAAgB,QAAO,QAAQ,SAAS;AACpD,QAAO,IAAI,SAAS,YAAY;AAC9B,SAAO,KAAK,eAAe,SAAS,CAAC;EAKrC,MAAM,cAAc;AAClB,OAAI,CAAC,OAAO,eAAgB,UAAS;;AAEvC,eAAa,MAAM;GACnB;;;;;;;AAQJ,eAAsB,eAAe,MAA8B;AACjE,OAAM,YAAY;AAClB,SAAQ,KAAK,KAAK;;;;;;;;;;;;;;AAepB,eAAsB,aAA4B;AAChD,OAAM,QAAQ,IAAI,CAAC,cAAc,QAAQ,OAAO,EAAE,cAAc,QAAQ,OAAO,CAAC,CAAC;;;;;;;;;AC5CnF,MAAa,IAAI;CACf,OAAO,MAAc,MAAM,KAAK,EAAE;CAClC,MAAM,MAAc,MAAM,IAAI,EAAE;CAChC,QAAQ,MAAc,MAAM,MAAM,EAAE;CACpC,SAAS,MAAc,MAAM,OAAO,EAAE;CACtC,OAAO,MAAc,MAAM,KAAK,EAAE;CAClC,MAAM,MAAc,MAAM,IAAI,EAAE;CAChC,OAAO,MAAc,MAAM,KAAK,EAAE;CAClC,KAAK,MAAc,MAAM,MAAM,OAAO,EAAE;CACxC,OAAO,MAAc,MAAM,OAAO,OAAO,EAAE;CAC3C,MAAM,MAAc,MAAM,IAAI,OAAO,EAAE;CACxC;AAED,SAAgBC,OAAK,OAAO,IAAU;AACpC,SAAQ,IAAI,KAAK;;AAGnB,SAAgB,QAAQ,OAAqB;AAC3C,SAAM;AACN,SAAQ,IAAI,MAAM,KAAK,KAAK,OAAO,MAAM,CAAC;AAC1C,SAAQ,IAAI,MAAM,IAAI,OAAO,IAAI,OAAO,MAAM,OAAO,CAAC,CAAC;;AAOzD,SAAgB,WAAW;CACzB,MAAM,KAAK,gBAAgB;EACzB,OAAO,QAAQ;EACf,QAAQ,QAAQ;EACjB,CAAC;AAEF,IAAG,GAAG,gBAAgB;AACpB,UAAM;AACN,SAAK,EAAE,IAAI,uDAAuD,CAAC;AACnE,UAAM;AAIN,EAAK,eAAe,EAAE;GACtB;AAEF,QAAO;;AAKT,eAAsB,OAAO,IAAQ,UAAmC;AACtE,QAAO,IAAI,SAAS,YAAY;AAC9B,KAAG,SAAS,WAAW,WAAW;AAChC,WAAQ,OAAO,MAAM,CAAC;IACtB;GACF;;;AAIJ,eAAsB,WACpB,IACA,SACA,aAAa,GACI;AACjB,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,MAAM,MAAM,KAAK,KAAK,IAAI,EAAE,GAAG;EACrC,MAAM,QAAQ,MAAM,aAAa,MAAM,KAAK,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG;EAC3E,MAAM,SAAS,MAAM,aAAa,MAAM,IAAI,iBAAiB,GAAG;AAChE,UAAQ,IAAI,GAAG,IAAI,GAAG,QAAQ,SAAS;AACvC,MAAI,QAAQ,GAAG,YACb,SAAQ,IAAI,MAAM,IAAI,QAAQ,QAAQ,GAAG,cAAc,CAAC;;AAG5D,SAAM;AAEN,QAAO,MAAM;EACX,MAAM,SAAS,MAAM,OACnB,IACA,MAAM,KAAK,qBAAqB,QAAQ,OAAO,cAAc,aAAa,EAAE,KAAK,CAClF;AAED,MAAI,WAAW,GAAI,QAAO;EAE1B,MAAM,IAAI,SAAS,QAAQ,GAAG;AAC9B,MAAI,CAAC,MAAM,EAAE,IAAI,KAAK,KAAK,KAAK,QAAQ,OACtC,QAAO,IAAI;AAGb,UAAQ,IAAI,EAAE,KAAK,uCAAuC,QAAQ,OAAO,GAAG,CAAC;;;;AAKjF,eAAsB,YACpB,IACA,UACA,aAAa,MACK;CAClB,MAAM,OAAO,aAAa,UAAU;CACpC,MAAM,SAAS,MAAM,OAAO,IAAI,KAAK,SAAS,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI;AAErE,KAAI,WAAW,GAAI,QAAO;AAC1B,QAAO,OAAO,aAAa,CAAC,WAAW,IAAI;;;;;;;;;AAc7C,SAAgB,gBAAyC;AACvD,QAAO,eAAeC,eAAa,4BAA4B;;AAGjE,SAAgB,eAAe,MAAqC;AAClE,iBAAgBA,eAAa,MAAM,EAAE,OAAO,6BAA6B,CAAC;;AAG5E,SAAgB,YAAY,SAAwC;CAClE,MAAM,UAAU,eAAe;CAC/B,MAAM,SAAS;EAAE,GAAG;EAAS,GAAG;EAAS;AACzC,KAAI,QAAQ,YAAY,OAAO,QAAQ,aAAa,YAAY,QAAQ,aAAa,KACnF,QAAO,WAAW;EAAE,GAAI,QAAQ;EAAqB,GAAI,QAAQ;EAAqB;AAExF,gBAAe,OAAO;;AAOxB,SAAgB,YAAqB;AACnC,KAAI;AAEF,SADe,UAAU,UAAU,CAAC,YAAY,EAAE,EAAE,OAAO,QAAQ,CAAC,CACtD,WAAW;SACnB;AACN,SAAO;;;AAIX,SAAgB,eAAuB;CACrC,MAAM,aAAa;EACjB,KAAK,QAAQ,KAAK,EAAE,SAAS;EAC7B,KAAK,SAAS,EAAE,OAAO,MAAM,OAAO,SAAS;EAC7C,KAAK,KAAK,OAAO,SAAS,OAAO,gBAAgB,cAAc,OAAO,SAAS;EAChF;AACD,MAAK,MAAM,aAAa,WACtB,KAAI,WAAW,KAAK,WAAW,qBAAqB,CAAC,CAAE,QAAO;AAEhE,QAAO,KAAK,QAAQ,KAAK,EAAE,SAAS;;AAGtC,eAAsB,uBAAuB,kBAA4C;AACvF,KAAI;EACF,MAAM,WAAW,MAAM,OAAO;EAE9B,MAAM,SAAS,KADJ,SAAS,WAAW,UACT,OAAO,EAAE,kBAAkB,CAAC;AAClD,QAAM,OAAO,SAAS;AACtB,QAAM,OAAO,KAAK;AAClB,SAAO;SACD;AACN,SAAO;;;AAQX,SAAgBC,oBAA0B;CACxC,MAAM,aAAa;EACjB,KAAK,QAAQ,KAAK,EAAE,YAAY;EAChC,KAAK,SAAS,EAAE,OAAO,MAAM,OAAO,YAAY;EAChD,KAAK,KAAK,OAAO,SAAS,OAAO,gBAAgB,cAAc,OAAO,YAAY;EACnF;AACD,MAAK,MAAM,aAAa,WACtB,KAAI,WAAW,KAAK,WAAW,wBAAwB,CAAC,CAAE,QAAO;AAEnE,QAAO,KAAK,QAAQ,KAAK,EAAE,YAAY;;AAGzC,SAAgB,cAAsB;CACpC,MAAM,aAAa;EACjB,KAAK,QAAQ,KAAK,EAAE,OAAO,QAAQ;EACnC,KAAK,SAAS,EAAE,OAAO,MAAM,OAAO,OAAO,QAAQ;EACnD,KAAK,KAAK,OAAO,SAAS,OAAO,gBAAgB,cAAc,OAAO,OAAO,QAAQ;EACtF;AACD,MAAK,MAAM,aAAa,WACtB,KAAI,WAAW,KAAK,WAAW,kBAAkB,CAAC,CAAE,QAAO;AAE7D,QAAO,KAAK,QAAQ,KAAK,EAAE,OAAO,QAAQ;;AAG5C,SAAgB,kBAA0B;CACxC,MAAM,YAAY,IAAI,IAAI,KAAK,OAAO,KAAK,IAAI,CAAC;CAChD,MAAM,aAAa,KAAK,WAAW,MAAM,MAAM,QAAQ;CAEvD,MAAM,aAAa;EACjB;EACA,KAAK,QAAQ,KAAK,EAAE,QAAQ,QAAQ;EACpC,KAAK,SAAS,EAAE,OAAO,MAAM,OAAO,QAAQ,QAAQ;EACpD,KAAK,KAAK,OAAO,SAAS,OAAO,gBAAgB,cAAc,OAAO,QAAQ,QAAQ;EACvF;AACD,MAAK,MAAM,aAAa,WACtB,KAAI,WAAW,KAAK,WAAW,gBAAgB,CAAC,CAAE,QAAO;AAE3D,QAAO;;AAGT,SAAgB,aAAqB;CACnC,MAAM,YAAY,IAAI,IAAI,KAAK,OAAO,KAAK,IAAI,CAAC;CAChD,MAAM,aAAa,KAAK,WAAW,MAAM,MAAM,KAAK;CAEpD,MAAM,aAAa;EACjB;EACA,KAAK,QAAQ,KAAK,EAAE,OAAO;EAC3B,KAAK,SAAS,EAAE,OAAO,MAAM,OAAO,OAAO;EAC3C,KAAK,KAAK,OAAO,SAAS,OAAO,gBAAgB,cAAc,OAAO,OAAO;EAC9E;AACD,MAAK,MAAM,aAAa,WACtB,KAAI,WAAW,KAAK,WAAW,SAAS,CAAC,CAAE,QAAO;AAEpD,QAAO;;AAGT,SAAgB,sBAAqC;CACnD,MAAM,aAAa;EACjB,KAAK,QAAQ,KAAK,EAAE,wBAAwB;EAC5C,KAAK,SAAS,EAAE,OAAO,MAAM,OAAO,wBAAwB;EAC5D,KAAK,KAAK,OAAO,SAAS,OAAO,gBAAgB,cAAc,OAAO,wBAAwB;EAC/F;AACD,MAAK,MAAM,aAAa,WACtB,KAAI,WAAW,UAAU,CAAE,QAAO;AAEpC,QAAO;;AAGT,SAAgB,oBAAmC;CACjD,MAAM,aAAa;EACjB,KAAK,QAAQ,KAAK,EAAE,uBAAuB;EAC3C,KAAK,SAAS,EAAE,OAAO,MAAM,OAAO,uBAAuB;EAC3D,KAAK,KAAK,OAAO,SAAS,OAAO,gBAAgB,cAAc,OAAO,uBAAuB;EAC9F;AACD,MAAK,MAAM,aAAa,WACtB,KAAI,WAAW,UAAU,CAAE,QAAO;AAEpC,QAAO;;;;;;ACxQT,SAAgB,cAAoB;AAClC,SAAM;AACN,QAAK,MAAM,KAAK,KAAK,+CAA+C,CAAC;AACrE,QAAK,MAAM,KAAK,KAAK,+CAA+C,CAAC;AACrE,QAAK,MAAM,KAAK,KAAK,+CAA+C,CAAC;AACrE,SAAM;AACN,QAAK,0EAA0E;AAC/E,QAAK,2EAA2E;AAChF,QAAK,gFAAgF;AACrF,SAAM;AACN,QAAK,EAAE,IAAI,kEAAkE,CAAC;AAC9E,QAAK,EAAE,IAAI,wCAAwC,CAAC;;;;;;ACRtD,eAAe,YAAY,IAA0B;CACnD,MAAM,YAAY,cAAc;AAGhC,KAAI,CAAC,WAFe,KAAK,WAAW,qBAAqB,CAE7B,EAAE;AAC5B,UAAQ,IAAI,EAAE,KAAK,mCAAmC,YAAY,CAAC;AACnE,UAAQ,IAAI,EAAE,IAAI,qCAAqC,CAAC;AACxD,UAAQ,IAAI,EAAE,IAAI,2BAA2B,CAAC;AAC9C,SAAO;;AAGT,SAAQ,IAAI,EAAE,IAAI,wCAAwC,UAAU,KAAK,CAAC;AAE1E,KAAI;AAMF,MALe,UAAU,UAAU;GAAC;GAAW;GAAM;GAAK,EAAE;GAC1D,KAAK;GACL,OAAO;GACR,CAAC,CAES,WAAW,GAAG;AACvB,WAAQ,IAAI,EAAE,KAAK,sDAAsD,CAAC;AAC1E,WAAQ,IAAI,EAAE,IAAI,UAAU,UAAU,0BAA0B,CAAC;AACjE,UAAO;;AAGT,UAAQ,IAAI,EAAE,GAAG,gCAAgC,CAAC;AAClD,SAAO;UACA,GAAG;AACV,UAAQ,IAAI,EAAE,KAAK,mCAAmC,IAAI,CAAC;AAC3D,SAAO;;;AAIX,eAAsB,YAAY,IAA0C;AAC1E,SAAQ,0BAA0B;CAElC,MAAM,WAAW,eAAe;AAChC,KAAI,SAAS,gBAAgB;EAC3B,MAAM,UAAU,OAAO,SAAS,eAAe;AAC/C,MAAI,YAAY,YAAY;AAC1B,OAAI;IAEF,MAAM,SADS,UAAU,UAAU;KAAC;KAAM;KAAY;KAAqB;KAAY;KAAc,EAAE,EAAE,OAAO,QAAQ,CAAC,CACnG,QAAQ,UAAU,CAAC,MAAM;AAC/C,QAAI,UAAU,OAAO,SAAS,KAAK,EAAE;AACnC,aAAQ,IAAI,EAAE,GAAG,6DAA6D,CAAC;AAC/E,YAAO;;AAET,YAAQ,IAAI,EAAE,IAAI,4DAA4D,CAAC;AAC/E,UAAM,YAAY,GAAG;AACrB,UAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAK,CAAC;AAC7C,YAAQ,IAAI,EAAE,GAAG,gCAAgC,CAAC;WAC5C;AACN,YAAQ,IAAI,EAAE,GAAG,sDAAsD,CAAC;;AAE1E,UAAO;SACF;AACL,WAAQ,IAAI,EAAE,GAAG,oBAAoB,QAAQ,aAAa,CAAC;AAC3D,UAAO;;;AAIX,SAAM;AACN,QAAK,kDAAkD;AACvD,SAAM;AAaN,KAXe,MAAM,WAAW,IAAI,CAClC;EACE,OAAO;EACP,aAAa;EACd,EACD;EACE,OAAO;EACP,aAAa;EACd,CACF,CAAC,KAEa,GAAG;AAChB,UAAM;AACN,UAAQ,IAAI,EAAE,GAAG,+CAA+C,CAAC;AACjE,SAAO,EAAE,gBAAgB,UAAU;;AAIrC,SAAM;AACN,QAAK,+EAA+E;AACpF,SAAM;AAEN,KAAI,WAAW,EAAE;AACf,UAAQ,IAAI,EAAE,GAAG,uBAAuB,CAAC;AACzC,UAAM;AAIN,MAFkB,MAAM,YAAY,IAAI,iEAAiE,KAAK,EAE/F;AACb,WAAM;AACN,SAAM,YAAY,GAAG;AACrB,WAAM;AAEN,WAAQ,IAAI,EAAE,IAAI,mDAAmD,CAAC;AACtE,SAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAK,CAAC;GAE7C,MAAM,UAAU;AAChB,WAAQ,IAAI,EAAE,IAAI,2BAA2B,QAAQ,KAAK,CAAC;AAG3D,OADY,MAAM,uBAAuB,QAAQ,CAE/C,SAAQ,IAAI,EAAE,GAAG,yBAAyB,CAAC;QACtC;AACL,YAAQ,IAAI,EAAE,KAAK,+DAA+D,CAAC;AACnF,YAAQ,IAAI,EAAE,IAAI,+EAA+E,CAAC;;AAEpG,UAAO;IAAE,gBAAgB;IAAY,UAAU,EAAE,kBAAkB,SAAS;IAAE;;OAGhF,SAAQ,IAAI,EAAE,IAAI,4DAA4D,CAAC;AAIjF,SAAM;AACN,QAAK,8CAA8C;AACnD,SAAM;AAIN,KAFmB,MAAM,YAAY,IAAI,gFAAgF,KAAK,EAE9G;EACd,MAAM,UAAU,MAAM,OAAO,IAAI,MAAM,KAAK,wBAAwB,CAAC;AACrE,MAAI,SAAS;AACX,WAAQ,IAAI,EAAE,IAAI,0BAA0B,CAAC;AAE7C,OADkB,MAAM,uBAAuB,QAAQ,CAErD,SAAQ,IAAI,EAAE,GAAG,yBAAyB,CAAC;OAE3C,SAAQ,IAAI,EAAE,KAAK,kEAAkE,CAAC;AAExF,UAAO;IAAE,gBAAgB;IAAY,UAAU,EAAE,kBAAkB,SAAS;IAAE;;;CAIlF,MAAM,OAAO,MAAM,OAAO,IAAI,MAAM,KAAK,uBAAuB,CAAC,IAAI;CACrE,MAAM,UAAU,MAAM,OAAO,IAAI,MAAM,KAAK,kBAAkB,CAAC,IAAI;CACnE,MAAM,WAAW,MAAM,OAAO,IAAI,MAAM,KAAK,qBAAqB,CAAC,IAAI;CAIvE,MAAM,UAAU,gBAHH,MAAM,OAAO,IAAI,MAAM,KAAK,iBAAiB,CAAC,IAAI,MAG1B,GAFpB,MAAM,OAAO,IAAI,MAAM,KAAK,qBAAqB,CAAC,IAAI,MAEtB,GAAG,KAAK,GAAG,QAAQ,GAAG;AACvE,SAAQ,IAAI,EAAE,IAAI,wBAAwB,UAAU,CAAC;AACrD,SAAQ,IAAI,EAAE,IAAI,0BAA0B,CAAC;AAG7C,KADkB,MAAM,uBAAuB,QAAQ,CAErD,SAAQ,IAAI,EAAE,GAAG,yBAAyB,CAAC;KAE3C,SAAQ,IAAI,EAAE,KAAK,kEAAkE,CAAC;AAGxF,QAAO;EAAE,gBAAgB;EAAY,UAAU,EAAE,kBAAkB,SAAS;EAAE;;;;;;AC/JhF,eAAsB,cAAc,IAA0C;AAC5E,SAAQ,0BAA0B;CAElC,MAAM,WAAW,eAAe;AAChC,KAAI,SAAS,gBAAgB;AAC3B,UAAQ,IAAI,EAAE,GAAG,oBAAoB,SAAS,eAAe,aAAa,CAAC;AAC3E,SAAO,EAAE,gBAAgB,SAAS,gBAAgB;;AAGpD,SAAM;AACN,QAAK,4EAA4E;AACjF,QAAK,yDAAyD;AAC9D,SAAM;CAEN,MAAM,SAAS,MAAM,WAAW,IAAI;EAClC;GACE,OAAO;GACP,aAAa;GACd;EACD;GACE,OAAO;GACP,aAAa;GACd;EACD;GACE,OAAO;GACP,aAAa;GACd;EACD;GACE,OAAO;GACP,aAAa;GACd;EACF,CAAC;CASF,MAAM,gBAPwC;EAC5C,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG;EACJ,CAE4B;AAE7B,SAAM;AACN,KAAI,eAAe;AACjB,UAAQ,IAAI,EAAE,GAAG,mBAAmB,gBAAgB,CAAC;AACrD,UAAQ,IAAI,EAAE,IAAI,qEAAqE,CAAC;QACnF;AACL,UAAQ,IAAI,EAAE,GAAG,uDAAuD,CAAC;AACzE,UAAQ,IAAI,EAAE,IAAI,kEAAkE,CAAC;;AAGvF,QAAO,EAAE,gBAAgB,iBAAiB,QAAQ;;;;;;AC9CpD,eAAsB,aAAa,IAA0B;AAC3D,SAAQ,0CAA0C;AAClD,SAAM;AACN,QAAK,sEAAsE;AAC3E,QAAK,yEAAyE;AAC9E,SAAM;CAEN,MAAM,YAAY,KAAK,SAAS,EAAE,UAAU;CAC5C,MAAM,WAAW,KAAK,WAAW,YAAY;CAC7C,MAAM,aAAa,KAAK,YAAY,iBAAiB;CACrD,MAAM,eAAeC,mBAAiB;CACtC,MAAM,eAAe,KAAK,cAAc,wBAAwB;AAEhE,KAAI,CAAC,WAAW,aAAa,EAAE;AAC7B,UAAQ,IAAI,EAAE,KAAK,yBAAyB,aAAa,CAAC;AAC1D,UAAQ,IAAI,EAAE,IAAI,6EAA6E,CAAC;AAChG,SAAO;;AAGT,KAAI,WAAW,SAAS,EAAE;EACxB,MAAM,UAAU,aAAa,UAAU,QAAQ;EAC/C,MAAM,cAAc,QAAQ,SAAS,yBAAyB;AAC9D,MAAI,YACF,SAAQ,IAAI,EAAE,IAAI,4CAA4C,CAAC;OAC1D;AACL,WAAQ,IAAI,EAAE,OAAO,0DAA0D,CAAC;AAChF,WAAQ,IAAI,EAAE,IAAI,iDAAiD,CAAC;;AAEtE,UAAM;AAGN,MAAI,CADc,MAAM,YAAY,IAAI,4DAA4D,YAAY,EAChG;AACd,WAAQ,IAAI,EAAE,IAAI,0CAA0C,CAAC;AAC7D,UAAO;;AAGT,MAAI,CAAC,aAAa;GAChB,MAAM,aAAa,WAAW;AAC9B,iBAAc,YAAY,SAAS,QAAQ;AAC3C,WAAQ,IAAI,EAAE,GAAG,mCAAmC,aAAa,CAAC;;;CAItE,IAAI,WAAW,aAAa,cAAc,QAAQ;AAClD,YAAW,SAAS,QAAQ,eAAe,SAAS,CAAC;AAErD,KAAI,CAAC,WAAW,UAAU,CACxB,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;AAE3C,eAAc,UAAU,UAAU,QAAQ;AAE1C,SAAM;AACN,SAAQ,IAAI,EAAE,GAAG,mDAAmD,CAAC;AAErE,KAAI,CAAC,WAAW,WAAW,EAAE;AAC3B,UAAM;AACN,SAAK,wEAAwE;AAC7E,SAAK,oCAAoC;AACzC,UAAM;AACN,UAAQ,IAAI,MAAM,KAAK,UAAU,aAAa,0BAA0B,aAAa,CAAC;AACtF,UAAM;AAGN,MADoB,MAAM,YAAY,IAAI,wCAAwC,KAAK,EACtE;GACf,MAAM,cAAc,KAAK,cAAc,yBAAyB;AAChE,OAAI,WAAW,YAAY,EAAE;AAC3B,QAAI,CAAC,WAAW,WAAW,CACzB,WAAU,YAAY,EAAE,WAAW,MAAM,CAAC;AAE5C,kBAAc,YAAY,aAAa,aAAa,QAAQ,EAAE,QAAQ;AACtE,YAAQ,IAAI,EAAE,GAAG,WAAW,aAAa,CAAC;AAC1C,YAAQ,IAAI,EAAE,IAAI,0EAA0E,CAAC;SAE7F,SAAQ,IAAI,EAAE,KAAK,0DAA0D,CAAC;;OAIlF,SAAQ,IAAI,EAAE,IAAI,6BAA6B,WAAW,CAAC;AAG7D,QAAO;;;;;;AClFT,eAAsB,aAAa,IAA0B;AAC3D,SAAQ,iCAAiC;AACzC,SAAM;AACN,QAAK,4EAA4E;AACjF,SAAM;CAGN,MAAM,eAAe,KADAC,mBAAiB,EACE,wBAAwB;AAEhE,KAAI,CAAC,WAAW,aAAa,EAAE;AAC7B,UAAQ,IAAI,EAAE,KAAK,+BAA+B,aAAa,CAAC;AAChE,UAAQ,IAAI,EAAE,IAAI,qCAAqC,CAAC;AACxD,SAAO;;CAGT,MAAM,WAAW,KAAK,SAAS,EAAE,WAAW,UAAU,MAAM;CAC5D,MAAM,YAAY,KAAK,UAAU,WAAW;AAE5C,KAAI,WAAW,UAAU,EAAE;EACzB,MAAM,UAAU,aAAa,WAAW,QAAQ;EAChD,MAAM,cAAc,QAAQ,SAAS,yBAAyB;AAC9D,MAAI,YACF,SAAQ,IAAI,EAAE,IAAI,2CAA2C,CAAC;OACzD;AACL,WAAQ,IAAI,EAAE,OAAO,iDAAiD,CAAC;AACvE,WAAQ,IAAI,EAAE,IAAI,iDAAiD,CAAC;;AAEtE,UAAM;AAGN,MAAI,CADc,MAAM,YAAY,IAAI,mEAAmE,YAAY,EACvG;AACd,WAAQ,IAAI,EAAE,IAAI,yCAAyC,CAAC;AAC5D,UAAO;;AAGT,MAAI,CAAC,aAAa;GAChB,MAAM,aAAa,YAAY;AAC/B,iBAAc,YAAY,SAAS,QAAQ;AAC3C,WAAQ,IAAI,EAAE,GAAG,kCAAkC,aAAa,CAAC;;YAI/D,CADY,MAAM,YAAY,IAAI,uDAAuD,KAAK,EACpF;AACZ,UAAQ,IAAI,EAAE,IAAI,qCAAqC,CAAC;AACxD,SAAO;;CAIX,IAAI,WAAW,aAAa,cAAc,QAAQ;AAClD,YAAW,SAAS,QAAQ,eAAe,SAAS,CAAC;AAErD,KAAI,CAAC,WAAW,SAAS,CACvB,WAAU,UAAU,EAAE,WAAW,MAAM,CAAC;AAE1C,eAAc,WAAW,UAAU,QAAQ;AAE3C,SAAM;AACN,SAAQ,IAAI,EAAE,GAAG,0CAA0C,CAAC;AAC5D,QAAO;;;;;;AC1DT,eAAsB,oBAAoB,IAA0B;AAClE,SAAQ,4BAA4B;AACpC,SAAM;AACN,QAAK,qEAAqE;AAC1E,QAAK,wEAAwE;AAC7E,QAAK,sEAAsE;AAC3E,SAAM;CAGN,MAAM,eAAe,KADAC,mBAAiB,EACE,gCAAgC;AAExE,KAAI,CAAC,WAAW,aAAa,EAAE;AAC7B,UAAQ,IAAI,EAAE,KAAK,2CAA2C,aAAa,CAAC;AAC5E,UAAQ,IAAI,EAAE,IAAI,6CAA6C,CAAC;AAChE,SAAO;;CAGT,MAAM,WAAW,KAAK,SAAS,EAAE,WAAW,UAAU,MAAM;CAC5D,MAAM,YAAY,KAAK,UAAU,uBAAuB;AAExD,KAAI,WAAW,UAAU,EAAE;EACzB,MAAM,UAAU,aAAa,WAAW,QAAQ;EAChD,MAAM,cAAc,QAAQ,SAAS,yBAAyB;AAC9D,MAAI,YACF,SAAQ,IAAI,EAAE,IAAI,uDAAuD,CAAC;OACrE;AACL,WAAQ,IAAI,EAAE,OAAO,6DAA6D,CAAC;AACnF,WAAQ,IAAI,EAAE,IAAI,iDAAiD,CAAC;;AAEtE,UAAM;AAGN,MAAI,CADc,MAAM,YAAY,IAAI,2EAA2E,YAAY,EAC/G;AACd,WAAQ,IAAI,EAAE,IAAI,qDAAqD,CAAC;AACxE,UAAO;;AAGT,MAAI,CAAC,aAAa;GAChB,MAAM,aAAa,YAAY;AAC/B,iBAAc,YAAY,SAAS,QAAQ;AAC3C,WAAQ,IAAI,EAAE,GAAG,8CAA8C,aAAa,CAAC;;YAI3E,CADY,MAAM,YAAY,IAAI,2EAA2E,KAAK,EACxG;AACZ,UAAQ,IAAI,EAAE,IAAI,6CAA6C,CAAC;AAChE,SAAO;;CAIX,MAAM,WAAW,aAAa,cAAc,QAAQ;AAEpD,KAAI,CAAC,WAAW,SAAS,CACvB,WAAU,UAAU,EAAE,WAAW,MAAM,CAAC;AAE1C,eAAc,WAAW,UAAU,QAAQ;AAE3C,SAAM;AACN,SAAQ,IAAI,EAAE,GAAG,sDAAsD,CAAC;AACxE,QAAO;;;;;;;;;;;;;;;;;;ACtCT,eAAsB,eAAe,IAA0B;AAC7D,SAAQ,0BAA0B;AAClC,SAAM;AACN,QAAK,0EAA0E;AAC/E,QAAK,+EAA+E;AACpF,SAAM;CAEN,MAAM,aAAa,KAAK,YAAY,EAAE,SAAS;AAE/C,KAAI,CAAC,WAAW,WAAW,EAAE;AAC3B,UAAQ,IAAI,EAAE,KAAK,sDAAsD,CAAC;AAC1E,SAAO;;CAGT,MAAM,aAAa,YAAY,WAAW,CAAC,QAAQ,SAAS;AAE1D,SAAO,WAAW,KADR,KAAK,YAAY,KAAK,EACN,WAAW,CAAC;GACtC;AAEF,KAAI,WAAW,WAAW,GAAG;AAC3B,UAAQ,IAAI,EAAE,KAAK,wCAAwC,CAAC;AAC5D,SAAO;;AAGT,SAAQ,IAAI,EAAE,IAAI,WAAW,WAAW,OAAO,0BAA0B,CAAC;AAC1E,SAAM;AAON,KAAI,CALY,MAAM,YACpB,IACA,WAAW,WAAW,OAAO,yCAC7B,KACD,EACa;AACZ,UAAQ,IAAI,EAAE,IAAI,sCAAsC,CAAC;AACzD,SAAO;;CAGT,MAAM,aAAa,KAAK,SAAS,EAAE,WAAW,SAAS;AACvD,WAAU,YAAY,EAAE,WAAW,MAAM,CAAC;CAG1C,MAAM,cAAc,KAAK,YAAY,OAAO;AAC5C,KAAI,WAAW,YAAY,CACzB,MAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,YAAY,KAAK,aAAa,KAAK;AACzC,MAAI,WAAW,UAAU,IAAI,UAAU,UAAU,CAAC,gBAAgB,CAChE,YAAW,UAAU;;CAK3B,MAAM,cAAc,UAAU,KAAK;CACnC,IAAI,YAAY;CAChB,IAAI,UAAU;CACd,IAAI,UAAU;AAEd,MAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,SAAS,QAAQ,KAAK,YAAY,KAAK,CAAC;EAC9C,MAAM,SAAS,KAAK,YAAY,KAAK;AAGrC,MAAI,WAAW,OAAO,IAAI,CAAC,UAAU,OAAO,CAAC,gBAAgB,EAAE;AAC7D,WAAQ,IAAI,EAAE,IAAI,UAAU,KAAK,uCAAuC,CAAC;AACzE;AACA;;AAIF,MAAI,WAAW,OAAO,IAAI,UAAU,OAAO,CAAC,gBAAgB,EAAE;AAE5D,OAAI,QADkB,aAAa,OAAO,CAChB,KAAK,QAAQ;AAErC;AACA;;AAGF,cAAW,OAAO;AAClB;;AAGF,MAAI,YACF,aAAY,QAAQ,OAAO;OACtB;AAEL,aAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;AACtC,gBAAa,KAAK,QAAQ,WAAW,EAAE,KAAK,QAAQ,WAAW,CAAC;;AAElE;;AAGF,SAAM;CACN,MAAM,SAAS,cAAc,cAAc;AAC3C,KAAI,UAAU,EACZ,SAAQ,IAAI,EAAE,GAAG,GAAG,UAAU,SAAS,OAAO,IAAI,QAAQ,YAAY,QAAQ,yBAAyB,CAAC;MACnG;AACL,UAAQ,IAAI,EAAE,GAAG,GAAG,UAAU,eAAe,OAAO,wBAAwB,CAAC;AAC7E,MAAI,UAAU,EACZ,SAAQ,IAAI,EAAE,IAAI,KAAK,QAAQ,qDAAqD,CAAC;;AAGzF,QAAO;;;;;;ACtHT,MAAMC,gBAAc,UAAU,KAAK;AAEnC,eAAsB,UAAU,IAA0B;AACxD,SAAQ,0BAA0B;AAClC,SAAM;AACN,QAAK,yEAAyE;AAC9E,QAAK,2DAA2D;AAChE,KAAIA,cACF,QAAK,iEAAiE;KAEtE,QAAK,iEAAiE;AAExE,SAAM;AAGN,KAAI,CADY,MAAM,YAAY,IAAI,wEAAwE,KAAK,EACrG;AACZ,UAAQ,IAAI,EAAE,IAAI,gCAAgC,CAAC;AACnD,SAAO;;CAGT,MAAM,WAAW,aAAa;CAC9B,MAAM,gBAAgB,qBAAqB;CAC3C,MAAM,cAAc,mBAAmB;CAEvC,MAAM,YAAY,KAAK,SAAS,EAAE,UAAU;CAC5C,MAAM,cAAc,KAAK,WAAW,QAAQ;AAE5C,KAAI,CAAC,WAAW,YAAY,CAC1B,WAAU,aAAa,EAAE,WAAW,MAAM,CAAC;CAG7C,IAAI,eAAe;CAEnB,SAAS,YAAY,KAAa,MAAc,OAAqB;AACnE,MAAI,CAAC,WAAW,IAAI,EAAE;AACpB,WAAQ,IAAI,EAAE,KAAK,uBAAuB,MAAM,CAAC;AACjD;;EAGF,MAAM,SAAS,QAAQ,IAAI;AAG3B,MAAI,WAAW,KAAK,IAAK,UAAU,MAAM,EAAE,gBAAgB,OAAO,CAAC,EAAE,kBAAkB,CACrF,KAAI;GACF,MAAM,OAAO,UAAU,KAAK;AAC5B,OAAI,KAAK,gBAAgB,EAAE;AACzB,QAAI,QAAQ,aAAa,KAAK,CAAC,KAAK,QAAQ;AAC1C,aAAQ,IAAI,EAAE,IAAI,cAAc,QAAQ,CAAC;AACzC;;AAGF,eAAW,KAAK;cACP,KAAK,QAAQ,CAEtB,YAAW,KAAK;OAEhB;UAEI;AAKV,MAAIA,cACF,aAAY,QAAQ,KAAK;OACpB;AACL,gBAAa,KAAK,KAAK;AACvB,aAAU,MAAM,IAAM;;EAExB,MAAM,OAAOA,gBAAc,WAAW;AACtC,UAAQ,IAAI,EAAE,GAAG,GAAG,KAAK,IAAI,QAAQ,CAAC;AACtC,iBAAe;;AAGjB,SAAM;AACN,aAAY,KAAK,UAAU,iBAAiB,EAAE,KAAK,aAAa,qBAAqB,EAAE,qBAAqB;AAC5G,aAAY,KAAK,UAAU,kBAAkB,EAAE,KAAK,aAAa,sBAAsB,EAAE,sBAAsB;AAC/G,aAAY,KAAK,UAAU,sBAAsB,EAAE,KAAK,aAAa,0BAA0B,EAAE,0BAA0B;AAE3H,KAAI,cACF,aAAY,eAAe,KAAK,WAAW,wBAAwB,EAAE,wBAAwB;KAE7F,SAAQ,IAAI,EAAE,KAAK,2DAA2D,CAAC;AAGjF,KAAI,YACF,aAAY,aAAa,KAAK,WAAW,uBAAuB,EAAE,uBAAuB;KAEzF,SAAQ,IAAI,EAAE,KAAK,yDAAyD,CAAC;AAG/E,QAAO;;;;;;AC3FT,MAAM,cAAc,UAAU,KAAK;AAEnC,eAAsB,YAAY,IAA0B;AAC1D,SAAQ,yCAAyC;AACjD,SAAM;AACN,QAAK,4EAA4E;AACjF,QAAK,0EAA0E;AAC/E,KAAI,YACF,QAAK,iEAAiE;KAEtE,QAAK,iEAAiE;AAExE,SAAM;AAGN,KAAI,CADY,MAAM,YAAY,IAAI,qDAAqD,KAAK,EAClF;AACZ,UAAQ,IAAI,EAAE,IAAI,4CAA4C,CAAC;AAC/D,SAAO;;CAGT,MAAM,eAAe,iBAAiB;AAEtC,KAAI,CAAC,WAAW,aAAa,EAAE;AAC7B,UAAQ,IAAI,EAAE,KAAK,yCAAyC,eAAe,CAAC;AAC5E,UAAQ,IAAI,EAAE,IAAI,2CAA2C,CAAC;AAC9D,SAAO;;CAIT,MAAM,cAAc,KADF,KAAK,SAAS,EAAE,UAAU,EACR,QAAQ;AAE5C,KAAI,CAAC,WAAW,YAAY,CAC1B,WAAU,aAAa,EAAE,WAAW,MAAM,CAAC;CAG7C,IAAI;AACJ,KAAI;AACF,aAAW,YAAY,aAAa,CAAC,QAAQ,MAAM,EAAE,SAAS,OAAO,CAAC;UAC/D,GAAG;AACV,UAAQ,IAAI,EAAE,KAAK,iCAAiC,IAAI,CAAC;AACzD,SAAO;;AAGT,KAAI,SAAS,WAAW,GAAG;AACzB,UAAQ,IAAI,EAAE,KAAK,mEAAmE,CAAC;AACvF,SAAO;;AAGT,SAAM;CACN,IAAI,cAAc;CAClB,IAAI,eAAe;CACnB,IAAI,eAAe;AAEnB,MAAK,MAAM,YAAY,UAAU;EAC/B,MAAM,MAAM,QAAQ,KAAK,cAAc,SAAS,CAAC;EACjD,MAAM,OAAO,KAAK,aAAa,SAAS;EAGxC,IAAI,YAAY;AAChB,MAAI,WAAW,KAAK,IAAI,UAAU,MAAM,EAAE,gBAAgB,OAAO,CAAC,EAAE,kBAAkB,CACpF,KAAI;GACF,MAAM,OAAO,UAAU,KAAK;AAC5B,OAAI,KAAK,gBAAgB,CACvB,KAAI,QAAQ,aAAa,KAAK,CAAC,KAAK,KAAK;AACvC,YAAQ,IAAI,EAAE,IAAI,cAAc,WAAW,CAAC;AAC5C;AACA,gBAAY;SAEZ,YAAW,KAAK;YAET,KAAK,QAAQ,CACtB,YAAW,KAAK;UAEZ;AAKV,MAAI,WAAW;AACb,OAAI,YACF,aAAY,KAAK,KAAK;QACjB;AACL,iBAAa,KAAK,KAAK;AACvB,cAAU,MAAM,IAAM;;GAExB,MAAM,OAAO,cAAc,WAAW;AACtC,WAAQ,IAAI,EAAE,GAAG,GAAG,KAAK,IAAI,WAAW,CAAC;AACzC;;EAIF,MAAM,cAAc,KAAK,aAAa,SAAS,QAAQ,UAAU,MAAM,CAAC;AACxE,MAAI,WAAW,YAAY,EAAE;AAC3B,cAAW,YAAY;AACvB,WAAQ,IAAI,EAAE,GAAG,qBAAqB,SAAS,QAAQ,UAAU,MAAM,GAAG,CAAC;AAC3E;;;AAIJ,SAAM;AACN,KAAI,cAAc,KAAK,eAAe,GAAG;EACvC,MAAM,QAAQ,EAAE;EAChB,MAAM,OAAO,cAAc,WAAW;AACtC,MAAI,cAAc,EAAG,OAAM,KAAK,GAAG,YAAY,WAAW,OAAO;AACjE,MAAI,eAAe,EAAG,OAAM,KAAK,GAAG,aAAa,UAAU;AAC3D,MAAI,eAAe,EAAG,OAAM,KAAK,GAAG,aAAa,+BAA+B;AAChF,UAAQ,IAAI,EAAE,GAAG,MAAM,KAAK,KAAK,GAAG,IAAI,CAAC;OAEzC,SAAQ,IAAI,EAAE,IAAI,SAAS,aAAa,2BAA2B,CAAC;AAGtE,QAAO,cAAc,KAAK,eAAe;;;;;ACrG3C,MAAM,gBAAgB,KADH,KAAK,SAAS,EAAE,UAAU,EACN,gBAAgB;;;;;;;AAgEvD,SAAgB,mBAA4C;AAC1D,QAAO,eAAe,eAAe,0BAA0B;;AAGjE,SAAgB,kBAAkB,MAAqC;AACrE,iBAAgB,eAAe,MAAM,EAAE,OAAO,2BAA2B,CAAC;;;;;AAU5E,SAAS,SACP,UACA,UACA,QACS;CACT,IAAI,UAAU;CAEd,MAAM,WACJ,OAAO,SAAS,WAAW,YAAY,SAAS,WAAW,OACvD,SAAS,SACT,EAAE;AAGR,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,CACjD,KAAI,OAAO,UAAU,eAAe,KAAK,UAAU,IAAI,CACrD,QAAO,KAAK,MAAM,IAAI,kBAAkB,IAAI,cAAc,CAAC;MACtD;AACL,WAAS,OAAO;AAChB,SAAO,KAAK,MAAM,MAAM,gBAAgB,MAAM,CAAC;AAC/C,YAAU;;AAId,UAAS,SAAS;AAClB,QAAO;;;;;;;AAQT,SAAS,YAAY,KAAqB;AAExC,SADa,IAAI,MAAM,IAAI,CAAC,KAAK,IAAI,KACzB,QAAQ,qBAAqB,GAAG;;;;;;;;AAS9C,SAAS,4BAA4B,OAAgC;CACnE,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,MAAM,QAAQ,MACjB,MAAK,MAAM,SAAS,KAAK,OAAO;AAC9B,OAAK,IAAI,MAAM,QAAQ;EAEvB,MAAM,OAAO,MAAM,QAAQ,MAAM,IAAI,CAAC,KAAK;AAC3C,MAAI,KAAM,MAAK,IAAI,KAAK;AAExB,OAAK,IAAI,YAAY,MAAM,QAAQ,CAAC;;AAGxC,QAAO;;;;;;;AAQT,SAAS,iBACP,eACA,cACA,iBACA,iBACS;AACT,MAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;EAC7C,MAAM,OAAO,cAAc;AAC3B,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,IAErC,KADqB,YAAY,KAAK,MAAM,GAAG,QAAQ,KAClC,gBAAgB,KAAK,MAAM,GAAG,YAAY,iBAAiB;AAE9E,QAAK,MAAM,GAAG,UAAU;AAExB,OAAI,oBAAoB,OACtB,MAAK,UAAU;AAEjB,UAAO;;;AAIb,QAAO;;;;;;;AAQT,SAAS,WACP,UACA,UACA,QACS;CACT,IAAI,UAAU;CAEd,MAAM,eACJ,OAAO,SAAS,aAAa,YAAY,SAAS,aAAa,OAC3D,SAAS,WACT,EAAE;AAGR,MAAK,MAAM,SAAS,UAAU;EAC5B,MAAM,EAAE,UAAU,SAAS,YAAY;EAEvC,MAAM,gBAA4B,MAAM,QAAQ,aAAa,UAAU,GAClE,aAAa,YACd,EAAE;EAEN,MAAM,WAAW,QAAQ,MAAM,IAAI,CAAC,KAAK,IAAI;EAC7C,MAAM,OAAO,YAAY,QAAQ;EAGjC,MAAM,eAAe,4BAA4B,cAAc;AAC/D,MAAI,aAAa,IAAI,QAAQ,IAAI,aAAa,IAAI,SAAS,EAAE;AAC3D,UAAO,KAAK,MAAM,IAAI,mBAAmB,SAAS,KAAK,SAAS,qBAAqB,CAAC;AACtF;;AAIF,MAAI,aAAa,IAAI,KAAK,EACxB;OAAI,iBAAiB,eAAe,MAAM,SAAS,QAAQ,EAAE;AAC3D,iBAAa,YAAY;AACzB,WAAO,KAAK,MAAM,OAAO,oBAAoB,SAAS,KAAK,SAAS,6BAA6B,CAAC;AAClG,cAAU;AACV;;;EAKJ,MAAM,UAAoB,EACxB,OAAO,CAAC;GAAE,MAAM;GAAW;GAAS,CAAC,EACtC;AACD,MAAI,YAAY,OACd,SAAQ,UAAU;AAGpB,gBAAc,KAAK,QAAQ;AAC3B,eAAa,YAAY;AAEzB,SAAO,KAAK,MAAM,MAAM,iBAAiB,SAAS,KAAK,WAAW,CAAC;AACnE,YAAU;;AAGZ,UAAS,WAAW;AACpB,QAAO;;;;;AAMT,SAAS,gBACP,UACA,UACA,QACS;AACT,KAAI,OAAO,UAAU,eAAe,KAAK,UAAU,aAAa,EAAE;AAChE,SAAO,KAAK,MAAM,IAAI,2CAA2C,CAAC;AAClE,SAAO;;AAGT,UAAS,gBAAgB,EAAE,GAAG,UAAU;AACxC,QAAO,KAAK,MAAM,MAAM,qBAAqB,CAAC;AAC9C,QAAO;;;;;AAMT,SAAS,iBACP,UACA,UACA,QACS;CACT,IAAI,UAAU;CAEd,MAAM,QACJ,OAAO,SAAS,mBAAmB,YAAY,SAAS,mBAAmB,OACvE,SAAS,iBACT,EAAE;AAGR,MAAK,MAAM,QAAQ,CAAC,SAAS,OAAO,EAAW;EAC7C,MAAM,UAAU,SAAS;AACzB,MAAI,CAAC,WAAW,QAAQ,WAAW,EAAG;EAEtC,MAAM,WAAqB,MAAM,QAAQ,MAAM,MAAM,GAAG,MAAM,QAAQ,EAAE;EACxE,MAAM,cAAc,IAAI,IAAI,SAAS;AAErC,OAAK,MAAM,SAAS,QAClB,KAAI,YAAY,IAAI,MAAM,CACxB,QAAO,KAAK,MAAM,IAAI,0BAA0B,KAAK,IAAI,MAAM,mBAAmB,CAAC;OAC9E;AACL,YAAS,KAAK,MAAM;AACpB,eAAY,IAAI,MAAM;AACtB,UAAO,KAAK,MAAM,MAAM,uBAAuB,KAAK,IAAI,QAAQ,CAAC;AACjE,aAAU;;AAId,QAAM,QAAQ;;AAGhB,UAAS,iBAAiB;AAC1B,QAAO;;;;;AAMT,SAAS,WACP,UACA,UACA,QACS;CACT,IAAI,UAAU;AAEd,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,CACjD,KAAI,OAAO,UAAU,eAAe,KAAK,UAAU,IAAI,CACrD,QAAO,KAAK,MAAM,IAAI,cAAc,IAAI,cAAc,CAAC;MAClD;AACL,WAAS,OAAO;AAChB,SAAO,KAAK,MAAM,MAAM,iBAAiB,MAAM,CAAC;AAChD,YAAU;;AAId,QAAO;;;;;;;;AAaT,SAAgB,cAAc,MAAyC;CACrE,MAAM,WAAW,kBAAkB;CACnC,MAAM,SAAmB,EAAE;CAC3B,IAAI,UAAU;AAEd,KAAI,KAAK,QAAQ,UAAa,OAAO,KAAK,KAAK,IAAI,CAAC,SAAS,GAC3D;MAAI,SAAS,UAAU,KAAK,KAAK,OAAO,CAAE,WAAU;;AAGtD,KAAI,KAAK,UAAU,UAAa,KAAK,MAAM,SAAS,GAClD;MAAI,WAAW,UAAU,KAAK,OAAO,OAAO,CAAE,WAAU;;AAG1D,KAAI,KAAK,eAAe,QACtB;MAAI,gBAAgB,UAAU,KAAK,YAAY,OAAO,CAAE,WAAU;;AAGpE,KAAI,KAAK,gBAAgB,QACvB;MAAI,iBAAiB,UAAU,KAAK,aAAa,OAAO,CAAE,WAAU;;AAGtE,KAAI,KAAK,UAAU,UAAa,OAAO,KAAK,KAAK,MAAM,CAAC,SAAS,GAC/D;MAAI,WAAW,UAAU,KAAK,OAAO,OAAO,CAAE,WAAU;;AAG1D,KAAI,QACF,mBAAkB,SAAS;AAG7B,QAAO;EAAE;EAAS;EAAQ;;;;;;AC3W5B,eAAsB,WAAW,IAAyB;AACxD,SAAQ,0BAA0B;AAClC,SAAM;AACN,QAAK,yEAAyE;AAC9E,QAAK,6CAA6C;AAClD,SAAM;CAGN,MAAM,SADS,MAAM,OAAO,IAAI,MAAM,KAAK,2BAA2B,CAAC,IAC9C;AACzB,SAAM;AACN,SAAQ,IAAI,EAAE,GAAG,0BAA0B,SAAS,CAAC;AACrD,QAAO;;AAGT,eAAsB,aAAa,IAAQ,QAAkC;AAC3E,SAAQ,yBAAyB;AACjC,SAAM;AACN,QAAK,0EAA0E;AAC/E,QAAK,uEAAuE;AAC5E,SAAM;AAGN,KAAI,CADU,MAAM,YAAY,IAAI,yEAAyE,KAAK,EACtG;AACV,UAAQ,IAAI,EAAE,IAAI,6BAA6B,CAAC;AAChD,SAAO;;CAKT,MAAM,SAAS,cAAc;EAC3B,KAAK;GACH,SAJW,KAAK,SAAS,EAAE,UAAU;GAKrC,iCAAiC;GACjC,+BAA+B;GAC/B,IAAI;GACL;EACD,OAAO;GACL;IAAE,UAAU;IAAgB,SAAS;IAAyC;GAC9E;IAAE,UAAU;IAAgB,SAAS;IAA0C;GAC/E;IAAE,UAAU;IAAgB,SAAS;IAA6C;GAClF;IAAE,UAAU;IAAgB,SAAS;IAA2C;GAChF;IAAE,UAAU;IAAgB,SAAS;IAAqE;GAC1G;IAAE,UAAU;IAAgB,SAAS;IAAW,SAAS;IAA4C;GACrG;IAAE,UAAU;IAAoB,SAAS;IAA8C;GACvF;IAAE,UAAU;IAAoB,SAAS;IAA0C;GACnF;IAAE,UAAU;IAAoB,SAAS;IAAyE;GAIlH;IAAE,UAAU;IAAoB,SAAS;IAA4C;GACrF;IAAE,UAAU;IAAc,SAAS;IAAQ,SAAS;IAA2C;GAC/F;IAAE,UAAU;IAAc,SAAS;IAAK,SAAS;IAAmE;GACpH;IAAE,UAAU;IAAe,SAAS;IAAa,SAAS;IAAwC;GAClG;IAAE,UAAU;IAAe,SAAS;IAAK,SAAS;IAAoE;GACtH;IAAE,UAAU;IAAe,SAAS;IAAK,SAAS;IAA4C;GAC9F;IAAE,UAAU;IAAe,SAAS;IAAK,SAAS;IAA6C;GAC/F;IAAE,UAAU;IAAe,SAAS;IAAK,SAAS;IAA4C;GAC9F;IAAE,UAAU;IAAQ,SAAS;IAAkC;GAC/D;IAAE,UAAU;IAAQ,SAAS;IAA6D;GAC1F;IAAE,UAAU;IAAQ,SAAS;IAAwC;GACrE;IAAE,UAAU;IAAgB,SAAS;IAA2C;GAChF;IAAE,UAAU;IAAgB,SAAS;IAAqE;GAC1G;IAAE,UAAU;IAAc,SAAS;IAAgD;GACnF;IAAE,UAAU;IAAc,SAAS;IAAmE;GACtG;IAAE,UAAU;IAAc,SAAS;IAAiD;GACpF;IAAE,UAAU;IAAc,SAAS;IAAmE;GACtG;IAAE,UAAU;IAAc,SAAS;IAAI,SAAS;IAAuC;GACxF;EACD,YAAY;GACV,MAAM;GACN,SAAS;GACV;EACD,aAAa;GACX,OAAO;IAAC;IAAQ;IAAQ;IAAS;IAAQ;IAAQ;IAAQ;IAAY;IAAa;IAAgB;IAAa;IAAgB;IAAW;GAC1I,MAAM;IAAC;IAAkB;IAAmB;IAAkB;IAAsB;IAAuB;IAAuB;GACnI;EACD,OAAO,EACL,4BAA4B,MAC7B;EACF,CAAC;AAEF,SAAM;AACN,MAAK,MAAM,KAAK,OAAO,OACrB,SAAQ,IAAI,EAAE;AAGhB,KAAI,CAAC,OAAO,QACV,SAAQ,IAAI,EAAE,IAAI,kDAAkD,CAAC;AAGvE,QAAO,OAAO;;;;;;AC1FhB,eAAsB,WAAW,IAA0B;AACzD,SAAQ,yBAAyB;AACjC,SAAM;AACN,QAAK,4EAA4E;AACjF,SAAM;AAKN,KAFe,WADG,KAAK,SAAS,EAAE,WAAW,gBAAgB,2BAA2B,CACpD,EAExB;AACV,UAAQ,IAAI,EAAE,IAAI,wCAAwC,CAAC;AAC3D,UAAM;AAGN,MAAI,CADc,MAAM,YAAY,IAAI,2CAA2C,MAAM,EACzE;AACd,WAAQ,IAAI,EAAE,IAAI,0CAA0C,CAAC;AAC7D,UAAO;;YAIL,CADY,MAAM,YAAY,IAAI,yDAAyD,KAAK,EACtF;AACZ,UAAQ,IAAI,EAAE,IAAI,8DAA8D,CAAC;AACjF,SAAO;;AAIX,SAAM;AAGN,KAFe,UAAU,OAAO,CAAC,UAAU,UAAU,EAAE,EAAE,OAAO,WAAW,CAAC,CAEjE,WAAW,GAAG;AACvB,UAAQ,IAAI,EAAE,KAAK,4DAA4D,CAAC;AAChF,SAAO;;AAGT,SAAQ,IAAI,EAAE,GAAG,0CAA0C,CAAC;AAC5D,QAAO;;;;;;ACnCT,eAAsB,QAAQ,IAA0B;AACtD,SAAQ,4BAA4B;AACpC,SAAM;AACN,QAAK,6EAA6E;AAClF,SAAM;CAEN,MAAM,iBAAiB,KAAK,SAAS,EAAE,eAAe;AACtD,KAAI,WAAW,eAAe,CAC5B,KAAI;EACF,MAAM,MAAM,aAAa,gBAAgB,QAAQ;EAEjD,MAAM,aADS,KAAK,MAAM,IAAI,CACJ;AAC1B,MAAI,cAAc,OAAO,UAAU,eAAe,KAAK,YAAY,MAAM,EAAE;AACzE,WAAQ,IAAI,EAAE,GAAG,uDAAuD,CAAC;AACzE,WAAQ,IAAI,EAAE,IAAI,+BAA+B,CAAC;AAClD,UAAO;;SAEH;AAMV,KAAI,CADa,MAAM,YAAY,IAAI,kDAAkD,KAAK,EAC/E;AACb,UAAQ,IAAI,EAAE,IAAI,6DAA6D,CAAC;AAChF,SAAO;;AAGT,SAAM;AAGN,KAFe,UAAU,OAAO,CAAC,OAAO,UAAU,EAAE,EAAE,OAAO,WAAW,CAAC,CAE9D,WAAW,GAAG;AACvB,UAAQ,IAAI,EAAE,KAAK,2DAA2D,CAAC;AAC/E,SAAO;;AAGT,SAAQ,IAAI,EAAE,GAAG,+CAA+C,CAAC;AACjE,QAAO;;;;;;ACrCT,eAAsB,gBAAgB,IAAuB;AAC3D,SAAQ,gCAAgC;AACxC,SAAM;AACN,QAAK,6EAA6E;AAClF,QAAK,4EAA4E;AACjF,QAAK,0DAA0D;AAC/D,SAAM;CAEN,MAAM,WAAW;EACf,KAAK,SAAS,EAAE,WAAW;EAC3B,KAAK,SAAS,EAAE,YAAY;EAC5B,KAAK,SAAS,EAAE,MAAM;EACvB,CAAC,OAAO,WAAW;AAEpB,KAAI,SAAS,SAAS,GAAG;AACvB,SAAK,4CAA4C;AACjD,OAAK,MAAM,KAAK,SACd,SAAQ,IAAI,MAAM,IAAI,OAAO,IAAI,CAAC;AAEpC,UAAM;;CAGR,MAAM,UAAU,MAAM,YAAY,IAAI,gEAAgE,MAAM;AAE5G,KAAI,SAAS;AACX,UAAM;AACN,UAAQ,IAAI,MAAM,IAAI,kDAAkD,CAAC;QACpE;AACL,UAAQ,IAAI,MAAM,IAAI,kDAAkD,CAAC;AACzE,UAAQ,IAAI,MAAM,IAAI,8CAA8C,CAAC;;AAGvE,CAAC,gBAA2C,WAAW;;;;;;ACjCzD,eAAsB,iBAAiB,IAAuB;AAC5D,SAAQ,yBAAyB;AACjC,SAAM;AACN,QAAK,yEAAyE;AAC9E,QAAK,yEAAyE;AAC9E,SAAM;AAIN,KAFkB,gBAA2C,SAK3D,KAFoB,MAAM,YAAY,IAAI,2DAA2D,KAAK,EAEzF;AACf,UAAM;AACN,UAAQ,IAAI,EAAE,IAAI,uBAAuB,CAAC;AAE1C,MAAI;AAEF,OADe,UAAU,OAAO;IAAC;IAAU;IAAS;IAAe,EAAE;IAAE,OAAO;IAAQ,SAAS;IAAO,CAAC,CAC5F,WAAW,EACpB,SAAQ,IAAI,EAAE,GAAG,gCAAgC,CAAC;OAElD,SAAQ,IAAI,EAAE,KAAK,yDAAyD,CAAC;UAEzE;AACN,WAAQ,IAAI,EAAE,KAAK,yDAAyD,CAAC;;AAG/E,UAAM;AACN,UAAQ,IAAI,EAAE,IAAI,gDAAgD,CAAC;AAEnE,MAAI;AAEF,OADe,UAAU,OAAO,CAAC,YAAY,OAAO,EAAE;IAAE,OAAO;IAAW,SAAS;IAAO,CAAC,CAChF,WAAW,EACpB,SAAQ,IAAI,EAAE,KAAK,sEAAsE,CAAC;UAEtF;AACN,WAAQ,IAAI,EAAE,KAAK,+DAA+D,CAAC;;QAEhF;AACL,UAAQ,IAAI,MAAM,IAAI,6CAA6C,CAAC;AACpE,UAAQ,IAAI,MAAM,IAAI,2CAA2C,CAAC;;MAE/D;AACL,UAAQ,IAAI,MAAM,IAAI,mDAAmD,CAAC;AAC1E,UAAQ,IAAI,MAAM,IAAI,iDAAiD,CAAC;AACxE,UAAQ,IAAI,MAAM,IAAI,0CAA0C,CAAC;;;;;;;AC9CrE,SAAgB,YACd,eACA,mBACA,mBACA,0BACA,qBACA,gBACA,kBACA,iBACA,QACA,iBACA,eACM;AACN,SAAQ,iBAAiB;AACzB,SAAM;AACN,SAAQ,IAAI,MAAM,MAAM,oCAAoC,CAAC;AAC7D,SAAM;CAEN,MAAM,UAAU,cAAc;CAC9B,MAAM,QAAQ,cAAc;AAE5B,QAAK,MAAM,KAAK,6BAA6B,GAAG,MAAM,IAAIC,cAAY,CAAC;AACvE,SAAM;AACN,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,WAAW,SAAS,CAAC;AAChF,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,SAAS,UAAU,SAAS,QAAQ,+BAA+B,CAAC;AAC/H,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,oBAAoB,oCAAoC,cAAc,CAAC;AAClI,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,oBAAoB,8CAA8C,cAAc,CAAC;AAC5I,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,2BAA2B,0DAA0D,cAAc,CAAC;AAC/J,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,sBAAsB,uDAAuD,cAAc,CAAC;AACvJ,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,iBAAiB,wDAAwD,cAAc,CAAC;AACnJ,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,mBAAmB,gDAAgD,cAAc,CAAC;AAC7I,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,OAAO,CAAC;AACnE,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,kBAAkB,kDAAkD,cAAc,CAAC;AAC9I,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,kBAAkB,mCAAmC,cAAc,CAAC;AAC/H,SAAQ,IAAI,MAAM,IAAI,uBAAuB,GAAG,MAAM,KAAK,gBAAgB,iCAAiC,cAAc,CAAC;AAC3H,SAAM;AACN,SAAQ,IAAI,MAAM,KAAK,OAAO,mDAAmD,CAAC;AAClF,SAAM;AAEN,QAAK,MAAM,KAAK,gBAAgB,CAAC;AACjC,SAAM;AACN,SAAQ,IAAI,MAAM,IAAI,2BAA2B,CAAC;AAClD,SAAQ,IAAI,MAAM,KAAK,qCAAqC,CAAC;AAC7D,SAAM;AACN,SAAQ,IAAI,MAAM,IAAI,yBAAyB,CAAC;AAChD,SAAQ,IAAI,MAAM,KAAK,6BAA6B,CAAC;AACrD,SAAM;AACN,SAAQ,IAAI,MAAM,IAAI,8BAA8B,CAAC;AACrD,SAAQ,IAAI,MAAM,KAAK,uCAAuC,CAAC;AAC/D,SAAM;AACN,KAAI,SAAS,UAAU,QAAQ;AAC7B,UAAQ,IAAI,MAAM,IAAI,gDAAgD,CAAC;AACvE,UAAQ,IAAI,MAAM,KAAK,uBAAuB,CAAC;AAC/C,UAAM;AACN,UAAQ,IAAI,MAAM,IAAI,wBAAwB,CAAC;AAC/C,UAAQ,IAAI,MAAM,KAAK,uDAAuD,CAAC;AAC/E,UAAM;;AAER,SAAQ,IAAI,MAAM,IAAI,oCAAoC,CAAC;AAC3D,SAAQ,IAAI,MAAM,KAAK,uBAAuB,CAAC;AAC/C,SAAM;AACN,SAAQ,IAAI,MAAM,IAAI,0BAA0B,CAAC;AACjD,SAAQ,IAAI,MAAM,KAAK,iBAAiB,CAAC;AACzC,SAAM;;;;;;;;;;;;;;;ACvBR,SAAgBC,YAAU,KAAqB;AAC7C,QAAO,IACJ,UAAU,OAAO,CAEjB,QAAQ,4DAA4D,GAAG,CACvE,QAAQ,oBAAoB,IAAI,CAChC,QAAQ,YAAY,GAAG,CACvB,aAAa;;;;;;;;;;;;AAalB,SAAgB,aAAa,IAAwB;CACnD,MAAM,OAAO,GACV,QACC;;;;;sCAMD,CACA,KAAK;CAER,MAAM,sBAAgB,IAAI,KAAK;AAC/B,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,SAAsB;GAC1B,OAAO,IAAI;GACX,MAAM,IAAI;GACV,UAAU,IAAI;GACf;AACD,MAAI,IAAIA,YAAU,IAAI,MAAM,EAAE,OAAO;EAGrC,MAAM,UAAUA,YAAU,IAAI,KAAK;AACnC,MAAI,CAAC,IAAI,IAAI,QAAQ,CAAE,KAAI,IAAI,SAAS,OAAO;;AAEjD,QAAO;;;AAQT,SAAgB,WAAW,QAAiC;AAC1D,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,UAAU,MAAM,MAAM;AAC5B,MAAI,QAAQ,aAAa,CAAC,WAAW,mBAAmB,EAAE;GACxD,MAAM,QAAQ,QAAQ,MAAM,mBAAmB,OAAO,CAAC,MAAM;AAC7D,OAAI,MAAO,QAAO;;;AAGtB,QAAO;;;;;;;;;;;;;;;AA2BT,SAAgB,iBAAiB,QAA2B;AAC1D,QAAO,OAAO,MAAM,MAAM,EAAE,MAAM,CAAC,aAAa,KAAK,MAAM;;;;;;;;;;AAW7D,SAAgB,aAAa,OAAqB,SAA8B;AAgB9E,KAAI,MAAM,WAAW;EACnB,MAAM,MAAM,QAAQ,IAAIA,YAAU,MAAM,UAAU,CAAC;AACnD,MAAI,IACF,QAAO;GACL,SAAS,IAAI;GACb,UAAU,IAAI;GACd,QAAQ;GACT;;CAQL,MAAM,QAAQ,WAAW,MAAM,OAAO;AACtC,KAAI,OAAO;EACT,MAAM,MAAM,QAAQ,IAAIA,YAAU,MAAM,CAAC;AACzC,MAAI,IACF,QAAO;GACL,SAAS,IAAI;GACb,UAAU,IAAI;GACd,QAAQ;GACT;AAIH,SAAO;GAAE,GAAG;GAAU,SAAS;GAAO;;AAMxC,KAAI,MAAM,gBAAgB,iBAAiB,MAAM,OAAO,EAAE;EACxD,MAAM,MAAM,QAAQ,IAAIA,YAAU,MAAM,aAAa,CAAC;AACtD,MAAI,IACF,QAAO;GAAE,SAAS,IAAI;GAAO,UAAU,IAAI;GAAU,QAAQ;GAAS;AAExE,SAAO;GAAE,GAAG;GAAU,SAAS,MAAM;GAAc;;AAMrD,KAAI,MAAM,UAAW,QAAO;EAAE,GAAG;EAAU,SAAS,MAAM;EAAW;AAErE,QAAO,EAAE,GAAG,UAAU;;;;;;;;;;ACrLxB,MAAM,MAAM;AA2CZ,eAAe,KACb,OACA,MACA,MACY;CACZ,MAAM,MAAM,MAAM,MAAM,GAAG,MAAM,QAAQ;EACvC,QAAQ,MAAM,UAAU;EACxB,SAAS;GACP,eAAe,UAAU;GACzB,GAAI,MAAM,OAAO,EAAE,gBAAgB,oBAAoB,GAAG,EAAE;GAC7D;EACD,MAAM,MAAM,OAAO,KAAK,UAAU,KAAK,KAAK,GAAG;EAChD,CAAC;AAEF,KAAI,CAAC,IAAI,IAAI;EAGX,MAAM,OAAO,IAAI,WAAW,MAAM,oDAAoD;AACtF,QAAM,IAAI,MAAM,WAAW,IAAI,OAAO,GAAG,IAAI,aAAa,OAAO;;AAInE,KAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,QAAQ,MAAM,IAAI,MAAM;;;;;;;;;;AAW1B,eAAe,QAAW,OAAe,MAA4B;CACnE,MAAM,MAAW,EAAE;CACnB,IAAI;CACJ,IAAI,QAAQ;AAEZ,IAAG;EACD,MAAM,MAAM,KAAK,SAAS,IAAI,GAAG,MAAM;EAEvC,MAAM,OAAO,MAAM,KAAkB,OADzB,SAAS,GAAG,OAAO,IAAI,SAAS,mBAAmB,OAAO,KAAK,KAC3B;AAChD,MAAI,KAAK,GAAI,KAAK,WAAW,EAAE,CAAE;AACjC,WAAS,KAAK;UACP,UAAU,EAAE,QAAQ;AAE7B,QAAO;;;;;;;;;;AAWT,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;AA4BjB,SAAgB,gBACd,UACA,MAC6B;CAC7B,MAAM,OAAO,SAAS,QAAQ,MAAM,CAAC,EAAE,eAAe,CAAC,EAAE,WAAW;CACpE,MAAM,6BAAa,IAAI,KAA4B;AACnD,MAAK,MAAM,KAAK,MAAM;AACpB,MAAI,CAAC,EAAE,UAAW;EAClB,MAAM,WAAW,WAAW,IAAI,EAAE,UAAU;AAC5C,MAAI,SAAU,UAAS,KAAK,EAAE;MACzB,YAAW,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;;CAGvC,MAAM,sBAAM,IAAI,KAA6B;CAC7C,MAAM,cAAc,KAAK,MAAM,MAAM,EAAE,OAAO,KAAK;AACnD,KAAI,CAAC,YAAa,QAAO;AAIzB,KAAI,IAAI,MAAM;EAAE,SAAS;EAAa,WAAW;EAAM,CAAC;CAExD,MAAM,QAAQ,SAAsB,cAA4B;AAC9D,MAAI,IAAI,IAAI,QAAQ,GAAG,CAAE;AAgBzB,MAAI,QAAQ,WAAW;AACrB,WAAQ,OAAO,MACb,iCAAiC,QAAQ,KAAK,KAAK,QAAQ,GAAG,8GAE/D;AACD;;AAGF,MAAI,IAAI,QAAQ,IAAI;GAAE;GAAS;GAAW,CAAC;AAC3C,OAAK,MAAM,SAAS,WAAW,IAAI,QAAQ,GAAG,IAAI,EAAE,CAAE,MAAK,OAAO,UAAU;;AAG9E,MAAK,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI,EAAE,CAAE,MAAK,KAAK,IAAI,KAAK;AAEjE,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BT,SAAgB,gBAAgB,OAAe,MAA0B,QAAkC;CACzG,MAAM,SAAS,MAAM,UAAU;CAC/B,MAAM,QAAQ,QAAQ,UAAU;AAChC,KAAI,WAAW,KAAK,SAAS,OAAQ;AAErC,SAAQ,OAAO,MACb,0CAA0C,MAAM,YACpC,OAAO,sBAAsB,MAAM,KAAK,SAAS,MAAM,kJAEP,MAAM,eACnE;;;;;;;;AASH,SAAgB,SACd,KACwB;CACxB,MAAM,QAAQ,KAAK,MAAM;AACzB,KAAI,CAAC,MAAO,QAAO,EAAE;AACrB,QAAO,SAAS,KAAK,MAAM,GAAG,EAAE,UAAU,OAAO,GAAG,EAAE,YAAY,OAAO;;AAG3E,eAAsB,aAAa,OAA6D;AAE9F,SADiB,MAAM,QAAqB,OAAO,YAAY,EAE5D,QAAQ,MAAM,CAAC,EAAE,eAAe,CAAC,EAAE,WAAW,CAC9C,KAAK,OAAO;EAAE,IAAI,EAAE;EAAI,MAAM,EAAE;EAAM,EAAE;;;AAQ7C,SAASC,gBAAc,KAAqB;AAC1C,QAAO,IACJ,QAAQ,4DAA4D,GAAG,CACvE,MAAM,CACN,aAAa;;;AAIlB,SAAS,WAAW,MAAwC;AAC1D,SAAQ,MAAR;EACE,KAAK,EAAG,QAAO;EACf,KAAK,EAAG,QAAO;EACf,KAAK,EAAG,QAAO;EACf,QAAS,QAAO;;;AAIpB,SAAS,aAAa,GAAqC;AACzD,SAAQ,GAAR;EACE,KAAK,KAAM,QAAO;EAClB,KAAK,KAAM,QAAO;EAClB,KAAK,KAAM,QAAO;EAClB,QAAS,QAAO;;;AAQpB,IAAa,kBAAb,MAAqD;CACnD,AAAS,aAAa;CAEtB,YACE,AAAiB,QACjB,AAAiB,SAEjB,AAAiB,YACjB;EAJiB;EACA;EAEA;;;;;;;CAQnB,AAAQ,QAAuB;AAC7B,SAAO,KAAK,OAAO,QAAQ,MAAM,IAAI,QAAQ,IAAI,iBAAiB,MAAM,IAAI;;CAG9E,eAAwB;AACtB,SAAO,QAAQ,KAAK,OAAO,WAAW,KAAK,OAAO,IAAI,KAAK,OAAO,cAAc;;;CAIlF,IAAY,eAA8B;AACxC,SAAO,KAAK,YAAY,gBAAgB;;;;;;;;;;CAW1C,MAAM,QAAQ,IAAkC;EAC9C,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,SAAS,CAAC,KAAK,OAAO,cAAe,QAAO;EAEjD,IAAI;AACJ,MAAI;AACF,OAAI,MAAM,KAAe,OAAO,UAAU,mBAAmB,GAAG,GAAG;UAC7D;AACN,UAAO;;AAET,MAAI,CAAC,GAAG,GAAI,QAAO;EAInB,MAAM,QADS,gBADE,MAAM,QAAqB,OAAO,YAAY,EACtB,KAAK,OAAO,cAAc,CAC9C,IAAI,EAAE,cAAc,GAAG;EAE5C,MAAM,QAAQ,aACZ;GAAE,QAAQ,EAAE,UAAU,EAAE;GAAE,WAAW,OAAO,aAAa;GAAM,cAAc,KAAK;GAAc,EAChG,KAAK,QACN;AAED,SAAO;GACL,IAAI,EAAE;GACN,OAAO,EAAE;GACT,MAAM,EAAE,eAAe;GACvB;GACA,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,QAAQ;GACvC,YAAY,EAAE,KAAK,eAAgB,EAAE,IAAI,UAAU,OAAQ;GAC3D,UAAU,WAAW,EAAE,SAAS;GAChC,QAAQ,EAAE,UAAU,EAAE;GACtB,WAAW,oCAAoC,EAAE;GAClD;;CAGH,MAAM,SAAS,OAAoB,EAAE,EAAmB;EACtD,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,SAAS,CAAC,KAAK,OAAO,cAAe,QAAO,EAAE;EAKnD,MAAM,WAAW,MAAM,QAAqB,OAAO,YAAY;EAC/D,MAAM,OAAO,KAAK,OAAO;EACzB,MAAM,SAAS,gBAAgB,UAAU,KAAK;AAC9C,MAAI,OAAO,SAAS,EAAG,QAAO,EAAE;EAChC,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;EAIpE,MAAM,OAAmB,EAAE;AAC3B,OAAK,MAAM,MAAM,MAAM,MAAM,CAC3B,MAAK,KAAK,GAAI,MAAM,QAAkB,OAAO,qBAAqB,mBAAmB,GAAG,GAAG,CAAE;EAG/F,MAAM,MAAc,EAAE;AAEtB,OAAK,MAAM,KAAK,MAAM;AACpB,OAAI,EAAE,WAAW,EAAE,WAAY;GAE/B,MAAM,QAAQ,OAAO,IAAI,EAAE,WAAW;AACtC,OAAI,CAAC,MAAO;GAKZ,MAAM,QAAQ,aACZ;IAAE,QAAQ,EAAE,UAAU,EAAE;IAAE,WAAW,MAAM;IAAW,cAAc,KAAK;IAAc,EACvF,KAAK,QACN;AAED,OAAI,KAAK,SAAS,MAAM,YAAY,KAAK,MAAO;AAChD,OAAI,KAAK,oBAAoB,SAAS,MAAM,YAAY,KAAM;GAE9D,MAAM,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,QAAQ;AAC9C,OAAI,KAAK,WAGP;QAAI,CAAC,OAAO,IAAI,MAAM,GAAG,GAAG,GAAG,KAAK,UAAW;;AAGjD,OAAI,KAAK;IACP,IAAI,EAAE;IACN,OAAO,EAAE;IACT,MAAM,EAAE,eAAe;IACvB;IACA;IACA,YAAY,EAAE,KAAK,eAAgB,EAAE,IAAI,UAAU,OAAQ;IAC3D,UAAU,WAAW,EAAE,SAAS;IAChC,QAAQ,EAAE,UAAU,EAAE;IAGtB,WAAW,oCAAoC,EAAE;IAClD,CAAC;AAEF,OAAI,KAAK,SAAS,IAAI,UAAU,KAAK,MAAO;;AAG9C,SAAO;;;;;;;;;;;;;;;;;CAkBT,MAAM,IAAI,MAA8B;EACtC,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,SAAS,CAAC,KAAK,OAAO,cACzB,OAAM,IAAI,MAAM,wDAAwD;EAY1E,IAAI,iBAAgC;AACpC,MAAI,KAAK,SAAS,CAAC,KAAK,KACtB,kBAAiB,MAAM,KAAK,mBAAmB,KAAK,MAAM;EAG5D,MAAM,SAAS,CAAC,GAAI,KAAK,UAAU,EAAE,CAAE;AAEvC,MADmB,KAAK,SAAS,CAAC,KAAK,QAAQ,CAAC,kBAG9C,CAAC,OAAO,MAAM,MAAM,EAAE,aAAa,KAAK,OAAO,KAAK,MAAO,aAAa,GAAG,CAE3E,QAAO,KAAK,OAAO,KAAK,QAAQ;EAKlC,MAAM,YAAY,KAAK,SAAS,KAAK,OAAO,SAAY,KAAK,OAAO;EAIpE,MAAM,YAAY,KAAK,QAClB,MAAM,KAAK,uBAAuB,KAAK,KAAK,EAAE,KAC9C,kBAAkB,KAAK,OAAO;EAEnC,MAAM,UAAU,MAAM,KAAe,OAAO,UAAU;GACpD,QAAQ;GACR,MAAM;IACJ,SAAS,KAAK;IACd,aAAa,KAAK;IAClB,YAAY;IACZ,GAAI,YAAY,EAAE,YAAY,WAAW,GAAG,EAAE;IAC9C;IACA,UAAU,aAAa,KAAK,SAAS;IACrC,GAAG,SAAS,KAAK,IAAI;IACtB;GACF,CAAC;AAGF,kBAAgB,oBAAoB,KAAK,MAAM,QAAQ,eAAe,GAAG;AAEzE,SAAO;GACL,IAAI,QAAQ;GACZ,OAAO,QAAQ;GACf,MAAM,QAAQ,eAAe;GAC7B,OAAO,KAAK,QACR,aAAa;IAAE;IAAQ,WAAW;IAAM,cAAc,KAAK;IAAc,EAAE,KAAK,QAAQ,GACxF,EAAE,GAAG,UAAU;GACnB,KAAK,QAAQ,KAAK,YAAY,QAAQ,KAAK,QAAQ;GACnD,UAAU,WAAW,QAAQ,SAAS;GACtC,QAAQ,QAAQ,UAAU,EAAE;GAC5B,WAAW,oCAAoC,QAAQ;GACxD;;;;;;;;;CAUH,MAAM,UAAU,IAAY,QAAiC;EAC3D,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,8DAA8D;AAC1F,QAAM,KAAe,OAAO,UAAU,MAAM;GAAE,QAAQ;GAAQ,MAAM,EAAE,QAAQ;GAAE,CAAC;;;;;;;;;;CAWnF,MAAM,OAAO,IAAY,WAAkC;EACzD,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,8DAA8D;AAC1F,QAAM,KAAe,OAAO,UAAU,MAAM;GAAE,QAAQ;GAAQ,MAAM,EAAE,YAAY,WAAW;GAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiClG,MAAc,mBAAmB,OAAuC;AACtE,MAAI;AACF,QAAK,MAAM,KAAK,MAAM,KAAK,iBAAiB,CAE1C,KADiB,aAAa;IAAE,QAAQ,EAAE;IAAE,WAAW,EAAE;IAAM,EAAE,KAAK,QAAQ,CACjE,YAAY,MAAO,QAAO,EAAE;UAErC;AAGR,SAAO;;CAGT,MAAM,kBAAgE;EACpE,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,SAAS,CAAC,KAAK,OAAO,cACzB,OAAM,IAAI,MAAM,8DAA8D;EAGhF,MAAM,OAAO,KAAK,OAAO;AAEzB,UADiB,MAAM,QAAqB,OAAO,YAAY,EAE5D,QAAQ,MAAM,CAAC,EAAE,eAAe,CAAC,EAAE,cAAc,EAAE,cAAc,KAAK,CACtE,KAAK,OAAO;GAAE,IAAI,EAAE;GAAI,MAAM,EAAE;GAAM,EAAE;;CAG7C,MAAM,uBAAuB,MAAyD;EACpF,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,SAAS,CAAC,KAAK,OAAO,cACzB,OAAM,IAAI,MAAM,8DAA8D;EAGhF,MAAM,OAAO,KAAK,OAAO;EACzB,MAAM,WAAW,MAAM,QAAqB,OAAO,YAAY;EAC/D,MAAM,OAAOA,gBAAc,KAAK;AAEhC,OAAK,MAAM,KAAK,UAAU;AACxB,OAAI,EAAE,eAAe,EAAE,WAAY;AACnC,OAAI,EAAE,cAAc,QAAQA,gBAAc,EAAE,KAAK,KAAK,KACpD,QAAO;IAAE,IAAI,EAAE;IAAI,SAAS;IAAO;;AAQvC,SAAO;GAAE,KAJO,MAAM,KAAkB,OAAO,aAAa;IAC1D,QAAQ;IACR,MAAM;KAAE;KAAM,WAAW;KAAM;IAChC,CAAC,EACmB;GAAI,SAAS;GAAM;;;CAI1C,MAAM,QAAQ,IAAY,SAAgC;EACxD,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,8DAA8D;AAQ1F,kBAAgB,WAAW,UAPX,MAAM,KAA2B,OAAO,aAAa;GACnE,QAAQ;GACR,MAAM;IAAE,SAAS;IAAI;IAAS;GAC/B,CAAC,GAI2C,WAAW,GAAG;;;CAI7D,MAAM,aACJ,QACoE;EACpE,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,MAAO,QAAO,EAAE;AAOrB,UANa,MAAM,QACjB,OACA,qBAAqB,mBAAmB,OAAO,GAChD,EAGW,KAAK,OAAO;GAAE,IAAI,EAAE;GAAI,SAAS,EAAE,WAAW;GAAI,UAAU,EAAE;GAAW,EAAE;;CAGzF,MAAM,cAAc,WAAkC;EACpD,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,8DAA8D;AAC1F,QAAM,KAAc,OAAO,aAAa,aAAa,EAAE,QAAQ,UAAU,CAAC;;CAG5E,MAAM,SAAS,IAA2B;EACxC,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,wDAAwD;AACpF,QAAM,KAAW,OAAO,UAAU,GAAG,SAAS,EAAE,QAAQ,QAAQ,CAAC;;;;;;;ACrpBrE,eAAsB,YAAY,IAA0C;AAC1E,SAAQ,+BAA+B;CAGvC,MAAM,UADW,eAAe,CACN,SAAS,EAAE;CACrC,MAAM,UAAU,QAAQ,WAAW;AAEnC,KAAI,SAAS,UAAU,SAAS,eAAe;AAC7C,UAAQ,IAAI,EAAE,GAAG,yCAAyC,CAAC;AAC3D,SAAO,EAAE,OAAO,SAAS;;AAG3B,SAAM;AACN,QAAK,4EAA4E;AACjF,QAAK,0EAA0E;AAC/E,SAAM;AACN,QAAK,+EAA+E;AACpF,SAAM;AAGN,KAAI,CADW,MAAM,YAAY,IAAI,qDAAqD,MAAM,EACnF;AACX,UAAM;AACN,UAAQ,IAAI,EAAE,GAAG,yBAAyB,CAAC;AAC3C,UAAQ,IAAI,EAAE,IAAI,yCAAyC,CAAC;AAC5D,SAAO,EAAE,OAAO,EAAE,SAAS,OAAO,EAAE;;AAOtC,SAAM;AACN,QAAK,qCAAqC;AAC1C,QAAK,EAAE,IAAI,4CAA4C,CAAC;AACxD,QAAK,EAAE,IAAI,kEAAkE,CAAC;AAC9E,SAAM;AACN,QAAK,gFAAgF;AACrF,SAAM;CAEN,MAAM,WAAW,QAAQ,IAAI,iBAAiB,MAAM;CACpD,IAAI,SAAS,SAAS,UAAU;AAEhC,KAAI,CAAC,UAAU,UAEb;MADe,MAAM,YAAY,IAAI,qDAAqD,KAAK,CACnF,UAAS;;AAGvB,QAAO,CAAC,QAAQ;AACd,YAAU,MAAM,OAAO,IAAI,mCAAmC,EAAE,MAAM;AACtE,MAAI,CAAC,OAAQ,SAAQ,IAAI,EAAE,KAAK,4DAA4D,CAAC;;AAM/F,SAAM;AACN,QAAK,sCAAsC;CAE3C,IAAI;AACJ,KAAI;AACF,aAAW,MAAM,aAAa,OAAO;UAC9B,GAAG;AACV,UAAM;AACN,UAAQ,IAAI,EAAE,KAAK,4BAA4B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,GAAG,CAAC;AAC7F,UAAQ,IAAI,EAAE,IAAI,2DAA2D,CAAC;AAC9E,UAAQ,IAAI,EAAE,IAAI,oDAAoD,CAAC;AACvE,SAAO,EAAE,OAAO,EAAE,SAAS,OAAO,EAAE;;AAGtC,KAAI,SAAS,WAAW,GAAG;AACzB,UAAQ,IAAI,EAAE,KAAK,uEAAuE,CAAC;AAC3F,SAAO,EAAE,OAAO,EAAE,SAAS,OAAO,EAAE;;AAGtC,SAAM;AACN,QAAK,yEAAyE;AAC9E,SAAM;AACN,UAAS,SAAS,GAAG,MAAMC,OAAK,OAAO,OAAO,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC;AAC/E,SAAM;CAEN,IAAI,gBAAgB;AACpB,QAAO,CAAC,eAAe;EACrB,MAAM,UAAU,MAAM,OAAO,IAAI,uBAAuB,SAAS,OAAO,KAAK,EAAE,MAAM;EACrF,MAAM,MAAM,OAAO,SAAS,QAAQ,GAAG,GAAG;AAC1C,MAAI,OAAO,UAAU,IAAI,IAAI,OAAO,KAAK,MAAM,SAAS,OACtD,iBAAgB,SAAS,KAAM;MAE/B,SAAQ,IAAI,EAAE,KAAK,kCAAkC,SAAS,OAAO,GAAG,CAAC;;CAI7E,MAAM,SAAS,SAAS,MAAM,MAAM,EAAE,OAAO,cAAc;AAM3D,SAAM;AACN,SAAQ,IAAI,EAAE,GAAG,wBAAwB,OAAO,KAAK,QAAQ,cAAc,IAAI,CAAC;AAChF,SAAQ,IAAI,EAAE,IAAI,gEAAgE,CAAC;AACnF,SAAQ,IAAI,EAAE,IAAI,+EAA+E,CAAC;AAQlG,QAAO,EACL,OAAO;EACL,SAAS;EACT,cATiB,MAAM,YACzB,IACA,oEACA,MACD;EAMG,WAAW,EACT,SAAS;GAAE,SAAS;GAAM;GAAQ;GAAe,EAClD;EACF,EACF;;;;;;;;;;;;;;;;;;;;;;ACtGH,MAAM,YAAY,KADC,KAAK,SAAS,EAAE,UAAU,EACV,QAAQ;AAC3C,MAAM,YAAY,KAAK,SAAS,EAAE,UAAU,MAAM;AAElD,MAAa,iBAAiB;AAC9B,MAAM,sBAAsB;;AAQ5B,SAAS,UAAkB;AACzB,KAAI;AACF,SAAO,aAAa,kBAAkB,CAAC,MAAM,EAAE,EAAE,UAAU,QAAQ,CAAC,CAAC,MAAM,IAAI;SACzE;AACN,SAAO;;;AAQX,SAAS,eAAe,OAA0B;CAChD,MAAM,WAAW,kBAAkB;CACnC,MAAM,QAAS,OAAO,SAAS,aAAa,YAAY,SAAS,aAAa,OAC1E,SAAS,WACT,EAAE;CAEN,MAAM,aAAa,MAAM,QAAQ,MAAM,cAAc,GAChD,MAAM,gBACP,EAAE;CAEN,IAAI,UAAU;CACd,IAAI,WAAW;AAEf,MAAK,MAAM,QAAQ,YAAY;AAC7B,MAAI,KAAK,eAAe,QAAS;EACjC,MAAM,UAAU,MAAM,QAAQ,KAAK,SAAS,GAAI,KAAK,WAA8C,EAAE;AACrG,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,MAAM,OAAO,MAAM,eAAe,WAAW,MAAM,aAAa;AACtE,OAAI,QAAQ,eAAgB,YAAW;AACvC,OAAI,IAAI,SAAS,oBAAoB,EAAE;AACrC,UAAM,aAAa;AACnB,UAAM,KAAK,yCAAyC,iBAAiB;AACrE,cAAU;AACV,eAAW;;;;AAOjB,KAAI,CAAC,UAAU;AACb,aAAW,KAAK;GACd,SAAS;GACT,OAAO,CAAC;IAAE,MAAM;IAAW,SAAS;IAAgB,CAAC;GACtD,CAAC;AACF,QAAM,KAAK,qCAAqC,iBAAiB;AACjE,YAAU;;AAGZ,KAAI,SAAS;AACX,QAAM,gBAAgB;AACtB,WAAS,WAAW;AACpB,oBAAkB,SAAS;OAE3B,OAAM,KAAK,4CAA4C;AAEzD,QAAO;;AAOT,MAAM,eAAe,QAAgB;;kBAEnB,IAAI;gCACU,IAAI;0BACV,IAAI;aACjB,IAAI;YACL,IAAI;;;AAIhB,SAAS,KAAK,MAAc,KAAqB;AAC/C,KAAI,SAAS,SAAU,QAAO,YAAY,IAAI;AAC9C,KAAI,SAAS,UAAW,QAAO,oFAAoF,IAAI;AACvH,KAAI,SAAS,aAAc,QAAO,yEAAyE,IAAI;AAG/G,QAAO,wDAAwD,KAAK,0BAA0B,IAAI;;AAGpG,SAAS,aAAa,OAA0B;CAC9C,MAAM,MAAM,SAAS;CACrB,IAAI,UAAU;AACd,MAAK,MAAM,QAAQ;EAAC;EAAO;EAAW;EAAU;EAAW;EAAa,EAAE;EACxE,MAAM,OAAO,KAAK,WAAW,KAAK;EAClC,MAAM,OAAO;EACb,IAAI,UAAyB;AAC7B,MAAI,WAAW,KAAK,CAClB,KAAI;AACF,aAAU,aAAa,QAAQ;IAAC;IAAM;IAAM;IAAK,EAAE,EAAE,UAAU,QAAQ,CAAC;UAClE;AACN,aAAU;;AAGd,MAAI,YAAY,QAAQ,CAAC,QAAQ,SAAS,KAAK,EAAE;GAE/C,MAAM,QAAQ,KAAK,WAAW,GAAG,KAAK,UAAU;AAChD,OAAI,CAAC,WAAW,MAAM,EAAE;AACtB,eAAW,MAAM,MAAM;AACvB,UAAM,KAAK,GAAG,KAAK,6BAA6B,KAAK,UAAU;UAC1D;AACL,eAAW,KAAK;AAChB,UAAM,KAAK,GAAG,KAAK,cAAc,KAAK,0BAA0B;;AAElE,aAAU;aACD,YAAY,KACrB,WAAU;OACL;AACL,SAAM,KAAK,GAAG,KAAK,wBAAwB;AAC3C;;AAGF,gBAAc,MADD,KAAK,MAAM,IAAI,EACF;GAAE,UAAU;GAAQ,MAAM;GAAO,CAAC;AAC5D,MAAI;AACF,aAAU,MAAM,IAAM;UAChB;AAGR,QAAM,KAAK,GAAG,KAAK,qBAAqB,IAAI,WAAW;;AAEzD,QAAO;;AAOT,SAAS,cAAc,OAA0B;CAC/C,MAAM,MAAM,KAAK,WAAW,yBAAyB;AACrD,KAAI,CAAC,WAAW,IAAI,CAAE,QAAO;AAC7B,YAAW,IAAI;AACf,OAAM,KAAK,WAAW,oBAAoB,mCAAmC;AAC7E,QAAO;;AAKT,SAAgB,iBAAgC;CAC9C,MAAM,QAAkB,EAAE;CAC1B,IAAI,UAAU,eAAe,MAAM;AACnC,KAAI,aAAa,MAAM,CAAE,WAAU;AACnC,KAAI,cAAc,MAAM,CAAE,WAAU;AACpC,KAAI,CAAC,WAAW,UAAU,CACxB,OAAM,KAAK,SAAS,UAAU,yCAAyC;AAEzE,QAAO;EAAE;EAAO;EAAS;;;;;;ACrL3B,eAAsB,YAAY,IAA0C;AAC1E,SAAQ,uCAAuC;CAG/C,MAAM,UADW,eAAe,CACP;AAEzB,KAAI,SAAS,aAAa,OAAO,KAAK,QAAQ,UAAU,CAAC,SAAS,GAAG;AACnE,MAAI,QAAQ,YAAY,OAAO;AAC7B,WAAQ,IAAI,EAAE,GAAG,yEAAyE,CAAC;AAC3F,QAAK,MAAM,KAAK,gBAAgB,CAAC,MAAO,QAAK,KAAK,IAAI;;AAExD,SAAO,EAAE,SAAS,SAAS;;AAG7B,SAAM;AACN,QAAK,sEAAsE;AAC3E,QAAK,+DAA+D;AACpE,SAAM;AACN,QAAK,yEAAyE;AAC9E,QAAK,uEAAuE;AAC5E,SAAM;AACN,QAAK,4EAA4E;AACjF,SAAM;AAGN,KAAI,CADW,MAAM,YAAY,IAAI,oCAAoC,MAAM,EAClE;AACX,UAAM;AACN,UAAQ,IAAI,EAAE,GAAG,6BAA6B,CAAC;AAC/C,UAAQ,IAAI,EAAE,IAAI,sEAAsE,CAAC;AACzF,SAAO,EAAE,SAAS;GAAE,SAAS;GAAO,WAAW,EAAE;GAAE,SAAS,EAAE;GAAE,EAAE;;AAGpE,SAAM;AACN,QAAK,uEAAuE;AAC5E,QAAK,iDAAiD;AACtD,SAAM;AACN,QAAK,EAAE,IAAI,sCAAsC,CAAC;AAClD,QAAK,EAAE,IAAI,4DAA4D,CAAC;AACxE,QAAK,EAAE,IAAI,gDAAgD,CAAC;AAC5D,QAAK,EAAE,IAAI,2DAA2D,CAAC;AACvE,SAAM;AACN,QAAK,+DAA+D;AACpE,SAAM;CAEN,MAAM,IAAI,gBAAgB;AAC1B,MAAK,MAAM,KAAK,EAAE,MAAO,QAAK,KAAK,IAAI;AACvC,QAAO,EAAE,SAAS;EAAE,SAAS;EAAO,WAAW,EAAE;EAAE,SAAS,EAAE;EAAE,EAAE;;;;;ACpBpE,eAAe,WAA0B;CACvC,MAAM,KAAK,UAAU;AAErB,KAAI;AACF,MAAI,WAAWC,cAAY,EAAE;GAC3B,MAAM,UAAU,YAAY;AAC5B,WAAM;AACN,WAAQ,IACN,MAAM,OAAO,qCAAqC,GAClD,MAAM,IAAI,uDAAuD,CAClE;AACD,WAAQ,IAAI,MAAM,IAAI,aAAaA,gBAAc,CAAC;AAClD,WAAQ,IAAI,MAAM,IAAI,sBAAsB,QAAQ,iBAAiB,CAAC;AACtE,WAAM;AAGN,OAAI,CADY,MAAM,YAAY,IAAI,sCAAsC,KAAK,EACnE;AACZ,OAAG,OAAO;AACV,WAAK,MAAM,IAAI,qBAAqB,CAAC;AACrC,YAAM;AACN;;;AAKJ,eAAa;AACb,UAAM;AACN,QAAM,OAAO,IAAI,MAAM,IAAI,kCAAkC,CAAC;EAG9D,MAAM,gBAAgB,MAAM,YAAY,GAAG;EAG3C,MAAM,kBAAkB,MAAM,cAAc,GAAG;EAG/C,MAAM,oBAAoB,MAAM,aAAa,GAAG;EAGhD,MAAM,oBAAoB,MAAM,aAAa,GAAG;EAGhD,MAAM,2BAA2B,MAAM,oBAAoB,GAAG;EAG9D,MAAM,sBAAsB,MAAM,eAAe,GAAG;EAGpD,MAAM,iBAAiB,MAAM,UAAU,GAAG;EAG1C,MAAM,mBAAmB,MAAM,YAAY,GAAG;EAG9C,MAAM,SAAS,MAAM,WAAW,GAAG;EAGnC,MAAM,kBAAkB,MAAM,aAAa,IAAI,OAAO;EAGtD,MAAM,kBAAkB,MAAM,WAAW,GAAG;EAG5C,MAAM,gBAAgB,MAAM,QAAQ,GAAG;AAGvC,QAAM,gBAAgB,GAAG;EAGzB,MAAM,aAAa,MAAM,YAAY,GAAG;EAGxC,MAAM,gBAAgB,MAAM,YAAY,GAAG;EAG3C,MAAM,aAAa;GAAE,GAAG;GAAe,GAAG;GAAiB,GAAG;GAAY,GAAG;GAAe;AAC5F,cAAY,WAAW;AAEvB,UAAM;AACN,UAAQ,IAAI,MAAM,MAAM,yBAAyB,CAAC;AAGlD,QAAM,iBAAiB,GAAG;AAG1B,cACE,YACA,mBACA,mBACA,0BACA,qBACA,gBACA,kBACA,iBACA,QACA,iBACA,cACD;WAEO;AACR,KAAG,OAAO;;;AAId,SAAgB,qBAAqB,SAAwB;AAC3D,SACG,QAAQ,QAAQ,CAChB,MAAM,UAAU,CAChB,YACC,uFACD,CACA,OAAO,YAAY;AAClB,QAAM,UAAU;GAChB;;;;;;;AC1HN,SAAgB,aAAa,UAAiC;CAC5D,MAAM,YAAY,KAAK,UAAU,QAAQ;AACzC,KAAI,WAAW,UAAU,CAAE,QAAO;CAClC,MAAM,MAAM,KAAK,UAAU,WAAW,QAAQ;AAC9C,KAAI,WAAW,IAAI,CAAE,QAAO;AAC5B,QAAO;;;;;;AAOT,SAAgBC,qBACd,4BACA,UACe;AACf,KAAI,CAAC,2BAA4B,QAAO;AACxC,KAAI,CAAC,WAAW,2BAA2B,CAAE,QAAO;AACpD,KAAI,YAAY,+BAA+B,SAAU,QAAO;AAChE,QAAO;;;AAIT,SAAS,YAAY,GAAoB;AACvC,KAAI;AACF,YAAU,EAAE;AACZ,SAAO;SACD;AACN,SAAO;;;;AAKX,SAAgB,WAAW,MAAc,OAA4B;AACnE,KAAI,CAAC,MAAM,IAAI,KAAK,CAAE,QAAO;CAC7B,IAAI,IAAI;AACR,QAAO,MAAM,IAAI,GAAG,KAAK,GAAG,IAAI,CAAE;AAClC,QAAO,GAAG,KAAK,GAAG;;;;;;AAOpB,SAAgB,oBAAoB,KAAqB;CACvD,IAAI,UAAU;AACd,KAAI,CAAC,WAAW,IAAI,CAAE,QAAO;AAC7B,MAAK,MAAM,SAAS,YAAY,IAAI,EAAE;EACpC,MAAM,OAAO,KAAK,KAAK,MAAM;AAC7B,MAAI;AAEF,OADa,UAAU,KAAK,CACnB,gBAAgB,EAEvB;QAAI,CAAC,WADU,aAAa,KAAK,CACV,EAAE;AACvB,gBAAW,KAAK;AAChB;;;UAGE;;AAIV,QAAO;;;;;;AAOT,SAAgB,iBACd,UACA,QACA,QACA,OACS;AACT,KAAI,YAAY,SAAS,CACvB,KAAI;AAEF,MADa,UAAU,SAAS,CACvB,gBAAgB,EAAE;AAEzB,OADgB,aAAa,SAAS,KACtB,OACd,QAAO;AAET,cAAW,SAAS;SACf;AACL,UAAO,KAAK,GAAG,MAAM,8CAA8C;AACnE,UAAO;;UAEF,GAAG;AACV,SAAO,KAAK,GAAG,MAAM,mCAAmC,IAAI;AAC5D,SAAO;;AAIX,KAAI;AACF,cAAY,QAAQ,SAAS;AAC7B,SAAO;UACA,GAAG;AACV,SAAO,KAAK,GAAG,MAAM,8BAA8B,IAAI;AACvD,SAAO;;;;;;;;AASX,SAAgB,oBACd,UACA,QACA,MACS;AACT,KAAI,CAAC,YAAY,SAAS,CACxB,QAAO;AAGT,KAAI;EACF,MAAM,OAAO,UAAU,SAAS;AAEhC,MAAI,KAAK,aAAa,CACpB,QAAO;AAGT,MAAI,KAAK,gBAAgB,EAAE;AACzB,cAAW,SAAS;AACpB,UAAO;;AAGT,SAAO,KAAK,GAAG,KAAK,uDAAuD;AAC3E,SAAO;UACA,GAAG;AACV,SAAO,KAAK,GAAG,KAAK,6BAA6B,IAAI;AACrD,SAAO;;;;;;;;;;;;;AA8BX,SAAgB,UAAU,WAAmB,IAAyB;CACpE,MAAM,QAAmB;EAAE,SAAS;EAAG,SAAS;EAAG,SAAS;EAAG,SAAS;EAAG,QAAQ,EAAE;EAAE;AAEvF,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;AACzC,OAAM,WAAW,oBAAoB,UAAU;CAE/C,MAAM,WAAW,GACd,QACC;;sCAGD,CACA,KAAK;CAER,MAAM,6BAAa,IAAI,KAAa;AAEpC,MAAK,MAAM,WAAW,SACpB,KAAI,QAAQ,WAAW,UAAU;EAC/B,MAAM,WAAW,aAAa,QAAQ,UAAU;EAChD,MAAM,iBAAiBA,qBAAmB,QAAQ,kBAAkB,SAAS;AAE7E,MAAI,CAAC,YAAY,CAAC,eAChB;EAGF,MAAM,OAAO,WAAW,QAAQ,MAAM,WAAW;AACjD,aAAW,IAAI,KAAK;EACpB,MAAM,WAAW,KAAK,WAAW,KAAK;AAEtC,MAAI,CAAC,oBAAoB,UAAU,MAAM,QAAQ,KAAK,CACpD;AAGF,MAAI;AACF,aAAU,UAAU,EAAE,WAAW,MAAM,CAAC;WACjC,GAAG;AACV,SAAM,OAAO,KAAK,GAAG,KAAK,yCAAyC,IAAI;AACvE;;AAGF,MAAI,SAGF,KADgB,iBADE,KAAK,UAAU,QAAQ,EACG,UAAU,MAAM,QAAQ,GAAG,KAAK,QAAQ,CACvE,OAAM;MACd,OAAM;AAGb,MAAI,eAGF,KADgB,iBADK,KAAK,UAAU,WAAW,EACA,gBAAgB,MAAM,QAAQ,GAAG,KAAK,WAAW,CACnF,OAAM;MACd,OAAM;AAGb,MAAI;AACF,MAAG,QAAQ,qEAAqE,CAAC,IAC/E,UACA,KAAK,KAAK,EACV,QAAQ,GACT;UACK;YAGC,QAAQ,WAAW,YAAY;EACxC,MAAM,aAAa,KAAK,WAAW,WAAW;AAC9C,YAAU,YAAY,EAAE,WAAW,MAAM,CAAC;EAC1C,MAAM,WAAW,KAAK,YAAY,GAAG,QAAQ,KAAK,KAAK;AACvD,MAAI,CAAC,WAAW,SAAS,EAAE;GACzB,MAAM,UAAU;IACd,KAAK,QAAQ;IACb;IACA;IACA;IACA,eAAe,QAAQ;IACvB,eAAe,QAAQ;IACvB;IACD,CAAC,KAAK,KAAK;AACZ,OAAI;AACF,kBAAc,UAAU,SAAS,QAAQ;AACzC,UAAM;YACC,GAAG;AACV,UAAM,OAAO,KAAK,GAAG,QAAQ,KAAK,mBAAmB,IAAI;;;;AAMjE,QAAO;;;;;;;;;;AClQT,SAAgB,cAAc,WAAmB,IAAoB;AACnE,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;CAEzC,MAAM,OAAO,GACV,QACC;;;;;iDAMD,CACA,KAAK;CAER,MAAM,oBAAoB,GAAG,QAC3B;;;sBAID;CAED,MAAM,SAAS,KAAK,QAAQ,MAAM,EAAE,WAAW,SAAS;CACxD,MAAM,WAAW,KAAK,QAAQ,MAAM,EAAE,WAAW,SAAS;CAE1D,MAAM,QAAkB;EACtB;EACA;EACA,2DAA0C,IAAI,MAAM,EAAC,aAAa;EAClE;EACA;EACA;EACA;EACA;EACD;AAED,MAAK,MAAM,OAAO,QAAQ;EACxB,MAAM,OAAQ,kBAAkB,IAAI,IAAI,GAAG,CAAc,KAAK,MAAM,KAAK,EAAE,KAAK,IAAI,CAAC,KAAK,IAAI;EAC9F,MAAM,aAAa,QAAQ,IAAI,YAAY;EAE3C,MAAM,WAAW,aAAa,IAAI,UAAU;EAC5C,MAAM,iBAAiBC,qBAAmB,IAAI,kBAAkB,SAAS;EACzE,MAAM,UAAoB,EAAE;AAC5B,MAAI,SAAU,SAAQ,KAAK,QAAQ;AACnC,MAAI,eAAgB,SAAQ,KAAK,WAAW;EAC5C,MAAM,eAAe,QAAQ,SAAS,IAAI,QAAQ,KAAK,KAAK,GAAG;AAE/D,QAAM,KACJ,OAAO,IAAI,KAAK,IAAI,IAAI,aAAa,OAAO,IAAI,cAAc,KAAK,WAAW,KAAK,aAAa,KAAK,QAAQ,IAAI,IAClH;;AAGH,KAAI,SAAS,SAAS,GAAG;AACvB,QAAM,KACJ,IACA,wBACA,IACA,iCACA,gCACD;AACD,OAAK,MAAM,OAAO,UAAU;GAC1B,MAAM,OAAQ,kBAAkB,IAAI,IAAI,GAAG,CAAc,KAAK,MAAM,KAAK,EAAE,KAAK,IAAI,CAAC,KAAK,IAAI;AAC9F,SAAM,KACJ,MAAM,IAAI,aAAa,aAAa,IAAI,KAAK,SAAS,IAAI,cAAc,KAAK,QAAQ,IAAI,IAC1F;;;AAIL,OAAM,KACJ,IACA,OACA,IAAI,KAAK,OAAO,oBAAoB,OAAO,OAAO,WAAW,SAAS,OAAO,YAC9E;AAED,eAAc,KAAK,WAAW,YAAY,EAAE,MAAM,KAAK,KAAK,GAAG,MAAM,QAAQ;;;;;;AAO/E,SAAgB,mBAAmB,WAAmB,IAAsB;CAC1E,MAAM,YAAY,KAAK,WAAW,UAAU;AAC5C,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;CAEzC,MAAM,UAAU,GACb,QAAQ,0CAA0C,CAClD,KAAK;CAER,MAAM,oBAAoB,GAAG,QAC3B;;;;;;+CAOD;CAED,IAAI,UAAU;AACd,MAAK,MAAM,OAAO,SAAS;EACzB,MAAM,WAAW,kBAAkB,IAAI,IAAI,GAAG;AAC9C,MAAI,CAAC,SAAS,OAAQ;EAEtB,MAAM,QAAkB;GACtB,YAAY,IAAI;GAChB;GACA,2DAA0C,IAAI,MAAM,EAAC,aAAa;GAClE;GACA;GACA;GACA;GACA;GACD;AAED,OAAK,MAAM,KAAK,UAAU;GACxB,MAAM,OACJ,EAAE,WAAW,WACT,KAAK,EAAE,KAAK,IAAI,EAAE,aAAa,MAC/B,IAAI,EAAE,aAAa,gBAAgB,EAAE,KAAK;AAChD,SAAM,KAAK,KAAK,KAAK,KAAK,EAAE,OAAO,KAAK,EAAE,cAAc,KAAK,QAAQ,EAAE,YAAY,CAAC,IAAI;;AAG1F,QAAM,KAAK,IAAI,OAAO,IAAI,SAAS,OAAO,uBAAuB,IAAI,KAAK,KAAK;AAE/E,gBAAc,KAAK,WAAW,GAAG,IAAI,KAAK,KAAK,EAAE,MAAM,KAAK,KAAK,GAAG,MAAM,QAAQ;AAClF;;AAGF,QAAO;;;;;AAMT,SAAgB,mBAA2B;AACzC,QAAO,KAAK,SAAS,EAAE,QAAQ,iBAAiB;;;;;;AC1IlD,MAAa,sBAAsB;;;;;AAMnC,SAAgB,aAAa,KAAuB;CAClD,MAAM,UAAoB,EAAE;AAC5B,KAAI,CAAC,WAAW,IAAI,CAAE,QAAO;CAE7B,IAAI;AACJ,KAAI;AACF,YAAU,YAAY,IAAI;SACpB;AACN,SAAO;;AAGT,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,MAAM,WAAW,IAAI,CAAE;AAE3B,MAAI,UAAU,gBAAgB,sBAAsB,KAAK,MAAM,CAAE;EACjE,MAAM,OAAO,KAAK,KAAK,MAAM;EAC7B,IAAI;AACJ,MAAI;AACF,UAAO,UAAU,KAAK;UAChB;AACN;;AAEF,MAAI,KAAK,aAAa,EACpB;OAAI,UAAU,KAAK,MAAM,EAAE;IACzB,IAAI;AACJ,QAAI;AACF,oBAAe,YAAY,KAAK;YAC1B;AACN;;AAEF,SAAK,MAAM,SAAS,cAAc;KAChC,MAAM,YAAY,KAAK,MAAM,MAAM;AACnC,SAAI,UAAU,KAAK,MAAM,IAAI,WAAW,UAAU,EAAE;MAClD,IAAI;AACJ,UAAI;AACF,oBAAa,YAAY,UAAU;cAC7B;AACN;;AAEF,WAAK,MAAM,KAAK,WACd,KAAI,EAAE,SAAS,MAAM,IAAI,CAAC,EAAE,WAAW,IAAI,CACzC,SAAQ,KAAK,KAAK,WAAW,EAAE,CAAC;;;;aAMjC,MAAM,SAAS,MAAM,CAC9B,SAAQ,KAAK,KAAK;;AAGtB,QAAO;;;;;;AAOT,SAAgB,iBAAiB,UAA0B;CACzD,MAAM,YAAY,SAAS,MAAM,uBAAuB;AACxD,KAAI,UAAW,QAAO,GAAG,UAAU,GAAG,GAAG,UAAU;CAGnD,MAAM,aADW,SAAS,MAAM,IAAI,CAAC,KAAK,IAAI,IACnB,MAAM,iCAAiC;AAClE,KAAI,UAAW,QAAO,GAAG,UAAU,GAAG,GAAG,UAAU;AAEnD,QAAO;;;;;;AAOT,SAAgB,oBAAoB,UAAiC;CACnE,MAAM,eAA8B,EAAE;AAGtC,MAAK,MAAM,WAFM,CAAC,SAAS,WAAW,EAEN;EAC9B,MAAM,WAAW,KAAK,UAAU,QAAQ;AACxC,MAAI,CAAC,WAAW,SAAS,CAAE;EAE3B,IAAI;AACJ,MAAI;GACF,MAAM,OAAO,UAAU,SAAS;AAChC,OAAI,KAAK,gBAAgB,CACvB,WAAU,aAAa,SAAS;YACvB,KAAK,aAAa,CAC3B,WAAU;OAEV;UAEI;AACN;;EAGF,MAAM,QAAQ,aAAa,QAAQ;AACnC,OAAK,MAAM,WAAW,OAAO;GAC3B,MAAM,WAAW,QAAQ,MAAM,IAAI,CAAC,KAAK,IAAI;AAC7C,OAAI,CAAC,oBAAoB,KAAK,SAAS,CAAE;GAGzC,MAAM,eAAe,GAAG,QAAQ,GADZ,SAAS,SAAS,QAAQ;GAE9C,MAAM,iBAAiB,aAAa,QAAQ,SAAS,GAAG;AAExD,gBAAa,KAAK;IAChB;IACA;IACA;IACA,WAAW,iBAAiB,QAAQ;IACpC,UAAU,SAAS,QAAQ,SAAS,GAAG;IACxC,CAAC;;;AAIN,QAAO;;;;;;;ACjHT,SAAgB,eAAe,MAAsB;AACnD,QAAO,IAAI,KAAK;;;;;;AAOlB,SAAS,kBAAkB,UAA2B;CACpD,IAAI;AACJ,KAAI;AACF,YAAU,aAAa,UAAU,QAAQ;SACnC;AACN,SAAO;;CAIT,MAAM,UAAU,QAAQ,QAFL,mCAEyB,GAAG,CAAC,QAD7B,6CACiD,GAAG;AACvE,KAAI,YAAY,QAAS,QAAO;AAChC,KAAI;AACF,gBAAc,UAAU,SAAS,QAAQ;AACzC,SAAO;SACD;AACN,SAAO;;;;;;;;;;;AAYX,SAAS,kBAAkB,SAA0B;AASnD,QARa,QACV,QAAQ,6CAA6C,GAAG,CACxD,MAAM,KAAK,CACX,QAAQ,SAAS;EAChB,MAAM,IAAI,KAAK,MAAM;AACrB,SAAO,MAAM,MAAM,MAAM;GACzB,CACD,KAAK,GAAG,CACC,SAAS;;;;;;;AAQvB,SAAS,wBACP,UACA,MACA,aACS;CACT,IAAI;AACJ,KAAI;AACF,YAAU,aAAa,UAAU,QAAQ;SACnC;AACN,SAAO;;AAIT,KAAI,CAAC,kBAAkB,QAAQ,CAAE,QAAO;CAExC,MAAM,aAAa,eAAe,KAAK,CAAC,QAAQ,SAAS,GAAG;CAC5D,MAAM,WAAW,KAAK,WAAW;AACjC,KAAI,QAAQ,SAAS,SAAS,CAAE,QAAO;CAEvC,MAAM,SAAS,YAAY,WAAW,KAAK,YAAY;AACvD,KAAI;AACF,gBAAc,UAAU,UAAU,QAAQ,QAAQ;AAClD,SAAO;SACD;AACN,SAAO;;;;;;;;;;;;;;;;;AAsBX,SAAgB,oBACd,WACA,IACA,YAAY,GACJ;AACR,KAAI,CAAC,WAAW,UAAU,CAAE,QAAO;CAEnC,MAAM,WAAW,GACd,QACC;;;0BAID,CACA,KAAK;CAER,IAAI,UAAU;AAEd,MAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,WAAW,KAAK,WAAW,QAAQ,KAAK;AAC9C,MAAI,CAAC,WAAW,SAAS,CAAE;EAG3B,MAAM,eAAe,KAAK,UAAU,aAAa;AACjD,MAAI,WAAW,aAAa,CAC1B,KAAI;AAAE,cAAW,aAAa;UAAU;EAG1C,MAAM,eAAe,oBAAoB,SAAS;AAClD,MAAI,aAAa,SAAS,UAAW;AAGrC,eAAa,MAAM,GAAG,MAAM;AAC1B,OAAI,EAAE,cAAc,EAAE,UAAW,QAAO,EAAE,UAAU,cAAc,EAAE,UAAU;AAC9E,UAAO,EAAE,SAAS,cAAc,EAAE,SAAS;IAC3C;EAEF,MAAM,0BAAU,IAAI,KAAkC;AACtD,OAAK,MAAM,MAAM,cAAc;AAC7B,OAAI,CAAC,QAAQ,IAAI,GAAG,UAAU,CAAE,SAAQ,IAAI,GAAG,WAAW,EAAE,CAAC;AAC7D,WAAQ,IAAI,GAAG,UAAU,CAAE,KAAK,GAAG;;EAGrC,MAAM,YAAY,aAAa,IAAI,SAAS,MAAM,oBAAoB,GAAG,MAAM;EAC/E,MAAM,WAAW,aAAa,aAAa,SAAS,IAAI,SAAS,MAAM,oBAAoB,GAAG,MAAM;EAEpG,MAAM,QAAkB;GACtB,cAAc,QAAQ;GACtB;GACA,2DAA0C,IAAI,MAAM,EAAC,aAAa;GAClE;GACA;GACA;GACA,mBAAmB,aAAa;GAChC,qBAAqB,UAAU,MAAM;GACrC,gBAAgB,QAAQ,KAAK;GAC7B;GACA;GACA;GACD;AAED,OAAK,MAAM,CAAC,IAAI,UAAU,SAAS;AACjC,SAAM,KAAK,OAAO,MAAM,GAAG;AAC3B,QAAK,MAAM,MAAM,MACf,OAAM,KAAK,OAAO,GAAG,eAAe,GAAG,GAAG,SAAS,IAAI;AAEzD,SAAM,KAAK,GAAG;;AAGhB,QAAM,KAAK,aAAa,IAAI,IAAI,QAAQ,KAAK,YAAY,GAAG;EAE5D,MAAM,aAAa,KAAK,UAAU,eAAe,QAAQ,KAAK,CAAC;AAC/D,MAAI;AACF,iBAAc,YAAY,MAAM,KAAK,KAAK,EAAE,QAAQ;AACpD;UACM;AACN;;AAGF,OAAK,MAAM,MAAM,cAAc;AAC7B,qBAAkB,GAAG,QAAQ;AAC7B,2BAAwB,GAAG,SAAS,QAAQ,MAAM,QAAQ,aAAa;;;AAI3E,QAAO;;;;;;;;;;;AAgBT,SAAgB,eACd,IACmE;CACnE,MAAM,UAAU;EAAE,cAAc;EAAG,eAAe;EAAG,QAAQ,EAAE;EAAc;CAE7E,MAAM,WAAW,GACd,QACC;;;0BAID,CACA,KAAK;AAER,MAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,aAAuB,EAAE;EAG/B,MAAM,YAAY,KAAK,QAAQ,WAAW,QAAQ;EAClD,MAAM,MAAM,KAAK,QAAQ,WAAW,WAAW,QAAQ;EACvD,MAAM,WAAW,WAAW,UAAU,GAAG,YAAY,WAAW,IAAI,GAAG,MAAM;AAE7E,MAAI,SAAU,YAAW,KAAK,SAAS;AAEvC,MACE,QAAQ,oBACR,WAAW,QAAQ,iBAAiB,IACpC,QAAQ,qBAAqB,SAE7B,YAAW,KAAK,QAAQ,iBAAiB;AAG3C,OAAK,MAAM,OAAO,YAAY;GAC5B,MAAM,QAAQ,aAAa,IAAI;AAC/B,QAAK,MAAM,YAAY,OAAO;AAC5B,YAAQ;IACR,IAAI;AACJ,QAAI;AACF,eAAU,aAAa,UAAU,QAAQ;aAClC,GAAG;AACV,aAAQ,OAAO,KAAK,GAAG,SAAS,iBAAiB,IAAI;AACrD;;AAGF,QAAI,CAAC,QAAQ,SAAS,WAAW,CAAE;IAEnC,MAAM,UAAU,QAAQ,QACtB,oCACA,KACD;AAED,QAAI,YAAY,QAAS;IAEzB,MAAM,UAAU,QAAQ,QAAQ,wBAAwB,OAAO;AAE/D,QAAI;AACF,mBAAc,UAAU,SAAS,QAAQ;AACzC,aAAQ;aACD,GAAG;AACV,aAAQ,OAAO,KAAK,GAAG,SAAS,kBAAkB,IAAI;;;;;AAM9D,QAAO;;;;;;;;;ACrOT,SAAS,kBAAkB,KAAa,MAAuB;CAC7D,MAAM,WAAW,KAAK,KAAK,KAAK;AAChC,KAAI;AAEF,MAAI,CADS,UAAU,SAAS,CACtB,gBAAgB,CAAE,QAAO;AAEnC,SAAO,WADQ,aAAa,SAAS,CACZ;SACnB;AACN,SAAO;;;;;;AAOX,SAAS,iBAAiB,KAAa,MAAuB;AAC5D,KAAI;AACF,YAAU,KAAK,KAAK,KAAK,CAAC;AAC1B,SAAO;SACD;AACN,SAAO;;;;;;;;;;;;;AA6BX,SAAgB,YAAY,WAAmB,IAAiC;CAC9E,MAAM,SAA4B;EAChC;EACA,SAAS,EAAE;EACX,QAAQ,EAAE;EACV,UAAU,EAAE;EACZ,SAAS,EAAE;EACX,eAAe;EAChB;AAED,KAAI,CAAC,WAAW,UAAU,CACxB,QAAO;CAGT,MAAM,WAAW,GACd,QACC;8CAED,CACA,KAAK;AAER,QAAO,gBAAgB,SAAS;CAGhC,MAAM,iCAAiB,IAAI,KAAyB;AACpD,MAAK,MAAM,KAAK,SACd,gBAAe,IAAI,EAAE,MAAM,EAAE;CAI/B,MAAM,4BAAY,IAAI,KAAa;CACnC,IAAI,UAAoB,EAAE;AAC1B,KAAI;AACF,YAAU,YAAY,UAAU;SAC1B;AACN,SAAO;;AAGT,MAAK,MAAM,SAAS,SAAS;AAE3B,MAAI,MAAM,WAAW,IAAI,CAAE;EAE3B,MAAM,WAAW,KAAK,WAAW,MAAM;EACvC,IAAI;AACJ,MAAI;AACF,UAAO,UAAU,SAAS;UACpB;AACN;;EAGF,MAAM,UAAU,eAAe,IAAI,MAAM;AAEzC,MAAI,KAAK,aAAa,EAAE;AAEtB,OAAI,CAAC,SAAS;AAEZ,WAAO,SAAS,KAAK;KACnB,MAAM;KACN,UAAU;KACV,QAAQ;KACR,OAAO;KACP,OAAO;KACR,CAAC;AACF;;AAGF,aAAU,IAAI,MAAM;GAEpB,MAAM,UAAU,kBAAkB,UAAU,QAAQ;GACpD,MAAM,aAAa,kBAAkB,UAAU,WAAW;GAC1D,MAAM,cAAc,iBAAiB,UAAU,QAAQ;GACvD,MAAM,iBAAiB,iBAAiB,UAAU,WAAW;AAE7D,OAAI,WAAW,YAAY;IACzB,MAAM,QAAkB,EAAE;AAC1B,QAAI,QAAS,OAAM,KAAK,QAAQ;AAChC,QAAI,WAAY,OAAM,KAAK,WAAW;AACtC,WAAO,QAAQ,KAAK;KAClB,MAAM;KACN,UAAU;KACV,QAAQ;KACR,OAAO;KACP,OAAO,YAAY,MAAM,KAAK,KAAK;KACpC,CAAC;UACG;IAEL,MAAM,SAAmB,EAAE;AAC3B,QAAI,YAAa,QAAO,KAAK,QAAQ;AACrC,QAAI,eAAgB,QAAO,KAAK,WAAW;AAC3C,WAAO,OAAO,KAAK;KACjB,MAAM;KACN,UAAU;KACV,QAAQ;KACR,OAAO;KACP,OAAO,wBAAwB,OAAO,KAAK,KAAK,IAAI;KACrD,CAAC;;aAEK,KAAK,gBAAgB,EAAE;GAEhC,IAAI,SAAwB;AAC5B,OAAI;AACF,aAAS,aAAa,SAAS;WACzB;GAIR,MAAM,eAAe,WAAW,QAAQ,WAAW,OAAO;AAE1D,OAAI,CAAC,SAAS;AACZ,WAAO,SAAS,KAAK;KACnB,MAAM;KACN,UAAU;KACV;KACA,OAAO;KACP,OAAO;KACR,CAAC;AACF;;AAGF,aAAU,IAAI,MAAM;AAEpB,OAAI,aACF,QAAO,QAAQ,KAAK;IAClB,MAAM;IACN,UAAU;IACV;IACA,OAAO;IACP,OAAO;IACR,CAAC;OAEF,QAAO,OAAO,KAAK;IACjB,MAAM;IACN,UAAU;IACV;IACA,OAAO;IACP,OAAO,mBAAmB,UAAU;IACrC,CAAC;;;AAOR,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,UAAU,IAAI,QAAQ,KAAK,CAAE;EAEjC,MAAM,iBAAiB,KAAK,QAAQ,WAAW,QAAQ;EACvD,MAAM,WAAW,KAAK,QAAQ,WAAW,WAAW,QAAQ;EAC5D,MAAM,kBAAkB,WAAW,eAAe,IAAI,WAAW,SAAS;EAC1E,MAAM,iBACJ,QAAQ,qBAAqB,QAAQ,WAAW,QAAQ,iBAAiB;AAE3E,MAAI,mBAAmB,gBAAgB;GACrC,MAAM,UAAoB,EAAE;AAC5B,OAAI,gBAAiB,SAAQ,KAAK,QAAQ;AAC1C,OAAI,eAAgB,SAAQ,KAAK,WAAW;AAC5C,UAAO,QAAQ,KAAK;IAClB,MAAM,QAAQ;IACd,UAAU,KAAK,WAAW,QAAQ,KAAK;IACvC,QAAQ;IACR,OAAO;IACP,OAAO,gBAAgB,QAAQ,KAAK,KAAK,CAAC;IAC3C,CAAC;;;AAIN,QAAO;;;;;;;;;ACtOT,SAAS,aAAa,UAA2B;AAC/C,KAAI,SAAU,QAAO,WAAW,SAAS;AAEzC,KAAI;EACF,MAAM,MAAM,aAAaC,eAAa,QAAQ;EAC9C,MAAM,MAAM,KAAK,MAAM,IAAI;AAC3B,MAAI,OAAO,IAAI,sBAAsB,YAAY,IAAI,kBACnD,QAAO,WAAW,IAAI,kBAAkB;SAEpC;AAGR,QAAO,kBAAkB;;;;;AAM3B,SAAS,cAAc,WAAyB;CAC9C,IAAI,MAA+B,EAAE;AACrC,KAAI;AACF,QAAM,KAAK,MAAM,aAAaA,eAAa,QAAQ,CAAC;SAC9C;AAGR,KAAI,oBAAoB;AACxB,WAAU,QAAQA,cAAY,EAAE,EAAE,WAAW,MAAM,CAAC;AACpD,eAAcA,eAAa,KAAK,UAAU,KAAK,MAAM,EAAE,GAAG,MAAM,QAAQ;;AAO1E,SAAS,QACP,IACA,MACM;CACN,MAAM,YAAY,aAAa,KAAK,MAAM;CAC1C,MAAM,IAAI,KAAK,SAAS;AAExB,KAAI,CAAC,GAAG;AACN,UAAQ,KAAK;AACb,UAAQ,IAAI,OAAO,sBAAsB,CAAC;AAC1C,UAAQ,IAAIC,MAAI,YAAY,YAAY,CAAC;AACzC,UAAQ,KAAK;;AAIf,KAAI,CAAC,EAAG,SAAQ,OAAO,MAAM,wBAAwB;CACrD,MAAM,QAAQ,UAAU,WAAW,GAAG;AACtC,KAAI,CAAC,GAAG;AACN,UAAQ,OAAO,MACb,OAAOC,KAAG,YAAY,CAAC,YAAY,MAAM,QAAQ,YAAY,MAAM,QAAQ,YAAY,MAAM,QAAQ,UAAU,MAAM,QAAQ,IAC9H;AACD,MAAI,MAAM,OAAO,OACf,MAAK,MAAM,KAAK,MAAM,OACpB,SAAQ,IAAIC,OAAK,cAAc,IAAI,CAAC;;AAM1C,KAAI,CAAC,EAAG,SAAQ,OAAO,MAAM,wBAAwB;AACrD,eAAc,WAAW,GAAG;AAC5B,KAAI,CAAC,EAAG,SAAQ,IAAI,OAAOD,KAAG,SAAS,CAAC,sCAAsC;AAG9E,KAAI,CAAC,EAAG,SAAQ,OAAO,MAAM,8BAA8B;CAC3D,MAAM,aAAa,mBAAmB,WAAW,GAAG;AACpD,KAAI,CAAC,EAAG,SAAQ,IAAI,OAAOA,KAAG,UAAU,CAAC,MAAM,WAAW,kCAAkC;AAG5F,KAAI,CAAC,EAAG,SAAQ,OAAO,MAAM,2BAA2B;CACxD,MAAM,WAAW,eAAe,GAAG;AACnC,KAAI,CAAC,GAAG;AACN,UAAQ,IACN,OAAOA,KAAG,QAAQ,CAAC,QAAQ,SAAS,cAAc,4BAA4B,SAAS,aAAa,aACrG;AACD,MAAI,SAAS,OAAO,OAClB,MAAK,MAAM,KAAK,SAAS,OACvB,SAAQ,IAAIC,OAAK,cAAc,IAAI,CAAC;;AAM1C,KAAI,CAAC,EAAG,SAAQ,OAAO,MAAM,+BAA+B;CAC5D,MAAM,cAAc,oBAAoB,WAAW,GAAG;AACtD,KAAI,CAAC,EAAG,SAAQ,IAAI,OAAOD,KAAG,WAAW,CAAC,KAAK,YAAY,kCAAkC;AAG7F,eAAc,UAAU;AAExB,KAAI,CAAC,GAAG;AACN,UAAQ,KAAK;AACb,UAAQ,IAAIA,KAAG,2BAA2B,YAAY,CAAC;AACvD,UAAQ,KAAK;;;AAIjB,SAASE,YAAU,IAAc,MAAgC;CAC/D,MAAM,YAAY,aAAa,KAAK,MAAM;CAC1C,MAAM,SAAS,YAAY,WAAW,GAAG;AAEzC,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,8BAA8B,CAAC;AAClD,SAAQ,IAAIH,MAAI,YAAY,YAAY,CAAC;AACzC,SAAQ,KAAK;AAEb,KAAI,CAAC,WAAW,UAAU,EAAE;AAC1B,UAAQ,IAAIE,OAAK,2DAA2D,CAAC;AAC7E,UAAQ,KAAK;AACb;;CAIF,MAAM,eAAe,OAAO,QAAQ;CACpC,MAAM,cAAc,OAAO,OAAO;CAClC,MAAM,gBAAgB,OAAO,SAAS;CACtC,MAAM,eAAe,OAAO,QAAQ;AAEpC,SAAQ,IACN,KAAK,MAAM,MAAM,WAAW,CAAC,GAAG,aAAa,KACxC,MAAM,IAAI,UAAU,CAAC,GAAG,YAAY,KACpC,MAAM,OAAO,YAAY,CAAC,GAAG,cAAc,KAC3C,MAAM,KAAK,WAAW,CAAC,GAAG,eAChC;AACD,SAAQ,KAAK;AAEb,KAAI,OAAO,QAAQ,UAAU,OAAO,QAAQ,UAAU,IAAI;AACxD,UAAQ,IAAIE,OAAK,sBAAsB,CAAC;AACxC,OAAK,MAAM,KAAK,OAAO,QACrB,SAAQ,IAAI,OAAO,MAAM,MAAM,IAAI,CAAC,GAAGA,OAAK,EAAE,KAAK,CAAC,IAAIJ,MAAI,QAAQ,EAAE,UAAU,IAAI,GAAG;AAEzF,UAAQ,KAAK;YACJ,OAAO,QAAQ,SAAS,IAAI;AACrC,UAAQ,IAAII,OAAK,KAAK,OAAO,QAAQ,OAAO,8BAA8B,CAAC;AAC3E,UAAQ,KAAK;;AAGf,KAAI,OAAO,OAAO,QAAQ;AACxB,UAAQ,IAAI,IAAI,sCAAsC,CAAC;AACvD,OAAK,MAAM,KAAK,OAAO,OACrB,SAAQ,IAAI,OAAO,MAAM,IAAI,IAAI,CAAC,GAAGA,OAAK,EAAE,KAAK,CAAC,IAAIJ,MAAI,EAAE,MAAM,GAAG;AAEvE,UAAQ,IAAIA,MAAI,2BAA2B,CAAC;AAC5C,UAAQ,KAAK;;AAGf,KAAI,OAAO,SAAS,QAAQ;AAC1B,UAAQ,IAAIE,OAAK,6CAA6C,CAAC;AAC/D,OAAK,MAAM,KAAK,OAAO,SACrB,SAAQ,IAAI,OAAO,MAAM,OAAO,IAAI,CAAC,GAAGE,OAAK,EAAE,KAAK,CAAC,IAAIJ,MAAI,EAAE,MAAM,GAAG;AAE1E,UAAQ,KAAK;;AAGf,KAAI,OAAO,QAAQ,QAAQ;AACzB,UAAQ,IAAIE,OAAK,+CAA+C,CAAC;AACjE,OAAK,MAAM,KAAK,OAAO,QACrB,SAAQ,IAAI,OAAO,MAAM,KAAK,IAAI,CAAC,GAAGE,OAAK,EAAE,KAAK,CAAC,IAAIJ,MAAI,EAAE,MAAM,GAAG;AAExE,UAAQ,KAAK;;AAGf,KAAI,gBAAgB,KAAK,kBAAkB,KAAK,iBAAiB,GAAG;AAClE,UAAQ,IAAIC,KAAG,sBAAsB,CAAC;AACtC,UAAQ,KAAK;;;AAIjB,SAAS,QAAQ,MAAgC;CAC/C,MAAM,YAAY,aAAa,KAAK,MAAM;CAE1C,MAAM,QAAQ,UAAU,MAAM,IAAI,CAAC,OAAO,QAAQ;CAClD,MAAM,YAAY,MAAM,MAAM,SAAS,MAAM;AAE7C,KAAI,CAAC,WAAW,UAAU,EAAE;AAC1B,UAAQ,MAAM,IAAI,uBAAuB,YAAY,CAAC;AACtD,UAAQ,MAAMD,MAAI,2BAA2B,CAAC;AAC9C,UAAQ,WAAW;AACnB;;CAGF,MAAM,MAAM,yBAAyB,mBAAmB,UAAU;AAClE,SAAQ,IAAIA,MAAI,cAAc,MAAM,CAAC;AACrC,KAAI;AACF,WAAS,SAAS,IAAI,IAAI,EAAE,OAAO,UAAU,CAAC;AAC9C,UAAQ,IAAIC,KAAG,mBAAmB,UAAU,gBAAgB,CAAC;UACtD,GAAG;AACV,UAAQ,MAAM,IAAI,8BAA8B,IAAI,CAAC;AACrD,UAAQ,MAAMD,MAAI,0EAA0E,CAAC;AAC7F,UAAQ,WAAW;AACnB;;;AAQJ,SAAgB,yBACd,aACA,OACM;AAEN,aACG,QAAQ,OAAO,CACf,YAAY,0EAA0E,CACtF,OAAO,kBAAkB,uDAAuD,CAChF,OAAO,WAAW,8CAA8C,CAChE,QAAQ,SAA8C;AACrD,UAAQ,OAAO,EAAE,KAAK;GACtB;AAGJ,aACG,QAAQ,SAAS,CACjB,YAAY,qEAAqE,CACjF,OAAO,kBAAkB,sBAAsB,CAC/C,QAAQ,SAA6B;AACpC,cAAU,OAAO,EAAE,KAAK;GACxB;AAGJ,aACG,QAAQ,OAAO,CACf,YAAY,qCAAqC,CACjD,OAAO,kBAAkB,sBAAsB,CAC/C,QAAQ,SAA6B;AACpC,UAAQ,KAAK;GACb;;;;;;ACvQN,SAAgB,UAAU,GAAW,QAAQ,GAAW;AAEtD,QADiB,EAAE,MAAM,IAAI,CAAC,OAAO,QAAQ,CAC7B,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI;;AAIzC,IAAI,SAA0B;;AAG9B,SAAgB,WAAqB;AACnC,KAAI,CAAC,OACH,KAAI;AACF,WAAS,gBAAgB;UAClB,GAAG;AACV,UAAQ,MAAM,IAAI,qCAAqC,IAAI,CAAC;AAC5D,UAAQ,KAAK,EAAE;;AAGnB,QAAO;;;;;AClBT,eAAe,WACb,MACA,MACe;CACf,MAAM,QAAQ,SAAS,KAAK,SAAS,KAAK,GAAG;CAC7C,MAAM,YAAa,KAAK,aAAa;CACrC,MAAM,OAAQ,KAAK,QAAQ;CAE3B,MAAM,EAAE,kBAAkB,MAAM,OAAO;CAGvC,MAAM,SAAS,cAFJ,UAAU,EAEY;EAAE,WAAW;EAAM;EAAO;EAAW;EAAM,CAAC;AAE7E,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,uBAAuB,CAAC;AAC3C,SAAQ,IAAIK,MAAI,oBAAoB,OAAO,CAAC;AAC5C,SAAQ,IAAIA,MAAI,YAAY,MAAM,eAAe,UAAU,UAAU,OAAO,CAAC;AAC7E,SAAQ,KAAK;AAEb,KAAI,OAAO,MAAM,WAAW,GAAG;AAC7B,UAAQ,IAAIC,OAAK,kFAAkF,CAAC;AACpG,UAAQ,KAAK;AACb;;AAGF,SAAQ,IAAI,KAAK,MAAM,KAAK,IAAI,CAAC,GAAGC,OAAK,UAAU,OAAO,KAAK,CAAC,CAAC,IAAIF,MAAI,SAAS,GAAG;CAErF,MAAM,0BAAU,IAAI,KAAkC;AACtD,MAAK,MAAM,QAAQ,OAAO,OAAO;EAC/B,MAAM,OAAO,QAAQ,IAAI,KAAK,MAAM,IAAI,EAAE;AAC1C,OAAK,KAAK,KAAK;AACf,UAAQ,IAAI,KAAK,OAAO,KAAK;;AAG/B,MAAK,IAAI,IAAI,GAAG,KAAK,OAAO,KAAK;EAC/B,MAAM,QAAQ,QAAQ,IAAI,EAAE,IAAI,EAAE;AAClC,MAAI,MAAM,WAAW,EAAG;AAExB,UAAQ,KAAK;AACb,UAAQ,IAAIA,MAAI,KAAK,KAAK,OAAO,IAAI,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC;AACtD,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,SAAS,KAAK,OAAO,EAAE;GAC7B,MAAM,cAAc,OAAO,gBAAgB,SAAS,KAAK,KAAK;GAC9D,MAAM,YAAY,KAAK,aAAa,eAAe,MAAM,OAAO,MAAM;GACtE,MAAM,aAAa,cAAc,MAAM,OAAO,eAAe,GAAG;GAChE,MAAM,QAAQ,KAAK,SAAS,UAAU,KAAK,KAAK;GAChD,MAAM,QAAQA,MAAI,MAAM,KAAK,QAAQ,OAAO,KAAK,WAAW;AAC5D,WAAQ,IACN,KAAK,SAAS,UAAU,IAAI,CAAC,GAAGE,OAAK,MAAM,GAAG,WAAW,IAAI,MAAM,IAAIF,MAAI,UAAU,KAAK,SAAS,CAAC,GACrG;;;AAIL,SAAQ,KAAK;CACb,MAAM,cAAc,GAAG,OAAO,MAAM,OAAO,WAAW,OAAO,MAAM,QAAO,MAAK,EAAE,SAAS,aAAa,CAAC,OAAO,eAAe,OAAO,MAAM,QAAO,MAAK,EAAE,SAAS,cAAc,CAAC,OAAO;AACxL,SAAQ,IAAIA,MAAI,KAAK,cAAc,CAAC;AACpC,KAAI,OAAO,gBAAgB,SAAS,EAClC,SAAQ,IAAIG,KAAG,KAAK,OAAO,gBAAgB,OAAO,2BAA2B,CAAC;AAEhF,KAAI,OAAO,gBACT,SAAQ,IAAIF,OAAK,uDAAuD,CAAC;AAE3E,SAAQ,KAAK;;AAGf,SAAgB,uBAAuB,QAAuB;AAC5D,QACG,QAAQ,iBAAiB,CACzB,YAAY,0CAA0C,CACtD,OAAO,eAAe,kCAAkC,IAAI,CAC5D,OAAO,mBAAmB,6CAA6C,OAAO,CAC9E,OAAO,cAAc,6CAA6C,MAAM,CACxE,OAAO,OAAO,MAAc,SAAgE;AAC3F,MAAI;AACF,SAAM,WAAW,MAAM,KAAK;WACrB,GAAG;AACV,WAAQ,MAAM,IAAI,YAAY,IAAI,CAAC;AACnC,WAAQ,WAAW;AACnB;;GAEF;;;;;AChFN,eAAe,UAAU,MAKP;CAChB,MAAM,QAAS,KAAK,SAAS;CAC7B,MAAM,cAAc,KAAK;CACzB,MAAM,aAAa,SAAS,KAAK,QAAQ,MAAM,GAAG;CAClD,MAAM,eAAe,KAAK,UACrB,KAAK,QAAQ,MAAM,IAAI,CAAC,KAAI,MAAK,EAAE,MAAM,CAAC,GAC3C;CAEJ,MAAM,EAAE,iBAAiB,MAAM,OAAO;CAGtC,MAAM,SAAS,aAFJ,UAAU,EAEW;EAAE;EAAO;EAAa;EAAY,SAAS;EAAc,CAAC;AAE1F,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,sBAAsB,CAAC;AAC1C,SAAQ,IAAIG,MAAI,YAAY,QAAQ,UAAU,YAAY,WAAW,eAAe,aAAa,KAAK,UAAU,WAAW,WAAW,eAAe,KAAK,CAAC;AAC3J,SAAQ,KAAK;CAEb,MAAM,QAAQ,OAAO;CACrB,MAAM,aAAa,SAAS,KAAK,MAAM,QAAQ,SAAS,KAAK,MAAM,SAAS,MAAM;CAClF,MAAM,WAAW;CACjB,MAAM,SAAS,KAAK,MAAO,QAAQ,MAAO,SAAS;CACnD,MAAM,MAAM,WAAW,IAAI,OAAO,OAAO,CAAC,GAAGA,MAAI,IAAI,OAAO,WAAW,OAAO,CAAC;AAC/E,SAAQ,IAAI,mBAAmB,WAAWC,OAAK,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,GAAG;AAC5E,SAAQ,KAAK;AACb,SAAQ,IAAID,MAAI,YAAY,OAAO,WAAW,YAAY,OAAO,aAAa,CAAC;AAC/E,SAAQ,KAAK;AAEb,KAAI,OAAO,UAAU,WAAW,EAC9B,SAAQ,IAAIE,KAAG,6BAA6B,CAAC;MACxC;AACL,UAAQ,IAAIC,OAAK,yBAAyB,OAAO,UAAU,SAAS,CAAC;EACrE,MAAM,UAAU,OAAO,UAAU,MAAM,GAAG,GAAG;AAC7C,OAAK,MAAM,MAAM,QACf,SAAQ,IAAI,OAAO,MAAM,IAAI,IAAI,CAAC,GAAGH,MAAI,UAAU,GAAG,WAAW,CAAC,CAAC,KAAKC,OAAK,GAAG,UAAU,CAAC,GAAGD,MAAI,SAAS,GAAG,WAAW,GAAG,GAAG;AAEjI,MAAI,OAAO,UAAU,SAAS,GAC5B,SAAQ,IAAIA,MAAI,eAAe,OAAO,UAAU,SAAS,GAAG,OAAO,CAAC;AAEtE,UAAQ,KAAK;;AAGf,KAAI,OAAO,QAAQ,WAAW,EAC5B,SAAQ,IAAIE,KAAG,6BAA6B,CAAC;MACxC;AACL,UAAQ,IAAIC,OAAK,yBAAyB,OAAO,QAAQ,SAAS,CAAC;EACnE,MAAM,UAAU,OAAO,QAAQ,MAAM,GAAG,GAAG;AAC3C,OAAK,MAAM,KAAK,QACd,SAAQ,IAAI,OAAO,MAAM,OAAO,IAAI,CAAC,GAAGH,MAAI,UAAU,EAAE,CAAC,GAAG;AAE9D,MAAI,OAAO,QAAQ,SAAS,GAC1B,SAAQ,IAAIA,MAAI,eAAe,OAAO,QAAQ,SAAS,GAAG,OAAO,CAAC;AAEpE,UAAQ,KAAK;;AAGf,KAAI,OAAO,wBAAwB,EACjC,SAAQ,IAAIE,KAAG,+CAA+C,CAAC;KAE/D,SAAQ,IAAIC,OAAK,4BAA4B,OAAO,uBAAuB,CAAC;AAG9E,KAAI,OAAO,gBAAgB,WAAW,EACpC,SAAQ,IAAID,KAAG,6BAA6B,CAAC;MACxC;AACL,UAAQ,IAAIC,OAAK,yBAAyB,OAAO,gBAAgB,OAAO,uBAAuB,CAAC;EAChG,MAAM,UAAU,OAAO,gBAAgB,MAAM,GAAG,EAAE;AAClD,OAAK,MAAM,MAAM,QACf,SAAQ,IAAI,OAAO,MAAM,IAAI,IAAI,CAAC,GAAGH,MAAI,UAAU,GAAG,CAAC,GAAG;AAE5D,MAAI,OAAO,gBAAgB,SAAS,EAClC,SAAQ,IAAIA,MAAI,eAAe,OAAO,gBAAgB,SAAS,EAAE,OAAO,CAAC;;AAI7E,SAAQ,KAAK;;AAGf,SAAgB,sBAAsB,QAAuB;AAC3D,QACG,QAAQ,SAAS,CACjB,YAAY,mEAAmE,CAC/E,OAAO,eAAe,kCAAkC,OAAO,CAC/D,OAAO,oBAAoB,iDAAiD,CAC5E,OAAO,cAAc,sDAAsD,KAAK,CAChF,OAAO,qBAAqB,2EAA2E,CACvG,OAAO,OAAO,SAAgF;AAC7F,MAAI;AACF,SAAM,UAAU,KAAK;WACd,GAAG;AACV,WAAQ,MAAM,IAAI,YAAY,IAAI,CAAC;AACnC,WAAQ,WAAW;AACnB;;GAEF;;;;;ACnGN,eAAe,YACb,MACA,MACe;AACf,KAAI,CAAC,KAAK,gBAAgB;AACxB,UAAQ,MAAM,IAAI,mCAAmC,CAAC;AACtD,UAAQ,WAAW;AACnB;;CAGF,MAAM,iBAAiB,SAAS,KAAK,gBAAgB,GAAG;CACxD,MAAM,QAAQ,SAAS,KAAK,SAAS,MAAM,GAAG;CAC9C,MAAM,gBAAgB,WAAW,KAAK,iBAAiB,MAAM;CAC7D,MAAM,mBAAmB,SAAS,KAAK,eAAe,KAAK,GAAG;CAE9D,MAAM,EAAE,mBAAmB,MAAM,OAAO;CACxC,MAAM,KAAK,UAAU;AAErB,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,wBAAwB,CAAC;AAC5C,SAAQ,IAAII,MAAI,qBAAqB,OAAO,CAAC;AAC7C,SAAQ,OAAO,MAAMA,MAAI,8CAA8C,CAAC;CAExE,MAAM,UAAU,MAAM,eAAe,IAAI;EACvC,eAAe;EACf;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,QAAQ,WAAW,GAAG;AACxB,UAAQ,IAAIC,OAAK,sFAAsF,CAAC;AACxG,UAAQ,KAAK;AACb;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAIC,OAAK,WAAW,QAAQ,OAAO,4BAA4B,CAAC;AACxE,SAAQ,KAAK;AAEb,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,IAAI,QAAQ;EAClB,MAAM,QAAQ,EAAE,SAAS,UAAU,EAAE,KAAK;EAC1C,MAAM,cAAc,KAAK,MAAM,EAAE,gBAAgB,GAAG;AACpD,UAAQ,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,IAAIA,OAAK,MAAM,GAAG;AAC9E,UAAQ,IAAI,SAASF,MAAI,YAAY,CAAC,GAAG,MAAM,QAAQ,EAAE,cAAc,QAAQ,EAAE,CAAC,CAAC,GAAG,IAAI,OAAO,YAAY,GAAG,IAAI,OAAO,KAAK,YAAY,GAAG;AAC/I,UAAQ,IAAI,SAASA,MAAI,UAAU,CAAC,GAAG,EAAE,iBAAiB,QAAQ,EAAE,CAAC,KAAKA,MAAI,kBAAkB,CAAC,GAAG,EAAE,gBAAgB;AACtH,MAAI,EAAE,cACJ,SAAQ,IAAI,SAASA,MAAI,WAAW,CAAC,GAAG,EAAE,cAAc,MAAM,GAAG,IAAI,GAAG;AAE1E,UAAQ,KAAK;;;AAIjB,SAAgB,wBAAwB,QAAuB;AAC7D,QACG,QAAQ,kBAAkB,CAC1B,YAAY,6EAA6E,CACzF,eAAe,0BAA0B,gDAAgD,CACzF,OAAO,eAAe,mBAAmB,KAAK,CAC9C,OAAO,wBAAwB,mCAAmC,MAAM,CACxE,OAAO,sBAAsB,0BAA0B,IAAI,CAC3D,OAAO,OAAO,MAAc,SAAoG;AAC/H,MAAI;AACF,SAAM,YAAY,MAAM,KAAK;WACtB,GAAG;AACV,WAAQ,MAAM,IAAI,YAAY,IAAI,CAAC;AACnC,WAAQ,WAAW;AACnB;;GAEF;;;;;ACvEN,eAAe,WACb,MACA,MACe;AACf,KAAI,CAAC,KAAK,gBAAgB;AACxB,UAAQ,MAAM,IAAI,mCAAmC,CAAC;AACtD,UAAQ,WAAW;AACnB;;CAGF,MAAM,iBAAiB,SAAS,KAAK,gBAAgB,GAAG;CACxD,MAAM,QAAQ,SAAS,KAAK,SAAS,KAAK,GAAG;CAC7C,MAAM,gBAAgB,KAAK,kBAAkB;CAE7C,MAAM,EAAE,kBAAkB,MAAM,OAAO;CACvC,MAAM,KAAK,UAAU;AAErB,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,uBAAuB,CAAC;AAC3C,SAAQ,IAAIG,MAAI,WAAW,OAAO,CAAC;AACnC,SAAQ,OAAO,MAAMA,MAAI,+BAA+B,CAAC;CAEzD,MAAM,cAAc,MAAM,cAAc,IAAI;EAC1C,UAAU;EACV;EACA;EACA;EACD,CAAC;AAEF,KAAI,YAAY,WAAW,GAAG;AAC5B,UAAQ,IAAIC,OAAK,kEAAkE,CAAC;AACpF,UAAQ,KAAK;AACb;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAIC,OAAK,KAAK,YAAY,OAAO,2BAA2B,CAAC;AACrE,SAAQ,KAAK;AAEb,MAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;EAC3C,MAAM,IAAI,YAAY;EACtB,MAAM,QAAQ,EAAE,SAAS,UAAU,EAAE,KAAK;AAC1C,UAAQ,IAAI,KAAK,MAAM,MAAM,OAAO,IAAI,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,IAAIA,OAAK,MAAM,GAAG;AAC/E,UAAQ,IAAI,SAASF,MAAI,SAAS,CAAC,GAAG,MAAM,MAAM,EAAE,MAAM,QAAQ,EAAE,CAAC,CAAC,IAAIA,MAAI,YAAY,CAAC,GAAG,EAAE,cAAc,QAAQ,EAAE,CAAC,IAAIA,MAAI,OAAO,CAAC,GAAG,EAAE,SAAS,QAAQ,EAAE,CAAC,IAAIA,MAAI,YAAY,CAAC,GAAG,EAAE,cAAc,QAAQ,EAAE,GAAG;AACvN,UAAQ,IAAI,SAASA,MAAI,UAAU,CAAC,GAAG,EAAE,SAAS;AAClD,UAAQ,IAAI,SAASA,MAAI,YAAY,CAAC,GAAG,MAAM,KAAK,EAAE,kBAAkB,GAAG;AAC3E,UAAQ,KAAK;;;AAIjB,SAAgB,uBAAuB,QAAuB;AAC5D,QACG,QAAQ,iBAAiB,CACzB,YAAY,8CAA8C,CAC1D,eAAe,0BAA0B,gDAAgD,CACzF,OAAO,eAAe,uBAAuB,IAAI,CACjD,OAAO,uBAAuB,6CAA6C,CAC3E,OAAO,OAAO,MAAc,SAA+E;AAC1G,MAAI;AACF,SAAM,WAAW,MAAM,KAAK;WACrB,GAAG;AACV,WAAQ,MAAM,IAAI,YAAY,IAAI,CAAC;AACnC,WAAQ,WAAW;AACnB;;GAEF;;;;;ACjEN,eAAe,YACb,UACA,MACe;AACf,KAAI,CAAC,KAAK,gBAAgB;AACxB,UAAQ,MAAM,IAAI,mCAAmC,CAAC;AACtD,UAAQ,WAAW;AACnB;;CAGF,MAAM,iBAAiB,SAAS,KAAK,gBAAgB,GAAG;CACxD,MAAM,QAAQ,SAAS,KAAK,SAAS,KAAK,GAAG;CAC7C,MAAM,QAAQ,SAAS,KAAK,SAAS,MAAM,GAAG;CAE9C,MAAM,EAAE,mBAAmB,MAAM,OAAO;CACxC,MAAM,KAAK,UAAU;AAErB,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,wBAAwB,CAAC;AAC5C,SAAQ,IAAIG,MAAI,gBAAgB,SAAS,GAAG,CAAC;AAC7C,SAAQ,OAAO,MAAMA,MAAI,4CAA4C,CAAC;CAEtE,MAAM,SAAS,MAAM,eAAe,IAAI;EAAE;EAAU;EAAgB;EAAO;EAAO,CAAC;AAEnF,KAAI,OAAO,cAAc,WAAW,GAAG;AACrC,UAAQ,IAAIC,OAAK,2DAA2D,CAAC;AAC7E,UAAQ,KAAK;AACb;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAIC,OAAK,KAAK,OAAO,cAAc,OAAO,yBAAyB,OAAO,QAAQ,OAAO,aAAa,CAAC;AAC/G,SAAQ,IAAIF,MAAI,cAAc,OAAO,QAAQ,KAAK,KAAK,GAAG,CAAC;AAC3D,SAAQ,KAAK;AAEb,MAAK,MAAM,QAAQ,OAAO,eAAe;EACvC,MAAM,QAAQ,KAAK,SAAS,UAAU,KAAK,KAAK;AAChD,UAAQ,IAAI,KAAK,MAAM,KAAK,IAAI,CAAC,GAAGE,OAAK,MAAM,CAAC,IAAIF,MAAI,IAAI,KAAK,OAAO,YAAY,KAAK,MAAM,QAAQ,EAAE,GAAG,GAAG;AAC/G,MAAI,KAAK,QACP,SAAQ,IAAI,OAAOA,MAAI,KAAK,QAAQ,MAAM,GAAG,IAAI,CAAC,GAAG;AAEvD,UAAQ,KAAK;;AAGf,KAAI,OAAO,YAAY,SAAS,GAAG;AACjC,UAAQ,IAAIE,OAAK,8BAA8B,CAAC;AAChD,OAAK,MAAM,QAAQ,OAAO,YAAY,MAAM,GAAG,GAAG,CAChD,SAAQ,IACN,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAGF,MAAI,KAAK,WAAW,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,GAAGA,MAAI,KAAK,SAAS,CAAC,IACrFA,MAAI,UAAU,KAAK,SAAS,CAAC,CAAC,KAAKA,MAAI,UAAU,KAAK,OAAO,CAAC,CAAC,IAC/DA,MAAI,aAAa,KAAK,WAAW,GACrC;AAEH,UAAQ,KAAK;;AAGf,SAAQ,IAAIE,OAAK,2CAA2C,CAAC;AAC7D,SAAQ,KAAK;CACb,MAAM,cAAc,OAAO,gBAAgB,MAAM,KAAK;AACtD,MAAK,MAAM,QAAQ,YACjB,SAAQ,IAAI,KAAKF,MAAI,KAAK,GAAG;AAE/B,SAAQ,KAAK;;AAGf,SAAgB,wBAAwB,QAAuB;AAC7D,QACG,QAAQ,sBAAsB,CAC9B,YAAY,0EAA0E,CACtF,eAAe,0BAA0B,gDAAgD,CACzF,OAAO,eAAe,8CAA8C,IAAI,CACxE,OAAO,eAAe,qCAAqC,KAAK,CAChE,OAAO,OAAO,UAAkB,SAAsE;AACrG,MAAI;AACF,SAAM,YAAY,UAAU,KAAK;WAC1B,GAAG;AACV,WAAQ,MAAM,IAAI,YAAY,IAAI,CAAC;AACnC,WAAQ,WAAW;AACnB;;GAEF;;;;;AChFN,eAAe,UAAU,MAMP;AAChB,KAAI,CAAC,KAAK,gBAAgB;AACxB,UAAQ,MAAM,IAAI,mCAAmC,CAAC;AACtD,UAAQ,WAAW;AACnB;;CAGF,MAAM,iBAAiB,SAAS,KAAK,gBAAgB,GAAG;CACxD,MAAM,eAAe,SAAS,KAAK,QAAQ,MAAM,GAAG;CACpD,MAAM,iBAAiB,SAAS,KAAK,WAAW,KAAK,GAAG;CACxD,MAAM,YAAY,SAAS,KAAK,aAAa,MAAM,GAAG;CACtD,MAAM,sBAAsB,WAAW,KAAK,aAAa,OAAO;CAEhE,MAAM,EAAE,iBAAiB,MAAM,OAAO;CACtC,MAAM,KAAK,UAAU;AAErB,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,sBAAsB,CAAC;AAC1C,SAAQ,IAAIG,MAAI,eAAe,aAAa,kBAAkB,eAAe,eAAe,sBAAsB,CAAC;AACnH,SAAQ,OAAO,MAAMA,MAAI,mCAAmC,CAAC;CAE7D,MAAM,SAAS,MAAM,aAAa,IAAI;EACpC;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,OAAO,OAAO,WAAW,GAAG;AAC9B,UAAQ,IAAIC,OAAK,oCAAoC,aAAa,yCAAyC,CAAC;AAC5G,UAAQ,KAAK;AACb;;CAGF,MAAM,WAAW,IAAI,KAAK,OAAO,WAAW,KAAK,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG;CAC5E,MAAM,SAAS,IAAI,KAAK,OAAO,WAAW,GAAG,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG;AAExE,SAAQ,KAAK;AACb,SAAQ,IAAIC,OAAK,KAAK,OAAO,OAAO,OAAO,iBAAiB,OAAO,mBAAmB,WAAW,SAAS,KAAK,OAAO,IAAI,CAAC;AAC3H,SAAQ,KAAK;AAEb,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,OAAO,QAAQ,KAAK;EAC7C,MAAM,UAAU,OAAO,OAAO;EAC9B,MAAM,eAAe,KAAK,MAAM,QAAQ,kBAAkB,GAAG;EAC7D,MAAM,kBAAkB,QAAQ,mBAAmB,MAAM,OAAO,yBAAyB,GAAG;AAE5F,UAAQ,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,IAAIA,OAAK,QAAQ,MAAM,GAAG,kBAAkB;AACxG,UAAQ,IACN,SAASF,MAAI,SAAS,CAAC,GAAG,QAAQ,KAAK,IACpCA,MAAI,aAAa,CAAC,GAAG,IAAI,OAAO,aAAa,GAAG,IAAI,OAAO,KAAK,aAAa,CAAC,GAAG,QAAQ,gBAAgB,QAAQ,EAAE,CAAC,IACpHA,MAAI,UAAU,CAAC,GAAG,KAAK,MAAM,QAAQ,cAAc,IAAI,CAAC,GAC5D;EAED,MAAM,UAAU,QAAQ,MAAM,MAAM,GAAG,EAAE;AACzC,OAAK,MAAM,QAAQ,SAAS;GAC1B,MAAM,QAAQ,KAAK,SAAS,UAAU,KAAK,KAAK;AAChD,WAAQ,IAAI,SAASA,MAAI,IAAI,CAAC,GAAG,QAAQ;;AAE3C,MAAI,QAAQ,MAAM,SAAS,EACzB,SAAQ,IAAIA,MAAI,iBAAiB,QAAQ,MAAM,SAAS,EAAE,OAAO,CAAC;AAEpE,UAAQ,KAAK;;;AAIjB,SAAgB,sBAAsB,QAAuB;AAC3D,QACG,QAAQ,SAAS,CACjB,YAAY,0DAA0D,CACtE,eAAe,0BAA0B,gDAAgD,CACzF,OAAO,cAAc,4BAA4B,KAAK,CACtD,OAAO,kBAAkB,6BAA6B,IAAI,CAC1D,OAAO,oBAAoB,4BAA4B,KAAK,CAC5D,OAAO,mBAAmB,6CAA6C,OAAO,CAC9E,OAAO,OAAO,SAA+G;AAC5H,MAAI;AACF,SAAM,UAAU,KAAK;WACd,GAAG;AACV,WAAQ,MAAM,IAAI,YAAY,IAAI,CAAC;AACnC,WAAQ,WAAW;AACnB;;GAEF;;;;;ACrFN,SAAgB,uBACd,QACA,QACM;AACN,wBAAuB,OAAO;AAC9B,uBAAsB,OAAO;AAC7B,yBAAwB,OAAO;AAC/B,wBAAuB,OAAO;AAC9B,yBAAwB,OAAO;AAC/B,uBAAsB,OAAO;;;;;ACF/B,SAASG,UAAQ,QAAgB,QAAmD;AAClF,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,UAAU;EACd,MAAM,UAAU,OAAmB;AAAE,OAAI,CAAC,SAAS;AAAE,cAAU;AAAM,QAAI;;;EAEzE,MAAM,SAAS,iBAAiB,eAAe,QAAQ;AACrD,UAAO,MAAM,KAAK,UAAU;IAAE,IAAI;IAAG;IAAQ;IAAQ,CAAC,GAAG,KAAK;IAC9D;EACF,IAAI,OAAO;AACX,SAAO,GAAG,SAAS,UAAU;AAC3B,WAAQ,MAAM,UAAU;AAExB,OAAI;IACF,MAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,WAAO,KAAK;AACZ,iBAAa,KAAK,KAAK,QAAQ,KAAK,OAAO,GAAG,OAAO,IAAI,MAAM,KAAK,SAAS,kBAAkB,CAAC,CAAC;WAC3F;IAGR;AACF,SAAO,GAAG,aAAa;AACrB,gBAAa;AACX,QAAI;KACF,MAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,UAAK,KAAK,QAAQ,KAAK,OAAO,GAAG,OAAO,IAAI,MAAM,KAAK,SAAS,kBAAkB,CAAC;aAC5E,GAAG;AAAE,YAAO,EAAE;;KACvB;IACF;AACF,SAAO,GAAG,UAAU,MAAM,aAAa,OAAO,EAAE,CAAC,CAAC;AAClD,mBAAiB;AAAE,UAAO,SAAS;AAAE,gBAAa,uBAAO,IAAI,MAAM,cAAc,CAAC,CAAC;KAAK,IAAM;GAC9F;;AAOJ,SAAS,UAAU,MAAsB;AACvC,SAAQ,MAAR;EACE,KAAK,WAAa,QAAO,MAAM,KAAK,KAAK;EACzC,KAAK,SAAa,QAAO,MAAM,IAAI,KAAK;EACxC,KAAK,UAAa,QAAO,MAAM,MAAM,KAAK;EAC1C,KAAK,WAAa,QAAO,MAAM,OAAO,KAAK;EAC3C,KAAK,YAAa,QAAO,MAAM,KAAK,KAAK;EACzC,KAAK,SAAa,QAAO,MAAM,QAAQ,KAAK;EAC5C,QAAkB,QAAO,MAAM,MAAM,KAAK;;;AAQ9C,SAASC,QAAM,IAAuC;AACpD,KAAI,CAAC,GAAI,QAAOC,MAAI,IAAI;AACxB,KAAI;EACF,MAAM,IAAI,IAAI,KAAK,GAAG;AAGtB,SAAO,GAFM,EAAE,aAAa,CAAC,MAAM,GAAG,GAAG,CAE1B,GADF,EAAE,aAAa,CAAC,MAAM,IAAI,GAAG;SAEpC;AACN,SAAOA,MAAI,IAAI;;;AAQnB,SAASC,QAAM,GAAW,QAAwB;AAChD,KAAI,CAAC,EAAG,QAAO;AACf,KAAI,EAAE,UAAU,OAAQ,QAAO;AAC/B,QAAO,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG;;AA4BlC,eAAeC,UAAQ,MAKL;CAChB,MAAM,QAAQ,SAAS,KAAK,SAAS,MAAM,GAAG;CAE9C,MAAM,SAAkC,EAAE,OAAO;AACjD,KAAI,KAAK,QAAS,QAAO,eAAe,KAAK;AAC7C,KAAI,KAAK,KAAS,QAAO,OAAO,KAAK;AACrC,KAAI,KAAK,QAAS,QAAO,aAAa,KAAK;CAE3C,IAAI;AACJ,KAAI;AACF,iBAAgB,MAAMJ,UAAQ,oBAAoB,OAAO;UAClD,GAAG;AACV,UAAQ,MAAM,IAAI,iCAAiC,IAAI,CAAC;AACxD,UAAQ,MAAME,MAAI,kDAAkD,CAAC;AACrE,UAAQ,WAAW;AACnB;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,qBAAqB,CAAC;CAEzC,MAAM,cAAwB,EAAE;AAChC,KAAI,KAAK,QAAS,aAAY,KAAK,YAAY,KAAK,UAAU;AAC9D,KAAI,KAAK,KAAS,aAAY,KAAK,SAAS,KAAK,OAAO;AACxD,KAAI,KAAK,QAAS,aAAY,KAAK,YAAY,KAAK,UAAU;AAC9D,aAAY,KAAK,UAAU,QAAQ;AACnC,SAAQ,IAAIA,MAAI,KAAK,YAAY,KAAK,QAAQ,GAAG,CAAC;AAClD,SAAQ,KAAK;AAEb,KAAI,CAAC,gBAAgB,aAAa,WAAW,GAAG;AAC9C,UAAQ,IAAIG,OAAK,2BAA2B,CAAC;AAC7C,UAAQ,KAAK;AACb;;CAIF,MAAM,OAAU;CAChB,MAAM,SAAU;CAChB,MAAM,UAAU;CAChB,MAAM,SAAU;CAChB,MAAM,OAAU;AAGhB,SAAQ,IACN,OACAC,OAAK,KAAK,OAAO,KAAK,CAAC,GAAG,OAC1BA,OAAK,OAAO,OAAO,OAAO,CAAC,GAAG,OAC9BA,OAAK,QAAQ,OAAO,QAAQ,CAAC,GAAG,OAChCA,OAAK,UAAU,OAAO,OAAO,CAAC,GAAG,OACjCA,OAAK,aAAa,CACnB;AACD,SAAQ,IACNJ,MACE,OACA,IAAI,OAAO,KAAK,GAAG,OACnB,IAAI,OAAO,OAAO,GAAG,OACrB,IAAI,OAAO,QAAQ,GAAG,OACtB,IAAI,OAAO,OAAO,GAAG,OACrB,IAAI,OAAO,KAAK,CACjB,CACF;AAED,MAAK,MAAM,OAAO,cAAc;EAC9B,MAAM,QAAa,OAAO,IAAI,GAAG,CAAC,SAAS,MAAM,IAAI;EACrD,MAAM,UAAa,UAAU,IAAI,QAAQ,GAAG,CAAC,OAAO,UAAU,UAAU,IAAI,QAAQ,GAAG,CAAC,UAAU,IAAI,QAAQ,IAAI,QAAQ;EAC1H,MAAM,WAAaC,QAAM,IAAI,SAAS,IAAI,QAAQ,CAAC,OAAO,QAAQ;EAClE,MAAM,UAAaA,QAAM,IAAI,gBAAgB,KAAK,OAAO,CAAC,OAAO,OAAO;EACxE,MAAM,QAAaF,QAAM,IAAI,WAAW;AAExC,UAAQ,IAAI,KAAK,MAAM,IAAI,QAAQ,IAAI,SAAS,IAAI,QAAQ,IAAIC,MAAI,MAAM,GAAG;;AAG/E,SAAQ,KAAK;AACb,SAAQ,IAAIA,MAAI,KAAK,aAAa,OAAO,iBAAiB,CAAC;AAC3D,SAAQ,KAAK;;AAOf,eAAe,UACb,OACA,MACe;CACf,MAAM,QAAQ,SAAS,KAAK,SAAS,MAAM,GAAG;CAE9C,MAAM,SAAkC;EAAE;EAAO;EAAO;AACxD,KAAI,KAAK,QAAS,QAAO,eAAe,KAAK;AAC7C,KAAI,KAAK,KAAS,QAAO,OAAO,KAAK;CAErC,IAAI;AACJ,KAAI;AAEF,oBAAmB,MAAMF,UAAQ,qBAAqB;GAAE,GAAG;GAAQ,OAAO,QAAQ;GAAG,OAAO;GAAW,CAAC;UACjG,GAAG;AACV,UAAQ,MAAM,IAAI,iCAAiC,IAAI,CAAC;AACxD,UAAQ,MAAME,MAAI,kDAAkD,CAAC;AACrE,UAAQ,WAAW;AACnB;;CAIF,MAAM,IAAI,MAAM,aAAa;CAC7B,MAAM,eAAe,gBAClB,QAAO,OACL,EAAE,SAAS,IAAI,aAAa,CAAC,SAAS,EAAE,KACxC,EAAE,aAAa,IAAI,aAAa,CAAC,SAAS,EAAE,KAC5C,EAAE,sBAAsB,IAAI,aAAa,CAAC,SAAS,EAAE,CACvD,CACA,MAAM,GAAG,MAAM;AAElB,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,2BAA2B,CAAC;AAC/C,SAAQ,IAAIA,MAAI,aAAa,MAAM,GAAG,KAAK,UAAU,cAAc,KAAK,YAAY,KAAK,KAAK,OAAO,WAAW,KAAK,SAAS,GAAG,WAAW,QAAQ,CAAC;AACrJ,SAAQ,KAAK;AAEb,KAAI,CAAC,gBAAgB,aAAa,WAAW,GAAG;AAC9C,UAAQ,IAAIG,OAAK,+BAA+B,MAAM,IAAI,CAAC;AAC3D,UAAQ,KAAK;AACb;;AAGF,MAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;EAC5C,MAAM,MAAM,aAAa;EACzB,MAAM,MAAM,MAAM,IAAI,OAAO,IAAI,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC;EACrD,MAAM,OAAO,UAAU,IAAI,QAAQ,GAAG;EACtC,MAAM,QAAQC,OAAK,IAAI,SAAS,aAAa;EAC7C,MAAM,OAAO,IAAI,eAAeJ,MAAI,IAAI,IAAI,aAAa,GAAG,GAAGA,MAAI,MAAM;EACzE,MAAM,KAAKA,MAAID,QAAM,IAAI,WAAW,CAAC;EACrC,MAAM,KAAKC,MAAI,IAAI,IAAI,KAAK;AAE5B,UAAQ,IAAI,KAAK,IAAI,IAAI,KAAK,OAAO,GAAG,CAAC,IAAI,QAAQ;AACrD,UAAQ,IAAI,cAAc,KAAK,IAAI,GAAG,IAAI,KAAK;AAE/C,MAAI,IAAI,WAAW;GACjB,MAAM,UAAUC,QAAM,IAAI,UAAU,QAAQ,OAAO,IAAI,EAAE,IAAI;AAC7D,WAAQ,IAAI,cAAcD,MAAI,QAAQ,GAAG;;AAE3C,UAAQ,KAAK;;AAGf,SAAQ,IAAIA,MAAI,KAAK,aAAa,OAAO,YAAY,CAAC;AACtD,SAAQ,KAAK;;AAOf,eAAeK,aAA0B;CACvC,IAAI;AACJ,KAAI;AACF,UAAS,MAAMP,UAAQ,qBAAqB,EAAE,CAAC;UACxC,GAAG;AACV,UAAQ,MAAM,IAAI,iCAAiC,IAAI,CAAC;AACxD,UAAQ,MAAME,MAAI,kDAAkD,CAAC;AACrE,UAAQ,WAAW;AACnB;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,+BAA+B,CAAC;AACnD,SAAQ,KAAK;AAEb,SAAQ,IAAI,KAAKI,OAAK,sBAAsB,CAAC,IAAI,MAAM,KAAK,OAAO,MAAM,SAAS,EAAE,CAAC,GAAG;AACxF,KAAI,MAAM,YACR,SAAQ,IAAI,KAAKA,OAAK,eAAe,CAAC,WAAWJ,MAAID,QAAM,MAAM,YAAY,CAAC,GAAG;AAEnF,SAAQ,KAAK;AAGb,KAAI,MAAM,WAAW,MAAM,QAAQ,SAAS,GAAG;AAC7C,UAAQ,IAAIK,OAAK,aAAa,CAAC;EAC/B,MAAM,WAAW,KAAK,IAAI,GAAG,MAAM,QAAQ,KAAK,MAAM,EAAE,MAAM,CAAC;AAC/D,OAAK,MAAM,OAAO,MAAM,SAAS;GAC/B,MAAM,WAAW;GACjB,MAAM,SAAS,KAAK,MAAO,IAAI,QAAQ,WAAY,SAAS;GAC5D,MAAM,MAAM,MAAM,KAAK,IAAI,OAAO,OAAO,CAAC,GAAGJ,MAAI,IAAI,OAAO,WAAW,OAAO,CAAC;GAC/E,MAAM,QAAQ,UAAU,IAAI,KAAK,CAAC,OAAO,MAAM,UAAU,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,QAAQ;AAC7F,WAAQ,IAAI,OAAO,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,MAAM,CAAC,SAAS,EAAE,GAAG;;AAEvE,UAAQ,KAAK;;AAIf,KAAI,MAAM,cAAc,MAAM,WAAW,SAAS,GAAG;AACnD,UAAQ,IAAII,OAAK,gBAAgB,CAAC;EAClC,MAAM,WAAW,KAAK,IAAI,GAAG,MAAM,WAAW,KAAK,MAAM,EAAE,MAAM,CAAC;EAClE,MAAM,OAAO,MAAM,WAAW,MAAM,GAAG,GAAG;AAC1C,OAAK,MAAM,OAAO,MAAM;GACtB,MAAM,WAAW;GACjB,MAAM,SAAS,KAAK,MAAO,IAAI,QAAQ,WAAY,SAAS;GAC5D,MAAM,MAAM,MAAM,MAAM,IAAI,OAAO,OAAO,CAAC,GAAGJ,MAAI,IAAI,OAAO,WAAW,OAAO,CAAC;GAChF,MAAM,SAAS,IAAI,gBAAgB,KAAK,OAAO,GAAG;AAClD,WAAQ,IAAI,OAAOA,MAAI,MAAM,CAAC,IAAI,IAAI,IAAI,OAAO,IAAI,MAAM,CAAC,SAAS,EAAE,GAAG;;AAE5E,MAAI,MAAM,WAAW,SAAS,GAC5B,SAAQ,IAAIA,MAAI,eAAe,MAAM,WAAW,SAAS,GAAG,kBAAkB,CAAC;AAEjF,UAAQ,KAAK;;AAGf,MAAK,CAAC,MAAM,WAAW,MAAM,QAAQ,WAAW,OAAO,CAAC,MAAM,cAAc,MAAM,WAAW,WAAW,IAAI;AAC1G,UAAQ,IAAIG,OAAK,6BAA6B,CAAC;AAC/C,UAAQ,KAAK;;;AAQjB,SAAgB,4BAA4B,QAAuB;AAEjE,QACG,QAAQ,OAAO,CACf,YAAY,2BAA2B,CACvC,OAAO,oBAAoB,yBAAyB,CACpD,OAAO,iBAAiB,0EAA0E,CAClG,OAAO,kBAAkB,uBAAuB,CAChD,OAAO,eAAe,mBAAmB,KAAK,CAC9C,OAAO,OAAO,SAAgF;AAC7F,MAAI;AACF,SAAMD,UAAQ,KAAK;WACZ,GAAG;AACV,WAAQ,MAAM,IAAI,YAAY,IAAI,CAAC;AACnC,WAAQ,WAAW;AACnB;;GAEF;AAGJ,QACG,QAAQ,iBAAiB,CACzB,YAAY,iDAAiD,CAC7D,OAAO,oBAAoB,yBAAyB,CACpD,OAAO,iBAAiB,iBAAiB,CACzC,OAAO,eAAe,mBAAmB,KAAK,CAC9C,OAAO,OAAO,OAAe,SAA8D;AAC1F,MAAI;AACF,SAAM,UAAU,OAAO,KAAK;WACrB,GAAG;AACV,WAAQ,MAAM,IAAI,YAAY,IAAI,CAAC;AACnC,WAAQ,WAAW;AACnB;;GAEF;AAGJ,QACG,QAAQ,QAAQ,CAChB,YAAY,2DAA2D,CACvE,OAAO,YAAY;AAClB,MAAI;AACF,SAAMG,YAAU;WACT,GAAG;AACV,WAAQ,MAAM,IAAI,YAAY,IAAI,CAAC;AACnC,WAAQ,WAAW;AACnB;;GAEF;;;;;AC7WN,SAAS,QAAQ,QAAgB,QAAmD;AAClF,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,UAAU;EACd,MAAM,UAAU,OAAmB;AAAE,OAAI,CAAC,SAAS;AAAE,cAAU;AAAM,QAAI;;;EAEzE,MAAM,SAAS,iBAAiB,eAAe,QAAQ;AACrD,UAAO,MAAM,KAAK,UAAU;IAAE,IAAI;IAAG;IAAQ;IAAQ,CAAC,GAAG,KAAK;IAC9D;EACF,IAAI,OAAO;AACX,SAAO,GAAG,SAAS,UAAU;AAC3B,WAAQ,MAAM,UAAU;AACxB,OAAI;IACF,MAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,WAAO,KAAK;AACZ,iBAAa,KAAK,KAAK,QAAQ,KAAK,OAAO,GAAG,OAAO,IAAI,MAAM,KAAK,SAAS,kBAAkB,CAAC,CAAC;WAC3F;IAGR;AACF,SAAO,GAAG,aAAa;AACrB,gBAAa;AACX,QAAI;KACF,MAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,UAAK,KAAK,QAAQ,KAAK,OAAO,GAAG,OAAO,IAAI,MAAM,KAAK,SAAS,kBAAkB,CAAC;aAC5E,GAAG;AAAE,YAAO,EAAE;;KACvB;IACF;AACF,SAAO,GAAG,UAAU,MAAM,aAAa,OAAO,EAAE,CAAC,CAAC;AAClD,mBAAiB;AAAE,UAAO,SAAS;AAAE,gBAAa,uBAAO,IAAI,MAAM,cAAc,CAAC,CAAC;KAAK,IAAM;GAC9F;;AAOJ,SAASC,QAAM,IAAuC;AACpD,KAAI,CAAC,GAAI,QAAOC,MAAI,IAAI;AACxB,KAAI;EACF,MAAM,IAAI,IAAI,KAAK,GAAG;AACtB,SAAO,GAAG,EAAE,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,aAAa,CAAC,MAAM,IAAI,GAAG;SACjE;AACN,SAAOA,MAAI,IAAI;;;AAInB,SAASC,cAAY,QAAwB;AAC3C,SAAQ,QAAR;EACE,KAAK,SAAY,QAAO,MAAM,MAAM,OAAO;EAC3C,KAAK,QAAY,QAAO,MAAM,OAAO,OAAO;EAC5C,KAAK,WAAY,QAAO,MAAM,IAAI,OAAO;EACzC,QAAiB,QAAO,MAAM,MAAM,OAAO;;;AAI/C,SAAS,MAAM,GAAW,QAAwB;AAChD,KAAI,CAAC,EAAG,QAAO;AACf,QAAO,EAAE,UAAU,SAAS,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG;;AAuB3D,eAAe,aAAa,MAKV;CAChB,MAAM,SAAkC;EACtC,OAAO,KAAK,SAAS;EACrB,OAAO,SAAS,KAAK,SAAS,MAAM,GAAG;EACxC;AACD,KAAI,KAAK,OAAQ,QAAO,SAAS,KAAK;CAEtC,IAAI;AACJ,KAAI;AACF,SAAQ,MAAM,QAAQ,yBAAyB,OAAO;UAC/C,GAAG;AACV,UAAQ,MAAM,IAAI,iCAAiC,IAAI,CAAC;AACxD,UAAQ,MAAMD,MAAI,kDAAkD,CAAC;AACrE,UAAQ,WAAW;AACnB;;AAGF,KAAI,KAAK,MAAM;AACb,UAAQ,IAAI,KAAK,UAAU,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC;AAChD;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,oBAAoB,CAAC;AACxC,SAAQ,IAAIA,MAAI,YAAY,OAAO,QAAQ,KAAK,SAAS,gBAAgB,KAAK,WAAW,KAAK,CAAC;AAC/F,SAAQ,KAAK;AAEb,KAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,UAAQ,IAAIE,OAAK,4BAA4B,CAAC;AAC9C,UAAQ,IAAIF,MAAI,yDAAyD,CAAC;AAC1E,UAAQ,KAAK;AACb;;CAGF,MAAM,SAAS;CACf,MAAM,QAAS;CACf,MAAM,SAAS;CACf,MAAM,SAAS;CACf,MAAM,SAAS;AAEf,SAAQ,IACN,OACAG,OAAK,QAAQ,OAAO,OAAO,CAAC,GAAG,OAC/BA,OAAK,SAAS,OAAO,MAAM,CAAC,GAAG,OAC/BA,OAAK,SAAS,OAAO,OAAO,CAAC,GAAG,OAChCA,OAAK,WAAW,SAAS,OAAO,CAAC,GAAG,OACpCA,OAAK,YAAY,OAAO,OAAO,CAAC,GAAG,OACnCA,OAAK,WAAW,CACjB;AACD,SAAQ,IAAIH,MAAI,OAAO,IAAI,OAAO,SAAS,QAAQ,SAAS,SAAS,SAAS,GAAG,CAAC,CAAC;AAEnF,MAAK,MAAM,KAAK,MAAM;EACpB,MAAM,OAAS,MAAM,EAAE,cAAc,IAAI,OAAO,CAAC,OAAO,OAAO;EAC/D,MAAM,MAAS,MAAM,EAAE,UAAU,IAAI,MAAM,CAAC,OAAO,MAAM;EACzD,MAAM,OAASC,cAAY,EAAE,UAAU,GAAG;EAC1C,MAAM,UAAU,KAAK,OAAO,UAAU,KAAK,UAAU,EAAE,UAAU,IAAI,QAAQ;EAC7E,MAAM,OAAS,OAAO,EAAE,iBAAiB,EAAE,CAAC,SAAS,OAAO;EAC5D,MAAM,OAASF,QAAM,EAAE,eAAe,CAAC,OAAO,OAAO;EACrD,MAAM,QAAS,MAAM,QAAQ,EAAE,iBAAiB,GAAG,EAAE,iBAAiB,KAAK,KAAK,GAAG;AACnF,UAAQ,IAAI,KAAK,MAAM,MAAM,KAAK,CAAC,IAAIC,MAAI,IAAI,CAAC,IAAI,QAAQ,IAAI,MAAM,KAAK,KAAK,CAAC,IAAIA,MAAI,KAAK,CAAC,IAAIA,MAAI,MAAM,OAAO,GAAG,CAAC,GAAG;;AAG7H,SAAQ,KAAK;AACb,SAAQ,IAAIA,MAAI,KAAK,KAAK,OAAO,WAAW,CAAC;AAC7C,SAAQ,KAAK;;AAOf,SAAgB,sBAAsB,QAAuB;AAC3D,QACG,QAAQ,YAAY,CACpB,YAAY,kEAAkE,CAC9E,OAAO,mBAAmB,oBAAoB,UAAU,CACxD,OAAO,qBAAqB,8CAA8C,CAC1E,OAAO,eAAe,gBAAgB,KAAK,CAC3C,OAAO,UAAU,kBAAkB,CACnC,OAAO,OAAO,SAA8E;AAC3F,MAAI;AACF,SAAM,aAAa,KAAK;WACjB,GAAG;AACV,WAAQ,MAAM,IAAI,YAAY,IAAI,CAAC;AACnC,WAAQ,WAAW;AACnB;;GAEF;;;;;AC9JN,SAAS,KAAK,OAAO,IAAI;AACvB,SAAQ,IAAI,KAAK;;AAGnB,SAAS,KAAK,KAAa;AACzB,SAAQ,IAAI,MAAM,KAAK,KAAK,OAAO,MAAM,CAAC;;AAG5C,SAAS,KAAK,KAAa;AACzB,SAAQ,IAAII,MAAI,KAAK,MAAM,CAAC;;AAG9B,SAAS,QAAQ,KAAa;AAC5B,SAAQ,IAAIC,KAAG,KAAK,MAAM,CAAC;;AAG7B,SAAS,QAAQ,KAAa;AAC5B,SAAQ,IAAIC,OAAK,KAAK,MAAM,CAAC;;AAG/B,SAAS,MAAM,KAAa;AAC1B,SAAQ,IAAI,IAAI,KAAK,MAAM,CAAC;;;;;;AAO9B,SAASC,MAAI,KAAa,KAAuB;AAC/C,KAAI;AACF,WAAS,KAAK;GACZ,OAAO;GACP,KAAK,OAAO,QAAQ,KAAK;GACzB,KAAK,QAAQ;GACd,CAAC;AACF,SAAO;SACD;AACN,SAAO;;;;;;AAOX,SAAS,QAAQ,KAAa,KAA6B;AACzD,KAAI;AACF,SAAO,SAAS,KAAK;GACnB,OAAO;GACP,KAAK,OAAO,QAAQ,KAAK;GACzB,KAAK,QAAQ;GACd,CAAC,CAAC,UAAU,CAAC,MAAM;SACd;AACN,SAAO;;;;;;;AAQX,SAAS,eAAuB;AAI9B,QAAO,KAFW,QADC,cAAc,OAAO,KAAK,IAAI,CACZ,EAEd,QAAQ;;;;;AAMjC,SAAS,kBAA0B;CACjC,MAAM,aAAa;EACjB,KAAK,cAAc,EAAE,YAAY;EACjC,KAAK,SAAS,EAAE,OAAO,MAAM,OAAO,YAAY;EAChD,KAAK,KAAK,OAAO,SAAS,OAAO,gBAAgB,UAAU,OAAO,YAAY;EAC/E;AACD,MAAK,MAAM,KAAK,WACd,KAAI,WAAW,KAAK,GAAG,wBAAwB,CAAC,CAAE,QAAO;AAE3D,QAAO,KAAK,cAAc,EAAE,YAAY;;;;;;AAW1C,SAAS,iBAAgC;AACvC,MAAK,yBAAyB;CAK9B,MAAM,SAAS,QAAQ,iCAHR,cAAc,CAGkC;AAC/D,KAAI,CAAC,QAAQ;AACX,QAAM,qDAAqD;AAC3D,OAAK,8EAA8E;AACnF,SAAO;;CAIT,MAAM,SAAS,QAAQ,6BAA6B,OAAO;AAC3D,KAAI,CAAC,QAAQ;AACX,QAAM,sDAAsD;AAC5D,OAAK,uEAAuE;AAC5E,SAAO;;AAGT,MAAK,eAAe,SAAS;AAC7B,MAAK,eAAe,SAAS;AAC7B,QAAO;;;;;;AAOT,SAAS,UAAU,SAA0B;AAC3C,MAAK,4BAA4B;CAGjC,MAAM,SAAS,QAAQ,0BAA0B,QAAQ;AACzD,KAAI,CAAC,QAAQ;AACX,OAAK,4CAA4C;AACjD,SAAO;;AAGT,MAAK,6BAA6B;CAClC,MAAM,cAAc,OAAO,MAAM,KAAK,CAAC,MAAM,GAAG,EAAE;AAClD,MAAK,MAAM,KAAK,YACd,MAAK,KAAK,IAAI;AAEhB,KAAI,OAAO,MAAM,KAAK,CAAC,SAAS,EAC9B,MAAK,aAAa,OAAO,MAAM,KAAK,CAAC,SAAS,EAAE,OAAO;CAGzD,MAAM,SAAS,QAAQ,8CAA8C,QAAQ;AAC7E,KAAI,WAAW,MAAM;AACnB,UAAQ,0EAA0E;AAClF,SAAO;;AAGT,KAAI,OAAO,SAAS,2BAA2B,EAAE;AAC/C,OAAK,oBAAoB;AACzB,SAAO;;AAGT,SAAQ,mBAAmB;AAC3B,QAAO;;;;;;AAOT,SAAS,SAAS,SAAgC;AAChD,MAAK,qCAAqC;CAG1C,MAAM,aAAa,QAAQ,sBAAsB,QAAQ;AAGzD,KAAI,CADWA,MAAI,wBAAwB,QAAQ,EACtC;AACX,UAAQ,iEAAiE;AACzE,SAAO;;CAIT,MAAM,YAAY,QAAQ,sBAAsB,QAAQ;AAExD,KAAI,eAAe,WAAW;AAC5B,UAAQ,uCAAuC;AAC/C,SAAO;;CAIT,MAAM,MAAM,QACV,qBAAqB,cAAc,GAAG,IAAI,aAAa,UACvD,QACD;AACD,KAAI,KAAK;AACP,UAAQ,sBAAsB;AAC9B,OAAK,MAAM,KAAK,IAAI,MAAM,KAAK,CAC7B,MAAK,KAAK,IAAI;;AAIlB,QAAO;;;;;AAMT,SAAS,aAAa,SAAuB;AAC3C,MAAK,6CAA6C;CAElD,MAAM,SAAS,UAAU,OAAO,CAAC,SAAS,MAAM,EAAE;EAChD,KAAK;EACL,OAAO;EACP,UAAU;EACX,CAAC;AAEF,KAAI,OAAO,WAAW,EACpB,SAAQ,0BAA0B;MAC7B;EACL,MAAM,SAAS,OAAO,UAAU;AAChC,MAAI,OAAO,SAAS,WAAW,EAAE;AAC/B,WAAQ,2DAA2D;AACnE,QAAK,eAAe;AACpB,QAAK,6BAA6B;AAClC,QAAK,oBAAoB;AACzB,QAAK,mBAAmB;SACnB;AACL,WAAQ,mCAAmC,OAAO,MAAM,IAAI,kBAAkB;AAC9E,QAAK,uBAAuB;AAC5B,QAAK,kDAAkD;;;;;;;AAQ7D,SAAS,UAAU,SAA0B;AAC3C,MAAK,mDAAmD;AAExD,MAAK,6BAA6B;AAElC,KAAI,CADcA,MAAI,eAAe,QAAQ,EAC7B;AACd,QAAM,sBAAsB;AAC5B,SAAO;;AAGT,MAAK,cAAc;AAEnB,KAAI,CADUA,MAAI,iBAAiB,QAAQ,EAC/B;AACV,QAAM,wBAAwB;AAC9B,SAAO;;AAGT,SAAQ,kBAAkB;AAC1B,QAAO;;;;;;AAOT,SAAS,kBAAkB,SAAuB;AAChD,MAAK,2BAA2B;CAGhC,MAAM,UAAU,eAAe;AAC/B,KAAI,WAAW,QAAQ,CACrB,KAAI;EACF,MAAM,MAAM,SAAS,aAAa,SAAS,OAAO,CAAC,MAAM,EAAE,GAAG;AAC9D,MAAI,CAAC,MAAM,IAAI,IAAI,MAAM,GAEvB;OADe,QAAQ,aAAa,MAAM,KAC3B,MAAM;AACnB,YAAQ,8BAA8B,IAAI,IAAI;AAC9C;;;SAGE;AAaV,KAPe,UAAU,OAAO,CAAC,UAAU,UAAU,EAAE;EACrD,KAAK;EACL,OAAO;EACP,UAAU;EACV,SAAS;EACV,CAAC,CAES,WAAW,EACpB,SAAQ,oBAAoB;MACvB;AACL,UAAQ,0CAA0C;AAClD,OAAK,yCAAyC;AAC9C,OAAK,uCAAuC;;;;;;AAOhD,SAAS,oBAAoB,SAAiB,eAAoC;AAChF,MAAK,iCAAiC;CAGtC,MAAM,eAAe,KADA,iBAAiB,EACE,wBAAwB;CAChE,MAAM,WAAW,KAAK,SAAS,EAAE,WAAW,YAAY;AAExD,KAAI,CAAC,WAAW,aAAa,EAAE;AAC7B,OAAK,iDAAiD;AACtD;;AAGF,KAAI,CAAC,WAAW,SAAS,EAAE;AACzB,OAAK,2CAA2C;AAChD;;AAMF,KAAI,CAHgB,aAAa,UAAU,OAAO,CACf,SAAS,yBAAyB,EAEhD;AACnB,UAAQ,gEAAgE;AACxE,OAAK,4DAA4D;AACjE,OAAK,OAAO,eAAe;AAC3B;;CAIF,IAAI,kBAAkB;AACtB,KAAI,eAAe;EAEjB,MAAM,OAAO,QACX,4DACA,QACD;AACD,oBAAkB,CAAC,EAAE,QAAQ,KAAK,MAAM,CAAC,SAAS;;AAGpD,KAAI,CAAC,iBAAiB;AACpB,OAAK,+CAA+C;AACpD;;AAGF,MAAK,kDAAkD;AACvD,MAAK,wEAAwE;CAE7E,IAAI,WAAW,aAAa,cAAc,OAAO;AAEjD,YAAW,SAAS,QAAQ,eAAe,SAAS,CAAC;AACrD,eAAc,UAAU,UAAU,OAAO;AAEzC,SAAQ,uDAAuD;;;;;AAMjE,SAAS,iBAAiB,SAAuB;AAC/C,MAAK,+BAA+B;AAQpC,KANe,UAAU,OAAO,CAAC,YAAY,OAAO,EAAE;EACpD,KAAK;EACL,OAAO;EACP,SAAS;EACV,CAAC,CAES,WAAW,EACpB,SAAQ,0BAA0B;MAC7B;AACL,UAAQ,oCAAoC;AAC5C,OAAK,oCAAoC;;;AAQ7C,eAAe,YAA2B;AACxC,OAAM;AACN,SAAQ,IAAI,MAAM,KAAK,KAAK,0CAA0C,CAAC;AACvE,SAAQ,IAAI,MAAM,KAAK,KAAK,0CAA0C,CAAC;AACvE,SAAQ,IAAI,MAAM,KAAK,KAAK,0CAA0C,CAAC;AACvE,OAAM;CAGN,MAAM,UAAU,gBAAgB;AAChC,KAAI,CAAC,SAAS;AACZ,QAAM;AACN,UAAQ,WAAW;AACnB;;CAIF,MAAM,WAAW,UAAU,QAAQ;CAGnC,MAAM,gBAAgB,SAAS,QAAQ;AAGvC,KAAI,SACF,cAAa,QAAQ;AASvB,KAAI,CADY,UAAU,QAAQ,EACpB;AACZ,QAAM;AACN,QAAM,mDAAmD;AACzD,QAAM;AACN,UAAQ,WAAW;AACnB;;AAIF,mBAAkB,QAAQ;AAG1B,qBAAoB,SAAS,cAAc;AAG3C,kBAAiB,QAAQ;AAGzB,OAAM;AACN,SAAQ,IAAI,MAAM,KAAK,KAAK,0CAA0C,CAAC;AACvE,KAAI,cACF,SAAQ,4BAA4B;KAEpC,SAAQ,8CAA8C;AAExD,SAAQ,IAAIH,MAAI,cAAc,GAAG,MAAM,KAAK,QAAQ,iBAAiB,QAAQ,IAAI,UAAU,CAAC;AAC5F,OAAM;;AAOR,SAAgB,sBAAsB,SAAwB;AAC5D,SACG,QAAQ,SAAS,CACjB,YACC,gGACD,CACA,OAAO,YAAY;AAClB,QAAM,WAAW;GACjB;;;;;ACjcN,SAASI,eAAwB;AAE/B,QAAO,IAAI,UADI,YAAY,CACC,WAAW;;AAGzC,SAAS,UAAU,MAAgC;AACjD,SAAQ,MAAR;EACE,KAAK,MACH,QAAOC,OAAK,KAAK;EACnB,KAAK,QACH,QAAOC,OAAKC,KAAG,KAAK,CAAC;EACvB,KAAK,OACH,QAAOA,KAAG,KAAK;EACjB,QACE,QAAOA,KAAG,KAAK;;;AAIrB,SAAS,YAAY,QAA4B,gBAAgC;AAC/E,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,4BAA4B,CAAC;AAChD,SAAQ,KAAK;AACb,SAAQ,IAAI,KAAKD,OAAK,QAAQ,CAAC,MAAM,UAAU,OAAO,KAAK,GAAG;AAC9D,SAAQ,KAAK;AACb,SAAQ,IAAI,KAAKA,OAAK,YAAY,GAAG;AACrC,MAAK,MAAM,CAAC,IAAI,QAAQ,OAAO,QAAQ,OAAO,SAAS,EAAE;EACvD,MAAM,IAAI;EACV,MAAM,SAAS,EAAE,UAAUC,KAAG,UAAU,GAAGC,MAAI,WAAW;EAC1D,IAAI,QAAQ;AACZ,MAAI,OAAO,UAAU,EAAE,IAAK,SAAQA,MAAI,KAAK,EAAE,MAAM;AACrD,MAAI,OAAO,WAAW,EAAE,UAAW,SAAQA,MAAI,YAAY,EAAE,YAAY;AACzE,UAAQ,IAAI,OAAOF,OAAK,GAAG,OAAO,GAAG,CAAC,GAAG,SAAS,QAAQ;;AAE5D,SAAQ,KAAK;AACb,SAAQ,IAAI,KAAKA,OAAK,UAAU,CAAC,IAAI,eAAe,SAAS,IAAI,eAAe,KAAK,KAAK,GAAGE,MAAI,SAAS,GAAG;AAC7G,SAAQ,KAAK;AACb,SAAQ,IAAI,KAAKF,OAAK,WAAW,CAAC,GAAGE,MAAI,mBAAmB,GAAG;AAC/D,MAAK,MAAM,CAAC,OAAO,aAAa,OAAO,QAAQ,OAAO,QAAQ,EAAE;EAC9D,MAAM,KAAM,SAAsB,KAAK,KAAK,IAAIA,MAAI,SAAS;AAC7D,UAAQ,IAAI,OAAO,MAAM,OAAO,GAAG,CAAC,KAAK,KAAK;;AAEhD,SAAQ,KAAK;;AAOf,eAAe,YAA2B;CACxC,MAAM,SAASJ,cAAY;AAC3B,KAAI;EACF,MAAM,EAAE,QAAQ,mBAAmB,MAAM,OAAO,uBAAuB;AACvE,cAAY,QAAQ,eAAe;UAC5B,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,KAAK;AACb,UAAQ,IAAIC,OAAK,6BAA6B,CAAC;AAC/C,UAAQ,IAAIG,MAAI,KAAK,MAAM,CAAC;AAC5B,UAAQ,IAAIA,MAAI,oCAAoC,CAAC;AACrD,UAAQ,KAAK;AACb,UAAQ,WAAW;AACnB;;;AAIJ,eAAe,OAAO,MAOJ;CAChB,MAAM,SAASJ,cAAY;CAG3B,MAAM,QAGF,EAAE;AAEN,KAAI,KAAK,MAAM;EACb,MAAM,aAAiC;GACrC;GAAQ;GAAS;GAAY;GAAQ;GAAS;GAAO;GACtD;AACD,MAAI,CAAC,WAAW,SAAS,KAAK,KAAyB,EAAE;AACvD,WAAQ,MAAM,IAAI,iBAAiB,KAAK,KAAK,WAAW,WAAW,KAAK,KAAK,GAAG,CAAC;AACjF,WAAQ,WAAW;AACnB;;AAEF,QAAM,OAAO,KAAK;;CAIpB,MAAM,WAAoC,EAAE;AAE5C,KAAI,KAAK,cACP,MAAK,MAAM,MAAM,KAAK,cACpB,UAAS,MAAM,EAAE,SAAS,MAAM;AAIpC,KAAI,KAAK,eACP,MAAK,MAAM,MAAM,KAAK,eACpB,UAAS,MAAM,EAAE,SAAS,OAAO;AAIrC,KAAI,KAAK,QACP,UAAS,UAAU;EACjB,GAAI,SAAS,WAAqB,EAAE;EACpC,KAAK,KAAK;EACX;AAGH,KAAI,KAAK,aACP,UAAS,UAAU;EACjB,GAAI,SAAS,WAAqB,EAAE;EACpC,UAAU,KAAK;EAChB;AAGH,KAAI,KAAK,kBACP,UAAS,cAAc;EACrB,GAAI,SAAS,eAAyB,EAAE;EACxC,WAAW,KAAK;EACjB;AAGH,KAAI,OAAO,KAAK,SAAS,CAAC,SAAS,EACjC,OAAM,WAAW;AAGnB,KAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,UAAU;AAClC,UAAQ,MAAM,IAAI,8DAA8D,CAAC;AACjF,UAAQ,IAAII,MAAI,yCAAyC,CAAC;AAC1D,UAAQ,IAAIA,MAAI,sEAAsE,CAAC;AACvF,UAAQ,WAAW;AACnB;;AAGF,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,OAAO,sBAC9B,MACD;AACD,UAAQ,KAAK;AACb,UAAQ,IAAID,KAAG,iCAAiC,CAAC;AACjD,UAAQ,IAAI,KAAKD,OAAK,QAAQ,CAAC,GAAG,UAAU,OAAO,KAAK,GAAG;AAC3D,UAAQ,KAAK;UACN,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,MAAM,IAAI,aAAa,MAAM,CAAC;AACtC,UAAQ,WAAW;AACnB;;;AAIJ,eAAe,QAAQ,MAAyC;CAC9D,MAAM,SAASF,cAAY;CAC3B,MAAM,QAAS,KAAK,SAAS;AAE7B,SAAQ,KAAK;AACb,SAAQ,IAAII,MAAI,kBAAkB,MAAM,kBAAkB,CAAC;AAE3D,KAAI;EACF,MAAM,SAAS,MAAM,OAAO,iBAAiB;GAC3C;GACA,SAAS,0BAA0B,MAAM;GACzC,OAAO;GACR,CAAC;AAEF,MAAI,OAAO,kBAAkB,WAAW,KAAK,OAAO,kBAAkB,SAAS,EAC7E,SAAQ,IAAIH,OAAK,yBAAyB,CAAC;WAClC,OAAO,kBAAkB,WAAW,EAC7C,SAAQ,IAAIA,OAAK,sEAAsE,CAAC;MAExF,SAAQ,IAAIE,KAAG,cAAc,OAAO,kBAAkB,KAAK,KAAK,GAAG,CAAC;AAGtE,MAAI,OAAO,eAAe,SAAS,EACjC,SAAQ,IAAIF,OAAK,aAAa,OAAO,eAAe,KAAK,KAAK,GAAG,CAAC;AAGpE,UAAQ,IAAIG,MAAI,WAAW,OAAO,OAAO,CAAC;AAC1C,UAAQ,KAAK;UACN,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,MAAM,IAAI,aAAa,MAAM,CAAC;AACtC,UAAQ,WAAW;AACnB;;;AAIJ,eAAe,QACb,OACA,SACA,MACe;CACf,MAAM,SAASJ,cAAY;CAC3B,MAAM,cAAmC;EACvC;EAAS;EAAY;EAAc;EAAQ;EAC5C;AAED,KAAI,CAAC,YAAY,SAAS,MAA2B,EAAE;AACrD,UAAQ,MACN,IAAI,kBAAkB,MAAM,WAAW,YAAY,KAAK,KAAK,GAAG,CACjE;AACD,UAAQ,WAAW;AACnB;;AAGF,KAAI;EACF,MAAM,SAAS,MAAM,OAAO,iBAAiB;GACpC;GACP;GACA,OAAO,KAAK;GACb,CAAC;AAEF,MAAI,OAAO,kBAAkB,SAAS,EACpC,SAAQ,IAAIG,KAAG,cAAc,OAAO,kBAAkB,KAAK,KAAK,GAAG,CAAC;MAEpE,SAAQ,IAAIF,OAAK,2CAA2C,CAAC;AAG/D,MAAI,OAAO,eAAe,SAAS,EACjC,SAAQ,IAAIA,OAAK,aAAa,OAAO,eAAe,KAAK,KAAK,GAAG,CAAC;UAE7D,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,MAAM,IAAI,aAAa,MAAM,CAAC;AACtC,UAAQ,WAAW;AACnB;;;AAQJ,SAAgB,uBAAuB,WAA0B;AAE/D,WACG,QAAQ,SAAS,CACjB,YAAY,qDAAqD,CACjE,OAAO,YAAY;AAClB,QAAM,WAAW;GACjB;AAGJ,WACG,QAAQ,MAAM,CACd,YAAY,sDAAsD,CAClE,OAAO,YAAY;AAClB,QAAM,WAAW;GACjB;AAGJ,WACG,QAAQ,MAAM,CACd,YAAY,oDAAoD,CAChE,OACC,iBACA,wEACD,CACA,OAAO,yBAAyB,8BAA8B,CAC9D,OAAO,0BAA0B,+BAA+B,CAChE,OAAO,oBAAoB,4BAA4B,CACvD,OACC,2BACA,yDACD,CACA,OAAO,kCAAkC,yBAAyB,CAClE,OACC,OAAO,SAOD;AACJ,QAAM,OAAO;GACX,MAAM,KAAK;GACX,eAAe,KAAK;GACpB,gBAAgB,KAAK;GACrB,SAAS,KAAK;GACd,cAAc,KAAK;GACnB,mBAAmB,KAAK;GACzB,CAAC;GAEL;AAGH,WACG,QAAQ,OAAO,CACf,YAAY,uDAAuD,CACnE,OACC,mBACA,oEACA,OACD,CACA,OAAO,OAAO,SAA6B;AAC1C,QAAM,QAAQ,KAAK;GACnB;AAGJ,WACG,QAAQ,yBAAyB,CACjC,YAAY,8DAA8D,CAC1E,OAAO,mBAAmB,8BAA8B,CACxD,OAAO,OAAO,OAAe,SAAiB,SAA6B;AAC1E,QAAM,QAAQ,OAAO,SAAS,KAAK;GACnC;;;;;;;;;;;;AC5QN,SAAgB,WAAW,MAAoB;CAC7C,MAAM,QAAQ,CAAC,sBAAsB,KAAK,QAAQ;AAClD,KAAI,KAAK,IAAK,OAAM,KAAK,QAAQ,KAAK,MAAM;AAC5C,KAAI,KAAK,aAAa,KAAM,OAAM,KAAK,aAAa,KAAK,WAAW;AACpE,KAAI,KAAK,KAAK,MAAM,CAAE,OAAM,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC;AACtD,KAAI,KAAK,UAAW,OAAM,KAAK,IAAI,WAAW,KAAK,YAAY;AAC/D,OAAM,KACJ,IACA,eAAe,KAAK,MACpB,kCAAkC,KAAK,GAAG,oCAC1C,yEACA,4FACD;AACD,QAAO,MAAM,KAAK,KAAK;;AAOzB,eAAsB,aACpB,MACA,MACyB;AACzB,KAAI,KAAK,MAAM,YAAY,KACzB,QAAO;EACL;EACA,SAAS;EACT,QAAQ,KAAK,MAAM,UACf,IAAI,KAAK,MAAM,QAAQ,4BACvB;EACL;CAGH,MAAM,UAAU,KAAK,MAAM;AAE3B,KAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,UAW9B,QAAO;EAAE;EAAM,SAAS;EAAW,SAAS;EAAS,QANtC,KAAK,SAChB,qBAAqB,QAAQ,qCAC7B,KAAK,YACH,wBACA;EAEuD;CAG/D,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,KAAK,UAAU,SAAS,SAAS,WAAW,KAAK,EAAE,EAChE,eAAe,KAAK,kBAAkB,OACvC,CAAC;UACK,GAAG;AAGV,SAAO;GACL;GACA,SAAS;GACT,SAAS;GACT,QAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;GACnD;;AAKH,KAAI,OAAO,YAAY,kBAAkB,CAAC,OAAO,QAAQ,SAAS,mBAAmB,CACnF,QAAO;EACL;EACA,SAAS;EACT,SAAS,OAAO,WAAW;EAC3B,QAAQ,GAAG,OAAO,UAAU,+BAA+B,2DAA2D,QAAQ;EAC/H;AAGH,QAAO;EAAE;EAAM,SAAS,OAAO;EAAS,SAAS,OAAO,WAAW;EAAS,QAAQ,OAAO;EAAQ;;;;;;;;AASrG,eAAsB,YACpB,OACA,MAC2B;CAC3B,MAAM,UAA4B,EAAE;AACpC,MAAK,MAAM,QAAQ,MACjB,SAAQ,KAAK,MAAM,aAAa,MAAM,KAAK,CAAC;AAE9C,QAAO;;;;;;;;;;;;;;;;;;;;;;;;AC5IT,MAAM,gCAAgC;;;;;;;;;;AAWtC,MAAM,iBAAiB;AASvB,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;AASF,SAASI,MAAI,KAAa,MAAgB,OAAe,aAAsC;AAC7F,QAAO,IAAI,SAAS,SAAS,WAAW;AAetC,EAdc,SACZ,KACA,MACA;GAAE,SAAS;GAAa,WAAW,OAAO;GAAM,GAC/C,OAAO,QAAQ,WAAW;AAGzB,OAAI,SAAS,CAAC,OAAO,MAAM,CAAC,WAAW,IAAI,EAAE;AAC3C,WAAO,IAAI,MAAM,OAAO,MAAM,IAAI,MAAM,QAAQ,CAAC;AACjD;;AAEF,WAAQ,OAAO;IAElB,CACK,OAAO,IAAI,MAAM;GACvB;;AAGJ,IAAa,oBAAb,MAAoD;CAClD,YACE,AAAiB,MAAM,YACvB,AAAiB,cAAc,+BAC/B;EAFiB;EACA;;CAGnB,MAAM,SACJ,SACA,SACA,MAC0B;EAC1B,MAAM,OAAO;GAAC;GAAY;GAAS;GAAW;GAAU;GAAa,OAAO,KAAK,YAAY;GAAC;AAC9F,MAAI,CAAC,KAAK,cAAe,MAAK,KAAK,aAAa;EAEhD,MAAM,SAAS,MAAMA,MACnB,KAAK,KACL,MACA,SACA,KAAK,cAAc,MAAO,eAC3B;EAID,MAAM,QAAQ,OAAO,YAAY,IAAI;AACrC,MAAI,UAAU,GACZ,OAAM,IAAI,MAAM,8BAA8B,OAAO,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG;EAG9E,IAAI;AACJ,MAAI;AACF,UAAO,KAAK,MAAM,OAAO,MAAM,MAAM,CAAC;UAChC;AACN,SAAM,IAAI,MAAM,qCAAqC,OAAO,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG;;AAMrF,MAAI,CAAC,KAAK,WAAW,CAAC,eAAe,IAAI,KAAK,QAAQ,CACpD,QAAO;GACL,SAAS;GACT,SAAS,KAAK,WAAW;GACzB,QAAQ,qCAAqC,OAAO,KAAK,QAAQ;GAClE;AAGH,SAAO;GACL,SAAS,KAAK;GACd,SAAS,KAAK,WAAW,KAAK,WAAW;GACzC,QAAQ,KAAK;GACd;;;;;;;;;;AAWL,IAAa,iBAAb,MAA4B;CAC1B,YACE,AAAiB,MAAM,YACvB,AAAiB,cAAc,IAC/B;EAFiB;EACA;;CAGnB,MAAM,IAAI,SAAiB,UAAwC;EACjE,IAAI;AACJ,MAAI;AACF,YAAS,MAAMA,MACb,KAAK,KACL;IAAC;IAAO;IAAS;IAAW;IAAU;IAAa,OAAO,KAAK,YAAY;IAAC,EAC5E,UACA,KAAK,cAAc,MAAO,eAC3B;WACM,GAAG;AACV,UAAO;IAAE,OAAO;IAAU,QAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IAAE;;EAGhF,MAAM,QAAQ,OAAO,YAAY,IAAI;AACrC,MAAI,UAAU,GAAI,QAAO;GAAE,OAAO;GAAU,QAAQ;GAA6B;AAEjF,MAAI;GACF,MAAM,OAAO,KAAK,MAAM,OAAO,MAAM,MAAM,CAAC;GAU5C,MAAM,QAAQ,KAAK,UAAU,KAAK,YAAY,OAAO,YAAY;AACjE,OAAI,UAAU,aAAa,UAAU,UAAU,UAAU,YAAY,UAAU,SAC7E,QAAO;IAAE;IAAO,OAAO,KAAK;IAAO,QAAQ,KAAK;IAAQ;AAG1D,UAAO;IAAE,OAAO;IAAU,QAAQ,mCAAmC,OAAO,KAAK,MAAM;IAAI;UACrF;AACN,UAAO;IAAE,OAAO;IAAU,QAAQ;IAAoC;;;;;;;;;;;AAY5E,eAAsB,aAAa,MAAM,YAA4C;AACnF,KAAI;EACF,MAAM,OAAO,MAAM,IAAI,SAAiB,SAAS,WAAW;AAC1D,YAAS,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,KAAQ,GAAG,OAAO,QAAQ,WAAW;AACtE,QAAI,SAAS,CAAC,OAAQ,QAAO,MAAM;QAC9B,SAAQ,SAAS,OAAO;KAC7B;IACF;AACF,SAAO,UAAU,KAAK,KAAK,GAAG,IAAI,eAAe,IAAI,GAAG;SAClD;AACN,SAAO;;;;;;;;;;AAWX,eAAsB,eACpB,MAAM,YACN,cAAc,+BACa;AAC3B,KAAI;AAOF,UANa,MAAM,IAAI,SAAiB,SAAS,WAAW;AAC1D,YAAS,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,KAAQ,GAAG,OAAO,QAAQ,WAAW;AACtE,QAAI,SAAS,CAAC,OAAQ,QAAO,MAAM;QAC9B,SAAQ,SAAS,OAAO;KAC7B;IACF,EACU,SAAS,WAAW,GAAG,IAAI,kBAAkB,KAAK,YAAY,GAAG;SACvE;AACN,SAAO;;;;;;;AChMX,MAAa,gBAAgB;;AAM7B,MAAM,kBAAkB;;AAGxB,MAAa,2BAA2B;;AAGxC,MAAM,eAAe;;AAGrB,MAAa,4BAA4B;;;;;;;;;;;;;;;;;AAkBzC,MAAa,iBAAiB;;AAG9B,MAAa,wBAAwB;;AAGrC,SAAgB,oBAAoB,UAA4B;AAC9D,QAAO,KAAK,IAAI,gBAAgB,SAAS,GAAG,gBAAgB,sBAAsB;;;;;;;;;;;;;;;;;;;AAsEpF,SAAgB,SAAS,OAAiB,QAAyB;AACjE,QAAO,MAAM,UAAU,YAAY;;;;;;;;;AAUrC,SAAgB,kBAAkB,MAAY,OAA0B;AACtE,QAAO,UAAU,KAAK,IAAI,SAAS,OAAO,KAAK,GAAG;;AAGpD,SAAgB,UAAU,MAAqB;AAC7C,QAAO,KAAK,OAAO,MAAM,MAAM,EAAE,aAAa,KAAK,cAAc;;;AAInE,SAAgB,kBAAkB,MAA2B;AAC3D,MAAK,MAAM,SAAS,KAAK,QAAQ;EAC/B,MAAM,IAAI,gBAAgB,KAAK,MAAM,MAAM,CAAC;AAC5C,MAAI,GAAG;GACL,MAAM,IAAI,OAAO,SAAS,EAAE,IAAK,GAAG;AACpC,UAAO,EAAE,GAAI,aAAa,KAAK,MAAM,IAAI,KAAK;;;AAGlD,QAAO;;;;;;;;AAST,SAAgB,eAAe,MAAY,KAAqB;AAC9D,KAAI,CAAC,KAAK,IAAK,QAAO,OAAO;CAC7B,MAAM,MAAM,KAAK,IAAI,UAAU,KAAK,GAAG,KAAK,IAAI,aAAa,KAAK;CAClE,MAAM,QAAQ,IAAI,KAAK,IAAI,CAAC,SAAS;AACrC,KAAI,OAAO,MAAM,MAAM,CAAE,QAAO,OAAO;AACvC,QAAO,KAAK,OAAO,MAAM,SAAS,IAAO;;;;;;;;;;;;;;;;;;AAwB3C,MAAM,aAAa;;;;;;;;;;AAWnB,SAAgB,oBAAoB,YAAwD;AAC1F,KAAI,CAAC,WAAY,QAAO;CACxB,MAAM,IAAI,WAAW,MAAM,CAAC,aAAa;AAEzC,KAAI,gCAAgC,KAAK,EAAE,CAAE,QAAO;EAAC;EAAG;EAAG;EAAE;AAC7D,KAAI,0BAA0B,KAAK,EAAE,CAAE,QAAO,CAAC,EAAE;AACjD,KAAI,4BAA4B,KAAK,EAAE,EAAE;EACvC,MAAM,IAAI,OAAO,SAAS,4BAA4B,KAAK,EAAE,CAAE,IAAK,GAAG;AACvE,SAAO,IAAI,IAAI,CAAC,EAAE,GAAG;;AAEvB,KAAI,kBAAkB,KAAK,EAAE,IAAI,YAAY,KAAK,EAAE,CAAE,QAAO,CAAC,EAAE;AAChE,KAAI,6BAA6B,KAAK,EAAE,EAAE;EACxC,MAAM,IAAI,OAAO,SAAS,6BAA6B,KAAK,EAAE,CAAE,IAAK,GAAG;AACxE,SAAO,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG;;AAI3B,KAAI,oCAAoC,KAAK,EAAE,IAAI,cAAc,KAAK,EAAE,CAAE,QAAO;EAAC;EAAI;EAAI;EAAI;EAAG;AACjG,KAAI,mBAAmB,KAAK,EAAE,IAAI,aAAa,KAAK,EAAE,IAAI,eAAe,KAAK,EAAE,CAAE,QAAO,CAAC,KAAK,IAAI;AACnG,KACE,0IAA0I,KAAK,EAAE,IACjJ,aAAa,KAAK,EAAE,CAEpB,QAAO,CAAC,EAAE;AAGZ,QAAO;;;;AAKT,SAAS,YAAY,SAAiB,QAAwB;CAC5D,MAAM,IAAI,IAAI,KAAK,QAAQ,UAAU,KAAK,GAAG,QAAQ,aAAa,QAAQ,CAAC,SAAS;CACpF,MAAM,IAAI,IAAI,KAAK,OAAO,UAAU,KAAK,GAAG,OAAO,aAAa,OAAO,CAAC,SAAS;AACjF,KAAI,OAAO,MAAM,EAAE,IAAI,OAAO,MAAM,EAAE,CAAE,QAAO;AAC/C,QAAO,KAAK,OAAO,IAAI,KAAK,WAAW;;;;;;;;;AAUzC,SAAgB,UAAU,MAAY,SAA6C;AACjF,KAAI,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,KAAK,WAAY,QAAO;AACtD,KAAI,KAAK,OAAO,QAAS,QAAO;CAEhC,MAAM,QAAQ,YAAY,SAAS,KAAK,IAAI;AAC5C,KAAI,CAAC,OAAO,SAAS,MAAM,IAAI,SAAS,EAAG,QAAO;CAElD,MAAM,WAAW,oBAAoB,KAAK,WAAW;AACrD,QAAO,aAAa,OAAO,OAAO,SAAS,SAAS,MAAM;;;;;;;;;;;AAY5D,SAAgB,iBAAiB,YAAoB,SAAyB;CAC5E,MAAM,OAAO,QAAQ,MAAM,GAAG,GAAG;CACjC,MAAM,UAAU,QAAQ,SAAS;CACjC,MAAM,kBAAkB,YAAY,KAAK,WAAW;AAKpD,QAAO,GAAG,aADG,WAAW,CAAC,kBAAkB,OAAO,QAAQ,MAAM,IAAI,GAAG,KAAK,GAChD,YAAY;;;;;;;;;;;;;;;;;;AAmB1C,SAAgB,gBAAgB,MAAc,MAAsB;CAClE,MAAM,KAAK,4CAA4C,KAAK,KAAK;AACjE,KAAI,CAAC,GAAI,yBAAO,IAAI,KAAK,GAAG,KAAK,WAAW,EAAC,SAAS;CAEtD,IAAI,OAAO,OAAO,GAAG,GAAG;CACxB,MAAM,SAAS,GAAG,IAAI,aAAa;AACnC,KAAI,WAAW,QAAQ,OAAO,GAAI,SAAQ;AAC1C,KAAI,WAAW,QAAQ,SAAS,GAAI,QAAO;CAE3C,MAAM,KAAK,OAAO,KAAK,CAAC,SAAS,GAAG,IAAI;CACxC,MAAM,KAAK,OAAO,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC,SAAS,GAAG,IAAI;AACtD,yBAAO,IAAI,KAAK,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,KAAK,EAAC,SAAS;;;;;;;;;;;;;AAkBrD,SAAgB,gBAAgB,UAAoB,WAAW,0BAAkC;CAC/F,MAAM,QAAQ,SAAS,QAAQ,MAAM,OAAO,SAAS,EAAE,IAAI,IAAI,EAAE;AACjE,KAAI,MAAM,WAAW,EAAG,QAAO;CAC/B,MAAM,SAAS,MAAM,MAAM,GAAG;AAC9B,QAAO,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,OAAO,CAAC;;;;;;;;AA6CnF,SAAgB,OAAO,MAAY,MAA+B;CAChE,MAAM,EAAE,KAAK,UAAU;CACvB,MAAM,UAAU,UAAU,KAAK;CAC/B,MAAM,UAAU,eAAe,MAAM,IAAI;CACzC,MAAM,YAAY,MAAM,UAAU,KAAK;AAEvC,KAAI,SAAS;EACX,MAAM,cAAc,KAAK,cAAc,KAAK;EAC5C,MAAM,UAAU,KAAK,cAAc,KAAK;EACxC,MAAM,cAAc,YAAY,UAAa,KAAK,QAAQ,QAAQ,KAAK,MAAM;EAC7E,MAAM,SAAS,SAAS,OAAO,KAAK,GAAG;AAmBvC,MAAK,UAAU,UAAU,KAFC,eAAe,gBAAgB,UAAa,cAAc,IAIlF,QAAO;GAAE,QAAQ;GAAY;GAAM,iBADlB,YAAY,KAAK,IAAI,GAAG,KAAK,OAAO,MAAM,aAAa,IAAO,CAAC,GAAG;GACrB;EAKhE,MAAM,QAAQ,aAAa;AAC3B,MAAI,UAAU,OAKZ,QAAO;GACL,QAAQ;GACR;GACA,QAAQ;GACT;EAGH,MAAM,UAAU,KAAK,IAAI,GAAG,KAAK,OAAO,MAAM,SAAS,IAAO,CAAC;EAC/D,MAAM,WAAW,gBAAgB,KAAK,UAAU,KAAK,OAAO,EAAE,CAAC;EAM/D,MAAM,eAAe,oBAAoB,KAAK,UAAU,KAAK,OAAO,EAAE,CAAC;AACvE,MAAI,WAAW,aACb,QAAO;GAAE,QAAQ;GAAa;GAAM,gBAAgB;GAAS,kBAAkB;GAAc;AAM/F,MAAI,CAAC,OAAQ,QAAO;GAAE,QAAQ;GAAW;GAAM,gBAAgB;GAAS;AAExE,MAAI,WAAW,WAAW,aACxB,QAAO;GAAE,QAAQ;GAAS;GAAM,gBAAgB;GAAS,iBAAiB;GAAU;AAEtF,SAAO;GAAE,QAAQ;GAAW;GAAM,gBAAgB;GAAS;;CAO7D,MAAM,UAAU,KAAK,cAAc,KAAK;CAaxC,MAAM,aAAa,KAAK,cAAc,KAAK,QAAQ;AAuBnD,KAAI,CAAC,KAAK,iBAAiB,CAAC,cAAc,UAAU,MAAM,QAAQ,EAAE;EAIlE,MAAM,SAAS,IAAI,KAAK,QAAS,UAAU,KAAK,GAAG,QAAQ,aAAa,QAAS,CAAC,SAAS;AAG3F,SAAO;GAAE,QAAQ;GAAa;GAAM,WADlC,KAAK,cAAc,SAAS,MAAM,iBAAiB,KAAK,YAAY,QAAS,GAAG;GACnC;;AAGjD,KAAI,UAAU,EAAG,QAAO;EAAE,QAAQ;EAAQ;EAAM;CAEhD,MAAM,QAAQ,kBAAkB,KAAK;AACrC,KAAI,UAAU,QAAQ,UAAU,MAC9B,QAAO;EACL,QAAQ;EACR;EACA,QAAQ,cAAc,QAAQ,cAAc,MAAM;EACnD;AAGH,QAAO;EAAE,QAAQ;EAAY;EAAM,gBAAgB,KAAK,IAAI,GAAG,QAAQ;EAAE;;;;;;;;;AAU3E,SAAgB,cAAc,OAAuB;CACnD,MAAM,OAAO;EAAE,IAAI;EAAG,IAAI;EAAG,IAAI;EAAG,IAAI;EAAG;AAC3C,QAAO,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM;EAC/B,MAAM,IAAI,KAAK,EAAE,YAAY,KAAK,EAAE;AACpC,MAAI,MAAM,EAAG,QAAO;AACpB,UAAQ,EAAE,OAAO,IAAI,cAAc,EAAE,OAAO,GAAG;GAC/C;;;;;;;;;;;;;;;;;ACvfJ,MAAaC,eAAa,KAAK,SAAS,EAAE,QAAQ,uBAAuB;;AAGzE,MAAM,gBAAgB;;;;;;;;;;;;AA0FtB,SAAS,aAA6B;AACpC,QAAO;EACL,WAAW,EAAE;EACb,cAAc,EAAE;EAChB,SAAS,EAAE;EACX,cAAc,EAAE;EAChB,kBAAkB,EAAE;EACpB,WAAW,EAAE;EACb,aAAa,EAAE;EACf,kBAAkB,EAAE;EACpB,aAAa,EAAE;EACf,QAAQ,EAAE;EACX;;;AAIH,MAAM,iCAAiC;;AAGvC,MAAM,kBAAkB,OAAU,KAAK;;;;;;;;;;;;;AAcvC,eAAe,SACb,MACA,UACA,QACA,OACA,KACA,MACe;CACf,MAAM,OAAO,MAAM,UAAU,KAAK;AAClC,KAAI,SAAS,UAAa,MAAM,OAAO,gBAAiB;AACxD,OAAM,UAAU,KAAK,MAAM;CAE3B,IAAI,YAAY;AAChB,KAAI;EACF,MAAM,EAAE,sBAAsB,MAAM,OAAO;EAC3C,MAAM,EAAE,eAAe,MAAM,OAAO;AAepC,eAde,MAAM,kBACnB;GACE,OAAO;GACP,OAAO,QAAQ;GAOf,SAAS,IAAI,KAAK,MAAM,IAAI;GAC7B,EACD,YAAY,CAAC,cACd,EACkB,kBAAkB,SAAS;SACxC;AAiBR,KAAI,aAAa,CAAC,MAAM,SAAS,QAAS;AAC1C,KAAI;AACF,QAAM,KAAK,SAAS,QAClB,KAAK,IACL,GAAG,WAAW,KAAK,SAAS,OAAO,OAAO,qGAG3C;SACK;;;;;;;AAUV,SAAgBC,YAAU,MAA8B;AACtD,KAAI;EACF,MAAM,MAAM,eAAe,MAAM,8BAA8B;AAC/D,SAAO;GAAE,GAAG,YAAY;GAAE,GAAI;GAAmC;SAC3D;AACN,SAAO,YAAY;;;AAIvB,SAAgBC,YAAU,OAAuB,MAAoB;AACnE,iBAAgB,MAAM,OAA6C,EAAE,QAAQ,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCvF,SAAS,kBAAkB,OAAuB,WAAyB;CACzE,MAAM,SAASD,YAAU,UAAU;AACnC,MAAK,MAAM,CAAC,IAAI,YAAY,OAAO,QAAQ,MAAM,YAAY,EAAE;EAC7D,MAAM,UAAU,OAAO,YAAY;AACnC,MAAI,YAAY,UAAa,UAAU,SAAS;AAC9C,SAAM,YAAY,MAAM;AACxB,UAAO,MAAM,UAAU;AACvB,UAAO,MAAM,YAAY;AACzB,UAAO,MAAM,aAAa;;;;;;;;;AAyChC,MAAM,uBAAuB;;;;;;;;;;;;;;;AAoD7B,SAAS,cAAc,MAAM,YAAqB;AAChD,KAAI;EACF,MAAM,MAAM,aAAa,KAAK,CAAC,WAAW,SAAS,EAAE;GACnD,SAAS;GACT,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAO;GAClC,CAAC;AAGF,SAAO,yCAAyC,KAAK,IAAI,IAAI,CAAC,oBAAoB,KAAK,IAAI;SACrF;AACN,SAAO;;;;;;;;;;;;;;;;;;;;;;AAuBX,SAAS,qBAAqB,OAAe,OAA6B;CACxE,MAAM,OAAO,IAAI,IAAI,MAAM,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;AAEjD,MAAK,MAAM,MAAM,OAAO,KAAK,MAAM,iBAAiB,EAAE;EACpD,MAAM,OAAO,KAAK,IAAI,GAAG;AAGzB,MAAI,CAAC,MAAM;AACT,UAAO,MAAM,iBAAiB;AAC9B;;AAOF,MAAI,CAAC,kBAAkB,MAAM,MAAM,CACjC,QAAO,MAAM,iBAAiB;;;AAKpC,eAAsB,KAAK,MAAwC;CACjE,MAAM,MAAM,KAAK,OAAO,KAAK,KAAK;CAClC,MAAM,YAAY,KAAK,aAAaD;CACpC,MAAM,QAAQC,YAAU,UAAU;CAClC,MAAM,SAAqB;EAAE,WAAW,EAAE;EAAE,YAAY;EAAG,WAAW;EAAG,OAAO;EAAG,QAAQ;EAAG;CAE9F,MAAM,QAAQ,MAAM,KAAK,SAAS,SAAS,EAAE,iBAAiB,MAAM,CAAC;CACrE,MAAM,UAAU,cAAc,MAAM;AAEpC,KAAI,CAAC,KAAK,OAAQ,sBAAqB,OAAO,MAAM;CAGpD,MAAM,gBAAgB,KAAK,iBAAiB,eAAe;AAE3D,MAAK,MAAM,QAAQ,SAAS;EAG1B,MAAM,UAAU,MAAM,YAAY,KAAK;EAEvC,MAAM,IAAI,OAAO,MAAM;GACrB;GACA;GACA,SAAS,MAAM;GACf,aAAa,MAAM;GACnB,aAAa,MAAM;GACnB;GACD,CAAC;EACF,IAAI,OAAO;AAKX,MAAI,CAAC,KAAK,UAAU,KAAK,IAAK,OAAM,YAAY,KAAK,MAAM,KAAK;AAKhE,MAAI,CAAC,KAAK,OACR,KAAI,UAAU,KAAK,EAAE;AAYnB,OAAI,MAAM,YAAY,KAAK,QAAQ,UAAa,KAAK,cAAc,YAAY,QAC7E;QAAI,UAAU,MAAM,QAAQ,CAC1B,OAAM,iBAAiB,KAAK,MAAM,iBAAiB,KAAK,YAAY,QAAQ;;AAGhF,SAAM,YAAY,KAAK,QAAQ;QAE/B,QAAO,MAAM,YAAY,KAAK;AAIlC,UAAQ,EAAE,QAAV;GACE,KAAK,OACH;GAEF,KAAK,aAAa;IAChB,MAAM,IAAI,MAAM,eAAe,MAAM,GAAG,MAAM,OAAO,IAAI;AACzD,WAAO,wBAAwB,EAAE;AACjC,QAAI,CAAC,KAAK,QAAQ;AAChB,SAAI,CAAC,EAAE,OAAQ,QAAO;AACtB,SAAI,EAAE,MAAO,QAAO;AACpB,aAAQ,MAAM,gBAAgB,MAAM,EAAE,WAAW,MAAM,MAAM;AAE7D,SAAI,EAAE,UAAW,OAAM,iBAAiB,KAAK,MAAM,EAAE;;AAEvD;;GAGF,KAAK;AACH,WAAO,EAAE;AACT;GAEF,KAAK;AACH,WAAO,GAAG,EAAE,eAAe;AAC3B;GAEF,KAAK,YAAY;IACf,MAAM,IAAI,MAAM,eAAe,MAAM,EAAE,gBAAgB,MAAM,OAAO,IAAI;AACxE,WAAO,EAAE;AACT,QAAI,CAAC,KAAK,QAAQ;AAIhB,SAAI,CAAC,EAAE,OAAQ,QAAO;AACtB,SAAI,EAAE,MAAO,QAAO;;AAEtB;;GAGF,KAAK;AACH,WAAO,MAAM,eAAe,MAAM,EAAE,iBAAiB,MAAM,OAAO,IAAI;AACtE,QAAI,CAAC,KAAK,OAAQ,QAAO;AACzB;GAEF,KAAK;AACH,WAAO,WAAW,EAAE,eAAe,kBAAkB,EAAE,iBAAiB;AACxE,QAAI,CAAC,KAAK,QAAQ;AAChB,WAAM,aAAa,MAAM,KAAK,UAAU,MAAM;AAI9C,YAAO;;AAET;GAGF,KAAK;GACL,KAAK,YAAY;IAEf,MAAM,SAAS,MAAM,YAAY,MADjB,EAAE,WAAW,UAAU,EAAE,iBAAiB,MACV,MAAM,MAAM;AAC5D,WAAO,OAAO;AACd,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAO;AACP,SAAI,OAAO,MAAO,QAAO;;AAE3B;;;AAIJ,SAAO,UAAU,KAAK;GAAE,UAAU;GAAG;GAAM,CAAC;;AAG9C,KAAI,CAAC,KAAK,QAAQ;AAChB,oBAAkB,OAAO,UAAU;AACnC,cAAU,OAAO,UAAU;;AAE7B,QAAO;;;;;;;;;;;;;;;;;;;AAwBT,SAAS,2BAA2B,QAAuD;CACzF,MAAM,SAAS,OAAO,UAAU;AAChC,QACE,wBAAwB,KAAK,OAAO,IACpC,wBAAwB,KAAK,OAAO,IACpC,6BAA6B,KAAK,OAAO;;AAI7C,eAAe,eACb,MACA,SACA,MACA,OACA,KAC6D;CAC7D,MAAM,OAAO,UAAU,IAAI,KAAK,QAAQ,WAAW;AACnD,KAAI,KAAK,OACP,QAAO;EAAE,MAAM,qBAAqB,KAAK,MAAM,WAAW,WAAW;EAAQ,OAAO;EAAO;CAO7F,MAAM,SAAS,MAAM,OAAO,KAAK;AACjC,KAAI,QAAQ;AACV,MAAI,OAAO,SAAS,KAAK,OAAO,SAAY;AAO1C,SAAM,SACJ,MACA,kBACA,oBAAoB,OAAO,OAAO,iFAElC,OACA,KACA,KACD;AACD,UAAO;IACL,MAAM,gBAAgB,IAAI,KAAK,OAAO,GAAG,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,KAAK,OAAO;IACnG,OAAO;IACP,QAAQ;IACT;;AAGH,SAAO,MAAM,OAAO,KAAK;AACzB,SAAO,MAAM,iBAAiB,KAAK;;AAcrC,KAAI,CAAC,KAAK,MAAM,SAAS;EACvB,MAAM,OAAO,KAAK,MAAM,UAAU,KAAK,KAAK,MAAM,QAAQ,wBAAwB;AAClF,QAAM,SACJ,MACA,6BACA,SAAS,KAAK,mBAAmB,KAAK,4GAEtC,OACA,KACA,KACD;AACD,SAAO;GAAE,MAAM,6BAA6B,OAAO;GAAQ,OAAO;GAAM;;AAkB1E,KAAI,KAAK,MAAM,YAAY,CAAC,WAAW,KAAK,MAAM,SAAS,EAAE;EAC3D,MAAM,SAAS,yBAAyB,KAAK,MAAM;AACnD,QAAM,OAAO,KAAK,MAAM;GAAE;GAAQ,IAAI;GAAK,KAAK,KAAK,OAAO;GAAW;AACvE,QAAM,SACJ,MACA,uBACA,+CAA+C,KAAK,MAAM,SAAS,sKAGnE,OACA,KACA,KACD;AACD,SAAO;GAAE,MAAM,YAAY;GAAU,OAAO;GAAM,QAAQ;GAAM;;CAgBlE,MAAM,UAAU,CAAC,GAAG,KAAK,QAAQ,cAAc;AAC/C,OAAM,KAAK,SAAS,UAAU,KAAK,IAAI,QAAQ;CAE/C,MAAM,SAAS,MAAM,aAAa,MAAM;EACtC,WAAW,KAAK;EAChB,cAAc,KAAK;EACnB,eAAe;EAChB,CAAC;AAQF,KAAI,OAAO,YAAY,eAAe,OAAO,YAAY,YAAY,OAAO,YAAY,WAAW;AACjG,QAAM,UAAU,KAAK,MAAM;AAC3B,SAAO,MAAM,aAAa,KAAK;AAC/B,SAAO,MAAM,iBAAiB,KAAK;AACnC,SAAO,MAAM,OAAO,KAAK;AAIzB,SAAO,MAAM,UAAU,KAAK;AAC5B,QAAM,YAAY,MAAM,MAAM,IAAI;AAClC,SAAO;GAAE,MAAM,GAAG,OAAO,QAAQ,MAAM,OAAO,UAAU;GAAQ,OAAO;GAAO;;AAQhF,OAAM,KAAK,SAAS,UAAU,KAAK,IAAI,KAAK,OAAO;AACnD,OAAM,iBAAiB,MAAM,KAAK,SAAS;CAE3C,MAAM,SAAS,MAAM,iBAAiB,KAAK,OAAO,KAAK;AACvD,OAAM,iBAAiB,KAAK,MAAM;CAClC,MAAM,SAAS,GAAG,OAAO,UAAU,OAAO,SAAS,QAAQ,OAAO,SAAS;CAM3E,MAAM,YAAY,2BAA2B,OAAO;AAEpD,KAAI,aAAa,SAAS,gCAAgC;AAKxD,QAAM,OAAO,KAAK,MAAM;GACtB,QAAQ;GACR,IAAI;GACJ,KAAK,KAAK,OAAO;GAClB;AACD,QAAM,SACJ,MACA,uBACA,sBAAsB,YAAY,KAAK,GAAG,MAAM,SAAS,GAAG,OAAO,6JAGnE,OACA,KACA,KACD;AACD,SAAO;GACL,MAAM,YAAY,YAAY,sBAAsB,GAAG,MAAM,oBAAoB,IAAI;GACrF,OAAO;GACR;;AAEH,QAAO;EAAE,MAAM,mBAAmB,MAAM,GAAG,+BAA+B,KAAK;EAAU,OAAO;EAAO;;;;;;;;;;;AAYzG,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;AAoB7B,MAAM,aAAa;;;;;;;;;;;;;AAcnB,eAAe,YAAY,MAAY,MAAmB,WAAkC;AAC1F,KAAI,CAAC,KAAK,SAAS,QAAS;CAC5B,MAAM,OAAO,IAAI,KAAK,UAAU,CAAC,aAAa,CAAC,QAAQ,KAAK,IAAI,CAAC,MAAM,GAAG,GAAG;AAC7E,KAAI;AAIF,QAAM,KAAK,SAAS,QAClB,KAAK,IACL,GAAG,WAAW,GAAG,qBAAqB,aAAa,KAAK,QAAQ,KAAK,MAAM,QAAQ,gCAEpF;SACK;;;;;;;;;;AAaV,eAAe,iBAAiB,MAAY,UAA0C;CACpF,MAAM,EAAE,cAAc,kBAAkB;AACxC,KAAI,CAAC,gBAAgB,CAAC,cAAe;AACrC,KAAI;EACF,MAAM,WAAW,MAAM,aAAa,KAAK,UAAU,KAAK,GAAG;AAC3D,OAAK,MAAM,KAAK,SAKd,KAAI,EAAE,QAAQ,SAAS,qBAAqB,CAC1C,OAAM,cAAc,KAAK,UAAU,EAAE,GAAG;SAGtC;;;;;;;;;;;;;;;;;;;;;;;;AA2BV,eAAsB,aACpB,MACA,UACA,OACe;AACf,OAAM,SAAS,UACb,KAAK,IACL,KAAK,OAAO,QAAQ,MAAM,EAAE,aAAa,KAAK,cAAc,CAC7D;AACD,OAAM,iBAAiB,MAAM,SAAS;AACtC,QAAO,MAAM,UAAU,KAAK;AAC5B,QAAO,MAAM,aAAa,KAAK;AAC/B,QAAO,MAAM,YAAY,KAAK;;;;;;;;;;AAWhC,eAAe,gBACb,MACA,WACA,MACA,OACiB;AACjB,KAAI,CAAC,UAAW,QAAO;AACvB,KAAI,CAAC,KAAK,SAAS,OAAQ,QAAO;AAElC,KAAI;AACF,QAAM,KAAK,SAAS,OAAO,KAAK,IAAI,UAAU;EAG9C,MAAM,eAAe,+BAA+B,KAAK,UAAU,GAAG;AACtE,MAAI,aAAc,OAAM,YAAY,KAAK,MAAM;AAC/C,SAAO,wBAAwB,UAAU;UAClC,GAAG;AACV,SAAO,qCAAqC,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;;;AAI1F,eAAe,eACb,MACA,iBACA,MACA,OACA,KACiB;AACjB,KAAI,KAAK,OAAQ,QAAO,eAAe,cAAc,IAAI,mBAAmB,IAAI;CAEhF,MAAM,YAAY,MAAM,aAAa,KAAK,OAAO,KAAK;AACtD,OAAM,aAAa,MAAM,KAAK,UAAU,MAAM;AAI9C,KAAI,oBAAoB,QAAQ,CAAC,UAAU;EACzC,MAAM,OAAO,MAAM,QAAQ,KAAK,OAAO,EAAE;AACzC,OAAK,KAAK,gBAAgB;AAC1B,QAAM,QAAQ,KAAK,MAAM,KAAK,MAAM,CAAC,cAAc;;AAQrD,SAJE,oBAAoB,OAChB,iCACA,gBAAgB,gBAAgB,GAAG,WAAW,oCAAoC,QAEzE,MAAM,sBAAsB,MAAM,MAAM,OAAO,IAAI;;;;;;;;;;;;;;AAepE,eAAe,sBACb,MACA,MACA,OACA,KACiB;CACjB,MAAM,OAAO,MAAM,iBAAiB,KAAK;AACzC,KAAI,CAAC,KAAM,QAAO;AAClB,QAAO,MAAM,iBAAiB,KAAK;CAEnC,MAAM,OAAO,+BAA+B,KAAK,KAAK,GAAG;AACzD,KAAI,CAAC,QAAQ,CAAC,KAAK,SAAS,OAAQ,QAAO;AAC3C,KAAI,gBAAgB,MAAM,KAAK,IAAI,IACjC,QAAO;AAGT,KAAI;AACF,QAAM,KAAK,SAAS,OAAO,KAAK,IAAI,KAAK;AACzC,QAAM,YAAY,KAAK,MAAM;AAC7B,SAAO,sBAAsB,KAAK;UAC3B,GAAG;AACV,SAAO,kCAAkC,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;;;AAIvF,eAAe,YACb,MACA,SACA,MACA,OAC2C;CAC3C,MAAM,UAAU,KAAK,MAAM;CAC3B,MAAM,KAAK,YAAY,OAAO,YAAY,GAAG,QAAQ;AAErD,KAAI,KAAK,OAAQ,QAAO;EAAE,MAAM,eAAe,WAAW,IAAI,IAAI,GAAG;EAAY,OAAO;EAAO;AAE/F,KAAI,CAAC,KAAK,UAAU,CAAC,QAGnB,QAAO;EAAE,MAAM,YAAY,GAAG;EAAiD,OAAO;EAAO;CAG/F,MAAM,SAAS,MAAM,KAAK,OAAO,IAC/B,SACA,sCAAsC,KAAK,MAAM,6BAClD;AAKD,KAAI,OAAO,UAAU,aAAa,OAAO,UAAU,QAAQ;AACzD,QAAM,aAAa,KAAK,MAAM;EAE9B,MAAM,WAAW,gBAAgB,MAAM,QAAQ,KAAK,OAAO,EAAE,CAAC;AAC9D,MAAI,YAAY,QAAQ,UAAU,WAAW,qBAG3C,QAAO;GACL,MAAM,aAAa,GAAG,uBAAuB,SAAS,MAAM,qBAAqB;GACjF,OAAO;GACR;AAGH,SAAO,OAAO,UAAU,SACpB;GAAE,MAAM,cAAc,GAAG;GAA4B,OAAO;GAAO,GACnE;GAAE,MAAM,eAAe,GAAG,IAAI,OAAO,SAAS;GAAiB,OAAO;GAAO;;CAGnF,MAAM,SAAS,MAAM,aAAa,KAAK,OAAO,KAAK;AACnD,OAAM,aAAa,KAAK,MAAM;CAK9B,MAAM,OAAO,OAAO,UAAU,WAAW,iBAAiB;AAE1D,KAAI,SAAS,0BACX,QAAO;EACL,MAAM,eAAe,GAAG,KAAK,KAAK,IAAI,MAAM,IAAI,OAAO,UAAU,YAAY;EAC7E,OAAO;EACR;AAEH,QAAO;EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,0BAA0B,SAAS;EAAM,OAAO;EAAO;;;;;;;;;;;;;;;;;;;ACnhC5F,MAAa,iBAAiB;AAC9B,MAAM,gBAAgB,KAAK,SAAS,EAAE,WAAW,eAAe;AAChE,MAAa,iBAAiB,KAAK,eAAe,GAAG,eAAe,QAAQ;AAC5E,MAAa,eAAe,kBAAkB;;AAG9C,MAAa,wBAAwB;;;;;;;;;;;AAYrC,SAAS,UAAkB;CACzB,IAAI,MAAM,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AACjD,MAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,OAAK,MAAM,aAAa,CAAC,KAAK,KAAK,QAAQ,OAAO,YAAY,EAAE,KAAK,KAAK,OAAO,YAAY,CAAC,CAC5F,KAAI,WAAW,UAAU,CAAE,QAAO;EAEpC,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAER,OAAM,IAAI,MACR,6HACD;;AAGH,SAAgB,sBAAsB,cAAsB,KAAqB;AAC/E,QAAO;;;;;cAKK,eAAe;;;;;kBAKX,IAAI;;;;;;eAMP,aAAa;;;;;;cAMd,aAAa;;;cAGb,aAAa;;;;;;;;;;;AAmB3B,SAAgB,gBAAgB,eAAe,uBAAsC;AACnF,KAAI,CAAC,WAAW,cAAc,CAAE,WAAU,eAAe,EAAE,WAAW,MAAM,CAAC;AAO7E,eAAc,gBAJA,sBAAsB,cADxB,SAAS,CACiC,EAIjB,OAAO;AAE5C,WAAU,aAAa,CAAC,UAAU,eAAe,EAAE,EAAE,UAAU,QAAQ,CAAC;CACxE,MAAM,OAAO,UAAU,aAAa,CAAC,QAAQ,eAAe,EAAE,EAAE,UAAU,QAAQ,CAAC;AAEnF,QAAO;EACL,WAAW;EACX;EACA,QAAQ,KAAK,WAAW;EACxB,SACE,KAAK,WAAW,IACZ,uCAAuC,KAAK,MAAM,eAAe,GAAG,CAAC,SACrE,6CAA6C,KAAK,UAAU,IAAI,MAAM;EAC7E;;AAGH,SAAgB,oBAA4B;AAC1C,KAAI,CAAC,WAAW,eAAe,CAAE,QAAO;AACxC,WAAU,aAAa,CAAC,UAAU,eAAe,EAAE,EAAE,UAAU,QAAQ,CAAC;AACxE,YAAW,eAAe;AAC1B,QAAO;;AAGT,SAAgB,iBAA2E;AAEzF,KAAI,CADc,WAAW,eAAe,CAC5B,QAAO;EAAE,WAAW;EAAO,SAAS;EAAO,QAAQ;EAAkB;CAGrF,MAAM,UADO,UAAU,aAAa,CAAC,QAAQ,eAAe,EAAE,EAAE,UAAU,QAAQ,CAAC,CAC9D,WAAW;AAChC,QAAO;EACL,WAAW;EACX;EACA,QAAQ,UACJ,gBAAgB,iBAChB;EACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC/FH,SAAgB,YAAY,MAA0C;CACpE,MAAM,QAAQ,KAAK,MAChB,aAAa,CACb,QAAQ,oBAAoB,IAAI,CAChC,QAAQ,YAAY,GAAG,CACvB,MAAM,GAAG,GAAG,CACZ,QAAQ,QAAQ,GAAG;AACtB,QAAO,QAAQ,GAAG,MAAM,KAAK,KAAK,OAAO,KAAK;;;AAIhD,SAAgB,YAAY,aAAqB,MAA0C;AACzF,QAAO,KAAK,aAAa,SAAS,SAAS,GAAG,YAAY,KAAK,CAAC,KAAK;;;;;;;;;AAUvE,SAAgB,cACd,MACA,UACA,aACQ;CACR,MAAM,MAAgB,EAAE;AAExB,KAAI,KAAK,MAAM;AACf,KAAI,KAAK,YAAY,KAAK,KAAK;AAC/B,KAAI,KAAK,UAAU,KAAK,UAAU,KAAK,MAAM,GAAG;AAChD,KAAI,KAAK,MAAM,QAAS,KAAI,KAAK,UAAU,KAAK,MAAM,UAAU;AAChE,KAAI,KAAK,IAAK,KAAI,KAAK,QAAQ,KAAK,MAAM;AAC1C,KAAI,KAAK,WAAY,KAAI,KAAK,eAAe,KAAK,UAAU,KAAK,WAAW,GAAG;AAC/E,KAAI,KAAK,cAAc,cAAc;AACrC,KAAI,KAAK,UAAW,KAAI,KAAK,WAAW,KAAK,YAAY;AACzD,KAAI,KAAK,MAAM;AACf,KAAI,KAAK,GAAG;AACZ,KAAI,KAAK,KAAK,KAAK,QAAQ;AAC3B,KAAI,KAAK,GAAG;AAEZ,KAAI,KAAK,KAAK,MAAM,EAAE;AACpB,MAAI,KAAK,UAAU;AACnB,MAAI,KAAK,GAAG;AACZ,MAAI,KAAK,KAAK,KAAK,MAAM,CAAC;AAC1B,MAAI,KAAK,GAAG;;AAGd,KAAI,KAAK,kBAAkB,SAAS,OAAO,GAAG;AAC9C,KAAI,KAAK,GAAG;AAEZ,KAAI,SAAS,WAAW,GAAG;AAGzB,MAAI,KAAK,0CAA0C;AACnD,MAAI,KAAK,GAAG;OAEZ,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,OAAO,EAAE,WAAW,EAAE,SAAS,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,GAAG;AACtE,MAAI,KAAK,OAAO,OAAO;AACvB,MAAI,KAAK,GAAG;AACZ,MAAI,KAAK,EAAE,QAAQ,MAAM,CAAC;AAC1B,MAAI,KAAK,GAAG;;AAIhB,QAAO,IAAI,KAAK,KAAK;;;AAIvB,SAAS,aAAa,MAAsB;AAC1C,QAAO,KACJ,MAAM,KAAK,CACX,QAAQ,MAAM,CAAC,EAAE,WAAW,cAAc,CAAC,CAC3C,KAAK,KAAK;;;;;;;;;AAUf,SAAgB,aACd,aACA,MACA,UACA,aACe;CACf,MAAM,OAAO,YAAY,aAAa,KAAK;AAO3C,KAAI,SAAS,WAAW,EACtB,QAAO;EAAE;EAAM,SAAS;EAAO,cAAc;EAAG,SAAS;EAAiB;CAG5E,MAAM,OAAO,cAAc,MAAM,UAAU,YAAY;AAEvD,KAAI,WAAW,KAAK,CAClB,KAAI;AAOF,MAAI,aAAa,aAAa,MAAM,QAAQ,CAAC,KAAK,aAAa,KAAK,CAClE,QAAO;GAAE;GAAM,SAAS;GAAO,cAAc,SAAS;GAAQ;SAE1D;AAMV,WAAU,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAC7C,eAAc,MAAM,MAAM,QAAQ;AAClC,QAAO;EAAE;EAAM,SAAS;EAAM,cAAc,SAAS;EAAQ;;;;;;;;;;;;;;;;;;;;ACxJ/D,MAAM,MAAM,UAAU,SAAS;;;;;;;;;;;;;AAiC/B,eAAsB,oBAAoB,MAAM,YAAqC;CACnF,IAAI;AACJ,KAAI;AACF,GAAC,CAAE,UAAW,MAAM,IAAI,KAAK,CAAC,YAAY,OAAO,EAAE,EAAE,SAAS,KAAQ,CAAC;SACjE;AACN,SAAO,EAAE;;CAGX,MAAM,UAA0B,EAAE;AAClC,MAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,EAAE;EAGrC,MAAM,IAAI,KAAK,MAAM,6BAA6B;AAClD,MAAI,CAAC,EAAG;EACR,MAAM,OAAO,EAAE,GAAG,MAAM;EACxB,MAAM,YAAY,EAAE,GAAG,MAAM;AAC7B,MAAI,CAAC,QAAQ,CAAC,UAAW;AACzB,UAAQ,KAAK;GAAE;GAAM;GAAW,CAAC;;AAEnC,QAAO;;;AAIT,SAAgB,cAAc,MAAsB;AAClD,QAAO,KAAK,MAAM,CAAC,aAAa,CAAC,QAAQ,QAAQ,IAAI;;;;;;;;;AAUvD,SAAgB,UACd,UACA,UACc;CACd,MAAM,yBAAS,IAAI,KAA2B;AAC9C,MAAK,MAAM,KAAK,SAAU,QAAO,IAAI,cAAc,EAAE,KAAK,EAAE,EAAE;CAE9D,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,OAAqB,EAAE;AAE7B,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,MAAM,cAAc,EAAE,KAAK;EACjC,MAAM,UAAU,OAAO,IAAI,IAAI;AAC/B,MAAI,QAAS,MAAK,IAAI,IAAI;AAC1B,OAAK,KAAK;GACR,MAAM,EAAE;GACR,OAAO,UAAU,WAAW;GAC5B,WAAW,EAAE;GACb,WAAW,SAAS;GACrB,CAAC;;AAGJ,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,MAAM,cAAc,EAAE,KAAK;AACjC,MAAI,KAAK,IAAI,IAAI,CAAE;AACnB,OAAK,KAAK;GAAE,MAAM,EAAE;GAAM,OAAO;GAAgB,WAAW,EAAE;GAAI,CAAC;;CAGrE,MAAM,OAAqC;EACzC,QAAQ;EACR,gBAAgB;EAChB,gBAAgB;EACjB;AACD,QAAO,KAAK,MACT,GAAG,MAAM,KAAK,EAAE,SAAS,KAAK,EAAE,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,CACxE;;;;;;;;;;;;;;;;;;;AC3EH,SAAS,YAAY,IAAc,MAAc,WAAmC;AAClF,KAAI,CAAC,UAAW,QAAO,qBAAqB,KAAK;CAEjD,MAAM,UAAU,GACb,QAAQ,0EAA0E,CAClF,IAAI,UAAU;AAEjB,KAAI,CAAC,QAAS,QAAO,qBAAqB,UAAU;CAEpD,MAAM,WAAW,GACd,QAAQ,+DAA+D,CACvE,IAAI,KAAK;AAEZ,KAAI,SAEF,QAAO,SAAS,eAAe,QAAQ,KACnC,OACA,UAAU,KAAK;AAGrB,IAAG,QAAQ,wDAAwD,CAAC,IAAI,MAAM,QAAQ,GAAG;AACzF,QAAO,aAAa,QAAQ;;;;;;;;;;AAW9B,SAAS,oBAAoB,MAAoB,UAAyB;CACxE,MAAM,SAAS,KAAK,QAAQ,MAAM,EAAE,UAAU,SAAS;CACvD,MAAM,WAAW,KAAK,QAAQ,MAAM,EAAE,UAAU,eAAe;CAC/D,MAAM,SAAS,KAAK,QAAQ,MAAM,EAAE,UAAU,eAAe;AAE7D,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,uBAAuB,OAAO,OAAO,GAAG,CAAC;AAChE,MAAK,MAAM,KAAK,OAAQ,SAAQ,IAAI,MAAM,MAAM,SAAS,GAAG,EAAE,KAAK;AACnE,KAAI,OAAO,WAAW,EAAG,SAAQ,IAAI,IAAI,WAAW,CAAC;AAErD,KAAI,SAAS,SAAS,GAAG;AACvB,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,KAAK,uCAAuC,SAAS,OAAO,GAAG,CAAC;AAClF,OAAK,MAAM,KAAK,SAAU,SAAQ,IAAI,MAAM,OAAO,SAAS,GAAG,EAAE,KAAK;AACtE,UAAQ,KAAK;AACb,UAAQ,IAAI,IAAI,6DAA6D,CAAC;;AAGhF,KAAI,OAAO,SAAS,GAAG;AACrB,UAAQ,KAAK;AACb,UAAQ,IAAI,MAAM,KAAK,8BAA8B,OAAO,OAAO,GAAG,CAAC;AACvE,OAAK,MAAM,KAAK,OAAQ,SAAQ,IAAI,IAAI,WAAW,EAAE,KAAK,CAAC;AAC3D,UAAQ,IAAI,IAAI,wEAAwE,CAAC;;AAG3F,KAAI,UAAU;AACZ,UAAQ,KAAK;AACb,UAAQ,IAAI,IAAI,4DAA4D,CAAC;;AAE/E,SAAQ,KAAK;;AAGf,MAAM,MAAM,MAAM;AAClB,MAAM,OAAO,MAAM;AACnB,MAAM,OAAO,MAAM;AACnB,MAAM,KAAK,MAAM;AAMjB,SAAS,gBAAwC;CAE/C,MAAM,QADS,YAAY,CACN;AACrB,KAAI,CAAC,OAAO,QAAS,QAAO;CAE5B,MAAM,UAAU,aAAa,cAAc,CAAC;CAC5C,MAAM,WAAW,IAAI,gBAAgB,MAAM,UAAU,SAAS,SAAS,EACrE,cAAc,MAAM,cACrB,CAAC;AACF,QAAO,SAAS,cAAc,GAAG,WAAW;;;AAI9C,SAAS,qBAA2B;AAClC,SAAQ,MAAM,MAAM,OAAO,gCAAgC,CAAC;AAC5D,SAAQ,MAAM,IAAI,oDAAoD,CAAC;;AAGzE,SAAS,WAAmB;AAC1B,yBAAO,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;;;;;;;;AAa9C,SAAS,aAAa,UAAmC;AACvD,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,KAAK,gBAAgB;GAAE,OAAO,QAAQ;GAAO,QAAQ,QAAQ;GAAQ,UAAU;GAAM,CAAC;EAC5F,MAAM,MAAM,QAAQ;EAGpB,MAAM,QAAQ,IAAI,MAAM,KAAK,IAAI;AACjC,EAAC,GAA0D,kBAAkB,MAAc;AACzF,OAAI,CAAC,IAAI,MAAO,OAAM,EAAE;;AAE1B,QAAM,SAAS;AACf,MAAI,QAAQ;AACZ,KAAG,SAAS,KAAK,WAAW;AAC1B,OAAI,QAAQ;AACZ,SAAM,KAAK;AACX,MAAG,OAAO;AACV,WAAQ,OAAO,MAAM,CAAC;IACtB;GACF;;;AAIJ,SAAS,OAAO,OAAmC;AACjD,KAAI,CAAC,MAAO,QAAO,IAAI,YAAY;AACnC,QAAO,MAAM,UAAU,IAAI,aAAa,GAAG,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,MAAM,GAAG;;AAcjF,SAAS,YAAY,MAAoB;AACvC,KAAI,KAAK,MAAM,SAAS;EACtB,MAAM,MAAM,KAAK,MAAM,WAAW,UAAU,UAAU;AACtD,SAAO,MAAM,KAAK,KAAK,MAAM,QAAQ,GAAG,IAAI,KAAK,IAAI,GAAG;;AAE1D,QAAO,KAAK,MAAM,UACd,MAAM,OAAO,WAAW,GAAG,IAAI,KAAK,KAAK,MAAM,QAAQ,sBAAsB,GAC7E,MAAM,OAAO,WAAW;;AAG9B,SAAS,WAAW,OAAqB;AACvC,KAAI,MAAM,WAAW,GAAG;AACtB,UAAQ,IAAI,IAAI,kBAAkB,CAAC;AACnC;;AAEF,SAAQ,KAAK;AACb,MAAK,MAAM,KAAK,OAAO;EACrB,MAAM,MAAM,EAAE,MAAM,IAAI,QAAQ,EAAE,IAAI,MAAM,GAAG,GAAG,GAAG,GAAG;EACxD,MAAM,OAAO,EAAE,aAAa,OAAO,IAAI,IAAI,EAAE,WAAW,GAAG;AAC3D,UAAQ,IAAI,KAAK,KAAK,EAAE,MAAM,GAAG,OAAO,MAAM;AAC9C,UAAQ,IAAI,OAAO,YAAY,EAAE,CAAC,GAAG,IAAI,OAAO,EAAE,GAAG,GAAG;;AAE1D,SAAQ,KAAK;;AAGf,SAAS,aAAa,SAAiC;CACrD,MAAM,SAAiC;EACrC,WAAW,MAAM,MAAM,IAAI;EAC3B,SAAS,MAAM,MAAM,IAAI;EACzB,UAAU,MAAM,OAAO,IAAI;EAC3B,cAAc,MAAM,IAAI,IAAI;EAC5B,aAAa,MAAM,IAAI,IAAI;EAC3B,SAAS,IAAI,IAAI;EAClB;AAED,SAAQ,KAAK;AACb,MAAK,MAAM,KAAK,SAAS;AACvB,UAAQ,IAAI,KAAK,OAAO,EAAE,SAAS,GAAG,EAAE,KAAK,QAAQ;EACrD,MAAM,SAAS,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM;AAChE,MAAI,OAAQ,SAAQ,IAAI,SAAS,IAAI,OAAO,GAAG;;AAEjD,SAAQ,KAAK;CAEb,MAAM,SAAS,QAAQ,QAAgC,KAAK,MAAM;AAChE,MAAI,EAAE,YAAY,IAAI,EAAE,YAAY,KAAK;AACzC,SAAO;IACN,EAAE,CAAC;AACN,SAAQ,IAAI,IAAI,OAAO,OAAO,QAAQ,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC;AACvF,SAAQ,KAAK;;AAOf,SAAgB,qBAAqB,SAAwB;AAC3D,SACG,QAAQ,WAAW,CACnB,YAAY,0FAA0F,CACtG,OAAO,uBAAuB,kDAAkD,CAChF,OAAO,gBAAgB,mDAAmD,CAC1E,OAAO,OAAO,SAAS;EACtB,MAAM,WAAW,eAAe;AAChC,MAAI,CAAC,SAAU,QAAO,oBAAoB;AAE1C,MAAI,CAAC,SAAS,mBAAmB,CAAC,SAAS,wBAAwB;AACjE,WAAQ,KAAK;AACb,WAAQ,IACN,MAAM,OAAO,KAAK,SAAS,WAAW,wCAAwC,CAC/E;AACD,WAAQ,IAAI,IAAI,gEAAgE,CAAC;AACjF,WAAQ,KAAK;AACb;;EAGF,MAAM,CAAC,UAAU,YAAY,MAAM,QAAQ,IAAI,CAC7C,qBAAqB,EACrB,SAAS,iBAAiB,CAC3B,CAAC;EAEF,MAAM,OAAO,UAAU,UAAU,SAAS;EAE1C,MAAM,SAAS,KAAK,YAChB,KAAK,QAAQ,MAAM,EAAE,UAAU,eAAe,CAAC,KAAK,MAAM,EAAE,KAAK,GAC/D,KAAK,UAAmC,EAAE;AAEhD,MAAI,OAAO,SAAS,GAAG;GACrB,MAAM,cAAc,IAAI,IACtB,KAAK,QAAQ,MAAM,EAAE,UAAU,eAAe,CAAC,KAAK,MAAM,cAAc,EAAE,KAAK,CAAC,CACjF;AACD,WAAQ,KAAK;AACb,QAAK,MAAM,QAAQ,QAAQ;AAIzB,QAAI,CAAC,YAAY,IAAI,cAAc,KAAK,CAAC,EAAE;AACzC,aAAQ,IAAI,MAAM,OAAO,cAAc,OAAO,GAAG,IAAI,kDAAkD,CAAC;AACxG;;IAEF,MAAM,EAAE,IAAI,YAAY,MAAM,SAAS,uBAAuB,KAAK;AACnE,YAAQ,KACL,UAAU,MAAM,MAAM,cAAc,GAAG,IAAI,cAAc,IAAI,OAAO,IAAI,KAAK,KAAK,CACpF;IASD,MAAM,MAAM,KAAK,MAAM,MAAM,cAAc,EAAE,KAAK,KAAK,cAAc,KAAK,CAAC;IAC3E,MAAM,OAAO,YAAY,cAAc,EAAE,MAAM,KAAK,UAAU;AAC9D,QAAI,KAAM,SAAQ,IAAI,IAAI,cAAc,OAAO,CAAC;;AAElD,WAAQ,KAAK;AACb;;AAGF,sBAAoB,MAAM,SAAS,WAAW,EAAE;GAChD;AAEJ,SACG,QAAQ,OAAO,CACf,YAAY,6BAA6B,CACzC,OAAO,qBAAqB,uCAAuC,CACnE,OAAO,gBAAgB,4CAA4C,CACnE,OAAO,WAAW,8BAA8B,CAChD,OAAO,YAAY,oCAAoC,CACvD,OAAO,eAAe,0BAA0B,MAAM,OAAO,SAAS,GAAG,GAAG,CAAC,CAC7E,OAAO,OAAO,SAAS;EACtB,MAAM,WAAW,eAAe;AAChC,MAAI,CAAC,SAAU,QAAO,oBAAoB;AAQ1C,aANc,MAAM,SAAS,SAAS;GACpC,OAAO,KAAK;GACZ,WAAW,KAAK,QAAQ,UAAU,GAAG,KAAK;GAC1C,iBAAiB,CAAC,KAAK;GACvB,OAAO,KAAK;GACb,CAAC,CACe;GACjB;AAEJ,SACG,QAAQ,cAAc,CACtB,YAAY,2BAA2B,CACvC,OAAO,qBAAqB,iDAAiD,CAC7E,OAAO,iBAAiB,qEAAqE,CAC7F,OAAO,gBAAgB,2EAA2E,CAClG,OAAO,kBAAkB,8BAA8B,CACvD,OAAO,eAAe,oDAAoD,CAC1E,OAAO,wBAAwB,uEAAuE,CACtG,OAAO,OAAO,OAAO,SAAS;EAC7B,MAAM,WAAW,eAAe;AAChC,MAAI,CAAC,SAAU,QAAO,oBAAoB;AAI1C,MAAI,CAAC,KAAK,MAAM;AACd,WAAQ,IAAI,IAAI,6DAA6D,CAAC;AAC9E,WAAQ,IAAI,IAAI,uDAAuD,CAAC;;EAG1E,MAAM,UAAU,MAAM,SAAS,IAAI;GACjC;GACA,MAAM,KAAK,QAAQ;GACnB,OAAO,KAAK,SAAS;GACrB,KAAK,KAAK;GACV,UAAU,KAAK;GACf,WAAW,KAAK;GAChB,MAAM,KAAK;GACZ,CAAC;AAEF,UAAQ,IAAI,MAAM,MAAM,YAAY,QAAQ,QAAQ,CAAC;AACrD,UAAQ,IAAI,OAAO,YAAY,QAAQ,CAAC,GAAG,IAAI,OAAO,QAAQ,GAAG,GAAG;GACpE;AAEJ,SACG,QAAQ,WAAW,CACnB,YAAY,oDAAoD,CAChE,OAAO,qBAAqB,uCAAuC,CACnE,OAAO,WAAW,kCAAkC,CACpD,OAAO,aAAa,0DAA0D,CAC9E,OAAO,cAAc,2DAA2D,CAChF,OAAO,OAAO,SAAS;EACtB,MAAM,WAAW,eAAe;AAChC,MAAI,CAAC,SAAU,QAAO,oBAAoB;EAE1C,MAAM,SAAS,YAAY;EAC3B,MAAM,QAAQ,MAAM,SAAS,SAAS;GACpC,OAAO,KAAK;GACZ,WAAW,KAAK,QAAQ,UAAU,GAAG;GACrC,iBAAiB;GAClB,CAAC;AAEF,MAAI,MAAM,WAAW,GAAG;AACtB,WAAQ,IAAI,IAAI,yBAAyB,CAAC;AAC1C;;EAKF,MAAM,YAAY,KAAK,SACnB,OACA,MAAM,eAAe,QAAW,OAAO,OAAO,oBAAoB;AAEtE,MAAI,CAAC,KAAK,UAAU,CAAC,UACnB,SAAQ,IAAI,IAAI,sEAAsE,CAAC;AAUzF,eAPgB,MAAM,YAAY,OAAO;GACvC;GACA,cAAc,KAAK,SAAS,QAAS,OAAO,OAAO,gBAAgB;GACnE,eAAe,KAAK,UAAU;GAC9B,QAAQ,QAAQ,KAAK,OAAO;GAC7B,CAAC,CAEmB;GACrB;AAEJ,SACG,QAAQ,OAAO,CACf,YAAY,0EAA0E,CACtF,OAAO,aAAa,mDAAmD,CACvE,OAAO,OAAO,SAAS;EACtB,MAAM,WAAW,eAAe;AAChC,MAAI,CAAC,SAAU,QAAO,oBAAoB;EAE1C,MAAM,SAAS,YAAY;EAI3B,MAAM,SAAS,MAAM,KAAK;GACd;GACV,WALgB,KAAK,SAAS,OAAO,MAAM,eAAe,QAAW,OAAO,OAAO,oBAAoB;GAMvG,QALa,KAAK,SAAS,OAAO,MAAM,cAAc;GAMtD,cAAc,OAAO,OAAO,gBAAgB;GAC5C,QAAQ,QAAQ,KAAK,OAAO;GAC7B,CAAC;AAEF,MAAI,OAAO,UAAU,WAAW,GAAG;AACjC,WAAQ,IAAI,IAAI,kCAAkC,CAAC;AACnD;;EAGF,MAAM,OAA+B;GACnC,UAAU,MAAM,MAAM,IAAI;GAC1B,WAAW,MAAM,MAAM,IAAI;GAC3B,UAAU,MAAM,MAAM,IAAI;GAC1B,SAAS,IAAI,IAAI;GACjB,OAAO,MAAM,OAAO,IAAI;GACxB,UAAU,MAAM,OAAO,IAAI;GAC3B,WAAW,MAAM,IAAI,IAAS;GAC9B,MAAM,IAAI,IAAI;GACd,MAAM,IAAI,IAAI;GACf;AAED,UAAQ,KAAK;AACb,OAAK,MAAM,EAAE,UAAU,UAAU,OAAO,WAAW;AACjD,WAAQ,IAAI,KAAK,KAAK,SAAS,WAAW,IAAI,GAAG,SAAS,KAAK,QAAQ;AACvE,OAAI,KAAM,SAAQ,IAAI,SAAS,IAAI,KAAK,GAAG;;AAE7C,UAAQ,KAAK;AACb,UAAQ,IACN,IACE,KAAK,OAAO,WAAW,eAAe,OAAO,UAAU,cACpD,OAAO,OAAO,WAAW,OAAO,MAAM,QAC1C,CACF;AACD,UAAQ,KAAK;GACb;CAEJ,MAAM,cAAc,QACjB,QAAQ,WAAW,CACnB,YAAY,wEAAwE;AAEvF,aACG,QAAQ,UAAU,CAClB,YAAY,yDAAyD,CACrE,OAAO,qBAAqB,0BAA0B,MAAM,OAAO,SAAS,GAAG,GAAG,CAAC,CACnF,QAAQ,SAAS;EAChB,MAAM,IAAI,gBAAgB,KAAK,YAAY,sBAAsB;AACjE,UAAQ,IAAI,EAAE,SAAS,MAAM,MAAM,KAAK,EAAE,UAAU,GAAG,MAAM,OAAO,KAAK,EAAE,UAAU,CAAC;AACtF,UAAQ,IAAI,IAAI,KAAK,EAAE,YAAY,CAAC;AACpC,UAAQ,IAAI,IAAI,+EAA+E,CAAC;GAChG;AAEJ,aACG,QAAQ,YAAY,CACpB,YAAY,6BAA6B,CACzC,aAAa,QAAQ,IAAI,MAAM,MAAM,KAAK,mBAAmB,GAAG,CAAC,CAAC;AAErE,aACG,QAAQ,SAAS,CACjB,YAAY,2DAA2D,CACvE,aAAa;EACZ,MAAM,IAAI,gBAAgB;AAC1B,UAAQ,KAAK;AACb,UAAQ,IAAI,KAAK,EAAE,YAAY,MAAM,MAAM,YAAY,GAAG,IAAI,gBAAgB,KACvE,EAAE,YAAa,EAAE,UAAU,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,eAAe,GAAI,KAAK;AACjG,UAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,CAAC;AACjC,UAAQ,KAAK;GACb;AAEJ,SACG,QAAQ,SAAS,CACjB,YAAY,yEAAyE,CACrF,OAAO,WAAW,qDAAqD,CACvE,OAAO,cAAc,0DAA0D,CAC/E,OAAO,kBAAkB,8DAA8D,CACvF,OAAO,mBAAmB,oCAAoC,CAC9D,OAAO,oBAAoB,uCAAuC,MAAM,OAAO,SAAS,GAAG,GAAG,CAAC,CAC/F,OAAO,0BAA0B,2DAA2D,CAC5F,OAAO,aAAa,wDAAwD,CAC5E,OAAO,OAAO,SAAS;EACtB,MAAM,MAAM,eAAe;EAC3B,MAAM,QAAS,IAAI,SAAS,EAAE;AAC9B,QAAM,cAAc,EAAE;AACtB,QAAM,UAAU,YAAY,EAAE;EAC9B,MAAM,UAAU,MAAM,UAAU;AAMhC,MAHE,CAAC,KAAK,SAAS,CAAC,KAAK,WAAW,CAAC,KAAK,WAAW,CAAC,KAAK,YACvD,KAAK,YAAY,UAAa,KAAK,iBAAiB,UAAa,CAAC,KAAK,SAE1D;AACb,WAAQ,KAAK;AACb,WAAQ,IAAI,KAAK,KAAK,WAAW,CAAC,QAAQ,MAAM,UAAU,MAAM,MAAM,UAAU,GAAG,IAAI,WAAW,GAAG;AACrG,WAAQ,IAAI,KAAK,KAAK,QAAQ,CAAC,WAAW,OAAO,QAAQ,OAAO,GAC9D,CAAC,QAAQ,UAAU,QAAQ,IAAI,kBAAkB,IAAI,2CAA2C,GAAG,KAClG;AACH,WAAQ,IAAI,KAAK,KAAK,eAAe,CAAC,IAAI,QAAQ,iBAAiB,IAAI,YAAY,GAAG;AACtF,WAAQ,IAAI,KAAK,KAAK,WAAW,CAAC,QAAQ,QAAQ,qBAAqB,IAAI,YAAY,GAAG;AAC1F,WAAQ,IAAI,KAAK,KAAK,gBAAgB,CAAC,GAAG,MAAM,eAAe,MAAM,MAAM,KAAK,GAAG,IAAI,MAAM,GAAG;AAChG,WAAQ,IAAI,KAAK,KAAK,UAAU,CAAC,SAAS,MAAM,uBAAuB,IAAI,iBAAiB,GAAG;AAC/F,WAAQ,KAAK;AACb,WAAQ,IAAI,IAAI,KAAKE,gBAAc,CAAC;AACpC,WAAQ,KAAK;AACb;;AAGF,MAAI,KAAK,SAAS,KAAK,SAAS;AAC9B,WAAQ,MAAM,MAAM,OAAO,gDAAgD,CAAC;AAC5E,WAAQ,WAAW;AACnB;;AAGF,MAAI,KAAK,SAAS;GAChB,MAAM,WAAW,QAAQ,IAAI,iBAAiB,MAAM;AACpD,OAAI,CAAC,UAAU;AACb,YAAQ,MAAM,MAAM,OAAO,oDAAoD,CAAC;AAChF,YAAQ,WAAW;AACnB;;AAEF,WAAQ,SAAS;AACjB,WAAQ,UAAU;AAClB,SAAM,UAAU;;AAGlB,MAAI,KAAK,OAAO;GACd,MAAM,UAAU,MAAM,aAAa,iCAAiC;AACpE,OAAI,CAAC,SAAS;AACZ,YAAQ,MAAM,MAAM,OAAO,uCAAuC,CAAC;AACnE,YAAQ,WAAW;AACnB;;AAEF,WAAQ,SAAS;AACjB,WAAQ,UAAU;AAClB,SAAM,UAAU;;AAGlB,MAAI,KAAK,SAAS;AAAE,WAAQ,gBAAgB,KAAK;AAAS,SAAM,UAAU;;AAC1E,MAAI,KAAK,SAAU,SAAQ,oBAAoB,KAAK;AACpD,MAAI,KAAK,YAAY,OAAW,OAAM,sBAAsB,KAAK;AACjE,MAAI,KAAK,iBAAiB,OAAW,OAAM,eAAe,KAAK,iBAAiB;AAChF,MAAI,KAAK,QAAS,OAAM,UAAU;AAElC,MAAI,QAAQ;AACZ,iBAAe,IAAI;AAInB,MAAI;AACF,aAAUA,eAAa,IAAM;UACvB;AACN,WAAQ,IAAI,MAAM,OAAO,uCAAuCA,cAAY,wBAAwB,CAAC;;AAGvG,UAAQ,IAAI,MAAM,MAAM,WAAW,CAAC;AACpC,MAAI,QAAQ,UAAU,CAAC,QAAQ,cAC7B,SAAQ,IAAI,IAAI,oEAAoE,CAAC;GAEvF;AAEJ,SACG,QAAQ,eAAe,CACvB,YAAY,kFAAkF,CAC9F,OAAO,WAAW,+CAA+C,CACjE,OAAO,eAAe,uDAAuD,CAC7E,OAAO,OAAO,IAAI,SAAgD;EACjE,MAAM,WAAW,eAAe;AAChC,MAAI,CAAC,SAAU,QAAO,oBAAoB;AAW1C,MAAI,CANU,MAAM,eAAe,UAAU,IAAI;GAC/C,OAAO,KAAK;GACZ,QAAQ,KAAK,WAAW;GACzB,CAAC,CAGU,SAAQ,WAAW;GAC/B;AAEJ,SACG,QAAQ,YAAY,CACpB,YAAY,8DAA8D,CAC1E,OAAO,gBAAgB,6CAA6C,CACpE,OAAO,OAAO,IAAI,SAAS;EAC1B,MAAM,WAAW,eAAe;AAChC,MAAI,CAAC,SAAU,QAAO,oBAAoB;EAK1C,IAAI,WAA4E;AAChF,MAAI,KAAK,YAAY,MACnB,YAAW,MAAM,kBAAkB,UAAU,GAAG;AAGlD,QAAM,SAAS,SAAS,GAAG;AAC3B,UAAQ,IAAI,MAAM,MAAM,eAAe,GAAG,GAAG,CAAC;AAc9C,MAAI;GACF,MAAM,OAAO,MAAM,SAAS,QAAQ,GAAG;AACvC,OAAI,MAAM;IACR,MAAM,QAAQC,YAAUC,aAAW;AACnC,UAAM,aAAa,MAAM,UAAU,MAAM;AAOzC,QAAI,KAAK,IAAK,OAAM,YAAY,MAAM,KAAK;AAC3C,gBAAU,OAAOA,aAAW;;UAExB;AAIR,MAAI,UAAU;GACZ,MAAM,OAAO,GAAG,SAAS,aAAa,UAAU,SAAS,iBAAiB,IAAI,KAAK;AACnF,WAAQ,IACN,SAAS,UACL,IAAI,uBAAuB,KAAK,MAAM,SAAS,OAAO,GACtD,IAAI,+BAA+B,KAAK,eAAe,CAC5D;;GAEH;;;;;;;;;;;;;;AAeN,eAAsB,eACpB,UACA,IACA,OAA8C,EAAE,EAC9B;AAClB,KAAI,CAAC,SAAS,WAAW,CAAC,SAAS,cAAc;AAC/C,MAAI,CAAC,KAAK,MAAO,SAAQ,IAAI,KAAK,KAAK,SAAS,WAAW,mCAAmC,CAAC;AAC/F,SAAO;;CAGT,MAAM,OAAO,MAAM,SAAS,QAAQ,GAAG;AACvC,KAAI,CAAC,MAAM;AACT,MAAI,CAAC,KAAK,MAAO,SAAQ,IAAI,KAAK,aAAa,GAAG,GAAG,CAAC;AACtD,SAAO;;CAGT,MAAM,OAAO,KAAK,MAAM;AACxB,KAAI,CAAC,KAAK,MAAM,WAAW,CAAC,MAAM;AAChC,MAAI,CAAC,KAAK,MACR,SAAQ,IACN,KAAK,2CAA2C,GAC9C,IAAI,8CAA8C,CACrD;AAEH,SAAO;;CAIT,MAAM,IAAI,aAAa,MAAM,MADZ,MAAM,SAAS,aAAa,GAAG,mBACH,IAAI,MAAM,EAAC,aAAa,CAAC;AAEtE,KAAI,EAAE,WAAW,KAAK,OACpB,OAAM,kBAAkB,MAAM,EAAE,MAAM,EAAE,cAAc,KAAK,MAAM;AAGnE,KAAI,CAAC,KAAK,OAAO;EACf,MAAM,OAAO,GAAG,EAAE,aAAa,UAAU,EAAE,iBAAiB,IAAI,KAAK;AACrE,MAAI,EAAE,YAAY,gBAChB,SAAQ,IAAI,IAAI,qDAAqD,CAAC;MAEtE,SAAQ,IACN,EAAE,UACE,GAAG,YAAY,KAAK,MAAM,GAAG,EAAE,OAC/B,IAAI,oBAAoB,KAAK,eAAe,CACjD;;AAGL,QAAO;;;;;;;;;;;;;;;;;;;;AAqBT,eAAe,kBACb,MACA,MACA,UACA,OACe;CACf,MAAM,UAAU,KAAK,MAAM;AAC3B,KAAI,CAAC,QAAS;AAEd,KAAI;EACF,MAAM,YAAY,MAAM,gBAAgB;AACxC,MAAI,CAAC,UAAW;EAEhB,MAAM,UAAU;GACd,gDAAgD,SAAS,UAAU,aAAa,IAAI,KAAK,IAAI,OAAO,KAAK,MAAM;GAC/G;GACA,2BAA2B;GAC3B,eAAe,KAAK;GACpB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAAC,KAAK,KAAK;EAGZ,MAAM,SAAS,MAAM,UAAU,SAAS,SAAS,SAAS,EAAE,eAAe,OAAO,CAAC;AAEnF,MAAI,CAAC,SAAS,OAAO,YAAY,YAC/B,SAAQ,IAAI,IAAI,cAAc,QAAQ,2BAA2B,CAAC;SAE9D;;;;;;;;;;AAaV,eAAe,kBACb,UACA,IAC0E;AAC1E,KAAI;AACF,MAAI,CAAC,SAAS,aAAc,QAAO;EAGnC,MAAM,QADQ,MAAM,SAAS,SAAS,EAAE,iBAAiB,MAAM,CAAC,EAC7C,MAAM,MAAM,EAAE,OAAO,GAAG;AAC3C,MAAI,CAAC,MAAM;AACT,WAAQ,IAAI,IAAI,6CAA6C,CAAC;AAC9D,UAAO;;EAGT,MAAM,UAAU,KAAK,MAAM;EAC3B,MAAM,OAAO,KAAK,MAAM;AACxB,MAAI,CAAC,WAAW,CAAC,MAAM;AACrB,WAAQ,IACN,KAAK,kDAAkD,GACrD,IAAI,gDAAgD,CACvD;AACD,UAAO;;AAIT,SAAO,aAAa,MAAM,MADT,MAAM,SAAS,aAAa,GAAG,mBACN,IAAI,MAAM,EAAC,aAAa,CAAC;UAC5D,GAAG;AACV,UAAQ,IACN,KAAK,uCAAuC,IACzC,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,EAC9C;AACD,SAAO;;;;;;AC7xBX,SAAS,aAAwB;AAE/B,QAAO,IAAI,UADI,YAAY,CACC,WAAW;;AAGzC,SAAS,cAAc,YAAoB,QAAQ,IAAY;CAC7D,MAAM,SAAS,KAAK,MAAM,aAAa,MAAM;CAC7C,MAAM,QAAQ,QAAQ;AACtB,QAAO,MAAM,IAAI,OAAO,OAAO,GAAG,IAAI,OAAO,MAAM,GAAG;;AAGxD,SAAS,iBAAiB,QAAgC;AACxD,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,oBAAoB,CAAC;AACxC,SAAQ,KAAK;AAEb,SAAQ,IAAI,KAAKC,OAAK,mBAAmB,CAAC,MAAM,OAAO,kBAAkBC,MAAI,SAAS,GAAG;AACzF,SAAQ,IAAI,KAAKD,OAAK,qBAAqB,CAAC,IAAI,OAAO,oBAAoBC,MAAI,SAAS,GAAG;AAC3F,SAAQ,IACN,KAAKD,OAAK,cAAc,CAAC,WAAW,cAAc,OAAO,WAAW,CAAC,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC,GAC9G;AACD,SAAQ,IAAI,KAAKA,OAAK,iBAAiB,CAAC,QAAQ,OAAO,aAAa;AAEpE,KAAI,OAAO,YAAY,SAAS,GAAG;AACjC,UAAQ,KAAK;AACb,UAAQ,IAAI,KAAKA,OAAK,eAAe,GAAG;AACxC,OAAK,MAAM,KAAK,OAAO,aAAa;GAClC,MAAM,MAAM,cAAc,EAAE,OAAO,GAAG;GACtC,MAAM,SAAS,EAAE,SAAS,OAAO,iBAAiBC,MAAI,aAAa,GAAG;AACtE,WAAQ,IAAI,OAAO,EAAE,KAAK,OAAO,GAAG,CAAC,GAAG,IAAI,IAAI,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC,GAAG,SAAS;;;AAI1F,SAAQ,KAAK;AAEb,KAAI,OAAO,SAAS;AAClB,UAAQ,IACNC,OAAK,yBAAyB,GAC9BD,MAAI,wCAAwC,OAAO,iBAAiB,UAAU,OAAO,eAAe,GAAG,CACxG;AACD,UAAQ,KAAK;YACJ,OAAO,oBAAoB,OAAO,qBAAqB,OAAO,gBAAgB;AACvF,UAAQ,IAAIE,KAAG,sBAAsB,GAAGF,MAAI,qCAAqC,CAAC;AAClF,UAAQ,KAAK;YACJ,CAAC,OAAO,gBAAgB;AACjC,MAAI,OAAO,iBACT,SAAQ,IACNE,KAAG,4BAA4B,GAAGH,OAAK,OAAO,iBAAiB,GAC/DC,MAAI,kBAAkB,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC,IAAI,CAC/D;MAED,SAAQ,IAAIA,MAAI,+CAA+C,CAAC;AAElE,UAAQ,KAAK;QACR;AACL,UAAQ,IACNA,MAAI,mCAAmC,OAAO,iBAAiB,oBAAoB,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC,sBAAsB,CAC9I;AACD,UAAQ,KAAK;;;AAQjB,eAAe,SACb,SACA,MAKe;CACf,MAAM,SAAS,YAAY;CAE3B,MAAM,YAAY,KAAK,YAAY,WAAW,KAAK,UAAU,GAAG;AAEhE,KAAI,cAAc,WAAc,MAAM,UAAU,IAAI,YAAY,KAAK,YAAY,IAAI;AACnF,UAAQ,MAAM,IAAI,iDAAiD,CAAC;AACpE,UAAQ,WAAW;AACnB;;AAGF,KAAI;EACF,MAAM,SAAS,MAAM,OAAO,WAAW;GACrC;GACA,gBAAgB,KAAK;GACrB;GACD,CAAC;AAEF,MAAI,KAAK,MAAM;AACb,WAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;AAC5C;;AAGF,mBAAiB,OAAO;AAGxB,MAAI,OAAO,SAAS;AAClB,WAAQ,WAAW;AACnB;;UAEK,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,KAAK;AACb,UAAQ,IAAIC,OAAK,6BAA6B,CAAC;AAC/C,UAAQ,IAAID,MAAI,KAAK,MAAM,CAAC;AAC5B,UAAQ,IAAIA,MAAI,oCAAoC,CAAC;AACrD,UAAQ,KAAK;AACb,UAAQ,WAAW;AACnB;;;AAQJ,SAAgB,sBAAsB,UAAyB;AAE7D,UACG,QAAQ,kBAAkB,CAC1B,YACC,qLAGD,CACA,OACC,oBACA,kDACD,CACA,OACC,mBACA,+DACD,CACA,OAAO,UAAU,yBAAyB,CAC1C,OACC,OACE,SACA,SACG;AACH,QAAM,SAAS,SAAS,KAAK;GAEhC;;;;;;;;;;;;;;;;;;AC3IL,MAAM,aAAa,KAAK,SAAS,EAAE,WAAW,OAAO,yBAAyB;AAO9E,SAAS,YAA2B;AAClC,KAAI;AACF,MAAI,WAAW,WAAW,EAAE;GAC1B,MAAM,MAAM,KAAK,MAAM,aAAa,YAAY,QAAQ,CAAC;AACzD,OAAI,OAAO,OAAO,QAAQ,YAAY,IAAI,UAAW,QAAO;;SAExD;AACR,QAAO,EAAE,WAAW,EAAE,EAAE;;AAG1B,SAAS,UAAU,OAA4B;AAC7C,KAAI;EACF,MAAM,MAAM,KAAK,SAAS,EAAE,WAAW,MAAM;AAC7C,MAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AACzD,gBAAc,YAAY,KAAK,UAAU,OAAO,MAAM,EAAE,EAAE,QAAQ;UAC3D,GAAG;AACV,UAAQ,OAAO,MAAM,uCAAuC,EAAE,IAAI;;;;;;;;AAwBtE,SAAS,iBAAiB,SAA+B;CACvD,MAAM,YAAY,KAAK,QAAQ,WAAW,QAAQ;AAClD,KAAI,CAAC,WAAW,UAAU,CAAE,QAAO,EAAE;CAErC,MAAM,QAAgD,EAAE;CAExD,SAAS,KAAK,KAAa,OAAqB;AAC9C,MAAI,QAAQ,EAAG;EACf,IAAI;AACJ,MAAI;AACF,aAAU,YAAY,IAAI;UACpB;AACN;;AAEF,OAAK,MAAM,QAAQ,SAAS;GAC1B,MAAM,OAAO,KAAK,KAAK,KAAK;GAC5B,IAAI;AACJ,OAAI;AACF,SAAK,SAAS,KAAK;WACb;AACN;;AAEF,OAAI,GAAG,aAAa,CAClB,MAAK,MAAM,QAAQ,EAAE;YACZ,GAAG,QAAQ,IAAI,KAAK,SAAS,MAAM,IAAI,WAAW,KAAK,KAAK,CACrE,OAAM,KAAK;IAAE,MAAM;IAAM,OAAO,GAAG;IAAS,CAAC;;;AAKnD,MAAK,WAAW,EAAE;AAElB,OAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;AACvC,QAAO,MAAM,KAAK,MAAM,EAAE,KAAK;;;;;;;;AAgCjC,eAAsB,oBACpB,UAA2B,EAAE,EACJ;CACzB,MAAM,SAAyB;EAC7B,iBAAiB;EACjB,mBAAmB;EACnB,eAAe;EACf,oBAAoB;EACpB,QAAQ;EACT;CAED,MAAM,SAAS,YAAY;AAC3B,KAAI,OAAO,mBAAmB,WAC5B,OAAM,IAAI,MACR,gHAED;CAGH,MAAM,UAAU,MAAM,qBAAqB,OAAO;AAClD,KAAI,QAAQ,gBAAgB,WAC1B,OAAM,IAAI,MACR,0EACD;CAGH,MAAM,OAAc,QAA2C,SAAS;CAGxE,MAAM,eAAe,gBAAgB;CAGrC,MAAM,WAAW,cAAc;CAC/B,IAAI;AACJ,KAAI;AACF,MAAI,QAAQ,aAAa;GACvB,MAAM,MAAM,SACT,QAAQ,0DAA0D,CAClE,IAAI,QAAQ,YAAY;AAC3B,OAAI,CAAC,IACH,OAAM,IAAI,MAAM,sBAAsB,QAAQ,cAAc;AAE9D,cAAW,CAAC,IAAI;QAEhB,YAAW,SACR,QACC,iFACD,CACA,KAAK;WAEF;AACR,WAAS,OAAO;;CAIlB,MAAM,QAAQ,WAAW;CACzB,MAAM,OAAsB,EAAE;AAC9B,MAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,QAAQ,iBAAiB,QAAQ;AACvC,OAAK,MAAM,YAAY,OAAO;AAC5B,OAAI,MAAM,UAAU,UAAW;AAC/B,QAAK,KAAK;IAAE;IAAS;IAAU,CAAC;;;CAIpC,MAAM,QAAQ,QAAQ,QAAQ,KAAK,IAAI,QAAQ,OAAO,KAAK,OAAO,GAAG,KAAK;AAG1E,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;EAC9B,MAAM,EAAE,SAAS,aAAa,KAAK;AACnC,UAAQ,aAAa,IAAI,GAAG,OAAO,SAAS;AAE5C,MAAI,QAAQ,QAAQ;AAClB,UAAO;AACP;;EAGF,IAAI;AACJ,MAAI;AACF,iBAAc,aAAa,UAAU,QAAQ;WACtC,GAAG;AACV,WAAQ,OAAO,MAAM,6BAA6B,SAAS,IAAI,EAAE,IAAI;AACrE,UAAO;AACP;;AAGF,MAAI;GACF,MAAM,QAAQ,MAAM,uBAAuB,MAAM;IAC/C,aAAa;IACb,aAAa,QAAQ;IACrB,WAAW,QAAQ;IACnB,WAAW,YAAY;IACvB,QAAQ;IACR,OAAO;IACP;IACD,CAAC;AAEF,UAAO;AACP,UAAO,qBAAqB,MAAM;AAClC,UAAO,iBAAiB,MAAM;AAC9B,UAAO,sBAAsB,MAAM;AAInC,SAAM,UAAU,6BAAY,IAAI,MAAM,EAAC,aAAa;AAEpD,QAAK,IAAI,KAAK,MAAM,EAAG,WAAU,MAAM;WAChC,GAAG;AACV,WAAQ,OAAO,MAAM,gCAAgC,SAAS,IAAI,EAAE,IAAI;AACxE,UAAO;;;AAIX,KAAI,CAAC,QAAQ,OAAQ,WAAU,MAAM;AACrC,cAAa,OAAO;AACpB,OAAM,QAAQ,OAAO;AAErB,QAAO;;;;;ACzOT,eAAeG,YAA+D;CAC5E,MAAM,SAAS,YAAY;AAC3B,KAAI,OAAO,mBAAmB,YAAY;AACxC,UAAQ,MAAM,IAAI,0CAA0C,CAAC;AAC7D,UAAQ,MAAMC,MAAI,sEAAkE,CAAC;AACrF,UAAQ,KAAK,EAAE;;CAEjB,MAAM,UAAU,MAAM,qBAAqB,OAAO;AAClD,KAAI,QAAQ,gBAAgB,YAAY;AACtC,UAAQ,MAAM,IAAI,wDAAwD,CAAC;AAC3E,UAAQ,KAAK,EAAE;;AAGjB,QAAO;EAAE,MADK,QAA2C,SAAS;EACnD,aAAa,QAAQ,OAAO;EAAE;;AAG/C,SAASC,UAAQ,GAAW,GAAmB;AAC7C,QAAO,EAAE,UAAU,IAAI,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG;;AAOjD,eAAe,YAAY,MAIT;CAChB,MAAM,QAAQ,KAAK,QAAQ,SAAS,KAAK,OAAO,GAAG,GAAG;AACtD,KAAI,UAAU,WAAc,MAAM,MAAM,IAAI,QAAQ,IAAI;AACtD,UAAQ,MAAM,IAAI,uCAAuC,CAAC;AAC1D,UAAQ,WAAW;AACnB;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,oBAAoB,CAAC;AACxC,SAAQ,KAAK;AACb,KAAI,KAAK,QAAS,SAAQ,IAAI,KAAKC,OAAK,WAAW,CAAC,GAAG,KAAK,UAAU;AACtE,KAAI,MAAO,SAAQ,IAAI,KAAKA,OAAK,SAAS,CAAC,KAAK,QAAQ;AACxD,KAAI,KAAK,OAAQ,SAAQ,IAAI,KAAKA,OAAK,QAAQ,CAAC,MAAMC,OAAK,UAAU,GAAG;AACxE,SAAQ,KAAK;AAEb,KAAI;EACF,MAAM,SAAS,MAAM,oBAAoB;GACvC,aAAa,KAAK;GAClB;GACA,QAAQ,KAAK;GACb,aAAa,SAAS,OAAO,SAAS;IACpC,MAAM,QAAQF,UAAQ,KAAK,QAAQ,QAAQ,IAAI,QAAQ,IAAI,IAAI,EAAE,GAAG;AACpE,YAAQ,OAAO,MAAM,MAAM,QAAQ,GAAG,MAAM,IAAID,MAAI,MAAM,CAAC,IAAI;;GAElE,CAAC;AAEF,UAAQ,KAAK;AACb,UAAQ,IAAII,KAAG,sBAAsB,CAAC;AACtC,UAAQ,IAAI,2BAA2BF,OAAK,OAAO,OAAO,gBAAgB,CAAC,GAAG;AAC9E,UAAQ,IAAI,2BAA2BA,OAAK,OAAO,OAAO,kBAAkB,CAAC,GAAG;AAChF,UAAQ,IAAI,2BAA2BA,OAAK,OAAO,OAAO,cAAc,CAAC,GAAG;AAC5E,UAAQ,IAAI,2BAA2BA,OAAK,OAAO,OAAO,mBAAmB,CAAC,GAAG;AACjF,MAAI,OAAO,SAAS,EAClB,SAAQ,IAAI,OAAOC,OAAK,UAAU,CAAC,eAAe,OAAO,SAAS;AAEpE,UAAQ,KAAK;UACN,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,MAAM,IAAI,KAAK,MAAM,CAAC;AAC9B,UAAQ,WAAW;AACnB;;;AAQJ,eAAeE,WAAS,MAON;CAChB,MAAM,EAAE,MAAM,UAAU,MAAMN,WAAS;AACvC,KAAI;EACF,IAAI;AACJ,MAAI,KAAK,SAAS;GAChB,MAAM,IAAI,MAAM,KAAK,MACnB,mDACA,CAAC,KAAK,QAAQ,CACf;AACD,OAAI,EAAE,KAAK,WAAW,EACpB,SAAQ,MAAMI,OAAK,oCAAoC,KAAK,UAAU,CAAC;OAEvE,aAAY,EAAE,KAAK,GAAG;;EAI1B,MAAM,OAAO,KAAK,OAAO,IAAI,KAAK,KAAK,KAAK,GAAG;AAC/C,MAAI,QAAQ,MAAM,KAAK,SAAS,CAAC,EAAE;AACjC,WAAQ,MAAM,IAAI,2BAA2B,KAAK,OAAO,CAAC;AAC1D,WAAQ,WAAW;AACnB;;EAGF,MAAM,UAAU,MAAM,QAAQ,MAAM;GAClC,SAAS,KAAK;GACd,WAAW,KAAK;GAChB,QAAQ,KAAK;GACb,YAAY;GACZ,OAAO;GACR,CAAC;AAEF,MAAI,KAAK,MAAM;AACb,WAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC;AAC7C;;AAGF,UAAQ,KAAK;AACb,UAAQ,IAAI,OAAO,KAAK,QAAQ,OAAO,YAAY,CAAC;AACpD,UAAQ,KAAK;AACb,OAAK,MAAM,KAAK,SAAS;GACvB,MAAM,WAAW,EAAE,WACfH,MAAI,gBAAgB,EAAE,SAAS,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,GAC7DA,MAAI,gBAAgB,EAAE,WAAW,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG;AACnE,WAAQ,IACN,KAAKE,OAAK,EAAE,QAAQ,CAAC,GAAGF,MAAI,IAAI,CAAC,GAAG,EAAE,UAAU,GAAGA,MAAI,IAAI,CAAC,GAAG,EAAE,OAAO,IAAI,WAC7E;;AAEH,UAAQ,KAAK;WACL;AACR,QAAM,OAAO;;;AAQjB,eAAeM,UAAQ,MAA2D;CAChF,MAAM,QAAQ,KAAK,QAAQ,SAAS,KAAK,OAAO,GAAG,GAAG;CACtD,MAAM,EAAE,MAAM,UAAU,MAAMP,WAAS;AACvC,KAAI;EACF,IAAI;AACJ,MAAI,KAAK,SAAS;GAChB,MAAM,IAAI,MAAM,KAAK,MACnB,mDACA,CAAC,KAAK,QAAQ,CACf;AACD,OAAI,EAAE,KAAK,SAAS,EAAG,aAAY,EAAE,KAAK,GAAG;;EAG/C,MAAM,UAAU,MAAM,QAAQ,MAAM,EAAE,YAAY,WAAW,CAAC;EAC9D,MAAM,QAAQ,QAAQ,MAAM,GAAG,MAAM;AAErC,UAAQ,KAAK;AACb,UAAQ,IACN,OAAO,KAAK,MAAM,OAAO,MAAM,QAAQ,OAAO,4BAA4B,CAC3E;AACD,UAAQ,KAAK;AACb,OAAK,MAAM,KAAK,MACd,SAAQ,IACN,KAAKG,OAAK,EAAE,QAAQ,CAAC,GAAGF,MAAI,IAAI,CAAC,GAAG,EAAE,UAAU,GAAGA,MAAI,IAAI,CAAC,GAAG,EAAE,SAClE;AAEH,MAAI,QAAQ,SAAS,MAAM,QAAQ;AACjC,WAAQ,KAAK;AACb,WAAQ,IAAIA,MAAI,MAAM,QAAQ,SAAS,MAAM,OAAO,2BAA2B,CAAC;;AAElF,UAAQ,KAAK;WACL;AACR,QAAM,OAAO;;;AAQjB,eAAe,WAA0B;CACvC,MAAM,EAAE,MAAM,UAAU,MAAMD,WAAS;AACvC,KAAI;EACF,MAAM,SAAS,MAAM,KAAK,MAOxB;;;;;;wBAOD;EAED,MAAM,iBAAiB,MAAM,KAAK,MAChC;;;;;;YAOD;EAED,MAAM,MAAM,OAAO,KAAK,MAAM,EAAE;AAChC,UAAQ,KAAK;AACb,UAAQ,IAAI,OAAO,iBAAiB,CAAC;AACrC,UAAQ,KAAK;AACb,UAAQ,IAAI,KAAKG,OAAK,iBAAiB,CAAC,UAAU,IAAI,SAAS,MAAM;AACrE,UAAQ,IAAI,KAAKA,OAAK,mBAAmB,CAAC,QAAQ,IAAI,SAAS,MAAM;AACrE,UAAQ,IAAI,KAAKA,OAAK,eAAe,CAAC,YAAY,IAAI,eAAe,MAAM;AAC3E,UAAQ,IAAI,KAAKA,OAAK,qBAAqB,CAAC,MAAM,IAAI,YAAY,MAAM;AACxE,UAAQ,IAAI,KAAKA,OAAK,uBAAuB,CAAC,IAAI,IAAI,cAAc,MAAM;AAC1E,UAAQ,IAAI,KAAKA,OAAK,kBAAkB,CAAC,SAAS,eAAe,KAAK,IAAI,SAAS,MAAM;AACzF,UAAQ,KAAK;WACL;AACR,QAAM,OAAO;;;AAQjB,SAAgB,mBAAmB,OAAsB;AACvD,OACG,QAAQ,WAAW,CACnB,YAAY,2DAA2D,CACvE,OAAO,oBAAoB,wCAAwC,CACnE,OAAO,eAAe,qCAAqC,CAC3D,OAAO,aAAa,wDAAwD,CAC5E,OAAO,OAAO,SAAiE;AAC9E,QAAM,YAAY,KAAK;GACvB;AAEJ,OACG,QAAQ,QAAQ,CAChB,YAAY,mEAAmE,CAC/E,OAAO,iBAAiB,oBAAoB,CAC5C,OAAO,mBAAmB,sBAAsB,CAChD,OAAO,gBAAgB,mBAAmB,CAC1C,OAAO,kBAAkB,+CAA+C,CACxE,OAAO,oBAAoB,6BAA6B,CACxD,OAAO,UAAU,kBAAkB,CACnC,OACC,OAAO,SAOD;AACJ,QAAMG,WAAS,KAAK;GAEvB;AAEH,OACG,QAAQ,OAAO,CACf,YAAY,+BAA+B,CAC3C,OAAO,oBAAoB,6BAA6B,CACxD,OAAO,eAAe,4BAA4B,KAAK,CACvD,OAAO,OAAO,SAA+C;AAC5D,QAAMC,UAAQ,KAAK;GACnB;AAEJ,OACG,QAAQ,QAAQ,CAChB,YAAY,6CAA6C,CACzD,OAAO,YAAY;AAClB,QAAM,UAAU;GAChB;;;;;AChRN,SAAS,cAAc;AACrB,QAAO,gBAAgB;;AAGzB,SAAS,YAAY,KAAuD;CAC1E,MAAM,KAAK,aAAa;AACxB,KAAI;EAEF,MAAM,MADO,GAAG,QAAQ,IAAI,CACX,KAAK;AACtB,MAAI,IAAI,WAAW,EAAG,QAAO;GAAE,SAAS,EAAE;GAAE,MAAM,EAAE;GAAE;EACtD,MAAM,UAAU,OAAO,KAAK,IAAI,GAAG;AAEnC,SAAO;GAAE;GAAS,MADL,IAAI,KAAK,MAAM,QAAQ,KAAK,MAAM,EAAE,GAAG,CAAC;GAC7B;WAChB;AACR,KAAG,OAAO;;;AAId,SAAS,eAAyB;CAChC,MAAM,KAAK,aAAa;AACxB,KAAI;AAMF,SALa,GACV,QACC,+FACD,CACA,KAAK,CACI,KAAK,MAAM,EAAE,KAAK;WACtB;AACR,KAAG,OAAO;;;AAId,SAAS,aAAa,OAAyD;CAC7E,MAAM,KAAK,aAAa;AACxB,KAAI;AAEF,MAAI,CAAC,2BAA2B,KAAK,MAAM,CACzC,OAAM,IAAI,MAAM,uBAAuB,QAAQ;EAEjD,MAAM,OAAO,GAAG,QAAQ,qBAAqB,MAAM,GAAG,CAAC,KAAK;AAQ5D,MAAI,KAAK,WAAW,EAClB,OAAM,IAAI,MAAM,oBAAoB,QAAQ;AAW9C,SAAO;GAAE,SATO;IAAC;IAAO;IAAQ;IAAQ;IAAW;IAAW;IAAK;GASjD,MARL,KAAK,KAAK,MAAM;IAC3B,EAAE;IACF,EAAE;IACF,EAAE;IACF,EAAE,UAAU,aAAa;IACzB,EAAE,cAAc;IAChB,EAAE,KAAK,OAAO;IACf,CAAC;GAC4B;WACtB;AACR,KAAG,OAAO;;;AAQd,eAAe,UAAU;CAEvB,MAAM,WADS,YAAY,CACH,YAAY,EAAE;CAEtC,MAAM,EAAE,SAAS,MAAM,OAAO;CAc9B,MAAM,OAAO,IAAI,KAXE,SAAS,mBACxB,EAAE,kBAAkB,SAAS,kBAAkB,GAC/C;EACE,MAAM,SAAS,QAAQ;EACvB,MAAM,SAAS,QAAQ;EACvB,UAAU,SAAS,YAAY;EAC/B,MAAM,SAAS,QAAQ;EACvB,UAAU,SAAS,YAAY;EAC/B,yBAAyB,SAAS,uBAAuB;EAC1D,CAE4B;AAGjC,KAAI;AAEF,GADe,MAAM,KAAK,SAAS,EAC5B,SAAS;UACT,GAAG;AACV,QAAM,KAAK,KAAK;EAChB,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,QAAM,IAAI,MAAM,+BAA+B,MAAM;;AAGvD,QAAO;;AAGT,eAAe,cACb,KACmD;CACnD,MAAM,OAAO,MAAM,SAAS;AAC5B,KAAI;EACF,MAAM,SAAS,MAAM,KAAK,MAAM,IAAI;EACpC,MAAM,UAAU,OAAO,OAAO,KAAK,MAAM,EAAE,KAAK;AAIhD,SAAO;GAAE;GAAS,MAHL,OAAO,KAAK,KAAK,MAC5B,QAAQ,KAAK,MAAM,EAAE,GAAG,CACzB;GACuB;WAChB;AACR,QAAM,KAAK,KAAK;;;AAIpB,eAAe,iBAAoC;CACjD,MAAM,OAAO,MAAM,SAAS;AAC5B,KAAI;AAIF,UAHe,MAAM,KAAK,MACxB,iFACD,EACa,KAAK,KAAK,MAAM,EAAE,UAAU;WAClC;AACR,QAAM,KAAK,KAAK;;;AAIpB,eAAe,eACb,OACmD;CACnD,MAAM,OAAO,MAAM,SAAS;AAC5B,KAAI;EACF,MAAM,SAAS,MAAM,KAAK,MAMxB;;;mCAIA,CAAC,MAAM,CACR;AACD,MAAI,OAAO,KAAK,WAAW,EACzB,OAAM,IAAI,MAAM,qCAAqC,QAAQ;AAS/D,SAAO;GAAE,SAPO;IAAC;IAAU;IAAQ;IAAY;IAAU;GAOvC,MANL,OAAO,KAAK,KAAK,MAAM;IAClC,EAAE;IACF,EAAE;IACF,EAAE,gBAAgB,QAAQ,QAAQ;IAClC,EAAE,kBAAkB;IACrB,CAAC;GACsB;WAChB;AACR,QAAM,KAAK,KAAK;;;AAQpB,SAAS,YACP,QACA,MACM;AACN,KAAI,MAAM;EACR,MAAM,UAAU,OAAO,KAAK,KAAK,QAAQ;GACvC,MAAM,MAA+B,EAAE;AACvC,UAAO,QAAQ,SAAS,KAAK,MAAM;AACjC,QAAI,OAAO,IAAI;KACf;AACF,UAAO;IACP;AACF,UAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC;AAC7C;;AAGF,KAAI,OAAO,KAAK,WAAW,GAAG;AAC5B,UAAQ,KAAK;AACb,UAAQ,IAAIC,MAAI,cAAc,CAAC;AAC/B,UAAQ,KAAK;AACb;;CAGF,MAAM,aAAa,OAAO,KAAK,KAAK,QAClC,IAAI,KAAK,SAAS;AAChB,MAAI,SAAS,QAAQ,SAAS,OAAW,QAAOA,MAAI,OAAO;AAC3D,MAAI,OAAO,SAAS,SAAU,QAAO,KAAK,UAAU,KAAK;AACzD,SAAO,OAAO,KAAK;GACnB,CACH;AAED,SAAQ,KAAK;AACb,SAAQ,IAAI,YAAY,OAAO,SAAS,WAAW,CAAC;AACpD,SAAQ,KAAK;AACb,SAAQ,IAAIA,MAAI,KAAK,OAAO,KAAK,OAAO,SAAS,CAAC;AAClD,SAAQ,KAAK;;AAOf,eAAe,SACb,KACA,MACe;CACf,MAAM,SAAmB,KAAK,MAAM;AACpC,SAAQ,KAAK;AACb,SAAQ,IACN,OAAO,YAAY,OAAO,GAAG,GAAGA,MAAI,KAAK,IAAI,MAAM,GAAG,GAAG,GAAG,IAAI,SAAS,KAAK,MAAM,KAAK,CAC1F;AAED,KAAI;EACF,IAAI;AACJ,MAAI,WAAW,WACb,UAAS,MAAM,cAAc,IAAI;MAEjC,UAAS,YAAY,IAAI;AAE3B,cAAY,QAAQ,KAAK,QAAQ,MAAM;UAChC,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,MAAM,IAAI,KAAK,MAAM,CAAC;AAC9B,UAAQ,WAAW;AACnB;;;AAQJ,eAAe,UAAU,MAAiC;CACxD,MAAM,SAAmB,KAAK,MAAM;AACpC,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,aAAa,OAAO,GAAG,CAAC;AAC3C,SAAQ,KAAK;AAEb,KAAI;EACF,IAAI;AACJ,MAAI,WAAW,WACb,UAAS,MAAM,gBAAgB;MAE/B,UAAS,cAAc;AAGzB,MAAI,KAAK,MAAM;AACb,WAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;AAC5C;;AAGF,MAAI,OAAO,WAAW,EACpB,SAAQ,IAAIA,MAAI,sBAAsB,CAAC;MAEvC,MAAK,MAAM,KAAK,OACd,SAAQ,IAAI,KAAKC,OAAK,EAAE,GAAG;AAG/B,UAAQ,KAAK;AACb,UAAQ,IAAID,MAAI,KAAK,OAAO,OAAO,WAAW,CAAC;AAC/C,UAAQ,KAAK;UACN,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,MAAM,IAAI,KAAK,MAAM,CAAC;AAC9B,UAAQ,WAAW;AACnB;;;AAQJ,eAAe,UAAU,OAAe,MAAiC;CACvE,MAAM,SAAmB,KAAK,MAAM;AACpC,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,aAAa,MAAM,IAAI,OAAO,GAAG,CAAC;AAErD,KAAI;EACF,IAAI;AACJ,MAAI,WAAW,WACb,UAAS,MAAM,eAAe,MAAM;OAC/B;GACL,MAAM,MAAM,aAAa,MAAM;AAI/B,YAAS;IAAE,SAAS,IAAI;IAAS,MAAM,IAAI;IAAM;;AAEnD,cAAY,QAAQ,KAAK,QAAQ,MAAM;UAChC,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,MAAM,IAAI,KAAK,MAAM,CAAC;AAC9B,UAAQ,WAAW;AACnB;;;AAQJ,SAAgB,mBAAmB,OAAsB;AACvD,OACG,QAAQ,cAAc,CACtB,YAAY,gDAAgD,CAC5D,OAAO,iBAAiB,iDAAiD,SAAS,CAClF,OAAO,UAAU,+BAA+B,CAChD,OAAO,OAAO,KAAa,SAAqB;AAC/C,QAAM,SAAS,KAAK,KAAK;GACzB;AAEJ,OACG,QAAQ,SAAS,CACjB,YAAY,2CAA2C,CACvD,OAAO,iBAAiB,iDAAiD,SAAS,CAClF,OAAO,UAAU,uBAAuB,CACxC,OAAO,OAAO,SAAqB;AAClC,QAAM,UAAU,KAAK;GACrB;AAEJ,OACG,QAAQ,iBAAiB,CACzB,YAAY,iCAAiC,CAC7C,OAAO,iBAAiB,iDAAiD,SAAS,CAClF,OAAO,UAAU,uBAAuB,CACxC,OAAO,OAAO,OAAe,SAAqB;AACjD,QAAM,UAAU,OAAO,KAAK;GAC5B;;;;;;;;;;ACpVN,SAAS,cAA6B;CACpC,MAAM,aAAuB,EAAE;AAC/B,KAAI;AACF,aAAW,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,CAAC;SAClD;AAGR,KAAI,QAAQ,KAAK,GAAI,YAAW,KAAK,QAAQ,QAAQ,KAAK,GAAG,CAAC;AAE9D,MAAK,MAAM,SAAS,YAAY;EAC9B,IAAI,MAAM;AACV,OAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;GAC1B,MAAM,QAAQ,KAAK,KAAK,QAAQ,WAAW;AAC3C,OAAI,WAAW,MAAM,CAAE,QAAO;GAC9B,MAAM,SAAS,QAAQ,IAAI;AAC3B,OAAI,WAAW,IAAK;AACpB,SAAM;;;AAGV,QAAO;;;;;;;AAQT,SAAS,WAAW,GAAmB;AACrC,QAAO,EACJ,QAAQ,SAAS,IAAI,CACrB,QAAQ,qBAAqB,IAAI,MAAM,MAAM,KAAK,EAAE,CAAC;;AAG1D,SAAS,eAAe,IAAoB;CAC1C,MAAM,MAAgB,EAAE;CACxB,IAAI,UAAU;AACd,MAAK,MAAM,QAAQ,GAAG,MAAM,KAAK,EAAE;AAEjC,MAAI,oBAAoB,KAAK,KAAK,CAAE;AAEpC,MAAI,OAAO,KAAK,KAAK,EAAE;AACrB,aAAU,CAAC;AACX;;AAEF,MAAI,SAAS;AACX,OAAI,KAAK,MAAM,KAAK,KAAK,CAAC;AAC1B;;AAEF,MAAI,YAAY,KAAK,KAAK,EAAE;AAC1B,OAAI,KAAK,MAAM,KAAK,KAAK,QAAQ,aAAa,GAAG,CAAC,CAAC;AACnD;;AAEF,MAAI,QAAQ,KAAK,KAAK,EAAE;AACtB,OAAI,KAAKE,MAAI,KAAK,QAAQ,SAAS,GAAG,CAAC,CAAC;AACxC;;AAGF,MAAI,sCAAsC,KAAK,KAAK,EAAE;AACpD,OAAI,KAAKA,MAAI,KAAK,CAAC;AACnB;;AAEF,MAAI,KAAK,WAAW,KAAK,CAAC;;AAG5B,QAAO,IAAI,SAAS,KAAK,IAAI,GAAG,MAAM,KAAK,GAAI,KAAI,OAAO;AAC1D,QAAO,IAAI,KAAK,KAAK;;AAGvB,SAAS,UAAU,SAA2B;AAC5C,QAAO,YAAY,QAAQ,CACxB,QAAQ,MAAM,EAAE,SAAS,MAAM,IAAI,MAAM,YAAY,CACrD,KAAK,MAAM,EAAE,QAAQ,SAAS,GAAG,CAAC,CAClC,MAAM;;AAGX,SAAgB,oBAAoB,SAAwB;AAC1D,SACG,QAAQ,cAAc,CACtB,YAAY,4EAA4E,CACxF,OAAO,SAAS,8CAA8C,CAC9D,QAAQ,MAA0B,SAA4B;EAC7D,MAAM,UAAU,aAAa;AAE7B,MAAI,CAAC,SAAS;AACZ,WAAQ,MAAM,IAAI,4BAA4B,CAAC;AAC/C,WAAQ,MAAMA,MAAI,sEAAsE,CAAC;AACzF,WAAQ,WAAW;AACnB;;AAIF,MAAI,CAAC,MAAM;GACT,MAAM,YAAY,KAAK,SAAS,YAAY;AAC5C,OAAI,WAAW,UAAU,EAAE;IACzB,MAAM,KAAK,aAAa,WAAW,OAAO;AAC1C,YAAQ,IAAI,KAAK,MAAM,KAAK,eAAe,GAAG,CAAC;UAC1C;AACL,YAAQ,IAAI,MAAM,KAAK,wBAAwB,CAAC;AAChD,SAAK,MAAM,KAAK,UAAU,QAAQ,CAAE,SAAQ,IAAI,KAAK,IAAI;AACzD,YAAQ,IAAIA,MAAI,sDAAsD,CAAC;;AAEzE;;EAIF,MAAM,OAAO,KAAK,SAAS,GAAG,KAAK,KAAK;AACxC,MAAI,WAAW,KAAK,EAAE;GACpB,MAAM,KAAK,aAAa,MAAM,OAAO;AACrC,WAAQ,IAAI,KAAK,MAAM,KAAK,eAAe,GAAG,CAAC;AAC/C;;AAGF,UAAQ,MAAM,IAAI,sBAAsB,KAAK,IAAI,CAAC;EAClD,MAAM,QAAQ,UAAU,QAAQ;EAChC,MAAM,OAAO,MAAM,QAAQ,MAAM,EAAE,SAAS,KAAK,IAAI,KAAK,SAAS,EAAE,CAAC;AACtE,MAAI,KAAK,SAAS,EAChB,SAAQ,MAAMA,MAAI,mBAAmB,KAAK,KAAK,KAAK,CAAC,GAAG,CAAC;MAEzD,SAAQ,MAAMA,MAAI,gBAAgB,MAAM,KAAK,KAAK,GAAG,CAAC;AAExD,UAAQ,MAAMA,MAAI,iBAAiB,KAAK,SAAS,CAAC;AAClD,UAAQ,WAAW;GACnB;;;;;;;;;;;;;;;;;;AC/GN,SAAS,YAAY,KAA6C;CAChE,MAAM,MAAM,IAAI;AAChB,KAAI,CAAC,IAAK,QAAO;CAEjB,MAAM,UAAU,IAAI;AAGpB,KAAI,MAAM,QAAQ,QAAQ,EAAE;EAC1B,MAAM,QAAkB,EAAE;AAC1B,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,IAAI;AACV,OAAI,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,SACzC,OAAM,KAAK,EAAE,KAAK;;AAGtB,SAAO,MAAM,KAAK,IAAI,IAAI;;AAI5B,KAAI,OAAO,YAAY,SACrB,QAAO,WAAW;AAGpB,QAAO;;;;;AAMT,SAASC,WAAS,MAAwB;AACxC,QAAO,KACJ,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,MAAM,MAAM,CACZ,QAAQ,MAAM;AACb,MAAI,EAAE,SAAS,EAAG,QAAO;AACzB,MAAI,QAAQ,KAAK,EAAE,CAAE,QAAO;AAC5B,MAAI,WAAW,IAAI,EAAE,CAAE,QAAO;AAC9B,SAAO;GACP;;;;;;;;;AAcN,SAAgB,qBAAqB,YAAmC;CACtE,MAAM,cAAc,KAClB,SAAS,EACT,WACA,YACA,YACA,WACD;AAED,KAAI,CAAC,WAAW,YAAY,CAAE,QAAO;CAErC,IAAI;AACJ,KAAI;AACF,YAAU,YAAY,YAAY,CAAC,QAAQ,MAAM,EAAE,SAAS,SAAS,CAAC;SAChE;AACN,SAAO;;AAGT,KAAI,CAAC,QAAQ,OAAQ,QAAO;AAc5B,QAXe,QACZ,KAAK,MAAM;EACV,MAAM,WAAW,KAAK,aAAa,EAAE;AACrC,MAAI;AACF,UAAO;IAAE,MAAM;IAAU,OAAO,SAAS,SAAS,CAAC;IAAS;UACtD;AACN,UAAO;IAAE,MAAM;IAAU,OAAO;IAAG;;GAErC,CACD,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CAEtB,GAAG;;;;;;;;;;;;AAanB,SAAgB,iBACd,WACA,QAAgB,IACN;AACV,KAAI,CAAC,WAAW,UAAU,CAAE,QAAO,EAAE;CAErC,IAAI;AACJ,KAAI;AACF,QAAM,aAAa,WAAW,OAAO;SAC/B;AACN,SAAO,EAAE;;CAGX,MAAM,QAAQ,IAAI,MAAM,KAAK,CAAC,QAAQ,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE;CAChE,MAAM,WAAqB,EAAE;CAC7B,MAAM,QAAQ,QAAQ;AAGtB,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,KAAK,SAAS,SAAS,OAAO,KAAK;EACrE,IAAI;AACJ,MAAI;AACF,SAAM,KAAK,MAAM,MAAM,GAAG;UACpB;AACN;;EAGF,MAAM,OAAO,IAAI;AACjB,MAAI,SAAS,eAAe,SAAS,OAAQ;EAE7C,MAAM,OAAO,YAAY,IAAI;AAC7B,MAAI,QAAQ,KAAK,MAAM,CAAC,SAAS,EAE/B,UAAS,QAAQ,KAAK;;AAI1B,QAAO;;;;;;;;;;;;;;;;AAiBT,SAAgB,aAAa,UAAoB,MAA4B;CAC3E,MAAM,WAAW,MAAM,YAAY;CACnC,MAAM,YAAY,MAAM,aAAa;AAErC,KAAI,CAAC,SAAS,OAAQ,QAAO;CAG7B,MAAM,uBAAO,IAAI,KAAqB;AACtC,MAAK,MAAM,OAAO,SAChB,MAAK,MAAM,SAASA,WAAS,IAAI,CAC/B,MAAK,IAAI,QAAQ,KAAK,IAAI,MAAM,IAAI,KAAK,EAAE;AAI/C,KAAI,CAAC,KAAK,KAAM,QAAO;CAGvB,MAAM,WAAW,MAAM,KAAK,KAAK,SAAS,CAAC,CACxC,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG,CAC3B,MAAM,GAAG,SAAS,CAClB,KAAK,CAAC,UAAU,KAAK;AAExB,KAAI,CAAC,SAAS,OAAQ,QAAO;CAG7B,IAAI,OAAO,SAAS,KAAK,IAAI;AAC7B,KAAI,KAAK,SAAS,UAChB,QAAO,KAAK,MAAM,GAAG,UAAU,CAAC,QAAQ,WAAW,GAAG;AAGxD,QAAO,QAAQ;;;;;AC9MjB,SAAgBC,aAAW,IAAc,MAAsC;AAC7E,QAAO,GACJ,QACC,qFACD,CACA,IAAI,KAAK;;AAGd,SAAgB,YAAY,QAAwB;AAClD,SAAQ,QAAR;EACE,KAAK,YACH,QAAO,MAAM,MAAM,OAAO;EAC5B,KAAK,YACH,QAAO,MAAM,KAAK,OAAO;EAC3B,QACE,QAAO,MAAM,OAAO,OAAO;;;;AAKjC,SAAgBC,cAAY,MAAsB;AAChD,QAAO,KACJ,QAAQ,MAAM,IAAI,CAClB,QAAQ,UAAU,MAAM,EAAE,aAAa,CAAC;;;AAI7C,SAAgB,YAAY,SAA6B;AACvD,QAAO,KAAK,SAAS,EAAE,WAAW,YAAY,QAAQ,aAAa,QAAQ;;;AAI7E,SAAgB,eACd,QACA,MACA,WACQ;AAER,QAAO,GADG,OAAO,OAAO,CAAC,SAAS,GAAG,IAAI,CAC7B,KAAK,KAAK,KAAK,UAAU;;;AAIvC,SAAgB,eACd,IACA,SACA,gBACY;CACZ,IAAI;AAEJ,KAAI,mBAAmB,SACrB,WAAU,GACP,QACC,2EACD,CACA,IAAI,QAAQ,GAAG;MACb;EACL,MAAM,MAAM,SAAS,gBAAgB,GAAG;AACxC,MAAI,MAAM,IAAI,EAAE;AACd,WAAQ,MAAM,IAAI,2BAA2B,iBAAiB,CAAC;AAC/D,WAAQ,KAAK,EAAE;;AAEjB,YAAU,GACP,QAAQ,6DAA6D,CACrE,IAAI,QAAQ,IAAI,IAAI;;AAGzB,KAAI,CAAC,SAAS;AACZ,UAAQ,MACN,IAAI,WAAW,eAAe,wBAAwB,QAAQ,OAAO,CACtE;AACD,UAAQ,KAAK,EAAE;;AAGjB,QAAO;;AAGT,SAAgB,UAAU,IAAc,SAAyB;AAC/D,IAAG,QAAQ,+CAA+C,CAAC,IAAI,QAAQ;AAIvE,QAHY,GACT,QAAQ,qCAAqC,CAC7C,IAAI,QAAQ,CACJ;;AAGb,SAAgB,eAAe,IAAc,WAA6B;AASxE,QARa,GACV,QACC;;;wBAID,CACA,IAAI,UAAU,CACL,KAAK,MAAM,EAAE,KAAK;;;;;ACzDhC,SAAgB,QACd,IACA,aACA,MACM;CACN,MAAM,QAAQ,SAAS,KAAK,SAAS,MAAM,GAAG;CAC9C,MAAM,SAAoB,EAAE;CAE5B,IAAI,QAAQ;;;;;CAMZ,MAAM,QAAkB,EAAE;AAC1B,KAAI,aAAa;EACf,MAAM,UAAUC,aAAW,IAAI,YAAY;AAC3C,MAAI,CAAC,SAAS;AACZ,WAAQ,MAAM,IAAI,sBAAsB,cAAc,CAAC;AACvD,WAAQ,WAAW;AACnB;;AAEF,QAAM,KAAK,mBAAmB;AAC9B,SAAO,KAAK,QAAQ,GAAG;;AAEzB,KAAI,KAAK,QAAQ;AACf,QAAM,KAAK,eAAe;AAC1B,SAAO,KAAK,KAAK,OAAO;;AAG1B,KAAI,MAAM,OAAQ,UAAS,YAAY,MAAM,KAAK,QAAQ;AAC1D,UAAS;AACT,UAAS,UAAU;CAEnB,MAAM,OAAO,GAAG,QAAQ,MAAM,CAAC,IAAI,GAAG,OAAO;AAK7C,KAAI,CAAC,KAAK,QAAQ;AAChB,UAAQ,IAAIC,OAAK,qBAAqB,CAAC;AACvC;;CAGF,MAAM,cAAc,CAAC;CACrB,MAAM,UAAU,cACZ;EAAC;EAAK;EAAW;EAAQ;EAAS;EAAU;EAAS,GACrD;EAAC;EAAK;EAAQ;EAAS;EAAU;EAAS;CAE9C,MAAM,YAAY,KAAK,KAAK,MAAM;EAChC,MAAM,QAAQ,EAAE,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,GAAG,GAAG,GAAG,QAAQ,EAAE;EACrE,MAAM,SACJ,EAAE,eAAe,OAAOC,MAAI,EAAE,YAAY,gBAAgB,CAAC,GAAGA,MAAI,IAAI;AACxE,MAAI,YACF,QAAO;GACLA,MAAI,IAAI,EAAE,SAAS;GACnB,EAAE;GACF,EAAE;GACF;GACA,YAAY,EAAE,OAAO;GACrB;GACD;AAEH,SAAO;GAACA,MAAI,IAAI,EAAE,SAAS;GAAE,EAAE;GAAM;GAAO,YAAY,EAAE,OAAO;GAAE;GAAO;GAC1E;AAEF,SAAQ,KAAK;AACb,KAAI,aAAa;EACf,MAAM,UAAUF,aAAW,IAAI,YAAY;AAC3C,UAAQ,IAAI,KAAKG,OAAK,QAAQ,aAAa,CAAC,YAAY;AACxD,UAAQ,KAAK;;AAEf,SAAQ,IAAI,YAAY,SAAS,UAAU,CAAC;AAC5C,SAAQ,KAAK;AACb,SAAQ,IAAID,MAAI,KAAK,KAAK,OAAO,4BAA4B,MAAM,GAAG,CAAC;;AAOzE,SAAgB,QACd,IACA,aACA,eACM;CACN,MAAM,UAAUF,aAAW,IAAI,YAAY;AAC3C,KAAI,CAAC,SAAS;AACZ,UAAQ,MAAM,IAAI,sBAAsB,cAAc,CAAC;AACvD,UAAQ,WAAW;AACnB;;CAGF,MAAM,UAAU,eAAe,IAAI,SAAS,cAAc;AAE1D,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,cAAc,QAAQ,OAAO,IAAI,QAAQ,QAAQ,CAAC;AACrE,SAAQ,KAAK;AACb,SAAQ,IACN,KAAKG,OAAK,WAAW,CAAC,OAAO,QAAQ,aAAa,IAAI,QAAQ,KAAK,GACpE;AACD,SAAQ,IAAI,KAAKA,OAAK,QAAQ,CAAC,UAAU,QAAQ,OAAO;AACxD,SAAQ,IAAI,KAAKA,OAAK,UAAU,CAAC,QAAQ,YAAY,QAAQ,OAAO,GAAG;AACvE,SAAQ,IAAI,KAAKA,OAAK,YAAY,CAAC,MAAM,QAAQ,WAAW;AAC5D,SAAQ,IAAI,KAAKA,OAAK,QAAQ,CAAC,UAAU,QAAQ,OAAO;AACxD,KAAI,QAAQ,kBACV,SAAQ,IACN,KAAKA,OAAK,aAAa,CAAC,KAAKD,MAAI,QAAQ,kBAAkB,GAC5D;AAEH,KAAI,QAAQ,eAAe,KACzB,SAAQ,IACN,KAAKC,OAAK,UAAU,CAAC,QAAQ,QAAQ,YAAY,gBAAgB,GAClE;AAEH,SAAQ,IAAI,KAAKA,OAAK,WAAW,CAAC,OAAO,QAAQ,QAAQ,WAAW,GAAG;AACvE,KAAI,QAAQ,UACV,SAAQ,IAAI,KAAKA,OAAK,UAAU,CAAC,QAAQ,QAAQ,QAAQ,UAAU,GAAG;AAExE,SAAQ,KAAK;;AAOf,SAAgB,UACd,IACA,aACA,gBACA,SACM;CACN,MAAM,UAAUH,aAAW,IAAI,YAAY;AAC3C,KAAI,CAAC,SAAS;AACZ,UAAQ,MAAM,IAAI,sBAAsB,cAAc,CAAC;AACvD,UAAQ,WAAW;AACnB;;CAGF,MAAM,UAAU,eAAe,IAAI,SAAS,eAAe;CAC3D,MAAM,WAAW,YAAY,QAAQ;AAErC,KAAI,CAAC,WAAW,SAAS,EAAE;AACzB,UAAQ,MAAM,IAAI,8BAA8B,WAAW,CAAC;AAC5D,UAAQ,WAAW;AACnB;;CAGF,MAAM,YAAYI,cAAY,QAAQ;CACtC,MAAM,cAAc,eAAe,QAAQ,QAAQ,QAAQ,MAAM,UAAU;CAC3E,MAAM,UAAU,KAAK,UAAU,QAAQ,SAAS;CAChD,MAAM,UAAU,KAAK,UAAU,YAAY;AAE3C,KAAI,WAAW,QAAQ,EACrB;MAAI,YAAY,QACd,KAAI;AACF,cAAW,SAAS,QAAQ;WACrB,GAAG;AACV,WAAQ,MAAM,IAAI,0BAA0B,IAAI,CAAC;AACjD,WAAQ,WAAW;AACnB;;QAGC;AACL,UAAQ,IACNH,OAAK,4CAA4C,QAAQ,WAAW,CACrE;AACD,UAAQ,IAAIA,OAAK,0DAA0D,CAAC;;AAG9E,KAAI,WAAW,QAAQ,CACrB,KAAI;EAEF,MAAM,QADU,aAAa,SAAS,OAAO,CACvB,MAAM,KAAK;EACjC,IAAI,YAAY;EAChB,MAAM,UAAU,MAAM,KAAK,SAAS;AAClC,OAAI,CAAC,aAAa,KAAK,WAAW,KAAK,EAAE;AACvC,gBAAY;AACZ,WAAO,KAAK;;AAEd,UAAO;IACP;AACF,MAAI,CAAC,UAAW,SAAQ,QAAQ,KAAK,aAAa,GAAG;AACrD,gBAAc,SAAS,QAAQ,KAAK,KAAK,EAAE,OAAO;UAC3C,GAAG;AACV,UAAQ,MAAM,IAAI,gCAAgC,IAAI,CAAC;;CAI3D,MAAM,iBAAiB,QACpB,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG;AAE1B,IAAG,QACD,qEACD,CAAC,IAAI,gBAAgB,WAAW,aAAa,QAAQ,GAAG;AAEzD,SAAQ,KAAK;AACb,SAAQ,IAAII,KAAG,cAAc,QAAQ,OAAO,WAAW,CAAC;AACxD,SAAQ,IAAI,KAAKF,OAAK,OAAO,CAAC,KAAK,QAAQ,WAAW;AACtD,SAAQ,IAAI,KAAKA,OAAK,OAAO,CAAC,KAAK,cAAc;AACjD,SAAQ,IAAI,KAAKA,OAAK,QAAQ,CAAC,IAAI,iBAAiB;AACpD,SAAQ,IAAI,KAAKA,OAAK,SAAS,CAAC,GAAG,YAAY;AAC/C,SAAQ,KAAK;;AAOf,SAAgB,QACd,IACA,aACA,gBACA,MACM;CACN,MAAM,UAAUH,aAAW,IAAI,YAAY;AAC3C,KAAI,CAAC,SAAS;AACZ,UAAQ,MAAM,IAAI,sBAAsB,cAAc,CAAC;AACvD,UAAQ,WAAW;AACnB;;CAGF,MAAM,UAAU,eAAe,IAAI,SAAS,eAAe;CAC3D,MAAM,iBAAiB,qBAAqB,QAAQ,YAAY;AAEhE,KAAI,CAAC,gBAAgB;AACnB,UAAQ,IAAIC,OAAK,4CAA4C,cAAc,CAAC;AAC5E,UAAQ,IAAI,kBAAkB;AAC9B;;CAGF,MAAM,WAAW,iBAAiB,eAAe;AAEjD,KAAI,SAAS,SAAS,GAAG;AACvB,UAAQ,IACNA,OAAK,6BAA6B,SAAS,OAAO,iBAAiB,CACpE;AACD,UAAQ,IAAI,kBAAkB;AAC9B;;CAGF,MAAM,gBAAgB,aAAa,SAAS;AAC5C,SAAQ,IAAI,cAAc;AAE1B,KAAI,KAAK,OAAO;AACd,UAAQ,KAAK;AACb,UAAQ,IAAIC,MAAI,+BAA+B,QAAQ,OAAO,KAAK,CAAC;AACpE,YAAU,IAAI,aAAa,OAAO,QAAQ,OAAO,EAAE,cAAc;;;AAQrE,SAAgB,OACd,IACA,aACA,eACA,SACM;CACN,MAAM,UAAUF,aAAW,IAAI,YAAY;AAC3C,KAAI,CAAC,SAAS;AACZ,UAAQ,MAAM,IAAI,sBAAsB,cAAc,CAAC;AACvD,UAAQ,WAAW;AACnB;;CAGF,MAAM,UAAU,eAAe,IAAI,SAAS,cAAc;AAE1D,KAAI,QAAQ,WAAW,GAAG;EACxB,MAAM,UAAU,eAAe,IAAI,QAAQ,GAAG;AAC9C,UAAQ,KAAK;AACb,MAAI,QAAQ,WAAW,EACrB,SAAQ,IAAIE,MAAI,cAAc,QAAQ,OAAO,eAAe,CAAC;MAE7D,SAAQ,IACN,KAAKC,OAAK,YAAY,QAAQ,SAAS,CAAC,SAAS,QAC9C,KAAK,MAAM,MAAM,KAAK,EAAE,CAAC,CACzB,KAAK,KAAK,GACd;AAEH,UAAQ,KAAK;AACb;;CAGF,MAAM,OAAO,QACV,SAAS,MAAM,EAAE,MAAM,IAAI,CAAC,CAC5B,KAAK,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC,CAClC,QAAQ,MAAM,EAAE,SAAS,EAAE;AAE9B,KAAI,KAAK,WAAW,GAAG;AACrB,UAAQ,IAAIF,OAAK,0BAA0B,CAAC;AAC5C;;CAGF,MAAM,QAAkB,EAAE;CAC1B,MAAM,UAAoB,EAAE;AAE5B,MAAK,MAAM,WAAW,MAAM;EAC1B,MAAM,QAAQ,UAAU,IAAI,QAAQ;AAIpC,MAHe,GACZ,QAAQ,iEAAiE,CACzE,IAAI,QAAQ,IAAI,MAAM,CAEvB,SAAQ,KAAK,QAAQ;OAChB;AACL,MAAG,QACD,8DACD,CAAC,IAAI,QAAQ,IAAI,MAAM;AACxB,SAAM,KAAK,QAAQ;;;AAIvB,SAAQ,KAAK;AACb,KAAI,MAAM,OACR,SAAQ,IACNI,KACE,qBAAqB,QAAQ,OAAO,IAAI,MACrC,KAAK,MAAM,MAAM,KAAK,EAAE,CAAC,CACzB,KAAK,KAAK,GACd,CACF;AAEH,KAAI,QAAQ,OACV,SAAQ,IAAIH,MAAI,sBAAsB,QAAQ,KAAK,KAAK,GAAG,CAAC;CAG9D,MAAM,UAAU,eAAe,IAAI,QAAQ,GAAG;AAC9C,SAAQ,IACN,KAAKC,OAAK,YAAY,CAAC,GAAG,QAAQ,KAAK,MAAM,MAAM,KAAK,EAAE,CAAC,CAAC,KAAK,KAAK,GACvE;AACD,SAAQ,KAAK;;AAOf,SAAgB,SACd,IACA,aACA,eACA,mBACA,MACM;CACN,MAAM,UAAUH,aAAW,IAAI,YAAY;AAC3C,KAAI,CAAC,SAAS;AACZ,UAAQ,MAAM,IAAI,sBAAsB,cAAc,CAAC;AACvD,UAAQ,WAAW;AACnB;;CAGF,MAAM,UAAU,eAAe,IAAI,SAAS,cAAc;CAE1D,MAAM,gBAAgB,GACnB,QAAQ,6DAA6D,CACrE,IAAI,kBAAkB;AAIzB,KAAI,CAAC,eAAe;AAClB,UAAQ,MAAM,IAAI,6BAA6B,oBAAoB,CAAC;AACpE,UAAQ,WAAW;AACnB;;CAGF,MAAM,aAAa;EAAC;EAAW;EAAa;EAAY;CACxD,MAAM,WAAW,KAAK,QAAQ;AAC9B,KAAI,CAAC,WAAW,SAAS,SAAS,EAAE;AAClC,UAAQ,MACN,IAAI,sBAAsB,SAAS,YAAY,WAAW,KAAK,KAAK,GAAG,CACxE;AACD,UAAQ,WAAW;AACnB;;AAGF,KAAI;AACF,KAAG,QACD;4BAED,CAAC,IAAI,QAAQ,IAAI,cAAc,IAAI,UAAU,KAAK,KAAK,CAAC;SACnD;AACN,UAAQ,IACNC,OACE,mCAAmC,QAAQ,OAAO,KAAK,oBACxD,CACF;AACD;;AAGF,SAAQ,KAAK;AACb,SAAQ,IACNI,KACE,qBAAqB,QAAQ,OAAO,IAAI,QAAQ,KAAK,MAAM,cAAc,aAAa,IAAI,kBAAkB,GAC7G,CACF;AACD,SAAQ,IAAIH,MAAI,gBAAgB,WAAW,CAAC;AAC5C,SAAQ,KAAK;;AAOf,SAAgB,UACd,IACA,MACM;CACN,MAAM,UAAU,SAAS,KAAK,WAAW,MAAM,GAAG;CAClD,MAAM,SAAS,KAAK,KAAK,GAAG,UAAU,KAAK;CAC3C,MAAM,oBAAoB,KAAK,SAAS,EAAE,WAAW,WAAW;AAEhE,KAAI,CAAC,WAAW,kBAAkB,EAAE;AAClC,UAAQ,IAAI,IAAI,uCAAuC,CAAC;AACxD;;CAYF,MAAM,SAA0B,EAAE;CAClC,MAAM,UAAU,YAAY,kBAAkB;AAE9C,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,aAAa,KAAK,mBAAmB,MAAM;AACjD,MAAI;AACF,OAAI,CAAC,SAAS,WAAW,CAAC,aAAa,CAAE;UACnC;AACN;;EAGF,IAAI,cAA6B;EACjC,IAAI,cAAc;AAElB,MAAI;AACF,QAAK,MAAM,QAAQ,YAAY,WAAW,EAAE;AAC1C,QAAI,CAAC,KAAK,SAAS,SAAS,CAAE;IAC9B,MAAM,WAAW,KAAK,YAAY,KAAK;AACvC,QAAI;KACF,MAAM,QAAQ,SAAS,SAAS,CAAC;AACjC,SAAI,QAAQ,aAAa;AACvB,oBAAc;AACd,oBAAc;;YAEV;AACN;;;UAGE;AACN;;AAGF,MAAI,CAAC,eAAe,cAAc,OAAQ;EAE1C,MAAM,UAAU,GACb,QACC,2EACD,CACA,IAAI,MAAM;AAIb,SAAO,KAAK;GACV,MAAM,SAAS,QAAQ;GACvB,aAAa,SAAS,gBAAgB,SAAS,QAAQ;GACvD,UAAU,SAAS,aAAa;GAChC,YAAY;GACZ,cAAc,IAAI,KAAK,YAAY;GACnC,WAAW;GACZ,CAAC;;AAGJ,QAAO,MAAM,GAAG,MAAM,EAAE,aAAa,SAAS,GAAG,EAAE,aAAa,SAAS,CAAC;CAE1E,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,UAAU,OAAO,QAAQ,MAAM;EACnC,MAAM,MAAM,EAAE,KAAK,QAAQ,SAAS,GAAG;AACvC,MAAI,KAAK,IAAI,IAAI,CAAE,QAAO;AAC1B,OAAK,IAAI,IAAI;AACb,SAAO;GACP;AAEF,KAAI,KAAK,MAAM;AACb,UAAQ,IACN,KAAK,UACH,QAAQ,KAAK,OAAO;GAClB,MAAM,EAAE;GACR,cAAc,EAAE;GAChB,WAAW,EAAE;GACb,eAAe,EAAE,aAAa,aAAa;GAC5C,EAAE,EACH,MACA,EACD,CACF;AACD;;AAGF,KAAI,QAAQ,WAAW,GAAG;AACxB,UAAQ,IAAIA,MAAI,kCAAkC,QAAQ,WAAW,CAAC;AACtE;;AAGF,SAAQ,IACN,OAAO,4BAA4B,GACjCA,MAAI,sBAAsB,QAAQ,MAAM,CAC3C;AACD,SAAQ,KAAK;CAEb,MAAM,OAAO,QAAQ,KAAK,MAAM;EAC9B,MAAM,OAAO,EAAE,aAAa,cAAc,CAAC,MAAM,GAAG,EAAE;EACtD,MAAM,UAAU,EAAE,WACd,EAAE,SAAS,QAAQ,SAAS,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI,EAAE,OACzD,EAAE;AACN,SAAO;GAAC,MAAM,KAAK,QAAQ;GAAEA,MAAI,EAAE,KAAK;GAAE,MAAM,MAAM,KAAK;GAAC;GAC5D;AAEF,SAAQ,IAAI,YAAY;EAAC;EAAa;EAAW;EAAc,EAAE,KAAK,CAAC;;AAOzE,eAAsB,aAAa,MAIjB;CAChB,MAAM,EAAE,WAAW,iBAAiB,wBAAwB,MAAM,OAChE;CAEF,MAAM,EAAE,iBAAiB,MAAM,OAAO;CACtC,MAAM,EAAE,yBAAyB,MAAM,OAAO;CAC9C,MAAM,EAAE,eAAe,MAAM,OAAO;CAEpC,MAAM,SAAS,YAAY;CAC3B,MAAM,aAAa,cAAc;CACjC,MAAM,aAAa,MAAM,qBAAqB,OAAO;CAErD,MAAM,YAAY,KAAK,OAAO,QAAQ,KAAK;CAC3C,MAAM,SAAS,MAAM,UACnB,YACA,YACA,WACA,KAAK,QACN;AAED,KAAI,CAAC,QAAQ;AACX,UAAQ,KAAK;AACb,UAAQ,IAAID,OAAK,mCAAmC,UAAU,CAAC;AAC/D,UAAQ,KAAK;AACb,UAAQ,IACNC,MAAI,0CAA0C,IAC3C,KAAK,UAAUA,MAAI,oBAAoB,GAAG,IAC9C;AACD,UAAQ,KAAK;AACb,UAAQ,IAAIA,MAAI,wDAAwD,CAAC;AACzE,UAAQ,KAAK;AACb;;AAGF,KAAI,KAAK,MAAM;AACb,UAAQ,IAAI,oBAAoB,OAAO,CAAC;AACxC;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAI,OAAO,mBAAmB,CAAC;AACvC,SAAQ,KAAK;AACb,SAAQ,IAAI,KAAKC,OAAK,WAAW,CAAC,OAAO,OAAO,eAAe;AAC/D,SAAQ,IAAI,KAAKA,OAAK,QAAQ,CAAC,UAAU,OAAO,OAAO;AACvD,SAAQ,IAAI,KAAKA,OAAK,aAAa,CAAC,KAAK,OAAO,YAAY;AAC5D,SAAQ,IAAI,KAAKA,OAAK,UAAU,CAAC,QAAQ,OAAO,SAAS;AACzD,SAAQ,IACN,KAAKA,OAAK,cAAc,CAAC,KAAK,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC,GACnE;AACD,SAAQ,KAAK;AACb,SAAQ,IAAIE,KAAG,gBAAgB,GAAGF,OAAK,OAAO,KAAK,CAAC;AACpD,SAAQ,KAAK;;;;;;;;;AClnBf,SAAS,qBAAoC;CAC3C,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,oBAAoB,KAAK,SAAS,EAAE,WAAW,WAAW;AAEhE,KAAI,CAAC,WAAW,kBAAkB,CAAE,QAAO;CAE3C,MAAM,kBAAkB,IAAI,QAAQ,aAAa,IAAI;CACrD,IAAI,aAA4B;AAEhC,KAAI;EACF,MAAM,UAAU,YAAY,kBAAkB;AAE9C,MAAI,QAAQ,SAAS,gBAAgB,CACnC,cAAa;MAEb,MAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,OAAO,KAAK,mBAAmB,MAAM;AAC3C,OAAI;AACF,QAAI,CAAC,SAAS,KAAK,CAAC,aAAa,CAAE;WAC7B;AACN;;AAIF,OAFkB,MAAM,QAAQ,OAAO,GAAG,KACzB,gBAAgB,QAAQ,OAAO,GAAG,EACvB;AAC1B,iBAAa;AACb;;;SAIA;AACN,SAAO;;AAGT,KAAI,CAAC,WAAY,QAAO;CACxB,MAAM,WAAW,KAAK,mBAAmB,YAAY,QAAQ;AAC7D,QAAO,WAAW,SAAS,GAAG,WAAW;;AAG3C,SAAS,mBAAmB,UAAiC;CAC3D,IAAI;AACJ,KAAI;AACF,YAAU,YAAY,SAAS;SACzB;AACN,SAAO;;CAGT,MAAM,UAAU,QAAQ,QAAQ,MAAM,EAAE,SAAS,MAAM,CAAC;AACxD,KAAI,QAAQ,WAAW,EAAG,QAAO;CAEjC,IAAI,aAA4B;CAChC,IAAI,cAAc;AAElB,MAAK,MAAM,QAAQ,SAAS;EAC1B,MAAM,OAAO,KAAK,UAAU,KAAK;AACjC,MAAI;GACF,MAAM,EAAE,YAAY,SAAS,KAAK;AAClC,OAAI,UAAU,aAAa;AACzB,kBAAc;AACd,iBAAa;;UAET;;AAKV,QAAO;;AAGT,SAAS,oBAAoB,UAAkB,eAAgC;CAC7E,MAAM,UAAU,SAAS,QAAQ,iBAAiB,IAAI,CAAC,MAAM,IAAI;CACjE,MAAM,UAAU,KAAK,QAAQ,EAAE,kBAAkB,UAAU;AAC3D,KAAI,CAAC,WAAW,QAAQ,CAAE,QAAO;AACjC,KAAI;EACF,MAAM,EAAE,YAAY,SAAS,QAAQ;AACrC,SAAO,KAAK,KAAK,GAAG,UAAU,gBAAgB;SACxC;AACN,SAAO;;;AAIX,SAAS,wBAAwB,UAAwB;CACvD,MAAM,UAAU,SAAS,QAAQ,iBAAiB,IAAI,CAAC,MAAM,IAAI;CACjE,MAAM,UAAU,KAAK,QAAQ,EAAE,kBAAkB,UAAU;AAC3D,KAAI;AACF,gBAAc,SAAS,OAAO,KAAK,KAAK,CAAC,EAAE,OAAO;SAC5C;;AAKV,SAAgB,cACd,SACA,MACM;CACN,MAAM,gBAAgB,SAAS,KAAK,UAAU,OAAO,GAAG;CAExD,MAAM,WAAW,oBAAoB;AACrC,KAAI,CAAC,SAAU;AAEf,KAAI,oBAAoB,UAAU,cAAc,CAAE;CAElD,MAAM,WAAW,mBAAmB,SAAS;AAC7C,KAAI,CAAC,SAAU;CAGf,MAAM,QAAQ,sCADI,IAAI,MAAM,EAAC,aAAa,CACG,IAAI,QAAQ;CAEzD,MAAM,UAAU,GAAG,SAAS;AAC5B,KAAI;AAEF,gBAAc,SADG,aAAa,UAAU,OAAO,GACb,OAAO,OAAO;AAChD,aAAW,SAAS,SAAS;SACvB;AACN,MAAI;AACF,OAAI,WAAW,QAAQ,CAAE,YAAW,SAAS,UAAU,QAAQ;UACzD;AAGR;;AAGF,yBAAwB,SAAS;;;;;;AC7EnC,SAAgB,kBAAkB,SAAyC;AACzE,KAAI,CAAC,QAAS,QAAO;CACrB,MAAM,MAAM,OAAO,QAAQ,OAAO,CAAC,SAAS,GAAG,IAAI;CACnD,MAAM,YAAY,QAAQ,SAAS,QAAQ,QAAQ;AACnD,QAAO,GAAG,IAAI,KAAK,QAAQ,KAAK,KAAK;;;;;;;;;;;;;;;;;AAkBvC,SAAgB,mBACd,SACA,SACA,UACQ;AAYR,KAAI,SAAU,QAAO,SAAS,SAAS,CAAC,QAAQ,SAAS,GAAG;AAC5D,KAAI,QAAS,QAAO,kBAAkB,QAAQ;AAC9C,QAAO;;;AAIT,SAAgB,gBAAgB,SAAoC;CAClE,MAAM,WAAWG,eAAa,QAAQ,WAAW,QAAQ,eAAe,GAAG;AAC3E,QAAO,WAAW,eAAe,SAAS,GAAG;;;;;;;;;;;AAY/C,SAAgB,oBACd,IACA,KACwB;CACxB,MAAM,OAAO,GAAG,QACd;;;;eAKD;CAED,MAAM,SAAS,KAAK,IAAI,IAAI;AAC5B,KAAI,OAAQ,QAAO;CAEnB,IAAI;AACJ,KAAI;AACF,aAAW,aAAa,IAAI;SACtB;AACN;;AAEF,KAAI,aAAa,IAAK,QAAO;AAE7B,QAAO,KAAK,IAAI,SAAS;;AAG3B,SAAS,wBAA8B;AACrC,SAAQ,MACN,OACE,MAAM,MAAM,MAAM,KAChB,kEACD,GACD,SACA,MAAM,OACJ,0IAED,GACD,OACA,MAAM,MAAM,6DAA6D,GACzE,OACA,MAAM,KAAK,wDAAwD,GACnE,MAAM,KAAK,qDAAqD,GAChE,OACA,MAAM,IAAI,kBAAkB,GAC5B,MAAM,KAAK,gDAAgD,GAC3D,OACA,MAAM,IACJ,wEACD,CACJ;;AAOH,SAAgB,SAAS,IAAc,MAA0B;CAE/D,IAAI,OAAO;AAEX,KAAI,KAAK,UAAU;AACjB,MAAI;AACF,UAAO,aAAa,KAAK,SAAS,CAAC,MAAM;WAClC,KAAK;AACZ,WAAQ,MACN,MAAM,IAAI,cAAc,GACtB,uCAAuC,KAAK,SAAS,IAAI,OAAO,IAAI,GACvE;AACD,WAAQ,WAAW;AACnB;;AAEF,MAAI,CAAC,MAAM;AACT,WAAQ,MACN,MAAM,IAAI,cAAc,GACtB,sBAAsB,KAAK,SAAS,YACvC;AACD,WAAQ,WAAW;AACnB;;YAEO,CAAC,KAAK,QAAQ;AACvB,yBAAuB;AACvB,UAAQ,WAAW;AACnB;;CAIF,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,UAAU,oBAAoB,IAAI,IAAI;AAE5C,KAAI,CAAC,SAAS;AACZ,UAAQ,MACN,MAAM,IAAI,cAAc,GACtB;SACU,MACb;AACD,UAAQ,WAAW;AACnB;;CAIF,MAAM,UAAU,GACb,QACC,2EACD,CACA,IAAI,QAAQ,GAAG;CAelB,MAAM,WAAW,gBAAgB,QAAQ;CACzC,MAAM,cAAc,mBAAmB,SAAS,SAAS,SAAS;AAOlE,KAAI,CAAC,SAAS;EACZ,MAAM,WAAW,GACd,QACC;;;;mCAKD,CACA,IAAI,QAAQ,IAAI,QAAQ,MAAM,QAAQ,MAAM,QAAQ,KAAK;AAM5D,UAAQ,IACN,MAAM,OACJ,oBAAoB,QAAQ,KAAK,2FAC2B,YAAY,KACzE,CACF;AACD,OAAK,MAAM,KAAK,SACd,SAAQ,IACN,MAAM,IACJ,YAAY,EAAE,KAAK,KAAK,EAAE,UAAU,QAAQ,EAAE,EAAE,gDACjD,CACF;;CAKL,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;CAS1C,MAAM,SAAS,cAAc;EAC3B,UAAU,QAAQ;EAClB,UAAU;EACV;EACA,WAAW,KAAK;EAChB;EACA;EACA;EACA,QAAQ,KAAK;EACd,CAAC;AAEF,KAAI,OAAO,WAAW,UAAU;AAC9B,UAAQ,MACN,MAAM,IAAI,cAAc,GAAG,4BAA4B,OAAO,QAC/D;AACD,UAAQ,WAAW;AACnB;;AAGF,KAAI,KAAK,QAAQ;AACf,UAAQ,IACN,OAAO,MAAM,KAAK,4BAA4B,GAAG,MAAM,MAAM,KAAK,OAAO,QAAQ,IAAI,CACtF;AACD,UAAQ,IAAI,MAAM,IAAI,IAAI,OAAO,GAAG,CAAC,CAAC;AACtC,UAAQ,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC;AACpC,UAAQ,IAAI,MAAM,IAAI,IAAI,OAAO,GAAG,CAAC,CAAC;OAEtC,SAAQ,IACN,MAAM,MAAM,6BAA6B,GAAG,MAAM,KAAK,OAAO,QAAQ,IAAI,CAC3E;AAOH,KAAI,KACF,KAAI,CAAC,SACH,SAAQ,IACN,MAAM,OACJ,gEACD,CACF;UACQ,KAAK,OACd,SAAQ,IACN,MAAM,IAAI,iCAAiC,SAAS,SAAS,GAAG,CACjE;MACI;EACL,MAAM,EAAE,UAAU,UAAU,uBAC1B,UACA,MACA,UACD;AACD,MAAI,SACF,SAAQ,IACN,MAAM,MAAM,kCAAkC,GAC5C,MAAM,KAAK,SAAS,SAAS,CAAC,CACjC;WACQ,MACT,SAAQ,IACN,MAAM,OAAO,wCAAwC,MAAM,GAAG,CAC/D;;CAMP,MAAM,MAAM;EACV;EACA,MAAM,MAAM,MAAM,KAChB,iEACD;EACD;EACA,MAAM,OACJ,8CACE,MAAM,MAAM,KAAK,QAAQ,GACzB,MAAM,OAAO,uBAAuB,CACvC;EACD;EACA,MAAM,IAAI,KAAK,yBAAyB;EACxC;EACA,MAAM,IAAI,kDAAkD;EAC5D,MAAM,IAAI,8DAA8D;EACxE,MAAM,IAAI,iEAAiE;EAC3E,MAAM,IACJ,yEACD;EACD;EACA,MAAM,IAAI,4DAA4D;EACtE,MAAM,IAAI,qDAAqD;EAC/D;EACD,CAAC,KAAK,KAAK;AAEZ,SAAQ,IAAI,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;ACvVlB,MAAM,cAAc;;AAEpB,MAAM,mBAAmB;;AAEzB,MAAM,YAAY;;;;;;AAWlB,SAAS,YAAY,KAA6C;CAChE,MAAM,MAAM,IAAI;AAChB,KAAI,CAAC,IAAK,QAAO;CAEjB,MAAM,UAAU,IAAI;AACpB,KAAI,OAAO,YAAY,SAAU,QAAO,WAAW;AAEnD,KAAI,MAAM,QAAQ,QAAQ,EAAE;EAC1B,MAAM,QAAkB,EAAE;AAC1B,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,IAAI;AACV,OAAI,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,SAAU,OAAM,KAAK,EAAE,KAAK;;AAEzE,SAAO,MAAM,KAAK,IAAI,IAAI;;AAG5B,QAAO;;;;;;;;;;;AAYT,SAAS,cAAc,MAAsB;AAC3C,QAAO,KACJ,QAAQ,iDAAiD,GAAG,CAC5D,QAAQ,2DAA2D,GAAG,CACtE,QAAQ,wDAAwD,GAAG,CACnE,QAAQ,mDAAmD,GAAG,CAC9D,MAAM;;;;;AAMX,SAAS,YAAY,MAAuB;CAC1C,MAAM,IAAI,KAAK,WAAW;AAC1B,QACE,EAAE,WAAW,UAAU,IACvB,EAAE,WAAW,uBAAuB,IACpC,EAAE,WAAW,kDAAkD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCnE,SAAgB,qBAAqB,SAG1B;CACT,MAAM,OAAO,KAAK,SAAS,EAAE,WAAW,WAAW;CAEnD,MAAM,WAAW,QAAQ,cAAc,KAAK,MAAM,QAAQ,YAAY,GAAG;AACzE,KAAI,YAAY,eAAe,SAAS,CAAE,QAAO;CAEjD,MAAM,UAAU,KAAK,MAAM,UAAU,QAAQ,UAAU,CAAC;AACxD,KAAI,eAAe,QAAQ,CAAE,QAAO;AAIpC,QAAO,YAAY;;AAGrB,SAAS,eAAe,KAAsB;AAC5C,KAAI,CAAC,WAAW,IAAI,CAAE,QAAO;AAC7B,KAAI;AACF,MAAI,YAAY,IAAI,CAAC,MAAM,MAAM,EAAE,SAAS,SAAS,CAAC,CAAE,QAAO;EAC/D,MAAM,WAAW,KAAK,KAAK,WAAW;AACtC,SAAO,WAAW,SAAS,IAAI,YAAY,SAAS,CAAC,MAAM,MAAM,EAAE,SAAS,SAAS,CAAC;SAChF;AACN,SAAO;;;AAIX,SAAgB,gBACd,YACA,WACU;AACV,KAAI,CAAC,WAAW,WAAW,CAAE,QAAO,EAAE;AAEtC,KAAI,WAAW;EAGb,MAAM,QAAQ,CAFG,KAAK,YAAY,YAAY,GAAG,UAAU,QAAQ,EACtD,KAAK,YAAY,GAAG,UAAU,QAAQ,CACrB,CAAC,QAAQ,MAAM,WAAW,EAAE,CAAC;AAC3D,MAAI,MAAM,SAAS,EAAG,QAAO;;CAG/B,IAAI,SAAiD;CACrD,IAAI;AACJ,KAAI;AACF,YAAU,YAAY,WAAW;SAC3B;AACN,SAAO,EAAE;;AAGX,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,CAAC,MAAM,SAAS,SAAS,CAAE;EAC/B,MAAM,OAAO,KAAK,YAAY,MAAM;AACpC,MAAI;GACF,MAAM,EAAE,YAAY,SAAS,KAAK;AAClC,OAAI,CAAC,UAAU,UAAU,OAAO,MAAO,UAAS;IAAE,MAAM;IAAM,OAAO;IAAS;UACxE;;AAKV,QAAO,SAAS,CAAC,OAAO,KAAK,GAAG,EAAE;;;;;;;;;;AAWpC,SAAgB,cACd,iBACA,QAAQ,aACE;CACV,MAAM,cAAwB,EAAE;CAChC,MAAM,eAAyB,EAAE;CAEjC,MAAM,QAAQ,MAAc,SAAyB;EACnD,MAAM,UAAU,cAAc,KAAK;AACnC,MAAI,CAAC,WAAW,YAAY,QAAQ,CAAE;AACtC,OAAK,KACH,QAAQ,SAAS,mBACb,QAAQ,MAAM,GAAG,iBAAiB,CAAC,SAAS,GAAG,OAC/C,QACL;;AAGH,MAAK,MAAM,QAAQ,iBAAiB;EAClC,IAAI;AACJ,MAAI;AACF,SAAM,aAAa,MAAM,OAAO;UAC1B;AACN;;AAGF,OAAK,MAAM,QAAQ,IAAI,MAAM,KAAK,EAAE;AAClC,OAAI,CAAC,KAAK,MAAM,CAAE;GAClB,IAAI;AACJ,OAAI;AACF,UAAM,KAAK,MAAM,KAAK;WAChB;AACN;;AAGF,OAAI,IAAI,SAAS,eAAe;AAC9B,QAAI,OAAO,IAAI,eAAe,SAAU,MAAK,IAAI,YAAY,YAAY;AACzE;;AAGF,OAAI,IAAI,SAAS,QAAQ;IACvB,MAAM,OAAO,YAAY,IAAI;AAC7B,QAAI,KAAM,MAAK,MAAM,aAAa;;;;CAKxC,MAAM,SAAS,YAAY,SAAS,IAAI,cAAc;AAKtD,QADgB,OAAO,QAAQ,GAAG,MAAM,MAAM,KAAK,MAAM,OAAO,IAAI,GAAG,CACxD,MAAM,CAAC,MAAM;;AAgB9B,SAAgB,gBAAgB,KAAiC;CAC/D,MAAM,OAAO,SAAkC;AAC7C,MAAI;AACF,UAAO,aAAa,OAAO,MAAM;IAC/B;IACA,UAAU;IACV,OAAO;KAAC;KAAU;KAAQ;KAAS;IACpC,CAAC,CAAC,MAAM;UACH;AACN,UAAO;;;CAIX,MAAM,SAAS,IAAI;EAAC;EAAa;EAAgB;EAAO,CAAC;AACzD,KAAI,WAAW,KAAM,QAAO;CAG5B,MAAM,OADS,IAAI,CAAC,UAAU,cAAc,CAAC,IAAI,IAC9B,MAAM,KAAK,CAAC,QAAQ,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE;AAEjE,QAAO;EACL;EACA,MAAM,IAAI;GAAC;GAAO;GAAM;GAAiB,CAAC;EAC1C,SAAS,IAAI,MAAM,GAAG,UAAU;EAChC,UAAU,KAAK,IAAI,GAAG,IAAI,SAAS,UAAU;EAC9C;;;;;;;;;;;;;;;;;;AA8BH,MAAM,eAAe;;AAGrB,SAAS,aAAa,MAAsB;AAC1C,QAAO,KAAK,QAAQ,aAAa,IAAI,CAAC,MAAM;;;;;;;;;;AAW9C,SAAS,OAAO,OAAuB;AAErC,QADiB,MAAM,QAAQ,kBAAkB,GAAG,CAAC,MAAM,IACxC,MAAM,MAAM;;;;;;;;;;;;;;;;AAiBjC,SAAgB,cACd,SACA,MACA,IACQ;CACR,MAAM,KAAK,QAAQ,GAAG,QAAQ,oBAAoB,IAAI;CACtD,MAAM,IAAI,aAAa,QAAQ,GAAG;CAClC,MAAM,IACJ,QAAQ,SAAS,IAAI,QAAQ,MAAM,EAAE,CAAC,IAAI,aAAa,CAAC,KAAK,MAAM,GAAG;CAExE,MAAM,QAAkB;EAAC;EAAK,KAAK;EAAM,KAAK;EAAK,KAAK;EAAK;EAAM;CAEnE,IAAI,QAAQ;AACZ,KAAI,MAAM;EACR,MAAM,QAAQ,KAAK,QAAQ,MAAM,GAAG,aAAa;AACjD,UAAQ,KAAK,IAAI,GAAG,KAAK,QAAQ,SAAS,aAAa,GAAG,KAAK;AAC/D,QAAM,SAAS,OAAO,MAAM,MAAM,KAAK,IAAI,IAAI,EAAE,GAAG,OAAO,MAAM,GAAG,CAAC;;CAGvE,IAAI;AACJ,KAAI,CAAC,KACH,KAAI;MACC;EACL,MAAM,QAAQ,KAAK,QAAQ;EAC3B,MAAM,OAAO,KAAK,QAAQ;AAC1B,MAAI,yBAAyB,KAAK,OAAO,GAAG,KAAK,IAAI,MAAM;AAC3D,MAAI,QAAQ,EAAG,MAAK,MAAM,MAAM;AAChC,OAAK;;AAEP,OAAM,KAAK,KAAK,EAAE,SAAS,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;AAEvD,QAAO,MAAM,KAAK,KAAK;;AAGzB,SAAgB,kBAAkB,OAA4B;CAC5D,MAAM,KAAK,MAAM,8BAAa,IAAI,MAAM,EAAC,aAAa;CACtD,MAAM,UAAU,cAAc,MAAM,mBAAmB,EAAE,CAAC;AAC1D,KAAI,QAAQ,WAAW,EAAG,QAAO;AAGjC,QAAO,cAAc,SADR,gBAAgB,MAAM,IAAI,EACH,GAAG;;;;;;;;;;;;;AC5VzC,SAAS,cAAc,SAAqB,WAA4B;AACtE,KAAI;EACF,MAAM,kBAAkB,gBAAgB,qBAAqB,QAAQ,EAAE,UAAU;AACjF,SAAO,kBAAkB;GAAE,KAAK,QAAQ,KAAK;GAAE;GAAiB,CAAC;SAC3D;AACN,SAAO;;;AAIX,SAAgB,YACd,IACA,aACA,gBACA,WACM;CAEN,IAAI;AAEJ,KAAI,aAAa;AACf,YAAU,GACP,QACC,qFACD,CACA,IAAI,YAAY;AACnB,MAAI,CAAC,QAAS;QACT;EAIL,MAAM,MAAM,oBAAoB,IAAI,QAAQ,KAAK,CAAC;AAClD,MAAI,CAAC,IAAK;AACV,YAAU;;CAIZ,IAAI;CACJ,MAAM,MAAM,kBAAkB;AAE9B,KAAI,QAAQ,SACV,WAAU,GACP,QACC,2EACD,CACA,IAAI,QAAS,GAAG;MACd;EACL,MAAM,MAAM,SAAS,KAAK,GAAG;AAC7B,MAAI,CAAC,MAAM,IAAI,CACb,WAAU,GACP,QAAQ,6DAA6D,CACrE,IAAI,QAAS,IAAI,IAAI;;AAqB5B,eAAc;EACZ,UAAU,QAAS;EACnB,UAAU;EACV,aAAa,mBAAmB,SAAU,SAAS,gBAAgB,QAAS,CAAC;EAC7E;EACA,KAAK,QAAQ,KAAK;EAClB,MAAM,cAAc,SAAU,UAAU;EACzC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACtEJ,MAAM,wBAAwB;;;AAI9B,MAAM,sBAAsB,MAAS;AAiBrC,SAAS,eAAe,YAAwC;AAC9D,QAAO;EAAE,QAAQ;EAAW,YAAY;EAAM;EAAY,UAAU;EAAM,QAAQ;EAAW;;AAO/F,SAAgB,wBAAwB,WAA2B;AACjE,QAAO,KAAK,QAAQ,EAAE,eAAe,UAAU,OAAO;;;;;;;AAexD,SAAgB,mBAAmB,WAAmB,MAAM,KAAK,KAAK,EAA6B;AACjG,KAAI,CAAC,UAAW,QAAO;CACvB,MAAM,OAAO,wBAAwB,UAAU;AAC/C,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;CAE9B,IAAI;AACJ,KAAI;AACF,QAAM,KAAK,MAAM,aAAa,MAAM,QAAQ,CAAC;SACvC;AACN,SAAO;;AAGT,KAAI,OAAO,IAAI,cAAc,YAAY,MAAM,IAAI,YAAY,oBAC7D,QAAO;AAET,KAAI,OAAO,IAAI,oBAAoB,YAAY,OAAO,IAAI,wBAAwB,SAChF,QAAO;CAGT,MAAM,aAAa,IAAI;AAEvB,QAAO;EACL,QAAQ;EACR,YAHiB,KAAK,MAAO,IAAI,kBAAkB,MAAO,WAAW;EAIrE;EACA,UAAU,IAAI,kBAAkB;EAChC,QAAQ;EACT;;;;;;;;;;AAqBH,SAAS,WAAW,OAA2B;AAC7C,SACG,MAAM,gBAAgB,MACtB,MAAM,2BAA2B,MACjC,MAAM,+BAA+B;;;;;;;;;;;;;AAe1C,SAAgB,0BACd,gBACA,YACoB;AACpB,KAAI,CAAC,kBAAkB,CAAC,WAAW,eAAe,CAChD,QAAO,eAAe,cAAc,uBAAuB;CAG7D,IAAI;AACJ,KAAI;AACF,QAAM,aAAa,gBAAgB,QAAQ;SACrC;AACN,SAAO,eAAe,cAAc,uBAAuB;;CAI7D,MAAM,OADQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,QAAQ,MAAM,EAAE,MAAM,CAAC,CACzC,MAAM,CAAC,sBAAsB;CAIhD,IAAI,YAA2B;AAC/B,MAAK,IAAI,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;EACzC,IAAI;AACJ,MAAI;AACF,WAAQ,KAAK,MAAM,KAAK,GAAG;UACrB;AACN;;AAEF,MAAI,OAAO,SAAS,eAAe,cAAc,MAAM;GACrD,MAAM,QAAQ,MAAM,SAAS;AAC7B,OAAI,OAAO,UAAU,YAAY,UAAU,GAAI,aAAY;;EAE7D,MAAM,QAAQ,OAAO,SAAS;AAC9B,MAAI,SAAS,OAAO,UAAU,UAAU;GACtC,MAAM,SAAS,cAAc,yBAAyB,UAAU,IAAI;GACpE,MAAM,aAAa,WAAW,MAAM;AACpC,UAAO;IACL,QAAQ;IACR;IACA,YAAY;IACZ,UAAU,aAAa;IACvB,QAAQ;IACT;;;AAKL,QAAO,eADQ,cAAc,yBAAyB,UAAU,IAAI,uBACvC;;AAO/B,SAAgB,eACd,OACA,MAAM,KAAK,KAAK,EACI;AACpB,KAAI,MAAM,WAAW;EACnB,MAAM,iBAAiB,mBAAmB,MAAM,WAAW,IAAI;AAC/D,MAAI,eAAgB,QAAO;;AAG7B,KAAI,MAAM,eAGR,QAAO,0BAA0B,MAAM,gBAAgB,MAAM,WAAW;AAG1E,QAAO,eAAe,MAAM,cAAc,uBAAuB;;AA+EnE,MAAa,0BAA0B;;;AAIvC,MAAM,+BAA+B;AAErC,MAAa,0BAA0B;CACrC,QAAQ;CACR,SAAS;CACT,WAAW;CACZ;;;AAoBD,MAAa,sBAAsB,KAAK,SAAS,EAAE,WAAW,WAAW;AAEzE,SAAS,kBAAkB,KAAqB;AAC9C,QAAO,IAAI,QAAQ,YAAY,IAAI;;;;;;;;;;AAyBrC,SAAS,uBAAuB,KAAa,aAA+B;CAC1E,MAAM,aAAa,KAAK,aAAa,kBAAkB,IAAI,CAAC;AAC5D,KAAI,CAAC,WAAW,WAAW,CAAE,QAAO,EAAE;CAEtC,MAAM,QAAkB,EAAE;CAC1B,MAAM,WAAW,QAAsB;AACrC,MAAI,CAAC,WAAW,IAAI,CAAE;EACtB,IAAI;AACJ,MAAI;AACF,aAAU,YAAY,IAAI;UACpB;AACN;;AAEF,OAAK,MAAM,SAAS,QAClB,KAAI,MAAM,SAAS,SAAS,CAAE,OAAM,KAAK,KAAK,KAAK,MAAM,CAAC;;AAG9D,SAAQ,WAAW;AACnB,SAAQ,KAAK,YAAY,WAAW,CAAC;AACrC,QAAO;;;AAIT,MAAM,kBAAkB;AAcxB,SAAgB,YAAY,OAAyD;AACnF,KAAI,OAAO,UAAU,YAAY,UAAU,MAAM,UAAU,gBAAiB,QAAO;CACnF,MAAM,OAAO,kBAAkB,MAAM;AACrC,QAAO,KAAK,WAAW,UAAU,GAAG,WAAW;;;;;;;;;;AAWjD,SAAgB,qBACd,QACA,gBACS;AACT,KAAI,WAAW,aAAa,WAAW,SAAU,QAAO;AACxD,QAAO,EAAE,kBAAkB,qBAAqB,EAAE,IAAI,OAAO;;;;;AAM/D,SAAgB,oBAAoB,YAAkC;CACpE,MAAM,2BAAW,IAAI,KAAa;AAClC,KAAI;AACF,OAAK,MAAM,KAAK,OAAO,OAAO,mBAAmB,WAAW,CAAC,QAAQ,UAAU,EAAE;AAC/E,YAAS,IAAI,YAAY,EAAE,OAAO,QAAQ,CAAC;AAC3C,OAAI,EAAE,OAAO,KAAM,UAAS,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;;SAEvD;AACN,SAAO;;AAET,QAAO;;;;;;;;;AAUT,SAAgB,sBAAsB,MAAqC;CACzE,IAAI;AACJ,KAAI;AACF,QAAM,aAAa,MAAM,QAAQ;SAC3B;AACN,SAAO;;CAET,MAAM,QAAQ,IAAI,MAAM,KAAK;AAC7B,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;EAC1C,MAAM,OAAO,MAAM;AACnB,MAAI,CAAC,KAAK,SAAS,YAAU,CAAE;AAC/B,MAAI;GACF,MAAM,QAAQ,KAAK,MAAM,KAAK;AAC9B,OAAI,MAAM,SAAS,YAAa;GAChC,MAAM,QAAQ,MAAM,SAAS;AAC7B,OAAI,OAAO,UAAU,YAAY,UAAU,gBAAiB,QAAO,YAAY,MAAM;UAC/E;AACN;;;AAGJ,QAAO;;;;;;;;;AAUT,SAAS,2BACP,KACA,aACA,gBACyB;CACzB,MAAM,wBAAQ,IAAI,KAAoC;AAEtD,MAAK,MAAM,QAAQ,uBAAuB,KAAK,YAAY,EAAE;EAC3D,IAAI;AACJ,MAAI;AACF,SAAM,aAAa,MAAM,QAAQ;UAC3B;AACN;;EAaF,IAAI,YAA2B;EAC/B,IAAI,kBAAkB;AAEtB,OAAK,MAAM,QAAQ,IAAI,MAAM,KAAK,EAAE;AAClC,OAAI,CAAC,KAAK,MAAM,CAAE;GAClB,IAAI;AAQJ,OAAI;AACF,YAAQ,KAAK,MAAM,KAAK;WAClB;AACN;;AAEF,OAAI,MAAM,SAAS,aAAa;IAC9B,MAAM,QAAQ,MAAM,SAAS;AAC7B,QAAI,OAAO,UAAU,YAAY,UAAU,gBAAiB,aAAY;AACxE;;AAEF,OAAI,MAAM,SAAS,YAAY,MAAM,YAAY,mBAAoB;GACrE,MAAM,YAAY,MAAM,iBAAiB;AACzC,OAAI,OAAO,cAAc,YAAY,CAAC,OAAO,SAAS,UAAU,CAAE;GAClE,MAAM,cAAc,MAAM,YAAY,KAAK,MAAM,MAAM,UAAU,GAAG;AACpE,OAAI,CAAC,OAAO,SAAS,YAAY,CAAE;AACnC,OAAI,qBAAqB,YAAY,UAAU,EAAE,eAAe,EAAE;AAChE;AACA;;GAGF,MAAM,MAAM,MAAM,QAAQ,GAAG,YAAY,GAAG;AAC5C,OAAI,CAAC,MAAM,IAAI,IAAI,CACjB,OAAM,IAAI,KAAK;IAAE;IAAW;IAAa,WAAW,MAAM;IAAY,MAAM,MAAM;IAAM,CAAC;;AAI7F,MAAI,kBAAkB,EACpB,SAAQ,MACN,0BAA0B,gBAAgB,+DACP,UAAU,KAAK,OACnD;;AAIL,QAAO,CAAC,GAAG,MAAM,QAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,YAAY;;;;;;;;;AAU1E,SAAgB,0BACd,KACA,cAAsB,qBACtB,YACyB;AACzB,KAAI,CAAC,IAAK,QAAO,EAAE;AACnB,QAAO,2BAA2B,KAAK,aAAa,oBAAoB,WAAW,CAAC,CACjF,MAAM,GAAG,6BAA6B;;;;;;;;;;;;;AAc3C,SAAgB,yBACd,KACA,cAAsB,qBACtB,YACe;CACf,MAAM,UAAU,0BAA0B,KAAK,aAAa,WAAW;AACvE,KAAI,QAAQ,WAAW,EAAG,QAAO;CACjC,MAAM,UAAU,KAAK,IAAI,GAAG,QAAQ,KAAK,MAAM,EAAE,UAAU,CAAC;AAC5D,SAAQ,MACN,uCAAuC,IAAI,IAAI,QAAQ,iBACvD,QAAQ,KAAK,MAAM,GAAG,EAAE,UAAU,GAAG,EAAE,YAAY,CAAC,KAAK,KAAK,GAAG,IAClE;AACD,QAAO;;;;;;;;;;AAWT,SAAgB,sBAAsB,MAAyB,QAAQ,KAAa;CAClF,MAAM,MAAM,IAAI;AAChB,KAAI,QAAQ,UAAa,QAAQ,GAAI,QAAO;CAE5C,MAAM,SAAS,OAAO,IAAI;AAC1B,KAAI,CAAC,OAAO,SAAS,OAAO,IAAI,UAAU,KAAK,SAAS,KAAK;AAC3D,UAAQ,MACN,mDAAmD,IAAI,6DACxB,wBAAwB,GACxD;AACD,SAAO;;AAET,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2FT,SAAgB,sBACd,SACA,MAAyB,QAAQ,KACjC,OAAiC,EAAE,EACZ;CACvB,MAAM,kBAAkB,QAAQ,WAAW;CAC3C,MAAM,iBAAiB,sBAAsB,IAAI;CACjD,MAAM,0BAA0B,KAAK,MAAM,QAAQ,cAAc,iBAAiB,KAAK;CAEvF,MAAM,wBAAwB,KAAK,oBAAoB,SACnD,KAAK,kBACL,KAAK,MACH,yBAAyB,KAAK,IAAI,GAClC;CAEN,IAAI;CACJ,IAAI;AAEJ,KAAI,0BAA0B,MAAM;AAClC,2BAAyB;AACzB,kBAAgB;AAChB,UAAQ,MACN,qHAC6B,wBAAwB,IAAI,eAAe,SAAS,QAAQ,WAAW,0BAChF,uBAAuB,GAC5C;YACQ,yBAAyB,yBAAyB;AAC3D,2BAAyB;AACzB,kBAAgB;AAChB,UAAQ,MACN,sDAAsD,sBAAsB,+BAC7C,6BAA6B,wCAC9C,wBAAwB,YAAY,uBAAuB,4BAC1E;QACI;AACL,2BAAyB;AACzB,kBAAgB;AAChB,UAAQ,MACN,8DAA8D,sBAAsB,6BACtE,wBAAwB,6DAC7B,uBAAuB,iCACjC;;CAGH,MAAM,SAAS,MAAc,UAA0B;AACrD,MAAI,SAAS,EAAG,QAAO;AACvB,UAAQ,MACN,kBAAkB,KAAK,4BAA4B,MAAM,UACtD,QAAQ,WAAW,gCACvB;AACD,SAAO;;AAGT,QAAO;EACL,cAAc,MAAM,UAAU,yBAAyB,wBAAwB,OAAO;EACtF,eAAe,MAAM,WAAW,yBAAyB,wBAAwB,QAAQ;EACzF,iBAAiB,MAAM,aAAa,yBAAyB,wBAAwB,UAAU;EAC/F;EACA;EACA;EACA;EACA;EACA;EACD;;;;;;;;;;;;;AAcH,SAAgB,kBACd,YACA,YACA,cACiB;CACjB,MAAM,QAAQ,IAAI,IAAI,aAAa;AAKnC,QAJgD,CAC9C,CAAC,UAAU,WAAW,aAAa,EACnC,CAAC,WAAW,WAAW,cAAc,CACtC,CACc,QAAQ,CAAC,MAAM,YAAY,CAAC,MAAM,IAAI,KAAK,IAAI,cAAc,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK;;;;;;;;;;;AAY3G,SAAgB,YAAY,YAAoB,YAA4C;AAC1F,QAAO,cAAc,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;AClxBlC,SAAgB,gBAAgB,MAAyB,QAAQ,KAAc;AAC7E,QAAO,IAAI,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC4B5B,MAAM,qBAAqB;;;;;;;;;;AAW3B,MAAM,qBAAqB,MAAS;;AAGpC,MAAM,iBAAkC,CAAC,UAAU,UAAU;AAuB7D,SAAgB,iBAAiB,WAA2B;AAC1D,QAAO,KAAK,QAAQ,EAAE,8BAA8B,UAAU,OAAO;;AAGvE,SAAgB,iBAAiB,WAAyC;CACxE,MAAM,OAAO,iBAAiB,UAAU;AACxC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;EAAE,WAAW,EAAE;EAAE,SAAS;EAAM;AAC9D,KAAI;EACF,MAAM,MAAM,KAAK,MAAM,aAAa,MAAM,QAAQ,CAAC;AACnD,SAAO;GACL,WAAW,MAAM,QAAQ,IAAI,UAAU,GAAI,IAAI,YAAgC,EAAE;GACjF,SAAS,IAAI,WAAW,MAAM,QAAQ,IAAI,QAAQ,WAAW,IAAI,OAAO,IAAI,QAAQ,eAAe,WAC/F,IAAI,UACJ;GACL;SACK;AACN,SAAO;GAAE,WAAW,EAAE;GAAE,SAAS;GAAM;;;AAI3C,SAAS,iBAAiB,WAAmB,OAAmC;AAC9E,KAAI;AACF,gBAAc,iBAAiB,UAAU,EAAE,KAAK,UAAU,MAAM,EAAE,QAAQ;SACpE;;AAgEV,eAAe,uBAAuB,SAMpB;AAEhB,OADe,IAAI,WAAW,CACjB,KACX,sBACA;EACE,MAAM;EAIN,UAAU;EACV,SAAS;GACP,WAAW,QAAQ;GACnB,KAAK,QAAQ;GACb,gBAAgB,QAAQ;GACxB,WAAW,QAAQ;GACnB,QAAQ,QAAQ;GACjB;EACF,EACD,mBACD;;AAgBH,MAAM,cAAmC;CACvC,aAAa,UACX,eAAe;EAAE,WAAW,MAAM;EAAW,gBAAgB,MAAM;EAAgB,CAAC;CACtF,WAAW;CACX,WAAW;CACX,WAAW;CACX,SAAS;CACT,WAAW,KAAK,KAAK;CACtB;AAaD,MAAM,UAAiC;CAAE,WAAW,EAAE;CAAE,WAAW,EAAE;CAAE;;;;;;;;;;;;;AAcvE,eAAsB,+BACpB,OACA,OAA4B,aACI;AAKhC,KAAI,iBAAiB,EAAE;AACrB,UAAQ,MAAM,sCAAsC,MAAM,UAAU,gDAAgD;AACpH,SAAO;;AAKT,KACE,MAAM,kBACN,qBAAqB,sBAAsB,MAAM,eAAe,CAAC,EACjE;AACA,UAAQ,MAAM,sCAAsC,MAAM,UAAU,0EAA0E;AAC9I,SAAO;;CAGT,IAAI,QAAQ,KAAK,UAAU,MAAM,UAAU;AAC3C,KAAI,eAAe,OAAO,MAAM,MAAM,UAAU,SAAS,EAAE,CAAC,CAAE,QAAO;CAKrE,IAAI,qBAAsC,EAAE;AAS5C,KAAI,MAAM,SAAS;EACjB,MAAM,QAAQ,KAAK,UAAU,MAAM,UAAU;AAI7C,MAFE,UAAU,QAAQ,KAAK,MAAM,MAAM,YAAY,GAAG,KAAK,MAAM,MAAM,QAAQ,WAAW,EAE5D;GAC1B,MAAM,iBAAiB,MAAM,QAAQ,WAAW,QAAQ,MAAM,CAAC,MAAM,UAAU,SAAS,EAAE,CAAC;AAC3F,WAAQ;IAAE,WAAW,CAAC,GAAG,MAAM,WAAW,GAAG,eAAe;IAAE,SAAS;IAAM;AAC7E,QAAK,UAAU,MAAM,WAAW,MAAM;AACtC,wBAAqB;AACrB,WAAQ,MACN,sCAAsC,MAAM,UAAU,eAAe,eAAe,KAAK,KAAK,CAAC,yCACvD,MAAO,YAAY,IAC5D;AACD,OAAI,eAAe,OAAO,MAAM,MAAM,UAAU,SAAS,EAAE,CAAC,CAC1D,QAAO;IAAE,WAAW,EAAE;IAAE,WAAW;IAAoB;aAIhD,KAAK,KAAK,GAAG,KAAK,MAAM,MAAM,QAAQ,WAAW,GAAG,mBAE7D,QAAO;OACF;AACL,WAAQ,MACN,sCAAsC,MAAM,UAAU,aAAa,MAAM,QAAQ,WAAW,KAAK,KAAK,CAAC,oBACpF,mBAAmB,2HAEvC;AACD,WAAQ;IAAE,GAAG;IAAO,SAAS;IAAM;AACnC,QAAK,UAAU,MAAM,WAAW,MAAM;;;CAI1C,MAAM,UAAU,KAAK,WAAW,MAAM;AACtC,KAAI,QAAQ,WAAW,QAAQ,QAAQ,eAAe,KACpD,QAAO;EAAE,WAAW,EAAE;EAAE,WAAW;EAAoB;CAGzD,MAAM,aAAa,sBAAsB,SAAS,QAAQ,KAAK,EAAE,KAAK,MAAM,KAAK,CAAC;AAClF,KAAI,CAAC,WAAW,gBACd,SAAQ,MACN,sCAAsC,MAAM,UAAU,2DACxB,QAAQ,WAAW,sDAClD;CAGH,MAAM,eAAe,kBAAkB,QAAQ,YAAY,YAAY,MAAM,UAAU;AACvF,KAAI,aAAa,WAAW,EAAG,QAAO;EAAE,WAAW,EAAE;EAAE,WAAW;EAAoB;CAEtF,MAAM,SAAS,YAAY,QAAQ,YAAY,WAAW;CAC1D,MAAM,aAAa,aAAa,aAAa,SAAS;AAEtD,KAAI;AACF,QAAM,KAAK,QAAQ;GACjB,WAAW,MAAM;GACjB,KAAK,MAAM;GACX,gBAAgB,MAAM;GACtB,WAAW;GACX;GACD,CAAC;UACK,KAAK;AACZ,UAAQ,MACN,sCAAsC,MAAM,UAAU,4CAA4C,MACnG;AAED,SAAO;GAAE,WAAW,EAAE;GAAE,WAAW;GAAoB;;AAGzD,SAAQ;EAAE,GAAG;EAAO,SAAS;GAAE,YAAY;GAAc,YAAY,IAAI,KAAK,KAAK,KAAK,CAAC,CAAC,aAAa;GAAE;EAAE;AAC3G,MAAK,UAAU,MAAM,WAAW,MAAM;AACtC,SAAQ,MACN,sCAAsC,MAAM,UAAU,cAAc,aAAa,KAAK,KAAK,CAAC,OACtF,QAAQ,WAAW,mBAAmB,WAAW,uBAAuB,kBAC7D,WAAW,cAAc,mBAAmB,WAAW,eAAe,WAAW,OAAO,wDAE1G;AAED,QAAO;EAAE,WAAW;EAAc,WAAW;EAAoB;;;;;;ACtUnE,MAAM,kBAAkB;;;;;;;;;AAcxB,SAAS,aAAa,UAA0B;CAC9C,MAAM,MAAM,SAAS,QAAQ,iBAAiB,IAAI,CAAC,MAAM,IAAI;AAC7D,QAAO,KAAK,QAAQ,EAAE,gBAAgB,MAAM;;AAG9C,SAAS,UAAU,UAAkB,eAAgC;CACnE,MAAM,IAAI,aAAa,SAAS;AAChC,KAAI,CAAC,WAAW,EAAE,CAAE,QAAO;AAC3B,KAAI;AACF,SAAO,KAAK,KAAK,GAAG,SAAS,EAAE,CAAC,UAAU,gBAAgB;SACpD;AACN,SAAO;;;AAIX,SAAS,cAAc,UAAwB;AAC7C,KAAI;AACF,gBAAc,aAAa,SAAS,EAAE,OAAO,KAAK,KAAK,CAAC,EAAE,OAAO;SAC3D;;AASV,eAAsB,YAAY,IAAc,MAAsC;CACpF,MAAM,SAAS,SAAS,KAAK,UAAU,OAAO,gBAAgB,EAAE,GAAG;CACnE,MAAM,MAAM,QAAQ,KAAK;CAEzB,MAAM,UAAU,oBAAoB,IAAI,IAAI;AAC5C,KAAI,CAAC,QAAS;AAEd,KAAI,CAAC,KAAK,UAAU,UAAU,QAAQ,WAAW,OAAO,CAAE;CAQ1D,MAAM,kBAAkB,gBACtB,qBAAqB,QAAQ,EAC7B,KAAK,UACN;AASD,KAAI,KAAK,aAAa,CAAC,KAAK,QAAQ;EAClC,MAAM,iBAAiB,gBAAgB,gBAAgB,SAAS;AAChE,MAAI;AACF,SAAM,+BAA+B;IACnC,WAAW,KAAK;IAChB;IACA,gBAAgB;IACjB,CAAC;UACI;;CAMV,MAAM,OAAO,kBAAkB;EAAE;EAAK;EAAiB,CAAC;AAExD,KAAI,CAAC,KAAM;CAEX,MAAM,UAAU,GACb,QACC,2EACD,CACA,IAAI,QAAQ,GAAG;CAElB,MAAM,SAAS,cAAc;EAC3B,UAAU,QAAQ;EAClB,UAAU;EACV,aAAa,mBAAmB,SAAS,SAAS,gBAAgB,QAAQ,CAAC;EAC3E,WAAW,KAAK;EAChB;EACA;EACA,QAAQ,KAAK;EACd,CAAC;AAEF,KAAI,KAAK,QAAQ;AACf,UAAQ,IAAI,OAAO,MAAM;AACzB,UAAQ,IAAI,gCAAgC,OAAO,SAAS;AAC5D;;AAGF,eAAc,QAAQ,UAAU;;;;;ACvHlC,SAAS,QAAQ,OAAuB;AACtC,KAAI,QAAQ,KAAM,QAAO,GAAG,MAAM;AAClC,KAAI,QAAQ,QAAW,QAAO,GAAG,KAAK,MAAM,QAAQ,KAAK,CAAC;AAC1D,QAAO,IAAI,QAAQ,SAAW,QAAQ,EAAE,CAAC;;AAG3C,SAAS,UAAU,QAA+B;AAChD,SAAQ,QAAR;EACE,KAAK,YACH,QAAO,MAAM,MAAM,YAAY;EACjC,KAAK,OACH,QAAO,MAAM,OAAO,OAAO;EAC7B,KAAK,kBACH,QAAO,MAAM,OAAO,kBAAkB;EACxC,KAAK,SACH,QAAO,MAAM,IAAI,SAAS;;;AAQhC,SAAS,mBAAmB,iBAAwC,SAAwB;CAE1F,MAAM,YAAY,UACd,kBACA,gBAAgB,QAAQ,MAAM,EAAE,SAAS,QAAQ;CACrD,MAAM,eAAe,gBAAgB,SAAS,UAAU;AAExD,KAAI,UAAU,WAAW,EAAG;AAE5B,SAAQ,IAAI,OAAO,OAAO,gBAAgB,GAAG,KAAK;CAElD,MAAM,cAAc;EAAC;EAAK;EAAM;EAAQ;EAAa;EAAO;CAC5D,MAAM,WAAW,UAAU,KAAK,GAAG,QAAQ;EACzC,MAAM,UAAU,EAAE,UAAU,MAAM,GAAG,EAAE;EAEvC,MAAM,UAAU,EAAE,WAAW,EAAE;EAC/B,MAAM,OAAO,QAAQ,SAAS,KAAK,QAAQ,MAAM,GAAG,GAAG,GAAG,MAAM;EAChE,MAAM,WAAW,EAAE,WAAW,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,OAAO;EACvE,MAAM,OAAO,EAAE,SAAS,WACpB,MAAM,KAAK,EAAE,KAAK,GAClB,MAAM,IAAI,EAAE,KAAK;AAErB,SAAO;GACL,MAAM,IAAI,OAAO,MAAM,EAAE,CAAC;GAC1B,MAAM,KAAK,QAAQ;GACnB;GACA;GACA;GACD;GACD;AAEF,SAAQ,IAAI,YAAY,aAAa,SAAS,CAAC;AAE/C,KAAI,eAAe,EACjB,SAAQ,IACNC,MAAI,MAAM,aAAa,YAAY,iBAAiB,IAAI,KAAK,IAAI,mCAAmC,CACrG;;AAQL,eAAsB,UACpB,IACA,MACe;CACf,MAAM,QAAQ,SAAS,KAAK,KAAK,MAAM,GAAG;CAC1C,MAAM,aAAa,KAAK,QAAQ;CAChC,MAAM,UAAU,KAAK,YAAY;CAGjC,MAAM,eAAe,MAAM,mBAAmB;CAE9C,MAAM,WAAW,aAAa,IAAI;EAChC;EACA,QAAQ,aAAa,QAAQ;EAC9B,CAAC;AAEF,KAAI,KAAK,MAAM;EACb,MAAM,SAAS;GACb,MAAM,aAAa,KAAK,OAAO;IAC7B,OAAO,EAAE;IACT,WAAW,EAAE;IACb,MAAM,EAAE;IACR,SAAS,EAAE;IACX,UAAU,EAAE;IACZ,MAAM,EAAE;IACR,QAAQ,EAAE;IACX,EAAE;GACH,QAAQ,SAAS,KAAK,GAAG,SAAS;IAChC,KAAK,MAAM;IACX,MAAM,EAAE;IACR,SAAS,EAAE;IACX,WAAW,EAAE;IACb,eAAe,EAAE;IACjB,KAAK,OAAO,EAAE,MAAM;IACpB,OAAO,EAAE;IACT,MAAM,EAAE;IACR,gBAAgB,EAAE;IAClB,WAAW,EAAE;IACb,UAAU,EAAE;IACZ,cAAc,EAAE;IAChB,aAAa,EAAE;IAChB,EAAE;GACJ;AACD,UAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;AAC5C;;CAIF,MAAM,gBAAgB,aAAa,MAAM,MAAM,EAAE,SAAS,SAAS;AACnE,KAAI,aAAa,SAAS,EACxB,oBAAmB,cAAc,QAAQ;AAI3C,KAAI,SAAS,WAAW,GAAG;AACzB,MAAI,CAAC,cAEH,KAAI,WACF,SAAQ,IAAI,IAAI,4CAA4C,CAAC;MAE7D,SAAQ,IACN,IACE,kQAID,CACF;AAIL;;CAGF,MAAM,QAAQ,aACV,4CACA;AACJ,SAAQ,IAAI,OAAO,OAAO,MAAM,GAAG,KAAK;CAExC,MAAM,UAAU;EAAC;EAAK;EAAM;EAAO;EAAkB;EAAe;EAAQ;EAAQ;EAAS;CAE7F,MAAM,OAAO,SAAS,KAAK,GAAG,QAAQ;EACpC,MAAM,OAAO,EAAE,gBAAgB,EAAE;EACjC,MAAM,SAAS,EAAE,iBACb,MAAM,IACJ,EAAE,eAAe,SAAS,KACtB,EAAE,eAAe,MAAM,GAAG,GAAG,GAAG,MAChC,EAAE,eACP,GACDA,MAAI,IAAI;AAEZ,SAAO;GACL,MAAM,IAAI,OAAO,MAAM,EAAE,CAAC;GAC1B,MAAM,KAAK,EAAE,QAAQ;GACrBA,MAAI,OAAO,EAAE,MAAM,CAAC;GACpB,KAAK,SAAS,KAAK,KAAK,MAAM,GAAG,GAAG,GAAG,MAAM;GAC7C;GACA,EAAE,YAAY,IAAI,OAAO,EAAE,UAAU,GAAGA,MAAI,IAAI;GAChDA,MAAI,QAAQ,EAAE,aAAa,CAAC;GAC5B,UAAU,EAAE,cAAc;GAC3B;GACD;AAEF,SAAQ,IAAI,YAAY,SAAS,KAAK,CAAC;AAEvC,SAAQ,IACN,OACEA,MAAI,sBAAsB,GAC1B,MAAM,MAAM,oBAAoB,GAChC,QACC,gBACGA,MAAI,sBAAsB,GAAG,MAAM,MAAM,gBAAgB,GAAG,OAC5D,OACH,aACG,KACAA,MAAI,sBAAsB,GAC1B,MAAM,MAAM,qBAAqB,GACjC,MACP;;;;;ACrKH,SAAgB,QACd,IACA,OACA,MACM;CAEN,MAAM,cAAc,aAAa,IAAI;EAAE,OAAO;EAAK,QAAQ;EAAS,CAAC;CAErE,IAAI;AACJ,KAAI;AACF,aAAW,yBAAyB,aAAa,MAAM;UAChD,YAAY;EACnB,IAAI,MAAM,OAAO,WAAW,CAAC,QAAQ,YAAY,GAAG;AAEpD,MAAI,IAAI,SAAS,4BAA4B,CAC3C,QAAO,sBAAsB,MAAM;AAErC,UAAQ,MAAM,IAAI,IAAI,CAAC;AACvB,UAAQ,WAAW;AACnB;;CAGF,MAAM,EAAE,SAAS,gBAAgB,iBAAiB;CAGlD,IAAI;CACJ,IAAI;AAEJ,KAAI,eAAe,WAAW;AAC5B,kBAAgB,eAAe;AAC/B,qBAAmB;YACV,eAAe,YAAY;EAEpC,MAAM,cAAc,YAAY,QAC7B,MAAM,EAAE,eAAe,eAAe,cAAc,EAAE,UACxD;AACD,cAAY,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;AAC7C,MAAI,YAAY,SAAS,GAAG;AAC1B,sBAAmB,YAAY;AAC/B,mBAAgB,iBAAiB;;;CAKrC,MAAM,SACJ,eAAe,gBACf,eAAe,oBACf,eAAe;CAEjB,IAAI;AACJ,KAAI;AACF,eAAa,aAAa,OAAO;SAC3B;AACN,UAAQ,MACN,IACE,YAAY,MAAM,uEACI,OAAO,mDAE9B,CACF;AACD,UAAQ,WAAW;AACnB;;CAIF,MAAM,OAAO,gBAAgB;CAE7B,MAAM,YAAY,SAAS,KAAK;AAGhC,KAAI,KAAK,QAAQ;AACf,MAAI,eAAe;GACjB,MAAM,aAAa,mBAAmB,cAAc,WAAW,KAAK,WAAW,KAAK;GACpF,MAAM,YAAY,kBAAkB,KAAK,WAAW,KAAK;AACzD,WAAQ,IACN,OAAO,MAAM,KAAK,iDAAiD,GAAG,KACvE;AACD,WAAQ,IAAI,eAAe,MAAM,KAAK,WAAW,GAAG;AACpD,WAAQ,IAAI,+BAA+B,cAAc,mCAAmC;AAC5F,WAAQ,IAAI,eAAe,MAAM,MAAM,WAAW,GAAG;AACrD,WAAQ,IAAI,eAAe,MAAM,OAAO,UAAU,GAAG;AACrD,OAAI,kBAAkB;AACpB,YAAQ,IAAI,iBAAiB,iBAAiB,OAAO;AACrD,YAAQ,IAAI,eAAe,OAAO,iBAAiB,MAAM,GAAG;AAC5D,YAAQ,IAAI,eAAe,iBAAiB,gBAAgB;AAC5D,YAAQ,IAAI,eAAe,iBAAiB,oBAAoB,eAAe;;SAE5E;GACL,MAAM,YAAY,kBAAkB,KAAK,WAAW,KAAK;AACzD,WAAQ,IACN,OAAO,MAAM,KAAK,wDAAwD,GAAG,KAC9E;AACD,WAAQ,IAAI,YAAY,MAAM,KAAK,WAAW,GAAG;AACjD,WAAQ,IAAI,YAAY,MAAM,MAAM,UAAU,GAAG;;AAEnD,UAAQ,KAAK;AACb;;AAIF,KAAI,eAAe;EAEjB,MAAM,QAAQ,YAAY,eAAe,WAAW;AAEpD,MAAI,MAAM,IAAI;GAEZ,MAAM,SAAS,UACb,UACA;IAAC;IAAY;IAAe;IAAU;IAAM;IAAU,EACtD;IACE,KAAK;IACL,OAAO;IACP,KAAK,QAAQ;IACd,CACF;AACD,OAAI,OAAO,OAAO;AAChB,YAAQ,MAAM,IAAI,4BAA4B,OAAO,MAAM,UAAU,CAAC;AACtE,YAAQ,WAAW;AACnB;;AAEF,gBAAa,WAAW;AACxB,WAAQ,WAAW,OAAO,UAAU;AACpC;SACK;AAEL,WAAQ,OAAO,MACb,MAAM,OACJ,yBAAyB,cAAc,MAAM,GAAG,EAAE,CAAC,IAAI,MAAM,UAAU,gBAAgB,mDAExF,CACF;GACD,MAAM,SAAS,UACb,UACA;IAAC;IAAU;IAAM;IAAU,EAC3B;IACE,KAAK;IACL,OAAO;IACP,KAAK,QAAQ;IACd,CACF;AACD,OAAI,OAAO,OAAO;AAChB,YAAQ,MAAM,IAAI,4BAA4B,OAAO,MAAM,UAAU,CAAC;AACtE,YAAQ,WAAW;AACnB;;AAEF,gBAAa,WAAW;AACxB,WAAQ,WAAW,OAAO,UAAU;AACpC;;QAEG;EAEL,MAAM,SAAS,UACb,UACA;GAAC;GAAU;GAAM;GAAU,EAC3B;GACE,KAAK;GACL,OAAO;GACP,KAAK,QAAQ;GACd,CACF;AACD,MAAI,OAAO,OAAO;AAChB,WAAQ,MAAM,IAAI,4BAA4B,OAAO,MAAM,UAAU,CAAC;AACtE,WAAQ,WAAW;AACnB;;AAEF,eAAa,WAAW;AACxB,UAAQ,WAAW,OAAO,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;ACzLxC,MAAM,UAAU;;;;;;;;AAiEhB,SAAgB,cAAc,cAAc,mBAAmB,EAAsB;CACnF,MAAM,MAA0B,EAAE;CAElC,IAAI;AACJ,KAAI;AACF,gBAAc,YAAY,YAAY;SAChC;AACN,SAAO;;AAGT,MAAK,MAAM,cAAc,aAAa;EACpC,MAAM,aAAa,KAAK,aAAa,WAAW;EAChD,MAAM,cAAc,KAAK,YAAY,WAAW;AAChD,MAAI,CAAC,WAAW,YAAY,CAAE;EAE9B,IAAI;AACJ,MAAI;AACF,WAAQ,YAAY,YAAY;UAC1B;AACN;;AAGF,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,CAAC,KAAK,SAAS,SAAS,CAAE;GAC9B,MAAM,OAAO,KAAK,MAAM,GAAG,GAAG;AAC9B,OAAI,CAAC,QAAQ,KAAK,KAAK,CAAE;AACzB,OAAI,WAAW,KAAK,YAAY,KAAK,CAAC,CAAE;GAExC,IAAI,QAAQ;GACZ,IAAI,QAAQ;AACZ,OAAI;IACF,MAAM,KAAK,SAAS,KAAK,aAAa,KAAK,CAAC;AAC5C,YAAQ,GAAG;AACX,YAAQ,GAAG;WACL;AACN;;AAGF,OAAI,KAAK;IACP;IACA;IACA;IACA,KAAK,eAAe,WAAW;IAC/B;IACA;IACA,YAAY,EAAE;IACd,iBAAiB,gBAAgB,KAAK,aAAa,KAAK,CAAC;IAC1D,CAAC;;;AAMN,QAAO,IAAI,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;AAU9C,SAAgB,mBAAmB,MAAc,KAA8B;AAC7E,KAAI,CAAC,IAAK,QAAO,EAAE;CAEnB,MAAM,aAAuB,EAAE;AAC/B,MAAK,MAAM,YAAY;EAAC;EAAK,KAAK,KAAK,QAAQ;EAAE,KAAK,KAAK,QAAQ;EAAC,CAClE,KAAI;AACF,OAAK,MAAM,SAAS,YAAY,SAAS,CACvC,KAAI,MAAM,SAAS,MAAM,CAAE,YAAW,KAAK,KAAK,UAAU,MAAM,CAAC;SAE7D;CAcV,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,OAAiB,EAAE;AACzB,MAAK,MAAM,QAAQ,YAAY;EAC7B,IAAI;AACJ,MAAI;GACF,MAAM,KAAK,SAAS,KAAK;AACzB,SAAM,GAAG,GAAG,IAAI,GAAG,GAAG;UAChB;AACN,SAAM,KAAK,aAAa;;AAE1B,MAAI,KAAK,IAAI,IAAI,CAAE;AACnB,OAAK,IAAI,IAAI;AAEb,MAAI;AACF,OAAI,aAAa,MAAM,OAAO,CAAC,SAAS,KAAK,CAAE,MAAK,KAAK,KAAK;UACxD;;AAIV,QAAO;;AAGT,SAAS,MAAM,OAAuB;AACpC,KAAI,SAAS,OAAO,KAAM,QAAO,IAAI,QAAQ,OAAO,MAAM,QAAQ,EAAE,CAAC;AACrE,KAAI,SAAS,KAAM,QAAO,GAAG,KAAK,MAAM,QAAQ,KAAK,CAAC;AACtD,QAAO,GAAG,MAAM;;;AA0BlB,MAAM,aAAa;;;;;;;;;;;AAYnB,SAAgB,cACd,YACA,MACgE;CAChE,MAAM,UAAU,WAAW,QAAQ,MAAM;AACvC,MAAI,KAAK,YAAY,EAAE,WAAW,WAAW,EAAG,QAAO;AACvD,MAAI,KAAK,OAAO,EAAE,QAAQ,KAAK,IAAK,QAAO;AAC3C,SAAO;GACP;AAMF,QAAO;EAAE;EAAS,WAJA,KAAK,eACnB,UACA,QAAQ,QAAQ,MAAM,EAAE,gBAAgB;EAEf;;AAG/B,SAAgB,WAAW,OAAuB,EAAE,EAAQ;CAC1D,MAAM,aAAa,eAAe;AAClC,MAAK,MAAM,KAAK,WACd,GAAE,aAAa,mBAAmB,EAAE,MAAM,EAAE,IAAI;CASlD,MAAM,EAAE,SAAS,WAAW,cAAc,cAAc,YAAY,KAAK;AAEzE,KAAI,KAAK,MAAM;AACb,UAAQ,IACN,KAAK,UACH;GACE;GAGA,cAAc,UAAU,KAAK,MAAM,EAAE,KAAK;GAC1C,UAAU,QAAQ,KAAK,QAAQ;GAChC,EACD,MACA,EACD,CACF;AACD,MAAI,KAAK,QAAS,MAAK,MAAM,KAAK,UAAW,iBAAgB,EAAE,MAAM,EAAE,WAAW;AAClF;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAIC,OAAK,kCAAkC,CAAC;AACpD,SAAQ,KAAK;AAEb,KAAI,UAAU,WAAW,GAAG;AAC1B,MAAI,WAAW,SAAS,GAAG;AACzB,WAAQ,IAAIC,KAAG,qCAAqC,CAAC;AACrD,WAAQ,IAAIC,MAAI,KAAK,WAAW,OAAO,sDAAsD,CAAC;SACzF;AACL,WAAQ,IAAID,KAAG,sEAAsE,CAAC;AACtF,WAAQ,IAAIC,MAAI,wDAAwD,CAAC;;AAE3E,UAAQ,KAAK;AACb;;CAGF,MAAM,WAAW,UAAU,QAAQ,MAAM,EAAE,WAAW,SAAS,EAAE;CACjE,MAAM,aAAa,UAAU,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,EAAE;AAE7D,SAAQ,IACNC,OACE,KAAK,UAAU,OAAO,kBAAkB,MAAM,WAAW,CAAC,0BAC3D,CACF;AACD,KAAI,UAAU,WAAW,WAAW,OAClC,SAAQ,IAAID,MAAI,oBAAoB,WAAW,OAAO,gBAAgB,CAAC;AAEzE,KAAI,SAAS,SAAS,EACpB,SAAQ,IACN,IACE,KAAK,SAAS,OAAO,oFACtB,CACF;CAEH,MAAM,QAAQ,UAAU,QAAQ,MAAM,CAAC,EAAE,gBAAgB;AACzD,KAAI,MAAM,SAAS,EAIjB,SAAQ,IACNA,MACE,KAAK,MAAM,OAAO,yEACnB,CACF;AAEH,SAAQ,KAAK;CAIb,MAAM,UAAU,CAAC,GAAG,UAAU,GAAG,UAAU,QAAQ,MAAM,EAAE,WAAW,WAAW,EAAE,CAAC;CACpF,MAAM,QAAQ,KAAK,MAAM,UAAU,QAAQ,MAAM,GAAG,WAAW;AAE/D,MAAK,MAAM,KAAK,OAAO;EACrB,MAAM,QAAQ,EAAE,OAAOA,MAAI,iBAAiB,EAAE,WAAW,GAAG;EAC5D,MAAM,OAAO,EAAE,kBAAkB,KAAKC,OAAK,6BAA6B;AACxE,UAAQ,IACN,KAAKH,OAAK,EAAE,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,IAAI,QAAQ,OAC1E;AACD,OAAK,MAAM,QAAQ,EAAE,WAGnB,SAAQ,IACN,EAAE,kBACE,IAAI,sBAAsB,OAAO,GACjCE,MAAI,sBAAsB,KAAK,8CAA8C,CAClF;;AAIL,KAAI,MAAM,SAAS,QAAQ,QAAQ;AACjC,UAAQ,KAAK;AACb,UAAQ,IACNA,MAAI,WAAW,QAAQ,SAAS,MAAM,OAAO,+BAA+B,CAC7E;;AAGH,SAAQ,KAAK;AAEb,KAAI,CAAC,KAAK,SAAS;AACjB,UAAQ,IAAIA,MAAI,mCAAmC,CAAC;AACpD,UAAQ,IAAIA,MAAI,4EAA4E,CAAC;AAC7F,UAAQ,IAAIA,MAAI,oEAAoE,CAAC;AACrF,UAAQ,KAAK;AACb,UAAQ,IAAIA,MAAI,8EAA8E,CAAC;AAC/F,UAAQ,IAAIA,MAAI,yCAAyC,CAAC;AAC1D,UAAQ,IAAIA,MAAI,iDAAiD,CAAC;AAClE,UAAQ,KAAK;AACb;;CAMF,MAAM,SAAS;CACf,MAAM,UAAU,UAAU,SAAS,OAAO;CAE1C,IAAI,WAAW;CACf,MAAM,SAAmB,EAAE;AAC3B,MAAK,MAAM,KAAK,OACd,KAAI,gBAAgB,EAAE,MAAM,EAAE,WAAW,CAAE;KACtC,QAAO,KAAK,EAAE,KAAK;AAG1B,SAAQ,IAAID,KAAG,cAAc,SAAS,MAAM,OAAO,OAAO,eAAe,CAAC;AAC1E,KAAI,UAAU,EACZ,SAAQ,IACNC,MAAI,aAAa,QAAQ,kEAAkE,CAC5F;AAEH,KAAI,OAAO,SAAS,GAAG;AACrB,UAAQ,IAAIC,OAAK,uBAAuB,OAAO,OAAO,GAAG,CAAC;AAC1D,OAAK,MAAM,QAAQ,OAAQ,SAAQ,IAAID,MAAI,OAAO,OAAO,CAAC;;AAE5D,SAAQ,KAAK;;;;;AC5Xf,SAAgB,wBACd,YACA,OACM;AAGN,YACG,QAAQ,OAAO,CACf,YACC,6MAID,CACA,OAAO,cAAc,0CAA0C,KAAK,CACpE,OAAO,SAAS,wDAAwD,CACxE,OAAO,UAAU,6CAA6C,CAC9D,OAAO,OAAO,SAAwD;AACrE,QAAM,UAAU,OAAO,EAAE,KAAK;GAC9B;AASJ,YACG,QAAQ,UAAU,CAIlB,YACC,0NAGD,CACA,OAAO,aAAa,uDAAuD,CAC3E,OAAO,cAAc,iDAAiD,CACtE,OAAO,gBAAgB,oDAAoD,CAC3E,OAAO,SAAS,yDAAyD,CACzE,OAAO,mBAAmB,qDAAqD,CAC/E,OAAO,UAAU,gDAAgD,CACjE,QACE,SAOK;AACJ,aAAW,KAAK;GAEnB;AAGH,YACG,QAAQ,+BAA+B,CACvC,YAAY,2CAA2C,CACvD,QAAQ,aAAqB,WAAmB;AAC/C,UAAQ,OAAO,EAAE,aAAa,OAAO;GACrC;AAGJ,YACG,QAAQ,4CAA4C,CACpD,YACC,uEACD,CACA,QAAQ,aAAqB,QAAgB,YAAoB;AAChE,YAAU,OAAO,EAAE,aAAa,QAAQ,QAAQ;GAChD;AAGJ,YACG,QAAQ,+BAA+B,CACvC,YACC,gEACD,CACA,OAAO,WAAW,mDAAmD,CACrE,QACE,aAAqB,QAAgB,SAA8B;AAClE,UAAQ,OAAO,EAAE,aAAa,QAAQ,KAAK;GAE9C;AAGH,YACG,QAAQ,wCAAwC,CAChD,YACC,iFACD,CACA,QAAQ,aAAqB,QAAgB,SAAmB;AAC/D,SAAO,OAAO,EAAE,aAAa,QAAQ,KAAK;GAC1C;AAGJ,YACG,QAAQ,iDAAiD,CACzD,YACC,mEACD,CACA,OAAO,iBAAiB,8CAA8C,UAAU,CAChF,QAEG,aACA,QACA,eACA,SACG;AACH,WAAS,OAAO,EAAE,aAAa,QAAQ,eAAe,KAAK;GAE9D;AAGH,YACG,QAAQ,6CAA6C,CACrD,YACC,wPAID,CACA,OACC,uBACA,yQAID,CACA,QAEG,aACA,gBACA,SACG;AACH,cAAY,OAAO,EAAE,aAAa,gBAAgB,KAAK,UAAU;GAEpE;AAGH,YACG,QAAQ,uBAAuB,CAC/B,YACC,mNAGD,CACA,OACC,uBACA,kEACA,MACD,CACA,QAAQ,SAAiB,SAA8B;AACtD,gBAAc,SAAS,KAAK;GAC5B;AAGJ,YACG,QAAQ,WAAW,CACnB,YACC,iSAID,CACA,OACC,uBACA,sIAED,CACA,OACC,uBACA,gFACA,MACD,CACA,OAAO,aAAa,iDAAiD,CACrE,OAAO,OAAO,SAAoE;AACjF,QAAM,YAAY,OAAO,EAAE,KAAK;GAChC;AAGJ,YACG,QAAQ,SAAS,CACjB,YACC,8JAGD,CACA,OACC,iBACA,uEACA,KACD,CACA,OAAO,UAAU,+CAA+C,CAChE,QAAQ,SAA+C;AACtD,YAAU,OAAO,EAAE,KAAK;GACxB;AAGJ,YACG,QAAQ,SAAS,CACjB,YAAY,iEAAiE,CAC7E,OAAO,cAAc,0CAA0C,KAAK,CACpE,OAAO,SAAS,wDAAwD,CACxE,OAAO,UAAU,6CAA6C,CAC9D,OAAO,OAAO,SAAwD;AACrE,UAAQ,OAAO,MACb,yFACD;AACD,QAAM,UAAU,OAAO,EAAE,KAAK;GAC9B;AAGJ,YACG,QAAQ,oBAAoB,CAC5B,YACC,kRAID,CACA,OAAO,eAAe,yDAAyD,CAC/E,OAAO,aAAa,+DAA+D,CACnF,OAAO,aAAa,4DAA4D,CAChF,QACE,UAAkB,SAAqE;AACtF,UAAQ,OAAO,MACb,6EACD;AACD,UAAQ,OAAO,EAAE,UAAU;GAAE,QAAQ,KAAK;GAAU,MAAM,KAAK;GAAQ,QAAQ,KAAK;GAAQ,CAAC;GAEhG;AAGH,YACG,QAAQ,QAAQ,CAChB,YACC,4OAID,CACA,OAAO,aAAa,mDAAmD,CACvE,OACC,sBACA,gGACD,CACA,OACC,uBACA,sEACD,CACA,OACC,aACA,6DACD,CACA,QAAQ,SAAsF;AAC7F,UAAQ,OAAO,MACb,6EACD;AACD,WAAS,OAAO,EAAE;GAChB,QAAQ,KAAK;GACb,UAAU,KAAK;GACf,WAAW,KAAK;GAChB,QAAQ,KAAK,SAAS;GACvB,CAAC;GACF;AAGJ,YACG,QAAQ,aAAa,CACrB,YACC,gMAGD,CACA,OACC,gBACA,4DACD,CACA,OACC,oBACA,wDACD,CACA,OAAO,UAAU,+CAA+C,CAChE,OACC,OAAO,SAA6D;AAClE,QAAM,aAAa,KAAK;GAE3B;;;;;;;;;;;;;;;;;;;;;;;ACjRL,SAAgB,YAAoB;AAClC,QAAO,KAAK,SAAS,EAAE,WAAW,SAAS;;AAG7C,SAAgB,UAAU,MAAsB;AAC9C,QAAO,KAAK,WAAW,EAAE,GAAG,KAAK,KAAK;;;AAIxC,SAAS,iBAAiB,MAAiD;CACzE,MAAM,MAAyC,EAAE;CACjD,IAAI,MAAM;AACV,MAAK,MAAM,OAAO,KAAK,MAAM,KAAK,EAAE;EAClC,MAAM,WAAW,gBAAgB,KAAK,IAAI;AAC1C,MAAI,YAAY,KAAK;GACnB,MAAM,MAAM,IAAI;GAChB,MAAM,OAAO,SAAS,GAAG,MAAM,CAAC,QAAQ,gBAAgB,GAAG;AAC3D,OAAI,OAAO,MAAM,QAAQ,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK,GAAG,CAAC,KAAK;AACvD;;EAEF,MAAM,KAAK,6BAA6B,KAAK,IAAI;AACjD,MAAI,CAAC,GAAI;AACT,QAAM,GAAG;EACT,MAAM,MAAM,GAAG,GAAG,MAAM;AACxB,MAAI,CAAC,IACH,KAAI,OAAO,EAAE;WACJ,IAAI,WAAW,IAAI,IAAI,IAAI,SAAS,IAAI,CACjD,KAAI,OAAO,IACR,MAAM,GAAG,GAAG,CACZ,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,QAAQ,gBAAgB,GAAG,CAAC,CAChD,OAAO,QAAQ;MAElB,KAAI,OAAO,IAAI,QAAQ,gBAAgB,GAAG;;AAG9C,QAAO;;;AAIT,SAAgB,eAAe,MAAc,MAAc,MAAwB;CACjF,MAAM,IAAI,qCAAqC,KAAK,KAAK;AACzD,KAAI,CAAC,EACH,OAAM,IAAI,MACR,GAAG,KAAK,gEACT;CAEH,MAAM,KAAK,iBAAiB,EAAE,GAAG;CACjC,MAAM,OAAO,EAAE,GAAG,MAAM;AACxB,KAAI,CAAC,KAAM,OAAM,IAAI,MAAM,GAAG,KAAK,oDAAoD;CACvF,MAAM,MAAgB;EAAE;EAAM;EAAM;EAAM;AAC1C,KAAI,OAAO,GAAG,UAAU,YAAY,GAAG,MAAO,KAAI,QAAQ,GAAG;AAC7D,KAAI,MAAM,QAAQ,GAAG,MAAM,IAAI,GAAG,MAAM,OAAQ,KAAI,QAAQ,GAAG;AAC/D,KAAI,OAAO,GAAG,gBAAgB,YAAY,GAAG,YAAa,KAAI,cAAc,GAAG;AAC/E,QAAO;;;AAIT,SAAgB,UAAU,MAAwB;CAChD,MAAM,OAAO,UAAU,KAAK;CAC5B,IAAI;AACJ,KAAI;AACF,SAAO,aAAa,MAAM,OAAO;SAC3B;AACN,QAAM,IAAI,MACR,mBAAmB,KAAK,OAAO,KAAK,8EAErC;;AAEH,QAAO,eAAe,MAAM,MAAM,KAAK;;;;AAKzC,MAAM,aAAwC;CAC5C,OAAO;CACP,QAAQ;CACR,MAAM;CACP;;;;AAKD,SAAgB,UACd,OACA,WACkB;CAClB,MAAM,IAAI,MAAM,aAAa;AAC7B,KAAI,EAAE,SAAS,QAAQ,CAAE,QAAO;AAChC,KAAI,EAAE,SAAS,SAAS,CAAE,QAAO;AACjC,KAAI,EAAE,SAAS,OAAO,CAAE,QAAO;AAC/B,KAAI,UACF,MAAK,MAAM,KAAK,OAAO,OAAO,UAAU,EAAE;EACxC,MAAM,WAAW,EAAE,aAAa;AAChC,MAAI,SAAU,QAAO;AACrB,MAAI,EAAE,OAAO,SAAS,MAAO,QAAO;AACpC,MAAI,EAAE,OAAO,YAAY,MAAO,QAAO;;AAG3C,QAAO;;;AAIT,MAAM,sCAAsB,IAAI,KAAa;;;;AAK7C,SAAgB,aACd,OACA,WACoB;AACpB,KAAI,CAAC,MAAO,QAAO;CACnB,MAAM,OAAO,UAAU,OAAO,UAAU;AACxC,KAAI,KAAM,QAAO,WAAW;AAC5B,KAAI,CAAC,oBAAoB,IAAI,MAAM,EAAE;AACnC,sBAAoB,IAAI,MAAM;AAC9B,UAAQ,MACN,mBAAmB,MAAM,gDACA,WAAW,OAAO,SAC5C;;AAEH,QAAO,WAAW;;;AAIpB,SAAgB,gBAAgB,KAAyB;CACvD,MAAM,OAAiB,EAAE;AACzB,KAAI,IAAI,OAAO,OAAQ,MAAK,KAAK,kBAAkB,IAAI,MAAM,KAAK,IAAI,CAAC;AACvE,MAAK,KAAK,0BAA0B,IAAI,KAAK;AAC7C,QAAO;;;AAIT,SAAgB,WAAW,MAAc,QAA2C;AAClF,QAAO,GAAG,KAAK,IAAI,UAAU,YAAY,MAAM,GAAG,KAAK,SAAS,IAAI,GAAG;;;;;AC/IzE,SAASE,OAAK,GAAkB;CAC9B,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,SAAQ,MAAM,IAAI,eAAe,GAAG,IAAI;AACxC,SAAQ,WAAW;;;AAIrB,SAAS,WAAW,OAAqD;CACvE,MAAM,MAA8B,EAAE;AACtC,MAAK,MAAM,KAAK,SAAS,EAAE,EAAE;EAC3B,MAAM,KAAK,EAAE,QAAQ,IAAI;AACzB,MAAI,MAAM,EACR,OAAM,IAAI,mBAAmB,kCAAkC,EAAE,GAAG;AAEtE,MAAI,EAAE,MAAM,GAAG,GAAG,IAAI,EAAE,MAAM,KAAK,EAAE;;AAEvC,QAAO;;AAGT,SAASC,cAAY,GAAmB;CACtC,MAAM,IAAI,SAAS,GAAG,GAAG;AACzB,KAAI,OAAO,MAAM,EAAE,IAAI,KAAK,EAC1B,OAAM,IAAI,mBAAmB,oCAAoC,EAAE,GAAG;AAExE,QAAO;;AAGT,SAAS,cAAc,GAAmB;CACxC,MAAM,IAAI,SAAS,GAAG,GAAG;AACzB,KAAI,CAAC,OAAO,UAAU,EAAE,IAAI,IAAI,KAAK,IAAI,EACvC,OAAM,IAAI,mBAAmB,wEAAwE,EAAE,GAAG;AAE5G,QAAO;;;AAIT,SAAS,YAAY,MAAsC;CACzD,MAAM,MAAgB,EAAE;AACxB,MAAK,MAAM,KAAK,QAAQ,EAAE,CACxB,MAAK,MAAM,KAAK,EAAE,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ,EAAE;AACjE,MAAI,CAAE,cAAoC,SAAS,EAAE,CACnD,OAAM,IAAI,mBAAmB,IAAI,EAAE,wBAAwB,cAAc,KAAK,KAAK,CAAC,GAAG;AAEzF,MAAI,CAAC,IAAI,SAAS,EAAE,CAAE,KAAI,KAAK,EAAE;;AAGrC,QAAO;;;AAYT,SAAS,iBAAiB,QAA4B,MAAiC;CACrF,MAAM,iBACJ,KAAK,aAAa,UAClB,KAAK,QAAQ,UACb,KAAK,gBAAgB,UACrB,KAAK,gBAAgB,UACrB,KAAK,UAAU;AACjB,KAAI,WAAW,UAAa,CAAC,eAAgB,QAAO;CACpD,MAAM,MAAoC,EAAE;AAC5C,KAAI,WAAW,OAAW,KAAI,WAAW,OAAO,MAAM,IAAI,CAAC;AAC3D,KAAI,KAAK,aAAa,OAAW,KAAI,WAAW,KAAK;AACrD,KAAI,KAAK,QAAQ,OAAW,KAAI,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;AAC9F,KAAI,KAAK,gBAAgB,OAAW,KAAI,cAAc,KAAK;AAC3D,KAAI,KAAK,gBAAgB,OAAW,KAAI,cAAc,KAAK;AAC3D,KAAI,KAAK,UAAU,OAAW,KAAI,QAAQ,KAAK,MAAM,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;AACpG,KAAI,WAAW,UAAa,OAAO,SAAS,IAAI,IAAI,IAAI,UAAU;EAEhE,MAAM,QAAQ,OAAO,MAAM,IAAI,CAAC;AAChC,MAAI,SAAS,UAAU,UACrB,OAAM,IAAI,mBACR,sBAAsB,MAAM,yFAE7B;;AAGL,KAAI,CAAC,OAAO,KAAK,IAAI,CAAC,OACpB,OAAM,IAAI,mBAAmB,+FAA+F;AAE9H,QAAO;;AAGT,SAAgB,+BAA+B,cAA6B;AAC1E,cACG,YAAY,qEAAqE,CACjF,aAAa;EACZ,MAAM,EAAE,YAAY,oBAAoB;AACxC,UAAQ,KAAK;AACb,OAAK,MAAM,QAAQ,kBAAkB,QAAQ,CAAE,SAAQ,IAAI,KAAK,OAAO;AACvE,UAAQ,KAAK;AACb,UAAQ,IAAIC,MAAI,iBAAiB,QAAQ,UAAU,OAAO,MAAM,gBAAgB,QAAQ,UAAU,QAAQ,OAAO,CAAC;AAClH,UAAQ,KAAK;GACb;AAEJ,cACG,QAAQ,aAAa,CACrB,YACC,wYAKD,CACA,OAAO,oBAAoB,wEAAwE,CACnG,eAAe,mBAAmB,qCAAqC,CACvE,OAAO,qBAAqB,8DAA8D,CAC1F,OAAO,wBAAwB,2CAA2C,CAC1E,OAAO,sBAAsB,oCAAoC,GAAW,QAAkB,CAAC,GAAG,KAAK,EAAE,EAAE,EAAE,CAAa,CAC1H,OAAO,iBAAiB,uCAAuC,CAC/D,OAAO,sBAAsB,0EAA0E,CACvG,OAAO,wBAAwB,6DAA6D,CAC5F,OAAO,qBAAqB,6DAA6D,CACzF,OAAO,6BAA6B,uEAAuED,cAAY,CACvH,OAAO,uBAAuB,2DAA2D,CACzF,OAAO,qBAAqB,0DAA0D,cAAc,CACpG,OAAO,iBAAiB,6CAA6C,cAAc,KAAK,KAAK,GAAG,MAAM,GAAW,QAAkB,CAAC,GAAG,KAAK,EAAE,EAAE,EAAE,CAAa,CAC/J,QAEG,MACA,SAeG;AACH,MAAI;GACF,MAAM,WAAW,KAAK;GACtB,MAAM,SAAS,KAAK;AACpB,OAAI,YAAY,aAAa,eAAe,aAAa,SACvD,OAAM,IAAI,mBAAmB,gDAAgD,SAAS,GAAG;AAE3F,OAAI,UAAU,WAAW,YAAY,WAAW,QAC9C,OAAM,IAAI,mBAAmB,0CAA0C,OAAO,GAAG;AAEnF,OAAI,aAAa,YAAY,CAAC,KAAK,QACjC,OAAM,IAAI,mBAAmB,kEAAkE;GAEjG,MAAM,OAAO,YAAY,KAAK,KAAK;AACnC,eAAY;IACV;IACA,SAAS,KAAK,WAAW;IACzB,SAAS,KAAK,WAAW;IACzB,OAAO,KAAK;IACZ,WAAW,KAAK;IAChB,KAAK,WAAW,KAAK,IAAI;IACzB,MAAM,KAAK;IACX,GAAI,WAAW,EAAE,UAAU,GAAG,EAAE;IAChC,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,aAAa,GAAG,EAAE;IAC7D,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;IAC5B,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAK,eAAe,GAAG,EAAE;IACnE,YAAY,KAAK;IACjB,GAAI,KAAK,aAAa,SAAY,EAAE,UAAU,KAAK,UAAU,GAAG,EAAE;IAClE,GAAI,KAAK,SAAS,EAAE,MAAM,GAAG,EAAE;IAChC,CAAC;GACF,MAAM,EAAE,YAAY,oBAAoB;AACxC,WAAQ,IAAIE,KAAG,YAAY,KAAK,QAAQ,CAAC;AACzC,OAAI,QAAQ,WAAW,KAAM,SAAQ,IAAI,aAAa,KAAK,aAAa,QAAQ,UAAU,OAAO,QAAQ;AACzG,QAAK,MAAM,QAAQ,kBAAkB,QAAQ,CAAE,SAAQ,IAAID,MAAI,KAAK,OAAO,CAAC;WACrE,GAAG;AACV,UAAK,EAAE;;GAGZ;AAEH,cACG,QAAQ,gBAAgB,CACxB,YAAY,0EAA0E,CACtF,OAAO,qBAAqB,+CAA+C,cAAc,CACzF,OAAO,iBAAiB,6CAA6C,cAAc,KAAK,KAAK,GAAG,wBAAwB,GAAW,QAAkB,CAAC,GAAG,KAAK,EAAE,EAAE,EAAE,CAAa,CACjL,QAEG,MACA,SACG;AACH,MAAI;AACF,OAAI,KAAK,aAAa,UAAa,KAAK,SAAS,OAC/C,OAAM,IAAI,mBAAmB,qDAAqD;GAEpF,MAAM,OAAO,KAAK,SAAS,SAAY,SAAY,YAAY,KAAK,KAAK;AACzE,kBAAe,MAAM;IACnB,GAAI,KAAK,aAAa,SAAY,EAAE,UAAU,KAAK,UAAU,GAAG,EAAE;IAClE,GAAI,SAAS,SAAY,EAAE,MAAM,GAAG,EAAE;IACvC,CAAC;AACF,WAAQ,IAAIC,KAAG,YAAY,KAAK,UAAU,CAAC;GAC3C,MAAM,EAAE,YAAY,oBAAoB;AACxC,QAAK,MAAM,QAAQ,kBAAkB,QAAQ,CAAE,SAAQ,IAAID,MAAI,KAAK,OAAO,CAAC;WACrE,GAAG;AACV,UAAK,EAAE;;GAGZ;AAEH,cACG,QAAQ,gBAAgB,CACxB,YAAY,mDAAmD,CAC/D,QAAQ,SAAiB;AACxB,MAAI;AACF,kBAAe,KAAK;AACpB,WAAQ,IAAIC,KAAG,YAAY,KAAK,UAAU,CAAC;WACpC,GAAG;AACV,UAAK,EAAE;;GAET;AAEJ,cACG,QAAQ,aAAa,CACrB,YAAY,wEAAwE,CACpF,QAAQ,SAAiB;AACxB,MAAI;AACF,eAAY,KAAK;AACjB,WAAQ,IAAIA,KAAG,oBAAoB,OAAO,CAAC;WACpC,GAAG;AACV,UAAK,EAAE;;GAET;AAEJ,cACG,QAAQ,gBAAgB,CACxB,YAAY,+CAA+C,CAC3D,QAAQ,SAAiB;AACxB,MAAI;AACF,sBAAmB,MAAM,KAAK;AAC9B,WAAQ,IAAIA,KAAG,YAAY,KAAK,UAAU,CAAC;WACpC,GAAG;AACV,UAAK,EAAE;;GAET;AAEJ,cACG,QAAQ,iBAAiB,CACzB,YAAY,qEAAqE,CACjF,QAAQ,SAAiB;AACxB,MAAI;AACF,sBAAmB,MAAM,MAAM;AAC/B,WAAQ,IAAIA,KAAG,YAAY,KAAK,WAAW,CAAC;WACrC,GAAG;AACV,UAAK,EAAE;;GAET;AAEJ,cACG,QAAQ,cAAc,CACtB,YAAY,mEAAmE,CAC/E,OAAO,OAAO,SAA6B;AAC1C,MAAI;GACF,MAAM,EAAE,YAAY,oBAAoB;GACxC,MAAM,QAAQ,QAAQ,QAAQ,UAAU;AACxC,OAAI,CAAC,SAAS,CAAC,QAAQ,UAAU,OAC/B,OAAM,IAAI,mBACR,OAAO,sBAAsB,KAAK,KAAK,2CACxC;AAEH,WAAQ,IAAID,MAAI,aAAa,MAAM,IAAI,QAAQ,UAAU,OAAO,OAAO,QAAQ,KAAK,CAAC;GACrF,MAAM,IAAI,MAAM,aAAa,OAAO,QAAQ,UAAU,QAAQ,cAAc,QAAQ,CAAC;GACrF,MAAM,OAAO,GAAG,EAAE,SAAS,GAAG,EAAE,MAAM,KAAK,EAAE,YAAY,KAAM,QAAQ,EAAE,CAAC;AAC1E,OAAI,EAAE,SAAS;AAEb,YAAQ,IAAIA,MAAI,KAAK,KAAK,IAAI,EAAE,UAAU,CAAC;AAC3C,YAAQ,IAAI,YAAY,EAAE,OAAO,MAAM,GAAG,IAAI,GAAG;AACjD;;AAEF,WAAQ,IAAI,EAAE,KAAKC,KAAG,KAAK,OAAO,GAAG,IAAI,KAAK,OAAO,CAAC;AACtD,WAAQ,IAAI,YAAY,EAAE,OAAO,MAAM,GAAG,IAAI,GAAG;AACjD,WAAQ,WAAW,EAAE,KAAK,IAAI;WACvB,GAAG;AACV,UAAK,EAAE;;GAET;;AAKN,SAAS,eAAqB;CAC5B,MAAM,EAAE,YAAY,oBAAoB;CACxC,MAAM,UAAU,OAAO,QAAQ,QAAQ,QAAQ;AAC/C,KAAI,CAAC,QAAQ,QAAQ;AACnB,UAAQ,IAAID,MAAI,oEAAoE,CAAC;AACrF;;AAEF,MAAK,MAAM,CAAC,KAAK,WAAW,QAAS,SAAQ,IAAI,KAAK,IAAI,OAAO,GAAG,CAAC,GAAG,gBAAgB,OAAO,GAAG;;AAGpG,SAAS,yBAAyB,YAA2B;AAC3D,YACG,QAAQ,OAAO,CACf,YAAY,kDAAkD,CAC9D,aAAa,cAAc,CAAC;AAE/B,YACG,QAAQ,uBAAuB,CAC/B,YACC,0JAED,CACA,OAAO,qBAAqB,+CAA+C,CAC3E,OAAO,iBAAiB,4DAA4D,CACpF,OAAO,yBAAyB,8DAA8D,cAAc,CAC5G,OAAO,yBAAyB,oDAAoD,MACnF,EAAE,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ,CAClD,CACA,OAAO,uBAAuB,6EAA6E,CAC3G,QAAQ,KAAa,QAA4B,SAAuB;AACvE,MAAI;GACF,MAAM,QAAQ,iBAAiB,QAAQ,KAAK;AAC5C,YAAS,KAAK,MAAM;AACpB,WAAQ,IAAIC,KAAG,SAAS,IAAI,KAAK,gBAAgB,MAAM,GAAG,CAAC;WACpD,GAAG;AACV,UAAK,EAAE;;GAET;AAEJ,YACG,QAAQ,gBAAgB,CACxB,YAAY,qDAAqD,CACjE,QAAQ,QAAgB;AACvB,MAAI;AACF,cAAW,IAAI;AACf,WAAQ,IAAIA,KAAG,SAAS,IAAI,UAAU,CAAC;WAChC,GAAG;AACV,UAAK,EAAE;;GAET;;;AAIN,SAAgB,4BAA4B,WAA0B;AAKpE,0BAJmB,UAChB,QAAQ,UAAU,CAClB,YAAY,gEAAgE,CAC5E,aAAa,cAAc,CAAC,CACK;AAOpC,0BAJiB,UACd,QAAQ,SAAS,EAAE,QAAQ,MAAM,CAAC,CAClC,YAAY,oDAAoD,CAChE,aAAa,cAAc,CAAC,CACG;;;;;AC7WpC,SAASC,OAAK,GAAkB;CAC9B,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,SAAQ,MAAM,IAAI,eAAe,GAAG,IAAI;AACxC,SAAQ,WAAW;;AAGrB,SAAgB,2BAA2B,WAA0B;AACnE,WACG,QAAQ,uBAAuB,CAC/B,YACC,yRAID,CACA,OAAO,qBAAqB,uDAAuD,CACnF,QAAQ,MAA0B,OAA2B,SAAgC;AAC5F,MAAI;GACF,MAAM,EAAE,YAAY,oBAAoB;AACxC,OAAI,SAAS,QAAW;AACtB,SAAK,MAAM,QAAQ,eAAe,QAAQ,CAAE,SAAQ,IAAI,KAAK,OAAO;AACpE;;GAEF,IAAI,aAA8B;GAClC,IAAI,KAAK;AACT,OAAI,kBAAkB,KAAK,EAAE;AAC3B,iBAAa;AACb,QAAI,UAAU,QAAW;KACvB,MAAM,OAAO,oBAAoB,SAAS,KAAK,SAAS;KACxD,MAAM,IAAI,QAAQ,UAAU;AAC5B,SAAI,EAAG,SAAQ,IAAI,KAAK,KAAK,GAAG,WAAW,GAAG,EAAE,OAAO,eAAe,WAAW;AACjF;;AAEF,SAAK;cACI,UAAU,OACnB,OAAM,IAAI,mBACR,+BAA+B,MAAM,4CACtC;GAEH,MAAM,OAAO,oBAAoB,SAAS,KAAK,SAAS;GAExD,MAAM,IADQ,iBAAiB,MAAM,YAAY,GAAG,CACpC,UAAU;AAC1B,OAAI,GAAG;AACL,YAAQ,IAAIC,KAAG,GAAG,KAAK,GAAG,WAAW,UAAU,EAAE,OAAO,cAAc,CAAC;AACvE,YAAQ,IAAIC,MAAI,KAAK,eAAe,EAAE,GAAG,CAAC;;WAErC,GAAG;AACV,UAAK,EAAE;;GAET;;;;;;;;;;;;;ACvCN,MAAM,WAA6C;CAAC;CAAQ;CAAU;CAAU;CAAO;AAEvF,SAASC,QAAM,IAA2B;AACxC,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;;AAO1D,SAAS,eAAe,QAAgB,IAA2B;CACjE,IAAI;AACJ,KAAI;AACF,QAAM,aAAa,WAAW,QAAQ,GAAG,EAAE,OAAO;SAC5C;AACN,SAAO;;CAET,MAAM,QAAQ,IAAI,MAAM,KAAK;AAC7B,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;EAC1C,MAAM,OAAO,MAAM,GAAG,MAAM;AAC5B,MAAI,CAAC,KAAK,WAAW,IAAI,CAAE;AAC3B,MAAI;GACF,MAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,OAAI,EAAE,SAAS,YAAY,OAAO,EAAE,WAAW,SAAU,QAAO,EAAE;UAC5D;;AAIV,QAAO;;AAGT,SAAS,aAAa,QAAgB,IAA8B;CAClE,MAAM,SAAS,eAAe,QAAQ,GAAG,GAAG;AAC5C,QAAO;EACL,IAAI,GAAG;EACP,OAAO,GAAG;EACV,OAAO,GAAG;EACV,IAAI,GAAG;EACP,MAAM,GAAG;EACT,IAAI,GAAG,UAAU,WAAW,GAAG,MAAM,OAAO;EAC5C;EACA,QAAQ,kBAAkB,UAAU,GAAG;EACxC;;;;;;;AAQH,eAAsB,YACpB,QACA,KACA,OAAoB,EAAE,EACkC;CACxD,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,wBAAQ,IAAI,KAA2B;CAC7C,MAAM,0BAAU,IAAI,KAAa;AAEjC,MAAK,MAAM,MAAM,KAAK;EACpB,MAAM,KAAK,WAAW,QAAQ,GAAG;AACjC,MAAI,CAAC,GAAI,OAAM,IAAI,MAAM,oBAAoB,GAAG,GAAG;AACnD,QAAM,IAAI,IAAI,GAAG;AACjB,MAAI,CAAC,SAAS,SAAS,GAAG,MAAM,CAAE,SAAQ,IAAI,GAAG;;CAEnD,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,QAAO,QAAQ,OAAO,KAAK,KAAK,KAAK,GAAG,UAAU;AAChD,QAAMA,QAAM,KAAK,IAAI,YAAY,KAAK,IAAI,GAAG,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC;AACrE,OAAK,MAAM,MAAM,CAAC,GAAG,QAAQ,EAAE;GAC7B,MAAM,KAAK,WAAW,QAAQ,GAAG;AACjC,OAAI,GAAI,OAAM,IAAI,IAAI,GAAG;AACzB,OAAI,CAAC,MAAM,SAAS,SAAS,MAAM,IAAI,GAAG,CAAE,MAAM,CAAE,SAAQ,OAAO,GAAG;;;AAG1E,QAAO;EAAE,SAAS,IAAI,KAAK,OAAO,aAAa,QAAQ,MAAM,IAAI,GAAG,CAAE,CAAC;EAAE,UAAU,CAAC,GAAG,QAAQ;EAAE;;;;;AC/DnG,SAAS,gBAAwB;CAC/B,MAAM,EAAE,YAAY,oBAAoB;AACxC,QAAO,cAAc,QAAQ;;AAG/B,SAAS,KAAK,GAAkB;CAC9B,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,SAAQ,MAAM,IAAI,eAAe,GAAG,IAAI;AACxC,SAAQ,WAAW;;AAGrB,SAAgB,uBAAuB,WAA0B;AAC/D,WACG,QAAQ,MAAM,CACd,YACC,mTAKD,CACA,mBAAmB,KAAK,CACxB,OAAO,qBAAqB,2DAA2D,CACvF,OAAO,kBAAkB,yGAAyG,CAClI,OAAO,iBAAiB,mDAAmD,CAC3E,OAAO,oBAAoB,gFAAgF,CAC3G,OAAO,kBAAkB,kEAAkE,CAC3F,OAAO,mBAAmB,6CAA6C,CACvE,OAAO,kBAAkB,sDAAsD,CAC/E,OAAO,iBAAiB,+EAA+E,CACvG,OAAO,aAAa,4CAA4C,CAChE,OAAO,cAAc,iGAAiG,CACtH,OAAO,iBAAiB,4BAA4B,CACpD,SAAS,aAAa,qEAAqE,CAC3F,OACC,OACE,MACA,SAYG;AACH,MAAI;GACF,MAAM,YAAY,KAAK,SAAS,KAAK;GACrC,IAAI,aAAa;GACjB,IAAI,QAAQ,KAAK;GACjB,IAAI;AACJ,OAAI,KAAK,OAAO;IAGd,MAAM,MAAM,UAAU,KAAK,MAAM;AACjC,iBAAa,CAAC,GAAG,gBAAgB,IAAI,EAAE,GAAG,KAAK;IAG/C,IAAI;AACJ,QAAI;AACF,sBAAiB,oBAAoB,CAAC,QAAQ;YACxC;AACN,sBAAiB;;AAEnB,iBAAa,aAAa,IAAI,OAAO,eAAe;AACpD,QAAI,CAAC,MAAO,SAAQ,WAAW,KAAK,OAAO,gBAAgB,KAAK,CAAC,OAAO;;AAE1E,OAAI,KAAK,OAAO;IACd,MAAM,QAAQ,gBAAgB,WAAW,CAAC;AAC1C,QAAI,CAAC,MACH,OAAM,IAAI,MAAM,yCAAyC;IAE3D,MAAM,KAAK,MAAM,SAAS;KACxB,QAAQ,KAAK,MAAM,MAAM,IAAI;KAC7B,WAAW,aAAa;KACxB,cAAc,KAAK;KACnB,WAAW,KAAK;KAChB;KACA,QAAQ,KAAK,SAAS;KACtB,SAAS,KAAK;KACd;KACA;KACD,CAAC;AACF,YAAQ,WAAW;AACnB;;GAEF,MAAM,KAAK,MAAM,UAAU;IACzB,cAAc,KAAK;IACnB,WAAW,aAAa;IACxB,WAAW,KAAK;IAChB;IACA,SAAS,KAAK;IACd,QAAQ,KAAK,SAAS;IACtB,cAAc,KAAK;IACnB;IACD,CAAC;AACF,WAAQ,WAAW;WACZ,GAAG;AACV,QAAK,EAAE;;GAGZ;AAEH,WACG,QAAQ,KAAK,CACb,YAAY,4EAA4E,CACxF,OAAO,SAAS,yDAAyD,CACzE,QAAQ,SAA4B;AACnC,MAAI;AACF,WAAQ,IAAI,SAAS,eAAe,EAAE,KAAK,QAAQ,KAAK,CAAC;WAClD,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,cAAc,CACtB,YAAY,sEAAsE,CAClF,OAAO,SAAS,iCAAiC,CACjD,OAAO,sBAAsB,mEAAmE,CAChG,OAAO,OAAO,IAAwB,SAAyD;AAC9F,MAAI;GACF,MAAM,EAAE,YAAY,oBAAoB;GACxC,MAAM,WACJ,KAAK,aAAa,UAAa,KAAK,aAAa,QAC7C,IACA,KAAK,aAAa,OAChB,QAAQ,KAAK,eACb,OAAO,KAAK,SAAS;AAC7B,SAAM,cAAc,eAAe,EAAE,MAAM,MAAM,KAAK,QAAQ,MAAM,SAAS;WACtE,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,cAAc,CACtB,YAAY,yDAAyD,CACrE,OAAO,cAAc,kCAAkC,YAAY,CACnE,QAAQ,IAAY,SAA4B;AAC/C,MAAI;AACF,WAAQ,IAAI,aAAa,eAAe,EAAE,IAAI,QAAQ,OAAO,UAAU,MAAM,KAAK,KAAK,CAAC;WACjF,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,QAAQ,CAChB,YAAY,4DAA4D,CACxE,aAAa;EACZ,MAAM,OAAO,MAAM,SAAS;GAAC;GAAM;GAAK;GAAsB,EAAE,EAAE,OAAO,WAAW,CAAC;AACrF,OAAK,GAAG,UAAU,MAAM,qBAAK,IAAI,MAAM,qBAAqB,EAAE,UAAU,CAAC,CAAC;AAC1E,OAAK,GAAG,UAAU,SAAS;AACzB,WAAQ,WAAW,QAAQ;IAC3B;GACF;AAEJ,WACG,QAAQ,YAAY,CACpB,YAAY,0EAA0E,CACtF,OAAO,WAAW,4GAA4G,CAC9H,OAAO,OAAO,IAAwB,SAA8B;AACnE,MAAI;GACF,MAAM,EAAE,YAAY,oBAAoB;GACxC,MAAM,SAAS,eAAe;GAC9B,MAAM,OAAO,QAAQ,IAAI,oBAAoB;AAC7C,OAAI,IAAI;IACN,MAAM,MAAM,KAAK,QACb,MAAM,mBAAmB,IAAI,QAAQ,KAAK,UAAU,KAAK,GACzD,MAAM,kBAAkB,QAAQ,SAAS,IAAI,KAAK;AACtD,YAAQ,IAAI,IAAI;SAEhB,SAAQ,IAAI,MAAM,eAAe,QAAQ,SAAS,MAAM,KAAK,UAAU,KAAK,CAAC;WAExE,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,aAAa,CACrB,YAAY,+EAA+E,CAC3F,QAAQ,SAA6B;AACpC,MAAI;GACF,MAAM,SAAS,eAAe;GAC9B,MAAM,SAAS,WAAW,OAAO;AACjC,OAAI,SAAS,OAAO;AAClB,YAAQ,IAAI,WAAW,OAAO,GAAG,aAAa,QAAQ,OAAO,GAAGC,MAAI,kBAAkB,CAAC;AACvF;;AAEF,OAAI,SAAS,QAAQ;AACnB,QAAI,CAAC,WAAW,OAAO,EAAE;AACvB,aAAQ,IAAIA,MAAI,kBAAkB,CAAC;AACnC;;IAEF,MAAM,QAAQ,aAAa,QAAQ,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK;AAC7D,YAAQ,IAAI,MAAM,MAAM,IAAI,CAAC,KAAK,KAAK,CAAC;AACxC;;AAEF,OAAI,MAAM;IACR,MAAM,OAAO,WAAW,QAAQ,KAAK;AACrC,QAAI,CAAC,WAAW,KAAK,EAAE;AACrB,0BAAK,IAAI,MAAM,oBAAoB,OAAO,CAAC;AAC3C;;IAEF,MAAM,QAAQ,aAAa,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK;AAC3D,YAAQ,IAAI,MAAM,MAAM,IAAI,CAAC,KAAK,KAAK,CAAC;AACxC;;AAEF,OAAI,CAAC,WAAW,OAAO,EAAE;AACvB,YAAQ,IAAIA,MAAI,mBAAmB,CAAC;AACpC;;AAEF,QAAK,MAAM,KAAK,YAAY,OAAO,CAAC,QAAQ,MAAM,EAAE,SAAS,SAAS,CAAC,CAAC,MAAM,EAAE;IAC9E,MAAM,KAAK,SAAS,GAAG,OAAO,GAAG,IAAI;IACrC,MAAM,MAAM,GAAG,OAAO,OAAO,MAAM,QAAQ,EAAE;AAC7C,YAAQ,IAAI,KAAK,EAAE,QAAQ,UAAU,GAAG,CAAC,IAAI,GAAG,OAAO,GAAG,MAAM,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,GAAG;;WAE1G,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,kBAAkB,CAC1B,YAAY,qEAAqE,CACjF,OAAO,OAAO,IAAY,SAAiB;AAC1C,MAAI;AACF,SAAM,YAAY,eAAe,EAAE,IAAI,KAAK;AAC5C,WAAQ,IAAI,KAAK;WACV,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,iBAAiB,CACzB,YACC,4NAED,CACA,OAAO,OAAO,SAAiB;AAC9B,MAAI;GACF,IAAI;AACJ,OAAI;AACF,cAAU,KAAK,MAAM,KAAK;WACpB;AACN,yBAAK,IAAI,MAAM,wBAAwB,OAAO,CAAC;AAC/C;;GAEF,MAAM,IAAI,MAAM,kBAAkB,eAAe,EAAE,QAAQ,KAAK,QAAQ;AACxE,WAAQ,IAAI,QAAQ,EAAE,GAAG,IAAI,EAAE,KAAK,WAAW,EAAE,GAAG,cAAc;WAC3D,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,aAAa,CACrB,YAAY,4HAA4H,CACxI,QAAQ,OAAe;AACtB,MAAI;AACF,WAAQ,IAAI,YAAY,eAAe,EAAE,GAAG,CAAC;WACtC,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,gBAAgB,CACxB,YAAY,oGAAoG,CAChH,OAAO,oBAAoB,iDAAiD,YAAY,CACxF,OAAO,OAAO,KAAe,SAA+B;AAC3D,MAAI;GACF,MAAM,EAAE,SAAS,aAAa,MAAM,YAAY,eAAe,EAAE,KAAK,EACpE,YAAY,KAAK,WAAW,OAAO,KACpC,CAAC;AACF,QAAK,MAAM,KAAK,QAAS,SAAQ,IAAI,KAAK,UAAU,EAAE,CAAC;AACvD,OAAI,SAAS,QAAQ;AACnB,YAAQ,MAAM,IAAI,0CAA0C,SAAS,KAAK,KAAK,GAAG,CAAC;AACnF,YAAQ,WAAW;AACnB;;GAEF,MAAM,SAAS,QAAQ,QAAQ,MAAM,CAAC,EAAE,GAAG,CAAC,KAAK,MAAM,EAAE,GAAG;AAC5D,OAAI,OAAO,QAAQ;AACjB,YAAQ,MAAM,IAAI,4BAA4B,OAAO,KAAK,KAAK,GAAG,CAAC;AACnE,YAAQ,WAAW;;WAEd,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,eAAe,CACvB,YAAY,uDAAuD,CACnE,QAAQ,OAAe;AACtB,MAAI;AACF,WAAQ,IAAI,cAAc,eAAe,EAAE,GAAG,CAAC;WACxC,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,sBAAsB,CAC9B,YACC,iNAGD,CACA,QAAQ,IAAY,QAAgB;AACnC,MAAI;AACF,OAAI,QAAQ,SAAS,QAAQ,MAAM;AACjC,yBAAK,IAAI,MAAM,8BAA8B,CAAC;AAC9C;;AAEF,OAAI,CAAC,WAAW,eAAe,EAAE,GAAG,EAAE;AACpC,yBAAK,IAAI,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAC1C;;GAEF,MAAM,OAAO,MAAM,UAAU,CAAC,YAAY,IAAI,EAAE,EAAE,OAAO,WAAW,CAAC;AACrE,QAAK,GAAG,UAAU,MAChB,qBAAK,IAAI,MAAM,+BAA+B,EAAE,QAAQ,yBAAyB,CAAC,CACnF;AACD,QAAK,GAAG,UAAU,SAAS;AACzB,QAAI,SAAS,EAAG,SAAQ,IAAI,cAAc,QAAQ,QAAQ,KAAK,aAAa;AAC5E,YAAQ,WAAW,QAAQ;KAC3B;WACK,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,qBAAqB,CAC7B,YAAY,6EAA6E,CACzF,OAAO,cAAc,gEAAgE,CACrF,OAAO,aAAa,mDAAmD,CACvE,OAAO,OAAO,IAAY,MAAc,SAAgD;AACvF,MAAI;GAEF,MAAM,MAAM,WADG,eAAe,EACC,GAAG;AAClC,OAAI,CAAC,KAAK;AACR,yBAAK,IAAI,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAC1C;;AAEF,OAAI,CAAC,IAAI,eAAe;AACtB,yBACE,IAAI,MACF,UAAU,GAAG,+GAEd,CACF;AACD;;GAEF,IAAI,QAAQ;GACZ,MAAM,KAAK,MAAM,UAAU;IACzB,cAAc,IAAI;IAClB,WAAW,IAAI;IACf,OAAO,KAAK,IAAI;IAChB,QAAQ,KAAK,SAAS;IACtB,YAAY;KAAC;KAAY,IAAI;KAAe;KAAM;KAAK;IACvD,gBAAgB,QAAQ;AACtB,aAAQ;;IAEX,CAAC;AACF,OAAI,KAAK,YAAY,QAAQ,MAAO,SAAQ,IAAI,MAAM;AACtD,WAAQ,WAAW;WACZ,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,eAAe,CACvB,YACC,yIAED,CACA,OAAO,cAAc,8BAA8B,mBAAmB,IAAI,YAAY,CACtF,OAAO,OAAO,MAA0B,SAA4B;AACnE,MAAI;GACF,MAAM,SAAS,eAAe;AAC9B,OAAI,SAAS,QAAQ;AACnB,YAAQ,IAAI,UAAU,OAAO,CAAC;AAC9B;;AAEF,OAAI,MAAM;AACR,yBAAK,IAAI,MAAM,2BAA2B,KAAK,oBAAoB,CAAC;AACpE;;GAEF,MAAM,MAAM,MAAM,mBAAmB,KAAK,QAAQ,oBAAoB,OAAO;AAC7E,WAAQ,IAAI,sBAAsB,IAAI,+BAA+B,IAAI,aAAa;WAC/E,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,aAAa,CACrB,YAAY,0EAA0E,CACtF,QAAQ,SAA6B;AACpC,MAAI,QAAQ,SAAS,QAAQ;AAC3B,wBAAK,IAAI,MAAM,yBAAyB,KAAK,oBAAoB,CAAC;AAClE;;AAEF,MAAI;GACF,MAAM,EAAE,YAAY,oBAAoB;AACxC,WAAQ,IAAI,YAAY,QAAQ,CAAC;WAC1B,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,qCAAqC,CAC7C,YACC,wNAGD,CACA,QAAQ,MAA0B,KAAyB,YAAgC;AAC1F,MAAI;GACF,MAAM,MAAM,iBACV,eAAe,EACf,QAAQ,QAAQ,IAAI,oBAAoB,IACxC,OAAO,QAAQ,KAAK,EACpB,WAAW,GACZ;AACD,OAAI,IAAK,SAAQ,IAAI,IAAI;WAClB,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,KAAK,CACb,YAAY,yEAAyE,CACrF,aAAa;AACZ,MAAI;AACF,qBAAkB,KAAK;AACvB,WAAQ,IAAI,uEAAuE;WAC5E,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,MAAM,CACd,YAAY,mDAAmD,CAC/D,aAAa;AACZ,MAAI;AACF,qBAAkB,MAAM;AACxB,WAAQ,IAAI,sDAAsD;WAC3D,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,+BAA+B,CACvC,YACC,oUAKD,CACA,QAAQ,QAA4B,aAAiC;AACpE,MAAI;AACF,OAAI,WAAW,UAAa,WAAW,UAAU;AAC/C,YAAQ,IAAI,mBAAmB,gBAAgB,CAAC,CAAC,KAAK,KAAK,CAAC;AAC5D;;AAEF,OAAI,WAAW,MAAM;IACnB,MAAM,IAAI,WAAW,SAAS;AAC9B,YAAQ,IACN,EAAE,YACE,kCAAkC,EAAE,SAAS,oBAC7C,0BAA0B,EAAE,SAAS,yCAC1C;AACD,YAAQ,IAAI,wBAAwB,EAAE,QAAQ,KAAK,KAAK,GAAG;AAC3D,YAAQ,IAAI,gBAAgB,EAAE,QAAQ;AACtC,YAAQ,IAAI,iGAAiG;AAC7G;;AAEF,OAAI,WAAW,OAAO;IACpB,MAAM,IAAI,aAAa;AACvB,YAAQ,IAAI,2BAA2B,EAAE,SAAS,mCAAmC;AACrF,YAAQ,IAAI,oBAAoB,EAAE,QAAQ,KAAK,KAAK,GAAG;AACvD;;AAEF,wBAAK,IAAI,MAAM,4BAA4B,OAAO,+BAA+B,CAAC;WAC3E,GAAG;AACV,QAAK,EAAE;;GAET;AAEJ,WACG,QAAQ,UAAU,CAClB,YAAY,oFAAoF,CAChG,aAAa;AACZ,MAAI;GACF,MAAM,IAAI,gBAAgB;AAC1B,QAAK,MAAM,QAAQ,EAAE,MAAO,SAAQ,IAAI,KAAK,OAAO;WAC7C,GAAG;AACV,QAAK,EAAE;;GAET;AAMJ,gCAJqB,UAClB,QAAQ,YAAY,CACpB,YAAY,2DAA2D,CAE9B;AAC5C,6BAA4B,UAAU;AACtC,4BAA2B,UAAU;;AAGvC,SAAS,YAAY,GAAmB;CACtC,MAAM,IAAI,SAAS,GAAG,GAAG;AACzB,QAAO,OAAO,MAAM,EAAE,GAAG,IAAI;;;;;ACzgB/B,MAAa,sBAAsB;CACjC;CACA;CACA;CACA;CACD;AAGD,MAAa,iBAAiB;AAC9B,MAAa,iBAAiB;;;;;;;;ACtD9B,MAAM,uBAAiC;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,yBAAyB,IAAI,IAAI;CACrC;CAAK;CAAM;CAAO;CAAM;CAAM;CAAM;CAAO;CAAM;CAAM;CAAO;CAC9D;CAAO;CAAO;CAAQ;CAAQ;CAAM;CAAM;CAC3C,CAAC;AAEF,SAAS,kBAAkB,KAA4B;CACrD,IAAI,IAAI,IAAI,MAAM;AAClB,KAAI,EAAE,QAAQ,yBAAyB,GAAG;AAC1C,KAAI,EAAE,QAAQ,aAAa,GAAG;AAC9B,KAAI,EAAE,QAAQ,iBAAiB,GAAG;AAClC,KAAI,EAAE,QAAQ,2BAA2B,KAAK;AAC9C,KAAI,EAAE,QAAQ,uBAAuB,KAAK;AAC1C,KAAI,EAAE,QAAQ,cAAc,KAAK;AACjC,KAAI,EAAE,QAAQ,WAAW,GAAG;AAC5B,KAAI,EAAE,QAAQ,YAAY,GAAG,CAAC,QAAQ,YAAY,GAAG;AACrD,KAAI,EAAE,QAAQ,QAAQ,IAAI,CAAC,MAAM;AACjC,QAAO,EAAE,UAAU,IAAI,IAAI;;AAG7B,SAAS,aAAa,MAAuB;AAC3C,QAAO,qBAAqB,MAAM,OAAO,GAAG,KAAK,KAAK,CAAC;;AAGzD,SAAS,YAAY,MAAsB;AAEzC,QADc,KAAK,MAAM,IAAI,CAE1B,KAAK,MAAM,MAAM;EAChB,MAAM,QAAQ,KAAK,aAAa;AAChC,MAAI,MAAM,KAAK,uBAAuB,IAAI,MAAM,CAAE,QAAO;AACzD,SAAO,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE;GACnD,CACD,KAAK,IAAI;;AAGd,SAAgB,aAAa,KAAqB;CAChD,IAAI,IAAI,IAAI,QAAQ,oBAAoB,GAAG;AAC3C,KAAI,EAAE,QAAQ,QAAQ,IAAI,CAAC,MAAM;AACjC,KAAI,EAAE,SAAS,IAAI;EACjB,MAAM,YAAY,EAAE,MAAM,GAAG,GAAG;EAChC,MAAM,YAAY,UAAU,YAAY,IAAI;AAC5C,MAAI,YAAY,KAAK,UAAU,MAAM,GAAG,UAAU,GAAG;;AAEvD,KAAI,EAAE,MAAM;AACZ,QAAO,YAAY,EAAE;;AAGvB,SAAgB,gBAAgB,SAAyB;CACvD,MAAM,QAAQ,QAAQ,MAAM,KAAK;CAEjC,MAAM,2BAA2B,IAAI,IAAI;EACvC;EAAa;EAAW;EAAa;EACrC;EAAW;EAAY;EAAW;EACnC,CAAC;CACF,MAAM,wBAAwB,IAAI,IAAI;EACpC;EAAc;EAAQ;EAAQ;EAAY;EAC1C;EAAY;EAAW;EACxB,CAAC;CAEF,IAAI,SAAS;CACb,IAAI,cAAc;CAClB,IAAI,iBAAgC;CACpC,MAAM,sBAAgC,EAAE;CACxC,MAAM,kBAA4B,EAAE;AAEpC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,KAAK,MAAM;AAE3B,MAAI,QAAQ,WAAW,KAAK,EAAE;AAC5B,YAAS;AACT;;AAEF,MAAI,CAAC,OAAQ;AAEb,MAAI,CAAC,aAAa;AAChB,OAAI,YAAY,MAAO,eAAc;AACrC;;AAGF,MAAI,QAAQ,WAAW,MAAM,EAAE;GAC7B,MAAM,cAAc,QAAQ,MAAM,EAAE,CAAC,MAAM;AAC3C,OAAI,yBAAyB,IAAI,YAAY,CAC3C,kBAAiB;YACR,sBAAsB,IAAI,YAAY,CAC/C,kBAAiB;QACZ;AACL,qBAAiB;AACjB,oBAAgB,KAAK,YAAY;;AAEnC;;AAGF,MAAI,QAAQ,WAAW,IAAI,CAAE;AAC7B,MAAI,YAAY,MAAO;AACvB,MAAI,QAAQ,WAAW,OAAO,IAAI,cAAc,KAAK,QAAQ,CAAE;AAE/D,MAAI,mBAAmB,aAAa,QAAQ,SAAS,GAAG;GACtD,MAAM,iBAAiB,QAAQ,QAAQ,kBAAkB,GAAG;GAC5D,MAAM,YAAY,eAAe,SAAS,IAAI,iBAAiB;AAC/D,OAAI,UAAU,WAAW,EAAG;AAC5B,uBAAoB,KAAK,UAAU;;;AAIvC,MAAK,MAAM,OAAO,qBAAqB;EACrC,MAAM,UAAU,kBAAkB,IAAI;AACtC,MAAI,CAAC,QAAS;AACd,MAAI,aAAa,QAAQ,CAAE;AAC3B,MAAI,QAAQ,WAAW,OAAO,IAAI,QAAQ,SAAS,eAAe,CAAE;AACpE,MAAI,QAAQ,SAAS,EAAG;AACxB,SAAO,aAAa,QAAQ;;AAG9B,MAAK,MAAM,WAAW,iBAAiB;EACrC,MAAM,UAAU,kBAAkB,QAAQ;AAC1C,MAAI,CAAC,QAAS;AACd,MAAI,aAAa,QAAQ,CAAE;AAC3B,MAAI,QAAQ,SAAS,KAAK,QAAQ,SAAS,GACzC,QAAO,aAAa,QAAQ;;AAIhC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,KAAK,MAAM;AAC3B,MAAI,QAAQ,WAAW,KAAK,EAAE;GAC5B,MAAM,QAAQ,QAAQ,MAAM,EAAE,CAAC,MAAM;AACrC,OAAI,kBAAkB,KAAK,MAAM,CAAE;GACnC,MAAM,UAAU,kBAAkB,MAAM;AACxC,OAAI,WAAW,CAAC,aAAa,QAAQ,IAAI,QAAQ,SAAS,EACxD,QAAO,aAAa,QAAQ;;;AAKlC,QAAO;;;AAIT,SAAgB,OAAO,GAAmB;AACxC,QAAO,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;;;;;AC9HnC,SAAgB,eAAe,IAA4B;AACzD,QAAO,GACJ,QACC,8HACD,CACA,KAAK;;AAGV,SAAgB,WACd,IACA,MACwB;AACxB,QAAO,GACJ,QACC,uGACD,CACA,IAAI,KAAK;;AAGd,SAAS,mBACP,IACA,WACc;AACd,QAAO,GACJ,QAAQ,kEAAkE,CAC1E,IAAI,UAAU;;AAOnB,SAAS,iBAAiB,UAAiC;CACzD,MAAM,YAAY,KAAK,UAAU,QAAQ;AACzC,KAAI,WAAW,UAAU,CAAE,QAAO;CAClC,MAAM,MAAM,KAAK,UAAU,WAAW,QAAQ;AAC9C,KAAI,WAAW,IAAI,CAAE,QAAO;AAC5B,QAAO;;AAGT,SAAS,mBACP,SACA,cACe;CACf,MAAM,YACJ,QAAQ,oBACR,KAAK,SAAS,EAAE,WAAW,YAAY,QAAQ,aAAa,QAAQ;AAEtE,KAAI,CAAC,WAAW,UAAU,CAAE,QAAO;AACnC,KAAI,gBAAgB,cAAc,aAAc,QAAO;AACvD,QAAO;;AAGT,SAAgB,iBAAiB,SAA+B;CAC9D,MAAM,UAAU,iBAAiB,QAAQ,UAAU;CACnD,MAAM,YAAY,mBAAmB,SAAS,QAAQ;CACtD,MAAM,OAAiB,EAAE;AACzB,KAAI,QAAS,MAAK,KAAK,QAAQ;AAC/B,KAAI,UAAW,MAAK,KAAK,UAAU;AACnC,QAAO;;AAOT,SAAS,eAAe,SAA0B;AAIhD,KAAI,CAHsB,oBAAoB,MAAM,QAClD,QAAQ,SAAS,IAAI,CACtB,CACuB,QAAO;CAE/B,MAAM,QAAQ,QAAQ,MAAM,KAAK;CACjC,IAAI,aAAa;AAEjB,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,KAAK,MAAM;AAC3B,MAAI,YAAY,gBAAgB;AAC9B,gBAAa;AACb;;AAEF,MAAI,QAAQ,WAAW,MAAM,IAAI,WAAY;AAC7C,MAAI,CAAC,WAAY;AACjB,MAAI,CAAC,QAAS;AACd,MAAI,QAAQ,WAAW,OAAO,IAAI,QAAQ,SAAS,MAAM,CAAE;AAC3D,MAAI,QAAQ,WAAW,OAAO,CAAE;AAChC,MAAI,YAAY,MAAO;AACvB,MAAI,YAAY,qBAAsB;AACtC,MAAI,YAAY,cAAc,YAAY,qBAAsB;AAChE,SAAO;;AAGT,QAAO;;AAOT,SAAgB,aACd,UACA,cACoB;CACpB,MAAM,aAAiC,EAAE;CAEzC,IAAI,YAAsB,EAAE;AAC5B,KAAI;AACF,cAAY,YAAY,UAAU,EAAE,eAAe,MAAM,CAAC,CACvD,QAAQ,MAAM,EAAE,QAAQ,IAAI,EAAE,KAAK,SAAS,MAAM,CAAC,CACnD,KAAK,MAAM,EAAE,KAAK;SACf;CAIR,MAAM,cAAwD,EAAE;AAChE,KAAI;EACF,MAAM,aAAa,YAAY,UAAU,EAAE,eAAe,MAAM,CAAC;AACjE,OAAK,MAAM,SAAS,YAAY;AAC9B,OAAI,CAAC,MAAM,aAAa,CAAE;AAC1B,OAAI,CAAC,UAAU,KAAK,MAAM,KAAK,CAAE;GACjC,MAAM,UAAU,KAAK,UAAU,MAAM,KAAK;GAC1C,MAAM,YAAY,YAAY,SAAS,EAAE,eAAe,MAAM,CAAC;AAC/D,QAAK,MAAM,UAAU,WAAW;AAC9B,QAAI,CAAC,OAAO,aAAa,CAAE;IAC3B,MAAM,WAAW,KAAK,SAAS,OAAO,KAAK;IAC3C,MAAM,QAAQ,YAAY,SAAS,CAAC,QAAQ,MAAM,EAAE,SAAS,MAAM,CAAC;AACpE,SAAK,MAAM,KAAK,MACd,aAAY,KAAK;KAAE,UAAU;KAAG,UAAU,KAAK,UAAU,EAAE;KAAE,CAAC;;;SAI9D;CAIR,MAAM,WAAqD,CACzD,GAAG,UAAU,KAAK,OAAO;EAAE,UAAU;EAAG,UAAU,KAAK,UAAU,EAAE;EAAE,EAAE,EACvE,GAAG,YACJ;AAED,MAAK,MAAM,EAAE,UAAU,cAAc,UAAU;EAC7C,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EAEJ,MAAM,cAAc,eAAe,KAAK,SAAS;EACjD,MAAM,cAAc,eAAe,KAAK,SAAS;AAEjD,MAAI,aAAa;AACf,SAAM,SAAS,YAAY,IAAI,GAAG;AAClC,UAAO,YAAY;AACnB,cAAW,YAAY;AACvB,oBAAiB;aACR,aAAa;AACtB,SAAM,SAAS,YAAY,IAAI,GAAG;AAClC,UAAO,YAAY;AACnB,cAAW,YAAY;AACvB,oBAAiB;QAEjB;EAGF,IAAI,YAAY;EAChB,IAAI,UAAU;AACd,MAAI;AACF,eAAY,SAAS,SAAS,CAAC;AAC/B,aAAU,aAAa,UAAU,OAAO;UAClC;AACN;;EAGF,MAAM,YACJ,aAAa,IAAI,SAAS,IAC1B,aAAa,IAAI,SAAS,MAAM,GAAG,SAAS,GAAG,CAAC,MAAM,GAAG,IACzD;AAEF,MAAI,mBAAmB,iBACrB;OAAI,YAAY,OAAO,eAAe,QAAQ,CAC5C,kBAAiB;YAEjB,aAAa,iBACb,cAAc,QAAQ,IAAI,QAAQ,OAClC,aAAa,kDAEb,kBAAiB;;EAIrB,MAAM,YAA8B;GAClC,SAAS;GACT;GACA;GACA;GACA;GACA;GACA,QAAQ;GACT;AAED,MACE,mBAAmB,aACnB,mBAAmB,gBAEnB,WAAU,WAAW,gBAAgB,QAAQ;AAG/C,aAAW,KAAK,UAAU;;AAG5B,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BT,SAAS,iBACP,YACqB;AACrB,wBAAO,IAAI,KAAK;;AAGlB,SAAgB,eACd,IACA,SACoB;CACpB,MAAM,gBAAgB,iBAAiB,QAAQ;AAC/C,KAAI,cAAc,WAAW,EAAG,QAAO;CAEvC,MAAM,aAAa,mBAAmB,IAAI,QAAQ,GAAG;CACrD,MAAM,+BAAe,IAAI,KAAyB;AAClD,MAAK,MAAM,KAAK,WAAY,cAAa,IAAI,EAAE,UAAU,EAAE;CAE3D,MAAM,gBAAgC,EAAE;CACxC,MAAM,eAAmC,EAAE;AAE3C,MAAK,MAAM,YAAY,eAAe;EACpC,MAAM,aAAa,aAAa,UAAU,aAAa;AACvD,MAAI,WAAW,WAAW,EAAG;EAE7B,MAAM,WAAW,WAAW,QAAQ,MAAM,EAAE,mBAAmB,QAAQ;EACvE,MAAM,WAAW,WAAW,QACzB,MACC,EAAE,mBAAmB,aAAa,EAAE,mBAAmB,gBAC1D;EACD,MAAM,YAAY,WAAW,QAAQ,MAAM,EAAE,mBAAmB,QAAQ;AAExE,gBAAc,KAAK;GAAE;GAAU;GAAU;GAAU,QAAQ;GAAW,CAAC;AACvE,eAAa,KAAK,GAAG,UAAU;;AAGjC,KAAI,cAAc,WAAW,EAAG,QAAO;AAIvC,QAAO;EAAE;EAAS,WAAW;EAAe,aAFxB,iBAAiB,aAAa;EAEO;;;;;AC5R3D,eAAe,mBAAmB,UAAqC;AACrE,KAAI,SAAS,WAAW,EAAG,QAAO;AAClC,KAAI;EACF,MAAM,EAAE,eAAe,MAAM,OAAO;EACpC,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,MAAM,SAAS,YAAY;AAC3B,MAAI,OAAO,mBAAmB,WAAY,QAAO;EACjD,MAAM,YAAY,IAAI,gBAAgB,OAAO,YAAY,EAAE,CAAC;AAE5D,MADgB,MAAM,UAAU,gBAAgB,EACnC;AAAE,SAAM,UAAU,OAAO;AAAE,UAAO;;EAC/C,MAAM,OAAQ,UAEX;EACH,MAAM,eAAe,SAAS,KAAK,GAAG,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK;EACnE,MAAM,SAAS,MAAM,KAAK,MACxB,4DAA4D,aAAa,IACzE,SACD;AACD,QAAM,UAAU,OAAO;AACvB,SAAO,SAAS,OAAO,KAAK,IAAI,KAAK,KAAK,GAAG;SACvC;AACN,SAAO;;;AAIX,eAAe,oBACb,OACiB;AACjB,KAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,KAAI;EACF,MAAM,EAAE,eAAe,MAAM,OAAO;EACpC,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,MAAM,SAAS,YAAY;AAC3B,MAAI,OAAO,mBAAmB,WAAY,QAAO;EACjD,MAAM,YAAY,IAAI,gBAAgB,OAAO,YAAY,EAAE,CAAC;EAC5D,MAAM,UAAU,MAAM,UAAU,gBAAgB;AAChD,MAAI,SAAS;AACX,WAAQ,OAAO,MAAM,2CAA2C,QAAQ,sCAAsC;AAC9G,SAAM,UAAU,OAAO;AACvB,UAAO;;EAQT,MAAM,SAAS,MAND,UAKX,KACuB,SAAS;EACnC,IAAI,eAAe;AACnB,MAAI;AACF,SAAM,OAAO,MAAM,SAAS,EAAE,CAAC;AAC/B,QAAK,MAAM,EAAE,SAAS,aAAa,OAAO;IACxC,MAAM,IAAI,MAAM,OAAO,MAAM,kDAAkD,CAAC,SAAS,QAAQ,CAAC;AAClG,oBAAgB,EAAE,YAAY;AAC9B,UAAM,OAAO,MAAM,mDAAmD,CAAC,SAAS,QAAQ,CAAC;;AAE3F,SAAM,OAAO,MAAM,UAAU,EAAE,CAAC;WACzB,GAAG;AACV,SAAM,OAAO,MAAM,YAAY,EAAE,CAAC;AAClC,SAAM;YACE;AACR,UAAO,SAAS;;AAElB,QAAM,UAAU,OAAO;AACvB,SAAO;UACA,GAAG;AACV,UAAQ,OAAO,MAAM,uDAAuD,EAAE,IAAI;AAClF,SAAO;;;AAQX,eAAsB,cAAc,OAAqC;CACvE,IAAI,cAAc;CAClB,IAAI,cAAc;CAClB,IAAI,YAAY;CAChB,IAAI,gBAAgB;AAEpB,MAAK,MAAM,QAAQ,OAAO;AAKxB,MAAI,EAHF,KAAK,UAAU,MACZ,MAAM,EAAE,SAAS,SAAS,KAAK,EAAE,SAAS,SAAS,KAAK,EAAE,OAAO,SAAS,EAC5E,IAAI,KAAK,YAAY,OAAO,GACjB;AAEd,UAAQ,KAAK;AACb,UAAQ,IACN,OAAO,cAAc,KAAK,QAAQ,aAAa,IAAI,KAAK,QAAQ,KAAK,GAAG,CACzE;AAED,OAAK,MAAM,WAAW,KAAK,WAAW;AACpC,WAAQ,IAAIC,MAAI,YAAY,QAAQ,WAAW,CAAC;AAEhD,OAAI,QAAQ,SAAS,SAAS,GAAG;AAC/B,YAAQ,IAAIC,OAAK,wDAAwD,CAAC;AAC1E,SAAK,MAAM,KAAK,QAAQ,UAAU;AAChC,aAAQ,IACN,OAAO,MAAM,OAAO,OAAO,CAAC,GAAGD,MAAI,OAAO,EAAE,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,KAC/D,EAAE,SAAS,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAC7C,GAAGA,MAAI,IAAI,EAAE,UAAU,IAAI,GAC7B;AACD;;AAEF,YAAQ,KAAK;;AAGf,OAAI,QAAQ,SAAS,SAAS,GAAG;AAC/B,YAAQ,IAAIC,OAAK,gDAAgD,CAAC;AAClE,SAAK,MAAM,KAAK,QAAQ,UAAU;KAChC,MAAM,WAAW,EAAE,YAAY;AAC/B,aAAQ,IAAI,OAAO,MAAM,OAAO,MAAM,CAAC,IAAI,EAAE,WAAW;AACxD,aAAQ,IAAI,cAAc,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,KAAK;AAC1E;;AAEF,YAAQ,KAAK;;AAGf,OAAI,QAAQ,OAAO,SAAS,GAAG;AAC7B,YAAQ,IAAIA,OAAK,gCAAgC,CAAC;AAClD,SAAK,MAAM,KAAK,QAAQ,QAAQ;KAC9B,MAAM,CAAC,MAAM,SAAS,EAAE,KAAK,MAAM,IAAI;AACvC,aAAQ,IAAI,OAAO,MAAM,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW;AACtD,aAAQ,IAAI,cAAc,KAAK,GAAG,MAAM,GAAG,EAAE,WAAW;AACxD;;AAEF,YAAQ,KAAK;;;AAIjB,MAAI,KAAK,YAAY,OAAO,GAAG;AAC7B,WAAQ,IACNA,OAAK,+DAA+D,CACrE;AACD,QAAK,MAAM,CAAC,MAAM,SAAS,KAAK,aAAa;AAC3C,YAAQ,IACN,OAAO,MAAM,KAAK,MAAM,CAAC,KAAK,OAAO,KAAK,CAAC,MAAM,OAAO,KAAK,GAC9D;AACD;;AAEF,WAAQ,KAAK;;;CAIjB,MAAM,iBAA2B,EAAE;AACnC,MAAK,MAAM,QAAQ,MACjB,MAAK,MAAM,WAAW,KAAK,UACzB,MAAK,MAAM,KAAK,QAAQ,QAAQ;EAC9B,MAAM,CAAC,MAAM,SAAS,EAAE,KAAK,MAAM,IAAI;EACvC,MAAM,aAAa,KAAK,QAAQ,UAAU,MAAM,OAAO,EAAE,SAAS;AAClE,MAAI,EAAE,aAAa,WAAY,gBAAe,KAAK,EAAE,SAAS;;CAKpE,MAAM,gBAAgB,MAAM,mBAAmB,eAAe;AAE9D,SAAQ,KAAK;AACb,SAAQ,IAAIA,OAAK,uBAAuB,CAAC;AACzC,SAAQ,IAAI,OAAO,MAAM,OAAO,OAAO,CAAC,GAAG,YAAY,iDAAiD;AACxG,SAAQ,IAAI,OAAO,MAAM,OAAO,MAAM,CAAC,IAAI,YAAY,6BAA6B;AACpF,SAAQ,IAAI,OAAO,MAAM,KAAK,MAAM,CAAC,IAAI,cAAc,uBAAuB;AAC9E,SAAQ,IAAI,OAAO,MAAM,KAAK,MAAM,CAAC,IAAI,UAAU,sCAAsC;AACzF,KAAI,gBAAgB,EAClB,SAAQ,IACN,OAAO,MAAM,QAAQ,MAAM,CAAC,IAAI,cAAc,uEAC/C;UACQ,eAAe,SAAS,EACjC,SAAQ,IACN,OAAO,MAAM,QAAQ,MAAM,CAAC,iFAC7B;AAEH,SAAQ,KAAK;AACb,SAAQ,IAAIC,OAAK,uDAAuD,CAAC;AACzE,SAAQ,KAAK;;AAOf,eAAsB,eACpB,IACA,OACA,aACe;CACf,IAAI,UAAU;CACd,IAAI,UAAU;CACd,IAAI,QAAQ;CACZ,IAAI,aAAa;CACjB,IAAI,YAAY;CAChB,MAAM,gBAA6D,EAAE;AAErE,MAAK,MAAM,QAAQ,OAAO;AACxB,UAAQ,KAAK;AACb,UAAQ,IACN,OAAO,cAAc,KAAK,QAAQ,aAAa,IAAI,KAAK,QAAQ,KAAK,GAAG,CACzE;AAED,OAAK,MAAM,WAAW,KAAK,WAAW;GACpC,MAAM,EAAE,aAAa;AACrB,OAAI,KAAK,UAAU,SAAS,EAAG,SAAQ,IAAIF,MAAI,gBAAgB,WAAW,CAAC;AAkB3E,QAAK,MAAM,KAAK,QAAQ,UAAU;AAChC,QAAI;KACF,MAAM,aAAa,KAAK,QAAQ,EAAE,SAAS,EAAE,WAAW;AACxD,eAAU,YAAY,EAAE,WAAW,MAAM,CAAC;KAC1C,IAAI,OAAO,KAAK,YAAY,EAAE,SAAS;AAEvC,SAAI,WAAW,KAAK,EAAE;MACpB,MAAM,QAAQ,SAAS,EAAE,SAAS,CAAC,QAAQ,SAAS,GAAG;AACvD,aAAO,KAAK,YAAY,GAAG,EAAE,SAAS,QAAQ,SAAS,GAAG,CAAC,IAAI,MAAM,MAAM;;AAE7E,gBAAW,EAAE,UAAU,KAAK;AAC5B,aAAQ,IAAIG,KAAG,UAAU,EAAE,WAAW,GAAGH,MAAI,eAAe,CAAC;AAC7D;aACO,GAAG;AACV,aAAQ,IAAI,IAAI,qBAAqB,EAAE,SAAS,IAAI,IAAI,CAAC;;AAE3D,QAAI,EAAE,QACJ,KAAI;AACF,QAAG,QAAQ,oCAAoC,CAAC,IAAI,EAAE,QAAQ,GAAG;AACjE;aACO,GAAG;AACV,aAAQ,IAAI,IAAI,6BAA6B,EAAE,OAAO,YAAY,IAAI,CAAC;;;AAM7E,QAAK,MAAM,KAAK,QAAQ,UAAU;IAChC,MAAM,WAAW,EAAE,YAAY;IAC/B,MAAM,cAAc,GAAG,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS;IAClE,MAAM,UAAU,KAAK,UAAU,YAAY;AAE3C,QAAI,EAAE,aAAa,QACjB,KAAI;AACF,gBAAW,EAAE,UAAU,QAAQ;AAC/B,aAAQ,IAAIG,KAAG,UAAU,EAAE,WAAW,CAAC;AACvC,aAAQ,IAAIH,MAAI,YAAY,cAAc,CAAC;AAC3C;AACA,KAAC,EAA2B,WAAW;AACvC,KAAC,EAA2B,WAAW;aAChC,GAAG;AACV,aAAQ,IAAI,IAAI,iBAAiB,EAAE,SAAS,IAAI,IAAI,CAAC;AACrD;;AAIJ,QAAI;KAEF,MAAM,QADU,aAAa,SAAS,OAAO,CACvB,MAAM,KAAK;KACjC,IAAI,YAAY;KAChB,MAAM,UAAU,MAAM,KAAK,SAAS;AAClC,UAAI,CAAC,aAAa,KAAK,WAAW,KAAK,EAAE;AACvC,mBAAY;AACZ,cAAO,KAAK;;AAEd,aAAO;OACP;AACF,SAAI,CAAC,UAAW,SAAQ,QAAQ,KAAK,YAAY,GAAG;AACpD,mBAAc,SAAS,QAAQ,KAAK,KAAK,EAAE,OAAO;YAC5C;AAIR,QAAI,EAAE,SAAS;KACb,MAAM,iBAAiB,SACpB,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG;AAC1B,SAAI;AACF,SAAG,QACD,qEACD,CAAC,IAAI,gBAAgB,UAAU,aAAa,EAAE,QAAQ,GAAG;AAC1D;cACO,GAAG;AACV,cAAQ,IAAI,IAAI,iCAAiC,EAAE,OAAO,IAAI,IAAI,CAAC;;;;AAMzE,OAAI,KAAK,YAAY,OAAO,GAAG;IAC7B,MAAM,aAAa,QAAQ,OAAO,QAAQ,MACxC,KAAK,YAAY,IAAI,EAAE,OAAO,CAC/B;IAED,MAAM,YAIA,EAAE;AACR,SAAK,MAAM,KAAK,YAAY;KAC1B,MAAM,SAAS,KAAK,YAAY,IAAI,EAAE,OAAO;KAE7C,MAAM,WAAW,KAAK,UADD,SAAS,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,WACP;AAC7C,SAAI;AACF,UAAI,WAAW,EAAE,SAAS,EAAE;AAC1B,kBAAW,EAAE,UAAU,SAAS;AAChC,iBAAU,KAAK;QAAE,WAAW;QAAG;QAAU;QAAQ,CAAC;;cAE7C,GAAG;AACV,cAAQ,IAAI,IAAI,uBAAuB,EAAE,OAAO,IAAI,IAAI,CAAC;;;AAI7D,SAAK,MAAM,EAAE,WAAW,GAAG,UAAU,YAAY,WAAW;KAC1D,MAAM,cAAc,EAAE,SAAS,QAAQ,UAAU,OAAO,OAAO,CAAC;KAChE,MAAM,UAAU,KAAK,UAAU,YAAY;AAC3C,SAAI;AACF,iBAAW,UAAU,QAAQ;AAC7B,cAAQ,IACNG,KAAG,WAAW,OAAO,EAAE,OAAO,CAAC,MAAM,OAAO,OAAO,CAAC,IAAI,cAAc,CACvE;AACD;AACA,MAAC,EAA2B,WAAW;AACvC,MAAC,EAA2B,WAAW;AACvC,MAAC,EAAyB,SAAS;cAC5B,GAAG;AACV,cAAQ,IAAI,IAAI,wBAAwB,OAAO,IAAI,IAAI,CAAC;;AAG1D,SAAI,WAAW,QAAQ,CACrB,KAAI;AAYF,oBAAc,SAXE,aAAa,SAAS,OAAO,CACvB,MAAM,KAAK,CACX,KAAK,SAAS;AAClC,WAAI,KAAK,MAAM,oBAAoB,CACjC,QAAO,KAAK,QACV,qBACA,aAAa,OAAO,OAAO,CAAC,GAC7B;AAEH,cAAO;QACP,CAC6B,KAAK,KAAK,EAAE,OAAO;aAC5C;;IAMZ,MAAM,cAAc,UACjB,QAAQ,EAAE,WAAW,QAAQ,EAAE,WAAW,KAAK,CAC/C,KAAK,EAAE,WAAW,GAAG,cAAc;KAClC,SAAS,EAAE;KACX;KACA,aAAa,EAAE;KAChB,EAAE;AAEL,QAAI,YAAY,SAAS,GAAG;KAC1B,MAAM,aAAa,GAAG,kBAAkB;AACtC,WAAK,MAAM,EAAE,SAAS,YAAY,YAChC,IAAG,QAAQ,8CAA8C,CAAC,IACxD,CAAC,QACD,QAAQ,GACT;AAEH,WAAK,MAAM,EAAE,SAAS,QAAQ,iBAAiB,YAC7C,IAAG,QACD,4DACD,CAAC,IAAI,QAAQ,aAAa,QAAQ,GAAG;OAExC;AACF,SAAI;AACF,kBAAY;AACZ,mBAAa,YAAY;cAClB,GAAG;AACV,cAAQ,IAAI,IAAI,mCAAmC,IAAI,CAAC;;;;AAM9D,QAAK,MAAM,KAAK,QAAQ,QAAQ;IAC9B,MAAM,CAAC,MAAM,SAAS,EAAE,KAAK,MAAM,IAAI;IACvC,MAAM,YAAY,KAAK,UAAU,MAAM,MAAM;IAC7C,MAAM,aAAa,KAAK,WAAW,EAAE,SAAS;AAC9C,QAAI,EAAE,aAAa,WAAY;AAE/B,QAAI;AACF,eAAU,WAAW,EAAE,WAAW,MAAM,CAAC;aAClC,GAAG;AACV,aAAQ,IAAI,IAAI,gBAAgB,UAAU,IAAI,IAAI,CAAC;AACnD;;IAGF,MAAM,aAAa,EAAE;AACrB,QAAI;AACF,SAAI,WAAW,EAAE,SAAS,EAAE;AAC1B,iBAAW,EAAE,UAAU,WAAW;AAClC,cAAQ,IAAIA,KAAG,UAAU,EAAE,WAAW,CAAC;AACvC,cAAQ,IAAIH,MAAI,YAAY,KAAK,GAAG,MAAM,GAAG,EAAE,WAAW,CAAC;AAC3D;AACA,oBAAc,KAAK;OAAE,SAAS;OAAY,SAAS;OAAY,CAAC;;aAE3D,GAAG;AACV,aAAQ,IAAI,IAAI,eAAe,EAAE,SAAS,IAAI,IAAI,CAAC;AACnD;;IAGF,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,EAAE;AAC9C,QAAI,EAAE,QACJ,KAAI;AACF,QAAG,QAAQ,gDAAgD,CAAC,IAC1D,iBACA,EAAE,QAAQ,GACX;AACD;aACO,GAAG;AACV,aAAQ,IAAI,IAAI,6BAA6B,EAAE,SAAS,IAAI,IAAI,CAAC;;;;;CAQ3E,IAAI,kBAAkB;AACtB,KAAI,cAAc,SAAS,GAAG;AAC5B,UAAQ,KAAK;AACb,UAAQ,IACNA,MACE,cAAc,cAAc,OAAO,sDACpC,CACF;EACD,MAAM,SAAS,MAAM,oBAAoB,cAAc;AACvD,MAAI,UAAU,GAAG;AACf,qBAAkB;AAClB,WAAQ,IACNG,KAAG,aAAa,gBAAgB,kDAAkD,CACnF;QAED,SAAQ,IACND,OACE,2EACD,CACF;;AAIL,SAAQ,KAAK;AACb,SAAQ,IAAID,OAAK,sBAAsB,CAAC;AACxC,SAAQ,IAAIE,KAAG,OAAO,QAAQ,qBAAqB,CAAC;AACpD,SAAQ,IAAIA,KAAG,OAAO,QAAQ,qBAAqB,CAAC;AACpD,SAAQ,IAAIA,KAAG,OAAO,WAAW,wBAAwB,CAAC;AAC1D,SAAQ,IAAIA,KAAG,OAAO,MAAM,yCAAyC,CAAC;AACtE,SAAQ,IAAIA,KAAG,OAAO,UAAU,gCAAgC,CAAC;AACjE,KAAI,cAAc,SAAS,EACzB,SAAQ,IACNA,KACE,OAAO,gBAAgB,wDACxB,CACF;AAGH,KAAI,CAAC,aAAa;AAChB,UAAQ,KAAK;AACb,UAAQ,IACNH,MAAI,2EAA2E,CAChF;AACD,UAAQ,IAAIA,MAAI,wDAAwD,CAAC;;AAG3E,SAAQ,KAAK;;;;;ACjff,SAAgB,8BACd,YACA,OACM;AACN,YACG,QAAQ,yBAAyB,CACjC,YACC,oGACD,CACA,OAAO,aAAa,oDAAoD,CACxE,OAAO,iBAAiB,4CAA4C,CACpE,OAAO,gBAAgB,8CAA8C,CACrE,OACC,OACE,aACA,SACG;EACH,MAAM,KAAK,OAAO;EAClB,MAAM,SAAS,CAAC,KAAK;EACrB,MAAM,cAAc,KAAK,YAAY;EAErC,IAAI;AACJ,MAAI,aAAa;GACf,MAAM,IAAI,WAAW,IAAI,YAAY;AACrC,OAAI,CAAC,GAAG;AACN,YAAQ,MAAM,IAAI,sBAAsB,cAAc,CAAC;AACvD,YAAQ,WAAW;AACnB;;AAEF,cAAW,CAAC,EAAE;QAEd,YAAW,eAAe,GAAG;AAG/B,UAAQ,KAAK;AACb,UAAQ,IACN,OACE,SACI,8DACA,oCACL,CACF;AACD,UAAQ,IAAII,MAAI,eAAe,SAAS,OAAO,gBAAgB,CAAC;EAEhE,MAAM,QAAQ,EAAE;AAChB,OAAK,MAAM,WAAW,UAAU;GAC9B,MAAM,OAAO,eAAe,IAAI,QAAQ;AACxC,OAAI,KAAM,OAAM,KAAK,KAAK;;EAG5B,MAAM,cAAc,MAAM,QACvB,MACC,EAAE,UAAU,MACT,MACC,EAAE,SAAS,SAAS,KACpB,EAAE,SAAS,SAAS,KACpB,EAAE,OAAO,SAAS,EACrB,IAAI,EAAE,YAAY,OAAO,EAC7B;AAED,MAAI,YAAY,WAAW,GAAG;AAC5B,WAAQ,KAAK;AACb,WAAQ,IAAIC,KAAG,iDAAiD,CAAC;AACjE,WAAQ,KAAK;AACb;;AAGF,MAAI,OACF,OAAM,cAAc,YAAY;MAEhC,OAAM,eAAe,IAAI,aAAa,YAAY;GAGvD;;;;;ACtDL,SAAS,aAAa,UAAwD;CAC5E,MAAM,UAAU,aAAa,UAAU,QAAQ;AAE/C,KAAI,QAAQ,SAAS,wBAAwB,CAC3C,QAAO;EAAE,WAAW;EAAO,MAAM;EAAU;CAG7C,IAAI,UAAU,QAAQ,QAAQ,2BAA2B,wBAAwB;AAEjF,KAAI,CAAC,QAAQ,SAAS,iBAAiB,EAAE;EACvC,MAAM,kCAAiB,IAAI,MAAM,EAAC,aAAa;AAC/C,YAAU,QAAQ,QAChB,uBACA,kBAAkB,eAAe,yBAClC;;CAIH,MAAM,MAAM,GAAG,SAAS;AACxB,eAAc,KAAK,SAAS,QAAQ;AACpC,YAAW,KAAK,SAAS;AAEzB,QAAO;EAAE,WAAW;EAAM,MAAM;EAAU;;AAO5C,SAAgB,OAAO,IAAc,MAA0B;AAI7D,UAAS,IAAI,KAAK;CAGlB,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,UAAU,GACb,QACC;;;;iBAKD,CACA,IAAI,IAAI;AAEX,KAAI,CAAC,QAEH;CAIF,MAAM,WAAWC,eAAa,QAAQ,WAAW,QAAQ,eAAe,GAAG;AAE3E,KAAI,CAAC,UAAU;AACb,UAAQ,IACN,MAAM,IAAI,kEAAkE,CAC7E;AACD,cAAY,KAAK,OAAO;AACxB;;CAIF,MAAM,WAAW,eAAe,SAAS;AAEzC,KAAI,CAAC,UAAU;AACb,UAAQ,IAAI,MAAM,IAAI,gDAAgD,CAAC;AACvE,cAAY,KAAK,OAAO;AACxB;;AAIF,KAAI,KAAK,QAAQ;AACf,UAAQ,IACN,OACE,MAAM,KAAK,yCAAyC,GACpD,MACA,MAAM,KAAK,SAAS,CACvB;AACD,UAAQ,IACN,MAAM,IACJ,oFAED,CACF;AACD,UAAQ,IACN,MAAM,IAAI,0CAA0C,CACrD;QACI;EACL,MAAM,EAAE,WAAW,SAAS,aAAa,SAAS;AAClD,MAAI,UACF,SAAQ,IACN,MAAM,MAAM,6BAA6B,GAAG,MAAM,KAAK,SAAS,KAAK,CAAC,CACvE;MAED,SAAQ,IACN,MAAM,IACJ,4CAA4C,SAAS,SAAS,GAC/D,CACF;;AAKL,aAAY,KAAK,OAAO;;AAG1B,SAAS,YAAY,QAAwB;CAC3C,MAAM,QAAQ,SAAS,eAAe;CACtC,MAAM,MAAM;EACV;EACA,MAAM,MAAM,MAAM,KAChB,mBAAmB,MAAM,yCAC1B;EACD;EACA,MAAM,OACJ,8CACE,MAAM,MAAM,KAAK,QAAQ,GACzB,MAAM,OAAO,uBAAuB,CACvC;EACD;EACA,MAAM,IAAI,KAAK,yBAAyB;EACxC;EACA,MAAM,IAAI,kDAAkD;EAC5D,MAAM,IAAI,4DAA4D;EACtE,MAAM,IAAI,iEAAiE;EAC3E,MAAM,IAAI,qDAAqD;EAC/D;EACA,MAAM,IACJ,sEACD;EACD,MAAM,IAAI,iEAAiE;EAC3E;EACD,CAAC,KAAK,KAAK;AAEZ,SAAQ,IAAI,IAAI;;;;;;;;;;;;;;;;;;AC3IlB,SAAS,MAAM,IAA2B;AACxC,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;AAG1D,SAAS,aAAa,GAAgC;CACpD,MAAM,OAAO,EAAE,WAAW,EAAE;AAC5B,QAAO,GAAG,MAAM,KAAK,EAAE,UAAU,MAAM,GAAG,EAAE,CAAC,CAAC,GAAG,MAAM,KAAK,KAAK;;;AAInE,MAAM,YAAY;;AAElB,MAAM,UAAU;;;;;;;;;;;;;;;AAgBhB,eAAe,oBACb,YACA,WACyD;CACzD,MAAM,UAAU,IAAI,IAAI,WAAW;CACnC,MAAM,uBAAO,IAAI,KAAa;AAE9B,OAAM,MAAM,KAAK,IAAI,WAAW,UAAU,CAAC;CAE3C,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,QAAO,QAAQ,OAAO,KAAK,KAAK,KAAK,GAAG,UAAU;EAChD,IAAI;AACJ,MAAI;AACF,aAAU,MAAM,mBAAmB;UAC7B;AAGN,SAAM,MAAM,QAAQ;AACpB;;AAGF,OAAK,MAAM,KAAK,QACd,KAAI,QAAQ,IAAI,EAAE,UAAU,IAAI,EAAE,UAAU;AAC1C,WAAQ,OAAO,EAAE,UAAU;AAC3B,QAAK,IAAI,EAAE,UAAU;;EAKzB,MAAM,UAAU,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,UAAU,CAAC;AACxD,OAAK,MAAM,MAAM,CAAC,GAAG,QAAQ,CAC3B,KAAI,CAAC,QAAQ,IAAI,GAAG,CAAE,SAAQ,OAAO,GAAG;AAG1C,MAAI,QAAQ,OAAO,EAAG,OAAM,MAAM,QAAQ;;AAG5C,QAAO;EAAE;EAAM,YAAY;EAAS;;;AAQtC,SAAgB,YAAY,GAAwB,MAAuB;CACzE,MAAM,SAAS,KAAK,MAAM,CAAC,aAAa;AACxC,KAAI,CAAC,OAAQ,QAAO;AAEpB,SADc,EAAE,WAAW,EAAE,QAAQ,IAAI,aAAa,CAC1C,SAAS,OAAO,IAAI,EAAE,UAAU,aAAa,CAAC,SAAS,OAAO;;AAG5E,eAAsB,YAAY,MAKhB;CAIhB,MAAM,SAAS,KAAK,QAAQ;CAG5B,IAAI;AACJ,KAAI;AACF,iBAAe,MAAM,mBAAmB;UACjC,GAAG;AACV,UAAQ,MAAM,IAAI,sDAAsD,CAAC;AACzE,UAAQ,MAAMC,MAAI,kEAAkE,CAAC;AACrF,UAAQ,WAAW;AACnB;;CAIF,MAAM,YAAY,aAAa,QAAQ,MAAM,EAAE,SAAS,SAAS;CACjE,MAAM,iBAAiB,KAAK,OACxB,UAAU,QAAQ,MAAM,YAAY,GAAG,KAAK,KAAM,CAAC,GACnD;CACJ,MAAM,UAAU,aAAa,SAAS,UAAU;AAEhD,KAAI,KAAK,MAAM;AAGb,UAAQ,IACNA,MACE,YAAY,KAAK,KAAK,IAAI,eAAe,OAAO,MAAM,UAAU,OAAO,6BAExE,CACF;AACD,MAAI,eAAe,WAAW,GAAG;AAC/B,WAAQ,IAAIC,OAAK,mCAAmC,KAAK,KAAK,sBAAsB,CAAC;AACrF;;;AAIJ,KAAI,UAAU,EACZ,SAAQ,OAAO,MACb,YAAY,QAAQ,iBAAiB,YAAY,IAAI,KAAK,IAAI,mBAC/D;AAGH,KAAI,eAAe,WAAW,GAAG;AAC/B,UAAQ,IAAIA,OAAK,gEAAgE,CAAC;AAClF;;AAIF,KAAI,KAAK,QAAQ;AACf,UAAQ,IAAI,OAAO,OAAO,uCAAuC,GAAG,KAAK;AACzE,OAAK,MAAM,KAAK,gBAAgB;AAC9B,WAAQ,IAAI,OAAO,aAAa,EAAE,CAAC;AACnC,WAAQ,IAAID,MAAI,gCAA8B,CAAC;AAC/C,OAAI,KAAK,KACP,SAAQ,IAAIA,MAAI,cAAc,OAAO,uBAAuB,CAAC;;AAGjE,UAAQ,KAAK;AACb;;AAIF,SAAQ,IACN,OACE,OAAO,4BAA4B,GACnC,OACAA,MAAI,KAAK,eAAe,OAAO,iCAAiC,GAChE,KACH;CAED,MAAM,UAMD,EAAE;AAEP,MAAK,MAAM,KAAK,gBAAgB;AAC9B,UAAQ,OAAO,MAAM,OAAO,aAAa,EAAE,GAAG,MAAM;EAIpD,MAAM,cAAc,MAAM,cAAc,EAAE,WAAW,gBAAgB;AAYrE,MAAI,YAAY,UAAU;AACxB,WAAQ,IAAIC,OAAK,kDAAkD,CAAC;AACpE,WAAQ,KAAK;IAAE,SAAS;IAAG,SAAS;IAAM,aAAa;IAAM,CAAC;AAC9D;;AAGF,MAAI,CAAC,YAAY,IAAI;AACnB,WAAQ,IAAI,IAAI,cAAc,YAAY,SAAS,iBAAiB,CAAC;AACrE,WAAQ,KAAK;IAAE,SAAS;IAAG,SAAS;IAAO,OAAO,YAAY;IAAO,CAAC;AACtE;;AAEF,UAAQ,IAAIC,KAAG,SAAS,CAAC;AACzB,UAAQ,KAAK;GAAE,SAAS;GAAG,SAAS;GAAM,CAAC;;AAI7C,KAAI,KAAK,MAAM;EACb,MAAM,iBAAiB,QAAQ,QAAQ,MAAM,EAAE,QAAQ,CAAC,KAAK,MAAM,EAAE,QAAQ;AAC7E,MAAI,eAAe,SAAS,GAAG;AAC7B,WAAQ,IACN,OACEF,MACE,qEACY,KAAK,MAAM,SAAS,IAAK,CAAC,KACvC,CACJ;GAED,MAAM,EAAE,eAAe,MAAM,oBAC3B,eAAe,KAAK,MAAM,EAAE,UAAU,EACtC,OACD;AAED,WAAQ,KAAK;AACb,QAAK,MAAM,KAAK,gBAAgB;AAG9B,QAAI,WAAW,IAAI,EAAE,UAAU,EAAE;AAC/B,aAAQ,IACN,OACE,aAAa,EAAE,GACf,MACAC,OAAK,qCAAqC,CAC7C;AACD;;AAGF,YAAQ,OAAO,MAAM,OAAO,aAAa,EAAE,GAAG,cAAc;IAC5D,MAAM,aAAa,MAAM,cAAc,EAAE,WAAW,QAAQ;IAC5D,MAAM,MAAM,QAAQ,MAAM,MAAM,EAAE,QAAQ,cAAc,EAAE,UAAU;AACpE,QAAI,SAAS,WAAW;AACxB,QAAI,CAAC,WAAW,GACd,SAAQ,IAAIA,OAAK,mBAAmB,WAAW,SAAS,WAAW,CAAC;QAEpE,SAAQ,IAAIC,KAAG,SAAS,CAAC;;AAI7B,OAAI,WAAW,OAAO,EACpB,SAAQ,IACN,OACEF,MACE,KAAK,WAAW,KAAK,mGAEtB,CACJ;;;CAMP,MAAM,QAAQ,QAAQ;CACtB,MAAM,YAAY,QAAQ,QAAQ,MAAM,EAAE,QAAQ,CAAC;CACnD,MAAM,cAAc,QAAQ,QAAQ,MAAM,EAAE,YAAY,CAAC;CACzD,MAAM,YAAY,YAAY;CAC9B,MAAM,SAAS,QAAQ;AAEvB,SAAQ,KAAK;AACb,KAAI,WAAW,KAAK,gBAAgB,EAClC,SAAQ,IAAIE,KAAG,OAAO,MAAM,kCAAkC,CAAC;MAC1D;AACL,UAAQ,IACNA,KAAG,GAAG,UAAU,YAAY,IACzB,cAAc,IAAID,OAAK,KAAK,YAAY,gBAAgB,GAAG,OAC3D,SAAS,IAAI,IAAI,KAAK,OAAO,gBAAgB,GAAG,MACjDD,MAAI,SAAS,MAAM,cAAc,CACpC;AACD,OAAK,MAAM,KAAK,QAAQ,QAAQ,MAAM,CAAC,EAAE,QAAQ,EAAE;GACjD,MAAM,QAAQ,EAAE,QAAQ,WAAW,EAAE,QAAQ,QAAQ,EAAE,QAAQ,UAAU,MAAM,GAAG,EAAE;AACpF,WAAQ,IAAI,IAAI,KAAK,MAAM,IAAI,EAAE,SAAS,kBAAkB,CAAC;;AAE/D,MAAI,cAAc,EAGhB,SAAQ,IACNA,MACE,oTAKD,CACF;;AAIL,KAAI,KAAK,MAAM;EACb,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,OAAO,CAAC;AAC/C,UAAQ,IAAIA,MAAI,mBAAmB,OAAO,cAAc,CAAC;;AAG3D,SAAQ,KAAK;;;;;;;;;;;;;;;;;AC/Sf,eAAsB,cAAc,MAA2C;AAC7E,KAAI,KAAK,QAAQ;AACf,UAAQ,IAAIG,MAAI,mBAAmB,CAAC;AACpC,UAAQ,IAAIA,MAAI,oEAAoE,CAAC;AACrF,UAAQ,IAAIA,MAAI,sFAAsF,CAAC;AACvG;;CAGF,IAAI,YAAY;AAGhB,SAAQ,OAAO,MAAM,iCAAiC;AACtD,KAAI;EAIF,MAAM,KAHK,MAAM,aAAa,uBAAuB,EAAE,EAAE,IAAO,EAGnD,WAAW;AACxB,UAAQ,IAAIC,KAAG,WAAW,EAAE,OAAO,MAAM,IAAI,MAAM,QAAQ,CAAC;UACrD,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,MAAI,IAAI,SAAS,SAAS,IAAI,IAAI,SAAS,eAAe,EAAE;AAC1D,WAAQ,IAAIC,OAAK,wDAAwD,CAAC;AAC1E,eAAY;SACP;AACL,WAAQ,IAAI,IAAI,IAAI,CAAC;AACrB,eAAY;;;AAKhB,SAAQ,OAAO,MAAM,gDAAgD;AACrE,KAAI;EAIF,MAAM,KAHK,MAAM,aAAa,mBAAmB,EAAE,EAAE,IAAO,EAG/C,WAAW;AACxB,UAAQ,IAAID,KAAG,WAAW,EAAE,UAAU,MAAM,IAAI,KAAK,MAAM,CAAC;UACrD,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,MAAI,IAAI,SAAS,SAAS,IAAI,IAAI,SAAS,eAAe,EAAE;AAC1D,WAAQ,IAAIC,OAAK,8DAA8D,CAAC;AAChF,eAAY;SACP;AACL,WAAQ,IAAI,IAAI,IAAI,CAAC;AACrB,eAAY;;;AAIhB,KAAI,WAAW;AACb,UAAQ,IAAIA,OAAK,mEAAmE,CAAC;AACrF,UAAQ,WAAW;OAEnB,SAAQ,IAAID,KAAG,0CAA0C,CAAC;;;;;;;;;;;;;;;;;;;;ACjB9D,MAAM,eAAe,KAAK,SAAS,EAAE,WAAW,gBAAgB;AAMhE,SAAS,eAAe,GAAW,SAAS,IAAY;AACtD,KAAI,EAAE,UAAU,OAAQ,QAAO;AAE/B,QAAO,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG;;AAOrC,SAAS,MAAM,IAAoB;CACjC,MAAM,IAAI,IAAI,KAAK,GAAG;CACtB,MAAM,OAAO,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AACrD,QACE,GAAG,EAAE,aAAa,CAAC,GAAG,IAAI,EAAE,UAAU,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,SAAS,CAAC,CAAC,GAC7D,IAAI,EAAE,UAAU,CAAC,CAAC,GAAG,IAAI,EAAE,YAAY,CAAC;;AAQ/C,eAAsB,cACpB,OACA,YACyB;AACzB,KAAI,CAAC,WAAW,aAAa,CAAE,QAAO,EAAE;CAExC,MAAM,aAAa,MAAM,aAAa;CAItC,MAAM,4BAAY,IAAI,KAA2B;CACjD,MAAM,YAA0B;EAC9B,WAAW;EACX,aAAa;EACb,kBAAkB;EAClB,SAAS;EACT,YAAY;EACb;CAED,MAAM,KAAK,gBAAgB;EACzB,OAAO,iBAAiB,cAAc,EAAE,UAAU,QAAQ,CAAC;EAC3D,WAAW;EACZ,CAAC;AAEF,YAAW,MAAM,QAAQ,IAAI;EAC3B,MAAM,IAAI,KAAK,MAAM;AACrB,MAAI,CAAC,EAAG;EAER,IAAI;AACJ,MAAI;AACF,WAAQ,KAAK,MAAM,EAAE;UACf;AACN;;EAGF,MAAM,UAAU,MAAM,WAAW;AACjC,MAAI,CAAC,QAAQ,aAAa,CAAC,SAAS,WAAW,CAAE;EAEjD,MAAM,KAAK,MAAM,aAAa;EAC9B,MAAM,UAAU,MAAM,WAAW;EACjC,MAAM,YAAY,MAAM,aAAa;AAErC,MAAI,CAAC,WAAW;AAEd,OAAI,KAAK,UAAU,aAAa;AAC9B,cAAU,cAAc;AACxB,cAAU,mBAAmB;AAC7B,cAAU,UAAU;;AAEtB,aAAU;AACV;;EAGF,MAAM,WAAW,UAAU,IAAI,UAAU;AACzC,MAAI,CAAC,SACH,WAAU,IAAI,WAAW;GACvB;GACA,aAAa;GACb,kBAAkB;GAClB;GACA,YAAY;GACb,CAAC;OACG;AACL,YAAS;AACT,OAAI,KAAK,SAAS,aAAa;AAC7B,aAAS,cAAc;AACvB,aAAS,mBAAmB;AAC5B,aAAS,UAAU;;;;AAUzB,QAJe,CAAC,GAAG,UAAU,QAAQ,CAAC,CAAC,MACpC,GAAG,MAAM,EAAE,cAAc,EAAE,YAC7B,CAEa,MAAM,GAAG,WAAW;;AAOpC,eAAsB,QACpB,OACA,MACe;CACf,MAAM,aAAa,SAAS,KAAK,KAAK,MAAM,GAAG;AAE/C,KAAI,CAAC,WAAW,aAAa,EAAE;AAC7B,UAAQ,MAAM,IAAI,qCAAqC,CAAC;AACxD,UAAQ,WAAW;AACnB;;CAGF,MAAM,UAAU,MAAM,cAAc,OAAO,WAAW;AAEtD,KAAI,KAAK,MAAM;AACb,UAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC;AAC7C;;AAGF,KAAI,QAAQ,WAAW,GAAG;AACxB,UAAQ,IAAIE,OAAK,+BAA+B,MAAM,+BAA+B,CAAC;AACtF,UAAQ,IAAIC,MAAI,4CAA4C,CAAC;AAC7D;;AAGF,SAAQ,IAAI,OAAO,OAAO,yBAAyB,GAAG,KAAK;AAC3D,SAAQ,IAAIA,MAAI,aAAa,MAAM,MAAM,QAAQ,OAAO,sCAAsC,CAAC;CAE/F,MAAM,UAAU;EAAC;EAAK;EAAM;EAAQ;EAAW;EAAuB;CACtE,MAAM,OAAO,QAAQ,KAAK,GAAG,QAAQ;EACnC,MAAM,WAAW,EAAE,aAAa,cAAc,MAAM,GAAG,EAAE;EACzD,MAAM,OAAO,EAAE,cAAc,IAAI,MAAM,EAAE,YAAY,GAAGA,MAAI,IAAI;EAChE,MAAM,UAAU,eAAe,EAAE,WAAWA,MAAI,IAAI,CAAC;EAErD,MAAM,UAAU,EAAE,iBACf,QAAQ,QAAQ,IAAI,CACpB,MAAM,CACN,MAAM,GAAG,GAAG;EACf,MAAM,UAAU,QAAQ,SAAS,EAAE,iBAAiB,QAAQ,QAAQ,IAAI,CAAC,MAAM,CAAC,SAC5E,IAAI,QAAQ,MACZ,IAAI,QAAQ;AAEhB,SAAO;GACLA,MAAI,OAAO,MAAM,EAAE,CAAC;GACpB,MAAM,KAAK,QAAQ;GACnB;GACAA,MAAI,QAAQ;GACZ,MAAM,IAAI,QAAQ;GACnB;GACD;AAEF,SAAQ,IAAI,YAAY,SAAS,KAAK,CAAC;AACvC,SAAQ,KAAK;AACb,SAAQ,IAAIA,MAAI,aAAa,GAAG,MAAM,MAAM,kBAAkB,CAAC;AAC/D,SAAQ,KAAK;;;;;ACrJf,SAAS,YAAY,IAAc,iBAA+C;AAChF,KAAI;EACF,MAAM,OAAO,GACV,QAAQ;;;;;;;;QAQP,CACD,KAAK;AACR,SAAO,kBAAkB,OAAO,KAAK,QAAQ,MAAM,EAAE,WAAW,SAAS;SACnE;AACN,SAAO,EAAE;;;AAQb,MAAM,UAAU,IAAI,IAAI;CACtB;CAAgB;CAAQ;CAAa;CAAQ;CAAS;CAAU;CAChE;CAAS;CAAU;CAAe;CAAS;CAC5C,CAAC;AACF,MAAM,UAAU,IAAI,IAAI;CACtB;CAAO;CAAO;CAAO;CAAQ;CAAS;CAAQ;CAAU;CAAS;CACjE;CAAY;CAAO;CAAS;CAAM;CACnC,CAAC;;AAGF,SAAS,aAAa,MAAc,YAAY,IAAY;CAC1D,MAAM,yBAAS,IAAI,KAAa;CAChC,MAAM,QAAQ,KAAa,UAAkB;AAC3C,MAAI,QAAQ,KAAK,OAAO,QAAQ,YAAY,EAAG;EAC/C,IAAI;AACJ,MAAI;AACF,aAAU,YAAY,IAAI;UACpB;AACN;;AAEF,OAAK,MAAM,QAAQ,SAAS;AAC1B,OAAI,KAAK,WAAW,IAAI,IAAI,QAAQ,IAAI,KAAK,CAAE;GAC/C,MAAM,OAAO,KAAK,KAAK,KAAK;GAC5B,IAAI,QAAQ;AACZ,OAAI;AACF,YAAQ,SAAS,KAAK,CAAC,aAAa;WAC9B;AACN;;AAEF,OAAI,OAAO;AACT,SAAK,MAAM,OAAO,SAAS,KAAK,CAAE,QAAO,IAAI,IAAI;AACjD,SAAK,MAAM,QAAQ,EAAE;cACZ,KAAK,aAAa,CAAC,SAAS,MAAM,CAC3C,MAAK,MAAM,OAAO,SAAS,KAAK,CAAE,QAAO,IAAI,IAAI;;;AAIvD,MAAK,MAAM,EAAE;AACb,QAAO,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,UAAU,CAAC,KAAK,IAAI;;AAGzD,SAAS,SAAS,GAAqB;AACrC,QAAO,EACJ,QAAQ,UAAU,GAAG,CACrB,QAAQ,WAAW,IAAI,CACvB,MAAM,gBAAgB,CACtB,KAAK,MAAM,EAAE,aAAa,CAAC,CAC3B,QAAQ,MAAM,EAAE,UAAU,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC;;AAepD,SAAS,OAAO,QAA0C;AACxD,SAAQ,QAAR;EACE,KAAK,OACH,QAAO,MAAM,MAAM,UAAU;EAC/B,KAAK,YACH,QAAO,MAAM,KAAK,UAAU;EAC9B,KAAK,UACH,QAAO,MAAM,QAAQ,UAAU;EACjC,QACE,QAAO,MAAM,IAAI,UAAU;;;AAIjC,SAAS,IAAI,GAAW,GAAmB;CAEzC,MAAM,UAAU,EAAE,QAAQ,mBAAmB,GAAG;AAChD,KAAI,QAAQ,UAAU,EAAG,QAAO;AAChC,QAAO,IAAI,IAAI,OAAO,IAAI,QAAQ,OAAO;;AAG3C,SAAS,QAAQ,GAAW,MAAM,IAAY;AAC5C,KAAI,CAAC,EAAG,QAAO;AACf,QAAO,EAAE,UAAU,MAAM,IAAI,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;;;;;;;;AASxD,SAAS,cACP,aACA,UACA,cACA,aACY;CACZ,MAAM,UAAU,aAAa,cAAc,aAAa,UAAU,KAAK;CAKvE,MAAM,2BAAW,IAAI,KAAqB;CAC1C,MAAM,2BAAW,IAAI,KAAqB;AAC1C,MAAK,MAAM,KAAK,aAAa;AAC3B,WAAS,IAAI,EAAE,MAAM,EAAE,UAAU;AACjC,WAAS,IAAIC,gBAAc,EAAE,gBAAgB,EAAE,KAAK,CAAC,aAAa,EAAE,EAAE,UAAU;;CAGlF,MAAM,gCAAgB,IAAI,KAA6B;AACvD,MAAK,MAAM,KAAK,aAAa;AAC3B,MAAI,CAAC,EAAE,aAAa,CAAC,EAAE,WAAY;EACnC,MAAM,OAAO,cAAc,IAAI,EAAE,WAAW;AAC5C,MAAI,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAO,eAAc,IAAI,EAAE,YAAY,EAAE;;CAGvE,MAAM,QAAoB,EAAE;AAC5B,MAAK,MAAM,KAAK,SAAS;AAGvB,MAAI,CAAC,EAAE,KAAK,MAAM,CAAE;EAGpB,IAAI;EACJ,IAAI;AAEJ,MAAI,EAAE,aAAa;AACjB,SACE,EAAE,YAAY,gBACd,EAAE,YAAY,oBACd,EAAE,YAAY;AAChB,UAAO,EAAE,YAAY,YAAY,EAAE,YAAY,OAAO;aAC7C,EAAE,WAAW,UACtB,OAAM,EAAE;WACC,EAAE,QAAQ,SAAS,IAAI,EAAE,KAAK,CACvC,OAAM,SAAS,IAAI,EAAE,KAAK;MAK1B,OAAM,SAAS,IAAI,EAAE,KAAK,aAAa,CAAC;AAG1C,MAAI,OAAO,CAAC,MAAM;GAChB,MAAM,MAAM,UAAU,IAAI;AAC1B,UAAO,cAAc,IAAI,IAAI,EAAE;;EAGjC,IAAI;AACJ,MAAI,IAEF,UAAS;GAAE,GAAG;GAAK,MAAM,EAAE;GAAM;GAAK;GAAM,MAAM,EAAE;GAAe,MAAM,EAAE;GAAM;WACxE,EAAE,cACX,UAAS;GAAE,GAAG;GAAK,MAAM,EAAE;GAAM,MAAM,EAAE;GAAe,MAAM,EAAE;GAAM;MAEtE;EAIF,MAAM,KAAK,MAAM,aAAa,IAAI,GAAG;EAErC,MAAM,MAAM,OAAO,EAAE,OAAO;EAC5B,MAAM,OAAO,MAAM,MAAM,IAAI,EAAE,MAAM,GAAG,CAAC;EACzC,MAAM,QAAQ,MAAMC,MAAI,QAAQ,KAAK,GAAG,CAAC,GAAG,MAAM,MAAM,2BAA2B;EACnF,MAAM,MACJ,EAAE,WAAW,SACT,MAAM,MAAM,MAAM,GAClB,EAAE,eAAe,IACfA,MAAI,OAAO,EAAE,aAAa,CAAC,GAC3BA,MAAI,IAAI;EAChB,MAAM,SAAS,KAAKA,MAAI,OAAO,GAAG,GAAG;EACrC,MAAM,UAAU,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,GAAG,QAAQ;EAC1D,MAAM,SAAS,GAAG,EAAE,KAAK,GAAG,OAAO,GAAG,GAAG,KAAK,aAAa;AAE3D,QAAM,KAAK;GAAE;GAAQ;GAAS;GAAQ,CAAC;;AAEzC,QAAO;;AAoBT,MAAM,aAAa,MAAsB,EAAE,QAAQ,mBAAmB,GAAG;;AAGzE,SAAS,kBAAkB,KAAmB;CAC5C,MAAM,MACJ,QAAQ,aAAa,WAAW,SAAS,QAAQ,aAAa,UAAU,aAAa;AACvF,KAAI;AACF,QAAM,KAAK,CAAC,IAAI,EAAE;GAAE,OAAO;GAAU,UAAU;GAAM,CAAC,CAAC,OAAO;SACxD;;;;;;;;AAWV,SAAS,aAAa,GAAW,KAAqB;AACpD,KAAI,OAAO,EAAG,QAAO;CACrB,IAAI,MAAM;CACV,IAAI,MAAM;CACV,IAAI,IAAI;AACR,QAAO,IAAI,EAAE,QAAQ;AACnB,MAAI,EAAE,OAAO,QAAQ;GACnB,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC,MAAM,kBAAkB;AAC7C,OAAI,GAAG;AACL,WAAO,EAAE;AACT,SAAK,EAAE,GAAG;AACV;;;AAGJ,MAAI,OAAO,MAAM,EAAG,QAAO,MAAM;AACjC,SAAO,EAAE;AACT;AACA;;AAEF,QAAO,MAAM;;AAGf,SAAS,YAAY,OAAmB,OAA2B;CACjE,MAAM,IAAI,MAAM,MAAM,CAAC,aAAa;AACpC,KAAI,CAAC,EAAG,QAAO;CACf,MAAM,SAAS,EAAE,MAAM,MAAM;AAC7B,QAAO,MAAM,QAAQ,MAAM,OAAO,OAAO,MAAM,EAAE,OAAO,SAAS,EAAE,CAAC,CAAC;;;AAIvE,MAAM,2BAAW,IAAI,KAAuB;AAC5C,SAAS,WAAW,KAA2B;AAC7C,KAAI,CAAC,IAAI,IACP,QAAO,IAAI,OAAO,CAAC,MAAM,MAAM,gCAAgC,CAAC,GAAG,CAACA,MAAI,yBAAyB,CAAC;CAEpG,MAAM,SAAS,SAAS,IAAI,IAAI,IAAI;AACpC,KAAI,OAAQ,QAAO;CACnB,MAAM,QAAkB,EAAE;CAC1B,MAAM,QAAQ,eAAe,IAAI,KAAK,EAAE;AACxC,KAAI,MAAM,QAAQ;AAChB,QAAM,KAAKA,MAAI,gBAAgB,CAAC;AAChC,OAAK,MAAM,KAAK,MAAO,OAAM,KAAK,OAAOA,MAAI,EAAE,IAAI,MAAM,GAAG,GAAG,CAAC,GAAG,OAAOA,MAAI,OAAO,EAAE,MAAM,CAAC,CAAC;OAE/F,OAAM,KAAKA,MAAI,sBAAsB,CAAC;AAExC,UAAS,IAAI,IAAI,KAAK,MAAM;AAC5B,QAAO;;AAGT,eAAe,YACb,OACA,UACgC;CAChC,MAAM,QAAQ,QAAQ;CACtB,MAAM,MAAM,QAAQ;AAEpB,QAAO,IAAI,SAAgC,YAAY;EACrD,IAAI,OAAO;EACX,IAAI,QAAQ;EACZ,IAAI,OAA6B;EACjC,IAAI,WAAW;EACf,IAAI,SAAS;EACb,IAAI,UAA2B;EAC/B,IAAI,QAAQ;EAEZ,MAAM,SAAS,MAAM,SAAS;AAC9B,MAAI;AACF,SAAM,aAAa,KAAK;UAClB;AAGR,QAAM,QAAQ;AACd,MAAI,MAAM,uBAAuB;EAEjC,MAAM,iBAAiB;AACrB,OAAI,MAAM,uBAAuB;AACjC,SAAM,eAAe,QAAQ,OAAO;AACpC,OAAI;AACF,UAAM,aAAa,OAAO;WACpB;AAGR,SAAM,OAAO;;EAEf,MAAM,UAAU,QAA+B;AAC7C,aAAU;AACV,WAAQ,IAAI;;EAGd,MAAM,kBAAkB;AACtB,cAAW,YAAY,MAAM,MAAM;AACnC,OAAI,UAAU,SAAS,OAAQ,UAAS,KAAK,IAAI,GAAG,SAAS,SAAS,EAAE;;EAG1E,MAAM,eAAe;GACnB,MAAM,OAAO,IAAI,QAAQ,IAAI,OAAO,IAAI,IAAI,OAAO;GACnD,MAAM,OAAO,IAAI,WAAW,IAAI,UAAU,KAAK,IAAI,UAAU;GAC7D,MAAM,QAAkB,EAAE;GAG1B,MAAM,QAAQ,MAAM,KAAK,QAAQ,GAAGA,MAAI,iCAAiC;AACzE,SAAM,KAAK,MAAM;AACjB,OAAI,SAAS,SACX,OAAM,KAAK,OAAO,MAAM,KAAK,YAAY,GAAG,QAAQ,MAAM,QAAQ,IAAI,CAAC;OAEvE,OAAM,KACJ,OAAOA,MAAI,GAAG,SAAS,OAAO,GAAG,MAAM,SAAS,IAC/C,QAAQA,MAAI,aAAa,QAAQ,GAAG,IACtC;AAEH,SAAM,KAAKA,MAAI,OAAO,IAAI,OAAO,KAAK,IAAI,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC;GAG1D,MAAM,YAAY;GAClB,MAAM,aAAa,KAAK,IAAI,GAAG,OAAO,IAAI,YAAY,EAAE;GACxD,IAAI,QAAQ;AACZ,OAAI,UAAU,WAAY,SAAQ,SAAS,aAAa;GACxD,MAAM,MAAM,KAAK,IAAI,SAAS,QAAQ,QAAQ,WAAW;AACzD,OAAI,SAAS,WAAW,EACtB,OAAM,KAAK,OAAOA,MAAI,yDAAyD,CAAC;AAElF,QAAK,IAAI,IAAI,OAAO,IAAI,KAAK,KAAK;IAChC,MAAM,MAAM,SAAS;AACrB,QAAI,MAAM,OACR,OAAM,KAAK,MAAM,QAAQ,MAAM,UAAU,IAAI,QAAQ,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC;QAE3F,OAAM,KAAK,OAAO,IAAI,QAAQ;;AAKlC,SAAM,KAAKA,MAAI,OAAO,IAAI,OAAO,KAAK,IAAI,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC;GAC1D,MAAM,MAAM,SAAS;AACrB,OAAI,KAAK;AACP,UAAM,KAAK,OAAO,MAAM,KAAK,MAAM,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,MAAMA,MAAI,QAAQ,QAAQ,IAAI,OAAO,KAAK,OAAO,GAAG,CAAC,GAAG,IAAI;AAC9H,SAAK,MAAM,KAAK,WAAW,IAAI,OAAO,CAAC,MAAM,GAAG,YAAY,EAAE,CAAE,OAAM,KAAK,EAAE;;AAI/E,SAAM,KAAKA,MAAI,OAAO,IAAI,OAAO,KAAK,IAAI,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC;AAC1D,OAAI,QACF,OAAM,KACJ,OAAO,MAAM,OAAO,WAAW,QAAQ,OAAO,KAAK,oBAAoB,GACvEA,MAAI,wBAAwB,GAAG,MAAM,KAAK,IAAI,GAAGA,MAAI,IAAI,GAAG,MAAM,KAAK,IAAI,CAC5E;YACQ,SAAS,SAClB,OAAM,KAAK,OAAOA,MAAI,oBAAoB,GAAG,MAAM,KAAK,QAAQ,GAAGA,MAAI,WAAW,GAAG,MAAM,KAAK,MAAM,GAAGA,MAAI,UAAU,CAAC;QACnH;IACL,MAAM,QAAkB,EAAE;AAC1B,QAAI,KAAK,OAAO,KAAM,OAAM,KAAK,MAAM,KAAK,IAAI,GAAGA,MAAI,aAAa,CAAC;AACrE,QAAI,KAAK,OAAO,IAAK,OAAM,KAAK,MAAM,KAAK,IAAI,GAAGA,MAAI,OAAO,CAAC;AAC9D,QAAI,KAAK,OAAO,IAAK,OAAM,KAAK,MAAM,KAAK,IAAI,GAAGA,MAAI,MAAM,CAAC;AAC7D,QAAI,KAAK,OAAO,IAAK,OAAM,KAAK,MAAM,KAAK,IAAI,GAAGA,MAAI,UAAU,CAAC;AACjE,QAAI,KAAK,OAAO,IAAK,OAAM,KAAK,MAAM,KAAK,IAAI,GAAGA,MAAI,UAAU,CAAC;AACjE,QAAI,KAAK,OAAO,IAAK,OAAM,KAAK,MAAM,KAAK,IAAI,GAAGA,MAAI,UAAU,CAAC;AACjE,QAAI,KAAK,OAAO,KAAM,OAAM,KAAK,MAAM,KAAK,IAAI,GAAGA,MAAI,UAAU,CAAC;AAClE,UAAM,KAAK,MAAM,KAAK,IAAI,GAAGA,MAAI,UAAU,CAAC;AAC5C,UAAM,KAAK,MAAM,KAAK,KAAK,GAAGA,MAAI,QAAQ,CAAC;AAC3C,UAAM,KAAK,MAAM,KAAK,IAAI,GAAGA,MAAI,QAAQ,CAAC;AAC1C,UAAM,KAAK,OAAO,MAAM,KAAKA,MAAI,QAAQ,CAAC,CAAC;AAC3C,QAAI,MAAO,OAAM,KAAK,OAAOA,MAAI,MAAM,CAAC;;GAK1C,MAAM,UAAU,MAAM,KAAK,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,KAAK,WAAW;AACxE,OAAI,MAAM,WAAW,UAAU,eAAe;;EAGhD,MAAM,QAAQ,UAAkB;AAC9B,OAAI,SAAS,WAAW,EAAG;AAC3B,aAAU,SAAS,QAAQ,SAAS,UAAU,SAAS;;EAGzD,MAAM,UAAU,QAAgB;GAC9B,MAAM,IAAI,IAAI,SAAS,OAAO;GAC9B,MAAM,MAAM,SAAS;AAGrB,OAAI,MAAM,IAAQ,QAAO,OAAO,KAAK;AAGrC,OAAI,SAAS;IACX,MAAM,SAAS;AACf,cAAU;AACV,QAAI,MAAM,OAAO,MAAM,IAErB,KADkB,WAAW,SAAS,OAAO,OAAO,GAAG,OACxC;AACb,YAAO,KAAK,QAAQ,MAAM,MAAM,OAAO;AACvC,cAAS,OAAO,OAAO,OAAO,OAAO,GAAG;AACxC,gBAAW;AACX,aAAQ,YAAY,OAAO,OAAO,KAAK;UAEvC,SAAQ,qBAAqB,OAAO,OAAO,KAAK;QAGlD,SAAQ;AAEV,WAAO,QAAQ;;AAIjB,OAAI,MAAM,UAAU;AAAE,SAAK,GAAG;AAAE,WAAO,QAAQ;;AAC/C,OAAI,MAAM,UAAU;AAAE,SAAK,EAAE;AAAE,WAAO,QAAQ;;AAC9C,OAAI,MAAM,WAAW;AAAE,SAAK,GAAG;AAAE,WAAO,QAAQ;;AAChD,OAAI,MAAM,WAAW;AAAE,SAAK,EAAE;AAAE,WAAO,QAAQ;;AAE/C,OAAI,SAAS,UAAU;AACrB,QAAI,MAAM,QAAQ,MAAM,MAAM;AAAE,YAAO;AAAW,YAAO,QAAQ;;AACjE,QAAI,MAAM,QAAQ;AAAE,YAAO;AAAW,YAAO,QAAQ;;AACrD,QAAI,MAAM,OAAU,MAAM,MAAM;AAAE,aAAQ,MAAM,MAAM,GAAG,GAAG;AAAE,gBAAW;AAAE,YAAO,QAAQ;;AAE1F,QAAI,EAAE,WAAW,KAAK,KAAK,KAAK;AAAE,cAAS;AAAG,cAAS;AAAG,gBAAW;AAAE,YAAO,QAAQ;;AACtF;;AAIF,OAAI,MAAM,OAAO,MAAM,OAAQ,QAAO,OAAO,KAAK;AAClD,OAAI,MAAM,OAAO,MAAM,KAAK;AAAE,YAAQ;AAAI,WAAO;AAAU,WAAO,QAAQ;;AAC1E,OAAI,MAAM,KAAK;AAAE,SAAK,EAAE;AAAE,WAAO,QAAQ;;AACzC,OAAI,MAAM,KAAK;AAAE,SAAK,GAAG;AAAE,WAAO,QAAQ;;AAC1C,OAAI,CAAC,IAAK;AACV,OAAI,MAAM,OAAO,IAAI,OAAO,KAAM,QAAO,OAAO;IAAE,QAAQ,IAAI;IAAQ,QAAQ;IAAU,CAAC;AACzF,OAAI,MAAM,OAAO,IAAI,OAAO,IAAK,QAAO,OAAO;IAAE,QAAQ,IAAI;IAAQ,QAAQ;IAAO,CAAC;AACrF,OAAI,MAAM,OAAO,IAAI,OAAO,IAAK,QAAO,OAAO;IAAE,QAAQ,IAAI;IAAQ,QAAQ;IAAM,CAAC;AAEpF,OAAI,MAAM,OAAO,IAAI,OAAO,IAAK,QAAO,OAAO;IAAE,QAAQ,IAAI;IAAQ,QAAQ;IAAU,CAAC;AACxF,OAAI,MAAM,OAAO,IAAI,OAAO,IAAK,QAAO,OAAO;IAAE,QAAQ,IAAI;IAAQ,QAAQ;IAAa,CAAC;AAE3F,OAAI,MAAM,OAAO,IAAI,OAAO,KAAK;AAAE,sBAAkB,IAAI,OAAO,IAAI;AAAE;;AAEtE,OAAI,MAAM,KAAK;AACb,QAAI,IAAI,OAAO,MAAM;AAAE,eAAU;AAAK,aAAQ;UACzC,SAAQ;AACb,WAAO,QAAQ;;AAEjB,OAAI,MAAM,QAAQ,MAAM,MAAM;AAE5B,QAAI,IAAI,OAAO,KAAM,QAAO,OAAO;KAAE,QAAQ,IAAI;KAAQ,QAAQ;KAAU,CAAC;AAC5E,QAAI,IAAI,OAAO,IAAK,QAAO,OAAO;KAAE,QAAQ,IAAI;KAAQ,QAAQ;KAAO,CAAC;;;AAI5E,QAAM,GAAG,QAAQ,OAAO;AACxB,UAAQ;GACR;;AAaJ,SAAS,eAAe,MAAc,OAA0B;CAC9D,MAAM,QAAmB,EAAE;CAC3B,MAAM,QAAQ,KAAa,UAAkB;AAC3C,MAAI,QAAQ,KAAK,MAAM,UAAU,IAAK;EACtC,IAAI;AACJ,MAAI;AACF,aAAU,YAAY,IAAI;UACpB;AACN;;AAEF,OAAK,MAAM,QAAQ,SAAS;AAC1B,OAAI,KAAK,WAAW,IAAI,IAAI,QAAQ,IAAI,KAAK,CAAE;GAC/C,MAAM,OAAO,KAAK,KAAK,KAAK;GAC5B,IAAI;AACJ,OAAI;AACF,SAAK,SAAS,KAAK;WACb;AACN;;AAEF,OAAI,GAAG,aAAa,CAAE,MAAK,MAAM,QAAQ,EAAE;YAClC,KAAK,aAAa,CAAC,SAAS,MAAM,CACzC,OAAM,KAAK;IAAE,KAAK,KAAK,MAAM,KAAK,SAAS,EAAE;IAAE;IAAM,OAAO,GAAG;IAAS,CAAC;;;AAI/E,MAAK,MAAM,EAAE;AACb,OAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;AACvC,QAAO,MAAM,MAAM,GAAG,MAAM;;AAY9B,eAAsB,QAAQ,IAAc,OAAiB,EAAE,EAAiB;AAK9E,KAAI,EAJgB,CAAC,CAAC,QAAQ,OAAO,SAAS,CAAC,CAAC,QAAQ,MAAM,QAI5C;EAChB,MAAM,cAAc,aAAa,IAAI;GAAE,OAAO;GAAK,QAAQ;GAAS,CAAC;EACrE,MAAM,WAAW,YAAY,IAAI,KAAK,OAAO,MAAM;EACnD,IAAI,OAAsD,EAAE;AAC5D,MAAI;AACF,UAAO,MAAM,mBAAmB;UAC1B;AAIR,wBADgB,aAAa,MAAM,aAAa,UAAU,KAAK,OAAO,MAAM,EAC7C,KAAK,MAAM,SAAY,GAAG;AACzD;;CAIF,MAAM,cAAc,aAAa,IAAI;EAAE,OAAO;EAAK,QAAQ;EAAS,CAAC;CACrE,MAAM,WAAW,YAAY,IAAI,KAAK,OAAO,MAAM;CACnD,IAAI,OAAsD,EAAE;AAC5D,KAAI;AACF,SAAO,MAAM,mBAAmB;SAC1B;CAIR,MAAM,cAAc,YAAY,IAAI,KAAK;CACzC,MAAM,QAAQ,cAAc,aAAa,UAAU,MAAM,YAAY;AACrE,KAAI,MAAM,WAAW,GAAG;AACtB,UAAQ,OAAO,MAAMA,MAAI,qCAAqC,CAAC;AAC/D;;CAKF,MAAM,YAAY,QAA6B;AAC7C,MAAI,CAAC,IAAI,KAAM,QAAO;AACtB,MAAI;GACF,MAAM,KAAK,KAAK;AAMhB,UALY,GACT,QACC,gHACD,CACA,IAAI,IAAI,IAAI,IAAI,KAAK,CACb,UAAU;UACf;AACN,UAAO;;;CAKX,MAAM,MAAM,MAAM,YAAY,OAAO,SAAS;AAC9C,KAAI,CAAC,IAAK;CACV,MAAM,EAAE,QAAQ,WAAW;AAG3B,KAAI,WAAW,UAAU;AACvB,MAAI,OAAO,MAAM;GACf,MAAM,IAAI,mBAAmB,OAAO,KAAK;AACzC,OAAI,CAAC,EAAE,GACL,SAAQ,OAAO,MAAM,MAAM,OAAO,uBAAuB,EAAE,SAAS,gBAAgB,IAAI,CAAC;;AAG7F;;AAGF,KAAI,CAAC,OAAO,KAAK;AACf,UAAQ,OAAO,MAAMA,MAAI,iCAAiC,OAAO,KAAK,MAAM,CAAC;AAC7E;;AAMF,KAAI,WAAW,MAAM;EACnB,MAAM,OAAO,QAAQ,IAAI;AACzB,MAAI,KACF,KAAI;AACF,iBAAc,MAAM,OAAO,KAAK,OAAO;AACvC;UACM;AAIV,eAAa,OAAO,IAAI;AACxB;;CAKF,MAAM,SAAS,WAAW,WAAW,WAAW,WAAW,cAAc,WAAW;AACpF,aAAY,OAAO,KAAK,OAAO,MAAM;EAAE,YAAY;EAAM;EAAQ,QAAQ,KAAK;EAAQ,CAAC;;;;;;;;;;;;;;;ACjoBzF,SAAS,aAAqB;AAC5B,KAAI;EAEF,MAAM,UAAU,KADE,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACzB,qBAAqB;AAErD,SADY,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC,CAC1C,WAAW;SAChB;AACN,SAAO;;;AAQX,IAAI,MAAuB;AAE3B,SAAS,QAAkB;AACzB,KAAI,CAAC,IACH,KAAI;AACF,QAAM,cAAc;UACb,GAAG;AAOV,QAAM,IAAI,MAAM,gCAAgC,IAAI;;AAGxD,QAAO;;;;;;AAWT,SAAgB,eAAwB;CACtC,MAAM,UAAU,IAAI,SAAS;AAE7B,SACG,KAAK,MAAM,CACX,YAAY,oDAAoD,CAChE,QAAQ,YAAY,EAAE,iBAAiB,yBAAyB,CAChE,YACC,SACA;;;;;;;;;;;;;;;;;;;;oFAqBD;AAUH,0BAJoB,QACjB,QAAQ,WAAW,CACnB,YAAY,wDAAwD,EAEjC,MAAM;AAO5C,0BAJmB,QAChB,QAAQ,UAAU,CAClB,YAAY,2BAA2B,EAEL,MAAM;CAmB3C,MAAM,aAAa,QAChB,QAAQ,UAAU,CAClB,YACC,8IAED;AAEH,yBAAwB,YAAY,MAAM;AAC1C,+BAA8B,YAAY,MAAM;AAahD,wBAPkB,QACf,QAAQ,SAAS,CACjB,YACC,qIAED,CAE8B;AAEjC,SACG,QAAQ,WAAW,CACnB,YAAY,sDAAsD,CAClE,OAAO,SAAS,qEAAqE,CACrF,OAAO,mBAAmB,qCAAqC,KAAK,CACpE,OAAO,OAAO,SAAwC;AACrD,QAAM,QAAQ,OAAO,EAAE,QAAW,QAAW,KAAK;GAClD;AAUJ,0BAJoB,QACjB,QAAQ,WAAW,CACnB,YAAY,sDAAsD,EAE/B,MAAM;AAU5C,wBAJkB,QACf,QAAQ,SAAS,CACjB,YAAY,2CAA2C,EAExB,MAAM;AAUxC,0BAJoB,QACjB,QAAQ,WAAW,CACnB,YAAY,kEAAkE,CAE5C;AAUrC,qBAJe,QACZ,QAAQ,MAAM,CACd,YAAY,4CAA4C,CAEhC;AAU3B,wBAJkB,QACf,QAAQ,SAAS,CACjB,YAAY,0EAA0E,CAExD;AAMjC,wBAAuB,QAAQ;AAC/B,yBAAwB,QAAQ;AAMhC,sBAAqB,QAAQ;AAC7B,uBAAsB,QAAQ;AAU9B,wBAJkB,QACf,QAAQ,SAAS,CACjB,YAAY,oDAAoD,CAElC;AAWjC,sBALgB,QACb,QAAQ,OAAO,CACf,MAAM,QAAQ,CACd,YAAY,iEAAiE,CAEnD;AAU7B,uBAJiB,QACd,QAAQ,QAAQ,CAChB,YAAY,kFAAkF,CAElE;AAU/B,oBAJc,QACX,QAAQ,KAAK,CACb,YAAY,yDAAyD,CAE/C;AAUzB,oBAJc,QACX,QAAQ,KAAK,CACb,YAAY,kEAAkE,CAExD;AAUzB,0BAJoB,QACjB,QAAQ,WAAW,CACnB,YAAY,oEAAoE,EAE7C,MAAM;AAU5C,wBAJkB,QACf,QAAQ,SAAS,CACjB,YAAY,kFAAkF,EAE/D,MAAM;AAUxC,6BAJuB,QACpB,QAAQ,cAAc,CACtB,YAAY,+CAA+C,CAEnB;AAU3C,uBAJiB,QACd,QAAQ,QAAQ,CAChB,YAAY,6EAA6E,CAE7D;AAM/B,qBAAoB,QAAQ;AAO5B,SACG,QAAQ,iBAAiB,CACzB,YACC,mMAGD,CACA,OAAO,aAAa,uCAAuC,CAC3D,OACC,sBACA,gGACD,CACA,OACC,uBACA,sEACD,CACA,OACC,aACA,6DACD,CACA,OAAO,UAAU,iEAAiE,CAClF,OACC,iBACA,sSAID,CACA,OACC,eACA,oRAIA,SACD,CACA,OAAO,OAAO,QAA4B,SAAoI;AAC7K,MAAI,WAAW,MACb,OAAM,YAAY;GAChB,MAAM,KAAK;GACX,QAAQ,KAAK;GACb,MAAM,KAAK;GACX,MAAM,KAAK,SAAS,SAAY,SAAS,KAAK,MAAM,GAAG,GAAG;GAC3D,CAAC;OACG;AACL,OAAI,WAAW,QAAW;AACxB,YAAQ,MAAM,IAAI,mBAAmB,OAAO,iCAAiC,CAAC;AAC9E,YAAQ,WAAW;AACnB;;AAGF,YAAS,OAAO,EAAE;IAChB,QAAQ,KAAK;IACb,UAAU,KAAK;IACf,WAAW,KAAK;IAChB,QAAQ,KAAK,SAAS;IACvB,CAAC;;GAEJ;AAGJ,SACG,QAAQ,MAAM,CACd,YACC,uFACD,CACA,OAAO,aAAa,2CAA2C,CAC/D,OACC,sBACA,gGACD,CACA,OACC,uBACA,sEACD,CACA,OACC,aACA,6DACD,CACA,QAAQ,SAAsF;AAC7F,SAAO,OAAO,EAAE;GACd,QAAQ,KAAK;GACb,UAAU,KAAK;GACf,WAAW,KAAK;GAChB,QAAQ,KAAK,SAAS;GACvB,CAAC;GACF;AAGJ,SACG,QAAQ,cAAc,CACtB,YACC,kMAGD,CACA,OAAO,aAAa,uDAAuD,CAC3E,OAAO,OAAO,SAA+B;AAC5C,QAAM,cAAc,KAAK;GACzB;AAOJ,SACG,QAAQ,iBAAiB,EAAE,QAAQ,MAAM,CAAC,CAC1C,YAAY,uDAAuD,CACnE,OAAO,aAAa,4DAA4D,CAChF,QAAQ,MAAc,SAA+B;AACpD,UAAQ,OAAO,EAAE,MAAM,EAAE,QAAQ,KAAK,QAAQ,CAAC;GAC/C;AAGJ,SACG,QAAQ,mBAAmB,EAAE,QAAQ,MAAM,CAAC,CAC5C,YAAY,kEAAkE,CAC9E,QAAQ,eAAuB;EAC9B,MAAM,KAAK,OAAO;EAClB,MAAM,UAAU,kBAAkB,IAAI,WAAW;AACjD,MAAI,CAAC,SAAS;AACZ,WAAQ,MAAM,sBAAsB,aAAa;AACjD,WAAQ,WAAW;AACnB;;AAGF,MAAI,WAAW,QAAQ,UAAU,EAAE;AACjC,WAAQ,OAAO,MAAM,QAAQ,YAAY,KAAK;AAC9C;;AAGF,UAAQ,OAAO,MACbC,OAAK,mBAAmB,QAAQ,UAAU,IAAI,GAC9CC,MAAI,sCAAsC,CAC3C;EAED,MAAM,SAAS,cAAc,QAAQ,UAAU;AAE/C,MAAI,OAAO,OAAO;GAChB,MAAM,UAAU,OAAO;GACvB,MAAM,aAAa,UAAU,QAAQ;GACrC,MAAM,KAAK,KAAK;AAChB,MAAG,QACD,kFACD,CAAC,IAAI,SAAS,YAAY,IAAI,QAAQ,GAAG;AAC1C,WAAQ,OAAO,MACbC,KAAG,kBAAkB,YAAY,QAAQ,WAAW,GAAG,CAAC,IAAI,GAC5DD,MAAI,OAAO,QAAQ,IAAI,GACvBC,KAAG,sBAAsB,CAC1B;AACD,WAAQ,OAAO,MAAM,UAAU,KAAK;AACpC;;AAGF,MAAI,OAAO,WAAW;AACpB,WAAQ,OAAO,MACbF,OAAK,+BAA+B,SAAS,QAAQ,UAAU,CAAC,YAAY,CAC7E;AACD,QAAK,MAAM,aAAa,OAAO,UAC7B,SAAQ,OAAO,MAAMC,MAAI,KAAK,UAAU,IAAI,CAAC;AAE/C,WAAQ,OAAO,MACbA,MAAI,8CAA8C,QAAQ,KAAK,WAAW,CAC3E;AACD,WAAQ,WAAW;AACnB;;AAGF,UAAQ,OAAO,MACb,IACE,YAAY,QAAQ,KAAK,eAAe,QAAQ,UAAU,mDAChC,SAAS,QAAQ,UAAU,CAAC,6BACvD,GACDA,MAAI,mCAAmC,QAAQ,KAAK,eAAe,CACpE;AACD,UAAQ,WAAW;GACnB;AAiBJ,CAXiB,QACd,QAAQ,wBAAwB,EAAE,QAAQ,MAAM,CAAC,CACjD,YAAY,yEAAyE,CACrF,OAAO,eAAe,mCAAmC,KAAK,CAC9D,OAAO,qBAAqB,iDAAiD,CAC7E,QACE,aAAiC,SAA8C;AAC9E,UAAa,OAAO,EAAE,aAAa,KAAK;GAE3C,CAGA,QAAQ,sBAAsB,CAC9B,YAAY,mDAAmD,CAC/D,OAAO,eAAe,mCAAmC,KAAK,CAC9D,OAAO,qBAAqB,iDAAiD,CAC7E,QACE,aAAiC,SAA8C;AAC9E,UAAa,OAAO,EAAE,aAAa,KAAK;GAE3C;AAMH,SACG,QAAQ,gBAAgB,EAAE,QAAQ,MAAM,CAAC,CACzC,YAAY,+CAA+C,CAC3D,OAAO,mBAAmB,sCAAsC,KAAK,CACrE,OAAO,UAAU,uBAAuB,CACxC,OAAO,OAAO,OAAe,SAAyC;AACrE,QAAM,QAAQ,OAAO,KAAK;GAC1B;AAMJ,SACG,QAAQ,aAAa,CACrB,YAAY,2EAAyE,CACrF,aAAa;AACZ,UAAQ,OAAO,MACb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DD;GACD;AAMJ,SACG,QAAQ,wBAAwB;EAAE,WAAW;EAAM,QAAQ;EAAM,CAAC,CAClE,YACC,2OAKD,CACA,OAAO,cAAc,iCAAiC,CACtD,OAAO,aAAa,4CAA4C,CAChE,OAAO,mBAAmB,qCAAqC,KAAK,CACpE,OAAO,SAAS,qEAAqE,CACrF,OAAO,UAAU,gEAAgE,CACjF,OAAO,OAAO,OAA2B,MAA0B,SAA0F;AAI5J,MAAI,UAAU,UAAa,CAAC,KAAK,MAAM;AACrC,SAAM,QAAQ,OAAO,EAAE;IAAE,KAAK,KAAK;IAAK,QAAQ,KAAK;IAAQ,CAAC;AAC9D;;EAEF,MAAM,QAAQ,SAAS,SAAY,SAAS,MAAM,GAAG,GAAG;AACxD,QAAM,QAAQ,OAAO,EAAE,OAAO,OAAO,KAAK;GAC1C;AAMJ,SAAQ,gBAAgB,EACtB,WAAW,QAAQ,QAAQ,OAAO,MAAM,IAAI,IAAI,CAAC,EAClD,CAAC;AAUF,SAAQ,cAAc;AAEtB,QAAO"}
|