@andrebuzeli/git-mcp 7.1.0 → 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
@@ -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 = [
@@ -77,7 +79,7 @@ async function main() {
77
79
  // Create MCP Server with STDIO transport
78
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
85
  server.setRequestHandler(types_1.ListToolsRequestSchema, async () => {
@@ -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;
@@ -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.1.0",
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",