@fml-inc/fml 0.0.29 → 0.1.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.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/src/targets/registry.ts","../node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/src/targets/claude.ts","../node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/src/scanner/categories.ts","../node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/src/scanner/reader.ts","../node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/src/targets/gemini.ts","../node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/src/targets/codex.ts","../node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/src/db/query.ts","../node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/src/doctor.ts","../node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/src/targets/types.ts","../node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/src/targets/claude-desktop.ts"],"sourcesContent":["import type { TargetAdapter } from \"./types.js\";\n\nconst targets = new Map<string, TargetAdapter>();\n\nexport function registerTarget(adapter: TargetAdapter): void {\n if (targets.has(adapter.id)) {\n throw new Error(`Target \"${adapter.id}\" is already registered`);\n }\n targets.set(adapter.id, adapter);\n}\n\nexport function getTarget(id: string): TargetAdapter | undefined {\n return targets.get(id);\n}\n\nexport function getTargetOrThrow(id: string): TargetAdapter {\n const v = targets.get(id);\n if (!v) {\n throw new Error(\n `Unknown target: \"${id}\". Known: ${[...targets.keys()].join(\", \")}`,\n );\n }\n return v;\n}\n\nexport function allTargets(): TargetAdapter[] {\n return [...targets.values()];\n}\n\nexport function targetIds(): string[] {\n return [...targets.keys()];\n}\n","import fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { config } from \"../config.js\";\nimport { defaultToolCategory } from \"../scanner/categories.js\";\nimport { readNewLines } from \"../scanner/reader.js\";\nimport { registerTarget } from \"./registry.js\";\nimport type { ParsedToolCall, ParseResult, TargetAdapter } from \"./types.js\";\n\nconst CLAUDE_TOOL_CATEGORIES: Record<string, string> = {\n Read: \"Read\",\n read_file: \"Read\",\n ReadNotebook: \"Read\",\n Edit: \"Edit\",\n StrReplace: \"Edit\",\n MultiEdit: \"Edit\",\n Write: \"Write\",\n create_file: \"Write\",\n NotebookEdit: \"Write\",\n Bash: \"Bash\",\n Grep: \"Grep\",\n Glob: \"Glob\",\n list_dir: \"Glob\",\n Task: \"Task\",\n Agent: \"Task\",\n TaskCreate: \"Task\",\n TaskUpdate: \"Task\",\n Skill: \"Tool\",\n WebSearch: \"Web\",\n WebFetch: \"Web\",\n ToolSearch: \"Web\",\n};\n\nfunction claudeToolCategory(toolName: string): string {\n const mapped = CLAUDE_TOOL_CATEGORIES[toolName];\n if (mapped) return mapped;\n if (toolName.startsWith(\"mcp__\")) return \"MCP\";\n if (toolName.toLowerCase().includes(\"subagent\")) return \"Task\";\n return defaultToolCategory(toolName);\n}\n\n// ── System message detection & command envelope handling ────────────────────\n\n/** Patterns for system-injected user messages that should be marked isSystem. */\nconst SYSTEM_MESSAGE_PREFIXES = [\n \"This session is being continued\",\n \"[Request interrupted\",\n \"<task-notification>\",\n \"<local-command-\",\n \"Stop hook feedback:\",\n];\n\n/** Check if content matches a known system-injected pattern. */\nfunction isSystemMessage(content: string): boolean {\n const trimmed = content.trimStart();\n return SYSTEM_MESSAGE_PREFIXES.some((p) => trimmed.startsWith(p));\n}\n\nconst CMD_NAME_RE = /<command-name>([^<]+)<\\/command-name>/;\nconst CMD_ARGS_RE = /<command-args>([^<]*)<\\/command-args>/;\nconst CMD_MSG_RE = /<command-message>([^<]+)<\\/command-message>/;\nconst CMD_STRIP_RE =\n /<\\/?(?:command-name|command-message|command-args)>[^<]*<\\/(?:command-name|command-message|command-args)>|<\\/?(?:command-name|command-message|command-args)>/g;\n\n/**\n * Detect command/skill XML envelopes and convert to readable form.\n * Returns [convertedText, true] if it was a command envelope,\n * or [originalText, false] if not.\n */\nfunction extractCommandText(content: string): [string, boolean] {\n // Strip BOM and leading whitespace for matching\n const trimmed = content.replace(/^\\uFEFF/, \"\").trimStart();\n if (\n !trimmed.startsWith(\"<command-message>\") &&\n !trimmed.startsWith(\"<command-name>\")\n ) {\n return [content, false];\n }\n // Verify it's purely command XML (no trailing prose)\n const stripped = trimmed.replace(CMD_STRIP_RE, \"\");\n if (stripped.trim() !== \"\") {\n return [content, false];\n }\n\n const nameMatch = CMD_NAME_RE.exec(content);\n if (!nameMatch) {\n // Bare <command-message> without <command-name>\n const msgMatch = CMD_MSG_RE.exec(content);\n if (msgMatch) return [`/${msgMatch[1]}`, true];\n return [content, false];\n }\n\n let name = nameMatch[1];\n if (!name.startsWith(\"/\")) name = `/${name}`;\n const argsMatch = CMD_ARGS_RE.exec(content);\n const args = argsMatch?.[1]?.trim();\n return [args ? `${name} ${args}` : name, true];\n}\n\n/** Extract total text length from a tool_result content field. */\nfunction extractToolResultTextLength(content: unknown): number {\n if (typeof content === \"string\") return content.length;\n if (Array.isArray(content)) {\n let len = 0;\n for (const b of content) {\n if (\n typeof b === \"object\" &&\n b !== null &&\n (b as Record<string, unknown>).type === \"text\"\n ) {\n const text = (b as Record<string, unknown>).text;\n if (typeof text === \"string\") len += text.length;\n }\n }\n return len;\n }\n return 0;\n}\n\n// ── DAG fork detection ─────────────────────────────────────────────────────\n// Adapted from agentsview's parseDAG/walkBranch (internal/parser/claude.go).\n// Detects uuid/parentUuid branching in JSONL files and separates large-gap\n// forks (>FORK_THRESHOLD user turns on first child) into separate sessions.\n\ninterface DagEntry {\n uuid: string;\n parentUuid: string;\n type: \"user\" | \"assistant\";\n lineIndex: number;\n timestampMs: number;\n}\n\nconst FORK_THRESHOLD = 3;\n\n/** True if entries don't form a simple linear chain. */\nfunction hasDAGFork(entries: DagEntry[]): boolean {\n for (let i = 1; i < entries.length; i++) {\n if (entries[i].parentUuid !== entries[i - 1].uuid) return true;\n }\n return false;\n}\n\ninterface ForkBranch {\n parentId: string;\n dagIndices: number[];\n}\n\n/**\n * Walk the DAG from root, detecting large-gap forks.\n * Returns which dag entry indices belong to the main path vs fork branches.\n */\nfunction detectForks(\n entries: DagEntry[],\n sessionId: string,\n): { mainDagIndices: number[]; forkBranches: ForkBranch[] } {\n const children = new Map<string, number[]>();\n const roots: number[] = [];\n const uuidSet = new Set<string>();\n\n for (let i = 0; i < entries.length; i++) {\n const e = entries[i];\n uuidSet.add(e.uuid);\n if (!e.parentUuid) {\n roots.push(i);\n } else {\n const kids = children.get(e.parentUuid) ?? [];\n kids.push(i);\n children.set(e.parentUuid, kids);\n }\n }\n\n // Need exactly one root; all parentUuids must reference known uuids\n if (roots.length !== 1) {\n return { mainDagIndices: entries.map((_, i) => i), forkBranches: [] };\n }\n for (const e of entries) {\n if (e.parentUuid && !uuidSet.has(e.parentUuid)) {\n return { mainDagIndices: entries.map((_, i) => i), forkBranches: [] };\n }\n }\n\n const forkBranches: ForkBranch[] = [];\n\n /** Count ALL user entries reachable from startIdx (full subtree DFS). */\n function countUserTurns(startIdx: number): number {\n const stack = [startIdx];\n let count = 0;\n while (stack.length > 0) {\n const idx = stack.pop()!;\n if (entries[idx].type === \"user\") count++;\n for (const k of children.get(entries[idx].uuid) ?? []) stack.push(k);\n }\n return count;\n }\n\n function walkBranch(startIdx: number, ownerId: string): number[] {\n const pathIndices: number[] = [];\n let current: number | null = startIdx;\n\n while (current !== null) {\n pathIndices.push(current);\n const kids: number[] = children.get(entries[current].uuid) ?? [];\n\n if (kids.length === 0) {\n current = null;\n } else if (kids.length === 1) {\n current = kids[0];\n } else {\n // Fork point — check first child's subtree user turn count\n const firstChildTurns = countUserTurns(kids[0]);\n if (firstChildTurns <= FORK_THRESHOLD) {\n // Small-gap retry: follow latest child (last), skip earlier\n current = kids[kids.length - 1];\n } else {\n // Large-gap fork: follow first child on main path,\n // collect other children as separate fork branches\n for (let k = 1; k < kids.length; k++) {\n const branchIndices = walkBranch(kids[k], ownerId);\n forkBranches.push({\n parentId: ownerId,\n dagIndices: branchIndices,\n });\n }\n current = kids[0];\n }\n }\n }\n return pathIndices;\n }\n\n const mainDagIndices = walkBranch(roots[0], sessionId);\n return { mainDagIndices, forkBranches };\n}\n\n/** Claude Code session files use standard UUIDs as filenames. */\nconst UUID_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nconst CLAUDE_DIR = path.join(os.homedir(), \".claude\");\n\nconst claude: TargetAdapter = {\n id: \"claude\",\n\n config: {\n dir: CLAUDE_DIR,\n configPath: path.join(CLAUDE_DIR, \"settings.json\"),\n configFormat: \"json\",\n },\n\n hooks: {\n // Claude Code uses plugin marketplace, not direct hook registration.\n // Marketplace setup is handled separately in the install command;\n // this method handles the settings.json portion only.\n events: [],\n applyInstallConfig(existing, _opts) {\n const settings = { ...existing };\n settings.extraKnownMarketplaces =\n (settings.extraKnownMarketplaces as Record<string, unknown>) ?? {};\n (settings.extraKnownMarketplaces as Record<string, unknown>)[\n \"local-plugins\"\n ] = {\n source: { source: \"directory\", path: config.marketplaceDir },\n };\n settings.enabledPlugins =\n (settings.enabledPlugins as Record<string, unknown>) ?? {};\n (settings.enabledPlugins as Record<string, unknown>)[\n \"panopticon@local-plugins\"\n ] = true;\n\n // Clean up stale hooks from older panopticon/fml installs that wrote\n // hook entries directly into settings.json. The plugin system now\n // handles hooks via hooks.json, so these are redundant and break when\n // package paths change.\n const hooks = settings.hooks as Record<string, unknown[]> | undefined;\n if (hooks) {\n for (const event of Object.keys(hooks)) {\n const entries = hooks[event];\n if (!Array.isArray(entries)) continue;\n hooks[event] = entries.filter(\n (h) =>\n !(\n typeof h === \"object\" &&\n h !== null &&\n JSON.stringify(h).includes(\"hook-handler\")\n ),\n );\n if ((hooks[event] as unknown[]).length === 0) delete hooks[event];\n }\n if (Object.keys(hooks).length === 0) delete settings.hooks;\n }\n\n return settings;\n },\n removeInstallConfig(existing) {\n const settings = { ...existing };\n const marketplaces = settings.extraKnownMarketplaces as\n | Record<string, unknown>\n | undefined;\n if (marketplaces) {\n delete marketplaces[\"local-plugins\"];\n if (Object.keys(marketplaces).length === 0)\n delete settings.extraKnownMarketplaces;\n }\n const plugins = settings.enabledPlugins as\n | Record<string, unknown>\n | undefined;\n if (plugins) {\n delete plugins[\"panopticon@local-plugins\"];\n delete plugins[\"fml@local-plugins\"];\n if (Object.keys(plugins).length === 0) delete settings.enabledPlugins;\n }\n return settings;\n },\n },\n\n shellEnv: {\n envVars(port, proxy) {\n const vars: Array<[string, string]> = [\n [\"CLAUDE_CODE_ENABLE_TELEMETRY\", \"1\"],\n ];\n if (proxy) {\n vars.push([\n \"ANTHROPIC_BASE_URL\",\n `http://localhost:${port}/proxy/anthropic`,\n ]);\n }\n return vars;\n },\n },\n\n events: {\n // Claude Code already sends canonical event names\n eventMap: {},\n formatPermissionResponse({ allow, reason }) {\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: allow ? \"allow\" : \"deny\",\n permissionDecisionReason: reason,\n },\n };\n },\n },\n\n detect: {\n displayName: \"Claude Code\",\n isInstalled: () => fs.existsSync(CLAUDE_DIR),\n isConfigured() {\n try {\n const settings = JSON.parse(\n fs.readFileSync(path.join(CLAUDE_DIR, \"settings.json\"), \"utf-8\"),\n );\n const plugins = settings.enabledPlugins ?? {};\n return (\n !!plugins[\"panopticon@local-plugins\"] ||\n !!plugins[\"fml@local-plugins\"]\n );\n } catch {\n return false;\n }\n },\n },\n\n proxy: {\n upstreamHost: \"api.anthropic.com\",\n accumulatorType: \"anthropic\",\n },\n\n otel: {\n metrics: {\n metricNames: [\"claude_code.token.usage\"],\n aggregation: \"SUM\",\n tokenTypeAttrs: [\"$.type\"],\n modelAttrs: [\"$.model\"],\n },\n },\n\n ident: {\n modelPatterns: [/^claude-/],\n },\n\n scanner: {\n normalizeToolCategory: claudeToolCategory,\n discover() {\n const projectsDir = path.join(CLAUDE_DIR, \"projects\");\n const files: { filePath: string }[] = [];\n const safeReaddir = (d: string) => {\n try {\n return fs.readdirSync(d);\n } catch {\n return [];\n }\n };\n const safeIsDir = (d: string) => {\n try {\n return fs.statSync(d).isDirectory();\n } catch {\n return false;\n }\n };\n try {\n for (const slug of fs.readdirSync(projectsDir)) {\n const slugDir = path.join(projectsDir, slug);\n if (!safeIsDir(slugDir)) continue;\n for (const entry of fs.readdirSync(slugDir)) {\n const entryPath = path.join(slugDir, entry);\n if (entry.endsWith(\".jsonl\")) {\n files.push({ filePath: entryPath });\n }\n // Recurse into session UUID directories for subagent JSONL files\n // e.g. {slug}/{uuid}/subagents/agent-*.jsonl\n if (safeIsDir(entryPath)) {\n const subagentsDir = path.join(entryPath, \"subagents\");\n for (const sub of safeReaddir(subagentsDir)) {\n if (sub.endsWith(\".jsonl\")) {\n files.push({\n filePath: path.join(subagentsDir, sub),\n });\n }\n }\n }\n }\n }\n } catch {\n /* projects dir may not exist */\n }\n return files;\n },\n\n parseFile(filePath: string, fromByteOffset: number): ParseResult | null {\n const { lines, newByteOffset } = readNewLines(filePath, fromByteOffset);\n if (lines.length === 0) return null;\n\n // For continuation detection: extract UUID from file path\n // File pattern: ~/.claude/projects/{slug}/{uuid}.jsonl\n const fileUuid = path.basename(filePath, \".jsonl\");\n const isSubagentPath = filePath.includes(\"/subagents/\");\n\n let meta: ParseResult[\"meta\"];\n const turns: ParseResult[\"turns\"] = [];\n const events: ParseResult[\"events\"] = [];\n const messages: ParseResult[\"messages\"] = [];\n let turnIndex = 0;\n let ordinal = 0;\n let firstPrompt: string | undefined;\n // Map tool_use_id → subagent session ID (e.g. \"agent-abc123\")\n const subagentMap = new Map<string, string>();\n // Tool results from filtered-out messages (tool-result-only user entries)\n const orphanedToolResults = new Map<\n string,\n { contentLength: number; contentRaw: string; timestampMs?: number }\n >();\n\n // DAG tracking: collect uuid/parentUuid entries and map them to\n // message/turn indices for fork partitioning\n const dagEntries: DagEntry[] = [];\n // Per-message and per-turn: which line index produced it (-1 = no DAG)\n const msgLineIdx: number[] = [];\n const turnLineIdx: number[] = [];\n\n for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {\n const line = lines[lineIdx];\n let obj: Record<string, unknown>;\n try {\n obj = JSON.parse(line);\n } catch {\n continue;\n }\n\n const type = obj.type as string;\n const sessionId = obj.sessionId as string | undefined;\n const agentId = obj.agentId as string | undefined;\n const tsMs = obj.timestamp\n ? new Date(obj.timestamp as string).getTime()\n : Date.now();\n\n // Track uuid/parentUuid for DAG fork detection\n const uuid = obj.uuid as string | undefined;\n const parentUuid = obj.parentUuid as string | undefined;\n if (uuid && (type === \"user\" || type === \"assistant\")) {\n dagEntries.push({\n uuid,\n parentUuid: parentUuid ?? \"\",\n type,\n lineIndex: lineIdx,\n timestampMs: tsMs,\n });\n }\n\n if (!meta && sessionId) {\n const common = {\n cliVersion: obj.version as string | undefined,\n cwd: obj.cwd as string | undefined,\n startedAtMs: tsMs,\n };\n if (agentId) {\n // Subagent files have agentId set and sessionId is the parent's ID.\n // Use \"agent-{agentId}\" as the session ID (matches file naming).\n meta = {\n sessionId: `agent-${agentId}`,\n parentSessionId: sessionId,\n relationshipType: \"subagent\",\n ...common,\n };\n } else if (\n !isSubagentPath &&\n UUID_RE.test(fileUuid) &&\n sessionId !== fileUuid\n ) {\n // Continuation: file UUID differs from JSONL sessionId.\n // This file continues the original session under a new file\n // (e.g. claude --continue / --resume).\n meta = {\n sessionId: fileUuid,\n parentSessionId: sessionId,\n relationshipType: \"continuation\",\n ...common,\n };\n } else {\n meta = { sessionId, ...common };\n }\n }\n\n const sid = meta?.sessionId ?? sessionId ?? \"\";\n\n if (type === \"user\") {\n const msg = obj.message as Record<string, unknown> | undefined;\n const content = msg?.content;\n let preview: string | undefined;\n const textParts: string[] = [];\n const toolResults = new Map<\n string,\n { contentLength: number; contentRaw: string; timestampMs?: number }\n >();\n\n if (typeof content === \"string\") {\n preview = content.slice(0, 200);\n textParts.push(content);\n } else if (Array.isArray(content)) {\n for (const block of content) {\n if (typeof block !== \"object\" || block === null) continue;\n const b = block as Record<string, unknown>;\n if (b.type === \"text\" && typeof b.text === \"string\") {\n textParts.push(b.text);\n } else if (b.type === \"tool_result\") {\n const textLen = extractToolResultTextLength(b.content);\n // Store raw content as-is: string stays string, arrays/objects get serialized\n const raw =\n typeof b.content === \"string\"\n ? b.content\n : JSON.stringify(b.content ?? \"\");\n toolResults.set(b.tool_use_id as string, {\n contentLength: textLen,\n contentRaw: raw,\n timestampMs: tsMs,\n });\n }\n }\n if (textParts.length > 0) {\n preview = textParts[0].slice(0, 200);\n }\n }\n if (!firstPrompt && preview) firstPrompt = preview;\n\n let fullContent = textParts.join(\"\\n\");\n\n // Skip meta/compact messages\n const isMeta = obj.isMeta === true;\n const isCompact = obj.isCompactSummary === true;\n if (isMeta || isCompact) {\n // still push turn below, but skip message\n } else if (fullContent.length === 0 && toolResults.size > 0) {\n // Tool-result-only user message (no text content).\n // Don't create a message — collect results for backfill.\n for (const [id, result] of toolResults) {\n orphanedToolResults.set(id, result);\n }\n } else if (fullContent.length > 0) {\n // Convert command envelopes to readable form\n const [converted, wasCommand] = extractCommandText(fullContent);\n if (wasCommand) {\n fullContent = converted;\n }\n\n // Detect system-injected messages\n const isSystem = isSystemMessage(fullContent);\n\n messages.push({\n sessionId: sid,\n ordinal: ordinal++,\n role: \"user\",\n content: fullContent,\n timestampMs: tsMs,\n hasThinking: false,\n hasToolUse: false,\n isSystem,\n contentLength: fullContent.length,\n hasContextTokens: false,\n hasOutputTokens: false,\n uuid,\n parentUuid: parentUuid ?? undefined,\n toolCalls: [],\n toolResults,\n });\n msgLineIdx.push(lineIdx);\n }\n\n turns.push({\n sessionId: sid,\n turnIndex: turnIndex++,\n timestampMs: tsMs,\n role: \"user\",\n contentPreview: preview,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheCreationTokens: 0,\n reasoningTokens: 0,\n });\n turnLineIdx.push(lineIdx);\n }\n\n if (type === \"assistant\") {\n const msg = obj.message as Record<string, unknown> | undefined;\n const usage = msg?.usage as Record<string, unknown> | undefined;\n const model = msg?.model as string | undefined;\n if (meta && model && !meta.model) meta.model = model;\n\n // Build message content and tool calls from content blocks\n const textParts: string[] = [];\n let hasThinking = false;\n let hasToolUse = false;\n const toolCalls: ParsedToolCall[] = [];\n\n const content = msg?.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (typeof block !== \"object\" || block === null) continue;\n const b = block as Record<string, unknown>;\n\n if (b.type === \"text\" && typeof b.text === \"string\") {\n textParts.push(b.text);\n }\n\n if (b.type === \"thinking\") {\n hasThinking = true;\n if (typeof b.thinking === \"string\") {\n textParts.push(`[Thinking]\\n${b.thinking}\\n[/Thinking]`);\n }\n\n events.push({\n sessionId: sid,\n eventType: \"thinking\",\n timestampMs: tsMs,\n content:\n typeof b.thinking === \"string\"\n ? b.thinking.slice(0, 2_000)\n : undefined,\n metadata: {\n has_signature: !!b.signature,\n },\n });\n }\n\n if (b.type === \"tool_use\") {\n hasToolUse = true;\n const toolName = (b.name as string) ?? \"\";\n const input = b.input as Record<string, unknown> | undefined;\n const inputJson = input ? JSON.stringify(input) : undefined;\n\n // Extract skill name for Skill tools\n let skillName: string | undefined;\n if (toolName === \"Skill\" && input) {\n skillName = (input.skill ?? input.name) as string | undefined;\n }\n\n toolCalls.push({\n toolUseId: (b.id as string) ?? \"\",\n toolName,\n category: claudeToolCategory(toolName),\n inputJson,\n skillName,\n timestampMs: tsMs,\n });\n\n events.push({\n sessionId: sid,\n eventType: \"tool_call\",\n timestampMs: tsMs,\n toolName,\n toolInput: inputJson?.slice(0, 10_000),\n metadata: { tool_use_id: b.id },\n });\n }\n }\n }\n\n const fullContent = textParts.join(\"\\n\");\n const inputTokens = (usage?.input_tokens as number) ?? 0;\n const outTokens = (usage?.output_tokens as number) ?? 0;\n const cacheRead = (usage?.cache_read_input_tokens as number) ?? 0;\n const cacheCreation =\n (usage?.cache_creation_input_tokens as number) ?? 0;\n const ctxTokens = inputTokens + cacheRead + cacheCreation;\n const hasCtx = inputTokens > 0 || cacheRead > 0 || cacheCreation > 0;\n\n messages.push({\n sessionId: sid,\n ordinal: ordinal++,\n role: \"assistant\",\n content: fullContent,\n timestampMs: tsMs,\n hasThinking,\n hasToolUse,\n isSystem: false,\n contentLength: fullContent.length,\n model,\n tokenUsage: usage ? JSON.stringify(usage) : undefined,\n contextTokens: hasCtx ? ctxTokens : undefined,\n outputTokens: outTokens > 0 ? outTokens : undefined,\n hasContextTokens: hasCtx,\n hasOutputTokens: outTokens > 0,\n uuid,\n parentUuid: parentUuid ?? undefined,\n toolCalls,\n toolResults: new Map(),\n });\n msgLineIdx.push(lineIdx);\n\n turns.push({\n sessionId: sid,\n turnIndex: turnIndex++,\n timestampMs: tsMs,\n model,\n role: \"assistant\",\n inputTokens,\n outputTokens: outTokens,\n cacheReadTokens: cacheRead,\n cacheCreationTokens: cacheCreation,\n reasoningTokens: 0,\n });\n turnLineIdx.push(lineIdx);\n }\n\n // Extract content blocks from user messages (events only — message already built above)\n if (type === \"user\") {\n const msg = obj.message as Record<string, unknown> | undefined;\n const content = msg?.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (typeof block !== \"object\" || block === null) continue;\n const b = block as Record<string, unknown>;\n\n if (b.type === \"tool_result\") {\n const resultContent = b.content;\n events.push({\n sessionId: sid,\n eventType: \"tool_result\",\n timestampMs: tsMs,\n toolOutput:\n typeof resultContent === \"string\"\n ? resultContent.slice(0, 500)\n : undefined,\n metadata: {\n tool_use_id: b.tool_use_id,\n is_error: b.is_error,\n },\n });\n }\n\n if (b.type === \"image\") {\n const src = b.source as Record<string, unknown> | undefined;\n events.push({\n sessionId: sid,\n eventType: \"image\",\n timestampMs: tsMs,\n metadata: {\n media_type: src?.media_type,\n source_type: src?.type,\n },\n });\n }\n }\n }\n }\n\n // System events\n if (type === \"system\") {\n const data = obj.data as Record<string, unknown> | undefined;\n const level = obj.level as string | undefined;\n const subtype = obj.subtype as string | undefined;\n\n if (data?.type === \"api_error\" || level === \"error\") {\n events.push({\n sessionId: sid,\n eventType: \"error\",\n timestampMs: tsMs,\n content:\n typeof data?.message === \"string\" ? data.message : undefined,\n metadata: {\n level,\n retryAttempt: data?.retryAttempt,\n maxRetries: data?.maxRetries,\n retryInMs: data?.retryInMs,\n },\n });\n } else if (\n subtype === \"stop_hook_summary\" ||\n level === \"suggestion\"\n ) {\n events.push({\n sessionId: sid,\n eventType: subtype ?? \"system\",\n timestampMs: tsMs,\n metadata: {\n subtype,\n level,\n hookCount: obj.hookCount,\n hookInfos: obj.hookInfos,\n stopReason: obj.stopReason,\n preventedContinuation: obj.preventedContinuation,\n },\n });\n }\n }\n\n // File history snapshots\n if (type === \"file-history-snapshot\") {\n const data = obj.data as Record<string, unknown> | undefined;\n const messageId = obj.messageId as string | undefined;\n events.push({\n sessionId: sid,\n eventType: \"file_snapshot\",\n timestampMs: tsMs,\n metadata: { messageId, ...(data ?? {}) },\n });\n }\n\n // Progress events\n if (type === \"progress\") {\n const data = obj.data as Record<string, unknown> | undefined;\n const hookEvent = data?.hookEvent as string | undefined;\n const progressType = data?.type as string | undefined;\n\n if (hookEvent || data?.durationMs) {\n // Hook-related progress (PreToolUse, PostToolUse, Stop, etc.)\n events.push({\n sessionId: sid,\n eventType: hookEvent ? `progress:${hookEvent}` : \"progress\",\n timestampMs: tsMs,\n toolName: (data?.hookName ?? data?.toolName) as\n | string\n | undefined,\n metadata: {\n hookEvent,\n durationMs: data?.durationMs,\n },\n });\n } else if (progressType === \"agent_progress\") {\n // Subagent activity — build tool_use_id → agent session mapping\n const tuid = (data?.parentToolUseID ?? obj.parentToolUseID) as\n | string\n | undefined;\n const agentId = (data?.agentId ?? obj.agentId) as\n | string\n | undefined;\n if (tuid && agentId) {\n subagentMap.set(tuid, `agent-${agentId}`);\n }\n events.push({\n sessionId: sid,\n eventType: \"agent_progress\",\n timestampMs: tsMs,\n metadata: {\n parentToolUseID: tuid,\n toolUseID: obj.toolUseID,\n },\n });\n }\n }\n\n // Queue operations (user prompt queue)\n if (type === \"queue-operation\") {\n const operation = obj.operation as string | undefined;\n // Extract subagent mapping from enqueue operations\n if (operation === \"enqueue\" && typeof obj.content === \"string\") {\n try {\n const qc = JSON.parse(obj.content) as Record<string, unknown>;\n const tuid = qc.tool_use_id as string | undefined;\n const taskId = qc.task_id as string | undefined;\n if (tuid && taskId) {\n subagentMap.set(tuid, `agent-${taskId}`);\n }\n } catch {\n // Try XML-style extraction as fallback\n const tuidMatch = obj.content.match(\n /<tool-use-id>([^<]+)<\\/tool-use-id>/,\n );\n const taskMatch = obj.content.match(\n /<task-id>([^<]+)<\\/task-id>/,\n );\n if (tuidMatch?.[1] && taskMatch?.[1]) {\n subagentMap.set(tuidMatch[1], `agent-${taskMatch[1]}`);\n }\n }\n }\n events.push({\n sessionId: sid,\n eventType: `queue:${operation ?? \"unknown\"}`,\n timestampMs: tsMs,\n content:\n typeof obj.content === \"string\"\n ? obj.content.slice(0, 500)\n : undefined,\n });\n }\n\n // Last prompt marker\n if (type === \"last-prompt\") {\n events.push({\n sessionId: sid,\n eventType: \"last_prompt\",\n timestampMs: tsMs,\n content:\n typeof obj.lastPrompt === \"string\"\n ? obj.lastPrompt.slice(0, 500)\n : undefined,\n });\n }\n }\n\n if (meta && firstPrompt && !meta.firstPrompt)\n meta.firstPrompt = firstPrompt;\n\n // Annotate tool calls with subagent session IDs\n if (subagentMap.size > 0) {\n for (const msg of messages) {\n for (const tc of msg.toolCalls) {\n const agentSid = subagentMap.get(tc.toolUseId);\n if (agentSid && (tc.category === \"Task\" || tc.toolName === \"Agent\"))\n tc.subagentSessionId = agentSid;\n }\n }\n }\n\n // DAG fork detection on incremental reads: if new lines contain a fork,\n // signal the caller to reset the watermark and reparse from byte 0.\n if (\n fromByteOffset > 0 &&\n dagEntries.length > 1 &&\n hasDAGFork(dagEntries)\n ) {\n return {\n meta,\n turns: [],\n events: [],\n messages: [],\n newByteOffset: fromByteOffset, // don't advance watermark\n needsFullReparse: true,\n };\n }\n\n // DAG fork detection on full file reads\n if (\n fromByteOffset === 0 &&\n meta &&\n dagEntries.length > 1 &&\n hasDAGFork(dagEntries)\n ) {\n const { mainDagIndices, forkBranches } = detectForks(\n dagEntries,\n meta.sessionId,\n );\n\n if (forkBranches.length > 0) {\n // Build line index sets for main path and each fork branch\n const mainLineSet = new Set(\n mainDagIndices.map((i) => dagEntries[i].lineIndex),\n );\n\n // Partition messages and turns by line index membership\n const mainMessages = messages.filter((_, i) =>\n mainLineSet.has(msgLineIdx[i]),\n );\n const mainTurns = turns.filter((_, i) =>\n mainLineSet.has(turnLineIdx[i]),\n );\n\n // Re-index ordinals and turn indices for main path\n for (let i = 0; i < mainMessages.length; i++) {\n mainMessages[i] = { ...mainMessages[i], ordinal: i };\n }\n for (let i = 0; i < mainTurns.length; i++) {\n mainTurns[i] = { ...mainTurns[i], turnIndex: i };\n }\n\n // Build fork ParseResults\n const forks: ParseResult[] = [];\n for (const branch of forkBranches) {\n const branchLineSet = new Set(\n branch.dagIndices.map((i) => dagEntries[i].lineIndex),\n );\n const forkUuid = dagEntries[branch.dagIndices[0]].uuid;\n const forkSessionId = `${meta.sessionId}-${forkUuid}`;\n const forkStartMs = dagEntries[branch.dagIndices[0]].timestampMs;\n\n const forkMessages = messages\n .filter((_, i) => branchLineSet.has(msgLineIdx[i]))\n .map((m, i) => ({\n ...m,\n sessionId: forkSessionId,\n ordinal: i,\n }));\n const forkTurns = turns\n .filter((_, i) => branchLineSet.has(turnLineIdx[i]))\n .map((t, i) => ({\n ...t,\n sessionId: forkSessionId,\n turnIndex: i,\n }));\n\n forks.push({\n meta: {\n sessionId: forkSessionId,\n parentSessionId: branch.parentId,\n relationshipType: \"fork\",\n model: meta.model,\n cwd: meta.cwd,\n cliVersion: meta.cliVersion,\n startedAtMs: forkStartMs,\n },\n turns: forkTurns,\n events: [], // events stay in main result\n messages: forkMessages,\n newByteOffset,\n });\n }\n\n return {\n meta,\n turns: mainTurns,\n events, // all events stay in main\n messages: mainMessages,\n newByteOffset,\n forks,\n orphanedToolResults:\n orphanedToolResults.size > 0 ? orphanedToolResults : undefined,\n };\n }\n }\n\n return {\n meta,\n turns,\n events,\n messages,\n newByteOffset,\n orphanedToolResults:\n orphanedToolResults.size > 0 ? orphanedToolResults : undefined,\n };\n },\n },\n};\n\nregisterTarget(claude);\n","/**\n * Default tool category fallback for unknown tool names.\n * Each target provides its own normalizeToolCategory with a full map;\n * this is the final fallback when no target-specific match is found.\n */\nexport function defaultToolCategory(_toolName: string): string {\n return \"\";\n}\n","import fs from \"node:fs\";\n\n/**\n * Read complete lines appended to a file since the given byte offset.\n * Incomplete trailing lines (no newline) are left for the next read.\n */\nexport function readNewLines(\n filePath: string,\n fromByteOffset: number,\n): { lines: string[]; newByteOffset: number } {\n let fd: number;\n try {\n fd = fs.openSync(filePath, \"r\");\n } catch {\n return { lines: [], newByteOffset: fromByteOffset };\n }\n\n try {\n const size = fs.fstatSync(fd).size;\n if (size <= fromByteOffset) {\n return { lines: [], newByteOffset: fromByteOffset };\n }\n\n const bytesToRead = size - fromByteOffset;\n const buf = Buffer.alloc(bytesToRead);\n fs.readSync(fd, buf, 0, bytesToRead, fromByteOffset);\n\n const text = buf.toString(\"utf-8\");\n const lastNewline = text.lastIndexOf(\"\\n\");\n if (lastNewline === -1) {\n // No complete line yet\n return { lines: [], newByteOffset: fromByteOffset };\n }\n\n const complete = text.slice(0, lastNewline);\n const lines = complete.split(\"\\n\").filter((l) => l.length > 0);\n const bytesConsumed = Buffer.byteLength(\n text.slice(0, lastNewline + 1),\n \"utf-8\",\n );\n\n return {\n lines,\n newByteOffset: fromByteOffset + bytesConsumed,\n };\n } finally {\n fs.closeSync(fd);\n }\n}\n","import fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type { HookInput } from \"../hooks/ingest.js\";\nimport { defaultToolCategory } from \"../scanner/categories.js\";\nimport { registerTarget } from \"./registry.js\";\nimport type { ParsedToolCall, ParseResult, TargetAdapter } from \"./types.js\";\n\nconst GEMINI_TOOL_CATEGORIES: Record<string, string> = {\n read_file: \"Read\",\n read_many_files: \"Read\",\n write_file: \"Write\",\n edit_file: \"Edit\",\n run_shell_command: \"Bash\",\n shell: \"Bash\",\n glob: \"Glob\",\n list_directory: \"Glob\",\n grep: \"Grep\",\n search_files: \"Grep\",\n web_search: \"Web\",\n};\n\nfunction geminiToolCategory(toolName: string): string {\n return GEMINI_TOOL_CATEGORIES[toolName] ?? defaultToolCategory(toolName);\n}\n\nconst GEMINI_DIR = path.join(os.homedir(), \".gemini\");\n\nconst gemini: TargetAdapter = {\n id: \"gemini\",\n\n config: {\n dir: GEMINI_DIR,\n configPath: path.join(GEMINI_DIR, \"settings.json\"),\n configFormat: \"json\",\n },\n\n hooks: {\n // Gemini's native event names\n events: [\"SessionStart\", \"BeforeModel\", \"BeforeTool\", \"AfterTool\"],\n applyInstallConfig(existing, opts) {\n const settings = { ...existing };\n const hookBin = path.join(opts.pluginRoot, \"bin\", \"hook-handler\");\n const mcpBin = path.join(opts.pluginRoot, \"bin\", \"mcp-server\");\n\n // Deep-copy hooks to avoid mutating the input\n const hooks = structuredClone(\n (settings.hooks as Record<string, unknown[]>) ?? {},\n );\n for (const event of this.events) {\n hooks[event] = hooks[event] || [];\n // Remove existing panopticon hooks (check both old and new format)\n hooks[event] = (hooks[event] as Array<Record<string, unknown>>)\n .map((group) => ({\n ...group,\n hooks: (\n (group.hooks as Array<Record<string, unknown>>) || []\n ).filter(\n (h) =>\n !(h.command as string)?.includes(\"panopticon\") &&\n !(h.path as string)?.includes(\"panopticon\"),\n ),\n }))\n .filter((group) => (group.hooks as unknown[]).length > 0);\n // Add panopticon hook\n (hooks[event] as unknown[]).push({\n hooks: [\n {\n type: \"command\",\n command: `node ${hookBin} gemini ${opts.port}${opts.proxy ? \" --proxy\" : \"\"}`,\n },\n ],\n });\n }\n settings.hooks = hooks;\n\n // Register MCP server\n settings.mcpServers =\n (settings.mcpServers as Record<string, unknown>) || {};\n (settings.mcpServers as Record<string, unknown>).panopticon = {\n command: \"node\",\n args: [mcpBin],\n };\n\n // Configure telemetry\n settings.telemetry =\n (settings.telemetry as Record<string, unknown>) || {};\n Object.assign(settings.telemetry as Record<string, unknown>, {\n enabled: true,\n target: \"local\",\n otlpProtocol: \"http\",\n otlpEndpoint: `http://localhost:${opts.port}`,\n });\n\n return settings;\n },\n removeInstallConfig(existing) {\n const settings = { ...existing };\n\n // Remove panopticon hooks from each event (check both old and new format)\n const hooks = settings.hooks as Record<string, unknown[]> | undefined;\n if (hooks) {\n for (const event of Object.keys(hooks)) {\n hooks[event] = (hooks[event] as Array<Record<string, unknown>>)\n .map((group) => ({\n ...group,\n hooks: (\n (group.hooks as Array<Record<string, unknown>>) || []\n ).filter(\n (h) =>\n !(h.command as string)?.includes(\"panopticon\") &&\n !(h.path as string)?.includes(\"panopticon\"),\n ),\n }))\n .filter((group) => (group.hooks as unknown[]).length > 0);\n if ((hooks[event] as unknown[]).length === 0) delete hooks[event];\n }\n if (Object.keys(hooks).length === 0) delete settings.hooks;\n }\n\n // Remove hooksConfig if empty\n delete settings.hooksConfig;\n\n // Remove MCP server\n const servers = settings.mcpServers as\n | Record<string, unknown>\n | undefined;\n if (servers) {\n delete servers.panopticon;\n if (Object.keys(servers).length === 0) delete settings.mcpServers;\n }\n\n // Remove telemetry config\n delete settings.telemetry;\n\n return settings;\n },\n },\n\n shellEnv: {\n envVars(port) {\n return [\n [\"GEMINI_TELEMETRY_ENABLED\", \"true\"],\n [\"GEMINI_TELEMETRY_TARGET\", \"local\"],\n [\"GEMINI_TELEMETRY_USE_COLLECTOR\", \"true\"],\n [\"GEMINI_TELEMETRY_OTLP_ENDPOINT\", `http://localhost:${port}`],\n [\"GEMINI_TELEMETRY_OTLP_PROTOCOL\", \"http\"],\n [\"GEMINI_TELEMETRY_LOG_PROMPTS\", \"true\"],\n ];\n },\n },\n\n events: {\n eventMap: {\n BeforeTool: \"PreToolUse\",\n AfterTool: \"PostToolUse\",\n BeforeModel: \"UserPromptSubmit\",\n },\n\n normalizePayload(data: HookInput): HookInput {\n // Extract user_prompt from Gemini's llm_request.messages format\n const messages = (data as Record<string, unknown>).llm_request as\n | { messages?: Array<{ role: string; content: unknown }> }\n | undefined;\n if (messages?.messages && Array.isArray(messages.messages)) {\n const lastUser = [...messages.messages]\n .reverse()\n .find((m) => m.role === \"user\");\n if (lastUser?.content) {\n const text =\n typeof lastUser.content === \"string\"\n ? lastUser.content\n : Array.isArray(lastUser.content)\n ? (lastUser.content as Array<{ type: string; text?: string }>)\n .filter((p) => p.type === \"text\")\n .map((p) => p.text)\n .join(\"\\n\")\n : \"\";\n if (text) {\n (data as Record<string, unknown>).user_prompt = text;\n }\n }\n }\n return data;\n },\n\n formatPermissionResponse({ allow, reason }) {\n return { decision: allow ? \"allow\" : \"deny\", reason };\n },\n },\n\n detect: {\n displayName: \"Gemini CLI\",\n isInstalled: () => fs.existsSync(GEMINI_DIR),\n isConfigured() {\n try {\n const settings = JSON.parse(\n fs.readFileSync(path.join(GEMINI_DIR, \"settings.json\"), \"utf-8\"),\n );\n return !!settings.telemetry?.enabled;\n } catch {\n return false;\n }\n },\n },\n\n proxy: {\n upstreamHost: \"generativelanguage.googleapis.com\",\n accumulatorType: \"openai\",\n },\n\n otel: {\n serviceName: \"gemini-cli\",\n metrics: {\n metricNames: [\"gemini_cli.token.usage\", \"gen_ai.client.token.usage\"],\n aggregation: \"MAX\",\n tokenTypeAttrs: ['$.\"gen_ai.token.type\"'],\n modelAttrs: ['$.\"gen_ai.response.model\"'],\n },\n },\n\n scanner: {\n normalizeToolCategory: geminiToolCategory,\n discover() {\n const tmpDir = path.join(GEMINI_DIR, \"tmp\");\n const files: { filePath: string }[] = [];\n const safeReaddir = (d: string) => {\n try {\n return fs.readdirSync(d);\n } catch {\n return [];\n }\n };\n for (const project of safeReaddir(tmpDir)) {\n const chatsDir = path.join(tmpDir, project, \"chats\");\n for (const entry of safeReaddir(chatsDir)) {\n if (entry.startsWith(\"session-\") && entry.endsWith(\".json\"))\n files.push({ filePath: path.join(chatsDir, entry) });\n }\n }\n return files;\n },\n\n parseFile(filePath: string, fromByteOffset: number): ParseResult | null {\n let size: number;\n try {\n size = fs.statSync(filePath).size;\n } catch {\n return null;\n }\n if (size <= fromByteOffset) return null;\n\n // Gemini writes a single JSON object, re-read fully when changed.\n let session: Record<string, unknown>;\n try {\n session = JSON.parse(fs.readFileSync(filePath, \"utf-8\"));\n } catch {\n return null;\n }\n\n const sessionId = session.sessionId as string | undefined;\n if (!sessionId) return null;\n\n const messages = session.messages as\n | Array<Record<string, unknown>>\n | undefined;\n if (!messages?.length) return null;\n\n const meta: ParseResult[\"meta\"] = {\n sessionId,\n startedAtMs: session.startTime\n ? new Date(session.startTime as string).getTime()\n : undefined,\n };\n\n const turns: ParseResult[\"turns\"] = [];\n const events: ParseResult[\"events\"] = [];\n const parsedMessages: ParseResult[\"messages\"] = [];\n let turnIndex = 0;\n let ordinal = 0;\n let firstPrompt: string | undefined;\n\n for (const msg of messages) {\n const type = msg.type as string;\n const timestamp = msg.timestamp as string | undefined;\n const timestampMs = timestamp\n ? new Date(timestamp).getTime()\n : Date.now();\n\n if (type === \"user\") {\n const content = msg.content as Array<{ text?: string }> | undefined;\n const textParts: string[] = [];\n if (content) {\n for (const block of content) {\n if (block.text) textParts.push(block.text);\n }\n }\n const fullContent = textParts.join(\"\\n\");\n const preview = textParts[0]?.slice(0, 200);\n if (!firstPrompt && preview) firstPrompt = preview;\n\n turns.push({\n sessionId,\n turnIndex: turnIndex++,\n timestampMs,\n role: \"user\",\n contentPreview: preview,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheCreationTokens: 0,\n reasoningTokens: 0,\n });\n\n if (fullContent.length > 0) {\n parsedMessages.push({\n sessionId,\n ordinal: ordinal++,\n role: \"user\",\n content: fullContent,\n timestampMs,\n hasThinking: false,\n hasToolUse: false,\n isSystem: false,\n contentLength: fullContent.length,\n hasContextTokens: false,\n hasOutputTokens: false,\n toolCalls: [],\n toolResults: new Map(),\n });\n }\n }\n\n if (type === \"gemini\") {\n const model = msg.model as string | undefined;\n const tokens = msg.tokens as Record<string, number> | undefined;\n if (model && !meta.model) meta.model = model;\n\n const inputTokens = tokens?.input ?? 0;\n const outTokens = tokens?.output ?? 0;\n const cacheRead = tokens?.cached ?? 0;\n const reasoning = tokens?.thoughts ?? 0;\n\n turns.push({\n sessionId,\n turnIndex: turnIndex++,\n timestampMs,\n model,\n role: \"assistant\",\n contentPreview:\n typeof msg.content === \"string\"\n ? msg.content.slice(0, 200)\n : undefined,\n inputTokens,\n outputTokens: outTokens,\n cacheReadTokens: cacheRead,\n cacheCreationTokens: 0,\n reasoningTokens: reasoning,\n });\n\n // Build message content\n const textParts: string[] = [];\n if (typeof msg.content === \"string\") textParts.push(msg.content);\n\n // Tool calls\n const msgToolCalls = msg.toolCalls as\n | Array<Record<string, unknown>>\n | undefined;\n const toolCalls: ParsedToolCall[] = [];\n const toolResults = new Map<\n string,\n { contentLength: number; contentRaw: string }\n >();\n\n if (msgToolCalls) {\n for (let tcIdx = 0; tcIdx < msgToolCalls.length; tcIdx++) {\n const tc = msgToolCalls[tcIdx];\n const toolName = ((tc.name ?? tc.displayName) as string) ?? \"\";\n const inputJson = tc.args ? JSON.stringify(tc.args) : undefined;\n const toolUseId =\n (tc.id as string) || `${toolName}-${timestampMs}-${tcIdx}`;\n\n toolCalls.push({\n toolUseId,\n toolName,\n category: geminiToolCategory(toolName),\n inputJson,\n });\n\n const result = tc.result as\n | Array<Record<string, unknown>>\n | undefined;\n const fnResponse = result?.[0]?.functionResponse as\n | Record<string, unknown>\n | undefined;\n const responseObj = fnResponse?.response as\n | Record<string, unknown>\n | undefined;\n const output =\n typeof responseObj?.output === \"string\"\n ? responseObj.output\n : fnResponse\n ? JSON.stringify(fnResponse)\n : undefined;\n if (output) {\n toolResults.set(toolUseId, {\n contentLength: output.length,\n contentRaw: output,\n });\n }\n\n events.push({\n sessionId,\n eventType: \"tool_call\",\n timestampMs,\n toolName,\n toolInput: inputJson?.slice(0, 1000),\n toolOutput: output?.slice(0, 1000),\n });\n }\n }\n\n // Thoughts/reasoning\n let hasThinking = false;\n const thoughts = msg.thoughts as\n | Array<Record<string, unknown>>\n | undefined;\n if (thoughts) {\n hasThinking = true;\n for (const t of thoughts) {\n const text = (t.description ?? t.subject) as string | undefined;\n if (text) textParts.push(`[Thinking]\\n${text}\\n[/Thinking]`);\n events.push({\n sessionId,\n eventType: \"reasoning\",\n timestampMs,\n content: text?.slice(0, 500),\n });\n }\n }\n\n const fullContent = textParts.join(\"\\n\");\n const ctxTokens = inputTokens + cacheRead;\n const hasCtx = inputTokens > 0 || cacheRead > 0;\n\n parsedMessages.push({\n sessionId,\n ordinal: ordinal++,\n role: \"assistant\",\n content: fullContent,\n timestampMs,\n hasThinking,\n hasToolUse: toolCalls.length > 0,\n isSystem: false,\n contentLength: fullContent.length,\n model,\n tokenUsage: tokens ? JSON.stringify(tokens) : undefined,\n contextTokens: hasCtx ? ctxTokens : undefined,\n outputTokens: outTokens > 0 ? outTokens : undefined,\n hasContextTokens: hasCtx,\n hasOutputTokens: outTokens > 0,\n toolCalls,\n toolResults,\n });\n }\n\n if (type === \"info\") {\n events.push({\n sessionId,\n eventType: \"info\",\n timestampMs,\n content:\n typeof msg.content === \"string\"\n ? msg.content.slice(0, 500)\n : undefined,\n });\n }\n }\n\n if (firstPrompt) meta.firstPrompt = firstPrompt;\n return {\n meta,\n turns,\n events,\n messages: parsedMessages,\n newByteOffset: size,\n absoluteIndices: true,\n };\n },\n },\n};\n\nregisterTarget(gemini);\n","import fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { defaultToolCategory } from \"../scanner/categories.js\";\nimport { readNewLines } from \"../scanner/reader.js\";\nimport { registerTarget } from \"./registry.js\";\nimport type { ParsedToolCall, ParseResult, TargetAdapter } from \"./types.js\";\n\nconst CODEX_TOOL_CATEGORIES: Record<string, string> = {\n shell_command: \"Bash\",\n shell: \"Bash\",\n exec_command: \"Bash\",\n write_stdin: \"Bash\",\n run_command: \"Bash\",\n read_file: \"Read\",\n write_file: \"Write\",\n create_file: \"Write\",\n edit_file: \"Edit\",\n list_dir: \"Glob\",\n grep_search: \"Grep\",\n finder: \"Grep\",\n spawn_agent: \"Task\",\n};\n\nfunction codexToolCategory(toolName: string): string {\n return CODEX_TOOL_CATEGORIES[toolName] ?? defaultToolCategory(toolName);\n}\n\nconst CODEX_SYSTEM_PREFIXES = [\n \"# AGENTS.md\",\n \"<environment_context>\",\n \"<INSTRUCTIONS>\",\n \"<subagent_notification>\",\n];\n\nfunction isCodexSystemMessage(content: string): boolean {\n return CODEX_SYSTEM_PREFIXES.some((p) => content.startsWith(p));\n}\n\nconst CODEX_DIR = path.join(os.homedir(), \".codex\");\nconst CODEX_HOOKS_JSON = path.join(CODEX_DIR, \"hooks.json\");\n\n// PascalCase event names matching the Codex hooks engine schema\nconst HOOK_EVENTS = [\n \"SessionStart\",\n \"UserPromptSubmit\",\n \"PreToolUse\",\n \"PostToolUse\",\n \"Stop\",\n];\n\nfunction readHooksJson(): Record<string, unknown> {\n try {\n return JSON.parse(fs.readFileSync(CODEX_HOOKS_JSON, \"utf-8\"));\n } catch {\n return {};\n }\n}\n\nfunction writeHooksJson(data: Record<string, unknown>): void {\n fs.mkdirSync(CODEX_DIR, { recursive: true });\n fs.writeFileSync(CODEX_HOOKS_JSON, `${JSON.stringify(data, null, 2)}\\n`);\n}\n\nconst codex: TargetAdapter = {\n id: \"codex\",\n\n config: {\n dir: CODEX_DIR,\n configPath: path.join(CODEX_DIR, \"config.toml\"),\n configFormat: \"toml\",\n },\n\n hooks: {\n events: HOOK_EVENTS,\n applyInstallConfig(existing, opts) {\n const codexConfig = { ...existing };\n const hookBin = path.join(opts.pluginRoot, \"bin\", \"hook-handler\");\n const mcpBin = path.join(opts.pluginRoot, \"bin\", \"mcp-server\");\n\n // Enable the codex_hooks feature flag\n codexConfig.features =\n (codexConfig.features as Record<string, unknown>) ?? {};\n (codexConfig.features as Record<string, unknown>).codex_hooks = true;\n // Codex reads this at top level, not under [features]\n codexConfig.suppress_unstable_features_warning = true;\n\n // Remove any legacy TOML hook entries\n delete codexConfig.hooks;\n\n // Write hooks.json (the format the new hooks engine expects)\n const hooksFile = readHooksJson();\n const hooks = (hooksFile.hooks as Record<string, unknown[]>) ?? {};\n const proxyFlag = opts.proxy ? \" --proxy\" : \"\";\n const command = `node ${hookBin} codex ${opts.port}${proxyFlag}`;\n for (const event of HOOK_EVENTS) {\n const groups = (hooks[event] ?? []) as Array<Record<string, unknown>>;\n // Remove existing panopticon groups\n hooks[event] = groups.filter((g) => {\n const h = ((g.hooks as unknown[]) ?? []) as Array<\n Record<string, unknown>\n >;\n return !h.some((entry) =>\n (entry.command as string)?.includes(\"panopticon\"),\n );\n });\n // Add panopticon hook group\n (hooks[event] as unknown[]).push({\n hooks: [{ type: \"command\", command, timeout: 10 }],\n });\n }\n hooksFile.hooks = hooks;\n writeHooksJson(hooksFile);\n\n // Configure API proxy (opt-in via --proxy)\n if (opts.proxy) {\n codexConfig.openai_base_url = `http://localhost:${opts.port}/proxy/codex`;\n } else {\n delete codexConfig.openai_base_url;\n }\n\n // Configure OTel telemetry\n delete codexConfig.telemetry; // Remove legacy format\n const endpoint = `http://localhost:${opts.port}`;\n const exporterConfig = { endpoint, protocol: \"binary\" };\n codexConfig.otel = {\n ...((codexConfig.otel as Record<string, unknown>) ?? {}),\n log_user_prompt: true,\n exporter: { \"otlp-http\": exporterConfig },\n trace_exporter: { \"otlp-http\": exporterConfig },\n metrics_exporter: { \"otlp-http\": exporterConfig },\n };\n\n // Register MCP server\n const mcpServers = (codexConfig.mcp_servers ?? {}) as Record<\n string,\n unknown\n >;\n mcpServers.panopticon = { command: \"node\", args: [mcpBin] };\n codexConfig.mcp_servers = mcpServers;\n\n return codexConfig;\n },\n removeInstallConfig(existing) {\n const cfg = { ...existing };\n\n // Remove feature flags\n const features = cfg.features as Record<string, unknown> | undefined;\n if (features) {\n delete features.codex_hooks;\n if (Object.keys(features).length === 0) delete cfg.features;\n }\n delete cfg.suppress_unstable_features_warning;\n\n // Remove any legacy TOML hook entries\n delete cfg.hooks;\n\n // Remove panopticon hooks from hooks.json\n const hooksFile = readHooksJson();\n const hooks = hooksFile.hooks as Record<string, unknown[]> | undefined;\n if (hooks) {\n for (const event of Object.keys(hooks)) {\n hooks[event] = (\n hooks[event] as Array<Record<string, unknown>>\n ).filter((g) => {\n const h = ((g.hooks as unknown[]) ?? []) as Array<\n Record<string, unknown>\n >;\n return !h.some((entry) =>\n (entry.command as string)?.includes(\"panopticon\"),\n );\n });\n if ((hooks[event] as unknown[]).length === 0) delete hooks[event];\n }\n if (Object.keys(hooks).length === 0) {\n // Remove hooks.json entirely if empty\n try {\n fs.unlinkSync(CODEX_HOOKS_JSON);\n } catch {}\n } else {\n hooksFile.hooks = hooks;\n writeHooksJson(hooksFile);\n }\n }\n\n // Remove proxy URL if it points to panopticon\n if (\n typeof cfg.openai_base_url === \"string\" &&\n cfg.openai_base_url.includes(\"panopticon\")\n ) {\n delete cfg.openai_base_url;\n }\n\n // Remove OTel config\n delete cfg.telemetry; // Legacy format\n const otel = cfg.otel as Record<string, unknown> | undefined;\n if (otel) {\n delete otel.log_user_prompt;\n delete otel.exporter;\n delete otel.trace_exporter;\n delete otel.metrics_exporter;\n if (Object.keys(otel).length === 0) delete cfg.otel;\n }\n\n // Remove MCP server\n const servers = cfg.mcp_servers as Record<string, unknown> | undefined;\n if (servers) {\n delete servers.panopticon;\n if (Object.keys(servers).length === 0) delete cfg.mcp_servers;\n }\n\n return cfg;\n },\n },\n\n shellEnv: {\n // Codex CLI reads its config from TOML, no shell env vars needed\n envVars() {\n return [];\n },\n },\n\n events: {\n // Codex uses snake_case but the hook handler already accepts both cases;\n // no mapping needed since ingest.ts normalizes at storage time\n eventMap: {},\n formatPermissionResponse({ allow, reason }) {\n // Codex uses the same format as Claude Code\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: allow ? \"allow\" : \"deny\",\n permissionDecisionReason: reason,\n },\n };\n },\n },\n\n detect: {\n displayName: \"Codex CLI\",\n isInstalled: () => fs.existsSync(CODEX_DIR),\n isConfigured() {\n try {\n const content = fs.readFileSync(CODEX_HOOKS_JSON, \"utf-8\");\n return content.includes(\"panopticon\");\n } catch {\n return false;\n }\n },\n },\n\n otel: {\n serviceName: \"codex_cli_rs\",\n metrics: {\n metricNames: [\"codex.turn.token_usage\"],\n aggregation: \"SUM\",\n tokenTypeAttrs: [\"$.token_type\"],\n modelAttrs: [\"$.model\"],\n tokenTypeMap: {\n cached_input: \"cacheRead\",\n reasoning_output: \"output\",\n },\n excludeTokenTypes: [\"total\"],\n },\n logFields: {\n eventTypeExprs: [\"body\", `json_extract(attributes, '$.\"event.name\"')`],\n timestampMsExprs: [\n \"CAST(timestamp_ns / 1000000 AS INTEGER)\",\n `CAST(strftime('%s', json_extract(attributes, '$.\"event.timestamp\"')) AS INTEGER) * 1000`,\n ],\n },\n },\n\n ident: {\n modelPatterns: [/^(gpt-|o[1-9]|chatgpt-)/],\n },\n\n proxy: {\n upstreamHost(headers) {\n // Codex auto-detect: ChatGPT OAuth (JWT) vs API key route to different upstreams\n const auth = headers.authorization ?? \"\";\n return auth.startsWith(\"Bearer eyJ\") ? \"chatgpt.com\" : \"api.openai.com\";\n },\n rewritePath(requestPath, headers) {\n const auth = headers.authorization ?? \"\";\n const isChatGptOAuth = auth.startsWith(\"Bearer eyJ\");\n return isChatGptOAuth\n ? `/backend-api/codex${requestPath}`\n : `/v1${requestPath}`;\n },\n accumulatorType: \"openai\",\n },\n\n scanner: {\n normalizeToolCategory: codexToolCategory,\n discover() {\n const sessionsDir = path.join(CODEX_DIR, \"sessions\");\n const files: { filePath: string }[] = [];\n const safeReaddir = (d: string) => {\n try {\n return fs.readdirSync(d);\n } catch {\n return [];\n }\n };\n for (const year of safeReaddir(sessionsDir)) {\n for (const month of safeReaddir(path.join(sessionsDir, year))) {\n for (const day of safeReaddir(path.join(sessionsDir, year, month))) {\n const dayDir = path.join(sessionsDir, year, month, day);\n for (const entry of safeReaddir(dayDir)) {\n if (entry.endsWith(\".jsonl\"))\n files.push({ filePath: path.join(dayDir, entry) });\n }\n }\n }\n }\n return files;\n },\n\n parseFile(filePath: string, fromByteOffset: number): ParseResult | null {\n const { lines, newByteOffset } = readNewLines(filePath, fromByteOffset);\n if (lines.length === 0) return null;\n\n let meta: ParseResult[\"meta\"];\n const turns: ParseResult[\"turns\"] = [];\n const events: ParseResult[\"events\"] = [];\n const messages: ParseResult[\"messages\"] = [];\n let turnIndex = 0;\n let ordinal = 0;\n let currentModel: string | undefined;\n let firstPrompt: string | undefined;\n\n // Collect tool calls and results keyed by call_id to match them up\n let pendingToolCalls: ParsedToolCall[] = [];\n let pendingAssistantContent = \"\";\n const toolResultsByCallId = new Map<\n string,\n { contentLength: number; contentRaw: string }\n >();\n\n for (const line of lines) {\n let obj: Record<string, unknown>;\n try {\n obj = JSON.parse(line);\n } catch {\n continue;\n }\n\n const type = obj.type as string;\n const timestamp = obj.timestamp as string | undefined;\n const tsMs = timestamp ? new Date(timestamp).getTime() : Date.now();\n const payload = obj.payload as Record<string, unknown> | undefined;\n\n if (type === \"session_meta\" && payload) {\n meta = {\n sessionId: payload.id as string,\n cwd: payload.cwd as string | undefined,\n cliVersion: payload.cli_version as string | undefined,\n startedAtMs: payload.timestamp\n ? new Date(payload.timestamp as string).getTime()\n : tsMs,\n };\n }\n\n if (type === \"turn_context\" && payload) {\n currentModel = payload.model as string | undefined;\n if (meta && currentModel && !meta.model) meta.model = currentModel;\n }\n\n const sid = meta?.sessionId ?? \"\";\n\n if (type === \"event_msg\" && payload) {\n const eventType = payload.type as string;\n\n if (eventType === \"user_message\") {\n const message = payload.message as string | undefined;\n if (!firstPrompt && message) firstPrompt = message.slice(0, 200);\n turns.push({\n sessionId: sid,\n turnIndex: turnIndex++,\n timestampMs: tsMs,\n model: currentModel,\n role: \"user\",\n contentPreview: message?.slice(0, 200),\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheCreationTokens: 0,\n reasoningTokens: 0,\n });\n\n if (message && !isCodexSystemMessage(message)) {\n messages.push({\n sessionId: sid,\n ordinal: ordinal++,\n role: \"user\",\n content: message,\n timestampMs: tsMs,\n hasThinking: false,\n hasToolUse: false,\n isSystem: false,\n contentLength: message.length,\n hasContextTokens: false,\n hasOutputTokens: false,\n toolCalls: [],\n toolResults: new Map(),\n });\n }\n }\n\n if (eventType === \"token_count\") {\n const info = payload.info as Record<string, unknown> | null;\n if (!info) continue;\n const lastUsage = info.last_token_usage as\n | Record<string, unknown>\n | undefined;\n if (!lastUsage) continue;\n\n const inputTokens = (lastUsage.input_tokens as number) ?? 0;\n const outTokens = (lastUsage.output_tokens as number) ?? 0;\n const cacheRead = (lastUsage.cached_input_tokens as number) ?? 0;\n const reasoning =\n (lastUsage.reasoning_output_tokens as number) ?? 0;\n const ctxTokens = inputTokens + cacheRead;\n const hasCtx = inputTokens > 0 || cacheRead > 0;\n\n turns.push({\n sessionId: sid,\n turnIndex: turnIndex++,\n timestampMs: tsMs,\n model: currentModel,\n role: \"assistant\",\n inputTokens,\n outputTokens: outTokens,\n cacheReadTokens: cacheRead,\n cacheCreationTokens: 0,\n reasoningTokens: reasoning,\n });\n\n // Flush accumulated tool calls into an assistant message\n const toolResults = new Map<\n string,\n { contentLength: number; contentRaw: string }\n >();\n for (const tc of pendingToolCalls) {\n const result = toolResultsByCallId.get(tc.toolUseId);\n if (result) toolResults.set(tc.toolUseId, result);\n }\n\n messages.push({\n sessionId: sid,\n ordinal: ordinal++,\n role: \"assistant\",\n content: pendingAssistantContent,\n timestampMs: tsMs,\n hasThinking: false,\n hasToolUse: pendingToolCalls.length > 0,\n isSystem: false,\n contentLength: pendingAssistantContent.length,\n model: currentModel,\n tokenUsage: JSON.stringify(lastUsage),\n contextTokens: hasCtx ? ctxTokens : undefined,\n outputTokens: outTokens > 0 ? outTokens : undefined,\n hasContextTokens: hasCtx,\n hasOutputTokens: outTokens > 0,\n toolCalls: pendingToolCalls,\n toolResults,\n });\n pendingToolCalls = [];\n pendingAssistantContent = \"\";\n }\n\n if (eventType === \"agent_message\") {\n events.push({\n sessionId: sid,\n eventType: \"agent_message\",\n timestampMs: tsMs,\n content: (payload.message as string)?.slice(0, 500),\n metadata: { phase: payload.phase },\n });\n }\n }\n\n // Tool calls and content from response_item\n if (type === \"response_item\") {\n const p = obj.payload as Record<string, unknown> | undefined;\n const itemType = p?.type as string | undefined;\n\n // Capture assistant text content from message/text items\n if (itemType === \"message\" || itemType === \"text\") {\n const text =\n (p?.text as string) ??\n (p?.content as string) ??\n (p?.output_text as string);\n if (text) {\n pendingAssistantContent +=\n (pendingAssistantContent ? \"\\n\" : \"\") + text;\n }\n }\n\n if (itemType === \"function_call\") {\n const toolName = (p?.name as string) ?? \"\";\n const callId = (p?.call_id as string) ?? \"\";\n const inputJson =\n typeof p?.arguments === \"string\"\n ? p.arguments\n : JSON.stringify(p?.arguments);\n\n pendingToolCalls.push({\n toolUseId: callId,\n toolName,\n category: codexToolCategory(toolName),\n inputJson,\n });\n\n events.push({\n sessionId: sid,\n eventType: \"tool_call\",\n timestampMs: tsMs,\n toolName,\n toolInput: inputJson?.slice(0, 1000),\n metadata: { call_id: callId },\n });\n }\n\n if (itemType === \"function_call_output\") {\n const callId = (p?.call_id as string) ?? \"\";\n const output = typeof p?.output === \"string\" ? p.output : undefined;\n\n if (output) {\n toolResultsByCallId.set(callId, {\n contentLength: output.length,\n contentRaw: output,\n });\n }\n\n events.push({\n sessionId: sid,\n eventType: \"tool_result\",\n timestampMs: tsMs,\n toolOutput: output?.slice(0, 1000),\n metadata: { call_id: callId },\n });\n }\n }\n }\n\n if (meta && firstPrompt && !meta.firstPrompt)\n meta.firstPrompt = firstPrompt;\n return { meta, turns, events, messages, newByteOffset };\n },\n },\n};\n\nregisterTarget(codex);\n","import { allTargets } from \"../targets/index.js\";\nimport type {\n ActivitySessionDetail,\n ActivitySummaryResult,\n ChildSession,\n SearchMatch,\n SearchResult,\n Session,\n SessionListResult,\n SessionTimelineResult,\n SpendingGroup,\n SpendingResult,\n TimelineMessage,\n TimelineToolCall,\n} from \"../types.js\";\nimport { getDb } from \"./schema.js\";\n\nfunction parseSince(since?: string): number | null {\n if (!since) return null;\n const match = since.match(/^(\\d+)(h|d|m)$/);\n if (match) {\n const [, num, unit] = match;\n const ms = unit === \"h\" ? 3600000 : unit === \"d\" ? 86400000 : 60000;\n return Date.now() - parseInt(num, 10) * ms;\n }\n const date = new Date(since);\n return Number.isNaN(date.getTime()) ? null : date.getTime();\n}\n\nfunction toIso(ms: number): string {\n return new Date(ms).toISOString();\n}\n\n// ── OTel log SQL helpers (adapter-driven) ────────────────────────────────────\n\n/** Build COALESCE/CASE expressions for OTel log event type and timestamp from adapter specs. */\nfunction buildOtelLogExprs(): { eventType: string; timestampMs: string } {\n const defaultTs = \"CAST(timestamp_ns / 1000000 AS INTEGER)\";\n const eventExprs = new Set<string>([\"body\"]);\n const extraTsExprs = new Set<string>();\n\n for (const target of allTargets()) {\n const lf = target.otel?.logFields;\n if (!lf) continue;\n for (const expr of lf.eventTypeExprs ?? []) {\n eventExprs.add(expr);\n }\n for (const expr of lf.timestampMsExprs ?? []) {\n if (expr !== defaultTs) extraTsExprs.add(expr);\n }\n }\n\n const eventExprArr = [...eventExprs];\n const eventType =\n eventExprArr.length === 1\n ? eventExprArr[0]\n : `COALESCE(${eventExprArr.join(\", \")})`;\n\n let timestampMs = defaultTs;\n if (extraTsExprs.size > 0) {\n const fallbackArr = [...extraTsExprs];\n const fallbackExpr =\n fallbackArr.length === 1\n ? fallbackArr[0]\n : `COALESCE(${fallbackArr.join(\", \")})`;\n timestampMs = `CASE WHEN timestamp_ns > 0 THEN ${defaultTs} ELSE ${fallbackExpr} END`;\n }\n\n return { eventType, timestampMs };\n}\n\nlet _otelLogExprs: { eventType: string; timestampMs: string } | null = null;\nfunction otelLogExprs() {\n if (!_otelLogExprs) _otelLogExprs = buildOtelLogExprs();\n return _otelLogExprs;\n}\n\n// ── Sessions ──────────────────────────────────────────────────────────────────\n\ninterface RawRepoRow {\n session_id: string;\n repository: string;\n git_user_name: string | null;\n git_user_email: string | null;\n}\n\n/** Cost SQL fragment for a session row with model + token columns. */\nconst SESSION_COST_SQL = `\n COALESCE((\n SELECT s.total_input_tokens * COALESCE(mp.input_per_m, 0) / 1000000.0\n + s.total_output_tokens * COALESCE(mp.output_per_m, 0) / 1000000.0\n + s.total_cache_read_tokens * COALESCE(mp.cache_read_per_m, 0) / 1000000.0\n + s.total_cache_creation_tokens * COALESCE(mp.cache_write_per_m, 0) / 1000000.0\n FROM model_pricing mp\n WHERE s.model LIKE mp.model_id || '%'\n ORDER BY LENGTH(mp.model_id) DESC, mp.updated_ms DESC\n LIMIT 1\n ), 0)`;\n\nexport function listSessions(\n opts: { limit?: number; since?: string } = {},\n): SessionListResult {\n const db = getDb();\n const limit = opts.limit ?? 20;\n const sinceMs = parseSince(opts.since);\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n if (sinceMs) {\n conditions.push(\"s.started_at_ms >= ?\");\n params.push(sinceMs);\n }\n const where =\n conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n\n const sql = `\n SELECT s.session_id, s.target, s.model, s.project,\n s.started_at_ms, s.ended_at_ms, s.first_prompt, s.summary,\n COALESCE(s.turn_count, 0) as turn_count,\n COALESCE(s.message_count, 0) as message_count,\n COALESCE(s.total_input_tokens, 0) as total_input_tokens,\n COALESCE(s.total_output_tokens, 0) as total_output_tokens,\n s.parent_session_id, s.relationship_type,\n ${SESSION_COST_SQL} as total_cost\n FROM sessions s\n ${where}\n ORDER BY s.started_at_ms DESC\n LIMIT ?\n `;\n params.push(limit);\n\n const rows = db.prepare(sql).all(...params) as Array<{\n session_id: string;\n target: string | null;\n model: string | null;\n project: string | null;\n started_at_ms: number | null;\n ended_at_ms: number | null;\n first_prompt: string | null;\n summary: string | null;\n turn_count: number;\n message_count: number;\n total_input_tokens: number;\n total_output_tokens: number;\n parent_session_id: string | null;\n relationship_type: string | null;\n total_cost: number;\n }>;\n\n const sessionIds = rows.map((r) => r.session_id);\n const repoRows =\n sessionIds.length > 0\n ? (db\n .prepare(\n `SELECT session_id, repository, git_user_name, git_user_email\n FROM session_repositories\n WHERE session_id IN (${sessionIds.map(() => \"?\").join(\",\")})`,\n )\n .all(...sessionIds) as RawRepoRow[])\n : [];\n\n const reposBySession = new Map<string, RawRepoRow[]>();\n for (const r of repoRows) {\n const list = reposBySession.get(r.session_id) ?? [];\n list.push(r);\n reposBySession.set(r.session_id, list);\n }\n\n const sessions: Session[] = rows.map((row) => ({\n sessionId: row.session_id,\n target: row.target,\n model: row.model,\n project: row.project,\n startedAt: row.started_at_ms ? toIso(row.started_at_ms) : null,\n endedAt: row.ended_at_ms ? toIso(row.ended_at_ms) : null,\n firstPrompt: row.first_prompt,\n turnCount: row.turn_count,\n messageCount: row.message_count,\n totalInputTokens: row.total_input_tokens,\n totalOutputTokens: row.total_output_tokens,\n totalCost: row.total_cost,\n repositories: (reposBySession.get(row.session_id) ?? []).map((r) => ({\n name: r.repository,\n gitUserName: r.git_user_name,\n gitUserEmail: r.git_user_email,\n })),\n parentSessionId: row.parent_session_id,\n relationshipType: row.relationship_type,\n summary: row.summary,\n }));\n\n return {\n sessions,\n totalCount: sessions.length,\n source: \"local\",\n };\n}\n\n// ── Timeline ──────────────────────────────────────────────────────────────────\n\nexport function sessionTimeline(opts: {\n sessionId: string;\n limit?: number;\n offset?: number;\n fullPayloads?: boolean;\n}): SessionTimelineResult {\n const db = getDb();\n const limit = opts.limit ?? 50;\n const offset = opts.offset ?? 0;\n const truncate = !opts.fullPayloads;\n\n // Session metadata\n const sessionRow = db\n .prepare(\n \"SELECT session_id, target, model, project, parent_session_id, relationship_type FROM sessions WHERE session_id = ?\",\n )\n .get(opts.sessionId) as\n | {\n session_id: string;\n target: string | null;\n model: string | null;\n project: string | null;\n parent_session_id: string | null;\n relationship_type: string | null;\n }\n | undefined;\n\n if (!sessionRow) {\n return {\n session: null,\n messages: [],\n totalMessages: 0,\n hasMore: false,\n source: \"local\",\n };\n }\n\n const repoRows = db\n .prepare(\n \"SELECT repository, git_user_name, git_user_email FROM session_repositories WHERE session_id = ?\",\n )\n .all(opts.sessionId) as RawRepoRow[];\n\n // Child sessions (forks + subagents)\n const childRows = db\n .prepare(\n \"SELECT session_id, relationship_type, model, COALESCE(turn_count, 0) as turn_count, first_prompt, started_at_ms FROM sessions WHERE parent_session_id = ?\",\n )\n .all(opts.sessionId) as Array<{\n session_id: string;\n relationship_type: string;\n model: string | null;\n turn_count: number;\n first_prompt: string | null;\n started_at_ms: number | null;\n }>;\n\n const childSessions: ChildSession[] = childRows.map((r) => ({\n sessionId: r.session_id,\n relationshipType: r.relationship_type,\n model: r.model,\n turnCount: r.turn_count,\n firstPrompt: r.first_prompt,\n startedAtMs: r.started_at_ms,\n }));\n\n // Message count\n const totalMessages = (\n db\n .prepare(\"SELECT COUNT(*) as c FROM messages WHERE session_id = ?\")\n .get(opts.sessionId) as { c: number }\n ).c;\n\n // Messages\n const contentCol = truncate ? \"SUBSTR(m.content, 1, 500)\" : \"m.content\";\n const msgRows = db\n .prepare(\n `SELECT m.id, m.ordinal, m.role, ${contentCol} as content, m.timestamp_ms,\n m.model, m.is_system, m.has_thinking, m.has_tool_use,\n m.content_length, m.uuid, m.parent_uuid,\n m.token_usage, m.context_tokens, m.output_tokens\n FROM messages m\n WHERE m.session_id = ?\n ORDER BY m.ordinal ASC\n LIMIT ? OFFSET ?`,\n )\n .all(opts.sessionId, limit, offset) as Array<{\n id: number;\n ordinal: number;\n role: string;\n content: string;\n timestamp_ms: number | null;\n model: string | null;\n is_system: number;\n has_thinking: number;\n has_tool_use: number;\n content_length: number;\n uuid: string | null;\n parent_uuid: string | null;\n token_usage: string | null;\n context_tokens: number;\n output_tokens: number;\n }>;\n\n // Batch-load tool calls\n const msgIds = msgRows.map((m) => m.id);\n const tcRows =\n msgIds.length > 0\n ? (db\n .prepare(\n `SELECT tc.message_id, tc.tool_name, tc.category, tc.tool_use_id,\n tc.input_json, tc.skill_name, tc.result_content_length,\n tc.duration_ms, tc.subagent_session_id\n FROM tool_calls tc\n WHERE tc.message_id IN (${msgIds.map(() => \"?\").join(\",\")})\n ORDER BY tc.id ASC`,\n )\n .all(...msgIds) as Array<{\n message_id: number;\n tool_name: string;\n category: string;\n tool_use_id: string | null;\n input_json: string | null;\n skill_name: string | null;\n result_content_length: number | null;\n duration_ms: number | null;\n subagent_session_id: string | null;\n }>)\n : [];\n\n // Subagent metadata lookup\n const subagentIds = [\n ...new Set(\n tcRows.map((tc) => tc.subagent_session_id).filter(Boolean) as string[],\n ),\n ];\n const subagentMap = new Map<\n string,\n { model: string | null; turn_count: number; first_prompt: string | null }\n >();\n if (subagentIds.length > 0) {\n const subRows = db\n .prepare(\n `SELECT session_id, model, COALESCE(turn_count, 0) as turn_count, first_prompt\n FROM sessions WHERE session_id IN (${subagentIds.map(() => \"?\").join(\",\")})`,\n )\n .all(...subagentIds) as Array<{\n session_id: string;\n model: string | null;\n turn_count: number;\n first_prompt: string | null;\n }>;\n for (const r of subRows) {\n subagentMap.set(r.session_id, {\n model: r.model,\n turn_count: r.turn_count,\n first_prompt: r.first_prompt,\n });\n }\n }\n\n // Group tool calls by message_id\n const tcByMessage = new Map<number, typeof tcRows>();\n for (const tc of tcRows) {\n const list = tcByMessage.get(tc.message_id) ?? [];\n list.push(tc);\n tcByMessage.set(tc.message_id, list);\n }\n\n const messages: TimelineMessage[] = msgRows.map((m) => {\n const tcs = tcByMessage.get(m.id) ?? [];\n return {\n id: m.id,\n ordinal: m.ordinal,\n role: m.role,\n content: m.content,\n timestampMs: m.timestamp_ms,\n model: m.model,\n isSystem: m.is_system === 1,\n hasThinking: m.has_thinking === 1,\n hasToolUse: m.has_tool_use === 1,\n contentLength: m.content_length,\n uuid: m.uuid,\n parentUuid: m.parent_uuid,\n tokenUsage: m.token_usage,\n contextTokens: m.context_tokens,\n outputTokens: m.output_tokens,\n toolCalls: tcs.map((tc): TimelineToolCall => {\n const sub = tc.subagent_session_id\n ? subagentMap.get(tc.subagent_session_id)\n : undefined;\n return {\n toolName: tc.tool_name,\n category: tc.category,\n toolUseId: tc.tool_use_id,\n inputJson: truncate\n ? (tc.input_json?.slice(0, 500) ?? null)\n : tc.input_json,\n skillName: tc.skill_name,\n resultContentLength: tc.result_content_length,\n durationMs: tc.duration_ms,\n subagentSessionId: tc.subagent_session_id,\n subagent: sub\n ? {\n sessionId: tc.subagent_session_id!,\n model: sub.model,\n turnCount: sub.turn_count,\n firstPrompt: sub.first_prompt,\n }\n : null,\n };\n }),\n };\n });\n\n return {\n session: {\n sessionId: opts.sessionId,\n target: sessionRow.target,\n model: sessionRow.model,\n project: sessionRow.project,\n parentSessionId: sessionRow.parent_session_id,\n relationshipType: sessionRow.relationship_type,\n repositories: repoRows.map((r) => ({\n name: r.repository,\n gitUserName: r.git_user_name,\n gitUserEmail: r.git_user_email,\n })),\n childSessions,\n },\n messages,\n totalMessages,\n hasMore: offset + limit < totalMessages,\n source: \"local\",\n };\n}\n\n// ── Spending ──────────────────────────────────────────────────────────────────\n\nexport function costBreakdown(\n opts: { since?: string; groupBy?: \"session\" | \"model\" | \"day\" } = {},\n): SpendingResult {\n const db = getDb();\n const sinceMs = parseSince(opts.since);\n const groupBy = opts.groupBy ?? \"session\";\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n if (sinceMs) {\n conditions.push(\"s.started_at_ms >= ?\");\n params.push(sinceMs);\n }\n const where =\n conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n\n let groupExpr: string;\n let selectExpr: string;\n if (groupBy === \"day\") {\n groupExpr = \"date(s.started_at_ms / 1000, 'unixepoch')\";\n selectExpr = `${groupExpr} as group_key`;\n } else if (groupBy === \"model\") {\n groupExpr = \"COALESCE(s.model, 'unknown')\";\n selectExpr = `${groupExpr} as group_key`;\n } else {\n groupExpr = \"s.session_id\";\n selectExpr = \"s.session_id as group_key\";\n }\n\n const sql = `\n SELECT ${selectExpr},\n SUM(COALESCE(s.total_input_tokens, 0) + COALESCE(s.total_cache_read_tokens, 0) + COALESCE(s.total_cache_creation_tokens, 0)) as input_tokens,\n SUM(COALESCE(s.total_output_tokens, 0)) as output_tokens,\n SUM(COALESCE(s.total_input_tokens, 0) + COALESCE(s.total_output_tokens, 0) + COALESCE(s.total_cache_read_tokens, 0) + COALESCE(s.total_cache_creation_tokens, 0)) as total_tokens,\n SUM(${SESSION_COST_SQL}) as total_cost,\n COUNT(DISTINCT s.session_id) as session_count\n FROM sessions s\n ${where}\n GROUP BY ${groupExpr}\n ORDER BY ${groupBy === \"day\" ? \"group_key DESC\" : \"total_tokens DESC\"}\n `;\n\n const rows = db.prepare(sql).all(...params) as Array<{\n group_key: string;\n input_tokens: number;\n output_tokens: number;\n total_tokens: number;\n total_cost: number;\n session_count: number;\n }>;\n\n const groups: SpendingGroup[] = rows.map((row) => ({\n key: row.group_key,\n inputTokens: row.input_tokens,\n outputTokens: row.output_tokens,\n totalTokens: row.total_tokens,\n totalCost: row.total_cost,\n sessionCount: row.session_count,\n }));\n\n const totals = {\n inputTokens: groups.reduce((sum, g) => sum + g.inputTokens, 0),\n outputTokens: groups.reduce((sum, g) => sum + g.outputTokens, 0),\n totalTokens: groups.reduce((sum, g) => sum + g.totalTokens, 0),\n totalCost: groups.reduce((sum, g) => sum + g.totalCost, 0),\n };\n\n return { groups, totals, groupBy, source: \"local\" };\n}\n\n// ── Search ────────────────────────────────────────────────────────────────────\n\ninterface RawSearchRow {\n source: string;\n id: number;\n session_id: string;\n event_type: string;\n timestamp_ms: number;\n tool_name: string | null;\n cwd: string | null;\n payload: string | null;\n}\n\nexport function search(opts: {\n query: string;\n eventTypes?: string[];\n since?: string;\n limit?: number;\n offset?: number;\n fullPayloads?: boolean;\n}): SearchResult {\n const db = getDb();\n const limit = opts.limit ?? 20;\n const offset = opts.offset ?? 0;\n const sinceMs = parseSince(opts.since);\n const pattern = `%${opts.query}%`;\n const truncate = !opts.fullPayloads;\n\n const hookPayloadCol = truncate\n ? \"SUBSTR(decompress(h.payload), 1, 500)\"\n : \"decompress(h.payload)\";\n\n const hookConditions: string[] = [];\n const hookParams: unknown[] = [];\n\n hookConditions.push(\n \"(h.id IN (SELECT rowid FROM hook_events_fts WHERE hook_events_fts MATCH ?) OR h.tool_name LIKE ? OR h.event_type LIKE ?)\",\n );\n hookParams.push(opts.query, pattern, pattern);\n\n if (opts.eventTypes?.length) {\n hookConditions.push(\n `h.event_type IN (${opts.eventTypes.map(() => \"?\").join(\",\")})`,\n );\n hookParams.push(...opts.eventTypes);\n }\n if (sinceMs) {\n hookConditions.push(\"h.timestamp_ms >= ?\");\n hookParams.push(sinceMs);\n }\n\n const hookSql = `\n SELECT 'hook' as source, h.id, h.session_id, h.event_type, h.timestamp_ms,\n h.tool_name, h.cwd, ${hookPayloadCol} as payload\n FROM hook_events h\n WHERE ${hookConditions.join(\" AND \")}\n `;\n\n const otelConditions: string[] = [\"(o.body LIKE ? OR o.attributes LIKE ?)\"];\n const otelParams: unknown[] = [pattern, pattern];\n\n if (sinceMs) {\n otelConditions.push(\"CAST(o.timestamp_ns / 1000000 AS INTEGER) >= ?\");\n otelParams.push(sinceMs);\n }\n\n const otelAttrsCol = truncate\n ? \"SUBSTR(o.attributes, 1, 500)\"\n : \"o.attributes\";\n\n // Use table-qualified versions of the OTel expressions for the aliased query\n const otelEventTypeQ = otelLogExprs().eventType.replace(\n /\\b(body|timestamp_ns|attributes)\\b/g,\n \"o.$1\",\n );\n const otelTimestampMsQ = otelLogExprs().timestampMs.replace(\n /\\b(body|timestamp_ns|attributes)\\b/g,\n \"o.$1\",\n );\n\n const otelSql = `\n SELECT 'otel' as source, o.id, o.session_id,\n ${otelEventTypeQ} as event_type,\n ${otelTimestampMsQ} as timestamp_ms,\n NULL as tool_name, NULL as cwd, ${otelAttrsCol} as payload\n FROM otel_logs o\n WHERE ${otelConditions.join(\" AND \")}\n `;\n\n // Messages FTS search\n const msgContentCol = truncate ? \"SUBSTR(m.content, 1, 500)\" : \"m.content\";\n const msgConditions: string[] = [\n \"m.id IN (SELECT rowid FROM messages_fts WHERE messages_fts MATCH ?)\",\n ];\n const msgParams: unknown[] = [opts.query];\n if (sinceMs) {\n msgConditions.push(\"m.timestamp_ms >= ?\");\n msgParams.push(sinceMs);\n }\n\n const msgSql = `\n SELECT 'message' as source, m.id, m.session_id,\n m.role as event_type, m.timestamp_ms,\n NULL as tool_name, NULL as cwd, ${msgContentCol} as payload\n FROM messages m\n WHERE ${msgConditions.join(\" AND \")}\n `;\n\n // Session summary search\n const summaryConditions: string[] = [\n \"s.summary IS NOT NULL\",\n \"s.summary LIKE ?\",\n ];\n const summaryParams: unknown[] = [pattern];\n if (sinceMs) {\n summaryConditions.push(\"s.started_at_ms >= ?\");\n summaryParams.push(sinceMs);\n }\n\n const summarySql = `\n SELECT 'summary' as source, s.session_id as id, s.session_id,\n 'summary' as event_type, s.started_at_ms as timestamp_ms,\n NULL as tool_name, NULL as cwd, SUBSTR(s.summary, 1, 500) as payload\n FROM sessions s\n WHERE ${summaryConditions.join(\" AND \")}\n `;\n\n const countSql = `SELECT COUNT(*) as total FROM (${hookSql} UNION ALL ${otelSql} UNION ALL ${msgSql} UNION ALL ${summarySql})`;\n const total = (\n db\n .prepare(countSql)\n .get(...hookParams, ...otelParams, ...msgParams, ...summaryParams) as {\n total: number;\n }\n ).total;\n\n const sql = `\n SELECT * FROM (${hookSql} UNION ALL ${otelSql} UNION ALL ${msgSql} UNION ALL ${summarySql})\n ORDER BY timestamp_ms DESC\n LIMIT ? OFFSET ?\n `;\n\n const rows = db\n .prepare(sql)\n .all(\n ...hookParams,\n ...otelParams,\n ...msgParams,\n ...summaryParams,\n limit,\n offset,\n ) as RawSearchRow[];\n\n const results: SearchMatch[] = rows.map((row) => {\n const snippet = row.payload ?? row.event_type ?? \"\";\n const matchType =\n row.source === \"summary\"\n ? \"summary\"\n : row.source === \"message\"\n ? \"message\"\n : row.event_type === \"UserPromptSubmit\"\n ? \"prompt\"\n : row.tool_name\n ? \"tool_use\"\n : \"event\";\n\n return {\n sessionId: row.session_id,\n timestamp: toIso(row.timestamp_ms),\n matchType,\n matchSnippet:\n typeof snippet === \"string\"\n ? snippet.slice(0, 300)\n : String(snippet).slice(0, 300),\n eventType: row.event_type,\n toolName: row.tool_name,\n };\n });\n\n return {\n results,\n totalMatches: total,\n query: opts.query,\n source: \"local\",\n };\n}\n\n// ── Activity Summary ──────────────────────────────────────────────────────────\n\nexport function activitySummary(\n opts: { since?: string } = {},\n): ActivitySummaryResult {\n const db = getDb();\n const sinceMs = parseSince(opts.since ?? \"24h\") ?? Date.now() - 86400000;\n const now = Date.now();\n\n const rawSessions = db\n .prepare(\n `SELECT s.session_id, s.model, s.project,\n s.started_at_ms, s.ended_at_ms,\n COALESCE(s.total_input_tokens, 0) + COALESCE(s.total_output_tokens, 0) +\n COALESCE(s.total_cache_read_tokens, 0) + COALESCE(s.total_cache_creation_tokens, 0) as total_tokens,\n ${SESSION_COST_SQL} as total_cost\n FROM sessions s\n WHERE s.started_at_ms >= ?\n ORDER BY s.started_at_ms ASC`,\n )\n .all(sinceMs) as Array<{\n session_id: string;\n model: string | null;\n project: string | null;\n started_at_ms: number | null;\n ended_at_ms: number | null;\n total_tokens: number;\n total_cost: number;\n }>;\n\n let totalCost = 0;\n let totalTokens = 0;\n const sessions: ActivitySessionDetail[] = [];\n\n for (const s of rawSessions) {\n totalCost += s.total_cost;\n totalTokens += s.total_tokens;\n\n // User prompts from messages\n const prompts = db\n .prepare(\n \"SELECT SUBSTR(content, 1, 100) as prompt FROM messages WHERE session_id = ? AND role = 'user' AND is_system = 0 ORDER BY ordinal ASC LIMIT 10\",\n )\n .all(s.session_id) as { prompt: string }[];\n\n // Tool usage from tool_calls\n const tools = db\n .prepare(\n \"SELECT tool_name, COUNT(*) as count FROM tool_calls WHERE session_id = ? GROUP BY tool_name ORDER BY count DESC\",\n )\n .all(s.session_id) as { tool_name: string; count: number }[];\n\n // Files from Write/Edit tool_calls input_json\n const fileRows = db\n .prepare(\n \"SELECT DISTINCT json_extract(input_json, '$.file_path') as file_path FROM tool_calls WHERE session_id = ? AND tool_name IN ('Write', 'Edit') AND input_json IS NOT NULL\",\n )\n .all(s.session_id) as { file_path: string | null }[];\n\n const repos = db\n .prepare(\n \"SELECT repository, git_user_name, git_user_email FROM session_repositories WHERE session_id = ?\",\n )\n .all(s.session_id) as RawRepoRow[];\n\n const durationMs =\n s.started_at_ms && s.ended_at_ms ? s.ended_at_ms - s.started_at_ms : 0;\n\n sessions.push({\n sessionId: s.session_id,\n startedAt: s.started_at_ms ? toIso(s.started_at_ms) : null,\n durationMinutes: Math.round(durationMs / 60000),\n model: s.model,\n project: s.project,\n repositories: repos.map((r) => ({\n name: r.repository,\n gitUserName: r.git_user_name,\n gitUserEmail: r.git_user_email,\n })),\n userPrompts: prompts.map((p) => p.prompt),\n toolsUsed: tools.map((t) => ({ tool: t.tool_name, count: t.count })),\n filesModified: fileRows\n .map((f) => f.file_path)\n .filter(Boolean) as string[],\n totalCost: s.total_cost,\n });\n }\n\n // Global top tools from tool_calls\n const topTools = db\n .prepare(\n `SELECT tc.tool_name, COUNT(*) as count\n FROM tool_calls tc\n INNER JOIN sessions s ON tc.session_id = s.session_id\n WHERE s.started_at_ms >= ?\n GROUP BY tc.tool_name ORDER BY count DESC LIMIT 10`,\n )\n .all(sinceMs) as { tool_name: string; count: number }[];\n\n return {\n period: {\n since: toIso(sinceMs),\n until: toIso(now),\n },\n totalSessions: rawSessions.length,\n totalTokens,\n totalCost,\n topTools: topTools.map((t) => ({ tool: t.tool_name, count: t.count })),\n sessions,\n source: \"local\",\n };\n}\n\n// ── Plans (panopticon-specific, not in unified types) ─────────────────────────\n\nexport function listPlans(\n opts: { session_id?: string; since?: string; limit?: number } = {},\n) {\n const db = getDb();\n const limit = opts.limit ?? 20;\n const sinceMs = parseSince(opts.since);\n\n const conditions: string[] = [\n \"tool_name = 'ExitPlanMode'\",\n \"event_type = 'PreToolUse'\",\n ];\n const params: unknown[] = [];\n\n if (opts.session_id) {\n conditions.push(\"session_id = ?\");\n params.push(opts.session_id);\n }\n if (sinceMs) {\n conditions.push(\"timestamp_ms >= ?\");\n params.push(sinceMs);\n }\n\n const sql = `\n SELECT id, session_id, timestamp_ms, plan, allowed_prompts\n FROM hook_events\n WHERE ${conditions.join(\" AND \")}\n ORDER BY timestamp_ms DESC\n LIMIT ?\n `;\n params.push(limit);\n\n const rows = db.prepare(sql).all(...params) as {\n id: number;\n session_id: string;\n timestamp_ms: number;\n plan: string | null;\n allowed_prompts: string | null;\n }[];\n\n return rows.map((r) => ({\n id: r.id,\n session_id: r.session_id,\n timestamp: toIso(r.timestamp_ms),\n plan: r.plan,\n allowed_prompts: r.allowed_prompts ? JSON.parse(r.allowed_prompts) : null,\n }));\n}\n\n// ── Get Event (panopticon-specific) ───────────────────────────────────────────\n\nexport function print(opts: {\n source: \"hook\" | \"otel\" | \"message\";\n id: number;\n}) {\n const db = getDb();\n\n if (opts.source === \"hook\") {\n const sql = `\n SELECT 'hook' as source, id, session_id, event_type, timestamp_ms,\n tool_name, cwd, user_prompt, file_path, command, plan,\n tool_result, allowed_prompts, decompress(payload) as payload\n FROM hook_events\n WHERE id = ?\n `;\n return db.prepare(sql).get(opts.id) ?? null;\n }\n\n if (opts.source === \"message\") {\n const msg = db\n .prepare(\n `SELECT m.id, m.session_id, m.ordinal, m.role, m.content, m.timestamp_ms,\n m.has_thinking, m.has_tool_use, m.content_length, m.is_system,\n m.model, m.token_usage, m.context_tokens, m.output_tokens,\n m.uuid, m.parent_uuid\n FROM messages m WHERE m.id = ?`,\n )\n .get(opts.id) as Record<string, unknown> | undefined;\n if (!msg) return null;\n const toolCalls = db\n .prepare(\n `SELECT tool_name, category, tool_use_id, input_json, skill_name,\n result_content_length, duration_ms, subagent_session_id\n FROM tool_calls WHERE message_id = ?`,\n )\n .all(opts.id);\n return { source: \"message\", ...msg, tool_calls: toolCalls };\n }\n\n const sql = `\n SELECT 'otel' as source, id, session_id, ${otelLogExprs().eventType} as event_type,\n ${otelLogExprs().timestampMs} as timestamp_ms,\n NULL as tool_name, NULL as cwd, attributes, severity_text,\n ${otelLogExprs().eventType} as body\n FROM otel_logs\n WHERE id = ?\n `;\n return db.prepare(sql).get(opts.id) ?? null;\n}\n\n// ── Raw Query (panopticon-specific) ──────────────────────────────────────────\n\nexport function rawQuery(sql: string) {\n const db = getDb();\n\n const trimmed = sql.trim().toUpperCase();\n if (\n !trimmed.startsWith(\"SELECT\") &&\n !trimmed.startsWith(\"WITH\") &&\n !trimmed.startsWith(\"PRAGMA\")\n ) {\n throw new Error(\"Only SELECT, WITH, and PRAGMA statements are allowed\");\n }\n\n if (!trimmed.startsWith(\"PRAGMA\") && !trimmed.includes(\"LIMIT\")) {\n sql = `${sql.trimEnd().replace(/;$/, \"\")} LIMIT 1000`;\n }\n\n return db.prepare(sql).all();\n}\n\n// ── DB Stats (panopticon-specific) ───────────────────────────────────────────\n\nexport function dbStats() {\n const db = getDb();\n const logs = db.prepare(\"SELECT COUNT(*) as count FROM otel_logs\").get() as {\n count: number;\n };\n const metrics = db\n .prepare(\"SELECT COUNT(*) as count FROM otel_metrics\")\n .get() as { count: number };\n const hooks = db\n .prepare(\"SELECT COUNT(*) as count FROM hook_events\")\n .get() as { count: number };\n const spans = db\n .prepare(\"SELECT COUNT(*) as count FROM otel_spans\")\n .get() as { count: number };\n const sessions = db\n .prepare(\"SELECT COUNT(*) as count FROM sessions\")\n .get() as { count: number };\n const scannerTurns = db\n .prepare(\"SELECT COUNT(*) as count FROM scanner_turns\")\n .get() as { count: number };\n const scannerEvents = db\n .prepare(\"SELECT COUNT(*) as count FROM scanner_events\")\n .get() as { count: number };\n\n const messages = db\n .prepare(\"SELECT COUNT(*) as count FROM messages\")\n .get() as { count: number };\n const toolCalls = db\n .prepare(\"SELECT COUNT(*) as count FROM tool_calls\")\n .get() as { count: number };\n\n return {\n sessions: sessions.count,\n messages: messages.count,\n tool_calls: toolCalls.count,\n scanner_turns: scannerTurns.count,\n scanner_events: scannerEvents.count,\n hook_events: hooks.count,\n otel_logs: logs.count,\n otel_metrics: metrics.count,\n otel_spans: spans.count,\n };\n}\n","/**\n * Doctor — diagnostic checks for panopticon health.\n *\n * Exported for use by fml-plugin and other integrators.\n */\n\nimport fs from \"node:fs\";\nimport http from \"node:http\";\nimport os from \"node:os\";\nimport { config } from \"./config.js\";\nimport { dbStats } from \"./db/query.js\";\nimport { closeDb, getDb } from \"./db/schema.js\";\nimport { readWatermark, watermarkKey } from \"./sync/watermark.js\";\nimport { allTargets } from \"./targets/index.js\";\nimport { loadUnifiedConfig } from \"./unified-config.js\";\n\nexport interface CheckResult {\n label: string;\n status: \"ok\" | \"warn\" | \"fail\";\n detail: string;\n}\n\nexport interface RecentEvent {\n eventType: string;\n toolName: string | null;\n timestamp: string;\n}\n\nexport interface RecentError {\n id: number;\n body: string;\n}\n\nexport interface DoctorResult {\n checks: CheckResult[];\n system: {\n os: string;\n node: string;\n sandbox: boolean;\n };\n recentEvents: RecentEvent[];\n recentErrors: RecentError[];\n}\n\nfunction checkHealth(port: number, host: string): Promise<boolean> {\n return new Promise((resolve) => {\n const req = http.request(\n { hostname: host, port, path: \"/health\", method: \"GET\", timeout: 2000 },\n (res) => {\n res.resume();\n resolve(res.statusCode === 200);\n },\n );\n req.on(\"error\", () => resolve(false));\n req.on(\"timeout\", () => {\n req.destroy();\n resolve(false);\n });\n req.end();\n });\n}\n\n/**\n * Run panopticon diagnostic checks.\n *\n * Returns structured results — callers handle display.\n */\nexport async function doctor(): Promise<DoctorResult> {\n const checks: CheckResult[] = [];\n\n const isSandbox =\n process.env.SANDBOX !== undefined ||\n fs.existsSync(path.join(os.homedir(), \".sandbox-home\"));\n\n // 1. Database\n if (!fs.existsSync(config.dbPath)) {\n checks.push({\n label: \"Database\",\n status: \"fail\",\n detail: `Not found at ${config.dbPath}. Run install to initialize.`,\n });\n } else {\n try {\n const stats = dbStats();\n const total = stats.hook_events + stats.otel_logs + stats.otel_metrics;\n checks.push({\n label: \"Database\",\n status: \"ok\",\n detail: `${total} rows (${stats.hook_events} hooks, ${stats.otel_logs} logs, ${stats.otel_metrics} metrics)`,\n });\n closeDb();\n } catch (err) {\n checks.push({\n label: \"Database\",\n status: \"fail\",\n detail: `Error: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n }\n\n // 2. Server\n const serverUp = await checkHealth(config.port, \"127.0.0.1\");\n if (serverUp) {\n checks.push({\n label: \"Server\",\n status: \"ok\",\n detail: `Listening on 127.0.0.1:${config.port}`,\n });\n } else {\n checks.push({\n label: \"Server\",\n status: \"warn\",\n detail: `Not responding on port ${config.port}`,\n });\n }\n\n // 3. Shell environment\n const shellRc = path.join(\n os.homedir(),\n process.env.SHELL?.includes(\"zsh\") ? \".zshrc\" : \".bashrc\",\n );\n const rcContent = fs.existsSync(shellRc)\n ? fs.readFileSync(shellRc, \"utf-8\")\n : \"\";\n\n const hasTelemetry = rcContent.includes(\"CLAUDE_CODE_ENABLE_TELEMETRY\");\n const hasEndpoint = rcContent.includes(\"OTEL_EXPORTER_OTLP_ENDPOINT\");\n\n if (hasTelemetry && hasEndpoint) {\n checks.push({\n label: \"Shell Env\",\n status: \"ok\",\n detail: `Telemetry configured in ${path.basename(shellRc)}`,\n });\n } else if (hasTelemetry || hasEndpoint) {\n checks.push({\n label: \"Shell Env\",\n status: \"warn\",\n detail: `Partial config in ${path.basename(shellRc)} — re-run install`,\n });\n } else {\n checks.push({\n label: \"Shell Env\",\n status: \"warn\",\n detail: `Not configured. Run install to set up telemetry.`,\n });\n }\n\n // 4. Coding tool integration\n const tools: Array<{ name: string; dir: string; configured: boolean }> = [];\n\n for (const t of allTargets()) {\n if (t.detect.isInstalled()) {\n tools.push({\n name: t.detect.displayName,\n dir: t.config.dir,\n configured: t.detect.isConfigured(),\n });\n }\n }\n\n if (tools.length === 0) {\n checks.push({\n label: \"Tools\",\n status: \"warn\",\n detail: \"No supported coding tools found\",\n });\n } else {\n const configured = tools.filter((t) => t.configured);\n const unconfigured = tools.filter((t) => !t.configured);\n if (unconfigured.length === 0) {\n checks.push({\n label: \"Tools\",\n status: \"ok\",\n detail: configured.map((t) => t.name).join(\", \"),\n });\n } else {\n const detail = tools\n .map((t) => `${t.name} ${t.configured ? \"✓\" : \"(not configured)\"}`)\n .join(\", \");\n checks.push({\n label: \"Tools\",\n status: \"warn\",\n detail: `${detail} — re-run install`,\n });\n }\n }\n\n // 5. Recent data\n if (fs.existsSync(config.dbPath)) {\n try {\n const db = getDb();\n const latest = db\n .prepare(\"SELECT MAX(timestamp_ms) as ts FROM hook_events\")\n .get() as { ts: number | null } | undefined;\n closeDb();\n\n if (latest?.ts) {\n const ago = Date.now() - latest.ts;\n const minutes = Math.floor(ago / 60000);\n if (minutes < 60) {\n checks.push({\n label: \"Data Flow\",\n status: \"ok\",\n detail: `Last event ${minutes}m ago`,\n });\n } else if (minutes < 1440) {\n checks.push({\n label: \"Data Flow\",\n status: \"ok\",\n detail: `Last event ${Math.floor(minutes / 60)}h ago`,\n });\n } else {\n checks.push({\n label: \"Data Flow\",\n status: \"warn\",\n detail: `Last event ${Math.floor(minutes / 1440)}d ago`,\n });\n }\n } else {\n checks.push({\n label: \"Data Flow\",\n status: \"warn\",\n detail: \"No events recorded yet\",\n });\n }\n } catch {\n // Already reported DB error above\n }\n }\n\n // 6. Sync targets\n try {\n const cfg = loadUnifiedConfig();\n const targets = cfg.sync.targets;\n if (targets.length === 0) {\n checks.push({\n label: \"Sync\",\n status: \"ok\",\n detail: \"No targets configured\",\n });\n } else {\n const tables = [\"hook_events\", \"otel_logs\", \"otel_metrics\"];\n const targetDetails: string[] = [];\n for (const t of targets) {\n const watermarks = tables.map((table) =>\n readWatermark(watermarkKey(table, t.name)),\n );\n const minWm = Math.min(...watermarks);\n const wmLabel = minWm > 0 ? `synced to #${minWm}` : \"not synced yet\";\n targetDetails.push(`${t.name} → ${t.url} (${wmLabel})`);\n }\n checks.push({\n label: \"Sync\",\n status: \"ok\",\n detail: `${targets.length} target${targets.length > 1 ? \"s\" : \"\"}: ${targetDetails.join(\"; \")}`,\n });\n }\n } catch {\n checks.push({\n label: \"Sync\",\n status: \"warn\",\n detail: \"Could not read sync config\",\n });\n }\n\n // 7. Sentry\n try {\n const cfg = loadUnifiedConfig();\n const dsn = process.env.PANOPTICON_SENTRY_DSN ?? cfg.sentryDsn;\n checks.push({\n label: \"Sentry\",\n status: dsn ? \"ok\" : \"ok\",\n detail: dsn ? \"Configured\" : \"Not configured (optional)\",\n });\n } catch {\n // Non-critical\n }\n\n // 8. Recent events and errors (informational, not checks)\n let recentEvents: RecentEvent[] = [];\n let recentErrors: RecentError[] = [];\n\n if (fs.existsSync(config.dbPath)) {\n try {\n const db = getDb();\n\n const events = db\n .prepare(\n \"SELECT event_type, tool_name, timestamp_ms FROM hook_events ORDER BY id DESC LIMIT 3\",\n )\n .all() as {\n event_type: string;\n tool_name: string | null;\n timestamp_ms: number;\n }[];\n\n recentEvents = events.map((e) => ({\n eventType: e.event_type,\n toolName: e.tool_name,\n timestamp: new Date(e.timestamp_ms).toISOString(),\n }));\n\n const errors = db\n .prepare(\n \"SELECT id, body FROM otel_logs WHERE severity_text = 'ERROR' ORDER BY id DESC LIMIT 3\",\n )\n .all() as { id: number; body: string | null }[];\n\n recentErrors = errors.map((e) => ({\n id: e.id,\n body: (e.body ?? \"\").slice(0, 200),\n }));\n\n closeDb();\n } catch {\n // DB errors already reported above\n }\n }\n\n return {\n checks,\n system: {\n os: `${os.platform()} (${os.release()} ${os.arch()})`,\n node: process.version,\n sandbox: isSandbox,\n },\n recentEvents,\n recentErrors,\n };\n}\n\nimport path from \"node:path\";\n","/**\n * Target adapter types — each supported coding tool declares its specifics\n * via this interface so consumers can iterate over the registry instead of\n * hardcoding target-specific branches.\n */\n\nimport type { HookInput } from \"../hooks/ingest.js\";\n\n/**\n * All hook event names panopticon registers for.\n *\n * Claude Code fires these as shell commands via hooks.json — each invocation\n * pipes a JSON payload to stdin with session_id, hook_event_name, and\n * event-specific fields. Panopticon's hook-handler POSTs the payload to\n * the local server, which calls processHookEvent() in ingest.ts.\n *\n * Non-Claude targets (Gemini, Codex) map their native event names to these\n * canonical names via their adapter's eventMap.\n */\nexport const ALL_EVENTS = [\n // ── Session lifecycle ────────────────────────────────────────────────────\n // SessionStart: Fired once when `claude` launches or a new conversation begins.\n // Payload: cwd, permission_mode, model, agent_version.\n // Panopticon uses this to auto-start the server process (see handler.ts).\n \"SessionStart\",\n // SessionEnd: Fired when the session process exits (user quits, ctrl+C, or\n // process terminates). May not fire on SIGKILL. Carries final session state.\n \"SessionEnd\",\n // Setup: Fired early in startup, after setCwd() but before the REPL renders.\n // Runs before trust dialogs, so git commands may not have executed yet.\n \"Setup\",\n\n // ── User interaction ─────────────────────────────────────────────────────\n // UserPromptSubmit: Fired when the user submits a prompt (presses Enter in\n // REPL or sends via SDK). Payload: prompt text. Fires before model inference.\n \"UserPromptSubmit\",\n\n // ── Tool lifecycle (highest volume events) ───────────────────────────────\n // PreToolUse: Fired BEFORE each tool execution. Payload: tool_name, tool_input.\n // This is the permission enforcement point — hooks can return a\n // permissionDecision (\"allow\"/\"deny\") to approve/reject without user prompt.\n // Panopticon checks allowed.json here for auto-approval rules.\n \"PreToolUse\",\n // PostToolUse: Fired AFTER a tool executes successfully. Payload: tool_name,\n // tool_input, tool_result. Good for auditing what tools actually did.\n \"PostToolUse\",\n // PostToolUseFailure: Fired AFTER a tool execution fails (error, file not found,\n // permission denied, etc). Same shape as PostToolUse but indicates failure.\n \"PostToolUseFailure\",\n\n // ── Permission prompts ───────────────────────────────────────────────────\n // PermissionRequest: Fired when Claude Code is about to show the user a\n // permission prompt (tool needs approval). Payload: tool_name, tool_input.\n \"PermissionRequest\",\n // PermissionDenied: Fired when the user denies a permission prompt.\n \"PermissionDenied\",\n\n // ── Model turn lifecycle ─────────────────────────────────────────────────\n // Stop: Fired when the model finishes a turn and stops generating (no more\n // tool calls to make). Natural end of each assistant response cycle.\n // A session has many Stop events but only one SessionEnd.\n \"Stop\",\n // StopFailure: Fired when the model stops due to an error — rate limit,\n // prompt too long, auth failure, etc. The model never produced a valid\n // response for this turn.\n \"StopFailure\",\n\n // ── Subagents ────────────────────────────────────────────────────────────\n // SubagentStart: Fired when a subagent is spawned via the Agent tool.\n // Payload includes the agent type and description.\n \"SubagentStart\",\n // SubagentStop: Fired when a subagent completes (success or failure).\n \"SubagentStop\",\n\n // ── Context compaction ───────────────────────────────────────────────────\n // PreCompact: Fired before conversation context is compacted (summarized to\n // reduce token count). Useful for capturing pre-compaction state.\n \"PreCompact\",\n // PostCompact: Fired after compaction completes. Payload includes token\n // counts before/after compaction.\n \"PostCompact\",\n\n // ── Notifications ────────────────────────────────────────────────────────\n // Notification: System notifications — rate limit warnings, usage alerts, etc.\n \"Notification\",\n\n // ── Team / background tasks ──────────────────────────────────────────────\n // TeammateIdle: Fired when a teammate agent (swarm mode) has no work to do.\n \"TeammateIdle\",\n // TaskCreated: Fired when a background task is created (via TaskCreate tool).\n \"TaskCreated\",\n // TaskCompleted: Fired when a background task finishes.\n \"TaskCompleted\",\n\n // ── MCP auth / elicitation ───────────────────────────────────────────────\n // Elicitation: Fired when an MCP server triggers an OAuth/auth flow prompt.\n \"Elicitation\",\n // ElicitationResult: Fired when the user completes or cancels the auth flow.\n \"ElicitationResult\",\n\n // ── Configuration & file system ──────────────────────────────────────────\n // ConfigChange: Fired when settings.json, CLAUDE.md, or similar config changes.\n \"ConfigChange\",\n // InstructionsLoaded: Fired when CLAUDE.md files are loaded into context.\n \"InstructionsLoaded\",\n // CwdChanged: Fired when Claude Code's working directory changes (via /add-dir\n // or similar). NOTE: `cd` in Bash does NOT trigger this — that only affects\n // the subprocess shell, not the harness's cwd.\n \"CwdChanged\",\n // FileChanged: Fired when a watched file is modified on disk (external edit).\n \"FileChanged\",\n\n // ── Worktree management ──────────────────────────────────────────────────\n // WorktreeCreate: Fired when a git worktree is created (--worktree flag or\n // EnterWorktree tool).\n \"WorktreeCreate\",\n // WorktreeRemove: Fired when a git worktree is removed (ExitWorktree tool\n // or session cleanup).\n \"WorktreeRemove\",\n] as const;\n\n/** Union type of all supported event names. */\nexport type CanonicalEvent = (typeof ALL_EVENTS)[number];\n\n// ── Config & Paths ──────────────────────────────────────────────────────────\n\nexport interface TargetConfigSpec {\n /** Directory where this target stores its config, e.g. ~/.claude */\n dir: string;\n /** Path to the main config file */\n configPath: string;\n /** Format of the config file */\n configFormat: \"json\" | \"toml\";\n}\n\n// ── Hook Registration ───────────────────────────────────────────────────────\n\nexport interface TargetInstallOpts {\n pluginRoot: string;\n port: number;\n proxy?: boolean;\n}\n\nexport interface TargetHookSpec {\n /** Event names this target uses, in the target's own convention */\n events: string[];\n /**\n * Apply panopticon hook registration (and related config like MCP servers,\n * telemetry) to this target's existing config. Each target handles its own\n * deduplication/merge logic. Returns the modified config.\n */\n applyInstallConfig(\n existingConfig: Record<string, unknown>,\n opts: TargetInstallOpts,\n ): Record<string, unknown>;\n /**\n * Remove panopticon hook registration (and related config like MCP servers,\n * telemetry) from this target's existing config. Returns the modified config.\n */\n removeInstallConfig(\n existingConfig: Record<string, unknown>,\n ): Record<string, unknown>;\n}\n\n// ── Shell Environment ───────────────────────────────────────────────────────\n\nexport interface TargetShellEnvSpec {\n /**\n * Env vars to export as [varName, value] tuples.\n * These are target-specific; shared OTEL_* vars are handled separately.\n */\n envVars(port: number, proxy: boolean): Array<[string, string]>;\n}\n\n// ── Event Normalization ─────────────────────────────────────────────────────\n\nexport interface TargetEventSpec {\n /** Map from target's event name to canonical panopticon event name. */\n eventMap: Record<string, CanonicalEvent>;\n /**\n * Transform the raw hook payload before storage.\n * Used e.g. by Gemini to extract user_prompt from llm_request.messages.\n */\n normalizePayload?(data: HookInput): HookInput;\n /**\n * Format a permission response for this target's expected shape.\n */\n formatPermissionResponse(decision: {\n allow: boolean;\n reason: string;\n }): Record<string, unknown>;\n}\n\n// ── Doctor / Detection ──────────────────────────────────────────────────────\n\nexport interface TargetDetectSpec {\n /** Human-readable name for display, e.g. \"Claude Code\" */\n displayName: string;\n /** Check whether this target is installed on the system */\n isInstalled(): boolean;\n /** Check whether panopticon is configured within this target */\n isConfigured(): boolean;\n}\n\n// ── OTel Telemetry Schema ────────────────────────────────────────────────────\n\nexport interface MetricSpec {\n /** OTel metric name(s) this target emits for token usage */\n metricNames: string[];\n /** Aggregation function: 'SUM' for per-request deltas, 'MAX' for cumulative counters */\n aggregation: \"SUM\" | \"MAX\";\n /** JSON paths to extract token type from metric attributes (first non-null wins) */\n tokenTypeAttrs: string[];\n /** JSON paths to extract model name from metric attributes (first non-null wins) */\n modelAttrs: string[];\n /** Remap token_type values before aggregation, e.g. { cached_input: 'cacheRead' } */\n tokenTypeMap?: Record<string, string>;\n /** Token type values to exclude (e.g. 'total' to avoid double-counting) */\n excludeTokenTypes?: string[];\n}\n\nexport interface OtelLogFieldSpec {\n /** SQL expressions to extract event type from otel_logs (COALESCEd). Default: ['body'] */\n eventTypeExprs?: string[];\n /** SQL expressions to extract timestamp in ms from otel_logs. Default: ['CAST(timestamp_ns / 1000000 AS INTEGER)'] */\n timestampMsExprs?: string[];\n}\n\nexport interface TargetOtelSpec {\n /** OTel service.name this target emits. Used for session inference when metrics lack session_id. */\n serviceName?: string;\n /** Token usage metric declaration. Undefined means no token metrics. */\n metrics?: MetricSpec;\n /** How to extract event type and timestamp from otel_logs rows. */\n logFields?: OtelLogFieldSpec;\n}\n\n// ── Target Identification ────────────────────────────────────────────────────\n\nexport interface TargetIdentSpec {\n /** Model-name regex patterns for last-resort target identification from hook payloads */\n modelPatterns?: RegExp[];\n}\n\n// ── Proxy (optional — not all targets use the proxy) ────────────────────────\n\nexport interface TargetProxySpec {\n /**\n * Upstream host for API proxying.\n * String for simple mapping; function for dynamic routing (e.g. Codex JWT).\n */\n upstreamHost: string | ((headers: Record<string, string>) => string);\n /** Path rewrite rule, if needed. Default: pass through. */\n rewritePath?(path: string, headers: Record<string, string>): string;\n /** Which stream accumulator to use */\n accumulatorType: \"anthropic\" | \"openai\";\n}\n\n// ── Session File Scanner ─────────────────────────────────────────────────────\n\nexport interface DiscoveredFile {\n filePath: string;\n}\n\nexport interface ParsedTurn {\n sessionId: string;\n turnIndex: number;\n timestampMs: number;\n model?: string;\n role: \"user\" | \"assistant\";\n contentPreview?: string;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheCreationTokens: number;\n reasoningTokens: number;\n}\n\nexport type RelationshipType = \"subagent\" | \"continuation\" | \"fork\";\n\nexport interface ParsedSession {\n sessionId: string;\n parentSessionId?: string;\n relationshipType?: RelationshipType;\n model?: string;\n cwd?: string;\n cliVersion?: string;\n startedAtMs?: number;\n firstPrompt?: string;\n}\n\nexport interface ParsedEvent {\n sessionId: string;\n eventType: string; // tool_call, tool_result, error, agent_message, reasoning, file_snapshot, info\n timestampMs: number;\n toolName?: string;\n toolInput?: string;\n toolOutput?: string;\n content?: string;\n metadata?: Record<string, unknown>;\n}\n\n// ── Parsed messages & tool calls (for messages/tool_calls tables) ───────────\n\nexport interface ParsedToolCall {\n toolUseId: string;\n toolName: string;\n category: string;\n inputJson?: string;\n skillName?: string;\n resultContentLength?: number;\n resultContent?: string;\n subagentSessionId?: string;\n /** Timestamp (ms) when the tool was invoked (from the assistant message). */\n timestampMs?: number;\n}\n\nexport interface ParsedMessage {\n sessionId: string;\n ordinal: number;\n role: \"user\" | \"assistant\";\n content: string;\n timestampMs?: number;\n hasThinking: boolean;\n hasToolUse: boolean;\n isSystem: boolean;\n contentLength: number;\n model?: string;\n tokenUsage?: string;\n contextTokens?: number;\n outputTokens?: number;\n hasContextTokens: boolean;\n hasOutputTokens: boolean;\n /** DAG node UUID from the JSONL line that produced this message. */\n uuid?: string;\n /** Parent DAG node UUID (the line this message is a reply to). */\n parentUuid?: string;\n toolCalls: ParsedToolCall[];\n /** tool_use_id → raw result content (from tool_result blocks in user messages) */\n toolResults: Map<\n string,\n { contentLength: number; contentRaw: string; timestampMs?: number }\n >;\n}\n\nexport interface ParseResult {\n meta?: ParsedSession;\n turns: ParsedTurn[];\n events: ParsedEvent[];\n messages: ParsedMessage[];\n newByteOffset: number;\n /**\n * When true, turn indices are absolute (0-based from start of session)\n * and the caller should NOT re-index them. Used by parsers that re-read\n * the full file (e.g. Gemini JSON) rather than reading incrementally.\n * INSERT OR IGNORE handles dedup via the UNIQUE constraint.\n */\n absoluteIndices?: boolean;\n /** Additional sessions from DAG fork detection (branched conversations). */\n forks?: ParseResult[];\n /**\n * When true, the parser detected a DAG fork during incremental reading.\n * The caller should reset the file watermark and reparse from byte 0\n * so fork detection can run on the full file.\n */\n needsFullReparse?: boolean;\n /**\n * Tool results from filtered-out messages (e.g. tool-result-only user\n * messages) that still need to be backfilled into tool_calls.\n */\n orphanedToolResults?: Map<\n string,\n { contentLength: number; contentRaw: string; timestampMs?: number }\n >;\n}\n\nexport interface TargetScannerSpec {\n /** Discover session files on disk for this target. */\n discover(): DiscoveredFile[];\n /**\n * Parse a session file. Receives the file path and current byte offset.\n * Returns parsed data and new byte offset, or null if no new data.\n */\n parseFile(filePath: string, fromByteOffset: number): ParseResult | null;\n /** Normalize a tool name to a standard category for analytics grouping. */\n normalizeToolCategory(toolName: string): string;\n}\n\n// ── The Adapter ─────────────────────────────────────────────────────────────\n\nexport interface TargetAdapter {\n /** Machine identifier: \"claude\", \"gemini\", \"codex\", etc. */\n id: string;\n config: TargetConfigSpec;\n hooks: TargetHookSpec;\n shellEnv: TargetShellEnvSpec;\n events: TargetEventSpec;\n detect: TargetDetectSpec;\n /** Proxy spec is optional — not every target routes through the proxy */\n proxy?: TargetProxySpec;\n /** OTel telemetry schema — how this target emits metrics and logs */\n otel?: TargetOtelSpec;\n /** How to identify this target from hook payloads when no explicit source field is present */\n ident?: TargetIdentSpec;\n /** Session file scanner — reads local transcript files for token usage */\n scanner?: TargetScannerSpec;\n}\n","import fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { registerTarget } from \"./registry.js\";\nimport type { TargetAdapter } from \"./types.js\";\n\nconst CLAUDE_DESKTOP_DIR = path.join(\n os.homedir(),\n \"Library\",\n \"Application Support\",\n \"Claude\",\n);\nconst CLAUDE_DESKTOP_CONFIG = path.join(\n CLAUDE_DESKTOP_DIR,\n \"claude_desktop_config.json\",\n);\n\nconst claudeDesktop: TargetAdapter = {\n id: \"claude-desktop\",\n\n config: {\n dir: CLAUDE_DESKTOP_DIR,\n configPath: CLAUDE_DESKTOP_CONFIG,\n configFormat: \"json\",\n },\n\n hooks: {\n // Claude Desktop uses MCP servers, not hooks\n events: [],\n applyInstallConfig(existing, opts) {\n const cfg = { ...existing };\n const serverBin = path.join(opts.pluginRoot, \"bin\", \"mcp-server\");\n cfg.mcpServers = (cfg.mcpServers as Record<string, unknown>) ?? {};\n (cfg.mcpServers as Record<string, unknown>).panopticon = {\n command: \"node\",\n args: [serverBin],\n };\n return cfg;\n },\n removeInstallConfig(existing) {\n const cfg = { ...existing };\n const servers = cfg.mcpServers as Record<string, unknown> | undefined;\n if (servers) {\n delete servers.panopticon;\n if (Object.keys(servers).length === 0) delete cfg.mcpServers;\n }\n return cfg;\n },\n },\n\n shellEnv: {\n envVars() {\n return [];\n },\n },\n\n events: {\n eventMap: {},\n formatPermissionResponse({ allow, reason }) {\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: allow ? \"allow\" : \"deny\",\n permissionDecisionReason: reason,\n },\n };\n },\n },\n\n detect: {\n displayName: \"Claude Desktop\",\n isInstalled: () => fs.existsSync(CLAUDE_DESKTOP_DIR),\n isConfigured() {\n try {\n const cfg = JSON.parse(fs.readFileSync(CLAUDE_DESKTOP_CONFIG, \"utf-8\"));\n return !!cfg.mcpServers?.panopticon;\n } catch {\n return false;\n }\n },\n },\n};\n\nregisterTarget(claudeDesktop);\n"],"mappings":";;;;;;;;;;;;;ACAA,OAAOA,SAAQ;AACf,OAAO,QAAQ;AACf,OAAO,UAAU;AEFjB,OAAO,QAAQ;ACAf,OAAOA,SAAQ;AACf,OAAOC,SAAQ;AACf,OAAOC,WAAU;ACFjB,OAAOF,SAAQ;AACf,OAAOC,SAAQ;AACf,OAAOC,WAAU;ALAjB,IAAM,UAAU,oBAAI,IAA2B;AAExC,SAAS,eAAe,SAA8B;AAC3D,MAAI,QAAQ,IAAI,QAAQ,EAAE,GAAG;AAC3B,UAAM,IAAI,MAAM,WAAW,QAAQ,EAAE,yBAAyB;EAChE;AACA,UAAQ,IAAI,QAAQ,IAAI,OAAO;AACjC;AAgBO,SAAS,aAA8B;AAC5C,SAAO,CAAC,GAAG,QAAQ,OAAO,CAAC;AAC7B;AEtBO,SAAS,oBAAoB,WAA2B;AAC7D,SAAO;AACT;ACDO,SAAS,aACd,UACA,gBAC4C;AAC5C,MAAI;AACJ,MAAI;AACF,SAAK,GAAG,SAAS,UAAU,GAAG;EAChC,QAAQ;AACN,WAAO,EAAE,OAAO,CAAC,GAAG,eAAe,eAAe;EACpD;AAEA,MAAI;AACF,UAAM,OAAO,GAAG,UAAU,EAAE,EAAE;AAC9B,QAAI,QAAQ,gBAAgB;AAC1B,aAAO,EAAE,OAAO,CAAC,GAAG,eAAe,eAAe;IACpD;AAEA,UAAM,cAAc,OAAO;AAC3B,UAAM,MAAM,OAAO,MAAM,WAAW;AACpC,OAAG,SAAS,IAAI,KAAK,GAAG,aAAa,cAAc;AAEnD,UAAM,OAAO,IAAI,SAAS,OAAO;AACjC,UAAM,cAAc,KAAK,YAAY,IAAI;AACzC,QAAI,gBAAgB,IAAI;AAEtB,aAAO,EAAE,OAAO,CAAC,GAAG,eAAe,eAAe;IACpD;AAEA,UAAM,WAAW,KAAK,MAAM,GAAG,WAAW;AAC1C,UAAM,QAAQ,SAAS,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC7D,UAAM,gBAAgB,OAAO;MAC3B,KAAK,MAAM,GAAG,cAAc,CAAC;MAC7B;IACF;AAEA,WAAO;MACL;MACA,eAAe,iBAAiB;IAClC;EACF,UAAA;AACE,OAAG,UAAU,EAAE;EACjB;AACF;AFvCA,IAAM,yBAAiD;EACrD,MAAM;EACN,WAAW;EACX,cAAc;EACd,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,aAAa;EACb,cAAc;EACd,MAAM;EACN,MAAM;EACN,MAAM;EACN,UAAU;EACV,MAAM;EACN,OAAO;EACP,YAAY;EACZ,YAAY;EACZ,OAAO;EACP,WAAW;EACX,UAAU;EACV,YAAY;AACd;AAEA,SAAS,mBAAmB,UAA0B;AACpD,QAAM,SAAS,uBAAuB,QAAQ;AAC9C,MAAI,OAAQ,QAAO;AACnB,MAAI,SAAS,WAAW,OAAO,EAAG,QAAO;AACzC,MAAI,SAAS,YAAY,EAAE,SAAS,UAAU,EAAG,QAAO;AACxD,SAAO,oBAAoB,QAAQ;AACrC;AAKA,IAAM,0BAA0B;EAC9B;EACA;EACA;EACA;EACA;AACF;AAGA,SAAS,gBAAgB,SAA0B;AACjD,QAAM,UAAU,QAAQ,UAAU;AAClC,SAAO,wBAAwB,KAAK,CAAC,MAAM,QAAQ,WAAW,CAAC,CAAC;AAClE;AAEA,IAAM,cAAc;AACpB,IAAM,cAAc;AACpB,IAAM,aAAa;AACnB,IAAM,eACJ;AAOF,SAAS,mBAAmB,SAAoC;AAE9D,QAAM,UAAU,QAAQ,QAAQ,WAAW,EAAE,EAAE,UAAU;AACzD,MACE,CAAC,QAAQ,WAAW,mBAAmB,KACvC,CAAC,QAAQ,WAAW,gBAAgB,GACpC;AACA,WAAO,CAAC,SAAS,KAAK;EACxB;AAEA,QAAM,WAAW,QAAQ,QAAQ,cAAc,EAAE;AACjD,MAAI,SAAS,KAAK,MAAM,IAAI;AAC1B,WAAO,CAAC,SAAS,KAAK;EACxB;AAEA,QAAM,YAAY,YAAY,KAAK,OAAO;AAC1C,MAAI,CAAC,WAAW;AAEd,UAAM,WAAW,WAAW,KAAK,OAAO;AACxC,QAAI,SAAU,QAAO,CAAC,IAAI,SAAS,CAAC,CAAC,IAAI,IAAI;AAC7C,WAAO,CAAC,SAAS,KAAK;EACxB;AAEA,MAAI,OAAO,UAAU,CAAC;AACtB,MAAI,CAAC,KAAK,WAAW,GAAG,EAAG,QAAO,IAAI,IAAI;AAC1C,QAAM,YAAY,YAAY,KAAK,OAAO;AAC1C,QAAM,OAAO,YAAY,CAAC,GAAG,KAAK;AAClC,SAAO,CAAC,OAAO,GAAG,IAAI,IAAI,IAAI,KAAK,MAAM,IAAI;AAC/C;AAGA,SAAS,4BAA4B,SAA0B;AAC7D,MAAI,OAAO,YAAY,SAAU,QAAO,QAAQ;AAChD,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,QAAI,MAAM;AACV,eAAW,KAAK,SAAS;AACvB,UACE,OAAO,MAAM,YACb,MAAM,QACL,EAA8B,SAAS,QACxC;AACA,cAAM,OAAQ,EAA8B;AAC5C,YAAI,OAAO,SAAS,SAAU,QAAO,KAAK;MAC5C;IACF;AACA,WAAO;EACT;AACA,SAAO;AACT;AAeA,IAAM,iBAAiB;AAGvB,SAAS,WAAW,SAA8B;AAChD,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,QAAI,QAAQ,CAAC,EAAE,eAAe,QAAQ,IAAI,CAAC,EAAE,KAAM,QAAO;EAC5D;AACA,SAAO;AACT;AAWA,SAAS,YACP,SACA,WAC0D;AAC1D,QAAM,WAAW,oBAAI,IAAsB;AAC3C,QAAM,QAAkB,CAAC;AACzB,QAAM,UAAU,oBAAI,IAAY;AAEhC,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,IAAI,QAAQ,CAAC;AACnB,YAAQ,IAAI,EAAE,IAAI;AAClB,QAAI,CAAC,EAAE,YAAY;AACjB,YAAM,KAAK,CAAC;IACd,OAAO;AACL,YAAM,OAAO,SAAS,IAAI,EAAE,UAAU,KAAK,CAAC;AAC5C,WAAK,KAAK,CAAC;AACX,eAAS,IAAI,EAAE,YAAY,IAAI;IACjC;EACF;AAGA,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,EAAE,gBAAgB,QAAQ,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,cAAc,CAAC,EAAE;EACtE;AACA,aAAW,KAAK,SAAS;AACvB,QAAI,EAAE,cAAc,CAAC,QAAQ,IAAI,EAAE,UAAU,GAAG;AAC9C,aAAO,EAAE,gBAAgB,QAAQ,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,cAAc,CAAC,EAAE;IACtE;EACF;AAEA,QAAM,eAA6B,CAAC;AAGpC,WAAS,eAAe,UAA0B;AAChD,UAAM,QAAQ,CAAC,QAAQ;AACvB,QAAI,QAAQ;AACZ,WAAO,MAAM,SAAS,GAAG;AACvB,YAAM,MAAM,MAAM,IAAI;AACtB,UAAI,QAAQ,GAAG,EAAE,SAAS,OAAQ;AAClC,iBAAW,KAAK,SAAS,IAAI,QAAQ,GAAG,EAAE,IAAI,KAAK,CAAC,EAAG,OAAM,KAAK,CAAC;IACrE;AACA,WAAO;EACT;AAEA,WAAS,WAAW,UAAkB,SAA2B;AAC/D,UAAM,cAAwB,CAAC;AAC/B,QAAI,UAAyB;AAE7B,WAAO,YAAY,MAAM;AACvB,kBAAY,KAAK,OAAO;AACxB,YAAM,OAAiB,SAAS,IAAI,QAAQ,OAAO,EAAE,IAAI,KAAK,CAAC;AAE/D,UAAI,KAAK,WAAW,GAAG;AACrB,kBAAU;MACZ,WAAW,KAAK,WAAW,GAAG;AAC5B,kBAAU,KAAK,CAAC;MAClB,OAAO;AAEL,cAAM,kBAAkB,eAAe,KAAK,CAAC,CAAC;AAC9C,YAAI,mBAAmB,gBAAgB;AAErC,oBAAU,KAAK,KAAK,SAAS,CAAC;QAChC,OAAO;AAGL,mBAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,kBAAM,gBAAgB,WAAW,KAAK,CAAC,GAAG,OAAO;AACjD,yBAAa,KAAK;cAChB,UAAU;cACV,YAAY;YACd,CAAC;UACH;AACA,oBAAU,KAAK,CAAC;QAClB;MACF;IACF;AACA,WAAO;EACT;AAEA,QAAM,iBAAiB,WAAW,MAAM,CAAC,GAAG,SAAS;AACrD,SAAO,EAAE,gBAAgB,aAAa;AACxC;AAGA,IAAM,UACJ;AAEF,IAAM,aAAa,KAAK,KAAK,GAAG,QAAQ,GAAG,SAAS;AAEpD,IAAM,SAAwB;EAC5B,IAAI;EAEJ,QAAQ;IACN,KAAK;IACL,YAAY,KAAK,KAAK,YAAY,eAAe;IACjD,cAAc;EAChB;EAEA,OAAO;;;;IAIL,QAAQ,CAAC;IACT,mBAAmB,UAAU,OAAO;AAClC,YAAM,WAAW,EAAE,GAAG,SAAS;AAC/B,eAAS,yBACN,SAAS,0BAAsD,CAAC;AAClE,eAAS,uBACR,eACF,IAAI;QACF,QAAQ,EAAE,QAAQ,aAAa,MAAM,OAAO,eAAe;MAC7D;AACA,eAAS,iBACN,SAAS,kBAA8C,CAAC;AAC1D,eAAS,eACR,0BACF,IAAI;AAMJ,YAAM,QAAQ,SAAS;AACvB,UAAI,OAAO;AACT,mBAAW,SAAS,OAAO,KAAK,KAAK,GAAG;AACtC,gBAAM,UAAU,MAAM,KAAK;AAC3B,cAAI,CAAC,MAAM,QAAQ,OAAO,EAAG;AAC7B,gBAAM,KAAK,IAAI,QAAQ;YACrB,CAAC,MACC,EACE,OAAO,MAAM,YACb,MAAM,QACN,KAAK,UAAU,CAAC,EAAE,SAAS,cAAc;UAE/C;AACA,cAAK,MAAM,KAAK,EAAgB,WAAW,EAAG,QAAO,MAAM,KAAK;QAClE;AACA,YAAI,OAAO,KAAK,KAAK,EAAE,WAAW,EAAG,QAAO,SAAS;MACvD;AAEA,aAAO;IACT;IACA,oBAAoB,UAAU;AAC5B,YAAM,WAAW,EAAE,GAAG,SAAS;AAC/B,YAAM,eAAe,SAAS;AAG9B,UAAI,cAAc;AAChB,eAAO,aAAa,eAAe;AACnC,YAAI,OAAO,KAAK,YAAY,EAAE,WAAW;AACvC,iBAAO,SAAS;MACpB;AACA,YAAM,UAAU,SAAS;AAGzB,UAAI,SAAS;AACX,eAAO,QAAQ,0BAA0B;AACzC,eAAO,QAAQ,mBAAmB;AAClC,YAAI,OAAO,KAAK,OAAO,EAAE,WAAW,EAAG,QAAO,SAAS;MACzD;AACA,aAAO;IACT;EACF;EAEA,UAAU;IACR,QAAQ,MAAM,OAAO;AACnB,YAAM,OAAgC;QACpC,CAAC,gCAAgC,GAAG;MACtC;AACA,UAAI,OAAO;AACT,aAAK,KAAK;UACR;UACA,oBAAoB,IAAI;QAC1B,CAAC;MACH;AACA,aAAO;IACT;EACF;EAEA,QAAQ;;IAEN,UAAU,CAAC;IACX,yBAAyB,EAAE,OAAO,OAAO,GAAG;AAC1C,aAAO;QACL,oBAAoB;UAClB,eAAe;UACf,oBAAoB,QAAQ,UAAU;UACtC,0BAA0B;QAC5B;MACF;IACF;EACF;EAEA,QAAQ;IACN,aAAa;IACb,aAAa,MAAMC,IAAG,WAAW,UAAU;IAC3C,eAAe;AACb,UAAI;AACF,cAAM,WAAW,KAAK;UACpBA,IAAG,aAAa,KAAK,KAAK,YAAY,eAAe,GAAG,OAAO;QACjE;AACA,cAAM,UAAU,SAAS,kBAAkB,CAAC;AAC5C,eACE,CAAC,CAAC,QAAQ,0BAA0B,KACpC,CAAC,CAAC,QAAQ,mBAAmB;MAEjC,QAAQ;AACN,eAAO;MACT;IACF;EACF;EAEA,OAAO;IACL,cAAc;IACd,iBAAiB;EACnB;EAEA,MAAM;IACJ,SAAS;MACP,aAAa,CAAC,yBAAyB;MACvC,aAAa;MACb,gBAAgB,CAAC,QAAQ;MACzB,YAAY,CAAC,SAAS;IACxB;EACF;EAEA,OAAO;IACL,eAAe,CAAC,UAAU;EAC5B;EAEA,SAAS;IACP,uBAAuB;IACvB,WAAW;AACT,YAAM,cAAc,KAAK,KAAK,YAAY,UAAU;AACpD,YAAM,QAAgC,CAAC;AACvC,YAAM,cAAc,CAAC,MAAc;AACjC,YAAI;AACF,iBAAOA,IAAG,YAAY,CAAC;QACzB,QAAQ;AACN,iBAAO,CAAC;QACV;MACF;AACA,YAAM,YAAY,CAAC,MAAc;AAC/B,YAAI;AACF,iBAAOA,IAAG,SAAS,CAAC,EAAE,YAAY;QACpC,QAAQ;AACN,iBAAO;QACT;MACF;AACA,UAAI;AACF,mBAAW,QAAQA,IAAG,YAAY,WAAW,GAAG;AAC9C,gBAAM,UAAU,KAAK,KAAK,aAAa,IAAI;AAC3C,cAAI,CAAC,UAAU,OAAO,EAAG;AACzB,qBAAW,SAASA,IAAG,YAAY,OAAO,GAAG;AAC3C,kBAAM,YAAY,KAAK,KAAK,SAAS,KAAK;AAC1C,gBAAI,MAAM,SAAS,QAAQ,GAAG;AAC5B,oBAAM,KAAK,EAAE,UAAU,UAAU,CAAC;YACpC;AAGA,gBAAI,UAAU,SAAS,GAAG;AACxB,oBAAM,eAAe,KAAK,KAAK,WAAW,WAAW;AACrD,yBAAW,OAAO,YAAY,YAAY,GAAG;AAC3C,oBAAI,IAAI,SAAS,QAAQ,GAAG;AAC1B,wBAAM,KAAK;oBACT,UAAU,KAAK,KAAK,cAAc,GAAG;kBACvC,CAAC;gBACH;cACF;YACF;UACF;QACF;MACF,QAAQ;MAER;AACA,aAAO;IACT;IAEA,UAAU,UAAkB,gBAA4C;AACtE,YAAM,EAAE,OAAO,cAAc,IAAI,aAAa,UAAU,cAAc;AACtE,UAAI,MAAM,WAAW,EAAG,QAAO;AAI/B,YAAM,WAAW,KAAK,SAAS,UAAU,QAAQ;AACjD,YAAM,iBAAiB,SAAS,SAAS,aAAa;AAEtD,UAAI;AACJ,YAAM,QAA8B,CAAC;AACrC,YAAM,SAAgC,CAAC;AACvC,YAAM,WAAoC,CAAC;AAC3C,UAAI,YAAY;AAChB,UAAI,UAAU;AACd,UAAI;AAEJ,YAAM,cAAc,oBAAI,IAAoB;AAE5C,YAAM,sBAAsB,oBAAI,IAG9B;AAIF,YAAM,aAAyB,CAAC;AAEhC,YAAM,aAAuB,CAAC;AAC9B,YAAM,cAAwB,CAAC;AAE/B,eAAS,UAAU,GAAG,UAAU,MAAM,QAAQ,WAAW;AACvD,cAAM,OAAO,MAAM,OAAO;AAC1B,YAAI;AACJ,YAAI;AACF,gBAAM,KAAK,MAAM,IAAI;QACvB,QAAQ;AACN;QACF;AAEA,cAAM,OAAO,IAAI;AACjB,cAAM,YAAY,IAAI;AACtB,cAAM,UAAU,IAAI;AACpB,cAAM,OAAO,IAAI,YACb,IAAI,KAAK,IAAI,SAAmB,EAAE,QAAQ,IAC1C,KAAK,IAAI;AAGb,cAAM,OAAO,IAAI;AACjB,cAAM,aAAa,IAAI;AACvB,YAAI,SAAS,SAAS,UAAU,SAAS,cAAc;AACrD,qBAAW,KAAK;YACd;YACA,YAAY,cAAc;YAC1B;YACA,WAAW;YACX,aAAa;UACf,CAAC;QACH;AAEA,YAAI,CAAC,QAAQ,WAAW;AACtB,gBAAM,SAAS;YACb,YAAY,IAAI;YAChB,KAAK,IAAI;YACT,aAAa;UACf;AACA,cAAI,SAAS;AAGX,mBAAO;cACL,WAAW,SAAS,OAAO;cAC3B,iBAAiB;cACjB,kBAAkB;cAClB,GAAG;YACL;UACF,WACE,CAAC,kBACD,QAAQ,KAAK,QAAQ,KACrB,cAAc,UACd;AAIA,mBAAO;cACL,WAAW;cACX,iBAAiB;cACjB,kBAAkB;cAClB,GAAG;YACL;UACF,OAAO;AACL,mBAAO,EAAE,WAAW,GAAG,OAAO;UAChC;QACF;AAEA,cAAM,MAAM,MAAM,aAAa,aAAa;AAE5C,YAAI,SAAS,QAAQ;AACnB,gBAAM,MAAM,IAAI;AAChB,gBAAM,UAAU,KAAK;AACrB,cAAI;AACJ,gBAAM,YAAsB,CAAC;AAC7B,gBAAM,cAAc,oBAAI,IAGtB;AAEF,cAAI,OAAO,YAAY,UAAU;AAC/B,sBAAU,QAAQ,MAAM,GAAG,GAAG;AAC9B,sBAAU,KAAK,OAAO;UACxB,WAAW,MAAM,QAAQ,OAAO,GAAG;AACjC,uBAAW,SAAS,SAAS;AAC3B,kBAAI,OAAO,UAAU,YAAY,UAAU,KAAM;AACjD,oBAAM,IAAI;AACV,kBAAI,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,UAAU;AACnD,0BAAU,KAAK,EAAE,IAAI;cACvB,WAAW,EAAE,SAAS,eAAe;AACnC,sBAAM,UAAU,4BAA4B,EAAE,OAAO;AAErD,sBAAM,MACJ,OAAO,EAAE,YAAY,WACjB,EAAE,UACF,KAAK,UAAU,EAAE,WAAW,EAAE;AACpC,4BAAY,IAAI,EAAE,aAAuB;kBACvC,eAAe;kBACf,YAAY;kBACZ,aAAa;gBACf,CAAC;cACH;YACF;AACA,gBAAI,UAAU,SAAS,GAAG;AACxB,wBAAU,UAAU,CAAC,EAAE,MAAM,GAAG,GAAG;YACrC;UACF;AACA,cAAI,CAAC,eAAe,QAAS,eAAc;AAE3C,cAAI,cAAc,UAAU,KAAK,IAAI;AAGrC,gBAAM,SAAS,IAAI,WAAW;AAC9B,gBAAM,YAAY,IAAI,qBAAqB;AAC3C,cAAI,UAAU,WAAW;UAEzB,WAAW,YAAY,WAAW,KAAK,YAAY,OAAO,GAAG;AAG3D,uBAAW,CAAC,IAAI,MAAM,KAAK,aAAa;AACtC,kCAAoB,IAAI,IAAI,MAAM;YACpC;UACF,WAAW,YAAY,SAAS,GAAG;AAEjC,kBAAM,CAAC,WAAW,UAAU,IAAI,mBAAmB,WAAW;AAC9D,gBAAI,YAAY;AACd,4BAAc;YAChB;AAGA,kBAAM,WAAW,gBAAgB,WAAW;AAE5C,qBAAS,KAAK;cACZ,WAAW;cACX,SAAS;cACT,MAAM;cACN,SAAS;cACT,aAAa;cACb,aAAa;cACb,YAAY;cACZ;cACA,eAAe,YAAY;cAC3B,kBAAkB;cAClB,iBAAiB;cACjB;cACA,YAAY,cAAc;cAC1B,WAAW,CAAC;cACZ;YACF,CAAC;AACD,uBAAW,KAAK,OAAO;UACzB;AAEA,gBAAM,KAAK;YACT,WAAW;YACX,WAAW;YACX,aAAa;YACb,MAAM;YACN,gBAAgB;YAChB,aAAa;YACb,cAAc;YACd,iBAAiB;YACjB,qBAAqB;YACrB,iBAAiB;UACnB,CAAC;AACD,sBAAY,KAAK,OAAO;QAC1B;AAEA,YAAI,SAAS,aAAa;AACxB,gBAAM,MAAM,IAAI;AAChB,gBAAM,QAAQ,KAAK;AACnB,gBAAM,QAAQ,KAAK;AACnB,cAAI,QAAQ,SAAS,CAAC,KAAK,MAAO,MAAK,QAAQ;AAG/C,gBAAM,YAAsB,CAAC;AAC7B,cAAI,cAAc;AAClB,cAAI,aAAa;AACjB,gBAAM,YAA8B,CAAC;AAErC,gBAAM,UAAU,KAAK;AACrB,cAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,uBAAW,SAAS,SAAS;AAC3B,kBAAI,OAAO,UAAU,YAAY,UAAU,KAAM;AACjD,oBAAM,IAAI;AAEV,kBAAI,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,UAAU;AACnD,0BAAU,KAAK,EAAE,IAAI;cACvB;AAEA,kBAAI,EAAE,SAAS,YAAY;AACzB,8BAAc;AACd,oBAAI,OAAO,EAAE,aAAa,UAAU;AAClC,4BAAU,KAAK;EAAe,EAAE,QAAQ;YAAe;gBACzD;AAEA,uBAAO,KAAK;kBACV,WAAW;kBACX,WAAW;kBACX,aAAa;kBACb,SACE,OAAO,EAAE,aAAa,WAClB,EAAE,SAAS,MAAM,GAAG,GAAK,IACzB;kBACN,UAAU;oBACR,eAAe,CAAC,CAAC,EAAE;kBACrB;gBACF,CAAC;cACH;AAEA,kBAAI,EAAE,SAAS,YAAY;AACzB,6BAAa;AACb,sBAAM,WAAY,EAAE,QAAmB;AACvC,sBAAM,QAAQ,EAAE;AAChB,sBAAM,YAAY,QAAQ,KAAK,UAAU,KAAK,IAAI;AAGlD,oBAAI;AACJ,oBAAI,aAAa,WAAW,OAAO;AACjC,8BAAa,MAAM,SAAS,MAAM;gBACpC;AAEA,0BAAU,KAAK;kBACb,WAAY,EAAE,MAAiB;kBAC/B;kBACA,UAAU,mBAAmB,QAAQ;kBACrC;kBACA;kBACA,aAAa;gBACf,CAAC;AAED,uBAAO,KAAK;kBACV,WAAW;kBACX,WAAW;kBACX,aAAa;kBACb;kBACA,WAAW,WAAW,MAAM,GAAG,GAAM;kBACrC,UAAU,EAAE,aAAa,EAAE,GAAG;gBAChC,CAAC;cACH;YACF;UACF;AAEA,gBAAM,cAAc,UAAU,KAAK,IAAI;AACvC,gBAAM,cAAe,OAAO,gBAA2B;AACvD,gBAAM,YAAa,OAAO,iBAA4B;AACtD,gBAAM,YAAa,OAAO,2BAAsC;AAChE,gBAAM,gBACH,OAAO,+BAA0C;AACpD,gBAAM,YAAY,cAAc,YAAY;AAC5C,gBAAM,SAAS,cAAc,KAAK,YAAY,KAAK,gBAAgB;AAEnE,mBAAS,KAAK;YACZ,WAAW;YACX,SAAS;YACT,MAAM;YACN,SAAS;YACT,aAAa;YACb;YACA;YACA,UAAU;YACV,eAAe,YAAY;YAC3B;YACA,YAAY,QAAQ,KAAK,UAAU,KAAK,IAAI;YAC5C,eAAe,SAAS,YAAY;YACpC,cAAc,YAAY,IAAI,YAAY;YAC1C,kBAAkB;YAClB,iBAAiB,YAAY;YAC7B;YACA,YAAY,cAAc;YAC1B;YACA,aAAa,oBAAI,IAAI;UACvB,CAAC;AACD,qBAAW,KAAK,OAAO;AAEvB,gBAAM,KAAK;YACT,WAAW;YACX,WAAW;YACX,aAAa;YACb;YACA,MAAM;YACN;YACA,cAAc;YACd,iBAAiB;YACjB,qBAAqB;YACrB,iBAAiB;UACnB,CAAC;AACD,sBAAY,KAAK,OAAO;QAC1B;AAGA,YAAI,SAAS,QAAQ;AACnB,gBAAM,MAAM,IAAI;AAChB,gBAAM,UAAU,KAAK;AACrB,cAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,uBAAW,SAAS,SAAS;AAC3B,kBAAI,OAAO,UAAU,YAAY,UAAU,KAAM;AACjD,oBAAM,IAAI;AAEV,kBAAI,EAAE,SAAS,eAAe;AAC5B,sBAAM,gBAAgB,EAAE;AACxB,uBAAO,KAAK;kBACV,WAAW;kBACX,WAAW;kBACX,aAAa;kBACb,YACE,OAAO,kBAAkB,WACrB,cAAc,MAAM,GAAG,GAAG,IAC1B;kBACN,UAAU;oBACR,aAAa,EAAE;oBACf,UAAU,EAAE;kBACd;gBACF,CAAC;cACH;AAEA,kBAAI,EAAE,SAAS,SAAS;AACtB,sBAAM,MAAM,EAAE;AACd,uBAAO,KAAK;kBACV,WAAW;kBACX,WAAW;kBACX,aAAa;kBACb,UAAU;oBACR,YAAY,KAAK;oBACjB,aAAa,KAAK;kBACpB;gBACF,CAAC;cACH;YACF;UACF;QACF;AAGA,YAAI,SAAS,UAAU;AACrB,gBAAM,OAAO,IAAI;AACjB,gBAAM,QAAQ,IAAI;AAClB,gBAAM,UAAU,IAAI;AAEpB,cAAI,MAAM,SAAS,eAAe,UAAU,SAAS;AACnD,mBAAO,KAAK;cACV,WAAW;cACX,WAAW;cACX,aAAa;cACb,SACE,OAAO,MAAM,YAAY,WAAW,KAAK,UAAU;cACrD,UAAU;gBACR;gBACA,cAAc,MAAM;gBACpB,YAAY,MAAM;gBAClB,WAAW,MAAM;cACnB;YACF,CAAC;UACH,WACE,YAAY,uBACZ,UAAU,cACV;AACA,mBAAO,KAAK;cACV,WAAW;cACX,WAAW,WAAW;cACtB,aAAa;cACb,UAAU;gBACR;gBACA;gBACA,WAAW,IAAI;gBACf,WAAW,IAAI;gBACf,YAAY,IAAI;gBAChB,uBAAuB,IAAI;cAC7B;YACF,CAAC;UACH;QACF;AAGA,YAAI,SAAS,yBAAyB;AACpC,gBAAM,OAAO,IAAI;AACjB,gBAAM,YAAY,IAAI;AACtB,iBAAO,KAAK;YACV,WAAW;YACX,WAAW;YACX,aAAa;YACb,UAAU,EAAE,WAAW,GAAI,QAAQ,CAAC,EAAG;UACzC,CAAC;QACH;AAGA,YAAI,SAAS,YAAY;AACvB,gBAAM,OAAO,IAAI;AACjB,gBAAM,YAAY,MAAM;AACxB,gBAAM,eAAe,MAAM;AAE3B,cAAI,aAAa,MAAM,YAAY;AAEjC,mBAAO,KAAK;cACV,WAAW;cACX,WAAW,YAAY,YAAY,SAAS,KAAK;cACjD,aAAa;cACb,UAAW,MAAM,YAAY,MAAM;cAGnC,UAAU;gBACR;gBACA,YAAY,MAAM;cACpB;YACF,CAAC;UACH,WAAW,iBAAiB,kBAAkB;AAE5C,kBAAM,OAAQ,MAAM,mBAAmB,IAAI;AAG3C,kBAAMC,WAAW,MAAM,WAAW,IAAI;AAGtC,gBAAI,QAAQA,UAAS;AACnB,0BAAY,IAAI,MAAM,SAASA,QAAO,EAAE;YAC1C;AACA,mBAAO,KAAK;cACV,WAAW;cACX,WAAW;cACX,aAAa;cACb,UAAU;gBACR,iBAAiB;gBACjB,WAAW,IAAI;cACjB;YACF,CAAC;UACH;QACF;AAGA,YAAI,SAAS,mBAAmB;AAC9B,gBAAM,YAAY,IAAI;AAEtB,cAAI,cAAc,aAAa,OAAO,IAAI,YAAY,UAAU;AAC9D,gBAAI;AACF,oBAAM,KAAK,KAAK,MAAM,IAAI,OAAO;AACjC,oBAAM,OAAO,GAAG;AAChB,oBAAM,SAAS,GAAG;AAClB,kBAAI,QAAQ,QAAQ;AAClB,4BAAY,IAAI,MAAM,SAAS,MAAM,EAAE;cACzC;YACF,QAAQ;AAEN,oBAAM,YAAY,IAAI,QAAQ;gBAC5B;cACF;AACA,oBAAM,YAAY,IAAI,QAAQ;gBAC5B;cACF;AACA,kBAAI,YAAY,CAAC,KAAK,YAAY,CAAC,GAAG;AACpC,4BAAY,IAAI,UAAU,CAAC,GAAG,SAAS,UAAU,CAAC,CAAC,EAAE;cACvD;YACF;UACF;AACA,iBAAO,KAAK;YACV,WAAW;YACX,WAAW,SAAS,aAAa,SAAS;YAC1C,aAAa;YACb,SACE,OAAO,IAAI,YAAY,WACnB,IAAI,QAAQ,MAAM,GAAG,GAAG,IACxB;UACR,CAAC;QACH;AAGA,YAAI,SAAS,eAAe;AAC1B,iBAAO,KAAK;YACV,WAAW;YACX,WAAW;YACX,aAAa;YACb,SACE,OAAO,IAAI,eAAe,WACtB,IAAI,WAAW,MAAM,GAAG,GAAG,IAC3B;UACR,CAAC;QACH;MACF;AAEA,UAAI,QAAQ,eAAe,CAAC,KAAK;AAC/B,aAAK,cAAc;AAGrB,UAAI,YAAY,OAAO,GAAG;AACxB,mBAAW,OAAO,UAAU;AAC1B,qBAAW,MAAM,IAAI,WAAW;AAC9B,kBAAM,WAAW,YAAY,IAAI,GAAG,SAAS;AAC7C,gBAAI,aAAa,GAAG,aAAa,UAAU,GAAG,aAAa;AACzD,iBAAG,oBAAoB;UAC3B;QACF;MACF;AAIA,UACE,iBAAiB,KACjB,WAAW,SAAS,KACpB,WAAW,UAAU,GACrB;AACA,eAAO;UACL;UACA,OAAO,CAAC;UACR,QAAQ,CAAC;UACT,UAAU,CAAC;UACX,eAAe;;UACf,kBAAkB;QACpB;MACF;AAGA,UACE,mBAAmB,KACnB,QACA,WAAW,SAAS,KACpB,WAAW,UAAU,GACrB;AACA,cAAM,EAAE,gBAAgB,aAAa,IAAI;UACvC;UACA,KAAK;QACP;AAEA,YAAI,aAAa,SAAS,GAAG;AAE3B,gBAAM,cAAc,IAAI;YACtB,eAAe,IAAI,CAAC,MAAM,WAAW,CAAC,EAAE,SAAS;UACnD;AAGA,gBAAM,eAAe,SAAS;YAAO,CAAC,GAAG,MACvC,YAAY,IAAI,WAAW,CAAC,CAAC;UAC/B;AACA,gBAAM,YAAY,MAAM;YAAO,CAAC,GAAG,MACjC,YAAY,IAAI,YAAY,CAAC,CAAC;UAChC;AAGA,mBAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,yBAAa,CAAC,IAAI,EAAE,GAAG,aAAa,CAAC,GAAG,SAAS,EAAE;UACrD;AACA,mBAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,sBAAU,CAAC,IAAI,EAAE,GAAG,UAAU,CAAC,GAAG,WAAW,EAAE;UACjD;AAGA,gBAAM,QAAuB,CAAC;AAC9B,qBAAW,UAAU,cAAc;AACjC,kBAAM,gBAAgB,IAAI;cACxB,OAAO,WAAW,IAAI,CAAC,MAAM,WAAW,CAAC,EAAE,SAAS;YACtD;AACA,kBAAM,WAAW,WAAW,OAAO,WAAW,CAAC,CAAC,EAAE;AAClD,kBAAM,gBAAgB,GAAG,KAAK,SAAS,IAAI,QAAQ;AACnD,kBAAM,cAAc,WAAW,OAAO,WAAW,CAAC,CAAC,EAAE;AAErD,kBAAM,eAAe,SAClB,OAAO,CAAC,GAAG,MAAM,cAAc,IAAI,WAAW,CAAC,CAAC,CAAC,EACjD,IAAI,CAAC,GAAG,OAAO;cACd,GAAG;cACH,WAAW;cACX,SAAS;YACX,EAAE;AACJ,kBAAM,YAAY,MACf,OAAO,CAAC,GAAG,MAAM,cAAc,IAAI,YAAY,CAAC,CAAC,CAAC,EAClD,IAAI,CAAC,GAAG,OAAO;cACd,GAAG;cACH,WAAW;cACX,WAAW;YACb,EAAE;AAEJ,kBAAM,KAAK;cACT,MAAM;gBACJ,WAAW;gBACX,iBAAiB,OAAO;gBACxB,kBAAkB;gBAClB,OAAO,KAAK;gBACZ,KAAK,KAAK;gBACV,YAAY,KAAK;gBACjB,aAAa;cACf;cACA,OAAO;cACP,QAAQ,CAAC;;cACT,UAAU;cACV;YACF,CAAC;UACH;AAEA,iBAAO;YACL;YACA,OAAO;YACP;;YACA,UAAU;YACV;YACA;YACA,qBACE,oBAAoB,OAAO,IAAI,sBAAsB;UACzD;QACF;MACF;AAEA,aAAO;QACL;QACA;QACA;QACA;QACA;QACA,qBACE,oBAAoB,OAAO,IAAI,sBAAsB;MACzD;IACF;EACF;AACF;AAEA,eAAe,MAAM;AGhiCrB,IAAM,yBAAiD;EACrD,WAAW;EACX,iBAAiB;EACjB,YAAY;EACZ,WAAW;EACX,mBAAmB;EACnB,OAAO;EACP,MAAM;EACN,gBAAgB;EAChB,MAAM;EACN,cAAc;EACd,YAAY;AACd;AAEA,SAAS,mBAAmB,UAA0B;AACpD,SAAO,uBAAuB,QAAQ,KAAK,oBAAoB,QAAQ;AACzE;AAEA,IAAM,aAAaC,MAAK,KAAKC,IAAG,QAAQ,GAAG,SAAS;AAEpD,IAAM,SAAwB;EAC5B,IAAI;EAEJ,QAAQ;IACN,KAAK;IACL,YAAYD,MAAK,KAAK,YAAY,eAAe;IACjD,cAAc;EAChB;EAEA,OAAO;;IAEL,QAAQ,CAAC,gBAAgB,eAAe,cAAc,WAAW;IACjE,mBAAmB,UAAU,MAAM;AACjC,YAAM,WAAW,EAAE,GAAG,SAAS;AAC/B,YAAM,UAAUA,MAAK,KAAK,KAAK,YAAY,OAAO,cAAc;AAChE,YAAM,SAASA,MAAK,KAAK,KAAK,YAAY,OAAO,YAAY;AAG7D,YAAM,QAAQ;QACX,SAAS,SAAuC,CAAC;MACpD;AACA,iBAAW,SAAS,KAAK,QAAQ;AAC/B,cAAM,KAAK,IAAI,MAAM,KAAK,KAAK,CAAC;AAEhC,cAAM,KAAK,IAAK,MAAM,KAAK,EACxB,IAAI,CAAC,WAAW;UACf,GAAG;UACH,QACG,MAAM,SAA4C,CAAC,GACpD;YACA,CAAC,MACC,CAAE,EAAE,SAAoB,SAAS,YAAY,KAC7C,CAAE,EAAE,MAAiB,SAAS,YAAY;UAC9C;QACF,EAAE,EACD,OAAO,CAAC,UAAW,MAAM,MAAoB,SAAS,CAAC;AAEzD,cAAM,KAAK,EAAgB,KAAK;UAC/B,OAAO;YACL;cACE,MAAM;cACN,SAAS,QAAQ,OAAO,WAAW,KAAK,IAAI,GAAG,KAAK,QAAQ,aAAa,EAAE;YAC7E;UACF;QACF,CAAC;MACH;AACA,eAAS,QAAQ;AAGjB,eAAS,aACN,SAAS,cAA0C,CAAC;AACtD,eAAS,WAAuC,aAAa;QAC5D,SAAS;QACT,MAAM,CAAC,MAAM;MACf;AAGA,eAAS,YACN,SAAS,aAAyC,CAAC;AACtD,aAAO,OAAO,SAAS,WAAsC;QAC3D,SAAS;QACT,QAAQ;QACR,cAAc;QACd,cAAc,oBAAoB,KAAK,IAAI;MAC7C,CAAC;AAED,aAAO;IACT;IACA,oBAAoB,UAAU;AAC5B,YAAM,WAAW,EAAE,GAAG,SAAS;AAG/B,YAAM,QAAQ,SAAS;AACvB,UAAI,OAAO;AACT,mBAAW,SAAS,OAAO,KAAK,KAAK,GAAG;AACtC,gBAAM,KAAK,IAAK,MAAM,KAAK,EACxB,IAAI,CAAC,WAAW;YACf,GAAG;YACH,QACG,MAAM,SAA4C,CAAC,GACpD;cACA,CAAC,MACC,CAAE,EAAE,SAAoB,SAAS,YAAY,KAC7C,CAAE,EAAE,MAAiB,SAAS,YAAY;YAC9C;UACF,EAAE,EACD,OAAO,CAAC,UAAW,MAAM,MAAoB,SAAS,CAAC;AAC1D,cAAK,MAAM,KAAK,EAAgB,WAAW,EAAG,QAAO,MAAM,KAAK;QAClE;AACA,YAAI,OAAO,KAAK,KAAK,EAAE,WAAW,EAAG,QAAO,SAAS;MACvD;AAGA,aAAO,SAAS;AAGhB,YAAM,UAAU,SAAS;AAGzB,UAAI,SAAS;AACX,eAAO,QAAQ;AACf,YAAI,OAAO,KAAK,OAAO,EAAE,WAAW,EAAG,QAAO,SAAS;MACzD;AAGA,aAAO,SAAS;AAEhB,aAAO;IACT;EACF;EAEA,UAAU;IACR,QAAQ,MAAM;AACZ,aAAO;QACL,CAAC,4BAA4B,MAAM;QACnC,CAAC,2BAA2B,OAAO;QACnC,CAAC,kCAAkC,MAAM;QACzC,CAAC,kCAAkC,oBAAoB,IAAI,EAAE;QAC7D,CAAC,kCAAkC,MAAM;QACzC,CAAC,gCAAgC,MAAM;MACzC;IACF;EACF;EAEA,QAAQ;IACN,UAAU;MACR,YAAY;MACZ,WAAW;MACX,aAAa;IACf;IAEA,iBAAiB,MAA4B;AAE3C,YAAM,WAAY,KAAiC;AAGnD,UAAI,UAAU,YAAY,MAAM,QAAQ,SAAS,QAAQ,GAAG;AAC1D,cAAM,WAAW,CAAC,GAAG,SAAS,QAAQ,EACnC,QAAQ,EACR,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAChC,YAAI,UAAU,SAAS;AACrB,gBAAM,OACJ,OAAO,SAAS,YAAY,WACxB,SAAS,UACT,MAAM,QAAQ,SAAS,OAAO,IAC3B,SAAS,QACP,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAC/B,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK,IAAI,IACZ;AACR,cAAI,MAAM;AACP,iBAAiC,cAAc;UAClD;QACF;MACF;AACA,aAAO;IACT;IAEA,yBAAyB,EAAE,OAAO,OAAO,GAAG;AAC1C,aAAO,EAAE,UAAU,QAAQ,UAAU,QAAQ,OAAO;IACtD;EACF;EAEA,QAAQ;IACN,aAAa;IACb,aAAa,MAAMF,IAAG,WAAW,UAAU;IAC3C,eAAe;AACb,UAAI;AACF,cAAM,WAAW,KAAK;UACpBA,IAAG,aAAaE,MAAK,KAAK,YAAY,eAAe,GAAG,OAAO;QACjE;AACA,eAAO,CAAC,CAAC,SAAS,WAAW;MAC/B,QAAQ;AACN,eAAO;MACT;IACF;EACF;EAEA,OAAO;IACL,cAAc;IACd,iBAAiB;EACnB;EAEA,MAAM;IACJ,aAAa;IACb,SAAS;MACP,aAAa,CAAC,0BAA0B,2BAA2B;MACnE,aAAa;MACb,gBAAgB,CAAC,uBAAuB;MACxC,YAAY,CAAC,2BAA2B;IAC1C;EACF;EAEA,SAAS;IACP,uBAAuB;IACvB,WAAW;AACT,YAAM,SAASA,MAAK,KAAK,YAAY,KAAK;AAC1C,YAAM,QAAgC,CAAC;AACvC,YAAM,cAAc,CAAC,MAAc;AACjC,YAAI;AACF,iBAAOF,IAAG,YAAY,CAAC;QACzB,QAAQ;AACN,iBAAO,CAAC;QACV;MACF;AACA,iBAAW,WAAW,YAAY,MAAM,GAAG;AACzC,cAAM,WAAWE,MAAK,KAAK,QAAQ,SAAS,OAAO;AACnD,mBAAW,SAAS,YAAY,QAAQ,GAAG;AACzC,cAAI,MAAM,WAAW,UAAU,KAAK,MAAM,SAAS,OAAO;AACxD,kBAAM,KAAK,EAAE,UAAUA,MAAK,KAAK,UAAU,KAAK,EAAE,CAAC;QACvD;MACF;AACA,aAAO;IACT;IAEA,UAAU,UAAkB,gBAA4C;AACtE,UAAI;AACJ,UAAI;AACF,eAAOF,IAAG,SAAS,QAAQ,EAAE;MAC/B,QAAQ;AACN,eAAO;MACT;AACA,UAAI,QAAQ,eAAgB,QAAO;AAGnC,UAAI;AACJ,UAAI;AACF,kBAAU,KAAK,MAAMA,IAAG,aAAa,UAAU,OAAO,CAAC;MACzD,QAAQ;AACN,eAAO;MACT;AAEA,YAAM,YAAY,QAAQ;AAC1B,UAAI,CAAC,UAAW,QAAO;AAEvB,YAAM,WAAW,QAAQ;AAGzB,UAAI,CAAC,UAAU,OAAQ,QAAO;AAE9B,YAAM,OAA4B;QAChC;QACA,aAAa,QAAQ,YACjB,IAAI,KAAK,QAAQ,SAAmB,EAAE,QAAQ,IAC9C;MACN;AAEA,YAAM,QAA8B,CAAC;AACrC,YAAM,SAAgC,CAAC;AACvC,YAAM,iBAA0C,CAAC;AACjD,UAAI,YAAY;AAChB,UAAI,UAAU;AACd,UAAI;AAEJ,iBAAW,OAAO,UAAU;AAC1B,cAAM,OAAO,IAAI;AACjB,cAAM,YAAY,IAAI;AACtB,cAAM,cAAc,YAChB,IAAI,KAAK,SAAS,EAAE,QAAQ,IAC5B,KAAK,IAAI;AAEb,YAAI,SAAS,QAAQ;AACnB,gBAAM,UAAU,IAAI;AACpB,gBAAM,YAAsB,CAAC;AAC7B,cAAI,SAAS;AACX,uBAAW,SAAS,SAAS;AAC3B,kBAAI,MAAM,KAAM,WAAU,KAAK,MAAM,IAAI;YAC3C;UACF;AACA,gBAAM,cAAc,UAAU,KAAK,IAAI;AACvC,gBAAM,UAAU,UAAU,CAAC,GAAG,MAAM,GAAG,GAAG;AAC1C,cAAI,CAAC,eAAe,QAAS,eAAc;AAE3C,gBAAM,KAAK;YACT;YACA,WAAW;YACX;YACA,MAAM;YACN,gBAAgB;YAChB,aAAa;YACb,cAAc;YACd,iBAAiB;YACjB,qBAAqB;YACrB,iBAAiB;UACnB,CAAC;AAED,cAAI,YAAY,SAAS,GAAG;AAC1B,2BAAe,KAAK;cAClB;cACA,SAAS;cACT,MAAM;cACN,SAAS;cACT;cACA,aAAa;cACb,YAAY;cACZ,UAAU;cACV,eAAe,YAAY;cAC3B,kBAAkB;cAClB,iBAAiB;cACjB,WAAW,CAAC;cACZ,aAAa,oBAAI,IAAI;YACvB,CAAC;UACH;QACF;AAEA,YAAI,SAAS,UAAU;AACrB,gBAAM,QAAQ,IAAI;AAClB,gBAAM,SAAS,IAAI;AACnB,cAAI,SAAS,CAAC,KAAK,MAAO,MAAK,QAAQ;AAEvC,gBAAM,cAAc,QAAQ,SAAS;AACrC,gBAAM,YAAY,QAAQ,UAAU;AACpC,gBAAM,YAAY,QAAQ,UAAU;AACpC,gBAAM,YAAY,QAAQ,YAAY;AAEtC,gBAAM,KAAK;YACT;YACA,WAAW;YACX;YACA;YACA,MAAM;YACN,gBACE,OAAO,IAAI,YAAY,WACnB,IAAI,QAAQ,MAAM,GAAG,GAAG,IACxB;YACN;YACA,cAAc;YACd,iBAAiB;YACjB,qBAAqB;YACrB,iBAAiB;UACnB,CAAC;AAGD,gBAAM,YAAsB,CAAC;AAC7B,cAAI,OAAO,IAAI,YAAY,SAAU,WAAU,KAAK,IAAI,OAAO;AAG/D,gBAAM,eAAe,IAAI;AAGzB,gBAAM,YAA8B,CAAC;AACrC,gBAAM,cAAc,oBAAI,IAGtB;AAEF,cAAI,cAAc;AAChB,qBAAS,QAAQ,GAAG,QAAQ,aAAa,QAAQ,SAAS;AACxD,oBAAM,KAAK,aAAa,KAAK;AAC7B,oBAAM,WAAa,GAAG,QAAQ,GAAG,eAA2B;AAC5D,oBAAM,YAAY,GAAG,OAAO,KAAK,UAAU,GAAG,IAAI,IAAI;AACtD,oBAAM,YACH,GAAG,MAAiB,GAAG,QAAQ,IAAI,WAAW,IAAI,KAAK;AAE1D,wBAAU,KAAK;gBACb;gBACA;gBACA,UAAU,mBAAmB,QAAQ;gBACrC;cACF,CAAC;AAED,oBAAM,SAAS,GAAG;AAGlB,oBAAM,aAAa,SAAS,CAAC,GAAG;AAGhC,oBAAM,cAAc,YAAY;AAGhC,oBAAM,SACJ,OAAO,aAAa,WAAW,WAC3B,YAAY,SACZ,aACE,KAAK,UAAU,UAAU,IACzB;AACR,kBAAI,QAAQ;AACV,4BAAY,IAAI,WAAW;kBACzB,eAAe,OAAO;kBACtB,YAAY;gBACd,CAAC;cACH;AAEA,qBAAO,KAAK;gBACV;gBACA,WAAW;gBACX;gBACA;gBACA,WAAW,WAAW,MAAM,GAAG,GAAI;gBACnC,YAAY,QAAQ,MAAM,GAAG,GAAI;cACnC,CAAC;YACH;UACF;AAGA,cAAI,cAAc;AAClB,gBAAM,WAAW,IAAI;AAGrB,cAAI,UAAU;AACZ,0BAAc;AACd,uBAAW,KAAK,UAAU;AACxB,oBAAM,OAAQ,EAAE,eAAe,EAAE;AACjC,kBAAI,KAAM,WAAU,KAAK;EAAe,IAAI;YAAe;AAC3D,qBAAO,KAAK;gBACV;gBACA,WAAW;gBACX;gBACA,SAAS,MAAM,MAAM,GAAG,GAAG;cAC7B,CAAC;YACH;UACF;AAEA,gBAAM,cAAc,UAAU,KAAK,IAAI;AACvC,gBAAM,YAAY,cAAc;AAChC,gBAAM,SAAS,cAAc,KAAK,YAAY;AAE9C,yBAAe,KAAK;YAClB;YACA,SAAS;YACT,MAAM;YACN,SAAS;YACT;YACA;YACA,YAAY,UAAU,SAAS;YAC/B,UAAU;YACV,eAAe,YAAY;YAC3B;YACA,YAAY,SAAS,KAAK,UAAU,MAAM,IAAI;YAC9C,eAAe,SAAS,YAAY;YACpC,cAAc,YAAY,IAAI,YAAY;YAC1C,kBAAkB;YAClB,iBAAiB,YAAY;YAC7B;YACA;UACF,CAAC;QACH;AAEA,YAAI,SAAS,QAAQ;AACnB,iBAAO,KAAK;YACV;YACA,WAAW;YACX;YACA,SACE,OAAO,IAAI,YAAY,WACnB,IAAI,QAAQ,MAAM,GAAG,GAAG,IACxB;UACR,CAAC;QACH;MACF;AAEA,UAAI,YAAa,MAAK,cAAc;AACpC,aAAO;QACL;QACA;QACA;QACA,UAAU;QACV,eAAe;QACf,iBAAiB;MACnB;IACF;EACF;AACF;AAEA,eAAe,MAAM;ACperB,IAAM,wBAAgD;EACpD,eAAe;EACf,OAAO;EACP,cAAc;EACd,aAAa;EACb,aAAa;EACb,WAAW;EACX,YAAY;EACZ,aAAa;EACb,WAAW;EACX,UAAU;EACV,aAAa;EACb,QAAQ;EACR,aAAa;AACf;AAEA,SAAS,kBAAkB,UAA0B;AACnD,SAAO,sBAAsB,QAAQ,KAAK,oBAAoB,QAAQ;AACxE;AAEA,IAAM,wBAAwB;EAC5B;EACA;EACA;EACA;AACF;AAEA,SAAS,qBAAqB,SAA0B;AACtD,SAAO,sBAAsB,KAAK,CAAC,MAAM,QAAQ,WAAW,CAAC,CAAC;AAChE;AAEA,IAAM,YAAYE,MAAK,KAAKC,IAAG,QAAQ,GAAG,QAAQ;AAClD,IAAM,mBAAmBD,MAAK,KAAK,WAAW,YAAY;AAG1D,IAAM,cAAc;EAClB;EACA;EACA;EACA;EACA;AACF;AAEA,SAAS,gBAAyC;AAChD,MAAI;AACF,WAAO,KAAK,MAAMF,IAAG,aAAa,kBAAkB,OAAO,CAAC;EAC9D,QAAQ;AACN,WAAO,CAAC;EACV;AACF;AAEA,SAAS,eAAe,MAAqC;AAC3DA,MAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3CA,MAAG,cAAc,kBAAkB,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;CAAI;AACzE;AAEA,IAAM,QAAuB;EAC3B,IAAI;EAEJ,QAAQ;IACN,KAAK;IACL,YAAYE,MAAK,KAAK,WAAW,aAAa;IAC9C,cAAc;EAChB;EAEA,OAAO;IACL,QAAQ;IACR,mBAAmB,UAAU,MAAM;AACjC,YAAM,cAAc,EAAE,GAAG,SAAS;AAClC,YAAM,UAAUA,MAAK,KAAK,KAAK,YAAY,OAAO,cAAc;AAChE,YAAM,SAASA,MAAK,KAAK,KAAK,YAAY,OAAO,YAAY;AAG7D,kBAAY,WACT,YAAY,YAAwC,CAAC;AACvD,kBAAY,SAAqC,cAAc;AAEhE,kBAAY,qCAAqC;AAGjD,aAAO,YAAY;AAGnB,YAAM,YAAY,cAAc;AAChC,YAAM,QAAS,UAAU,SAAuC,CAAC;AACjE,YAAM,YAAY,KAAK,QAAQ,aAAa;AAC5C,YAAM,UAAU,QAAQ,OAAO,UAAU,KAAK,IAAI,GAAG,SAAS;AAC9D,iBAAW,SAAS,aAAa;AAC/B,cAAM,SAAU,MAAM,KAAK,KAAK,CAAC;AAEjC,cAAM,KAAK,IAAI,OAAO,OAAO,CAAC,MAAM;AAClC,gBAAM,IAAM,EAAE,SAAuB,CAAC;AAGtC,iBAAO,CAAC,EAAE;YAAK,CAAC,UACb,MAAM,SAAoB,SAAS,YAAY;UAClD;QACF,CAAC;AAEA,cAAM,KAAK,EAAgB,KAAK;UAC/B,OAAO,CAAC,EAAE,MAAM,WAAW,SAAS,SAAS,GAAG,CAAC;QACnD,CAAC;MACH;AACA,gBAAU,QAAQ;AAClB,qBAAe,SAAS;AAGxB,UAAI,KAAK,OAAO;AACd,oBAAY,kBAAkB,oBAAoB,KAAK,IAAI;MAC7D,OAAO;AACL,eAAO,YAAY;MACrB;AAGA,aAAO,YAAY;AACnB,YAAM,WAAW,oBAAoB,KAAK,IAAI;AAC9C,YAAM,iBAAiB,EAAE,UAAU,UAAU,SAAS;AACtD,kBAAY,OAAO;QACjB,GAAK,YAAY,QAAoC,CAAC;QACtD,iBAAiB;QACjB,UAAU,EAAE,aAAa,eAAe;QACxC,gBAAgB,EAAE,aAAa,eAAe;QAC9C,kBAAkB,EAAE,aAAa,eAAe;MAClD;AAGA,YAAM,aAAc,YAAY,eAAe,CAAC;AAIhD,iBAAW,aAAa,EAAE,SAAS,QAAQ,MAAM,CAAC,MAAM,EAAE;AAC1D,kBAAY,cAAc;AAE1B,aAAO;IACT;IACA,oBAAoB,UAAU;AAC5B,YAAM,MAAM,EAAE,GAAG,SAAS;AAG1B,YAAM,WAAW,IAAI;AACrB,UAAI,UAAU;AACZ,eAAO,SAAS;AAChB,YAAI,OAAO,KAAK,QAAQ,EAAE,WAAW,EAAG,QAAO,IAAI;MACrD;AACA,aAAO,IAAI;AAGX,aAAO,IAAI;AAGX,YAAM,YAAY,cAAc;AAChC,YAAM,QAAQ,UAAU;AACxB,UAAI,OAAO;AACT,mBAAW,SAAS,OAAO,KAAK,KAAK,GAAG;AACtC,gBAAM,KAAK,IACT,MAAM,KAAK,EACX,OAAO,CAAC,MAAM;AACd,kBAAM,IAAM,EAAE,SAAuB,CAAC;AAGtC,mBAAO,CAAC,EAAE;cAAK,CAAC,UACb,MAAM,SAAoB,SAAS,YAAY;YAClD;UACF,CAAC;AACD,cAAK,MAAM,KAAK,EAAgB,WAAW,EAAG,QAAO,MAAM,KAAK;QAClE;AACA,YAAI,OAAO,KAAK,KAAK,EAAE,WAAW,GAAG;AAEnC,cAAI;AACFF,gBAAG,WAAW,gBAAgB;UAChC,QAAQ;UAAC;QACX,OAAO;AACL,oBAAU,QAAQ;AAClB,yBAAe,SAAS;QAC1B;MACF;AAGA,UACE,OAAO,IAAI,oBAAoB,YAC/B,IAAI,gBAAgB,SAAS,YAAY,GACzC;AACA,eAAO,IAAI;MACb;AAGA,aAAO,IAAI;AACX,YAAM,OAAO,IAAI;AACjB,UAAI,MAAM;AACR,eAAO,KAAK;AACZ,eAAO,KAAK;AACZ,eAAO,KAAK;AACZ,eAAO,KAAK;AACZ,YAAI,OAAO,KAAK,IAAI,EAAE,WAAW,EAAG,QAAO,IAAI;MACjD;AAGA,YAAM,UAAU,IAAI;AACpB,UAAI,SAAS;AACX,eAAO,QAAQ;AACf,YAAI,OAAO,KAAK,OAAO,EAAE,WAAW,EAAG,QAAO,IAAI;MACpD;AAEA,aAAO;IACT;EACF;EAEA,UAAU;;IAER,UAAU;AACR,aAAO,CAAC;IACV;EACF;EAEA,QAAQ;;;IAGN,UAAU,CAAC;IACX,yBAAyB,EAAE,OAAO,OAAO,GAAG;AAE1C,aAAO;QACL,oBAAoB;UAClB,eAAe;UACf,oBAAoB,QAAQ,UAAU;UACtC,0BAA0B;QAC5B;MACF;IACF;EACF;EAEA,QAAQ;IACN,aAAa;IACb,aAAa,MAAMA,IAAG,WAAW,SAAS;IAC1C,eAAe;AACb,UAAI;AACF,cAAM,UAAUA,IAAG,aAAa,kBAAkB,OAAO;AACzD,eAAO,QAAQ,SAAS,YAAY;MACtC,QAAQ;AACN,eAAO;MACT;IACF;EACF;EAEA,MAAM;IACJ,aAAa;IACb,SAAS;MACP,aAAa,CAAC,wBAAwB;MACtC,aAAa;MACb,gBAAgB,CAAC,cAAc;MAC/B,YAAY,CAAC,SAAS;MACtB,cAAc;QACZ,cAAc;QACd,kBAAkB;MACpB;MACA,mBAAmB,CAAC,OAAO;IAC7B;IACA,WAAW;MACT,gBAAgB,CAAC,QAAQ,4CAA4C;MACrE,kBAAkB;QAChB;QACA;MACF;IACF;EACF;EAEA,OAAO;IACL,eAAe,CAAC,yBAAyB;EAC3C;EAEA,OAAO;IACL,aAAa,SAAS;AAEpB,YAAM,OAAO,QAAQ,iBAAiB;AACtC,aAAO,KAAK,WAAW,YAAY,IAAI,gBAAgB;IACzD;IACA,YAAY,aAAa,SAAS;AAChC,YAAM,OAAO,QAAQ,iBAAiB;AACtC,YAAM,iBAAiB,KAAK,WAAW,YAAY;AACnD,aAAO,iBACH,qBAAqB,WAAW,KAChC,MAAM,WAAW;IACvB;IACA,iBAAiB;EACnB;EAEA,SAAS;IACP,uBAAuB;IACvB,WAAW;AACT,YAAM,cAAcE,MAAK,KAAK,WAAW,UAAU;AACnD,YAAM,QAAgC,CAAC;AACvC,YAAM,cAAc,CAAC,MAAc;AACjC,YAAI;AACF,iBAAOF,IAAG,YAAY,CAAC;QACzB,QAAQ;AACN,iBAAO,CAAC;QACV;MACF;AACA,iBAAW,QAAQ,YAAY,WAAW,GAAG;AAC3C,mBAAW,SAAS,YAAYE,MAAK,KAAK,aAAa,IAAI,CAAC,GAAG;AAC7D,qBAAW,OAAO,YAAYA,MAAK,KAAK,aAAa,MAAM,KAAK,CAAC,GAAG;AAClE,kBAAM,SAASA,MAAK,KAAK,aAAa,MAAM,OAAO,GAAG;AACtD,uBAAW,SAAS,YAAY,MAAM,GAAG;AACvC,kBAAI,MAAM,SAAS,QAAQ;AACzB,sBAAM,KAAK,EAAE,UAAUA,MAAK,KAAK,QAAQ,KAAK,EAAE,CAAC;YACrD;UACF;QACF;MACF;AACA,aAAO;IACT;IAEA,UAAU,UAAkB,gBAA4C;AACtE,YAAM,EAAE,OAAO,cAAc,IAAI,aAAa,UAAU,cAAc;AACtE,UAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,UAAI;AACJ,YAAM,QAA8B,CAAC;AACrC,YAAM,SAAgC,CAAC;AACvC,YAAM,WAAoC,CAAC;AAC3C,UAAI,YAAY;AAChB,UAAI,UAAU;AACd,UAAI;AACJ,UAAI;AAGJ,UAAI,mBAAqC,CAAC;AAC1C,UAAI,0BAA0B;AAC9B,YAAM,sBAAsB,oBAAI,IAG9B;AAEF,iBAAW,QAAQ,OAAO;AACxB,YAAI;AACJ,YAAI;AACF,gBAAM,KAAK,MAAM,IAAI;QACvB,QAAQ;AACN;QACF;AAEA,cAAM,OAAO,IAAI;AACjB,cAAM,YAAY,IAAI;AACtB,cAAM,OAAO,YAAY,IAAI,KAAK,SAAS,EAAE,QAAQ,IAAI,KAAK,IAAI;AAClE,cAAM,UAAU,IAAI;AAEpB,YAAI,SAAS,kBAAkB,SAAS;AACtC,iBAAO;YACL,WAAW,QAAQ;YACnB,KAAK,QAAQ;YACb,YAAY,QAAQ;YACpB,aAAa,QAAQ,YACjB,IAAI,KAAK,QAAQ,SAAmB,EAAE,QAAQ,IAC9C;UACN;QACF;AAEA,YAAI,SAAS,kBAAkB,SAAS;AACtC,yBAAe,QAAQ;AACvB,cAAI,QAAQ,gBAAgB,CAAC,KAAK,MAAO,MAAK,QAAQ;QACxD;AAEA,cAAM,MAAM,MAAM,aAAa;AAE/B,YAAI,SAAS,eAAe,SAAS;AACnC,gBAAM,YAAY,QAAQ;AAE1B,cAAI,cAAc,gBAAgB;AAChC,kBAAM,UAAU,QAAQ;AACxB,gBAAI,CAAC,eAAe,QAAS,eAAc,QAAQ,MAAM,GAAG,GAAG;AAC/D,kBAAM,KAAK;cACT,WAAW;cACX,WAAW;cACX,aAAa;cACb,OAAO;cACP,MAAM;cACN,gBAAgB,SAAS,MAAM,GAAG,GAAG;cACrC,aAAa;cACb,cAAc;cACd,iBAAiB;cACjB,qBAAqB;cACrB,iBAAiB;YACnB,CAAC;AAED,gBAAI,WAAW,CAAC,qBAAqB,OAAO,GAAG;AAC7C,uBAAS,KAAK;gBACZ,WAAW;gBACX,SAAS;gBACT,MAAM;gBACN,SAAS;gBACT,aAAa;gBACb,aAAa;gBACb,YAAY;gBACZ,UAAU;gBACV,eAAe,QAAQ;gBACvB,kBAAkB;gBAClB,iBAAiB;gBACjB,WAAW,CAAC;gBACZ,aAAa,oBAAI,IAAI;cACvB,CAAC;YACH;UACF;AAEA,cAAI,cAAc,eAAe;AAC/B,kBAAM,OAAO,QAAQ;AACrB,gBAAI,CAAC,KAAM;AACX,kBAAM,YAAY,KAAK;AAGvB,gBAAI,CAAC,UAAW;AAEhB,kBAAM,cAAe,UAAU,gBAA2B;AAC1D,kBAAM,YAAa,UAAU,iBAA4B;AACzD,kBAAM,YAAa,UAAU,uBAAkC;AAC/D,kBAAM,YACH,UAAU,2BAAsC;AACnD,kBAAM,YAAY,cAAc;AAChC,kBAAM,SAAS,cAAc,KAAK,YAAY;AAE9C,kBAAM,KAAK;cACT,WAAW;cACX,WAAW;cACX,aAAa;cACb,OAAO;cACP,MAAM;cACN;cACA,cAAc;cACd,iBAAiB;cACjB,qBAAqB;cACrB,iBAAiB;YACnB,CAAC;AAGD,kBAAM,cAAc,oBAAI,IAGtB;AACF,uBAAW,MAAM,kBAAkB;AACjC,oBAAM,SAAS,oBAAoB,IAAI,GAAG,SAAS;AACnD,kBAAI,OAAQ,aAAY,IAAI,GAAG,WAAW,MAAM;YAClD;AAEA,qBAAS,KAAK;cACZ,WAAW;cACX,SAAS;cACT,MAAM;cACN,SAAS;cACT,aAAa;cACb,aAAa;cACb,YAAY,iBAAiB,SAAS;cACtC,UAAU;cACV,eAAe,wBAAwB;cACvC,OAAO;cACP,YAAY,KAAK,UAAU,SAAS;cACpC,eAAe,SAAS,YAAY;cACpC,cAAc,YAAY,IAAI,YAAY;cAC1C,kBAAkB;cAClB,iBAAiB,YAAY;cAC7B,WAAW;cACX;YACF,CAAC;AACD,+BAAmB,CAAC;AACpB,sCAA0B;UAC5B;AAEA,cAAI,cAAc,iBAAiB;AACjC,mBAAO,KAAK;cACV,WAAW;cACX,WAAW;cACX,aAAa;cACb,SAAU,QAAQ,SAAoB,MAAM,GAAG,GAAG;cAClD,UAAU,EAAE,OAAO,QAAQ,MAAM;YACnC,CAAC;UACH;QACF;AAGA,YAAI,SAAS,iBAAiB;AAC5B,gBAAM,IAAI,IAAI;AACd,gBAAM,WAAW,GAAG;AAGpB,cAAI,aAAa,aAAa,aAAa,QAAQ;AACjD,kBAAM,OACH,GAAG,QACH,GAAG,WACH,GAAG;AACN,gBAAI,MAAM;AACR,0CACG,0BAA0B,OAAO,MAAM;YAC5C;UACF;AAEA,cAAI,aAAa,iBAAiB;AAChC,kBAAM,WAAY,GAAG,QAAmB;AACxC,kBAAM,SAAU,GAAG,WAAsB;AACzC,kBAAM,YACJ,OAAO,GAAG,cAAc,WACpB,EAAE,YACF,KAAK,UAAU,GAAG,SAAS;AAEjC,6BAAiB,KAAK;cACpB,WAAW;cACX;cACA,UAAU,kBAAkB,QAAQ;cACpC;YACF,CAAC;AAED,mBAAO,KAAK;cACV,WAAW;cACX,WAAW;cACX,aAAa;cACb;cACA,WAAW,WAAW,MAAM,GAAG,GAAI;cACnC,UAAU,EAAE,SAAS,OAAO;YAC9B,CAAC;UACH;AAEA,cAAI,aAAa,wBAAwB;AACvC,kBAAM,SAAU,GAAG,WAAsB;AACzC,kBAAM,SAAS,OAAO,GAAG,WAAW,WAAW,EAAE,SAAS;AAE1D,gBAAI,QAAQ;AACV,kCAAoB,IAAI,QAAQ;gBAC9B,eAAe,OAAO;gBACtB,YAAY;cACd,CAAC;YACH;AAEA,mBAAO,KAAK;cACV,WAAW;cACX,WAAW;cACX,aAAa;cACb,YAAY,QAAQ,MAAM,GAAG,GAAI;cACjC,UAAU,EAAE,SAAS,OAAO;YAC9B,CAAC;UACH;QACF;MACF;AAEA,UAAI,QAAQ,eAAe,CAAC,KAAK;AAC/B,aAAK,cAAc;AACrB,aAAO,EAAE,MAAM,OAAO,QAAQ,UAAU,cAAc;IACxD;EACF;AACF;AAEA,eAAe,KAAK;;;AC2Xb,SAAS,UAAU;AACxB,QAAM,KAAK,MAAM;AACjB,QAAM,OAAO,GAAG,QAAQ,yCAAyC,EAAE,IAAI;AAGvE,QAAM,UAAU,GACb,QAAQ,4CAA4C,EACpD,IAAI;AACP,QAAM,QAAQ,GACX,QAAQ,2CAA2C,EACnD,IAAI;AACP,QAAM,QAAQ,GACX,QAAQ,0CAA0C,EAClD,IAAI;AACP,QAAM,WAAW,GACd,QAAQ,wCAAwC,EAChD,IAAI;AACP,QAAM,eAAe,GAClB,QAAQ,6CAA6C,EACrD,IAAI;AACP,QAAM,gBAAgB,GACnB,QAAQ,8CAA8C,EACtD,IAAI;AAEP,QAAM,WAAW,GACd,QAAQ,wCAAwC,EAChD,IAAI;AACP,QAAM,YAAY,GACf,QAAQ,0CAA0C,EAClD,IAAI;AAEP,SAAO;IACL,UAAU,SAAS;IACnB,UAAU,SAAS;IACnB,YAAY,UAAU;IACtB,eAAe,aAAa;IAC5B,gBAAgB,cAAc;IAC9B,aAAa,MAAM;IACnB,WAAW,KAAK;IAChB,cAAc,QAAQ;IACtB,YAAY,MAAM;EACpB;AACF;;;ACz8BA,OAAOE,SAAQ;AACf,OAAO,UAAU;AACjB,OAAOC,SAAQ;AAoUf,OAAOC,WAAU;AAhSjB,SAAS,YAAY,MAAc,MAAgC;AACjE,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,MAAM,KAAK;MACf,EAAE,UAAU,MAAM,MAAM,MAAM,WAAW,QAAQ,OAAO,SAAS,IAAK;MACtE,CAAC,QAAQ;AACP,YAAI,OAAO;AACX,gBAAQ,IAAI,eAAe,GAAG;MAChC;IACF;AACA,QAAI,GAAG,SAAS,MAAM,QAAQ,KAAK,CAAC;AACpC,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ;AACZ,cAAQ,KAAK;IACf,CAAC;AACD,QAAI,IAAI;EACV,CAAC;AACH;AAOA,eAAsB,SAAgC;AACpD,QAAM,SAAwB,CAAC;AAE/B,QAAM,YACJ,QAAQ,IAAI,YAAY,UACxBF,IAAG,WAAWE,MAAK,KAAKD,IAAG,QAAQ,GAAG,eAAe,CAAC;AAGxD,MAAI,CAACD,IAAG,WAAW,OAAO,MAAM,GAAG;AACjC,WAAO,KAAK;MACV,OAAO;MACP,QAAQ;MACR,QAAQ,gBAAgB,OAAO,MAAM;IACvC,CAAC;EACH,OAAO;AACL,QAAI;AACF,YAAM,QAAQ,QAAQ;AACtB,YAAM,QAAQ,MAAM,cAAc,MAAM,YAAY,MAAM;AAC1D,aAAO,KAAK;QACV,OAAO;QACP,QAAQ;QACR,QAAQ,GAAG,KAAK,UAAU,MAAM,WAAW,WAAW,MAAM,SAAS,UAAU,MAAM,YAAY;MACnG,CAAC;AACD,cAAQ;IACV,SAAS,KAAK;AACZ,aAAO,KAAK;QACV,OAAO;QACP,QAAQ;QACR,QAAQ,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;MACpE,CAAC;IACH;EACF;AAGA,QAAM,WAAW,MAAM,YAAY,OAAO,MAAM,WAAW;AAC3D,MAAI,UAAU;AACZ,WAAO,KAAK;MACV,OAAO;MACP,QAAQ;MACR,QAAQ,0BAA0B,OAAO,IAAI;IAC/C,CAAC;EACH,OAAO;AACL,WAAO,KAAK;MACV,OAAO;MACP,QAAQ;MACR,QAAQ,0BAA0B,OAAO,IAAI;IAC/C,CAAC;EACH;AAGA,QAAM,UAAUE,MAAK;IACnBD,IAAG,QAAQ;IACX,QAAQ,IAAI,OAAO,SAAS,KAAK,IAAI,WAAW;EAClD;AACA,QAAM,YAAYD,IAAG,WAAW,OAAO,IACnCA,IAAG,aAAa,SAAS,OAAO,IAChC;AAEJ,QAAM,eAAe,UAAU,SAAS,8BAA8B;AACtE,QAAM,cAAc,UAAU,SAAS,6BAA6B;AAEpE,MAAI,gBAAgB,aAAa;AAC/B,WAAO,KAAK;MACV,OAAO;MACP,QAAQ;MACR,QAAQ,2BAA2BE,MAAK,SAAS,OAAO,CAAC;IAC3D,CAAC;EACH,WAAW,gBAAgB,aAAa;AACtC,WAAO,KAAK;MACV,OAAO;MACP,QAAQ;MACR,QAAQ,qBAAqBA,MAAK,SAAS,OAAO,CAAC;IACrD,CAAC;EACH,OAAO;AACL,WAAO,KAAK;MACV,OAAO;MACP,QAAQ;MACR,QAAQ;IACV,CAAC;EACH;AAGA,QAAM,QAAmE,CAAC;AAE1E,aAAW,KAAK,WAAW,GAAG;AAC5B,QAAI,EAAE,OAAO,YAAY,GAAG;AAC1B,YAAM,KAAK;QACT,MAAM,EAAE,OAAO;QACf,KAAK,EAAE,OAAO;QACd,YAAY,EAAE,OAAO,aAAa;MACpC,CAAC;IACH;EACF;AAEA,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,KAAK;MACV,OAAO;MACP,QAAQ;MACR,QAAQ;IACV,CAAC;EACH,OAAO;AACL,UAAM,aAAa,MAAM,OAAO,CAAC,MAAM,EAAE,UAAU;AACnD,UAAM,eAAe,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU;AACtD,QAAI,aAAa,WAAW,GAAG;AAC7B,aAAO,KAAK;QACV,OAAO;QACP,QAAQ;QACR,QAAQ,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI;MACjD,CAAC;IACH,OAAO;AACL,YAAM,SAAS,MACZ,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,aAAa,WAAM,kBAAkB,EAAE,EACjE,KAAK,IAAI;AACZ,aAAO,KAAK;QACV,OAAO;QACP,QAAQ;QACR,QAAQ,GAAG,MAAM;MACnB,CAAC;IACH;EACF;AAGA,MAAIF,IAAG,WAAW,OAAO,MAAM,GAAG;AAChC,QAAI;AACF,YAAM,KAAK,MAAM;AACjB,YAAM,SAAS,GACZ,QAAQ,iDAAiD,EACzD,IAAI;AACP,cAAQ;AAER,UAAI,QAAQ,IAAI;AACd,cAAM,MAAM,KAAK,IAAI,IAAI,OAAO;AAChC,cAAM,UAAU,KAAK,MAAM,MAAM,GAAK;AACtC,YAAI,UAAU,IAAI;AAChB,iBAAO,KAAK;YACV,OAAO;YACP,QAAQ;YACR,QAAQ,cAAc,OAAO;UAC/B,CAAC;QACH,WAAW,UAAU,MAAM;AACzB,iBAAO,KAAK;YACV,OAAO;YACP,QAAQ;YACR,QAAQ,cAAc,KAAK,MAAM,UAAU,EAAE,CAAC;UAChD,CAAC;QACH,OAAO;AACL,iBAAO,KAAK;YACV,OAAO;YACP,QAAQ;YACR,QAAQ,cAAc,KAAK,MAAM,UAAU,IAAI,CAAC;UAClD,CAAC;QACH;MACF,OAAO;AACL,eAAO,KAAK;UACV,OAAO;UACP,QAAQ;UACR,QAAQ;QACV,CAAC;MACH;IACF,QAAQ;IAER;EACF;AAGA,MAAI;AACF,UAAM,MAAM,kBAAkB;AAC9B,UAAMG,WAAU,IAAI,KAAK;AACzB,QAAIA,SAAQ,WAAW,GAAG;AACxB,aAAO,KAAK;QACV,OAAO;QACP,QAAQ;QACR,QAAQ;MACV,CAAC;IACH,OAAO;AACL,YAAM,SAAS,CAAC,eAAe,aAAa,cAAc;AAC1D,YAAM,gBAA0B,CAAC;AACjC,iBAAW,KAAKA,UAAS;AACvB,cAAM,aAAa,OAAO;UAAI,CAAC,UAC7B,cAAc,aAAa,OAAO,EAAE,IAAI,CAAC;QAC3C;AACA,cAAM,QAAQ,KAAK,IAAI,GAAG,UAAU;AACpC,cAAM,UAAU,QAAQ,IAAI,cAAc,KAAK,KAAK;AACpD,sBAAc,KAAK,GAAG,EAAE,IAAI,WAAM,EAAE,GAAG,KAAK,OAAO,GAAG;MACxD;AACA,aAAO,KAAK;QACV,OAAO;QACP,QAAQ;QACR,QAAQ,GAAGA,SAAQ,MAAM,UAAUA,SAAQ,SAAS,IAAI,MAAM,EAAE,KAAK,cAAc,KAAK,IAAI,CAAC;MAC/F,CAAC;IACH;EACF,QAAQ;AACN,WAAO,KAAK;MACV,OAAO;MACP,QAAQ;MACR,QAAQ;IACV,CAAC;EACH;AAGA,MAAI;AACF,UAAM,MAAM,kBAAkB;AAC9B,UAAM,MAAM,QAAQ,IAAI,yBAAyB,IAAI;AACrD,WAAO,KAAK;MACV,OAAO;MACP,QAAQ,MAAM,OAAO;MACrB,QAAQ,MAAM,eAAe;IAC/B,CAAC;EACH,QAAQ;EAER;AAGA,MAAI,eAA8B,CAAC;AACnC,MAAI,eAA8B,CAAC;AAEnC,MAAIH,IAAG,WAAW,OAAO,MAAM,GAAG;AAChC,QAAI;AACF,YAAM,KAAK,MAAM;AAEjB,YAAM,SAAS,GACZ;QACC;MACF,EACC,IAAI;AAMP,qBAAe,OAAO,IAAI,CAAC,OAAO;QAChC,WAAW,EAAE;QACb,UAAU,EAAE;QACZ,WAAW,IAAI,KAAK,EAAE,YAAY,EAAE,YAAY;MAClD,EAAE;AAEF,YAAM,SAAS,GACZ;QACC;MACF,EACC,IAAI;AAEP,qBAAe,OAAO,IAAI,CAAC,OAAO;QAChC,IAAI,EAAE;QACN,OAAO,EAAE,QAAQ,IAAI,MAAM,GAAG,GAAG;MACnC,EAAE;AAEF,cAAQ;IACV,QAAQ;IAER;EACF;AAEA,SAAO;IACL;IACA,QAAQ;MACN,IAAI,GAAGC,IAAG,SAAS,CAAC,KAAKA,IAAG,QAAQ,CAAC,IAAIA,IAAG,KAAK,CAAC;MAClD,MAAM,QAAQ;MACd,SAAS;IACX;IACA;IACA;EACF;AACF;;;AE1UA,OAAOG,SAAQ;AACf,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAIjB,IAAM,qBAAqBC,MAAK;EAC9BC,IAAG,QAAQ;EACX;EACA;EACA;AACF;AACA,IAAM,wBAAwBD,MAAK;EACjC;EACA;AACF;AAEA,IAAM,gBAA+B;EACnC,IAAI;EAEJ,QAAQ;IACN,KAAK;IACL,YAAY;IACZ,cAAc;EAChB;EAEA,OAAO;;IAEL,QAAQ,CAAC;IACT,mBAAmB,UAAU,MAAM;AACjC,YAAM,MAAM,EAAE,GAAG,SAAS;AAC1B,YAAM,YAAYA,MAAK,KAAK,KAAK,YAAY,OAAO,YAAY;AAChE,UAAI,aAAc,IAAI,cAA0C,CAAC;AAChE,UAAI,WAAuC,aAAa;QACvD,SAAS;QACT,MAAM,CAAC,SAAS;MAClB;AACA,aAAO;IACT;IACA,oBAAoB,UAAU;AAC5B,YAAM,MAAM,EAAE,GAAG,SAAS;AAC1B,YAAM,UAAU,IAAI;AACpB,UAAI,SAAS;AACX,eAAO,QAAQ;AACf,YAAI,OAAO,KAAK,OAAO,EAAE,WAAW,EAAG,QAAO,IAAI;MACpD;AACA,aAAO;IACT;EACF;EAEA,UAAU;IACR,UAAU;AACR,aAAO,CAAC;IACV;EACF;EAEA,QAAQ;IACN,UAAU,CAAC;IACX,yBAAyB,EAAE,OAAO,OAAO,GAAG;AAC1C,aAAO;QACL,oBAAoB;UAClB,eAAe;UACf,oBAAoB,QAAQ,UAAU;UACtC,0BAA0B;QAC5B;MACF;IACF;EACF;EAEA,QAAQ;IACN,aAAa;IACb,aAAa,MAAME,IAAG,WAAW,kBAAkB;IACnD,eAAe;AACb,UAAI;AACF,cAAM,MAAM,KAAK,MAAMA,IAAG,aAAa,uBAAuB,OAAO,CAAC;AACtE,eAAO,CAAC,CAAC,IAAI,YAAY;MAC3B,QAAQ;AACN,eAAO;MACT;IACF;EACF;AACF;AAEA,eAAe,aAAa;","names":["fs","os","path","fs","agentId","path","os","fs","os","path","targets","fs","os","path","path","os","fs"]}