baucli 1.0.1__tar.gz → 1.0.2__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.
- {baucli-1.0.1 → baucli-1.0.2}/PKG-INFO +1 -1
- {baucli-1.0.1 → baucli-1.0.2}/baucli/__init__.py +1 -1
- {baucli-1.0.1 → baucli-1.0.2}/baucli/cli.py +171 -3
- {baucli-1.0.1 → baucli-1.0.2}/baucli/config.py +160 -10
- {baucli-1.0.1 → baucli-1.0.2}/baucli/project.py +8 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli.egg-info/PKG-INFO +1 -1
- baucli-1.0.2/baucli.egg-info/entry_points.txt +2 -0
- {baucli-1.0.1 → baucli-1.0.2}/setup.py +2 -2
- baucli-1.0.1/baucli.egg-info/entry_points.txt +0 -2
- {baucli-1.0.1 → baucli-1.0.2}/README.md +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/apex_rebuild.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/apexlang.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/api.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/db/__init__.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/db/mssql.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/db/mysql.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/db/oracle.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/db/postgresql.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/docmig.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/git.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/legacy/__init__.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/legacy/delphi.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/mcp_server.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/rag_embed.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/sample_scan.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/scope_scan.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/screenshot.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/seed_check.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/stress.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli/sync.py +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli.egg-info/SOURCES.txt +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli.egg-info/dependency_links.txt +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli.egg-info/requires.txt +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/baucli.egg-info/top_level.txt +0 -0
- {baucli-1.0.1 → baucli-1.0.2}/setup.cfg +0 -0
|
@@ -36,6 +36,7 @@ from baucli.config import (
|
|
|
36
36
|
load_config, save_config, is_configured, get_database,
|
|
37
37
|
add_database, remove_database, list_databases, set_db_password,
|
|
38
38
|
get_db_password, delete_db_password, DEFAULT_CONFIG,
|
|
39
|
+
BauConfigError,
|
|
39
40
|
)
|
|
40
41
|
from baucli.api import BauApi, BauApiError, BauSessionError
|
|
41
42
|
from baucli.sync import sync_database, sync_all, get_extractor, get_sync_recommendation, PARALLEL_THRESHOLD
|
|
@@ -134,7 +135,9 @@ def _do_login(cfg: dict, email: str, password: str) -> bool:
|
|
|
134
135
|
# ═══════════════════════════════════════════════════════════
|
|
135
136
|
@click.group()
|
|
136
137
|
@click.version_option(__version__, prog_name=__product__)
|
|
137
|
-
|
|
138
|
+
@click.option("--profile", "profile", default=None,
|
|
139
|
+
help="Perfil de credencial a usar nesta invocação (sobrepõe bau.toml/default).")
|
|
140
|
+
def main(profile):
|
|
138
141
|
"""BAUCLI — AI-powered database documentation.
|
|
139
142
|
|
|
140
143
|
\b
|
|
@@ -144,7 +147,8 @@ def main():
|
|
|
144
147
|
baucli sync
|
|
145
148
|
baucli context
|
|
146
149
|
"""
|
|
147
|
-
|
|
150
|
+
if profile:
|
|
151
|
+
os.environ["BAUCLI_PROFILE"] = profile
|
|
148
152
|
|
|
149
153
|
|
|
150
154
|
# ── Aviso global: "golden desatualizado" ────────────────────────────
|
|
@@ -1846,6 +1850,113 @@ def _write_root_pointer_md(current_schema: str, primary_locale: str,
|
|
|
1846
1850
|
pointer.write_text(new_content, encoding="utf-8")
|
|
1847
1851
|
|
|
1848
1852
|
|
|
1853
|
+
# ═══════════════════════════════════════════════════════════
|
|
1854
|
+
# baucli profile (Fase 1 — perfis de credencial por tenant)
|
|
1855
|
+
# ═══════════════════════════════════════════════════════════
|
|
1856
|
+
@main.group()
|
|
1857
|
+
def profiles():
|
|
1858
|
+
"""Perfis de credencial (1 BAUCLI_HOME, N tenants).
|
|
1859
|
+
|
|
1860
|
+
\b
|
|
1861
|
+
profiles add NOME --tenant T --api-key K [--base-url U] [--db DB] [--default]
|
|
1862
|
+
profiles ls
|
|
1863
|
+
profiles use NOME
|
|
1864
|
+
profiles rm NOME
|
|
1865
|
+
O bau.toml do projeto escolhe o perfil por [bau].profile (sem segredo no repo).
|
|
1866
|
+
"""
|
|
1867
|
+
pass
|
|
1868
|
+
|
|
1869
|
+
|
|
1870
|
+
@profiles.command("add")
|
|
1871
|
+
@click.argument("name")
|
|
1872
|
+
@click.option("--tenant", "tenant_code", default=None, help="Código do tenant (ex.: GRUPO_DASS).")
|
|
1873
|
+
@click.option("--api-key", "api_key", required=True, help="API key do dev nesse tenant (bau_...).")
|
|
1874
|
+
@click.option("--base-url", "base_url", default=None, help="URL da API (default: a do config).")
|
|
1875
|
+
@click.option("--db", "default_db", default=None, help="Alias de database default do perfil.")
|
|
1876
|
+
@click.option("--default", "make_default", is_flag=True, help="Torna este o perfil default do BAUCLI_HOME.")
|
|
1877
|
+
def profile_add(name, tenant_code, api_key, base_url, default_db, make_default):
|
|
1878
|
+
"""Cria/atualiza um perfil e grava sua api_key no keyring."""
|
|
1879
|
+
from .config import load_config as _lc, save_config, set_profile_api_key
|
|
1880
|
+
cfg = _lc(apply_profile=False)
|
|
1881
|
+
profiles = cfg.setdefault("profiles", {})
|
|
1882
|
+
entry = profiles.get(name, {})
|
|
1883
|
+
if tenant_code:
|
|
1884
|
+
entry["tenant_code"] = tenant_code
|
|
1885
|
+
if base_url:
|
|
1886
|
+
entry["base_url"] = base_url
|
|
1887
|
+
if default_db:
|
|
1888
|
+
entry["default_db"] = default_db
|
|
1889
|
+
profiles[name] = entry
|
|
1890
|
+
if make_default or not cfg.get("default_profile"):
|
|
1891
|
+
cfg["default_profile"] = name
|
|
1892
|
+
save_config(cfg)
|
|
1893
|
+
in_kr = set_profile_api_key(name, api_key)
|
|
1894
|
+
tail = " (default do BAUCLI_HOME)" if cfg.get("default_profile") == name else ""
|
|
1895
|
+
console.print(f"[green]Perfil '{name}' salvo{tail}[/green] — chave no "
|
|
1896
|
+
f"{'keyring' if in_kr else '.credentials'}.")
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
@profiles.command("ls")
|
|
1900
|
+
def profile_ls():
|
|
1901
|
+
"""Lista os perfis do BAUCLI_HOME e qual está ativo."""
|
|
1902
|
+
from .config import load_config as _lc, resolve_profile_name, get_profile_api_key
|
|
1903
|
+
cfg = _lc(apply_profile=False)
|
|
1904
|
+
profiles = cfg.get("profiles", {})
|
|
1905
|
+
if not profiles:
|
|
1906
|
+
console.print(" [yellow]Nenhum perfil.[/yellow] Crie: "
|
|
1907
|
+
"[cyan]baucli profile add NOME --tenant T --api-key K[/cyan]")
|
|
1908
|
+
return
|
|
1909
|
+
try:
|
|
1910
|
+
active = resolve_profile_name(cfg)
|
|
1911
|
+
except Exception:
|
|
1912
|
+
active = None
|
|
1913
|
+
default = cfg.get("default_profile")
|
|
1914
|
+
table = RichTable(title=f"Perfis ({len(profiles)})", border_style="dim")
|
|
1915
|
+
table.add_column("Nome", style="cyan")
|
|
1916
|
+
table.add_column("Tenant")
|
|
1917
|
+
table.add_column("DB default")
|
|
1918
|
+
table.add_column("Chave")
|
|
1919
|
+
table.add_column("Default", style="yellow")
|
|
1920
|
+
table.add_column("Ativo", style="green")
|
|
1921
|
+
for n, p in profiles.items():
|
|
1922
|
+
table.add_row(n, p.get("tenant_code", "—"), p.get("default_db", "—"),
|
|
1923
|
+
"sim" if get_profile_api_key(n) else "—",
|
|
1924
|
+
"★" if n == default else "", "●" if n == active else "")
|
|
1925
|
+
console.print(table)
|
|
1926
|
+
console.print(" [dim]Ativo = env BAUCLI_PROFILE > bau.toml [bau].profile > default.[/dim]")
|
|
1927
|
+
|
|
1928
|
+
|
|
1929
|
+
@profiles.command("use")
|
|
1930
|
+
@click.argument("name")
|
|
1931
|
+
def profile_use(name):
|
|
1932
|
+
"""Define o perfil default do BAUCLI_HOME."""
|
|
1933
|
+
from .config import load_config as _lc, save_config
|
|
1934
|
+
cfg = _lc(apply_profile=False)
|
|
1935
|
+
if name not in cfg.get("profiles", {}):
|
|
1936
|
+
console.print(f"[red]Perfil '{name}' não existe.[/red] Veja: baucli profile ls")
|
|
1937
|
+
raise SystemExit(1)
|
|
1938
|
+
cfg["default_profile"] = name
|
|
1939
|
+
save_config(cfg)
|
|
1940
|
+
console.print(f"[green]Perfil default = '{name}'.[/green]")
|
|
1941
|
+
|
|
1942
|
+
|
|
1943
|
+
@profiles.command("rm")
|
|
1944
|
+
@click.argument("name")
|
|
1945
|
+
def profile_rm(name):
|
|
1946
|
+
"""Remove um perfil e sua api_key."""
|
|
1947
|
+
from .config import load_config as _lc, save_config, delete_profile_api_key
|
|
1948
|
+
cfg = _lc(apply_profile=False)
|
|
1949
|
+
if name not in cfg.get("profiles", {}):
|
|
1950
|
+
console.print(f"[yellow]Perfil '{name}' já não existe.[/yellow]")
|
|
1951
|
+
return
|
|
1952
|
+
cfg["profiles"].pop(name, None)
|
|
1953
|
+
if cfg.get("default_profile") == name:
|
|
1954
|
+
cfg["default_profile"] = next(iter(cfg["profiles"]), None)
|
|
1955
|
+
save_config(cfg)
|
|
1956
|
+
delete_profile_api_key(name)
|
|
1957
|
+
console.print(f"[green]Perfil '{name}' removido.[/green]")
|
|
1958
|
+
|
|
1959
|
+
|
|
1849
1960
|
# ═══════════════════════════════════════════════════════════
|
|
1850
1961
|
# baucli status
|
|
1851
1962
|
# ═══════════════════════════════════════════════════════════
|
|
@@ -1854,6 +1965,10 @@ def status():
|
|
|
1854
1965
|
"""Show agent config, quota, and pending deploys."""
|
|
1855
1966
|
cfg = load_config()
|
|
1856
1967
|
|
|
1968
|
+
if cfg.get("_active_profile"):
|
|
1969
|
+
console.print(f"[dim]Perfil ativo:[/dim] [cyan]{cfg['_active_profile']}[/cyan] "
|
|
1970
|
+
f"· tenant {cfg.get('tenant_code') or '—'}")
|
|
1971
|
+
|
|
1857
1972
|
show_db_panel("Status")
|
|
1858
1973
|
|
|
1859
1974
|
# Databases table
|
|
@@ -7750,5 +7865,58 @@ def rag_search_cmd(pergunta, schema, top):
|
|
|
7750
7865
|
f"[bold]{it['key']}[/bold]: {it['text'][:140]}")
|
|
7751
7866
|
|
|
7752
7867
|
|
|
7868
|
+
def _report_crash(exc: Exception):
|
|
7869
|
+
"""Salva o traceback completo num log local e mostra mensagem de Suporte."""
|
|
7870
|
+
import traceback
|
|
7871
|
+
import time
|
|
7872
|
+
from baucli.config import BAUCLI_DIR
|
|
7873
|
+
log_path = None
|
|
7874
|
+
try:
|
|
7875
|
+
BAUCLI_DIR.mkdir(parents=True, exist_ok=True)
|
|
7876
|
+
log_path = BAUCLI_DIR / "last_error.log"
|
|
7877
|
+
with open(log_path, "w", encoding="utf-8") as f:
|
|
7878
|
+
f.write(f"{__product__} v{__version__}\n")
|
|
7879
|
+
f.write(f"cmd: {' '.join(sys.argv)}\n")
|
|
7880
|
+
f.write(f"time: {time.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
|
7881
|
+
f.write("".join(traceback.format_exception(type(exc), exc, exc.__traceback__)))
|
|
7882
|
+
except Exception:
|
|
7883
|
+
log_path = None
|
|
7884
|
+
console.print()
|
|
7885
|
+
console.print("[red]Ops — o BAUCLI encontrou um erro inesperado e não conseguiu concluir a operação.[/red]")
|
|
7886
|
+
console.print(f" [dim]{type(exc).__name__}: {exc}[/dim]")
|
|
7887
|
+
console.print(" Contate o [bold]Suporte BEG[/bold]: [cyan]www.begcloud.com[/cyan]")
|
|
7888
|
+
if log_path:
|
|
7889
|
+
console.print(f" Anexe este arquivo ao chamado: [cyan]{log_path}[/cyan]")
|
|
7890
|
+
console.print(" [dim]Detalhe técnico (devs): rode de novo com BAUCLI_DEBUG=1[/dim]")
|
|
7891
|
+
|
|
7892
|
+
|
|
7893
|
+
def cli():
|
|
7894
|
+
"""Entry point do console com captura GLOBAL de erros (UX amigável).
|
|
7895
|
+
|
|
7896
|
+
- Erros de configuração (JSON inválido) → mensagem clara e auto-corrigível.
|
|
7897
|
+
- Ctrl-C → 'Cancelado.'.
|
|
7898
|
+
- Qualquer erro não tratado → mensagem de Suporte + traceback salvo em
|
|
7899
|
+
BAUCLI_HOME/last_error.log. BAUCLI_DEBUG=1 mostra o traceback cru.
|
|
7900
|
+
Erros de uso do Click (opção inválida etc.) seguem o comportamento padrão.
|
|
7901
|
+
"""
|
|
7902
|
+
try:
|
|
7903
|
+
main(standalone_mode=True)
|
|
7904
|
+
except SystemExit:
|
|
7905
|
+
raise
|
|
7906
|
+
except KeyboardInterrupt:
|
|
7907
|
+
console.print("\n[dim]Cancelado.[/dim]")
|
|
7908
|
+
sys.exit(130)
|
|
7909
|
+
except BauConfigError as e:
|
|
7910
|
+
console.print(f"[red]Configuração inválida.[/red]\n{e}")
|
|
7911
|
+
sys.exit(1)
|
|
7912
|
+
except click.ClickException:
|
|
7913
|
+
raise
|
|
7914
|
+
except Exception as e: # noqa: BLE001 — captura geral proposital
|
|
7915
|
+
if os.environ.get("BAUCLI_DEBUG"):
|
|
7916
|
+
raise
|
|
7917
|
+
_report_crash(e)
|
|
7918
|
+
sys.exit(1)
|
|
7919
|
+
|
|
7920
|
+
|
|
7753
7921
|
if __name__ == "__main__":
|
|
7754
|
-
|
|
7922
|
+
cli()
|
|
@@ -38,6 +38,12 @@ try:
|
|
|
38
38
|
except Exception:
|
|
39
39
|
_keyring = None
|
|
40
40
|
|
|
41
|
+
class BauConfigError(Exception):
|
|
42
|
+
"""Problema no arquivo de configuração que o próprio usuário pode corrigir
|
|
43
|
+
(ex.: JSON malformado). Tratado pela CLI com mensagem clara — sem traceback,
|
|
44
|
+
sem 'contate o Suporte' (é auto-corrigível)."""
|
|
45
|
+
|
|
46
|
+
|
|
41
47
|
DEFAULT_CONFIG = {
|
|
42
48
|
"license_key": None,
|
|
43
49
|
"api_url": "https://begbrokerdev.begcloud.com/ords/bauctx/api/v1",
|
|
@@ -52,6 +58,8 @@ DEFAULT_CONFIG = {
|
|
|
52
58
|
"session_expires_at": None,
|
|
53
59
|
"user_email": None,
|
|
54
60
|
"language": "en_US",
|
|
61
|
+
"profiles": {},
|
|
62
|
+
"default_profile": None,
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
|
|
@@ -63,23 +71,73 @@ def ensure_dir():
|
|
|
63
71
|
os.chmod(BAU_DIR, 0o700)
|
|
64
72
|
|
|
65
73
|
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
_in_profile_apply = False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def load_config(apply_profile: bool = True) -> dict:
|
|
78
|
+
"""Load config from ~/.baucli/config.json.
|
|
79
|
+
|
|
80
|
+
When profiles are defined AND ``apply_profile`` is True, overlays the active
|
|
81
|
+
profile's credential (api_url / tenant_code / default_db / api_key) onto the
|
|
82
|
+
flat config so every consumer (CLI + MCP) transparently uses the right
|
|
83
|
+
(tenant, key). Precedence of the active profile: env BAUCLI_PROFILE >
|
|
84
|
+
bau.toml [bau].profile > config.json default_profile. No profiles / no match
|
|
85
|
+
→ identical to the legacy flat behavior. Pass ``apply_profile=False`` to get
|
|
86
|
+
the raw config (used by the profile-management commands themselves).
|
|
87
|
+
"""
|
|
68
88
|
if not CONFIG_FILE.exists():
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
89
|
+
merged = DEFAULT_CONFIG.copy()
|
|
90
|
+
else:
|
|
91
|
+
try:
|
|
92
|
+
with open(CONFIG_FILE) as f:
|
|
93
|
+
cfg = json.load(f)
|
|
94
|
+
except json.JSONDecodeError as e:
|
|
95
|
+
raise BauConfigError(
|
|
96
|
+
f"O arquivo de configuração está com JSON inválido:\n"
|
|
97
|
+
f" {CONFIG_FILE}\n"
|
|
98
|
+
f" → linha {e.lineno}, coluna {e.colno} ({e.msg}).\n"
|
|
99
|
+
f"Dica: todo valor de texto precisa de aspas (ex.: "
|
|
100
|
+
f"\"tenant_code\": \"GRUPO_DASS\"); só null/true/false/números ficam sem.\n"
|
|
101
|
+
f"Valide com: python3 -m json.tool {CONFIG_FILE}"
|
|
102
|
+
) from e
|
|
103
|
+
# Merge with defaults for missing keys
|
|
104
|
+
merged = DEFAULT_CONFIG.copy()
|
|
105
|
+
merged.update(cfg)
|
|
106
|
+
|
|
107
|
+
global _in_profile_apply
|
|
108
|
+
if apply_profile and merged.get("profiles") and not _in_profile_apply:
|
|
109
|
+
try:
|
|
110
|
+
_in_profile_apply = True
|
|
111
|
+
name = resolve_profile_name(merged)
|
|
112
|
+
if name:
|
|
113
|
+
_apply_profile(merged, name)
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
finally:
|
|
117
|
+
_in_profile_apply = False
|
|
75
118
|
return merged
|
|
76
119
|
|
|
77
120
|
|
|
78
121
|
def save_config(cfg: dict):
|
|
79
|
-
"""Save config to ~/.baucli/config.json.
|
|
122
|
+
"""Save config to ~/.baucli/config.json.
|
|
123
|
+
|
|
124
|
+
Never persists a profile overlay into the flat config: strips the transient
|
|
125
|
+
`_active_profile`/`_pre_profile` markers and, for each key the active profile
|
|
126
|
+
overlaid, writes the PRE-profile (raw) value back — unless the caller changed
|
|
127
|
+
it on purpose (then the caller's value wins). Keeps profile secrets out of
|
|
128
|
+
config.json and prevents a load→mutate→save from leaking one tenant's key.
|
|
129
|
+
"""
|
|
80
130
|
ensure_dir()
|
|
131
|
+
to_write = dict(cfg)
|
|
132
|
+
pre = to_write.pop("_pre_profile", None)
|
|
133
|
+
to_write.pop("_active_profile", None)
|
|
134
|
+
if pre:
|
|
135
|
+
for k, ov in pre.items():
|
|
136
|
+
raw, overlaid = ov[0], ov[1]
|
|
137
|
+
if to_write.get(k) == overlaid: # caller didn't change it → restore raw
|
|
138
|
+
to_write[k] = raw
|
|
81
139
|
with open(CONFIG_FILE, "w") as f:
|
|
82
|
-
json.dump(
|
|
140
|
+
json.dump(to_write, f, indent=2, ensure_ascii=False)
|
|
83
141
|
if sys.platform != "win32":
|
|
84
142
|
os.chmod(CONFIG_FILE, 0o600)
|
|
85
143
|
|
|
@@ -148,6 +206,98 @@ def delete_db_password(db_name: str):
|
|
|
148
206
|
save_credentials(creds)
|
|
149
207
|
|
|
150
208
|
|
|
209
|
+
# ── Profiles (Fase 1: 1 BAUCLI_HOME, N perfis por tenant) ──────────────────
|
|
210
|
+
# Um "profile" é uma credencial NOMEADA: {tenant_code, base_url, default_db} no
|
|
211
|
+
# config.json + a api_key no keyring (namespaced). O bau.toml do projeto escolhe
|
|
212
|
+
# o perfil por nome lógico ([bau].profile) — sem segredo no repo. Precedência do
|
|
213
|
+
# perfil ativo: env BAUCLI_PROFILE > [bau].profile > config.json default_profile.
|
|
214
|
+
|
|
215
|
+
def _profile_keyring_user(name: str) -> str:
|
|
216
|
+
return f"__profile__:{name}"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def get_profile_api_key(name: str) -> Optional[str]:
|
|
220
|
+
"""API key do perfil: keyring primeiro, senão o arquivo .credentials."""
|
|
221
|
+
if _keyring:
|
|
222
|
+
try:
|
|
223
|
+
k = _keyring.get_password(KEYRING_SERVICE, _profile_keyring_user(name))
|
|
224
|
+
if k:
|
|
225
|
+
return k
|
|
226
|
+
except Exception:
|
|
227
|
+
pass
|
|
228
|
+
return load_credentials().get(_profile_keyring_user(name), {}).get("api_key")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def set_profile_api_key(name: str, api_key: str) -> bool:
|
|
232
|
+
"""Grava a api_key do perfil (keyring se houver, senão .credentials)."""
|
|
233
|
+
if _keyring:
|
|
234
|
+
try:
|
|
235
|
+
_keyring.set_password(KEYRING_SERVICE, _profile_keyring_user(name), api_key)
|
|
236
|
+
return True
|
|
237
|
+
except Exception:
|
|
238
|
+
pass
|
|
239
|
+
creds = load_credentials()
|
|
240
|
+
creds.setdefault(_profile_keyring_user(name), {})["api_key"] = api_key
|
|
241
|
+
save_credentials(creds)
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def delete_profile_api_key(name: str):
|
|
246
|
+
"""Remove a api_key do perfil (keyring + .credentials). Idempotente."""
|
|
247
|
+
if _keyring:
|
|
248
|
+
try:
|
|
249
|
+
_keyring.delete_password(KEYRING_SERVICE, _profile_keyring_user(name))
|
|
250
|
+
except Exception:
|
|
251
|
+
pass
|
|
252
|
+
creds = load_credentials()
|
|
253
|
+
if _profile_keyring_user(name) in creds:
|
|
254
|
+
creds.pop(_profile_keyring_user(name), None)
|
|
255
|
+
save_credentials(creds)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _project_profile() -> Optional[str]:
|
|
259
|
+
"""[bau].profile do bau.toml mais próximo (walk-up). None em qualquer falha."""
|
|
260
|
+
try:
|
|
261
|
+
from .project import find_project
|
|
262
|
+
proj = find_project()
|
|
263
|
+
return proj.profile if proj else None
|
|
264
|
+
except Exception:
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def resolve_profile_name(cfg: dict) -> Optional[str]:
|
|
269
|
+
"""Nome do perfil ativo: env BAUCLI_PROFILE > [bau].profile > default_profile."""
|
|
270
|
+
return (os.environ.get("BAUCLI_PROFILE")
|
|
271
|
+
or _project_profile()
|
|
272
|
+
or cfg.get("default_profile"))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _apply_profile(cfg: dict, name: str) -> dict:
|
|
276
|
+
"""Overlay dos campos do perfil sobre o cfg flat (in-place).
|
|
277
|
+
|
|
278
|
+
Registra em `_pre_profile` {campo: [raw, overlaid]} para o save_config poder
|
|
279
|
+
reverter (nunca persistir a chave do perfil nos campos flat)."""
|
|
280
|
+
prof = (cfg.get("profiles") or {}).get(name)
|
|
281
|
+
if not prof:
|
|
282
|
+
return cfg
|
|
283
|
+
pre = {}
|
|
284
|
+
|
|
285
|
+
def _ov(key, val):
|
|
286
|
+
if val is None:
|
|
287
|
+
return
|
|
288
|
+
pre[key] = [cfg.get(key), val]
|
|
289
|
+
cfg[key] = val
|
|
290
|
+
|
|
291
|
+
_ov("api_url", prof.get("base_url"))
|
|
292
|
+
_ov("tenant_code", prof.get("tenant_code"))
|
|
293
|
+
_ov("default_db", prof.get("default_db"))
|
|
294
|
+
_ov("api_key", get_profile_api_key(name))
|
|
295
|
+
cfg["_active_profile"] = name
|
|
296
|
+
if pre:
|
|
297
|
+
cfg["_pre_profile"] = pre
|
|
298
|
+
return cfg
|
|
299
|
+
|
|
300
|
+
|
|
151
301
|
def get_database(cfg: dict, db_name: Optional[str] = None) -> tuple:
|
|
152
302
|
"""Get database config by name or default.
|
|
153
303
|
|
|
@@ -88,6 +88,14 @@ class Project:
|
|
|
88
88
|
v = self._get("project", "scope")
|
|
89
89
|
return v.upper() if v else None
|
|
90
90
|
|
|
91
|
+
# -- [bau] -------------------------------------------------------------
|
|
92
|
+
@property
|
|
93
|
+
def profile(self) -> str | None:
|
|
94
|
+
"""[bau].profile — nome lógico do perfil de credencial (BAUCLI_HOME).
|
|
95
|
+
Seleciona qual (tenant, chave) o baucli/MCP usam neste projeto, sem
|
|
96
|
+
segredo no repo. Precedência: env BAUCLI_PROFILE > este > default_profile."""
|
|
97
|
+
return self._get("bau", "profile")
|
|
98
|
+
|
|
91
99
|
# -- [connection] ------------------------------------------------------
|
|
92
100
|
@property
|
|
93
101
|
def connection_name(self) -> str | None:
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="baucli",
|
|
5
|
-
version="1.0.
|
|
5
|
+
version="1.0.2",
|
|
6
6
|
description="BAUCLI — AI-powered database documentation CLI for BAU",
|
|
7
7
|
long_description=open("README.md").read(),
|
|
8
8
|
long_description_content_type="text/markdown",
|
|
@@ -28,7 +28,7 @@ setup(
|
|
|
28
28
|
},
|
|
29
29
|
entry_points={
|
|
30
30
|
"console_scripts": [
|
|
31
|
-
"baucli=baucli.cli:
|
|
31
|
+
"baucli=baucli.cli:cli",
|
|
32
32
|
],
|
|
33
33
|
},
|
|
34
34
|
classifiers=[
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|