@react-grab/claude-code 0.0.87 → 0.0.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -7138,7 +7138,7 @@ var import_picocolors = __toESM(require_picocolors());
7138
7138
  var DEFAULT_PORT = 4567;
7139
7139
 
7140
7140
  // src/cli.ts
7141
- var VERSION = "0.0.87";
7141
+ var VERSION = "0.0.89";
7142
7142
  var serverPath = path2.join(__dirname, "server.cjs");
7143
7143
  execa(process.execPath, [serverPath], {
7144
7144
  detached: true,
package/dist/cli.js CHANGED
@@ -7130,7 +7130,7 @@ var import_picocolors = __toESM(require_picocolors());
7130
7130
  var DEFAULT_PORT = 4567;
7131
7131
 
7132
7132
  // src/cli.ts
7133
- var VERSION = "0.0.87";
7133
+ var VERSION = "0.0.89";
7134
7134
  var serverPath = join(__dirname, "server.cjs");
7135
7135
  execa(process.execPath, [serverPath], {
7136
7136
  detached: true,
package/dist/client.cjs CHANGED
@@ -12,7 +12,7 @@ var parseSSEEvent = (eventBlock) => {
12
12
  }
13
13
  return { eventType, data };
14
14
  };
15
- async function* streamSSE(stream, signal) {
15
+ var streamSSE = async function* (stream, signal) {
16
16
  const reader = stream.getReader();
17
17
  const decoder = new TextDecoder();
18
18
  let buffer = "";
@@ -51,40 +51,54 @@ async function* streamSSE(stream, signal) {
51
51
  } catch {
52
52
  }
53
53
  }
54
- }
55
-
56
- // src/constants.ts
57
- var DEFAULT_PORT = 4567;
58
- var COMPLETED_STATUS = "Completed successfully";
59
-
60
- // src/client.ts
61
- var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
62
- var DEFAULT_OPTIONS = {
63
- systemPrompt: {
64
- type: "preset",
65
- preset: "claude_code",
66
- append: `You are helping a user make changes to a React component based on a selected element.
67
- The user has selected an element from their UI and wants you to help modify it.
68
- Provide clear, concise status updates as you work.`
69
- },
70
- model: "haiku",
71
- permissionMode: "bypassPermissions",
72
- maxTurns: 10
73
54
  };
74
- async function* streamFromServer(serverUrl, context, signal) {
55
+ var isRecord = (value) => typeof value === "object" && value !== null;
56
+ var getStoredAgentContext = (storage, sessionId, storageKey = STORAGE_KEY) => {
57
+ const rawSessions = storage.getItem(storageKey);
58
+ if (!rawSessions) throw new Error("No sessions to resume");
59
+ let parsed;
60
+ try {
61
+ parsed = JSON.parse(rawSessions);
62
+ } catch {
63
+ throw new Error("Failed to parse stored sessions");
64
+ }
65
+ if (!isRecord(parsed)) throw new Error("Invalid stored sessions");
66
+ const storedSession = parsed[sessionId];
67
+ if (!isRecord(storedSession)) {
68
+ throw new Error(`Session ${sessionId} not found`);
69
+ }
70
+ const context = storedSession.context;
71
+ if (!isRecord(context)) throw new Error(`Session ${sessionId} is invalid`);
72
+ const content = context.content;
73
+ const prompt = context.prompt;
74
+ if (typeof content !== "string" || typeof prompt !== "string") {
75
+ throw new Error(`Session ${sessionId} is invalid`);
76
+ }
77
+ const options = context.options;
78
+ const storedSessionId = context.sessionId;
79
+ return {
80
+ content,
81
+ prompt,
82
+ options,
83
+ sessionId: typeof storedSessionId === "string" ? storedSessionId : void 0
84
+ };
85
+ };
86
+ var streamAgentStatusFromServer = async function* (options, context, signal) {
75
87
  const startTime = Date.now();
76
88
  const sessionId = context.sessionId;
89
+ const pollIntervalMs = options.pollIntervalMs ?? 100;
90
+ const agentUrl = `${options.serverUrl}${options.agentPath ?? "/agent"}`;
77
91
  const handleAbort = () => {
78
- if (sessionId) {
79
- fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" }).catch(
80
- () => {
81
- }
82
- );
83
- }
92
+ if (!sessionId) return;
93
+ const abortPath = options.abortPath?.(sessionId) ?? `/abort/${sessionId}`;
94
+ fetch(`${options.serverUrl}${abortPath}`, { method: "POST" }).catch(
95
+ () => {
96
+ }
97
+ );
84
98
  };
85
99
  signal.addEventListener("abort", handleAbort);
86
100
  try {
87
- const response = await fetch(`${serverUrl}/agent`, {
101
+ const response = await fetch(agentUrl, {
88
102
  method: "POST",
89
103
  headers: { "Content-Type": "application/json" },
90
104
  body: JSON.stringify(context),
@@ -97,29 +111,29 @@ async function* streamFromServer(serverUrl, context, signal) {
97
111
  throw new Error("No response body");
98
112
  }
99
113
  const iterator = streamSSE(response.body, signal)[Symbol.asyncIterator]();
100
- let done = false;
114
+ let isDone = false;
101
115
  let pendingNext = iterator.next();
102
116
  let lastStatus = null;
103
- while (!done) {
117
+ while (!isDone) {
104
118
  const result = await Promise.race([
105
119
  pendingNext.then((iteratorResult) => ({
106
120
  type: "status",
107
121
  iteratorResult
108
122
  })),
109
123
  new Promise(
110
- (resolve) => setTimeout(() => resolve({ type: "timeout" }), 100)
124
+ (resolve) => setTimeout(() => resolve({ type: "timeout" }), pollIntervalMs)
111
125
  )
112
126
  ]);
113
127
  const elapsedSeconds = (Date.now() - startTime) / 1e3;
114
128
  if (result.type === "status") {
115
129
  const iteratorResult = result.iteratorResult;
116
- done = iteratorResult.done ?? false;
117
- if (!done && iteratorResult.value) {
130
+ isDone = iteratorResult.done ?? false;
131
+ if (!isDone && iteratorResult.value) {
118
132
  lastStatus = iteratorResult.value;
119
133
  pendingNext = iterator.next();
120
134
  }
121
135
  }
122
- if (lastStatus === COMPLETED_STATUS) {
136
+ if (lastStatus === options.completedStatus) {
123
137
  yield `Completed in ${elapsedSeconds.toFixed(1)}s`;
124
138
  } else if (lastStatus) {
125
139
  yield `${lastStatus} ${elapsedSeconds.toFixed(1)}s`;
@@ -130,58 +144,87 @@ async function* streamFromServer(serverUrl, context, signal) {
130
144
  } finally {
131
145
  signal.removeEventListener("abort", handleAbort);
132
146
  }
133
- }
147
+ };
148
+ var createCachedConnectionChecker = (checkConnection, ttlMs = CONNECTION_CHECK_TTL_MS) => {
149
+ let cache = null;
150
+ return async () => {
151
+ const now = Date.now();
152
+ if (cache && now - cache.timestamp < ttlMs) return cache.result;
153
+ try {
154
+ const result = await checkConnection();
155
+ cache = { result, timestamp: now };
156
+ return result;
157
+ } catch {
158
+ cache = { result: false, timestamp: now };
159
+ return false;
160
+ }
161
+ };
162
+ };
163
+
164
+ // src/constants.ts
165
+ var DEFAULT_PORT = 4567;
166
+ var COMPLETED_STATUS = "Completed successfully";
167
+
168
+ // src/client.ts
169
+ var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
170
+ var DEFAULT_OPTIONS = {
171
+ systemPrompt: {
172
+ type: "preset",
173
+ preset: "claude_code",
174
+ append: `You are helping a user make changes to a React component based on a selected element.
175
+ The user has selected an element from their UI and wants you to help modify it.
176
+ Provide clear, concise status updates as you work.`
177
+ },
178
+ model: "haiku",
179
+ permissionMode: "bypassPermissions",
180
+ maxTurns: 10
181
+ };
182
+ var isReactGrabApi = (value) => typeof value === "object" && value !== null && "setAgent" in value;
134
183
  var createClaudeAgentProvider = (providerOptions = {}) => {
135
184
  const { serverUrl = DEFAULT_SERVER_URL, getOptions } = providerOptions;
136
- let connectionCache = null;
137
185
  const mergeOptions = (contextOptions) => ({
138
186
  ...DEFAULT_OPTIONS,
139
187
  ...getOptions?.() ?? {},
140
188
  ...contextOptions ?? {}
141
189
  });
190
+ const checkConnection = createCachedConnectionChecker(async () => {
191
+ const response = await fetch(`${serverUrl}/health`, { method: "GET" });
192
+ return response.ok;
193
+ }, CONNECTION_CHECK_TTL_MS);
142
194
  return {
143
195
  send: async function* (context, signal) {
144
196
  const mergedContext = {
145
197
  ...context,
146
198
  options: mergeOptions(context.options)
147
199
  };
148
- yield* streamFromServer(serverUrl, mergedContext, signal);
200
+ yield* streamAgentStatusFromServer(
201
+ { serverUrl, completedStatus: COMPLETED_STATUS },
202
+ mergedContext,
203
+ signal
204
+ );
149
205
  },
150
206
  resume: async function* (sessionId, signal, storage) {
151
- const savedSessions = storage.getItem(STORAGE_KEY);
152
- if (!savedSessions) {
153
- throw new Error("No sessions to resume");
154
- }
155
- const sessionsObject = JSON.parse(savedSessions);
156
- const session = sessionsObject[sessionId];
157
- if (!session) {
158
- throw new Error(`Session ${sessionId} not found`);
159
- }
160
- const context = session.context;
207
+ const storedContext = getStoredAgentContext(storage, sessionId);
208
+ const context = {
209
+ content: storedContext.content,
210
+ prompt: storedContext.prompt,
211
+ options: storedContext.options,
212
+ sessionId: storedContext.sessionId ?? sessionId
213
+ };
161
214
  const mergedContext = {
162
215
  ...context,
163
216
  options: mergeOptions(context.options)
164
217
  };
165
218
  yield "Resuming...";
166
- yield* streamFromServer(serverUrl, mergedContext, signal);
219
+ yield* streamAgentStatusFromServer(
220
+ { serverUrl, completedStatus: COMPLETED_STATUS },
221
+ mergedContext,
222
+ signal
223
+ );
167
224
  },
168
225
  supportsResume: true,
169
226
  supportsFollowUp: true,
170
- checkConnection: async () => {
171
- const now = Date.now();
172
- if (connectionCache && now - connectionCache.timestamp < CONNECTION_CHECK_TTL_MS) {
173
- return connectionCache.result;
174
- }
175
- try {
176
- const response = await fetch(`${serverUrl}/health`, { method: "GET" });
177
- const result = response.ok;
178
- connectionCache = { result, timestamp: now };
179
- return result;
180
- } catch {
181
- connectionCache = { result: false, timestamp: now };
182
- return false;
183
- }
184
- },
227
+ checkConnection,
185
228
  undo: async () => {
186
229
  try {
187
230
  await fetch(`${serverUrl}/undo`, { method: "POST" });
@@ -193,24 +236,25 @@ var createClaudeAgentProvider = (providerOptions = {}) => {
193
236
  var attachAgent = async () => {
194
237
  if (typeof window === "undefined") return;
195
238
  const provider = createClaudeAgentProvider();
196
- const attach = (api2) => {
197
- api2.setAgent({ provider, storage: sessionStorage });
239
+ const attach = (api) => {
240
+ api.setAgent({ provider, storage: sessionStorage });
198
241
  };
199
- const api = window.__REACT_GRAB__;
200
- if (api) {
201
- attach(api);
242
+ const existingApi = window.__REACT_GRAB__;
243
+ if (isReactGrabApi(existingApi)) {
244
+ attach(existingApi);
202
245
  return;
203
246
  }
204
247
  window.addEventListener(
205
248
  "react-grab:init",
206
249
  (event) => {
207
- const customEvent = event;
208
- attach(customEvent.detail);
250
+ if (!(event instanceof CustomEvent)) return;
251
+ if (!isReactGrabApi(event.detail)) return;
252
+ attach(event.detail);
209
253
  },
210
254
  { once: true }
211
255
  );
212
256
  const apiAfterListener = window.__REACT_GRAB__;
213
- if (apiAfterListener) {
257
+ if (isReactGrabApi(apiAfterListener)) {
214
258
  attach(apiAfterListener);
215
259
  }
216
260
  };
@@ -1,6 +1,6 @@
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(`
1
+ var ReactGrabClaudeCode=(function(exports){'use strict';var y=5e3,b="react-grab:agent-sessions",v=t=>{let e="",o="";for(let n of t.split(`
2
+ `))n.startsWith("event:")?e=n.slice(6).trim():n.startsWith("data:")&&(o=n.slice(5).trim());return {eventType:e,data:o}},T=async function*(t,e){let o=t.getReader(),n=new TextDecoder,r="",s=false,i=()=>{s=true,o.cancel().catch(()=>{});};e.addEventListener("abort",i);try{if(e.aborted)throw new DOMException("Aborted","AbortError");for(;;){let d=await o.read();if(s||e.aborted)throw new DOMException("Aborted","AbortError");let{done:a,value:l}=d;l&&(r+=n.decode(l,{stream:!0}));let c;for(;(c=r.indexOf(`
3
3
 
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.
4
+ `))!==-1;){let{eventType:f,data:p}=v(r.slice(0,c));if(r=r.slice(c+2),f==="done")return;if(f==="error")throw new Error(p||"Agent error");p&&(yield p);}if(a)break}}finally{e.removeEventListener("abort",i);try{o.releaseLock();}catch{}}},h=t=>typeof t=="object"&&t!==null,S=(t,e,o=b)=>{let n=t.getItem(o);if(!n)throw new Error("No sessions to resume");let r;try{r=JSON.parse(n);}catch{throw new Error("Failed to parse stored sessions")}if(!h(r))throw new Error("Invalid stored sessions");let s=r[e];if(!h(s))throw new Error(`Session ${e} not found`);let i=s.context;if(!h(i))throw new Error(`Session ${e} is invalid`);let d=i.content,a=i.prompt;if(typeof d!="string"||typeof a!="string")throw new Error(`Session ${e} is invalid`);let l=i.options,c=i.sessionId;return {content:d,prompt:a,options:l,sessionId:typeof c=="string"?c:void 0}},w=async function*(t,e,o){let n=Date.now(),r=e.sessionId,s=t.pollIntervalMs??100,i=`${t.serverUrl}${t.agentPath??"/agent"}`,d=()=>{if(!r)return;let a=t.abortPath?.(r)??`/abort/${r}`;fetch(`${t.serverUrl}${a}`,{method:"POST"}).catch(()=>{});};o.addEventListener("abort",d);try{let a=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:o});if(!a.ok)throw new Error(`Server error: ${a.status}`);if(!a.body)throw new Error("No response body");let l=T(a.body,o)[Symbol.asyncIterator](),c=!1,f=l.next(),p=null;for(;!c;){let A=await Promise.race([f.then(u=>({type:"status",iteratorResult:u})),new Promise(u=>setTimeout(()=>u({type:"timeout"}),s))]),m=(Date.now()-n)/1e3;if(A.type==="status"){let u=A.iteratorResult;c=u.done??!1,!c&&u.value&&(p=u.value,f=l.next());}p===t.completedStatus?yield `Completed in ${m.toFixed(1)}s`:p?yield `${p} ${m.toFixed(1)}s`:yield `Working\u2026 ${m.toFixed(1)}s`;}}finally{o.removeEventListener("abort",d);}},E=(t,e=y)=>{let o=null;return async()=>{let n=Date.now();if(o&&n-o.timestamp<e)return o.result;try{let r=await t();return o={result:r,timestamp:n},r}catch{return o={result:false,timestamp:n},false}}};var g="Completed successfully";var _=`http://localhost:${4567}`,x={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*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;})({});
6
+ Provide clear, concise status updates as you work.`},model:"haiku",permissionMode:"bypassPermissions",maxTurns:10},C=t=>typeof t=="object"&&t!==null&&"setAgent"in t,R=(t={})=>{let{serverUrl:e=_,getOptions:o}=t,n=s=>({...x,...o?.()??{},...s??{}}),r=E(async()=>(await fetch(`${e}/health`,{method:"GET"})).ok,y);return {send:async function*(s,i){let d={...s,options:n(s.options)};yield*w({serverUrl:e,completedStatus:g},d,i);},resume:async function*(s,i,d){let a=S(d,s),l={content:a.content,prompt:a.prompt,options:a.options,sessionId:a.sessionId??s},c={...l,options:n(l.options)};yield "Resuming...",yield*w({serverUrl:e,completedStatus:g},c,i);},supportsResume:true,supportsFollowUp:true,checkConnection:r,undo:async()=>{try{await fetch(`${e}/undo`,{method:"POST"});}catch{}}}},P=async()=>{if(typeof window>"u")return;let t=R(),e=r=>{r.setAgent({provider:t,storage:sessionStorage});},o=window.__REACT_GRAB__;if(C(o)){e(o);return}window.addEventListener("react-grab:init",r=>{r instanceof CustomEvent&&C(r.detail)&&e(r.detail);},{once:true});let n=window.__REACT_GRAB__;C(n)&&e(n);};P();exports.attachAgent=P;exports.createClaudeAgentProvider=R;return exports;})({});
package/dist/client.js CHANGED
@@ -10,7 +10,7 @@ var parseSSEEvent = (eventBlock) => {
10
10
  }
11
11
  return { eventType, data };
12
12
  };
13
- async function* streamSSE(stream, signal) {
13
+ var streamSSE = async function* (stream, signal) {
14
14
  const reader = stream.getReader();
15
15
  const decoder = new TextDecoder();
16
16
  let buffer = "";
@@ -49,40 +49,54 @@ async function* streamSSE(stream, signal) {
49
49
  } catch {
50
50
  }
51
51
  }
52
- }
53
-
54
- // src/constants.ts
55
- var DEFAULT_PORT = 4567;
56
- var COMPLETED_STATUS = "Completed successfully";
57
-
58
- // src/client.ts
59
- var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
60
- var DEFAULT_OPTIONS = {
61
- systemPrompt: {
62
- type: "preset",
63
- preset: "claude_code",
64
- append: `You are helping a user make changes to a React component based on a selected element.
65
- The user has selected an element from their UI and wants you to help modify it.
66
- Provide clear, concise status updates as you work.`
67
- },
68
- model: "haiku",
69
- permissionMode: "bypassPermissions",
70
- maxTurns: 10
71
52
  };
72
- async function* streamFromServer(serverUrl, context, signal) {
53
+ var isRecord = (value) => typeof value === "object" && value !== null;
54
+ var getStoredAgentContext = (storage, sessionId, storageKey = STORAGE_KEY) => {
55
+ const rawSessions = storage.getItem(storageKey);
56
+ if (!rawSessions) throw new Error("No sessions to resume");
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(rawSessions);
60
+ } catch {
61
+ throw new Error("Failed to parse stored sessions");
62
+ }
63
+ if (!isRecord(parsed)) throw new Error("Invalid stored sessions");
64
+ const storedSession = parsed[sessionId];
65
+ if (!isRecord(storedSession)) {
66
+ throw new Error(`Session ${sessionId} not found`);
67
+ }
68
+ const context = storedSession.context;
69
+ if (!isRecord(context)) throw new Error(`Session ${sessionId} is invalid`);
70
+ const content = context.content;
71
+ const prompt = context.prompt;
72
+ if (typeof content !== "string" || typeof prompt !== "string") {
73
+ throw new Error(`Session ${sessionId} is invalid`);
74
+ }
75
+ const options = context.options;
76
+ const storedSessionId = context.sessionId;
77
+ return {
78
+ content,
79
+ prompt,
80
+ options,
81
+ sessionId: typeof storedSessionId === "string" ? storedSessionId : void 0
82
+ };
83
+ };
84
+ var streamAgentStatusFromServer = async function* (options, context, signal) {
73
85
  const startTime = Date.now();
74
86
  const sessionId = context.sessionId;
87
+ const pollIntervalMs = options.pollIntervalMs ?? 100;
88
+ const agentUrl = `${options.serverUrl}${options.agentPath ?? "/agent"}`;
75
89
  const handleAbort = () => {
76
- if (sessionId) {
77
- fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" }).catch(
78
- () => {
79
- }
80
- );
81
- }
90
+ if (!sessionId) return;
91
+ const abortPath = options.abortPath?.(sessionId) ?? `/abort/${sessionId}`;
92
+ fetch(`${options.serverUrl}${abortPath}`, { method: "POST" }).catch(
93
+ () => {
94
+ }
95
+ );
82
96
  };
83
97
  signal.addEventListener("abort", handleAbort);
84
98
  try {
85
- const response = await fetch(`${serverUrl}/agent`, {
99
+ const response = await fetch(agentUrl, {
86
100
  method: "POST",
87
101
  headers: { "Content-Type": "application/json" },
88
102
  body: JSON.stringify(context),
@@ -95,29 +109,29 @@ async function* streamFromServer(serverUrl, context, signal) {
95
109
  throw new Error("No response body");
96
110
  }
97
111
  const iterator = streamSSE(response.body, signal)[Symbol.asyncIterator]();
98
- let done = false;
112
+ let isDone = false;
99
113
  let pendingNext = iterator.next();
100
114
  let lastStatus = null;
101
- while (!done) {
115
+ while (!isDone) {
102
116
  const result = await Promise.race([
103
117
  pendingNext.then((iteratorResult) => ({
104
118
  type: "status",
105
119
  iteratorResult
106
120
  })),
107
121
  new Promise(
108
- (resolve) => setTimeout(() => resolve({ type: "timeout" }), 100)
122
+ (resolve) => setTimeout(() => resolve({ type: "timeout" }), pollIntervalMs)
109
123
  )
110
124
  ]);
111
125
  const elapsedSeconds = (Date.now() - startTime) / 1e3;
112
126
  if (result.type === "status") {
113
127
  const iteratorResult = result.iteratorResult;
114
- done = iteratorResult.done ?? false;
115
- if (!done && iteratorResult.value) {
128
+ isDone = iteratorResult.done ?? false;
129
+ if (!isDone && iteratorResult.value) {
116
130
  lastStatus = iteratorResult.value;
117
131
  pendingNext = iterator.next();
118
132
  }
119
133
  }
120
- if (lastStatus === COMPLETED_STATUS) {
134
+ if (lastStatus === options.completedStatus) {
121
135
  yield `Completed in ${elapsedSeconds.toFixed(1)}s`;
122
136
  } else if (lastStatus) {
123
137
  yield `${lastStatus} ${elapsedSeconds.toFixed(1)}s`;
@@ -128,58 +142,87 @@ async function* streamFromServer(serverUrl, context, signal) {
128
142
  } finally {
129
143
  signal.removeEventListener("abort", handleAbort);
130
144
  }
131
- }
145
+ };
146
+ var createCachedConnectionChecker = (checkConnection, ttlMs = CONNECTION_CHECK_TTL_MS) => {
147
+ let cache = null;
148
+ return async () => {
149
+ const now = Date.now();
150
+ if (cache && now - cache.timestamp < ttlMs) return cache.result;
151
+ try {
152
+ const result = await checkConnection();
153
+ cache = { result, timestamp: now };
154
+ return result;
155
+ } catch {
156
+ cache = { result: false, timestamp: now };
157
+ return false;
158
+ }
159
+ };
160
+ };
161
+
162
+ // src/constants.ts
163
+ var DEFAULT_PORT = 4567;
164
+ var COMPLETED_STATUS = "Completed successfully";
165
+
166
+ // src/client.ts
167
+ var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
168
+ var DEFAULT_OPTIONS = {
169
+ systemPrompt: {
170
+ type: "preset",
171
+ preset: "claude_code",
172
+ append: `You are helping a user make changes to a React component based on a selected element.
173
+ The user has selected an element from their UI and wants you to help modify it.
174
+ Provide clear, concise status updates as you work.`
175
+ },
176
+ model: "haiku",
177
+ permissionMode: "bypassPermissions",
178
+ maxTurns: 10
179
+ };
180
+ var isReactGrabApi = (value) => typeof value === "object" && value !== null && "setAgent" in value;
132
181
  var createClaudeAgentProvider = (providerOptions = {}) => {
133
182
  const { serverUrl = DEFAULT_SERVER_URL, getOptions } = providerOptions;
134
- let connectionCache = null;
135
183
  const mergeOptions = (contextOptions) => ({
136
184
  ...DEFAULT_OPTIONS,
137
185
  ...getOptions?.() ?? {},
138
186
  ...contextOptions ?? {}
139
187
  });
188
+ const checkConnection = createCachedConnectionChecker(async () => {
189
+ const response = await fetch(`${serverUrl}/health`, { method: "GET" });
190
+ return response.ok;
191
+ }, CONNECTION_CHECK_TTL_MS);
140
192
  return {
141
193
  send: async function* (context, signal) {
142
194
  const mergedContext = {
143
195
  ...context,
144
196
  options: mergeOptions(context.options)
145
197
  };
146
- yield* streamFromServer(serverUrl, mergedContext, signal);
198
+ yield* streamAgentStatusFromServer(
199
+ { serverUrl, completedStatus: COMPLETED_STATUS },
200
+ mergedContext,
201
+ signal
202
+ );
147
203
  },
148
204
  resume: async function* (sessionId, signal, storage) {
149
- const savedSessions = storage.getItem(STORAGE_KEY);
150
- if (!savedSessions) {
151
- throw new Error("No sessions to resume");
152
- }
153
- const sessionsObject = JSON.parse(savedSessions);
154
- const session = sessionsObject[sessionId];
155
- if (!session) {
156
- throw new Error(`Session ${sessionId} not found`);
157
- }
158
- const context = session.context;
205
+ const storedContext = getStoredAgentContext(storage, sessionId);
206
+ const context = {
207
+ content: storedContext.content,
208
+ prompt: storedContext.prompt,
209
+ options: storedContext.options,
210
+ sessionId: storedContext.sessionId ?? sessionId
211
+ };
159
212
  const mergedContext = {
160
213
  ...context,
161
214
  options: mergeOptions(context.options)
162
215
  };
163
216
  yield "Resuming...";
164
- yield* streamFromServer(serverUrl, mergedContext, signal);
217
+ yield* streamAgentStatusFromServer(
218
+ { serverUrl, completedStatus: COMPLETED_STATUS },
219
+ mergedContext,
220
+ signal
221
+ );
165
222
  },
166
223
  supportsResume: true,
167
224
  supportsFollowUp: true,
168
- checkConnection: async () => {
169
- const now = Date.now();
170
- if (connectionCache && now - connectionCache.timestamp < CONNECTION_CHECK_TTL_MS) {
171
- return connectionCache.result;
172
- }
173
- try {
174
- const response = await fetch(`${serverUrl}/health`, { method: "GET" });
175
- const result = response.ok;
176
- connectionCache = { result, timestamp: now };
177
- return result;
178
- } catch {
179
- connectionCache = { result: false, timestamp: now };
180
- return false;
181
- }
182
- },
225
+ checkConnection,
183
226
  undo: async () => {
184
227
  try {
185
228
  await fetch(`${serverUrl}/undo`, { method: "POST" });
@@ -191,24 +234,25 @@ var createClaudeAgentProvider = (providerOptions = {}) => {
191
234
  var attachAgent = async () => {
192
235
  if (typeof window === "undefined") return;
193
236
  const provider = createClaudeAgentProvider();
194
- const attach = (api2) => {
195
- api2.setAgent({ provider, storage: sessionStorage });
237
+ const attach = (api) => {
238
+ api.setAgent({ provider, storage: sessionStorage });
196
239
  };
197
- const api = window.__REACT_GRAB__;
198
- if (api) {
199
- attach(api);
240
+ const existingApi = window.__REACT_GRAB__;
241
+ if (isReactGrabApi(existingApi)) {
242
+ attach(existingApi);
200
243
  return;
201
244
  }
202
245
  window.addEventListener(
203
246
  "react-grab:init",
204
247
  (event) => {
205
- const customEvent = event;
206
- attach(customEvent.detail);
248
+ if (!(event instanceof CustomEvent)) return;
249
+ if (!isReactGrabApi(event.detail)) return;
250
+ attach(event.detail);
207
251
  },
208
252
  { once: true }
209
253
  );
210
254
  const apiAfterListener = window.__REACT_GRAB__;
211
- if (apiAfterListener) {
255
+ if (isReactGrabApi(apiAfterListener)) {
212
256
  attach(apiAfterListener);
213
257
  }
214
258
  };
package/dist/server.cjs CHANGED
@@ -18187,9 +18187,11 @@ Check that the command is executable: chmod +x $(which ${commandName})`;
18187
18187
  };
18188
18188
 
18189
18189
  // src/server.ts
18190
- var VERSION = "0.0.87";
18190
+ var VERSION = "0.0.89";
18191
18191
  try {
18192
- fetch(`https://www.react-grab.com/api/version?source=claude-code&t=${Date.now()}`).catch(() => {
18192
+ fetch(
18193
+ `https://www.react-grab.com/api/version?source=claude-code&t=${Date.now()}`
18194
+ ).catch(() => {
18193
18195
  });
18194
18196
  } catch {
18195
18197
  }
@@ -18206,6 +18208,75 @@ var claudeSessionMap = /* @__PURE__ */ new Map();
18206
18208
  var abortedSessions = /* @__PURE__ */ new Set();
18207
18209
  var lastClaudeSessionId;
18208
18210
  var isTextBlock = (block) => block.type === "text";
18211
+ var runAgent = async function* (prompt, options) {
18212
+ const sessionId = options?.sessionId;
18213
+ const isAborted2 = () => {
18214
+ if (options?.signal?.aborted) return true;
18215
+ if (sessionId && abortedSessions.has(sessionId)) return true;
18216
+ return false;
18217
+ };
18218
+ try {
18219
+ yield { type: "status", content: "Thinking\u2026" };
18220
+ const env = { ...process.env };
18221
+ delete env.NODE_OPTIONS;
18222
+ delete env.VSCODE_INSPECTOR_OPTIONS;
18223
+ const claudeSessionId = sessionId ? claudeSessionMap.get(sessionId) : void 0;
18224
+ const queryResult = query({
18225
+ prompt,
18226
+ options: {
18227
+ pathToClaudeCodeExecutable: resolveClaudePath(),
18228
+ includePartialMessages: true,
18229
+ env,
18230
+ ...options,
18231
+ cwd: options?.cwd ?? process.env.REACT_GRAB_CWD ?? process.cwd(),
18232
+ ...claudeSessionId ? { resume: claudeSessionId } : {}
18233
+ }
18234
+ });
18235
+ let capturedClaudeSessionId;
18236
+ for await (const message of queryResult) {
18237
+ if (isAborted2()) break;
18238
+ if (!capturedClaudeSessionId && message.session_id) {
18239
+ capturedClaudeSessionId = message.session_id;
18240
+ }
18241
+ if (message.type === "assistant") {
18242
+ const textContent = message.message.content.filter(isTextBlock).map((block) => block.text).join(" ");
18243
+ if (textContent) {
18244
+ yield { type: "status", content: textContent };
18245
+ }
18246
+ }
18247
+ if (message.type === "result") {
18248
+ yield {
18249
+ type: "status",
18250
+ content: message.subtype === "success" ? COMPLETED_STATUS : "Task finished"
18251
+ };
18252
+ }
18253
+ }
18254
+ if (!isAborted2() && capturedClaudeSessionId) {
18255
+ if (sessionId) {
18256
+ claudeSessionMap.set(sessionId, capturedClaudeSessionId);
18257
+ }
18258
+ lastClaudeSessionId = capturedClaudeSessionId;
18259
+ }
18260
+ if (!isAborted2()) {
18261
+ yield { type: "done", content: "" };
18262
+ }
18263
+ } catch (error) {
18264
+ if (!isAborted2()) {
18265
+ const errorMessage = error instanceof Error ? formatSpawnError(error, "claude") : "Unknown error";
18266
+ const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
18267
+ const fullError = stderr && stderr.trim() ? `${errorMessage}
18268
+
18269
+ stderr:
18270
+ ${stderr.trim()}` : errorMessage;
18271
+ yield { type: "error", content: fullError };
18272
+ yield { type: "done", content: "" };
18273
+ }
18274
+ } finally {
18275
+ if (sessionId) {
18276
+ abortedSessions.delete(sessionId);
18277
+ }
18278
+ }
18279
+ };
18209
18280
  var createServer = () => {
18210
18281
  const app = new Hono2();
18211
18282
  app.use("*", cors());
@@ -18218,67 +18289,17 @@ var createServer = () => {
18218
18289
 
18219
18290
  ${content}`;
18220
18291
  return streamSSE(context, async (stream2) => {
18221
- const isAborted2 = () => sessionId && abortedSessions.has(sessionId);
18222
- try {
18223
- await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
18224
- const env = { ...process.env };
18225
- delete env.NODE_OPTIONS;
18226
- delete env.VSCODE_INSPECTOR_OPTIONS;
18227
- const queryResult = query({
18228
- prompt: userPrompt,
18229
- options: {
18230
- pathToClaudeCodeExecutable: resolveClaudePath(),
18231
- includePartialMessages: true,
18232
- env,
18233
- ...options,
18234
- cwd: options?.cwd ?? process.env.REACT_GRAB_CWD ?? process.cwd(),
18235
- ...isFollowUp && claudeSessionId ? { resume: claudeSessionId } : {}
18236
- }
18237
- });
18238
- let capturedClaudeSessionId;
18239
- for await (const message of queryResult) {
18240
- if (isAborted2()) break;
18241
- if (!capturedClaudeSessionId && message.session_id) {
18242
- capturedClaudeSessionId = message.session_id;
18243
- }
18244
- if (message.type === "assistant") {
18245
- const textContent = message.message.content.filter(isTextBlock).map((block) => block.text).join(" ");
18246
- if (textContent) {
18247
- await stream2.writeSSE({ data: textContent, event: "status" });
18248
- }
18249
- }
18250
- if (message.type === "result") {
18251
- await stream2.writeSSE({
18252
- data: message.subtype === "success" ? COMPLETED_STATUS : "Task finished",
18253
- event: "status"
18254
- });
18255
- }
18256
- }
18257
- if (!isAborted2() && capturedClaudeSessionId) {
18258
- if (sessionId) {
18259
- claudeSessionMap.set(sessionId, capturedClaudeSessionId);
18260
- }
18261
- lastClaudeSessionId = capturedClaudeSessionId;
18262
- }
18263
- if (!isAborted2()) {
18264
- await stream2.writeSSE({ data: "", event: "done" });
18265
- }
18266
- } catch (error) {
18267
- if (!isAborted2()) {
18268
- const errorMessage = error instanceof Error ? formatSpawnError(error, "claude") : "Unknown error";
18269
- const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
18270
- const fullError = stderr && stderr.trim() ? `${errorMessage}
18271
-
18272
- stderr:
18273
- ${stderr.trim()}` : errorMessage;
18292
+ for await (const message of runAgent(userPrompt, {
18293
+ ...options,
18294
+ sessionId
18295
+ })) {
18296
+ if (message.type === "error") {
18274
18297
  await stream2.writeSSE({
18275
- data: `Error: ${fullError}`,
18298
+ data: `Error: ${message.content}`,
18276
18299
  event: "error"
18277
18300
  });
18278
- }
18279
- } finally {
18280
- if (sessionId) {
18281
- abortedSessions.delete(sessionId);
18301
+ } else {
18302
+ await stream2.writeSSE({ data: message.content, event: message.type });
18282
18303
  }
18283
18304
  }
18284
18305
  });
@@ -18334,4 +18355,5 @@ if ((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filen
18334
18355
  }
18335
18356
 
18336
18357
  exports.createServer = createServer;
18358
+ exports.runAgent = runAgent;
18337
18359
  exports.startServer = startServer;
package/dist/server.d.cts CHANGED
@@ -1,7 +1,12 @@
1
1
  import * as hono_types from 'hono/types';
2
2
  import { Hono } from 'hono';
3
+ import { Options } from '@anthropic-ai/claude-agent-sdk';
4
+ import { AgentCoreOptions, AgentMessage } from '@react-grab/utils/server';
3
5
 
6
+ interface ClaudeAgentOptions extends AgentCoreOptions, Omit<Options, "cwd"> {
7
+ }
8
+ declare const runAgent: (prompt: string, options?: ClaudeAgentOptions) => AsyncGenerator<AgentMessage>;
4
9
  declare const createServer: () => Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
5
10
  declare const startServer: (port?: number) => Promise<void>;
6
11
 
7
- export { createServer, startServer };
12
+ export { type ClaudeAgentOptions, createServer, runAgent, startServer };
package/dist/server.d.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import * as hono_types from 'hono/types';
2
2
  import { Hono } from 'hono';
3
+ import { Options } from '@anthropic-ai/claude-agent-sdk';
4
+ import { AgentCoreOptions, AgentMessage } from '@react-grab/utils/server';
3
5
 
6
+ interface ClaudeAgentOptions extends AgentCoreOptions, Omit<Options, "cwd"> {
7
+ }
8
+ declare const runAgent: (prompt: string, options?: ClaudeAgentOptions) => AsyncGenerator<AgentMessage>;
4
9
  declare const createServer: () => Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
5
10
  declare const startServer: (port?: number) => Promise<void>;
6
11
 
7
- export { createServer, startServer };
12
+ export { type ClaudeAgentOptions, createServer, runAgent, startServer };
package/dist/server.js CHANGED
@@ -18158,9 +18158,11 @@ Check that the command is executable: chmod +x $(which ${commandName})`;
18158
18158
  };
18159
18159
 
18160
18160
  // src/server.ts
18161
- var VERSION = "0.0.87";
18161
+ var VERSION = "0.0.89";
18162
18162
  try {
18163
- fetch(`https://www.react-grab.com/api/version?source=claude-code&t=${Date.now()}`).catch(() => {
18163
+ fetch(
18164
+ `https://www.react-grab.com/api/version?source=claude-code&t=${Date.now()}`
18165
+ ).catch(() => {
18164
18166
  });
18165
18167
  } catch {
18166
18168
  }
@@ -18177,6 +18179,75 @@ var claudeSessionMap = /* @__PURE__ */ new Map();
18177
18179
  var abortedSessions = /* @__PURE__ */ new Set();
18178
18180
  var lastClaudeSessionId;
18179
18181
  var isTextBlock = (block) => block.type === "text";
18182
+ var runAgent = async function* (prompt, options) {
18183
+ const sessionId = options?.sessionId;
18184
+ const isAborted2 = () => {
18185
+ if (options?.signal?.aborted) return true;
18186
+ if (sessionId && abortedSessions.has(sessionId)) return true;
18187
+ return false;
18188
+ };
18189
+ try {
18190
+ yield { type: "status", content: "Thinking\u2026" };
18191
+ const env = { ...process.env };
18192
+ delete env.NODE_OPTIONS;
18193
+ delete env.VSCODE_INSPECTOR_OPTIONS;
18194
+ const claudeSessionId = sessionId ? claudeSessionMap.get(sessionId) : void 0;
18195
+ const queryResult = query({
18196
+ prompt,
18197
+ options: {
18198
+ pathToClaudeCodeExecutable: resolveClaudePath(),
18199
+ includePartialMessages: true,
18200
+ env,
18201
+ ...options,
18202
+ cwd: options?.cwd ?? process.env.REACT_GRAB_CWD ?? process.cwd(),
18203
+ ...claudeSessionId ? { resume: claudeSessionId } : {}
18204
+ }
18205
+ });
18206
+ let capturedClaudeSessionId;
18207
+ for await (const message of queryResult) {
18208
+ if (isAborted2()) break;
18209
+ if (!capturedClaudeSessionId && message.session_id) {
18210
+ capturedClaudeSessionId = message.session_id;
18211
+ }
18212
+ if (message.type === "assistant") {
18213
+ const textContent = message.message.content.filter(isTextBlock).map((block) => block.text).join(" ");
18214
+ if (textContent) {
18215
+ yield { type: "status", content: textContent };
18216
+ }
18217
+ }
18218
+ if (message.type === "result") {
18219
+ yield {
18220
+ type: "status",
18221
+ content: message.subtype === "success" ? COMPLETED_STATUS : "Task finished"
18222
+ };
18223
+ }
18224
+ }
18225
+ if (!isAborted2() && capturedClaudeSessionId) {
18226
+ if (sessionId) {
18227
+ claudeSessionMap.set(sessionId, capturedClaudeSessionId);
18228
+ }
18229
+ lastClaudeSessionId = capturedClaudeSessionId;
18230
+ }
18231
+ if (!isAborted2()) {
18232
+ yield { type: "done", content: "" };
18233
+ }
18234
+ } catch (error) {
18235
+ if (!isAborted2()) {
18236
+ const errorMessage = error instanceof Error ? formatSpawnError(error, "claude") : "Unknown error";
18237
+ const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
18238
+ const fullError = stderr && stderr.trim() ? `${errorMessage}
18239
+
18240
+ stderr:
18241
+ ${stderr.trim()}` : errorMessage;
18242
+ yield { type: "error", content: fullError };
18243
+ yield { type: "done", content: "" };
18244
+ }
18245
+ } finally {
18246
+ if (sessionId) {
18247
+ abortedSessions.delete(sessionId);
18248
+ }
18249
+ }
18250
+ };
18180
18251
  var createServer = () => {
18181
18252
  const app = new Hono2();
18182
18253
  app.use("*", cors());
@@ -18189,67 +18260,17 @@ var createServer = () => {
18189
18260
 
18190
18261
  ${content}`;
18191
18262
  return streamSSE(context, async (stream2) => {
18192
- const isAborted2 = () => sessionId && abortedSessions.has(sessionId);
18193
- try {
18194
- await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
18195
- const env = { ...process.env };
18196
- delete env.NODE_OPTIONS;
18197
- delete env.VSCODE_INSPECTOR_OPTIONS;
18198
- const queryResult = query({
18199
- prompt: userPrompt,
18200
- options: {
18201
- pathToClaudeCodeExecutable: resolveClaudePath(),
18202
- includePartialMessages: true,
18203
- env,
18204
- ...options,
18205
- cwd: options?.cwd ?? process.env.REACT_GRAB_CWD ?? process.cwd(),
18206
- ...isFollowUp && claudeSessionId ? { resume: claudeSessionId } : {}
18207
- }
18208
- });
18209
- let capturedClaudeSessionId;
18210
- for await (const message of queryResult) {
18211
- if (isAborted2()) break;
18212
- if (!capturedClaudeSessionId && message.session_id) {
18213
- capturedClaudeSessionId = message.session_id;
18214
- }
18215
- if (message.type === "assistant") {
18216
- const textContent = message.message.content.filter(isTextBlock).map((block) => block.text).join(" ");
18217
- if (textContent) {
18218
- await stream2.writeSSE({ data: textContent, event: "status" });
18219
- }
18220
- }
18221
- if (message.type === "result") {
18222
- await stream2.writeSSE({
18223
- data: message.subtype === "success" ? COMPLETED_STATUS : "Task finished",
18224
- event: "status"
18225
- });
18226
- }
18227
- }
18228
- if (!isAborted2() && capturedClaudeSessionId) {
18229
- if (sessionId) {
18230
- claudeSessionMap.set(sessionId, capturedClaudeSessionId);
18231
- }
18232
- lastClaudeSessionId = capturedClaudeSessionId;
18233
- }
18234
- if (!isAborted2()) {
18235
- await stream2.writeSSE({ data: "", event: "done" });
18236
- }
18237
- } catch (error) {
18238
- if (!isAborted2()) {
18239
- const errorMessage = error instanceof Error ? formatSpawnError(error, "claude") : "Unknown error";
18240
- const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
18241
- const fullError = stderr && stderr.trim() ? `${errorMessage}
18242
-
18243
- stderr:
18244
- ${stderr.trim()}` : errorMessage;
18263
+ for await (const message of runAgent(userPrompt, {
18264
+ ...options,
18265
+ sessionId
18266
+ })) {
18267
+ if (message.type === "error") {
18245
18268
  await stream2.writeSSE({
18246
- data: `Error: ${fullError}`,
18269
+ data: `Error: ${message.content}`,
18247
18270
  event: "error"
18248
18271
  });
18249
- }
18250
- } finally {
18251
- if (sessionId) {
18252
- abortedSessions.delete(sessionId);
18272
+ } else {
18273
+ await stream2.writeSSE({ data: message.content, event: message.type });
18253
18274
  }
18254
18275
  }
18255
18276
  });
@@ -18304,4 +18325,4 @@ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
18304
18325
  startServer(DEFAULT_PORT).catch(console.error);
18305
18326
  }
18306
18327
 
18307
- export { createServer, startServer };
18328
+ export { createServer, runAgent, startServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-grab/claude-code",
3
- "version": "0.0.87",
3
+ "version": "0.0.89",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "react-grab-claude-code": "./dist/cli.cjs"
@@ -26,7 +26,7 @@
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.10.7",
28
28
  "tsup": "^8.4.0",
29
- "@react-grab/utils": "0.0.87"
29
+ "@react-grab/utils": "0.0.89"
30
30
  },
31
31
  "dependencies": {
32
32
  "@anthropic-ai/claude-agent-sdk": "^0.1.0",
@@ -35,7 +35,7 @@
35
35
  "fkill": "^9.0.0",
36
36
  "hono": "^4.0.0",
37
37
  "picocolors": "^1.1.1",
38
- "react-grab": "0.0.87"
38
+ "react-grab": "0.0.89"
39
39
  },
40
40
  "scripts": {
41
41
  "dev": "tsup --watch",