@andrebuzeli/git-mcp 10.0.3 → 10.0.5

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 (54) hide show
  1. package/README.md +37 -56
  2. package/dist/index.js +50 -13
  3. package/dist/server-new.d.ts +2 -0
  4. package/dist/server-new.js +224 -0
  5. package/dist/tools/gitArchive.d.ts +0 -8
  6. package/dist/tools/gitArchive.js +4 -12
  7. package/dist/tools/gitBackup.js +7 -7
  8. package/dist/tools/gitFiles-new.d.ts +89 -0
  9. package/dist/tools/gitFiles-new.js +335 -0
  10. package/dist/tools/gitFiles.d.ts +18 -15
  11. package/dist/tools/gitFiles.js +54 -15
  12. package/dist/tools/gitFix.js +7 -7
  13. package/dist/tools/gitFix.tool.js +4 -4
  14. package/dist/tools/gitHistory.d.ts +2 -25
  15. package/dist/tools/gitHistory.js +4 -25
  16. package/dist/tools/gitIgnore.d.ts +2 -14
  17. package/dist/tools/gitIgnore.js +5 -17
  18. package/dist/tools/gitIssues.d.ts +0 -4
  19. package/dist/tools/gitIssues.js +12 -18
  20. package/dist/tools/gitMonitor.js +1 -1
  21. package/dist/tools/gitPackages.d.ts +28 -0
  22. package/dist/tools/gitPackages.js +29 -1
  23. package/dist/tools/gitPulls.d.ts +60 -0
  24. package/dist/tools/gitPulls.js +68 -4
  25. package/dist/tools/gitRelease.d.ts +43 -0
  26. package/dist/tools/gitRelease.js +48 -4
  27. package/dist/tools/gitRemote.d.ts +23 -0
  28. package/dist/tools/gitRemote.js +23 -0
  29. package/dist/tools/gitReset.d.ts +23 -0
  30. package/dist/tools/gitReset.js +23 -0
  31. package/dist/tools/gitStash.d.ts +31 -0
  32. package/dist/tools/gitStash.js +31 -0
  33. package/dist/tools/gitSync.d.ts +6 -2
  34. package/dist/tools/gitSync.js +10 -6
  35. package/dist/tools/gitTags.d.ts +31 -0
  36. package/dist/tools/gitTags.js +31 -0
  37. package/dist/tools/gitUpdate.d.ts +0 -27
  38. package/dist/tools/gitUpdate.js +1 -26
  39. package/dist/tools/gitUpload.d.ts +2 -9
  40. package/dist/tools/gitUpload.js +81 -42
  41. package/dist/tools/gitWorkflow.d.ts +8 -0
  42. package/dist/tools/gitWorkflow.js +37 -3
  43. package/dist/utils/cache.d.ts +96 -0
  44. package/dist/utils/cache.js +208 -0
  45. package/dist/utils/gitAdapter.js +19 -3
  46. package/dist/utils/logger.d.ts +45 -0
  47. package/dist/utils/logger.js +140 -0
  48. package/dist/utils/rateLimiter.d.ts +113 -0
  49. package/dist/utils/rateLimiter.js +257 -0
  50. package/dist/utils/validation.d.ts +115 -0
  51. package/dist/utils/validation.js +270 -0
  52. package/package.json +1 -1
  53. package/dist/config.d.ts +0 -5
  54. package/dist/config.js +0 -35
@@ -0,0 +1,89 @@
1
+ import { Tool } from '../types.js';
2
+ export declare class GitFilesTool implements Tool {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: {
6
+ type: "object";
7
+ properties: {
8
+ action: {
9
+ type: string;
10
+ enum: string[];
11
+ description: string;
12
+ };
13
+ projectPath: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ filePath: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ directoryPath: {
22
+ type: string;
23
+ description: string;
24
+ };
25
+ content: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ comment: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ patterns: {
34
+ type: string;
35
+ items: {
36
+ type: string;
37
+ };
38
+ description: string;
39
+ };
40
+ searchText: {
41
+ type: string;
42
+ description: string;
43
+ };
44
+ query: {
45
+ type: string;
46
+ description: string;
47
+ };
48
+ searchPath: {
49
+ type: string;
50
+ description: string;
51
+ };
52
+ };
53
+ required: string[];
54
+ additionalProperties: boolean;
55
+ };
56
+ handle(params: Record<string, any>): Promise<any>;
57
+ /**
58
+ * Lê arquivo com cache
59
+ */
60
+ private handleRead;
61
+ /**
62
+ * Lista diretório com cache
63
+ */
64
+ private handleList;
65
+ /**
66
+ * Cria arquivo com validação de segurança
67
+ */
68
+ private handleCreate;
69
+ /**
70
+ * Atualiza arquivo com validação de segurança
71
+ */
72
+ private handleUpdate;
73
+ /**
74
+ * Deleta arquivo com validação de segurança
75
+ */
76
+ private handleDelete;
77
+ /**
78
+ * Busca texto em arquivos com cache
79
+ */
80
+ private handleSearch;
81
+ /**
82
+ * Busca recursivamente em diretórios
83
+ */
84
+ private searchInDirectory;
85
+ /**
86
+ * Verifica se arquivo é buscável
87
+ */
88
+ private isSearchableFile;
89
+ }
@@ -0,0 +1,335 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { MCPError } from '../utils/errors.js';
4
+ import { cacheHelpers } from '../utils/cache.js';
5
+ import { Logger } from '../utils/logger.js';
6
+ export class GitFilesTool {
7
+ constructor() {
8
+ this.name = 'git-files';
9
+ this.description = 'Read-only file operations for repositories - local operations only with caching and rate limiting';
10
+ this.inputSchema = {
11
+ type: "object",
12
+ properties: {
13
+ action: {
14
+ type: "string",
15
+ enum: ["read", "list", "create", "update", "delete", "search"],
16
+ description: "File operation to perform"
17
+ },
18
+ projectPath: {
19
+ type: "string",
20
+ description: "Absolute path to the project directory (REQUIRED)"
21
+ },
22
+ filePath: {
23
+ type: "string",
24
+ description: "Relative path to the file within projectPath - required for read, create, update, delete"
25
+ },
26
+ directoryPath: {
27
+ type: "string",
28
+ description: "Relative path to directory for listing - optional, defaults to projectPath root"
29
+ },
30
+ content: {
31
+ type: "string",
32
+ description: "File content to write - required for create and update actions"
33
+ },
34
+ comment: {
35
+ type: "string",
36
+ description: "Comment to add when appending patterns to .gitignore - optional for add action"
37
+ },
38
+ patterns: {
39
+ type: "array",
40
+ items: { type: "string" },
41
+ description: "Array of file patterns - required for add and remove actions"
42
+ },
43
+ searchText: {
44
+ type: "string",
45
+ description: "Text to search for in files - required for search action"
46
+ },
47
+ query: {
48
+ type: "string",
49
+ description: "Alternative name for searchText - required for search action"
50
+ },
51
+ searchPath: {
52
+ type: "string",
53
+ description: "Relative path to search within - optional for search, defaults to projectPath root"
54
+ }
55
+ },
56
+ required: ["projectPath", "action"],
57
+ additionalProperties: true
58
+ };
59
+ }
60
+ async handle(params) {
61
+ const logger = Logger.getInstance();
62
+ const startTime = Date.now();
63
+ try {
64
+ const action = params.action;
65
+ const projectPath = params.projectPath;
66
+ if (!action || !projectPath) {
67
+ throw new MCPError('VALIDATION_ERROR', 'action and projectPath are required');
68
+ }
69
+ logger.debug(`Executing git-files:${action}`, { projectPath, filePath: params.filePath });
70
+ let result;
71
+ switch (action) {
72
+ case 'read':
73
+ result = await this.handleRead(projectPath, params.filePath);
74
+ break;
75
+ case 'list':
76
+ result = await this.handleList(projectPath, params.directoryPath);
77
+ break;
78
+ case 'create':
79
+ result = await this.handleCreate(projectPath, params.filePath, params.content || '');
80
+ // Invalidar cache após modificação
81
+ cacheHelpers.invalidateRepoCache(projectPath);
82
+ break;
83
+ case 'update':
84
+ result = await this.handleUpdate(projectPath, params.filePath, params.content);
85
+ // Invalidar cache após modificação
86
+ cacheHelpers.invalidateRepoCache(projectPath);
87
+ break;
88
+ case 'delete':
89
+ result = await this.handleDelete(projectPath, params.filePath);
90
+ // Invalidar cache após modificação
91
+ cacheHelpers.invalidateRepoCache(projectPath);
92
+ break;
93
+ case 'search':
94
+ result = await this.handleSearch(projectPath, params.searchText || params.query, params.searchPath);
95
+ break;
96
+ default:
97
+ throw new MCPError('VALIDATION_ERROR', `Unknown action: ${action}`);
98
+ }
99
+ const duration = Date.now() - startTime;
100
+ logger.debug(`git-files:${action} completed in ${duration}ms`);
101
+ return result;
102
+ }
103
+ catch (error) {
104
+ const duration = Date.now() - startTime;
105
+ logger.error(`git-files operation failed`, {
106
+ action: params.action,
107
+ projectPath: params.projectPath,
108
+ duration,
109
+ error: error.message
110
+ });
111
+ throw error;
112
+ }
113
+ }
114
+ /**
115
+ * Lê arquivo com cache
116
+ */
117
+ async handleRead(projectPath, filePath) {
118
+ if (!filePath) {
119
+ throw new MCPError('VALIDATION_ERROR', 'filePath is required for read action');
120
+ }
121
+ const fullPath = path.resolve(projectPath, filePath);
122
+ // Verificar segurança: path traversal
123
+ if (!fullPath.startsWith(path.resolve(projectPath))) {
124
+ throw new MCPError('SECURITY_ERROR', 'File path must be within project directory');
125
+ }
126
+ // Leitura de arquivo sem cache por enquanto
127
+ try {
128
+ const content = await fs.readFile(fullPath, 'utf8');
129
+ return { success: true, path: fullPath, content, size: content.length };
130
+ }
131
+ catch (error) {
132
+ if (error.code === 'ENOENT') {
133
+ throw new MCPError('FILE_NOT_FOUND', `File not found: ${filePath}`);
134
+ }
135
+ throw new MCPError('FILE_ERROR', `Failed to read file: ${error.message}`);
136
+ }
137
+ }
138
+ /**
139
+ * Lista diretório com cache
140
+ */
141
+ async handleList(projectPath, directoryPath) {
142
+ const dirPath = directoryPath ? path.join(projectPath, directoryPath) : projectPath;
143
+ // Verificar segurança: path traversal
144
+ if (!dirPath.startsWith(path.resolve(projectPath))) {
145
+ throw new MCPError('SECURITY_ERROR', 'Directory path must be within project directory');
146
+ }
147
+ // Usar cache para listagem de diretórios
148
+ // Listar diretório sem cache por enquanto
149
+ try {
150
+ const files = await fs.readdir(dirPath, { withFileTypes: true });
151
+ return {
152
+ success: true,
153
+ files: files.map(entry => ({
154
+ name: entry.name,
155
+ isDirectory: entry.isDirectory(),
156
+ isFile: entry.isFile(),
157
+ path: path.join(directoryPath || '', entry.name)
158
+ }))
159
+ };
160
+ }
161
+ catch (error) {
162
+ if (error.code === 'ENOENT') {
163
+ throw new MCPError('DIRECTORY_NOT_FOUND', `Directory not found: ${directoryPath || 'root'}`);
164
+ }
165
+ throw new MCPError('DIRECTORY_ERROR', `Failed to list directory: ${error.message}`);
166
+ }
167
+ }
168
+ /**
169
+ * Cria arquivo com validação de segurança
170
+ */
171
+ async handleCreate(projectPath, filePath, content = '') {
172
+ if (!filePath) {
173
+ throw new MCPError('VALIDATION_ERROR', 'filePath is required for create action');
174
+ }
175
+ const targetPath = path.resolve(projectPath, filePath);
176
+ // Verificar segurança: path traversal
177
+ if (!targetPath.startsWith(path.resolve(projectPath))) {
178
+ throw new MCPError('SECURITY_ERROR', 'File path must be within project directory');
179
+ }
180
+ try {
181
+ // Criar diretório se necessário
182
+ const dir = path.dirname(targetPath);
183
+ await fs.mkdir(dir, { recursive: true });
184
+ await fs.writeFile(targetPath, content, 'utf-8');
185
+ return {
186
+ success: true,
187
+ created: true,
188
+ path: targetPath,
189
+ size: content.length,
190
+ };
191
+ }
192
+ catch (error) {
193
+ throw new MCPError('FILE_ERROR', `Failed to create file: ${error.message}`);
194
+ }
195
+ }
196
+ /**
197
+ * Atualiza arquivo com validação de segurança
198
+ */
199
+ async handleUpdate(projectPath, filePath, content) {
200
+ if (!filePath) {
201
+ throw new MCPError('VALIDATION_ERROR', 'filePath is required for update action');
202
+ }
203
+ if (content === undefined) {
204
+ throw new MCPError('VALIDATION_ERROR', 'content is required for update action');
205
+ }
206
+ const targetPath = path.resolve(projectPath, filePath);
207
+ // Verificar segurança: path traversal
208
+ if (!targetPath.startsWith(path.resolve(projectPath))) {
209
+ throw new MCPError('SECURITY_ERROR', 'File path must be within project directory');
210
+ }
211
+ try {
212
+ // Verificar se arquivo existe
213
+ await fs.access(targetPath);
214
+ await fs.writeFile(targetPath, content, 'utf-8');
215
+ return {
216
+ success: true,
217
+ updated: true,
218
+ path: targetPath,
219
+ size: content.length,
220
+ };
221
+ }
222
+ catch (error) {
223
+ if (error.code === 'ENOENT') {
224
+ throw new MCPError('FILE_NOT_FOUND', 'File does not exist. Use create action.');
225
+ }
226
+ throw new MCPError('FILE_ERROR', `Failed to update file: ${error.message}`);
227
+ }
228
+ }
229
+ /**
230
+ * Deleta arquivo com validação de segurança
231
+ */
232
+ async handleDelete(projectPath, filePath) {
233
+ if (!filePath) {
234
+ throw new MCPError('VALIDATION_ERROR', 'filePath is required for delete action');
235
+ }
236
+ const targetPath = path.resolve(projectPath, filePath);
237
+ // Verificar segurança: path traversal
238
+ if (!targetPath.startsWith(path.resolve(projectPath))) {
239
+ throw new MCPError('SECURITY_ERROR', 'File path must be within project directory');
240
+ }
241
+ try {
242
+ await fs.unlink(targetPath);
243
+ return {
244
+ success: true,
245
+ deleted: true,
246
+ path: targetPath,
247
+ };
248
+ }
249
+ catch (error) {
250
+ if (error.code === 'ENOENT') {
251
+ throw new MCPError('FILE_NOT_FOUND', `File not found: ${filePath}`);
252
+ }
253
+ throw new MCPError('FILE_ERROR', `Failed to delete file: ${error.message}`);
254
+ }
255
+ }
256
+ /**
257
+ * Busca texto em arquivos com cache
258
+ */
259
+ async handleSearch(projectPath, searchText, searchPath) {
260
+ if (!searchText) {
261
+ throw new MCPError('VALIDATION_ERROR', 'searchText or query is required for search action');
262
+ }
263
+ const searchDir = searchPath ? path.join(projectPath, searchPath) : projectPath;
264
+ // Verificar segurança: path traversal
265
+ if (!searchDir.startsWith(path.resolve(projectPath))) {
266
+ throw new MCPError('SECURITY_ERROR', 'Search path must be within project directory');
267
+ }
268
+ // Busca sem cache por enquanto
269
+ try {
270
+ const results = await this.searchInDirectory(searchDir, searchText);
271
+ return {
272
+ success: true,
273
+ searchText,
274
+ searchPath: searchPath || 'root',
275
+ results: results.slice(0, 50), // Limitar resultados para performance
276
+ totalResults: results.length,
277
+ };
278
+ }
279
+ catch (error) {
280
+ throw new MCPError('SEARCH_ERROR', `Failed to search files: ${error.message}`);
281
+ }
282
+ }
283
+ /**
284
+ * Busca recursivamente em diretórios
285
+ */
286
+ async searchInDirectory(dir, searchText) {
287
+ const results = [];
288
+ try {
289
+ const entries = await fs.readdir(dir, { withFileTypes: true });
290
+ for (const entry of entries) {
291
+ const fullPath = path.join(dir, entry.name);
292
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
293
+ // Recursivamente buscar em subdiretórios
294
+ const subResults = await this.searchInDirectory(fullPath, searchText);
295
+ results.push(...subResults);
296
+ }
297
+ else if (entry.isFile() && this.isSearchableFile(entry.name)) {
298
+ // Buscar em arquivos
299
+ try {
300
+ const content = await fs.readFile(fullPath, 'utf8');
301
+ const lines = content.split('\n');
302
+ lines.forEach((line, index) => {
303
+ if (line.toLowerCase().includes(searchText.toLowerCase())) {
304
+ results.push({
305
+ file: fullPath,
306
+ line: index + 1,
307
+ text: line.trim(),
308
+ });
309
+ }
310
+ });
311
+ }
312
+ catch (error) {
313
+ // Ignorar erros de leitura (arquivos binários, etc.)
314
+ }
315
+ }
316
+ }
317
+ }
318
+ catch (error) {
319
+ // Ignorar erros de acesso
320
+ }
321
+ return results;
322
+ }
323
+ /**
324
+ * Verifica se arquivo é buscável
325
+ */
326
+ isSearchableFile(filename) {
327
+ const searchableExtensions = [
328
+ '.js', '.ts', '.jsx', '.tsx', '.json', '.md', '.txt', '.yml', '.yaml',
329
+ '.html', '.css', '.scss', '.sass', '.less', '.xml', '.sh', '.bat',
330
+ '.py', '.java', '.cpp', '.c', '.h', '.php', '.rb', '.go', '.rs'
331
+ ];
332
+ const ext = path.extname(filename).toLowerCase();
333
+ return searchableExtensions.includes(ext);
334
+ }
335
+ }
@@ -62,8 +62,9 @@ export declare class GitFilesTool implements Tool {
62
62
  size?: undefined;
63
63
  updated?: undefined;
64
64
  deleted?: undefined;
65
- matches?: undefined;
66
- count?: undefined;
65
+ searchText?: undefined;
66
+ results?: undefined;
67
+ totalResults?: undefined;
67
68
  } | {
68
69
  success: boolean;
69
70
  files: string[];
@@ -73,8 +74,9 @@ export declare class GitFilesTool implements Tool {
73
74
  size?: undefined;
74
75
  updated?: undefined;
75
76
  deleted?: undefined;
76
- matches?: undefined;
77
- count?: undefined;
77
+ searchText?: undefined;
78
+ results?: undefined;
79
+ totalResults?: undefined;
78
80
  } | {
79
81
  success: boolean;
80
82
  created: boolean;
@@ -84,8 +86,9 @@ export declare class GitFilesTool implements Tool {
84
86
  files?: undefined;
85
87
  updated?: undefined;
86
88
  deleted?: undefined;
87
- matches?: undefined;
88
- count?: undefined;
89
+ searchText?: undefined;
90
+ results?: undefined;
91
+ totalResults?: undefined;
89
92
  } | {
90
93
  success: boolean;
91
94
  updated: boolean;
@@ -95,8 +98,9 @@ export declare class GitFilesTool implements Tool {
95
98
  files?: undefined;
96
99
  created?: undefined;
97
100
  deleted?: undefined;
98
- matches?: undefined;
99
- count?: undefined;
101
+ searchText?: undefined;
102
+ results?: undefined;
103
+ totalResults?: undefined;
100
104
  } | {
101
105
  success: boolean;
102
106
  deleted: boolean;
@@ -106,15 +110,14 @@ export declare class GitFilesTool implements Tool {
106
110
  created?: undefined;
107
111
  size?: undefined;
108
112
  updated?: undefined;
109
- matches?: undefined;
110
- count?: undefined;
113
+ searchText?: undefined;
114
+ results?: undefined;
115
+ totalResults?: undefined;
111
116
  } | {
112
117
  success: boolean;
113
- matches: {
114
- file: string;
115
- snippet: string;
116
- }[];
117
- count: number;
118
+ searchText: any;
119
+ results: any[];
120
+ totalResults: number;
118
121
  path?: undefined;
119
122
  content?: undefined;
120
123
  files?: undefined;
@@ -146,29 +146,68 @@ export class GitFilesTool {
146
146
  }
147
147
  catch (error) {
148
148
  if (error.code === 'ENOENT') {
149
- throw new MCPError('FILE_NOT_FOUND', 'File does not exist');
149
+ throw new MCPError('FILE_NOT_FOUND', `File not found: ${filePath}`);
150
150
  }
151
151
  throw new MCPError('FILE_ERROR', `Failed to delete file: ${error.message}`);
152
152
  }
153
153
  }
154
154
  if (action === 'search') {
155
- // Simple string search within files in projectPath (non-recursive)
156
- const searchPath = params.searchPath ? path.join(projectPath, params.searchPath) : projectPath;
157
- const files = await fs.readdir(searchPath);
158
- const q = params.searchText ?? params.query ?? '';
159
- const matches = [];
160
- for (const f of files) {
155
+ const searchText = params.searchText || params.query;
156
+ if (!searchText)
157
+ throw new MCPError('VALIDATION_ERROR', 'searchText or query is required');
158
+ const searchPath = params.searchPath || projectPath;
159
+ const searchDir = path.join(projectPath, searchPath);
160
+ // Security: ensure search path is within projectPath
161
+ if (!searchDir.startsWith(path.resolve(projectPath))) {
162
+ throw new MCPError('SECURITY_ERROR', 'Search path must be within project directory');
163
+ }
164
+ const results = [];
165
+ const searchInDirectory = async (dir) => {
161
166
  try {
162
- const txt = await fs.readFile(path.join(searchPath, f), 'utf8');
163
- if (txt.includes(q))
164
- matches.push({ file: f, snippet: txt.substring(txt.indexOf(q), txt.indexOf(q) + 200) });
167
+ const entries = await fs.readdir(dir, { withFileTypes: true });
168
+ for (const entry of entries) {
169
+ const fullPath = path.join(dir, entry.name);
170
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
171
+ await searchInDirectory(fullPath);
172
+ }
173
+ else if (entry.isFile()) {
174
+ try {
175
+ const content = await fs.readFile(fullPath, 'utf8');
176
+ const lines = content.split('\n');
177
+ lines.forEach((line, index) => {
178
+ if (line.toLowerCase().includes(searchText.toLowerCase())) {
179
+ results.push({
180
+ file: fullPath,
181
+ line: index + 1,
182
+ text: line.trim(),
183
+ });
184
+ }
185
+ });
186
+ }
187
+ catch (error) {
188
+ // Ignore files that can't be read (binary, etc.)
189
+ }
190
+ }
191
+ }
165
192
  }
166
- catch (e) {
167
- // ignore binary/unreadable
193
+ catch (error) {
194
+ // Ignore directories that can't be accessed
168
195
  }
169
- }
170
- return { success: true, matches, count: matches.length };
196
+ };
197
+ await searchInDirectory(searchDir);
198
+ return {
199
+ success: true,
200
+ searchText,
201
+ results: results.slice(0, 50), // Limit results for performance
202
+ totalResults: results.length,
203
+ };
204
+ return {
205
+ success: true,
206
+ searchText,
207
+ results: results.slice(0, 50), // Limit results for performance
208
+ totalResults: results.length,
209
+ };
171
210
  }
172
- throw new MCPError('VALIDATION_ERROR', `Unsupported action: ${action}`);
211
+ throw new MCPError('VALIDATION_ERROR', `Unknown action: ${action}`);
173
212
  }
174
213
  }
@@ -2,6 +2,7 @@ import simpleGit from 'simple-git';
2
2
  import path from 'path';
3
3
  import fs from 'fs/promises';
4
4
  import { existsSync } from 'fs';
5
+ import { buildGitHubUrl, buildGiteaUrl } from '../utils/repoHelpers.js';
5
6
  export async function handleGitFix(args) {
6
7
  try {
7
8
  const { projectPath, githubRepo, giteaRepo, autoDetect = true } = args;
@@ -106,16 +107,15 @@ export async function handleGitFix(args) {
106
107
  }
107
108
  }
108
109
  // Adicionar novos remotes no padrão dual
109
- const giteaUrl = process.env.GITEA_URL || 'http://nas-ubuntu:9999';
110
- const githubUrl = `https://github.com/${finalGithubRepo}.git`;
111
- const giteaRepoUrl = `${giteaUrl}/${finalGiteaRepo}.git`;
110
+ const githubUrl = buildGitHubUrl(finalGithubRepo.split('/')[0], finalGithubRepo.split('/')[1]);
111
+ const giteaUrlFull = buildGiteaUrl(finalGiteaRepo.split('/')[0], finalGiteaRepo.split('/')[1]);
112
112
  await git.addRemote('github', githubUrl);
113
- result.fixed.push(`✅ Adicionado remote GitHub: ${githubUrl}`);
114
- await git.addRemote('gitea', giteaRepoUrl);
115
- result.fixed.push(`✅ Adicionado remote Gitea: ${giteaRepoUrl}`);
113
+ result.fixed.push(`✅ Adicionado remote GitHub: ${githubUrl.replace(/oauth2:[^@]+@/, 'oauth2:***@')}`);
114
+ await git.addRemote('gitea', giteaUrlFull);
115
+ result.fixed.push(`✅ Adicionado remote Gitea: ${giteaUrlFull.replace(/:[^:]+@/, ':***@')}`);
116
116
  // Configurar origin como push múltiplo
117
117
  await git.addRemote('origin', githubUrl);
118
- await git.addConfig('remote.origin.pushurl', giteaRepoUrl, false, 'local');
118
+ await git.addConfig('remote.origin.pushurl', giteaUrlFull, false, 'local');
119
119
  result.fixed.push(`✅ Configurado origin para push dual (GitHub + Gitea)`);
120
120
  // Capturar remotes depois
121
121
  const remotesAfter = await git.getRemotes(true);
@@ -67,19 +67,19 @@ export class GitFixTool {
67
67
  properties: {
68
68
  projectPath: {
69
69
  type: "string",
70
- description: "Absolute path to the Git repository (REQUIRED)"
70
+ description: "The absolute path to the Git repository directory on the local filesystem. This should be the root directory containing the .git folder. For example: '/home/user/my-project' on Linux/Mac or 'C:\\Users\\user\\my-project' on Windows."
71
71
  },
72
72
  githubRepo: {
73
73
  type: "string",
74
- description: "GitHub repo in format 'owner/repo' (optional, auto-detected)"
74
+ description: "GitHub repo in format \"owner/repo\" (auto-detected if not provided)"
75
75
  },
76
76
  giteaRepo: {
77
77
  type: "string",
78
- description: "Gitea repo in format 'owner/repo' (optional, auto-detected)"
78
+ description: "Gitea repo in format \"owner/repo\" (auto-detected if not provided)"
79
79
  },
80
80
  autoDetect: {
81
81
  type: "boolean",
82
- description: "Auto-detect repos from existing remotes (optional, default: true)"
82
+ description: "Auto-detect repos from existing remotes (default: true)"
83
83
  }
84
84
  },
85
85
  required: ["projectPath"],
@@ -10,36 +10,13 @@ export declare class GitHistoryTool implements Tool {
10
10
  inputSchema: {
11
11
  type: "object";
12
12
  properties: {
13
- action: {
14
- type: string;
15
- enum: string[];
16
- description: string;
17
- };
18
13
  projectPath: {
19
14
  type: string;
20
15
  description: string;
21
16
  };
22
- description: {
23
- type: string;
24
- description: string;
25
- };
26
- message: {
27
- type: string;
28
- description: string;
29
- };
30
- title: {
31
- type: string;
32
- description: string;
33
- };
34
- tags: {
35
- type: string;
36
- items: {
37
- type: string;
38
- };
39
- description: string;
40
- };
41
- category: {
17
+ action: {
42
18
  type: string;
19
+ enum: string[];
43
20
  description: string;
44
21
  };
45
22
  owner: {