vite-plugin-koyi 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.
- package/LICENSE +21 -0
- package/dist/client.iife.js +81 -0
- package/dist/index.cjs +65 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +392 -0
- package/dist/index.js.map +1 -0
- package/dist/node/claude-bridge.d.ts +58 -0
- package/dist/node/claude-bridge.d.ts.map +1 -0
- package/dist/node/index.d.ts +65 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/server.d.ts +24 -0
- package/dist/node/server.d.ts.map +1 -0
- package/dist/shared/types.d.ts +81 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/node/claude-bridge.ts","../src/node/server.ts","../src/node/index.ts"],"sourcesContent":["/**\n * Claude Bridge — connects the WebSocket server to Claude.\n *\n * Supports two modes:\n * - 'api' → Uses the Anthropic Node.js SDK with an API key (streaming).\n * - 'cli' → Spawns the local `claude` CLI (Claude Code) in --print mode.\n * With autoApply=true, adds --dangerously-skip-permissions so Claude\n * can modify files directly without confirmation prompts.\n */\nimport { spawn, type ChildProcess } from 'child_process'\nimport os from 'os'\nimport path from 'path'\nimport fs from 'fs'\nimport Anthropic from '@anthropic-ai/sdk'\nimport type { ChatRole, ImageAttachment } from '../shared/types.js'\n\nexport interface ClaudeBridgeOptions {\n mode: 'api' | 'cli'\n apiKey?: string\n projectRoot: string\n model?: string\n /**\n * When true, code changes suggested by Claude are applied to disk automatically\n * without requiring user confirmation.\n * - CLI mode: adds --dangerously-skip-permissions flag\n * - API mode: the system prompt instructs Claude to output changes in a\n * machine-readable patch format that the server writes to disk\n */\n autoApply?: boolean\n}\n\nexport interface StreamRequest {\n userContent: string\n /** Pre-built context string from selected DOM elements */\n systemContext: string\n history: Array<{ role: ChatRole; content: string }>\n /** Images the user attached to this message */\n images?: ImageAttachment[]\n}\n\nconst BASE_SYSTEM_PROMPT = `You are Koyi, an expert AI assistant specialized in frontend development.\nYou help developers understand, debug, and improve their UI components in real-time.\n\nWhen the user provides DOM element context (selected from the live browser view), you will:\n1. Analyze the component source code at that location\n2. Understand the element's role in the UI\n3. Provide precise, actionable suggestions\n\nGuidelines:\n- Be concise and focused on the specific element/issue\n- Always show code examples with correct language syntax highlighting\n- Reference specific file paths and line numbers when discussing code\n- Prefer modern, idiomatic patterns for the detected framework`\n\nexport class ClaudeBridge {\n private client?: Anthropic\n private options: ClaudeBridgeOptions\n private activeProc?: ChildProcess\n private abortController?: AbortController\n /** Content of the project's CLAUDE.md file (empty string if not found) */\n private claudeMdContent: string\n /**\n * Claude CLI session ID for the current conversation.\n * Set after the first CLI turn; used with --resume on subsequent turns.\n * Cleared when the conversation resets (history.length === 0) or on abort.\n */\n private cliSessionId?: string\n\n constructor(options: ClaudeBridgeOptions) {\n this.options = options\n this.claudeMdContent = this.readClaudeMd()\n\n if (this.claudeMdContent) {\n console.log('[koyi] Loaded CLAUDE.md project instructions')\n }\n\n if (options.mode === 'api') {\n const apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY\n if (!apiKey) {\n console.warn(\n '[koyi] No ANTHROPIC_API_KEY found. Set it in your environment or pass apiKey to KoyiPlugin().'\n )\n }\n this.client = new Anthropic({ apiKey })\n }\n }\n\n /**\n * Read CLAUDE.md from the project root.\n * Checks multiple candidate paths in priority order.\n */\n private readClaudeMd(): string {\n const candidates = [\n 'CLAUDE.md',\n 'claude.md',\n '.claude/CLAUDE.md',\n '.claude/claude.md'\n ]\n for (const name of candidates) {\n const fullPath = path.resolve(this.options.projectRoot, name)\n if (fs.existsSync(fullPath)) {\n try {\n return fs.readFileSync(fullPath, 'utf-8').trim()\n } catch {\n // ignore read errors\n }\n }\n }\n return ''\n }\n\n /**\n * Build the complete system prompt, merging in order:\n * 1. Base Koyi instructions\n * 2. Project-level CLAUDE.md (if present)\n * 3. DOM element context for the current request (if present)\n */\n private buildSystemPrompt(systemContext: string): string {\n const parts: string[] = [BASE_SYSTEM_PROMPT]\n\n if (this.claudeMdContent) {\n parts.push(\n '---\\n\\n## Project Instructions (from CLAUDE.md)\\n\\n' +\n this.claudeMdContent\n )\n }\n\n if (systemContext) {\n parts.push(\n '---\\n\\n## Current Context (selected DOM elements)\\n\\n' + systemContext\n )\n }\n\n return parts.join('\\n\\n')\n }\n\n async stream(\n request: StreamRequest,\n onChunk: (text: string) => void,\n onDone: () => void\n ): Promise<void> {\n if (this.options.mode === 'cli') {\n // Reset the CLI session whenever the client starts a fresh conversation\n if (request.history.length === 0) {\n this.cliSessionId = undefined\n }\n return this.streamViaCLI(request, onChunk, onDone)\n }\n return this.streamViaAPI(request, onChunk, onDone)\n }\n\n abort(): void {\n this.abortController?.abort()\n this.activeProc?.kill('SIGTERM')\n this.activeProc = undefined\n // The session may be in an incomplete state after abort — start fresh next turn\n this.cliSessionId = undefined\n }\n\n // ─── Anthropic SDK (API mode) ───────────────────────────────────────────────\n\n private async streamViaAPI(\n request: StreamRequest,\n onChunk: (text: string) => void,\n onDone: () => void\n ): Promise<void> {\n if (!this.client) {\n throw new Error(\n 'Anthropic client not initialized. Provide an API key or use claudeMode: \"cli\".'\n )\n }\n\n const { userContent, systemContext, history, images } = request\n\n const systemPrompt = this.buildSystemPrompt(systemContext)\n\n // Build user content: optional image blocks followed by the text message\n const userContentParam: Anthropic.Messages.MessageParam['content'] =\n images?.length\n ? [\n ...images.map((img) => ({\n type: 'image' as const,\n source: {\n type: 'base64' as const,\n media_type: img.mimeType as\n | 'image/jpeg'\n | 'image/png'\n | 'image/gif'\n | 'image/webp',\n data: img.base64\n }\n })),\n { type: 'text' as const, text: userContent }\n ]\n : userContent\n\n const messages: Anthropic.Messages.MessageParam[] = [\n ...history.map((h) => ({ role: h.role, content: h.content })),\n { role: 'user', content: userContentParam }\n ]\n\n this.abortController = new AbortController()\n\n const stream = this.client.messages.stream(\n {\n model: this.options.model ?? 'claude-opus-4-5',\n max_tokens: 8096,\n system: systemPrompt,\n messages\n },\n { signal: this.abortController.signal }\n )\n\n for await (const event of stream) {\n if (\n event.type === 'content_block_delta' &&\n event.delta.type === 'text_delta'\n ) {\n onChunk(event.delta.text)\n }\n }\n\n onDone()\n this.abortController = undefined\n }\n\n // ─── Claude Code CLI (cli mode) ─────────────────────────────────────────────\n\n private streamViaCLI(\n request: StreamRequest,\n onChunk: (text: string) => void,\n onDone: () => void\n ): Promise<void> {\n return new Promise((resolve, reject) => {\n const { userContent, systemContext } = request\n\n // ── Build CLI arguments ────────────────────────────────────────────\n const cliArgs: string[] = []\n\n if (this.cliSessionId) {\n // Resume the existing Claude CLI session.\n // Claude manages the full conversation history internally — no need\n // to re-send previous turns as text.\n cliArgs.push('--resume', this.cliSessionId)\n console.log(`[koyi] Resuming CLI session ${this.cliSessionId}`)\n }\n\n // --print → non-interactive, output to stdout\n // --output-format → NDJSON stream so we can capture session_id\n // --verbose → required by the CLI when combining --print + stream-json\n cliArgs.push('--print', '--output-format', 'stream-json', '--verbose')\n\n if (this.options.autoApply) {\n cliArgs.push('--dangerously-skip-permissions')\n }\n\n // ── Write temp files for attached images ───────────────────────────\n const tempImageFiles: string[] = []\n if (request.images?.length) {\n for (let i = 0; i < request.images.length; i++) {\n const img = request.images[i]\n const ext = (img.mimeType.split('/')[1] ?? 'png').replace(\n 'jpeg',\n 'jpg'\n )\n const tempPath = path.join(\n os.tmpdir(),\n `koyi-img-${Date.now()}-${i}.${ext}`\n )\n fs.writeFileSync(tempPath, Buffer.from(img.base64, 'base64'))\n tempImageFiles.push(tempPath)\n cliArgs.push('--image', tempPath)\n }\n }\n\n // ── Build prompt ───────────────────────────────────────────────────\n let prompt: string\n if (this.cliSessionId) {\n // Resuming: only send the new user message.\n // Re-attach DOM context if the user selected elements this turn.\n const parts: string[] = []\n if (systemContext) {\n parts.push(\n '## Current Context (selected DOM elements)\\n\\n' + systemContext,\n '---'\n )\n }\n parts.push(userContent)\n prompt = parts.join('\\n\\n')\n } else {\n // Fresh session: include full system prompt + user request\n const systemSection = this.buildSystemPrompt(systemContext)\n prompt = [\n systemSection,\n `---\\n\\n## User Request\\n\\n${userContent}`\n ].join('\\n\\n')\n }\n\n cliArgs.push(prompt)\n\n // ── Spawn ──────────────────────────────────────────────────────────\n const proc = spawn('claude', cliArgs, {\n cwd: this.options.projectRoot,\n env: { ...process.env },\n stdio: ['ignore', 'pipe', 'pipe']\n })\n\n this.activeProc = proc\n\n // ── Parse NDJSON stream ────────────────────────────────────────────\n // Claude CLI with --output-format stream-json emits one JSON object\n // per line. We buffer partial lines and parse complete ones.\n let lineBuffer = ''\n\n type StreamEvent = {\n type?: string\n session_id?: string\n message?: { content?: Array<{ type: string; text?: string }> }\n }\n\n const processLine = (line: string): void => {\n if (!line.trim()) return\n try {\n const event = JSON.parse(line) as StreamEvent\n // Persist the session ID so subsequent turns can use --resume\n if (event.session_id) {\n this.cliSessionId = event.session_id\n }\n // Forward text content from assistant message events\n if (event.type === 'assistant' && event.message?.content) {\n for (const block of event.message.content) {\n if (block.type === 'text' && block.text) {\n onChunk(block.text)\n }\n }\n }\n } catch {\n // Not JSON (e.g. a plain-text fallback line) — forward as raw text\n onChunk(line + '\\n')\n }\n }\n\n proc.stdout.on('data', (chunk: Buffer) => {\n lineBuffer += chunk.toString('utf-8')\n const lines = lineBuffer.split('\\n')\n lineBuffer = lines.pop() ?? '' // keep the incomplete trailing line\n lines.forEach(processLine)\n })\n\n proc.stderr.on('data', (chunk: Buffer) => {\n const text = chunk.toString('utf-8')\n if (\n !text.includes('Bypassing Permissions') &&\n !text.includes('Human:')\n ) {\n console.warn('[koyi] claude stderr:', text.trim())\n }\n })\n\n proc.on('close', (code) => {\n // Flush any remaining buffered content\n if (lineBuffer.trim()) processLine(lineBuffer)\n lineBuffer = ''\n\n // Clean up temp image files\n for (const f of tempImageFiles) {\n try {\n fs.unlinkSync(f)\n } catch {\n /* ignore */\n }\n }\n\n this.activeProc = undefined\n if (code === 0 || code === null) {\n onDone()\n resolve()\n } else {\n reject(new Error(`Claude CLI exited with code ${code}`))\n }\n })\n\n proc.on('error', (err: NodeJS.ErrnoException) => {\n this.activeProc = undefined\n if (err.code === 'ENOENT') {\n reject(\n new Error(\n '[koyi] `claude` command not found.\\n' +\n ' Install Claude Code with: npm install -g @anthropic-ai/claude-code\\n' +\n ' Or switch to API mode: KoyiPlugin({ claudeMode: \"api\", apiKey: \"sk-...\" })'\n )\n )\n } else {\n reject(err)\n }\n })\n })\n }\n}\n","/**\n * Koyi WebSocket Server\n *\n * Attaches to the Vite dev server's HTTP server via the 'upgrade' event so we\n * don't need a separate port. The browser client connects to:\n * ws://localhost:<vite-port>/__koyi_ws\n *\n * Responsibilities:\n * 1. Accept WebSocket connections from the browser overlay\n * 2. Parse DOM context (read source file snippets from disk)\n * 3. Stream responses from Claude back to the client\n */\nimport { WebSocketServer, WebSocket } from 'ws'\nimport type { IncomingMessage, Server } from 'http'\nimport path from 'path'\nimport fs from 'fs'\nimport type {\n ClientMessage,\n ServerMessage,\n DomContext\n} from '../shared/types.js'\nimport { ClaudeBridge, type ClaudeBridgeOptions } from './claude-bridge.js'\n\nexport const WS_PATH = '/__koyi_ws'\n\n/** Lines of source code to include above/below the target line */\nconst CONTEXT_RADIUS = 25\n\nexport interface KoyiServerOptions {\n projectRoot: string\n claudeMode: 'api' | 'cli'\n apiKey?: string\n model?: string\n autoApply?: boolean\n}\n\nexport class KoyiServer {\n private wss: WebSocketServer\n private bridge: ClaudeBridge\n private projectRoot: string\n\n constructor(httpServer: Server, options: KoyiServerOptions) {\n this.projectRoot = options.projectRoot\n this.bridge = new ClaudeBridge({\n mode: options.claudeMode,\n apiKey: options.apiKey,\n projectRoot: options.projectRoot,\n model: options.model,\n autoApply: options.autoApply\n } satisfies ClaudeBridgeOptions)\n\n this.wss = new WebSocketServer({ noServer: true })\n\n // Intercept HTTP upgrade requests on the Vite dev server\n httpServer.on('upgrade', (req: IncomingMessage, socket: any, head: any) => {\n if (req.url === WS_PATH) {\n this.wss.handleUpgrade(req, socket, head, (ws) => {\n this.wss.emit('connection', ws, req)\n this.handleConnection(ws)\n })\n }\n })\n }\n\n private handleConnection(ws: WebSocket): void {\n const send = (msg: ServerMessage): void => {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(msg))\n }\n }\n\n send({ type: 'ready' })\n\n ws.on('message', async (raw) => {\n let msg: ClientMessage\n try {\n msg = JSON.parse(raw.toString()) as ClientMessage\n } catch {\n return\n }\n\n if (msg.type === 'send_message') {\n const { messageId, content, contexts, history, images } = msg\n\n const systemContext = await this.buildSystemContext(contexts)\n\n send({ type: 'stream_start', messageId })\n\n try {\n await this.bridge.stream(\n { userContent: content, systemContext, history, images },\n (text) => send({ type: 'stream_chunk', messageId, text }),\n () => send({ type: 'stream_end', messageId })\n )\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err)\n console.error('[koyi] Bridge error:', message)\n send({ type: 'stream_error', messageId, error: message })\n }\n } else if (msg.type === 'abort') {\n this.bridge.abort()\n }\n })\n\n ws.on('error', (err) => {\n console.error('[koyi] WebSocket error:', err.message)\n })\n }\n\n /**\n * Build a rich textual context block for Claude from selected DOM elements.\n * For each element, reads the relevant source snippet from disk.\n */\n private async buildSystemContext(contexts: DomContext[]): Promise<string> {\n if (contexts.length === 0) return ''\n\n const parts: string[] = []\n\n for (const ctx of contexts) {\n const lines: string[] = []\n\n // ── Element summary ──────────────────────────────────────────────────\n const elDesc = [\n `<${ctx.tagName}`,\n ctx.id ? ` id=\"${ctx.id}\"` : '',\n ctx.className ? ` class=\"${ctx.className}\"` : '',\n '>'\n ].join('')\n\n lines.push(`### Element: ${elDesc}`)\n\n if (ctx.filePath) {\n lines.push(`**Source:** \\`${ctx.filePath}:${ctx.line}:${ctx.column}\\``)\n\n // ── Read source file snippet ────────────────────────────────────\n const resolved = this.resolveFilePath(ctx.filePath)\n if (resolved) {\n try {\n const fileContent = fs.readFileSync(resolved, 'utf-8')\n const fileLines = fileContent.split('\\n')\n const startIdx = Math.max(0, ctx.line - 1 - CONTEXT_RADIUS)\n const endIdx = Math.min(\n fileLines.length,\n ctx.line - 1 + CONTEXT_RADIUS\n )\n const snippet = fileLines.slice(startIdx, endIdx).join('\\n')\n const ext = path.extname(resolved).slice(1) || 'tsx'\n\n lines.push(\n `\\n\\`\\`\\`${ext}\\n// ${ctx.filePath} (lines ${startIdx + 1}–${endIdx})\\n${snippet}\\n\\`\\`\\``\n )\n } catch {\n lines.push('_(Could not read source file)_')\n }\n }\n }\n\n // ── HTML snippet ──────────────────────────────────────────────────\n if (ctx.outerHTML) {\n const truncated =\n ctx.outerHTML.length > 600\n ? ctx.outerHTML.slice(0, 600) + '…'\n : ctx.outerHTML\n lines.push(`\\n**HTML:**\\n\\`\\`\\`html\\n${truncated}\\n\\`\\`\\``)\n }\n\n parts.push(lines.join('\\n'))\n }\n\n return parts.join('\\n\\n---\\n\\n')\n }\n\n /** Resolve a (possibly relative) file path to an absolute path on disk */\n private resolveFilePath(filePath: string): string | null {\n if (path.isAbsolute(filePath) && fs.existsSync(filePath)) {\n return filePath\n }\n // Strip leading slash and try from project root\n const rel = filePath.startsWith('/') ? filePath.slice(1) : filePath\n const candidate = path.resolve(this.projectRoot, rel)\n return fs.existsSync(candidate) ? candidate : null\n }\n}\n","/**\n * vite-plugin-koyi — main plugin entry point\n *\n * What this plugin does:\n * 1. Uses @code-inspector/core to inject `data-insp-path` attributes into every\n * JSX/Vue/Svelte element during the Vite transform step (build-time).\n * 2. Starts a WebSocket server on the Vite dev server that bridges the browser\n * overlay to the local Claude Code CLI or Anthropic API.\n * 3. Injects the pre-built browser overlay (dist/client.iife.js) into every\n * HTML page served by the dev server.\n *\n * Usage in vite.config.ts:\n * import { KoyiPlugin } from 'vite-plugin-koyi'\n * export default defineConfig({\n * plugins: [KoyiPlugin({ claudeMode: 'cli' })]\n * })\n */\nimport type { Plugin, ViteDevServer, HtmlTagDescriptor } from 'vite'\nimport type { Server as HttpServer } from 'http'\nimport path from 'path'\nimport fs from 'fs'\nimport { fileURLToPath } from 'url'\nimport {\n transformCode,\n isJsTypeFile,\n normalizePath\n} from '@code-inspector/core'\nimport { KoyiServer, WS_PATH } from './server.js'\n\n// Support both CJS (__dirname) and ESM (import.meta.url)\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nconst _dirname: string =\n typeof __dirname !== 'undefined'\n ? __dirname\n : path.dirname(fileURLToPath(import.meta.url))\n\nconst CLIENT_BUNDLE = path.resolve(_dirname, 'client.iife.js')\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport interface KoyiOptions {\n /**\n * How to connect to Claude:\n * - `'cli'` (default) — spawns the local `claude` CLI (Claude Code).\n * Requires: `npm install -g @anthropic-ai/claude-code`\n * - `'api'` — uses the Anthropic SDK with an API key.\n */\n claudeMode?: 'cli' | 'api'\n\n /**\n * Anthropic API key. Required when `claudeMode` is `'api'`.\n * Falls back to `process.env.ANTHROPIC_API_KEY`.\n */\n apiKey?: string\n\n /**\n * Claude model to use (only for `'api'` mode).\n * @default 'claude-opus-4-5'\n */\n model?: string\n\n /**\n * Keyboard shortcut to toggle the Koyi panel.\n * Format: modifier+key, e.g. 'ctrl+shift+k', 'alt+k'\n * @default 'ctrl+shift+k'\n */\n hotkey?: string\n\n /**\n * Initial position of the panel.\n * @default { x: 'right', y: 'bottom' }\n */\n position?: { x?: 'left' | 'right'; y?: 'top' | 'bottom' }\n\n /**\n * When `true`, code changes suggested by Claude are applied to the project\n * files **automatically** without requiring any confirmation.\n *\n * - CLI mode: passes `--dangerously-skip-permissions` to the `claude` CLI so\n * Claude Code can write/edit files freely.\n * - API mode: instructs Claude to emit complete file contents, which the\n * server writes to disk automatically.\n *\n * @default true\n */\n autoApply?: boolean\n}\n\n// ─── Plugin factory ───────────────────────────────────────────────────────────\n\nexport function KoyiPlugin(options: KoyiOptions = {}): Plugin[] {\n const {\n claudeMode = 'cli',\n apiKey = process.env.ANTHROPIC_API_KEY,\n model,\n hotkey = 'ctrl+shift+k',\n position = { x: 'right', y: 'bottom' },\n autoApply = true\n } = options\n\n let projectRoot = process.cwd()\n\n // ── Plugin 1: DOM path injection (build-time AST transform) ──────────────\n const transformPlugin: Plugin = {\n name: 'vite-plugin-koyi:transform',\n enforce: 'pre',\n apply: (_, { command }) => command === 'serve',\n\n configResolved(config) {\n projectRoot = config.root\n },\n\n transform(code: string, id: string) {\n // Only transform JS/TS/JSX/TSX/Vue/Svelte files; skip node_modules\n if (\n id.includes('node_modules') ||\n id.includes('/__koyi') ||\n (!isJsTypeFile(id) && !id.endsWith('.vue') && !id.endsWith('.svelte'))\n ) {\n return code\n }\n\n const filePath = normalizePath(id.split('?')[0])\n let fileType = ''\n if (filePath.endsWith('.vue')) fileType = 'vue'\n else if (filePath.endsWith('.svelte')) fileType = 'svelte'\n else if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx'))\n fileType = 'jsx'\n else if (filePath.endsWith('.ts') || filePath.endsWith('.js'))\n fileType = 'js'\n\n if (!fileType) return code\n\n try {\n return transformCode({\n content: code,\n filePath,\n fileType,\n // Use absolute paths so the server can read the file directly\n pathType: 'absolute',\n // Avoid transforming our own injected overlay elements\n escapeTags: ['koyi-overlay']\n })\n } catch {\n // Silently skip files that can't be parsed (e.g. non-JSX TS)\n return code\n }\n }\n }\n\n // ── Plugin 2: WebSocket server + client injection ─────────────────────────\n const serverPlugin: Plugin = {\n name: 'vite-plugin-koyi:server',\n apply: (_, { command }) => command === 'serve',\n\n configureServer(server: ViteDevServer) {\n if (!server.httpServer) return\n\n new KoyiServer(server.httpServer as HttpServer, {\n projectRoot,\n claudeMode,\n apiKey,\n model,\n autoApply\n })\n\n // Print a helpful startup message\n server.httpServer.once('listening', () => {\n const address = server.httpServer!.address()\n const port = typeof address === 'object' && address ? address.port : '?'\n console.log(\n `\\n \\x1b[36m⚡ Koyi AI\\x1b[0m ` +\n `\\x1b[2mws://localhost:${port}${WS_PATH}\\x1b[0m` +\n ` mode=${claudeMode} toggle=${hotkey}\\n`\n )\n })\n },\n\n transformIndexHtml(): HtmlTagDescriptor[] {\n const clientCode = fs.existsSync(CLIENT_BUNDLE)\n ? fs.readFileSync(CLIENT_BUNDLE, 'utf-8')\n : `console.warn('[koyi] Client bundle not found. Run: pnpm build:client')`\n\n const config = JSON.stringify({ hotkey, position, wsPath: WS_PATH })\n\n return [\n {\n tag: 'script',\n attrs: { type: 'text/javascript', id: '__koyi_script' },\n // Inject the bundle + mount the web component\n children: `${clientCode}\n;(function(){\n if(typeof window==='undefined') return;\n window.__KOYI_CONFIG__=${config};\n if(!customElements.get('koyi-overlay')){\n // The IIFE registers <koyi-overlay> automatically.\n // We just need to ensure it's in the DOM.\n }\n if(!document.querySelector('koyi-overlay')){\n var el=document.createElement('koyi-overlay');\n document.body.appendChild(el);\n }\n})();`,\n injectTo: 'body'\n }\n ]\n }\n }\n\n return [transformPlugin, serverPlugin]\n}\n"],"names":["BASE_SYSTEM_PROMPT","ClaudeBridge","options","apiKey","Anthropic","candidates","name","fullPath","path","fs","systemContext","parts","request","onChunk","onDone","userContent","history","images","systemPrompt","userContentParam","img","messages","h","stream","event","resolve","reject","cliArgs","tempImageFiles","i","ext","tempPath","os","prompt","proc","spawn","lineBuffer","processLine","line","block","chunk","lines","text","code","f","err","WS_PATH","CONTEXT_RADIUS","KoyiServer","httpServer","WebSocketServer","req","socket","head","ws","send","msg","WebSocket","raw","messageId","content","contexts","message","ctx","elDesc","resolved","fileLines","startIdx","endIdx","snippet","truncated","filePath","rel","candidate","_dirname","fileURLToPath","CLIENT_BUNDLE","KoyiPlugin","claudeMode","model","hotkey","position","autoApply","projectRoot","_","command","config","id","isJsTypeFile","normalizePath","fileType","transformCode","server","address","port","clientCode"],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ChatRole, ImageAttachment } from '../shared/types.js';
|
|
2
|
+
export interface ClaudeBridgeOptions {
|
|
3
|
+
mode: 'api' | 'cli';
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
model?: string;
|
|
7
|
+
/**
|
|
8
|
+
* When true, code changes suggested by Claude are applied to disk automatically
|
|
9
|
+
* without requiring user confirmation.
|
|
10
|
+
* - CLI mode: adds --dangerously-skip-permissions flag
|
|
11
|
+
* - API mode: the system prompt instructs Claude to output changes in a
|
|
12
|
+
* machine-readable patch format that the server writes to disk
|
|
13
|
+
*/
|
|
14
|
+
autoApply?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface StreamRequest {
|
|
17
|
+
userContent: string;
|
|
18
|
+
/** Pre-built context string from selected DOM elements */
|
|
19
|
+
systemContext: string;
|
|
20
|
+
history: Array<{
|
|
21
|
+
role: ChatRole;
|
|
22
|
+
content: string;
|
|
23
|
+
}>;
|
|
24
|
+
/** Images the user attached to this message */
|
|
25
|
+
images?: ImageAttachment[];
|
|
26
|
+
}
|
|
27
|
+
export declare class ClaudeBridge {
|
|
28
|
+
private client?;
|
|
29
|
+
private options;
|
|
30
|
+
private activeProc?;
|
|
31
|
+
private abortController?;
|
|
32
|
+
/** Content of the project's CLAUDE.md file (empty string if not found) */
|
|
33
|
+
private claudeMdContent;
|
|
34
|
+
/**
|
|
35
|
+
* Claude CLI session ID for the current conversation.
|
|
36
|
+
* Set after the first CLI turn; used with --resume on subsequent turns.
|
|
37
|
+
* Cleared when the conversation resets (history.length === 0) or on abort.
|
|
38
|
+
*/
|
|
39
|
+
private cliSessionId?;
|
|
40
|
+
constructor(options: ClaudeBridgeOptions);
|
|
41
|
+
/**
|
|
42
|
+
* Read CLAUDE.md from the project root.
|
|
43
|
+
* Checks multiple candidate paths in priority order.
|
|
44
|
+
*/
|
|
45
|
+
private readClaudeMd;
|
|
46
|
+
/**
|
|
47
|
+
* Build the complete system prompt, merging in order:
|
|
48
|
+
* 1. Base Koyi instructions
|
|
49
|
+
* 2. Project-level CLAUDE.md (if present)
|
|
50
|
+
* 3. DOM element context for the current request (if present)
|
|
51
|
+
*/
|
|
52
|
+
private buildSystemPrompt;
|
|
53
|
+
stream(request: StreamRequest, onChunk: (text: string) => void, onDone: () => void): Promise<void>;
|
|
54
|
+
abort(): void;
|
|
55
|
+
private streamViaAPI;
|
|
56
|
+
private streamViaCLI;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=claude-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-bridge.d.ts","sourceRoot":"","sources":["../../src/node/claude-bridge.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEnE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,KAAK,GAAG,KAAK,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnD,+CAA+C;IAC/C,MAAM,CAAC,EAAE,eAAe,EAAE,CAAA;CAC3B;AAgBD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,CAAW;IAC1B,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,UAAU,CAAC,CAAc;IACjC,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,0EAA0E;IAC1E,OAAO,CAAC,eAAe,CAAQ;IAC/B;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAAC,CAAQ;gBAEjB,OAAO,EAAE,mBAAmB;IAmBxC;;;OAGG;IACH,OAAO,CAAC,YAAY;IAoBpB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAmBnB,MAAM,CACV,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAC/B,MAAM,EAAE,MAAM,IAAI,GACjB,OAAO,CAAC,IAAI,CAAC;IAWhB,KAAK,IAAI,IAAI;YAUC,YAAY;IAmE1B,OAAO,CAAC,YAAY;CA0KrB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vite-plugin-koyi — main plugin entry point
|
|
3
|
+
*
|
|
4
|
+
* What this plugin does:
|
|
5
|
+
* 1. Uses @code-inspector/core to inject `data-insp-path` attributes into every
|
|
6
|
+
* JSX/Vue/Svelte element during the Vite transform step (build-time).
|
|
7
|
+
* 2. Starts a WebSocket server on the Vite dev server that bridges the browser
|
|
8
|
+
* overlay to the local Claude Code CLI or Anthropic API.
|
|
9
|
+
* 3. Injects the pre-built browser overlay (dist/client.iife.js) into every
|
|
10
|
+
* HTML page served by the dev server.
|
|
11
|
+
*
|
|
12
|
+
* Usage in vite.config.ts:
|
|
13
|
+
* import { KoyiPlugin } from 'vite-plugin-koyi'
|
|
14
|
+
* export default defineConfig({
|
|
15
|
+
* plugins: [KoyiPlugin({ claudeMode: 'cli' })]
|
|
16
|
+
* })
|
|
17
|
+
*/
|
|
18
|
+
import type { Plugin } from 'vite';
|
|
19
|
+
export interface KoyiOptions {
|
|
20
|
+
/**
|
|
21
|
+
* How to connect to Claude:
|
|
22
|
+
* - `'cli'` (default) — spawns the local `claude` CLI (Claude Code).
|
|
23
|
+
* Requires: `npm install -g @anthropic-ai/claude-code`
|
|
24
|
+
* - `'api'` — uses the Anthropic SDK with an API key.
|
|
25
|
+
*/
|
|
26
|
+
claudeMode?: 'cli' | 'api';
|
|
27
|
+
/**
|
|
28
|
+
* Anthropic API key. Required when `claudeMode` is `'api'`.
|
|
29
|
+
* Falls back to `process.env.ANTHROPIC_API_KEY`.
|
|
30
|
+
*/
|
|
31
|
+
apiKey?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Claude model to use (only for `'api'` mode).
|
|
34
|
+
* @default 'claude-opus-4-5'
|
|
35
|
+
*/
|
|
36
|
+
model?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Keyboard shortcut to toggle the Koyi panel.
|
|
39
|
+
* Format: modifier+key, e.g. 'ctrl+shift+k', 'alt+k'
|
|
40
|
+
* @default 'ctrl+shift+k'
|
|
41
|
+
*/
|
|
42
|
+
hotkey?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Initial position of the panel.
|
|
45
|
+
* @default { x: 'right', y: 'bottom' }
|
|
46
|
+
*/
|
|
47
|
+
position?: {
|
|
48
|
+
x?: 'left' | 'right';
|
|
49
|
+
y?: 'top' | 'bottom';
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* When `true`, code changes suggested by Claude are applied to the project
|
|
53
|
+
* files **automatically** without requiring any confirmation.
|
|
54
|
+
*
|
|
55
|
+
* - CLI mode: passes `--dangerously-skip-permissions` to the `claude` CLI so
|
|
56
|
+
* Claude Code can write/edit files freely.
|
|
57
|
+
* - API mode: instructs Claude to emit complete file contents, which the
|
|
58
|
+
* server writes to disk automatically.
|
|
59
|
+
*
|
|
60
|
+
* @default true
|
|
61
|
+
*/
|
|
62
|
+
autoApply?: boolean;
|
|
63
|
+
}
|
|
64
|
+
export declare function KoyiPlugin(options?: KoyiOptions): Plugin[];
|
|
65
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAoC,MAAM,MAAM,CAAA;AAwBpE,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK,CAAA;IAE1B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,QAAQ,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,CAAC,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;KAAE,CAAA;IAEzD;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAID,wBAAgB,UAAU,CAAC,OAAO,GAAE,WAAgB,GAAG,MAAM,EAAE,CAwH9D"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Server } from 'http';
|
|
2
|
+
export declare const WS_PATH = "/__koyi_ws";
|
|
3
|
+
export interface KoyiServerOptions {
|
|
4
|
+
projectRoot: string;
|
|
5
|
+
claudeMode: 'api' | 'cli';
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
model?: string;
|
|
8
|
+
autoApply?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare class KoyiServer {
|
|
11
|
+
private wss;
|
|
12
|
+
private bridge;
|
|
13
|
+
private projectRoot;
|
|
14
|
+
constructor(httpServer: Server, options: KoyiServerOptions);
|
|
15
|
+
private handleConnection;
|
|
16
|
+
/**
|
|
17
|
+
* Build a rich textual context block for Claude from selected DOM elements.
|
|
18
|
+
* For each element, reads the relevant source snippet from disk.
|
|
19
|
+
*/
|
|
20
|
+
private buildSystemContext;
|
|
21
|
+
/** Resolve a (possibly relative) file path to an absolute path on disk */
|
|
22
|
+
private resolveFilePath;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/node/server.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAmB,MAAM,EAAE,MAAM,MAAM,CAAA;AAUnD,eAAO,MAAM,OAAO,eAAe,CAAA;AAKnC,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,KAAK,GAAG,KAAK,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAQ;gBAEf,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB;IAuB1D,OAAO,CAAC,gBAAgB;IA6CxB;;;OAGG;YACW,kBAAkB;IA2DhC,0EAA0E;IAC1E,OAAO,CAAC,eAAe;CASxB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types between the Node.js plugin server and the browser client overlay.
|
|
3
|
+
* Keep this file free of any Node.js or browser-specific imports.
|
|
4
|
+
*/
|
|
5
|
+
/** Info about a DOM element selected by the user for context */
|
|
6
|
+
export interface DomContext {
|
|
7
|
+
/** Source file path (absolute or relative to project root) */
|
|
8
|
+
filePath: string;
|
|
9
|
+
/** Line number (1-based) */
|
|
10
|
+
line: number;
|
|
11
|
+
/** Column number (1-based) */
|
|
12
|
+
column: number;
|
|
13
|
+
/** HTML tag name, e.g. "div", "button" */
|
|
14
|
+
tagName: string;
|
|
15
|
+
/** element.id */
|
|
16
|
+
id?: string;
|
|
17
|
+
/** element.className */
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Truncated outerHTML (max 500 chars) */
|
|
20
|
+
outerHTML: string;
|
|
21
|
+
}
|
|
22
|
+
/** An image the user attached to a message */
|
|
23
|
+
export interface ImageAttachment {
|
|
24
|
+
/** Original filename, e.g. "screenshot.png" */
|
|
25
|
+
name: string;
|
|
26
|
+
/** MIME type, e.g. "image/png" */
|
|
27
|
+
mimeType: string;
|
|
28
|
+
/** Pure base64-encoded image data (no data-URL prefix) */
|
|
29
|
+
base64: string;
|
|
30
|
+
}
|
|
31
|
+
export type ChatRole = 'user' | 'assistant';
|
|
32
|
+
export interface ChatMessage {
|
|
33
|
+
id: string;
|
|
34
|
+
role: ChatRole;
|
|
35
|
+
/** Full text content (may be built up incrementally during streaming) */
|
|
36
|
+
content: string;
|
|
37
|
+
/** DOM elements attached as context for this message */
|
|
38
|
+
contexts?: DomContext[];
|
|
39
|
+
/** Images attached to this message */
|
|
40
|
+
images?: ImageAttachment[];
|
|
41
|
+
timestamp: number;
|
|
42
|
+
/** True while the assistant is still streaming a response */
|
|
43
|
+
isStreaming?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** Messages sent from the browser Client → Node.js Server */
|
|
46
|
+
export type ClientMessage = {
|
|
47
|
+
type: 'send_message';
|
|
48
|
+
messageId: string;
|
|
49
|
+
content: string;
|
|
50
|
+
/** DOM contexts the user attached to this message */
|
|
51
|
+
contexts: DomContext[];
|
|
52
|
+
/** Trimmed conversation history for multi-turn context */
|
|
53
|
+
history: Array<{
|
|
54
|
+
role: ChatRole;
|
|
55
|
+
content: string;
|
|
56
|
+
}>;
|
|
57
|
+
/** Images attached to this message */
|
|
58
|
+
images?: ImageAttachment[];
|
|
59
|
+
} | {
|
|
60
|
+
type: 'abort';
|
|
61
|
+
messageId: string;
|
|
62
|
+
};
|
|
63
|
+
/** Messages sent from the Node.js Server → browser Client */
|
|
64
|
+
export type ServerMessage = {
|
|
65
|
+
type: 'ready';
|
|
66
|
+
} | {
|
|
67
|
+
type: 'stream_start';
|
|
68
|
+
messageId: string;
|
|
69
|
+
} | {
|
|
70
|
+
type: 'stream_chunk';
|
|
71
|
+
messageId: string;
|
|
72
|
+
text: string;
|
|
73
|
+
} | {
|
|
74
|
+
type: 'stream_end';
|
|
75
|
+
messageId: string;
|
|
76
|
+
} | {
|
|
77
|
+
type: 'stream_error';
|
|
78
|
+
messageId: string;
|
|
79
|
+
error: string;
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,gEAAgE;AAChE,MAAM,WAAW,UAAU;IACzB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAA;IAChB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB;IACjB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAA;CAClB;AAID,8CAA8C;AAC9C,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAA;IACZ,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAA;CACf;AAID,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAA;AAE3C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,QAAQ,CAAA;IACd,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAA;IACf,wDAAwD;IACxD,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAA;IACvB,sCAAsC;IACtC,MAAM,CAAC,EAAE,eAAe,EAAE,CAAA;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAID,6DAA6D;AAC7D,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,cAAc,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,QAAQ,EAAE,UAAU,EAAE,CAAA;IACtB,0DAA0D;IAC1D,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnD,sCAAsC;IACtC,MAAM,CAAC,EAAE,eAAe,EAAE,CAAA;CAC3B,GACD;IACE,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAEL,6DAA6D;AAC7D,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-koyi",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vite plugin for AI-assisted frontend development — live DOM context picker + Claude Code / Anthropic API integration",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"vite",
|
|
7
|
+
"vite-plugin",
|
|
8
|
+
"ai",
|
|
9
|
+
"claude",
|
|
10
|
+
"frontend",
|
|
11
|
+
"developer-tools",
|
|
12
|
+
"dom-inspector",
|
|
13
|
+
"anthropic"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.0.0"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"require": "./dist/index.cjs",
|
|
27
|
+
"types": "./dist/index.d.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "pnpm build:client && pnpm build:plugin && pnpm build:types",
|
|
37
|
+
"build:client": "vite build --config vite.client.config.ts",
|
|
38
|
+
"build:plugin": "vite build --config vite.plugin.config.ts",
|
|
39
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
40
|
+
"prepublishOnly": "pnpm build",
|
|
41
|
+
"dev:client": "vite build --config vite.client.config.ts --watch",
|
|
42
|
+
"typecheck": "tsc --noEmit"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"vite": ">=4.0.0"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@anthropic-ai/sdk": "^0.36.3",
|
|
49
|
+
"@code-inspector/core": "^1.4.2",
|
|
50
|
+
"react-markdown": "^10.1.0",
|
|
51
|
+
"react-syntax-highlighter": "^16.1.1",
|
|
52
|
+
"remark-gfm": "^4.0.1",
|
|
53
|
+
"ws": "^8.18.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.14.2",
|
|
57
|
+
"@types/react": "^18.3.3",
|
|
58
|
+
"@types/react-dom": "^18.3.0",
|
|
59
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
|
60
|
+
"@types/ws": "^8.5.10",
|
|
61
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
62
|
+
"react": "^18.3.1",
|
|
63
|
+
"react-dom": "^18.3.1",
|
|
64
|
+
"typescript": "^5.4.5",
|
|
65
|
+
"vite": "^5.4.0"
|
|
66
|
+
}
|
|
67
|
+
}
|