cursor-buddy 0.0.2 → 0.0.3
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/{client-DKZY5bI1.d.mts → client-CPQnk2_x.d.mts} +80 -109
- package/dist/client-CPQnk2_x.d.mts.map +1 -0
- package/dist/{client-Bd33JD8T.mjs → client-DAa4L2fE.mjs} +995 -481
- package/dist/client-DAa4L2fE.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react/index.d.mts +21 -21
- package/dist/react/index.d.mts.map +1 -1
- package/dist/react/index.mjs +98 -83
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.mjs +24 -11
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
- package/README.md +0 -344
- package/dist/client-Bd33JD8T.mjs.map +0 -1
- package/dist/client-DKZY5bI1.d.mts.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client-Bd33JD8T.mjs","names":[],"sources":["../src/core/state-machine.ts","../src/core/utils/audio-worklet.ts","../src/core/utils/audio.ts","../src/core/services/voice-capture.ts","../src/core/services/audio-playback.ts","../src/core/utils/screenshot.ts","../src/core/services/screen-capture.ts","../src/core/atoms.ts","../src/core/bezier.ts","../src/core/services/pointer-controller.ts","../src/core/pointing.ts","../src/core/client.ts"],"sourcesContent":["import type { VoiceState, VoiceEvent } from \"./types\"\n\n/**\n * State transition table for the voice interaction flow.\n * Maps current state + event type to next state.\n */\nconst transitions: Record<VoiceState, Partial<Record<VoiceEvent[\"type\"], VoiceState>>> = {\n idle: {\n HOTKEY_PRESSED: \"listening\",\n },\n listening: {\n HOTKEY_RELEASED: \"processing\",\n ERROR: \"idle\",\n },\n processing: {\n AI_RESPONSE_COMPLETE: \"responding\",\n HOTKEY_PRESSED: \"listening\", // Interruption\n ERROR: \"idle\",\n },\n responding: {\n TTS_COMPLETE: \"idle\",\n HOTKEY_PRESSED: \"listening\", // Interruption\n ERROR: \"idle\",\n },\n}\n\nexport interface StateMachine {\n /** Get current state */\n getState(): VoiceState\n /** Attempt a state transition. Returns true if transition was valid. */\n transition(event: VoiceEvent): boolean\n /** Subscribe to state changes */\n subscribe(listener: () => void): () => void\n /** Reset to idle state */\n reset(): void\n}\n\n/**\n * Create a simple typed state machine for the voice interaction flow.\n *\n * States: idle -> listening -> processing -> responding -> idle\n *\n * Supports interruption: pressing hotkey during processing or responding\n * immediately transitions back to listening.\n */\nexport function createStateMachine(initial: VoiceState = \"idle\"): StateMachine {\n let state = initial\n const listeners = new Set<() => void>()\n\n function notify() {\n listeners.forEach((listener) => listener())\n }\n\n return {\n getState: () => state,\n\n transition: (event: VoiceEvent): boolean => {\n const nextState = transitions[state][event.type]\n if (!nextState) return false\n\n state = nextState\n notify()\n return true\n },\n\n subscribe: (listener: () => void) => {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n reset: () => {\n state = \"idle\"\n notify()\n },\n }\n}\n","/**\n * AudioWorklet processor code for voice capture.\n * Inlined as a blob URL to avoid separate file serving requirements.\n */\nconst workletCode = `\nclass AudioCaptureProcessor extends AudioWorkletProcessor {\n constructor() {\n super()\n this.isRecording = true\n }\n\n process(inputs) {\n if (!this.isRecording) return false\n\n const input = inputs[0]\n if (input && input.length > 0) {\n const channelData = input[0]\n\n // Send audio data to main thread\n this.port.postMessage({\n type: \"audio\",\n data: new Float32Array(channelData)\n })\n\n // Calculate RMS for audio level visualization\n let sum = 0\n for (let i = 0; i < channelData.length; i++) {\n sum += channelData[i] * channelData[i]\n }\n const rms = Math.sqrt(sum / channelData.length)\n this.port.postMessage({ type: \"level\", rms })\n }\n\n return true\n }\n}\n\nregisterProcessor(\"audio-capture-processor\", AudioCaptureProcessor)\n`\n\nlet cachedBlobURL: string | null = null\n\n/**\n * Create a blob URL for the audio worklet processor.\n * Caches the URL to avoid creating multiple blobs.\n */\nexport function createWorkletBlobURL(): string {\n if (!cachedBlobURL) {\n const blob = new Blob([workletCode], { type: \"application/javascript\" })\n cachedBlobURL = URL.createObjectURL(blob)\n }\n return cachedBlobURL\n}\n\n/**\n * Clean up the cached worklet blob URL.\n * Call this when the app unmounts if needed.\n */\nexport function revokeWorkletBlobURL(): void {\n if (cachedBlobURL) {\n URL.revokeObjectURL(cachedBlobURL)\n cachedBlobURL = null\n }\n}\n","/**\n * Audio conversion utilities for voice capture.\n * Converts Float32 audio data to WAV format for server transcription.\n */\n\n/**\n * Merge multiple Float32Array chunks into a single array\n */\nexport function mergeAudioChunks(chunks: Float32Array[]): Float32Array {\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0)\n const result = new Float32Array(totalLength)\n\n let offset = 0\n for (const chunk of chunks) {\n result.set(chunk, offset)\n offset += chunk.length\n }\n\n return result\n}\n\n/**\n * Convert Float32 audio data to 16-bit PCM\n */\nfunction floatTo16BitPCM(\n output: DataView,\n offset: number,\n input: Float32Array\n): void {\n for (let i = 0; i < input.length; i++, offset += 2) {\n const sample = Math.max(-1, Math.min(1, input[i]))\n output.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7fff, true)\n }\n}\n\n/**\n * Write a string to a DataView\n */\nfunction writeString(view: DataView, offset: number, string: string): void {\n for (let i = 0; i < string.length; i++) {\n view.setUint8(offset + i, string.charCodeAt(i))\n }\n}\n\n/**\n * Encode Float32 audio data as a WAV file\n */\nexport function encodeWAV(samples: Float32Array, sampleRate: number): Blob {\n const numChannels = 1\n const bitsPerSample = 16\n const bytesPerSample = bitsPerSample / 8\n const blockAlign = numChannels * bytesPerSample\n\n const dataLength = samples.length * bytesPerSample\n const buffer = new ArrayBuffer(44 + dataLength)\n const view = new DataView(buffer)\n\n // RIFF header\n writeString(view, 0, \"RIFF\")\n view.setUint32(4, 36 + dataLength, true)\n writeString(view, 8, \"WAVE\")\n\n // fmt chunk\n writeString(view, 12, \"fmt \")\n view.setUint32(16, 16, true) // chunk size\n view.setUint16(20, 1, true) // audio format (PCM)\n view.setUint16(22, numChannels, true)\n view.setUint32(24, sampleRate, true)\n view.setUint32(28, sampleRate * blockAlign, true) // byte rate\n view.setUint16(32, blockAlign, true)\n view.setUint16(34, bitsPerSample, true)\n\n // data chunk\n writeString(view, 36, \"data\")\n view.setUint32(40, dataLength, true)\n\n floatTo16BitPCM(view, 44, samples)\n\n return new Blob([buffer], { type: \"audio/wav\" })\n}\n","import { createWorkletBlobURL } from \"../utils/audio-worklet\"\nimport { mergeAudioChunks, encodeWAV } from \"../utils/audio\"\n\nconst SAMPLE_RATE = 16000\nconst AUDIO_LEVEL_BOOST = 10.2\n\n/**\n * Framework-agnostic service for voice capture using AudioWorkletNode.\n */\nexport class VoiceCaptureService {\n private audioContext: AudioContext | null = null\n private workletNode: AudioWorkletNode | null = null\n private stream: MediaStream | null = null\n private chunks: Float32Array[] = []\n private levelCallback: ((level: number) => void) | null = null\n\n /**\n * Register a callback to receive audio level updates (0-1).\n * Called at ~60fps during recording for waveform visualization.\n */\n onLevel(callback: (level: number) => void): void {\n this.levelCallback = callback\n }\n\n /**\n * Start recording audio from the microphone.\n * @throws Error if microphone access is denied\n */\n async start(): Promise<void> {\n this.chunks = []\n\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate: SAMPLE_RATE,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n },\n })\n this.stream = stream\n\n const audioContext = new AudioContext({ sampleRate: SAMPLE_RATE })\n this.audioContext = audioContext\n\n // Load worklet from blob URL\n const workletURL = createWorkletBlobURL()\n await audioContext.audioWorklet.addModule(workletURL)\n\n const source = audioContext.createMediaStreamSource(stream)\n const workletNode = new AudioWorkletNode(\n audioContext,\n \"audio-capture-processor\"\n )\n this.workletNode = workletNode\n\n workletNode.port.onmessage = (event) => {\n const { type, data, rms } = event.data\n\n if (type === \"audio\") {\n this.chunks.push(data)\n } else if (type === \"level\" && this.levelCallback) {\n // Boost audio level for better visualization\n const boostedLevel = Math.min(rms * AUDIO_LEVEL_BOOST, 1)\n this.levelCallback(boostedLevel)\n }\n }\n\n source.connect(workletNode)\n // Don't connect to destination - we don't want to play back the mic\n }\n\n /**\n * Stop recording and return the captured audio as a WAV blob.\n */\n async stop(): Promise<Blob> {\n // Stop the media stream tracks\n if (this.stream) {\n this.stream.getTracks().forEach((track) => track.stop())\n this.stream = null\n }\n\n // Disconnect and close audio nodes\n if (this.workletNode) {\n this.workletNode.disconnect()\n this.workletNode = null\n }\n\n if (this.audioContext) {\n await this.audioContext.close()\n this.audioContext = null\n }\n\n // Reset audio level\n this.levelCallback?.(0)\n\n // Encode captured audio as WAV\n const audioData = mergeAudioChunks(this.chunks)\n const wavBlob = encodeWAV(audioData, SAMPLE_RATE)\n\n this.chunks = []\n\n return wavBlob\n }\n\n /**\n * Clean up all resources.\n */\n dispose(): void {\n if (this.stream) {\n this.stream.getTracks().forEach((track) => track.stop())\n this.stream = null\n }\n if (this.workletNode) {\n this.workletNode.disconnect()\n this.workletNode = null\n }\n if (this.audioContext) {\n this.audioContext.close()\n this.audioContext = null\n }\n this.chunks = []\n this.levelCallback = null\n }\n}\n","/**\n * Framework-agnostic service for audio playback with abort support.\n */\nexport class AudioPlaybackService {\n private audio: HTMLAudioElement | null = null\n private currentUrl: string | null = null\n\n /**\n * Play audio from a blob. Stops any currently playing audio first.\n * @param blob - Audio blob to play\n * @param signal - Optional AbortSignal to cancel playback\n * @returns Promise that resolves when playback completes\n */\n async play(blob: Blob, signal?: AbortSignal): Promise<void> {\n // Stop any current playback\n this.stop()\n\n // Check if already aborted\n if (signal?.aborted) return\n\n const url = URL.createObjectURL(blob)\n this.currentUrl = url\n this.audio = new Audio(url)\n\n // Set up abort handler\n const abortHandler = () => this.stop()\n signal?.addEventListener(\"abort\", abortHandler)\n\n return new Promise<void>((resolve, reject) => {\n if (!this.audio) {\n this.cleanup()\n resolve()\n return\n }\n\n this.audio.onended = () => {\n signal?.removeEventListener(\"abort\", abortHandler)\n this.cleanup()\n resolve()\n }\n\n this.audio.onerror = () => {\n signal?.removeEventListener(\"abort\", abortHandler)\n this.cleanup()\n reject(new Error(\"Audio playback failed\"))\n }\n\n this.audio.play().catch((err) => {\n signal?.removeEventListener(\"abort\", abortHandler)\n this.cleanup()\n reject(err)\n })\n })\n }\n\n /**\n * Stop any currently playing audio.\n */\n stop(): void {\n if (this.audio) {\n this.audio.pause()\n this.audio.onended = null\n this.audio.onerror = null\n this.audio = null\n }\n this.cleanup()\n }\n\n private cleanup(): void {\n if (this.currentUrl) {\n URL.revokeObjectURL(this.currentUrl)\n this.currentUrl = null\n }\n }\n}\n","import html2canvas from \"html2canvas-pro\"\nimport type { ScreenshotResult } from \"../types\"\n\nconst MAX_WIDTH = 1280\n\nfunction getCaptureMetrics() {\n return {\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n}\n\n/**\n * Resize canvas to max width while maintaining aspect ratio\n */\nfunction resizeCanvas(\n canvas: HTMLCanvasElement,\n maxWidth: number\n): HTMLCanvasElement {\n if (canvas.width <= maxWidth) {\n return canvas\n }\n\n const scale = maxWidth / canvas.width\n const resized = document.createElement(\"canvas\")\n resized.width = maxWidth\n resized.height = Math.round(canvas.height * scale)\n\n const ctx = resized.getContext(\"2d\")\n if (ctx) {\n ctx.drawImage(canvas, 0, 0, resized.width, resized.height)\n }\n\n return resized\n}\n\n/**\n * Create a fallback canvas when screenshot capture fails.\n * Returns a simple gray canvas with an error message.\n */\nfunction createFallbackCanvas(): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\")\n canvas.width = Math.min(window.innerWidth, MAX_WIDTH)\n canvas.height = Math.round(\n (window.innerHeight / window.innerWidth) * canvas.width\n )\n\n const ctx = canvas.getContext(\"2d\")\n if (ctx) {\n ctx.fillStyle = \"#f0f0f0\"\n ctx.fillRect(0, 0, canvas.width, canvas.height)\n ctx.fillStyle = \"#666\"\n ctx.font = \"16px sans-serif\"\n ctx.textAlign = \"center\"\n ctx.fillText(\"Screenshot unavailable\", canvas.width / 2, canvas.height / 2)\n }\n\n return canvas\n}\n\n/**\n * Capture a screenshot of the current viewport.\n * Uses html2canvas to render the DOM to a canvas, then exports as JPEG.\n * Falls back to a placeholder if capture fails (e.g., due to unsupported CSS).\n */\nexport async function captureViewport(): Promise<ScreenshotResult> {\n const captureMetrics = getCaptureMetrics()\n let canvas: HTMLCanvasElement\n\n try {\n canvas = await html2canvas(document.body, {\n scale: 1,\n useCORS: true,\n logging: false,\n width: captureMetrics.viewportWidth,\n height: captureMetrics.viewportHeight,\n x: window.scrollX,\n y: window.scrollY,\n })\n } catch {\n canvas = createFallbackCanvas()\n }\n\n const resized = resizeCanvas(canvas, MAX_WIDTH)\n\n return {\n imageData: resized.toDataURL(\"image/jpeg\", 0.8),\n width: resized.width,\n height: resized.height,\n viewportWidth: captureMetrics.viewportWidth,\n viewportHeight: captureMetrics.viewportHeight,\n }\n}\n","import { captureViewport } from \"../utils/screenshot\"\nimport type { ScreenshotResult } from \"../types\"\n\n/**\n * Framework-agnostic service for capturing viewport screenshots.\n */\nexport class ScreenCaptureService {\n /**\n * Capture a screenshot of the current viewport.\n * @returns Screenshot result with image data and dimensions\n */\n async capture(): Promise<ScreenshotResult> {\n return captureViewport()\n }\n}\n","import { atom } from \"nanostores\"\nimport type { Point, PointingTarget, ConversationMessage } from \"./types\"\n\n/**\n * Nanostores atoms for reactive values that don't need state machine semantics.\n * These update frequently (e.g., 60fps audio levels) and are framework-agnostic.\n */\n\n// Audio level during recording (0-1, updates at ~60fps)\nexport const $audioLevel = atom<number>(0)\n\n// Mouse cursor position (real cursor, not buddy)\nexport const $cursorPosition = atom<Point>({ x: 0, y: 0 })\n\n// Buddy animated position (follows cursor with spring physics, or flies to target)\nexport const $buddyPosition = atom<Point>({ x: 0, y: 0 })\n\n// Buddy rotation in radians (direction of travel during pointing)\nexport const $buddyRotation = atom<number>(0)\n\n// Buddy scale (1.0 normal, up to 1.3 during flight)\nexport const $buddyScale = atom<number>(1)\n\n// Current pointing target parsed from AI response\nexport const $pointingTarget = atom<PointingTarget | null>(null)\n\n// Whether buddy overlay is enabled/visible\nexport const $isEnabled = atom<boolean>(true)\n\n// Whether TTS is currently playing\nexport const $isSpeaking = atom<boolean>(false)\n\n// Conversation history for context\nexport const $conversationHistory = atom<ConversationMessage[]>([])\n","import type { Point } from \"./types\";\n\n/**\n * Bezier flight animation for cursor pointing.\n */\n\n/**\n * Quadratic bezier curve: B(t) = (1-t)²P₀ + 2(1-t)t·P₁ + t²P₂\n */\nfunction quadraticBezier(p0: Point, p1: Point, p2: Point, t: number): Point {\n const oneMinusT = 1 - t;\n return {\n x: oneMinusT * oneMinusT * p0.x + 2 * oneMinusT * t * p1.x + t * t * p2.x,\n y: oneMinusT * oneMinusT * p0.y + 2 * oneMinusT * t * p1.y + t * t * p2.y,\n };\n}\n\n/**\n * Bezier tangent (derivative): B'(t) = 2(1-t)(P₁-P₀) + 2t(P₂-P₁)\n */\nfunction bezierTangent(p0: Point, p1: Point, p2: Point, t: number): Point {\n const oneMinusT = 1 - t;\n return {\n x: 2 * oneMinusT * (p1.x - p0.x) + 2 * t * (p2.x - p1.x),\n y: 2 * oneMinusT * (p1.y - p0.y) + 2 * t * (p2.y - p1.y),\n };\n}\n\n/**\n * Ease-in-out cubic for smooth acceleration/deceleration\n */\nfunction easeInOutCubic(t: number): number {\n return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;\n}\n\nexport interface BezierFlightCallbacks {\n onFrame: (position: Point, rotation: number, scale: number) => void;\n onComplete: () => void;\n}\n\n/**\n * Animate cursor along a parabolic bezier arc from start to end.\n * Used when the AI points at a UI element.\n *\n * @param from - Starting position\n * @param to - Target position\n * @param durationMs - Flight duration in milliseconds\n * @param callbacks - Frame and completion callbacks\n * @returns Cancel function to stop the animation\n */\nexport function animateBezierFlight(\n from: Point,\n to: Point,\n durationMs: number,\n callbacks: BezierFlightCallbacks,\n): () => void {\n const startTime = performance.now();\n const distance = Math.hypot(to.x - from.x, to.y - from.y);\n\n // Control point: offset upward by 20% of distance (creates parabolic arc)\n const controlPoint: Point = {\n x: (from.x + to.x) / 2,\n y: Math.min(from.y, to.y) - distance * 0.2,\n };\n\n let animationFrameId: number;\n\n function animate(now: number) {\n const elapsed = now - startTime;\n const linearProgress = Math.min(elapsed / durationMs, 1);\n const easedProgress = easeInOutCubic(linearProgress);\n\n const position = quadraticBezier(from, controlPoint, to, easedProgress);\n const tangent = bezierTangent(from, controlPoint, to, easedProgress);\n const rotation = Math.atan2(tangent.y, tangent.x);\n\n // Scale pulse: grows to 1.3x at midpoint, returns to 1x\n const scale = 1 + Math.sin(linearProgress * Math.PI) * 0.3;\n\n callbacks.onFrame(position, rotation, scale);\n\n if (linearProgress < 1) {\n animationFrameId = requestAnimationFrame(animate);\n } else {\n callbacks.onComplete();\n }\n }\n\n animationFrameId = requestAnimationFrame(animate);\n\n return () => cancelAnimationFrame(animationFrameId);\n}\n","import {\n $buddyPosition,\n $buddyRotation,\n $buddyScale,\n $cursorPosition,\n $pointingTarget,\n} from \"../atoms\"\nimport { animateBezierFlight } from \"../bezier\"\nimport type { PointingTarget } from \"../types\"\n\nconst POINTING_LOCK_TIMEOUT_MS = 10_000\n\ntype PointerMode = \"follow\" | \"flying\" | \"anchored\"\n\n/**\n * Controller for cursor pointing behavior.\n * Manages the pointer state machine (follow -> flying -> anchored -> follow)\n * and cursor animation.\n */\nexport class PointerController {\n private mode: PointerMode = \"follow\"\n private cancelAnimation: (() => void) | null = null\n private releaseTimeout: ReturnType<typeof setTimeout> | null = null\n private listeners = new Set<() => void>()\n\n /**\n * Animate cursor to point at a target.\n */\n pointAt(target: PointingTarget): void {\n // Clear any previous pointing state\n this.release()\n\n this.mode = \"flying\"\n $pointingTarget.set(target)\n\n const startPos = $buddyPosition.get()\n const endPos = { x: target.x, y: target.y }\n\n this.cancelAnimation = animateBezierFlight(startPos, endPos, 800, {\n onFrame: (position, rotation, scale) => {\n $buddyPosition.set(position)\n $buddyRotation.set(rotation)\n $buddyScale.set(scale)\n },\n onComplete: () => {\n this.cancelAnimation = null\n this.mode = \"anchored\"\n $buddyPosition.set(endPos)\n $buddyRotation.set(0)\n $buddyScale.set(1)\n this.scheduleRelease()\n this.notify()\n },\n })\n\n this.notify()\n }\n\n /**\n * Release the cursor from pointing mode back to follow mode.\n */\n release(): void {\n // Cancel any in-progress animation\n if (this.cancelAnimation) {\n this.cancelAnimation()\n this.cancelAnimation = null\n }\n\n // Clear release timeout\n if (this.releaseTimeout) {\n clearTimeout(this.releaseTimeout)\n this.releaseTimeout = null\n }\n\n // Reset to follow mode\n this.mode = \"follow\"\n $pointingTarget.set(null)\n $buddyPosition.set($cursorPosition.get())\n $buddyRotation.set(0)\n $buddyScale.set(1)\n\n this.notify()\n }\n\n /**\n * Check if cursor is currently pointing (flying or anchored).\n */\n isPointing(): boolean {\n return this.mode !== \"follow\"\n }\n\n /**\n * Get current pointer mode.\n */\n getMode(): PointerMode {\n return this.mode\n }\n\n /**\n * Subscribe to pointer state changes.\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /**\n * Update buddy position to follow cursor when in follow mode.\n * Call this on cursor position changes.\n */\n updateFollowPosition(): void {\n if (this.mode === \"follow\") {\n $buddyPosition.set($cursorPosition.get())\n $buddyRotation.set(0)\n $buddyScale.set(1)\n }\n }\n\n private scheduleRelease(): void {\n this.releaseTimeout = setTimeout(() => {\n this.releaseTimeout = null\n this.release()\n }, POINTING_LOCK_TIMEOUT_MS)\n }\n\n private notify(): void {\n this.listeners.forEach((listener) => listener())\n }\n}\n","import type { PointingTarget } from \"./types\"\n\n/**\n * Parses [POINT:x,y:label] tags from AI responses.\n * Format matches the Swift Clicky app for consistency.\n */\n\nconst POINTING_TAG_REGEX = /\\[POINT:(\\d+),(\\d+):([^\\]]+)\\]\\s*$/\n\n/**\n * Extract pointing target from response text.\n * Returns null if no valid POINT tag is found at the end.\n */\nexport function parsePointingTag(response: string): PointingTarget | null {\n const match = response.match(POINTING_TAG_REGEX)\n if (!match) return null\n\n return {\n x: parseInt(match[1], 10),\n y: parseInt(match[2], 10),\n label: match[3].trim(),\n }\n}\n\n/**\n * Remove POINT tag from response text for display/TTS.\n */\nexport function stripPointingTag(response: string): string {\n return response.replace(POINTING_TAG_REGEX, \"\").trim()\n}\n","import { createStateMachine, type StateMachine } from \"./state-machine\"\nimport { VoiceCaptureService } from \"./services/voice-capture\"\nimport { AudioPlaybackService } from \"./services/audio-playback\"\nimport { ScreenCaptureService } from \"./services/screen-capture\"\nimport { PointerController } from \"./services/pointer-controller\"\nimport { $audioLevel, $conversationHistory, $isEnabled } from \"./atoms\"\nimport { parsePointingTag, stripPointingTag } from \"./pointing\"\nimport type {\n VoiceState,\n CursorBuddyClientOptions,\n CursorBuddySnapshot,\n PointingTarget,\n ScreenshotResult,\n ConversationMessage,\n} from \"./types\"\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\nfunction mapPointToViewport(\n target: PointingTarget,\n screenshot: ScreenshotResult\n): PointingTarget {\n if (screenshot.width <= 0 || screenshot.height <= 0) {\n return target\n }\n\n const scaleX = screenshot.viewportWidth / screenshot.width\n const scaleY = screenshot.viewportHeight / screenshot.height\n\n return {\n ...target,\n x: clamp(\n Math.round(target.x * scaleX),\n 0,\n Math.max(screenshot.viewportWidth - 1, 0)\n ),\n y: clamp(\n Math.round(target.y * scaleY),\n 0,\n Math.max(screenshot.viewportHeight - 1, 0)\n ),\n }\n}\n\n/**\n * Internal services interface for dependency injection (testing).\n */\nexport interface CursorBuddyServices {\n voiceCapture?: VoiceCaptureService\n audioPlayback?: AudioPlaybackService\n screenCapture?: ScreenCaptureService\n pointerController?: PointerController\n}\n\n/**\n * Framework-agnostic client for cursor buddy voice interactions.\n *\n * Manages the complete voice interaction flow:\n * idle -> listening -> processing -> responding -> idle\n *\n * Supports interruption: pressing hotkey during any state aborts\n * in-flight work and immediately transitions to listening.\n */\nexport class CursorBuddyClient {\n private endpoint: string\n private options: CursorBuddyClientOptions\n\n // Services\n private voiceCapture: VoiceCaptureService\n private audioPlayback: AudioPlaybackService\n private screenCapture: ScreenCaptureService\n private pointerController: PointerController\n private stateMachine: StateMachine\n\n // State\n private transcript = \"\"\n private response = \"\"\n private error: Error | null = null\n private abortController: AbortController | null = null\n\n // Cached snapshot for useSyncExternalStore (must be referentially stable)\n private cachedSnapshot: CursorBuddySnapshot\n\n // Subscriptions\n private listeners = new Set<() => void>()\n\n constructor(\n endpoint: string,\n options: CursorBuddyClientOptions = {},\n services: CursorBuddyServices = {}\n ) {\n this.endpoint = endpoint\n this.options = options\n\n // Initialize services (allow injection for testing)\n this.voiceCapture = services.voiceCapture ?? new VoiceCaptureService()\n this.audioPlayback = services.audioPlayback ?? new AudioPlaybackService()\n this.screenCapture = services.screenCapture ?? new ScreenCaptureService()\n this.pointerController = services.pointerController ?? new PointerController()\n this.stateMachine = createStateMachine()\n\n // Initialize cached snapshot\n this.cachedSnapshot = this.buildSnapshot()\n\n // Wire up audio level to atom\n this.voiceCapture.onLevel((level) => $audioLevel.set(level))\n\n // Wire up state machine changes\n this.stateMachine.subscribe(() => {\n this.options.onStateChange?.(this.stateMachine.getState())\n this.notify()\n })\n\n // Wire up pointer controller\n this.pointerController.subscribe(() => this.notify())\n }\n\n // === Public API ===\n\n /**\n * Start listening for voice input.\n * Aborts any in-flight work from previous session.\n */\n startListening(): void {\n // 1. Abort previous session synchronously\n this.abort()\n\n // 2. Clear UI state immediately\n this.transcript = \"\"\n this.response = \"\"\n this.error = null\n this.pointerController.release()\n\n // 3. Transition state\n this.stateMachine.transition({ type: \"HOTKEY_PRESSED\" })\n this.notify()\n\n // 4. Start mic (async, errors go to error state)\n this.abortController = new AbortController()\n this.voiceCapture.start().catch((err) => this.handleError(err))\n }\n\n /**\n * Stop listening and process the voice input.\n */\n async stopListening(): Promise<void> {\n if (this.stateMachine.getState() !== \"listening\") return\n\n this.stateMachine.transition({ type: \"HOTKEY_RELEASED\" })\n const signal = this.abortController?.signal\n\n try {\n // Stop mic, capture screenshot in parallel\n const [audioBlob, screenshot] = await Promise.all([\n this.voiceCapture.stop(),\n this.screenCapture.capture(),\n ])\n\n if (signal?.aborted) return\n\n // Transcribe\n const transcript = await this.transcribe(audioBlob, signal)\n if (signal?.aborted) return\n\n this.transcript = transcript\n this.options.onTranscript?.(transcript)\n this.notify()\n\n // Chat\n const response = await this.chat(transcript, screenshot, signal)\n if (signal?.aborted) return\n\n // Parse pointing tag and strip from response\n const pointTarget = parsePointingTag(response)\n const cleanResponse = stripPointingTag(response)\n\n this.response = cleanResponse\n this.stateMachine.transition({\n type: \"AI_RESPONSE_COMPLETE\",\n response: cleanResponse,\n })\n this.options.onResponse?.(cleanResponse)\n\n // Update history (only on success, never partial)\n const history = $conversationHistory.get()\n const newHistory: ConversationMessage[] = [\n ...history,\n { role: \"user\", content: transcript },\n { role: \"assistant\", content: cleanResponse },\n ]\n $conversationHistory.set(newHistory)\n\n // Point if needed\n if (pointTarget) {\n const mappedTarget = mapPointToViewport(pointTarget, screenshot)\n this.options.onPoint?.(mappedTarget)\n this.pointerController.pointAt(mappedTarget)\n }\n\n // TTS\n if (cleanResponse) {\n await this.speak(cleanResponse, signal)\n }\n if (signal?.aborted) return\n\n this.stateMachine.transition({ type: \"TTS_COMPLETE\" })\n } catch (err) {\n // Interruption is not an error\n if (signal?.aborted) return\n this.handleError(err instanceof Error ? err : new Error(\"Unknown error\"))\n }\n }\n\n /**\n * Enable or disable the buddy.\n */\n setEnabled(enabled: boolean): void {\n $isEnabled.set(enabled)\n this.notify()\n }\n\n /**\n * Manually point at coordinates.\n */\n pointAt(x: number, y: number, label: string): void {\n this.pointerController.pointAt({ x, y, label })\n }\n\n /**\n * Dismiss the current pointing target.\n */\n dismissPointing(): void {\n this.pointerController.release()\n }\n\n /**\n * Reset to idle state and stop any in-progress work.\n */\n reset(): void {\n this.abort()\n this.transcript = \"\"\n this.response = \"\"\n this.error = null\n this.pointerController.release()\n this.stateMachine.reset()\n this.notify()\n }\n\n /**\n * Update buddy position to follow cursor.\n * Call this on cursor position changes.\n */\n updateCursorPosition(): void {\n this.pointerController.updateFollowPosition()\n }\n\n /**\n * Subscribe to state changes.\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /**\n * Get current state snapshot for React's useSyncExternalStore.\n * Returns a cached object to ensure referential stability.\n */\n getSnapshot(): CursorBuddySnapshot {\n return this.cachedSnapshot\n }\n\n /**\n * Build a new snapshot object.\n */\n private buildSnapshot(): CursorBuddySnapshot {\n return {\n state: this.stateMachine.getState(),\n transcript: this.transcript,\n response: this.response,\n error: this.error,\n isPointing: this.pointerController.isPointing(),\n isEnabled: $isEnabled.get(),\n }\n }\n\n // === Private Methods ===\n\n private abort(): void {\n this.abortController?.abort()\n this.abortController = null\n this.audioPlayback.stop()\n // Reset audio level on abort\n $audioLevel.set(0)\n }\n\n private async transcribe(blob: Blob, signal?: AbortSignal): Promise<string> {\n const formData = new FormData()\n formData.append(\"audio\", blob, \"recording.wav\")\n\n const response = await fetch(`${this.endpoint}/transcribe`, {\n method: \"POST\",\n body: formData,\n signal,\n })\n\n if (!response.ok) {\n throw new Error(\"Transcription failed\")\n }\n\n const { text } = await response.json()\n return text\n }\n\n private async chat(\n transcript: string,\n screenshot: ScreenshotResult,\n signal?: AbortSignal\n ): Promise<string> {\n const history = $conversationHistory.get()\n\n const response = await fetch(`${this.endpoint}/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n screenshot: screenshot.imageData,\n capture: {\n width: screenshot.width,\n height: screenshot.height,\n },\n transcript,\n history,\n }),\n signal,\n })\n\n if (!response.ok) {\n throw new Error(\"Chat request failed\")\n }\n\n // Stream the response\n const reader = response.body?.getReader()\n if (!reader) throw new Error(\"No response body\")\n\n const decoder = new TextDecoder()\n let fullResponse = \"\"\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n const chunk = decoder.decode(value, { stream: true })\n fullResponse += chunk\n\n // Update response progressively for UI\n this.response = stripPointingTag(fullResponse)\n this.notify()\n }\n\n return fullResponse\n }\n\n private async speak(text: string, signal?: AbortSignal): Promise<void> {\n const response = await fetch(`${this.endpoint}/tts`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ text }),\n signal,\n })\n\n if (!response.ok) {\n throw new Error(\"TTS request failed\")\n }\n\n const audioBlob = await response.blob()\n await this.audioPlayback.play(audioBlob, signal)\n }\n\n private handleError(err: Error): void {\n this.error = err\n this.stateMachine.transition({ type: \"ERROR\", error: err })\n this.options.onError?.(err)\n this.notify()\n }\n\n private notify(): void {\n // Update cached snapshot before notifying (required for useSyncExternalStore)\n this.cachedSnapshot = this.buildSnapshot()\n this.listeners.forEach((listener) => listener())\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client-DKZY5bI1.d.mts","names":[],"sources":["../src/core/services/voice-capture.ts","../src/core/services/audio-playback.ts","../src/core/types.ts","../src/core/services/screen-capture.ts","../src/core/services/pointer-controller.ts","../src/core/client.ts"],"mappings":"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"}
|