agent-cli 0.105.1__tar.gz → 0.105.2__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.105.1 → agent_cli-0.105.2}/.github/workflows/pytest.yml +1 -1
- {agent_cli-0.105.1 → agent_cli-0.105.2}/PKG-INFO +1 -1
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/audio_format.py +17 -13
- agent_cli-0.105.2/agent_cli/server/whisper/backends/audio.py +44 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/backends/base.py +1 -1
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/backends/nemo.py +6 -24
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/backends/transformers.py +8 -1
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/appcast.xml +18 -19
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/core/test_audio_format.py +55 -7
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_faster_whisper_backend.py +59 -1
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_nemo_backend.py +39 -27
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_server_whisper.py +57 -1
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_transformers_backend.py +101 -2
- agent_cli-0.105.2/tests/test_whisper_backend_audio.py +171 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.claude/skills/agent-cli-dev/SKILL.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.claude/skills/agent-cli-dev/examples.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.claude-plugin/README.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.claude-plugin/marketplace.json +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.claude-plugin/plugin.json +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.claude-plugin/skills/agent-cli-dev/SKILL.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.claude-plugin/skills/agent-cli-dev/examples.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.cursorrules +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.dockerignore +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.env.example +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/logo.svg +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/release-drafter.yml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/renovate.json +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/scripts/check_extras_sync.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/scripts/check_plugin_skill_sync.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/scripts/normalize_appcast.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/scripts/sync_extras.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/scripts/sync_requirements.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/workflows/automerge.yml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/workflows/docker.yml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/workflows/docs.yml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/workflows/markdown-code-runner.yml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/workflows/release-drafter.yml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/workflows/release.yml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.github/workflows/toc.yaml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.gitignore +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.jscpd.json +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.pre-commit-config.yaml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.prompts/docs-review.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/.prompts/pr-review.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/CLAUDE.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/LICENSE +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/LLAMA_SERVER_USAGE.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/README.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/__main__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_extras.json +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_overrides/nemo-whisper.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/.gitkeep +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/audio.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/diarization.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/faster-whisper.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/kokoro.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/llm.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/memory.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/mlx-whisper.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/nemo-whisper.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/piper.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/rag.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/server.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/speed.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/vad.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/vectordb.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/whisper-transformers.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_requirements/wyoming.txt +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/_tools.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/_voice_agent_common.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/assistant.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/autocorrect.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/chat.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/diarize_live_session.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/memory/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/memory/add.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/memory/proxy.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/rag_proxy.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/speak.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/speakers.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/transcribe.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/transcribe_live.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/agents/voice_edit.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/api.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/cli.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/config.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/config_cmd.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/constants.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/alignment.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/audio.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/chroma.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/deps.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/diarization.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/openai_proxy.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/process.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/reranker.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/speaker_identity.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/sse.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/transcription_logger.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/utils.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/vad.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/core/watch.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/daemon/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/daemon/cli.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/_branch_name.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/_config.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/_output.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/cleanup.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/cli.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/aider.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/base.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/claude.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/codex.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/continue_dev.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/copilot.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/cursor_agent.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/gemini.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/opencode.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/coding_agents/registry.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/base.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/cursor.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/emacs.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/jetbrains.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/nano.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/neovim.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/registry.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/sublime.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/vim.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/vscode.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/editors/zed.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/hooks.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/launch.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/project.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/registry.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/skill/SKILL.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/skill/examples.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/apple_terminal.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/base.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/cmux.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/gnome.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/iterm2.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/kitty.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/registry.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/tmux.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/warp.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/terminals/zellij.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/dev/worktree.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/docs_gen.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/example-config.toml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/install/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/install/common.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/install/extras.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/install/hotkeys.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/install/launchd.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/install/service_config.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/install/services.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/install/systemd.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_files.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_filters.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_git.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_indexer.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_ingest.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_persistence.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_prompt.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_retrieval.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_store.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_streaming.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/_tasks.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/api.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/client.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/engine.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/entities.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/memory/models.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/opts.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/py.typed +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/_indexer.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/_indexing.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/_prompt.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/_retriever.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/_store.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/_utils.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/api.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/client.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/engine.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/rag/models.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/.runtime/.gitkeep +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/linux-hotkeys/README.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/linux-hotkeys/toggle-autocorrect.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/linux-hotkeys/toggle-transcription.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/linux-hotkeys/toggle-voice-edit.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/macos-hotkeys/README.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/macos-hotkeys/skhd-config-example +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/macos-hotkeys/toggle-autocorrect.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/macos-hotkeys/toggle-transcription.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/macos-hotkeys/toggle-voice-edit.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/nvidia-asr-server/README.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/nvidia-asr-server/pyproject.toml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/nvidia-asr-server/server.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/nvidia-asr-server/shell.nix +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/nvidia-asr-server/uv.lock +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/run-openwakeword.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/setup-linux-hotkeys.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/setup-linux.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/setup-macos-hotkeys.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/setup-macos.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/setup-windows.ps1 +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/start-all-services-windows.ps1 +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/scripts/start-all-services.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/cli.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/common.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/model_manager.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/model_registry.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/proxy/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/proxy/api.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/proxy/diarization.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/streaming.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/tts/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/tts/api.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/tts/backends/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/tts/backends/base.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/tts/backends/kokoro.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/tts/backends/piper.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/tts/model_manager.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/tts/model_registry.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/tts/wyoming_handler.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/api.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/backends/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/backends/faster_whisper.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/backends/mlx.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/languages.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/model_manager.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/model_registry.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/server/whisper/wyoming_handler.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/services/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/services/_wyoming_utils.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/services/asr.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/services/llm.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/services/tts.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/agent_cli/services/wake_word.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docker/diarization.Dockerfile +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docker/docker-compose.yml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docker/memory-proxy.Dockerfile +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docker/rag-proxy.Dockerfile +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docker/transcribe-proxy.Dockerfile +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docker/tts.Dockerfile +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docker/whisper.Dockerfile +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/CNAME +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/architecture/index.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/architecture/memory.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/architecture/rag.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/assistant.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/autocorrect.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/chat.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/config.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/daemon.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/dev.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/diarize-live-session.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/index.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/install-extras.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/install-hotkeys.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/install-services.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/memory.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/rag-proxy.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/server/index.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/server/transcribe-proxy.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/server/tts.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/server/whisper.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/speak.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/speakers.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/start-services.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/transcribe-live.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/transcribe.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/commands/voice-edit.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/configuration.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/getting-started.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/iOS_Shortcut_Guide.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/index.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/installation/docker.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/installation/index.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/installation/linux.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/installation/macos-app.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/installation/macos.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/installation/nixos.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/installation/windows.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/logo-avatar.svg +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/logo-clean.svg +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/overrides/partials/integrations/analytics/custom.html +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/run_markdown_code_runner.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/docs/system-integration.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/example.agent-cli-config.toml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/justfile +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Package.resolved +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Package.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/README.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Resources/AgentCLI.entitlements +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Resources/Info.plist +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Resources/dmg-background.svg +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/AgentCLIApp.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/AgentCommand.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/AgentCommandRunner.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/AgentRuntime.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/AppDelegate.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/AppMetadata.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/AppUpdater.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/BootstrapState.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/CommandResult.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/ConfigurableHotkeyController.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/FocusedTextTarget.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/LiveTranscriptionPreview.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/LoginItemController.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/MenuActivityStatus.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/MenuActivityStatusRow.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/MenuActivityTracker.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/MenuBarIcon.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/RecentTranscriptionReader.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/RecordingIndicatorController.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/RecordingSoundSettings.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/RuntimeSettings.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/SettingsWindowController.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/Shortcuts.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/StatusMenuController.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/TranscriptPasteController.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/TranscriptionSettings.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Sources/AgentCLI/VoiceLevelOverlay.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Tests/AgentCLITests/AgentCommandTests.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Tests/AgentCLITests/ConfigurableHotkeyControllerTests.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Tests/AgentCLITests/LivePreviewSwiftTestingTests.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Tests/AgentCLITests/LiveTranscriptionPreviewTests.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Tests/AgentCLITests/LoginItemControllerTests.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Tests/AgentCLITests/RecentTranscriptionReaderTests.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Tests/AgentCLITests/RecordingIndicatorControllerTests.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Tests/AgentCLITests/ShortcutDefaultsTests.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/AgentCLI/Tests/AgentCLITests/VoiceLevelMeterTests.swift +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/build-macos-app.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/macos/test-macos-app-e2e.sh +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/pyproject.toml +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/reddit.md +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/shell.nix +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_fix_my_text.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_interactive.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_interactive_extra.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_memory_add.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_speak.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_speak_e2e.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_speakers.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_transcribe.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_transcribe_agent.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_transcribe_e2e.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_transcribe_live.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_transcribe_recovery.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_tts_common.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_tts_common_extra.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_voice_agent_common.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_voice_edit.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_voice_edit_e2e.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/agents/test_wake_word_assistant.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/conftest.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/core/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/core/test_audio.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/core/test_audio_levels.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/core/test_chroma.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/core/test_sse.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/core/test_vad.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/core/test_watch.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/test_cleanup.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/test_cli.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/test_coding_agents.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/test_editors.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/test_hooks.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/test_launch.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/test_project.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/test_terminals.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/test_verification.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/dev/test_worktree.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/install/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/install/test_extras.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/install/test_launchd.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_api_health.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_api_integration_liveish.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_client.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_engine.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_files.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_filters.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_git_integration.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_indexer.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_memory_integration.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_proxy_passthrough.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_store.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/memory/test_utils.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/mocks/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/mocks/audio.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/mocks/llm.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/mocks/wyoming.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/__init__.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_api.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_engine.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_history.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_indexer.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_indexing.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_rag_client.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_rag_integration_liveish.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_rag_proxy_passthrough.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_retriever.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_store.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/rag/test_utils.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_alignment.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_api.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_api_integration.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_asr.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_asr_recovery.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_audio_e2e.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_cli.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_config.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_config_cmd.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_daemon.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_diarization.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_diarize_live_session.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_docs_gen.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_env_vars.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_json_output.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_llm.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_llm_gemini.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_macos_app.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_memory_tools.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_mlx_backend.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_normalize_appcast.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_process_manager.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_proxy_diarization.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_requires_extras.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_server_streaming.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_server_tts.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_server_tts_wyoming.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_server_whisper_wyoming.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_services.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_speaker_identity.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_tools.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_tts.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_utils.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_wake_word.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/tests/test_wyoming_utils.py +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/uv.lock +0 -0
- {agent_cli-0.105.1 → agent_cli-0.105.2}/zensical.toml +0 -0
|
@@ -43,6 +43,6 @@ jobs:
|
|
|
43
43
|
run: uv run --extra audio --extra diarization --extra llm --extra rag --extra memory --extra vad --extra faster-whisper --extra piper --extra kokoro --extra server --extra speed --extra test pytest -vvv
|
|
44
44
|
- name: Upload coverage reports to Codecov
|
|
45
45
|
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'
|
|
46
|
-
uses: codecov/codecov-action@v7.
|
|
46
|
+
uses: codecov/codecov-action@v7.1.0
|
|
47
47
|
env:
|
|
48
48
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agent-cli
|
|
3
|
-
Version: 0.105.
|
|
3
|
+
Version: 0.105.2
|
|
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>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
import io
|
|
6
7
|
import logging
|
|
7
8
|
import shutil
|
|
@@ -152,13 +153,13 @@ def convert_audio_to_wyoming_format(
|
|
|
152
153
|
output_path.unlink(missing_ok=True)
|
|
153
154
|
|
|
154
155
|
|
|
155
|
-
def convert_audio_to_wav_format(
|
|
156
|
+
async def convert_audio_to_wav_format(
|
|
156
157
|
audio_data: bytes,
|
|
157
158
|
source_filename: str,
|
|
158
159
|
*,
|
|
159
|
-
timeout: int | None = 60,
|
|
160
|
+
timeout: int | None = 60, # noqa: ASYNC109 - bounds and cleans up the child process
|
|
160
161
|
) -> bytes:
|
|
161
|
-
"""Convert
|
|
162
|
+
"""Convert to 16kHz mono 16-bit WAV, stopping FFmpeg on cancellation or timeout."""
|
|
162
163
|
if not shutil.which("ffmpeg"):
|
|
163
164
|
msg = "FFmpeg not found in PATH. Please install FFmpeg to convert audio formats."
|
|
164
165
|
raise RuntimeError(msg)
|
|
@@ -185,20 +186,23 @@ def convert_audio_to_wav_format(
|
|
|
185
186
|
]
|
|
186
187
|
|
|
187
188
|
logger.debug("Running FFmpeg WAV conversion: %s", " ".join(cmd))
|
|
189
|
+
process = await asyncio.create_subprocess_exec(
|
|
190
|
+
*cmd,
|
|
191
|
+
stdout=asyncio.subprocess.DEVNULL,
|
|
192
|
+
stderr=asyncio.subprocess.PIPE,
|
|
193
|
+
)
|
|
188
194
|
try:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
capture_output=True,
|
|
192
|
-
text=False,
|
|
193
|
-
check=False,
|
|
194
|
-
timeout=timeout,
|
|
195
|
-
)
|
|
196
|
-
except subprocess.TimeoutExpired as e:
|
|
195
|
+
_, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
|
196
|
+
except TimeoutError as e:
|
|
197
197
|
msg = f"FFmpeg conversion timed out after {timeout} seconds"
|
|
198
198
|
raise RuntimeError(msg) from e
|
|
199
|
+
finally:
|
|
200
|
+
if process.returncode is None:
|
|
201
|
+
process.kill()
|
|
202
|
+
await process.communicate()
|
|
199
203
|
|
|
200
|
-
if
|
|
201
|
-
stderr_text =
|
|
204
|
+
if process.returncode != 0:
|
|
205
|
+
stderr_text = (stderr or b"").decode("utf-8", errors="replace")
|
|
202
206
|
logger.error("FFmpeg WAV conversion failed: %s", stderr_text)
|
|
203
207
|
msg = f"FFmpeg WAV conversion failed: {stderr_text}"
|
|
204
208
|
raise RuntimeError(msg)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Audio preparation for ASR backends that require PCM WAV input."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
import wave
|
|
7
|
+
|
|
8
|
+
from agent_cli import constants
|
|
9
|
+
from agent_cli.core.audio_format import convert_audio_to_wav_format
|
|
10
|
+
from agent_cli.server.whisper.backends.base import InvalidAudioError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def prepare_wav_audio(
|
|
14
|
+
audio: bytes,
|
|
15
|
+
source_filename: str | None,
|
|
16
|
+
*,
|
|
17
|
+
backend_label: str,
|
|
18
|
+
) -> bytes:
|
|
19
|
+
"""Return 16kHz mono 16-bit PCM WAV bytes, converting only when needed.
|
|
20
|
+
|
|
21
|
+
Container validity alone is insufficient: Transformers reads samples as
|
|
22
|
+
int16 mono. Normalize other sample formats before model-worker dispatch.
|
|
23
|
+
Backends with their own decoders, such as faster-whisper, do not need this.
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
with wave.open(io.BytesIO(audio), "rb") as wav_file:
|
|
27
|
+
if (
|
|
28
|
+
wav_file.getsampwidth() == constants.AUDIO_FORMAT_WIDTH
|
|
29
|
+
and wav_file.getnchannels() == constants.AUDIO_CHANNELS
|
|
30
|
+
and wav_file.getframerate() == constants.AUDIO_RATE
|
|
31
|
+
):
|
|
32
|
+
return audio
|
|
33
|
+
except (wave.Error, EOFError, RuntimeError):
|
|
34
|
+
# wave can raise RuntimeError when a malformed RIFF chunk seeks past EOF.
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
return await convert_audio_to_wav_format(audio, source_filename or "audio")
|
|
39
|
+
except RuntimeError as exc:
|
|
40
|
+
msg = (
|
|
41
|
+
f"Unsupported audio format for {backend_label}. "
|
|
42
|
+
"Provide a valid WAV file or install ffmpeg to convert uploads."
|
|
43
|
+
)
|
|
44
|
+
raise InvalidAudioError(msg) from exc
|
|
@@ -89,7 +89,7 @@ class WhisperBackend(Protocol):
|
|
|
89
89
|
"""Transcribe audio data.
|
|
90
90
|
|
|
91
91
|
Args:
|
|
92
|
-
audio:
|
|
92
|
+
audio: Encoded audio bytes (16kHz mono 16-bit PCM WAV preferred).
|
|
93
93
|
source_filename: Optional filename to help detect audio format.
|
|
94
94
|
language: Language code or None for auto-detection
|
|
95
95
|
task: "transcribe" or "translate" (to English)
|
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import inspect
|
|
7
|
-
import io
|
|
8
7
|
import logging
|
|
9
8
|
import os
|
|
10
9
|
import sys
|
|
@@ -18,11 +17,10 @@ from multiprocessing import get_context
|
|
|
18
17
|
from pathlib import Path
|
|
19
18
|
from typing import Any, Literal
|
|
20
19
|
|
|
21
|
-
from agent_cli.core.audio_format import convert_audio_to_wav_format
|
|
22
20
|
from agent_cli.core.process import set_process_title
|
|
21
|
+
from agent_cli.server.whisper.backends.audio import prepare_wav_audio
|
|
23
22
|
from agent_cli.server.whisper.backends.base import (
|
|
24
23
|
BackendConfig,
|
|
25
|
-
InvalidAudioError,
|
|
26
24
|
TranscriptionResult,
|
|
27
25
|
)
|
|
28
26
|
|
|
@@ -182,26 +180,6 @@ def _audio_duration_seconds(wav_path: str) -> float:
|
|
|
182
180
|
return 0.0
|
|
183
181
|
|
|
184
182
|
|
|
185
|
-
def _prepare_audio_for_nemo(audio_bytes: bytes, source_filename: str | None) -> bytes:
|
|
186
|
-
"""Return a WAV container suitable for NeMo file-path transcription."""
|
|
187
|
-
try:
|
|
188
|
-
with wave.open(io.BytesIO(audio_bytes), "rb"):
|
|
189
|
-
return audio_bytes
|
|
190
|
-
except (wave.Error, EOFError):
|
|
191
|
-
pass
|
|
192
|
-
|
|
193
|
-
filename = source_filename or "audio"
|
|
194
|
-
try:
|
|
195
|
-
return convert_audio_to_wav_format(audio_bytes, filename)
|
|
196
|
-
except RuntimeError as exc:
|
|
197
|
-
logger.warning("FFmpeg conversion failed for NeMo Whisper: %s", exc)
|
|
198
|
-
msg = (
|
|
199
|
-
"Unsupported audio format for NeMo Whisper. "
|
|
200
|
-
"Provide a WAV file or install ffmpeg to convert uploads."
|
|
201
|
-
)
|
|
202
|
-
raise InvalidAudioError(msg) from exc
|
|
203
|
-
|
|
204
|
-
|
|
205
183
|
def _build_transcribe_kwargs(
|
|
206
184
|
transcribe_func: Any,
|
|
207
185
|
*,
|
|
@@ -433,7 +411,11 @@ class NemoWhisperBackend:
|
|
|
433
411
|
)
|
|
434
412
|
self._warned_initial_prompt_ignored = True
|
|
435
413
|
|
|
436
|
-
audio = await
|
|
414
|
+
audio = await prepare_wav_audio(
|
|
415
|
+
audio,
|
|
416
|
+
source_filename,
|
|
417
|
+
backend_label="NeMo Whisper",
|
|
418
|
+
)
|
|
437
419
|
|
|
438
420
|
kwargs: dict[str, Any] = {
|
|
439
421
|
"language": language,
|
|
@@ -15,6 +15,7 @@ from pathlib import Path
|
|
|
15
15
|
from typing import Any, Literal
|
|
16
16
|
|
|
17
17
|
from agent_cli.core.process import set_process_title
|
|
18
|
+
from agent_cli.server.whisper.backends.audio import prepare_wav_audio
|
|
18
19
|
from agent_cli.server.whisper.backends.base import (
|
|
19
20
|
BackendConfig,
|
|
20
21
|
TranscriptionResult,
|
|
@@ -593,7 +594,7 @@ class TransformersWhisperBackend:
|
|
|
593
594
|
self,
|
|
594
595
|
audio: bytes,
|
|
595
596
|
*,
|
|
596
|
-
source_filename: str | None = None,
|
|
597
|
+
source_filename: str | None = None,
|
|
597
598
|
language: str | None = None,
|
|
598
599
|
task: Literal["transcribe", "translate"] = "transcribe",
|
|
599
600
|
initial_prompt: str | None = None,
|
|
@@ -606,6 +607,12 @@ class TransformersWhisperBackend:
|
|
|
606
607
|
msg = "Model not loaded. Call load() first."
|
|
607
608
|
raise RuntimeError(msg)
|
|
608
609
|
|
|
610
|
+
audio = await prepare_wav_audio(
|
|
611
|
+
audio,
|
|
612
|
+
source_filename,
|
|
613
|
+
backend_label="transformers ASR",
|
|
614
|
+
)
|
|
615
|
+
|
|
609
616
|
# Write audio to temp file for wave parsing in subprocess
|
|
610
617
|
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
|
|
611
618
|
tmp.write(audio)
|
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
<title>Agent CLI Updates</title>
|
|
5
5
|
<link>https://github.com/basnijholt/agent-cli</link>
|
|
6
6
|
<description>Signed Agent CLI macOS app updates.</description>
|
|
7
|
+
<item>
|
|
8
|
+
<title>0.105.1</title>
|
|
9
|
+
<pubDate>Tue, 08 Sep 2026 13:10:30 +0000</pubDate>
|
|
10
|
+
<link>https://github.com/basnijholt/agent-cli</link>
|
|
11
|
+
<sparkle:version>205</sparkle:version>
|
|
12
|
+
<sparkle:shortVersionString>0.105.1</sparkle:shortVersionString>
|
|
13
|
+
<sparkle:minimumSystemVersion>13.0</sparkle:minimumSystemVersion>
|
|
14
|
+
<sparkle:hardwareRequirements>arm64</sparkle:hardwareRequirements>
|
|
15
|
+
<description sparkle:format="markdown"><![CDATA[## Bug fixes
|
|
16
|
+
|
|
17
|
+
- Release unused CUDA cache after each transcription in the transformers ASR backend, keeping model weights loaded for the next request. Long recordings no longer leave their peak temporary GPU allocation reserved throughout the idle timeout. In a Qwen3-ASR test, idle process VRAM dropped from 10.89 GiB to 4.13 GiB. (#642)
|
|
18
|
+
- Clear temporary tensor references held by failed-request tracebacks, including chained and grouped exceptions, before releasing CUDA cache. CPU and MPS behavior is unchanged. (#642)
|
|
19
|
+
|
|
20
|
+
**Full changelog:** https://github.com/basnijholt/agent-cli/compare/v0.105.0...v0.105.1
|
|
21
|
+
|
|
22
|
+
]]></description>
|
|
23
|
+
<enclosure url="https://github.com/basnijholt/agent-cli/releases/download/v0.105.1/AgentCLI.zip" length="18455354" type="application/octet-stream" sparkle:edSignature="vZqacIut3YOxE/I4mgUeHiI9jviHLgzymE3jK8R7aLEooAPZhNqkzxTFpB95iWShRRWfR8lf9T4ztVFs0EHUDw=="/>
|
|
24
|
+
</item>
|
|
7
25
|
<item>
|
|
8
26
|
<title>0.105.0</title>
|
|
9
27
|
<pubDate>Tue, 08 Sep 2026 10:29:52 +0000</pubDate>
|
|
@@ -190,24 +208,5 @@
|
|
|
190
208
|
]]></description>
|
|
191
209
|
<enclosure url="https://github.com/basnijholt/agent-cli/releases/download/v0.101.0/AgentCLI.zip" length="22912290" type="application/octet-stream" sparkle:edSignature="9re7kqubAoQgSpsmDvAT/LCWy8/tI1RzR2uGkb+BCwGJbtfvuzDU2ETm/JD+lpQt3Ayik6ULAOv+/5S6JSmdBA=="/>
|
|
192
210
|
</item>
|
|
193
|
-
<item>
|
|
194
|
-
<title>0.100.0</title>
|
|
195
|
-
<pubDate>Wed, 10 Jun 2026 17:59:16 +0000</pubDate>
|
|
196
|
-
<link>https://github.com/basnijholt/agent-cli</link>
|
|
197
|
-
<sparkle:version>195</sparkle:version>
|
|
198
|
-
<sparkle:shortVersionString>0.100.0</sparkle:shortVersionString>
|
|
199
|
-
<sparkle:minimumSystemVersion>13.0</sparkle:minimumSystemVersion>
|
|
200
|
-
<sparkle:hardwareRequirements>arm64</sparkle:hardwareRequirements>
|
|
201
|
-
<description sparkle:format="markdown"><![CDATA[## What’s Changed
|
|
202
|
-
|
|
203
|
-
* Add cmux support to agent-cli dev (#589) @basnijholt
|
|
204
|
-
* ⬆️ Update sparkle-project/Sparkle to v2.9.3 (#587) @[renovate[bot]](https://github.com/apps/renovate)
|
|
205
|
-
* [pre-commit.ci] pre-commit autoupdate (#588) @[pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)
|
|
206
|
-
* ⬆️ Update codecov/codecov-action action to v7 (#586) @[renovate[bot]](https://github.com/apps/renovate)
|
|
207
|
-
* ⬆️ Update codecov/codecov-action action to v6.0.2 (#585) @[renovate[bot]](https://github.com/apps/renovate)
|
|
208
|
-
|
|
209
|
-
]]></description>
|
|
210
|
-
<enclosure url="https://github.com/basnijholt/agent-cli/releases/download/v0.100.0/AgentCLI.zip" length="22908645" type="application/octet-stream" sparkle:edSignature="2xn/3yQOqyqwCQ5871EAgFra/D/z6UrDl1B+ZxUh7PnOxJpL4Oe44UzQgjFB+LJXzqrY6Kmqvu9GBmk+3aZ1Ag=="/>
|
|
211
|
-
</item>
|
|
212
211
|
</channel>
|
|
213
212
|
</rss>
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
import shutil
|
|
6
7
|
import struct
|
|
7
|
-
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
8
11
|
|
|
9
12
|
import pytest
|
|
10
13
|
|
|
@@ -78,28 +81,73 @@ def test_convert_audio_arguments() -> None:
|
|
|
78
81
|
assert "s16le" in cmd
|
|
79
82
|
|
|
80
83
|
|
|
81
|
-
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_convert_audio_to_wav_arguments() -> None:
|
|
82
86
|
"""Regression test: WAV conversion should produce a PCM WAV container."""
|
|
83
87
|
with (
|
|
84
88
|
patch("shutil.which", return_value="/usr/bin/ffmpeg"),
|
|
85
|
-
patch("
|
|
89
|
+
patch("asyncio.create_subprocess_exec") as mock_run,
|
|
86
90
|
patch("pathlib.Path.read_bytes", return_value=b"wav_data"),
|
|
87
91
|
patch("shutil.rmtree"),
|
|
88
92
|
):
|
|
89
|
-
mock_run.return_value = MagicMock(
|
|
93
|
+
mock_run.return_value = MagicMock(
|
|
94
|
+
returncode=0, communicate=AsyncMock(return_value=(None, b""))
|
|
95
|
+
)
|
|
90
96
|
|
|
91
|
-
converted = audio_format.convert_audio_to_wav_format(b"input_data", "test.mp3")
|
|
97
|
+
converted = await audio_format.convert_audio_to_wav_format(b"input_data", "test.mp3")
|
|
92
98
|
|
|
93
99
|
assert converted == b"wav_data"
|
|
94
100
|
args, kwargs = mock_run.call_args
|
|
95
|
-
assert kwargs
|
|
96
|
-
cmd = args
|
|
101
|
+
assert kwargs["stderr"] == asyncio.subprocess.PIPE
|
|
102
|
+
cmd = args
|
|
97
103
|
assert cmd[0] == "ffmpeg"
|
|
98
104
|
assert "-acodec" in cmd
|
|
99
105
|
assert "pcm_s16le" in cmd
|
|
100
106
|
assert "-f" not in cmd
|
|
101
107
|
|
|
102
108
|
|
|
109
|
+
@pytest.mark.parametrize("cancel", [False, True], ids=["timeout", "cancellation"])
|
|
110
|
+
@pytest.mark.asyncio
|
|
111
|
+
async def test_wav_conversion_stops_process_and_removes_files(
|
|
112
|
+
monkeypatch: pytest.MonkeyPatch, cancel: bool
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Timed-out or cancelled requests must reap FFmpeg before removing its files."""
|
|
115
|
+
spawn = asyncio.create_subprocess_exec
|
|
116
|
+
started = asyncio.Event()
|
|
117
|
+
processes: list[asyncio.subprocess.Process] = []
|
|
118
|
+
directories: list[Path] = []
|
|
119
|
+
|
|
120
|
+
async def start_process(*args: str, stdout: int, stderr: int) -> asyncio.subprocess.Process:
|
|
121
|
+
directories.append(Path(args[-1]).parent)
|
|
122
|
+
process = await spawn(
|
|
123
|
+
sys.executable, "-c", "import time; time.sleep(60)", stdout=stdout, stderr=stderr
|
|
124
|
+
)
|
|
125
|
+
processes.append(process)
|
|
126
|
+
started.set()
|
|
127
|
+
return process
|
|
128
|
+
|
|
129
|
+
monkeypatch.setattr("shutil.which", lambda _name: "ffmpeg")
|
|
130
|
+
monkeypatch.setattr(asyncio, "create_subprocess_exec", start_process)
|
|
131
|
+
task = asyncio.create_task(
|
|
132
|
+
audio_format.convert_audio_to_wav_format(b"input", "voice.m4a", timeout=1),
|
|
133
|
+
)
|
|
134
|
+
try:
|
|
135
|
+
await asyncio.wait_for(started.wait(), timeout=1)
|
|
136
|
+
assert directories[0].exists()
|
|
137
|
+
if cancel:
|
|
138
|
+
task.cancel()
|
|
139
|
+
error = asyncio.CancelledError if cancel else RuntimeError
|
|
140
|
+
with pytest.raises(error):
|
|
141
|
+
await task
|
|
142
|
+
assert processes[0].returncode is not None
|
|
143
|
+
assert not directories[0].exists()
|
|
144
|
+
finally:
|
|
145
|
+
for process in processes:
|
|
146
|
+
if process.returncode is None:
|
|
147
|
+
process.kill()
|
|
148
|
+
await process.communicate()
|
|
149
|
+
|
|
150
|
+
|
|
103
151
|
def test_convert_audio_integration(sample_wav_data: bytes) -> None:
|
|
104
152
|
"""Integration test using actual ffmpeg if available."""
|
|
105
153
|
if not shutil.which("ffmpeg"):
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import io
|
|
6
|
+
import wave
|
|
5
7
|
from concurrent.futures.process import BrokenProcessPool
|
|
6
8
|
from typing import TYPE_CHECKING, cast
|
|
7
9
|
from unittest.mock import AsyncMock, patch
|
|
@@ -58,9 +60,65 @@ async def test_faster_whisper_transcribe_recovers_from_broken_process_pool() ->
|
|
|
58
60
|
patch.object(backend, "load", new=AsyncMock(side_effect=fake_load)) as load_mock,
|
|
59
61
|
):
|
|
60
62
|
mock_loop.return_value.run_in_executor = mock_run_in_executor
|
|
61
|
-
result = await backend.transcribe(
|
|
63
|
+
result = await backend.transcribe(_create_test_wav())
|
|
62
64
|
|
|
63
65
|
assert result.text == "hello world"
|
|
64
66
|
unload_mock.assert_awaited_once()
|
|
65
67
|
load_mock.assert_awaited_once()
|
|
66
68
|
assert executors_seen == [initial_executor, recovered_executor]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _create_test_wav() -> bytes:
|
|
72
|
+
"""Create a tiny valid 16kHz mono 16-bit WAV file."""
|
|
73
|
+
buffer = io.BytesIO()
|
|
74
|
+
with wave.open(buffer, "wb") as wav_file:
|
|
75
|
+
wav_file.setnchannels(1)
|
|
76
|
+
wav_file.setsampwidth(2)
|
|
77
|
+
wav_file.setframerate(16000)
|
|
78
|
+
wav_file.writeframes(b"\x00\x00" * 160)
|
|
79
|
+
return buffer.getvalue()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def _transcribe_capturing_audio(audio: bytes, source_filename: str | None) -> bytes:
|
|
83
|
+
"""Run the backend against a stub executor and return the bytes it dispatched."""
|
|
84
|
+
backend = FasterWhisperBackend(BackendConfig(model_name="tiny"))
|
|
85
|
+
backend._executor = cast("ProcessPoolExecutor", object())
|
|
86
|
+
dispatched: dict[str, bytes] = {}
|
|
87
|
+
|
|
88
|
+
async def mock_run_in_executor(
|
|
89
|
+
_executor: object,
|
|
90
|
+
_func: object,
|
|
91
|
+
audio_bytes: bytes,
|
|
92
|
+
_kwargs: dict[str, object],
|
|
93
|
+
) -> dict[str, object]:
|
|
94
|
+
dispatched["audio"] = audio_bytes
|
|
95
|
+
return {
|
|
96
|
+
"text": "hello",
|
|
97
|
+
"language": "en",
|
|
98
|
+
"language_probability": 1.0,
|
|
99
|
+
"duration": 0.01,
|
|
100
|
+
"segments": [],
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
with patch("asyncio.get_running_loop") as mock_loop:
|
|
104
|
+
mock_loop.return_value.run_in_executor = mock_run_in_executor
|
|
105
|
+
result = await backend.transcribe(audio, source_filename=source_filename)
|
|
106
|
+
|
|
107
|
+
assert result.text == "hello"
|
|
108
|
+
return dispatched["audio"]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.mark.parametrize(
|
|
112
|
+
("audio", "filename"),
|
|
113
|
+
[(b"OggS\x00\x02", "voice.ogg"), (b"\x00\x00\x00 ftypM4A ", "voice.m4a")],
|
|
114
|
+
)
|
|
115
|
+
@pytest.mark.asyncio
|
|
116
|
+
async def test_transcribe_preserves_native_decoder_input_without_ffmpeg(
|
|
117
|
+
monkeypatch: pytest.MonkeyPatch, audio: bytes, filename: str
|
|
118
|
+
) -> None:
|
|
119
|
+
"""PyAV owns decoding; pre-conversion would add an unnecessary system dependency.
|
|
120
|
+
|
|
121
|
+
https://github.com/SYSTRAN/faster-whisper/blob/master/faster_whisper/audio.py
|
|
122
|
+
"""
|
|
123
|
+
monkeypatch.setattr("shutil.which", lambda _name: None)
|
|
124
|
+
assert await _transcribe_capturing_audio(audio, filename) == audio
|
|
@@ -9,15 +9,18 @@ import wave
|
|
|
9
9
|
from concurrent.futures import ThreadPoolExecutor
|
|
10
10
|
from concurrent.futures.process import BrokenProcessPool
|
|
11
11
|
from types import ModuleType, SimpleNamespace
|
|
12
|
-
from typing import TYPE_CHECKING, Any
|
|
12
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
13
|
+
from unittest.mock import patch
|
|
13
14
|
|
|
14
15
|
import pytest
|
|
15
16
|
|
|
16
17
|
from agent_cli.server.cli import _is_parakeet_model
|
|
18
|
+
from agent_cli.server.whisper.backends import audio as audio_preparation
|
|
17
19
|
from agent_cli.server.whisper.backends import nemo as backend
|
|
18
20
|
from agent_cli.server.whisper.backends.base import BackendConfig, InvalidAudioError
|
|
19
21
|
|
|
20
22
|
if TYPE_CHECKING:
|
|
23
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
21
24
|
from pathlib import Path
|
|
22
25
|
|
|
23
26
|
|
|
@@ -308,51 +311,60 @@ def test_audio_duration_seconds_returns_zero_for_non_wav(tmp_path: Path) -> None
|
|
|
308
311
|
assert backend._audio_duration_seconds(str(audio_path)) == 0.0
|
|
309
312
|
|
|
310
313
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
) -> None:
|
|
314
|
-
"""Valid WAV uploads should not require FFmpeg conversion."""
|
|
315
|
-
audio = _create_test_wav()
|
|
316
|
-
monkeypatch.setattr(
|
|
317
|
-
backend,
|
|
318
|
-
"convert_audio_to_wav_format",
|
|
319
|
-
lambda *_args, **_kwargs: pytest.fail("unexpected conversion"),
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
assert backend._prepare_audio_for_nemo(audio, "sample.wav") == audio
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
def test_prepare_audio_for_nemo_converts_non_wav(
|
|
326
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
327
|
-
) -> None:
|
|
314
|
+
@pytest.mark.asyncio
|
|
315
|
+
async def test_transcribe_converts_non_wav_upload(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
328
316
|
"""Non-WAV uploads should be converted to a WAV container before NeMo sees them."""
|
|
329
317
|
converted = _create_test_wav()
|
|
330
318
|
calls: dict[str, object] = {}
|
|
331
319
|
|
|
332
|
-
def fake_convert(audio: bytes, source_filename: str) -> bytes:
|
|
320
|
+
async def fake_convert(audio: bytes, source_filename: str) -> bytes:
|
|
333
321
|
calls["audio"] = audio
|
|
334
322
|
calls["source_filename"] = source_filename
|
|
335
323
|
return converted
|
|
336
324
|
|
|
337
|
-
monkeypatch.setattr(
|
|
325
|
+
monkeypatch.setattr(audio_preparation, "convert_audio_to_wav_format", fake_convert)
|
|
326
|
+
nemo_backend = backend.NemoWhisperBackend(BackendConfig(model_name="parakeet-tdt-0.6b-v2"))
|
|
327
|
+
nemo_backend._executor = cast("ProcessPoolExecutor", object())
|
|
328
|
+
dispatched: dict[str, bytes] = {}
|
|
329
|
+
|
|
330
|
+
async def mock_run_in_executor(
|
|
331
|
+
_executor: object,
|
|
332
|
+
_func: object,
|
|
333
|
+
audio_bytes: bytes,
|
|
334
|
+
_kwargs: dict[str, object],
|
|
335
|
+
) -> dict[str, Any]:
|
|
336
|
+
dispatched["audio"] = audio_bytes
|
|
337
|
+
return {
|
|
338
|
+
"text": "hello",
|
|
339
|
+
"language": "en",
|
|
340
|
+
"language_probability": 1.0,
|
|
341
|
+
"duration": 0.01,
|
|
342
|
+
"segments": [],
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
with patch("asyncio.get_running_loop") as mock_loop:
|
|
346
|
+
mock_loop.return_value.run_in_executor = mock_run_in_executor
|
|
347
|
+
result = await nemo_backend.transcribe(b"ID3", source_filename="sample.mp3")
|
|
338
348
|
|
|
339
|
-
assert
|
|
349
|
+
assert result.text == "hello"
|
|
350
|
+
assert dispatched["audio"] == converted
|
|
340
351
|
assert calls == {"audio": b"ID3", "source_filename": "sample.mp3"}
|
|
341
352
|
|
|
342
353
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
) -> None:
|
|
354
|
+
@pytest.mark.asyncio
|
|
355
|
+
async def test_transcribe_raises_invalid_audio_error(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
346
356
|
"""Conversion failures should become API-visible invalid-audio errors."""
|
|
347
357
|
|
|
348
|
-
def fake_convert(audio: bytes, source_filename: str) -> bytes: # noqa: ARG001
|
|
358
|
+
async def fake_convert(audio: bytes, source_filename: str) -> bytes: # noqa: ARG001
|
|
349
359
|
msg = "ffmpeg failed"
|
|
350
360
|
raise RuntimeError(msg)
|
|
351
361
|
|
|
352
|
-
monkeypatch.setattr(
|
|
362
|
+
monkeypatch.setattr(audio_preparation, "convert_audio_to_wav_format", fake_convert)
|
|
363
|
+
nemo_backend = backend.NemoWhisperBackend(BackendConfig(model_name="parakeet-tdt-0.6b-v2"))
|
|
364
|
+
nemo_backend._executor = cast("ProcessPoolExecutor", object())
|
|
353
365
|
|
|
354
366
|
with pytest.raises(InvalidAudioError, match="Unsupported audio format for NeMo"):
|
|
355
|
-
|
|
367
|
+
await nemo_backend.transcribe(b"not audio", source_filename="sample.mp3")
|
|
356
368
|
|
|
357
369
|
|
|
358
370
|
def test_build_transcribe_kwargs_omits_language_for_plain_asr_models() -> None:
|
|
@@ -20,7 +20,7 @@ from agent_cli.server.cli import (
|
|
|
20
20
|
)
|
|
21
21
|
from agent_cli.server.model_manager import ModelStats
|
|
22
22
|
from agent_cli.server.whisper.backends import TranscriptionResult
|
|
23
|
-
from agent_cli.server.whisper.backends.base import UnsupportedRequestError
|
|
23
|
+
from agent_cli.server.whisper.backends.base import InvalidAudioError, UnsupportedRequestError
|
|
24
24
|
from agent_cli.server.whisper.model_manager import (
|
|
25
25
|
WhisperModelConfig as ModelConfig,
|
|
26
26
|
)
|
|
@@ -602,6 +602,62 @@ class TestWhisperAPI:
|
|
|
602
602
|
assert data["status"] == "healthy"
|
|
603
603
|
assert data["models"] == []
|
|
604
604
|
|
|
605
|
+
def test_transcribe_forwards_upload_filename_to_backend(
|
|
606
|
+
self,
|
|
607
|
+
client: TestClient,
|
|
608
|
+
mock_registry: WhisperModelRegistry,
|
|
609
|
+
) -> None:
|
|
610
|
+
"""The upload filename is the demuxer hint backends need to convert non-WAV audio."""
|
|
611
|
+
manager = mock_registry.get_manager()
|
|
612
|
+
with patch.object(
|
|
613
|
+
manager,
|
|
614
|
+
"transcribe",
|
|
615
|
+
new_callable=AsyncMock,
|
|
616
|
+
return_value=TranscriptionResult(
|
|
617
|
+
text="Hello world",
|
|
618
|
+
language="en",
|
|
619
|
+
language_probability=0.95,
|
|
620
|
+
duration=1.5,
|
|
621
|
+
segments=[],
|
|
622
|
+
),
|
|
623
|
+
) as mock_transcribe:
|
|
624
|
+
response = client.post(
|
|
625
|
+
"/v1/audio/transcriptions",
|
|
626
|
+
files={"file": ("voice.m4a", b"\x00\x00\x00 ftypM4A ", "audio/mp4")},
|
|
627
|
+
data={"model": "whisper-1"},
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
assert response.status_code == 200
|
|
631
|
+
assert response.json() == {"text": "Hello world"}
|
|
632
|
+
assert mock_transcribe.await_args is not None
|
|
633
|
+
assert mock_transcribe.await_args.kwargs["source_filename"] == "voice.m4a"
|
|
634
|
+
|
|
635
|
+
def test_transcribe_undecodable_audio_returns_400(
|
|
636
|
+
self,
|
|
637
|
+
client: TestClient,
|
|
638
|
+
mock_registry: WhisperModelRegistry,
|
|
639
|
+
) -> None:
|
|
640
|
+
"""Undecodable audio is an actionable 400, never a 200 or a raw parser traceback."""
|
|
641
|
+
manager = mock_registry.get_manager()
|
|
642
|
+
with patch.object(
|
|
643
|
+
manager,
|
|
644
|
+
"transcribe",
|
|
645
|
+
new_callable=AsyncMock,
|
|
646
|
+
side_effect=InvalidAudioError(
|
|
647
|
+
"Unsupported audio format for faster-whisper. "
|
|
648
|
+
"Provide a WAV file or install ffmpeg to convert uploads.",
|
|
649
|
+
),
|
|
650
|
+
):
|
|
651
|
+
response = client.post(
|
|
652
|
+
"/v1/audio/transcriptions",
|
|
653
|
+
files={"file": ("voice.ogg", b"OggS\x00\x02", "audio/ogg")},
|
|
654
|
+
data={"model": "whisper-1"},
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
assert response.status_code == 400
|
|
658
|
+
assert "Unsupported audio format" in response.json()["detail"]
|
|
659
|
+
assert "install ffmpeg" in response.json()["detail"]
|
|
660
|
+
|
|
605
661
|
def test_transcribe_empty_audio_returns_400(self, client: TestClient) -> None:
|
|
606
662
|
"""Test that empty audio file returns 400 error."""
|
|
607
663
|
response = client.post(
|