agent-cli 0.70.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. agent_cli/__init__.py +5 -0
  2. agent_cli/__main__.py +6 -0
  3. agent_cli/_extras.json +14 -0
  4. agent_cli/_requirements/.gitkeep +0 -0
  5. agent_cli/_requirements/audio.txt +79 -0
  6. agent_cli/_requirements/faster-whisper.txt +215 -0
  7. agent_cli/_requirements/kokoro.txt +425 -0
  8. agent_cli/_requirements/llm.txt +183 -0
  9. agent_cli/_requirements/memory.txt +355 -0
  10. agent_cli/_requirements/mlx-whisper.txt +222 -0
  11. agent_cli/_requirements/piper.txt +176 -0
  12. agent_cli/_requirements/rag.txt +402 -0
  13. agent_cli/_requirements/server.txt +154 -0
  14. agent_cli/_requirements/speed.txt +77 -0
  15. agent_cli/_requirements/vad.txt +155 -0
  16. agent_cli/_requirements/wyoming.txt +71 -0
  17. agent_cli/_tools.py +368 -0
  18. agent_cli/agents/__init__.py +23 -0
  19. agent_cli/agents/_voice_agent_common.py +136 -0
  20. agent_cli/agents/assistant.py +383 -0
  21. agent_cli/agents/autocorrect.py +284 -0
  22. agent_cli/agents/chat.py +496 -0
  23. agent_cli/agents/memory/__init__.py +31 -0
  24. agent_cli/agents/memory/add.py +190 -0
  25. agent_cli/agents/memory/proxy.py +160 -0
  26. agent_cli/agents/rag_proxy.py +128 -0
  27. agent_cli/agents/speak.py +209 -0
  28. agent_cli/agents/transcribe.py +671 -0
  29. agent_cli/agents/transcribe_daemon.py +499 -0
  30. agent_cli/agents/voice_edit.py +291 -0
  31. agent_cli/api.py +22 -0
  32. agent_cli/cli.py +106 -0
  33. agent_cli/config.py +503 -0
  34. agent_cli/config_cmd.py +307 -0
  35. agent_cli/constants.py +27 -0
  36. agent_cli/core/__init__.py +1 -0
  37. agent_cli/core/audio.py +461 -0
  38. agent_cli/core/audio_format.py +299 -0
  39. agent_cli/core/chroma.py +88 -0
  40. agent_cli/core/deps.py +191 -0
  41. agent_cli/core/openai_proxy.py +139 -0
  42. agent_cli/core/process.py +195 -0
  43. agent_cli/core/reranker.py +120 -0
  44. agent_cli/core/sse.py +87 -0
  45. agent_cli/core/transcription_logger.py +70 -0
  46. agent_cli/core/utils.py +526 -0
  47. agent_cli/core/vad.py +175 -0
  48. agent_cli/core/watch.py +65 -0
  49. agent_cli/dev/__init__.py +14 -0
  50. agent_cli/dev/cli.py +1588 -0
  51. agent_cli/dev/coding_agents/__init__.py +19 -0
  52. agent_cli/dev/coding_agents/aider.py +24 -0
  53. agent_cli/dev/coding_agents/base.py +167 -0
  54. agent_cli/dev/coding_agents/claude.py +39 -0
  55. agent_cli/dev/coding_agents/codex.py +24 -0
  56. agent_cli/dev/coding_agents/continue_dev.py +15 -0
  57. agent_cli/dev/coding_agents/copilot.py +24 -0
  58. agent_cli/dev/coding_agents/cursor_agent.py +48 -0
  59. agent_cli/dev/coding_agents/gemini.py +28 -0
  60. agent_cli/dev/coding_agents/opencode.py +15 -0
  61. agent_cli/dev/coding_agents/registry.py +49 -0
  62. agent_cli/dev/editors/__init__.py +19 -0
  63. agent_cli/dev/editors/base.py +89 -0
  64. agent_cli/dev/editors/cursor.py +15 -0
  65. agent_cli/dev/editors/emacs.py +46 -0
  66. agent_cli/dev/editors/jetbrains.py +56 -0
  67. agent_cli/dev/editors/nano.py +31 -0
  68. agent_cli/dev/editors/neovim.py +33 -0
  69. agent_cli/dev/editors/registry.py +59 -0
  70. agent_cli/dev/editors/sublime.py +20 -0
  71. agent_cli/dev/editors/vim.py +42 -0
  72. agent_cli/dev/editors/vscode.py +15 -0
  73. agent_cli/dev/editors/zed.py +20 -0
  74. agent_cli/dev/project.py +568 -0
  75. agent_cli/dev/registry.py +52 -0
  76. agent_cli/dev/skill/SKILL.md +141 -0
  77. agent_cli/dev/skill/examples.md +571 -0
  78. agent_cli/dev/terminals/__init__.py +19 -0
  79. agent_cli/dev/terminals/apple_terminal.py +82 -0
  80. agent_cli/dev/terminals/base.py +56 -0
  81. agent_cli/dev/terminals/gnome.py +51 -0
  82. agent_cli/dev/terminals/iterm2.py +84 -0
  83. agent_cli/dev/terminals/kitty.py +77 -0
  84. agent_cli/dev/terminals/registry.py +48 -0
  85. agent_cli/dev/terminals/tmux.py +58 -0
  86. agent_cli/dev/terminals/warp.py +132 -0
  87. agent_cli/dev/terminals/zellij.py +78 -0
  88. agent_cli/dev/worktree.py +856 -0
  89. agent_cli/docs_gen.py +417 -0
  90. agent_cli/example-config.toml +185 -0
  91. agent_cli/install/__init__.py +5 -0
  92. agent_cli/install/common.py +89 -0
  93. agent_cli/install/extras.py +174 -0
  94. agent_cli/install/hotkeys.py +48 -0
  95. agent_cli/install/services.py +87 -0
  96. agent_cli/memory/__init__.py +7 -0
  97. agent_cli/memory/_files.py +250 -0
  98. agent_cli/memory/_filters.py +63 -0
  99. agent_cli/memory/_git.py +157 -0
  100. agent_cli/memory/_indexer.py +142 -0
  101. agent_cli/memory/_ingest.py +408 -0
  102. agent_cli/memory/_persistence.py +182 -0
  103. agent_cli/memory/_prompt.py +91 -0
  104. agent_cli/memory/_retrieval.py +294 -0
  105. agent_cli/memory/_store.py +169 -0
  106. agent_cli/memory/_streaming.py +44 -0
  107. agent_cli/memory/_tasks.py +48 -0
  108. agent_cli/memory/api.py +113 -0
  109. agent_cli/memory/client.py +272 -0
  110. agent_cli/memory/engine.py +361 -0
  111. agent_cli/memory/entities.py +43 -0
  112. agent_cli/memory/models.py +112 -0
  113. agent_cli/opts.py +433 -0
  114. agent_cli/py.typed +0 -0
  115. agent_cli/rag/__init__.py +3 -0
  116. agent_cli/rag/_indexer.py +67 -0
  117. agent_cli/rag/_indexing.py +226 -0
  118. agent_cli/rag/_prompt.py +30 -0
  119. agent_cli/rag/_retriever.py +156 -0
  120. agent_cli/rag/_store.py +48 -0
  121. agent_cli/rag/_utils.py +218 -0
  122. agent_cli/rag/api.py +175 -0
  123. agent_cli/rag/client.py +299 -0
  124. agent_cli/rag/engine.py +302 -0
  125. agent_cli/rag/models.py +55 -0
  126. agent_cli/scripts/.runtime/.gitkeep +0 -0
  127. agent_cli/scripts/__init__.py +1 -0
  128. agent_cli/scripts/check_plugin_skill_sync.py +50 -0
  129. agent_cli/scripts/linux-hotkeys/README.md +63 -0
  130. agent_cli/scripts/linux-hotkeys/toggle-autocorrect.sh +45 -0
  131. agent_cli/scripts/linux-hotkeys/toggle-transcription.sh +58 -0
  132. agent_cli/scripts/linux-hotkeys/toggle-voice-edit.sh +58 -0
  133. agent_cli/scripts/macos-hotkeys/README.md +45 -0
  134. agent_cli/scripts/macos-hotkeys/skhd-config-example +5 -0
  135. agent_cli/scripts/macos-hotkeys/toggle-autocorrect.sh +12 -0
  136. agent_cli/scripts/macos-hotkeys/toggle-transcription.sh +37 -0
  137. agent_cli/scripts/macos-hotkeys/toggle-voice-edit.sh +37 -0
  138. agent_cli/scripts/nvidia-asr-server/README.md +99 -0
  139. agent_cli/scripts/nvidia-asr-server/pyproject.toml +27 -0
  140. agent_cli/scripts/nvidia-asr-server/server.py +255 -0
  141. agent_cli/scripts/nvidia-asr-server/shell.nix +32 -0
  142. agent_cli/scripts/nvidia-asr-server/uv.lock +4654 -0
  143. agent_cli/scripts/run-openwakeword.sh +11 -0
  144. agent_cli/scripts/run-piper-windows.ps1 +30 -0
  145. agent_cli/scripts/run-piper.sh +24 -0
  146. agent_cli/scripts/run-whisper-linux.sh +40 -0
  147. agent_cli/scripts/run-whisper-macos.sh +6 -0
  148. agent_cli/scripts/run-whisper-windows.ps1 +51 -0
  149. agent_cli/scripts/run-whisper.sh +9 -0
  150. agent_cli/scripts/run_faster_whisper_server.py +136 -0
  151. agent_cli/scripts/setup-linux-hotkeys.sh +72 -0
  152. agent_cli/scripts/setup-linux.sh +108 -0
  153. agent_cli/scripts/setup-macos-hotkeys.sh +61 -0
  154. agent_cli/scripts/setup-macos.sh +76 -0
  155. agent_cli/scripts/setup-windows.ps1 +63 -0
  156. agent_cli/scripts/start-all-services-windows.ps1 +53 -0
  157. agent_cli/scripts/start-all-services.sh +178 -0
  158. agent_cli/scripts/sync_extras.py +138 -0
  159. agent_cli/server/__init__.py +3 -0
  160. agent_cli/server/cli.py +721 -0
  161. agent_cli/server/common.py +222 -0
  162. agent_cli/server/model_manager.py +288 -0
  163. agent_cli/server/model_registry.py +225 -0
  164. agent_cli/server/proxy/__init__.py +3 -0
  165. agent_cli/server/proxy/api.py +444 -0
  166. agent_cli/server/streaming.py +67 -0
  167. agent_cli/server/tts/__init__.py +3 -0
  168. agent_cli/server/tts/api.py +335 -0
  169. agent_cli/server/tts/backends/__init__.py +82 -0
  170. agent_cli/server/tts/backends/base.py +139 -0
  171. agent_cli/server/tts/backends/kokoro.py +403 -0
  172. agent_cli/server/tts/backends/piper.py +253 -0
  173. agent_cli/server/tts/model_manager.py +201 -0
  174. agent_cli/server/tts/model_registry.py +28 -0
  175. agent_cli/server/tts/wyoming_handler.py +249 -0
  176. agent_cli/server/whisper/__init__.py +3 -0
  177. agent_cli/server/whisper/api.py +413 -0
  178. agent_cli/server/whisper/backends/__init__.py +89 -0
  179. agent_cli/server/whisper/backends/base.py +97 -0
  180. agent_cli/server/whisper/backends/faster_whisper.py +225 -0
  181. agent_cli/server/whisper/backends/mlx.py +270 -0
  182. agent_cli/server/whisper/languages.py +116 -0
  183. agent_cli/server/whisper/model_manager.py +157 -0
  184. agent_cli/server/whisper/model_registry.py +28 -0
  185. agent_cli/server/whisper/wyoming_handler.py +203 -0
  186. agent_cli/services/__init__.py +343 -0
  187. agent_cli/services/_wyoming_utils.py +64 -0
  188. agent_cli/services/asr.py +506 -0
  189. agent_cli/services/llm.py +228 -0
  190. agent_cli/services/tts.py +450 -0
  191. agent_cli/services/wake_word.py +142 -0
  192. agent_cli-0.70.5.dist-info/METADATA +2118 -0
  193. agent_cli-0.70.5.dist-info/RECORD +196 -0
  194. agent_cli-0.70.5.dist-info/WHEEL +4 -0
  195. agent_cli-0.70.5.dist-info/entry_points.txt +4 -0
  196. agent_cli-0.70.5.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,77 @@
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv export --extra speed --no-dev --no-emit-project --no-hashes
3
+ annotated-types==0.7.0
4
+ # via pydantic
5
+ anyio==4.12.1
6
+ # via httpx
7
+ audiostretchy==1.3.5
8
+ # via agent-cli
9
+ certifi==2026.1.4
10
+ # via
11
+ # httpcore
12
+ # httpx
13
+ click==8.3.1
14
+ # via
15
+ # typer
16
+ # typer-slim
17
+ colorama==0.4.6 ; sys_platform == 'win32'
18
+ # via click
19
+ dotenv==0.9.9
20
+ # via agent-cli
21
+ fire==0.7.1
22
+ # via audiostretchy
23
+ h11==0.16.0
24
+ # via httpcore
25
+ httpcore==1.0.9
26
+ # via httpx
27
+ httpx==0.28.1
28
+ # via agent-cli
29
+ idna==3.11
30
+ # via
31
+ # anyio
32
+ # httpx
33
+ markdown-it-py==4.0.0
34
+ # via rich
35
+ mdurl==0.1.2
36
+ # via markdown-it-py
37
+ numpy==2.3.5
38
+ # via audiostretchy
39
+ psutil==7.2.1 ; sys_platform == 'win32'
40
+ # via agent-cli
41
+ pydantic==2.12.5
42
+ # via agent-cli
43
+ pydantic-core==2.41.5
44
+ # via pydantic
45
+ pygments==2.19.2
46
+ # via rich
47
+ pyperclip==1.11.0
48
+ # via agent-cli
49
+ python-dotenv==1.2.1
50
+ # via dotenv
51
+ rich==14.2.0
52
+ # via
53
+ # agent-cli
54
+ # typer
55
+ # typer-slim
56
+ setproctitle==1.3.7
57
+ # via agent-cli
58
+ shellingham==1.5.4
59
+ # via
60
+ # typer
61
+ # typer-slim
62
+ termcolor==3.3.0
63
+ # via fire
64
+ typer==0.21.1
65
+ # via agent-cli
66
+ typer-slim==0.21.1
67
+ # via agent-cli
68
+ typing-extensions==4.15.0
69
+ # via
70
+ # anyio
71
+ # pydantic
72
+ # pydantic-core
73
+ # typer
74
+ # typer-slim
75
+ # typing-inspection
76
+ typing-inspection==0.4.2
77
+ # via pydantic
@@ -0,0 +1,155 @@
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv export --extra vad --no-dev --no-emit-project --no-hashes
3
+ annotated-types==0.7.0
4
+ # via pydantic
5
+ anyio==4.12.1
6
+ # via httpx
7
+ certifi==2026.1.4
8
+ # via
9
+ # httpcore
10
+ # httpx
11
+ click==8.3.1
12
+ # via
13
+ # typer
14
+ # typer-slim
15
+ colorama==0.4.6 ; sys_platform == 'win32'
16
+ # via click
17
+ coloredlogs==15.0.1
18
+ # via onnxruntime
19
+ dotenv==0.9.9
20
+ # via agent-cli
21
+ filelock==3.20.3
22
+ # via torch
23
+ flatbuffers==25.12.19
24
+ # via onnxruntime
25
+ fsspec==2026.1.0
26
+ # via torch
27
+ h11==0.16.0
28
+ # via httpcore
29
+ httpcore==1.0.9
30
+ # via httpx
31
+ httpx==0.28.1
32
+ # via agent-cli
33
+ humanfriendly==10.0
34
+ # via coloredlogs
35
+ idna==3.11
36
+ # via
37
+ # anyio
38
+ # httpx
39
+ jinja2==3.1.6
40
+ # via torch
41
+ markdown-it-py==4.0.0
42
+ # via rich
43
+ markupsafe==3.0.3
44
+ # via jinja2
45
+ mdurl==0.1.2
46
+ # via markdown-it-py
47
+ mpmath==1.3.0
48
+ # via sympy
49
+ networkx==3.6.1
50
+ # via torch
51
+ numpy==2.3.5
52
+ # via onnxruntime
53
+ nvidia-cublas-cu12==12.8.4.1 ; platform_machine == 'x86_64' and sys_platform == 'linux'
54
+ # via
55
+ # nvidia-cudnn-cu12
56
+ # nvidia-cusolver-cu12
57
+ # torch
58
+ nvidia-cuda-cupti-cu12==12.8.90 ; platform_machine == 'x86_64' and sys_platform == 'linux'
59
+ # via torch
60
+ nvidia-cuda-nvrtc-cu12==12.8.93 ; platform_machine == 'x86_64' and sys_platform == 'linux'
61
+ # via torch
62
+ nvidia-cuda-runtime-cu12==12.8.90 ; platform_machine == 'x86_64' and sys_platform == 'linux'
63
+ # via torch
64
+ nvidia-cudnn-cu12==9.10.2.21 ; platform_machine == 'x86_64' and sys_platform == 'linux'
65
+ # via torch
66
+ nvidia-cufft-cu12==11.3.3.83 ; platform_machine == 'x86_64' and sys_platform == 'linux'
67
+ # via torch
68
+ nvidia-cufile-cu12==1.13.1.3 ; platform_machine == 'x86_64' and sys_platform == 'linux'
69
+ # via torch
70
+ nvidia-curand-cu12==10.3.9.90 ; platform_machine == 'x86_64' and sys_platform == 'linux'
71
+ # via torch
72
+ nvidia-cusolver-cu12==11.7.3.90 ; platform_machine == 'x86_64' and sys_platform == 'linux'
73
+ # via torch
74
+ nvidia-cusparse-cu12==12.5.8.93 ; platform_machine == 'x86_64' and sys_platform == 'linux'
75
+ # via
76
+ # nvidia-cusolver-cu12
77
+ # torch
78
+ nvidia-cusparselt-cu12==0.7.1 ; platform_machine == 'x86_64' and sys_platform == 'linux'
79
+ # via torch
80
+ nvidia-nccl-cu12==2.27.5 ; platform_machine == 'x86_64' and sys_platform == 'linux'
81
+ # via torch
82
+ nvidia-nvjitlink-cu12==12.8.93 ; platform_machine == 'x86_64' and sys_platform == 'linux'
83
+ # via
84
+ # nvidia-cufft-cu12
85
+ # nvidia-cusolver-cu12
86
+ # nvidia-cusparse-cu12
87
+ # torch
88
+ nvidia-nvshmem-cu12==3.3.20 ; platform_machine == 'x86_64' and sys_platform == 'linux'
89
+ # via torch
90
+ nvidia-nvtx-cu12==12.8.90 ; platform_machine == 'x86_64' and sys_platform == 'linux'
91
+ # via torch
92
+ onnxruntime==1.20.1
93
+ # via silero-vad
94
+ packaging==25.0
95
+ # via
96
+ # onnxruntime
97
+ # silero-vad
98
+ protobuf==6.33.4
99
+ # via onnxruntime
100
+ psutil==7.2.1 ; sys_platform == 'win32'
101
+ # via agent-cli
102
+ pydantic==2.12.5
103
+ # via agent-cli
104
+ pydantic-core==2.41.5
105
+ # via pydantic
106
+ pygments==2.19.2
107
+ # via rich
108
+ pyperclip==1.11.0
109
+ # via agent-cli
110
+ pyreadline3==3.5.4 ; sys_platform == 'win32'
111
+ # via humanfriendly
112
+ python-dotenv==1.2.1
113
+ # via dotenv
114
+ rich==14.2.0
115
+ # via
116
+ # agent-cli
117
+ # typer
118
+ # typer-slim
119
+ setproctitle==1.3.7
120
+ # via agent-cli
121
+ setuptools==80.9.0 ; python_full_version >= '3.12'
122
+ # via torch
123
+ shellingham==1.5.4
124
+ # via
125
+ # typer
126
+ # typer-slim
127
+ silero-vad==6.2.0
128
+ # via agent-cli
129
+ sympy==1.14.0
130
+ # via
131
+ # onnxruntime
132
+ # torch
133
+ torch==2.9.1
134
+ # via
135
+ # silero-vad
136
+ # torchaudio
137
+ torchaudio==2.9.1
138
+ # via silero-vad
139
+ triton==3.5.1 ; platform_machine == 'x86_64' and sys_platform == 'linux'
140
+ # via torch
141
+ typer==0.21.1
142
+ # via agent-cli
143
+ typer-slim==0.21.1
144
+ # via agent-cli
145
+ typing-extensions==4.15.0
146
+ # via
147
+ # anyio
148
+ # pydantic
149
+ # pydantic-core
150
+ # torch
151
+ # typer
152
+ # typer-slim
153
+ # typing-inspection
154
+ typing-inspection==0.4.2
155
+ # via pydantic
@@ -0,0 +1,71 @@
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv export --extra wyoming --no-dev --no-emit-project --no-hashes
3
+ annotated-types==0.7.0
4
+ # via pydantic
5
+ anyio==4.12.1
6
+ # via httpx
7
+ certifi==2026.1.4
8
+ # via
9
+ # httpcore
10
+ # httpx
11
+ click==8.3.1
12
+ # via
13
+ # typer
14
+ # typer-slim
15
+ colorama==0.4.6 ; sys_platform == 'win32'
16
+ # via click
17
+ dotenv==0.9.9
18
+ # via agent-cli
19
+ h11==0.16.0
20
+ # via httpcore
21
+ httpcore==1.0.9
22
+ # via httpx
23
+ httpx==0.28.1
24
+ # via agent-cli
25
+ idna==3.11
26
+ # via
27
+ # anyio
28
+ # httpx
29
+ markdown-it-py==4.0.0
30
+ # via rich
31
+ mdurl==0.1.2
32
+ # via markdown-it-py
33
+ psutil==7.2.1 ; sys_platform == 'win32'
34
+ # via agent-cli
35
+ pydantic==2.12.5
36
+ # via agent-cli
37
+ pydantic-core==2.41.5
38
+ # via pydantic
39
+ pygments==2.19.2
40
+ # via rich
41
+ pyperclip==1.11.0
42
+ # via agent-cli
43
+ python-dotenv==1.2.1
44
+ # via dotenv
45
+ rich==14.2.0
46
+ # via
47
+ # agent-cli
48
+ # typer
49
+ # typer-slim
50
+ setproctitle==1.3.7
51
+ # via agent-cli
52
+ shellingham==1.5.4
53
+ # via
54
+ # typer
55
+ # typer-slim
56
+ typer==0.21.1
57
+ # via agent-cli
58
+ typer-slim==0.21.1
59
+ # via agent-cli
60
+ typing-extensions==4.15.0
61
+ # via
62
+ # anyio
63
+ # pydantic
64
+ # pydantic-core
65
+ # typer
66
+ # typer-slim
67
+ # typing-inspection
68
+ typing-inspection==0.4.2
69
+ # via pydantic
70
+ wyoming==1.8.0
71
+ # via agent-cli
agent_cli/_tools.py ADDED
@@ -0,0 +1,368 @@
1
+ """Tool definitions for the chat agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import subprocess
8
+ from datetime import UTC, datetime
9
+ from pathlib import Path
10
+ from typing import TYPE_CHECKING, Any, TypeVar
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Callable
14
+
15
+
16
+ # Memory system helpers
17
+
18
+
19
+ def _get_memory_file_path() -> Path:
20
+ """Get the path to the memory file.
21
+
22
+ If the environment variable ``AGENT_CLI_HISTORY_DIR`` is set (by the
23
+ running agent), store the memory file in that directory.
24
+ Otherwise fall back to the user's config directory.
25
+ """
26
+ history_dir = os.getenv("AGENT_CLI_HISTORY_DIR")
27
+ if history_dir:
28
+ return Path(history_dir).expanduser() / "long_term_memory.json"
29
+
30
+ return Path.home() / ".config" / "agent-cli" / "memory" / "long_term_memory.json"
31
+
32
+
33
+ def _load_memories() -> list[dict[str, Any]]:
34
+ """Load memories from file, returning empty list if file doesn't exist."""
35
+ memory_file = _get_memory_file_path()
36
+ if not memory_file.exists():
37
+ return []
38
+
39
+ with memory_file.open("r") as f:
40
+ return json.load(f)
41
+
42
+
43
+ def _save_memories(memories: list[dict[str, Any]]) -> None:
44
+ """Save memories to file, creating directories if needed."""
45
+ memory_file = _get_memory_file_path()
46
+ memory_file.parent.mkdir(parents=True, exist_ok=True)
47
+
48
+ with memory_file.open("w") as f:
49
+ json.dump(memories, f, indent=2)
50
+
51
+
52
+ def _find_memory_by_id(memories: list[dict[str, Any]], memory_id: int) -> dict[str, Any] | None:
53
+ """Find a memory by ID in the memories list."""
54
+ for memory in memories:
55
+ if memory["id"] == memory_id:
56
+ return memory
57
+ return None
58
+
59
+
60
+ def _format_memory_summary(memory: dict[str, Any]) -> str:
61
+ """Format a memory for display in search results."""
62
+ return (
63
+ f"ID: {memory['id']} | Category: {memory['category']} | "
64
+ f"Content: {memory['content']} | Tags: {', '.join(memory['tags'])}"
65
+ )
66
+
67
+
68
+ def _format_memory_detailed(memory: dict[str, Any]) -> str:
69
+ """Format a memory with full details for listing."""
70
+ created = datetime.fromisoformat(memory["timestamp"]).strftime("%Y-%m-%d %H:%M")
71
+ updated_info = ""
72
+ if "updated_at" in memory:
73
+ updated = datetime.fromisoformat(memory["updated_at"]).strftime("%Y-%m-%d %H:%M")
74
+ updated_info = f" (updated: {updated})"
75
+
76
+ return (
77
+ f"ID: {memory['id']} | Category: {memory['category']}\n"
78
+ f"Content: {memory['content']}\n"
79
+ f"Tags: {', '.join(memory['tags']) if memory['tags'] else 'None'}\n"
80
+ f"Created: {created}{updated_info}\n"
81
+ )
82
+
83
+
84
+ def _parse_tags(tags_string: str) -> list[str]:
85
+ """Parse comma-separated tags string into a list of clean tags."""
86
+ return [tag.strip() for tag in tags_string.split(",") if tag.strip()]
87
+
88
+
89
+ R = TypeVar("R")
90
+
91
+
92
+ def _memory_operation(operation_name: str, operation_func: Callable[[], str]) -> str:
93
+ """Wrapper for memory operations with consistent error handling."""
94
+ try:
95
+ return operation_func()
96
+ except Exception as e:
97
+ return f"Error {operation_name}: {e}"
98
+
99
+
100
+ def read_file(path: str) -> str:
101
+ """Read the content of a file.
102
+
103
+ Args:
104
+ path: The path to the file to read.
105
+
106
+ """
107
+ try:
108
+ return Path(path).read_text()
109
+ except FileNotFoundError:
110
+ return f"Error: File not found at {path}"
111
+ except OSError as e:
112
+ return f"Error reading file: {e}"
113
+
114
+
115
+ def execute_code(code: str) -> str:
116
+ """Execute a shell command.
117
+
118
+ Args:
119
+ code: The shell command to execute.
120
+
121
+ """
122
+ try:
123
+ result = subprocess.run(
124
+ code.split(),
125
+ capture_output=True,
126
+ text=True,
127
+ check=True,
128
+ )
129
+ return result.stdout
130
+ except subprocess.CalledProcessError as e:
131
+ return f"Error executing code: {e.stderr}"
132
+ except FileNotFoundError:
133
+ return f"Error: Command not found: {code.split()[0]}"
134
+
135
+
136
+ def add_memory(content: str, category: str = "general", tags: str = "") -> str:
137
+ """Add important information to long-term memory for future conversations.
138
+
139
+ Use this when the user shares:
140
+ - Personal information (name, job, location, family, etc.)
141
+ - Preferences (favorite foods, work style, communication preferences, etc.)
142
+ - Important facts they want remembered (birthdays, project details, goals, etc.)
143
+ - Tasks or commitments they mention
144
+
145
+ Always ask for permission before storing personal or sensitive information.
146
+
147
+ Args:
148
+ content: The specific information to remember (be descriptive and clear)
149
+ category: Type of memory - use "personal", "preferences", "facts", "tasks", "projects", or "general"
150
+ tags: Comma-separated keywords that would help find this memory later (e.g., "work, python, programming")
151
+
152
+ Returns:
153
+ Confirmation message with the memory ID
154
+
155
+ """
156
+
157
+ def _add_memory_operation() -> str:
158
+ memories = _load_memories()
159
+
160
+ memory = {
161
+ "id": len(memories) + 1,
162
+ "content": content,
163
+ "category": category,
164
+ "tags": _parse_tags(tags),
165
+ "timestamp": datetime.now(UTC).isoformat(),
166
+ }
167
+
168
+ memories.append(memory)
169
+ _save_memories(memories)
170
+
171
+ return f"Memory added successfully with ID {memory['id']}"
172
+
173
+ return _memory_operation("adding memory", _add_memory_operation)
174
+
175
+
176
+ def search_memory(query: str, category: str = "") -> str:
177
+ """Search long-term memory for relevant information before answering questions.
178
+
179
+ Use this tool:
180
+ - Before answering questions about the user's preferences, personal info, or past conversations
181
+ - When the user asks "what do you remember about..." or similar questions
182
+ - When you need context about the user's work, projects, or goals
183
+ - To check if you've discussed a topic before
184
+
185
+ The search looks through memory content and tags for matches.
186
+
187
+ Args:
188
+ query: Keywords to search for (e.g., "programming languages", "work schedule", "preferences")
189
+ category: Optional filter by category ("personal", "preferences", "facts", "tasks", "projects")
190
+
191
+ Returns:
192
+ Relevant memories found, or message if none found
193
+
194
+ """
195
+
196
+ def _search_memory_operation() -> str:
197
+ memories = _load_memories()
198
+
199
+ if not memories:
200
+ return "No memories found. Memory system not initialized."
201
+
202
+ # Simple text-based search
203
+ query_lower = query.lower()
204
+ relevant_memories = []
205
+
206
+ for memory in memories:
207
+ # Check if query matches content, tags, or category
208
+ content_match = query_lower in memory["content"].lower()
209
+ tag_match = any(query_lower in tag.lower() for tag in memory["tags"])
210
+ category_match = not category or memory["category"].lower() == category.lower()
211
+
212
+ if (content_match or tag_match) and category_match:
213
+ relevant_memories.append(memory)
214
+
215
+ if not relevant_memories:
216
+ return f"No memories found matching '{query}'"
217
+
218
+ # Format results
219
+ results = [_format_memory_summary(memory) for memory in relevant_memories[-5:]]
220
+
221
+ return "\n".join(results)
222
+
223
+ return _memory_operation("searching memory", _search_memory_operation)
224
+
225
+
226
+ def update_memory(memory_id: int, content: str = "", category: str = "", tags: str = "") -> str:
227
+ """Update an existing memory by ID.
228
+
229
+ Use this tool:
230
+ - When the user wants to correct or modify previously stored information
231
+ - When information has changed (e.g., job change, preference updates)
232
+ - When the user says "update my memory about..." or "change the memory where..."
233
+
234
+ Only provide the fields that should be updated - empty fields will keep existing values.
235
+
236
+ Args:
237
+ memory_id: The ID of the memory to update (use search_memory or list_all_memories to find IDs)
238
+ content: New content for the memory (leave empty to keep existing)
239
+ category: New category (leave empty to keep existing)
240
+ tags: New comma-separated tags (leave empty to keep existing)
241
+
242
+ Returns:
243
+ Confirmation message or error if memory ID not found
244
+
245
+ """
246
+
247
+ def _update_memory_operation() -> str:
248
+ memories = _load_memories()
249
+
250
+ if not memories:
251
+ return "No memories found. Memory system not initialized."
252
+
253
+ # Find memory to update
254
+ memory_to_update = _find_memory_by_id(memories, memory_id)
255
+ if not memory_to_update:
256
+ return f"Memory with ID {memory_id} not found."
257
+
258
+ # Update fields if provided
259
+ if content:
260
+ memory_to_update["content"] = content
261
+ if category:
262
+ memory_to_update["category"] = category
263
+ if tags:
264
+ memory_to_update["tags"] = _parse_tags(tags)
265
+
266
+ # Add update timestamp
267
+ memory_to_update["updated_at"] = datetime.now(UTC).isoformat()
268
+
269
+ _save_memories(memories)
270
+ return f"Memory ID {memory_id} updated successfully."
271
+
272
+ return _memory_operation("updating memory", _update_memory_operation)
273
+
274
+
275
+ def list_all_memories(limit: int = 10) -> str:
276
+ """List all memories with their details.
277
+
278
+ Use this tool:
279
+ - When the user asks "show me all my memories" or "list everything you remember"
280
+ - When they want to see specific memory IDs for updating or reference
281
+ - To provide a complete overview of stored information
282
+
283
+ Shows memories in reverse chronological order (newest first).
284
+
285
+ Args:
286
+ limit: Maximum number of memories to show (default 10, use higher numbers if user wants more)
287
+
288
+ Returns:
289
+ Formatted list of all memories with IDs, content, categories, and tags
290
+
291
+ """
292
+
293
+ def _list_all_memories_operation() -> str:
294
+ memories = _load_memories()
295
+
296
+ if not memories:
297
+ return "No memories stored yet."
298
+
299
+ # Sort by ID (newest first) and limit results
300
+ memories_to_show = sorted(memories, key=lambda x: x["id"], reverse=True)[:limit]
301
+
302
+ results = [f"Showing {len(memories_to_show)} of {len(memories)} total memories:\n"]
303
+ results.extend(_format_memory_detailed(memory) for memory in memories_to_show)
304
+
305
+ if len(memories) > limit:
306
+ results.append(
307
+ f"... and {len(memories) - limit} more memories. Use a higher limit to see more.",
308
+ )
309
+
310
+ return "\n".join(results)
311
+
312
+ return _memory_operation("listing memories", _list_all_memories_operation)
313
+
314
+
315
+ def list_memory_categories() -> str:
316
+ """List all memory categories and their counts to see what has been remembered.
317
+
318
+ Use this tool:
319
+ - When the user asks "what categories do you have?"
320
+ - To get a quick overview of memory organization
321
+ - When the user wants to know what types of information are stored
322
+
323
+ This provides a summary view before using list_all_memories for details.
324
+
325
+ Returns:
326
+ Summary of memory categories with counts (e.g., "personal: 5 memories")
327
+
328
+ """
329
+
330
+ def _list_categories_operation() -> str:
331
+ memories = _load_memories()
332
+
333
+ if not memories:
334
+ return "No memories found. Memory system not initialized."
335
+
336
+ # Count categories
337
+ categories: dict[str, int] = {}
338
+ for memory in memories:
339
+ category = memory["category"]
340
+ categories[category] = categories.get(category, 0) + 1
341
+
342
+ if not categories:
343
+ return "No memory categories found."
344
+
345
+ results = ["Memory Categories:"]
346
+ for category, count in sorted(categories.items()):
347
+ results.append(f"- {category}: {count} memories")
348
+
349
+ return "\n".join(results)
350
+
351
+ return _memory_operation("listing categories", _list_categories_operation)
352
+
353
+
354
+ def tools() -> list:
355
+ """Return a list of tools."""
356
+ from pydantic_ai.common_tools.duckduckgo import duckduckgo_search_tool # noqa: PLC0415
357
+ from pydantic_ai.tools import Tool # noqa: PLC0415
358
+
359
+ return [
360
+ Tool(read_file),
361
+ Tool(execute_code),
362
+ Tool(add_memory),
363
+ Tool(search_memory),
364
+ Tool(update_memory),
365
+ Tool(list_all_memories),
366
+ Tool(list_memory_categories),
367
+ duckduckgo_search_tool(),
368
+ ]
@@ -0,0 +1,23 @@
1
+ """Agent implementations for the Agent CLI."""
2
+
3
+ from . import (
4
+ assistant,
5
+ autocorrect,
6
+ chat,
7
+ memory,
8
+ rag_proxy,
9
+ speak,
10
+ transcribe,
11
+ voice_edit,
12
+ )
13
+
14
+ __all__ = [
15
+ "assistant",
16
+ "autocorrect",
17
+ "chat",
18
+ "memory",
19
+ "rag_proxy",
20
+ "speak",
21
+ "transcribe",
22
+ "voice_edit",
23
+ ]