cc-prompter 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin.ts","../src/sidecar.ts","../src/pty-session.ts","../src/assets.ts"],"sourcesContent":["/**\n * CC Prompter — Vite Plugin\n *\n * 为 Vite dev server 提供:\n * 1. 内置 code-inspector-plugin(Shift+Alt 悬停定位源码)\n * 2. 启动 Sidecar Express server 管理 PTY sessions(自动选择空闲端口)\n * 3. 注入轻量脚本监听 code-inspector 事件,弹出 Claude 面板\n * 4. 提供 /__panel/ HTML 给 iframe\n *\n * 仅在 dev 模式生效,build 时不注入脚本。\n *\n * 用法:plugins: [ccPromptPlugin()]\n * 等同于同时配置 codeInspectorPlugin + cc-prompt-plugin。\n */\n\nimport type { Plugin } from 'vite';\nimport type { Server } from 'http';\nimport { codeInspectorPlugin } from 'code-inspector-plugin';\nimport { startSidecar } from './sidecar.js';\nimport { getPanelHtml, getInjectScript } from './assets.js';\n\nexport interface CcPromptOptions {\n /** Sidecar 启动端口,默认 3456(被占用时自动 +1) */\n port?: number;\n /** 项目根目录,默认 vite config.root */\n root?: string;\n /** 是否启用 code-inspector,默认 true */\n inspector?: boolean;\n}\n\nexport function ccPromptPlugin(options?: CcPromptOptions): Plugin[] {\n const enableInspector = options?.inspector !== false;\n let projectRoot = process.cwd();\n let sidecarServer: Server | null = null;\n let actualPort = 0;\n\n const startPort = options?.port || 3456;\n\n let cleanedUp = false;\n function cleanup() {\n if (cleanedUp) return;\n cleanedUp = true;\n if (sidecarServer) {\n sidecarServer.close();\n sidecarServer = null;\n }\n }\n\n // ── 1. code-inspector plugin ──\n const inspectorPlugin = enableInspector\n ? codeInspectorPlugin({\n bundler: 'vite',\n behavior: {\n locate: false,\n copy: false,\n },\n hideDomPathAttr: true,\n hideConsole: true,\n })\n : null;\n\n // ── 2. cc-prompter plugin ──\n const promptPlugin: Plugin = {\n name: 'cc-prompt-plugin',\n\n configResolved(config) {\n projectRoot = options?.root || config.root;\n },\n\n configureServer(server) {\n // Start sidecar — auto-picks available port\n sidecarServer = startSidecar(projectRoot, { startPort });\n\n // Wait for sidecar to actually start, then grab port\n const checkPort = () => {\n const addr = sidecarServer?.address();\n if (addr && typeof addr === 'object') {\n actualPort = addr.port;\n console.log(`[cc-prompter] Sidecar port: ${actualPort}`);\n }\n };\n setTimeout(checkPort, 200);\n setTimeout(checkPort, 1000);\n\n // Expose actual sidecar port to inject script\n server.middlewares.use('/__cc-port', (req, res) => {\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.end(String(actualPort || startPort));\n });\n\n // Clean up on dev server close\n server.httpServer?.on('close', cleanup);\n process.on('SIGTERM', () => { cleanup(); process.exit(0); });\n process.on('SIGINT', () => { cleanup(); process.exit(0); });\n process.on('exit', cleanup);\n },\n\n // Only inject in dev mode (ctx.server is undefined during build)\n transformIndexHtml: {\n order: 'post',\n handler(html, ctx) {\n if (!ctx.server) return html;\n const script = getInjectScript();\n return html.replace('</body>', `<script>${script}</script></body>`);\n },\n },\n\n closeBundle() {\n cleanup();\n },\n };\n\n // Return composed plugin array\n const plugins: Plugin[] = [promptPlugin];\n if (inspectorPlugin) plugins.unshift(inspectorPlugin);\n return plugins;\n}\n","/**\n * CC Prompter — Sidecar API Server\n *\n * Express server 运行在端口 3456,管理 PTY session 生命周期。\n * 提供 REST API + SSE 流式响应。\n */\n\nimport express from 'express';\nimport { createServer, type Server } from 'http';\nimport { PtySession } from './pty-session.js';\nimport { getPanelHtml } from './assets.js';\nimport type {\n SessionInfo,\n CreateSessionRequest,\n SendMessageRequest,\n SendCommandRequest,\n SseEvent,\n} from './types.js';\n\n// ── Session Manager ─────────────────────────────────────\n\nclass SessionManager {\n private sessions = new Map<string, PtySession>();\n private counter = 0;\n\n async create(cwd: string): Promise<PtySession> {\n const id = `s${++this.counter}-${Date.now().toString(36)}`;\n const session = new PtySession(id, cwd);\n this.sessions.set(id, session);\n\n // Clean up on exit\n session.on('exit', () => {\n // Keep in map for history, but mark exited\n });\n\n await session.spawn();\n return session;\n }\n\n get(id: string): PtySession | undefined {\n return this.sessions.get(id);\n }\n\n list(): SessionInfo[] {\n return Array.from(this.sessions.values()).map(s => {\n const info = s.getInfo();\n return {\n id: info.id,\n title: info.title,\n status: info.status,\n createdAt: info.createdAt,\n lastActivityAt: info.lastActivityAt,\n messageCount: info.messageCount,\n lastMessagePreview: info.lastMessagePreview,\n };\n });\n }\n\n destroy(id: string): boolean {\n const session = this.sessions.get(id);\n if (!session) return false;\n session.kill();\n this.sessions.delete(id);\n return true;\n }\n\n destroyAll(): void {\n for (const session of this.sessions.values()) {\n session.kill();\n }\n this.sessions.clear();\n }\n}\n\n// ── Sidecar Server ──────────────────────────────────────\n\nexport interface SidecarOptions {\n startPort?: number;\n}\n\nexport function startSidecar(projectRoot: string, options?: SidecarOptions): Server {\n const startPort = options?.startPort || 3456;\n const app = express();\n app.use(express.json());\n\n const manager = new SessionManager();\n\n // ── CORS for iframe ──\n app.use((req, res, next) => {\n res.header('Access-Control-Allow-Origin', '*');\n res.header('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');\n res.header('Access-Control-Allow-Headers', 'Content-Type');\n if (req.method === 'OPTIONS') {\n res.sendStatus(204);\n return;\n }\n next();\n });\n\n // ── Panel HTML (served to iframe) ──\n app.get('/__panel/', (_req, res) => {\n const html = getPanelHtml();\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.setHeader('Content-Length', Buffer.byteLength(html));\n res.end(html);\n });\n\n // ── Panel favicon (no-op) ──\n app.get('/favicon.ico', (_req, res) => {\n res.sendStatus(204);\n });\n\n // ── List sessions ──\n app.get('/api/sessions', (_req, res) => {\n res.json(manager.list());\n });\n\n // ── Create session ──\n app.post<Record<string, string>, any, CreateSessionRequest>('/api/sessions', async (req, res) => {\n try {\n const cwd = req.body?.cwd || projectRoot;\n const session = await manager.create(cwd);\n res.json(session.getInfo());\n } catch (err: any) {\n res.status(500).json({ error: err.message });\n }\n });\n\n // ── Send message (SSE stream) ──\n app.post<Record<string, string>, any, SendMessageRequest>(\n '/api/sessions/:id/message',\n async (req, res) => {\n const session = manager.get(req.params.id);\n if (!session) {\n res.status(404).json({ error: 'Session not found' });\n return;\n }\n if (session.status === 'exited') {\n res.status(410).json({ error: 'Session exited' });\n return;\n }\n if (session.status === 'busy') {\n res.status(409).json({ error: 'Session busy' });\n return;\n }\n\n const { content, sourceInfo } = req.body;\n if (!content) {\n res.status(400).json({ error: 'Missing content' });\n return;\n }\n\n // Build prompt with optional source context (single-line to avoid multi-line input mode)\n let prompt = content;\n if (sourceInfo) {\n const relPath = sourceInfo.path;\n const parts = [\n `[source: ${relPath}:${sourceInfo.line}:${sourceInfo.column}]`,\n ];\n if (sourceInfo.elementInfo) {\n parts.push(`[element: ${sourceInfo.elementInfo}]`);\n }\n prompt = parts.join(' ') + ' ' + content;\n }\n\n // SSE headers\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders();\n\n // Forward session events as SSE\n const onMessage = (evt: SseEvent) => {\n res.write(`data: ${JSON.stringify(evt)}\\n\\n`);\n\n // Stop streaming on 'done'\n if (evt.type === 'done') {\n cleanup();\n }\n };\n\n const onError = (err: Error) => {\n res.write(`data: ${JSON.stringify({ type: 'error', content: err.message })}\\n\\n`);\n cleanup();\n };\n\n const cleanup = () => {\n if (cleanedUp) return;\n cleanedUp = true;\n session.removeListener('message', onMessage);\n session.removeListener('error', onError);\n res.end();\n };\n\n session.on('message', onMessage);\n session.on('error', onError);\n\n // Client disconnect — delay registration to avoid premature close\n // (Express/Node HTTP can fire 'close' early on SSE connections)\n let cleanedUp = false;\n setTimeout(() => {\n req.on('close', () => {\n if (!cleanedUp) {\n cleanup();\n }\n });\n }, 3000);\n\n // Send to PTY\n try {\n await session.sendMessage(prompt);\n } catch (err: any) {\n res.write(`data: ${JSON.stringify({ type: 'error', content: err.message })}\\n\\n`);\n cleanup();\n }\n },\n );\n\n // ── Send command ──\n app.post<Record<string, string>, any, SendCommandRequest>(\n '/api/sessions/:id/command',\n (req, res) => {\n const session = manager.get(req.params.id);\n if (!session) {\n res.status(404).json({ error: 'Session not found' });\n return;\n }\n\n const { command } = req.body;\n if (!command) {\n res.status(400).json({ error: 'Missing command' });\n return;\n }\n\n try {\n session.sendCommand(command);\n res.json({ ok: true });\n } catch (err: any) {\n res.status(500).json({ error: err.message });\n }\n },\n );\n\n // ── Interrupt session (Escape key) ──\n app.post('/api/sessions/:id/interrupt', (req, res) => {\n const session = manager.get(req.params.id);\n if (!session) {\n res.status(404).json({ error: 'Session not found' });\n return;\n }\n try {\n session.interrupt();\n res.json({ ok: true });\n } catch (err: any) {\n res.status(500).json({ error: err.message });\n }\n });\n\n // ── Delete session ──\n app.delete('/api/sessions/:id', (req, res) => {\n if (manager.destroy(req.params.id)) {\n res.json({ ok: true });\n } else {\n res.status(404).json({ error: 'Session not found' });\n }\n });\n\n // ── Session history ──\n app.get('/api/sessions/:id/history', (req, res) => {\n const session = manager.get(req.params.id);\n if (!session) {\n res.status(404).json({ error: 'Session not found' });\n return;\n }\n res.json(session.getHistory());\n });\n\n const server = createServer(app);\n const MAX_PORT = startPort + 10;\n\n function tryListen(port: number): Promise<Server> {\n return new Promise((resolve, reject) => {\n server.listen(port, () => {\n console.log(`[cc-prompter] Sidecar running on http://localhost:${port}`);\n resolve(server);\n });\n server.on('error', (err: any) => {\n if (err.code === 'EADDRINUSE' && port < MAX_PORT) {\n console.log(`[cc-prompter] Port ${port} in use, trying ${port + 1}...`);\n tryListen(port + 1).then(resolve, reject);\n } else {\n reject(err);\n }\n });\n });\n }\n\n // Fire-and-forget listen (returns server immediately for API compat)\n tryListen(startPort).catch((err) => {\n console.error(`[cc-prompter] Failed to start sidecar:`, err.message);\n });\n\n // Graceful shutdown\n server.on('close', () => {\n manager.destroyAll();\n });\n\n return server;\n}\n","/**\n * CC Prompter — PTY Session\n *\n * 每个实例管理一个常驻的 claude CLI 进程(通过 node-pty)。\n *\n * 输出解析策略(双通道):\n * 1. PTY 输出解析(主要)— 实时从 TUI 输出提取响应文本\n * 2. JSONL transcript(辅助)— 结构化事件,用于 tool_use 等\n *\n * 就绪检测:解析 PTY 输出中的提示符(\"for shortcuts\"、\"/effort\")。\n */\n\nimport { createRequire } from 'module';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\nconst _metaUrl = typeof __filename !== 'undefined' ? __filename : fileURLToPath(import.meta.url);\nconst require = createRequire(_metaUrl);\nimport { EventEmitter } from 'events';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport type { IPty } from 'node-pty-prebuilt-multiarch';\nimport type {\n SessionStatus,\n ChatMessage,\n ToolUseInfo,\n JsonlEvent,\n SseEvent,\n} from './types.js';\n\n// node-pty-prebuilt-multiarch uses CJS internally — must use require()\nfunction loadPty(): typeof import('node-pty-prebuilt-multiarch') {\n return require('node-pty-prebuilt-multiarch');\n}\n\n// ── Helpers ─────────────────────────────────────────────\n\nfunction resolveClaudeBin(cwd: string): string {\n const local = path.resolve(cwd, 'node_modules/@anthropic-ai/claude-code/bin/claude.exe');\n if (fs.existsSync(local)) return local;\n return 'claude';\n}\n\nfunction findClaudeProjectsDir(): string {\n return path.join(os.homedir(), '.claude', 'projects');\n}\n\n/** Convert a cwd path to the Claude projects directory name */\nfunction cwdToProjectDir(cwd: string): string {\n // /Users/ryan/mycode/AgentPlat/demo → -Users-ryan-mycode-AgentPlat-demo\n return '-' + cwd.replace(/^\\//, '').replace(/\\//g, '-');\n}\n\n/** Scan for the most recently modified .jsonl in a specific project dir */\nfunction findRecentJsonl(cwd: string, afterMs: number): string | null {\n const projectsDir = findClaudeProjectsDir();\n const projectSubdir = cwdToProjectDir(cwd);\n const targetDir = path.join(projectsDir, projectSubdir);\n\n if (!fs.existsSync(targetDir)) return null;\n\n const files = fs.readdirSync(targetDir).filter(f => f.endsWith('.jsonl'));\n let best: { path: string; mtime: number } | null = null;\n for (const f of files) {\n const fp = path.join(targetDir, f);\n try {\n const stat = fs.statSync(fp);\n if (stat.mtimeMs > afterMs) {\n if (!best || stat.mtimeMs > best.mtime) {\n best = { path: fp, mtime: stat.mtimeMs };\n }\n }\n } catch { continue; }\n }\n return best?.path || null;\n}\n\nfunction sessionIdFromJsonlPath(jsonPath: string): string | null {\n const base = path.basename(jsonPath, '.jsonl');\n const match = base.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/);\n return match ? match[0] : null;\n}\n\n/** Strip ANSI escape sequences for analysis */\nfunction stripAnsi(s: string): string {\n return s\n .replace(/\\x1b\\[[0-9;]*[a-zA-Z]/g, '')\n .replace(/\\x1b\\].*?\\x07/g, '')\n .replace(/\\x1b\\[[\\?]?[0-9;]*[a-zA-Z]/g, '')\n .replace(/\\r/g, '');\n}\n\n// ── PtySession ──────────────────────────────────────────\n\nexport class PtySession extends EventEmitter {\n readonly id: string;\n readonly cwd: string;\n status: SessionStatus = 'spawning';\n\n private pty: IPty | null = null;\n private jsonlPath: string | null = null;\n private sessionId: string | null = null;\n private history: ChatMessage[] = [];\n private jsonlOffset = 0;\n private jsonlWatcher: fs.FSWatcher | null = null;\n private spawnTime: number;\n private messageSentAt = 0;\n private title = 'New Session';\n private lastActivityAt: number;\n private killed = false;\n private ptyBuffer = ''; // accumulated for prompt detection\n private jsonlDiscoverPromise: Promise<void> | null = null;\n\n // ── PTY streaming fields ──\n private busyBuffer = ''; // accumulated during busy state\n private lastUserContent = ''; // last user message text\n private ptyResponseText = ''; // extracted response text so far\n private ptyResponseEmitted = 0; // chars already emitted\n private usedJsonl = false; // JSONL events received this turn\n private ptyDoneEmitted = false;\n private lastProgress = ''; // last emitted progress text\n private interrupted = false; // set when user sends interrupt\n\n constructor(id: string, cwd: string) {\n super();\n this.id = id;\n this.cwd = cwd;\n this.spawnTime = Date.now();\n this.lastActivityAt = this.spawnTime;\n }\n\n /** Spawn the claude process via PTY */\n async spawn(): Promise<void> {\n const ptyModule = loadPty();\n const bin = resolveClaudeBin(this.cwd);\n\n console.log(`[pty-session ${this.id}] spawning: ${bin} cwd: ${this.cwd}`);\n\n this.pty = ptyModule.spawn(bin, [], {\n name: 'xterm-256color',\n cols: 120,\n rows: 30,\n cwd: this.cwd,\n env: { ...process.env } as Record<string, string>,\n });\n\n console.log(`[pty-session ${this.id}] PID: ${this.pty.pid}`);\n\n // Watch PTY output\n this.pty.onData((data: string) => {\n const clean = stripAnsi(data);\n this.ptyBuffer += clean;\n\n this.detectPrompt();\n\n // Parse for streaming response when busy and JSONL not active\n if (this.status === 'busy' && !this.usedJsonl) {\n this.busyBuffer += clean;\n this.parseBusyOutput();\n }\n });\n\n this.pty.onExit(({ exitCode }) => {\n console.log(`[pty-session ${this.id}] exited with code: ${exitCode}`);\n this.status = 'exited';\n this.lastActivityAt = Date.now();\n this.emit('exit', exitCode);\n this.cleanup();\n });\n }\n\n // ── Prompt Detection ──────────────────────────────────\n\n private detectPrompt(): void {\n const indicators = [\n /for shortcuts/,\n /\\/effort/,\n /refactor/,\n ];\n\n for (const re of indicators) {\n if (re.test(this.ptyBuffer) && this.status === 'spawning') {\n console.log(`[pty-session ${this.id}] detected prompt → ready`);\n this.status = 'ready';\n this.emit('ready');\n return;\n }\n }\n\n // After interrupt, detect prompt returning to finish the stream\n if (this.interrupted && this.status === 'busy') {\n for (const re of indicators) {\n if (re.test(this.ptyBuffer)) {\n console.log(`[pty-session ${this.id}] detected prompt after interrupt → done`);\n this.interrupted = false;\n this.ptyDoneEmitted = true;\n this.status = 'ready';\n this.lastActivityAt = Date.now();\n this.emit('message', { type: 'done', durationMs: 0 } as SseEvent);\n return;\n }\n }\n }\n }\n\n private async waitUntilReady(timeoutMs = 30_000): Promise<void> {\n if (this.status === 'ready') return;\n if (this.status === 'exited') throw new Error('Session exited');\n\n return new Promise((resolve, reject) => {\n const deadline = Date.now() + timeoutMs;\n const timer = setInterval(() => {\n if (this.status === 'ready') {\n clearInterval(timer);\n resolve();\n } else if (this.status === 'exited') {\n clearInterval(timer);\n reject(new Error('Session exited while waiting'));\n } else if (Date.now() > deadline) {\n clearInterval(timer);\n reject(new Error('Timeout waiting for session to be ready'));\n }\n }, 100);\n });\n }\n\n // ── Send Message ──────────────────────────────────────\n\n async sendMessage(content: string): Promise<void> {\n if (!this.pty || this.status === 'exited') {\n throw new Error('Session not active');\n }\n if (this.status === 'busy') {\n throw new Error('Session busy');\n }\n\n // Wait for claude prompt to be ready\n if (this.status !== 'ready') {\n console.log(`[pty-session ${this.id}] waiting for prompt before sending message...`);\n await this.waitUntilReady();\n }\n\n await new Promise(r => setTimeout(r, 200));\n\n this.status = 'busy';\n this.lastActivityAt = Date.now();\n\n // Reset streaming state for this turn\n this.busyBuffer = '';\n this.lastUserContent = content;\n this.ptyResponseText = '';\n this.ptyResponseEmitted = 0;\n this.usedJsonl = false;\n this.ptyDoneEmitted = false;\n this.lastProgress = '';\n this.interrupted = false;\n\n // JSONL discovery in background\n if (!this.messageSentAt) {\n this.messageSentAt = Date.now();\n console.log(`[pty-session ${this.id}] first message, starting JSONL discovery`);\n this.jsonlDiscoverPromise = this.discoverJsonl();\n }\n\n console.log(`[pty-session ${this.id}] writing to PTY: ${JSON.stringify(content.slice(0, 100))}`);\n\n // Write content + double Enter for reliable submission\n this.pty.write(content + '\\r');\n await new Promise(r => setTimeout(r, 150));\n this.pty.write('\\r');\n }\n\n /** Send a slash command to the PTY */\n sendCommand(command: string): void {\n if (!this.pty || this.status === 'exited') {\n throw new Error('Session not active');\n }\n\n this.pty.write(command + '\\r');\n\n if (command === '/new') {\n this.history = [];\n this.title = 'New Session';\n this.jsonlPath = null;\n this.jsonlOffset = 0;\n this.jsonlWatcher?.close();\n this.jsonlWatcher = null;\n this.sessionId = null;\n this.messageSentAt = 0;\n this.status = 'ready';\n this.ptyBuffer = '';\n }\n }\n\n /** Send Escape to PTY to interrupt current generation */\n interrupt(): void {\n if (!this.pty || this.status === 'exited') {\n throw new Error('Session not active');\n }\n if (this.status !== 'busy') return;\n this.interrupted = true;\n this.pty.write('\\x1b');\n\n // Safety timeout: if prompt detection fails, force finish after 5s\n setTimeout(() => {\n if (this.interrupted && this.status === 'busy') {\n console.log(`[pty-session ${this.id}] interrupt timeout → force done`);\n this.interrupted = false;\n this.ptyDoneEmitted = true;\n this.status = 'ready';\n this.lastActivityAt = Date.now();\n this.emit('message', { type: 'done', durationMs: 0 } as SseEvent);\n }\n }, 5000);\n }\n\n // ── PTY Output Parsing (streaming fallback) ───────────\n\n /**\n * Parse PTY output during busy state to extract streaming response.\n *\n * Claude Code TUI patterns:\n * - Spinner frames: ✳ ✶ ✻ ✽ ✢ · (ignore — just animation)\n * - Response text: ⏺<text> or ●<text>\n * - Tool use: ⚡<tool_name> or ✢ editing <file>\n * - Completion: \"Brewed for Xs\" (ONLY reliable indicator)\n * - ⚠️ ❯ appears in input echo too — NOT a completion signal!\n * - Timing: (Xs · ↓NNN tokens)\n */\n private parseBusyOutput(): void {\n // ── 0. Extract progress for UI feedback ──\n this.emitProgress();\n\n // ── 1. Try to extract response text ──\n const respMatch = this.busyBuffer.match(/⏺([一-鿿 -〿＀-￯].+)/s);\n if (respMatch) {\n let raw = respMatch[1];\n raw = raw.replace(/[✳✶✻✽✢·].*$/s, '').trim();\n raw = raw.replace(/─{3,}.*$/s, '').trim();\n raw = raw.replace(/Brewed for.*$/s, '').trim();\n raw = raw.replace(/Sautéed for.*$/s, '').trim();\n raw = raw.replace(/esctointerrupt.*$/s, '').trim();\n\n if (raw.length > this.ptyResponseText.length) {\n this.ptyResponseText = raw;\n this.emitIncrementalText();\n }\n }\n\n // ── 2. Extract tool calls (Update/Read/Edit) ──\n const toolCallMatch = this.busyBuffer.match(/⏺(Update|Read|Edit|Write|Bash)\\(([^)]+)\\)/);\n if (toolCallMatch) {\n const toolName = toolCallMatch[1];\n const filePath = toolCallMatch[2];\n this.emit('message', {\n type: 'assistant_tool',\n tool: { name: toolName, input: { file: filePath } },\n } as SseEvent);\n }\n\n // ── 3. Detect completion ──\n if (!this.ptyDoneEmitted && /(?:Brewed|Sautéed) for/.test(this.busyBuffer)) {\n this.ptyDoneEmitted = true;\n\n // Final flush: emit any remaining text\n if (this.ptyResponseText.length > this.ptyResponseEmitted) {\n this.emitIncrementalText();\n }\n\n // If no response was emitted at all, try one more aggressive extraction\n if (this.ptyResponseEmitted === 0) {\n const finalMatch = this.busyBuffer.match(/⏺(.+)/s);\n if (finalMatch) {\n let text = finalMatch[1]\n .replace(/[✳✶✻✽✢·].*$/s, '')\n .replace(/─{3,}.*$/s, '')\n .replace(/Brewed for.*$/s, '')\n .replace(/esctointerrupt.*$/s, '')\n .trim();\n if (text.length > 0) {\n this.emitUserIfNeeded();\n this.history.push({\n role: 'assistant', content: text, timestamp: Date.now(),\n });\n this.emit('message', { type: 'assistant_text', content: text } as SseEvent);\n this.ptyResponseEmitted = text.length;\n }\n }\n }\n\n // Extract duration\n const durMatch = this.busyBuffer.match(/Brewed for (\\d+)s/);\n const durationMs = durMatch ? parseInt(durMatch[1]) * 1000 : 0;\n\n // Update title from first user message\n if (this.history.filter(m => m.role === 'user').length <= 1 && this.lastUserContent) {\n this.title = this.lastUserContent.slice(0, 60);\n this.emit('title-change', this.title);\n }\n\n this.status = 'ready';\n this.lastActivityAt = Date.now();\n this.emit('message', { type: 'done', durationMs } as SseEvent);\n }\n }\n\n /** Emit only the newly arrived characters (incremental streaming) */\n private emitIncrementalText(): void {\n const newText = this.ptyResponseText.slice(this.ptyResponseEmitted);\n if (newText.length === 0) return;\n\n // First chunk → emit user message + start assistant\n if (this.ptyResponseEmitted === 0) {\n this.emitUserIfNeeded();\n }\n\n this.ptyResponseEmitted = this.ptyResponseText.length;\n this.emit('message', { type: 'assistant_text', content: newText } as SseEvent);\n }\n\n /**\n * Extract and emit progress updates from busyBuffer.\n *\n * Parses PTY output for Claude Code's progress indicators:\n * - \"Thinking for Xs, reading N files\"\n * - \"Thought for Xs, read N files\"\n * - \"Crafting… (Xs · ↓NN tokens)\"\n * - \"Update(file)\" / \"Read(file)\"\n * - \"⎿ Removed N lines\"\n * - \"(Xs · ↓NN tokens)\" timing\n */\n private emitProgress(): void {\n // Strip spinner chars + noise, collapse whitespace\n const text = this.busyBuffer\n .replace(/[✳✶✻✽✢·][a-zA-Z0-9…]{0,4}/g, '')\n .replace(/\\s+/g, ' ');\n\n // Ordered by specificity — last match wins (most recent progress)\n const patterns: [RegExp, (m: RegExpMatchArray) => string][] = [\n // Thinking phase\n [/Thinking for (\\d+s)[^─]{0,60}(reading \\d+ file[^)]*)?/, (m) => {\n return m[0].replace(/\\s+/g, ' ').replace(/\\s*\\(ctrl.*$/, '').trim();\n }],\n // Thought completed\n [/Thought for (\\d+s)[^─]{0,60}(read \\d+ file[^)]*)?/, (m) => {\n return m[0].replace(/\\s+/g, ' ').replace(/\\s*\\(ctrl.*$/, '').trim();\n }],\n // Tool call: Update(file) / Read(file)\n [/⏺(Update|Read|Edit|Write|Bash)\\(([^)]+)\\)/, (m) => {\n return m[1] + ': ' + m[2].split('/').slice(-2).join('/');\n }],\n // Tool result: ⎿ Removed N lines\n [/⎿\\s*(Removed|Added|Modified|Created)\\s+(\\d+)\\s+(lines?)/, (m) => {\n return m[1] + ' ' + m[2] + ' ' + m[3];\n }],\n // Crafting with timing\n [/Crafting[^─]{0,30}\\(\\d+s[^)]*\\)/, (m) => {\n return m[0].replace(/\\s+/g, ' ').trim();\n }],\n // Simple timing: (5s · ↓9 tokens)\n [/\\((\\d+s)\\s*·\\s*[↓↑]\\s*(\\d+)\\s*tokens?\\)/, (m) => {\n return m[1] + ' · ' + m[2] + ' tokens';\n }],\n ];\n\n // Find the last matching pattern\n let progress = '';\n for (const [re, fn] of patterns) {\n const m = text.match(re);\n if (m) progress = fn(m);\n }\n\n // Emit only if changed\n if (progress && progress !== this.lastProgress) {\n this.lastProgress = progress;\n this.emit('message', { type: 'progress', content: progress } as SseEvent);\n }\n }\n\n /** Emit user message event (only if not already emitted this turn) */\n private emitUserIfNeeded(): void {\n if (!this.lastUserContent) return;\n // Check if user message already in history\n const already = this.history.some(\n m => m.role === 'user' && m.content === this.lastUserContent,\n );\n if (!already) {\n this.history.push({\n role: 'user',\n content: this.lastUserContent,\n timestamp: Date.now(),\n });\n this.emit('message', { type: 'user', content: this.lastUserContent } as SseEvent);\n }\n }\n\n // ── JSONL Discovery & Parsing (structured events) ─────\n\n private async discoverJsonl(): Promise<void> {\n const searchStart = this.messageSentAt - 2000;\n\n // Poll for up to 60 seconds (120 × 500ms)\n for (let i = 0; i < 120; i++) {\n if (this.killed) return;\n const jsonl = findRecentJsonl(this.cwd, searchStart);\n if (jsonl) {\n this.jsonlPath = jsonl;\n this.sessionId = sessionIdFromJsonlPath(jsonl) || null;\n console.log(`[pty-session ${this.id}] found JSONL: ${jsonl} (session: ${this.sessionId})`);\n this.startTailingJsonl();\n return;\n }\n await new Promise(r => setTimeout(r, 500));\n }\n console.warn(`[pty-session ${this.id}] JSONL not found after 60s — using PTY output parsing`);\n }\n\n private startTailingJsonl(): void {\n if (!this.jsonlPath) return;\n\n this.readNewJsonlLines();\n\n try {\n this.jsonlWatcher = fs.watch(\n path.dirname(this.jsonlPath),\n (eventType, filename) => {\n if (filename === path.basename(this.jsonlPath!)) {\n this.readNewJsonlLines();\n }\n },\n );\n } catch (err) {\n console.warn(`[pty-session ${this.id}] fs.watch failed:`, err);\n }\n }\n\n private readNewJsonlLines(): void {\n if (!this.jsonlPath) return;\n\n try {\n const stat = fs.statSync(this.jsonlPath);\n if (stat.size <= this.jsonlOffset) return;\n\n const fd = fs.openSync(this.jsonlPath, 'r');\n const buf = Buffer.alloc(stat.size - this.jsonlOffset);\n fs.readSync(fd, buf, 0, buf.length, this.jsonlOffset);\n fs.closeSync(fd);\n this.jsonlOffset = stat.size;\n\n const text = buf.toString('utf8');\n for (const line of text.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const evt: JsonlEvent = JSON.parse(trimmed);\n this.processJsonlEvent(evt);\n } catch {\n // skip malformed lines\n }\n }\n } catch {\n // file might be temporarily unavailable\n }\n }\n\n /** Process a JSONL event — marks usedJsonl to disable PTY parsing */\n private processJsonlEvent(evt: JsonlEvent): void {\n // Once JSONL events arrive, disable PTY output parsing\n this.usedJsonl = true;\n\n if (!this.sessionId && evt.sessionId) {\n this.sessionId = evt.sessionId;\n }\n\n switch (evt.type) {\n case 'user': {\n const text = typeof evt.message?.content === 'string'\n ? evt.message.content\n : Array.isArray(evt.message?.content)\n ? evt.message.content\n .filter((c: any) => c.type === 'text')\n .map((c: any) => c.text)\n .join('\\n')\n : '';\n this.history.push({\n role: 'user',\n content: text,\n timestamp: evt.timestamp ? new Date(evt.timestamp).getTime() : Date.now(),\n });\n this.lastActivityAt = Date.now();\n this.emit('message', { type: 'user', content: text } as SseEvent);\n if (this.history.filter(m => m.role === 'user').length === 1 && text) {\n this.title = text.slice(0, 60);\n this.emit('title-change', this.title);\n }\n break;\n }\n case 'assistant': {\n const content = evt.message?.content;\n if (!Array.isArray(content)) break;\n\n const texts = content\n .filter((c: any) => c.type === 'text')\n .map((c: any) => c.text)\n .join('\\n')\n .trim();\n\n const tools: ToolUseInfo[] = content\n .filter((c: any) => c.type === 'tool_use')\n .map((c: any) => ({\n name: c.name,\n input: c.input || {},\n }));\n\n if (texts || tools.length) {\n this.history.push({\n role: 'assistant',\n content: texts,\n toolUse: tools.length ? tools : undefined,\n timestamp: evt.timestamp ? new Date(evt.timestamp).getTime() : Date.now(),\n });\n }\n\n if (texts) {\n this.emit('message', { type: 'assistant_text', content: texts } as SseEvent);\n }\n for (const t of tools) {\n this.emit('message', { type: 'assistant_tool', tool: t } as SseEvent);\n }\n break;\n }\n case 'system': {\n if (evt.subtype === 'tool_result') {\n const lastAssistant = [...this.history].reverse().find(m => m.role === 'assistant' && m.toolUse?.length);\n if (lastAssistant?.toolUse?.length) {\n const lastTool = lastAssistant.toolUse[lastAssistant.toolUse.length - 1];\n const resultText = typeof evt.message?.content === 'string'\n ? evt.message.content : '';\n lastTool.result = resultText.slice(0, 500);\n }\n const resultContent = typeof evt.message?.content === 'string'\n ? evt.message.content\n : Array.isArray(evt.message?.content)\n ? evt.message.content.map((c: any) => c.text || '').join('')\n : '';\n this.emit('message', { type: 'system', content: resultContent.slice(0, 200) } as SseEvent);\n } else if (evt.subtype === 'turn_duration') {\n this.status = 'ready';\n this.lastActivityAt = Date.now();\n this.emit('message', { type: 'done', durationMs: evt.durationMs } as SseEvent);\n }\n break;\n }\n }\n }\n\n // ── Lifecycle ─────────────────────────────────────────\n\n kill(): void {\n this.killed = true;\n this.cleanup();\n if (this.pty) {\n try { this.pty.kill(); } catch { /* already dead */ }\n this.pty = null;\n }\n this.status = 'exited';\n }\n\n private cleanup(): void {\n if (this.jsonlWatcher) {\n this.jsonlWatcher.close();\n this.jsonlWatcher = null;\n }\n }\n\n getInfo(): {\n id: string;\n title: string;\n status: SessionStatus;\n createdAt: number;\n lastActivityAt: number;\n messageCount: number;\n lastMessagePreview: string;\n sessionId: string | null;\n } {\n const lastMsg = this.history[this.history.length - 1];\n return {\n id: this.id,\n title: this.title,\n status: this.status,\n createdAt: this.spawnTime,\n lastActivityAt: this.lastActivityAt,\n messageCount: this.history.length,\n lastMessagePreview: lastMsg ? lastMsg.content.slice(0, 80) : '',\n sessionId: this.sessionId,\n };\n }\n\n getHistory(): ChatMessage[] {\n return [...this.history];\n }\n}\n","/**\n * CC Prompter — Asset Loader\n *\n * 运行时从包根目录读取 panel.html 和 inject.js。\n * 兼容 ESM(import.meta.url)和 CJS(__dirname)。\n *\n * 解析策略:\n * - tsup 构建后:资产文件复制到 dist/,__dirname 是 dist/\n * - Vite 直接引用 TS:__dirname 是 src/,资产文件在上一级\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\n\n// Resolve __dirname in both ESM and CJS contexts\nconst _dirname = typeof __dirname !== 'undefined'\n ? __dirname\n : dirname(fileURLToPath(import.meta.url));\n\n/** Resolve asset path — checks dist/ (build) then parent (dev) */\nfunction assetPath(filename: string): string {\n // 1. Same dir as this file (dist/ after build, or src/ in dev)\n const here = join(_dirname, filename);\n if (existsSync(here)) return here;\n\n // 2. Parent dir (package root — when running from src/ via vite)\n const parent = join(_dirname, '..', filename);\n if (existsSync(parent)) return parent;\n\n // 3. Fallback — let it throw with a clear message\n throw new Error(\n `[cc-prompter] Asset not found: ${filename}\\n` +\n ` Tried: ${here}\\n` +\n ` Tried: ${parent}\\n` +\n ` __dirname: ${_dirname}`\n );\n}\n\nexport function getPanelHtml(): string {\n return readFileSync(assetPath('panel.html'), 'utf8');\n}\n\nexport function getInjectScript(): string {\n return readFileSync(assetPath('inject.js'), 'utf8');\n}\n"],"mappings":"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","names":["require","resolve","existsSync","join","dirname","fileURLToPath","resolve"]}
package/dist/inject.js ADDED
@@ -0,0 +1,329 @@
1
+ (function() {
2
+ var SIDECAR = 'http://localhost:3456'; // default, will be overridden
3
+ var portLoaded = false;
4
+
5
+ // Fetch actual sidecar port from vite middleware
6
+ fetch('/__cc-port').then(function(r) { return r.text(); }).then(function(p) {
7
+ if (p && parseInt(p) > 0) {
8
+ SIDECAR = 'http://localhost:' + p;
9
+ portLoaded = true;
10
+ }
11
+ }).catch(function() {
12
+ // Fallback: use default port
13
+ });
14
+ var container = null;
15
+ var iframe = null;
16
+ var iframeReady = false;
17
+ var dragBar = null;
18
+ var resizeHandle = null;
19
+
20
+ // Drag state
21
+ var dragging = false;
22
+ var dragOffsetX = 0;
23
+ var dragOffsetY = 0;
24
+
25
+ // Resize state
26
+ var resizing = false;
27
+ var resizeStartX = 0;
28
+ var resizeStartY = 0;
29
+ var resizeStartW = 0;
30
+ var resizeStartH = 0;
31
+
32
+ // Edge resize state (top/left/bottom/right)
33
+ var edgeResizing = false;
34
+ var edgeDir = '';
35
+ var edgeStartX = 0;
36
+ var edgeStartY = 0;
37
+ var edgeStartRect = null;
38
+
39
+ var MIN_W = 320;
40
+ var MIN_H = 280;
41
+ var EDGE_THRESHOLD = 6;
42
+
43
+ function getOrCreatePanel() {
44
+ if (container) return container;
45
+
46
+ // Container
47
+ container = document.createElement('div');
48
+ container.id = 'cc-prompter-container';
49
+ container.style.cssText =
50
+ 'position:fixed;bottom:16px;right:16px;width:480px;height:540px;' +
51
+ 'border:1px solid #e0e0e0;border-radius:10px;z-index:99999;' +
52
+ 'box-shadow:0 4px 24px rgba(0,0,0,0.10);display:none;background:#fff;' +
53
+ 'overflow:visible;';
54
+
55
+ // Drag bar
56
+ dragBar = document.createElement('div');
57
+ dragBar.style.cssText =
58
+ 'height:28px;background:#fafafa;border-bottom:1px solid #ebebeb;' +
59
+ 'cursor:move;display:flex;align-items:center;justify-content:space-between;' +
60
+ 'padding:0 10px;user-select:none;border-radius:10px 10px 0 0;';
61
+ dragBar.innerHTML =
62
+ '<span style="font-size:11px;font-weight:600;color:#888;letter-spacing:0.3px;">CC Prompter</span>' +
63
+ '<span style="font-size:11px;color:#bbb;" id="cc-drag-hint">drag to move</span>';
64
+
65
+ // Iframe wrapper (holds iframe + resize handle, clips corners)
66
+ var wrap = document.createElement('div');
67
+ wrap.style.cssText = 'position:relative;width:100%;height:calc(100% - 28px);overflow:hidden;border-radius:0 0 10px 10px;';
68
+
69
+ // Iframe
70
+ iframe = document.createElement('iframe');
71
+ iframe.id = 'cc-prompter-panel';
72
+ iframe.src = SIDECAR + '/__panel/';
73
+ iframe.style.cssText = 'width:100%;height:100%;border:none;';
74
+
75
+ // Resize handle (bottom-right corner)
76
+ resizeHandle = document.createElement('div');
77
+ resizeHandle.style.cssText =
78
+ 'position:absolute;right:0;bottom:0;width:16px;height:16px;' +
79
+ 'cursor:nwse-resize;z-index:10;';
80
+ // SVG grip icon
81
+ resizeHandle.innerHTML =
82
+ '<svg width="16" height="16" viewBox="0 0 16 16" style="opacity:0.3;">' +
83
+ '<path d="M14 14L14 10M14 14L10 14M14 14L14 6M14 14L6 14M14 14L14 2M14 14L2 14" ' +
84
+ 'stroke="#888" stroke-width="1.2" stroke-linecap="round"/></svg>';
85
+
86
+ wrap.appendChild(iframe);
87
+ wrap.appendChild(resizeHandle);
88
+
89
+ container.appendChild(dragBar);
90
+ container.appendChild(wrap);
91
+ document.body.appendChild(container);
92
+
93
+ iframe.onload = function() { iframeReady = true; };
94
+
95
+ // Drag events
96
+ dragBar.addEventListener('mousedown', function(e) {
97
+ dragging = true;
98
+ var rect = container.getBoundingClientRect();
99
+ dragOffsetX = e.clientX - rect.left;
100
+ dragOffsetY = e.clientY - rect.top;
101
+ container.style.transition = 'none';
102
+ e.preventDefault();
103
+ });
104
+
105
+ // Hide drag hint after first drag
106
+ dragBar.addEventListener('mousedown', function() {
107
+ var hint = document.getElementById('cc-drag-hint');
108
+ if (hint) setTimeout(function() { hint.style.display = 'none'; }, 2000);
109
+ });
110
+
111
+ // Corner resize events
112
+ resizeHandle.addEventListener('mousedown', function(e) {
113
+ resizing = true;
114
+ resizeStartX = e.clientX;
115
+ resizeStartY = e.clientY;
116
+ resizeStartW = container.offsetWidth;
117
+ resizeStartH = container.offsetHeight;
118
+ container.style.transition = 'none';
119
+ e.preventDefault();
120
+ e.stopPropagation();
121
+ });
122
+
123
+ // Edge resize — detect mouse near edges of container
124
+ container.addEventListener('mousedown', function(e) {
125
+ if (e.target === resizeHandle || e.target.closest('#cc-prompter-panel')) return;
126
+ var rect = container.getBoundingClientRect();
127
+ var x = e.clientX - rect.left;
128
+ var y = e.clientY - rect.top;
129
+ var dir = '';
130
+
131
+ if (x < EDGE_THRESHOLD) dir = 'w';
132
+ else if (x > rect.width - EDGE_THRESHOLD) dir = 'e';
133
+
134
+ if (y < EDGE_THRESHOLD) dir += 'n';
135
+ else if (y > rect.height - EDGE_THRESHOLD) dir += 's';
136
+
137
+ // Only edge-resize from container border, not drag bar or iframe
138
+ if (dir && e.target === container) {
139
+ edgeResizing = true;
140
+ edgeDir = dir;
141
+ edgeStartX = e.clientX;
142
+ edgeStartY = e.clientY;
143
+ edgeStartRect = { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
144
+ container.style.transition = 'none';
145
+ e.preventDefault();
146
+ }
147
+ });
148
+
149
+ return container;
150
+ }
151
+
152
+ // Global mouse handlers
153
+ document.addEventListener('mousemove', function(e) {
154
+ if (dragging && container) {
155
+ var x = e.clientX - dragOffsetX;
156
+ var y = e.clientY - dragOffsetY;
157
+ x = Math.max(0, Math.min(x, window.innerWidth - container.offsetWidth));
158
+ y = Math.max(0, Math.min(y, window.innerHeight - container.offsetHeight));
159
+ container.style.left = x + 'px';
160
+ container.style.top = y + 'px';
161
+ container.style.right = 'auto';
162
+ container.style.bottom = 'auto';
163
+ }
164
+
165
+ if (resizing && container) {
166
+ var dw = e.clientX - resizeStartX;
167
+ var dh = e.clientY - resizeStartY;
168
+ var nw = Math.max(MIN_W, resizeStartW + dw);
169
+ var nh = Math.max(MIN_H, resizeStartH + dh);
170
+ // Clamp to viewport
171
+ var rect = container.getBoundingClientRect();
172
+ nw = Math.min(nw, window.innerWidth - rect.left);
173
+ nh = Math.min(nh, window.innerHeight - rect.top);
174
+ container.style.width = nw + 'px';
175
+ container.style.height = nh + 'px';
176
+ }
177
+
178
+ if (edgeResizing && container && edgeStartRect) {
179
+ var dx = e.clientX - edgeStartX;
180
+ var dy = e.clientY - edgeStartY;
181
+ var nl = edgeStartRect.left;
182
+ var nt = edgeStartRect.top;
183
+ var nw = edgeStartRect.width;
184
+ var nh = edgeStartRect.height;
185
+
186
+ if (edgeDir.indexOf('e') >= 0) {
187
+ nw = Math.max(MIN_W, edgeStartRect.width + dx);
188
+ nw = Math.min(nw, window.innerWidth - nl);
189
+ }
190
+ if (edgeDir.indexOf('w') >= 0) {
191
+ var proposedW = edgeStartRect.width - dx;
192
+ if (proposedW >= MIN_W && nl + dx >= 0) {
193
+ nw = proposedW;
194
+ nl = edgeStartRect.left + dx;
195
+ }
196
+ }
197
+ if (edgeDir.indexOf('s') >= 0) {
198
+ nh = Math.max(MIN_H, edgeStartRect.height + dy);
199
+ nh = Math.min(nh, window.innerHeight - nt);
200
+ }
201
+ if (edgeDir.indexOf('n') >= 0) {
202
+ var proposedH = edgeStartRect.height - dy;
203
+ if (proposedH >= MIN_H && nt + dy >= 0) {
204
+ nh = proposedH;
205
+ nt = edgeStartRect.top + dy;
206
+ }
207
+ }
208
+
209
+ container.style.left = nl + 'px';
210
+ container.style.top = nt + 'px';
211
+ container.style.right = 'auto';
212
+ container.style.bottom = 'auto';
213
+ container.style.width = nw + 'px';
214
+ container.style.height = nh + 'px';
215
+ }
216
+
217
+ // Update cursor on container edges
218
+ if (container && !dragging && !resizing && !edgeResizing) {
219
+ var rect = container.getBoundingClientRect();
220
+ var x = e.clientX - rect.left;
221
+ var y = e.clientY - rect.top;
222
+ var cursor = '';
223
+
224
+ if (x < EDGE_THRESHOLD || x > rect.width - EDGE_THRESHOLD) {
225
+ if (y < EDGE_THRESHOLD || y > rect.height - EDGE_THRESHOLD) {
226
+ cursor = 'nwse-resize';
227
+ } else {
228
+ cursor = x < EDGE_THRESHOLD ? 'ew-resize' : 'ew-resize';
229
+ }
230
+ } else if (y < EDGE_THRESHOLD || y > rect.height - EDGE_THRESHOLD) {
231
+ cursor = 'ns-resize';
232
+ }
233
+
234
+ if (cursor && e.target === container) {
235
+ container.style.cursor = cursor;
236
+ } else if (e.target === container) {
237
+ container.style.cursor = 'default';
238
+ }
239
+ }
240
+ });
241
+
242
+ document.addEventListener('mouseup', function() {
243
+ if (dragging) {
244
+ dragging = false;
245
+ if (container) container.style.transition = '';
246
+ }
247
+ if (resizing) {
248
+ resizing = false;
249
+ if (container) container.style.transition = '';
250
+ }
251
+ if (edgeResizing) {
252
+ edgeResizing = false;
253
+ edgeDir = '';
254
+ edgeStartRect = null;
255
+ if (container) container.style.transition = '';
256
+ }
257
+ });
258
+
259
+ function showPanel(source) {
260
+ var c = getOrCreatePanel();
261
+ c.style.display = 'block';
262
+ function send() {
263
+ try {
264
+ iframe.contentWindow.postMessage({
265
+ type: 'cc-prompter:source-info',
266
+ source: source
267
+ }, SIDECAR);
268
+ } catch(e) {
269
+ setTimeout(send, 100);
270
+ }
271
+ }
272
+ if (iframeReady) { send(); }
273
+ else { iframe.onload = function() { iframeReady = true; send(); }; }
274
+ }
275
+
276
+ // Listen for hide message from iframe (Escape key)
277
+ window.addEventListener('message', function(e) {
278
+ if (e.data && e.data.type === 'cc-prompter:hide') {
279
+ if (container && container.style.display !== 'none') {
280
+ container.style.display = 'none';
281
+ }
282
+ }
283
+ });
284
+
285
+ // Track mouse for element detection
286
+ var mx = 0, my = 0;
287
+ document.addEventListener('mousemove', function(e) { mx = e.clientX; my = e.clientY; });
288
+
289
+ // code-inspector click handler
290
+ window.addEventListener('code-inspector:trackCode', function(e) {
291
+ var d = e.detail || {};
292
+ var el = document.elementFromPoint(mx, my);
293
+ var info = '';
294
+ if (el) {
295
+ var tag = el.tagName.toLowerCase();
296
+ var cls = (el.className && typeof el.className === 'string')
297
+ ? el.className.replace(/\s+/g, ' ').trim().slice(0, 80) : '';
298
+ var txt = (el.textContent || '').trim().slice(0, 50);
299
+ info = '<' + tag
300
+ + (el.id ? ' id="' + el.id + '"' : '')
301
+ + (cls ? ' class="' + cls + '"' : '')
302
+ + (txt ? ' text~"' + txt + '"' : '')
303
+ + '>';
304
+ }
305
+ showPanel({
306
+ name: d.name || '',
307
+ path: d.path || '',
308
+ line: d.line || 0,
309
+ column: d.column || 0,
310
+ elementInfo: info
311
+ });
312
+ });
313
+
314
+ // Escape to hide
315
+ document.addEventListener('keydown', function(e) {
316
+ if (e.key === 'Escape') {
317
+ if (container && container.style.display !== 'none') container.style.display = 'none';
318
+ }
319
+ });
320
+
321
+ // Ctrl+Shift+P toggle
322
+ document.addEventListener('keydown', function(e) {
323
+ if (e.ctrlKey && e.shiftKey && e.key === 'P') {
324
+ e.preventDefault();
325
+ var c = getOrCreatePanel();
326
+ c.style.display = c.style.display === 'none' ? 'block' : 'none';
327
+ }
328
+ });
329
+ })();