@react-grab/cursor 0.0.87 → 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 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.87";
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.87";
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* streamSSE(stream, signal) {
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
- // src/constants.ts
57
- var DEFAULT_PORT = 5567;
58
- var COMPLETED_STATUS = "Completed successfully";
59
-
60
- // src/client.ts
61
- var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
62
- async function* streamFromServer(serverUrl, context, signal) {
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
- fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" }).catch(
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(`${serverUrl}/agent`, {
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 done = false;
114
+ let isDone = false;
89
115
  let pendingNext = iterator.next();
90
116
  let lastStatus = null;
91
- while (!done) {
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" }), 100)
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
- done = iteratorResult.done ?? false;
105
- if (!done && iteratorResult.value) {
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 === COMPLETED_STATUS) {
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* streamFromServer(serverUrl, mergedContext, signal);
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 savedSessions = storage.getItem(STORAGE_KEY);
139
- if (!savedSessions) {
140
- throw new Error("No sessions to resume");
141
- }
142
- const sessionsObject = JSON.parse(savedSessions);
143
- const session = sessionsObject[sessionId];
144
- if (!session) {
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* streamFromServer(serverUrl, mergedContext, signal);
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: async () => {
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 = (api2) => {
190
- api2.setAgent({ provider, storage: sessionStorage });
232
+ const attach = (api) => {
233
+ api.setAgent({ provider, storage: sessionStorage });
191
234
  };
192
- const api = window.__REACT_GRAB__;
193
- if (api) {
194
- attach(api);
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
- const customEvent = event;
201
- attach(customEvent.detail);
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
  };
@@ -1,5 +1,5 @@
1
- var ReactGrabCursor=(function(exports){'use strict';var g=5e3,m="react-grab:agent-sessions",w=c=>{let t="",r="";for(let o of c.split(`
2
- `))o.startsWith("event:")?t=o.slice(6).trim():o.startsWith("data:")&&(r=o.slice(5).trim());return {eventType:t,data:r}};async function*A(c,t){let r=c.getReader(),o=new TextDecoder,n="",e=false,s=()=>{e=true,r.cancel().catch(()=>{});};t.addEventListener("abort",s);try{if(t.aborted)throw new DOMException("Aborted","AbortError");for(;;){let i=await r.read();if(e||t.aborted)throw new DOMException("Aborted","AbortError");let{done:d,value:f}=i;f&&(n+=o.decode(f,{stream:!0}));let a;for(;(a=n.indexOf(`
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:u,data:l}=w(n.slice(0,a));if(n=n.slice(a+2),u==="done")return;if(u==="error")throw new Error(l||"Agent error");l&&(yield l);}if(d)break}}finally{t.removeEventListener("abort",s);try{r.releaseLock();}catch{}}}var y="Completed successfully";var h=`http://localhost:${5567}`;async function*E(c,t,r){let o=Date.now(),n=t.sessionId,e=()=>{n&&fetch(`${c}/abort/${n}`,{method:"POST"}).catch(()=>{});};r.addEventListener("abort",e);try{let s=await fetch(`${c}/agent`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),signal:r});if(!s.ok)throw new Error(`Server error: ${s.status}`);if(!s.body)throw new Error("No response body");let i=A(s.body,r)[Symbol.asyncIterator](),d=!1,f=i.next(),a=null;for(;!d;){let u=await Promise.race([f.then(p=>({type:"status",iteratorResult:p})),new Promise(p=>setTimeout(()=>p({type:"timeout"}),100))]),l=(Date.now()-o)/1e3;if(u.type==="status"){let p=u.iteratorResult;d=p.done??!1,!d&&p.value&&(a=p.value,f=i.next());}a===y?yield `Completed in ${l.toFixed(1)}s`:a?yield `${a} ${l.toFixed(1)}s`:yield `Working\u2026 ${l.toFixed(1)}s`;}}finally{r.removeEventListener("abort",e);}}var b=(c={})=>{let{serverUrl:t=h,getOptions:r}=c,o=null,n=e=>({...r?.()??{},...e??{}});return {send:async function*(e,s){let i={...e,options:n(e.options)};yield*E(t,i,s);},resume:async function*(e,s,i){let d=i.getItem(m);if(!d)throw new Error("No sessions to resume");let a=JSON.parse(d)[e];if(!a)throw new Error(`Session ${e} not found`);let u=a.context,l={...u,options:n(u.options)};yield "Resuming...",yield*E(t,l,s);},supportsResume:true,supportsFollowUp:true,checkConnection:async()=>{let e=Date.now();if(o&&e-o.timestamp<g)return o.result;try{let i=(await fetch(`${t}/health`,{method:"GET"})).ok;return o={result:i,timestamp:e},i}catch{return o={result:false,timestamp:e},false}},abort:async e=>{try{await fetch(`${t}/abort/${e}`,{method:"POST"});}catch{}},undo:async()=>{try{await fetch(`${t}/undo`,{method:"POST"});}catch{}}}},S=async()=>{if(typeof window>"u")return;let c=b(),t=n=>{n.setAgent({provider:c,storage:sessionStorage});},r=window.__REACT_GRAB__;if(r){t(r);return}window.addEventListener("react-grab:init",n=>{t(n.detail);},{once:true});let o=window.__REACT_GRAB__;o&&t(o);};S();
5
- exports.attachAgent=S;exports.createCursorAgentProvider=b;return exports;})({});
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* streamSSE(stream, signal) {
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
- // src/constants.ts
55
- var DEFAULT_PORT = 5567;
56
- var COMPLETED_STATUS = "Completed successfully";
57
-
58
- // src/client.ts
59
- var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
60
- async function* streamFromServer(serverUrl, context, signal) {
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
- fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" }).catch(
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(`${serverUrl}/agent`, {
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 done = false;
112
+ let isDone = false;
87
113
  let pendingNext = iterator.next();
88
114
  let lastStatus = null;
89
- while (!done) {
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" }), 100)
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
- done = iteratorResult.done ?? false;
103
- if (!done && iteratorResult.value) {
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 === COMPLETED_STATUS) {
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* streamFromServer(serverUrl, mergedContext, signal);
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 savedSessions = storage.getItem(STORAGE_KEY);
137
- if (!savedSessions) {
138
- throw new Error("No sessions to resume");
139
- }
140
- const sessionsObject = JSON.parse(savedSessions);
141
- const session = sessionsObject[sessionId];
142
- if (!session) {
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* streamFromServer(serverUrl, mergedContext, signal);
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: async () => {
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 = (api2) => {
188
- api2.setAgent({ provider, storage: sessionStorage });
230
+ const attach = (api) => {
231
+ api.setAgent({ provider, storage: sessionStorage });
189
232
  };
190
- const api = window.__REACT_GRAB__;
191
- if (api) {
192
- attach(api);
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
- const customEvent = event;
199
- attach(customEvent.detail);
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.87";
12132
+ var VERSION = "0.0.89";
12133
12133
  try {
12134
- fetch(`https://www.react-grab.com/api/version?source=cursor&t=${Date.now()}`).catch(() => {
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 cursorAgentArgs = [
12167
- "--print",
12168
- "--output-format",
12169
- "stream-json",
12170
- "--force"
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: cursor-agent is not installed. Please install the Cursor Agent CLI to use this provider.
12285
-
12286
- Installation: https://cursor.com/docs/cli/overview`,
12349
+ data: `Error: ${message.content}`,
12287
12350
  event: "error"
12288
12351
  });
12289
- return;
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.87";
12120
+ var VERSION = "0.0.89";
12121
12121
  try {
12122
- fetch(`https://www.react-grab.com/api/version?source=cursor&t=${Date.now()}`).catch(() => {
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 cursorAgentArgs = [
12155
- "--print",
12156
- "--output-format",
12157
- "stream-json",
12158
- "--force"
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: cursor-agent is not installed. Please install the Cursor Agent CLI to use this provider.
12273
-
12274
- Installation: https://cursor.com/docs/cli/overview`,
12337
+ data: `Error: ${message.content}`,
12275
12338
  event: "error"
12276
12339
  });
12277
- return;
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.87",
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.87"
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.87"
37
+ "react-grab": "0.0.89"
38
38
  },
39
39
  "scripts": {
40
40
  "dev": "tsup --watch",