@rulemetric/cli 0.2.4 → 0.4.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.
Files changed (64) hide show
  1. package/dist/{chunk-JM6BQSRE.js → chunk-2WWGG2A5.js} +11 -11
  2. package/dist/{chunk-4A7FKR2O.js → chunk-42A3HEEH.js} +50 -3
  3. package/dist/chunk-42A3HEEH.js.map +7 -0
  4. package/dist/{chunk-PDURR7O3.js → chunk-46LMTDRE.js} +2 -2
  5. package/dist/{chunk-ISDA2QFX.js → chunk-4QGATD5Y.js} +209 -2
  6. package/dist/chunk-4QGATD5Y.js.map +7 -0
  7. package/dist/{chunk-L674RNKQ.js → chunk-ANOWI23I.js} +51 -1
  8. package/dist/chunk-ANOWI23I.js.map +7 -0
  9. package/dist/{chunk-3LHLSY7V.js → chunk-C7SRRSPA.js} +6 -6
  10. package/dist/{chunk-B3HILODD.js → chunk-CLC7ZFH2.js} +6 -6
  11. package/dist/{chunk-PIOVE3TN.js → chunk-FUS5C4SB.js} +2 -2
  12. package/dist/{chunk-PHLMIZKM.js → chunk-JNFIYBFL.js} +30 -5
  13. package/dist/chunk-JNFIYBFL.js.map +7 -0
  14. package/dist/{chunk-RDIH4AG2.js → chunk-MTLAIJED.js} +3 -3
  15. package/dist/{chunk-NIDLJO6F.js → chunk-QSN77T7C.js} +23 -1
  16. package/dist/{chunk-NIDLJO6F.js.map → chunk-QSN77T7C.js.map} +2 -2
  17. package/dist/chunk-SO3T35U7.js +80 -0
  18. package/dist/chunk-SO3T35U7.js.map +7 -0
  19. package/dist/{chunk-ELL34R3K.js → chunk-XMWHTOCC.js} +3 -3
  20. package/dist/{chunk-3IU7GFXS.js → chunk-YEBBX4HQ.js} +2 -2
  21. package/dist/commands/evals/agent.js +12 -14
  22. package/dist/commands/evals/agent.js.map +2 -2
  23. package/dist/commands/hooks/emit-instructions.js +25 -0
  24. package/dist/commands/hooks/emit-instructions.js.map +7 -0
  25. package/dist/commands/hooks/install.js +18 -24
  26. package/dist/commands/hooks/install.js.map +2 -2
  27. package/dist/commands/hooks/uninstall.js +4 -4
  28. package/dist/commands/hooks/uninstall.js.map +2 -2
  29. package/dist/commands/research/backfill.js +3 -3
  30. package/dist/commands/research/tail-transcript.js +3 -3
  31. package/dist/commands/service/install.js +186 -11
  32. package/dist/commands/service/install.js.map +2 -2
  33. package/dist/commands/service/uninstall.js +33 -2
  34. package/dist/commands/service/uninstall.js.map +2 -2
  35. package/dist/dashboard/Dashboard.js +12 -12
  36. package/dist/dashboard/data.js +2 -2
  37. package/dist/{dist-I6BCOLNG.js → dist-NEECHU47.js} +6 -2
  38. package/dist/lib/agent-loop.js +12 -12
  39. package/dist/lib/handlers/process-announcement.js +3 -3
  40. package/dist/lib/handlers/process-changelog.js +3 -3
  41. package/dist/lib/handlers/process-launch.js +3 -3
  42. package/dist/lib/handlers/process-run-cleanup.js +4 -4
  43. package/dist/lib/handlers/process-session-goal.js +2 -2
  44. package/dist/lib/hooks-config.js +7 -1
  45. package/dist/lib/instruction-snapshot.js +8 -0
  46. package/dist/lib/instruction-snapshot.js.map +7 -0
  47. package/dist/lib/manual-tasks.js +3 -3
  48. package/dist/lib/statusline-shim.js +1 -1
  49. package/dist/lib/worktree.js +2 -2
  50. package/oclif.manifest.json +1014 -977
  51. package/package.json +18 -15
  52. package/dist/chunk-4A7FKR2O.js.map +0 -7
  53. package/dist/chunk-ISDA2QFX.js.map +0 -7
  54. package/dist/chunk-L674RNKQ.js.map +0 -7
  55. package/dist/chunk-PHLMIZKM.js.map +0 -7
  56. /package/dist/{chunk-JM6BQSRE.js.map → chunk-2WWGG2A5.js.map} +0 -0
  57. /package/dist/{chunk-PDURR7O3.js.map → chunk-46LMTDRE.js.map} +0 -0
  58. /package/dist/{chunk-3LHLSY7V.js.map → chunk-C7SRRSPA.js.map} +0 -0
  59. /package/dist/{chunk-B3HILODD.js.map → chunk-CLC7ZFH2.js.map} +0 -0
  60. /package/dist/{chunk-PIOVE3TN.js.map → chunk-FUS5C4SB.js.map} +0 -0
  61. /package/dist/{chunk-RDIH4AG2.js.map → chunk-MTLAIJED.js.map} +0 -0
  62. /package/dist/{chunk-ELL34R3K.js.map → chunk-XMWHTOCC.js.map} +0 -0
  63. /package/dist/{chunk-3IU7GFXS.js.map → chunk-YEBBX4HQ.js.map} +0 -0
  64. /package/dist/{dist-I6BCOLNG.js.map → dist-NEECHU47.js.map} +0 -0
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../packages/session/src/types.ts", "../../../packages/session/src/adapters/claude-code.ts", "../../../packages/session/src/adapters/cursor.ts", "../../../packages/session/src/adapters/vscode-copilot.ts", "../../../packages/session/src/adapters/copilot-agent.ts", "../../../packages/session/src/adapters/rulemetric-mcp.ts", "../../../packages/session/src/registry.ts", "../../../packages/session/src/analyzer.ts", "../../../packages/session/src/providers/claude-code.ts", "../../../packages/session/src/providers/codex.ts", "../../../packages/session/src/providers/copilot.ts", "../../../packages/session/src/providers/pi.ts", "../../../packages/session/src/providers/index.ts", "../../../packages/session/src/launchers/claude-code.ts", "../../../packages/session/src/terminal/tmux.ts", "../../../packages/session/src/launchers/codex.ts", "../../../packages/session/src/launchers/aider.ts", "../../../packages/session/src/launchers/generic.ts", "../../../packages/session/src/launchers/index.ts", "../../../packages/session/src/launchers/worktree-path.ts"],
4
- "sourcesContent": ["export const SESSION_TOOLS = ['claude_code', 'cursor', 'vscode_copilot', 'copilot_agent', 'rulemetric_mcp', 'codex', 'pi', 'opencode', 'antigravity'] as const;\nexport type SessionTool = (typeof SESSION_TOOLS)[number];\n\nexport const SESSION_EVENT_TYPES = [\n 'user_message',\n 'assistant_message',\n 'tool_call',\n 'tool_result',\n 'thinking',\n 'error',\n] as const;\nexport type SessionEventType = (typeof SESSION_EVENT_TYPES)[number];\n\nexport interface TokenUsage {\n input: number;\n output: number;\n cacheRead?: number;\n cacheWrite?: number;\n}\n\nexport interface SessionEvent {\n sequence: number;\n eventType: SessionEventType;\n content: string | null;\n toolName: string | null;\n toolInput: Record<string, unknown> | null;\n toolOutput: Record<string, unknown> | null;\n tokenUsage: TokenUsage | null;\n timestamp: string;\n metadata: Record<string, unknown>;\n}\n\nexport interface ParsedSession {\n tool: SessionTool;\n projectPath: string | null;\n gitBranch: string | null;\n startedAt: string;\n endedAt: string | null;\n events: SessionEvent[];\n metadata: Record<string, unknown>;\n}\n\nexport interface SessionAdapter {\n tool: SessionTool;\n parse(input: string): ParsedSession;\n}\n\nexport interface Deviation {\n eventSequence: number;\n instructionId: string;\n description: string;\n severity: 'low' | 'medium' | 'high';\n}\n\nexport interface InstructionSuggestion {\n instructionId: string;\n currentContent: string;\n suggestedContent: string;\n rationale: string;\n confidence: number;\n}\n\nexport interface SessionAnalysis {\n adherenceScore: number;\n deviations: Deviation[];\n suggestions: InstructionSuggestion[];\n summary: string;\n analyzedAt: string;\n}\n", "import type { SessionAdapter, SessionEvent, ParsedSession } from '../types.js';\n\ninterface ClaudeCodeRecord {\n type: string;\n uuid?: string;\n parentUuid?: string | null;\n sessionId?: string;\n requestId?: string;\n timestamp?: string;\n cwd?: string;\n gitBranch?: string;\n version?: string;\n userType?: string;\n slug?: string;\n message?: {\n role: string;\n model?: string;\n stop_reason?: string;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n service_tier?: string;\n speed?: string;\n inference_geo?: string;\n server_tool_use?: Record<string, number>;\n cache_creation?: Record<string, number>;\n };\n content: string | Array<{ type: string; text?: string; thinking?: string; id?: string; name?: string; input?: Record<string, unknown>; tool_use_id?: string; content?: string }>;\n };\n toolUseMessages?: unknown[];\n}\n\nexport class ClaudeCodeAdapter implements SessionAdapter {\n tool = 'claude_code' as const;\n\n parse(input: string): ParsedSession {\n const lines = input.split('\\n').filter((line) => line.trim());\n const records: ClaudeCodeRecord[] = [];\n\n for (const line of lines) {\n try {\n records.push(JSON.parse(line) as ClaudeCodeRecord);\n } catch {\n // Skip malformed lines\n }\n }\n\n const messageRecords = records.filter(\n (r) => r.type === 'user' || r.type === 'assistant',\n );\n\n let projectPath: string | null = null;\n let gitBranch: string | null = null;\n const metadata: Record<string, unknown> = {};\n\n // Extract project info from first user message\n const firstUser = records.find((r) => r.type === 'user');\n if (firstUser) {\n projectPath = firstUser.cwd ?? null;\n gitBranch = firstUser.gitBranch ?? null;\n if (firstUser.version) metadata.claudeCodeVersion = firstUser.version;\n if (firstUser.sessionId) metadata.sessionId = firstUser.sessionId;\n }\n\n // Extract model from first assistant message\n const firstAssistant = records.find((r) => r.type === 'assistant');\n if (firstAssistant?.message?.model) metadata.model = firstAssistant.message.model;\n\n const events: SessionEvent[] = [];\n let sequence = 0;\n\n for (const record of messageRecords) {\n const timestamp = record.timestamp ?? new Date().toISOString();\n\n if (record.type === 'user') {\n const messageContent = record.message?.content;\n\n if (typeof messageContent === 'string') {\n events.push({\n sequence: sequence++,\n eventType: 'user_message',\n content: messageContent,\n toolName: null,\n toolInput: null,\n toolOutput: null,\n tokenUsage: null,\n timestamp,\n metadata: {},\n });\n } else if (Array.isArray(messageContent)) {\n for (const block of messageContent) {\n if (block.type === 'tool_result') {\n events.push({\n sequence: sequence++,\n eventType: 'tool_result',\n content: typeof block.content === 'string' ? block.content : JSON.stringify(block.content),\n toolName: null,\n toolInput: null,\n toolOutput: null,\n tokenUsage: null,\n timestamp,\n metadata: { toolUseId: block.tool_use_id },\n });\n }\n }\n }\n } else if (record.type === 'assistant') {\n const contentBlocks = Array.isArray(record.message?.content)\n ? record.message.content\n : [];\n\n // Extract token usage from the API response\n const usage = record.message?.usage;\n const tokenUsage = usage\n ? {\n input: usage.input_tokens ?? 0,\n output: usage.output_tokens ?? 0,\n cacheRead: usage.cache_read_input_tokens ?? 0,\n cacheWrite: usage.cache_creation_input_tokens ?? 0,\n }\n : null;\n\n // Build metadata with model info and extra usage details\n const assistantMeta: Record<string, unknown> = {};\n if (record.message?.model) assistantMeta.model = record.message.model;\n if (record.message?.stop_reason) assistantMeta.stopReason = record.message.stop_reason;\n if (record.requestId) assistantMeta.requestId = record.requestId;\n if (usage?.service_tier) assistantMeta.serviceTier = usage.service_tier;\n if (usage?.speed) assistantMeta.speed = usage.speed;\n if (usage?.cache_creation) assistantMeta.cacheCreationDetail = usage.cache_creation;\n if (usage?.server_tool_use) assistantMeta.serverToolUse = usage.server_tool_use;\n\n for (const block of contentBlocks) {\n if (block.type === 'text') {\n events.push({\n sequence: sequence++,\n eventType: 'assistant_message',\n content: block.text ?? '',\n toolName: null,\n toolInput: null,\n toolOutput: null,\n tokenUsage,\n timestamp,\n metadata: assistantMeta,\n });\n } else if (block.type === 'thinking') {\n events.push({\n sequence: sequence++,\n eventType: 'thinking',\n content: block.thinking ?? '',\n toolName: null,\n toolInput: null,\n toolOutput: null,\n tokenUsage,\n timestamp,\n metadata: assistantMeta,\n });\n } else if (block.type === 'tool_use') {\n events.push({\n sequence: sequence++,\n eventType: 'tool_call',\n content: null,\n toolName: block.name ?? 'unknown',\n toolInput: block.input ?? null,\n toolOutput: null,\n tokenUsage,\n timestamp,\n metadata: { ...assistantMeta, toolUseId: block.id },\n });\n }\n }\n }\n }\n\n const timestamps = events.map((e) => e.timestamp).sort();\n const now = new Date().toISOString();\n\n return {\n tool: 'claude_code',\n projectPath,\n gitBranch,\n startedAt: timestamps[0] ?? now,\n endedAt: timestamps[timestamps.length - 1] ?? null,\n events,\n metadata,\n };\n }\n}\n", "import type { SessionAdapter, SessionEvent, ParsedSession } from '../types.js';\n\ninterface CursorBubble {\n type: number; // 1 = user, 2 = assistant\n text: string;\n createdAt?: string;\n}\n\ninterface CursorComposer {\n composerId: string;\n conversationId?: string;\n createdAt?: string;\n conversation?: CursorBubble[];\n}\n\ninterface CursorComposerData {\n allComposers: Record<string, CursorComposer>;\n}\n\nexport class CursorAdapter implements SessionAdapter {\n tool = 'cursor' as const;\n\n /**\n * Parse from a SQLite database file path.\n * Reads `composer.composerData` from the ItemTable.\n */\n parse(dbPath: string): ParsedSession {\n // Dynamic import to avoid bundling better-sqlite3 when not needed\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const Database = require('better-sqlite3');\n const db = new Database(dbPath, { readonly: true });\n\n try {\n const row = db.prepare(\n \"SELECT value FROM ItemTable WHERE key = 'composer.composerData'\",\n ).get() as { value: string } | undefined;\n\n if (!row) {\n return this.emptySession();\n }\n\n return this.parseComposerJson(row.value);\n } finally {\n db.close();\n }\n }\n\n /**\n * Parse from raw composer data JSON (for testing without SQLite).\n */\n parseComposerJson(json: string): ParsedSession {\n let data: CursorComposerData;\n try {\n data = JSON.parse(json) as CursorComposerData;\n } catch {\n return this.emptySession();\n }\n\n const composers = Object.values(data.allComposers ?? {});\n if (composers.length === 0) {\n return this.emptySession();\n }\n\n // Take the most recent composer conversation\n const composer = composers.sort((a, b) => {\n const aTime = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const bTime = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return bTime - aTime;\n })[0];\n\n const bubbles = composer.conversation ?? [];\n const events: SessionEvent[] = [];\n\n for (let i = 0; i < bubbles.length; i++) {\n const bubble = bubbles[i];\n const timestamp = bubble.createdAt ?? new Date().toISOString();\n\n events.push({\n sequence: i,\n eventType: bubble.type === 1 ? 'user_message' : 'assistant_message',\n content: bubble.text,\n toolName: null,\n toolInput: null,\n toolOutput: null,\n tokenUsage: null,\n timestamp,\n metadata: {},\n });\n }\n\n const timestamps = events.map((e) => e.timestamp).sort();\n const now = new Date().toISOString();\n\n return {\n tool: 'cursor',\n projectPath: null,\n gitBranch: null,\n startedAt: timestamps[0] ?? now,\n endedAt: timestamps[timestamps.length - 1] ?? null,\n events,\n metadata: { composerId: composer.composerId },\n };\n }\n\n private emptySession(): ParsedSession {\n return {\n tool: 'cursor',\n projectPath: null,\n gitBranch: null,\n startedAt: new Date().toISOString(),\n endedAt: null,\n events: [],\n metadata: {},\n };\n }\n}\n", "import type { SessionAdapter, SessionEvent, ParsedSession } from '../types.js';\n\ninterface CopilotRequest {\n message: string;\n timestamp?: number;\n response?: {\n message: string;\n timestamp?: number;\n };\n}\n\ninterface CopilotSession {\n requesterUsername?: string;\n requests?: CopilotRequest[];\n customTitle?: string;\n}\n\nexport class VSCodeCopilotAdapter implements SessionAdapter {\n tool = 'vscode_copilot' as const;\n\n parse(input: string): ParsedSession {\n let data: CopilotSession;\n try {\n data = JSON.parse(input) as CopilotSession;\n } catch {\n return this.emptySession();\n }\n\n const requests = data.requests ?? [];\n const events: SessionEvent[] = [];\n let sequence = 0;\n\n for (const req of requests) {\n const userTimestamp = req.timestamp\n ? new Date(req.timestamp).toISOString()\n : new Date().toISOString();\n\n events.push({\n sequence: sequence++,\n eventType: 'user_message',\n content: req.message,\n toolName: null,\n toolInput: null,\n toolOutput: null,\n tokenUsage: null,\n timestamp: userTimestamp,\n metadata: {},\n });\n\n if (req.response) {\n const assistantTimestamp = req.response.timestamp\n ? new Date(req.response.timestamp).toISOString()\n : userTimestamp;\n\n events.push({\n sequence: sequence++,\n eventType: 'assistant_message',\n content: req.response.message,\n toolName: null,\n toolInput: null,\n toolOutput: null,\n tokenUsage: null,\n timestamp: assistantTimestamp,\n metadata: {},\n });\n }\n }\n\n const timestamps = events.map((e) => e.timestamp).sort();\n const now = new Date().toISOString();\n\n return {\n tool: 'vscode_copilot',\n projectPath: null,\n gitBranch: null,\n startedAt: timestamps[0] ?? now,\n endedAt: timestamps[timestamps.length - 1] ?? null,\n events,\n metadata: {\n ...(data.requesterUsername && { username: data.requesterUsername }),\n ...(data.customTitle && { title: data.customTitle }),\n },\n };\n }\n\n private emptySession(): ParsedSession {\n return {\n tool: 'vscode_copilot',\n projectPath: null,\n gitBranch: null,\n startedAt: new Date().toISOString(),\n endedAt: null,\n events: [],\n metadata: {},\n };\n }\n}\n", "import type { SessionAdapter, SessionEvent, ParsedSession } from '../types.js';\n\ninterface CopilotAgentToolCall {\n name?: string;\n input?: Record<string, unknown>;\n output?: Record<string, unknown>;\n timestamp?: number;\n}\n\ninterface CopilotAgentRequest {\n message: string;\n timestamp?: number;\n agentMode?: boolean;\n toolCalls?: CopilotAgentToolCall[];\n response?: {\n message: string;\n timestamp?: number;\n };\n}\n\ninterface CopilotAgentSession {\n requesterUsername?: string;\n requests?: CopilotAgentRequest[];\n customTitle?: string;\n agentMode?: boolean;\n}\n\nexport class CopilotAgentAdapter implements SessionAdapter {\n tool = 'copilot_agent' as const;\n\n parse(input: string): ParsedSession {\n let data: CopilotAgentSession;\n try {\n data = JSON.parse(input) as CopilotAgentSession;\n } catch {\n return this.emptySession();\n }\n\n const requests = data.requests ?? [];\n const events: SessionEvent[] = [];\n let sequence = 0;\n\n for (const req of requests) {\n const userTimestamp = req.timestamp\n ? new Date(req.timestamp).toISOString()\n : new Date().toISOString();\n\n events.push({\n sequence: sequence++,\n eventType: 'user_message',\n content: req.message,\n toolName: null,\n toolInput: null,\n toolOutput: null,\n tokenUsage: null,\n timestamp: userTimestamp,\n metadata: {},\n });\n\n // Emit tool_call events for each tool call\n if (req.toolCalls) {\n for (const tc of req.toolCalls) {\n const toolTimestamp = tc.timestamp\n ? new Date(tc.timestamp).toISOString()\n : userTimestamp;\n\n events.push({\n sequence: sequence++,\n eventType: 'tool_call',\n content: null,\n toolName: tc.name ?? 'unknown',\n toolInput: tc.input ?? null,\n toolOutput: tc.output ?? null,\n tokenUsage: null,\n timestamp: toolTimestamp,\n metadata: {},\n });\n }\n }\n\n if (req.response) {\n const assistantTimestamp = req.response.timestamp\n ? new Date(req.response.timestamp).toISOString()\n : userTimestamp;\n\n events.push({\n sequence: sequence++,\n eventType: 'assistant_message',\n content: req.response.message,\n toolName: null,\n toolInput: null,\n toolOutput: null,\n tokenUsage: null,\n timestamp: assistantTimestamp,\n metadata: {},\n });\n }\n }\n\n const timestamps = events.map((e) => e.timestamp).sort();\n const now = new Date().toISOString();\n\n return {\n tool: 'copilot_agent',\n projectPath: null,\n gitBranch: null,\n startedAt: timestamps[0] ?? now,\n endedAt: timestamps[timestamps.length - 1] ?? null,\n events,\n metadata: {\n ...(data.requesterUsername && { username: data.requesterUsername }),\n ...(data.customTitle && { title: data.customTitle }),\n ...(data.agentMode != null && { agentMode: data.agentMode }),\n },\n };\n }\n\n private emptySession(): ParsedSession {\n return {\n tool: 'copilot_agent',\n projectPath: null,\n gitBranch: null,\n startedAt: new Date().toISOString(),\n endedAt: null,\n events: [],\n metadata: {},\n };\n }\n}\n", "import type { SessionAdapter, SessionEvent, ParsedSession } from '../types.js';\n\ninterface RuleMetricMcpEvent {\n sequence?: number;\n eventType?: string;\n content?: string | null;\n toolName?: string | null;\n toolInput?: Record<string, unknown> | null;\n toolOutput?: Record<string, unknown> | null;\n tokenUsage?: { input: number; output: number; cacheRead?: number; cacheWrite?: number } | null;\n timestamp?: string;\n metadata?: Record<string, unknown>;\n}\n\ninterface RuleMetricMcpSession {\n events?: RuleMetricMcpEvent[];\n projectPath?: string;\n gitBranch?: string;\n startedAt?: string;\n endedAt?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport class RuleMetricMcpAdapter implements SessionAdapter {\n tool = 'rulemetric_mcp' as const;\n\n parse(input: string): ParsedSession {\n let data: RuleMetricMcpSession;\n try {\n data = JSON.parse(input) as RuleMetricMcpSession;\n } catch {\n return this.emptySession();\n }\n\n const rawEvents = data.events ?? [];\n const events: SessionEvent[] = rawEvents.map((e, i) => ({\n sequence: e.sequence ?? i,\n eventType: this.normalizeEventType(e.eventType),\n content: e.content ?? null,\n toolName: e.toolName ?? null,\n toolInput: e.toolInput ?? null,\n toolOutput: e.toolOutput ?? null,\n tokenUsage: e.tokenUsage ?? null,\n timestamp: e.timestamp ?? new Date().toISOString(),\n metadata: e.metadata ?? {},\n }));\n\n const now = new Date().toISOString();\n\n return {\n tool: 'rulemetric_mcp',\n projectPath: data.projectPath ?? null,\n gitBranch: data.gitBranch ?? null,\n startedAt: data.startedAt ?? events[0]?.timestamp ?? now,\n endedAt: data.endedAt ?? events[events.length - 1]?.timestamp ?? null,\n events,\n metadata: data.metadata ?? {},\n };\n }\n\n private normalizeEventType(type?: string): SessionEvent['eventType'] {\n const valid = ['user_message', 'assistant_message', 'tool_call', 'tool_result', 'thinking', 'error'];\n if (type && valid.includes(type)) return type as SessionEvent['eventType'];\n return 'assistant_message';\n }\n\n private emptySession(): ParsedSession {\n return {\n tool: 'rulemetric_mcp',\n projectPath: null,\n gitBranch: null,\n startedAt: new Date().toISOString(),\n endedAt: null,\n events: [],\n metadata: {},\n };\n }\n}\n", "import type { SessionAdapter, SessionTool } from './types.js';\nimport { ClaudeCodeAdapter } from './adapters/claude-code.js';\nimport { CursorAdapter } from './adapters/cursor.js';\nimport { VSCodeCopilotAdapter } from './adapters/vscode-copilot.js';\nimport { CopilotAgentAdapter } from './adapters/copilot-agent.js';\nimport { RuleMetricMcpAdapter } from './adapters/rulemetric-mcp.js';\n\nconst adapters: Record<string, () => SessionAdapter> = {\n claude_code: () => new ClaudeCodeAdapter(),\n cursor: () => new CursorAdapter(),\n vscode_copilot: () => new VSCodeCopilotAdapter(),\n copilot_agent: () => new CopilotAgentAdapter(),\n rulemetric_mcp: () => new RuleMetricMcpAdapter(),\n};\n\nexport function getAdapter(tool: SessionTool): SessionAdapter {\n const factory = adapters[tool];\n if (!factory) {\n throw new Error(`Unknown session tool: ${tool}`);\n }\n return factory();\n}\n\nexport function detectTool(input: string): SessionTool | null {\n const trimmed = input.trim();\n\n if (trimmed.startsWith('{')) {\n // Try single-line JSON first (JSONL first line), then full multi-line JSON\n const firstLine = trimmed.split('\\n')[0];\n let parsed: Record<string, unknown> | null = null;\n\n try {\n parsed = JSON.parse(firstLine);\n } catch {\n // First line wasn't complete JSON (pretty-printed) \u2014 try full content\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n // Not valid JSON \u2014 fall through to JSONL detection\n }\n }\n\n if (parsed) {\n // Claude Code JSONL: has type field with user/assistant\n if (parsed.type === 'user' || parsed.type === 'assistant') {\n return 'claude_code';\n }\n\n // RuleMetric MCP: has top-level events array without other format markers\n if (Array.isArray(parsed.events) && !parsed.requests && !parsed.allComposers) {\n return 'rulemetric_mcp';\n }\n\n // Copilot Agent: has requests array with toolCalls or agentMode flag\n if (Array.isArray(parsed.requests)) {\n const hasToolCalls = parsed.requests.some(\n (r: Record<string, unknown>) => Array.isArray(r.toolCalls),\n );\n if (hasToolCalls || parsed.agentMode) {\n return 'copilot_agent';\n }\n return 'vscode_copilot';\n }\n\n // Cursor: has allComposers key\n if (parsed.allComposers) {\n return 'cursor';\n }\n }\n\n // JSONL detection: multiple lines, each valid JSON\n if (!parsed) {\n try {\n const lines = trimmed.split('\\n').filter((l) => l.trim());\n if (lines.length > 1) {\n const first = JSON.parse(lines[0]);\n if (first.type === 'user' || first.type === 'assistant') {\n return 'claude_code';\n }\n }\n } catch {\n // Not JSONL either\n }\n }\n }\n\n return null;\n}\n", "import type { SessionEvent, SessionAnalysis } from './types.js';\n\ninterface InstructionContext {\n id: string;\n name: string;\n content: string;\n}\n\nexport function buildAnalysisPrompt(\n events: SessionEvent[],\n instructions: InstructionContext[],\n): string {\n const instructionSection = instructions.length > 0\n ? instructions.map((i) => `### ${i.name} (ID: ${i.id})\\n\\n${i.content}`).join('\\n\\n---\\n\\n')\n : 'No instructions were linked to this session.';\n\n const eventSummary = events.map((e) => {\n const prefix = `[${e.sequence}] ${e.eventType}`;\n if (e.eventType === 'tool_call') {\n return `${prefix}: ${e.toolName}(${JSON.stringify(e.toolInput)})`;\n }\n if (e.eventType === 'tool_result') {\n const preview = e.content?.slice(0, 200) ?? '';\n return `${prefix}: ${preview}${e.content && e.content.length > 200 ? '...' : ''}`;\n }\n const preview = e.content?.slice(0, 300) ?? '';\n return `${prefix}: ${preview}${e.content && e.content.length > 300 ? '...' : ''}`;\n }).join('\\n');\n\n return `You are analyzing an AI coding session to evaluate how well the agent followed its instructions and to suggest improvements.\n\n## Active Instructions\n\n${instructionSection}\n\n## Session Transcript\n\n${eventSummary}\n\n## Analysis Tasks\n\n1. **Adherence Score** (0-100): Rate how well the agent followed each active instruction. 100 = perfect adherence.\n2. **Deviations**: List specific events where the agent deviated from instructions. Each deviation needs:\n - eventSequence: the event number\n - instructionId: which instruction was violated\n - description: what went wrong\n - severity: \"low\", \"medium\", or \"high\"\n3. **Suggestions**: For each gap or deviation, propose a specific edit to the instruction. Each suggestion needs:\n - instructionId: which instruction to improve\n - currentContent: the relevant excerpt from the instruction\n - suggestedContent: your proposed replacement text\n - rationale: why this change would help\n - confidence: 0-100 how confident you are this would improve outcomes\n4. **Summary**: One paragraph summarizing the session quality.\n\nReturn your response as JSON matching this schema:\n{\n \"adherenceScore\": <number 0-100>,\n \"deviations\": [{ \"eventSequence\": <number>, \"instructionId\": \"<string>\", \"description\": \"<string>\", \"severity\": \"<low|medium|high>\" }],\n \"suggestions\": [{ \"instructionId\": \"<string>\", \"currentContent\": \"<string>\", \"suggestedContent\": \"<string>\", \"rationale\": \"<string>\", \"confidence\": <number 0-100> }],\n \"summary\": \"<string>\"\n}\n\nReturn valid JSON only.`;\n}\n\nexport function parseAnalysisResponse(response: string): SessionAnalysis | null {\n try {\n // Extract JSON from response (may be wrapped in markdown fences)\n const jsonMatch = response.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) return null;\n\n const parsed = JSON.parse(jsonMatch[0]);\n\n // Validate required fields\n if (\n typeof parsed.adherenceScore !== 'number' ||\n !Array.isArray(parsed.deviations) ||\n !Array.isArray(parsed.suggestions) ||\n typeof parsed.summary !== 'string'\n ) {\n return null;\n }\n\n return {\n adherenceScore: parsed.adherenceScore,\n deviations: parsed.deviations,\n suggestions: parsed.suggestions,\n summary: parsed.summary,\n analyzedAt: new Date().toISOString(),\n };\n } catch {\n return null;\n }\n}\n", "import { readdir, stat } from 'fs/promises';\nimport { basename, join } from 'path';\nimport { homedir } from 'os';\n\nimport type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js';\n\nconst shortNames: Record<string, string> = {\n 'claude-opus-4-7': 'Opus 4.7',\n 'claude-opus-4-6': 'Opus 4.6',\n 'claude-opus-4-5': 'Opus 4.5',\n 'claude-opus-4-1': 'Opus 4.1',\n 'claude-opus-4': 'Opus 4',\n 'claude-sonnet-4-6': 'Sonnet 4.6',\n 'claude-sonnet-4-5': 'Sonnet 4.5',\n 'claude-sonnet-4': 'Sonnet 4',\n 'claude-3-7-sonnet': 'Sonnet 3.7',\n 'claude-3-5-sonnet': 'Sonnet 3.5',\n 'claude-haiku-4-5': 'Haiku 4.5',\n 'claude-3-5-haiku': 'Haiku 3.5',\n};\n\nfunction getClaudeDir(): string {\n return process.env['CLAUDE_CONFIG_DIR'] || join(homedir(), '.claude');\n}\n\nfunction getProjectsDir(): string {\n return join(getClaudeDir(), 'projects');\n}\n\nfunction getDesktopSessionsDir(): string {\n if (process.platform === 'darwin') return join(homedir(), 'Library', 'Application Support', 'Claude', 'local-agent-mode-sessions');\n if (process.platform === 'win32') return join(homedir(), 'AppData', 'Roaming', 'Claude', 'local-agent-mode-sessions');\n return join(homedir(), '.config', 'Claude', 'local-agent-mode-sessions');\n}\n\nasync function findDesktopProjectDirs(base: string): Promise<string[]> {\n const results: string[] = [];\n async function walk(dir: string, depth: number): Promise<void> {\n if (depth > 8) return;\n const entries = await readdir(dir).catch(() => []);\n for (const entry of entries) {\n if (entry === 'node_modules' || entry === '.git') continue;\n const full = join(dir, entry);\n const s = await stat(full).catch(() => null);\n if (!s?.isDirectory()) continue;\n if (entry === 'projects') {\n const projectDirs = await readdir(full).catch(() => []);\n for (const pd of projectDirs) {\n const pdFull = join(full, pd);\n const pdStat = await stat(pdFull).catch(() => null);\n if (pdStat?.isDirectory()) results.push(pdFull);\n }\n } else {\n await walk(full, depth + 1);\n }\n }\n }\n await walk(base, 0);\n return results;\n}\n\nexport const claudeCode: Provider = {\n name: 'claude',\n displayName: 'Claude Code',\n\n modelDisplayName(model: string): string {\n const canonical = model.replace(/@.*$/, '').replace(/-\\d{8}$/, '');\n for (const [key, name] of Object.entries(shortNames)) {\n if (canonical.startsWith(key)) return name;\n }\n return canonical;\n },\n\n toolDisplayName(rawTool: string): string {\n return rawTool;\n },\n\n async discoverSessions(): Promise<SessionSource[]> {\n const sources: SessionSource[] = [];\n\n const projectsDir = getProjectsDir();\n try {\n const entries = await readdir(projectsDir);\n for (const dirName of entries) {\n const dirPath = join(projectsDir, dirName);\n const dirStat = await stat(dirPath).catch(() => null);\n if (dirStat?.isDirectory()) {\n sources.push({ path: dirPath, project: dirName, provider: 'claude' });\n }\n }\n } catch { /* projects dir may not exist */ }\n\n const desktopDirs = await findDesktopProjectDirs(getDesktopSessionsDir());\n for (const dirPath of desktopDirs) {\n sources.push({ path: dirPath, project: basename(dirPath), provider: 'claude' });\n }\n\n return sources;\n },\n\n createSessionParser(): SessionParser {\n // Claude Code sessions are parsed via the existing ClaudeCodeAdapter.\n // This provider handles discovery; parsing is done by the adapter.\n return {\n async *parse() { /* no-op \u2014 use ClaudeCodeAdapter for full parsing */ },\n };\n },\n};\n", "import { readdir, readFile, stat } from 'fs/promises';\nimport { basename, join } from 'path';\nimport { homedir } from 'os';\n\nimport type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js';\n\nconst modelDisplayNames: Record<string, string> = {\n 'gpt-5.3-codex': 'GPT-5.3 Codex',\n 'gpt-5.4-mini': 'GPT-5.4 Mini',\n 'gpt-5.4': 'GPT-5.4',\n 'gpt-5': 'GPT-5',\n 'gpt-4o-mini': 'GPT-4o Mini',\n 'gpt-4o': 'GPT-4o',\n};\n\nconst toolNameMap: Record<string, string> = {\n exec_command: 'Bash',\n read_file: 'Read',\n write_file: 'Edit',\n apply_diff: 'Edit',\n apply_patch: 'Edit',\n spawn_agent: 'Agent',\n close_agent: 'Agent',\n wait_agent: 'Agent',\n read_dir: 'Glob',\n};\n\ntype CodexEntry = {\n type: string;\n timestamp?: string;\n payload?: {\n type?: string;\n role?: string;\n cwd?: string;\n model_provider?: string;\n originator?: string;\n session_id?: string;\n model?: string;\n name?: string;\n content?: Array<{ type?: string; text?: string }>;\n info?: {\n model?: string;\n model_name?: string;\n last_token_usage?: CodexTokenUsage;\n total_token_usage?: CodexTokenUsage;\n };\n };\n};\n\ntype CodexTokenUsage = {\n input_tokens?: number;\n cached_input_tokens?: number;\n output_tokens?: number;\n reasoning_output_tokens?: number;\n total_tokens?: number;\n};\n\nfunction getCodexDir(): string {\n return process.env['CODEX_HOME'] ?? join(homedir(), '.codex');\n}\n\nfunction sanitizeProject(cwd: string): string {\n return cwd.replace(/^\\//, '').replace(/\\//g, '-');\n}\n\nasync function readFirstLine(filePath: string): Promise<CodexEntry | null> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const line = content.split('\\n')[0];\n if (!line?.trim()) return null;\n return JSON.parse(line) as CodexEntry;\n } catch {\n return null;\n }\n}\n\nasync function isValidCodexSession(filePath: string): Promise<{ valid: boolean; meta?: CodexEntry }> {\n const entry = await readFirstLine(filePath);\n if (!entry) return { valid: false };\n const valid = entry.type === 'session_meta' &&\n typeof entry.payload?.originator === 'string' &&\n entry.payload.originator.toLowerCase().startsWith('codex');\n return { valid, meta: valid ? entry : undefined };\n}\n\nasync function discoverSessionsInDir(codexDir: string): Promise<SessionSource[]> {\n const sessionsDir = join(codexDir, 'sessions');\n const sources: SessionSource[] = [];\n\n let years: string[];\n try {\n years = await readdir(sessionsDir);\n } catch {\n return sources;\n }\n\n for (const year of years) {\n if (!/^\\d{4}$/.test(year)) continue;\n const yearDir = join(sessionsDir, year);\n const months = await readdir(yearDir).catch(() => [] as string[]);\n\n for (const month of months) {\n if (!/^\\d{2}$/.test(month)) continue;\n const monthDir = join(yearDir, month);\n const days = await readdir(monthDir).catch(() => [] as string[]);\n\n for (const day of days) {\n if (!/^\\d{2}$/.test(day)) continue;\n const dayDir = join(monthDir, day);\n const files = await readdir(dayDir).catch(() => [] as string[]);\n\n for (const file of files) {\n if (!file.startsWith('rollout-') || !file.endsWith('.jsonl')) continue;\n const filePath = join(dayDir, file);\n const s = await stat(filePath).catch(() => null);\n if (!s?.isFile()) continue;\n\n const { valid, meta } = await isValidCodexSession(filePath);\n if (!valid || !meta) continue;\n\n const cwd = meta.payload?.cwd ?? 'unknown';\n sources.push({ path: filePath, project: sanitizeProject(cwd), provider: 'codex' });\n }\n }\n }\n }\n\n return sources;\n}\n\nfunction resolveModel(info: CodexEntry['payload'], sessionModel?: string): string {\n return info?.info?.model\n ?? info?.info?.model_name\n ?? sessionModel\n ?? 'gpt-5';\n}\n\nfunction createParser(source: SessionSource, seenKeys: Set<string>): SessionParser {\n return {\n async *parse(): AsyncGenerator<ParsedProviderCall> {\n let content: string;\n try {\n content = await readFile(source.path, 'utf-8');\n } catch {\n return;\n }\n\n const lines = content.split('\\n').filter(l => l.trim());\n let sessionModel: string | undefined;\n let sessionId = '';\n let prevCumulativeTotal = 0;\n let prevInput = 0;\n let prevCached = 0;\n let prevOutput = 0;\n let prevReasoning = 0;\n let pendingTools: string[] = [];\n let pendingUserMessage = '';\n\n for (const line of lines) {\n let entry: CodexEntry;\n try {\n entry = JSON.parse(line) as CodexEntry;\n } catch {\n continue;\n }\n\n if (entry.type === 'session_meta') {\n sessionId = entry.payload?.session_id ?? basename(source.path, '.jsonl');\n sessionModel = entry.payload?.model;\n continue;\n }\n\n if (entry.type === 'response_item' && entry.payload?.type === 'function_call') {\n const rawName = entry.payload.name ?? '';\n pendingTools.push(toolNameMap[rawName] ?? rawName);\n continue;\n }\n\n if (entry.type === 'response_item' && entry.payload?.type === 'message' && entry.payload?.role === 'user') {\n const texts = (entry.payload.content ?? [])\n .filter(c => c.type === 'input_text')\n .map(c => c.text ?? '')\n .filter(Boolean);\n if (texts.length > 0) pendingUserMessage = texts.join(' ');\n continue;\n }\n\n if (entry.type === 'event_msg' && entry.payload?.type === 'token_count') {\n const info = entry.payload.info;\n if (!info) continue;\n\n const cumulativeTotal = info.total_token_usage?.total_tokens ?? 0;\n if (cumulativeTotal > 0 && cumulativeTotal === prevCumulativeTotal) continue;\n prevCumulativeTotal = cumulativeTotal;\n\n const last = info.last_token_usage;\n let inputTokens = 0;\n let cachedInputTokens = 0;\n let outputTokens = 0;\n let reasoningTokens = 0;\n\n if (last) {\n inputTokens = last.input_tokens ?? 0;\n cachedInputTokens = last.cached_input_tokens ?? 0;\n outputTokens = last.output_tokens ?? 0;\n reasoningTokens = last.reasoning_output_tokens ?? 0;\n } else if (cumulativeTotal > 0) {\n const total = info.total_token_usage;\n if (!total) continue;\n inputTokens = (total.input_tokens ?? 0) - prevInput;\n cachedInputTokens = (total.cached_input_tokens ?? 0) - prevCached;\n outputTokens = (total.output_tokens ?? 0) - prevOutput;\n reasoningTokens = (total.reasoning_output_tokens ?? 0) - prevReasoning;\n }\n\n if (!last) {\n const total = info.total_token_usage;\n if (total) {\n prevInput = total.input_tokens ?? 0;\n prevCached = total.cached_input_tokens ?? 0;\n prevOutput = total.output_tokens ?? 0;\n prevReasoning = total.reasoning_output_tokens ?? 0;\n }\n }\n\n const totalTokens = inputTokens + cachedInputTokens + outputTokens + reasoningTokens;\n if (totalTokens === 0) continue;\n\n // OpenAI includes cached tokens inside input_tokens; normalize to non-cached only\n const uncachedInputTokens = Math.max(0, inputTokens - cachedInputTokens);\n\n const model = resolveModel(entry.payload, sessionModel);\n const timestamp = entry.timestamp ?? '';\n const dedupKey = `codex:${source.path}:${timestamp}:${cumulativeTotal}`;\n\n if (seenKeys.has(dedupKey)) continue;\n seenKeys.add(dedupKey);\n\n yield {\n provider: 'codex',\n model,\n inputTokens: uncachedInputTokens,\n outputTokens,\n cacheCreationInputTokens: 0,\n cacheReadInputTokens: cachedInputTokens,\n cachedInputTokens,\n reasoningTokens,\n costUSD: 0, // Cost calculated downstream\n tools: pendingTools,\n bashCommands: [],\n timestamp,\n speed: 'standard',\n deduplicationKey: dedupKey,\n userMessage: pendingUserMessage,\n sessionId,\n };\n\n pendingTools = [];\n pendingUserMessage = '';\n }\n }\n },\n };\n}\n\nexport const codex: Provider = {\n name: 'codex',\n displayName: 'Codex',\n\n modelDisplayName(model: string): string {\n for (const [key, name] of Object.entries(modelDisplayNames)) {\n if (model.startsWith(key)) return name;\n }\n return model;\n },\n\n toolDisplayName(rawTool: string): string {\n return toolNameMap[rawTool] ?? rawTool;\n },\n\n async discoverSessions(): Promise<SessionSource[]> {\n return discoverSessionsInDir(getCodexDir());\n },\n\n createSessionParser(source: SessionSource, seenKeys: Set<string>): SessionParser {\n return createParser(source, seenKeys);\n },\n};\n", "import { readdir, readFile, stat } from 'fs/promises';\nimport { basename, dirname, join } from 'path';\nimport { homedir } from 'os';\n\nimport type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js';\n\nconst modelDisplayNames: Record<string, string> = {\n 'gpt-4.1-nano': 'GPT-4.1 Nano',\n 'gpt-4.1-mini': 'GPT-4.1 Mini',\n 'gpt-4.1': 'GPT-4.1',\n 'gpt-4o-mini': 'GPT-4o Mini',\n 'gpt-4o': 'GPT-4o',\n 'gpt-5-mini': 'GPT-5 Mini',\n 'gpt-5': 'GPT-5',\n 'claude-sonnet-4-5': 'Sonnet 4.5',\n 'claude-sonnet-4': 'Sonnet 4',\n 'claude-3-7-sonnet': 'Sonnet 3.7',\n 'claude-3-5-sonnet': 'Sonnet 3.5',\n 'o4-mini': 'o4-mini',\n 'o3': 'o3',\n};\n\nconst toolNameMap: Record<string, string> = {\n bash: 'Bash',\n read_file: 'Read',\n write_file: 'Edit',\n edit_file: 'Edit',\n create_file: 'Write',\n delete_file: 'Delete',\n search_files: 'Grep',\n find_files: 'Glob',\n list_directory: 'LS',\n web_search: 'WebSearch',\n fetch_webpage: 'WebFetch',\n github_repo: 'GitHub',\n};\n\nconst modelDisplayEntries = Object.entries(modelDisplayNames).sort((a, b) => b[0].length - a[0].length);\n\ntype ToolRequest = {\n name?: string;\n toolCallId?: string;\n type?: string;\n};\n\ntype CopilotEvent =\n | { type: 'session.model_change'; timestamp?: string; data: { newModel: string } }\n | { type: 'user.message'; timestamp?: string; data: { content: string } }\n | { type: 'assistant.message'; timestamp?: string; data: { messageId: string; outputTokens: number; toolRequests?: ToolRequest[] } };\n\nfunction getCopilotSessionStateDir(): string {\n return join(homedir(), '.copilot', 'session-state');\n}\n\nfunction parseCwd(yaml: string): string | null {\n const match = yaml.match(/^cwd:\\s*(.+)$/m);\n if (!match?.[1]) return null;\n return match[1].replace(/\\s*#.*$/, '').replace(/^['\"]|['\"]$/g, '').trim() || null;\n}\n\nfunction createParser(source: SessionSource, seenKeys: Set<string>): SessionParser {\n return {\n async *parse(): AsyncGenerator<ParsedProviderCall> {\n let content: string;\n try {\n content = await readFile(source.path, 'utf-8');\n } catch {\n return;\n }\n\n const sessionId = basename(dirname(source.path));\n const lines = content.split('\\n').filter(l => l.trim());\n let currentModel = '';\n let pendingUserMessage = '';\n\n for (const line of lines) {\n let event: CopilotEvent;\n try {\n event = JSON.parse(line) as CopilotEvent;\n } catch {\n continue;\n }\n\n if (event.type === 'session.model_change') {\n currentModel = event.data.newModel ?? currentModel;\n continue;\n }\n\n if (event.type === 'user.message') {\n pendingUserMessage = event.data.content ?? '';\n continue;\n }\n\n if (event.type === 'assistant.message') {\n const { messageId, outputTokens, toolRequests = [] } = event.data;\n if (outputTokens === 0) continue;\n if (!currentModel) continue;\n\n const dedupKey = `copilot:${sessionId}:${messageId}`;\n if (seenKeys.has(dedupKey)) continue;\n seenKeys.add(dedupKey);\n\n const tools = toolRequests\n .map(t => t.name ?? '')\n .filter(Boolean)\n .map(n => toolNameMap[n] ?? n);\n\n yield {\n provider: 'copilot',\n model: currentModel,\n inputTokens: 0, // Copilot only logs outputTokens\n outputTokens,\n cacheCreationInputTokens: 0,\n cacheReadInputTokens: 0,\n cachedInputTokens: 0,\n reasoningTokens: 0,\n costUSD: 0,\n tools,\n bashCommands: [],\n timestamp: event.timestamp ?? '',\n speed: 'standard',\n deduplicationKey: dedupKey,\n userMessage: pendingUserMessage,\n sessionId,\n };\n\n pendingUserMessage = '';\n }\n }\n },\n };\n}\n\nasync function discoverSessionsInDir(sessionStateDir: string): Promise<SessionSource[]> {\n const sources: SessionSource[] = [];\n\n let sessionDirs: string[];\n try {\n sessionDirs = await readdir(sessionStateDir);\n } catch {\n return sources;\n }\n\n for (const sessionId of sessionDirs) {\n const eventsPath = join(sessionStateDir, sessionId, 'events.jsonl');\n const s = await stat(eventsPath).catch(() => null);\n if (!s?.isFile()) continue;\n\n let project = sessionId;\n try {\n const yaml = await readFile(join(sessionStateDir, sessionId, 'workspace.yaml'), 'utf-8');\n const cwd = parseCwd(yaml);\n if (cwd) project = basename(cwd);\n } catch { /* no workspace.yaml */ }\n\n sources.push({ path: eventsPath, project, provider: 'copilot' });\n }\n\n return sources;\n}\n\nexport const copilot: Provider = {\n name: 'copilot',\n displayName: 'GitHub Copilot',\n\n modelDisplayName(model: string): string {\n for (const [key, name] of modelDisplayEntries) {\n if (model === key || model.startsWith(key + '-')) return name;\n }\n return model;\n },\n\n toolDisplayName(rawTool: string): string {\n return toolNameMap[rawTool] ?? rawTool;\n },\n\n async discoverSessions(): Promise<SessionSource[]> {\n return discoverSessionsInDir(getCopilotSessionStateDir());\n },\n\n createSessionParser(source: SessionSource, seenKeys: Set<string>): SessionParser {\n return createParser(source, seenKeys);\n },\n};\n", "import { readdir, readFile, stat } from 'fs/promises';\nimport { basename, join } from 'path';\nimport { homedir } from 'os';\n\nimport type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js';\n\nconst modelDisplayNames: Record<string, string> = {\n 'gpt-5.4': 'GPT-5.4',\n 'gpt-5.4-mini': 'GPT-5.4 Mini',\n 'gpt-5': 'GPT-5',\n 'gpt-4o': 'GPT-4o',\n 'gpt-4o-mini': 'GPT-4o Mini',\n};\n\nconst toolNameMap: Record<string, string> = {\n bash: 'Bash',\n read: 'Read',\n edit: 'Edit',\n write: 'Write',\n glob: 'Glob',\n grep: 'Grep',\n task: 'Agent',\n dispatch_agent: 'Agent',\n fetch: 'WebFetch',\n search: 'WebSearch',\n todo: 'TodoWrite',\n patch: 'Patch',\n};\n\nconst modelDisplayEntries = Object.entries(modelDisplayNames).sort((a, b) => b[0].length - a[0].length);\n\ntype PiEntry = {\n type: string;\n id?: string;\n timestamp?: string;\n cwd?: string;\n message?: {\n role?: string;\n content?: Array<{ type?: string; text?: string; name?: string; arguments?: Record<string, unknown> }>;\n model?: string;\n responseId?: string;\n usage?: {\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n };\n };\n};\n\nfunction getPiSessionsDir(): string {\n return join(homedir(), '.pi', 'agent', 'sessions');\n}\n\nasync function readFirstEntry(filePath: string): Promise<PiEntry | null> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const line = content.split('\\n')[0];\n if (!line?.trim()) return null;\n return JSON.parse(line) as PiEntry;\n } catch {\n return null;\n }\n}\n\nfunction extractBashCommands(cmd: string): string[] {\n return cmd.split(/[;&|]+/).map(s => s.trim().split(/\\s+/)[0]).filter(Boolean);\n}\n\nasync function discoverSessionsInDir(sessionsDir: string): Promise<SessionSource[]> {\n const sources: SessionSource[] = [];\n\n let projectDirs: string[];\n try {\n projectDirs = await readdir(sessionsDir);\n } catch {\n return sources;\n }\n\n for (const dirName of projectDirs) {\n const dirPath = join(sessionsDir, dirName);\n const dirStat = await stat(dirPath).catch(() => null);\n if (!dirStat?.isDirectory()) continue;\n\n let files: string[];\n try {\n files = await readdir(dirPath);\n } catch {\n continue;\n }\n\n for (const file of files) {\n if (!file.endsWith('.jsonl')) continue;\n const filePath = join(dirPath, file);\n const fileStat = await stat(filePath).catch(() => null);\n if (!fileStat?.isFile()) continue;\n\n const first = await readFirstEntry(filePath);\n if (!first || first.type !== 'session') continue;\n\n const cwd = first.cwd ?? dirName;\n sources.push({ path: filePath, project: basename(cwd), provider: 'pi' });\n }\n }\n\n return sources;\n}\n\nfunction createParser(source: SessionSource, seenKeys: Set<string>): SessionParser {\n return {\n async *parse(): AsyncGenerator<ParsedProviderCall> {\n let content: string;\n try {\n content = await readFile(source.path, 'utf-8');\n } catch {\n return;\n }\n\n const lines = content.split('\\n').filter(l => l.trim());\n let sessionId = basename(source.path, '.jsonl');\n let pendingUserMessage = '';\n\n for (const [lineIdx, line] of lines.entries()) {\n let entry: PiEntry;\n try {\n entry = JSON.parse(line) as PiEntry;\n } catch {\n continue;\n }\n\n if (entry.type === 'session') {\n sessionId = entry.id ?? sessionId;\n continue;\n }\n\n if (entry.type !== 'message') continue;\n\n const msg = entry.message;\n if (!msg) continue;\n\n if (msg.role === 'user') {\n const texts = (msg.content ?? [])\n .filter(c => c.type === 'text')\n .map(c => c.text ?? '')\n .filter(Boolean);\n if (texts.length > 0) pendingUserMessage = texts.join(' ');\n continue;\n }\n\n if (msg.role !== 'assistant' || !msg.usage) continue;\n\n const { input, output, cacheRead, cacheWrite } = msg.usage;\n if (input === 0 && output === 0) continue;\n\n const model = msg.model ?? 'gpt-5';\n const responseId = msg.responseId ?? '';\n const dedupKey = `pi:${source.path}:${responseId || entry.id || entry.timestamp || String(lineIdx)}`;\n\n if (seenKeys.has(dedupKey)) continue;\n seenKeys.add(dedupKey);\n\n const toolCalls = (msg.content ?? []).filter(c => c.type === 'toolCall' && c.name);\n const tools = toolCalls.map(c => toolNameMap[c.name!] ?? c.name!);\n const bashCommands = toolCalls\n .filter(c => c.name === 'bash')\n .flatMap(c => {\n const cmd = c.arguments?.['command'];\n return typeof cmd === 'string' ? extractBashCommands(cmd) : [];\n });\n\n const timestamp = entry.timestamp ?? '';\n\n yield {\n provider: 'pi',\n model,\n inputTokens: input,\n outputTokens: output,\n cacheCreationInputTokens: cacheWrite,\n cacheReadInputTokens: cacheRead,\n cachedInputTokens: cacheRead,\n reasoningTokens: 0,\n costUSD: 0,\n tools,\n bashCommands,\n timestamp,\n speed: 'standard',\n deduplicationKey: dedupKey,\n userMessage: pendingUserMessage,\n sessionId,\n };\n\n pendingUserMessage = '';\n }\n },\n };\n}\n\nexport const pi: Provider = {\n name: 'pi',\n displayName: 'Pi',\n\n modelDisplayName(model: string): string {\n for (const [key, name] of modelDisplayEntries) {\n if (model.startsWith(key)) return name;\n }\n return model;\n },\n\n toolDisplayName(rawTool: string): string {\n return toolNameMap[rawTool] ?? rawTool;\n },\n\n async discoverSessions(): Promise<SessionSource[]> {\n return discoverSessionsInDir(getPiSessionsDir());\n },\n\n createSessionParser(source: SessionSource, seenKeys: Set<string>): SessionParser {\n return createParser(source, seenKeys);\n },\n};\n", "import { claudeCode } from './claude-code.js';\nimport { codex } from './codex.js';\nimport { copilot } from './copilot.js';\nimport { pi } from './pi.js';\nimport type { Provider, SessionSource } from './types.js';\n\nexport type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js';\n\n// Core providers (no native dependencies)\nconst coreProviders: Provider[] = [claudeCode, codex, copilot, pi];\n\n// Optional providers (require better-sqlite3, lazy-loaded)\nlet cursorProvider: Provider | null = null;\nlet cursorLoadAttempted = false;\n\nasync function loadCursor(): Promise<Provider | null> {\n if (cursorLoadAttempted) return cursorProvider;\n cursorLoadAttempted = true;\n try {\n const { cursor } = await import('./cursor.js');\n cursorProvider = cursor;\n return cursor;\n } catch {\n return null;\n }\n}\n\nlet opencodeProvider: Provider | null = null;\nlet opencodeLoadAttempted = false;\n\nasync function loadOpenCode(): Promise<Provider | null> {\n if (opencodeLoadAttempted) return opencodeProvider;\n opencodeLoadAttempted = true;\n try {\n const { opencode } = await import('./opencode.js');\n opencodeProvider = opencode;\n return opencode;\n } catch {\n return null;\n }\n}\n\nexport async function getAllProviders(): Promise<Provider[]> {\n const [cursor, opencode] = await Promise.all([loadCursor(), loadOpenCode()]);\n const all = [...coreProviders];\n if (cursor) all.push(cursor);\n if (opencode) all.push(opencode);\n return all;\n}\n\nexport async function discoverAllSessions(providerFilter?: string): Promise<SessionSource[]> {\n const allProviders = await getAllProviders();\n const filtered = providerFilter && providerFilter !== 'all'\n ? allProviders.filter(p => p.name === providerFilter)\n : allProviders;\n const all: SessionSource[] = [];\n for (const provider of filtered) {\n const sessions = await provider.discoverSessions();\n all.push(...sessions);\n }\n return all;\n}\n\nexport async function getProvider(name: string): Promise<Provider | undefined> {\n if (name === 'cursor') return (await loadCursor()) ?? undefined;\n if (name === 'opencode') return (await loadOpenCode()) ?? undefined;\n return coreProviders.find(p => p.name === name);\n}\n", "import { spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ToolLauncher, LaunchConfig, LaunchResult, LaunchCapabilities } from './types.js';\nimport {\n buildTmuxAttachCommand,\n buildTmuxLaunchArgs,\n buildTmuxSendKeysArgs,\n isTmuxAvailable,\n runTmux,\n sanitizeTmuxName,\n type TmuxRunResult,\n} from '../terminal/tmux.js';\n\ninterface ClaudeCodeLauncherOptions {\n tmuxAvailable?: () => Promise<boolean>;\n runTmux?: (args: string[]) => Promise<TmuxRunResult>;\n}\n\ntype TmuxFallbackReason = NonNullable<LaunchResult['fallbackReason']>;\n\n// Mirror the API's `z.string().uuid()` contract (8-4-4-4-12 hex). runId is a\n// server-minted uuid today and the generic launch-jobs route strips it via\n// Zod, so this is NOT client-reachable \u2014 it's defense-in-depth so a future\n// relaxation of that boundary can't turn the Terminal.app `.command` script\n// into shell command-substitution RCE (e.g. a runId of `$(touch /tmp/x)`).\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\n/**\n * Validate a caller-supplied runId at the launcher boundary. Returns the runId\n * only if it's a well-formed uuid; otherwise returns undefined (and logs a\n * warning) so NONE of the three injection paths stamp an untrusted value.\n */\nexport function validateRunId(runId: string | undefined): string | undefined {\n if (runId === undefined) return undefined;\n if (UUID_RE.test(runId)) return runId;\n console.warn(\n `[claude-code-launcher] dropping non-uuid runId (RULEMETRIC_RUN_ID not injected): ${JSON.stringify(runId)}`,\n );\n return undefined;\n}\n\n/**\n * Single-quote a string for safe inclusion in a POSIX shell script. Single\n * quotes suppress ALL expansion (command substitution, parameter expansion,\n * backticks); the `'\\''` trick closes the quote, emits an escaped literal\n * quote, then reopens \u2014 the only escape single-quoting needs.\n */\nexport function shellSingleQuote(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n\nexport class ClaudeCodeLauncher implements ToolLauncher {\n tool = 'claude_code' as const;\n\n constructor(private readonly options: ClaudeCodeLauncherOptions = {}) {}\n\n capabilities: LaunchCapabilities = {\n headless: true,\n interactive: true,\n remoteSpawn: true,\n };\n\n async launch(config: LaunchConfig): Promise<LaunchResult> {\n const sessionId = randomUUID();\n\n // Validate the runId ONCE at the launcher boundary so all three injection\n // paths (headless env, tmux argv, Terminal.app .command) only ever see a\n // well-formed uuid. A non-uuid value is dropped (see validateRunId).\n const safeConfig: LaunchConfig = { ...config, runId: validateRunId(config.runId) };\n\n if (safeConfig.mode === 'headless') {\n return this.launchHeadless(safeConfig, sessionId);\n }\n return this.launchInteractive(safeConfig, sessionId);\n }\n\n private async launchHeadless(config: LaunchConfig, sessionId: string): Promise<LaunchResult> {\n const args = ['-p', config.prompt ?? '', '--output-format', 'json'];\n if (config.systemPrompt) args.push('--system-prompt', config.systemPrompt);\n if (config.model) args.push('--model', config.model);\n\n // Remove CLAUDECODE flag so spawned claude doesn't think it's a nested session.\n // Remove HTTPS_PROXY so headless runs bypass the capture proxy \u2014 these are\n // infrastructure calls, not user sessions.\n const env = { ...process.env };\n delete env.CLAUDECODE;\n delete env.HTTPS_PROXY;\n // Task 3.1: stamp the run id so the spawned session's hook forwards it.\n // NOTE: RULEMETRIC_RUN_ID is effectively a no-op on the headless path today\n // \u2014 headless runs delete HTTPS_PROXY above to bypass the capture proxy/hook,\n // and runs always use mode:'interactive'. Kept for forward-compat. runId is\n // already validated as a uuid at the launch() boundary.\n if (config.runId) env.RULEMETRIC_RUN_ID = config.runId;\n\n return new Promise((resolve) => {\n const proc = spawn('claude', args, {\n cwd: config.projectPath,\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n proc.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });\n proc.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });\n\n const timer = config.timeout\n ? setTimeout(() => { proc.kill('SIGTERM'); }, config.timeout)\n : null;\n\n proc.on('close', (code) => {\n if (timer) clearTimeout(timer);\n let output = stdout;\n try {\n const parsed = JSON.parse(stdout);\n output = parsed.result ?? parsed.content ?? stdout;\n } catch { /* raw text */ }\n\n resolve({\n sessionId,\n pid: proc.pid,\n exitCode: code,\n output,\n error: code !== 0 ? stderr : undefined,\n });\n });\n\n proc.on('error', (err) => {\n if (timer) clearTimeout(timer);\n resolve({ sessionId, error: `Failed to spawn claude: ${err.message}` });\n });\n });\n }\n\n private async launchInteractive(config: LaunchConfig, sessionId: string): Promise<LaunchResult> {\n const tmuxAvailableCheck = this.options.tmuxAvailable ?? isTmuxAvailable;\n if (await tmuxAvailableCheck().catch(() => false)) {\n return this.launchInteractiveTmux(config, sessionId);\n }\n return this.launchInteractiveTerminal(config, sessionId, 'tmux_not_found');\n }\n\n private async launchInteractiveTmux(config: LaunchConfig, sessionId: string): Promise<LaunchResult> {\n const run = this.options.runTmux ?? runTmux;\n const target = sanitizeTmuxName(`claude-${config.resumeSessionId ?? sessionId}`);\n const launchResult = await run(buildTmuxLaunchArgs({\n sessionName: target,\n projectPath: config.projectPath,\n resumeSessionId: config.resumeSessionId,\n model: config.model,\n // Task 3.1: inject the run id into the spawned session's env so its\n // session-start hook forwards it to POST /api/sessions (stamps run_id).\n runId: config.runId,\n }));\n\n if (launchResult.code !== 0) {\n return this.launchInteractiveTerminal(\n config,\n sessionId,\n 'tmux_launch_failed',\n `tmux new-session exited ${launchResult.code}: ${launchResult.stderr.trim() || 'no stderr'}`,\n );\n }\n\n const attachResult = await this.openTerminalAttach(target, sessionId);\n\n if (config.prompt) {\n const sendResult = await run(buildTmuxSendKeysArgs(target, config.prompt));\n if (sendResult.code !== 0) {\n return {\n sessionId,\n liveSendAvailable: false,\n fallbackReason: 'tmux_launch_failed',\n error: `tmux send-keys exited ${sendResult.code}: ${sendResult.stderr.trim() || 'no stderr'}`,\n };\n }\n const enterResult = await run(buildTmuxSendKeysArgs(target, 'Enter', { literal: false }));\n if (enterResult.code !== 0) {\n return {\n sessionId,\n liveSendAvailable: false,\n fallbackReason: 'tmux_launch_failed',\n error: `tmux send Enter exited ${enterResult.code}: ${enterResult.stderr.trim() || 'no stderr'}`,\n };\n }\n }\n\n return {\n sessionId,\n pid: attachResult.pid,\n liveSendAvailable: true,\n ...(attachResult.error ? { error: attachResult.error } : {}),\n terminalTarget: {\n kind: 'tmux',\n target,\n attachCommand: buildTmuxAttachCommand(target),\n live: true,\n },\n };\n }\n\n private async openTerminalAttach(\n target: string,\n sessionId: string,\n ): Promise<{ pid?: number; error?: string }> {\n const scriptPath = join(tmpdir(), `rulemetric-attach-${sessionId}.command`);\n const scriptBody =\n '#!/bin/zsh\\n' +\n 'rm -- \"$0\"\\n' +\n `${buildTmuxAttachCommand(target)}\\n`;\n await writeFile(scriptPath, scriptBody, { mode: 0o755 });\n\n return new Promise((resolve) => {\n const proc = spawn('open', ['-a', 'Terminal', scriptPath], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stderr = '';\n proc.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });\n proc.on('close', (code) => {\n if (code === 0) {\n resolve({ pid: proc.pid });\n } else {\n resolve({\n pid: proc.pid,\n error: `open attach exited ${code}: ${stderr.trim() || 'no stderr'}`,\n });\n }\n });\n proc.on('error', (err) => {\n resolve({ error: `Failed to spawn open attach: ${err.message}` });\n });\n });\n }\n\n private async launchInteractiveTerminal(\n config: LaunchConfig,\n sessionId: string,\n fallbackReason: TmuxFallbackReason,\n fallbackDetail?: string,\n ): Promise<LaunchResult> {\n // Build the claude command that runs INSIDE the new Terminal window. We\n // bake it into a .command script (next step) so the user sees a single\n // self-contained file in their tmpdir instead of a long inline AppleScript.\n const parts = ['claude'];\n if (config.resumeSessionId) {\n parts.push('--resume', config.resumeSessionId);\n } else if (config.prompt) {\n parts.push('-p', JSON.stringify(config.prompt));\n }\n if (config.model) parts.push('--model', config.model);\n const cmd = parts.join(' ');\n\n // Write a self-deleting .command script, then ask LaunchServices (via\n // `open -a Terminal`) to hand it to Terminal.app.\n //\n // History: this used to call `osascript \"tell Terminal to do script ...\"`.\n // That path goes through the macOS TCC AppleEvents bucket, which silently\n // denies launchd-spawned processes (the worker is launched via\n // `com.rulemetric.worker.plist`) \u2014 leaving the user with a \"completed\"\n // launch row and no Terminal window. See:\n // docs/incidents/2026-05-24-launcher-tcc-silent-failure.md.\n //\n // `open -a Terminal foo.command` goes through LaunchServices instead,\n // which is exempt from the AppleEvents bucket AND surfaces a real exit\n // code we can propagate to the mirror row.\n const scriptPath = join(tmpdir(), `rulemetric-launch-${sessionId}.command`);\n // The script self-deletes its own file (`rm -- \"$0\"`) BEFORE handing off\n // to claude so we don't leak temp files even if the user kills the window\n // mid-session. `cd ... || exit` ensures we don't silently launch claude\n // in $HOME when the project path is gone.\n // Task 3.1: export the run id (when present) into the script's shell so\n // the spawned claude \u2014 and its session-start hook \u2014 see RULEMETRIC_RUN_ID.\n // SINGLE-quote the value (not JSON/double-quote): double quotes still allow\n // `$(...)` / backtick command-substitution, so a value like `$(touch /tmp/x)`\n // would EXECUTE when the .command runs. Single quotes suppress all\n // expansion. config.runId is already uuid-validated at the launch()\n // boundary; the quoting is a second layer so the script is robust\n // regardless of value.\n const runIdExport = config.runId\n ? `export RULEMETRIC_RUN_ID=${shellSingleQuote(config.runId)}\\n`\n : '';\n const scriptBody =\n '#!/bin/zsh\\n' +\n 'rm -- \"$0\"\\n' +\n `cd ${JSON.stringify(config.projectPath)} || exit\\n` +\n runIdExport +\n `${cmd}\\n`;\n await writeFile(scriptPath, scriptBody, { mode: 0o755 });\n\n // Note: NOT detached / NOT unref'd. `open` returns once LaunchServices has\n // accepted the request (~10s of ms), so we CAN await its close + propagate\n // the exit code without coupling the Terminal window's lifetime to the\n // worker. Terminal.app is its own process and survives the worker exiting.\n return new Promise((resolve) => {\n const proc = spawn('open', ['-a', 'Terminal', scriptPath], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stderr = '';\n proc.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });\n proc.on('close', (code) => {\n if (code === 0) {\n resolve({\n sessionId,\n pid: proc.pid,\n liveSendAvailable: false,\n fallbackReason,\n ...(fallbackDetail ? { error: fallbackDetail } : {}),\n });\n } else {\n const tail = stderr.trim() || 'no stderr';\n resolve({\n sessionId,\n pid: proc.pid,\n liveSendAvailable: false,\n fallbackReason,\n error: fallbackDetail\n ? `${fallbackDetail}; open exited ${code}: ${tail}`\n : `open exited ${code}: ${tail}`,\n });\n }\n });\n proc.on('error', (err) => {\n resolve({\n sessionId,\n liveSendAvailable: false,\n fallbackReason,\n error: `Failed to spawn open: ${err.message}`,\n });\n });\n });\n }\n}\n", "import { spawn } from 'node:child_process';\n\nexport interface TmuxLaunchArgsInput {\n sessionName: string;\n projectPath: string;\n resumeSessionId?: string;\n model?: string;\n /**\n * Multi-session-run id (Task 3.1). When set, the launched `claude` command\n * is prefixed with `env RULEMETRIC_RUN_ID=<id>` so the value reaches ONLY\n * the spawned session's process (not the tmux server env), where the\n * session-start hook forwards it to POST /api/sessions.\n */\n runId?: string;\n}\n\nexport interface TmuxSendKeysOptions {\n literal?: boolean;\n}\n\nexport function sanitizeTmuxName(input: string): string {\n const cleaned = input\n .trim()\n .replace(/[^A-Za-z0-9_.-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80);\n return `rm-${cleaned || 'session'}`;\n}\n\nexport function buildTmuxLaunchArgs(input: TmuxLaunchArgsInput): string[] {\n // Prefix with `env RULEMETRIC_RUN_ID=<id>` so the var is scoped to the\n // spawned claude process only \u2014 tmux passes argv straight to exec, so this\n // never leaks into the tmux server's environment.\n const claudeArgs = input.runId\n ? ['env', `RULEMETRIC_RUN_ID=${input.runId}`, 'claude']\n : ['claude'];\n if (input.resumeSessionId) {\n claudeArgs.push('--resume', input.resumeSessionId);\n }\n if (input.model) {\n claudeArgs.push('--model', input.model);\n }\n\n return [\n 'new-session',\n '-d',\n '-s',\n input.sessionName,\n '-c',\n input.projectPath,\n '--',\n ...claudeArgs,\n ];\n}\n\nexport function buildTmuxSendKeysArgs(\n target: string,\n message: string,\n options: TmuxSendKeysOptions = {},\n): string[] {\n if (options.literal === false) {\n return ['send-keys', '-t', target, message];\n }\n return ['send-keys', '-t', target, '-l', '--', message];\n}\n\nexport function buildTmuxHasSessionArgs(target: string): string[] {\n return ['has-session', '-t', target];\n}\n\nexport function buildTmuxAttachCommand(target: string): string {\n return `tmux attach -t ${target}`;\n}\n\nexport interface TmuxRunResult {\n code: number | null;\n stderr: string;\n}\n\n// Homebrew installs tmux to /opt/homebrew/bin (Apple Silicon) or\n// /usr/local/bin (Intel). The launchd worker runs with a minimal PATH that\n// omits /opt/homebrew/bin, so a bare spawn('tmux') fails with ENOENT under\n// launchd even though it works in an interactive shell. Augment PATH at the\n// spawn boundary so live-send/launch work in the real (launchd) worker\n// environment \u2014 verified 2026-06-09 when every terminal target flipped to\n// `missing` because the worker couldn't resolve tmux.\nexport function tmuxSpawnEnv(): NodeJS.ProcessEnv {\n const extraDirs = ['/opt/homebrew/bin', '/usr/local/bin'];\n const parts = (process.env.PATH ?? '').split(':').filter(Boolean);\n const missing = extraDirs.filter((d) => !parts.includes(d));\n if (missing.length === 0) return process.env;\n return { ...process.env, PATH: [...parts, ...missing].join(':') };\n}\n\nexport function runTmux(args: string[]): Promise<TmuxRunResult> {\n return new Promise((resolve) => {\n const proc = spawn('tmux', args, {\n stdio: ['ignore', 'ignore', 'pipe'],\n env: tmuxSpawnEnv(),\n });\n let stderr = '';\n proc.stderr.on('data', (d: Buffer) => {\n stderr += d.toString();\n });\n proc.on('close', (code) => resolve({ code, stderr }));\n proc.on('error', (err) => resolve({ code: null, stderr: err.message }));\n });\n}\n\nexport async function isTmuxAvailable(): Promise<boolean> {\n const result = await runTmux(['-V']);\n return result.code === 0;\n}\n", "// codex.ts \u2014 OpenAI's Codex CLI (codex -q \"prompt\")\nimport { spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport type { ToolLauncher, LaunchConfig, LaunchResult, LaunchCapabilities } from './types.js';\n\nexport class CodexLauncher implements ToolLauncher {\n tool = 'codex' as const;\n capabilities: LaunchCapabilities = { headless: true, interactive: true, remoteSpawn: true };\n\n async launch(config: LaunchConfig): Promise<LaunchResult> {\n const sessionId = randomUUID();\n if (config.mode === 'headless') {\n return this.launchHeadless(config, sessionId);\n }\n return this.launchInteractive(config, sessionId);\n }\n\n private launchHeadless(config: LaunchConfig, sessionId: string): Promise<LaunchResult> {\n const args = ['-q', config.prompt ?? ''];\n if (config.model) args.push('--model', config.model);\n\n return new Promise((resolve) => {\n const proc = spawn('codex', args, {\n cwd: config.projectPath,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n proc.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });\n proc.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });\n\n const timer = config.timeout\n ? setTimeout(() => { proc.kill('SIGTERM'); }, config.timeout)\n : null;\n\n proc.on('close', (code) => {\n if (timer) clearTimeout(timer);\n resolve({ sessionId, pid: proc.pid, exitCode: code, output: stdout, error: code !== 0 ? stderr : undefined });\n });\n\n proc.on('error', (err) => {\n if (timer) clearTimeout(timer);\n resolve({ sessionId, error: `Failed to spawn codex: ${err.message}` });\n });\n });\n }\n\n private launchInteractive(config: LaunchConfig, sessionId: string): Promise<LaunchResult> {\n const parts = ['codex'];\n if (config.prompt) parts.push(JSON.stringify(config.prompt));\n const script = `cd ${JSON.stringify(config.projectPath)} && ${parts.join(' ')}`;\n const proc = spawn('osascript', ['-e', `tell application \"Terminal\" to do script ${JSON.stringify(script)}`], {\n stdio: 'ignore',\n detached: true,\n });\n proc.unref();\n return Promise.resolve({ sessionId, pid: proc.pid });\n }\n}\n", "// aider.ts \u2014 Aider CLI (aider --message \"prompt\" for headless)\nimport { spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport type { ToolLauncher, LaunchConfig, LaunchResult, LaunchCapabilities } from './types.js';\n\nexport class AiderLauncher implements ToolLauncher {\n tool = 'aider' as const;\n capabilities: LaunchCapabilities = { headless: true, interactive: true, remoteSpawn: true };\n\n async launch(config: LaunchConfig): Promise<LaunchResult> {\n const sessionId = randomUUID();\n if (config.mode === 'headless') {\n return this.launchHeadless(config, sessionId);\n }\n return this.launchInteractive(config, sessionId);\n }\n\n private launchHeadless(config: LaunchConfig, sessionId: string): Promise<LaunchResult> {\n const args = ['--message', config.prompt ?? '', '--yes'];\n if (config.model) args.push('--model', config.model);\n\n return new Promise((resolve) => {\n const proc = spawn('aider', args, {\n cwd: config.projectPath,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n proc.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });\n proc.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });\n\n const timer = config.timeout\n ? setTimeout(() => { proc.kill('SIGTERM'); }, config.timeout)\n : null;\n\n proc.on('close', (code) => {\n if (timer) clearTimeout(timer);\n resolve({ sessionId, pid: proc.pid, exitCode: code, output: stdout, error: code !== 0 ? stderr : undefined });\n });\n\n proc.on('error', (err) => {\n if (timer) clearTimeout(timer);\n resolve({ sessionId, error: `Failed to spawn aider: ${err.message}` });\n });\n });\n }\n\n private launchInteractive(config: LaunchConfig, sessionId: string): Promise<LaunchResult> {\n const parts = ['aider'];\n if (config.model) parts.push('--model', config.model);\n const script = `cd ${JSON.stringify(config.projectPath)} && ${parts.join(' ')}`;\n const proc = spawn('osascript', ['-e', `tell application \"Terminal\" to do script ${JSON.stringify(script)}`], {\n stdio: 'ignore',\n detached: true,\n });\n proc.unref();\n return Promise.resolve({ sessionId, pid: proc.pid });\n }\n}\n", "// generic.ts \u2014 opens a terminal in the project directory (fallback for unknown tools)\nimport { spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport type { ToolLauncher, LaunchConfig, LaunchResult, LaunchCapabilities } from './types.js';\n\nexport class GenericLauncher implements ToolLauncher {\n constructor(public tool: string) {}\n\n capabilities: LaunchCapabilities = { headless: false, interactive: true, remoteSpawn: false };\n\n async launch(config: LaunchConfig): Promise<LaunchResult> {\n const sessionId = randomUUID();\n if (config.mode === 'headless') {\n return { sessionId, error: `${this.tool} does not support headless mode` };\n }\n\n // Just open a terminal in the project directory\n const script = `cd ${JSON.stringify(config.projectPath)} && echo \"Session ${sessionId} \u2014 tool: ${this.tool}\" && exec $SHELL`;\n const proc = spawn('osascript', ['-e', `tell application \"Terminal\" to do script ${JSON.stringify(script)}`], {\n stdio: 'ignore',\n detached: true,\n });\n proc.unref();\n return { sessionId, pid: proc.pid };\n }\n}\n", "import type { ToolLauncher } from './types.js';\nimport { ClaudeCodeLauncher } from './claude-code.js';\nimport { CodexLauncher } from './codex.js';\nimport { AiderLauncher } from './aider.js';\nimport { GenericLauncher } from './generic.js';\n\nexport type { ToolLauncher, LaunchConfig, LaunchResult, LaunchCapabilities } from './types.js';\nexport { ClaudeCodeLauncher } from './claude-code.js';\nexport { CodexLauncher } from './codex.js';\nexport { AiderLauncher } from './aider.js';\nexport { GenericLauncher } from './generic.js';\n\nconst launchers: Record<string, () => ToolLauncher> = {\n claude_code: () => new ClaudeCodeLauncher(),\n codex: () => new CodexLauncher(),\n aider: () => new AiderLauncher(),\n};\n\nexport function getLauncher(tool: string): ToolLauncher {\n const factory = launchers[tool];\n return factory ? factory() : new GenericLauncher(tool);\n}\n\nexport function getAvailableLaunchers(): Array<{ tool: string; capabilities: ToolLauncher['capabilities'] }> {\n return Object.entries(launchers).map(([tool, factory]) => ({\n tool,\n capabilities: factory().capabilities,\n }));\n}\n", "import path from 'node:path';\n\n/**\n * Shared, pure worktree-path math used by BOTH the worker's `ensureWorktree`\n * (apps/cli/src/lib/worktree.ts, which actually creates the worktree) and the\n * API's run reconcile fallback (apps/api/src/routes/runs.ts, which must derive\n * the same path to match captured sessions). It lives here, in the cross-app\n * `@rulemetric/session` package, so neither side duplicates the sanitizer \u2014\n * a drift between the two would silently break session\u2192run attribution.\n *\n * NOTE: this returns a `path.resolve`-only absolute path. The worker\n * additionally `realpath`-resolves it after creation (macOS `/var` \u2192\n * `/private/var`, etc.), so the persisted `launch_jobs.result.worktreePath` is\n * the authoritative join key. This derivation is a best-effort fallback for\n * the window before a job persists its result, and is exact only when the\n * repo root contains no symlinks.\n */\n\n/** Turn an arbitrary branch name into a filesystem-safe single path segment. */\nexport function sanitizeBranch(branch: string): string {\n return (\n branch\n // collapse any run of non-safe chars into a single dash\n .replace(/[^a-zA-Z0-9._-]+/g, '-')\n // trim leading/trailing dashes/dots so we never produce `.` or `-foo`\n .replace(/^[-.]+|[-.]+$/g, '') || 'worktree'\n );\n}\n\n/**\n * Compute the default worktree path for a (repoRoot, branch) pair: a SIBLING of\n * `repoRoot` at `<repoRoot>/../.rulemetric-worktrees/<sanitized-branch>`. Never\n * inside `repoRoot` (a nested worktree would be tracked by the parent repo).\n */\nexport function worktreePathFor(options: {\n repoRoot: string;\n branch: string;\n basePath?: string;\n}): string {\n return path.resolve(\n options.basePath ??\n path.join(options.repoRoot, '..', '.rulemetric-worktrees', sanitizeBranch(options.branch)),\n );\n}\n"],
5
- "mappings": "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",
6
- "names": ["preview", "readdir", "stat", "basename", "join", "homedir", "readdir", "readFile", "stat", "basename", "join", "homedir", "modelDisplayNames", "toolNameMap", "createParser", "discoverSessionsInDir", "readdir", "readFile", "stat", "basename", "join", "homedir", "modelDisplayNames", "toolNameMap", "modelDisplayEntries", "discoverSessionsInDir", "createParser", "spawn", "join", "spawn", "join", "spawn", "randomUUID", "spawn", "randomUUID", "spawn", "randomUUID"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib/hooks-config.ts"],
4
- "sourcesContent": ["import { execSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport type DetectedTool = 'claude_code' | 'cursor' | 'copilot_agent' | 'antigravity';\n\n/**\n * Detect which AI coding tools are installed in the project by checking\n * for their config directories.\n */\nexport function detectInstalledTools(projectPath: string): DetectedTool[] {\n const tools: DetectedTool[] = ['claude_code'];\n\n if (existsSync(join(projectPath, '.cursor'))) {\n tools.push('cursor');\n }\n\n if (existsSync(join(projectPath, '.github'))) {\n tools.push('copilot_agent');\n }\n\n // Antigravity's workspace config lives at `.agents/`. The user-level\n // hooks file at ~/.gemini/config/hooks.json is installed unconditionally\n // by writeAntigravityUserHooks() so we don't gate on that here.\n if (existsSync(join(projectPath, '.agents'))) {\n tools.push('antigravity');\n }\n\n return tools;\n}\n\n/**\n * Generate Cursor hooks config object.\n *\n * Cursor uses event names like beforeSubmitPrompt, afterFileEdit, etc.\n * Each event maps to an array of hook objects with a `command` field.\n */\nexport function generateCursorHooksConfig(): object {\n // Event coverage matches Cursor 1.7+ hook API (cursor.com/docs/hooks).\n // `sessionStart` is required so the registry file is written with\n // conversation_id before any tool runs; without it the proxy-based linker\n // path would have nothing to map and we'd drop captures. `sessionEnd`\n // emits the session-end signal once per chat conversation (vs `stop`\n // which fires per-turn \u2014 those go through post-tool-use).\n return {\n version: 1,\n hooks: {\n sessionStart: [\n { command: 'rulemetric hooks run session-start' },\n ],\n beforeSubmitPrompt: [\n { command: 'rulemetric hooks run user-prompt' },\n ],\n afterFileEdit: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n beforeShellExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n afterShellExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n beforeMCPExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n afterMCPExecution: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n // `afterAgentResponse` carries the full assistant response text +\n // token usage. Verified empirically via a probe hook on\n // 2026-05-12 \u2014 Cursor passes `text`, `input_tokens`,\n // `output_tokens`, `cache_read_tokens`, `cache_write_tokens`. This\n // is the cleanest path to per-turn assistant content for Cursor\n // (chat traffic itself bypasses the proxy via HTTP/2 \u2014 see\n // docs/audits/2026-05-11-cursor-capture-architecture.md).\n afterAgentResponse: [\n { command: 'rulemetric hooks run assistant-response' },\n ],\n // `stop` fires when the agent loop ends for a single turn (status:\n // completed|aborted|error). Per-turn, NOT per-session \u2014 route it to\n // post-tool-use, not session-end. The real session boundary is\n // `sessionEnd` below.\n stop: [\n { command: 'rulemetric hooks run post-tool-use' },\n ],\n sessionEnd: [\n { command: 'rulemetric hooks run session-end' },\n ],\n },\n };\n}\n\n/**\n * Generate Copilot Agent hooks config object.\n *\n * Copilot uses event names like sessionStart, userPromptSubmitted, etc.\n * Each event maps to an array of hook objects with `type` and `bash` fields.\n */\nexport function generateCopilotHooksConfig(): object {\n return {\n version: 1,\n hooks: {\n sessionStart: [\n { type: 'command', bash: 'rulemetric hooks run session-start' },\n ],\n userPromptSubmitted: [\n { type: 'command', bash: 'rulemetric hooks run user-prompt' },\n ],\n postToolUse: [\n { type: 'command', bash: 'rulemetric hooks run post-tool-use' },\n ],\n sessionEnd: [\n { type: 'command', bash: 'rulemetric hooks run session-end' },\n ],\n },\n };\n}\n\n/**\n * Merge rulemetric hooks into a Cursor `hooks.json` at the given directory.\n *\n * Used by both the project (`<repo>/.cursor/`) and user (`~/.cursor/`) writers.\n * Returns the config file path, or null if the cursor dir doesn't exist.\n */\nfunction writeCursorHooksToDir(cursorDir: string): string | null {\n if (!existsSync(cursorDir)) {\n return null;\n }\n\n const configPath = join(cursorDir, 'hooks.json');\n const generated = generateCursorHooksConfig() as {\n version: number;\n hooks: Record<string, Array<{ command: string }>>;\n };\n\n let existing: { version?: number; hooks?: Record<string, unknown[]> } = {};\n if (existsSync(configPath)) {\n try {\n existing = JSON.parse(readFileSync(configPath, 'utf-8'));\n } catch {\n existing = {};\n }\n }\n\n const merged: Record<string, unknown[]> = { ...(existing.hooks ?? {}) };\n\n for (const [event, hooks] of Object.entries(generated.hooks)) {\n const current = merged[event] ?? [];\n const hasRulemetric = current.some(\n (h: unknown) =>\n typeof h === 'object' &&\n h !== null &&\n 'command' in h &&\n typeof (h as { command: string }).command === 'string' &&\n (h as { command: string }).command.includes('rulemetric'),\n );\n if (!hasRulemetric) {\n merged[event] = [...current, ...hooks];\n }\n }\n\n const result = {\n version: existing.version ?? generated.version,\n hooks: merged,\n };\n\n writeFileSync(configPath, JSON.stringify(result, null, 2) + '\\n');\n return configPath;\n}\n\n/**\n * Write Cursor hooks config to `<projectPath>/.cursor/hooks.json`.\n * Merges with existing config \u2014 appends rulemetric hooks without duplicating.\n * Returns the config file path, or null if `.cursor/` doesn't exist.\n */\nexport function writeCursorHooks(projectPath: string): string | null {\n return writeCursorHooksToDir(join(projectPath, '.cursor'));\n}\n\n/**\n * Write Cursor hooks config to user-level `~/.cursor/hooks.json` so capture\n * works for every Cursor conversation without per-project setup.\n *\n * Returns the config file path, or null if `~/.cursor/` doesn't exist\n * (i.e., Cursor has never been launched on this machine).\n */\nexport function writeCursorUserHooks(): string | null {\n return writeCursorHooksToDir(join(homedir(), '.cursor'));\n}\n\n/**\n * Write Copilot Agent hooks config to `.github/hooks/rulemetric.json`.\n * Creates the hooks directory if needed.\n * Returns the config file path, or null if `.github/` doesn't exist.\n */\nexport function writeCopilotHooks(projectPath: string): string | null {\n const githubDir = join(projectPath, '.github');\n if (!existsSync(githubDir)) {\n return null;\n }\n\n const hooksDir = join(githubDir, 'hooks');\n mkdirSync(hooksDir, { recursive: true });\n\n const configPath = join(hooksDir, 'rulemetric.json');\n const config = generateCopilotHooksConfig();\n\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n return configPath;\n}\n\n/**\n * Strip rulemetric hooks from a Cursor `hooks.json` at the given path.\n * Preserves other hooks and removes empty event arrays.\n * Returns true if changes were made.\n */\nfunction removeCursorHooksFromFile(configPath: string): boolean {\n if (!existsSync(configPath)) {\n return false;\n }\n\n let config: { version?: number; hooks?: Record<string, unknown[]> };\n try {\n config = JSON.parse(readFileSync(configPath, 'utf-8'));\n } catch {\n return false;\n }\n\n if (!config.hooks) {\n return false;\n }\n\n let changed = false;\n for (const [event, hooks] of Object.entries(config.hooks)) {\n const filtered = hooks.filter((h: unknown) => {\n if (typeof h !== 'object' || h === null || !('command' in h)) return true;\n const cmd = (h as { command: string }).command;\n if (typeof cmd === 'string' && cmd.includes('rulemetric')) {\n changed = true;\n return false;\n }\n return true;\n });\n\n if (filtered.length === 0) {\n delete config.hooks[event];\n } else {\n config.hooks[event] = filtered;\n }\n }\n\n if (changed) {\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n }\n\n return changed;\n}\n\n/**\n * Remove rulemetric hooks from `<projectPath>/.cursor/hooks.json`.\n */\nexport function removeCursorHooks(projectPath: string): boolean {\n return removeCursorHooksFromFile(join(projectPath, '.cursor', 'hooks.json'));\n}\n\n/**\n * Remove rulemetric hooks from user-level `~/.cursor/hooks.json`.\n */\nexport function removeCursorUserHooks(): boolean {\n return removeCursorHooksFromFile(join(homedir(), '.cursor', 'hooks.json'));\n}\n\n/**\n * Write Cursor proxy config to `.cursor/settings.json`.\n * Sets `http.proxy` and `http.proxyStrictSSL` while preserving existing settings.\n * Returns true if `.cursor/` exists and config was written, false otherwise.\n */\nexport function writeCursorProxyConfig(projectPath: string, gatewayPort: number, certPath: string): boolean {\n const cursorDir = join(projectPath, '.cursor');\n if (!existsSync(cursorDir)) return false;\n\n const settingsPath = join(cursorDir, 'settings.json');\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = existsSync(certPath);\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Write VS Code proxy config to `.vscode/settings.json` for Copilot capture.\n * Sets `http.proxy`, `http.proxyStrictSSL`, and Copilot-specific override\n * while preserving existing settings.\n * Returns true if config was written, false if `.vscode/` doesn't exist.\n */\nexport function writeVSCodeProxyConfig(projectPath: string, gatewayPort: number, certPath: string): boolean {\n const vscodeDir = join(projectPath, '.vscode');\n mkdirSync(vscodeDir, { recursive: true });\n\n const settingsPath = join(vscodeDir, 'settings.json');\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = existsSync(certPath);\n\n // Copilot-specific override for surgical proxy targeting\n const copilotAdvanced = (settings['github.copilot.advanced'] ?? {}) as Record<string, unknown>;\n copilotAdvanced['debug.overrideProxyUrl'] = `http://localhost:${gatewayPort}`;\n settings['github.copilot.advanced'] = copilotAdvanced;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Remove VS Code proxy config from `.vscode/settings.json`.\n * Removes `http.proxy`, `http.proxyStrictSSL`, and Copilot proxy override.\n * Returns true if changes were made.\n */\nexport function removeVSCodeProxyConfig(projectPath: string): boolean {\n const settingsPath = join(projectPath, '.vscode', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n\n let settings: Record<string, unknown>;\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { return false; }\n\n let changed = false;\n\n if (settings['http.proxy']) {\n delete settings['http.proxy'];\n delete settings['http.proxyStrictSSL'];\n changed = true;\n }\n\n const copilotAdvanced = settings['github.copilot.advanced'] as Record<string, unknown> | undefined;\n if (copilotAdvanced?.['debug.overrideProxyUrl']) {\n delete copilotAdvanced['debug.overrideProxyUrl'];\n if (Object.keys(copilotAdvanced).length === 0) {\n delete settings['github.copilot.advanced'];\n }\n changed = true;\n }\n\n if (changed) {\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n }\n\n return changed;\n}\n\n/**\n * Remove Copilot Agent hooks by deleting `.github/hooks/rulemetric.json`.\n * Returns true if the file was removed.\n */\nexport function removeCopilotHooks(projectPath: string): boolean {\n const configPath = join(projectPath, '.github', 'hooks', 'rulemetric.json');\n if (!existsSync(configPath)) {\n return false;\n }\n\n unlinkSync(configPath);\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// macOS LaunchAgent for GUI app env vars (HTTPS_PROXY, NODE_EXTRA_CA_CERTS)\n// ---------------------------------------------------------------------------\n\nconst LAUNCH_AGENT_LABEL = 'com.rulemetric.env';\n\nfunction getLaunchAgentPath(): string {\n return join(homedir(), 'Library', 'LaunchAgents', `${LAUNCH_AGENT_LABEL}.plist`);\n}\n\n/**\n * Set HTTPS_PROXY and NODE_EXTRA_CA_CERTS for all GUI apps on macOS.\n *\n * Two layers:\n * 1. `launchctl setenv` \u2014 immediate effect for newly launched apps\n * 2. LaunchAgent plist \u2014 persists across reboots\n *\n * This ensures VS Code / Copilot / Cursor route LLM traffic through the\n * gateway even when launched from Dock or Spotlight (not a terminal).\n *\n * References:\n * - anthropics/claude-code#30318 (same pattern for Claude Desktop)\n * - Snyk IDE docs recommend launchctl setenv for macOS\n * - Apple QA1067 (archived) \u2014 original env var mechanism removed in 10.10\n */\nexport function installMacOSProxyEnv(gatewayPort: number, certPath: string): boolean {\n if (process.platform !== 'darwin') return false;\n\n const proxyUrl = `http://localhost:${gatewayPort}`;\n\n // Layer 1: immediate effect via launchctl setenv\n try {\n execSync(`launchctl setenv HTTPS_PROXY ${proxyUrl}`, { stdio: 'pipe' });\n if (existsSync(certPath)) {\n execSync(`launchctl setenv NODE_EXTRA_CA_CERTS ${certPath}`, { stdio: 'pipe' });\n }\n } catch {\n return false;\n }\n\n // Layer 2: LaunchAgent plist for persistence across reboots\n const plistPath = getLaunchAgentPath();\n const agentsDir = join(homedir(), 'Library', 'LaunchAgents');\n mkdirSync(agentsDir, { recursive: true });\n\n const certLine = existsSync(certPath)\n ? `launchctl setenv NODE_EXTRA_CA_CERTS ${certPath};`\n : '';\n\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${LAUNCH_AGENT_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>/bin/sh</string>\n <string>-c</string>\n <string>launchctl setenv HTTPS_PROXY ${proxyUrl};${certLine}</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n</dict>\n</plist>\n`;\n\n writeFileSync(plistPath, plist);\n return true;\n}\n\n/**\n * Remove the RuleMetric LaunchAgent and unset env vars.\n */\nexport function uninstallMacOSProxyEnv(): boolean {\n if (process.platform !== 'darwin') return false;\n\n let changed = false;\n\n // Unset env vars for current session\n try {\n execSync('launchctl unsetenv HTTPS_PROXY', { stdio: 'pipe' });\n execSync('launchctl unsetenv NODE_EXTRA_CA_CERTS', { stdio: 'pipe' });\n } catch { /* may not be set */ }\n\n // Remove LaunchAgent plist\n const plistPath = getLaunchAgentPath();\n if (existsSync(plistPath)) {\n unlinkSync(plistPath);\n changed = true;\n }\n\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// VS Code user-level proxy settings\n// ---------------------------------------------------------------------------\n\nfunction getVSCodeUserSettingsPath(): string {\n if (process.platform === 'darwin') {\n return join(homedir(), 'Library', 'Application Support', 'Code', 'User', 'settings.json');\n }\n if (process.platform === 'win32') {\n return join(process.env.APPDATA ?? '', 'Code', 'User', 'settings.json');\n }\n return join(homedir(), '.config', 'Code', 'User', 'settings.json');\n}\n\n/**\n * Set http.proxy in VS Code user settings (not workspace).\n *\n * Workspace-level http.proxy is silently ignored because VS Code restricts\n * it to APPLICATION scope (microsoft/vscode#236932). User-level settings\n * are the only reliable way to configure http.proxy.\n */\nexport function writeVSCodeUserProxyConfig(gatewayPort: number): boolean {\n const settingsPath = getVSCodeUserSettingsPath();\n if (!existsSync(join(settingsPath, '..'))) return false;\n\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n // Only set if not already configured by the user\n if (settings['http.proxy'] && !(settings['http.proxy'] as string).includes('localhost')) {\n return false; // user has a custom proxy, don't overwrite\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = false;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Remove RuleMetric proxy config from VS Code user settings.\n */\nexport function removeVSCodeUserProxyConfig(): boolean {\n const settingsPath = getVSCodeUserSettingsPath();\n if (!existsSync(settingsPath)) return false;\n\n let settings: Record<string, unknown>;\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { return false; }\n\n const proxy = settings['http.proxy'] as string | undefined;\n if (!proxy?.includes('localhost')) return false;\n\n delete settings['http.proxy'];\n delete settings['http.proxyStrictSSL'];\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Cursor user-level proxy settings\n// ---------------------------------------------------------------------------\n//\n// Cursor is a VS Code fork and uses the same settings.json shape, but reads\n// from `Cursor/User/settings.json` instead of `Code/User/settings.json`. The\n// `.cursor/settings.json` (workspace) path can't reach Cursor's Electron main\n// process for the same reason VS Code's workspace path can't reach VS Code's\n// \u2014 the http.proxy setting is restricted to APPLICATION scope and only the\n// user-level file is honored.\n\nfunction getCursorUserSettingsPath(): string {\n if (process.platform === 'darwin') {\n return join(homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'settings.json');\n }\n if (process.platform === 'win32') {\n return join(process.env.APPDATA ?? '', 'Cursor', 'User', 'settings.json');\n }\n return join(homedir(), '.config', 'Cursor', 'User', 'settings.json');\n}\n\n/**\n * Set http.proxy in Cursor user settings (not workspace). Mirrors\n * `writeVSCodeUserProxyConfig` \u2014 workspace-level http.proxy is silently\n * ignored due to APPLICATION-scope restriction (Cursor inherits this from\n * VS Code, see microsoft/vscode#236932).\n *\n * Returns true if config was written, false if Cursor isn't installed\n * (User dir absent) or the user already has a non-localhost proxy set.\n */\nexport function writeCursorUserProxyConfig(gatewayPort: number): boolean {\n const settingsPath = getCursorUserSettingsPath();\n if (!existsSync(join(settingsPath, '..'))) return false;\n\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* empty */ }\n }\n\n if (settings['http.proxy'] && !(settings['http.proxy'] as string).includes('localhost')) {\n return false;\n }\n\n settings['http.proxy'] = `http://localhost:${gatewayPort}`;\n settings['http.proxyStrictSSL'] = false;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n/**\n * Remove RuleMetric proxy config from Cursor user settings.\n */\nexport function removeCursorUserProxyConfig(): boolean {\n const settingsPath = getCursorUserSettingsPath();\n if (!existsSync(settingsPath)) return false;\n\n let settings: Record<string, unknown>;\n try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { return false; }\n\n const proxy = settings['http.proxy'] as string | undefined;\n if (!proxy?.includes('localhost')) return false;\n\n delete settings['http.proxy'];\n delete settings['http.proxyStrictSSL'];\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n return true;\n}\n\n// \u2500\u2500\u2500 Antigravity (Google's Gemini-based agentic IDE) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Antigravity hooks live in `.agents/hooks.json` (workspace) and\n// `~/.gemini/config/hooks.json` (user-level). Events follow the\n// SessionStart / UserPromptSubmit / PreToolUse / PostToolUse / Stop /\n// SessionEnd vocabulary. Each hook command sets `RULEMETRIC_HOOK_TOOL=\n// antigravity` so `_normalize.sh` routes through the Antigravity branch.\n\ninterface AntigravityHookEntry {\n type: 'command';\n command: string;\n timeout?: number;\n}\n\ninterface AntigravityHookBlock {\n matcher?: string;\n hooks: AntigravityHookEntry[];\n}\n\ninterface AntigravityRuleMetricBlock {\n enabled: boolean;\n SessionStart: AntigravityHookBlock[];\n UserPromptSubmit: AntigravityHookBlock[];\n PreToolUse: AntigravityHookBlock[];\n PostToolUse: AntigravityHookBlock[];\n Stop: AntigravityHookBlock[];\n SessionEnd: AntigravityHookBlock[];\n}\n\nexport type AntigravityHooksConfig = { rulemetric: AntigravityRuleMetricBlock };\n\n/** Resolve the binary string to invoke for a hook command. */\nfunction getRulemetricCommand(): string {\n // Prefer the local Node binary + bin/run.js so the hook works even when\n // `rulemetric` isn't on PATH for the GUI Antigravity process.\n const nodeBin = process.execPath;\n // bin/run.js lives next to dist/ in the installed CLI; resolve from\n // import.meta isn't available in a portable way here, so we use the\n // process argv1 if it's pointing at the CLI entry, else fall back to\n // the bare command name. In practice this function is called inside\n // `rulemetric hooks install`, where process.argv[1] is bin/run.js.\n const cliEntry = process.argv[1];\n if (cliEntry && cliEntry.endsWith('run.js')) {\n return `${nodeBin} ${cliEntry}`;\n }\n return 'rulemetric';\n}\n\nexport function generateAntigravityHooksConfig(): AntigravityHooksConfig {\n const cmd = getRulemetricCommand();\n const mk = (name: string): AntigravityHookEntry => ({\n type: 'command',\n command: `RULEMETRIC_HOOK_TOOL=antigravity ${cmd} hooks run ${name}`,\n timeout: 30,\n });\n return {\n rulemetric: {\n enabled: true,\n SessionStart: [{ hooks: [mk('session-start')] }],\n UserPromptSubmit: [{ hooks: [mk('user-prompt')] }],\n PreToolUse: [{ matcher: '.*', hooks: [mk('post-tool-use')] }],\n PostToolUse: [{ matcher: '.*', hooks: [mk('post-tool-use')] }],\n Stop: [{ hooks: [mk('assistant-response')] }],\n SessionEnd: [{ hooks: [mk('session-end')] }],\n },\n };\n}\n\nfunction writeAntigravityHooksToDir(dir: string): string | null {\n const configPath = join(dir, 'hooks.json');\n let existing: Record<string, unknown> = {};\n if (existsSync(configPath)) {\n try { existing = JSON.parse(readFileSync(configPath, 'utf-8')); } catch { existing = {}; }\n }\n const generated = generateAntigravityHooksConfig();\n // Antigravity keys hook blocks by name; RuleMetric owns its own block\n // and never touches sibling blocks the user may have added.\n const merged = { ...existing, rulemetric: generated.rulemetric };\n mkdirSync(dir, { recursive: true });\n writeFileSync(configPath, JSON.stringify(merged, null, 2) + '\\n');\n return configPath;\n}\n\nexport function writeAntigravityHooks(projectPath: string): string | null {\n return writeAntigravityHooksToDir(join(projectPath, '.agents'));\n}\n\nexport function writeAntigravityUserHooks(): string | null {\n const configDir = join(homedir(), '.gemini', 'config');\n return writeAntigravityHooksToDir(configDir);\n}\n\nfunction removeAntigravityHooksFromFile(configPath: string): boolean {\n if (!existsSync(configPath)) return false;\n let existing: Record<string, unknown>;\n try { existing = JSON.parse(readFileSync(configPath, 'utf-8')); } catch { return false; }\n if (!('rulemetric' in existing)) return false;\n delete existing.rulemetric;\n if (Object.keys(existing).length === 0) {\n // No other blocks \u2014 remove the file entirely.\n try { unlinkSync(configPath); } catch { /* ignore */ }\n } else {\n writeFileSync(configPath, JSON.stringify(existing, null, 2) + '\\n');\n }\n return true;\n}\n\nexport function removeAntigravityHooks(projectPath: string): boolean {\n return removeAntigravityHooksFromFile(join(projectPath, '.agents', 'hooks.json'));\n}\n\nexport function removeAntigravityUserHooks(): boolean {\n const modern = removeAntigravityHooksFromFile(join(homedir(), '.gemini', 'config', 'hooks.json'));\n const legacy = removeAntigravityHooksFromFile(join(homedir(), '.gemini', 'hooks.json'));\n return modern || legacy;\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib/statusline-shim.ts"],
4
- "sourcesContent": ["import { existsSync, readFileSync, writeFileSync, chmodSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n// Path where the shim script lives \u2014 readable by the `statusLine` config key.\nexport const STATUSLINE_SHIM_PATH = join(homedir(), '.claude', 'statusline-rulemetric.sh');\n\n/**\n * Write ~/.claude/statusline-rulemetric.sh.\n *\n * The script is invoked by Claude Code on every statusline refresh cycle.\n * Claude Code feeds a JSON payload on stdin that includes `rate_limits`\n * (same shape as GET /api/oauth/usage) using its own in-memory token \u2014\n * so freshness is tied to the statusline cadence, not the proxy capturing\n * a stale keychain token.\n *\n * The script:\n * 1. Reads stdin once (Claude Code closes it after the payload).\n * 2. If jq is available, extracts each rate_limit window and POSTs it to\n * POST /api/internal/research/observe as an oauth_usage observation.\n * 3. If a `prevCommand` is supplied (the user's original statusLine value),\n * it is executed with the same stdin forwarded so the user's prompt text\n * is still displayed \u2014 we must chain, not replace.\n * 4. Exits 0 regardless so Claude Code always gets a clean statusline.\n *\n * Auth is read from the same priority chain as the hook scripts:\n * ~/.config/rulemetric/env (RULEMETRIC_ACCESS_TOKEN / RULEMETRIC_API_KEY)\n * RULEMETRIC_API_KEY / RULEMETRIC_ACCESS_TOKEN env vars\n *\n * Returns the path written.\n */\nexport function writeStatuslineShim(prevCommand?: string): string {\n const chainBlock = prevCommand\n ? `\n# Chain to the original statusLine command so the user's prompt is preserved.\necho \"$STDIN_PAYLOAD\" | ${prevCommand}`\n : '';\n\n const script = `#!/usr/bin/env bash\n# RuleMetric statusline shim \u2014 auto-generated by \\`rulemetric hooks install --statusline-usage\\`\n# Captures Claude Code rate_limits from statusline stdin and forwards to the research pipeline.\n# DO NOT EDIT \u2014 re-run \\`rulemetric hooks install --statusline-usage\\` to regenerate.\n\nset -euo pipefail\n\n# Read stdin once (Claude Code closes the pipe after the payload).\nSTDIN_PAYLOAD=\"$(cat)\"\n\n# \u2500\u2500 Auth \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCONFIG_ENV=\"$HOME/.config/rulemetric/env\"\nif [ -f \"$CONFIG_ENV\" ]; then\n # shellcheck source=/dev/null\n source \"$CONFIG_ENV\"\nfi\n\nAPI_URL=\"\\${RULEMETRIC_API_URL:-https://rulemetric.com}\"\nAUTH_TOKEN=\"\\${RULEMETRIC_ACCESS_TOKEN:-\\${RULEMETRIC_API_KEY:-}}\"\n\nif [ -z \"$AUTH_TOKEN\" ]; then\n # No auth \u2014 still chain so the user's statusline works.\n${chainBlock}\n exit 0\nfi\n\n# \u2500\u2500 Extract + POST rate_limit windows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nif ! command -v jq &>/dev/null; then\n # jq missing \u2014 chain and bail; don't break the user's statusline.\n${chainBlock}\n exit 0\nfi\n\nOBSERVED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n\nfor WIRE_KEY in five_hour seven_day seven_day_opus; do\n # Map wire key \u2192 limit_type (case statement avoids bash 4 associative arrays).\n case \"$WIRE_KEY\" in\n five_hour) LIMIT_TYPE=\"five_hour_percent\" ;;\n seven_day) LIMIT_TYPE=\"seven_day_percent\" ;;\n seven_day_opus) LIMIT_TYPE=\"seven_day_opus_percent\" ;;\n *) continue ;;\n esac\n\n # Try both wire shapes:\n # Legacy: { \"rate_limits\": { \"five_hour\": { \"used_percentage\": ..., \"resets_at\": ... } } }\n # Current: { \"five_hour\": { \"used_percentage\": ..., \"resets_at\": ... } }\n WINDOW=\"$(echo \"$STDIN_PAYLOAD\" | jq -c \".rate_limits.\\${WIRE_KEY} // .\\${WIRE_KEY} // empty\" 2>/dev/null || true)\"\n\n if [ -z \"$WINDOW\" ] || [ \"$WINDOW\" = \"null\" ]; then\n continue\n fi\n\n USED_PCT=\"$(echo \"$WINDOW\" | jq '.used_percentage // empty' 2>/dev/null || true)\"\n RESETS_AT_EPOCH=\"$(echo \"$WINDOW\" | jq '.resets_at // empty' 2>/dev/null || true)\"\n\n if [ -z \"$USED_PCT\" ] || [ \"$USED_PCT\" = \"null\" ]; then\n continue\n fi\n\n # Convert unix epoch to ISO-8601 if present.\n if [ -n \"$RESETS_AT_EPOCH\" ] && [ \"$RESETS_AT_EPOCH\" != \"null\" ]; then\n RESETS_AT_ISO=\"$(date -u -r \"$RESETS_AT_EPOCH\" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \\\n || date -u -d \"@$RESETS_AT_EPOCH\" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \\\n || echo \"\")\"\n else\n RESETS_AT_ISO=\"\"\n fi\n\n PAYLOAD=\"$(jq -nc \\\\\n --arg provider \"anthropic\" \\\\\n --arg plan \"unknown\" \\\\\n --argjson value \"$USED_PCT\" \\\\\n --arg limitType \"$LIMIT_TYPE\" \\\\\n --arg observedAt \"$OBSERVED_AT\" \\\\\n --arg resetsAt \"$RESETS_AT_ISO\" \\\\\n '{\n provider: $provider,\n plan: $plan,\n limitType: $limitType,\n value: $value,\n unit: \"percentage\",\n comparator: \"=\",\n sourceType: \"oauth_usage\",\n sourceUrl: null,\n confidence: 1.0,\n observedAt: $observedAt,\n resetsAt: (if $resetsAt == \"\" then null else $resetsAt end)\n }')\"\n\n curl -sf -o /dev/null \\\\\n -X POST \"$API_URL/api/internal/research/observe\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $AUTH_TOKEN\" \\\\\n --max-time 3 \\\\\n --data \"$PAYLOAD\" &\ndone\n\n# Wait for background curls (non-blocking from Claude Code's perspective \u2014\n# the shim itself exits promptly; curl jobs are fire-and-forget).\nwait\n${chainBlock}\nexit 0\n`;\n\n writeFileSync(STATUSLINE_SHIM_PATH, script, { encoding: 'utf-8' });\n chmodSync(STATUSLINE_SHIM_PATH, 0o755);\n return STATUSLINE_SHIM_PATH;\n}\n\n// The settings object shape for statusLine (Claude Code v1.x+).\nexport const STATUSLINE_SETTINGS_VALUE = {\n type: 'command' as const,\n command: STATUSLINE_SHIM_PATH,\n refreshInterval: 30,\n};\n\n/**\n * Read the current statusLine command from a settings object, if any.\n * Returns undefined if not set or already points to the shim (idempotent).\n * Handles both the legacy string form and the current object form.\n */\nexport function readCurrentStatusLine(\n settings: Record<string, unknown>,\n): string | undefined {\n const current = settings.statusLine;\n if (!current) return undefined;\n if (typeof current === 'string') {\n if (current === STATUSLINE_SHIM_PATH) return undefined;\n return current;\n }\n if (typeof current === 'object' && current !== null) {\n const cmd = (current as Record<string, unknown>).command;\n if (typeof cmd !== 'string') return undefined;\n if (cmd === STATUSLINE_SHIM_PATH) return undefined; // already installed\n return cmd;\n }\n return undefined;\n}\n\n/**\n * Returns true if the settings object already points to the shim.\n */\nexport function isStatuslineShimInstalled(settings: Record<string, unknown>): boolean {\n const current = settings.statusLine;\n if (!current) return false;\n if (typeof current === 'string') return current === STATUSLINE_SHIM_PATH;\n if (typeof current === 'object' && current !== null) {\n return (current as Record<string, unknown>).command === STATUSLINE_SHIM_PATH;\n }\n return false;\n}\n"],
5
- "mappings": ";AAAA,SAAmC,eAAe,iBAAiB;AACnE,SAAS,eAAe;AACxB,SAAS,YAAY;AAGd,IAAM,uBAAuB,KAAK,QAAQ,GAAG,WAAW,0BAA0B;AA0BlF,SAAS,oBAAoB,aAA8B;AAChE,QAAM,aAAa,cACf;AAAA;AAAA,0BAEoB,WAAW,KAC/B;AAEJ,QAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBf,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOV,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwEV,UAAU;AAAA;AAAA;AAIV,gBAAc,sBAAsB,QAAQ,EAAE,UAAU,QAAQ,CAAC;AACjE,YAAU,sBAAsB,GAAK;AACrC,SAAO;AACT;AAGO,IAAM,4BAA4B;AAAA,EACvC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,iBAAiB;AACnB;AAOO,SAAS,sBACd,UACoB;AACpB,QAAM,UAAU,SAAS;AACzB,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,OAAO,YAAY,UAAU;AAC/B,QAAI,YAAY,qBAAsB,QAAO;AAC7C,WAAO;AAAA,EACT;AACA,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,MAAO,QAAoC;AACjD,QAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAI,QAAQ,qBAAsB,QAAO;AACzC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAKO,SAAS,0BAA0B,UAA4C;AACpF,QAAM,UAAU,SAAS;AACzB,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,OAAO,YAAY,SAAU,QAAO,YAAY;AACpD,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,WAAQ,QAAoC,YAAY;AAAA,EAC1D;AACA,SAAO;AACT;",
6
- "names": []
7
- }