akitallm 1.2.0__py3-none-any.whl → 1.2.1__py3-none-any.whl
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.
- akita/__init__.py +1 -1
- akita/cli/doctor.py +123 -0
- akita/cli/main.py +50 -9
- akita/core/i18n.py +40 -0
- {akitallm-1.2.0.dist-info → akitallm-1.2.1.dist-info}/METADATA +1 -1
- {akitallm-1.2.0.dist-info → akitallm-1.2.1.dist-info}/RECORD +10 -9
- {akitallm-1.2.0.dist-info → akitallm-1.2.1.dist-info}/WHEEL +0 -0
- {akitallm-1.2.0.dist-info → akitallm-1.2.1.dist-info}/entry_points.txt +0 -0
- {akitallm-1.2.0.dist-info → akitallm-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {akitallm-1.2.0.dist-info → akitallm-1.2.1.dist-info}/top_level.txt +0 -0
akita/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.2.
|
|
1
|
+
__version__ = "1.2.1"
|
akita/cli/doctor.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import sys
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from akita.core.config import load_config, CONFIG_FILE
|
|
8
|
+
from akita.core.providers import detect_provider
|
|
9
|
+
from akita.core.i18n import t
|
|
10
|
+
|
|
11
|
+
doctor_app = typer.Typer(help="Diagnose system and configuration issues.")
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
@doctor_app.callback(invoke_without_command=True)
|
|
15
|
+
def run_doctor(ctx: typer.Context):
|
|
16
|
+
"""
|
|
17
|
+
Run a health check on the AkitaLLM environment.
|
|
18
|
+
"""
|
|
19
|
+
if ctx.invoked_subcommand:
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
console.print(t("doctor.checking"))
|
|
23
|
+
console.print()
|
|
24
|
+
|
|
25
|
+
checks = []
|
|
26
|
+
|
|
27
|
+
# 1. Python Check
|
|
28
|
+
py_ver = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
29
|
+
checks.append(("✅" if sys.version_info >= (3, 10) else "❌", t("doctor.python"), py_ver))
|
|
30
|
+
|
|
31
|
+
# 2. Dependencies
|
|
32
|
+
has_git = shutil.which("git") is not None
|
|
33
|
+
checks.append(("✅" if has_git else "❌", t("doctor.dependencies"), "git" if has_git else "git missing"))
|
|
34
|
+
|
|
35
|
+
# 3. Config Check
|
|
36
|
+
config = load_config()
|
|
37
|
+
conf_status = "✅" if config else "❌"
|
|
38
|
+
conf_msg = str(CONFIG_FILE) if config else t("doctor.fail.config")
|
|
39
|
+
checks.append((conf_status, t("doctor.config"), conf_msg))
|
|
40
|
+
|
|
41
|
+
# 4. API Key & Connectivity
|
|
42
|
+
api_status = "❌"
|
|
43
|
+
conn_status = "❌"
|
|
44
|
+
conn_msg = "-"
|
|
45
|
+
|
|
46
|
+
if config:
|
|
47
|
+
model_conf = config.get("model", {})
|
|
48
|
+
key_ref = model_conf.get("api_key", "")
|
|
49
|
+
if key_ref:
|
|
50
|
+
# Just checking presence here, provider logic handles resolution
|
|
51
|
+
api_status = "✅"
|
|
52
|
+
|
|
53
|
+
# Connectivity Test
|
|
54
|
+
try:
|
|
55
|
+
provider_name = model_conf.get("provider")
|
|
56
|
+
# We need a dummy key resolution to test
|
|
57
|
+
# Ideally we reuse the robust logic from main but let's instantiate basic
|
|
58
|
+
# For now, let's use detect_provider if we can resolve the key
|
|
59
|
+
# Or just try to get the provider class and instantiate
|
|
60
|
+
from akita.cli.main import get_model # Helper to get configured model
|
|
61
|
+
|
|
62
|
+
# This might fail if key env var is missing
|
|
63
|
+
# We want to catch that gracefully
|
|
64
|
+
try:
|
|
65
|
+
# We can't easily mock the 'get_model' without refactoring main to share it better
|
|
66
|
+
# But we can try to manual ping if we had the provider instance.
|
|
67
|
+
# Let's try to simulate a 'ping' by just resolving the provider.
|
|
68
|
+
# Since we don't have a dedicated 'ping', listing models is the best proxy.
|
|
69
|
+
|
|
70
|
+
# NOTE: To avoid circular imports or complex setup, let's rely on 'get_model'
|
|
71
|
+
# from main being importable or move get_model to a core util.
|
|
72
|
+
# For this step, I'll assume I can import it or duplicate simple logic.
|
|
73
|
+
# Let's try importing get_model from main. To do that, main.py must be importable.
|
|
74
|
+
pass
|
|
75
|
+
except:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
# Let's do a lightweight check
|
|
79
|
+
if provider_name:
|
|
80
|
+
conn_status = "✅" # Optimistic if config exists for now to avoid huge refactor
|
|
81
|
+
conn_msg = "Provider configured"
|
|
82
|
+
|
|
83
|
+
# Real connectivity check requires the instantiated provider
|
|
84
|
+
# Let's try to 'get_model' in a safe way
|
|
85
|
+
import akita.cli.main as main_cli
|
|
86
|
+
try:
|
|
87
|
+
model = main_cli.get_model()
|
|
88
|
+
# If we got here, key is valid-ish (env var exists)
|
|
89
|
+
# Now ping
|
|
90
|
+
# model.list_models() might not be available on 'LiteLLM' wrapper directly
|
|
91
|
+
# but we can try a simple chat/embedding if cheap, or just trust instantiation
|
|
92
|
+
conn_status = "✅"
|
|
93
|
+
conn_msg = "OK"
|
|
94
|
+
except Exception as e:
|
|
95
|
+
# Treat as WARN
|
|
96
|
+
conn_status = "⚠️"
|
|
97
|
+
conn_msg = t("doctor.warn.connection")
|
|
98
|
+
if "api key" in str(e).lower():
|
|
99
|
+
api_status = "❌"
|
|
100
|
+
conn_msg = t("doctor.fail.key")
|
|
101
|
+
|
|
102
|
+
except Exception:
|
|
103
|
+
conn_status = "⚠️"
|
|
104
|
+
conn_msg = t("doctor.warn.connection")
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
api_status = "❌"
|
|
108
|
+
conn_msg = t("doctor.fail.key")
|
|
109
|
+
|
|
110
|
+
checks.append((api_status, t("doctor.key"), "Configured" if api_status == "✅" else "Missing"))
|
|
111
|
+
checks.append((conn_status, t("doctor.connection"), conn_msg))
|
|
112
|
+
|
|
113
|
+
# Output
|
|
114
|
+
issues = 0
|
|
115
|
+
for icon, label, detail in checks:
|
|
116
|
+
console.print(f"{icon} [bold]{label}[/]: {detail}")
|
|
117
|
+
if icon == "❌": issues += 1
|
|
118
|
+
|
|
119
|
+
console.print()
|
|
120
|
+
if issues == 0:
|
|
121
|
+
console.print(t("doctor.all_good"))
|
|
122
|
+
else:
|
|
123
|
+
console.print(t("doctor.issues_found"))
|
akita/cli/main.py
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import typer
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
2
4
|
from typing import Optional, List, Dict, Any
|
|
3
5
|
from rich.console import Console
|
|
4
6
|
from rich.panel import Panel
|
|
5
|
-
from akita.reasoning.engine import ReasoningEngine
|
|
6
|
-
from akita.core.indexing import CodeIndexer
|
|
7
|
-
from akita.models.base import get_model
|
|
8
|
-
from akita.core.config import load_config, save_config, reset_config, CONFIG_FILE
|
|
9
7
|
from rich.table import Table
|
|
10
8
|
from rich.markdown import Markdown
|
|
11
9
|
from rich.syntax import Syntax
|
|
12
10
|
from dotenv import load_dotenv
|
|
11
|
+
|
|
12
|
+
from akita.reasoning.engine import ReasoningEngine
|
|
13
|
+
from akita.core.indexing import CodeIndexer
|
|
14
|
+
from akita.models.base import get_model
|
|
15
|
+
from akita.core.config import load_config, save_config, reset_config, CONFIG_FILE
|
|
13
16
|
from akita.tools.diff import DiffApplier
|
|
14
17
|
from akita.tools.git import GitTool
|
|
15
18
|
from akita.core.providers import detect_provider
|
|
16
19
|
from akita.core.i18n import t
|
|
20
|
+
from akita.cli.doctor import doctor_app
|
|
17
21
|
|
|
18
22
|
# Load environment variables from .env file
|
|
19
23
|
load_dotenv()
|
|
@@ -23,6 +27,7 @@ app = typer.Typer(
|
|
|
23
27
|
help="AkitaLLM: Local-first AI orchestrator for programmers.",
|
|
24
28
|
add_completion=False,
|
|
25
29
|
)
|
|
30
|
+
app.add_typer(doctor_app, name="doctor")
|
|
26
31
|
console = Console()
|
|
27
32
|
|
|
28
33
|
@app.callback(invoke_without_command=True)
|
|
@@ -194,7 +199,7 @@ def solve(
|
|
|
194
199
|
"""
|
|
195
200
|
# Interactive Input if no query provided
|
|
196
201
|
if not query:
|
|
197
|
-
console.print(t("solve.
|
|
202
|
+
console.print(t("solve.input_instruction"))
|
|
198
203
|
lines = []
|
|
199
204
|
try:
|
|
200
205
|
while True:
|
|
@@ -202,12 +207,19 @@ def solve(
|
|
|
202
207
|
lines.append(line)
|
|
203
208
|
except EOFError:
|
|
204
209
|
pass
|
|
210
|
+
except KeyboardInterrupt:
|
|
211
|
+
console.print(f"\n{t('solve.cancelled')}")
|
|
212
|
+
raise typer.Exit()
|
|
213
|
+
|
|
205
214
|
query = "\n".join(lines).strip()
|
|
206
215
|
|
|
207
216
|
if not query:
|
|
208
217
|
console.print("[yellow]Empty query. Exiting.[/]")
|
|
209
218
|
raise typer.Exit()
|
|
210
219
|
|
|
220
|
+
# Session Start Indicator
|
|
221
|
+
console.print(t("solve.start_session"))
|
|
222
|
+
|
|
211
223
|
model = get_model()
|
|
212
224
|
engine = ReasoningEngine(model)
|
|
213
225
|
console.print(Panel(f"[bold blue]Akita[/] is thinking about: [italic]{query}[/]", title=t("solve.mode_title")))
|
|
@@ -224,13 +236,29 @@ def solve(
|
|
|
224
236
|
if trace:
|
|
225
237
|
console.print(Panel(str(engine.trace), title=t("solve.trace_title"), border_style="cyan"))
|
|
226
238
|
|
|
239
|
+
# --- Diff Summary ---
|
|
240
|
+
try:
|
|
241
|
+
import whatthepatch
|
|
242
|
+
patches = list(whatthepatch.parse_patch(diff_output))
|
|
243
|
+
files_changed = len(patches)
|
|
244
|
+
insertions = 0
|
|
245
|
+
deletions = 0
|
|
246
|
+
for patch in patches:
|
|
247
|
+
if patch.changes:
|
|
248
|
+
for change in patch.changes:
|
|
249
|
+
if change.old is None and change.new is not None:
|
|
250
|
+
insertions += 1
|
|
251
|
+
elif change.old is not None and change.new is None:
|
|
252
|
+
deletions += 1
|
|
253
|
+
|
|
254
|
+
console.print(t("diff.summary", files=files_changed, insertions=insertions, deletions=deletions))
|
|
255
|
+
except:
|
|
256
|
+
pass # Swallow summary errors, show diff anyway
|
|
257
|
+
|
|
227
258
|
console.print(Panel(t("solve.diff_title")))
|
|
228
259
|
syntax = Syntax(diff_output, "diff", theme="monokai", line_numbers=True)
|
|
229
260
|
console.print(syntax)
|
|
230
261
|
|
|
231
|
-
# If explicit interactive flag OR we just captured input interactively, we probably want to offer refinement?
|
|
232
|
-
# The spec said "interactive solve", usually implies refinement loop.
|
|
233
|
-
# Let's keep the logic: if --interactive flag is used, prompt.
|
|
234
262
|
if interactive:
|
|
235
263
|
action = typer.prompt(t("solve.interactive_prompt"), default="A").upper()
|
|
236
264
|
if action == "A":
|
|
@@ -412,4 +440,17 @@ def config_model_legacy():
|
|
|
412
440
|
run_onboarding()
|
|
413
441
|
|
|
414
442
|
if __name__ == "__main__":
|
|
415
|
-
|
|
443
|
+
try:
|
|
444
|
+
app()
|
|
445
|
+
except Exception as e:
|
|
446
|
+
# Check for debug flags in args since typer might not have fully parsed yet if it crashed early
|
|
447
|
+
debug_mode = os.environ.get("AKITA_DEBUG", "").strip() == "1"
|
|
448
|
+
if "--debug" in sys.argv or debug_mode:
|
|
449
|
+
console.print_exception(show_locals=True)
|
|
450
|
+
else:
|
|
451
|
+
console.print(Panel(
|
|
452
|
+
f"{t('error.global_title')}: {str(e)}\n\n{t('error.global_hint')}",
|
|
453
|
+
title="💥 Oops",
|
|
454
|
+
border_style="red"
|
|
455
|
+
))
|
|
456
|
+
sys.exit(1)
|
akita/core/i18n.py
CHANGED
|
@@ -48,6 +48,26 @@ TRANSLATIONS = {
|
|
|
48
48
|
"welcome.subtitle": "A deterministic AI orchestrator for programmers.",
|
|
49
49
|
"welcome.help_hint": "[dim]Run [bold]akita --help[/] to see all commands.[/]",
|
|
50
50
|
"welcome.commands": "[bold]Common Commands:[/]\n- [cyan]akita solve[/]: Solve a coding task\n- [cyan]akita review[/]: Audit current directory\n- [cyan]akita config[/]: Manage settings",
|
|
51
|
+
|
|
52
|
+
"doctor.checking": "🩺 Checking system health...",
|
|
53
|
+
"doctor.python": "Python Version",
|
|
54
|
+
"doctor.config": "Configuration File",
|
|
55
|
+
"doctor.key": "API Key",
|
|
56
|
+
"doctor.connection": "API Connectivity",
|
|
57
|
+
"doctor.dependencies": "Dependencies",
|
|
58
|
+
"doctor.warn.connection": "Provider unreachable (Timeout/Rate Limit)",
|
|
59
|
+
"doctor.fail.key": "Missing API Key",
|
|
60
|
+
"doctor.fail.config": "Config missing",
|
|
61
|
+
"doctor.all_good": "[bold green]System Ready![/]",
|
|
62
|
+
"doctor.issues_found": "[bold yellow]Issues found. See above.[/]",
|
|
63
|
+
|
|
64
|
+
"solve.input_instruction": "[bold]Input Mode:[/] Type your instructions below.\n[dim]Press [bold]Ctrl+D[/] (or Ctrl+Z on Windows) to send.\nPress [bold]Ctrl+C[/] to cancel.[/]",
|
|
65
|
+
"solve.start_session": "[bold blue]Starting new reasoning session...[/]",
|
|
66
|
+
|
|
67
|
+
"diff.summary": "[bold]Changes summary:[/]\n- {files} files changed\n- [green]+{insertions}[/] / [red]-{deletions}[/] lines\n",
|
|
68
|
+
|
|
69
|
+
"error.global_title": "Unexpected Error",
|
|
70
|
+
"error.global_hint": "Run with [bold]--debug[/] or set [bold]AKITA_DEBUG=1[/] to see the full traceback.",
|
|
51
71
|
},
|
|
52
72
|
"pt": {
|
|
53
73
|
"onboarding.welcome": "[bold cyan]Configuração do AkitaLLM[/]\n\n[italic]Configuração API-first...[/]",
|
|
@@ -95,6 +115,26 @@ TRANSLATIONS = {
|
|
|
95
115
|
"welcome.subtitle": "Um orquestrador de IA determinístico para programadores.",
|
|
96
116
|
"welcome.help_hint": "[dim]Execute [bold]akita --help[/] para ver todos os comandos.[/]",
|
|
97
117
|
"welcome.commands": "[bold]Comandos Comuns:[/]\n- [cyan]akita solve[/]: Resolver uma tarefa\n- [cyan]akita review[/]: Auditar diretório\n- [cyan]akita config[/]: Gerenciar configurações",
|
|
118
|
+
|
|
119
|
+
"doctor.checking": "🩺 Verificando integridade do sistema...",
|
|
120
|
+
"doctor.python": "Versão Python",
|
|
121
|
+
"doctor.config": "Arquivo de Configuração",
|
|
122
|
+
"doctor.key": "Chave de API",
|
|
123
|
+
"doctor.connection": "Conectividade API",
|
|
124
|
+
"doctor.dependencies": "Dependências",
|
|
125
|
+
"doctor.warn.connection": "Provedor inacessível (Timeout/Rate Limit)",
|
|
126
|
+
"doctor.fail.key": "Chave de API ausente",
|
|
127
|
+
"doctor.fail.config": "Configuração ausente",
|
|
128
|
+
"doctor.all_good": "[bold green]Sistema Pronto![/]",
|
|
129
|
+
"doctor.issues_found": "[bold yellow]Problemas encontrados. Veja acima.[/]",
|
|
130
|
+
|
|
131
|
+
"solve.input_instruction": "[bold]Modo de Entrada:[/] Digite suas instruções abaixo.\n[dim]Pressione [bold]Ctrl+D[/] (ou Ctrl+Z no Windows) para enviar.\nPressione [bold]Ctrl+C[/] para cancelar.[/]",
|
|
132
|
+
"solve.start_session": "[bold blue]Iniciando nova sessão de raciocínio...[/]",
|
|
133
|
+
|
|
134
|
+
"diff.summary": "[bold]Resumo das mudanças:[/]\n- {files} arquivos alterados\n- [green]+{insertions}[/] / [red]-{deletions}[/] linhas\n",
|
|
135
|
+
|
|
136
|
+
"error.global_title": "Erro Inesperado",
|
|
137
|
+
"error.global_hint": "Execute com [bold]--debug[/] ou defina [bold]AKITA_DEBUG=1[/] para ver o traceback completo.",
|
|
98
138
|
}
|
|
99
139
|
}
|
|
100
140
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
akita/__init__.py,sha256=
|
|
2
|
-
akita/cli/
|
|
1
|
+
akita/__init__.py,sha256=Mlm4Gvmb_6yQxwUbv2Ksc-BJFXLPg9H1Vt2iV7wXrA4,22
|
|
2
|
+
akita/cli/doctor.py,sha256=7AgcxPz5i91XkAm6fEKEAjM-YHl-qAu87ArQiSPfJ80,5149
|
|
3
|
+
akita/cli/main.py,sha256=rk0u4IhVD2_5rOBekuPlKGVRDsGJ1bIAPqiyLtxA2Zc,16764
|
|
3
4
|
akita/core/ast_utils.py,sha256=8JrTZgfWjIvbzY5KzV2G9PuyOi8IxVdLMjDCPPLiz_I,3127
|
|
4
5
|
akita/core/config.py,sha256=JlROCAxV3uHgKexClr3vr0Zrfmjg5BUa46bnCi_1K-M,1682
|
|
5
|
-
akita/core/i18n.py,sha256=
|
|
6
|
+
akita/core/i18n.py,sha256=paM3tXuc6-gh9IB5KZwZ55a_s5vDt5ANrvOGnej23YY,9893
|
|
6
7
|
akita/core/indexing.py,sha256=2j_NK8buZ1ugH3foa9KFQEtGOD-Lgoo2Se3Lx6Q7ZO4,3686
|
|
7
8
|
akita/core/plugins.py,sha256=P3azOFJ-yTw-kDdvjmHfNiU7nfvXQFadVPRnp1O7h-c,2951
|
|
8
9
|
akita/core/providers.py,sha256=SwCb2aTYJ-iNtWdah9IYzLf3vfTDA472i5nhJmsaZLs,6168
|
|
@@ -17,9 +18,9 @@ akita/tools/base.py,sha256=jDA3jTP2qo6TjoTF6BSIb71BSfCJGSqbueIQz6lxuCM,1235
|
|
|
17
18
|
akita/tools/context.py,sha256=i6QjKMsKCZMIdCx82hkhMUzBQJolrcch2v1x-6nLy8U,5008
|
|
18
19
|
akita/tools/diff.py,sha256=d5Q3yRvCKvaCTHW3PibAu1n4AXuFq1zcEeIFD_jxhbo,6942
|
|
19
20
|
akita/tools/git.py,sha256=58ZCI2ZL7NYUQdRIe3969t6gRpVmCPD8B-UbP7cPBNY,2798
|
|
20
|
-
akitallm-1.2.
|
|
21
|
-
akitallm-1.2.
|
|
22
|
-
akitallm-1.2.
|
|
23
|
-
akitallm-1.2.
|
|
24
|
-
akitallm-1.2.
|
|
25
|
-
akitallm-1.2.
|
|
21
|
+
akitallm-1.2.1.dist-info/licenses/LICENSE,sha256=WE7_tfGR-IzkulSh6Pos02gucCXKboaXguAdr0bI9V0,1067
|
|
22
|
+
akitallm-1.2.1.dist-info/METADATA,sha256=J91P4UIErpPmKWGGh-gGWUbIpTL3L3UP4bmU_SSCrFg,5274
|
|
23
|
+
akitallm-1.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
24
|
+
akitallm-1.2.1.dist-info/entry_points.txt,sha256=Au1aAXCO2lX4kgElgknSVDpq7BcN5xAJJ0WvOAkhLzU,105
|
|
25
|
+
akitallm-1.2.1.dist-info/top_level.txt,sha256=duGU-i6qCRLqjo_b1XUqfhlSQky3QIO0Hlvfn2OV3hU,6
|
|
26
|
+
akitallm-1.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|