@agi-cli/sdk 0.1.49 → 0.1.51

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.
Files changed (50) hide show
  1. package/README.md +237 -538
  2. package/package.json +11 -7
  3. package/src/agent/types.ts +1 -1
  4. package/src/index.ts +99 -64
  5. package/src/errors.ts +0 -102
  6. package/src/providers/resolver.ts +0 -84
  7. package/src/streaming/artifacts.ts +0 -41
  8. package/src/tools/builtin/bash.ts +0 -73
  9. package/src/tools/builtin/bash.txt +0 -7
  10. package/src/tools/builtin/edit.ts +0 -145
  11. package/src/tools/builtin/edit.txt +0 -7
  12. package/src/tools/builtin/file-cache.ts +0 -39
  13. package/src/tools/builtin/finish.ts +0 -11
  14. package/src/tools/builtin/finish.txt +0 -5
  15. package/src/tools/builtin/fs/cd.ts +0 -19
  16. package/src/tools/builtin/fs/cd.txt +0 -5
  17. package/src/tools/builtin/fs/index.ts +0 -20
  18. package/src/tools/builtin/fs/ls.ts +0 -57
  19. package/src/tools/builtin/fs/ls.txt +0 -8
  20. package/src/tools/builtin/fs/pwd.ts +0 -17
  21. package/src/tools/builtin/fs/pwd.txt +0 -5
  22. package/src/tools/builtin/fs/read.ts +0 -49
  23. package/src/tools/builtin/fs/read.txt +0 -8
  24. package/src/tools/builtin/fs/tree.ts +0 -67
  25. package/src/tools/builtin/fs/tree.txt +0 -8
  26. package/src/tools/builtin/fs/util.ts +0 -95
  27. package/src/tools/builtin/fs/write.ts +0 -61
  28. package/src/tools/builtin/fs/write.txt +0 -8
  29. package/src/tools/builtin/git.commit.txt +0 -6
  30. package/src/tools/builtin/git.diff.txt +0 -5
  31. package/src/tools/builtin/git.status.txt +0 -5
  32. package/src/tools/builtin/git.ts +0 -112
  33. package/src/tools/builtin/glob.ts +0 -82
  34. package/src/tools/builtin/glob.txt +0 -8
  35. package/src/tools/builtin/grep.ts +0 -138
  36. package/src/tools/builtin/grep.txt +0 -9
  37. package/src/tools/builtin/ignore.ts +0 -45
  38. package/src/tools/builtin/patch.ts +0 -273
  39. package/src/tools/builtin/patch.txt +0 -7
  40. package/src/tools/builtin/plan.ts +0 -58
  41. package/src/tools/builtin/plan.txt +0 -6
  42. package/src/tools/builtin/progress.ts +0 -55
  43. package/src/tools/builtin/progress.txt +0 -7
  44. package/src/tools/builtin/ripgrep.ts +0 -71
  45. package/src/tools/builtin/ripgrep.txt +0 -7
  46. package/src/tools/builtin/websearch.ts +0 -219
  47. package/src/tools/builtin/websearch.txt +0 -12
  48. package/src/tools/loader.ts +0 -390
  49. package/src/types/index.ts +0 -11
  50. package/src/types/types.ts +0 -4
@@ -1,145 +0,0 @@
1
- import { tool, type Tool } from 'ai';
2
- import { z } from 'zod';
3
- import DESCRIPTION from './edit.txt' with { type: 'text' };
4
-
5
- const replaceOp = z.object({
6
- type: z.literal('replace'),
7
- find: z.string().describe('String or regex (when regex=true)'),
8
- replace: z.string().default(''),
9
- regex: z.boolean().optional().default(false),
10
- flags: z.string().optional().default('g'),
11
- count: z
12
- .number()
13
- .int()
14
- .min(1)
15
- .optional()
16
- .describe('Limit number of replacements'),
17
- });
18
-
19
- const insertOp = z.object({
20
- type: z.literal('insert'),
21
- position: z.enum(['before', 'after', 'start', 'end']).default('after'),
22
- pattern: z.string().optional().describe('Anchor pattern for before/after'),
23
- content: z.string(),
24
- once: z.boolean().optional().default(true),
25
- });
26
-
27
- const deleteRangeOp = z.object({
28
- type: z.literal('delete_range'),
29
- start: z.string().describe('Start marker (first occurrence)'),
30
- end: z.string().describe('End marker (first occurrence after start)'),
31
- includeBoundaries: z.boolean().optional().default(false),
32
- });
33
-
34
- const opSchema = z.discriminatedUnion('type', [
35
- replaceOp,
36
- insertOp,
37
- deleteRangeOp,
38
- ]);
39
-
40
- export const editTool: Tool = tool({
41
- description: DESCRIPTION,
42
- inputSchema: z.object({
43
- path: z.string().min(1),
44
- ops: z.array(opSchema).min(1),
45
- create: z.boolean().optional().default(false),
46
- }),
47
- async execute({
48
- path,
49
- ops,
50
- create,
51
- }: {
52
- path: string;
53
- ops: z.infer<typeof opSchema>[];
54
- create?: boolean;
55
- }) {
56
- const file = Bun.file(path);
57
- if (!(await file.exists())) {
58
- if (!create) throw new Error(`File not found: ${path}`);
59
- await Bun.write(path, '');
60
- }
61
- let text = await Bun.file(path).text();
62
- let applied = 0;
63
-
64
- for (const op of ops) {
65
- if (op.type === 'replace') {
66
- const originalText = text;
67
- if (op.regex) {
68
- const re = new RegExp(op.find, op.flags || 'g');
69
- if (op.count && op.count > 0) {
70
- let n = 0;
71
- text = text.replace(re, (m) => {
72
- if (n < (op.count as number)) {
73
- n += 1;
74
- return op.replace;
75
- }
76
- return m;
77
- });
78
- } else text = text.replace(re, op.replace);
79
- } else {
80
- // Check if the text to find exists
81
- if (!text.includes(op.find)) {
82
- console.warn(
83
- `Warning: Text not found for replace operation: "${op.find.substring(0, 50)}${op.find.length > 50 ? '...' : ''}"`,
84
- );
85
- }
86
- if (op.count && op.count > 0) {
87
- let remaining = op.count as number;
88
- let idx = text.indexOf(op.find);
89
- while (idx !== -1 && remaining > 0) {
90
- text =
91
- text.slice(0, idx) +
92
- op.replace +
93
- text.slice(idx + op.find.length);
94
- remaining -= 1;
95
- idx = text.indexOf(op.find, idx + op.replace.length);
96
- }
97
- } else {
98
- text = text.split(op.find).join(op.replace);
99
- }
100
- }
101
- // Only count as applied if text actually changed
102
- if (text !== originalText) {
103
- applied += 1;
104
- }
105
- } else if (op.type === 'insert') {
106
- if (op.position === 'start') {
107
- text = `${op.content}${text}`;
108
- applied += 1;
109
- continue;
110
- }
111
- if (op.position === 'end') {
112
- text = `${text}${op.content}`;
113
- applied += 1;
114
- continue;
115
- }
116
- if (!op.pattern)
117
- throw new Error('insert requires pattern for before/after');
118
- const idx = text.indexOf(op.pattern);
119
- if (idx === -1) continue;
120
- if (op.position === 'before')
121
- text = text.slice(0, idx) + op.content + text.slice(idx);
122
- else
123
- text =
124
- text.slice(0, idx + op.pattern.length) +
125
- op.content +
126
- text.slice(idx + op.pattern.length);
127
- applied += 1;
128
- if (op.once) continue;
129
- } else if (op.type === 'delete_range') {
130
- const startIdx = text.indexOf(op.start);
131
- if (startIdx === -1) continue;
132
- const after = startIdx + op.start.length;
133
- const endIdx = text.indexOf(op.end, after);
134
- if (endIdx === -1) continue;
135
- const from = op.includeBoundaries ? startIdx : after;
136
- const to = op.includeBoundaries ? endIdx + op.end.length : endIdx;
137
- text = text.slice(0, from) + text.slice(to);
138
- applied += 1;
139
- }
140
- }
141
-
142
- await Bun.write(path, text);
143
- return { path, opsApplied: applied, bytes: text.length };
144
- },
145
- });
@@ -1,7 +0,0 @@
1
- - Edit a file using structured operations
2
- - Supported ops: `replace`, `insert`, `delete_range`
3
- - Paths are relative to the project root; can create files if flagged
4
-
5
- Usage tips:
6
- - Prefer minimal, targeted changes for clarity
7
- - For sweeping refactors, consider Write or Patch to replace full content
@@ -1,39 +0,0 @@
1
- /**
2
- * File content cache to track modifications during a session/step
3
- * This helps ensure tools always work with the latest file content
4
- */
5
-
6
- const fileContentCache = new Map<string, Map<string, string>>();
7
-
8
- export function getFileCache(sessionId: string): Map<string, string> {
9
- if (!fileContentCache.has(sessionId)) {
10
- fileContentCache.set(sessionId, new Map());
11
- }
12
- return fileContentCache.get(sessionId) as Map<string, string>;
13
- }
14
-
15
- export function updateFileCache(
16
- sessionId: string,
17
- filePath: string,
18
- content: string,
19
- ): void {
20
- const cache = getFileCache(sessionId);
21
- cache.set(filePath, content);
22
- }
23
-
24
- export function getCachedContent(
25
- sessionId: string,
26
- filePath: string,
27
- ): string | undefined {
28
- const cache = getFileCache(sessionId);
29
- return cache.get(filePath);
30
- }
31
-
32
- export function clearFileCache(sessionId: string): void {
33
- fileContentCache.delete(sessionId);
34
- }
35
-
36
- export function clearFileCacheEntry(sessionId: string, filePath: string): void {
37
- const cache = getFileCache(sessionId);
38
- cache.delete(filePath);
39
- }
@@ -1,11 +0,0 @@
1
- import { z } from 'zod';
2
- import { tool } from 'ai';
3
- import DESCRIPTION from './finish.txt' with { type: 'text' };
4
-
5
- export const finishTool = tool({
6
- description: DESCRIPTION,
7
- inputSchema: z.object({}),
8
- async execute() {
9
- return { done: true } as const;
10
- },
11
- });
@@ -1,5 +0,0 @@
1
- - Signal that the task is complete
2
- - Agent should stream the summary directly as assistant message not using the finish tool
3
-
4
- Usage tips:
5
- - Ensure all necessary outputs are already saved/emitted before finishing
@@ -1,19 +0,0 @@
1
- import { tool, type Tool } from 'ai';
2
- import { z } from 'zod';
3
- import DESCRIPTION from './cd.txt' with { type: 'text' };
4
-
5
- // description imported above
6
-
7
- export function buildCdTool(): { name: string; tool: Tool } {
8
- const cd = tool({
9
- description: DESCRIPTION,
10
- inputSchema: z.object({
11
- path: z.string().describe('Relative directory path'),
12
- }),
13
- async execute({ path }: { path: string }) {
14
- // Actual cwd update is handled in the adapter; this is a placeholder schema
15
- return { cwd: path };
16
- },
17
- });
18
- return { name: 'cd', tool: cd };
19
- }
@@ -1,5 +0,0 @@
1
- - Change the current working directory (relative to the project root)
2
- - The runner enforces boundaries; this tool only requests a new cwd
3
-
4
- Usage tip:
5
- - Use PWD to verify the current directory
@@ -1,20 +0,0 @@
1
- import type { Tool } from 'ai';
2
- import { buildReadTool } from './read.ts';
3
- import { buildWriteTool } from './write.ts';
4
- import { buildLsTool } from './ls.ts';
5
- import { buildTreeTool } from './tree.ts';
6
- import { buildPwdTool } from './pwd.ts';
7
- import { buildCdTool } from './cd.ts';
8
-
9
- export function buildFsTools(
10
- projectRoot: string,
11
- ): Array<{ name: string; tool: Tool }> {
12
- const out: Array<{ name: string; tool: Tool }> = [];
13
- out.push(buildReadTool(projectRoot));
14
- out.push(buildWriteTool(projectRoot));
15
- out.push(buildLsTool(projectRoot));
16
- out.push(buildTreeTool(projectRoot));
17
- out.push(buildPwdTool());
18
- out.push(buildCdTool());
19
- return out;
20
- }
@@ -1,57 +0,0 @@
1
- import { tool, type Tool } from 'ai';
2
- import { z } from 'zod';
3
- import { spawn } from 'bun';
4
- import { expandTilde, isAbsoluteLike, resolveSafePath } from './util.ts';
5
- import DESCRIPTION from './ls.txt' with { type: 'text' };
6
- import { toIgnoredBasenames } from '../ignore.ts';
7
-
8
- // description imported above
9
-
10
- export function buildLsTool(projectRoot: string): { name: string; tool: Tool } {
11
- const ls = tool({
12
- description: DESCRIPTION,
13
- inputSchema: z.object({
14
- path: z
15
- .string()
16
- .default('.')
17
- .describe(
18
- "Directory path. Relative to project root by default; absolute ('/...') and home ('~/...') paths are allowed.",
19
- ),
20
- ignore: z
21
- .array(z.string())
22
- .optional()
23
- .describe('List of directory names/globs to ignore'),
24
- }),
25
- async execute({ path, ignore }: { path: string; ignore?: string[] }) {
26
- const req = expandTilde(path || '.');
27
- const abs = isAbsoluteLike(req)
28
- ? req
29
- : resolveSafePath(projectRoot, req || '.');
30
- const ignored = toIgnoredBasenames(ignore);
31
- const proc = spawn({
32
- cmd: ['ls', '-1p'],
33
- cwd: abs,
34
- stdout: 'pipe',
35
- stderr: 'pipe',
36
- });
37
- const exitCode = await proc.exited;
38
- const stdout = await new Response(proc.stdout).text();
39
- const stderr = await new Response(proc.stderr).text();
40
- if (exitCode !== 0) {
41
- const message = (stderr || stdout || 'ls failed').trim();
42
- throw new Error(`ls failed for ${req}: ${message}`);
43
- }
44
- const entries = stdout
45
- .split('\n')
46
- .map((line) => line.trim())
47
- .filter((line) => line.length > 0 && !line.startsWith('.'))
48
- .map((line) => ({
49
- name: line.replace(/\/$/, ''),
50
- type: line.endsWith('/') ? 'dir' : 'file',
51
- }))
52
- .filter((entry) => !(entry.type === 'dir' && ignored.has(entry.name)));
53
- return { path: req, entries };
54
- },
55
- });
56
- return { name: 'ls', tool: ls };
57
- }
@@ -1,8 +0,0 @@
1
- - Lists files and directories in a given path (non-recursive)
2
- - Accepts absolute ('/...'), home ('~/...'), or project-relative paths
3
- - Hides common build and cache folders by default (node_modules, dist, .git, etc.)
4
- - Optional ignore patterns allow further filtering of directory names
5
-
6
- Usage tips:
7
- - Prefer the Glob tool for pattern-based discovery and Grep for content search
8
- - Use the Tree tool for a hierarchical view with limited depth
@@ -1,17 +0,0 @@
1
- import { tool, type Tool } from 'ai';
2
- import { z } from 'zod';
3
- import DESCRIPTION from './pwd.txt' with { type: 'text' };
4
-
5
- // description imported above
6
-
7
- export function buildPwdTool(): { name: string; tool: Tool } {
8
- const pwd = tool({
9
- description: DESCRIPTION,
10
- inputSchema: z.object({}).optional(),
11
- async execute() {
12
- // Actual cwd resolution is handled in the adapter; this is a placeholder schema
13
- return { cwd: '.' };
14
- },
15
- });
16
- return { name: 'pwd', tool: pwd };
17
- }
@@ -1,5 +0,0 @@
1
- - Return the current working directory (relative to the project root)
2
- - The runner maintains a per-session cwd; this tool reports it
3
-
4
- Usage tip:
5
- - Combine with the CD tool to navigate within the workspace
@@ -1,49 +0,0 @@
1
- import { tool, type Tool } from 'ai';
2
- import { z } from 'zod';
3
- import { expandTilde, isAbsoluteLike, resolveSafePath } from './util.ts';
4
- import DESCRIPTION from './read.txt' with { type: 'text' };
5
-
6
- const embeddedTextAssets: Record<string, string> = {};
7
-
8
- // description imported above
9
-
10
- export function buildReadTool(projectRoot: string): {
11
- name: string;
12
- tool: Tool;
13
- } {
14
- const read = tool({
15
- description: DESCRIPTION,
16
- inputSchema: z.object({
17
- path: z
18
- .string()
19
- .describe(
20
- "File path. Relative to project root by default; absolute ('/...') and home ('~/...') paths are allowed.",
21
- ),
22
- }),
23
- async execute({ path }: { path: string }) {
24
- const req = expandTilde(path);
25
- if (isAbsoluteLike(req)) {
26
- const f = Bun.file(req);
27
- if (await f.exists()) {
28
- const content = await f.text();
29
- return { path: req, content, size: content.length };
30
- }
31
- throw new Error(`File not found: ${req}`);
32
- }
33
- const abs = resolveSafePath(projectRoot, req);
34
- const f = Bun.file(abs);
35
- if (await f.exists()) {
36
- const content = await f.text();
37
- return { path: req, content, size: content.length };
38
- }
39
- const embedded = embeddedTextAssets[req];
40
- if (embedded) {
41
- const ef = Bun.file(embedded);
42
- const content = await ef.text();
43
- return { path: req, content, size: content.length };
44
- }
45
- throw new Error(`File not found: ${req}`);
46
- },
47
- });
48
- return { name: 'read', tool: read };
49
- }
@@ -1,8 +0,0 @@
1
- - Read a text file from the workspace
2
- - Accepts absolute ('/...'), home ('~/...'), or project-relative paths
3
- - Returns file text and size in bytes
4
- - May serve embedded text assets for some paths (internal optimization)
5
-
6
- Usage tips:
7
- - Prefer relative project paths when possible (more portable)
8
- - For large files or searches, use the Grep or Ripgrep tool
@@ -1,67 +0,0 @@
1
- import { tool, type Tool } from 'ai';
2
- import { z } from 'zod';
3
- import { spawn } from 'bun';
4
- import { expandTilde, isAbsoluteLike, resolveSafePath } from './util.ts';
5
- import DESCRIPTION from './tree.txt' with { type: 'text' };
6
- import { toIgnoredBasenames } from '../ignore.ts';
7
-
8
- // description imported above
9
-
10
- export function buildTreeTool(projectRoot: string): {
11
- name: string;
12
- tool: Tool;
13
- } {
14
- const tree = tool({
15
- description: DESCRIPTION,
16
- inputSchema: z.object({
17
- path: z.string().default('.'),
18
- depth: z
19
- .number()
20
- .int()
21
- .min(1)
22
- .max(20)
23
- .optional()
24
- .describe('Optional depth limit (defaults to full depth).'),
25
- ignore: z
26
- .array(z.string())
27
- .optional()
28
- .describe('List of directory names/globs to ignore'),
29
- }),
30
- async execute({
31
- path,
32
- depth,
33
- ignore,
34
- }: {
35
- path: string;
36
- depth?: number;
37
- ignore?: string[];
38
- }) {
39
- const req = expandTilde(path || '.');
40
- const start = isAbsoluteLike(req)
41
- ? req
42
- : resolveSafePath(projectRoot, req || '.');
43
- const ignored = toIgnoredBasenames(ignore);
44
- const args = ['tree'];
45
- if (typeof depth === 'number') args.push('-L', String(depth));
46
- if (ignored.size) args.push('-I', Array.from(ignored).join('|'));
47
- args.push('.');
48
-
49
- const proc = spawn({
50
- cmd: args,
51
- cwd: start,
52
- stdout: 'pipe',
53
- stderr: 'pipe',
54
- });
55
- const exitCode = await proc.exited;
56
- const stdout = await new Response(proc.stdout).text();
57
- const stderr = await new Response(proc.stderr).text();
58
- if (exitCode !== 0) {
59
- const message = (stderr || stdout || 'tree failed').trim();
60
- throw new Error(`tree failed for ${req}: ${message}`);
61
- }
62
- const output = stdout.trimEnd();
63
- return { path: req, depth: depth ?? null, tree: output };
64
- },
65
- });
66
- return { name: 'tree', tool: tree };
67
- }
@@ -1,8 +0,0 @@
1
- - Render a shallow directory tree from a starting path
2
- - Accepts absolute, home, or project-relative paths
3
- - Skips common build/cache folders (node_modules, dist, .git, etc.) by default
4
- - Depth is capped to avoid excessive output (1–5)
5
-
6
- Usage tips:
7
- - Use the LS tool for a flat listing of one directory
8
- - Use the Glob and Grep tools for file pattern and content searches respectively
@@ -1,95 +0,0 @@
1
- import { createTwoFilesPatch } from 'diff';
2
- import { resolve as resolvePath } from 'node:path';
3
-
4
- function normalizeForComparison(value: string) {
5
- const withForwardSlashes = value.replace(/\\/g, '/');
6
- return process.platform === 'win32'
7
- ? withForwardSlashes.toLowerCase()
8
- : withForwardSlashes;
9
- }
10
-
11
- export function resolveSafePath(projectRoot: string, p: string) {
12
- const root = resolvePath(projectRoot);
13
- const target = resolvePath(root, p || '.');
14
- const rootNorm = (() => {
15
- const normalized = normalizeForComparison(root);
16
- if (normalized === '/') return '/';
17
- return normalized.replace(/[\\/]+$/, '');
18
- })();
19
- const targetNorm = normalizeForComparison(target);
20
- const rootWithSlash = rootNorm === '/' ? '/' : `${rootNorm}/`;
21
- const inProject =
22
- targetNorm === rootNorm || targetNorm.startsWith(rootWithSlash);
23
- if (!inProject) throw new Error(`Path escapes project root: ${p}`);
24
- return target;
25
- }
26
-
27
- export function expandTilde(p: string): string {
28
- const home = process.env.HOME || process.env.USERPROFILE || '';
29
- if (!home) return p;
30
- if (p === '~') return home;
31
- if (p.startsWith('~/')) return `${home}/${p.slice(2)}`;
32
- return p;
33
- }
34
-
35
- export function isAbsoluteLike(p: string): boolean {
36
- return p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p);
37
- }
38
-
39
- export async function buildWriteArtifact(
40
- relPath: string,
41
- existed: boolean,
42
- oldText: string,
43
- newText: string,
44
- ) {
45
- let patch = '';
46
- try {
47
- patch = createTwoFilesPatch(
48
- `a/${relPath}`,
49
- `b/${relPath}`,
50
- String(oldText ?? ''),
51
- String(newText ?? ''),
52
- '',
53
- '',
54
- { context: 3 },
55
- );
56
- } catch {}
57
- if (!patch || !patch.trim().length) {
58
- const header = existed ? 'Update File' : 'Add File';
59
- const oldLines = String(oldText ?? '').split('\n');
60
- const newLines = String(newText ?? '').split('\n');
61
- const lines: string[] = [];
62
- lines.push('*** Begin Patch');
63
- lines.push(`*** ${header}: ${relPath}`);
64
- lines.push('@@');
65
- if (existed) for (const l of oldLines) lines.push(`-${l}`);
66
- for (const l of newLines) lines.push(`+${l}`);
67
- lines.push('*** End Patch');
68
- patch = lines.join('\n');
69
- }
70
- const { additions, deletions } = summarizePatchCounts(patch);
71
- return {
72
- kind: 'file_diff',
73
- patch,
74
- summary: { files: 1, additions, deletions },
75
- } as const;
76
- }
77
-
78
- export function summarizePatchCounts(patch: string): {
79
- additions: number;
80
- deletions: number;
81
- } {
82
- let adds = 0;
83
- let dels = 0;
84
- for (const line of String(patch || '').split('\n')) {
85
- if (
86
- line.startsWith('+++') ||
87
- line.startsWith('---') ||
88
- line.startsWith('diff ')
89
- )
90
- continue;
91
- if (line.startsWith('+')) adds += 1;
92
- else if (line.startsWith('-')) dels += 1;
93
- }
94
- return { additions: adds, deletions: dels };
95
- }
@@ -1,61 +0,0 @@
1
- import { tool, type Tool } from 'ai';
2
- import { z } from 'zod';
3
- import { $ } from 'bun';
4
- import {
5
- buildWriteArtifact,
6
- resolveSafePath,
7
- expandTilde,
8
- isAbsoluteLike,
9
- } from './util.ts';
10
- import DESCRIPTION from './write.txt' with { type: 'text' };
11
-
12
- // description imported above
13
-
14
- export function buildWriteTool(projectRoot: string): {
15
- name: string;
16
- tool: Tool;
17
- } {
18
- const write = tool({
19
- description: DESCRIPTION,
20
- inputSchema: z.object({
21
- path: z
22
- .string()
23
- .describe(
24
- 'Relative file path within the project. Writes outside the project are not allowed.',
25
- ),
26
- content: z.string().describe('Text content to write'),
27
- createDirs: z.boolean().optional().default(true),
28
- }),
29
- async execute({
30
- path,
31
- content,
32
- createDirs,
33
- }: {
34
- path: string;
35
- content: string;
36
- createDirs?: boolean;
37
- }) {
38
- const req = expandTilde(path);
39
- if (isAbsoluteLike(req)) {
40
- throw new Error(
41
- `Refusing to write outside project root: ${req}. Use a relative path within the project.`,
42
- );
43
- }
44
- const abs = resolveSafePath(projectRoot, req);
45
- if (createDirs) {
46
- await $`mkdir -p ${abs.slice(0, abs.lastIndexOf('/'))}`;
47
- }
48
- let existed = false;
49
- let oldText = '';
50
- try {
51
- const f = Bun.file(abs);
52
- existed = await f.exists();
53
- if (existed) oldText = await f.text();
54
- } catch {}
55
- await Bun.write(abs, content);
56
- const artifact = await buildWriteArtifact(req, existed, oldText, content);
57
- return { path: req, bytes: content.length, artifact } as const;
58
- },
59
- });
60
- return { name: 'write', tool: write };
61
- }
@@ -1,8 +0,0 @@
1
- - Write text to a file in the workspace
2
- - Creates the file if it does not exist (when allowed)
3
- - Only writes within the project root; absolute paths are rejected
4
- - Returns a compact patch artifact summarizing the change
5
-
6
- Usage tips:
7
- - Prefer idempotent writes by providing the full intended content
8
- - For localized edits, consider the Edit tool with structured operations
@@ -1,6 +0,0 @@
1
- - Create a git commit with the provided message
2
- - Supports `amend` and `signoff` flags
3
-
4
- Usage tips:
5
- - Only commit after explicit user approval
6
- - Consider including scope and brief rationale in the message
@@ -1,5 +0,0 @@
1
- - Show git diff
2
- - By default shows staged changes; set `all=true` for full working tree diff
3
-
4
- Usage tips:
5
- - Use with `git_status` to preview changes before committing
@@ -1,5 +0,0 @@
1
- - Show git status in porcelain v1 format summary
2
- - Reports staged and unstaged counts and a capped raw list
3
-
4
- Usage tips:
5
- - Use before committing to understand working tree changes