@react-grab/gemini 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 +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 +66 -6
- package/dist/server.js +66 -6
- 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 = 8567;
|
|
7139
7139
|
|
|
7140
7140
|
// src/cli.ts
|
|
7141
|
-
var VERSION = "0.0.
|
|
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("\
|
|
7149
|
+
`${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Gemini)")}`
|
|
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 = 8567;
|
|
7131
7131
|
|
|
7132
7132
|
// src/cli.ts
|
|
7133
|
-
var VERSION = "0.0.
|
|
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("\
|
|
7141
|
+
`${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Gemini)")}`
|
|
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 = 8567;
|
|
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 createGeminiAgentProvider = (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 createGeminiAgentProvider: (providerOptions?: GeminiAgentProviderO
|
|
|
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 createGeminiAgentProvider: (providerOptions?: GeminiAgentProviderO
|
|
|
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 ReactGrabGemini=(function(exports){'use strict';var
|
|
2
|
-
`))n.startsWith("event:")?
|
|
1
|
+
var ReactGrabGemini=(function(exports){'use strict';var f=5e3,g="react-grab:agent-sessions",w=c=>{let e="",o="";for(let n of c.split(`
|
|
2
|
+
`))n.startsWith("event:")?e=n.slice(6).trim():n.startsWith("data:")&&(o=n.slice(5).trim());return {eventType:e,data:o}};async function*A(c,e){let o=c.getReader(),n=new TextDecoder,r="",t=false,s=()=>{t=true,o.cancel().catch(()=>{});};e.addEventListener("abort",s);try{if(e.aborted)throw new DOMException("Aborted","AbortError");for(;;){let i=await o.read();if(t||e.aborted)throw new DOMException("Aborted","AbortError");let{done:d,value:u}=i;u&&(r+=n.decode(u,{stream:!0}));let a;for(;(a=r.indexOf(`
|
|
3
3
|
|
|
4
|
-
`))!==-1;){let{eventType:
|
|
5
|
-
exports.attachAgent=
|
|
4
|
+
`))!==-1;){let{eventType:p,data:l}=w(r.slice(0,a));if(r=r.slice(a+2),p==="done")return;if(p==="error")throw new Error(l||"Agent error");l&&(yield l);}if(d)break}}finally{e.removeEventListener("abort",s);try{o.releaseLock();}catch{}}}var y="Completed successfully";var b=`http://localhost:${8567}`;async function*E(c,e,o){let n=Date.now(),r=e.sessionId,t=()=>{r&&fetch(`${c}/abort/${r}`,{method:"POST"}).catch(()=>{});};o.addEventListener("abort",t);try{let s=await fetch(`${c}/agent`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:o});if(!s.ok)throw new Error(`Server error: ${s.status}`);if(!s.body)throw new Error("No response body");let i=A(s.body,o)[Symbol.asyncIterator](),d=!1,u=i.next(),a=null;for(;!d;){let p=await Promise.race([u.then(m=>({type:"status",iteratorResult:m})),new Promise(m=>setTimeout(()=>m({type:"timeout"}),100))]),l=(Date.now()-n)/1e3;if(p.type==="status"){let m=p.iteratorResult;d=m.done??!1,!d&&m.value&&(a=m.value,u=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{o.removeEventListener("abort",t);}}var S=(c={})=>{let{serverUrl:e=b,getOptions:o}=c,n=null,r=t=>({...o?.()??{},...t??{}});return {send:async function*(t,s){let i={...t,options:r(t.options)};yield*E(e,i,s);},resume:async function*(t,s,i){let d=i.getItem(g);if(!d)throw new Error("No sessions to resume");let a=JSON.parse(d)[t];if(!a)throw new Error(`Session ${t} not found`);let p=a.context,l={...p,options:r(p.options)};yield "Resuming...",yield*E(e,l,s);},supportsResume:true,supportsFollowUp:true,checkConnection:async()=>{let t=Date.now();if(n&&t-n.timestamp<f)return n.result;try{let i=(await fetch(`${e}/health`,{method:"GET"})).ok;return n={result:i,timestamp:t},i}catch{return n={result:false,timestamp:t},false}},abort:async t=>{try{await fetch(`${e}/abort/${t}`,{method:"POST"});}catch{}},undo:async()=>{try{await fetch(`${e}/undo`,{method:"POST"});}catch{}}}},T=async()=>{if(typeof window>"u")return;let c=S(),e=r=>{r.setAgent({provider:c,storage:sessionStorage});},o=window.__REACT_GRAB__;if(o){e(o);return}window.addEventListener("react-grab:init",r=>{e(r.detail);},{once:true});let n=window.__REACT_GRAB__;n&&e(n);};T();
|
|
5
|
+
exports.attachAgent=T;exports.createGeminiAgentProvider=S;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 = 8567;
|
|
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 createGeminiAgentProvider = (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 = 8567;
|
|
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.86";
|
|
12133
|
+
try {
|
|
12134
|
+
fetch(`https://www.react-grab.com/api/version?source=gemini&t=${Date.now()}`).catch(() => {
|
|
12135
|
+
});
|
|
12136
|
+
} catch {
|
|
12137
|
+
}
|
|
12107
12138
|
var geminiSessionMap = /* @__PURE__ */ new Map();
|
|
12108
12139
|
var activeProcesses = /* @__PURE__ */ new Map();
|
|
12140
|
+
var lastGeminiSessionId;
|
|
12109
12141
|
var parseStreamLine = (line) => {
|
|
12110
12142
|
const trimmed = line.trim();
|
|
12111
12143
|
if (!trimmed) return null;
|
|
@@ -12138,13 +12170,13 @@ ${content}`;
|
|
|
12138
12170
|
let geminiProcess;
|
|
12139
12171
|
let stderrBuffer = "";
|
|
12140
12172
|
try {
|
|
12141
|
-
await stream2.writeSSE({ data: "Thinking
|
|
12173
|
+
await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
|
|
12142
12174
|
geminiProcess = execa("gemini", geminiArgs, {
|
|
12143
12175
|
stdin: "pipe",
|
|
12144
12176
|
stdout: "pipe",
|
|
12145
12177
|
stderr: "pipe",
|
|
12146
12178
|
env: { ...process.env },
|
|
12147
|
-
cwd: process.cwd()
|
|
12179
|
+
cwd: process.env.REACT_GRAB_CWD ?? process.cwd()
|
|
12148
12180
|
});
|
|
12149
12181
|
if (sessionId) {
|
|
12150
12182
|
activeProcesses.set(sessionId, geminiProcess);
|
|
@@ -12201,7 +12233,7 @@ ${content}`;
|
|
|
12201
12233
|
case "result":
|
|
12202
12234
|
if (event.status === "success") {
|
|
12203
12235
|
await stream2.writeSSE({
|
|
12204
|
-
data:
|
|
12236
|
+
data: COMPLETED_STATUS,
|
|
12205
12237
|
event: "status"
|
|
12206
12238
|
});
|
|
12207
12239
|
} else if (event.status === "error") {
|
|
@@ -12251,9 +12283,12 @@ ${content}`;
|
|
|
12251
12283
|
if (sessionId && capturedSessionId) {
|
|
12252
12284
|
geminiSessionMap.set(sessionId, capturedSessionId);
|
|
12253
12285
|
}
|
|
12286
|
+
if (capturedSessionId) {
|
|
12287
|
+
lastGeminiSessionId = capturedSessionId;
|
|
12288
|
+
}
|
|
12254
12289
|
await stream2.writeSSE({ data: "", event: "done" });
|
|
12255
12290
|
} catch (error) {
|
|
12256
|
-
const errorMessage = error instanceof Error ? error
|
|
12291
|
+
const errorMessage = error instanceof Error ? formatSpawnError(error, "gemini") : "Unknown error";
|
|
12257
12292
|
const stderrContent = stderrBuffer.trim();
|
|
12258
12293
|
const fullError = stderrContent ? `${errorMessage}
|
|
12259
12294
|
|
|
@@ -12276,6 +12311,31 @@ ${stderrContent}` : errorMessage;
|
|
|
12276
12311
|
}
|
|
12277
12312
|
return context.json({ status: "ok" });
|
|
12278
12313
|
});
|
|
12314
|
+
app.post("/undo", async (context) => {
|
|
12315
|
+
if (!lastGeminiSessionId) {
|
|
12316
|
+
return context.json({ status: "error", message: "No session to undo" });
|
|
12317
|
+
}
|
|
12318
|
+
try {
|
|
12319
|
+
const geminiArgs = [
|
|
12320
|
+
"--output-format",
|
|
12321
|
+
"stream-json",
|
|
12322
|
+
"--yolo",
|
|
12323
|
+
"--session",
|
|
12324
|
+
lastGeminiSessionId,
|
|
12325
|
+
"undo"
|
|
12326
|
+
];
|
|
12327
|
+
await execa("gemini", geminiArgs, {
|
|
12328
|
+
stdout: "pipe",
|
|
12329
|
+
stderr: "pipe",
|
|
12330
|
+
env: { ...process.env },
|
|
12331
|
+
cwd: process.env.REACT_GRAB_CWD ?? process.cwd()
|
|
12332
|
+
});
|
|
12333
|
+
return context.json({ status: "ok" });
|
|
12334
|
+
} catch (error) {
|
|
12335
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
12336
|
+
return context.json({ status: "error", message: errorMessage });
|
|
12337
|
+
}
|
|
12338
|
+
});
|
|
12279
12339
|
app.get("/health", (context) => {
|
|
12280
12340
|
return context.json({ status: "ok", provider: "gemini" });
|
|
12281
12341
|
});
|
|
@@ -12288,7 +12348,7 @@ var startServer = async (port = DEFAULT_PORT) => {
|
|
|
12288
12348
|
const app = createServer();
|
|
12289
12349
|
serve({ fetch: app.fetch, port });
|
|
12290
12350
|
console.log(
|
|
12291
|
-
`${import_picocolors.default.magenta("\
|
|
12351
|
+
`${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Gemini)")}`
|
|
12292
12352
|
);
|
|
12293
12353
|
console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${port}`)}`);
|
|
12294
12354
|
};
|
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 = 8567;
|
|
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.86";
|
|
12121
|
+
try {
|
|
12122
|
+
fetch(`https://www.react-grab.com/api/version?source=gemini&t=${Date.now()}`).catch(() => {
|
|
12123
|
+
});
|
|
12124
|
+
} catch {
|
|
12125
|
+
}
|
|
12095
12126
|
var geminiSessionMap = /* @__PURE__ */ new Map();
|
|
12096
12127
|
var activeProcesses = /* @__PURE__ */ new Map();
|
|
12128
|
+
var lastGeminiSessionId;
|
|
12097
12129
|
var parseStreamLine = (line) => {
|
|
12098
12130
|
const trimmed = line.trim();
|
|
12099
12131
|
if (!trimmed) return null;
|
|
@@ -12126,13 +12158,13 @@ ${content}`;
|
|
|
12126
12158
|
let geminiProcess;
|
|
12127
12159
|
let stderrBuffer = "";
|
|
12128
12160
|
try {
|
|
12129
|
-
await stream2.writeSSE({ data: "Thinking
|
|
12161
|
+
await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
|
|
12130
12162
|
geminiProcess = execa("gemini", geminiArgs, {
|
|
12131
12163
|
stdin: "pipe",
|
|
12132
12164
|
stdout: "pipe",
|
|
12133
12165
|
stderr: "pipe",
|
|
12134
12166
|
env: { ...process.env },
|
|
12135
|
-
cwd: process.cwd()
|
|
12167
|
+
cwd: process.env.REACT_GRAB_CWD ?? process.cwd()
|
|
12136
12168
|
});
|
|
12137
12169
|
if (sessionId) {
|
|
12138
12170
|
activeProcesses.set(sessionId, geminiProcess);
|
|
@@ -12189,7 +12221,7 @@ ${content}`;
|
|
|
12189
12221
|
case "result":
|
|
12190
12222
|
if (event.status === "success") {
|
|
12191
12223
|
await stream2.writeSSE({
|
|
12192
|
-
data:
|
|
12224
|
+
data: COMPLETED_STATUS,
|
|
12193
12225
|
event: "status"
|
|
12194
12226
|
});
|
|
12195
12227
|
} else if (event.status === "error") {
|
|
@@ -12239,9 +12271,12 @@ ${content}`;
|
|
|
12239
12271
|
if (sessionId && capturedSessionId) {
|
|
12240
12272
|
geminiSessionMap.set(sessionId, capturedSessionId);
|
|
12241
12273
|
}
|
|
12274
|
+
if (capturedSessionId) {
|
|
12275
|
+
lastGeminiSessionId = capturedSessionId;
|
|
12276
|
+
}
|
|
12242
12277
|
await stream2.writeSSE({ data: "", event: "done" });
|
|
12243
12278
|
} catch (error) {
|
|
12244
|
-
const errorMessage = error instanceof Error ? error
|
|
12279
|
+
const errorMessage = error instanceof Error ? formatSpawnError(error, "gemini") : "Unknown error";
|
|
12245
12280
|
const stderrContent = stderrBuffer.trim();
|
|
12246
12281
|
const fullError = stderrContent ? `${errorMessage}
|
|
12247
12282
|
|
|
@@ -12264,6 +12299,31 @@ ${stderrContent}` : errorMessage;
|
|
|
12264
12299
|
}
|
|
12265
12300
|
return context.json({ status: "ok" });
|
|
12266
12301
|
});
|
|
12302
|
+
app.post("/undo", async (context) => {
|
|
12303
|
+
if (!lastGeminiSessionId) {
|
|
12304
|
+
return context.json({ status: "error", message: "No session to undo" });
|
|
12305
|
+
}
|
|
12306
|
+
try {
|
|
12307
|
+
const geminiArgs = [
|
|
12308
|
+
"--output-format",
|
|
12309
|
+
"stream-json",
|
|
12310
|
+
"--yolo",
|
|
12311
|
+
"--session",
|
|
12312
|
+
lastGeminiSessionId,
|
|
12313
|
+
"undo"
|
|
12314
|
+
];
|
|
12315
|
+
await execa("gemini", geminiArgs, {
|
|
12316
|
+
stdout: "pipe",
|
|
12317
|
+
stderr: "pipe",
|
|
12318
|
+
env: { ...process.env },
|
|
12319
|
+
cwd: process.env.REACT_GRAB_CWD ?? process.cwd()
|
|
12320
|
+
});
|
|
12321
|
+
return context.json({ status: "ok" });
|
|
12322
|
+
} catch (error) {
|
|
12323
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
12324
|
+
return context.json({ status: "error", message: errorMessage });
|
|
12325
|
+
}
|
|
12326
|
+
});
|
|
12267
12327
|
app.get("/health", (context) => {
|
|
12268
12328
|
return context.json({ status: "ok", provider: "gemini" });
|
|
12269
12329
|
});
|
|
@@ -12276,7 +12336,7 @@ var startServer = async (port = DEFAULT_PORT) => {
|
|
|
12276
12336
|
const app = createServer();
|
|
12277
12337
|
serve({ fetch: app.fetch, port });
|
|
12278
12338
|
console.log(
|
|
12279
|
-
`${import_picocolors.default.magenta("\
|
|
12339
|
+
`${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Gemini)")}`
|
|
12280
12340
|
);
|
|
12281
12341
|
console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${port}`)}`);
|
|
12282
12342
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-grab/gemini",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.86",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"react-grab-gemini": "./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.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.
|
|
37
|
+
"react-grab": "0.0.86"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
40
|
"dev": "tsup --watch",
|