agent-cli 0.67.2__tar.gz → 0.68.1__tar.gz
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-0.67.2 → agent_cli-0.68.1}/PKG-INFO +9 -2
- {agent_cli-0.67.2 → agent_cli-0.68.1}/README.md +8 -1
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/deps.py +58 -27
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/install/extras.py +38 -20
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/cli.py +14 -14
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/architecture/index.md +27 -10
- agent_cli-0.68.1/tests/test_requires_extras.py +285 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/zensical.toml +1 -0
- agent_cli-0.67.2/tests/test_requires_extras.py +0 -63
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.claude/skills/agent-cli-dev/SKILL.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.claude/skills/agent-cli-dev/examples.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.claude-plugin/README.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.claude-plugin/marketplace.json +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.claude-plugin/plugin.json +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.claude-plugin/skills/agent-cli-dev/SKILL.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.claude-plugin/skills/agent-cli-dev/examples.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.cursorrules +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.env.example +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/logo.svg +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/release-drafter.yml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/renovate.json +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/scripts/check_extras_sync.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/scripts/sync_requirements.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/workflows/automerge.yml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/workflows/docker.yml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/workflows/docs.yml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/workflows/markdown-code-runner.yml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/workflows/pytest.yml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/workflows/release-drafter.yml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/workflows/release.yml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.github/workflows/toc.yaml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.gitignore +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.jscpd.json +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.pre-commit-config.yaml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.prompts/docs-review.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/.prompts/pr-review.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/CLAUDE.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/LICENSE +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/__main__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_extras.json +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/.gitkeep +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/audio.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/faster-whisper.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/kokoro.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/llm.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/memory.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/mlx-whisper.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/piper.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/rag.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/server.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/speed.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_requirements/vad.txt +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/_tools.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/_voice_agent_common.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/assistant.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/autocorrect.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/chat.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/memory/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/memory/add.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/memory/proxy.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/rag_proxy.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/speak.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/transcribe.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/transcribe_daemon.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/agents/voice_edit.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/api.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/cli.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/config.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/config_cmd.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/constants.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/audio.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/audio_format.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/chroma.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/openai_proxy.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/process.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/reranker.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/sse.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/transcription_logger.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/utils.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/vad.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/core/watch.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/cli.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/aider.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/base.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/claude.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/codex.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/continue_dev.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/copilot.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/cursor_agent.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/gemini.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/opencode.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/coding_agents/registry.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/base.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/cursor.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/emacs.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/jetbrains.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/nano.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/neovim.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/registry.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/sublime.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/vim.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/vscode.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/editors/zed.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/project.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/registry.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/skill/SKILL.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/skill/examples.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/terminals/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/terminals/apple_terminal.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/terminals/base.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/terminals/gnome.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/terminals/iterm2.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/terminals/kitty.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/terminals/registry.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/terminals/tmux.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/terminals/warp.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/terminals/zellij.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/dev/worktree.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/docs_gen.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/example-config.toml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/install/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/install/common.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/install/hotkeys.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/install/services.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_files.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_filters.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_git.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_indexer.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_ingest.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_persistence.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_prompt.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_retrieval.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_store.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_streaming.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/_tasks.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/api.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/client.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/engine.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/entities.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/memory/models.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/opts.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/py.typed +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/_indexer.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/_indexing.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/_prompt.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/_retriever.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/_store.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/_utils.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/api.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/client.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/engine.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/rag/models.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/.runtime/.gitkeep +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/check_plugin_skill_sync.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/linux-hotkeys/README.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/linux-hotkeys/toggle-autocorrect.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/linux-hotkeys/toggle-transcription.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/linux-hotkeys/toggle-voice-edit.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/macos-hotkeys/README.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/macos-hotkeys/skhd-config-example +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/macos-hotkeys/toggle-autocorrect.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/macos-hotkeys/toggle-transcription.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/macos-hotkeys/toggle-voice-edit.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/nvidia-asr-server/README.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/nvidia-asr-server/pyproject.toml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/nvidia-asr-server/server.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/nvidia-asr-server/shell.nix +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/nvidia-asr-server/uv.lock +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/run-openwakeword.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/run-piper-windows.ps1 +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/run-piper.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/run-whisper-linux.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/run-whisper-macos.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/run-whisper-windows.ps1 +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/run-whisper.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/run_faster_whisper_server.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/setup-linux-hotkeys.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/setup-linux.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/setup-macos-hotkeys.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/setup-macos.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/setup-windows.ps1 +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/start-all-services-windows.ps1 +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/start-all-services.sh +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/scripts/sync_extras.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/common.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/model_manager.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/model_registry.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/proxy/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/proxy/api.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/streaming.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/tts/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/tts/api.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/tts/backends/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/tts/backends/base.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/tts/backends/kokoro.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/tts/backends/piper.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/tts/model_manager.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/tts/model_registry.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/tts/wyoming_handler.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/whisper/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/whisper/api.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/whisper/backends/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/whisper/backends/base.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/whisper/backends/faster_whisper.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/whisper/backends/mlx.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/whisper/languages.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/whisper/model_manager.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/whisper/model_registry.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/server/whisper/wyoming_handler.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/services/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/services/_wyoming_utils.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/services/asr.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/services/llm.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/services/tts.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/agent_cli/services/wake_word.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docker/docker-compose.yml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docker/transcription-proxy.Dockerfile +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docker/tts.Dockerfile +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docker/whisper.Dockerfile +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/CNAME +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/architecture/memory.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/architecture/rag.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/assistant.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/autocorrect.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/chat.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/config.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/dev.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/index.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/install-extras.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/install-hotkeys.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/install-services.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/memory.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/rag-proxy.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/server/index.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/server/transcription-proxy.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/server/tts.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/server/whisper.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/speak.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/start-services.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/transcribe-daemon.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/transcribe.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/commands/voice-edit.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/configuration.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/getting-started.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/iOS_Shortcut_Guide.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/index.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/installation/docker.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/installation/index.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/installation/linux.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/installation/macos.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/installation/nixos.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/installation/windows.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/logo-clean.svg +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/overrides/partials/integrations/analytics/custom.html +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/run_markdown_code_runner.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/docs/system-integration.md +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/example.agent-cli-config.toml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/justfile +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/pyproject.toml +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/shell.nix +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_fix_my_text.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_interactive.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_interactive_extra.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_memory_add.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_speak.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_speak_e2e.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_transcribe.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_transcribe_agent.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_transcribe_daemon.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_transcribe_e2e.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_transcribe_recovery.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_tts_common.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_tts_common_extra.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_voice_agent_common.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_voice_edit.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_voice_edit_e2e.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/agents/test_wake_word_assistant.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/conftest.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/core/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/core/test_audio_format.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/core/test_chroma.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/core/test_sse.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/core/test_vad.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/core/test_watch.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/dev/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/dev/test_cli.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/dev/test_coding_agents.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/dev/test_editors.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/dev/test_project.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/dev/test_terminals.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/dev/test_verification.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/dev/test_worktree.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/install/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/install/test_extras.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_api_health.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_api_integration_liveish.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_client.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_engine.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_files.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_filters.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_git_integration.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_indexer.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_memory_integration.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_proxy_passthrough.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_store.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/memory/test_utils.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/mocks/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/mocks/audio.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/mocks/llm.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/mocks/wyoming.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/__init__.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_api.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_engine.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_history.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_indexer.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_indexing.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_rag_client.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_rag_integration_liveish.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_rag_proxy_passthrough.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_retriever.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_store.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/rag/test_utils.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_api.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_api_integration.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_asr.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_asr_recovery.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_audio_e2e.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_cli.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_config.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_config_cmd.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_docs_gen.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_env_vars.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_json_output.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_llm.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_llm_gemini.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_memory_tools.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_mlx_backend.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_process_manager.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_server_streaming.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_server_tts.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_server_whisper.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_services.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_tools.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_tts.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_utils.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_wake_word.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/tests/test_wyoming_utils.py +0 -0
- {agent_cli-0.67.2 → agent_cli-0.68.1}/uv.lock +0 -0
|
@@ -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
|
|
@@ -377,7 +377,14 @@ All necessary scripts are bundled with the package, so you can run these command
|
|
|
377
377
|
|
|
378
378
|
#### Installing Optional Extras
|
|
379
379
|
|
|
380
|
-
Some features require additional Python dependencies.
|
|
380
|
+
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:
|
|
381
|
+
|
|
382
|
+
```toml
|
|
383
|
+
[settings]
|
|
384
|
+
auto_install_extras = false
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
You can also manually install extras with `install-extras`:
|
|
381
388
|
|
|
382
389
|
```bash
|
|
383
390
|
# List available extras
|
|
@@ -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
|
|
|
@@ -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![/]")
|
|
@@ -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] "
|
|
@@ -141,22 +141,39 @@ Usage: [rag-proxy command](../commands/rag-proxy.md).
|
|
|
141
141
|
|
|
142
142
|
## Dependencies
|
|
143
143
|
|
|
144
|
+
Agent CLI uses a modular dependency structure. The base package is lightweight, with features installed as optional extras.
|
|
145
|
+
|
|
144
146
|
### Core Dependencies
|
|
145
147
|
|
|
148
|
+
Always installed:
|
|
149
|
+
|
|
146
150
|
- **typer** - CLI framework
|
|
147
|
-
- **pydantic
|
|
148
|
-
- **sounddevice** - Audio I/O
|
|
149
|
-
- **pyperclip** - Clipboard access
|
|
151
|
+
- **pydantic** - Data validation
|
|
150
152
|
- **rich** - Terminal formatting
|
|
151
|
-
- **
|
|
152
|
-
- **
|
|
153
|
-
|
|
153
|
+
- **pyperclip** - Clipboard access
|
|
154
|
+
- **httpx** - HTTP client
|
|
155
|
+
|
|
156
|
+
### Provider Extras
|
|
157
|
+
|
|
158
|
+
Install with `agent-cli install-extras <name>` or `pip install agent-cli[name]`:
|
|
159
|
+
|
|
160
|
+
| Extra | Purpose | Key Packages |
|
|
161
|
+
|-------|---------|--------------|
|
|
162
|
+
| `audio` | Voice features | sounddevice, wyoming, numpy |
|
|
163
|
+
| `llm` | AI processing | pydantic-ai-slim (OpenAI, Gemini) |
|
|
164
|
+
|
|
165
|
+
### Feature Extras
|
|
154
166
|
|
|
155
|
-
|
|
167
|
+
| Extra | Purpose | Key Packages |
|
|
168
|
+
|-------|---------|--------------|
|
|
169
|
+
| `vad` | Voice activity detection | silero-vad |
|
|
170
|
+
| `rag` | Document chat | chromadb, markitdown |
|
|
171
|
+
| `memory` | Long-term memory | chromadb |
|
|
172
|
+
| `server` | Local ASR/TTS servers | fastapi |
|
|
173
|
+
| `faster-whisper` | Whisper (CUDA/CPU) | faster-whisper |
|
|
174
|
+
| `mlx-whisper` | Whisper (Apple Silicon) | mlx-whisper |
|
|
156
175
|
|
|
157
|
-
|
|
158
|
-
- **chromadb** - Vector database (for RAG and memory)
|
|
159
|
-
- **markitdown** - Document parsing (for RAG)
|
|
176
|
+
See [`install-extras`](../commands/install-extras.md) for the full list and installation instructions.
|
|
160
177
|
|
|
161
178
|
## Platform Support
|
|
162
179
|
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Tests for @requires_extras decorator functionality."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from agent_cli.core.deps import (
|
|
12
|
+
EXTRAS,
|
|
13
|
+
_check_and_install_extras,
|
|
14
|
+
_get_auto_install_setting,
|
|
15
|
+
_try_auto_install,
|
|
16
|
+
get_install_hint,
|
|
17
|
+
requires_extras,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestRequiresExtrasDecorator:
|
|
22
|
+
"""Test the requires_extras decorator functionality."""
|
|
23
|
+
|
|
24
|
+
def test_decorator_stores_extras_on_function(self) -> None:
|
|
25
|
+
"""The decorator should store required extras on the function."""
|
|
26
|
+
|
|
27
|
+
@requires_extras("audio", "llm")
|
|
28
|
+
def sample_command() -> str:
|
|
29
|
+
return "success"
|
|
30
|
+
|
|
31
|
+
assert hasattr(sample_command, "_required_extras")
|
|
32
|
+
assert sample_command._required_extras == ("audio", "llm")
|
|
33
|
+
|
|
34
|
+
def test_get_install_hint_with_pipe_syntax(self) -> None:
|
|
35
|
+
"""Pipe syntax shows all alternatives in the hint."""
|
|
36
|
+
hint = get_install_hint("piper|kokoro")
|
|
37
|
+
assert "requires one of:" in hint
|
|
38
|
+
assert "'piper'" in hint
|
|
39
|
+
assert "'kokoro'" in hint
|
|
40
|
+
# Brackets are escaped for rich markup (\\[)
|
|
41
|
+
assert "agent-cli\\[piper]" in hint
|
|
42
|
+
assert "agent-cli\\[kokoro]" in hint
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TestExtrasMetadata:
|
|
46
|
+
"""Test the _extras.json metadata is properly structured."""
|
|
47
|
+
|
|
48
|
+
def test_extras_dict_structure(self) -> None:
|
|
49
|
+
"""EXTRAS dict should have proper structure."""
|
|
50
|
+
assert isinstance(EXTRAS, dict)
|
|
51
|
+
for name, value in EXTRAS.items():
|
|
52
|
+
assert isinstance(name, str)
|
|
53
|
+
assert isinstance(value, tuple)
|
|
54
|
+
assert len(value) == 2
|
|
55
|
+
desc, packages = value
|
|
56
|
+
assert isinstance(desc, str)
|
|
57
|
+
assert isinstance(packages, list)
|
|
58
|
+
assert all(isinstance(pkg, str) for pkg in packages)
|
|
59
|
+
|
|
60
|
+
def test_essential_extras_present(self) -> None:
|
|
61
|
+
"""Essential extras should be defined."""
|
|
62
|
+
essential = ["audio", "llm", "rag", "memory", "vad"]
|
|
63
|
+
for extra in essential:
|
|
64
|
+
assert extra in EXTRAS, f"Missing essential extra: {extra}"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class TestAutoInstallSetting:
|
|
68
|
+
"""Test the auto-install extras configuration."""
|
|
69
|
+
|
|
70
|
+
def test_default_is_enabled(self) -> None:
|
|
71
|
+
"""Auto-install should be enabled by default."""
|
|
72
|
+
env = dict(os.environ)
|
|
73
|
+
env.pop("AGENT_CLI_NO_AUTO_INSTALL", None)
|
|
74
|
+
with (
|
|
75
|
+
patch.dict(os.environ, env, clear=True),
|
|
76
|
+
patch("agent_cli.core.deps.load_config", return_value={}),
|
|
77
|
+
):
|
|
78
|
+
assert _get_auto_install_setting() is True
|
|
79
|
+
|
|
80
|
+
def test_env_var_disables(self) -> None:
|
|
81
|
+
"""AGENT_CLI_NO_AUTO_INSTALL=1 should disable auto-install."""
|
|
82
|
+
with patch.dict(os.environ, {"AGENT_CLI_NO_AUTO_INSTALL": "1"}):
|
|
83
|
+
assert _get_auto_install_setting() is False
|
|
84
|
+
|
|
85
|
+
def test_env_var_true_disables(self) -> None:
|
|
86
|
+
"""AGENT_CLI_NO_AUTO_INSTALL=true should disable auto-install."""
|
|
87
|
+
with patch.dict(os.environ, {"AGENT_CLI_NO_AUTO_INSTALL": "true"}):
|
|
88
|
+
assert _get_auto_install_setting() is False
|
|
89
|
+
|
|
90
|
+
def test_env_var_yes_disables(self) -> None:
|
|
91
|
+
"""AGENT_CLI_NO_AUTO_INSTALL=yes should disable auto-install."""
|
|
92
|
+
with patch.dict(os.environ, {"AGENT_CLI_NO_AUTO_INSTALL": "yes"}):
|
|
93
|
+
assert _get_auto_install_setting() is False
|
|
94
|
+
|
|
95
|
+
def test_config_file_disables(self) -> None:
|
|
96
|
+
"""Config [settings] section with auto_install_extras = false should disable."""
|
|
97
|
+
env = dict(os.environ)
|
|
98
|
+
env.pop("AGENT_CLI_NO_AUTO_INSTALL", None)
|
|
99
|
+
with (
|
|
100
|
+
patch.dict(os.environ, env, clear=True),
|
|
101
|
+
patch(
|
|
102
|
+
"agent_cli.core.deps.load_config",
|
|
103
|
+
return_value={"settings": {"auto_install_extras": False}},
|
|
104
|
+
),
|
|
105
|
+
):
|
|
106
|
+
assert _get_auto_install_setting() is False
|
|
107
|
+
|
|
108
|
+
def test_config_file_enables(self) -> None:
|
|
109
|
+
"""Config [settings] section with auto_install_extras = true should enable."""
|
|
110
|
+
env = dict(os.environ)
|
|
111
|
+
env.pop("AGENT_CLI_NO_AUTO_INSTALL", None)
|
|
112
|
+
with (
|
|
113
|
+
patch.dict(os.environ, env, clear=True),
|
|
114
|
+
patch(
|
|
115
|
+
"agent_cli.core.deps.load_config",
|
|
116
|
+
return_value={"settings": {"auto_install_extras": True}},
|
|
117
|
+
),
|
|
118
|
+
):
|
|
119
|
+
assert _get_auto_install_setting() is True
|
|
120
|
+
|
|
121
|
+
def test_env_var_takes_precedence(self) -> None:
|
|
122
|
+
"""Environment variable should take precedence over config file."""
|
|
123
|
+
with (
|
|
124
|
+
patch.dict(os.environ, {"AGENT_CLI_NO_AUTO_INSTALL": "1"}),
|
|
125
|
+
patch(
|
|
126
|
+
"agent_cli.core.deps.load_config",
|
|
127
|
+
return_value={"settings": {"auto_install_extras": True}},
|
|
128
|
+
),
|
|
129
|
+
):
|
|
130
|
+
assert _get_auto_install_setting() is False
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class TestTryAutoInstall:
|
|
134
|
+
"""Test the _try_auto_install function."""
|
|
135
|
+
|
|
136
|
+
def test_flattens_alternatives(self) -> None:
|
|
137
|
+
"""Alternatives like 'piper|kokoro' should pick the first option."""
|
|
138
|
+
with patch(
|
|
139
|
+
"agent_cli.install.extras.install_extras_programmatic",
|
|
140
|
+
return_value=True,
|
|
141
|
+
) as mock_install:
|
|
142
|
+
result = _try_auto_install(["audio", "piper|kokoro"])
|
|
143
|
+
assert result is True
|
|
144
|
+
mock_install.assert_called_once_with(["audio", "piper"])
|
|
145
|
+
|
|
146
|
+
def test_returns_install_result(self) -> None:
|
|
147
|
+
"""Should return the result from install_extras_programmatic."""
|
|
148
|
+
with patch(
|
|
149
|
+
"agent_cli.install.extras.install_extras_programmatic",
|
|
150
|
+
return_value=False,
|
|
151
|
+
):
|
|
152
|
+
assert _try_auto_install(["audio"]) is False
|
|
153
|
+
|
|
154
|
+
with patch(
|
|
155
|
+
"agent_cli.install.extras.install_extras_programmatic",
|
|
156
|
+
return_value=True,
|
|
157
|
+
):
|
|
158
|
+
assert _try_auto_install(["audio"]) is True
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class TestCheckAndInstallExtras:
|
|
162
|
+
"""Test the _check_and_install_extras function."""
|
|
163
|
+
|
|
164
|
+
def test_returns_empty_when_all_installed(self) -> None:
|
|
165
|
+
"""Should return empty list when all extras are already installed."""
|
|
166
|
+
with patch("agent_cli.core.deps.check_extra_installed", return_value=True):
|
|
167
|
+
result = _check_and_install_extras(("audio", "llm"))
|
|
168
|
+
assert result == []
|
|
169
|
+
|
|
170
|
+
def test_returns_missing_when_auto_install_disabled(self) -> None:
|
|
171
|
+
"""Should return missing list without installing when disabled."""
|
|
172
|
+
with (
|
|
173
|
+
patch("agent_cli.core.deps.check_extra_installed", return_value=False),
|
|
174
|
+
patch("agent_cli.core.deps._get_auto_install_setting", return_value=False),
|
|
175
|
+
patch("agent_cli.core.deps.print_error_message") as mock_error,
|
|
176
|
+
):
|
|
177
|
+
result = _check_and_install_extras(("fake-extra",))
|
|
178
|
+
assert result == ["fake-extra"]
|
|
179
|
+
mock_error.assert_called_once()
|
|
180
|
+
|
|
181
|
+
def test_returns_missing_when_install_fails(self) -> None:
|
|
182
|
+
"""Should return missing list when auto-install fails."""
|
|
183
|
+
with (
|
|
184
|
+
patch("agent_cli.core.deps.check_extra_installed", return_value=False),
|
|
185
|
+
patch("agent_cli.core.deps._get_auto_install_setting", return_value=True),
|
|
186
|
+
patch("agent_cli.core.deps._try_auto_install", return_value=False),
|
|
187
|
+
patch("agent_cli.core.deps.print_error_message") as mock_error,
|
|
188
|
+
):
|
|
189
|
+
result = _check_and_install_extras(("fake-extra",))
|
|
190
|
+
assert result == ["fake-extra"]
|
|
191
|
+
mock_error.assert_called_once()
|
|
192
|
+
assert "Auto-install failed" in mock_error.call_args[0][0]
|
|
193
|
+
|
|
194
|
+
def test_returns_empty_when_install_succeeds(self) -> None:
|
|
195
|
+
"""Should return empty list when auto-install succeeds."""
|
|
196
|
+
check_results = iter([False, True]) # First call: missing, second: installed
|
|
197
|
+
with (
|
|
198
|
+
patch(
|
|
199
|
+
"agent_cli.core.deps.check_extra_installed",
|
|
200
|
+
side_effect=lambda _: next(check_results),
|
|
201
|
+
),
|
|
202
|
+
patch("agent_cli.core.deps._get_auto_install_setting", return_value=True),
|
|
203
|
+
patch("agent_cli.core.deps._try_auto_install", return_value=True),
|
|
204
|
+
):
|
|
205
|
+
result = _check_and_install_extras(("fake-extra",))
|
|
206
|
+
assert result == []
|
|
207
|
+
|
|
208
|
+
def test_returns_still_missing_after_partial_install(self) -> None:
|
|
209
|
+
"""Should return still-missing extras after install completes."""
|
|
210
|
+
# First check: missing, install succeeds, second check: still missing
|
|
211
|
+
with (
|
|
212
|
+
patch("agent_cli.core.deps.check_extra_installed", return_value=False),
|
|
213
|
+
patch("agent_cli.core.deps._get_auto_install_setting", return_value=True),
|
|
214
|
+
patch("agent_cli.core.deps._try_auto_install", return_value=True),
|
|
215
|
+
patch("agent_cli.core.deps.print_error_message") as mock_error,
|
|
216
|
+
):
|
|
217
|
+
result = _check_and_install_extras(("fake-extra",))
|
|
218
|
+
assert result == ["fake-extra"]
|
|
219
|
+
mock_error.assert_called_once()
|
|
220
|
+
assert "still missing" in mock_error.call_args[0][0]
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class TestDecoratorIntegration:
|
|
224
|
+
"""Test the requires_extras decorator end-to-end behavior."""
|
|
225
|
+
|
|
226
|
+
def test_calls_function_when_extras_installed(self) -> None:
|
|
227
|
+
"""Decorated function should run when extras are installed."""
|
|
228
|
+
with patch("agent_cli.core.deps.check_extra_installed", return_value=True):
|
|
229
|
+
|
|
230
|
+
@requires_extras("audio")
|
|
231
|
+
def my_command() -> str:
|
|
232
|
+
return "success"
|
|
233
|
+
|
|
234
|
+
assert my_command() == "success"
|
|
235
|
+
|
|
236
|
+
def test_exits_when_extras_missing_and_auto_install_disabled(self) -> None:
|
|
237
|
+
"""Should exit when extras missing and auto-install is disabled."""
|
|
238
|
+
with (
|
|
239
|
+
patch("agent_cli.core.deps.check_extra_installed", return_value=False),
|
|
240
|
+
patch("agent_cli.core.deps._get_auto_install_setting", return_value=False),
|
|
241
|
+
patch("agent_cli.core.deps.print_error_message"),
|
|
242
|
+
):
|
|
243
|
+
|
|
244
|
+
@requires_extras("fake-extra")
|
|
245
|
+
def my_command() -> str:
|
|
246
|
+
return "success"
|
|
247
|
+
|
|
248
|
+
with pytest.raises(typer.Exit) as exc_info:
|
|
249
|
+
my_command()
|
|
250
|
+
assert exc_info.value.exit_code == 1
|
|
251
|
+
|
|
252
|
+
def test_auto_installs_and_runs_on_success(self) -> None:
|
|
253
|
+
"""Should auto-install, then run the function on success."""
|
|
254
|
+
check_results = iter([False, True]) # Missing first, then installed
|
|
255
|
+
with (
|
|
256
|
+
patch(
|
|
257
|
+
"agent_cli.core.deps.check_extra_installed",
|
|
258
|
+
side_effect=lambda _: next(check_results),
|
|
259
|
+
),
|
|
260
|
+
patch("agent_cli.core.deps._get_auto_install_setting", return_value=True),
|
|
261
|
+
patch("agent_cli.core.deps._try_auto_install", return_value=True),
|
|
262
|
+
):
|
|
263
|
+
|
|
264
|
+
@requires_extras("fake-extra")
|
|
265
|
+
def my_command() -> str:
|
|
266
|
+
return "success"
|
|
267
|
+
|
|
268
|
+
assert my_command() == "success"
|
|
269
|
+
|
|
270
|
+
def test_exits_when_auto_install_fails(self) -> None:
|
|
271
|
+
"""Should exit when auto-install fails."""
|
|
272
|
+
with (
|
|
273
|
+
patch("agent_cli.core.deps.check_extra_installed", return_value=False),
|
|
274
|
+
patch("agent_cli.core.deps._get_auto_install_setting", return_value=True),
|
|
275
|
+
patch("agent_cli.core.deps._try_auto_install", return_value=False),
|
|
276
|
+
patch("agent_cli.core.deps.print_error_message"),
|
|
277
|
+
):
|
|
278
|
+
|
|
279
|
+
@requires_extras("fake-extra")
|
|
280
|
+
def my_command() -> str:
|
|
281
|
+
return "success"
|
|
282
|
+
|
|
283
|
+
with pytest.raises(typer.Exit) as exc_info:
|
|
284
|
+
my_command()
|
|
285
|
+
assert exc_info.value.exit_code == 1
|
|
@@ -49,6 +49,7 @@ nav = [
|
|
|
49
49
|
] },
|
|
50
50
|
{ "Setup" = [
|
|
51
51
|
{ "config" = "commands/config.md" },
|
|
52
|
+
{ "install-extras" = "commands/install-extras.md" },
|
|
52
53
|
{ "install-services" = "commands/install-services.md" },
|
|
53
54
|
{ "install-hotkeys" = "commands/install-hotkeys.md" },
|
|
54
55
|
{ "start-services" = "commands/start-services.md" },
|