agent-cli 0.66.2__py3-none-any.whl → 0.67.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.
Files changed (39) hide show
  1. agent_cli/_extras.json +13 -0
  2. agent_cli/_requirements/audio.txt +60 -0
  3. agent_cli/_requirements/{whisper.txt → faster-whisper.txt} +4 -112
  4. agent_cli/_requirements/{tts-kokoro.txt → kokoro.txt} +4 -107
  5. agent_cli/_requirements/llm.txt +177 -0
  6. agent_cli/_requirements/memory.txt +6 -97
  7. agent_cli/_requirements/{whisper-mlx.txt → mlx-whisper.txt} +61 -124
  8. agent_cli/_requirements/{tts.txt → piper.txt} +4 -121
  9. agent_cli/_requirements/rag.txt +9 -96
  10. agent_cli/_requirements/server.txt +3 -120
  11. agent_cli/_requirements/speed.txt +5 -138
  12. agent_cli/_requirements/vad.txt +4 -137
  13. agent_cli/agents/assistant.py +2 -0
  14. agent_cli/agents/autocorrect.py +2 -0
  15. agent_cli/agents/chat.py +2 -0
  16. agent_cli/agents/memory/add.py +2 -0
  17. agent_cli/agents/memory/proxy.py +2 -0
  18. agent_cli/agents/rag_proxy.py +2 -0
  19. agent_cli/agents/speak.py +2 -0
  20. agent_cli/agents/transcribe.py +2 -0
  21. agent_cli/agents/transcribe_daemon.py +2 -0
  22. agent_cli/agents/voice_edit.py +2 -0
  23. agent_cli/core/deps.py +130 -14
  24. agent_cli/dev/skill/SKILL.md +2 -2
  25. agent_cli/docs_gen.py +0 -42
  26. agent_cli/install/extras.py +6 -14
  27. agent_cli/memory/__init__.py +1 -18
  28. agent_cli/rag/__init__.py +0 -19
  29. agent_cli/scripts/sync_extras.py +138 -0
  30. agent_cli/server/cli.py +4 -0
  31. agent_cli/services/_wyoming_utils.py +4 -2
  32. agent_cli/services/asr.py +13 -3
  33. agent_cli/services/tts.py +5 -2
  34. agent_cli/services/wake_word.py +6 -3
  35. {agent_cli-0.66.2.dist-info → agent_cli-0.67.0.dist-info}/METADATA +33 -29
  36. {agent_cli-0.66.2.dist-info → agent_cli-0.67.0.dist-info}/RECORD +39 -35
  37. {agent_cli-0.66.2.dist-info → agent_cli-0.67.0.dist-info}/WHEEL +0 -0
  38. {agent_cli-0.66.2.dist-info → agent_cli-0.67.0.dist-info}/entry_points.txt +0 -0
  39. {agent_cli-0.66.2.dist-info → agent_cli-0.67.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,100 +1,39 @@
1
1
  # This file was autogenerated by uv via the following command:
2
2
  # uv export --extra vad --no-dev --no-emit-project --no-hashes
3
- annotated-types==0.7.0
4
- # via pydantic
5
3
  anyio==4.12.1
6
- # via
7
- # google-genai
8
- # httpx
9
- # openai
10
- brotli==1.2.0 ; platform_python_implementation == 'CPython'
11
- # via httpx
12
- brotlicffi==1.2.0.0 ; platform_python_implementation != 'CPython'
13
4
  # via httpx
14
5
  certifi==2026.1.4
15
6
  # via
16
7
  # httpcore
17
8
  # httpx
18
- # requests
19
- cffi==2.0.0
20
- # via
21
- # brotlicffi
22
- # sounddevice
23
- charset-normalizer==3.4.4
24
- # via requests
25
9
  click==8.3.1
26
- # via
27
- # ddgs
28
- # typer
29
- colorama==0.4.6
30
- # via
31
- # click
32
- # griffe
33
- # tqdm
10
+ # via typer
11
+ colorama==0.4.6 ; sys_platform == 'win32'
12
+ # via click
34
13
  coloredlogs==15.0.1
35
14
  # via onnxruntime
36
- ddgs==9.10.0
37
- # via pydantic-ai-slim
38
- distro==1.9.0
39
- # via
40
- # google-genai
41
- # openai
42
15
  dotenv==0.9.9
43
16
  # via agent-cli
44
- fake-useragent==2.2.0
45
- # via ddgs
46
17
  filelock==3.20.3
47
18
  # via torch
48
19
  flatbuffers==25.12.19
49
20
  # via onnxruntime
50
21
  fsspec==2026.1.0
51
22
  # via torch
52
- genai-prices==0.0.51
53
- # via pydantic-ai-slim
54
- google-auth==2.47.0
55
- # via
56
- # google-genai
57
- # pydantic-ai-slim
58
- google-genai==1.58.0
59
- # via agent-cli
60
- griffe==1.15.0
61
- # via pydantic-ai-slim
62
23
  h11==0.16.0
63
24
  # via httpcore
64
- h2==4.3.0
65
- # via httpx
66
- hpack==4.1.0
67
- # via h2
68
25
  httpcore==1.0.9
69
26
  # via httpx
70
27
  httpx==0.28.1
71
- # via
72
- # agent-cli
73
- # ddgs
74
- # genai-prices
75
- # google-genai
76
- # openai
77
- # pydantic-ai-slim
78
- # pydantic-graph
28
+ # via agent-cli
79
29
  humanfriendly==10.0
80
30
  # via coloredlogs
81
- hyperframe==6.1.0
82
- # via h2
83
31
  idna==3.11
84
32
  # via
85
33
  # anyio
86
34
  # httpx
87
- # requests
88
- importlib-metadata==8.7.1
89
- # via opentelemetry-api
90
35
  jinja2==3.1.6
91
36
  # via torch
92
- jiter==0.12.0
93
- # via openai
94
- logfire-api==4.18.0
95
- # via pydantic-graph
96
- lxml==6.0.2
97
- # via ddgs
98
37
  markdown-it-py==4.0.0
99
38
  # via rich
100
39
  markupsafe==3.0.3
@@ -150,43 +89,14 @@ nvidia-nvtx-cu12==12.8.90 ; platform_machine == 'x86_64' and sys_platform == 'li
150
89
  # via torch
151
90
  onnxruntime==1.20.1
152
91
  # via silero-vad
153
- openai==2.15.0
154
- # via
155
- # agent-cli
156
- # pydantic-ai-slim
157
- opentelemetry-api==1.39.1
158
- # via pydantic-ai-slim
159
92
  packaging==25.0
160
93
  # via
161
94
  # onnxruntime
162
95
  # silero-vad
163
- primp==0.15.0
164
- # via ddgs
165
96
  protobuf==6.33.4
166
97
  # via onnxruntime
167
98
  psutil==7.2.1 ; sys_platform == 'win32'
168
99
  # via agent-cli
169
- pyasn1==0.6.1
170
- # via
171
- # pyasn1-modules
172
- # rsa
173
- pyasn1-modules==0.4.2
174
- # via google-auth
175
- pycparser==2.23 ; implementation_name != 'PyPy'
176
- # via cffi
177
- pydantic==2.12.5
178
- # via
179
- # genai-prices
180
- # google-genai
181
- # openai
182
- # pydantic-ai-slim
183
- # pydantic-graph
184
- pydantic-ai-slim==1.42.0
185
- # via agent-cli
186
- pydantic-core==2.41.5
187
- # via pydantic
188
- pydantic-graph==1.42.0
189
- # via pydantic-ai-slim
190
100
  pygments==2.19.2
191
101
  # via rich
192
102
  pyperclip==1.11.0
@@ -195,20 +105,10 @@ pyreadline3==3.5.4 ; sys_platform == 'win32'
195
105
  # via humanfriendly
196
106
  python-dotenv==1.2.1
197
107
  # via dotenv
198
- regex==2026.1.15
199
- # via tiktoken
200
- requests==2.32.5
201
- # via
202
- # google-auth
203
- # google-genai
204
- # pydantic-ai-slim
205
- # tiktoken
206
108
  rich==14.2.0
207
109
  # via
208
110
  # agent-cli
209
111
  # typer
210
- rsa==4.9.1
211
- # via google-auth
212
112
  setproctitle==1.3.7
213
113
  # via agent-cli
214
114
  setuptools==80.9.0 ; python_full_version >= '3.12'
@@ -217,30 +117,16 @@ shellingham==1.5.4
217
117
  # via typer
218
118
  silero-vad==6.2.0
219
119
  # via agent-cli
220
- sniffio==1.3.1
221
- # via
222
- # google-genai
223
- # openai
224
- socksio==1.0.0
225
- # via httpx
226
- sounddevice==0.5.3
227
- # via agent-cli
228
120
  sympy==1.14.0
229
121
  # via
230
122
  # onnxruntime
231
123
  # torch
232
- tenacity==9.1.2
233
- # via google-genai
234
- tiktoken==0.12.0
235
- # via pydantic-ai-slim
236
124
  torch==2.9.1
237
125
  # via
238
126
  # silero-vad
239
127
  # torchaudio
240
128
  torchaudio==2.9.1
241
129
  # via silero-vad
242
- tqdm==4.67.1
243
- # via openai
244
130
  triton==3.5.1 ; platform_machine == 'x86_64' and sys_platform == 'linux'
245
131
  # via torch
246
132
  typer==0.21.1
@@ -248,24 +134,5 @@ typer==0.21.1
248
134
  typing-extensions==4.15.0
249
135
  # via
250
136
  # anyio
251
- # google-genai
252
- # openai
253
- # opentelemetry-api
254
- # pydantic
255
- # pydantic-core
256
137
  # torch
257
138
  # typer
258
- # typing-inspection
259
- typing-inspection==0.4.2
260
- # via
261
- # pydantic
262
- # pydantic-ai-slim
263
- # pydantic-graph
264
- urllib3==2.3.0
265
- # via requests
266
- websockets==15.0.1
267
- # via google-genai
268
- wyoming==1.8.0
269
- # via agent-cli
270
- zipp==3.23.0
271
- # via importlib-metadata
@@ -41,6 +41,7 @@ from agent_cli.agents._voice_agent_common import (
41
41
  from agent_cli.cli import app
42
42
  from agent_cli.core import audio, process
43
43
  from agent_cli.core.audio import setup_devices
44
+ from agent_cli.core.deps import requires_extras
44
45
  from agent_cli.core.utils import (
45
46
  InteractiveStopEvent,
46
47
  maybe_live,
@@ -254,6 +255,7 @@ async def _async_main(
254
255
 
255
256
 
256
257
  @app.command("assistant", rich_help_panel="Voice Commands")
258
+ @requires_extras("audio", "llm")
257
259
  def assistant(
258
260
  *,
259
261
  # --- Provider Selection ---
@@ -14,6 +14,7 @@ import typer
14
14
 
15
15
  from agent_cli import config, opts
16
16
  from agent_cli.cli import app
17
+ from agent_cli.core.deps import requires_extras
17
18
  from agent_cli.core.utils import (
18
19
  console,
19
20
  create_status,
@@ -209,6 +210,7 @@ async def _async_autocorrect(
209
210
 
210
211
 
211
212
  @app.command("autocorrect", rich_help_panel="Text Commands")
213
+ @requires_extras("llm")
212
214
  def autocorrect(
213
215
  *,
214
216
  text: str | None = typer.Argument(
agent_cli/agents/chat.py CHANGED
@@ -29,6 +29,7 @@ from agent_cli._tools import tools
29
29
  from agent_cli.cli import app
30
30
  from agent_cli.core import process
31
31
  from agent_cli.core.audio import setup_devices
32
+ from agent_cli.core.deps import requires_extras
32
33
  from agent_cli.core.utils import (
33
34
  InteractiveStopEvent,
34
35
  console,
@@ -374,6 +375,7 @@ async def _async_main(
374
375
 
375
376
 
376
377
  @app.command("chat", rich_help_panel="Voice Commands")
378
+ @requires_extras("audio", "llm")
377
379
  def chat(
378
380
  *,
379
381
  # --- Provider Selection ---
@@ -13,6 +13,7 @@ import typer
13
13
 
14
14
  from agent_cli import opts
15
15
  from agent_cli.agents.memory import memory_app
16
+ from agent_cli.core.deps import requires_extras
16
17
  from agent_cli.core.utils import console, print_command_line_args
17
18
 
18
19
  if TYPE_CHECKING:
@@ -110,6 +111,7 @@ def _write_memories(
110
111
 
111
112
 
112
113
  @memory_app.command("add")
114
+ @requires_extras("memory")
113
115
  def add(
114
116
  memories: list[str] = typer.Argument( # noqa: B008
115
117
  None,
@@ -10,10 +10,12 @@ from rich.logging import RichHandler
10
10
 
11
11
  from agent_cli import constants, opts
12
12
  from agent_cli.agents.memory import memory_app
13
+ from agent_cli.core.deps import requires_extras
13
14
  from agent_cli.core.utils import console, print_command_line_args, print_error_message
14
15
 
15
16
 
16
17
  @memory_app.command("proxy")
18
+ @requires_extras("memory")
17
19
  def proxy(
18
20
  memory_path: Path = typer.Option( # noqa: B008
19
21
  "./memory_db",
@@ -10,6 +10,7 @@ from rich.logging import RichHandler
10
10
 
11
11
  from agent_cli import constants, opts
12
12
  from agent_cli.cli import app
13
+ from agent_cli.core.deps import requires_extras
13
14
  from agent_cli.core.utils import (
14
15
  console,
15
16
  print_command_line_args,
@@ -18,6 +19,7 @@ from agent_cli.core.utils import (
18
19
 
19
20
 
20
21
  @app.command("rag-proxy", rich_help_panel="Servers")
22
+ @requires_extras("rag")
21
23
  def rag_proxy(
22
24
  docs_folder: Path = typer.Option( # noqa: B008
23
25
  "./rag_docs",
agent_cli/agents/speak.py CHANGED
@@ -14,6 +14,7 @@ from agent_cli import config, opts
14
14
  from agent_cli.cli import app
15
15
  from agent_cli.core import process
16
16
  from agent_cli.core.audio import setup_devices
17
+ from agent_cli.core.deps import requires_extras
17
18
  from agent_cli.core.utils import (
18
19
  enable_json_mode,
19
20
  get_clipboard_text,
@@ -80,6 +81,7 @@ async def _async_main(
80
81
 
81
82
 
82
83
  @app.command("speak", rich_help_panel="Text Commands")
84
+ @requires_extras("audio")
83
85
  def speak(
84
86
  *,
85
87
  text: str | None = typer.Argument(
@@ -19,6 +19,7 @@ from agent_cli import config, opts
19
19
  from agent_cli.cli import app
20
20
  from agent_cli.core import process
21
21
  from agent_cli.core.audio import setup_devices
22
+ from agent_cli.core.deps import requires_extras
22
23
  from agent_cli.core.utils import (
23
24
  enable_json_mode,
24
25
  format_short_timedelta,
@@ -461,6 +462,7 @@ async def _async_main( # noqa: PLR0912, PLR0915, C901
461
462
 
462
463
 
463
464
  @app.command("transcribe", rich_help_panel="Voice Commands")
465
+ @requires_extras("audio", "llm")
464
466
  def transcribe( # noqa: PLR0912
465
467
  *,
466
468
  extra_instructions: str | None = typer.Option(
@@ -25,6 +25,7 @@ from agent_cli.cli import app
25
25
  from agent_cli.core import process
26
26
  from agent_cli.core.audio import open_audio_stream, setup_devices, setup_input_stream
27
27
  from agent_cli.core.audio_format import check_ffmpeg_available, save_audio_as_mp3
28
+ from agent_cli.core.deps import requires_extras
28
29
  from agent_cli.core.utils import (
29
30
  console,
30
31
  print_command_line_args,
@@ -287,6 +288,7 @@ async def _daemon_loop(cfg: DaemonConfig) -> None: # noqa: PLR0912, PLR0915
287
288
 
288
289
 
289
290
  @app.command("transcribe-daemon", rich_help_panel="Voice Commands")
291
+ @requires_extras("audio", "vad", "llm")
290
292
  def transcribe_daemon( # noqa: PLR0912
291
293
  *,
292
294
  # Daemon-specific options
@@ -47,6 +47,7 @@ from agent_cli.agents._voice_agent_common import (
47
47
  from agent_cli.cli import app
48
48
  from agent_cli.core import process
49
49
  from agent_cli.core.audio import setup_devices
50
+ from agent_cli.core.deps import requires_extras
50
51
  from agent_cli.core.utils import (
51
52
  enable_json_mode,
52
53
  get_clipboard_text,
@@ -173,6 +174,7 @@ async def _async_main(
173
174
 
174
175
 
175
176
  @app.command("voice-edit", rich_help_panel="Voice Commands")
177
+ @requires_extras("audio", "llm")
176
178
  def voice_edit(
177
179
  *,
178
180
  # --- Provider Selection ---
agent_cli/core/deps.py CHANGED
@@ -2,22 +2,138 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import functools
6
+ import json
5
7
  from importlib.util import find_spec
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING, TypeVar
6
10
 
11
+ import typer
7
12
 
8
- def ensure_optional_dependencies(
9
- required: dict[str, str],
10
- *,
11
- extra_name: str,
12
- install_hint: str | None = None,
13
- ) -> None:
14
- """Ensure optional dependencies are present, otherwise raise ImportError."""
15
- missing = [
16
- pkg_name for module_name, pkg_name in required.items() if find_spec(module_name) is None
13
+ from agent_cli.core.utils import print_error_message
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Callable
17
+
18
+ F = TypeVar("F", bound="Callable[..., object]")
19
+
20
+ # Load extras from JSON file
21
+ _EXTRAS_FILE = Path(__file__).parent.parent / "_extras.json"
22
+ EXTRAS: dict[str, tuple[str, list[str]]] = {
23
+ k: (v[0], v[1]) for k, v in json.loads(_EXTRAS_FILE.read_text()).items()
24
+ }
25
+
26
+
27
+ def _check_package_installed(pkg: str) -> bool:
28
+ """Check if a single package is installed."""
29
+ top_module = pkg.split(".")[0]
30
+ try:
31
+ return find_spec(top_module) is not None
32
+ except (ValueError, ModuleNotFoundError):
33
+ return False
34
+
35
+
36
+ def check_extra_installed(extra: str) -> bool:
37
+ """Check if packages for an extra are installed using find_spec (no actual import).
38
+
39
+ Supports `|` syntax for alternatives: "piper|kokoro" means ANY of these extras.
40
+ For regular extras, ALL packages must be installed.
41
+ """
42
+ # Handle "extra1|extra2" syntax - any of these extras is sufficient
43
+ if "|" in extra:
44
+ return any(check_extra_installed(e) for e in extra.split("|"))
45
+
46
+ if extra not in EXTRAS:
47
+ return True # Unknown extra, assume OK
48
+ _, packages = EXTRAS[extra]
49
+
50
+ # All packages must be installed
51
+ return all(_check_package_installed(pkg) for pkg in packages)
52
+
53
+
54
+ def get_install_hint(extra: str) -> str:
55
+ """Get install command hint for an extra.
56
+
57
+ Supports `|` syntax for alternatives: "piper|kokoro" shows both options.
58
+ """
59
+ # Handle "extra1|extra2" syntax - show all options
60
+ if "|" in extra:
61
+ alternatives = extra.split("|")
62
+ options = []
63
+ for alt in alternatives:
64
+ desc, _ = EXTRAS.get(alt, ("", []))
65
+ options.append((alt, desc))
66
+
67
+ lines = ["This command requires one of:"]
68
+ for alt, desc in options:
69
+ if desc:
70
+ lines.append(f" - '{alt}' ({desc})")
71
+ else:
72
+ lines.append(f" - '{alt}'")
73
+ lines.append("")
74
+ lines.append("Install one with:")
75
+ for alt, _ in options:
76
+ lines.append(f' uv tool install "agent-cli[{alt}]" -p 3.13')
77
+ lines.append(" # or")
78
+ for alt, _ in options:
79
+ lines.append(f" agent-cli install-extras {alt}")
80
+ return "\n".join(lines)
81
+
82
+ desc, _ = EXTRAS.get(extra, ("", []))
83
+ lines = [
84
+ f"This command requires the '{extra}' extra",
17
85
  ]
18
- if not missing:
19
- return
86
+ if desc:
87
+ lines[0] += f" ({desc})"
88
+ lines[0] += "."
89
+ lines.append("")
90
+ lines.append("Install with:")
91
+ lines.append(f' uv tool install "agent-cli[{extra}]" -p 3.13')
92
+ lines.append(" # or")
93
+ lines.append(f" agent-cli install-extras {extra}")
94
+ return "\n".join(lines)
95
+
96
+
97
+ def requires_extras(*extras: str) -> Callable[[F], F]:
98
+ """Decorator to declare required extras for a command.
99
+
100
+ When a required extra is missing, the decorator prints a helpful error
101
+ message and exits with code 1.
102
+
103
+ The decorator stores the required extras on the function for test validation.
104
+
105
+ Process management flags (--stop, --status, --toggle) skip the dependency
106
+ check since they just manage running processes without using the actual
107
+ dependencies.
108
+
109
+ Example:
110
+ @app.command("rag-proxy")
111
+ @requires_extras("rag")
112
+ def rag_proxy(...):
113
+ ...
114
+
115
+ """
116
+
117
+ def decorator(func: F) -> F:
118
+ # Store extras on function for test introspection
119
+ func._required_extras = extras # type: ignore[attr-defined]
120
+
121
+ @functools.wraps(func)
122
+ def wrapper(*args: object, **kwargs: object) -> object:
123
+ # Skip dependency check for process management and info operations
124
+ # These don't need the actual dependencies, just manage processes or list info
125
+ if any(kwargs.get(flag) for flag in ("stop", "status", "toggle", "list_devices")):
126
+ return func(*args, **kwargs)
127
+
128
+ missing = [e for e in extras if not check_extra_installed(e)]
129
+ if missing:
130
+ for extra in missing:
131
+ print_error_message(get_install_hint(extra))
132
+ raise typer.Exit(1)
133
+ return func(*args, **kwargs)
134
+
135
+ # Preserve the extras on wrapper too
136
+ wrapper._required_extras = extras # type: ignore[attr-defined]
137
+ return wrapper # type: ignore[return-value]
20
138
 
21
- hint = install_hint or f"`pip install agent-cli[{extra_name}]`"
22
- msg = f"Missing required dependencies for {extra_name}: {', '.join(missing)}. Please install with {hint}."
23
- raise ImportError(msg)
139
+ return decorator
@@ -13,10 +13,10 @@ If `agent-cli` is not available, install it first:
13
13
 
14
14
  ```bash
15
15
  # Install globally
16
- uv tool install agent-cli
16
+ uv tool install agent-cli -p 3.13
17
17
 
18
18
  # Or run directly without installing
19
- uvx agent-cli dev new <branch-name> --agent --prompt "..."
19
+ uvx --python 3.13 agent-cli dev new <branch-name> --agent --prompt "..."
20
20
  ```
21
21
 
22
22
  ## When to spawn parallel agents
agent_cli/docs_gen.py CHANGED
@@ -16,7 +16,6 @@ Example usage in Markdown files:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- from pathlib import Path
20
19
  from typing import Any, get_origin
21
20
 
22
21
  import click
@@ -369,47 +368,6 @@ def config_example(command_path: str | None = None) -> str:
369
368
  return "\n".join(lines)
370
369
 
371
370
 
372
- def readme_section(section_name: str) -> str:
373
- """Extract a section from README.md for reuse in other docs.
374
-
375
- Sections are marked with HTML comments like:
376
- <!-- SECTION:section_name:START -->
377
- Content here...
378
- <!-- SECTION:section_name:END -->
379
-
380
- Args:
381
- section_name: The name of the section to extract (e.g., "why-i-built-this")
382
-
383
- Returns:
384
- The content between the section markers (without the markers themselves)
385
-
386
- """
387
- # Find the README.md relative to this module
388
- readme_path = Path(__file__).parent.parent / "README.md"
389
- if not readme_path.exists():
390
- return f"*Could not find README.md at {readme_path}*"
391
-
392
- content = readme_path.read_text()
393
-
394
- # Look for section markers
395
- start_marker = f"<!-- SECTION:{section_name}:START -->"
396
- end_marker = f"<!-- SECTION:{section_name}:END -->"
397
-
398
- start_idx = content.find(start_marker)
399
- if start_idx == -1:
400
- return f"*Section '{section_name}' not found in README.md*"
401
-
402
- end_idx = content.find(end_marker, start_idx)
403
- if end_idx == -1:
404
- return f"*End marker for section '{section_name}' not found in README.md*"
405
-
406
- # Extract content between markers (excluding the markers themselves)
407
- section_content = content[start_idx + len(start_marker) : end_idx]
408
-
409
- # Strip leading/trailing whitespace but preserve internal formatting
410
- return section_content.strip()
411
-
412
-
413
371
  def all_options_for_docs(command_path: str) -> str:
414
372
  """Generate complete options documentation for a command page.
415
373
 
@@ -12,20 +12,11 @@ from typing import Annotated
12
12
  import typer
13
13
 
14
14
  from agent_cli.cli import app
15
+ from agent_cli.core.deps import EXTRAS as _EXTRAS_META
15
16
  from agent_cli.core.utils import console, print_error_message
16
17
 
17
- # Extra name -> description mapping (used for docs generation)
18
- EXTRAS: dict[str, str] = {
19
- "rag": "RAG proxy (ChromaDB, embeddings)",
20
- "memory": "Long-term memory proxy",
21
- "vad": "Voice Activity Detection (silero-vad)",
22
- "whisper": "Local Whisper ASR (faster-whisper)",
23
- "whisper-mlx": "MLX Whisper for Apple Silicon",
24
- "tts": "Local Piper TTS",
25
- "tts-kokoro": "Kokoro neural TTS",
26
- "server": "FastAPI server components",
27
- "speed": "Audio speed adjustment (audiostretchy)",
28
- }
18
+ # Extract descriptions from the centralized EXTRAS metadata
19
+ EXTRAS: dict[str, str] = {name: desc for name, (desc, _) in _EXTRAS_META.items()}
29
20
 
30
21
 
31
22
  def _requirements_dir() -> Path:
@@ -72,8 +63,9 @@ def _install_via_uv_tool(extras: list[str]) -> bool:
72
63
  """Reinstall agent-cli via uv tool with the specified extras."""
73
64
  extras_str = ",".join(extras)
74
65
  package_spec = f"agent-cli[{extras_str}]"
75
- console.print(f"Reinstalling via uv tool: [cyan]{package_spec}[/]")
76
- cmd = ["uv", "tool", "install", package_spec, "--force"]
66
+ python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
67
+ console.print(f"Reinstalling via uv tool: [cyan]{package_spec}[/] (Python {python_version})")
68
+ cmd = ["uv", "tool", "install", package_spec, "--force", "--python", python_version]
77
69
  result = subprocess.run(cmd, check=False)
78
70
  return result.returncode == 0
79
71
 
@@ -2,23 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from agent_cli.core.deps import ensure_optional_dependencies
6
-
7
- _REQUIRED_DEPS = {
8
- "chromadb": "chromadb",
9
- "fastapi": "fastapi",
10
- "uvicorn": "uvicorn",
11
- "onnxruntime": "onnxruntime",
12
- "huggingface_hub": "huggingface-hub",
13
- "transformers": "transformers",
14
- }
15
-
16
- ensure_optional_dependencies(
17
- _REQUIRED_DEPS,
18
- extra_name="memory",
19
- install_hint="`pip install agent-cli[memory]` or `uv sync --extra memory`",
20
- )
21
-
22
- from agent_cli.memory.client import MemoryClient # noqa: E402
5
+ from agent_cli.memory.client import MemoryClient
23
6
 
24
7
  __all__ = ["MemoryClient"]
agent_cli/rag/__init__.py CHANGED
@@ -1,22 +1,3 @@
1
1
  """RAG module."""
2
2
 
3
3
  from __future__ import annotations
4
-
5
- from agent_cli.core.deps import ensure_optional_dependencies
6
-
7
- _REQUIRED_DEPS = {
8
- "chromadb": "chromadb",
9
- "watchfiles": "watchfiles",
10
- "markitdown": "markitdown",
11
- "fastapi": "fastapi",
12
- "uvicorn": "uvicorn",
13
- "onnxruntime": "onnxruntime",
14
- "huggingface_hub": "huggingface-hub",
15
- "transformers": "transformers",
16
- }
17
-
18
- ensure_optional_dependencies(
19
- _REQUIRED_DEPS,
20
- extra_name="rag",
21
- install_hint="`pip install agent-cli[rag]` or `uv sync --extra rag`",
22
- )