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 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.core.utils import print_error_message
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 True # Unknown extra, assume OK
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 requires_extras(*extras: str) -> Callable[[F], F]:
120
- """Decorator to declare required extras for a command.
121
-
122
- When a required extra is missing, the decorator prints a helpful error
123
- message and exits with code 1.
124
-
125
- The decorator stores the required extras on the function for test validation.
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
- Example:
132
- @app.command("rag-proxy")
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
- # Skip dependency check for process management and info operations
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
 
@@ -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
- # If running from uv tool install, reinstall with extras to persist them
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
- return
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
- HAS_UVICORN = find_spec("uvicorn") is not None
25
- HAS_FASTAPI = find_spec("fastapi") is not None
26
- HAS_FASTER_WHISPER = find_spec("faster_whisper") is not None
27
- HAS_MLX_WHISPER = find_spec("mlx_whisper") is not None
28
- HAS_PIPER = find_spec("piper") is not None
29
- HAS_KOKORO = find_spec("kokoro") is not None
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 HAS_UVICORN or not HAS_FASTAPI:
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 HAS_KOKORO:
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 HAS_PIPER:
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 HAS_PIPER and not HAS_KOKORO:
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 HAS_FASTER_WHISPER:
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 HAS_MLX_WHISPER:
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 HAS_FASTER_WHISPER:
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.67.2
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. Use `install-extras` to install them with pinned versions:
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=roYg8KHqFAxuyRqVauKssr6gHNqSZ2_kJYD50O95bnM,5318
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=ZknvNHXQDlg4K9Wn1Bs7tYQxx5T5wyQKi_Kea-aB1gU,5161
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=ZpsDmf9Z18jk2N1bdJILN2urPXb8iLYzrlHF9sCxmuY,23021
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.67.2.dist-info/METADATA,sha256=z4zcDhqiGt7d92suAsXn_-vLpzmYJEInek6WC5t0ar4,155800
192
- agent_cli-0.67.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
193
- agent_cli-0.67.2.dist-info/entry_points.txt,sha256=FUv-fB2atLsPUk_RT4zqnZl1coz4_XHFwRALOKOF38s,97
194
- agent_cli-0.67.2.dist-info/licenses/LICENSE,sha256=majJU6S9kC8R8bW39NVBHyv32Dq50FL6TDxECG2WVts,1068
195
- agent_cli-0.67.2.dist-info/RECORD,,
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,,