vite-plugin-koyi 0.1.5 → 0.1.6
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 +24 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +118 -112
- package/dist/index.js.map +1 -1
- package/dist/node/claude-bridge.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("path"),f=require("fs"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("path"),f=require("fs"),k=require("url"),S=require("@code-inspector/core"),b=require("ws"),A=require("child_process"),j=require("os"),T=require("@anthropic-ai/sdk");var C=typeof document<"u"?document.currentScript:null;const M=`You are Koyi, an expert AI assistant specialized in frontend development.
|
|
2
2
|
You help developers understand, debug, and improve their UI components in real-time.
|
|
3
3
|
|
|
4
4
|
When the user provides DOM element context (selected from the live browser view), you will:
|
|
@@ -24,7 +24,7 @@ Whenever you make or suggest any file modifications, you MUST display every chan
|
|
|
24
24
|
|
|
25
25
|
If you apply multiple files, repeat the diff block for each file.
|
|
26
26
|
After the diff(s), briefly summarize in one or two sentences what was changed and why.
|
|
27
|
-
Never silently modify files without showing the diff in your response.`;class
|
|
27
|
+
Never silently modify files without showing the diff in your response.`;class R{constructor(t){if(this.options=t,this.claudeMdContent=this.readClaudeMd(),this.claudeMdContent&&console.log("[koyi] Loaded CLAUDE.md project instructions"),t.mode==="api"){const n=t.apiKey??process.env.ANTHROPIC_API_KEY;n||console.warn("[koyi] No ANTHROPIC_API_KEY found. Set it in your environment or pass apiKey to KoyiPlugin()."),this.client=new T({apiKey:n})}}readClaudeMd(){const t=["CLAUDE.md","claude.md",".claude/CLAUDE.md",".claude/claude.md"];for(const n of t){const e=y.resolve(this.options.projectRoot,n);if(f.existsSync(e))try{return f.readFileSync(e,"utf-8").trim()}catch{}}return""}buildSystemPrompt(t){const n=[M];return this.claudeMdContent&&n.push(`---
|
|
28
28
|
|
|
29
29
|
## Project Instructions (from CLAUDE.md)
|
|
30
30
|
|
|
@@ -34,36 +34,41 @@ Never silently modify files without showing the diff in your response.`;class M{
|
|
|
34
34
|
|
|
35
35
|
`+t),n.join(`
|
|
36
36
|
|
|
37
|
-
`)}async stream(t,n,e){return this.options.mode==="cli"?(t.history.length===0&&(this.cliSessionId=void 0),this.streamViaCLI(t,n,e)):this.streamViaAPI(t,n,e)}abort(){this.abortController?.abort(),this.activeProc?.kill("SIGTERM"),this.activeProc=void 0,this.cliSessionId=void 0}async streamViaAPI(t,n,e){if(!this.client)throw new Error('Anthropic client not initialized. Provide an API key or use claudeMode: "cli".');const{userContent:
|
|
37
|
+
`)}async stream(t,n,e){return this.options.mode==="cli"?(t.history.length===0&&(this.cliSessionId=void 0),this.streamViaCLI(t,n,e)):this.streamViaAPI(t,n,e)}abort(){this.abortController?.abort(),this.activeProc?.kill("SIGTERM"),this.activeProc=void 0,this.cliSessionId=void 0}async streamViaAPI(t,n,e){if(!this.client)throw new Error('Anthropic client not initialized. Provide an API key or use claudeMode: "cli".');const{userContent:a,systemContext:d,history:l,images:m}=t,u=this.buildSystemPrompt(d),h=m?.length?[...m.map(s=>({type:"image",source:{type:"base64",media_type:s.mimeType,data:s.base64}})),{type:"text",text:a}]:a,r=[...l.map(s=>({role:s.role,content:s.content})),{role:"user",content:h}];this.abortController=new AbortController;const o=this.client.messages.stream({model:this.options.model??"claude-opus-4-5",max_tokens:8096,system:u,messages:r},{signal:this.abortController.signal});for await(const s of o)s.type==="content_block_delta"&&s.delta.type==="text_delta"&&n(s.delta.text);e(),this.abortController=void 0}streamViaCLI(t,n,e){return new Promise((a,d)=>{const{userContent:l,systemContext:m}=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 h=[];if(t.images?.length)for(let i=0;i<t.images.length;i++){const c=t.images[i],g=(c.mimeType.split("/")[1]??"png").replace("jpeg","jpg"),P=y.join(j.tmpdir(),`koyi-img-${Date.now()}-${i}.${g}`);f.writeFileSync(P,Buffer.from(c.base64,"base64")),h.push(P)}const r=h.length>0?`
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
## Attached Images
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
`+h.map((i,c)=>`Image ${c+1}: ${i}`).join(`
|
|
42
|
+
`):"";let o;if(this.cliSessionId){const i=[];m&&i.push(`## Current Context (selected DOM elements)
|
|
43
|
+
|
|
44
|
+
`+m,"---"),i.push(l+r),o=i.join(`
|
|
45
|
+
|
|
46
|
+
`)}else o=[this.buildSystemPrompt(m),`---
|
|
42
47
|
|
|
43
48
|
## User Request
|
|
44
49
|
|
|
45
|
-
${
|
|
50
|
+
${l}${r}`].join(`
|
|
46
51
|
|
|
47
|
-
`);u.push(
|
|
48
|
-
`)}};
|
|
49
|
-
`);
|
|
50
|
-
`),
|
|
51
|
-
`),s=y.extname(
|
|
52
|
+
`);u.push(o);const s=A.spawn("claude",u,{cwd:this.options.projectRoot,env:{...process.env},stdio:["ignore","pipe","pipe"]});this.activeProc=s;let p="";const w=i=>{if(i.trim())try{const c=JSON.parse(i);if(c.session_id&&(this.cliSessionId=c.session_id),c.type==="assistant"&&c.message?.content)for(const g of c.message.content)g.type==="text"&&g.text&&n(g.text)}catch{n(i+`
|
|
53
|
+
`)}};s.stdout.on("data",i=>{p+=i.toString("utf-8");const c=p.split(`
|
|
54
|
+
`);p=c.pop()??"",c.forEach(w)}),s.stderr.on("data",i=>{const c=i.toString("utf-8");!c.includes("Bypassing Permissions")&&!c.includes("Human:")&&console.warn("[koyi] claude stderr:",c.trim())}),s.on("close",i=>{p.trim()&&w(p),p="";for(const c of h)try{f.unlinkSync(c)}catch{}this.activeProc=void 0,i===0||i===null?(e(),a()):d(new Error(`Claude CLI exited with code ${i}`))}),s.on("error",i=>{this.activeProc=void 0,i.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(i)})})}}const _="/__koyi_ws",I=25;class E{constructor(t,n){this.projectRoot=n.projectRoot,this.bridge=new R({mode:n.claudeMode,apiKey:n.apiKey,projectRoot:n.projectRoot,model:n.model,autoApply:n.autoApply}),this.wss=new b.WebSocketServer({noServer:!0}),t.on("upgrade",(e,a,d)=>{e.url===_&&this.wss.handleUpgrade(e,a,d,l=>{this.wss.emit("connection",l,e),this.handleConnection(l)})})}handleConnection(t){const n=e=>{t.readyState===b.WebSocket.OPEN&&t.send(JSON.stringify(e))};n({type:"ready"}),t.on("message",async e=>{let a;try{a=JSON.parse(e.toString())}catch{return}if(a.type==="send_message"){const{messageId:d,content:l,contexts:m,history:u,images:h}=a,r=await this.buildSystemContext(m);n({type:"stream_start",messageId:d});try{await this.bridge.stream({userContent:l,systemContext:r,history:u,images:h},o=>n({type:"stream_chunk",messageId:d,text:o}),()=>n({type:"stream_end",messageId:d}))}catch(o){const s=o instanceof Error?o.message:String(o);console.error("[koyi] Bridge error:",s),n({type:"stream_error",messageId:d,error:s})}}else a.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 n=[];for(const e of t){const a=[],d=[`<${e.tagName}`,e.id?` id="${e.id}"`:"",e.className?` class="${e.className}"`:"",">"].join("");if(a.push(`### Element: ${d}`),e.filePath){a.push(`**Source:** \`${e.filePath}:${e.line}:${e.column}\``);const l=this.resolveFilePath(e.filePath);if(l)try{const u=f.readFileSync(l,"utf-8").split(`
|
|
55
|
+
`),h=Math.max(0,e.line-1-I),r=Math.min(u.length,e.line-1+I),o=u.slice(h,r).join(`
|
|
56
|
+
`),s=y.extname(l).slice(1)||"tsx";a.push(`
|
|
52
57
|
\`\`\`${s}
|
|
53
|
-
// ${e.filePath} (lines ${
|
|
58
|
+
// ${e.filePath} (lines ${h+1}–${r})
|
|
54
59
|
${o}
|
|
55
|
-
\`\`\``)}catch{
|
|
60
|
+
\`\`\``)}catch{a.push("_(Could not read source file)_")}}if(e.outerHTML){const l=e.outerHTML.length>600?e.outerHTML.slice(0,600)+"…":e.outerHTML;a.push(`
|
|
56
61
|
**HTML:**
|
|
57
62
|
\`\`\`html
|
|
58
|
-
${
|
|
59
|
-
\`\`\``)}n.push(
|
|
63
|
+
${l}
|
|
64
|
+
\`\`\``)}n.push(a.join(`
|
|
60
65
|
`))}return n.join(`
|
|
61
66
|
|
|
62
67
|
---
|
|
63
68
|
|
|
64
|
-
`)}resolveFilePath(t){if(y.isAbsolute(t)&&f.existsSync(t))return t;const n=t.startsWith("/")?t.slice(1):t,e=y.resolve(this.projectRoot,n);return f.existsSync(e)?e:null}}const
|
|
65
|
-
\x1B[36m⚡ Koyi AI\x1B[0m \x1B[2mws://localhost:${s}${
|
|
66
|
-
`)}))},transformIndexHtml(){const
|
|
69
|
+
`)}resolveFilePath(t){if(y.isAbsolute(t)&&f.existsSync(t))return t;const n=t.startsWith("/")?t.slice(1):t,e=y.resolve(this.projectRoot,n);return f.existsSync(e)?e:null}}const $=typeof __dirname<"u"?__dirname:y.dirname(k.fileURLToPath(typeof document>"u"?require("url").pathToFileURL(__filename).href:C&&C.tagName.toUpperCase()==="SCRIPT"&&C.src||new URL("index.cjs",document.baseURI).href)),x=y.resolve($,"client.iife.js");function L(v={}){const{claudeMode:t="cli",apiKey:n=process.env.ANTHROPIC_API_KEY,model:e,hotkey:a="ctrl+shift+k",position:d={x:"right",y:"bottom"},autoApply:l=!0}=v;let m=process.cwd();return[{name:"vite-plugin-koyi:transform",enforce:"pre",apply:(r,{command:o})=>o==="serve",configResolved(r){m=r.root},transform(r,o){if(o.includes("node_modules")||o.includes("/__koyi")||!S.isJsTypeFile(o)&&!o.endsWith(".vue")&&!o.endsWith(".svelte"))return r;const s=S.normalizePath(o.split("?")[0]);let p="";if(s.endsWith(".vue")?p="vue":s.endsWith(".svelte")?p="svelte":s.endsWith(".tsx")||s.endsWith(".jsx")?p="jsx":(s.endsWith(".ts")||s.endsWith(".js"))&&(p="js"),!p)return r;try{return S.transformCode({content:r,filePath:s,fileType:p,pathType:"absolute",escapeTags:["koyi-overlay"]})}catch{return r}}},{name:"vite-plugin-koyi:server",apply:(r,{command:o})=>o==="serve",configureServer(r){r.httpServer&&(new E(r.httpServer,{projectRoot:m,claudeMode:t,apiKey:n,model:e,autoApply:l}),r.httpServer.once("listening",()=>{const o=r.httpServer.address(),s=typeof o=="object"&&o?o.port:"?";console.log(`
|
|
70
|
+
\x1B[36m⚡ Koyi AI\x1B[0m \x1B[2mws://localhost:${s}${_}\x1B[0m mode=${t} toggle=${a}
|
|
71
|
+
`)}))},transformIndexHtml(){const r=f.existsSync(x)?f.readFileSync(x,"utf-8"):"console.warn('[koyi] Client bundle not found. Run: pnpm build:client')",o=JSON.stringify({hotkey:a,position:d,wsPath:_});return[{tag:"script",attrs:{type:"text/javascript",id:"__koyi_script"},children:`${r}
|
|
67
72
|
;(function(){
|
|
68
73
|
if(typeof window==='undefined') return;
|
|
69
74
|
window.__KOYI_CONFIG__=${o};
|
|
@@ -75,5 +80,5 @@ ${a}
|
|
|
75
80
|
var el=document.createElement('koyi-overlay');
|
|
76
81
|
document.body.appendChild(el);
|
|
77
82
|
}
|
|
78
|
-
})();`,injectTo:"body"}]}}]}exports.KoyiPlugin
|
|
83
|
+
})();`,injectTo:"body"}]}}]}exports.KoyiPlugin=L;
|
|
79
84
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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\n## Code Change Reporting (IMPORTANT)\nWhenever you make or suggest any file modifications, you MUST display every changed file in the Koyi panel using the following format so the user can review what is being modified:\n\n\\`\\`\\`diff\n// filepath: <relative/path/to/file>\n- removed line\n+ added line\n unchanged context line\n\\`\\`\\`\n\nIf you apply multiple files, repeat the diff block for each file.\nAfter the diff(s), briefly summarize in one or two sentences what was changed and why.\nNever silently modify files without showing the diff in your response.`\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 // IMPORTANT: enforce:'pre' ensures this runs before Vite's own transforms,\n // but within the same enforce group, array order still matters.\n // Users MUST list KoyiPlugin() BEFORE their framework plugin (e.g. react())\n // in their vite.config so this transform sees original JSX source.\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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"}
|
|
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\n## Code Change Reporting (IMPORTANT)\nWhenever you make or suggest any file modifications, you MUST display every changed file in the Koyi panel using the following format so the user can review what is being modified:\n\n\\`\\`\\`diff\n// filepath: <relative/path/to/file>\n- removed line\n+ added line\n unchanged context line\n\\`\\`\\`\n\nIf you apply multiple files, repeat the diff block for each file.\nAfter the diff(s), briefly summarize in one or two sentences what was changed and why.\nNever silently modify files without showing the diff in your response.`\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 }\n }\n\n // ── Build prompt ───────────────────────────────────────────────────\n // Build image reference section to embed in the prompt (CLI has no --image flag)\n const imageSection =\n tempImageFiles.length > 0\n ? '\\n\\n## Attached Images\\n\\n' +\n tempImageFiles.map((p, i) => `Image ${i + 1}: ${p}`).join('\\n')\n : ''\n\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 + imageSection)\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}${imageSection}`\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 // IMPORTANT: enforce:'pre' ensures this runs before Vite's own transforms,\n // but within the same enforce group, array order still matters.\n // Users MUST list KoyiPlugin() BEFORE their framework plugin (e.g. react())\n // in their vite.config so this transform sees original JSX source.\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","ext","tempPath","os","imageSection","p","i","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
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import y from "path";
|
|
2
2
|
import f from "fs";
|
|
3
|
-
import { fileURLToPath as
|
|
4
|
-
import { isJsTypeFile as
|
|
5
|
-
import { WebSocketServer as
|
|
6
|
-
import { spawn as
|
|
7
|
-
import
|
|
3
|
+
import { fileURLToPath as x } from "url";
|
|
4
|
+
import { isJsTypeFile as I, normalizePath as b, transformCode as k } from "@code-inspector/core";
|
|
5
|
+
import { WebSocketServer as A, WebSocket as j } from "ws";
|
|
6
|
+
import { spawn as T } from "child_process";
|
|
7
|
+
import $ from "os";
|
|
8
8
|
import M from "@anthropic-ai/sdk";
|
|
9
9
|
const E = `You are Koyi, an expert AI assistant specialized in frontend development.
|
|
10
10
|
You help developers understand, debug, and improve their UI components in real-time.
|
|
@@ -33,7 +33,7 @@ Whenever you make or suggest any file modifications, you MUST display every chan
|
|
|
33
33
|
If you apply multiple files, repeat the diff block for each file.
|
|
34
34
|
After the diff(s), briefly summarize in one or two sentences what was changed and why.
|
|
35
35
|
Never silently modify files without showing the diff in your response.`;
|
|
36
|
-
class
|
|
36
|
+
class R {
|
|
37
37
|
constructor(t) {
|
|
38
38
|
if (this.options = t, this.claudeMdContent = this.readClaudeMd(), this.claudeMdContent && console.log("[koyi] Loaded CLAUDE.md project instructions"), t.mode === "api") {
|
|
39
39
|
const o = t.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
@@ -99,137 +99,143 @@ class $ {
|
|
|
99
99
|
throw new Error(
|
|
100
100
|
'Anthropic client not initialized. Provide an API key or use claudeMode: "cli".'
|
|
101
101
|
);
|
|
102
|
-
const { userContent: a, systemContext: d, history:
|
|
103
|
-
...
|
|
102
|
+
const { userContent: a, systemContext: d, history: l, images: u } = t, m = this.buildSystemPrompt(d), h = u?.length ? [
|
|
103
|
+
...u.map((s) => ({
|
|
104
104
|
type: "image",
|
|
105
105
|
source: {
|
|
106
106
|
type: "base64",
|
|
107
|
-
media_type:
|
|
108
|
-
data:
|
|
107
|
+
media_type: s.mimeType,
|
|
108
|
+
data: s.base64
|
|
109
109
|
}
|
|
110
110
|
})),
|
|
111
111
|
{ type: "text", text: a }
|
|
112
|
-
] : a,
|
|
113
|
-
...
|
|
114
|
-
{ role: "user", content:
|
|
112
|
+
] : a, r = [
|
|
113
|
+
...l.map((s) => ({ role: s.role, content: s.content })),
|
|
114
|
+
{ role: "user", content: h }
|
|
115
115
|
];
|
|
116
116
|
this.abortController = new AbortController();
|
|
117
|
-
const
|
|
117
|
+
const n = this.client.messages.stream(
|
|
118
118
|
{
|
|
119
119
|
model: this.options.model ?? "claude-opus-4-5",
|
|
120
120
|
max_tokens: 8096,
|
|
121
121
|
system: m,
|
|
122
|
-
messages:
|
|
122
|
+
messages: r
|
|
123
123
|
},
|
|
124
124
|
{ signal: this.abortController.signal }
|
|
125
125
|
);
|
|
126
|
-
for await (const
|
|
127
|
-
|
|
126
|
+
for await (const s of n)
|
|
127
|
+
s.type === "content_block_delta" && s.delta.type === "text_delta" && o(s.delta.text);
|
|
128
128
|
e(), this.abortController = void 0;
|
|
129
129
|
}
|
|
130
130
|
// ─── Claude Code CLI (cli mode) ─────────────────────────────────────────────
|
|
131
131
|
streamViaCLI(t, o, e) {
|
|
132
132
|
return new Promise((a, d) => {
|
|
133
|
-
const { userContent:
|
|
133
|
+
const { userContent: l, systemContext: u } = t, m = [];
|
|
134
134
|
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");
|
|
135
|
-
const
|
|
135
|
+
const h = [];
|
|
136
136
|
if (t.images?.length)
|
|
137
|
-
for (let
|
|
138
|
-
const
|
|
137
|
+
for (let i = 0; i < t.images.length; i++) {
|
|
138
|
+
const c = t.images[i], g = (c.mimeType.split("/")[1] ?? "png").replace(
|
|
139
139
|
"jpeg",
|
|
140
140
|
"jpg"
|
|
141
|
-
),
|
|
142
|
-
|
|
143
|
-
`koyi-img-${Date.now()}-${
|
|
141
|
+
), w = y.join(
|
|
142
|
+
$.tmpdir(),
|
|
143
|
+
`koyi-img-${Date.now()}-${i}.${g}`
|
|
144
144
|
);
|
|
145
|
-
f.writeFileSync(
|
|
145
|
+
f.writeFileSync(w, Buffer.from(c.base64, "base64")), h.push(w);
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
const r = h.length > 0 ? `
|
|
148
|
+
|
|
149
|
+
## Attached Images
|
|
150
|
+
|
|
151
|
+
` + h.map((i, c) => `Image ${c + 1}: ${i}`).join(`
|
|
152
|
+
`) : "";
|
|
153
|
+
let n;
|
|
148
154
|
if (this.cliSessionId) {
|
|
149
|
-
const
|
|
150
|
-
|
|
155
|
+
const i = [];
|
|
156
|
+
u && i.push(
|
|
151
157
|
`## Current Context (selected DOM elements)
|
|
152
158
|
|
|
153
|
-
` +
|
|
159
|
+
` + u,
|
|
154
160
|
"---"
|
|
155
|
-
),
|
|
161
|
+
), i.push(l + r), n = i.join(`
|
|
156
162
|
|
|
157
163
|
`);
|
|
158
164
|
} else
|
|
159
|
-
|
|
160
|
-
this.buildSystemPrompt(
|
|
165
|
+
n = [
|
|
166
|
+
this.buildSystemPrompt(u),
|
|
161
167
|
`---
|
|
162
168
|
|
|
163
169
|
## User Request
|
|
164
170
|
|
|
165
|
-
${
|
|
171
|
+
${l}${r}`
|
|
166
172
|
].join(`
|
|
167
173
|
|
|
168
174
|
`);
|
|
169
|
-
m.push(
|
|
170
|
-
const s =
|
|
175
|
+
m.push(n);
|
|
176
|
+
const s = T("claude", m, {
|
|
171
177
|
cwd: this.options.projectRoot,
|
|
172
178
|
env: { ...process.env },
|
|
173
179
|
stdio: ["ignore", "pipe", "pipe"]
|
|
174
180
|
});
|
|
175
181
|
this.activeProc = s;
|
|
176
|
-
let
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
182
|
+
let p = "";
|
|
183
|
+
const C = (i) => {
|
|
184
|
+
if (i.trim())
|
|
179
185
|
try {
|
|
180
|
-
const
|
|
181
|
-
if (
|
|
182
|
-
for (const g of
|
|
186
|
+
const c = JSON.parse(i);
|
|
187
|
+
if (c.session_id && (this.cliSessionId = c.session_id), c.type === "assistant" && c.message?.content)
|
|
188
|
+
for (const g of c.message.content)
|
|
183
189
|
g.type === "text" && g.text && o(g.text);
|
|
184
190
|
} catch {
|
|
185
|
-
o(
|
|
191
|
+
o(i + `
|
|
186
192
|
`);
|
|
187
193
|
}
|
|
188
194
|
};
|
|
189
|
-
s.stdout.on("data", (
|
|
190
|
-
|
|
191
|
-
const
|
|
195
|
+
s.stdout.on("data", (i) => {
|
|
196
|
+
p += i.toString("utf-8");
|
|
197
|
+
const c = p.split(`
|
|
192
198
|
`);
|
|
193
|
-
|
|
194
|
-
}), s.stderr.on("data", (
|
|
195
|
-
const
|
|
196
|
-
!
|
|
197
|
-
}), s.on("close", (
|
|
198
|
-
|
|
199
|
-
for (const
|
|
199
|
+
p = c.pop() ?? "", c.forEach(C);
|
|
200
|
+
}), s.stderr.on("data", (i) => {
|
|
201
|
+
const c = i.toString("utf-8");
|
|
202
|
+
!c.includes("Bypassing Permissions") && !c.includes("Human:") && console.warn("[koyi] claude stderr:", c.trim());
|
|
203
|
+
}), s.on("close", (i) => {
|
|
204
|
+
p.trim() && C(p), p = "";
|
|
205
|
+
for (const c of h)
|
|
200
206
|
try {
|
|
201
|
-
f.unlinkSync(
|
|
207
|
+
f.unlinkSync(c);
|
|
202
208
|
} catch {
|
|
203
209
|
}
|
|
204
|
-
this.activeProc = void 0,
|
|
205
|
-
}), s.on("error", (
|
|
206
|
-
this.activeProc = void 0,
|
|
210
|
+
this.activeProc = void 0, i === 0 || i === null ? (e(), a()) : d(new Error(`Claude CLI exited with code ${i}`));
|
|
211
|
+
}), s.on("error", (i) => {
|
|
212
|
+
this.activeProc = void 0, i.code === "ENOENT" ? d(
|
|
207
213
|
new Error(
|
|
208
214
|
'[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-..." })'
|
|
209
215
|
)
|
|
210
|
-
) : d(
|
|
216
|
+
) : d(i);
|
|
211
217
|
});
|
|
212
218
|
});
|
|
213
219
|
}
|
|
214
220
|
}
|
|
215
|
-
const
|
|
216
|
-
class
|
|
221
|
+
const S = "/__koyi_ws", _ = 25;
|
|
222
|
+
class N {
|
|
217
223
|
constructor(t, o) {
|
|
218
|
-
this.projectRoot = o.projectRoot, this.bridge = new
|
|
224
|
+
this.projectRoot = o.projectRoot, this.bridge = new R({
|
|
219
225
|
mode: o.claudeMode,
|
|
220
226
|
apiKey: o.apiKey,
|
|
221
227
|
projectRoot: o.projectRoot,
|
|
222
228
|
model: o.model,
|
|
223
229
|
autoApply: o.autoApply
|
|
224
|
-
}), this.wss = new
|
|
225
|
-
e.url ===
|
|
226
|
-
this.wss.emit("connection",
|
|
230
|
+
}), this.wss = new A({ noServer: !0 }), t.on("upgrade", (e, a, d) => {
|
|
231
|
+
e.url === S && this.wss.handleUpgrade(e, a, d, (l) => {
|
|
232
|
+
this.wss.emit("connection", l, e), this.handleConnection(l);
|
|
227
233
|
});
|
|
228
234
|
});
|
|
229
235
|
}
|
|
230
236
|
handleConnection(t) {
|
|
231
237
|
const o = (e) => {
|
|
232
|
-
t.readyState ===
|
|
238
|
+
t.readyState === j.OPEN && t.send(JSON.stringify(e));
|
|
233
239
|
};
|
|
234
240
|
o({ type: "ready" }), t.on("message", async (e) => {
|
|
235
241
|
let a;
|
|
@@ -239,17 +245,17 @@ class R {
|
|
|
239
245
|
return;
|
|
240
246
|
}
|
|
241
247
|
if (a.type === "send_message") {
|
|
242
|
-
const { messageId: d, content:
|
|
248
|
+
const { messageId: d, content: l, contexts: u, history: m, images: h } = a, r = await this.buildSystemContext(u);
|
|
243
249
|
o({ type: "stream_start", messageId: d });
|
|
244
250
|
try {
|
|
245
251
|
await this.bridge.stream(
|
|
246
|
-
{ userContent:
|
|
247
|
-
(
|
|
252
|
+
{ userContent: l, systemContext: r, history: m, images: h },
|
|
253
|
+
(n) => o({ type: "stream_chunk", messageId: d, text: n }),
|
|
248
254
|
() => o({ type: "stream_end", messageId: d })
|
|
249
255
|
);
|
|
250
|
-
} catch (
|
|
251
|
-
const
|
|
252
|
-
console.error("[koyi] Bridge error:",
|
|
256
|
+
} catch (n) {
|
|
257
|
+
const s = n instanceof Error ? n.message : String(n);
|
|
258
|
+
console.error("[koyi] Bridge error:", s), o({ type: "stream_error", messageId: d, error: s });
|
|
253
259
|
}
|
|
254
260
|
} else a.type === "abort" && this.bridge.abort();
|
|
255
261
|
}), t.on("error", (e) => {
|
|
@@ -272,20 +278,20 @@ class R {
|
|
|
272
278
|
].join("");
|
|
273
279
|
if (a.push(`### Element: ${d}`), e.filePath) {
|
|
274
280
|
a.push(`**Source:** \`${e.filePath}:${e.line}:${e.column}\``);
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
281
|
+
const l = this.resolveFilePath(e.filePath);
|
|
282
|
+
if (l)
|
|
277
283
|
try {
|
|
278
|
-
const m = f.readFileSync(
|
|
279
|
-
`),
|
|
284
|
+
const m = f.readFileSync(l, "utf-8").split(`
|
|
285
|
+
`), h = Math.max(0, e.line - 1 - _), r = Math.min(
|
|
280
286
|
m.length,
|
|
281
|
-
e.line - 1 +
|
|
282
|
-
),
|
|
283
|
-
`),
|
|
287
|
+
e.line - 1 + _
|
|
288
|
+
), n = m.slice(h, r).join(`
|
|
289
|
+
`), s = y.extname(l).slice(1) || "tsx";
|
|
284
290
|
a.push(
|
|
285
291
|
`
|
|
286
|
-
\`\`\`${
|
|
287
|
-
// ${e.filePath} (lines ${
|
|
288
|
-
${
|
|
292
|
+
\`\`\`${s}
|
|
293
|
+
// ${e.filePath} (lines ${h + 1}–${r})
|
|
294
|
+
${n}
|
|
289
295
|
\`\`\``
|
|
290
296
|
);
|
|
291
297
|
} catch {
|
|
@@ -293,11 +299,11 @@ ${s}
|
|
|
293
299
|
}
|
|
294
300
|
}
|
|
295
301
|
if (e.outerHTML) {
|
|
296
|
-
const
|
|
302
|
+
const l = e.outerHTML.length > 600 ? e.outerHTML.slice(0, 600) + "…" : e.outerHTML;
|
|
297
303
|
a.push(`
|
|
298
304
|
**HTML:**
|
|
299
305
|
\`\`\`html
|
|
300
|
-
${
|
|
306
|
+
${l}
|
|
301
307
|
\`\`\``);
|
|
302
308
|
}
|
|
303
309
|
o.push(a.join(`
|
|
@@ -317,74 +323,74 @@ ${c}
|
|
|
317
323
|
return f.existsSync(e) ? e : null;
|
|
318
324
|
}
|
|
319
325
|
}
|
|
320
|
-
const
|
|
321
|
-
function
|
|
326
|
+
const L = typeof __dirname < "u" ? __dirname : y.dirname(x(import.meta.url)), P = y.resolve(L, "client.iife.js");
|
|
327
|
+
function Y(v = {}) {
|
|
322
328
|
const {
|
|
323
329
|
claudeMode: t = "cli",
|
|
324
330
|
apiKey: o = process.env.ANTHROPIC_API_KEY,
|
|
325
331
|
model: e,
|
|
326
332
|
hotkey: a = "ctrl+shift+k",
|
|
327
333
|
position: d = { x: "right", y: "bottom" },
|
|
328
|
-
autoApply:
|
|
334
|
+
autoApply: l = !0
|
|
329
335
|
} = v;
|
|
330
|
-
let
|
|
336
|
+
let u = process.cwd();
|
|
331
337
|
return [{
|
|
332
338
|
name: "vite-plugin-koyi:transform",
|
|
333
339
|
enforce: "pre",
|
|
334
|
-
apply: (
|
|
335
|
-
configResolved(
|
|
336
|
-
|
|
340
|
+
apply: (r, { command: n }) => n === "serve",
|
|
341
|
+
configResolved(r) {
|
|
342
|
+
u = r.root;
|
|
337
343
|
},
|
|
338
|
-
transform(
|
|
339
|
-
if (
|
|
340
|
-
return
|
|
341
|
-
const
|
|
342
|
-
let
|
|
343
|
-
if (
|
|
344
|
+
transform(r, n) {
|
|
345
|
+
if (n.includes("node_modules") || n.includes("/__koyi") || !I(n) && !n.endsWith(".vue") && !n.endsWith(".svelte"))
|
|
346
|
+
return r;
|
|
347
|
+
const s = b(n.split("?")[0]);
|
|
348
|
+
let p = "";
|
|
349
|
+
if (s.endsWith(".vue") ? p = "vue" : s.endsWith(".svelte") ? p = "svelte" : s.endsWith(".tsx") || s.endsWith(".jsx") ? p = "jsx" : (s.endsWith(".ts") || s.endsWith(".js")) && (p = "js"), !p) return r;
|
|
344
350
|
try {
|
|
345
|
-
return
|
|
346
|
-
content:
|
|
347
|
-
filePath:
|
|
348
|
-
fileType:
|
|
351
|
+
return k({
|
|
352
|
+
content: r,
|
|
353
|
+
filePath: s,
|
|
354
|
+
fileType: p,
|
|
349
355
|
// Use absolute paths so the server can read the file directly
|
|
350
356
|
pathType: "absolute",
|
|
351
357
|
// Avoid transforming our own injected overlay elements
|
|
352
358
|
escapeTags: ["koyi-overlay"]
|
|
353
359
|
});
|
|
354
360
|
} catch {
|
|
355
|
-
return
|
|
361
|
+
return r;
|
|
356
362
|
}
|
|
357
363
|
}
|
|
358
364
|
}, {
|
|
359
365
|
name: "vite-plugin-koyi:server",
|
|
360
|
-
apply: (
|
|
361
|
-
configureServer(
|
|
362
|
-
|
|
363
|
-
projectRoot:
|
|
366
|
+
apply: (r, { command: n }) => n === "serve",
|
|
367
|
+
configureServer(r) {
|
|
368
|
+
r.httpServer && (new N(r.httpServer, {
|
|
369
|
+
projectRoot: u,
|
|
364
370
|
claudeMode: t,
|
|
365
371
|
apiKey: o,
|
|
366
372
|
model: e,
|
|
367
|
-
autoApply:
|
|
368
|
-
}),
|
|
369
|
-
const
|
|
373
|
+
autoApply: l
|
|
374
|
+
}), r.httpServer.once("listening", () => {
|
|
375
|
+
const n = r.httpServer.address(), s = typeof n == "object" && n ? n.port : "?";
|
|
370
376
|
console.log(
|
|
371
377
|
`
|
|
372
|
-
\x1B[36m⚡ Koyi AI\x1B[0m \x1B[2mws://localhost:${
|
|
378
|
+
\x1B[36m⚡ Koyi AI\x1B[0m \x1B[2mws://localhost:${s}${S}\x1B[0m mode=${t} toggle=${a}
|
|
373
379
|
`
|
|
374
380
|
);
|
|
375
381
|
}));
|
|
376
382
|
},
|
|
377
383
|
transformIndexHtml() {
|
|
378
|
-
const
|
|
384
|
+
const r = f.existsSync(P) ? f.readFileSync(P, "utf-8") : "console.warn('[koyi] Client bundle not found. Run: pnpm build:client')", n = JSON.stringify({ hotkey: a, position: d, wsPath: S });
|
|
379
385
|
return [
|
|
380
386
|
{
|
|
381
387
|
tag: "script",
|
|
382
388
|
attrs: { type: "text/javascript", id: "__koyi_script" },
|
|
383
389
|
// Inject the bundle + mount the web component
|
|
384
|
-
children: `${
|
|
390
|
+
children: `${r}
|
|
385
391
|
;(function(){
|
|
386
392
|
if(typeof window==='undefined') return;
|
|
387
|
-
window.__KOYI_CONFIG__=${
|
|
393
|
+
window.__KOYI_CONFIG__=${n};
|
|
388
394
|
if(!customElements.get('koyi-overlay')){
|
|
389
395
|
// The IIFE registers <koyi-overlay> automatically.
|
|
390
396
|
// We just need to ensure it's in the DOM.
|
|
@@ -401,6 +407,6 @@ function F(v = {}) {
|
|
|
401
407
|
}];
|
|
402
408
|
}
|
|
403
409
|
export {
|
|
404
|
-
|
|
410
|
+
Y as KoyiPlugin
|
|
405
411
|
};
|
|
406
412
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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\n## Code Change Reporting (IMPORTANT)\nWhenever you make or suggest any file modifications, you MUST display every changed file in the Koyi panel using the following format so the user can review what is being modified:\n\n\\`\\`\\`diff\n// filepath: <relative/path/to/file>\n- removed line\n+ added line\n unchanged context line\n\\`\\`\\`\n\nIf you apply multiple files, repeat the diff block for each file.\nAfter the diff(s), briefly summarize in one or two sentences what was changed and why.\nNever silently modify files without showing the diff in your response.`\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 // IMPORTANT: enforce:'pre' ensures this runs before Vite's own transforms,\n // but within the same enforce group, array order still matters.\n // Users MUST list KoyiPlugin() BEFORE their framework plugin (e.g. react())\n // in their vite.config so this transform sees original JSX source.\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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"}
|
|
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\n## Code Change Reporting (IMPORTANT)\nWhenever you make or suggest any file modifications, you MUST display every changed file in the Koyi panel using the following format so the user can review what is being modified:\n\n\\`\\`\\`diff\n// filepath: <relative/path/to/file>\n- removed line\n+ added line\n unchanged context line\n\\`\\`\\`\n\nIf you apply multiple files, repeat the diff block for each file.\nAfter the diff(s), briefly summarize in one or two sentences what was changed and why.\nNever silently modify files without showing the diff in your response.`\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 }\n }\n\n // ── Build prompt ───────────────────────────────────────────────────\n // Build image reference section to embed in the prompt (CLI has no --image flag)\n const imageSection =\n tempImageFiles.length > 0\n ? '\\n\\n## Attached Images\\n\\n' +\n tempImageFiles.map((p, i) => `Image ${i + 1}: ${p}`).join('\\n')\n : ''\n\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 + imageSection)\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}${imageSection}`\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 // IMPORTANT: enforce:'pre' ensures this runs before Vite's own transforms,\n // but within the same enforce group, array order still matters.\n // Users MUST list KoyiPlugin() BEFORE their framework plugin (e.g. react())\n // in their vite.config so this transform sees original JSX source.\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","ext","tempPath","os","imageSection","p","i","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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"}
|
|
@@ -1 +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;AA8BD,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;
|
|
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;AA8BD,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;CAgLrB"}
|