@cuylabs/agent-code 0.1.6 → 0.6.0

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/README.md CHANGED
@@ -1,18 +1,40 @@
1
1
  # @cuylabs/agent-code
2
2
 
3
- An embeddable AI coding agent library built on top of the [Vercel AI SDK](https://sdk.vercel.ai/).
3
+ Coding-agent package built on `@cuylabs/agent-core`.
4
4
 
5
- Designed to be embedded into larger applications, using modern Vercel AI SDK patterns for streaming, tool calling, and multi-provider support.
5
+ It gives you a ready-made coding toolset and a convenience package for starting
6
+ from a coding-focused surface without rebuilding the common file and shell tools
7
+ yourself.
6
8
 
7
- ## Features
9
+ ## Package Boundary
8
10
 
9
- - šŸ”Œ **Embeddable** - Drop into any Node.js/TypeScript application
10
- - šŸ¤– **Multi-Provider** - Works with OpenAI, Anthropic, Google, and more via Vercel AI SDK
11
- - šŸ› ļø **Coding Tools** - Built-in tools for file operations, search, and shell commands
12
- - šŸ“” **Streaming** - Real-time streaming of responses and tool execution
13
- - šŸ”§ **Extensible** - Easy to add custom tools
14
- - šŸ’¾ **Session Management** - Built-in conversation history tracking
15
- - 🧠 **Reasoning Support** - Works with reasoning models (o3-mini, etc.)
11
+ Use `@cuylabs/agent-code` when you want:
12
+
13
+ - the built-in coding tools
14
+ - `defaultCodingTools` for a full coding agent
15
+ - easy read-only or no-shell tool subsets
16
+ - a convenience package that re-exports `agent-core`
17
+
18
+ Use `@cuylabs/agent-core` directly when you need lower-level framework surfaces
19
+ such as:
20
+
21
+ - middleware and prompt construction
22
+ - runtime helpers
23
+ - skills, presets, and sub-agent composition
24
+ - non-coding toolsets or framework-level customization
25
+
26
+ Focused tool imports are available from `@cuylabs/agent-code/tools`.
27
+
28
+ ## Included Tools
29
+
30
+ | Tool | Purpose |
31
+ |------|---------|
32
+ | `read` | Read file contents with line numbers |
33
+ | `write` | Create or overwrite files |
34
+ | `edit` | Surgical text replacement |
35
+ | `grep` | Search file contents with regex |
36
+ | `glob` | Find files by glob pattern |
37
+ | `bash` | Execute shell commands |
16
38
 
17
39
  ## Installation
18
40
 
@@ -28,222 +50,74 @@ pnpm add @cuylabs/agent-code ai @ai-sdk/openai
28
50
  import { createAgent, defaultCodingTools } from "@cuylabs/agent-code";
29
51
  import { openai } from "@ai-sdk/openai";
30
52
 
31
- // Create an agent with GPT-4o
32
53
  const agent = createAgent({
33
54
  model: openai("gpt-4o"),
34
55
  cwd: process.cwd(),
35
56
  tools: defaultCodingTools,
36
57
  });
37
58
 
38
- // Non-streaming usage
39
- const { response, toolCalls } = await agent.send(
59
+ const { response } = await agent.send(
40
60
  "session-1",
41
- "List all TypeScript files in the src directory"
61
+ "List the TypeScript files in src and summarize what each one does.",
42
62
  );
43
- console.log(response);
44
63
 
45
- // Streaming usage
46
- for await (const event of agent.chat("session-1", "Fix the bug in utils.ts")) {
47
- switch (event.type) {
48
- case "text-delta":
49
- process.stdout.write(event.text);
50
- break;
51
- case "tool-start":
52
- console.log(`\nšŸ”§ ${event.toolName}...`);
53
- break;
54
- case "tool-result":
55
- console.log(`āœ… ${event.toolName} complete`);
56
- break;
57
- case "error":
58
- console.error(`āŒ Error:`, event.error);
59
- break;
60
- }
61
- }
64
+ console.log(response);
62
65
  ```
63
66
 
64
- ## Built-in Tools
65
-
66
- | Tool | Description |
67
- |------|-------------|
68
- | `bash` | Execute shell commands |
69
- | `read` | Read file contents with line numbers |
70
- | `edit` | Edit files by replacing text |
71
- | `write` | Create or overwrite files |
72
- | `grep` | Search file contents with regex |
73
- | `glob` | Find files by glob pattern |
74
-
75
- ## Custom Tools
67
+ ## Choosing A Tool Set
76
68
 
77
- Add your own tools using the `Tool.define` helper:
69
+ Use the default set:
78
70
 
79
71
  ```typescript
80
- import { createAgent, Tool } from "@cuylabs/agent-code";
81
- import { openai } from "@ai-sdk/openai";
82
- import { z } from "zod";
83
-
84
- const myTool = Tool.define("my_custom_tool", {
85
- description: "Does something custom",
86
- parameters: z.object({
87
- input: z.string().describe("The input to process"),
88
- }),
89
- execute: async (params, ctx) => ({
90
- title: "Custom Tool",
91
- output: `Processed: ${params.input}`,
92
- metadata: {},
93
- }),
94
- });
72
+ import { createAgent, defaultCodingTools } from "@cuylabs/agent-code";
95
73
 
96
74
  const agent = createAgent({
97
- model: openai("gpt-4o"),
75
+ model,
98
76
  cwd: process.cwd(),
77
+ tools: defaultCodingTools,
99
78
  });
100
-
101
- agent.addTool(myTool);
102
79
  ```
103
80
 
104
- ## Using Different Providers
81
+ Or build a narrower agent:
105
82
 
106
83
  ```typescript
107
- // OpenAI (recommended)
108
- import { openai } from "@ai-sdk/openai";
109
- const agent = createAgent({
110
- model: openai("gpt-4o"),
111
- cwd: process.cwd(),
112
- });
113
-
114
- // OpenAI with reasoning
115
- const reasoningAgent = createAgent({
116
- model: openai("o3-mini"),
117
- cwd: process.cwd(),
118
- reasoningLevel: "high",
119
- });
120
-
121
- // Anthropic
122
- import { anthropic } from "@ai-sdk/anthropic";
123
- const agent = createAgent({
124
- model: anthropic("claude-sonnet-4-20250514"),
84
+ import {
85
+ createAgent,
86
+ readTool,
87
+ grepTool,
88
+ globTool,
89
+ } from "@cuylabs/agent-code";
90
+
91
+ const readOnlyAgent = createAgent({
92
+ model,
125
93
  cwd: process.cwd(),
126
- });
127
-
128
- // Google
129
- import { google } from "@ai-sdk/google";
130
- const agent = createAgent({
131
- model: google("gemini-2.0-flash"),
132
- cwd: process.cwd(),
133
- });
134
- ```
135
-
136
- ## Configuration
137
-
138
- ```typescript
139
- import { createAgent, defaultCodingTools, readTool, grepTool, globTool } from "@cuylabs/agent-code";
140
- import { openai } from "@ai-sdk/openai";
141
-
142
- const agent = createAgent({
143
- // Required: Vercel AI SDK model instance
144
- model: openai("gpt-4o"),
145
-
146
- // Working directory for file operations (default: process.cwd())
147
- cwd: "/path/to/project",
148
-
149
- // System prompt (has sensible defaults for coding)
150
- systemPrompt: "You are a helpful coding assistant...",
151
-
152
- // Temperature (0-1, default varies by provider)
153
- temperature: 0.7,
154
-
155
- // Max output tokens (default: 32000)
156
- maxOutputTokens: 16000,
157
-
158
- // Max steps for tool calling loops (default: 50)
159
- maxSteps: 25,
160
-
161
- // Reasoning level for reasoning models (default: "off")
162
- reasoningLevel: "high", // "off", "low", "medium", "high"
163
-
164
- // Tools to enable (defaults to empty, use defaultCodingTools for all)
165
- tools: defaultCodingTools,
166
-
167
- // Or use a subset for read-only operations
168
- // tools: [readTool, grepTool, globTool],
94
+ tools: [readTool, grepTool, globTool],
169
95
  });
170
96
  ```
171
97
 
172
- ## Session Management
98
+ If you only want the tool surface:
173
99
 
174
100
  ```typescript
175
- // Sessions are managed automatically
176
- // Just use a unique session ID for each conversation
177
-
178
- // List all sessions
179
- const sessions = await agent.listSessions();
180
-
181
- // Delete a session
182
- await agent.deleteSession("session-id");
183
-
184
- // Get current session's messages
185
- const messages = agent.getMessages();
186
-
187
- // Branch from current point (for undo/redo)
188
- const branchId = await agent.branch("checkpoint before refactor");
101
+ import { defaultCodingTools, readTool } from "@cuylabs/agent-code/tools";
189
102
  ```
190
103
 
191
- ## Events
104
+ ## Relationship To `agent-core`
192
105
 
193
- The streaming `chat()` method yields these events:
106
+ `agent-code` is the coding-tool layer. It does not own the lower-level agent
107
+ framework semantics.
194
108
 
195
- ```typescript
196
- type AgentEvent =
197
- // Text streaming
198
- | { type: "text-start" }
199
- | { type: "text-delta"; text: string }
200
- | { type: "text-end" }
201
-
202
- // Reasoning (for o3-mini, etc.)
203
- | { type: "reasoning-start"; id: string }
204
- | { type: "reasoning-delta"; id: string; text: string }
205
- | { type: "reasoning-end"; id: string }
206
-
207
- // Tool execution
208
- | { type: "tool-start"; toolName: string; toolCallId: string; input: unknown }
209
- | { type: "tool-result"; toolName: string; toolCallId: string; result: unknown }
210
- | { type: "tool-error"; toolName: string; toolCallId: string; error: string }
211
-
212
- // Progress
213
- | { type: "step-start"; step: number; maxSteps: number }
214
- | { type: "step-finish"; step: number; usage?: TokenUsage; finishReason?: string }
215
-
216
- // Completion
217
- | { type: "error"; error: Error }
218
- | { type: "complete"; usage?: TokenUsage };
219
- ```
220
-
221
- ## Examples
222
-
223
- See the [examples/](./examples/) folder for working examples:
224
-
225
- | # | File | What it shows |
226
- |---|------|---------------|
227
- | 01 | `01-basic.ts` | Basic streaming chat with coding tools |
228
- | 02 | `02-reasoning.ts` | Using reasoning models (o4-mini) |
229
- | 03 | `03-read-only.ts` | Read-only agent (no bash/edit/write) |
230
- | 04 | `04-custom-tools.ts` | Adding your own tools |
231
-
232
- ```bash
233
- cd packages/agent-code/examples
234
- cp .env.example .env
235
- # Add your OPENAI_API_KEY
236
-
237
- npx tsx examples/01-basic.ts
238
- ```
109
+ - `agent-core` owns sessions, middleware, runtime helpers, prompt construction, and tracing
110
+ - `agent-code` adds a coding-focused toolset and convenience defaults on top
239
111
 
240
- ## Architecture
112
+ So if your main need is ā€œgive my agent practical coding tools,ā€ use
113
+ `agent-code`. If your main need is ā€œcustomize the framework internals,ā€ use
114
+ `agent-core`.
241
115
 
242
- This library is built on:
116
+ ## Learn More
243
117
 
244
- - **@cuylabs/agent-core** - Core agent infrastructure (sessions, streaming, tool execution)
245
- - **Vercel AI SDK** - LLM interaction and multi-provider support
246
- - **Zod** - Parameter validation for tools
118
+ - [examples/README.md](./examples/README.md) for runnable examples
119
+ - [docs/README.md](./docs/README.md) for per-tool guides
120
+ - [docs/read.md](./docs/read.md), [docs/edit.md](./docs/edit.md), and [docs/bash.md](./docs/bash.md) for the tool details most people care about first
247
121
 
248
122
  ## License
249
123
 
@@ -5,8 +5,12 @@ var DEFAULT_TIMEOUT = 2 * 60 * 1e3;
5
5
  var bashParameters = z.object({
6
6
  command: z.string().describe("The shell command to execute"),
7
7
  timeout: z.number().optional().describe("Timeout in milliseconds (default: 120000)"),
8
- workdir: z.string().optional().describe("Working directory to run the command in. Defaults to current working directory."),
9
- description: z.string().describe("Clear, concise description of what this command does in 5-10 words")
8
+ workdir: z.string().optional().describe(
9
+ "Working directory to run the command in. Defaults to current working directory."
10
+ ),
11
+ description: z.string().describe(
12
+ "Clear, concise description of what this command does in 5-10 words"
13
+ )
10
14
  });
11
15
  async function executeBash(params, host, cwd, abort) {
12
16
  const workdir = params.workdir || cwd;
@@ -35,6 +39,11 @@ IMPORTANT:
35
39
  - Output is automatically truncated if too long
36
40
  - For pager commands, output is piped through 'cat' automatically`,
37
41
  parameters: bashParameters,
42
+ replayPolicy: {
43
+ mode: "manual",
44
+ sideEffectLevel: "external",
45
+ reason: "Arbitrary shell execution may affect processes, services, or systems outside the current turn."
46
+ },
38
47
  async execute(params, ctx) {
39
48
  const host = ctx.host;
40
49
  const result = await executeBash(params, host, ctx.cwd, ctx.abort);
@@ -141,7 +150,9 @@ async function isBinaryFile(filepath, host) {
141
150
  var readParameters = z2.object({
142
151
  filePath: z2.string().describe("The path to the file to read"),
143
152
  offset: z2.number().optional().describe("The line number to start reading from (0-based)"),
144
- limit: z2.number().optional().describe(`The number of lines to read (defaults to ${DEFAULT_READ_LIMIT})`)
153
+ limit: z2.number().optional().describe(
154
+ `The number of lines to read (defaults to ${DEFAULT_READ_LIMIT})`
155
+ )
145
156
  });
146
157
  var readTool = Tool2.define("read", {
147
158
  description: `Read the contents of a file.
@@ -153,6 +164,10 @@ IMPORTANT:
153
164
  - Binary files cannot be read
154
165
  - Images and PDFs are returned as attachments`,
155
166
  parameters: readParameters,
167
+ replayPolicy: {
168
+ sideEffectLevel: "none",
169
+ reason: "Read-only file inspection is safe to replay."
170
+ },
156
171
  async execute(params, ctx) {
157
172
  const host = ctx.host;
158
173
  let filepath = params.filePath;
@@ -338,7 +353,9 @@ function findTextFuzzy(content, searchText) {
338
353
  }
339
354
  var editParameters = z3.object({
340
355
  filePath: z3.string().describe("The absolute path to the file to modify"),
341
- oldString: z3.string().describe("The text to replace (must match exactly or with minor whitespace differences)"),
356
+ oldString: z3.string().describe(
357
+ "The text to replace (must match exactly or with minor whitespace differences)"
358
+ ),
342
359
  newString: z3.string().describe("The text to replace it with (must be different from oldString)"),
343
360
  replaceAll: z3.boolean().optional().describe("Replace all occurrences of oldString (default: false)")
344
361
  });
@@ -352,6 +369,11 @@ IMPORTANT:
352
369
  - Use replaceAll: true to replace all occurrences
353
370
  - For creating new files, use the write tool instead`,
354
371
  parameters: editParameters,
372
+ replayPolicy: {
373
+ mode: "manual",
374
+ sideEffectLevel: "local",
375
+ reason: "Text replacement mutates local files and needs explicit replay control."
376
+ },
355
377
  // Enable automatic baseline capture for turn tracking
356
378
  fileOps: {
357
379
  pathArgs: ["filePath"],
@@ -445,6 +467,11 @@ IMPORTANT:
445
467
  - Parent directories are created automatically
446
468
  - Existing content is completely replaced`,
447
469
  parameters: writeParameters,
470
+ replayPolicy: {
471
+ mode: "manual",
472
+ sideEffectLevel: "local",
473
+ reason: "Replaying a file write can overwrite state unexpectedly."
474
+ },
448
475
  // Enable automatic baseline capture for turn tracking
449
476
  fileOps: {
450
477
  pathArgs: ["filePath"],
@@ -504,8 +531,12 @@ function sq(s) {
504
531
  }
505
532
  var grepParameters = z5.object({
506
533
  pattern: z5.string().describe("The regex pattern to search for in file contents"),
507
- path: z5.string().optional().describe("The directory to search in. Defaults to current working directory."),
508
- include: z5.string().optional().describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")')
534
+ path: z5.string().optional().describe(
535
+ "The directory to search in. Defaults to current working directory."
536
+ ),
537
+ include: z5.string().optional().describe(
538
+ 'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'
539
+ )
509
540
  });
510
541
  async function executeGrep(params, host, cwd, abort) {
511
542
  const searchPath = params.path ? path4.isAbsolute(params.path) ? params.path : path4.resolve(cwd, params.path) : cwd;
@@ -569,6 +600,10 @@ IMPORTANT:
569
600
  - Results are sorted by modification time (newest first)
570
601
  - Maximum ${MAX_RESULTS} results returned`,
571
602
  parameters: grepParameters,
603
+ replayPolicy: {
604
+ sideEffectLevel: "none",
605
+ reason: "Content search is read-only and safe to repeat."
606
+ },
572
607
  async execute(params, ctx) {
573
608
  if (!params.pattern) {
574
609
  throw new Error("pattern is required");
@@ -610,10 +645,10 @@ async function findFiles(pattern, cwd, host, abort) {
610
645
  const files = [];
611
646
  let truncated = false;
612
647
  try {
613
- const result = await host.exec(
614
- `rg --files --glob ${sq2(pattern)}`,
615
- { cwd, signal: abort }
616
- );
648
+ const result = await host.exec(`rg --files --glob ${sq2(pattern)}`, {
649
+ cwd,
650
+ signal: abort
651
+ });
617
652
  if (result.exitCode === 0 || result.exitCode === 1) {
618
653
  const found = result.stdout.trim().split(/\r?\n/).filter(Boolean);
619
654
  for (const file of found) {
@@ -658,8 +693,12 @@ async function findFiles(pattern, cwd, host, abort) {
658
693
  return { files, truncated };
659
694
  }
660
695
  var globParameters = z6.object({
661
- pattern: z6.string().describe('The glob pattern to match files (e.g. "**/*.ts", "src/**/*.js")'),
662
- path: z6.string().optional().describe("The directory to search in. Defaults to current working directory.")
696
+ pattern: z6.string().describe(
697
+ 'The glob pattern to match files (e.g. "**/*.ts", "src/**/*.js")'
698
+ ),
699
+ path: z6.string().optional().describe(
700
+ "The directory to search in. Defaults to current working directory."
701
+ )
663
702
  });
664
703
  var globTool = Tool6.define("glob", {
665
704
  description: `Find files matching a glob pattern.
@@ -670,10 +709,19 @@ IMPORTANT:
670
709
  - Results are sorted by modification time (newest first)
671
710
  - Maximum ${MAX_RESULTS2} results returned`,
672
711
  parameters: globParameters,
712
+ replayPolicy: {
713
+ sideEffectLevel: "none",
714
+ reason: "Filesystem discovery does not mutate state."
715
+ },
673
716
  async execute(params, ctx) {
674
717
  const host = ctx.host;
675
718
  const searchPath = params.path ? path5.isAbsolute(params.path) ? params.path : path5.resolve(ctx.cwd, params.path) : ctx.cwd;
676
- const { files, truncated } = await findFiles(params.pattern, searchPath, host, ctx.abort);
719
+ const { files, truncated } = await findFiles(
720
+ params.pattern,
721
+ searchPath,
722
+ host,
723
+ ctx.abort
724
+ );
677
725
  if (files.length === 0) {
678
726
  return {
679
727
  title: params.pattern,
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  toolset,
19
19
  writeParameters,
20
20
  writeTool
21
- } from "./chunk-LMWOKL6B.js";
21
+ } from "./chunk-U6W6NJNW.js";
22
22
 
23
23
  // src/index.ts
24
24
  export * from "@cuylabs/agent-core";
@@ -11,13 +11,13 @@ declare const bashParameters: z.ZodObject<{
11
11
  workdir: z.ZodOptional<z.ZodString>;
12
12
  description: z.ZodString;
13
13
  }, "strip", z.ZodTypeAny, {
14
- command: string;
15
14
  description: string;
15
+ command: string;
16
16
  timeout?: number | undefined;
17
17
  workdir?: string | undefined;
18
18
  }, {
19
- command: string;
20
19
  description: string;
20
+ command: string;
21
21
  timeout?: number | undefined;
22
22
  workdir?: string | undefined;
23
23
  }>;
@@ -35,13 +35,13 @@ declare const bashTool: Tool.Info<z.ZodObject<{
35
35
  workdir: z.ZodOptional<z.ZodString>;
36
36
  description: z.ZodString;
37
37
  }, "strip", z.ZodTypeAny, {
38
- command: string;
39
38
  description: string;
39
+ command: string;
40
40
  timeout?: number | undefined;
41
41
  workdir?: string | undefined;
42
42
  }, {
43
- command: string;
44
43
  description: string;
44
+ command: string;
45
45
  timeout?: number | undefined;
46
46
  workdir?: string | undefined;
47
47
  }>, {
@@ -61,12 +61,12 @@ declare const readParameters: z.ZodObject<{
61
61
  limit: z.ZodOptional<z.ZodNumber>;
62
62
  }, "strip", z.ZodTypeAny, {
63
63
  filePath: string;
64
- offset?: number | undefined;
65
64
  limit?: number | undefined;
65
+ offset?: number | undefined;
66
66
  }, {
67
67
  filePath: string;
68
- offset?: number | undefined;
69
68
  limit?: number | undefined;
69
+ offset?: number | undefined;
70
70
  }>;
71
71
  type ReadParams = z.infer<typeof readParameters>;
72
72
  /**
@@ -78,12 +78,12 @@ declare const readTool: Tool.Info<z.ZodObject<{
78
78
  limit: z.ZodOptional<z.ZodNumber>;
79
79
  }, "strip", z.ZodTypeAny, {
80
80
  filePath: string;
81
- offset?: number | undefined;
82
81
  limit?: number | undefined;
82
+ offset?: number | undefined;
83
83
  }, {
84
84
  filePath: string;
85
- offset?: number | undefined;
86
85
  limit?: number | undefined;
86
+ offset?: number | undefined;
87
87
  }>, {
88
88
  totalLines: number;
89
89
  linesRead: number;
@@ -147,11 +147,11 @@ declare const writeParameters: z.ZodObject<{
147
147
  filePath: z.ZodString;
148
148
  content: z.ZodString;
149
149
  }, "strip", z.ZodTypeAny, {
150
- filePath: string;
151
150
  content: string;
152
- }, {
153
151
  filePath: string;
152
+ }, {
154
153
  content: string;
154
+ filePath: string;
155
155
  }>;
156
156
  type WriteParams = z.infer<typeof writeParameters>;
157
157
  /**
@@ -161,11 +161,11 @@ declare const writeTool: Tool.Info<z.ZodObject<{
161
161
  filePath: z.ZodString;
162
162
  content: z.ZodString;
163
163
  }, "strip", z.ZodTypeAny, {
164
- filePath: string;
165
164
  content: string;
166
- }, {
167
165
  filePath: string;
166
+ }, {
168
167
  content: string;
168
+ filePath: string;
169
169
  }>, {
170
170
  filepath: string;
171
171
  created: boolean;
@@ -18,7 +18,7 @@ import {
18
18
  toolset,
19
19
  writeParameters,
20
20
  writeTool
21
- } from "../chunk-LMWOKL6B.js";
21
+ } from "../chunk-U6W6NJNW.js";
22
22
  export {
23
23
  ToolsetBuilder,
24
24
  bashParameters,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuylabs/agent-code",
3
- "version": "0.1.6",
3
+ "version": "0.6.0",
4
4
  "description": "Embeddable AI coding agent built on @cuylabs/agent-core",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "ai": "^6.0.67",
26
26
  "zod": "^3.24.0",
27
- "@cuylabs/agent-core": "^0.4.0"
27
+ "@cuylabs/agent-core": "^0.6.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "@ai-sdk/anthropic": "^3.0.0",
@@ -76,6 +76,7 @@
76
76
  "build": "tsup src/index.ts src/tools/index.ts --format esm --dts --clean",
77
77
  "dev": "tsup src/index.ts src/tools/index.ts --format esm --dts --watch",
78
78
  "typecheck": "tsc --noEmit",
79
- "test": "vitest run"
79
+ "test": "vitest run",
80
+ "test:watch": "vitest"
80
81
  }
81
82
  }