tau-coding-agent 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. tau/__init__.py +0 -0
  2. tau/agent/__init__.py +11 -0
  3. tau/agent/prompt/__init__.py +10 -0
  4. tau/agent/prompt/builder.py +302 -0
  5. tau/agent/prompt/types.py +33 -0
  6. tau/agent/service.py +369 -0
  7. tau/agent/types.py +61 -0
  8. tau/auth/manager.py +247 -0
  9. tau/auth/storage.py +82 -0
  10. tau/auth/types.py +41 -0
  11. tau/builtins/__init__.py +4 -0
  12. tau/builtins/__pycache__/__init__.cpython-313.pyc +0 -0
  13. tau/builtins/__pycache__/__init__.cpython-314.pyc +0 -0
  14. tau/builtins/commands/__init__.py +41 -0
  15. tau/builtins/commands/__pycache__/__init__.cpython-313.pyc +0 -0
  16. tau/builtins/commands/__pycache__/__init__.cpython-314.pyc +0 -0
  17. tau/builtins/commands/__pycache__/clear.cpython-313.pyc +0 -0
  18. tau/builtins/commands/__pycache__/clear.cpython-314.pyc +0 -0
  19. tau/builtins/commands/__pycache__/compact.cpython-313.pyc +0 -0
  20. tau/builtins/commands/__pycache__/compact.cpython-314.pyc +0 -0
  21. tau/builtins/commands/__pycache__/reload.cpython-313.pyc +0 -0
  22. tau/builtins/commands/__pycache__/reload.cpython-314.pyc +0 -0
  23. tau/builtins/commands/__pycache__/session.cpython-313.pyc +0 -0
  24. tau/builtins/commands/__pycache__/session.cpython-314.pyc +0 -0
  25. tau/builtins/commands/clear.py +16 -0
  26. tau/builtins/commands/compact.py +28 -0
  27. tau/builtins/commands/reload.py +27 -0
  28. tau/builtins/commands/session.py +19 -0
  29. tau/builtins/extensions/footer/__init__.py +76 -0
  30. tau/builtins/extensions/footer/__pycache__/__init__.cpython-313.pyc +0 -0
  31. tau/builtins/extensions/footer/__pycache__/git.cpython-313.pyc +0 -0
  32. tau/builtins/extensions/footer/__pycache__/model.cpython-313.pyc +0 -0
  33. tau/builtins/extensions/footer/__pycache__/utils.cpython-313.pyc +0 -0
  34. tau/builtins/extensions/footer/git.py +26 -0
  35. tau/builtins/extensions/footer/model.py +69 -0
  36. tau/builtins/extensions/footer/utils.py +44 -0
  37. tau/builtins/extensions/header/__init__.py +18 -0
  38. tau/builtins/extensions/header/__pycache__/__init__.cpython-313.pyc +0 -0
  39. tau/builtins/models/__init__.py +0 -0
  40. tau/builtins/models/__pycache__/__init__.cpython-313.pyc +0 -0
  41. tau/builtins/models/__pycache__/text.cpython-313.pyc +0 -0
  42. tau/builtins/models/audio.py +43 -0
  43. tau/builtins/models/image.py +43 -0
  44. tau/builtins/models/text.py +482 -0
  45. tau/builtins/models/video.py +40 -0
  46. tau/builtins/prompts/commit.md +7 -0
  47. tau/builtins/prompts/docs.md +7 -0
  48. tau/builtins/prompts/explain.md +7 -0
  49. tau/builtins/prompts/fix.md +7 -0
  50. tau/builtins/prompts/refactor.md +7 -0
  51. tau/builtins/prompts/review.md +7 -0
  52. tau/builtins/prompts/test.md +7 -0
  53. tau/builtins/providers/__init__.py +0 -0
  54. tau/builtins/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  55. tau/builtins/providers/__pycache__/text.cpython-313.pyc +0 -0
  56. tau/builtins/providers/audio.py +10 -0
  57. tau/builtins/providers/image.py +9 -0
  58. tau/builtins/providers/text.py +33 -0
  59. tau/builtins/providers/video.py +6 -0
  60. tau/builtins/skills/code-review/SKILL.md +4 -0
  61. tau/builtins/skills/debug/SKILL.md +4 -0
  62. tau/builtins/skills/git-commit/SKILL.md +4 -0
  63. tau/builtins/themes/dark.yaml +1 -0
  64. tau/builtins/themes/light.yaml +46 -0
  65. tau/builtins/tools/__init__.py +73 -0
  66. tau/builtins/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  67. tau/builtins/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  68. tau/builtins/tools/__pycache__/bash.cpython-313.pyc +0 -0
  69. tau/builtins/tools/__pycache__/bash.cpython-314.pyc +0 -0
  70. tau/builtins/tools/__pycache__/edit.cpython-313.pyc +0 -0
  71. tau/builtins/tools/__pycache__/edit.cpython-314.pyc +0 -0
  72. tau/builtins/tools/__pycache__/glob.cpython-313.pyc +0 -0
  73. tau/builtins/tools/__pycache__/glob.cpython-314.pyc +0 -0
  74. tau/builtins/tools/__pycache__/grep.cpython-313.pyc +0 -0
  75. tau/builtins/tools/__pycache__/grep.cpython-314.pyc +0 -0
  76. tau/builtins/tools/__pycache__/ls.cpython-313.pyc +0 -0
  77. tau/builtins/tools/__pycache__/ls.cpython-314.pyc +0 -0
  78. tau/builtins/tools/__pycache__/read.cpython-313.pyc +0 -0
  79. tau/builtins/tools/__pycache__/read.cpython-314.pyc +0 -0
  80. tau/builtins/tools/__pycache__/terminal.cpython-313.pyc +0 -0
  81. tau/builtins/tools/__pycache__/terminal.cpython-314.pyc +0 -0
  82. tau/builtins/tools/__pycache__/write.cpython-313.pyc +0 -0
  83. tau/builtins/tools/__pycache__/write.cpython-314.pyc +0 -0
  84. tau/builtins/tools/edit.py +215 -0
  85. tau/builtins/tools/glob.py +112 -0
  86. tau/builtins/tools/grep.py +146 -0
  87. tau/builtins/tools/ls.py +135 -0
  88. tau/builtins/tools/read.py +122 -0
  89. tau/builtins/tools/terminal.py +150 -0
  90. tau/builtins/tools/write.py +105 -0
  91. tau/commands/__init__.py +10 -0
  92. tau/commands/registry.py +71 -0
  93. tau/commands/types.py +33 -0
  94. tau/console/__init__.py +0 -0
  95. tau/console/cli.py +266 -0
  96. tau/console/commands/__init__.py +0 -0
  97. tau/console/commands/auth.py +193 -0
  98. tau/console/commands/packages.py +104 -0
  99. tau/console/commands/update.py +76 -0
  100. tau/core/__init__.py +0 -0
  101. tau/core/registry.py +102 -0
  102. tau/engine/__init__.py +47 -0
  103. tau/engine/service.py +768 -0
  104. tau/engine/types.py +163 -0
  105. tau/extensions/__init__.py +28 -0
  106. tau/extensions/api.py +928 -0
  107. tau/extensions/context.py +462 -0
  108. tau/extensions/events.py +70 -0
  109. tau/extensions/loader.py +386 -0
  110. tau/extensions/runtime.py +184 -0
  111. tau/extensions/settings.py +137 -0
  112. tau/hooks/__init__.py +112 -0
  113. tau/hooks/engine.py +237 -0
  114. tau/hooks/inference.py +21 -0
  115. tau/hooks/runtime.py +126 -0
  116. tau/hooks/service.py +121 -0
  117. tau/hooks/session.py +117 -0
  118. tau/hooks/tui.py +61 -0
  119. tau/hooks/types.py +72 -0
  120. tau/inference/__init__.py +180 -0
  121. tau/inference/api/__init__.py +0 -0
  122. tau/inference/api/audio/__init__.py +0 -0
  123. tau/inference/api/audio/base.py +29 -0
  124. tau/inference/api/audio/builtins.py +15 -0
  125. tau/inference/api/audio/elevenlabs_audio.py +183 -0
  126. tau/inference/api/audio/gemini_audio.py +95 -0
  127. tau/inference/api/audio/openai_audio.py +159 -0
  128. tau/inference/api/audio/registry.py +15 -0
  129. tau/inference/api/audio/sarvam_audio.py +163 -0
  130. tau/inference/api/audio/service.py +103 -0
  131. tau/inference/api/audio/utils.py +47 -0
  132. tau/inference/api/image/__init__.py +0 -0
  133. tau/inference/api/image/base.py +17 -0
  134. tau/inference/api/image/builtins.py +8 -0
  135. tau/inference/api/image/gemini_image.py +77 -0
  136. tau/inference/api/image/openai_image.py +103 -0
  137. tau/inference/api/image/openrouter.py +144 -0
  138. tau/inference/api/image/registry.py +15 -0
  139. tau/inference/api/image/service.py +71 -0
  140. tau/inference/api/registry.py +82 -0
  141. tau/inference/api/text/__init__.py +0 -0
  142. tau/inference/api/text/anthropic_claude_code.py +222 -0
  143. tau/inference/api/text/anthropic_messages.py +196 -0
  144. tau/inference/api/text/base.py +40 -0
  145. tau/inference/api/text/builtins.py +19 -0
  146. tau/inference/api/text/gemini_generate.py +234 -0
  147. tau/inference/api/text/github_copilot_chat.py +172 -0
  148. tau/inference/api/text/google_antigravity.py +522 -0
  149. tau/inference/api/text/mistral_chat.py +284 -0
  150. tau/inference/api/text/ollama_chat.py +200 -0
  151. tau/inference/api/text/openai_codex_responses.py +497 -0
  152. tau/inference/api/text/openai_completions.py +227 -0
  153. tau/inference/api/text/openai_responses.py +235 -0
  154. tau/inference/api/text/registry.py +50 -0
  155. tau/inference/api/text/service.py +297 -0
  156. tau/inference/api/text/types.py +7 -0
  157. tau/inference/api/text/utils.py +228 -0
  158. tau/inference/api/video/__init__.py +0 -0
  159. tau/inference/api/video/base.py +26 -0
  160. tau/inference/api/video/builtins.py +7 -0
  161. tau/inference/api/video/fal_video.py +119 -0
  162. tau/inference/api/video/openrouter_video.py +142 -0
  163. tau/inference/api/video/registry.py +15 -0
  164. tau/inference/api/video/service.py +72 -0
  165. tau/inference/model/__init__.py +0 -0
  166. tau/inference/model/registry.py +102 -0
  167. tau/inference/model/types.py +65 -0
  168. tau/inference/provider/__init__.py +0 -0
  169. tau/inference/provider/oauth/__init__.py +35 -0
  170. tau/inference/provider/oauth/anthropic_claude_code.py +286 -0
  171. tau/inference/provider/oauth/github_copilot.py +333 -0
  172. tau/inference/provider/oauth/google_antigravity.py +258 -0
  173. tau/inference/provider/oauth/openai_codex.py +309 -0
  174. tau/inference/provider/oauth/pkce.py +14 -0
  175. tau/inference/provider/oauth/types.py +46 -0
  176. tau/inference/provider/oauth/utils.py +154 -0
  177. tau/inference/provider/registry.py +141 -0
  178. tau/inference/provider/types.py +114 -0
  179. tau/inference/types.py +549 -0
  180. tau/inference/utils.py +219 -0
  181. tau/message/__init__.py +0 -0
  182. tau/message/types.py +482 -0
  183. tau/message/utils.py +178 -0
  184. tau/packages/__init__.py +11 -0
  185. tau/packages/manager.py +190 -0
  186. tau/packages/types.py +20 -0
  187. tau/packages/utils.py +67 -0
  188. tau/prompts/expand.py +58 -0
  189. tau/prompts/loader.py +69 -0
  190. tau/prompts/registry.py +45 -0
  191. tau/prompts/types.py +24 -0
  192. tau/rpc/__init__.py +8 -0
  193. tau/rpc/mode.py +783 -0
  194. tau/rpc/types.py +252 -0
  195. tau/runtime/service.py +759 -0
  196. tau/runtime/types.py +303 -0
  197. tau/session/branch_summarization.py +312 -0
  198. tau/session/compaction.py +646 -0
  199. tau/session/manager.py +652 -0
  200. tau/session/types.py +188 -0
  201. tau/session/utils.py +233 -0
  202. tau/settings/manager.py +1077 -0
  203. tau/settings/paths.py +150 -0
  204. tau/settings/storage.py +63 -0
  205. tau/settings/types.py +173 -0
  206. tau/settings/utils.py +25 -0
  207. tau/skills/loader.py +91 -0
  208. tau/skills/registry.py +70 -0
  209. tau/skills/types.py +25 -0
  210. tau/themes/loader.py +238 -0
  211. tau/themes/registry.py +108 -0
  212. tau/themes/types.py +19 -0
  213. tau/tool/__init__.py +3 -0
  214. tau/tool/registry.py +117 -0
  215. tau/tool/render.py +21 -0
  216. tau/tool/types.py +244 -0
  217. tau/trust/__init__.py +13 -0
  218. tau/trust/manager.py +80 -0
  219. tau/trust/types.py +14 -0
  220. tau/trust/utils.py +72 -0
  221. tau/tui/__init__.py +54 -0
  222. tau/tui/agent_hooks.py +346 -0
  223. tau/tui/ansi.py +330 -0
  224. tau/tui/app.py +540 -0
  225. tau/tui/autocomplete.py +33 -0
  226. tau/tui/capabilities.py +119 -0
  227. tau/tui/commands/__init__.py +3 -0
  228. tau/tui/commands/appearance.py +498 -0
  229. tau/tui/commands/auth.py +232 -0
  230. tau/tui/commands/context.py +38 -0
  231. tau/tui/commands/misc.py +82 -0
  232. tau/tui/commands/model.py +118 -0
  233. tau/tui/commands/session.py +464 -0
  234. tau/tui/component.py +268 -0
  235. tau/tui/components/__init__.py +0 -0
  236. tau/tui/components/autocomplete_manager.py +267 -0
  237. tau/tui/components/autocomplete_picker.py +143 -0
  238. tau/tui/components/box.py +90 -0
  239. tau/tui/components/command_palette.py +144 -0
  240. tau/tui/components/dynamic_border.py +19 -0
  241. tau/tui/components/file_picker.py +233 -0
  242. tau/tui/components/image.py +181 -0
  243. tau/tui/components/inline_selector.py +71 -0
  244. tau/tui/components/layout.py +1194 -0
  245. tau/tui/components/message_list.py +692 -0
  246. tau/tui/components/modal.py +97 -0
  247. tau/tui/components/model_palette.py +204 -0
  248. tau/tui/components/picker_overlay.py +174 -0
  249. tau/tui/components/prompt_overlay.py +236 -0
  250. tau/tui/components/resume_modal.py +372 -0
  251. tau/tui/components/select_list.py +222 -0
  252. tau/tui/components/settings_modal.py +274 -0
  253. tau/tui/components/settings_schema.py +203 -0
  254. tau/tui/components/spinner.py +119 -0
  255. tau/tui/components/text_input.py +396 -0
  256. tau/tui/components/text_prompt.py +82 -0
  257. tau/tui/components/tree_select_list.py +580 -0
  258. tau/tui/components/trust_screen.py +97 -0
  259. tau/tui/diff.py +114 -0
  260. tau/tui/fuzzy.py +99 -0
  261. tau/tui/input.py +496 -0
  262. tau/tui/input_handler.py +716 -0
  263. tau/tui/keybindings.py +87 -0
  264. tau/tui/markdown.py +286 -0
  265. tau/tui/message_renderers.py +31 -0
  266. tau/tui/overlay.py +326 -0
  267. tau/tui/renderer.py +378 -0
  268. tau/tui/terminal.py +499 -0
  269. tau/tui/theme.py +148 -0
  270. tau/tui/tui.py +544 -0
  271. tau/tui/ui_context.py +768 -0
  272. tau/tui/utils.py +20 -0
  273. tau/utils/__init__.py +0 -0
  274. tau/utils/http_proxy.py +221 -0
  275. tau/utils/image_processing.py +172 -0
  276. tau/utils/secrets.py +59 -0
  277. tau/utils/version_check.py +60 -0
  278. tau_coding_agent-0.1.0.dist-info/METADATA +177 -0
  279. tau_coding_agent-0.1.0.dist-info/RECORD +283 -0
  280. tau_coding_agent-0.1.0.dist-info/WHEEL +5 -0
  281. tau_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
  282. tau_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
  283. tau_coding_agent-0.1.0.dist-info/top_level.txt +1 -0
tau/settings/paths.py ADDED
@@ -0,0 +1,150 @@
1
+ """Configuration directory paths for Tau application.
2
+
3
+ Tau stores all user configuration and session data in ~/.tau/ (global)
4
+ and .tau/ (project-level). This module provides functions to get the
5
+ correct paths for different data types.
6
+
7
+ Global paths: ~/.tau/
8
+ - settings.json: user preferences
9
+ - auth.json: authentication credentials
10
+ - sessions/: persistent session data
11
+ - prompts/, tools/, skills/, commands/: custom extensions
12
+ - themes/: custom UI themes
13
+ - hooks/: lifecycle hooks
14
+
15
+ Project paths: .tau/
16
+ - settings.json: project-specific overrides
17
+ - extensions/: project-local extensions
18
+ """
19
+ from pathlib import Path
20
+
21
+ APP_NAME = "Tau"
22
+ PACKAGE_NAME = "tau-coding-agent"
23
+ CONFIG_DIR_NAME = ".tau"
24
+
25
+ CONFIG_DIR_PATH = Path.home() / CONFIG_DIR_NAME
26
+
27
+
28
+ # ── Centralized app name helper ────────────────────────────────────────────────
29
+
30
+ def get_app_name() -> str:
31
+ """Return the application name."""
32
+ return APP_NAME
33
+
34
+
35
+ def get_package_name() -> str:
36
+ """Return the PyPI distribution package name."""
37
+ return PACKAGE_NAME
38
+
39
+
40
+ def get_app_version() -> str:
41
+ """Return the installed package version, falling back to '0.1.0'."""
42
+ try:
43
+ from importlib.metadata import version
44
+ return version(get_package_name())
45
+ except Exception:
46
+ return "0.1.0"
47
+
48
+
49
+ def get_config_dir(cwd: Path | None = None) -> Path:
50
+ if cwd is not None and cwd.exists():
51
+ return cwd / CONFIG_DIR_NAME
52
+ return CONFIG_DIR_PATH
53
+
54
+
55
+ # ── User-facing files ────────────────────────────────────────────────────────
56
+
57
+ def get_settings_path(cwd: Path | None = None) -> Path:
58
+ return get_config_dir(cwd) / "settings.json"
59
+
60
+
61
+ def get_auth_path() -> Path:
62
+ return CONFIG_DIR_PATH / "auth.json"
63
+
64
+
65
+ def get_system_prompt_path(cwd: Path | None = None) -> Path:
66
+ return get_config_dir(cwd) / "SYSTEM.md"
67
+
68
+
69
+ def get_append_system_prompt_path(cwd: Path | None = None) -> Path:
70
+ return get_config_dir(cwd) / "APPEND_SYSTEM.md"
71
+
72
+
73
+ # ── Runtime dirs (all flat under .tau/) ───────────────────────────────────────
74
+
75
+ def get_sessions_dir() -> Path:
76
+ return CONFIG_DIR_PATH / "sessions"
77
+
78
+
79
+ def get_logs_dir(cwd: Path | None = None) -> Path:
80
+ "Path to a per-run logs directory, named by the run/session id."
81
+ return get_config_dir(cwd) / "logs"
82
+
83
+
84
+ def get_themes_dir(cwd: Path | None = None) -> Path:
85
+ return get_config_dir(cwd) / "themes"
86
+
87
+
88
+ def get_extensions_dir(cwd: Path | None = None) -> Path:
89
+ return get_config_dir(cwd) / "extensions"
90
+
91
+
92
+ def get_prompts_dir(cwd: Path | None = None) -> Path:
93
+ return get_config_dir(cwd) / "prompts"
94
+
95
+
96
+ def get_tools_dir(cwd: Path | None = None) -> Path:
97
+ return get_config_dir(cwd) / "tools"
98
+
99
+
100
+ def get_skills_dir(cwd: Path | None = None) -> Path:
101
+ return get_config_dir(cwd) / "skills"
102
+
103
+
104
+ def get_commands_dir(cwd: Path | None = None) -> Path:
105
+ return get_config_dir(cwd) / "commands"
106
+
107
+
108
+ def get_hooks_dir(cwd: Path | None = None) -> Path:
109
+ return get_config_dir(cwd) / "hooks"
110
+
111
+
112
+ def get_temp_dir(cwd: Path | None = None) -> Path:
113
+ return get_config_dir(cwd) / "temp"
114
+
115
+
116
+ def get_packages_venv(cwd: Path | None = None) -> Path:
117
+ return get_config_dir(cwd) / "venv"
118
+
119
+
120
+ def get_builtins_dir() -> Path:
121
+ return Path(__file__).parent.parent / "builtins"
122
+
123
+
124
+ def get_docs_dir() -> Path:
125
+ """Get the docs directory path.
126
+
127
+ Works both when tau is installed via pip and when running from source.
128
+ """
129
+ try:
130
+ from importlib.resources import files
131
+ docs_ref = files("tau").joinpath("docs")
132
+ return Path(str(docs_ref))
133
+ except (TypeError, ModuleNotFoundError, AttributeError):
134
+ package_root = Path(__file__).parent.parent.parent
135
+ return package_root / "docs"
136
+
137
+
138
+ def get_readme_path() -> Path:
139
+ """Get the README.md path.
140
+
141
+ Works both when tau is installed via pip and when running from source.
142
+ """
143
+ try:
144
+ from importlib.resources import files
145
+ readme_ref = files("tau").joinpath("README.md")
146
+ return Path(str(readme_ref))
147
+ except (TypeError, ModuleNotFoundError, AttributeError):
148
+ package_root = Path(__file__).parent.parent.parent
149
+ return package_root / "README.md"
150
+
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+ from tau.settings.paths import get_settings_path
3
+ from tau.settings.types import LockResult, SCOPE
4
+ from abc import ABC, abstractmethod
5
+ from typing import Callable
6
+ from filelock import FileLock
7
+ from pathlib import Path
8
+
9
+
10
+ class SettingsStorage(ABC):
11
+ """Abstract storage backend for settings."""
12
+
13
+ @abstractmethod
14
+ def with_lock(self, scope: SCOPE, fn: Callable[[str | None], LockResult]) -> LockResult:
15
+ """Execute fn with locked access to the storage."""
16
+ pass
17
+
18
+ class FileSettingsStorage(SettingsStorage):
19
+ """File-based storage backend with locking."""
20
+
21
+ def __init__(self, cwd: Path, config_dir: Path | None = None):
22
+ self.global_settings_path = (
23
+ config_dir / "settings.json" if config_dir else get_settings_path()
24
+ )
25
+ self.project_settings_path = get_settings_path(cwd)
26
+ self._ensure_parent_dir(self.global_settings_path)
27
+
28
+ def _ensure_parent_dir(self, path: Path) -> None:
29
+ path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
30
+
31
+ def _ensure_file_exists(self, path: Path) -> None:
32
+ if not path.exists():
33
+ path.write_text("{}", encoding="utf-8")
34
+ path.chmod(0o600)
35
+
36
+ def with_lock(self, scope: SCOPE, fn: Callable[[str | None], LockResult]) -> LockResult:
37
+ path = self.global_settings_path if scope == SCOPE.GLOBAL else self.project_settings_path
38
+ lock_path = path.with_suffix(".lock")
39
+
40
+ with FileLock(lock_path):
41
+ self._ensure_file_exists(path)
42
+ current = path.read_text(encoding="utf-8") if path.exists() else "{}"
43
+ result = fn(current)
44
+ if result.next is not None:
45
+ path.write_text(result.next, encoding="utf-8")
46
+ return result
47
+
48
+ class InMemorySettingsStorage(SettingsStorage):
49
+ """In-memory storage backend for testing."""
50
+
51
+ def __init__(self):
52
+ self.global_data: str = "{}"
53
+ self.project_data: str = "{}"
54
+
55
+ def with_lock(self, scope: SCOPE, fn: Callable[[str | None], LockResult]) -> LockResult:
56
+ current = self.global_data if scope == SCOPE.GLOBAL else self.project_data
57
+ result = fn(current)
58
+ if result.next is not None:
59
+ if scope == SCOPE.GLOBAL:
60
+ self.global_data = result.next
61
+ else:
62
+ self.project_data = result.next
63
+ return result
tau/settings/types.py ADDED
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from enum import Enum
4
+ from typing import Optional, Literal, Any
5
+ from tau.engine.types import SteeringMode, FollowupMode
6
+ from tau.inference.types import Transport, ThinkingLevel
7
+
8
+
9
+ class SCOPE(str, Enum):
10
+ GLOBAL = "global"
11
+ PROJECT = "project"
12
+
13
+
14
+ @dataclass
15
+ class LockResult:
16
+ result: Any
17
+ next: str | None = None
18
+
19
+
20
+ @dataclass
21
+ class SettingsError:
22
+ scope: SCOPE
23
+ error: Exception
24
+
25
+
26
+ @dataclass
27
+ class ProviderRetrySettings:
28
+ timeout_ms: Optional[int] = None
29
+ max_retries: Optional[int] = None
30
+ max_retry_delay_ms: Optional[int] = None
31
+
32
+
33
+ @dataclass
34
+ class RetrySettings:
35
+ enabled: Optional[bool] = None
36
+ max_retries: Optional[int] = None
37
+ base_delay_ms: Optional[int] = None
38
+ provider: Optional[ProviderRetrySettings] = None
39
+
40
+
41
+ @dataclass
42
+ class ThinkingBudgetsSettings:
43
+ minimal: Optional[int] = None
44
+ low: Optional[int] = None
45
+ medium: Optional[int] = None
46
+ high: Optional[int] = None
47
+ xhigh: Optional[int] = None
48
+ max: Optional[int] = None
49
+
50
+
51
+ @dataclass
52
+ class ImageSettings:
53
+ auto_resize: Optional[bool] = None # resize images to 2000×2000 max before sending to LLM (default: True)
54
+ block_images: Optional[bool] = None # prevent all images from being sent to the LLM (default: False)
55
+
56
+
57
+ @dataclass
58
+ class CompactionSettings:
59
+ enabled: Optional[bool] = None # enable auto-compaction (default: True)
60
+ reserve_tokens: Optional[int] = None # tokens reserved for LLM response (default: 16384)
61
+ keep_recent_tokens: Optional[int] = None # recent tokens to keep verbatim (default: 20000)
62
+
63
+
64
+ @dataclass
65
+ class BranchSummarySettings:
66
+ enabled: Optional[bool] = None # enable branch summarization (default: True)
67
+ skip_prompt: Optional[bool] = None # always skip the "Summarize branch?" prompt (default: False)
68
+ reserve_tokens: Optional[int] = None # tokens to reserve when summarizing (default: 16384)
69
+
70
+
71
+ @dataclass
72
+ class ExtensionEntry:
73
+ """Per-extension config entry stored in the ``extensions.list`` settings block."""
74
+ path: str
75
+ name: Optional[str] = None
76
+ enabled: bool = True
77
+ source: Optional[str] = None
78
+ author: Optional[str] = None
79
+ settings: Optional[dict] = field(default=None)
80
+
81
+
82
+ @dataclass
83
+ class ExtensionsSettings:
84
+ """Global extension toggle plus per-extension configuration list."""
85
+ enabled: Optional[bool] = None # global on/off for all extensions
86
+ list: Optional[list[ExtensionEntry]] = None
87
+
88
+
89
+ @dataclass
90
+ class PackageEntry:
91
+ """A package installed via pip/uv/poetry into the tau-managed venv."""
92
+ source: str # "pypi:name@1.0", "git+https://...", "/local/path"
93
+ name: str # package name (normalised)
94
+ version: Optional[str] = None # installed version, if known
95
+ installed_path: Optional[str] = None # path to package dir inside the venv
96
+ enabled: bool = True
97
+
98
+
99
+ @dataclass
100
+ class PackagesSettings:
101
+ """Installed package list stored in settings."""
102
+ list: Optional[list[PackageEntry]] = None
103
+
104
+
105
+ @dataclass
106
+ class TerminalSettings:
107
+ shell_path: Optional[str] = None # custom shell binary (default: system shell)
108
+ shell_command_prefix: Optional[str] = None # lines prepended inside the shell before each command
109
+
110
+
111
+ @dataclass
112
+ class HTTPProxySettings:
113
+ url: Optional[str] = None # Proxy URL for both HTTP and HTTPS (overrides env vars)
114
+ no_proxy: Optional[str] = None # Comma-separated hosts to exclude from proxying
115
+ headers: Optional[dict[str, str]] = None # Custom headers for proxy (e.g., authentication)
116
+
117
+
118
+ @dataclass
119
+ class Settings:
120
+ # Model / provider
121
+ provider: Optional[str] = None
122
+ model: Optional[str] = None
123
+ thinking_level: Optional[ThinkingLevel] = None
124
+ transport: Optional[Transport] = None
125
+ enabled_models: Optional[list[str]] = None
126
+
127
+ # UI
128
+ theme: Optional[str] = None
129
+ show_thinking: Optional[bool] = None
130
+ show_tool_calls: Optional[bool] = None
131
+ show_images: Optional[bool] = None
132
+ picker_max_visible: Optional[int] = None
133
+
134
+ # Queue behaviour
135
+ steering_mode: Optional[SteeringMode] = None
136
+ follow_up_mode: Optional[FollowupMode] = None
137
+
138
+ # Nested sub-settings
139
+ retry: Optional[RetrySettings] = None
140
+ thinking_budgets: Optional[ThinkingBudgetsSettings] = None
141
+ image: Optional[ImageSettings] = None
142
+ compaction: Optional[CompactionSettings] = None
143
+ branch_summary: Optional[BranchSummarySettings] = None
144
+
145
+ # Execution
146
+ terminal: Optional[TerminalSettings] = None
147
+
148
+ # Network
149
+ http_idle_timeout_ms: Optional[int] = None # idle timeout for LLM HTTP streams (default: 60000)
150
+ websocket_connect_timeout_ms: Optional[int] = None # WebSocket connect/open handshake timeout
151
+ http_proxy: Optional[HTTPProxySettings] = None # HTTP proxy configuration (overrides env vars)
152
+
153
+ # Project trust (global only)
154
+ project_trust: Optional[Literal["ask", "always", "never"]] = None # default: "ask" — controls loading of project files (.tau/ config, extensions), project context files (AGENTS.md/CLAUDE.md), and project skills
155
+
156
+ # Session
157
+ session_dir: Optional[str] = None
158
+
159
+ # Startup
160
+ quiet_startup: Optional[bool] = None # suppress startup notices (default: False)
161
+
162
+ # UI behaviour
163
+ double_escape_action: Optional[Literal["fork", "tree", "none"]] = None # action on double-Escape with empty editor (default: "fork")
164
+ tree_filter_mode: Optional[Literal["default", "no-tools", "user-only", "labeled-only", "all"]] = None # default /tree filter mode
165
+ autocomplete_max_visible: Optional[int] = None # max items in autocomplete dropdown (default: 5)
166
+ show_hardware_cursor: Optional[bool] = None # show terminal cursor while positioning (IME support, default: False)
167
+ editor_padding_x: Optional[int] = None # horizontal padding for the input editor (default: 0)
168
+
169
+ # Extensions
170
+ extensions: Optional[ExtensionsSettings] = None
171
+
172
+ # Packages
173
+ packages: Optional[PackagesSettings] = None
tau/settings/utils.py ADDED
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ def set_nested(d: dict, key: str, value: Any) -> None:
7
+ """Set ``value`` at a dot-separated ``key`` path inside dict ``d``, creating intermediate dicts."""
8
+ parts = key.split(".", 1)
9
+ if len(parts) == 1:
10
+ d[key] = value
11
+ else:
12
+ head, rest = parts
13
+ if not isinstance(d.get(head), dict):
14
+ d[head] = {}
15
+ set_nested(d[head], rest, value)
16
+
17
+
18
+ def coerce_enum(enum_cls: type, value: Any) -> Any:
19
+ """Coerce a raw value into ``enum_cls``; return None on failure (treated as unset)."""
20
+ if value is None or isinstance(value, enum_cls):
21
+ return value
22
+ try:
23
+ return enum_cls(value)
24
+ except (ValueError, KeyError):
25
+ return None
tau/skills/loader.py ADDED
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from tau.skills.types import LoadSkillsResult, Skill, SkillLoadError
6
+
7
+
8
+ def _parse_frontmatter(text: str) -> tuple[dict[str, str], str]:
9
+ """Parse YAML frontmatter from skill markdown text."""
10
+ text = text.lstrip("\n")
11
+ if not text.startswith("---"):
12
+ return {}, text
13
+ end = text.find("\n---", 3)
14
+ if end == -1:
15
+ return {}, text
16
+ fm_text = text[3:end].strip()
17
+ body = text[end + 4:].lstrip("\n")
18
+ meta: dict[str, str] = {}
19
+ for line in fm_text.splitlines():
20
+ if ":" in line:
21
+ key, _, val = line.partition(":")
22
+ meta[key.strip().lower()] = val.strip()
23
+ return meta, body
24
+
25
+
26
+ def load_skill_from_file(path: Path, name_hint: str | None = None) -> tuple[Skill | None, str | None]:
27
+ """Load a skill from a markdown file."""
28
+ try:
29
+ text = path.read_text(encoding="utf-8")
30
+ except Exception as exc:
31
+ return None, f"read error: {exc}"
32
+
33
+ meta, body = _parse_frontmatter(text)
34
+ body = body.strip()
35
+ if not body:
36
+ return None, "skill body is empty"
37
+
38
+ name = meta.get("name") or name_hint or path.stem.lower()
39
+ description = meta.get("description", "")
40
+ if not description:
41
+ return None, "missing 'description' field"
42
+
43
+ disable = meta.get("disable-model-invocation", "").lower() in ("true", "1", "yes")
44
+
45
+ return Skill(
46
+ name=name.lower(),
47
+ description=description,
48
+ content=body,
49
+ file_path=str(path),
50
+ base_dir=str(path.parent),
51
+ disable_model_invocation=disable,
52
+ ), None
53
+
54
+
55
+ def load_skills_from_dir(directory: Path) -> LoadSkillsResult:
56
+ """
57
+ Scan a directory for skills.
58
+
59
+ Rules:
60
+ - If a subdir contains SKILL.md → load it as one skill (name = dirname)
61
+ - If a .md file sits at the root → load it as one skill (name = stem)
62
+ - Recurse into subdirs that don't have SKILL.md to find nested skill dirs
63
+ """
64
+ result = LoadSkillsResult()
65
+ if not directory.is_dir():
66
+ return result
67
+
68
+ def _scan(d: Path) -> None:
69
+ skill_md = d / "SKILL.md"
70
+ if skill_md.is_file():
71
+ skill, err = load_skill_from_file(skill_md, name_hint=d.name.lower())
72
+ if err or skill is None:
73
+ result.errors.append(SkillLoadError(str(skill_md), err or "unknown"))
74
+ else:
75
+ result.skills[skill.name] = skill
76
+ return
77
+
78
+ if d == directory:
79
+ for path in sorted(d.glob("*.md")):
80
+ skill, err = load_skill_from_file(path)
81
+ if err or skill is None:
82
+ result.errors.append(SkillLoadError(str(path), err or "unknown"))
83
+ else:
84
+ result.skills[skill.name] = skill
85
+
86
+ for sub in sorted(d.iterdir()):
87
+ if sub.is_dir():
88
+ _scan(sub)
89
+
90
+ _scan(directory)
91
+ return result
tau/skills/registry.py ADDED
@@ -0,0 +1,70 @@
1
+ """
2
+ Skill registry.
3
+
4
+ Priority (highest wins):
5
+ project (.tau/skills/ relative to cwd)
6
+ global (~/.tau/skills/)
7
+ builtin (tau/builtins/skills/)
8
+
9
+ Skills are listed in the system prompt as <available_skills> XML so the model
10
+ can load them on demand via the read tool, or invoked explicitly with
11
+ /skill:name [args].
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+ from typing import Any, List
17
+
18
+ from tau.core.registry import Registry
19
+ from tau.skills.types import Skill, SkillLoadError
20
+ from tau.skills.loader import load_skills_from_dir
21
+
22
+
23
+ class SkillRegistry(Registry[Skill, SkillLoadError]):
24
+ def _load_from_dir(self, path: Path) -> Any:
25
+ return load_skills_from_dir(path)
26
+
27
+ def _get_dir(self, cwd: Path | None = None) -> Path:
28
+ from tau.settings.paths import get_skills_dir
29
+ return get_skills_dir(cwd)
30
+
31
+ def _builtins_subdir(self) -> str:
32
+ return "skills"
33
+
34
+ def _extract_items(self, result: Any) -> dict[str, Skill]:
35
+ return result.skills
36
+
37
+ def _extract_errors(self, result: Any) -> list[SkillLoadError]:
38
+ return result.errors
39
+
40
+ def list(self) -> list[Skill]:
41
+ """Return all skills available to the model (excluding disabled ones)."""
42
+ self._ensure_builtins()
43
+ return [s for s in self._registry.values() if not s.disable_model_invocation]
44
+
45
+ def list_all(self) -> List[Skill]:
46
+ """Return all registered skills, including disabled ones."""
47
+ self._ensure_builtins()
48
+ return list(self._registry.values())
49
+
50
+ def format_for_system_prompt(self, skills: List[Skill]) -> str:
51
+ """Format a skill list as XML for inclusion in the system prompt."""
52
+ if not skills:
53
+ return ""
54
+ visible = [s for s in skills if not s.disable_model_invocation]
55
+ if not visible:
56
+ return ""
57
+ lines = ["<available_skills>"]
58
+ for s in visible:
59
+ lines.append(" <skill>")
60
+ lines.append(f" <name>{s.name}</name>")
61
+ lines.append(f" <description>{s.description}</description>")
62
+ lines.append(f" <location>{s.file_path}</location>")
63
+ lines.append(" </skill>")
64
+ lines.append("</available_skills>")
65
+ lines.append("")
66
+ lines.append("When a task matches a skill's description, use the read tool to load the skill file and follow its instructions.")
67
+ return "\n".join(lines)
68
+
69
+
70
+ skill_registry = SkillRegistry()
tau/skills/types.py ADDED
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+
6
+ @dataclass
7
+ class Skill:
8
+ name: str
9
+ description: str
10
+ content: str
11
+ file_path: str
12
+ base_dir: str
13
+ disable_model_invocation: bool = False
14
+
15
+
16
+ @dataclass
17
+ class SkillLoadError:
18
+ path: str
19
+ error: str
20
+
21
+
22
+ @dataclass
23
+ class LoadSkillsResult:
24
+ skills: dict[str, Skill] = field(default_factory=dict)
25
+ errors: list[SkillLoadError] = field(default_factory=list)