@react-grab/claude-code 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 = 4567;
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("(Claude Code)")}`
7149
+ `${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Claude Code)")}`
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 = 4567;
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("(Claude Code)")}`
7141
+ `${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Claude Code)")}`
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 = 4567;
58
+ var COMPLETED_STATUS = "Completed successfully";
58
59
 
59
60
  // src/client.ts
60
61
  var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
@@ -98,6 +99,7 @@ async function* streamFromServer(serverUrl, context, signal) {
98
99
  const iterator = streamSSE(response.body, signal)[Symbol.asyncIterator]();
99
100
  let done = false;
100
101
  let pendingNext = iterator.next();
102
+ let lastStatus = null;
101
103
  while (!done) {
102
104
  const result = await Promise.race([
103
105
  pendingNext.then((iteratorResult) => ({
@@ -108,23 +110,22 @@ async function* streamFromServer(serverUrl, context, signal) {
108
110
  (resolve) => setTimeout(() => resolve({ type: "timeout" }), 100)
109
111
  )
110
112
  ]);
111
- if (result.type === "timeout") {
112
- const elapsedSeconds = (Date.now() - startTime) / 1e3;
113
- yield `Working\u2026 ${elapsedSeconds.toFixed(1)}s`;
114
- } else {
113
+ const elapsedSeconds = (Date.now() - startTime) / 1e3;
114
+ if (result.type === "status") {
115
115
  const iteratorResult = result.iteratorResult;
116
116
  done = iteratorResult.done ?? false;
117
117
  if (!done && iteratorResult.value) {
118
- const status = iteratorResult.value;
119
- if (status === "Completed successfully") {
120
- const totalSeconds = ((Date.now() - startTime) / 1e3).toFixed(1);
121
- yield `Completed in ${totalSeconds}s`;
122
- } else {
123
- yield status;
124
- }
118
+ lastStatus = iteratorResult.value;
125
119
  pendingNext = iterator.next();
126
120
  }
127
121
  }
122
+ if (lastStatus === COMPLETED_STATUS) {
123
+ yield `Completed in ${elapsedSeconds.toFixed(1)}s`;
124
+ } else if (lastStatus) {
125
+ yield `${lastStatus} ${elapsedSeconds.toFixed(1)}s`;
126
+ } else {
127
+ yield `Working\u2026 ${elapsedSeconds.toFixed(1)}s`;
128
+ }
128
129
  }
129
130
  } finally {
130
131
  signal.removeEventListener("abort", handleAbort);
@@ -180,6 +181,12 @@ var createClaudeAgentProvider = (providerOptions = {}) => {
180
181
  connectionCache = { result: false, timestamp: now };
181
182
  return false;
182
183
  }
184
+ },
185
+ undo: async () => {
186
+ try {
187
+ await fetch(`${serverUrl}/undo`, { method: "POST" });
188
+ } catch {
189
+ }
183
190
  }
184
191
  };
185
192
  };
package/dist/client.d.cts CHANGED
@@ -13,6 +13,7 @@ declare const createClaudeAgentProvider: (providerOptions?: ClaudeAgentProviderO
13
13
  supportsResume: boolean;
14
14
  supportsFollowUp: boolean;
15
15
  checkConnection: () => Promise<boolean>;
16
+ undo: () => Promise<void>;
16
17
  };
17
18
  declare global {
18
19
  interface Window {
package/dist/client.d.ts CHANGED
@@ -13,6 +13,7 @@ declare const createClaudeAgentProvider: (providerOptions?: ClaudeAgentProviderO
13
13
  supportsResume: boolean;
14
14
  supportsFollowUp: boolean;
15
15
  checkConnection: () => Promise<boolean>;
16
+ undo: () => Promise<void>;
16
17
  };
17
18
  declare global {
18
19
  interface Window {
@@ -1,6 +1,6 @@
1
- var ReactGrabClaudeCode=(function(exports){'use strict';var m=5e3,f="react-grab:agent-sessions",A=c=>{let e="",n="";for(let t of c.split(`
2
- `))t.startsWith("event:")?e=t.slice(6).trim():t.startsWith("data:")&&(n=t.slice(5).trim());return {eventType:e,data:n}};async function*y(c,e){let n=c.getReader(),t=new TextDecoder,s="",o=false,a=()=>{o=true,n.cancel().catch(()=>{});};e.addEventListener("abort",a);try{if(e.aborted)throw new DOMException("Aborted","AbortError");for(;;){let i=await n.read();if(o||e.aborted)throw new DOMException("Aborted","AbortError");let{done:l,value:u}=i;u&&(s+=t.decode(u,{stream:!0}));let d;for(;(d=s.indexOf(`
1
+ var ReactGrabClaudeCode=(function(exports){'use strict';var f=5e3,y="react-grab:agent-sessions",E=c=>{let e="",n="";for(let o of c.split(`
2
+ `))o.startsWith("event:")?e=o.slice(6).trim():o.startsWith("data:")&&(n=o.slice(5).trim());return {eventType:e,data:n}};async function*g(c,e){let n=c.getReader(),o=new TextDecoder,s="",t=false,r=()=>{t=true,n.cancel().catch(()=>{});};e.addEventListener("abort",r);try{if(e.aborted)throw new DOMException("Aborted","AbortError");for(;;){let a=await n.read();if(t||e.aborted)throw new DOMException("Aborted","AbortError");let{done:l,value:m}=a;m&&(s+=o.decode(m,{stream:!0}));let i;for(;(i=s.indexOf(`
3
3
 
4
- `))!==-1;){let{eventType:r,data:p}=A(s.slice(0,d));if(s=s.slice(d+2),r==="done")return;if(r==="error")throw new Error(p||"Agent error");p&&(yield p);}if(l)break}}finally{e.removeEventListener("abort",a);try{n.releaseLock();}catch{}}}var w=`http://localhost:${4567}`,C={systemPrompt:{type:"preset",preset:"claude_code",append:`You are helping a user make changes to a React component based on a selected element.
4
+ `))!==-1;){let{eventType:u,data:d}=E(s.slice(0,i));if(s=s.slice(i+2),u==="done")return;if(u==="error")throw new Error(d||"Agent error");d&&(yield d);}if(l)break}}finally{e.removeEventListener("abort",r);try{n.releaseLock();}catch{}}}var A="Completed successfully";var w=`http://localhost:${4567}`,b={systemPrompt:{type:"preset",preset:"claude_code",append:`You are helping a user make changes to a React component based on a selected element.
5
5
  The user has selected an element from their UI and wants you to help modify it.
6
- Provide clear, concise status updates as you work.`},model:"haiku",permissionMode:"bypassPermissions",maxTurns:10};async function*g(c,e,n){let t=Date.now(),s=e.sessionId,o=()=>{s&&fetch(`${c}/abort/${s}`,{method:"POST"}).catch(()=>{});};n.addEventListener("abort",o);try{let a=await fetch(`${c}/agent`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:n});if(!a.ok)throw new Error(`Server error: ${a.status}`);if(!a.body)throw new Error("No response body");let i=y(a.body,n)[Symbol.asyncIterator](),l=!1,u=i.next();for(;!l;){let d=await Promise.race([u.then(r=>({type:"status",iteratorResult:r})),new Promise(r=>setTimeout(()=>r({type:"timeout"}),100))]);if(d.type==="timeout")yield `Working\u2026 ${((Date.now()-t)/1e3).toFixed(1)}s`;else {let r=d.iteratorResult;if(l=r.done??!1,!l&&r.value){let p=r.value;p==="Completed successfully"?yield `Completed in ${((Date.now()-t)/1e3).toFixed(1)}s`:yield p,u=i.next();}}}}finally{n.removeEventListener("abort",o);}}var E=(c={})=>{let{serverUrl:e=w,getOptions:n}=c,t=null,s=o=>({...C,...n?.()??{},...o??{}});return {send:async function*(o,a){let i={...o,options:s(o.options)};yield*g(e,i,a);},resume:async function*(o,a,i){let l=i.getItem(f);if(!l)throw new Error("No sessions to resume");let d=JSON.parse(l)[o];if(!d)throw new Error(`Session ${o} not found`);let r=d.context,p={...r,options:s(r.options)};yield "Resuming...",yield*g(e,p,a);},supportsResume:true,supportsFollowUp:true,checkConnection:async()=>{let o=Date.now();if(t&&o-t.timestamp<m)return t.result;try{let i=(await fetch(`${e}/health`,{method:"GET"})).ok;return t={result:i,timestamp:o},i}catch{return t={result:false,timestamp:o},false}}}},b=async()=>{if(typeof window>"u")return;let c=E(),e=s=>{s.setAgent({provider:c,storage:sessionStorage});},n=window.__REACT_GRAB__;if(n){e(n);return}window.addEventListener("react-grab:init",s=>{e(s.detail);},{once:true});let t=window.__REACT_GRAB__;t&&e(t);};b();exports.attachAgent=b;exports.createClaudeAgentProvider=E;return exports;})({});
6
+ Provide clear, concise status updates as you work.`},model:"haiku",permissionMode:"bypassPermissions",maxTurns:10};async function*h(c,e,n){let o=Date.now(),s=e.sessionId,t=()=>{s&&fetch(`${c}/abort/${s}`,{method:"POST"}).catch(()=>{});};n.addEventListener("abort",t);try{let r=await fetch(`${c}/agent`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:n});if(!r.ok)throw new Error(`Server error: ${r.status}`);if(!r.body)throw new Error("No response body");let a=g(r.body,n)[Symbol.asyncIterator](),l=!1,m=a.next(),i=null;for(;!l;){let u=await Promise.race([m.then(p=>({type:"status",iteratorResult:p})),new Promise(p=>setTimeout(()=>p({type:"timeout"}),100))]),d=(Date.now()-o)/1e3;if(u.type==="status"){let p=u.iteratorResult;l=p.done??!1,!l&&p.value&&(i=p.value,m=a.next());}i===A?yield `Completed in ${d.toFixed(1)}s`:i?yield `${i} ${d.toFixed(1)}s`:yield `Working\u2026 ${d.toFixed(1)}s`;}}finally{n.removeEventListener("abort",t);}}var S=(c={})=>{let{serverUrl:e=w,getOptions:n}=c,o=null,s=t=>({...b,...n?.()??{},...t??{}});return {send:async function*(t,r){let a={...t,options:s(t.options)};yield*h(e,a,r);},resume:async function*(t,r,a){let l=a.getItem(y);if(!l)throw new Error("No sessions to resume");let i=JSON.parse(l)[t];if(!i)throw new Error(`Session ${t} not found`);let u=i.context,d={...u,options:s(u.options)};yield "Resuming...",yield*h(e,d,r);},supportsResume:true,supportsFollowUp:true,checkConnection:async()=>{let t=Date.now();if(o&&t-o.timestamp<f)return o.result;try{let a=(await fetch(`${e}/health`,{method:"GET"})).ok;return o={result:a,timestamp:t},a}catch{return o={result:false,timestamp:t},false}},undo:async()=>{try{await fetch(`${e}/undo`,{method:"POST"});}catch{}}}},T=async()=>{if(typeof window>"u")return;let c=S(),e=s=>{s.setAgent({provider:c,storage:sessionStorage});},n=window.__REACT_GRAB__;if(n){e(n);return}window.addEventListener("react-grab:init",s=>{e(s.detail);},{once:true});let o=window.__REACT_GRAB__;o&&e(o);};T();exports.attachAgent=T;exports.createClaudeAgentProvider=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 = 4567;
56
+ var COMPLETED_STATUS = "Completed successfully";
56
57
 
57
58
  // src/client.ts
58
59
  var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
@@ -96,6 +97,7 @@ async function* streamFromServer(serverUrl, context, signal) {
96
97
  const iterator = streamSSE(response.body, signal)[Symbol.asyncIterator]();
97
98
  let done = false;
98
99
  let pendingNext = iterator.next();
100
+ let lastStatus = null;
99
101
  while (!done) {
100
102
  const result = await Promise.race([
101
103
  pendingNext.then((iteratorResult) => ({
@@ -106,23 +108,22 @@ async function* streamFromServer(serverUrl, context, signal) {
106
108
  (resolve) => setTimeout(() => resolve({ type: "timeout" }), 100)
107
109
  )
108
110
  ]);
109
- if (result.type === "timeout") {
110
- const elapsedSeconds = (Date.now() - startTime) / 1e3;
111
- yield `Working\u2026 ${elapsedSeconds.toFixed(1)}s`;
112
- } else {
111
+ const elapsedSeconds = (Date.now() - startTime) / 1e3;
112
+ if (result.type === "status") {
113
113
  const iteratorResult = result.iteratorResult;
114
114
  done = iteratorResult.done ?? false;
115
115
  if (!done && iteratorResult.value) {
116
- const status = iteratorResult.value;
117
- if (status === "Completed successfully") {
118
- const totalSeconds = ((Date.now() - startTime) / 1e3).toFixed(1);
119
- yield `Completed in ${totalSeconds}s`;
120
- } else {
121
- yield status;
122
- }
116
+ lastStatus = iteratorResult.value;
123
117
  pendingNext = iterator.next();
124
118
  }
125
119
  }
120
+ if (lastStatus === COMPLETED_STATUS) {
121
+ yield `Completed in ${elapsedSeconds.toFixed(1)}s`;
122
+ } else if (lastStatus) {
123
+ yield `${lastStatus} ${elapsedSeconds.toFixed(1)}s`;
124
+ } else {
125
+ yield `Working\u2026 ${elapsedSeconds.toFixed(1)}s`;
126
+ }
126
127
  }
127
128
  } finally {
128
129
  signal.removeEventListener("abort", handleAbort);
@@ -178,6 +179,12 @@ var createClaudeAgentProvider = (providerOptions = {}) => {
178
179
  connectionCache = { result: false, timestamp: now };
179
180
  return false;
180
181
  }
182
+ },
183
+ undo: async () => {
184
+ try {
185
+ await fetch(`${serverUrl}/undo`, { method: "POST" });
186
+ } catch {
187
+ }
181
188
  }
182
189
  };
183
190
  };
package/dist/server.cjs CHANGED
@@ -18156,12 +18156,43 @@ function query({
18156
18156
 
18157
18157
  // src/constants.ts
18158
18158
  var DEFAULT_PORT = 4567;
18159
+ var COMPLETED_STATUS = "Completed successfully";
18159
18160
 
18160
18161
  // ../utils/dist/server.js
18161
18162
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
18163
+ var COMMAND_INSTALL_MAP = {
18164
+ "cursor-agent": "Install Cursor (https://cursor.com) and ensure 'cursor-agent' is in your PATH.",
18165
+ gemini: "Install the Gemini CLI: npm install -g @anthropic-ai/gemini-cli\nOr see: https://github.com/google-gemini/gemini-cli",
18166
+ claude: "Install Claude Code: npm install -g @anthropic-ai/claude-code\nOr see: https://github.com/anthropics/claude-code"
18167
+ };
18168
+ var formatSpawnError = (error, commandName) => {
18169
+ const spawnError = error;
18170
+ const isNotFound = spawnError.code === "ENOENT" || spawnError.message && spawnError.message.includes("ENOENT");
18171
+ if (isNotFound) {
18172
+ const installInfo = COMMAND_INSTALL_MAP[commandName];
18173
+ const baseMessage = `Command '${commandName}' not found.`;
18174
+ {
18175
+ return `${baseMessage}
18176
+
18177
+ ${installInfo}`;
18178
+ }
18179
+ }
18180
+ const isPermissionDenied = spawnError.code === "EACCES" || spawnError.message && spawnError.message.includes("EACCES");
18181
+ if (isPermissionDenied) {
18182
+ return `Permission denied when trying to run '${commandName}'.
18183
+
18184
+ Check that the command is executable: chmod +x $(which ${commandName})`;
18185
+ }
18186
+ return error.message;
18187
+ };
18162
18188
 
18163
18189
  // src/server.ts
18164
- var VERSION = "0.0.84";
18190
+ var VERSION = "0.0.86";
18191
+ try {
18192
+ fetch(`https://www.react-grab.com/api/version?source=claude-code&t=${Date.now()}`).catch(() => {
18193
+ });
18194
+ } catch {
18195
+ }
18165
18196
  var resolveClaudePath = () => {
18166
18197
  const command = process.platform === "win32" ? "where claude" : "which claude";
18167
18198
  try {
@@ -18173,6 +18204,7 @@ var resolveClaudePath = () => {
18173
18204
  };
18174
18205
  var claudeSessionMap = /* @__PURE__ */ new Map();
18175
18206
  var abortedSessions = /* @__PURE__ */ new Set();
18207
+ var lastClaudeSessionId;
18176
18208
  var isTextBlock = (block) => block.type === "text";
18177
18209
  var createServer = () => {
18178
18210
  const app = new Hono2();
@@ -18188,7 +18220,7 @@ ${content}`;
18188
18220
  return streamSSE(context, async (stream2) => {
18189
18221
  const isAborted2 = () => sessionId && abortedSessions.has(sessionId);
18190
18222
  try {
18191
- await stream2.writeSSE({ data: "Thinking...", event: "status" });
18223
+ await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
18192
18224
  const env = { ...process.env };
18193
18225
  delete env.NODE_OPTIONS;
18194
18226
  delete env.VSCODE_INSPECTOR_OPTIONS;
@@ -18196,10 +18228,10 @@ ${content}`;
18196
18228
  prompt: userPrompt,
18197
18229
  options: {
18198
18230
  pathToClaudeCodeExecutable: resolveClaudePath(),
18199
- cwd: process.cwd(),
18200
18231
  includePartialMessages: true,
18201
18232
  env,
18202
18233
  ...options,
18234
+ cwd: options?.cwd ?? process.env.REACT_GRAB_CWD ?? process.cwd(),
18203
18235
  ...isFollowUp && claudeSessionId ? { resume: claudeSessionId } : {}
18204
18236
  }
18205
18237
  });
@@ -18217,7 +18249,7 @@ ${content}`;
18217
18249
  }
18218
18250
  if (message.type === "result") {
18219
18251
  await stream2.writeSSE({
18220
- data: message.subtype === "success" ? "Completed successfully" : "Task finished",
18252
+ data: message.subtype === "success" ? COMPLETED_STATUS : "Task finished",
18221
18253
  event: "status"
18222
18254
  });
18223
18255
  }
@@ -18226,13 +18258,14 @@ ${content}`;
18226
18258
  if (sessionId) {
18227
18259
  claudeSessionMap.set(sessionId, capturedClaudeSessionId);
18228
18260
  }
18261
+ lastClaudeSessionId = capturedClaudeSessionId;
18229
18262
  }
18230
18263
  if (!isAborted2()) {
18231
18264
  await stream2.writeSSE({ data: "", event: "done" });
18232
18265
  }
18233
18266
  } catch (error) {
18234
18267
  if (!isAborted2()) {
18235
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
18268
+ const errorMessage = error instanceof Error ? formatSpawnError(error, "claude") : "Unknown error";
18236
18269
  const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
18237
18270
  const fullError = stderr && stderr.trim() ? `${errorMessage}
18238
18271
 
@@ -18255,6 +18288,31 @@ ${stderr.trim()}` : errorMessage;
18255
18288
  abortedSessions.add(sessionId);
18256
18289
  return context.json({ status: "ok" });
18257
18290
  });
18291
+ app.post("/undo", async (context) => {
18292
+ if (!lastClaudeSessionId) {
18293
+ return context.json({ status: "error", message: "No session to undo" });
18294
+ }
18295
+ try {
18296
+ const env = { ...process.env };
18297
+ delete env.NODE_OPTIONS;
18298
+ delete env.VSCODE_INSPECTOR_OPTIONS;
18299
+ const queryResult = query({
18300
+ prompt: "undo",
18301
+ options: {
18302
+ pathToClaudeCodeExecutable: resolveClaudePath(),
18303
+ env,
18304
+ cwd: process.env.REACT_GRAB_CWD ?? process.cwd(),
18305
+ resume: lastClaudeSessionId
18306
+ }
18307
+ });
18308
+ for await (const _message of queryResult) {
18309
+ }
18310
+ return context.json({ status: "ok" });
18311
+ } catch (error) {
18312
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
18313
+ return context.json({ status: "error", message: errorMessage });
18314
+ }
18315
+ });
18258
18316
  app.get("/health", (context) => {
18259
18317
  return context.json({ status: "ok", provider: "claude" });
18260
18318
  });
@@ -18267,7 +18325,7 @@ var startServer = async (port = DEFAULT_PORT) => {
18267
18325
  const app = createServer();
18268
18326
  serve({ fetch: app.fetch, port });
18269
18327
  console.log(
18270
- `${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Claude Code)")}`
18328
+ `${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Claude Code)")}`
18271
18329
  );
18272
18330
  console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${port}`)}`);
18273
18331
  };
package/dist/server.js CHANGED
@@ -18127,12 +18127,43 @@ function query({
18127
18127
 
18128
18128
  // src/constants.ts
18129
18129
  var DEFAULT_PORT = 4567;
18130
+ var COMPLETED_STATUS = "Completed successfully";
18130
18131
 
18131
18132
  // ../utils/dist/server.js
18132
18133
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
18134
+ var COMMAND_INSTALL_MAP = {
18135
+ "cursor-agent": "Install Cursor (https://cursor.com) and ensure 'cursor-agent' is in your PATH.",
18136
+ gemini: "Install the Gemini CLI: npm install -g @anthropic-ai/gemini-cli\nOr see: https://github.com/google-gemini/gemini-cli",
18137
+ claude: "Install Claude Code: npm install -g @anthropic-ai/claude-code\nOr see: https://github.com/anthropics/claude-code"
18138
+ };
18139
+ var formatSpawnError = (error, commandName) => {
18140
+ const spawnError = error;
18141
+ const isNotFound = spawnError.code === "ENOENT" || spawnError.message && spawnError.message.includes("ENOENT");
18142
+ if (isNotFound) {
18143
+ const installInfo = COMMAND_INSTALL_MAP[commandName];
18144
+ const baseMessage = `Command '${commandName}' not found.`;
18145
+ {
18146
+ return `${baseMessage}
18147
+
18148
+ ${installInfo}`;
18149
+ }
18150
+ }
18151
+ const isPermissionDenied = spawnError.code === "EACCES" || spawnError.message && spawnError.message.includes("EACCES");
18152
+ if (isPermissionDenied) {
18153
+ return `Permission denied when trying to run '${commandName}'.
18154
+
18155
+ Check that the command is executable: chmod +x $(which ${commandName})`;
18156
+ }
18157
+ return error.message;
18158
+ };
18133
18159
 
18134
18160
  // src/server.ts
18135
- var VERSION = "0.0.84";
18161
+ var VERSION = "0.0.86";
18162
+ try {
18163
+ fetch(`https://www.react-grab.com/api/version?source=claude-code&t=${Date.now()}`).catch(() => {
18164
+ });
18165
+ } catch {
18166
+ }
18136
18167
  var resolveClaudePath = () => {
18137
18168
  const command = process.platform === "win32" ? "where claude" : "which claude";
18138
18169
  try {
@@ -18144,6 +18175,7 @@ var resolveClaudePath = () => {
18144
18175
  };
18145
18176
  var claudeSessionMap = /* @__PURE__ */ new Map();
18146
18177
  var abortedSessions = /* @__PURE__ */ new Set();
18178
+ var lastClaudeSessionId;
18147
18179
  var isTextBlock = (block) => block.type === "text";
18148
18180
  var createServer = () => {
18149
18181
  const app = new Hono2();
@@ -18159,7 +18191,7 @@ ${content}`;
18159
18191
  return streamSSE(context, async (stream2) => {
18160
18192
  const isAborted2 = () => sessionId && abortedSessions.has(sessionId);
18161
18193
  try {
18162
- await stream2.writeSSE({ data: "Thinking...", event: "status" });
18194
+ await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
18163
18195
  const env = { ...process.env };
18164
18196
  delete env.NODE_OPTIONS;
18165
18197
  delete env.VSCODE_INSPECTOR_OPTIONS;
@@ -18167,10 +18199,10 @@ ${content}`;
18167
18199
  prompt: userPrompt,
18168
18200
  options: {
18169
18201
  pathToClaudeCodeExecutable: resolveClaudePath(),
18170
- cwd: process.cwd(),
18171
18202
  includePartialMessages: true,
18172
18203
  env,
18173
18204
  ...options,
18205
+ cwd: options?.cwd ?? process.env.REACT_GRAB_CWD ?? process.cwd(),
18174
18206
  ...isFollowUp && claudeSessionId ? { resume: claudeSessionId } : {}
18175
18207
  }
18176
18208
  });
@@ -18188,7 +18220,7 @@ ${content}`;
18188
18220
  }
18189
18221
  if (message.type === "result") {
18190
18222
  await stream2.writeSSE({
18191
- data: message.subtype === "success" ? "Completed successfully" : "Task finished",
18223
+ data: message.subtype === "success" ? COMPLETED_STATUS : "Task finished",
18192
18224
  event: "status"
18193
18225
  });
18194
18226
  }
@@ -18197,13 +18229,14 @@ ${content}`;
18197
18229
  if (sessionId) {
18198
18230
  claudeSessionMap.set(sessionId, capturedClaudeSessionId);
18199
18231
  }
18232
+ lastClaudeSessionId = capturedClaudeSessionId;
18200
18233
  }
18201
18234
  if (!isAborted2()) {
18202
18235
  await stream2.writeSSE({ data: "", event: "done" });
18203
18236
  }
18204
18237
  } catch (error) {
18205
18238
  if (!isAborted2()) {
18206
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
18239
+ const errorMessage = error instanceof Error ? formatSpawnError(error, "claude") : "Unknown error";
18207
18240
  const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
18208
18241
  const fullError = stderr && stderr.trim() ? `${errorMessage}
18209
18242
 
@@ -18226,6 +18259,31 @@ ${stderr.trim()}` : errorMessage;
18226
18259
  abortedSessions.add(sessionId);
18227
18260
  return context.json({ status: "ok" });
18228
18261
  });
18262
+ app.post("/undo", async (context) => {
18263
+ if (!lastClaudeSessionId) {
18264
+ return context.json({ status: "error", message: "No session to undo" });
18265
+ }
18266
+ try {
18267
+ const env = { ...process.env };
18268
+ delete env.NODE_OPTIONS;
18269
+ delete env.VSCODE_INSPECTOR_OPTIONS;
18270
+ const queryResult = query({
18271
+ prompt: "undo",
18272
+ options: {
18273
+ pathToClaudeCodeExecutable: resolveClaudePath(),
18274
+ env,
18275
+ cwd: process.env.REACT_GRAB_CWD ?? process.cwd(),
18276
+ resume: lastClaudeSessionId
18277
+ }
18278
+ });
18279
+ for await (const _message of queryResult) {
18280
+ }
18281
+ return context.json({ status: "ok" });
18282
+ } catch (error) {
18283
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
18284
+ return context.json({ status: "error", message: errorMessage });
18285
+ }
18286
+ });
18229
18287
  app.get("/health", (context) => {
18230
18288
  return context.json({ status: "ok", provider: "claude" });
18231
18289
  });
@@ -18238,7 +18296,7 @@ var startServer = async (port = DEFAULT_PORT) => {
18238
18296
  const app = createServer();
18239
18297
  serve({ fetch: app.fetch, port });
18240
18298
  console.log(
18241
- `${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Claude Code)")}`
18299
+ `${import_picocolors.default.magenta("\u273F")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Claude Code)")}`
18242
18300
  );
18243
18301
  console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${port}`)}`);
18244
18302
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-grab/claude-code",
3
- "version": "0.0.84",
3
+ "version": "0.0.86",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "react-grab-claude-code": "./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
  "@anthropic-ai/claude-agent-sdk": "^0.1.0",
@@ -34,7 +35,7 @@
34
35
  "fkill": "^9.0.0",
35
36
  "hono": "^4.0.0",
36
37
  "picocolors": "^1.1.1",
37
- "react-grab": "0.0.84"
38
+ "react-grab": "0.0.86"
38
39
  },
39
40
  "scripts": {
40
41
  "dev": "tsup --watch",