@react-grab/cursor 0.0.88 → 0.0.89
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/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/client.cjs +104 -60
- package/dist/client.global.js +4 -4
- package/dist/client.js +104 -60
- package/dist/server.cjs +188 -133
- package/dist/server.d.cts +7 -1
- package/dist/server.d.ts +7 -1
- package/dist/server.js +188 -134
- package/package.json +3 -3
package/dist/cli.cjs
CHANGED
|
@@ -7138,7 +7138,7 @@ var import_picocolors = __toESM(require_picocolors());
|
|
|
7138
7138
|
var DEFAULT_PORT = 5567;
|
|
7139
7139
|
|
|
7140
7140
|
// src/cli.ts
|
|
7141
|
-
var VERSION = "0.0.
|
|
7141
|
+
var VERSION = "0.0.89";
|
|
7142
7142
|
var serverPath = path2.join(__dirname, "server.cjs");
|
|
7143
7143
|
execa(process.execPath, [serverPath], {
|
|
7144
7144
|
detached: true,
|
package/dist/cli.js
CHANGED
|
@@ -7130,7 +7130,7 @@ var import_picocolors = __toESM(require_picocolors());
|
|
|
7130
7130
|
var DEFAULT_PORT = 5567;
|
|
7131
7131
|
|
|
7132
7132
|
// src/cli.ts
|
|
7133
|
-
var VERSION = "0.0.
|
|
7133
|
+
var VERSION = "0.0.89";
|
|
7134
7134
|
var serverPath = join(__dirname, "server.cjs");
|
|
7135
7135
|
execa(process.execPath, [serverPath], {
|
|
7136
7136
|
detached: true,
|
package/dist/client.cjs
CHANGED
|
@@ -12,7 +12,7 @@ var parseSSEEvent = (eventBlock) => {
|
|
|
12
12
|
}
|
|
13
13
|
return { eventType, data };
|
|
14
14
|
};
|
|
15
|
-
async function*
|
|
15
|
+
var streamSSE = async function* (stream, signal) {
|
|
16
16
|
const reader = stream.getReader();
|
|
17
17
|
const decoder = new TextDecoder();
|
|
18
18
|
let buffer = "";
|
|
@@ -51,28 +51,54 @@ async function* streamSSE(stream, signal) {
|
|
|
51
51
|
} catch {
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
};
|
|
55
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
56
|
+
var getStoredAgentContext = (storage, sessionId, storageKey = STORAGE_KEY) => {
|
|
57
|
+
const rawSessions = storage.getItem(storageKey);
|
|
58
|
+
if (!rawSessions) throw new Error("No sessions to resume");
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(rawSessions);
|
|
62
|
+
} catch {
|
|
63
|
+
throw new Error("Failed to parse stored sessions");
|
|
64
|
+
}
|
|
65
|
+
if (!isRecord(parsed)) throw new Error("Invalid stored sessions");
|
|
66
|
+
const storedSession = parsed[sessionId];
|
|
67
|
+
if (!isRecord(storedSession)) {
|
|
68
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
69
|
+
}
|
|
70
|
+
const context = storedSession.context;
|
|
71
|
+
if (!isRecord(context)) throw new Error(`Session ${sessionId} is invalid`);
|
|
72
|
+
const content = context.content;
|
|
73
|
+
const prompt = context.prompt;
|
|
74
|
+
if (typeof content !== "string" || typeof prompt !== "string") {
|
|
75
|
+
throw new Error(`Session ${sessionId} is invalid`);
|
|
76
|
+
}
|
|
77
|
+
const options = context.options;
|
|
78
|
+
const storedSessionId = context.sessionId;
|
|
79
|
+
return {
|
|
80
|
+
content,
|
|
81
|
+
prompt,
|
|
82
|
+
options,
|
|
83
|
+
sessionId: typeof storedSessionId === "string" ? storedSessionId : void 0
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
var streamAgentStatusFromServer = async function* (options, context, signal) {
|
|
63
87
|
const startTime = Date.now();
|
|
64
88
|
const sessionId = context.sessionId;
|
|
89
|
+
const pollIntervalMs = options.pollIntervalMs ?? 100;
|
|
90
|
+
const agentUrl = `${options.serverUrl}${options.agentPath ?? "/agent"}`;
|
|
65
91
|
const handleAbort = () => {
|
|
66
|
-
if (sessionId)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
if (!sessionId) return;
|
|
93
|
+
const abortPath = options.abortPath?.(sessionId) ?? `/abort/${sessionId}`;
|
|
94
|
+
fetch(`${options.serverUrl}${abortPath}`, { method: "POST" }).catch(
|
|
95
|
+
() => {
|
|
96
|
+
}
|
|
97
|
+
);
|
|
72
98
|
};
|
|
73
99
|
signal.addEventListener("abort", handleAbort);
|
|
74
100
|
try {
|
|
75
|
-
const response = await fetch(
|
|
101
|
+
const response = await fetch(agentUrl, {
|
|
76
102
|
method: "POST",
|
|
77
103
|
headers: { "Content-Type": "application/json" },
|
|
78
104
|
body: JSON.stringify(context),
|
|
@@ -85,29 +111,29 @@ async function* streamFromServer(serverUrl, context, signal) {
|
|
|
85
111
|
throw new Error("No response body");
|
|
86
112
|
}
|
|
87
113
|
const iterator = streamSSE(response.body, signal)[Symbol.asyncIterator]();
|
|
88
|
-
let
|
|
114
|
+
let isDone = false;
|
|
89
115
|
let pendingNext = iterator.next();
|
|
90
116
|
let lastStatus = null;
|
|
91
|
-
while (!
|
|
117
|
+
while (!isDone) {
|
|
92
118
|
const result = await Promise.race([
|
|
93
119
|
pendingNext.then((iteratorResult) => ({
|
|
94
120
|
type: "status",
|
|
95
121
|
iteratorResult
|
|
96
122
|
})),
|
|
97
123
|
new Promise(
|
|
98
|
-
(resolve) => setTimeout(() => resolve({ type: "timeout" }),
|
|
124
|
+
(resolve) => setTimeout(() => resolve({ type: "timeout" }), pollIntervalMs)
|
|
99
125
|
)
|
|
100
126
|
]);
|
|
101
127
|
const elapsedSeconds = (Date.now() - startTime) / 1e3;
|
|
102
128
|
if (result.type === "status") {
|
|
103
129
|
const iteratorResult = result.iteratorResult;
|
|
104
|
-
|
|
105
|
-
if (!
|
|
130
|
+
isDone = iteratorResult.done ?? false;
|
|
131
|
+
if (!isDone && iteratorResult.value) {
|
|
106
132
|
lastStatus = iteratorResult.value;
|
|
107
133
|
pendingNext = iterator.next();
|
|
108
134
|
}
|
|
109
135
|
}
|
|
110
|
-
if (lastStatus ===
|
|
136
|
+
if (lastStatus === options.completedStatus) {
|
|
111
137
|
yield `Completed in ${elapsedSeconds.toFixed(1)}s`;
|
|
112
138
|
} else if (lastStatus) {
|
|
113
139
|
yield `${lastStatus} ${elapsedSeconds.toFixed(1)}s`;
|
|
@@ -118,57 +144,74 @@ async function* streamFromServer(serverUrl, context, signal) {
|
|
|
118
144
|
} finally {
|
|
119
145
|
signal.removeEventListener("abort", handleAbort);
|
|
120
146
|
}
|
|
121
|
-
}
|
|
147
|
+
};
|
|
148
|
+
var createCachedConnectionChecker = (checkConnection, ttlMs = CONNECTION_CHECK_TTL_MS) => {
|
|
149
|
+
let cache = null;
|
|
150
|
+
return async () => {
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
if (cache && now - cache.timestamp < ttlMs) return cache.result;
|
|
153
|
+
try {
|
|
154
|
+
const result = await checkConnection();
|
|
155
|
+
cache = { result, timestamp: now };
|
|
156
|
+
return result;
|
|
157
|
+
} catch {
|
|
158
|
+
cache = { result: false, timestamp: now };
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// src/constants.ts
|
|
165
|
+
var DEFAULT_PORT = 5567;
|
|
166
|
+
var COMPLETED_STATUS = "Completed successfully";
|
|
167
|
+
|
|
168
|
+
// src/client.ts
|
|
169
|
+
var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
|
|
170
|
+
var isReactGrabApi = (value) => typeof value === "object" && value !== null && "setAgent" in value;
|
|
122
171
|
var createCursorAgentProvider = (providerOptions = {}) => {
|
|
123
172
|
const { serverUrl = DEFAULT_SERVER_URL, getOptions } = providerOptions;
|
|
124
|
-
let connectionCache = null;
|
|
125
173
|
const mergeOptions = (contextOptions) => ({
|
|
126
174
|
...getOptions?.() ?? {},
|
|
127
175
|
...contextOptions ?? {}
|
|
128
176
|
});
|
|
177
|
+
const checkConnection = createCachedConnectionChecker(async () => {
|
|
178
|
+
const response = await fetch(`${serverUrl}/health`, { method: "GET" });
|
|
179
|
+
return response.ok;
|
|
180
|
+
}, CONNECTION_CHECK_TTL_MS);
|
|
129
181
|
return {
|
|
130
182
|
send: async function* (context, signal) {
|
|
131
183
|
const mergedContext = {
|
|
132
184
|
...context,
|
|
133
185
|
options: mergeOptions(context.options)
|
|
134
186
|
};
|
|
135
|
-
yield*
|
|
187
|
+
yield* streamAgentStatusFromServer(
|
|
188
|
+
{ serverUrl, completedStatus: COMPLETED_STATUS },
|
|
189
|
+
mergedContext,
|
|
190
|
+
signal
|
|
191
|
+
);
|
|
136
192
|
},
|
|
137
193
|
resume: async function* (sessionId, signal, storage) {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
throw new Error(`Session ${sessionId} not found`);
|
|
146
|
-
}
|
|
147
|
-
const context = session.context;
|
|
194
|
+
const storedContext = getStoredAgentContext(storage, sessionId);
|
|
195
|
+
const context = {
|
|
196
|
+
content: storedContext.content,
|
|
197
|
+
prompt: storedContext.prompt,
|
|
198
|
+
options: storedContext.options,
|
|
199
|
+
sessionId: storedContext.sessionId ?? sessionId
|
|
200
|
+
};
|
|
148
201
|
const mergedContext = {
|
|
149
202
|
...context,
|
|
150
203
|
options: mergeOptions(context.options)
|
|
151
204
|
};
|
|
152
205
|
yield "Resuming...";
|
|
153
|
-
yield*
|
|
206
|
+
yield* streamAgentStatusFromServer(
|
|
207
|
+
{ serverUrl, completedStatus: COMPLETED_STATUS },
|
|
208
|
+
mergedContext,
|
|
209
|
+
signal
|
|
210
|
+
);
|
|
154
211
|
},
|
|
155
212
|
supportsResume: true,
|
|
156
213
|
supportsFollowUp: true,
|
|
157
|
-
checkConnection
|
|
158
|
-
const now = Date.now();
|
|
159
|
-
if (connectionCache && now - connectionCache.timestamp < CONNECTION_CHECK_TTL_MS) {
|
|
160
|
-
return connectionCache.result;
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
const response = await fetch(`${serverUrl}/health`, { method: "GET" });
|
|
164
|
-
const result = response.ok;
|
|
165
|
-
connectionCache = { result, timestamp: now };
|
|
166
|
-
return result;
|
|
167
|
-
} catch {
|
|
168
|
-
connectionCache = { result: false, timestamp: now };
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
},
|
|
214
|
+
checkConnection,
|
|
172
215
|
abort: async (sessionId) => {
|
|
173
216
|
try {
|
|
174
217
|
await fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" });
|
|
@@ -186,24 +229,25 @@ var createCursorAgentProvider = (providerOptions = {}) => {
|
|
|
186
229
|
var attachAgent = async () => {
|
|
187
230
|
if (typeof window === "undefined") return;
|
|
188
231
|
const provider = createCursorAgentProvider();
|
|
189
|
-
const attach = (
|
|
190
|
-
|
|
232
|
+
const attach = (api) => {
|
|
233
|
+
api.setAgent({ provider, storage: sessionStorage });
|
|
191
234
|
};
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
attach(
|
|
235
|
+
const existingApi = window.__REACT_GRAB__;
|
|
236
|
+
if (isReactGrabApi(existingApi)) {
|
|
237
|
+
attach(existingApi);
|
|
195
238
|
return;
|
|
196
239
|
}
|
|
197
240
|
window.addEventListener(
|
|
198
241
|
"react-grab:init",
|
|
199
242
|
(event) => {
|
|
200
|
-
|
|
201
|
-
|
|
243
|
+
if (!(event instanceof CustomEvent)) return;
|
|
244
|
+
if (!isReactGrabApi(event.detail)) return;
|
|
245
|
+
attach(event.detail);
|
|
202
246
|
},
|
|
203
247
|
{ once: true }
|
|
204
248
|
);
|
|
205
249
|
const apiAfterListener = window.__REACT_GRAB__;
|
|
206
|
-
if (apiAfterListener) {
|
|
250
|
+
if (isReactGrabApi(apiAfterListener)) {
|
|
207
251
|
attach(apiAfterListener);
|
|
208
252
|
}
|
|
209
253
|
};
|
package/dist/client.global.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var ReactGrabCursor=(function(exports){'use strict';var
|
|
2
|
-
`))
|
|
1
|
+
var ReactGrabCursor=(function(exports){'use strict';var A=5e3,b="react-grab:agent-sessions",v=e=>{let t="",r="";for(let n of e.split(`
|
|
2
|
+
`))n.startsWith("event:")?t=n.slice(6).trim():n.startsWith("data:")&&(r=n.slice(5).trim());return {eventType:t,data:r}},T=async function*(e,t){let r=e.getReader(),n=new TextDecoder,o="",s=false,a=()=>{s=true,r.cancel().catch(()=>{});};t.addEventListener("abort",a);try{if(t.aborted)throw new DOMException("Aborted","AbortError");for(;;){let d=await r.read();if(s||t.aborted)throw new DOMException("Aborted","AbortError");let{done:i,value:l}=d;l&&(o+=n.decode(l,{stream:!0}));let c;for(;(c=o.indexOf(`
|
|
3
3
|
|
|
4
|
-
`))!==-1;){let{eventType:
|
|
5
|
-
exports.attachAgent=
|
|
4
|
+
`))!==-1;){let{eventType:f,data:p}=v(o.slice(0,c));if(o=o.slice(c+2),f==="done")return;if(f==="error")throw new Error(p||"Agent error");p&&(yield p);}if(i)break}}finally{t.removeEventListener("abort",a);try{r.releaseLock();}catch{}}},m=e=>typeof e=="object"&&e!==null,S=(e,t,r=b)=>{let n=e.getItem(r);if(!n)throw new Error("No sessions to resume");let o;try{o=JSON.parse(n);}catch{throw new Error("Failed to parse stored sessions")}if(!m(o))throw new Error("Invalid stored sessions");let s=o[t];if(!m(s))throw new Error(`Session ${t} not found`);let a=s.context;if(!m(a))throw new Error(`Session ${t} is invalid`);let d=a.content,i=a.prompt;if(typeof d!="string"||typeof i!="string")throw new Error(`Session ${t} is invalid`);let l=a.options,c=a.sessionId;return {content:d,prompt:i,options:l,sessionId:typeof c=="string"?c:void 0}},h=async function*(e,t,r){let n=Date.now(),o=t.sessionId,s=e.pollIntervalMs??100,a=`${e.serverUrl}${e.agentPath??"/agent"}`,d=()=>{if(!o)return;let i=e.abortPath?.(o)??`/abort/${o}`;fetch(`${e.serverUrl}${i}`,{method:"POST"}).catch(()=>{});};r.addEventListener("abort",d);try{let i=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),signal:r});if(!i.ok)throw new Error(`Server error: ${i.status}`);if(!i.body)throw new Error("No response body");let l=T(i.body,r)[Symbol.asyncIterator](),c=!1,f=l.next(),p=null;for(;!c;){let C=await Promise.race([f.then(u=>({type:"status",iteratorResult:u})),new Promise(u=>setTimeout(()=>u({type:"timeout"}),s))]),g=(Date.now()-n)/1e3;if(C.type==="status"){let u=C.iteratorResult;c=u.done??!1,!c&&u.value&&(p=u.value,f=l.next());}p===e.completedStatus?yield `Completed in ${g.toFixed(1)}s`:p?yield `${p} ${g.toFixed(1)}s`:yield `Working\u2026 ${g.toFixed(1)}s`;}}finally{r.removeEventListener("abort",d);}},E=(e,t=A)=>{let r=null;return async()=>{let n=Date.now();if(r&&n-r.timestamp<t)return r.result;try{let o=await e();return r={result:o,timestamp:n},o}catch{return r={result:false,timestamp:n},false}}};var w="Completed successfully";var x=`http://localhost:${5567}`,y=e=>typeof e=="object"&&e!==null&&"setAgent"in e,_=(e={})=>{let{serverUrl:t=x,getOptions:r}=e,n=s=>({...r?.()??{},...s??{}}),o=E(async()=>(await fetch(`${t}/health`,{method:"GET"})).ok,A);return {send:async function*(s,a){let d={...s,options:n(s.options)};yield*h({serverUrl:t,completedStatus:w},d,a);},resume:async function*(s,a,d){let i=S(d,s),l={content:i.content,prompt:i.prompt,options:i.options,sessionId:i.sessionId??s},c={...l,options:n(l.options)};yield "Resuming...",yield*h({serverUrl:t,completedStatus:w},c,a);},supportsResume:true,supportsFollowUp:true,checkConnection:o,abort:async s=>{try{await fetch(`${t}/abort/${s}`,{method:"POST"});}catch{}},undo:async()=>{try{await fetch(`${t}/undo`,{method:"POST"});}catch{}}}},R=async()=>{if(typeof window>"u")return;let e=_(),t=o=>{o.setAgent({provider:e,storage:sessionStorage});},r=window.__REACT_GRAB__;if(y(r)){t(r);return}window.addEventListener("react-grab:init",o=>{o instanceof CustomEvent&&y(o.detail)&&t(o.detail);},{once:true});let n=window.__REACT_GRAB__;y(n)&&t(n);};R();
|
|
5
|
+
exports.attachAgent=R;exports.createCursorAgentProvider=_;return exports;})({});
|
package/dist/client.js
CHANGED
|
@@ -10,7 +10,7 @@ var parseSSEEvent = (eventBlock) => {
|
|
|
10
10
|
}
|
|
11
11
|
return { eventType, data };
|
|
12
12
|
};
|
|
13
|
-
async function*
|
|
13
|
+
var streamSSE = async function* (stream, signal) {
|
|
14
14
|
const reader = stream.getReader();
|
|
15
15
|
const decoder = new TextDecoder();
|
|
16
16
|
let buffer = "";
|
|
@@ -49,28 +49,54 @@ async function* streamSSE(stream, signal) {
|
|
|
49
49
|
} catch {
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
};
|
|
53
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
54
|
+
var getStoredAgentContext = (storage, sessionId, storageKey = STORAGE_KEY) => {
|
|
55
|
+
const rawSessions = storage.getItem(storageKey);
|
|
56
|
+
if (!rawSessions) throw new Error("No sessions to resume");
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(rawSessions);
|
|
60
|
+
} catch {
|
|
61
|
+
throw new Error("Failed to parse stored sessions");
|
|
62
|
+
}
|
|
63
|
+
if (!isRecord(parsed)) throw new Error("Invalid stored sessions");
|
|
64
|
+
const storedSession = parsed[sessionId];
|
|
65
|
+
if (!isRecord(storedSession)) {
|
|
66
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
67
|
+
}
|
|
68
|
+
const context = storedSession.context;
|
|
69
|
+
if (!isRecord(context)) throw new Error(`Session ${sessionId} is invalid`);
|
|
70
|
+
const content = context.content;
|
|
71
|
+
const prompt = context.prompt;
|
|
72
|
+
if (typeof content !== "string" || typeof prompt !== "string") {
|
|
73
|
+
throw new Error(`Session ${sessionId} is invalid`);
|
|
74
|
+
}
|
|
75
|
+
const options = context.options;
|
|
76
|
+
const storedSessionId = context.sessionId;
|
|
77
|
+
return {
|
|
78
|
+
content,
|
|
79
|
+
prompt,
|
|
80
|
+
options,
|
|
81
|
+
sessionId: typeof storedSessionId === "string" ? storedSessionId : void 0
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
var streamAgentStatusFromServer = async function* (options, context, signal) {
|
|
61
85
|
const startTime = Date.now();
|
|
62
86
|
const sessionId = context.sessionId;
|
|
87
|
+
const pollIntervalMs = options.pollIntervalMs ?? 100;
|
|
88
|
+
const agentUrl = `${options.serverUrl}${options.agentPath ?? "/agent"}`;
|
|
63
89
|
const handleAbort = () => {
|
|
64
|
-
if (sessionId)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
if (!sessionId) return;
|
|
91
|
+
const abortPath = options.abortPath?.(sessionId) ?? `/abort/${sessionId}`;
|
|
92
|
+
fetch(`${options.serverUrl}${abortPath}`, { method: "POST" }).catch(
|
|
93
|
+
() => {
|
|
94
|
+
}
|
|
95
|
+
);
|
|
70
96
|
};
|
|
71
97
|
signal.addEventListener("abort", handleAbort);
|
|
72
98
|
try {
|
|
73
|
-
const response = await fetch(
|
|
99
|
+
const response = await fetch(agentUrl, {
|
|
74
100
|
method: "POST",
|
|
75
101
|
headers: { "Content-Type": "application/json" },
|
|
76
102
|
body: JSON.stringify(context),
|
|
@@ -83,29 +109,29 @@ async function* streamFromServer(serverUrl, context, signal) {
|
|
|
83
109
|
throw new Error("No response body");
|
|
84
110
|
}
|
|
85
111
|
const iterator = streamSSE(response.body, signal)[Symbol.asyncIterator]();
|
|
86
|
-
let
|
|
112
|
+
let isDone = false;
|
|
87
113
|
let pendingNext = iterator.next();
|
|
88
114
|
let lastStatus = null;
|
|
89
|
-
while (!
|
|
115
|
+
while (!isDone) {
|
|
90
116
|
const result = await Promise.race([
|
|
91
117
|
pendingNext.then((iteratorResult) => ({
|
|
92
118
|
type: "status",
|
|
93
119
|
iteratorResult
|
|
94
120
|
})),
|
|
95
121
|
new Promise(
|
|
96
|
-
(resolve) => setTimeout(() => resolve({ type: "timeout" }),
|
|
122
|
+
(resolve) => setTimeout(() => resolve({ type: "timeout" }), pollIntervalMs)
|
|
97
123
|
)
|
|
98
124
|
]);
|
|
99
125
|
const elapsedSeconds = (Date.now() - startTime) / 1e3;
|
|
100
126
|
if (result.type === "status") {
|
|
101
127
|
const iteratorResult = result.iteratorResult;
|
|
102
|
-
|
|
103
|
-
if (!
|
|
128
|
+
isDone = iteratorResult.done ?? false;
|
|
129
|
+
if (!isDone && iteratorResult.value) {
|
|
104
130
|
lastStatus = iteratorResult.value;
|
|
105
131
|
pendingNext = iterator.next();
|
|
106
132
|
}
|
|
107
133
|
}
|
|
108
|
-
if (lastStatus ===
|
|
134
|
+
if (lastStatus === options.completedStatus) {
|
|
109
135
|
yield `Completed in ${elapsedSeconds.toFixed(1)}s`;
|
|
110
136
|
} else if (lastStatus) {
|
|
111
137
|
yield `${lastStatus} ${elapsedSeconds.toFixed(1)}s`;
|
|
@@ -116,57 +142,74 @@ async function* streamFromServer(serverUrl, context, signal) {
|
|
|
116
142
|
} finally {
|
|
117
143
|
signal.removeEventListener("abort", handleAbort);
|
|
118
144
|
}
|
|
119
|
-
}
|
|
145
|
+
};
|
|
146
|
+
var createCachedConnectionChecker = (checkConnection, ttlMs = CONNECTION_CHECK_TTL_MS) => {
|
|
147
|
+
let cache = null;
|
|
148
|
+
return async () => {
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
if (cache && now - cache.timestamp < ttlMs) return cache.result;
|
|
151
|
+
try {
|
|
152
|
+
const result = await checkConnection();
|
|
153
|
+
cache = { result, timestamp: now };
|
|
154
|
+
return result;
|
|
155
|
+
} catch {
|
|
156
|
+
cache = { result: false, timestamp: now };
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/constants.ts
|
|
163
|
+
var DEFAULT_PORT = 5567;
|
|
164
|
+
var COMPLETED_STATUS = "Completed successfully";
|
|
165
|
+
|
|
166
|
+
// src/client.ts
|
|
167
|
+
var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
|
|
168
|
+
var isReactGrabApi = (value) => typeof value === "object" && value !== null && "setAgent" in value;
|
|
120
169
|
var createCursorAgentProvider = (providerOptions = {}) => {
|
|
121
170
|
const { serverUrl = DEFAULT_SERVER_URL, getOptions } = providerOptions;
|
|
122
|
-
let connectionCache = null;
|
|
123
171
|
const mergeOptions = (contextOptions) => ({
|
|
124
172
|
...getOptions?.() ?? {},
|
|
125
173
|
...contextOptions ?? {}
|
|
126
174
|
});
|
|
175
|
+
const checkConnection = createCachedConnectionChecker(async () => {
|
|
176
|
+
const response = await fetch(`${serverUrl}/health`, { method: "GET" });
|
|
177
|
+
return response.ok;
|
|
178
|
+
}, CONNECTION_CHECK_TTL_MS);
|
|
127
179
|
return {
|
|
128
180
|
send: async function* (context, signal) {
|
|
129
181
|
const mergedContext = {
|
|
130
182
|
...context,
|
|
131
183
|
options: mergeOptions(context.options)
|
|
132
184
|
};
|
|
133
|
-
yield*
|
|
185
|
+
yield* streamAgentStatusFromServer(
|
|
186
|
+
{ serverUrl, completedStatus: COMPLETED_STATUS },
|
|
187
|
+
mergedContext,
|
|
188
|
+
signal
|
|
189
|
+
);
|
|
134
190
|
},
|
|
135
191
|
resume: async function* (sessionId, signal, storage) {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
throw new Error(`Session ${sessionId} not found`);
|
|
144
|
-
}
|
|
145
|
-
const context = session.context;
|
|
192
|
+
const storedContext = getStoredAgentContext(storage, sessionId);
|
|
193
|
+
const context = {
|
|
194
|
+
content: storedContext.content,
|
|
195
|
+
prompt: storedContext.prompt,
|
|
196
|
+
options: storedContext.options,
|
|
197
|
+
sessionId: storedContext.sessionId ?? sessionId
|
|
198
|
+
};
|
|
146
199
|
const mergedContext = {
|
|
147
200
|
...context,
|
|
148
201
|
options: mergeOptions(context.options)
|
|
149
202
|
};
|
|
150
203
|
yield "Resuming...";
|
|
151
|
-
yield*
|
|
204
|
+
yield* streamAgentStatusFromServer(
|
|
205
|
+
{ serverUrl, completedStatus: COMPLETED_STATUS },
|
|
206
|
+
mergedContext,
|
|
207
|
+
signal
|
|
208
|
+
);
|
|
152
209
|
},
|
|
153
210
|
supportsResume: true,
|
|
154
211
|
supportsFollowUp: true,
|
|
155
|
-
checkConnection
|
|
156
|
-
const now = Date.now();
|
|
157
|
-
if (connectionCache && now - connectionCache.timestamp < CONNECTION_CHECK_TTL_MS) {
|
|
158
|
-
return connectionCache.result;
|
|
159
|
-
}
|
|
160
|
-
try {
|
|
161
|
-
const response = await fetch(`${serverUrl}/health`, { method: "GET" });
|
|
162
|
-
const result = response.ok;
|
|
163
|
-
connectionCache = { result, timestamp: now };
|
|
164
|
-
return result;
|
|
165
|
-
} catch {
|
|
166
|
-
connectionCache = { result: false, timestamp: now };
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
},
|
|
212
|
+
checkConnection,
|
|
170
213
|
abort: async (sessionId) => {
|
|
171
214
|
try {
|
|
172
215
|
await fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" });
|
|
@@ -184,24 +227,25 @@ var createCursorAgentProvider = (providerOptions = {}) => {
|
|
|
184
227
|
var attachAgent = async () => {
|
|
185
228
|
if (typeof window === "undefined") return;
|
|
186
229
|
const provider = createCursorAgentProvider();
|
|
187
|
-
const attach = (
|
|
188
|
-
|
|
230
|
+
const attach = (api) => {
|
|
231
|
+
api.setAgent({ provider, storage: sessionStorage });
|
|
189
232
|
};
|
|
190
|
-
const
|
|
191
|
-
if (
|
|
192
|
-
attach(
|
|
233
|
+
const existingApi = window.__REACT_GRAB__;
|
|
234
|
+
if (isReactGrabApi(existingApi)) {
|
|
235
|
+
attach(existingApi);
|
|
193
236
|
return;
|
|
194
237
|
}
|
|
195
238
|
window.addEventListener(
|
|
196
239
|
"react-grab:init",
|
|
197
240
|
(event) => {
|
|
198
|
-
|
|
199
|
-
|
|
241
|
+
if (!(event instanceof CustomEvent)) return;
|
|
242
|
+
if (!isReactGrabApi(event.detail)) return;
|
|
243
|
+
attach(event.detail);
|
|
200
244
|
},
|
|
201
245
|
{ once: true }
|
|
202
246
|
);
|
|
203
247
|
const apiAfterListener = window.__REACT_GRAB__;
|
|
204
|
-
if (apiAfterListener) {
|
|
248
|
+
if (isReactGrabApi(apiAfterListener)) {
|
|
205
249
|
attach(apiAfterListener);
|
|
206
250
|
}
|
|
207
251
|
};
|
package/dist/server.cjs
CHANGED
|
@@ -12129,9 +12129,11 @@ Check that the command is executable: chmod +x $(which ${commandName})`;
|
|
|
12129
12129
|
};
|
|
12130
12130
|
|
|
12131
12131
|
// src/server.ts
|
|
12132
|
-
var VERSION = "0.0.
|
|
12132
|
+
var VERSION = "0.0.89";
|
|
12133
12133
|
try {
|
|
12134
|
-
fetch(
|
|
12134
|
+
fetch(
|
|
12135
|
+
`https://www.react-grab.com/api/version?source=cursor&t=${Date.now()}`
|
|
12136
|
+
).catch(() => {
|
|
12135
12137
|
});
|
|
12136
12138
|
} catch {
|
|
12137
12139
|
}
|
|
@@ -12151,6 +12153,181 @@ var extractTextFromMessage = (message) => {
|
|
|
12151
12153
|
if (!message?.content) return "";
|
|
12152
12154
|
return message.content.filter((block) => block.type === "text").map((block) => block.text).join(" ").trim();
|
|
12153
12155
|
};
|
|
12156
|
+
var runAgent = async function* (prompt, options) {
|
|
12157
|
+
const cursorAgentArgs = [
|
|
12158
|
+
"--print",
|
|
12159
|
+
"--output-format",
|
|
12160
|
+
"stream-json",
|
|
12161
|
+
"--force"
|
|
12162
|
+
];
|
|
12163
|
+
if (options?.model) {
|
|
12164
|
+
cursorAgentArgs.push("--model", options.model);
|
|
12165
|
+
}
|
|
12166
|
+
const workspacePath = options?.workspace ?? options?.cwd ?? process.env.REACT_GRAB_CWD ?? process.cwd();
|
|
12167
|
+
const cursorChatId = options?.sessionId ? cursorSessionMap.get(options.sessionId) : void 0;
|
|
12168
|
+
if (cursorChatId) {
|
|
12169
|
+
cursorAgentArgs.push("--resume", cursorChatId);
|
|
12170
|
+
}
|
|
12171
|
+
let cursorProcess;
|
|
12172
|
+
let stderrBuffer = "";
|
|
12173
|
+
try {
|
|
12174
|
+
yield { type: "status", content: "Thinking\u2026" };
|
|
12175
|
+
cursorProcess = execa("cursor-agent", cursorAgentArgs, {
|
|
12176
|
+
stdin: "pipe",
|
|
12177
|
+
stdout: "pipe",
|
|
12178
|
+
stderr: "pipe",
|
|
12179
|
+
env: { ...process.env },
|
|
12180
|
+
cwd: workspacePath
|
|
12181
|
+
});
|
|
12182
|
+
if (options?.sessionId) {
|
|
12183
|
+
activeProcesses.set(options.sessionId, cursorProcess);
|
|
12184
|
+
}
|
|
12185
|
+
if (cursorProcess.stderr) {
|
|
12186
|
+
cursorProcess.stderr.on("data", (chunk) => {
|
|
12187
|
+
stderrBuffer += chunk.toString();
|
|
12188
|
+
});
|
|
12189
|
+
}
|
|
12190
|
+
const messageQueue = [];
|
|
12191
|
+
let resolveWait = null;
|
|
12192
|
+
let processEnded = false;
|
|
12193
|
+
let capturedCursorChatId;
|
|
12194
|
+
const enqueueMessage = (message) => {
|
|
12195
|
+
messageQueue.push(message);
|
|
12196
|
+
if (resolveWait) {
|
|
12197
|
+
resolveWait();
|
|
12198
|
+
resolveWait = null;
|
|
12199
|
+
}
|
|
12200
|
+
};
|
|
12201
|
+
const processLine = (line) => {
|
|
12202
|
+
const event = parseStreamLine(line);
|
|
12203
|
+
if (!event) return;
|
|
12204
|
+
if (!capturedCursorChatId && event.session_id) {
|
|
12205
|
+
capturedCursorChatId = event.session_id;
|
|
12206
|
+
}
|
|
12207
|
+
switch (event.type) {
|
|
12208
|
+
case "assistant": {
|
|
12209
|
+
const textContent = extractTextFromMessage(event.message);
|
|
12210
|
+
if (textContent) {
|
|
12211
|
+
enqueueMessage({ type: "status", content: textContent });
|
|
12212
|
+
}
|
|
12213
|
+
break;
|
|
12214
|
+
}
|
|
12215
|
+
case "result":
|
|
12216
|
+
if (event.subtype === "success") {
|
|
12217
|
+
enqueueMessage({ type: "status", content: COMPLETED_STATUS });
|
|
12218
|
+
} else if (event.subtype === "error" || event.is_error) {
|
|
12219
|
+
enqueueMessage({
|
|
12220
|
+
type: "error",
|
|
12221
|
+
content: event.result || "Unknown error"
|
|
12222
|
+
});
|
|
12223
|
+
} else {
|
|
12224
|
+
enqueueMessage({ type: "status", content: "Task finished" });
|
|
12225
|
+
}
|
|
12226
|
+
break;
|
|
12227
|
+
}
|
|
12228
|
+
};
|
|
12229
|
+
let buffer = "";
|
|
12230
|
+
if (cursorProcess.stdout) {
|
|
12231
|
+
cursorProcess.stdout.on("data", (chunk) => {
|
|
12232
|
+
buffer += chunk.toString();
|
|
12233
|
+
let newlineIndex;
|
|
12234
|
+
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
12235
|
+
const line = buffer.slice(0, newlineIndex);
|
|
12236
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
12237
|
+
processLine(line);
|
|
12238
|
+
}
|
|
12239
|
+
});
|
|
12240
|
+
}
|
|
12241
|
+
if (cursorProcess.stdin) {
|
|
12242
|
+
cursorProcess.stdin.write(prompt);
|
|
12243
|
+
cursorProcess.stdin.end();
|
|
12244
|
+
}
|
|
12245
|
+
const childProcess4 = cursorProcess;
|
|
12246
|
+
childProcess4.on("close", (code) => {
|
|
12247
|
+
if (options?.sessionId) {
|
|
12248
|
+
activeProcesses.delete(options.sessionId);
|
|
12249
|
+
}
|
|
12250
|
+
if (buffer.trim()) {
|
|
12251
|
+
processLine(buffer);
|
|
12252
|
+
}
|
|
12253
|
+
if (options?.sessionId && capturedCursorChatId) {
|
|
12254
|
+
cursorSessionMap.set(options.sessionId, capturedCursorChatId);
|
|
12255
|
+
}
|
|
12256
|
+
if (capturedCursorChatId) {
|
|
12257
|
+
lastCursorChatId = capturedCursorChatId;
|
|
12258
|
+
}
|
|
12259
|
+
processEnded = true;
|
|
12260
|
+
if (code !== 0 && !childProcess4.killed) {
|
|
12261
|
+
enqueueMessage({
|
|
12262
|
+
type: "error",
|
|
12263
|
+
content: `cursor-agent exited with code ${code}`
|
|
12264
|
+
});
|
|
12265
|
+
}
|
|
12266
|
+
enqueueMessage({ type: "done", content: "" });
|
|
12267
|
+
if (resolveWait) {
|
|
12268
|
+
resolveWait();
|
|
12269
|
+
resolveWait = null;
|
|
12270
|
+
}
|
|
12271
|
+
});
|
|
12272
|
+
childProcess4.on("error", (error) => {
|
|
12273
|
+
if (options?.sessionId) {
|
|
12274
|
+
activeProcesses.delete(options.sessionId);
|
|
12275
|
+
}
|
|
12276
|
+
processEnded = true;
|
|
12277
|
+
const isNotInstalled = "code" in error && error.code === "ENOENT";
|
|
12278
|
+
if (isNotInstalled) {
|
|
12279
|
+
enqueueMessage({
|
|
12280
|
+
type: "error",
|
|
12281
|
+
content: "cursor-agent is not installed. Please install the Cursor Agent CLI to use this provider.\n\nInstallation: https://cursor.com/docs/cli/overview"
|
|
12282
|
+
});
|
|
12283
|
+
} else {
|
|
12284
|
+
const errorMessage = formatSpawnError(error, "cursor-agent");
|
|
12285
|
+
const stderrContent = stderrBuffer.trim();
|
|
12286
|
+
const fullError = stderrContent ? `${errorMessage}
|
|
12287
|
+
|
|
12288
|
+
stderr:
|
|
12289
|
+
${stderrContent}` : errorMessage;
|
|
12290
|
+
enqueueMessage({ type: "error", content: fullError });
|
|
12291
|
+
}
|
|
12292
|
+
enqueueMessage({ type: "done", content: "" });
|
|
12293
|
+
if (resolveWait) {
|
|
12294
|
+
resolveWait();
|
|
12295
|
+
resolveWait = null;
|
|
12296
|
+
}
|
|
12297
|
+
});
|
|
12298
|
+
while (true) {
|
|
12299
|
+
if (options?.signal?.aborted) {
|
|
12300
|
+
if (cursorProcess && !cursorProcess.killed) {
|
|
12301
|
+
cursorProcess.kill("SIGTERM");
|
|
12302
|
+
}
|
|
12303
|
+
return;
|
|
12304
|
+
}
|
|
12305
|
+
if (messageQueue.length > 0) {
|
|
12306
|
+
const message = messageQueue.shift();
|
|
12307
|
+
if (message.type === "done") {
|
|
12308
|
+
yield message;
|
|
12309
|
+
return;
|
|
12310
|
+
}
|
|
12311
|
+
yield message;
|
|
12312
|
+
} else if (processEnded) {
|
|
12313
|
+
return;
|
|
12314
|
+
} else {
|
|
12315
|
+
await new Promise((resolve) => {
|
|
12316
|
+
resolveWait = resolve;
|
|
12317
|
+
});
|
|
12318
|
+
}
|
|
12319
|
+
}
|
|
12320
|
+
} catch (error) {
|
|
12321
|
+
const errorMessage = error instanceof Error ? formatSpawnError(error, "cursor-agent") : "Unknown error";
|
|
12322
|
+
const stderrContent = stderrBuffer.trim();
|
|
12323
|
+
const fullError = stderrContent ? `${errorMessage}
|
|
12324
|
+
|
|
12325
|
+
stderr:
|
|
12326
|
+
${stderrContent}` : errorMessage;
|
|
12327
|
+
yield { type: "error", content: fullError };
|
|
12328
|
+
yield { type: "done", content: "" };
|
|
12329
|
+
}
|
|
12330
|
+
};
|
|
12154
12331
|
var createServer = () => {
|
|
12155
12332
|
const app = new Hono2();
|
|
12156
12333
|
app.use("*", cors());
|
|
@@ -12163,141 +12340,18 @@ var createServer = () => {
|
|
|
12163
12340
|
|
|
12164
12341
|
${content}`;
|
|
12165
12342
|
return streamSSE(context, async (stream2) => {
|
|
12166
|
-
const
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
"
|
|
12171
|
-
];
|
|
12172
|
-
if (options?.model) {
|
|
12173
|
-
cursorAgentArgs.push("--model", options.model);
|
|
12174
|
-
}
|
|
12175
|
-
const workspacePath = options?.workspace ?? process.env.REACT_GRAB_CWD ?? process.cwd();
|
|
12176
|
-
if (isFollowUp && cursorChatId) {
|
|
12177
|
-
cursorAgentArgs.push("--resume", cursorChatId);
|
|
12178
|
-
}
|
|
12179
|
-
let cursorProcess;
|
|
12180
|
-
let stderrBuffer = "";
|
|
12181
|
-
try {
|
|
12182
|
-
await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
|
|
12183
|
-
cursorProcess = execa("cursor-agent", cursorAgentArgs, {
|
|
12184
|
-
stdin: "pipe",
|
|
12185
|
-
stdout: "pipe",
|
|
12186
|
-
stderr: "pipe",
|
|
12187
|
-
env: { ...process.env },
|
|
12188
|
-
cwd: workspacePath
|
|
12189
|
-
});
|
|
12190
|
-
if (sessionId) {
|
|
12191
|
-
activeProcesses.set(sessionId, cursorProcess);
|
|
12192
|
-
}
|
|
12193
|
-
if (cursorProcess.stderr) {
|
|
12194
|
-
cursorProcess.stderr.on("data", (chunk) => {
|
|
12195
|
-
stderrBuffer += chunk.toString();
|
|
12196
|
-
});
|
|
12197
|
-
}
|
|
12198
|
-
let buffer = "";
|
|
12199
|
-
let capturedCursorChatId;
|
|
12200
|
-
const processLine = async (line) => {
|
|
12201
|
-
const event = parseStreamLine(line);
|
|
12202
|
-
if (!event) return;
|
|
12203
|
-
if (!capturedCursorChatId && event.session_id) {
|
|
12204
|
-
capturedCursorChatId = event.session_id;
|
|
12205
|
-
}
|
|
12206
|
-
switch (event.type) {
|
|
12207
|
-
case "assistant": {
|
|
12208
|
-
const textContent = extractTextFromMessage(event.message);
|
|
12209
|
-
if (textContent) {
|
|
12210
|
-
await stream2.writeSSE({ data: textContent, event: "status" });
|
|
12211
|
-
}
|
|
12212
|
-
break;
|
|
12213
|
-
}
|
|
12214
|
-
case "result":
|
|
12215
|
-
if (event.subtype === "success") {
|
|
12216
|
-
await stream2.writeSSE({
|
|
12217
|
-
data: COMPLETED_STATUS,
|
|
12218
|
-
event: "status"
|
|
12219
|
-
});
|
|
12220
|
-
} else if (event.subtype === "error" || event.is_error) {
|
|
12221
|
-
await stream2.writeSSE({
|
|
12222
|
-
data: `Error: ${event.result || "Unknown error"}`,
|
|
12223
|
-
event: "error"
|
|
12224
|
-
});
|
|
12225
|
-
} else {
|
|
12226
|
-
await stream2.writeSSE({
|
|
12227
|
-
data: "Task finished",
|
|
12228
|
-
event: "status"
|
|
12229
|
-
});
|
|
12230
|
-
}
|
|
12231
|
-
break;
|
|
12232
|
-
}
|
|
12233
|
-
};
|
|
12234
|
-
if (cursorProcess.stdout) {
|
|
12235
|
-
cursorProcess.stdout.on("data", async (chunk) => {
|
|
12236
|
-
buffer += chunk.toString();
|
|
12237
|
-
let newlineIndex;
|
|
12238
|
-
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
12239
|
-
const line = buffer.slice(0, newlineIndex);
|
|
12240
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
12241
|
-
await processLine(line);
|
|
12242
|
-
}
|
|
12243
|
-
});
|
|
12244
|
-
}
|
|
12245
|
-
if (cursorProcess.stdin) {
|
|
12246
|
-
cursorProcess.stdin.write(userPrompt);
|
|
12247
|
-
cursorProcess.stdin.end();
|
|
12248
|
-
}
|
|
12249
|
-
if (cursorProcess) {
|
|
12250
|
-
const childProcess4 = cursorProcess;
|
|
12251
|
-
await new Promise((resolve, reject) => {
|
|
12252
|
-
childProcess4.on("close", (code) => {
|
|
12253
|
-
if (sessionId) {
|
|
12254
|
-
activeProcesses.delete(sessionId);
|
|
12255
|
-
}
|
|
12256
|
-
if (code === 0 || childProcess4.killed) {
|
|
12257
|
-
resolve();
|
|
12258
|
-
} else {
|
|
12259
|
-
reject(new Error(`cursor-agent exited with code ${code}`));
|
|
12260
|
-
}
|
|
12261
|
-
});
|
|
12262
|
-
childProcess4.on("error", (error) => {
|
|
12263
|
-
if (sessionId) {
|
|
12264
|
-
activeProcesses.delete(sessionId);
|
|
12265
|
-
}
|
|
12266
|
-
reject(error);
|
|
12267
|
-
});
|
|
12268
|
-
});
|
|
12269
|
-
}
|
|
12270
|
-
if (buffer.trim()) {
|
|
12271
|
-
await processLine(buffer);
|
|
12272
|
-
}
|
|
12273
|
-
if (sessionId && capturedCursorChatId) {
|
|
12274
|
-
cursorSessionMap.set(sessionId, capturedCursorChatId);
|
|
12275
|
-
}
|
|
12276
|
-
if (capturedCursorChatId) {
|
|
12277
|
-
lastCursorChatId = capturedCursorChatId;
|
|
12278
|
-
}
|
|
12279
|
-
await stream2.writeSSE({ data: "", event: "done" });
|
|
12280
|
-
} catch (error) {
|
|
12281
|
-
const isNotInstalled = error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
12282
|
-
if (isNotInstalled) {
|
|
12343
|
+
for await (const message of runAgent(userPrompt, {
|
|
12344
|
+
...options,
|
|
12345
|
+
sessionId
|
|
12346
|
+
})) {
|
|
12347
|
+
if (message.type === "error") {
|
|
12283
12348
|
await stream2.writeSSE({
|
|
12284
|
-
data: `Error:
|
|
12285
|
-
|
|
12286
|
-
Installation: https://cursor.com/docs/cli/overview`,
|
|
12349
|
+
data: `Error: ${message.content}`,
|
|
12287
12350
|
event: "error"
|
|
12288
12351
|
});
|
|
12289
|
-
|
|
12352
|
+
} else {
|
|
12353
|
+
await stream2.writeSSE({ data: message.content, event: message.type });
|
|
12290
12354
|
}
|
|
12291
|
-
const errorMessage = error instanceof Error ? formatSpawnError(error, "cursor-agent") : "Unknown error";
|
|
12292
|
-
const stderrContent = stderrBuffer.trim();
|
|
12293
|
-
const fullError = stderrContent ? `${errorMessage}
|
|
12294
|
-
|
|
12295
|
-
stderr:
|
|
12296
|
-
${stderrContent}` : errorMessage;
|
|
12297
|
-
await stream2.writeSSE({
|
|
12298
|
-
data: `Error: ${fullError}`,
|
|
12299
|
-
event: "error"
|
|
12300
|
-
});
|
|
12301
12355
|
}
|
|
12302
12356
|
});
|
|
12303
12357
|
});
|
|
@@ -12363,4 +12417,5 @@ if ((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filen
|
|
|
12363
12417
|
}
|
|
12364
12418
|
|
|
12365
12419
|
exports.createServer = createServer;
|
|
12420
|
+
exports.runAgent = runAgent;
|
|
12366
12421
|
exports.startServer = startServer;
|
package/dist/server.d.cts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
|
+
import { AgentCoreOptions, AgentMessage } from '@react-grab/utils/server';
|
|
3
4
|
|
|
5
|
+
interface CursorAgentOptions extends AgentCoreOptions {
|
|
6
|
+
model?: string;
|
|
7
|
+
workspace?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const runAgent: (prompt: string, options?: CursorAgentOptions) => AsyncGenerator<AgentMessage>;
|
|
4
10
|
declare const createServer: () => Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
5
11
|
declare const startServer: (port?: number) => Promise<void>;
|
|
6
12
|
|
|
7
|
-
export { createServer, startServer };
|
|
13
|
+
export { type CursorAgentOptions, createServer, runAgent, startServer };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
|
+
import { AgentCoreOptions, AgentMessage } from '@react-grab/utils/server';
|
|
3
4
|
|
|
5
|
+
interface CursorAgentOptions extends AgentCoreOptions {
|
|
6
|
+
model?: string;
|
|
7
|
+
workspace?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const runAgent: (prompt: string, options?: CursorAgentOptions) => AsyncGenerator<AgentMessage>;
|
|
4
10
|
declare const createServer: () => Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
5
11
|
declare const startServer: (port?: number) => Promise<void>;
|
|
6
12
|
|
|
7
|
-
export { createServer, startServer };
|
|
13
|
+
export { type CursorAgentOptions, createServer, runAgent, startServer };
|
package/dist/server.js
CHANGED
|
@@ -12117,9 +12117,11 @@ Check that the command is executable: chmod +x $(which ${commandName})`;
|
|
|
12117
12117
|
};
|
|
12118
12118
|
|
|
12119
12119
|
// src/server.ts
|
|
12120
|
-
var VERSION = "0.0.
|
|
12120
|
+
var VERSION = "0.0.89";
|
|
12121
12121
|
try {
|
|
12122
|
-
fetch(
|
|
12122
|
+
fetch(
|
|
12123
|
+
`https://www.react-grab.com/api/version?source=cursor&t=${Date.now()}`
|
|
12124
|
+
).catch(() => {
|
|
12123
12125
|
});
|
|
12124
12126
|
} catch {
|
|
12125
12127
|
}
|
|
@@ -12139,6 +12141,181 @@ var extractTextFromMessage = (message) => {
|
|
|
12139
12141
|
if (!message?.content) return "";
|
|
12140
12142
|
return message.content.filter((block) => block.type === "text").map((block) => block.text).join(" ").trim();
|
|
12141
12143
|
};
|
|
12144
|
+
var runAgent = async function* (prompt, options) {
|
|
12145
|
+
const cursorAgentArgs = [
|
|
12146
|
+
"--print",
|
|
12147
|
+
"--output-format",
|
|
12148
|
+
"stream-json",
|
|
12149
|
+
"--force"
|
|
12150
|
+
];
|
|
12151
|
+
if (options?.model) {
|
|
12152
|
+
cursorAgentArgs.push("--model", options.model);
|
|
12153
|
+
}
|
|
12154
|
+
const workspacePath = options?.workspace ?? options?.cwd ?? process.env.REACT_GRAB_CWD ?? process.cwd();
|
|
12155
|
+
const cursorChatId = options?.sessionId ? cursorSessionMap.get(options.sessionId) : void 0;
|
|
12156
|
+
if (cursorChatId) {
|
|
12157
|
+
cursorAgentArgs.push("--resume", cursorChatId);
|
|
12158
|
+
}
|
|
12159
|
+
let cursorProcess;
|
|
12160
|
+
let stderrBuffer = "";
|
|
12161
|
+
try {
|
|
12162
|
+
yield { type: "status", content: "Thinking\u2026" };
|
|
12163
|
+
cursorProcess = execa("cursor-agent", cursorAgentArgs, {
|
|
12164
|
+
stdin: "pipe",
|
|
12165
|
+
stdout: "pipe",
|
|
12166
|
+
stderr: "pipe",
|
|
12167
|
+
env: { ...process.env },
|
|
12168
|
+
cwd: workspacePath
|
|
12169
|
+
});
|
|
12170
|
+
if (options?.sessionId) {
|
|
12171
|
+
activeProcesses.set(options.sessionId, cursorProcess);
|
|
12172
|
+
}
|
|
12173
|
+
if (cursorProcess.stderr) {
|
|
12174
|
+
cursorProcess.stderr.on("data", (chunk) => {
|
|
12175
|
+
stderrBuffer += chunk.toString();
|
|
12176
|
+
});
|
|
12177
|
+
}
|
|
12178
|
+
const messageQueue = [];
|
|
12179
|
+
let resolveWait = null;
|
|
12180
|
+
let processEnded = false;
|
|
12181
|
+
let capturedCursorChatId;
|
|
12182
|
+
const enqueueMessage = (message) => {
|
|
12183
|
+
messageQueue.push(message);
|
|
12184
|
+
if (resolveWait) {
|
|
12185
|
+
resolveWait();
|
|
12186
|
+
resolveWait = null;
|
|
12187
|
+
}
|
|
12188
|
+
};
|
|
12189
|
+
const processLine = (line) => {
|
|
12190
|
+
const event = parseStreamLine(line);
|
|
12191
|
+
if (!event) return;
|
|
12192
|
+
if (!capturedCursorChatId && event.session_id) {
|
|
12193
|
+
capturedCursorChatId = event.session_id;
|
|
12194
|
+
}
|
|
12195
|
+
switch (event.type) {
|
|
12196
|
+
case "assistant": {
|
|
12197
|
+
const textContent = extractTextFromMessage(event.message);
|
|
12198
|
+
if (textContent) {
|
|
12199
|
+
enqueueMessage({ type: "status", content: textContent });
|
|
12200
|
+
}
|
|
12201
|
+
break;
|
|
12202
|
+
}
|
|
12203
|
+
case "result":
|
|
12204
|
+
if (event.subtype === "success") {
|
|
12205
|
+
enqueueMessage({ type: "status", content: COMPLETED_STATUS });
|
|
12206
|
+
} else if (event.subtype === "error" || event.is_error) {
|
|
12207
|
+
enqueueMessage({
|
|
12208
|
+
type: "error",
|
|
12209
|
+
content: event.result || "Unknown error"
|
|
12210
|
+
});
|
|
12211
|
+
} else {
|
|
12212
|
+
enqueueMessage({ type: "status", content: "Task finished" });
|
|
12213
|
+
}
|
|
12214
|
+
break;
|
|
12215
|
+
}
|
|
12216
|
+
};
|
|
12217
|
+
let buffer = "";
|
|
12218
|
+
if (cursorProcess.stdout) {
|
|
12219
|
+
cursorProcess.stdout.on("data", (chunk) => {
|
|
12220
|
+
buffer += chunk.toString();
|
|
12221
|
+
let newlineIndex;
|
|
12222
|
+
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
12223
|
+
const line = buffer.slice(0, newlineIndex);
|
|
12224
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
12225
|
+
processLine(line);
|
|
12226
|
+
}
|
|
12227
|
+
});
|
|
12228
|
+
}
|
|
12229
|
+
if (cursorProcess.stdin) {
|
|
12230
|
+
cursorProcess.stdin.write(prompt);
|
|
12231
|
+
cursorProcess.stdin.end();
|
|
12232
|
+
}
|
|
12233
|
+
const childProcess4 = cursorProcess;
|
|
12234
|
+
childProcess4.on("close", (code) => {
|
|
12235
|
+
if (options?.sessionId) {
|
|
12236
|
+
activeProcesses.delete(options.sessionId);
|
|
12237
|
+
}
|
|
12238
|
+
if (buffer.trim()) {
|
|
12239
|
+
processLine(buffer);
|
|
12240
|
+
}
|
|
12241
|
+
if (options?.sessionId && capturedCursorChatId) {
|
|
12242
|
+
cursorSessionMap.set(options.sessionId, capturedCursorChatId);
|
|
12243
|
+
}
|
|
12244
|
+
if (capturedCursorChatId) {
|
|
12245
|
+
lastCursorChatId = capturedCursorChatId;
|
|
12246
|
+
}
|
|
12247
|
+
processEnded = true;
|
|
12248
|
+
if (code !== 0 && !childProcess4.killed) {
|
|
12249
|
+
enqueueMessage({
|
|
12250
|
+
type: "error",
|
|
12251
|
+
content: `cursor-agent exited with code ${code}`
|
|
12252
|
+
});
|
|
12253
|
+
}
|
|
12254
|
+
enqueueMessage({ type: "done", content: "" });
|
|
12255
|
+
if (resolveWait) {
|
|
12256
|
+
resolveWait();
|
|
12257
|
+
resolveWait = null;
|
|
12258
|
+
}
|
|
12259
|
+
});
|
|
12260
|
+
childProcess4.on("error", (error) => {
|
|
12261
|
+
if (options?.sessionId) {
|
|
12262
|
+
activeProcesses.delete(options.sessionId);
|
|
12263
|
+
}
|
|
12264
|
+
processEnded = true;
|
|
12265
|
+
const isNotInstalled = "code" in error && error.code === "ENOENT";
|
|
12266
|
+
if (isNotInstalled) {
|
|
12267
|
+
enqueueMessage({
|
|
12268
|
+
type: "error",
|
|
12269
|
+
content: "cursor-agent is not installed. Please install the Cursor Agent CLI to use this provider.\n\nInstallation: https://cursor.com/docs/cli/overview"
|
|
12270
|
+
});
|
|
12271
|
+
} else {
|
|
12272
|
+
const errorMessage = formatSpawnError(error, "cursor-agent");
|
|
12273
|
+
const stderrContent = stderrBuffer.trim();
|
|
12274
|
+
const fullError = stderrContent ? `${errorMessage}
|
|
12275
|
+
|
|
12276
|
+
stderr:
|
|
12277
|
+
${stderrContent}` : errorMessage;
|
|
12278
|
+
enqueueMessage({ type: "error", content: fullError });
|
|
12279
|
+
}
|
|
12280
|
+
enqueueMessage({ type: "done", content: "" });
|
|
12281
|
+
if (resolveWait) {
|
|
12282
|
+
resolveWait();
|
|
12283
|
+
resolveWait = null;
|
|
12284
|
+
}
|
|
12285
|
+
});
|
|
12286
|
+
while (true) {
|
|
12287
|
+
if (options?.signal?.aborted) {
|
|
12288
|
+
if (cursorProcess && !cursorProcess.killed) {
|
|
12289
|
+
cursorProcess.kill("SIGTERM");
|
|
12290
|
+
}
|
|
12291
|
+
return;
|
|
12292
|
+
}
|
|
12293
|
+
if (messageQueue.length > 0) {
|
|
12294
|
+
const message = messageQueue.shift();
|
|
12295
|
+
if (message.type === "done") {
|
|
12296
|
+
yield message;
|
|
12297
|
+
return;
|
|
12298
|
+
}
|
|
12299
|
+
yield message;
|
|
12300
|
+
} else if (processEnded) {
|
|
12301
|
+
return;
|
|
12302
|
+
} else {
|
|
12303
|
+
await new Promise((resolve) => {
|
|
12304
|
+
resolveWait = resolve;
|
|
12305
|
+
});
|
|
12306
|
+
}
|
|
12307
|
+
}
|
|
12308
|
+
} catch (error) {
|
|
12309
|
+
const errorMessage = error instanceof Error ? formatSpawnError(error, "cursor-agent") : "Unknown error";
|
|
12310
|
+
const stderrContent = stderrBuffer.trim();
|
|
12311
|
+
const fullError = stderrContent ? `${errorMessage}
|
|
12312
|
+
|
|
12313
|
+
stderr:
|
|
12314
|
+
${stderrContent}` : errorMessage;
|
|
12315
|
+
yield { type: "error", content: fullError };
|
|
12316
|
+
yield { type: "done", content: "" };
|
|
12317
|
+
}
|
|
12318
|
+
};
|
|
12142
12319
|
var createServer = () => {
|
|
12143
12320
|
const app = new Hono2();
|
|
12144
12321
|
app.use("*", cors());
|
|
@@ -12151,141 +12328,18 @@ var createServer = () => {
|
|
|
12151
12328
|
|
|
12152
12329
|
${content}`;
|
|
12153
12330
|
return streamSSE(context, async (stream2) => {
|
|
12154
|
-
const
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
"
|
|
12159
|
-
];
|
|
12160
|
-
if (options?.model) {
|
|
12161
|
-
cursorAgentArgs.push("--model", options.model);
|
|
12162
|
-
}
|
|
12163
|
-
const workspacePath = options?.workspace ?? process.env.REACT_GRAB_CWD ?? process.cwd();
|
|
12164
|
-
if (isFollowUp && cursorChatId) {
|
|
12165
|
-
cursorAgentArgs.push("--resume", cursorChatId);
|
|
12166
|
-
}
|
|
12167
|
-
let cursorProcess;
|
|
12168
|
-
let stderrBuffer = "";
|
|
12169
|
-
try {
|
|
12170
|
-
await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
|
|
12171
|
-
cursorProcess = execa("cursor-agent", cursorAgentArgs, {
|
|
12172
|
-
stdin: "pipe",
|
|
12173
|
-
stdout: "pipe",
|
|
12174
|
-
stderr: "pipe",
|
|
12175
|
-
env: { ...process.env },
|
|
12176
|
-
cwd: workspacePath
|
|
12177
|
-
});
|
|
12178
|
-
if (sessionId) {
|
|
12179
|
-
activeProcesses.set(sessionId, cursorProcess);
|
|
12180
|
-
}
|
|
12181
|
-
if (cursorProcess.stderr) {
|
|
12182
|
-
cursorProcess.stderr.on("data", (chunk) => {
|
|
12183
|
-
stderrBuffer += chunk.toString();
|
|
12184
|
-
});
|
|
12185
|
-
}
|
|
12186
|
-
let buffer = "";
|
|
12187
|
-
let capturedCursorChatId;
|
|
12188
|
-
const processLine = async (line) => {
|
|
12189
|
-
const event = parseStreamLine(line);
|
|
12190
|
-
if (!event) return;
|
|
12191
|
-
if (!capturedCursorChatId && event.session_id) {
|
|
12192
|
-
capturedCursorChatId = event.session_id;
|
|
12193
|
-
}
|
|
12194
|
-
switch (event.type) {
|
|
12195
|
-
case "assistant": {
|
|
12196
|
-
const textContent = extractTextFromMessage(event.message);
|
|
12197
|
-
if (textContent) {
|
|
12198
|
-
await stream2.writeSSE({ data: textContent, event: "status" });
|
|
12199
|
-
}
|
|
12200
|
-
break;
|
|
12201
|
-
}
|
|
12202
|
-
case "result":
|
|
12203
|
-
if (event.subtype === "success") {
|
|
12204
|
-
await stream2.writeSSE({
|
|
12205
|
-
data: COMPLETED_STATUS,
|
|
12206
|
-
event: "status"
|
|
12207
|
-
});
|
|
12208
|
-
} else if (event.subtype === "error" || event.is_error) {
|
|
12209
|
-
await stream2.writeSSE({
|
|
12210
|
-
data: `Error: ${event.result || "Unknown error"}`,
|
|
12211
|
-
event: "error"
|
|
12212
|
-
});
|
|
12213
|
-
} else {
|
|
12214
|
-
await stream2.writeSSE({
|
|
12215
|
-
data: "Task finished",
|
|
12216
|
-
event: "status"
|
|
12217
|
-
});
|
|
12218
|
-
}
|
|
12219
|
-
break;
|
|
12220
|
-
}
|
|
12221
|
-
};
|
|
12222
|
-
if (cursorProcess.stdout) {
|
|
12223
|
-
cursorProcess.stdout.on("data", async (chunk) => {
|
|
12224
|
-
buffer += chunk.toString();
|
|
12225
|
-
let newlineIndex;
|
|
12226
|
-
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
12227
|
-
const line = buffer.slice(0, newlineIndex);
|
|
12228
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
12229
|
-
await processLine(line);
|
|
12230
|
-
}
|
|
12231
|
-
});
|
|
12232
|
-
}
|
|
12233
|
-
if (cursorProcess.stdin) {
|
|
12234
|
-
cursorProcess.stdin.write(userPrompt);
|
|
12235
|
-
cursorProcess.stdin.end();
|
|
12236
|
-
}
|
|
12237
|
-
if (cursorProcess) {
|
|
12238
|
-
const childProcess4 = cursorProcess;
|
|
12239
|
-
await new Promise((resolve, reject) => {
|
|
12240
|
-
childProcess4.on("close", (code) => {
|
|
12241
|
-
if (sessionId) {
|
|
12242
|
-
activeProcesses.delete(sessionId);
|
|
12243
|
-
}
|
|
12244
|
-
if (code === 0 || childProcess4.killed) {
|
|
12245
|
-
resolve();
|
|
12246
|
-
} else {
|
|
12247
|
-
reject(new Error(`cursor-agent exited with code ${code}`));
|
|
12248
|
-
}
|
|
12249
|
-
});
|
|
12250
|
-
childProcess4.on("error", (error) => {
|
|
12251
|
-
if (sessionId) {
|
|
12252
|
-
activeProcesses.delete(sessionId);
|
|
12253
|
-
}
|
|
12254
|
-
reject(error);
|
|
12255
|
-
});
|
|
12256
|
-
});
|
|
12257
|
-
}
|
|
12258
|
-
if (buffer.trim()) {
|
|
12259
|
-
await processLine(buffer);
|
|
12260
|
-
}
|
|
12261
|
-
if (sessionId && capturedCursorChatId) {
|
|
12262
|
-
cursorSessionMap.set(sessionId, capturedCursorChatId);
|
|
12263
|
-
}
|
|
12264
|
-
if (capturedCursorChatId) {
|
|
12265
|
-
lastCursorChatId = capturedCursorChatId;
|
|
12266
|
-
}
|
|
12267
|
-
await stream2.writeSSE({ data: "", event: "done" });
|
|
12268
|
-
} catch (error) {
|
|
12269
|
-
const isNotInstalled = error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
12270
|
-
if (isNotInstalled) {
|
|
12331
|
+
for await (const message of runAgent(userPrompt, {
|
|
12332
|
+
...options,
|
|
12333
|
+
sessionId
|
|
12334
|
+
})) {
|
|
12335
|
+
if (message.type === "error") {
|
|
12271
12336
|
await stream2.writeSSE({
|
|
12272
|
-
data: `Error:
|
|
12273
|
-
|
|
12274
|
-
Installation: https://cursor.com/docs/cli/overview`,
|
|
12337
|
+
data: `Error: ${message.content}`,
|
|
12275
12338
|
event: "error"
|
|
12276
12339
|
});
|
|
12277
|
-
|
|
12340
|
+
} else {
|
|
12341
|
+
await stream2.writeSSE({ data: message.content, event: message.type });
|
|
12278
12342
|
}
|
|
12279
|
-
const errorMessage = error instanceof Error ? formatSpawnError(error, "cursor-agent") : "Unknown error";
|
|
12280
|
-
const stderrContent = stderrBuffer.trim();
|
|
12281
|
-
const fullError = stderrContent ? `${errorMessage}
|
|
12282
|
-
|
|
12283
|
-
stderr:
|
|
12284
|
-
${stderrContent}` : errorMessage;
|
|
12285
|
-
await stream2.writeSSE({
|
|
12286
|
-
data: `Error: ${fullError}`,
|
|
12287
|
-
event: "error"
|
|
12288
|
-
});
|
|
12289
12343
|
}
|
|
12290
12344
|
});
|
|
12291
12345
|
});
|
|
@@ -12350,4 +12404,4 @@ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
|
12350
12404
|
startServer(DEFAULT_PORT).catch(console.error);
|
|
12351
12405
|
}
|
|
12352
12406
|
|
|
12353
|
-
export { createServer, startServer };
|
|
12407
|
+
export { createServer, runAgent, startServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-grab/cursor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.89",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"react-grab-cursor": "./dist/cli.cjs"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^22.10.7",
|
|
28
28
|
"tsup": "^8.4.0",
|
|
29
|
-
"@react-grab/utils": "0.0.
|
|
29
|
+
"@react-grab/utils": "0.0.89"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@hono/node-server": "^1.19.6",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"fkill": "^9.0.0",
|
|
35
35
|
"hono": "^4.0.0",
|
|
36
36
|
"picocolors": "^1.1.1",
|
|
37
|
-
"react-grab": "0.0.
|
|
37
|
+
"react-grab": "0.0.89"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"dev": "tsup --watch",
|