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/dist/index.cjs ADDED
@@ -0,0 +1,65 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("path"),y=require("fs"),w=require("url"),C=require("@code-inspector/core"),x=require("ws"),k=require("child_process"),j=require("os"),A=require("@anthropic-ai/sdk");var _=typeof document<"u"?document.currentScript:null;const T=`You are Koyi, an expert AI assistant specialized in frontend development.
2
+ You help developers understand, debug, and improve their UI components in real-time.
3
+
4
+ When the user provides DOM element context (selected from the live browser view), you will:
5
+ 1. Analyze the component source code at that location
6
+ 2. Understand the element's role in the UI
7
+ 3. Provide precise, actionable suggestions
8
+
9
+ Guidelines:
10
+ - Be concise and focused on the specific element/issue
11
+ - Always show code examples with correct language syntax highlighting
12
+ - Reference specific file paths and line numbers when discussing code
13
+ - Prefer modern, idiomatic patterns for the detected framework`;class E{constructor(t){if(this.options=t,this.claudeMdContent=this.readClaudeMd(),this.claudeMdContent&&console.log("[koyi] Loaded CLAUDE.md project instructions"),t.mode==="api"){const s=t.apiKey??process.env.ANTHROPIC_API_KEY;s||console.warn("[koyi] No ANTHROPIC_API_KEY found. Set it in your environment or pass apiKey to KoyiPlugin()."),this.client=new A({apiKey:s})}}readClaudeMd(){const t=["CLAUDE.md","claude.md",".claude/CLAUDE.md",".claude/claude.md"];for(const s of t){const e=f.resolve(this.options.projectRoot,s);if(y.existsSync(e))try{return y.readFileSync(e,"utf-8").trim()}catch{}}return""}buildSystemPrompt(t){const s=[T];return this.claudeMdContent&&s.push(`---
14
+
15
+ ## Project Instructions (from CLAUDE.md)
16
+
17
+ `+this.claudeMdContent),t&&s.push(`---
18
+
19
+ ## Current Context (selected DOM elements)
20
+
21
+ `+t),s.join(`
22
+
23
+ `)}async stream(t,s,e){return this.options.mode==="cli"?(t.history.length===0&&(this.cliSessionId=void 0),this.streamViaCLI(t,s,e)):this.streamViaAPI(t,s,e)}abort(){this.abortController?.abort(),this.activeProc?.kill("SIGTERM"),this.activeProc=void 0,this.cliSessionId=void 0}async streamViaAPI(t,s,e){if(!this.client)throw new Error('Anthropic client not initialized. Provide an API key or use claudeMode: "cli".');const{userContent:c,systemContext:d,history:a,images:p}=t,u=this.buildSystemPrompt(d),m=p?.length?[...p.map(o=>({type:"image",source:{type:"base64",media_type:o.mimeType,data:o.base64}})),{type:"text",text:c}]:c,i=[...a.map(o=>({role:o.role,content:o.content})),{role:"user",content:m}];this.abortController=new AbortController;const n=this.client.messages.stream({model:this.options.model??"claude-opus-4-5",max_tokens:8096,system:u,messages:i},{signal:this.abortController.signal});for await(const o of n)o.type==="content_block_delta"&&o.delta.type==="text_delta"&&s(o.delta.text);e(),this.abortController=void 0}streamViaCLI(t,s,e){return new Promise((c,d)=>{const{userContent:a,systemContext:p}=t,u=[];this.cliSessionId&&(u.push("--resume",this.cliSessionId),console.log(`[koyi] Resuming CLI session ${this.cliSessionId}`)),u.push("--print","--output-format","stream-json","--verbose"),this.options.autoApply&&u.push("--dangerously-skip-permissions");const m=[];if(t.images?.length)for(let r=0;r<t.images.length;r++){const l=t.images[r],g=(l.mimeType.split("/")[1]??"png").replace("jpeg","jpg"),S=f.join(j.tmpdir(),`koyi-img-${Date.now()}-${r}.${g}`);y.writeFileSync(S,Buffer.from(l.base64,"base64")),m.push(S),u.push("--image",S)}let i;if(this.cliSessionId){const r=[];p&&r.push(`## Current Context (selected DOM elements)
24
+
25
+ `+p,"---"),r.push(a),i=r.join(`
26
+
27
+ `)}else i=[this.buildSystemPrompt(p),`---
28
+
29
+ ## User Request
30
+
31
+ ${a}`].join(`
32
+
33
+ `);u.push(i);const n=k.spawn("claude",u,{cwd:this.options.projectRoot,env:{...process.env},stdio:["ignore","pipe","pipe"]});this.activeProc=n;let o="";const h=r=>{if(r.trim())try{const l=JSON.parse(r);if(l.session_id&&(this.cliSessionId=l.session_id),l.type==="assistant"&&l.message?.content)for(const g of l.message.content)g.type==="text"&&g.text&&s(g.text)}catch{s(r+`
34
+ `)}};n.stdout.on("data",r=>{o+=r.toString("utf-8");const l=o.split(`
35
+ `);o=l.pop()??"",l.forEach(h)}),n.stderr.on("data",r=>{const l=r.toString("utf-8");!l.includes("Bypassing Permissions")&&!l.includes("Human:")&&console.warn("[koyi] claude stderr:",l.trim())}),n.on("close",r=>{o.trim()&&h(o),o="";for(const l of m)try{y.unlinkSync(l)}catch{}this.activeProc=void 0,r===0||r===null?(e(),c()):d(new Error(`Claude CLI exited with code ${r}`))}),n.on("error",r=>{this.activeProc=void 0,r.code==="ENOENT"?d(new Error('[koyi] `claude` command not found.\n Install Claude Code with: npm install -g @anthropic-ai/claude-code\n Or switch to API mode: KoyiPlugin({ claudeMode: "api", apiKey: "sk-..." })')):d(r)})})}}const P="/__koyi_ws",b=25;class M{constructor(t,s){this.projectRoot=s.projectRoot,this.bridge=new E({mode:s.claudeMode,apiKey:s.apiKey,projectRoot:s.projectRoot,model:s.model,autoApply:s.autoApply}),this.wss=new x.WebSocketServer({noServer:!0}),t.on("upgrade",(e,c,d)=>{e.url===P&&this.wss.handleUpgrade(e,c,d,a=>{this.wss.emit("connection",a,e),this.handleConnection(a)})})}handleConnection(t){const s=e=>{t.readyState===x.WebSocket.OPEN&&t.send(JSON.stringify(e))};s({type:"ready"}),t.on("message",async e=>{let c;try{c=JSON.parse(e.toString())}catch{return}if(c.type==="send_message"){const{messageId:d,content:a,contexts:p,history:u,images:m}=c,i=await this.buildSystemContext(p);s({type:"stream_start",messageId:d});try{await this.bridge.stream({userContent:a,systemContext:i,history:u,images:m},n=>s({type:"stream_chunk",messageId:d,text:n}),()=>s({type:"stream_end",messageId:d}))}catch(n){const o=n instanceof Error?n.message:String(n);console.error("[koyi] Bridge error:",o),s({type:"stream_error",messageId:d,error:o})}}else c.type==="abort"&&this.bridge.abort()}),t.on("error",e=>{console.error("[koyi] WebSocket error:",e.message)})}async buildSystemContext(t){if(t.length===0)return"";const s=[];for(const e of t){const c=[],d=[`<${e.tagName}`,e.id?` id="${e.id}"`:"",e.className?` class="${e.className}"`:"",">"].join("");if(c.push(`### Element: ${d}`),e.filePath){c.push(`**Source:** \`${e.filePath}:${e.line}:${e.column}\``);const a=this.resolveFilePath(e.filePath);if(a)try{const u=y.readFileSync(a,"utf-8").split(`
36
+ `),m=Math.max(0,e.line-1-b),i=Math.min(u.length,e.line-1+b),n=u.slice(m,i).join(`
37
+ `),o=f.extname(a).slice(1)||"tsx";c.push(`
38
+ \`\`\`${o}
39
+ // ${e.filePath} (lines ${m+1}–${i})
40
+ ${n}
41
+ \`\`\``)}catch{c.push("_(Could not read source file)_")}}if(e.outerHTML){const a=e.outerHTML.length>600?e.outerHTML.slice(0,600)+"…":e.outerHTML;c.push(`
42
+ **HTML:**
43
+ \`\`\`html
44
+ ${a}
45
+ \`\`\``)}s.push(c.join(`
46
+ `))}return s.join(`
47
+
48
+ ---
49
+
50
+ `)}resolveFilePath(t){if(f.isAbsolute(t)&&y.existsSync(t))return t;const s=t.startsWith("/")?t.slice(1):t,e=f.resolve(this.projectRoot,s);return y.existsSync(e)?e:null}}const R=typeof __dirname<"u"?__dirname:f.dirname(w.fileURLToPath(typeof document>"u"?require("url").pathToFileURL(__filename).href:_&&_.tagName.toUpperCase()==="SCRIPT"&&_.src||new URL("index.cjs",document.baseURI).href)),I=f.resolve(R,"client.iife.js");function $(v={}){const{claudeMode:t="cli",apiKey:s=process.env.ANTHROPIC_API_KEY,model:e,hotkey:c="ctrl+shift+k",position:d={x:"right",y:"bottom"},autoApply:a=!0}=v;let p=process.cwd();return[{name:"vite-plugin-koyi:transform",enforce:"pre",apply:(i,{command:n})=>n==="serve",configResolved(i){p=i.root},transform(i,n){if(n.includes("node_modules")||n.includes("/__koyi")||!C.isJsTypeFile(n)&&!n.endsWith(".vue")&&!n.endsWith(".svelte"))return i;const o=C.normalizePath(n.split("?")[0]);let h="";if(o.endsWith(".vue")?h="vue":o.endsWith(".svelte")?h="svelte":o.endsWith(".tsx")||o.endsWith(".jsx")?h="jsx":(o.endsWith(".ts")||o.endsWith(".js"))&&(h="js"),!h)return i;try{return C.transformCode({content:i,filePath:o,fileType:h,pathType:"absolute",escapeTags:["koyi-overlay"]})}catch{return i}}},{name:"vite-plugin-koyi:server",apply:(i,{command:n})=>n==="serve",configureServer(i){i.httpServer&&(new M(i.httpServer,{projectRoot:p,claudeMode:t,apiKey:s,model:e,autoApply:a}),i.httpServer.once("listening",()=>{const n=i.httpServer.address(),o=typeof n=="object"&&n?n.port:"?";console.log(`
51
+ \x1B[36m⚡ Koyi AI\x1B[0m \x1B[2mws://localhost:${o}${P}\x1B[0m mode=${t} toggle=${c}
52
+ `)}))},transformIndexHtml(){const i=y.existsSync(I)?y.readFileSync(I,"utf-8"):"console.warn('[koyi] Client bundle not found. Run: pnpm build:client')",n=JSON.stringify({hotkey:c,position:d,wsPath:P});return[{tag:"script",attrs:{type:"text/javascript",id:"__koyi_script"},children:`${i}
53
+ ;(function(){
54
+ if(typeof window==='undefined') return;
55
+ window.__KOYI_CONFIG__=${n};
56
+ if(!customElements.get('koyi-overlay')){
57
+ // The IIFE registers <koyi-overlay> automatically.
58
+ // We just need to ensure it's in the DOM.
59
+ }
60
+ if(!document.querySelector('koyi-overlay')){
61
+ var el=document.createElement('koyi-overlay');
62
+ document.body.appendChild(el);
63
+ }
64
+ })();`,injectTo:"body"}]}}]}exports.KoyiPlugin=$;
65
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","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","_documentCurrentScript","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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"}
package/dist/index.js ADDED
@@ -0,0 +1,392 @@
1
+ import f from "path";
2
+ import y from "fs";
3
+ import { fileURLToPath as x } from "url";
4
+ import { isJsTypeFile as b, normalizePath as I, transformCode as w } from "@code-inspector/core";
5
+ import { WebSocketServer as k, WebSocket as A } from "ws";
6
+ import { spawn as j } from "child_process";
7
+ import E from "os";
8
+ import T from "@anthropic-ai/sdk";
9
+ const $ = `You are Koyi, an expert AI assistant specialized in frontend development.
10
+ You help developers understand, debug, and improve their UI components in real-time.
11
+
12
+ When the user provides DOM element context (selected from the live browser view), you will:
13
+ 1. Analyze the component source code at that location
14
+ 2. Understand the element's role in the UI
15
+ 3. Provide precise, actionable suggestions
16
+
17
+ Guidelines:
18
+ - Be concise and focused on the specific element/issue
19
+ - Always show code examples with correct language syntax highlighting
20
+ - Reference specific file paths and line numbers when discussing code
21
+ - Prefer modern, idiomatic patterns for the detected framework`;
22
+ class M {
23
+ constructor(t) {
24
+ if (this.options = t, this.claudeMdContent = this.readClaudeMd(), this.claudeMdContent && console.log("[koyi] Loaded CLAUDE.md project instructions"), t.mode === "api") {
25
+ const o = t.apiKey ?? process.env.ANTHROPIC_API_KEY;
26
+ o || console.warn(
27
+ "[koyi] No ANTHROPIC_API_KEY found. Set it in your environment or pass apiKey to KoyiPlugin()."
28
+ ), this.client = new T({ apiKey: o });
29
+ }
30
+ }
31
+ /**
32
+ * Read CLAUDE.md from the project root.
33
+ * Checks multiple candidate paths in priority order.
34
+ */
35
+ readClaudeMd() {
36
+ const t = [
37
+ "CLAUDE.md",
38
+ "claude.md",
39
+ ".claude/CLAUDE.md",
40
+ ".claude/claude.md"
41
+ ];
42
+ for (const o of t) {
43
+ const e = f.resolve(this.options.projectRoot, o);
44
+ if (y.existsSync(e))
45
+ try {
46
+ return y.readFileSync(e, "utf-8").trim();
47
+ } catch {
48
+ }
49
+ }
50
+ return "";
51
+ }
52
+ /**
53
+ * Build the complete system prompt, merging in order:
54
+ * 1. Base Koyi instructions
55
+ * 2. Project-level CLAUDE.md (if present)
56
+ * 3. DOM element context for the current request (if present)
57
+ */
58
+ buildSystemPrompt(t) {
59
+ const o = [$];
60
+ return this.claudeMdContent && o.push(
61
+ `---
62
+
63
+ ## Project Instructions (from CLAUDE.md)
64
+
65
+ ` + this.claudeMdContent
66
+ ), t && o.push(
67
+ `---
68
+
69
+ ## Current Context (selected DOM elements)
70
+
71
+ ` + t
72
+ ), o.join(`
73
+
74
+ `);
75
+ }
76
+ async stream(t, o, e) {
77
+ return this.options.mode === "cli" ? (t.history.length === 0 && (this.cliSessionId = void 0), this.streamViaCLI(t, o, e)) : this.streamViaAPI(t, o, e);
78
+ }
79
+ abort() {
80
+ this.abortController?.abort(), this.activeProc?.kill("SIGTERM"), this.activeProc = void 0, this.cliSessionId = void 0;
81
+ }
82
+ // ─── Anthropic SDK (API mode) ───────────────────────────────────────────────
83
+ async streamViaAPI(t, o, e) {
84
+ if (!this.client)
85
+ throw new Error(
86
+ 'Anthropic client not initialized. Provide an API key or use claudeMode: "cli".'
87
+ );
88
+ const { userContent: a, systemContext: d, history: c, images: p } = t, m = this.buildSystemPrompt(d), u = p?.length ? [
89
+ ...p.map((n) => ({
90
+ type: "image",
91
+ source: {
92
+ type: "base64",
93
+ media_type: n.mimeType,
94
+ data: n.base64
95
+ }
96
+ })),
97
+ { type: "text", text: a }
98
+ ] : a, i = [
99
+ ...c.map((n) => ({ role: n.role, content: n.content })),
100
+ { role: "user", content: u }
101
+ ];
102
+ this.abortController = new AbortController();
103
+ const s = this.client.messages.stream(
104
+ {
105
+ model: this.options.model ?? "claude-opus-4-5",
106
+ max_tokens: 8096,
107
+ system: m,
108
+ messages: i
109
+ },
110
+ { signal: this.abortController.signal }
111
+ );
112
+ for await (const n of s)
113
+ n.type === "content_block_delta" && n.delta.type === "text_delta" && o(n.delta.text);
114
+ e(), this.abortController = void 0;
115
+ }
116
+ // ─── Claude Code CLI (cli mode) ─────────────────────────────────────────────
117
+ streamViaCLI(t, o, e) {
118
+ return new Promise((a, d) => {
119
+ const { userContent: c, systemContext: p } = t, m = [];
120
+ this.cliSessionId && (m.push("--resume", this.cliSessionId), console.log(`[koyi] Resuming CLI session ${this.cliSessionId}`)), m.push("--print", "--output-format", "stream-json", "--verbose"), this.options.autoApply && m.push("--dangerously-skip-permissions");
121
+ const u = [];
122
+ if (t.images?.length)
123
+ for (let r = 0; r < t.images.length; r++) {
124
+ const l = t.images[r], g = (l.mimeType.split("/")[1] ?? "png").replace(
125
+ "jpeg",
126
+ "jpg"
127
+ ), S = f.join(
128
+ E.tmpdir(),
129
+ `koyi-img-${Date.now()}-${r}.${g}`
130
+ );
131
+ y.writeFileSync(S, Buffer.from(l.base64, "base64")), u.push(S), m.push("--image", S);
132
+ }
133
+ let i;
134
+ if (this.cliSessionId) {
135
+ const r = [];
136
+ p && r.push(
137
+ `## Current Context (selected DOM elements)
138
+
139
+ ` + p,
140
+ "---"
141
+ ), r.push(c), i = r.join(`
142
+
143
+ `);
144
+ } else
145
+ i = [
146
+ this.buildSystemPrompt(p),
147
+ `---
148
+
149
+ ## User Request
150
+
151
+ ${c}`
152
+ ].join(`
153
+
154
+ `);
155
+ m.push(i);
156
+ const s = j("claude", m, {
157
+ cwd: this.options.projectRoot,
158
+ env: { ...process.env },
159
+ stdio: ["ignore", "pipe", "pipe"]
160
+ });
161
+ this.activeProc = s;
162
+ let n = "";
163
+ const h = (r) => {
164
+ if (r.trim())
165
+ try {
166
+ const l = JSON.parse(r);
167
+ if (l.session_id && (this.cliSessionId = l.session_id), l.type === "assistant" && l.message?.content)
168
+ for (const g of l.message.content)
169
+ g.type === "text" && g.text && o(g.text);
170
+ } catch {
171
+ o(r + `
172
+ `);
173
+ }
174
+ };
175
+ s.stdout.on("data", (r) => {
176
+ n += r.toString("utf-8");
177
+ const l = n.split(`
178
+ `);
179
+ n = l.pop() ?? "", l.forEach(h);
180
+ }), s.stderr.on("data", (r) => {
181
+ const l = r.toString("utf-8");
182
+ !l.includes("Bypassing Permissions") && !l.includes("Human:") && console.warn("[koyi] claude stderr:", l.trim());
183
+ }), s.on("close", (r) => {
184
+ n.trim() && h(n), n = "";
185
+ for (const l of u)
186
+ try {
187
+ y.unlinkSync(l);
188
+ } catch {
189
+ }
190
+ this.activeProc = void 0, r === 0 || r === null ? (e(), a()) : d(new Error(`Claude CLI exited with code ${r}`));
191
+ }), s.on("error", (r) => {
192
+ this.activeProc = void 0, r.code === "ENOENT" ? d(
193
+ new Error(
194
+ '[koyi] `claude` command not found.\n Install Claude Code with: npm install -g @anthropic-ai/claude-code\n Or switch to API mode: KoyiPlugin({ claudeMode: "api", apiKey: "sk-..." })'
195
+ )
196
+ ) : d(r);
197
+ });
198
+ });
199
+ }
200
+ }
201
+ const C = "/__koyi_ws", _ = 25;
202
+ class R {
203
+ constructor(t, o) {
204
+ this.projectRoot = o.projectRoot, this.bridge = new M({
205
+ mode: o.claudeMode,
206
+ apiKey: o.apiKey,
207
+ projectRoot: o.projectRoot,
208
+ model: o.model,
209
+ autoApply: o.autoApply
210
+ }), this.wss = new k({ noServer: !0 }), t.on("upgrade", (e, a, d) => {
211
+ e.url === C && this.wss.handleUpgrade(e, a, d, (c) => {
212
+ this.wss.emit("connection", c, e), this.handleConnection(c);
213
+ });
214
+ });
215
+ }
216
+ handleConnection(t) {
217
+ const o = (e) => {
218
+ t.readyState === A.OPEN && t.send(JSON.stringify(e));
219
+ };
220
+ o({ type: "ready" }), t.on("message", async (e) => {
221
+ let a;
222
+ try {
223
+ a = JSON.parse(e.toString());
224
+ } catch {
225
+ return;
226
+ }
227
+ if (a.type === "send_message") {
228
+ const { messageId: d, content: c, contexts: p, history: m, images: u } = a, i = await this.buildSystemContext(p);
229
+ o({ type: "stream_start", messageId: d });
230
+ try {
231
+ await this.bridge.stream(
232
+ { userContent: c, systemContext: i, history: m, images: u },
233
+ (s) => o({ type: "stream_chunk", messageId: d, text: s }),
234
+ () => o({ type: "stream_end", messageId: d })
235
+ );
236
+ } catch (s) {
237
+ const n = s instanceof Error ? s.message : String(s);
238
+ console.error("[koyi] Bridge error:", n), o({ type: "stream_error", messageId: d, error: n });
239
+ }
240
+ } else a.type === "abort" && this.bridge.abort();
241
+ }), t.on("error", (e) => {
242
+ console.error("[koyi] WebSocket error:", e.message);
243
+ });
244
+ }
245
+ /**
246
+ * Build a rich textual context block for Claude from selected DOM elements.
247
+ * For each element, reads the relevant source snippet from disk.
248
+ */
249
+ async buildSystemContext(t) {
250
+ if (t.length === 0) return "";
251
+ const o = [];
252
+ for (const e of t) {
253
+ const a = [], d = [
254
+ `<${e.tagName}`,
255
+ e.id ? ` id="${e.id}"` : "",
256
+ e.className ? ` class="${e.className}"` : "",
257
+ ">"
258
+ ].join("");
259
+ if (a.push(`### Element: ${d}`), e.filePath) {
260
+ a.push(`**Source:** \`${e.filePath}:${e.line}:${e.column}\``);
261
+ const c = this.resolveFilePath(e.filePath);
262
+ if (c)
263
+ try {
264
+ const m = y.readFileSync(c, "utf-8").split(`
265
+ `), u = Math.max(0, e.line - 1 - _), i = Math.min(
266
+ m.length,
267
+ e.line - 1 + _
268
+ ), s = m.slice(u, i).join(`
269
+ `), n = f.extname(c).slice(1) || "tsx";
270
+ a.push(
271
+ `
272
+ \`\`\`${n}
273
+ // ${e.filePath} (lines ${u + 1}–${i})
274
+ ${s}
275
+ \`\`\``
276
+ );
277
+ } catch {
278
+ a.push("_(Could not read source file)_");
279
+ }
280
+ }
281
+ if (e.outerHTML) {
282
+ const c = e.outerHTML.length > 600 ? e.outerHTML.slice(0, 600) + "…" : e.outerHTML;
283
+ a.push(`
284
+ **HTML:**
285
+ \`\`\`html
286
+ ${c}
287
+ \`\`\``);
288
+ }
289
+ o.push(a.join(`
290
+ `));
291
+ }
292
+ return o.join(`
293
+
294
+ ---
295
+
296
+ `);
297
+ }
298
+ /** Resolve a (possibly relative) file path to an absolute path on disk */
299
+ resolveFilePath(t) {
300
+ if (f.isAbsolute(t) && y.existsSync(t))
301
+ return t;
302
+ const o = t.startsWith("/") ? t.slice(1) : t, e = f.resolve(this.projectRoot, o);
303
+ return y.existsSync(e) ? e : null;
304
+ }
305
+ }
306
+ const L = typeof __dirname < "u" ? __dirname : f.dirname(x(import.meta.url)), P = f.resolve(L, "client.iife.js");
307
+ function F(v = {}) {
308
+ const {
309
+ claudeMode: t = "cli",
310
+ apiKey: o = process.env.ANTHROPIC_API_KEY,
311
+ model: e,
312
+ hotkey: a = "ctrl+shift+k",
313
+ position: d = { x: "right", y: "bottom" },
314
+ autoApply: c = !0
315
+ } = v;
316
+ let p = process.cwd();
317
+ return [{
318
+ name: "vite-plugin-koyi:transform",
319
+ enforce: "pre",
320
+ apply: (i, { command: s }) => s === "serve",
321
+ configResolved(i) {
322
+ p = i.root;
323
+ },
324
+ transform(i, s) {
325
+ if (s.includes("node_modules") || s.includes("/__koyi") || !b(s) && !s.endsWith(".vue") && !s.endsWith(".svelte"))
326
+ return i;
327
+ const n = I(s.split("?")[0]);
328
+ let h = "";
329
+ if (n.endsWith(".vue") ? h = "vue" : n.endsWith(".svelte") ? h = "svelte" : n.endsWith(".tsx") || n.endsWith(".jsx") ? h = "jsx" : (n.endsWith(".ts") || n.endsWith(".js")) && (h = "js"), !h) return i;
330
+ try {
331
+ return w({
332
+ content: i,
333
+ filePath: n,
334
+ fileType: h,
335
+ // Use absolute paths so the server can read the file directly
336
+ pathType: "absolute",
337
+ // Avoid transforming our own injected overlay elements
338
+ escapeTags: ["koyi-overlay"]
339
+ });
340
+ } catch {
341
+ return i;
342
+ }
343
+ }
344
+ }, {
345
+ name: "vite-plugin-koyi:server",
346
+ apply: (i, { command: s }) => s === "serve",
347
+ configureServer(i) {
348
+ i.httpServer && (new R(i.httpServer, {
349
+ projectRoot: p,
350
+ claudeMode: t,
351
+ apiKey: o,
352
+ model: e,
353
+ autoApply: c
354
+ }), i.httpServer.once("listening", () => {
355
+ const s = i.httpServer.address(), n = typeof s == "object" && s ? s.port : "?";
356
+ console.log(
357
+ `
358
+ \x1B[36m⚡ Koyi AI\x1B[0m \x1B[2mws://localhost:${n}${C}\x1B[0m mode=${t} toggle=${a}
359
+ `
360
+ );
361
+ }));
362
+ },
363
+ transformIndexHtml() {
364
+ const i = y.existsSync(P) ? y.readFileSync(P, "utf-8") : "console.warn('[koyi] Client bundle not found. Run: pnpm build:client')", s = JSON.stringify({ hotkey: a, position: d, wsPath: C });
365
+ return [
366
+ {
367
+ tag: "script",
368
+ attrs: { type: "text/javascript", id: "__koyi_script" },
369
+ // Inject the bundle + mount the web component
370
+ children: `${i}
371
+ ;(function(){
372
+ if(typeof window==='undefined') return;
373
+ window.__KOYI_CONFIG__=${s};
374
+ if(!customElements.get('koyi-overlay')){
375
+ // The IIFE registers <koyi-overlay> automatically.
376
+ // We just need to ensure it's in the DOM.
377
+ }
378
+ if(!document.querySelector('koyi-overlay')){
379
+ var el=document.createElement('koyi-overlay');
380
+ document.body.appendChild(el);
381
+ }
382
+ })();`,
383
+ injectTo: "body"
384
+ }
385
+ ];
386
+ }
387
+ }];
388
+ }
389
+ export {
390
+ F as KoyiPlugin
391
+ };
392
+ //# sourceMappingURL=index.js.map