windcode 0.1.0__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.0 → windcode-0.1.2}/.gitignore +6 -0
- {windcode-0.1.0 → windcode-0.1.2}/.windcode/config.toml.example +2 -2
- {windcode-0.1.0 → windcode-0.1.2}/AGENTS.md +3 -2
- {windcode-0.1.0 → windcode-0.1.2}/PKG-INFO +6 -5
- {windcode-0.1.0 → windcode-0.1.2}/README.md +5 -4
- {windcode-0.1.0 → windcode-0.1.2}/examples/README.md +1 -1
- {windcode-0.1.0 → windcode-0.1.2}/pyproject.toml +1 -1
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/__init__.py +1 -1
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/cli.py +4 -3
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/config/__init__.py +8 -1
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/config/loader.py +27 -1
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/service.py +13 -2
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/sdk.py +6 -43
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/app.py +18 -7
- {windcode-0.1.0 → windcode-0.1.2}/uv.lock +1 -1
- {windcode-0.1.0 → windcode-0.1.2}/.github/workflows/publish.yml +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/.python-version +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/.windcode/skills/code-review/SKILL.md +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/.windcode/skills/code-review/agents/openai.yaml +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/LICENSE +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/assets/image.png +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/assets/image1.png +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/assets/image2.png +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/assets/image3.png +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/assets/image4.png +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/examples/01_stateful_chat.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/examples/02_custom_tool.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/examples/03_multi_agent.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/examples/04_mcp_tools.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/examples/05_skill.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/examples/06_stream_and_cancel.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/examples/07_bash_approval.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/examples/skills/release_notes/SKILL.md +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/__main__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/auth/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/auth/store.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/config/models.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/config/writer.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/context/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/context/compactor.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/context/estimator.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/context/truncation.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/domain/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/domain/errors.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/domain/events.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/domain/messages.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/domain/models.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/domain/subagents.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/domain/tools.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/commands.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/discovery.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/events.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/hooks/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/hooks/dispatcher.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/hooks/executor.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/hooks/loader.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/hooks/models.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/mcp/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/mcp/adapter.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/mcp/catalog.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/mcp/client.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/mcp/runtime.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/mcp/tools.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/models.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/paths.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/plugins/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/plugins/installer.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/plugins/manifest.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/runtime.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/skills/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/skills/loader.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/skills/parser.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/skills/tools.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/snapshot.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/extensions/state.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/instructions/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/instructions/loader.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/memory/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/memory/extraction.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/memory/models.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/memory/refiner.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/memory/security.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/memory/service.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/memory/store.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/observability/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/observability/redaction.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/observability/trace.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/policy/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/policy/engine.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/policy/models.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/providers/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/providers/_utils.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/providers/anthropic.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/providers/base.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/providers/catalog.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/providers/errors.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/providers/openai_compat.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/providers/openai_responses.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/providers/registry.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/control.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/event_bus.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/loop.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/prompts.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/report.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/retry.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/scheduler.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/subagents/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/subagents/approvals.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/subagents/budgets.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/subagents/coordinator.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/subagents/factory.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/subagents/roles.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/runtime/subagents/verification.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/sandbox/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/sandbox/bwrap.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/sessions/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/sessions/artifacts.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/sessions/models.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/sessions/store.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/sessions/tree.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/apply_patch.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/ask_user.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/builtins.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/edit_file.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/filesystem.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/glob.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/grep.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/memory.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/read_file.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/registry.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/shell.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/subagents/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/subagents/cancel.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/subagents/integrate.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/subagents/list.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/subagents/spawn.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/subagents/wait.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tools/write_file.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/commands.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/permission_display.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/styles.tcss +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/approval.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/command_menu.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/extensions.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/input.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/memory.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/messages.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/models.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/question.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/sessions.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/status.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/subagents.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/tools.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/tui/widgets/welcome.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/types.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/worktrees/__init__.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/worktrees/git.py +0 -0
- {windcode-0.1.0 → windcode-0.1.2}/src/windcode/worktrees/manager.py +0 -0
- {windcode-0.1.0 → 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
|
|
@@ -122,6 +120,13 @@ class WindcodeApp(App[None]):
|
|
|
122
120
|
return provider.model
|
|
123
121
|
return "按配置"
|
|
124
122
|
|
|
123
|
+
def _model_setup_message(self) -> str | None:
|
|
124
|
+
if self.client.transport_registry.aliases:
|
|
125
|
+
return None
|
|
126
|
+
if self.config.providers:
|
|
127
|
+
return "模型 Provider 尚未连接, 请检查 API Key 或重新配置 Provider"
|
|
128
|
+
return "尚未配置模型 Provider, 请先连接模型后再开始任务"
|
|
129
|
+
|
|
125
130
|
def _make_banner(self) -> RichText:
|
|
126
131
|
banner = RichText()
|
|
127
132
|
banner.append("▣ ", style="bold color(75)")
|
|
@@ -172,6 +177,8 @@ class WindcodeApp(App[None]):
|
|
|
172
177
|
self.set_class(self.size.width < 60, "narrow")
|
|
173
178
|
self.query_one("#chat-input", ChatInput).focus()
|
|
174
179
|
self._update_status("loading MCP" if self.client.required_mcp_loading else "idle")
|
|
180
|
+
if setup_message := self._model_setup_message():
|
|
181
|
+
self.query_one("#welcome-view", WelcomeView).show_notice(setup_message)
|
|
175
182
|
if self.client.required_mcp_loading:
|
|
176
183
|
self.query_one("#welcome-view", WelcomeView).start_mcp_loading()
|
|
177
184
|
self.run_worker(self._load_required_mcp(), group="mcp-startup", exclusive=True)
|
|
@@ -188,7 +195,7 @@ class WindcodeApp(App[None]):
|
|
|
188
195
|
return
|
|
189
196
|
self.query_one("#welcome-view", WelcomeView).stop_mcp_loading()
|
|
190
197
|
self._update_status("idle")
|
|
191
|
-
await self._show_system_message("MCP 服务已加载")
|
|
198
|
+
await self._show_system_message(self._model_setup_message() or "MCP 服务已加载")
|
|
192
199
|
|
|
193
200
|
def on_resize(self, event: events.Resize) -> None:
|
|
194
201
|
self.set_class(event.size.width < 60, "narrow")
|
|
@@ -230,7 +237,7 @@ class WindcodeApp(App[None]):
|
|
|
230
237
|
await self._show_system_message("没有可复制的内容", error=True)
|
|
231
238
|
return
|
|
232
239
|
try:
|
|
233
|
-
|
|
240
|
+
self.copy_to_clipboard(text)
|
|
234
241
|
preview = text.replace("\n", " ")[:60]
|
|
235
242
|
await self._show_system_message(
|
|
236
243
|
f"已复制到剪贴板: {preview}{'...' if len(text) > 60 else ''}"
|
|
@@ -458,6 +465,10 @@ class WindcodeApp(App[None]):
|
|
|
458
465
|
await self._start_prompt(value)
|
|
459
466
|
|
|
460
467
|
async def _start_prompt(self, value: str) -> None:
|
|
468
|
+
if setup_message := self._model_setup_message():
|
|
469
|
+
await self._show_system_message(setup_message, error=True)
|
|
470
|
+
await self._open_provider_manager()
|
|
471
|
+
return
|
|
461
472
|
if self.handle is not None and not self.handle.done:
|
|
462
473
|
self.prompt_queue.append(value)
|
|
463
474
|
self._update_status(f"运行中 · 队列 {len(self.prompt_queue)}")
|
|
@@ -1006,7 +1017,7 @@ class WindcodeApp(App[None]):
|
|
|
1006
1017
|
# If the user has selected text, copy it first instead of canceling.
|
|
1007
1018
|
selected = self.screen.get_selected_text()
|
|
1008
1019
|
if selected:
|
|
1009
|
-
|
|
1020
|
+
self.copy_to_clipboard(selected)
|
|
1010
1021
|
self.screen.clear_selection()
|
|
1011
1022
|
return
|
|
1012
1023
|
if self.handle is not None and not self.handle.done:
|
|
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
|