windcode 0.1.1__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {windcode-0.1.1 → windcode-0.1.2}/.gitignore +6 -0
- {windcode-0.1.1 → windcode-0.1.2}/.windcode/config.toml.example +2 -2
- {windcode-0.1.1 → windcode-0.1.2}/AGENTS.md +3 -2
- {windcode-0.1.1 → windcode-0.1.2}/PKG-INFO +6 -5
- {windcode-0.1.1 → windcode-0.1.2}/README.md +5 -4
- {windcode-0.1.1 → windcode-0.1.2}/examples/README.md +1 -1
- {windcode-0.1.1 → windcode-0.1.2}/pyproject.toml +1 -1
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/__init__.py +1 -1
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/cli.py +4 -3
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/config/__init__.py +8 -1
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/config/loader.py +27 -1
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/service.py +13 -2
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/sdk.py +6 -43
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/app.py +2 -4
- {windcode-0.1.1 → windcode-0.1.2}/uv.lock +1 -1
- {windcode-0.1.1 → windcode-0.1.2}/.github/workflows/publish.yml +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/.python-version +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/.windcode/skills/code-review/SKILL.md +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/.windcode/skills/code-review/agents/openai.yaml +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/LICENSE +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/assets/image.png +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/assets/image1.png +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/assets/image2.png +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/assets/image3.png +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/assets/image4.png +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/examples/01_stateful_chat.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/examples/02_custom_tool.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/examples/03_multi_agent.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/examples/04_mcp_tools.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/examples/05_skill.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/examples/06_stream_and_cancel.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/examples/07_bash_approval.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/examples/skills/release_notes/SKILL.md +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/__main__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/auth/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/auth/store.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/config/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/config/writer.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/context/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/context/compactor.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/context/estimator.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/context/truncation.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/domain/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/domain/errors.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/domain/events.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/domain/messages.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/domain/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/domain/subagents.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/domain/tools.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/commands.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/discovery.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/events.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/hooks/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/hooks/dispatcher.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/hooks/executor.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/hooks/loader.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/hooks/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/mcp/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/mcp/adapter.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/mcp/catalog.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/mcp/client.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/mcp/runtime.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/mcp/tools.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/paths.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/plugins/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/plugins/installer.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/plugins/manifest.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/runtime.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/skills/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/skills/loader.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/skills/parser.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/skills/tools.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/snapshot.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/extensions/state.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/instructions/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/instructions/loader.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/memory/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/memory/extraction.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/memory/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/memory/refiner.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/memory/security.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/memory/service.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/memory/store.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/observability/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/observability/redaction.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/observability/trace.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/policy/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/policy/engine.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/policy/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/providers/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/providers/_utils.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/providers/anthropic.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/providers/base.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/providers/catalog.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/providers/errors.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/providers/openai_compat.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/providers/openai_responses.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/providers/registry.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/control.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/event_bus.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/loop.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/prompts.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/report.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/retry.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/scheduler.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/subagents/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/subagents/approvals.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/subagents/budgets.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/subagents/coordinator.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/subagents/factory.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/subagents/roles.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/runtime/subagents/verification.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/sandbox/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/sandbox/bwrap.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/sessions/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/sessions/artifacts.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/sessions/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/sessions/store.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/sessions/tree.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/apply_patch.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/ask_user.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/builtins.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/edit_file.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/filesystem.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/glob.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/grep.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/memory.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/read_file.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/registry.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/shell.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/subagents/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/subagents/cancel.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/subagents/integrate.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/subagents/list.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/subagents/spawn.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/subagents/wait.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tools/write_file.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/commands.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/permission_display.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/styles.tcss +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/approval.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/command_menu.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/extensions.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/input.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/memory.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/messages.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/models.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/question.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/sessions.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/status.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/subagents.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/tools.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/tui/widgets/welcome.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/types.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/worktrees/__init__.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/worktrees/git.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/worktrees/manager.py +0 -0
- {windcode-0.1.1 → windcode-0.1.2}/src/windcode/worktrees/models.py +0 -0
|
@@ -56,8 +56,9 @@ explicit SDK state_root
|
|
|
56
56
|
-> user_storage_root
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
The root contains `memory/`, `sessions/`, `traces/`, `extensions/`, and `worktrees/`.
|
|
60
|
-
`.windcode/config.toml`, `.windcode
|
|
59
|
+
The root contains `skill/`, `memory/`, `sessions/`, `traces/`, `extensions/`, and `worktrees/`.
|
|
60
|
+
`.windcode/config.toml`, runtime directories under `.windcode/`, `tests/`, and `spec/` are local-only
|
|
61
|
+
ignored paths.
|
|
61
62
|
Never remove, rewrite, force-add, or commit them without an explicit user request.
|
|
62
63
|
|
|
63
64
|
For MCP servers, `enable` controls discovery and runtime visibility; `required` only controls startup
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: windcode
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A safe, extensible terminal coding agent
|
|
5
5
|
Project-URL: Homepage, https://github.com/tingfeng347/windcode
|
|
6
6
|
Project-URL: Repository, https://github.com/tingfeng347/windcode
|
|
@@ -149,12 +149,13 @@ Windcode 将记忆、会话、trace、扩展状态和 Worktree 统一存放在
|
|
|
149
149
|
|
|
150
150
|
```toml
|
|
151
151
|
[storage]
|
|
152
|
-
project_state_root = ".windcode
|
|
153
|
-
user_storage_root = "~/.local/state/windcode
|
|
152
|
+
project_state_root = ".windcode"
|
|
153
|
+
user_storage_root = "~/.local/state/windcode"
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
`.windcode
|
|
156
|
+
配置项目状态根时优先使用项目目录;未配置时使用用户状态目录。Skill 会同时扫描两边的
|
|
157
|
+
`skill/`,同名时项目级覆盖用户级。`.windcode/config.toml` 和 `.windcode/` 下的运行状态都
|
|
158
|
+
不应提交到 Git。
|
|
158
159
|
|
|
159
160
|
## 开发
|
|
160
161
|
|
|
@@ -114,12 +114,13 @@ Windcode 将记忆、会话、trace、扩展状态和 Worktree 统一存放在
|
|
|
114
114
|
|
|
115
115
|
```toml
|
|
116
116
|
[storage]
|
|
117
|
-
project_state_root = ".windcode
|
|
118
|
-
user_storage_root = "~/.local/state/windcode
|
|
117
|
+
project_state_root = ".windcode"
|
|
118
|
+
user_storage_root = "~/.local/state/windcode"
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
`.windcode
|
|
121
|
+
配置项目状态根时优先使用项目目录;未配置时使用用户状态目录。Skill 会同时扫描两边的
|
|
122
|
+
`skill/`,同名时项目级覆盖用户级。`.windcode/config.toml` 和 `.windcode/` 下的运行状态都
|
|
123
|
+
不应提交到 Git。
|
|
123
124
|
|
|
124
125
|
## 开发
|
|
125
126
|
|
|
@@ -8,7 +8,7 @@ from collections.abc import Sequence
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
from windcode.config import AppConfig, ConfigError, PermissionMode, load_config
|
|
11
|
+
from windcode.config import AppConfig, ConfigError, PermissionMode, ensure_user_config, load_config
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass(frozen=True, slots=True)
|
|
@@ -156,8 +156,9 @@ def run(argv: Sequence[str] | None = None) -> int:
|
|
|
156
156
|
options = parse_options(arguments)
|
|
157
157
|
if not options.workspace.is_dir():
|
|
158
158
|
raise ConfigError(options.workspace, "workspace is not a directory")
|
|
159
|
+
write_config_file = options.config_file or ensure_user_config()
|
|
159
160
|
config = resolve_config(options)
|
|
160
|
-
except ConfigError as exc:
|
|
161
|
+
except (ConfigError, OSError) as exc:
|
|
161
162
|
print(f"windcode: {exc}", file=sys.stderr)
|
|
162
163
|
return 2
|
|
163
164
|
|
|
@@ -171,7 +172,7 @@ def run(argv: Sequence[str] | None = None) -> int:
|
|
|
171
172
|
permission_mode=(
|
|
172
173
|
options.permission_mode.value if options.permission_mode is not None else None
|
|
173
174
|
),
|
|
174
|
-
config_file=
|
|
175
|
+
config_file=write_config_file,
|
|
175
176
|
)
|
|
176
177
|
app.run()
|
|
177
178
|
return 0
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
from windcode.config.loader import
|
|
1
|
+
from windcode.config.loader import (
|
|
2
|
+
ConfigError,
|
|
3
|
+
default_user_config_path,
|
|
4
|
+
ensure_user_config,
|
|
5
|
+
load_config,
|
|
6
|
+
)
|
|
2
7
|
from windcode.config.models import (
|
|
3
8
|
HARD_MAX_CONCURRENT_SUBAGENTS,
|
|
4
9
|
HARD_MAX_SUBAGENT_TASKS,
|
|
@@ -35,6 +40,8 @@ __all__ = [
|
|
|
35
40
|
"StorageConfig",
|
|
36
41
|
"SubagentConfig",
|
|
37
42
|
"TraceConfig",
|
|
43
|
+
"default_user_config_path",
|
|
44
|
+
"ensure_user_config",
|
|
38
45
|
"load_config",
|
|
39
46
|
"save_memory_config",
|
|
40
47
|
"save_model_config",
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
|
+
import os
|
|
4
5
|
import tomllib
|
|
5
6
|
from collections.abc import Mapping
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, cast
|
|
8
9
|
|
|
10
|
+
import tomli_w
|
|
9
11
|
from platformdirs import user_config_path
|
|
10
12
|
from pydantic import ValidationError
|
|
11
13
|
|
|
@@ -18,6 +20,30 @@ class ConfigError(ValueError):
|
|
|
18
20
|
super().__init__(f"{source}: {message}")
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
def default_user_config_path() -> Path:
|
|
24
|
+
return user_config_path("windcode") / "config.toml"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def ensure_user_config(path: Path | None = None) -> Path:
|
|
28
|
+
"""Create the default user configuration once without overwriting an existing file."""
|
|
29
|
+
target = (path or default_user_config_path()).expanduser().resolve()
|
|
30
|
+
if target.exists():
|
|
31
|
+
return target
|
|
32
|
+
|
|
33
|
+
content = tomli_w.dumps(AppConfig().model_dump(mode="json", exclude_none=True)).encode()
|
|
34
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
try:
|
|
36
|
+
descriptor = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
37
|
+
except FileExistsError:
|
|
38
|
+
return target
|
|
39
|
+
|
|
40
|
+
with os.fdopen(descriptor, "wb") as stream:
|
|
41
|
+
stream.write(content)
|
|
42
|
+
stream.flush()
|
|
43
|
+
os.fsync(stream.fileno())
|
|
44
|
+
return target
|
|
45
|
+
|
|
46
|
+
|
|
21
47
|
def _deep_merge(base: dict[str, Any], overlay: Mapping[str, Any]) -> dict[str, Any]:
|
|
22
48
|
merged = copy.deepcopy(base)
|
|
23
49
|
for key, value in overlay.items():
|
|
@@ -52,7 +78,7 @@ def load_config(
|
|
|
52
78
|
overrides: Mapping[str, Any] | None = None,
|
|
53
79
|
) -> AppConfig:
|
|
54
80
|
workspace = workspace.expanduser().resolve()
|
|
55
|
-
default_user =
|
|
81
|
+
default_user = default_user_config_path()
|
|
56
82
|
default_project = workspace / ".windcode" / "config.toml"
|
|
57
83
|
layers: list[tuple[Path | str, dict[str, Any]]] = [
|
|
58
84
|
(
|
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from typing import cast
|
|
9
9
|
from uuid import uuid4
|
|
10
10
|
|
|
11
|
+
from platformdirs import user_state_path
|
|
11
12
|
from pydantic import TypeAdapter, ValidationError
|
|
12
13
|
|
|
13
14
|
from windcode.config.models import (
|
|
@@ -57,13 +58,23 @@ class ExtensionService:
|
|
|
57
58
|
workspace: Path,
|
|
58
59
|
state_store: ExtensionStateStore,
|
|
59
60
|
plugins_root: Path,
|
|
61
|
+
*,
|
|
62
|
+
user_skill_root: Path | None = None,
|
|
60
63
|
) -> None:
|
|
61
64
|
self.config = config
|
|
62
65
|
self.workspace = workspace.expanduser().resolve()
|
|
63
66
|
self.state_store = state_store
|
|
64
67
|
self.plugins_root = plugins_root
|
|
65
|
-
|
|
66
|
-
self.
|
|
68
|
+
default_user_skill_root = user_skill_root or user_state_path("windcode") / "skill"
|
|
69
|
+
self._user_skill_roots = tuple(
|
|
70
|
+
dict.fromkeys(
|
|
71
|
+
(
|
|
72
|
+
default_user_skill_root.expanduser().resolve(),
|
|
73
|
+
*(Path(path).expanduser().resolve() for path in config.skill_roots),
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
self._project_skill_root = self.workspace / ".windcode" / "skill"
|
|
67
78
|
loaded = state_store.load()
|
|
68
79
|
self._state = loaded.state
|
|
69
80
|
self._state_diagnostics = loaded.diagnostics
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import shutil
|
|
5
4
|
from collections.abc import AsyncIterator, Mapping
|
|
6
5
|
from dataclasses import replace
|
|
7
6
|
from pathlib import Path
|
|
@@ -226,21 +225,13 @@ class Windcode:
|
|
|
226
225
|
def _resolve_state_root(self, explicit_root: Path | None) -> Path:
|
|
227
226
|
if explicit_root is not None:
|
|
228
227
|
return explicit_root.expanduser().resolve()
|
|
229
|
-
|
|
228
|
+
configured_project_root = self.config.storage.project_state_root
|
|
229
|
+
if configured_project_root is not None:
|
|
230
|
+
return self._configured_state_path(configured_project_root)
|
|
230
231
|
configured_user_root = self.config.storage.user_storage_root
|
|
231
|
-
|
|
232
|
-
self._configured_state_path(configured_user_root)
|
|
233
|
-
|
|
234
|
-
else legacy_root / "state"
|
|
235
|
-
)
|
|
236
|
-
source_root = user_root if user_root.exists() else legacy_root
|
|
237
|
-
configured = self.config.storage.project_state_root
|
|
238
|
-
if configured is None:
|
|
239
|
-
self._migrate_state_root(source_root, user_root)
|
|
240
|
-
return user_root
|
|
241
|
-
project_root = self._configured_state_path(configured)
|
|
242
|
-
self._migrate_state_root(source_root, project_root)
|
|
243
|
-
return project_root
|
|
232
|
+
if configured_user_root is not None:
|
|
233
|
+
return self._configured_state_path(configured_user_root)
|
|
234
|
+
return user_state_path("windcode").expanduser().resolve()
|
|
244
235
|
|
|
245
236
|
def _configured_state_path(self, value: str) -> Path:
|
|
246
237
|
project_root = Path(value).expanduser()
|
|
@@ -248,34 +239,6 @@ class Windcode:
|
|
|
248
239
|
project_root = self.workspace / project_root
|
|
249
240
|
return project_root.resolve()
|
|
250
241
|
|
|
251
|
-
@staticmethod
|
|
252
|
-
def _state_manifest(root: Path) -> dict[str, int]:
|
|
253
|
-
return {
|
|
254
|
-
str(path.relative_to(root)): path.stat().st_size
|
|
255
|
-
for path in root.rglob("*")
|
|
256
|
-
if path.is_file() and not path.is_symlink()
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
@classmethod
|
|
260
|
-
def _migrate_state_root(cls, source: Path, target: Path) -> None:
|
|
261
|
-
"""Copy the complete legacy state once, validate it, then atomically install it."""
|
|
262
|
-
if source == target or target.exists():
|
|
263
|
-
return
|
|
264
|
-
target.parent.mkdir(parents=True, exist_ok=True)
|
|
265
|
-
temporary_parent = source.parent if target.is_relative_to(source) else target.parent
|
|
266
|
-
temporary = temporary_parent / f".{target.name}.migrate-{uuid4().hex}"
|
|
267
|
-
try:
|
|
268
|
-
if source.exists():
|
|
269
|
-
shutil.copytree(source, temporary, copy_function=shutil.copy2)
|
|
270
|
-
if cls._state_manifest(source) != cls._state_manifest(temporary):
|
|
271
|
-
raise OSError("project state migration validation failed")
|
|
272
|
-
else:
|
|
273
|
-
temporary.mkdir(parents=True)
|
|
274
|
-
temporary.replace(target)
|
|
275
|
-
finally:
|
|
276
|
-
if temporary.exists():
|
|
277
|
-
shutil.rmtree(temporary)
|
|
278
|
-
|
|
279
242
|
@classmethod
|
|
280
243
|
def open(
|
|
281
244
|
cls,
|
|
@@ -16,7 +16,7 @@ from textual.theme import Theme
|
|
|
16
16
|
from textual.widgets import Static
|
|
17
17
|
|
|
18
18
|
from windcode.auth import CredentialStore, CredentialStoreError
|
|
19
|
-
from windcode.config import AppConfig, PermissionMode, ProviderConfig
|
|
19
|
+
from windcode.config import AppConfig, PermissionMode, ProviderConfig, default_user_config_path
|
|
20
20
|
from windcode.domain.events import (
|
|
21
21
|
ApprovalRequested,
|
|
22
22
|
ApprovalResponse,
|
|
@@ -86,9 +86,7 @@ class WindcodeApp(App[None]):
|
|
|
86
86
|
super().__init__()
|
|
87
87
|
self.config = config
|
|
88
88
|
self.workspace = workspace
|
|
89
|
-
self.config_file = (
|
|
90
|
-
(config_file or self.workspace / ".windcode" / "config.toml").expanduser().resolve()
|
|
91
|
-
)
|
|
89
|
+
self.config_file = (config_file or default_user_config_path()).expanduser().resolve()
|
|
92
90
|
self.model = model
|
|
93
91
|
self.session_id = session_id
|
|
94
92
|
self.permission_mode = permission_mode or config.permission.mode.value
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|