cursor-buddy 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 leojuriolli7
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,314 @@
1
+ # cursor-buddy
2
+
3
+ AI-powered cursor companion for web apps. Push-to-talk voice assistant that can see your screen and point at things.
4
+
5
+ ## Features
6
+
7
+ - **Push-to-talk voice input** — Hold a hotkey to speak, release to send
8
+ - **Screenshot context** — AI sees your current viewport
9
+ - **Voice responses** — Text-to-speech playback
10
+ - **Cursor pointing** — AI can point at UI elements it references
11
+ - **Framework agnostic** — Adapter-based server, works with Next.js, Express, Hono
12
+ - **Customizable** — CSS variables, custom components, headless mode
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install cursor-buddy
18
+ # or
19
+ pnpm add cursor-buddy
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### 1. Server Setup
25
+
26
+ Create an API route that handles chat, transcription, and TTS.
27
+
28
+ ```ts
29
+ // lib/cursor-buddy.ts
30
+ import { createCursorBuddyHandler } from "cursor-buddy/server"
31
+ import { openai } from "@ai-sdk/openai"
32
+
33
+ export const cursorBuddy = createCursorBuddyHandler({
34
+ model: openai("gpt-4o"),
35
+ speechModel: openai.speech("tts-1"),
36
+ transcriptionModel: openai.transcription("whisper-1"),
37
+ })
38
+ ```
39
+
40
+ #### Next.js App Router
41
+
42
+ ```ts
43
+ // app/api/cursor-buddy/[...path]/route.ts
44
+ import { toNextJsHandler } from "cursor-buddy/server/next"
45
+ import { cursorBuddy } from "@/lib/cursor-buddy"
46
+
47
+ export const { GET, POST } = toNextJsHandler(cursorBuddy)
48
+ ```
49
+
50
+ ### 2. Client Setup
51
+
52
+ Add the `<CursorBuddy />` component to your app.
53
+
54
+ ```tsx
55
+ // app/layout.tsx
56
+ import { CursorBuddy } from "cursor-buddy/client"
57
+
58
+ export default function RootLayout({ children }) {
59
+ return (
60
+ <html>
61
+ <body>
62
+ {children}
63
+ <CursorBuddy endpoint="/api/cursor-buddy" />
64
+ </body>
65
+ </html>
66
+ )
67
+ }
68
+ ```
69
+
70
+ That's it! Hold **Ctrl+Alt** to speak, release to send.
71
+
72
+ ## Server Configuration
73
+
74
+ ```ts
75
+ createCursorBuddyHandler({
76
+ // Required
77
+ model: LanguageModel, // AI SDK chat model
78
+ speechModel: SpeechModel, // AI SDK speech model
79
+ transcriptionModel: TranscriptionModel, // AI SDK transcription model
80
+
81
+ // Optional
82
+ system: string | ((ctx) => string), // Custom system prompt
83
+ tools: Record<string, Tool>, // AI SDK tools
84
+ maxHistory: number, // Max conversation history (default: 10)
85
+ })
86
+ ```
87
+
88
+ ### Custom System Prompt
89
+
90
+ ```ts
91
+ createCursorBuddyHandler({
92
+ model: openai("gpt-4o"),
93
+ speechModel: openai.speech("tts-1"),
94
+ transcriptionModel: openai.transcription("whisper-1"),
95
+
96
+ // Extend the default prompt
97
+ system: ({ defaultPrompt }) => `
98
+ ${defaultPrompt}
99
+
100
+ You are helping users navigate a project management dashboard.
101
+ The sidebar contains: Projects, Tasks, Calendar, Settings.
102
+ `,
103
+ })
104
+ ```
105
+
106
+ ## Client Configuration
107
+
108
+ ```tsx
109
+ <CursorBuddy
110
+ // Required
111
+ endpoint="/api/cursor-buddy"
112
+
113
+ // Optional
114
+ hotkey="ctrl+alt" // Push-to-talk hotkey (default: "ctrl+alt")
115
+ muted={false} // Disable TTS playback
116
+ container={element} // Portal container (default: document.body)
117
+
118
+ // Custom components
119
+ cursor={(props) => <CustomCursor {...props} />}
120
+ speechBubble={(props) => <CustomBubble {...props} />}
121
+ waveform={(props) => <CustomWaveform {...props} />}
122
+
123
+ // Callbacks
124
+ onTranscript={(text) => {}} // Called when speech is transcribed
125
+ onResponse={(text) => {}} // Called when AI responds
126
+ onPoint={(target) => {}} // Called when AI points at element
127
+ onStateChange={(state) => {}} // Called on state change
128
+ onError={(error) => {}} // Called on error
129
+ />
130
+ ```
131
+
132
+ ## Customization
133
+
134
+ ### CSS Variables
135
+
136
+ Cursor buddy styles are customizable via CSS variables. Override them in your stylesheet:
137
+
138
+ ```css
139
+ :root {
140
+ /* Cursor colors by state */
141
+ --cursor-buddy-color-idle: #3b82f6;
142
+ --cursor-buddy-color-listening: #ef4444;
143
+ --cursor-buddy-color-processing: #eab308;
144
+ --cursor-buddy-color-responding: #22c55e;
145
+
146
+ /* Speech bubble */
147
+ --cursor-buddy-bubble-bg: #ffffff;
148
+ --cursor-buddy-bubble-text: #1f2937;
149
+ --cursor-buddy-bubble-radius: 8px;
150
+ --cursor-buddy-bubble-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
151
+
152
+ /* Waveform */
153
+ --cursor-buddy-waveform-color: #ef4444;
154
+ }
155
+ ```
156
+
157
+ ### Custom Components
158
+
159
+ Replace default components with your own:
160
+
161
+ ```tsx
162
+ import { CursorBuddy, type CursorRenderProps } from "cursor-buddy/client"
163
+
164
+ function MyCursor({ state, rotation, scale }: CursorRenderProps) {
165
+ return (
166
+ <div style={{ transform: `rotate(${rotation}rad) scale(${scale})` }}>
167
+ {state === "listening" ? "🎤" : "👆"}
168
+ </div>
169
+ )
170
+ }
171
+
172
+ <CursorBuddy
173
+ endpoint="/api/cursor-buddy"
174
+ cursor={(props) => <MyCursor {...props} />}
175
+ />
176
+ ```
177
+
178
+ ## Headless Mode
179
+
180
+ For full control, use the provider and hook directly:
181
+
182
+ ```tsx
183
+ import {
184
+ CursorBuddyProvider,
185
+ useCursorBuddy
186
+ } from "cursor-buddy/client"
187
+
188
+ function App() {
189
+ return (
190
+ <CursorBuddyProvider endpoint="/api/cursor-buddy">
191
+ <MyCustomUI />
192
+ </CursorBuddyProvider>
193
+ )
194
+ }
195
+
196
+ function MyCustomUI() {
197
+ const {
198
+ state, // "idle" | "listening" | "processing" | "responding"
199
+ transcript, // Latest user speech
200
+ response, // Latest AI response
201
+ audioLevel, // 0-1, for waveform visualization
202
+ isEnabled,
203
+ isSpeaking,
204
+ isPointing,
205
+ error,
206
+
207
+ // Actions
208
+ startListening,
209
+ stopListening,
210
+ setEnabled,
211
+ speak, // Manually trigger TTS
212
+ pointAt, // Manually point at coordinates
213
+ reset,
214
+ } = useCursorBuddy()
215
+
216
+ return (
217
+ <div>
218
+ <p>State: {state}</p>
219
+ <button
220
+ onMouseDown={startListening}
221
+ onMouseUp={stopListening}
222
+ >
223
+ Hold to speak
224
+ </button>
225
+ </div>
226
+ )
227
+ }
228
+ ```
229
+
230
+ ## Render Props Types
231
+
232
+ ```ts
233
+ interface CursorRenderProps {
234
+ state: "idle" | "listening" | "processing" | "responding"
235
+ isPointing: boolean
236
+ rotation: number // Radians, direction of travel
237
+ scale: number // 1.0 normal, up to 1.3 during flight
238
+ }
239
+
240
+ interface SpeechBubbleRenderProps {
241
+ text: string
242
+ isVisible: boolean
243
+ }
244
+
245
+ interface WaveformRenderProps {
246
+ audioLevel: number // 0-1
247
+ isListening: boolean
248
+ }
249
+ ```
250
+
251
+ ## API Reference
252
+
253
+ ### Server Exports (`cursor-buddy/server`)
254
+
255
+ | Export | Description |
256
+ |--------|-------------|
257
+ | `createCursorBuddyHandler` | Create the main request handler |
258
+ | `DEFAULT_SYSTEM_PROMPT` | Default system prompt for reference |
259
+ | `CursorBuddyHandlerConfig` | Type for handler configuration |
260
+ | `CursorBuddyHandler` | Return type of `createCursorBuddyHandler` |
261
+
262
+ ### Server Adapters (`cursor-buddy/server/next`)
263
+
264
+ | Export | Description |
265
+ |--------|-------------|
266
+ | `toNextJsHandler` | Convert handler to Next.js App Router format |
267
+
268
+ ### Client Exports (`cursor-buddy/client`)
269
+
270
+ | Export | Description |
271
+ |--------|-------------|
272
+ | `CursorBuddy` | Drop-in component with built-in UI |
273
+ | `CursorBuddyProvider` | Headless provider for custom UI |
274
+ | `useCursorBuddy` | Hook to access state and actions |
275
+
276
+ ### Types (`cursor-buddy/client`)
277
+
278
+ | Export | Description |
279
+ |--------|-------------|
280
+ | `CursorBuddyProps` | Props for `<CursorBuddy />` |
281
+ | `CursorBuddyProviderProps` | Props for `<CursorBuddyProvider />` |
282
+ | `CursorBuddyContextValue` | Return type of `useCursorBuddy()` |
283
+ | `CursorRenderProps` | Props passed to custom cursor |
284
+ | `SpeechBubbleRenderProps` | Props passed to custom speech bubble |
285
+ | `WaveformRenderProps` | Props passed to custom waveform |
286
+
287
+ ### Core Types (`cursor-buddy`)
288
+
289
+ | Export | Description |
290
+ |--------|-------------|
291
+ | `VoiceState` | `"idle" \| "listening" \| "processing" \| "responding"` |
292
+ | `PointingTarget` | `{ x: number, y: number, label: string }` |
293
+ | `Point` | `{ x: number, y: number }` |
294
+
295
+ ## How It Works
296
+
297
+ 1. User holds the hotkey (Ctrl+Alt)
298
+ 2. Microphone captures audio, waveform shows audio level
299
+ 3. User releases hotkey
300
+ 4. Screenshot of viewport is captured
301
+ 5. Audio is transcribed via AI SDK
302
+ 6. Screenshot + capture metadata sent to AI model
303
+ 7. AI responds with text, optionally including `[POINT:x,y:label]` tag in screenshot-image coordinates
304
+ 8. Response is spoken via TTS
305
+ 9. If pointing tag present, coordinates are mapped back to the live viewport and the cursor animates to the target location
306
+
307
+ ## TODOS
308
+ - [ ] Faster transcription -> chat -> TTS flow (eg single endpoint instead of 3 calls)
309
+ - [ ] Composition pattern for custom components
310
+ - [ ] Better hotkey management
311
+
312
+ ## License
313
+
314
+ MIT
@@ -0,0 +1,151 @@
1
+ "use client";
2
+ import { a as VoiceState, i as SpeechBubbleRenderProps, o as WaveformRenderProps, r as PointingTarget, t as CursorRenderProps } from "../types-b2KrNyuu.mjs";
3
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
4
+
5
+ //#region src/client/components/Overlay.d.ts
6
+ interface OverlayProps {
7
+ /** Custom cursor renderer */
8
+ cursor?: React.ReactNode | ((props: CursorRenderProps) => React.ReactNode);
9
+ /** Custom speech bubble renderer */
10
+ speechBubble?: (props: SpeechBubbleRenderProps) => React.ReactNode;
11
+ /** Custom waveform renderer */
12
+ waveform?: (props: WaveformRenderProps) => React.ReactNode;
13
+ /** Container element for portal (defaults to document.body) */
14
+ container?: HTMLElement | null;
15
+ }
16
+ //#endregion
17
+ //#region src/client/CursorBuddy.d.ts
18
+ interface CursorBuddyProps extends Pick<OverlayProps, "cursor" | "speechBubble" | "waveform"> {
19
+ /** API endpoint for cursor buddy server */
20
+ endpoint: string;
21
+ /** Hotkey for push-to-talk (default: "ctrl+alt") */
22
+ hotkey?: string;
23
+ /** Whether TTS is muted */
24
+ muted?: boolean;
25
+ /** Container element for portal (defaults to document.body) */
26
+ container?: HTMLElement | null;
27
+ /** Callback when transcript is ready */
28
+ onTranscript?: (text: string) => void;
29
+ /** Callback when AI responds */
30
+ onResponse?: (text: string) => void;
31
+ /** Callback when pointing at element */
32
+ onPoint?: (target: PointingTarget) => void;
33
+ /** Callback when state changes */
34
+ onStateChange?: (state: VoiceState) => void;
35
+ /** Callback when error occurs */
36
+ onError?: (error: Error) => void;
37
+ }
38
+ /**
39
+ * Drop-in cursor buddy component.
40
+ *
41
+ * Adds an AI-powered cursor companion to your app. Users hold the hotkey
42
+ * (default: Ctrl+Alt) to speak. The SDK captures a screenshot, transcribes
43
+ * the speech, sends it to the AI, speaks the response, and can point at
44
+ * elements on screen.
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * import { CursorBuddy } from "cursor-buddy/client"
49
+ *
50
+ * function App() {
51
+ * return (
52
+ * <>
53
+ * <YourApp />
54
+ * <CursorBuddy endpoint="/api/cursor-buddy" />
55
+ * </>
56
+ * )
57
+ * }
58
+ * ```
59
+ */
60
+ declare function CursorBuddy({
61
+ endpoint,
62
+ hotkey,
63
+ muted,
64
+ container,
65
+ cursor,
66
+ speechBubble,
67
+ waveform,
68
+ onTranscript,
69
+ onResponse,
70
+ onPoint,
71
+ onStateChange,
72
+ onError
73
+ }: CursorBuddyProps): _$react_jsx_runtime0.JSX.Element;
74
+ //#endregion
75
+ //#region src/client/CursorBuddyProvider.d.ts
76
+ interface CursorBuddyProviderProps {
77
+ /** API endpoint for cursor buddy server */
78
+ endpoint: string;
79
+ /** Whether TTS is muted */
80
+ muted?: boolean;
81
+ /** Callback when transcript is ready */
82
+ onTranscript?: (text: string) => void;
83
+ /** Callback when AI responds */
84
+ onResponse?: (text: string) => void;
85
+ /** Callback when pointing at element */
86
+ onPoint?: (target: {
87
+ x: number;
88
+ y: number;
89
+ label: string;
90
+ }) => void;
91
+ /** Callback when state changes */
92
+ onStateChange?: (state: VoiceState) => void;
93
+ /** Callback when error occurs */
94
+ onError?: (error: Error) => void;
95
+ /** Children */
96
+ children: React.ReactNode;
97
+ }
98
+ declare function CursorBuddyProvider({
99
+ endpoint,
100
+ muted,
101
+ onTranscript,
102
+ onResponse,
103
+ onPoint,
104
+ onStateChange,
105
+ onError,
106
+ children
107
+ }: CursorBuddyProviderProps): _$react_jsx_runtime0.JSX.Element;
108
+ //#endregion
109
+ //#region src/client/context.d.ts
110
+ interface CursorBuddyContextValue {
111
+ /** Current voice state */
112
+ state: VoiceState;
113
+ /** Latest transcribed user speech */
114
+ transcript: string;
115
+ /** Latest AI response (stripped of POINT tags) */
116
+ response: string;
117
+ /** Current audio level (0-1) */
118
+ audioLevel: number;
119
+ /** Whether the buddy is enabled */
120
+ isEnabled: boolean;
121
+ /** Whether TTS is currently playing */
122
+ isSpeaking: boolean;
123
+ /** Whether currently engaged with a pointing target */
124
+ isPointing: boolean;
125
+ /** Current error (null if none) */
126
+ error: Error | null;
127
+ /** Start listening (called automatically by hotkey) */
128
+ startListening: () => void;
129
+ /** Stop listening and process (called automatically by hotkey release) */
130
+ stopListening: () => void;
131
+ /** Enable or disable the buddy */
132
+ setEnabled: (enabled: boolean) => void;
133
+ /** Manually speak text via TTS */
134
+ speak: (text: string) => Promise<void>;
135
+ /** Manually point at coordinates */
136
+ pointAt: (x: number, y: number, label: string) => void;
137
+ /** Dismiss the current pointing target and return to follow mode */
138
+ dismissPointing: () => void;
139
+ /** Reset to idle state */
140
+ reset: () => void;
141
+ }
142
+ //#endregion
143
+ //#region src/client/hooks/useCursorBuddy.d.ts
144
+ /**
145
+ * Hook to access cursor buddy state and actions.
146
+ * Must be used within a CursorBuddyProvider.
147
+ */
148
+ declare function useCursorBuddy(): CursorBuddyContextValue;
149
+ //#endregion
150
+ export { CursorBuddy, type CursorBuddyContextValue, type CursorBuddyProps, CursorBuddyProvider, type CursorBuddyProviderProps, type CursorRenderProps, type SpeechBubbleRenderProps, type WaveformRenderProps, useCursorBuddy };
151
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/client/components/Overlay.tsx","../../src/client/CursorBuddy.tsx","../../src/client/CursorBuddyProvider.tsx","../../src/client/context.ts","../../src/client/hooks/useCursorBuddy.ts"],"mappings":";;;;UAoBiB,YAAA;;EAEf,MAAA,GAAS,KAAA,CAAM,SAAA,KAAc,KAAA,EAAO,iBAAA,KAAsB,KAAA,CAAM,SAAA;;EAEhE,YAAA,IAAgB,KAAA,EAAO,uBAAA,KAA4B,KAAA,CAAM,SAAA;EAJ9B;EAM3B,QAAA,IAAY,KAAA,EAAO,mBAAA,KAAwB,KAAA,CAAM,SAAA;EAJxC;EAMT,SAAA,GAAY,WAAA;AAAA;;;UChBG,gBAAA,SACP,IAAA,CAAK,YAAA;;EAEb,QAAA;EDKe;ECHf,MAAA;;EAEA,KAAA;EDGoC;ECDpC,SAAA,GAAY,WAAA;EDGW;ECDvB,YAAA,IAAgB,IAAA;EDGG;ECDnB,UAAA,IAAc,IAAA;EDGF;ECDZ,OAAA,IAAW,MAAA,EAAQ,cAAA;EDCI;ECCvB,aAAA,IAAiB,KAAA,EAAO,UAAA;EDPf;ECST,OAAA,IAAW,KAAA,EAAO,KAAA;AAAA;;;;;;;;;;;;;;;;;;;;;AAnBpB;;iBAwEgB,WAAA,CAAA;EACd,QAAA;EACA,MAAA;EACA,KAAA;EACA,SAAA;EACA,MAAA;EACA,YAAA;EACA,QAAA;EACA,YAAA;EACA,UAAA;EACA,OAAA;EACA,aAAA;EACA;AAAA,GACC,gBAAA,GAAgB,oBAAA,CAAA,GAAA,CAAA,OAAA;;;UC9DF,wBAAA;;EAEf,QAAA;;EAEA,KAAA;EFnB2B;EEqB3B,YAAA,IAAgB,IAAA;EFnBP;EEqBT,UAAA,IAAc,IAAA;EFrB4C;EEuB1D,OAAA,IAAW,MAAA;IAAU,CAAA;IAAW,CAAA;IAAW,KAAA;EAAA;EFjBpB;EEmBvB,aAAA,IAAiB,KAAA,EAAO,UAAA;EFzBxB;EE2BA,OAAA,IAAW,KAAA,EAAO,KAAA;EF3BH;EE6Bf,QAAA,EAAU,KAAA,CAAM,SAAA;AAAA;AAAA,iBAiCF,mBAAA,CAAA;EACd,QAAA;EACA,KAAA;EACA,YAAA;EACA,UAAA;EACA,OAAA;EACA,aAAA;EACA,OAAA;EACA;AAAA,GACC,wBAAA,GAAwB,oBAAA,CAAA,GAAA,CAAA,OAAA;;;UC1FV,uBAAA;;EAEf,KAAA,EAAO,UAAA;;EAEP,UAAA;EHa2B;EGX3B,QAAA;EHaS;EGXT,UAAA;EHW0D;EGT1D,SAAA;EHWmD;EGTnD,UAAA;EHW2C;EGT3C,UAAA;EHWuB;EGTvB,KAAA,EAAO,KAAA;EHGP;EGAA,cAAA;EHAe;EGEf,aAAA;EHF6B;EGI7B,UAAA,GAAa,OAAA;EHJmD;EGMhE,KAAA,GAAQ,IAAA,aAAiB,OAAA;EHJF;EGMvB,OAAA,GAAU,CAAA,UAAW,CAAA,UAAW,KAAA;EHNmB;EGQnD,eAAA;EHNA;EGQA,KAAA;AAAA;;;;;;;iBC3Bc,cAAA,CAAA,GAAkB,uBAAA"}