vibe-weaver 1.0.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.
@@ -0,0 +1,279 @@
1
+ /**
2
+ * File Tools for Vibe-Weaver CLI
3
+ * Real file operations against local filesystem
4
+ */
5
+
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+ import chalk from 'chalk';
9
+ import { generateDiff } from '../lib/code-parser.js';
10
+
11
+ export interface FileOperationResult {
12
+ success: boolean;
13
+ path: string;
14
+ content?: string;
15
+ error?: string;
16
+ diff?: string;
17
+ }
18
+
19
+ /**
20
+ * Read a file from the local filesystem
21
+ */
22
+ export async function readFile(filePath: string, baseDir: string = process.cwd()): Promise<FileOperationResult> {
23
+ try {
24
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
25
+ const content = await fs.readFile(fullPath, 'utf-8');
26
+ return { success: true, path: filePath, content };
27
+ } catch (error) {
28
+ return {
29
+ success: false,
30
+ path: filePath,
31
+ error: error instanceof Error ? error.message : String(error)
32
+ };
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Write content to a file
38
+ */
39
+ export async function writeFile(
40
+ filePath: string,
41
+ content: string,
42
+ baseDir: string = process.cwd(),
43
+ options: { dryRun?: boolean; showDiff?: boolean } = {}
44
+ ): Promise<FileOperationResult> {
45
+ try {
46
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
47
+ const dir = path.dirname(fullPath);
48
+
49
+ // Check if file exists for diff
50
+ let oldContent = '';
51
+ try {
52
+ oldContent = await fs.readFile(fullPath, 'utf-8');
53
+ } catch {
54
+ // File doesn't exist, that's fine
55
+ }
56
+
57
+ // Generate diff if requested and file exists
58
+ const diff = options.showDiff && oldContent ? generateDiff(oldContent, content, filePath) : undefined;
59
+
60
+ if (options.dryRun) {
61
+ return { success: true, path: filePath, diff };
62
+ }
63
+
64
+ // Create directory if needed
65
+ await fs.mkdir(dir, { recursive: true });
66
+
67
+ // Write file
68
+ await fs.writeFile(fullPath, content, 'utf-8');
69
+
70
+ return { success: true, path: filePath, diff };
71
+ } catch (error) {
72
+ return {
73
+ success: false,
74
+ path: filePath,
75
+ error: error instanceof Error ? error.message : String(error)
76
+ };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Edit a file using search/replace
82
+ */
83
+ export async function editFile(
84
+ filePath: string,
85
+ search: string,
86
+ replace: string,
87
+ baseDir: string = process.cwd(),
88
+ options: { dryRun?: boolean; showDiff?: boolean } = {}
89
+ ): Promise<FileOperationResult> {
90
+ try {
91
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
92
+
93
+ // Read current content
94
+ const content = await fs.readFile(fullPath, 'utf-8');
95
+
96
+ // Check if search string exists
97
+ if (!content.includes(search)) {
98
+ return {
99
+ success: false,
100
+ path: filePath,
101
+ error: `Search string not found in file`
102
+ };
103
+ }
104
+
105
+ // Generate diff
106
+ const newContent = content.replace(search, replace);
107
+ const diff = options.showDiff ? generateDiff(content, newContent, filePath) : undefined;
108
+
109
+ if (options.dryRun) {
110
+ return { success: true, path: filePath, diff };
111
+ }
112
+
113
+ // Write updated content
114
+ await fs.writeFile(fullPath, newContent, 'utf-8');
115
+
116
+ return { success: true, path: filePath, diff };
117
+ } catch (error) {
118
+ return {
119
+ success: false,
120
+ path: filePath,
121
+ error: error instanceof Error ? error.message : String(error)
122
+ };
123
+ }
124
+ }
125
+
126
+ /**
127
+ * List files in a directory
128
+ */
129
+ export async function listDir(dirPath: string = '.', baseDir: string = process.cwd()): Promise<{ path: string; isDirectory: boolean }[]> {
130
+ const fullPath = path.isAbsolute(dirPath) ? dirPath : path.join(baseDir, dirPath);
131
+
132
+ try {
133
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
134
+ return entries.map(entry => ({
135
+ path: entry.name,
136
+ isDirectory: entry.isDirectory()
137
+ }));
138
+ } catch (error) {
139
+ throw new Error(`Failed to list directory: ${error instanceof Error ? error.message : String(error)}`);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Search for a pattern in files
145
+ */
146
+ export async function grep(
147
+ pattern: string,
148
+ baseDir: string = process.cwd(),
149
+ options: { filePattern?: string; recursive?: boolean } = {}
150
+ ): Promise<{ file: string; line: number; content: string }[]> {
151
+ const results: { file: string; line: number; content: string }[] = [];
152
+ const fs = await import('fs/promises');
153
+
154
+ async function searchDir(dir: string) {
155
+ const entries = await fs.readdir(dir, { withFileTypes: true });
156
+
157
+ for (const entry of entries) {
158
+ const fullPath = path.join(dir, entry.name);
159
+
160
+ // Skip node_modules, .git, etc.
161
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) {
162
+ continue;
163
+ }
164
+
165
+ if (entry.isDirectory() && options.recursive !== false) {
166
+ await searchDir(fullPath);
167
+ } else if (entry.isFile()) {
168
+ // Check file pattern
169
+ if (options.filePattern && !new RegExp(options.filePattern).test(entry.name)) {
170
+ continue;
171
+ }
172
+
173
+ // Search in file
174
+ try {
175
+ const content = await fs.readFile(fullPath, 'utf-8');
176
+ const lines = content.split('\n');
177
+
178
+ for (let i = 0; i < lines.length; i++) {
179
+ if (lines[i].includes(pattern)) {
180
+ results.push({
181
+ file: path.relative(baseDir, fullPath),
182
+ line: i + 1,
183
+ content: lines[i].trim()
184
+ });
185
+ }
186
+ }
187
+ } catch {
188
+ // Skip binary files or unreadable files
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ await searchDir(baseDir);
195
+ return results;
196
+ }
197
+
198
+ /**
199
+ * Find files matching a glob pattern
200
+ */
201
+ export async function glob(
202
+ pattern: string,
203
+ baseDir: string = process.cwd()
204
+ ): Promise<string[]> {
205
+ const results: string[] = [];
206
+ const patternParts = pattern.split('/');
207
+ const regexPattern = patternParts.map(part => {
208
+ if (part === '**') return '.*';
209
+ if (part === '*') return '[^/]*';
210
+ return part.replace(/\./g, '\\.').replace(/\*/g, '.*');
211
+ }).join('/');
212
+
213
+ const regex = new RegExp(`^${regexPattern}$`);
214
+
215
+ async function searchDir(dir: string) {
216
+ const entries = await fs.readdir(dir, { withFileTypes: true });
217
+
218
+ for (const entry of entries) {
219
+ const fullPath = path.join(dir, entry.name);
220
+ const relativePath = path.relative(baseDir, fullPath);
221
+
222
+ // Skip node_modules, .git, etc.
223
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) {
224
+ continue;
225
+ }
226
+
227
+ if (entry.isDirectory()) {
228
+ await searchDir(fullPath);
229
+ } else if (entry.isFile()) {
230
+ if (regex.test(relativePath) || regex.test(entry.name)) {
231
+ results.push(relativePath);
232
+ }
233
+ }
234
+ }
235
+ }
236
+
237
+ await searchDir(baseDir);
238
+ return results;
239
+ }
240
+
241
+ /**
242
+ * Check if a file exists
243
+ */
244
+ export async function fileExists(filePath: string, baseDir: string = process.cwd()): Promise<boolean> {
245
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
246
+ try {
247
+ await fs.access(fullPath);
248
+ return true;
249
+ } catch {
250
+ return false;
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Get file stats
256
+ */
257
+ export async function getFileStats(filePath: string, baseDir: string = process.cwd()): Promise<{ size: number; modified: Date } | null> {
258
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
259
+ try {
260
+ const stats = await fs.stat(fullPath);
261
+ return {
262
+ size: stats.size,
263
+ modified: stats.mtime
264
+ };
265
+ } catch {
266
+ return null;
267
+ }
268
+ }
269
+
270
+ export default {
271
+ readFile,
272
+ writeFile,
273
+ editFile,
274
+ listDir,
275
+ grep,
276
+ glob,
277
+ fileExists,
278
+ getFileStats
279
+ };