@stan-chen/simple-cli 0.2.1

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 (87) hide show
  1. package/README.md +287 -0
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.js +259 -0
  4. package/dist/commands/add.d.ts +9 -0
  5. package/dist/commands/add.js +50 -0
  6. package/dist/commands/git/commit.d.ts +12 -0
  7. package/dist/commands/git/commit.js +97 -0
  8. package/dist/commands/git/status.d.ts +6 -0
  9. package/dist/commands/git/status.js +42 -0
  10. package/dist/commands/index.d.ts +16 -0
  11. package/dist/commands/index.js +376 -0
  12. package/dist/commands/mcp/status.d.ts +6 -0
  13. package/dist/commands/mcp/status.js +31 -0
  14. package/dist/commands/swarm.d.ts +36 -0
  15. package/dist/commands/swarm.js +236 -0
  16. package/dist/commands.d.ts +32 -0
  17. package/dist/commands.js +427 -0
  18. package/dist/context.d.ts +116 -0
  19. package/dist/context.js +327 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.js +109 -0
  22. package/dist/lib/agent.d.ts +98 -0
  23. package/dist/lib/agent.js +281 -0
  24. package/dist/lib/editor.d.ts +74 -0
  25. package/dist/lib/editor.js +441 -0
  26. package/dist/lib/git.d.ts +164 -0
  27. package/dist/lib/git.js +351 -0
  28. package/dist/lib/ui.d.ts +159 -0
  29. package/dist/lib/ui.js +252 -0
  30. package/dist/mcp/client.d.ts +22 -0
  31. package/dist/mcp/client.js +81 -0
  32. package/dist/mcp/manager.d.ts +186 -0
  33. package/dist/mcp/manager.js +442 -0
  34. package/dist/prompts/provider.d.ts +22 -0
  35. package/dist/prompts/provider.js +78 -0
  36. package/dist/providers/index.d.ts +15 -0
  37. package/dist/providers/index.js +82 -0
  38. package/dist/providers/multi.d.ts +11 -0
  39. package/dist/providers/multi.js +28 -0
  40. package/dist/registry.d.ts +24 -0
  41. package/dist/registry.js +379 -0
  42. package/dist/repoMap.d.ts +5 -0
  43. package/dist/repoMap.js +79 -0
  44. package/dist/router.d.ts +41 -0
  45. package/dist/router.js +108 -0
  46. package/dist/skills.d.ts +25 -0
  47. package/dist/skills.js +288 -0
  48. package/dist/swarm/coordinator.d.ts +86 -0
  49. package/dist/swarm/coordinator.js +257 -0
  50. package/dist/swarm/index.d.ts +28 -0
  51. package/dist/swarm/index.js +29 -0
  52. package/dist/swarm/task.d.ts +104 -0
  53. package/dist/swarm/task.js +221 -0
  54. package/dist/swarm/types.d.ts +132 -0
  55. package/dist/swarm/types.js +37 -0
  56. package/dist/swarm/worker.d.ts +107 -0
  57. package/dist/swarm/worker.js +299 -0
  58. package/dist/tools/analyzeFile.d.ts +16 -0
  59. package/dist/tools/analyzeFile.js +43 -0
  60. package/dist/tools/git.d.ts +40 -0
  61. package/dist/tools/git.js +236 -0
  62. package/dist/tools/glob.d.ts +34 -0
  63. package/dist/tools/glob.js +165 -0
  64. package/dist/tools/grep.d.ts +53 -0
  65. package/dist/tools/grep.js +296 -0
  66. package/dist/tools/linter.d.ts +35 -0
  67. package/dist/tools/linter.js +349 -0
  68. package/dist/tools/listDir.d.ts +29 -0
  69. package/dist/tools/listDir.js +50 -0
  70. package/dist/tools/memory.d.ts +34 -0
  71. package/dist/tools/memory.js +215 -0
  72. package/dist/tools/readFiles.d.ts +25 -0
  73. package/dist/tools/readFiles.js +31 -0
  74. package/dist/tools/reloadTools.d.ts +11 -0
  75. package/dist/tools/reloadTools.js +22 -0
  76. package/dist/tools/runCommand.d.ts +32 -0
  77. package/dist/tools/runCommand.js +79 -0
  78. package/dist/tools/scraper.d.ts +31 -0
  79. package/dist/tools/scraper.js +211 -0
  80. package/dist/tools/writeFiles.d.ts +63 -0
  81. package/dist/tools/writeFiles.js +87 -0
  82. package/dist/ui/server.d.ts +5 -0
  83. package/dist/ui/server.js +74 -0
  84. package/dist/watcher.d.ts +35 -0
  85. package/dist/watcher.js +164 -0
  86. package/docs/assets/logo.jpeg +0 -0
  87. package/package.json +78 -0
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Tool: readFiles
3
+ * Read contents of one or more files
4
+ */
5
+ import { z } from 'zod';
6
+ export declare const name = "readFiles";
7
+ export declare const description = "Read the contents of one or more files from the filesystem";
8
+ export declare const permission: "read";
9
+ export declare const schema: z.ZodObject<{
10
+ paths: z.ZodArray<z.ZodString, "many">;
11
+ encoding: z.ZodOptional<z.ZodString>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ paths: string[];
14
+ encoding?: string | undefined;
15
+ }, {
16
+ paths: string[];
17
+ encoding?: string | undefined;
18
+ }>;
19
+ interface FileResult {
20
+ path: string;
21
+ content?: string;
22
+ error?: string;
23
+ }
24
+ export declare const execute: (args: Record<string, unknown>) => Promise<FileResult[]>;
25
+ export {};
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Tool: readFiles
3
+ * Read contents of one or more files
4
+ */
5
+ import { readFile } from 'fs/promises';
6
+ import { z } from 'zod';
7
+ export const name = 'readFiles';
8
+ export const description = 'Read the contents of one or more files from the filesystem';
9
+ export const permission = 'read';
10
+ export const schema = z.object({
11
+ paths: z.array(z.string()).describe('Array of file paths to read'),
12
+ encoding: z.string().optional().describe('File encoding (default: utf-8)')
13
+ });
14
+ export const execute = async (args) => {
15
+ const parsed = schema.parse(args);
16
+ const encoding = (parsed.encoding || 'utf-8');
17
+ const results = [];
18
+ for (const path of parsed.paths) {
19
+ try {
20
+ const content = await readFile(path, encoding);
21
+ results.push({ path, content });
22
+ }
23
+ catch (error) {
24
+ results.push({
25
+ path,
26
+ error: error instanceof Error ? error.message : 'Unknown error'
27
+ });
28
+ }
29
+ }
30
+ return results;
31
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tool: reloadTools
3
+ * Reloads all tools, including built-in, MCP, and project-specific skills.
4
+ * Use this after creating a new skill file in the 'skills/' directory.
5
+ */
6
+ import { z } from 'zod';
7
+ export declare const name = "reloadTools";
8
+ export declare const description = "Reload all tools to pick up new skills or changes to existing tools";
9
+ export declare const permission: "read";
10
+ export declare const schema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
11
+ export declare const execute: (_args: Record<string, unknown>) => Promise<string>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Tool: reloadTools
3
+ * Reloads all tools, including built-in, MCP, and project-specific skills.
4
+ * Use this after creating a new skill file in the 'skills/' directory.
5
+ */
6
+ import { z } from 'zod';
7
+ import { getContextManager } from '../context.js';
8
+ export const name = 'reloadTools';
9
+ export const description = 'Reload all tools to pick up new skills or changes to existing tools';
10
+ export const permission = 'read';
11
+ export const schema = z.object({});
12
+ export const execute = async (_args) => {
13
+ const ctx = getContextManager();
14
+ await ctx.initialize();
15
+ const tools = ctx.getTools();
16
+ const summary = Array.from(tools.values()).reduce((acc, tool) => {
17
+ const source = tool.source || 'unknown';
18
+ acc[source] = (acc[source] || 0) + 1;
19
+ return acc;
20
+ }, {});
21
+ return `All tools reloaded successfully.\n\nSummary:\n- Built-in: ${summary.builtin || 0}\n- Project Skills: ${summary.project || 0}\n- MCP Tools: ${summary.mcp || 0}\n\nNewly discovered/updated tools are now ready for use.`;
22
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Tool: runCommand
3
+ * Execute shell commands in a sandboxed environment
4
+ */
5
+ import { z } from 'zod';
6
+ export declare const name = "runCommand";
7
+ export declare const description = "Execute a shell command with timeout and environment restrictions";
8
+ export declare const permission: "execute";
9
+ export declare const schema: z.ZodObject<{
10
+ command: z.ZodString;
11
+ cwd: z.ZodOptional<z.ZodString>;
12
+ timeout: z.ZodOptional<z.ZodNumber>;
13
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ command: string;
16
+ env?: Record<string, string> | undefined;
17
+ timeout?: number | undefined;
18
+ cwd?: string | undefined;
19
+ }, {
20
+ command: string;
21
+ env?: Record<string, string> | undefined;
22
+ timeout?: number | undefined;
23
+ cwd?: string | undefined;
24
+ }>;
25
+ interface CommandResult {
26
+ exitCode: number;
27
+ stdout: string;
28
+ stderr: string;
29
+ timedOut: boolean;
30
+ }
31
+ export declare const execute: (args: Record<string, unknown>) => Promise<CommandResult>;
32
+ export {};
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Tool: runCommand
3
+ * Execute shell commands in a sandboxed environment
4
+ */
5
+ import { spawn } from 'child_process';
6
+ import { z } from 'zod';
7
+ export const name = 'runCommand';
8
+ export const description = 'Execute a shell command with timeout and environment restrictions';
9
+ export const permission = 'execute';
10
+ export const schema = z.object({
11
+ command: z.string().describe('The shell command to execute'),
12
+ cwd: z.string().optional().describe('Working directory for the command'),
13
+ timeout: z.number().optional().describe('Timeout in milliseconds (default: 30000)'),
14
+ env: z.record(z.string()).optional().describe('Additional environment variables')
15
+ });
16
+ // Restricted environment - remove sensitive variables
17
+ const createSafeEnv = (additionalEnv) => {
18
+ // Inherit the full host environment to ensure tools like 'gh', 'git', etc.
19
+ // can access their configuration and credentials.
20
+ const env = { ...process.env };
21
+ // Add any additional env vars
22
+ if (additionalEnv) {
23
+ Object.assign(env, additionalEnv);
24
+ }
25
+ return env;
26
+ };
27
+ export const execute = async (args) => {
28
+ const parsed = schema.parse(args);
29
+ const timeout = parsed.timeout || 30000;
30
+ const env = createSafeEnv(parsed.env);
31
+ return new Promise((resolve) => {
32
+ let stdout = '';
33
+ let stderr = '';
34
+ let timedOut = false;
35
+ const child = spawn(parsed.command, {
36
+ shell: true,
37
+ cwd: parsed.cwd || process.cwd(),
38
+ env,
39
+ stdio: ['ignore', 'pipe', 'pipe']
40
+ });
41
+ const timer = setTimeout(() => {
42
+ timedOut = true;
43
+ child.kill('SIGTERM');
44
+ setTimeout(() => child.kill('SIGKILL'), 1000);
45
+ }, timeout);
46
+ child.stdout?.on('data', (data) => {
47
+ stdout += data.toString();
48
+ // Limit output size
49
+ if (stdout.length > 100000) {
50
+ stdout = stdout.slice(0, 100000) + '\n... (output truncated)';
51
+ child.kill('SIGTERM');
52
+ }
53
+ });
54
+ child.stderr?.on('data', (data) => {
55
+ stderr += data.toString();
56
+ if (stderr.length > 50000) {
57
+ stderr = stderr.slice(0, 50000) + '\n... (output truncated)';
58
+ }
59
+ });
60
+ child.on('close', (code) => {
61
+ clearTimeout(timer);
62
+ resolve({
63
+ exitCode: code ?? 1,
64
+ stdout: stdout.trim(),
65
+ stderr: stderr.trim(),
66
+ timedOut
67
+ });
68
+ });
69
+ child.on('error', (error) => {
70
+ clearTimeout(timer);
71
+ resolve({
72
+ exitCode: 1,
73
+ stdout: '',
74
+ stderr: error.message,
75
+ timedOut: false
76
+ });
77
+ });
78
+ });
79
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Web Scraper Tool - Fetches and converts web content to markdown
3
+ * Uses turndown for robust HTML to Markdown conversion (with regex fallback)
4
+ */
5
+ import { z } from 'zod';
6
+ import type { Tool } from '../registry.js';
7
+ export declare const inputSchema: z.ZodObject<{
8
+ url: z.ZodString;
9
+ convertToMarkdown: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
10
+ verifySSL: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
11
+ timeout: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ url: string;
14
+ timeout: number;
15
+ convertToMarkdown: boolean;
16
+ verifySSL: boolean;
17
+ }, {
18
+ url: string;
19
+ timeout?: number | undefined;
20
+ convertToMarkdown?: boolean | undefined;
21
+ verifySSL?: boolean | undefined;
22
+ }>;
23
+ type ScraperInput = z.infer<typeof inputSchema>;
24
+ export declare function execute(input: ScraperInput): Promise<{
25
+ url: string;
26
+ content: string;
27
+ contentType: string;
28
+ error?: string;
29
+ }>;
30
+ export declare const tool: Tool;
31
+ export {};
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Web Scraper Tool - Fetches and converts web content to markdown
3
+ * Uses turndown for robust HTML to Markdown conversion (with regex fallback)
4
+ */
5
+ import { z } from 'zod';
6
+ // Input schema
7
+ export const inputSchema = z.object({
8
+ url: z.string().url().describe('URL to scrape'),
9
+ convertToMarkdown: z.boolean().optional().default(true).describe('Convert HTML to Markdown'),
10
+ verifySSL: z.boolean().optional().default(true).describe('Verify SSL certificates'),
11
+ timeout: z.number().optional().default(30000).describe('Timeout in milliseconds'),
12
+ });
13
+ // Cached turndown instance
14
+ let turndownInstance = null;
15
+ let turndownLoaded = false;
16
+ // Get or create turndown instance (lazy load)
17
+ async function getTurndown() {
18
+ if (!turndownLoaded) {
19
+ turndownLoaded = true;
20
+ try {
21
+ const mod = await import('turndown');
22
+ const TurndownService = mod.default;
23
+ turndownInstance = new TurndownService({
24
+ headingStyle: 'atx',
25
+ hr: '---',
26
+ bulletListMarker: '-',
27
+ codeBlockStyle: 'fenced',
28
+ emDelimiter: '*',
29
+ strongDelimiter: '**',
30
+ linkStyle: 'inlined',
31
+ });
32
+ // Remove script and style elements
33
+ turndownInstance.remove(['script', 'style', 'noscript', 'iframe', 'svg']);
34
+ // Custom rule for code blocks with language hints
35
+ turndownInstance.addRule('codeBlock', {
36
+ filter: (node) => {
37
+ return (node.nodeName === 'PRE' &&
38
+ node.firstChild !== null &&
39
+ node.firstChild.nodeName === 'CODE');
40
+ },
41
+ replacement: (_content, node) => {
42
+ const codeNode = node.firstChild;
43
+ const className = codeNode.getAttribute?.('class') || '';
44
+ const langMatch = className.match(/language-(\w+)/);
45
+ const lang = langMatch ? langMatch[1] : '';
46
+ const code = codeNode.textContent || '';
47
+ return `\n\n\`\`\`${lang}\n${code.trim()}\n\`\`\`\n\n`;
48
+ },
49
+ });
50
+ }
51
+ catch {
52
+ // turndown not installed, will use fallback
53
+ }
54
+ }
55
+ return turndownInstance;
56
+ }
57
+ // Fallback regex-based HTML to Markdown conversion
58
+ function htmlToMarkdownFallback(html) {
59
+ let md = html;
60
+ // Remove script and style tags
61
+ md = md.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
62
+ md = md.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '');
63
+ // Convert headings
64
+ md = md.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '\n# $1\n');
65
+ md = md.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '\n## $1\n');
66
+ md = md.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '\n### $1\n');
67
+ md = md.replace(/<h4[^>]*>(.*?)<\/h4>/gi, '\n#### $1\n');
68
+ md = md.replace(/<h5[^>]*>(.*?)<\/h5>/gi, '\n##### $1\n');
69
+ md = md.replace(/<h6[^>]*>(.*?)<\/h6>/gi, '\n###### $1\n');
70
+ // Convert paragraphs
71
+ md = md.replace(/<p[^>]*>(.*?)<\/p>/gis, '\n$1\n');
72
+ // Convert links
73
+ md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)');
74
+ // Convert images
75
+ md = md.replace(/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, '![$2]($1)');
76
+ md = md.replace(/<img[^>]*src="([^"]*)"[^>]*\/?>/gi, '![]($1)');
77
+ // Convert bold
78
+ md = md.replace(/<strong[^>]*>(.*?)<\/strong>/gi, '**$1**');
79
+ md = md.replace(/<b[^>]*>(.*?)<\/b>/gi, '**$1**');
80
+ // Convert italic
81
+ md = md.replace(/<em[^>]*>(.*?)<\/em>/gi, '*$1*');
82
+ md = md.replace(/<i[^>]*>(.*?)<\/i>/gi, '*$1*');
83
+ // Convert inline code
84
+ md = md.replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`');
85
+ // Convert code blocks
86
+ md = md.replace(/<pre[^>]*><code[^>]*>(.*?)<\/code><\/pre>/gis, '\n```\n$1\n```\n');
87
+ md = md.replace(/<pre[^>]*>(.*?)<\/pre>/gis, '\n```\n$1\n```\n');
88
+ // Convert lists
89
+ md = md.replace(/<ul[^>]*>(.*?)<\/ul>/gis, (_, content) => {
90
+ return content.replace(/<li[^>]*>(.*?)<\/li>/gi, '- $1\n');
91
+ });
92
+ md = md.replace(/<ol[^>]*>(.*?)<\/ol>/gis, (_, content) => {
93
+ let i = 1;
94
+ return content.replace(/<li[^>]*>(.*?)<\/li>/gi, () => `${i++}. $1\n`);
95
+ });
96
+ // Convert blockquotes
97
+ md = md.replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gis, (_, content) => {
98
+ return content.split('\n').map((line) => `> ${line}`).join('\n');
99
+ });
100
+ // Convert line breaks
101
+ md = md.replace(/<br\s*\/?>/gi, '\n');
102
+ // Convert horizontal rules
103
+ md = md.replace(/<hr\s*\/?>/gi, '\n---\n');
104
+ // Remove remaining HTML tags
105
+ md = md.replace(/<[^>]+>/g, '');
106
+ // Decode HTML entities
107
+ md = md.replace(/&nbsp;/g, ' ');
108
+ md = md.replace(/&amp;/g, '&');
109
+ md = md.replace(/&lt;/g, '<');
110
+ md = md.replace(/&gt;/g, '>');
111
+ md = md.replace(/&quot;/g, '"');
112
+ md = md.replace(/&#39;/g, "'");
113
+ // Clean up whitespace
114
+ md = md.replace(/\n{3,}/g, '\n\n');
115
+ md = md.trim();
116
+ return md;
117
+ }
118
+ // Slim down HTML by removing unnecessary elements before conversion
119
+ function slimDownHtml(html) {
120
+ let slim = html;
121
+ // Remove SVG elements
122
+ slim = slim.replace(/<svg\b[^<]*(?:(?!<\/svg>)<[^<]*)*<\/svg>/gi, '');
123
+ // Remove data: URLs (large embedded content)
124
+ slim = slim.replace(/\s*(?:href|src)=["']data:[^"']*["']/gi, '');
125
+ // Remove inline styles
126
+ slim = slim.replace(/\s*style=["'][^"']*["']/gi, '');
127
+ // Remove tracking pixels and tiny images
128
+ slim = slim.replace(/<img[^>]*(?:width|height)=["']?[01](?:px)?["']?[^>]*>/gi, '');
129
+ return slim;
130
+ }
131
+ // Check if content looks like HTML
132
+ function looksLikeHtml(content) {
133
+ const htmlPatterns = [
134
+ /<!DOCTYPE\s+html/i,
135
+ /<html/i,
136
+ /<head/i,
137
+ /<body/i,
138
+ /<div/i,
139
+ /<p>/i,
140
+ /<a\s+href=/i,
141
+ ];
142
+ return htmlPatterns.some(pattern => pattern.test(content));
143
+ }
144
+ // Convert HTML to Markdown using turndown or fallback
145
+ async function htmlToMarkdown(html) {
146
+ const turndown = await getTurndown();
147
+ if (turndown) {
148
+ try {
149
+ return turndown.turndown(html);
150
+ }
151
+ catch {
152
+ // Fall through to regex fallback
153
+ }
154
+ }
155
+ return htmlToMarkdownFallback(html);
156
+ }
157
+ // Execute scraping
158
+ export async function execute(input) {
159
+ const { url, convertToMarkdown, verifySSL, timeout } = inputSchema.parse(input);
160
+ try {
161
+ const controller = new AbortController();
162
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
163
+ const response = await fetch(url, {
164
+ signal: controller.signal,
165
+ headers: {
166
+ 'User-Agent': 'simplecli/0.2.1 (+https://github.com/stancsz/simple-cli)',
167
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
168
+ },
169
+ });
170
+ clearTimeout(timeoutId);
171
+ if (!response.ok) {
172
+ return {
173
+ url,
174
+ content: '',
175
+ contentType: '',
176
+ error: `HTTP ${response.status}: ${response.statusText}`,
177
+ };
178
+ }
179
+ const contentType = response.headers.get('content-type') || '';
180
+ let content = await response.text();
181
+ // Convert HTML to Markdown if requested
182
+ if (convertToMarkdown && (contentType.includes('text/html') || looksLikeHtml(content))) {
183
+ content = slimDownHtml(content);
184
+ content = await htmlToMarkdown(content);
185
+ // Clean up excessive whitespace
186
+ content = content.replace(/\n{3,}/g, '\n\n').trim();
187
+ }
188
+ return {
189
+ url,
190
+ content,
191
+ contentType,
192
+ };
193
+ }
194
+ catch (error) {
195
+ const errorMessage = error instanceof Error ? error.message : String(error);
196
+ return {
197
+ url,
198
+ content: '',
199
+ contentType: '',
200
+ error: errorMessage.includes('abort') ? 'Request timed out' : errorMessage,
201
+ };
202
+ }
203
+ }
204
+ // Tool definition
205
+ export const tool = {
206
+ name: 'scrapeUrl',
207
+ description: 'Fetch a URL and convert its content to markdown. Useful for reading web pages, documentation, or API responses.',
208
+ inputSchema,
209
+ permission: 'read',
210
+ execute: async (args) => execute(args),
211
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Tool: writeFiles
3
+ * Write or update files with search/replace support (Aider-style)
4
+ */
5
+ import { z } from 'zod';
6
+ export declare const name = "writeFiles";
7
+ export declare const description = "Write or modify files. ALWAYS provide an array of objects in the \"files\" parameter, even for a single file. Each object must have \"path\" and either \"content\" (for full write) or \"searchReplace\" (for edits).";
8
+ export declare const permission: "write";
9
+ export declare const schema: z.ZodObject<{
10
+ files: z.ZodArray<z.ZodObject<{
11
+ path: z.ZodString;
12
+ content: z.ZodOptional<z.ZodString>;
13
+ searchReplace: z.ZodOptional<z.ZodArray<z.ZodObject<{
14
+ search: z.ZodString;
15
+ replace: z.ZodString;
16
+ }, "strip", z.ZodTypeAny, {
17
+ replace: string;
18
+ search: string;
19
+ }, {
20
+ replace: string;
21
+ search: string;
22
+ }>, "many">>;
23
+ }, "strip", z.ZodTypeAny, {
24
+ path: string;
25
+ content?: string | undefined;
26
+ searchReplace?: {
27
+ replace: string;
28
+ search: string;
29
+ }[] | undefined;
30
+ }, {
31
+ path: string;
32
+ content?: string | undefined;
33
+ searchReplace?: {
34
+ replace: string;
35
+ search: string;
36
+ }[] | undefined;
37
+ }>, "many">;
38
+ }, "strip", z.ZodTypeAny, {
39
+ files: {
40
+ path: string;
41
+ content?: string | undefined;
42
+ searchReplace?: {
43
+ replace: string;
44
+ search: string;
45
+ }[] | undefined;
46
+ }[];
47
+ }, {
48
+ files: {
49
+ path: string;
50
+ content?: string | undefined;
51
+ searchReplace?: {
52
+ replace: string;
53
+ search: string;
54
+ }[] | undefined;
55
+ }[];
56
+ }>;
57
+ interface WriteResult {
58
+ path: string;
59
+ success: boolean;
60
+ message: string;
61
+ }
62
+ export declare const execute: (args: Record<string, unknown>) => Promise<WriteResult[]>;
63
+ export {};
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Tool: writeFiles
3
+ * Write or update files with search/replace support (Aider-style)
4
+ */
5
+ import { readFile, writeFile, mkdir } from 'fs/promises';
6
+ import { dirname, resolve } from 'path';
7
+ import { z } from 'zod';
8
+ export const name = 'writeFiles';
9
+ export const description = 'Write or modify files. ALWAYS provide an array of objects in the "files" parameter, even for a single file. Each object must have "path" and either "content" (for full write) or "searchReplace" (for edits).';
10
+ export const permission = 'write';
11
+ const FileWriteSchema = z.object({
12
+ path: z.string().describe('File path to write'),
13
+ content: z.string().optional().describe('Full content to write (for new files or full rewrites)'),
14
+ searchReplace: z.array(z.object({
15
+ search: z.string().describe('Text to search for'),
16
+ replace: z.string().describe('Text to replace with')
17
+ })).optional().describe('Array of search/replace operations for targeted edits')
18
+ });
19
+ export const schema = z.object({
20
+ files: z.array(FileWriteSchema).describe('Array of files to write/modify')
21
+ });
22
+ export const execute = async (args) => {
23
+ const parsed = schema.parse(args);
24
+ const results = [];
25
+ for (const file of parsed.files) {
26
+ try {
27
+ // Ensure directory exists
28
+ await mkdir(dirname(file.path), { recursive: true });
29
+ if (file.content !== undefined) {
30
+ // Full file write
31
+ await writeFile(file.path, file.content, 'utf-8');
32
+ const absPath = resolve(file.path);
33
+ results.push({
34
+ path: file.path,
35
+ success: true,
36
+ message: `File written successfully to ${absPath}`
37
+ });
38
+ }
39
+ else if (file.searchReplace && file.searchReplace.length > 0) {
40
+ // Search/replace operations
41
+ let content = await readFile(file.path, 'utf-8');
42
+ let changesApplied = 0;
43
+ const absPath = resolve(file.path);
44
+ for (const { search, replace } of file.searchReplace) {
45
+ if (content.includes(search)) {
46
+ // Apply replacement to all occurrences
47
+ const newContent = content.replaceAll(search, replace);
48
+ if (newContent !== content) {
49
+ content = newContent;
50
+ changesApplied++;
51
+ }
52
+ }
53
+ }
54
+ if (changesApplied > 0) {
55
+ await writeFile(file.path, content, 'utf-8');
56
+ results.push({
57
+ path: file.path,
58
+ success: true,
59
+ message: `Applied ${changesApplied} search/replace operation(s) to ${absPath}`
60
+ });
61
+ }
62
+ else {
63
+ results.push({
64
+ path: file.path,
65
+ success: false,
66
+ message: `No matching search patterns found in ${absPath}`
67
+ });
68
+ }
69
+ }
70
+ else {
71
+ results.push({
72
+ path: file.path,
73
+ success: false,
74
+ message: 'No content or searchReplace provided'
75
+ });
76
+ }
77
+ }
78
+ catch (error) {
79
+ results.push({
80
+ path: file.path,
81
+ success: false,
82
+ message: error instanceof Error ? error.message : 'Unknown error'
83
+ });
84
+ }
85
+ }
86
+ return results;
87
+ };
@@ -0,0 +1,5 @@
1
+ export interface ServerOptions {
2
+ port: number;
3
+ host: string;
4
+ }
5
+ export declare function startServer(options: ServerOptions): Promise<void>;