agent-cli 0.67.2__py3-none-any.whl → 0.68.0__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![/]")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-cli
3
- Version: 0.67.2
3
+ Version: 0.68.0
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
@@ -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.0.dist-info/METADATA,sha256=0eWIbitl8b9hqUg_Fh8fjJNd2UJ91_qZDYdynfh60GU,156032
192
+ agent_cli-0.68.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
193
+ agent_cli-0.68.0.dist-info/entry_points.txt,sha256=FUv-fB2atLsPUk_RT4zqnZl1coz4_XHFwRALOKOF38s,97
194
+ agent_cli-0.68.0.dist-info/licenses/LICENSE,sha256=majJU6S9kC8R8bW39NVBHyv32Dq50FL6TDxECG2WVts,1068
195
+ agent_cli-0.68.0.dist-info/RECORD,,