@andrebuzeli/git-mcp 7.0.3 → 7.2.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/dist/index.js CHANGED
@@ -4,9 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
8
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
- const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const index_1 = require("@modelcontextprotocol/sdk/server/index");
8
+ const stdio_1 = require("@modelcontextprotocol/sdk/server/stdio");
9
+ const types_1 = require("@modelcontextprotocol/sdk/types");
10
10
  const providerManager_1 = require("./providers/providerManager");
11
11
  const config_1 = require("./config");
12
12
  const gitFiles_1 = require("./tools/gitFiles");
@@ -30,6 +30,7 @@ const gitUpload_1 = require("./tools/gitUpload");
30
30
  const gitUpdate_1 = require("./tools/gitUpdate");
31
31
  const gitHistory_1 = require("./tools/gitHistory");
32
32
  const gitFix_tool_1 = require("./tools/gitFix.tool");
33
+ const gitIgnore_1 = require("./tools/gitIgnore");
33
34
  const toolsGuide_1 = __importDefault(require("./resources/toolsGuide"));
34
35
  async function main() {
35
36
  // Load optional mcp.json configuration (will populate process.env if values present)
@@ -41,7 +42,7 @@ async function main() {
41
42
  if (process.env.DEBUG) {
42
43
  console.error('Provider validation:', JSON.stringify(validation, null, 2));
43
44
  }
44
- // Register all 21 Git tools
45
+ // Register all 22 Git tools
45
46
  const tools = [
46
47
  new gitWorkflow_1.GitWorkflowTool(),
47
48
  new gitFiles_1.GitFilesTool(),
@@ -64,6 +65,7 @@ async function main() {
64
65
  new gitUpdate_1.GitUpdateTool(),
65
66
  new gitHistory_1.GitHistoryTool(),
66
67
  new gitFix_tool_1.GitFixTool(),
68
+ new gitIgnore_1.GitIgnoreTool(),
67
69
  ];
68
70
  // Register resources
69
71
  const resources = [
@@ -75,12 +77,12 @@ async function main() {
75
77
  console.error(`Registered ${resources.length} resource(s)`);
76
78
  }
77
79
  // Create MCP Server with STDIO transport
78
- const server = new index_js_1.Server({
80
+ const server = new index_1.Server({
79
81
  name: '@andrebuzeli/git-mcp',
80
- version: '6.3.2',
82
+ version: '7.2.0',
81
83
  });
82
84
  // Register tool list handler
83
- server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
85
+ server.setRequestHandler(types_1.ListToolsRequestSchema, async () => {
84
86
  return {
85
87
  tools: tools.map(tool => ({
86
88
  name: tool.name,
@@ -94,7 +96,7 @@ async function main() {
94
96
  };
95
97
  });
96
98
  // Register tool execution handler
97
- server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
99
+ server.setRequestHandler(types_1.CallToolRequestSchema, async (request) => {
98
100
  const toolName = request.params.name;
99
101
  const tool = tools.find(t => t.name === toolName);
100
102
  if (!tool) {
@@ -124,7 +126,7 @@ async function main() {
124
126
  }
125
127
  });
126
128
  // Register resource list handler
127
- server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
129
+ server.setRequestHandler(types_1.ListResourcesRequestSchema, async () => {
128
130
  return {
129
131
  resources: resources.map(resource => ({
130
132
  uri: resource.uri,
@@ -135,7 +137,7 @@ async function main() {
135
137
  };
136
138
  });
137
139
  // Register resource read handler
138
- server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
140
+ server.setRequestHandler(types_1.ReadResourceRequestSchema, async (request) => {
139
141
  const uri = request.params.uri;
140
142
  const resource = resources.find(r => r.uri === uri);
141
143
  if (!resource) {
@@ -152,7 +154,7 @@ async function main() {
152
154
  };
153
155
  });
154
156
  // Start server with STDIO transport
155
- const transport = new stdio_js_1.StdioServerTransport();
157
+ const transport = new stdio_1.StdioServerTransport();
156
158
  await server.connect(transport);
157
159
  // Only log in debug mode
158
160
  if (process.env.DEBUG) {
@@ -2,5 +2,55 @@ import { Tool, MCPContext } from '../types';
2
2
  export declare class GitArchiveTool implements Tool {
3
3
  name: string;
4
4
  description: string;
5
- handle(params: Record<string, any>, ctx: MCPContext): Promise<void>;
5
+ handle(params: Record<string, any>, ctx: MCPContext): Promise<{
6
+ success: boolean;
7
+ archivePath: string;
8
+ message: string;
9
+ archives?: undefined;
10
+ count?: undefined;
11
+ archivesDir?: undefined;
12
+ valid?: undefined;
13
+ archive?: undefined;
14
+ error?: undefined;
15
+ } | {
16
+ success: boolean;
17
+ archives: string[];
18
+ count: number;
19
+ archivesDir: any;
20
+ archivePath?: undefined;
21
+ message?: undefined;
22
+ valid?: undefined;
23
+ archive?: undefined;
24
+ error?: undefined;
25
+ } | {
26
+ success: boolean;
27
+ archives: never[];
28
+ count: number;
29
+ message: string;
30
+ archivePath?: undefined;
31
+ archivesDir?: undefined;
32
+ valid?: undefined;
33
+ archive?: undefined;
34
+ error?: undefined;
35
+ } | {
36
+ success: boolean;
37
+ valid: boolean;
38
+ archive: any;
39
+ message: string;
40
+ archivePath?: undefined;
41
+ archives?: undefined;
42
+ count?: undefined;
43
+ archivesDir?: undefined;
44
+ error?: undefined;
45
+ } | {
46
+ success: boolean;
47
+ valid: boolean;
48
+ error: any;
49
+ archivePath?: undefined;
50
+ message?: undefined;
51
+ archives?: undefined;
52
+ count?: undefined;
53
+ archivesDir?: undefined;
54
+ archive?: undefined;
55
+ }>;
6
56
  }
@@ -1,11 +1,16 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.GitArchiveTool = void 0;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
4
9
  const errors_1 = require("../utils/errors");
5
10
  class GitArchiveTool {
6
11
  constructor() {
7
12
  this.name = 'git-archive';
8
- this.description = 'Repository archive operations - local operations';
13
+ this.description = 'Repository archive operations';
9
14
  }
10
15
  async handle(params, ctx) {
11
16
  const action = params.action;
@@ -14,11 +19,74 @@ class GitArchiveTool {
14
19
  throw new errors_1.MCPError('VALIDATION_ERROR', 'action and projectPath are required');
15
20
  }
16
21
  switch (action) {
17
- case 'create':
22
+ case 'create': {
23
+ const archiveName = params.archiveName || `archive-${Date.now()}.json`;
24
+ const outputPath = params.outputPath || path_1.default.join(projectPath, '.git-archives');
25
+ try {
26
+ // Criar diretório de archives se não existir
27
+ await promises_1.default.mkdir(outputPath, { recursive: true });
28
+ const archivePath = path_1.default.join(outputPath, archiveName);
29
+ const archiveData = {
30
+ projectPath,
31
+ timestamp: new Date().toISOString(),
32
+ type: 'git-archive',
33
+ };
34
+ await promises_1.default.writeFile(archivePath, JSON.stringify(archiveData, null, 2));
35
+ return {
36
+ success: true,
37
+ archivePath,
38
+ message: 'Archive created successfully',
39
+ };
40
+ }
41
+ catch (error) {
42
+ throw new errors_1.MCPError('FILE_ERROR', `Failed to create archive: ${error.message}`);
43
+ }
44
+ }
45
+ case 'list': {
46
+ const archivesDir = params.archivesDir || path_1.default.join(projectPath, '.git-archives');
47
+ try {
48
+ const files = await promises_1.default.readdir(archivesDir);
49
+ const archives = files.filter(f => f.endsWith('.json'));
50
+ return {
51
+ success: true,
52
+ archives,
53
+ count: archives.length,
54
+ archivesDir,
55
+ };
56
+ }
57
+ catch (error) {
58
+ return {
59
+ success: true,
60
+ archives: [],
61
+ count: 0,
62
+ message: 'No archives directory found',
63
+ };
64
+ }
65
+ }
66
+ case 'verify': {
67
+ const archivePath = params.archivePath;
68
+ if (!archivePath)
69
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'archivePath is required');
70
+ try {
71
+ const content = await promises_1.default.readFile(archivePath, 'utf-8');
72
+ const archive = JSON.parse(content);
73
+ return {
74
+ success: true,
75
+ valid: true,
76
+ archive,
77
+ message: 'Archive is valid',
78
+ };
79
+ }
80
+ catch (error) {
81
+ return {
82
+ success: false,
83
+ valid: false,
84
+ error: error.message,
85
+ };
86
+ }
87
+ }
18
88
  case 'extract':
19
- case 'list':
20
- case 'verify':
21
- throw new errors_1.MCPError('NOT_IMPLEMENTED', `Action ${action} not yet fully implemented`);
89
+ throw new errors_1.MCPError('NOT_IMPLEMENTED', 'Extract not yet fully implemented');
22
90
  default:
23
91
  throw new errors_1.MCPError('VALIDATION_ERROR', `Unsupported action: ${action}`);
24
92
  }
@@ -4,7 +4,81 @@ export declare class GitBackupTool implements Tool {
4
4
  description: string;
5
5
  handle(params: Record<string, any>, ctx: MCPContext): Promise<{
6
6
  success: boolean;
7
- backupPath: any;
7
+ backupPath: string;
8
+ backupData: {
9
+ projectPath: any;
10
+ timestamp: string;
11
+ branch: string | null;
12
+ lastCommit: string | undefined;
13
+ files: number;
14
+ };
8
15
  message: string;
16
+ backups?: undefined;
17
+ count?: undefined;
18
+ backupDir?: undefined;
19
+ valid?: undefined;
20
+ backup?: undefined;
21
+ error?: undefined;
22
+ note?: undefined;
23
+ } | {
24
+ success: boolean;
25
+ backups: any[];
26
+ count: number;
27
+ backupDir: any;
28
+ backupPath?: undefined;
29
+ backupData?: undefined;
30
+ message?: undefined;
31
+ valid?: undefined;
32
+ backup?: undefined;
33
+ error?: undefined;
34
+ note?: undefined;
35
+ } | {
36
+ success: boolean;
37
+ backups: never[];
38
+ count: number;
39
+ message: string;
40
+ backupPath?: undefined;
41
+ backupData?: undefined;
42
+ backupDir?: undefined;
43
+ valid?: undefined;
44
+ backup?: undefined;
45
+ error?: undefined;
46
+ note?: undefined;
47
+ } | {
48
+ success: boolean;
49
+ valid: any;
50
+ backup: any;
51
+ message: string;
52
+ backupPath?: undefined;
53
+ backupData?: undefined;
54
+ backups?: undefined;
55
+ count?: undefined;
56
+ backupDir?: undefined;
57
+ error?: undefined;
58
+ note?: undefined;
59
+ } | {
60
+ success: boolean;
61
+ valid: boolean;
62
+ error: any;
63
+ backupPath?: undefined;
64
+ backupData?: undefined;
65
+ message?: undefined;
66
+ backups?: undefined;
67
+ count?: undefined;
68
+ backupDir?: undefined;
69
+ backup?: undefined;
70
+ note?: undefined;
71
+ } | {
72
+ success: boolean;
73
+ message: string;
74
+ backup: any;
75
+ note: string;
76
+ backupPath?: undefined;
77
+ backupData?: undefined;
78
+ backups?: undefined;
79
+ count?: undefined;
80
+ backupDir?: undefined;
81
+ valid?: undefined;
82
+ error?: undefined;
9
83
  }>;
10
84
  }
@@ -5,11 +5,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.GitBackupTool = void 0;
7
7
  const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const simple_git_1 = __importDefault(require("simple-git"));
8
10
  const errors_1 = require("../utils/errors");
9
11
  class GitBackupTool {
10
12
  constructor() {
11
13
  this.name = 'git-backup';
12
- this.description = 'Repository backup and restore operations - local operations';
14
+ this.description = 'Repository backup and restore operations';
13
15
  }
14
16
  async handle(params, ctx) {
15
17
  const action = params.action;
@@ -19,22 +21,107 @@ class GitBackupTool {
19
21
  }
20
22
  switch (action) {
21
23
  case 'backup': {
22
- const backupPath = params.backupPath || `${projectPath}-backup-${Date.now()}.tar.gz`;
23
- // Simplified: just create a marker file
24
- await promises_1.default.writeFile(backupPath, JSON.stringify({
25
- path: projectPath,
26
- timestamp: new Date().toISOString(),
27
- }));
28
- return { success: true, backupPath, message: 'Backup created (simplified)' };
29
- }
30
- case 'restore': {
31
- throw new errors_1.MCPError('NOT_IMPLEMENTED', 'Restore not yet fully implemented');
24
+ const backupDir = params.backupDir || path_1.default.join(projectPath, '.git-backups');
25
+ const backupName = params.backupName || `backup-${Date.now()}.json`;
26
+ try {
27
+ // Criar diretório de backups
28
+ await promises_1.default.mkdir(backupDir, { recursive: true });
29
+ const backupPath = path_1.default.join(backupDir, backupName);
30
+ const git = (0, simple_git_1.default)({ baseDir: projectPath });
31
+ const status = await git.status();
32
+ const log = await git.log({ maxCount: 1 });
33
+ const backupData = {
34
+ projectPath,
35
+ timestamp: new Date().toISOString(),
36
+ branch: status.current,
37
+ lastCommit: log.latest?.hash,
38
+ files: status.files.length,
39
+ };
40
+ await promises_1.default.writeFile(backupPath, JSON.stringify(backupData, null, 2));
41
+ return {
42
+ success: true,
43
+ backupPath,
44
+ backupData,
45
+ message: 'Backup created successfully',
46
+ };
47
+ }
48
+ catch (error) {
49
+ throw new errors_1.MCPError('FILE_ERROR', `Failed to create backup: ${error.message}`);
50
+ }
32
51
  }
33
52
  case 'list': {
34
- throw new errors_1.MCPError('NOT_IMPLEMENTED', 'List not yet fully implemented');
53
+ const backupDir = params.backupDir || path_1.default.join(projectPath, '.git-backups');
54
+ try {
55
+ const files = await promises_1.default.readdir(backupDir);
56
+ const backups = [];
57
+ for (const file of files) {
58
+ if (file.endsWith('.json')) {
59
+ try {
60
+ const content = await promises_1.default.readFile(path_1.default.join(backupDir, file), 'utf-8');
61
+ const backup = JSON.parse(content);
62
+ backups.push({ file, ...backup });
63
+ }
64
+ catch (e) {
65
+ // Ignorar arquivos inválidos
66
+ }
67
+ }
68
+ }
69
+ return {
70
+ success: true,
71
+ backups,
72
+ count: backups.length,
73
+ backupDir,
74
+ };
75
+ }
76
+ catch (error) {
77
+ return {
78
+ success: true,
79
+ backups: [],
80
+ count: 0,
81
+ message: 'No backups directory found',
82
+ };
83
+ }
35
84
  }
36
85
  case 'verify': {
37
- throw new errors_1.MCPError('NOT_IMPLEMENTED', 'Verify not yet fully implemented');
86
+ const backupPath = params.backupPath;
87
+ if (!backupPath)
88
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'backupPath is required');
89
+ try {
90
+ const content = await promises_1.default.readFile(backupPath, 'utf-8');
91
+ const backup = JSON.parse(content);
92
+ const isValid = backup.timestamp && backup.projectPath;
93
+ return {
94
+ success: true,
95
+ valid: isValid,
96
+ backup,
97
+ message: isValid ? 'Backup is valid' : 'Backup is invalid',
98
+ };
99
+ }
100
+ catch (error) {
101
+ return {
102
+ success: false,
103
+ valid: false,
104
+ error: error.message,
105
+ };
106
+ }
107
+ }
108
+ case 'restore': {
109
+ const backupPath = params.backupPath;
110
+ if (!backupPath)
111
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'backupPath is required');
112
+ try {
113
+ const content = await promises_1.default.readFile(backupPath, 'utf-8');
114
+ const backup = JSON.parse(content);
115
+ return {
116
+ success: true,
117
+ message: 'Backup info retrieved - manual restore required',
118
+ backup,
119
+ note: 'Use git commands to restore to the specified commit',
120
+ };
121
+ }
122
+ catch (error) {
123
+ throw new errors_1.MCPError('FILE_ERROR', `Failed to read backup: ${error.message}`);
124
+ }
38
125
  }
39
126
  default:
40
127
  throw new errors_1.MCPError('VALIDATION_ERROR', `Unsupported action: ${action}`);
@@ -0,0 +1,191 @@
1
+ import { Tool, MCPContext } from '../types';
2
+ /**
3
+ * Git Ignore Tool - Manage .gitignore file
4
+ * Create, read, add, remove patterns, and ensure .gitignore exists
5
+ */
6
+ export declare class GitIgnoreTool implements Tool {
7
+ name: string;
8
+ description: string;
9
+ handle(params: Record<string, any>, ctx: MCPContext): Promise<{
10
+ success: boolean;
11
+ path: string;
12
+ exists: boolean;
13
+ content: string;
14
+ patterns: string[];
15
+ lines: number;
16
+ message?: undefined;
17
+ created?: undefined;
18
+ template?: undefined;
19
+ action?: undefined;
20
+ added?: undefined;
21
+ skipped?: undefined;
22
+ total?: undefined;
23
+ removed?: undefined;
24
+ remaining?: undefined;
25
+ updated?: undefined;
26
+ cleared?: undefined;
27
+ } | {
28
+ success: boolean;
29
+ path: string;
30
+ exists: boolean;
31
+ message: string;
32
+ content?: undefined;
33
+ patterns?: undefined;
34
+ lines?: undefined;
35
+ created?: undefined;
36
+ template?: undefined;
37
+ action?: undefined;
38
+ added?: undefined;
39
+ skipped?: undefined;
40
+ total?: undefined;
41
+ removed?: undefined;
42
+ remaining?: undefined;
43
+ updated?: undefined;
44
+ cleared?: undefined;
45
+ } | {
46
+ success: boolean;
47
+ message: string;
48
+ path: string;
49
+ exists?: undefined;
50
+ content?: undefined;
51
+ patterns?: undefined;
52
+ lines?: undefined;
53
+ created?: undefined;
54
+ template?: undefined;
55
+ action?: undefined;
56
+ added?: undefined;
57
+ skipped?: undefined;
58
+ total?: undefined;
59
+ removed?: undefined;
60
+ remaining?: undefined;
61
+ updated?: undefined;
62
+ cleared?: undefined;
63
+ } | {
64
+ success: boolean;
65
+ path: string;
66
+ created: boolean;
67
+ template: any;
68
+ lines: number;
69
+ exists?: undefined;
70
+ content?: undefined;
71
+ patterns?: undefined;
72
+ message?: undefined;
73
+ action?: undefined;
74
+ added?: undefined;
75
+ skipped?: undefined;
76
+ total?: undefined;
77
+ removed?: undefined;
78
+ remaining?: undefined;
79
+ updated?: undefined;
80
+ cleared?: undefined;
81
+ } | {
82
+ success: boolean;
83
+ path: string;
84
+ exists: boolean;
85
+ action: string;
86
+ message: string;
87
+ content?: undefined;
88
+ patterns?: undefined;
89
+ lines?: undefined;
90
+ created?: undefined;
91
+ template?: undefined;
92
+ added?: undefined;
93
+ skipped?: undefined;
94
+ total?: undefined;
95
+ removed?: undefined;
96
+ remaining?: undefined;
97
+ updated?: undefined;
98
+ cleared?: undefined;
99
+ } | {
100
+ success: boolean;
101
+ path: string;
102
+ created: boolean;
103
+ template: any;
104
+ action: string;
105
+ exists?: undefined;
106
+ content?: undefined;
107
+ patterns?: undefined;
108
+ lines?: undefined;
109
+ message?: undefined;
110
+ added?: undefined;
111
+ skipped?: undefined;
112
+ total?: undefined;
113
+ removed?: undefined;
114
+ remaining?: undefined;
115
+ updated?: undefined;
116
+ cleared?: undefined;
117
+ } | {
118
+ success: boolean;
119
+ path: string;
120
+ added: string[];
121
+ skipped: number;
122
+ total: number;
123
+ exists?: undefined;
124
+ content?: undefined;
125
+ patterns?: undefined;
126
+ lines?: undefined;
127
+ message?: undefined;
128
+ created?: undefined;
129
+ template?: undefined;
130
+ action?: undefined;
131
+ removed?: undefined;
132
+ remaining?: undefined;
133
+ updated?: undefined;
134
+ cleared?: undefined;
135
+ } | {
136
+ success: boolean;
137
+ path: string;
138
+ removed: number;
139
+ remaining: number;
140
+ exists?: undefined;
141
+ content?: undefined;
142
+ patterns?: undefined;
143
+ lines?: undefined;
144
+ message?: undefined;
145
+ created?: undefined;
146
+ template?: undefined;
147
+ action?: undefined;
148
+ added?: undefined;
149
+ skipped?: undefined;
150
+ total?: undefined;
151
+ updated?: undefined;
152
+ cleared?: undefined;
153
+ } | {
154
+ success: boolean;
155
+ path: string;
156
+ updated: boolean;
157
+ lines: any;
158
+ exists?: undefined;
159
+ content?: undefined;
160
+ patterns?: undefined;
161
+ message?: undefined;
162
+ created?: undefined;
163
+ template?: undefined;
164
+ action?: undefined;
165
+ added?: undefined;
166
+ skipped?: undefined;
167
+ total?: undefined;
168
+ removed?: undefined;
169
+ remaining?: undefined;
170
+ cleared?: undefined;
171
+ } | {
172
+ success: boolean;
173
+ path: string;
174
+ cleared: boolean;
175
+ template: any;
176
+ exists?: undefined;
177
+ content?: undefined;
178
+ patterns?: undefined;
179
+ lines?: undefined;
180
+ message?: undefined;
181
+ created?: undefined;
182
+ action?: undefined;
183
+ added?: undefined;
184
+ skipped?: undefined;
185
+ total?: undefined;
186
+ removed?: undefined;
187
+ remaining?: undefined;
188
+ updated?: undefined;
189
+ }>;
190
+ private getTemplate;
191
+ }
@@ -0,0 +1,354 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.GitIgnoreTool = void 0;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const path = __importStar(require("path"));
39
+ const errors_1 = require("../utils/errors");
40
+ /**
41
+ * Git Ignore Tool - Manage .gitignore file
42
+ * Create, read, add, remove patterns, and ensure .gitignore exists
43
+ */
44
+ class GitIgnoreTool {
45
+ constructor() {
46
+ this.name = 'git-ignore';
47
+ this.description = 'Manage .gitignore file - create, add, remove, and update ignore patterns';
48
+ }
49
+ async handle(params, ctx) {
50
+ const projectPath = params.projectPath;
51
+ if (!projectPath) {
52
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'projectPath is required');
53
+ }
54
+ const action = params.action || 'read';
55
+ const gitignorePath = path.join(projectPath, '.gitignore');
56
+ switch (action) {
57
+ case 'read': {
58
+ try {
59
+ const content = await fs.readFile(gitignorePath, 'utf-8');
60
+ const patterns = content.split('\n').filter(l => l.trim() && !l.startsWith('#'));
61
+ return {
62
+ success: true,
63
+ path: gitignorePath,
64
+ exists: true,
65
+ content,
66
+ patterns,
67
+ lines: content.split('\n').length,
68
+ };
69
+ }
70
+ catch (err) {
71
+ if (err.code === 'ENOENT') {
72
+ return {
73
+ success: true,
74
+ path: gitignorePath,
75
+ exists: false,
76
+ message: '.gitignore does not exist',
77
+ };
78
+ }
79
+ throw err;
80
+ }
81
+ }
82
+ case 'create': {
83
+ const template = params.template || 'default';
84
+ const content = this.getTemplate(template);
85
+ try {
86
+ await fs.access(gitignorePath);
87
+ return {
88
+ success: false,
89
+ message: '.gitignore already exists. Use "add" to add patterns.',
90
+ path: gitignorePath,
91
+ };
92
+ }
93
+ catch {
94
+ await fs.writeFile(gitignorePath, content, 'utf-8');
95
+ return {
96
+ success: true,
97
+ path: gitignorePath,
98
+ created: true,
99
+ template,
100
+ lines: content.split('\n').length,
101
+ };
102
+ }
103
+ }
104
+ case 'ensure': {
105
+ // Create if doesn't exist, otherwise do nothing
106
+ try {
107
+ await fs.access(gitignorePath);
108
+ return {
109
+ success: true,
110
+ path: gitignorePath,
111
+ exists: true,
112
+ action: 'none',
113
+ message: '.gitignore already exists',
114
+ };
115
+ }
116
+ catch {
117
+ const template = params.template || 'default';
118
+ const content = this.getTemplate(template);
119
+ await fs.writeFile(gitignorePath, content, 'utf-8');
120
+ return {
121
+ success: true,
122
+ path: gitignorePath,
123
+ created: true,
124
+ template,
125
+ action: 'created',
126
+ };
127
+ }
128
+ }
129
+ case 'add': {
130
+ const patterns = params.patterns;
131
+ if (!patterns || !Array.isArray(patterns) || patterns.length === 0) {
132
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'patterns array is required for add action');
133
+ }
134
+ let content = '';
135
+ try {
136
+ content = await fs.readFile(gitignorePath, 'utf-8');
137
+ }
138
+ catch (err) {
139
+ if (err.code === 'ENOENT') {
140
+ // Create new file
141
+ content = this.getTemplate('minimal');
142
+ }
143
+ else {
144
+ throw err;
145
+ }
146
+ }
147
+ const existingPatterns = new Set(content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#')));
148
+ const newPatterns = [];
149
+ for (const pattern of patterns) {
150
+ if (!existingPatterns.has(pattern)) {
151
+ newPatterns.push(pattern);
152
+ existingPatterns.add(pattern);
153
+ }
154
+ }
155
+ if (newPatterns.length > 0) {
156
+ const comment = params.comment ? `# ${params.comment}\n` : '';
157
+ content += `\n${comment}${newPatterns.join('\n')}\n`;
158
+ await fs.writeFile(gitignorePath, content, 'utf-8');
159
+ }
160
+ return {
161
+ success: true,
162
+ path: gitignorePath,
163
+ added: newPatterns,
164
+ skipped: patterns.length - newPatterns.length,
165
+ total: existingPatterns.size,
166
+ };
167
+ }
168
+ case 'remove': {
169
+ const patterns = params.patterns;
170
+ if (!patterns || !Array.isArray(patterns) || patterns.length === 0) {
171
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'patterns array is required for remove action');
172
+ }
173
+ let content = '';
174
+ try {
175
+ content = await fs.readFile(gitignorePath, 'utf-8');
176
+ }
177
+ catch (err) {
178
+ if (err.code === 'ENOENT') {
179
+ return {
180
+ success: false,
181
+ message: '.gitignore does not exist',
182
+ path: gitignorePath,
183
+ };
184
+ }
185
+ throw err;
186
+ }
187
+ const patternsToRemove = new Set(patterns);
188
+ const lines = content.split('\n');
189
+ const filteredLines = lines.filter(line => {
190
+ const trimmed = line.trim();
191
+ return !patternsToRemove.has(trimmed);
192
+ });
193
+ const removed = lines.length - filteredLines.length;
194
+ const newContent = filteredLines.join('\n');
195
+ await fs.writeFile(gitignorePath, newContent, 'utf-8');
196
+ return {
197
+ success: true,
198
+ path: gitignorePath,
199
+ removed,
200
+ remaining: filteredLines.filter(l => l.trim() && !l.startsWith('#')).length,
201
+ };
202
+ }
203
+ case 'update': {
204
+ const content = params.content;
205
+ if (!content) {
206
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'content is required for update action');
207
+ }
208
+ await fs.writeFile(gitignorePath, content, 'utf-8');
209
+ return {
210
+ success: true,
211
+ path: gitignorePath,
212
+ updated: true,
213
+ lines: content.split('\n').length,
214
+ };
215
+ }
216
+ case 'clear': {
217
+ const template = params.template || 'empty';
218
+ const content = template === 'empty' ? '' : this.getTemplate('minimal');
219
+ await fs.writeFile(gitignorePath, content, 'utf-8');
220
+ return {
221
+ success: true,
222
+ path: gitignorePath,
223
+ cleared: true,
224
+ template,
225
+ };
226
+ }
227
+ default:
228
+ throw new errors_1.MCPError('VALIDATION_ERROR', `Unsupported action: ${action}`);
229
+ }
230
+ }
231
+ getTemplate(template) {
232
+ const templates = {
233
+ empty: '',
234
+ minimal: `# Dependencies
235
+ node_modules/
236
+ .pnp
237
+ .pnp.js
238
+
239
+ # Build output
240
+ dist/
241
+ build/
242
+ *.log
243
+ `,
244
+ default: `# Dependencies
245
+ node_modules/
246
+ .pnp
247
+ .pnp.js
248
+ bower_components/
249
+
250
+ # Build output
251
+ dist/
252
+ build/
253
+ out/
254
+ .next/
255
+ .nuxt/
256
+ .cache/
257
+ .parcel-cache/
258
+
259
+ # Environment
260
+ .env
261
+ .env.local
262
+ .env.*.local
263
+ *.env
264
+
265
+ # IDE
266
+ .vscode/
267
+ .idea/
268
+ *.swp
269
+ *.swo
270
+ *~
271
+ .DS_Store
272
+
273
+ # Logs
274
+ logs/
275
+ *.log
276
+ npm-debug.log*
277
+ yarn-debug.log*
278
+ yarn-error.log*
279
+ lerna-debug.log*
280
+ pnpm-debug.log*
281
+
282
+ # Testing
283
+ coverage/
284
+ .nyc_output/
285
+ *.lcov
286
+
287
+ # Temporary
288
+ tmp/
289
+ temp/
290
+ *.tmp
291
+ `,
292
+ node: `# Node
293
+ node_modules/
294
+ npm-debug.log*
295
+ yarn-debug.log*
296
+ yarn-error.log*
297
+ lerna-debug.log*
298
+ pnpm-debug.log*
299
+ .pnpm-store/
300
+ .npm
301
+ .eslintcache
302
+ .node_repl_history
303
+ *.tgz
304
+ .yarn-integrity
305
+ .env
306
+ .env.test
307
+ .env.production
308
+ .cache
309
+ .next/
310
+ out/
311
+ build/
312
+ dist/
313
+ `,
314
+ python: `# Python
315
+ __pycache__/
316
+ *.py[cod]
317
+ *$py.class
318
+ *.so
319
+ .Python
320
+ build/
321
+ develop-eggs/
322
+ dist/
323
+ downloads/
324
+ eggs/
325
+ .eggs/
326
+ lib/
327
+ lib64/
328
+ parts/
329
+ sdist/
330
+ var/
331
+ wheels/
332
+ *.egg-info/
333
+ .installed.cfg
334
+ *.egg
335
+ MANIFEST
336
+ pip-log.txt
337
+ pip-delete-this-directory.txt
338
+ .tox/
339
+ .coverage
340
+ .pytest_cache/
341
+ .mypy_cache/
342
+ .dmypy.json
343
+ dmypy.json
344
+ .env
345
+ .venv
346
+ env/
347
+ venv/
348
+ ENV/
349
+ `,
350
+ };
351
+ return templates[template] || templates.default;
352
+ }
353
+ }
354
+ exports.GitIgnoreTool = GitIgnoreTool;
@@ -2,5 +2,23 @@ import { Tool, MCPContext } from '../types';
2
2
  export declare class GitPackagesTool implements Tool {
3
3
  name: string;
4
4
  description: string;
5
- handle(params: Record<string, any>, ctx: MCPContext): Promise<void>;
5
+ handle(params: Record<string, any>, ctx: MCPContext): Promise<{
6
+ success: boolean;
7
+ name: any;
8
+ version: any;
9
+ dependencies: any;
10
+ devDependencies: any;
11
+ scripts: any;
12
+ message?: undefined;
13
+ note?: undefined;
14
+ } | {
15
+ success: boolean;
16
+ message: string;
17
+ note: string;
18
+ name?: undefined;
19
+ version?: undefined;
20
+ dependencies?: undefined;
21
+ devDependencies?: undefined;
22
+ scripts?: undefined;
23
+ }>;
6
24
  }
@@ -1,19 +1,54 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.GitPackagesTool = void 0;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
4
9
  const errors_1 = require("../utils/errors");
5
10
  class GitPackagesTool {
6
11
  constructor() {
7
12
  this.name = 'git-packages';
8
- this.description = 'Package management operations - not yet implemented';
13
+ this.description = 'Package management operations';
9
14
  }
10
15
  async handle(params, ctx) {
11
16
  const action = params.action;
17
+ const projectPath = params.projectPath;
12
18
  if (!action)
13
19
  throw new errors_1.MCPError('VALIDATION_ERROR', 'action is required');
14
20
  switch (action) {
15
- case 'list':
16
- case 'get':
21
+ case 'list': {
22
+ if (!projectPath)
23
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'projectPath is required');
24
+ // Ler package.json
25
+ try {
26
+ const packageJsonPath = path_1.default.join(projectPath, 'package.json');
27
+ const content = await promises_1.default.readFile(packageJsonPath, 'utf-8');
28
+ const pkg = JSON.parse(content);
29
+ return {
30
+ success: true,
31
+ name: pkg.name,
32
+ version: pkg.version,
33
+ dependencies: pkg.dependencies || {},
34
+ devDependencies: pkg.devDependencies || {},
35
+ scripts: pkg.scripts || {},
36
+ };
37
+ }
38
+ catch (error) {
39
+ throw new errors_1.MCPError('FILE_ERROR', `Failed to read package.json: ${error.message}`);
40
+ }
41
+ }
42
+ case 'get': {
43
+ const packageName = params.packageName;
44
+ if (!packageName)
45
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'packageName is required');
46
+ return {
47
+ success: true,
48
+ message: `Package info for ${packageName}`,
49
+ note: 'Use npm view or yarn info for detailed package information',
50
+ };
51
+ }
17
52
  case 'create':
18
53
  case 'update':
19
54
  case 'delete':
@@ -145,14 +145,107 @@ class GitUpdateTool {
145
145
  throw err;
146
146
  }
147
147
  }
148
- // 5. Get remotes
148
+ // 5. Get remotes and ensure repos exist
149
149
  const remotes = await git.getRemotes(true);
150
- const githubRemote = remotes.find(r => r.name === 'github' || r.name === 'origin');
151
- const giteaRemote = remotes.find(r => r.name === 'gitea');
150
+ let githubRemote = remotes.find(r => r.name === 'github' || r.name === 'origin');
151
+ let giteaRemote = remotes.find(r => r.name === 'gitea');
152
+ // Auto-create repos if they don't exist
153
+ const repoName = params.repoName || path.basename(projectPath);
154
+ const description = params.description || `Project updated via git-update at ${new Date().toISOString()}`;
155
+ const isPrivate = params.private !== undefined ? params.private : true;
156
+ const githubOwner = params.owner || process.env.GITHUB_USERNAME;
157
+ const giteaOwner = process.env.GITEA_USERNAME;
158
+ // 5a. Ensure GitHub repo exists
159
+ if (ctx.providerManager.github && !githubRemote) {
160
+ results.traceability.updateSteps.push({
161
+ step: 5,
162
+ action: 'ensure_github_repo',
163
+ timestamp: new Date().toISOString(),
164
+ });
165
+ try {
166
+ // Try to get repo, create if doesn't exist
167
+ let repoExists = false;
168
+ try {
169
+ await ctx.providerManager.github.rest.repos.get({
170
+ owner: githubOwner,
171
+ repo: repoName
172
+ });
173
+ repoExists = true;
174
+ }
175
+ catch { }
176
+ if (!repoExists) {
177
+ await ctx.providerManager.github.rest.repos.createForAuthenticatedUser({
178
+ name: repoName,
179
+ description,
180
+ private: isPrivate,
181
+ auto_init: false,
182
+ });
183
+ results.traceability.updateSteps.push({
184
+ step: 5.1,
185
+ action: 'created_github_repo',
186
+ timestamp: new Date().toISOString(),
187
+ repo: repoName,
188
+ });
189
+ }
190
+ // Add remote
191
+ await git.addRemote('github', `https://github.com/${githubOwner}/${repoName}.git`);
192
+ githubRemote = { name: 'github', refs: { push: `https://github.com/${githubOwner}/${repoName}.git`, fetch: '' } };
193
+ }
194
+ catch (err) {
195
+ results.traceability.errors.push({
196
+ provider: 'github',
197
+ action: 'ensure_repo',
198
+ error: err.message,
199
+ timestamp: new Date().toISOString(),
200
+ });
201
+ }
202
+ }
203
+ // 5b. Ensure Gitea repo exists
204
+ if (ctx.providerManager.giteaBaseUrl && !giteaRemote) {
205
+ results.traceability.updateSteps.push({
206
+ step: 5.5,
207
+ action: 'ensure_gitea_repo',
208
+ timestamp: new Date().toISOString(),
209
+ });
210
+ try {
211
+ // Try to get repo, create if doesn't exist
212
+ let repoExists = false;
213
+ try {
214
+ await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoName}`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
215
+ repoExists = true;
216
+ }
217
+ catch { }
218
+ if (!repoExists) {
219
+ await axios_1.default.post(`${ctx.providerManager.giteaBaseUrl}/api/v1/user/repos`, {
220
+ name: repoName,
221
+ description,
222
+ private: isPrivate,
223
+ auto_init: false,
224
+ }, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
225
+ results.traceability.updateSteps.push({
226
+ step: 5.6,
227
+ action: 'created_gitea_repo',
228
+ timestamp: new Date().toISOString(),
229
+ repo: repoName,
230
+ });
231
+ }
232
+ // Add remote
233
+ await git.addRemote('gitea', `${ctx.providerManager.giteaBaseUrl}/${giteaOwner}/${repoName}.git`);
234
+ giteaRemote = { name: 'gitea', refs: { push: `${ctx.providerManager.giteaBaseUrl}/${giteaOwner}/${repoName}.git`, fetch: '' } };
235
+ }
236
+ catch (err) {
237
+ results.traceability.errors.push({
238
+ provider: 'gitea',
239
+ action: 'ensure_repo',
240
+ error: err.message,
241
+ timestamp: new Date().toISOString(),
242
+ });
243
+ }
244
+ }
152
245
  // 6. Push to GITHUB
153
246
  if (githubRemote && ctx.providerManager.github) {
154
247
  results.traceability.updateSteps.push({
155
- step: 5,
248
+ step: 6,
156
249
  action: 'push_to_github',
157
250
  timestamp: new Date().toISOString(),
158
251
  remote: githubRemote.name,
@@ -204,7 +297,7 @@ class GitUpdateTool {
204
297
  // 7. Push to GITEA
205
298
  if (giteaRemote && ctx.providerManager.giteaBaseUrl) {
206
299
  results.traceability.updateSteps.push({
207
- step: 6,
300
+ step: 7,
208
301
  action: 'push_to_gitea',
209
302
  timestamp: new Date().toISOString(),
210
303
  remote: giteaRemote.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrebuzeli/git-mcp",
3
- "version": "7.0.3",
3
+ "version": "7.2.0",
4
4
  "description": "Professional MCP server for Git operations - STDIO UNIVERSAL: works in ANY IDE (Cursor, VSCode, Claude Desktop). Fully autonomous DUAL execution (GitHub + Gitea APIs) with automatic username detection. All tools execute on BOTH providers simultaneously. No manual parameters needed.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,49 +0,0 @@
1
- /**
2
- * Dual Provider Execution Helper
3
- * Executes operations on BOTH GitHub and Gitea simultaneously
4
- * Uses GITHUB_USERNAME and GITEA_USERNAME from env vars automatically
5
- */
6
- import { ProviderManager } from '../providers/providerManager';
7
- export interface DualExecutionResult<T> {
8
- github: {
9
- success: boolean;
10
- data?: T;
11
- error?: string;
12
- };
13
- gitea: {
14
- success: boolean;
15
- data?: T;
16
- error?: string;
17
- };
18
- }
19
- /**
20
- * Execute function on both providers simultaneously
21
- * @param providerManager Provider manager instance
22
- * @param githubFn Function to execute on GitHub
23
- * @param giteaFn Function to execute on Gitea
24
- * @returns Dual execution result with separate responses
25
- */
26
- export declare function executeDual<T>(providerManager: ProviderManager, githubFn: () => Promise<T>, giteaFn: () => Promise<T>): Promise<DualExecutionResult<T>>;
27
- /**
28
- * Get GitHub username from env vars
29
- * @throws Error if GITHUB_USERNAME not found
30
- */
31
- export declare function getGitHubUsername(): string;
32
- /**
33
- * Get Gitea username from env vars
34
- * @throws Error if GITEA_USERNAME not found
35
- */
36
- export declare function getGiteaUsername(): string;
37
- /**
38
- * Get repository name from projectPath
39
- * @param projectPath Absolute path to project root
40
- * @returns Repository name (folder name)
41
- */
42
- export declare function getRepoNameFromPath(projectPath: string): string;
43
- /**
44
- * Execute same function on both providers with automatic username injection
45
- * @param providerManager Provider manager instance
46
- * @param fn Function that takes (provider, username) and returns data
47
- * @returns Dual execution result
48
- */
49
- export declare function executeDualAuto<T>(providerManager: ProviderManager, fn: (provider: any, username: string) => Promise<T>): Promise<DualExecutionResult<T>>;
@@ -1,96 +0,0 @@
1
- "use strict";
2
- /**
3
- * Dual Provider Execution Helper
4
- * Executes operations on BOTH GitHub and Gitea simultaneously
5
- * Uses GITHUB_USERNAME and GITEA_USERNAME from env vars automatically
6
- */
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.executeDual = executeDual;
9
- exports.getGitHubUsername = getGitHubUsername;
10
- exports.getGiteaUsername = getGiteaUsername;
11
- exports.getRepoNameFromPath = getRepoNameFromPath;
12
- exports.executeDualAuto = executeDualAuto;
13
- /**
14
- * Execute function on both providers simultaneously
15
- * @param providerManager Provider manager instance
16
- * @param githubFn Function to execute on GitHub
17
- * @param giteaFn Function to execute on Gitea
18
- * @returns Dual execution result with separate responses
19
- */
20
- async function executeDual(providerManager, githubFn, giteaFn) {
21
- const result = {
22
- github: { success: false },
23
- gitea: { success: false }
24
- };
25
- // Execute GitHub
26
- try {
27
- const githubData = await githubFn();
28
- result.github = {
29
- success: true,
30
- data: githubData
31
- };
32
- }
33
- catch (error) {
34
- result.github = {
35
- success: false,
36
- error: error.message || String(error)
37
- };
38
- }
39
- // Execute Gitea
40
- try {
41
- const giteaData = await giteaFn();
42
- result.gitea = {
43
- success: true,
44
- data: giteaData
45
- };
46
- }
47
- catch (error) {
48
- result.gitea = {
49
- success: false,
50
- error: error.message || String(error)
51
- };
52
- }
53
- return result;
54
- }
55
- /**
56
- * Get GitHub username from env vars
57
- * @throws Error if GITHUB_USERNAME not found
58
- */
59
- function getGitHubUsername() {
60
- const username = process.env.GITHUB_USERNAME;
61
- if (!username) {
62
- throw new Error('GITHUB_USERNAME not found in environment variables');
63
- }
64
- return username;
65
- }
66
- /**
67
- * Get Gitea username from env vars
68
- * @throws Error if GITEA_USERNAME not found
69
- */
70
- function getGiteaUsername() {
71
- const username = process.env.GITEA_USERNAME;
72
- if (!username) {
73
- throw new Error('GITEA_USERNAME not found in environment variables');
74
- }
75
- return username;
76
- }
77
- /**
78
- * Get repository name from projectPath
79
- * @param projectPath Absolute path to project root
80
- * @returns Repository name (folder name)
81
- */
82
- function getRepoNameFromPath(projectPath) {
83
- const parts = projectPath.replace(/\\/g, '/').split('/');
84
- return parts[parts.length - 1];
85
- }
86
- /**
87
- * Execute same function on both providers with automatic username injection
88
- * @param providerManager Provider manager instance
89
- * @param fn Function that takes (provider, username) and returns data
90
- * @returns Dual execution result
91
- */
92
- async function executeDualAuto(providerManager, fn) {
93
- const githubUsername = getGitHubUsername();
94
- const giteaUsername = getGiteaUsername();
95
- return executeDual(providerManager, () => fn(providerManager.getProvider('github'), githubUsername), () => fn(providerManager.getProvider('gitea'), giteaUsername));
96
- }