@react-grab/claude-code 0.0.88 → 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 +1 -1
- package/dist/cli.js +1 -1
- package/dist/client.cjs +115 -71
- package/dist/client.global.js +4 -4
- package/dist/client.js +115 -71
- package/dist/server.cjs +82 -60
- package/dist/server.d.cts +6 -1
- package/dist/server.d.ts +6 -1
- package/dist/server.js +82 -61
- package/package.json +3 -3
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.
|
|
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.
|
|
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*
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
114
|
+
let isDone = false;
|
|
101
115
|
let pendingNext = iterator.next();
|
|
102
116
|
let lastStatus = null;
|
|
103
|
-
while (!
|
|
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" }),
|
|
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
|
-
|
|
117
|
-
if (!
|
|
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 ===
|
|
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*
|
|
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
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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*
|
|
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
|
|
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 = (
|
|
197
|
-
|
|
239
|
+
const attach = (api) => {
|
|
240
|
+
api.setAgent({ provider, storage: sessionStorage });
|
|
198
241
|
};
|
|
199
|
-
const
|
|
200
|
-
if (
|
|
201
|
-
attach(
|
|
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
|
-
|
|
208
|
-
|
|
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
|
};
|
package/dist/client.global.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var ReactGrabClaudeCode=(function(exports){'use strict';var
|
|
2
|
-
`))
|
|
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:
|
|
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}
|
|
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*
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
112
|
+
let isDone = false;
|
|
99
113
|
let pendingNext = iterator.next();
|
|
100
114
|
let lastStatus = null;
|
|
101
|
-
while (!
|
|
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" }),
|
|
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
|
-
|
|
115
|
-
if (!
|
|
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 ===
|
|
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*
|
|
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
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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*
|
|
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
|
|
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 = (
|
|
195
|
-
|
|
237
|
+
const attach = (api) => {
|
|
238
|
+
api.setAgent({ provider, storage: sessionStorage });
|
|
196
239
|
};
|
|
197
|
-
const
|
|
198
|
-
if (
|
|
199
|
-
attach(
|
|
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
|
-
|
|
206
|
-
|
|
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.
|
|
18190
|
+
var VERSION = "0.0.89";
|
|
18191
18191
|
try {
|
|
18192
|
-
fetch(
|
|
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
|
-
|
|
18222
|
-
|
|
18223
|
-
|
|
18224
|
-
|
|
18225
|
-
|
|
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: ${
|
|
18298
|
+
data: `Error: ${message.content}`,
|
|
18276
18299
|
event: "error"
|
|
18277
18300
|
});
|
|
18278
|
-
}
|
|
18279
|
-
|
|
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.
|
|
18161
|
+
var VERSION = "0.0.89";
|
|
18162
18162
|
try {
|
|
18163
|
-
fetch(
|
|
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
|
-
|
|
18193
|
-
|
|
18194
|
-
|
|
18195
|
-
|
|
18196
|
-
|
|
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: ${
|
|
18269
|
+
data: `Error: ${message.content}`,
|
|
18247
18270
|
event: "error"
|
|
18248
18271
|
});
|
|
18249
|
-
}
|
|
18250
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
38
|
+
"react-grab": "0.0.89"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"dev": "tsup --watch",
|