gitpr-cli 0.0.13__tar.gz → 0.0.15__tar.gz
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.
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/PKG-INFO +12 -2
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/README.md +11 -1
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/PKG-INFO +12 -2
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/SOURCES.txt +3 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/pyproject.toml +1 -1
- gitpr_cli-0.0.15/src/blame_engine.py +216 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/config.py +13 -2
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/core.py +4 -0
- gitpr_cli-0.0.15/src/issue_engine.py +76 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/main.py +83 -2
- gitpr_cli-0.0.15/src/tui_issue.py +34 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/updater.py +1 -1
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/LICENSE +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/dependency_links.txt +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/entry_points.txt +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/requires.txt +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/top_level.txt +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/setup.cfg +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/__init__.py +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/ai_providers.py +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/cache.py +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/linter_engine.py +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/security.py +0 -0
- {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/tests/test_core.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitpr-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.15
|
|
4
4
|
Summary: Automação de PRs, Commits e Code Review com IA (Gemini e DeepSeek)
|
|
5
5
|
Author-email: Natan Fiuza <contato@natanfiuza.dev.br>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -29,6 +29,8 @@ Este projeto foi desenvolvido em Python e utiliza as seguintes bibliotecas princ
|
|
|
29
29
|
* [**Pytest**](https://docs.pytest.org/): Para execução de testes unitários de forma simples, colorida e legível no console.
|
|
30
30
|
* [**Cryptography**](https://cryptography.io/): Para garantir que sua `GEMINI_API_KEY` seja armazenada de forma encriptada e segura no disco.
|
|
31
31
|
* [**PyYAML**](https://pyyaml.org/): Utilizado para ler e processar as regras customizadas de análise estática do arquivo `.gitpr.linter.yml`.
|
|
32
|
+
* [**Textual**](https://textual.textualize.io/): Biblioteca poderosa para a criação de Interfaces Gráficas de Terminal (TUI), utilizada no painel interativo de geração e edição de Issues.
|
|
33
|
+
* [**Requests**](https://pypi.org/project/requests/): Biblioteca elegante e robusta para requisições HTTP, utilizada para a comunicação com a API REST do GitHub.
|
|
32
34
|
|
|
33
35
|
----
|
|
34
36
|
|
|
@@ -124,6 +126,7 @@ Você pode passar as seguintes *flags* para ações específicas:
|
|
|
124
126
|
* `-l` ou `--linter`: Roda **apenas o linter estático local** (sem chamadas de IA). Ideal para usar em pipelines de CI/CD para bloquear código fora do padrão.
|
|
125
127
|
* `-ih` ou `--installhooks`: Instala automaticamente os **Git Hooks locais** (`pre-commit` e `prepare-commit-msg`) no seu repositório.
|
|
126
128
|
* `-s` ou `--skill`: Cria os arquivos de template de contexto da IA (`.gitpr.commit.md`, `.gitpr.pr.md`, `.gitpr.review.md`, `.gitpr.filereview.md`) e do Linter (`.gitpr.linter.yml`) na raiz do projeto.
|
|
129
|
+
* `-is` ou `--issue`: Gera automaticamente o rascunho de uma **Issue padronizada** a partir do diff e abre uma interface interativa (TUI) para edição, salvamento ou envio direto para o repositório no GitHub via API REST.
|
|
127
130
|
* `-u` ou `--update`: Verifica e instala a versão mais recente do GitPR (Auto-Updater).
|
|
128
131
|
* `-h` ou `--help`: Exibe o menu de ajuda.
|
|
129
132
|
|
|
@@ -175,7 +178,8 @@ Se você deseja implementar o GitPR como uma barreira de qualidade automatizada
|
|
|
175
178
|
|
|
176
179
|
* [**Guia de Git Hooks Locais (Shift-Left)**](docs/local-git-hooks.md): Como usar o `gitpr --installhooks` para criar travas na máquina do desenvolvedor (bloqueio de *console.log*, *localhost*, etc.) e usar a IA para escrever mensagens de commit automaticamente no editor.
|
|
177
180
|
* [**Integração com CI/CD (GitHub Actions)**](docs/github-ci-linter.md): Como rodar o GitPR no seu pipeline na nuvem para realizar a validação estática e travar o botão de "Merge" de Pull Requests que violem as regras do projeto.
|
|
178
|
-
|
|
181
|
+
* [**Geração de Issues e Interface TUI**](docs/issue-tui-help.md): Como utilizar a interface gráfica de terminal (TUI) para revisar e gerenciar Issues estruturadas antes do envio.
|
|
182
|
+
* [**Integração e Segurança do Token GitHub (PAT)**](docs/github-pat-integration.md): Entenda como o GitPR gera issues diretamente no seu repositório de forma autenticada, mantendo suas credenciais criptografadas localmente.
|
|
179
183
|
|
|
180
184
|
## ⚡ Sistema de Cache Local (Economia de Quota)
|
|
181
185
|
|
|
@@ -190,6 +194,12 @@ Nunca mais se preocupe em baixar novas versões manualmente. O GitPR possui um G
|
|
|
190
194
|
* Você pode forçar a busca e instalação rodando `gitpr --update` ou `gitpr -u`.
|
|
191
195
|
* A ferramenta utiliza a técnica de *Hot-Swap*, baixando o novo `.exe` e substituindo a versão antiga de forma transparente.
|
|
192
196
|
|
|
197
|
+
## Publicar no PyPi
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pipenv run python -m build
|
|
201
|
+
pipenv run twine upload dist/*
|
|
202
|
+
```
|
|
193
203
|
## **🤝 Como Contribuir**
|
|
194
204
|
|
|
195
205
|
Contribuições são muito bem-vindas! Para contribuir:
|
|
@@ -13,6 +13,8 @@ Este projeto foi desenvolvido em Python e utiliza as seguintes bibliotecas princ
|
|
|
13
13
|
* [**Pytest**](https://docs.pytest.org/): Para execução de testes unitários de forma simples, colorida e legível no console.
|
|
14
14
|
* [**Cryptography**](https://cryptography.io/): Para garantir que sua `GEMINI_API_KEY` seja armazenada de forma encriptada e segura no disco.
|
|
15
15
|
* [**PyYAML**](https://pyyaml.org/): Utilizado para ler e processar as regras customizadas de análise estática do arquivo `.gitpr.linter.yml`.
|
|
16
|
+
* [**Textual**](https://textual.textualize.io/): Biblioteca poderosa para a criação de Interfaces Gráficas de Terminal (TUI), utilizada no painel interativo de geração e edição de Issues.
|
|
17
|
+
* [**Requests**](https://pypi.org/project/requests/): Biblioteca elegante e robusta para requisições HTTP, utilizada para a comunicação com a API REST do GitHub.
|
|
16
18
|
|
|
17
19
|
----
|
|
18
20
|
|
|
@@ -108,6 +110,7 @@ Você pode passar as seguintes *flags* para ações específicas:
|
|
|
108
110
|
* `-l` ou `--linter`: Roda **apenas o linter estático local** (sem chamadas de IA). Ideal para usar em pipelines de CI/CD para bloquear código fora do padrão.
|
|
109
111
|
* `-ih` ou `--installhooks`: Instala automaticamente os **Git Hooks locais** (`pre-commit` e `prepare-commit-msg`) no seu repositório.
|
|
110
112
|
* `-s` ou `--skill`: Cria os arquivos de template de contexto da IA (`.gitpr.commit.md`, `.gitpr.pr.md`, `.gitpr.review.md`, `.gitpr.filereview.md`) e do Linter (`.gitpr.linter.yml`) na raiz do projeto.
|
|
113
|
+
* `-is` ou `--issue`: Gera automaticamente o rascunho de uma **Issue padronizada** a partir do diff e abre uma interface interativa (TUI) para edição, salvamento ou envio direto para o repositório no GitHub via API REST.
|
|
111
114
|
* `-u` ou `--update`: Verifica e instala a versão mais recente do GitPR (Auto-Updater).
|
|
112
115
|
* `-h` ou `--help`: Exibe o menu de ajuda.
|
|
113
116
|
|
|
@@ -159,7 +162,8 @@ Se você deseja implementar o GitPR como uma barreira de qualidade automatizada
|
|
|
159
162
|
|
|
160
163
|
* [**Guia de Git Hooks Locais (Shift-Left)**](docs/local-git-hooks.md): Como usar o `gitpr --installhooks` para criar travas na máquina do desenvolvedor (bloqueio de *console.log*, *localhost*, etc.) e usar a IA para escrever mensagens de commit automaticamente no editor.
|
|
161
164
|
* [**Integração com CI/CD (GitHub Actions)**](docs/github-ci-linter.md): Como rodar o GitPR no seu pipeline na nuvem para realizar a validação estática e travar o botão de "Merge" de Pull Requests que violem as regras do projeto.
|
|
162
|
-
|
|
165
|
+
* [**Geração de Issues e Interface TUI**](docs/issue-tui-help.md): Como utilizar a interface gráfica de terminal (TUI) para revisar e gerenciar Issues estruturadas antes do envio.
|
|
166
|
+
* [**Integração e Segurança do Token GitHub (PAT)**](docs/github-pat-integration.md): Entenda como o GitPR gera issues diretamente no seu repositório de forma autenticada, mantendo suas credenciais criptografadas localmente.
|
|
163
167
|
|
|
164
168
|
## ⚡ Sistema de Cache Local (Economia de Quota)
|
|
165
169
|
|
|
@@ -174,6 +178,12 @@ Nunca mais se preocupe em baixar novas versões manualmente. O GitPR possui um G
|
|
|
174
178
|
* Você pode forçar a busca e instalação rodando `gitpr --update` ou `gitpr -u`.
|
|
175
179
|
* A ferramenta utiliza a técnica de *Hot-Swap*, baixando o novo `.exe` e substituindo a versão antiga de forma transparente.
|
|
176
180
|
|
|
181
|
+
## Publicar no PyPi
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pipenv run python -m build
|
|
185
|
+
pipenv run twine upload dist/*
|
|
186
|
+
```
|
|
177
187
|
## **🤝 Como Contribuir**
|
|
178
188
|
|
|
179
189
|
Contribuições são muito bem-vindas! Para contribuir:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitpr-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.15
|
|
4
4
|
Summary: Automação de PRs, Commits e Code Review com IA (Gemini e DeepSeek)
|
|
5
5
|
Author-email: Natan Fiuza <contato@natanfiuza.dev.br>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -29,6 +29,8 @@ Este projeto foi desenvolvido em Python e utiliza as seguintes bibliotecas princ
|
|
|
29
29
|
* [**Pytest**](https://docs.pytest.org/): Para execução de testes unitários de forma simples, colorida e legível no console.
|
|
30
30
|
* [**Cryptography**](https://cryptography.io/): Para garantir que sua `GEMINI_API_KEY` seja armazenada de forma encriptada e segura no disco.
|
|
31
31
|
* [**PyYAML**](https://pyyaml.org/): Utilizado para ler e processar as regras customizadas de análise estática do arquivo `.gitpr.linter.yml`.
|
|
32
|
+
* [**Textual**](https://textual.textualize.io/): Biblioteca poderosa para a criação de Interfaces Gráficas de Terminal (TUI), utilizada no painel interativo de geração e edição de Issues.
|
|
33
|
+
* [**Requests**](https://pypi.org/project/requests/): Biblioteca elegante e robusta para requisições HTTP, utilizada para a comunicação com a API REST do GitHub.
|
|
32
34
|
|
|
33
35
|
----
|
|
34
36
|
|
|
@@ -124,6 +126,7 @@ Você pode passar as seguintes *flags* para ações específicas:
|
|
|
124
126
|
* `-l` ou `--linter`: Roda **apenas o linter estático local** (sem chamadas de IA). Ideal para usar em pipelines de CI/CD para bloquear código fora do padrão.
|
|
125
127
|
* `-ih` ou `--installhooks`: Instala automaticamente os **Git Hooks locais** (`pre-commit` e `prepare-commit-msg`) no seu repositório.
|
|
126
128
|
* `-s` ou `--skill`: Cria os arquivos de template de contexto da IA (`.gitpr.commit.md`, `.gitpr.pr.md`, `.gitpr.review.md`, `.gitpr.filereview.md`) e do Linter (`.gitpr.linter.yml`) na raiz do projeto.
|
|
129
|
+
* `-is` ou `--issue`: Gera automaticamente o rascunho de uma **Issue padronizada** a partir do diff e abre uma interface interativa (TUI) para edição, salvamento ou envio direto para o repositório no GitHub via API REST.
|
|
127
130
|
* `-u` ou `--update`: Verifica e instala a versão mais recente do GitPR (Auto-Updater).
|
|
128
131
|
* `-h` ou `--help`: Exibe o menu de ajuda.
|
|
129
132
|
|
|
@@ -175,7 +178,8 @@ Se você deseja implementar o GitPR como uma barreira de qualidade automatizada
|
|
|
175
178
|
|
|
176
179
|
* [**Guia de Git Hooks Locais (Shift-Left)**](docs/local-git-hooks.md): Como usar o `gitpr --installhooks` para criar travas na máquina do desenvolvedor (bloqueio de *console.log*, *localhost*, etc.) e usar a IA para escrever mensagens de commit automaticamente no editor.
|
|
177
180
|
* [**Integração com CI/CD (GitHub Actions)**](docs/github-ci-linter.md): Como rodar o GitPR no seu pipeline na nuvem para realizar a validação estática e travar o botão de "Merge" de Pull Requests que violem as regras do projeto.
|
|
178
|
-
|
|
181
|
+
* [**Geração de Issues e Interface TUI**](docs/issue-tui-help.md): Como utilizar a interface gráfica de terminal (TUI) para revisar e gerenciar Issues estruturadas antes do envio.
|
|
182
|
+
* [**Integração e Segurança do Token GitHub (PAT)**](docs/github-pat-integration.md): Entenda como o GitPR gera issues diretamente no seu repositório de forma autenticada, mantendo suas credenciais criptografadas localmente.
|
|
179
183
|
|
|
180
184
|
## ⚡ Sistema de Cache Local (Economia de Quota)
|
|
181
185
|
|
|
@@ -190,6 +194,12 @@ Nunca mais se preocupe em baixar novas versões manualmente. O GitPR possui um G
|
|
|
190
194
|
* Você pode forçar a busca e instalação rodando `gitpr --update` ou `gitpr -u`.
|
|
191
195
|
* A ferramenta utiliza a técnica de *Hot-Swap*, baixando o novo `.exe` e substituindo a versão antiga de forma transparente.
|
|
192
196
|
|
|
197
|
+
## Publicar no PyPi
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pipenv run python -m build
|
|
201
|
+
pipenv run twine upload dist/*
|
|
202
|
+
```
|
|
193
203
|
## **🤝 Como Contribuir**
|
|
194
204
|
|
|
195
205
|
Contribuições são muito bem-vindas! Para contribuir:
|
|
@@ -9,11 +9,14 @@ gitpr_cli.egg-info/requires.txt
|
|
|
9
9
|
gitpr_cli.egg-info/top_level.txt
|
|
10
10
|
src/__init__.py
|
|
11
11
|
src/ai_providers.py
|
|
12
|
+
src/blame_engine.py
|
|
12
13
|
src/cache.py
|
|
13
14
|
src/config.py
|
|
14
15
|
src/core.py
|
|
16
|
+
src/issue_engine.py
|
|
15
17
|
src/linter_engine.py
|
|
16
18
|
src/main.py
|
|
17
19
|
src/security.py
|
|
20
|
+
src/tui_issue.py
|
|
18
21
|
src/updater.py
|
|
19
22
|
tests/test_core.py
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import click
|
|
3
|
+
import re
|
|
4
|
+
import os
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from src.core import get_current_branch
|
|
7
|
+
from src.config import get_api_key, get_api_model, get_ai_provider
|
|
8
|
+
from src.ai_providers import call_ai_model
|
|
9
|
+
|
|
10
|
+
def execute_git_blame(file_path, start_line, end_line, commit_hash=None):
|
|
11
|
+
"""Executa o git blame e retorna uma lista de hashes únicos."""
|
|
12
|
+
cmd = ["git", "blame", f"-L", f"{start_line},{end_line}"]
|
|
13
|
+
if commit_hash:
|
|
14
|
+
cmd.append(commit_hash)
|
|
15
|
+
cmd.extend(["--", file_path])
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
result = subprocess.run(
|
|
19
|
+
cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
|
|
20
|
+
)
|
|
21
|
+
hashes = set()
|
|
22
|
+
for line in result.stdout.strip().split('\n'):
|
|
23
|
+
if line:
|
|
24
|
+
match = re.match(r'^([a-fA-F0-9]+)\s', line)
|
|
25
|
+
if match:
|
|
26
|
+
commit = match.group(1)
|
|
27
|
+
if not commit.startswith('000000'):
|
|
28
|
+
hashes.add(commit)
|
|
29
|
+
return list(hashes)
|
|
30
|
+
except subprocess.CalledProcessError as e:
|
|
31
|
+
# Se falhar (ex: arquivo não existia naquele commit antigo), silenciamos e retornamos vazio
|
|
32
|
+
return []
|
|
33
|
+
|
|
34
|
+
def execute_git_show(commit_hash, file_path):
|
|
35
|
+
"""Executa o git show para pegar o diff exato."""
|
|
36
|
+
cmd = ["git", "show", commit_hash, "--", file_path]
|
|
37
|
+
try:
|
|
38
|
+
result = subprocess.run(
|
|
39
|
+
cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
|
|
40
|
+
)
|
|
41
|
+
return result.stdout
|
|
42
|
+
except subprocess.CalledProcessError:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def get_commit_info(commit_hash):
|
|
46
|
+
"""Busca autor, data e mensagem do commit."""
|
|
47
|
+
cmd = ["git", "show", "-s", "--format=%an|%ad|%s", "--date=short", commit_hash]
|
|
48
|
+
try:
|
|
49
|
+
res = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", check=True)
|
|
50
|
+
parts = res.stdout.strip().split('|', 2)
|
|
51
|
+
if len(parts) == 3:
|
|
52
|
+
return {"author": parts[0], "date": parts[1], "message": parts[2]}
|
|
53
|
+
except:
|
|
54
|
+
pass
|
|
55
|
+
return {"author": "Desconhecido", "date": "Desconhecida", "message": "Sem mensagem"}
|
|
56
|
+
|
|
57
|
+
def analyze_commit_with_ai(commit_hash, file_path):
|
|
58
|
+
"""Usa a IA para ler o diff e classificar como ORIGEM ou REFATORACAO."""
|
|
59
|
+
diff = execute_git_show(commit_hash, file_path)
|
|
60
|
+
if not diff:
|
|
61
|
+
return {"status": "ORIGEM", "motivo": "Diff não encontrado (arquivo possivelmente criado aqui)."}
|
|
62
|
+
|
|
63
|
+
provider = get_ai_provider()
|
|
64
|
+
api_key = get_api_key(provider)
|
|
65
|
+
if not api_key:
|
|
66
|
+
return {"status": "ORIGEM", "motivo": "Sem chave de API. Assumindo origem."}
|
|
67
|
+
|
|
68
|
+
# Usamos o modelo 'simple' (Flash/Lite) para economizar dinheiro no loop
|
|
69
|
+
api_model = get_api_model(provider, task_complexity="simple")
|
|
70
|
+
|
|
71
|
+
skill_path = os.path.join(os.getcwd(), ".gitpr.blame.md")
|
|
72
|
+
if os.path.exists(skill_path):
|
|
73
|
+
with open(skill_path, "r", encoding="utf-8") as f:
|
|
74
|
+
sys_inst = f.read()
|
|
75
|
+
else:
|
|
76
|
+
sys_inst = 'Você é um Arquiteto de Software. Analise o diff e determine se é a ORIGEM da regra (lógica nova) ou REFATORAÇÃO. Responda APENAS com JSON: {"status": "ORIGEM", "motivo": "Explique o que foi introduzido"} ou {"status": "REFATORACAO", "motivo": "Explique o que foi alterado"}'
|
|
77
|
+
|
|
78
|
+
prompt = (
|
|
79
|
+
f"Analise o diff do commit {commit_hash} e retorne o JSON solicitado.\n\n"
|
|
80
|
+
f"DIFF:\n{diff[:4000]}" # Limitamos a 4000 caracteres para não pesar a requisição
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
click.secho(f" 🤖 Consultando a IA ({api_model}) sobre o commit {commit_hash[:8]}...", fg="cyan", dim=True)
|
|
84
|
+
|
|
85
|
+
result_json = call_ai_model(provider, api_key, api_model, prompt, sys_inst)
|
|
86
|
+
|
|
87
|
+
if result_json and "status" in result_json:
|
|
88
|
+
return result_json
|
|
89
|
+
|
|
90
|
+
return {"status": "ORIGEM", "motivo": "IA não retornou formato válido."}
|
|
91
|
+
|
|
92
|
+
def run_blame_analysis(file_path, start_line, end_line):
|
|
93
|
+
"""Motor de Loop Temporal que constrói a Timeline consolidada."""
|
|
94
|
+
click.secho(f"\n🔍 Iniciando Arqueologia de Código...", fg="cyan", bold=True)
|
|
95
|
+
click.echo(f"📍 Arquivo: {file_path} (Linhas: {start_line} até {end_line})")
|
|
96
|
+
|
|
97
|
+
initial_commits = execute_git_blame(file_path, start_line, end_line)
|
|
98
|
+
|
|
99
|
+
if not initial_commits:
|
|
100
|
+
click.secho("⚠️ Nenhum commit rastreável encontrado nestas linhas.", fg="yellow")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
click.secho(f"✅ Encontrado(s) {len(initial_commits)} commit(s) na superfície. Iniciando viagem no tempo...\n", fg="green")
|
|
104
|
+
|
|
105
|
+
master_timeline = []
|
|
106
|
+
seen_hashes = set()
|
|
107
|
+
|
|
108
|
+
# LOOP DE COLETA DE DADOS
|
|
109
|
+
for base_commit in initial_commits:
|
|
110
|
+
current_commit = base_commit
|
|
111
|
+
depth = 0
|
|
112
|
+
max_depth = 4 # Trava de segurança para não rodar infinito em código legado
|
|
113
|
+
|
|
114
|
+
while depth < max_depth:
|
|
115
|
+
# Se já analisamos este commit em outra trilha, não gasta requisição à toa
|
|
116
|
+
if current_commit in seen_hashes:
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
seen_hashes.add(current_commit)
|
|
120
|
+
info = get_commit_info(current_commit)
|
|
121
|
+
ai_analysis = analyze_commit_with_ai(current_commit, file_path)
|
|
122
|
+
|
|
123
|
+
status = str(ai_analysis.get("status", "ORIGEM")).upper()
|
|
124
|
+
motivo = str(ai_analysis.get("motivo", ""))
|
|
125
|
+
|
|
126
|
+
master_timeline.append({
|
|
127
|
+
"hash": current_commit[:8],
|
|
128
|
+
"info": info,
|
|
129
|
+
"status": status,
|
|
130
|
+
"motivo": motivo,
|
|
131
|
+
"raw_date": info["date"] # Usado para ordenação
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
if status == "ORIGEM":
|
|
135
|
+
break
|
|
136
|
+
|
|
137
|
+
# É refatoração, vamos buscar o commit pai no passado
|
|
138
|
+
depth += 1
|
|
139
|
+
parent_hash = f"{current_commit}^"
|
|
140
|
+
parent_commits = execute_git_blame(file_path, start_line, end_line, parent_hash)
|
|
141
|
+
|
|
142
|
+
if not parent_commits:
|
|
143
|
+
break
|
|
144
|
+
current_commit = parent_commits[0]
|
|
145
|
+
|
|
146
|
+
# ORDENAÇÃO CRONOLÓGICA (Do mais antigo para o mais novo)
|
|
147
|
+
master_timeline.sort(key=lambda x: x["raw_date"])
|
|
148
|
+
|
|
149
|
+
# EXIBIÇÃO VISUAL NO TERMINAL (ÚNICA)
|
|
150
|
+
click.secho(f"\n📜 Histórico Consolidado da Regra (Linhas {start_line}-{end_line}):", fg="magenta", bold=True)
|
|
151
|
+
|
|
152
|
+
for item in master_timeline:
|
|
153
|
+
cor = "green" if item["status"] == "ORIGEM" else "yellow"
|
|
154
|
+
icone = "👶" if item["status"] == "ORIGEM" else "🔧"
|
|
155
|
+
|
|
156
|
+
click.secho(f"\n[{item['info']['date']}] {icone} {item['status']}: Por {item['info']['author']} (Commit: {item['hash']})", fg=cor, bold=True)
|
|
157
|
+
click.echo(f" └─ Mensagem: \"{item['info']['message']}\"")
|
|
158
|
+
if item["motivo"]:
|
|
159
|
+
click.secho(f" └─ Análise IA: {item['motivo']}", fg="cyan", dim=True)
|
|
160
|
+
|
|
161
|
+
click.echo("\n" + "-"*60 + "\n")
|
|
162
|
+
|
|
163
|
+
# GERAÇÃO DO RELATÓRIO MARKDOWN (ÚNICO)
|
|
164
|
+
click.secho("📝 Gerando relatório Markdown unificado com o resumo da IA...", fg="cyan")
|
|
165
|
+
|
|
166
|
+
branch_name = get_current_branch()
|
|
167
|
+
safe_branch_name = branch_name.replace("/", "-").replace("\\", "-")
|
|
168
|
+
current_time = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
169
|
+
|
|
170
|
+
pattern = os.getenv("OUTPUT_FILE_NAME_BLAME", "{branch}_{datetime}_BLAME_REPORT.md")
|
|
171
|
+
output_filename = pattern.format(branch=safe_branch_name, datetime=current_time)
|
|
172
|
+
|
|
173
|
+
# Monta a Tabela Markdown
|
|
174
|
+
md_content = f"# Linha do tempo da regra investigada\n\n"
|
|
175
|
+
md_content += f"**Arquivo:** `{file_path}` (Linhas {start_line}-{end_line})\n\n"
|
|
176
|
+
md_content += "| Data | Commit | Autor | O quê |\n"
|
|
177
|
+
md_content += "|---|---|---|---|\n"
|
|
178
|
+
|
|
179
|
+
for item in master_timeline:
|
|
180
|
+
data_fmt = item['info']['date']
|
|
181
|
+
hash_curto = item['hash']
|
|
182
|
+
autor = item['info']['author']
|
|
183
|
+
msg_commit = item['info']['message']
|
|
184
|
+
|
|
185
|
+
# Pega a explicação da IA ou coloca um fallback seguro
|
|
186
|
+
explicacao_ia = item['motivo'] if item['motivo'] else "Alteração identificada na regra"
|
|
187
|
+
|
|
188
|
+
# Junta a explicação da IA com a mensagem do commit (Estilo Tabela de Referência)
|
|
189
|
+
motivo_final = f"{explicacao_ia} — *\"{msg_commit}\"*"
|
|
190
|
+
|
|
191
|
+
md_content += f"| {data_fmt} | `{hash_curto}` | {autor} | {motivo_final} |\n"
|
|
192
|
+
|
|
193
|
+
# IA gera o Resumo Analítico Final
|
|
194
|
+
summary_prompt = "Baseado na seguinte linha do tempo de commits de uma regra de negócio, escreva um único parágrafo resumindo a idade da regra, o autor original, o número de refatorações e deduza qual era a intenção original de negócio (o motivo real da regra existir no sistema).\n\n"
|
|
195
|
+
for item in master_timeline:
|
|
196
|
+
summary_prompt += f"[{item['info']['date']}] {item['info']['author']} ({item['hash']}) - {item['status']}: {item['motivo']}\n"
|
|
197
|
+
|
|
198
|
+
provider = get_ai_provider()
|
|
199
|
+
api_key = get_api_key(provider)
|
|
200
|
+
api_model = get_api_model(provider, task_complexity="advanced")
|
|
201
|
+
sys_inst = "Você é um Arquiteto de Software. Gere APENAS um objeto JSON no formato {\"resumo\": \"texto do resumo\"}."
|
|
202
|
+
|
|
203
|
+
click.secho(f" 🤖 Consultando a IA ({api_model}) para o Resumo Executivo...", fg="cyan", dim=True)
|
|
204
|
+
summary_json = call_ai_model(provider, api_key, api_model, summary_prompt, sys_inst)
|
|
205
|
+
|
|
206
|
+
resumo_texto = summary_json.get("resumo", "Resumo não disponível.") if summary_json else "Resumo não disponível."
|
|
207
|
+
|
|
208
|
+
md_content += f"\n**Resumo:** {resumo_texto}\n"
|
|
209
|
+
|
|
210
|
+
# Salva no disco
|
|
211
|
+
try:
|
|
212
|
+
with open(output_filename, "w", encoding="utf-8") as f:
|
|
213
|
+
f.write(md_content)
|
|
214
|
+
click.secho(f"✅ Relatório unificado salvo com sucesso: '{output_filename}'", fg="green", bold=True)
|
|
215
|
+
except Exception as e:
|
|
216
|
+
click.secho(f"❌ Erro ao salvar o relatório: {e}", fg="red")
|
|
@@ -20,7 +20,9 @@ DEFAULT_CONFIG = {
|
|
|
20
20
|
"OUTPUT_FILE_NAME": "{branch}_{datetime}_PR_DESC.md",
|
|
21
21
|
"OUTPUT_FILE_NAME_REVIEW": "{branch}_{datetime}_PR_REVIEW.txt",
|
|
22
22
|
"OUTPUT_FILE_NAME_FULLREVIEW": "{branch}_{datetime}_PR_FULLREVIEW.txt",
|
|
23
|
-
"OUTPUT_FILE_NAME_FILEREVIEW": "{branch}_{datetime}_FILE_REVIEW.txt"
|
|
23
|
+
"OUTPUT_FILE_NAME_FILEREVIEW": "{branch}_{datetime}_FILE_REVIEW.txt",
|
|
24
|
+
"OUTPUT_FILE_NAME_BLAME": "{branch}_{datetime}_BLAME_REPORT.md",
|
|
25
|
+
"OUTPUT_FILE_NAME_ISSUE": "{branch}_{datetime}_ISSUE.md"
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
def get_ai_provider():
|
|
@@ -151,4 +153,13 @@ def load_linter_rules():
|
|
|
151
153
|
return []
|
|
152
154
|
except Exception as e:
|
|
153
155
|
click.secho(f"\n❌ Erro inesperado ao ler as regras do linter: {e}", fg="red")
|
|
154
|
-
return []
|
|
156
|
+
return []
|
|
157
|
+
|
|
158
|
+
def get_github_token():
|
|
159
|
+
"""Lê e desencripta o token de acesso pessoal (PAT) do GitHub."""
|
|
160
|
+
load_dotenv(ENV_FILE)
|
|
161
|
+
encrypted_token = os.getenv("GITHUB_TOKEN_ENCRYPTED")
|
|
162
|
+
|
|
163
|
+
if encrypted_token:
|
|
164
|
+
return decrypt_data(encrypted_token)
|
|
165
|
+
return None
|
|
@@ -76,6 +76,8 @@ def get_skill_context(action_type="pr"):
|
|
|
76
76
|
target_file = ".gitpr.pr.md"
|
|
77
77
|
elif action_type == "filereview": # NOVO!
|
|
78
78
|
target_file = ".gitpr.filereview.md"
|
|
79
|
+
elif action_type == "issue":
|
|
80
|
+
target_file = ".gitpr.issue.md"
|
|
79
81
|
else: # review ou fullreview
|
|
80
82
|
target_file = ".gitpr.review.md"
|
|
81
83
|
|
|
@@ -188,6 +190,8 @@ def generate_skill_template():
|
|
|
188
190
|
".gitpr.review.md": "gitpr.review.md",
|
|
189
191
|
".gitpr.linter.yml": "gitpr.linter.yml",
|
|
190
192
|
".gitpr.filereview.md": "gitpr.filereview.md",
|
|
193
|
+
".gitpr.blame.md": "gitpr.blame.md",
|
|
194
|
+
".gitpr.issue.md": "gitpr.issue.md",
|
|
191
195
|
}
|
|
192
196
|
|
|
193
197
|
success_count = 0
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import re
|
|
3
|
+
import os
|
|
4
|
+
import click
|
|
5
|
+
from src.ai_providers import call_ai_model
|
|
6
|
+
from src.cache import get_cached_response, save_cached_response
|
|
7
|
+
from src.config import get_api_key, get_api_model, get_ai_provider
|
|
8
|
+
from src.ai_providers import call_ai_model
|
|
9
|
+
|
|
10
|
+
def get_github_repo_info():
|
|
11
|
+
"""Extrai o formato owner/repo do comando git remote -v."""
|
|
12
|
+
try:
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
["git", "remote", "-v"],
|
|
15
|
+
capture_output=True,
|
|
16
|
+
text=True,
|
|
17
|
+
check=True
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Busca padrões como git@github.com:owner/repo.git ou https://github.com/owner/repo.git
|
|
21
|
+
match = re.search(r'github\.com[:/](.+?)/(.+?)(\.git)?\s+\(push\)', result.stdout)
|
|
22
|
+
|
|
23
|
+
if match:
|
|
24
|
+
owner = match.group(1)
|
|
25
|
+
repo = match.group(2).replace('.git', '')
|
|
26
|
+
return f"{owner}/{repo}"
|
|
27
|
+
|
|
28
|
+
return None
|
|
29
|
+
except subprocess.CalledProcessError:
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
def generate_issue_content(diff_text):
|
|
33
|
+
"""Envia o diff para a IA e retorna um dicionário com título e corpo da issue."""
|
|
34
|
+
if not diff_text or not diff_text.strip():
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
provider = get_ai_provider()
|
|
38
|
+
api_key = get_api_key(provider)
|
|
39
|
+
|
|
40
|
+
if not api_key:
|
|
41
|
+
click.secho("❌ Erro: Chave de API não encontrada.", fg="red")
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
# Utilizamos o modelo avançado para garantir a qualidade da estrutura da Issue
|
|
45
|
+
api_model = get_api_model(provider, task_complexity="advanced")
|
|
46
|
+
|
|
47
|
+
skill_path = os.path.join(os.getcwd(), ".gitpr.issue.md")
|
|
48
|
+
sys_inst = ""
|
|
49
|
+
|
|
50
|
+
if os.path.exists(skill_path):
|
|
51
|
+
with open(skill_path, "r", encoding="utf-8") as f:
|
|
52
|
+
sys_inst = f.read()
|
|
53
|
+
else:
|
|
54
|
+
sys_inst = "Você é um Arquiteto de Software. Siga o formato O Que / Por Que / Onde / Como para documentar a Issue."
|
|
55
|
+
|
|
56
|
+
prompt = (
|
|
57
|
+
f"Gere o objeto JSON solicitado seguindo as instruções de sistema para documentar a seguinte alteração:\n\n"
|
|
58
|
+
f"DIFF PARA ANÁLISE:\n{diff_text}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# --- NOVO: Tenta recuperar do Cache ---
|
|
62
|
+
cached_data = get_cached_response("issue", prompt)
|
|
63
|
+
if cached_data:
|
|
64
|
+
click.secho("⚡ Resposta da Issue recuperada do cache local.", fg="green", dim=True)
|
|
65
|
+
return cached_data
|
|
66
|
+
|
|
67
|
+
click.secho(f"🤖 Estruturando a Issue usando {provider.capitalize()} ({api_model})...", fg="cyan", dim=True)
|
|
68
|
+
|
|
69
|
+
result_json = call_ai_model(provider, api_key, api_model, prompt, sys_inst)
|
|
70
|
+
|
|
71
|
+
if result_json and "titulo" in result_json and "corpo" in result_json:
|
|
72
|
+
# --- NOVO: Salva no Cache ---
|
|
73
|
+
save_cached_response("issue", "issue", prompt, result_json)
|
|
74
|
+
return result_json
|
|
75
|
+
|
|
76
|
+
return {"titulo": "Erro ao gerar título", "corpo": "Não foi possível gerar o corpo da issue pela IA."}
|
|
@@ -29,7 +29,7 @@ def print_banner():
|
|
|
29
29
|
"""
|
|
30
30
|
click.secho(banner, fg="cyan", bold=True)
|
|
31
31
|
click.secho(f" 🚀 Automação Inteligente de PRs com IA (v{__version__})", fg="yellow", bold=True)
|
|
32
|
-
click.secho(" Opções: -c,--commit | -r,--review | -f,--fullreview | -l,--linter | -s,--skill | -u,--update | -ih,--installhooks | -h ou --help\n", fg="white", dim=True)
|
|
32
|
+
click.secho(" Opções: -c,--commit | -r,--review | -f,--fullreview | -l,--linter | -s,--skill | -u,--update | -ih,--installhooks | -is,--issue | -h ou --help\n", fg="white", dim=True)
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
# Configuração nativa do Click para aceitar -h além de --help
|
|
@@ -45,8 +45,10 @@ def print_banner():
|
|
|
45
45
|
@click.option('--hook', type=click.Path(), hidden=True, help="Caminho do arquivo de commit (uso interno dos hooks).")
|
|
46
46
|
@click.option('-q', '--quiet', is_flag=True, hidden=True, help="Oculta o banner e logs não essenciais (uso interno).")
|
|
47
47
|
@click.option('-i', '--input', type=click.Path(exists=True), help="Caminho de um arquivo específico para análise completa.")
|
|
48
|
+
@click.option('-b', '--blame', type=str, help="Analisa a origem de uma regra de negócio (ex: arquivo.py:10-20 ou apenas arquivo.py).")
|
|
49
|
+
@click.option('-is', '--issue', is_flag=True, help="Gera uma Issue padronizada das alterações atuais e abre a interface interativa.")
|
|
48
50
|
@click.option('-p', '--provider', type=click.Choice(['gemini', 'deepseek']), help="Força a utilização de um provedor de IA específico nesta execução.")
|
|
49
|
-
def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, quiet, provider, input):
|
|
51
|
+
def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, quiet, provider, input, blame, issue):
|
|
50
52
|
"""
|
|
51
53
|
GitPR CLI - Automação de PRs e Code Review com IA.
|
|
52
54
|
|
|
@@ -137,6 +139,85 @@ def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, q
|
|
|
137
139
|
click.echo("---\n")
|
|
138
140
|
return
|
|
139
141
|
|
|
142
|
+
# Módulo Arqueólogo (--blame)
|
|
143
|
+
if blame:
|
|
144
|
+
# Parser para separar o arquivo das linhas
|
|
145
|
+
if ":" in blame:
|
|
146
|
+
# Modo Direto: gitpr --blame arquivo:10-20
|
|
147
|
+
file_path, lines = blame.split(":", 1)
|
|
148
|
+
try:
|
|
149
|
+
if "-" in lines:
|
|
150
|
+
start_line, end_line = lines.split("-")
|
|
151
|
+
else:
|
|
152
|
+
start_line = end_line = lines
|
|
153
|
+
except ValueError:
|
|
154
|
+
click.secho("❌ Formato de linhas inválido. Use inicio-fim (ex: 10-20).", fg="red")
|
|
155
|
+
return
|
|
156
|
+
else:
|
|
157
|
+
# Modo Interativo: gitpr --blame arquivo
|
|
158
|
+
file_path = blame
|
|
159
|
+
if not os.path.exists(file_path):
|
|
160
|
+
click.secho(f"❌ O arquivo '{file_path}' não foi encontrado.", fg="red")
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
click.secho(f"📂 Arquivo selecionado: {file_path}", fg="cyan", bold=True)
|
|
164
|
+
lines_input = click.prompt("Quais linhas você deseja investigar? (Ex: 10-20 ou apenas 45)")
|
|
165
|
+
|
|
166
|
+
if "-" in lines_input:
|
|
167
|
+
start_line, end_line = lines_input.split("-")
|
|
168
|
+
else:
|
|
169
|
+
start_line = end_line = lines_input
|
|
170
|
+
|
|
171
|
+
# Validação final do arquivo
|
|
172
|
+
if not os.path.exists(file_path):
|
|
173
|
+
click.secho(f"❌ O arquivo '{file_path}' não foi encontrado.", fg="red")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
# Aciona o motor
|
|
177
|
+
from src.blame_engine import run_blame_analysis
|
|
178
|
+
run_blame_analysis(file_path.strip(), start_line.strip(), end_line.strip())
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
# Módulo Issue (--issue)
|
|
182
|
+
if issue:
|
|
183
|
+
# Importações sob demanda para não atrasar a CLI se não for usar a TUI
|
|
184
|
+
from src.issue_engine import generate_issue_content, get_github_repo_info
|
|
185
|
+
from src.tui_issue import validate_or_request_github_token, IssueApp
|
|
186
|
+
|
|
187
|
+
diff_text = get_git_diff()
|
|
188
|
+
if not diff_text or not diff_text.strip():
|
|
189
|
+
click.secho("\n⚠️ Nenhum código novo encontrado. Faça alguma alteração antes de gerar a issue.\n", fg="yellow")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# Garante que as chaves da IA estão configuradas antes de chamar o motor
|
|
193
|
+
setup_environment()
|
|
194
|
+
|
|
195
|
+
# Gera o conteúdo com a IA
|
|
196
|
+
issue_data = generate_issue_content(diff_text)
|
|
197
|
+
if not issue_data:
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
# Pega informações do repositório
|
|
201
|
+
repo_info = get_github_repo_info()
|
|
202
|
+
|
|
203
|
+
# Valida ou solicita o Token PAT
|
|
204
|
+
github_token = validate_or_request_github_token(repo_info)
|
|
205
|
+
|
|
206
|
+
if not github_token:
|
|
207
|
+
click.secho("❌ Acesso cancelado. O Token do GitHub é obrigatório para esta ação.", fg="red")
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
# Roda a Interface Gráfica do Terminal
|
|
211
|
+
app = IssueApp(issue_data=issue_data, repo_info=repo_info, github_token=github_token)
|
|
212
|
+
app.run()
|
|
213
|
+
|
|
214
|
+
# Exibe a mensagem de retorno após fechar a TUI
|
|
215
|
+
if app.final_message:
|
|
216
|
+
cor = "green" if app.final_action in ["saved", "created"] else "red"
|
|
217
|
+
click.secho(f"\n{app.final_message}\n", fg=cor, bold=True)
|
|
218
|
+
|
|
219
|
+
return
|
|
220
|
+
|
|
140
221
|
# Validação do Modo Input
|
|
141
222
|
if input and not (review or fullreview):
|
|
142
223
|
click.secho("\n❌ Erro: A opção --input (-i) só pode ser utilizada em conjunto com --review (-r) ou --fullreview (-f).", fg="red", bold=True)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from dotenv import set_key
|
|
3
|
+
from src.security import encrypt_data
|
|
4
|
+
from src.config import get_github_token, ENV_FILE
|
|
5
|
+
|
|
6
|
+
# Importa a classe do aplicativo da nossa nova pasta de classes
|
|
7
|
+
from src.ui.issue_app import IssueApp
|
|
8
|
+
|
|
9
|
+
def validate_or_request_github_token(repo_info):
|
|
10
|
+
"""Verifica se o PAT existe, caso contrário solicita ao usuário, encripta e salva."""
|
|
11
|
+
token = get_github_token()
|
|
12
|
+
if token:
|
|
13
|
+
return token
|
|
14
|
+
|
|
15
|
+
click.secho(f"\n🔐 Autenticação do GitHub Necessária", fg="cyan", bold=True)
|
|
16
|
+
click.echo("Para criar issues diretamente, precisamos de um Personal Access Token (PAT).")
|
|
17
|
+
click.echo("Clique no link abaixo para gerar um com a permissão 'repo' já selecionada:")
|
|
18
|
+
|
|
19
|
+
repo_param = repo_info if repo_info else "seu-repositorio"
|
|
20
|
+
url_token = f"https://github.com/settings/tokens/new?scopes=repo&description=GitPR+Token+({repo_param})"
|
|
21
|
+
click.secho(f"👉 {url_token}\n", fg="blue", underline=True)
|
|
22
|
+
|
|
23
|
+
# --- NOVO: Link dinâmico para a documentação técnica ---
|
|
24
|
+
click.secho("📚 Entenda por que precisamos do Token e como ele é protegido por criptografia:", fg="cyan", dim=True)
|
|
25
|
+
click.secho("👉 https://github.com/natanfiuza/gitpr/blob/main/docs/github-pat-integration.md\n", fg="blue", underline=True)
|
|
26
|
+
|
|
27
|
+
raw_token = click.prompt("Cole aqui o seu Token (PAT)", hide_input=True)
|
|
28
|
+
|
|
29
|
+
encrypted_token = encrypt_data(raw_token.strip())
|
|
30
|
+
|
|
31
|
+
set_key(ENV_FILE, "GITHUB_TOKEN_ENCRYPTED", encrypted_token)
|
|
32
|
+
click.secho("✅ Token encriptado e salvo com segurança no .env!\n", fg="green")
|
|
33
|
+
|
|
34
|
+
return raw_token.strip()
|
|
@@ -7,7 +7,7 @@ import click
|
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
|
|
9
9
|
# Versão atual do seu executável local (Atualize isso a cada novo build!)
|
|
10
|
-
__version__ = "0.0.
|
|
10
|
+
__version__ = "0.0.15"
|
|
11
11
|
GITHUB_API_URL = "https://api.github.com/repos/natanfiuza/gitpr/releases/latest"
|
|
12
12
|
PYPI_API_URL = "https://pypi.org/pypi/gitpr-cli/json"
|
|
13
13
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|