@andre.buzeli/git-mcp 15.12.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.
@@ -0,0 +1,317 @@
1
+ import { Octokit } from "@octokit/rest";
2
+ import axios from "axios";
3
+ import { getProvidersEnv } from "../utils/repoHelpers.js";
4
+
5
+ export class ProviderManager {
6
+ constructor() {
7
+ const { githubToken, giteaUrl, giteaToken } = getProvidersEnv();
8
+ this.githubToken = githubToken || "";
9
+ this.giteaUrl = giteaUrl || "";
10
+ this.giteaToken = giteaToken || "";
11
+ this.github = this.githubToken ? new Octokit({ auth: this.githubToken }) : null;
12
+ this._githubOwner = "";
13
+ this._giteaOwner = "";
14
+ this._ownerFetchedAt = 0;
15
+ this._remoteUrlsCache = new Map(); // Cache para URLs de remotes
16
+ this._cacheExpiry = 10 * 60 * 1000; // 10 minutos
17
+ }
18
+
19
+ async getGitHubOwner() {
20
+ if (!this.github) return "";
21
+ const now = Date.now();
22
+ if (this._githubOwner && now - this._ownerFetchedAt < 5 * 60 * 1000) return this._githubOwner;
23
+ try {
24
+ const me = await this.github.rest.users.getAuthenticated();
25
+ this._githubOwner = me.data.login || "";
26
+ this._ownerFetchedAt = now;
27
+ return this._githubOwner;
28
+ } catch {
29
+ return "";
30
+ }
31
+ }
32
+
33
+ async getGiteaOwner() {
34
+ if (!this.giteaUrl || !this.giteaToken) return "";
35
+ const now = Date.now();
36
+ if (this._giteaOwner && now - this._ownerFetchedAt < 5 * 60 * 1000) return this._giteaOwner;
37
+ try {
38
+ const r = await axios.get(`${this.giteaUrl}/api/v1/user`, {
39
+ headers: { Authorization: `token ${this.giteaToken}` },
40
+ timeout: 8000,
41
+ });
42
+ const d = r.data || {};
43
+ this._giteaOwner = d.login || d.username || "";
44
+ this._ownerFetchedAt = now;
45
+ return this._giteaOwner;
46
+ } catch {
47
+ return "";
48
+ }
49
+ }
50
+
51
+ async getPrioritizedUser() {
52
+ // 1. Gitea
53
+ if (this.giteaUrl && this.giteaToken) {
54
+ try {
55
+ const r = await axios.get(`${this.giteaUrl}/api/v1/user`, {
56
+ headers: { Authorization: `token ${this.giteaToken}` },
57
+ timeout: 5000
58
+ });
59
+ if (r.data) {
60
+ return {
61
+ username: r.data.login || r.data.username,
62
+ email: r.data.email,
63
+ provider: 'gitea'
64
+ };
65
+ }
66
+ } catch (e) { /* ignore */ }
67
+ }
68
+
69
+ // 2. GitHub
70
+ if (this.github) {
71
+ try {
72
+ const me = await this.github.rest.users.getAuthenticated();
73
+ let email = me.data.email;
74
+
75
+ // Se email for null (privado), tenta buscar lista de emails
76
+ if (!email) {
77
+ try {
78
+ const emailsLog = await this.github.rest.users.listEmailsForAuthenticatedUser();
79
+ const primary = emailsLog.data.find(e => e.primary) || emailsLog.data[0];
80
+ if (primary) email = primary.email;
81
+ } catch { /* ignore scope errors */ }
82
+ }
83
+
84
+ return {
85
+ username: me.data.login,
86
+ email,
87
+ provider: 'github'
88
+ };
89
+ } catch (e) { /* ignore */ }
90
+ }
91
+
92
+ return null;
93
+ }
94
+
95
+ async getRemoteUrls(repoName, organization) {
96
+ const cacheKey = `urls_${organization || ""}_${repoName}`;
97
+ const now = Date.now();
98
+
99
+ // Verificar cache
100
+ if (this._remoteUrlsCache.has(cacheKey)) {
101
+ const cached = this._remoteUrlsCache.get(cacheKey);
102
+ if (now - cached.timestamp < this._cacheExpiry) {
103
+ return cached.urls;
104
+ }
105
+ }
106
+
107
+ const urls = {};
108
+
109
+ // GitHub URL
110
+ if (this.github) {
111
+ const owner = organization || await this.getGitHubOwner();
112
+ if (owner) {
113
+ urls.github = `https://github.com/${owner}/${repoName}.git`;
114
+ }
115
+ }
116
+
117
+ // Gitea URL
118
+ if (this.giteaUrl && this.giteaToken) {
119
+ const owner = organization || await this.getGiteaOwner();
120
+ if (owner) {
121
+ const base = this.giteaUrl.replace(/\/$/, "");
122
+ urls.gitea = `${base}/${owner}/${repoName}.git`;
123
+ }
124
+ }
125
+
126
+ // Cachear resultado
127
+ this._remoteUrlsCache.set(cacheKey, { urls, timestamp: now });
128
+
129
+ return urls;
130
+ }
131
+
132
+ async ensureRepos({ repoName, createIfMissing = true, description = "Managed by git-mcpv2", isPublic = false, organization }) {
133
+ // Por padrão, repositórios são PRIVADOS. Use isPublic=true para público.
134
+ const isPrivate = !isPublic;
135
+ const results = { github: null, gitea: null };
136
+ // GitHub
137
+ if (this.github) {
138
+ const owner = organization || await this.getGitHubOwner();
139
+ if (owner) {
140
+ try {
141
+ const full = `${owner}/${repoName}`;
142
+ try {
143
+ await this.github.rest.repos.get({ owner, repo: repoName });
144
+ results.github = { ok: true, repo: full, created: false };
145
+ } catch {
146
+ if (createIfMissing) {
147
+ let cr;
148
+ if (organization) {
149
+ // Criar repo na organização
150
+ cr = await this.github.rest.repos.createInOrg({
151
+ org: organization,
152
+ name: repoName,
153
+ description,
154
+ private: isPrivate,
155
+ auto_init: false,
156
+ });
157
+ } else {
158
+ // Criar repo no usuário pessoal
159
+ cr = await this.github.rest.repos.createForAuthenticatedUser({
160
+ name: repoName,
161
+ description,
162
+ private: isPrivate,
163
+ auto_init: false,
164
+ });
165
+ }
166
+ results.github = { ok: true, repo: cr.data.full_name, created: true };
167
+ } else {
168
+ results.github = { ok: false, error: "missing" };
169
+ }
170
+ }
171
+ } catch (e) {
172
+ results.github = { ok: false, error: String(e?.message || e) };
173
+ }
174
+ }
175
+ }
176
+ // Gitea
177
+ if (this.giteaUrl && this.giteaToken) {
178
+ const owner = organization || await this.getGiteaOwner();
179
+ if (owner) {
180
+ try {
181
+ const base = this.giteaUrl.replace(/\/$/, "");
182
+ const getRepo = await axios.get(`${base}/api/v1/repos/${owner}/${repoName}`,
183
+ { headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
184
+ if (getRepo.status === 200) {
185
+ results.gitea = { ok: true, repo: `${owner}/${repoName}`, created: false };
186
+ }
187
+ } catch (e) {
188
+ if (createIfMissing) {
189
+ try {
190
+ const createUrl = organization
191
+ ? `${this.giteaUrl}/api/v1/orgs/${organization}/repos`
192
+ : `${this.giteaUrl}/api/v1/user/repos`;
193
+ const cr = await axios.post(createUrl,
194
+ { name: repoName, description, private: isPrivate, auto_init: false },
195
+ { headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
196
+ results.gitea = { ok: true, repo: `${cr.data?.owner?.login || owner}/${repoName}`, created: true };
197
+ } catch (err) {
198
+ results.gitea = { ok: false, error: String(err?.message || err) };
199
+ }
200
+ } else {
201
+ results.gitea = { ok: false, error: "missing" };
202
+ }
203
+ }
204
+ }
205
+ }
206
+ return results;
207
+ }
208
+
209
+ async listAllRepos(options = {}) {
210
+ const { maxPages = 10, perPage = 100, organization } = options;
211
+ const results = { github: [], gitea: [] };
212
+
213
+ // GitHub - com paginação
214
+ if (this.github) {
215
+ try {
216
+ let page = 1;
217
+ let hasMore = true;
218
+ while (hasMore && page <= maxPages) {
219
+ let data;
220
+ if (organization) {
221
+ // Listar repos da organização
222
+ const resp = await this.github.rest.repos.listForOrg({
223
+ org: organization,
224
+ per_page: perPage,
225
+ page,
226
+ type: "all"
227
+ });
228
+ data = resp.data;
229
+ } else {
230
+ // Listar repos do usuário
231
+ const resp = await this.github.rest.repos.listForAuthenticatedUser({
232
+ per_page: perPage,
233
+ page,
234
+ visibility: "all"
235
+ });
236
+ data = resp.data;
237
+ }
238
+ results.github.push(...data.map(r => ({
239
+ name: r.name,
240
+ fullName: r.full_name,
241
+ url: r.html_url,
242
+ isPrivate: r.private
243
+ })));
244
+ hasMore = data.length === perPage;
245
+ page++;
246
+ }
247
+ } catch (e) {
248
+ console.error("GitHub List Error:", e.message);
249
+ }
250
+ }
251
+
252
+ // Gitea - com paginação
253
+ if (this.giteaUrl && this.giteaToken) {
254
+ try {
255
+ let page = 1;
256
+ let hasMore = true;
257
+ const base = this.giteaUrl.replace(/\/$/, "");
258
+ while (hasMore && page <= maxPages) {
259
+ const endpoint = organization
260
+ ? `${base}/api/v1/orgs/${organization}/repos?limit=${perPage}&page=${page}`
261
+ : `${base}/api/v1/user/repos?limit=${perPage}&page=${page}`;
262
+ const { data } = await axios.get(endpoint, {
263
+ headers: { Authorization: `token ${this.giteaToken}` }
264
+ });
265
+ results.gitea.push(...data.map(r => ({
266
+ name: r.name,
267
+ fullName: r.full_name,
268
+ url: r.html_url,
269
+ isPrivate: r.private
270
+ })));
271
+ hasMore = data.length === perPage;
272
+ page++;
273
+ }
274
+ } catch (e) {
275
+ console.error("Gitea List Error:", e.message);
276
+ }
277
+ }
278
+ return results;
279
+ }
280
+
281
+ async listOrgs() {
282
+ const results = { github: [], gitea: [] };
283
+
284
+ // GitHub
285
+ if (this.github) {
286
+ try {
287
+ const { data } = await this.github.rest.orgs.listForAuthenticatedUser({ per_page: 100 });
288
+ results.github = data.map(o => ({
289
+ name: o.login,
290
+ description: o.description || "",
291
+ url: o.html_url
292
+ }));
293
+ } catch (e) {
294
+ console.error("GitHub ListOrgs Error:", e.message);
295
+ }
296
+ }
297
+
298
+ // Gitea
299
+ if (this.giteaUrl && this.giteaToken) {
300
+ try {
301
+ const base = this.giteaUrl.replace(/\/$/, "");
302
+ const { data } = await axios.get(`${base}/api/v1/user/orgs`, {
303
+ headers: { Authorization: `token ${this.giteaToken}` }
304
+ });
305
+ results.gitea = (data || []).map(o => ({
306
+ name: o.username || o.name,
307
+ description: o.description || "",
308
+ url: o.html_url || `${base}/${o.username || o.name}`
309
+ }));
310
+ } catch (e) {
311
+ console.error("Gitea ListOrgs Error:", e.message);
312
+ }
313
+ }
314
+
315
+ return results;
316
+ }
317
+ }
@@ -0,0 +1,276 @@
1
+ // Resource único com documentação completa do git-mcp
2
+
3
+ const TOOLS_GUIDE = `# git-mcp - Guia Completo
4
+
5
+ ## ⭐ Fluxo Recomendado (Automatizado)
6
+ \`\`\`
7
+ git-workflow action="update" message="sua mensagem" → Executa status, add, commit e push automaticamente
8
+ \`\`\`
9
+
10
+ ## Fluxo Manual (se preferir controle individual)
11
+ \`\`\`
12
+ 1. git-workflow status → ver arquivos modificados
13
+ 2. git-workflow add files=["."] → adicionar ao staging
14
+ 3. git-workflow commit message="msg" → criar commit
15
+ 4. git-workflow push → enviar para GitHub e Gitea
16
+ \`\`\`
17
+
18
+ ---
19
+
20
+ ## git-workflow
21
+ Operações Git essenciais.
22
+
23
+ | Action | Descrição | Parâmetros |
24
+ |--------|-----------|------------|
25
+ | **update** | **⭐ RECOMENDADO - Fluxo completo automatizado** | **message, files=["."], gitignore=["pattern"], force, skipIfClean, organization** |
26
+ | status | Ver arquivos modificados/staged | - |
27
+ | add | Adicionar ao staging | files=["."] ou ["arquivo.js"] |
28
+ | commit | Criar commit | message="descrição" (obrigatório) |
29
+ | push | Enviar para GitHub+Gitea | force=true se histórico divergir |
30
+ | init | Inicializar repo + criar remotes | createGitignore=true, organization |
31
+ | ensure-remotes | Configurar remotes | organization |
32
+ | clean | Remover arquivos não rastreados | - |
33
+
34
+ **Exemplo update:** \`{ "projectPath": "/path", "action": "update", "message": "feat: nova func", "gitignore": ["*.log"] }\`
35
+ **Com organização:** \`{ "projectPath": "/path", "action": "init", "organization": "automacao-casa" }\`
36
+
37
+ ---
38
+
39
+ ## git-branches
40
+ Gerenciar branches.
41
+
42
+ | Action | Descrição | Parâmetros |
43
+ |--------|-----------|------------|
44
+ | list | Listar branches | - |
45
+ | create | Criar branch | branch="feature/nome" |
46
+ | checkout | Trocar de branch | branch="nome" |
47
+ | delete | Deletar branch | branch="nome", force=true |
48
+ | rename | Renomear | branch="old", newBranch="new" |
49
+
50
+ **Convenções:** feature/*, bugfix/*, hotfix/*, release/*
51
+
52
+ ---
53
+
54
+ ## git-merge (NOVO)
55
+ Mesclar branches.
56
+
57
+ | Action | Descrição | Parâmetros |
58
+ |--------|-----------|------------|
59
+ | merge | Mesclar branch | sourceBranch="feature/x", targetBranch="main" |
60
+ | status | Verificar conflitos | - |
61
+ | abort | Abortar merge | - |
62
+
63
+ **Opções:** fastForwardOnly=true (só FF), squash=true (um commit)
64
+
65
+ ---
66
+
67
+ ## git-diff (NOVO)
68
+ Visualizar diferenças.
69
+
70
+ | Action | Descrição | Parâmetros |
71
+ |--------|-----------|------------|
72
+ | show | Ver mudanças locais | filepath="src/file.js" (opcional) |
73
+ | compare | Comparar commits | fromRef="HEAD~1", toRef="HEAD" |
74
+ | stat | Estatísticas | fromRef="HEAD~1", toRef="HEAD" |
75
+
76
+ ---
77
+
78
+ ## git-clone (NOVO)
79
+ Clonar repositórios.
80
+
81
+ | Action | Descrição | Parâmetros |
82
+ |--------|-----------|------------|
83
+ | clone | Clonar repo | url="https://...", targetPath="./pasta" |
84
+
85
+ **Opções:** branch="main", depth=1 (shallow clone)
86
+
87
+ ---
88
+
89
+ ## git-tags
90
+ Versionamento semântico.
91
+
92
+ | Action | Descrição | Parâmetros |
93
+ |--------|-----------|------------|
94
+ | list | Listar tags | - |
95
+ | create | Criar tag | tag="v1.0.0", message="opcional" |
96
+ | delete | Deletar tag | tag="v1.0.0" |
97
+ | push | Enviar para remotes | tag="v1.0.0" |
98
+
99
+ **Versões:** v1.0.0 (inicial), v1.1.0 (feature), v1.0.1 (bugfix), v2.0.0 (breaking)
100
+
101
+ ---
102
+
103
+ ## git-stash
104
+ Salvar mudanças temporariamente.
105
+
106
+ | Action | Descrição | Parâmetros |
107
+ |--------|-----------|------------|
108
+ | list | Ver stashes salvos | - |
109
+ | save | Salvar mudanças | message="WIP", includeUntracked=true |
110
+ | pop | Restaurar e remover | ref="stash@{0}" |
111
+ | apply | Restaurar sem remover | ref="stash@{0}" |
112
+ | drop | Remover sem restaurar | ref="stash@{0}" |
113
+ | clear | Remover todos | - |
114
+
115
+ ---
116
+
117
+ ## git-reset
118
+ Desfazer commits. ⚠️ CUIDADO
119
+
120
+ | Action | Descrição | Parâmetros |
121
+ |--------|-----------|------------|
122
+ | soft | Mantém mudanças staged | ref="HEAD~1" |
123
+ | mixed | Mantém arquivos, remove staging | ref="HEAD~1" |
124
+ | hard | ⚠️ DESCARTA tudo | ref="HEAD~1" |
125
+ | hard-clean | ⚠️⚠️ DESCARTA + remove untracked | ref="HEAD" |
126
+
127
+ **Refs:** HEAD~1 (1 commit), HEAD~2 (2 commits), SHA específico
128
+
129
+ ---
130
+
131
+ ## git-remote
132
+ Operações em repos remotos GitHub/Gitea.
133
+
134
+ | Action | Descrição | Parâmetros |
135
+ |--------|-----------|------------|
136
+ | list | Ver remotes configurados | - |
137
+ | list-all | Listar todos repos da conta ou org | organization |
138
+ | list-orgs | Listar organizações disponíveis | - |
139
+ | ensure | Criar repos + configurar remotes | organization, isPublic |
140
+ | release-create | Criar release | tag="v1.0.0", name="", body="", organization |
141
+ | topics-set | Definir tópicos | topics=["js","mcp"], organization |
142
+ | label-create | Criar label | name="bug", color="ff0000", organization |
143
+ | repo-delete | ⚠️ Deletar repo | organization |
144
+ | milestone-create | Criar milestone | title="v1.0", organization |
145
+ | fork-create | Criar fork | organization |
146
+ | fork-list | Listar forks | organization |
147
+ | star-set | Dar estrela | - |
148
+ | star-unset | Remover estrela | - |
149
+ | contents-create | Criar arquivo via API | path="", content="", branch="", organization |
150
+
151
+ **Exemplo com organização:** \`{ "projectPath": "/path", "action": "ensure", "organization": "minha-org" }\`
152
+
153
+ ---
154
+
155
+ ## 🏢 Organizações (Suporte Opcional)
156
+ Criar repos dentro de organizações GitHub/Gitea.
157
+
158
+ **Parâmetro:** \`organization\` (opcional em git-workflow, git-remote, git-issues, git-pulls)
159
+
160
+ | Uso | Exemplo |
161
+ |-----|---------|
162
+ | Conta pessoal (padrão) | \`{ "action": "init", ... }\` → usuario/repo |
163
+ | Em organização | \`{ "action": "init", "organization": "minha-org" }\` → minha-org/repo |
164
+ | Listar orgs | \`{ "action": "list-orgs" }\` via git-remote |
165
+
166
+ **Nota:** O token precisa ter permissão de criar repos na organização.
167
+
168
+
169
+ ## git-issues
170
+ Gerenciar issues.
171
+
172
+ | Action | Descrição | Parâmetros |
173
+ |--------|-----------|------------|
174
+ | create | Criar issue | title="Bug: ...", body="Descrição" |
175
+ | list | Listar issues | - |
176
+ | comment | Comentar | number=1, body="Comentário" |
177
+
178
+ ---
179
+
180
+ ## git-pulls
181
+ Gerenciar Pull Requests.
182
+
183
+ | Action | Descrição | Parâmetros |
184
+ |--------|-----------|------------|
185
+ | create | Criar PR | head="feature/x", base="main", title="" |
186
+ | list | Listar PRs | - |
187
+ | files | Ver arquivos do PR | number=1 |
188
+
189
+ ---
190
+
191
+ ## git-sync
192
+ Sincronizar com remotes.
193
+
194
+ | Action | Descrição | Parâmetros |
195
+ |--------|-----------|------------|
196
+ | fetch | Baixar sem aplicar | remote="github", branch="" |
197
+ | pull | Baixar e aplicar | remote="github", branch="" |
198
+
199
+ ---
200
+
201
+ ## git-config
202
+ Configurações Git.
203
+
204
+ | Action | Descrição | Parâmetros |
205
+ |--------|-----------|------------|
206
+ | get | Obter valor | key="user.name" |
207
+ | set | Definir valor | key="user.name", value="Nome" |
208
+ | unset | Remover | key="user.name" |
209
+ | list | Listar todas | scope="local" |
210
+
211
+ **Keys:** user.name, user.email, core.autocrlf
212
+
213
+ ---
214
+
215
+ ## git-files
216
+ Ler arquivos do histórico Git.
217
+
218
+ | Action | Descrição | Parâmetros |
219
+ |--------|-----------|------------|
220
+ | list | Listar arquivos | ref="HEAD" |
221
+ | read | Ler conteúdo | filepath="package.json", ref="HEAD" |
222
+
223
+ ---
224
+
225
+ ## git-history
226
+ Ver histórico de commits.
227
+
228
+ | Action | Descrição | Parâmetros |
229
+ |--------|-----------|------------|
230
+ | log | Listar commits | ref="HEAD", maxCount=50 |
231
+
232
+ ---
233
+
234
+ ## git-ignore
235
+ Gerenciar .gitignore.
236
+
237
+ | Action | Descrição | Parâmetros |
238
+ |--------|-----------|------------|
239
+ | list | Ver padrões | - |
240
+ | create | Criar .gitignore | patterns=["node_modules/","*.log"] |
241
+ | add | Adicionar padrões | patterns=["dist/"] |
242
+ | remove | Remover padrões | patterns=["*.log"] |
243
+
244
+ ---
245
+
246
+ ## Erros Comuns
247
+
248
+ | Erro | Causa | Solução |
249
+ |------|-------|---------|
250
+ | PUSH_REJECTED | Histórico divergente | push com force=true |
251
+ | REMOTE_NOT_FOUND | Remote não configurado | git-remote ensure |
252
+ | BRANCH_NOT_FOUND | Branch não existe | git-branches list |
253
+ | TAG_ALREADY_EXISTS | Tag duplicada | Deletar ou usar outro nome |
254
+ | NOTHING_TO_STASH | Working tree limpa | Nada a fazer |
255
+ | INSUFFICIENT_HISTORY | HEAD~N muito grande | Verificar git-history log |
256
+ | AUTH_*_INVALID | Token inválido | Verificar GITHUB_TOKEN/GITEA_TOKEN |
257
+ | MERGE_CONFLICT | Conflito durante merge | Resolver manualmente ou abort |
258
+ | INVALID_PATH | Path traversal detectado | Usar caminhos absolutos válidos |
259
+ | CLONE_FAILED | Erro ao clonar | Verificar URL e autenticação |
260
+ `;
261
+
262
+ export function getResources() {
263
+ return [{
264
+ uri: "git://tools-guide",
265
+ name: "git-mcp Tools Guide",
266
+ description: "Documentação completa de todas as tools e actions do git-mcp",
267
+ mimeType: "text/markdown"
268
+ }];
269
+ }
270
+
271
+ export function readResource(uri) {
272
+ if (uri === "git://tools-guide") {
273
+ return TOOLS_GUIDE;
274
+ }
275
+ return null;
276
+ }