agent-cli 0.105.1__tar.gz → 0.106.0__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.
Files changed (460) hide show
  1. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/workflows/docker.yml +4 -4
  2. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/workflows/pytest.yml +1 -1
  3. {agent_cli-0.105.1 → agent_cli-0.106.0}/PKG-INFO +1 -1
  4. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/transcribe.py +24 -0
  5. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/audio_format.py +17 -13
  6. agent_cli-0.106.0/agent_cli/server/whisper/backends/audio.py +44 -0
  7. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/backends/base.py +1 -1
  8. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/backends/nemo.py +6 -24
  9. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/backends/transformers.py +8 -1
  10. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/AgentCommandRunner.swift +6 -1
  11. agent_cli-0.106.0/macos/AgentCLI/Sources/AgentCLI/CommandResult.swift +17 -0
  12. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/VoiceLevelOverlay.swift +85 -28
  13. agent_cli-0.106.0/macos/AgentCLI/Tests/AgentCLITests/TranscriptionResultTests.swift +23 -0
  14. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Tests/AgentCLITests/VoiceLevelMeterTests.swift +89 -0
  15. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/appcast.xml +37 -34
  16. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_transcribe.py +2 -0
  17. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_transcribe_agent.py +61 -0
  18. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_transcribe_recovery.py +5 -1
  19. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/core/test_audio_format.py +55 -7
  20. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_faster_whisper_backend.py +59 -1
  21. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_nemo_backend.py +39 -27
  22. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_server_whisper.py +57 -1
  23. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_transformers_backend.py +101 -2
  24. agent_cli-0.106.0/tests/test_whisper_backend_audio.py +171 -0
  25. agent_cli-0.105.1/macos/AgentCLI/Sources/AgentCLI/CommandResult.swift +0 -6
  26. {agent_cli-0.105.1 → agent_cli-0.106.0}/.claude/skills/agent-cli-dev/SKILL.md +0 -0
  27. {agent_cli-0.105.1 → agent_cli-0.106.0}/.claude/skills/agent-cli-dev/examples.md +0 -0
  28. {agent_cli-0.105.1 → agent_cli-0.106.0}/.claude-plugin/README.md +0 -0
  29. {agent_cli-0.105.1 → agent_cli-0.106.0}/.claude-plugin/marketplace.json +0 -0
  30. {agent_cli-0.105.1 → agent_cli-0.106.0}/.claude-plugin/plugin.json +0 -0
  31. {agent_cli-0.105.1 → agent_cli-0.106.0}/.claude-plugin/skills/agent-cli-dev/SKILL.md +0 -0
  32. {agent_cli-0.105.1 → agent_cli-0.106.0}/.claude-plugin/skills/agent-cli-dev/examples.md +0 -0
  33. {agent_cli-0.105.1 → agent_cli-0.106.0}/.cursorrules +0 -0
  34. {agent_cli-0.105.1 → agent_cli-0.106.0}/.dockerignore +0 -0
  35. {agent_cli-0.105.1 → agent_cli-0.106.0}/.env.example +0 -0
  36. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/logo.svg +0 -0
  37. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/release-drafter.yml +0 -0
  38. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/renovate.json +0 -0
  39. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/scripts/check_extras_sync.py +0 -0
  40. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/scripts/check_plugin_skill_sync.py +0 -0
  41. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/scripts/normalize_appcast.py +0 -0
  42. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/scripts/sync_extras.py +0 -0
  43. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/scripts/sync_requirements.py +0 -0
  44. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/workflows/automerge.yml +0 -0
  45. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/workflows/docs.yml +0 -0
  46. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/workflows/markdown-code-runner.yml +0 -0
  47. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/workflows/release-drafter.yml +0 -0
  48. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/workflows/release.yml +0 -0
  49. {agent_cli-0.105.1 → agent_cli-0.106.0}/.github/workflows/toc.yaml +0 -0
  50. {agent_cli-0.105.1 → agent_cli-0.106.0}/.gitignore +0 -0
  51. {agent_cli-0.105.1 → agent_cli-0.106.0}/.jscpd.json +0 -0
  52. {agent_cli-0.105.1 → agent_cli-0.106.0}/.pre-commit-config.yaml +0 -0
  53. {agent_cli-0.105.1 → agent_cli-0.106.0}/.prompts/docs-review.md +0 -0
  54. {agent_cli-0.105.1 → agent_cli-0.106.0}/.prompts/pr-review.md +0 -0
  55. {agent_cli-0.105.1 → agent_cli-0.106.0}/CLAUDE.md +0 -0
  56. {agent_cli-0.105.1 → agent_cli-0.106.0}/LICENSE +0 -0
  57. {agent_cli-0.105.1 → agent_cli-0.106.0}/LLAMA_SERVER_USAGE.md +0 -0
  58. {agent_cli-0.105.1 → agent_cli-0.106.0}/README.md +0 -0
  59. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/__init__.py +0 -0
  60. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/__main__.py +0 -0
  61. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_extras.json +0 -0
  62. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_overrides/nemo-whisper.txt +0 -0
  63. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/.gitkeep +0 -0
  64. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/audio.txt +0 -0
  65. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/diarization.txt +0 -0
  66. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/faster-whisper.txt +0 -0
  67. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/kokoro.txt +0 -0
  68. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/llm.txt +0 -0
  69. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/memory.txt +0 -0
  70. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/mlx-whisper.txt +0 -0
  71. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/nemo-whisper.txt +0 -0
  72. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/piper.txt +0 -0
  73. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/rag.txt +0 -0
  74. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/server.txt +0 -0
  75. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/speed.txt +0 -0
  76. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/vad.txt +0 -0
  77. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/vectordb.txt +0 -0
  78. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/whisper-transformers.txt +0 -0
  79. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_requirements/wyoming.txt +0 -0
  80. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/_tools.py +0 -0
  81. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/__init__.py +0 -0
  82. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/_voice_agent_common.py +0 -0
  83. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/assistant.py +0 -0
  84. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/autocorrect.py +0 -0
  85. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/chat.py +0 -0
  86. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/diarize_live_session.py +0 -0
  87. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/memory/__init__.py +0 -0
  88. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/memory/add.py +0 -0
  89. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/memory/proxy.py +0 -0
  90. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/rag_proxy.py +0 -0
  91. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/speak.py +0 -0
  92. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/speakers.py +0 -0
  93. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/transcribe_live.py +0 -0
  94. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/agents/voice_edit.py +0 -0
  95. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/api.py +0 -0
  96. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/cli.py +0 -0
  97. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/config.py +0 -0
  98. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/config_cmd.py +0 -0
  99. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/constants.py +0 -0
  100. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/__init__.py +0 -0
  101. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/alignment.py +0 -0
  102. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/audio.py +0 -0
  103. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/chroma.py +0 -0
  104. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/deps.py +0 -0
  105. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/diarization.py +0 -0
  106. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/openai_proxy.py +0 -0
  107. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/process.py +0 -0
  108. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/reranker.py +0 -0
  109. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/speaker_identity.py +0 -0
  110. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/sse.py +0 -0
  111. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/transcription_logger.py +0 -0
  112. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/utils.py +0 -0
  113. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/vad.py +0 -0
  114. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/core/watch.py +0 -0
  115. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/daemon/__init__.py +0 -0
  116. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/daemon/cli.py +0 -0
  117. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/__init__.py +0 -0
  118. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/_branch_name.py +0 -0
  119. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/_config.py +0 -0
  120. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/_output.py +0 -0
  121. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/cleanup.py +0 -0
  122. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/cli.py +0 -0
  123. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/__init__.py +0 -0
  124. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/aider.py +0 -0
  125. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/base.py +0 -0
  126. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/claude.py +0 -0
  127. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/codex.py +0 -0
  128. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/continue_dev.py +0 -0
  129. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/copilot.py +0 -0
  130. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/cursor_agent.py +0 -0
  131. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/gemini.py +0 -0
  132. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/opencode.py +0 -0
  133. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/coding_agents/registry.py +0 -0
  134. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/__init__.py +0 -0
  135. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/base.py +0 -0
  136. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/cursor.py +0 -0
  137. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/emacs.py +0 -0
  138. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/jetbrains.py +0 -0
  139. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/nano.py +0 -0
  140. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/neovim.py +0 -0
  141. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/registry.py +0 -0
  142. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/sublime.py +0 -0
  143. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/vim.py +0 -0
  144. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/vscode.py +0 -0
  145. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/editors/zed.py +0 -0
  146. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/hooks.py +0 -0
  147. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/launch.py +0 -0
  148. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/project.py +0 -0
  149. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/registry.py +0 -0
  150. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/skill/SKILL.md +0 -0
  151. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/skill/examples.md +0 -0
  152. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/__init__.py +0 -0
  153. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/apple_terminal.py +0 -0
  154. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/base.py +0 -0
  155. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/cmux.py +0 -0
  156. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/gnome.py +0 -0
  157. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/iterm2.py +0 -0
  158. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/kitty.py +0 -0
  159. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/registry.py +0 -0
  160. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/tmux.py +0 -0
  161. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/warp.py +0 -0
  162. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/terminals/zellij.py +0 -0
  163. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/dev/worktree.py +0 -0
  164. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/docs_gen.py +0 -0
  165. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/example-config.toml +0 -0
  166. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/install/__init__.py +0 -0
  167. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/install/common.py +0 -0
  168. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/install/extras.py +0 -0
  169. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/install/hotkeys.py +0 -0
  170. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/install/launchd.py +0 -0
  171. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/install/service_config.py +0 -0
  172. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/install/services.py +0 -0
  173. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/install/systemd.py +0 -0
  174. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/__init__.py +0 -0
  175. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_files.py +0 -0
  176. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_filters.py +0 -0
  177. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_git.py +0 -0
  178. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_indexer.py +0 -0
  179. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_ingest.py +0 -0
  180. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_persistence.py +0 -0
  181. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_prompt.py +0 -0
  182. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_retrieval.py +0 -0
  183. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_store.py +0 -0
  184. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_streaming.py +0 -0
  185. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/_tasks.py +0 -0
  186. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/api.py +0 -0
  187. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/client.py +0 -0
  188. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/engine.py +0 -0
  189. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/entities.py +0 -0
  190. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/memory/models.py +0 -0
  191. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/opts.py +0 -0
  192. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/py.typed +0 -0
  193. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/__init__.py +0 -0
  194. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/_indexer.py +0 -0
  195. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/_indexing.py +0 -0
  196. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/_prompt.py +0 -0
  197. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/_retriever.py +0 -0
  198. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/_store.py +0 -0
  199. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/_utils.py +0 -0
  200. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/api.py +0 -0
  201. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/client.py +0 -0
  202. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/engine.py +0 -0
  203. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/rag/models.py +0 -0
  204. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/.runtime/.gitkeep +0 -0
  205. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/__init__.py +0 -0
  206. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/linux-hotkeys/README.md +0 -0
  207. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/linux-hotkeys/toggle-autocorrect.sh +0 -0
  208. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/linux-hotkeys/toggle-transcription.sh +0 -0
  209. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/linux-hotkeys/toggle-voice-edit.sh +0 -0
  210. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/macos-hotkeys/README.md +0 -0
  211. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/macos-hotkeys/skhd-config-example +0 -0
  212. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/macos-hotkeys/toggle-autocorrect.sh +0 -0
  213. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/macos-hotkeys/toggle-transcription.sh +0 -0
  214. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/macos-hotkeys/toggle-voice-edit.sh +0 -0
  215. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/nvidia-asr-server/README.md +0 -0
  216. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/nvidia-asr-server/pyproject.toml +0 -0
  217. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/nvidia-asr-server/server.py +0 -0
  218. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/nvidia-asr-server/shell.nix +0 -0
  219. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/nvidia-asr-server/uv.lock +0 -0
  220. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/run-openwakeword.sh +0 -0
  221. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/setup-linux-hotkeys.sh +0 -0
  222. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/setup-linux.sh +0 -0
  223. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/setup-macos-hotkeys.sh +0 -0
  224. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/setup-macos.sh +0 -0
  225. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/setup-windows.ps1 +0 -0
  226. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/start-all-services-windows.ps1 +0 -0
  227. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/scripts/start-all-services.sh +0 -0
  228. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/__init__.py +0 -0
  229. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/cli.py +0 -0
  230. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/common.py +0 -0
  231. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/model_manager.py +0 -0
  232. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/model_registry.py +0 -0
  233. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/proxy/__init__.py +0 -0
  234. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/proxy/api.py +0 -0
  235. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/proxy/diarization.py +0 -0
  236. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/streaming.py +0 -0
  237. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/tts/__init__.py +0 -0
  238. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/tts/api.py +0 -0
  239. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/tts/backends/__init__.py +0 -0
  240. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/tts/backends/base.py +0 -0
  241. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/tts/backends/kokoro.py +0 -0
  242. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/tts/backends/piper.py +0 -0
  243. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/tts/model_manager.py +0 -0
  244. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/tts/model_registry.py +0 -0
  245. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/tts/wyoming_handler.py +0 -0
  246. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/__init__.py +0 -0
  247. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/api.py +0 -0
  248. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/backends/__init__.py +0 -0
  249. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/backends/faster_whisper.py +0 -0
  250. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/backends/mlx.py +0 -0
  251. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/languages.py +0 -0
  252. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/model_manager.py +0 -0
  253. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/model_registry.py +0 -0
  254. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/server/whisper/wyoming_handler.py +0 -0
  255. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/services/__init__.py +0 -0
  256. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/services/_wyoming_utils.py +0 -0
  257. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/services/asr.py +0 -0
  258. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/services/llm.py +0 -0
  259. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/services/tts.py +0 -0
  260. {agent_cli-0.105.1 → agent_cli-0.106.0}/agent_cli/services/wake_word.py +0 -0
  261. {agent_cli-0.105.1 → agent_cli-0.106.0}/docker/diarization.Dockerfile +0 -0
  262. {agent_cli-0.105.1 → agent_cli-0.106.0}/docker/docker-compose.yml +0 -0
  263. {agent_cli-0.105.1 → agent_cli-0.106.0}/docker/memory-proxy.Dockerfile +0 -0
  264. {agent_cli-0.105.1 → agent_cli-0.106.0}/docker/rag-proxy.Dockerfile +0 -0
  265. {agent_cli-0.105.1 → agent_cli-0.106.0}/docker/transcribe-proxy.Dockerfile +0 -0
  266. {agent_cli-0.105.1 → agent_cli-0.106.0}/docker/tts.Dockerfile +0 -0
  267. {agent_cli-0.105.1 → agent_cli-0.106.0}/docker/whisper.Dockerfile +0 -0
  268. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/CNAME +0 -0
  269. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/architecture/index.md +0 -0
  270. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/architecture/memory.md +0 -0
  271. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/architecture/rag.md +0 -0
  272. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/assistant.md +0 -0
  273. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/autocorrect.md +0 -0
  274. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/chat.md +0 -0
  275. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/config.md +0 -0
  276. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/daemon.md +0 -0
  277. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/dev.md +0 -0
  278. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/diarize-live-session.md +0 -0
  279. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/index.md +0 -0
  280. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/install-extras.md +0 -0
  281. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/install-hotkeys.md +0 -0
  282. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/install-services.md +0 -0
  283. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/memory.md +0 -0
  284. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/rag-proxy.md +0 -0
  285. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/server/index.md +0 -0
  286. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/server/transcribe-proxy.md +0 -0
  287. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/server/tts.md +0 -0
  288. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/server/whisper.md +0 -0
  289. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/speak.md +0 -0
  290. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/speakers.md +0 -0
  291. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/start-services.md +0 -0
  292. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/transcribe-live.md +0 -0
  293. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/transcribe.md +0 -0
  294. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/commands/voice-edit.md +0 -0
  295. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/configuration.md +0 -0
  296. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/getting-started.md +0 -0
  297. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/iOS_Shortcut_Guide.md +0 -0
  298. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/index.md +0 -0
  299. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/installation/docker.md +0 -0
  300. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/installation/index.md +0 -0
  301. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/installation/linux.md +0 -0
  302. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/installation/macos-app.md +0 -0
  303. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/installation/macos.md +0 -0
  304. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/installation/nixos.md +0 -0
  305. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/installation/windows.md +0 -0
  306. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/logo-avatar.svg +0 -0
  307. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/logo-clean.svg +0 -0
  308. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/overrides/partials/integrations/analytics/custom.html +0 -0
  309. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/run_markdown_code_runner.py +0 -0
  310. {agent_cli-0.105.1 → agent_cli-0.106.0}/docs/system-integration.md +0 -0
  311. {agent_cli-0.105.1 → agent_cli-0.106.0}/example.agent-cli-config.toml +0 -0
  312. {agent_cli-0.105.1 → agent_cli-0.106.0}/justfile +0 -0
  313. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Package.resolved +0 -0
  314. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Package.swift +0 -0
  315. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/README.md +0 -0
  316. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Resources/AgentCLI.entitlements +0 -0
  317. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Resources/Info.plist +0 -0
  318. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Resources/dmg-background.svg +0 -0
  319. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/AgentCLIApp.swift +0 -0
  320. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/AgentCommand.swift +0 -0
  321. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/AgentRuntime.swift +0 -0
  322. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/AppDelegate.swift +0 -0
  323. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/AppMetadata.swift +0 -0
  324. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/AppUpdater.swift +0 -0
  325. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/BootstrapState.swift +0 -0
  326. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/ConfigurableHotkeyController.swift +0 -0
  327. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/FocusedTextTarget.swift +0 -0
  328. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/LiveTranscriptionPreview.swift +0 -0
  329. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/LoginItemController.swift +0 -0
  330. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/MenuActivityStatus.swift +0 -0
  331. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/MenuActivityStatusRow.swift +0 -0
  332. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/MenuActivityTracker.swift +0 -0
  333. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/MenuBarIcon.swift +0 -0
  334. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/RecentTranscriptionReader.swift +0 -0
  335. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/RecordingIndicatorController.swift +0 -0
  336. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/RecordingSoundSettings.swift +0 -0
  337. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/RuntimeSettings.swift +0 -0
  338. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/SettingsWindowController.swift +0 -0
  339. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/Shortcuts.swift +0 -0
  340. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/StatusMenuController.swift +0 -0
  341. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/TranscriptPasteController.swift +0 -0
  342. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Sources/AgentCLI/TranscriptionSettings.swift +0 -0
  343. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Tests/AgentCLITests/AgentCommandTests.swift +0 -0
  344. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Tests/AgentCLITests/ConfigurableHotkeyControllerTests.swift +0 -0
  345. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Tests/AgentCLITests/LivePreviewSwiftTestingTests.swift +0 -0
  346. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Tests/AgentCLITests/LiveTranscriptionPreviewTests.swift +0 -0
  347. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Tests/AgentCLITests/LoginItemControllerTests.swift +0 -0
  348. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Tests/AgentCLITests/RecentTranscriptionReaderTests.swift +0 -0
  349. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Tests/AgentCLITests/RecordingIndicatorControllerTests.swift +0 -0
  350. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/AgentCLI/Tests/AgentCLITests/ShortcutDefaultsTests.swift +0 -0
  351. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/build-macos-app.sh +0 -0
  352. {agent_cli-0.105.1 → agent_cli-0.106.0}/macos/test-macos-app-e2e.sh +0 -0
  353. {agent_cli-0.105.1 → agent_cli-0.106.0}/pyproject.toml +0 -0
  354. {agent_cli-0.105.1 → agent_cli-0.106.0}/reddit.md +0 -0
  355. {agent_cli-0.105.1 → agent_cli-0.106.0}/shell.nix +0 -0
  356. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/__init__.py +0 -0
  357. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/__init__.py +0 -0
  358. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_fix_my_text.py +0 -0
  359. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_interactive.py +0 -0
  360. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_interactive_extra.py +0 -0
  361. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_memory_add.py +0 -0
  362. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_speak.py +0 -0
  363. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_speak_e2e.py +0 -0
  364. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_speakers.py +0 -0
  365. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_transcribe_e2e.py +0 -0
  366. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_transcribe_live.py +0 -0
  367. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_tts_common.py +0 -0
  368. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_tts_common_extra.py +0 -0
  369. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_voice_agent_common.py +0 -0
  370. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_voice_edit.py +0 -0
  371. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_voice_edit_e2e.py +0 -0
  372. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/agents/test_wake_word_assistant.py +0 -0
  373. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/conftest.py +0 -0
  374. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/core/__init__.py +0 -0
  375. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/core/test_audio.py +0 -0
  376. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/core/test_audio_levels.py +0 -0
  377. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/core/test_chroma.py +0 -0
  378. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/core/test_sse.py +0 -0
  379. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/core/test_vad.py +0 -0
  380. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/core/test_watch.py +0 -0
  381. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/__init__.py +0 -0
  382. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/test_cleanup.py +0 -0
  383. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/test_cli.py +0 -0
  384. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/test_coding_agents.py +0 -0
  385. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/test_editors.py +0 -0
  386. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/test_hooks.py +0 -0
  387. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/test_launch.py +0 -0
  388. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/test_project.py +0 -0
  389. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/test_terminals.py +0 -0
  390. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/test_verification.py +0 -0
  391. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/dev/test_worktree.py +0 -0
  392. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/install/__init__.py +0 -0
  393. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/install/test_extras.py +0 -0
  394. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/install/test_launchd.py +0 -0
  395. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/__init__.py +0 -0
  396. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_api_health.py +0 -0
  397. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_api_integration_liveish.py +0 -0
  398. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_client.py +0 -0
  399. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_engine.py +0 -0
  400. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_files.py +0 -0
  401. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_filters.py +0 -0
  402. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_git_integration.py +0 -0
  403. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_indexer.py +0 -0
  404. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_memory_integration.py +0 -0
  405. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_proxy_passthrough.py +0 -0
  406. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_store.py +0 -0
  407. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/memory/test_utils.py +0 -0
  408. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/mocks/__init__.py +0 -0
  409. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/mocks/audio.py +0 -0
  410. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/mocks/llm.py +0 -0
  411. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/mocks/wyoming.py +0 -0
  412. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/__init__.py +0 -0
  413. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_api.py +0 -0
  414. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_engine.py +0 -0
  415. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_history.py +0 -0
  416. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_indexer.py +0 -0
  417. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_indexing.py +0 -0
  418. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_rag_client.py +0 -0
  419. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_rag_integration_liveish.py +0 -0
  420. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_rag_proxy_passthrough.py +0 -0
  421. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_retriever.py +0 -0
  422. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_store.py +0 -0
  423. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/rag/test_utils.py +0 -0
  424. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_alignment.py +0 -0
  425. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_api.py +0 -0
  426. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_api_integration.py +0 -0
  427. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_asr.py +0 -0
  428. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_asr_recovery.py +0 -0
  429. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_audio_e2e.py +0 -0
  430. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_cli.py +0 -0
  431. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_config.py +0 -0
  432. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_config_cmd.py +0 -0
  433. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_daemon.py +0 -0
  434. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_diarization.py +0 -0
  435. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_diarize_live_session.py +0 -0
  436. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_docs_gen.py +0 -0
  437. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_env_vars.py +0 -0
  438. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_json_output.py +0 -0
  439. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_llm.py +0 -0
  440. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_llm_gemini.py +0 -0
  441. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_macos_app.py +0 -0
  442. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_memory_tools.py +0 -0
  443. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_mlx_backend.py +0 -0
  444. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_normalize_appcast.py +0 -0
  445. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_process_manager.py +0 -0
  446. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_proxy_diarization.py +0 -0
  447. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_requires_extras.py +0 -0
  448. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_server_streaming.py +0 -0
  449. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_server_tts.py +0 -0
  450. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_server_tts_wyoming.py +0 -0
  451. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_server_whisper_wyoming.py +0 -0
  452. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_services.py +0 -0
  453. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_speaker_identity.py +0 -0
  454. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_tools.py +0 -0
  455. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_tts.py +0 -0
  456. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_utils.py +0 -0
  457. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_wake_word.py +0 -0
  458. {agent_cli-0.105.1 → agent_cli-0.106.0}/tests/test_wyoming_utils.py +0 -0
  459. {agent_cli-0.105.1 → agent_cli-0.106.0}/uv.lock +0 -0
  460. {agent_cli-0.105.1 → agent_cli-0.106.0}/zensical.toml +0 -0
@@ -49,7 +49,7 @@ jobs:
49
49
  fetch-depth: 0 # Full history for version detection
50
50
 
51
51
  - name: Set up Docker Buildx
52
- uses: docker/setup-buildx-action@v4.3.0
52
+ uses: docker/setup-buildx-action@v4.4.1
53
53
 
54
54
  - name: Log in to Container Registry
55
55
  uses: docker/login-action@v4.5.1
@@ -79,7 +79,7 @@ jobs:
79
79
  type=sha,suffix=-${{ matrix.suffix }}
80
80
 
81
81
  - name: Build and push
82
- uses: docker/build-push-action@v7.3.0
82
+ uses: docker/build-push-action@v7.4.0
83
83
  with:
84
84
  context: .
85
85
  file: ${{ matrix.dockerfile }}
@@ -122,7 +122,7 @@ jobs:
122
122
  fetch-depth: 0 # Full history for version detection
123
123
 
124
124
  - name: Set up Docker Buildx
125
- uses: docker/setup-buildx-action@v4.3.0
125
+ uses: docker/setup-buildx-action@v4.4.1
126
126
 
127
127
  - name: Log in to Container Registry
128
128
  uses: docker/login-action@v4.5.1
@@ -152,7 +152,7 @@ jobs:
152
152
  type=sha
153
153
 
154
154
  - name: Build and push
155
- uses: docker/build-push-action@v7.3.0
155
+ uses: docker/build-push-action@v7.4.0
156
156
  with:
157
157
  context: .
158
158
  file: ${{ matrix.dockerfile }}
@@ -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.0.0
46
+ uses: codecov/codecov-action@v7.1.1
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.1
3
+ Version: 0.106.0
4
4
  Summary: A suite of AI-powered command-line tools for text correction, audio transcription, and voice assistance.
5
5
  Project-URL: Homepage, https://github.com/basnijholt/agent-cli
6
6
  Author-email: Bas Nijholt <bas@nijho.lt>
@@ -477,6 +477,9 @@ async def _async_main( # noqa: PLR0912, PLR0915, C901
477
477
  live_preview_config=live_preview_config,
478
478
  )
479
479
 
480
+ if transcript and not transcript.strip():
481
+ transcript = None
482
+
480
483
  elapsed = time.monotonic() - start_time
481
484
 
482
485
  # Apply diarization if enabled
@@ -554,6 +557,9 @@ async def _async_main( # noqa: PLR0912, PLR0915, C901
554
557
  live=live,
555
558
  context=combined_context,
556
559
  )
560
+ if general_cfg.clipboard and not (processed_transcript or "").strip():
561
+ # Keep the raw speech recoverable if cleanup overwrote it with blank text.
562
+ pyperclip.copy(transcript)
557
563
 
558
564
  # Log transcription if requested
559
565
  if transcription_log:
@@ -615,6 +621,21 @@ async def _async_main( # noqa: PLR0912, PLR0915, C901
615
621
  )
616
622
 
617
623
 
624
+ def _require_transcript(result: TranscriptResult, provider: str) -> None:
625
+ """Do not report successful dictation when the backend produced no text."""
626
+ if not (result.get("transcript") or "").strip():
627
+ if result.get("raw_transcript"):
628
+ message = "LLM cleanup returned no text. Retry with --no-llm to use the raw transcript."
629
+ else:
630
+ message = f"No transcript returned by {provider}. Check the microphone and ASR server."
631
+ typer.echo(
632
+ f"{message} "
633
+ "If a recording was saved, retry with agent-cli transcribe --last-recording.",
634
+ err=True,
635
+ )
636
+ raise typer.Exit(1)
637
+
638
+
618
639
  @app.command("transcribe", rich_help_panel="Voice Commands")
619
640
  @requires_extras("audio", "llm", process_name="transcribe")
620
641
  def transcribe( # noqa: PLR0912, PLR0911, PLR0915, C901
@@ -896,6 +917,7 @@ def transcribe( # noqa: PLR0912, PLR0911, PLR0915, C901
896
917
  LOGGER.exception("Diarization failed")
897
918
  _print_diarization_error(exc)
898
919
  raise typer.Exit(1) from None
920
+ _require_transcript(result, asr_provider)
899
921
  if json_output:
900
922
  print(json.dumps(result))
901
923
  return
@@ -937,6 +959,7 @@ def transcribe( # noqa: PLR0912, PLR0911, PLR0915, C901
937
959
  ):
938
960
  return
939
961
 
962
+ result = TranscriptResult(raw_transcript=None, transcript=None, llm_enabled=False)
940
963
  # Use context manager before audio setup so --stop can target startup reliably.
941
964
  try:
942
965
  with process.pid_file_context(process_name), suppress(KeyboardInterrupt):
@@ -992,5 +1015,6 @@ def transcribe( # noqa: PLR0912, PLR0911, PLR0915, C901
992
1015
  LOGGER.exception("Diarization failed")
993
1016
  _print_diarization_error(exc)
994
1017
  raise typer.Exit(1) from None
1018
+ _require_transcript(result, asr_provider)
995
1019
  if json_output:
996
1020
  print(json.dumps(result))
@@ -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 audio data to a 16kHz mono 16-bit PCM WAV container using FFmpeg."""
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
- result = subprocess.run(
190
- cmd,
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 result.returncode != 0:
201
- stderr_text = result.stderr.decode("utf-8", errors="replace")
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: Audio data as bytes (WAV format, 16kHz, 16-bit, mono)
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 asyncio.to_thread(_prepare_audio_for_nemo, audio, source_filename)
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, # noqa: ARG002
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)
@@ -252,7 +252,12 @@ final class AgentCommandRunner: ObservableObject {
252
252
  }
253
253
  }
254
254
 
255
- let result = AgentRuntime.shared.runAgentCLI(arguments: commandArguments)
255
+ let commandResult = AgentRuntime.shared.runAgentCLI(arguments: commandArguments)
256
+ // A stop acknowledgement may be empty; a completed recording must contain text.
257
+ // Validate here too because user-installed CLIs can predate the CLI-side check.
258
+ let result = shouldStartRecording && command.identifier == AgentCommand.toggleTranscription.identifier
259
+ ? commandResult.requiringTranscript()
260
+ : commandResult
256
261
  let message = Self.statusMessage(for: command, result: result)
257
262
  let notificationTitle = Self.notificationTitle(for: command, result: result)
258
263
  let notificationBody = Self.notificationBody(for: command, result: result, statusMessage: message)
@@ -0,0 +1,17 @@
1
+ import Foundation
2
+
3
+ struct CommandResult {
4
+ let exitCode: Int32
5
+ let output: String
6
+
7
+ func requiringTranscript() -> CommandResult {
8
+ guard exitCode == 0, output.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
9
+ return self
10
+ }
11
+ return CommandResult(
12
+ exitCode: 1,
13
+ output: "No transcript returned. Check the microphone and ASR server, then try again. "
14
+ + "If a recording was saved, recover it with agent-cli transcribe --last-recording."
15
+ )
16
+ }
17
+ }
@@ -3,7 +3,7 @@ import Foundation
3
3
  import SwiftUI
4
4
 
5
5
  enum VoiceLevelOverlayLayout {
6
- static let pillSize = CGSize(width: 147, height: 38)
6
+ static let pillSize = CGSize(width: 190, height: 38)
7
7
  static let textWidth = CGFloat(420)
8
8
  static let textHeight = CGFloat(86)
9
9
  static let shadowRadius = CGFloat(13)
@@ -72,41 +72,57 @@ struct VoiceLevelOverlayView: View {
72
72
  height: VoiceLevelOverlayLayout.panelSize(showsPreviewSpace: showsPreviewSpace).height,
73
73
  alignment: .bottom
74
74
  )
75
- .accessibilityLabel(Text("Voice level"))
75
+ .accessibilityElement(children: .ignore)
76
+ .accessibilityLabel(Text(meter.captureState == .recording
77
+ ? "Recording \(meter.recordingDuration)"
78
+ : meter.captureState.label))
79
+ .accessibilityValue(Text(showsPreviewSpace ? preview.text : ""))
76
80
  }
77
81
 
78
82
  private var levelMeter: some View {
79
- HStack(alignment: .center, spacing: 3.5) {
83
+ HStack(spacing: 8) {
84
+ if meter.captureState == .recording {
85
+ Text(meter.recordingDuration)
86
+ .font(.system(size: 12, weight: .semibold))
87
+ .monospacedDigit()
88
+ waveform
89
+ } else {
90
+ ProgressView()
91
+ .controlSize(.small)
92
+ Text(meter.captureState.label)
93
+ .font(.system(size: 12, weight: .semibold))
94
+ }
95
+ }
96
+ .foregroundStyle(statusColor)
97
+ .frame(width: VoiceLevelOverlayLayout.pillSize.width, height: VoiceLevelOverlayLayout.pillSize.height)
98
+ .background(Capsule().fill(statusColor.opacity(isLightMode ? 0.10 : 0.14)))
99
+ .background(Capsule().fill(backgroundColor))
100
+ .overlay(Capsule().stroke(statusColor.opacity(0.55), lineWidth: 1))
101
+ .shadow(
102
+ color: shadowColor,
103
+ radius: VoiceLevelOverlayLayout.shadowRadius,
104
+ y: VoiceLevelOverlayLayout.shadowYOffset
105
+ )
106
+ }
107
+
108
+ private var waveform: some View {
109
+ HStack(alignment: .center, spacing: 2) {
80
110
  ForEach(Array(meter.amplitudes.enumerated()), id: \.offset) { _, amplitude in
81
111
  Capsule()
82
112
  .fill(
83
113
  LinearGradient(
84
114
  colors: [
85
- barGradientStart,
86
- barGradientEnd
115
+ statusColor.opacity(0.65),
116
+ statusColor
87
117
  ],
88
118
  startPoint: .bottom,
89
119
  endPoint: .top
90
120
  )
91
121
  )
92
- .frame(width: 3.5, height: max(5, 25 * amplitude))
122
+ .frame(width: 2, height: max(5, 25 * amplitude))
93
123
  .animation(.easeOut(duration: 0.11), value: amplitude)
94
124
  }
95
125
  }
96
- .frame(width: VoiceLevelOverlayLayout.pillSize.width, height: VoiceLevelOverlayLayout.pillSize.height)
97
- .background(
98
- Capsule()
99
- .fill(backgroundColor)
100
- )
101
- .overlay(
102
- Capsule()
103
- .stroke(borderColor, lineWidth: 1)
104
- )
105
- .shadow(
106
- color: shadowColor,
107
- radius: VoiceLevelOverlayLayout.shadowRadius,
108
- y: VoiceLevelOverlayLayout.shadowYOffset
109
- )
110
126
  }
111
127
 
112
128
  private var isLightMode: Bool {
@@ -133,12 +149,15 @@ struct VoiceLevelOverlayView: View {
133
149
  Color.black.opacity(isLightMode ? 0.16 : 0.24)
134
150
  }
135
151
 
136
- private var barGradientStart: Color {
137
- isLightMode ? Color(red: 0.04, green: 0.45, blue: 0.95) : Color(red: 0.18, green: 0.82, blue: 0.92)
138
- }
139
-
140
- private var barGradientEnd: Color {
141
- isLightMode ? Color(red: 0.07, green: 0.72, blue: 0.68) : Color(red: 0.64, green: 0.96, blue: 0.58)
152
+ private var statusColor: Color {
153
+ if meter.captureState == .recording {
154
+ return isLightMode
155
+ ? Color(red: 0.04, green: 0.40, blue: 0.18)
156
+ : Color(red: 0.42, green: 0.91, blue: 0.58)
157
+ }
158
+ return isLightMode
159
+ ? Color(red: 0.62, green: 0.34, blue: 0.0)
160
+ : Color(red: 1.0, green: 0.74, blue: 0.25)
142
161
  }
143
162
  }
144
163
 
@@ -306,10 +325,28 @@ enum VoiceLevelLog {
306
325
  }()
307
326
  }
308
327
 
328
+ enum VoiceCaptureState {
329
+ case idle
330
+ case connecting
331
+ case recording
332
+ case waitingForAudio
333
+
334
+ var label: String {
335
+ switch self {
336
+ case .idle: return "Ready"
337
+ case .connecting: return "Connecting…"
338
+ case .recording: return "Recording"
339
+ case .waitingForAudio: return "Waiting for audio…"
340
+ }
341
+ }
342
+ }
343
+
309
344
  final class VoiceLevelMeter: ObservableObject {
310
345
  static let shared = VoiceLevelMeter()
311
346
 
312
347
  @Published private(set) var amplitudes = VoiceLevelMeter.idleAmplitudes
348
+ @Published private(set) var captureState = VoiceCaptureState.idle
349
+ @Published private(set) var recordingDuration = "00:00"
313
350
 
314
351
  private static let barCount = 16
315
352
  private static let idleAmplitudes = Array(repeating: CGFloat(0.16), count: barCount)
@@ -319,6 +356,7 @@ final class VoiceLevelMeter: ObservableObject {
319
356
  private let levelLogURL: URL
320
357
  private let now: () -> Date
321
358
  private var timer: Timer?
359
+ private var recordingStartedAt: Date?
322
360
  private var phase = 0.0
323
361
  private var smoothedLevel = CGFloat(0.16)
324
362
 
@@ -330,6 +368,9 @@ final class VoiceLevelMeter: ObservableObject {
330
368
  func start() {
331
369
  guard timer == nil else { return }
332
370
  VoiceLevelLog.reset(levelLogURL)
371
+ captureState = .connecting
372
+ recordingStartedAt = nil
373
+ recordingDuration = "00:00"
333
374
  phase = 0
334
375
  smoothedLevel = Self.idleLevel
335
376
  amplitudes = Self.idleAmplitudes
@@ -345,13 +386,29 @@ final class VoiceLevelMeter: ObservableObject {
345
386
  func stop() {
346
387
  timer?.invalidate()
347
388
  timer = nil
389
+ captureState = .idle
390
+ recordingStartedAt = nil
391
+ recordingDuration = "00:00"
348
392
  phase = 0
349
393
  smoothedLevel = Self.idleLevel
350
394
  amplitudes = Self.idleAmplitudes
351
395
  }
352
396
 
353
- private func pollLevel() {
354
- let level = VoiceLevelLog.latestLevel(from: levelLogURL, now: now()) ?? Self.idleLevel
397
+ func pollLevel() {
398
+ guard timer != nil else { return }
399
+ let now = now()
400
+ guard let level = VoiceLevelLog.latestLevel(from: levelLogURL, now: now) else {
401
+ if captureState == .recording {
402
+ captureState = .waitingForAudio
403
+ }
404
+ amplitudes = Self.idleAmplitudes
405
+ return
406
+ }
407
+ captureState = .recording
408
+ let startedAt = recordingStartedAt ?? now
409
+ recordingStartedAt = startedAt
410
+ let elapsedSeconds = max(0, Int(now.timeIntervalSince(startedAt)))
411
+ recordingDuration = String(format: "%02d:%02d", elapsedSeconds / 60, elapsedSeconds % 60)
355
412
  updateDisplay(level: level)
356
413
  }
357
414
 
@@ -0,0 +1,23 @@
1
+ #if canImport(XCTest)
2
+ import XCTest
3
+ @testable import AgentCLI
4
+
5
+ final class TranscriptionResultTests: XCTestCase {
6
+ func testEmptySuccessfulRecordingBecomesFailure() {
7
+ for output in ["", " \n\t"] {
8
+ let result = CommandResult(exitCode: 0, output: output).requiringTranscript()
9
+ XCTAssertNotEqual(result.exitCode, 0)
10
+ XCTAssertTrue(result.output.contains("No transcript"))
11
+ }
12
+ }
13
+
14
+ func testTranscriptAndBackendFailureArePreserved() {
15
+ let success = CommandResult(exitCode: 0, output: "Hello world").requiringTranscript()
16
+ XCTAssertEqual(success.exitCode, 0)
17
+ XCTAssertEqual(success.output, "Hello world")
18
+ let failure = CommandResult(exitCode: 7, output: "Connection lost").requiringTranscript()
19
+ XCTAssertEqual(failure.exitCode, 7)
20
+ XCTAssertEqual(failure.output, "Connection lost")
21
+ }
22
+ }
23
+ #endif
@@ -4,6 +4,95 @@ import XCTest
4
4
  @testable import AgentCLI
5
5
 
6
6
  final class VoiceLevelMeterTests: XCTestCase {
7
+ func testRecordingTimerExcludesStartupAndResetsBetweenRecordings() throws {
8
+ let logURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
9
+ var now = try XCTUnwrap(Self.iso8601.date(from: "2026-06-04T12:00:00Z"))
10
+ let meter = VoiceLevelMeter(levelLogURL: logURL, now: { now })
11
+ defer {
12
+ meter.stop()
13
+ try? FileManager.default.removeItem(at: logURL)
14
+ }
15
+ meter.start()
16
+ now = now.addingTimeInterval(10)
17
+ meter.pollLevel()
18
+ XCTAssertEqual(meter.recordingDuration, "00:00")
19
+
20
+ func publishAudio() throws {
21
+ try "{\"timestamp\":\"\(Self.iso8601.string(from: now))\",\"level\":0}"
22
+ .write(to: logURL, atomically: true, encoding: .utf8)
23
+ meter.pollLevel()
24
+ }
25
+ try publishAudio()
26
+ XCTAssertEqual(meter.recordingDuration, "00:00")
27
+ now = now.addingTimeInterval(9)
28
+ try publishAudio()
29
+ XCTAssertEqual(meter.recordingDuration, "00:09")
30
+ now = now.addingTimeInterval(56)
31
+ try publishAudio()
32
+ XCTAssertEqual(meter.recordingDuration, "01:05")
33
+ now = now.addingTimeInterval(3600)
34
+ try publishAudio()
35
+ XCTAssertEqual(meter.recordingDuration, "61:05")
36
+
37
+ meter.stop()
38
+ XCTAssertEqual(meter.recordingDuration, "00:00")
39
+ meter.start()
40
+ try publishAudio()
41
+ XCTAssertEqual(meter.recordingDuration, "00:00")
42
+ }
43
+
44
+ func testRecordingReadinessFollowsFreshAudioIncludingSilence() throws {
45
+ let logURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
46
+ var now = try XCTUnwrap(Self.iso8601.date(from: "2026-06-04T12:00:00Z"))
47
+ let meter = VoiceLevelMeter(levelLogURL: logURL, now: { now })
48
+ defer {
49
+ meter.stop()
50
+ try? FileManager.default.removeItem(at: logURL)
51
+ }
52
+
53
+ // A previous recording must not make a new request look ready.
54
+ try #"{"timestamp":"2026-06-04T12:00:00Z","level":0.9}"#
55
+ .write(to: logURL, atomically: true, encoding: .utf8)
56
+ meter.start()
57
+ XCTAssertEqual(meter.captureState, .connecting)
58
+
59
+ try #"{"timestamp":"2026-06-04T12:00:00Z","level":0}"#
60
+ .write(to: logURL, atomically: true, encoding: .utf8)
61
+ meter.pollLevel()
62
+ XCTAssertEqual(meter.captureState, .recording, "Silent audio still proves capture is running.")
63
+
64
+ now = now.addingTimeInterval(2)
65
+ meter.pollLevel()
66
+ XCTAssertEqual(meter.captureState, .waitingForAudio)
67
+
68
+ try #"{"timestamp":"2026-06-04T12:00:02Z","level":0.6}"#
69
+ .write(to: logURL, atomically: true, encoding: .utf8)
70
+ meter.pollLevel()
71
+ XCTAssertEqual(meter.captureState, .recording)
72
+
73
+ meter.stop()
74
+ meter.pollLevel()
75
+ XCTAssertEqual(meter.captureState, .idle, "Late polling must not revive a released overlay.")
76
+ meter.start()
77
+ XCTAssertEqual(meter.captureState, .connecting)
78
+ }
79
+
80
+ func testConnectingDoesNotAnimateFakeMicrophoneActivity() throws {
81
+ let logURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
82
+ let meter = VoiceLevelMeter(levelLogURL: logURL)
83
+ defer {
84
+ meter.stop()
85
+ try? FileManager.default.removeItem(at: logURL)
86
+ }
87
+ meter.start()
88
+ let initialAmplitudes = meter.amplitudes
89
+ meter.pollLevel()
90
+ XCTAssertEqual(meter.captureState, .connecting)
91
+ XCTAssertEqual(meter.amplitudes, initialAmplitudes)
92
+ meter.stop()
93
+ XCTAssertEqual(meter.captureState, .idle)
94
+ }
95
+
7
96
  func testDisplayAmplitudesUseSineWaveShape() {
8
97
  let amplitudes = VoiceLevelMeter.displayAmplitudes(level: 0.6, phase: 0.22)
9
98