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.
- tau/__init__.py +0 -0
- tau/agent/__init__.py +11 -0
- tau/agent/prompt/__init__.py +10 -0
- tau/agent/prompt/builder.py +302 -0
- tau/agent/prompt/types.py +33 -0
- tau/agent/service.py +369 -0
- tau/agent/types.py +61 -0
- tau/auth/manager.py +247 -0
- tau/auth/storage.py +82 -0
- tau/auth/types.py +41 -0
- tau/builtins/__init__.py +4 -0
- tau/builtins/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/commands/__init__.py +41 -0
- tau/builtins/commands/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/clear.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/clear.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/compact.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/compact.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/reload.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/reload.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/session.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/session.cpython-314.pyc +0 -0
- tau/builtins/commands/clear.py +16 -0
- tau/builtins/commands/compact.py +28 -0
- tau/builtins/commands/reload.py +27 -0
- tau/builtins/commands/session.py +19 -0
- tau/builtins/extensions/footer/__init__.py +76 -0
- tau/builtins/extensions/footer/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/git.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/model.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/utils.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/git.py +26 -0
- tau/builtins/extensions/footer/model.py +69 -0
- tau/builtins/extensions/footer/utils.py +44 -0
- tau/builtins/extensions/header/__init__.py +18 -0
- tau/builtins/extensions/header/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/models/__init__.py +0 -0
- tau/builtins/models/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/models/__pycache__/text.cpython-313.pyc +0 -0
- tau/builtins/models/audio.py +43 -0
- tau/builtins/models/image.py +43 -0
- tau/builtins/models/text.py +482 -0
- tau/builtins/models/video.py +40 -0
- tau/builtins/prompts/commit.md +7 -0
- tau/builtins/prompts/docs.md +7 -0
- tau/builtins/prompts/explain.md +7 -0
- tau/builtins/prompts/fix.md +7 -0
- tau/builtins/prompts/refactor.md +7 -0
- tau/builtins/prompts/review.md +7 -0
- tau/builtins/prompts/test.md +7 -0
- tau/builtins/providers/__init__.py +0 -0
- tau/builtins/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/providers/__pycache__/text.cpython-313.pyc +0 -0
- tau/builtins/providers/audio.py +10 -0
- tau/builtins/providers/image.py +9 -0
- tau/builtins/providers/text.py +33 -0
- tau/builtins/providers/video.py +6 -0
- tau/builtins/skills/code-review/SKILL.md +4 -0
- tau/builtins/skills/debug/SKILL.md +4 -0
- tau/builtins/skills/git-commit/SKILL.md +4 -0
- tau/builtins/themes/dark.yaml +1 -0
- tau/builtins/themes/light.yaml +46 -0
- tau/builtins/tools/__init__.py +73 -0
- tau/builtins/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/bash.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/bash.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/edit.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/edit.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/glob.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/glob.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/grep.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/grep.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/ls.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/ls.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/read.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/read.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/terminal.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/terminal.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/write.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/write.cpython-314.pyc +0 -0
- tau/builtins/tools/edit.py +215 -0
- tau/builtins/tools/glob.py +112 -0
- tau/builtins/tools/grep.py +146 -0
- tau/builtins/tools/ls.py +135 -0
- tau/builtins/tools/read.py +122 -0
- tau/builtins/tools/terminal.py +150 -0
- tau/builtins/tools/write.py +105 -0
- tau/commands/__init__.py +10 -0
- tau/commands/registry.py +71 -0
- tau/commands/types.py +33 -0
- tau/console/__init__.py +0 -0
- tau/console/cli.py +266 -0
- tau/console/commands/__init__.py +0 -0
- tau/console/commands/auth.py +193 -0
- tau/console/commands/packages.py +104 -0
- tau/console/commands/update.py +76 -0
- tau/core/__init__.py +0 -0
- tau/core/registry.py +102 -0
- tau/engine/__init__.py +47 -0
- tau/engine/service.py +768 -0
- tau/engine/types.py +163 -0
- tau/extensions/__init__.py +28 -0
- tau/extensions/api.py +928 -0
- tau/extensions/context.py +462 -0
- tau/extensions/events.py +70 -0
- tau/extensions/loader.py +386 -0
- tau/extensions/runtime.py +184 -0
- tau/extensions/settings.py +137 -0
- tau/hooks/__init__.py +112 -0
- tau/hooks/engine.py +237 -0
- tau/hooks/inference.py +21 -0
- tau/hooks/runtime.py +126 -0
- tau/hooks/service.py +121 -0
- tau/hooks/session.py +117 -0
- tau/hooks/tui.py +61 -0
- tau/hooks/types.py +72 -0
- tau/inference/__init__.py +180 -0
- tau/inference/api/__init__.py +0 -0
- tau/inference/api/audio/__init__.py +0 -0
- tau/inference/api/audio/base.py +29 -0
- tau/inference/api/audio/builtins.py +15 -0
- tau/inference/api/audio/elevenlabs_audio.py +183 -0
- tau/inference/api/audio/gemini_audio.py +95 -0
- tau/inference/api/audio/openai_audio.py +159 -0
- tau/inference/api/audio/registry.py +15 -0
- tau/inference/api/audio/sarvam_audio.py +163 -0
- tau/inference/api/audio/service.py +103 -0
- tau/inference/api/audio/utils.py +47 -0
- tau/inference/api/image/__init__.py +0 -0
- tau/inference/api/image/base.py +17 -0
- tau/inference/api/image/builtins.py +8 -0
- tau/inference/api/image/gemini_image.py +77 -0
- tau/inference/api/image/openai_image.py +103 -0
- tau/inference/api/image/openrouter.py +144 -0
- tau/inference/api/image/registry.py +15 -0
- tau/inference/api/image/service.py +71 -0
- tau/inference/api/registry.py +82 -0
- tau/inference/api/text/__init__.py +0 -0
- tau/inference/api/text/anthropic_claude_code.py +222 -0
- tau/inference/api/text/anthropic_messages.py +196 -0
- tau/inference/api/text/base.py +40 -0
- tau/inference/api/text/builtins.py +19 -0
- tau/inference/api/text/gemini_generate.py +234 -0
- tau/inference/api/text/github_copilot_chat.py +172 -0
- tau/inference/api/text/google_antigravity.py +522 -0
- tau/inference/api/text/mistral_chat.py +284 -0
- tau/inference/api/text/ollama_chat.py +200 -0
- tau/inference/api/text/openai_codex_responses.py +497 -0
- tau/inference/api/text/openai_completions.py +227 -0
- tau/inference/api/text/openai_responses.py +235 -0
- tau/inference/api/text/registry.py +50 -0
- tau/inference/api/text/service.py +297 -0
- tau/inference/api/text/types.py +7 -0
- tau/inference/api/text/utils.py +228 -0
- tau/inference/api/video/__init__.py +0 -0
- tau/inference/api/video/base.py +26 -0
- tau/inference/api/video/builtins.py +7 -0
- tau/inference/api/video/fal_video.py +119 -0
- tau/inference/api/video/openrouter_video.py +142 -0
- tau/inference/api/video/registry.py +15 -0
- tau/inference/api/video/service.py +72 -0
- tau/inference/model/__init__.py +0 -0
- tau/inference/model/registry.py +102 -0
- tau/inference/model/types.py +65 -0
- tau/inference/provider/__init__.py +0 -0
- tau/inference/provider/oauth/__init__.py +35 -0
- tau/inference/provider/oauth/anthropic_claude_code.py +286 -0
- tau/inference/provider/oauth/github_copilot.py +333 -0
- tau/inference/provider/oauth/google_antigravity.py +258 -0
- tau/inference/provider/oauth/openai_codex.py +309 -0
- tau/inference/provider/oauth/pkce.py +14 -0
- tau/inference/provider/oauth/types.py +46 -0
- tau/inference/provider/oauth/utils.py +154 -0
- tau/inference/provider/registry.py +141 -0
- tau/inference/provider/types.py +114 -0
- tau/inference/types.py +549 -0
- tau/inference/utils.py +219 -0
- tau/message/__init__.py +0 -0
- tau/message/types.py +482 -0
- tau/message/utils.py +178 -0
- tau/packages/__init__.py +11 -0
- tau/packages/manager.py +190 -0
- tau/packages/types.py +20 -0
- tau/packages/utils.py +67 -0
- tau/prompts/expand.py +58 -0
- tau/prompts/loader.py +69 -0
- tau/prompts/registry.py +45 -0
- tau/prompts/types.py +24 -0
- tau/rpc/__init__.py +8 -0
- tau/rpc/mode.py +783 -0
- tau/rpc/types.py +252 -0
- tau/runtime/service.py +759 -0
- tau/runtime/types.py +303 -0
- tau/session/branch_summarization.py +312 -0
- tau/session/compaction.py +646 -0
- tau/session/manager.py +652 -0
- tau/session/types.py +188 -0
- tau/session/utils.py +233 -0
- tau/settings/manager.py +1077 -0
- tau/settings/paths.py +150 -0
- tau/settings/storage.py +63 -0
- tau/settings/types.py +173 -0
- tau/settings/utils.py +25 -0
- tau/skills/loader.py +91 -0
- tau/skills/registry.py +70 -0
- tau/skills/types.py +25 -0
- tau/themes/loader.py +238 -0
- tau/themes/registry.py +108 -0
- tau/themes/types.py +19 -0
- tau/tool/__init__.py +3 -0
- tau/tool/registry.py +117 -0
- tau/tool/render.py +21 -0
- tau/tool/types.py +244 -0
- tau/trust/__init__.py +13 -0
- tau/trust/manager.py +80 -0
- tau/trust/types.py +14 -0
- tau/trust/utils.py +72 -0
- tau/tui/__init__.py +54 -0
- tau/tui/agent_hooks.py +346 -0
- tau/tui/ansi.py +330 -0
- tau/tui/app.py +540 -0
- tau/tui/autocomplete.py +33 -0
- tau/tui/capabilities.py +119 -0
- tau/tui/commands/__init__.py +3 -0
- tau/tui/commands/appearance.py +498 -0
- tau/tui/commands/auth.py +232 -0
- tau/tui/commands/context.py +38 -0
- tau/tui/commands/misc.py +82 -0
- tau/tui/commands/model.py +118 -0
- tau/tui/commands/session.py +464 -0
- tau/tui/component.py +268 -0
- tau/tui/components/__init__.py +0 -0
- tau/tui/components/autocomplete_manager.py +267 -0
- tau/tui/components/autocomplete_picker.py +143 -0
- tau/tui/components/box.py +90 -0
- tau/tui/components/command_palette.py +144 -0
- tau/tui/components/dynamic_border.py +19 -0
- tau/tui/components/file_picker.py +233 -0
- tau/tui/components/image.py +181 -0
- tau/tui/components/inline_selector.py +71 -0
- tau/tui/components/layout.py +1194 -0
- tau/tui/components/message_list.py +692 -0
- tau/tui/components/modal.py +97 -0
- tau/tui/components/model_palette.py +204 -0
- tau/tui/components/picker_overlay.py +174 -0
- tau/tui/components/prompt_overlay.py +236 -0
- tau/tui/components/resume_modal.py +372 -0
- tau/tui/components/select_list.py +222 -0
- tau/tui/components/settings_modal.py +274 -0
- tau/tui/components/settings_schema.py +203 -0
- tau/tui/components/spinner.py +119 -0
- tau/tui/components/text_input.py +396 -0
- tau/tui/components/text_prompt.py +82 -0
- tau/tui/components/tree_select_list.py +580 -0
- tau/tui/components/trust_screen.py +97 -0
- tau/tui/diff.py +114 -0
- tau/tui/fuzzy.py +99 -0
- tau/tui/input.py +496 -0
- tau/tui/input_handler.py +716 -0
- tau/tui/keybindings.py +87 -0
- tau/tui/markdown.py +286 -0
- tau/tui/message_renderers.py +31 -0
- tau/tui/overlay.py +326 -0
- tau/tui/renderer.py +378 -0
- tau/tui/terminal.py +499 -0
- tau/tui/theme.py +148 -0
- tau/tui/tui.py +544 -0
- tau/tui/ui_context.py +768 -0
- tau/tui/utils.py +20 -0
- tau/utils/__init__.py +0 -0
- tau/utils/http_proxy.py +221 -0
- tau/utils/image_processing.py +172 -0
- tau/utils/secrets.py +59 -0
- tau/utils/version_check.py +60 -0
- tau_coding_agent-0.1.0.dist-info/METADATA +177 -0
- tau_coding_agent-0.1.0.dist-info/RECORD +283 -0
- tau_coding_agent-0.1.0.dist-info/WHEEL +5 -0
- tau_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
- tau_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
|
tau/settings/storage.py
ADDED
|
@@ -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)
|