@react-grab/cursor 0.0.84 → 0.0.86

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.84";
7141
+ var VERSION = "0.0.86";
7142
7142
  var serverPath = path2.join(__dirname, "server.cjs");
7143
7143
  execa(process.execPath, [serverPath], {
7144
7144
  detached: true,
@@ -7146,6 +7146,6 @@ execa(process.execPath, [serverPath], {
7146
7146
  cleanup: false
7147
7147
  }).unref();
7148
7148
  console.log(
7149
- `${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Cursor)")}`
7149
+ `${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Cursor)")}`
7150
7150
  );
7151
7151
  console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${DEFAULT_PORT}`)}`);
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.84";
7133
+ var VERSION = "0.0.86";
7134
7134
  var serverPath = join(__dirname, "server.cjs");
7135
7135
  execa(process.execPath, [serverPath], {
7136
7136
  detached: true,
@@ -7138,6 +7138,6 @@ execa(process.execPath, [serverPath], {
7138
7138
  cleanup: false
7139
7139
  }).unref();
7140
7140
  console.log(
7141
- `${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Cursor)")}`
7141
+ `${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Cursor)")}`
7142
7142
  );
7143
7143
  console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${DEFAULT_PORT}`)}`);
package/dist/client.cjs CHANGED
@@ -55,6 +55,7 @@ async function* streamSSE(stream, signal) {
55
55
 
56
56
  // src/constants.ts
57
57
  var DEFAULT_PORT = 5567;
58
+ var COMPLETED_STATUS = "Completed successfully";
58
59
 
59
60
  // src/client.ts
60
61
  var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
@@ -86,6 +87,7 @@ async function* streamFromServer(serverUrl, context, signal) {
86
87
  const iterator = streamSSE(response.body, signal)[Symbol.asyncIterator]();
87
88
  let done = false;
88
89
  let pendingNext = iterator.next();
90
+ let lastStatus = null;
89
91
  while (!done) {
90
92
  const result = await Promise.race([
91
93
  pendingNext.then((iteratorResult) => ({
@@ -96,23 +98,22 @@ async function* streamFromServer(serverUrl, context, signal) {
96
98
  (resolve) => setTimeout(() => resolve({ type: "timeout" }), 100)
97
99
  )
98
100
  ]);
99
- if (result.type === "timeout") {
100
- const elapsedSeconds = (Date.now() - startTime) / 1e3;
101
- yield `Working\u2026 ${elapsedSeconds.toFixed(1)}s`;
102
- } else {
101
+ const elapsedSeconds = (Date.now() - startTime) / 1e3;
102
+ if (result.type === "status") {
103
103
  const iteratorResult = result.iteratorResult;
104
104
  done = iteratorResult.done ?? false;
105
105
  if (!done && iteratorResult.value) {
106
- const status = iteratorResult.value;
107
- if (status === "Completed successfully") {
108
- const totalSeconds = ((Date.now() - startTime) / 1e3).toFixed(1);
109
- yield `Completed in ${totalSeconds}s`;
110
- } else {
111
- yield status;
112
- }
106
+ lastStatus = iteratorResult.value;
113
107
  pendingNext = iterator.next();
114
108
  }
115
109
  }
110
+ if (lastStatus === COMPLETED_STATUS) {
111
+ yield `Completed in ${elapsedSeconds.toFixed(1)}s`;
112
+ } else if (lastStatus) {
113
+ yield `${lastStatus} ${elapsedSeconds.toFixed(1)}s`;
114
+ } else {
115
+ yield `Working\u2026 ${elapsedSeconds.toFixed(1)}s`;
116
+ }
116
117
  }
117
118
  } finally {
118
119
  signal.removeEventListener("abort", handleAbort);
@@ -173,6 +174,12 @@ var createCursorAgentProvider = (providerOptions = {}) => {
173
174
  await fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" });
174
175
  } catch {
175
176
  }
177
+ },
178
+ undo: async () => {
179
+ try {
180
+ await fetch(`${serverUrl}/undo`, { method: "POST" });
181
+ } catch {
182
+ }
176
183
  }
177
184
  };
178
185
  };
package/dist/client.d.cts CHANGED
@@ -17,6 +17,7 @@ declare const createCursorAgentProvider: (providerOptions?: CursorAgentProviderO
17
17
  supportsFollowUp: boolean;
18
18
  checkConnection: () => Promise<boolean>;
19
19
  abort: (sessionId: string) => Promise<void>;
20
+ undo: () => Promise<void>;
20
21
  };
21
22
  declare global {
22
23
  interface Window {
package/dist/client.d.ts CHANGED
@@ -17,6 +17,7 @@ declare const createCursorAgentProvider: (providerOptions?: CursorAgentProviderO
17
17
  supportsFollowUp: boolean;
18
18
  checkConnection: () => Promise<boolean>;
19
19
  abort: (sessionId: string) => Promise<void>;
20
+ undo: () => Promise<void>;
20
21
  };
21
22
  declare global {
22
23
  interface Window {
@@ -1,5 +1,5 @@
1
- var ReactGrabCursor=(function(exports){'use strict';var f=5e3,m="react-grab:agent-sessions",y=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*g(c,t){let r=c.getReader(),o=new TextDecoder,n="",e=false,i=()=>{e=true,r.cancel().catch(()=>{});};t.addEventListener("abort",i);try{if(t.aborted)throw new DOMException("Aborted","AbortError");for(;;){let a=await r.read();if(e||t.aborted)throw new DOMException("Aborted","AbortError");let{done:l,value:p}=a;p&&(n+=o.decode(p,{stream:!0}));let d;for(;(d=n.indexOf(`
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(`
3
3
 
4
- `))!==-1;){let{eventType:s,data:u}=y(n.slice(0,d));if(n=n.slice(d+2),s==="done")return;if(s==="error")throw new Error(u||"Agent error");u&&(yield u);}if(l)break}}finally{t.removeEventListener("abort",i);try{r.releaseLock();}catch{}}}var C=`http://localhost:${5567}`;async function*A(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 i=await fetch(`${c}/agent`,{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 a=g(i.body,r)[Symbol.asyncIterator](),l=!1,p=a.next();for(;!l;){let d=await Promise.race([p.then(s=>({type:"status",iteratorResult:s})),new Promise(s=>setTimeout(()=>s({type:"timeout"}),100))]);if(d.type==="timeout")yield `Working\u2026 ${((Date.now()-o)/1e3).toFixed(1)}s`;else {let s=d.iteratorResult;if(l=s.done??!1,!l&&s.value){let u=s.value;u==="Completed successfully"?yield `Completed in ${((Date.now()-o)/1e3).toFixed(1)}s`:yield u,p=a.next();}}}}finally{r.removeEventListener("abort",e);}}var E=(c={})=>{let{serverUrl:t=C,getOptions:r}=c,o=null,n=e=>({...r?.()??{},...e??{}});return {send:async function*(e,i){let a={...e,options:n(e.options)};yield*A(t,a,i);},resume:async function*(e,i,a){let l=a.getItem(m);if(!l)throw new Error("No sessions to resume");let d=JSON.parse(l)[e];if(!d)throw new Error(`Session ${e} not found`);let s=d.context,u={...s,options:n(s.options)};yield "Resuming...",yield*A(t,u,i);},supportsResume:true,supportsFollowUp:true,checkConnection:async()=>{let e=Date.now();if(o&&e-o.timestamp<f)return o.result;try{let a=(await fetch(`${t}/health`,{method:"GET"})).ok;return o={result:a,timestamp:e},a}catch{return o={result:false,timestamp:e},false}},abort:async e=>{try{await fetch(`${t}/abort/${e}`,{method:"POST"});}catch{}}}},b=async()=>{if(typeof window>"u")return;let c=E(),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);};b();
5
- exports.attachAgent=b;exports.createCursorAgentProvider=E;return exports;})({});
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;})({});
package/dist/client.js CHANGED
@@ -53,6 +53,7 @@ async function* streamSSE(stream, signal) {
53
53
 
54
54
  // src/constants.ts
55
55
  var DEFAULT_PORT = 5567;
56
+ var COMPLETED_STATUS = "Completed successfully";
56
57
 
57
58
  // src/client.ts
58
59
  var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
@@ -84,6 +85,7 @@ async function* streamFromServer(serverUrl, context, signal) {
84
85
  const iterator = streamSSE(response.body, signal)[Symbol.asyncIterator]();
85
86
  let done = false;
86
87
  let pendingNext = iterator.next();
88
+ let lastStatus = null;
87
89
  while (!done) {
88
90
  const result = await Promise.race([
89
91
  pendingNext.then((iteratorResult) => ({
@@ -94,23 +96,22 @@ async function* streamFromServer(serverUrl, context, signal) {
94
96
  (resolve) => setTimeout(() => resolve({ type: "timeout" }), 100)
95
97
  )
96
98
  ]);
97
- if (result.type === "timeout") {
98
- const elapsedSeconds = (Date.now() - startTime) / 1e3;
99
- yield `Working\u2026 ${elapsedSeconds.toFixed(1)}s`;
100
- } else {
99
+ const elapsedSeconds = (Date.now() - startTime) / 1e3;
100
+ if (result.type === "status") {
101
101
  const iteratorResult = result.iteratorResult;
102
102
  done = iteratorResult.done ?? false;
103
103
  if (!done && iteratorResult.value) {
104
- const status = iteratorResult.value;
105
- if (status === "Completed successfully") {
106
- const totalSeconds = ((Date.now() - startTime) / 1e3).toFixed(1);
107
- yield `Completed in ${totalSeconds}s`;
108
- } else {
109
- yield status;
110
- }
104
+ lastStatus = iteratorResult.value;
111
105
  pendingNext = iterator.next();
112
106
  }
113
107
  }
108
+ if (lastStatus === COMPLETED_STATUS) {
109
+ yield `Completed in ${elapsedSeconds.toFixed(1)}s`;
110
+ } else if (lastStatus) {
111
+ yield `${lastStatus} ${elapsedSeconds.toFixed(1)}s`;
112
+ } else {
113
+ yield `Working\u2026 ${elapsedSeconds.toFixed(1)}s`;
114
+ }
114
115
  }
115
116
  } finally {
116
117
  signal.removeEventListener("abort", handleAbort);
@@ -171,6 +172,12 @@ var createCursorAgentProvider = (providerOptions = {}) => {
171
172
  await fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" });
172
173
  } catch {
173
174
  }
175
+ },
176
+ undo: async () => {
177
+ try {
178
+ await fetch(`${serverUrl}/undo`, { method: "POST" });
179
+ } catch {
180
+ }
174
181
  }
175
182
  };
176
183
  };
package/dist/server.cjs CHANGED
@@ -12098,14 +12098,46 @@ var import_picocolors = __toESM(require_picocolors());
12098
12098
 
12099
12099
  // src/constants.ts
12100
12100
  var DEFAULT_PORT = 5567;
12101
+ var COMPLETED_STATUS = "Completed successfully";
12101
12102
 
12102
12103
  // ../utils/dist/server.js
12103
12104
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
12105
+ var COMMAND_INSTALL_MAP = {
12106
+ "cursor-agent": "Install Cursor (https://cursor.com) and ensure 'cursor-agent' is in your PATH.",
12107
+ gemini: "Install the Gemini CLI: npm install -g @anthropic-ai/gemini-cli\nOr see: https://github.com/google-gemini/gemini-cli",
12108
+ claude: "Install Claude Code: npm install -g @anthropic-ai/claude-code\nOr see: https://github.com/anthropics/claude-code"
12109
+ };
12110
+ var formatSpawnError = (error, commandName) => {
12111
+ const spawnError = error;
12112
+ const isNotFound = spawnError.code === "ENOENT" || spawnError.message && spawnError.message.includes("ENOENT");
12113
+ if (isNotFound) {
12114
+ const installInfo = COMMAND_INSTALL_MAP[commandName];
12115
+ const baseMessage = `Command '${commandName}' not found.`;
12116
+ {
12117
+ return `${baseMessage}
12118
+
12119
+ ${installInfo}`;
12120
+ }
12121
+ }
12122
+ const isPermissionDenied = spawnError.code === "EACCES" || spawnError.message && spawnError.message.includes("EACCES");
12123
+ if (isPermissionDenied) {
12124
+ return `Permission denied when trying to run '${commandName}'.
12125
+
12126
+ Check that the command is executable: chmod +x $(which ${commandName})`;
12127
+ }
12128
+ return error.message;
12129
+ };
12104
12130
 
12105
12131
  // src/server.ts
12106
- var VERSION = "0.0.84";
12132
+ var VERSION = "0.0.86";
12133
+ try {
12134
+ fetch(`https://www.react-grab.com/api/version?source=cursor&t=${Date.now()}`).catch(() => {
12135
+ });
12136
+ } catch {
12137
+ }
12107
12138
  var cursorSessionMap = /* @__PURE__ */ new Map();
12108
12139
  var activeProcesses = /* @__PURE__ */ new Map();
12140
+ var lastCursorChatId;
12109
12141
  var parseStreamLine = (line) => {
12110
12142
  const trimmed = line.trim();
12111
12143
  if (!trimmed) return null;
@@ -12140,23 +12172,20 @@ ${content}`;
12140
12172
  if (options?.model) {
12141
12173
  cursorAgentArgs.push("--model", options.model);
12142
12174
  }
12143
- if (options?.workspace) {
12144
- cursorAgentArgs.push("--workspace", options.workspace);
12145
- } else {
12146
- cursorAgentArgs.push("--workspace", process.cwd());
12147
- }
12175
+ const workspacePath = options?.workspace ?? process.env.REACT_GRAB_CWD ?? process.cwd();
12148
12176
  if (isFollowUp && cursorChatId) {
12149
12177
  cursorAgentArgs.push("--resume", cursorChatId);
12150
12178
  }
12151
12179
  let cursorProcess;
12152
12180
  let stderrBuffer = "";
12153
12181
  try {
12154
- await stream2.writeSSE({ data: "Thinking...", event: "status" });
12182
+ await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
12155
12183
  cursorProcess = execa("cursor-agent", cursorAgentArgs, {
12156
12184
  stdin: "pipe",
12157
12185
  stdout: "pipe",
12158
12186
  stderr: "pipe",
12159
- env: { ...process.env }
12187
+ env: { ...process.env },
12188
+ cwd: workspacePath
12160
12189
  });
12161
12190
  if (sessionId) {
12162
12191
  activeProcesses.set(sessionId, cursorProcess);
@@ -12185,7 +12214,7 @@ ${content}`;
12185
12214
  case "result":
12186
12215
  if (event.subtype === "success") {
12187
12216
  await stream2.writeSSE({
12188
- data: "Completed successfully",
12217
+ data: COMPLETED_STATUS,
12189
12218
  event: "status"
12190
12219
  });
12191
12220
  } else if (event.subtype === "error" || event.is_error) {
@@ -12244,9 +12273,22 @@ ${content}`;
12244
12273
  if (sessionId && capturedCursorChatId) {
12245
12274
  cursorSessionMap.set(sessionId, capturedCursorChatId);
12246
12275
  }
12276
+ if (capturedCursorChatId) {
12277
+ lastCursorChatId = capturedCursorChatId;
12278
+ }
12247
12279
  await stream2.writeSSE({ data: "", event: "done" });
12248
12280
  } catch (error) {
12249
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
12281
+ const isNotInstalled = error instanceof Error && "code" in error && error.code === "ENOENT";
12282
+ if (isNotInstalled) {
12283
+ 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`,
12287
+ event: "error"
12288
+ });
12289
+ return;
12290
+ }
12291
+ const errorMessage = error instanceof Error ? formatSpawnError(error, "cursor-agent") : "Unknown error";
12250
12292
  const stderrContent = stderrBuffer.trim();
12251
12293
  const fullError = stderrContent ? `${errorMessage}
12252
12294
 
@@ -12268,6 +12310,38 @@ ${stderrContent}` : errorMessage;
12268
12310
  }
12269
12311
  return context.json({ status: "ok" });
12270
12312
  });
12313
+ app.post("/undo", async (context) => {
12314
+ if (!lastCursorChatId) {
12315
+ return context.json({ status: "error", message: "No session to undo" });
12316
+ }
12317
+ try {
12318
+ const cursorAgentArgs = [
12319
+ "--print",
12320
+ "--output-format",
12321
+ "stream-json",
12322
+ "--force",
12323
+ "--resume",
12324
+ lastCursorChatId
12325
+ ];
12326
+ const workspacePath = process.env.REACT_GRAB_CWD ?? process.cwd();
12327
+ const cursorProcess = execa("cursor-agent", cursorAgentArgs, {
12328
+ stdin: "pipe",
12329
+ stdout: "pipe",
12330
+ stderr: "pipe",
12331
+ env: { ...process.env },
12332
+ cwd: workspacePath
12333
+ });
12334
+ if (cursorProcess.stdin) {
12335
+ cursorProcess.stdin.write("undo");
12336
+ cursorProcess.stdin.end();
12337
+ }
12338
+ await cursorProcess;
12339
+ return context.json({ status: "ok" });
12340
+ } catch (error) {
12341
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
12342
+ return context.json({ status: "error", message: errorMessage });
12343
+ }
12344
+ });
12271
12345
  app.get("/health", (context) => {
12272
12346
  return context.json({ status: "ok", provider: "cursor" });
12273
12347
  });
@@ -12280,7 +12354,7 @@ var startServer = async (port = DEFAULT_PORT) => {
12280
12354
  const app = createServer();
12281
12355
  serve({ fetch: app.fetch, port });
12282
12356
  console.log(
12283
- `${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Cursor)")}`
12357
+ `${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Cursor)")}`
12284
12358
  );
12285
12359
  console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${port}`)}`);
12286
12360
  };
package/dist/server.js CHANGED
@@ -12086,14 +12086,46 @@ var import_picocolors = __toESM(require_picocolors());
12086
12086
 
12087
12087
  // src/constants.ts
12088
12088
  var DEFAULT_PORT = 5567;
12089
+ var COMPLETED_STATUS = "Completed successfully";
12089
12090
 
12090
12091
  // ../utils/dist/server.js
12091
12092
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
12093
+ var COMMAND_INSTALL_MAP = {
12094
+ "cursor-agent": "Install Cursor (https://cursor.com) and ensure 'cursor-agent' is in your PATH.",
12095
+ gemini: "Install the Gemini CLI: npm install -g @anthropic-ai/gemini-cli\nOr see: https://github.com/google-gemini/gemini-cli",
12096
+ claude: "Install Claude Code: npm install -g @anthropic-ai/claude-code\nOr see: https://github.com/anthropics/claude-code"
12097
+ };
12098
+ var formatSpawnError = (error, commandName) => {
12099
+ const spawnError = error;
12100
+ const isNotFound = spawnError.code === "ENOENT" || spawnError.message && spawnError.message.includes("ENOENT");
12101
+ if (isNotFound) {
12102
+ const installInfo = COMMAND_INSTALL_MAP[commandName];
12103
+ const baseMessage = `Command '${commandName}' not found.`;
12104
+ {
12105
+ return `${baseMessage}
12106
+
12107
+ ${installInfo}`;
12108
+ }
12109
+ }
12110
+ const isPermissionDenied = spawnError.code === "EACCES" || spawnError.message && spawnError.message.includes("EACCES");
12111
+ if (isPermissionDenied) {
12112
+ return `Permission denied when trying to run '${commandName}'.
12113
+
12114
+ Check that the command is executable: chmod +x $(which ${commandName})`;
12115
+ }
12116
+ return error.message;
12117
+ };
12092
12118
 
12093
12119
  // src/server.ts
12094
- var VERSION = "0.0.84";
12120
+ var VERSION = "0.0.86";
12121
+ try {
12122
+ fetch(`https://www.react-grab.com/api/version?source=cursor&t=${Date.now()}`).catch(() => {
12123
+ });
12124
+ } catch {
12125
+ }
12095
12126
  var cursorSessionMap = /* @__PURE__ */ new Map();
12096
12127
  var activeProcesses = /* @__PURE__ */ new Map();
12128
+ var lastCursorChatId;
12097
12129
  var parseStreamLine = (line) => {
12098
12130
  const trimmed = line.trim();
12099
12131
  if (!trimmed) return null;
@@ -12128,23 +12160,20 @@ ${content}`;
12128
12160
  if (options?.model) {
12129
12161
  cursorAgentArgs.push("--model", options.model);
12130
12162
  }
12131
- if (options?.workspace) {
12132
- cursorAgentArgs.push("--workspace", options.workspace);
12133
- } else {
12134
- cursorAgentArgs.push("--workspace", process.cwd());
12135
- }
12163
+ const workspacePath = options?.workspace ?? process.env.REACT_GRAB_CWD ?? process.cwd();
12136
12164
  if (isFollowUp && cursorChatId) {
12137
12165
  cursorAgentArgs.push("--resume", cursorChatId);
12138
12166
  }
12139
12167
  let cursorProcess;
12140
12168
  let stderrBuffer = "";
12141
12169
  try {
12142
- await stream2.writeSSE({ data: "Thinking...", event: "status" });
12170
+ await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
12143
12171
  cursorProcess = execa("cursor-agent", cursorAgentArgs, {
12144
12172
  stdin: "pipe",
12145
12173
  stdout: "pipe",
12146
12174
  stderr: "pipe",
12147
- env: { ...process.env }
12175
+ env: { ...process.env },
12176
+ cwd: workspacePath
12148
12177
  });
12149
12178
  if (sessionId) {
12150
12179
  activeProcesses.set(sessionId, cursorProcess);
@@ -12173,7 +12202,7 @@ ${content}`;
12173
12202
  case "result":
12174
12203
  if (event.subtype === "success") {
12175
12204
  await stream2.writeSSE({
12176
- data: "Completed successfully",
12205
+ data: COMPLETED_STATUS,
12177
12206
  event: "status"
12178
12207
  });
12179
12208
  } else if (event.subtype === "error" || event.is_error) {
@@ -12232,9 +12261,22 @@ ${content}`;
12232
12261
  if (sessionId && capturedCursorChatId) {
12233
12262
  cursorSessionMap.set(sessionId, capturedCursorChatId);
12234
12263
  }
12264
+ if (capturedCursorChatId) {
12265
+ lastCursorChatId = capturedCursorChatId;
12266
+ }
12235
12267
  await stream2.writeSSE({ data: "", event: "done" });
12236
12268
  } catch (error) {
12237
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
12269
+ const isNotInstalled = error instanceof Error && "code" in error && error.code === "ENOENT";
12270
+ if (isNotInstalled) {
12271
+ 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`,
12275
+ event: "error"
12276
+ });
12277
+ return;
12278
+ }
12279
+ const errorMessage = error instanceof Error ? formatSpawnError(error, "cursor-agent") : "Unknown error";
12238
12280
  const stderrContent = stderrBuffer.trim();
12239
12281
  const fullError = stderrContent ? `${errorMessage}
12240
12282
 
@@ -12256,6 +12298,38 @@ ${stderrContent}` : errorMessage;
12256
12298
  }
12257
12299
  return context.json({ status: "ok" });
12258
12300
  });
12301
+ app.post("/undo", async (context) => {
12302
+ if (!lastCursorChatId) {
12303
+ return context.json({ status: "error", message: "No session to undo" });
12304
+ }
12305
+ try {
12306
+ const cursorAgentArgs = [
12307
+ "--print",
12308
+ "--output-format",
12309
+ "stream-json",
12310
+ "--force",
12311
+ "--resume",
12312
+ lastCursorChatId
12313
+ ];
12314
+ const workspacePath = process.env.REACT_GRAB_CWD ?? process.cwd();
12315
+ const cursorProcess = execa("cursor-agent", cursorAgentArgs, {
12316
+ stdin: "pipe",
12317
+ stdout: "pipe",
12318
+ stderr: "pipe",
12319
+ env: { ...process.env },
12320
+ cwd: workspacePath
12321
+ });
12322
+ if (cursorProcess.stdin) {
12323
+ cursorProcess.stdin.write("undo");
12324
+ cursorProcess.stdin.end();
12325
+ }
12326
+ await cursorProcess;
12327
+ return context.json({ status: "ok" });
12328
+ } catch (error) {
12329
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
12330
+ return context.json({ status: "error", message: errorMessage });
12331
+ }
12332
+ });
12259
12333
  app.get("/health", (context) => {
12260
12334
  return context.json({ status: "ok", provider: "cursor" });
12261
12335
  });
@@ -12268,7 +12342,7 @@ var startServer = async (port = DEFAULT_PORT) => {
12268
12342
  const app = createServer();
12269
12343
  serve({ fetch: app.fetch, port });
12270
12344
  console.log(
12271
- `${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Cursor)")}`
12345
+ `${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Cursor)")}`
12272
12346
  );
12273
12347
  console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${port}`)}`);
12274
12348
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-grab/cursor",
3
- "version": "0.0.84",
3
+ "version": "0.0.86",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "react-grab-cursor": "./dist/cli.cjs"
@@ -24,8 +24,9 @@
24
24
  "dist"
25
25
  ],
26
26
  "devDependencies": {
27
+ "@types/node": "^22.10.7",
27
28
  "tsup": "^8.4.0",
28
- "@react-grab/utils": "0.0.84"
29
+ "@react-grab/utils": "0.0.86"
29
30
  },
30
31
  "dependencies": {
31
32
  "@hono/node-server": "^1.19.6",
@@ -33,7 +34,7 @@
33
34
  "fkill": "^9.0.0",
34
35
  "hono": "^4.0.0",
35
36
  "picocolors": "^1.1.1",
36
- "react-grab": "0.0.84"
37
+ "react-grab": "0.0.86"
37
38
  },
38
39
  "scripts": {
39
40
  "dev": "tsup --watch",