@react-grab/cursor 0.0.85 → 0.0.87
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 +2 -2
- package/dist/cli.js +2 -2
- package/dist/client.cjs +18 -11
- package/dist/client.d.cts +1 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.global.js +4 -4
- package/dist/client.js +18 -11
- package/dist/server.cjs +80 -11
- package/dist/server.js +80 -11
- package/package.json +4 -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.87";
|
|
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("\
|
|
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.
|
|
7133
|
+
var VERSION = "0.0.87";
|
|
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("\
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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 {
|
package/dist/client.global.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var ReactGrabCursor=(function(exports){'use strict';var
|
|
2
|
-
`))o.startsWith("event:")?t=o.slice(6).trim():o.startsWith("data:")&&(r=o.slice(5).trim());return {eventType:t,data:r}};async function*
|
|
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:
|
|
5
|
-
exports.attachAgent=
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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,12 +12098,38 @@ 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.
|
|
12132
|
+
var VERSION = "0.0.87";
|
|
12107
12133
|
try {
|
|
12108
12134
|
fetch(`https://www.react-grab.com/api/version?source=cursor&t=${Date.now()}`).catch(() => {
|
|
12109
12135
|
});
|
|
@@ -12111,6 +12137,7 @@ try {
|
|
|
12111
12137
|
}
|
|
12112
12138
|
var cursorSessionMap = /* @__PURE__ */ new Map();
|
|
12113
12139
|
var activeProcesses = /* @__PURE__ */ new Map();
|
|
12140
|
+
var lastCursorChatId;
|
|
12114
12141
|
var parseStreamLine = (line) => {
|
|
12115
12142
|
const trimmed = line.trim();
|
|
12116
12143
|
if (!trimmed) return null;
|
|
@@ -12145,23 +12172,20 @@ ${content}`;
|
|
|
12145
12172
|
if (options?.model) {
|
|
12146
12173
|
cursorAgentArgs.push("--model", options.model);
|
|
12147
12174
|
}
|
|
12148
|
-
|
|
12149
|
-
cursorAgentArgs.push("--workspace", options.workspace);
|
|
12150
|
-
} else {
|
|
12151
|
-
cursorAgentArgs.push("--workspace", process.cwd());
|
|
12152
|
-
}
|
|
12175
|
+
const workspacePath = options?.workspace ?? process.env.REACT_GRAB_CWD ?? process.cwd();
|
|
12153
12176
|
if (isFollowUp && cursorChatId) {
|
|
12154
12177
|
cursorAgentArgs.push("--resume", cursorChatId);
|
|
12155
12178
|
}
|
|
12156
12179
|
let cursorProcess;
|
|
12157
12180
|
let stderrBuffer = "";
|
|
12158
12181
|
try {
|
|
12159
|
-
await stream2.writeSSE({ data: "Thinking
|
|
12182
|
+
await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
|
|
12160
12183
|
cursorProcess = execa("cursor-agent", cursorAgentArgs, {
|
|
12161
12184
|
stdin: "pipe",
|
|
12162
12185
|
stdout: "pipe",
|
|
12163
12186
|
stderr: "pipe",
|
|
12164
|
-
env: { ...process.env }
|
|
12187
|
+
env: { ...process.env },
|
|
12188
|
+
cwd: workspacePath
|
|
12165
12189
|
});
|
|
12166
12190
|
if (sessionId) {
|
|
12167
12191
|
activeProcesses.set(sessionId, cursorProcess);
|
|
@@ -12190,7 +12214,7 @@ ${content}`;
|
|
|
12190
12214
|
case "result":
|
|
12191
12215
|
if (event.subtype === "success") {
|
|
12192
12216
|
await stream2.writeSSE({
|
|
12193
|
-
data:
|
|
12217
|
+
data: COMPLETED_STATUS,
|
|
12194
12218
|
event: "status"
|
|
12195
12219
|
});
|
|
12196
12220
|
} else if (event.subtype === "error" || event.is_error) {
|
|
@@ -12249,9 +12273,22 @@ ${content}`;
|
|
|
12249
12273
|
if (sessionId && capturedCursorChatId) {
|
|
12250
12274
|
cursorSessionMap.set(sessionId, capturedCursorChatId);
|
|
12251
12275
|
}
|
|
12276
|
+
if (capturedCursorChatId) {
|
|
12277
|
+
lastCursorChatId = capturedCursorChatId;
|
|
12278
|
+
}
|
|
12252
12279
|
await stream2.writeSSE({ data: "", event: "done" });
|
|
12253
12280
|
} catch (error) {
|
|
12254
|
-
const
|
|
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";
|
|
12255
12292
|
const stderrContent = stderrBuffer.trim();
|
|
12256
12293
|
const fullError = stderrContent ? `${errorMessage}
|
|
12257
12294
|
|
|
@@ -12273,6 +12310,38 @@ ${stderrContent}` : errorMessage;
|
|
|
12273
12310
|
}
|
|
12274
12311
|
return context.json({ status: "ok" });
|
|
12275
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
|
+
});
|
|
12276
12345
|
app.get("/health", (context) => {
|
|
12277
12346
|
return context.json({ status: "ok", provider: "cursor" });
|
|
12278
12347
|
});
|
|
@@ -12285,7 +12354,7 @@ var startServer = async (port = DEFAULT_PORT) => {
|
|
|
12285
12354
|
const app = createServer();
|
|
12286
12355
|
serve({ fetch: app.fetch, port });
|
|
12287
12356
|
console.log(
|
|
12288
|
-
`${import_picocolors.default.magenta("\
|
|
12357
|
+
`${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Cursor)")}`
|
|
12289
12358
|
);
|
|
12290
12359
|
console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${port}`)}`);
|
|
12291
12360
|
};
|
package/dist/server.js
CHANGED
|
@@ -12086,12 +12086,38 @@ 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.
|
|
12120
|
+
var VERSION = "0.0.87";
|
|
12095
12121
|
try {
|
|
12096
12122
|
fetch(`https://www.react-grab.com/api/version?source=cursor&t=${Date.now()}`).catch(() => {
|
|
12097
12123
|
});
|
|
@@ -12099,6 +12125,7 @@ try {
|
|
|
12099
12125
|
}
|
|
12100
12126
|
var cursorSessionMap = /* @__PURE__ */ new Map();
|
|
12101
12127
|
var activeProcesses = /* @__PURE__ */ new Map();
|
|
12128
|
+
var lastCursorChatId;
|
|
12102
12129
|
var parseStreamLine = (line) => {
|
|
12103
12130
|
const trimmed = line.trim();
|
|
12104
12131
|
if (!trimmed) return null;
|
|
@@ -12133,23 +12160,20 @@ ${content}`;
|
|
|
12133
12160
|
if (options?.model) {
|
|
12134
12161
|
cursorAgentArgs.push("--model", options.model);
|
|
12135
12162
|
}
|
|
12136
|
-
|
|
12137
|
-
cursorAgentArgs.push("--workspace", options.workspace);
|
|
12138
|
-
} else {
|
|
12139
|
-
cursorAgentArgs.push("--workspace", process.cwd());
|
|
12140
|
-
}
|
|
12163
|
+
const workspacePath = options?.workspace ?? process.env.REACT_GRAB_CWD ?? process.cwd();
|
|
12141
12164
|
if (isFollowUp && cursorChatId) {
|
|
12142
12165
|
cursorAgentArgs.push("--resume", cursorChatId);
|
|
12143
12166
|
}
|
|
12144
12167
|
let cursorProcess;
|
|
12145
12168
|
let stderrBuffer = "";
|
|
12146
12169
|
try {
|
|
12147
|
-
await stream2.writeSSE({ data: "Thinking
|
|
12170
|
+
await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
|
|
12148
12171
|
cursorProcess = execa("cursor-agent", cursorAgentArgs, {
|
|
12149
12172
|
stdin: "pipe",
|
|
12150
12173
|
stdout: "pipe",
|
|
12151
12174
|
stderr: "pipe",
|
|
12152
|
-
env: { ...process.env }
|
|
12175
|
+
env: { ...process.env },
|
|
12176
|
+
cwd: workspacePath
|
|
12153
12177
|
});
|
|
12154
12178
|
if (sessionId) {
|
|
12155
12179
|
activeProcesses.set(sessionId, cursorProcess);
|
|
@@ -12178,7 +12202,7 @@ ${content}`;
|
|
|
12178
12202
|
case "result":
|
|
12179
12203
|
if (event.subtype === "success") {
|
|
12180
12204
|
await stream2.writeSSE({
|
|
12181
|
-
data:
|
|
12205
|
+
data: COMPLETED_STATUS,
|
|
12182
12206
|
event: "status"
|
|
12183
12207
|
});
|
|
12184
12208
|
} else if (event.subtype === "error" || event.is_error) {
|
|
@@ -12237,9 +12261,22 @@ ${content}`;
|
|
|
12237
12261
|
if (sessionId && capturedCursorChatId) {
|
|
12238
12262
|
cursorSessionMap.set(sessionId, capturedCursorChatId);
|
|
12239
12263
|
}
|
|
12264
|
+
if (capturedCursorChatId) {
|
|
12265
|
+
lastCursorChatId = capturedCursorChatId;
|
|
12266
|
+
}
|
|
12240
12267
|
await stream2.writeSSE({ data: "", event: "done" });
|
|
12241
12268
|
} catch (error) {
|
|
12242
|
-
const
|
|
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";
|
|
12243
12280
|
const stderrContent = stderrBuffer.trim();
|
|
12244
12281
|
const fullError = stderrContent ? `${errorMessage}
|
|
12245
12282
|
|
|
@@ -12261,6 +12298,38 @@ ${stderrContent}` : errorMessage;
|
|
|
12261
12298
|
}
|
|
12262
12299
|
return context.json({ status: "ok" });
|
|
12263
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
|
+
});
|
|
12264
12333
|
app.get("/health", (context) => {
|
|
12265
12334
|
return context.json({ status: "ok", provider: "cursor" });
|
|
12266
12335
|
});
|
|
@@ -12273,7 +12342,7 @@ var startServer = async (port = DEFAULT_PORT) => {
|
|
|
12273
12342
|
const app = createServer();
|
|
12274
12343
|
serve({ fetch: app.fetch, port });
|
|
12275
12344
|
console.log(
|
|
12276
|
-
`${import_picocolors.default.magenta("\
|
|
12345
|
+
`${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Cursor)")}`
|
|
12277
12346
|
);
|
|
12278
12347
|
console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${port}`)}`);
|
|
12279
12348
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-grab/cursor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.87",
|
|
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.
|
|
29
|
+
"@react-grab/utils": "0.0.87"
|
|
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.
|
|
37
|
+
"react-grab": "0.0.87"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
40
|
"dev": "tsup --watch",
|