cursor-buddy 0.0.10 → 0.0.11
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/README.md +196 -22
- package/dist/{client-CliXcNch.mjs → client-D7kFGsuH.mjs} +634 -300
- package/dist/client-D7kFGsuH.mjs.map +1 -0
- package/dist/client-DoqSfCbo.d.mts +82 -0
- package/dist/client-DoqSfCbo.d.mts.map +1 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +1 -1
- package/dist/{point-tool-l3FewgM9.d.mts → point-tool-B_s8op--.d.mts} +3 -9
- package/dist/point-tool-B_s8op--.d.mts.map +1 -0
- package/dist/point-tool-DZJmhD8e.mjs.map +1 -1
- package/dist/react/index.d.mts +83 -6
- package/dist/react/index.d.mts.map +1 -1
- package/dist/react/index.mjs +268 -13
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/adapters/next.d.mts +1 -1
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.mjs +84 -28
- package/dist/server/index.mjs.map +1 -1
- package/dist/{client-sjVVGYPU.d.mts → types-BU0Gegg2.d.mts} +123 -180
- package/dist/types-BU0Gegg2.d.mts.map +1 -0
- package/dist/{types-BJfkApb_.d.mts → types-ClkvIgAm.d.mts} +1 -1
- package/dist/{types-BJfkApb_.d.mts.map → types-ClkvIgAm.d.mts.map} +1 -1
- package/package.json +3 -2
- package/dist/client-CliXcNch.mjs.map +0 -1
- package/dist/client-sjVVGYPU.d.mts.map +0 -1
- package/dist/point-tool-l3FewgM9.d.mts.map +0 -1
package/dist/react/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { c as CursorRenderProps, d as SpeechBubbleRenderProps,
|
|
2
|
+
import { _ as ToolCallState, b as ToolDisplayOptions, c as CursorRenderProps, d as SpeechBubbleRenderProps, g as ToolCallEvent, h as ToolBubbleRenderProps, m as WaveformRenderProps, n as CursorBuddyClientOptions, o as CursorBuddySpeechConfig, p as VoiceState, r as CursorBuddyMediaMode, s as CursorBuddyTranscriptionConfig, u as PointingTarget, v as ToolCallStatus, x as ToolResultEvent, y as ToolDisplayConfig } from "../types-BU0Gegg2.mjs";
|
|
3
3
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
4
|
|
|
5
5
|
//#region src/react/components/Overlay.d.ts
|
|
@@ -10,12 +10,16 @@ interface OverlayProps {
|
|
|
10
10
|
speechBubble?: (props: SpeechBubbleRenderProps) => React.ReactNode;
|
|
11
11
|
/** Custom waveform renderer */
|
|
12
12
|
waveform?: (props: WaveformRenderProps) => React.ReactNode;
|
|
13
|
+
/** Tool display configuration */
|
|
14
|
+
toolDisplay?: ToolDisplayConfig;
|
|
15
|
+
/** Custom tool bubble renderer (overrides per-tool config) */
|
|
16
|
+
renderToolBubble?: (props: ToolBubbleRenderProps) => React.ReactNode;
|
|
13
17
|
/** Container element for portal (defaults to document.body) */
|
|
14
18
|
container?: HTMLElement | null;
|
|
15
19
|
}
|
|
16
20
|
//#endregion
|
|
17
21
|
//#region src/react/components/CursorBuddy.d.ts
|
|
18
|
-
interface CursorBuddyProps extends Pick<OverlayProps, "cursor" | "speechBubble" | "waveform"> {
|
|
22
|
+
interface CursorBuddyProps extends Pick<OverlayProps, "cursor" | "speechBubble" | "waveform" | "toolDisplay" | "renderToolBubble"> {
|
|
19
23
|
/** API endpoint for cursor buddy server */
|
|
20
24
|
endpoint: string;
|
|
21
25
|
/** Hotkey for push-to-talk (default: "ctrl+alt") */
|
|
@@ -36,6 +40,10 @@ interface CursorBuddyProps extends Pick<OverlayProps, "cursor" | "speechBubble"
|
|
|
36
40
|
onStateChange?: (state: VoiceState) => void;
|
|
37
41
|
/** Callback when error occurs */
|
|
38
42
|
onError?: (error: Error) => void;
|
|
43
|
+
/** Callback when a tool is called */
|
|
44
|
+
onToolCall?: (event: ToolCallEvent) => void;
|
|
45
|
+
/** Callback when a tool completes */
|
|
46
|
+
onToolResult?: (event: ToolResultEvent) => void;
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
49
|
* Drop-in cursor buddy component.
|
|
@@ -69,11 +77,15 @@ declare function CursorBuddy({
|
|
|
69
77
|
cursor,
|
|
70
78
|
speechBubble,
|
|
71
79
|
waveform,
|
|
80
|
+
toolDisplay,
|
|
81
|
+
renderToolBubble,
|
|
72
82
|
onTranscript,
|
|
73
83
|
onResponse,
|
|
74
84
|
onPoint,
|
|
75
85
|
onStateChange,
|
|
76
|
-
onError
|
|
86
|
+
onError,
|
|
87
|
+
onToolCall,
|
|
88
|
+
onToolResult
|
|
77
89
|
}: CursorBuddyProps): _$react_jsx_runtime0.JSX.Element;
|
|
78
90
|
//#endregion
|
|
79
91
|
//#region src/react/hooks.d.ts
|
|
@@ -84,7 +96,7 @@ interface UseCursorBuddyReturn {
|
|
|
84
96
|
liveTranscript: string;
|
|
85
97
|
/** Latest transcribed user speech */
|
|
86
98
|
transcript: string;
|
|
87
|
-
/** Latest AI response
|
|
99
|
+
/** Latest AI response */
|
|
88
100
|
response: string;
|
|
89
101
|
/** Current audio level (0-1) */
|
|
90
102
|
audioLevel: number;
|
|
@@ -94,6 +106,12 @@ interface UseCursorBuddyReturn {
|
|
|
94
106
|
isPointing: boolean;
|
|
95
107
|
/** Current error (null if none) */
|
|
96
108
|
error: Error | null;
|
|
109
|
+
/** All tool calls in current turn */
|
|
110
|
+
toolCalls: ToolCallState[];
|
|
111
|
+
/** Visible, non-expired tool calls */
|
|
112
|
+
activeToolCalls: ToolCallState[];
|
|
113
|
+
/** Tool awaiting user approval, or null */
|
|
114
|
+
pendingApproval: ToolCallState | null;
|
|
97
115
|
/** Start listening (called automatically by hotkey) */
|
|
98
116
|
startListening: () => void;
|
|
99
117
|
/** Stop listening and process (called automatically by hotkey release) */
|
|
@@ -106,6 +124,12 @@ interface UseCursorBuddyReturn {
|
|
|
106
124
|
dismissPointing: () => void;
|
|
107
125
|
/** Reset to idle state */
|
|
108
126
|
reset: () => void;
|
|
127
|
+
/** Approve a pending tool call */
|
|
128
|
+
approveToolCall: (id: string) => void;
|
|
129
|
+
/** Deny a pending tool call */
|
|
130
|
+
denyToolCall: (id: string) => void;
|
|
131
|
+
/** Dismiss a tool call bubble manually */
|
|
132
|
+
dismissToolCall: (id: string) => void;
|
|
109
133
|
}
|
|
110
134
|
/**
|
|
111
135
|
* Hook to access cursor buddy state and actions.
|
|
@@ -130,13 +154,66 @@ declare function CursorBuddyProvider({
|
|
|
130
154
|
endpoint,
|
|
131
155
|
transcription,
|
|
132
156
|
speech,
|
|
157
|
+
toolDisplay,
|
|
133
158
|
children,
|
|
134
159
|
onTranscript,
|
|
135
160
|
onResponse,
|
|
136
161
|
onPoint,
|
|
137
162
|
onStateChange,
|
|
138
|
-
onError
|
|
163
|
+
onError,
|
|
164
|
+
onToolCall,
|
|
165
|
+
onToolResult
|
|
139
166
|
}: CursorBuddyProviderProps): _$react_jsx_runtime0.JSX.Element;
|
|
140
167
|
//#endregion
|
|
141
|
-
|
|
168
|
+
//#region src/react/components/ToolBubble.d.ts
|
|
169
|
+
interface ToolBubbleProps extends ToolBubbleRenderProps {
|
|
170
|
+
/** Custom render function (from toolDisplay config) */
|
|
171
|
+
customRender?: (props: ToolBubbleRenderProps) => React.ReactNode;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Default tool bubble component.
|
|
175
|
+
* Displays tool call status with optional approve/deny buttons.
|
|
176
|
+
*/
|
|
177
|
+
declare function ToolBubble({
|
|
178
|
+
toolName,
|
|
179
|
+
args,
|
|
180
|
+
status,
|
|
181
|
+
label,
|
|
182
|
+
result,
|
|
183
|
+
error,
|
|
184
|
+
approve,
|
|
185
|
+
deny,
|
|
186
|
+
dismiss,
|
|
187
|
+
customRender
|
|
188
|
+
}: ToolBubbleProps): _$react_jsx_runtime0.JSX.Element;
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/react/components/ToolBubbleStack.d.ts
|
|
191
|
+
interface ToolBubbleStackProps {
|
|
192
|
+
/** Active tool calls to display */
|
|
193
|
+
toolCalls: ToolCallState[];
|
|
194
|
+
/** Tool display configuration */
|
|
195
|
+
toolDisplay?: ToolDisplayConfig;
|
|
196
|
+
/** Approve a tool call */
|
|
197
|
+
onApprove: (id: string) => void;
|
|
198
|
+
/** Deny a tool call */
|
|
199
|
+
onDeny: (id: string) => void;
|
|
200
|
+
/** Dismiss a tool call bubble */
|
|
201
|
+
onDismiss: (id: string) => void;
|
|
202
|
+
/** Custom render function for all bubbles (overrides per-tool config) */
|
|
203
|
+
renderToolBubble?: (props: ToolBubbleRenderProps) => React.ReactNode;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Stack of tool bubbles displayed near the cursor.
|
|
207
|
+
* Renders all active tool calls in a vertical column.
|
|
208
|
+
*/
|
|
209
|
+
declare function ToolBubbleStack({
|
|
210
|
+
toolCalls,
|
|
211
|
+
toolDisplay,
|
|
212
|
+
onApprove,
|
|
213
|
+
onDeny,
|
|
214
|
+
onDismiss,
|
|
215
|
+
renderToolBubble
|
|
216
|
+
}: ToolBubbleStackProps): _$react_jsx_runtime0.JSX.Element | null;
|
|
217
|
+
//#endregion
|
|
218
|
+
export { CursorBuddy, type CursorBuddyMediaMode, type CursorBuddyProps, CursorBuddyProvider, type CursorBuddyProviderProps, type CursorBuddySpeechConfig, type CursorBuddyTranscriptionConfig, type CursorRenderProps, type SpeechBubbleRenderProps, ToolBubble, type ToolBubbleProps, type ToolBubbleRenderProps, ToolBubbleStack, type ToolBubbleStackProps, type ToolCallEvent, type ToolCallState, type ToolCallStatus, type ToolDisplayConfig, type ToolDisplayOptions, type ToolResultEvent, type UseCursorBuddyReturn, type WaveformRenderProps, useCursorBuddy };
|
|
142
219
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/react/components/Overlay.tsx","../../src/react/components/CursorBuddy.tsx","../../src/react/hooks.ts","../../src/react/provider.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/react/components/Overlay.tsx","../../src/react/components/CursorBuddy.tsx","../../src/react/hooks.ts","../../src/react/provider.tsx","../../src/react/components/ToolBubble.tsx","../../src/react/components/ToolBubbleStack.tsx"],"mappings":"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"}
|
package/dist/react/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { a as $buddyScale, i as $buddyRotation, n as $audioLevel, o as $cursorPosition, r as $buddyPosition, s as $pointingTarget, t as CursorBuddyClient } from "../client-
|
|
2
|
+
import { a as $buddyScale, i as $buddyRotation, n as $audioLevel, o as $cursorPosition, r as $buddyPosition, s as $pointingTarget, t as CursorBuddyClient } from "../client-D7kFGsuH.mjs";
|
|
3
3
|
import { useStore } from "@nanostores/react";
|
|
4
4
|
import { createContext, useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { createPortal } from "react-dom";
|
|
7
7
|
//#region src/react/styles.css?inline
|
|
8
|
-
var styles_default = "
|
|
8
|
+
var styles_default = ":root{--cursor-buddy-color-idle:#3b82f6;--cursor-buddy-color-listening:#ef4444;--cursor-buddy-color-processing:#eab308;--cursor-buddy-color-responding:#22c55e;--cursor-buddy-cursor-stroke:#fff;--cursor-buddy-cursor-shadow:0 2px 4px #0000004d;--cursor-buddy-bubble-bg:#fff;--cursor-buddy-bubble-text:#1f2937;--cursor-buddy-bubble-radius:8px;--cursor-buddy-bubble-padding:8px 12px;--cursor-buddy-bubble-shadow:0 4px 12px #00000026;--cursor-buddy-bubble-max-width:200px;--cursor-buddy-bubble-font-size:14px;--cursor-buddy-waveform-color:#ef4444;--cursor-buddy-waveform-bar-width:4px;--cursor-buddy-waveform-bar-radius:2px;--cursor-buddy-waveform-gap:3px;--cursor-buddy-z-index:2147480000;--cursor-buddy-transition-fast:.1s;--cursor-buddy-transition-normal:.2s;--cursor-buddy-animation-duration:.3s}.cursor-buddy-overlay{pointer-events:none;isolation:isolate;z-index:var(--cursor-buddy-z-index);position:fixed;inset:0}.cursor-buddy-container{position:absolute;transform:translate(8px,4px)}.cursor-buddy-cursor{transition:transform var(--cursor-buddy-transition-fast) ease-out;filter:drop-shadow(var(--cursor-buddy-cursor-shadow))}.cursor-buddy-cursor polygon{stroke:var(--cursor-buddy-cursor-stroke);stroke-width:2px;transition:fill var(--cursor-buddy-transition-normal) ease-out}.cursor-buddy-cursor--idle polygon{fill:var(--cursor-buddy-color-idle)}.cursor-buddy-cursor--listening polygon{fill:var(--cursor-buddy-color-listening)}.cursor-buddy-cursor--processing polygon{fill:var(--cursor-buddy-color-processing)}.cursor-buddy-cursor--responding polygon{fill:var(--cursor-buddy-color-responding)}.cursor-buddy-cursor__spinner{color:var(--cursor-buddy-color-processing)}.cursor-buddy-cursor--listening{animation:1.5s ease-in-out infinite cursor-buddy-pulse}@keyframes cursor-buddy-pulse{0%,to{filter:drop-shadow(var(--cursor-buddy-cursor-shadow))}50%{filter:drop-shadow(0 0 8px var(--cursor-buddy-color-listening))}}.cursor-buddy-cursor--processing{animation:2s linear infinite cursor-buddy-spin-subtle}@keyframes cursor-buddy-spin-subtle{0%{filter:drop-shadow(var(--cursor-buddy-cursor-shadow)) hue-rotate(0deg)}to{filter:drop-shadow(var(--cursor-buddy-cursor-shadow)) hue-rotate(360deg)}}.cursor-buddy-bubble{pointer-events:auto;cursor:pointer;max-width:var(--cursor-buddy-bubble-max-width);padding:var(--cursor-buddy-bubble-padding);background-color:var(--cursor-buddy-bubble-bg);color:var(--cursor-buddy-bubble-text);border-radius:var(--cursor-buddy-bubble-radius);box-shadow:var(--cursor-buddy-bubble-shadow);font-size:var(--cursor-buddy-bubble-font-size);overflow-wrap:break-word;word-break:break-word;user-select:none;width:max-content;animation:cursor-buddy-fade-in var(--cursor-buddy-animation-duration) ease-out;line-height:1.4;position:absolute;top:-8px;left:24px}@keyframes cursor-buddy-fade-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.cursor-buddy-waveform{align-items:center;gap:var(--cursor-buddy-waveform-gap);height:24px;animation:cursor-buddy-fade-in var(--cursor-buddy-animation-duration) ease-out;display:flex;position:absolute;top:2px;left:24px}.cursor-buddy-waveform-bar{width:var(--cursor-buddy-waveform-bar-width);background-color:var(--cursor-buddy-waveform-color);border-radius:var(--cursor-buddy-waveform-bar-radius);transition:height 50ms ease-out}.cursor-buddy-fade-out{animation:cursor-buddy-fade-out var(--cursor-buddy-animation-duration) ease-out forwards}@keyframes cursor-buddy-fade-out{0%{opacity:1}to{opacity:0}}:root{--cursor-buddy-tool-bg:#fff;--cursor-buddy-tool-text:#1f2937;--cursor-buddy-tool-border:#e5e7eb;--cursor-buddy-tool-radius:8px;--cursor-buddy-tool-padding:8px 12px;--cursor-buddy-tool-shadow:0 4px 12px #00000026;--cursor-buddy-tool-gap:8px;--cursor-buddy-tool-pending:#3b82f6;--cursor-buddy-tool-approval:#f59e0b;--cursor-buddy-tool-success:#22c55e;--cursor-buddy-tool-error:#ef4444;--cursor-buddy-tool-denied:#6b7280;--cursor-buddy-tool-btn-approve-bg:#22c55e;--cursor-buddy-tool-btn-approve-text:#fff;--cursor-buddy-tool-btn-deny-bg:#ef4444;--cursor-buddy-tool-btn-deny-text:#fff}.cursor-buddy-tool-stack{gap:var(--cursor-buddy-tool-gap);pointer-events:auto;flex-direction:column;display:flex;position:absolute;top:32px;left:24px}.cursor-buddy-tool-stack__item{animation:cursor-buddy-fade-in var(--cursor-buddy-animation-duration) ease-out}.cursor-buddy-tool-bubble{padding:var(--cursor-buddy-tool-padding);background-color:var(--cursor-buddy-tool-bg);color:var(--cursor-buddy-tool-text);border-radius:var(--cursor-buddy-tool-radius);box-shadow:var(--cursor-buddy-tool-shadow);white-space:nowrap;user-select:none;border-left:3px solid var(--cursor-buddy-tool-pending);align-items:center;gap:8px;font-size:13px;line-height:1.4;display:flex}.cursor-buddy-tool-bubble--pending,.cursor-buddy-tool-bubble--approved{border-left-color:var(--cursor-buddy-tool-pending)}.cursor-buddy-tool-bubble--awaiting_approval{border-left-color:var(--cursor-buddy-tool-approval)}.cursor-buddy-tool-bubble--completed{border-left-color:var(--cursor-buddy-tool-success)}.cursor-buddy-tool-bubble--failed{border-left-color:var(--cursor-buddy-tool-error)}.cursor-buddy-tool-bubble--denied{border-left-color:var(--cursor-buddy-tool-denied)}.cursor-buddy-tool-bubble__content{align-items:center;gap:6px;display:flex}.cursor-buddy-tool-bubble__label{font-weight:500}.cursor-buddy-tool-icon{justify-content:center;align-items:center;width:16px;height:16px;font-size:12px;font-weight:700;display:inline-flex}.cursor-buddy-tool-icon--spinner{border:2px solid var(--cursor-buddy-tool-pending);border-top-color:#0000;border-radius:50%;width:14px;height:14px;animation:.8s linear infinite cursor-buddy-tool-spin}@keyframes cursor-buddy-tool-spin{to{transform:rotate(360deg)}}.cursor-buddy-tool-icon--check{color:var(--cursor-buddy-tool-success)}.cursor-buddy-tool-icon--error{color:var(--cursor-buddy-tool-error)}.cursor-buddy-tool-icon--denied{color:var(--cursor-buddy-tool-denied)}.cursor-buddy-tool-icon--question{color:var(--cursor-buddy-tool-approval)}.cursor-buddy-tool-bubble__actions{gap:4px;margin-left:4px;display:flex}.cursor-buddy-tool-button{cursor:pointer;border:none;border-radius:4px;padding:4px 10px;font-size:12px;font-weight:500;transition:opacity .15s}.cursor-buddy-tool-button:hover{opacity:.9}.cursor-buddy-tool-button:active{opacity:.8}.cursor-buddy-tool-button--approve{background-color:var(--cursor-buddy-tool-btn-approve-bg);color:var(--cursor-buddy-tool-btn-approve-text)}.cursor-buddy-tool-button--deny{background-color:var(--cursor-buddy-tool-btn-deny-bg);color:var(--cursor-buddy-tool-btn-deny-text)}.cursor-buddy-tool-bubble__dismiss{color:#9ca3af;cursor:pointer;background-color:#0000;border:none;border-radius:50%;justify-content:center;align-items:center;width:18px;height:18px;margin-left:auto;padding:0;font-size:12px;transition:color .15s,background-color .15s;display:flex}.cursor-buddy-tool-bubble__dismiss:hover{color:#6b7280;background-color:#f3f4f6}";
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region src/react/utils/inject-styles.ts
|
|
11
11
|
const STYLE_ID = "cursor-buddy-styles";
|
|
@@ -36,15 +36,18 @@ const CursorBuddyContext = createContext(null);
|
|
|
36
36
|
/**
|
|
37
37
|
* Provider for cursor buddy. Creates and manages the client instance.
|
|
38
38
|
*/
|
|
39
|
-
function CursorBuddyProvider({ endpoint, transcription, speech, children, onTranscript, onResponse, onPoint, onStateChange, onError }) {
|
|
39
|
+
function CursorBuddyProvider({ endpoint, transcription, speech, toolDisplay, children, onTranscript, onResponse, onPoint, onStateChange, onError, onToolCall, onToolResult }) {
|
|
40
40
|
const [client] = useState(() => new CursorBuddyClient(endpoint, {
|
|
41
41
|
onTranscript,
|
|
42
42
|
onResponse,
|
|
43
43
|
onPoint,
|
|
44
44
|
onStateChange,
|
|
45
45
|
onError,
|
|
46
|
+
onToolCall,
|
|
47
|
+
onToolResult,
|
|
46
48
|
speech,
|
|
47
|
-
transcription
|
|
49
|
+
transcription,
|
|
50
|
+
toolDisplay
|
|
48
51
|
}));
|
|
49
52
|
useEffect(() => {
|
|
50
53
|
injectStyles();
|
|
@@ -93,7 +96,10 @@ function useCursorBuddy() {
|
|
|
93
96
|
setEnabled: useCallback((enabled) => client.setEnabled(enabled), [client]),
|
|
94
97
|
pointAt: useCallback((x, y, label) => client.pointAt(x, y, label), [client]),
|
|
95
98
|
dismissPointing: useCallback(() => client.dismissPointing(), [client]),
|
|
96
|
-
reset: useCallback(() => client.reset(), [client])
|
|
99
|
+
reset: useCallback(() => client.reset(), [client]),
|
|
100
|
+
approveToolCall: useCallback((id) => client.approveToolCall(id), [client]),
|
|
101
|
+
denyToolCall: useCallback((id) => client.denyToolCall(id), [client]),
|
|
102
|
+
dismissToolCall: useCallback((id) => client.dismissToolCall(id), [client])
|
|
97
103
|
};
|
|
98
104
|
}
|
|
99
105
|
//#endregion
|
|
@@ -300,6 +306,61 @@ function createHotkeyController(hotkey, options) {
|
|
|
300
306
|
};
|
|
301
307
|
}
|
|
302
308
|
//#endregion
|
|
309
|
+
//#region src/core/hotkeys/approval-shortcuts.ts
|
|
310
|
+
/**
|
|
311
|
+
* Approval shortcut keys
|
|
312
|
+
*/
|
|
313
|
+
const APPROVE_KEYS = ["y", "enter"];
|
|
314
|
+
const DENY_KEYS = ["n", "escape"];
|
|
315
|
+
/**
|
|
316
|
+
* Create a controller for approval keyboard shortcuts.
|
|
317
|
+
*
|
|
318
|
+
* Listens for:
|
|
319
|
+
* - Y or Enter → onApprove
|
|
320
|
+
* - N or Escape → onDeny
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```ts
|
|
324
|
+
* const controller = createApprovalShortcutController({
|
|
325
|
+
* onApprove: () => approveToolCall(id),
|
|
326
|
+
* onDeny: () => denyToolCall(id),
|
|
327
|
+
* enabled: pendingApproval !== null,
|
|
328
|
+
* })
|
|
329
|
+
*
|
|
330
|
+
* // Later, cleanup
|
|
331
|
+
* controller.destroy()
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
function createApprovalShortcutController(options) {
|
|
335
|
+
let enabled = options.enabled ?? true;
|
|
336
|
+
const { onApprove, onDeny } = options;
|
|
337
|
+
function handleKeyDown(event) {
|
|
338
|
+
if (!enabled) return;
|
|
339
|
+
const target = event.target;
|
|
340
|
+
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) return;
|
|
341
|
+
const key = event.key.toLowerCase();
|
|
342
|
+
if (APPROVE_KEYS.includes(key)) {
|
|
343
|
+
event.preventDefault();
|
|
344
|
+
onApprove();
|
|
345
|
+
} else if (DENY_KEYS.includes(key)) {
|
|
346
|
+
event.preventDefault();
|
|
347
|
+
onDeny();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
351
|
+
return {
|
|
352
|
+
get isEnabled() {
|
|
353
|
+
return enabled;
|
|
354
|
+
},
|
|
355
|
+
setEnabled(newEnabled) {
|
|
356
|
+
enabled = newEnabled;
|
|
357
|
+
},
|
|
358
|
+
destroy() {
|
|
359
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
//#endregion
|
|
303
364
|
//#region src/react/use-hotkey.ts
|
|
304
365
|
/**
|
|
305
366
|
* Hook for detecting hotkey press/release.
|
|
@@ -352,6 +413,52 @@ function useHotkey(hotkey, onPress, onRelease, enabled = true) {
|
|
|
352
413
|
}, [enabled]);
|
|
353
414
|
}
|
|
354
415
|
//#endregion
|
|
416
|
+
//#region src/react/use-approval-shortcuts.ts
|
|
417
|
+
/**
|
|
418
|
+
* Hook for approval keyboard shortcuts.
|
|
419
|
+
*
|
|
420
|
+
* When enabled, listens for:
|
|
421
|
+
* - Y or Enter → onApprove
|
|
422
|
+
* - N or Escape → onDeny
|
|
423
|
+
*
|
|
424
|
+
* Automatically enables when `enabled` is true.
|
|
425
|
+
* Ignores keypresses when focus is in an input/textarea.
|
|
426
|
+
*
|
|
427
|
+
* @param enabled - Whether shortcuts should be active
|
|
428
|
+
* @param onApprove - Called when user presses Y or Enter
|
|
429
|
+
* @param onDeny - Called when user presses N or Escape
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```tsx
|
|
433
|
+
* useApprovalShortcuts(
|
|
434
|
+
* pendingApproval !== null,
|
|
435
|
+
* () => approveToolCall(pendingApproval.id),
|
|
436
|
+
* () => denyToolCall(pendingApproval.id)
|
|
437
|
+
* )
|
|
438
|
+
* ```
|
|
439
|
+
*/
|
|
440
|
+
function useApprovalShortcuts(enabled, onApprove, onDeny) {
|
|
441
|
+
const onApproveRef = useRef(onApprove);
|
|
442
|
+
const onDenyRef = useRef(onDeny);
|
|
443
|
+
onApproveRef.current = onApprove;
|
|
444
|
+
onDenyRef.current = onDeny;
|
|
445
|
+
const controllerRef = useRef(null);
|
|
446
|
+
useEffect(() => {
|
|
447
|
+
controllerRef.current = createApprovalShortcutController({
|
|
448
|
+
onApprove: () => onApproveRef.current(),
|
|
449
|
+
onDeny: () => onDenyRef.current(),
|
|
450
|
+
enabled
|
|
451
|
+
});
|
|
452
|
+
return () => {
|
|
453
|
+
controllerRef.current?.destroy();
|
|
454
|
+
controllerRef.current = null;
|
|
455
|
+
};
|
|
456
|
+
}, []);
|
|
457
|
+
useEffect(() => {
|
|
458
|
+
controllerRef.current?.setEnabled(enabled);
|
|
459
|
+
}, [enabled]);
|
|
460
|
+
}
|
|
461
|
+
//#endregion
|
|
355
462
|
//#region src/react/components/Cursor.tsx
|
|
356
463
|
const BASE_ROTATION = -Math.PI / 6;
|
|
357
464
|
/**
|
|
@@ -425,6 +532,138 @@ function DefaultSpeechBubble({ text, isVisible, onClick }) {
|
|
|
425
532
|
});
|
|
426
533
|
}
|
|
427
534
|
//#endregion
|
|
535
|
+
//#region src/react/components/ToolBubble.tsx
|
|
536
|
+
/**
|
|
537
|
+
* Status icons for tool call states
|
|
538
|
+
*/
|
|
539
|
+
function StatusIcon({ status }) {
|
|
540
|
+
switch (status) {
|
|
541
|
+
case "pending":
|
|
542
|
+
case "approved": return /* @__PURE__ */ jsx("span", {
|
|
543
|
+
className: "cursor-buddy-tool-icon cursor-buddy-tool-icon--spinner",
|
|
544
|
+
"aria-label": "Loading"
|
|
545
|
+
});
|
|
546
|
+
case "awaiting_approval": return /* @__PURE__ */ jsx("span", {
|
|
547
|
+
className: "cursor-buddy-tool-icon cursor-buddy-tool-icon--question",
|
|
548
|
+
"aria-label": "Needs approval",
|
|
549
|
+
children: "?"
|
|
550
|
+
});
|
|
551
|
+
case "completed": return /* @__PURE__ */ jsx("span", {
|
|
552
|
+
className: "cursor-buddy-tool-icon cursor-buddy-tool-icon--check",
|
|
553
|
+
"aria-label": "Completed",
|
|
554
|
+
children: "✓"
|
|
555
|
+
});
|
|
556
|
+
case "denied": return /* @__PURE__ */ jsx("span", {
|
|
557
|
+
className: "cursor-buddy-tool-icon cursor-buddy-tool-icon--denied",
|
|
558
|
+
"aria-label": "Denied",
|
|
559
|
+
children: "✕"
|
|
560
|
+
});
|
|
561
|
+
case "failed": return /* @__PURE__ */ jsx("span", {
|
|
562
|
+
className: "cursor-buddy-tool-icon cursor-buddy-tool-icon--error",
|
|
563
|
+
"aria-label": "Failed",
|
|
564
|
+
children: "!"
|
|
565
|
+
});
|
|
566
|
+
default: return null;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Default tool bubble component.
|
|
571
|
+
* Displays tool call status with optional approve/deny buttons.
|
|
572
|
+
*/
|
|
573
|
+
function ToolBubble({ toolName, args, status, label, result, error, approve, deny, dismiss, customRender }) {
|
|
574
|
+
if (customRender) return /* @__PURE__ */ jsx(Fragment, { children: customRender({
|
|
575
|
+
toolName,
|
|
576
|
+
args,
|
|
577
|
+
status,
|
|
578
|
+
label,
|
|
579
|
+
result,
|
|
580
|
+
error,
|
|
581
|
+
approve,
|
|
582
|
+
deny,
|
|
583
|
+
dismiss
|
|
584
|
+
}) });
|
|
585
|
+
const needsApproval = status === "awaiting_approval";
|
|
586
|
+
const isTerminal = status === "completed" || status === "denied" || status === "failed";
|
|
587
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
588
|
+
className: `cursor-buddy-tool-bubble cursor-buddy-tool-bubble--${status}`,
|
|
589
|
+
role: "status",
|
|
590
|
+
"aria-live": "polite",
|
|
591
|
+
children: [
|
|
592
|
+
/* @__PURE__ */ jsxs("div", {
|
|
593
|
+
className: "cursor-buddy-tool-bubble__content",
|
|
594
|
+
children: [/* @__PURE__ */ jsx(StatusIcon, { status }), /* @__PURE__ */ jsx("span", {
|
|
595
|
+
className: "cursor-buddy-tool-bubble__label",
|
|
596
|
+
children: label
|
|
597
|
+
})]
|
|
598
|
+
}),
|
|
599
|
+
needsApproval && approve && deny && /* @__PURE__ */ jsxs("div", {
|
|
600
|
+
className: "cursor-buddy-tool-bubble__actions",
|
|
601
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
602
|
+
type: "button",
|
|
603
|
+
className: "cursor-buddy-tool-button cursor-buddy-tool-button--approve",
|
|
604
|
+
onClick: approve,
|
|
605
|
+
"aria-label": `Approve ${toolName}`,
|
|
606
|
+
children: "Yes (Y)"
|
|
607
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
608
|
+
type: "button",
|
|
609
|
+
className: "cursor-buddy-tool-button cursor-buddy-tool-button--deny",
|
|
610
|
+
onClick: deny,
|
|
611
|
+
"aria-label": `Deny ${toolName}`,
|
|
612
|
+
children: "No (Esc)"
|
|
613
|
+
})]
|
|
614
|
+
}),
|
|
615
|
+
isTerminal && /* @__PURE__ */ jsx("button", {
|
|
616
|
+
type: "button",
|
|
617
|
+
className: "cursor-buddy-tool-bubble__dismiss",
|
|
618
|
+
onClick: dismiss,
|
|
619
|
+
"aria-label": "Dismiss",
|
|
620
|
+
children: "✕"
|
|
621
|
+
})
|
|
622
|
+
]
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
//#endregion
|
|
626
|
+
//#region src/react/components/ToolBubbleStack.tsx
|
|
627
|
+
/**
|
|
628
|
+
* Stack of tool bubbles displayed near the cursor.
|
|
629
|
+
* Renders all active tool calls in a vertical column.
|
|
630
|
+
*/
|
|
631
|
+
function ToolBubbleStack({ toolCalls, toolDisplay, onApprove, onDeny, onDismiss, renderToolBubble }) {
|
|
632
|
+
if (toolCalls.length === 0) return null;
|
|
633
|
+
return /* @__PURE__ */ jsx("div", {
|
|
634
|
+
className: "cursor-buddy-tool-stack",
|
|
635
|
+
role: "region",
|
|
636
|
+
"aria-label": "Tool calls",
|
|
637
|
+
children: toolCalls.map((toolCall) => {
|
|
638
|
+
const toolConfig = toolDisplay?.[toolCall.toolName] ?? toolDisplay?.["*"];
|
|
639
|
+
if (toolConfig?.mode === "hidden") return null;
|
|
640
|
+
const needsApproval = toolCall.status === "awaiting_approval";
|
|
641
|
+
const bubbleProps = {
|
|
642
|
+
toolName: toolCall.toolName,
|
|
643
|
+
args: toolCall.args,
|
|
644
|
+
status: toolCall.status,
|
|
645
|
+
label: toolCall.label,
|
|
646
|
+
result: toolCall.result,
|
|
647
|
+
error: toolCall.error,
|
|
648
|
+
approve: needsApproval ? () => onApprove(toolCall.id) : void 0,
|
|
649
|
+
deny: needsApproval ? () => onDeny(toolCall.id) : void 0,
|
|
650
|
+
dismiss: () => onDismiss(toolCall.id)
|
|
651
|
+
};
|
|
652
|
+
if (renderToolBubble) return /* @__PURE__ */ jsx("div", {
|
|
653
|
+
className: "cursor-buddy-tool-stack__item",
|
|
654
|
+
children: renderToolBubble(bubbleProps)
|
|
655
|
+
}, toolCall.id);
|
|
656
|
+
return /* @__PURE__ */ jsx("div", {
|
|
657
|
+
className: "cursor-buddy-tool-stack__item",
|
|
658
|
+
children: /* @__PURE__ */ jsx(ToolBubble, {
|
|
659
|
+
...bubbleProps,
|
|
660
|
+
customRender: toolConfig?.render
|
|
661
|
+
})
|
|
662
|
+
}, toolCall.id);
|
|
663
|
+
})
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
//#endregion
|
|
428
667
|
//#region src/react/components/Waveform.tsx
|
|
429
668
|
const EMPTY_BARS = Array.from({ length: 12 }, () => 0);
|
|
430
669
|
/**
|
|
@@ -460,13 +699,14 @@ function DefaultWaveform({ audioLevel, isListening }) {
|
|
|
460
699
|
//#endregion
|
|
461
700
|
//#region src/react/components/Overlay.tsx
|
|
462
701
|
/**
|
|
463
|
-
* Overlay component that renders the cursor, speech bubble, and
|
|
702
|
+
* Overlay component that renders the cursor, speech bubble, waveform, and tool bubbles.
|
|
464
703
|
* Uses React portal to render at the document body level.
|
|
465
704
|
*/
|
|
466
|
-
function Overlay({ cursor, speechBubble, waveform, container }) {
|
|
705
|
+
function Overlay({ cursor, speechBubble, waveform, toolDisplay, renderToolBubble, container }) {
|
|
467
706
|
const [isMounted, setIsMounted] = useState(false);
|
|
468
707
|
useEffect(() => setIsMounted(true), []);
|
|
469
|
-
const { state, isPointing, isEnabled, dismissPointing } = useCursorBuddy();
|
|
708
|
+
const { state, isPointing, isEnabled, dismissPointing, activeToolCalls, pendingApproval, approveToolCall, denyToolCall, dismissToolCall } = useCursorBuddy();
|
|
709
|
+
useApprovalShortcuts(pendingApproval !== null, () => pendingApproval && approveToolCall(pendingApproval.id), () => pendingApproval && denyToolCall(pendingApproval.id));
|
|
470
710
|
const buddyPosition = useStore($buddyPosition);
|
|
471
711
|
const buddyRotation = useStore($buddyRotation);
|
|
472
712
|
const buddyScale = useStore($buddyScale);
|
|
@@ -503,7 +743,15 @@ function Overlay({ cursor, speechBubble, waveform, container }) {
|
|
|
503
743
|
children: [
|
|
504
744
|
cursorElement,
|
|
505
745
|
state === "listening" && waveformElement,
|
|
506
|
-
isPointing && speechBubbleElement
|
|
746
|
+
isPointing && speechBubbleElement,
|
|
747
|
+
/* @__PURE__ */ jsx(ToolBubbleStack, {
|
|
748
|
+
toolCalls: activeToolCalls,
|
|
749
|
+
toolDisplay,
|
|
750
|
+
onApprove: approveToolCall,
|
|
751
|
+
onDeny: denyToolCall,
|
|
752
|
+
onDismiss: dismissToolCall,
|
|
753
|
+
renderToolBubble
|
|
754
|
+
})
|
|
507
755
|
]
|
|
508
756
|
})
|
|
509
757
|
});
|
|
@@ -516,13 +764,15 @@ function Overlay({ cursor, speechBubble, waveform, container }) {
|
|
|
516
764
|
/**
|
|
517
765
|
* Internal component that sets up hotkey handling
|
|
518
766
|
*/
|
|
519
|
-
function CursorBuddyInner({ hotkey = "ctrl+alt", cursor, speechBubble, waveform, container }) {
|
|
767
|
+
function CursorBuddyInner({ hotkey = "ctrl+alt", cursor, speechBubble, waveform, toolDisplay, renderToolBubble, container }) {
|
|
520
768
|
const { startListening, stopListening, isEnabled } = useCursorBuddy();
|
|
521
769
|
useHotkey(hotkey, startListening, stopListening, isEnabled);
|
|
522
770
|
return /* @__PURE__ */ jsx(Overlay, {
|
|
523
771
|
cursor,
|
|
524
772
|
speechBubble,
|
|
525
773
|
waveform,
|
|
774
|
+
toolDisplay,
|
|
775
|
+
renderToolBubble,
|
|
526
776
|
container
|
|
527
777
|
});
|
|
528
778
|
}
|
|
@@ -549,26 +799,31 @@ function CursorBuddyInner({ hotkey = "ctrl+alt", cursor, speechBubble, waveform,
|
|
|
549
799
|
* }
|
|
550
800
|
* ```
|
|
551
801
|
*/
|
|
552
|
-
function CursorBuddy({ endpoint, hotkey, container, speech, transcription, cursor, speechBubble, waveform, onTranscript, onResponse, onPoint, onStateChange, onError }) {
|
|
802
|
+
function CursorBuddy({ endpoint, hotkey, container, speech, transcription, cursor, speechBubble, waveform, toolDisplay, renderToolBubble, onTranscript, onResponse, onPoint, onStateChange, onError, onToolCall, onToolResult }) {
|
|
553
803
|
return /* @__PURE__ */ jsx(CursorBuddyProvider, {
|
|
554
804
|
endpoint,
|
|
555
805
|
speech,
|
|
556
806
|
transcription,
|
|
807
|
+
toolDisplay,
|
|
557
808
|
onTranscript,
|
|
558
809
|
onResponse,
|
|
559
810
|
onPoint,
|
|
560
811
|
onStateChange,
|
|
561
812
|
onError,
|
|
813
|
+
onToolCall,
|
|
814
|
+
onToolResult,
|
|
562
815
|
children: /* @__PURE__ */ jsx(CursorBuddyInner, {
|
|
563
816
|
hotkey,
|
|
564
817
|
cursor,
|
|
565
818
|
speechBubble,
|
|
566
819
|
waveform,
|
|
820
|
+
toolDisplay,
|
|
821
|
+
renderToolBubble,
|
|
567
822
|
container
|
|
568
823
|
})
|
|
569
824
|
});
|
|
570
825
|
}
|
|
571
826
|
//#endregion
|
|
572
|
-
export { CursorBuddy, CursorBuddyProvider, useCursorBuddy };
|
|
827
|
+
export { CursorBuddy, CursorBuddyProvider, ToolBubble, ToolBubbleStack, useCursorBuddy };
|
|
573
828
|
|
|
574
829
|
//# sourceMappingURL=index.mjs.map
|