@react-grab/claude-code 0.0.78 → 0.0.80

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/client.js CHANGED
@@ -67,19 +67,33 @@ async function* streamSSE(stream, signal) {
67
67
  }
68
68
  }
69
69
  async function* streamFromServer(serverUrl, context, signal) {
70
- const response = await fetch(`${serverUrl}/agent`, {
71
- method: "POST",
72
- headers: { "Content-Type": "application/json" },
73
- body: JSON.stringify(context),
74
- signal
75
- });
76
- if (!response.ok) {
77
- throw new Error(`Server error: ${response.status}`);
78
- }
79
- if (!response.body) {
80
- throw new Error("No response body");
70
+ const sessionId = context.sessionId;
71
+ const handleAbort = () => {
72
+ if (sessionId) {
73
+ fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" }).catch(
74
+ () => {
75
+ }
76
+ );
77
+ }
78
+ };
79
+ signal.addEventListener("abort", handleAbort);
80
+ try {
81
+ const response = await fetch(`${serverUrl}/agent`, {
82
+ method: "POST",
83
+ headers: { "Content-Type": "application/json" },
84
+ body: JSON.stringify(context),
85
+ signal
86
+ });
87
+ if (!response.ok) {
88
+ throw new Error(`Server error: ${response.status}`);
89
+ }
90
+ if (!response.body) {
91
+ throw new Error("No response body");
92
+ }
93
+ yield* streamSSE(response.body, signal);
94
+ } finally {
95
+ signal.removeEventListener("abort", handleAbort);
81
96
  }
82
- yield* streamSSE(response.body, signal);
83
97
  }
84
98
  var createClaudeAgentProvider = (providerOptions = {}) => {
85
99
  const { serverUrl = DEFAULT_SERVER_URL, getOptions } = providerOptions;
@@ -116,6 +130,7 @@ var createClaudeAgentProvider = (providerOptions = {}) => {
116
130
  yield* streamFromServer(serverUrl, mergedContext, signal);
117
131
  },
118
132
  supportsResume: true,
133
+ supportsFollowUp: true,
119
134
  checkConnection: async () => {
120
135
  const now = Date.now();
121
136
  if (connectionCache && now - connectionCache.timestamp < CONNECTION_CHECK_TTL_MS) {
@@ -142,19 +157,26 @@ var createClaudeAgentProvider = (providerOptions = {}) => {
142
157
  var attachAgent = async () => {
143
158
  if (typeof window === "undefined") return;
144
159
  const provider = createClaudeAgentProvider();
160
+ const attach = (api2) => {
161
+ api2.setAgent({ provider, storage: sessionStorage });
162
+ };
145
163
  const api = window.__REACT_GRAB__;
146
164
  if (api) {
147
- api.setAgent({ provider, storage: sessionStorage });
165
+ attach(api);
148
166
  return;
149
167
  }
150
168
  window.addEventListener(
151
169
  "react-grab:init",
152
170
  (event) => {
153
171
  const customEvent = event;
154
- customEvent.detail.setAgent({ provider, storage: sessionStorage });
172
+ attach(customEvent.detail);
155
173
  },
156
174
  { once: true }
157
175
  );
176
+ const apiAfterListener = window.__REACT_GRAB__;
177
+ if (apiAfterListener) {
178
+ attach(apiAfterListener);
179
+ }
158
180
  };
159
181
  attachAgent();
160
182
 
package/dist/server.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var net = require('net');
3
+ var child_process = require('child_process');
4
4
  var url = require('url');
5
5
  var http = require('http');
6
6
  var http2 = require('http2');
@@ -8,7 +8,6 @@ var stream = require('stream');
8
8
  var crypto = require('crypto');
9
9
  var path = require('path');
10
10
  var events = require('events');
11
- var child_process = require('child_process');
12
11
  var readline = require('readline');
13
12
  var fs = require('fs');
14
13
  var promises = require('fs/promises');
@@ -36,7 +35,6 @@ function _interopNamespace(e) {
36
35
  return Object.freeze(n);
37
36
  }
38
37
 
39
- var net__default = /*#__PURE__*/_interopDefault(net);
40
38
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
41
39
  var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
42
40
 
@@ -8762,8 +8760,7 @@ var ProcessTransport = class {
8762
8760
  env.CLAUDE_CODE_ENTRYPOINT = "sdk-ts";
8763
8761
  }
8764
8762
  const fs2 = getFsImplementation();
8765
- const isPathCommand = !pathToClaudeCodeExecutable.includes('/') && !pathToClaudeCodeExecutable.includes('\\');
8766
- if (!isPathCommand && !fs2.existsSync(pathToClaudeCodeExecutable)) {
8763
+ if (!fs2.existsSync(pathToClaudeCodeExecutable)) {
8767
8764
  const errorMessage = isNativeBinary(pathToClaudeCodeExecutable) ? `Claude Code native binary not found at ${pathToClaudeCodeExecutable}. Please ensure Claude Code is installed via native installer or specify a valid path with options.pathToClaudeCodeExecutable.` : `Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`;
8768
8765
  throw new ReferenceError(errorMessage);
8769
8766
  }
@@ -14577,7 +14574,12 @@ function query({
14577
14574
  } else if (systemPrompt.type === "preset") {
14578
14575
  appendSystemPrompt = systemPrompt.append;
14579
14576
  }
14580
- let pathToClaudeCodeExecutable = rest.pathToClaudeCodeExecutable || "claude";
14577
+ let pathToClaudeCodeExecutable = rest.pathToClaudeCodeExecutable;
14578
+ if (!pathToClaudeCodeExecutable) {
14579
+ const filename = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('server.cjs', document.baseURI).href)));
14580
+ const dirname2 = path.join(filename, "..");
14581
+ pathToClaudeCodeExecutable = path.join(dirname2, "cli.js");
14582
+ }
14581
14583
  process.env.CLAUDE_AGENT_SDK_VERSION = "0.1.46";
14582
14584
  const {
14583
14585
  abortController = createAbortController(),
@@ -14696,30 +14698,55 @@ function query({
14696
14698
  var DEFAULT_PORT = 4567;
14697
14699
 
14698
14700
  // src/server.ts
14699
- var VERSION = "0.0.78";
14701
+ var VERSION = "0.0.80";
14702
+ var resolveClaudePath = () => {
14703
+ const command = process.platform === "win32" ? "where claude" : "which claude";
14704
+ try {
14705
+ const result = child_process.execSync(command, { encoding: "utf8" }).trim();
14706
+ return result.split("\n")[0];
14707
+ } catch {
14708
+ return "claude";
14709
+ }
14710
+ };
14711
+ var claudeSessionMap = /* @__PURE__ */ new Map();
14712
+ var abortedSessions = /* @__PURE__ */ new Set();
14713
+ var lastClaudeSessionId;
14700
14714
  var isTextBlock = (block) => block.type === "text";
14701
14715
  var createServer = () => {
14702
14716
  const app = new Hono2();
14703
- app.use("/*", cors());
14717
+ app.use("*", cors());
14704
14718
  app.post("/agent", async (context) => {
14705
14719
  const body = await context.req.json();
14706
- const { content, prompt, options } = body;
14707
- const fullPrompt = `${prompt}
14720
+ const { content, prompt, options, sessionId } = body;
14721
+ const claudeSessionId = sessionId ? claudeSessionMap.get(sessionId) : void 0;
14722
+ const isFollowUp = Boolean(claudeSessionId);
14723
+ const userPrompt = isFollowUp ? prompt : `${prompt}
14708
14724
 
14709
14725
  ${content}`;
14710
14726
  return streamSSE(context, async (stream2) => {
14727
+ const isAborted2 = () => sessionId && abortedSessions.has(sessionId);
14711
14728
  try {
14712
14729
  await stream2.writeSSE({ data: "Thinking...", event: "status" });
14730
+ const env = { ...process.env };
14731
+ delete env.NODE_OPTIONS;
14732
+ delete env.VSCODE_INSPECTOR_OPTIONS;
14713
14733
  const queryResult = query({
14714
- prompt: fullPrompt,
14734
+ prompt: userPrompt,
14715
14735
  options: {
14716
- pathToClaudeCodeExecutable: "claude",
14736
+ pathToClaudeCodeExecutable: resolveClaudePath(),
14717
14737
  cwd: process.cwd(),
14718
14738
  includePartialMessages: true,
14719
- ...options
14739
+ env,
14740
+ ...options,
14741
+ ...isFollowUp && claudeSessionId ? { resume: claudeSessionId } : {}
14720
14742
  }
14721
14743
  });
14744
+ let capturedClaudeSessionId;
14722
14745
  for await (const message of queryResult) {
14746
+ if (isAborted2()) break;
14747
+ if (!capturedClaudeSessionId && message.session_id) {
14748
+ capturedClaudeSessionId = message.session_id;
14749
+ }
14723
14750
  if (message.type === "assistant") {
14724
14751
  const textContent = message.message.content.filter(isTextBlock).map((block) => block.text).join(" ");
14725
14752
  if (textContent) {
@@ -14733,37 +14760,77 @@ ${content}`;
14733
14760
  });
14734
14761
  }
14735
14762
  }
14736
- await stream2.writeSSE({ data: "", event: "done" });
14763
+ if (!isAborted2() && capturedClaudeSessionId) {
14764
+ lastClaudeSessionId = capturedClaudeSessionId;
14765
+ if (sessionId) {
14766
+ claudeSessionMap.set(sessionId, capturedClaudeSessionId);
14767
+ }
14768
+ }
14769
+ if (!isAborted2()) {
14770
+ await stream2.writeSSE({ data: "", event: "done" });
14771
+ }
14737
14772
  } catch (error) {
14738
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
14739
- await stream2.writeSSE({
14740
- data: `Error: ${errorMessage}`,
14741
- event: "error"
14742
- });
14773
+ if (!isAborted2()) {
14774
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
14775
+ const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
14776
+ const fullError = stderr && stderr.trim() ? `${errorMessage}
14777
+
14778
+ stderr:
14779
+ ${stderr.trim()}` : errorMessage;
14780
+ await stream2.writeSSE({
14781
+ data: `Error: ${fullError}`,
14782
+ event: "error"
14783
+ });
14784
+ }
14785
+ } finally {
14786
+ if (sessionId) {
14787
+ abortedSessions.delete(sessionId);
14788
+ }
14743
14789
  }
14744
14790
  });
14745
14791
  });
14792
+ app.post("/abort/:sessionId", (context) => {
14793
+ const { sessionId } = context.req.param();
14794
+ abortedSessions.add(sessionId);
14795
+ return context.json({ status: "ok" });
14796
+ });
14797
+ app.post("/undo", async (context) => {
14798
+ if (!lastClaudeSessionId) {
14799
+ return context.json({ status: "error", message: "No session to undo" });
14800
+ }
14801
+ try {
14802
+ const env = { ...process.env };
14803
+ delete env.NODE_OPTIONS;
14804
+ delete env.VSCODE_INSPECTOR_OPTIONS;
14805
+ const queryResult = query({
14806
+ prompt: "/rewind",
14807
+ options: {
14808
+ pathToClaudeCodeExecutable: resolveClaudePath(),
14809
+ cwd: process.cwd(),
14810
+ env,
14811
+ resume: lastClaudeSessionId
14812
+ }
14813
+ });
14814
+ for await (const message of queryResult) {
14815
+ if (message.type === "result") break;
14816
+ }
14817
+ return context.json({ status: "ok" });
14818
+ } catch (error) {
14819
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
14820
+ return context.json({ status: "error", message: errorMessage });
14821
+ }
14822
+ });
14746
14823
  app.get("/health", (context) => {
14747
14824
  return context.json({ status: "ok", provider: "claude" });
14748
14825
  });
14749
14826
  return app;
14750
14827
  };
14751
- var isPortInUse = (port) => new Promise((resolve) => {
14752
- const server = net__default.default.createServer();
14753
- server.once("error", () => resolve(true));
14754
- server.once("listening", () => {
14755
- server.close();
14756
- resolve(false);
14757
- });
14758
- server.listen(port);
14759
- });
14760
14828
  var startServer = async (port = DEFAULT_PORT) => {
14761
- if (await isPortInUse(port)) {
14762
- return;
14763
- }
14764
14829
  const app = createServer();
14765
14830
  serve({ fetch: app.fetch, port });
14766
- console.log(`${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Claude Code)")}`);
14831
+ console.log(
14832
+ `${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Claude Code)")}`
14833
+ );
14767
14834
  console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${port}`)}`);
14768
14835
  };
14769
14836
  if ((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('server.cjs', document.baseURI).href)) === url.pathToFileURL(process.argv[1]).href) {
package/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import net from 'net';
1
+ import { execSync, spawn } from 'child_process';
2
2
  import { pathToFileURL, fileURLToPath } from 'url';
3
3
  import { createServer as createServer$1 } from 'http';
4
4
  import { Http2ServerRequest } from 'http2';
@@ -6,7 +6,6 @@ import { Readable } from 'stream';
6
6
  import crypto, { randomUUID } from 'crypto';
7
7
  import { dirname, join } from 'path';
8
8
  import { setMaxListeners } from 'events';
9
- import { spawn } from 'child_process';
10
9
  import { createInterface } from 'readline';
11
10
  import * as fs from 'fs';
12
11
  import { realpathSync } from 'fs';
@@ -8736,8 +8735,7 @@ var ProcessTransport = class {
8736
8735
  env.CLAUDE_CODE_ENTRYPOINT = "sdk-ts";
8737
8736
  }
8738
8737
  const fs2 = getFsImplementation();
8739
- const isPathCommand = !pathToClaudeCodeExecutable.includes('/') && !pathToClaudeCodeExecutable.includes('\\');
8740
- if (!isPathCommand && !fs2.existsSync(pathToClaudeCodeExecutable)) {
8738
+ if (!fs2.existsSync(pathToClaudeCodeExecutable)) {
8741
8739
  const errorMessage = isNativeBinary(pathToClaudeCodeExecutable) ? `Claude Code native binary not found at ${pathToClaudeCodeExecutable}. Please ensure Claude Code is installed via native installer or specify a valid path with options.pathToClaudeCodeExecutable.` : `Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`;
8742
8740
  throw new ReferenceError(errorMessage);
8743
8741
  }
@@ -14551,7 +14549,12 @@ function query({
14551
14549
  } else if (systemPrompt.type === "preset") {
14552
14550
  appendSystemPrompt = systemPrompt.append;
14553
14551
  }
14554
- let pathToClaudeCodeExecutable = rest.pathToClaudeCodeExecutable || "claude";
14552
+ let pathToClaudeCodeExecutable = rest.pathToClaudeCodeExecutable;
14553
+ if (!pathToClaudeCodeExecutable) {
14554
+ const filename = fileURLToPath(import.meta.url);
14555
+ const dirname2 = join(filename, "..");
14556
+ pathToClaudeCodeExecutable = join(dirname2, "cli.js");
14557
+ }
14555
14558
  process.env.CLAUDE_AGENT_SDK_VERSION = "0.1.46";
14556
14559
  const {
14557
14560
  abortController = createAbortController(),
@@ -14670,30 +14673,55 @@ function query({
14670
14673
  var DEFAULT_PORT = 4567;
14671
14674
 
14672
14675
  // src/server.ts
14673
- var VERSION = "0.0.78";
14676
+ var VERSION = "0.0.80";
14677
+ var resolveClaudePath = () => {
14678
+ const command = process.platform === "win32" ? "where claude" : "which claude";
14679
+ try {
14680
+ const result = execSync(command, { encoding: "utf8" }).trim();
14681
+ return result.split("\n")[0];
14682
+ } catch {
14683
+ return "claude";
14684
+ }
14685
+ };
14686
+ var claudeSessionMap = /* @__PURE__ */ new Map();
14687
+ var abortedSessions = /* @__PURE__ */ new Set();
14688
+ var lastClaudeSessionId;
14674
14689
  var isTextBlock = (block) => block.type === "text";
14675
14690
  var createServer = () => {
14676
14691
  const app = new Hono2();
14677
- app.use("/*", cors());
14692
+ app.use("*", cors());
14678
14693
  app.post("/agent", async (context) => {
14679
14694
  const body = await context.req.json();
14680
- const { content, prompt, options } = body;
14681
- const fullPrompt = `${prompt}
14695
+ const { content, prompt, options, sessionId } = body;
14696
+ const claudeSessionId = sessionId ? claudeSessionMap.get(sessionId) : void 0;
14697
+ const isFollowUp = Boolean(claudeSessionId);
14698
+ const userPrompt = isFollowUp ? prompt : `${prompt}
14682
14699
 
14683
14700
  ${content}`;
14684
14701
  return streamSSE(context, async (stream2) => {
14702
+ const isAborted2 = () => sessionId && abortedSessions.has(sessionId);
14685
14703
  try {
14686
14704
  await stream2.writeSSE({ data: "Thinking...", event: "status" });
14705
+ const env = { ...process.env };
14706
+ delete env.NODE_OPTIONS;
14707
+ delete env.VSCODE_INSPECTOR_OPTIONS;
14687
14708
  const queryResult = query({
14688
- prompt: fullPrompt,
14709
+ prompt: userPrompt,
14689
14710
  options: {
14690
- pathToClaudeCodeExecutable: "claude",
14711
+ pathToClaudeCodeExecutable: resolveClaudePath(),
14691
14712
  cwd: process.cwd(),
14692
14713
  includePartialMessages: true,
14693
- ...options
14714
+ env,
14715
+ ...options,
14716
+ ...isFollowUp && claudeSessionId ? { resume: claudeSessionId } : {}
14694
14717
  }
14695
14718
  });
14719
+ let capturedClaudeSessionId;
14696
14720
  for await (const message of queryResult) {
14721
+ if (isAborted2()) break;
14722
+ if (!capturedClaudeSessionId && message.session_id) {
14723
+ capturedClaudeSessionId = message.session_id;
14724
+ }
14697
14725
  if (message.type === "assistant") {
14698
14726
  const textContent = message.message.content.filter(isTextBlock).map((block) => block.text).join(" ");
14699
14727
  if (textContent) {
@@ -14707,37 +14735,77 @@ ${content}`;
14707
14735
  });
14708
14736
  }
14709
14737
  }
14710
- await stream2.writeSSE({ data: "", event: "done" });
14738
+ if (!isAborted2() && capturedClaudeSessionId) {
14739
+ lastClaudeSessionId = capturedClaudeSessionId;
14740
+ if (sessionId) {
14741
+ claudeSessionMap.set(sessionId, capturedClaudeSessionId);
14742
+ }
14743
+ }
14744
+ if (!isAborted2()) {
14745
+ await stream2.writeSSE({ data: "", event: "done" });
14746
+ }
14711
14747
  } catch (error) {
14712
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
14713
- await stream2.writeSSE({
14714
- data: `Error: ${errorMessage}`,
14715
- event: "error"
14716
- });
14748
+ if (!isAborted2()) {
14749
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
14750
+ const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
14751
+ const fullError = stderr && stderr.trim() ? `${errorMessage}
14752
+
14753
+ stderr:
14754
+ ${stderr.trim()}` : errorMessage;
14755
+ await stream2.writeSSE({
14756
+ data: `Error: ${fullError}`,
14757
+ event: "error"
14758
+ });
14759
+ }
14760
+ } finally {
14761
+ if (sessionId) {
14762
+ abortedSessions.delete(sessionId);
14763
+ }
14717
14764
  }
14718
14765
  });
14719
14766
  });
14767
+ app.post("/abort/:sessionId", (context) => {
14768
+ const { sessionId } = context.req.param();
14769
+ abortedSessions.add(sessionId);
14770
+ return context.json({ status: "ok" });
14771
+ });
14772
+ app.post("/undo", async (context) => {
14773
+ if (!lastClaudeSessionId) {
14774
+ return context.json({ status: "error", message: "No session to undo" });
14775
+ }
14776
+ try {
14777
+ const env = { ...process.env };
14778
+ delete env.NODE_OPTIONS;
14779
+ delete env.VSCODE_INSPECTOR_OPTIONS;
14780
+ const queryResult = query({
14781
+ prompt: "/rewind",
14782
+ options: {
14783
+ pathToClaudeCodeExecutable: resolveClaudePath(),
14784
+ cwd: process.cwd(),
14785
+ env,
14786
+ resume: lastClaudeSessionId
14787
+ }
14788
+ });
14789
+ for await (const message of queryResult) {
14790
+ if (message.type === "result") break;
14791
+ }
14792
+ return context.json({ status: "ok" });
14793
+ } catch (error) {
14794
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
14795
+ return context.json({ status: "error", message: errorMessage });
14796
+ }
14797
+ });
14720
14798
  app.get("/health", (context) => {
14721
14799
  return context.json({ status: "ok", provider: "claude" });
14722
14800
  });
14723
14801
  return app;
14724
14802
  };
14725
- var isPortInUse = (port) => new Promise((resolve) => {
14726
- const server = net.createServer();
14727
- server.once("error", () => resolve(true));
14728
- server.once("listening", () => {
14729
- server.close();
14730
- resolve(false);
14731
- });
14732
- server.listen(port);
14733
- });
14734
14803
  var startServer = async (port = DEFAULT_PORT) => {
14735
- if (await isPortInUse(port)) {
14736
- return;
14737
- }
14738
14804
  const app = createServer();
14739
14805
  serve({ fetch: app.fetch, port });
14740
- console.log(`${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Claude Code)")}`);
14806
+ console.log(
14807
+ `${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Claude Code)")}`
14808
+ );
14741
14809
  console.log(`- Local: ${import_picocolors.default.cyan(`http://localhost:${port}`)}`);
14742
14810
  };
14743
14811
  if (import.meta.url === pathToFileURL(process.argv[1]).href) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-grab/claude-code",
3
- "version": "0.0.78",
3
+ "version": "0.0.80",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "react-grab-claude-code": "./dist/cli.js"
@@ -24,14 +24,18 @@
24
24
  "dist"
25
25
  ],
26
26
  "devDependencies": {
27
+ "@types/cross-spawn": "^6.0.6",
28
+ "@types/kill-port": "^2.0.3",
27
29
  "tsup": "^8.4.0"
28
30
  },
29
31
  "dependencies": {
30
32
  "@anthropic-ai/claude-agent-sdk": "^0.1.0",
31
33
  "@hono/node-server": "^1.19.6",
34
+ "cross-spawn": "^7.0.6",
32
35
  "hono": "^4.0.0",
36
+ "kill-port": "^2.0.1",
33
37
  "picocolors": "^1.1.1",
34
- "react-grab": "0.0.78"
38
+ "react-grab": "0.0.80"
35
39
  },
36
40
  "scripts": {
37
41
  "dev": "tsup --watch",