agent-cli 0.67.2__py3-none-any.whl → 0.68.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.
- agent_cli/core/deps.py +58 -27
- agent_cli/install/extras.py +38 -20
- agent_cli/server/cli.py +14 -14
- {agent_cli-0.67.2.dist-info → agent_cli-0.68.1.dist-info}/METADATA +9 -2
- {agent_cli-0.67.2.dist-info → agent_cli-0.68.1.dist-info}/RECORD +8 -8
- {agent_cli-0.67.2.dist-info → agent_cli-0.68.1.dist-info}/WHEEL +0 -0
- {agent_cli-0.67.2.dist-info → agent_cli-0.68.1.dist-info}/entry_points.txt +0 -0
- {agent_cli-0.67.2.dist-info → agent_cli-0.68.1.dist-info}/licenses/LICENSE +0 -0
agent_cli/core/deps.py
CHANGED
|
@@ -4,19 +4,29 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import functools
|
|
6
6
|
import json
|
|
7
|
+
import os
|
|
7
8
|
from importlib.util import find_spec
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import TYPE_CHECKING, TypeVar
|
|
10
11
|
|
|
11
12
|
import typer
|
|
12
13
|
|
|
13
|
-
from agent_cli.
|
|
14
|
+
from agent_cli.config import load_config
|
|
15
|
+
from agent_cli.core.utils import console, print_error_message
|
|
14
16
|
|
|
15
17
|
if TYPE_CHECKING:
|
|
16
18
|
from collections.abc import Callable
|
|
17
19
|
|
|
18
20
|
F = TypeVar("F", bound="Callable[..., object]")
|
|
19
21
|
|
|
22
|
+
|
|
23
|
+
def _get_auto_install_setting() -> bool:
|
|
24
|
+
"""Check if auto-install is enabled (default: True)."""
|
|
25
|
+
if os.environ.get("AGENT_CLI_NO_AUTO_INSTALL", "").lower() in ("1", "true", "yes"):
|
|
26
|
+
return False
|
|
27
|
+
return load_config().get("settings", {}).get("auto_install_extras", True)
|
|
28
|
+
|
|
29
|
+
|
|
20
30
|
# Load extras from JSON file
|
|
21
31
|
_EXTRAS_FILE = Path(__file__).parent.parent / "_extras.json"
|
|
22
32
|
EXTRAS: dict[str, tuple[str, list[str]]] = {
|
|
@@ -44,7 +54,7 @@ def check_extra_installed(extra: str) -> bool:
|
|
|
44
54
|
return any(check_extra_installed(e) for e in extra.split("|"))
|
|
45
55
|
|
|
46
56
|
if extra not in EXTRAS:
|
|
47
|
-
return
|
|
57
|
+
return False # Unknown extra, trigger install attempt to surface error
|
|
48
58
|
_, packages = EXTRAS[extra]
|
|
49
59
|
|
|
50
60
|
# All packages must be installed
|
|
@@ -116,44 +126,65 @@ def get_combined_install_hint(extras: list[str]) -> str:
|
|
|
116
126
|
return "\n".join(lines)
|
|
117
127
|
|
|
118
128
|
|
|
119
|
-
def
|
|
120
|
-
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
def _try_auto_install(missing: list[str]) -> bool:
|
|
130
|
+
"""Attempt to auto-install missing extras. Returns True if successful."""
|
|
131
|
+
from agent_cli.install.extras import install_extras_programmatic # noqa: PLC0415
|
|
132
|
+
|
|
133
|
+
# Flatten alternatives (e.g., "piper|kokoro" -> just pick the first one)
|
|
134
|
+
extras_to_install = []
|
|
135
|
+
for extra in missing:
|
|
136
|
+
if "|" in extra:
|
|
137
|
+
# For alternatives, install the first option
|
|
138
|
+
extras_to_install.append(extra.split("|")[0])
|
|
139
|
+
else:
|
|
140
|
+
extras_to_install.append(extra)
|
|
141
|
+
|
|
142
|
+
console.print(
|
|
143
|
+
f"[yellow]Auto-installing missing extras: {', '.join(extras_to_install)}[/]",
|
|
144
|
+
)
|
|
145
|
+
return install_extras_programmatic(extras_to_install)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _check_and_install_extras(extras: tuple[str, ...]) -> list[str]:
|
|
149
|
+
"""Check for missing extras and attempt auto-install. Returns list of still-missing."""
|
|
150
|
+
missing = [e for e in extras if not check_extra_installed(e)]
|
|
151
|
+
if not missing:
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
if not _get_auto_install_setting():
|
|
155
|
+
print_error_message(get_combined_install_hint(missing))
|
|
156
|
+
return missing
|
|
157
|
+
|
|
158
|
+
if not _try_auto_install(missing):
|
|
159
|
+
print_error_message("Auto-install failed.\n" + get_combined_install_hint(missing))
|
|
160
|
+
return missing
|
|
161
|
+
|
|
162
|
+
console.print("[green]Installation complete![/]")
|
|
163
|
+
still_missing = [e for e in extras if not check_extra_installed(e)]
|
|
164
|
+
if still_missing:
|
|
165
|
+
print_error_message(
|
|
166
|
+
"Auto-install completed but some dependencies are still missing.\n"
|
|
167
|
+
+ get_combined_install_hint(still_missing),
|
|
168
|
+
)
|
|
169
|
+
return still_missing
|
|
126
170
|
|
|
127
|
-
Process management flags (--stop, --status, --toggle) skip the dependency
|
|
128
|
-
check since they just manage running processes without using the actual
|
|
129
|
-
dependencies.
|
|
130
171
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
@requires_extras("rag")
|
|
134
|
-
def rag_proxy(...):
|
|
135
|
-
...
|
|
172
|
+
def requires_extras(*extras: str) -> Callable[[F], F]:
|
|
173
|
+
"""Decorator to declare required extras for a command.
|
|
136
174
|
|
|
175
|
+
Auto-installs missing extras by default. Disable via AGENT_CLI_NO_AUTO_INSTALL=1
|
|
176
|
+
or config [settings] auto_install_extras = false.
|
|
137
177
|
"""
|
|
138
178
|
|
|
139
179
|
def decorator(func: F) -> F:
|
|
140
|
-
# Store extras on function for test introspection
|
|
141
180
|
func._required_extras = extras # type: ignore[attr-defined]
|
|
142
181
|
|
|
143
182
|
@functools.wraps(func)
|
|
144
183
|
def wrapper(*args: object, **kwargs: object) -> object:
|
|
145
|
-
|
|
146
|
-
# These don't need the actual dependencies, just manage processes or list info
|
|
147
|
-
if any(kwargs.get(flag) for flag in ("stop", "status", "toggle", "list_devices")):
|
|
148
|
-
return func(*args, **kwargs)
|
|
149
|
-
|
|
150
|
-
missing = [e for e in extras if not check_extra_installed(e)]
|
|
151
|
-
if missing:
|
|
152
|
-
print_error_message(get_combined_install_hint(missing))
|
|
184
|
+
if _check_and_install_extras(extras):
|
|
153
185
|
raise typer.Exit(1)
|
|
154
186
|
return func(*args, **kwargs)
|
|
155
187
|
|
|
156
|
-
# Preserve the extras on wrapper too
|
|
157
188
|
wrapper._required_extras = extras # type: ignore[attr-defined]
|
|
158
189
|
return wrapper # type: ignore[return-value]
|
|
159
190
|
|
agent_cli/install/extras.py
CHANGED
|
@@ -86,6 +86,38 @@ def _install_cmd() -> list[str]:
|
|
|
86
86
|
return cmd
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
def _install_extras_impl(extras: list[str], *, quiet: bool = False) -> bool:
|
|
90
|
+
"""Install extras. Returns True on success, False on failure."""
|
|
91
|
+
if _is_uv_tool_install():
|
|
92
|
+
current_extras = _get_current_uv_tool_extras()
|
|
93
|
+
new_extras = sorted(set(current_extras) | set(extras))
|
|
94
|
+
return _install_via_uv_tool(new_extras)
|
|
95
|
+
|
|
96
|
+
cmd = _install_cmd()
|
|
97
|
+
for extra in extras:
|
|
98
|
+
req_file = _requirements_path(extra)
|
|
99
|
+
if not quiet:
|
|
100
|
+
console.print(f"Installing [cyan]{extra}[/]...")
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
[*cmd, "-r", str(req_file)],
|
|
103
|
+
check=False,
|
|
104
|
+
capture_output=quiet,
|
|
105
|
+
)
|
|
106
|
+
if result.returncode != 0:
|
|
107
|
+
return False
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def install_extras_programmatic(extras: list[str], *, quiet: bool = False) -> bool:
|
|
112
|
+
"""Install extras programmatically (for auto-install feature)."""
|
|
113
|
+
available = _available_extras()
|
|
114
|
+
valid = [e for e in extras if e in available]
|
|
115
|
+
invalid = [e for e in extras if e not in available]
|
|
116
|
+
if invalid:
|
|
117
|
+
console.print(f"[yellow]Unknown extras (skipped): {', '.join(invalid)}[/]")
|
|
118
|
+
return bool(valid) and _install_extras_impl(valid, quiet=quiet)
|
|
119
|
+
|
|
120
|
+
|
|
89
121
|
@app.command("install-extras", rich_help_panel="Installation")
|
|
90
122
|
def install_extras(
|
|
91
123
|
extras: Annotated[list[str] | None, typer.Argument(help="Extras to install")] = None,
|
|
@@ -128,25 +160,11 @@ def install_extras(
|
|
|
128
160
|
print_error_message(f"Unknown extras: {invalid}. Use --list to see available.")
|
|
129
161
|
raise typer.Exit(1)
|
|
130
162
|
|
|
131
|
-
|
|
163
|
+
if not _install_extras_impl(extras):
|
|
164
|
+
print_error_message("Failed to install extras")
|
|
165
|
+
raise typer.Exit(1)
|
|
166
|
+
|
|
132
167
|
if _is_uv_tool_install():
|
|
133
|
-
current_extras = _get_current_uv_tool_extras()
|
|
134
|
-
new_extras = sorted(set(current_extras) | set(extras))
|
|
135
|
-
if not _install_via_uv_tool(new_extras):
|
|
136
|
-
print_error_message("Failed to reinstall via uv tool")
|
|
137
|
-
raise typer.Exit(1)
|
|
138
168
|
console.print("[green]Done! Extras will persist across uv tool upgrade.[/]")
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
# Standard pip/uv pip install for non-tool environments
|
|
142
|
-
cmd = _install_cmd()
|
|
143
|
-
|
|
144
|
-
for extra in extras:
|
|
145
|
-
req_file = _requirements_path(extra)
|
|
146
|
-
console.print(f"Installing [cyan]{extra}[/]...")
|
|
147
|
-
result = subprocess.run([*cmd, "-r", str(req_file)], check=False)
|
|
148
|
-
if result.returncode != 0:
|
|
149
|
-
print_error_message(f"Failed to install '{extra}'")
|
|
150
|
-
raise typer.Exit(1)
|
|
151
|
-
|
|
152
|
-
console.print("[green]Done![/]")
|
|
169
|
+
else:
|
|
170
|
+
console.print("[green]Done![/]")
|
agent_cli/server/cli.py
CHANGED
|
@@ -20,13 +20,13 @@ console = Console()
|
|
|
20
20
|
err_console = Console(stderr=True)
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
22
22
|
|
|
23
|
-
# Check for optional dependencies
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
# Check for optional dependencies at call time (not module load time)
|
|
24
|
+
# This is important because auto-install may install packages after the module is loaded
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _has(package: str) -> bool:
|
|
28
|
+
return find_spec(package) is not None
|
|
29
|
+
|
|
30
30
|
|
|
31
31
|
app = typer.Typer(
|
|
32
32
|
name="server",
|
|
@@ -48,7 +48,7 @@ def server_callback(ctx: typer.Context) -> None:
|
|
|
48
48
|
|
|
49
49
|
def _check_server_deps() -> None:
|
|
50
50
|
"""Check that server dependencies are available."""
|
|
51
|
-
if not
|
|
51
|
+
if not _has("uvicorn") or not _has("fastapi"):
|
|
52
52
|
err_console.print(
|
|
53
53
|
"[bold red]Error:[/bold red] Server dependencies not installed. "
|
|
54
54
|
"Run: [cyan]pip install agent-cli\\[server][/cyan] "
|
|
@@ -62,7 +62,7 @@ def _check_tts_deps(backend: str = "auto") -> None:
|
|
|
62
62
|
_check_server_deps()
|
|
63
63
|
|
|
64
64
|
if backend == "kokoro":
|
|
65
|
-
if not
|
|
65
|
+
if not _has("kokoro"):
|
|
66
66
|
err_console.print(
|
|
67
67
|
"[bold red]Error:[/bold red] Kokoro backend requires kokoro. "
|
|
68
68
|
"Run: [cyan]pip install agent-cli\\[tts-kokoro][/cyan] "
|
|
@@ -72,7 +72,7 @@ def _check_tts_deps(backend: str = "auto") -> None:
|
|
|
72
72
|
return
|
|
73
73
|
|
|
74
74
|
if backend == "piper":
|
|
75
|
-
if not
|
|
75
|
+
if not _has("piper"):
|
|
76
76
|
err_console.print(
|
|
77
77
|
"[bold red]Error:[/bold red] Piper backend requires piper-tts. "
|
|
78
78
|
"Run: [cyan]pip install agent-cli\\[tts][/cyan] "
|
|
@@ -82,7 +82,7 @@ def _check_tts_deps(backend: str = "auto") -> None:
|
|
|
82
82
|
return
|
|
83
83
|
|
|
84
84
|
# For auto, check if either is available
|
|
85
|
-
if not
|
|
85
|
+
if not _has("piper") and not _has("kokoro"):
|
|
86
86
|
err_console.print(
|
|
87
87
|
"[bold red]Error:[/bold red] No TTS backend available. "
|
|
88
88
|
"Run: [cyan]pip install agent-cli\\[tts][/cyan] for Piper "
|
|
@@ -136,7 +136,7 @@ def _check_whisper_deps(backend: str, *, download_only: bool = False) -> None:
|
|
|
136
136
|
"""Check that Whisper dependencies are available."""
|
|
137
137
|
_check_server_deps()
|
|
138
138
|
if download_only:
|
|
139
|
-
if not
|
|
139
|
+
if not _has("faster_whisper"):
|
|
140
140
|
err_console.print(
|
|
141
141
|
"[bold red]Error:[/bold red] faster-whisper is required for --download-only. "
|
|
142
142
|
"Run: [cyan]pip install agent-cli\\[whisper][/cyan] "
|
|
@@ -146,7 +146,7 @@ def _check_whisper_deps(backend: str, *, download_only: bool = False) -> None:
|
|
|
146
146
|
return
|
|
147
147
|
|
|
148
148
|
if backend == "mlx":
|
|
149
|
-
if not
|
|
149
|
+
if not _has("mlx_whisper"):
|
|
150
150
|
err_console.print(
|
|
151
151
|
"[bold red]Error:[/bold red] MLX Whisper backend requires mlx-whisper. "
|
|
152
152
|
"Run: [cyan]pip install mlx-whisper[/cyan]",
|
|
@@ -154,7 +154,7 @@ def _check_whisper_deps(backend: str, *, download_only: bool = False) -> None:
|
|
|
154
154
|
raise typer.Exit(1)
|
|
155
155
|
return
|
|
156
156
|
|
|
157
|
-
if not
|
|
157
|
+
if not _has("faster_whisper"):
|
|
158
158
|
err_console.print(
|
|
159
159
|
"[bold red]Error:[/bold red] Whisper dependencies not installed. "
|
|
160
160
|
"Run: [cyan]pip install agent-cli\\[whisper][/cyan] "
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.68.1
|
|
4
4
|
Summary: A suite of AI-powered command-line tools for text correction, audio transcription, and voice assistance.
|
|
5
5
|
Project-URL: Homepage, https://github.com/basnijholt/agent-cli
|
|
6
6
|
Author-email: Bas Nijholt <bas@nijho.lt>
|
|
@@ -459,7 +459,14 @@ All necessary scripts are bundled with the package, so you can run these command
|
|
|
459
459
|
|
|
460
460
|
#### Installing Optional Extras
|
|
461
461
|
|
|
462
|
-
Some features require additional Python dependencies.
|
|
462
|
+
Some features require additional Python dependencies. By default, **agent-cli will auto-install missing extras** when you run a command that needs them. To disable this, set `AGENT_CLI_NO_AUTO_INSTALL=1` or add to your config file:
|
|
463
|
+
|
|
464
|
+
```toml
|
|
465
|
+
[settings]
|
|
466
|
+
auto_install_extras = false
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
You can also manually install extras with `install-extras`:
|
|
463
470
|
|
|
464
471
|
```bash
|
|
465
472
|
# List available extras
|
|
@@ -40,7 +40,7 @@ agent_cli/core/__init__.py,sha256=c_knH7u9QgjsfMIil9NP4bVizHawLUMYoQWU4H9vMlQ,46
|
|
|
40
40
|
agent_cli/core/audio.py,sha256=43FpYe2Wu_BYK9xJ_55V4xHjHJeFwQ5aM-CQzlTryt8,15168
|
|
41
41
|
agent_cli/core/audio_format.py,sha256=zk3qlYMAlKYPz1enrjihQQspl_C218v1Rbcm7Uktlew,8773
|
|
42
42
|
agent_cli/core/chroma.py,sha256=Vb_ny7SzAIL9SCEGlYgYOqsdG9BgusFGMj0RUzb6W90,2728
|
|
43
|
-
agent_cli/core/deps.py,sha256=
|
|
43
|
+
agent_cli/core/deps.py,sha256=58zyHSJdzIAkJ0gZ7-snrASy8Ro9NePZmElch13r-SQ,6429
|
|
44
44
|
agent_cli/core/openai_proxy.py,sha256=f2kqxk6bAOeN7gOzU0JnyS-RYtXUcK5Gbsa_pBmlCv0,4470
|
|
45
45
|
agent_cli/core/process.py,sha256=Zay6beX4JUbkBHr6xbJxwVBjVFDABmRHQCXVPQH93r8,5916
|
|
46
46
|
agent_cli/core/reranker.py,sha256=Qv5ASGUdseLzI6eQRfNeQY-Lvv4SOgLOu56CpwmszDM,3779
|
|
@@ -91,7 +91,7 @@ agent_cli/dev/terminals/warp.py,sha256=j-Jvz_BbWYC3QfLrvl4CbDh03c9OGRFmuCzjyB2ud
|
|
|
91
91
|
agent_cli/dev/terminals/zellij.py,sha256=GnQnopimb9XH67CZGHjnbVWpVSWhaLCATGJizCT5TkY,2321
|
|
92
92
|
agent_cli/install/__init__.py,sha256=JQPrOrtdNd1Y1NmQDkb3Nmm1qdyn3kPjhQwy9D8ryjI,124
|
|
93
93
|
agent_cli/install/common.py,sha256=WvnmcjnFTW0d1HZrKVGzj5Tg3q8Txk_ZOdc4a1MBFWI,3121
|
|
94
|
-
agent_cli/install/extras.py,sha256=
|
|
94
|
+
agent_cli/install/extras.py,sha256=BPIlMdDeUo-tsKb_9Ttx3799YsIV_yup79ESFNpoJ1o,5731
|
|
95
95
|
agent_cli/install/hotkeys.py,sha256=bwGoPeEKK6VI-IrKU8Q0RLMW9smkDNU7CdqD3Nbsd-w,1626
|
|
96
96
|
agent_cli/install/services.py,sha256=2s_7ThxaElKCuomBYTn4Z36TF_o_arNeyJ4f8Wh4jEI,2912
|
|
97
97
|
agent_cli/memory/__init__.py,sha256=8XNpVzP-qjF8o49A_eXsH_Rbp_FmxTIcknnvxq7vHms,162
|
|
@@ -156,7 +156,7 @@ agent_cli/scripts/nvidia-asr-server/server.py,sha256=kPNQIVF3exblvqMtIVk38Y6sZy2
|
|
|
156
156
|
agent_cli/scripts/nvidia-asr-server/shell.nix,sha256=IT20j5YNj_wc7MdXi7ndogGodDNSGwyq8G0bNoZEpmg,1003
|
|
157
157
|
agent_cli/scripts/nvidia-asr-server/uv.lock,sha256=5WWaqWOuV_moMPC-LIZK-A-Y5oaHr1tUn_vbR-IupzY,728608
|
|
158
158
|
agent_cli/server/__init__.py,sha256=NZuJHlLHck9KWrepNZHrJONptYCQI9P-uTqknSFI5Ds,71
|
|
159
|
-
agent_cli/server/cli.py,sha256=
|
|
159
|
+
agent_cli/server/cli.py,sha256=QA7MmASRN9Z5lDIQ25IBmxnFMsmW9AzmFsVqnUuafU0,22961
|
|
160
160
|
agent_cli/server/common.py,sha256=fD2AZdM716TXcz1T4ZDPpPaKynVOEjbVC1LDDloDmDo,6463
|
|
161
161
|
agent_cli/server/model_manager.py,sha256=93l_eeZeqnPALyDIK24or61tvded9TbM8tnde0okVjY,9225
|
|
162
162
|
agent_cli/server/model_registry.py,sha256=KrRV1XxbFYuXu5rJlHFh6PTl_2BKiWnWsaNrf-0c6wQ,6988
|
|
@@ -188,8 +188,8 @@ agent_cli/services/asr.py,sha256=aRaCLVCygsJ15qyQEPECOZsdSrnlLPbyY4RwAqY0qIw,172
|
|
|
188
188
|
agent_cli/services/llm.py,sha256=Kwdo6pbMYI9oykF-RBe1iaL3KsYrNWTLdRSioewmsGQ,7199
|
|
189
189
|
agent_cli/services/tts.py,sha256=NX5Qnq7ddLI3mwm3nzhbR3zB1Os4Ip4sSVSjDZDTBcI,14855
|
|
190
190
|
agent_cli/services/wake_word.py,sha256=JFJ1SA22H4yko9DXiQ1t7fcoxeALLAe3iBrLs0z8rX4,4664
|
|
191
|
-
agent_cli-0.
|
|
192
|
-
agent_cli-0.
|
|
193
|
-
agent_cli-0.
|
|
194
|
-
agent_cli-0.
|
|
195
|
-
agent_cli-0.
|
|
191
|
+
agent_cli-0.68.1.dist-info/METADATA,sha256=jkg7fVafbi-nnKMpUvs7Zink8exu_LWucHpM46YtNqA,156032
|
|
192
|
+
agent_cli-0.68.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
193
|
+
agent_cli-0.68.1.dist-info/entry_points.txt,sha256=FUv-fB2atLsPUk_RT4zqnZl1coz4_XHFwRALOKOF38s,97
|
|
194
|
+
agent_cli-0.68.1.dist-info/licenses/LICENSE,sha256=majJU6S9kC8R8bW39NVBHyv32Dq50FL6TDxECG2WVts,1068
|
|
195
|
+
agent_cli-0.68.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|